sv 1.0.0-next.6 → 1.0.0-next.8

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.
@@ -0,0 +1,2375 @@
1
+ import { T as getSharedFiles, _ as defineAddon, dt as R, v as defineAddonOptions } from "./engine-Bw7mIv-H.mjs";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { Walker, coerceVersion, color, createPrinter, dedent, defineEnv, fileExists, isKit3, loadPackageJson, pnpm, resolveCommandArray, resolveLibPrefix, sanitizeName, svelteConfig, transforms } from "@sveltejs/sv-utils";
5
+ import V from "node:process";
6
+ import crypto from "node:crypto";
7
+ //#region src/addons/ai-tools.ts
8
+ const REGEX_MD = /\.md$/;
9
+ const CLIENTS = {
10
+ "claude-code": {
11
+ label: "Claude Code",
12
+ pluginSettings: {
13
+ path: ".claude/settings.json",
14
+ marketplace: "svelte",
15
+ repo: "sveltejs/ai-tools",
16
+ id: "svelte@svelte"
17
+ },
18
+ agentPath: "AGENTS.md",
19
+ agentLink: {
20
+ path: ".claude/CLAUDE.md",
21
+ contents: "@../AGENTS.md\n"
22
+ },
23
+ configPath: ".mcp.json",
24
+ skillsPath: ".claude/skills",
25
+ agentsPath: ".claude/agents",
26
+ mcpOptions: {
27
+ typeLocal: "stdio",
28
+ typeRemote: "http",
29
+ env: true
30
+ }
31
+ },
32
+ cursor: {
33
+ label: "Cursor",
34
+ agentPath: "AGENTS.md",
35
+ configPath: ".cursor/mcp.json",
36
+ skillsPath: ".cursor/skills",
37
+ agentsPath: ".cursor/agents",
38
+ mcpOptions: {}
39
+ },
40
+ gemini: {
41
+ label: "Gemini",
42
+ agentPath: "GEMINI.md",
43
+ configPath: ".gemini/settings.json",
44
+ skillsPath: ".gemini/skills",
45
+ agentsPath: ".gemini/agents",
46
+ schema: "https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json",
47
+ mcpOptions: {}
48
+ },
49
+ opencode: {
50
+ label: "OpenCode",
51
+ pluginOnly: true,
52
+ agentPath: "AGENTS.md",
53
+ configPath: ".opencode/opencode.json",
54
+ schema: "https://opencode.ai/config.json",
55
+ customData: { plugin: ["@sveltejs/opencode"] },
56
+ extraFiles: [{
57
+ path: ".opencode/svelte.json",
58
+ data: { $schema: "https://svelte.dev/opencode/schema.json" }
59
+ }]
60
+ },
61
+ vscode: {
62
+ label: "VS Code",
63
+ agentPath: "AGENTS.md",
64
+ configPath: ".vscode/mcp.json",
65
+ skillsPath: ".github/skills",
66
+ agentsPath: ".github/agents",
67
+ agentExtension: ".agent.md",
68
+ mcpOptions: { serversKey: "servers" }
69
+ },
70
+ other: {
71
+ label: "Other",
72
+ agentPath: "AGENTS.md"
73
+ }
74
+ };
75
+ const acceptsTools = (client) => Boolean(client && !client.pluginOnly && (client.skillsPath || client.agentsPath || client.configPath));
76
+ const wantsLooseTools = (client, delivery) => acceptsTools(client) && (!client.pluginSettings || delivery !== "plugin");
77
+ const wantsMcpConfig = (client, delivery) => Boolean(client?.mcpOptions) && wantsLooseTools(client, delivery);
78
+ const TOOLS = {
79
+ mcp: {
80
+ label: "MCP server",
81
+ kind: "mcp"
82
+ },
83
+ "svelte-code-writer": {
84
+ label: "svelte-code-writer",
85
+ kind: "skill",
86
+ hint: "skill"
87
+ },
88
+ "svelte-core-bestpractices": {
89
+ label: "svelte-core-bestpractices",
90
+ kind: "skill",
91
+ hint: "skill"
92
+ },
93
+ "svelte-file-editor": {
94
+ label: "svelte-file-editor",
95
+ kind: "agent",
96
+ hint: "sub-agent"
97
+ }
98
+ };
99
+ const options$7 = defineAddonOptions().add("ide", {
100
+ question: "Which client would you like to use?",
101
+ type: "multiselect",
102
+ default: [],
103
+ options: Object.entries(CLIENTS).map(([value, client]) => ({
104
+ value,
105
+ label: client.label
106
+ })),
107
+ required: true
108
+ }).add("delivery", {
109
+ question: "How would you like to add the Svelte tools?",
110
+ type: "select",
111
+ default: "plugin",
112
+ options: [{
113
+ value: "plugin",
114
+ label: "Svelte plugin",
115
+ hint: "recommended, auto-installs & updates"
116
+ }, {
117
+ value: "tools",
118
+ label: "Individual tools",
119
+ hint: "choose exactly what to add"
120
+ }],
121
+ condition: ({ ide }) => ide.some((i) => CLIENTS[i]?.pluginSettings)
122
+ }).add("tools", {
123
+ question: "Which tools would you like to add?",
124
+ type: "multiselect",
125
+ default: Object.keys(TOOLS),
126
+ options: Object.entries(TOOLS).map(([value, t]) => ({
127
+ value,
128
+ label: t.label,
129
+ hint: t.hint
130
+ })),
131
+ required: false,
132
+ condition: ({ ide, delivery }) => ide.some((i) => wantsLooseTools(CLIENTS[i], delivery))
133
+ }).add("mcpSetup", {
134
+ question: "Which MCP setup would you like to use?",
135
+ type: "select",
136
+ default: "remote",
137
+ options: [{
138
+ value: "local",
139
+ label: "Local",
140
+ hint: "will use stdio"
141
+ }, {
142
+ value: "remote",
143
+ label: "Remote",
144
+ hint: "will use a remote endpoint"
145
+ }],
146
+ condition: ({ ide, delivery, tools }) => (tools === void 0 || tools.includes("mcp")) && ide.some((i) => wantsMcpConfig(CLIENTS[i], delivery))
147
+ }).build();
148
+ var ai_tools_default = defineAddon({
149
+ id: "ai-tools",
150
+ shortDescription: "Svelte AI tools",
151
+ homepage: "https://svelte.dev/docs/ai",
152
+ options: options$7,
153
+ run: ({ sv, options }) => {
154
+ const usePlugin = options.delivery === "plugin";
155
+ const selected = options.tools ?? Object.keys(TOOLS);
156
+ const selectedSkills = selected.filter((t) => TOOLS[t]?.kind === "skill");
157
+ const selectedAgents = selected.filter((t) => TOOLS[t]?.kind === "agent");
158
+ const wantsMcp = selected.includes("mcp");
159
+ const getLocalConfig = (o) => {
160
+ return {
161
+ ...o?.typeLocal ? { type: o.typeLocal } : {},
162
+ command: o?.command ?? "npx",
163
+ ...o?.env ? { env: {} } : {},
164
+ ...o?.args === null ? {} : { args: o?.args ?? ["-y", "@sveltejs/mcp"] }
165
+ };
166
+ };
167
+ const getRemoteConfig = (o) => {
168
+ return {
169
+ ...o?.typeRemote ? { type: o.typeRemote } : {},
170
+ url: "https://mcp.svelte.dev/mcp"
171
+ };
172
+ };
173
+ const filesAdded = [];
174
+ const filesExistingAlready = [];
175
+ const sharedFiles = getSharedFiles();
176
+ const mcpFiles = sharedFiles.filter((file) => file.include.includes("mcp"));
177
+ const skillFiles = sharedFiles.filter((file) => file.include.includes("skills"));
178
+ const agentFiles = sharedFiles.filter((file) => file.include.includes("agents"));
179
+ const agentFile = mcpFiles.find((file) => file.name === "AGENTS.md");
180
+ const addFile = (path, contents) => {
181
+ sv.file(path, (content) => {
182
+ if (content) {
183
+ filesExistingAlready.push(path);
184
+ return false;
185
+ }
186
+ if (!filesAdded.includes(path)) filesAdded.push(path);
187
+ return contents;
188
+ });
189
+ };
190
+ for (const ide of options.ide) {
191
+ const client = CLIENTS[ide];
192
+ if (!client) continue;
193
+ if (client.pluginSettings && usePlugin) {
194
+ const plugin = client.pluginSettings;
195
+ sv.file(plugin.path, transforms.json(({ data }) => {
196
+ data.extraKnownMarketplaces ??= {};
197
+ data.extraKnownMarketplaces[plugin.marketplace] ??= { source: {
198
+ source: "github",
199
+ repo: plugin.repo
200
+ } };
201
+ data.enabledPlugins ??= {};
202
+ data.enabledPlugins[plugin.id] = true;
203
+ }));
204
+ continue;
205
+ }
206
+ if (!client.agentPath) continue;
207
+ const { mcpOptions, agentPath, agentLink, configPath, skillsPath, agentsPath, agentExtension, schema, customData, extraFiles } = client;
208
+ if (!filesAdded.includes(agentPath)) sv.file(agentPath, (content) => {
209
+ if (content) {
210
+ if (!filesExistingAlready.includes(agentPath)) filesExistingAlready.push(agentPath);
211
+ return false;
212
+ }
213
+ filesAdded.push(agentPath);
214
+ return agentFile?.contents ?? "";
215
+ });
216
+ if (agentLink) addFile(agentLink.path, agentLink.contents);
217
+ const writeMcp = Boolean(mcpOptions) && wantsMcp;
218
+ if (configPath && (writeMcp || customData)) sv.file(configPath, transforms.json(({ data }) => {
219
+ if (schema) data["$schema"] = schema;
220
+ if (customData) for (const [key, value] of Object.entries(customData)) data[key] = value;
221
+ if (writeMcp) {
222
+ const key = mcpOptions.serversKey ?? "mcpServers";
223
+ data[key] ??= {};
224
+ data[key].svelte = options.mcpSetup === "local" ? getLocalConfig(mcpOptions) : getRemoteConfig(mcpOptions);
225
+ }
226
+ }));
227
+ if (extraFiles) for (const extra of extraFiles) sv.file(extra.path, transforms.json(({ data }) => {
228
+ for (const [key, value] of Object.entries(extra.data)) data[key] = value;
229
+ }));
230
+ if (skillsPath) for (const file of skillFiles) {
231
+ if (!selectedSkills.includes(file.name.split("/")[0])) continue;
232
+ addFile(`${skillsPath}/${file.name}`, file.contents);
233
+ }
234
+ if (agentsPath) for (const file of agentFiles) {
235
+ if (!selectedAgents.includes(file.name.replace(REGEX_MD, ""))) continue;
236
+ const ext = agentExtension ?? ".md";
237
+ addFile(`${agentsPath}/${file.name.replace(REGEX_MD, ext)}`, file.contents);
238
+ }
239
+ }
240
+ if (filesExistingAlready.length > 0) R.warn(`${filesExistingAlready.map((path) => color.path(path)).join(", ")} already exists, we didn't touch ${filesExistingAlready.length > 1 ? "them" : "it"}. See ${color.website("https://svelte.dev/docs/ai")} for manual setup.`);
241
+ },
242
+ nextSteps({ options, cwd }) {
243
+ const steps = [];
244
+ for (const ide of options.ide) {
245
+ const client = CLIENTS[ide];
246
+ if (!client?.pluginSettings) continue;
247
+ const plugin = client.pluginSettings;
248
+ const settingsPath = path.resolve(cwd, plugin.path);
249
+ if (!fs.existsSync(settingsPath)) continue;
250
+ let pluginEnabled = false;
251
+ try {
252
+ pluginEnabled = Boolean(JSON.parse(fs.readFileSync(settingsPath, "utf8")).enabledPlugins?.[plugin.id]);
253
+ } catch {}
254
+ if (!pluginEnabled) continue;
255
+ const looseDirs = [client.skillsPath, client.agentsPath].filter((dir) => Boolean(dir) && fs.existsSync(path.resolve(cwd, dir)) && fs.readdirSync(path.resolve(cwd, dir)).some((entry) => Object.keys(TOOLS).includes(entry.replace(REGEX_MD, ""))));
256
+ if (looseDirs.length === 0) continue;
257
+ steps.push(`${client.label}: the Svelte plugin is enabled and ${looseDirs.map((d) => color.path(d)).join(" + ")} also exist${looseDirs.length > 1 ? "" : "s"} - remove ${looseDirs.length > 1 ? "them" : "it"} (or disable the plugin) to avoid duplicate skills/sub-agents.`);
258
+ }
259
+ if (options.delivery === "plugin" && options.ide.includes("claude-code")) steps.push(`Open the project in Claude Code and trust the workspace - the Svelte plugin installs automatically.`);
260
+ if (options.ide.includes("other")) steps.push(`For other clients: ${color.website(`https://svelte.dev/docs/ai/${options.mcpSetup ?? "remote"}-setup#Other-clients`)}`);
261
+ return steps;
262
+ }
263
+ });
264
+ //#endregion
265
+ //#region src/addons/common.ts
266
+ const ESLINT_VERSION = "^10.4.1";
267
+ const addEslintConfigPrettier = transforms.script(({ ast, js }) => {
268
+ const sveltePluginImport = ast.body.filter((n) => n.type === "ImportDeclaration").find((n) => n.type === "ImportDeclaration" && n.source.value === "eslint-plugin-svelte" && n.specifiers?.some((n) => n.type === "ImportDefaultSpecifier"));
269
+ let svelteImportName;
270
+ for (const specifier of sveltePluginImport?.specifiers ?? []) if (specifier.type === "ImportDefaultSpecifier" && specifier.local?.name) svelteImportName = specifier.local.name;
271
+ svelteImportName ??= "svelte";
272
+ js.imports.addDefault(ast, {
273
+ from: "eslint-plugin-svelte",
274
+ as: svelteImportName
275
+ });
276
+ js.imports.addDefault(ast, {
277
+ from: "eslint-config-prettier",
278
+ as: "prettier"
279
+ });
280
+ const fallbackConfig = js.common.parseExpression("[]");
281
+ const eslintConfig = js.exports.createDefault(ast, { fallback: fallbackConfig }).value;
282
+ let elements = [];
283
+ if (eslintConfig.type === "ArrayExpression") elements = eslintConfig.elements;
284
+ else if (eslintConfig.type === "CallExpression") {
285
+ if (eslintConfig.arguments.length === 1 && eslintConfig.arguments[0].type === "ArrayExpression") elements = eslintConfig.arguments[0].elements;
286
+ else elements = eslintConfig.arguments;
287
+ } else return false;
288
+ const prettier = js.common.parseExpression("prettier");
289
+ const sveltePrettierConfig = js.common.parseExpression(`${svelteImportName}.configs.prettier`);
290
+ const nodesToInsert = [];
291
+ if (!js.common.contains(eslintConfig, prettier)) nodesToInsert.push(prettier);
292
+ if (!js.common.contains(eslintConfig, sveltePrettierConfig)) nodesToInsert.push(sveltePrettierConfig);
293
+ const isSvelteConfig = (maybeSpread) => {
294
+ const el = maybeSpread?.type === "SpreadElement" ? maybeSpread?.argument : maybeSpread;
295
+ return el?.type === "MemberExpression" && el.object.type === "MemberExpression" && el.object.object.type === "Identifier" && el.object.object.name === svelteImportName && el.object.property.type === "Identifier" && el.object.property.name === "configs";
296
+ };
297
+ const idx = elements.findIndex(isSvelteConfig);
298
+ if (idx !== -1) elements.splice(idx + 1, 0, ...nodesToInsert);
299
+ else elements.push(...nodesToInsert);
300
+ });
301
+ /**
302
+ * Path of the prettier config file we generate. We always emit a `.js` file (with a JSDoc `@type`)
303
+ * rather than `.ts`, since the VSCode prettier extension can't load a `.ts` config without a recent
304
+ * enough Node in its extension host (see prettier/prettier-vscode#3989).
305
+ */
306
+ function prettierConfigPath() {
307
+ return "prettier.config.js";
308
+ }
309
+ /**
310
+ * Creates a `prettier.config.js` file with our defaults. Bails (leaving the file untouched)
311
+ * if a config is already defined.
312
+ */
313
+ const createPrettierConfig = (opts) => transforms.script(({ ast, comments, js }) => {
314
+ if (ast.body.some((node) => node.type === "ExportDefaultDeclaration")) {
315
+ R.warn(`A ${color.warning("prettier")} config already exists. Skipping initialization.`);
316
+ return false;
317
+ }
318
+ const plugins = ["prettier-plugin-svelte"];
319
+ if (opts.tailwind) plugins.push("prettier-plugin-tailwindcss");
320
+ const properties = [
321
+ "useTabs: true",
322
+ "singleQuote: true",
323
+ `trailingComma: 'none'`,
324
+ "printWidth: 100",
325
+ `plugins: [${plugins.map((p) => `'${p}'`).join(", ")}]`,
326
+ `overrides: [{ files: '*.svelte', options: { parser: 'svelte' } }]`
327
+ ];
328
+ if (opts.tailwind && opts.stylesheet) properties.push(`tailwindStylesheet: '${opts.stylesheet}'`);
329
+ js.common.appendFromString(ast, {
330
+ comments,
331
+ code: `
332
+ /** @type {import("prettier").Config} */
333
+ const config = { ${properties.join(", ")} };
334
+ export default config;
335
+ `
336
+ });
337
+ });
338
+ /**
339
+ * Augments an existing `prettier.config.js` config object with the tailwindcss plugin and
340
+ * stylesheet. Used when prettier was added before tailwindcss.
341
+ */
342
+ const addPrettierTailwind = (opts) => transforms.script(({ ast, js }) => {
343
+ if (!ast.body.some((node) => node.type === "ExportDefaultDeclaration")) return false;
344
+ const { value } = js.exports.createDefault(ast, { fallback: js.object.create({}) });
345
+ if (value.type !== "ObjectExpression") return false;
346
+ const plugins = js.object.property(value, {
347
+ name: "plugins",
348
+ fallback: js.array.create()
349
+ });
350
+ if (plugins.type === "ArrayExpression") js.array.append(plugins, "prettier-plugin-tailwindcss");
351
+ if (!value.properties.some((p) => p.type === "Property" && p.key.type === "Identifier" && p.key.name === "tailwindStylesheet")) js.object.property(value, {
352
+ name: "tailwindStylesheet",
353
+ fallback: js.common.createLiteral(opts.stylesheet)
354
+ });
355
+ });
356
+ const addToDemoPage = (path, language) => transforms.svelteScript({ language }, ({ ast, js, svelte }) => {
357
+ for (const node of ast.fragment.nodes) if (node.type === "RegularElement") {
358
+ const hrefAttribute = node.attributes.find((x) => x.type === "Attribute" && x.name === "href");
359
+ if (!hrefAttribute || !hrefAttribute.value) continue;
360
+ if (!Array.isArray(hrefAttribute.value)) continue;
361
+ if (hrefAttribute.value.some((x) => x.type === "Text" && x.data.includes(`/demo/${path}`))) return false;
362
+ }
363
+ js.imports.addNamed(ast.instance.content, {
364
+ imports: ["resolve"],
365
+ from: "$app/paths"
366
+ });
367
+ svelte.addFragment(ast, `<a href={resolve('/demo/${path}')}>${path}</a>`, { mode: "prepend" });
368
+ });
369
+ /**
370
+ * Returns the corresponding `@types/node` version for the version of Node.js running in the current process.
371
+ *
372
+ * If the installed version of Node.js is from a `Current` release, then the major is decremented to
373
+ * the nearest `LTS` release version.
374
+ */
375
+ function getNodeTypesVersion() {
376
+ const nodeVersion = V.versions.node;
377
+ const isDenoOrBun = Boolean(V.versions.deno ?? V.versions.bun);
378
+ const [major] = nodeVersion.split(".");
379
+ const majorNum = Number(major);
380
+ const isEvenMajor = majorNum % 2 === 0;
381
+ if (!!V.release.lts || isDenoOrBun && isEvenMajor) return `^${major}`;
382
+ return `^${isEvenMajor ? majorNum - 2 : majorNum - 1}`;
383
+ }
384
+ //#endregion
385
+ //#region \0@oxc-project+runtime@0.150.0/helpers/esm/taggedTemplateLiteral.js
386
+ function _taggedTemplateLiteral(e, t) {
387
+ return t || (t = e.slice(0)), Object.freeze(Object.defineProperties(e, { raw: { value: Object.freeze(t) } }));
388
+ }
389
+ //#endregion
390
+ //#region src/addons/better-auth.ts
391
+ var _templateObject$1;
392
+ var _templateObject2;
393
+ const options$6 = defineAddonOptions().add("demo", {
394
+ question: "Which demo would you like to include?",
395
+ type: "multiselect",
396
+ default: ["password"],
397
+ options: [{
398
+ value: "password",
399
+ label: "Email & Password"
400
+ }, {
401
+ value: "github",
402
+ label: "GitHub OAuth"
403
+ }],
404
+ required: false
405
+ }).build();
406
+ var better_auth_default = defineAddon({
407
+ id: "better-auth",
408
+ shortDescription: "auth library",
409
+ homepage: "https://www.better-auth.com",
410
+ options: options$6,
411
+ setup: ({ isKit, dependencyVersion, unsupported, dependsOn, runsAfter }) => {
412
+ if (!isKit) unsupported("Requires SvelteKit");
413
+ if (!dependencyVersion("drizzle-orm")) dependsOn("drizzle");
414
+ runsAfter("sveltekitAdapter");
415
+ runsAfter("tailwindcss");
416
+ runsAfter("experimental");
417
+ },
418
+ run: ({ sv, cwd, language, options, directory, dependencyVersion, file }) => {
419
+ const lib = resolveLibPrefix(dependencyVersion("@sveltejs/kit"));
420
+ const svelteVersion = dependencyVersion("svelte");
421
+ const svelte5 = !!svelteVersion && coerceVersion(svelteVersion).major === 5;
422
+ const [ts, s5] = createPrinter(language === "ts", svelte5);
423
+ const demoPassword = options.demo.includes("password");
424
+ const demoGithub = options.demo.includes("github");
425
+ const hasDemo = demoPassword || demoGithub;
426
+ let drizzleDialect;
427
+ let d1 = false;
428
+ sv.devDependency("auth", "^1.6.24");
429
+ sv.devDependency("better-auth", "^1.6.24");
430
+ sv.file(`drizzle.config.${language}`, transforms.script(({ ast }) => {
431
+ const isProp = (name, node) => node.key.type === "Identifier" && node.key.name === name;
432
+ Walker.walk(ast, null, { Property(node) {
433
+ if (isProp("dialect", node) && node.value.type === "Literal" && typeof node.value.value === "string") drizzleDialect = node.value.value;
434
+ if (isProp("driver", node) && node.value.type === "Literal" && node.value.value === "d1-http") d1 = true;
435
+ } });
436
+ if (!drizzleDialect) throw new Error("Failed to detect DB dialect in your `drizzle.config.[js|ts]` file");
437
+ return false;
438
+ }));
439
+ sv.file(".env", generateEnv$1(demoGithub, false));
440
+ sv.file(".env.example", generateEnv$1(demoGithub, true));
441
+ const env = defineEnv({
442
+ sv,
443
+ cwd,
444
+ dependencyVersion
445
+ });
446
+ env.define({
447
+ name: "ORIGIN",
448
+ description: "The app origin (base URL), e.g. `http://localhost:5173`."
449
+ });
450
+ env.define({
451
+ name: "BETTER_AUTH_SECRET",
452
+ description: "Secret used to sign tokens. For production use 32 characters generated with high entropy. See [Better Auth installation](https://www.better-auth.com/docs/installation)."
453
+ });
454
+ if (demoGithub) {
455
+ env.define({
456
+ name: "GITHUB_CLIENT_ID",
457
+ description: "GitHub OAuth client ID. See [Better Auth GitHub provider](https://www.better-auth.com/docs/authentication/github)."
458
+ });
459
+ env.define({
460
+ name: "GITHUB_CLIENT_SECRET",
461
+ description: "GitHub OAuth client secret. See [Better Auth GitHub provider](https://www.better-auth.com/docs/authentication/github)."
462
+ });
463
+ }
464
+ sv.file(`${directory.lib}/server/auth.${language}`, transforms.script(({ ast, comments, js }) => {
465
+ js.imports.addNamed(ast, {
466
+ from: `${lib}/server/db/index.${language}`,
467
+ imports: [d1 ? "getDb" : "db"]
468
+ });
469
+ js.imports.addNamed(ast, {
470
+ from: "$app/server",
471
+ imports: ["getRequestEvent"]
472
+ });
473
+ js.imports.addNamed(ast, {
474
+ from: "better-auth/svelte-kit",
475
+ imports: ["sveltekitCookies"]
476
+ });
477
+ js.imports.addNamed(ast, {
478
+ from: "better-auth/adapters/drizzle",
479
+ imports: ["drizzleAdapter"]
480
+ });
481
+ js.imports.addNamed(ast, {
482
+ from: "better-auth/minimal",
483
+ imports: ["betterAuth"]
484
+ });
485
+ const origin = env.reference(ast, js, { name: "ORIGIN" });
486
+ const secret = env.reference(ast, js, { name: "BETTER_AUTH_SECRET" });
487
+ const githubId = demoGithub ? env.reference(ast, js, { name: "GITHUB_CLIENT_ID" }) : "";
488
+ const githubSecret = demoGithub ? env.reference(ast, js, { name: "GITHUB_CLIENT_SECRET" }) : "";
489
+ const provider = {
490
+ mysql: "mysql",
491
+ postgresql: "pg",
492
+ sqlite: "sqlite",
493
+ turso: "sqlite"
494
+ }[drizzleDialect];
495
+ const githubProvider = demoGithub ? `
496
+ socialProviders: {
497
+ github: {
498
+ clientId: ${githubId},
499
+ clientSecret: ${githubSecret},
500
+ },
501
+ },` : "";
502
+ let authConfig = "";
503
+ if (d1) authConfig = dedent`
504
+ const authConfig = {
505
+ baseURL: ${origin},
506
+ secret: ${secret},
507
+ emailAndPassword: {
508
+ enabled: true
509
+ },${githubProvider}
510
+ plugins: [
511
+ sveltekitCookies(getRequestEvent) // make sure this is the last plugin in the array
512
+ ],
513
+ }${ts(" satisfies Omit<Parameters<typeof betterAuth>[0], \"database\">")};
514
+
515
+ export const createAuth = (d1${ts(": D1Database")}) => betterAuth({
516
+ ...authConfig,
517
+ database: drizzleAdapter(getDb(d1), { provider: '${provider}' }),
518
+ });
519
+
520
+ /**
521
+ * DO NOT USE!
522
+ *
523
+ * This instance is used by the \`auth\` CLI for schema generation ONLY.
524
+ * To access \`auth\` at runtime, use \`event.locals.auth\`.
525
+ */
526
+ export const auth = createAuth(null${ts("!")});`;
527
+ else authConfig = dedent`
528
+ export const auth = betterAuth({
529
+ baseURL: ${origin},
530
+ secret: ${secret},
531
+ database: drizzleAdapter(db, { provider: '${provider}' }),
532
+ emailAndPassword: {
533
+ enabled: true
534
+ },${githubProvider}
535
+ plugins: [
536
+ sveltekitCookies(getRequestEvent) // make sure this is the last plugin in the array
537
+ ],
538
+ });`;
539
+ js.common.appendFromString(ast, {
540
+ code: authConfig,
541
+ comments
542
+ });
543
+ }));
544
+ const authConfigPath = `${directory.lib}/server/auth.${language}`;
545
+ const authSchemaPath = `${directory.lib}/server/db/auth.schema.${language}`;
546
+ sv.file(file.package, transforms.json(({ data, json }) => {
547
+ json.packageScriptsUpsert(data, "auth:schema", `auth generate --config ${authConfigPath} --output ${authSchemaPath} --yes`);
548
+ }));
549
+ sv.file(`${directory.lib}/server/db/auth.schema.${language}`, transforms.text(({ content }) => {
550
+ if (content) return false;
551
+ return dedent`
552
+ // If you see this file, you have not run the auth:schema script yet, but you should!
553
+ export {};
554
+ `;
555
+ }));
556
+ sv.file(`${directory.lib}/server/db/schema.${language}`, transforms.script(({ ast, js }) => {
557
+ js.exports.addNamespace(ast, { from: "./auth.schema" });
558
+ }));
559
+ sv.file("src/app.d.ts", transforms.script(({ ast, comments, js }) => {
560
+ if (d1) js.imports.addNamed(ast, {
561
+ imports: ["createAuth"],
562
+ from: `${lib}/server/auth.${language}`
563
+ });
564
+ js.imports.addNamed(ast, {
565
+ imports: ["User", "Session"],
566
+ from: "better-auth",
567
+ isType: true
568
+ });
569
+ const locals = js.kit.addGlobalAppInterface(ast, { name: "Locals" });
570
+ if (!locals) throw new Error("Failed detecting `locals` interface in `src/app.d.ts`");
571
+ comments.remove((c) => c.type === "Line" && c.value.trim() === "interface Locals {}");
572
+ const user = locals.body.body.find((prop) => js.common.hasTypeProperty(prop, { name: "user" }));
573
+ const session = locals.body.body.find((prop) => js.common.hasTypeProperty(prop, { name: "session" }));
574
+ const auth = locals.body.body.find((prop) => js.common.hasTypeProperty(prop, { name: "auth" }));
575
+ if (!user) locals.body.body.push(js.common.createTypeProperty("user", "User", true));
576
+ if (!session) locals.body.body.push(js.common.createTypeProperty("session", "Session", true));
577
+ if (d1 && !auth) locals.body.body.push(js.common.createTypeProperty("auth", "ReturnType<typeof createAuth>", false));
578
+ }));
579
+ sv.file(`src/hooks.server.${language}`, transforms.script(({ ast, comments, js }) => {
580
+ js.imports.addNamed(ast, {
581
+ imports: ["svelteKitHandler"],
582
+ from: "better-auth/svelte-kit"
583
+ });
584
+ js.imports.addNamed(ast, {
585
+ imports: [d1 ? "createAuth" : "auth"],
586
+ from: `${lib}/server/auth.${language}`
587
+ });
588
+ env.importEnv(ast, js, ["building"]);
589
+ const d1HandleSetup = d1 ? dedent`
590
+ if (!event.platform?.env?.DB) throw new Error('D1 binding "DB" not found - are you running with wrangler?');
591
+ event.locals.auth = createAuth(event.platform.env.DB);
592
+ const { auth } = event.locals;\n` : "";
593
+ const handleContent = dedent`
594
+ async ({ event, resolve }) => {${d1HandleSetup}
595
+ // Fetch current session from Better Auth
596
+ const session = await auth.api.getSession({
597
+ headers: event.request.headers
598
+ });
599
+ // Make session and user available on server
600
+ if (session) {
601
+ event.locals.session = session.session;
602
+ event.locals.user = session.user;
603
+ }
604
+ return svelteKitHandler({ event, resolve, auth, building });
605
+ };
606
+
607
+ export const handle = sequence(handleBetterAuth, handleSession);
608
+ `;
609
+ js.kit.addHooksHandle(ast, {
610
+ language,
611
+ newHandleName: "handleBetterAuth",
612
+ handleContent,
613
+ comments
614
+ });
615
+ }));
616
+ if (hasDemo) {
617
+ sv.file(`${directory.kitRoutes}/demo/+page.svelte`, addToDemoPage("better-auth", language));
618
+ sv.file(`${directory.kitRoutes}/demo/better-auth/login/+page.server.${language}`, (content) => {
619
+ if (content) {
620
+ const filePath = `${directory.kitRoutes}/demo/better-auth/login/+page.server.${language}`;
621
+ R.warn(`Existing ${color.warning(filePath)} file. Could not update.`);
622
+ return false;
623
+ }
624
+ const d1AuthLine = d1 ? "\n const { auth } = event.locals;\n" : "";
625
+ const signInEmailAction = demoPassword ? `
626
+ signInEmail: async (event) => {${d1AuthLine}
627
+ const formData = await event.request.formData();
628
+ const email = formData.get('email')?.toString() ?? '';
629
+ const password = formData.get('password')?.toString() ?? '';
630
+
631
+ try {
632
+ await auth.api.signInEmail({
633
+ body: {
634
+ email,
635
+ password,
636
+ callbackURL: '/auth/verification-success'
637
+ }
638
+ });
639
+ } catch (error) {
640
+ if (error instanceof APIError) {
641
+ return fail(400, { message: error.message || 'Signin failed' });
642
+ }
643
+ return fail(500, { message: 'Unexpected error' });
644
+ }
645
+
646
+ return redirect(302, '/demo/better-auth');
647
+ },
648
+ signUpEmail: async (event) => {${d1AuthLine}
649
+ const formData = await event.request.formData();
650
+ const email = formData.get('email')?.toString() ?? '';
651
+ const password = formData.get('password')?.toString() ?? '';
652
+ const name = formData.get('name')?.toString() ?? '';
653
+
654
+ try {
655
+ await auth.api.signUpEmail({
656
+ body: {
657
+ email,
658
+ password,
659
+ name,
660
+ callbackURL: '/auth/verification-success'
661
+ }
662
+ });
663
+ } catch (error) {
664
+ if (error instanceof APIError) {
665
+ return fail(400, { message: error.message || 'Registration failed' });
666
+ }
667
+ return fail(500, { message: 'Unexpected error' });
668
+ }
669
+
670
+ return redirect(302, '/demo/better-auth');
671
+ },` : "";
672
+ const signInSocialAction = demoGithub ? `
673
+ signInSocial: async (event) => {${d1AuthLine}
674
+ const formData = await event.request.formData();
675
+ const provider = formData.get('provider')?.toString() ?? 'github';
676
+ const callbackURL = formData.get('callbackURL')?.toString() ?? '/demo/better-auth';
677
+
678
+ const result = await auth.api.signInSocial({
679
+ body: {
680
+ provider: provider${ts(" as \"github\"")},
681
+ callbackURL
682
+ }
683
+ });
684
+
685
+ if (result.url) {
686
+ return redirect(302, result.url);
687
+ }
688
+ return fail(400, { message: 'Social sign-in failed' });
689
+ },` : "";
690
+ const needsAPIError = demoPassword;
691
+ return dedent`
692
+ import { fail, redirect } from '@sveltejs/kit';
693
+ ${ts("import type { Actions } from './$types';")}
694
+ ${ts("import type { PageServerLoad } from './$types';")}
695
+ ${!d1 ? `import { auth } from '${lib}/server/auth.${language}';` : ""}
696
+ ${needsAPIError ? "import { APIError } from 'better-auth/api';" : ""}
697
+
698
+ export const load${ts(": PageServerLoad")} = (event) => {
699
+ if (event.locals.user) {
700
+ return redirect(302, '/demo/better-auth');
701
+ }
702
+ return {};
703
+ };
704
+
705
+ export const actions${ts(": Actions")} = {${signInEmailAction}${signInSocialAction}
706
+ };
707
+ `;
708
+ });
709
+ sv.file(`${directory.kitRoutes}/demo/better-auth/login/+page.svelte`, (content) => {
710
+ if (content) {
711
+ const filePath = `${directory.kitRoutes}/demo/better-auth/login/+page.svelte`;
712
+ R.warn(`Existing ${color.warning(filePath)} file. Could not update.`);
713
+ return false;
714
+ }
715
+ const tailwind = dependencyVersion("@tailwindcss/vite") !== void 0;
716
+ const input = tailwind ? " class=\"mt-1 px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500\"" : "";
717
+ const btn = tailwind ? " class=\"bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition\"" : "";
718
+ const passwordForm = demoPassword ? `
719
+ <form method="post" action="?/signInEmail" use:enhance>
720
+ <label>
721
+ Email
722
+ <input type="email" name="email"${input} />
723
+ </label>
724
+ <label>
725
+ Password
726
+ <input type="password" name="password"${input} />
727
+ </label>
728
+ <label>
729
+ Name (for registration)
730
+ <input name="name"${input} />
731
+ </label>
732
+ <button${btn}>Login</button>
733
+ <button formaction="?/signUpEmail"${btn}>Register</button>
734
+ </form>
735
+ ${tailwind ? `<p class="text-red-500">{form?.message ?? ''}</p>` : `<p style="color: red">{form?.message ?? ''}</p>`}` : "";
736
+ const separator = demoPassword && demoGithub ? `\n\n\t\t\t\t\t<hr ${tailwind ? "class=\"my-4\"" : ""} />\n` : "";
737
+ const githubForm = demoGithub ? `
738
+ <form method="post" action="?/signInSocial" use:enhance>
739
+ <input type="hidden" name="provider" value="github" />
740
+ <input type="hidden" name="callbackURL" value="/demo/better-auth" />
741
+ <button${btn}>Sign in with GitHub</button>
742
+ </form>` : "";
743
+ return dedent(_templateObject$1 || (_templateObject$1 = _taggedTemplateLiteral([
744
+ "\n <script ",
745
+ ">\n import { enhance } from '$app/forms';\n ",
746
+ "\n ",
747
+ "\n <\/script>\n\n <h1>Login</h1>",
748
+ "",
749
+ "",
750
+ "\n "
751
+ ])), ts("lang='ts'"), ts("import type { ActionData } from './$types';\n"), s5(`let { form }${ts(": { form: ActionData }")} = $props();`, `export let form${ts(": ActionData")};`), passwordForm, separator, githubForm);
752
+ });
753
+ sv.file(`${directory.kitRoutes}/demo/better-auth/+page.server.${language}`, (content) => {
754
+ if (content) {
755
+ const filePath = `${directory.kitRoutes}/demo/better-auth/+page.server.${language}`;
756
+ R.warn(`Existing ${color.warning(filePath)} file. Could not update.`);
757
+ return false;
758
+ }
759
+ const d1AuthLine = d1 ? "\n const { auth } = event.locals;\n" : "";
760
+ return dedent`
761
+ import { redirect } from '@sveltejs/kit';
762
+ ${ts("import type { Actions } from './$types';")}
763
+ ${ts("import type { PageServerLoad } from './$types';")}
764
+ ${!d1 ? `import { auth } from '${lib}/server/auth.${language}';` : ""}
765
+
766
+ export const load${ts(": PageServerLoad")} = (event) => {
767
+ if (!event.locals.user) {
768
+ return redirect(302, '/demo/better-auth/login');
769
+ }
770
+ return { user: event.locals.user };
771
+ };
772
+
773
+ export const actions${ts(": Actions")} = {
774
+ signOut: async (event) => {${d1AuthLine}
775
+ await auth.api.signOut({
776
+ headers: event.request.headers
777
+ });
778
+ return redirect(302, '/demo/better-auth/login');
779
+ }
780
+ };
781
+ `;
782
+ });
783
+ sv.file(`${directory.kitRoutes}/demo/better-auth/+page.svelte`, (content) => {
784
+ if (content) {
785
+ const filePath = `${directory.kitRoutes}/demo/better-auth/+page.svelte`;
786
+ R.warn(`Existing ${color.warning(filePath)} file. Could not update.`);
787
+ return false;
788
+ }
789
+ const tailwind = dependencyVersion("@tailwindcss/vite") !== void 0;
790
+ return dedent(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral([
791
+ "\n <script ",
792
+ ">\n import { enhance } from '$app/forms';\n ",
793
+ "\n ",
794
+ "\n <\/script>\n\n <h1>Hi, {data.user.name}!</h1>\n <p>Your user ID is {data.user.id}.</p>\n <form method=\"post\" action=\"?/signOut\" use:enhance>\n <button ",
795
+ ">Sign out</button>\n </form>\n "
796
+ ])), ts("lang='ts'"), ts("import type { PageServerData } from './$types';\n"), s5(`let { data }${ts(": { data: PageServerData }")} = $props();`, `export let data${ts(": PageServerData")};`), tailwind ? "class=\"bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition\"" : "");
797
+ });
798
+ }
799
+ },
800
+ nextSteps: ({ options, packageManager }) => {
801
+ const steps = [
802
+ `Run ${color.command(resolveCommandArray(packageManager, "run", ["auth:schema"]))} to generate the auth schema`,
803
+ `Run ${color.command(resolveCommandArray(packageManager, "run", ["db:push"]))} to update your database`,
804
+ `Check ${color.env("ORIGIN")} & ${color.env("BETTER_AUTH_SECRET")} in ${color.path(".env")} and adjust it to your needs`
805
+ ];
806
+ if (options.demo.includes("github")) steps.push(`Set your ${color.env("GITHUB_CLIENT_ID")} and ${color.env("GITHUB_CLIENT_SECRET")} in ${color.path(".env")}`);
807
+ if (options.demo.length > 0) steps.push(`Visit ${color.route("/demo/better-auth")} route to view the demo`);
808
+ return steps;
809
+ }
810
+ });
811
+ const generateEnv$1 = (demoGithub, isExample) => transforms.text(({ content, text }) => {
812
+ content = text.upsert(content, "ORIGIN", {
813
+ value: isExample ? `""` : `"http://localhost:5173"`,
814
+ separator: true
815
+ });
816
+ content = text.upsert(content, "BETTER_AUTH_SECRET", {
817
+ value: isExample ? `""` : `"${crypto.randomUUID()}"`,
818
+ comment: [
819
+ "Better Auth",
820
+ "For production use 32 characters and generated with high entropy",
821
+ "https://www.better-auth.com/docs/installation"
822
+ ],
823
+ separator: true
824
+ });
825
+ if (demoGithub) {
826
+ content = text.upsert(content, "GITHUB_CLIENT_ID", {
827
+ value: `""`,
828
+ comment: "GitHub OAuth\n# https://www.better-auth.com/docs/authentication/github"
829
+ });
830
+ content = text.upsert(content, "GITHUB_CLIENT_SECRET", { value: `""` });
831
+ }
832
+ return content;
833
+ });
834
+ //#endregion
835
+ //#region src/addons/drizzle.ts
836
+ const PORTS = {
837
+ mysql: "3306",
838
+ postgresql: "5432",
839
+ sqlite: "",
840
+ d1: ""
841
+ };
842
+ const options$5 = defineAddonOptions().add("database", {
843
+ question: "Which database would you like to use?",
844
+ type: "select",
845
+ default: "sqlite",
846
+ options: [
847
+ {
848
+ value: "postgresql",
849
+ label: "PostgreSQL"
850
+ },
851
+ {
852
+ value: "mysql",
853
+ label: "MySQL"
854
+ },
855
+ {
856
+ value: "sqlite",
857
+ label: "SQLite"
858
+ },
859
+ {
860
+ value: "d1",
861
+ label: "Cloudflare D1"
862
+ }
863
+ ]
864
+ }).add("postgresql", {
865
+ question: "Which PostgreSQL client would you like to use?",
866
+ type: "select",
867
+ group: "client",
868
+ default: "postgres.js",
869
+ options: [{
870
+ value: "postgres.js",
871
+ label: "Postgres.JS",
872
+ hint: "recommended for most users"
873
+ }, {
874
+ value: "neon",
875
+ label: "Neon",
876
+ hint: "popular hosted platform"
877
+ }],
878
+ condition: ({ database }) => database === "postgresql"
879
+ }).add("mysql", {
880
+ question: "Which MySQL client would you like to use?",
881
+ type: "select",
882
+ group: "client",
883
+ default: "mysql2",
884
+ options: [{
885
+ value: "mysql2",
886
+ hint: "recommended for most users"
887
+ }, {
888
+ value: "planetscale",
889
+ label: "PlanetScale",
890
+ hint: "popular hosted platform"
891
+ }],
892
+ condition: ({ database }) => database === "mysql"
893
+ }).add("sqlite", {
894
+ question: "Which SQLite client would you like to use?",
895
+ type: "select",
896
+ group: "client",
897
+ default: "libsql",
898
+ options: [
899
+ {
900
+ value: "node-sqlite",
901
+ label: "node:sqlite",
902
+ hint: "built-in to Node.js and Deno"
903
+ },
904
+ {
905
+ value: "better-sqlite3",
906
+ hint: "for server environments"
907
+ },
908
+ {
909
+ value: "libsql",
910
+ label: "libSQL",
911
+ hint: "for serverless environments"
912
+ },
913
+ {
914
+ value: "turso",
915
+ label: "Turso",
916
+ hint: "popular hosted platform"
917
+ }
918
+ ],
919
+ condition: ({ database }) => database === "sqlite"
920
+ }).add("docker", {
921
+ question: "Do you want to run the database locally with docker-compose?",
922
+ default: false,
923
+ type: "boolean",
924
+ condition: ({ database, mysql, postgresql }) => database === "mysql" && mysql === "mysql2" || database === "postgresql" && postgresql === "postgres.js"
925
+ }).build();
926
+ var drizzle_default = defineAddon({
927
+ id: "drizzle",
928
+ shortDescription: "database orm",
929
+ homepage: "https://orm.drizzle.team",
930
+ options: options$5,
931
+ setup: ({ isKit, unsupported, runsAfter }) => {
932
+ runsAfter("prettier");
933
+ runsAfter("sveltekitAdapter");
934
+ runsAfter("experimental");
935
+ if (!isKit) return unsupported("Requires SvelteKit");
936
+ },
937
+ run: ({ sv, language, options, directory, dependencyVersion, cwd, cancel, file, packageManager }) => {
938
+ const [ts] = createPrinter(language === "ts");
939
+ const baseDBPath = path.resolve(cwd, directory.lib, "server", "db");
940
+ const paths = {
941
+ "drizzle config": path.resolve(cwd, `drizzle.config.${language}`),
942
+ "database schema": path.resolve(baseDBPath, `schema.${language}`),
943
+ database: path.resolve(baseDBPath, `index.${language}`)
944
+ };
945
+ for (const [fileType, filePath] of Object.entries(paths)) if (fs.existsSync(filePath)) return cancel(`Preexisting ${fileType} file at '${filePath}'`);
946
+ sv.devDependency("drizzle-orm", "^0.45.2");
947
+ sv.devDependency("drizzle-kit", "^0.31.10");
948
+ sv.devDependency("@types/node", getNodeTypesVersion());
949
+ if (options.mysql === "mysql2") sv.devDependency("mysql2", "^3.22.4");
950
+ if (options.mysql === "planetscale") sv.devDependency("@planetscale/database", "^1.20.1");
951
+ if (options.postgresql === "neon") sv.devDependency("@neondatabase/serverless", "^1.1.0");
952
+ if (options.postgresql === "postgres.js") sv.devDependency("postgres", "^3.4.9");
953
+ if (options.sqlite === "better-sqlite3") {
954
+ sv.dependency("better-sqlite3", "^13.0.2");
955
+ sv.devDependency("@types/better-sqlite3", "^9.6.0");
956
+ }
957
+ if (options.sqlite === "libsql" || options.sqlite === "turso") sv.devDependency("@libsql/client", "^0.17.3");
958
+ if (packageManager === "pnpm") sv.file(file.findUp("pnpm-workspace.yaml"), pnpm.allowBuilds({
959
+ cwd,
960
+ packages: ["esbuild"]
961
+ }));
962
+ sv.file(".env", generateEnv(options, false));
963
+ sv.file(".env.example", generateEnv(options, true));
964
+ if (options.docker && (options.mysql === "mysql2" || options.postgresql === "postgres.js")) {
965
+ const composeFile = [
966
+ "compose.yaml",
967
+ "compose.yml",
968
+ "docker-compose.yaml",
969
+ "docker-compose.yml"
970
+ ].find((option) => fs.existsSync(path.resolve(cwd, option))) ?? "compose.yaml";
971
+ sv.file(composeFile, (content) => {
972
+ if (content.length > 0) return false;
973
+ const imageName = options.database === "mysql" ? "mysql" : "postgres";
974
+ const port = PORTS[options.database];
975
+ const USER = "root";
976
+ const PASSWORD = "mysecretpassword";
977
+ const DB_NAME = "local";
978
+ let dbSpecificContent = "";
979
+ if (options.mysql === "mysql2") dbSpecificContent = `
980
+ MYSQL_ROOT_PASSWORD: ${PASSWORD}
981
+ MYSQL_DATABASE: ${DB_NAME}
982
+ volumes:
983
+ - mysqldata:/var/lib/mysql
984
+ volumes:
985
+ mysqldata:
986
+ `;
987
+ if (options.postgresql === "postgres.js") dbSpecificContent = `
988
+ POSTGRES_USER: ${USER}
989
+ POSTGRES_PASSWORD: ${PASSWORD}
990
+ POSTGRES_DB: ${DB_NAME}
991
+ volumes:
992
+ - pgdata:/var/lib/postgresql
993
+ volumes:
994
+ pgdata:
995
+ `;
996
+ return dedent`
997
+ services:
998
+ db:
999
+ image: ${imageName}
1000
+ restart: always
1001
+ ports:
1002
+ - ${port}:${port}
1003
+ environment: ${dbSpecificContent}
1004
+ `;
1005
+ });
1006
+ }
1007
+ sv.file(file.package, transforms.json(({ data, json }) => {
1008
+ if (options.docker) json.packageScriptsUpsert(data, "db:start", "docker compose up");
1009
+ json.packageScriptsUpsert(data, "db:push", "drizzle-kit push");
1010
+ json.packageScriptsUpsert(data, "db:generate", "drizzle-kit generate");
1011
+ json.packageScriptsUpsert(data, "db:migrate", "drizzle-kit migrate");
1012
+ json.packageScriptsUpsert(data, "db:studio", "drizzle-kit studio");
1013
+ }));
1014
+ if (Boolean(dependencyVersion("prettier"))) sv.file(".prettierignore", transforms.text(({ content, text }) => text.upsert(content, "/drizzle/")));
1015
+ if (options.database === "sqlite") sv.file(file.gitignore, transforms.text(({ content, text }) => {
1016
+ if (content.length === 0) return false;
1017
+ return text.upsert(content, "*.db", { comment: "SQLite" });
1018
+ }));
1019
+ sv.file(paths["drizzle config"], transforms.script(({ ast, js }) => {
1020
+ const d1 = options.database === "d1";
1021
+ const turso = options.sqlite === "turso";
1022
+ js.imports.addNamed(ast, {
1023
+ from: "drizzle-kit",
1024
+ imports: { defineConfig: "defineConfig" }
1025
+ });
1026
+ if (d1) ast.body.push(js.common.parseStatement("if (!process.env.CLOUDFLARE_ACCOUNT_ID) throw new Error('CLOUDFLARE_ACCOUNT_ID is not set');"), js.common.parseStatement("if (!process.env.CLOUDFLARE_DATABASE_ID) throw new Error('CLOUDFLARE_DATABASE_ID is not set');"), js.common.parseStatement("if (!process.env.CLOUDFLARE_D1_TOKEN) throw new Error('CLOUDFLARE_D1_TOKEN is not set');"));
1027
+ else ast.body.push(js.common.parseStatement("if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set');"));
1028
+ const getDialect = () => {
1029
+ if (d1) return "sqlite";
1030
+ if (turso) return "turso";
1031
+ return options.database;
1032
+ };
1033
+ const getCredentials = () => {
1034
+ const creds = [];
1035
+ if (d1) {
1036
+ creds.push("accountId: process.env.CLOUDFLARE_ACCOUNT_ID,");
1037
+ creds.push("databaseId: process.env.CLOUDFLARE_DATABASE_ID,");
1038
+ creds.push("token: process.env.CLOUDFLARE_D1_TOKEN,");
1039
+ }
1040
+ if (turso) creds.push("authToken: process.env.DATABASE_AUTH_TOKEN,");
1041
+ if (!d1) creds.push("url: process.env.DATABASE_URL,");
1042
+ return creds.join("\n");
1043
+ };
1044
+ js.exports.createDefault(ast, { fallback: js.common.parseExpression(`
1045
+ defineConfig({
1046
+ schema: "./${directory.lib}/server/db/schema.${language}",
1047
+ dialect: "${getDialect()}",
1048
+ ${d1 ? "driver: 'd1-http'," : ""}
1049
+ dbCredentials: {
1050
+ ${getCredentials()}
1051
+ },
1052
+ verbose: true,
1053
+ strict: true
1054
+ })
1055
+ `) });
1056
+ }));
1057
+ if (isKit3(dependencyVersion("@sveltejs/kit"))) {
1058
+ const configFile = language === "ts" ? "tsconfig.json" : "jsconfig.json";
1059
+ if (fileExists(cwd, configFile)) sv.file(configFile, transforms.json(({ data }) => {
1060
+ const include = data.include ??= ["src"];
1061
+ if (!include.includes(`drizzle.config.${language}`)) include.push(`drizzle.config.${language}`);
1062
+ }));
1063
+ } else svelteConfig.edit({
1064
+ sv,
1065
+ cwd
1066
+ }, ({ override, js }) => {
1067
+ override({ typescript: { config: js.common.parseExpression(`(config) => { config.include.push('../drizzle.config.${language}')}`) } });
1068
+ });
1069
+ sv.file(paths["database schema"], transforms.script(({ ast, js }) => {
1070
+ let taskSchemaExpression;
1071
+ if (options.database === "sqlite" || options.database === "d1") {
1072
+ js.imports.addNamed(ast, {
1073
+ from: "drizzle-orm/sqlite-core",
1074
+ imports: [
1075
+ "integer",
1076
+ "sqliteTable",
1077
+ "text"
1078
+ ]
1079
+ });
1080
+ taskSchemaExpression = js.common.parseExpression(`sqliteTable('task', {
1081
+ id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
1082
+ title: text('title').notNull(),
1083
+ priority: integer('priority').notNull().default(1)
1084
+ })`);
1085
+ }
1086
+ if (options.database === "mysql") {
1087
+ js.imports.addNamed(ast, {
1088
+ from: "drizzle-orm/mysql-core",
1089
+ imports: [
1090
+ "mysqlTable",
1091
+ "serial",
1092
+ "int",
1093
+ "text"
1094
+ ]
1095
+ });
1096
+ taskSchemaExpression = js.common.parseExpression(`mysqlTable('task', {
1097
+ id: serial('id').primaryKey(),
1098
+ title: text('title').notNull(),
1099
+ priority: int('priority').notNull().default(1)
1100
+ })`);
1101
+ }
1102
+ if (options.database === "postgresql") {
1103
+ js.imports.addNamed(ast, {
1104
+ from: "drizzle-orm/pg-core",
1105
+ imports: [
1106
+ "pgTable",
1107
+ "serial",
1108
+ "integer",
1109
+ "text"
1110
+ ]
1111
+ });
1112
+ taskSchemaExpression = js.common.parseExpression(`pgTable('task', {
1113
+ id: serial('id').primaryKey(),
1114
+ title: text('title').notNull(),
1115
+ priority: integer('priority').notNull().default(1)
1116
+ })`);
1117
+ }
1118
+ if (!taskSchemaExpression) throw new Error("unreachable state...");
1119
+ const taskIdentifier = js.variables.declaration(ast, {
1120
+ kind: "const",
1121
+ name: "task",
1122
+ value: taskSchemaExpression
1123
+ });
1124
+ js.exports.createNamed(ast, {
1125
+ name: "task",
1126
+ fallback: taskIdentifier
1127
+ });
1128
+ }));
1129
+ const env = defineEnv({
1130
+ sv,
1131
+ cwd,
1132
+ dependencyVersion
1133
+ });
1134
+ if (options.database !== "d1") {
1135
+ env.define({
1136
+ name: "DATABASE_URL",
1137
+ description: "The database connection string."
1138
+ });
1139
+ if (options.sqlite === "turso") env.define({
1140
+ name: "DATABASE_AUTH_TOKEN",
1141
+ description: "Auth token for the [Turso](https://turso.tech) database."
1142
+ });
1143
+ }
1144
+ sv.file(paths.database, transforms.script(({ ast, js }) => {
1145
+ if (options.database === "d1") {
1146
+ js.imports.addNamespace(ast, {
1147
+ from: "./schema",
1148
+ as: "schema"
1149
+ });
1150
+ js.imports.addNamed(ast, {
1151
+ from: "drizzle-orm/d1",
1152
+ imports: ["drizzle"]
1153
+ });
1154
+ const getDbFn = js.common.parseStatement(`export const getDb = (d1${ts(": D1Database")}) => drizzle(d1, { schema });`);
1155
+ ast.body.push(getDbFn);
1156
+ return;
1157
+ }
1158
+ const dbUrl = env.reference(ast, js, { name: "DATABASE_URL" });
1159
+ js.imports.addNamespace(ast, {
1160
+ from: "./schema",
1161
+ as: "schema"
1162
+ });
1163
+ ast.body.push(js.common.parseStatement(`if (!${dbUrl}) throw new Error('DATABASE_URL is not set');`));
1164
+ let clientExpression;
1165
+ if (options.sqlite === "better-sqlite3") {
1166
+ js.imports.addDefault(ast, {
1167
+ from: "better-sqlite3",
1168
+ as: "Database"
1169
+ });
1170
+ js.imports.addNamed(ast, {
1171
+ from: "drizzle-orm/better-sqlite3",
1172
+ imports: ["drizzle"]
1173
+ });
1174
+ clientExpression = js.common.parseExpression(`new Database(${dbUrl})`);
1175
+ }
1176
+ if (options.sqlite === "libsql" || options.sqlite === "turso") {
1177
+ js.imports.addNamed(ast, {
1178
+ from: "@libsql/client",
1179
+ imports: ["createClient"]
1180
+ });
1181
+ js.imports.addNamed(ast, {
1182
+ from: "drizzle-orm/libsql",
1183
+ imports: ["drizzle"]
1184
+ });
1185
+ if (options.sqlite === "turso") {
1186
+ const dbToken = env.reference(ast, js, { name: "DATABASE_AUTH_TOKEN" });
1187
+ ast.body.push(js.common.parseStatement(`if (!${dbToken}) throw new Error('DATABASE_AUTH_TOKEN is not set');`));
1188
+ clientExpression = js.common.parseExpression(`createClient({ url: ${dbUrl}, authToken: ${dbToken} })`);
1189
+ } else clientExpression = js.common.parseExpression(`createClient({ url: ${dbUrl} })`);
1190
+ }
1191
+ if (options.sqlite === "node-sqlite") {
1192
+ js.imports.addNamed(ast, {
1193
+ from: "node:sqlite",
1194
+ imports: ["DatabaseSync"]
1195
+ });
1196
+ js.imports.addNamed(ast, {
1197
+ from: "drizzle-orm/node-sqlite",
1198
+ imports: ["drizzle"]
1199
+ });
1200
+ clientExpression = js.common.parseExpression(`new DatabaseSync(${dbUrl})`);
1201
+ }
1202
+ if (options.mysql === "mysql2" || options.mysql === "planetscale") {
1203
+ js.imports.addDefault(ast, {
1204
+ from: "mysql2/promise",
1205
+ as: "mysql"
1206
+ });
1207
+ js.imports.addNamed(ast, {
1208
+ from: "drizzle-orm/mysql2",
1209
+ imports: ["drizzle"]
1210
+ });
1211
+ clientExpression = js.common.parseExpression(`mysql.createPool(${dbUrl})`);
1212
+ }
1213
+ if (options.postgresql === "neon") {
1214
+ js.imports.addNamed(ast, {
1215
+ from: "@neondatabase/serverless",
1216
+ imports: ["neon"]
1217
+ });
1218
+ js.imports.addNamed(ast, {
1219
+ from: "drizzle-orm/neon-http",
1220
+ imports: ["drizzle"]
1221
+ });
1222
+ clientExpression = js.common.parseExpression(`neon(${dbUrl})`);
1223
+ }
1224
+ if (options.postgresql === "postgres.js") {
1225
+ js.imports.addDefault(ast, {
1226
+ from: "postgres",
1227
+ as: "postgres"
1228
+ });
1229
+ js.imports.addNamed(ast, {
1230
+ from: "drizzle-orm/postgres-js",
1231
+ imports: ["drizzle"]
1232
+ });
1233
+ clientExpression = js.common.parseExpression(`postgres(${dbUrl})`);
1234
+ }
1235
+ if (!clientExpression) throw new Error("unreachable state...");
1236
+ ast.body.push(js.variables.declaration(ast, {
1237
+ kind: "const",
1238
+ name: "client",
1239
+ value: clientExpression
1240
+ }));
1241
+ const drizzleCall = js.functions.createCall({
1242
+ name: "drizzle",
1243
+ args: ["client"],
1244
+ useIdentifiers: true
1245
+ });
1246
+ const paramObject = js.object.create({ schema: js.variables.createIdentifier("schema") });
1247
+ if (options.database === "mysql") {
1248
+ const mode = options.mysql === "planetscale" ? "planetscale" : "default";
1249
+ js.object.property(paramObject, {
1250
+ name: "mode",
1251
+ fallback: js.common.createLiteral(mode)
1252
+ });
1253
+ }
1254
+ drizzleCall.arguments.push(paramObject);
1255
+ const db = js.variables.declaration(ast, {
1256
+ kind: "const",
1257
+ name: "db",
1258
+ value: drizzleCall
1259
+ });
1260
+ js.exports.createNamed(ast, {
1261
+ name: "db",
1262
+ fallback: db
1263
+ });
1264
+ }));
1265
+ },
1266
+ nextSteps: ({ options, packageManager, cwd, dependencyVersion }) => {
1267
+ const pm = (command, args) => color.command(resolveCommandArray(packageManager, command, args));
1268
+ const steps = [];
1269
+ if (options.database === "d1") {
1270
+ if (!dependencyVersion("@sveltejs/adapter-cloudflare")) steps.push(`Cloudflare D1 requires ${color.addon("@sveltejs/adapter-cloudflare")}. Run ${pm("execute", [
1271
+ "sv",
1272
+ "add",
1273
+ "sveltekit-adapter=adapter:cloudflare"
1274
+ ])} to add it`);
1275
+ steps.push(`Add your ${color.env("CLOUDFLARE_ACCOUNT_ID")}, ${color.env("CLOUDFLARE_DATABASE_ID")}, and ${color.env("CLOUDFLARE_D1_TOKEN")} to ${color.path(".env")}`);
1276
+ const ext = fileExists(cwd, "wrangler.toml") ? "toml" : "jsonc";
1277
+ steps.push(`Run ${pm("execute-local", [
1278
+ "wrangler",
1279
+ "d1",
1280
+ "create",
1281
+ "<DATABASE_NAME>"
1282
+ ])} to generate a D1 database ID for your ${color.path(`wrangler.${ext}`)}`);
1283
+ }
1284
+ if (options.docker) steps.push(`Run ${pm("run", ["db:start"])} to start the docker container`);
1285
+ else if (options.database !== "d1") steps.push(`Check ${color.env("DATABASE_URL")} in ${color.path(".env")} and adjust it to your needs`);
1286
+ steps.push(`Run ${pm("run", ["db:push"])} to update your database schema`);
1287
+ return steps;
1288
+ }
1289
+ });
1290
+ const generateEnv = (opts, isExample) => transforms.text(({ content, text }) => {
1291
+ const DB_URL_KEY = "DATABASE_URL";
1292
+ if (opts.database === "d1") {
1293
+ content = text.upsert(content, "CLOUDFLARE_ACCOUNT_ID", {
1294
+ value: "\"\"",
1295
+ comment: ["Cloudflare D1"],
1296
+ separator: true
1297
+ });
1298
+ content = text.upsert(content, "CLOUDFLARE_DATABASE_ID", { value: "\"\"" });
1299
+ content = text.upsert(content, "CLOUDFLARE_D1_TOKEN", { value: "\"\"" });
1300
+ return content;
1301
+ }
1302
+ let value;
1303
+ const comment = ["Drizzle"];
1304
+ if (opts.docker) value = `"${opts.database === "mysql" ? "mysql" : "postgres"}://root:mysecretpassword@localhost:${PORTS[opts.database]}/local"`;
1305
+ else if ([
1306
+ "better-sqlite3",
1307
+ "libsql",
1308
+ "node-sqlite"
1309
+ ].includes(opts.sqlite)) value = opts.sqlite === "libsql" ? "file:local.db" : "local.db";
1310
+ else if (opts.sqlite === "turso") {
1311
+ if (isExample) {
1312
+ value = "\"libsql://db-name-user.turso.io\"";
1313
+ comment.push("Replace with your DB credentials!", {
1314
+ text: "A local DB can also be used in dev as well",
1315
+ mode: "append"
1316
+ }, {
1317
+ text: `${DB_URL_KEY}="file:local.db"`,
1318
+ mode: "append"
1319
+ });
1320
+ } else {
1321
+ value = "\"file:local.db\"";
1322
+ comment.push("Replace with your DB credentials!", `${DB_URL_KEY}="libsql://db-name-user.turso.io"`, "A local DB can also be used in dev as well");
1323
+ }
1324
+ } else if (opts.database === "mysql") {
1325
+ value = "\"mysql://user:password@host:port/db-name\"";
1326
+ comment.push("Replace with your DB credentials!");
1327
+ } else if (opts.database === "postgresql") {
1328
+ value = "\"postgres://user:password@host:port/db-name\"";
1329
+ comment.push("Replace with your DB credentials!");
1330
+ } else value = "";
1331
+ content = text.upsert(content, DB_URL_KEY, {
1332
+ value,
1333
+ comment,
1334
+ separator: true
1335
+ });
1336
+ if (opts.sqlite === "turso") content = text.upsert(content, "DATABASE_AUTH_TOKEN", { value: isExample ? `""` : `"${crypto.randomUUID()}"` });
1337
+ return content;
1338
+ });
1339
+ //#endregion
1340
+ //#region src/addons/enhanced-img.ts
1341
+ const REGEX_IMPORTED_IMG = /import (\w+) from '([^']+\.(?:png|jpe?g|webp|avif|gif))';/g;
1342
+ const REGEX_REGEX_CHARS = /[.*+?^${}()|[\]\\]/g;
1343
+ function escapeRegex(value) {
1344
+ return value.replace(REGEX_REGEX_CHARS, "\\$&");
1345
+ }
1346
+ var enhanced_img_default = defineAddon({
1347
+ id: "enhanced-img",
1348
+ shortDescription: "image optimization",
1349
+ homepage: "https://svelte.dev/docs/kit/images",
1350
+ options: {},
1351
+ run: ({ sv, file, packageManager, cwd }) => {
1352
+ sv.devDependency("@sveltejs/enhanced-img", "^1.0.0-next.5");
1353
+ if (packageManager === "pnpm") sv.file(file.findUp("pnpm-workspace.yaml"), pnpm.allowBuilds({
1354
+ cwd,
1355
+ packages: ["sharp"]
1356
+ }));
1357
+ sv.files({
1358
+ include: "src/**/*.svelte",
1359
+ where: (content) => content.includes("<img")
1360
+ }, transforms.text(({ content }) => {
1361
+ let next = content;
1362
+ for (const [statement, binding, src] of content.matchAll(REGEX_IMPORTED_IMG)) {
1363
+ const tag = new RegExp(`<img([^>]*?)src=\\{${binding}\\}`, "g");
1364
+ if (!tag.test(next)) continue;
1365
+ const importLine = new RegExp(`[ \\t]*${escapeRegex(statement)}\\r?\\n?`);
1366
+ const unused = !new RegExp(`(?<![\\w"'./-])${binding}\\b`).test(content.replace(importLine, "").replaceAll(tag, ""));
1367
+ next = next.replaceAll(tag, `<enhanced:img$1src="${src}"`);
1368
+ if (unused) next = next.replace(importLine, "");
1369
+ }
1370
+ return next === content ? false : next;
1371
+ }));
1372
+ sv.file(file.viteConfig, transforms.script(({ ast, js }) => {
1373
+ js.imports.addNamed(ast, {
1374
+ imports: ["enhancedImages"],
1375
+ from: "@sveltejs/enhanced-img"
1376
+ });
1377
+ js.vite.addPlugin(ast, {
1378
+ code: "enhancedImages()",
1379
+ mode: "prepend"
1380
+ });
1381
+ }));
1382
+ },
1383
+ nextSteps: () => [`Imported images now use ${color.command("`<enhanced:img>`")}`, `Docs: ${color.website("https://svelte.dev/docs/kit/images")}`]
1384
+ });
1385
+ //#endregion
1386
+ //#region src/addons/eslint.ts
1387
+ var eslint_default = defineAddon({
1388
+ id: "eslint",
1389
+ shortDescription: "linter",
1390
+ homepage: "https://eslint.org",
1391
+ options: {},
1392
+ run: ({ sv, language, dependencyVersion, file, cwd }) => {
1393
+ const typescript = language === "ts";
1394
+ const prettierInstalled = Boolean(dependencyVersion("prettier"));
1395
+ const configLocation = svelteConfig.find(cwd);
1396
+ const svelteConfigFile = configLocation?.kind === "svelte" ? configLocation.path : void 0;
1397
+ sv.devDependency("eslint", ESLINT_VERSION);
1398
+ sv.devDependency("eslint-plugin-svelte", "^3.19.0");
1399
+ sv.devDependency("globals", "^17.6.0");
1400
+ sv.devDependency("@eslint/js", "^10.0.1");
1401
+ sv.devDependency("@types/node", getNodeTypesVersion());
1402
+ if (typescript) sv.devDependency("typescript-eslint", "^8.60.1");
1403
+ if (prettierInstalled) sv.devDependency("eslint-config-prettier", "^10.1.8");
1404
+ sv.file(file.package, transforms.json(({ data, json }) => {
1405
+ json.packageScriptsUpsert(data, "lint", "eslint .");
1406
+ }));
1407
+ sv.file("eslint.config.js", transforms.script(({ ast, comments, js }) => {
1408
+ const eslintConfigs = [];
1409
+ if (svelteConfigFile) js.imports.addDefault(ast, {
1410
+ from: `./${svelteConfigFile}`,
1411
+ as: "svelteConfig"
1412
+ });
1413
+ const gitIgnorePathStatement = js.common.parseStatement("\nconst gitignorePath = path.resolve(import.meta.dirname, '.gitignore');");
1414
+ js.common.appendStatement(ast, { statement: gitIgnorePathStatement });
1415
+ const ignoresConfig = js.common.parseExpression("includeIgnoreFile(gitignorePath)");
1416
+ eslintConfigs.push(ignoresConfig);
1417
+ const jsConfig = js.common.parseExpression("js.configs.recommended");
1418
+ eslintConfigs.push(jsConfig);
1419
+ if (typescript) {
1420
+ const tsConfig = js.common.parseExpression("ts.configs.recommended");
1421
+ eslintConfigs.push(tsConfig);
1422
+ }
1423
+ const svelteConfig = js.common.parseExpression("svelte.configs.recommended");
1424
+ eslintConfigs.push(svelteConfig);
1425
+ const globalsBrowser = js.common.createSpread(js.common.parseExpression("globals.browser"));
1426
+ const globalsNode = js.common.createSpread(js.common.parseExpression("globals.node"));
1427
+ const globalsObjLiteral = js.object.create({});
1428
+ globalsObjLiteral.properties = [globalsBrowser, globalsNode];
1429
+ const rules = js.object.create({ "\"no-undef\"": "off" });
1430
+ if (rules.properties[0].type !== "Property") throw new Error("rules.properties[0].type !== \"Property\"");
1431
+ comments.add(rules.properties[0].key, {
1432
+ type: "Line",
1433
+ value: " typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects."
1434
+ });
1435
+ comments.add(rules.properties[0].key, {
1436
+ type: "Line",
1437
+ value: " see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors"
1438
+ });
1439
+ const globalsConfig = js.object.create({
1440
+ languageOptions: { globals: globalsObjLiteral },
1441
+ rules: typescript ? rules : void 0
1442
+ });
1443
+ eslintConfigs.push(globalsConfig);
1444
+ const svelteConfigProp = svelteConfigFile ? { svelteConfig: js.variables.createIdentifier("svelteConfig") } : {};
1445
+ if (typescript) {
1446
+ const svelteTSParserConfig = js.object.create({
1447
+ files: [
1448
+ "**/*.svelte",
1449
+ "**/*.svelte.ts",
1450
+ "**/*.svelte.js"
1451
+ ],
1452
+ languageOptions: { parserOptions: {
1453
+ projectService: true,
1454
+ extraFileExtensions: [".svelte"],
1455
+ parser: js.variables.createIdentifier("ts.parser"),
1456
+ ...svelteConfigProp
1457
+ } }
1458
+ });
1459
+ eslintConfigs.push(svelteTSParserConfig);
1460
+ } else {
1461
+ const svelteTSParserConfig = js.object.create({
1462
+ files: ["**/*.svelte", "**/*.svelte.js"],
1463
+ languageOptions: { parserOptions: { ...svelteConfigProp } }
1464
+ });
1465
+ eslintConfigs.push(svelteTSParserConfig);
1466
+ }
1467
+ const rulesOverride = js.object.create({ rules: {} });
1468
+ eslintConfigs.push(rulesOverride);
1469
+ if (rulesOverride.properties[0].type !== "Property") throw new Error("rulesOverride.properties[0].type !== \"Property\"");
1470
+ comments.add(rulesOverride.properties[0].key, {
1471
+ type: "Line",
1472
+ value: " Override or add rule settings here, such as:"
1473
+ });
1474
+ comments.add(rulesOverride.properties[0].key, {
1475
+ type: "Line",
1476
+ value: " 'svelte/button-has-type': 'error'"
1477
+ });
1478
+ const exportExpression = js.functions.createCall({
1479
+ name: "defineConfig",
1480
+ args: []
1481
+ });
1482
+ if (typescript) exportExpression.arguments.push(...eslintConfigs);
1483
+ else {
1484
+ const eslintArray = js.array.create();
1485
+ eslintConfigs.map((x) => js.array.append(eslintArray, x));
1486
+ exportExpression.arguments.push(eslintArray);
1487
+ }
1488
+ const { value: defaultExport } = js.exports.createDefault(ast, { fallback: exportExpression });
1489
+ if (defaultExport !== exportExpression) {
1490
+ R.warn("An eslint config is already defined. Skipping initialization.");
1491
+ return false;
1492
+ }
1493
+ if (typescript) js.imports.addDefault(ast, {
1494
+ from: "typescript-eslint",
1495
+ as: "ts"
1496
+ });
1497
+ js.imports.addDefault(ast, {
1498
+ from: "globals",
1499
+ as: "globals"
1500
+ });
1501
+ js.imports.addNamed(ast, {
1502
+ from: "eslint/config",
1503
+ imports: ["defineConfig"]
1504
+ });
1505
+ js.imports.addDefault(ast, {
1506
+ from: "eslint-plugin-svelte",
1507
+ as: "svelte"
1508
+ });
1509
+ js.imports.addDefault(ast, {
1510
+ from: "@eslint/js",
1511
+ as: "js"
1512
+ });
1513
+ js.imports.addNamed(ast, {
1514
+ from: "eslint/config",
1515
+ imports: ["includeIgnoreFile"]
1516
+ });
1517
+ js.imports.addDefault(ast, {
1518
+ from: "node:path",
1519
+ as: "path"
1520
+ });
1521
+ }));
1522
+ sv.file(".vscode/extensions.json", transforms.json(({ data, json }) => {
1523
+ json.arrayUpsert(data, "recommendations", "dbaeumer.vscode-eslint");
1524
+ }));
1525
+ if (prettierInstalled) sv.file("eslint.config.js", addEslintConfigPrettier);
1526
+ }
1527
+ });
1528
+ //#endregion
1529
+ //#region src/addons/experimental.ts
1530
+ const FEATURES = {
1531
+ async: {
1532
+ label: "async",
1533
+ hint: "await in components",
1534
+ path: "compilerOptions.experimental.async"
1535
+ },
1536
+ remoteFunctions: { label: "remote functions" },
1537
+ forkPreloads: {
1538
+ label: "forked preloading",
1539
+ off: true
1540
+ }
1541
+ };
1542
+ const options$4 = defineAddonOptions().add("features", {
1543
+ question: "Which experimental features do you want to enable?",
1544
+ type: "multiselect",
1545
+ default: Object.entries(FEATURES).filter(([, f]) => !f.off).map(([value]) => value),
1546
+ options: Object.entries(FEATURES).map(([value, { label, hint }]) => ({
1547
+ value,
1548
+ label,
1549
+ hint
1550
+ })),
1551
+ required: false
1552
+ }).build();
1553
+ var experimental_default = defineAddon({
1554
+ id: "experimental",
1555
+ shortDescription: "svelte & kit experimental features",
1556
+ homepage: "https://svelte.dev/docs/kit/configuration#experimental",
1557
+ options: options$4,
1558
+ run: ({ sv, cwd, options }) => {
1559
+ const config = {};
1560
+ for (const [name, f] of Object.entries(FEATURES)) {
1561
+ if (!options.features.includes(name)) continue;
1562
+ const keys = (f.path ?? `experimental.${name}`).split(".");
1563
+ let target = config;
1564
+ for (const key of keys.slice(0, -1)) target = target[key] ??= {};
1565
+ target[keys.at(-1)] = true;
1566
+ }
1567
+ if (Object.keys(config).length) svelteConfig.edit({
1568
+ sv,
1569
+ cwd
1570
+ }, ({ override }) => override(config));
1571
+ }
1572
+ });
1573
+ //#endregion
1574
+ //#region src/addons/mdsvex.ts
1575
+ var mdsvex_default = defineAddon({
1576
+ id: "mdsvex",
1577
+ shortDescription: "svelte + markdown",
1578
+ homepage: "https://mdsvex.pngwn.io",
1579
+ options: {},
1580
+ run: ({ sv, cwd }) => {
1581
+ sv.devDependency("mdsvex", "^0.12.7");
1582
+ svelteConfig.edit({
1583
+ sv,
1584
+ cwd
1585
+ }, ({ ast, property, override, js }) => {
1586
+ js.imports.addNamed(ast, {
1587
+ from: "mdsvex",
1588
+ imports: ["mdsvex"]
1589
+ });
1590
+ let preprocessorArray = property("preprocess", { fallback: js.array.create() });
1591
+ if (!(preprocessorArray.type === "ArrayExpression")) {
1592
+ const previousElement = preprocessorArray;
1593
+ preprocessorArray = js.array.create();
1594
+ js.array.append(preprocessorArray, previousElement);
1595
+ override({ preprocess: preprocessorArray });
1596
+ }
1597
+ const mdsvexCall = js.functions.createCall({
1598
+ name: "mdsvex",
1599
+ args: []
1600
+ });
1601
+ mdsvexCall.arguments.push(js.object.create({ extensions: [".svx", ".md"] }));
1602
+ js.array.append(preprocessorArray, mdsvexCall);
1603
+ const extensionsArray = property("extensions", { fallback: js.array.create() });
1604
+ js.array.append(extensionsArray, ".svelte");
1605
+ js.array.append(extensionsArray, ".svx");
1606
+ js.array.append(extensionsArray, ".md");
1607
+ });
1608
+ }
1609
+ });
1610
+ //#endregion
1611
+ //#region src/addons/paraglide.ts
1612
+ const DEFAULT_INLANG_PROJECT = {
1613
+ $schema: "https://inlang.com/schema/project-settings",
1614
+ modules: ["https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js", "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js"],
1615
+ "plugin.inlang.messageFormat": { pathPattern: "./messages/{locale}.json" }
1616
+ };
1617
+ const options$3 = defineAddonOptions().add("languageTags", {
1618
+ question: `Which languages would you like to support? ${color.optional("(e.g. en,de-ch)")}`,
1619
+ type: "string",
1620
+ default: "en, es",
1621
+ validate(input) {
1622
+ if (!input) return;
1623
+ const { invalidLanguageTags, validLanguageTags } = parseLanguageTagInput(input);
1624
+ if (invalidLanguageTags.length > 0) {
1625
+ if (invalidLanguageTags.length === 1) return `The input "${invalidLanguageTags[0]}" is not a valid IETF BCP 47 language tag`;
1626
+ else return `The inputs ${new Intl.ListFormat("en", {
1627
+ style: "long",
1628
+ type: "conjunction"
1629
+ }).format(invalidLanguageTags.map((x) => `"${x}"`))} are not valid BCP47 language tags`;
1630
+ }
1631
+ if (validLanguageTags.length === 0) return "Please enter at least one valid BCP47 language tag. Eg: en";
1632
+ }
1633
+ }).add("demo", {
1634
+ type: "boolean",
1635
+ default: true,
1636
+ question: "Do you want to include a demo?"
1637
+ }).build();
1638
+ var paraglide_default = defineAddon({
1639
+ id: "paraglide",
1640
+ shortDescription: "i18n",
1641
+ homepage: "https://inlang.com/m/gerre34r/library-inlang-paraglideJs",
1642
+ options: options$3,
1643
+ setup: ({ isKit, unsupported, runsAfter }) => {
1644
+ if (!isKit) unsupported("Requires SvelteKit");
1645
+ runsAfter("experimental");
1646
+ },
1647
+ run: ({ sv, options, file, language, directory, dependencyVersion }) => {
1648
+ const [ts] = createPrinter(language === "ts");
1649
+ const kitRange = dependencyVersion("@sveltejs/kit");
1650
+ const lib = resolveLibPrefix(kitRange);
1651
+ const pathType = isKit3(kitRange) ? "Path" : "Pathname";
1652
+ const paraglideOutDir = `${directory.lib}/paraglide`;
1653
+ sv.devDependency("@inlang/paraglide-js", "^2.18.2");
1654
+ sv.file(file.viteConfig, transforms.script(({ ast, js }) => {
1655
+ const vitePluginName = "paraglideVitePlugin";
1656
+ js.imports.addNamed(ast, {
1657
+ imports: [vitePluginName],
1658
+ from: "@inlang/paraglide-js"
1659
+ });
1660
+ js.vite.addPlugin(ast, { code: `${vitePluginName}({
1661
+ project: './project.inlang',
1662
+ outdir: './${paraglideOutDir}',
1663
+ emitTsDeclarations: true
1664
+ })` });
1665
+ }));
1666
+ sv.file(`src/hooks.${language}`, transforms.script(({ ast, comments, js }) => {
1667
+ js.imports.addNamed(ast, {
1668
+ from: `${lib}/paraglide/runtime.js`,
1669
+ imports: ["deLocalizeUrl"]
1670
+ });
1671
+ const expression = js.common.parseExpression("(request) => deLocalizeUrl(request.url).pathname");
1672
+ const rerouteIdentifier = js.variables.declaration(ast, {
1673
+ kind: "const",
1674
+ name: `reroute${language === "ts" ? ": Reroute" : ""}`,
1675
+ value: expression
1676
+ });
1677
+ const existingExport = js.exports.createNamed(ast, {
1678
+ name: "reroute",
1679
+ fallback: rerouteIdentifier
1680
+ });
1681
+ if (existingExport.declaration !== rerouteIdentifier) R.warn("Adding the reroute hook automatically failed. Add it manually");
1682
+ if (language === "ts") js.imports.addNamed(ast, {
1683
+ from: "@sveltejs/kit/hooks",
1684
+ imports: ["Reroute"],
1685
+ isType: true
1686
+ });
1687
+ else js.common.addJsDocTypeComment(existingExport, comments, { type: "import('@sveltejs/kit/hooks').Reroute" });
1688
+ }));
1689
+ sv.file(`src/hooks.server.${language}`, transforms.script(({ ast, comments, js }) => {
1690
+ js.imports.addNamed(ast, {
1691
+ from: `${lib}/paraglide/server.js`,
1692
+ imports: ["paraglideMiddleware"]
1693
+ });
1694
+ js.imports.addNamed(ast, {
1695
+ from: `${lib}/paraglide/runtime.js`,
1696
+ imports: ["getTextDirection"]
1697
+ });
1698
+ js.kit.addHooksHandle(ast, {
1699
+ language,
1700
+ newHandleName: "handleParaglide",
1701
+ handleContent: `({ event, resolve }) => paraglideMiddleware(event.request, ({ request, locale }) => {
1702
+ return resolve({ ...event, request }, {
1703
+ transformPageChunk: ({ html }) => html.replace('%paraglide.lang%', locale).replace('%paraglide.dir%', getTextDirection(locale))
1704
+ });
1705
+ });`,
1706
+ comments
1707
+ });
1708
+ }));
1709
+ sv.file("src/app.html", transforms.html(({ ast, html }) => {
1710
+ const htmlNode = ast.nodes.find((child) => child.type === "RegularElement" && child.name === "html");
1711
+ if (!htmlNode) {
1712
+ R.warn("Could not find <html> node in app.html. You'll need to add the language placeholder manually");
1713
+ return false;
1714
+ }
1715
+ html.addAttribute(htmlNode, "lang", "%paraglide.lang%");
1716
+ html.addAttribute(htmlNode, "dir", "%paraglide.dir%");
1717
+ }));
1718
+ sv.file(file.gitignore, transforms.text(({ content, text }) => {
1719
+ if (!content) return false;
1720
+ content = text.upsert(content, paraglideOutDir, { comment: "Paraglide" });
1721
+ content = text.upsert(content, "project.inlang/cache/");
1722
+ return content;
1723
+ }));
1724
+ sv.file("project.inlang/settings.json", transforms.json(({ data }) => {
1725
+ if (Object.keys(data).length > 0) return false;
1726
+ for (const key in DEFAULT_INLANG_PROJECT) data[key] = DEFAULT_INLANG_PROJECT[key];
1727
+ const { validLanguageTags } = parseLanguageTagInput(options.languageTags);
1728
+ data.baseLocale = validLanguageTags[0];
1729
+ data.locales = validLanguageTags;
1730
+ }));
1731
+ sv.file(`${directory.kitRoutes}/+layout.svelte`, transforms.svelteScript({ language }, ({ ast, svelte, js }) => {
1732
+ js.imports.addNamed(ast.instance.content, {
1733
+ imports: ["locales", "localizeHref"],
1734
+ from: `${lib}/paraglide/runtime.js`
1735
+ });
1736
+ js.imports.addNamed(ast.instance.content, {
1737
+ imports: ["page"],
1738
+ from: "$app/state"
1739
+ });
1740
+ js.imports.addNamed(ast.instance.content, {
1741
+ imports: ["resolve"],
1742
+ from: "$app/paths"
1743
+ });
1744
+ if (language === "ts") js.imports.addNamed(ast.instance.content, {
1745
+ imports: [pathType],
1746
+ from: "$app/types",
1747
+ isType: true
1748
+ });
1749
+ svelte.addFragment(ast, dedent`
1750
+ <div style="display:none">
1751
+ {#each locales as locale (locale)}
1752
+ <a href={resolve(localizeHref(page.url.pathname, { locale })${ts(` as ${pathType}`)})}>{locale}</a>
1753
+ {/each}
1754
+ </div>`, { language });
1755
+ }));
1756
+ if (options.demo) {
1757
+ sv.file(`${directory.kitRoutes}/demo/+page.svelte`, addToDemoPage("paraglide", language));
1758
+ sv.file(`${directory.kitRoutes}/demo/paraglide/+page.svelte`, transforms.svelteScript({ language }, ({ ast, svelte, js }) => {
1759
+ js.imports.addNamed(ast.instance.content, {
1760
+ imports: { m: "m" },
1761
+ from: `${lib}/paraglide/messages.js`
1762
+ });
1763
+ js.imports.addNamed(ast.instance.content, {
1764
+ imports: { setLocale: "setLocale" },
1765
+ from: `${lib}/paraglide/runtime.js`
1766
+ });
1767
+ let templateCode = "<h1>{m.hello_world({ name: 'SvelteKit User' })}</h1>";
1768
+ const { validLanguageTags } = parseLanguageTagInput(options.languageTags);
1769
+ const links = validLanguageTags.map((x) => `<button onclick={() => setLocale('${x}')}>${x}</button>`).join("");
1770
+ templateCode += `<div>${links}</div>`;
1771
+ templateCode += "<p>If you use VSCode, install the <a href=\"https://marketplace.visualstudio.com/items?itemName=inlang.vs-code-extension\" target=\"_blank\">Sherlock i18n extension</a> for a better i18n experience.</p>";
1772
+ svelte.addFragment(ast, templateCode);
1773
+ }));
1774
+ }
1775
+ const { validLanguageTags } = parseLanguageTagInput(options.languageTags);
1776
+ for (const languageTag of validLanguageTags) sv.file(`messages/${languageTag}.json`, transforms.json(({ data }) => {
1777
+ data["$schema"] = "https://inlang.com/schema/inlang-message-format";
1778
+ data.hello_world = `Hello, {name} from ${languageTag}!`;
1779
+ }));
1780
+ },
1781
+ nextSteps: () => [`Edit your messages in ${color.path("messages/en.json")}`, options$3.demo && `Visit ${color.route("/demo/paraglide")} route to view the demo`].filter((line) => Boolean(line))
1782
+ });
1783
+ const isValidLanguageTag = (languageTag) => RegExp("^((?<grandfathered>(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?<language>([A-Za-z]{2,3}(-(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?))(-(?<script>[A-Za-z]{4}))?(-(?<region>[A-Za-z]{2}|[0-9]{3}))?(-(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*))$").test(languageTag);
1784
+ function parseLanguageTagInput(input) {
1785
+ const probablyLanguageTags = input.replace(/[,:\s]/g, " ").split(" ").filter(Boolean).map((tag) => tag.toLowerCase());
1786
+ const validLanguageTags = [];
1787
+ const invalidLanguageTags = [];
1788
+ for (const tag of probablyLanguageTags) if (isValidLanguageTag(tag)) validLanguageTags.push(tag);
1789
+ else invalidLanguageTags.push(tag);
1790
+ return {
1791
+ validLanguageTags,
1792
+ invalidLanguageTags
1793
+ };
1794
+ }
1795
+ //#endregion
1796
+ //#region src/addons/playwright.ts
1797
+ var playwright_default = defineAddon({
1798
+ id: "playwright",
1799
+ shortDescription: "browser testing",
1800
+ homepage: "https://playwright.dev",
1801
+ options: {},
1802
+ run: ({ sv, language, file, isKit, directory }) => {
1803
+ sv.devDependency("@playwright/test", "^1.60.0");
1804
+ sv.file(file.package, transforms.json(({ data, json }) => {
1805
+ json.packageScriptsUpsert(data, "test:e2e", "playwright test");
1806
+ json.packageScriptsUpsert(data, "test:e2e", "playwright install", { mode: "prepend" });
1807
+ json.packageScriptsUpsert(data, "test", "npm run test:e2e");
1808
+ }));
1809
+ sv.file(file.gitignore, transforms.text(({ content, text }) => {
1810
+ if (!content) return false;
1811
+ return text.upsert(content, "test-results", { comment: "Playwright" });
1812
+ }));
1813
+ const testDir = isKit ? `${directory.kitRoutes}/demo/playwright` : directory.src;
1814
+ const testRoute = isKit ? "/demo/playwright" : "/";
1815
+ if (isKit) {
1816
+ sv.file(`${directory.kitRoutes}/demo/+page.svelte`, addToDemoPage("playwright", language));
1817
+ sv.file(`${testDir}/+page.svelte`, transforms.text(({ content }) => {
1818
+ if (content) return false;
1819
+ return dedent`
1820
+ <h1>Playwright e2e test demo</h1>
1821
+ `;
1822
+ }));
1823
+ }
1824
+ sv.file(`${testDir}/${isKit ? "page" : "app"}.svelte.e2e.${language}`, transforms.text(({ content }) => {
1825
+ if (content) return false;
1826
+ return dedent`
1827
+ import { expect, test } from '@playwright/test';
1828
+
1829
+ test('has expected h1', async ({ page }) => {
1830
+ await page.goto('${testRoute}');
1831
+ await expect(page.locator('h1')).toBeVisible();
1832
+ });
1833
+ `;
1834
+ }));
1835
+ sv.file(`playwright.config.${language}`, transforms.script(({ ast, js }) => {
1836
+ const defineConfig = js.common.parseExpression("defineConfig({})");
1837
+ const { value: defaultExport } = js.exports.createDefault(ast, { fallback: defineConfig });
1838
+ const config = {
1839
+ webServer: {
1840
+ command: "npm run build && npm run preview",
1841
+ port: 4173
1842
+ },
1843
+ testMatch: "**/*.e2e.{ts,js}"
1844
+ };
1845
+ if (defaultExport.type === "CallExpression" && defaultExport.arguments[0]?.type === "ObjectExpression") {
1846
+ js.imports.addNamed(ast, {
1847
+ imports: ["defineConfig"],
1848
+ from: "@playwright/test"
1849
+ });
1850
+ js.object.overrideProperties(defaultExport.arguments[0], config);
1851
+ } else if (defaultExport.type === "ObjectExpression") js.object.overrideProperties(defaultExport, config);
1852
+ else R.warn("Unexpected playwright config for playwright add-on. Could not update.");
1853
+ }));
1854
+ },
1855
+ nextSteps: ({ isKit, packageManager }) => {
1856
+ const steps = [];
1857
+ steps.push(`Run ${color.command(resolveCommandArray(packageManager, "execute-local", ["playwright", "install"]))} to download browsers`);
1858
+ if (isKit) steps.push(`Visit ${color.route("/demo/playwright")} to see the demo page`);
1859
+ steps.push(`Run ${color.command(resolveCommandArray(packageManager, "run", ["test:e2e"]))} to execute the example tests`);
1860
+ return steps;
1861
+ }
1862
+ });
1863
+ //#endregion
1864
+ //#region src/addons/prettier.ts
1865
+ var prettier_default = defineAddon({
1866
+ id: "prettier",
1867
+ shortDescription: "formatter",
1868
+ homepage: "https://prettier.io",
1869
+ options: {},
1870
+ run: ({ sv, dependencyVersion, file }) => {
1871
+ const tailwindcssInstalled = Boolean(dependencyVersion("tailwindcss"));
1872
+ if (tailwindcssInstalled) sv.devDependency("prettier-plugin-tailwindcss", "^0.8.0");
1873
+ sv.devDependency("prettier", "^3.8.3");
1874
+ sv.devDependency("prettier-plugin-svelte", "^4.1.0");
1875
+ sv.file(".prettierignore", transforms.text(({ content }) => {
1876
+ if (content) return false;
1877
+ return dedent`
1878
+ # Package Managers
1879
+ package-lock.json
1880
+ pnpm-lock.yaml
1881
+ yarn.lock
1882
+ bun.lock
1883
+ bun.lockb
1884
+
1885
+ # Miscellaneous
1886
+ /static/
1887
+ `;
1888
+ }));
1889
+ sv.file(prettierConfigPath(), createPrettierConfig({
1890
+ tailwind: tailwindcssInstalled,
1891
+ stylesheet: tailwindcssInstalled ? file.getRelative({ to: file.stylesheet }) : void 0
1892
+ }));
1893
+ const eslintInfo = checkEslint(dependencyVersion("eslint"));
1894
+ sv.file(file.package, transforms.json(({ data, json }) => {
1895
+ json.packageScriptsUpsert(data, "lint", "prettier --check .", { mode: "prepend" });
1896
+ json.packageScriptsUpsert(data, "format", "prettier --write .");
1897
+ }));
1898
+ sv.file(".vscode/extensions.json", transforms.json(({ data, json }) => {
1899
+ json.arrayUpsert(data, "recommendations", "esbenp.prettier-vscode");
1900
+ }));
1901
+ if (eslintInfo.installed && !eslintInfo.supported) R.warn(`An unsupported major version of ${color.warning("eslint")} was detected. Skipping ${color.warning("eslint-config-prettier")} installation.`);
1902
+ if (eslintInfo.supported) {
1903
+ sv.devDependency("eslint-config-prettier", "^10.1.8");
1904
+ sv.file("eslint.config.js", addEslintConfigPrettier);
1905
+ }
1906
+ }
1907
+ });
1908
+ function checkEslint(version) {
1909
+ if (!version) return {
1910
+ installed: false,
1911
+ supported: false
1912
+ };
1913
+ const supportedMajor = coerceVersion(ESLINT_VERSION).major;
1914
+ return {
1915
+ installed: true,
1916
+ supported: coerceVersion(version).major === supportedMajor
1917
+ };
1918
+ }
1919
+ //#endregion
1920
+ //#region src/addons/storybook.ts
1921
+ var storybook_default = defineAddon({
1922
+ id: "storybook",
1923
+ shortDescription: "frontend workshop",
1924
+ homepage: "https://storybook.js.org",
1925
+ options: {},
1926
+ setup: ({ runsAfter }) => {
1927
+ runsAfter("vitest");
1928
+ runsAfter("eslint");
1929
+ },
1930
+ run: async ({ sv }) => {
1931
+ const args = [
1932
+ `create-storybook@latest`,
1933
+ "--skip-install",
1934
+ "--no-dev"
1935
+ ];
1936
+ if (V.env.NODE_ENV?.toLowerCase() === "test") args.push(...["--no-features", "--yes"]);
1937
+ await sv.execute(args, "inherit");
1938
+ sv.devDependency(`@types/node`, getNodeTypesVersion());
1939
+ }
1940
+ });
1941
+ //#endregion
1942
+ //#region src/addons/sveltekit-adapter.ts
1943
+ const adapters = [
1944
+ {
1945
+ id: "auto",
1946
+ package: "@sveltejs/adapter-auto",
1947
+ version: "^8.0.0-next.3"
1948
+ },
1949
+ {
1950
+ id: "node",
1951
+ package: "@sveltejs/adapter-node",
1952
+ version: "^6.0.0-next.10"
1953
+ },
1954
+ {
1955
+ id: "static",
1956
+ package: "@sveltejs/adapter-static",
1957
+ version: "^4.0.0-next.4"
1958
+ },
1959
+ {
1960
+ id: "vercel",
1961
+ package: "@sveltejs/adapter-vercel",
1962
+ version: "^7.0.0-next.6"
1963
+ },
1964
+ {
1965
+ id: "cloudflare",
1966
+ package: "@sveltejs/adapter-cloudflare",
1967
+ version: "^8.0.0-next.6"
1968
+ },
1969
+ {
1970
+ id: "netlify",
1971
+ package: "@sveltejs/adapter-netlify",
1972
+ version: "^7.0.0-next.8"
1973
+ }
1974
+ ];
1975
+ /** The README blockquote pointing at the adapters docs, only relevant while on `adapter-auto`. */
1976
+ const ADAPTER_HINT_REGEX = /(?:\r?\n)*^> [^\r\n]*\(https:\/\/svelte\.dev\/docs\/kit\/adapters\)[^\r\n]*$/m;
1977
+ const options$2 = defineAddonOptions().add("adapter", {
1978
+ type: "select",
1979
+ question: "Which SvelteKit adapter would you like to use?",
1980
+ default: "auto",
1981
+ options: adapters.map((p) => ({
1982
+ value: p.id,
1983
+ label: p.id,
1984
+ hint: p.package
1985
+ }))
1986
+ }).add("cfTarget", {
1987
+ condition: (options) => options.adapter === "cloudflare",
1988
+ type: "select",
1989
+ question: "Are you deploying to Workers (assets) or Pages?",
1990
+ default: "workers",
1991
+ options: [{
1992
+ value: "workers",
1993
+ label: "Workers",
1994
+ hint: "Recommended way to deploy to Cloudflare"
1995
+ }, {
1996
+ value: "pages",
1997
+ label: "Pages"
1998
+ }]
1999
+ }).build();
2000
+ var sveltekit_adapter_default = defineAddon({
2001
+ id: "sveltekit-adapter",
2002
+ alias: "adapter",
2003
+ shortDescription: "deployment",
2004
+ homepage: "https://svelte.dev/docs/kit/adapters",
2005
+ options: options$2,
2006
+ setup: ({ isKit, unsupported }) => {
2007
+ if (!isKit) unsupported("Requires SvelteKit");
2008
+ },
2009
+ run: ({ sv, options, packageManager, file, cwd }) => {
2010
+ const adapter = adapters.find((a) => a.id === options.adapter);
2011
+ sv.file(file.package, transforms.json(({ data }) => {
2012
+ const devDeps = data["devDependencies"];
2013
+ for (const pkg of Object.keys(devDeps)) if (pkg.startsWith("@sveltejs/adapter-")) delete devDeps[pkg];
2014
+ if (options.adapter === "cloudflare") {
2015
+ const preview = options.cfTarget === "workers" ? "wrangler dev .svelte-kit/cloudflare/_worker.js --port 4173" : "wrangler pages dev .svelte-kit/cloudflare --port 4173";
2016
+ data.scripts.preview = preview;
2017
+ }
2018
+ }));
2019
+ sv.devDependency(adapter.package, adapter.version);
2020
+ if (adapter.package !== "@sveltejs/adapter-auto") sv.file("README.md", transforms.text(({ content }) => content.replace(ADAPTER_HINT_REGEX, "")));
2021
+ svelteConfig.edit({
2022
+ sv,
2023
+ cwd
2024
+ }, ({ ast, override, js }) => {
2025
+ const adapterImports = ast.body.filter((n) => n.type === "ImportDeclaration").find((importDecl) => typeof importDecl.source.value === "string" && importDecl.source.value.startsWith("@sveltejs/adapter-") && importDecl.importKind === "value");
2026
+ let adapterName = "adapter";
2027
+ if (adapterImports) {
2028
+ adapterImports.source.value = adapter.package;
2029
+ adapterImports.source.raw = void 0;
2030
+ adapterName = (adapterImports.specifiers?.find((s) => s.type === "ImportDefaultSpecifier")).local.name;
2031
+ } else js.imports.addDefault(ast, {
2032
+ from: adapter.package,
2033
+ as: adapterName
2034
+ });
2035
+ override({ adapter: js.functions.createCall({
2036
+ name: adapterName,
2037
+ args: [],
2038
+ useIdentifiers: true
2039
+ }) }, adapter.package === "@sveltejs/adapter-auto" ? void 0 : { dropLeadingComments: ["adapter"] });
2040
+ });
2041
+ if (adapter.package === "@sveltejs/adapter-cloudflare") {
2042
+ sv.devDependency("wrangler", "^4.97.0");
2043
+ if (packageManager === "pnpm") sv.file(file.findUp("pnpm-workspace.yaml"), pnpm.allowBuilds({
2044
+ cwd,
2045
+ packages: ["workerd"]
2046
+ }));
2047
+ const ext = fileExists(cwd, "wrangler.toml") ? "toml" : "jsonc";
2048
+ const applyWranglerConfig = (data) => {
2049
+ if (ext === "jsonc") data.$schema ??= "./node_modules/wrangler/config-schema.json";
2050
+ if (!data.name) {
2051
+ const pkg = loadPackageJson(cwd);
2052
+ data.name = sanitizeName(pkg.data.name, "wrangler");
2053
+ }
2054
+ data.compatibility_date ??= (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2055
+ data.compatibility_flags ??= [];
2056
+ if (!data.compatibility_flags.includes("nodejs_compat") && !data.compatibility_flags.includes("nodejs_als")) data.compatibility_flags.push("nodejs_als");
2057
+ switch (options.cfTarget) {
2058
+ case "workers":
2059
+ data.main = ".svelte-kit/cloudflare/_worker.js";
2060
+ data.assets ??= {};
2061
+ data.assets.binding = "ASSETS";
2062
+ data.assets.directory = ".svelte-kit/cloudflare";
2063
+ data.workers_dev = true;
2064
+ data.preview_urls = true;
2065
+ break;
2066
+ case "pages": data.pages_build_output_dir = ".svelte-kit/cloudflare";
2067
+ }
2068
+ };
2069
+ sv.file(`wrangler.${ext}`, ext === "toml" ? transforms.toml(({ data }) => applyWranglerConfig(data)) : transforms.json(({ data }) => applyWranglerConfig(data)));
2070
+ if (file.typeConfig) {
2071
+ sv.file(file.package, transforms.json(({ data, json }) => {
2072
+ json.packageScriptsUpsert(data, "gen", "wrangler types");
2073
+ json.packageScriptsUpsert(data, "check", "wrangler types --check", { mode: "prepend" });
2074
+ json.packageScriptsUpsert(data, "build", "wrangler types --check", { mode: "prepend" });
2075
+ }));
2076
+ sv.file(file.typeConfig, transforms.json(({ data }) => {
2077
+ data.compilerOptions ??= {};
2078
+ data.compilerOptions.types ??= [];
2079
+ data.compilerOptions.types.push("./worker-configuration.d.ts");
2080
+ }));
2081
+ sv.file("src/app.d.ts", transforms.script(({ ast, comments, js }) => {
2082
+ const platform = js.kit.addGlobalAppInterface(ast, { name: "Platform" });
2083
+ if (!platform) throw new Error("Failed detecting `platform` interface in `src/app.d.ts`");
2084
+ comments.remove((c) => c.type === "Line" && c.value.trim() === "interface Platform {}");
2085
+ platform.body.body.push(js.common.createTypeProperty("env", "Env"), js.common.createTypeProperty("ctx", "ExecutionContext"), js.common.createTypeProperty("caches", "CacheStorage"), js.common.createTypeProperty("cf", "IncomingRequestCfProperties", true));
2086
+ }));
2087
+ }
2088
+ }
2089
+ },
2090
+ nextSteps({ options, packageManager }) {
2091
+ const steps = [];
2092
+ if (options.adapter === "cloudflare") steps.push(`Run ${color.command(resolveCommandArray(packageManager, "run", ["gen"]))} to update ${color.addon("cloudflare")} types`);
2093
+ return steps;
2094
+ }
2095
+ });
2096
+ //#endregion
2097
+ //#region src/addons/tailwindcss.ts
2098
+ const plugins = [{
2099
+ id: "typography",
2100
+ package: "@tailwindcss/typography",
2101
+ version: "^0.5.19"
2102
+ }, {
2103
+ id: "forms",
2104
+ package: "@tailwindcss/forms",
2105
+ version: "^0.5.11"
2106
+ }];
2107
+ const options$1 = defineAddonOptions().add("plugins", {
2108
+ type: "multiselect",
2109
+ question: "Which plugins would you like to add?",
2110
+ options: plugins.map((p) => ({
2111
+ value: p.id,
2112
+ label: p.id,
2113
+ hint: p.package
2114
+ })),
2115
+ default: [],
2116
+ required: false
2117
+ }).build();
2118
+ var tailwindcss_default = defineAddon({
2119
+ id: "tailwindcss",
2120
+ alias: "tailwind",
2121
+ shortDescription: "css framework",
2122
+ homepage: "https://tailwindcss.com",
2123
+ options: options$1,
2124
+ run: ({ sv, options, file, isKit, directory, dependencyVersion, language }) => {
2125
+ const prettierInstalled = Boolean(dependencyVersion("prettier"));
2126
+ sv.devDependency("tailwindcss", "^4.3.0");
2127
+ sv.devDependency("@tailwindcss/vite", "^4.3.0");
2128
+ if (prettierInstalled) sv.devDependency("prettier-plugin-tailwindcss", "^0.8.0");
2129
+ for (const plugin of plugins) {
2130
+ if (!options.plugins.includes(plugin.id)) continue;
2131
+ sv.devDependency(plugin.package, plugin.version);
2132
+ }
2133
+ sv.file(file.viteConfig, transforms.script(({ ast, js }) => {
2134
+ const vitePluginName = "tailwindcss";
2135
+ js.imports.addDefault(ast, {
2136
+ as: vitePluginName,
2137
+ from: "@tailwindcss/vite"
2138
+ });
2139
+ js.vite.addPlugin(ast, {
2140
+ code: `${vitePluginName}()`,
2141
+ mode: "prepend"
2142
+ });
2143
+ }));
2144
+ sv.file(file.stylesheet, transforms.css(({ ast, css }) => {
2145
+ for (const plugin of plugins) {
2146
+ if (!options.plugins.includes(plugin.id)) continue;
2147
+ css.addAtRule(ast, {
2148
+ name: "plugin",
2149
+ params: `'${plugin.package}'`,
2150
+ append: false
2151
+ });
2152
+ }
2153
+ css.addAtRule(ast, {
2154
+ name: "import",
2155
+ params: `'tailwindcss'`,
2156
+ append: false
2157
+ });
2158
+ }));
2159
+ if (isKit) {
2160
+ const layoutSvelte = `${directory.kitRoutes}/+layout.svelte`;
2161
+ const stylesheetRelative = file.getRelative({
2162
+ from: layoutSvelte,
2163
+ to: file.stylesheet
2164
+ });
2165
+ sv.file(layoutSvelte, transforms.svelteScript({ language }, ({ ast, svelte, js }) => {
2166
+ js.imports.addEmpty(ast.instance.content, { from: stylesheetRelative });
2167
+ if (ast.fragment.nodes.length === 0) {
2168
+ const svelteVersion = dependencyVersion("svelte");
2169
+ if (!svelteVersion) throw new Error("Failed to determine svelte version");
2170
+ svelte.addSlot(ast, { svelteVersion });
2171
+ }
2172
+ }));
2173
+ } else {
2174
+ const appSvelte = `${directory.src}/App.svelte`;
2175
+ const stylesheetRelative = file.getRelative({
2176
+ from: appSvelte,
2177
+ to: file.stylesheet
2178
+ });
2179
+ sv.file(appSvelte, transforms.svelteScript({ language }, ({ ast, js }) => {
2180
+ js.imports.addEmpty(ast.instance.content, { from: stylesheetRelative });
2181
+ }));
2182
+ }
2183
+ sv.file(".vscode/settings.json", transforms.json(({ data }) => {
2184
+ data["files.associations"] ??= {};
2185
+ data["files.associations"]["*.css"] = "tailwindcss";
2186
+ }));
2187
+ sv.file(".vscode/extensions.json", transforms.json(({ data, json }) => {
2188
+ json.arrayUpsert(data, "recommendations", "bradlc.vscode-tailwindcss");
2189
+ }));
2190
+ if (prettierInstalled) sv.file(prettierConfigPath(), addPrettierTailwind({ stylesheet: file.getRelative({ to: file.stylesheet }) }));
2191
+ }
2192
+ });
2193
+ //#endregion
2194
+ //#region src/addons/vitest-addon.ts
2195
+ var _templateObject;
2196
+ const options = defineAddonOptions().add("usages", {
2197
+ question: "What do you want to use vitest for?",
2198
+ type: "multiselect",
2199
+ default: ["unit", "component"],
2200
+ options: [{
2201
+ value: "unit",
2202
+ label: "unit testing"
2203
+ }, {
2204
+ value: "component",
2205
+ label: "component testing"
2206
+ }],
2207
+ required: true
2208
+ }).build();
2209
+ let vitestV3Installed = false;
2210
+ //#endregion
2211
+ //#region src/addons/index.ts
2212
+ const officialAddons = {
2213
+ prettier: prettier_default,
2214
+ eslint: eslint_default,
2215
+ vitest: defineAddon({
2216
+ id: "vitest",
2217
+ shortDescription: "unit testing",
2218
+ homepage: "https://vitest.dev",
2219
+ options,
2220
+ run: ({ sv, file, language, directory, options, dependencyVersion }) => {
2221
+ const unitTesting = options.usages.includes("unit");
2222
+ const componentTesting = options.usages.includes("component");
2223
+ const vitestVersion = dependencyVersion("vitest");
2224
+ if (vitestVersion) vitestV3Installed = coerceVersion(vitestVersion).major === 3;
2225
+ sv.devDependency("vitest", "^4.1.8");
2226
+ if (componentTesting) {
2227
+ sv.devDependency("@vitest/browser-playwright", "^4.1.8");
2228
+ sv.devDependency("vitest-browser-svelte", "^2.1.1");
2229
+ sv.devDependency("playwright", "^1.60.0");
2230
+ }
2231
+ sv.file(file.package, transforms.json(({ data, json }) => {
2232
+ json.packageScriptsUpsert(data, "test:unit", "vitest");
2233
+ json.packageScriptsUpsert(data, "test", "npm run test:unit -- --run", { mode: "prepend" });
2234
+ }));
2235
+ const examplesDir = `${directory.lib}/vitest-examples`;
2236
+ const [ts] = createPrinter(language === "ts");
2237
+ if (unitTesting || componentTesting) sv.file(`${examplesDir}/greet.${language}`, transforms.text(({ content }) => {
2238
+ if (content) return false;
2239
+ return dedent`
2240
+ export function greet(name${ts(": string")})${ts(": string")} {
2241
+ return 'Hello, ' + name + '!';
2242
+ }
2243
+ `;
2244
+ }));
2245
+ if (unitTesting) sv.file(`${examplesDir}/greet.spec.${language}`, transforms.text(({ content }) => {
2246
+ if (content) return false;
2247
+ return dedent`
2248
+ import { describe, it, expect } from 'vitest';
2249
+ import { greet } from './greet';
2250
+
2251
+ describe('greet', () => {
2252
+ it('returns a greeting', () => {
2253
+ expect(greet('Svelte')).toBe('Hello, Svelte!');
2254
+ });
2255
+ });
2256
+ `;
2257
+ }));
2258
+ if (componentTesting) {
2259
+ sv.file(`${examplesDir}/Welcome.svelte`, transforms.text(({ content }) => {
2260
+ if (content) return false;
2261
+ return dedent(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n <script>\n import { greet } from './greet';\n\n let { host = 'SvelteKit', guest = 'Vitest' } = $props();\n <\/script>\n\n <h1>{greet(host)}</h1>\n <p>{greet(guest)}</p>\n "])));
2262
+ }));
2263
+ sv.file(`${examplesDir}/Welcome.svelte.spec.${language}`, transforms.text(({ content }) => {
2264
+ if (content) return false;
2265
+ return dedent`
2266
+ import { page } from 'vitest/browser';
2267
+ import { describe, expect, it } from 'vitest';
2268
+ import { render } from 'vitest-browser-svelte';
2269
+ import Welcome from './Welcome.svelte';
2270
+
2271
+ describe('Welcome.svelte', () => {
2272
+ it('renders greetings for host and guest', async () => {
2273
+ render(Welcome, { host: 'SvelteKit', guest: 'Vitest' });
2274
+
2275
+ await expect.element(page.getByRole('heading', { level: 1 })).toHaveTextContent('Hello, SvelteKit!');
2276
+ await expect.element(page.getByText('Hello, Vitest!')).toBeInTheDocument();
2277
+ });
2278
+ });
2279
+ `;
2280
+ }));
2281
+ }
2282
+ sv.file(file.viteConfig, transforms.script(({ ast, js }) => {
2283
+ const clientObjectExpression = js.object.create({
2284
+ extends: `./${file.viteConfig}`,
2285
+ test: {
2286
+ name: "client",
2287
+ browser: {
2288
+ enabled: true,
2289
+ provider: js.functions.createCall({
2290
+ name: "playwright",
2291
+ args: []
2292
+ }),
2293
+ instances: [{
2294
+ browser: "chromium",
2295
+ headless: true
2296
+ }]
2297
+ },
2298
+ include: ["src/**/*.svelte.{test,spec}.{js,ts}"],
2299
+ exclude: ["src/lib/server/**"]
2300
+ }
2301
+ });
2302
+ const serverObjectExpression = js.object.create({
2303
+ extends: `./${file.viteConfig}`,
2304
+ test: {
2305
+ name: "server",
2306
+ environment: "node",
2307
+ include: ["src/**/*.{test,spec}.{js,ts}"],
2308
+ exclude: ["src/**/*.svelte.{test,spec}.{js,ts}"]
2309
+ }
2310
+ });
2311
+ const viteConfig = js.vite.getConfig(ast);
2312
+ const testObject = js.object.property(viteConfig, {
2313
+ name: "test",
2314
+ fallback: js.object.create({ expect: { requireAssertions: true } })
2315
+ });
2316
+ const workspaceArray = js.object.property(testObject, {
2317
+ name: "projects",
2318
+ fallback: js.array.create()
2319
+ });
2320
+ if (componentTesting) js.array.append(workspaceArray, clientObjectExpression);
2321
+ if (unitTesting) js.array.append(workspaceArray, serverObjectExpression);
2322
+ if (componentTesting) js.imports.addNamed(ast, {
2323
+ imports: ["playwright"],
2324
+ from: "@vitest/browser-playwright"
2325
+ });
2326
+ const importName = "defineConfig";
2327
+ const { statement, alias } = js.imports.find(ast, {
2328
+ name: importName,
2329
+ from: "vite"
2330
+ });
2331
+ if (statement) {
2332
+ js.imports.addNamed(ast, {
2333
+ imports: { defineConfig: alias },
2334
+ from: "vitest/config"
2335
+ });
2336
+ js.imports.remove(ast, {
2337
+ name: importName,
2338
+ from: "vite",
2339
+ statement
2340
+ });
2341
+ }
2342
+ }));
2343
+ },
2344
+ nextSteps: ({ language, options }) => {
2345
+ const toReturn = [];
2346
+ if (vitestV3Installed) {
2347
+ if (options.usages.includes("component")) {
2348
+ toReturn.push(`Uninstall ${color.command("@vitest/browser")} package`);
2349
+ toReturn.push(`Update usage from ${color.command("'@vitest/browser...'")} to ${color.command("'vitest/browser'")}`);
2350
+ }
2351
+ toReturn.push(`${color.optional("Optional")} Check ${color.path("./vite.config.ts")} and remove duplicate project definitions`);
2352
+ toReturn.push(`${color.optional("Optional")} Remove ${color.path(`./vitest-setup-client.${language}`)} file`);
2353
+ }
2354
+ return toReturn;
2355
+ }
2356
+ }),
2357
+ playwright: playwright_default,
2358
+ tailwindcss: tailwindcss_default,
2359
+ enhancedImg: enhanced_img_default,
2360
+ sveltekitAdapter: sveltekit_adapter_default,
2361
+ drizzle: drizzle_default,
2362
+ betterAuth: better_auth_default,
2363
+ mdsvex: mdsvex_default,
2364
+ paraglide: paraglide_default,
2365
+ storybook: storybook_default,
2366
+ aiTools: ai_tools_default,
2367
+ experimental: experimental_default
2368
+ };
2369
+ function getAddonDetails(id) {
2370
+ const details = Object.values(officialAddons).find((a) => a.id === id);
2371
+ if (!details) throw new Error(`Invalid add-on: ${id}`);
2372
+ return details;
2373
+ }
2374
+ //#endregion
2375
+ export { officialAddons as n, getAddonDetails as t };