@hunt-labs/bytekit-cli 0.2.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +172 -0
- package/README.md +193 -15
- package/dist/client.d.ts +39 -18
- package/dist/client.js +230 -39
- package/dist/commands/account.d.ts +11 -0
- package/dist/commands/account.js +12 -80
- package/dist/commands/bulk.js +14 -0
- package/dist/commands/fetch.d.ts +14 -0
- package/dist/commands/fetch.js +45 -6
- package/dist/commands/monitors.js +83 -3
- package/dist/commands/scrape.js +190 -10
- package/dist/commands/screenshots.d.ts +12 -0
- package/dist/commands/screenshots.js +146 -6
- package/dist/commands/search.js +5 -1
- package/dist/commands/sitemap.js +17 -1
- package/dist/commands/usage.d.ts +13 -0
- package/dist/commands/usage.js +81 -0
- package/dist/commands/webhook-deliveries.d.ts +11 -0
- package/dist/commands/webhook-deliveries.js +59 -0
- package/dist/index.js +4 -0
- package/dist/lib/download-artifact.d.ts +13 -0
- package/dist/lib/download-artifact.js +25 -0
- package/dist/lib/parse-flags.d.ts +46 -0
- package/dist/lib/parse-flags.js +90 -0
- package/dist/output.d.ts +88 -0
- package/dist/output.js +188 -7
- package/package.json +2 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { buildClient } from '../client.js';
|
|
3
|
+
import { printResult, handleError } from '../output.js';
|
|
4
|
+
/**
|
|
5
|
+
* The `usage` command group (#2545) — the `x-sdk-scope: v0.1` usage endpoints, previously curl-only.
|
|
6
|
+
*
|
|
7
|
+
* `daily` / `by-endpoint` accept the same three optional query flags (`--from`, `--to`,
|
|
8
|
+
* `--api-key`). Each maps 1:1 to its snake_case query param and is written ONLY when present; when
|
|
9
|
+
* NO flag is passed the SDK method is invoked with ZERO arguments (the `monitors list` pattern), so
|
|
10
|
+
* the bare `/v1/usage/daily` URL is serialized unchanged.
|
|
11
|
+
*/
|
|
12
|
+
export function makeUsageCommand(getGlobal) {
|
|
13
|
+
const cmd = new Command('usage').description('Account usage and billing-period reporting');
|
|
14
|
+
cmd
|
|
15
|
+
.command('get')
|
|
16
|
+
.description('Get current billing-period usage summary')
|
|
17
|
+
.action(async () => {
|
|
18
|
+
const g = getGlobal();
|
|
19
|
+
const client = buildClient(g);
|
|
20
|
+
try {
|
|
21
|
+
const result = await client.usage.get();
|
|
22
|
+
printResult(result, g.json);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
handleError(err);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
cmd
|
|
29
|
+
.command('daily')
|
|
30
|
+
.description('Get daily usage breakdown (default last 30 days; max 366)')
|
|
31
|
+
.option('--from <date>', 'Start date YYYY-MM-DD (omitted → server default, 30 days ago)')
|
|
32
|
+
.option('--to <date>', 'End date YYYY-MM-DD (omitted → server default, today)')
|
|
33
|
+
.option('--api-key <ak_id>', 'Filter by API key prefixed ID (omitted → all keys)')
|
|
34
|
+
.action(async (opts) => {
|
|
35
|
+
const g = getGlobal();
|
|
36
|
+
const client = buildClient(g);
|
|
37
|
+
const params = {};
|
|
38
|
+
if (opts.from !== undefined)
|
|
39
|
+
params.from = opts.from;
|
|
40
|
+
if (opts.to !== undefined)
|
|
41
|
+
params.to = opts.to;
|
|
42
|
+
if (opts.apiKey !== undefined)
|
|
43
|
+
params.api_key = opts.apiKey;
|
|
44
|
+
try {
|
|
45
|
+
const result = Object.keys(params).length > 0
|
|
46
|
+
? await client.usage.daily(params)
|
|
47
|
+
: await client.usage.daily();
|
|
48
|
+
printResult(result, g.json);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
handleError(err);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
cmd
|
|
55
|
+
.command('by-endpoint')
|
|
56
|
+
.description('Get per-endpoint bandwidth breakdown (default last 30 days; max 366)')
|
|
57
|
+
.option('--from <date>', 'Start date YYYY-MM-DD (omitted → server default, 30 days ago)')
|
|
58
|
+
.option('--to <date>', 'End date YYYY-MM-DD (omitted → server default, today)')
|
|
59
|
+
.option('--api-key <ak_id>', 'Filter by API key prefixed ID (omitted → all keys)')
|
|
60
|
+
.action(async (opts) => {
|
|
61
|
+
const g = getGlobal();
|
|
62
|
+
const client = buildClient(g);
|
|
63
|
+
const params = {};
|
|
64
|
+
if (opts.from !== undefined)
|
|
65
|
+
params.from = opts.from;
|
|
66
|
+
if (opts.to !== undefined)
|
|
67
|
+
params.to = opts.to;
|
|
68
|
+
if (opts.apiKey !== undefined)
|
|
69
|
+
params.api_key = opts.apiKey;
|
|
70
|
+
try {
|
|
71
|
+
const result = Object.keys(params).length > 0
|
|
72
|
+
? await client.usage.byEndpoint(params)
|
|
73
|
+
: await client.usage.byEndpoint();
|
|
74
|
+
printResult(result, g.json);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
handleError(err);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return cmd;
|
|
81
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { type GlobalOptions } from '../client.js';
|
|
3
|
+
/**
|
|
4
|
+
* The `webhook-deliveries` command group (#2545) — inspect and requeue webhook delivery attempts.
|
|
5
|
+
*
|
|
6
|
+
* `list` flags map 1:1 to their query params and are omitted-when-absent; a bare `list` calls the
|
|
7
|
+
* SDK with ZERO args so the plain `/v1/webhook-deliveries` URL is serialized unchanged.
|
|
8
|
+
*/
|
|
9
|
+
export declare function makeWebhookDeliveriesCommand(getGlobal: () => GlobalOptions & {
|
|
10
|
+
json: boolean;
|
|
11
|
+
}): Command;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { buildClient } from '../client.js';
|
|
3
|
+
import { printResult, handleError } from '../output.js';
|
|
4
|
+
import { parseEnum, parseIntFlag } from '../lib/parse-flags.js';
|
|
5
|
+
/** The `listWebhookDeliveries` `status` query enum (DISTINCT from the monitor status enum). */
|
|
6
|
+
const DELIVERY_STATUSES = ['pending', 'delivered', 'failed', 'exhausted'];
|
|
7
|
+
/**
|
|
8
|
+
* The `webhook-deliveries` command group (#2545) — inspect and requeue webhook delivery attempts.
|
|
9
|
+
*
|
|
10
|
+
* `list` flags map 1:1 to their query params and are omitted-when-absent; a bare `list` calls the
|
|
11
|
+
* SDK with ZERO args so the plain `/v1/webhook-deliveries` URL is serialized unchanged.
|
|
12
|
+
*/
|
|
13
|
+
export function makeWebhookDeliveriesCommand(getGlobal) {
|
|
14
|
+
const cmd = new Command('webhook-deliveries').description('Webhook delivery operations');
|
|
15
|
+
cmd
|
|
16
|
+
.command('list')
|
|
17
|
+
.description('List webhook deliveries')
|
|
18
|
+
.option('--limit <n>', 'Maximum number of deliveries to return', (v) => parseIntFlag('--limit', v))
|
|
19
|
+
.option('--cursor <cursor>', 'Pagination cursor from a previous page')
|
|
20
|
+
.option('--status <status>', `Filter by delivery status: ${DELIVERY_STATUSES.join(', ')}`, (v) => parseEnum('--status', DELIVERY_STATUSES, v))
|
|
21
|
+
.option('--since <iso>', 'Only deliveries created after this ISO date-time')
|
|
22
|
+
.action(async (opts) => {
|
|
23
|
+
const g = getGlobal();
|
|
24
|
+
const client = buildClient(g);
|
|
25
|
+
const params = {};
|
|
26
|
+
if (opts.limit !== undefined)
|
|
27
|
+
params.limit = opts.limit;
|
|
28
|
+
if (opts.cursor !== undefined)
|
|
29
|
+
params.cursor = opts.cursor;
|
|
30
|
+
if (opts.status !== undefined)
|
|
31
|
+
params.status = opts.status;
|
|
32
|
+
if (opts.since !== undefined)
|
|
33
|
+
params.since = opts.since;
|
|
34
|
+
try {
|
|
35
|
+
const result = Object.keys(params).length > 0
|
|
36
|
+
? await client.webhooks.list(params)
|
|
37
|
+
: await client.webhooks.list();
|
|
38
|
+
printResult(result, g.json);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
handleError(err);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
cmd
|
|
45
|
+
.command('retry <id>')
|
|
46
|
+
.description('Requeue a failed or exhausted webhook delivery')
|
|
47
|
+
.action(async (id) => {
|
|
48
|
+
const g = getGlobal();
|
|
49
|
+
const client = buildClient(g);
|
|
50
|
+
try {
|
|
51
|
+
const result = await client.webhooks.retry(id);
|
|
52
|
+
printResult(result, g.json);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
handleError(err);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
return cmd;
|
|
59
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,8 @@ import { makeMonitorsCommand } from './commands/monitors.js';
|
|
|
11
11
|
import { makeSitemapCommand } from './commands/sitemap.js';
|
|
12
12
|
import { makeSearchCommand } from './commands/search.js';
|
|
13
13
|
import { makeAccountCommand } from './commands/account.js';
|
|
14
|
+
import { makeUsageCommand } from './commands/usage.js';
|
|
15
|
+
import { makeWebhookDeliveriesCommand } from './commands/webhook-deliveries.js';
|
|
14
16
|
// Resolve version from package.json at runtime (supports compiled output).
|
|
15
17
|
const require = createRequire(import.meta.url);
|
|
16
18
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
@@ -44,6 +46,8 @@ export function buildProgram() {
|
|
|
44
46
|
program.addCommand(makeSitemapCommand(getGlobal));
|
|
45
47
|
program.addCommand(makeSearchCommand(getGlobal));
|
|
46
48
|
program.addCommand(makeAccountCommand(getGlobal));
|
|
49
|
+
program.addCommand(makeUsageCommand(getGlobal));
|
|
50
|
+
program.addCommand(makeWebhookDeliveriesCommand(getGlobal));
|
|
47
51
|
return program;
|
|
48
52
|
}
|
|
49
53
|
/** Resolve symlinks and `..` segments, falling back to the raw path if it cannot be resolved. */
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Download a screenshot artifact from its `image_url` and write the raw bytes to `dest`.
|
|
3
|
+
*
|
|
4
|
+
* - `dest === '-'` writes the bytes to stdout (the conventional dash-is-stdout convention),
|
|
5
|
+
* never with `console.log` — binary must not be coerced or newline-appended.
|
|
6
|
+
* - any other `dest` is a file path written verbatim.
|
|
7
|
+
*
|
|
8
|
+
* `globalThis.fetch` is resolved at CALL time (never captured at module/construction load) so a
|
|
9
|
+
* test that stubs `globalThis.fetch` in a `beforeEach` — after this module was imported — still
|
|
10
|
+
* sees its stub (G-049). A non-2xx response throws with the HTTP status so `handleError` renders a
|
|
11
|
+
* clean one-liner and exits 1.
|
|
12
|
+
*/
|
|
13
|
+
export declare function downloadArtifact(url: string, dest: string): Promise<void>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
/**
|
|
3
|
+
* Download a screenshot artifact from its `image_url` and write the raw bytes to `dest`.
|
|
4
|
+
*
|
|
5
|
+
* - `dest === '-'` writes the bytes to stdout (the conventional dash-is-stdout convention),
|
|
6
|
+
* never with `console.log` — binary must not be coerced or newline-appended.
|
|
7
|
+
* - any other `dest` is a file path written verbatim.
|
|
8
|
+
*
|
|
9
|
+
* `globalThis.fetch` is resolved at CALL time (never captured at module/construction load) so a
|
|
10
|
+
* test that stubs `globalThis.fetch` in a `beforeEach` — after this module was imported — still
|
|
11
|
+
* sees its stub (G-049). A non-2xx response throws with the HTTP status so `handleError` renders a
|
|
12
|
+
* clean one-liner and exits 1.
|
|
13
|
+
*/
|
|
14
|
+
export async function downloadArtifact(url, dest) {
|
|
15
|
+
const res = await globalThis.fetch(url);
|
|
16
|
+
if (!res.ok) {
|
|
17
|
+
throw new Error(`artifact download failed: HTTP ${res.status}`);
|
|
18
|
+
}
|
|
19
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
20
|
+
if (dest === '-') {
|
|
21
|
+
process.stdout.write(bytes);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
writeFileSync(dest, bytes);
|
|
25
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared commander argParser helpers for CLI flag values (#2480 cr-3). One home for the parser
|
|
3
|
+
* family that scrape.ts, screenshots.ts, and monitors.ts previously each held private copies of —
|
|
4
|
+
* every off-spec value is rejected locally, at parse time, with an actionable message, before any
|
|
5
|
+
* HTTP request is issued.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Parse a whole-number flag value. The strict `/^\d+$/` test deterministically rejects `abc`, ``,
|
|
9
|
+
* `1.5`, `-5`, `1e2`, so an off-spec value fails at parse time with an actionable message instead
|
|
10
|
+
* of a server 422 or a NaN on the wire.
|
|
11
|
+
*/
|
|
12
|
+
export declare function parseIntFlag(flag: string, value: string): number;
|
|
13
|
+
/** Reject an off-spec enum value at parse time with the allowed set in the message. */
|
|
14
|
+
export declare function parseEnum<T extends string>(flag: string, allowed: readonly T[], value: string): T;
|
|
15
|
+
/** Split a comma-separated flag value into a trimmed, non-empty string array. */
|
|
16
|
+
export declare function parseCsv(value: string): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Parse a finite decimal flag value (#2545). Unlike {@link parseIntFlag}, the `/^\d+(\.\d+)?$/`
|
|
19
|
+
* test accepts a fractional value such as `2.5` — required for spec `type: number` fields like
|
|
20
|
+
* `monitors change_threshold` (default 5, range 0–100) that `parseIntFlag`'s `/^\d+$/` would reject
|
|
21
|
+
* at parse time. Rejects `abc`, ``, `-5`, `1.` and `1e2` with the flag named in the message.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseNumberFlag(flag: string, value: string): number;
|
|
24
|
+
/**
|
|
25
|
+
* Parse a `cache_ttl` flag value (#2545). Mirrors the gateway validator's own format grammar
|
|
26
|
+
* (`^(0|\d+[hd])$`, `packages/gateway/src/validators/shared.ts`) locally so an off-grammar value
|
|
27
|
+
* (`30m`, `soon`) is rejected at parse time with an actionable message instead of a server 422.
|
|
28
|
+
* The string is returned verbatim — the validator accepts the string `'0'`, and the range cap
|
|
29
|
+
* (7d/168h for scrape+fetch) stays a server-side concern.
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseCacheTtl(flag: string, value: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Parse a JSON-object- or JSON-array-valued flag (#2545). The ONLY `JSON.parse` call site for CLI
|
|
34
|
+
* flag values — every JSON-shaped flag (`--cookies`, `--headers`, `--custom`,
|
|
35
|
+
* `--webhook-headers`, `--options`, `--scrape-options`) routes through here, so command modules
|
|
36
|
+
* carry no raw `JSON.parse` (grep-gated). Rejects malformed JSON AND a value whose top-level shape
|
|
37
|
+
* does not match `expect`, with a copy-pasteable example in the message (E5).
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseJsonFlag(flag: string, value: string, expect: 'object' | 'array'): unknown;
|
|
40
|
+
/**
|
|
41
|
+
* Commander collector for a repeatable, CSV-aware `--format` flag (#2545). Each occurrence is
|
|
42
|
+
* CSV-split and appended to the accumulator, so `--format raw_html --format markdown` and
|
|
43
|
+
* `--format raw_html,markdown` both yield `['raw_html','markdown']`. Commander seeds `prev` with
|
|
44
|
+
* the option default (or the previous return) on each occurrence.
|
|
45
|
+
*/
|
|
46
|
+
export declare function collectFormats(value: string, prev: string[] | undefined): string[];
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { InvalidArgumentError } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Shared commander argParser helpers for CLI flag values (#2480 cr-3). One home for the parser
|
|
4
|
+
* family that scrape.ts, screenshots.ts, and monitors.ts previously each held private copies of —
|
|
5
|
+
* every off-spec value is rejected locally, at parse time, with an actionable message, before any
|
|
6
|
+
* HTTP request is issued.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Parse a whole-number flag value. The strict `/^\d+$/` test deterministically rejects `abc`, ``,
|
|
10
|
+
* `1.5`, `-5`, `1e2`, so an off-spec value fails at parse time with an actionable message instead
|
|
11
|
+
* of a server 422 or a NaN on the wire.
|
|
12
|
+
*/
|
|
13
|
+
export function parseIntFlag(flag, value) {
|
|
14
|
+
if (!/^\d+$/.test(value)) {
|
|
15
|
+
throw new InvalidArgumentError(`${flag} must be a whole number.`);
|
|
16
|
+
}
|
|
17
|
+
return Number(value);
|
|
18
|
+
}
|
|
19
|
+
/** Reject an off-spec enum value at parse time with the allowed set in the message. */
|
|
20
|
+
export function parseEnum(flag, allowed, value) {
|
|
21
|
+
if (!allowed.includes(value)) {
|
|
22
|
+
throw new InvalidArgumentError(`${flag} must be one of: ${allowed.join(', ')}.`);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
/** Split a comma-separated flag value into a trimmed, non-empty string array. */
|
|
27
|
+
export function parseCsv(value) {
|
|
28
|
+
return value
|
|
29
|
+
.split(',')
|
|
30
|
+
.map((s) => s.trim())
|
|
31
|
+
.filter((s) => s.length > 0);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse a finite decimal flag value (#2545). Unlike {@link parseIntFlag}, the `/^\d+(\.\d+)?$/`
|
|
35
|
+
* test accepts a fractional value such as `2.5` — required for spec `type: number` fields like
|
|
36
|
+
* `monitors change_threshold` (default 5, range 0–100) that `parseIntFlag`'s `/^\d+$/` would reject
|
|
37
|
+
* at parse time. Rejects `abc`, ``, `-5`, `1.` and `1e2` with the flag named in the message.
|
|
38
|
+
*/
|
|
39
|
+
export function parseNumberFlag(flag, value) {
|
|
40
|
+
if (!/^\d+(\.\d+)?$/.test(value)) {
|
|
41
|
+
throw new InvalidArgumentError(`${flag} must be a number.`);
|
|
42
|
+
}
|
|
43
|
+
return Number(value);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Parse a `cache_ttl` flag value (#2545). Mirrors the gateway validator's own format grammar
|
|
47
|
+
* (`^(0|\d+[hd])$`, `packages/gateway/src/validators/shared.ts`) locally so an off-grammar value
|
|
48
|
+
* (`30m`, `soon`) is rejected at parse time with an actionable message instead of a server 422.
|
|
49
|
+
* The string is returned verbatim — the validator accepts the string `'0'`, and the range cap
|
|
50
|
+
* (7d/168h for scrape+fetch) stays a server-side concern.
|
|
51
|
+
*/
|
|
52
|
+
export function parseCacheTtl(flag, value) {
|
|
53
|
+
if (!/^(0|\d+[hd])$/.test(value)) {
|
|
54
|
+
throw new InvalidArgumentError(`${flag} must be 0, Nh (hours), or Nd (days) — e.g. 48h, 2d, 0.`);
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Parse a JSON-object- or JSON-array-valued flag (#2545). The ONLY `JSON.parse` call site for CLI
|
|
60
|
+
* flag values — every JSON-shaped flag (`--cookies`, `--headers`, `--custom`,
|
|
61
|
+
* `--webhook-headers`, `--options`, `--scrape-options`) routes through here, so command modules
|
|
62
|
+
* carry no raw `JSON.parse` (grep-gated). Rejects malformed JSON AND a value whose top-level shape
|
|
63
|
+
* does not match `expect`, with a copy-pasteable example in the message (E5).
|
|
64
|
+
*/
|
|
65
|
+
export function parseJsonFlag(flag, value, expect) {
|
|
66
|
+
const example = expect === 'array' ? `[{"name":"a","value":"b"}]` : `{"X-Header":"value"}`;
|
|
67
|
+
let parsed;
|
|
68
|
+
try {
|
|
69
|
+
parsed = JSON.parse(value);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
throw new InvalidArgumentError(`${flag} must be valid JSON (a ${expect}), e.g. '${example}'.`);
|
|
73
|
+
}
|
|
74
|
+
const matches = expect === 'array'
|
|
75
|
+
? Array.isArray(parsed)
|
|
76
|
+
: typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed);
|
|
77
|
+
if (!matches) {
|
|
78
|
+
throw new InvalidArgumentError(`${flag} must be a JSON ${expect}, e.g. '${example}'.`);
|
|
79
|
+
}
|
|
80
|
+
return parsed;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Commander collector for a repeatable, CSV-aware `--format` flag (#2545). Each occurrence is
|
|
84
|
+
* CSV-split and appended to the accumulator, so `--format raw_html --format markdown` and
|
|
85
|
+
* `--format raw_html,markdown` both yield `['raw_html','markdown']`. Commander seeds `prev` with
|
|
86
|
+
* the option default (or the previous return) on each occurrence.
|
|
87
|
+
*/
|
|
88
|
+
export function collectFormats(value, prev) {
|
|
89
|
+
return [...(prev ?? []), ...parseCsv(value)];
|
|
90
|
+
}
|
package/dist/output.d.ts
CHANGED
|
@@ -22,6 +22,24 @@ export interface OutputFlags {
|
|
|
22
22
|
raw?: boolean | undefined;
|
|
23
23
|
output?: string | undefined;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Serialize a value as the CLI's `--json` payload text. The single serializer EVERY `--json`
|
|
27
|
+
* branch in this module goes through, so no branch can regress to a raw `JSON.stringify`.
|
|
28
|
+
*
|
|
29
|
+
* `JSON.stringify` returns the VALUE `undefined` — not a string — for `undefined`, functions and
|
|
30
|
+
* symbols. Handed to `console.log` that prints the literal text `undefined`, which is NOT valid
|
|
31
|
+
* JSON and breaks every `| jq` pipeline; handed to `writeFileSync` it throws
|
|
32
|
+
* `ERR_INVALID_ARG_TYPE`. A `204 No Content` response resolves to `undefined` in the SDK
|
|
33
|
+
* (`monitors.delete` is typed `Promise<void>`), so this is the ORDINARY shape of
|
|
34
|
+
* `bytekit monitors delete <id> --json`, not an exotic edge case (#2544).
|
|
35
|
+
*
|
|
36
|
+
* The representation is JSON `null` — the canonical JSON spelling of "no content". Deliberately
|
|
37
|
+
* NOT a synthesized envelope such as `{"status":"deleted"}`: that is a shape the API never
|
|
38
|
+
* returned, and it would have to be re-invented (and kept consistent) per command. `null` is
|
|
39
|
+
* uniform across every 204-returning path, and `… --json | jq .` prints `null` and exits 0.
|
|
40
|
+
* The contract is documented in README (`Exit codes and output contracts`) and CHANGELOG.
|
|
41
|
+
*/
|
|
42
|
+
export declare function toJsonText(data: unknown): string;
|
|
25
43
|
/**
|
|
26
44
|
* Resolve the output mode from the flags, and enforce the `--json` / `--raw` exclusion.
|
|
27
45
|
*
|
|
@@ -56,6 +74,40 @@ export declare function resolveOutputMode(flags: OutputFlags): OutputMode;
|
|
|
56
74
|
* not text, so `--raw` can never print `[object Object]` or a format the caller did not ask for.
|
|
57
75
|
*/
|
|
58
76
|
export declare function extractRawBody(response: unknown, requestedFormat?: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* Exit code for `--raw`/`-o` against a 202 queued (async) scrape.
|
|
79
|
+
*
|
|
80
|
+
* Deliberately NOT 0 (a full raw-body success) and NOT 1 (a usage/API error): the job WAS accepted
|
|
81
|
+
* and billed, there is simply no content body to emit yet. A distinct code lets a caller branch on
|
|
82
|
+
* "queued" without parsing stderr.
|
|
83
|
+
*/
|
|
84
|
+
export declare const QUEUED_RAW_EXIT_CODE = 2;
|
|
85
|
+
/** A 202 ScrapeQueuedEnvelope: `status: 'queued'` plus a stable `sc_` id (openapi.yaml). */
|
|
86
|
+
interface QueuedEnvelope {
|
|
87
|
+
status: 'queued';
|
|
88
|
+
id: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Handle `--raw`/`-o` on a 202 queued envelope.
|
|
92
|
+
*
|
|
93
|
+
* The user asked for a content body that does not exist yet — but the job was accepted, so losing
|
|
94
|
+
* the handle (the old behaviour: throw `--raw: response contains no formats`, exit 1) is the real
|
|
95
|
+
* defect. Instead: emit the `sc_` id to STDOUT so `$(bytekit scrape create … --raw)` still captures
|
|
96
|
+
* a usable handle, print a human note to STDERR explaining it is queued and how to poll, and signal
|
|
97
|
+
* {@link QUEUED_RAW_EXIT_CODE} — distinguishable from both success and error.
|
|
98
|
+
*
|
|
99
|
+
* The exit code is a SOFT `process.exitCode`, never a hard `process.exit` (#2544). Node documents
|
|
100
|
+
* `process.stdout` as ASYNCHRONOUS when it points at a pipe: a write that cannot be handed to the
|
|
101
|
+
* kernel in one go is queued in userspace and drained on a later event-loop turn, and
|
|
102
|
+
* `process.exit()` runs before any of those turns and DISCARDS the queue. That is exactly the
|
|
103
|
+
* `sc=$(bytekit scrape create … --async --raw)` case this branch exists to serve — the id would be
|
|
104
|
+
* truncated away whenever stdout is backlogged. Setting `process.exitCode` instead lets the process
|
|
105
|
+
* end naturally once stdout has drained, with the same observable exit code.
|
|
106
|
+
*
|
|
107
|
+
* Returns `void` (not `never`): callers MUST `return` after invoking it, since execution now
|
|
108
|
+
* continues past this function.
|
|
109
|
+
*/
|
|
110
|
+
export declare function emitQueuedRaw(env: QueuedEnvelope): void;
|
|
59
111
|
/**
|
|
60
112
|
* Emit a raw body to the target.
|
|
61
113
|
*
|
|
@@ -78,6 +130,27 @@ export declare function emitRaw(body: string, target: OutputTarget): void;
|
|
|
78
130
|
* `--json | jq` pipelines stay byte-identical.
|
|
79
131
|
*/
|
|
80
132
|
export declare function emitResult(result: unknown, mode: OutputMode, target: OutputTarget, requestedFormat?: string): void;
|
|
133
|
+
/**
|
|
134
|
+
* Collect the `X-Fetch-*` response headers of a {@link FetchResult} into a Title-Cased object —
|
|
135
|
+
* the documented `--json` payload for a fetch command.
|
|
136
|
+
*/
|
|
137
|
+
export declare function collectFetchMetadata(headers: Headers): Record<string, string>;
|
|
138
|
+
/**
|
|
139
|
+
* Render a `/v1/fetch` content result.
|
|
140
|
+
*
|
|
141
|
+
* The SDK's fetch resource resolves a {@link FetchResult} whose `bytes` ARE the fetched content and
|
|
142
|
+
* whose `headers` carry the `X-Fetch-*` metadata.
|
|
143
|
+
*
|
|
144
|
+
* - default — write the raw body BYTES verbatim (`process.stdout.write`, never `console.log`: no
|
|
145
|
+
* coercion, no appended newline, binary-safe). `bytes` is used rather than the `body` string
|
|
146
|
+
* because the string is a lossy UTF-8 decode for true binary passthrough (images, PDFs).
|
|
147
|
+
* - `--json` — print the `X-Fetch-*` metadata object instead of the body.
|
|
148
|
+
* - `target.file` — write the same bytes to that path instead of stdout, byte-for-byte.
|
|
149
|
+
*
|
|
150
|
+
* Falls back to {@link printResult} for any result that is not a fetch result (defensive — e.g. a
|
|
151
|
+
* bulk-fetch envelope routed here by mistake), so no path can silently emit nothing.
|
|
152
|
+
*/
|
|
153
|
+
export declare function emitFetchResult(result: unknown, jsonMode: boolean, target?: OutputTarget): void;
|
|
81
154
|
/**
|
|
82
155
|
* Print a result to stdout.
|
|
83
156
|
*
|
|
@@ -86,5 +159,20 @@ export declare function emitResult(result: unknown, mode: OutputMode, target: Ou
|
|
|
86
159
|
export declare function printResult(data: unknown, jsonMode: boolean): void;
|
|
87
160
|
/**
|
|
88
161
|
* Handle a CLI command error: print to stderr and exit 1.
|
|
162
|
+
*
|
|
163
|
+
* The message always begins `Error: ` (a prefix scripts grep on) followed by the error message and
|
|
164
|
+
* an optional parenthesised suffix built from the two diagnostic axes the ByteKit errors carry:
|
|
165
|
+
*
|
|
166
|
+
* - a `ByteKitError` from the transport carries both a numeric `status` and an API `code`
|
|
167
|
+
* (`rate_limited`, `invalid_url`, …) → `Error: <message> (<code>, status: <n>)`;
|
|
168
|
+
* - a `ByteKitConnectionError` (client-side timeout / abort / socket failure) carries a `code`
|
|
169
|
+
* (`timeout` | `aborted` | `connection`) but NO status → `Error: <message> (<code>)`;
|
|
170
|
+
* - a plain error with neither → `Error: <message>`.
|
|
171
|
+
*
|
|
172
|
+
* The `code` axis is read only off the two ByteKit error classes (see {@link errorCode}) — a plain
|
|
173
|
+
* error carrying an unrelated string `.code` (a Node errno error) gets NO code suffix. When a
|
|
174
|
+
* status is present but no code (a hand-built error), the suffix is `(status: <n>)` with no empty
|
|
175
|
+
* placeholder — never `(, status: <n>)`.
|
|
89
176
|
*/
|
|
90
177
|
export declare function handleError(err: unknown): never;
|
|
178
|
+
export {};
|