Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .changeset/check-schema-positive-marker-and-skip-count.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
---
'@object-ui/cli': minor
---

`objectui check` judges a file's `type` only when the file is recognisable as an ObjectUI schema, and reports how many it declined to judge.

A root `type` was treated as a component key wherever it appeared. `type` heads at
least seven unrelated JSON vocabularies, and the most common of them is
`package.json`'s `"type": "module"` — so the first line a user saw running
`objectui check` in their own project was a warning about their own package
manifest. Measured at this repository's root: 46 warnings, 45 of them
`package.json` (objectui#5127).

A file now enters type judgement only when its root carries a structural key
declared on `BaseSchema` — `children`, `body`, `className`, `placeholder`,
`style`, the `visible`/`hidden`/`disabled` predicate family, `testId`,
`ariaLabel`. Every other root-`type` vocabulary — JSON Schema's `"array"`, an
`.eslintrc.json`'s `"commonjs"`, a package manifest's `"module"` — is simply
never judged. The key set is read out of the node contract rather than invented,
and it is closed: it grows only when `BaseSchema` grows.

A list of filenames to exclude was the alternative and was rejected: it is a
second hand-maintained list of the shape objectui#5115 had just finished
deleting, and it can only ever enumerate the foreign vocabularies someone already
thought of. This is a positive marker instead.

Because the marker narrows what is checked, the command now also reports the
count of files that had a root `type` and no marker, together with the marker
keys that opt one back in. That number is the coverage this gate gives up until
schema files are recognisable, and printing it is what keeps the loss visible
rather than silent. The `.yaml`/`.yml` half of the scan is unchanged — it was
never type-judged, before this change or after it. Exit codes are untouched: a
JSON parse failure remains the only thing that fails the run.

No public `$schema` URL is introduced. An earlier revision also admitted a file
whose root `$schema` had an `objectui.org` host; the maintainer ruled against
minting that identifier (2026-08-20, objectui#5127), so the structural key is the
only marker. Because the matching was host-based rather than literal, that arm
can be added later without invalidating a single file.
47 changes: 41 additions & 6 deletions packages/cli/src/__tests__/check-jsonc-parse.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -194,9 +194,24 @@ describe('objectui check — genuinely malformed JSON still fails the run', () =
});
});

/**
* These fixtures carry `className` — a STRUCTURAL marker key — because
* objectui#5127 gated the warning arm behind a positive ObjectUI marker: a
* bare `{"type": ...}` file is no longer judged at all, so without a marker
* every assertion below would pass for the wrong reason — including the two
* that assert SILENCE, which would then be measuring nothing.
*
* An earlier revision declared a `$schema` URL here instead. The maintainer's
* 2026-08-20 ruling removed that arm, so those fixtures would have been
* admitted by nothing at all while every assertion in this section stayed
* green. Each silence assertion below is now paired with a counter-probe that
* proves judgement actually ran. The gate itself is pinned in
* `check-schema-marker.test.ts`; this section is about the warning arm the gate
* admits files to.
*/
describe('objectui check — the unknown-type warning arm is untouched (objectui#5127)', () => {
it('still warns for an unrecognised root type, and still does not fail the run', async () => {
writeFile('bogus.json', '{"type":"totally-made-up-xyz"}');
writeFile('bogus.json', '{"className":"p-0","type":"totally-made-up-xyz"}');

await check(cwd);

Expand All@@ -210,7 +225,10 @@ describe('objectui check — the unknown-type warning arm is untouched (objectui
// Files that previously died at the parse step now reach the type check —
// the warning arm's reach grows, but its verdict and its exit-code
// neutrality are unchanged.
writeFile('commented.json', '{\n // a comment\n "type": "totally-made-up-xyz",\n}\n');
writeFile(
'commented.json',
'{\n // a comment\n "className": "p-0",\n "type": "totally-made-up-xyz",\n}\n'
);

await check(cwd);

Expand All@@ -219,23 +237,40 @@ describe('objectui check — the unknown-type warning arm is untouched (objectui
});

it('stays silent for a registered type', async () => {
writeFile('grid.json', '{"type":"object-grid","objectApiName":"account"}');
writeFile(
'grid.json',
'{"className":"p-0","type":"object-grid","objectApiName":"account"}'
);
// Counter-probe: same marker, same admission path, a type nothing
// registers. Its warning is what makes `grid.json`'s silence a verdict
// instead of a file the gate never let through.
writeFile('probe.json', '{"className":"p-0","type":"totally-made-up-xyz"}');

await check(cwd);

expect(unknownTypeWarnings()).toEqual([]);
expect(unknownTypeWarnings()).toEqual([
expect.stringContaining('Unknown schema type "totally-made-up-xyz" in probe.json'),
]);
expect(exitCodes).toEqual([]);
});

it('does not run the type check on a file that failed to parse', async () => {
// A parse failure short-circuits before the schema arm, exactly as the
// thrown `JSON.parse` used to.
writeFile('broken-typed.json', '{ "type": "totally-made-up-xyz", }}');
writeFile('broken-typed.json', '{ "className": "p-0", "type": "totally-made-up-xyz", }}');
// Counter-probe: byte-for-byte the same document minus the stray brace.
// It warns, so the silence about `broken-typed.json` is attributable to
// the parse short-circuit — the only difference between the two files —
// and not to a marker the gate declined.
writeFile('parsed-typed.json', '{ "className": "p-0", "type": "totally-made-up-xyz" }');

await check(cwd);

expect(parseErrorLines()).toHaveLength(1);
expect(unknownTypeWarnings()).toEqual([]);
expect(parseErrorLines()[0]).toContain('broken-typed.json');
expect(unknownTypeWarnings()).toEqual([
expect.stringContaining('Unknown schema type "totally-made-up-xyz" in parsed-typed.json'),
]);
expect(exitCodes).toEqual([1]);
});
});
39 changes: 36 additions & 3 deletions packages/cli/src/__tests__/check-known-types.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,8 +31,29 @@ let cwd: string;
let lines: string[];
let restoreLog: () => void;

function writeSchema(name: string, body: unknown): void {
writeFileSync(join(cwd, name), JSON.stringify(body));
/**
* Every fixture carries `className` — a STRUCTURAL marker key — because
* objectui#5127 gated type judgement behind a positive ObjectUI marker: a bare
* `{"type": ...}` file is not judged at all now. Without a marker the warning
* assertions below would fail and — worse — the SILENCE assertion would keep
* passing while measuring nothing, which is the shape of a test that survives
* the deletion of the feature it covers.
*
* That is not assumed here, it is measured: dropping the injection from this
* helper turns the warning tests red and leaves the silence test GREEN, which
* is precisely why that test carries its own counter-probe below rather than
* trusting this comment.
*
* `className` is the least semantically loaded key in the marker set — it says
* nothing about a node's children, visibility or interaction state — so it
* perturbs no fixture's meaning. An earlier revision declared a `$schema` URL
* instead; the maintainer's 2026-08-20 ruling removed that arm, and a marker
* that names a way in the build no longer honours is a fixture that admits
* nothing. The gate itself is pinned separately, in
* `check-schema-marker.test.ts`; this file is about the derived key set.
*/
function writeSchema(name: string, body: Record<string, unknown>): void {
writeFileSync(join(cwd, name), JSON.stringify({ className: 'p-0', ...body }));
}

/** Warnings only, with the ANSI colouring chalk may add stripped off. */
Expand DownExpand Up@@ -83,8 +104,16 @@ describe('objectui check — unknown schema types', () => {
writeSchema('grid.json', { type: 'object-grid', objectApiName: 'account' });
writeSchema('ns-grid.json', { type: 'view:grid', objectApiName: 'account' });
writeSchema('gallery-ok.json', { type: 'object-gallery' });
// Counter-probe. Written by the same helper, so it carries the same marker
// and travels the same admission path; its warning is what makes the three
// silences above VERDICTS rather than a judgement that never ran. Without
// it this assertion holds equally well when nothing is judged at all —
// measured, and the reason it is here (objectui#5127).
writeSchema('probe.json', { type: 'totally-made-up-xyz' });
await check(cwd);
expect(unknownTypeWarnings()).toEqual([]);
expect(unknownTypeWarnings()).toEqual([
expect.stringContaining('Unknown schema type "totally-made-up-xyz" in probe.json'),
]);
});

it('still warns for a type nothing registers', async () => {
Expand All@@ -99,6 +128,10 @@ describe('objectui check — unknown schema types', () => {
// list would otherwise be free to become a breaking change by accident.
writeSchema('bogus.json', { type: 'totally-made-up-xyz' });
await check(cwd);
// Both halves of this test's own sentence. Asserting only the exit
// neutrality would keep passing if the type were never REPORTED either,
// which is the state a lost marker puts this fixture in.
expect(unknownTypeWarnings()).toHaveLength(1);
expect(lines.some((l) => l.includes('All checks passed'))).toBe(true);
});
});
Loading
Loading