@evertondgn/solid-tooling 0.5.0 → 0.7.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 +543 -598
  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 +449 -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,266 @@
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 RELATIVE_BASE = "./";
13
+ const PARENT_SEGMENT = /^(?:\.|%2e){2}$/iu;
14
+ const URL_IGNORED = /[\t\n\r]/gu;
15
+ const PERCENT_ESCAPE = /%[\da-f]{2}/giu;
16
+ const HEX_RADIX = 16;
17
+ const OUTPUT_URL = "file:///output/";
18
+ const HASH_LENGTH = 16;
19
+ const MAX_WEIGHT = 1e3;
20
+ const FORMATS = {
21
+ ".woff2": "woff2",
22
+ ".woff": "woff",
23
+ ".ttf": "truetype",
24
+ ".otf": "opentype"
25
+ };
26
+ const numericWeight = v.pipe(v.number(), v.integer(), v.minValue(1), v.maxValue(MAX_WEIGHT));
27
+ const textWeight = v.pipe(v.string(), v.regex(/^(?:normal|bold|(?:[1-9]\d{0,2}|1000)(?: (?:[1-9]\d{0,2}|1000))?)$/u));
28
+ const weightSchema = v.union([numericWeight, textWeight]);
29
+ 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));
30
+ const fontSchema = v.strictObject({
31
+ path: v.pipe(v.string(), v.nonEmpty()),
32
+ weight: v.optional(weightSchema, "normal"),
33
+ style: v.optional(v.picklist([
34
+ "normal",
35
+ "italic",
36
+ "oblique"
37
+ ]), "normal"),
38
+ display: v.optional(v.picklist([
39
+ "auto",
40
+ "block",
41
+ "swap",
42
+ "fallback",
43
+ "optional"
44
+ ]), "swap"),
45
+ unicodeRange: v.optional(unicodeSchema),
46
+ preload: v.optional(v.boolean(), false)
47
+ });
48
+ const familyName = v.pipe(v.string(), v.nonEmpty(), v.check((value) => !/\p{Cc}/u.test(value), "Font families cannot contain control characters."));
49
+ const fileList = v.pipe(v.array(fontSchema), v.minLength(1));
50
+ const familySchema = v.strictObject({
51
+ family: familyName,
52
+ files: fileList
53
+ });
54
+ const familiesSchema = v.array(familySchema);
55
+ function hash(source) {
56
+ return createHash("sha256").update(source).digest("hex").slice(0, HASH_LENGTH);
57
+ }
58
+ function cssString(value) {
59
+ return `"${value.replaceAll("\\", String.raw`\\`).replaceAll("\"", String.raw`\"`)}"`;
60
+ }
61
+ function isInside(root, file) {
62
+ const relative = path.relative(root, file);
63
+ return relative !== "" && !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
64
+ }
65
+ function isRelativeBase({ base, ssr }) {
66
+ if (base !== RELATIVE_BASE) return false;
67
+ if (ssr !== false) throw new Error("defineSolidApp fonts: a relative base needs ssr: false.");
68
+ return true;
69
+ }
70
+ function fontUrls({ base, ssr }) {
71
+ if (isRelativeBase({
72
+ base,
73
+ ssr
74
+ })) return {
75
+ css: (fileName) => path.posix.basename(fileName),
76
+ html: (fileName) => `${RELATIVE_BASE}${fileName}`
77
+ };
78
+ if (!base.startsWith("/") || base.startsWith("//") || /[?#]/u.test(base)) throw new Error("defineSolidApp fonts: base must be a same-origin absolute pathname.");
79
+ const url = (fileName) => `${base.replace(/\/$/u, "")}/${fileName}`;
80
+ return {
81
+ css: url,
82
+ html: url
83
+ };
84
+ }
85
+ function decodeOnce(value) {
86
+ return value.replaceAll(PERCENT_ESCAPE, (escape) => String.fromCodePoint(Number.parseInt(escape.slice(1), HEX_RADIX)));
87
+ }
88
+ function hasParentSegment(value) {
89
+ return value.split(/[\\/]/u).some((segment) => PARENT_SEGMENT.test(segment.replaceAll(URL_IGNORED, "")));
90
+ }
91
+ async function readFontAssets({ root, fonts, base, assetsDir, ssr }) {
92
+ const urls = fontUrls({
93
+ base,
94
+ ssr
95
+ });
96
+ if (path.isAbsolute(assetsDir) || hasParentSegment(assetsDir) || hasParentSegment(decodeOnce(assetsDir)) || !new URL(`${OUTPUT_URL}${assetsDir}/file`).href.startsWith(OUTPUT_URL)) throw new Error("defineSolidApp fonts: assetsDir must stay inside the client output.");
97
+ const families = v.parse(familiesSchema, fonts);
98
+ const directory = await realpath(root);
99
+ const assets = /* @__PURE__ */ new Map();
100
+ const files = /* @__PURE__ */ new Set();
101
+ const preloads = /* @__PURE__ */ new Map();
102
+ const rules = [];
103
+ for (const { family, files: faces } of families) for (const face of faces) {
104
+ 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.`);
105
+ const file = await realpath(path.resolve(directory, face.path));
106
+ if (!isInside(directory, file)) throw new Error(`defineSolidApp fonts: ${face.path} must stay inside the application root.`);
107
+ const extension = path.extname(file);
108
+ const format = FORMATS[extension];
109
+ if (!format) throw new Error(`defineSolidApp fonts: ${face.path} must be a woff2, woff, ttf or otf file.`);
110
+ const source = await readFile(file);
111
+ const key = `${hash(source)}${extension}`;
112
+ let asset = assets.get(key);
113
+ if (!asset) {
114
+ const name = path.basename(file, extension).replaceAll(/[^\w-]/gu, "_");
115
+ asset = {
116
+ fileName: path.posix.join(assetsDir, `${name}-${key}`),
117
+ source,
118
+ type: `font/${extension.slice(1)}`
119
+ };
120
+ assets.set(key, asset);
121
+ }
122
+ files.add(file);
123
+ const href = urls.html(asset.fileName);
124
+ if (face.preload) preloads.set(href, {
125
+ href,
126
+ type: asset.type
127
+ });
128
+ 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(urls.css(asset.fileName))}) format(${cssString(format)});${face.unicodeRange ? `\n unicode-range: ${face.unicodeRange};` : ""}\n}`);
129
+ }
130
+ const source = `${rules.join("\n\n")}\n`;
131
+ const fileName = path.posix.join(assetsDir, `solid-fonts-${hash(source)}.css`);
132
+ return {
133
+ assets: [...assets.values(), {
134
+ fileName,
135
+ source,
136
+ type: "text/css"
137
+ }],
138
+ files: [...files],
139
+ stylesheet: urls.html(fileName),
140
+ preloads: [...preloads.values()]
141
+ };
142
+ }
143
+ // src/fonts/index.ts
144
+ const MODULE_ID = "virtual:solid-app/fonts";
145
+ const RESOLVED_ID = "\0solid-app:fonts";
146
+ function fontsPlugin(options) {
147
+ if (options.fonts === void 0) return void 0;
148
+ if (!Array.isArray(options.fonts)) throw new TypeError("defineSolidApp fonts: fonts must be an array of family descriptors.");
149
+ if (options.fonts.length === 0) return void 0;
150
+ let assets;
151
+ let base = "/";
152
+ let assetsDir = "assets";
153
+ let isBuild = false;
154
+ const read = () => readFontAssets({
155
+ ...options,
156
+ base,
157
+ assetsDir
158
+ });
159
+ return {
160
+ name: "solid-app:fonts",
161
+ async configResolved(config) {
162
+ ({base} = config);
163
+ ({assetsDir} = config.build);
164
+ isBuild = config.command === "build";
165
+ assets = read();
166
+ await assets;
167
+ },
168
+ async buildStart() {
169
+ const result = await assets;
170
+ for (const file of result.files) this.addWatchFile(file);
171
+ if (isBuild && this.environment.name === "client") for (const asset of result.assets) this.emitFile({
172
+ type: "asset",
173
+ fileName: asset.fileName,
174
+ source: asset.source
175
+ });
176
+ },
177
+ resolveId: (id) => id === MODULE_ID ? RESOLVED_ID : void 0,
178
+ async load(id) {
179
+ if (id !== RESOLVED_ID) return void 0;
180
+ const { stylesheet, preloads } = await assets;
181
+ return `export const stylesheet = ${JSON.stringify(stylesheet)}; export const preloads = ${JSON.stringify(preloads)}`;
182
+ },
183
+ async transformIndexHtml() {
184
+ if (options.ssr !== false) return void 0;
185
+ const { stylesheet, preloads } = await assets;
186
+ return [{
187
+ tag: "link",
188
+ attrs: {
189
+ rel: "stylesheet",
190
+ href: stylesheet
191
+ }
192
+ }, ...preloads.map(({ href, type }) => ({
193
+ tag: "link",
194
+ attrs: {
195
+ rel: "preload",
196
+ href,
197
+ as: "font",
198
+ type,
199
+ crossorigin: "anonymous"
200
+ }
201
+ }))];
202
+ },
203
+ configureServer(server) {
204
+ server.middlewares.use((request, response, next) => {
205
+ (async () => {
206
+ try {
207
+ const asset = (await assets).assets.find((item) => request.url?.split("?")[0] === `${base}${item.fileName}`);
208
+ if (!asset) {
209
+ next();
210
+ return;
211
+ }
212
+ response.setHeader("Content-Type", asset.type);
213
+ response.setHeader("Cache-Control", "no-cache");
214
+ response.end(asset.source);
215
+ } catch (error) {
216
+ next(error);
217
+ }
218
+ })();
219
+ });
220
+ },
221
+ async handleHotUpdate({ file, server }) {
222
+ if (!(await assets).files.includes(file)) return void 0;
223
+ assets = read();
224
+ await assets;
225
+ for (const environment of Object.values(server.environments)) {
226
+ const module = environment.moduleGraph.getModuleById(RESOLVED_ID);
227
+ if (module) environment.moduleGraph.invalidateModule(module);
228
+ }
229
+ server.ws.send({ type: "full-reload" });
230
+ return [];
231
+ }
232
+ };
233
+ }
234
+ // src/vite/static-paths.ts
235
+ function isUnderLocale({ staticPath, locales }) {
236
+ const [firstSegment] = staticPath.split("/").slice(1);
237
+ return locales.includes(firstSegment ?? "");
238
+ }
239
+ function staticFilePaths({ options, locales }) {
240
+ const appPaths = [...(options.publicAssets ?? []).map((asset) => `${asset.baseURL}/**`), ...options.staticPaths ?? []];
241
+ const collision = appPaths.find((staticPath) => isUnderLocale({
242
+ staticPath,
243
+ locales
244
+ }));
245
+ if (collision) throw new Error(`defineSolidApp: the static path ${collision} is inside a locale folder; move it out of /${collision.split("/")[1]}/.`);
246
+ const seo = typeof options.seo === "object" ? options.seo : {};
247
+ const hasMarkdown = typeof seo.markdown === "object" && seo.markdown.static === true;
248
+ const markdownPaths = locales.length > 0 ? locales.map((locale) => `/${locale}/**.md`) : ["/**.md"];
249
+ const publishedPaths = [
250
+ ...hasMarkdown ? markdownPaths : [],
251
+ ...typeof seo.llms === "object" ? ["/llms.txt"] : [],
252
+ ...typeof seo.llmsFull === "object" ? ["/llms-full.txt"] : [],
253
+ ...seo.sitemap !== void 0 && seo.sitemap !== false ? ["/sitemap.xml", "/sitemaps/pages/**"] : [],
254
+ ...hasMarkdown ? ["/_markdown/static.json"] : []
255
+ ];
256
+ return [
257
+ "/assets/**",
258
+ ...appPaths,
259
+ ...publishedPaths
260
+ ];
261
+ }
8
262
  // src/vite/plugins.ts
9
263
  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
264
  async function loadPluginModules() {
18
265
  const [solid, routing, icons, loaders] = await Promise.all([
19
266
  import("@solidjs/vite-plugin"),
@@ -34,35 +281,29 @@ async function readExtension({ options, target, mode }) {
34
281
  mode
35
282
  }) ?? {};
36
283
  }
284
+ async function i18nPlugins(options) {
285
+ if (options.i18nPlugin === false || !hasI18n(options.root)) return [];
286
+ const { i18nPlugin } = await import("./i18nPlugin-CS7Gob6R.mjs");
287
+ return i18nPlugin();
288
+ }
37
289
  async function readLocales(root) {
290
+ let source;
291
+ try {
292
+ source = await readFile(path.join(root, I18N_SETTINGS), "utf8");
293
+ } catch (error) {
294
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return [];
295
+ throw new Error(`defineSolidApp: cannot read the locales from ${I18N_SETTINGS}.`, { cause: error });
296
+ }
38
297
  let settings;
39
298
  try {
40
- settings = JSON.parse(await readFile(path.join(root, "project.inlang/settings.json"), "utf8"));
299
+ settings = JSON.parse(source);
41
300
  } catch (error) {
42
- throw new Error("defineSolidApp: cannot read the locales from project.inlang/settings.json.", { cause: error });
301
+ throw new Error(`defineSolidApp: cannot read the locales from ${I18N_SETTINGS}.`, { cause: error });
43
302
  }
44
303
  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.");
304
+ if (!Array.isArray(locales) || !locales.every((locale) => typeof locale === "string")) throw new Error(`defineSolidApp: ${I18N_SETTINGS} must list its locales as strings.`);
46
305
  return locales;
47
306
  }
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
307
  function iconsPlugin({ modules, options }) {
67
308
  return modules.Icons({
68
309
  compiler: "solid",
@@ -79,70 +320,149 @@ function staticHeaders({ securityHeaders }) {
79
320
  "cross-origin-resource-policy": securityHeaders["cross-origin-resource-policy"]
80
321
  };
81
322
  }
323
+ function clientPlugins({ i18n, modules, extension, options, mode }) {
324
+ const e2eRoutes = mode === "e2e" ? options.e2e?.routes : void 0;
325
+ return [
326
+ localeModule(options),
327
+ fontsPlugin(options),
328
+ ...extension.plugins ?? [],
329
+ ...i18n,
330
+ iconsPlugin({
331
+ modules,
332
+ options
333
+ }),
334
+ modules.solid({ ssr: false }),
335
+ modules.fileRoutes(e2eRoutes ? {
336
+ dir: e2eRoutes,
337
+ httpMethods: true
338
+ } : {
339
+ types: ROUTE_TYPES,
340
+ httpMethods: true
341
+ })
342
+ ];
343
+ }
344
+ function hasStaticMarkdown(options) {
345
+ return typeof options.seo === "object" && typeof options.seo.markdown === "object" && options.seo.markdown.static === true;
346
+ }
347
+ async function createPublication(options) {
348
+ const features = typeof options.seo === "object" ? options.seo : {};
349
+ const enabled = hasStaticMarkdown(options) || typeof features.llms === "object" || typeof features.sitemap === "object";
350
+ let publication;
351
+ if (enabled) {
352
+ const { publicationBuild } = await import("./publication-CRUTOB0B.mjs");
353
+ publication = publicationBuild(features);
354
+ }
355
+ return publication;
356
+ }
357
+ async function serverPlugins({ options, mode }) {
358
+ const [modules, { nitro }, extension, locales, i18n] = await Promise.all([
359
+ loadPluginModules(),
360
+ import("nitro/vite"),
361
+ readExtension({
362
+ options,
363
+ target: "app",
364
+ mode
365
+ }),
366
+ hasStaticMarkdown(options) ? readLocales(options.root) : [],
367
+ i18nPlugins(options)
368
+ ]);
369
+ const isE2e = mode === "e2e";
370
+ const middleware = isE2e ? options.e2e?.middleware ?? options.middleware : options.middleware;
371
+ const e2eRoutes = isE2e ? options.e2e?.routes : void 0;
372
+ const headers = staticHeaders(options);
373
+ const publication = await createPublication(options);
374
+ return [
375
+ localeModule(options),
376
+ fontsPlugin(options),
377
+ seoModule({
378
+ root: options.root,
379
+ mode,
380
+ seo: options.seo
381
+ }),
382
+ ...publication ? [publication.plugin] : [],
383
+ ...extension.plugins ?? [],
384
+ ...i18n,
385
+ iconsPlugin({
386
+ modules,
387
+ options
388
+ }),
389
+ modules.solid({
390
+ start: middleware ? { middleware } : {},
391
+ ssr: true,
392
+ serverFunctions: options.serverSetup ? { serverSetup: options.serverSetup } : true
393
+ }),
394
+ modules.fileRoutes(e2eRoutes ? {
395
+ dir: e2eRoutes,
396
+ httpMethods: true
397
+ } : {
398
+ types: ROUTE_TYPES,
399
+ httpMethods: true
400
+ }),
401
+ nitro({
402
+ serverEntry: false,
403
+ preset: "vercel",
404
+ modules: [...publication ? [publication.module] : [], ...extension.nitroModules ?? []],
405
+ publicAssets: options.publicAssets ?? [],
406
+ routeRules: {
407
+ ...Object.fromEntries(staticFilePaths({
408
+ options,
409
+ locales
410
+ }).map((staticPath) => [staticPath, { headers }])),
411
+ ...options.routeRules
412
+ }
413
+ })
414
+ ];
415
+ }
82
416
  function appPlugins({ options, mode }) {
83
417
  return lazyPlugins(async () => {
84
- const [modules, { nitro }, extension, locales] = await Promise.all([
418
+ if (options.ssr !== false) return serverPlugins({
419
+ options,
420
+ mode
421
+ });
422
+ const [modules, extension, i18n] = await Promise.all([
85
423
  loadPluginModules(),
86
- import("nitro/vite"),
87
424
  readExtension({
88
425
  options,
89
426
  target: "app",
90
427
  mode
91
428
  }),
92
- readLocales(options.root)
429
+ i18nPlugins(options)
93
430
  ]);
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
- ];
431
+ return clientPlugins({
432
+ modules,
433
+ extension,
434
+ options,
435
+ mode,
436
+ i18n
437
+ });
130
438
  });
131
439
  }
132
440
  function testPlugins({ options, mode }) {
133
441
  return lazyPlugins(async () => {
134
- const [modules, extension] = await Promise.all([loadPluginModules(), readExtension({
135
- options,
136
- target: "test",
137
- mode
138
- })]);
442
+ const [modules, extension, i18n] = await Promise.all([
443
+ loadPluginModules(),
444
+ readExtension({
445
+ options,
446
+ target: "test",
447
+ mode
448
+ }),
449
+ i18nPlugins(options)
450
+ ]);
139
451
  return [
452
+ localeModule(options),
453
+ fontsPlugin(options),
454
+ ...options.ssr === false ? [] : [seoModule({
455
+ root: options.root,
456
+ mode,
457
+ seo: options.seo
458
+ })],
140
459
  ...extension.plugins ?? [],
460
+ ...i18n,
141
461
  iconsPlugin({
142
462
  modules,
143
463
  options
144
464
  }),
145
- modules.solid({ serverFunctions: true }),
465
+ modules.solid(options.ssr === false ? {} : { serverFunctions: true }),
146
466
  modules.fileRoutes({
147
467
  types: ROUTE_TYPES,
148
468
  httpMethods: true
@@ -152,6 +472,16 @@ function testPlugins({ options, mode }) {
152
472
  }
153
473
  // src/vite/tasks.ts
154
474
  const TOOLING_CI_TEST_WORKERS = 3;
475
+ const TOOL_NAMES = [
476
+ "knip",
477
+ "tsc",
478
+ "playwright",
479
+ "lefthook",
480
+ "commitlint"
481
+ ];
482
+ function toolCommand(name) {
483
+ return `node '${fileURLToPath(new URL("cli.mjs", import.meta.url)).replaceAll("'", String.raw`'\''`)}' ${name}`;
484
+ }
155
485
  function fromWorkspace(pattern) {
156
486
  return {
157
487
  pattern,
@@ -166,9 +496,8 @@ function installInputs({ workspace }) {
166
496
  ];
167
497
  return workspace ? patterns.map((pattern) => fromWorkspace(pattern)) : patterns;
168
498
  }
169
- function createTasks({ workspace, isCi }) {
499
+ function createTasks({ workspace, isCi, hasI18n }) {
170
500
  const install = installInputs({ workspace });
171
- const lockfile = workspace ? fromWorkspace("bun.lock") : "bun.lock";
172
501
  const dependsOn = workspace ? { dependsOn: workspace.dependsOn } : {};
173
502
  const projectCheckInputs = [
174
503
  { auto: true },
@@ -183,20 +512,25 @@ function createTasks({ workspace, isCi }) {
183
512
  ];
184
513
  const testConcurrency = isCi ? ` --test-concurrency=${TOOLING_CI_TEST_WORKERS}` : "";
185
514
  return {
186
- "i18n-generate": {
187
- command: "bun tooling/i18n/generate.ts",
515
+ ...Object.fromEntries(TOOL_NAMES.map((name) => [name, {
516
+ command: toolCommand(name),
517
+ cache: false
518
+ }])),
519
+ ...hasI18n && { "i18n-generate": {
520
+ command: `bun '${fileURLToPath(new URL("i18n-generate.mjs", import.meta.url)).replaceAll("'", String.raw`'\''`)}'`,
521
+ ...dependsOn,
188
522
  cache: {
189
523
  input: [
524
+ { auto: true },
190
525
  "src/**/i18n/*.json",
191
- "tooling/i18n/**",
192
526
  "project.inlang/settings.json",
193
527
  "project.inlang/paraglide.config.ts",
194
528
  "package.json",
195
- lockfile
529
+ ...install
196
530
  ],
197
531
  output: ["paraglide/**", ".paraglide/messages/**"]
198
532
  }
199
- },
533
+ } },
200
534
  "lint-css": {
201
535
  command: "bun tooling/css/check.ts",
202
536
  ...dependsOn,
@@ -212,7 +546,7 @@ function createTasks({ workspace, isCi }) {
212
546
  }
213
547
  },
214
548
  "dead-code-project": {
215
- command: "knip-bun --config tooling/knip.ts && knip-bun --config tooling/knip.ts --strict",
549
+ command: `${toolCommand("knip")} --config tooling/knip.ts && ${toolCommand("knip")} --config tooling/knip.ts --strict`,
216
550
  ...dependsOn,
217
551
  cache: {
218
552
  env: ["NODE_ENV"],
@@ -246,7 +580,7 @@ function createTasks({ workspace, isCi }) {
246
580
  }
247
581
  },
248
582
  "typecheck-node": {
249
- command: "tsc --project tsconfig.node.json",
583
+ command: `${toolCommand("tsc")} --project tsconfig.node.json`,
250
584
  ...dependsOn,
251
585
  cache: {
252
586
  input: [
@@ -277,12 +611,35 @@ function createTasks({ workspace, isCi }) {
277
611
  function isPathPrefix(baseURL) {
278
612
  return typeof baseURL === "string" && baseURL.startsWith("/") && baseURL.length > 1 && !baseURL.endsWith("/");
279
613
  }
280
- function validateOptions(options) {
614
+ const SERVER_OPTIONS = [
615
+ "middleware",
616
+ "serverSetup",
617
+ "securityHeaders",
618
+ "publicAssets",
619
+ "staticPaths",
620
+ "routeRules",
621
+ "publication",
622
+ "seo"
623
+ ];
624
+ function validateServerOptions(options) {
625
+ if ("publication" in options) throw new Error("publication was replaced by seo.markdown, seo.llms and seo.llmsFull");
626
+ if (typeof options.seo === "object") validatePublicationFeatures(options.seo);
281
627
  const asset = (options.publicAssets ?? []).find(({ baseURL }) => !isPathPrefix(baseURL));
282
628
  if (asset) throw new Error(`defineSolidApp: the publicAssets entry for ${asset.dir} needs a baseURL such as /images, without a trailing slash.`);
283
629
  const staticPath = (options.staticPaths ?? []).find((pattern) => !pattern.startsWith("/"));
284
630
  if (staticPath !== void 0) throw new Error(`defineSolidApp: the static path ${staticPath} must start with a slash.`);
285
631
  }
632
+ function validateClientOptions(options) {
633
+ const serverOption = [...SERVER_OPTIONS.filter((key) => key in options), ..."middleware" in (options.e2e ?? {}) ? ["e2e.middleware"] : []].at(0);
634
+ if (serverOption !== void 0) throw new Error(`defineSolidApp: ${serverOption} needs SSR; remove it or remove ssr: false.`);
635
+ }
636
+ function validateOptions(options) {
637
+ if (options.ssr === false) {
638
+ validateClientOptions(options);
639
+ return;
640
+ }
641
+ validateServerOptions(options);
642
+ }
286
643
  function loadTestEnvFile(root) {
287
644
  try {
288
645
  loadEnvFile(path.join(root, ".env.test"));
@@ -304,7 +661,8 @@ function defineSolidApp(options) {
304
661
  run: { tasks: {
305
662
  ...createTasks({
306
663
  workspace: options.workspace,
307
- isCi: Boolean(env.CI)
664
+ isCi: Boolean(env.CI),
665
+ hasI18n: hasI18n(root)
308
666
  }),
309
667
  ...options.tasks
310
668
  } },
@@ -329,7 +687,10 @@ function defineSolidApp(options) {
329
687
  fmt,
330
688
  lint,
331
689
  test: solidTest({
690
+ ...options.test,
332
691
  root,
692
+ language: options.language,
693
+ seo: options.ssr === false ? void 0 : options.seo,
333
694
  plugins: () => testPlugins({
334
695
  options,
335
696
  mode