diff --git a/.changeset/twelve-shirts-tell.md b/.changeset/twelve-shirts-tell.md new file mode 100644 index 00000000..02cf964c --- /dev/null +++ b/.changeset/twelve-shirts-tell.md @@ -0,0 +1,5 @@ +--- +'sv': patch +--- + +feat: add `devtools-json` addon (using `vite-plugin-devtools-json`) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c4714b86..68f2687f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,7 @@ Run package specific tests by specifying a project flag to the package and runni pnpm test --project core # addons / create / migrate / etc. ``` -To run a individual test. `cd` into the package. Run the local `test` script to that package, with a path arg to the individual peice you want tested. Eg: +To run a individual test. `cd` into the package. Run the local `test` script to that package, with a path arg to the individual piece you want tested. Eg: ```bash pnpm test [path-to-test] ``` diff --git a/documentation/docs/20-commands/20-sv-add.md b/documentation/docs/20-commands/20-sv-add.md index d627a937..07f9f314 100644 --- a/documentation/docs/20-commands/20-sv-add.md +++ b/documentation/docs/20-commands/20-sv-add.md @@ -38,3 +38,4 @@ You can select multiple space-separated add-ons from [the list below](#Official- - [`sveltekit-adapter`](sveltekit-adapter) - [`tailwindcss`](tailwind) - [`vitest`](vitest) +- [`devtools-json`](devtools-json) diff --git a/documentation/docs/30-add-ons/60-devtools-json.md b/documentation/docs/30-add-ons/60-devtools-json.md new file mode 100644 index 00000000..6fabf641 --- /dev/null +++ b/documentation/docs/30-add-ons/60-devtools-json.md @@ -0,0 +1,21 @@ +--- +title: devtools-json +--- + +`devtools-json` is essentially a vite plugin [vite-plugin-devtools-json](https://github.com/ChromeDevTools/vite-plugin-devtools-json/) for generating the Chrome DevTools project settings file on-the-fly in the devserver. + +It will prevent this server log: + +```sh +Not found: /.well-known/appspecific/com.chrome.devtools.json +``` + +## Usage + +```bash +npx sv add devtools-json +``` + +## What you get + +- the `vite` plugin added to your vite plugin options. diff --git a/packages/addons/_config/official.ts b/packages/addons/_config/official.ts index 3ef21878..c143e06c 100644 --- a/packages/addons/_config/official.ts +++ b/packages/addons/_config/official.ts @@ -1,14 +1,15 @@ import type { AddonWithoutExplicitArgs } from '@sveltejs/cli-core'; +import devtoolsJson from '../devtools-json/index.ts'; import drizzle from '../drizzle/index.ts'; import eslint from '../eslint/index.ts'; -import sveltekitAdapter from '../sveltekit-adapter/index.ts'; import lucia from '../lucia/index.ts'; import mdsvex from '../mdsvex/index.ts'; import paraglide from '../paraglide/index.ts'; import playwright from '../playwright/index.ts'; import prettier from '../prettier/index.ts'; import storybook from '../storybook/index.ts'; +import sveltekitAdapter from '../sveltekit-adapter/index.ts'; import tailwindcss from '../tailwindcss/index.ts'; import vitest from '../vitest-addon/index.ts'; @@ -25,7 +26,8 @@ export const officialAddons = [ lucia, mdsvex, paraglide, - storybook + storybook, + devtoolsJson ] as AddonWithoutExplicitArgs[]; export function getAddonDetails(id: string): AddonWithoutExplicitArgs { diff --git a/packages/addons/_tests/devtools-json/test.ts b/packages/addons/_tests/devtools-json/test.ts new file mode 100644 index 00000000..3e2d82d0 --- /dev/null +++ b/packages/addons/_tests/devtools-json/test.ts @@ -0,0 +1,50 @@ +import { expect } from '@playwright/test'; +import { setupTest } from '../_setup/suite.ts'; +import devtoolsJson from '../../devtools-json/index.ts'; +import fs from 'node:fs'; +import path from 'node:path'; + +const { test, variants, prepareServer } = setupTest({ devtoolsJson } as { + devtoolsJson?: typeof devtoolsJson; +}); + +test.concurrent.for(variants)('default - %s', async (variant, { page, ...ctx }) => { + const cwd = await ctx.run(variant, { devtoolsJson: {} }); + + const { close } = await prepareServer({ cwd, page }); + // kill server process when we're done + ctx.onTestFinished(async () => await close()); + + const ext = variant.includes('ts') ? 'ts' : 'js'; + const viteFile = path.resolve(cwd, `vite.config.${ext}`); + const viteContent = fs.readFileSync(viteFile, 'utf8'); + + // Check if we have the import part + expect(viteContent).toContain(`import devtoolsJson from`); + expect(viteContent).toContain(`vite-plugin-devtools-json`); + + // Check if it's called + expect(viteContent).toContain(`devtoolsJson()`); +}); + +test.concurrent.for(variants)( + 'without the addon should be selected - %s', + async (variant, { page, ...ctx }) => { + const cwd = await ctx.run(variant, {}); + + const { close } = await prepareServer({ cwd, page }); + // kill server process when we're done + ctx.onTestFinished(async () => await close()); + + const ext = variant.includes('ts') ? 'ts' : 'js'; + const viteFile = path.resolve(cwd, `vite.config.${ext}`); + const viteContent = fs.readFileSync(viteFile, 'utf8'); + + // Check if we have the import part + expect(viteContent).toContain(`import devtoolsJson from`); + expect(viteContent).toContain(`vite-plugin-devtools-json`); + + // Check if it's called + expect(viteContent).toContain(`devtoolsJson()`); + } +); diff --git a/packages/addons/devtools-json/index.ts b/packages/addons/devtools-json/index.ts new file mode 100644 index 00000000..fe8ba6fe --- /dev/null +++ b/packages/addons/devtools-json/index.ts @@ -0,0 +1,40 @@ +import { defineAddon } from '@sveltejs/cli-core'; +import { array, functions, imports, object, exports } from '@sveltejs/cli-core/js'; +import { parseScript } from '@sveltejs/cli-core/parsers'; + +export default defineAddon({ + id: 'devtools-json', + shortDescription: 'devtools json', + homepage: 'https://github.com/ChromeDevTools/vite-plugin-devtools-json', + options: {}, + + setup: ({ defaultSelection }) => { + defaultSelection({ + create: true, + add: false + }); + }, + + run: ({ sv, typescript }) => { + const ext = typescript ? 'ts' : 'js'; + + sv.devDependency('vite-plugin-devtools-json', '^0.2.0'); + + // add the vite plugin + sv.file(`vite.config.${ext}`, (content) => { + const { ast, generateCode } = parseScript(content); + + const vitePluginName = 'devtoolsJson'; + imports.addDefault(ast, 'vite-plugin-devtools-json', vitePluginName); + + const { value: rootObject } = exports.defaultExport(ast, functions.call('defineConfig', [])); + const param1 = functions.argumentByIndex(rootObject, 0, object.createEmpty()); + + const pluginsArray = object.property(param1, 'plugins', array.createEmpty()); + const pluginFunctionCall = functions.call(vitePluginName, []); + array.push(pluginsArray, pluginFunctionCall); + + return generateCode(); + }); + } +}); diff --git a/packages/cli/commands/add/index.ts b/packages/cli/commands/add/index.ts index cc2d85b4..4121b696 100644 --- a/packages/cli/commands/add/index.ts +++ b/packages/cli/commands/add/index.ts @@ -201,15 +201,16 @@ export const add = new Command('add') common.runCommand(async () => { const selectedAddonIds = selectedAddons.map(({ id }) => id); - const { nextSteps } = await runAddCommand(options, selectedAddonIds); - if (nextSteps) p.note(nextSteps, 'Next steps', { format: (line) => line }); + const { nextSteps } = await runAddCommand(options, selectedAddonIds, 'add'); + if (nextSteps) p.note(nextSteps, 'Next steps', { format: (line: string) => line }); }); }); type SelectedAddon = { type: 'official' | 'community'; addon: AddonWithoutExplicitArgs }; export async function runAddCommand( options: Options, - selectedAddonIds: string[] + selectedAddonIds: string[], + from: 'create' | 'add' ): Promise<{ nextSteps?: string; packageManager?: AgentName | null }> { const selectedAddons: SelectedAddon[] = selectedAddonIds.map((id) => ({ type: 'official', @@ -390,6 +391,17 @@ export async function runAddCommand( const setups = selectedAddons.length ? selectedAddons.map(({ addon }) => addon) : officialAddons; const addonSetupResults = setupAddons(setups, workspace); + let initialValues: string[] = []; + if (from === 'create') { + initialValues = Object.entries(addonSetupResults) + .filter((c) => c[1].defaultSelection.create === true) + .map((c) => c[0]); + } else if (from === 'add') { + initialValues = Object.entries(addonSetupResults) + .filter((c) => c[1].defaultSelection.add === true) + .map((c) => c[0]); + } + // prompt which addons to apply if (selectedAddons.length === 0) { const addonOptions = officialAddons @@ -404,7 +416,8 @@ export async function runAddCommand( const selected = await p.multiselect({ message: `What would you like to add to your project? ${pc.dim('(use arrow keys / space bar)')}`, options: addonOptions, - required: false + required: false, + initialValues }); if (p.isCancel(selected)) { p.cancel('Operation cancelled.'); diff --git a/packages/cli/commands/create.ts b/packages/cli/commands/create.ts index 5d259c31..b7ffcaa6 100644 --- a/packages/cli/commands/create.ts +++ b/packages/cli/commands/create.ts @@ -184,7 +184,8 @@ async function createProject(cwd: ProjectPath, options: Options) { community: [], addons: {} }, - [] + [], + 'create' ); packageManager = pm; addOnNextSteps = nextSteps; diff --git a/packages/cli/lib/install.ts b/packages/cli/lib/install.ts index 958a8ee4..1ae3d874 100644 --- a/packages/cli/lib/install.ts +++ b/packages/cli/lib/install.ts @@ -87,7 +87,12 @@ export function setupAddons( const addonSetupResults: Record = {}; for (const addon of addons) { - const setupResult: AddonSetupResult = { unsupported: [], dependsOn: [], runsAfter: [] }; + const setupResult: AddonSetupResult = { + unsupported: [], + dependsOn: [], + runsAfter: [], + defaultSelection: { create: false, add: false } + }; addon.setup?.({ ...workspace, dependsOn: (name) => { @@ -95,7 +100,10 @@ export function setupAddons( setupResult.runsAfter.push(name); }, unsupported: (reason) => setupResult.unsupported.push(reason), - runsAfter: (name) => setupResult.runsAfter.push(name) + runsAfter: (name) => setupResult.runsAfter.push(name), + defaultSelection: (defaultSelection) => { + setupResult.defaultSelection = defaultSelection; + } }); addonSetupResults[addon.id] = setupResult; } diff --git a/packages/core/addon/config.ts b/packages/core/addon/config.ts index fbce3775..dd9daf08 100644 --- a/packages/core/addon/config.ts +++ b/packages/core/addon/config.ts @@ -38,6 +38,7 @@ export type Addon = { dependsOn: (name: string) => void; unsupported: (reason: string) => void; runsAfter: (addonName: string) => void; + defaultSelection: (args: { create: boolean; add: boolean }) => void; } ) => MaybePromise; run: (workspace: Workspace & { sv: SvApi }) => MaybePromise; @@ -60,7 +61,12 @@ export function defineAddon(config: Addon): return config; } -export type AddonSetupResult = { dependsOn: string[]; unsupported: string[]; runsAfter: string[] }; +export type AddonSetupResult = { + dependsOn: string[]; + unsupported: string[]; + runsAfter: string[]; + defaultSelection: { create: boolean; add: boolean }; +}; export type AddonWithoutExplicitArgs = Addon>; export type AddonConfigWithoutExplicitArgs = Addon>;