@timekast/factory 1.26.0 → 1.27.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.
@@ -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 { DEV_NEXT_GATE_FILE, DEV_NEXT_SCRIPT_CMD, DEV_NEXT_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, VERIFY_QUICK_SCRIPT_CMD, VERIFY_QUICK_SCRIPT_NAME, VERIFY_SCRIPT_CMD, VERIFY_SCRIPT_FILE, VERIFY_SCRIPT_NAME, PORTS_E2E_COMMENT, PORTS_E2E_COMMENT_KEY, PORTS_E2E_KEY, PORTS_KEY, PREFLIGHT_SCRIPT_CMD, PREFLIGHT_SCRIPT_FILE, PREFLIGHT_SCRIPT_NAME, PROFILES, RETIRED_SCRIPT_CMDS, SHIPPED_E2E_PORT, UPDATE_SCRIPT_NAME, } from './constants.js';
11
+ import { DEV_NEXT_GATE_FILE, DEV_NEXT_SCRIPT_CMD, DEV_NEXT_SCRIPT_NAME, E2E_SETUP_SCRIPT_CMD, E2E_SETUP_SCRIPT_FILE, E2E_SETUP_SCRIPT_NAME, EVIDENCE_VISUAL_SCRIPT_CMD, EVIDENCE_VISUAL_SCRIPT_FILE, EVIDENCE_VISUAL_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, VERIFY_QUICK_SCRIPT_CMD, VERIFY_QUICK_SCRIPT_NAME, VERIFY_SCRIPT_CMD, VERIFY_SCRIPT_FILE, VERIFY_SCRIPT_NAME, PORTS_E2E_COMMENT, PORTS_E2E_COMMENT_KEY, PORTS_E2E_KEY, PORTS_KEY, PREFLIGHT_SCRIPT_CMD, PREFLIGHT_SCRIPT_FILE, PREFLIGHT_SCRIPT_NAME, PROFILES, RETIRED_SCRIPT_CMDS, SETUP_E2E_VARS_SCRIPT_CMD, SETUP_E2E_VARS_SCRIPT_FILE, SETUP_E2E_VARS_SCRIPT_NAME, SHIPPED_E2E_PORT, UPDATE_SCRIPT_NAME, VERCEL_BUILD_OBSOLETE_CMDS, VERCEL_BUILD_SCRIPT_CMD, VERCEL_BUILD_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
@@ -111,6 +111,18 @@ function ensureFactoryScripts(pkg, extraScripts = {}) {
111
111
  * backing file lands.
112
112
  *
113
113
  * - `preflight` — when its sweep script ships (full brain).
114
+ * - `setup:e2e:vars` — publishes the CI E2E auth env vars (Fase 1 of the
115
+ * `e2e-ci-auth-env` retrofit), when `scripts/tools/setup-e2e.ts` ships (full brain).
116
+ * - `evidence:visual` — the visual-evidence harness (`fx-visual-evidence`), gated on the
117
+ * harness itself (`scripts/tools/visual-evidence/`) rather than on the runner: it is what
118
+ * this alias needs, and a `core` derivative receives neither. Forwards to `test:e2e`, so the
119
+ * capture pass goes through the ephemeral Neon branch like every other phase.
120
+ * - `e2e:setup` — installs the Playwright browsers, gated on the e2e runner that
121
+ * refuses to start without them and names this alias in its message. A repo
122
+ * whose runner ships but whose `package.json` lacks the alias would be told to
123
+ * run a script it does not have — which is why it is maintained here rather
124
+ * than only in the Factory's own `package.json` (frozen at bootstrap in a
125
+ * derivative, so it is unreachable any other way).
114
126
  * - `generate:*` — one alias per reference generator whose `scripts/tools/
115
127
  * generate-*.mjs` ships (full brain). These arrive via the `scripts/**`
116
128
  * tracked set on `update`/`beta`, so an existing derivative self-heals the
@@ -140,6 +152,12 @@ export function kitLocalScripts(rootDir) {
140
152
  scripts[PREFLIGHT_SCRIPT_NAME] = PREFLIGHT_SCRIPT_CMD;
141
153
  if (has(INVITE_ADMIN_SCRIPT_FILE))
142
154
  scripts[INVITE_ADMIN_SCRIPT_NAME] = INVITE_ADMIN_SCRIPT_CMD;
155
+ if (has(SETUP_E2E_VARS_SCRIPT_FILE))
156
+ scripts[SETUP_E2E_VARS_SCRIPT_NAME] = SETUP_E2E_VARS_SCRIPT_CMD;
157
+ if (has(E2E_SETUP_SCRIPT_FILE))
158
+ scripts[E2E_SETUP_SCRIPT_NAME] = E2E_SETUP_SCRIPT_CMD;
159
+ if (has(EVIDENCE_VISUAL_SCRIPT_FILE))
160
+ scripts[EVIDENCE_VISUAL_SCRIPT_NAME] = EVIDENCE_VISUAL_SCRIPT_CMD;
143
161
  for (const gen of GENERATOR_SCRIPTS) {
144
162
  if (has(gen.file))
145
163
  scripts[gen.name] = gen.cmd;
@@ -355,6 +373,48 @@ export function removeRetiredScripts(pkgPath) {
355
373
  }
356
374
  return report;
357
375
  }
376
+ /**
377
+ * Reorder a derivative's `vercel-build` so the production build runs BEFORE the migration.
378
+ *
379
+ * WHY THIS IS ITS OWN FUNCTION AND NOT AN ENTRY IN `FACTORY_SCRIPTS`. Everything in that
380
+ * registry is INSERTED when missing, because those are the CLI's own `factory:*` aliases and a
381
+ * repo without them is a repo that is missing them. `vercel-build` is the opposite kind of key:
382
+ * it belongs to the boilerplate, arrives frozen with `src/` at `factory new` (BR-FACTORY-006),
383
+ * and in a `core`/non-Factory repo its absence is CORRECT — inserting it there would point a
384
+ * build command at a stack that repo does not have. So this heals in place and never inserts,
385
+ * the same shape as `emptyShippedE2EPort`.
386
+ *
387
+ * The damage it repairs is in `VERCEL_BUILD_SCRIPT_NAME`'s docstring: with the migration first,
388
+ * a build that fails AFTER it leaves the production database migrated while Vercel keeps
389
+ * serving the previous build — new schema, old code, and a deployment rollback cannot undo it.
390
+ *
391
+ * Surgical like the rest of this module: reads, mutates a single key, re-serializes with the
392
+ * original indentation + trailing newline, and writes ONLY on a real change — so a second run
393
+ * finds `already-correct` and writes nothing.
394
+ *
395
+ * @param pkgPath Absolute path to the derived project's `package.json`.
396
+ */
397
+ export function healVercelBuildOrder(pkgPath) {
398
+ const raw = readFileSync(pkgPath, 'utf8');
399
+ const indent = detectIndent(raw);
400
+ const pkg = parsePackageJson(raw);
401
+ const scripts = pkg.scripts;
402
+ if (typeof scripts !== 'object' || scripts === null || Array.isArray(scripts))
403
+ return 'absent';
404
+ const current = scripts[VERCEL_BUILD_SCRIPT_NAME];
405
+ if (current === undefined)
406
+ return 'absent';
407
+ if (current === VERCEL_BUILD_SCRIPT_CMD)
408
+ return 'already-correct';
409
+ // Exact match against what the kit actually shipped, byte for byte. A dev who appended a flag
410
+ // — or wrote the same two halves with different spacing — keeps their command through every
411
+ // future update: an unlisted variant erring toward "leave it alone" is the safe side here.
412
+ if (!VERCEL_BUILD_OBSOLETE_CMDS.includes(current))
413
+ return 'preserved';
414
+ scripts[VERCEL_BUILD_SCRIPT_NAME] = VERCEL_BUILD_SCRIPT_CMD;
415
+ writeFileSync(pkgPath, `${JSON.stringify(pkg, null, indent)}\n`, 'utf8');
416
+ return 'reordered';
417
+ }
358
418
  /**
359
419
  * The kit-owned maintenance of a derivative's `package.json`, in the ONE order that is correct.
360
420
  *
@@ -395,6 +455,10 @@ export function maintainKitScripts(pkgPath) {
395
455
  // `scripts/**` can derive one per checkout. Conditional: it only ever touches that exact
396
456
  // value, so a dev-chosen port survives untouched.
397
457
  const portAction = emptyShippedE2EPort(pkgPath);
458
+ // Reorder `vercel-build` so the build runs before the migration. Conditional in the same way:
459
+ // it only ever rewrites a value the kit itself shipped, and never creates the key — a repo
460
+ // without it is a repo that should not have one.
461
+ const vercelBuildAction = healVercelBuildOrder(pkgPath);
398
462
  const removedKeys = new Set(Object.entries(retiredReport)
399
463
  .filter(([, action]) => action === 'removed')
400
464
  .map(([name]) => name));
@@ -402,7 +466,7 @@ export function maintainKitScripts(pkgPath) {
402
466
  .filter(([name, r]) => (r.action === 'added' || r.action === 'healed') && !removedKeys.has(name))
403
467
  .map(([name]) => name);
404
468
  const retired = [...removedKeys].filter((name) => insert.perScript[name]?.action !== 'added');
405
- return { insert, retiredReport, portAction, inserted, retired };
469
+ return { insert, retiredReport, portAction, vercelBuildAction, inserted, retired };
406
470
  }
407
471
  /**
408
472
  * Surgically set the derived project's `package.json.agentKitVersion` to the
@@ -0,0 +1,27 @@
1
+ {
2
+ "prefixes": ["NEXT_PUBLIC_", "RATE_LIMIT_"],
3
+ "keys": [
4
+ "APP_URL",
5
+ "AUTH_DEBUG",
6
+ "AUTH_GITHUB_ID",
7
+ "AUTH_GOOGLE_ID",
8
+ "AUTH_TRUST_HOST",
9
+ "EMAIL_FROM",
10
+ "EMAIL_LOGO_URL",
11
+ "EMAIL_PROVIDER",
12
+ "EMAIL_SERVER_HOST",
13
+ "EMAIL_SERVER_PORT",
14
+ "EMAIL_SERVER_SECURE",
15
+ "EMAIL_SERVER_USER",
16
+ "MFA_ENABLED",
17
+ "MFA_REQUIRED_ALL",
18
+ "NEXTAUTH_URL",
19
+ "NODE_ENV",
20
+ "SENTRY_ORG",
21
+ "SENTRY_PROJECT",
22
+ "SUPPORT_EMAIL",
23
+ "VAPID_SUBJECT",
24
+ "WEBAUTHN_RP_ID",
25
+ "WEBAUTHN_RP_NAME"
26
+ ]
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timekast/factory",
3
- "version": "1.26.0",
3
+ "version": "1.27.0",
4
4
  "description": "Public, thin CLI to bootstrap and maintain TimeKast Factory derived projects.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",