Skip to content

Commit c2815a2

Browse files
fix(cli): the package-docs step line reports what it collected, not what it attempted (#18445)
Fixes #18432 Clause-②: no — this PR puts no new key on a published payload. It changes only a progress line's text and the doc transcript that quotes it; no input the build accepts or refuses changes. Declared by the dispatching seat in claim `5696261079`. ## What changed `os build` / `os compile` printed its package-docs step line **before** the call it announces, and the line carried no count: ``` if (!flags.json) printStep('Collecting package docs (ADR-0046)...'); const docsResult = collectAndLintDocs(...) ``` So a build that collected nothing emitted the same reassuring sentence as one that collected four documents. The print now happens after the collection and reports it: ``` → Collecting package docs (ADR-0046)... 0 collected → Collecting package docs (ADR-0046)... 4 collected ``` This is the reassurance half of #18170. #18428 landed the audible half, where an uncollected docs directory speaks for itself; this card was fenced out of that PR because the transcript quoting the step line lived on a page PR #18416 had staked. That page merged at 2026-09-16T10:48Z, so the fence is lifted. ## The doc half is a measurement, not a copy-edit `content/docs/deployment/cli.mdx` carries the step line inside a fence tagged `transcript=os-build`. The number written there is the sample fixture's real count, **measured, not chosen**: - **N = 0**, read off a real `os build` run. - **Where measured**: the fixture the page's own callout names — the `blank` starter from `packages/create-objectstack/src/templates/blank` (the two-field `my_app_note` object and the three connector plugins) plus the four-field ticket object, the view and the action from `content/docs/getting-started/build-with-claude-code.mdx` §3, renamed out of that page's `support_desk_` namespace into `my_app_`, with the `support` app deliberately not added. Built out-of-tree in the session scratchpad against this branch's `packages/`, run through `packages/cli/bin/run-dev.js`. - **The reconstruction is corroborated by the page's other numbers**, which reproduce exactly on the same run and were not touched: `Running author-time rules (45)`, `Data: 2 Objects 6 Fields`, `UI: 1 Views 1 Actions`, `Runtime: 3 plugins`. - **Why 0**: neither the `blank` template nor the walkthrough ships a `src/docs/` directory, and the stack declares no inline `docs:`. The measured run is the transcript's own case of the sentence the card drafted. Measured run, verbatim: ``` → Checking capability providers (#3366)... → Collecting package docs (ADR-0046)... 0 collected → Writing artifact... ✓ Build complete (118ms) ``` ## The transcript-drift gate does NOT hold this number — and that is correct Dispatch Zone 2 assumed `check:docs-transcript-drift` would hold the new value. **Measured and falsified**, so it is reported rather than relied on: that gate's `TOKENS` array in `scripts/docs-audit/check-docs-transcript-drift.mjs` carries exactly one row, `author-time-rule-count`, matching `author-time rules \((\d+)\)`. It compares the `45` in this block and nothing else. That is not a hole this PR opens. The gate is scoped by design to values a **live registry** derives; `0 collected` is *fixture identity*, in the same class as `2 Objects 6 Fields` and `1 Views 1 Actions`, which the page's existing callout already covers ("everything else is fixture identity and reproduces"). A `TOKENS` row here would have to build a sample project rather than import a registry, which is a different gate. Named so the next author does not read the drift gate's green as vouching for this digit. The number that gate *does* hold is unchanged by this PR, and `check:docs-transcript-drift` was run. ## Tests `packages/cli/test/build-docs-step-count.e2e.test.ts` pins the behaviour **as a pair**, because a test that only asserted "the step line was printed" passes on the defective tree — the defective tree printed it unconditionally: | fixture | step line | |---|---| | no `src/docs/` at all | `0 collected` | | `src/docs/` present and empty | `0 collected` | | two docs | `2 collected` | Each run's printed count is also compared against the emitted `dist/objectstack.json`, so a number that drifted away from the set it describes cannot pass as text. The pre-fix spelling — the sentence with nothing after the ellipsis — is pinned **absent**. `--json` is asserted to stay one JSON document carrying no step text. The suite spawns the CLI, so it reuses `childEnv()` and the `bin/run-dev.js` + tsx shape of `build-json-advisory-parity.e2e.test.ts` rather than inventing one. ⚠️ **Where this pin runs, stated so nobody reads "tests added" as "gated on every PR".** The `.e2e.test.ts` name puts it in this package's **nightly tier**: `packages/cli/vitest.config.ts` routes the population through `OS_TEST_TIERS`, so the queue and per-PR runs collect the 212 non-tier files and this file is not among them; the nightly on `main` collects the tier files, where it lands in `integration` by `vitest-tiers.ts`'s SPAWN predicate. That is the package's own measured cost design — every one of its 60 spawners lives there — so a new spawner follows it rather than having me make a unilateral call about the Test Core critical path. Locally it was run under that switch, and `pnpm check:tier-file-adoption` is green. ## Declared deviation from the claim's file surface Claim `5696261079` declares a two-file surface. This PR carries four files, and the two extra ones are declared rather than quiet: - `packages/cli/test/build-docs-step-count.e2e.test.ts` — the pin the dispatching seat itself asked for in its suggested route ("the assertion that would have caught the original defect"), and what AGENTS.md's Post-Task Checklist step 1 owes. - `.changeset/18432-docs-step-line-reports-count.md` — `@objectstack/cli` is a published package and this moves user-visible build output, so a `patch` changeset is owed and `skip-changeset` would be wrong. No production file outside the two the claim names is touched. `content/docs/releases/` and `packages/spec/` are untouched. ## Verification Gate derivation is from the **actual** changed files, recomputed by the tool from the merge base rather than a hand-written list, and reconciled back with `--ran` carrying each command's own exit code (captured before any pipe): ``` node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -> 93 command(s), change set: the 4 files above, merge base 55fd5ee node scripts/pm/dispatch-gates.mjs --ran RAN_RECORD_FILE --repo objectstack-ai/objectstack -> 93 derived famil(ies) accounted for — 93 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero — all 93 recorded an exit code and none of them is 3) ``` **92 of 93 green.** The one non-zero is the known pre-existing red: | gate | exit | reading | |---|---|---| | `pnpm check:cross-package-test-inputs` | 1 | **Pre-existing, not this PR.** Its only finding is rooted in `packages/cli/test/init-created-files-summary.e2e.test.ts` descending `packages/spec/dist/` — a file this diff never touches, and the new test file appears nowhere in the report. Filed as #18353 / #18348. | Five gates first answered `PREREQUISITE NOT MET` against a partly-built tree (`check:skill-examples` exit 1 on a missing `packages/client-react/dist`, `check:dual-build-cjs-loads` / `check:i18n` / `check:i18n-coverage` / `check:i18n-walk-parity` exit 3). Those are *nothing was measured*, not passes and not findings, so they were re-run after `pnpm build` and all five are **exit 0**. `pnpm check:docs-transcript-drift`, `pnpm check:doc-anchors`, `pnpm check:doc-authoring`, `pnpm check:docs-single-h1` and `pnpm check:nul-bytes` are green on the edited page. `pnpm lint` — the repo-wide `eslint . --no-inline-config`, which the derivation never names — was run **whole**, not narrowed: **exit 0**, no findings. No `.cache/objectui-*` existed in this worktree to pollute it. **Tests** ``` pnpm --filter @objectstack/cli typecheck exit 0 (check:test-typecheck: the test layer compiles under tsconfig.test.json; debt unchanged at 3 file(s) / 28 error(s) / 6 pinned signature(s)) pnpm --filter @objectstack/cli exec vitest run --project unit --shard=N/4 shard 1 53 files / 883 tests passed shard 2 53 files / 750 tests passed shard 3 52 files / 622 tests passed shard 4 52 files / 737 tests passed 210 files / 2992 tests, all green OS_TEST_TIERS=nightly pnpm --filter @objectstack/cli exec vitest run \ --project integration test/build-docs-step-count.e2e.test.ts Test Files 1 passed (1) Tests 5 passed (5) ``` **Reverse verification — the pin can fail, proved rather than asserted.** The fix was committed first, then the pre-fix ordering was restored on disk and the suite re-run: ``` HEAD blob: a1c7daf ON-DISK PROOF: fixed-spelling count 1 -> 0; pre-fix-spelling count now 1 mutated hash: a2b25a52a3bd89486f9266f0bd5e9de03de65d2c result: Tests 4 failed | 1 passed (5) restored: hash matches HEAD blob and `git diff HEAD` is empty ``` ⚠️ **The predicted direction, stated before the run and then observed: four red, one green.** The `--json` case is a *control*, not a regression detector — `--json` prints no step line on either tree, so a run in which it also went red would mean the ablation had hit something other than the ordering. The four behavioural pins are the ones that must move, and they did. The mutation was proved to reach disk by occurrence count on the text being replaced, not by the editor's exit code; the restore is proved by blob-hash equality plus an empty `git diff HEAD`, not by a return code, and it runs from an `EXIT INT TERM` trap with an absolute path. **Where the measurement of N happened, reproducibly** ``` $ cd FIXTURE_DIR # blank starter + ticket object/view/action, my_app_ namespace $ tsx packages/cli/bin/run-dev.js build → Running author-time rules (45)... → Checking capability providers (#3366)... → Collecting package docs (ADR-0046)... 0 collected → Writing artifact... ✓ Build complete (118ms) Data: 2 Objects 6 Fields UI: 1 Views 1 Actions Runtime: 3 plugins ``` The fixture was built in the session scratchpad, outside the repository, and deleted; nothing of it is in this diff. ## Acceptance notes - **Pre-existing red, not this PR's**: `check:cross-package-test-inputs` exits 1 on a tree where `packages/spec/dist` is built. Already filed as #18353 / #18348; no seventh card. - **Noted, not filed** — the `transcript=os-build` block elides two summary rows the current CLI prints for that fixture (`Logic: 0 Flows` and `Security: 0 Positions 0 Permissions`) and the two `field-no-consumers` advisories it raises. Elision is explicitly within what the drift gate's design allows for a hand-authored transcript ("an elided, annotated, sometimes abbreviated paste"), so this is an observation about the page's editorial choices, not a defect, and it is out of this card's declared scope. Carrier: the next PR that re-measures this block, or a `docs-accuracy-audit` pass over `content/docs/deployment/cli.mdx`. Authored by Claude Code in session `session_01DvvamiacK328idtBYJBxV3`. --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <[email protected]>
1 parent 2502b8a commit c2815a2

4 files changed

Lines changed: 250 additions & 3 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"@objectstack/cli": patch
3+
---
4+
5+
`os build` / `os compile` — the package-docs step line is printed **after** the collection it announces and carries the count, so a build that collected nothing no longer reads identically to one that collected four documents (#18432).
6+
7+
```
8+
→ Collecting package docs (ADR-0046)... ← before: every run
9+
→ Collecting package docs (ADR-0046)... 0 collected ← after: this run found none
10+
→ Collecting package docs (ADR-0046)... 4 collected
11+
```
12+
13+
The sentence was unconditional and was emitted **before** `collectAndLintDocs` ran, so the reassurance it offers — the docs step ran, and it found your docs — was true of every run including the ones that found nothing at all. This is the reassurance half of #18170: an exit-0 build carrying the usual progress line is the shape every reader trusts. #18428 landed the audible half, where an uncollected docs directory speaks for itself.
14+
15+
- **The docs step now reports what it collected, not what it attempted.** A project whose `src/docs/` is empty, or whose docs directory moved into a package under an ADR-0130 layout, prints `0 collected` here instead of the same sentence a successful collection prints.
16+
- **The printed number is the artifact's `docs` set**, the same `docsResult.docs` the build writes into `dist/objectstack.json` — pinned from both ends (absent directory, empty directory, two docs) in `packages/cli/test/build-docs-step-count.e2e.test.ts`, because a test that only asserted the sentence was printed passes on the defective tree.
17+
- **`--json` is unchanged**: the line has always lived behind `if (!flags.json)` and the machine face still emits one JSON document with no step text.

content/docs/deployment/cli.mdx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ os compile --json # JSON output for CI pipelines
591591
→ Validating protocol compliance...
592592
→ Running author-time rules (45)...
593593
→ Checking capability providers (#3366)...
594-
→ Collecting package docs (ADR-0046)...
594+
→ Collecting package docs (ADR-0046)... 0 collected
595595
→ Writing artifact...
596596
597597
✓ Build complete (74ms)
@@ -604,7 +604,13 @@ os compile --json # JSON output for CI pipelines
604604
```
605605
606606
Those counts are from a sample project, named in full under [`os info`](#os-info)
607-
below — the same fixture backs both output examples.
607+
below — the same fixture backs both output examples. It ships no `src/docs/`
608+
directory, which is why the docs step reads `0 collected`.
609+
610+
The docs step reports what it collected, not what it attempted — a project whose
611+
`src/docs/` is empty, or whose docs directory moved into a package under an
612+
ADR-0130 layout, prints `0 collected` here instead of the same sentence a
613+
successful collection prints.
608614
609615
The resulting `dist/objectstack.json` is a **portable, self-describing deployment unit** —
610616
you can hand it to `os start` (locally or on a server), publish it to a CDN, or fetch it

packages/cli/src/commands/compile.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,8 +649,17 @@ export default class Compile extends Command {
649649
//
650650
// Not a registry rule: it reads `src/docs/` off disk, and the docs it
651651
// collects there are an INPUT to the artifact, not just a check.
652-
if (!flags.json) printStep('Collecting package docs (ADR-0046)...');
652+
//
653+
// [#18432] The step line is printed AFTER the call and carries the count.
654+
// Printed before it, the line announced a collection the build had
655+
// not performed yet, so a run that collected NOTHING — an empty or
656+
// absent `src/docs/`, or a docs directory that moved into a package
657+
// under an ADR-0130 layout — emitted the same reassuring sentence as
658+
// a run that collected four documents. Reporting the count is what
659+
// makes the two runs distinguishable; the ordering is what makes the
660+
// count available to report.
653661
const docsResult = collectAndLintDocs(absolutePath, result.data as Record<string, unknown>);
662+
if (!flags.json) printStep(`Collecting package docs (ADR-0046)... ${docsResult.docs.length} collected`);
654663
const docErrors = docsResult.issues.filter((i) => i.severity === 'error');
655664
// [#11727] Consumed by BOTH faces — the text block below and the `--json`
656665
// payload. Only the text block read it before, so the advisories were
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* #18432 — `os build`'s package-docs step line was printed BEFORE the call it
5+
* announces, so a build that collected nothing read identically to one that
6+
* collected four documents.
7+
*
8+
* if (!flags.json) printStep('Collecting package docs (ADR-0046)...');
9+
* const docsResult = collectAndLintDocs(...)
10+
*
11+
* The sentence was unconditional and carried no count, so the reassurance it
12+
* offers — "the docs step ran, and it found your docs" — was true of every run
13+
* including the ones that found nothing at all. That is the reassurance half of
14+
* #18170: an exit-0 build with the usual progress line is the shape every
15+
* reader trusts. #18428 landed the audible half (an uncollected docs directory
16+
* now speaks); this is the other half.
17+
*
18+
* ## WHAT THESE PINS ASSERT — the PAIR, not the sentence
19+
*
20+
* A test that only checked "the step line is printed" passes on the defective
21+
* tree and pins nothing: the defective tree printed it unconditionally. So the
22+
* behaviour is pinned from both ends over fixtures that differ in exactly one
23+
* respect — whether `src/docs/` holds anything:
24+
*
25+
* - absent `src/docs/` -> `0 collected`
26+
* - empty `src/docs/` -> `0 collected`
27+
* - two docs -> `2 collected`
28+
*
29+
* Only the count tells the three runs apart, and only a line printed AFTER the
30+
* collection can carry one — which is why this is the assertion that would have
31+
* caught the original defect rather than a restatement of it.
32+
*
33+
* ## The count is the ARTIFACT's docs, not a decoration
34+
*
35+
* `compile.ts` writes `finalBundle.docs = docsResult.docs` from the same value
36+
* it now prints, so each run's printed count is compared against the emitted
37+
* artifact. A number that drifted away from the set it describes would be a
38+
* second silent-reassurance defect wearing the fix's clothes; asserting only
39+
* the text could not see it.
40+
*
41+
* ## `--json` carries no step line, and that is pinned too
42+
*
43+
* The printed line lives behind `if (!flags.json)`. The template literal is new
44+
* and the `--json` face must stay one JSON document, so the machine face is
45+
* asserted to parse and to carry no step text at all.
46+
*
47+
* ALTITUDE: this spawns the CLI (`bin/run-dev.js` through tsx) rather than
48+
* calling a helper, because the defect is in the ORDER of two statements inside
49+
* the command body — there is no seam below the process that can observe it.
50+
* Same spawn shape and the same `childEnv()` as
51+
* `build-json-advisory-parity.e2e.test.ts`, so nothing here is a new pattern.
52+
*/
53+
54+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
55+
import { execFile } from 'node:child_process';
56+
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
57+
import { tmpdir } from 'node:os';
58+
import { join, resolve } from 'node:path';
59+
import { fileURLToPath } from 'node:url';
60+
import { childEnv } from './helpers/serve-process.js';
61+
62+
const HERE = resolve(fileURLToPath(import.meta.url), '..');
63+
const CLI = resolve(HERE, '../bin/run-dev.js');
64+
const TSX = resolve(HERE, '../../../node_modules/.bin/tsx');
65+
66+
interface Run {
67+
code: number;
68+
stdout: string;
69+
stderr: string;
70+
}
71+
72+
function runCli(args: string[], cwd: string): Promise<Run> {
73+
return new Promise((resolvePromise) => {
74+
execFile(
75+
TSX,
76+
[CLI, ...args],
77+
{ cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) },
78+
(err, stdout, stderr) => {
79+
resolvePromise({
80+
code: err ? (typeof (err as { code?: unknown }).code === 'number' ? (err as unknown as { code: number }).code : 1) : 0,
81+
stdout: String(stdout),
82+
stderr: String(stderr),
83+
});
84+
},
85+
);
86+
});
87+
}
88+
89+
/** The step line, matched so the COUNT is captured and nothing else is assumed. */
90+
const STEP_LINE = /Collecting package docs \(ADR-0046\)\.\.\.\s*(\d+) collected/;
91+
92+
/** The sentence with NO count — the pre-fix spelling, asserted absent. */
93+
const COUNTLESS_STEP_LINE = /Collecting package docs \(ADR-0046\)\.\.\.\s*$/m;
94+
95+
/** What the step line reported on this run, or `null` when it was not printed. */
96+
function printedCount(run: Run): number | null {
97+
const m = STEP_LINE.exec(run.stdout);
98+
return m ? Number(m[1]) : null;
99+
}
100+
101+
/** How many docs the emitted artifact actually carries. */
102+
function artifactDocCount(dir: string): number {
103+
const raw = readFileSync(join(dir, 'dist', 'objectstack.json'), 'utf8');
104+
const parsed = JSON.parse(raw) as { docs?: unknown };
105+
return Array.isArray(parsed.docs) ? parsed.docs.length : 0;
106+
}
107+
108+
/**
109+
* A stack that builds cleanly. The namespace is a parameter because ADR-0046
110+
* lints doc names for the package's namespace prefix — a fixture whose docs did
111+
* not carry it would fail the build and every count below would be reading an
112+
* aborted run.
113+
*/
114+
const config = (ns: string) => `
115+
export default {
116+
manifest: { id: 'com.example.${ns}', name: '${ns}', version: '1.0.0', type: 'app', namespace: '${ns}' },
117+
requires: [],
118+
objects: [
119+
{
120+
name: '${ns}_thing',
121+
label: 'Thing',
122+
sharingModel: 'private',
123+
fields: { title: { type: 'text', label: 'Title' } },
124+
},
125+
],
126+
};
127+
`;
128+
129+
const doc = (title: string) => `---
130+
title: ${title}
131+
---
132+
133+
# ${title}
134+
135+
Body text.
136+
`;
137+
138+
/** `absent` has no `src/docs/` at all; `empty` has the directory and no files. */
139+
const dirs: Record<'absent' | 'empty' | 'two', string> = { absent: '', empty: '', two: '' };
140+
let root = '';
141+
142+
beforeAll(() => {
143+
root = mkdtempSync(join(tmpdir(), 'os-docs-step-'));
144+
145+
dirs.absent = join(root, 'absent');
146+
mkdirSync(join(dirs.absent, 'src'), { recursive: true });
147+
writeFileSync(join(dirs.absent, 'objectstack.config.ts'), config('dsabsent'));
148+
149+
dirs.empty = join(root, 'empty');
150+
mkdirSync(join(dirs.empty, 'src', 'docs'), { recursive: true });
151+
writeFileSync(join(dirs.empty, 'objectstack.config.ts'), config('dsempty'));
152+
153+
dirs.two = join(root, 'two');
154+
mkdirSync(join(dirs.two, 'src', 'docs'), { recursive: true });
155+
writeFileSync(join(dirs.two, 'objectstack.config.ts'), config('dstwo'));
156+
writeFileSync(join(dirs.two, 'src', 'docs', 'dstwo_intro.md'), doc('Intro'));
157+
writeFileSync(join(dirs.two, 'src', 'docs', 'dstwo_guide.md'), doc('Guide'));
158+
});
159+
160+
afterAll(() => {
161+
if (root) rmSync(root, { recursive: true, force: true });
162+
});
163+
164+
describe('[#18432] the package-docs step line reports what it collected', () => {
165+
it('ABSENT `src/docs/`: the step line reads `0 collected`, and the artifact carries no docs', async () => {
166+
const run = await runCli(['build'], dirs.absent);
167+
// Asserted first: a non-zero exit would make every claim below vacuous.
168+
expect(run.code, `stdout:\n${run.stdout}\nstderr:\n${run.stderr}`).toBe(0);
169+
170+
expect(printedCount(run)).toBe(0);
171+
expect(artifactDocCount(dirs.absent)).toBe(0);
172+
173+
// The pre-fix spelling — the sentence with nothing after the ellipsis — is
174+
// the shape this card exists to remove. Pinned as absent so a revert of the
175+
// ordering shows up here rather than in a customer's build log.
176+
expect(COUNTLESS_STEP_LINE.test(run.stdout)).toBe(false);
177+
}, 120_000);
178+
179+
it('EMPTY `src/docs/`: also `0 collected` — a present-but-empty directory is not a collection', async () => {
180+
const run = await runCli(['build'], dirs.empty);
181+
expect(run.code, `stdout:\n${run.stdout}\nstderr:\n${run.stderr}`).toBe(0);
182+
183+
expect(printedCount(run)).toBe(0);
184+
expect(artifactDocCount(dirs.empty)).toBe(0);
185+
}, 120_000);
186+
187+
it('TWO docs: the step line reads `2 collected` — the other end of the pair', async () => {
188+
const run = await runCli(['build'], dirs.two);
189+
expect(run.code, `stdout:\n${run.stdout}\nstderr:\n${run.stderr}`).toBe(0);
190+
191+
expect(printedCount(run)).toBe(2);
192+
// The printed number IS the set the artifact receives, not a tally kept
193+
// beside it.
194+
expect(artifactDocCount(dirs.two)).toBe(2);
195+
}, 120_000);
196+
197+
it('the three runs are DISTINGUISHABLE — the defect was that they were not', async () => {
198+
// The whole card in one assertion. On the defective tree all three runs
199+
// printed the identical sentence; the only thing that separates them is the
200+
// count, and the count is only available after the collection has happened.
201+
const [absent, empty, two] = await Promise.all([
202+
runCli(['build'], dirs.absent),
203+
runCli(['build'], dirs.empty),
204+
runCli(['build'], dirs.two),
205+
]);
206+
expect([printedCount(absent), printedCount(empty), printedCount(two)]).toEqual([0, 0, 2]);
207+
}, 180_000);
208+
209+
it('`--json` prints no step line at all and stays one JSON document', async () => {
210+
const run = await runCli(['build', '--json'], dirs.two);
211+
expect(run.code, `stdout:\n${run.stdout}\nstderr:\n${run.stderr}`).toBe(0);
212+
expect(() => JSON.parse(run.stdout)).not.toThrow();
213+
expect(run.stdout).not.toMatch(/Collecting package docs/);
214+
}, 120_000);
215+
});

0 commit comments

Comments
 (0)