@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/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
|
-
#
|
|
1
|
+
# @rexezuge/tooling
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
```
|
package/dist/eslint.d.ts
ADDED
|
@@ -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"}
|