sv 0.11.2 → 0.11.3
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/add-BWQarWDB.mjs +7000 -0
- package/dist/add-nRRWTjzp.d.mts +35 -0
- package/dist/bin.mjs +25 -1845
- package/dist/{index-7xp7FWpU.d.mts → core-CnPhgWST.d.mts} +294 -46
- package/dist/lib/core.d.mts +2 -0
- package/dist/lib/core.mjs +3 -0
- package/dist/lib/index.d.mts +16 -0
- package/dist/lib/index.mjs +4 -4
- package/dist/lib/testing.d.mts +108 -0
- package/dist/lib/testing.mjs +970 -927
- package/dist/{package-manager-CySZrSUa.mjs → package-manager-DkCPtZM1.mjs} +219 -1328
- package/dist/shared.json +30 -4
- package/dist/templates/addon/assets/DOT-gitignore +27 -0
- package/dist/templates/addon/assets/src/index.js +52 -0
- package/dist/templates/addon/assets/tests/addon.test.js +50 -0
- package/dist/templates/addon/assets/tests/setup/global.js +14 -0
- package/dist/templates/addon/assets/tests/setup/suite.js +130 -0
- package/dist/templates/addon/assets/vitest.config.js +16 -0
- package/dist/templates/addon/files.types=checkjs.json +1 -0
- package/dist/templates/addon/files.types=none.json +1 -0
- package/dist/templates/addon/files.types=typescript.json +1 -0
- package/dist/templates/addon/meta.json +4 -0
- package/dist/templates/addon/package.json +32 -0
- package/dist/templates/demo/files.types=checkjs.json +5 -5
- package/dist/templates/demo/files.types=none.json +5 -5
- package/dist/templates/demo/files.types=typescript.json +5 -5
- 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/dist/{core-D715tamU.mjs → utils-DjBRIDJG.mjs} +26494 -25089
- package/package.json +7 -7
- package/dist/index.d.mts +0 -2
- package/dist/index2.d.mts +0 -65
- package/dist/lib/core/index.mjs +0 -4
- package/dist/official-P5OKi7QM.mjs +0 -2586
- package/dist/testing.d.mts +0 -50
|
@@ -1,2586 +0,0 @@
|
|
|
1
|
-
import { i as __toESM } from "./chunk-BjMGrMj9.mjs";
|
|
2
|
-
import { C as writeFile, D as q, S as readFile, T as resolveCommand, _ as fileExists, b as getPackageJson, g as commonFilePaths, h as sanitizeName, k as getSharedFiles, r as getUserAgent, u as any, w as detect, x as installPackages, y as getHighlighter } from "./package-manager-CySZrSUa.mjs";
|
|
3
|
-
import { A as property, B as parseStatement, C as addNamed, D as createCall, E as remove, F as contains, I as createLiteral, L as createSpread, M as create$1, N as addJsDocTypeComment, O as create, P as appendStatement, R as hasTypeProperty, S as addEmpty, T as find, V as dedent_default, _ as createDefault, a as toFragment, at as parseSvelte, b as declaration, c as createPrinter, ct as MagicString, d as defineAddonOptions, dt as require_picocolors, g as addHooksHandle, h as addGlobalAppInterface, it as parseScript, j as append, k as overrideProperties, lt as walk, m as getConfig, n as addSlot, nt as parseHtml, ot as parseToml, p as addPlugin, q as T, r as ensureScript, rt as parseJson, t as import_picocolors$1, tt as parseCss, u as defineAddon, v as createNamed, w as addNamespace, x as addDefault, y as createIdentifier, z as parseExpression } from "./core-D715tamU.mjs";
|
|
4
|
-
import fs from "node:fs";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import process from "node:process";
|
|
7
|
-
|
|
8
|
-
//#region lib/cli/utils/env.ts
|
|
9
|
-
const TESTING = process.env.NODE_ENV?.toLowerCase() === "test";
|
|
10
|
-
|
|
11
|
-
//#endregion
|
|
12
|
-
//#region lib/cli/add/workspace.ts
|
|
13
|
-
async function createWorkspace({ cwd: cwd$1, packageManager, override }) {
|
|
14
|
-
const resolvedCwd = path.resolve(cwd$1);
|
|
15
|
-
const typescript = any([commonFilePaths.jsconfig, commonFilePaths.tsconfig], { cwd: cwd$1 })?.endsWith(commonFilePaths.tsconfig) ?? false;
|
|
16
|
-
const viteConfigPath = path.join(resolvedCwd, commonFilePaths.viteConfigTS);
|
|
17
|
-
const viteConfig = fs.existsSync(viteConfigPath) ? commonFilePaths.viteConfigTS : commonFilePaths.viteConfig;
|
|
18
|
-
const svelteConfigPath = path.join(resolvedCwd, commonFilePaths.svelteConfigTS);
|
|
19
|
-
const svelteConfig = fs.existsSync(svelteConfigPath) ? commonFilePaths.svelteConfigTS : commonFilePaths.svelteConfig;
|
|
20
|
-
let dependencies = {};
|
|
21
|
-
if (override?.dependencies) dependencies = override.dependencies;
|
|
22
|
-
else {
|
|
23
|
-
let directory = resolvedCwd;
|
|
24
|
-
const workspaceRoot = findWorkspaceRoot(directory);
|
|
25
|
-
const { root } = path.parse(directory);
|
|
26
|
-
while (directory && directory.length >= workspaceRoot.length) {
|
|
27
|
-
if (fs.existsSync(path.join(directory, commonFilePaths.packageJson))) {
|
|
28
|
-
const { data: packageJson } = getPackageJson(directory);
|
|
29
|
-
dependencies = {
|
|
30
|
-
...packageJson.devDependencies,
|
|
31
|
-
...packageJson.dependencies,
|
|
32
|
-
...dependencies
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
if (root === directory) break;
|
|
36
|
-
directory = path.dirname(directory);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
for (const [key, value] of Object.entries(dependencies)) dependencies[key] = value.replaceAll(/[^\d|.]/g, "");
|
|
40
|
-
const kit = override?.kit ? override.kit : dependencies["@sveltejs/kit"] ? parseKitOptions(resolvedCwd) : void 0;
|
|
41
|
-
const stylesheet = kit ? `${kit.routesDirectory}/layout.css` : "src/app.css";
|
|
42
|
-
return {
|
|
43
|
-
cwd: resolvedCwd,
|
|
44
|
-
packageManager: packageManager ?? (await detect({ cwd: cwd$1 }))?.name ?? getUserAgent() ?? "npm",
|
|
45
|
-
typescript,
|
|
46
|
-
files: {
|
|
47
|
-
viteConfig,
|
|
48
|
-
svelteConfig,
|
|
49
|
-
stylesheet,
|
|
50
|
-
package: "package.json",
|
|
51
|
-
gitignore: ".gitignore",
|
|
52
|
-
prettierignore: ".prettierignore",
|
|
53
|
-
prettierrc: ".prettierrc",
|
|
54
|
-
eslintConfig: "eslint.config.js",
|
|
55
|
-
vscodeSettings: ".vscode/settings.json",
|
|
56
|
-
getRelative({ from, to }) {
|
|
57
|
-
from = from ?? "";
|
|
58
|
-
let relativePath = path.posix.relative(path.posix.dirname(from), to);
|
|
59
|
-
if (!relativePath.startsWith(".") && !relativePath.startsWith("/")) relativePath = `./${relativePath}`;
|
|
60
|
-
return relativePath;
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
kit,
|
|
64
|
-
dependencyVersion: (pkg) => dependencies[pkg]
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
function findWorkspaceRoot(cwd$1) {
|
|
68
|
-
const { root } = path.parse(cwd$1);
|
|
69
|
-
let directory = cwd$1;
|
|
70
|
-
while (directory && directory !== root) {
|
|
71
|
-
if (fs.existsSync(path.join(directory, commonFilePaths.packageJson))) {
|
|
72
|
-
if (fs.existsSync(path.join(directory, "pnpm-workspace.yaml"))) return directory;
|
|
73
|
-
const { data } = getPackageJson(directory);
|
|
74
|
-
if (data.workspaces) return directory;
|
|
75
|
-
}
|
|
76
|
-
directory = path.dirname(directory);
|
|
77
|
-
}
|
|
78
|
-
return cwd$1;
|
|
79
|
-
}
|
|
80
|
-
function parseKitOptions(cwd$1) {
|
|
81
|
-
const { ast } = parseScript(readFile(cwd$1, commonFilePaths.svelteConfig));
|
|
82
|
-
const defaultExport = ast.body.find((s) => s.type === "ExportDefaultDeclaration");
|
|
83
|
-
if (!defaultExport) throw Error(`Missing default export in \`${commonFilePaths.svelteConfig}\``);
|
|
84
|
-
let objectExpression;
|
|
85
|
-
if (defaultExport.declaration.type === "Identifier") {
|
|
86
|
-
const identifier = defaultExport.declaration;
|
|
87
|
-
for (const declaration$1 of ast.body) {
|
|
88
|
-
if (declaration$1.type !== "VariableDeclaration") continue;
|
|
89
|
-
const declarator = declaration$1.declarations.find((d) => d.type === "VariableDeclarator" && d.id.type === "Identifier" && d.id.name === identifier.name);
|
|
90
|
-
if (declarator?.init?.type !== "ObjectExpression") continue;
|
|
91
|
-
objectExpression = declarator.init;
|
|
92
|
-
}
|
|
93
|
-
if (!objectExpression) throw Error(`Unable to find svelte config object expression from \`${commonFilePaths.svelteConfig}\``);
|
|
94
|
-
} else if (defaultExport.declaration.type === "ObjectExpression") objectExpression = defaultExport.declaration;
|
|
95
|
-
if (!objectExpression) throw new Error(`Unexpected svelte config shape from \`${commonFilePaths.svelteConfig}\``);
|
|
96
|
-
const kit = property(objectExpression, {
|
|
97
|
-
name: "kit",
|
|
98
|
-
fallback: create({})
|
|
99
|
-
});
|
|
100
|
-
const files = property(kit, {
|
|
101
|
-
name: "files",
|
|
102
|
-
fallback: create({})
|
|
103
|
-
});
|
|
104
|
-
const routes = property(files, {
|
|
105
|
-
name: "routes",
|
|
106
|
-
fallback: createLiteral("")
|
|
107
|
-
});
|
|
108
|
-
const lib = property(files, {
|
|
109
|
-
name: "lib",
|
|
110
|
-
fallback: createLiteral("")
|
|
111
|
-
});
|
|
112
|
-
return {
|
|
113
|
-
routesDirectory: routes.value || "src/routes",
|
|
114
|
-
libDirectory: lib.value || "src/lib"
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
//#endregion
|
|
119
|
-
//#region lib/addons/install.ts
|
|
120
|
-
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
121
|
-
async function installAddon({ addons, cwd: cwd$1, options: options$7, packageManager = "npm" }) {
|
|
122
|
-
const workspace = await createWorkspace({
|
|
123
|
-
cwd: cwd$1,
|
|
124
|
-
packageManager
|
|
125
|
-
});
|
|
126
|
-
return await applyAddons({
|
|
127
|
-
addons,
|
|
128
|
-
workspace,
|
|
129
|
-
options: options$7,
|
|
130
|
-
addonSetupResults: setupAddons(Object.values(addons), workspace)
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
async function applyAddons({ addons, workspace, addonSetupResults, options: options$7 }) {
|
|
134
|
-
const filesToFormat = /* @__PURE__ */ new Set();
|
|
135
|
-
const allPnpmBuildDependencies = [];
|
|
136
|
-
const status = {};
|
|
137
|
-
const ordered = orderAddons(Object.entries(addons).map(([, addon]) => addon), addonSetupResults);
|
|
138
|
-
let hasFormatter = false;
|
|
139
|
-
for (const addon of ordered) {
|
|
140
|
-
const workspaceOptions = options$7[addon.id] || {};
|
|
141
|
-
const addonWorkspace = await createWorkspace({
|
|
142
|
-
cwd: workspace.cwd,
|
|
143
|
-
packageManager: workspace.packageManager
|
|
144
|
-
});
|
|
145
|
-
if (!hasFormatter) hasFormatter = !!addonWorkspace.dependencyVersion("prettier");
|
|
146
|
-
const { files, pnpmBuildDependencies, cancels } = await runAddon({
|
|
147
|
-
workspace: addonWorkspace,
|
|
148
|
-
workspaceOptions,
|
|
149
|
-
addon,
|
|
150
|
-
multiple: ordered.length > 1
|
|
151
|
-
});
|
|
152
|
-
files.forEach((f) => filesToFormat.add(f));
|
|
153
|
-
pnpmBuildDependencies.forEach((s) => allPnpmBuildDependencies.push(s));
|
|
154
|
-
if (cancels.length === 0) status[addon.id] = "success";
|
|
155
|
-
else status[addon.id] = cancels;
|
|
156
|
-
}
|
|
157
|
-
return {
|
|
158
|
-
filesToFormat: hasFormatter ? Array.from(filesToFormat) : [],
|
|
159
|
-
pnpmBuildDependencies: allPnpmBuildDependencies,
|
|
160
|
-
status
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
function setupAddons(addons, workspace) {
|
|
164
|
-
const addonSetupResults = {};
|
|
165
|
-
for (const addon of addons) {
|
|
166
|
-
const setupResult = {
|
|
167
|
-
unsupported: [],
|
|
168
|
-
dependsOn: [],
|
|
169
|
-
runsAfter: []
|
|
170
|
-
};
|
|
171
|
-
addon.setup?.({
|
|
172
|
-
...workspace,
|
|
173
|
-
dependsOn: (name) => {
|
|
174
|
-
setupResult.dependsOn.push(name);
|
|
175
|
-
setupResult.runsAfter.push(name);
|
|
176
|
-
},
|
|
177
|
-
unsupported: (reason) => setupResult.unsupported.push(reason),
|
|
178
|
-
runsAfter: (name) => setupResult.runsAfter.push(name)
|
|
179
|
-
});
|
|
180
|
-
addonSetupResults[addon.id] = setupResult;
|
|
181
|
-
}
|
|
182
|
-
return addonSetupResults;
|
|
183
|
-
}
|
|
184
|
-
async function runAddon({ addon, multiple, workspace, workspaceOptions }) {
|
|
185
|
-
const files = /* @__PURE__ */ new Set();
|
|
186
|
-
const options$7 = { ...workspaceOptions };
|
|
187
|
-
for (const [id, question] of Object.entries(addon.options)) if (question.condition?.(options$7) !== false) options$7[id] ??= question.default;
|
|
188
|
-
const dependencies = [];
|
|
189
|
-
const pnpmBuildDependencies = [];
|
|
190
|
-
const sv = {
|
|
191
|
-
file: (path$1, content) => {
|
|
192
|
-
try {
|
|
193
|
-
let fileContent = fileExists(workspace.cwd, path$1) ? readFile(workspace.cwd, path$1) : "";
|
|
194
|
-
fileContent = content(fileContent);
|
|
195
|
-
if (!fileContent) return fileContent;
|
|
196
|
-
writeFile(workspace, path$1, fileContent.replaceAll("<\\/script>", "<\/script>"));
|
|
197
|
-
files.add(path$1);
|
|
198
|
-
} catch (e) {
|
|
199
|
-
if (e instanceof Error) throw new Error(`Unable to process '${path$1}'. Reason: ${e.message}`);
|
|
200
|
-
throw e;
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
execute: async (commandArgs, stdio) => {
|
|
204
|
-
const { command, args } = resolveCommand(workspace.packageManager, "execute", commandArgs);
|
|
205
|
-
const addonPrefix = multiple ? `${addon.id}: ` : "";
|
|
206
|
-
const executedCommand = `${command} ${args.join(" ")}`;
|
|
207
|
-
if (!TESTING) T.step(`${addonPrefix}Running external command ${import_picocolors.default.gray(`(${executedCommand})`)}`);
|
|
208
|
-
if (workspace.packageManager === "npm") args.unshift("--yes");
|
|
209
|
-
try {
|
|
210
|
-
await q(command, args, {
|
|
211
|
-
nodeOptions: {
|
|
212
|
-
cwd: workspace.cwd,
|
|
213
|
-
stdio: TESTING ? "pipe" : stdio
|
|
214
|
-
},
|
|
215
|
-
throwOnError: true
|
|
216
|
-
});
|
|
217
|
-
} catch (error) {
|
|
218
|
-
const typedError = error;
|
|
219
|
-
throw new Error(`Failed to execute scripts '${executedCommand}': ${typedError.message}`, { cause: typedError.output });
|
|
220
|
-
}
|
|
221
|
-
},
|
|
222
|
-
dependency: (pkg, version) => {
|
|
223
|
-
dependencies.push({
|
|
224
|
-
pkg,
|
|
225
|
-
version,
|
|
226
|
-
dev: false
|
|
227
|
-
});
|
|
228
|
-
},
|
|
229
|
-
devDependency: (pkg, version) => {
|
|
230
|
-
dependencies.push({
|
|
231
|
-
pkg,
|
|
232
|
-
version,
|
|
233
|
-
dev: true
|
|
234
|
-
});
|
|
235
|
-
},
|
|
236
|
-
pnpmBuildDependency: (pkg) => {
|
|
237
|
-
pnpmBuildDependencies.push(pkg);
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
const cancels = [];
|
|
241
|
-
await addon.run({
|
|
242
|
-
cancel: (reason) => {
|
|
243
|
-
cancels.push(reason);
|
|
244
|
-
},
|
|
245
|
-
...workspace,
|
|
246
|
-
options: options$7,
|
|
247
|
-
sv
|
|
248
|
-
});
|
|
249
|
-
if (cancels.length === 0) {
|
|
250
|
-
const pkgPath = installPackages(dependencies, workspace);
|
|
251
|
-
files.add(pkgPath);
|
|
252
|
-
}
|
|
253
|
-
return {
|
|
254
|
-
files: Array.from(files),
|
|
255
|
-
pnpmBuildDependencies,
|
|
256
|
-
cancels
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
function orderAddons(addons, setupResults) {
|
|
260
|
-
return addons.sort((a, b) => {
|
|
261
|
-
return setupResults[a.id]?.runsAfter?.length - setupResults[b.id]?.runsAfter?.length;
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
//#endregion
|
|
266
|
-
//#region lib/addons/devtools-json/index.ts
|
|
267
|
-
var devtools_json_default = defineAddon({
|
|
268
|
-
id: "devtools-json",
|
|
269
|
-
shortDescription: "devtools json",
|
|
270
|
-
homepage: "https://github.com/ChromeDevTools/vite-plugin-devtools-json",
|
|
271
|
-
options: {},
|
|
272
|
-
run: ({ sv, files }) => {
|
|
273
|
-
sv.devDependency("vite-plugin-devtools-json", "^1.0.0");
|
|
274
|
-
sv.file(files.viteConfig, (content) => {
|
|
275
|
-
const { ast, generateCode } = parseScript(content);
|
|
276
|
-
const vitePluginName = "devtoolsJson";
|
|
277
|
-
addDefault(ast, {
|
|
278
|
-
as: vitePluginName,
|
|
279
|
-
from: "vite-plugin-devtools-json"
|
|
280
|
-
});
|
|
281
|
-
addPlugin(ast, { code: `${vitePluginName}()` });
|
|
282
|
-
return generateCode();
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
//#endregion
|
|
288
|
-
//#region lib/addons/common.ts
|
|
289
|
-
function addEslintConfigPrettier(content) {
|
|
290
|
-
const { ast, generateCode } = parseScript(content);
|
|
291
|
-
const sveltePluginImport = ast.body.filter((n) => n.type === "ImportDeclaration").find((n) => n.type === "ImportDeclaration" && n.source.value === "eslint-plugin-svelte" && n.specifiers?.some((n$1) => n$1.type === "ImportDefaultSpecifier"));
|
|
292
|
-
let svelteImportName;
|
|
293
|
-
for (const specifier of sveltePluginImport?.specifiers ?? []) if (specifier.type === "ImportDefaultSpecifier" && specifier.local?.name) svelteImportName = specifier.local.name;
|
|
294
|
-
svelteImportName ??= "svelte";
|
|
295
|
-
addDefault(ast, {
|
|
296
|
-
from: "eslint-plugin-svelte",
|
|
297
|
-
as: svelteImportName
|
|
298
|
-
});
|
|
299
|
-
addDefault(ast, {
|
|
300
|
-
from: "eslint-config-prettier",
|
|
301
|
-
as: "prettier"
|
|
302
|
-
});
|
|
303
|
-
const fallbackConfig = parseExpression("[]");
|
|
304
|
-
const eslintConfig = createDefault(ast, { fallback: fallbackConfig }).value;
|
|
305
|
-
if (eslintConfig.type !== "ArrayExpression" && eslintConfig.type !== "CallExpression") return content;
|
|
306
|
-
const prettier = parseExpression("prettier");
|
|
307
|
-
const sveltePrettierConfig = parseExpression(`${svelteImportName}.configs.prettier`);
|
|
308
|
-
const configSpread = createSpread(sveltePrettierConfig);
|
|
309
|
-
const nodesToInsert = [];
|
|
310
|
-
if (!contains(eslintConfig, prettier)) nodesToInsert.push(prettier);
|
|
311
|
-
if (!contains(eslintConfig, configSpread)) nodesToInsert.push(configSpread);
|
|
312
|
-
const elements = eslintConfig.type === "ArrayExpression" ? eslintConfig.elements : eslintConfig.arguments;
|
|
313
|
-
const idx = elements.findIndex((el) => el?.type === "SpreadElement" && el.argument.type === "MemberExpression" && el.argument.object.type === "MemberExpression" && el.argument.object.property.type === "Identifier" && el.argument.object.property.name === "configs" && el.argument.object.object.type === "Identifier" && el.argument.object.object.name === svelteImportName);
|
|
314
|
-
if (idx !== -1) elements.splice(idx + 1, 0, ...nodesToInsert);
|
|
315
|
-
else elements.push(...nodesToInsert);
|
|
316
|
-
return generateCode();
|
|
317
|
-
}
|
|
318
|
-
function addToDemoPage(existingContent, path$1, langTs) {
|
|
319
|
-
const { ast, generateCode } = parseSvelte(existingContent);
|
|
320
|
-
for (const node of ast.fragment.nodes) if (node.type === "RegularElement") {
|
|
321
|
-
const hrefAttribute = node.attributes.find((x) => x.type === "Attribute" && x.name === "href");
|
|
322
|
-
if (!hrefAttribute || !hrefAttribute.value) continue;
|
|
323
|
-
if (!Array.isArray(hrefAttribute.value)) continue;
|
|
324
|
-
if (hrefAttribute.value.some((x) => x.type === "Text" && x.data.includes(`/demo/${path$1}`))) return existingContent;
|
|
325
|
-
}
|
|
326
|
-
addNamed(ensureScript(ast, { langTs }), {
|
|
327
|
-
imports: ["resolve"],
|
|
328
|
-
from: "$app/paths"
|
|
329
|
-
});
|
|
330
|
-
ast.fragment.nodes.unshift(...toFragment(`<a href={resolve('/demo/${path$1}')}>${path$1}</a>`));
|
|
331
|
-
ast.fragment.nodes.unshift();
|
|
332
|
-
return generateCode();
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* Returns the corresponding `@types/node` version for the version of Node.js running in the current process.
|
|
336
|
-
*
|
|
337
|
-
* If the installed version of Node.js is from a `Current` release, then the major is decremented to
|
|
338
|
-
* the nearest `LTS` release version.
|
|
339
|
-
*/
|
|
340
|
-
function getNodeTypesVersion() {
|
|
341
|
-
const nodeVersion = process.versions.node;
|
|
342
|
-
const isDenoOrBun = Boolean(process.versions.deno ?? process.versions.bun);
|
|
343
|
-
const [major] = nodeVersion.split(".");
|
|
344
|
-
const majorNum = Number(major);
|
|
345
|
-
const isEvenMajor = majorNum % 2 === 0;
|
|
346
|
-
if (!!process.release.lts || isDenoOrBun && isEvenMajor) return `^${major}`;
|
|
347
|
-
return `^${isEvenMajor ? majorNum - 2 : majorNum - 1}`;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
//#endregion
|
|
351
|
-
//#region lib/addons/drizzle/index.ts
|
|
352
|
-
const PORTS = {
|
|
353
|
-
mysql: "3306",
|
|
354
|
-
postgresql: "5432",
|
|
355
|
-
sqlite: ""
|
|
356
|
-
};
|
|
357
|
-
const options$6 = defineAddonOptions().add("database", {
|
|
358
|
-
question: "Which database would you like to use?",
|
|
359
|
-
type: "select",
|
|
360
|
-
default: "sqlite",
|
|
361
|
-
options: [
|
|
362
|
-
{
|
|
363
|
-
value: "postgresql",
|
|
364
|
-
label: "PostgreSQL"
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
value: "mysql",
|
|
368
|
-
label: "MySQL"
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
value: "sqlite",
|
|
372
|
-
label: "SQLite"
|
|
373
|
-
}
|
|
374
|
-
]
|
|
375
|
-
}).add("postgresql", {
|
|
376
|
-
question: "Which PostgreSQL client would you like to use?",
|
|
377
|
-
type: "select",
|
|
378
|
-
group: "client",
|
|
379
|
-
default: "postgres.js",
|
|
380
|
-
options: [{
|
|
381
|
-
value: "postgres.js",
|
|
382
|
-
label: "Postgres.JS",
|
|
383
|
-
hint: "recommended for most users"
|
|
384
|
-
}, {
|
|
385
|
-
value: "neon",
|
|
386
|
-
label: "Neon",
|
|
387
|
-
hint: "popular hosted platform"
|
|
388
|
-
}],
|
|
389
|
-
condition: ({ database }) => database === "postgresql"
|
|
390
|
-
}).add("mysql", {
|
|
391
|
-
question: "Which MySQL client would you like to use?",
|
|
392
|
-
type: "select",
|
|
393
|
-
group: "client",
|
|
394
|
-
default: "mysql2",
|
|
395
|
-
options: [{
|
|
396
|
-
value: "mysql2",
|
|
397
|
-
hint: "recommended for most users"
|
|
398
|
-
}, {
|
|
399
|
-
value: "planetscale",
|
|
400
|
-
label: "PlanetScale",
|
|
401
|
-
hint: "popular hosted platform"
|
|
402
|
-
}],
|
|
403
|
-
condition: ({ database }) => database === "mysql"
|
|
404
|
-
}).add("sqlite", {
|
|
405
|
-
question: "Which SQLite client would you like to use?",
|
|
406
|
-
type: "select",
|
|
407
|
-
group: "client",
|
|
408
|
-
default: "libsql",
|
|
409
|
-
options: [
|
|
410
|
-
{
|
|
411
|
-
value: "better-sqlite3",
|
|
412
|
-
hint: "for traditional Node environments"
|
|
413
|
-
},
|
|
414
|
-
{
|
|
415
|
-
value: "libsql",
|
|
416
|
-
label: "libSQL",
|
|
417
|
-
hint: "for serverless environments"
|
|
418
|
-
},
|
|
419
|
-
{
|
|
420
|
-
value: "turso",
|
|
421
|
-
label: "Turso",
|
|
422
|
-
hint: "popular hosted platform"
|
|
423
|
-
}
|
|
424
|
-
],
|
|
425
|
-
condition: ({ database }) => database === "sqlite"
|
|
426
|
-
}).add("docker", {
|
|
427
|
-
question: "Do you want to run the database locally with docker-compose?",
|
|
428
|
-
default: false,
|
|
429
|
-
type: "boolean",
|
|
430
|
-
condition: ({ database, mysql, postgresql }) => database === "mysql" && mysql === "mysql2" || database === "postgresql" && postgresql === "postgres.js"
|
|
431
|
-
}).build();
|
|
432
|
-
var drizzle_default = defineAddon({
|
|
433
|
-
id: "drizzle",
|
|
434
|
-
shortDescription: "database orm",
|
|
435
|
-
homepage: "https://orm.drizzle.team",
|
|
436
|
-
options: options$6,
|
|
437
|
-
setup: ({ kit, unsupported, runsAfter }) => {
|
|
438
|
-
runsAfter("prettier");
|
|
439
|
-
if (!kit) return unsupported("Requires SvelteKit");
|
|
440
|
-
},
|
|
441
|
-
run: ({ sv, typescript, options: options$7, kit, dependencyVersion, cwd: cwd$1, cancel, files }) => {
|
|
442
|
-
if (!kit) throw new Error("SvelteKit is required");
|
|
443
|
-
const ext = typescript ? "ts" : "js";
|
|
444
|
-
const baseDBPath = path.resolve(cwd$1, kit.libDirectory, "server", "db");
|
|
445
|
-
const paths = {
|
|
446
|
-
"drizzle config": path.resolve(cwd$1, `drizzle.config.${ext}`),
|
|
447
|
-
"database schema": path.resolve(baseDBPath, `schema.${ext}`),
|
|
448
|
-
database: path.resolve(baseDBPath, `index.${ext}`)
|
|
449
|
-
};
|
|
450
|
-
for (const [fileType, filePath] of Object.entries(paths)) if (fs.existsSync(filePath)) return cancel(`Preexisting ${fileType} file at '${filePath}'`);
|
|
451
|
-
sv.devDependency("drizzle-orm", "^0.45.0");
|
|
452
|
-
sv.devDependency("drizzle-kit", "^0.31.8");
|
|
453
|
-
sv.devDependency("@types/node", getNodeTypesVersion());
|
|
454
|
-
if (options$7.mysql === "mysql2") sv.dependency("mysql2", "^3.15.3");
|
|
455
|
-
if (options$7.mysql === "planetscale") sv.dependency("@planetscale/database", "^1.19.0");
|
|
456
|
-
if (options$7.postgresql === "neon") sv.dependency("@neondatabase/serverless", "^1.0.2");
|
|
457
|
-
if (options$7.postgresql === "postgres.js") sv.dependency("postgres", "^3.4.7");
|
|
458
|
-
if (options$7.sqlite === "better-sqlite3") {
|
|
459
|
-
sv.dependency("better-sqlite3", "^12.5.0");
|
|
460
|
-
sv.devDependency("@types/better-sqlite3", "^7.6.13");
|
|
461
|
-
sv.pnpmBuildDependency("better-sqlite3");
|
|
462
|
-
}
|
|
463
|
-
if (options$7.sqlite === "libsql" || options$7.sqlite === "turso") sv.devDependency("@libsql/client", "^0.15.15");
|
|
464
|
-
sv.file(".env", (content) => generateEnvFileContent(content, options$7));
|
|
465
|
-
sv.file(".env.example", (content) => generateEnvFileContent(content, options$7));
|
|
466
|
-
if (options$7.docker && (options$7.mysql === "mysql2" || options$7.postgresql === "postgres.js")) {
|
|
467
|
-
const composeFileOptions = [
|
|
468
|
-
"docker-compose.yml",
|
|
469
|
-
"docker-compose.yaml",
|
|
470
|
-
"compose.yaml"
|
|
471
|
-
];
|
|
472
|
-
let composeFile = "";
|
|
473
|
-
for (const option of composeFileOptions) {
|
|
474
|
-
composeFile = option;
|
|
475
|
-
if (fs.existsSync(path.resolve(cwd$1, option))) break;
|
|
476
|
-
}
|
|
477
|
-
if (composeFile === "") throw new Error("unreachable state...");
|
|
478
|
-
sv.file(composeFile, (content) => {
|
|
479
|
-
if (content.length > 0) return content;
|
|
480
|
-
const imageName = options$7.database === "mysql" ? "mysql" : "postgres";
|
|
481
|
-
const port = PORTS[options$7.database];
|
|
482
|
-
const USER = "root";
|
|
483
|
-
const PASSWORD = "mysecretpassword";
|
|
484
|
-
const DB_NAME = "local";
|
|
485
|
-
let dbSpecificContent = "";
|
|
486
|
-
if (options$7.mysql === "mysql2") dbSpecificContent = `
|
|
487
|
-
MYSQL_ROOT_PASSWORD: ${PASSWORD}
|
|
488
|
-
MYSQL_DATABASE: ${DB_NAME}
|
|
489
|
-
volumes:
|
|
490
|
-
- mysqldata:/var/lib/mysql
|
|
491
|
-
volumes:
|
|
492
|
-
mysqldata:
|
|
493
|
-
`;
|
|
494
|
-
if (options$7.postgresql === "postgres.js") dbSpecificContent = `
|
|
495
|
-
POSTGRES_USER: ${USER}
|
|
496
|
-
POSTGRES_PASSWORD: ${PASSWORD}
|
|
497
|
-
POSTGRES_DB: ${DB_NAME}
|
|
498
|
-
volumes:
|
|
499
|
-
- pgdata:/var/lib/postgresql
|
|
500
|
-
volumes:
|
|
501
|
-
pgdata:
|
|
502
|
-
`;
|
|
503
|
-
content = dedent_default`
|
|
504
|
-
services:
|
|
505
|
-
db:
|
|
506
|
-
image: ${imageName}
|
|
507
|
-
restart: always
|
|
508
|
-
ports:
|
|
509
|
-
- ${port}:${port}
|
|
510
|
-
environment: ${dbSpecificContent}
|
|
511
|
-
`;
|
|
512
|
-
return content;
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
sv.file(files.package, (content) => {
|
|
516
|
-
const { data, generateCode } = parseJson(content);
|
|
517
|
-
data.scripts ??= {};
|
|
518
|
-
const scripts = data.scripts;
|
|
519
|
-
if (options$7.docker) scripts["db:start"] ??= "docker compose up";
|
|
520
|
-
scripts["db:push"] ??= "drizzle-kit push";
|
|
521
|
-
scripts["db:generate"] ??= "drizzle-kit generate";
|
|
522
|
-
scripts["db:migrate"] ??= "drizzle-kit migrate";
|
|
523
|
-
scripts["db:studio"] ??= "drizzle-kit studio";
|
|
524
|
-
return generateCode();
|
|
525
|
-
});
|
|
526
|
-
if (Boolean(dependencyVersion("prettier"))) sv.file(files.prettierignore, (content) => {
|
|
527
|
-
if (!content.includes(`/drizzle/`)) return content.trimEnd() + "\n/drizzle/";
|
|
528
|
-
return content;
|
|
529
|
-
});
|
|
530
|
-
if (options$7.database === "sqlite") sv.file(files.gitignore, (content) => {
|
|
531
|
-
if (content.length === 0) return content;
|
|
532
|
-
if (!content.includes("\n*.db")) content = content.trimEnd() + "\n\n# SQLite\n*.db";
|
|
533
|
-
return content;
|
|
534
|
-
});
|
|
535
|
-
sv.file(paths["drizzle config"], (content) => {
|
|
536
|
-
const { ast, generateCode } = parseScript(content);
|
|
537
|
-
addNamed(ast, {
|
|
538
|
-
from: "drizzle-kit",
|
|
539
|
-
imports: { defineConfig: "defineConfig" }
|
|
540
|
-
});
|
|
541
|
-
ast.body.push(parseStatement("if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set');"));
|
|
542
|
-
createDefault(ast, { fallback: parseExpression(`
|
|
543
|
-
defineConfig({
|
|
544
|
-
schema: "./src/lib/server/db/schema.${typescript ? "ts" : "js"}",
|
|
545
|
-
dialect: "${options$7.sqlite === "turso" ? "turso" : options$7.database}",
|
|
546
|
-
dbCredentials: {
|
|
547
|
-
${options$7.sqlite === "turso" ? "authToken: process.env.DATABASE_AUTH_TOKEN," : ""}
|
|
548
|
-
url: process.env.DATABASE_URL
|
|
549
|
-
},
|
|
550
|
-
verbose: true,
|
|
551
|
-
strict: true
|
|
552
|
-
})`) });
|
|
553
|
-
return generateCode();
|
|
554
|
-
});
|
|
555
|
-
sv.file(paths["database schema"], (content) => {
|
|
556
|
-
const { ast, generateCode } = parseScript(content);
|
|
557
|
-
let userSchemaExpression;
|
|
558
|
-
if (options$7.database === "sqlite") {
|
|
559
|
-
addNamed(ast, {
|
|
560
|
-
from: "drizzle-orm/sqlite-core",
|
|
561
|
-
imports: [
|
|
562
|
-
"integer",
|
|
563
|
-
"sqliteTable",
|
|
564
|
-
"text"
|
|
565
|
-
]
|
|
566
|
-
});
|
|
567
|
-
userSchemaExpression = parseExpression(`sqliteTable('user', {
|
|
568
|
-
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
569
|
-
age: integer('age')
|
|
570
|
-
})`);
|
|
571
|
-
}
|
|
572
|
-
if (options$7.database === "mysql") {
|
|
573
|
-
addNamed(ast, {
|
|
574
|
-
from: "drizzle-orm/mysql-core",
|
|
575
|
-
imports: [
|
|
576
|
-
"mysqlTable",
|
|
577
|
-
"serial",
|
|
578
|
-
"int"
|
|
579
|
-
]
|
|
580
|
-
});
|
|
581
|
-
userSchemaExpression = parseExpression(`mysqlTable('user', {
|
|
582
|
-
id: serial('id').primaryKey(),
|
|
583
|
-
age: int('age'),
|
|
584
|
-
})`);
|
|
585
|
-
}
|
|
586
|
-
if (options$7.database === "postgresql") {
|
|
587
|
-
addNamed(ast, {
|
|
588
|
-
from: "drizzle-orm/pg-core",
|
|
589
|
-
imports: [
|
|
590
|
-
"pgTable",
|
|
591
|
-
"serial",
|
|
592
|
-
"integer"
|
|
593
|
-
]
|
|
594
|
-
});
|
|
595
|
-
userSchemaExpression = parseExpression(`pgTable('user', {
|
|
596
|
-
id: serial('id').primaryKey(),
|
|
597
|
-
age: integer('age'),
|
|
598
|
-
})`);
|
|
599
|
-
}
|
|
600
|
-
if (!userSchemaExpression) throw new Error("unreachable state...");
|
|
601
|
-
const userIdentifier = declaration(ast, {
|
|
602
|
-
kind: "const",
|
|
603
|
-
name: "user",
|
|
604
|
-
value: userSchemaExpression
|
|
605
|
-
});
|
|
606
|
-
createNamed(ast, {
|
|
607
|
-
name: "user",
|
|
608
|
-
fallback: userIdentifier
|
|
609
|
-
});
|
|
610
|
-
return generateCode();
|
|
611
|
-
});
|
|
612
|
-
sv.file(paths["database"], (content) => {
|
|
613
|
-
const { ast, generateCode } = parseScript(content);
|
|
614
|
-
addNamed(ast, {
|
|
615
|
-
from: "$env/dynamic/private",
|
|
616
|
-
imports: ["env"]
|
|
617
|
-
});
|
|
618
|
-
addNamespace(ast, {
|
|
619
|
-
from: "./schema",
|
|
620
|
-
as: "schema"
|
|
621
|
-
});
|
|
622
|
-
const dbURLCheck = parseStatement("if (!env.DATABASE_URL) throw new Error('DATABASE_URL is not set');");
|
|
623
|
-
ast.body.push(dbURLCheck);
|
|
624
|
-
let clientExpression;
|
|
625
|
-
if (options$7.sqlite === "better-sqlite3") {
|
|
626
|
-
addDefault(ast, {
|
|
627
|
-
from: "better-sqlite3",
|
|
628
|
-
as: "Database"
|
|
629
|
-
});
|
|
630
|
-
addNamed(ast, {
|
|
631
|
-
from: "drizzle-orm/better-sqlite3",
|
|
632
|
-
imports: ["drizzle"]
|
|
633
|
-
});
|
|
634
|
-
clientExpression = parseExpression("new Database(env.DATABASE_URL)");
|
|
635
|
-
}
|
|
636
|
-
if (options$7.sqlite === "libsql" || options$7.sqlite === "turso") {
|
|
637
|
-
addNamed(ast, {
|
|
638
|
-
from: "@libsql/client",
|
|
639
|
-
imports: ["createClient"]
|
|
640
|
-
});
|
|
641
|
-
addNamed(ast, {
|
|
642
|
-
from: "drizzle-orm/libsql",
|
|
643
|
-
imports: ["drizzle"]
|
|
644
|
-
});
|
|
645
|
-
if (options$7.sqlite === "turso") {
|
|
646
|
-
addNamed(ast, {
|
|
647
|
-
from: "$app/environment",
|
|
648
|
-
imports: ["dev"]
|
|
649
|
-
});
|
|
650
|
-
const authTokenCheck = parseStatement("if (!dev && !env.DATABASE_AUTH_TOKEN) throw new Error('DATABASE_AUTH_TOKEN is not set');");
|
|
651
|
-
ast.body.push(authTokenCheck);
|
|
652
|
-
clientExpression = parseExpression("createClient({ url: env.DATABASE_URL, authToken: env.DATABASE_AUTH_TOKEN })");
|
|
653
|
-
} else clientExpression = parseExpression("createClient({ url: env.DATABASE_URL })");
|
|
654
|
-
}
|
|
655
|
-
if (options$7.mysql === "mysql2" || options$7.mysql === "planetscale") {
|
|
656
|
-
addDefault(ast, {
|
|
657
|
-
from: "mysql2/promise",
|
|
658
|
-
as: "mysql"
|
|
659
|
-
});
|
|
660
|
-
addNamed(ast, {
|
|
661
|
-
from: "drizzle-orm/mysql2",
|
|
662
|
-
imports: ["drizzle"]
|
|
663
|
-
});
|
|
664
|
-
clientExpression = parseExpression("mysql.createPool(env.DATABASE_URL)");
|
|
665
|
-
}
|
|
666
|
-
if (options$7.postgresql === "neon") {
|
|
667
|
-
addNamed(ast, {
|
|
668
|
-
from: "@neondatabase/serverless",
|
|
669
|
-
imports: ["neon"]
|
|
670
|
-
});
|
|
671
|
-
addNamed(ast, {
|
|
672
|
-
from: "drizzle-orm/neon-http",
|
|
673
|
-
imports: ["drizzle"]
|
|
674
|
-
});
|
|
675
|
-
clientExpression = parseExpression("neon(env.DATABASE_URL)");
|
|
676
|
-
}
|
|
677
|
-
if (options$7.postgresql === "postgres.js") {
|
|
678
|
-
addDefault(ast, {
|
|
679
|
-
from: "postgres",
|
|
680
|
-
as: "postgres"
|
|
681
|
-
});
|
|
682
|
-
addNamed(ast, {
|
|
683
|
-
from: "drizzle-orm/postgres-js",
|
|
684
|
-
imports: ["drizzle"]
|
|
685
|
-
});
|
|
686
|
-
clientExpression = parseExpression("postgres(env.DATABASE_URL)");
|
|
687
|
-
}
|
|
688
|
-
if (!clientExpression) throw new Error("unreachable state...");
|
|
689
|
-
ast.body.push(declaration(ast, {
|
|
690
|
-
kind: "const",
|
|
691
|
-
name: "client",
|
|
692
|
-
value: clientExpression
|
|
693
|
-
}));
|
|
694
|
-
const drizzleCall = createCall({
|
|
695
|
-
name: "drizzle",
|
|
696
|
-
args: ["client"],
|
|
697
|
-
useIdentifiers: true
|
|
698
|
-
});
|
|
699
|
-
const paramObject = create({ schema: createIdentifier("schema") });
|
|
700
|
-
if (options$7.database === "mysql") {
|
|
701
|
-
const mode = options$7.mysql === "planetscale" ? "planetscale" : "default";
|
|
702
|
-
property(paramObject, {
|
|
703
|
-
name: "mode",
|
|
704
|
-
fallback: createLiteral(mode)
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
drizzleCall.arguments.push(paramObject);
|
|
708
|
-
const db = declaration(ast, {
|
|
709
|
-
kind: "const",
|
|
710
|
-
name: "db",
|
|
711
|
-
value: drizzleCall
|
|
712
|
-
});
|
|
713
|
-
createNamed(ast, {
|
|
714
|
-
name: "db",
|
|
715
|
-
fallback: db
|
|
716
|
-
});
|
|
717
|
-
return generateCode();
|
|
718
|
-
});
|
|
719
|
-
},
|
|
720
|
-
nextSteps: ({ options: options$7, highlighter, packageManager }) => {
|
|
721
|
-
const steps = [`You will need to set ${highlighter.env("DATABASE_URL")} in your production environment`];
|
|
722
|
-
if (options$7.docker) {
|
|
723
|
-
const { command: command$1, args: args$1 } = resolveCommand(packageManager, "run", ["db:start"]);
|
|
724
|
-
steps.push(`Run ${highlighter.command(`${command$1} ${args$1.join(" ")}`)} to start the docker container`);
|
|
725
|
-
} else steps.push(`Check ${highlighter.env("DATABASE_URL")} in ${highlighter.path(".env")} and adjust it to your needs`);
|
|
726
|
-
const { command, args } = resolveCommand(packageManager, "run", ["db:push"]);
|
|
727
|
-
steps.push(`Run ${highlighter.command(`${command} ${args.join(" ")}`)} to update your database schema`);
|
|
728
|
-
return steps;
|
|
729
|
-
}
|
|
730
|
-
});
|
|
731
|
-
function generateEnvFileContent(content, opts) {
|
|
732
|
-
const DB_URL_KEY = "DATABASE_URL";
|
|
733
|
-
if (opts.docker) {
|
|
734
|
-
const protocol = opts.database === "mysql" ? "mysql" : "postgres";
|
|
735
|
-
const port = PORTS[opts.database];
|
|
736
|
-
content = addEnvVar(content, DB_URL_KEY, `"${protocol}://root:mysecretpassword@localhost:${port}/local"`);
|
|
737
|
-
return content;
|
|
738
|
-
}
|
|
739
|
-
if (opts.sqlite === "better-sqlite3" || opts.sqlite === "libsql") {
|
|
740
|
-
const dbFile = opts.sqlite === "libsql" ? "file:local.db" : "local.db";
|
|
741
|
-
content = addEnvVar(content, DB_URL_KEY, dbFile);
|
|
742
|
-
return content;
|
|
743
|
-
}
|
|
744
|
-
content = addEnvComment(content, "Replace with your DB credentials!");
|
|
745
|
-
if (opts.sqlite === "turso") {
|
|
746
|
-
content = addEnvVar(content, DB_URL_KEY, "\"libsql://db-name-user.turso.io\"");
|
|
747
|
-
content = addEnvVar(content, "DATABASE_AUTH_TOKEN", "\"\"");
|
|
748
|
-
content = addEnvComment(content, "A local DB can also be used in dev as well");
|
|
749
|
-
content = addEnvComment(content, `${DB_URL_KEY}="file:local.db"`);
|
|
750
|
-
}
|
|
751
|
-
if (opts.database === "mysql") content = addEnvVar(content, DB_URL_KEY, "\"mysql://user:password@host:port/db-name\"");
|
|
752
|
-
if (opts.database === "postgresql") content = addEnvVar(content, DB_URL_KEY, "\"postgres://user:password@host:port/db-name\"");
|
|
753
|
-
return content;
|
|
754
|
-
}
|
|
755
|
-
function addEnvVar(content, key, value) {
|
|
756
|
-
if (!content.includes(key + "=")) content = appendEnvContent(content, `${key}=${value}`);
|
|
757
|
-
return content;
|
|
758
|
-
}
|
|
759
|
-
function addEnvComment(content, comment) {
|
|
760
|
-
const commented = `# ${comment}`;
|
|
761
|
-
if (!content.includes(commented)) content = appendEnvContent(content, commented);
|
|
762
|
-
return content;
|
|
763
|
-
}
|
|
764
|
-
function appendEnvContent(existing, content) {
|
|
765
|
-
return (!existing.length || existing.endsWith("\n") ? existing : existing + "\n") + content + "\n";
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
//#endregion
|
|
769
|
-
//#region lib/addons/eslint/index.ts
|
|
770
|
-
var eslint_default = defineAddon({
|
|
771
|
-
id: "eslint",
|
|
772
|
-
shortDescription: "linter",
|
|
773
|
-
homepage: "https://eslint.org",
|
|
774
|
-
options: {},
|
|
775
|
-
run: ({ sv, typescript, dependencyVersion, files }) => {
|
|
776
|
-
const prettierInstalled = Boolean(dependencyVersion("prettier"));
|
|
777
|
-
sv.devDependency("eslint", "^9.39.1");
|
|
778
|
-
sv.devDependency("@eslint/compat", "^1.4.0");
|
|
779
|
-
sv.devDependency("eslint-plugin-svelte", "^3.13.1");
|
|
780
|
-
sv.devDependency("globals", "^16.5.0");
|
|
781
|
-
sv.devDependency("@eslint/js", "^9.39.1");
|
|
782
|
-
sv.devDependency("@types/node", getNodeTypesVersion());
|
|
783
|
-
if (typescript) sv.devDependency("typescript-eslint", "^8.48.1");
|
|
784
|
-
if (prettierInstalled) sv.devDependency("eslint-config-prettier", "^10.1.8");
|
|
785
|
-
sv.file(files.package, (content) => {
|
|
786
|
-
const { data, generateCode } = parseJson(content);
|
|
787
|
-
data.scripts ??= {};
|
|
788
|
-
const scripts = data.scripts;
|
|
789
|
-
const LINT_CMD = "eslint .";
|
|
790
|
-
scripts["lint"] ??= LINT_CMD;
|
|
791
|
-
if (!scripts["lint"].includes(LINT_CMD)) scripts["lint"] += ` && ${LINT_CMD}`;
|
|
792
|
-
return generateCode();
|
|
793
|
-
});
|
|
794
|
-
sv.file(files.vscodeSettings, (content) => {
|
|
795
|
-
if (!content) return content;
|
|
796
|
-
const { data, generateCode } = parseJson(content);
|
|
797
|
-
const validate = data["eslint.validate"];
|
|
798
|
-
if (validate && !validate.includes("svelte")) validate.push("svelte");
|
|
799
|
-
return generateCode();
|
|
800
|
-
});
|
|
801
|
-
sv.file(files.eslintConfig, (content) => {
|
|
802
|
-
const { ast, comments, generateCode } = parseScript(content);
|
|
803
|
-
const eslintConfigs = [];
|
|
804
|
-
addDefault(ast, {
|
|
805
|
-
from: "./svelte.config.js",
|
|
806
|
-
as: "svelteConfig"
|
|
807
|
-
});
|
|
808
|
-
const gitIgnorePathStatement = parseStatement("\nconst gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));");
|
|
809
|
-
appendStatement(ast, { statement: gitIgnorePathStatement });
|
|
810
|
-
const ignoresConfig = parseExpression("includeIgnoreFile(gitignorePath)");
|
|
811
|
-
eslintConfigs.push(ignoresConfig);
|
|
812
|
-
const jsConfig = parseExpression("js.configs.recommended");
|
|
813
|
-
eslintConfigs.push(jsConfig);
|
|
814
|
-
if (typescript) {
|
|
815
|
-
const tsConfig = parseExpression("ts.configs.recommended");
|
|
816
|
-
eslintConfigs.push(createSpread(tsConfig));
|
|
817
|
-
}
|
|
818
|
-
const svelteConfig = parseExpression("svelte.configs.recommended");
|
|
819
|
-
eslintConfigs.push(createSpread(svelteConfig));
|
|
820
|
-
const globalsBrowser = createSpread(parseExpression("globals.browser"));
|
|
821
|
-
const globalsNode = createSpread(parseExpression("globals.node"));
|
|
822
|
-
const globalsObjLiteral = create({});
|
|
823
|
-
globalsObjLiteral.properties = [globalsBrowser, globalsNode];
|
|
824
|
-
const rules = create({ "\"no-undef\"": "off" });
|
|
825
|
-
if (rules.properties[0].type !== "Property") throw new Error("rules.properties[0].type !== \"Property\"");
|
|
826
|
-
comments.add(rules.properties[0].key, {
|
|
827
|
-
type: "Line",
|
|
828
|
-
value: " typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects."
|
|
829
|
-
});
|
|
830
|
-
comments.add(rules.properties[0].key, {
|
|
831
|
-
type: "Line",
|
|
832
|
-
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"
|
|
833
|
-
});
|
|
834
|
-
const globalsConfig = create({
|
|
835
|
-
languageOptions: { globals: globalsObjLiteral },
|
|
836
|
-
rules: typescript ? rules : void 0
|
|
837
|
-
});
|
|
838
|
-
eslintConfigs.push(globalsConfig);
|
|
839
|
-
if (typescript) {
|
|
840
|
-
const svelteTSParserConfig = create({
|
|
841
|
-
files: [
|
|
842
|
-
"**/*.svelte",
|
|
843
|
-
"**/*.svelte.ts",
|
|
844
|
-
"**/*.svelte.js"
|
|
845
|
-
],
|
|
846
|
-
languageOptions: { parserOptions: {
|
|
847
|
-
projectService: true,
|
|
848
|
-
extraFileExtensions: [".svelte"],
|
|
849
|
-
parser: createIdentifier("ts.parser"),
|
|
850
|
-
svelteConfig: createIdentifier("svelteConfig")
|
|
851
|
-
} }
|
|
852
|
-
});
|
|
853
|
-
eslintConfigs.push(svelteTSParserConfig);
|
|
854
|
-
} else {
|
|
855
|
-
const svelteTSParserConfig = create({
|
|
856
|
-
files: ["**/*.svelte", "**/*.svelte.js"],
|
|
857
|
-
languageOptions: { parserOptions: { svelteConfig: createIdentifier("svelteConfig") } }
|
|
858
|
-
});
|
|
859
|
-
eslintConfigs.push(svelteTSParserConfig);
|
|
860
|
-
}
|
|
861
|
-
let exportExpression;
|
|
862
|
-
if (typescript) {
|
|
863
|
-
const tsConfigCall = createCall({
|
|
864
|
-
name: "defineConfig",
|
|
865
|
-
args: []
|
|
866
|
-
});
|
|
867
|
-
tsConfigCall.arguments.push(...eslintConfigs);
|
|
868
|
-
exportExpression = tsConfigCall;
|
|
869
|
-
} else {
|
|
870
|
-
const eslintArray = create$1();
|
|
871
|
-
eslintConfigs.map((x) => append(eslintArray, x));
|
|
872
|
-
exportExpression = eslintArray;
|
|
873
|
-
}
|
|
874
|
-
const { value: defaultExport, astNode } = createDefault(ast, { fallback: exportExpression });
|
|
875
|
-
if (defaultExport !== exportExpression) {
|
|
876
|
-
T.warn("An eslint config is already defined. Skipping initialization.");
|
|
877
|
-
return content;
|
|
878
|
-
}
|
|
879
|
-
if (!typescript) addJsDocTypeComment(astNode, comments, { type: "import('eslint').Linter.Config[]" });
|
|
880
|
-
if (typescript) addDefault(ast, {
|
|
881
|
-
from: "typescript-eslint",
|
|
882
|
-
as: "ts"
|
|
883
|
-
});
|
|
884
|
-
addDefault(ast, {
|
|
885
|
-
from: "globals",
|
|
886
|
-
as: "globals"
|
|
887
|
-
});
|
|
888
|
-
if (typescript) addNamed(ast, {
|
|
889
|
-
from: "eslint/config",
|
|
890
|
-
imports: ["defineConfig"]
|
|
891
|
-
});
|
|
892
|
-
addDefault(ast, {
|
|
893
|
-
from: "eslint-plugin-svelte",
|
|
894
|
-
as: "svelte"
|
|
895
|
-
});
|
|
896
|
-
addDefault(ast, {
|
|
897
|
-
from: "@eslint/js",
|
|
898
|
-
as: "js"
|
|
899
|
-
});
|
|
900
|
-
addNamed(ast, {
|
|
901
|
-
from: "@eslint/compat",
|
|
902
|
-
imports: ["includeIgnoreFile"]
|
|
903
|
-
});
|
|
904
|
-
addNamed(ast, {
|
|
905
|
-
from: "node:url",
|
|
906
|
-
imports: ["fileURLToPath"]
|
|
907
|
-
});
|
|
908
|
-
return generateCode();
|
|
909
|
-
});
|
|
910
|
-
if (prettierInstalled) sv.file(files.eslintConfig, addEslintConfigPrettier);
|
|
911
|
-
}
|
|
912
|
-
});
|
|
913
|
-
|
|
914
|
-
//#endregion
|
|
915
|
-
//#region lib/addons/lucia/index.ts
|
|
916
|
-
const TABLE_TYPE = {
|
|
917
|
-
mysql: "mysqlTable",
|
|
918
|
-
postgresql: "pgTable",
|
|
919
|
-
sqlite: "sqliteTable",
|
|
920
|
-
turso: "sqliteTable"
|
|
921
|
-
};
|
|
922
|
-
let drizzleDialect;
|
|
923
|
-
let schemaPath;
|
|
924
|
-
const options$5 = defineAddonOptions().add("demo", {
|
|
925
|
-
type: "boolean",
|
|
926
|
-
default: true,
|
|
927
|
-
question: `Do you want to include a demo? ${import_picocolors$1.default.dim("(includes a login/register page)")}`
|
|
928
|
-
}).build();
|
|
929
|
-
var lucia_default = defineAddon({
|
|
930
|
-
id: "lucia",
|
|
931
|
-
shortDescription: "auth guide",
|
|
932
|
-
homepage: "https://lucia-auth.com",
|
|
933
|
-
options: options$5,
|
|
934
|
-
setup: ({ kit, dependencyVersion, unsupported, dependsOn, runsAfter }) => {
|
|
935
|
-
if (!kit) unsupported("Requires SvelteKit");
|
|
936
|
-
if (!dependencyVersion("drizzle-orm")) dependsOn("drizzle");
|
|
937
|
-
runsAfter("tailwindcss");
|
|
938
|
-
},
|
|
939
|
-
run: ({ sv, typescript, options: options$7, kit, dependencyVersion }) => {
|
|
940
|
-
const ext = typescript ? "ts" : "js";
|
|
941
|
-
sv.devDependency("@oslojs/crypto", "^1.0.1");
|
|
942
|
-
sv.devDependency("@oslojs/encoding", "^1.1.0");
|
|
943
|
-
if (options$7.demo) sv.dependency("@node-rs/argon2", "^2.0.2");
|
|
944
|
-
sv.file(`drizzle.config.${ext}`, (content) => {
|
|
945
|
-
const { ast, generateCode } = parseScript(content);
|
|
946
|
-
const isProp = (name, node) => node.key.type === "Identifier" && node.key.name === name;
|
|
947
|
-
walk(ast, null, { Property(node) {
|
|
948
|
-
if (isProp("dialect", node) && node.value.type === "Literal" && typeof node.value.value === "string") drizzleDialect = node.value.value;
|
|
949
|
-
if (isProp("schema", node) && node.value.type === "Literal" && typeof node.value.value === "string") schemaPath = node.value.value;
|
|
950
|
-
} });
|
|
951
|
-
if (!drizzleDialect) throw new Error("Failed to detect DB dialect in your `drizzle.config.[js|ts]` file");
|
|
952
|
-
if (!schemaPath) throw new Error("Failed to find schema path in your `drizzle.config.[js|ts]` file");
|
|
953
|
-
return generateCode();
|
|
954
|
-
});
|
|
955
|
-
sv.file(schemaPath, (content) => {
|
|
956
|
-
const { ast, generateCode } = parseScript(content);
|
|
957
|
-
const createTable = (name) => createCall({
|
|
958
|
-
name: TABLE_TYPE[drizzleDialect],
|
|
959
|
-
args: [name]
|
|
960
|
-
});
|
|
961
|
-
const userDecl = declaration(ast, {
|
|
962
|
-
kind: "const",
|
|
963
|
-
name: "user",
|
|
964
|
-
value: createTable("user")
|
|
965
|
-
});
|
|
966
|
-
const sessionDecl = declaration(ast, {
|
|
967
|
-
kind: "const",
|
|
968
|
-
name: "session",
|
|
969
|
-
value: createTable("session")
|
|
970
|
-
});
|
|
971
|
-
const user = createNamed(ast, {
|
|
972
|
-
name: "user",
|
|
973
|
-
fallback: userDecl
|
|
974
|
-
});
|
|
975
|
-
const session = createNamed(ast, {
|
|
976
|
-
name: "session",
|
|
977
|
-
fallback: sessionDecl
|
|
978
|
-
});
|
|
979
|
-
const userTable = getCallExpression(user);
|
|
980
|
-
const sessionTable = getCallExpression(session);
|
|
981
|
-
if (!userTable || !sessionTable) throw new Error("failed to find call expression of `user` or `session`");
|
|
982
|
-
if (userTable.arguments.length === 1) userTable.arguments.push(create({}));
|
|
983
|
-
if (sessionTable.arguments.length === 1) sessionTable.arguments.push(create({}));
|
|
984
|
-
const userAttributes = userTable.arguments[1];
|
|
985
|
-
const sessionAttributes = sessionTable.arguments[1];
|
|
986
|
-
if (userAttributes?.type !== "ObjectExpression" || sessionAttributes?.type !== "ObjectExpression") throw new Error("unexpected shape of `user` or `session` table definition");
|
|
987
|
-
if (drizzleDialect === "sqlite" || drizzleDialect === "turso") {
|
|
988
|
-
addNamed(ast, {
|
|
989
|
-
from: "drizzle-orm/sqlite-core",
|
|
990
|
-
imports: [
|
|
991
|
-
"sqliteTable",
|
|
992
|
-
"text",
|
|
993
|
-
"integer"
|
|
994
|
-
]
|
|
995
|
-
});
|
|
996
|
-
overrideProperties(userAttributes, { id: parseExpression("text('id').primaryKey()") });
|
|
997
|
-
if (options$7.demo) overrideProperties(userAttributes, {
|
|
998
|
-
username: parseExpression("text('username').notNull().unique()"),
|
|
999
|
-
passwordHash: parseExpression("text('password_hash').notNull()")
|
|
1000
|
-
});
|
|
1001
|
-
overrideProperties(sessionAttributes, {
|
|
1002
|
-
id: parseExpression("text('id').primaryKey()"),
|
|
1003
|
-
userId: parseExpression("text('user_id').notNull().references(() => user.id)"),
|
|
1004
|
-
expiresAt: parseExpression("integer('expires_at', { mode: 'timestamp' }).notNull()")
|
|
1005
|
-
});
|
|
1006
|
-
}
|
|
1007
|
-
if (drizzleDialect === "mysql") {
|
|
1008
|
-
addNamed(ast, {
|
|
1009
|
-
from: "drizzle-orm/mysql-core",
|
|
1010
|
-
imports: [
|
|
1011
|
-
"mysqlTable",
|
|
1012
|
-
"varchar",
|
|
1013
|
-
"datetime"
|
|
1014
|
-
]
|
|
1015
|
-
});
|
|
1016
|
-
overrideProperties(userAttributes, { id: parseExpression("varchar('id', { length: 255 }).primaryKey()") });
|
|
1017
|
-
if (options$7.demo) overrideProperties(userAttributes, {
|
|
1018
|
-
username: parseExpression("varchar('username', { length: 32 }).notNull().unique()"),
|
|
1019
|
-
passwordHash: parseExpression("varchar('password_hash', { length: 255 }).notNull()")
|
|
1020
|
-
});
|
|
1021
|
-
overrideProperties(sessionAttributes, {
|
|
1022
|
-
id: parseExpression("varchar('id', { length: 255 }).primaryKey()"),
|
|
1023
|
-
userId: parseExpression("varchar('user_id', { length: 255 }).notNull().references(() => user.id)"),
|
|
1024
|
-
expiresAt: parseExpression("datetime('expires_at').notNull()")
|
|
1025
|
-
});
|
|
1026
|
-
}
|
|
1027
|
-
if (drizzleDialect === "postgresql") {
|
|
1028
|
-
addNamed(ast, {
|
|
1029
|
-
from: "drizzle-orm/pg-core",
|
|
1030
|
-
imports: [
|
|
1031
|
-
"pgTable",
|
|
1032
|
-
"text",
|
|
1033
|
-
"timestamp"
|
|
1034
|
-
]
|
|
1035
|
-
});
|
|
1036
|
-
overrideProperties(userAttributes, { id: parseExpression("text('id').primaryKey()") });
|
|
1037
|
-
if (options$7.demo) overrideProperties(userAttributes, {
|
|
1038
|
-
username: parseExpression("text('username').notNull().unique()"),
|
|
1039
|
-
passwordHash: parseExpression("text('password_hash').notNull()")
|
|
1040
|
-
});
|
|
1041
|
-
overrideProperties(sessionAttributes, {
|
|
1042
|
-
id: parseExpression("text('id').primaryKey()"),
|
|
1043
|
-
userId: parseExpression("text('user_id').notNull().references(() => user.id)"),
|
|
1044
|
-
expiresAt: parseExpression("timestamp('expires_at', { withTimezone: true, mode: 'date' }).notNull()")
|
|
1045
|
-
});
|
|
1046
|
-
}
|
|
1047
|
-
let code = generateCode();
|
|
1048
|
-
if (typescript) {
|
|
1049
|
-
if (!code.includes("export type Session =")) code += "\n\nexport type Session = typeof session.$inferSelect;";
|
|
1050
|
-
if (!code.includes("export type User =")) code += "\n\nexport type User = typeof user.$inferSelect;";
|
|
1051
|
-
}
|
|
1052
|
-
return code;
|
|
1053
|
-
});
|
|
1054
|
-
sv.file(`${kit?.libDirectory}/server/auth.${ext}`, (content) => {
|
|
1055
|
-
const { ast, generateCode } = parseScript(content);
|
|
1056
|
-
addNamespace(ast, {
|
|
1057
|
-
from: "$lib/server/db/schema",
|
|
1058
|
-
as: "table"
|
|
1059
|
-
});
|
|
1060
|
-
addNamed(ast, {
|
|
1061
|
-
from: "$lib/server/db",
|
|
1062
|
-
imports: ["db"]
|
|
1063
|
-
});
|
|
1064
|
-
addNamed(ast, {
|
|
1065
|
-
from: "@oslojs/encoding",
|
|
1066
|
-
imports: ["encodeBase64url", "encodeHexLowerCase"]
|
|
1067
|
-
});
|
|
1068
|
-
addNamed(ast, {
|
|
1069
|
-
from: "@oslojs/crypto/sha2",
|
|
1070
|
-
imports: ["sha256"]
|
|
1071
|
-
});
|
|
1072
|
-
addNamed(ast, {
|
|
1073
|
-
from: "drizzle-orm",
|
|
1074
|
-
imports: ["eq"]
|
|
1075
|
-
});
|
|
1076
|
-
if (typescript) addNamed(ast, {
|
|
1077
|
-
from: "@sveltejs/kit",
|
|
1078
|
-
imports: ["RequestEvent"],
|
|
1079
|
-
isType: true
|
|
1080
|
-
});
|
|
1081
|
-
const ms = new MagicString(generateCode().trim());
|
|
1082
|
-
const [ts] = createPrinter(typescript);
|
|
1083
|
-
if (!ms.original.includes("const DAY_IN_MS")) ms.append("\n\nconst DAY_IN_MS = 1000 * 60 * 60 * 24;");
|
|
1084
|
-
if (!ms.original.includes("export const sessionCookieName")) ms.append("\n\nexport const sessionCookieName = 'auth-session';");
|
|
1085
|
-
if (!ms.original.includes("export function generateSessionToken")) {
|
|
1086
|
-
const generateSessionToken = dedent_default`
|
|
1087
|
-
export function generateSessionToken() {
|
|
1088
|
-
const bytes = crypto.getRandomValues(new Uint8Array(18));
|
|
1089
|
-
const token = encodeBase64url(bytes);
|
|
1090
|
-
return token;
|
|
1091
|
-
}`;
|
|
1092
|
-
ms.append(`\n\n${generateSessionToken}`);
|
|
1093
|
-
}
|
|
1094
|
-
if (!ms.original.includes("async function createSession")) {
|
|
1095
|
-
const createSession = dedent_default`
|
|
1096
|
-
${ts("", "/**")}
|
|
1097
|
-
${ts("", " * @param {string} token")}
|
|
1098
|
-
${ts("", " * @param {string} userId")}
|
|
1099
|
-
${ts("", " */")}
|
|
1100
|
-
export async function createSession(token${ts(": string")}, userId${ts(": string")}) {
|
|
1101
|
-
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
|
1102
|
-
const session${ts(": table.Session")} = {
|
|
1103
|
-
id: sessionId,
|
|
1104
|
-
userId,
|
|
1105
|
-
expiresAt: new Date(Date.now() + DAY_IN_MS * 30)
|
|
1106
|
-
};
|
|
1107
|
-
await db.insert(table.session).values(session);
|
|
1108
|
-
return session;
|
|
1109
|
-
}`;
|
|
1110
|
-
ms.append(`\n\n${createSession}`);
|
|
1111
|
-
}
|
|
1112
|
-
if (!ms.original.includes("async function validateSessionToken")) {
|
|
1113
|
-
const validateSessionToken = dedent_default`
|
|
1114
|
-
${ts("", "/** @param {string} token */")}
|
|
1115
|
-
export async function validateSessionToken(token${ts(": string")}) {
|
|
1116
|
-
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
|
1117
|
-
const [result] = await db
|
|
1118
|
-
.select({
|
|
1119
|
-
// Adjust user table here to tweak returned data
|
|
1120
|
-
user: { id: table.user.id, username: table.user.username },
|
|
1121
|
-
session: table.session
|
|
1122
|
-
})
|
|
1123
|
-
.from(table.session)
|
|
1124
|
-
.innerJoin(table.user, eq(table.session.userId, table.user.id))
|
|
1125
|
-
.where(eq(table.session.id, sessionId));
|
|
1126
|
-
|
|
1127
|
-
if (!result) {
|
|
1128
|
-
return { session: null, user: null };
|
|
1129
|
-
}
|
|
1130
|
-
const { session, user } = result;
|
|
1131
|
-
|
|
1132
|
-
const sessionExpired = Date.now() >= session.expiresAt.getTime();
|
|
1133
|
-
if (sessionExpired) {
|
|
1134
|
-
await db.delete(table.session).where(eq(table.session.id, session.id));
|
|
1135
|
-
return { session: null, user: null };
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
const renewSession = Date.now() >= session.expiresAt.getTime() - DAY_IN_MS * 15;
|
|
1139
|
-
if (renewSession) {
|
|
1140
|
-
session.expiresAt = new Date(Date.now() + DAY_IN_MS * 30);
|
|
1141
|
-
await db
|
|
1142
|
-
.update(table.session)
|
|
1143
|
-
.set({ expiresAt: session.expiresAt })
|
|
1144
|
-
.where(eq(table.session.id, session.id));
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
return { session, user };
|
|
1148
|
-
}`;
|
|
1149
|
-
ms.append(`\n\n${validateSessionToken}`);
|
|
1150
|
-
}
|
|
1151
|
-
if (typescript && !ms.original.includes("export type SessionValidationResult")) ms.append(`\n\nexport type SessionValidationResult = Awaited<ReturnType<typeof validateSessionToken>>;`);
|
|
1152
|
-
if (!ms.original.includes("async function invalidateSession")) {
|
|
1153
|
-
const invalidateSession = dedent_default`
|
|
1154
|
-
${ts("", "/** @param {string} sessionId */")}
|
|
1155
|
-
export async function invalidateSession(sessionId${ts(": string")}) {
|
|
1156
|
-
await db.delete(table.session).where(eq(table.session.id, sessionId));
|
|
1157
|
-
}`;
|
|
1158
|
-
ms.append(`\n\n${invalidateSession}`);
|
|
1159
|
-
}
|
|
1160
|
-
if (!ms.original.includes("export function setSessionTokenCookie")) {
|
|
1161
|
-
const setSessionTokenCookie = dedent_default`
|
|
1162
|
-
${ts("", "/**")}
|
|
1163
|
-
${ts("", " * @param {import(\"@sveltejs/kit\").RequestEvent} event")}
|
|
1164
|
-
${ts("", " * @param {string} token")}
|
|
1165
|
-
${ts("", " * @param {Date} expiresAt")}
|
|
1166
|
-
${ts("", " */")}
|
|
1167
|
-
export function setSessionTokenCookie(event${ts(": RequestEvent")}, token${ts(": string")}, expiresAt${ts(": Date")}) {
|
|
1168
|
-
event.cookies.set(sessionCookieName, token, {
|
|
1169
|
-
expires: expiresAt,
|
|
1170
|
-
path: '/'
|
|
1171
|
-
});
|
|
1172
|
-
}`;
|
|
1173
|
-
ms.append(`\n\n${setSessionTokenCookie}`);
|
|
1174
|
-
}
|
|
1175
|
-
if (!ms.original.includes("export function deleteSessionTokenCookie")) {
|
|
1176
|
-
const deleteSessionTokenCookie = dedent_default`
|
|
1177
|
-
${ts("", "/** @param {import(\"@sveltejs/kit\").RequestEvent} event */")}
|
|
1178
|
-
export function deleteSessionTokenCookie(event${ts(": RequestEvent")}) {
|
|
1179
|
-
event.cookies.delete(sessionCookieName, {
|
|
1180
|
-
path: '/'
|
|
1181
|
-
});
|
|
1182
|
-
}`;
|
|
1183
|
-
ms.append(`\n\n${deleteSessionTokenCookie}`);
|
|
1184
|
-
}
|
|
1185
|
-
return ms.toString();
|
|
1186
|
-
});
|
|
1187
|
-
if (typescript) sv.file("src/app.d.ts", (content) => {
|
|
1188
|
-
const { ast, generateCode } = parseScript(content);
|
|
1189
|
-
const locals = addGlobalAppInterface(ast, { name: "Locals" });
|
|
1190
|
-
if (!locals) throw new Error("Failed detecting `locals` interface in `src/app.d.ts`");
|
|
1191
|
-
const user = locals.body.body.find((prop) => hasTypeProperty(prop, { name: "user" }));
|
|
1192
|
-
const session = locals.body.body.find((prop) => hasTypeProperty(prop, { name: "session" }));
|
|
1193
|
-
if (!user) locals.body.body.push(createLuciaType("user"));
|
|
1194
|
-
if (!session) locals.body.body.push(createLuciaType("session"));
|
|
1195
|
-
return generateCode();
|
|
1196
|
-
});
|
|
1197
|
-
sv.file(`src/hooks.server.${ext}`, (content) => {
|
|
1198
|
-
const { ast, generateCode } = parseScript(content);
|
|
1199
|
-
addNamespace(ast, {
|
|
1200
|
-
from: "$lib/server/auth",
|
|
1201
|
-
as: "auth"
|
|
1202
|
-
});
|
|
1203
|
-
addHooksHandle(ast, {
|
|
1204
|
-
typescript,
|
|
1205
|
-
newHandleName: "handleAuth",
|
|
1206
|
-
handleContent: getAuthHandleContent()
|
|
1207
|
-
});
|
|
1208
|
-
return generateCode();
|
|
1209
|
-
});
|
|
1210
|
-
if (options$7.demo) {
|
|
1211
|
-
sv.file(`${kit?.routesDirectory}/demo/+page.svelte`, (content) => {
|
|
1212
|
-
return addToDemoPage(content, "lucia", typescript);
|
|
1213
|
-
});
|
|
1214
|
-
sv.file(`${kit.routesDirectory}/demo/lucia/login/+page.server.${ext}`, (content) => {
|
|
1215
|
-
if (content) {
|
|
1216
|
-
const filePath = `${kit.routesDirectory}/demo/lucia/login/+page.server.${typescript ? "ts" : "js"}`;
|
|
1217
|
-
T.warn(`Existing ${import_picocolors$1.default.yellow(filePath)} file. Could not update.`);
|
|
1218
|
-
return content;
|
|
1219
|
-
}
|
|
1220
|
-
const [ts] = createPrinter(typescript);
|
|
1221
|
-
return dedent_default`
|
|
1222
|
-
import { hash, verify } from '@node-rs/argon2';
|
|
1223
|
-
import { encodeBase32LowerCase } from '@oslojs/encoding';
|
|
1224
|
-
import { fail, redirect } from '@sveltejs/kit';
|
|
1225
|
-
import { eq } from 'drizzle-orm';
|
|
1226
|
-
import * as auth from '$lib/server/auth';
|
|
1227
|
-
import { db } from '$lib/server/db';
|
|
1228
|
-
import * as table from '$lib/server/db/schema';
|
|
1229
|
-
${ts("import type { Actions, PageServerLoad } from './$types';\n")}
|
|
1230
|
-
export const load${ts(": PageServerLoad")} = async (event) => {
|
|
1231
|
-
if (event.locals.user) {
|
|
1232
|
-
return redirect(302, '/demo/lucia');
|
|
1233
|
-
}
|
|
1234
|
-
return {};
|
|
1235
|
-
};
|
|
1236
|
-
|
|
1237
|
-
export const actions${ts(": Actions")} = {
|
|
1238
|
-
login: async (event) => {
|
|
1239
|
-
const formData = await event.request.formData();
|
|
1240
|
-
const username = formData.get('username');
|
|
1241
|
-
const password = formData.get('password');
|
|
1242
|
-
|
|
1243
|
-
if (!validateUsername(username)) {
|
|
1244
|
-
return fail(400, { message: 'Invalid username (min 3, max 31 characters, alphanumeric only)' });
|
|
1245
|
-
}
|
|
1246
|
-
if (!validatePassword(password)) {
|
|
1247
|
-
return fail(400, { message: 'Invalid password (min 6, max 255 characters)' });
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
const results = await db
|
|
1251
|
-
.select()
|
|
1252
|
-
.from(table.user)
|
|
1253
|
-
.where(eq(table.user.username, username));
|
|
1254
|
-
|
|
1255
|
-
const existingUser = results.at(0);
|
|
1256
|
-
if (!existingUser) {
|
|
1257
|
-
return fail(400, { message: 'Incorrect username or password' });
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
const validPassword = await verify(existingUser.passwordHash, password, {
|
|
1261
|
-
memoryCost: 19456,
|
|
1262
|
-
timeCost: 2,
|
|
1263
|
-
outputLen: 32,
|
|
1264
|
-
parallelism: 1,
|
|
1265
|
-
});
|
|
1266
|
-
if (!validPassword) {
|
|
1267
|
-
return fail(400, { message: 'Incorrect username or password' });
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
const sessionToken = auth.generateSessionToken();
|
|
1271
|
-
const session = await auth.createSession(sessionToken, existingUser.id);
|
|
1272
|
-
auth.setSessionTokenCookie(event, sessionToken, session.expiresAt);
|
|
1273
|
-
|
|
1274
|
-
return redirect(302, '/demo/lucia');
|
|
1275
|
-
},
|
|
1276
|
-
register: async (event) => {
|
|
1277
|
-
const formData = await event.request.formData();
|
|
1278
|
-
const username = formData.get('username');
|
|
1279
|
-
const password = formData.get('password');
|
|
1280
|
-
|
|
1281
|
-
if (!validateUsername(username)) {
|
|
1282
|
-
return fail(400, { message: 'Invalid username' });
|
|
1283
|
-
}
|
|
1284
|
-
if (!validatePassword(password)) {
|
|
1285
|
-
return fail(400, { message: 'Invalid password' });
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
const userId = generateUserId();
|
|
1289
|
-
const passwordHash = await hash(password, {
|
|
1290
|
-
// recommended minimum parameters
|
|
1291
|
-
memoryCost: 19456,
|
|
1292
|
-
timeCost: 2,
|
|
1293
|
-
outputLen: 32,
|
|
1294
|
-
parallelism: 1,
|
|
1295
|
-
});
|
|
1296
|
-
|
|
1297
|
-
try {
|
|
1298
|
-
await db.insert(table.user).values({ id: userId, username, passwordHash });
|
|
1299
|
-
|
|
1300
|
-
const sessionToken = auth.generateSessionToken();
|
|
1301
|
-
const session = await auth.createSession(sessionToken, userId);
|
|
1302
|
-
auth.setSessionTokenCookie(event, sessionToken, session.expiresAt);
|
|
1303
|
-
} catch {
|
|
1304
|
-
return fail(500, { message: 'An error has occurred' });
|
|
1305
|
-
}
|
|
1306
|
-
return redirect(302, '/demo/lucia');
|
|
1307
|
-
},
|
|
1308
|
-
};
|
|
1309
|
-
|
|
1310
|
-
function generateUserId() {
|
|
1311
|
-
// ID with 120 bits of entropy, or about the same as UUID v4.
|
|
1312
|
-
const bytes = crypto.getRandomValues(new Uint8Array(15));
|
|
1313
|
-
const id = encodeBase32LowerCase(bytes);
|
|
1314
|
-
return id;
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
function validateUsername(username${ts(": unknown")})${ts(": username is string")} {
|
|
1318
|
-
return (
|
|
1319
|
-
typeof username === 'string' &&
|
|
1320
|
-
username.length >= 3 &&
|
|
1321
|
-
username.length <= 31 &&
|
|
1322
|
-
/^[a-z0-9_-]+$/.test(username)
|
|
1323
|
-
);
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
function validatePassword(password${ts(": unknown")})${ts(": password is string")} {
|
|
1327
|
-
return (
|
|
1328
|
-
typeof password === 'string' &&
|
|
1329
|
-
password.length >= 6 &&
|
|
1330
|
-
password.length <= 255
|
|
1331
|
-
);
|
|
1332
|
-
}
|
|
1333
|
-
`;
|
|
1334
|
-
});
|
|
1335
|
-
sv.file(`${kit.routesDirectory}/demo/lucia/login/+page.svelte`, (content) => {
|
|
1336
|
-
if (content) {
|
|
1337
|
-
const filePath = `${kit.routesDirectory}/demo/lucia/login/+page.svelte`;
|
|
1338
|
-
T.warn(`Existing ${import_picocolors$1.default.yellow(filePath)} file. Could not update.`);
|
|
1339
|
-
return content;
|
|
1340
|
-
}
|
|
1341
|
-
const tailwind = dependencyVersion("@tailwindcss/vite") !== void 0;
|
|
1342
|
-
const twInputClasses = "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\"";
|
|
1343
|
-
const twBtnClasses = "class=\"bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition\"";
|
|
1344
|
-
const svelte5 = !!dependencyVersion("svelte")?.startsWith("5");
|
|
1345
|
-
const [ts, s5] = createPrinter(typescript, svelte5);
|
|
1346
|
-
return dedent_default`
|
|
1347
|
-
<script ${ts("lang='ts'")}>
|
|
1348
|
-
import { enhance } from '$app/forms';
|
|
1349
|
-
${ts("import type { ActionData } from './$types';\n")}
|
|
1350
|
-
${s5(`let { form }${ts(": { form: ActionData }")} = $props();`, `export let form${ts(": ActionData")};`)}
|
|
1351
|
-
<\/script>
|
|
1352
|
-
|
|
1353
|
-
<h1>Login/Register</h1>
|
|
1354
|
-
<form method="post" action="?/login" use:enhance>
|
|
1355
|
-
<label>
|
|
1356
|
-
Username
|
|
1357
|
-
<input
|
|
1358
|
-
name="username"
|
|
1359
|
-
${tailwind ? twInputClasses : ""}
|
|
1360
|
-
/>
|
|
1361
|
-
</label>
|
|
1362
|
-
<label>
|
|
1363
|
-
Password
|
|
1364
|
-
<input
|
|
1365
|
-
type="password"
|
|
1366
|
-
name="password"
|
|
1367
|
-
${tailwind ? twInputClasses : ""}
|
|
1368
|
-
/>
|
|
1369
|
-
</label>
|
|
1370
|
-
<button ${tailwind ? twBtnClasses : ""}
|
|
1371
|
-
>Login</button>
|
|
1372
|
-
<button
|
|
1373
|
-
formaction="?/register"
|
|
1374
|
-
${tailwind ? twBtnClasses : ""}
|
|
1375
|
-
>Register</button>
|
|
1376
|
-
</form>
|
|
1377
|
-
<p style='color: red'>{form?.message ?? ''}</p>
|
|
1378
|
-
`;
|
|
1379
|
-
});
|
|
1380
|
-
sv.file(`${kit.routesDirectory}/demo/lucia/+page.server.${ext}`, (content) => {
|
|
1381
|
-
if (content) {
|
|
1382
|
-
const filePath = `${kit.routesDirectory}/demo/lucia/+page.server.${typescript ? "ts" : "js"}`;
|
|
1383
|
-
T.warn(`Existing ${import_picocolors$1.default.yellow(filePath)} file. Could not update.`);
|
|
1384
|
-
return content;
|
|
1385
|
-
}
|
|
1386
|
-
const [ts] = createPrinter(typescript);
|
|
1387
|
-
return dedent_default`
|
|
1388
|
-
import * as auth from '$lib/server/auth';
|
|
1389
|
-
import { fail, redirect } from '@sveltejs/kit';
|
|
1390
|
-
import { getRequestEvent } from '$app/server';
|
|
1391
|
-
${ts("import type { Actions, PageServerLoad } from './$types';\n")}
|
|
1392
|
-
export const load${ts(": PageServerLoad")} = async () => {
|
|
1393
|
-
const user = requireLogin()
|
|
1394
|
-
return { user };
|
|
1395
|
-
};
|
|
1396
|
-
|
|
1397
|
-
export const actions${ts(": Actions")} = {
|
|
1398
|
-
logout: async (event) => {
|
|
1399
|
-
if (!event.locals.session) {
|
|
1400
|
-
return fail(401);
|
|
1401
|
-
}
|
|
1402
|
-
await auth.invalidateSession(event.locals.session.id);
|
|
1403
|
-
auth.deleteSessionTokenCookie(event);
|
|
1404
|
-
|
|
1405
|
-
return redirect(302, '/demo/lucia/login');
|
|
1406
|
-
},
|
|
1407
|
-
};
|
|
1408
|
-
|
|
1409
|
-
function requireLogin() {
|
|
1410
|
-
const { locals } = getRequestEvent();
|
|
1411
|
-
|
|
1412
|
-
if (!locals.user) {
|
|
1413
|
-
return redirect(302, "/demo/lucia/login");
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
return locals.user;
|
|
1417
|
-
}
|
|
1418
|
-
`;
|
|
1419
|
-
});
|
|
1420
|
-
sv.file(`${kit.routesDirectory}/demo/lucia/+page.svelte`, (content) => {
|
|
1421
|
-
if (content) {
|
|
1422
|
-
const filePath = `${kit.routesDirectory}/demo/lucia/+page.svelte`;
|
|
1423
|
-
T.warn(`Existing ${import_picocolors$1.default.yellow(filePath)} file. Could not update.`);
|
|
1424
|
-
return content;
|
|
1425
|
-
}
|
|
1426
|
-
const svelte5 = !!dependencyVersion("svelte")?.startsWith("5");
|
|
1427
|
-
const [ts, s5] = createPrinter(typescript, svelte5);
|
|
1428
|
-
return dedent_default`
|
|
1429
|
-
<script ${ts("lang='ts'")}>
|
|
1430
|
-
import { enhance } from '$app/forms';
|
|
1431
|
-
${ts("import type { PageServerData } from './$types';\n")}
|
|
1432
|
-
${s5(`let { data }${ts(": { data: PageServerData }")} = $props();`, `export let data${ts(": PageServerData")};`)}
|
|
1433
|
-
<\/script>
|
|
1434
|
-
|
|
1435
|
-
<h1>Hi, {data.user.username}!</h1>
|
|
1436
|
-
<p>Your user ID is {data.user.id}.</p>
|
|
1437
|
-
<form method='post' action='?/logout' use:enhance>
|
|
1438
|
-
<button>Sign out</button>
|
|
1439
|
-
</form>
|
|
1440
|
-
`;
|
|
1441
|
-
});
|
|
1442
|
-
}
|
|
1443
|
-
},
|
|
1444
|
-
nextSteps: ({ highlighter, options: options$7, packageManager }) => {
|
|
1445
|
-
const { command, args } = resolveCommand(packageManager, "run", ["db:push"]);
|
|
1446
|
-
const steps = [`Run ${highlighter.command(`${command} ${args.join(" ")}`)} to update your database schema`];
|
|
1447
|
-
if (options$7.demo) steps.push(`Visit ${highlighter.route("/demo/lucia")} route to view the demo`);
|
|
1448
|
-
return steps;
|
|
1449
|
-
}
|
|
1450
|
-
});
|
|
1451
|
-
function createLuciaType(name) {
|
|
1452
|
-
return {
|
|
1453
|
-
type: "TSPropertySignature",
|
|
1454
|
-
key: {
|
|
1455
|
-
type: "Identifier",
|
|
1456
|
-
name
|
|
1457
|
-
},
|
|
1458
|
-
computed: false,
|
|
1459
|
-
typeAnnotation: {
|
|
1460
|
-
type: "TSTypeAnnotation",
|
|
1461
|
-
typeAnnotation: {
|
|
1462
|
-
type: "TSIndexedAccessType",
|
|
1463
|
-
objectType: {
|
|
1464
|
-
type: "TSImportType",
|
|
1465
|
-
argument: {
|
|
1466
|
-
type: "Literal",
|
|
1467
|
-
value: "$lib/server/auth"
|
|
1468
|
-
},
|
|
1469
|
-
qualifier: {
|
|
1470
|
-
type: "Identifier",
|
|
1471
|
-
name: "SessionValidationResult"
|
|
1472
|
-
}
|
|
1473
|
-
},
|
|
1474
|
-
indexType: {
|
|
1475
|
-
type: "TSLiteralType",
|
|
1476
|
-
literal: {
|
|
1477
|
-
type: "Literal",
|
|
1478
|
-
value: name
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
};
|
|
1484
|
-
}
|
|
1485
|
-
function getAuthHandleContent() {
|
|
1486
|
-
return `
|
|
1487
|
-
async ({ event, resolve }) => {
|
|
1488
|
-
const sessionToken = event.cookies.get(auth.sessionCookieName);
|
|
1489
|
-
if (!sessionToken) {
|
|
1490
|
-
event.locals.user = null;
|
|
1491
|
-
event.locals.session = null;
|
|
1492
|
-
return resolve(event);
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
const { session, user } = await auth.validateSessionToken(sessionToken);
|
|
1496
|
-
if (session) {
|
|
1497
|
-
auth.setSessionTokenCookie(event, sessionToken, session.expiresAt);
|
|
1498
|
-
} else {
|
|
1499
|
-
auth.deleteSessionTokenCookie(event);
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
event.locals.user = user;
|
|
1503
|
-
event.locals.session = session;
|
|
1504
|
-
|
|
1505
|
-
return resolve(event);
|
|
1506
|
-
};`;
|
|
1507
|
-
}
|
|
1508
|
-
function getCallExpression(ast) {
|
|
1509
|
-
let callExpression;
|
|
1510
|
-
walk(ast, null, { CallExpression(node) {
|
|
1511
|
-
callExpression ??= node;
|
|
1512
|
-
} });
|
|
1513
|
-
return callExpression;
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
//#endregion
|
|
1517
|
-
//#region lib/addons/mdsvex/index.ts
|
|
1518
|
-
var mdsvex_default = defineAddon({
|
|
1519
|
-
id: "mdsvex",
|
|
1520
|
-
shortDescription: "svelte + markdown",
|
|
1521
|
-
homepage: "https://mdsvex.pngwn.io",
|
|
1522
|
-
options: {},
|
|
1523
|
-
run: ({ sv, files }) => {
|
|
1524
|
-
sv.devDependency("mdsvex", "^0.12.6");
|
|
1525
|
-
sv.file(files.svelteConfig, (content) => {
|
|
1526
|
-
const { ast, generateCode } = parseScript(content);
|
|
1527
|
-
addNamed(ast, {
|
|
1528
|
-
from: "mdsvex",
|
|
1529
|
-
imports: ["mdsvex"]
|
|
1530
|
-
});
|
|
1531
|
-
const { value: exportDefault } = createDefault(ast, { fallback: create({}) });
|
|
1532
|
-
let preprocessorArray = property(exportDefault, {
|
|
1533
|
-
name: "preprocess",
|
|
1534
|
-
fallback: create$1()
|
|
1535
|
-
});
|
|
1536
|
-
if (!(preprocessorArray.type === "ArrayExpression")) {
|
|
1537
|
-
const previousElement = preprocessorArray;
|
|
1538
|
-
preprocessorArray = create$1();
|
|
1539
|
-
append(preprocessorArray, previousElement);
|
|
1540
|
-
overrideProperties(exportDefault, { preprocess: preprocessorArray });
|
|
1541
|
-
}
|
|
1542
|
-
const mdsvexCall = createCall({
|
|
1543
|
-
name: "mdsvex",
|
|
1544
|
-
args: []
|
|
1545
|
-
});
|
|
1546
|
-
append(preprocessorArray, mdsvexCall);
|
|
1547
|
-
const extensionsArray = property(exportDefault, {
|
|
1548
|
-
name: "extensions",
|
|
1549
|
-
fallback: create$1()
|
|
1550
|
-
});
|
|
1551
|
-
append(extensionsArray, ".svelte");
|
|
1552
|
-
append(extensionsArray, ".svx");
|
|
1553
|
-
return generateCode();
|
|
1554
|
-
});
|
|
1555
|
-
}
|
|
1556
|
-
});
|
|
1557
|
-
|
|
1558
|
-
//#endregion
|
|
1559
|
-
//#region lib/core/tooling/html/index.ts
|
|
1560
|
-
function addAttribute(element, name, value) {
|
|
1561
|
-
let existing = element.attributes.find((attr) => attr.type === "Attribute" && attr.name === name);
|
|
1562
|
-
if (!existing) {
|
|
1563
|
-
existing = {
|
|
1564
|
-
type: "Attribute",
|
|
1565
|
-
name,
|
|
1566
|
-
value: [],
|
|
1567
|
-
start: 0,
|
|
1568
|
-
end: 0,
|
|
1569
|
-
name_loc: {
|
|
1570
|
-
start: {
|
|
1571
|
-
column: 0,
|
|
1572
|
-
line: 0
|
|
1573
|
-
},
|
|
1574
|
-
end: {
|
|
1575
|
-
column: 0,
|
|
1576
|
-
line: 0
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
};
|
|
1580
|
-
element.attributes.push(existing);
|
|
1581
|
-
}
|
|
1582
|
-
existing.value = [{
|
|
1583
|
-
type: "Text",
|
|
1584
|
-
data: value,
|
|
1585
|
-
raw: value,
|
|
1586
|
-
start: 0,
|
|
1587
|
-
end: 0
|
|
1588
|
-
}];
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
//#endregion
|
|
1592
|
-
//#region lib/addons/paraglide/index.ts
|
|
1593
|
-
const DEFAULT_INLANG_PROJECT = {
|
|
1594
|
-
$schema: "https://inlang.com/schema/project-settings",
|
|
1595
|
-
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"],
|
|
1596
|
-
"plugin.inlang.messageFormat": { pathPattern: "./messages/{locale}.json" }
|
|
1597
|
-
};
|
|
1598
|
-
const options$4 = defineAddonOptions().add("languageTags", {
|
|
1599
|
-
question: `Which languages would you like to support? ${import_picocolors$1.default.gray("(e.g. en,de-ch)")}`,
|
|
1600
|
-
type: "string",
|
|
1601
|
-
default: "en, es",
|
|
1602
|
-
validate(input) {
|
|
1603
|
-
if (!input) return;
|
|
1604
|
-
const { invalidLanguageTags, validLanguageTags } = parseLanguageTagInput(input);
|
|
1605
|
-
if (invalidLanguageTags.length > 0) if (invalidLanguageTags.length === 1) return `The input "${invalidLanguageTags[0]}" is not a valid IETF BCP 47 language tag`;
|
|
1606
|
-
else return `The inputs ${new Intl.ListFormat("en", {
|
|
1607
|
-
style: "long",
|
|
1608
|
-
type: "conjunction"
|
|
1609
|
-
}).format(invalidLanguageTags.map((x) => `"${x}"`))} are not valid BCP47 language tags`;
|
|
1610
|
-
if (validLanguageTags.length === 0) return "Please enter at least one valid BCP47 language tag. Eg: en";
|
|
1611
|
-
}
|
|
1612
|
-
}).add("demo", {
|
|
1613
|
-
type: "boolean",
|
|
1614
|
-
default: true,
|
|
1615
|
-
question: "Do you want to include a demo?"
|
|
1616
|
-
}).build();
|
|
1617
|
-
var paraglide_default = defineAddon({
|
|
1618
|
-
id: "paraglide",
|
|
1619
|
-
shortDescription: "i18n",
|
|
1620
|
-
homepage: "https://inlang.com/m/gerre34r/library-inlang-paraglideJs",
|
|
1621
|
-
options: options$4,
|
|
1622
|
-
setup: ({ kit, unsupported }) => {
|
|
1623
|
-
if (!kit) unsupported("Requires SvelteKit");
|
|
1624
|
-
},
|
|
1625
|
-
run: ({ sv, options: options$7, files, typescript, kit }) => {
|
|
1626
|
-
const ext = typescript ? "ts" : "js";
|
|
1627
|
-
if (!kit) throw new Error("SvelteKit is required");
|
|
1628
|
-
const paraglideOutDir = "src/lib/paraglide";
|
|
1629
|
-
sv.devDependency("@inlang/paraglide-js", "^2.6.0");
|
|
1630
|
-
sv.file(files.viteConfig, (content) => {
|
|
1631
|
-
const { ast, generateCode } = parseScript(content);
|
|
1632
|
-
const vitePluginName = "paraglideVitePlugin";
|
|
1633
|
-
addNamed(ast, {
|
|
1634
|
-
imports: [vitePluginName],
|
|
1635
|
-
from: "@inlang/paraglide-js"
|
|
1636
|
-
});
|
|
1637
|
-
addPlugin(ast, { code: `${vitePluginName}({
|
|
1638
|
-
project: './project.inlang',
|
|
1639
|
-
outdir: './${paraglideOutDir}'
|
|
1640
|
-
})` });
|
|
1641
|
-
return generateCode();
|
|
1642
|
-
});
|
|
1643
|
-
sv.file(`src/hooks.${ext}`, (content) => {
|
|
1644
|
-
const { ast, generateCode } = parseScript(content);
|
|
1645
|
-
addNamed(ast, {
|
|
1646
|
-
from: "$lib/paraglide/runtime",
|
|
1647
|
-
imports: ["deLocalizeUrl"]
|
|
1648
|
-
});
|
|
1649
|
-
const expression = parseExpression("(request) => deLocalizeUrl(request.url).pathname");
|
|
1650
|
-
const rerouteIdentifier = declaration(ast, {
|
|
1651
|
-
kind: "const",
|
|
1652
|
-
name: "reroute",
|
|
1653
|
-
value: expression
|
|
1654
|
-
});
|
|
1655
|
-
if (createNamed(ast, {
|
|
1656
|
-
name: "reroute",
|
|
1657
|
-
fallback: rerouteIdentifier
|
|
1658
|
-
}).declaration !== rerouteIdentifier) T.warn("Adding the reroute hook automatically failed. Add it manually");
|
|
1659
|
-
return generateCode();
|
|
1660
|
-
});
|
|
1661
|
-
sv.file(`src/hooks.server.${ext}`, (content) => {
|
|
1662
|
-
const { ast, generateCode } = parseScript(content);
|
|
1663
|
-
addNamed(ast, {
|
|
1664
|
-
from: "$lib/paraglide/server",
|
|
1665
|
-
imports: ["paraglideMiddleware"]
|
|
1666
|
-
});
|
|
1667
|
-
addHooksHandle(ast, {
|
|
1668
|
-
typescript,
|
|
1669
|
-
newHandleName: "handleParaglide",
|
|
1670
|
-
handleContent: `({ event, resolve }) => paraglideMiddleware(event.request, ({ request, locale }) => {
|
|
1671
|
-
event.request = request;
|
|
1672
|
-
return resolve(event, {
|
|
1673
|
-
transformPageChunk: ({ html }) => html.replace('%paraglide.lang%', locale)
|
|
1674
|
-
});
|
|
1675
|
-
});`
|
|
1676
|
-
});
|
|
1677
|
-
return generateCode();
|
|
1678
|
-
});
|
|
1679
|
-
sv.file("src/app.html", (content) => {
|
|
1680
|
-
const { ast, generateCode } = parseHtml(content);
|
|
1681
|
-
const htmlNode = ast.nodes.find((child) => child.type === "RegularElement" && child.name === "html");
|
|
1682
|
-
if (!htmlNode) {
|
|
1683
|
-
T.warn("Could not find <html> node in app.html. You'll need to add the language placeholder manually");
|
|
1684
|
-
return generateCode();
|
|
1685
|
-
}
|
|
1686
|
-
addAttribute(htmlNode, "lang", "%paraglide.lang%");
|
|
1687
|
-
return generateCode();
|
|
1688
|
-
});
|
|
1689
|
-
sv.file(files.gitignore, (content) => {
|
|
1690
|
-
if (!content) return content;
|
|
1691
|
-
if (!content.includes(`\n${paraglideOutDir}`)) content = content.trimEnd() + `\n\n# Paraglide\n${paraglideOutDir}\nproject.inlang/cache/`;
|
|
1692
|
-
return content;
|
|
1693
|
-
});
|
|
1694
|
-
sv.file("project.inlang/settings.json", (content) => {
|
|
1695
|
-
if (content) return content;
|
|
1696
|
-
const { data, generateCode } = parseJson(content);
|
|
1697
|
-
for (const key in DEFAULT_INLANG_PROJECT) data[key] = DEFAULT_INLANG_PROJECT[key];
|
|
1698
|
-
const { validLanguageTags: validLanguageTags$1 } = parseLanguageTagInput(options$7.languageTags);
|
|
1699
|
-
data.baseLocale = validLanguageTags$1[0];
|
|
1700
|
-
data.locales = validLanguageTags$1;
|
|
1701
|
-
return generateCode();
|
|
1702
|
-
});
|
|
1703
|
-
sv.file(`${kit.routesDirectory}/+layout.svelte`, (content) => {
|
|
1704
|
-
const { ast, generateCode } = parseSvelte(content);
|
|
1705
|
-
const scriptAst = ensureScript(ast);
|
|
1706
|
-
addNamed(scriptAst, {
|
|
1707
|
-
imports: ["locales", "localizeHref"],
|
|
1708
|
-
from: "$lib/paraglide/runtime"
|
|
1709
|
-
});
|
|
1710
|
-
addNamed(scriptAst, {
|
|
1711
|
-
imports: ["page"],
|
|
1712
|
-
from: "$app/state"
|
|
1713
|
-
});
|
|
1714
|
-
ast.fragment.nodes.push(...toFragment(`<div style="display:none">
|
|
1715
|
-
{#each locales as locale}
|
|
1716
|
-
<a href={localizeHref(page.url.pathname, { locale })}>{locale}</a>
|
|
1717
|
-
{/each}
|
|
1718
|
-
</div>`));
|
|
1719
|
-
return generateCode();
|
|
1720
|
-
});
|
|
1721
|
-
if (options$7.demo) {
|
|
1722
|
-
sv.file(`${kit.routesDirectory}/demo/+page.svelte`, (content) => {
|
|
1723
|
-
return addToDemoPage(content, "paraglide", typescript);
|
|
1724
|
-
});
|
|
1725
|
-
sv.file(`${kit.routesDirectory}/demo/paraglide/+page.svelte`, (content) => {
|
|
1726
|
-
const { ast, generateCode } = parseSvelte(content);
|
|
1727
|
-
const scriptAst = ensureScript(ast, { langTs: typescript });
|
|
1728
|
-
addNamed(scriptAst, {
|
|
1729
|
-
imports: { m: "m" },
|
|
1730
|
-
from: "$lib/paraglide/messages.js"
|
|
1731
|
-
});
|
|
1732
|
-
addNamed(scriptAst, {
|
|
1733
|
-
imports: { setLocale: "setLocale" },
|
|
1734
|
-
from: "$lib/paraglide/runtime"
|
|
1735
|
-
});
|
|
1736
|
-
let templateCode = "<h1>{m.hello_world({ name: 'SvelteKit User' })}</h1>";
|
|
1737
|
-
const { validLanguageTags: validLanguageTags$1 } = parseLanguageTagInput(options$7.languageTags);
|
|
1738
|
-
const links = validLanguageTags$1.map((x) => `<button onclick={() => setLocale('${x}')}>${x}</button>`).join("");
|
|
1739
|
-
templateCode += `<div>${links}</div>`;
|
|
1740
|
-
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>";
|
|
1741
|
-
ast.fragment.nodes.push(...toFragment(templateCode));
|
|
1742
|
-
return generateCode();
|
|
1743
|
-
});
|
|
1744
|
-
}
|
|
1745
|
-
const { validLanguageTags } = parseLanguageTagInput(options$7.languageTags);
|
|
1746
|
-
for (const languageTag of validLanguageTags) sv.file(`messages/${languageTag}.json`, (content) => {
|
|
1747
|
-
const { data, generateCode } = parseJson(content);
|
|
1748
|
-
data["$schema"] = "https://inlang.com/schema/inlang-message-format";
|
|
1749
|
-
data.hello_world = `Hello, {name} from ${languageTag}!`;
|
|
1750
|
-
return generateCode();
|
|
1751
|
-
});
|
|
1752
|
-
},
|
|
1753
|
-
nextSteps: ({ highlighter }) => {
|
|
1754
|
-
const steps = [`Edit your messages in ${highlighter.path("messages/en.json")}`];
|
|
1755
|
-
if (options$4.demo) steps.push(`Visit ${highlighter.route("/demo/paraglide")} route to view the demo`);
|
|
1756
|
-
return steps;
|
|
1757
|
-
}
|
|
1758
|
-
});
|
|
1759
|
-
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);
|
|
1760
|
-
function parseLanguageTagInput(input) {
|
|
1761
|
-
const probablyLanguageTags = input.replace(/[,:\s]/g, " ").split(" ").filter(Boolean).map((tag) => tag.toLowerCase());
|
|
1762
|
-
const validLanguageTags = [];
|
|
1763
|
-
const invalidLanguageTags = [];
|
|
1764
|
-
for (const tag of probablyLanguageTags) if (isValidLanguageTag(tag)) validLanguageTags.push(tag);
|
|
1765
|
-
else invalidLanguageTags.push(tag);
|
|
1766
|
-
return {
|
|
1767
|
-
validLanguageTags,
|
|
1768
|
-
invalidLanguageTags
|
|
1769
|
-
};
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
//#endregion
|
|
1773
|
-
//#region lib/addons/mcp/index.ts
|
|
1774
|
-
const options$3 = defineAddonOptions().add("ide", {
|
|
1775
|
-
question: "Which client would you like to use?",
|
|
1776
|
-
type: "multiselect",
|
|
1777
|
-
default: [],
|
|
1778
|
-
options: [
|
|
1779
|
-
{
|
|
1780
|
-
value: "claude-code",
|
|
1781
|
-
label: "claude code"
|
|
1782
|
-
},
|
|
1783
|
-
{
|
|
1784
|
-
value: "cursor",
|
|
1785
|
-
label: "Cursor"
|
|
1786
|
-
},
|
|
1787
|
-
{
|
|
1788
|
-
value: "gemini",
|
|
1789
|
-
label: "Gemini"
|
|
1790
|
-
},
|
|
1791
|
-
{
|
|
1792
|
-
value: "opencode",
|
|
1793
|
-
label: "opencode"
|
|
1794
|
-
},
|
|
1795
|
-
{
|
|
1796
|
-
value: "vscode",
|
|
1797
|
-
label: "VSCode"
|
|
1798
|
-
},
|
|
1799
|
-
{
|
|
1800
|
-
value: "other",
|
|
1801
|
-
label: "Other"
|
|
1802
|
-
}
|
|
1803
|
-
],
|
|
1804
|
-
required: true
|
|
1805
|
-
}).add("setup", {
|
|
1806
|
-
question: "What setup would you like to use?",
|
|
1807
|
-
type: "select",
|
|
1808
|
-
default: "remote",
|
|
1809
|
-
options: [{
|
|
1810
|
-
value: "local",
|
|
1811
|
-
label: "Local",
|
|
1812
|
-
hint: "will use stdio"
|
|
1813
|
-
}, {
|
|
1814
|
-
value: "remote",
|
|
1815
|
-
label: "Remote",
|
|
1816
|
-
hint: "will use a remote endpoint"
|
|
1817
|
-
}],
|
|
1818
|
-
required: true
|
|
1819
|
-
}).build();
|
|
1820
|
-
var mcp_default = defineAddon({
|
|
1821
|
-
id: "mcp",
|
|
1822
|
-
shortDescription: "Svelte MCP",
|
|
1823
|
-
homepage: "https://svelte.dev/docs/mcp",
|
|
1824
|
-
options: options$3,
|
|
1825
|
-
run: ({ sv, options: options$7 }) => {
|
|
1826
|
-
const getLocalConfig = (o) => {
|
|
1827
|
-
return {
|
|
1828
|
-
...o?.type ? { type: o.type } : {},
|
|
1829
|
-
command: o?.command ?? "npx",
|
|
1830
|
-
...o?.env ? { env: {} } : {},
|
|
1831
|
-
...o?.args === null ? {} : { args: o?.args ?? ["-y", "@sveltejs/mcp"] }
|
|
1832
|
-
};
|
|
1833
|
-
};
|
|
1834
|
-
const getRemoteConfig = (o) => {
|
|
1835
|
-
return {
|
|
1836
|
-
...o?.type ? { type: o.type } : {},
|
|
1837
|
-
url: "https://mcp.svelte.dev/mcp"
|
|
1838
|
-
};
|
|
1839
|
-
};
|
|
1840
|
-
const configurator = {
|
|
1841
|
-
"claude-code": {
|
|
1842
|
-
agentPath: "CLAUDE.md",
|
|
1843
|
-
mcpPath: ".mcp.json",
|
|
1844
|
-
typeLocal: "stdio",
|
|
1845
|
-
typeRemote: "http",
|
|
1846
|
-
env: true
|
|
1847
|
-
},
|
|
1848
|
-
cursor: {
|
|
1849
|
-
agentPath: "AGENTS.md",
|
|
1850
|
-
mcpPath: ".cursor/mcp.json"
|
|
1851
|
-
},
|
|
1852
|
-
gemini: {
|
|
1853
|
-
agentPath: "GEMINI.md",
|
|
1854
|
-
schema: "https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json",
|
|
1855
|
-
mcpPath: ".gemini/settings.json"
|
|
1856
|
-
},
|
|
1857
|
-
opencode: {
|
|
1858
|
-
agentPath: "AGENTS.md",
|
|
1859
|
-
schema: "https://opencode.ai/config.json",
|
|
1860
|
-
mcpServersKey: "mcp",
|
|
1861
|
-
mcpPath: "opencode.json",
|
|
1862
|
-
typeLocal: "local",
|
|
1863
|
-
typeRemote: "remote",
|
|
1864
|
-
command: [
|
|
1865
|
-
"npx",
|
|
1866
|
-
"-y",
|
|
1867
|
-
"@sveltejs/mcp"
|
|
1868
|
-
],
|
|
1869
|
-
args: null
|
|
1870
|
-
},
|
|
1871
|
-
vscode: {
|
|
1872
|
-
agentPath: "AGENTS.md",
|
|
1873
|
-
mcpServersKey: "servers",
|
|
1874
|
-
mcpPath: ".vscode/mcp.json"
|
|
1875
|
-
},
|
|
1876
|
-
other: { other: true }
|
|
1877
|
-
};
|
|
1878
|
-
const filesAdded = [];
|
|
1879
|
-
const filesExistingAlready = [];
|
|
1880
|
-
const agentFile = getSharedFiles().filter((file) => file.include.includes("mcp")).find((file) => file.name === "AGENTS.md");
|
|
1881
|
-
for (const ide of options$7.ide) {
|
|
1882
|
-
const value = configurator[ide];
|
|
1883
|
-
if (value === void 0) continue;
|
|
1884
|
-
if ("other" in value) continue;
|
|
1885
|
-
const { mcpServersKey, agentPath, mcpPath, typeLocal, typeRemote, env, schema, command, args } = value;
|
|
1886
|
-
if (!filesAdded.includes(agentPath)) sv.file(agentPath, (content) => {
|
|
1887
|
-
if (content) {
|
|
1888
|
-
filesExistingAlready.push(agentPath);
|
|
1889
|
-
return content;
|
|
1890
|
-
}
|
|
1891
|
-
filesAdded.push(agentPath);
|
|
1892
|
-
return agentFile?.contents ?? "";
|
|
1893
|
-
});
|
|
1894
|
-
sv.file(mcpPath, (content) => {
|
|
1895
|
-
const { data, generateCode } = parseJson(content);
|
|
1896
|
-
if (schema) data["$schema"] = schema;
|
|
1897
|
-
const key = mcpServersKey ?? "mcpServers";
|
|
1898
|
-
data[key] ??= {};
|
|
1899
|
-
data[key].svelte = options$7.setup === "local" ? getLocalConfig({
|
|
1900
|
-
type: typeLocal,
|
|
1901
|
-
env,
|
|
1902
|
-
command,
|
|
1903
|
-
args
|
|
1904
|
-
}) : getRemoteConfig({ type: typeRemote });
|
|
1905
|
-
return generateCode();
|
|
1906
|
-
});
|
|
1907
|
-
}
|
|
1908
|
-
if (filesExistingAlready.length > 0) {
|
|
1909
|
-
const highlighter = getHighlighter();
|
|
1910
|
-
T.warn(`${filesExistingAlready.map((path$1) => highlighter.path(path$1)).join(", ")} already exists, we didn't touch ${filesExistingAlready.length > 1 ? "them" : "it"}. See ${highlighter.website("https://svelte.dev/docs/mcp/overview#Usage")} for manual setup.`);
|
|
1911
|
-
}
|
|
1912
|
-
},
|
|
1913
|
-
nextSteps({ highlighter, options: options$7 }) {
|
|
1914
|
-
const steps = [];
|
|
1915
|
-
if (options$7.ide.includes("other")) steps.push(`For other clients: ${highlighter.website(`https://svelte.dev/docs/mcp/${options$7.setup}-setup#Other-clients`)}`);
|
|
1916
|
-
return steps;
|
|
1917
|
-
}
|
|
1918
|
-
});
|
|
1919
|
-
|
|
1920
|
-
//#endregion
|
|
1921
|
-
//#region lib/addons/playwright/index.ts
|
|
1922
|
-
var playwright_default = defineAddon({
|
|
1923
|
-
id: "playwright",
|
|
1924
|
-
shortDescription: "browser testing",
|
|
1925
|
-
homepage: "https://playwright.dev",
|
|
1926
|
-
options: {},
|
|
1927
|
-
run: ({ sv, typescript, files }) => {
|
|
1928
|
-
const ext = typescript ? "ts" : "js";
|
|
1929
|
-
sv.devDependency("@playwright/test", "^1.57.0");
|
|
1930
|
-
sv.file(files.package, (content) => {
|
|
1931
|
-
const { data, generateCode } = parseJson(content);
|
|
1932
|
-
data.scripts ??= {};
|
|
1933
|
-
const scripts = data.scripts;
|
|
1934
|
-
const TEST_CMD = "playwright test";
|
|
1935
|
-
const RUN_TEST = "npm run test:e2e";
|
|
1936
|
-
scripts["test:e2e"] ??= TEST_CMD;
|
|
1937
|
-
scripts["test"] ??= RUN_TEST;
|
|
1938
|
-
if (!scripts["test"].includes(RUN_TEST)) scripts["test"] += ` && ${RUN_TEST}`;
|
|
1939
|
-
return generateCode();
|
|
1940
|
-
});
|
|
1941
|
-
sv.file(files.gitignore, (content) => {
|
|
1942
|
-
if (!content) return content;
|
|
1943
|
-
if (content.includes("test-results")) return content;
|
|
1944
|
-
return "test-results\n" + content.trim();
|
|
1945
|
-
});
|
|
1946
|
-
sv.file(`e2e/demo.test.${ext}`, (content) => {
|
|
1947
|
-
if (content) return content;
|
|
1948
|
-
return dedent_default`
|
|
1949
|
-
import { expect, test } from '@playwright/test';
|
|
1950
|
-
|
|
1951
|
-
test('home page has expected h1', async ({ page }) => {
|
|
1952
|
-
await page.goto('/');
|
|
1953
|
-
await expect(page.locator('h1')).toBeVisible();
|
|
1954
|
-
});
|
|
1955
|
-
`;
|
|
1956
|
-
});
|
|
1957
|
-
sv.file(`playwright.config.${ext}`, (content) => {
|
|
1958
|
-
const { ast, generateCode } = parseScript(content);
|
|
1959
|
-
const defineConfig = parseExpression("defineConfig({})");
|
|
1960
|
-
const { value: defaultExport } = createDefault(ast, { fallback: defineConfig });
|
|
1961
|
-
const config = {
|
|
1962
|
-
webServer: {
|
|
1963
|
-
command: "npm run build && npm run preview",
|
|
1964
|
-
port: 4173
|
|
1965
|
-
},
|
|
1966
|
-
testDir: "e2e"
|
|
1967
|
-
};
|
|
1968
|
-
if (defaultExport.type === "CallExpression" && defaultExport.arguments[0]?.type === "ObjectExpression") {
|
|
1969
|
-
addNamed(ast, {
|
|
1970
|
-
imports: ["defineConfig"],
|
|
1971
|
-
from: "@playwright/test"
|
|
1972
|
-
});
|
|
1973
|
-
overrideProperties(defaultExport.arguments[0], config);
|
|
1974
|
-
} else if (defaultExport.type === "ObjectExpression") overrideProperties(defaultExport, config);
|
|
1975
|
-
else T.warn("Unexpected playwright config for playwright add-on. Could not update.");
|
|
1976
|
-
return generateCode();
|
|
1977
|
-
});
|
|
1978
|
-
}
|
|
1979
|
-
});
|
|
1980
|
-
|
|
1981
|
-
//#endregion
|
|
1982
|
-
//#region lib/addons/prettier/index.ts
|
|
1983
|
-
var prettier_default = defineAddon({
|
|
1984
|
-
id: "prettier",
|
|
1985
|
-
shortDescription: "formatter",
|
|
1986
|
-
homepage: "https://prettier.io",
|
|
1987
|
-
options: {},
|
|
1988
|
-
run: ({ sv, dependencyVersion, files }) => {
|
|
1989
|
-
const tailwindcssInstalled = Boolean(dependencyVersion("tailwindcss"));
|
|
1990
|
-
if (tailwindcssInstalled) sv.devDependency("prettier-plugin-tailwindcss", "^0.7.2");
|
|
1991
|
-
sv.devDependency("prettier", "^3.7.4");
|
|
1992
|
-
sv.devDependency("prettier-plugin-svelte", "^3.4.0");
|
|
1993
|
-
sv.file(files.prettierignore, (content) => {
|
|
1994
|
-
if (content) return content;
|
|
1995
|
-
return dedent_default`
|
|
1996
|
-
# Package Managers
|
|
1997
|
-
package-lock.json
|
|
1998
|
-
pnpm-lock.yaml
|
|
1999
|
-
yarn.lock
|
|
2000
|
-
bun.lock
|
|
2001
|
-
bun.lockb
|
|
2002
|
-
|
|
2003
|
-
# Miscellaneous
|
|
2004
|
-
/static/
|
|
2005
|
-
`;
|
|
2006
|
-
});
|
|
2007
|
-
sv.file(files.prettierrc, (content) => {
|
|
2008
|
-
let data, generateCode;
|
|
2009
|
-
try {
|
|
2010
|
-
({data, generateCode} = parseJson(content));
|
|
2011
|
-
} catch {
|
|
2012
|
-
T.warn(`A ${import_picocolors$1.default.yellow(".prettierrc")} config already exists and cannot be parsed as JSON. Skipping initialization.`);
|
|
2013
|
-
return content;
|
|
2014
|
-
}
|
|
2015
|
-
if (Object.keys(data).length === 0) {
|
|
2016
|
-
data.useTabs = true;
|
|
2017
|
-
data.singleQuote = true;
|
|
2018
|
-
data.trailingComma = "none";
|
|
2019
|
-
data.printWidth = 100;
|
|
2020
|
-
}
|
|
2021
|
-
data.plugins ??= [];
|
|
2022
|
-
const plugins$1 = data.plugins;
|
|
2023
|
-
{
|
|
2024
|
-
const PLUGIN_NAME = "prettier-plugin-svelte";
|
|
2025
|
-
if (!plugins$1.includes(PLUGIN_NAME)) plugins$1.push(PLUGIN_NAME);
|
|
2026
|
-
}
|
|
2027
|
-
if (tailwindcssInstalled) {
|
|
2028
|
-
const PLUGIN_NAME = "prettier-plugin-tailwindcss";
|
|
2029
|
-
if (!plugins$1.includes(PLUGIN_NAME)) plugins$1.push(PLUGIN_NAME);
|
|
2030
|
-
data.tailwindStylesheet ??= files.getRelative({ to: files.stylesheet });
|
|
2031
|
-
}
|
|
2032
|
-
data.overrides ??= [];
|
|
2033
|
-
const overrides = data.overrides;
|
|
2034
|
-
if (!overrides.find((o) => o?.options?.parser === "svelte")) overrides.push({
|
|
2035
|
-
files: "*.svelte",
|
|
2036
|
-
options: { parser: "svelte" }
|
|
2037
|
-
});
|
|
2038
|
-
return generateCode();
|
|
2039
|
-
});
|
|
2040
|
-
const eslintVersion = dependencyVersion("eslint");
|
|
2041
|
-
const eslintInstalled = hasEslint(eslintVersion);
|
|
2042
|
-
sv.file(files.package, (content) => {
|
|
2043
|
-
const { data, generateCode } = parseJson(content);
|
|
2044
|
-
data.scripts ??= {};
|
|
2045
|
-
const scripts = data.scripts;
|
|
2046
|
-
const CHECK_CMD = "prettier --check .";
|
|
2047
|
-
scripts["format"] ??= "prettier --write .";
|
|
2048
|
-
if (eslintInstalled) {
|
|
2049
|
-
scripts["lint"] ??= `${CHECK_CMD} && eslint .`;
|
|
2050
|
-
if (!scripts["lint"].includes(CHECK_CMD)) scripts["lint"] += ` && ${CHECK_CMD}`;
|
|
2051
|
-
} else scripts["lint"] ??= CHECK_CMD;
|
|
2052
|
-
return generateCode();
|
|
2053
|
-
});
|
|
2054
|
-
if (eslintVersion?.startsWith(SUPPORTED_ESLINT_VERSION) === false) T.warn(`An older major version of ${import_picocolors$1.default.yellow("eslint")} was detected. Skipping ${import_picocolors$1.default.yellow("eslint-config-prettier")} installation.`);
|
|
2055
|
-
if (eslintInstalled) {
|
|
2056
|
-
sv.devDependency("eslint-config-prettier", "^10.1.8");
|
|
2057
|
-
sv.file(files.eslintConfig, addEslintConfigPrettier);
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
});
|
|
2061
|
-
const SUPPORTED_ESLINT_VERSION = "9";
|
|
2062
|
-
function hasEslint(version) {
|
|
2063
|
-
return !!version && version.startsWith(SUPPORTED_ESLINT_VERSION);
|
|
2064
|
-
}
|
|
2065
|
-
|
|
2066
|
-
//#endregion
|
|
2067
|
-
//#region lib/addons/storybook/index.ts
|
|
2068
|
-
var storybook_default = defineAddon({
|
|
2069
|
-
id: "storybook",
|
|
2070
|
-
shortDescription: "frontend workshop",
|
|
2071
|
-
homepage: "https://storybook.js.org",
|
|
2072
|
-
options: {},
|
|
2073
|
-
setup: ({ runsAfter }) => {
|
|
2074
|
-
runsAfter("vitest");
|
|
2075
|
-
runsAfter("eslint");
|
|
2076
|
-
},
|
|
2077
|
-
run: async ({ sv }) => {
|
|
2078
|
-
const args = [
|
|
2079
|
-
`create-storybook@latest`,
|
|
2080
|
-
"--skip-install",
|
|
2081
|
-
"--no-dev"
|
|
2082
|
-
];
|
|
2083
|
-
if (process.env.NODE_ENV?.toLowerCase() === "test") args.push(...["--no-features", "--yes"]);
|
|
2084
|
-
await sv.execute(args, "inherit");
|
|
2085
|
-
sv.devDependency(`@types/node`, getNodeTypesVersion());
|
|
2086
|
-
}
|
|
2087
|
-
});
|
|
2088
|
-
|
|
2089
|
-
//#endregion
|
|
2090
|
-
//#region lib/addons/sveltekit-adapter/index.ts
|
|
2091
|
-
const adapters = [
|
|
2092
|
-
{
|
|
2093
|
-
id: "auto",
|
|
2094
|
-
package: "@sveltejs/adapter-auto",
|
|
2095
|
-
version: "^7.0.0"
|
|
2096
|
-
},
|
|
2097
|
-
{
|
|
2098
|
-
id: "node",
|
|
2099
|
-
package: "@sveltejs/adapter-node",
|
|
2100
|
-
version: "^5.4.0"
|
|
2101
|
-
},
|
|
2102
|
-
{
|
|
2103
|
-
id: "static",
|
|
2104
|
-
package: "@sveltejs/adapter-static",
|
|
2105
|
-
version: "^3.0.10"
|
|
2106
|
-
},
|
|
2107
|
-
{
|
|
2108
|
-
id: "vercel",
|
|
2109
|
-
package: "@sveltejs/adapter-vercel",
|
|
2110
|
-
version: "^6.2.0"
|
|
2111
|
-
},
|
|
2112
|
-
{
|
|
2113
|
-
id: "cloudflare",
|
|
2114
|
-
package: "@sveltejs/adapter-cloudflare",
|
|
2115
|
-
version: "^7.2.4"
|
|
2116
|
-
},
|
|
2117
|
-
{
|
|
2118
|
-
id: "netlify",
|
|
2119
|
-
package: "@sveltejs/adapter-netlify",
|
|
2120
|
-
version: "^5.2.4"
|
|
2121
|
-
}
|
|
2122
|
-
];
|
|
2123
|
-
const options$2 = defineAddonOptions().add("adapter", {
|
|
2124
|
-
type: "select",
|
|
2125
|
-
question: "Which SvelteKit adapter would you like to use?",
|
|
2126
|
-
default: "auto",
|
|
2127
|
-
options: adapters.map((p) => ({
|
|
2128
|
-
value: p.id,
|
|
2129
|
-
label: p.id,
|
|
2130
|
-
hint: p.package
|
|
2131
|
-
}))
|
|
2132
|
-
}).add("cfTarget", {
|
|
2133
|
-
condition: (options$7) => options$7.adapter === "cloudflare",
|
|
2134
|
-
type: "select",
|
|
2135
|
-
question: "Are you deploying to Workers (assets) or Pages?",
|
|
2136
|
-
default: "workers",
|
|
2137
|
-
options: [{
|
|
2138
|
-
value: "workers",
|
|
2139
|
-
label: "Workers",
|
|
2140
|
-
hint: "Recommended way to deploy to Cloudflare"
|
|
2141
|
-
}, {
|
|
2142
|
-
value: "pages",
|
|
2143
|
-
label: "Pages"
|
|
2144
|
-
}]
|
|
2145
|
-
}).build();
|
|
2146
|
-
var sveltekit_adapter_default = defineAddon({
|
|
2147
|
-
id: "sveltekit-adapter",
|
|
2148
|
-
alias: "adapter",
|
|
2149
|
-
shortDescription: "deployment",
|
|
2150
|
-
homepage: "https://svelte.dev/docs/kit/adapters",
|
|
2151
|
-
options: options$2,
|
|
2152
|
-
setup: ({ kit, unsupported }) => {
|
|
2153
|
-
if (!kit) unsupported("Requires SvelteKit");
|
|
2154
|
-
},
|
|
2155
|
-
run: ({ sv, options: options$7, files, cwd: cwd$1, packageManager, typescript }) => {
|
|
2156
|
-
const adapter = adapters.find((a) => a.id === options$7.adapter);
|
|
2157
|
-
sv.file(files.package, (content) => {
|
|
2158
|
-
const { data, generateCode } = parseJson(content);
|
|
2159
|
-
const devDeps = data["devDependencies"];
|
|
2160
|
-
for (const pkg of Object.keys(devDeps)) if (pkg.startsWith("@sveltejs/adapter-")) delete devDeps[pkg];
|
|
2161
|
-
if (options$7.adapter === "cloudflare") {
|
|
2162
|
-
if (options$7.cfTarget === "workers") data.scripts.preview = "wrangler dev .svelte-kit/cloudflare/_worker.js --port 4173";
|
|
2163
|
-
else if (options$7.cfTarget === "pages") data.scripts.preview = "wrangler pages dev .svelte-kit/cloudflare --port 4173";
|
|
2164
|
-
}
|
|
2165
|
-
return generateCode();
|
|
2166
|
-
});
|
|
2167
|
-
sv.devDependency(adapter.package, adapter.version);
|
|
2168
|
-
sv.file(files.svelteConfig, (content) => {
|
|
2169
|
-
const { ast, comments, generateCode } = parseScript(content);
|
|
2170
|
-
const adapterImportDecl = ast.body.filter((n) => n.type === "ImportDeclaration").find((importDecl) => typeof importDecl.source.value === "string" && importDecl.source.value.startsWith("@sveltejs/adapter-") && importDecl.importKind === "value");
|
|
2171
|
-
let adapterName = "adapter";
|
|
2172
|
-
if (adapterImportDecl) {
|
|
2173
|
-
adapterImportDecl.source.value = adapter.package;
|
|
2174
|
-
adapterImportDecl.source.raw = void 0;
|
|
2175
|
-
adapterName = adapterImportDecl.specifiers?.find((s) => s.type === "ImportDefaultSpecifier")?.local?.name;
|
|
2176
|
-
} else addDefault(ast, {
|
|
2177
|
-
from: adapter.package,
|
|
2178
|
-
as: adapterName
|
|
2179
|
-
});
|
|
2180
|
-
const { value: config } = createDefault(ast, { fallback: create({}) });
|
|
2181
|
-
overrideProperties(config, { kit: { adapter: createCall({
|
|
2182
|
-
name: adapterName,
|
|
2183
|
-
args: [],
|
|
2184
|
-
useIdentifiers: true
|
|
2185
|
-
}) } });
|
|
2186
|
-
if (adapter.package !== "@sveltejs/adapter-auto") {
|
|
2187
|
-
const fallback = create({});
|
|
2188
|
-
const cfgKitValue = property(config, {
|
|
2189
|
-
name: "kit",
|
|
2190
|
-
fallback
|
|
2191
|
-
});
|
|
2192
|
-
comments.remove((c) => c.loc && cfgKitValue.loc && c.loc.start.line >= cfgKitValue.loc.start.line && c.loc.end.line <= cfgKitValue.loc.end.line);
|
|
2193
|
-
}
|
|
2194
|
-
return generateCode();
|
|
2195
|
-
});
|
|
2196
|
-
if (adapter.package === "@sveltejs/adapter-cloudflare") {
|
|
2197
|
-
sv.devDependency("wrangler", "^4.56.0");
|
|
2198
|
-
const configFormat = fileExists(cwd$1, "wrangler.toml") ? "toml" : "jsonc";
|
|
2199
|
-
sv.file(`wrangler.${configFormat}`, (content) => {
|
|
2200
|
-
const { data, generateCode } = configFormat === "jsonc" ? parseJson(content) : parseToml(content);
|
|
2201
|
-
if (configFormat === "jsonc") data.$schema ??= "./node_modules/wrangler/config-schema.json";
|
|
2202
|
-
if (!data.name) data.name = sanitizeName(parseJson(readFile(cwd$1, files.package)).data.name, "wrangler");
|
|
2203
|
-
data.compatibility_date ??= (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2204
|
-
data.compatibility_flags ??= [];
|
|
2205
|
-
if (!data.compatibility_flags.includes("nodejs_compat") && !data.compatibility_flags.includes("nodejs_als")) data.compatibility_flags.push("nodejs_als");
|
|
2206
|
-
switch (options$7.cfTarget) {
|
|
2207
|
-
case "workers":
|
|
2208
|
-
data.main = ".svelte-kit/cloudflare/_worker.js";
|
|
2209
|
-
data.assets ??= {};
|
|
2210
|
-
data.assets.binding = "ASSETS";
|
|
2211
|
-
data.assets.directory = ".svelte-kit/cloudflare";
|
|
2212
|
-
data.workers_dev = true;
|
|
2213
|
-
data.preview_urls = true;
|
|
2214
|
-
break;
|
|
2215
|
-
case "pages":
|
|
2216
|
-
data.pages_build_output_dir = ".svelte-kit/cloudflare";
|
|
2217
|
-
break;
|
|
2218
|
-
}
|
|
2219
|
-
return generateCode();
|
|
2220
|
-
});
|
|
2221
|
-
const jsconfig = fileExists(cwd$1, "jsconfig.json");
|
|
2222
|
-
if (typescript || jsconfig) {
|
|
2223
|
-
sv.file(files.gitignore, (content) => {
|
|
2224
|
-
return content.includes(".wrangler") && content.includes("worker-configuration.d.ts") ? content : `${content.trimEnd()}\n\n# Cloudflare Types\n/worker-configuration.d.ts`;
|
|
2225
|
-
});
|
|
2226
|
-
sv.file(files.package, (content) => {
|
|
2227
|
-
const { data, generateCode } = parseJson(content);
|
|
2228
|
-
data.scripts ??= {};
|
|
2229
|
-
data.scripts.types = "wrangler types";
|
|
2230
|
-
const { command, args } = resolveCommand(packageManager, "run", ["types"]);
|
|
2231
|
-
data.scripts.prepare = data.scripts.prepare ? `${command} ${args.join(" ")} && ${data.scripts.prepare}` : `${command} ${args.join(" ")}`;
|
|
2232
|
-
return generateCode();
|
|
2233
|
-
});
|
|
2234
|
-
sv.file(`${jsconfig ? "jsconfig" : "tsconfig"}.json`, (content) => {
|
|
2235
|
-
const { data, generateCode } = parseJson(content);
|
|
2236
|
-
data.compilerOptions ??= {};
|
|
2237
|
-
data.compilerOptions.types ??= [];
|
|
2238
|
-
data.compilerOptions.types.push("./worker-configuration.d.ts");
|
|
2239
|
-
return generateCode();
|
|
2240
|
-
});
|
|
2241
|
-
sv.file("src/app.d.ts", (content) => {
|
|
2242
|
-
const { ast, generateCode } = parseScript(content);
|
|
2243
|
-
const platform = addGlobalAppInterface(ast, { name: "Platform" });
|
|
2244
|
-
if (!platform) throw new Error("Failed detecting `platform` interface in `src/app.d.ts`");
|
|
2245
|
-
platform.body.body.push(createCloudflarePlatformType("env", "Env"), createCloudflarePlatformType("ctx", "ExecutionContext"), createCloudflarePlatformType("caches", "CacheStorage"), createCloudflarePlatformType("cf", "IncomingRequestCfProperties", true));
|
|
2246
|
-
return generateCode();
|
|
2247
|
-
});
|
|
2248
|
-
}
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
|
-
});
|
|
2252
|
-
function createCloudflarePlatformType(name, value, optional = false) {
|
|
2253
|
-
return {
|
|
2254
|
-
type: "TSPropertySignature",
|
|
2255
|
-
key: {
|
|
2256
|
-
type: "Identifier",
|
|
2257
|
-
name
|
|
2258
|
-
},
|
|
2259
|
-
computed: false,
|
|
2260
|
-
optional,
|
|
2261
|
-
typeAnnotation: {
|
|
2262
|
-
type: "TSTypeAnnotation",
|
|
2263
|
-
typeAnnotation: {
|
|
2264
|
-
type: "TSTypeReference",
|
|
2265
|
-
typeName: {
|
|
2266
|
-
type: "Identifier",
|
|
2267
|
-
name: value
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2271
|
-
};
|
|
2272
|
-
}
|
|
2273
|
-
|
|
2274
|
-
//#endregion
|
|
2275
|
-
//#region lib/core/tooling/css/index.ts
|
|
2276
|
-
function addAtRule(node, options$7) {
|
|
2277
|
-
let atRule = node.children.filter((x) => x.type === "Atrule").find((x) => x.name === options$7.name && x.prelude === options$7.params);
|
|
2278
|
-
if (atRule) return atRule;
|
|
2279
|
-
atRule = {
|
|
2280
|
-
type: "Atrule",
|
|
2281
|
-
name: options$7.name,
|
|
2282
|
-
prelude: options$7.params,
|
|
2283
|
-
block: null,
|
|
2284
|
-
start: 0,
|
|
2285
|
-
end: 0
|
|
2286
|
-
};
|
|
2287
|
-
if (!options$7.append) node.children.unshift(atRule);
|
|
2288
|
-
else node.children.push(atRule);
|
|
2289
|
-
return atRule;
|
|
2290
|
-
}
|
|
2291
|
-
|
|
2292
|
-
//#endregion
|
|
2293
|
-
//#region lib/addons/tailwindcss/index.ts
|
|
2294
|
-
const plugins = [{
|
|
2295
|
-
id: "typography",
|
|
2296
|
-
package: "@tailwindcss/typography",
|
|
2297
|
-
version: "^0.5.19"
|
|
2298
|
-
}, {
|
|
2299
|
-
id: "forms",
|
|
2300
|
-
package: "@tailwindcss/forms",
|
|
2301
|
-
version: "^0.5.10"
|
|
2302
|
-
}];
|
|
2303
|
-
const options$1 = defineAddonOptions().add("plugins", {
|
|
2304
|
-
type: "multiselect",
|
|
2305
|
-
question: "Which plugins would you like to add?",
|
|
2306
|
-
options: plugins.map((p) => ({
|
|
2307
|
-
value: p.id,
|
|
2308
|
-
label: p.id,
|
|
2309
|
-
hint: p.package
|
|
2310
|
-
})),
|
|
2311
|
-
default: [],
|
|
2312
|
-
required: false
|
|
2313
|
-
}).build();
|
|
2314
|
-
var tailwindcss_default = defineAddon({
|
|
2315
|
-
id: "tailwindcss",
|
|
2316
|
-
alias: "tailwind",
|
|
2317
|
-
shortDescription: "css framework",
|
|
2318
|
-
homepage: "https://tailwindcss.com",
|
|
2319
|
-
options: options$1,
|
|
2320
|
-
run: ({ sv, options: options$7, files, kit, dependencyVersion, typescript }) => {
|
|
2321
|
-
const prettierInstalled = Boolean(dependencyVersion("prettier"));
|
|
2322
|
-
sv.devDependency("tailwindcss", "^4.1.17");
|
|
2323
|
-
sv.devDependency("@tailwindcss/vite", "^4.1.17");
|
|
2324
|
-
sv.pnpmBuildDependency("@tailwindcss/oxide");
|
|
2325
|
-
if (prettierInstalled) sv.devDependency("prettier-plugin-tailwindcss", "^0.7.2");
|
|
2326
|
-
for (const plugin of plugins) {
|
|
2327
|
-
if (!options$7.plugins.includes(plugin.id)) continue;
|
|
2328
|
-
sv.devDependency(plugin.package, plugin.version);
|
|
2329
|
-
}
|
|
2330
|
-
sv.file(files.viteConfig, (content) => {
|
|
2331
|
-
const { ast, generateCode } = parseScript(content);
|
|
2332
|
-
const vitePluginName = "tailwindcss";
|
|
2333
|
-
addDefault(ast, {
|
|
2334
|
-
as: vitePluginName,
|
|
2335
|
-
from: "@tailwindcss/vite"
|
|
2336
|
-
});
|
|
2337
|
-
addPlugin(ast, {
|
|
2338
|
-
code: `${vitePluginName}()`,
|
|
2339
|
-
mode: "prepend"
|
|
2340
|
-
});
|
|
2341
|
-
return generateCode();
|
|
2342
|
-
});
|
|
2343
|
-
sv.file(files.stylesheet, (content) => {
|
|
2344
|
-
const { ast, generateCode } = parseCss(content);
|
|
2345
|
-
for (const plugin of plugins) {
|
|
2346
|
-
if (!options$7.plugins.includes(plugin.id)) continue;
|
|
2347
|
-
addAtRule(ast, {
|
|
2348
|
-
name: "plugin",
|
|
2349
|
-
params: `'${plugin.package}'`,
|
|
2350
|
-
append: false
|
|
2351
|
-
});
|
|
2352
|
-
}
|
|
2353
|
-
addAtRule(ast, {
|
|
2354
|
-
name: "import",
|
|
2355
|
-
params: `'tailwindcss'`,
|
|
2356
|
-
append: false
|
|
2357
|
-
});
|
|
2358
|
-
return generateCode();
|
|
2359
|
-
});
|
|
2360
|
-
if (!kit) {
|
|
2361
|
-
const appSvelte = "src/App.svelte";
|
|
2362
|
-
const stylesheetRelative = files.getRelative({
|
|
2363
|
-
from: appSvelte,
|
|
2364
|
-
to: files.stylesheet
|
|
2365
|
-
});
|
|
2366
|
-
sv.file(appSvelte, (content) => {
|
|
2367
|
-
const { ast, generateCode } = parseSvelte(content);
|
|
2368
|
-
const scriptAst = ensureScript(ast, { langTs: typescript });
|
|
2369
|
-
addEmpty(scriptAst, { from: stylesheetRelative });
|
|
2370
|
-
return generateCode();
|
|
2371
|
-
});
|
|
2372
|
-
} else {
|
|
2373
|
-
const layoutSvelte = `${kit?.routesDirectory}/+layout.svelte`;
|
|
2374
|
-
const stylesheetRelative = files.getRelative({
|
|
2375
|
-
from: layoutSvelte,
|
|
2376
|
-
to: files.stylesheet
|
|
2377
|
-
});
|
|
2378
|
-
sv.file(layoutSvelte, (content) => {
|
|
2379
|
-
const { ast, generateCode } = parseSvelte(content);
|
|
2380
|
-
const scriptAst = ensureScript(ast, { langTs: typescript });
|
|
2381
|
-
addEmpty(scriptAst, { from: stylesheetRelative });
|
|
2382
|
-
if (content.length === 0) {
|
|
2383
|
-
const svelteVersion = dependencyVersion("svelte");
|
|
2384
|
-
if (!svelteVersion) throw new Error("Failed to determine svelte version");
|
|
2385
|
-
addSlot(ast, { svelteVersion });
|
|
2386
|
-
}
|
|
2387
|
-
return generateCode();
|
|
2388
|
-
});
|
|
2389
|
-
}
|
|
2390
|
-
sv.file(files.vscodeSettings, (content) => {
|
|
2391
|
-
const { data, generateCode } = parseJson(content);
|
|
2392
|
-
data["files.associations"] ??= {};
|
|
2393
|
-
data["files.associations"]["*.css"] = "tailwindcss";
|
|
2394
|
-
return generateCode();
|
|
2395
|
-
});
|
|
2396
|
-
if (prettierInstalled) sv.file(files.prettierrc, (content) => {
|
|
2397
|
-
const { data, generateCode } = parseJson(content);
|
|
2398
|
-
data.plugins ??= [];
|
|
2399
|
-
const plugins$1 = data.plugins;
|
|
2400
|
-
const PLUGIN_NAME = "prettier-plugin-tailwindcss";
|
|
2401
|
-
if (!plugins$1.includes(PLUGIN_NAME)) plugins$1.push(PLUGIN_NAME);
|
|
2402
|
-
data.tailwindStylesheet ??= files.getRelative({ to: files.stylesheet });
|
|
2403
|
-
return generateCode();
|
|
2404
|
-
});
|
|
2405
|
-
}
|
|
2406
|
-
});
|
|
2407
|
-
|
|
2408
|
-
//#endregion
|
|
2409
|
-
//#region lib/addons/vitest-addon/index.ts
|
|
2410
|
-
const options = defineAddonOptions().add("usages", {
|
|
2411
|
-
question: "What do you want to use vitest for?",
|
|
2412
|
-
type: "multiselect",
|
|
2413
|
-
default: ["unit", "component"],
|
|
2414
|
-
options: [{
|
|
2415
|
-
value: "unit",
|
|
2416
|
-
label: "unit testing"
|
|
2417
|
-
}, {
|
|
2418
|
-
value: "component",
|
|
2419
|
-
label: "component testing"
|
|
2420
|
-
}],
|
|
2421
|
-
required: true
|
|
2422
|
-
}).build();
|
|
2423
|
-
let vitestV3Installed = false;
|
|
2424
|
-
var vitest_addon_default = defineAddon({
|
|
2425
|
-
id: "vitest",
|
|
2426
|
-
shortDescription: "unit testing",
|
|
2427
|
-
homepage: "https://vitest.dev",
|
|
2428
|
-
options,
|
|
2429
|
-
run: ({ sv, files, typescript, kit, options: options$7, dependencyVersion }) => {
|
|
2430
|
-
const ext = typescript ? "ts" : "js";
|
|
2431
|
-
const unitTesting = options$7.usages.includes("unit");
|
|
2432
|
-
const componentTesting = options$7.usages.includes("component");
|
|
2433
|
-
vitestV3Installed = (dependencyVersion("vitest") ?? "").replaceAll("^", "").replaceAll("~", "")?.startsWith("3.");
|
|
2434
|
-
sv.devDependency("vitest", "^4.0.15");
|
|
2435
|
-
if (componentTesting) {
|
|
2436
|
-
sv.devDependency("@vitest/browser-playwright", "^4.0.15");
|
|
2437
|
-
sv.devDependency("vitest-browser-svelte", "^2.0.1");
|
|
2438
|
-
sv.devDependency("playwright", "^1.57.0");
|
|
2439
|
-
}
|
|
2440
|
-
sv.file(files.package, (content) => {
|
|
2441
|
-
const { data, generateCode } = parseJson(content);
|
|
2442
|
-
data.scripts ??= {};
|
|
2443
|
-
const scripts = data.scripts;
|
|
2444
|
-
const TEST_CMD = "vitest";
|
|
2445
|
-
const RUN_TEST = "npm run test:unit -- --run";
|
|
2446
|
-
scripts["test:unit"] ??= TEST_CMD;
|
|
2447
|
-
scripts["test"] ??= RUN_TEST;
|
|
2448
|
-
if (!scripts["test"].includes(RUN_TEST)) scripts["test"] += ` && ${RUN_TEST}`;
|
|
2449
|
-
return generateCode();
|
|
2450
|
-
});
|
|
2451
|
-
if (unitTesting) sv.file(`src/demo.spec.${ext}`, (content) => {
|
|
2452
|
-
if (content) return content;
|
|
2453
|
-
return dedent_default`
|
|
2454
|
-
import { describe, it, expect } from 'vitest';
|
|
2455
|
-
|
|
2456
|
-
describe('sum test', () => {
|
|
2457
|
-
it('adds 1 + 2 to equal 3', () => {
|
|
2458
|
-
expect(1 + 2).toBe(3);
|
|
2459
|
-
});
|
|
2460
|
-
});
|
|
2461
|
-
`;
|
|
2462
|
-
});
|
|
2463
|
-
if (componentTesting) {
|
|
2464
|
-
const fileName = kit ? `${kit.routesDirectory}/page.svelte.spec.${ext}` : `src/App.svelte.test.${ext}`;
|
|
2465
|
-
sv.file(fileName, (content) => {
|
|
2466
|
-
if (content) return content;
|
|
2467
|
-
return dedent_default`
|
|
2468
|
-
import { page } from 'vitest/browser';
|
|
2469
|
-
import { describe, expect, it } from 'vitest';
|
|
2470
|
-
import { render } from 'vitest-browser-svelte';
|
|
2471
|
-
${kit ? "import Page from './+page.svelte';" : "import App from './App.svelte';"}
|
|
2472
|
-
|
|
2473
|
-
describe('${kit ? "/+page.svelte" : "App.svelte"}', () => {
|
|
2474
|
-
it('should render h1', async () => {
|
|
2475
|
-
render(${kit ? "Page" : "App"});
|
|
2476
|
-
|
|
2477
|
-
const heading = page.getByRole('heading', { level: 1 });
|
|
2478
|
-
await expect.element(heading).toBeInTheDocument();
|
|
2479
|
-
});
|
|
2480
|
-
});
|
|
2481
|
-
`;
|
|
2482
|
-
});
|
|
2483
|
-
}
|
|
2484
|
-
sv.file(files.viteConfig, (content) => {
|
|
2485
|
-
const { ast, generateCode } = parseScript(content);
|
|
2486
|
-
const clientObjectExpression = create({
|
|
2487
|
-
extends: `./${files.viteConfig}`,
|
|
2488
|
-
test: {
|
|
2489
|
-
name: "client",
|
|
2490
|
-
browser: {
|
|
2491
|
-
enabled: true,
|
|
2492
|
-
provider: createCall({
|
|
2493
|
-
name: "playwright",
|
|
2494
|
-
args: []
|
|
2495
|
-
}),
|
|
2496
|
-
instances: [{
|
|
2497
|
-
browser: "chromium",
|
|
2498
|
-
headless: true
|
|
2499
|
-
}]
|
|
2500
|
-
},
|
|
2501
|
-
include: ["src/**/*.svelte.{test,spec}.{js,ts}"],
|
|
2502
|
-
exclude: ["src/lib/server/**"]
|
|
2503
|
-
}
|
|
2504
|
-
});
|
|
2505
|
-
const serverObjectExpression = create({
|
|
2506
|
-
extends: `./${files.viteConfig}`,
|
|
2507
|
-
test: {
|
|
2508
|
-
name: "server",
|
|
2509
|
-
environment: "node",
|
|
2510
|
-
include: ["src/**/*.{test,spec}.{js,ts}"],
|
|
2511
|
-
exclude: ["src/**/*.svelte.{test,spec}.{js,ts}"]
|
|
2512
|
-
}
|
|
2513
|
-
});
|
|
2514
|
-
const viteConfig = getConfig(ast);
|
|
2515
|
-
const testObject = property(viteConfig, {
|
|
2516
|
-
name: "test",
|
|
2517
|
-
fallback: create({ expect: { requireAssertions: true } })
|
|
2518
|
-
});
|
|
2519
|
-
const workspaceArray = property(testObject, {
|
|
2520
|
-
name: "projects",
|
|
2521
|
-
fallback: create$1()
|
|
2522
|
-
});
|
|
2523
|
-
if (componentTesting) append(workspaceArray, clientObjectExpression);
|
|
2524
|
-
if (unitTesting) append(workspaceArray, serverObjectExpression);
|
|
2525
|
-
if (componentTesting) addNamed(ast, {
|
|
2526
|
-
imports: ["playwright"],
|
|
2527
|
-
from: "@vitest/browser-playwright"
|
|
2528
|
-
});
|
|
2529
|
-
const importName = "defineConfig";
|
|
2530
|
-
const { statement, alias } = find(ast, {
|
|
2531
|
-
name: importName,
|
|
2532
|
-
from: "vite"
|
|
2533
|
-
});
|
|
2534
|
-
if (statement) {
|
|
2535
|
-
addNamed(ast, {
|
|
2536
|
-
imports: { defineConfig: alias },
|
|
2537
|
-
from: "vitest/config"
|
|
2538
|
-
});
|
|
2539
|
-
remove(ast, {
|
|
2540
|
-
name: importName,
|
|
2541
|
-
from: "vite",
|
|
2542
|
-
statement
|
|
2543
|
-
});
|
|
2544
|
-
}
|
|
2545
|
-
return generateCode();
|
|
2546
|
-
});
|
|
2547
|
-
},
|
|
2548
|
-
nextSteps: ({ highlighter, typescript, options: options$7 }) => {
|
|
2549
|
-
const toReturn = [];
|
|
2550
|
-
if (vitestV3Installed) {
|
|
2551
|
-
if (options$7.usages.includes("component")) {
|
|
2552
|
-
toReturn.push(`Uninstall ${highlighter.command("@vitest/browser")} package`);
|
|
2553
|
-
toReturn.push(`Update usage from ${highlighter.command("'@vitest/browser...'")} to ${highlighter.command("'vitest/browser'")}`);
|
|
2554
|
-
}
|
|
2555
|
-
toReturn.push(`${highlighter.optional("Optional")} Check ${highlighter.path("./vite.config.ts")} and remove duplicate project definitions`);
|
|
2556
|
-
toReturn.push(`${highlighter.optional("Optional")} Remove ${highlighter.path("./vitest-setup-client" + (typescript ? ".ts" : ".js"))} file`);
|
|
2557
|
-
}
|
|
2558
|
-
return toReturn;
|
|
2559
|
-
}
|
|
2560
|
-
});
|
|
2561
|
-
|
|
2562
|
-
//#endregion
|
|
2563
|
-
//#region lib/addons/_config/official.ts
|
|
2564
|
-
const officialAddons = {
|
|
2565
|
-
prettier: prettier_default,
|
|
2566
|
-
eslint: eslint_default,
|
|
2567
|
-
vitest: vitest_addon_default,
|
|
2568
|
-
playwright: playwright_default,
|
|
2569
|
-
tailwindcss: tailwindcss_default,
|
|
2570
|
-
sveltekitAdapter: sveltekit_adapter_default,
|
|
2571
|
-
devtoolsJson: devtools_json_default,
|
|
2572
|
-
drizzle: drizzle_default,
|
|
2573
|
-
lucia: lucia_default,
|
|
2574
|
-
mdsvex: mdsvex_default,
|
|
2575
|
-
paraglide: paraglide_default,
|
|
2576
|
-
storybook: storybook_default,
|
|
2577
|
-
mcp: mcp_default
|
|
2578
|
-
};
|
|
2579
|
-
function getAddonDetails(id) {
|
|
2580
|
-
const details = Object.values(officialAddons).find((a) => a.id === id);
|
|
2581
|
-
if (!details) throw new Error(`Invalid add-on: ${id}`);
|
|
2582
|
-
return details;
|
|
2583
|
-
}
|
|
2584
|
-
|
|
2585
|
-
//#endregion
|
|
2586
|
-
export { setupAddons as a, installAddon as i, officialAddons as n, createWorkspace as o, applyAddons as r, getAddonDetails as t };
|