diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..4fd3f42
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,40 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Use Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "22"
+ cache: "npm"
+
+ - name: Cache node modules
+ uses: actions/cache@v4
+ with:
+ path: node_modules
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run ESLint
+ run: npm run eslint
+
+ - name: Run lint
+ run: npm run lint
+
+ - name: Run build
+ run: npm run build
diff --git a/package-lock.json b/package-lock.json
index 1a38341..bf8744f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,8 @@
"dependencies": {
"@raycast/api": "^1.55.2",
"@raycast/utils": "^1.8.0",
- "prettier": "^2.8.8"
+ "prettier": "^2.8.8",
+ "sql-formatter": "^15.6.2"
},
"devDependencies": {
"@raycast/eslint-config": "1.0.5",
@@ -719,6 +720,12 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
+ "node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "license": "MIT"
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -803,6 +810,12 @@
"node": ">=8"
}
},
+ "node_modules/discontinuous-range": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
+ "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==",
+ "license": "MIT"
+ },
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -1472,6 +1485,12 @@
"node": "*"
}
},
+ "node_modules/moo": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
+ "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -1490,6 +1509,28 @@
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
"dev": true
},
+ "node_modules/nearley": {
+ "version": "2.20.1",
+ "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz",
+ "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^2.19.0",
+ "moo": "^0.5.0",
+ "railroad-diagrams": "^1.0.0",
+ "randexp": "0.4.6"
+ },
+ "bin": {
+ "nearley-railroad": "bin/nearley-railroad.js",
+ "nearley-test": "bin/nearley-test.js",
+ "nearley-unparse": "bin/nearley-unparse.js",
+ "nearleyc": "bin/nearleyc.js"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://nearley.js.org/#give-to-nearley"
+ }
+ },
"node_modules/node-fetch": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
@@ -1655,6 +1696,25 @@
}
]
},
+ "node_modules/railroad-diagrams": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
+ "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==",
+ "license": "CC0-1.0"
+ },
+ "node_modules/randexp": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
+ "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "discontinuous-range": "1.0.0",
+ "ret": "~0.1.10"
+ },
+ "engines": {
+ "node": ">=0.12"
+ }
+ },
"node_modules/react": {
"version": "18.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz",
@@ -1711,6 +1771,15 @@
"node": ">=4"
}
},
+ "node_modules/ret": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12"
+ }
+ },
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -1846,6 +1915,25 @@
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true
},
+ "node_modules/sql-formatter": {
+ "version": "15.6.2",
+ "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.6.2.tgz",
+ "integrity": "sha512-ZjqOfJGuB97UeHzTJoTbadlM0h9ynehtSTHNUbGfXR4HZ4rCIoD2oIW91W+A5oE76k8hl0Uz5GD8Sx3Pt9Xa3w==",
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1",
+ "nearley": "^2.20.1"
+ },
+ "bin": {
+ "sql-formatter": "bin/sql-formatter-cli.cjs"
+ }
+ },
+ "node_modules/sql-formatter/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "license": "Python-2.0"
+ },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
diff --git a/package.json b/package.json
index 6c21c17..1662cc9 100644
--- a/package.json
+++ b/package.json
@@ -5,9 +5,7 @@
"description": "Format code on the clipboard",
"icon": "icon.png",
"author": "ikaraszi",
- "categories": [
- "Developer Tools"
- ],
+ "categories": ["Developer Tools"],
"license": "MIT",
"commands": [
{
@@ -105,6 +103,12 @@
"title": "Format YAML",
"description": "Formats YAML on the clipboard",
"mode": "view"
+ },
+ {
+ "name": "format-sql",
+ "title": "Format SQL",
+ "description": "Formats SQL on the clipboard",
+ "mode": "view"
}
],
"preferences": [
@@ -115,12 +119,41 @@
"description": "Specify the line length that the printer will wrap on.",
"placeholder": "100",
"required": false
+ },
+ {
+ "name": "sqlDialect",
+ "title": "SQL dialect",
+ "type": "dropdown",
+ "description": "Set the SQL dialect",
+ "data": [
+ { "value": "sql", "title": "Standard SQL" },
+ { "value": "bigquery", "title": "GCP BigQuery" },
+ { "value": "db2", "title": "IBM DB2" },
+ { "value": "db2i", "title": "IBM DB2i (experimental)" },
+ { "value": "duckdb", "title": "DuckDB" },
+ { "value": "hive", "title": "Apache Hive" },
+ { "value": "mariadb", "title": "MariaDB" },
+ { "value": "mysql", "title": "MySQL" },
+ { "value": "tidb", "title": "TiDB" },
+ { "value": "n1ql", "title": "Couchbase N1QL" },
+ { "value": "plsql", "title": "Oracle PL/SQL" },
+ { "value": "postgresql", "title": "PostgreSQL" },
+ { "value": "redshift", "title": "Amazon Redshift" },
+ { "value": "singlestoredb", "title": "SingleStoreDB" },
+ { "value": "snowflake", "title": "Snowflake" },
+ { "value": "spark", "title": "Spark" },
+ { "value": "sqlite", "title": "SQLite" },
+ { "value": "transactsql", "title": "SQL Server Transact-SQL" },
+ { "value": "trino", "title": "Trino / Presto" }
+ ],
+ "required": false
}
],
"dependencies": {
"@raycast/api": "^1.55.2",
"@raycast/utils": "^1.8.0",
- "prettier": "^2.8.8"
+ "prettier": "^2.8.8",
+ "sql-formatter": "^15.6.2"
},
"devDependencies": {
"@raycast/eslint-config": "1.0.5",
@@ -134,6 +167,7 @@
"build": "ray build -e dist",
"dev": "ray develop",
"fix-lint": "ray lint --fix",
+ "eslint": "eslint src",
"lint": "ray lint",
"publish": "npx @raycast/api@latest publish"
}
diff --git a/src/format-angular.ts b/src/format-angular.ts
index d36bd45..a73a13a 100644
--- a/src/format-angular.ts
+++ b/src/format-angular.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.angular;
+export default prettier.angular;
diff --git a/src/format-css.ts b/src/format-css.ts
index 0f52733..a7cb2dc 100644
--- a/src/format-css.ts
+++ b/src/format-css.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.css;
+export default prettier.css;
diff --git a/src/format-glimmer.ts b/src/format-glimmer.ts
index b35330a..90138ad 100644
--- a/src/format-glimmer.ts
+++ b/src/format-glimmer.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.glimmer;
+export default prettier.glimmer;
diff --git a/src/format-graphql.ts b/src/format-graphql.ts
index 0cdebe9..cc59ffe 100644
--- a/src/format-graphql.ts
+++ b/src/format-graphql.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.graphql;
+export default prettier.graphql;
diff --git a/src/format-html.ts b/src/format-html.ts
index 813b5cf..1d0bcdd 100644
--- a/src/format-html.ts
+++ b/src/format-html.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.html;
+export default prettier.html;
diff --git a/src/format-javascript.ts b/src/format-javascript.ts
index 952e9ec..9335b49 100644
--- a/src/format-javascript.ts
+++ b/src/format-javascript.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.javascript;
+export default prettier.javascript;
diff --git a/src/format-json.ts b/src/format-json.ts
index 1082e4e..4618d97 100644
--- a/src/format-json.ts
+++ b/src/format-json.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.json;
+export default prettier.json;
diff --git a/src/format-json5.ts b/src/format-json5.ts
index 8b4ebb9..a656b29 100644
--- a/src/format-json5.ts
+++ b/src/format-json5.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.json5;
+export default prettier.json5;
diff --git a/src/format-less.ts b/src/format-less.ts
index 6ac9aa3..b9a3c29 100644
--- a/src/format-less.ts
+++ b/src/format-less.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.less;
+export default prettier.less;
diff --git a/src/format-lwc.ts b/src/format-lwc.ts
index 0d66544..f29c453 100644
--- a/src/format-lwc.ts
+++ b/src/format-lwc.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.lwc;
+export default prettier.lwc;
diff --git a/src/format-markdown.ts b/src/format-markdown.ts
index 980da5d..5ca4ab4 100644
--- a/src/format-markdown.ts
+++ b/src/format-markdown.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.markdown;
+export default prettier.markdown;
diff --git a/src/format-mdx.ts b/src/format-mdx.ts
index 6d8f0fe..7f56987 100644
--- a/src/format-mdx.ts
+++ b/src/format-mdx.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.mdx;
+export default prettier.mdx;
diff --git a/src/format-scss.ts b/src/format-scss.ts
index ebf97df..df3ec8f 100644
--- a/src/format-scss.ts
+++ b/src/format-scss.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.scss;
+export default prettier.scss;
diff --git a/src/format-sql.tsx b/src/format-sql.tsx
new file mode 100644
index 0000000..9003e8c
--- /dev/null
+++ b/src/format-sql.tsx
@@ -0,0 +1,18 @@
+import { format } from "sql-formatter";
+
+import { Format } from "./format";
+import { getPreferences } from "./preferences";
+import { Formatter } from "./types";
+
+const { sqlDialect } = getPreferences();
+
+const formatter: Formatter = async (input) => {
+ return format(input, {
+ language: sqlDialect,
+ functionCase: "upper",
+ dataTypeCase: "lower",
+ keywordCase: "upper",
+ });
+};
+
+export default () => ;
diff --git a/src/format-typescript.ts b/src/format-typescript.ts
index cc58383..6c8657c 100644
--- a/src/format-typescript.ts
+++ b/src/format-typescript.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.typescript;
+export default prettier.typescript;
diff --git a/src/format-vue.ts b/src/format-vue.ts
index 9a08979..6fa906e 100644
--- a/src/format-vue.ts
+++ b/src/format-vue.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.vue;
+export default prettier.vue;
diff --git a/src/format-yaml.ts b/src/format-yaml.ts
index 8a24a95..95109d2 100644
--- a/src/format-yaml.ts
+++ b/src/format-yaml.ts
@@ -1,3 +1,3 @@
-import format from "./format";
+import prettier from "./prettier";
-export default format.yaml;
+export default prettier.yaml;
diff --git a/src/format.tsx b/src/format.tsx
index 4819f1c..2e437c0 100644
--- a/src/format.tsx
+++ b/src/format.tsx
@@ -3,44 +3,17 @@ import { usePromise } from "@raycast/utils";
import { useRef } from "react";
import { pipeline } from "./pipeline";
-import { Language, Parser } from "./types";
+import { Formatter, Language } from "./types";
-const supportedLanguages: Record = {
- angular: "angular",
- css: "css",
- glimmer: "glimmer",
- graphql: "graphql",
- html: "html",
- javascript: "babel",
- json: "json",
- json5: "json5",
- less: "less",
- lwc: "lwc",
- markdown: "markdown",
- mdx: "mdx",
- scss: "scss",
- typescript: "typescript",
- vue: "vue",
- yaml: "yaml",
-};
-
-type CommandProps = {
+type FormatProps = {
language: Language;
- parser: Parser;
+ formatter: Formatter;
};
-function Format({ language, parser }: CommandProps) {
+export function Format({ language, formatter }: FormatProps) {
const abortable = useRef();
- const { isLoading, data: markdown } = usePromise(pipeline, [language, parser], { abortable });
+ const { isLoading, data: markdown } = usePromise(pipeline, [language, formatter], { abortable });
return ;
}
-
-const formatterEntries = Object.entries(supportedLanguages).map(([language, parser]) => {
- return [language, () => ];
-});
-
-const formatters = Object.fromEntries(formatterEntries) as Record;
-
-export default formatters;
diff --git a/src/pipeline.ts b/src/pipeline.ts
index 8984c34..02a3f6c 100644
--- a/src/pipeline.ts
+++ b/src/pipeline.ts
@@ -1,17 +1,9 @@
-import { Clipboard, getPreferenceValues } from "@raycast/api";
-import { format } from "prettier";
+import { Clipboard } from "@raycast/api";
-import { Language, Parser, Preferences } from "./types";
+import { Formatter, Language } from "./types";
const readFromClipboard = async (): Promise => (await Clipboard.readText()) || "";
-const formatCode = (parser: Parser) => {
- const preferences = getPreferenceValues();
- const printWidth = parseInt(preferences.printWidth || "100", 10);
-
- return async (query: string): Promise => format(query, { parser, printWidth });
-};
-
const copyToClipboard = async (code: string): Promise => {
await Clipboard.copy(code);
return code;
@@ -21,6 +13,6 @@ const wrapWithCodeblock = (language: Language) => {
return (code: string) => ["```", " ", language, "\n", code, "\n", "``` "].join("");
};
-export const pipeline = async (language: Language, parser: Parser): Promise => {
- return readFromClipboard().then(formatCode(parser)).then(copyToClipboard).then(wrapWithCodeblock(language));
+export const pipeline = async (language: Language, formatter: Formatter): Promise => {
+ return readFromClipboard().then(formatter).then(copyToClipboard).then(wrapWithCodeblock(language));
};
diff --git a/src/preferences.ts b/src/preferences.ts
new file mode 100644
index 0000000..5e83a55
--- /dev/null
+++ b/src/preferences.ts
@@ -0,0 +1,25 @@
+import { getPreferenceValues } from "@raycast/api";
+import { SqlLanguage } from "sql-formatter";
+
+type RawPreferences = {
+ printWidth?: string;
+ sqlDialect?: string;
+};
+
+export type Preferences = {
+ printWidth: number;
+ sqlDialect: SqlLanguage;
+};
+
+export const getPreferences = (): Preferences => {
+ const rawPreferences = getPreferenceValues();
+
+ const printWidth = parseInt(rawPreferences.printWidth || "100", 10);
+ const rawDialect = rawPreferences.sqlDialect || "sql";
+ const sqlDialect = (rawDialect || "sql") as SqlLanguage;
+
+ return {
+ printWidth,
+ sqlDialect,
+ };
+};
diff --git a/src/prettier.tsx b/src/prettier.tsx
new file mode 100644
index 0000000..88ebe44
--- /dev/null
+++ b/src/prettier.tsx
@@ -0,0 +1,43 @@
+import { format, BuiltInParserName } from "prettier";
+
+import { Format } from "./format";
+import { getPreferences } from "./preferences";
+import { Formatter, Language } from "./types";
+
+type Parser = BuiltInParserName;
+type PrettierLanguage = Exclude;
+
+const prettierParsers: Record = {
+ angular: "angular",
+ css: "css",
+ glimmer: "glimmer",
+ graphql: "graphql",
+ html: "html",
+ javascript: "babel",
+ json5: "json5",
+ json: "json",
+ less: "less",
+ lwc: "lwc",
+ markdown: "markdown",
+ mdx: "mdx",
+ scss: "scss",
+ typescript: "typescript",
+ vue: "vue",
+ yaml: "yaml",
+};
+
+const { printWidth } = getPreferences();
+
+const buildFormatter = (parser: Parser): Formatter => {
+ return async (query) => format(query, { parser, printWidth });
+};
+
+const formatterEntries = Object.entries(prettierParsers).map(([language, parser]) => {
+ const formatter = buildFormatter(parser);
+
+ return [language, () => ];
+});
+
+const formatters = Object.fromEntries(formatterEntries) as Record;
+
+export default formatters;
diff --git a/src/types.ts b/src/types.ts
index 2a1dc6b..437a7b3 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,6 +1,4 @@
-import { BuiltInParserName } from "prettier";
-
-export type Parser = BuiltInParserName;
+export type Formatter = (query: string) => Promise;
export type Language =
| "angular"
@@ -16,10 +14,7 @@ export type Language =
| "markdown"
| "mdx"
| "scss"
+ | "sql"
| "typescript"
| "vue"
| "yaml";
-
-export type Preferences = {
- printWidth: string;
-};