Skip to content

Commit 3754bbe

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-24569-v18-ga-release-prose
2 parents 66ad6ce + ba4afcc commit 3754bbe

4 files changed

Lines changed: 204 additions & 23 deletions

File tree

‎packages/spec/scripts/build-console-objectstack-root.test.ts‎

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@
2222
// exported: the pin script packs whole packages and refuses one whose
2323
// `types` entry is missing, and a typeless tree did exactly that (consumer
2424
// pointer on objectstack#24306, measured again on this branch);
25-
// 5. stop on the pin script's refusal, before the install.
25+
// 5. stop on the pin script's refusal, before the install;
26+
// 6. first build objectui's OWN pin (the pin script with no OBJECTSTACK_ROOT
27+
// and no OS_SKIP_DTS) and keep that cache, its stamp included, outside the
28+
// one the stand-in replaces: once this tree stands in, objectui's install
29+
// is this tree, so only that build can be the injection assertion's
30+
// reference. A rerun reuses it while its stamp names the pin.
2631
//
2732
// ## How it runs: the real script, a fake world
2833
//
@@ -60,10 +65,17 @@ if [[ "\${1:-}" == "install" ]]; then exit 42; fi
6065
exit 0
6166
`;
6267

63-
/** A pin script that HAS the input: it reads OBJECTSTACK_ROOT and records it. */
68+
/** A pin script that HAS the input: it records OBJECTSTACK_ROOT and leaves the
69+
* cache the real one does, its stamp at the root, unless told to refuse. */
6470
const PIN_WITH_HOOK = `import fs from 'node:fs';
65-
fs.appendFileSync(process.env.SHIM_LOG, 'pin ' + process.argv.slice(2).join(' ') + ' | OBJECTSTACK_ROOT=' + (process.env.OBJECTSTACK_ROOT ?? 'unset') + ' | cwd=' + process.cwd() + '\\n');
66-
process.exit(Number(process.env.FAKE_PIN_EXIT ?? '0'));
71+
const root = process.env.OBJECTSTACK_ROOT;
72+
fs.appendFileSync(process.env.SHIM_LOG, 'pin ' + process.argv.slice(2).join(' ') + ' | OBJECTSTACK_ROOT=' + (root ?? 'unset') + ' | OS_SKIP_DTS=' + (process.env.OS_SKIP_DTS ?? 'unset') + ' | cwd=' + process.cwd() + '\\n');
73+
if (process.env.FAKE_PIN_EXIT) process.exit(Number(process.env.FAKE_PIN_EXIT));
74+
const cache = 'node_modules/.cache/objectstack';
75+
fs.rmSync(cache, { recursive: true, force: true });
76+
fs.mkdirSync(cache + '/spec', { recursive: true });
77+
const sha = fs.readFileSync('.objectstack-sha', 'utf8').trim();
78+
fs.writeFileSync(cache + '/.stamp.json', JSON.stringify(root ? { source: 'external', root } : { source: 'pin', sha }));
6779
`;
6880

6981
/** A pin script from before the input existed: it builds its own pin, always. */
@@ -158,6 +170,8 @@ function buildConsole(pin: string, env: Record<string, string> = {}): Run {
158170
}
159171

160172
const closureBuilds = (log: string[]) => log.filter((l) => l.startsWith('pnpm exec turbo run build'));
173+
const pinBuilds = (log: string[]) => log.filter((l) => l.startsWith('pin build'));
174+
const reference = (pin: string) => path.join(FW, '.cache', `objectui-${pin.slice(0, 12)}`, 'node_modules/.cache/objectstack-pin-reference');
161175
const installs = (log: string[]) => log.filter((l) => l.startsWith('pnpm install'));
162176

163177
describe('build-console.sh: the .objectstack-sha branch', () => {
@@ -181,13 +195,25 @@ describe('build-console.sh: the .objectstack-sha branch', () => {
181195
expect(run.status, run.output).toBe(42);
182196
expect(run.log).toEqual([
183197
'pnpm exec turbo run build --filter=@objectstack/client --filter=@objectstack/spec --output-logs=new-only | OS_SKIP_DTS=unset',
184-
`pin build | OBJECTSTACK_ROOT=${FW} | cwd=${run.buildRoot}`,
198+
`pin build | OBJECTSTACK_ROOT=unset | OS_SKIP_DTS=unset | cwd=${run.buildRoot}`,
199+
`pin build | OBJECTSTACK_ROOT=${FW} | OS_SKIP_DTS=unset | cwd=${run.buildRoot}`,
185200
'pnpm install --frozen-lockfile --prefer-offline --config.production=false | OS_SKIP_DTS=unset',
186201
]);
202+
// The pin's own cache survived the stand-in that replaced it: the reference.
203+
expect(JSON.parse(fs.readFileSync(path.join(reference(HOOK_PIN), '.stamp.json'), 'utf8'))).toEqual({ source: 'pin', sha: 'a'.repeat(40) });
204+
expect(fs.existsSync(path.join(reference(HOOK_PIN), 'spec'))).toBe(true);
205+
});
206+
207+
it("a rerun reuses the reference while its stamp names the pin, and rebuilds it when it does not", () => {
208+
expect(pinBuilds(buildConsole(HOOK_PIN).log)).toEqual([expect.stringContaining(`OBJECTSTACK_ROOT=${FW}`)]);
209+
fs.writeFileSync(path.join(reference(HOOK_PIN), '.stamp.json'), JSON.stringify({ source: 'pin', sha: 'b'.repeat(40) }));
210+
expect(pinBuilds(buildConsole(HOOK_PIN).log)).toHaveLength(2);
187211
});
188212

189213
it('an inherited OS_SKIP_DTS does not reach the closure build, and the script says so', () => {
214+
fs.rmSync(reference(HOOK_PIN), { recursive: true, force: true });
190215
const run = buildConsole(HOOK_PIN, { OS_SKIP_DTS: '1' });
216+
expect(pinBuilds(run.log)[0]).toContain('OBJECTSTACK_ROOT=unset | OS_SKIP_DTS=unset');
191217
expect(run.status, run.output).toBe(42);
192218
expect(closureBuilds(run.log)).toEqual([
193219
'pnpm exec turbo run build --filter=@objectstack/client --filter=@objectstack/spec --output-logs=new-only | OS_SKIP_DTS=unset',

‎scripts/assert-console-spec-injection.mjs‎

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
//
1717
// injected = this framework tree's packages/spec (what must be bundled)
1818
// vendored = the @objectstack/spec objectui's own lockfile installed
19-
// (what gets bundled when the injection is missing or broken)
19+
// (what gets bundled when the injection is missing or broken);
20+
// on a pin carrying `.objectstack-sha`, objectui's own build of
21+
// that commit — see readPinnedReference for why only that one
2022
//
2123
// ## The test is two-sided, because one side is not enough
2224
//
@@ -73,8 +75,9 @@
7375
// --assets <built console dist/assets>
7476
//
7577
// Exit: 0 = injection proven (or no skew to prove) · 1 = injection failed
76-
// 2 = inconclusive / cannot run
78+
// 2 = inconclusive / cannot run (including a reference that is this tree)
7779

80+
import fs from 'node:fs';
7881
import path from 'node:path';
7982

8083
import {
@@ -107,6 +110,50 @@ function parseArgs(argv) {
107110

108111
const args = parseArgs(process.argv);
109112

113+
/**
114+
* The objectstack commit --vendored was built at, when objectui pins one; null otherwise.
115+
*
116+
* A pin carrying `.objectstack-sha` installs @objectstack/* by commit, and
117+
* build-console.sh stands THIS tree in for that commit, so the spec objectui's
118+
* install holds is this tree's own, packed. As --vendored it equals --injected:
119+
* both pools are empty, the no-skew branch below passes, and the stamp records no
120+
* witness for ANY bundle. What a broken pack-or-inject ships there is objectui's
121+
* own build of the pinned commit, so that build is the only reference taken on
122+
* such a pin. It is known by the stamp objectui's scripts/objectstack-pin.mjs
123+
* writes at the root of the cache it packed into (`<cache>/.stamp.json` beside
124+
* `<cache>/spec`): `source: 'pin'`, `sha` equal to the pin. A stand-in, a copy
125+
* with no such stamp (the install's node_modules/@objectstack/spec) or a build of
126+
* another commit is refused as cannot-judge, never read as "no skew".
127+
*/
128+
function readPinnedReference(objectuiDir, vendoredDir) {
129+
const pinFile = path.join(objectuiDir, '.objectstack-sha');
130+
if (!fs.existsSync(pinFile)) return null;
131+
const pin = fs.readFileSync(pinFile, 'utf8').trim();
132+
const stampFile = path.join(path.dirname(vendoredDir), '.stamp.json');
133+
let stamp = null;
134+
try {
135+
stamp = JSON.parse(fs.readFileSync(stampFile, 'utf8'));
136+
} catch {
137+
// No readable stamp is refused below, with the rest.
138+
}
139+
if (stamp?.source !== 'pin' || stamp?.sha !== pin) {
140+
const found = stamp ? `source ${JSON.stringify(stamp.source)} at ${String(stamp.sha).slice(0, 12)}` : 'no readable stamp';
141+
fail(
142+
`objectui pins objectstack ${pin.slice(0, 12)} (.objectstack-sha), so --vendored must be objectui's own\n` +
143+
` build of that commit, and \`${stampFile}\` says ${found}. On this pin objectui's install is\n` +
144+
" this tree (the OBJECTSTACK_ROOT stand-in), and judged against itself this check would pass any\n" +
145+
" bundle. build-console.sh keeps that build before standing this tree in; pass its spec.",
146+
);
147+
}
148+
return pin;
149+
}
150+
151+
const vendoredPin = readPinnedReference(path.resolve(args.objectui), path.resolve(args.vendored));
152+
const vendoredName = vendoredPin
153+
? `@objectstack/spec of objectui's own build of objectstack ${vendoredPin.slice(0, 12)}`
154+
: 'PUBLISHED @objectstack/spec';
155+
const vendoredOnly = vendoredPin ? 'pin-only' : 'published-only';
156+
110157
const assetsDir = path.resolve(args.assets);
111158
// The dist root is the assets dir's parent: build-console.sh passes
112159
// `<dist>/assets`, so this is where the .objectui-sha stamp already lives.
@@ -162,6 +209,8 @@ function stamp(skew) {
162209
{
163210
name: '@objectstack/spec',
164211
injectedFrom: path.resolve(args.injected),
212+
vendoredFrom: path.resolve(args.vendored),
213+
vendoredPin,
165214
skew,
166215
freshWitness,
167216
staleDetector,
@@ -175,8 +224,11 @@ function stamp(skew) {
175224
}
176225
}
177226

227+
// With a pinned reference this is the honest case, never the vacuous one: an
228+
// independently built spec of the pinned commit declares the same descriptions as
229+
// this tree's, so a bundle carrying either carries the same text.
178230
if (freshCounts.pool === 0 && staleCounts.pool === 0) {
179-
console.log('✓ Injected and vendored @objectstack/spec declare the same descriptions');
231+
console.log(`✓ Injected and vendored @objectstack/spec declare the same descriptions${vendoredPin ? ` (vendored: ${vendoredName})` : ''}`);
180232
console.log(' — no observable skew, so nothing for this check to assert.');
181233
stamp(false);
182234
process.exit(0);
@@ -193,7 +245,7 @@ if (staleCounts.pool > 0 && staleDetector === null) {
193245
console.error(" it could look for is in the bundle, and objectui's own source at the pin carries");
194246
console.error(' each one too, so none can tell the published spec from objectui\'s own text.');
195247
console.error(' The injection is UNVERIFIED by this check.');
196-
console.error(` published-only descriptions: ${staleCounts.pool}, all of them carried by objectui's source`);
248+
console.error(` ${vendoredOnly} descriptions: ${staleCounts.pool}, all of them carried by objectui's source`);
197249
console.error(` first of them: "${staleHostCarried[0]}"`);
198250
process.exit(2);
199251
}
@@ -204,19 +256,19 @@ if (freshPresent !== true && stalePresent !== true) {
204256
console.error('✗ Neither spec appears in the built console — no @objectstack/spec');
205257
console.error(' content matched. The injection is UNVERIFIED by this check.');
206258
console.error(` injected-only descriptions in the bundle: ${freshCounts.inBundle} of ${freshCounts.pool}`);
207-
console.error(` published-only descriptions in the bundle: ${staleCounts.inBundle} of ${staleCounts.pool}`);
259+
console.error(` ${vendoredOnly} descriptions in the bundle: ${staleCounts.inBundle} of ${staleCounts.pool}`);
208260
for (const line of [...freshNote(' '), ...staleNote(' ')]) console.error(line);
209261
process.exit(2);
210262
}
211263

212264
if (stalePresent === true) {
213-
console.error("✗ Built console still carries the PUBLISHED @objectstack/spec.");
214-
console.error(' The console resolved spec from objectui\'s lockfile, so any authorable');
215-
console.error(' key this framework declared after the last spec publish is unreachable');
265+
console.error(`✗ Built console still carries the ${vendoredName}.`);
266+
console.error(` The console resolved spec from objectui's ${vendoredPin ? 'own pin' : 'lockfile'}, so any authorable`);
267+
console.error(` key this framework declared after ${vendoredPin ? 'that commit' : 'the last spec publish'} is unreachable`);
216268
console.error(' in the Studio designer — the defect objectstack#8134 exists to end.');
217269
console.error('');
218270
console.error(` Text found in the bundle that ONLY the vendored spec has (${staleCounts.inBundle} of`);
219-
console.error(` ${staleCounts.pool} published-only descriptions), the first of them:`);
271+
console.error(` ${staleCounts.pool} ${vendoredOnly} descriptions), the first of them:`);
220272
console.error(` "${staleDetector}"`);
221273
if (staleCounts.hostCarried > 0) {
222274
console.error('');
@@ -241,16 +293,16 @@ if (freshPresent !== true) {
241293
process.exit(2);
242294
}
243295

244-
console.log("✓ Console bundle carries THIS tree's @objectstack/spec, and only it.");
296+
console.log(`✓ Console bundle carries THIS tree's @objectstack/spec, and only it${vendoredPin ? ` (vendored: ${vendoredName})` : ''}.`);
245297
console.log(` present (injected only): "${freshWitness}"`);
246298
console.log(` — ${freshCounts.inBundle} of ${freshCounts.pool} injected-only descriptions are in the bundle`);
247299
for (const line of freshNote(' ')) console.log(line);
248300
if (staleDetector) {
249301
console.log(` absent (vendored only): "${staleDetector}"`);
250302
if (staleCounts.hostCarried === 0) {
251-
console.log(` — and all ${staleCounts.pool} published-only descriptions are absent`);
303+
console.log(` — and all ${staleCounts.pool} ${vendoredOnly} descriptions are absent`);
252304
} else {
253-
console.log(` — and ${staleCounts.pool - staleCounts.hostCarried} of ${staleCounts.pool} published-only descriptions are absent;`);
305+
console.log(` — and ${staleCounts.pool - staleCounts.hostCarried} of ${staleCounts.pool} ${vendoredOnly} descriptions are absent;`);
254306
for (const line of staleNote(' ')) console.log(line);
255307
}
256308
}

‎scripts/build-console.sh‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,10 @@ echo "→ Console will bundle @objectstack/spec from ${SPEC_PKG}"
338338
# exactly as before. A pin with it but without the hook refuses, like the two
339339
# vite.config.ts guards above, rather than letting objectui build its own pin.
340340
OBJECTSTACK_PIN_SCRIPT="${BUILD_ROOT}/scripts/objectstack-pin.mjs"
341+
# The spec the injection assertion at the end judges the bundle against: objectui's
342+
# own install, on a pin without `.objectstack-sha` (the block below says what
343+
# replaces it on a pin with one).
344+
VENDORED_SPEC="${BUILD_ROOT}/node_modules/@objectstack/spec"
341345
if [[ -f "${BUILD_ROOT}/.objectstack-sha" ]]; then
342346
if [[ ! -f "$OBJECTSTACK_PIN_SCRIPT" ]] || ! grep -q "OBJECTSTACK_ROOT" "$OBJECTSTACK_PIN_SCRIPT"; then
343347
echo "✗ objectui@${PINNED_SHA:0:12} carries .objectstack-sha but has no OBJECTSTACK_ROOT hook in"
@@ -383,6 +387,29 @@ if [[ -f "${BUILD_ROOT}/.objectstack-sha" ]]; then
383387
(cd "$FRAMEWORK_ROOT" && unset OS_SKIP_DTS && pnpm exec turbo run build "${OBJECTSTACK_TURBO_FILTERS[@]}" --output-logs=new-only)
384388
fi
385389

390+
# The REFERENCE for the injection assertion. Once this tree stands in below,
391+
# objectui's install IS this tree, so its spec equals --injected and the
392+
# assertion would pass any bundle with no witness. What a broken pack-or-inject
393+
# ships here is objectui's own build of the commit its .objectstack-sha names, so
394+
# build that first by objectui's own recipe (the pin script without
395+
# OBJECTSTACK_ROOT, declarations included as it requires) and keep a copy, the
396+
# pin's .stamp.json with it, outside the cache the stand-in replaces. The
397+
# assertion refuses any other reference on such a pin. Reused while its stamp
398+
# names this pin: the build of a fixed commit does not change.
399+
OBJECTSTACK_PIN_SHA="$(tr -d '[:space:]' < "${BUILD_ROOT}/.objectstack-sha")"
400+
PIN_REFERENCE="${BUILD_ROOT}/node_modules/.cache/objectstack-pin-reference"
401+
REFERENCE_SHA="$(node -e '
402+
try { const s = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")); if (s.source === "pin") process.stdout.write(String(s.sha)); } catch {}
403+
' "${PIN_REFERENCE}/.stamp.json")"
404+
if [[ "$REFERENCE_SHA" != "$OBJECTSTACK_PIN_SHA" ]]; then
405+
echo "→ Building objectui's own @objectstack/* at its .objectstack-sha ${OBJECTSTACK_PIN_SHA:0:12}, the reference the spec assertion judges this bundle against..."
406+
(cd "$BUILD_ROOT" && env -u OBJECTSTACK_ROOT -u OS_SKIP_DTS node scripts/objectstack-pin.mjs build)
407+
rm -rf "$PIN_REFERENCE" "${PIN_REFERENCE}.partial"
408+
cp -R "${BUILD_ROOT}/node_modules/.cache/objectstack" "${PIN_REFERENCE}.partial"
409+
mv "${PIN_REFERENCE}.partial" "$PIN_REFERENCE"
410+
fi
411+
VENDORED_SPEC="${PIN_REFERENCE}/spec"
412+
386413
# The pin script refuses (exit 1, reason on stderr) on a missing entry point,
387414
# and on two dependency mismatches between these packages and objectui's pin:
388415
# a packed manifest whose dependency NAMES differ from what objectui's lockfile
@@ -484,11 +511,11 @@ fi
484511
# --objectui is the checkout just built: objectui's own registry mirrors spec
485512
# descriptions, so text its tracked source carries is not evidence of either spec.
486513
# On a pin that installs @objectstack/* from this tree (the OBJECTSTACK_ROOT block
487-
# above), --vendored IS this tree's spec, packed: the script then reports no
488-
# skew, because objectui's install no longer holds a published spec to leak.
514+
# above), objectui's install is this tree's spec, packed, so --vendored is the
515+
# reference that block kept instead: objectui's own build of its .objectstack-sha.
489516
node "${FRAMEWORK_ROOT}/scripts/assert-console-spec-injection.mjs" \
490517
--injected "$SPEC_PKG" \
491-
--vendored "${BUILD_ROOT}/node_modules/@objectstack/spec" \
518+
--vendored "$VENDORED_SPEC" \
492519
--objectui "$BUILD_ROOT" \
493520
--assets "${TARGET}/assets"
494521

0 commit comments

Comments
 (0)