sv
sv exposes a programmatic API for creating projects and running add-ons.
defineAddon
Creates an add-on definition. See create your own for a full guide.
import { const transforms: {
script(cb: (file: {
ast: Program;
comments: Comments;
content: string;
js: typeof index_d_exports$3;
}) => void | false, options?: TransformOptions): (content: string) => string;
svelte(cb: (file: {
ast: AST.Root;
content: string;
svelte: typeof index_d_exports$4;
js: typeof index_d_exports$3;
}) => void | false, options?: TransformOptions): (content: string) => string;
... 6 more ...;
text(cb: (file: {
content: string;
text: typeof text_d_exports;
}) => string | false): TransformFn;
}
File transform primitives that know their format.
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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 are curried: call with the callback to get a (content: string) => string
function that plugs directly into sv.file().
transforms } from '@sveltejs/sv-utils';
import { function defineAddon<const Id extends string, Args extends OptionDefinition>(config: Addon<Args, Id>): Addon<Args, Id> (+1 overload)The entry point for your addon, It will hold every thing! (options, setup, run, nextSteps, ...)
For dynamic options added via addOption in setup, use the generic to get strong typing:
const addon = defineAddon<{ extra: boolean }>()({ ... });
addon.options.extra.default // boolean
defineAddon, function defineAddonOptions(): OptionBuilder<{}>Options for an addon.
Will be prompted to the user if there are not answered by args when calling the cli.
const options = defineAddonOptions()
.add('demo', {
question: `demo? ${color.optional('(a cool one!)')}`
type: string | boolean | number | select | multiselect,
default: true,
})
.build();
To define by args, you can do
npx sv add <addon>=<option1>:<value1>+<option2>:<value2>
defineAddonOptions } from 'sv';
export default defineAddon<"my-addon", {}>(config: Addon<{}, "my-addon">): Addon<{}, "my-addon"> (+1 overload)The entry point for your addon, It will hold every thing! (options, setup, run, nextSteps, ...)
For dynamic options added via addOption in setup, use the generic to get strong typing:
const addon = defineAddon<{ extra: boolean }>()({ ... });
addon.options.extra.default // boolean
defineAddon({
id: "my-addon"id: 'my-addon',
options: {}options: function defineAddonOptions(): OptionBuilder<{}>Options for an addon.
Will be prompted to the user if there are not answered by args when calling the cli.
const options = defineAddonOptions()
.add('demo', {
question: `demo? ${color.optional('(a cool one!)')}`
type: string | boolean | number | select | multiselect,
default: true,
})
.build();
To define by args, you can do
npx sv add <addon>=<option1>:<value1>+<option2>:<value2>
defineAddonOptions().function build(): {}build(),
// called before run - declare dependencies, environment requirements, and dynamic options
setup?: ((workspace: Workspace & {
dependsOn: (name: keyof OfficialAddons) => void;
unsupported: (reason: string) => void;
runsAfter: (name: keyof OfficialAddons) => void;
addOption: (key: string, question: Question) => void;
}) => MaybePromise<...>) | undefined
setup: ({ dependsOn: (name: keyof OfficialAddons) => voidOn what official addons does this addon depend on?
dependsOn, unsupported: (reason: string) => voidWhy is this addon not supported?
unsupported, addOption: (key: string, question: Question) => voidaddOption, isKit: booleanisKit }) => {
if (!isKit: booleanisKit) unsupported: (reason: string) => voidWhy is this addon not supported?
unsupported('Requires SvelteKit');
dependsOn: (name: keyof OfficialAddons) => voidOn what official addons does this addon depend on?
dependsOn('eslint');
// dynamically add options based on workspace state or fetched data
addOption: (key: string, question: Question) => voidaddOption('theme', {
question: stringquestion: 'Which theme?',
type: "select"type: 'select',
default: anydefault: 'dark',
options: {
value: any;
label?: string;
hint?: string;
}[]
options: [{ value: anyvalue: 'dark' }, { value: anyvalue: 'light' }]
});
},
// the actual work — add files, edit files, declare dependencies
run: (workspace: Workspace & {
options: OptionValues<{}> & Record<string, unknown>;
sv: SvApi;
cancel: (reason: string) => void;
}) => MaybePromise<void>
run: ({ sv: SvApisv, options: OptionValues<{}> & Record<string, unknown>Add-on options (includes dynamically added options from setup)
options, cancel: (reason: string) => voidCancel the addon at any time!
cancel }) => {
// add a dependency
sv: SvApisv.devDependency: (pkg: string, version: string) => voiddevDependency('my-lib', '^1.0.0');
// create or edit files using transforms from @sveltejs/sv-utils
sv: SvApisv.file: (path: string, edit: (content: string) => string | false) => voidEdit a file in the workspace. (will create it if it doesn't exist)
Return false from the callback to abort - the original content is returned unchanged.
file('src/lib/foo.ts', (content: stringcontent) => {
return 'export const foo = true;';
});
sv: SvApisv.file: (path: string, edit: (content: string) => string | false) => voidEdit a file in the workspace. (will create it if it doesn't exist)
Return false from the callback to abort - the original content is returned unchanged.
file(
'src/routes/+page.svelte',
const transforms: {
script(cb: (file: {
ast: Program;
comments: Comments;
content: string;
js: typeof index_d_exports$3;
}) => void | false, options?: TransformOptions): (content: string) => string;
svelte(cb: (file: {
ast: AST.Root;
content: string;
svelte: typeof index_d_exports$4;
js: typeof index_d_exports$3;
}) => void | false, options?: TransformOptions): (content: string) => string;
... 6 more ...;
text(cb: (file: {
content: string;
text: typeof text_d_exports;
}) => string | false): TransformFn;
}
File transform primitives that know their format.
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
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 are curried: call with the callback to get a (content: string) => string
function that plugs directly into sv.file().
transforms.function svelte(cb: (file: {
ast: AST.Root;
content: string;
svelte: typeof index_d_exports$4;
js: typeof index_d_exports$3;
}) => void | false, options?: TransformOptions): (content: string) => string
Transform a Svelte component file.
Return false from the callback to abort - the original content is returned unchanged.
svelte(({ ast: AST.Rootast, svelte: typeof index_d_exports$4svelte }) => {
svelte: typeof index_d_exports$4svelte.index_d_exports$4.addFragment(ast: AST.Root, content: string, options?: {
mode?: "append" | "prepend";
}): void
export index_d_exports$4.addFragment
addFragment(ast: AST.Rootast, '<p>Hello!</p>');
})
);
// cancel at any point if something is wrong
// cancel('reason');
},
// displayed after the add-on runs
nextSteps?: ((workspace: Workspace & {
options: OptionValues<{}> & Record<string, unknown>;
}) => string[]) | undefined
nextSteps: ({ options: OptionValues<{}> & Record<string, unknown>options }) => ['Run `npm run dev` to get started']
});The sv object in run provides file, dependency, devDependency, and execute. For file transforms (AST-based editing of scripts, Svelte components, CSS, JSON, etc.) and package manager helpers, see @sveltejs/sv-utils.
Typed dynamic options
If your add-on adds options dynamically in setup (e.g. from a fetch), you can pass a type parameter to defineAddon to get strong typing for those options:
const const addon: Addon<{} & SetupOptions<{
theme: string;
}>, "my-addon">
addon = defineAddon<{
theme: string;
}>(): <Id, Args>(config: Omit<Addon<Args & SetupOptions<{
theme: string;
}>, Id>, "options"> & {
options: Args;
}) => Addon<Args & SetupOptions<{
theme: string;
}>, Id> (+1 overload)
The entry point for your addon, It will hold every thing! (options, setup, run, nextSteps, ...)
For dynamic options added via addOption in setup, use the generic to get strong typing:
const addon = defineAddon<{ extra: boolean }>()({ ... });
addon.options.extra.default // boolean
defineAddon<{ theme: stringtheme: string }>()({
id: "my-addon"id: 'my-addon',
options: {}options: function defineAddonOptions(): OptionBuilder<{}>Options for an addon.
Will be prompted to the user if there are not answered by args when calling the cli.
const options = defineAddonOptions()
.add('demo', {
question: `demo? ${color.optional('(a cool one!)')}`
type: string | boolean | number | select | multiselect,
default: true,
})
.build();
To define by args, you can do
npx sv add <addon>=<option1>:<value1>+<option2>:<value2>
defineAddonOptions().function build(): {}build(),
setup?: ((workspace: Workspace & {
dependsOn: (name: keyof OfficialAddons) => void;
unsupported: (reason: string) => void;
runsAfter: (name: keyof OfficialAddons) => void;
addOption: (key: string, question: Question) => void;
}) => MaybePromise<...>) | undefined
setup: ({ addOption: (key: string, question: Question) => voidaddOption }) => {
addOption: (key: string, question: Question) => voidaddOption('theme', {
question: stringquestion: 'Which theme?',
type: "string"type: 'string',
default: stringdefault: 'dark'
});
},
run: (workspace: Workspace & {
options: OptionValues<{} & SetupOptions<{
theme: string;
}>> & Record<string, unknown>;
sv: SvApi;
cancel: (reason: string) => void;
}) => MaybePromise<void>
run: ({ options: OptionValues<{} & SetupOptions<{
theme: string;
}>> & Record<string, unknown>
Add-on options (includes dynamically added options from setup)
options }) => {
options: OptionValues<{} & SetupOptions<{
theme: string;
}>> & Record<string, unknown>
Add-on options (includes dynamically added options from setup)
options.theme: stringtheme; // string
}
});The type parameter maps value types (boolean, string, number) to question definitions. Without it, defineAddon stays strict and only allows statically defined options.
defineAddonOptions
Builder for add-on options. Chained with .add() and finalized with .build().
import { function defineAddonOptions(): OptionBuilder<{}>Options for an addon.
Will be prompted to the user if there are not answered by args when calling the cli.
const options = defineAddonOptions()
.add('demo', {
question: `demo? ${color.optional('(a cool one!)')}`
type: string | boolean | number | select | multiselect,
default: true,
})
.build();
To define by args, you can do
npx sv add <addon>=<option1>:<value1>+<option2>:<value2>
defineAddonOptions } from 'sv';
const const options: {
database: {
readonly question: "Which database?";
readonly type: "select";
readonly default: "postgresql";
readonly options: [{
readonly value: "postgresql";
}, {
readonly value: "mysql";
}, {
readonly value: "sqlite";
}];
};
docker: {
readonly question: "Add a docker-compose file?";
readonly type: "boolean";
readonly default: false;
readonly condition: (opts: OptionValues<Record<"database", {
readonly question: "Which database?";
readonly type: "select";
readonly default: "postgresql";
readonly options: [{
readonly value: "postgresql";
}, {
readonly value: "mysql";
}, {
readonly value: "sqlite";
}];
}> & Record<...>>) => boolean;
};
}
options = function defineAddonOptions(): OptionBuilder<{}>Options for an addon.
Will be prompted to the user if there are not answered by args when calling the cli.
const options = defineAddonOptions()
.add('demo', {
question: `demo? ${color.optional('(a cool one!)')}`
type: string | boolean | number | select | multiselect,
default: true,
})
.build();
To define by args, you can do
npx sv add <addon>=<option1>:<value1>+<option2>:<value2>
defineAddonOptions()
.add<"database", {
readonly question: "Which database?";
readonly type: "select";
readonly default: "postgresql";
readonly options: [{
readonly value: "postgresql";
}, {
readonly value: "mysql";
}, {
readonly value: "sqlite";
}];
}>(key: "database", question: {
readonly question: "Which database?";
readonly type: "select";
readonly default: "postgresql";
readonly options: [{
readonly value: "postgresql";
}, {
readonly value: "mysql";
}, {
readonly value: "sqlite";
}];
}): OptionBuilder<Record<"database", {
readonly question: "Which database?";
readonly type: "select";
readonly default: "postgresql";
readonly options: [{
readonly value: "postgresql";
}, {
readonly value: "mysql";
}, {
readonly value: "sqlite";
}];
}>>
This type is a bit complex, but in usage, it's quite simple!
The idea is to add() options one by one, with the key and the question.
.add('demo', {
question: 'Do you want to add a demo?',
type: 'boolean', // string, number, select, multiselect
default: true,
// condition: (o) => o.previousOption === 'ok',
})
add('database', {
question: "Which database?"question: 'Which database?',
type: "select"type: 'select',
default: "postgresql"default: 'postgresql',
options: [{
readonly value: "postgresql";
}, {
readonly value: "mysql";
}, {
readonly value: "sqlite";
}]
options: [
{ value: "postgresql"value: 'postgresql' },
{ value: "mysql"value: 'mysql' },
{ value: "sqlite"value: 'sqlite' }
]
})
.add<"docker", {
readonly question: "Add a docker-compose file?";
readonly type: "boolean";
readonly default: false;
readonly condition: (opts: OptionValues<Record<"database", {
readonly question: "Which database?";
readonly type: "select";
readonly default: "postgresql";
readonly options: [{
readonly value: "postgresql";
}, {
readonly value: "mysql";
}, {
readonly value: "sqlite";
}];
}> & Record<"docker", any>>) => boolean;
}>(key: "docker", question: {
readonly question: "Add a docker-compose file?";
readonly type: "boolean";
readonly default: false;
readonly condition: (opts: OptionValues<Record<"database", {
readonly question: "Which database?";
readonly type: "select";
readonly default: "postgresql";
readonly options: [{
readonly value: "postgresql";
}, {
readonly value: "mysql";
}, {
readonly value: "sqlite";
}];
}> & Record<"docker", any>>) => boolean;
}): OptionBuilder<...>
This type is a bit complex, but in usage, it's quite simple!
The idea is to add() options one by one, with the key and the question.
.add('demo', {
question: 'Do you want to add a demo?',
type: 'boolean', // string, number, select, multiselect
default: true,
// condition: (o) => o.previousOption === 'ok',
})
add('docker', {
question: "Add a docker-compose file?"question: 'Add a docker-compose file?',
type: "boolean"type: 'boolean',
default: falsedefault: false,
// only ask when database is not sqlite
condition: (opts: OptionValues<Record<"database", {
readonly question: "Which database?";
readonly type: "select";
readonly default: "postgresql";
readonly options: [{
readonly value: "postgresql";
}, {
readonly value: "mysql";
}, {
readonly value: "sqlite";
}];
}> & Record<"docker", any>>) => boolean
condition: (opts: OptionValues<Record<"database", {
readonly question: "Which database?";
readonly type: "select";
readonly default: "postgresql";
readonly options: [{
readonly value: "postgresql";
}, {
readonly value: "mysql";
}, {
readonly value: "sqlite";
}];
}> & Record<"docker", any>>
opts) => opts: OptionValues<Record<"database", {
readonly question: "Which database?";
readonly type: "select";
readonly default: "postgresql";
readonly options: [{
readonly value: "postgresql";
}, {
readonly value: "mysql";
}, {
readonly value: "sqlite";
}];
}> & Record<"docker", any>>
opts.database: "postgresql" | "mysql" | "sqlite"database !== 'sqlite'
})
.function build(): {
database: {
readonly question: "Which database?";
readonly type: "select";
readonly default: "postgresql";
readonly options: [{
readonly value: "postgresql";
}, {
readonly value: "mysql";
}, {
readonly value: "sqlite";
}];
};
docker: {
readonly question: "Add a docker-compose file?";
readonly type: "boolean";
readonly default: false;
readonly condition: (opts: OptionValues<Record<"database", {
readonly question: "Which database?";
readonly type: "select";
readonly default: "postgresql";
readonly options: [{
readonly value: "postgresql";
}, {
readonly value: "mysql";
}, {
readonly value: "sqlite";
}];
}> & Record<...>>) => boolean;
};
}
build();Options are asked in order. The condition callback receives the answers collected so far — return false to skip the question (its value will be undefined).
create
Programmatically create a new Svelte project.
import { function create(cwd: string, options: Options): voidcreate } from 'sv';
function create(cwd: string, options: Options): voidcreate('./my-app', {
name: stringname: 'my-app',
template: "minimal" | "demo" | "library" | "addon" | "svelte"template: 'minimal',
types: "typescript" | "checkjs" | "none"types: 'typescript'
});add
Programmatically run add-ons against an existing project.
import { function add<Addons extends AddonMap>({ addons, cwd, options, packageManager }: InstallOptions<Addons>): Promise<ReturnType<({ loadedAddons, workspace, setupResults, options }: ApplyAddonOptions) => Promise<{
filesToFormat: string[];
pnpmBuildDependencies: string[];
status: Record<string, string[] | "success">;
}>>>
add, const officialAddons: OfficialAddonsofficialAddons } from 'sv';
await add<{
prettier: Addon<any, string>;
}>({ addons, cwd, options, packageManager }: InstallOptions<{
prettier: Addon<any, string>;
}>): Promise<ReturnType<({ loadedAddons, workspace, setupResults, options }: ApplyAddonOptions) => Promise<{
filesToFormat: string[];
pnpmBuildDependencies: string[];
status: Record<string, string[] | "success">;
}>>>
add({
cwd: stringcwd: './my-app',
addons: {
prettier: Addon<any, string>;
}
addons: { prettier: Addon<any, string>prettier: const officialAddons: OfficialAddonsofficialAddons.prettier: Addon<any, string>prettier },
options: OptionMap<{
prettier: Addon<any, string>;
}>
options: { prettier: {}prettier: {} },
packageManager?: AgentName | undefinedpackageManager: 'npm'
});Edit this page on GitHub llms.txt