UNPKG

sv

Version:

A command line interface (CLI) for creating and maintaining Svelte applications

304 lines (303 loc) 11.3 kB
import { AgentName } from "@sveltejs/sv-utils"; //#region src/addons/index.d.ts type OfficialAddons = { prettier: Addon<any>; eslint: Addon<any>; vitest: Addon<any>; playwright: Addon<any>; tailwindcss: Addon<any>; sveltekitAdapter: Addon<any>; drizzle: Addon<any>; betterAuth: Addon<any>; mdsvex: Addon<any>; paraglide: Addon<any>; storybook: Addon<any>; mcp: Addon<any>; }; declare const officialAddons: OfficialAddons; //#endregion //#region src/core/options.d.ts type BooleanQuestion = { type: "boolean"; default: boolean; }; type StringQuestion = { type: "string"; default: string; validate?: (value: string | undefined) => string | Error | undefined; placeholder?: string; }; type NumberQuestion = { type: "number"; default: number; validate?: (value: string | undefined) => string | Error | undefined; placeholder?: string; }; type SelectQuestion<Value> = { type: "select"; default: NoInfer<Value>; options: Array<{ value: Value; label?: string; hint?: string; }>; }; type MultiSelectQuestion<Value> = { type: "multiselect"; default: NoInfer<Value[]>; options: Array<{ value: Value; label?: string; hint?: string; }>; required: boolean; }; type BaseQuestion<Args extends OptionDefinition> = { question: string; group?: string; /** * When this condition explicitly returns `false`, the question's value will * always be `undefined` and will not fallback to the specified `default` value. */ condition?: (options: OptionValues<Args>) => boolean; }; type Question<Args extends OptionDefinition = OptionDefinition> = BaseQuestion<Args> & (BooleanQuestion | StringQuestion | NumberQuestion | SelectQuestion<any> | MultiSelectQuestion<any>); type OptionDefinition = Record<string, Question<any>>; type OptionValues<Args extends OptionDefinition> = { [K in keyof Args]: Args[K] extends StringQuestion ? string : Args[K] extends BooleanQuestion ? boolean : Args[K] extends NumberQuestion ? number : Args[K] extends SelectQuestion<infer Value> ? Value : Args[K] extends MultiSelectQuestion<infer Value> ? Value[] : "ERROR: The value for this type is invalid. Ensure that the `default` value exists in `options`." }; //#endregion //#region src/core/workspace.d.ts type WorkspaceOptions<Args extends OptionDefinition> = OptionValues<Args>; type Workspace = { cwd: string; /** * Returns the dependency version declared in the package.json. * This may differ from the installed version. * Includes both dependencies and devDependencies. * Also checks parent package.json files if called in a monorepo. * @param pkg the package to check for * @returns the dependency version with any leading characters such as ^ or ~ removed */ dependencyVersion: (pkg: string) => string | undefined; /** to know if the workspace is using typescript or javascript */ language: "ts" | "js"; file: { viteConfig: "vite.config.js" | "vite.config.ts"; svelteConfig: "svelte.config.js" | "svelte.config.ts"; typeConfig: "jsconfig.json" | "tsconfig.json" | undefined; /** `${directory.routes}/layout.css` or `src/app.css` */ stylesheet: `${string}/layout.css` | "src/app.css"; package: "package.json"; gitignore: ".gitignore"; /** @deprecated use the string `.prettierignore` instead. */ prettierignore: ".prettierignore"; /** @deprecated use the string `.prettierrc` instead. */ prettierrc: ".prettierrc"; /** @deprecated use the string `eslint.config.js` instead. */ eslintConfig: "eslint.config.js"; /** @deprecated use the string `.vscode/settings.json` instead. */ vscodeSettings: ".vscode/settings.json"; /** @deprecated use the string `.vscode/extensions.json` instead. */ vscodeExtensions: ".vscode/extensions.json"; /** Get the relative path between two files */ getRelative: ({ from, to }: { from?: string; to: string; }) => string; /** * Find a file by walking up the directory tree from cwd. * Returns the relative path from cwd, or the filename itself if not found. */ findUp: (filename: string) => string; }; isKit: boolean; directory: { src: string; /** In SvelteKit taking `kit.files.lib` automatically. Falls back to `src/lib` in non-Kit projects */ lib: string; /** In SvelteKit taking `kit.files.routes` automatically. Falls back to `src/routes` in non-Kit projects */ kitRoutes: string; }; /** The package manager used to install dependencies */ packageManager: AgentName; }; //#endregion //#region src/core/config.d.ts type ConditionDefinition = (Workspace: Workspace) => boolean; type SvApi = { /** @deprecated use `pnpm.allowBuilds` from `@sveltejs/sv-utils` instead */pnpmBuildDependency: (pkg: string) => void; /** Add a package to the dependencies. */ dependency: (pkg: string, version: string) => void; /** Add a package to the dev dependencies. */ devDependency: (pkg: string, version: string) => void; /** Execute a command in the workspace. */ execute: (args: string[], stdio: "inherit" | "pipe") => Promise<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. */ file: (path: string, edit: (content: string) => string | false) => void; }; type Addon<Args extends OptionDefinition, Id extends string = string> = { id: Id; alias?: string; /** one-liner shown in prompts */ shortDescription?: string; /** link to docs/repo */ homepage?: string; /** If true, this addon won't appear in the interactive prompt but can still be used via CLI */ hidden?: boolean; options: Args; /** Setup the addon. Will be called before the addon is run. */ setup?: (workspace: Workspace & { /** On what official addons does this addon depend on? */dependsOn: (name: keyof typeof officialAddons) => void; /** * Why is this addon not supported? * @example * if (!isKit) unsupported('Requires SvelteKit'); */ unsupported: (reason: string) => void; /** On what official addons does this addon run after? */ runsAfter: (name: keyof typeof officialAddons) => void; }) => MaybePromise<void>; /** Run the addon. The actual execution of the addon... Add files, edit files, etc. */ run: (workspace: Workspace & { /** Add-on options */options: WorkspaceOptions<Args>; /** Api to interact with the workspace. */ sv: SvApi; /** Cancel the addon at any time! * @example * return cancel('There is a problem with...'); */ cancel: (reason: string) => void; }) => MaybePromise<void>; /** Next steps to display after the addon is run. */ nextSteps?: (workspace: Workspace & { options: WorkspaceOptions<Args>; }) => string[]; }; /** * The entry point for your addon, It will hold every thing! (options, setup, run, nextSteps, ...) */ declare function defineAddon<const Id extends string, Args extends OptionDefinition>(config: Addon<Args, Id>): Addon<Args, Id>; /** * Stage 1: Raw CLI input - what the user typed */ type AddonInput = { readonly specifier: string; readonly options: string[]; }; /** * Stage 2: Classified source - knows where addon comes from */ type AddonSource = { readonly kind: "official"; readonly id: string; } | { readonly kind: "file"; readonly path: string; } | { readonly kind: "npm"; readonly packageName: string; readonly npmUrl: string; readonly registryUrl: string; readonly tag: string; }; type AddonReference = { readonly specifier: string; readonly options: string[]; readonly source: AddonSource; }; /** * Stage 3: Code loaded - addon definition is always present */ type LoadedAddon = { readonly reference: AddonReference; readonly addon: AddonDefinition; }; /** * Stage 4: Setup done - has dependency info */ type PreparedAddon = LoadedAddon & { readonly setupResult: SetupResult; }; /** * Stage 5: User configured - has answers to questions */ type ConfiguredAddon = PreparedAddon & { readonly answers: OptionValues<any>; }; /** * Stage 6: Execution result */ type AddonResult = { readonly id: string; readonly status: "success" | { canceled: string[]; }; readonly files: string[]; }; type SetupResult = { dependsOn: string[]; unsupported: string[]; runsAfter: string[]; }; type AddonDefinition<Id extends string = string> = Addon<Record<string, Question<any>>, Id>; type MaybePromise<T> = Promise<T> | T; type Prettify<T> = { [K in keyof T]: T[K] } & unknown; type OptionBuilder<T extends OptionDefinition> = { /** * 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. * * ```ts * .add('demo', { * question: 'Do you want to add a demo?', * type: 'boolean', // string, number, select, multiselect * default: true, * // condition: (o) => o.previousOption === 'ok', * }) * ``` */ add<K extends string, const Q extends Question<T & Record<K, Q>>>(key: K, question: Q): OptionBuilder<T & Record<K, Q>>; /** Finalize all options of your `add-on`. */ build(): Prettify<T>; }; /** * Options for an addon. * * Will be prompted to the user if there are not answered by args when calling the cli. * * ```ts * 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 * ```sh * npx sv add <addon>=<option1>:<value1>+<option2>:<value2> * ``` */ declare function defineAddonOptions(): OptionBuilder<{}>; //#endregion //#region src/core/engine.d.ts type InstallOptions<Addons extends AddonMap> = { cwd: string; addons: Addons; options: OptionMap<Addons>; packageManager?: AgentName; }; type AddonMap = Record<string, Addon<any, any>>; type AddonById<Addons extends AddonMap, Id extends string> = Extract<Addons[keyof Addons], { id: Id; }>; type OptionMap<Addons extends AddonMap> = { [Id in Addons[keyof Addons]["id"]]: Partial<OptionValues<AddonById<Addons, Id>["options"]>> }; declare function add<Addons extends AddonMap>({ addons, cwd, options, packageManager }: InstallOptions<Addons>): Promise<ReturnType<typeof applyAddons>>; type ApplyAddonOptions = { loadedAddons: LoadedAddon[]; options: OptionMap<AddonMap>; workspace: Workspace; setupResults: Record<string, SetupResult>; }; declare function applyAddons({ loadedAddons, workspace, setupResults, options }: ApplyAddonOptions): Promise<{ filesToFormat: string[]; status: Record<string, string[] | "success">; }>; //#endregion export { StringQuestion as A, BooleanQuestion as C, OptionValues as D, OptionDefinition as E, Question as O, BaseQuestion as S, NumberQuestion as T, SvApi as _, Addon as a, Workspace as b, AddonReference as c, ConditionDefinition as d, ConfiguredAddon as f, SetupResult as g, PreparedAddon as h, add as i, officialAddons as j, SelectQuestion as k, AddonResult as l, OptionBuilder as m, InstallOptions as n, AddonDefinition as o, LoadedAddon as p, OptionMap as r, AddonInput as s, AddonMap as t, AddonSource as u, defineAddon as v, MultiSelectQuestion as w, WorkspaceOptions as x, defineAddonOptions as y };