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.
- package/dist/addons-DB2wPeBQ.mjs +2375 -0
- package/dist/bin.d.mts +1 -1
- package/dist/bin.mjs +2469 -46
- package/dist/{engine-qp2yrGle.mjs → engine-Bw7mIv-H.mjs} +1443 -5932
- package/dist/{engine-_lPWeyCH.d.mts → engine-D2x5kvrf.d.mts} +146 -128
- package/dist/shared.json +3 -3
- package/dist/src/index.d.mts +3 -7
- package/dist/src/index.mjs +2 -1
- package/dist/src/testing.d.mts +26 -37
- package/dist/src/testing.mjs +61 -51
- package/dist/templates/addon/assets/tests/setup/global.js +2 -2
- package/dist/templates/addon/package.json +3 -3
- package/dist/templates/demo/files.types=checkjs.json +1 -1
- package/dist/templates/demo/files.types=none.json +1 -1
- package/dist/templates/demo/files.types=typescript.json +1 -1
- package/dist/templates/demo/package.json +1 -1
- package/dist/templates/library/package.json +1 -1
- package/dist/templates/minimal/package.json +1 -1
- package/package.json +16 -5
- package/dist/templates/demo/assets/src/lib/images/svelte-welcome.webp +0 -0
|
@@ -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 };
|