sv 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/{shared.json → addons/ai-tools.json} +18 -199
  2. package/dist/{addons-DaktRhvH.mjs → addons-BCioxI2r.mjs} +134 -53
  3. package/dist/bin.mjs +39 -27
  4. package/dist/{engine-CpcTzL2R.mjs → engine-S8UpCG24.mjs} +81 -119
  5. package/dist/{engine-BLZdMrao.d.mts → engine-fNvU_-IL.d.mts} +3 -2
  6. package/dist/playground/PlaygroundLayout.svelte +218 -0
  7. package/dist/src/index.d.mts +36 -6
  8. package/dist/src/index.mjs +2 -2
  9. package/dist/src/testing.d.mts +1 -1
  10. package/dist/src/testing.mjs +1 -1
  11. package/dist/templates/addon/assets/CONTRIBUTING.md +48 -0
  12. package/dist/templates/addon/assets/README.md +29 -0
  13. package/dist/templates/addon/files.types=checkjs.json +10 -1
  14. package/dist/templates/addon/files.types=none.json +10 -1
  15. package/dist/templates/addon/files.types=typescript.json +10 -1
  16. package/dist/templates/addon/package.json +22 -16
  17. package/dist/templates/demo/assets/README.md +49 -0
  18. package/dist/templates/demo/files.types=checkjs.json +12 -0
  19. package/dist/templates/demo/files.types=none.json +12 -0
  20. package/dist/templates/demo/files.types=typescript.json +12 -0
  21. package/dist/templates/demo/package.json +11 -7
  22. package/dist/templates/library/assets/README.md +72 -0
  23. package/dist/templates/library/files.types=checkjs.json +12 -0
  24. package/dist/templates/library/files.types=none.json +12 -0
  25. package/dist/templates/library/files.types=typescript.json +12 -0
  26. package/dist/templates/library/package.json +29 -18
  27. package/dist/templates/minimal/assets/README.md +49 -0
  28. package/dist/templates/minimal/files.types=checkjs.json +12 -0
  29. package/dist/templates/minimal/files.types=none.json +12 -0
  30. package/dist/templates/minimal/files.types=typescript.json +12 -0
  31. package/dist/templates/minimal/package.json +11 -7
  32. package/package.json +4 -8
  33. package/dist/templates/demo/assets/DOT-npmrc +0 -1
  34. package/dist/templates/library/assets/DOT-npmrc +0 -1
  35. package/dist/templates/minimal/assets/DOT-npmrc +0 -1
@@ -1,4 +1,4 @@
1
- import { D as getSharedFiles, pt as R, v as defineAddon, y as defineAddonOptions } from "./engine-CpcTzL2R.mjs";
1
+ import { T as dist_dir, _ as defineAddon, pt as R, v as defineAddonOptions } from "./engine-S8UpCG24.mjs";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { KIT3_TSCONFIG, Walker, coerceVersion, color, createPrinter, dedent, defineDemoPage, defineEnv, fileExists, isKit3, loadPackageJson, pnpm, resolveCommandArray, resolveLibPrefix, sanitizeName, svelteConfig, transforms } from "@sveltejs/sv-utils";
@@ -56,6 +56,11 @@ const CLIENTS = {
56
56
  agentPath: "AGENTS.md",
57
57
  configPath: ".cursor/mcp.json",
58
58
  skillsPath: ".cursor/skills",
59
+ alsoReadsSkillsFrom: [
60
+ ".agents/skills",
61
+ ".claude/skills",
62
+ ".codex/skills"
63
+ ],
59
64
  agentsPath: ".cursor/agents",
60
65
  mcpOptions: {}
61
66
  },
@@ -64,6 +69,7 @@ const CLIENTS = {
64
69
  agentPath: "GEMINI.md",
65
70
  configPath: ".gemini/settings.json",
66
71
  skillsPath: ".gemini/skills",
72
+ alsoReadsSkillsFrom: [".agents/skills"],
67
73
  agentsPath: ".gemini/agents",
68
74
  schema: "https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json",
69
75
  mcpOptions: {}
@@ -73,6 +79,8 @@ const CLIENTS = {
73
79
  pluginOnly: true,
74
80
  agentPath: "AGENTS.md",
75
81
  configPath: ".opencode/opencode.json",
82
+ skillsPath: ".opencode/skills",
83
+ alsoReadsSkillsFrom: [".agents/skills"],
76
84
  schema: "https://opencode.ai/config.json",
77
85
  customData: { plugin: ["@sveltejs/opencode"] },
78
86
  extraFiles: [{
@@ -80,11 +88,26 @@ const CLIENTS = {
80
88
  data: { $schema: "https://svelte.dev/opencode/schema.json" }
81
89
  }]
82
90
  },
91
+ pi: {
92
+ label: "Pi",
93
+ pluginOnly: true,
94
+ agentPath: "AGENTS.md",
95
+ configPath: ".pi/settings.json",
96
+ skillsPath: ".pi/skills",
97
+ alsoReadsSkillsFrom: [".agents/skills"],
98
+ schema: "https://pi.dev/schemas/settings.schema.json",
99
+ customData: { packages: ["npm:@sveltejs/pi"] },
100
+ extraFiles: [{
101
+ path: ".pi/svelte.json",
102
+ data: { $schema: "https://svelte.dev/pi/schema.json" }
103
+ }]
104
+ },
83
105
  vscode: {
84
106
  label: "VS Code",
85
107
  agentPath: "AGENTS.md",
86
108
  configPath: ".vscode/mcp.json",
87
109
  skillsPath: ".github/skills",
110
+ alsoReadsSkillsFrom: [".claude/skills", ".agents/skills"],
88
111
  agentsPath: ".github/agents",
89
112
  agentExtension: ".agent.md",
90
113
  mcpOptions: { serversKey: "servers" }
@@ -118,6 +141,29 @@ const TOOLS = {
118
141
  hint: "sub-agent"
119
142
  }
120
143
  };
144
+ const OPT_IN_SKILLS = { "svelte-mcp-feedback": {
145
+ label: "svelte-mcp-feedback",
146
+ hint: "reports Svelte mistakes you correct to sveltejs/ai-tools"
147
+ } };
148
+ const skillDirsOf = (client) => client.skillsPath ? [client.skillsPath, ...client.alsoReadsSkillsFrom ?? []] : [];
149
+ const hasSkillIn = (cwd, dir, skill) => fs.existsSync(path.resolve(cwd, dir, skill, "SKILL.md"));
150
+ const isSkillInstalled = (cwd, client, skill) => skillDirsOf(client).some((dir) => hasSkillIn(cwd, dir, skill));
151
+ const readersOf = (dir) => Object.values(CLIENTS).filter((client) => skillDirsOf(client).includes(dir)).length;
152
+ const optInSkillsQuestion = (isInstalled = () => false) => {
153
+ const missingFor = (ides) => Object.keys(OPT_IN_SKILLS).filter((skill) => ides.some((i) => CLIENTS[i]?.skillsPath && !isInstalled(CLIENTS[i], skill)));
154
+ return {
155
+ question: "Would you like to add any opt-in skills?",
156
+ type: "multiselect",
157
+ default: [],
158
+ options: missingFor(Object.keys(CLIENTS)).map((value) => ({
159
+ value,
160
+ label: OPT_IN_SKILLS[value].label,
161
+ hint: OPT_IN_SKILLS[value].hint
162
+ })),
163
+ required: false,
164
+ condition: ({ ide }) => missingFor(ide).length > 0
165
+ };
166
+ };
121
167
  const options$8 = defineAddonOptions().add("ide", {
122
168
  question: "Which client would you like to use?",
123
169
  type: "multiselect",
@@ -166,18 +212,22 @@ const options$8 = defineAddonOptions().add("ide", {
166
212
  hint: "will use a remote endpoint"
167
213
  }],
168
214
  condition: ({ ide, delivery, tools }) => (tools === void 0 || tools.includes("mcp")) && ide.some((i) => wantsMcpConfig(CLIENTS[i], delivery))
169
- }).build();
215
+ }).add("optInSkills", optInSkillsQuestion()).build();
170
216
  const addon$8 = defineAddon({
171
217
  id: ADDON_IDS.aiTools,
172
218
  shortDescription: "Svelte AI tools",
173
219
  homepage: "https://svelte.dev/docs/ai",
174
220
  options: options$8,
175
- run: ({ sv, options }) => {
221
+ setup: ({ cwd, addOption }) => {
222
+ addOption("optInSkills", optInSkillsQuestion((client, skill) => isSkillInstalled(cwd, client, skill)));
223
+ },
224
+ run: ({ sv, options, cwd }) => {
176
225
  const usePlugin = options.delivery === "plugin";
177
226
  const selected = options.tools ?? Object.keys(TOOLS);
178
227
  const selectedSkills = selected.filter((t) => TOOLS[t]?.kind === "skill");
179
228
  const selectedAgents = selected.filter((t) => TOOLS[t]?.kind === "agent");
180
229
  const wantsMcp = selected.includes("mcp");
230
+ const selectedOptInSkills = (options.optInSkills ?? []).filter((s) => s in OPT_IN_SKILLS);
181
231
  const getLocalConfig = (o) => {
182
232
  return {
183
233
  ...o?.typeLocal ? { type: o.typeLocal } : {},
@@ -194,10 +244,14 @@ const addon$8 = defineAddon({
194
244
  };
195
245
  const filesAdded = [];
196
246
  const filesExistingAlready = [];
197
- const sharedFiles = getSharedFiles();
198
- const mcpFiles = sharedFiles.filter((file) => file.include.includes("mcp"));
199
- const skillFiles = sharedFiles.filter((file) => file.include.includes("skills"));
200
- const agentFiles = sharedFiles.filter((file) => file.include.includes("agents"));
247
+ const { files } = JSON.parse(fs.readFileSync(path.join(dist_dir, "addons", "ai-tools.json"), "utf-8"));
248
+ const at = (kind) => files.filter((file) => file.name.startsWith(`${kind}/`)).map((file) => ({
249
+ ...file,
250
+ name: file.name.slice(kind.length + 1)
251
+ }));
252
+ const mcpFiles = at("mcp");
253
+ const skillFiles = at("skills");
254
+ const agentFiles = at("agents");
201
255
  const agentFile = mcpFiles.find((file) => file.name === "AGENTS.md");
202
256
  const addFile = (path, contents) => {
203
257
  sv.file(path, (content) => {
@@ -209,6 +263,16 @@ const addon$8 = defineAddon({
209
263
  return contents;
210
264
  });
211
265
  };
266
+ const optInClients = options.ide.map((ide) => CLIENTS[ide]).filter((client) => Boolean(client?.skillsPath)).sort((a, b) => readersOf(b.skillsPath) - readersOf(a.skillsPath));
267
+ for (const skill of selectedOptInSkills) {
268
+ const files = skillFiles.filter((file) => file.name.split("/")[0] === skill);
269
+ const writtenDirs = /* @__PURE__ */ new Set();
270
+ for (const client of optInClients) {
271
+ if (skillDirsOf(client).some((dir) => writtenDirs.has(dir) || hasSkillIn(cwd, dir, skill))) continue;
272
+ for (const file of files) addFile(`${client.skillsPath}/${file.name}`, file.contents);
273
+ writtenDirs.add(client.skillsPath);
274
+ }
275
+ }
212
276
  for (const ide of options.ide) {
213
277
  const client = CLIENTS[ide];
214
278
  if (!client) continue;
@@ -249,11 +313,12 @@ const addon$8 = defineAddon({
249
313
  if (extraFiles) for (const extra of extraFiles) sv.file(extra.path, transforms.json(({ data }) => {
250
314
  for (const [key, value] of Object.entries(extra.data)) data[key] = value;
251
315
  }));
252
- if (skillsPath) for (const file of skillFiles) {
316
+ const looseTools = wantsLooseTools(client, options.delivery);
317
+ if (skillsPath && looseTools) for (const file of skillFiles) {
253
318
  if (!selectedSkills.includes(file.name.split("/")[0])) continue;
254
319
  addFile(`${skillsPath}/${file.name}`, file.contents);
255
320
  }
256
- if (agentsPath) for (const file of agentFiles) {
321
+ if (agentsPath && looseTools) for (const file of agentFiles) {
257
322
  if (!selectedAgents.includes(file.name.replace(REGEX_MD, ""))) continue;
258
323
  const ext = agentExtension ?? ".md";
259
324
  addFile(`${agentsPath}/${file.name.replace(REGEX_MD, ext)}`, file.contents);
@@ -713,11 +778,11 @@ const addon$7 = defineAddon({
713
778
  }
714
779
  });
715
780
  const generateEnv$1 = (demoGithub, isExample) => transforms.text(({ content, text }) => {
716
- content = text.upsert(content, "ORIGIN", {
781
+ content = text.add(content, "ORIGIN", {
717
782
  value: isExample ? `""` : `"http://localhost:5173"`,
718
783
  separator: true
719
784
  });
720
- content = text.upsert(content, "BETTER_AUTH_SECRET", {
785
+ content = text.add(content, "BETTER_AUTH_SECRET", {
721
786
  value: isExample ? `""` : `"${crypto.randomUUID()}"`,
722
787
  comment: [
723
788
  "Better Auth",
@@ -727,11 +792,11 @@ const generateEnv$1 = (demoGithub, isExample) => transforms.text(({ content, tex
727
792
  separator: true
728
793
  });
729
794
  if (demoGithub) {
730
- content = text.upsert(content, "GITHUB_CLIENT_ID", {
795
+ content = text.add(content, "GITHUB_CLIENT_ID", {
731
796
  value: `""`,
732
797
  comment: "GitHub OAuth\n# https://www.better-auth.com/docs/authentication/github"
733
798
  });
734
- content = text.upsert(content, "GITHUB_CLIENT_SECRET", { value: `""` });
799
+ content = text.add(content, "GITHUB_CLIENT_SECRET", { value: `""` });
735
800
  }
736
801
  return content;
737
802
  });
@@ -1052,10 +1117,10 @@ const addon$6 = defineAddon({
1052
1117
  json.packageScriptsUpsert(data, "db:migrate", "drizzle-kit migrate");
1053
1118
  json.packageScriptsUpsert(data, "db:studio", "drizzle-kit studio");
1054
1119
  }));
1055
- if (Boolean(dependencyVersion("prettier"))) sv.file(".prettierignore", transforms.text(({ content, text }) => text.upsert(content, "/drizzle/")));
1120
+ if (Boolean(dependencyVersion("prettier"))) sv.file(".prettierignore", transforms.text(({ content, text }) => text.add(content, "/drizzle/")));
1056
1121
  if (options.database === "sqlite") sv.file(file.gitignore, transforms.text(({ content, text }) => {
1057
1122
  if (content.length === 0) return false;
1058
- return text.upsert(content, "*.db", { comment: "SQLite" });
1123
+ return text.add(content, "*.db", { comment: "SQLite" });
1059
1124
  }));
1060
1125
  sv.file(paths["drizzle config"], transforms.script(({ ast, js }) => {
1061
1126
  const d1 = options.database === "d1";
@@ -1333,13 +1398,13 @@ const addon$6 = defineAddon({
1333
1398
  const generateEnv = (opts, isExample) => transforms.text(({ content, text }) => {
1334
1399
  const DB_URL_KEY = "DATABASE_URL";
1335
1400
  if (opts.database === "d1") {
1336
- content = text.upsert(content, "CLOUDFLARE_ACCOUNT_ID", {
1401
+ content = text.add(content, "CLOUDFLARE_ACCOUNT_ID", {
1337
1402
  value: "\"\"",
1338
1403
  comment: ["Cloudflare D1"],
1339
1404
  separator: true
1340
1405
  });
1341
- content = text.upsert(content, "CLOUDFLARE_DATABASE_ID", { value: "\"\"" });
1342
- content = text.upsert(content, "CLOUDFLARE_D1_TOKEN", { value: "\"\"" });
1406
+ content = text.add(content, "CLOUDFLARE_DATABASE_ID", { value: "\"\"" });
1407
+ content = text.add(content, "CLOUDFLARE_D1_TOKEN", { value: "\"\"" });
1343
1408
  return content;
1344
1409
  }
1345
1410
  let value;
@@ -1371,12 +1436,12 @@ const generateEnv = (opts, isExample) => transforms.text(({ content, text }) =>
1371
1436
  value = "\"postgres://user:password@host:port/db-name\"";
1372
1437
  comment.push("Replace with your DB credentials!");
1373
1438
  } else value = "";
1374
- content = text.upsert(content, DB_URL_KEY, {
1439
+ content = text.add(content, DB_URL_KEY, {
1375
1440
  value,
1376
1441
  comment,
1377
1442
  separator: true
1378
1443
  });
1379
- if (opts.sqlite === "turso") content = text.upsert(content, "DATABASE_AUTH_TOKEN", { value: isExample ? `""` : `"${crypto.randomUUID()}"` });
1444
+ if (opts.sqlite === "turso") content = text.add(content, "DATABASE_AUTH_TOKEN", { value: isExample ? `""` : `"${crypto.randomUUID()}"` });
1380
1445
  return content;
1381
1446
  });
1382
1447
  //#endregion
@@ -1448,6 +1513,13 @@ var eslint_default = defineAddon({
1448
1513
  language,
1449
1514
  entry: "node"
1450
1515
  });
1516
+ addToTypeConfigInclude({
1517
+ sv,
1518
+ cwd,
1519
+ language,
1520
+ kitVersion: dependencyVersion("@sveltejs/kit"),
1521
+ entry: "eslint.config.js"
1522
+ });
1451
1523
  if (typescript) sv.devDependency("typescript-eslint", "^8.60.1");
1452
1524
  if (prettierInstalled) sv.devDependency("eslint-config-prettier", "^10.1.8");
1453
1525
  sv.file(file.package, transforms.json(({ data, json }) => {
@@ -1766,8 +1838,8 @@ const addon$4 = defineAddon({
1766
1838
  }));
1767
1839
  sv.file(file.gitignore, transforms.text(({ content, text }) => {
1768
1840
  if (!content) return false;
1769
- content = text.upsert(content, paraglideOutDir, { comment: "Paraglide" });
1770
- content = text.upsert(content, "project.inlang/cache/");
1841
+ content = text.add(content, paraglideOutDir, { comment: "Paraglide" });
1842
+ content = text.add(content, "project.inlang/cache/");
1771
1843
  return content;
1772
1844
  }));
1773
1845
  const { validLanguageTags } = parseLanguageTagInput(options.languageTags);
@@ -1864,7 +1936,7 @@ const addon$3 = defineAddon({
1864
1936
  }));
1865
1937
  sv.file(file.gitignore, transforms.text(({ content, text }) => {
1866
1938
  if (!content) return false;
1867
- return text.upsert(content, "test-results", { comment: "Playwright" });
1939
+ return text.add(content, "test-results", { comment: "Playwright" });
1868
1940
  }));
1869
1941
  const withDemo = isKit && options.demo;
1870
1942
  const demo = defineDemoPage("playwright", language, directory.kitRoutes);
@@ -1933,7 +2005,7 @@ var prettier_default = defineAddon({
1933
2005
  shortDescription: "formatter",
1934
2006
  homepage: "https://prettier.io",
1935
2007
  options: {},
1936
- run: ({ sv, dependencyVersion, file }) => {
2008
+ run: ({ sv, dependencyVersion, file, cwd, language }) => {
1937
2009
  const tailwindcssInstalled = Boolean(dependencyVersion("tailwindcss"));
1938
2010
  if (tailwindcssInstalled) sv.devDependency("prettier-plugin-tailwindcss", "^0.8.0");
1939
2011
  sv.devDependency("prettier", "^3.8.3");
@@ -1956,6 +2028,13 @@ var prettier_default = defineAddon({
1956
2028
  tailwind: tailwindcssInstalled,
1957
2029
  stylesheet: tailwindcssInstalled ? file.getRelative({ to: file.stylesheet }) : void 0
1958
2030
  }));
2031
+ addToTypeConfigInclude({
2032
+ sv,
2033
+ cwd,
2034
+ language,
2035
+ kitVersion: dependencyVersion("@sveltejs/kit"),
2036
+ entry: prettierConfigPath()
2037
+ });
1959
2038
  const eslintInfo = checkEslint(dependencyVersion("eslint"));
1960
2039
  sv.file(file.package, transforms.json(({ data, json }) => {
1961
2040
  json.packageScriptsUpsert(data, "lint", "prettier --check .", { mode: "prepend" });
@@ -1993,12 +2072,13 @@ var storybook_default = defineAddon({
1993
2072
  runsAfter(ADDON_IDS.vitest);
1994
2073
  runsAfter(ADDON_IDS.eslint);
1995
2074
  },
1996
- run: async ({ sv }) => {
2075
+ run: async ({ sv, packageManager }) => {
1997
2076
  const args = [
1998
2077
  `create-storybook@latest`,
1999
2078
  "--skip-install",
2000
2079
  "--no-dev"
2001
2080
  ];
2081
+ if (packageManager === "pnpm") args.unshift("--allow-build=esbuild");
2002
2082
  if (V.env.NODE_ENV?.toLowerCase() === "test") args.push(...["--no-features", "--yes"]);
2003
2083
  await sv.execute(args, "inherit");
2004
2084
  sv.devDependency(`@types/node`, getNodeTypesVersion());
@@ -2017,6 +2097,11 @@ const adapters = [
2017
2097
  package: "@sveltejs/adapter-node",
2018
2098
  version: "^6.0.0"
2019
2099
  },
2100
+ {
2101
+ id: "bun",
2102
+ package: "@sveltejs/adapter-bun",
2103
+ version: "^1.0.0"
2104
+ },
2020
2105
  {
2021
2106
  id: "static",
2022
2107
  package: "@sveltejs/adapter-static",
@@ -2074,14 +2159,6 @@ const addon$2 = defineAddon({
2074
2159
  },
2075
2160
  run: ({ sv, options, packageManager, file, cwd, language }) => {
2076
2161
  const adapter = adapters.find((a) => a.id === options.adapter);
2077
- sv.file(file.package, transforms.json(({ data }) => {
2078
- const devDeps = data["devDependencies"];
2079
- for (const pkg of Object.keys(devDeps)) if (pkg.startsWith("@sveltejs/adapter-")) delete devDeps[pkg];
2080
- if (options.adapter === "cloudflare") {
2081
- const preview = options.cfTarget === "workers" ? "wrangler dev .svelte-kit/cloudflare/_worker.js --port 4173" : "wrangler pages dev .svelte-kit/cloudflare --port 4173";
2082
- data.scripts.preview = preview;
2083
- }
2084
- }));
2085
2162
  sv.devDependency(adapter.package, adapter.version);
2086
2163
  if (options.adapter === "node") {
2087
2164
  sv.devDependency("@types/node", getNodeTypesVersion());
@@ -2092,14 +2169,16 @@ const addon$2 = defineAddon({
2092
2169
  entry: "node"
2093
2170
  });
2094
2171
  }
2095
- if (adapter.package !== "@sveltejs/adapter-auto") sv.file("README.md", transforms.text(({ content }) => content.replace(ADAPTER_HINT_REGEX, "")));
2172
+ let previousAdapter;
2096
2173
  svelteConfig.edit({
2097
2174
  sv,
2098
2175
  cwd
2099
2176
  }, ({ ast, override, js }) => {
2100
- const adapterImports = ast.body.filter((n) => n.type === "ImportDeclaration").find((importDecl) => typeof importDecl.source.value === "string" && importDecl.source.value.startsWith("@sveltejs/adapter-") && importDecl.importKind === "value");
2177
+ const adapterImports = ast.body.filter((node) => node.type === "ImportDeclaration").find((importDecl) => typeof importDecl.source.value === "string" && importDecl.source.value.startsWith("@sveltejs/adapter-") && importDecl.importKind === "value");
2101
2178
  let adapterName = "adapter";
2102
2179
  if (adapterImports) {
2180
+ const foundPackage = adapterImports.source.value;
2181
+ if (typeof foundPackage === "string" && foundPackage !== adapter.package) previousAdapter = foundPackage;
2103
2182
  adapterImports.source.value = adapter.package;
2104
2183
  adapterImports.source.raw = void 0;
2105
2184
  adapterName = (adapterImports.specifiers?.find((s) => s.type === "ImportDefaultSpecifier")).local.name;
@@ -2113,6 +2192,13 @@ const addon$2 = defineAddon({
2113
2192
  useIdentifiers: true
2114
2193
  }) }, adapter.package === "@sveltejs/adapter-auto" ? void 0 : { dropLeadingComments: ["adapter"] });
2115
2194
  });
2195
+ sv.file(file.package, transforms.json(({ data }) => {
2196
+ if (previousAdapter) {
2197
+ delete data["devDependencies"]?.[previousAdapter];
2198
+ delete data["dependencies"]?.[previousAdapter];
2199
+ }
2200
+ }));
2201
+ if (adapter.package !== "@sveltejs/adapter-auto") sv.file("README.md", transforms.text(({ content }) => content.replace(ADAPTER_HINT_REGEX, "")));
2116
2202
  if (adapter.package === "@sveltejs/adapter-cloudflare") {
2117
2203
  sv.devDependency("wrangler", "^4.97.0");
2118
2204
  if (packageManager === "pnpm") sv.file(file.findUp("pnpm-workspace.yaml"), pnpm.allowBuilds({
@@ -2142,25 +2228,20 @@ const addon$2 = defineAddon({
2142
2228
  }
2143
2229
  };
2144
2230
  sv.file(`wrangler.${ext}`, ext === "toml" ? transforms.toml(({ data }) => applyWranglerConfig(data)) : transforms.json(({ data }) => applyWranglerConfig(data)));
2145
- if (file.typeConfig) {
2146
- sv.file(file.package, transforms.json(({ data, json }) => {
2231
+ sv.file(file.package, transforms.json(({ data, json }) => {
2232
+ if (file.typeConfig) {
2147
2233
  json.packageScriptsUpsert(data, "gen", "wrangler types");
2148
2234
  json.packageScriptsUpsert(data, "check", "wrangler types --check", { mode: "prepend" });
2149
2235
  json.packageScriptsUpsert(data, "build", "wrangler types --check", { mode: "prepend" });
2150
- }));
2151
- addToTypeConfigType({
2152
- sv,
2153
- cwd,
2154
- language,
2155
- entry: "./worker-configuration.d.ts"
2156
- });
2157
- sv.file("src/app.d.ts", transforms.script(({ ast, comments, js }) => {
2158
- const platform = js.kit.addGlobalAppInterface(ast, { name: "Platform" });
2159
- if (!platform) throw new Error("Failed detecting `platform` interface in `src/app.d.ts`");
2160
- comments.remove((c) => c.type === "Line" && c.value.trim() === "interface Platform {}");
2161
- platform.body.body.push(js.common.createTypeProperty("env", "Env"), js.common.createTypeProperty("ctx", "ExecutionContext"), js.common.createTypeProperty("caches", "CacheStorage"), js.common.createTypeProperty("cf", "IncomingRequestCfProperties", true));
2162
- }));
2163
- }
2236
+ }
2237
+ data.scripts.preview = options.cfTarget === "workers" ? "wrangler dev .svelte-kit/cloudflare/_worker.js --port 4173" : "wrangler pages dev .svelte-kit/cloudflare --port 4173";
2238
+ }));
2239
+ if (file.typeConfig) addToTypeConfigType({
2240
+ sv,
2241
+ cwd,
2242
+ language,
2243
+ entry: "./worker-configuration.d.ts"
2244
+ });
2164
2245
  }
2165
2246
  },
2166
2247
  nextSteps({ options, packageManager }) {
@@ -2304,10 +2385,10 @@ const addon = defineAddon({
2304
2385
  json.packageScriptsUpsert(data, "test", "npm run test:unit -- --run", { mode: "prepend" });
2305
2386
  }));
2306
2387
  const examplesDir = `${directory.lib}/vitest-examples`;
2307
- const [ts] = createPrinter(language === "ts");
2388
+ const [ts, jsdocs] = createPrinter(language === "ts", !!file.typeConfig?.endsWith("jsconfig.json"));
2308
2389
  if (unitTesting || componentTesting) sv.file(`${examplesDir}/greet.${language}`, transforms.text(({ content }) => {
2309
2390
  if (content) return false;
2310
- return dedent`
2391
+ return dedent`${jsdocs("/** @param {string} name */\n")}
2311
2392
  export function greet(name${ts(": string")})${ts(": string")} {
2312
2393
  return 'Hello, ' + name + '!';
2313
2394
  }
package/dist/bin.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { $ as parse$1, A as cliOptions, B as runAndValidateVerifications, C as execSync, D as getSharedFiles, E as dist, F as getPadding, G as UnsupportedError, H as updateAgent, I as helpConfig, J as array, K as name, L as isNodeError, M as filePaths, N as forwardExitCode, O as PackageJSONSchema, P as getHelpSections, Q as optional, R as normalizePosix, S as exec, T as templates, U as updateLibraryBuild, V as runCommand, W as updateReadme, X as literal, Y as boolean, Z as nonEmpty, _ as createLoadedAddon, _t as pe, a as setupAddons, at as string, b as getErrorHint, bt as R$1, c as AGENT_NAMES, ct as program, d as installDependencies, dt as Ce, et as picklist, f as installOption, ft as Ee, g as from, gt as le, h as up$2, ht as ge, i as prepareSvApi, it as strictObject, j as errorAndExit, k as buildAndLogArgs, l as addAllowBuildsIfPnpm, lt as Command, m as up$1, mt as Re, n as applyAddons, nt as record, o as createWorkspace, ot as transform, p as packageManagerPrompt, pt as R, q as version, r as orderAddons, rt as safeParse, s as findWorkspaceRoot, st as union, tt as pipe, u as detectPackageManager, ut as Option, vt, w as create$1, x as NonZeroExitError, yt as we, z as parseAddonOptions } from "./engine-CpcTzL2R.mjs";
3
- import { n as officialAddons$1, t as getAddonDetails } from "./addons-DaktRhvH.mjs";
2
+ import { $ as parse$1, A as cliOptions, B as runAndValidateVerifications, C as create$1, D as from, E as up$2, F as getPadding, G as UnsupportedError, H as updateAgent, I as helpConfig, J as array, K as name, L as isNodeError, M as filePaths, N as forwardExitCode, O as PackageJSONSchema, P as getHelpSections, Q as optional, R as normalizePosix, S as execSync, T as dist_dir, U as updateLibraryBuild, V as runCommand, W as updateReadme, X as literal, Y as boolean, Z as nonEmpty, _t as pe, a as setupAddons, at as string, b as NonZeroExitError, bt as R$1, c as AGENT_NAMES, ct as program, d as detectPackageManager, dt as Ce, et as picklist, f as installDependencies, ft as Ee, g as createLoadedAddon, gt as le, h as up$1, ht as ge, i as prepareSvApi, it as strictObject, j as errorAndExit, k as buildAndLogArgs, l as addAllowBuildsIfPnpm, lt as Command, m as packageManagerPrompt, mt as Re, n as applyAddons, nt as record, o as createWorkspace, ot as transform, p as installOption, pt as R, q as version, r as orderAddons, rt as safeParse, s as findWorkspaceRoot, st as union, tt as pipe, u as addEngineStrict, ut as Option, vt, w as templates, x as exec, y as getErrorHint, yt as we, z as parseAddonOptions } from "./engine-S8UpCG24.mjs";
3
+ import { n as officialAddons$1, t as getAddonDetails } from "./addons-BCioxI2r.mjs";
4
4
  import * as fs$1 from "node:fs";
5
5
  import fs from "node:fs";
6
6
  import * as path$1 from "node:path";
@@ -2063,25 +2063,19 @@ function setupPlaygroundProject(url, playground, cwd, installDependencies) {
2063
2063
  fs.writeFileSync(filePath, file.content, "utf8");
2064
2064
  }
2065
2065
  {
2066
- const playgroundFiles = getSharedFiles().filter((file) => file.include.includes("playground"));
2067
- for (const file of playgroundFiles) {
2068
- let contentToWrite = file.contents;
2069
- if (file.name === "src/lib/PlaygroundLayout.svelte") {
2070
- const { ast, generateCode } = parse.svelte(file.contents);
2071
- svelte.ensureScript(ast);
2072
- Walker.walk(ast.instance.content, null, { Literal(node) {
2073
- if (node.value === "$sv-title-$sv") {
2074
- node.value = playground.name;
2075
- node.raw = void 0;
2076
- } else if (node.value === "$sv-url-$sv") {
2077
- node.value = url;
2078
- node.raw = void 0;
2079
- }
2080
- } });
2081
- contentToWrite = generateCode();
2066
+ const layout = fs.readFileSync(path.join(dist_dir, "playground", "PlaygroundLayout.svelte"), "utf-8");
2067
+ const { ast, generateCode } = parse.svelte(layout);
2068
+ svelte.ensureScript(ast);
2069
+ Walker.walk(ast.instance.content, null, { Literal(node) {
2070
+ if (node.value === "$sv-title-$sv") {
2071
+ node.value = playground.name;
2072
+ node.raw = void 0;
2073
+ } else if (node.value === "$sv-url-$sv") {
2074
+ node.value = url;
2075
+ node.raw = void 0;
2082
2076
  }
2083
- fs.writeFileSync(path.join(cwd, file.name), contentToWrite, "utf-8");
2084
- }
2077
+ } });
2078
+ fs.writeFileSync(path.join(cwd, "src/lib/PlaygroundLayout.svelte"), generateCode(), "utf-8");
2085
2079
  }
2086
2080
  const filePath = path.join(cwd, "src/routes/+page.svelte");
2087
2081
  const content = fs.readFileSync(filePath, "utf-8");
@@ -2380,6 +2374,10 @@ async function createProject(cwd, options) {
2380
2374
  updateReadme(directory, prompt);
2381
2375
  updateAgent(directory, language, packageManager ?? "npm", loadedAddons);
2382
2376
  const addOnNextSteps = getNextSteps(addOnSuccessfulAddons, workspace, answers, addonSetupResults);
2377
+ addEngineStrict({
2378
+ cwd: projectPath,
2379
+ packageManager
2380
+ });
2383
2381
  addAllowBuildsIfPnpm({
2384
2382
  cwd: projectPath,
2385
2383
  packageManager,
@@ -2431,7 +2429,7 @@ async function createVirtualWorkspace({ cwd, template, type }) {
2431
2429
  kitRoutes: "src/routes"
2432
2430
  };
2433
2431
  }
2434
- const templatePackageJsonPath = dist(`templates/${template}`);
2432
+ const templatePackageJsonPath = path.join(dist_dir, "templates", template);
2435
2433
  const { data: packageJson } = loadPackageJson(templatePackageJsonPath);
2436
2434
  override.dependencies = {
2437
2435
  ...packageJson.devDependencies,
@@ -3085,6 +3083,16 @@ const migrationTasks = [
3085
3083
  summary: "Cross-origin mutative requests without a `Content-Type` header are rejected as CSRF.",
3086
3084
  instructions: "Ensure the submission sends an appropriate `Content-Type` header or add the destination to the narrowest possible `csrf.trustedOrigins` allowlist.",
3087
3085
  links: guideLink("Security-Cross-origin-form-submissions-require-a-Content-Type-header")
3086
+ },
3087
+ {
3088
+ title: "Review `redirect(...)` destinations for external URLs",
3089
+ checks: [{
3090
+ include: CODE_FILES,
3091
+ patterns: [/^(?=[\s\S]*['"]@sveltejs\/kit['"])(?=[\s\S]*\bredirect\s*\()/]
3092
+ }],
3093
+ summary: "`redirect(...)` to an external URL now requires an `external` option.",
3094
+ instructions: "The automatic migration only analyzes static URLs. Inspect each `redirect` call: if the destination can be an external URL at runtime, pass `{ external: true }` for a blanket allow, or `{ external: [...] }` for a list of permitted origins, or keep it as is when determining that this should only handle app-internal redirects.",
3095
+ links: guideLink("Miscellaneous-External-redirects-must-be-opted-into")
3088
3096
  }
3089
3097
  ];
3090
3098
  var collect_migration_instructions_default = defineMigrationTask({
@@ -3838,7 +3846,8 @@ function libSubpathImports(libDir) {
3838
3846
  var lib_alias_default = defineMigrationTask({
3839
3847
  id: "lib-alias",
3840
3848
  description: "Replace the $lib alias with #lib subpath imports",
3841
- run: ({ sv, cwd, directory }) => {
3849
+ run: ({ sv, cwd, directory, dependencyVersion }) => {
3850
+ const isLibrary = Boolean(dependencyVersion("@sveltejs/package"));
3842
3851
  sv.file("package.json", transforms.json(({ data }) => {
3843
3852
  data.imports = {
3844
3853
  ...libSubpathImports(directory.lib),
@@ -3855,11 +3864,14 @@ var lib_alias_default = defineMigrationTask({
3855
3864
  match = match.replace("$lib", "#lib");
3856
3865
  if (import_path && !import_path.endsWith(".js") && !import_path.endsWith(".ts")) {
3857
3866
  for (const ending of [
3858
- ".js",
3859
3867
  ".ts",
3860
- "/index.js",
3861
- "/index.ts"
3862
- ]) if (fs.existsSync(path.join(cwd, directory.lib, import_path + ending))) return match.slice(0, -1) + ending.replace(".ts", ".js") + match.slice(-1);
3868
+ ".js",
3869
+ "/index.ts",
3870
+ "/index.js"
3871
+ ]) if (fs.existsSync(path.join(cwd, directory.lib, import_path + ending))) {
3872
+ const specifier = isLibrary ? ending.replace(".ts", ".js") : ending;
3873
+ return match.slice(0, -1) + specifier + match.slice(-1);
3874
+ }
3863
3875
  }
3864
3876
  return match;
3865
3877
  });
@@ -3872,7 +3884,7 @@ var lib_alias_default = defineMigrationTask({
3872
3884
  const KIT3_MIN_VERSIONS = {
3873
3885
  vite: "^8.0.12",
3874
3886
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
3875
- svelte: "^5.57.1",
3887
+ svelte: "^5.57.2",
3876
3888
  "@sveltejs/package": "^3.0.0",
3877
3889
  "svelte-check": "^4.7.5",
3878
3890
  typescript: "^6.0.0"