@evertondgn/solid-tooling 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +193 -634
  2. package/app-env.d.ts +7 -0
  3. package/dist/architecture-plugin.mjs +1 -1
  4. package/dist/backend-plugin.mjs +139 -4
  5. package/dist/cli.d.mts +1 -0
  6. package/dist/cli.mjs +42 -0
  7. package/dist/{constants-Jqvtbvxu.mjs → constants-BZJm7ouk.mjs} +1 -1
  8. package/dist/css-filename-CEHQd2gz.mjs +40 -0
  9. package/dist/css-filename.mjs +1 -40
  10. package/dist/fmt-9dlUYOMk.mjs +34 -0
  11. package/dist/fmt.mjs +1 -34
  12. package/dist/i18n-generate.d.mts +1 -0
  13. package/dist/i18n-generate.mjs +23 -0
  14. package/dist/i18nPlugin-CS7Gob6R.mjs +109 -0
  15. package/dist/knip.d.mts +2 -1
  16. package/dist/knip.mjs +7 -2
  17. package/dist/{lint-UTfyg7AR.mjs → lint-ehdtTKfw.mjs} +33 -86
  18. package/dist/lint.mjs +1 -1
  19. package/dist/playwright-teardown.mjs +1 -1
  20. package/dist/playwright.d.mts +2 -1
  21. package/dist/playwright.mjs +4 -3
  22. package/dist/prepareMessages-BXmY6Ko2.mjs +222 -0
  23. package/dist/project-plugin.mjs +2 -2
  24. package/dist/protocol-DSNNUYja.mjs +18 -0
  25. package/dist/publication/read-llms-index.d.mts +12 -0
  26. package/dist/publication/read-llms-index.mjs +1 -0
  27. package/dist/publication-CRUTOB0B.mjs +885 -0
  28. package/dist/readLlmsIndex-Gj4iTxAk.mjs +27 -0
  29. package/dist/stylelint.mjs +2 -2
  30. package/dist/test-locale--tuH0Np5.mjs +3 -0
  31. package/dist/types-DHu2Z_ot.d.mts +49 -0
  32. package/dist/vite.d.mts +33 -7
  33. package/dist/vite.mjs +415 -88
  34. package/dist/vitest-BjLr7iLf.mjs +362 -0
  35. package/dist/vitest.d.mts +3 -7
  36. package/dist/vitest.mjs +1 -129
  37. package/lefthook.yml +3 -1
  38. package/node-env.d.ts +1 -0
  39. package/package.json +32 -39
  40. package/tsconfig.app.json +4 -9
  41. package/virtual-modules.d.ts +90 -0
package/dist/vite.mjs CHANGED
@@ -1,19 +1,232 @@
1
- import { fmt } from "./fmt.mjs";
2
- import { t as lint } from "./lint-UTfyg7AR.mjs";
3
- import { solidTest } from "./vitest.mjs";
1
+ import { t as fmt } from "./fmt-9dlUYOMk.mjs";
2
+ import { a as I18N_SETTINGS, i as validatePublicationFeatures, n as seoModule, o as hasI18n, r as localeModule, t as solidTest } from "./vitest-BjLr7iLf.mjs";
3
+ import { t as lint } from "./lint-ehdtTKfw.mjs";
4
4
  import path from "node:path";
5
- import { readFile } from "node:fs/promises";
6
5
  import { env, loadEnvFile } from "node:process";
6
+ import { fileURLToPath } from "node:url";
7
+ import { readFile, realpath } from "node:fs/promises";
7
8
  import { defineConfig, lazyPlugins, loadEnv } from "vite-plus";
9
+ import * as v from "valibot";
10
+ import { createHash } from "node:crypto";
11
+ // src/fonts/assets.ts
12
+ const HASH_LENGTH = 16;
13
+ const MAX_WEIGHT = 1e3;
14
+ const FORMATS = {
15
+ ".woff2": "woff2",
16
+ ".woff": "woff",
17
+ ".ttf": "truetype",
18
+ ".otf": "opentype"
19
+ };
20
+ const numericWeight = v.pipe(v.number(), v.integer(), v.minValue(1), v.maxValue(MAX_WEIGHT));
21
+ const textWeight = v.pipe(v.string(), v.regex(/^(?:normal|bold|(?:[1-9]\d{0,2}|1000)(?: (?:[1-9]\d{0,2}|1000))?)$/u));
22
+ const weightSchema = v.union([numericWeight, textWeight]);
23
+ const unicodeSchema = v.pipe(v.string(), v.regex(/^U\+[\dA-F?]{1,6}(?:-[\dA-F]{1,6})?(?:\s*,\s*U\+[\dA-F?]{1,6}(?:-[\dA-F]{1,6})?)*$/iu));
24
+ const fontSchema = v.strictObject({
25
+ path: v.pipe(v.string(), v.nonEmpty()),
26
+ weight: v.optional(weightSchema, "normal"),
27
+ style: v.optional(v.picklist([
28
+ "normal",
29
+ "italic",
30
+ "oblique"
31
+ ]), "normal"),
32
+ display: v.optional(v.picklist([
33
+ "auto",
34
+ "block",
35
+ "swap",
36
+ "fallback",
37
+ "optional"
38
+ ]), "swap"),
39
+ unicodeRange: v.optional(unicodeSchema),
40
+ preload: v.optional(v.boolean(), false)
41
+ });
42
+ const familyName = v.pipe(v.string(), v.nonEmpty(), v.check((value) => !/\p{Cc}/u.test(value), "Font families cannot contain control characters."));
43
+ const fileList = v.pipe(v.array(fontSchema), v.minLength(1));
44
+ const familySchema = v.strictObject({
45
+ family: familyName,
46
+ files: fileList
47
+ });
48
+ const familiesSchema = v.array(familySchema);
49
+ function hash(source) {
50
+ return createHash("sha256").update(source).digest("hex").slice(0, HASH_LENGTH);
51
+ }
52
+ function cssString(value) {
53
+ return `"${value.replaceAll("\\", String.raw`\\`).replaceAll("\"", String.raw`\"`)}"`;
54
+ }
55
+ function isInside(root, file) {
56
+ const relative = path.relative(root, file);
57
+ return relative !== "" && !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
58
+ }
59
+ async function readFontAssets({ root, fonts, base, assetsDir }) {
60
+ if (!base.startsWith("/") || base.startsWith("//") || /[?#]/u.test(base)) throw new Error("defineSolidApp fonts: base must be a same-origin absolute pathname.");
61
+ if (path.isAbsolute(assetsDir) || assetsDir.split(/[\\/]/u).includes("..")) throw new Error("defineSolidApp fonts: assetsDir must stay inside the client output.");
62
+ const families = v.parse(familiesSchema, fonts);
63
+ const directory = await realpath(root);
64
+ const assets = /* @__PURE__ */ new Map();
65
+ const files = /* @__PURE__ */ new Set();
66
+ const preloads = /* @__PURE__ */ new Map();
67
+ const rules = [];
68
+ const url = (fileName) => `${base.replace(/\/$/u, "")}/${fileName}`;
69
+ for (const { family, files: faces } of families) for (const face of faces) {
70
+ if (path.isAbsolute(face.path) || /[?#]/u.test(face.path)) throw new Error(`defineSolidApp fonts: ${face.path} must be relative to the application root, without a query or fragment.`);
71
+ const file = await realpath(path.resolve(directory, face.path));
72
+ if (!isInside(directory, file)) throw new Error(`defineSolidApp fonts: ${face.path} must stay inside the application root.`);
73
+ const extension = path.extname(file);
74
+ const format = FORMATS[extension];
75
+ if (!format) throw new Error(`defineSolidApp fonts: ${face.path} must be a woff2, woff, ttf or otf file.`);
76
+ const source = await readFile(file);
77
+ const key = `${hash(source)}${extension}`;
78
+ let asset = assets.get(key);
79
+ if (!asset) {
80
+ const name = path.basename(file, extension).replaceAll(/[^\w-]/gu, "_");
81
+ asset = {
82
+ fileName: path.posix.join(assetsDir, `${name}-${key}`),
83
+ source,
84
+ type: `font/${extension.slice(1)}`
85
+ };
86
+ assets.set(key, asset);
87
+ }
88
+ files.add(file);
89
+ const href = url(asset.fileName);
90
+ if (face.preload) preloads.set(href, {
91
+ href,
92
+ type: asset.type
93
+ });
94
+ rules.push(`@font-face {\n font-family: ${cssString(family)};\n font-style: ${face.style};\n font-display: ${face.display};\n font-weight: ${face.weight};\n src: url(${cssString(href)}) format(${cssString(format)});${face.unicodeRange ? `\n unicode-range: ${face.unicodeRange};` : ""}\n}`);
95
+ }
96
+ const source = `${rules.join("\n\n")}\n`;
97
+ const fileName = path.posix.join(assetsDir, `solid-fonts-${hash(source)}.css`);
98
+ return {
99
+ assets: [...assets.values(), {
100
+ fileName,
101
+ source,
102
+ type: "text/css"
103
+ }],
104
+ files: [...files],
105
+ stylesheet: url(fileName),
106
+ preloads: [...preloads.values()]
107
+ };
108
+ }
109
+ // src/fonts/index.ts
110
+ const MODULE_ID = "virtual:solid-app/fonts";
111
+ const RESOLVED_ID = "\0solid-app:fonts";
112
+ function fontsPlugin(options) {
113
+ if (options.fonts === void 0) return void 0;
114
+ if (!Array.isArray(options.fonts)) throw new TypeError("defineSolidApp fonts: fonts must be an array of family descriptors.");
115
+ if (options.fonts.length === 0) return void 0;
116
+ let assets;
117
+ let base = "/";
118
+ let assetsDir = "assets";
119
+ let isBuild = false;
120
+ const read = () => readFontAssets({
121
+ ...options,
122
+ base,
123
+ assetsDir
124
+ });
125
+ return {
126
+ name: "solid-app:fonts",
127
+ async configResolved(config) {
128
+ ({base} = config);
129
+ ({assetsDir} = config.build);
130
+ isBuild = config.command === "build";
131
+ assets = read();
132
+ await assets;
133
+ },
134
+ async buildStart() {
135
+ const result = await assets;
136
+ for (const file of result.files) this.addWatchFile(file);
137
+ if (isBuild && this.environment.name === "client") for (const asset of result.assets) this.emitFile({
138
+ type: "asset",
139
+ fileName: asset.fileName,
140
+ source: asset.source
141
+ });
142
+ },
143
+ resolveId: (id) => id === MODULE_ID ? RESOLVED_ID : void 0,
144
+ async load(id) {
145
+ if (id !== RESOLVED_ID) return void 0;
146
+ const { stylesheet, preloads } = await assets;
147
+ return `export const stylesheet = ${JSON.stringify(stylesheet)}; export const preloads = ${JSON.stringify(preloads)}`;
148
+ },
149
+ async transformIndexHtml() {
150
+ if (options.ssr !== false) return void 0;
151
+ const { stylesheet, preloads } = await assets;
152
+ return [{
153
+ tag: "link",
154
+ attrs: {
155
+ rel: "stylesheet",
156
+ href: stylesheet
157
+ }
158
+ }, ...preloads.map(({ href, type }) => ({
159
+ tag: "link",
160
+ attrs: {
161
+ rel: "preload",
162
+ href,
163
+ as: "font",
164
+ type,
165
+ crossorigin: "anonymous"
166
+ }
167
+ }))];
168
+ },
169
+ configureServer(server) {
170
+ server.middlewares.use((request, response, next) => {
171
+ (async () => {
172
+ try {
173
+ const asset = (await assets).assets.find((item) => request.url?.split("?")[0] === `${base}${item.fileName}`);
174
+ if (!asset) {
175
+ next();
176
+ return;
177
+ }
178
+ response.setHeader("Content-Type", asset.type);
179
+ response.setHeader("Cache-Control", "no-cache");
180
+ response.end(asset.source);
181
+ } catch (error) {
182
+ next(error);
183
+ }
184
+ })();
185
+ });
186
+ },
187
+ async handleHotUpdate({ file, server }) {
188
+ if (!(await assets).files.includes(file)) return void 0;
189
+ assets = read();
190
+ await assets;
191
+ for (const environment of Object.values(server.environments)) {
192
+ const module = environment.moduleGraph.getModuleById(RESOLVED_ID);
193
+ if (module) environment.moduleGraph.invalidateModule(module);
194
+ }
195
+ server.ws.send({ type: "full-reload" });
196
+ return [];
197
+ }
198
+ };
199
+ }
200
+ // src/vite/static-paths.ts
201
+ function isUnderLocale({ staticPath, locales }) {
202
+ const [firstSegment] = staticPath.split("/").slice(1);
203
+ return locales.includes(firstSegment ?? "");
204
+ }
205
+ function staticFilePaths({ options, locales }) {
206
+ const appPaths = [...(options.publicAssets ?? []).map((asset) => `${asset.baseURL}/**`), ...options.staticPaths ?? []];
207
+ const collision = appPaths.find((staticPath) => isUnderLocale({
208
+ staticPath,
209
+ locales
210
+ }));
211
+ if (collision) throw new Error(`defineSolidApp: the static path ${collision} is inside a locale folder; move it out of /${collision.split("/")[1]}/.`);
212
+ const seo = typeof options.seo === "object" ? options.seo : {};
213
+ const hasMarkdown = typeof seo.markdown === "object" && seo.markdown.static === true;
214
+ const markdownPaths = locales.length > 0 ? locales.map((locale) => `/${locale}/**.md`) : ["/**.md"];
215
+ const publishedPaths = [
216
+ ...hasMarkdown ? markdownPaths : [],
217
+ ...typeof seo.llms === "object" ? ["/llms.txt"] : [],
218
+ ...typeof seo.llmsFull === "object" ? ["/llms-full.txt"] : [],
219
+ ...seo.sitemap !== void 0 && seo.sitemap !== false ? ["/sitemap.xml", "/sitemaps/pages/**"] : [],
220
+ ...hasMarkdown ? ["/_markdown/static.json"] : []
221
+ ];
222
+ return [
223
+ "/assets/**",
224
+ ...appPaths,
225
+ ...publishedPaths
226
+ ];
227
+ }
8
228
  // src/vite/plugins.ts
9
229
  const ROUTE_TYPES = "src/@types/routes.d.ts";
10
- const SEO_FILE_PATHS = [
11
- "/llms.txt",
12
- "/llms-full.txt",
13
- "/sitemap.xml",
14
- "/sitemaps/pages/**",
15
- "/_markdown/static.json"
16
- ];
17
230
  async function loadPluginModules() {
18
231
  const [solid, routing, icons, loaders] = await Promise.all([
19
232
  import("@solidjs/vite-plugin"),
@@ -34,35 +247,29 @@ async function readExtension({ options, target, mode }) {
34
247
  mode
35
248
  }) ?? {};
36
249
  }
250
+ async function i18nPlugins(options) {
251
+ if (options.i18nPlugin === false || !hasI18n(options.root)) return [];
252
+ const { i18nPlugin } = await import("./i18nPlugin-CS7Gob6R.mjs");
253
+ return i18nPlugin();
254
+ }
37
255
  async function readLocales(root) {
256
+ let source;
257
+ try {
258
+ source = await readFile(path.join(root, I18N_SETTINGS), "utf8");
259
+ } catch (error) {
260
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return [];
261
+ throw new Error(`defineSolidApp: cannot read the locales from ${I18N_SETTINGS}.`, { cause: error });
262
+ }
38
263
  let settings;
39
264
  try {
40
- settings = JSON.parse(await readFile(path.join(root, "project.inlang/settings.json"), "utf8"));
265
+ settings = JSON.parse(source);
41
266
  } catch (error) {
42
- throw new Error("defineSolidApp: cannot read the locales from project.inlang/settings.json.", { cause: error });
267
+ throw new Error(`defineSolidApp: cannot read the locales from ${I18N_SETTINGS}.`, { cause: error });
43
268
  }
44
269
  const locales = settings !== null && typeof settings === "object" && "locales" in settings ? settings.locales : void 0;
45
- if (!Array.isArray(locales) || !locales.every((locale) => typeof locale === "string")) throw new Error("defineSolidApp: project.inlang/settings.json must list its locales as strings.");
270
+ if (!Array.isArray(locales) || !locales.every((locale) => typeof locale === "string")) throw new Error(`defineSolidApp: ${I18N_SETTINGS} must list its locales as strings.`);
46
271
  return locales;
47
272
  }
48
- function isUnderLocale({ staticPath, locales }) {
49
- const [firstSegment] = staticPath.split("/").slice(1);
50
- return locales.includes(firstSegment ?? "");
51
- }
52
- function staticFilePaths({ options, locales }) {
53
- const appPaths = [...(options.publicAssets ?? []).map((asset) => `${asset.baseURL}/**`), ...options.staticPaths ?? []];
54
- const collision = appPaths.find((staticPath) => isUnderLocale({
55
- staticPath,
56
- locales
57
- }));
58
- if (collision) throw new Error(`defineSolidApp: the static path ${collision} is inside a locale folder; move it out of /${collision.split("/")[1]}/.`);
59
- return [
60
- "/assets/**",
61
- ...appPaths,
62
- ...locales.map((locale) => `/${locale}/**.md`),
63
- ...SEO_FILE_PATHS
64
- ];
65
- }
66
273
  function iconsPlugin({ modules, options }) {
67
274
  return modules.Icons({
68
275
  compiler: "solid",
@@ -79,70 +286,149 @@ function staticHeaders({ securityHeaders }) {
79
286
  "cross-origin-resource-policy": securityHeaders["cross-origin-resource-policy"]
80
287
  };
81
288
  }
289
+ function clientPlugins({ i18n, modules, extension, options, mode }) {
290
+ const e2eRoutes = mode === "e2e" ? options.e2e?.routes : void 0;
291
+ return [
292
+ localeModule(options),
293
+ fontsPlugin(options),
294
+ ...extension.plugins ?? [],
295
+ ...i18n,
296
+ iconsPlugin({
297
+ modules,
298
+ options
299
+ }),
300
+ modules.solid({ ssr: false }),
301
+ modules.fileRoutes(e2eRoutes ? {
302
+ dir: e2eRoutes,
303
+ httpMethods: true
304
+ } : {
305
+ types: ROUTE_TYPES,
306
+ httpMethods: true
307
+ })
308
+ ];
309
+ }
310
+ function hasStaticMarkdown(options) {
311
+ return typeof options.seo === "object" && typeof options.seo.markdown === "object" && options.seo.markdown.static === true;
312
+ }
313
+ async function createPublication(options) {
314
+ const features = typeof options.seo === "object" ? options.seo : {};
315
+ const enabled = hasStaticMarkdown(options) || typeof features.llms === "object" || typeof features.sitemap === "object";
316
+ let publication;
317
+ if (enabled) {
318
+ const { publicationBuild } = await import("./publication-CRUTOB0B.mjs");
319
+ publication = publicationBuild(features);
320
+ }
321
+ return publication;
322
+ }
323
+ async function serverPlugins({ options, mode }) {
324
+ const [modules, { nitro }, extension, locales, i18n] = await Promise.all([
325
+ loadPluginModules(),
326
+ import("nitro/vite"),
327
+ readExtension({
328
+ options,
329
+ target: "app",
330
+ mode
331
+ }),
332
+ hasStaticMarkdown(options) ? readLocales(options.root) : [],
333
+ i18nPlugins(options)
334
+ ]);
335
+ const isE2e = mode === "e2e";
336
+ const middleware = isE2e ? options.e2e?.middleware ?? options.middleware : options.middleware;
337
+ const e2eRoutes = isE2e ? options.e2e?.routes : void 0;
338
+ const headers = staticHeaders(options);
339
+ const publication = await createPublication(options);
340
+ return [
341
+ localeModule(options),
342
+ fontsPlugin(options),
343
+ seoModule({
344
+ root: options.root,
345
+ mode,
346
+ seo: options.seo
347
+ }),
348
+ ...publication ? [publication.plugin] : [],
349
+ ...extension.plugins ?? [],
350
+ ...i18n,
351
+ iconsPlugin({
352
+ modules,
353
+ options
354
+ }),
355
+ modules.solid({
356
+ start: middleware ? { middleware } : {},
357
+ ssr: true,
358
+ serverFunctions: options.serverSetup ? { serverSetup: options.serverSetup } : true
359
+ }),
360
+ modules.fileRoutes(e2eRoutes ? {
361
+ dir: e2eRoutes,
362
+ httpMethods: true
363
+ } : {
364
+ types: ROUTE_TYPES,
365
+ httpMethods: true
366
+ }),
367
+ nitro({
368
+ serverEntry: false,
369
+ preset: "vercel",
370
+ modules: [...publication ? [publication.module] : [], ...extension.nitroModules ?? []],
371
+ publicAssets: options.publicAssets ?? [],
372
+ routeRules: {
373
+ ...Object.fromEntries(staticFilePaths({
374
+ options,
375
+ locales
376
+ }).map((staticPath) => [staticPath, { headers }])),
377
+ ...options.routeRules
378
+ }
379
+ })
380
+ ];
381
+ }
82
382
  function appPlugins({ options, mode }) {
83
383
  return lazyPlugins(async () => {
84
- const [modules, { nitro }, extension, locales] = await Promise.all([
384
+ if (options.ssr !== false) return serverPlugins({
385
+ options,
386
+ mode
387
+ });
388
+ const [modules, extension, i18n] = await Promise.all([
85
389
  loadPluginModules(),
86
- import("nitro/vite"),
87
390
  readExtension({
88
391
  options,
89
392
  target: "app",
90
393
  mode
91
394
  }),
92
- readLocales(options.root)
395
+ i18nPlugins(options)
93
396
  ]);
94
- const isE2e = mode === "e2e";
95
- const middleware = isE2e ? options.e2e?.middleware ?? options.middleware : options.middleware;
96
- const e2eRoutes = isE2e ? options.e2e?.routes : void 0;
97
- const headers = staticHeaders(options);
98
- return [
99
- ...extension.plugins ?? [],
100
- iconsPlugin({
101
- modules,
102
- options
103
- }),
104
- modules.solid({
105
- start: middleware ? { middleware } : {},
106
- ssr: true,
107
- serverFunctions: options.serverSetup ? { serverSetup: options.serverSetup } : true
108
- }),
109
- modules.fileRoutes(e2eRoutes ? {
110
- dir: e2eRoutes,
111
- httpMethods: true
112
- } : {
113
- types: ROUTE_TYPES,
114
- httpMethods: true
115
- }),
116
- nitro({
117
- serverEntry: false,
118
- preset: "vercel",
119
- modules: extension.nitroModules ?? [],
120
- publicAssets: options.publicAssets ?? [],
121
- routeRules: {
122
- ...Object.fromEntries(staticFilePaths({
123
- options,
124
- locales
125
- }).map((staticPath) => [staticPath, { headers }])),
126
- ...options.routeRules
127
- }
128
- })
129
- ];
397
+ return clientPlugins({
398
+ modules,
399
+ extension,
400
+ options,
401
+ mode,
402
+ i18n
403
+ });
130
404
  });
131
405
  }
132
406
  function testPlugins({ options, mode }) {
133
407
  return lazyPlugins(async () => {
134
- const [modules, extension] = await Promise.all([loadPluginModules(), readExtension({
135
- options,
136
- target: "test",
137
- mode
138
- })]);
408
+ const [modules, extension, i18n] = await Promise.all([
409
+ loadPluginModules(),
410
+ readExtension({
411
+ options,
412
+ target: "test",
413
+ mode
414
+ }),
415
+ i18nPlugins(options)
416
+ ]);
139
417
  return [
418
+ localeModule(options),
419
+ fontsPlugin(options),
420
+ ...options.ssr === false ? [] : [seoModule({
421
+ root: options.root,
422
+ mode,
423
+ seo: options.seo
424
+ })],
140
425
  ...extension.plugins ?? [],
426
+ ...i18n,
141
427
  iconsPlugin({
142
428
  modules,
143
429
  options
144
430
  }),
145
- modules.solid({ serverFunctions: true }),
431
+ modules.solid(options.ssr === false ? {} : { serverFunctions: true }),
146
432
  modules.fileRoutes({
147
433
  types: ROUTE_TYPES,
148
434
  httpMethods: true
@@ -152,6 +438,16 @@ function testPlugins({ options, mode }) {
152
438
  }
153
439
  // src/vite/tasks.ts
154
440
  const TOOLING_CI_TEST_WORKERS = 3;
441
+ const TOOL_NAMES = [
442
+ "knip",
443
+ "tsc",
444
+ "playwright",
445
+ "lefthook",
446
+ "commitlint"
447
+ ];
448
+ function toolCommand(name) {
449
+ return `node '${fileURLToPath(new URL("cli.mjs", import.meta.url)).replaceAll("'", String.raw`'\''`)}' ${name}`;
450
+ }
155
451
  function fromWorkspace(pattern) {
156
452
  return {
157
453
  pattern,
@@ -166,9 +462,8 @@ function installInputs({ workspace }) {
166
462
  ];
167
463
  return workspace ? patterns.map((pattern) => fromWorkspace(pattern)) : patterns;
168
464
  }
169
- function createTasks({ workspace, isCi }) {
465
+ function createTasks({ workspace, isCi, hasI18n }) {
170
466
  const install = installInputs({ workspace });
171
- const lockfile = workspace ? fromWorkspace("bun.lock") : "bun.lock";
172
467
  const dependsOn = workspace ? { dependsOn: workspace.dependsOn } : {};
173
468
  const projectCheckInputs = [
174
469
  { auto: true },
@@ -183,20 +478,25 @@ function createTasks({ workspace, isCi }) {
183
478
  ];
184
479
  const testConcurrency = isCi ? ` --test-concurrency=${TOOLING_CI_TEST_WORKERS}` : "";
185
480
  return {
186
- "i18n-generate": {
187
- command: "bun tooling/i18n/generate.ts",
481
+ ...Object.fromEntries(TOOL_NAMES.map((name) => [name, {
482
+ command: toolCommand(name),
483
+ cache: false
484
+ }])),
485
+ ...hasI18n && { "i18n-generate": {
486
+ command: `bun '${fileURLToPath(new URL("i18n-generate.mjs", import.meta.url)).replaceAll("'", String.raw`'\''`)}'`,
487
+ ...dependsOn,
188
488
  cache: {
189
489
  input: [
490
+ { auto: true },
190
491
  "src/**/i18n/*.json",
191
- "tooling/i18n/**",
192
492
  "project.inlang/settings.json",
193
493
  "project.inlang/paraglide.config.ts",
194
494
  "package.json",
195
- lockfile
495
+ ...install
196
496
  ],
197
497
  output: ["paraglide/**", ".paraglide/messages/**"]
198
498
  }
199
- },
499
+ } },
200
500
  "lint-css": {
201
501
  command: "bun tooling/css/check.ts",
202
502
  ...dependsOn,
@@ -212,7 +512,7 @@ function createTasks({ workspace, isCi }) {
212
512
  }
213
513
  },
214
514
  "dead-code-project": {
215
- command: "knip-bun --config tooling/knip.ts && knip-bun --config tooling/knip.ts --strict",
515
+ command: `${toolCommand("knip")} --config tooling/knip.ts && ${toolCommand("knip")} --config tooling/knip.ts --strict`,
216
516
  ...dependsOn,
217
517
  cache: {
218
518
  env: ["NODE_ENV"],
@@ -246,7 +546,7 @@ function createTasks({ workspace, isCi }) {
246
546
  }
247
547
  },
248
548
  "typecheck-node": {
249
- command: "tsc --project tsconfig.node.json",
549
+ command: `${toolCommand("tsc")} --project tsconfig.node.json`,
250
550
  ...dependsOn,
251
551
  cache: {
252
552
  input: [
@@ -277,12 +577,35 @@ function createTasks({ workspace, isCi }) {
277
577
  function isPathPrefix(baseURL) {
278
578
  return typeof baseURL === "string" && baseURL.startsWith("/") && baseURL.length > 1 && !baseURL.endsWith("/");
279
579
  }
280
- function validateOptions(options) {
580
+ const SERVER_OPTIONS = [
581
+ "middleware",
582
+ "serverSetup",
583
+ "securityHeaders",
584
+ "publicAssets",
585
+ "staticPaths",
586
+ "routeRules",
587
+ "publication",
588
+ "seo"
589
+ ];
590
+ function validateServerOptions(options) {
591
+ if ("publication" in options) throw new Error("publication was replaced by seo.markdown, seo.llms and seo.llmsFull");
592
+ if (typeof options.seo === "object") validatePublicationFeatures(options.seo);
281
593
  const asset = (options.publicAssets ?? []).find(({ baseURL }) => !isPathPrefix(baseURL));
282
594
  if (asset) throw new Error(`defineSolidApp: the publicAssets entry for ${asset.dir} needs a baseURL such as /images, without a trailing slash.`);
283
595
  const staticPath = (options.staticPaths ?? []).find((pattern) => !pattern.startsWith("/"));
284
596
  if (staticPath !== void 0) throw new Error(`defineSolidApp: the static path ${staticPath} must start with a slash.`);
285
597
  }
598
+ function validateClientOptions(options) {
599
+ const serverOption = [...SERVER_OPTIONS.filter((key) => key in options), ..."middleware" in (options.e2e ?? {}) ? ["e2e.middleware"] : []].at(0);
600
+ if (serverOption !== void 0) throw new Error(`defineSolidApp: ${serverOption} needs SSR; remove it or remove ssr: false.`);
601
+ }
602
+ function validateOptions(options) {
603
+ if (options.ssr === false) {
604
+ validateClientOptions(options);
605
+ return;
606
+ }
607
+ validateServerOptions(options);
608
+ }
286
609
  function loadTestEnvFile(root) {
287
610
  try {
288
611
  loadEnvFile(path.join(root, ".env.test"));
@@ -304,7 +627,8 @@ function defineSolidApp(options) {
304
627
  run: { tasks: {
305
628
  ...createTasks({
306
629
  workspace: options.workspace,
307
- isCi: Boolean(env.CI)
630
+ isCi: Boolean(env.CI),
631
+ hasI18n: hasI18n(root)
308
632
  }),
309
633
  ...options.tasks
310
634
  } },
@@ -329,7 +653,10 @@ function defineSolidApp(options) {
329
653
  fmt,
330
654
  lint,
331
655
  test: solidTest({
656
+ ...options.test,
332
657
  root,
658
+ language: options.language,
659
+ seo: options.ssr === false ? void 0 : options.seo,
333
660
  plugins: () => testPlugins({
334
661
  options,
335
662
  mode