Skip to content
This repository was archived by the owner on Oct 26, 2026. It is now read-only.

Commit 37268aa

Browse files
fix(cli): retire create, lint, test and studio, commands that could only fail; correct four residues to what the commands do (objectui#11500) (#11508)
Fixes #11500 Clause-②: yes ## What Triage ruled (comment `5960445419`) that four commands of the published `objectui` bin retire, and that four noted residues ride in the same PR as one-line corrections, each to what the command does. This PR: - removes `create`, `lint`, `test` and `studio` from `packages/cli/src/cli.ts`, and deletes `commands/create-plugin.ts`, `commands/lint.ts`, `commands/test.ts` and `commands/studio.ts`. `plugin` was the only type `create` accepted, so `create` goes whole. Nothing else imported the four modules, and they read nothing under `utils/`, so no other code goes. `SCAFFOLD_DEV_DEPENDENCIES` names neither ESLint nor Vitest, so it is untouched; - corrects the four residues (`doctor`, `serve` in the docs, `analyze` without `dist/`, `init` with an unknown template); - drops the four commands' sections from `content/docs/utilities/cli.mdx` and `packages/cli/README.md`, and names the plugin initializer in `cli.mdx`; - adds the pins in `packages/cli/src/__tests__/cli-bin.test.ts`, all run through the built `dist/cli.js`; - adds `.changeset/11500-retire-cli-create-lint-test-studio.md` (`@object-ui/cli` `minor`, BREAKING banner, one FROM to TO line per retired command, one line per residue, a Clause-② line). No refusal code was needed. On the BASE build, commander already refuses any command the program does not declare: `objectui bogus` exits 1 with `error: unknown command 'bogus'`. ## Measured: BASE `fcdc8ec91` build against the HEAD build One run per row, each in a fresh temp directory, on the built bin. Placeholders are spelled in capitals, because GitHub strips angle-bracket shapes from a saved body. | Run | BASE | HEAD | |---|---|---| | `create plugin x` | exit 1, `Cannot find module` on `create-plugin/dist/index.js` at the worktree ROOT (one directory too high); nothing written | exit 1, `error: unknown command 'create'`; nothing written | | `lint` | exit 1, "No Object UI application found" | exit 1, `error: unknown command 'lint'` | | `test` | exit 1, the same precondition error | exit 1, `error: unknown command 'test'` | | `studio` | run from a temp dir, it found this checkout's `apps/console` through its own `dist` path and started that app's Vite dev server; `timeout` stopped it and nothing was left running | exit 1, `error: unknown command 'studio'` | | `analyze`, no `dist/` | a warning, then "Analysis complete", exit 0 | `Error: No dist/ directory to analyze. Run objectui build first.` on stderr, exit 1 | | `init app -t nope` | exit 1, `Unknown template: nope`, and an empty `app/` left behind | exit 1, the same error, nothing written | | `doctor --help` | "Diagnose and fix common issues". `doctor` writes nothing and prints no Node line | "Diagnose common project setup issues (reports only, changes nothing)" | ## The plugin initializer, measured before it was written `packages/create-plugin/package.json` names `@object-ui/create-plugin` with one bin, `create-plugin`. It is not `private`, it is in the release's `fixed` group, and npm answers `17.6.0` for it. The bin takes `[plugin-name]`, `-d, --description` and `-a, --author`. In a temp directory, `npm create @object-ui/plugin probe-widget` installed `@object-ui/create-plugin@17.6.0` and ran `create-plugin probe-widget`. Under a pseudo-terminal, taking each prompt's default, it wrote `packages/plugin-probe-widget/` (8 files) and exited 0. So `cli.mdx` now carries a short "Scaffolding a plugin" section with `npm create @object-ui/plugin my-awesome-widget` and a link to the Create Plugin page, which says where to run it. No dependency was added. ## Contract notes - **The accept set narrows (Clause-② yes).** The published `objectui` bin refuses what it used to accept: - FROM `objectui create plugin NAME` (exit 1, generator not found) TO exit 1 with `error: unknown command 'create'`, nothing written. The replacement is `npm create @object-ui/plugin NAME`. - FROM `objectui lint` and `objectui test` (exit 1 on every run, a setup error reported as findings) TO the unknown-command error. The replacement is the project's own ESLint and Vitest. - FROM `objectui studio` TO the unknown-command error. There is no published replacement; contributors run `pnpm --filter console dev` in the monorepo. - FROM `objectui analyze` with no `dist/` exiting 0 TO exiting 1. There is no alias, no deprecation window and no silent accept. - **Unchanged.** `init` with a known template writes the same file map (pinned byte for byte by "writes exactly the file map buildInitFiles returns, byte for byte"). `analyze` over a `dist/` prints the same report. `doctor`'s checks and output are untouched; only its help line moves. No exported symbol of `@object-ui/cli` moves: `src/index.ts` exports `serve` and `init`, and `init`'s signature is unchanged. - **`validate` and `check` are not edited.** Draft PR objectui#11069 owns them. In the README, only `studio` and `create plugin NAME` leave the heading that also names `check`, `doctor` and `analyze`. Four untouched lines separate that hunk from objectui#11069's `validate` hunk. In `cli.mdx`, no hunk touches the `validate` or `check` sections. - **Two edits beyond the sections**, both naming retired commands: the README tagline and the `cli.mdx` intro sentence each listed lint and test. Both now say scaffold, develop, build and validate, as the package's own `description` already does. ## Pins All in `cli-bin.test.ts`, run through the built `dist/cli.js`: 1. The objectui#11496 enumeration pin, "lists exactly the declared commands, no more and no fewer": `SUBCOMMANDS` loses `create`, `lint`, `test` and `studio`. 2. "the retired create, lint, test and studio commands are refused (objectui#11500)": four rows. Each one exits non-zero, its stderr contains `unknown command 'NAME'`, and the temp directory stays empty. The control is `g page Probe` in the same directory, which writes `pages/probe.json`. These runs empty `PATH`. Run from inside a checkout, the BASE `studio` started the console dev server. With no `PATH`, a retired command that came back fails at its first external tool (at BASE, `/bin/sh: 1: pnpm: not found`), and the stderr assertion still turns red. 3. "doctor --help does not claim to fix anything (objectui#11500)": it asserts the kind of claim, not the wording, as the existing `check --help` pin does. 4. "exits non-zero without dist/, and claims no analysis (objectui#11500)". 5. "refuses an unknown template before creating the directory (objectui#11500)". The control is `init probe-app -t simple` in the same directory, which writes `probe-app/app.json`. 6. The `serve` row of the flag-contract table gains `--no-open`, a docs-alignment pin that is green at BASE by design. The `lint` and `test` rows go. ## Reverse verification Predictions were written down before each leg. In each leg, the mutation is a `git checkout COMMIT -- PATH`, proven on disk by blob hash. The CLI is then rebuilt, a marker is read from `dist/`, and `cli-bin.test.ts` runs. An EXIT/INT/TERM trap restores from HEAD and proves the restore by blob hash, an empty `git diff HEAD` and an empty porcelain. - **BASE leg.** `cli.ts`, `analyze.ts`, `init.ts` and the four deleted modules at `fcdc8ec91`, with the pins at HEAD `a20223743`. Blobs on disk equalled BASE: `004165f17300`, `63659c0b30c9`, `ad7bde24bfcc`, `e8438188bbc5`, `613605f90d07`, `48db26298d9a`, `5039e918e38e`. `dist/` carried the studio marker and the old doctor text, twice each. Predicted 8 red. Observed `Tests 8 failed | 40 passed (48)`: the enumeration pin, the four refusals, doctor, analyze and init. The `serve` row stayed green, as predicted. - **Restore-one leg.** HEAD plus `studio` alone: its module from BASE, and its import and command block re-inserted into `cli.ts`, each counted once on disk. Predicted 2 red. Observed `Tests 2 failed | 46 passed (48)`: the enumeration pin and the `studio` refusal. The six other new pins read green here, which also shows `dist/` was rebuilt from this leg's sources. - **Restore.** The tree equals HEAD `a20223743` (blobs `ec9d879fdc20`, `fb81635c05cb`, `ab62a52b5efd`; the four modules absent), and the CLI was rebuilt. The four BASE markers count 0 in `dist/`, and HEAD's doctor text counts 2, as a positive control. ## Local gates, at HEAD `a20223743` - `pnpm exec vitest run` over `packages/cli/src/__tests__/` in full, plus every test that names a retired command, `create-plugin`, `cli.mdx` or the CLI README, plus the `scripts/__tests__` readers of the CLI tree: `Test Files 39 passed (39)`, `Tests 658 passed (658)`. The 14 files beyond the CLI suite: `toc-anchor-parity.test.tsx`, `nested-page-children-11223.test.ts`, both `create-plugin` suites, and `spawned-build-vitest-env-8598`, `check-doc-snippet-emitted-census`, `doc-version-claims`, `package-files-exist`, `one-authority-per-exported-name-6273`, `check-published-tsconfig-tooling-exclude`, `body-dialect-census`, `body-dialect-producer-scan` and `check-test-path-roots`. Four other `scripts/__tests__` hits on the names `lint` and `test` are file stems and workflow job names, not the CLI. - `pnpm --filter @object-ui/cli type-check` exit 0 (its `tsconfig` includes `src/**/*`, and `--listFiles` lists `cli-bin.test.ts`); `pnpm --filter @object-ui/cli build` exit 0. - `docs:check-links` "Links are valid across 17 scan roots." · `check:doc-fences` OK · `check:readme-exports` OK · `markdown-test-inputs.mjs --audit` "115 candidate test files, all adjudicated" · `check:control-bytes` OK · `check:new-line-citations` "0 new citation(s)" · `check-changeset-presence.mjs` "8 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)" · `changeset:check` "No changeset declares a `major` bump." · `check:changeset-claims` report-only, see Acceptance notes · `check:pending-changeset-literals` "No test source names a pending changeset." · `check:test-path-roots`, `check:unused-deps`, `check:phantom-deps`, `check:doc-types`, `check:doc-example-ids`, `check:shell-escape-residue`, `check:unreferenced-sources` and `check:vi-mock-specifiers` OK · `check:doc-snippets` "Every covered documentation snippet compiles against the built types." · `check:doc-examples` OK · `pnpm check` "287 validated … 0 did not validate". The README and snippet gates ran after a scoped build of their package closure; before it they answered PRECONDITION NOT MET, which is not a verdict. - ESLint over all of `packages/cli/src`, the population CI's `eslint src` lints for this package: 47 files in the JSON output, equal to the 47 tracked `.ts` files there. 0 errors. 5 warnings, all in three files this diff does not touch. Type-aware linting is off (`tseslint.configs.recommended`, no `projectService`), and no rule in `eslint-rules/` reads the disk, so the verdicts of untouched files cannot move. - `scripts/check-governed-queue-guard.mjs --test` over the 11 paths: NOT GOVERNED. The full `pnpm test` and the repo-wide lint are left to CI. ## Acceptance notes - **Governed rows this PR leaves to the `skills` lane.** `skills/objectui/guides/project-setup.md`'s CLI table still lists the retired commands. These rows are cited by content, because a line address in a body is the banned cross-file form: - the row reading `objectui studio` / "Visual UI editor"; carrier: seat → objectui#11503 - the row reading `objectui lint` / "Lint generated app code (ESLint)"; carrier: seat → objectui#11503 - the row reading `objectui test` / "Run app tests (Vitest)"; carrier: seat → objectui#11503 - the row reading `objectui create plugin` / "Scaffold a new plugin"; carrier: seat → objectui#11503 - **Residue sizes.** `analyze`: two lines become one `throw`. `doctor`: one help line in `cli.ts` and one sentence in `cli.mdx`. `serve`: one docs line. `init`: one statement hoisted, which is one line added (`const files = buildInitFiles(…)` above the `mkdirSync`) and the loop header collapsed from three lines to one, plus a corrected comment. The old comment's "Unknown templates still throw here, before the first write" was false, because the directory was created first. - **What `analyze` prints without `dist/`.** Its two header lines still print on stdout before the error. The error is on stderr, and "Analysis complete" no longer prints. - **`check:changeset-claims`** flagged two pending changesets naming files this PR touches. Both were re-read, and both stay true. `7658-toc-code-span-anchor-parity.md` describes, in the past tense, headings its own change fixed. `7694-chart-series-chart-type-alias-refusal.md` names `objectui validate` in `cli.ts`, which this PR does not touch. - **Observed, not filed** (no declared contract is violated; carrier: none): - `doctor` exits 0 even with an error-level finding. At BASE, in an empty directory, it printed `x package.json not found` and `Found 2 issue(s).`, and exited 0. Nothing documents its exit code. - The published `@object-ui/create-plugin@17.6.0`, run with a non-TTY stdin, prints its second prompt and exits 0 with nothing written. objectui#8786's fix is on `main` but unreleased: the published bin contains no `isTTY` check. It ships with the next release. - `packages/plugin-markdown/src/toc-anchor-parity.test.tsx`'s `LIVE_SHAPES` still names the `objectui add COMPONENT` heading as live, with `where` naming `cli.mdx` and the README. Since objectui#11496 that heading exists in neither file, and after this PR neither does `objectui create plugin NAME`. The test hard-codes the heading strings and reads no file, so it stays green; only its `where` is stale. Implemented by the dispatched `domain:ui` seat 1 dev agent; PM session `https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37`. --- _Generated by [Claude Code](https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 58b480e commit 37268aa

11 files changed

Lines changed: 140 additions & 406 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@object-ui/cli': minor
3+
---
4+
5+
**BREAKING (`@object-ui/cli`):** four commands are retired: `objectui create`, `objectui lint`, `objectui test` and `objectui studio`. There is no alias window and no placeholder: the CLI now refuses each as an unknown command (objectui#11500).
6+
7+
(The bump is `minor` by this repo's release model: objectui's major follows the `@objectstack` family major, and its own breaking changes ship as `minor` with the breaking semantics stated here.)
8+
9+
- FROM `objectui create plugin NAME` TO `npm create @object-ui/plugin NAME`, the package's own initializer (`@object-ui/create-plugin`). `plugin` was the only type `create` accepted, so `create` itself goes.
10+
- FROM `objectui lint` and `objectui test` TO your project's own ESLint and Vitest (or whichever linter and test runner it declares), run directly.
11+
- FROM `objectui studio` TO nothing published. Contributors to the objectui monorepo run `pnpm --filter console dev`.
12+
13+
**Why.** Each of the four could only fail. `create plugin` looked for its generator one directory too high, so it exited 1 with a "Cannot find module" error in a checkout and in an installed copy alike, and wrote nothing. `lint` and `test` ran ESLint and Vitest inside the temp app `objectui dev` generates, which declares neither tool and has no lint config and no tests, so they failed on every run, and they reported the setup error as lint findings or failed tests. `studio` started this monorepo's console app rather than the local project its documentation promised, and outside the monorepo it exited 1 with "Console app not found in workspace".
14+
15+
**Corrected alongside, each to what the command does:**
16+
17+
- `objectui analyze` with no `dist/` directory now exits non-zero with an error naming the missing `dist/`. It used to print a warning, then "Analysis complete", and exit 0.
18+
- `objectui init NAME --template UNKNOWN` now refuses the template before it creates the `NAME` directory. It used to exit 1 and leave an empty `NAME` directory behind, which the next `init NAME` then refused as already existing.
19+
- `objectui doctor`'s help no longer says it fixes issues. It reports and changes no file. The CLI docs no longer promise a Node version check it never ran.
20+
- The CLI docs no longer say `objectui serve` lacks `--no-open`. It has always accepted it, as `dev` does.
21+
22+
**What changes at the command line.** `objectui create plugin my-widget`, `objectui lint`, `objectui test` and `objectui studio` now exit non-zero with commander's unknown-command error and write nothing. `objectui --help` no longer lists them. The CLI docs and the package README drop their sections, and the docs name the plugin initializer.
23+
24+
**Clause-②: yes.** The published CLI's accept set narrows: four commands it used to accept are now refused, and `analyze` without `dist/` now exits non-zero. No exported symbol moves; none of these commands is part of the package's exports.

‎content/docs/utilities/cli.mdx‎

Lines changed: 16 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ description: "Standalone command-line interface for building ObjectUI applicatio
66
# ObjectUI CLI
77

88
`@object-ui/cli` is a standalone command-line tool for scaffolding, developing,
9-
building, linting, testing, and validating ObjectUI applications driven by
10-
JSON or YAML schemas.
9+
building, and validating ObjectUI applications driven by JSON or YAML schemas.
1110

1211
It is **not** an oclif plugin — it ships its own `objectui` binary just like
1312
`vite`, `next`, or `astro`.
@@ -73,7 +72,7 @@ objectui dev --port 8080 --no-open
7372
### `objectui serve [schema]`
7473

7574
Legacy alias of `dev`, kept for older scripts. Same positional argument and
76-
`-p`/`-h` flags as `dev` (no `--no-open`).
75+
flags as `dev`, `--no-open` included.
7776

7877
### `objectui build [schema]`
7978

@@ -105,34 +104,6 @@ objectui start --port 8080 --dir build
105104
| `-h, --host <host>` | `0.0.0.0` | Host to bind |
106105
| `-d, --dir <dir>` | `dist` | Directory to serve |
107106

108-
### `objectui lint`
109-
110-
Lint the generated application code with ESLint.
111-
112-
```bash
113-
objectui lint --fix
114-
```
115-
116-
| Flag | Description |
117-
|---|---|
118-
| `--fix` | Automatically fix issues |
119-
120-
### `objectui test`
121-
122-
Run the application's tests with Vitest.
123-
124-
```bash
125-
objectui test --watch
126-
objectui test --coverage
127-
objectui test --ui
128-
```
129-
130-
| Flag | Description |
131-
|---|---|
132-
| `-w, --watch` | Watch mode |
133-
| `-c, --coverage` | Generate a coverage report |
134-
| `--ui` | Vitest UI |
135-
136107
### `objectui generate <type> <name>` (alias `g`)
137108

138109
Generate new resources.
@@ -278,26 +249,28 @@ failing exit code on a document that does not validate.
278249

279250
### `objectui doctor`
280251

281-
Diagnose common environment issues (Node version, deps, config sanity).
252+
Diagnose common project setup issues: `package.json` and its React and
253+
Tailwind dependencies, the Tailwind PostCSS plugin and CSS entry, and
254+
`tsconfig.json`. It reports only and changes no file.
282255

283-
### `objectui studio`
256+
### `objectui analyze`
284257

285-
Launch the visual designer (`@object-ui/plugin-designer`) against the local
286-
project.
258+
Analyze the built application's bundle: the total, JavaScript and CSS sizes of
259+
`dist/` in the current directory (where `objectui build` writes by default),
260+
and its ten largest files. No flags. With no `dist/` to analyze, it exits
261+
non-zero.
287262

288-
### `objectui create plugin <name>`
263+
## Scaffolding a plugin
289264

290-
Scaffold a new ObjectUI plugin via `@object-ui/create-plugin`.
265+
A plugin is scaffolded by its own initializer, `@object-ui/create-plugin`, not
266+
by this CLI:
291267

292268
```bash
293-
objectui create plugin my-awesome-widget
269+
npm create @object-ui/plugin my-awesome-widget
294270
```
295271

296-
### `objectui analyze`
297-
298-
Analyze the built application's bundle: the total, JavaScript and CSS sizes of
299-
`dist/` in the current directory (where `objectui build` writes by default),
300-
and its ten largest files. No flags.
272+
See [Create Plugin](/docs/utilities/create-plugin) for where to run it and what
273+
it writes.
301274

302275
## Schema file format
303276

‎packages/cli/README.md‎

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# @object-ui/cli
22

3-
> Standalone CLI for **Object UI** — scaffold, develop, build, lint, test and validate
3+
> Standalone CLI for **Object UI** — scaffold, develop, build and validate
44
> JSON/YAML schema-driven applications.
55
66
```bash
@@ -87,24 +87,6 @@ objectui start --port 8080 --dir build
8787

8888
Legacy alias of `dev` (kept for older scripts).
8989

90-
### `objectui lint`
91-
92-
Lint the generated application code via ESLint.
93-
94-
```bash
95-
objectui lint --fix
96-
```
97-
98-
### `objectui test`
99-
100-
Run the application's tests via Vitest.
101-
102-
```bash
103-
objectui test --watch
104-
objectui test --coverage
105-
objectui test --ui
106-
```
107-
10890
### `objectui generate <type> <name>` (alias `g`)
10991

11092
Generate new resources (`resource`/`object`, `page`, `plugin`).
@@ -120,7 +102,7 @@ type matches, the report names the nearest few of the accepted values instead of
120102
listing all of them. See the
121103
[docs](https://www.objectui.org/docs/utilities/cli) for worked output.
122104

123-
### `objectui check`, `objectui doctor`, `objectui studio`, `objectui analyze`, `objectui create plugin <name>`
105+
### `objectui check`, `objectui doctor`, `objectui analyze`
124106

125107
Utility commands — see `objectui --help`.
126108

‎packages/cli/src/__tests__/cli-bin.test.ts‎

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,23 @@ const PKG_PATH = resolve(__dirname, '../../package.json');
2323

2424
/**
2525
* The commands the CLI declares. The enumeration pin below holds this list
26-
* equal to the built CLI's `--help`, both directions (objectui#11496). `add` is
27-
* deliberately absent: it was retired, and the bin refuses it as an unknown
28-
* command. The CLI docs give each of these a `### objectui NAME` heading; keep
29-
* them in step by hand, because nothing in this file reads the docs.
26+
* equal to the built CLI's `--help`, both directions (objectui#11496). `add`
27+
* (objectui#11496) and `create`, `lint`, `test` and `studio` (objectui#11500)
28+
* are deliberately absent: they were retired, and the bin refuses each as an
29+
* unknown command. The CLI docs give each of these a `### objectui NAME`
30+
* heading; keep them in step by hand, because nothing in this file reads the
31+
* docs.
3032
*/
3133
const SUBCOMMANDS = [
3234
'serve',
3335
'dev',
3436
'build',
3537
'start',
3638
'init',
37-
'lint',
38-
'test',
3939
'generate',
4040
'doctor',
41-
'studio',
4241
'check',
4342
'validate',
44-
'create',
4543
'analyze',
4644
] as const;
4745

@@ -237,15 +235,26 @@ describe('@object-ui/cli bin', () => {
237235
});
238236
});
239237

238+
// objectui#11500. `doctor` was described as "Diagnose and fix common issues",
239+
// and it fixes nothing: it reads the project and reports. Asserts the kind of
240+
// claim, not the wording.
241+
it('doctor --help does not claim to fix anything (objectui#11500)', () => {
242+
const res = run(['doctor', '--help']);
243+
expect(res.code, res.stderr || res.stdout).toBe(0);
244+
// Commander prints `Usage: …`, a blank line, then the description paragraph.
245+
const description = res.stdout.split(/\n\s*\n/)[1] ?? '';
246+
// Lit control: the paragraph that was read is not empty.
247+
expect(description.trim()).not.toBe('');
248+
expect(description).not.toMatch(/\bfix/i);
249+
});
250+
240251
describe('flag contracts (locked-in by docs)', () => {
241252
const cases: Array<[string, RegExp[]]> = [
242253
['dev', [/-p, --port <port>/, /-h, --host <host>/, /--no-open/]],
243-
['serve', [/-p, --port <port>/, /-h, --host <host>/]],
254+
['serve', [/-p, --port <port>/, /-h, --host <host>/, /--no-open/]],
244255
['build', [/-o, --out-dir <dir>/, /--clean/]],
245256
['start', [/-p, --port <port>/, /-h, --host <host>/, /-d, --dir <dir>/]],
246257
['init', [/-t, --template <template>/]],
247-
['lint', [/--fix/]],
248-
['test', [/-w, --watch/, /-c, --coverage/, /--ui/]],
249258
['generate', [/<type> <name>/]],
250259
['validate', [/\[schema\]/]],
251260
];
@@ -319,6 +328,50 @@ describe('@object-ui/cli bin', () => {
319328
});
320329
});
321330

331+
// objectui#11500. Four commands that could only fail are retired with no
332+
// placeholder, so the bin must REFUSE each one the way it refuses a command it
333+
// never declared:
334+
// - `create plugin NAME` resolved its generator one directory too high and
335+
// never found it. `plugin` was `create`'s only type, so `create` goes
336+
// whole; `npm create @object-ui/plugin` is the package's own initializer.
337+
// - `lint` / `test` ran ESLint / Vitest in the temp app `dev` generates,
338+
// which declares neither toolchain, so they failed by construction.
339+
// - `studio` launched this monorepo's console, not the local project, and
340+
// failed outside the monorepo.
341+
// PATH is emptied for these runs. A retired command that came back would then
342+
// fail at its first external tool instead of starting a dev server or a
343+
// toolchain from inside this suite, and the stderr assertion still turns red.
344+
// The control is the same directory answering a declared command.
345+
describe('the retired create, lint, test and studio commands are refused (objectui#11500)', () => {
346+
const runWithoutPath = (args: string[], cwd: string) => {
347+
const res = spawnSync(process.execPath, [CLI_BIN, ...args], {
348+
cwd,
349+
encoding: 'utf-8',
350+
env: { ...process.env, PATH: '' },
351+
timeout: 60_000,
352+
});
353+
return { code: res.status ?? -1, stdout: res.stdout ?? '', stderr: res.stderr ?? '' };
354+
};
355+
356+
const cases: Array<[string, string[]]> = [
357+
['create', ['create', 'plugin', 'probe-widget']],
358+
['lint', ['lint']],
359+
['test', ['test']],
360+
['studio', ['studio']],
361+
];
362+
it.each(cases)('exits non-zero with the unknown-command error on `%s`, and writes nothing', (name, args) => {
363+
const work = mkdtempSync(join(tmpdir(), `objectui-cli-${name}-retired-`));
364+
const res = runWithoutPath(args, work);
365+
expect(res.code, res.stdout + res.stderr).not.toBe(0);
366+
expect(res.stderr).toContain(`unknown command '${name}'`);
367+
expect(tree(work)).toEqual([]);
368+
369+
const control = run(['g', 'page', 'Probe'], { cwd: work });
370+
expect(control.code, control.stdout + control.stderr).toBe(0);
371+
expect(tree(work)).toEqual(['pages', 'pages/probe.json']);
372+
});
373+
});
374+
322375
// objectui#11496, the family close-out over every command and flag. `analyze
323376
// --render-performance` printed the same fixed list of tips and ticks in any
324377
// directory, a built project or an empty one, and analysed nothing. It is
@@ -350,6 +403,18 @@ describe('@object-ui/cli bin', () => {
350403
expect(res.stdout).not.toMatch(/render performance/i);
351404
});
352405

406+
// objectui#11500. With no `dist/`, analyze printed a warning, then "Analysis
407+
// complete", and exited 0, so a script read an analysis that never happened
408+
// as a pass.
409+
it('exits non-zero without dist/, and claims no analysis (objectui#11500)', () => {
410+
const work = mkdtempSync(join(tmpdir(), 'objectui-cli-analyze-nodist-'));
411+
const res = run(['analyze'], { cwd: work });
412+
expect(res.code, res.stdout + res.stderr).not.toBe(0);
413+
expect(res.stderr).toContain('dist/');
414+
expect(res.stdout).not.toMatch(/analysis complete/i);
415+
expect(tree(work)).toEqual([]);
416+
});
417+
353418
it('lists neither flag in analyze --help', () => {
354419
const res = run(['analyze', '--help']);
355420
expect(res.code, res.stderr).toBe(0);
@@ -406,6 +471,21 @@ describe('@object-ui/cli bin', () => {
406471
expect(res.code, res.stdout + res.stderr).toBe(0);
407472
});
408473

474+
// objectui#11500. `init NAME -t nope` exited 1 but left an empty NAME
475+
// directory behind, so the next `init NAME` was refused as "already exists".
476+
// The control is the same directory scaffolding with a real template.
477+
it('refuses an unknown template before creating the directory (objectui#11500)', () => {
478+
const dir = mkdtempSync(join(tmpdir(), 'objectui-cli-init-unknown-template-'));
479+
const res = run(['init', 'probe-app', '-t', 'nope'], { cwd: dir });
480+
expect(res.code, res.stdout + res.stderr).not.toBe(0);
481+
expect(res.stderr).toMatch(/unknown template/i);
482+
expect(tree(dir)).toEqual([]);
483+
484+
const control = run(['init', 'probe-app', '-t', 'simple'], { cwd: dir });
485+
expect(control.code, control.stdout + control.stderr).toBe(0);
486+
expect(existsSync(join(dir, 'probe-app', 'app.json'))).toBe(true);
487+
});
488+
409489
it('scaffolds a project with the simple template', () => {
410490
for (const f of [
411491
'app.json',

0 commit comments

Comments
 (0)