@timekast/factory 1.20.0 → 1.21.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.
@@ -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
- * Roots a manifest path may live under (mirrors `profiles.json#track`).
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/**`). A correct release never produces these — this guards a
40
- * mispublished/tampered tarball from writing `src/` or escaping the repo root.
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 === CLAUDE_MD_FILE || ALLOWED_ROOTS.some((root) => rel.startsWith(root));
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 (\`.claude/\`, \`scripts/\`, \`CLAUDE.md\`, \`.husky/\`, \`.github/workflows/\`). ` +
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
  }
@@ -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
@@ -206,4 +238,10 @@ export const OBSOLETE_SCRIPT_CMDS = {
206
238
  [GENERATE_REFERENCE_NAME]: [
207
239
  GENERATOR_SCRIPTS.map((g) => `node ${g.file.replace(/\.ts$/, '.mjs')}`).join(' && '),
208
240
  ],
241
+ // The raw Playwright invocations the kit shipped before `e2e-direct.ts` existed. They run,
242
+ // which is what makes them worth healing: they simply run WITHOUT the compile-guard stub,
243
+ // so the failure they produce is a spec reporting `No tests found` rather than an error a
244
+ // dev can act on. A derivative that pointed either alias somewhere of its own keeps it.
245
+ [E2E_DIRECT_SCRIPT_NAME]: ['playwright test'],
246
+ [E2E_UI_SCRIPT_NAME]: ['playwright test --ui'],
209
247
  };
@@ -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 { 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,9 @@ 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.
113
116
  *
114
117
  * Insert-if-missing for all: a dev's own value for any of these is never
115
118
  * overwritten (see `ensureFactoryScripts`).
@@ -127,6 +130,10 @@ export function kitLocalScripts(rootDir) {
127
130
  }
128
131
  if (has(GENERATE_REFERENCE_GATE_FILE))
129
132
  scripts[GENERATE_REFERENCE_NAME] = GENERATE_REFERENCE_CMD;
133
+ if (has(E2E_DIRECT_SCRIPT_FILE)) {
134
+ scripts[E2E_DIRECT_SCRIPT_NAME] = E2E_DIRECT_SCRIPT_CMD;
135
+ scripts[E2E_UI_SCRIPT_NAME] = E2E_UI_SCRIPT_CMD;
136
+ }
130
137
  return scripts;
131
138
  }
132
139
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timekast/factory",
3
- "version": "1.20.0",
3
+ "version": "1.21.0",
4
4
  "description": "Public, thin CLI to bootstrap and maintain TimeKast Factory derived projects.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",