Skip to content

Commit b37f0b1

Browse files
claude[bot]claude
andauthored
fix(spec): probe for the checked-in sdui.manifest.json before prescribing how to make one (#16847)
* fix(spec): probe for the checked-in sdui.manifest.json before prescribing how to make one `check:react-declaration-parity`'s refusal text was a constant asserting "this repository contains no copy of it" and sending the reader to build objectui and dump a manifest in a browser. That has been false since #13446: the manifest is checked in at the repository root and lint.yml runs this gate against it with MANIFEST="$PWD/sdui.manifest.json". Twice measured, a dev read the prose, filed the gate as EXTERNAL_INPUT_REQUIRED / NOT MEASURED, and one such declaration reached a deliverable — on a head where setting that one variable gives exit 0. The text is now a function of a filesystem probe on the repository root resolved from the script's own location. When the manifest is there the reader gets a paste-ready block (a `cd` to the root, then the exact command CI runs); only when it is genuinely absent does the objectui dump path appear, and it names the path it looked at rather than making a claim about repositories in general. Both branches are pinned — one branch tested is how this defect was built. Also hardens the third leg of object-refinement-check-exports: it counts mount points by name without asserting the name is declared exactly once, so a shadowing local binding could satisfy it. Measured today: four names, one declaration each — a residual hole in the pin, not a live shadowing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x * fix(spec): name the regenerator that actually owns the root manifest The present-branch prescription said `pnpm sdui:manifest` rewrites the committed root manifest when .objectui-sha moves. Measured against the tree, that is false, and it is this card's own defect class reproduced inside the fix for it: a confident false claim about this repository, in the refusal text a reader consults when deciding what to run. - `pnpm sdui:manifest` is scripts/gen-sdui-manifest.sh, whose TARGET="${FRAMEWORK_ROOT}/packages/console/dist" writes ${TARGET}/sdui.manifest.json and runs the gate against that path. No line in it writes the root artefact or scripts/sdui-manifest.record.json. - node scripts/gen-sdui-manifest-node.mjs owns both: its header says "regenerate the repo-root sdui.manifest.json", OUT_PATH is ROOT/sdui.manifest.json and RECORD_PATH is ROOT/scripts/sdui-manifest.record.json. The record's own `generator` field names it, and check-sdui-manifest.mjs prescribes that call. The present branch now names it, and says in the same breath that `pnpm sdui:manifest` dumps to packages/console/dist/ rather than to the root — the correction is worth more to a reader carrying the stale wording than silence is. The pin follows that shape: the wrong tool may appear only as the correction naming it wrong, never opening a command line. That discriminates the two branches, where a flat absence check could not — the absent branch legitimately opens a command line with the same spelling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x * test(spec): make the pin enforce the whole sentence its comment promises The comment said the wrong tool may appear "ONLY as the correction naming it wrong — never as a command to run", but the assertion under it was `not.toMatch(/^\s*pnpm sdui:manifest/m)`, which enforces only the second half. Measured against the real generated text: the original defective claim ("`pnpm sdui:manifest` rewrites it when .objectui-sha moves") is itself MID-LINE, so the line-anchored form does not match it at all — it was caught only by the positive pins beside it. Prose asserting more than the code does, inside a change about prose asserting more than the tree does. The lookahead form enforces the whole sentence and catches the mid-line shape on its own. Measured on all three texts before it was written, via the real manifestPrescription() output rather than a hand-typed fixture: present branch anchored: false lookahead: false (assertion passes) defective anchored: FALSE lookahead: TRUE (only the new form catches it) absent branch anchored: true lookahead: true The absent branch legitimately opens command lines with that spelling, which is why this assertion stays inside the present-branch leg and must not be hoisted; a flat `not.toContain` is unavailable in the other direction, since it would forbid the correction itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent c930f85 commit b37f0b1

4 files changed

Lines changed: 297 additions & 38 deletions

File tree

packages/spec/scripts/check-react-blocks-declaration-parity.test.ts

Lines changed: 134 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ import fs from 'node:fs';
4242
import os from 'node:os';
4343
import path from 'node:path';
4444
import { fileURLToPath } from 'node:url';
45+
import {
46+
REPO_MANIFEST_RELATIVE,
47+
manifestPrescription,
48+
repoManifestIsCheckedIn,
49+
repoManifestPath,
50+
} from './manifest-prescription';
4551

4652
const HERE = path.dirname(fileURLToPath(import.meta.url));
4753
const PKG = path.resolve(HERE, '..');
@@ -296,9 +302,14 @@ describe('check:react-declaration-parity — the gate CAN go red (#4690)', () =>
296302
expect(status, output).toBe(1);
297303
expect(output).toMatch(/did NOT run/);
298304
expect(output).not.toMatch(/skipping/);
299-
// The refusal has to be actionable: the manifest's producer lives in another repo.
300-
expect(output).toContain('pnpm sdui:manifest');
301-
expect(output).toContain('OBJECTUI_ROOT=../objectui');
305+
// The refusal has to be actionable. WHICH prescription is actionable depends on
306+
// the repository the script is standing in, so this only asserts the branch that
307+
// is true HERE — a manifest is checked in at the root — and the prescription's
308+
// own tests below cover both. Asserting the dump path unconditionally is how
309+
// #16715 stayed invisible: the assertion agreed with the prose, and both were
310+
// stale in the same direction.
311+
expect(output).toContain('MANIFEST="$PWD/sdui.manifest.json"');
312+
expect(output).toContain('pnpm --filter @objectstack/spec check:react-declaration-parity');
302313
});
303314

304315
it('a MANIFEST path that does not exist fails loudly, naming the path', { timeout: SPAWN_TIMEOUT_MS }, () => {
@@ -491,3 +502,123 @@ describe('check:react-declaration-parity — the node contract, and its calibrat
491502
expect(status, output).toBe(0);
492503
});
493504
});
505+
506+
/**
507+
* THE REFUSAL'S PRESCRIPTION IS PROBED, AND BOTH WORLDS ARE PINNED (#16715).
508+
*
509+
* The gate's refusal text used to be a constant asserting "this repository contains
510+
* no copy of it" and sending the reader to build objectui and dump one in a browser.
511+
* That was false from #13446 onward — `sdui.manifest.json` is checked in at the root
512+
* and `lint.yml` runs this gate against it — and it is read at the exact moment
513+
* someone is deciding whether the gate can run at all. Twice measured (#16489 / PR
514+
* #16697, and PR #16777) a dev believed it and filed a locally-runnable gate as
515+
* `EXTERNAL_INPUT_REQUIRED` / NOT MEASURED; one of those declarations reached a
516+
* deliverable, on a head where setting one variable gave exit 0.
517+
*
518+
* ⭐ THE ONE-BRANCH SHAPE IS THE DEFECT, so testing one branch would rebuild it. The
519+
* probe is a real filesystem check against a temp root here — both answers are
520+
* producible — and the wiring (that the script probes at all, and prints the
521+
* paste-ready command in THIS repository) is asserted through a real spawn above and
522+
* below. Neither branch may assert the other's world: that is what each `not` pins.
523+
*/
524+
describe('check:react-declaration-parity — the prescription is probed, not asserted (#16715)', () => {
525+
/** A root that has a manifest, and one that does not — the two worlds, on disk. */
526+
function withRoots(fn: (roots: { withManifest: string; without: string }) => void): void {
527+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'react-parity-prescription-'));
528+
try {
529+
const withManifest = path.join(dir, 'has-one');
530+
const without = path.join(dir, 'has-none');
531+
fs.mkdirSync(withManifest);
532+
fs.mkdirSync(without);
533+
fs.writeFileSync(path.join(withManifest, REPO_MANIFEST_RELATIVE), '{"components":{}}', 'utf8');
534+
fn({ withManifest, without });
535+
} finally {
536+
fs.rmSync(dir, { recursive: true, force: true });
537+
}
538+
}
539+
540+
it('the probe answers from the filesystem, both ways', () => {
541+
withRoots(({ withManifest, without }) => {
542+
expect(repoManifestIsCheckedIn(withManifest)).toBe(true);
543+
expect(repoManifestIsCheckedIn(without)).toBe(false);
544+
expect(repoManifestPath(withManifest)).toBe(path.join(withManifest, 'sdui.manifest.json'));
545+
});
546+
});
547+
548+
it('BRANCH: manifest checked in — hands over a paste-ready command, not a production errand', () => {
549+
withRoots(({ withManifest }) => {
550+
const text = manifestPrescription({ repoRoot: withManifest, checkedIn: true });
551+
// The command CI runs, spelled as CI spells it, plus the `cd` that makes `$PWD`
552+
// true — so the block works pasted from any working directory.
553+
expect(text).toContain('MANIFEST="$PWD/sdui.manifest.json"');
554+
expect(text).toContain(`cd ${withManifest}`);
555+
expect(text).toContain('pnpm --filter @objectstack/spec check:react-declaration-parity');
556+
expect(text).toContain('--baseline react-declaration-parity.baseline.json --strict');
557+
expect(text).toContain(path.join(withManifest, 'sdui.manifest.json'));
558+
// ⭐ The negative half: the false sentence, and the errand it justified, are gone.
559+
expect(text).not.toContain('contains no copy of it');
560+
expect(text).not.toContain('pnpm objectui:build');
561+
expect(text).not.toContain('OBJECTUI_ROOT=../objectui');
562+
// ⭐ And it must name the RIGHT regenerator. `pnpm sdui:manifest` is
563+
// scripts/gen-sdui-manifest.sh, whose TARGET is packages/console/dist — it never
564+
// writes the root artefact or scripts/sdui-manifest.record.json.
565+
// `node scripts/gen-sdui-manifest-node.mjs` owns both, which is what the record's
566+
// own `generator` field and check-sdui-manifest.mjs both say. Sending a reader to
567+
// the wrong one is this card's defect class again: a confident false claim about
568+
// the tree, in the text consulted when deciding what to run.
569+
expect(text).toContain('gen-sdui-manifest-node.mjs');
570+
// The wrong tool may appear ONLY inside the correction that names it wrong —
571+
// nowhere else, command line or prose. The LOOKAHEAD is what enforces the whole of
572+
// that sentence, and it was measured before it was written: the original defective
573+
// claim ('`pnpm sdui:manifest` rewrites it when .objectui-sha moves') was itself
574+
// MID-LINE, so a line-anchored form does not match it at all and would have left
575+
// this comment promising more than the code delivers — the same over-claim, one
576+
// layer up, in a card about exactly that. A flat `not.toContain` is not available
577+
// either: it would forbid the correction itself. This assertion belongs to the
578+
// present branch alone — the absent branch legitimately opens command lines with
579+
// that spelling, so it must not be hoisted out of this leg.
580+
expect(text).not.toMatch(/pnpm sdui:manifest(?!` does NOT rewrite)/);
581+
expect(text).toContain('`pnpm sdui:manifest` does NOT rewrite this file');
582+
// …and it says, in words, what the two devs got wrong.
583+
expect(text).toMatch(/NOT MEASURED/);
584+
});
585+
});
586+
587+
it('BRANCH: no manifest — still hands over the whole production path', () => {
588+
withRoots(({ without }) => {
589+
const text = manifestPrescription({ repoRoot: without, checkedIn: false });
590+
expect(text).toContain('pnpm objectui:build');
591+
expect(text).toContain('pnpm sdui:manifest');
592+
expect(text).toContain('OBJECTUI_ROOT=../objectui');
593+
expect(text).toContain('MANIFEST=/path/to/sdui.manifest.json');
594+
// It names the path it actually looked at, so "no copy" is a reading and not a
595+
// claim about repositories in general — the exact over-reach that caused #16715.
596+
expect(text).toContain(path.join(without, 'sdui.manifest.json'));
597+
// ⭐ The negative half: it must not tell a reader a file is there when it is not.
598+
expect(text).not.toContain('IS checked in');
599+
expect(text).not.toContain('MANIFEST="$PWD/sdui.manifest.json"');
600+
});
601+
});
602+
603+
it('the two branches are genuinely different text, not one string with a toggle', () => {
604+
withRoots(({ withManifest }) => {
605+
const present = manifestPrescription({ repoRoot: withManifest, checkedIn: true });
606+
const absent = manifestPrescription({ repoRoot: withManifest, checkedIn: false });
607+
expect(present).not.toEqual(absent);
608+
});
609+
});
610+
611+
/**
612+
* The wiring, end to end: the script must PROBE, and in this repository the probe
613+
* must find the committed manifest. The unit tests above would all pass against a
614+
* script that never called the function.
615+
*/
616+
it('the real gate prints the checked-in branch in THIS repository', { timeout: SPAWN_TIMEOUT_MS }, () => {
617+
const { status, output } = runExit({});
618+
expect(status, output).toBe(1);
619+
expect(output).toContain('IS checked in at this repository\'s root');
620+
expect(output).toContain('MANIFEST="$PWD/sdui.manifest.json"');
621+
expect(output).not.toContain('contains no copy of it');
622+
expect(output).not.toContain('OBJECTUI_ROOT=../objectui');
623+
});
624+
});

packages/spec/scripts/check-react-blocks-declaration-parity.ts

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,22 @@
7777
// (scripts/gen-sdui-manifest.sh) is the wrapper that builds objectui at
7878
// `.objectui-sha`, dumps the manifest, and then runs THIS gate against it.
7979
//
80-
// This repository carries no manifest to fall back on. Measured, so the next reader
81-
// does not have to re-derive it: `packages/console/dist/` is gitignored (the package
82-
// tracks 4 files, none of them a dist), `scripts/build-console.sh` deliberately does
83-
// not produce one — it must not drag a browser into the console build, and says so —
84-
// and the published `@objectstack/console` tarball has none either (16.1.0: 513
85-
// files, zero `sdui` matches; its `dist/manifest.json` is the PWA manifest), so even
86-
// the CLI's `@objectstack/console/dist/sdui.manifest.json` fallback resolves to
87-
// nothing.
80+
// NOTHING HERE PRODUCES one — but since #13446 one is CHECKED IN. The production
81+
// half is unchanged and still measured: `packages/console/dist/` is gitignored (the
82+
// package tracks 4 files, none of them a dist), `scripts/build-console.sh`
83+
// deliberately does not produce one — it must not drag a browser into the console
84+
// build, and says so — and the published `@objectstack/console` tarball has none
85+
// either (16.1.0: 513 files, zero `sdui` matches; its `dist/manifest.json` is the PWA
86+
// manifest), so even the CLI's `@objectstack/console/dist/sdui.manifest.json`
87+
// fallback resolves to nothing.
88+
//
89+
// What DID change is the fallback: `sdui.manifest.json` is committed at the
90+
// repository root, `scripts/sdui-manifest.record.json` pins it to the `.objectui-sha`
91+
// it was dumped from, and `lint.yml` runs this gate against it on every PR
92+
// (`MANIFEST="$PWD/sdui.manifest.json"`). ⛔ So do not read a non-zero exit here as
93+
// "no input available": the input is in the tree, and the refusal text now probes for
94+
// it rather than asserting its absence (#16715 — `./manifest-prescription.ts`, which
95+
// carries what that stale assertion cost).
8896
//
8997
// Therefore "no manifest" never means "nothing to check" here. It means THIS GATE
9098
// DID NOT RUN — reported as exit 1, not as a `⚠` and exit 0. Until #4690 the two
@@ -113,7 +121,10 @@
113121
process.env.OS_EAGER_SCHEMAS = '1';
114122

115123
import fs from 'fs';
124+
import path from 'node:path';
125+
import { fileURLToPath } from 'node:url';
116126
import { z } from 'zod';
127+
import { manifestPrescription, repoManifestIsCheckedIn } from './manifest-prescription';
117128
import { REACT_BLOCKS } from '../src/ui/react-blocks';
118129
import { ComponentPropsMap } from '../src/ui/component.zod';
119130
import { PageComponentSchema } from '../src/ui/page.zod';
@@ -294,35 +305,25 @@ function manifestInputs(manifest: any, schemaType: string): string[] | null {
294305
}
295306

296307
/**
297-
* The prescription every "could not run" exit carries.
308+
* The repository root, resolved from THIS FILE's own location.
309+
*
310+
* Not from `process.cwd()` and not from an env var: the prescription's whole job is
311+
* to tell a reader where the manifest is, and a cwd-derived answer would be wrong for
312+
* exactly the reader who is lost. `packages/spec/scripts/` → three levels up.
313+
*/
314+
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
315+
316+
/**
317+
* The prescription every "could not run" exit carries — probed, not asserted.
298318
*
299-
* A refusal is only better than a skip if the reader can act on it. The manifest's
300-
* provenance is two repos away from whoever hits this — it is dumped from objectui's
301-
* registry in a browser — so the exit that replaced the skip has to hand over the
302-
* whole path, not just the missing variable's name.
319+
* Text and probe live in `./manifest-prescription.ts` so both branches (a manifest
320+
* checked in at the root, and none) are unit-testable without a repository that has
321+
* to be in two states at once. #16715 is what a single unprobed branch cost.
303322
*/
304-
const MANIFEST_PRESCRIPTION = [
305-
'',
306-
' The registry side of this comparison is objectui\'s sdui.manifest.json, and this',
307-
' repository contains no copy of it: packages/console/dist/ is gitignored, the console',
308-
' build deliberately does not produce one (it must not pull in a browser), and the',
309-
' published @objectstack/console ships none either. Produce one, then re-run:',
310-
'',
311-
' pnpm objectui:build # build + vendor the console at the pinned .objectui-sha',
312-
' pnpm sdui:manifest # dump the manifest in a browser AND run this ratchet',
313-
'',
314-
' Against a sibling objectui checkout, point the build at it first:',
315-
'',
316-
' OBJECTUI_ROOT=../objectui pnpm objectui:build && pnpm sdui:manifest',
317-
'',
318-
' Or, with a manifest already in hand:',
319-
'',
320-
' MANIFEST=/path/to/sdui.manifest.json \\',
321-
' pnpm --filter @objectstack/spec check:react-declaration-parity \\',
322-
' --baseline react-declaration-parity.baseline.json --strict',
323-
'',
324-
' (the dump needs a browser: pnpm exec playwright install chromium-headless-shell)',
325-
].join('\n');
323+
const MANIFEST_PRESCRIPTION = manifestPrescription({
324+
repoRoot: REPO_ROOT,
325+
checkedIn: repoManifestIsCheckedIn(REPO_ROOT),
326+
});
326327

327328
/**
328329
* Exit loudly because the gate could not run — never because it ran and disagreed.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// The prescription every "could not run" exit of `check:react-declaration-parity`
4+
// carries — and the PROBE that decides which prescription is true here.
5+
//
6+
// A refusal is only better than a skip if the reader can act on it. The manifest's
7+
// provenance is two repos away from whoever hits this — it is dumped from objectui's
8+
// registry in a browser — so the exit that replaced the skip has to hand over the
9+
// whole path, not just the missing variable's name.
10+
//
11+
// WHY THIS IS A FUNCTION OF A PROBE AND NOT A CONSTANT (#16715).
12+
//
13+
// The single constant this file replaced asserted, unconditionally, that "this
14+
// repository contains no copy of it" and sent the reader off to build objectui and
15+
// dump one in a browser. That stopped being true at #13446, when `sdui.manifest.json`
16+
// was checked in at the repository root and `lint.yml` began running this gate
17+
// against it with `MANIFEST="$PWD/sdui.manifest.json"`.
18+
//
19+
// The prose kept its old certainty, and it is read at the exact moment a reader is
20+
// deciding whether the gate can run at all — the shape that gets believed. Measured
21+
// cost, twice: on #16489 / PR #16697 and again on PR #16777 a dev read it, reported
22+
// the gate as `EXTERNAL_INPUT_REQUIRED` / NOT MEASURED, and one of those declarations
23+
// reached a deliverable — on a head where setting that one variable and re-running
24+
// gave exit 0 and "no new DECLARATION divergence vs accepted baseline".
25+
//
26+
// So: neither branch may assert the other's world. When the file is there the reader
27+
// gets a command to paste; only when it is genuinely absent does the objectui dump
28+
// path appear. Both branches are pinned in
29+
// `check-react-blocks-declaration-parity.test.ts` — one branch tested is how this
30+
// defect was built in the first place.
31+
32+
import fs from 'node:fs';
33+
import path from 'node:path';
34+
35+
/** Where the manifest is committed, relative to the repository root. */
36+
export const REPO_MANIFEST_RELATIVE = 'sdui.manifest.json';
37+
38+
/**
39+
* Is a manifest checked in at `repoRoot`?
40+
*
41+
* A filesystem probe and nothing else: no env var, no config key, no build state.
42+
* The question is "can the reader point MANIFEST at a file that exists right now",
43+
* and only the filesystem answers that.
44+
*/
45+
export function repoManifestPath(repoRoot: string): string {
46+
return path.join(repoRoot, REPO_MANIFEST_RELATIVE);
47+
}
48+
49+
export function repoManifestIsCheckedIn(repoRoot: string): boolean {
50+
return fs.existsSync(repoManifestPath(repoRoot));
51+
}
52+
53+
/**
54+
* The prescription text, for whichever world the probe found.
55+
*
56+
* `repoRoot` is printed as a `cd` so the block is paste-ready from any working
57+
* directory while still spelling the variable the way CI does — `$PWD` is only
58+
* correct once you are standing at the root, so the command that sets it says how
59+
* to get there.
60+
*/
61+
export function manifestPrescription(opts: { repoRoot: string; checkedIn: boolean }): string {
62+
const { repoRoot, checkedIn } = opts;
63+
64+
if (checkedIn) {
65+
return [
66+
'',
67+
" The registry side of this comparison is objectui's sdui.manifest.json, and a copy",
68+
` IS checked in at this repository's root (${repoManifestPath(repoRoot)}) — the very`,
69+
' artefact CI feeds this gate. Nothing has to be produced. Run it as CI runs it:',
70+
'',
71+
` cd ${repoRoot}`,
72+
' MANIFEST="$PWD/sdui.manifest.json" \\',
73+
' pnpm --filter @objectstack/spec check:react-declaration-parity \\',
74+
' --baseline react-declaration-parity.baseline.json --strict',
75+
'',
76+
' That is a complete local run: no browser, no objectui checkout, no dump step. So a',
77+
' non-zero exit from THIS gate is a reading, not an unavailable input — do not report',
78+
' it as EXTERNAL_INPUT_REQUIRED or NOT MEASURED (#16715).',
79+
'',
80+
' The committed manifest tracks the objectui pin: when .objectui-sha moves, regenerate it',
81+
' and its record with `node scripts/gen-sdui-manifest-node.mjs` (check-sdui-manifest.mjs',
82+
' prints the exact call). ⛔ `pnpm sdui:manifest` does NOT rewrite this file: it dumps to',
83+
' packages/console/dist/, not to the root.',
84+
].join('\n');
85+
}
86+
87+
return [
88+
'',
89+
" The registry side of this comparison is objectui's sdui.manifest.json, and no copy of",
90+
` it is checked in at ${repoManifestPath(repoRoot)}. Nothing here can produce one:`,
91+
' packages/console/dist/ is gitignored, the console build deliberately does not produce',
92+
' one (it must not pull in a browser), and the published @objectstack/console ships none',
93+
' either. Produce one, then re-run:',
94+
'',
95+
` cd ${repoRoot}`,
96+
' pnpm objectui:build # build + vendor the console at the pinned .objectui-sha',
97+
' pnpm sdui:manifest # dump the manifest in a browser AND run this ratchet',
98+
'',
99+
' Against a sibling objectui checkout, point the build at it first:',
100+
'',
101+
' OBJECTUI_ROOT=../objectui pnpm objectui:build && pnpm sdui:manifest',
102+
'',
103+
' Or, with a manifest already in hand:',
104+
'',
105+
' MANIFEST=/path/to/sdui.manifest.json \\',
106+
' pnpm --filter @objectstack/spec check:react-declaration-parity \\',
107+
' --baseline react-declaration-parity.baseline.json --strict',
108+
'',
109+
' (the dump needs a browser: pnpm exec playwright install chromium-headless-shell)',
110+
].join('\n');
111+
}

0 commit comments

Comments
 (0)