@ultimat3/cli 11.0.0 → 11.2.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.
@@ -0,0 +1,62 @@
1
+ // Whether a file is a pure re-export manifest — every statement in it an `import` or an `export`
2
+ // that declares nothing. The line ceiling is a rule about REVIEWABLE LOGIC, and such a file has
3
+ // none: its length is a function of the package's API size, so the ceiling measures the wrong
4
+ // thing there. One added statement of logic disqualifies it and re-arms the ceiling on the spot.
5
+
6
+ import { CLOSERS, maskLiterals, OPENERS } from './ts-scan';
7
+
8
+ /** Every statement a manifest may hold begins with one of these two words. */
9
+ const IMPORT_OR_EXPORT = /^(?:import|export)\b/;
10
+
11
+ /**
12
+ * An `export` that DECLARES rather than re-exports. `export const LIMIT = 1` is a value with an
13
+ * initialiser, `export function` is logic outright, and both are exactly what the ceiling is for —
14
+ * so a file holding one is an ordinary source file that happens to start with re-exports.
15
+ */
16
+ const DECLARES =
17
+ /^export\s+(?:default|declare|abstract|async|const|let|var|function|class|enum|namespace|module|interface)\b/;
18
+
19
+ /**
20
+ * `export type { Ctx } from './ctx'` is a re-export; `export type Ctx = { … }` is a type alias, and
21
+ * an alias is a declaration a reviewer reads. The brace is the whole distinction.
22
+ */
23
+ const TYPE_ALIAS = /^export\s+type\s+[A-Za-z_$]/;
24
+
25
+ /**
26
+ * Top-level statements, split at the `;` that ends each one at bracket depth 0, over MASKED source
27
+ * — comments and string contents blanked — so a `;` inside a specifier or a comment is not read as
28
+ * a boundary. `undefined` when the file ends in something this cannot read as a statement: a scan
29
+ * that guesses would exempt a file on the strength of not understanding it.
30
+ */
31
+ function topLevelStatements(masked: string): readonly string[] | undefined {
32
+ const statements: string[] = [];
33
+ let depth = 0;
34
+ let start = 0;
35
+ for (let i = 0; i < masked.length; i += 1) {
36
+ const ch = masked[i] as string;
37
+ if (OPENERS.has(ch)) depth += 1;
38
+ // Clamped, because an unbalanced closer would otherwise put every later `;` at a negative
39
+ // depth and the whole file would read as one unterminated statement.
40
+ else if (CLOSERS.has(ch)) depth = Math.max(0, depth - 1);
41
+ else if (ch === ';' && depth === 0) {
42
+ statements.push(masked.slice(start, i));
43
+ start = i + 1;
44
+ }
45
+ }
46
+ return masked.slice(start).trim() === '' ? statements : undefined;
47
+ }
48
+
49
+ /**
50
+ * True when every statement in `source` is an import or a declaration-free export. A file with no
51
+ * statement at all is NOT a manifest: an exemption has to be earned by what a file holds, and
52
+ * "this scan found nothing" is the one answer that must never grant one.
53
+ */
54
+ export function isReExportManifest(source: string): boolean {
55
+ const statements = topLevelStatements(maskLiterals(source));
56
+ if (statements === undefined || statements.length === 0) return false;
57
+ return statements.every((statement) => {
58
+ const text = statement.trim();
59
+ if (text === '') return true;
60
+ return IMPORT_OR_EXPORT.test(text) && !DECLARES.test(text) && !TYPE_ALIAS.test(text);
61
+ });
62
+ }
@@ -0,0 +1,90 @@
1
+ // The server a shot is taken against, and the hosts the page may reach while it is: the two
2
+ // facts a ROUTE capture and a COMPONENT capture share, and the only ones. Its own file so
3
+ // `island-shot.ts` can have them without importing the command that photographs a route, which
4
+ // would be a cycle between two files that otherwise have nothing to say to each other.
5
+
6
+ // why: no Bun native joins a path; `.x/shot` is a path both capture paths write under.
7
+ import { join } from 'node:path';
8
+ import { startDev } from './cmd-dev';
9
+ import { clearLock, isProcessAlive, lockPath, parseLock, preflight, writeLock } from './dev-lock';
10
+ import { DEV_BINDING } from './dev-roles';
11
+ import { resolveServices } from './dev-services';
12
+
13
+ export const SHOT_DIR = join('.x', 'shot');
14
+
15
+ /**
16
+ * How `devServerFor` starts a scratch server. A parameter with a default rather than a direct
17
+ * call, for the reason every `Runner` in this package is one: the failure path below — a boot that
18
+ * throws, and the lock it has to hand back — is otherwise only reachable by breaking a real app.
19
+ */
20
+ export type BootDevServer = (input: {
21
+ readonly root: string;
22
+ readonly port: number;
23
+ readonly env: Readonly<Record<string, string | undefined>>;
24
+ }) => Promise<{ readonly url: string; stop(): Promise<void> }>;
25
+
26
+ export interface ShotServer {
27
+ readonly url: string;
28
+ /** Which server the picture is of. Reported, because the two have different failure modes. */
29
+ readonly origin: 'booted' | 'reused';
30
+ stop(): Promise<void>;
31
+ }
32
+
33
+ /**
34
+ * One rule, two branches: photograph the `x dev` this checkout already has, or boot a scratch one.
35
+ * Reusing is not a convenience — embedded Postgres is a single-writer directory, so a second boot
36
+ * on one checkout is `X_DEV_ALREADY_RUNNING` and the picture would never be taken at all.
37
+ */
38
+ export async function devServerFor(
39
+ root: string,
40
+ env: Readonly<Record<string, string | undefined>>,
41
+ port: number,
42
+ boot: BootDevServer = (input) => startDev(input),
43
+ ): Promise<ShotServer> {
44
+ const services = resolveServices(root, env);
45
+ const file = Bun.file(lockPath(services.stateDir));
46
+ if (await file.exists()) {
47
+ const lock = parseLock(await file.text());
48
+ if (lock !== null && isProcessAlive(lock.pid)) {
49
+ return { url: lock.url, origin: 'reused', stop: () => Promise.resolve() };
50
+ }
51
+ }
52
+ const { release } = await preflight({
53
+ stateDir: services.stateDir,
54
+ port,
55
+ hostname: DEV_BINDING.hostname,
56
+ embeddedDb: services.db.mode === 'embedded',
57
+ });
58
+ // The directory is CLAIMED from here down — `preflight` returns holding it, never having merely
59
+ // looked — so a boot that throws has to give it back. `cmd-dev.ts` states the same rule at the
60
+ // same seam. Without it one failed `x shot` refused every later `x dev` and `x shot` on this
61
+ // checkout, naming a pid that had already exited. The original error is re-thrown untouched: a
62
+ // teardown must never replace the failure it is cleaning up after.
63
+ const dev = await boot({ root, port, env }).catch((error: unknown) => {
64
+ release();
65
+ throw error;
66
+ });
67
+ await writeLock(services.stateDir, {
68
+ pid: process.pid,
69
+ port,
70
+ url: dev.url,
71
+ startedAt: new Date().toISOString(),
72
+ });
73
+ return {
74
+ url: dev.url,
75
+ origin: 'booted',
76
+ async stop() {
77
+ clearLock(services.stateDir);
78
+ await dev.stop();
79
+ },
80
+ };
81
+ }
82
+
83
+ /** `--allow-hosts a.com,b.com` on top of the app's own host. Empty means the app's host alone. */
84
+ export const allowHostsFrom = (url: string, extra: string | undefined): readonly string[] => {
85
+ const named = (extra ?? '')
86
+ .split(',')
87
+ .map((host) => host.trim())
88
+ .filter((host) => host.length > 0);
89
+ return [new URL(url).hostname, ...named];
90
+ };
@@ -1,7 +1,8 @@
1
- // When a shot may be TAKEN: the two rules that decide whether the page has finished hydrating,
2
- // separated from both the command that drives a browser and the verdict that judges what came
3
- // back. Plain values and an injected sleep, so the whole loop is proved with neither.
1
+ // When a shot may be TAKEN: the rules that decide whether the page has finished, separated from
2
+ // both the command that drives a browser and the verdict that judges what came back. Plain values
3
+ // and an injected sleep, so every loop here is proved with neither.
4
4
 
5
+ import type { IslandReadiness } from './island-verdict';
5
6
  import type { IslandCount } from './shot-verdict';
6
7
 
7
8
  /**
@@ -55,3 +56,32 @@ export async function settleIslands(
55
56
  }
56
57
  return answer;
57
58
  }
59
+
60
+ /**
61
+ * Read the harness's readiness until the page goes QUIET or the window runs out.
62
+ *
63
+ * Quiet and not idle, which is the whole rule: the page's own watcher sets `ready` after N
64
+ * consecutive frames in which no request started and none settled, so a state whose fixture is
65
+ * deliberately `pending` still reaches it. Waiting for nothing in flight would hang on exactly the
66
+ * state an author declared on purpose, and a fixed sleep would photograph whatever a slow machine
67
+ * had painted by then.
68
+ *
69
+ * A `null` answer never overwrites a real one, for `settleIslands`' reason: `null` is "the page
70
+ * answered no probe", and a probe that fails once must not turn a ready page into an unready one.
71
+ */
72
+ export async function settleReadiness(
73
+ probe: () => Promise<IslandReadiness | null>,
74
+ options: SettleOptions,
75
+ ): Promise<IslandReadiness | null> {
76
+ const sleep = options.sleep ?? ((ms: number): Promise<void> => Bun.sleep(ms));
77
+ let answer = await probe();
78
+ let waited = 0;
79
+ while (answer?.ready !== true && waited < options.windowMs) {
80
+ // At least 1ms, or a `pollMs` of zero is a loop with no exit while the window stands.
81
+ const step = Math.max(1, Math.min(options.pollMs, options.windowMs - waited));
82
+ await sleep(step);
83
+ waited += step;
84
+ answer = (await probe()) ?? answer;
85
+ }
86
+ return answer;
87
+ }
package/src/ts-scan.ts CHANGED
@@ -19,6 +19,17 @@ export interface CodeSite extends SourceSite {
19
19
  readonly code: string;
20
20
  }
21
21
 
22
+ export interface UnresolvedCodeSite extends SourceSite {
23
+ /** The identifier exactly as written at the `code:` position. */
24
+ readonly name: string;
25
+ }
26
+
27
+ /** One file's codes, and the names at a `code:` position this scan could not turn into one. */
28
+ export interface CodeScan {
29
+ readonly sites: readonly CodeSite[];
30
+ readonly unresolved: readonly UnresolvedCodeSite[];
31
+ }
32
+
22
33
  // `ReadonlySet`, so a consumer cannot mutate what every scan in this package reads.
23
34
  export const QUOTES: ReadonlySet<string> = new Set(["'", '"', '`']);
24
35
  export const OPENERS: ReadonlySet<string> = new Set(['(', '[', '{']);
@@ -150,10 +161,18 @@ export function lineIndex(text: string): (index: number) => number {
150
161
  }
151
162
 
152
163
  /**
153
- * Every string literal in the value expression starting at `from`, at the expression's own bracket
154
- * depth. Spanning the expression is what makes `cond ? 'a' : 'b'` and `table[k] ?? 'c'` checkable
155
- * instead of silently skipped; the depth rule is what keeps `command.join(' ')`'s separator and
156
- * `table['key']`'s key out — an argument is not a fix.
164
+ * Every string literal the value expression starting at `from` can EVALUATE TO, at the
165
+ * expression's own bracket depth. Spanning the expression is what makes `cond ? 'a' : 'b'` and
166
+ * `table[k] ?? 'c'` checkable instead of silently skipped; the depth rule is what keeps
167
+ * `command.join(' ')`'s separator and `table['key']`'s key out — an argument is not a fix.
168
+ *
169
+ * A ternary's CONDITION is dropped, which is the difference between reading the expression and
170
+ * reading every literal in it. `fix: input.slug === '' ? 'x g …' : 'x g …'` published the empty
171
+ * string as a fix line — `X_ERROR_FIX_INVALID`, "the fix line is empty", against source whose two
172
+ * real fixes are both correct — and `input.key === 'timeZone' ? … : …` published `timeZone`, a
173
+ * string then judged for banned phrases and cited paths that is not a fix at all. Costly enough
174
+ * that `@ultimat3/testing`'s island-state errors carry two classes under one code rather than one
175
+ * class with a ternary in it.
157
176
  */
158
177
  export function valueLiterals(
159
178
  masked: string,
@@ -162,21 +181,42 @@ export function valueLiterals(
162
181
  lineAt: (index: number) => number,
163
182
  ): readonly FixSite[] {
164
183
  const found: { value: string; index: number }[] = [];
184
+ // The literals of the segment being read. A segment ended by `?` is a condition and is dropped
185
+ // whole; one ended by `:` or by the end of the expression is a value the fix can evaluate to.
186
+ let segment: { value: string; index: number }[] = [];
187
+ const keep = (): void => {
188
+ found.push(...segment);
189
+ segment = [];
190
+ };
165
191
  let depth = 0;
192
+ /** Open `?`s still waiting for their `:`, so a `:` outside a ternary stays an ordinary char. */
193
+ let conditionals = 0;
166
194
  for (let i = from; i < masked.length; i += 1) {
167
195
  const ch = masked[i] as string;
168
196
  if (QUOTES.has(ch)) {
169
197
  const end = endOfLiteral(masked, i);
170
198
  // A quote that never closes is one character of code, not an empty literal to report.
171
199
  if (end === i + 1) continue;
172
- if (depth === 0) found.push({ value: source.slice(i + 1, end - 1), index: i });
200
+ if (depth === 0) segment.push({ value: source.slice(i + 1, end - 1), index: i });
173
201
  i = end - 1;
174
202
  } else if (OPENERS.has(ch)) depth += 1;
175
203
  else if (CLOSERS.has(ch)) {
176
204
  if (depth === 0) break;
177
205
  depth -= 1;
206
+ } else if (depth === 0 && ch === '?') {
207
+ // `??` and `?.` are operators and end no segment: `input?.fix ?? 'x help'` evaluates to the
208
+ // literal, and dropping what came before it would drop the only answer the expression has.
209
+ if (masked[i + 1] === '?') i += 1;
210
+ else if (masked[i + 1] !== '.') {
211
+ segment = [];
212
+ conditionals += 1;
213
+ }
214
+ } else if (depth === 0 && ch === ':' && conditionals > 0) {
215
+ keep();
216
+ conditionals -= 1;
178
217
  } else if (depth === 0 && (ch === ',' || ch === ';')) break;
179
218
  }
219
+ keep();
180
220
  return found.map((literal) => ({
181
221
  at: '',
182
222
  line: lineAt(literal.index),
@@ -202,27 +242,111 @@ const CODE_AT_KEY = /\bcode\s*[:=]\s*(['"`])(X_[A-Z0-9_]+)\1/g;
202
242
  const CODE_LITERAL = /(['"`])(X_[A-Z0-9_]+)\1/g;
203
243
  const CODE_KEY = /^[\t ]*(X_[A-Z0-9_]+)\s*:/gm;
204
244
 
245
+ /**
246
+ * A `code` KEY, and never a member assignment: `found.code = SOMETHING` projects somebody else's
247
+ * code and declares none. The literal form above keeps its looser `\b` deliberately — a scanner
248
+ * that stopped collecting a code it has collected for four majors would shrink the manifest.
249
+ */
250
+ const CODE_KEY_POSITION = /(?<![.\w$])code\s*[:=]\s*/g;
251
+
252
+ /** Cheap enough to run on every file, so the masking pass below is paid only where it can pay. */
253
+ const HAS_CODE_IDENTIFIER = /(?<![.\w$])code\s*[:=]\s*[A-Za-z_$]/;
254
+
255
+ /** Sticky: the value expression is read at an exact offset, never out of a slice that may cut. */
256
+ const VALUE_IDENTIFIER = /([A-Za-z_$][\w$]*)\s*([.([]?)/y;
257
+
258
+ /**
259
+ * A module-scope `const NAME = 'X_…'`, and the names of every other module-scope const. Anchored
260
+ * at column 0, which is what makes it module scope without a parser: a `const` inside a function
261
+ * can be shadowed by another in a sibling scope, and a resolver that picked one of them would be
262
+ * guessing. The second set is the answer "that name IS declared here, and it is not a code" —
263
+ * `const STATUS_NOT_FOUND = 404` in `@ultimat3/realtime`'s NATS fake is the live instance, and a
264
+ * rule that reported it would be a rule the reader has to argue with.
265
+ */
266
+ const CODE_CONST =
267
+ /^(?:export\s+)?const\s+([A-Za-z_$][\w$]*)\s*(?::[^=\n]*)?=\s*(['"`])(X_[A-Z0-9_]+)\2/gm;
268
+ const MODULE_CONST = /^(?:export\s+)?const\s+([A-Za-z_$][\w$]*)\s*[:=]/gm;
269
+
270
+ /** House shape for a constant. A lowercase name at a `code:` is a type annotation or a re-raise. */
271
+ const CODE_CONSTANT_NAME = /^[A-Z][A-Z0-9_]+$/;
272
+
273
+ interface ModuleConstants {
274
+ /** Name → the code it holds. */
275
+ readonly codes: ReadonlyMap<string, string>;
276
+ /** Every module-scope const name, code-valued or not. */
277
+ readonly names: ReadonlySet<string>;
278
+ }
279
+
280
+ function moduleConstants(text: string): ModuleConstants {
281
+ const codes = new Map<string, string>();
282
+ const names = new Set<string>();
283
+ for (const match of text.matchAll(MODULE_CONST)) names.add(match[1] as string);
284
+ for (const match of text.matchAll(CODE_CONST)) codes.set(match[1] as string, match[3] as string);
285
+ return { codes, names };
286
+ }
287
+
288
+ /**
289
+ * The bare identifier a key's value is, or `undefined` when the value is anything else. A member
290
+ * read, an index and a call are all refused: `SEO_ERROR_CODES.metaMissing` is how two packages
291
+ * raise every code they own, the registry branch below already collects those literals, and
292
+ * judging the read would report eighteen working sites as broken.
293
+ */
294
+ function valueIdentifier(masked: string, from: number): string | undefined {
295
+ VALUE_IDENTIFIER.lastIndex = from;
296
+ const match = VALUE_IDENTIFIER.exec(masked);
297
+ return match === null || match[2] !== '' ? undefined : match[1];
298
+ }
299
+
205
300
  /**
206
301
  * Codes this file declares: every `code:` / `code =` throw site, plus — in a package's own code
207
302
  * registry — every entry of its code list or title table, whichever shape it uses. A registry is
208
303
  * the only place a bare `X_*` literal is a declaration; anywhere else it is a reference (an env
209
304
  * var named `X_BUILD_ID`, an HTTP status map keyed by code) and collecting it would invent a code.
305
+ *
306
+ * A `code:` written as an IDENTIFIER is resolved against the module-scope consts of the same file,
307
+ * and reported as `unresolved` when nothing there gives it a value (#277). Both halves matter and
308
+ * neither is optional: `const STALE = 'X_DOC_PACKAGE_GRAPH_STALE'` is what a DRY author writes, and
309
+ * a scan that skipped it silently left the code out of the manifest, out of `wiki/Error-Codes.md`'s
310
+ * demanded rows, out of `bun run gate-codes` and out of `x errors explain` — permissive, and quiet.
311
+ * The identifier half reads the MASKED text: `packages/cli/src/templates/` emits app source by the
312
+ * dozen inside template literals, and a `code: STALE` in one of those is text, not a declaration.
210
313
  */
211
- export function scanCodes(source: string, at: string): readonly CodeSite[] {
314
+ export function scanCodeDeclarations(source: string, at: string): CodeScan {
212
315
  const text = stripComments(source);
213
316
  const lineAt = lineIndex(text);
214
317
  const sites = new Map<string, CodeSite>();
318
+ const unresolved: UnresolvedCodeSite[] = [];
215
319
  const add = (code: string, index: number): void => {
216
320
  if (!sites.has(code)) sites.set(code, { at, line: lineAt(index), code });
217
321
  };
218
322
  for (const match of text.matchAll(CODE_AT_KEY)) add(match[2] as string, match.index);
323
+ if (HAS_CODE_IDENTIFIER.test(text)) {
324
+ const masked = maskLiterals(source);
325
+ const constants = moduleConstants(text);
326
+ for (const key of masked.matchAll(CODE_KEY_POSITION)) {
327
+ const name = valueIdentifier(masked, key.index + key[0].length);
328
+ if (name === undefined) continue;
329
+ const code = constants.codes.get(name);
330
+ if (code !== undefined) add(code, key.index);
331
+ else if (!constants.names.has(name) && CODE_CONSTANT_NAME.test(name)) {
332
+ unresolved.push({ at, line: lineAt(key.index), name });
333
+ }
334
+ }
335
+ }
219
336
  if (isCodeRegistry(text)) {
220
337
  for (const match of text.matchAll(CODE_LITERAL)) add(match[2] as string, match.index);
221
338
  for (const match of text.matchAll(CODE_KEY)) add(match[1] as string, match.index);
222
339
  }
223
- return [...sites.values()];
340
+ return { sites: [...sites.values()], unresolved };
224
341
  }
225
342
 
343
+ /**
344
+ * The codes alone, for every caller that has no report to attach a finding to. One scanner, one
345
+ * answer: the manifest, the docs check, `bun run gate-codes` and `x errors explain` all read this.
346
+ */
347
+ export const scanCodes = (source: string, at: string): readonly CodeSite[] =>
348
+ scanCodeDeclarations(source, at).sites;
349
+
226
350
  export interface CodeFixSite extends CodeSite {
227
351
  /**
228
352
  * The fix literal exactly as written, `${…}` included. Absent when the throw site builds its
@@ -250,6 +374,8 @@ function soleLiteral(
250
374
  return found.length === 1 ? found[0] : undefined;
251
375
  }
252
376
 
377
+ const CODE_NAME = /^X_[A-Z0-9_]+$/;
378
+
253
379
  /**
254
380
  * Every `X_*` code paired with the `fix:` written beside it — in the SAME object literal, which is
255
381
  * the whole rule. `new UltimateError({ code, cause, fix })` is the one shape this framework raises
@@ -263,6 +389,15 @@ function soleLiteral(
263
389
  export function scanCodeFixSites(source: string, at: string): readonly CodeFixSite[] {
264
390
  const masked = maskLiterals(source);
265
391
  const lineAt = lineIndex(masked);
392
+ // Lazily, because most files hold no `code:` at all and stripping is a whole extra pass over
393
+ // the text. Same resolver `scanCodeDeclarations` reads, so `x errors explain` can never see a
394
+ // smaller set of throw sites than the manifest does.
395
+ let constants: ModuleConstants | undefined;
396
+ const constantCode = (name: string | undefined): string | undefined => {
397
+ if (name === undefined) return undefined;
398
+ constants ??= moduleConstants(stripComments(source));
399
+ return constants.codes.get(name);
400
+ };
266
401
  const keys = new Map<number, { readonly kind: 'code' | 'fix'; readonly from: number }>();
267
402
  for (const key of masked.matchAll(CODE_OR_FIX_KEY)) {
268
403
  keys.set(key.index, {
@@ -293,11 +428,16 @@ export function scanCodeFixSites(source: string, at: string): readonly CodeFixSi
293
428
  const scope = stack.at(-1);
294
429
  if (key === undefined || scope === undefined) continue;
295
430
  const literal = soleLiteral(masked, source, key.from, lineAt);
296
- if (literal === undefined) continue;
297
431
  if (key.kind === 'fix') {
298
- if (!fixes.has(scope)) fixes.set(scope, literal.fix);
299
- } else if (/^X_[A-Z0-9_]+$/.test(literal.fix) && !codes.has(scope)) {
300
- codes.set(scope, { at, line: literal.line, code: literal.fix });
432
+ if (literal !== undefined && !fixes.has(scope)) fixes.set(scope, literal.fix);
433
+ continue;
434
+ }
435
+ // A fix has no second reading, so it stays literal-only; a code has exactly one, which is the
436
+ // module-scope const its own file declares it in.
437
+ const code =
438
+ literal === undefined ? constantCode(valueIdentifier(masked, key.from)) : literal.fix;
439
+ if (code !== undefined && CODE_NAME.test(code) && !codes.has(scope)) {
440
+ codes.set(scope, { at, line: literal?.line ?? lineAt(key.from), code });
301
441
  }
302
442
  }
303
443
  return [...codes].map(([scope, site]) => {
@@ -24,7 +24,7 @@ import { checkBudgets, readBuildStats } from './budgets';
24
24
  import { checkDestructiveMigrations } from './db-destructive';
25
25
  import { checkDocumentStyles, documentSurfaces } from './document-styles';
26
26
  import { checkSourceDrift } from './drift';
27
- import { checkErrorFixReport } from './error-contract';
27
+ import { checkErrorCodeResolution, checkErrorFixReport } from './error-contract';
28
28
  import { guardFindings } from './guards';
29
29
  import { catalogFindings } from './i18n-registration';
30
30
  import { liveRouteFindings } from './live-routes';
@@ -125,7 +125,15 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
125
125
  // it does not have. "checked 412, could not read 27" is what a reader can act on.
126
126
  async run(ctx) {
127
127
  const report = await checkErrorFixReport(ctx.root);
128
- const findings = [...report.findings, ...(await hostFindings(ctx, 'errors'))];
128
+ // The third rule on this step, and the one that is about the code rather than the fix: a
129
+ // `code:` reached through a name nothing in its own file declares is a code no reader of the
130
+ // set can see — not the manifest, not the reference page's coverage rule, not
131
+ // `x errors explain`. It runs here because it needs source and nothing else (#277).
132
+ const findings = [
133
+ ...report.findings,
134
+ ...(await checkErrorCodeResolution(ctx.root)),
135
+ ...(await hostFindings(ctx, 'errors')),
136
+ ];
129
137
  return {
130
138
  ...fromFindings(findings),
131
139
  output: msg('cli.verify.fixCoverage', {
@@ -1,12 +1,20 @@
1
- // Four shape rules the gate owns: one file, one job (a hard line ceiling), every workspace
2
- // package shipping the same contract files, every published package's tarball matching what its
3
- // manifest promises, and every published package being in the root build graph. All report
4
- // findings — a shape rule that is only written down is not a rule (axiom 3).
1
+ // Four shape rules the gate owns: one file, one job (a hard line ceiling on REVIEWABLE LOGIC),
2
+ // every workspace package shipping the same contract files, every published package's tarball
3
+ // matching what its manifest promises, and every published package being in the root build graph.
4
+ // All report findings — a shape rule that is only written down is not a rule (axiom 3).
5
+ //
6
+ // The ceiling exempts a pure re-export manifest, and only that. Such a file has one job by
7
+ // construction and its length is a function of the package's API size rather than of its
8
+ // complexity: `@ultimat3/core`'s `src/index.ts` reached 514 lines with 513 statements and not one
9
+ // of them logic, so the ceiling had stopped protecting anything and started refusing every new
10
+ // public subject. `isReExportManifest` is the whole carve-out — one statement of logic in such a
11
+ // file re-arms the ceiling on the same save, which is what keeps it from being a hole.
5
12
 
6
13
  import { existsSync } from 'node:fs';
7
14
  import { join } from 'node:path';
8
15
  import { ERROR_DOCS_URL, renderCauseValue } from '@ultimat3/core';
9
16
  import type { Finding } from './output';
17
+ import { isReExportManifest } from './reexport-manifest';
10
18
  import { eachSourceFile, isGenerated } from './source-files';
11
19
  import { checkRootReferences } from './tsconfig-references';
12
20
 
@@ -30,13 +38,21 @@ export const tooLongFinding = (path: string, lines: number): Finding => ({
30
38
  export const countLines = (text: string): number =>
31
39
  text === '' ? 0 : text.split('\n').length - (text.endsWith('\n') ? 1 : 0);
32
40
 
33
- /** Files are the unit of review: one file, one job, hard ceiling 500 lines. */
41
+ /**
42
+ * Files are the unit of review: one file, one job, hard ceiling 500 lines of reviewable logic.
43
+ *
44
+ * The source is read before the count is judged rather than after, because the exemption is a
45
+ * question about CONTENTS: a 3,000-line file of re-exports is one job and a 501-line file with one
46
+ * statement of logic in it is not, and only reading tells them apart.
47
+ */
34
48
  export async function checkFileSizes(root: string): Promise<readonly Finding[]> {
35
49
  const findings: Finding[] = [];
36
50
  for await (const path of eachSourceFile(root)) {
37
51
  if (isGenerated(path)) continue;
38
- const lines = countLines(await Bun.file(join(root, path)).text());
39
- if (lines > LINE_CEILING) findings.push(tooLongFinding(path, lines));
52
+ const source = await Bun.file(join(root, path)).text();
53
+ const lines = countLines(source);
54
+ if (lines <= LINE_CEILING || isReExportManifest(source)) continue;
55
+ findings.push(tooLongFinding(path, lines));
40
56
  }
41
57
  return findings;
42
58
  }