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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Top-level `defineCommand` in `src/index.ts` wires eleven subcommands (`cloud`, `

**Flag composition.** Flag definitions are split by domain in `src/config/flags/*.flags.ts` (api, binary, device, environment, execution, github, output) and re-exported as a single `flags` object from `src/constants.ts`. Commands that need the full cloud surface spread `...flags` into their citty `args`; subset commands import individual flag groups.

**Output rendering.** All human-facing output goes through `src/utils/ui.ts` (the canonical layer: `section`/`branch`/`fields`/`status`/`success`/`info`/`warn`) on top of `src/utils/styling.ts` (`colors`, `symbols`, `statusPalette`). The visual language is a Claude Code-style tree (`⏺` section headings, `⎿` branch groups). Commands must not hand-roll layouts from `colors`/`symbols` or call `console.log` (except the single `JSON.stringify` line under `--json`). Full rules and a cookbook live in `STYLE_GUIDE.md`.
**Output rendering.** All human-facing output goes through `src/utils/ui.ts` (the canonical layer: `section`/`branch`/`fields`/`status`/`success`/`info`/`warn`) on top of `src/utils/styling.ts` (`colors`, `symbols`, `statusPalette`). The visual language is a Claude Code-style tree (`⏺` section headings, `⎿` branch groups). Commands must not hand-roll layouts from `colors`/`symbols` or call `console.log`; the `--json` result goes through `logger.json`. Every command takes `--json` (`jsonFlag`), and `src/index.ts` turns thrown errors into `{ status: 'FAILED', error }` whenever `--json` is in argv. Full rules and a cookbook live in `STYLE_GUIDE.md`.

**Layered call stack.**
- `src/commands/*` — thin citty command definitions; orchestrate services, no I/O logic.
Expand Down
17 changes: 12 additions & 5 deletions STYLE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ rows beneath them.
need a shape that isn't there, add it to `ui` so every command gets it.

2. **Print through the logger.** Use `logger` from `src/utils/cli.ts`
(`logger.log` / `logger.warn` / `logger.error`). Never `console.log` directly
except for the single `JSON.stringify` line in `--json` mode.
(`logger.log` / `logger.warn` / `logger.error`, and `logger.json` for the
`--json` result). Never `console.log` directly.

3. **Sections then branches.** A screen is one or more `ui.section(title)`
headings, each followed by a `ui.branch([...])` group of detail rows. Key/
Expand All @@ -48,9 +48,16 @@ rows beneath them.
field labels are dim; colour is reserved for status and for emphasising a
value (IDs via `formatId`, URLs via `formatUrl`). Don't colour a label.

7. **`--json` suppresses everything else.** When `--json` is set, emit only the
single serialized object and gate all other output behind the flag (the
`out()` wrapper pattern in `cloud.ts`/`upload.ts`).
7. **`--json` suppresses everything else.** Every command takes `--json`
(`jsonFlag` from `output.flags.ts`). When it is set, stdout carries exactly
one object, printed with `logger.json`. Gate all other output behind the
flag (the `out()` wrapper pattern in `cloud.ts`/`upload.ts`). Warnings stay
on stderr. Interactive commands (`login`, `switch-org`) draw their prompts
and the login URL on stderr instead. Thrown errors need no handling: the
top-level handler in `index.ts` prints `{ "status": "FAILED", "error" }`
whenever `--json` is in argv. If a command has already printed its result
and still has to exit non-zero, use `logger.fail` so a second object isn't
printed.

8. **No full-width rulers.** The 80-char `dividers` and `box` are deprecated;
the tree provides the structure. Status words are lowercase.
Expand Down
29 changes: 23 additions & 6 deletions src/commands/artifacts.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { defineCommand } from 'citty';

import { apiFlags } from '../config/flags/api.flags.js';
import { ReportDownloadService } from '../services/report-download.service.js';
import { jsonFlag } from '../config/flags/output.flags.js';
import {
type DownloadResult,
ReportDownloadService,
} from '../services/report-download.service.js';
import { resolveAuth } from '../utils/auth.js';
import { CliError, logger, validateEnum } from '../utils/cli.js';
import { resolveApiUrl } from '../utils/config-store.js';
Expand Down Expand Up @@ -56,6 +60,7 @@ export const artifactsCommand = defineCommand({
description:
'Custom file path for downloaded JUnit report (default: ./report.xml)',
},
json: jsonFlag,
},
async run({ args }) {
const apiKeyFlag = args['api-key'] as string | undefined;
Expand All @@ -76,6 +81,9 @@ export const artifactsCommand = defineCommand({
const allurePath = args['allure-path'] as string | undefined;
const htmlPath = args['html-path'] as string | undefined;
const junitPath = args['junit-path'] as string | undefined;
const json = Boolean(args.json);
// Success lines are dropped under --json; warnings stay on stderr.
const log = json ? undefined : (m: string) => logger.log(m);

const auth = await resolveAuth({ apiKeyFlag });

Expand All @@ -92,33 +100,42 @@ export const artifactsCommand = defineCommand({
}

const service = new ReportDownloadService();
const downloads: DownloadResult[] = [];

if (downloadArtifacts) {
await service.downloadArtifacts({
downloads.push(await service.downloadArtifacts({
auth,
apiUrl,
artifactsPath: artifactsPath ?? './artifacts.zip',
debug,
downloadType: downloadArtifacts,
logger: (m: string) => logger.log(m),
logger: log,
uploadId,
warnLogger: (m: string) => logger.warn(m),
});
}));
}

if (report) {
await service.downloadReports({
const result = await service.downloadReports({
allurePath,
auth,
apiUrl,
debug,
htmlPath,
junitPath,
logger: (m: string) => logger.log(m),
logger: log,
reportType: report,
uploadId,
warnLogger: (m: string) => logger.warn(m),
});
if (result) downloads.push(result);
}

if (json) {
const ok = downloads.some((d) => d.ok);
logger.json({ status: ok ? 'SUCCESS' : 'FAILED', uploadId, downloads });
// Same exit code a thrown error would give, without a second JSON object.
if (!ok) logger.fail('Every download failed', 1);
}
},
});
Expand Down
6 changes: 2 additions & 4 deletions src/commands/cloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1051,8 +1051,7 @@ export const cloudCommand = defineCommand({
}

if (json) {
// eslint-disable-next-line no-console
console.log(JSON.stringify(jsonOutput, null, 2));
logger.json(jsonOutput);
return;
}

Expand Down Expand Up @@ -1192,8 +1191,7 @@ export const cloudCommand = defineCommand({
}

if (output) {
// eslint-disable-next-line no-console
console.log(JSON.stringify(output, null, 2));
logger.json(output);
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/commands/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,7 @@ export const listCommand = defineCommand({
})) as ListResponse;

if (json) {
// eslint-disable-next-line no-console
console.log(JSON.stringify(response, null, 2));
logger.json(response);
return;
}

Expand Down
Loading
Loading