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().
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);
().build(method)build():{}(), // called before run - declare dependencies, environment requirements, and dynamic optionssetup(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 dataaddOption(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 dependencysv(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-utilssv(parameter)sv:SvApi.file(property)file:(path:string,edit:(content:string)=>string|false)=>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.
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().
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);
Return false from the callback to abort - the original content is returned unchanged.
(({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 runsnextSteps(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, 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:
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 sqlitecondition(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).