@timekast/factory 1.20.0 → 1.22.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/dist/lib/atomic-swap.js +20 -7
- package/dist/lib/constants.js +62 -0
- package/dist/lib/neon-api.js +30 -1
- package/dist/lib/package-json.js +12 -1
- package/package.json +1 -1
package/dist/lib/atomic-swap.js
CHANGED
|
@@ -23,31 +23,44 @@
|
|
|
23
23
|
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmdirSync, rmSync, writeFileSync, } from 'node:fs';
|
|
24
24
|
import path from 'node:path';
|
|
25
25
|
import { CLIError } from './cli-error.js';
|
|
26
|
-
import { CLAUDE_MD_FILE, TIMEKAST_DIR } from './constants.js';
|
|
26
|
+
import { CLAUDE_MD_FILE, TIMEKAST_DIR, VITEST_SETUP_FILE } from './constants.js';
|
|
27
27
|
import { parseLockfile } from './lockfile.js';
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
29
|
+
* Directory roots a manifest path may live under (mirrors `profiles.json#track`).
|
|
30
30
|
*
|
|
31
31
|
* `.github/workflows/` is deliberately NARROWER than `.github/` — the tracked set is
|
|
32
32
|
* only the CI workflows, and widening the guard to the whole directory would let a
|
|
33
33
|
* tampered tarball rewrite issue templates, CODEOWNERS or funding config.
|
|
34
34
|
*/
|
|
35
35
|
const ALLOWED_ROOTS = ['.claude/', 'scripts/', '.husky/', '.github/workflows/'];
|
|
36
|
+
/**
|
|
37
|
+
* Repo-root FILES a manifest path may be (the non-directory half of `track`).
|
|
38
|
+
*
|
|
39
|
+
* Matched EXACTLY, never by prefix. Folding these into `ALLOWED_ROOTS` would be the
|
|
40
|
+
* short spelling, but `startsWith('vitest.setup.ts')` also accepts
|
|
41
|
+
* `vitest.setup.ts.evil` — the same widening `.github/workflows/` exists to avoid. A
|
|
42
|
+
* root file is a single path, so exact equality is what the track actually says.
|
|
43
|
+
*/
|
|
44
|
+
const ALLOWED_FILES = [CLAUDE_MD_FILE, VITEST_SETUP_FILE];
|
|
45
|
+
/** Human-readable rendering of the tracked scope, for the error message. */
|
|
46
|
+
const TRACKED_SCOPE_LABEL = [...ALLOWED_ROOTS, ...ALLOWED_FILES]
|
|
47
|
+
.map((entry) => `\`${entry}\``)
|
|
48
|
+
.join(', ');
|
|
36
49
|
/**
|
|
37
50
|
* Reject a manifest path that is unsafe to write: absolute, escaping via `..`, or
|
|
38
51
|
* outside the tracked scope (`.claude/**`, `scripts/**`, `CLAUDE.md`, `.husky/**`,
|
|
39
|
-
* `.github/workflows
|
|
40
|
-
* mispublished/tampered tarball from writing `src/` or escaping the repo
|
|
41
|
-
* Separator-agnostic (Windows-safe).
|
|
52
|
+
* `.github/workflows/**`, `vitest.setup.ts`). A correct release never produces these —
|
|
53
|
+
* this guards a mispublished/tampered tarball from writing `src/` or escaping the repo
|
|
54
|
+
* root. Separator-agnostic (Windows-safe).
|
|
42
55
|
*/
|
|
43
56
|
export function assertSafeTrackedPath(rel) {
|
|
44
57
|
const segments = rel.split(/[\\/]/);
|
|
45
58
|
if (rel.length === 0 || path.isAbsolute(rel) || segments.includes('..')) {
|
|
46
59
|
throw new CLIError(`El manifest declara un path inseguro \`${rel}\` (absoluto o con \`..\`); no se tocó nada.`);
|
|
47
60
|
}
|
|
48
|
-
const tracked = rel
|
|
61
|
+
const tracked = ALLOWED_FILES.includes(rel) || ALLOWED_ROOTS.some((root) => rel.startsWith(root));
|
|
49
62
|
if (!tracked) {
|
|
50
|
-
throw new CLIError(`El manifest declara \`${rel}\`, fuera del scope rastreado (
|
|
63
|
+
throw new CLIError(`El manifest declara \`${rel}\`, fuera del scope rastreado (${TRACKED_SCOPE_LABEL}). ` +
|
|
51
64
|
'El release está mal construido; no se tocó nada.');
|
|
52
65
|
}
|
|
53
66
|
}
|
package/dist/lib/constants.js
CHANGED
|
@@ -41,6 +41,17 @@ export const LOCKFILE_FILE = 'lockfile.json';
|
|
|
41
41
|
* exists on disk (path-match path). See `diffLockfiles` + the install commands.
|
|
42
42
|
*/
|
|
43
43
|
export const CLAUDE_MD_FILE = 'CLAUDE.md';
|
|
44
|
+
/**
|
|
45
|
+
* Repo-root Vitest setup file. A tracked manifest file (via `track`), so it flows through
|
|
46
|
+
* the normal diff/install engine and `factory update` keeps it current in every derivative.
|
|
47
|
+
*
|
|
48
|
+
* It is KIT-OWNED, unlike `CLAUDE.md` above: it carries the kit's own wiring (RTL matchers,
|
|
49
|
+
* `cleanup()`, and the `server-only`/`client-only` neutralization that keeps a marked module
|
|
50
|
+
* loadable outside a bundler). A derivative's project-specific mocks belong in the sibling
|
|
51
|
+
* `vitest.setup.project.ts`, which is dev-owned and ships in NEITHER profile — the same
|
|
52
|
+
* split as `.husky/pre-commit` → `.husky/pre-commit.project`.
|
|
53
|
+
*/
|
|
54
|
+
export const VITEST_SETUP_FILE = 'vitest.setup.ts';
|
|
44
55
|
/**
|
|
45
56
|
* Repo-root `.gitattributes`. NOT a tracked manifest file (absent from `track`),
|
|
46
57
|
* so it never flows through the diff/lockfile engine — a derived repo's own rules
|
|
@@ -158,6 +169,27 @@ export const GENERATOR_SCRIPTS = [
|
|
|
158
169
|
file: 'scripts/tools/generate-api.ts',
|
|
159
170
|
},
|
|
160
171
|
];
|
|
172
|
+
/**
|
|
173
|
+
* Kit-local E2E debugging aliases — raw Playwright, but through the wrapper that arms the
|
|
174
|
+
* `server-only` / `client-only` compile-guard stub (`scripts/tools/e2e-direct.ts`).
|
|
175
|
+
*
|
|
176
|
+
* `pnpm test:e2e` gets the stub from `e2e-runner.ts`; these two used to shell `playwright
|
|
177
|
+
* test` straight from `package.json` and therefore did not, so a spec that transitively
|
|
178
|
+
* reached a server-guarded module died at load with `No tests found` — but only here, never
|
|
179
|
+
* under the full runner.
|
|
180
|
+
*
|
|
181
|
+
* FILE-GATED, exactly like `GENERATOR_SCRIPTS`: the alias is only ensured when the wrapper
|
|
182
|
+
* it shells actually exists in the repo, so a `core`/non-Factory derivative never receives a
|
|
183
|
+
* script pointing at a file it lacks. Both gate on the SAME file (one wrapper, two entry
|
|
184
|
+
* points — `--ui` is just an argument forwarded to Playwright). Insert-if-missing; a dev's
|
|
185
|
+
* own value is never overwritten, and the previously-shipped raw commands self-heal through
|
|
186
|
+
* `OBSOLETE_SCRIPT_CMDS` below.
|
|
187
|
+
*/
|
|
188
|
+
export const E2E_DIRECT_SCRIPT_FILE = 'scripts/tools/e2e-direct.ts';
|
|
189
|
+
export const E2E_DIRECT_SCRIPT_NAME = 'test:e2e:direct';
|
|
190
|
+
export const E2E_DIRECT_SCRIPT_CMD = `tsx ${E2E_DIRECT_SCRIPT_FILE}`;
|
|
191
|
+
export const E2E_UI_SCRIPT_NAME = 'test:e2e:ui';
|
|
192
|
+
export const E2E_UI_SCRIPT_CMD = `tsx ${E2E_DIRECT_SCRIPT_FILE} --ui`;
|
|
161
193
|
/**
|
|
162
194
|
* Aggregate that runs every shipped reference generator in one shot (the
|
|
163
195
|
* one-command "regenerate everything" alias). Shells the files directly (not
|
|
@@ -168,6 +200,26 @@ export const GENERATOR_SCRIPTS = [
|
|
|
168
200
|
export const GENERATE_REFERENCE_NAME = 'generate:reference';
|
|
169
201
|
export const GENERATE_REFERENCE_CMD = 'tsx scripts/tools/generate-inventory.ts && tsx scripts/tools/generate-codebase.ts && tsx scripts/tools/generate-hooks.ts && tsx scripts/tools/generate-schema.ts && tsx scripts/tools/generate-api.ts';
|
|
170
202
|
export const GENERATE_REFERENCE_GATE_FILE = 'scripts/tools/generate-schema.ts';
|
|
203
|
+
/**
|
|
204
|
+
* The dev server's heap ceiling, carried inside the `dev:next` script the launcher shells
|
|
205
|
+
* (`scripts/tools/dev.mjs` → `pnpm dev:next`).
|
|
206
|
+
*
|
|
207
|
+
* WHY THE CLI OWNS IT. This value lives in `package.json`, which is frozen at bootstrap in a
|
|
208
|
+
* derivative (BR-FACTORY-006) and is NOT part of the tracked file set — so `factory update`
|
|
209
|
+
* swapping `.claude/` + `scripts/` can never reach it. The CLI is the only thing that can,
|
|
210
|
+
* exactly as it does for `agentKitVersion` and the retired e2e port. Without an entry here,
|
|
211
|
+
* every existing derivative would keep the old ceiling forever while new ones are born with
|
|
212
|
+
* the current one.
|
|
213
|
+
*
|
|
214
|
+
* FILE-GATED on the launcher, like `GENERATOR_SCRIPTS`: a `core`/non-Next derivative has no
|
|
215
|
+
* `dev.mjs` and never receives an alias for a dev server it does not run.
|
|
216
|
+
*/
|
|
217
|
+
export const DEV_NEXT_SCRIPT_NAME = 'dev:next';
|
|
218
|
+
export const DEV_NEXT_GATE_FILE = 'scripts/tools/dev.mjs';
|
|
219
|
+
export const DEV_NEXT_HEAP_MB = 4096;
|
|
220
|
+
export const DEV_NEXT_SCRIPT_CMD = `NODE_OPTIONS='--max-old-space-size=${DEV_NEXT_HEAP_MB}' next dev --turbopack`;
|
|
221
|
+
/** The ceiling(s) the kit shipped before — the ONLY values healing is allowed to overwrite. */
|
|
222
|
+
export const DEV_NEXT_OBSOLETE_HEAPS_MB = [2048];
|
|
171
223
|
/**
|
|
172
224
|
* The `ports` block of a derivative's `package.json`, and the one key inside it the CLI
|
|
173
225
|
* maintains.
|
|
@@ -206,4 +258,14 @@ export const OBSOLETE_SCRIPT_CMDS = {
|
|
|
206
258
|
[GENERATE_REFERENCE_NAME]: [
|
|
207
259
|
GENERATOR_SCRIPTS.map((g) => `node ${g.file.replace(/\.ts$/, '.mjs')}`).join(' && '),
|
|
208
260
|
],
|
|
261
|
+
// The raw Playwright invocations the kit shipped before `e2e-direct.ts` existed. They run,
|
|
262
|
+
// which is what makes them worth healing: they simply run WITHOUT the compile-guard stub,
|
|
263
|
+
// so the failure they produce is a spec reporting `No tests found` rather than an error a
|
|
264
|
+
// dev can act on. A derivative that pointed either alias somewhere of its own keeps it.
|
|
265
|
+
[E2E_DIRECT_SCRIPT_NAME]: ['playwright test'],
|
|
266
|
+
[E2E_UI_SCRIPT_NAME]: ['playwright test --ui'],
|
|
267
|
+
// Every heap ceiling the kit shipped for the dev server before the current one. Listed as
|
|
268
|
+
// whole COMMANDS (not just the number) so healing stays exact-match: a derivative that
|
|
269
|
+
// appended a flag of its own, or picked a ceiling the kit never shipped, is left alone.
|
|
270
|
+
[DEV_NEXT_SCRIPT_NAME]: DEV_NEXT_OBSOLETE_HEAPS_MB.map((mb) => `NODE_OPTIONS='--max-old-space-size=${mb}' next dev --turbopack`),
|
|
209
271
|
};
|
package/dist/lib/neon-api.js
CHANGED
|
@@ -28,6 +28,28 @@ const NEON_API_TIMEOUT_MS = 30_000;
|
|
|
28
28
|
export const NEON_MAIN_BRANCH = 'main';
|
|
29
29
|
/** Name of the isolated, data-clean second branch this step creates. */
|
|
30
30
|
export const NEON_DEVELOP_BRANCH = 'develop';
|
|
31
|
+
/**
|
|
32
|
+
* Autoscaling bounds every compute this CLI creates is born with.
|
|
33
|
+
*
|
|
34
|
+
* We declare them EXPLICITLY instead of inheriting Neon's account default, which
|
|
35
|
+
* is not stable over time: projects provisioned in earlier eras were born at a
|
|
36
|
+
* fixed `1-1 CU` (min == max), which bills a full CU for every hour the compute
|
|
37
|
+
* is merely awake — 4x the floor below — and today's default is `0.25-8`, whose
|
|
38
|
+
* ceiling is 32x the floor. Neither is a deliberate choice for a fresh derivative.
|
|
39
|
+
*
|
|
40
|
+
* The floor is what an idle-but-awake compute bills, so it is the single biggest
|
|
41
|
+
* lever on the fleet's Neon bill; 0.25 CU is Neon's minimum. The ceiling is pure
|
|
42
|
+
* headroom (it costs nothing until traffic actually needs it), so 2 CU gives a
|
|
43
|
+
* new project 8x of burst room while keeping a runaway query from silently
|
|
44
|
+
* scaling to 8 CU. A project that outgrows it raises the ceiling deliberately.
|
|
45
|
+
*/
|
|
46
|
+
export const NEON_AUTOSCALING_MIN_CU = 0.25;
|
|
47
|
+
export const NEON_AUTOSCALING_MAX_CU = 2;
|
|
48
|
+
/** The autoscaling pair as the Neon REST API spells it — shared by both writes. */
|
|
49
|
+
const NEON_AUTOSCALING_SETTINGS = {
|
|
50
|
+
autoscaling_limit_min_cu: NEON_AUTOSCALING_MIN_CU,
|
|
51
|
+
autoscaling_limit_max_cu: NEON_AUTOSCALING_MAX_CU,
|
|
52
|
+
};
|
|
31
53
|
/**
|
|
32
54
|
* Neon returns 423 (Locked) while a freshly-created project runs its post-create
|
|
33
55
|
* operations ("project already has running conflicting operations"). The develop
|
|
@@ -93,6 +115,10 @@ export async function createNeonProject(apiKey, projectName, orgId, deps = {}) {
|
|
|
93
115
|
...(orgId ? { org_id: orgId } : {}),
|
|
94
116
|
// Name the auto-created primary branch `main` so production reads cleanly.
|
|
95
117
|
branch: { name: NEON_MAIN_BRANCH },
|
|
118
|
+
// Bind the autoscaling bounds at the PROJECT level so the primary compute
|
|
119
|
+
// AND every branch created later (by us or by hand in the console) inherit
|
|
120
|
+
// them — otherwise a new branch silently re-adopts Neon's account default.
|
|
121
|
+
default_endpoint_settings: { ...NEON_AUTOSCALING_SETTINGS },
|
|
96
122
|
},
|
|
97
123
|
}),
|
|
98
124
|
signal: AbortSignal.timeout(NEON_API_TIMEOUT_MS),
|
|
@@ -129,7 +155,10 @@ export async function createDevelopBranch(apiKey, projectId, parentBranchId, dep
|
|
|
129
155
|
headers: neonHeaders(apiKey),
|
|
130
156
|
body: JSON.stringify({
|
|
131
157
|
branch: { name: NEON_DEVELOP_BRANCH, parent_id: parentBranchId },
|
|
132
|
-
|
|
158
|
+
// Stated explicitly rather than left to project inheritance: this branch is
|
|
159
|
+
// created in the same run as the project, and a bare `{type}` endpoint is
|
|
160
|
+
// exactly how the fleet ended up with computes nobody chose the size of.
|
|
161
|
+
endpoints: [{ type: 'read_write', ...NEON_AUTOSCALING_SETTINGS }],
|
|
133
162
|
}),
|
|
134
163
|
signal: AbortSignal.timeout(NEON_API_TIMEOUT_MS),
|
|
135
164
|
});
|
package/dist/lib/package-json.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { CLIError } from './cli-error.js';
|
|
11
|
-
import { FACTORY_SCRIPTS, GENERATE_REFERENCE_CMD, GENERATE_REFERENCE_GATE_FILE, GENERATE_REFERENCE_NAME, GENERATOR_SCRIPTS, INVITE_ADMIN_SCRIPT_CMD, INVITE_ADMIN_SCRIPT_FILE, INVITE_ADMIN_SCRIPT_NAME, OBSOLETE_SCRIPT_CMDS, PORTS_E2E_COMMENT, PORTS_E2E_COMMENT_KEY, PORTS_E2E_KEY, PORTS_KEY, PREFLIGHT_SCRIPT_CMD, PREFLIGHT_SCRIPT_FILE, PREFLIGHT_SCRIPT_NAME, PROFILES, SHIPPED_E2E_PORT, UPDATE_SCRIPT_NAME, } from './constants.js';
|
|
11
|
+
import { DEV_NEXT_GATE_FILE, DEV_NEXT_SCRIPT_CMD, DEV_NEXT_SCRIPT_NAME, E2E_DIRECT_SCRIPT_CMD, E2E_DIRECT_SCRIPT_FILE, E2E_DIRECT_SCRIPT_NAME, E2E_UI_SCRIPT_CMD, E2E_UI_SCRIPT_NAME, FACTORY_SCRIPTS, GENERATE_REFERENCE_CMD, GENERATE_REFERENCE_GATE_FILE, GENERATE_REFERENCE_NAME, GENERATOR_SCRIPTS, INVITE_ADMIN_SCRIPT_CMD, INVITE_ADMIN_SCRIPT_FILE, INVITE_ADMIN_SCRIPT_NAME, OBSOLETE_SCRIPT_CMDS, PORTS_E2E_COMMENT, PORTS_E2E_COMMENT_KEY, PORTS_E2E_KEY, PORTS_KEY, PREFLIGHT_SCRIPT_CMD, PREFLIGHT_SCRIPT_FILE, PREFLIGHT_SCRIPT_NAME, PROFILES, SHIPPED_E2E_PORT, UPDATE_SCRIPT_NAME, } from './constants.js';
|
|
12
12
|
/**
|
|
13
13
|
* Auto-detect the profile to install into an existing repo from its
|
|
14
14
|
* `package.json`: a `factoryVersion` field means the repo was born from the
|
|
@@ -110,6 +110,11 @@ function ensureFactoryScripts(pkg, extraScripts = {}) {
|
|
|
110
110
|
* tracked set on `update`/`beta`, so an existing derivative self-heals the
|
|
111
111
|
* aliases without the dev hand-editing `package.json`.
|
|
112
112
|
* - `generate:reference` — the aggregate that runs the whole set in one command.
|
|
113
|
+
* - `test:e2e:direct` / `test:e2e:ui` — the two raw-Playwright debugging entry
|
|
114
|
+
* points, routed through the wrapper that arms the compile-guard stub. Both
|
|
115
|
+
* gate on that one wrapper file.
|
|
116
|
+
* - `dev:next` — the dev server the launcher shells, whose heap ceiling the kit
|
|
117
|
+
* owns. Gated on the launcher (`dev.mjs`), which is what invokes it.
|
|
113
118
|
*
|
|
114
119
|
* Insert-if-missing for all: a dev's own value for any of these is never
|
|
115
120
|
* overwritten (see `ensureFactoryScripts`).
|
|
@@ -127,6 +132,12 @@ export function kitLocalScripts(rootDir) {
|
|
|
127
132
|
}
|
|
128
133
|
if (has(GENERATE_REFERENCE_GATE_FILE))
|
|
129
134
|
scripts[GENERATE_REFERENCE_NAME] = GENERATE_REFERENCE_CMD;
|
|
135
|
+
if (has(E2E_DIRECT_SCRIPT_FILE)) {
|
|
136
|
+
scripts[E2E_DIRECT_SCRIPT_NAME] = E2E_DIRECT_SCRIPT_CMD;
|
|
137
|
+
scripts[E2E_UI_SCRIPT_NAME] = E2E_UI_SCRIPT_CMD;
|
|
138
|
+
}
|
|
139
|
+
if (has(DEV_NEXT_GATE_FILE))
|
|
140
|
+
scripts[DEV_NEXT_SCRIPT_NAME] = DEV_NEXT_SCRIPT_CMD;
|
|
130
141
|
return scripts;
|
|
131
142
|
}
|
|
132
143
|
/**
|