diff --git a/documentation/docs/20-commands/20-sv-add.md b/documentation/docs/20-commands/20-sv-add.md index d4993651c..d8fc782e8 100644 --- a/documentation/docs/20-commands/20-sv-add.md +++ b/documentation/docs/20-commands/20-sv-add.md @@ -71,49 +71,20 @@ Prevents installing dependencies > [!NOTE] > Svelte maintainers have not reviewed community add-ons for malicious code! -You can find [community add-ons on npm](https://www.npmx.dev/search?q=keyword:sv-add) by searching for keyword: `sv-add`. - -### How to install a community add-on - -```sh -npx sv add [PROTOCOL][COMMUNITY_ADDON] -``` - -You can: - -- mix and match official and community add-ons -- use the interactive prompt or give args to the cli -- use the `--add` option in the `create` command - -```sh -npx sv add eslint "@supacool" -``` +Community add-ons are npm packages published by the community. Look out for add-ons from your favorite libraries and tools. _(soon)_ many are building `sv` add-ons to make integration a one-liner. You can find them [on npm](https://www.npmx.dev/search?q=keyword:sv-add) by searching for keyword: `sv-add`. ```sh -npx sv create --add eslint "@supacool" -``` - -### Package Protocols - -```sh -# Scoped package: @org (preferred), we will look for @org/sv -npx sv add "@supacool" - -# Regular npm package (with or without scope) -npx sv add my-cool-addon +# Install a community add-on by org +npx sv add @supacool -# Local add-on +# Use a local add-on (for development or custom/private add-ons) npx sv add file:../path/to/my-addon -``` - -### How to create a community add-on -To start on a good track, create your add-on with the `addon` template. +# Mix and match official and community add-ons +npx sv add eslint @supacool -```sh -npx sv create --template addon [path] +# Also works when creating a new project directly +npx sv create --add eslint @supacool ``` -In your new add-on directory, check out the `README.md` and `CONTRIBUTING.md` to get started. - -Then you can continue with the [API docs](/docs/cli/add-on) to start building your add-on. You can also have a look at the [official addons source code](https://github.com/sveltejs/cli/tree/main/packages/sv/src/addons) to get some inspiration on what can be done. +Want to create your own? Check the [Add-on Docs](community). diff --git a/documentation/docs/30-add-ons/16-better-auth.md b/documentation/docs/30-add-ons/02-better-auth.md similarity index 100% rename from documentation/docs/30-add-ons/16-better-auth.md rename to documentation/docs/30-add-ons/02-better-auth.md diff --git a/documentation/docs/30-add-ons/99-community.md b/documentation/docs/30-add-ons/99-community.md new file mode 100644 index 000000000..438d25fca --- /dev/null +++ b/documentation/docs/30-add-ons/99-community.md @@ -0,0 +1,222 @@ +--- +title: [create your own] +--- + +> [!NOTE] +> Community add-ons are currently **experimental**. The API may change. Don't use them in production yet! + +This guide covers how to create, test, and publish community add-ons for `sv`. + +## Quick start + +The easiest way to create an add-on is using the `addon` template: + +```sh +npx sv create --template addon [path] +``` + +The project has a `README.md` and `CONTRIBUTING.md` to guide you along. + +## Project structure + +Typically, an add-on looks like this: + +```js +// @noErrors +import { transforms } from '@sveltejs/sv-utils'; +import { defineAddon, defineAddonOptions } from 'sv'; + +// your add-on definition, the entry point +export default defineAddon({ + id: 'your-addon-name', + + // optional: one-liner shown in prompts + shortDescription: 'does X', + + // optional: link to docs/repo + homepage: 'https://...', + + // Define options for user prompts (or passed as arguments) + options: defineAddonOptions() + .add('who', { + question: 'To whom should the addon say hello?', + type: 'string' // boolean | number | select | multiselect + }) + .build(), + + // preparing step, check requirements and dependencies + setup: ({ dependsOn }) => { + dependsOn('tailwindcss'); + }, + + // actual execution of the addon + run: ({ isKit, cancel, sv, options, directory }) => { + if (!isKit) return cancel('SvelteKit is required'); + + // Add "Hello [who]!" to the root page + sv.file( + directory.kitRoutes + '/+page.svelte', + transforms.svelte(({ ast, svelte }) => { + svelte.addFragment(ast, `

Hello ${options.who}!

`); + }) + ); + } +}); +``` + +> `sv` is responsible for the file system - `sv.file()` accepts a `path` to the file and a callback function to modify it. +> `@sveltejs/sv-utils` is responsible for the content - `transforms.svelte()` provides you with the proper AST and utils to modify the file. See [sv-utils](/docs/cli/sv-utils) for the full API. + +## Development + +You can run your add-on locally using the `file:` protocol: + +```sh +cd /path/to/test-project +npx sv add file:../path/to/my-addon +``` + +This allows you to iterate quickly without publishing to npm. + +The `file:` protocol also works for custom or private add-ons that you don't intend to publish - for example, to standardize project setup across your team or organization. + +> [!NOTE] +> It is not necessary to build your add-on during development. + +## Testing + +The `sv/testing` module provides utilities for testing your add-on: + +```js +import { setupTest } from 'sv/testing'; +import { test, expect } from 'vitest'; +import addon from './index.js'; + +test('adds hello message', async () => { + const { content } = await setupTest({ + addon, + options: { who: 'World' }, + files: { + 'src/routes/+page.svelte': '

Welcome

' + } + }); + + expect(content('src/routes/+page.svelte')).toContain('Hello World!'); +}); +``` + +## Publishing + +### Bundling + +Community add-ons are bundled with [tsdown](https://tsdown.dev/) into a single file. Everything is bundled except `sv`. (It is a peer dependency provided at runtime.) + +### `package.json` + +Your add-on must have `sv` as a peer dependency and **no** `dependencies` in `package.json`: + +```jsonc +{ + "name": "@your-org/sv", + "version": "1.0.0", + "type": "module", + // entrypoint during development + "exports": { + ".": "./src/index.js" + }, + "publishConfig": { + "access": "public", + // entrypoint on build + "exports": { + ".": { "default": "./dist/index.js" } + } + }, + // cannot have dependencies + "dependencies": {}, + "peerDependencies": { + // minimum version required to run by this addon + "sv": "^0.13.0" + }, + // Add this keyword so users can discover your add-on + "keywords": ["sv-add"] +} +``` + +### Naming convention + +Name your package `@your-org/sv`. Users install it by typing just the org: + +```sh +# npm package: @your-org/sv +npx sv add @your-org +``` + +> [!NOTE] +> Unscoped packages are not supported yet + +### Export options + +`sv` first tries to import `your-package/sv`, then falls back to the default export. This means you have two options: + +1. **Default export** (recommended for dedicated add-on packages): + + ```json + { + "exports": { + ".": "./src/index.js" + } + } + ``` + +2. **`./sv` export** (for packages that also export other functionality): + ```json + { + "exports": { + ".": "./src/main.js", + "./sv": "./src/addon.js" + } + } + ``` + +### Publish to npm + +```sh +npm login +npm publish +``` + +> `prepublishOnly` automatically runs the build before publishing. + +## Next steps + +You can optionally display guidance in the console after your add-on runs: + +```js +// @noErrors +import { color } from '@sveltejs/sv-utils'; + +export default defineAddon({ + // ... + nextSteps: ({ options }) => [ + `Run ${color.command('npm run dev')} to start developing`, + `Check out the docs at https://...` + ] +}); +``` + +## Version compatibility + +Your add-on should specify a minimum `sv` version in `peerDependencies`. Your user will get a compatibility warning if their `sv` version has a different major version than what was specified. + +## Examples + +See the [official add-on source code](https://github.com/sveltejs/cli/tree/main/packages/sv/src/addons) for some real world examples. + +## Architecture + +The Svelte CLI is split into two packages with a clear boundary: + +- **`sv`** = **where and when** to do it. It owns paths, workspace detection, dependency tracking, and file I/O. The engine orchestrates add-on execution. +- **`@sveltejs/sv-utils`** = **what** to do to content. It provides parsers, language tooling, and typed transforms. Everything here is pure - no file system, no workspace awareness. + +This separation means transforms are testable without a workspace and composable across add-ons. diff --git a/documentation/docs/40-api/10-add-on.md b/documentation/docs/40-api/10-add-on.md deleted file mode 100644 index db4bc8ebc..000000000 --- a/documentation/docs/40-api/10-add-on.md +++ /dev/null @@ -1,192 +0,0 @@ ---- -title: add-on ---- - -> [!NOTE] -> Community add-ons are currently **experimental**. The API may change. Don't use them in production yet! - -This guide covers how to create, test, and publish community add-ons for `sv`. - -## Quick start - -The easiest way to create an add-on is using the addon template: - -```sh -npx sv create --template addon my-addon -``` - -## Add-on structure - -Typically, an add-on looks like this: - -```js -// @noErrors -import { transforms } from '@sveltejs/sv-utils'; -import { defineAddon, defineAddonOptions } from 'sv'; - -// Define options that will be prompted to the user (or passed as arguments) -const options = defineAddonOptions() - .add('who', { - question: 'To whom should the addon say hello?', - type: 'string' // boolean | number | select | multiselect - }) - .build(); - -// your add-on definition, the entry point -export default defineAddon({ - id: 'your-addon-name', - // shortDescription: 'does X', // optional: one-liner shown in prompts - // homepage: 'https://...', // optional: link to docs/repo - - options, - - // preparing step, check requirements and dependencies - setup: ({ dependsOn }) => { - dependsOn('tailwindcss'); - }, - - // actual execution of the addon - run: ({ isKit, cancel, sv, options, directory }) => { - if (!isKit) return cancel('SvelteKit is required'); - - // Add "Hello [who]!" to the root page - sv.file( - directory.routes + '/+page.svelte', - transforms.svelte(({ ast, svelte }) => { - svelte.addFragment(ast, `

Hello ${options.who}!

`); - }) - ); - } -}); -``` - -> `sv` owns the file system - `sv.file()` resolves the path, reads the file, applies the edit function, and writes the result. -> `@sveltejs/sv-utils` owns the content - `transforms.svelte()` returns a curried function that handles parsing, gives you the AST and utils, and serializes back. See [sv-utils](/docs/cli/sv-utils) for the full API. - -## Development with `file:` protocol - -While developing your add-on, you can test it locally using the `file:` protocol: - -```sh -# In your test project -npx sv add file:../path/to/my-addon -``` - -This allows you to iterate quickly without publishing to npm. - -## Testing with `sv/testing` - -The `sv/testing` module provides utilities for testing your add-on: - -```js -import { setupTest } from 'sv/testing'; -import { test, expect } from 'vitest'; -import addon from './index.js'; - -test('adds hello message', async () => { - const { content } = await setupTest({ - addon, - options: { who: 'World' }, - files: { - 'src/routes/+page.svelte': '

Welcome

' - } - }); - - expect(content('src/routes/+page.svelte')).toContain('Hello World!'); -}); -``` - -## Building and publishing - -### Bundling - -Community add-ons are bundled with [tsdown](https://tsdown.dev/) into a single file. Everything is bundled except `sv` (peer dependency, provided at runtime). - -```sh -npm run build -``` - -### Package structure - -Your add-on must have `sv` as a peer dependency and **no** `dependencies` in `package.json`: - -```json -{ - "name": "@your-org/sv", - "version": "1.0.0", - "type": "module", - "exports": { - ".": "./src/index.js" - }, - "publishConfig": { - "access": "public", - "exports": { - ".": { "default": "./dist/index.js" } - } - }, - "peerDependencies": { - "sv": "^0.13.0" - }, - "keywords": ["sv-add"] -} -``` - -- `exports` points to `./src/index.js` for local development with the `file:` protocol. -- `publishConfig.exports` overrides exports when publishing, pointing to the bundled `./dist/index.js`. - -> [!NOTE] -> Add the `sv-add` keyword so users can discover your add-on on npm. - -### Export options - -Your package can export the add-on in two ways: - -1. **Default export** (recommended for dedicated add-on packages): - - ```json - { - "exports": { - ".": "./src/index.js" - } - } - ``` - -2. **`/sv` export** (for packages that have other functionality): - ```json - { - "exports": { - ".": "./src/main.js", - "./sv": "./src/addon.js" - } - } - ``` - -### Publishing - -Community add-ons must be scoped packages (e.g. `@your-org/sv`). Users install with `npx sv add @your-org`. - -```sh -npm login -npm publish -``` - -> `prepublishOnly` automatically runs the build before publishing. - -## Next steps - -You can optionally display guidance after your add-on runs: - -```js -// @noErrors -export default defineAddon({ - // ... - nextSteps: ({ options }) => [ - `Run ${color.command('npm run dev')} to start developing`, - `Check out the docs at https://...` - ] -}); -``` - -## Version compatibility - -Your add-on should specify the minimum `sv` version it requires in `peerDependencies`. If a user's `sv` version has a different major version than what your add-on was built for, they will see a compatibility warning. diff --git a/documentation/docs/40-api/20-sv-utils.md b/documentation/docs/50-api/20-sv-utils.md similarity index 77% rename from documentation/docs/40-api/20-sv-utils.md rename to documentation/docs/50-api/20-sv-utils.md index daa0b82ac..4a714618d 100644 --- a/documentation/docs/40-api/20-sv-utils.md +++ b/documentation/docs/50-api/20-sv-utils.md @@ -5,29 +5,24 @@ title: sv-utils > [!NOTE] > `@sveltejs/sv-utils` is currently **experimental**. The API may change. Full documentation is not yet available. -`@sveltejs/sv-utils` provides utilities for parsing, transforming, and generating code in add-ons. +`@sveltejs/sv-utils` is an add-on utilty for parsing, transforming, and generating code.. ```sh npm install -D @sveltejs/sv-utils ``` -## Architecture +## transforms -The Svelte CLI is split into two packages with a clear boundary: - -- **`sv`** = **where and when** to do it. It owns paths, workspace detection, dependency tracking, and file I/O. The engine orchestrates add-on execution. -- **`@sveltejs/sv-utils`** = **what** to do to content. It provides parsers, language tooling, and typed transforms. Everything here is pure - no file system, no workspace awareness. - -This separation means transforms are testable without a workspace and composable across add-ons. - -## Transforms - -Transforms are curried, parser-aware functions that turn `string -> string`. Call a transform with your callback to get a function that plugs directly into `sv.file()`. The parser choice is baked into the transform type - you can't accidentally parse a vite config as Svelte because you never call a parser yourself. +`transforms` is a collection of parser-aware functions that lets you modify the files via abstract syntax tree (AST). It accepts a callback function. The return value is designed to be be passed directly into `sv.file()`. The parser choice is baked into the transform type - you can't accidentally parse a vite config as Svelte because you never call a parser yourself. Each transform injects relevant utilities into the callback, so you only need one import: ```js import { transforms } from '@sveltejs/sv-utils'; + +transforms.script(/* ... */); +transforms.svelte(/* ... */); +// ... ``` ### `transforms.script` @@ -73,7 +68,7 @@ import { transforms } from '@sveltejs/sv-utils'; sv.file( layoutPath, - transforms.svelteScript({ language }, ({ ast, svelte, js }) => { + transforms.svelteScript({ language: 'ts' }, ({ ast, svelte, js }) => { js.imports.addDefault(ast.instance.content, { as: 'Foo', from: './Foo.svelte' }); svelte.addFragment(ast, ''); }) @@ -161,22 +156,26 @@ Transforms are curried functions - call them with the callback, then apply to co ```js import { transforms } from '@sveltejs/sv-utils'; -const result = transforms.script(({ ast, js }) => { +const transform = transforms.script(({ ast, js }) => { js.imports.addDefault(ast, { as: 'foo', from: 'foo' }); -})('export default {}'); +}); +const result = transform('export default {}'); ``` ### Composability -For cases where you need to mix transforms and raw edits, use `sv.file` with a content callback and invoke the curried transform manually: +For cases where you need to mix and match transforms and raw edits, use `sv.file` with a content callback and invoke the curried transform manually: ```js // @noErrors sv.file(path, (content) => { // curried - content = transforms.script(({ ast, js }) => { + const transform = transforms.script(({ ast, js }) => { js.imports.addDefault(ast, { as: 'foo', from: 'bar' }); - })(content); + }); + + // parser manipulation + content = transform(content); // raw string manipulation content = content.replace('foo', 'baz'); @@ -198,9 +197,14 @@ export const addFooImport = transforms.svelte(({ ast, svelte, js }) => { }); ``` +```js +sv.file('+page.svelte', addFooImport); +sv.file('index.svelte', addFooImport); +``` + ## Parsers (low-level) -For cases where transforms don't fit (e.g., conditional parsing, error handling around the parser), the `parse` namespace is still available: +`transforms` will fit most users needs (e.g., conditional parsing, error handling around the parser). If not, `parse` is a low-level API available to you: ```js // @noErrors diff --git a/documentation/docs/40-api/index.md b/documentation/docs/50-api/index.md similarity index 100% rename from documentation/docs/40-api/index.md rename to documentation/docs/50-api/index.md