@rexezuge/tooling 0.0.0-stage → 1.0.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.
- package/LICENSE +21 -0
- package/README.md +297 -2
- package/dist/eslint.d.ts +120 -0
- package/dist/eslint.d.ts.map +1 -0
- package/dist/eslint.js +551 -0
- package/dist/eslint.js.map +1 -0
- package/dist/functions/pages-proxy.d.ts +113 -0
- package/dist/functions/pages-proxy.d.ts.map +1 -0
- package/dist/functions/pages-proxy.js +131 -0
- package/dist/functions/pages-proxy.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/scripts/backup/d1-target.d.ts +35 -0
- package/dist/scripts/backup/d1-target.d.ts.map +1 -0
- package/dist/scripts/backup/d1-target.js +31 -0
- package/dist/scripts/backup/d1-target.js.map +1 -0
- package/dist/scripts/backup/destination-config.d.ts +61 -0
- package/dist/scripts/backup/destination-config.d.ts.map +1 -0
- package/dist/scripts/backup/destination-config.js +57 -0
- package/dist/scripts/backup/destination-config.js.map +1 -0
- package/dist/scripts/backup/encrypt-backup.d.ts +35 -0
- package/dist/scripts/backup/encrypt-backup.d.ts.map +1 -0
- package/dist/scripts/backup/encrypt-backup.js +98 -0
- package/dist/scripts/backup/encrypt-backup.js.map +1 -0
- package/dist/scripts/backup/naming.d.ts +44 -0
- package/dist/scripts/backup/naming.d.ts.map +1 -0
- package/dist/scripts/backup/naming.js +58 -0
- package/dist/scripts/backup/naming.js.map +1 -0
- package/dist/scripts/check-god-files.d.ts +164 -0
- package/dist/scripts/check-god-files.d.ts.map +1 -0
- package/dist/scripts/check-god-files.js +271 -0
- package/dist/scripts/check-god-files.js.map +1 -0
- package/dist/scripts/ensure-spa-shell-stub.d.ts +3 -0
- package/dist/scripts/ensure-spa-shell-stub.d.ts.map +1 -0
- package/dist/scripts/ensure-spa-shell-stub.js +30 -0
- package/dist/scripts/ensure-spa-shell-stub.js.map +1 -0
- package/dist/scripts/init-secrets.d.ts +63 -0
- package/dist/scripts/init-secrets.d.ts.map +1 -0
- package/dist/scripts/init-secrets.js +240 -0
- package/dist/scripts/init-secrets.js.map +1 -0
- package/dist/scripts/lib/cli-args.d.ts +78 -0
- package/dist/scripts/lib/cli-args.d.ts.map +1 -0
- package/dist/scripts/lib/cli-args.js +116 -0
- package/dist/scripts/lib/cli-args.js.map +1 -0
- package/dist/scripts/lib/github-actions.d.ts +26 -0
- package/dist/scripts/lib/github-actions.d.ts.map +1 -0
- package/dist/scripts/lib/github-actions.js +38 -0
- package/dist/scripts/lib/github-actions.js.map +1 -0
- package/dist/scripts/lib/wrangler-table.d.ts +46 -0
- package/dist/scripts/lib/wrangler-table.d.ts.map +1 -0
- package/dist/scripts/lib/wrangler-table.js +99 -0
- package/dist/scripts/lib/wrangler-table.js.map +1 -0
- package/dist/scripts/migrations-lock.d.ts +3 -0
- package/dist/scripts/migrations-lock.d.ts.map +1 -0
- package/dist/scripts/migrations-lock.js +46 -0
- package/dist/scripts/migrations-lock.js.map +1 -0
- package/dist/scripts/prepare-wrangler-config.d.ts +3 -0
- package/dist/scripts/prepare-wrangler-config.d.ts.map +1 -0
- package/dist/scripts/prepare-wrangler-config.js +50 -0
- package/dist/scripts/prepare-wrangler-config.js.map +1 -0
- package/dist/scripts/spa-shell.d.ts +41 -0
- package/dist/scripts/spa-shell.d.ts.map +1 -0
- package/dist/scripts/spa-shell.js +155 -0
- package/dist/scripts/spa-shell.js.map +1 -0
- package/dist/scripts/validate-locales.d.ts +26 -0
- package/dist/scripts/validate-locales.d.ts.map +1 -0
- package/dist/scripts/validate-locales.js +350 -0
- package/dist/scripts/validate-locales.js.map +1 -0
- package/dist/scripts/verify-migrations.d.ts +62 -0
- package/dist/scripts/verify-migrations.d.ts.map +1 -0
- package/dist/scripts/verify-migrations.js +302 -0
- package/dist/scripts/verify-migrations.js.map +1 -0
- package/dist/scripts/verify-spa-shell.d.ts +3 -0
- package/dist/scripts/verify-spa-shell.d.ts.map +1 -0
- package/dist/scripts/verify-spa-shell.js +53 -0
- package/dist/scripts/verify-spa-shell.js.map +1 -0
- package/dist/scripts/wrangler-config/cli.d.ts +22 -0
- package/dist/scripts/wrangler-config/cli.d.ts.map +1 -0
- package/dist/scripts/wrangler-config/cli.js +51 -0
- package/dist/scripts/wrangler-config/cli.js.map +1 -0
- package/dist/scripts/wrangler-config/patches.d.ts +51 -0
- package/dist/scripts/wrangler-config/patches.d.ts.map +1 -0
- package/dist/scripts/wrangler-config/patches.js +140 -0
- package/dist/scripts/wrangler-config/patches.js.map +1 -0
- package/dist/scripts/wrangler-config/resources.d.ts +70 -0
- package/dist/scripts/wrangler-config/resources.d.ts.map +1 -0
- package/dist/scripts/wrangler-config/resources.js +290 -0
- package/dist/scripts/wrangler-config/resources.js.map +1 -0
- package/dist/scripts/wrangler-config/types.d.ts +103 -0
- package/dist/scripts/wrangler-config/types.d.ts.map +1 -0
- package/dist/scripts/wrangler-config/types.js +49 -0
- package/dist/scripts/wrangler-config/types.js.map +1 -0
- package/dist/test/integration-migrations.d.ts +167 -0
- package/dist/test/integration-migrations.d.ts.map +1 -0
- package/dist/test/integration-migrations.js +171 -0
- package/dist/test/integration-migrations.js.map +1 -0
- package/dist/test/mocks/cloudflare-workers.d.ts +106 -0
- package/dist/test/mocks/cloudflare-workers.d.ts.map +1 -0
- package/dist/test/mocks/cloudflare-workers.js +90 -0
- package/dist/test/mocks/cloudflare-workers.js.map +1 -0
- package/dist/vite.d.ts +117 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +125 -0
- package/dist/vite.js.map +1 -0
- package/dist/vitest-web.d.ts +73 -0
- package/dist/vitest-web.d.ts.map +1 -0
- package/dist/vitest-web.js +72 -0
- package/dist/vitest-web.js.map +1 -0
- package/dist/vitest.d.ts +92 -0
- package/dist/vitest.d.ts.map +1 -0
- package/dist/vitest.js +128 -0
- package/dist/vitest.js.map +1 -0
- package/package.json +58 -3
- package/src/eslint.test.ts +175 -0
- package/src/eslint.ts +640 -0
- package/src/functions/pages-proxy.test.ts +72 -0
- package/src/functions/pages-proxy.ts +187 -0
- package/src/github/actions/retry-step/action.yml +39 -0
- package/src/github/actions/setup-env/action.yml +20 -0
- package/src/github/dependabot.yml +30 -0
- package/src/github/workflows/backup-main.yml +46 -0
- package/src/github/workflows/continuous-deployment.yml +188 -0
- package/src/github/workflows/continuous-integration.yml +259 -0
- package/src/github/workflows/scheduled-version-update.yml +38 -0
- package/src/github/workflows/upstream-sync.yml +56 -0
- package/src/index.ts +42 -0
- package/src/scripts/backup/backup-rules.test.ts +105 -0
- package/src/scripts/backup/d1-target.ts +54 -0
- package/src/scripts/backup/destination-config.ts +92 -0
- package/src/scripts/backup/encrypt-backup.ts +107 -0
- package/src/scripts/backup/naming.ts +62 -0
- package/src/scripts/check-god-files.test.ts +131 -0
- package/src/scripts/check-god-files.ts +327 -0
- package/src/scripts/ensure-spa-shell-stub.ts +34 -0
- package/src/scripts/init-secrets.ts +265 -0
- package/src/scripts/lib/cli-args.ts +154 -0
- package/src/scripts/lib/github-actions.ts +41 -0
- package/src/scripts/lib/wrangler-table.ts +105 -0
- package/src/scripts/migrations-lock.ts +52 -0
- package/src/scripts/prepare-wrangler-config.ts +51 -0
- package/src/scripts/spa-shell.test.ts +91 -0
- package/src/scripts/spa-shell.ts +179 -0
- package/src/scripts/validate-locales.test.ts +89 -0
- package/src/scripts/validate-locales.ts +380 -0
- package/src/scripts/verify-migrations.test.ts +71 -0
- package/src/scripts/verify-migrations.ts +364 -0
- package/src/scripts/verify-spa-shell.ts +56 -0
- package/src/scripts/wrangler-config/cli.ts +51 -0
- package/src/scripts/wrangler-config/patches.ts +157 -0
- package/src/scripts/wrangler-config/resources.ts +330 -0
- package/src/scripts/wrangler-config/types.ts +113 -0
- package/src/test/integration-migrations.test.ts +169 -0
- package/src/test/integration-migrations.ts +267 -0
- package/src/test/mocks/cloudflare-workers.ts +115 -0
- package/src/vite.test.ts +83 -0
- package/src/vite.ts +202 -0
- package/src/vitest-web.ts +109 -0
- package/src/vitest.ts +185 -0
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SPA's Vite config.
|
|
3
|
+
*
|
|
4
|
+
* Provenance: converged from the `apps/web/vite.config.ts` of the eight repos
|
|
5
|
+
* that build a Vite SPA. They are one file with three variables: the plugin list
|
|
6
|
+
* (`@vitejs/plugin-react` + `@tailwindcss/vite`), the dev-server proxy target,
|
|
7
|
+
* and the `spa-shell-embed` plugin that copies `dist/index.html` into
|
|
8
|
+
* `apps/api/src/generated/spa-shell.ts`.
|
|
9
|
+
*
|
|
10
|
+
* Canonical decisions:
|
|
11
|
+
*
|
|
12
|
+
* 1. **The plugins are passed in, not imported.** The kit stays zero-dependency:
|
|
13
|
+
* `react`, `@vitejs/plugin-react` and `@tailwindcss/vite` belong to the app
|
|
14
|
+
* that uses them, and importing them here would make the tooling package
|
|
15
|
+
* undownloadable for a consumer without a React SPA.
|
|
16
|
+
* 2. **The `spa-shell-embed` hook stays per-repo, and is passed in.** It is the
|
|
17
|
+
* one genuinely repo-specific piece: each repo names its own worker and its
|
|
18
|
+
* own generated-module path. What the kit provides is the *contract* — see
|
|
19
|
+
* `spaShellEmbedPlugin`, which is the converged body of all eight copies.
|
|
20
|
+
* 3. **`server.proxy` is data, not code.** The paths differ per repo
|
|
21
|
+
* (`/user` + `/rest` in Edge-Sonic, `/api` in CalDAV-Bridge, `/tracker` in
|
|
22
|
+
* ChordDHT-Tracker) but the shape never does, so it is an option with a
|
|
23
|
+
* default.
|
|
24
|
+
* 4. **`build.emptyOutDir: true`** is the default, because a stale asset in
|
|
25
|
+
* `dist/` is served as a live bundle — the same half-refreshed build
|
|
26
|
+
* `scripts/verify-spa-shell.ts` rejects.
|
|
27
|
+
*
|
|
28
|
+
* The consumer's `apps/web/vite.config.ts` becomes a handful of lines — see the
|
|
29
|
+
* README.
|
|
30
|
+
*/
|
|
31
|
+
import { defineConfig, type Plugin } from 'vitest/config';
|
|
32
|
+
/**
|
|
33
|
+
* One dev-server proxy entry, as Vite's `server.proxy` accepts it.
|
|
34
|
+
*/
|
|
35
|
+
export interface ProxyEntry {
|
|
36
|
+
readonly target: string;
|
|
37
|
+
readonly changeOrigin?: boolean;
|
|
38
|
+
readonly bypass?: (request: {
|
|
39
|
+
headers: Record<string, string | string[] | undefined>;
|
|
40
|
+
}) => string | undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Where the SPA's built `index.html` is copied to, and under what module name.
|
|
44
|
+
*/
|
|
45
|
+
export interface SpaShellTarget {
|
|
46
|
+
/**
|
|
47
|
+
* Path of the generated module, relative to the repository root.
|
|
48
|
+
*
|
|
49
|
+
* `apps/api/src/generated/spa-shell.ts` in every repo that embeds a shell; the
|
|
50
|
+
* parameter exists because the path is the one thing that genuinely differs.
|
|
51
|
+
*/
|
|
52
|
+
readonly path: string;
|
|
53
|
+
/**
|
|
54
|
+
* The repository root the path is resolved against, as a file URL or a path.
|
|
55
|
+
*
|
|
56
|
+
* Defaults to the directory the build runs in (`process.cwd()`), which is the
|
|
57
|
+
* repo root when the build is `pnpm --filter @scope/web build`. Passed in by the
|
|
58
|
+
* Vite config, not derived from `import.meta.url`: this plugin object is created
|
|
59
|
+
* inside the kit's own module, so a URL relative to it points into
|
|
60
|
+
* `node_modules/@rexezuge/tooling` and writes the shell somewhere no compiler
|
|
61
|
+
* will ever look.
|
|
62
|
+
*/
|
|
63
|
+
readonly root?: string | URL;
|
|
64
|
+
/**
|
|
65
|
+
* The built `index.html`, as a path or file URL.
|
|
66
|
+
*
|
|
67
|
+
* Defaults to `dist/index.html` under the same root. Vite's `closeBundle` runs
|
|
68
|
+
* after the bundle is written, so this is the file the build just produced.
|
|
69
|
+
*/
|
|
70
|
+
readonly distIndex?: string | URL;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* What `defineWebViteConfig` accepts.
|
|
74
|
+
*/
|
|
75
|
+
export interface WebViteConfigOptions {
|
|
76
|
+
/**
|
|
77
|
+
* The SPA's plugins — `react()`, `tailwindcss()`, and anything else the app
|
|
78
|
+
* needs. Applied before the spa-shell plugin so the shell is written after the
|
|
79
|
+
* bundle exists.
|
|
80
|
+
*/
|
|
81
|
+
readonly plugins?: readonly Plugin[];
|
|
82
|
+
/**
|
|
83
|
+
* Dev-server proxy entries. Keyed by path prefix.
|
|
84
|
+
*/
|
|
85
|
+
readonly proxy?: Readonly<Record<string, ProxyEntry>>;
|
|
86
|
+
/**
|
|
87
|
+
* Where the built shell is embedded. Omit to skip the embedding entirely.
|
|
88
|
+
*/
|
|
89
|
+
readonly spaShell?: SpaShellTarget;
|
|
90
|
+
/**
|
|
91
|
+
* The build output directory.
|
|
92
|
+
*/
|
|
93
|
+
readonly outDir?: string;
|
|
94
|
+
/**
|
|
95
|
+
* The directory this config is resolved from, as a file URL. Defaults to the
|
|
96
|
+
* caller's location.
|
|
97
|
+
*/
|
|
98
|
+
readonly root?: string;
|
|
99
|
+
}
|
|
100
|
+
export declare function spaShellEmbedPlugin(target: SpaShellTarget): Plugin;
|
|
101
|
+
/**
|
|
102
|
+
* Build the SPA's Vite config.
|
|
103
|
+
*
|
|
104
|
+
* ```ts
|
|
105
|
+
* // apps/web/vite.config.ts
|
|
106
|
+
* import react from '@vitejs/plugin-react';
|
|
107
|
+
* import { defineWebViteConfig, spaShellEmbedPlugin } from '@rexezuge/tooling/vite';
|
|
108
|
+
*
|
|
109
|
+
* export default defineWebViteConfig({
|
|
110
|
+
* plugins: [react()],
|
|
111
|
+
* proxy: { '/user': { target: 'http://localhost:8787', changeOrigin: true } },
|
|
112
|
+
* spaShell: { path: '../../apps/api/src/generated/spa-shell.ts' },
|
|
113
|
+
* });
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export declare function defineWebViteConfig(options?: WebViteConfigOptions): ReturnType<typeof defineConfig>;
|
|
117
|
+
//# sourceMappingURL=vite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAKH,OAAO,EAAE,YAAY,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;KAAE,KAAK,MAAM,GAAG,SAAS,CAAC;CAC/G;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;;;;OASG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;OAEG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IACtD;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IACnC;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AA0CD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAyBlE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,oBAAyB,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAcvG"}
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SPA's Vite config.
|
|
3
|
+
*
|
|
4
|
+
* Provenance: converged from the `apps/web/vite.config.ts` of the eight repos
|
|
5
|
+
* that build a Vite SPA. They are one file with three variables: the plugin list
|
|
6
|
+
* (`@vitejs/plugin-react` + `@tailwindcss/vite`), the dev-server proxy target,
|
|
7
|
+
* and the `spa-shell-embed` plugin that copies `dist/index.html` into
|
|
8
|
+
* `apps/api/src/generated/spa-shell.ts`.
|
|
9
|
+
*
|
|
10
|
+
* Canonical decisions:
|
|
11
|
+
*
|
|
12
|
+
* 1. **The plugins are passed in, not imported.** The kit stays zero-dependency:
|
|
13
|
+
* `react`, `@vitejs/plugin-react` and `@tailwindcss/vite` belong to the app
|
|
14
|
+
* that uses them, and importing them here would make the tooling package
|
|
15
|
+
* undownloadable for a consumer without a React SPA.
|
|
16
|
+
* 2. **The `spa-shell-embed` hook stays per-repo, and is passed in.** It is the
|
|
17
|
+
* one genuinely repo-specific piece: each repo names its own worker and its
|
|
18
|
+
* own generated-module path. What the kit provides is the *contract* — see
|
|
19
|
+
* `spaShellEmbedPlugin`, which is the converged body of all eight copies.
|
|
20
|
+
* 3. **`server.proxy` is data, not code.** The paths differ per repo
|
|
21
|
+
* (`/user` + `/rest` in Edge-Sonic, `/api` in CalDAV-Bridge, `/tracker` in
|
|
22
|
+
* ChordDHT-Tracker) but the shape never does, so it is an option with a
|
|
23
|
+
* default.
|
|
24
|
+
* 4. **`build.emptyOutDir: true`** is the default, because a stale asset in
|
|
25
|
+
* `dist/` is served as a live bundle — the same half-refreshed build
|
|
26
|
+
* `scripts/verify-spa-shell.ts` rejects.
|
|
27
|
+
*
|
|
28
|
+
* The consumer's `apps/web/vite.config.ts` becomes a handful of lines — see the
|
|
29
|
+
* README.
|
|
30
|
+
*/
|
|
31
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
32
|
+
import { dirname } from 'node:path';
|
|
33
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
34
|
+
import { defineConfig } from 'vitest/config';
|
|
35
|
+
/**
|
|
36
|
+
* The `spa-shell-embed` plugin, converged from all eight copies.
|
|
37
|
+
*
|
|
38
|
+
* It exists because the API worker serves `SPA_HTML` from a module in its own
|
|
39
|
+
* source tree, and both that module and `dist/` are gitignored — so a fresh
|
|
40
|
+
* clone gets an empty stub from `postinstall` and every `GET /` returns a blank
|
|
41
|
+
* page until someone runs a build. This plugin is what makes the build write the
|
|
42
|
+
* real artefact, and `scripts/verify-spa-shell.ts` is what refuses to deploy
|
|
43
|
+
* without it.
|
|
44
|
+
*
|
|
45
|
+
* Written as a factory rather than inlined so the exit conditions are in one
|
|
46
|
+
* place: the `closeBundle` hook runs on every build, including the ones where
|
|
47
|
+
* `dist/index.html` was never produced, and a plugin that throws there fails
|
|
48
|
+
* builds that legitimately emit nothing.
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* Normalise a path-or-URL option to a URL, so `new URL(...)` can be used on it.
|
|
52
|
+
*
|
|
53
|
+
* Small, and here because two options accept either form: a path is what a consumer
|
|
54
|
+
* naturally writes in a config, and a URL is what `import.meta.url` hands them.
|
|
55
|
+
* Accepting both is one line and saves a `fileURLToPath` at every call site.
|
|
56
|
+
*/
|
|
57
|
+
function toUrl(value) {
|
|
58
|
+
return typeof value === 'string' ? pathToFileURL(value) : value;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The same, as a **directory** URL — with the trailing separator `new URL(rel, base)`
|
|
62
|
+
* needs.
|
|
63
|
+
*
|
|
64
|
+
* `new URL('apps/api/x.ts', 'file:///repo')` resolves to `/apps/api/x.ts`, silently
|
|
65
|
+
* dropping the base. The base has to end in a separator, and a path from
|
|
66
|
+
* `process.cwd()` never does, so this is where the plugin would otherwise write the
|
|
67
|
+
* shell into the filesystem root.
|
|
68
|
+
*/
|
|
69
|
+
function toDirUrl(value) {
|
|
70
|
+
const url = toUrl(value);
|
|
71
|
+
return url.href.endsWith('/') ? url : new URL(`${url.href}/`);
|
|
72
|
+
}
|
|
73
|
+
export function spaShellEmbedPlugin(target) {
|
|
74
|
+
return {
|
|
75
|
+
name: 'spa-shell-embed',
|
|
76
|
+
closeBundle() {
|
|
77
|
+
// Resolved from the directory the build runs in, never from this module's own
|
|
78
|
+
// URL: the plugin object is created here but executes in the consumer's Vite
|
|
79
|
+
// process, and `new URL(path, import.meta.url)` would resolve against
|
|
80
|
+
// `node_modules/@rexezuge/tooling`. `process.cwd()` is the repo root for
|
|
81
|
+
// `pnpm --filter @scope/web build`, which is how every consumer runs it.
|
|
82
|
+
const root = toDirUrl(target.root ?? process.cwd());
|
|
83
|
+
const outputPath = fileURLToPath(new URL(target.path, root));
|
|
84
|
+
const indexPath = target.distIndex === undefined ? fileURLToPath(new URL('dist/index.html', root)) : fileURLToPath(toUrl(target.distIndex));
|
|
85
|
+
if (!existsSync(indexPath)) {
|
|
86
|
+
console.warn('spa-shell-embed: dist/index.html not found, skipping');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const html = readFileSync(indexPath, 'utf8');
|
|
90
|
+
const output = `// Auto-generated by Vite build plugin - DO NOT EDIT.\nexport const SPA_HTML: string = ${JSON.stringify(html)};\n`;
|
|
91
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
92
|
+
writeFileSync(outputPath, output);
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Build the SPA's Vite config.
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* // apps/web/vite.config.ts
|
|
101
|
+
* import react from '@vitejs/plugin-react';
|
|
102
|
+
* import { defineWebViteConfig, spaShellEmbedPlugin } from '@rexezuge/tooling/vite';
|
|
103
|
+
*
|
|
104
|
+
* export default defineWebViteConfig({
|
|
105
|
+
* plugins: [react()],
|
|
106
|
+
* proxy: { '/user': { target: 'http://localhost:8787', changeOrigin: true } },
|
|
107
|
+
* spaShell: { path: '../../apps/api/src/generated/spa-shell.ts' },
|
|
108
|
+
* });
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export function defineWebViteConfig(options = {}) {
|
|
112
|
+
const plugins = [...(options.plugins ?? [])];
|
|
113
|
+
if (options.spaShell !== undefined) {
|
|
114
|
+
plugins.push(spaShellEmbedPlugin(options.spaShell));
|
|
115
|
+
}
|
|
116
|
+
return defineConfig({
|
|
117
|
+
...(plugins.length > 0 && { plugins }),
|
|
118
|
+
...(options.proxy !== undefined && { server: { proxy: { ...options.proxy } } }),
|
|
119
|
+
build: {
|
|
120
|
+
outDir: options.outDir ?? 'dist',
|
|
121
|
+
emptyOutDir: true,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=vite.js.map
|
package/dist/vite.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.js","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SPA's test and coverage gate.
|
|
3
|
+
*
|
|
4
|
+
* Provenance: converged from Durable-DAV-Router's and Mail-Otter's
|
|
5
|
+
* `vitest.web.config.mts`, which are the two repos that gave the frontend its own
|
|
6
|
+
* suite. Durable-DAV-Router's is the canonical shape — it is the one that records
|
|
7
|
+
* *why* the split exists; Mail-Otter's contributes the React plugin and the
|
|
8
|
+
* `apps/web/**` include.
|
|
9
|
+
*
|
|
10
|
+
* Canonical decisions:
|
|
11
|
+
*
|
|
12
|
+
* 1. **It is a separate config, not a `projects` entry in one.** With a single
|
|
13
|
+
* config the SPA suites ran under `node` and the worker suites under `jsdom`
|
|
14
|
+
* in the same pass, so a test could pass in one environment and fail in the
|
|
15
|
+
* other with neither run noticing.
|
|
16
|
+
* 2. **`pool: 'threads'`, bounded, rather than one forked worker per file.** Each
|
|
17
|
+
* jsdom document is a whole browser environment; a fork per file exhausts
|
|
18
|
+
* memory on a constrained machine, and when it does the run dies partway with
|
|
19
|
+
* a bare "Worker exited unexpectedly" and no test reports a failure — which
|
|
20
|
+
* reads as a flaky suite rather than as an out-of-memory kill.
|
|
21
|
+
* 3. **jsdom is set here for every file**, rather than a `@vitest-environment`
|
|
22
|
+
* pragma per file: these are the tests that need a DOM, and the pragma is one
|
|
23
|
+
* more place to forget.
|
|
24
|
+
* 4. **`include` covers both shapes the repos use** — the root-suite `web-*`
|
|
25
|
+
* naming convention and suites bundled under `apps/web` — and the same globs
|
|
26
|
+
* are what `defineUnitVitestConfig()` excludes.
|
|
27
|
+
*/
|
|
28
|
+
import { defineConfig, type Plugin } from 'vitest/config';
|
|
29
|
+
import type { CoverageThresholds, VitestAlias } from './vitest';
|
|
30
|
+
/**
|
|
31
|
+
* What `defineWebVitestConfig` accepts.
|
|
32
|
+
*/
|
|
33
|
+
export interface WebVitestConfigOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Vite plugins the SPA needs — `@vitejs/plugin-react` for a React app. Passed
|
|
36
|
+
* by the consumer rather than imported here, because the kit must stay
|
|
37
|
+
* zero-dependency and the plugin belongs to the app, not the harness.
|
|
38
|
+
*/
|
|
39
|
+
readonly plugins?: readonly Plugin[];
|
|
40
|
+
/**
|
|
41
|
+
* Extra resolver entries.
|
|
42
|
+
*/
|
|
43
|
+
readonly aliases?: readonly VitestAlias[];
|
|
44
|
+
/**
|
|
45
|
+
* The repo root, as a file URL.
|
|
46
|
+
*/
|
|
47
|
+
readonly root?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Which suites to collect. Defaults to both shapes the source repos use.
|
|
50
|
+
*/
|
|
51
|
+
readonly include?: readonly string[];
|
|
52
|
+
/**
|
|
53
|
+
* Coverage floors. Omitted by default; see `defineUnitVitestConfig`.
|
|
54
|
+
*/
|
|
55
|
+
readonly thresholds?: CoverageThresholds;
|
|
56
|
+
/**
|
|
57
|
+
* Where the coverage report is written. Separate from the unit config's, so
|
|
58
|
+
* the two gates never overwrite each other's report.
|
|
59
|
+
*/
|
|
60
|
+
readonly reportsDirectory?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build the SPA's test config.
|
|
64
|
+
*
|
|
65
|
+
* ```ts
|
|
66
|
+
* // vitest.web.config.mts
|
|
67
|
+
* import react from '@vitejs/plugin-react';
|
|
68
|
+
* import { defineWebVitestConfig } from '@rexezuge/tooling/vitest-web';
|
|
69
|
+
* export default defineWebVitestConfig({ plugins: [react()] });
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare function defineWebVitestConfig(options?: WebVitestConfigOptions): ReturnType<typeof defineConfig>;
|
|
73
|
+
//# sourceMappingURL=vitest-web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitest-web.d.ts","sourceRoot":"","sources":["../src/vitest-web.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAE1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC1C;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;OAEG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC;IACzC;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAQD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,sBAA2B,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CA2B3G"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SPA's test and coverage gate.
|
|
3
|
+
*
|
|
4
|
+
* Provenance: converged from Durable-DAV-Router's and Mail-Otter's
|
|
5
|
+
* `vitest.web.config.mts`, which are the two repos that gave the frontend its own
|
|
6
|
+
* suite. Durable-DAV-Router's is the canonical shape — it is the one that records
|
|
7
|
+
* *why* the split exists; Mail-Otter's contributes the React plugin and the
|
|
8
|
+
* `apps/web/**` include.
|
|
9
|
+
*
|
|
10
|
+
* Canonical decisions:
|
|
11
|
+
*
|
|
12
|
+
* 1. **It is a separate config, not a `projects` entry in one.** With a single
|
|
13
|
+
* config the SPA suites ran under `node` and the worker suites under `jsdom`
|
|
14
|
+
* in the same pass, so a test could pass in one environment and fail in the
|
|
15
|
+
* other with neither run noticing.
|
|
16
|
+
* 2. **`pool: 'threads'`, bounded, rather than one forked worker per file.** Each
|
|
17
|
+
* jsdom document is a whole browser environment; a fork per file exhausts
|
|
18
|
+
* memory on a constrained machine, and when it does the run dies partway with
|
|
19
|
+
* a bare "Worker exited unexpectedly" and no test reports a failure — which
|
|
20
|
+
* reads as a flaky suite rather than as an out-of-memory kill.
|
|
21
|
+
* 3. **jsdom is set here for every file**, rather than a `@vitest-environment`
|
|
22
|
+
* pragma per file: these are the tests that need a DOM, and the pragma is one
|
|
23
|
+
* more place to forget.
|
|
24
|
+
* 4. **`include` covers both shapes the repos use** — the root-suite `web-*`
|
|
25
|
+
* naming convention and suites bundled under `apps/web` — and the same globs
|
|
26
|
+
* are what `defineUnitVitestConfig()` excludes.
|
|
27
|
+
*/
|
|
28
|
+
import { defineConfig } from 'vitest/config';
|
|
29
|
+
import { fileURLToPath } from 'node:url';
|
|
30
|
+
/**
|
|
31
|
+
* The default suite globs: the root-suite `web-*` convention and the
|
|
32
|
+
* bundled-under-`apps/web` convention, so either layout works unchanged.
|
|
33
|
+
*/
|
|
34
|
+
const DEFAULT_INCLUDE = ['test/**/web-*.test.{ts,tsx}', 'apps/web/**/*.test.{ts,tsx}'];
|
|
35
|
+
/**
|
|
36
|
+
* Build the SPA's test config.
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* // vitest.web.config.mts
|
|
40
|
+
* import react from '@vitejs/plugin-react';
|
|
41
|
+
* import { defineWebVitestConfig } from '@rexezuge/tooling/vitest-web';
|
|
42
|
+
* export default defineWebVitestConfig({ plugins: [react()] });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function defineWebVitestConfig(options = {}) {
|
|
46
|
+
const root = options.root ?? new URL('.', import.meta.url).href;
|
|
47
|
+
const webSrc = fileURLToPath(new URL('apps/web/src', root));
|
|
48
|
+
const aliases = [{ find: /^@\//, replacement: `${webSrc}/` }, ...(options.aliases ?? [])];
|
|
49
|
+
return defineConfig({
|
|
50
|
+
...(options.plugins !== undefined && { plugins: [...options.plugins] }),
|
|
51
|
+
resolve: { alias: aliases },
|
|
52
|
+
test: {
|
|
53
|
+
globals: true,
|
|
54
|
+
environment: 'jsdom',
|
|
55
|
+
include: [...(options.include ?? DEFAULT_INCLUDE)],
|
|
56
|
+
// Threads, bounded, rather than one forked worker per file — see the
|
|
57
|
+
// header. A custom `exclude` replaces Vitest's defaults, so `node_modules`
|
|
58
|
+
// must be re-listed.
|
|
59
|
+
pool: 'threads',
|
|
60
|
+
exclude: ['**/node_modules/**', '**/dist/**', 'test/integration/**'],
|
|
61
|
+
coverage: {
|
|
62
|
+
provider: 'v8',
|
|
63
|
+
reporter: ['text', 'lcov'],
|
|
64
|
+
reportsDirectory: options.reportsDirectory ?? './coverage-spa',
|
|
65
|
+
include: ['apps/web/src/**/*.{ts,tsx}'],
|
|
66
|
+
exclude: ['**/*.test.{ts,tsx}', '**/*.d.ts', '**/types.ts', '**/generated/**'],
|
|
67
|
+
...(options.thresholds !== undefined && { thresholds: { ...options.thresholds } }),
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=vitest-web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitest-web.js","sourceRoot":"","sources":["../src/vitest-web.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,YAAY,EAAe,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAoCzC;;;GAGG;AACH,MAAM,eAAe,GAAsB,CAAC,6BAA6B,EAAE,6BAA6B,CAAC,CAAC;AAE1G;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,UAAkC,EAAE;IACxE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;IAChE,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC,CAAC;IAC5D,MAAM,OAAO,GAAkB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,GAAG,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC;IAEzG,OAAO,YAAY,CAAC;QAClB,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACvE,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE;QAC3B,IAAI,EAAE;YACJ,OAAO,EAAE,IAAI;YACb,WAAW,EAAE,OAAO;YACpB,OAAO,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,eAAe,CAAC,CAAC;YAClD,qEAAqE;YACrE,2EAA2E;YAC3E,qBAAqB;YACrB,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,CAAC,oBAAoB,EAAE,YAAY,EAAE,qBAAqB,CAAC;YACpE,QAAQ,EAAE;gBACR,QAAQ,EAAE,IAAI;gBACd,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;gBAC1B,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,gBAAgB;gBAC9D,OAAO,EAAE,CAAC,4BAA4B,CAAC;gBACvC,OAAO,EAAE,CAAC,oBAAoB,EAAE,WAAW,EAAE,aAAa,EAAE,iBAAiB,CAAC;gBAC9E,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;aACnF;SACF;KACF,CAAC,CAAC;AACL,CAAC"}
|
package/dist/vitest.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The unit-test Vitest config for the worker-and-packages half of the suite.
|
|
3
|
+
*
|
|
4
|
+
* Provenance: converged from the eight repos' `vitest.config.mts`. The coverage
|
|
5
|
+
* block is Durable-DAV-Router's (the most complete: v8 provider, explicit
|
|
6
|
+
* include/exclude patterns, thresholds with the reasoning for why they sit just
|
|
7
|
+
* under the measured value); the alias block is Durable-DAV's, which is the only
|
|
8
|
+
* one that also aliases `cloudflare:workers` and `cloudflare:sockets` to the
|
|
9
|
+
* Node mocks.
|
|
10
|
+
*
|
|
11
|
+
* Canonical decisions:
|
|
12
|
+
*
|
|
13
|
+
* 1. **Every file matching `include` is reported** — Vitest 4 removed
|
|
14
|
+
* `coverage.all`, and an explicit `include` now covers files no test
|
|
15
|
+
* imported. That is the behaviour the source repos configured `all: true` for,
|
|
16
|
+
* and the reason it mattered: without it a brand-new module with no test is
|
|
17
|
+
* invisible rather than counted as zero, which is the exact regression this
|
|
18
|
+
* gate exists to catch.
|
|
19
|
+
* 2. **The SPA is excluded here**, so this config and `defineWebVitestConfig()`
|
|
20
|
+
* partition the suite rather than each running all of it. Every SPA suite is
|
|
21
|
+
* named `web-*`. One number could not honestly describe both halves: the SPA's
|
|
22
|
+
* presentational modules pulled a shared floor down 16 points, so a change
|
|
23
|
+
* adding legitimate code in `apps/api` could fail the gate on the strength of
|
|
24
|
+
* files two directories away.
|
|
25
|
+
* 3. **A custom `exclude` re-lists `node_modules`** — `test/` is a workspace
|
|
26
|
+
* project and therefore has its own, so without the re-list the glob reaches
|
|
27
|
+
* into `test/node_modules` and tries to run other packages' suites.
|
|
28
|
+
* 4. **Thresholds are a consumer argument**, not a kit default. A floor is a
|
|
29
|
+
* per-repo ratchet measured against that repo's suite; the kit cannot know a
|
|
30
|
+
* number, and guessing one is how floors get lowered.
|
|
31
|
+
*/
|
|
32
|
+
import { defineConfig } from 'vitest/config';
|
|
33
|
+
/**
|
|
34
|
+
* One `{ find, replacement }` pair for Vitest's resolver.
|
|
35
|
+
*/
|
|
36
|
+
export interface VitestAlias {
|
|
37
|
+
readonly find: string | RegExp;
|
|
38
|
+
readonly replacement: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Coverage thresholds, in the four figures Vitest gates on.
|
|
42
|
+
*/
|
|
43
|
+
export interface CoverageThresholds {
|
|
44
|
+
readonly statements?: number;
|
|
45
|
+
readonly branches?: number;
|
|
46
|
+
readonly functions?: number;
|
|
47
|
+
readonly lines?: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* What `defineUnitVitestConfig` accepts. Every field is optional: the defaults
|
|
51
|
+
* cover the canonical monorepo layout, and a consumer overrides only what its
|
|
52
|
+
* tree actually differs in.
|
|
53
|
+
*/
|
|
54
|
+
export interface UnitVitestConfigOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Extra resolver entries, applied after the canonical ones.
|
|
57
|
+
*/
|
|
58
|
+
readonly aliases?: readonly VitestAlias[];
|
|
59
|
+
/**
|
|
60
|
+
* Where the repo root is, as a file URL. Defaults to the directory this
|
|
61
|
+
* factory is *called* from, which is the repo root's `vitest.config.mts`.
|
|
62
|
+
*/
|
|
63
|
+
readonly root?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Coverage `include` globs. Defaults to the worker plus every workspace
|
|
66
|
+
* package's `src`.
|
|
67
|
+
*/
|
|
68
|
+
readonly coverageInclude?: readonly string[];
|
|
69
|
+
/**
|
|
70
|
+
* Coverage `exclude` globs, merged with the canonical set.
|
|
71
|
+
*/
|
|
72
|
+
readonly coverageExclude?: readonly string[];
|
|
73
|
+
/**
|
|
74
|
+
* Coverage floors. Omitted by default; a repo that wants them sets them.
|
|
75
|
+
*/
|
|
76
|
+
readonly thresholds?: CoverageThresholds;
|
|
77
|
+
/**
|
|
78
|
+
* Where the coverage report is written.
|
|
79
|
+
*/
|
|
80
|
+
readonly reportsDirectory?: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Build the unit-test config.
|
|
84
|
+
*
|
|
85
|
+
* ```ts
|
|
86
|
+
* // vitest.config.mts
|
|
87
|
+
* import { defineUnitVitestConfig } from '@rexezuge/tooling/vitest';
|
|
88
|
+
* export default defineUnitVitestConfig();
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export declare function defineUnitVitestConfig(options?: UnitVitestConfigOptions): ReturnType<typeof defineConfig>;
|
|
92
|
+
//# sourceMappingURL=vitest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG7C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC1C;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C;;OAEG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C;;OAEG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC;IACzC;;OAEG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAmDD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,uBAA4B,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAuC7G"}
|
package/dist/vitest.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The unit-test Vitest config for the worker-and-packages half of the suite.
|
|
3
|
+
*
|
|
4
|
+
* Provenance: converged from the eight repos' `vitest.config.mts`. The coverage
|
|
5
|
+
* block is Durable-DAV-Router's (the most complete: v8 provider, explicit
|
|
6
|
+
* include/exclude patterns, thresholds with the reasoning for why they sit just
|
|
7
|
+
* under the measured value); the alias block is Durable-DAV's, which is the only
|
|
8
|
+
* one that also aliases `cloudflare:workers` and `cloudflare:sockets` to the
|
|
9
|
+
* Node mocks.
|
|
10
|
+
*
|
|
11
|
+
* Canonical decisions:
|
|
12
|
+
*
|
|
13
|
+
* 1. **Every file matching `include` is reported** — Vitest 4 removed
|
|
14
|
+
* `coverage.all`, and an explicit `include` now covers files no test
|
|
15
|
+
* imported. That is the behaviour the source repos configured `all: true` for,
|
|
16
|
+
* and the reason it mattered: without it a brand-new module with no test is
|
|
17
|
+
* invisible rather than counted as zero, which is the exact regression this
|
|
18
|
+
* gate exists to catch.
|
|
19
|
+
* 2. **The SPA is excluded here**, so this config and `defineWebVitestConfig()`
|
|
20
|
+
* partition the suite rather than each running all of it. Every SPA suite is
|
|
21
|
+
* named `web-*`. One number could not honestly describe both halves: the SPA's
|
|
22
|
+
* presentational modules pulled a shared floor down 16 points, so a change
|
|
23
|
+
* adding legitimate code in `apps/api` could fail the gate on the strength of
|
|
24
|
+
* files two directories away.
|
|
25
|
+
* 3. **A custom `exclude` re-lists `node_modules`** — `test/` is a workspace
|
|
26
|
+
* project and therefore has its own, so without the re-list the glob reaches
|
|
27
|
+
* into `test/node_modules` and tries to run other packages' suites.
|
|
28
|
+
* 4. **Thresholds are a consumer argument**, not a kit default. A floor is a
|
|
29
|
+
* per-repo ratchet measured against that repo's suite; the kit cannot know a
|
|
30
|
+
* number, and guessing one is how floors get lowered.
|
|
31
|
+
*/
|
|
32
|
+
import { defineConfig } from 'vitest/config';
|
|
33
|
+
import { fileURLToPath } from 'node:url';
|
|
34
|
+
/**
|
|
35
|
+
* The SPA's suites, excluded here so the two configs partition the suite.
|
|
36
|
+
*
|
|
37
|
+
* Every SPA suite is named `web-*`. `defineWebVitestConfig()` includes the same
|
|
38
|
+
* glob, so a file is collected by exactly one config.
|
|
39
|
+
*/
|
|
40
|
+
const SPA_FILES = 'test/**/web-*.test.{ts,tsx}';
|
|
41
|
+
/**
|
|
42
|
+
* The canonical alias set.
|
|
43
|
+
*
|
|
44
|
+
* Built relative to the consumer's repo root rather than the kit's directory,
|
|
45
|
+
* so the factory describes *their* layout. A Vitest config is a standalone file
|
|
46
|
+
* with no module graph to share through, and a suite resolving a workspace
|
|
47
|
+
* import differently from its sibling fails in a way that looks like a product
|
|
48
|
+
* bug — which is why these are stated once, here, rather than duplicated.
|
|
49
|
+
*/
|
|
50
|
+
function canonicalAliases(root) {
|
|
51
|
+
const at = (relative) => fileURLToPath(new URL(relative, root));
|
|
52
|
+
return [
|
|
53
|
+
{ find: 'cloudflare:workers', replacement: at('test/mocks/cloudflare-workers.ts') },
|
|
54
|
+
{ find: 'cloudflare:sockets', replacement: at('test/mocks/cloudflare-sockets.ts') },
|
|
55
|
+
{ find: /^@\//, replacement: `${at('apps/api/src')}/` },
|
|
56
|
+
{ find: /^~\//, replacement: `${at('apps/web/src')}/` },
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Coverage `exclude` globs converged from the source repos.
|
|
61
|
+
*
|
|
62
|
+
* The categories: build and test tooling (`scripts/` grows with project surface,
|
|
63
|
+
* not complexity, and the god-file guard excludes it for the same reason), test
|
|
64
|
+
* files, declarations, re-export barrels (`index.ts` carries no logic of its
|
|
65
|
+
* own), model records, generated files (the SPA shell is a one-line HTML blob
|
|
66
|
+
* with no logic to exercise), and type-only modules.
|
|
67
|
+
*/
|
|
68
|
+
const CANONICAL_COVERAGE_EXCLUDE = [
|
|
69
|
+
'scripts/**',
|
|
70
|
+
'**/*.test.ts',
|
|
71
|
+
'**/*.d.ts',
|
|
72
|
+
'**/index.ts',
|
|
73
|
+
'**/types.d.ts',
|
|
74
|
+
'**/model/**',
|
|
75
|
+
'**/generated/**',
|
|
76
|
+
'**/D1Types.ts',
|
|
77
|
+
'**/ServiceEnv.ts',
|
|
78
|
+
'**/env.d.ts',
|
|
79
|
+
];
|
|
80
|
+
/**
|
|
81
|
+
* Build the unit-test config.
|
|
82
|
+
*
|
|
83
|
+
* ```ts
|
|
84
|
+
* // vitest.config.mts
|
|
85
|
+
* import { defineUnitVitestConfig } from '@rexezuge/tooling/vitest';
|
|
86
|
+
* export default defineUnitVitestConfig();
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export function defineUnitVitestConfig(options = {}) {
|
|
90
|
+
const root = options.root ?? new URL('.', import.meta.url).href;
|
|
91
|
+
const coverageInclude = options.coverageInclude ?? ['apps/api/src/**/*.ts', 'apps/background/src/**/*.ts', 'packages/**/src/**/*.ts'];
|
|
92
|
+
const coverageExclude = [...CANONICAL_COVERAGE_EXCLUDE, ...(options.coverageExclude ?? [])];
|
|
93
|
+
const aliases = [...canonicalAliases(root), ...(options.aliases ?? [])];
|
|
94
|
+
return defineConfig({
|
|
95
|
+
resolve: { alias: aliases },
|
|
96
|
+
test: {
|
|
97
|
+
globals: true,
|
|
98
|
+
environment: 'node',
|
|
99
|
+
include: ['test/**/*.test.{ts,tsx}'],
|
|
100
|
+
// `test` is a workspace project and therefore has its own node_modules;
|
|
101
|
+
// without the re-list, the glob reaches into it.
|
|
102
|
+
exclude: ['**/node_modules/**', '**/dist/**', 'test/integration/**', SPA_FILES],
|
|
103
|
+
server: {
|
|
104
|
+
deps: {
|
|
105
|
+
// A `node_modules` dependency is externalized by default, so Node's
|
|
106
|
+
// own ESM loader resolves a `cloudflare:workers` specifier and rejects
|
|
107
|
+
// the scheme before `resolve.alias` is ever consulted. Inlining routes
|
|
108
|
+
// it back through Vite, where the alias applies.
|
|
109
|
+
inline: ['dofs'],
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
coverage: {
|
|
113
|
+
provider: 'v8',
|
|
114
|
+
// Vitest 4 removed `coverage.all`; an explicit `include` now reports
|
|
115
|
+
// every matching file whether or not a test imported it, which is the
|
|
116
|
+
// behaviour the source repos' `all: true` was there to get: a brand-new
|
|
117
|
+
// module with no test is counted as zero rather than being invisible,
|
|
118
|
+
// which is the exact regression this gate exists to catch.
|
|
119
|
+
reporter: ['text', 'lcov', 'html'],
|
|
120
|
+
reportsDirectory: options.reportsDirectory ?? './coverage',
|
|
121
|
+
include: [...coverageInclude],
|
|
122
|
+
exclude: coverageExclude,
|
|
123
|
+
...(options.thresholds !== undefined && { thresholds: { ...options.thresholds } }),
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=vitest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitest.js","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"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"}
|