UNPKG

sv

Version:

A CLI for creating and updating SvelteKit projects

1,414 lines (1,401 loc) 47.7 kB
#!/usr/bin/env node import { AGENT_NAMES, Command, De, Fe, Ge, J, Ke, Option, T, Ue, Vu, We, __toESM$1 as __toESM, addPnpmBuildDependencies, create, detect, et, from, getUserAgent, installDependencies, installOption, ke, packageManagerPrompt, program, require_picocolors$1 as require_picocolors, resolveCommand, templates, up, ze } from "./package-manager-DO5R9a6p.js"; import { applyAddons, communityAddonIds, createWorkspace, formatFiles, getAddonDetails, getCommunityAddon, getHighlighter, isVersionUnsupportedBelow, officialAddons, setupAddons } from "./addons-DrsfA5uM.js"; import fs, { existsSync } from "node:fs"; import path, { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import process from "node:process"; import { promisify } from "node:util"; import { exec, execSync } from "node:child_process"; import { createGunzip } from "node:zlib"; import { pipeline } from "node:stream/promises"; //#region packages/cli/package.json var name = "sv"; var version = "0.8.21"; var type = "module"; var description = "A CLI for creating and updating SvelteKit projects"; var license = "MIT"; var repository = { "type": "git", "url": "https://github.com/sveltejs/cli", "directory": "packages/cli" }; var homepage = "https://svelte.dev"; var scripts = { "check": "tsc", "format": "pnpm lint --write", "lint": "prettier --check . --config ../../prettier.config.js --ignore-path ../../.gitignore --ignore-path .gitignore --ignore-path ../../.prettierignore" }; var files = ["dist"]; var bin = "./dist/bin.js"; var exports = { ".": { "types": "./dist/lib/index.d.ts", "default": "./dist/index.js" }, "./testing": { "types": "./dist/lib/testing.d.ts", "default": "./dist/testing.js" } }; var devDependencies = { "@clack/prompts": "1.0.0-alpha.1", "@sveltejs/addons": "workspace:*", "@sveltejs/cli-core": "workspace:*", "@sveltejs/create": "workspace:*", "@types/degit": "^2.8.6", "@types/ps-tree": "^1.1.6", "commander": "^13.1.0", "degit": "^2.8.4", "empathic": "^1.1.0", "package-manager-detector": "^0.2.11", "picocolors": "^1.1.1", "ps-tree": "^1.2.0", "tinyexec": "^0.3.2", "valibot": "^0.41.0" }; var keywords = [ "create", "new", "project", "starter", "svelte", "sveltekit", "template", "wizard" ]; var package_default = { name, version, type, description, license, repository, homepage, scripts, files, bin, exports, devDependencies, keywords }; //#endregion //#region node_modules/.pnpm/valibot@0.41.0_typescript@5.8.3/node_modules/valibot/dist/index.js var store; function getGlobalConfig(config2) { return { lang: config2?.lang ?? store?.lang, message: config2?.message, abortEarly: config2?.abortEarly ?? store?.abortEarly, abortPipeEarly: config2?.abortPipeEarly ?? store?.abortPipeEarly }; } var store2; function getGlobalMessage(lang) { return store2?.get(lang); } var store3; function getSchemaMessage(lang) { return store3?.get(lang); } var store4; function getSpecificMessage(reference, lang) { return store4?.get(reference)?.get(lang); } function _stringify(input) { const type$1 = typeof input; if (type$1 === "string") return `"${input}"`; if (type$1 === "number" || type$1 === "bigint" || type$1 === "boolean") return `${input}`; if (type$1 === "object" || type$1 === "function") return (input && Object.getPrototypeOf(input)?.constructor?.name) ?? "null"; return type$1; } function _addIssue(context, label, dataset, config2, other) { const input = other && "input" in other ? other.input : dataset.value; const expected = other?.expected ?? context.expects ?? null; const received = other?.received ?? _stringify(input); const issue = { kind: context.kind, type: context.type, input, expected, received, message: `Invalid ${label}: ${expected ? `Expected ${expected} but r` : "R"}eceived ${received}`, requirement: context.requirement, path: other?.path, issues: other?.issues, lang: config2.lang, abortEarly: config2.abortEarly, abortPipeEarly: config2.abortPipeEarly }; const isSchema = context.kind === "schema"; const message = other?.message ?? context.message ?? getSpecificMessage(context.reference, issue.lang) ?? (isSchema ? getSchemaMessage(issue.lang) : null) ?? config2.message ?? getGlobalMessage(issue.lang); if (message) issue.message = typeof message === "function" ? message(issue) : message; if (isSchema) dataset.typed = false; if (dataset.issues) dataset.issues.push(issue); else dataset.issues = [issue]; } function _isValidObjectKey(object2, key) { return Object.hasOwn(object2, key) && key !== "__proto__" && key !== "prototype" && key !== "constructor"; } function _joinExpects(values, separator) { const list = [...new Set(values)]; if (list.length > 1) return `(${list.join(` ${separator} `)})`; return list[0] ?? "never"; } var ValiError = class extends Error { /** * The error issues. */ issues; /** * Creates a Valibot error with useful information. * * @param issues The error issues. */ constructor(issues) { super(issues[0].message); this.name = "ValiError"; this.issues = issues; } }; function transform(operation) { return { kind: "transformation", type: "transform", reference: transform, async: false, operation, _run(dataset) { dataset.value = this.operation(dataset.value); return dataset; } }; } function getDefault(schema, dataset, config2) { return typeof schema.default === "function" ? schema.default(dataset, config2) : schema.default; } function array(item, message) { return { kind: "schema", type: "array", reference: array, expects: "Array", async: false, item, message, _run(dataset, config2) { const input = dataset.value; if (Array.isArray(input)) { dataset.typed = true; dataset.value = []; for (let key = 0; key < input.length; key++) { const value2 = input[key]; const itemDataset = this.item._run({ typed: false, value: value2 }, config2); if (itemDataset.issues) { const pathItem = { type: "array", origin: "value", input, key, value: value2 }; for (const issue of itemDataset.issues) { if (issue.path) issue.path.unshift(pathItem); else issue.path = [pathItem]; dataset.issues?.push(issue); } if (!dataset.issues) dataset.issues = itemDataset.issues; if (config2.abortEarly) { dataset.typed = false; break; } } if (!itemDataset.typed) dataset.typed = false; dataset.value.push(itemDataset.value); } } else _addIssue(this, "type", dataset, config2); return dataset; } }; } function boolean(message) { return { kind: "schema", type: "boolean", reference: boolean, expects: "boolean", async: false, message, _run(dataset, config2) { if (typeof dataset.value === "boolean") dataset.typed = true; else _addIssue(this, "type", dataset, config2); return dataset; } }; } function optional(wrapped, ...args) { const schema = { kind: "schema", type: "optional", reference: optional, expects: `(${wrapped.expects} | undefined)`, async: false, wrapped, _run(dataset, config2) { if (dataset.value === void 0) { if ("default" in this) dataset.value = getDefault(this, dataset, config2); if (dataset.value === void 0) { dataset.typed = true; return dataset; } } return this.wrapped._run(dataset, config2); } }; if (0 in args) schema.default = args[0]; return schema; } function picklist(options$1, message) { return { kind: "schema", type: "picklist", reference: picklist, expects: _joinExpects(options$1.map(_stringify), "|"), async: false, options: options$1, message, _run(dataset, config2) { if (this.options.includes(dataset.value)) dataset.typed = true; else _addIssue(this, "type", dataset, config2); return dataset; } }; } function record(key, value2, message) { return { kind: "schema", type: "record", reference: record, expects: "Object", async: false, key, value: value2, message, _run(dataset, config2) { const input = dataset.value; if (input && typeof input === "object") { dataset.typed = true; dataset.value = {}; for (const entryKey in input) if (_isValidObjectKey(input, entryKey)) { const entryValue = input[entryKey]; const keyDataset = this.key._run({ typed: false, value: entryKey }, config2); if (keyDataset.issues) { const pathItem = { type: "object", origin: "key", input, key: entryKey, value: entryValue }; for (const issue of keyDataset.issues) { issue.path = [pathItem]; dataset.issues?.push(issue); } if (!dataset.issues) dataset.issues = keyDataset.issues; if (config2.abortEarly) { dataset.typed = false; break; } } const valueDataset = this.value._run({ typed: false, value: entryValue }, config2); if (valueDataset.issues) { const pathItem = { type: "object", origin: "value", input, key: entryKey, value: entryValue }; for (const issue of valueDataset.issues) { if (issue.path) issue.path.unshift(pathItem); else issue.path = [pathItem]; dataset.issues?.push(issue); } if (!dataset.issues) dataset.issues = valueDataset.issues; if (config2.abortEarly) { dataset.typed = false; break; } } if (!keyDataset.typed || !valueDataset.typed) dataset.typed = false; if (keyDataset.typed) dataset.value[keyDataset.value] = valueDataset.value; } } else _addIssue(this, "type", dataset, config2); return dataset; } }; } function strictObject(entries, message) { return { kind: "schema", type: "strict_object", reference: strictObject, expects: "Object", async: false, entries, message, _run(dataset, config2) { const input = dataset.value; if (input && typeof input === "object") { dataset.typed = true; dataset.value = {}; for (const key in this.entries) { const value2 = input[key]; const valueDataset = this.entries[key]._run({ typed: false, value: value2 }, config2); if (valueDataset.issues) { const pathItem = { type: "object", origin: "value", input, key, value: value2 }; for (const issue of valueDataset.issues) { if (issue.path) issue.path.unshift(pathItem); else issue.path = [pathItem]; dataset.issues?.push(issue); } if (!dataset.issues) dataset.issues = valueDataset.issues; if (config2.abortEarly) { dataset.typed = false; break; } } if (!valueDataset.typed) dataset.typed = false; if (valueDataset.value !== void 0 || key in input) dataset.value[key] = valueDataset.value; } if (!dataset.issues || !config2.abortEarly) { for (const key in input) if (!(key in this.entries)) { const value2 = input[key]; _addIssue(this, "type", dataset, config2, { input: value2, expected: "never", path: [{ type: "object", origin: "value", input, key, value: value2 }] }); break; } } } else _addIssue(this, "type", dataset, config2); return dataset; } }; } function string(message) { return { kind: "schema", type: "string", reference: string, expects: "string", async: false, message, _run(dataset, config2) { if (typeof dataset.value === "string") dataset.typed = true; else _addIssue(this, "type", dataset, config2); return dataset; } }; } function _subIssues(datasets) { let issues; if (datasets) for (const dataset of datasets) if (issues) issues.push(...dataset.issues); else issues = dataset.issues; return issues; } function union(options$1, message) { return { kind: "schema", type: "union", reference: union, expects: _joinExpects(options$1.map((option) => option.expects), "|"), async: false, options: options$1, message, _run(dataset, config2) { let validDataset; let typedDatasets; let untypedDatasets; for (const schema of this.options) { const optionDataset = schema._run({ typed: false, value: dataset.value }, config2); if (optionDataset.typed) if (optionDataset.issues) if (typedDatasets) typedDatasets.push(optionDataset); else typedDatasets = [optionDataset]; else { validDataset = optionDataset; break; } else if (untypedDatasets) untypedDatasets.push(optionDataset); else untypedDatasets = [optionDataset]; } if (validDataset) return validDataset; if (typedDatasets) { if (typedDatasets.length === 1) return typedDatasets[0]; _addIssue(this, "type", dataset, config2, { issues: _subIssues(typedDatasets) }); dataset.typed = true; } else if (untypedDatasets?.length === 1) return untypedDatasets[0]; else _addIssue(this, "type", dataset, config2, { issues: _subIssues(untypedDatasets) }); return dataset; } }; } function parse(schema, input, config2) { const dataset = schema._run({ typed: false, value: input }, getGlobalConfig(config2)); if (dataset.issues) throw new ValiError(dataset.issues); return dataset.value; } function pipe(...pipe2) { return { ...pipe2[0], pipe: pipe2, _run(dataset, config2) { for (const item of pipe2) if (item.kind !== "metadata") { if (dataset.issues && (item.kind === "schema" || item.kind === "transformation")) { dataset.typed = false; break; } if (!dataset.issues || !config2.abortEarly && !config2.abortPipeEarly) dataset = item._run(dataset, config2); } return dataset; } }; } //#endregion //#region node_modules/.pnpm/empathic@1.1.0/node_modules/empathic/package.mjs function up$1(options$1) { return up("package.json", options$1); } //#endregion //#region packages/cli/utils/errors.ts var UnsupportedError = class extends Error { name = "Unsupported Environment"; reasons = []; constructor(reasons) { super(); this.reasons = reasons; } }; //#endregion //#region packages/cli/utils/common.ts var import_picocolors$3 = __toESM(require_picocolors(), 1); const NO_PREFIX = "--no-"; let options = []; function getLongFlag(flags) { return flags.split(",").map((f) => f.trim()).find((f) => f.startsWith("--")); } const helpConfig = { argumentDescription: formatDescription, optionDescription: formatDescription, visibleOptions(cmd) { options = cmd.options; const visible = cmd.options.filter((o) => !o.hidden); const show = []; for (const option of visible) { const flag = getLongFlag(option.flags); if (flag?.startsWith(NO_PREFIX)) { const stripped = flag.slice(NO_PREFIX.length); const isNoVariant = visible.some((o) => getLongFlag(o.flags)?.startsWith(`--${stripped}`)); if (isNoVariant) continue; } show.push(option); } return show; }, optionTerm(option) { const longFlag = getLongFlag(option.flags); const flag = longFlag?.split(" ").at(0); if (!flag || !longFlag) return option.flags; const noVariant = `--no-${flag.slice(2)}`; const hasVariant = options.some((o) => getLongFlag(o.flags) === noVariant); if (hasVariant) return `--[no-]${longFlag.slice(2)}`; return option.flags; }, styleTitle: (str) => import_picocolors$3.default.underline(str), styleCommandText: (str) => import_picocolors$3.default.red(str), styleDescriptionText: (str) => import_picocolors$3.default.gray(str), styleOptionText: (str) => import_picocolors$3.default.white(str), styleArgumentText: (str) => import_picocolors$3.default.white(str), styleSubcommandText: (str) => import_picocolors$3.default.red(str) }; function formatDescription(arg) { let output = arg.description; if (arg.defaultValue !== undefined && String(arg.defaultValue)) output += import_picocolors$3.default.dim(` (default: ${JSON.stringify(arg.defaultValue)})`); if (arg.argChoices !== undefined && String(arg.argChoices)) output += import_picocolors$3.default.dim(` (choices: ${arg.argChoices.join(", ")})`); return output; } async function runCommand(action) { try { Ge(`Welcome to the Svelte CLI! ${import_picocolors$3.default.gray(`(v${package_default.version})`)}`); const minimumVersion = "18.3.0"; const unsupported = isVersionUnsupportedBelow(process.versions.node, minimumVersion); if (unsupported) T.warn(`You are using Node.js ${import_picocolors$3.default.red(process.versions.node)}, please upgrade to Node.js ${import_picocolors$3.default.green(minimumVersion)} or higher.`); await action(); Fe("You're all set!"); } catch (e) { if (e instanceof UnsupportedError) { const padding = getPadding(e.reasons.map((r) => r.id)); const message = e.reasons.map((r) => ` ${r.id.padEnd(padding)} ${import_picocolors$3.default.red(r.reason)}`).join("\n"); T.error(`${e.name}\n\n${message}`); T.message(); } else if (e instanceof Error) { T.error(e.stack ?? String(e)); T.message(); } De("Operation failed."); } } function getPadding(lines) { const lengths = lines.map((s) => s.length); return Math.max(...lengths); } function forwardExitCode(error) { if (error && typeof error === "object" && "status" in error && typeof error.status === "number") process.exit(error.status); else process.exit(1); } //#endregion //#region packages/cli/commands/add/fetch-packages.ts const NODE_MODULES = fileURLToPath(new URL("../node_modules", import.meta.url)); const REGISTRY = "https://registry.npmjs.org"; const Directive = { file: "file:", npm: "npm:" }; function verifyPackage(pkg, specifier) { const deps = { ...pkg.dependencies, ...pkg.peerDependencies }; if (!deps["@sveltejs/cli-core"]) throw new Error(`Invalid add-on package specified: '${specifier}' is missing a dependency on '@sveltejs/cli-core' in its 'package.json'`); for (const dep of Object.keys(deps)) { if (dep === "@sveltejs/cli-core") continue; throw new Error(`Invalid add-on package detected: '${specifier}'\nCommunity addons should not have any external 'dependencies' besides '@sveltejs/cli-core'. Consider bundling your dependencies if they are necessary`); } } async function downloadPackage(options$1) { const { pkg } = options$1; if (options$1.path) { const dest = path.join(NODE_MODULES, pkg.name.split("/").join(path.sep)); if (fs.existsSync(dest)) fs.rmSync(dest); const dir = path.dirname(dest); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); fs.symlinkSync(options$1.path, dest); const { default: details$1 } = await import(pkg.name); return details$1; } const tarballUrl = pkg.dist.tarball; const data = await fetch(tarballUrl); if (!data.body) throw new Error(`Unexpected response: '${tarballUrl}' responded with no body`); await pipeline( data.body, createGunzip() // extract(NODE_MODULES, { // map: (header) => { // // file paths from the tarball will always have a `package/` prefix, // // so we'll need to replace it with the name of the package // header.name = header.name.replace('package', pkg.name); // return header; // } // }) ); const { default: details } = await import(pkg.name); return details; } async function getPackageJSON({ cwd, packageName }) { let npm = packageName; if (packageName.startsWith(Directive.file)) { const pkgPath = path.resolve(cwd, packageName.slice(Directive.file.length)); const pkgJSONPath = path.resolve(pkgPath, "package.json"); const json = fs.readFileSync(pkgJSONPath, "utf8"); const pkg$1 = JSON.parse(json); verifyPackage(pkg$1, packageName); return { path: pkgPath, pkg: pkg$1, repo: pkgPath }; } if (packageName.startsWith(Directive.npm)) npm = packageName.slice(Directive.npm.length); const pkg = await fetchPackageJSON(npm); verifyPackage(pkg, packageName); return { pkg, repo: pkg.repository?.url ?? `https://www.npmjs.com/package/${npm}` }; } async function fetchPackageJSON(packageName) { let pkgName = packageName; let scope = ""; if (packageName.startsWith("@")) { const [org, name$2] = pkgName.split("/", 2); scope = `${org}/`; pkgName = name$2; } const [name$1, tag = "latest"] = pkgName.split("@"); const pkgUrl = `${REGISTRY}/${scope + name$1}/${tag}`; const resp = await fetch(pkgUrl); if (resp.status === 404) throw new Error(`Package '${packageName}' doesn't exist in the registry: '${pkgUrl}'`); if (resp.status < 200 && resp.status >= 300) throw new Error(`Failed to fetch '${pkgUrl}' - GET ${resp.status}`); return await resp.json(); } //#endregion //#region packages/cli/commands/add/preconditions.ts function getGlobalPreconditions(cwd, addons, addonSetupResult) { return { name: "global checks", preconditions: [{ name: "clean working directory", run: async () => { try { const asyncExec = promisify(exec); const { stdout: stdout$1 } = await asyncExec("git status --short", { cwd }); if (stdout$1) return { success: false, message: "Found modified files" }; return { success: true, message: undefined }; } catch { return { success: true, message: "Not a git repository" }; } } }, { name: "unsupported add-ons", run: () => { const reasons = addons.flatMap((a) => addonSetupResult[a.id].unsupported.map((reason) => ({ id: a.id, reason }))); if (reasons.length === 0) return { success: true, message: undefined }; throw new UnsupportedError(reasons); } }] }; } //#endregion //#region packages/cli/commands/add/index.ts var import_picocolors$2 = __toESM(require_picocolors(), 1); const aliases = officialAddons.map((c) => c.alias).filter((v) => v !== undefined); const addonOptions = getAddonOptionFlags(); const communityDetails = []; const AddonsSchema = array(string()); const OptionsSchema$1 = strictObject({ cwd: string(), install: union([boolean(), picklist(AGENT_NAMES)]), preconditions: boolean(), community: optional(union([AddonsSchema, boolean()])), addons: record(string(), optional(array(string()))) }); const defaultPkgPath = up$1(); const defaultCwd = defaultPkgPath ? path.dirname(defaultPkgPath) : undefined; const add = new Command("add").description("applies specified add-ons into a project").argument("[add-on...]", `add-ons to install`, (value, prev = []) => { const [addonId, optionFlags] = value.split("=", 2); const repeatedAddons = prev.find(({ id }) => id === addonId); if (repeatedAddons) { console.error(`Malformed arguments: Add-on '${addonId}' is repeated multiple times.`); process.exit(1); } if (optionFlags === undefined) { prev.push({ id: addonId, options: undefined }); return prev; } if (optionFlags.length > 0 && !/.+:.*/.test(optionFlags)) { console.error(`Malformed arguments: An add-on's option in '${value}' is missing it's option name or value (e.g. 'addon=option:value').`); process.exit(1); } const options$1 = optionFlags.match(/[^+]*:[^:]*(?=\+|$)/g) ?? []; prev.push({ id: addonId, options: options$1 }); return prev; }).option("-C, --cwd <path>", "path to working directory", defaultCwd).option("--no-preconditions", "skip validating preconditions").option("--no-install", "skip installing dependencies").addOption(installOption).configureHelp({ ...helpConfig, formatHelp(cmd, helper) { const termWidth = helper.padWidth(cmd, helper); const helpWidth = helper.helpWidth ?? 80; function callFormatItem(term, description$1) { return helper.formatItem(term, termWidth, description$1, helper); } let output = [`${helper.styleTitle("Usage:")} ${helper.styleUsage(helper.commandUsage(cmd))}`, ""]; const commandDescription = helper.commandDescription(cmd); if (commandDescription.length > 0) output = output.concat([helper.boxWrap(helper.styleCommandDescription(commandDescription), helpWidth), ""]); const argumentList = helper.visibleArguments(cmd).map((argument) => { return callFormatItem(helper.styleArgumentTerm(helper.argumentTerm(argument)), helper.styleArgumentDescription(helper.argumentDescription(argument))); }); if (argumentList.length > 0) output = output.concat([ helper.styleTitle("Arguments:"), ...argumentList, "" ]); const addonList = addonOptions.map((option) => { const description$1 = option.choices; return callFormatItem(helper.styleArgumentTerm(option.id), helper.styleArgumentDescription(description$1)); }); if (addonList.length > 0) output = output.concat([ helper.styleTitle("Add-On Options:"), ...addonList, "" ]); const optionList = helper.visibleOptions(cmd).map((option) => { return callFormatItem(helper.styleOptionTerm(helper.optionTerm(option)), helper.styleOptionDescription(helper.optionDescription(option))); }); if (optionList.length > 0) output = output.concat([ helper.styleTitle("Options:"), ...optionList, "" ]); if (helper.showGlobalOptions) { const globalOptionList = helper.visibleGlobalOptions(cmd).map((option) => { return callFormatItem(helper.styleOptionTerm(helper.optionTerm(option)), helper.styleOptionDescription(helper.optionDescription(option))); }); if (globalOptionList.length > 0) output = output.concat([ helper.styleTitle("Global Options:"), ...globalOptionList, "" ]); } const commandList = helper.visibleCommands(cmd).map((cmd$1) => { return callFormatItem(helper.styleSubcommandTerm(helper.subcommandTerm(cmd$1)), helper.styleSubcommandDescription(helper.subcommandDescription(cmd$1))); }); if (commandList.length > 0) output = output.concat([ helper.styleTitle("Commands:"), ...commandList, "" ]); return output.join("\n"); } }).action((addonArgs, opts) => { if (opts.cwd === undefined) { console.error("Invalid workspace: Please verify that you are inside of a Svelte project. You can also specify the working directory with `--cwd <path>`"); process.exit(1); } else if (!fs.existsSync(path.resolve(opts.cwd, "package.json"))) { console.error(`Invalid workspace: Path '${path.resolve(opts.cwd)}' is not a valid workspace.`); process.exit(1); } const addonIds = officialAddons.map((addon) => addon.id); const invalidAddons = addonArgs.filter(({ id }) => !addonIds.includes(id) && !aliases.includes(id)).map(({ id }) => id); if (invalidAddons.length > 0) { console.error(`Invalid add-ons specified: ${invalidAddons.join(", ")}`); process.exit(1); } const options$1 = parse(OptionsSchema$1, { ...opts, addons: {} }); const selectedAddons = transformAliases(addonArgs); selectedAddons.forEach((addon) => options$1.addons[addon.id] = addon.options); runCommand(async () => { const selectedAddonIds = selectedAddons.map(({ id }) => id); const { nextSteps } = await runAddCommand(options$1, selectedAddonIds); if (nextSteps.length > 0) Ke(nextSteps.join("\n"), "Next steps", { format: (line) => line }); }); }); async function runAddCommand(options$1, selectedAddonIds) { const selectedAddons = selectedAddonIds.map((id) => ({ type: "official", addon: getAddonDetails(id) })); const official = {}; const community = {}; for (const addonOption of addonOptions) { const addonId = addonOption.id; const specifiedOptions = options$1.addons[addonId]; if (!specifiedOptions) continue; const details$1 = getAddonDetails(addonId); if (!selectedAddons.find((d) => d.addon === details$1)) selectedAddons.push({ type: "official", addon: details$1 }); official[addonId] ??= {}; const optionEntries = Object.entries(details$1.options); for (const option of specifiedOptions) { let [optionId, optionValue] = option.split(":", 2); const optionEntry = optionEntries.find(([id, question$1]) => id === optionId || question$1.group === optionId); if (!optionEntry) { const { choices } = getOptionChoices(details$1); throw new Error(`Invalid '${addonId}' option: '${option}'\nAvailable options: ${choices.join(", ")}`); } const [questionId, question] = optionEntry; if (question.type === "multiselect" && optionValue === "none") optionValue = ""; let existingOption = official[addonId][questionId]; if (existingOption !== undefined) { if (typeof existingOption === "boolean") existingOption = existingOption ? "yes" : "no"; throw new Error(`Conflicting '${addonId}' option: '${option}' conflicts with '${questionId}:${existingOption}'`); } if (question.type === "boolean") official[addonId][questionId] = optionValue === "yes"; else if (question.type === "number") official[addonId][questionId] = Number(optionValue); else official[addonId][questionId] = optionValue; } for (const [id, question] of Object.entries(details$1.options)) if (question.condition?.(official[addonId]) !== false) official[addonId][id] ??= question.default; else if (official[addonId][id] !== undefined) throw new Error(`Incompatible '${addonId}' option specified: '${official[addonId][id]}'`); } if (options$1.community === true) { const communityAddons = await Promise.all(communityAddonIds.map(async (id) => await getCommunityAddon(id))); const promptOptions = communityAddons.map((addon) => ({ value: addon.id, label: addon.id, hint: "https://www.npmjs.com/package/" + addon.id })); const selected = await Ue({ message: "Which community tools would you like to add to your project?", options: promptOptions, required: false }); if (Vu(selected)) { De("Operation cancelled."); process.exit(1); } else if (selected.length === 0) { De("No add-ons selected. Exiting."); process.exit(1); } options$1.community = selected; } if (Array.isArray(options$1.community) && options$1.community.length > 0) { const addons = options$1.community.map((id) => { const hasDirective = Object.values(Directive).some((directive) => id.startsWith(directive)); if (hasDirective) return id; const validAddon = communityAddonIds.includes(id); if (!validAddon) throw new Error(`Invalid community add-on specified: '${id}'\nAvailable options: ${communityAddonIds.join(", ")}`); return id; }); const { start, stop } = J(); try { start("Resolving community add-on packages"); const pkgs = await Promise.all(addons.map(async (id) => { return await getPackageJSON({ cwd: options$1.cwd, packageName: id }); })); stop("Resolved community add-on packages"); T.warn("The Svelte maintainers have not reviewed community add-ons for malicious code. Use at your discretion."); const paddingName = getPadding(pkgs.map(({ pkg }) => pkg.name)); const paddingVersion = getPadding(pkgs.map(({ pkg }) => `(v${pkg.version})`)); const packageInfos = pkgs.map(({ pkg, repo: _repo }) => { const name$1 = import_picocolors$2.default.yellowBright(pkg.name.padEnd(paddingName)); const version$1 = import_picocolors$2.default.dim(`(v${pkg.version})`.padEnd(paddingVersion)); const repo = import_picocolors$2.default.dim(`(${_repo})`); return `${name$1} ${version$1} ${repo}`; }); T.message(packageInfos.join("\n")); const confirm = await ke({ message: "Would you like to continue?" }); if (confirm !== true) { De("Operation cancelled."); process.exit(1); } start("Downloading community add-on packages"); const details$1 = await Promise.all(pkgs.map(async (opts) => downloadPackage(opts))); for (const addon of details$1) { const id = addon.id; community[id] ??= {}; communityDetails.push(addon); selectedAddons.push({ type: "community", addon }); } stop("Downloaded community add-on packages"); } catch (err) { stop("Failed to resolve community add-on packages", 1); throw err; } } let workspace = await createWorkspace({ cwd: options$1.cwd }); const setups = selectedAddons.length ? selectedAddons.map(({ addon }) => addon) : officialAddons; const addonSetupResults = setupAddons(setups, workspace); if (selectedAddons.length === 0) { const addonOptions$1 = officialAddons.filter(({ id }) => addonSetupResults[id].unsupported.length === 0).map(({ id, homepage: homepage$1, shortDescription }) => ({ label: id, value: id, hint: `${shortDescription} - ${homepage$1}` })); const selected = await Ue({ message: `What would you like to add to your project? ${import_picocolors$2.default.dim("(use arrow keys / space bar)")}`, options: addonOptions$1, required: false }); if (Vu(selected)) { De("Operation cancelled."); process.exit(1); } for (const id of selected) { const addon = officialAddons.find((addon$1) => addon$1.id === id); selectedAddons.push({ type: "official", addon }); } } for (const { addon } of selectedAddons) { workspace = await createWorkspace(workspace); const setupResult = addonSetupResults[addon.id]; const missingDependencies = setupResult.dependsOn.filter((depId) => !selectedAddons.some((a) => a.addon.id === depId)); for (const depId of missingDependencies) { const dependency = officialAddons.find((a) => a.id === depId); if (!dependency) throw new Error(`'${addon.id}' depends on an invalid add-on: '${depId}'`); const install = await ke({ message: `The ${import_picocolors$2.default.bold(import_picocolors$2.default.cyan(addon.id))} add-on requires ${import_picocolors$2.default.bold(import_picocolors$2.default.cyan(depId))} to also be setup. ${import_picocolors$2.default.green("Include it?")}` }); if (install !== true) { De("Operation cancelled."); process.exit(1); } selectedAddons.push({ type: "official", addon: dependency }); } } if (options$1.preconditions && selectedAddons.length > 0) { const addons = selectedAddons.map(({ addon }) => addon); const { preconditions } = getGlobalPreconditions(options$1.cwd, addons, addonSetupResults); const fails = []; for (const condition of preconditions) { const { message, success } = await condition.run(); if (!success) fails.push({ name: condition.name, message }); } if (fails.length > 0) { const message = fails.map(({ name: name$1, message: message$1 }) => import_picocolors$2.default.yellow(`${name$1} (${message$1})`)).join("\n- "); Ke(`- ${message}`, "Preconditions not met", { format: (line) => line }); const force = await ke({ message: "Preconditions failed. Do you wish to continue?", initialValue: false }); if (Vu(force) || !force) { De("Operation cancelled."); process.exit(1); } } } for (const { addon, type: type$1 } of selectedAddons) { const addonId = addon.id; const questionPrefix = selectedAddons.length > 1 ? `${addon.id}: ` : ""; let values = {}; if (type$1 === "official") { official[addonId] ??= {}; values = official[addonId]; } if (type$1 === "community") { community[addonId] ??= {}; values = community[addonId]; } for (const [questionId, question] of Object.entries(addon.options)) { const shouldAsk = question.condition?.(values); if (shouldAsk === false || values[questionId] !== undefined) continue; let answer; const message = questionPrefix + question.question; if (question.type === "boolean") answer = await ke({ message, initialValue: question.default }); if (question.type === "select") answer = await ze({ message, initialValue: question.default, options: question.options }); if (question.type === "multiselect") answer = await Ue({ message, initialValues: question.default, required: question.required, options: question.options }); if (question.type === "string" || question.type === "number") { answer = await et({ message, initialValue: question.default.toString(), placeholder: question.placeholder, validate: question.validate }); if (question.type === "number") answer = Number(answer); } if (Vu(answer)) { De("Operation cancelled."); process.exit(1); } values[questionId] = answer; } } if (selectedAddons.length === 0) return { packageManager: null, nextSteps: [] }; const officialDetails = Object.keys(official).map((id) => getAddonDetails(id)); const commDetails = Object.keys(community).map((id) => communityDetails.find((a) => a.id === id)); const details = officialDetails.concat(commDetails); const addonMap = Object.assign({}, ...details.map((a) => ({ [a.id]: a }))); const { filesToFormat, pnpmBuildDependencies: addonPnpmBuildDependencies } = await applyAddons({ workspace, addonSetupResults, addons: addonMap, options: official }); T.success("Successfully setup add-ons"); let packageManager; if (options$1.install) { packageManager = options$1.install === true ? await packageManagerPrompt(options$1.cwd) : options$1.install; if (packageManager) { workspace.packageManager = packageManager; addPnpmBuildDependencies(workspace.cwd, packageManager, ["esbuild", ...addonPnpmBuildDependencies]); await installDependencies(packageManager, options$1.cwd); } } workspace = await createWorkspace(workspace); if (filesToFormat.length > 0 && packageManager && !!workspace.dependencyVersion("prettier")) { const { start, stop } = J(); start("Formatting modified files"); try { await formatFiles({ packageManager, cwd: options$1.cwd, paths: filesToFormat }); stop("Successfully formatted modified files"); } catch (e) { stop("Failed to format files"); if (e instanceof Error) T.error(e.message); } } const highlighter = getHighlighter(); const nextSteps = selectedAddons.map(({ addon }) => { if (!addon.nextSteps) return; let addonMessage = `${import_picocolors$2.default.green(addon.id)}:\n`; const options$2 = official[addon.id]; const addonNextSteps = addon.nextSteps({ ...workspace, options: options$2, highlighter }); addonMessage += ` - ${addonNextSteps.join("\n - ")}`; return addonMessage; }).filter((msg) => msg !== undefined); return { nextSteps, packageManager }; } /** * Dedupes and transforms aliases into their respective addon id */ function transformAliases(addons) { const set = new Map(); for (const addon of addons) if (aliases.includes(addon.id)) { const officialAddon = officialAddons.find((a) => a.alias === addon.id); set.set(officialAddon.id, { id: officialAddon.id, options: addon.options }); } else set.set(addon.id, addon); return Array.from(set.values()); } function getAddonOptionFlags() { const options$1 = []; for (const addon of officialAddons) { const id = addon.id; const details = getAddonDetails(id); if (Object.values(details.options).length === 0) continue; const { defaults, groups } = getOptionChoices(details); const choices = Object.entries(groups).map(([group, choices$1]) => `${import_picocolors$2.default.dim(`${group}:`)} ${choices$1.join(", ")}`).join("\n"); const preset = defaults.join(", ") || "none"; options$1.push({ id, choices, preset }); } return options$1; } function getOptionChoices(details) { const choices = []; const defaults = []; const groups = {}; const options$1 = {}; for (const [id, question] of Object.entries(details.options)) { let values = []; const applyDefault = question.condition?.(options$1) !== false; if (question.type === "boolean") { values = ["yes", `no`]; if (applyDefault) { options$1[id] = question.default; defaults.push(question.default ? values[0] : values[1]); } } if (question.type === "select") { values = question.options.map((o) => o.value); if (applyDefault) { options$1[id] = question.default; defaults.push(question.default); } } if (question.type === "multiselect") { values = question.options.map((o) => o.value); if (applyDefault) { options$1[id] = question.default; defaults.push(...question.default); } } if (question.type === "string" || question.type === "number") { values = ["<user-input>"]; if (applyDefault) { options$1[id] = question.default; defaults.push(question.default.toString()); } } choices.push(...values); const groupId = question.group ?? id; groups[groupId] ??= []; groups[groupId].push(...values); } return { choices, defaults, groups }; } //#endregion //#region packages/cli/commands/create.ts var import_picocolors$1 = __toESM(require_picocolors(), 1); const langs = ["ts", "jsdoc"]; const langMap = { ts: "typescript", jsdoc: "checkjs", false: "none" }; const templateChoices = templates.map((t) => t.name); const langOption = new Option("--types <lang>", "add type checking").choices(langs); const templateOption = new Option("--template <type>", "template to scaffold").choices(templateChoices); const ProjectPathSchema = optional(string()); const OptionsSchema = strictObject({ types: pipe(optional(union([picklist(langs), boolean()])), transform((lang) => langMap[String(lang)])), addOns: boolean(), install: union([boolean(), picklist(AGENT_NAMES)]), template: optional(picklist(templateChoices)) }); const create$1 = new Command("create").description("scaffolds a new SvelteKit project").argument("[path]", "where the project will be created").addOption(templateOption).addOption(langOption).option("--no-types").option("--no-add-ons", "skips interactive add-on installer").option("--no-install", "skip installing dependencies").addOption(installOption).configureHelp(helpConfig).action((projectPath, opts) => { const cwd = parse(ProjectPathSchema, projectPath); const options$1 = parse(OptionsSchema, opts); runCommand(async () => { const { directory, addOnNextSteps, packageManager } = await createProject(cwd, options$1); const highlight = (str) => import_picocolors$1.default.bold(import_picocolors$1.default.cyan(str)); let i = 1; const initialSteps = ["📁 Project steps", ""]; const relative = path.relative(process.cwd(), directory); const pm = packageManager ?? (await detect({ cwd: directory }))?.name ?? getUserAgent() ?? "npm"; if (relative !== "") { const pathHasSpaces = relative.includes(" "); initialSteps.push(` ${i++}: ${highlight(`cd ${pathHasSpaces ? `"${relative}"` : relative}`)}`); } if (!packageManager) { const { args: args$1, command: command$1 } = resolveCommand(pm, "install", []); initialSteps.push(` ${i++}: ${highlight(`${command$1} ${args$1.join(" ")}`)}`); } const { args, command } = resolveCommand(pm, "run", ["dev", "--open"]); const pmRunCmd = `${command} ${args.join(" ")}`; const steps = [ ...initialSteps, ` ${i++}: ${highlight(pmRunCmd)}`, "", `To close the dev server, hit ${highlight("Ctrl-C")}` ]; if (addOnNextSteps.length > 0) { steps.push("", "🧩 Add-on steps", ""); for (const step of addOnNextSteps) { const indented = step.replaceAll(" -", " -"); steps.push(` ${indented}`); } } steps.push("", `Stuck? Visit us at ${import_picocolors$1.default.cyan("https://svelte.dev/chat")}`); Ke(steps.join("\n"), "What's next?", { format: (line) => line }); }); }); async function createProject(cwd, options$1) { const { directory, template, language } = await We({ directory: () => { if (cwd) return Promise.resolve(path.resolve(cwd)); const defaultPath = "./"; return et({ message: "Where would you like your project to be created?", placeholder: ` (hit Enter to use '${defaultPath}')`, defaultValue: defaultPath }); }, force: async ({ results: { directory: directory$1 } }) => { if (fs.existsSync(directory$1) && fs.readdirSync(directory$1).filter((x) => !x.startsWith(".git")).length > 0) { const force = await ke({ message: "Directory not empty. Continue?", initialValue: false }); if (Vu(force) || !force) { De("Exiting."); process.exit(0); } } }, template: () => { if (options$1.template) return Promise.resolve(options$1.template); return ze({ message: "Which template would you like?", initialValue: "minimal", options: templates.map((t) => ({ label: t.title, value: t.name, hint: t.description })) }); }, language: () => { if (options$1.types) return Promise.resolve(options$1.types); return ze({ message: "Add type checking with TypeScript?", initialValue: "typescript", options: [ { label: "Yes, using TypeScript syntax", value: "typescript" }, { label: "Yes, using JavaScript with JSDoc comments", value: "checkjs" }, { label: "No", value: "none" } ] }); } }, { onCancel: () => { De("Operation cancelled."); process.exit(0); } }); const projectPath = path.resolve(directory); create(projectPath, { name: path.basename(projectPath), template, types: language }); T.success("Project created"); let packageManager; let addOnNextSteps = []; const installDeps = async (install) => { packageManager = install === true ? await packageManagerPrompt(projectPath) : install; addPnpmBuildDependencies(projectPath, packageManager, ["esbuild"]); if (packageManager) await installDependencies(packageManager, projectPath); }; if (options$1.addOns) { const { nextSteps, packageManager: pm } = await runAddCommand({ cwd: projectPath, install: options$1.install, preconditions: false, community: [], addons: {} }, []); packageManager = pm; addOnNextSteps = nextSteps; } else if (options$1.install) await installDeps(options$1.install); if (packageManager === null && options$1.install) await installDeps(options$1.install); return { directory: projectPath, addOnNextSteps, packageManager }; } //#endregion //#region packages/cli/commands/migrate.ts const migrate = new Command("migrate").description("a CLI for migrating Svelte(Kit) codebases").argument("<migration>", "migration to run").option("-C, --cwd <path>", "path to working directory", process.cwd()).configureHelp({ formatHelp() { runMigrate(process.cwd(), ["--help"]); return ""; } }).action((migration, options$1) => { runMigrate(options$1.cwd, [migration]); }); function runMigrate(cwd, args) { const pm = getUserAgent() ?? "npm"; try { const cmdArgs = ["svelte-migrate@latest", ...args]; if (pm === "npm") cmdArgs.unshift("--yes"); const cmd = resolveCommand(pm, "execute", cmdArgs); execSync(`${cmd.command} ${cmd.args.join(" ")}`, { stdio: "inherit", cwd }); } catch (error) { forwardExitCode(error); } } //#endregion //#region packages/cli/commands/check.ts var import_picocolors = __toESM(require_picocolors(), 1); const check = new Command("check").description("a CLI for checking your Svelte code").allowUnknownOption(true).allowExcessArguments(true).option("-C, --cwd <path>", "path to working directory", process.cwd()).configureHelp({ formatHelp() { runCheck(process.cwd(), ["--help"]); return ""; } }).action((options$1, check$1) => { const cwd = options$1.cwd; const args = check$1.args; runCheck(cwd, args); }); function runCheck(cwd, args) { const pm = getUserAgent() ?? "npm"; const resolved = from(cwd, "svelte-check", true); if (!resolved) { const cmd = resolveCommand(pm, "add", ["-D", "svelte-check"]); console.error(`'svelte-check' is not installed locally. Install it with: ${import_picocolors.default.bold(`${cmd.command} ${cmd.args.join(" ")}`)}`); process.exit(1); } try { const cmd = resolveCommand(pm, "execute-local", ["svelte-check", ...args]); execSync(`${cmd.command} ${cmd.args.join(" ")}`, { stdio: "inherit", cwd }); } catch (error) { forwardExitCode(error); } } //#endregion //#region packages/cli/bin.ts program.name(package_default.name).version(package_default.version, "-v, --version").configureHelp(helpConfig); program.addCommand(create$1).addCommand(add).addCommand(migrate).addCommand(check); program.parse(); //#endregion