sv 0.16.6 → 1.0.0-next.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.
package/dist/bin.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { _ as name, a as detectPackageManager, d as Command, g as helpConfig, h as forwardExitCode, l as from, n as add, r as create, u as program, v as version } from "./engine-DbEL5s1q.mjs";
3
- import { color, resolveCommandArray } from "@sveltejs/sv-utils";
2
+ import { A as Command, B as name, C as pipe, D as union, E as transform, F as forwardExitCode, G as ge, H as Ce, I as helpConfig, J as we, K as le, L as normalizePosix, O as from, P as errorAndExit, R as runAndValidateVerifications, S as picklist, T as string, U as Ee, V as version, W as R$1, Y as R, _ as array, a as verifyCleanWorkingDirectory, b as optional, c as AGENT_NAMES, d as installDependencies, f as installOption, i as create, k as program, n as prepareSvApi, o as formatFiles, p as packageManagerPrompt, q as vt, r as add, s as createWorkspace, u as detectPackageManager, v as boolean, w as strictObject, x as parse$1, y as literal, z as runCommand } from "./engine-DlJ4-99M.mjs";
3
+ import { Walker, coerceVersion, color, isVersionUnsupportedBelow, js, loadPackageJson, parse, resolveCommandArray, svelteConfig, transforms } from "@sveltejs/sv-utils";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
4
6
  import V from "node:process";
5
7
  import { execSync } from "node:child_process";
6
8
  //#region src/cli/check.ts
@@ -31,22 +33,2680 @@ async function runCheck(cwd, args) {
31
33
  }
32
34
  }
33
35
  //#endregion
34
- //#region src/cli/migrate.ts
35
- 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", V.cwd()).action(async (migration, options) => {
36
- await runMigrate(options.cwd, [migration]);
36
+ //#region src/migrate/index.ts
37
+ function defineMigration(migration) {
38
+ return migration;
39
+ }
40
+ function defineMigrationTask(task) {
41
+ return task;
42
+ }
43
+ //#endregion
44
+ //#region src/migrate/migration-task.ts
45
+ /** Marker prefixing every manual follow-up a migration leaves in the user's code. */
46
+ const MIGRATION_TASK_MARKER = "@migration-task";
47
+ let migrationTaskCount = 0;
48
+ /** Running total of `@migration-task` comments left in the user's code during this migration run. */
49
+ function getMigrationTaskCount() {
50
+ return migrationTaskCount;
51
+ }
52
+ /** Resets the running total; call before starting a migration run. */
53
+ function resetMigrationTaskCount() {
54
+ migrationTaskCount = 0;
55
+ }
56
+ /**
57
+ * Leaves a standardized `@migration-task` comment for the user to resolve manually, and bumps the
58
+ * running total so the post-migration summary can report it without re-reading files.
59
+ *
60
+ * The placement depends on the `target` you pass:
61
+ * - `{ comments, node }` for a JS/TS AST (`transforms.script`)
62
+ * - `{ fragment }` for a `.svelte` component (`transforms.svelte` / `svelteScript`)
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * addMigrationTask('rewrite this manually', { comments, node });
67
+ * addMigrationTask('rewrite this manually', { fragment: ast.fragment, anchor });
68
+ * ```
69
+ */
70
+ function addMigrationTask(message, target) {
71
+ if ("fragment" in target) addSvelteComment(target.fragment, message, target.anchor);
72
+ else target.comments.add(target.node, {
73
+ type: "Line",
74
+ value: ` ${MIGRATION_TASK_MARKER} ${message}`
75
+ }, target.position ? { position: target.position } : void 0);
76
+ migrationTaskCount++;
77
+ }
78
+ /**
79
+ * Returns a standalone `// @migration-task` line comment for files that are generated from strings
80
+ * (rather than through an AST), and bumps the running total so the post-migration summary can
81
+ * report it. Prefer `addMigrationTask` whenever an AST is available.
82
+ */
83
+ function createMigrationTaskComment(message) {
84
+ migrationTaskCount++;
85
+ return `// ${MIGRATION_TASK_MARKER} ${message}`;
86
+ }
87
+ function addSvelteComment(fragment, message, anchor) {
88
+ const comment = {
89
+ type: "Comment",
90
+ data: ` ${MIGRATION_TASK_MARKER} ${message} `,
91
+ start: 0,
92
+ end: 0
93
+ };
94
+ const newline = {
95
+ type: "Text",
96
+ data: "\n",
97
+ raw: "\n",
98
+ start: 0,
99
+ end: 0
100
+ };
101
+ const index = anchor ? fragment.nodes.indexOf(anchor) : -1;
102
+ if (index === -1) fragment.nodes.unshift(comment, newline);
103
+ else fragment.nodes.splice(index, 0, comment, newline);
104
+ }
105
+ //#endregion
106
+ //#region src/migrate/migrations/app-state/tasks/app-state/migrate.ts
107
+ const OLD_SOURCE$1 = "$app/stores";
108
+ const NEW_SOURCE$1 = "$app/state";
109
+ const NAVIGATING_REVIEW_MESSAGE = "`navigating` is always defined now - review this usage; use `navigating.to` for the pending navigation";
110
+ /** Stores exported from `$app/stores` that we know how to migrate. */
111
+ const KNOWN_STORES = /* @__PURE__ */ new Set([
112
+ "page",
113
+ "navigating",
114
+ "updated",
115
+ "getStores"
116
+ ]);
117
+ /**
118
+ * Migrate `$app/stores` usage to `$app/state` in one Svelte component.
119
+ * Returns whether anything changed.
120
+ */
121
+ function migrateAppState(ast, fragment) {
122
+ const imports = js.imports.findAll(ast, { from: OLD_SOURCE$1 }).filter((found) => found.kind === "static");
123
+ if (imports.length === 0) return false;
124
+ const locals = /* @__PURE__ */ new Map();
125
+ for (const binding of js.imports.bindings(ast, { from: OLD_SOURCE$1 })) {
126
+ if (!KNOWN_STORES.has(binding.imported)) continue;
127
+ locals.set(`$${binding.local}`, {
128
+ name: binding.local,
129
+ store: binding.imported
130
+ });
131
+ }
132
+ if (locals.size === 0) return false;
133
+ const bailReason = findBailReason(ast, locals);
134
+ if (bailReason) {
135
+ addMigrationTask(`could not auto-migrate \`${OLD_SOURCE$1}\` to \`${NEW_SOURCE$1}\`: ${bailReason}; migrate manually`, { fragment });
136
+ return true;
137
+ }
138
+ for (const found of imports) js.imports.setSource(found, NEW_SOURCE$1);
139
+ const state = { needsNavigatingReview: false };
140
+ derefStores(ast, locals, state);
141
+ derefStores(fragment, locals, state);
142
+ if (state.needsNavigatingReview) addMigrationTask(NAVIGATING_REVIEW_MESSAGE, { fragment });
143
+ return true;
144
+ }
145
+ /**
146
+ * Migrate `$app/stores` usage in a `.svelte.ts` / `.svelte.js` module.
147
+ *
148
+ * Modules never used the `$store` auto-subscription syntax, so the only mechanical change is
149
+ * renaming the import. `.subscribe(...)` / `get(store)` reactivity can't be auto-ported to runes,
150
+ * so those are flagged for manual follow-up. Returns whether anything changed.
151
+ */
152
+ function migrateAppStateModule(ast, comments) {
153
+ const imports = js.imports.findAll(ast, { from: OLD_SOURCE$1 }).filter((found) => found.kind === "static");
154
+ if (imports.length === 0) return false;
155
+ const storeNames = /* @__PURE__ */ new Set();
156
+ let getStoresImport;
157
+ for (const binding of js.imports.bindings(ast, { from: OLD_SOURCE$1 })) {
158
+ if (!KNOWN_STORES.has(binding.imported)) continue;
159
+ if (binding.imported === "getStores") getStoresImport = binding.declaration;
160
+ else storeNames.add(binding.local);
161
+ }
162
+ if (storeNames.size === 0 && !getStoresImport) return false;
163
+ for (const found of imports) js.imports.setSource(found, NEW_SOURCE$1);
164
+ if (getStoresImport) addMigrationTask("`getStores()` is not available; migrate manually", {
165
+ comments,
166
+ node: getStoresImport
167
+ });
168
+ Walker.walk(ast, null, {
169
+ MemberExpression(node, ctx) {
170
+ if (node.object.type === "Identifier" && storeNames.has(node.object.name) && node.property.type === "Identifier" && node.property.name === "subscribe") addMigrationTask("convert `" + node.object.name + ".subscribe(...)` to a rune (`$derived`/`$effect`)", {
171
+ comments,
172
+ node: enclosingStatement(ctx.path) ?? node
173
+ });
174
+ ctx.next();
175
+ },
176
+ CallExpression(node, ctx) {
177
+ if (node.callee.type === "Identifier" && node.callee.name === "get" && node.arguments[0]?.type === "Identifier" && storeNames.has(node.arguments[0].name)) addMigrationTask("replace `get(" + node.arguments[0].name + ")` - read the value directly (no longer a store)", {
178
+ comments,
179
+ node: enclosingStatement(ctx.path) ?? node
180
+ });
181
+ ctx.next();
182
+ }
183
+ });
184
+ return true;
185
+ }
186
+ function enclosingStatement(path) {
187
+ for (let i = path.length - 1; i >= 0; i -= 1) {
188
+ const node = path[i];
189
+ if (node.type === "VariableDeclaration" || node.type === "ExpressionStatement" || node.type === "ReturnStatement") return node;
190
+ }
191
+ }
192
+ /**
193
+ * Detect usage that can't be mechanically migrated. Returns a human-readable reason, or
194
+ * `undefined` when the file is safe to migrate.
195
+ */
196
+ function findBailReason(ast, locals) {
197
+ for (const local of locals.values()) if (local.store === "getStores") return "`getStores()` is not available";
198
+ const storeNames = new Set([...locals.values()].map((local) => local.name));
199
+ let reason;
200
+ Walker.walk(ast, null, {
201
+ LabeledStatement(node, ctx) {
202
+ if (node.label.name === "$") reason ??= "legacy reactive statement (`$:`)";
203
+ ctx.next();
204
+ },
205
+ ExportNamedDeclaration(node, ctx) {
206
+ if (node.declaration?.type === "VariableDeclaration" && node.declaration.kind === "let") reason ??= "legacy `export let` props";
207
+ ctx.next();
208
+ },
209
+ CallExpression(node, ctx) {
210
+ if (node.callee.type === "Identifier" && node.callee.name === "derived" && node.arguments[0]?.type === "Identifier" && storeNames.has(node.arguments[0].name)) reason ??= "`derived(...)` store";
211
+ ctx.next();
212
+ }
213
+ });
214
+ return reason;
215
+ }
216
+ /** Rewrite `$store` auto-subscription identifiers to their `$app/state` equivalent. */
217
+ function derefStores(node, locals, state) {
218
+ Walker.walk(node, null, { Identifier(node, ctx) {
219
+ const local = locals.get(node.name);
220
+ if (local === void 0) return;
221
+ const parent = ctx.path[ctx.path.length - 1];
222
+ if (!js.identifiers.isReference(node, parent)) return;
223
+ if (parent.type === "Property" && parent.shorthand && parent.key === node) {
224
+ parent.key = { ...node };
225
+ parent.shorthand = false;
226
+ }
227
+ if (local.store === "updated") {
228
+ js.common.replaceChild(parent, node, memberExpression(local.name, "current"));
229
+ return;
230
+ }
231
+ if (local.store === "navigating") {
232
+ if (isBooleanPosition(node, parent)) {
233
+ js.common.replaceChild(parent, node, memberExpression(local.name, "to"));
234
+ return;
235
+ }
236
+ if (!isMemberAccessObject(node, parent)) state.needsNavigatingReview = true;
237
+ }
238
+ node.name = local.name;
239
+ } });
240
+ }
241
+ /** Whether `node` is the object being accessed in `parent` (e.g. `$navigating` in `$navigating.to`). */
242
+ function isMemberAccessObject(node, parent) {
243
+ return parent.type === "MemberExpression" && parent.object === node;
244
+ }
245
+ /**
246
+ * Whether `node` sits in a boolean (truthiness-tested) position, where a bare
247
+ * `navigating` should become `navigating.to`.
248
+ */
249
+ function isBooleanPosition(node, parent) {
250
+ switch (parent.type) {
251
+ case "IfBlock":
252
+ case "IfStatement":
253
+ case "WhileStatement":
254
+ case "DoWhileStatement":
255
+ case "ConditionalExpression": return parent.test === node;
256
+ case "UnaryExpression": return parent.operator === "!" && parent.argument === node;
257
+ case "LogicalExpression": return (parent.operator === "&&" || parent.operator === "||") && parent.left === node;
258
+ default: return false;
259
+ }
260
+ }
261
+ function memberExpression(object, property) {
262
+ return {
263
+ type: "MemberExpression",
264
+ object: {
265
+ type: "Identifier",
266
+ name: object
267
+ },
268
+ property: {
269
+ type: "Identifier",
270
+ name: property
271
+ },
272
+ computed: false,
273
+ optional: false
274
+ };
275
+ }
276
+ //#endregion
277
+ //#region src/migrate/migrations/app-state/tasks/app-state.ts
278
+ var app_state_default$1 = defineMigrationTask({
279
+ id: "app-state",
280
+ description: "Migrate from \"$app/stores\" to \"$app/state\"",
281
+ run: ({ sv, language }) => {
282
+ sv.files({
283
+ include: "**/*.{svelte,svelte.ts,svelte.js}",
284
+ where: (content) => content.includes("$app/stores")
285
+ }, (content, path) => {
286
+ if (path.endsWith(".svelte")) return transforms.svelteScript({ language }, ({ ast }) => {
287
+ if (!migrateAppState(ast.instance.content, ast.fragment)) return false;
288
+ })(content);
289
+ return transforms.script(({ ast, comments }) => {
290
+ if (!migrateAppStateModule(ast, comments)) return false;
291
+ })(content);
292
+ });
293
+ }
37
294
  });
38
- async function runMigrate(cwd, args) {
39
- const pm = await detectPackageManager(cwd);
295
+ //#endregion
296
+ //#region src/migrate/migrations/app-state/index.ts
297
+ var app_state_default = defineMigration({
298
+ id: "app-state",
299
+ description: "Migrate from \"$app/stores\" to \"$app/state\"",
300
+ setup: ({ pkg }) => {
301
+ const kitPackageName = "@sveltejs/kit";
302
+ if (!pkg.devDependencies?.[kitPackageName]) throw new Error(`${color.command(kitPackageName)} is not a devDependency in package.json - this doesn't look like a SvelteKit project.\nPoint to one with ${color.command("--cwd <path>")}, or see ${color.command("sv migrate --help")}.`);
303
+ },
304
+ collect: ({ tasks }) => {
305
+ tasks.add(app_state_default$1, { prerequisite: true });
306
+ }
307
+ });
308
+ //#endregion
309
+ //#region src/migrate/migrations/legacy-migrations/index.ts
310
+ function legacyTask(name, description) {
311
+ return defineMigrationTask({
312
+ id: name,
313
+ description,
314
+ run: async ({ sv }) => {
315
+ await sv.execute(["svelte-migrate@1", name], "inherit");
316
+ }
317
+ });
318
+ }
319
+ function legacyMigration(name, description) {
320
+ return defineMigration({
321
+ id: name,
322
+ description,
323
+ legacy: true,
324
+ setup: () => {},
325
+ collect: ({ tasks }) => {
326
+ tasks.add(legacyTask(name, description), { prerequisite: true });
327
+ }
328
+ });
329
+ }
330
+ const legacyMigrations = [
331
+ legacyMigration("svelte-5", "Migrate from Svelte 4 to Svelte 5"),
332
+ legacyMigration("self-closing-tags", "Migrate to the new self-closing tag syntax"),
333
+ legacyMigration("svelte-4", "Migrate from Svelte 3 to Svelte 4"),
334
+ legacyMigration("sveltekit-2", "Migrate from SvelteKit 1.0 to SvelteKit 2.0"),
335
+ legacyMigration("package", "Migrate from @sveltejs/package@1 to @sveltejs/package@2"),
336
+ legacyMigration("routes", "Migrate from SvelteKit pre-1.0 routing to SvelteKit 1.0 routing")
337
+ ];
338
+ //#endregion
339
+ //#region src/migrate/migrations/sveltekit-3/tasks/collect-migration-instructions.ts
340
+ const REPORT_PATH = "MIGRATION_TASKS.md";
341
+ const GUIDE_URL = "https://next.svelte.dev/docs/kit/migrating-to-sveltekit-3";
342
+ const CODE_FILES = "**/*.{js,ts,svelte,mjs,mts,cjs,cts}";
343
+ const GENERATED_MARKER = "<!-- Generated by sv migrate sveltekit-3:collect-migration-instructions -->";
344
+ function guideLink(anchor) {
345
+ return [{
346
+ label: "Migrating to SvelteKit v3",
347
+ url: `${GUIDE_URL}#${anchor}`
348
+ }];
349
+ }
350
+ const migrationTasks = [
351
+ {
352
+ title: "Migrate remaining `base`, `assets`, and `resolveRoute` from `$app/paths` usages",
353
+ checks: [{
354
+ include: CODE_FILES,
355
+ patterns: [/^(?=[\s\S]*['"]\$app\/paths['"])(?=[\s\S]*(?:\bbase\b|\bassets\b|\bresolveRoute\b))/]
356
+ }],
357
+ summary: "The automatic migration can leave `base`, `assets`, and `resolveRoute` usages from `$app/paths`.",
358
+ instructions: "The deprecated base, assets, and resolveRoute exports have been removed from $app/paths. Use asset and resolve instead:```diff// instead of this...\nconst pathname = base + resolveRoute('/blog/[slug]', { slug });\nconst file = assets + '/foo.png';\n\n// ...do this:\nconst pathname = resolve('/blog/[slug]', { slug });\nconst file = asset('foo.png');\n```\nThe `Pathname` and `Asset` types have also been renamed to `Path` and `AssetPath`, and the leading `/` has been removed from those types — so `asset('/foo.png')` should now be `asset('foo.png')`, and pathnames passed to `resolve` no longer start with `/` (e.g. `resolve('blog/hello-world')`). Only route IDs start with `/` now.",
359
+ links: guideLink("$app-paths-base-assets-and-resolveRoute-removed")
360
+ },
361
+ {
362
+ title: "Migrate remaining `$app/stores` usages",
363
+ checks: [{
364
+ include: CODE_FILES,
365
+ patterns: ["$app/stores"]
366
+ }],
367
+ summary: "The automatic migration can leave `$app/stores` references when their store semantics require application context.",
368
+ instructions: "Replace each remaining `$app/stores` reference with `$app/state`. Remove auto-subscription prefixes, read `page` and `navigating` directly, and use `updated.current`. Rewrite `getStores()`, `get(store)`, subscriptions, re-exports, and dynamic imports according to how the value is used rather than only changing the module name.",
369
+ links: guideLink("$app-stores-(removed)")
370
+ },
371
+ {
372
+ title: "Migrate the removed `$service-worker` module",
373
+ checks: [{
374
+ include: CODE_FILES,
375
+ patterns: ["$service-worker"]
376
+ }],
377
+ summary: "SvelteKit 3 removes `$service-worker` and replaces its exports with several new modules.",
378
+ instructions: "Import `version` from `$app/env`, application asset metadata from `$app/manifest`, and path helpers from `$app/paths`. Account for the new manifest value shapes rather than blindly renaming `build` or `files` imports.",
379
+ links: guideLink("$service-worker-(removed)")
380
+ },
381
+ {
382
+ title: "Create a TypeScript project for the service worker",
383
+ checks: [{
384
+ include: ["src/service-worker.ts", "src/service-worker/**/*.ts"],
385
+ patterns: [/\S/]
386
+ }],
387
+ summary: "TypeScript service workers now need a separate project with service-worker-specific types.",
388
+ instructions: "Exclude the service worker from the root tsconfig and ensure `src/service-worker/tsconfig.json` extends `$app/tsconfig/service-worker`. Move a flat `src/service-worker.ts` entry to `src/service-worker/index.ts` if necessary, preserving any tooling references.",
389
+ links: guideLink("$app-tsconfig-service-worker")
390
+ },
391
+ {
392
+ title: "Make the service worker module-compatible",
393
+ checks: [{
394
+ include: ["src/service-worker.{js,ts}", "src/service-worker/**/*.{js,ts}"],
395
+ patterns: [/\bimportScripts\s*\(/]
396
+ }],
397
+ summary: "SvelteKit 3 registers service workers as modules, where classic worker APIs may not work.",
398
+ instructions: "Replace `importScripts(...)` and any other classic-worker-only assumptions with module imports or module-compatible equivalents.",
399
+ links: guideLink("Miscellaneous-Service-worker-registrations-use-type:-'module'")
400
+ },
401
+ {
402
+ title: "Copy `page.url` before mutating it",
403
+ checks: [{
404
+ include: CODE_FILES,
405
+ patterns: [
406
+ /\bpage\s*\.\s*url\s*\.\s*searchParams\s*\.\s*(?:append|delete|set|sort)\s*\(/,
407
+ /\bpage\s*\.\s*url\s*\.\s*(?:hash|host|hostname|href|password|pathname|port|protocol|search|username)\s*(?:=|\+\+|--)/,
408
+ "new URL(page.url)"
409
+ ]
410
+ }],
411
+ summary: "`page.url` and its search parameters are readonly in SvelteKit 3.",
412
+ instructions: "Create a mutable copy with `new URL(page.url.href)`, mutate that copy, and use it for navigation or serialization. Leave readonly accesses unchanged.",
413
+ links: guideLink("$app-state-page.url-is-now-readonly")
414
+ },
415
+ {
416
+ title: "Review `goto` destinations",
417
+ checks: [{
418
+ include: CODE_FILES,
419
+ patterns: [/^(?=[\s\S]*['"]\$app\/navigation['"])(?=[\s\S]*\bgoto\s*\()/]
420
+ }],
421
+ summary: "`goto(...)` now rejects destinations that do not resolve to an application route.",
422
+ instructions: "Confirm each destination is an internal route. Use `window.location.href` for external browser navigation, and handle rejected dynamic destinations where they can fail to resolve.",
423
+ links: guideLink("$app-navigation-goto-rejects-for-URLs-that-don't-resolve-to-a-route")
424
+ },
425
+ {
426
+ title: "Adjust noScroll/keepFocus usage",
427
+ checks: [{
428
+ include: CODE_FILES,
429
+ patterns: [
430
+ /^(?=[\s\S]*['"]\$app\/navigation['"])(?=[\s\S]*\bgoto\s*\([\s\S]*(noScroll|keepFocus))/,
431
+ "data-sveltekit-noscroll",
432
+ "data-sveltekit-keepfocus"
433
+ ]
434
+ }],
435
+ summary: "The options `noScroll` and `keepFocus` of `goto(...)` are merged into a single `reset` option.",
436
+ instructions: "replace the `noScroll` and `keepFocus` options of `goto` with a single `reset` option, and the `data-sveltekit-noscroll` and `data-sveltekit-keepfocus` attributes with `data-sveltekit-reset`. `noScroll: false / keepFocus: false` becomes `reset: true` and `noScroll: true / keepFocus: true` becomes `reset: false`.",
437
+ links: guideLink("$app-navigation-goto-options-are-updated")
438
+ },
439
+ {
440
+ title: "Adjust error(...) usage",
441
+ checks: [{
442
+ include: CODE_FILES,
443
+ patterns: [
444
+ /^(?=[\s\S]*['"]@sveltejs\/kit['"])(?=[\s\S]*\berror\s*\([0-9]+,\s*{)/,
445
+ "data-sveltekit-noscroll",
446
+ "data-sveltekit-keepfocus"
447
+ ]
448
+ }],
449
+ summary: "Signature of `error(...)` from `@sveltejs/kit` has changed.",
450
+ instructions: "Replace `error(status, { message, ... })` with `error(status, message, { ... })`.",
451
+ links: guideLink("Error-handling-error()-arguments-changed")
452
+ },
453
+ {
454
+ title: "Replace `invalidateAll` with `refreshAll`",
455
+ checks: [{
456
+ include: CODE_FILES,
457
+ patterns: [/^(?=[\s\S]*['"]\$app\/navigation['"])(?=[\s\S]*\bgoto\s*\()/]
458
+ }],
459
+ summary: "`invalidateAll` is deprecated in favor of `refreshAll`",
460
+ instructions: "Replace `invalidateAll()` with `refreshAll()`. Note that `refreshAll()` will keep `page.state` intact. Check if that has unwanted impact on the application; if yes point this out in a comment with steps to take instead of replacing that instance of `invalidateAll()`. Similarly, replace goto(..., { invalidateAll: true }) with goto(..., { refreshAll: true }).",
461
+ links: guideLink("$app-navigation-invalidateAll-is-deprecated-in-favour-of-refreshAll")
462
+ },
463
+ {
464
+ title: "`data-sveltekit-*` uses `false` instead of `\"off\"`",
465
+ checks: [{
466
+ include: "**/*.{svelte,html}",
467
+ patterns: [/data-sveltekit-[\w-]+=["']off["']/]
468
+ }],
469
+ summary: "The `\"off\"` value for `data-sveltekit-*` link attributes has been removed in favour of `false`.",
470
+ instructions: "Replace `data-sveltekit-...=\"off\"` with `data-sveltekit-...=\"false\"`.",
471
+ links: guideLink("Miscellaneous-data-sveltekit-uses-false-instead-of-'off'")
472
+ },
473
+ {
474
+ title: "Guard navigation `delta` access",
475
+ checks: [{
476
+ include: CODE_FILES,
477
+ patterns: [/^(?=[\s\S]*['"]\$app\/navigation['"])(?=[\s\S]*(?:\.\s*delta\b|\{[^}]*\bdelta\b))/]
478
+ }],
479
+ summary: "Navigation `delta` is now only defined for `popstate` navigations.",
480
+ instructions: "Check the navigation type or handle `undefined` before using `delta`. Do not treat an absent delta as zero unless that has the intended application meaning.",
481
+ links: guideLink("$app-navigation-delta-only-exists-for-popstate-navigations")
482
+ },
483
+ {
484
+ title: "Handle `preloadData` errors",
485
+ checks: [{
486
+ include: CODE_FILES,
487
+ patterns: [/^(?=[\s\S]*['"]\$app\/navigation['"])(?=[\s\S]*\bpreloadData\s*\()/]
488
+ }],
489
+ summary: "`preloadData(...)` can now return an error result, and redirects report their real status.",
490
+ instructions: "Inspect the complete result flow and add handling for `{ type: \"error\", status, error }` where absent. Do not assume every non-redirect result is loaded or has status 200.",
491
+ links: guideLink("$app-navigation-preloadData-can-return-an-error-result")
492
+ },
493
+ {
494
+ title: "Review enhanced forms with explicit actions",
495
+ checks: [{
496
+ include: "**/*.svelte",
497
+ patterns: [/^(?=[\s\S]*\buse:enhance\b)(?=[\s\S]*<form\b[^>]*\baction\s*=)/]
498
+ }],
499
+ summary: "Enhanced forms that submit to another page now navigate to that page.",
500
+ instructions: "Determine whether each action resolves to another page and confirm navigation is intended. If the form must remain on the current page, implement that behavior explicitly in the enhancement callback.",
501
+ links: guideLink("$app-forms")
502
+ },
503
+ {
504
+ title: "Migrate `handleValidationError`",
505
+ checks: [{
506
+ include: "**/hooks*.{js,ts,mjs,mts}",
507
+ patterns: ["handleValidationError"]
508
+ }],
509
+ summary: "`handleValidationError` is removed in SvelteKit 3.",
510
+ instructions: "Move validation-error handling into `handleError` and branch on `kind === \"validation\"`. Merge it carefully with any existing `handleError` logic.",
511
+ links: guideLink("Error-handling-handleValidationError-is-removed")
512
+ },
513
+ {
514
+ title: "Review `handleError` behavior",
515
+ checks: [{
516
+ include: "**/hooks*.{js,ts,mjs,mts}",
517
+ patterns: ["handleError"]
518
+ }],
519
+ summary: "SvelteKit 3 sends expected, validation, and rendering errors through `handleError`.",
520
+ instructions: "Review filtering, logging, reporting, returned error properties, and status handling for the broader set of errors. If `hooks.client` has an async hook, enable `compilerOptions.experimental.async` in `sveltekit(...)`.",
521
+ links: guideLink("Error-handling-handleError-receives-all-errors")
522
+ },
523
+ {
524
+ title: "CORS for static assets in development is handled by Vite",
525
+ summary: "SvelteKit no longer sets `access-control-allow-origin: *` on every static asset request in development. CORS is now delegated to Vite's built-in middleware.",
526
+ instructions: "If you rely on cross-origin access to static assets in dev, configure it in your Vite config by adding server.cors.origin = \"*\" to your config.",
527
+ links: guideLink("Security-CORS-for-static-assets-in-development-is-handled-by-Vite")
528
+ },
529
+ {
530
+ title: "`json` and `text` are deprecated",
531
+ checks: [{
532
+ include: CODE_FILES,
533
+ patterns: [/^(?=[\s\S]*['"]@sveltejs\/kit['"])(?=[\s\S]*\b(json|text)\s*\()/]
534
+ }],
535
+ summary: "The `json(...)` and `text(...)` helpers from `@sveltejs/kit` for generating responses are deprecated.",
536
+ instructions: "Use `Response.json(...)` and `new Response(text)` instead.",
537
+ links: guideLink("sveltejs-kit-json-and-text-are-deprecated")
538
+ },
539
+ {
540
+ title: "Remove the Node polyfills import",
541
+ checks: [{
542
+ include: CODE_FILES,
543
+ patterns: ["@sveltejs/kit/node/polyfills"]
544
+ }],
545
+ summary: "`@sveltejs/kit/node/polyfills` is removed because SvelteKit 3 requires modern Node.",
546
+ instructions: "Remove the obsolete side-effect import and verify that the custom server or adapter runs on Node 22.17 or newer.",
547
+ links: guideLink("sveltejs-kit-node-polyfills-(removed)")
548
+ },
549
+ {
550
+ title: "Remove `await` from synchronous Node helpers",
551
+ checks: [{
552
+ include: CODE_FILES,
553
+ patterns: [/^(?=[\s\S]*['"]@sveltejs\/kit\/node['"])(?=[\s\S]*\bawait\s+(?:\w+\.)?(?:getRequest|setResponse)\s*\()/]
554
+ }],
555
+ summary: "`getRequest` and `setResponse` from `@sveltejs/kit/node` are now synchronous.",
556
+ instructions: "Remove `await` only from calls bound to those SvelteKit helpers, then review surrounding promise composition and return types.",
557
+ links: guideLink("sveltejs-kit-node")
558
+ },
559
+ {
560
+ title: "Rename cookie option types",
561
+ checks: [{
562
+ include: CODE_FILES,
563
+ patterns: ["CookieSerializeOptions", "CookieParseOptions"]
564
+ }],
565
+ summary: "Cookie v2 renames its serialization and parsing option types.",
566
+ instructions: "Rename `CookieSerializeOptions` to `SerializeOptions` and `CookieParseOptions` to `ParseOptions`, changing only types imported from the `cookie` package.",
567
+ links: guideLink("Cookies-Updated-to-cookie-v2")
568
+ },
569
+ {
570
+ title: "Replace non-ASCII cookie names",
571
+ checks: [{
572
+ include: CODE_FILES,
573
+ patterns: [/\bcookies\s*\.\s*(?:get|getAll|set|delete|serialize)\s*\(\s*['"][^'"]*[^\x00-\x7f][^'"]*['"]/]
574
+ }],
575
+ summary: "Cookie v2 rejects cookie names containing non-ASCII characters.",
576
+ instructions: "Choose a stable ASCII replacement and consider compatibility or cleanup for cookies already issued under the old name.",
577
+ links: guideLink("Cookies-Updated-to-cookie-v2")
578
+ },
579
+ {
580
+ title: "Rename Cloudflare `platform.context`",
581
+ checks: [{
582
+ include: CODE_FILES,
583
+ patterns: [/\bplatform\s*(?:\?\.|\.)\s*context\b/]
584
+ }],
585
+ summary: "The Cloudflare adapter replaces `platform.context` with `platform.ctx`.",
586
+ instructions: "Confirm the value is the Cloudflare platform execution context, then rename it to `platform.ctx` and update related platform typings.",
587
+ links: guideLink("Adapters-adapter-cloudflare")
588
+ },
589
+ {
590
+ title: "Move adapter-node `ORIGIN` to `paths.origin`",
591
+ checks: [{
592
+ include: [
593
+ "Dockerfile*",
594
+ "**/Dockerfile*",
595
+ "**/*.{js,ts,mjs,mts,json,jsonc,yml,yaml,toml}"
596
+ ],
597
+ patterns: [
598
+ /\bprocess\.env\.ORIGIN\b/,
599
+ /\benv\s*\[\s*['"]ORIGIN['"]\s*\]/,
600
+ /^\s*(?:ENV|ARG)\s+ORIGIN\b/m,
601
+ /^\s*ORIGIN\s*[:=]/m
602
+ ]
603
+ }],
604
+ summary: "The adapter-node `ORIGIN` environment variable is removed.",
605
+ instructions: "If this value configures adapter-node, move the public-facing origin to `paths.origin` in `sveltekit(...)`. Remove the environment variable only after confirming it has no unrelated use.",
606
+ links: guideLink("Adapters-adapter-node")
607
+ },
608
+ {
609
+ title: "Replace the Vercel edge runtime",
610
+ checks: [{
611
+ include: "**/*.{js,ts,mjs,mts,json,jsonc}",
612
+ patterns: [/\bruntime\s*:\s*['"]edge['"]/]
613
+ }],
614
+ summary: "The Vercel adapter no longer supports the edge runtime.",
615
+ instructions: "Select a supported Vercel runtime and review runtime-specific APIs, dependencies, and deployment behavior before changing the configuration.",
616
+ links: guideLink("Adapters-adapter-vercel")
617
+ },
618
+ {
619
+ title: "Replace `builder.createEntries` in custom adapters",
620
+ checks: [{
621
+ include: CODE_FILES,
622
+ patterns: [/\bbuilder\s*\.\s*createEntries\s*\(/]
623
+ }],
624
+ summary: "The adapter API removes `builder.createEntries`.",
625
+ instructions: "Replace it with the appropriate `builder.writeClient`, `builder.writeServer`, and `builder.writePrerendered` flow for the adapter output model.",
626
+ links: guideLink("Adapters-Adapter-API-changes")
627
+ },
628
+ {
629
+ title: "Remove request route data from remote queries",
630
+ checks: [{
631
+ include: [
632
+ "**/*.remote.{js,ts}",
633
+ "**/*.remote.*.{js,ts}",
634
+ "**/remote.{js,ts}"
635
+ ],
636
+ patterns: [/\bevent\s*\.\s*(?:url|params|route)\b/]
637
+ }],
638
+ summary: "Remote queries can no longer access `event.url`, `event.params`, or `event.route`.",
639
+ instructions: "Pass the required values explicitly as validated query arguments and remove the request-specific event access.",
640
+ links: guideLink("Remote-functions-event.url-event.params-and-event.route-cannot-be-accessed-inside-queries")
641
+ },
642
+ {
643
+ title: "Use remote form field attributes",
644
+ checks: [{
645
+ include: "**/*.svelte",
646
+ patterns: [/^(?=[\s\S]*(?:from\s*['"][^'"]+\.remote(?:\.[^'"]+)?['"]|import\(\s*['"][^'"]+\.remote))(?=[\s\S]*\bname\s*=)/]
647
+ }],
648
+ summary: "Remote form controls must use attributes from the current form field.",
649
+ instructions: "Replace manually specified control names with attributes from the corresponding field `.as(...)` method, verifying that the field belongs to the submitted form (e.g. `<input name=\"foo\">` -> `<input {...myForm.fields.foo.as(\"text\")}>`).",
650
+ links: guideLink("Remote-functions-Form-submissions-require-field.as()")
651
+ },
652
+ {
653
+ title: "Review consumers of 204 responses",
654
+ checks: [{
655
+ include: ["src/routes/**/+server.{js,ts}", "**/*.{test,spec}.{js,ts,mjs,mts}"],
656
+ patterns: [/\bstatus\s*:\s*204\b/]
657
+ }],
658
+ summary: "SvelteKit 3 returns no body for 204 and other empty 2xx responses.",
659
+ instructions: "Find callers and tests for the matched endpoint and ensure they do not parse JSON or text from its empty response. Keep the endpoint response unchanged if it is already spec-compliant.",
660
+ links: guideLink("Responses-204-responses-return-no-content")
661
+ },
662
+ {
663
+ title: "Replace remaining `csrf.checkOrigin` configuration",
664
+ checks: [{
665
+ include: "**/{svelte,vite}.config.{js,ts,mjs,mts,cjs,cts}",
666
+ patterns: ["checkOrigin"]
667
+ }],
668
+ summary: "`csrf.checkOrigin` is removed and CSRF protection is always enabled.",
669
+ instructions: "Remove `checkOrigin`. If cross-origin submissions are intentional, configure the narrowest possible `csrf.trustedOrigins` allowlist instead of disabling protection.",
670
+ links: guideLink("Security-csrf.checkOrigin-replaced-by-csrf.trustedOrigins")
671
+ },
672
+ {
673
+ title: "Review cross-origin form submissions",
674
+ checks: [{
675
+ include: "**/*.svelte",
676
+ patterns: [/<form\b[^>]*\baction\s*=\s*['"](?:https?:)?\/\//]
677
+ }],
678
+ summary: "Cross-origin mutative requests without a `Content-Type` header are rejected as CSRF.",
679
+ instructions: "Ensure the submission sends an appropriate `Content-Type` header or add the destination to the narrowest possible `csrf.trustedOrigins` allowlist.",
680
+ links: guideLink("Security-Cross-origin-form-submissions-require-a-Content-Type-header")
681
+ }
682
+ ];
683
+ var collect_migration_instructions_default = defineMigrationTask({
684
+ id: "collect-migration-instructions",
685
+ description: `Collect instructions for non-automated migration tasks; creates ${REPORT_PATH} whose instructions you should follow to complete the migration`,
686
+ run: ({ sv }) => {
687
+ const findings = [];
688
+ for (const task of migrationTasks) {
689
+ const files = /* @__PURE__ */ new Set();
690
+ if (!task.checks) findings.push({
691
+ task,
692
+ files: []
693
+ });
694
+ else {
695
+ for (const check of task.checks) sv.files({
696
+ include: check.include,
697
+ exclude: [REPORT_PATH],
698
+ where: (content) => check.patterns.some((pattern) => matches(content, pattern))
699
+ }, (_content, file) => {
700
+ files.add(file);
701
+ return false;
702
+ });
703
+ if (files.size > 0) findings.push({
704
+ task,
705
+ files: [...files].sort()
706
+ });
707
+ }
708
+ }
709
+ if (findings.length === 0) return;
710
+ sv.file(REPORT_PATH, (content) => {
711
+ if (content && !content.includes("<!-- Generated by sv migrate sveltekit-3:collect-migration-instructions -->")) throw new Error(`'${REPORT_PATH}' already exists and was not generated by this migration. Move or rename it before rerunning the task.`);
712
+ return renderReport(findings);
713
+ });
714
+ }
715
+ });
716
+ function matches(content, pattern) {
717
+ return typeof pattern === "string" ? content.includes(pattern) : pattern.test(content);
718
+ }
719
+ function renderReport(findings) {
720
+ return `${GENERATED_MARKER}
721
+ # SvelteKit 3 migration tasks
722
+
723
+ Some migrations are uncommon, context-dependent, or disproportionately difficult to automate safely. This document lists them.
724
+
725
+ The findings are intentionally broad and may include files that do not require changes.
726
+
727
+ AI agents can work through the migration tasks below one at a time:
728
+
729
+ 1. Read the linked documentation or pull request before changing code.
730
+ 2. Inspect every listed file and its surrounding context.
731
+ 3. Ignore a finding when the file is not actually affected.
732
+ 4. Make the smallest necessary change. Do not perform unrelated refactors or introduce unnecessary abstractions.
733
+ 5. Ask the user when application intent cannot be inferred.
734
+ 6. Run the project's appropriate checks after editing.
735
+
736
+ Also search the project for \`@migration-task\` comments. They may describe additional manual migration work that must be resolved.
737
+
738
+ ## Migration tasks
739
+
740
+ ${findings.map(({ task, files }) => {
741
+ const links = task.links.map(({ label, url }) => `- [${label}](${url})`).join("\n");
742
+ const locations = files.map((file) => `- [ ] \`${normalizePosix(file)}\``).join("\n");
743
+ let output = `### ${task.title}
744
+
745
+ ${task.summary}
746
+
747
+ #### What to do
748
+
749
+ ${task.instructions}
750
+
751
+ #### References
752
+
753
+ ${links}`;
754
+ if (locations.length) output += `
755
+
756
+ #### Files to review
757
+
758
+ ${locations}`;
759
+ return output;
760
+ }).join("\n\n")}
761
+
762
+ ## Final verification
763
+
764
+ - [ ] Review every migration task and ignore any irrelevant findings.
765
+ - [ ] Resolve remaining \`@migration-task\` comments.
766
+ - [ ] Run the project's type checker and tests.
767
+ - [ ] Build the project successfully.
768
+ - [ ] Delete this file when all migration work is complete.
769
+ `;
770
+ }
771
+ //#endregion
772
+ //#region src/migrate/migrations/sveltekit-3/tasks/environment/env-namespace.ts
773
+ const OLD_SOURCE = "$app/environment";
774
+ const NEW_SOURCE = "$app/env";
775
+ /** Rename `$app/environment` imports to `$app/env`. Returns whether anything changed. */
776
+ function renameEnvNamespace(ast) {
777
+ return js.imports.renameSource(ast, {
778
+ from: OLD_SOURCE,
779
+ to: NEW_SOURCE
780
+ });
781
+ }
782
+ //#endregion
783
+ //#region src/migrate/migrations/sveltekit-3/tasks/environment/explicit-env-vars.ts
784
+ const ENV_MODULE = /^\$env\/(dynamic|static)\/(public|private)$/;
785
+ /** Migrate `$env/*` imports/usages to `$app/env/*` for one program. Returns whether it mutated `ast`. */
786
+ function migrateExplicitEnvVars(ast, envVars, template, comments) {
787
+ let mutated = false;
788
+ const envImports = collectEnvImports(ast, template, comments);
789
+ if (envImports === "migration-task") mutated = true;
790
+ else if (envImports.length > 0) {
791
+ changeEnvImports(ast, envImports);
792
+ replaceEnvUsages(envImports);
793
+ collectEnvVars(envImports, envVars);
794
+ mutated = true;
795
+ }
796
+ if (migrateDynamicEnvImports(ast, envVars, comments, template)) mutated = true;
797
+ if (template && migrateDynamicEnvImports(template, envVars, comments, template)) mutated = true;
798
+ return mutated;
799
+ }
800
+ /**
801
+ * Rewrite dynamic `import('$env/*')` expressions to `import('$app/env/*')`.
802
+ * Names destructured from the result are collected for the `src/env.ts` declaration;
803
+ * other shapes (namespace binding, rest element, computed keys) are flagged for manual work.
804
+ */
805
+ function migrateDynamicEnvImports(node, envVars, comments, svelteFragment) {
806
+ let mutated = false;
807
+ for (const found of js.imports.findAll(node, { from: ENV_MODULE })) {
808
+ if (found.kind !== "dynamic") continue;
809
+ const [, type, scope] = found.source.match(ENV_MODULE);
810
+ js.imports.setSource(found, `$app/env/${scope}`);
811
+ mutated = true;
812
+ const names = getDestructuredEnvNames(found.path);
813
+ if (names) for (const name of names) envVars.set(name, {
814
+ type,
815
+ scope,
816
+ name
817
+ });
818
+ else addUnsupportedDynamicImportComment(comments, findCommentTarget(found.path) ?? found.node, svelteFragment);
819
+ }
820
+ return mutated;
821
+ }
822
+ function getDestructuredEnvNames(path) {
823
+ for (let i = path.length - 1; i >= 0; i -= 1) {
824
+ const node = path[i];
825
+ if (node.type !== "VariableDeclarator") continue;
826
+ if (node.id.type !== "ObjectPattern") return;
827
+ const names = [];
828
+ for (const property of node.id.properties) {
829
+ if (property.type !== "Property") return;
830
+ if (property.key.type !== "Identifier" || property.computed) return;
831
+ names.push(property.key.name);
832
+ }
833
+ return names;
834
+ }
835
+ }
836
+ function addUnsupportedDynamicImportComment(comments, node, svelteFragment) {
837
+ const message = "Declare the imported env variables in src/env.ts manually.";
838
+ if (svelteFragment) {
839
+ addMigrationTask(message, {
840
+ fragment: svelteFragment,
841
+ anchor: node
842
+ });
843
+ return;
844
+ }
845
+ if (!comments) return;
846
+ addMigrationTask(message, {
847
+ comments,
848
+ node
849
+ });
850
+ }
851
+ function collectEnvImports(ast, template, comments) {
852
+ const envImports = [];
853
+ let hasMigrationTask = false;
854
+ const relevantImports = js.imports.findAll(ast, { from: /^\$env\// }).filter((found) => found.kind === "static");
855
+ if (relevantImports.length === 0) return envImports;
856
+ for (const found of relevantImports) {
857
+ const match = found.source.match(ENV_MODULE);
858
+ if (!match) continue;
859
+ const type = match[1];
860
+ const scope = match[2];
861
+ const envImport = type === "dynamic" ? collectDynamicEnvImport(ast, found, scope, template, comments) : collectStaticEnvImport(ast, found, scope);
862
+ if (envImport === "migration-task") {
863
+ hasMigrationTask = true;
864
+ continue;
865
+ }
866
+ if (envImport) envImports.push(envImport);
867
+ }
868
+ if (envImports.length === 0 && hasMigrationTask) return "migration-task";
869
+ return envImports;
870
+ }
871
+ function collectDynamicEnvImport(ast, found, scope, template, comments) {
872
+ const importNames = new Set(js.imports.bindings(ast, {
873
+ from: found.source,
874
+ name: "env"
875
+ }).filter((binding) => binding.declaration === found.node).map((binding) => binding.local));
876
+ if (importNames.size === 0) return;
877
+ const usages = getDynamicEnvUsages(ast, importNames, comments, template);
878
+ if (!usages) return "migration-task";
879
+ if (template) {
880
+ const templateUsages = getDynamicEnvUsages(template, importNames, comments, template);
881
+ if (!templateUsages) return "migration-task";
882
+ usages.push(...templateUsages);
883
+ }
884
+ if (usages.length === 0) return;
885
+ return {
886
+ type: "dynamic",
887
+ scope,
888
+ found,
889
+ usages
890
+ };
891
+ }
892
+ function collectStaticEnvImport(ast, found, scope) {
893
+ const importNames = js.imports.bindings(ast, { from: found.source }).filter((binding) => binding.kind === "named" && binding.declaration === found.node).map((binding) => binding.imported);
894
+ if (importNames.length === 0) return;
895
+ return {
896
+ type: "static",
897
+ scope,
898
+ found,
899
+ importNames
900
+ };
901
+ }
902
+ function getDynamicEnvUsages(node, importNames, comments, svelteFragment) {
903
+ const usages = [];
904
+ let hasUnsupportedUsage = false;
905
+ Walker.walk(node, null, { MemberExpression(node, walkContext) {
906
+ if (node.object.type === "Identifier" && importNames.has(node.object.name)) {
907
+ const name = getDynamicEnvUsageName(node);
908
+ if (!name) {
909
+ hasUnsupportedUsage = true;
910
+ addUnsupportedDynamicEnvComment(comments, findCommentTarget(walkContext.path) ?? node, svelteFragment);
911
+ walkContext.next();
912
+ return;
913
+ }
914
+ usages.push({
915
+ node,
916
+ parent: walkContext.path[walkContext.path.length - 1],
917
+ name
918
+ });
919
+ }
920
+ walkContext.next();
921
+ } });
922
+ if (hasUnsupportedUsage) return;
923
+ return usages;
924
+ }
925
+ function addUnsupportedDynamicEnvComment(comments, node, svelteFragment) {
926
+ const message = "Rewrite dynamic env lookup manually.";
927
+ if (svelteFragment) {
928
+ addMigrationTask(message, {
929
+ fragment: svelteFragment,
930
+ anchor: node
931
+ });
932
+ return;
933
+ }
934
+ if (!comments) return;
935
+ addMigrationTask(message, {
936
+ comments,
937
+ node
938
+ });
939
+ }
940
+ function findCommentTarget(path) {
941
+ for (let i = path.length - 1; i >= 0; i -= 1) {
942
+ const node = path[i];
943
+ if (node.type === "VariableDeclaration" || node.type === "ExpressionStatement" || node.type === "ReturnStatement") return node;
944
+ }
945
+ }
946
+ function getDynamicEnvUsageName(node) {
947
+ if (!node.computed && node.property.type === "Identifier") return node.property.name;
948
+ if (node.computed && node.property.type === "Literal" && typeof node.property.value === "string" && isValidIdentifierName(node.property.value)) return node.property.value;
949
+ }
950
+ function isValidIdentifierName(name) {
951
+ return /^[$A-Z_a-z][$\w]*$/.test(name);
952
+ }
953
+ function changeEnvImports(ast, envImports) {
954
+ const staticImports = envImports.filter((x) => x.type === "static");
955
+ for (const { scope, found } of staticImports) js.imports.setSource(found, `$app/env/${scope}`);
956
+ const dynamicImports = envImports.filter((x) => x.type === "dynamic");
957
+ for (const { scope, found, usages } of dynamicImports) {
958
+ js.imports.setSource(found, `$app/env/${scope}`);
959
+ found.node.specifiers = [];
960
+ const uniqueUsages = new Set(usages.map((usage) => usage.name));
961
+ for (const name of uniqueUsages) found.node.specifiers.push({
962
+ type: "ImportSpecifier",
963
+ imported: {
964
+ type: "Identifier",
965
+ name
966
+ },
967
+ local: {
968
+ type: "Identifier",
969
+ name
970
+ }
971
+ });
972
+ }
973
+ mergeEnvImports(ast);
974
+ }
975
+ /**
976
+ * If we initially have two imports from $env/static/public and $env/dynamic/public
977
+ * they will produce two separate imports which is not totally clean.
978
+ * This function merges multiple imports from the same new source into a single import.
979
+ *
980
+ * Specifiers of duplicated imports are assumed to be unique and are merged together.
981
+ */
982
+ function mergeEnvImports(ast) {
983
+ const seenImports = /* @__PURE__ */ new Map();
984
+ for (let i = 0; i < ast.body.length; i += 1) {
985
+ const node = ast.body[i];
986
+ if (node.type !== "ImportDeclaration") continue;
987
+ const source = node.source.value;
988
+ if (typeof source !== "string" || !source.startsWith("$app/env/")) continue;
989
+ const existingImport = seenImports.get(source);
990
+ if (!existingImport) {
991
+ seenImports.set(source, node);
992
+ continue;
993
+ }
994
+ existingImport.specifiers.push(...node.specifiers);
995
+ ast.body.splice(i, 1);
996
+ i -= 1;
997
+ }
998
+ }
999
+ function replaceEnvUsages(envImports) {
1000
+ const dynamicImports = envImports.filter((x) => x.type === "dynamic");
1001
+ for (const { usages } of dynamicImports) for (const usage of usages) {
1002
+ if (!usage.parent) continue;
1003
+ js.common.replaceChild(usage.parent, usage.node, {
1004
+ type: "Identifier",
1005
+ name: usage.name
1006
+ });
1007
+ }
1008
+ }
1009
+ function collectEnvVars(envImports, envVars) {
1010
+ for (const envImport of envImports) if (envImport.type === "static") for (const name of envImport.importNames) envVars.set(name, {
1011
+ type: envImport.type,
1012
+ scope: envImport.scope,
1013
+ name
1014
+ });
1015
+ else for (const usage of envImport.usages) envVars.set(usage.name, {
1016
+ type: envImport.type,
1017
+ scope: envImport.scope,
1018
+ name: usage.name
1019
+ });
1020
+ }
1021
+ function addEnvDeclarationFile(ast, comments, envVars) {
1022
+ if (envVars.size === 0) return false;
1023
+ js.imports.addNamed(ast, {
1024
+ from: "@sveltejs/kit/env",
1025
+ imports: ["defineEnvVars"]
1026
+ });
1027
+ const defineCall = js.functions.createCall({
1028
+ name: "defineEnvVars",
1029
+ args: []
1030
+ });
1031
+ const variablesObject = js.functions.getArgument(defineCall, {
1032
+ index: 0,
1033
+ fallback: js.object.create({})
1034
+ });
1035
+ const variablesIdentifier = js.variables.declaration(ast, {
1036
+ kind: "const",
1037
+ name: "variables",
1038
+ value: defineCall
1039
+ });
1040
+ const exportDeclaration = js.exports.createNamed(ast, {
1041
+ name: "variables",
1042
+ fallback: variablesIdentifier
1043
+ });
1044
+ let has_dynamic = false;
1045
+ for (const envVar of envVars.values()) {
1046
+ const value = js.object.property(variablesObject, {
1047
+ name: envVar.name,
1048
+ fallback: js.object.create({})
1049
+ });
1050
+ has_dynamic ||= envVar.type === "dynamic";
1051
+ js.object.overrideProperties(value, {
1052
+ public: envVar.scope === "public" ? true : void 0,
1053
+ static: envVar.type === "static" ? true : void 0,
1054
+ schema: envVar.type === "dynamic" ? js.functions.createArrow({
1055
+ params: ["input"],
1056
+ body: {
1057
+ type: "LogicalExpression",
1058
+ operator: "??",
1059
+ left: {
1060
+ type: "Identifier",
1061
+ name: "input"
1062
+ },
1063
+ right: {
1064
+ type: "Literal",
1065
+ value: "",
1066
+ raw: "''"
1067
+ }
1068
+ },
1069
+ async: false
1070
+ }) : void 0
1071
+ });
1072
+ }
1073
+ if (has_dynamic) addMigrationTask("Review usage of dynamic environment variables. They fall back to the empty string if not present, which may not be what you want.", {
1074
+ comments,
1075
+ node: exportDeclaration
1076
+ });
1077
+ }
1078
+ //#endregion
1079
+ //#region src/migrate/migrations/sveltekit-3/tasks/environment.ts
1080
+ var environment_default = defineMigrationTask({
1081
+ id: "environment",
1082
+ description: "Migrate to explicit environment variables ($app/env)",
1083
+ run: ({ sv, language }) => {
1084
+ const envVars = /* @__PURE__ */ new Map();
1085
+ sv.files({
1086
+ include: "**/*.{ts,js,svelte}",
1087
+ where: (content) => content.includes("$env/") || content.includes("$app/environment")
1088
+ }, (content, path) => {
1089
+ if (path.endsWith(".svelte")) return transforms.svelteScript({ language }, ({ ast }) => {
1090
+ const renamed = renameEnvNamespace(ast.instance.content);
1091
+ const migrated = migrateExplicitEnvVars(ast.instance.content, envVars, ast.fragment);
1092
+ if (!renamed && !migrated) return false;
1093
+ })(content);
1094
+ return transforms.script(({ ast, comments }) => {
1095
+ const renamed = renameEnvNamespace(ast);
1096
+ const migrated = migrateExplicitEnvVars(ast, envVars, void 0, comments);
1097
+ if (!renamed && !migrated) return false;
1098
+ })(content);
1099
+ });
1100
+ if (envVars.size > 0) sv.file(`src/env.${language}`, transforms.script(({ ast, comments }) => addEnvDeclarationFile(ast, comments, envVars)));
1101
+ }
1102
+ });
1103
+ //#endregion
1104
+ //#region src/migrate/migrations/sveltekit-3/tasks/external-redirects.ts
1105
+ const KIT_MODULE$1 = "@sveltejs/kit";
1106
+ const REDIRECT = "redirect";
1107
+ const BASES = ["https://sveltekit-a.invalid", "https://sveltekit-b.invalid"];
1108
+ /** Stands in for the dynamic remainder of a partially static destination (e.g. `https://${domain}`). */
1109
+ const PLACEHOLDER = "x";
1110
+ var external_redirects_default = defineMigrationTask({
1111
+ id: "external-redirects",
1112
+ description: "Opt external redirects into the new redirect behavior",
1113
+ run: ({ sv }) => {
1114
+ sv.files({
1115
+ include: "**/*.{js,ts,svelte}",
1116
+ where: (content) => content.includes(KIT_MODULE$1) && content.includes(REDIRECT)
1117
+ }, (content, file) => {
1118
+ if (file.endsWith(".svelte")) return transforms.svelte(({ ast }) => {
1119
+ let changed = false;
1120
+ if (ast.module && migrateExternalRedirects(ast.module.content)) changed = true;
1121
+ if (ast.instance && migrateExternalRedirects(ast.instance.content)) changed = true;
1122
+ if (!changed) return false;
1123
+ })(content);
1124
+ return transforms.script(({ ast }) => {
1125
+ if (!migrateExternalRedirects(ast)) return false;
1126
+ })(content);
1127
+ });
1128
+ }
1129
+ });
1130
+ function migrateExternalRedirects(ast) {
1131
+ const { locals, namespaces } = collectRedirectImports(ast);
1132
+ if (locals.size === 0 && namespaces.size === 0) return false;
1133
+ let changed = false;
1134
+ Walker.walk(ast, null, { CallExpression(node, { next }) {
1135
+ const destination = node.arguments[1];
1136
+ if (isRedirectCall(node, locals, namespaces) && node.arguments.length === 2 && destination?.type !== "SpreadElement") {
1137
+ const external = externalValue(destination);
1138
+ if (external !== void 0) {
1139
+ node.arguments.push(js.object.create({ external: js.common.createLiteral(external) }));
1140
+ changed = true;
1141
+ }
1142
+ }
1143
+ next();
1144
+ } });
1145
+ return changed;
1146
+ }
1147
+ function collectRedirectImports(ast) {
1148
+ const bindings = js.imports.bindings(ast, { from: KIT_MODULE$1 });
1149
+ return {
1150
+ locals: new Set(bindings.filter((binding) => binding.kind === "named" && binding.imported === REDIRECT && !binding.isType).map((binding) => binding.local)),
1151
+ namespaces: new Set(bindings.filter((binding) => binding.kind === "namespace" && !binding.isType).map((binding) => binding.local))
1152
+ };
1153
+ }
1154
+ function isRedirectCall(node, locals, namespaces) {
1155
+ const callee = node.callee;
1156
+ if (callee.type === "Identifier") return locals.has(callee.name);
1157
+ if (callee.type === "MemberExpression" && callee.object.type === "Identifier") {
1158
+ if (!namespaces.has(callee.object.name)) return false;
1159
+ if (callee.computed) return callee.property.type === "Literal" && callee.property.value === REDIRECT;
1160
+ return callee.property.type === "Identifier" && callee.property.name === REDIRECT;
1161
+ }
1162
+ return false;
1163
+ }
1164
+ function externalValue(destination) {
1165
+ const prefix = staticPrefix(destination);
1166
+ if (prefix === void 0) return void 0;
1167
+ let result = classify(prefix.value);
1168
+ if (result === void 0 && !prefix.complete) result = classify(prefix.value + PLACEHOLDER);
1169
+ return result === false ? void 0 : result;
1170
+ }
1171
+ /**
1172
+ * Classifies a destination string: `true` for external, `'javascript:'` for javascript URLs,
1173
+ * `false` for same-origin, and `undefined` when it cannot be parsed as a URL.
1174
+ */
1175
+ function classify(value) {
40
1176
  try {
41
- const cmdArgs = ["svelte-migrate@latest", ...args];
42
- if (pm === "npm") cmdArgs.unshift("--yes");
43
- execSync(resolveCommandArray(pm, "execute", cmdArgs).join(" "), {
44
- stdio: "inherit",
1177
+ const urls = BASES.map((base) => new URL(value, base));
1178
+ if (urls.some((url) => url.protocol === "javascript:")) return "javascript:";
1179
+ return urls.some((url, index) => url.origin !== BASES[index]) || false;
1180
+ } catch {
1181
+ return;
1182
+ }
1183
+ }
1184
+ function staticPrefix(expression) {
1185
+ if (expression.type === "Literal") return typeof expression.value === "string" ? {
1186
+ value: expression.value,
1187
+ complete: true
1188
+ } : void 0;
1189
+ if (expression.type === "TemplateLiteral") {
1190
+ const value = expression.quasis[0]?.value.cooked ?? expression.quasis[0]?.value.raw;
1191
+ if (value === void 0) return void 0;
1192
+ return {
1193
+ value,
1194
+ complete: expression.expressions.length === 0
1195
+ };
1196
+ }
1197
+ if (expression.type === "BinaryExpression" && expression.operator === "+") {
1198
+ if (expression.left.type === "PrivateIdentifier") return void 0;
1199
+ const left = staticPrefix(expression.left);
1200
+ return left && {
1201
+ value: left.value,
1202
+ complete: false
1203
+ };
1204
+ }
1205
+ }
1206
+ //#endregion
1207
+ //#region src/migrate/migrations/sveltekit-3/tasks/imports.ts
1208
+ const KIT_MODULE = "@sveltejs/kit";
1209
+ const MOVED_EXPORTS = /* @__PURE__ */ new Map([
1210
+ ["ActionResult", "$app/forms"],
1211
+ ["AfterNavigate", "$app/navigation"],
1212
+ ["BeforeNavigate", "$app/navigation"],
1213
+ ["CaughtError", "@sveltejs/kit/hooks"],
1214
+ ["ClientCaughtError", "@sveltejs/kit/hooks"],
1215
+ ["ClientInit", "@sveltejs/kit/hooks"],
1216
+ ["DefinedEnvVars", "@sveltejs/kit/env"],
1217
+ ["DefinedParams", "@sveltejs/kit/params"],
1218
+ ["EnvVarConfig", "@sveltejs/kit/env"],
1219
+ ["GotoOptions", "$app/navigation"],
1220
+ ["Handle", "@sveltejs/kit/hooks"],
1221
+ ["HandleClientError", "@sveltejs/kit/hooks"],
1222
+ ["HandleFetch", "@sveltejs/kit/hooks"],
1223
+ ["HandleServerError", "@sveltejs/kit/hooks"],
1224
+ ["InvalidField", "$app/server"],
1225
+ ["LiveQueryRequestedResult", "$app/server"],
1226
+ ["LiveRequestedEntry", "$app/server"],
1227
+ ["MatcherParam", "@sveltejs/kit/params"],
1228
+ ["Navigation", "$app/navigation"],
1229
+ ["NavigationBase", "$app/navigation"],
1230
+ ["NavigationEnter", "$app/navigation"],
1231
+ ["NavigationExternal", "$app/navigation"],
1232
+ ["NavigationFormSubmit", "$app/navigation"],
1233
+ ["NavigationGoto", "$app/navigation"],
1234
+ ["NavigationLeave", "$app/navigation"],
1235
+ ["NavigationLink", "$app/navigation"],
1236
+ ["NavigationPopState", "$app/navigation"],
1237
+ ["NavigationTarget", "$app/navigation"],
1238
+ ["NavigationType", "$app/navigation"],
1239
+ ["OnNavigate", "$app/navigation"],
1240
+ ["Page", "$app/state"],
1241
+ ["ParamDefinition", "@sveltejs/kit/params"],
1242
+ ["ParamMatcher", "@sveltejs/kit/params"],
1243
+ ["ParamValue", "@sveltejs/kit/params"],
1244
+ ["QueryRequestedResult", "$app/server"],
1245
+ ["ReadonlyURL", "$app/state"],
1246
+ ["ReadonlyURLSearchParams", "$app/state"],
1247
+ ["RemoteCommand", "$app/server"],
1248
+ ["RemoteForm", "$app/server"],
1249
+ ["RemoteFormEnhanceCallback", "$app/server"],
1250
+ ["RemoteFormEnhanceInstance", "$app/server"],
1251
+ ["RemoteFormField", "$app/server"],
1252
+ ["RemoteFormFields", "$app/server"],
1253
+ ["RemoteFormFieldType", "$app/server"],
1254
+ ["RemoteFormFieldValue", "$app/server"],
1255
+ ["RemoteFormInput", "$app/server"],
1256
+ ["RemoteFormIssue", "$app/server"],
1257
+ ["RemoteLiveQuery", "$app/server"],
1258
+ ["RemoteLiveQueryFunction", "$app/server"],
1259
+ ["RemotePrerenderFunction", "$app/server"],
1260
+ ["RemoteQuery", "$app/server"],
1261
+ ["RemoteQueryFunction", "$app/server"],
1262
+ ["RemoteQueryOverride", "$app/server"],
1263
+ ["RemoteQueryUpdate", "$app/server"],
1264
+ ["RemoteResource", "$app/server"],
1265
+ ["RequestedEntry", "$app/server"],
1266
+ ["RequestedResult", "$app/server"],
1267
+ ["Reroute", "@sveltejs/kit/hooks"],
1268
+ ["ResolveOptions", "@sveltejs/kit/hooks"],
1269
+ ["ServerInit", "@sveltejs/kit/hooks"],
1270
+ ["SubmitFunction", "$app/forms"],
1271
+ ["Transport", "@sveltejs/kit/hooks"],
1272
+ ["Transporter", "@sveltejs/kit/hooks"],
1273
+ ["ValidationError", "$app/server"],
1274
+ ["defineParams", "@sveltejs/kit/params"]
1275
+ ]);
1276
+ var imports_default = defineMigrationTask({
1277
+ id: "imports",
1278
+ description: "Move SvelteKit APIs to their new modules",
1279
+ run: ({ sv }) => {
1280
+ sv.files({
1281
+ include: "**/*.{js,ts,mjs,mts,cjs,cts,svelte}",
1282
+ where: (content) => content.includes(KIT_MODULE)
1283
+ }, (content, file) => {
1284
+ if (file.endsWith(".svelte")) return transforms.svelte(({ ast }) => {
1285
+ let changed = false;
1286
+ if (ast.module && migrateProgram(ast.module.content)) changed = true;
1287
+ if (ast.instance && migrateProgram(ast.instance.content)) changed = true;
1288
+ if (migrateComments(ast.comments)) changed = true;
1289
+ if (!changed) return false;
1290
+ })(content);
1291
+ return transforms.script(({ ast, comments }) => {
1292
+ const programChanged = migrateProgram(ast);
1293
+ const commentsChanged = migrateComments(comments.list());
1294
+ if (!programChanged && !commentsChanged) return false;
1295
+ })(content);
1296
+ });
1297
+ }
1298
+ });
1299
+ function migrateProgram(ast) {
1300
+ let changed = migrateNamedImports(ast);
1301
+ const body = [];
1302
+ for (const statement of ast.body) {
1303
+ if (statement.type === "ExportNamedDeclaration" && statement.source?.value === KIT_MODULE) {
1304
+ const replacements = splitExportSpecifiers(statement);
1305
+ if (replacements) {
1306
+ body.push(...replacements);
1307
+ changed = true;
1308
+ continue;
1309
+ }
1310
+ }
1311
+ body.push(statement);
1312
+ }
1313
+ ast.body = body;
1314
+ return migrateImportTypes(ast) || changed;
1315
+ }
1316
+ function migrateNamedImports(ast) {
1317
+ const bindings = js.imports.bindings(ast, { from: KIT_MODULE }).filter((binding) => binding.kind === "named");
1318
+ if (!bindings.some((binding) => MOVED_EXPORTS.has(binding.imported))) return false;
1319
+ const bySource = /* @__PURE__ */ new Map();
1320
+ for (const binding of bindings) {
1321
+ const source = MOVED_EXPORTS.get(binding.imported) ?? KIT_MODULE;
1322
+ const group = bySource.get(source) ?? [];
1323
+ group.push(binding);
1324
+ bySource.set(source, group);
1325
+ }
1326
+ for (const imported of new Set(bindings.map((binding) => binding.imported))) js.imports.remove(ast, {
1327
+ from: KIT_MODULE,
1328
+ name: imported
1329
+ });
1330
+ for (const [source, group] of [...bySource].reverse()) for (const binding of group) js.imports.addNamed(ast, {
1331
+ from: source,
1332
+ imports: { [binding.imported]: binding.local },
1333
+ isType: binding.isType
1334
+ });
1335
+ return true;
1336
+ }
1337
+ function splitExportSpecifiers(declaration) {
1338
+ const moved = /* @__PURE__ */ new Map();
1339
+ const remaining = [];
1340
+ for (const specifier of declaration.specifiers) {
1341
+ const name = specifier.local.type === "Identifier" ? specifier.local.name : String(specifier.local.value);
1342
+ const destination = MOVED_EXPORTS.get(name);
1343
+ if (!destination) {
1344
+ remaining.push(specifier);
1345
+ continue;
1346
+ }
1347
+ const specifiers = moved.get(destination) ?? [];
1348
+ specifiers.push(specifier);
1349
+ moved.set(destination, specifiers);
1350
+ }
1351
+ if (moved.size === 0) return;
1352
+ const declarations = [];
1353
+ if (remaining.length > 0) declarations.push({
1354
+ ...declaration,
1355
+ specifiers: remaining
1356
+ });
1357
+ for (const [source, specifiers] of moved) declarations.push({
1358
+ ...declaration,
1359
+ specifiers,
1360
+ source: {
1361
+ type: "Literal",
1362
+ value: source
1363
+ }
1364
+ });
1365
+ return declarations;
1366
+ }
1367
+ function migrateImportTypes(node) {
1368
+ if (!node || typeof node !== "object") return false;
1369
+ if (Array.isArray(node)) {
1370
+ let changed = false;
1371
+ for (const child of node) if (migrateImportTypes(child)) changed = true;
1372
+ return changed;
1373
+ }
1374
+ const record = node;
1375
+ let changed = false;
1376
+ if (record.type === "TSImportType") {
1377
+ const argument = record.argument;
1378
+ const qualifier = record.qualifier;
1379
+ if (argument?.value === KIT_MODULE && qualifier?.type === "Identifier" && typeof qualifier.name === "string") {
1380
+ const destination = MOVED_EXPORTS.get(qualifier.name);
1381
+ if (destination) {
1382
+ argument.value = destination;
1383
+ argument.raw = void 0;
1384
+ changed = true;
1385
+ }
1386
+ }
1387
+ }
1388
+ for (const child of Object.values(record)) if (migrateImportTypes(child)) changed = true;
1389
+ return changed;
1390
+ }
1391
+ function migrateComments(comments) {
1392
+ let changed = false;
1393
+ for (const comment of comments) {
1394
+ let value = comment.value.replace(/import(\s*\(\s*)(['"])@sveltejs\/kit\2(\s*\)\s*\.\s*)([A-Za-z_$][\w$]*)/g, (match, open, quote, close, name) => {
1395
+ const destination = MOVED_EXPORTS.get(name);
1396
+ if (!destination) return match;
1397
+ changed = true;
1398
+ return `import${open}${quote}${destination}${quote}${close}${name}`;
1399
+ });
1400
+ value = value.replace(/@import\s*\{([^}]+)\}\s*from\s*(['"])@sveltejs\/kit\2(;?)/g, (match, list, quote, semicolon) => {
1401
+ const groups = /* @__PURE__ */ new Map();
1402
+ for (const specifier of list.split(",")) {
1403
+ const imported = /^(?:type\s+)?([A-Za-z_$][\w$]*)/.exec(specifier.trim())?.[1];
1404
+ const source = imported && MOVED_EXPORTS.get(imported);
1405
+ const group = groups.get(source ?? KIT_MODULE) ?? [];
1406
+ group.push(specifier.trim());
1407
+ groups.set(source ?? KIT_MODULE, group);
1408
+ }
1409
+ if (groups.size === 1 && groups.has(KIT_MODULE)) return match;
1410
+ changed = true;
1411
+ return [...groups].map(([source, specifiers]) => `@import { ${specifiers.join(", ")} } from ${quote}${source}${quote}${semicolon}`).join("\n * ");
1412
+ });
1413
+ comment.value = value;
1414
+ }
1415
+ return changed;
1416
+ }
1417
+ //#endregion
1418
+ //#region src/migrate/migrations/sveltekit-3/tasks/lib-alias.ts
1419
+ const LIB_ALIAS = /(?<=['"`])\$lib(\/(.+)['"`]|['"`])/g;
1420
+ function libSubpathImports(libDir) {
1421
+ return {
1422
+ "#lib": `./${libDir}/index.js`,
1423
+ "#lib/*": `./${libDir}/*`
1424
+ };
1425
+ }
1426
+ var lib_alias_default = defineMigrationTask({
1427
+ id: "lib-alias",
1428
+ description: "Replace the $lib alias with #lib subpath imports",
1429
+ run: ({ sv, cwd, directory }) => {
1430
+ sv.file("package.json", transforms.json(({ data }) => {
1431
+ data.imports = {
1432
+ ...libSubpathImports(directory.lib),
1433
+ ...data.imports
1434
+ };
1435
+ }));
1436
+ sv.files({
1437
+ include: `${directory.src}/**/*.{svelte,svelte.ts,svelte.js,ts,js,svx,md}`,
1438
+ where: (content) => content.includes("$lib")
1439
+ }, (content) => {
1440
+ const rewritten = content.replace(LIB_ALIAS, (match, _, import_path) => {
1441
+ match = match.replace("$lib", "#lib");
1442
+ if (import_path && !import_path.endsWith(".js") && !import_path.endsWith(".ts")) {
1443
+ for (const ending of [
1444
+ ".js",
1445
+ ".ts",
1446
+ "/index.js",
1447
+ "/index.ts"
1448
+ ]) if (fs.existsSync(`${cwd}/${directory.lib}/${import_path}${ending}`)) return match.slice(0, -1) + ending.replace(".ts", ".js") + match.slice(-1);
1449
+ }
1450
+ return match;
1451
+ });
1452
+ return rewritten === content ? false : rewritten;
1453
+ });
1454
+ }
1455
+ });
1456
+ //#endregion
1457
+ //#region src/migrate/migrations/sveltekit-3/tasks/package-json.ts
1458
+ const KIT3_PEERS = {
1459
+ vite: "^8.0.12",
1460
+ "@sveltejs/vite-plugin-svelte": "^7.0.0",
1461
+ svelte: "^5.56.4",
1462
+ "svelte-check": "^4.7.5",
1463
+ typescript: "^6.0.0"
1464
+ };
1465
+ var package_json_default = defineMigrationTask({
1466
+ id: "package-json",
1467
+ description: "Update package.json to be compatible with SvelteKit 3.0",
1468
+ run: ({ sv, cwd, language, dependencyVersion }) => {
1469
+ sv.devDependency("@sveltejs/kit", "next");
1470
+ for (const [pkg, range] of Object.entries(KIT3_PEERS)) {
1471
+ if (pkg === "typescript" && language !== "ts") continue;
1472
+ const current = dependencyVersion(pkg);
1473
+ if (current && isVersionUnsupportedBelow(current, range)) sv.devDependency(pkg, range);
1474
+ }
1475
+ const { data: pkg } = loadPackageJson(cwd);
1476
+ const dependencies = {
1477
+ ...pkg.devDependencies,
1478
+ ...pkg.dependencies
1479
+ };
1480
+ for (const name of Object.keys(dependencies)) if (name.startsWith("@sveltejs/adapter-")) sv.devDependency(name, "next");
1481
+ }
1482
+ });
1483
+ //#endregion
1484
+ //#region src/migrate/migrations/sveltekit-3/tasks/params.ts
1485
+ const MATCHER_FILE = /^\w+\.[jt]s$/;
1486
+ const TEST_HARNESS_FILE = /\.template\.[jt]s$/;
1487
+ var params_default = defineMigrationTask({
1488
+ id: "params",
1489
+ description: "Consolidate parameter matchers into src/params.js or src/params.ts",
1490
+ run: ({ sv, cwd, directory, language }) => {
1491
+ const paramsDirectory = path.posix.join(directory.src, "params");
1492
+ const absoluteParamsDirectory = path.join(cwd, paramsDirectory);
1493
+ if (!fs.existsSync(absoluteParamsDirectory)) return;
1494
+ const entries = fs.readdirSync(absoluteParamsDirectory, { withFileTypes: true });
1495
+ const isMatcher = (entry) => entry.isFile() && MATCHER_FILE.test(entry.name);
1496
+ const foreignEntries = entries.filter((entry) => !isMatcher(entry) && !TEST_HARNESS_FILE.test(entry.name));
1497
+ const matcherFiles = entries.filter(isMatcher).map((entry) => path.posix.join(paramsDirectory, entry.name)).sort();
1498
+ if (matcherFiles.length === 0) return;
1499
+ const target = `${paramsDirectory}.${language}`;
1500
+ if (fs.existsSync(path.join(cwd, target))) throw new Error(`Cannot migrate parameter matchers because '${target}' already exists`);
1501
+ const context = {
1502
+ usedNames: /* @__PURE__ */ new Set(["defineParams", "params"]),
1503
+ imports: /* @__PURE__ */ new Map(),
1504
+ sideEffectImports: /* @__PURE__ */ new Set()
1505
+ };
1506
+ context.imports.set("named:defineParams:@sveltejs/kit/params", {
1507
+ source: "@sveltejs/kit/params",
1508
+ kind: "named",
1509
+ imported: "defineParams",
1510
+ local: "defineParams",
1511
+ typeOnly: false
1512
+ });
1513
+ const matchers = matcherFiles.map((file) => migrateMatcher(cwd, file, target, context));
1514
+ const parts = [];
1515
+ if (foreignEntries.length > 0) {
1516
+ const names = foreignEntries.map((entry) => entry.name).sort().join(", ");
1517
+ parts.push(createMigrationTaskComment(`'${paramsDirectory}' contains entries that are not parameter matchers (${names}) and was left in place - migrate them and delete the directory manually`));
1518
+ }
1519
+ parts.push(...generateImportCode(context), "");
1520
+ for (const matcher of matchers) if (matcher.hoisted) parts.push(matcher.hoisted, "");
1521
+ parts.push("export const params = defineParams({", matchers.map((matcher) => matcher.entry).join(",\n"), "});", "");
1522
+ const content = parts.join("\n");
1523
+ sv.file(target, () => content);
1524
+ if (foreignEntries.length > 0) return;
1525
+ for (const file of matcherFiles) fs.unlinkSync(path.join(cwd, file));
1526
+ if (fs.readdirSync(absoluteParamsDirectory).length === 0) fs.rmdirSync(absoluteParamsDirectory);
1527
+ }
1528
+ });
1529
+ function migrateMatcher(cwd, file, target, context) {
1530
+ const name = path.posix.basename(file).slice(0, -3);
1531
+ const content = stripParamMatcherComments(fs.readFileSync(path.join(cwd, file), "utf8"));
1532
+ const parsed = parse.script(content);
1533
+ const importDeclarations = [];
1534
+ const body = [];
1535
+ const paramMatcherTypes = /* @__PURE__ */ new Set(["ParamMatcher"]);
1536
+ let declaresMatch = false;
1537
+ let exportsMatch = false;
1538
+ for (const statement of parsed.ast.body) {
1539
+ if (statement.type === "ImportDeclaration") {
1540
+ if (statement.source.value === "@sveltejs/kit" || statement.source.value === "@sveltejs/kit/params") {
1541
+ for (const specifier of statement.specifiers) if (specifier.type === "ImportSpecifier" && specifier.imported.type === "Identifier" && specifier.imported.name === "ParamMatcher") paramMatcherTypes.add(specifier.local.name);
1542
+ statement.specifiers = statement.specifiers.filter((specifier) => specifier.type !== "ImportSpecifier" || specifier.imported.type !== "Identifier" || specifier.imported.name !== "ParamMatcher");
1543
+ if (statement.specifiers.length === 0) continue;
1544
+ }
1545
+ let source = String(statement.source.value);
1546
+ if (source.startsWith(".")) source = relocateImport(source, file, target);
1547
+ if (statement.specifiers.length === 0) context.sideEffectImports.add(source);
1548
+ else importDeclarations.push({
1549
+ declaration: statement,
1550
+ source
1551
+ });
1552
+ continue;
1553
+ }
1554
+ if (statement.type === "ExportDefaultDeclaration") throw new Error(`Cannot migrate '${file}' because it contains a default export`);
1555
+ if (statement.type === "ExportAllDeclaration") throw new Error(`Cannot migrate '${file}' because it re-exports another module`);
1556
+ if (statement.type === "ExportNamedDeclaration") {
1557
+ if (statement.declaration) {
1558
+ stripParamMatcherType(statement.declaration, paramMatcherTypes);
1559
+ if (declares(statement.declaration, "match")) {
1560
+ declaresMatch = true;
1561
+ exportsMatch = true;
1562
+ }
1563
+ body.push(statement.declaration);
1564
+ } else exportsMatch ||= statement.specifiers.some((specifier) => specifier.exported.type === "Identifier" && specifier.exported.name === "match");
1565
+ continue;
1566
+ }
1567
+ stripParamMatcherType(statement, paramMatcherTypes);
1568
+ declaresMatch ||= declares(statement, "match");
1569
+ body.push(statement);
1570
+ }
1571
+ if (!declaresMatch || !exportsMatch) throw new Error(`Cannot migrate '${file}' because it does not export a local 'match' declaration`);
1572
+ const renames = /* @__PURE__ */ new Map();
1573
+ for (const { declaration, source } of importDeclarations) {
1574
+ const typeOnly = declaration.importKind === "type";
1575
+ for (const specifier of declaration.specifiers) {
1576
+ const local = specifier.local.name;
1577
+ const finalLocal = registerImport(context, {
1578
+ source,
1579
+ kind: specifier.type === "ImportDefaultSpecifier" ? "default" : specifier.type === "ImportNamespaceSpecifier" ? "namespace" : "named",
1580
+ imported: specifier.type === "ImportDefaultSpecifier" ? "default" : specifier.type === "ImportNamespaceSpecifier" ? "*" : specifier.imported.type === "Identifier" ? specifier.imported.name : String(specifier.imported.value),
1581
+ local,
1582
+ typeOnly
1583
+ });
1584
+ if (finalLocal !== local) renames.set(local, finalLocal);
1585
+ }
1586
+ }
1587
+ const nestedBindings = js.scope.nestedBindingNames(body);
1588
+ for (const local of renames.keys()) if (nestedBindings.has(local)) throw new Error(`Cannot migrate '${file}' because the imported name '${local}' conflicts with another matcher and cannot be renamed safely`);
1589
+ const identifier = name.replace(/\W/g, "_");
1590
+ const preferredMatchName = `match${identifier.charAt(0).toUpperCase()}${identifier.slice(1)}`;
1591
+ parsed.ast.body = body;
1592
+ const topLevelBindings = js.scope.topLevelBindings([parsed.ast]);
1593
+ const tentativeNames = new Set(context.usedNames);
1594
+ const bodyRenames = /* @__PURE__ */ new Map();
1595
+ for (const binding of topLevelBindings) {
1596
+ const preferred = binding === "match" ? preferredMatchName : binding;
1597
+ const finalName = js.identifiers.uniqueName(preferred, tentativeNames);
1598
+ if (finalName !== binding) bodyRenames.set(binding, finalName);
1599
+ }
1600
+ const hoistable = [...bodyRenames.keys()].every((binding) => !nestedBindings.has(binding));
1601
+ if (hoistable) {
1602
+ for (const binding of topLevelBindings) context.usedNames.add(bodyRenames.get(binding) ?? binding);
1603
+ for (const [from, to] of bodyRenames) renames.set(from, to);
1604
+ }
1605
+ js.identifiers.renameReferences([parsed.ast], renames);
1606
+ const bodyCode = parsed.generateCode().trim();
1607
+ if (hoistable) {
1608
+ const matchName = bodyRenames.get("match");
1609
+ return {
1610
+ entry: `\t${propertyName(name)}: (param) => (${matchName}(param) ? param : undefined)`,
1611
+ hoisted: bodyCode
1612
+ };
1613
+ }
1614
+ return { entry: `\t${propertyName(name)}: (() => {\n${indent(bodyCode, 2)}\n\n\t\treturn (param) => (match(param) ? param : undefined);\n\t})()` };
1615
+ }
1616
+ /** Registers an import binding, deduplicating it and returning the module-level local name. */
1617
+ function registerImport(context, binding) {
1618
+ const key = `${binding.kind}:${binding.imported}:${binding.source}`;
1619
+ const existing = context.imports.get(key);
1620
+ if (existing) {
1621
+ existing.typeOnly &&= binding.typeOnly;
1622
+ return existing.local;
1623
+ }
1624
+ const local = js.identifiers.uniqueName(binding.local, context.usedNames);
1625
+ context.imports.set(key, {
1626
+ ...binding,
1627
+ local
1628
+ });
1629
+ return local;
1630
+ }
1631
+ function generateImportCode(context) {
1632
+ const bySource = /* @__PURE__ */ new Map();
1633
+ for (const binding of context.imports.values()) {
1634
+ const group = bySource.get(binding.source) ?? [];
1635
+ group.push(binding);
1636
+ bySource.set(binding.source, group);
1637
+ }
1638
+ const lines = [];
1639
+ for (const [source, bindings] of bySource) {
1640
+ const from = `from ${quoteString(source)};`;
1641
+ const defaultBinding = bindings.find((binding) => binding.kind === "default");
1642
+ const namespaceBinding = bindings.find((binding) => binding.kind === "namespace");
1643
+ const namedBindings = bindings.filter((binding) => binding.kind === "named");
1644
+ let defaultEmitted = false;
1645
+ if (namespaceBinding) {
1646
+ const prefix = defaultBinding && namedBindings.length === 0 ? `${defaultBinding.local}, ` : "";
1647
+ defaultEmitted = prefix !== "";
1648
+ lines.push(`import ${prefix}* as ${namespaceBinding.local} ${from}`);
1649
+ }
1650
+ if (namedBindings.length > 0 || defaultBinding && !defaultEmitted) {
1651
+ const typeOnly = namedBindings.length > 0 && namedBindings.every((binding) => binding.typeOnly) && (!defaultBinding || defaultEmitted);
1652
+ const specifiers = namedBindings.map((binding) => {
1653
+ const imported = /^[$A-Z_a-z][$\w]*$/.test(binding.imported) ? binding.imported : quoteString(binding.imported);
1654
+ const specifier = imported === binding.local ? binding.local : `${imported} as ${binding.local}`;
1655
+ return !typeOnly && binding.typeOnly ? `type ${specifier}` : specifier;
1656
+ }).join(", ");
1657
+ const clauses = [];
1658
+ if (defaultBinding && !defaultEmitted) clauses.push(defaultBinding.local);
1659
+ if (namedBindings.length > 0) clauses.push(`{ ${specifiers} }`);
1660
+ lines.push(`import ${typeOnly ? "type " : ""}${clauses.join(", ")} ${from}`);
1661
+ }
1662
+ }
1663
+ for (const source of context.sideEffectImports) lines.push(`import ${quoteString(source)};`);
1664
+ return lines;
1665
+ }
1666
+ function stripParamMatcherType(node, paramMatcherTypes) {
1667
+ if (node.type !== "VariableDeclaration") return;
1668
+ const declaration = node.declarations.find((declaration) => declaration.id.type === "Identifier" && declaration.id.name === "match");
1669
+ if (!declaration || declaration.id.type !== "Identifier") return;
1670
+ const annotation = declaration.id.typeAnnotation?.typeAnnotation;
1671
+ if (annotation?.type === "TSTypeReference" && annotation.typeName.type === "Identifier" && paramMatcherTypes.has(annotation.typeName.name)) delete declaration.id.typeAnnotation;
1672
+ if ((declaration.init?.type === "TSSatisfiesExpression" || declaration.init?.type === "TSAsExpression") && declaration.init.typeAnnotation.type === "TSTypeReference" && declaration.init.typeAnnotation.typeName.type === "Identifier" && paramMatcherTypes.has(declaration.init.typeAnnotation.typeName.name)) declaration.init = declaration.init.expression;
1673
+ }
1674
+ function stripParamMatcherComments(content) {
1675
+ return content.replace(/\/\*\*[\s\S]*?\*\//g, (comment) => {
1676
+ if (!comment.includes("ParamMatcher")) return comment;
1677
+ const lines = comment.split("\n").filter((line) => !line.includes("ParamMatcher"));
1678
+ return lines.length > 2 ? lines.join("\n") : "";
1679
+ });
1680
+ }
1681
+ function declares(node, name) {
1682
+ if ((node.type === "FunctionDeclaration" || node.type === "ClassDeclaration") && node.id?.name === name) return true;
1683
+ return node.type === "VariableDeclaration" && node.declarations.some((declaration) => declaration.id.type === "Identifier" && js.scope.collectPatternNames(declaration.id).includes(name));
1684
+ }
1685
+ function relocateImport(source, from, to) {
1686
+ const resolved = path.posix.normalize(path.posix.join(path.posix.dirname(from), source));
1687
+ let relative = path.posix.relative(path.posix.dirname(to), resolved);
1688
+ if (!relative.startsWith(".")) relative = `./${relative}`;
1689
+ return relative;
1690
+ }
1691
+ function propertyName(name) {
1692
+ return /^[$A-Z_a-z][$\w]*$/.test(name) ? name : quoteString(name);
1693
+ }
1694
+ function quoteString(value) {
1695
+ return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'`;
1696
+ }
1697
+ function indent(content, level) {
1698
+ const prefix = " ".repeat(level);
1699
+ return content.split("\n").map((line) => line ? `${prefix}${line}` : line).join("\n");
1700
+ }
1701
+ //#endregion
1702
+ //#region src/migrate/migrations/sveltekit-3/tasks/paths.ts
1703
+ const PATHS_MODULE = "$app/paths";
1704
+ const TYPES_MODULE = "$app/types";
1705
+ var paths_default = defineMigrationTask({
1706
+ id: "paths",
1707
+ description: "Migrate deprecated $app/paths APIs and path types",
1708
+ run: ({ sv }) => {
1709
+ sv.files({
1710
+ include: "**/*.{js,ts,svelte}",
1711
+ where: (content) => content.includes(PATHS_MODULE) || content.includes(TYPES_MODULE)
1712
+ }, (content, file) => {
1713
+ if (file.endsWith(".svelte")) return transforms.svelte(({ ast }) => {
1714
+ const programs = [];
1715
+ if (ast.module) programs.push(ast.module.content);
1716
+ if (ast.instance) programs.push(ast.instance.content);
1717
+ if (!migratePaths(programs, ast.fragment)) return false;
1718
+ })(content);
1719
+ return transforms.script(({ ast }) => {
1720
+ if (!migratePaths([ast])) return false;
1721
+ })(content);
1722
+ });
1723
+ }
1724
+ });
1725
+ function migratePaths(programs, fragment) {
1726
+ const roots = fragment ? [...programs, fragment] : programs;
1727
+ const pathBindings = programs.flatMap((program) => js.imports.bindings(program, { from: PATHS_MODULE }));
1728
+ const typeBindings = programs.flatMap((program) => js.imports.bindings(program, { from: TYPES_MODULE }));
1729
+ if (pathBindings.length === 0 && typeBindings.length === 0) return false;
1730
+ const baseLocals = importedLocals(pathBindings, "base");
1731
+ const assetsLocals = importedLocals(pathBindings, "assets");
1732
+ const resolveRouteLocals = importedLocals(pathBindings, "resolveRoute");
1733
+ const existingResolve = importAlias(pathBindings, "resolve");
1734
+ const existingAsset = importAlias(pathBindings, "asset");
1735
+ const needsResolve = baseLocals.length > 0 || resolveRouteLocals.length > 0;
1736
+ const needsAsset = assetsLocals.length > 0;
1737
+ const resolveLocal = existingResolve ?? preferredReplacementLocal(resolveRouteLocals, "resolve") ?? availableImportLocal(programs, "resolve", "resolvePath", [...baseLocals, ...resolveRouteLocals]);
1738
+ const assetLocal = existingAsset ?? availableImportLocal(programs, "asset", "assetPath", assetsLocals);
1739
+ const shadowedIdentifiers = js.scope.findShadowedIdentifiers(roots, [
1740
+ ...baseLocals,
1741
+ ...assetsLocals,
1742
+ ...resolveRouteLocals,
1743
+ resolveLocal,
1744
+ assetLocal
1745
+ ]);
1746
+ const resolveRouteCalls = /* @__PURE__ */ new Set();
1747
+ for (const root of roots) Walker.walk(root, null, { CallExpression(node, ctx) {
1748
+ if (isNamedIdentifier(node.callee, resolveRouteLocals) && !shadowedIdentifiers.has(node.callee)) resolveRouteCalls.add(node);
1749
+ ctx.next();
1750
+ } });
1751
+ let changed = false;
1752
+ if (baseLocals.length > 0 || assetsLocals.length > 0) changed = collapsePathPrefixes(roots, baseLocals, assetsLocals, resolveRouteLocals, resolveLocal, assetLocal, shadowedIdentifiers);
1753
+ if (js.identifiers.replaceReferences(roots, baseLocals, () => js.functions.createCall({
1754
+ name: resolveLocal,
1755
+ args: [""]
1756
+ }))) changed = true;
1757
+ if (js.identifiers.replaceReferences(roots, assetsLocals, () => js.functions.createCall({
1758
+ name: assetLocal,
1759
+ args: [""]
1760
+ }))) changed = true;
1761
+ if (js.imports.renameBinding(roots, {
1762
+ from: PATHS_MODULE,
1763
+ name: "assets",
1764
+ to: "asset",
1765
+ local: assetLocal
1766
+ })) changed = true;
1767
+ if (js.imports.renameBinding(roots, {
1768
+ from: PATHS_MODULE,
1769
+ name: "base",
1770
+ to: "resolve",
1771
+ local: resolveLocal
1772
+ })) changed = true;
1773
+ if (js.imports.renameBinding(roots, {
1774
+ from: PATHS_MODULE,
1775
+ name: "resolveRoute",
1776
+ to: "resolve",
1777
+ local: resolveLocal
1778
+ })) changed = true;
1779
+ const renamedAsset = renameImportedType(programs, roots, typeBindings, "Asset", "AssetPath");
1780
+ if (renameImportedType(programs, roots, typeBindings, "Pathname", "Path") || renamedAsset) changed = true;
1781
+ if (normalizePathCalls(roots, existingResolve !== void 0 || needsResolve ? resolveLocal : void 0, existingAsset !== void 0 || needsAsset ? assetLocal : void 0, resolveRouteCalls, shadowedIdentifiers)) changed = true;
1782
+ return changed;
1783
+ }
1784
+ function importAlias(bindings, name) {
1785
+ return bindings.find((binding) => binding.imported === name)?.local;
1786
+ }
1787
+ function importedLocals(bindings, name) {
1788
+ return bindings.filter((binding) => binding.kind === "named" && binding.imported === name).map((binding) => binding.local);
1789
+ }
1790
+ function preferredReplacementLocal(locals, replacement) {
1791
+ const local = locals[0];
1792
+ if (!local) return void 0;
1793
+ return local === "resolveRoute" ? replacement : local;
1794
+ }
1795
+ function collapsePathPrefixes(roots, baseLocals, assetsLocals, resolveRouteLocals, resolveLocal, assetLocal, shadowedIdentifiers) {
1796
+ let changed = false;
1797
+ const visitors = {
1798
+ BinaryExpression(node, ctx) {
1799
+ if (node.operator !== "+" || node.left.type !== "Identifier" || shadowedIdentifiers.has(node.left)) {
1800
+ ctx.next();
1801
+ return;
1802
+ }
1803
+ let replacement;
1804
+ if (baseLocals.includes(node.left.name)) replacement = isResolveRouteCall(node.right, resolveRouteLocals, shadowedIdentifiers) ? node.right : js.functions.createCall({
1805
+ name: resolveLocal,
1806
+ args: [withoutLeadingSlash(node.right)]
1807
+ });
1808
+ else if (assetsLocals.includes(node.left.name)) replacement = js.functions.createCall({
1809
+ name: assetLocal,
1810
+ args: [withoutLeadingSlash(node.right)]
1811
+ });
1812
+ if (!replacement) {
1813
+ ctx.next();
1814
+ return;
1815
+ }
1816
+ js.common.replaceChild(ctx.path[ctx.path.length - 1], node, replacement);
1817
+ changed = true;
1818
+ },
1819
+ TemplateLiteral(node, ctx) {
1820
+ const first = node.expressions[0];
1821
+ const parent = ctx.path[ctx.path.length - 1];
1822
+ if (first?.type !== "Identifier" || shadowedIdentifiers.has(first) || node.quasis[0]?.value.raw !== "" || parent.type === "TaggedTemplateExpression" && parent.quasi === node) {
1823
+ ctx.next();
1824
+ return;
1825
+ }
1826
+ let callee;
1827
+ if (baseLocals.includes(first.name)) callee = resolveLocal;
1828
+ if (assetsLocals.includes(first.name)) callee = assetLocal;
1829
+ if (!callee) {
1830
+ ctx.next();
1831
+ return;
1832
+ }
1833
+ const rest = {
1834
+ type: "TemplateLiteral",
1835
+ expressions: node.expressions.slice(1),
1836
+ quasis: node.quasis.slice(1)
1837
+ };
1838
+ if (baseLocals.includes(first.name) && rest.quasis[0]?.value.raw === "" && rest.expressions[0] && isResolveRouteCall(rest.expressions[0], resolveRouteLocals, shadowedIdentifiers)) {
1839
+ const onlyCall = rest.expressions.length === 1 && rest.quasis.every((quasi) => quasi.value.raw === "");
1840
+ js.common.replaceChild(parent, node, onlyCall ? rest.expressions[0] : rest);
1841
+ changed = true;
1842
+ return;
1843
+ }
1844
+ js.common.replaceChild(parent, node, js.functions.createCall({
1845
+ name: callee,
1846
+ args: [withoutLeadingSlash(rest)]
1847
+ }));
1848
+ changed = true;
1849
+ }
1850
+ };
1851
+ for (const root of roots) Walker.walk(root, null, visitors);
1852
+ return changed;
1853
+ }
1854
+ function renameImportedType(programs, roots, bindings, oldName, newName) {
1855
+ const oldLocals = importedLocals(bindings, oldName);
1856
+ if (oldLocals.length === 0) return false;
1857
+ const targetLocal = importAlias(bindings, newName) ?? (oldLocals[0] === oldName ? availableImportLocal(programs, newName, `App${newName}`, oldLocals) : oldLocals[0]);
1858
+ return js.imports.renameBinding(roots, {
1859
+ from: TYPES_MODULE,
1860
+ name: oldName,
1861
+ to: newName,
1862
+ local: targetLocal
1863
+ });
1864
+ }
1865
+ function availableImportLocal(programs, preferred, fallback, replacedLocals) {
1866
+ const bindings = js.scope.topLevelBindings(programs);
1867
+ for (const local of replacedLocals) bindings.delete(local);
1868
+ if (!bindings.has(preferred) || replacedLocals.includes(preferred)) return preferred;
1869
+ return js.identifiers.uniqueName(fallback, bindings);
1870
+ }
1871
+ function normalizePathCalls(roots, resolveLocal, assetLocal, resolveRouteCalls, shadowedIdentifiers) {
1872
+ let changed = false;
1873
+ const visitors = { CallExpression(node, ctx) {
1874
+ const argument = node.arguments[0];
1875
+ if (!argument || argument.type === "SpreadElement") {
1876
+ ctx.next();
1877
+ return;
1878
+ }
1879
+ const isAsset = assetLocal !== void 0 && node.callee.type === "Identifier" && node.callee.name === assetLocal;
1880
+ const isPathname = resolveLocal !== void 0 && node.callee.type === "Identifier" && node.callee.name === resolveLocal && node.arguments.length === 1 && !resolveRouteCalls.has(node) && !looksLikeRouteId(argument);
1881
+ if ((isAsset || isPathname) && node.callee.type === "Identifier" && !shadowedIdentifiers.has(node.callee) && removeStaticLeadingSlash(argument)) changed = true;
1882
+ ctx.next();
1883
+ } };
1884
+ for (const root of roots) Walker.walk(root, null, visitors);
1885
+ return changed;
1886
+ }
1887
+ function looksLikeRouteId(expression) {
1888
+ if (expression.type === "Literal" && typeof expression.value === "string") return /[[(]/.test(expression.value);
1889
+ if (expression.type === "TemplateLiteral") return expression.quasis.some((quasi) => /[[(]/.test(quasi.value.raw));
1890
+ return false;
1891
+ }
1892
+ function withoutLeadingSlash(expression) {
1893
+ if (removeStaticLeadingSlash(expression)) return expression;
1894
+ return {
1895
+ type: "CallExpression",
1896
+ callee: {
1897
+ type: "MemberExpression",
1898
+ object: expression,
1899
+ property: {
1900
+ type: "Identifier",
1901
+ name: "slice"
1902
+ },
1903
+ computed: false,
1904
+ optional: false
1905
+ },
1906
+ arguments: [js.common.createLiteral(1)],
1907
+ optional: false
1908
+ };
1909
+ }
1910
+ function removeStaticLeadingSlash(expression) {
1911
+ if (expression.type === "Literal" && typeof expression.value === "string") {
1912
+ if (!expression.value.startsWith("/")) return false;
1913
+ expression.value = expression.value.slice(1);
1914
+ expression.raw = void 0;
1915
+ return true;
1916
+ }
1917
+ if (expression.type === "TemplateLiteral") {
1918
+ const quasi = expression.quasis[0];
1919
+ if (!quasi?.value.raw.startsWith("/")) return false;
1920
+ quasi.value.raw = quasi.value.raw.slice(1);
1921
+ if (quasi.value.cooked !== void 0 && quasi.value.cooked !== null) quasi.value.cooked = quasi.value.cooked.slice(1);
1922
+ return true;
1923
+ }
1924
+ return false;
1925
+ }
1926
+ function isResolveRouteCall(expression, resolveRouteLocals, shadowedIdentifiers) {
1927
+ return expression.type === "CallExpression" && isNamedIdentifier(expression.callee, resolveRouteLocals) && !shadowedIdentifiers.has(expression.callee);
1928
+ }
1929
+ function isNamedIdentifier(node, names) {
1930
+ return node.type === "Identifier" && names.includes(node.name);
1931
+ }
1932
+ //#endregion
1933
+ //#region src/migrate/migrations/sveltekit-3/tasks/shallow-routing.ts
1934
+ const NAVIGATION_MODULE = "$app/navigation";
1935
+ const SHALLOW_METHODS = /* @__PURE__ */ new Map([["pushState", false], ["replaceState", true]]);
1936
+ const NAVIGATION_HOOKS = /* @__PURE__ */ new Set([
1937
+ "beforeNavigate",
1938
+ "afterNavigate",
1939
+ "onNavigate"
1940
+ ]);
1941
+ var shallow_routing_default = defineMigrationTask({
1942
+ id: "shallow-routing",
1943
+ description: "Migrate shallow routing to goto()",
1944
+ run: ({ sv, language }) => {
1945
+ sv.files({
1946
+ include: "**/*.{js,ts,svelte}",
1947
+ where: (content) => content.includes(NAVIGATION_MODULE)
1948
+ }, (content, file) => {
1949
+ if (file.endsWith(".svelte")) return transforms.svelteScript({ language }, ({ ast }) => {
1950
+ if (!migrateShallowRouting(ast.instance.content)) return false;
1951
+ })(content);
1952
+ return transforms.script(({ ast }) => {
1953
+ if (!migrateShallowRouting(ast)) return false;
1954
+ })(content);
1955
+ });
1956
+ }
1957
+ });
1958
+ function migrateShallowRouting(ast) {
1959
+ const methods = /* @__PURE__ */ new Map();
1960
+ const hooks = /* @__PURE__ */ new Set();
1961
+ const oldSpecifiers = [];
1962
+ const navigationImports = [];
1963
+ let gotoLocal;
1964
+ for (const binding of js.imports.bindings(ast, { from: NAVIGATION_MODULE })) {
1965
+ if (binding.imported === "goto") gotoLocal = binding.local;
1966
+ if (NAVIGATION_HOOKS.has(binding.imported)) hooks.add(binding.local);
1967
+ const replace = SHALLOW_METHODS.get(binding.imported);
1968
+ if (replace !== void 0) methods.set(binding.local, replace);
1969
+ if (replace !== void 0 && binding.kind === "named") oldSpecifiers.push(binding.specifier);
1970
+ }
1971
+ for (const statement of ast.body) {
1972
+ if (statement.type !== "ImportDeclaration" || statement.source.value !== NAVIGATION_MODULE) continue;
1973
+ navigationImports.push(statement);
1974
+ }
1975
+ if (methods.size === 0 && hooks.size === 0) return false;
1976
+ const { unmigratable, migratable } = scanMethodCalls(ast, methods);
1977
+ const removable = oldSpecifiers.filter((specifier) => !unmigratable.has(specifier.local.name));
1978
+ let changed = false;
1979
+ if (!gotoLocal && migratable) {
1980
+ const specifier = removable.shift();
1981
+ if (specifier) {
1982
+ const wasUnaliased = specifier.local.name === specifier.imported.name;
1983
+ gotoLocal = specifier.local.name;
1984
+ if (wasUnaliased && js.identifiers.freeName([ast], "goto") === "goto") {
1985
+ specifier.imported.name = "goto";
1986
+ specifier.local.name = "goto";
1987
+ gotoLocal = "goto";
1988
+ } else specifier.imported = js.variables.createIdentifier("goto");
1989
+ } else {
1990
+ gotoLocal = js.identifiers.freeName([ast], "goto");
1991
+ navigationImports.find((navigationImport) => navigationImport.specifiers.some((s) => oldSpecifiers.includes(s))).specifiers.push({
1992
+ type: "ImportSpecifier",
1993
+ imported: js.variables.createIdentifier("goto"),
1994
+ local: js.variables.createIdentifier(gotoLocal)
1995
+ });
1996
+ }
1997
+ changed = true;
1998
+ }
1999
+ if (removable.length > 0) {
2000
+ const emptiedImports = /* @__PURE__ */ new Set();
2001
+ for (const declaration of navigationImports) {
2002
+ if (declaration.specifiers.length === 0) continue;
2003
+ declaration.specifiers = declaration.specifiers.filter((specifier) => !removable.includes(specifier));
2004
+ if (declaration.specifiers.length === 0) emptiedImports.add(declaration);
2005
+ }
2006
+ ast.body = ast.body.filter((statement) => statement.type !== "ImportDeclaration" || !emptiedImports.has(statement));
2007
+ changed = true;
2008
+ }
2009
+ Walker.walk(ast, null, { CallExpression(node, { next }) {
2010
+ if (node.callee.type !== "Identifier") {
2011
+ next();
2012
+ return;
2013
+ }
2014
+ const replace = methods.get(node.callee.name);
2015
+ if (replace !== void 0 && gotoLocal && !node.arguments.some((argument) => argument.type === "SpreadElement")) {
2016
+ const state = node.arguments[1];
2017
+ node.callee.name = gotoLocal;
2018
+ node.arguments = [...node.arguments[0] ? [node.arguments[0]] : [], gotoOptions(replace, state)];
2019
+ changed = true;
2020
+ }
2021
+ if (hooks.has(node.callee.name) && migrateNavigationHook(node)) changed = true;
2022
+ next();
2023
+ } });
2024
+ return changed;
2025
+ }
2026
+ function scanMethodCalls(ast, methods) {
2027
+ const unmigratable = /* @__PURE__ */ new Set();
2028
+ let migratable = false;
2029
+ Walker.walk(ast, null, { CallExpression(node, { next }) {
2030
+ if (node.callee.type === "Identifier" && methods.has(node.callee.name)) if (node.arguments.some((argument) => argument.type === "SpreadElement")) unmigratable.add(node.callee.name);
2031
+ else migratable = true;
2032
+ next();
2033
+ } });
2034
+ return {
2035
+ unmigratable,
2036
+ migratable
2037
+ };
2038
+ }
2039
+ function gotoOptions(replace, state) {
2040
+ return js.object.create({
2041
+ shallow: js.common.createLiteral(true),
2042
+ replace: replace ? js.common.createLiteral(true) : void 0,
2043
+ state
2044
+ });
2045
+ }
2046
+ function migrateNavigationHook(call) {
2047
+ const callback = call.arguments[0];
2048
+ if (callback?.type !== "ArrowFunctionExpression" && callback?.type !== "FunctionExpression") return false;
2049
+ const shallow = shallowTest(callback);
2050
+ if (!shallow) return false;
2051
+ const guard = {
2052
+ type: "IfStatement",
2053
+ test: shallow,
2054
+ consequent: {
2055
+ type: "ReturnStatement",
2056
+ argument: null
2057
+ }
2058
+ };
2059
+ if (callback.body.type === "BlockStatement") {
2060
+ if (startsWithShallowGuard(callback.body, shallow)) return false;
2061
+ callback.body.body.unshift(guard);
2062
+ } else callback.body = {
2063
+ type: "BlockStatement",
2064
+ body: [guard, {
2065
+ type: "ReturnStatement",
2066
+ argument: callback.body
2067
+ }]
2068
+ };
2069
+ return true;
2070
+ }
2071
+ function shallowTest(callback) {
2072
+ const parameter = callback.params[0];
2073
+ if (!parameter) {
2074
+ callback.params.push({
2075
+ type: "ObjectPattern",
2076
+ properties: [patternProperty("shallow")]
2077
+ });
2078
+ return js.variables.createIdentifier("shallow");
2079
+ }
2080
+ if (parameter.type === "Identifier") return member(parameter.name, "shallow");
2081
+ if (parameter.type === "AssignmentPattern" && parameter.left.type === "Identifier") return member(parameter.left.name, "shallow");
2082
+ if (parameter.type !== "ObjectPattern") return void 0;
2083
+ for (const entry of parameter.properties) {
2084
+ if (entry.type !== "Property") continue;
2085
+ const key = entry.key;
2086
+ if ((key.type !== "Identifier" || key.name !== "shallow") && (key.type !== "Literal" || key.value !== "shallow")) continue;
2087
+ if (entry.value.type === "Identifier") return js.variables.createIdentifier(entry.value.name);
2088
+ if (entry.value.type === "AssignmentPattern" && entry.value.left.type === "Identifier") return js.variables.createIdentifier(entry.value.left.name);
2089
+ return;
2090
+ }
2091
+ const restIndex = parameter.properties.findIndex((entry) => entry.type === "RestElement");
2092
+ const shallowProperty = patternProperty("shallow");
2093
+ if (restIndex === -1) parameter.properties.push(shallowProperty);
2094
+ else parameter.properties.splice(restIndex, 0, shallowProperty);
2095
+ return js.variables.createIdentifier("shallow");
2096
+ }
2097
+ function startsWithShallowGuard(body, shallow) {
2098
+ const first = body.body[0];
2099
+ return first?.type === "IfStatement" && first.consequent.type === "ReturnStatement" && first.consequent.argument === null && sameExpression(first.test, shallow);
2100
+ }
2101
+ function sameExpression(left, right) {
2102
+ if (left.type === "Identifier" && right.type === "Identifier") return left.name === right.name;
2103
+ return left.type === "MemberExpression" && right.type === "MemberExpression" && left.object.type === "Identifier" && right.object.type === "Identifier" && left.object.name === right.object.name && left.property.type === "Identifier" && right.property.type === "Identifier" && left.property.name === right.property.name;
2104
+ }
2105
+ function patternProperty(name) {
2106
+ const value = js.variables.createIdentifier(name);
2107
+ return {
2108
+ type: "Property",
2109
+ key: js.variables.createIdentifier(name),
2110
+ value,
2111
+ kind: "init",
2112
+ method: false,
2113
+ shorthand: true,
2114
+ computed: false
2115
+ };
2116
+ }
2117
+ function member(object, property) {
2118
+ return {
2119
+ type: "MemberExpression",
2120
+ object: js.variables.createIdentifier(object),
2121
+ property: js.variables.createIdentifier(property),
2122
+ computed: false,
2123
+ optional: false
2124
+ };
2125
+ }
2126
+ //#endregion
2127
+ //#region src/migrate/migrations/sveltekit-3/tasks/svelte-config.ts
2128
+ const SVELTE_CONFIG_IMPORT = /(^|\/)svelte\.config(\.[mc]?[jt]s)?$/;
2129
+ var svelte_config_default = defineMigrationTask({
2130
+ id: "svelte-config",
2131
+ description: "Migrate svelte.config.* to vite.config.*",
2132
+ run: ({ sv, cwd }) => {
2133
+ const configSource = svelteConfig.find(cwd);
2134
+ if (!configSource) return;
2135
+ if (configSource.kind === "vite") return;
2136
+ const originalConfigObject = svelteConfig.read(cwd);
2137
+ if (!originalConfigObject) return;
2138
+ fs.unlinkSync(path.join(cwd, configSource.path));
2139
+ svelteConfig.edit({
2140
+ sv,
45
2141
  cwd
2142
+ }, ({ ast, override, comments }) => {
2143
+ const configDeclaration = originalConfigObject.ast.body.find((node) => node.type === "VariableDeclaration" && node.declarations.some((d) => d.init === originalConfigObject.config));
2144
+ const originalImports = [];
2145
+ const originalStatements = [];
2146
+ for (const node of originalConfigObject.ast.body) if (node.type === "ImportDeclaration") originalImports.push(node);
2147
+ else if (node.type !== "ExportDefaultDeclaration" && node !== configDeclaration) originalStatements.push(node);
2148
+ ast.body.unshift(...originalImports);
2149
+ const exportIndex = ast.body.findIndex((node) => node.type === "ExportDefaultDeclaration");
2150
+ ast.body.splice(exportIndex, 0, ...originalStatements);
2151
+ const attachments = collectComments(originalConfigObject.ast, originalConfigObject.comments);
2152
+ const propComments = /* @__PURE__ */ new Map();
2153
+ const newConfigProperties = [...originalConfigObject.config.properties, ...originalConfigObject.kit.properties];
2154
+ const keyedConfig = {};
2155
+ let trustsAllOrigins = false;
2156
+ for (const prop of newConfigProperties) {
2157
+ if (prop.type !== "Property") continue;
2158
+ if (prop.key.type !== "Identifier") continue;
2159
+ if (prop.key.name === "kit") continue;
2160
+ const propAttachment = attachments.get(prop);
2161
+ keyedConfig[prop.key.name] = prop.value;
2162
+ if (propAttachment) propComments.set(prop.key.name, propAttachment);
2163
+ }
2164
+ for (const [key, value] of Object.entries(keyedConfig)) {
2165
+ if (key === "experimental" && value.type === "ObjectExpression") {
2166
+ removeProperty(value, "handleRenderingErrors");
2167
+ removeProperty(value, "instrumentation");
2168
+ const index = value.properties.findIndex((prop) => prop.type === "Property" && prop.key.type === "Identifier" && prop.key.name === "tracing");
2169
+ if (index !== -1) {
2170
+ keyedConfig.tracing = value.properties[index].value;
2171
+ value.properties.splice(index, 1);
2172
+ }
2173
+ }
2174
+ if (key === "prerender" && value.type === "ObjectExpression") {
2175
+ const originProp = value.properties.find((prop) => prop.type === "Property" && prop.key.type === "Identifier" && prop.key.name === "origin");
2176
+ if (originProp) {
2177
+ keyedConfig.paths ??= {
2178
+ type: "ObjectExpression",
2179
+ properties: []
2180
+ };
2181
+ keyedConfig.paths.properties.push({
2182
+ type: "Property",
2183
+ key: {
2184
+ type: "Identifier",
2185
+ name: "origin"
2186
+ },
2187
+ value: originProp.value,
2188
+ kind: "init",
2189
+ method: false,
2190
+ shorthand: false,
2191
+ computed: false
2192
+ });
2193
+ removeProperty(value, "origin");
2194
+ if (value.properties.length === 0) delete keyedConfig.prerender;
2195
+ }
2196
+ }
2197
+ if (key === "csrf" && value.type === "ObjectExpression") {
2198
+ const checkOrigin = findDisabledCheckOrigin(value);
2199
+ if (checkOrigin) {
2200
+ checkOrigin.key.name = "trustedOrigins";
2201
+ checkOrigin.value = {
2202
+ type: "ArrayExpression",
2203
+ elements: [{
2204
+ type: "Literal",
2205
+ value: "*"
2206
+ }]
2207
+ };
2208
+ trustsAllOrigins = true;
2209
+ }
2210
+ }
2211
+ }
2212
+ delete keyedConfig.preloadStrategy;
2213
+ override(keyedConfig);
2214
+ if (trustsAllOrigins) Walker.walk(ast, null, { Property(node, { next }) {
2215
+ if (node.key.type === "Identifier" && node.key.name === "trustedOrigins") addMigrationTask("trusting all origins with '*' is generally not recommended, see https://svelte.dev/docs/kit/configuration#csrf", {
2216
+ comments,
2217
+ node
2218
+ });
2219
+ next();
2220
+ } });
2221
+ const apply = (node, record) => {
2222
+ for (const comment of record?.leading ?? []) comments.add(node, comment);
2223
+ for (const comment of record?.trailing ?? []) comments.add(node, comment, { position: "trailing" });
2224
+ };
2225
+ let rootConfig;
2226
+ forEachNode(ast, (node) => {
2227
+ apply(node, attachments.get(node));
2228
+ if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "sveltekit" && node.arguments[0]?.type === "ObjectExpression") rootConfig = node.arguments[0];
2229
+ });
2230
+ for (const prop of rootConfig?.properties ?? []) if (prop.type === "Property" && prop.key.type === "Identifier") apply(prop, propComments.get(prop.key.name));
46
2231
  });
47
- } catch (error) {
48
- forwardExitCode(error);
2232
+ let addedConfigLoader = false;
2233
+ sv.files({
2234
+ include: "**/*.{js,ts,mjs,mts,cjs,cts}",
2235
+ where: (content) => content.includes("svelte.config")
2236
+ }, (content, filePath) => {
2237
+ if (/(^|\/)eslint\.config\.[mc]?[jt]s$/.test(filePath)) {
2238
+ const { ast } = parse.script(content);
2239
+ const configImport = js.imports.findAll(ast, { from: SVELTE_CONFIG_IMPORT }).find((item) => item.kind === "static");
2240
+ if (configImport?.node.loc) {
2241
+ const start = offsetAt(content, configImport.node.loc.start);
2242
+ const end = offsetAt(content, configImport.node.loc.end);
2243
+ const replacement = "import { loadConfig } from '@sveltejs/load-config';\n\nconst svelteConfig = (await loadConfig('./', { traverse: false }))?.config;";
2244
+ addedConfigLoader = true;
2245
+ return content.slice(0, start) + replacement + content.slice(end);
2246
+ }
2247
+ }
2248
+ return transforms.script(({ ast, comments, js }) => {
2249
+ const found = js.imports.findAll(ast, { from: SVELTE_CONFIG_IMPORT });
2250
+ if (found.length === 0) return false;
2251
+ for (const imp of found) addMigrationTask("svelte.config was removed; switch to `import { loadConfig } from '@sveltejs/load-config'` to read your config", {
2252
+ comments,
2253
+ node: imp.node
2254
+ });
2255
+ })(content);
2256
+ });
2257
+ if (addedConfigLoader) sv.devDependency("@sveltejs/load-config", "^0.2.2");
2258
+ }
2259
+ });
2260
+ const posKey = (p) => p.line * 1e6 + p.column;
2261
+ function offsetAt(content, pos) {
2262
+ let offset = 0;
2263
+ for (let line = 1; line < pos.line; line++) offset = content.indexOf("\n", offset) + 1;
2264
+ return offset + pos.column;
2265
+ }
2266
+ /** Visits every AST node (objects with a `type`) reachable from `root`, including `root` itself. */
2267
+ function forEachNode(root, visit) {
2268
+ if (!root || typeof root !== "object") return;
2269
+ if (Array.isArray(root)) {
2270
+ for (const item of root) forEachNode(item, visit);
2271
+ return;
2272
+ }
2273
+ const node = root;
2274
+ if (typeof node.type === "string") visit(node);
2275
+ for (const key in node) {
2276
+ if (key === "loc" || key === "range" || key === "parent") continue;
2277
+ forEachNode(node[key], visit);
2278
+ }
2279
+ }
2280
+ /** Removes a statically named property from an object literal. */
2281
+ function removeProperty(value, name) {
2282
+ const index = value.properties.findIndex((prop) => prop.type === "Property" && prop.key.type === "Identifier" && prop.key.name === name);
2283
+ if (index !== -1) value.properties.splice(index, 1);
2284
+ }
2285
+ /**
2286
+ * Maps each source comment to the node it documents, returned keyed by node identity. A comment is
2287
+ * the trailing comment of the node it sits behind on the same line, otherwise the leading comment of
2288
+ * the next node that starts after it. Nodes are sorted start-ascending then end-descending so an
2289
+ * outer node (e.g. a `Property`) wins over a node it encloses that starts at the same spot (its key).
2290
+ */
2291
+ function collectComments(ast, source) {
2292
+ const nodes = [];
2293
+ forEachNode(ast, (node) => {
2294
+ if (node.loc) nodes.push({
2295
+ node,
2296
+ start: posKey(node.loc.start),
2297
+ end: posKey(node.loc.end)
2298
+ });
2299
+ });
2300
+ nodes.sort((a, b) => a.start - b.start || b.end - a.end);
2301
+ const topLevel = new Set(ast.body);
2302
+ const map = /* @__PURE__ */ new Map();
2303
+ const recordFor = (node) => {
2304
+ let record = map.get(node);
2305
+ if (!record) map.set(node, record = {
2306
+ leading: [],
2307
+ trailing: []
2308
+ });
2309
+ return record;
2310
+ };
2311
+ for (const comment of source.list()) {
2312
+ const start = posKey(comment.loc.start);
2313
+ const end = posKey(comment.loc.end);
2314
+ let prev;
2315
+ let next;
2316
+ for (const item of nodes) {
2317
+ if (item.end <= start && (!prev || item.end > prev.end)) prev = item;
2318
+ if (item.start >= end) {
2319
+ next = item;
2320
+ break;
2321
+ }
2322
+ }
2323
+ if (prev && comment.loc.start.line === prev.node.loc.end.line) {
2324
+ const safeInline = comment.type === "Block" || topLevel.has(prev.node);
2325
+ const value = safeInline ? comment.value : ` ${comment.value.trim()} `;
2326
+ recordFor(prev.node).trailing.push({
2327
+ type: safeInline ? comment.type : "Block",
2328
+ value
2329
+ });
2330
+ } else if (next) recordFor(next.node).leading.push({
2331
+ type: comment.type,
2332
+ value: comment.value
2333
+ });
2334
+ }
2335
+ return map;
2336
+ }
2337
+ /** Returns the `checkOrigin: false` property of a `csrf` object, if present. */
2338
+ function findDisabledCheckOrigin(value) {
2339
+ return value.properties.find((p) => p.type === "Property" && p.key.type === "Identifier" && p.key.name === "checkOrigin" && p.value.type === "Literal" && p.value.value === false);
2340
+ }
2341
+ //#endregion
2342
+ //#region src/migrate/migrations/sveltekit-3/tasks/tsconfig.ts
2343
+ const GENERATED_CONFIG = /(^|\/)\.svelte-kit\/tsconfig\.json$/;
2344
+ const LEADING_PARENT = /^\.\.\//;
2345
+ /** The config SvelteKit now generates into `node_modules`, replacing `.svelte-kit/tsconfig.json`. */
2346
+ const PARENT_CONFIG = "$app/tsconfig";
2347
+ /**
2348
+ * Options the generated config already sets. A local copy of the same value is noise, but a
2349
+ * different value is a deliberate override and stays.
2350
+ */
2351
+ const INHERITED_OPTIONS = {
2352
+ allowImportingTsExtensions: true,
2353
+ allowJs: true,
2354
+ checkJs: true,
2355
+ esModuleInterop: true,
2356
+ forceConsistentCasingInFileNames: true,
2357
+ isolatedModules: true,
2358
+ module: "esnext",
2359
+ moduleDetection: "force",
2360
+ moduleResolution: "bundler",
2361
+ noEmit: true,
2362
+ resolveJsonModule: true,
2363
+ skipLibCheck: true,
2364
+ target: "esnext",
2365
+ verbatimModuleSyntax: true
2366
+ };
2367
+ var tsconfig_default = defineMigrationTask({
2368
+ id: "tsconfig",
2369
+ description: "Extend the generated $app/tsconfig instead of .svelte-kit/tsconfig.json",
2370
+ run: ({ sv, cwd, file }) => {
2371
+ if (!file.typeConfig) return;
2372
+ const moved = dropTypescriptOption(sv, cwd);
2373
+ sv.file(path.relative(cwd, file.typeConfig), transforms.json(({ data }) => {
2374
+ const extended = Array.isArray(data.extends) ? [...data.extends] : [data.extends];
2375
+ const index = extended.findIndex((entry) => entry && GENERATED_CONFIG.test(entry));
2376
+ if (index === -1 && !extended.includes(PARENT_CONFIG)) return false;
2377
+ if (index !== -1) {
2378
+ extended[index] = PARENT_CONFIG;
2379
+ data.extends = Array.isArray(data.extends) ? extended : PARENT_CONFIG;
2380
+ }
2381
+ const include = data.include ??= ["src"];
2382
+ include.push(...moved.filter((entry) => !include.includes(entry)));
2383
+ if (data.compilerOptions?.types) {
2384
+ const types = data.compilerOptions.types;
2385
+ if (!types.includes("$app/types")) types.push("$app/types");
2386
+ }
2387
+ for (const [key, value] of Object.entries(data.compilerOptions ?? {})) if (INHERITED_OPTIONS[key] === value) delete data.compilerOptions[key];
2388
+ }));
2389
+ }
2390
+ });
2391
+ /**
2392
+ * Removes the deprecated `typescript.config` option, returning the paths its hook pushed onto
2393
+ * `include` so they can move to the project's own config. Hooks that do anything else are left in
2394
+ * place and flagged, since their intent can't be expressed as `include` entries.
2395
+ */
2396
+ function dropTypescriptOption(sv, cwd) {
2397
+ if (!svelteConfig.find(cwd)) return [];
2398
+ const moved = [];
2399
+ svelteConfig.edit({
2400
+ sv,
2401
+ cwd
2402
+ }, ({ ast, comments }) => {
2403
+ const found = findProperty(ast, "typescript");
2404
+ if (!found) return false;
2405
+ const pushed = includePushes(found.property.value);
2406
+ if (!pushed) {
2407
+ addMigrationTask("`typescript.config` is deprecated; configure TypeScript in tsconfig.json directly", {
2408
+ comments,
2409
+ node: found.property
2410
+ });
2411
+ return;
2412
+ }
2413
+ moved.push(...pushed.map((entry) => entry.replace(LEADING_PARENT, "")));
2414
+ found.remove();
2415
+ });
2416
+ return moved;
2417
+ }
2418
+ /** Finds a property by name anywhere in the config, wherever the config keeps it. */
2419
+ function findProperty(ast, name) {
2420
+ let found;
2421
+ Walker.walk(ast, null, { ObjectExpression(node, { next }) {
2422
+ const property = node.properties.find((p) => p.type === "Property" && p.key.type === "Identifier" && p.key.name === name);
2423
+ if (property) found ??= {
2424
+ property,
2425
+ remove: () => node.properties.splice(node.properties.indexOf(property), 1)
2426
+ };
2427
+ next();
2428
+ } });
2429
+ return found;
2430
+ }
2431
+ /** The literal paths a `typescript.config` hook pushes onto `include`, if that's all it does. */
2432
+ function includePushes(option) {
2433
+ if (option.type !== "ObjectExpression" || option.properties.length !== 1) return void 0;
2434
+ const [config] = option.properties;
2435
+ if (config.type !== "Property" || config.key.type !== "Identifier") return void 0;
2436
+ if (config.key.name !== "config") return void 0;
2437
+ const fn = config.value;
2438
+ if (fn.type !== "ArrowFunctionExpression" && fn.type !== "FunctionExpression") return void 0;
2439
+ if (fn.body.type !== "BlockStatement") return void 0;
2440
+ const paths = [];
2441
+ for (const statement of fn.body.body) {
2442
+ if (statement.type !== "ExpressionStatement") return void 0;
2443
+ const call = statement.expression;
2444
+ if (call.type !== "CallExpression" || !isIncludePush(call.callee)) return void 0;
2445
+ for (const argument of call.arguments) {
2446
+ if (argument.type !== "Literal" || typeof argument.value !== "string") return void 0;
2447
+ paths.push(argument.value);
2448
+ }
2449
+ }
2450
+ return paths;
2451
+ }
2452
+ const memberName = (node) => node.property.type === "Identifier" ? node.property.name : void 0;
2453
+ /** Matches the callee of `config.include.push(...)`. */
2454
+ function isIncludePush(callee) {
2455
+ return callee.type === "MemberExpression" && memberName(callee) === "push" && callee.object.type === "MemberExpression" && memberName(callee.object) === "include";
2456
+ }
2457
+ //#endregion
2458
+ //#region src/cli/migrate.ts
2459
+ const migrations = [
2460
+ defineMigration({
2461
+ id: "sveltekit-3",
2462
+ changelog: "https://next.svelte.dev/docs/kit/migrating-to-sveltekit-3",
2463
+ description: "A set of migrations for SvelteKit 3.0",
2464
+ setup: ({ pkg, requires }) => {
2465
+ const kitPackageName = "@sveltejs/kit";
2466
+ if (!pkg.devDependencies?.[kitPackageName]) throw new Error(`${color.command(kitPackageName)} is not a devDependency in package.json - this doesn't look like a SvelteKit project.\nPoint to one with ${color.command("--cwd <path>")}, or see ${color.command("sv migrate --help")}.`);
2467
+ const kitVersion = coerceVersion(pkg.devDependencies[kitPackageName]);
2468
+ if (kitVersion.major && kitVersion.major < 2) requires("sveltekit-2");
2469
+ },
2470
+ collect: ({ tasks }) => {
2471
+ tasks.add(package_json_default, { prerequisite: true });
2472
+ tasks.add(tsconfig_default, { prerequisite: true });
2473
+ tasks.add(svelte_config_default, { prerequisite: false });
2474
+ tasks.add(environment_default, { prerequisite: false });
2475
+ tasks.add(paths_default, { prerequisite: false });
2476
+ tasks.add(external_redirects_default, { prerequisite: false });
2477
+ tasks.add(shallow_routing_default, { prerequisite: false });
2478
+ tasks.add(params_default, { prerequisite: false });
2479
+ tasks.add(imports_default, { prerequisite: false });
2480
+ tasks.add(lib_alias_default, { prerequisite: false });
2481
+ tasks.add(app_state_default$1, { prerequisite: false });
2482
+ tasks.add(collect_migration_instructions_default, { prerequisite: false });
2483
+ }
2484
+ }),
2485
+ app_state_default,
2486
+ ...legacyMigrations
2487
+ ];
2488
+ const MigrationScheme = optional(picklist(migrations.map((m) => m.id)));
2489
+ function normalizeTasksOption(tasks) {
2490
+ return tasks === true ? [] : tasks;
2491
+ }
2492
+ const OptionsSchema = strictObject({
2493
+ cwd: optional(string(), "./"),
2494
+ files: optional(string()),
2495
+ gitCheck: boolean(),
2496
+ tasks: pipe(optional(union([array(string()), literal(true)])), transform(normalizeTasksOption)),
2497
+ confirm: optional(boolean(), false),
2498
+ install: optional(union([boolean(), picklist(AGENT_NAMES)]), true)
2499
+ });
2500
+ const migrate = new Command("migrate").description("a CLI for migrating Svelte(Kit) codebases").argument("[migration]", `migration to run`).option("--cwd <path>", "working directory to run the migration in").option("--files <glob>", "only run the migration on a subset of files matching the provided glob pattern").option("--no-git-check", "even if some files are dirty, no prompt will be shown").option("--tasks [task...]", "migration tasks to run. Omit list of tasks to show available tasks. Use `--task all` to run all migration tasks.").option("--confirm", "skip the final confirmation prompt").option("--no-install", "skip installing dependencies").addOption(installOption).action((migrationName, options) => {
2501
+ if (hasInstallConflict(V.argv)) {
2502
+ errorAndExit(`The ${color.command("--install")} and ${color.command("--no-install")} options cannot be used together.`);
2503
+ return;
2504
+ }
2505
+ let verifiedMigrationName = parse$1(MigrationScheme, migrationName);
2506
+ const verifiedOptions = parse$1(OptionsSchema, options);
2507
+ runCommand(async () => {
2508
+ const pkg = ensureValidWorkspace(verifiedOptions.cwd);
2509
+ if (!pkg) return;
2510
+ if (verifiedOptions.tasks?.length !== 0) await runAndValidateVerifications([...verifyCleanWorkingDirectory(options.cwd, options.gitCheck)]);
2511
+ if (!verifiedMigrationName) verifiedMigrationName = await Ee({
2512
+ message: "Select a migration to run",
2513
+ options: migrations.map((m) => ({
2514
+ value: m.id,
2515
+ label: m.id,
2516
+ hint: m.description
2517
+ }))
2518
+ });
2519
+ if (R(verifiedMigrationName)) {
2520
+ ge("Operation cancelled.");
2521
+ V.exit(1);
2522
+ }
2523
+ const migration = migrations.find((m) => m.id === verifiedMigrationName);
2524
+ if (!migration) {
2525
+ errorAndExit(`Migration ${verifiedMigrationName} not found`);
2526
+ return;
2527
+ }
2528
+ const legacyMigration = migration.legacy ?? false;
2529
+ if (legacyMigration && verifiedOptions.tasks !== void 0) {
2530
+ errorAndExit(`The migration ${migration.id} does not support task selection.`);
2531
+ return;
2532
+ }
2533
+ if (verifiedOptions.tasks?.length === 0) {
2534
+ const allTasks = await collectMigrationTasks(migration, verifiedOptions.cwd);
2535
+ if (allTasks.length === 0) {
2536
+ errorAndExit(`Migration "${migration.id}" did not return any tasks.`);
2537
+ return;
2538
+ }
2539
+ Ce(formatAvailableTasks(allTasks), `Available tasks for ${migration.id}`, { format: (line) => line });
2540
+ return;
2541
+ }
2542
+ const tasks = await determineTasks(migration, verifiedOptions, pkg);
2543
+ if (!tasks) return;
2544
+ resetMigrationTaskCount();
2545
+ const modifiedFiles = await applyTasks(verifiedOptions, tasks, legacyMigration);
2546
+ if (legacyMigration) return;
2547
+ const workspace = await createWorkspace({ cwd: verifiedOptions.cwd });
2548
+ await formatFiles({
2549
+ cwd: workspace.cwd,
2550
+ packageManager: workspace.packageManager,
2551
+ filesToFormat: modifiedFiles.values().toArray(),
2552
+ strategy: "project-script-then-files-only"
2553
+ });
2554
+ const packageManager = verifiedOptions.install === false ? null : verifiedOptions.install === true ? await packageManagerPrompt(workspace.cwd) : verifiedOptions.install;
2555
+ if (packageManager) await installDependencies(packageManager, workspace.cwd);
2556
+ reportNextSteps();
2557
+ });
2558
+ });
2559
+ function ensureValidWorkspace(cwd) {
2560
+ try {
2561
+ return loadPackageJson(cwd).data;
2562
+ } catch {
2563
+ errorAndExit(`No package.json found in ${path.resolve(cwd)}.\nPoint to a project with ${color.command("--cwd <path>")}, or see ${color.command("sv migrate --help")}.`);
2564
+ }
2565
+ }
2566
+ async function determineTasks(migration, options, pkg) {
2567
+ const requiredMigrations = [];
2568
+ const setupOptions = {
2569
+ pkg,
2570
+ cwd: options.cwd,
2571
+ requires: (migrationId) => {
2572
+ requiredMigrations.push(migrationId);
2573
+ }
2574
+ };
2575
+ try {
2576
+ await migration.setup(setupOptions);
2577
+ } catch (err) {
2578
+ errorAndExit(err instanceof Error ? err.message : String(err));
2579
+ return;
2580
+ }
2581
+ if (requiredMigrations.length > 0) {
2582
+ errorAndExit(`The migration ${migration.id} requires the following migrations to be run first: ${requiredMigrations.join(", ")}`);
2583
+ return;
2584
+ }
2585
+ const allTasks = await collectMigrationTasks(migration, options.cwd);
2586
+ if (allTasks.length === 0) {
2587
+ errorAndExit(`Migration "${migration.id}" did not return any tasks to run.`);
2588
+ return;
2589
+ }
2590
+ const prerequisiteTasks = allTasks.filter((t) => t.prerequisite);
2591
+ const selectableTasks = allTasks.filter((t) => !t.prerequisite);
2592
+ if (!options.tasks?.length && selectableTasks.length > 0) {
2593
+ const workflow = [];
2594
+ if (migration.changelog) workflow.push("Make sure to read the changelog for this migration before running it:", color.website(migration.changelog), "");
2595
+ workflow.push(`To make migration changes easier to review, run ${color.command("one selectable task at a time")} and ${color.command("commit after each run")}.`, "Tasks are intended to create focused commits, not independent migration paths.", `Most projects need ${color.warning("all applicable tasks")} before they work correctly.`);
2596
+ Ce(workflow.join("\n"), "Recommended workflow", { format: (line) => line });
2597
+ }
2598
+ const tasksToRun = [...prerequisiteTasks];
2599
+ if (options.tasks) tasksToRun.push(...selectTasksFromArgs(options.tasks, selectableTasks));
2600
+ else if (selectableTasks.length > 0) {
2601
+ const prerequisiteIds = prerequisiteTasks.map((t) => t.id).join(", ");
2602
+ const selectedTaskIds = await we({
2603
+ message: prerequisiteTasks.length ? `Select tasks to run; prerequisites always included: ${color.command(prerequisiteIds)}` : "Select the tasks to run",
2604
+ options: selectableTasks.map((t) => ({
2605
+ value: t.id,
2606
+ label: t.id,
2607
+ hint: t.description
2608
+ })),
2609
+ required: false
2610
+ });
2611
+ if (R(selectedTaskIds)) {
2612
+ ge("Operation cancelled.");
2613
+ V.exit(1);
2614
+ }
2615
+ const selectedTasks = selectableTasks.filter((t) => selectedTaskIds.includes(t.id));
2616
+ tasksToRun.push(...selectedTasks);
2617
+ }
2618
+ if (tasksToRun.length === 0) {
2619
+ errorAndExit("No tasks selected to run.");
2620
+ return;
2621
+ }
2622
+ Ce(`- ${tasksToRun.map(({ id, description }) => {
2623
+ return `${id} ${color.optional(`(${description})`)}`;
2624
+ }).join("\n- ")}`, "Migration steps", { format: (line) => line });
2625
+ if (!options.confirm) {
2626
+ const proceed = await le({
2627
+ message: "Do you want to proceed?",
2628
+ initialValue: false
2629
+ });
2630
+ if (R(proceed)) {
2631
+ ge("Operation cancelled.");
2632
+ V.exit(1);
2633
+ }
2634
+ if (!proceed) {
2635
+ errorAndExit("Migration cancelled by the user.");
2636
+ return;
2637
+ }
2638
+ }
2639
+ return tasksToRun;
2640
+ }
2641
+ async function collectMigrationTasks(migration, cwd) {
2642
+ const allTasks = [];
2643
+ const collectOptions = {
2644
+ cwd,
2645
+ tasks: { add: (task, options) => {
2646
+ allTasks.push({
2647
+ ...task,
2648
+ ...options
2649
+ });
2650
+ } }
2651
+ };
2652
+ await migration.collect(collectOptions);
2653
+ return allTasks;
2654
+ }
2655
+ function formatAvailableTasks(tasks) {
2656
+ return tasks.map(({ id, description, prerequisite }) => `- ${id}${prerequisite ? color.warning(" (prerequisite)") : ""}: ${color.optional(description)}`).join("\n");
2657
+ }
2658
+ function selectTasksFromArgs(selectedTaskIds, selectableTasks) {
2659
+ if (selectedTaskIds.length > 1 && (selectedTaskIds.includes("all") || selectedTaskIds.includes("prerequisite"))) errorAndExit(`The ${color.command("--tasks")} values ${color.command("all")} and ${color.command("prerequisite")} cannot be combined with other tasks.`);
2660
+ if (selectedTaskIds[0] === "prerequisite") return [];
2661
+ if (selectedTaskIds[0] === "all") return selectableTasks;
2662
+ const invalidTasks = selectedTaskIds.filter((id) => !selectableTasks.some((t) => t.id === id));
2663
+ if (invalidTasks.length > 0) errorAndExit(`Unknown migration task${invalidTasks.length === 1 ? "" : "s"}: ${invalidTasks.map((id) => color.command(id)).join(", ")}\nAvailable tasks: ${selectableTasks.map((t) => color.command(t.id)).join(", ")}`);
2664
+ return selectableTasks.filter((task) => selectedTaskIds.includes(task.id));
2665
+ }
2666
+ /** Prints a summary of the `@migration-task` comments left for the user to resolve, if any. */
2667
+ function reportNextSteps() {
2668
+ const total = getMigrationTaskCount();
2669
+ if (total === 0) return;
2670
+ Ce([`${total} ${total === 1 ? "comment" : "comments"} to review.`, `Search for ${color.command(MIGRATION_TASK_MARKER)} to resolve ${total === 1 ? "it" : "them"}.`].join("\n"), "Next steps", { format: (line) => line });
2671
+ }
2672
+ function hasInstallConflict(argv) {
2673
+ return argv.some((arg) => arg === "--install" || arg.startsWith("--install=")) && argv.includes("--no-install");
2674
+ }
2675
+ async function applyTasks(options, tasks, legacyMigration) {
2676
+ let allModifiedFiles = /* @__PURE__ */ new Set();
2677
+ let allUnmodifiedFiles = /* @__PURE__ */ new Set();
2678
+ const { start, stop, message, error } = vt();
2679
+ if (!legacyMigration) start("Applying migration tasks...");
2680
+ for (let i = 0; i < tasks.length; i++) {
2681
+ const task = tasks[i];
2682
+ if (!legacyMigration) message(`${i + 1}/${tasks.length}: ${task.id}`);
2683
+ try {
2684
+ const workspace = await createWorkspace({ cwd: options.cwd });
2685
+ const { sv, finalize } = prepareSvApi(workspace, {
2686
+ executeOutputPrefix: `${task.id}:`,
2687
+ filesFilter: options.files
2688
+ });
2689
+ await task.run({
2690
+ sv,
2691
+ ...workspace
2692
+ });
2693
+ if (!legacyMigration) {
2694
+ const { modifiedFiles, unmodifiedFiles } = finalize();
2695
+ allModifiedFiles = allModifiedFiles.union(modifiedFiles);
2696
+ allUnmodifiedFiles = allUnmodifiedFiles.union(unmodifiedFiles);
2697
+ }
2698
+ } catch (err) {
2699
+ const errorMessage = err instanceof Error ? err.message : String(err);
2700
+ const message = `Task '${task.id}' failed: ${errorMessage}`;
2701
+ if (!legacyMigration) error(message);
2702
+ R$1.message();
2703
+ ge("Migration failed.");
2704
+ V.exit(1);
2705
+ }
49
2706
  }
2707
+ if (!legacyMigration) stop("All tasks applied successfully!");
2708
+ if (allUnmodifiedFiles.size > 0) Ce(`The following files were modified by the migration,\nbut their content was not saved:\n- ${Array.from(allUnmodifiedFiles).join("\n- ")}`, "Unmodified files", { format: (line) => line });
2709
+ return allModifiedFiles;
50
2710
  }
51
2711
  //#endregion
52
2712
  //#region bin.ts