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 | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
If your add-on adds dynamic options via addOption during setup, pass their
types as a type argument:
constaddon=defineAddon<{extra:boolean}>()({...});// Take note of the extra call here: 👆👆// This works around Typescript's lack of partial type argumentsaddon.options.extra.default;// boolean
If your add-on adds dynamic options via addOption during setup, pass their
types as a type argument:
constaddon=defineAddon<{extra:boolean}>()({...});// Take note of the extra call here: 👆👆// This works around Typescript's lack of partial type argumentsaddon.options.extra.default;// boolean
().build(method)build():{}(), // called before run - declare dependencies, environment requirements, and dynamic options
setup(property)setup?:((workspace:Workspace&{dependsOn:(name:keyofOfficialAddons)=>void;unsupported:(reason:string)=>void;runsAfter:(name:keyofOfficialAddons)=>void;addOption:<Kextendsstring>(key:K,question:BaseQuestion<any>&Question<any>)=>void;})=>MaybePromise<void>)|undefined:({dependsOn(parameter)dependsOn:(name:keyofOfficialAddons)=>void
On what official addons does this addon depend on?
On what official addons does this addon depend on?
('eslint'); // dynamically add options based on workspace state or fetched data
addOption(parameter)addOption:<"theme">(key:"theme",question:BaseQuestion<any>&Question<any>)=>void('theme',{question(property)question:string:'Which theme?',type(property)type:"select":'select',default(property)default:any:'dark',options(property)options:{value:any;label?:string;hint?:string;}[]:[{value(property)value:any:'dark'},{value(property)value:any:'light'}]});},// the actual work — add files, edit files, declare dependenciesrun(property)run:(workspace:Workspace&{options:OptionValues<{}>&Record<string,unknown>;sv:SvApi;cancel:(reason:string)=>void;})=>MaybePromise<void>:({sv(parameter)sv:SvApi,options(parameter)options:OptionValues<{}>&Record<string,unknown>
Add-on options (includes dynamically added options from setup)
,cancel(parameter)cancel:(reason:string)=>void
Cancel the addon at any time!
})=>{ // add a dependency
sv(parameter)sv:SvApi.devDependency(property)devDependency:(pkg:string,version:string)=>void('my-lib','^1.0.0'); // create or edit files using transforms from @sveltejs/sv-utils
sv(parameter)sv:SvApi.file(property)file:(path:string,edit:FileEdit)=>void
Edit 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.
Edits matching files in the workspace.
The include and exclude patterns are glob patterns relative to the workspace root.
For each matching file, the edit callback is called with the file content,
and should return the new content (or false to abort editing that file).
Note: always adds excludes for node_modules and dot-prefixed directories
Returns true if searchString appears as a substring of the result of converting this
object to a String, at one or more positions that are
greater than or equal to position; otherwise, returns false.
searchString
search string
position
If position is undefined, 0 is assumed, so as to search all of the String.
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 | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
(({ast(parameter)ast:AST.Root,svelte(parameter)svelte:typeofindex_d_exports$4})=>{svelte(parameter)svelte:typeofindex_d_exports$4.addFragment(alias)index_d_exports$4.addFragment(ast:AST.Root,content:string,options?:{mode?:"append"|"prepend";language?:"ts"|"js";}):voidexportindex_d_exports$4.addFragment(ast(parameter)ast:AST.Root,'<p>Hello!</p>');})); // cancel at any point if something is wrong
// cancel('reason');
}, // displayed after the add-on runs
nextSteps(property)nextSteps?:((workspace:Workspace&{options:OptionValues<{}>&Record<string,unknown>;})=>string[])|undefined:({options(parameter)options:OptionValues<{}>&Record<string,unknown>})=>['Run `npm run dev` to get started']});
The sv object in run provides file, files, removeFile, 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:
If your add-on adds dynamic options via addOption during setup, pass their
types as a type argument:
constaddon=defineAddon<{extra:boolean}>()({...});// Take note of the extra call here: 👆👆// This works around Typescript's lack of partial type argumentsaddon.options.extra.default;// boolean
Add-on options (includes dynamically added options from setup)
.theme(property)theme:string;// string}});
The type parameter maps value types (boolean, string, number) to question definitions. Without it, defineAddon stays strict and only allows statically defined options.
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:
If your add-on adds dynamic options via addOption during setup, pass their
types as a type argument:
constaddon=defineAddon<{extra:boolean}>()({...});// Take note of the extra call here: 👆👆// This works around Typescript's lack of partial type argumentsaddon.options.extra.default;// boolean
Add-on options (includes dynamically added options from setup)
.theme(property)theme:string;// string}});
The type parameter maps value types (boolean, string, number) to question definitions. Without it, defineAddon stays strict and only allows statically defined options.
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:
If your add-on adds dynamic options via addOption during setup, pass their
types as a type argument:
constaddon=defineAddon<{extra:boolean}>()({...});// Take note of the extra call here: 👆👆// This works around Typescript's lack of partial type argumentsaddon.options.extra.default;// boolean
Add-on options (includes dynamically added options from setup)
.theme(property)theme:string;// 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().
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, multiselectdefault:true,// condition: (o) => o.previousOption === 'ok',})
('database',{question(property)question:"Which database?":'Which database?',type(property)type:"select":'select',default(property)default:"postgresql":'postgresql',options(property)options:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"sqlite";}]:[{value(property)value:"postgresql":'postgresql'},{value(property)value:"mysql":'mysql'},{value(property)value:"sqlite":'sqlite'}]}).add(method)add<"docker",{readonlyquestion:"Add a docker-compose file?";readonlytype:"boolean";readonlydefault:false;readonlycondition:(opts:OptionValues<Record<"database",{readonlyquestion:"Which database?";readonlytype:"select";readonlydefault:"postgresql";readonlyoptions:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"sqlite";}];}>&Record<"docker",any>>)=>boolean;}>(key:"docker",question:{readonlyquestion:"Add a docker-compose file?";readonlytype:"boolean";readonlydefault:false;readonlycondition:(opts:OptionValues<Record<"database",{readonlyquestion:"Which database?";readonlytype:"select";readonlydefault:"postgresql";readonlyoptions:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"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, multiselectdefault:true,// condition: (o) => o.previousOption === 'ok',})
('docker',{question(property)question:"Add a docker-compose file?":'Add a docker-compose file?',type(property)type:"boolean":'boolean',default(property)default:false:false, // only ask when database is not sqlite
condition(property)condition:(opts:OptionValues<Record<"database",{readonlyquestion:"Which database?";readonlytype:"select";readonlydefault:"postgresql";readonlyoptions:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"sqlite";}];}>&Record<"docker",any>>)=>boolean:(opts(parameter)opts:OptionValues<Record<"database",{readonlyquestion:"Which database?";readonlytype:"select";readonlydefault:"postgresql";readonlyoptions:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"sqlite";}];}>&Record<"docker",any>>)=>opts(parameter)opts:OptionValues<Record<"database",{readonlyquestion:"Which database?";readonlytype:"select";readonlydefault:"postgresql";readonlyoptions:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"sqlite";}];}>&Record<"docker",any>>.database(property)database:"postgresql"|"mysql"|"sqlite"!=='sqlite'}).build(method)build():{database:{readonlyquestion:"Which database?";readonlytype:"select";readonlydefault:"postgresql";readonlyoptions:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"sqlite";}];};docker:{readonlyquestion:"Add a docker-compose file?";readonlytype:"boolean";readonlydefault:false;readonlycondition:(opts:OptionValues<Record<"database",{readonlyquestion:"Which database?";readonlytype:"select";readonlydefault:"postgresql";readonlyoptions:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"sqlite";}];}>&Record<...>>)=>boolean;};}();
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).