@extenshi/cli 0.0.4 → 0.0.6
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/dist/cli.js +5 -2
- package/dist/cli.js.map +1 -1
- package/dist/scan.d.ts +63 -0
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +229 -39
- package/dist/scan.js.map +1 -1
- package/dist/sse.d.ts +30 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/sse.js +53 -0
- package/dist/sse.js.map +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -26,7 +26,7 @@ const MISSING_KEY_HELP = 'Error: API key not found.\n' +
|
|
|
26
26
|
' • run: extenshi login\n' +
|
|
27
27
|
' • set the EXTENSHI_API_KEY environment variable\n' +
|
|
28
28
|
' • add EXTENSHI_API_KEY=... to a .env file in this directory\n' +
|
|
29
|
-
'Get an API key at https://dojo.extenshi.io/billing
|
|
29
|
+
'Get an API key at https://dojo.extenshi.io/billing';
|
|
30
30
|
const _require = createRequire(import.meta.url);
|
|
31
31
|
const pkg = _require('../package.json');
|
|
32
32
|
program
|
|
@@ -37,8 +37,9 @@ program
|
|
|
37
37
|
.command('scan <artifact>')
|
|
38
38
|
.description('Scan a browser extension artifact (.crx, .xpi, or .zip)')
|
|
39
39
|
.option('--json', 'Output raw JSON (default: pretty-printed findings)')
|
|
40
|
-
.option('--extension-id <id>',
|
|
40
|
+
.option('--extension-id <id>', "Numeric catalog ID of a verified extension (required to spend free credits; see the Ownership tab in dojo). Paid credits don't need it.")
|
|
41
41
|
.option('--skip-preflight', 'Skip CI timeout pre-flight probe (use at your own risk)')
|
|
42
|
+
.option('--no-stream', 'Disable live progress streaming; wait for a single final JSON response')
|
|
42
43
|
.option('--api-url <url>', 'Override the scan API base URL (default: https://scan.extenshi.io)')
|
|
43
44
|
.action(async (artifact, opts) => {
|
|
44
45
|
try {
|
|
@@ -56,6 +57,8 @@ program
|
|
|
56
57
|
extensionId: opts.extensionId,
|
|
57
58
|
outputJson: opts.json ?? false,
|
|
58
59
|
skipPreflight: opts.skipPreflight ?? false,
|
|
60
|
+
// commander maps --no-stream to opts.stream === false; default on.
|
|
61
|
+
stream: opts.stream ?? true,
|
|
59
62
|
});
|
|
60
63
|
}
|
|
61
64
|
catch (err) {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,KAAK,QAAQ,MAAM,wBAAwB,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,MAAM,gBAAgB,GACrB,6BAA6B;IAC7B,oCAAoC;IACpC,2BAA2B;IAC3B,qDAAqD;IACrD,iEAAiE;IACjE,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/scan.d.ts
CHANGED
|
@@ -22,6 +22,50 @@
|
|
|
22
22
|
* "при network error не делайте автоматический retry —
|
|
23
23
|
* списанный credit вернётся автоматически в течение 1 минуты (auto-refund cron)"
|
|
24
24
|
*/
|
|
25
|
+
import { type Response } from 'undici';
|
|
26
|
+
/**
|
|
27
|
+
* Build the user-facing message for a 403 from the scan API.
|
|
28
|
+
*
|
|
29
|
+
* The free credit tier is gated: free credits ONLY cover extensions whose
|
|
30
|
+
* ownership you have verified, and ONLY when you name one with
|
|
31
|
+
* `--extension-id <id>` (the numeric catalog ID of a verified extension). A
|
|
32
|
+
* bare `extenshi scan <artifact>` running on free credits therefore 403s —
|
|
33
|
+
* which is confusing when dojo just says "5 free scans left".
|
|
34
|
+
*
|
|
35
|
+
* The BFF signals this case via a stable `errorCode`
|
|
36
|
+
* (`FREE_REQUIRES_CLAIM` / `FREE_REQUIRES_VERIFIED_OWNERSHIP`). The CLI ships
|
|
37
|
+
* via npm independently of the BFF deploy, so during version skew the
|
|
38
|
+
* deployed BFF may not set `errorCode` yet — we fall back to matching the
|
|
39
|
+
* human `error` text. Either way we append actionable next steps.
|
|
40
|
+
*
|
|
41
|
+
* Exported for unit testing.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildAccessDeniedMessage(body: {
|
|
44
|
+
error?: unknown;
|
|
45
|
+
errorCode?: unknown;
|
|
46
|
+
}): string;
|
|
47
|
+
/**
|
|
48
|
+
* A spinner whose label shows live elapsed seconds, rendered to stderr so it
|
|
49
|
+
* never corrupts --json output on stdout. ora auto-disables animation when
|
|
50
|
+
* stderr is not a TTY (CI), degrading to a couple of static log lines.
|
|
51
|
+
*
|
|
52
|
+
* The label paints immediately on `.start()` (at t=0, before the first tick),
|
|
53
|
+
* then a 1s ticker appends `(Ns)` so the line keeps visibly advancing even
|
|
54
|
+
* while the process is blocked on a long upload, a slow time-to-first-byte, or
|
|
55
|
+
* a gap between scan-progress events. `setText` swaps the base label without
|
|
56
|
+
* resetting the clock — this is what lets one spinner span the whole
|
|
57
|
+
* upload → connect → scanning → done lifecycle with a single continuous timer.
|
|
58
|
+
*
|
|
59
|
+
* Returns handles to settle it: `succeed` (green ✔), `fail` (red ✖), `stop`
|
|
60
|
+
* (clear line). Call exactly one of them — the ticker keeps running otherwise.
|
|
61
|
+
*/
|
|
62
|
+
export interface ElapsedSpinner {
|
|
63
|
+
setText: (text: string) => void;
|
|
64
|
+
succeed: (text: string) => void;
|
|
65
|
+
fail: (text: string) => void;
|
|
66
|
+
stop: () => void;
|
|
67
|
+
seconds: () => number;
|
|
68
|
+
}
|
|
25
69
|
export interface ScanOptions {
|
|
26
70
|
artifactPath: string;
|
|
27
71
|
apiKey: string;
|
|
@@ -29,6 +73,25 @@ export interface ScanOptions {
|
|
|
29
73
|
extensionId?: string;
|
|
30
74
|
outputJson: boolean;
|
|
31
75
|
skipPreflight: boolean;
|
|
76
|
+
/** Request live progress over SSE (default). `false` forces the legacy single-JSON mode. */
|
|
77
|
+
stream: boolean;
|
|
32
78
|
}
|
|
33
79
|
export declare function runScan(opts: ScanOptions): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Consume the SSE progress stream, updating the shared elapsed spinner on
|
|
82
|
+
* stderr, and render the final report (pretty or JSON) once the terminal
|
|
83
|
+
* `result` event arrives. Throws on a terminal `error` event or a truncated
|
|
84
|
+
* stream.
|
|
85
|
+
*
|
|
86
|
+
* `progress` is the live spinner started in runScan *before* the request. This
|
|
87
|
+
* function owns its single terminal settle (succeed/fail) from here on — the
|
|
88
|
+
* caller must not settle it once it has been handed over. Reusing one handle
|
|
89
|
+
* (rather than starting a fresh spinner here) keeps a single continuous timer
|
|
90
|
+
* across upload → connect → scanning → done, so the line never stalls and the
|
|
91
|
+
* console is never silent.
|
|
92
|
+
*
|
|
93
|
+
* Exported for unit testing the "feedback before the first progress event"
|
|
94
|
+
* path — the regression that made the console look frozen during upload/queue.
|
|
95
|
+
*/
|
|
96
|
+
export declare function renderSseStream(response: Response, outputJson: boolean, progress: ElapsedSpinner): Promise<void>;
|
|
34
97
|
//# sourceMappingURL=scan.d.ts.map
|
package/dist/scan.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAUvD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,CA0B/F;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,OAAO,EAAE,MAAM,MAAM,CAAA;CACrB;AAmCD,MAAM,WAAW,WAAW;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,OAAO,CAAA;IACnB,aAAa,EAAE,OAAO,CAAA;IACtB,4FAA4F;IAC5F,MAAM,EAAE,OAAO,CAAA;CACf;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA2G9D;AAsFD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CACpC,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,OAAO,EACnB,QAAQ,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CA2Df"}
|
package/dist/scan.js
CHANGED
|
@@ -27,31 +27,74 @@ import path from 'node:path';
|
|
|
27
27
|
import ora from 'ora';
|
|
28
28
|
import { FormData, fetch } from 'undici';
|
|
29
29
|
import { renderReport } from './render.js';
|
|
30
|
+
import { parseSseFrames } from './sse.js';
|
|
30
31
|
const SCAN_TIMEOUT_MS = 240_000; // 240 seconds
|
|
31
32
|
const PREFLIGHT_TIMEOUT_MS = 70_000; // 70 seconds
|
|
33
|
+
const CLI_BILLING_URL = 'https://dojo.extenshi.io/billing';
|
|
34
|
+
const DOJO_URL = 'https://dojo.extenshi.io';
|
|
32
35
|
/**
|
|
33
|
-
*
|
|
34
|
-
* never corrupts --json output on stdout. ora auto-disables animation when
|
|
35
|
-
* stderr is not a TTY (CI), degrading to a couple of static log lines.
|
|
36
|
+
* Build the user-facing message for a 403 from the scan API.
|
|
36
37
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
38
|
+
* The free credit tier is gated: free credits ONLY cover extensions whose
|
|
39
|
+
* ownership you have verified, and ONLY when you name one with
|
|
40
|
+
* `--extension-id <id>` (the numeric catalog ID of a verified extension). A
|
|
41
|
+
* bare `extenshi scan <artifact>` running on free credits therefore 403s —
|
|
42
|
+
* which is confusing when dojo just says "5 free scans left".
|
|
43
|
+
*
|
|
44
|
+
* The BFF signals this case via a stable `errorCode`
|
|
45
|
+
* (`FREE_REQUIRES_CLAIM` / `FREE_REQUIRES_VERIFIED_OWNERSHIP`). The CLI ships
|
|
46
|
+
* via npm independently of the BFF deploy, so during version skew the
|
|
47
|
+
* deployed BFF may not set `errorCode` yet — we fall back to matching the
|
|
48
|
+
* human `error` text. Either way we append actionable next steps.
|
|
49
|
+
*
|
|
50
|
+
* Exported for unit testing.
|
|
39
51
|
*/
|
|
52
|
+
export function buildAccessDeniedMessage(body) {
|
|
53
|
+
const errorMsg = typeof body.error === 'string' ? body.error : 'access denied';
|
|
54
|
+
const code = typeof body.errorCode === 'string' ? body.errorCode : '';
|
|
55
|
+
const isFreeGating = code === 'FREE_REQUIRES_CLAIM' ||
|
|
56
|
+
code === 'FREE_REQUIRES_VERIFIED_OWNERSHIP' ||
|
|
57
|
+
/free credit/i.test(errorMsg);
|
|
58
|
+
if (!isFreeGating) {
|
|
59
|
+
return `Access denied: ${errorMsg}`;
|
|
60
|
+
}
|
|
61
|
+
return (`Access denied: ${errorMsg}\n\n` +
|
|
62
|
+
`Free credits only cover extensions you have verified ownership of, and you\n` +
|
|
63
|
+
`must name one with --extension-id <id>:\n\n` +
|
|
64
|
+
` extenshi scan <artifact> --extension-id <id>\n\n` +
|
|
65
|
+
`where <id> is the numeric catalog ID of a verified extension (open the\n` +
|
|
66
|
+
`Ownership tab at ${DOJO_URL} to see your verified extensions and their IDs).\n\n` +
|
|
67
|
+
`Other options:\n` +
|
|
68
|
+
` • Claim ownership of an extension you publish:\n` +
|
|
69
|
+
` ${DOJO_URL} → Ownership tab → Claim Extension Ownership\n` +
|
|
70
|
+
` • Buy credits to scan arbitrary / unclaimed artifacts:\n` +
|
|
71
|
+
` ${CLI_BILLING_URL}`);
|
|
72
|
+
}
|
|
40
73
|
function startElapsedSpinner(label) {
|
|
41
74
|
const startedAt = Date.now();
|
|
42
75
|
const seconds = () => Math.round((Date.now() - startedAt) / 1000);
|
|
76
|
+
let base = label;
|
|
43
77
|
const spinner = ora({ text: label, stream: process.stderr }).start();
|
|
44
|
-
const
|
|
45
|
-
spinner.text = `${
|
|
46
|
-
}
|
|
78
|
+
const render = () => {
|
|
79
|
+
spinner.text = `${base} (${seconds()}s)`;
|
|
80
|
+
};
|
|
81
|
+
const ticker = setInterval(render, 1000);
|
|
47
82
|
// Don't let the 1s ticker keep the event loop alive on its own.
|
|
48
83
|
if (typeof ticker.unref === 'function')
|
|
49
84
|
ticker.unref();
|
|
50
85
|
return {
|
|
86
|
+
setText: (text) => {
|
|
87
|
+
base = text;
|
|
88
|
+
render();
|
|
89
|
+
},
|
|
51
90
|
succeed: (text) => {
|
|
52
91
|
clearInterval(ticker);
|
|
53
92
|
spinner.succeed(text);
|
|
54
93
|
},
|
|
94
|
+
fail: (text) => {
|
|
95
|
+
clearInterval(ticker);
|
|
96
|
+
spinner.fail(text);
|
|
97
|
+
},
|
|
55
98
|
stop: () => {
|
|
56
99
|
clearInterval(ticker);
|
|
57
100
|
spinner.stop();
|
|
@@ -60,7 +103,7 @@ function startElapsedSpinner(label) {
|
|
|
60
103
|
};
|
|
61
104
|
}
|
|
62
105
|
export async function runScan(opts) {
|
|
63
|
-
const { artifactPath, apiKey, apiUrl, extensionId, outputJson, skipPreflight } = opts;
|
|
106
|
+
const { artifactPath, apiKey, apiUrl, extensionId, outputJson, skipPreflight, stream } = opts;
|
|
64
107
|
// Validate artifact file exists
|
|
65
108
|
if (!fs.existsSync(artifactPath)) {
|
|
66
109
|
throw new Error(`Artifact file not found: ${artifactPath}`);
|
|
@@ -73,8 +116,11 @@ export async function runScan(opts) {
|
|
|
73
116
|
if (artifactStats.size > MAX_SIZE) {
|
|
74
117
|
throw new Error(`Artifact file exceeds 50 MB limit (${(artifactStats.size / 1024 / 1024).toFixed(1)} MB)`);
|
|
75
118
|
}
|
|
76
|
-
// Step 1: CI timeout pre-flight
|
|
77
|
-
|
|
119
|
+
// Step 1: CI timeout pre-flight.
|
|
120
|
+
// Skipped in streaming mode: the SSE keep-alive pings keep the connection
|
|
121
|
+
// alive past any outbound idle timeout, which is the very failure the
|
|
122
|
+
// pre-flight probe exists to detect — so it's redundant there.
|
|
123
|
+
if (!skipPreflight && !stream) {
|
|
78
124
|
await runPreflight(apiUrl);
|
|
79
125
|
}
|
|
80
126
|
// Step 2: Upload and scan
|
|
@@ -86,15 +132,22 @@ export async function runScan(opts) {
|
|
|
86
132
|
if (extensionId) {
|
|
87
133
|
form.append('extensionId', extensionId);
|
|
88
134
|
}
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
|
|
135
|
+
// Always start an elapsed spinner *before* the request so the console is
|
|
136
|
+
// never silent. In streaming mode this covers the otherwise-dead window —
|
|
137
|
+
// the multipart upload plus the server's time-to-first-byte — before any
|
|
138
|
+
// SSE frame arrives; renderSseStream then reuses the same handle so one
|
|
139
|
+
// continuous timer spans upload → connect → scanning → done. Legacy mode
|
|
140
|
+
// keeps the same spinner for its single long-blocking request.
|
|
141
|
+
const progress = startElapsedSpinner(stream ? `Uploading ${artifactName}` : `Scanning ${artifactName}`);
|
|
93
142
|
let response;
|
|
94
143
|
try {
|
|
95
144
|
response = await fetch(`${apiUrl}/api/v1/scan`, {
|
|
96
145
|
method: 'POST',
|
|
97
146
|
body: form,
|
|
147
|
+
// Negotiate streaming progress. Auth/billing errors still come back
|
|
148
|
+
// as JSON regardless, so the response handling branches on the
|
|
149
|
+
// actual Content-Type below, not on this header.
|
|
150
|
+
headers: stream ? { accept: 'text/event-stream' } : undefined,
|
|
98
151
|
signal: AbortSignal.timeout(SCAN_TIMEOUT_MS),
|
|
99
152
|
});
|
|
100
153
|
}
|
|
@@ -108,17 +161,57 @@ export async function runScan(opts) {
|
|
|
108
161
|
}
|
|
109
162
|
throw err;
|
|
110
163
|
}
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
164
|
+
// Auth/billing/gateway errors are always JSON (they happen before any
|
|
165
|
+
// stream starts), regardless of the Accept header → handle and throw.
|
|
166
|
+
if (!response.ok) {
|
|
167
|
+
progress.stop();
|
|
168
|
+
const body = await parseJsonBody(response, apiUrl);
|
|
169
|
+
throwForStatus(response.status, body);
|
|
170
|
+
}
|
|
171
|
+
// Step 3: Output.
|
|
172
|
+
// Streaming success is text/event-stream; legacy success (or a server that
|
|
173
|
+
// ignored the Accept header) is a single JSON body. Branch on the actual
|
|
174
|
+
// Content-Type rather than assuming the negotiated mode.
|
|
175
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
176
|
+
if (contentType.includes('text/event-stream')) {
|
|
177
|
+
// Hand the live spinner to renderSseStream — it owns the single
|
|
178
|
+
// succeed/fail settle from here, so this branch must not settle it.
|
|
179
|
+
await renderSseStream(response, outputJson, progress);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Single JSON body (legacy mode, or a server that ignored the streaming
|
|
183
|
+
// Accept header). There's no server-side progress to show, so relabel the
|
|
184
|
+
// elapsed spinner — otherwise a streaming-requested-but-JSON-answered scan
|
|
185
|
+
// would read "Uploading…" for its whole duration.
|
|
186
|
+
progress.setText(`Scanning ${artifactName}`);
|
|
116
187
|
let body;
|
|
117
188
|
try {
|
|
118
|
-
body =
|
|
189
|
+
body = await parseJsonBody(response, apiUrl);
|
|
119
190
|
}
|
|
120
|
-
catch {
|
|
191
|
+
catch (err) {
|
|
121
192
|
progress.stop();
|
|
193
|
+
throw err;
|
|
194
|
+
}
|
|
195
|
+
progress.succeed(`Scan complete in ${progress.seconds()}s`);
|
|
196
|
+
if (outputJson) {
|
|
197
|
+
process.stdout.write(`${JSON.stringify(body, null, 2)}\n`);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
renderReport(body);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Read the response body as JSON, with a clear error if the endpoint returns
|
|
205
|
+
* an HTML/plain-text page instead (404 from a misrouted gateway, 502 from
|
|
206
|
+
* Cloudflare, etc.) — blindly calling response.json() on that throws a cryptic
|
|
207
|
+
* "Unexpected token '<'" SyntaxError that hides the real failure.
|
|
208
|
+
*/
|
|
209
|
+
async function parseJsonBody(response, apiUrl) {
|
|
210
|
+
const raw = await response.text();
|
|
211
|
+
try {
|
|
212
|
+
return JSON.parse(raw);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
122
215
|
const snippet = raw.replace(/\s+/g, ' ').trim().slice(0, 200);
|
|
123
216
|
const statusText = response.statusText ? ` ${response.statusText}` : '';
|
|
124
217
|
throw new Error(`Server returned a non-JSON response (HTTP ${response.status}${statusText}).\n` +
|
|
@@ -127,32 +220,129 @@ export async function runScan(opts) {
|
|
|
127
220
|
(snippet ? ` Response: ${snippet}\n` : '') +
|
|
128
221
|
`Override the endpoint with --api-url if you use a custom deployment.`);
|
|
129
222
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
223
|
+
}
|
|
224
|
+
/** Map a non-2xx status + parsed body to an actionable thrown error. */
|
|
225
|
+
function throwForStatus(status, body) {
|
|
226
|
+
const errorMsg = typeof body.error === 'string' ? body.error : `HTTP ${status}`;
|
|
227
|
+
if (status === 401) {
|
|
228
|
+
throw new Error(`Authentication failed: ${errorMsg}\nCheck your EXTENSHI_API_KEY.`);
|
|
229
|
+
}
|
|
230
|
+
if (status === 402) {
|
|
231
|
+
throw new Error(`Insufficient credits.\nPurchase more at ${CLI_BILLING_URL}`);
|
|
232
|
+
}
|
|
233
|
+
if (status === 403) {
|
|
234
|
+
throw new Error(buildAccessDeniedMessage(body));
|
|
235
|
+
}
|
|
236
|
+
if (status === 429) {
|
|
237
|
+
const retryAfter = body.retryAfterSec ?? 60;
|
|
238
|
+
throw new Error(`Rate limit exceeded. Please wait ${retryAfter}s before trying again.`);
|
|
239
|
+
}
|
|
240
|
+
throw new Error(`Scan failed: ${errorMsg}`);
|
|
241
|
+
}
|
|
242
|
+
function safeJsonParse(data) {
|
|
243
|
+
try {
|
|
244
|
+
return JSON.parse(data);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/** Render a unicode progress bar like `[█████░░░░░]`. */
|
|
251
|
+
function progressBar(completed, total, width = 18) {
|
|
252
|
+
const filled = total > 0 ? Math.min(width, Math.round((completed / total) * width)) : 0;
|
|
253
|
+
return `[${'█'.repeat(filled)}${'░'.repeat(Math.max(0, width - filled))}]`;
|
|
254
|
+
}
|
|
255
|
+
/** Turn a progress event into a spinner line. */
|
|
256
|
+
function formatProgress(p) {
|
|
257
|
+
const stage = p.stage ?? '';
|
|
258
|
+
const completed = typeof p.completed === 'number' ? p.completed : 0;
|
|
259
|
+
const total = typeof p.total === 'number' ? p.total : null;
|
|
260
|
+
if (stage === 'accepted')
|
|
261
|
+
return 'Queued…';
|
|
262
|
+
if (stage === 'extracting')
|
|
263
|
+
return 'Extracting artifact…';
|
|
264
|
+
if (stage === 'scanning' && total) {
|
|
265
|
+
const scanner = p.scanner ? ` ${p.scanner}` : '';
|
|
266
|
+
return `Scanning ${progressBar(completed, total)} ${completed}/${total}${scanner}`;
|
|
267
|
+
}
|
|
268
|
+
// Unknown/future stage — show the raw label rather than erroring.
|
|
269
|
+
return `${stage || 'Working'}…`;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Consume the SSE progress stream, updating the shared elapsed spinner on
|
|
273
|
+
* stderr, and render the final report (pretty or JSON) once the terminal
|
|
274
|
+
* `result` event arrives. Throws on a terminal `error` event or a truncated
|
|
275
|
+
* stream.
|
|
276
|
+
*
|
|
277
|
+
* `progress` is the live spinner started in runScan *before* the request. This
|
|
278
|
+
* function owns its single terminal settle (succeed/fail) from here on — the
|
|
279
|
+
* caller must not settle it once it has been handed over. Reusing one handle
|
|
280
|
+
* (rather than starting a fresh spinner here) keeps a single continuous timer
|
|
281
|
+
* across upload → connect → scanning → done, so the line never stalls and the
|
|
282
|
+
* console is never silent.
|
|
283
|
+
*
|
|
284
|
+
* Exported for unit testing the "feedback before the first progress event"
|
|
285
|
+
* path — the regression that made the console look frozen during upload/queue.
|
|
286
|
+
*/
|
|
287
|
+
export async function renderSseStream(response, outputJson, progress) {
|
|
288
|
+
// Headers are in and the upload is done, but the worker may still be
|
|
289
|
+
// queueing before it emits the first `accepted`/`progress` frame. Swap the
|
|
290
|
+
// label now so the (still-ticking) spinner reads "Connecting to scanner
|
|
291
|
+
// (Ns)" rather than a stale "Uploading…" during that gap.
|
|
292
|
+
progress.setText('Connecting to scanner');
|
|
293
|
+
if (!response.body) {
|
|
294
|
+
progress.fail('Scan stream had no response body');
|
|
295
|
+
throw new Error('Scan stream had no response body');
|
|
296
|
+
}
|
|
297
|
+
const decoder = new TextDecoder();
|
|
298
|
+
let buffer = '';
|
|
299
|
+
let report = null;
|
|
300
|
+
let errorMessage = null;
|
|
301
|
+
const handle = (ev) => {
|
|
302
|
+
if (ev.event === 'progress') {
|
|
303
|
+
const p = safeJsonParse(ev.data);
|
|
304
|
+
if (p)
|
|
305
|
+
progress.setText(formatProgress(p));
|
|
136
306
|
}
|
|
137
|
-
if (
|
|
138
|
-
|
|
307
|
+
else if (ev.event === 'result') {
|
|
308
|
+
report = safeJsonParse(ev.data);
|
|
139
309
|
}
|
|
140
|
-
if (
|
|
141
|
-
|
|
310
|
+
else if (ev.event === 'error') {
|
|
311
|
+
const e = safeJsonParse(ev.data);
|
|
312
|
+
errorMessage = e && typeof e.error === 'string' ? e.error : 'scan failed';
|
|
142
313
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
314
|
+
};
|
|
315
|
+
try {
|
|
316
|
+
for await (const chunk of response.body) {
|
|
317
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
318
|
+
const { events, rest } = parseSseFrames(buffer);
|
|
319
|
+
buffer = rest;
|
|
320
|
+
for (const ev of events)
|
|
321
|
+
handle(ev);
|
|
146
322
|
}
|
|
147
|
-
|
|
323
|
+
// Flush any trailing frame not terminated by a blank line.
|
|
324
|
+
const { events } = parseSseFrames(`${buffer}\n\n`);
|
|
325
|
+
for (const ev of events)
|
|
326
|
+
handle(ev);
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
progress.fail('Scan stream interrupted');
|
|
330
|
+
throw err;
|
|
331
|
+
}
|
|
332
|
+
if (errorMessage) {
|
|
333
|
+
progress.fail(errorMessage);
|
|
334
|
+
throw new Error(`Scan failed: ${errorMessage}`);
|
|
335
|
+
}
|
|
336
|
+
if (!report) {
|
|
337
|
+
progress.fail('Scan ended without a result');
|
|
338
|
+
throw new Error('Scan stream ended before returning a result');
|
|
148
339
|
}
|
|
149
340
|
progress.succeed(`Scan complete in ${progress.seconds()}s`);
|
|
150
|
-
// Step 3: Output
|
|
151
341
|
if (outputJson) {
|
|
152
|
-
process.stdout.write(`${JSON.stringify(
|
|
342
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
153
343
|
}
|
|
154
344
|
else {
|
|
155
|
-
renderReport(
|
|
345
|
+
renderReport(report);
|
|
156
346
|
}
|
|
157
347
|
}
|
|
158
348
|
/**
|
package/dist/scan.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"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"}
|
package/dist/sse.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Server-Sent Events frame parser for the scan progress stream.
|
|
3
|
+
*
|
|
4
|
+
* The worker emits `text/event-stream` with named events (`progress`,
|
|
5
|
+
* `result`, `error`) plus keep-alive comment frames (`:ping`). See
|
|
6
|
+
* services/extenshi-scan-worker/PROGRESS_PROTOCOL.md.
|
|
7
|
+
*
|
|
8
|
+
* This module is intentionally pure (no I/O) so it can be unit-tested:
|
|
9
|
+
* feed it the accumulated buffer, get back the complete events plus the
|
|
10
|
+
* unconsumed tail to carry into the next chunk.
|
|
11
|
+
*/
|
|
12
|
+
export interface SseEvent {
|
|
13
|
+
/** The `event:` field value (defaults to "message" per the SSE spec). */
|
|
14
|
+
event: string;
|
|
15
|
+
/** The joined `data:` field value(s). */
|
|
16
|
+
data: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Split an accumulated SSE buffer into complete events.
|
|
20
|
+
*
|
|
21
|
+
* Frames are delimited by a blank line (`\n\n`). A partial frame at the end is
|
|
22
|
+
* returned as `rest` so the caller can prepend it to the next chunk. Comment
|
|
23
|
+
* frames (lines starting with `:`, e.g. keep-alive pings) and frames with no
|
|
24
|
+
* `data` are skipped.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseSseFrames(buffer: string): {
|
|
27
|
+
events: SseEvent[];
|
|
28
|
+
rest: string;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=sse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,QAAQ;IACxB,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAA;IACb,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAA;CACZ;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAmCnF"}
|
package/dist/sse.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Server-Sent Events frame parser for the scan progress stream.
|
|
3
|
+
*
|
|
4
|
+
* The worker emits `text/event-stream` with named events (`progress`,
|
|
5
|
+
* `result`, `error`) plus keep-alive comment frames (`:ping`). See
|
|
6
|
+
* services/extenshi-scan-worker/PROGRESS_PROTOCOL.md.
|
|
7
|
+
*
|
|
8
|
+
* This module is intentionally pure (no I/O) so it can be unit-tested:
|
|
9
|
+
* feed it the accumulated buffer, get back the complete events plus the
|
|
10
|
+
* unconsumed tail to carry into the next chunk.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Split an accumulated SSE buffer into complete events.
|
|
14
|
+
*
|
|
15
|
+
* Frames are delimited by a blank line (`\n\n`). A partial frame at the end is
|
|
16
|
+
* returned as `rest` so the caller can prepend it to the next chunk. Comment
|
|
17
|
+
* frames (lines starting with `:`, e.g. keep-alive pings) and frames with no
|
|
18
|
+
* `data` are skipped.
|
|
19
|
+
*/
|
|
20
|
+
export function parseSseFrames(buffer) {
|
|
21
|
+
// Normalize CRLF so frame splitting on `\n\n` is reliable regardless of
|
|
22
|
+
// the server's line endings. A `\r\n` straddling a chunk boundary stays in
|
|
23
|
+
// `rest` and is normalized on the next pass.
|
|
24
|
+
let buf = buffer.replace(/\r\n/g, '\n');
|
|
25
|
+
const events = [];
|
|
26
|
+
while (true) {
|
|
27
|
+
const boundary = buf.indexOf('\n\n');
|
|
28
|
+
if (boundary === -1)
|
|
29
|
+
break;
|
|
30
|
+
const frame = buf.slice(0, boundary);
|
|
31
|
+
buf = buf.slice(boundary + 2);
|
|
32
|
+
let eventName = 'message';
|
|
33
|
+
const dataLines = [];
|
|
34
|
+
for (const line of frame.split('\n')) {
|
|
35
|
+
if (line === '' || line.startsWith(':'))
|
|
36
|
+
continue; // blank or comment (keep-alive)
|
|
37
|
+
const colon = line.indexOf(':');
|
|
38
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
39
|
+
let value = colon === -1 ? '' : line.slice(colon + 1);
|
|
40
|
+
if (value.startsWith(' '))
|
|
41
|
+
value = value.slice(1); // strip one leading space
|
|
42
|
+
if (field === 'event')
|
|
43
|
+
eventName = value;
|
|
44
|
+
else if (field === 'data')
|
|
45
|
+
dataLines.push(value);
|
|
46
|
+
}
|
|
47
|
+
if (dataLines.length > 0) {
|
|
48
|
+
events.push({ event: eventName, data: dataLines.join('\n') });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { events, rest: buf };
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=sse.js.map
|
package/dist/sse.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.js","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AASH;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc;IAC5C,wEAAwE;IACxE,2EAA2E;IAC3E,6CAA6C;IAC7C,IAAI,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;IACvC,MAAM,MAAM,GAAe,EAAE,CAAA;IAE7B,OAAO,IAAI,EAAE,CAAC;QACb,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QACpC,IAAI,QAAQ,KAAK,CAAC,CAAC;YAAE,MAAK;QAE1B,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAA;QACpC,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAA;QAE7B,IAAI,SAAS,GAAG,SAAS,CAAA;QACzB,MAAM,SAAS,GAAa,EAAE,CAAA;QAE9B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAQ,CAAC,gCAAgC;YAElF,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YAC/B,MAAM,KAAK,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;YACxD,IAAI,KAAK,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;YACrD,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA,CAAC,0BAA0B;YAE5E,IAAI,KAAK,KAAK,OAAO;gBAAE,SAAS,GAAG,KAAK,CAAA;iBACnC,IAAI,KAAK,KAAK,MAAM;gBAAE,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACjD,CAAC;QAED,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC9D,CAAC;IACF,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAA;AAC7B,CAAC"}
|