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
7 changes: 7 additions & 0 deletions .changeset/cli-help-and-inspect-format.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'onsystem': patch
---

`onsystem --help` now says what the CLI does. `--config` names `onsystem.config.mts`, which is read along with the other four; `--tokens` names a DESIGN.md, which is a token file like a DTCG `.json` or a stylesheet; and `--format` lists `inspect: pretty | json`, which worked but was listed for `check` and `export` only. The CLI reference (`docs/cli.md`) already had all three.

`onsystem inspect --format xml`, or a typo, now exits 2 with one line, as `check` and `export` do for a format they do not take. It printed the pretty output and exited 0, so a script that asked for JSON and got text read it as the design system having nothing in it. The check runs before the project is loaded, so it covers a monorepo root too.
2 changes: 1 addition & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ It exits with:
| Option | What it does |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--explain` | Adds every candidate zero config looked at and why it was taken or not, the config it resolved to, the tokens that did not resolve and the components whose props did not |
| `--format <format>` | `json` prints what it read as JSON, for scripts |
| `--format <format>` | `pretty` (the default) or `json`, which prints what it read as JSON, for scripts; another value exits 2 |

## `export`

Expand Down
10 changes: 8 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,16 @@ with a config, components.json or package.json, and inspect lists every project.

Options
--root <dir> Project root (default: the config file's directory, or cwd)
--config <file> Config file (default: onsystem.config.{json,ts,mjs,js})
--config <file> Config file (default: onsystem.config.{json,ts,mts,js,mjs})
--components <glob> Component sources, repeatable (default: found through
components.json or a workspace package, else
components/ui/**/*.{tsx,jsx} and src/components/ui/...)
--tokens <file> Token file (DTCG .json or CSS), repeatable
--tokens <file> Token file (DTCG .json, CSS or DESIGN.md), repeatable
--docs <glob> Component docs (Markdown/MDX), repeatable
--no-cache Ignore the on-disk extraction cache
--no-watch serve: do not reload when files change
--format <format> check: pretty | json | github | sarif (default: pretty);
inspect: pretty | json (default: pretty);
export: dtcg | design-md
--out <file> export: write to this file instead of stdout
--max-warnings <n> check: exit 1 when there are more than n warnings
Expand Down Expand Up @@ -1114,6 +1115,11 @@ async function inspect(
values: { cache: boolean; format?: string | undefined; explain: boolean },
io: Io,
): Promise<number> {
const format = values.format ?? 'pretty';
if (!['pretty', 'json'].includes(format)) {
io.stderr(`inspect: unknown --format "${format}" (pretty, json)`);
return 2;
}
const target = await loadTarget(configOptions);
const { config } = target;
warnDeprecated(config.deprecations, 'pretty', io);
Expand Down
14 changes: 5 additions & 9 deletions test/cli-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@ const read = (file: string) =>
const COMMANDS = ['serve', 'check', 'inspect', 'export', 'init'] as const;
/** The section of docs/cli.md for options that do not name a command in `--help`. */
const SHARED = 'Options for every command';
/**
* Options the CLI takes for a command whose `--help` text does not name it: `inspect --format
* json`. The test below runs each one, so the row stays true.
*/
const BEYOND_HELP: Record<string, string[]> = { '`inspect`': ['--format'] };

interface Option {
/** The flags, such as `-y` and `--yes`. */
flags: string[];
Expand Down Expand Up @@ -166,6 +160,7 @@ describe('the CLI reference (docs/cli.md)', () => {
});
expect(options.find((o) => o.flags.includes('--format'))?.commands).toEqual([
'check',
'inspect',
'export',
]);
});
Expand Down Expand Up @@ -208,19 +203,20 @@ describe('the CLI reference (docs/cli.md)', () => {
const expected = option?.commands.length
? option.commands.map((c) => `\`${c}\``)
: [SHARED];
if (row.flags.some((flag) => BEYOND_HELP[heading]?.includes(flag))) continue;
expect(expected, `${row.flags.join(', ')} under "${heading}"`).toContain(heading);
}
}
});

it('runs inspect --format json, which --help does not list', async () => {
it('runs inspect --format json, the value its row names', async () => {
const out: string[] = [];
const io: Io = { cwd: DEMO_ROOT, color: false, stdout: (t) => out.push(t), stderr: () => {} };
expect(await main(['inspect', '--format', 'json', '--no-cache'], io)).toBe(0);
const model = JSON.parse(out.join('\n')) as { components: { name: string }[] };
expect(model.components.map((c) => c.name)).toContain('Button');
expect(section(cli, '`inspect`')).toMatch(/^\| `--format <format>` +\| `json` /m);
expect(section(cli, '`inspect`')).toMatch(
/^\| `--format <format>` +\| `pretty` \(the default\) or `json`/m,
);
});

it('names every config file name the CLI looks for, as docs/configuration.md does', () => {
Expand Down
23 changes: 23 additions & 0 deletions test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
import { describe, expect, it } from 'vitest';

import { main, type Io } from '../src/cli.js';
import { CONFIG_FILES } from '../src/config.js';
import { VERSION } from '../src/version.js';
import { DEMO_ROOT, fixture } from './helpers.js';

Expand Down Expand Up @@ -169,6 +170,7 @@ describe('onsystem check', () => {
expect((await run(['check'])).code).toBe(2);
expect((await run(['check', 'nope/**/*.tsx'])).stderr).toContain('no files match');
expect((await run(['check', 'x.tsx', '--format', 'xml'])).code).toBe(2);
expect((await run(['inspect', '--format', 'xml'])).code).toBe(2);
expect((await run(['frobnicate'])).code).toBe(2);
expect((await run(['--bogus'])).code).toBe(2);
});
Expand Down Expand Up @@ -869,6 +871,27 @@ describe('onsystem inspect, --help, --version', () => {
expect((await run(['--help'])).stdout).toContain('onsystem check <paths...>');
expect((await run(['-v'])).stdout).toBe(VERSION);
});

it('names every config file extension in --help', async () => {
const { stdout } = await run(['--help']);
const listed = /onsystem\.config\.\{([^}]+)\}/.exec(stdout)?.[1]?.split(',');
const extensions = CONFIG_FILES.map((name) => name.slice('onsystem.config.'.length));
expect([...(listed ?? [])].sort()).toEqual([...extensions].sort());
});

it('rejects an unknown --format, in a project and at a monorepo root', async () => {
const monorepo = fixture({
'pnpm-workspace.yaml': 'packages:\n - apps/*\n',
'package.json': '{ "name": "acme", "private": true }',
});
appIn(monorepo);
for (const cwd of [DEMO_ROOT, monorepo]) {
const { code, stdout, stderr } = await run(['inspect', '--format', 'xml', '--no-cache'], cwd);
expect(code).toBe(2);
expect(stderr).toBe('inspect: unknown --format "xml" (pretty, json)');
expect(stdout).toBe('');
}
});
});

describe('onsystem with no command', () => {
Expand Down
Loading