@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.
Files changed (160) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +297 -2
  3. package/dist/eslint.d.ts +120 -0
  4. package/dist/eslint.d.ts.map +1 -0
  5. package/dist/eslint.js +551 -0
  6. package/dist/eslint.js.map +1 -0
  7. package/dist/functions/pages-proxy.d.ts +113 -0
  8. package/dist/functions/pages-proxy.d.ts.map +1 -0
  9. package/dist/functions/pages-proxy.js +131 -0
  10. package/dist/functions/pages-proxy.js.map +1 -0
  11. package/dist/index.d.ts +31 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +28 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/scripts/backup/d1-target.d.ts +35 -0
  16. package/dist/scripts/backup/d1-target.d.ts.map +1 -0
  17. package/dist/scripts/backup/d1-target.js +31 -0
  18. package/dist/scripts/backup/d1-target.js.map +1 -0
  19. package/dist/scripts/backup/destination-config.d.ts +61 -0
  20. package/dist/scripts/backup/destination-config.d.ts.map +1 -0
  21. package/dist/scripts/backup/destination-config.js +57 -0
  22. package/dist/scripts/backup/destination-config.js.map +1 -0
  23. package/dist/scripts/backup/encrypt-backup.d.ts +35 -0
  24. package/dist/scripts/backup/encrypt-backup.d.ts.map +1 -0
  25. package/dist/scripts/backup/encrypt-backup.js +98 -0
  26. package/dist/scripts/backup/encrypt-backup.js.map +1 -0
  27. package/dist/scripts/backup/naming.d.ts +44 -0
  28. package/dist/scripts/backup/naming.d.ts.map +1 -0
  29. package/dist/scripts/backup/naming.js +58 -0
  30. package/dist/scripts/backup/naming.js.map +1 -0
  31. package/dist/scripts/check-god-files.d.ts +164 -0
  32. package/dist/scripts/check-god-files.d.ts.map +1 -0
  33. package/dist/scripts/check-god-files.js +271 -0
  34. package/dist/scripts/check-god-files.js.map +1 -0
  35. package/dist/scripts/ensure-spa-shell-stub.d.ts +3 -0
  36. package/dist/scripts/ensure-spa-shell-stub.d.ts.map +1 -0
  37. package/dist/scripts/ensure-spa-shell-stub.js +30 -0
  38. package/dist/scripts/ensure-spa-shell-stub.js.map +1 -0
  39. package/dist/scripts/init-secrets.d.ts +63 -0
  40. package/dist/scripts/init-secrets.d.ts.map +1 -0
  41. package/dist/scripts/init-secrets.js +240 -0
  42. package/dist/scripts/init-secrets.js.map +1 -0
  43. package/dist/scripts/lib/cli-args.d.ts +78 -0
  44. package/dist/scripts/lib/cli-args.d.ts.map +1 -0
  45. package/dist/scripts/lib/cli-args.js +116 -0
  46. package/dist/scripts/lib/cli-args.js.map +1 -0
  47. package/dist/scripts/lib/github-actions.d.ts +26 -0
  48. package/dist/scripts/lib/github-actions.d.ts.map +1 -0
  49. package/dist/scripts/lib/github-actions.js +38 -0
  50. package/dist/scripts/lib/github-actions.js.map +1 -0
  51. package/dist/scripts/lib/wrangler-table.d.ts +46 -0
  52. package/dist/scripts/lib/wrangler-table.d.ts.map +1 -0
  53. package/dist/scripts/lib/wrangler-table.js +99 -0
  54. package/dist/scripts/lib/wrangler-table.js.map +1 -0
  55. package/dist/scripts/migrations-lock.d.ts +3 -0
  56. package/dist/scripts/migrations-lock.d.ts.map +1 -0
  57. package/dist/scripts/migrations-lock.js +46 -0
  58. package/dist/scripts/migrations-lock.js.map +1 -0
  59. package/dist/scripts/prepare-wrangler-config.d.ts +3 -0
  60. package/dist/scripts/prepare-wrangler-config.d.ts.map +1 -0
  61. package/dist/scripts/prepare-wrangler-config.js +50 -0
  62. package/dist/scripts/prepare-wrangler-config.js.map +1 -0
  63. package/dist/scripts/spa-shell.d.ts +41 -0
  64. package/dist/scripts/spa-shell.d.ts.map +1 -0
  65. package/dist/scripts/spa-shell.js +155 -0
  66. package/dist/scripts/spa-shell.js.map +1 -0
  67. package/dist/scripts/validate-locales.d.ts +26 -0
  68. package/dist/scripts/validate-locales.d.ts.map +1 -0
  69. package/dist/scripts/validate-locales.js +350 -0
  70. package/dist/scripts/validate-locales.js.map +1 -0
  71. package/dist/scripts/verify-migrations.d.ts +62 -0
  72. package/dist/scripts/verify-migrations.d.ts.map +1 -0
  73. package/dist/scripts/verify-migrations.js +302 -0
  74. package/dist/scripts/verify-migrations.js.map +1 -0
  75. package/dist/scripts/verify-spa-shell.d.ts +3 -0
  76. package/dist/scripts/verify-spa-shell.d.ts.map +1 -0
  77. package/dist/scripts/verify-spa-shell.js +53 -0
  78. package/dist/scripts/verify-spa-shell.js.map +1 -0
  79. package/dist/scripts/wrangler-config/cli.d.ts +22 -0
  80. package/dist/scripts/wrangler-config/cli.d.ts.map +1 -0
  81. package/dist/scripts/wrangler-config/cli.js +51 -0
  82. package/dist/scripts/wrangler-config/cli.js.map +1 -0
  83. package/dist/scripts/wrangler-config/patches.d.ts +51 -0
  84. package/dist/scripts/wrangler-config/patches.d.ts.map +1 -0
  85. package/dist/scripts/wrangler-config/patches.js +140 -0
  86. package/dist/scripts/wrangler-config/patches.js.map +1 -0
  87. package/dist/scripts/wrangler-config/resources.d.ts +70 -0
  88. package/dist/scripts/wrangler-config/resources.d.ts.map +1 -0
  89. package/dist/scripts/wrangler-config/resources.js +290 -0
  90. package/dist/scripts/wrangler-config/resources.js.map +1 -0
  91. package/dist/scripts/wrangler-config/types.d.ts +103 -0
  92. package/dist/scripts/wrangler-config/types.d.ts.map +1 -0
  93. package/dist/scripts/wrangler-config/types.js +49 -0
  94. package/dist/scripts/wrangler-config/types.js.map +1 -0
  95. package/dist/test/integration-migrations.d.ts +167 -0
  96. package/dist/test/integration-migrations.d.ts.map +1 -0
  97. package/dist/test/integration-migrations.js +171 -0
  98. package/dist/test/integration-migrations.js.map +1 -0
  99. package/dist/test/mocks/cloudflare-workers.d.ts +106 -0
  100. package/dist/test/mocks/cloudflare-workers.d.ts.map +1 -0
  101. package/dist/test/mocks/cloudflare-workers.js +90 -0
  102. package/dist/test/mocks/cloudflare-workers.js.map +1 -0
  103. package/dist/vite.d.ts +117 -0
  104. package/dist/vite.d.ts.map +1 -0
  105. package/dist/vite.js +125 -0
  106. package/dist/vite.js.map +1 -0
  107. package/dist/vitest-web.d.ts +73 -0
  108. package/dist/vitest-web.d.ts.map +1 -0
  109. package/dist/vitest-web.js +72 -0
  110. package/dist/vitest-web.js.map +1 -0
  111. package/dist/vitest.d.ts +92 -0
  112. package/dist/vitest.d.ts.map +1 -0
  113. package/dist/vitest.js +128 -0
  114. package/dist/vitest.js.map +1 -0
  115. package/package.json +58 -3
  116. package/src/eslint.test.ts +175 -0
  117. package/src/eslint.ts +640 -0
  118. package/src/functions/pages-proxy.test.ts +72 -0
  119. package/src/functions/pages-proxy.ts +187 -0
  120. package/src/github/actions/retry-step/action.yml +39 -0
  121. package/src/github/actions/setup-env/action.yml +20 -0
  122. package/src/github/dependabot.yml +30 -0
  123. package/src/github/workflows/backup-main.yml +46 -0
  124. package/src/github/workflows/continuous-deployment.yml +188 -0
  125. package/src/github/workflows/continuous-integration.yml +259 -0
  126. package/src/github/workflows/scheduled-version-update.yml +38 -0
  127. package/src/github/workflows/upstream-sync.yml +56 -0
  128. package/src/index.ts +42 -0
  129. package/src/scripts/backup/backup-rules.test.ts +105 -0
  130. package/src/scripts/backup/d1-target.ts +54 -0
  131. package/src/scripts/backup/destination-config.ts +92 -0
  132. package/src/scripts/backup/encrypt-backup.ts +107 -0
  133. package/src/scripts/backup/naming.ts +62 -0
  134. package/src/scripts/check-god-files.test.ts +131 -0
  135. package/src/scripts/check-god-files.ts +327 -0
  136. package/src/scripts/ensure-spa-shell-stub.ts +34 -0
  137. package/src/scripts/init-secrets.ts +265 -0
  138. package/src/scripts/lib/cli-args.ts +154 -0
  139. package/src/scripts/lib/github-actions.ts +41 -0
  140. package/src/scripts/lib/wrangler-table.ts +105 -0
  141. package/src/scripts/migrations-lock.ts +52 -0
  142. package/src/scripts/prepare-wrangler-config.ts +51 -0
  143. package/src/scripts/spa-shell.test.ts +91 -0
  144. package/src/scripts/spa-shell.ts +179 -0
  145. package/src/scripts/validate-locales.test.ts +89 -0
  146. package/src/scripts/validate-locales.ts +380 -0
  147. package/src/scripts/verify-migrations.test.ts +71 -0
  148. package/src/scripts/verify-migrations.ts +364 -0
  149. package/src/scripts/verify-spa-shell.ts +56 -0
  150. package/src/scripts/wrangler-config/cli.ts +51 -0
  151. package/src/scripts/wrangler-config/patches.ts +157 -0
  152. package/src/scripts/wrangler-config/resources.ts +330 -0
  153. package/src/scripts/wrangler-config/types.ts +113 -0
  154. package/src/test/integration-migrations.test.ts +169 -0
  155. package/src/test/integration-migrations.ts +267 -0
  156. package/src/test/mocks/cloudflare-workers.ts +115 -0
  157. package/src/vite.test.ts +83 -0
  158. package/src/vite.ts +202 -0
  159. package/src/vitest-web.ts +109 -0
  160. package/src/vitest.ts +185 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rexezuge
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,298 @@
1
- # Temporary Holding Version
1
+ # @rexezuge/tooling
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Config factories, repo scripts, test helpers, and CI templates for the
4
+ Rexezuge-CloudflareWorkers family.
5
+
6
+ **Zero internal dependencies.** The config factories, the Pages proxy and the test
7
+ helpers import nothing from another `@rexezuge/*` package. The single exception is
8
+ `scripts/backup/encrypt-backup.ts`, which uses `@rexezuge/d1`'s AES-GCM so a backup
9
+ and the workers share one definition of "what an encryption key looks like"; that
10
+ dependency is declared on this package and is only ever resolved in a consumer
11
+ repo, where the kit is installed alongside `@rexezuge/d1`.
12
+
13
+ Everything here is a convergence of the same file copied across ten monorepos. Each
14
+ export carries a header comment naming the repos it came from and the decision the
15
+ convergence made — those headers are the documentation.
16
+
17
+ ---
18
+
19
+ ## 1. Config factories
20
+
21
+ Four factories, four lines of consumer wiring each.
22
+
23
+ ### ESLint
24
+
25
+ ```js
26
+ // eslint.config.mjs — the whole file
27
+ import { defineRepoLintConfig } from '@rexezuge/tooling/eslint';
28
+
29
+ export default defineRepoLintConfig({ scope: '@my-app' });
30
+ ```
31
+
32
+ Converged from Durable-DAV's 436-line `eslint.config.mjs` (the layered
33
+ `no-restricted-imports` boundary rules), Edge-Sonic's 730-line config (which lints
34
+ `test/**`, turns `unicorn/prefer-ternary` off with its reasons, and covers the
35
+ `import()` hole in `no-restricted-imports` with a matching `no-restricted-syntax`
36
+ rule), Mail-Meow's hard-error formatting gate, and AWS's `allowTypeImports` DAO
37
+ boundary.
38
+
39
+ The layer table is parameterized by `scope`, so `@my-app/api` is banned from
40
+ `apps/api` exactly as `@durable-dav/api` was. Canonical decisions:
41
+
42
+ - `prettier/prettier` is an **error**, not a warning. `pnpm run lint` is
43
+ `eslint --fix --quiet` in most repos, and `--quiet` discards warnings — so a
44
+ `prettier/prettier` warning is a silent no-op. Edge-Sonic had 242 files in that
45
+ state: the gate had never run.
46
+ - `test/**` is **linted**, not ignored, with a narrow override block for Vitest
47
+ idioms. The source repos ignored it, and ~200 KB of test code had accumulated
48
+ violations nothing reported.
49
+ - `apps/web` gets `globals.node` **plus** `globals.browser`, and is banned from
50
+ every `@scope/*` package — the SPA's own copy of a shared helper is the
51
+ convention, and a lint gate that does not say so is a convention.
52
+
53
+ A consumer that needs `eslint-plugin-react-hooks` appends it, because the kit stays
54
+ zero-dependency and the plugin belongs to the app:
55
+
56
+ ```js
57
+ import reactHooks from 'eslint-plugin-react-hooks';
58
+
59
+ export default [
60
+ ...defineRepoLintConfig({ scope: '@my-app' }),
61
+ {
62
+ files: ['apps/web/**/*.{ts,tsx}'],
63
+ plugins: { 'react-hooks': reactHooks },
64
+ rules: reactHooks.configs['recommended-latest'].rules,
65
+ },
66
+ ];
67
+ ```
68
+
69
+ ### Vitest — the worker and packages half
70
+
71
+ ```ts
72
+ // vitest.config.mts
73
+ import { defineUnitVitestConfig } from '@rexezuge/tooling/vitest';
74
+
75
+ export default defineUnitVitestConfig({
76
+ coverageInclude: ['apps/api/src/**/*.ts', 'apps/background/src/**/*.ts', 'packages/**/src/**/*.ts'],
77
+ thresholds: { statements: 60, branches: 55, functions: 65, lines: 60 },
78
+ });
79
+ ```
80
+
81
+ v8 coverage with the converged include/exclude patterns (scripts, tests, barrels,
82
+ generated files, type-only modules), `cloudflare:workers` / `cloudflare:sockets`
83
+ aliased to the bundled Node stand-ins, and `test/integration/**` plus every
84
+ `web-*` suite excluded so this config and the next one partition the suite rather
85
+ than each running all of it. Vitest 4 removed `coverage.all`; an explicit
86
+ `include` now reports files no test imported, which is the behaviour it was there
87
+ to get.
88
+
89
+ **Thresholds are a consumer argument, not a kit default.** A floor is a per-repo
90
+ ratchet measured against that repo's suite; the kit cannot know a number, and
91
+ guessing one is how floors get lowered.
92
+
93
+ ### Vitest — the SPA half
94
+
95
+ ```ts
96
+ // vitest.web.config.mts
97
+ import { defineWebVitestConfig } from '@rexezuge/tooling/vitest-web';
98
+
99
+ export default defineWebVitestConfig({
100
+ plugins: [react()],
101
+ thresholds: { statements: 55, branches: 52, functions: 54, lines: 56 },
102
+ });
103
+ ```
104
+
105
+ Converged from Durable-DAV-Router's and Mail-Otter's `vitest.web.config.mts`:
106
+ jsdom for every file, `pool: 'threads'` bounded rather than a fork per file (a
107
+ jsdom document per fork is what exhausts memory and dies with a bare "Worker
108
+ exited unexpectedly"), and both suite-naming conventions collected
109
+ (`test/**/web-*.test.{ts,tsx}` and `apps/web/**/*.test.{ts,tsx}`).
110
+
111
+ ### Vite — the SPA build
112
+
113
+ ```ts
114
+ // apps/web/vite.config.ts
115
+ import react from '@vitejs/plugin-react';
116
+ import { defineWebViteConfig, spaShellEmbedPlugin } from '@rexezuge/tooling/vite';
117
+
118
+ export default defineWebViteConfig({
119
+ plugins: [react(), tailwindcss()],
120
+ proxy: {
121
+ '/user': { target: 'http://localhost:8787', changeOrigin: true },
122
+ '/rest': { target: 'http://localhost:8787', changeOrigin: true },
123
+ },
124
+ // The ONE repo-specific piece, passed in rather than hardcoded: the worker and
125
+ // the generated-module path differ per repo.
126
+ spaShell: { path: 'apps/api/src/generated/spa-shell.ts' },
127
+ });
128
+ ```
129
+
130
+ `spaShellEmbedPlugin(target)` is the converged body of all eight copies of the
131
+ `spa-shell-embed` plugin, including Edge-Sonic's fix of skipping a build that
132
+ emitted no `dist/index.html` rather than throwing. Paths resolve from the directory
133
+ the build runs in — **not** from this module, which would point inside
134
+ `node_modules`.
135
+
136
+ ---
137
+
138
+ ## 2. Pages proxy
139
+
140
+ ```ts
141
+ // functions/[[path]].ts
142
+ import { createPagesProxy } from '@rexezuge/tooling/functions/pages-proxy';
143
+
144
+ export const onRequest = createPagesProxy();
145
+ ```
146
+
147
+ The converged `functions/[[path]].ts` shim, verified against Durable-DAV,
148
+ Mail-Meow and Edge-Git. It strips hop-by-hop and client-forwarding headers, sets
149
+ `X-Forwarded-Host/Proto/Uri` from values it reads off the request rather than the
150
+ client, replaces `X-Forwarded-For` from `CF-Connecting-IP` (never conditionally —
151
+ that is the defect Mail-Meow recorded fixing), forwards the body as a stream with
152
+ `duplex: 'half'` for anything but GET/HEAD, and hands the request to
153
+ `env.API_WORKER`.
154
+
155
+ Structural `Fetcher` / `PagesFunction` types: no `@cloudflare/workers-types`
156
+ import, so the module typechecks without a generated `worker-configuration.d.ts`.
157
+
158
+ `createPagesProxy({ forwardClientIp: false })` is the Edge-Sonic variant, for a
159
+ worker that refuses `X-Forwarded-For` outright.
160
+
161
+ ---
162
+
163
+ ## 3. Test helpers
164
+
165
+ ### `test/mocks/cloudflare-workers` — the `cloudflare:workers` stand-in
166
+
167
+ ```ts
168
+ // test/mocks/cloudflare-workers.ts (copy this file verbatim)
169
+ // vitest.config.mts — defineUnitVitestConfig already aliases it
170
+ { find: 'cloudflare:workers', replacement: 'test/mocks/cloudflare-workers.ts' }
171
+ ```
172
+
173
+ Converged from the eight copies: `DurableObject`, `RpcTarget` (present because
174
+ `dofs`' `Fs` extends it, and its absence is a module-load failure rather than a
175
+ test failure), and `WorkflowEntrypoint`. The ambient `DurableObjectState` /
176
+ `ExecutionContext` shapes are declared locally, so it typechecks inside the kit and
177
+ still typechecks against a consumer's generated types.
178
+
179
+ ### `test/integration-migrations` — applying the migration directory
180
+
181
+ ```ts
182
+ import { createMigrationHelper } from '@rexezuge/tooling/test/integration-migrations';
183
+
184
+ const migrations = createMigrationHelper({ migrationsDir: new URL('../../migrations', import.meta.url).pathname });
185
+
186
+ beforeAll(async () => {
187
+ await migrations.applyMigrations(env.DB); // every file, in wrangler's order
188
+ });
189
+
190
+ it('upgrades a pre-0004 database', async () => {
191
+ await migrations.applyMigrations(env.DB, { to: '0003_href_prefix_mode.sql' }); // seed the old shape
192
+ await migrations.applyMigrations(env.DB, { from: '0004_user_identity.sql' }); // then the one under test
193
+ });
194
+ ```
195
+
196
+ A factory, because every source repo hardcodes where its migrations live. Generic
197
+ over a structural `D1DatabaseLike` — `prepare`, and `batch` when the binding has it
198
+ — so a fake D1, a real D1 under workerd, and Miniflare's D1 all satisfy it. The
199
+ splitter is trigger-aware, and each file is one `batch()` whose failure names the
200
+ file and the statement.
201
+
202
+ ---
203
+
204
+ ## 4. Scripts
205
+
206
+ Every script is a standalone `.ts` entrypoint. Copy the `scripts/` tree into the
207
+ repo (that is the intended use — they resolve paths relative to the repository they
208
+ run in), or run it straight out of the installed package:
209
+
210
+ ```bash
211
+ pnpm exec tsx node_modules/@rexezuge/tooling/src/scripts/<name>.ts [args]
212
+ ```
213
+
214
+ | Script | What it does | Usage |
215
+ | --- | --- | --- |
216
+ | `check-god-files.ts` | 300-line warn / 400-line critical guard, warn-only by default, with an allowlist | `--root <dir>` `--allowlist <file>` `--soft 300` `--hard 400` `--fail-on-hard` |
217
+ | `ensure-spa-shell-stub.ts` | writes the empty `spa-shell.ts` stub when absent; never overwrites | `[repo-root]` |
218
+ | `verify-spa-shell.ts` | rejects a missing, stubbed, or half-refreshed SPA shell | `[repo-root]` |
219
+ | `validate-locales.ts` | two-way key parity, empty values, `{{placeholder}}` parity, base-locale bundle | `[locales-dir]` |
220
+ | `verify-migrations.ts` | migration names, numbering, digests against the lock; read-only | `[migrations-dir]` |
221
+ | `migrations-lock.ts` | `--write` for the above: add-only lock update | `[migrations-dir]` |
222
+ | `prepare-wrangler-config.ts` | materializes `wrangler.jsonc` and provisions the resources it names | env-driven |
223
+ | `init-secrets.ts` | creates the declared Secrets Store entries, generate-if-absent | env-driven |
224
+ | `backup/encrypt-backup.ts` | compresses and AES-GCM-encrypts an exported dump | `BACKUP_ENCRYPTION_KEY` |
225
+
226
+ The rules each script enforces live in a module beside it, so the gates are unit
227
+ tested here: `god-files` rules in `check-god-files.ts`, `spa-shell` rules in
228
+ `spa-shell.ts`, locale rules in `validate-locales.ts`, migration rules in
229
+ `verify-migrations.ts`.
230
+
231
+ ### The three that are gates, not chores
232
+
233
+ **`check-god-files.ts`** keeps every source repo's thresholds but makes the verdict
234
+ warn-only until the repo opts in with `--fail-on-hard`. The reason is in the
235
+ module: a ceiling set above the largest file in the tree measures nothing, and
236
+ Durable-DAV's own notes record ten files just over 300 with a hard limit that had
237
+ never fired. `scripts/god-files.allowlist.json` (a JSON array of repo-relative
238
+ paths, or `dir/` prefixes) records the known offenders so the report names only
239
+ what is new.
240
+
241
+ **`verify-migrations.ts` / `migrations-lock.ts`** exist because D1 records which
242
+ migrations it applied but not what they contained, so editing an applied migration
243
+ is invisible to every other tool. The lock is forward-only, `--write` is add-only
244
+ (an `edited` finding survives a write), and a squashed baseline exempts exactly the
245
+ set a squash rewrites or absorbs.
246
+
247
+ **`verify-spa-shell.ts`** is the only thing standing between a fresh clone and a
248
+ deploy that serves a blank page: both `apps/web/dist/` and the generated
249
+ `spa-shell.ts` are gitignored, and `postinstall` writes a deliberately empty stub.
250
+ Known limit, stated in the module: a *stale but self-consistent* pair passes.
251
+
252
+ ### The backup helpers
253
+
254
+ `backup/{d1-target,destination-config,naming,retention,encrypt-backup}.ts` are the
255
+ generic half of Edge-Sonic's backup suite — the rules, not the upload jobs.
256
+ `d1-target.ts` refuses to export a database that was auto-provisioned moments ago,
257
+ `destination-config.ts` fails closed on an unencrypted destination, `naming.ts`
258
+ pins the artifact format (the workflow's upload glob and this prefix are one fact in
259
+ two places), `retention.ts` rejects a bad window rather than defaulting one, and
260
+ `encrypt-backup.ts` does the crypto in-process with the kit's own AES-GCM.
261
+
262
+ **Left local, deliberately:** `s3-prune.ts`, `upload-s3.ts`, `upload-webdav.ts`,
263
+ `webdav-target.ts` (destination-specific: S3 credentials, rclone, and a WebDAV
264
+ client are per-repo configuration rather than shared rules), `compare-reference.ts`
265
+ and `change-email.ts` (business logic and personal-data operations, respectively —
266
+ neither is tooling).
267
+
268
+ ---
269
+
270
+ ## 5. GitHub templates
271
+
272
+ `src/github/` holds the converged family workflows and composite actions:
273
+
274
+ ```
275
+ src/github/
276
+ workflows/continuous-integration.yml # the superset of nine repos' CI jobs
277
+ workflows/continuous-deployment.yml # workflow_run-gated deploy, worker + pages
278
+ workflows/scheduled-version-update.yml # .github/.version bump
279
+ workflows/backup-main.yml # secret-gated mirrors to Azure DevOps / GitLab
280
+ actions/setup-env/action.yml # pnpm + node store cache
281
+ actions/retry-step/action.yml # retry a bash command, annotate the attempts
282
+ dependabot.yml # weekly, with the @rexezuge/* group
283
+ ```
284
+
285
+ Copy the tree to the repo root. Placeholders are marked `REPLACE` and listed at the
286
+ top of each file; the one that must change for the workflows to run at all is the
287
+ `pnpm --filter @<scope>/web build` invocation in `continuous-integration.yml` and
288
+ `continuous-deployment.yml`.
289
+
290
+ ---
291
+
292
+ ## 6. Verifying this package
293
+
294
+ ```bash
295
+ pnpm --filter @rexezuge/tooling exec tsc -p tsconfig.json
296
+ pnpm --filter @rexezuge/tooling typecheck
297
+ pnpm --filter @rexezuge/tooling test
298
+ ```
@@ -0,0 +1,120 @@
1
+ /**
2
+ * The converged ESLint flat config for a monorepo in the Rexezuge family.
3
+ *
4
+ * Provenance: the base is Durable-DAV's `eslint.config.mjs` (380+ lines, the most
5
+ * elaborate of the nine repos, with the layered `no-restricted-imports` boundary
6
+ * rules). Merged in from the other three stacks that share the same plugin set:
7
+ *
8
+ * - **Edge-Sonic** — `test/**` is *not* ignored (it is linted like everything else,
9
+ * with a narrow override block for Vitest idioms), and
10
+ * `unicorn/prefer-ternary` is off. Edge-Sonic's per-rule "deliberately does not
11
+ * follow" block is carried over verbatim in spirit: each entry is off because
12
+ * complying would make the code worse for what the project is, and each is
13
+ * listed rather than dropped so the decision stays reviewable.
14
+ * - **Mail-Meow** — `prettier/prettier` is an **error**, not a warning.
15
+ * - **Mail-Meow** — `prettier/prettier` is an **error**, not a warning.
16
+ * - **AWS-AccessBridge** — the `apps/api` DAO/service boundary uses
17
+ * `allowTypeImports` where the source repos do. It is also the source for the
18
+ * anchored skip patterns and the symlink-safe walk that came out of its own backlog.
19
+ * - **Edge-Sonic** — `test/**` is *not* ignored (it is linted like everything else,
20
+ * with a narrow override block for Vitest idioms), `unicorn/prefer-ternary` is off
21
+ * with a documented reason, and the `import()` hole in `no-restricted-imports` is
22
+ * covered by a matching `no-restricted-syntax` rule. Edge-Sonic's per-rule
23
+ * "deliberately does not follow" block is carried over verbatim in spirit: each
24
+ * entry is off because complying would make the code worse for what the project
25
+ * is, and each is listed rather than dropped so the decision stays reviewable.
26
+ *
27
+ * Canonical decisions, and why each one:
28
+ *
29
+ * 1. **`prettier/prettier: 'error'`.** Three of the four source repos had it at
30
+ * `warn`, which under `eslint --quiet` is unenforceable — a gate nobody can see
31
+ * is a gate that has never run. Formatting drift is therefore a hard failure.
32
+ * 2. **The layered boundary rules are parameterized by `scope`.** Every source
33
+ * repo wrote the same seven blocks with a different npm scope hardcoded; the
34
+ * factory takes `scope` (`'@myapp'`) and generates the `@myapp/*` patterns.
35
+ * 3. **`test/**` is linted, not ignored.** Durable-DAV/AWS/Mail-Meow ignored it;
36
+ * Edge-Sonic does not, and records why: ~200 KB of test code had accumulated
37
+ * violations that nothing reported.
38
+ * 4. **`apps/web` is a layer in its own right**, banned from every `@scope/*`
39
+ * package. Edge-Sonic is the only source with that block; the others had none,
40
+ * which is how their layer table's "may import: the browser" was a convention
41
+ * rather than a gate. A backend package imported by the SPA compiles and then
42
+ * fails at bundle time, with the error pointing at the bundler.
43
+ * 5. **jsdom + node globals for web files.** The sources ran `globals.node` only;
44
+ * `apps/web` needs browser globals as well, and the vitest config for the SPA
45
+ * runs under jsdom, so the lint environment matches the test environment.
46
+ * 6. **`eslint-plugin-react-hooks` is NOT imported here.** The kit stays
47
+ * zero-dependency, and the plugin belongs to the app that has JSX. A consumer
48
+ * appends it — the README shows the four lines.
49
+ *
50
+ * The consumer's repo-level `eslint.config.mjs` becomes four lines, and a repo that
51
+ * needs a boundary the canonical table does not name passes `extraLayers` — see
52
+ * {@link RepoLintConfigOptions}.
53
+ */
54
+ import type { FlatConfig } from 'typescript-eslint';
55
+ /**
56
+ * What `defineRepoLintConfig` accepts.
57
+ */
58
+ export interface RepoLintConfigOptions {
59
+ /**
60
+ * The consuming repo's npm scope, e.g. `'@myapp'`. Every `no-restricted-imports`
61
+ * pattern in the boundary rules is derived from it, so the same factory serves
62
+ * all nine repos.
63
+ */
64
+ readonly scope: string;
65
+ /**
66
+ * The workspace directories the layered boundary rules are scoped to, as repo-
67
+ * relative globs. Defaults to the canonical layout the nine repos share.
68
+ */
69
+ readonly projectDirs?: readonly string[];
70
+ /**
71
+ * Extra ignore globs, merged with the canonical set.
72
+ */
73
+ readonly extraIgnores?: readonly string[];
74
+ /**
75
+ * Where `projectService` resolves the tsconfig from. Defaults to the directory
76
+ * ESLint is invoked in, which is the repo root in every CI job.
77
+ */
78
+ readonly tsconfigRootDir?: string;
79
+ /**
80
+ * Extra boundary layers, appended to the canonical table.
81
+ *
82
+ * The escape valve for a repo whose layout has a boundary the canonical table
83
+ * does not name — AWS-AccessBridge's `apps/api/src/endpoints/**` rule ("route
84
+ * classes must not import DAOs directly") and its `aws4fetch` ban are the shape.
85
+ * Same {@link LayerBan} form, so `extraLayers` flows through the
86
+ * `no-restricted-imports` and the `no-restricted-syntax` guard alike.
87
+ */
88
+ readonly extraLayers?: readonly LayerBan[];
89
+ }
90
+ /**
91
+ * One layer's ban list, as the repo-relative package suffixes it may not import.
92
+ *
93
+ * Converged from the four source stacks. `backend-data` and `backend-services`
94
+ * appear in all four; `webdav`/`dav-store` are Durable-DAV's, `provider-clients`
95
+ * is Mail-Meow's and AWS's. A layer only bans what exists in its repo, so the
96
+ * union is safe: a repo without `provider-clients` simply never imports it.
97
+ */
98
+ export interface LayerBan {
99
+ /** Directory the rule is scoped to. */
100
+ readonly dir: string;
101
+ /** Package suffixes (relative to `scope`) that may not be imported from here. */
102
+ readonly ban: readonly string[];
103
+ /** Message shown when the rule fires. */
104
+ readonly message: string;
105
+ /** Type-only imports are allowed, for the DAO boundary in `apps/api`. */
106
+ readonly allowTypeImports?: boolean;
107
+ }
108
+ /**
109
+ * Build the repo's flat config.
110
+ *
111
+ * Consumed from the repo root as three lines:
112
+ *
113
+ * ```js
114
+ * // eslint.config.mjs
115
+ * import { defineRepoLintConfig } from '@rexezuge/tooling/eslint';
116
+ * export default defineRepoLintConfig({ scope: '@myapp' });
117
+ * ```
118
+ */
119
+ export declare function defineRepoLintConfig(options: RepoLintConfigOptions): FlatConfig.ConfigArray;
120
+ //# sourceMappingURL=eslint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eslint.d.ts","sourceRoot":"","sources":["../src/eslint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AASH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC;;OAEG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;;;;OAQG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;CAC5C;AAsCD;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ;IACvB,uCAAuC;IACvC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,yCAAyC;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yEAAyE;IACzE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACrC;AAyHD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,UAAU,CAAC,WAAW,CAmW3F"}