diff --git a/.changeset/hungry-numbers-stay.md b/.changeset/hungry-numbers-stay.md new file mode 100644 index 000000000..b6dbf6a35 --- /dev/null +++ b/.changeset/hungry-numbers-stay.md @@ -0,0 +1,5 @@ +--- +'sv': patch +--- + +feat(eslint): with `dbaeumer.vscode-eslint@3.0.20` ESLint extension, we don't need to set `eslint.validate` anymore diff --git a/packages/sv/lib/addons/drizzle/index.ts b/packages/sv/lib/addons/drizzle/index.ts index 437dac864..45625ca74 100644 --- a/packages/sv/lib/addons/drizzle/index.ts +++ b/packages/sv/lib/addons/drizzle/index.ts @@ -8,7 +8,13 @@ import { object, variables } from '../../core/tooling/js/index.ts'; -import { defineAddon, defineAddonOptions, dedent, type OptionValues } from '../../core/index.ts'; +import { + defineAddon, + defineAddonOptions, + dedent, + type OptionValues, + json +} from '../../core/index.ts'; import { parseJson, parseScript } from '../../core/tooling/parsers.ts'; import { resolveCommand } from 'package-manager-detector/commands'; import { getNodeTypesVersion } from '../common.ts'; @@ -186,13 +192,13 @@ export default defineAddon({ sv.file(files.package, (content) => { const { data, generateCode } = parseJson(content); - data.scripts ??= {}; - const scripts: Record = data.scripts; - if (options.docker) scripts['db:start'] ??= 'docker compose up'; - scripts['db:push'] ??= 'drizzle-kit push'; - scripts['db:generate'] ??= 'drizzle-kit generate'; - scripts['db:migrate'] ??= 'drizzle-kit migrate'; - scripts['db:studio'] ??= 'drizzle-kit studio'; + + if (options.docker) json.packageScriptsUpsert(data, 'db:start', 'docker compose up'); + json.packageScriptsUpsert(data, 'db:push', 'drizzle-kit push'); + json.packageScriptsUpsert(data, 'db:generate', 'drizzle-kit generate'); + json.packageScriptsUpsert(data, 'db:migrate', 'drizzle-kit migrate'); + json.packageScriptsUpsert(data, 'db:studio', 'drizzle-kit studio'); + return generateCode(); }); diff --git a/packages/sv/lib/addons/eslint/index.ts b/packages/sv/lib/addons/eslint/index.ts index 9580512f2..b3d9b58f4 100644 --- a/packages/sv/lib/addons/eslint/index.ts +++ b/packages/sv/lib/addons/eslint/index.ts @@ -1,4 +1,4 @@ -import { defineAddon, log } from '../../core/index.ts'; +import { defineAddon, log, json } from '../../core/index.ts'; import { array, common, @@ -34,22 +34,9 @@ export default defineAddon({ sv.file(files.package, (content) => { const { data, generateCode } = parseJson(content); - data.scripts ??= {}; - const scripts: Record = data.scripts; - const LINT_CMD = 'eslint .'; - scripts['lint'] ??= LINT_CMD; - if (!scripts['lint'].includes(LINT_CMD)) scripts['lint'] += ` && ${LINT_CMD}`; - return generateCode(); - }); - sv.file(files.vscodeSettings, (content) => { - if (!content) return content; + json.packageScriptsUpsert(data, 'lint', 'eslint .'); - const { data, generateCode } = parseJson(content); - const validate: string[] | undefined = data['eslint.validate']; - if (validate && !validate.includes('svelte')) { - validate.push('svelte'); - } return generateCode(); }); diff --git a/packages/sv/lib/addons/playwright/index.ts b/packages/sv/lib/addons/playwright/index.ts index 55bb603ae..4b2d830fc 100644 --- a/packages/sv/lib/addons/playwright/index.ts +++ b/packages/sv/lib/addons/playwright/index.ts @@ -1,4 +1,4 @@ -import { dedent, defineAddon, log } from '../../core/index.ts'; +import { dedent, defineAddon, json, log } from '../../core/index.ts'; import { common, exports, imports, object } from '../../core/tooling/js/index.ts'; import { parseJson, parseScript } from '../../core/tooling/parsers.ts'; @@ -12,13 +12,10 @@ export default defineAddon({ sv.file(files.package, (content) => { const { data, generateCode } = parseJson(content); - data.scripts ??= {}; - const scripts: Record = data.scripts; - const TEST_CMD = 'playwright test'; - const RUN_TEST = 'npm run test:e2e'; - scripts['test:e2e'] ??= TEST_CMD; - scripts['test'] ??= RUN_TEST; - if (!scripts['test'].includes(RUN_TEST)) scripts['test'] += ` && ${RUN_TEST}`; + + json.packageScriptsUpsert(data, 'test:e2e', 'playwright test'); + json.packageScriptsUpsert(data, 'test', 'npm run test:e2e'); + return generateCode(); }); diff --git a/packages/sv/lib/addons/prettier/index.ts b/packages/sv/lib/addons/prettier/index.ts index c8ec4bc58..224acfcb6 100644 --- a/packages/sv/lib/addons/prettier/index.ts +++ b/packages/sv/lib/addons/prettier/index.ts @@ -1,4 +1,4 @@ -import { dedent, defineAddon, log, colors } from '../../core/index.ts'; +import { dedent, defineAddon, log, colors, json } from '../../core/index.ts'; import { addEslintConfigPrettier } from '../common.ts'; import { parseJson } from '../../core/tooling/parsers.ts'; @@ -47,15 +47,10 @@ export default defineAddon({ data.printWidth = 100; } - data.plugins ??= []; - const plugins: string[] = data.plugins; - { - const PLUGIN_NAME = 'prettier-plugin-svelte'; - if (!plugins.includes(PLUGIN_NAME)) plugins.push(PLUGIN_NAME); - } + json.arrayUpsert(data, 'plugins', 'prettier-plugin-svelte'); + if (tailwindcssInstalled) { - const PLUGIN_NAME = 'prettier-plugin-tailwindcss'; - if (!plugins.includes(PLUGIN_NAME)) plugins.push(PLUGIN_NAME); + json.arrayUpsert(data, 'plugins', 'prettier-plugin-tailwindcss'); data.tailwindStylesheet ??= files.getRelative({ to: files.stylesheet }); } @@ -75,17 +70,10 @@ export default defineAddon({ sv.file(files.package, (content) => { const { data, generateCode } = parseJson(content); - data.scripts ??= {}; - const scripts: Record = data.scripts; - const CHECK_CMD = 'prettier --check .'; - scripts['format'] ??= 'prettier --write .'; + const cmd = `prettier --check .${eslintInstalled ? ` && eslint .` : ''}`; + json.packageScriptsUpsert(data, 'lint', cmd); + json.packageScriptsUpsert(data, 'format', 'prettier --write .'); - if (eslintInstalled) { - scripts['lint'] ??= `${CHECK_CMD} && eslint .`; - if (!scripts['lint'].includes(CHECK_CMD)) scripts['lint'] += ` && ${CHECK_CMD}`; - } else { - scripts['lint'] ??= CHECK_CMD; - } return generateCode(); }); diff --git a/packages/sv/lib/addons/tailwindcss/index.ts b/packages/sv/lib/addons/tailwindcss/index.ts index 3dec63c99..c50359895 100644 --- a/packages/sv/lib/addons/tailwindcss/index.ts +++ b/packages/sv/lib/addons/tailwindcss/index.ts @@ -1,4 +1,4 @@ -import { defineAddon, defineAddonOptions } from '../../core/index.ts'; +import { defineAddon, defineAddonOptions, json } from '../../core/index.ts'; import { imports, vite } from '../../core/tooling/js/index.ts'; import * as svelte from '../../core/tooling/svelte/index.ts'; import * as css from '../../core/tooling/css/index.ts'; @@ -126,10 +126,7 @@ export default defineAddon({ sv.file(files.prettierrc, (content) => { const { data, generateCode } = parseJson(content); - data.plugins ??= []; - const plugins: string[] = data.plugins; - const PLUGIN_NAME = 'prettier-plugin-tailwindcss'; - if (!plugins.includes(PLUGIN_NAME)) plugins.push(PLUGIN_NAME); + json.arrayUpsert(data, 'plugins', 'prettier-plugin-tailwindcss'); data.tailwindStylesheet ??= files.getRelative({ to: files.stylesheet }); return generateCode(); diff --git a/packages/sv/lib/addons/vitest-addon/index.ts b/packages/sv/lib/addons/vitest-addon/index.ts index 9cf9da6cd..80ac79795 100644 --- a/packages/sv/lib/addons/vitest-addon/index.ts +++ b/packages/sv/lib/addons/vitest-addon/index.ts @@ -1,4 +1,4 @@ -import { dedent, defineAddon, defineAddonOptions } from '../../core/index.ts'; +import { dedent, defineAddon, defineAddonOptions, json } from '../../core/index.ts'; import { array, imports, object, functions, vite } from '../../core/tooling/js/index.ts'; import { parseJson, parseScript } from '../../core/tooling/parsers.ts'; @@ -43,14 +43,11 @@ export default defineAddon({ sv.file(files.package, (content) => { const { data, generateCode } = parseJson(content); - data.scripts ??= {}; - const scripts: Record = data.scripts; - const TEST_CMD = 'vitest'; + + json.packageScriptsUpsert(data, 'test:unit', 'vitest'); // we use `--run` so that vitest doesn't run in watch mode when running `npm run test` - const RUN_TEST = 'npm run test:unit -- --run'; - scripts['test:unit'] ??= TEST_CMD; - scripts['test'] ??= RUN_TEST; - if (!scripts['test'].includes(RUN_TEST)) scripts['test'] += ` && ${RUN_TEST}`; + json.packageScriptsUpsert(data, 'test', 'npm run test:unit -- --run'); + return generateCode(); }); diff --git a/packages/sv/lib/cli/tests/snapshots/create-with-all-addons/package.json b/packages/sv/lib/cli/tests/snapshots/create-with-all-addons/package.json index 3c3f4f317..fba15278f 100644 --- a/packages/sv/lib/cli/tests/snapshots/create-with-all-addons/package.json +++ b/packages/sv/lib/cli/tests/snapshots/create-with-all-addons/package.json @@ -10,8 +10,8 @@ "prepare": "svelte-kit sync || echo ''", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "format": "prettier --write .", "lint": "prettier --check . && eslint .", + "format": "prettier --write .", "test:unit": "vitest", "test": "npm run test:unit -- --run && npm run test:e2e", "test:e2e": "playwright test", diff --git a/packages/sv/lib/core/index.ts b/packages/sv/lib/core/index.ts index bd89a09a1..e97ef9a3d 100644 --- a/packages/sv/lib/core/index.ts +++ b/packages/sv/lib/core/index.ts @@ -9,6 +9,14 @@ export type * from './addon/processors.ts'; export type * from './addon/options.ts'; export type * from './addon/config.ts'; export type * from './addon/workspace.ts'; +import { arrayUpsert, packageScriptsUpsert } from './tooling/json.ts'; +export const json: { + arrayUpsert: typeof arrayUpsert; + packageScriptsUpsert: typeof packageScriptsUpsert; +} = { + arrayUpsert, + packageScriptsUpsert +}; export { Walker } from './tooling/index.ts'; export * as js from './tooling/js/index.ts'; diff --git a/packages/sv/lib/core/tests/json.ts b/packages/sv/lib/core/tests/json.ts new file mode 100644 index 000000000..d8654e524 --- /dev/null +++ b/packages/sv/lib/core/tests/json.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from 'vitest'; +import { arrayUpsert, packageScriptsUpsert } from '../tooling/json.ts'; + +describe('arrayUpsert', () => { + it('append', () => { + const data = { a: ['b'] }; + arrayUpsert(data, 'a', 'c'); + expect(data).toEqual({ a: ['b', 'c'] }); + }); + + it('prepend', () => { + const data = { a: ['b'] }; + arrayUpsert(data, 'a', 'c', { mode: 'prepend' }); + expect(data).toEqual({ a: ['c', 'b'] }); + }); + + it('create', () => { + const data = { a: ['b'] }; + arrayUpsert(data, 'z', 'c'); + expect(data).toEqual({ a: ['b'], z: ['c'] }); + }); + + it('no change', () => { + const data = { a: ['b', 'c'] }; + arrayUpsert(data, 'a', 'b'); + expect(data).toEqual({ a: ['b', 'c'] }); + }); +}); + +describe('objectUpsert', () => { + it('create', () => { + const data = {}; + packageScriptsUpsert(data, 'lint', 'cmd'); + expect(data).toEqual({ scripts: { lint: 'cmd' } }); + }); + + it('add', () => { + const data = { scripts: { lint: 'b' } }; + packageScriptsUpsert(data, 'lint', 'cmd'); + expect(data).toEqual({ scripts: { lint: 'b && cmd' } }); + }); +}); diff --git a/packages/sv/lib/core/tooling/json.ts b/packages/sv/lib/core/tooling/json.ts new file mode 100644 index 000000000..a4baece88 --- /dev/null +++ b/packages/sv/lib/core/tooling/json.ts @@ -0,0 +1,29 @@ +export function arrayUpsert( + data: any, + key: string, + value: any, + options?: { + /** default: `append` */ + mode?: 'append' | 'prepend'; + } +): void { + const array = data[key] ?? []; + + if (array.includes(value)) return; + + if (options?.mode === 'prepend') { + array.unshift(value); + } else { + array.push(value); + } + data[key] = array; +} + +export function packageScriptsUpsert(data: any, key: string, value: any): void { + data.scripts ??= {}; + const scripts: Record = data.scripts; + scripts[key] ??= value; + if (!scripts[key].includes(value)) { + scripts[key] += ` && ${value}`; + } +}