@indigoai-us/hq-cli 5.251.0 → 5.253.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 +13 -0
- package/dist/command-catalog.generated.d.ts +3 -0
- package/dist/command-catalog.generated.js +4 -0
- package/dist/commands/publish.d.ts +8 -1
- package/dist/commands/publish.js +17 -4
- package/dist/commands/report.js +2 -0
- package/dist/lib/report-publish.d.ts +10 -0
- package/dist/lib/report-publish.js +44 -1
- package/dist/lib/report-snapshot.d.ts +14 -0
- package/dist/lib/report-snapshot.js +135 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.253.0] — 2026-09-27
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `hq publish --handle <handle>` publishes a pack under one of the creator handles your
|
|
10
|
+
account owns, for accounts that own more than one. The server refuses a handle you do
|
|
11
|
+
not own with a plain message. `--as` is unchanged and cannot be combined with `--handle`.
|
|
12
|
+
|
|
13
|
+
## [5.252.0] — 2026-09-27
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- `hq report publish` takes a snapshot of the finished page and publishes it as the report's preview image, using a browser already installed on the computer. A page that names its own preview image keeps it. If no browser is found the report publishes without a preview.
|
|
17
|
+
|
|
5
18
|
## [5.251.0] — 2026-09-27
|
|
6
19
|
|
|
7
20
|
## [5.250.0] — 2026-09-27
|
|
@@ -418,6 +418,9 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
418
418
|
}, {
|
|
419
419
|
readonly flags: "--on-behalf <handle>";
|
|
420
420
|
readonly description: "Alias for --as.";
|
|
421
|
+
}, {
|
|
422
|
+
readonly flags: "--handle <handle>";
|
|
423
|
+
readonly description: "Publish as one of your own claimed creator handles (for accounts that own more than one). Defaults to your first claimed handle.";
|
|
421
424
|
}];
|
|
422
425
|
readonly subcommands: readonly [];
|
|
423
426
|
}, {
|
|
@@ -530,6 +530,10 @@ export const COMMAND_CATALOG = [
|
|
|
530
530
|
{
|
|
531
531
|
"flags": "--on-behalf <handle>",
|
|
532
532
|
"description": "Alias for --as."
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
"flags": "--handle <handle>",
|
|
536
|
+
"description": "Publish as one of your own claimed creator handles (for accounts that own more than one). Defaults to your first claimed handle."
|
|
533
537
|
}
|
|
534
538
|
],
|
|
535
539
|
"subcommands": []
|
|
@@ -130,7 +130,7 @@ export declare function unwrapListing(body: Record<string, unknown>): ListingRes
|
|
|
130
130
|
* locally-resolved author handle — falling back gracefully when the server
|
|
131
131
|
* omits either field.
|
|
132
132
|
*/
|
|
133
|
-
export declare function buildListingNotice(status: number, body: Record<string, unknown>, packName: string, packVersion: string): string;
|
|
133
|
+
export declare function buildListingNotice(status: number, body: Record<string, unknown>, packName: string, packVersion: string, selectedHandle?: string): string;
|
|
134
134
|
/**
|
|
135
135
|
* The exact JSON body the deployed `POST /v1/listings` handler expects. Field
|
|
136
136
|
* shapes mirror the server contract: `type` ∈ {skill,worker}, `contributes` is
|
|
@@ -151,6 +151,12 @@ export interface ListingRequest {
|
|
|
151
151
|
* caller is staff (`@getindigo.ai`) and the server has the capability enabled.
|
|
152
152
|
*/
|
|
153
153
|
publishAsHandle?: string;
|
|
154
|
+
/**
|
|
155
|
+
* OPTIONAL explicit choice among the caller's OWN claimed creator handles
|
|
156
|
+
* (`hq publish --handle <h>`). The server refuses a handle the caller does
|
|
157
|
+
* not own (403 CREATOR_HANDLE_NOT_OWNED) instead of falling back.
|
|
158
|
+
*/
|
|
159
|
+
ownedCreatorHandle?: string;
|
|
154
160
|
/** Base64-encoded gzip pack tarball (pack.tar.gz bytes). */
|
|
155
161
|
tarball: string;
|
|
156
162
|
}
|
|
@@ -196,6 +202,7 @@ export interface PublishResult {
|
|
|
196
202
|
*/
|
|
197
203
|
export declare function runPublish(targetPath: string, deps: PublishDeps, opts?: {
|
|
198
204
|
publishAsHandle?: string;
|
|
205
|
+
creatorHandle?: string;
|
|
199
206
|
}): Promise<PublishResult>;
|
|
200
207
|
export declare function registerPublishCommand(program: Command): void;
|
|
201
208
|
//# sourceMappingURL=publish.d.ts.map
|
package/dist/commands/publish.js
CHANGED
|
@@ -231,7 +231,7 @@ export function unwrapListing(body) {
|
|
|
231
231
|
* locally-resolved author handle — falling back gracefully when the server
|
|
232
232
|
* omits either field.
|
|
233
233
|
*/
|
|
234
|
-
export function buildListingNotice(status, body, packName, packVersion) {
|
|
234
|
+
export function buildListingNotice(status, body, packName, packVersion, selectedHandle) {
|
|
235
235
|
if (status === 409) {
|
|
236
236
|
throw new Error(`${packName}@${packVersion} is already published (duplicate version). ` +
|
|
237
237
|
`Bump the version in package.yaml and re-run \`hq publish\`.`);
|
|
@@ -246,6 +246,10 @@ export function buildListingNotice(status, body, packName, packVersion) {
|
|
|
246
246
|
}
|
|
247
247
|
if (status === 401 || status === 403) {
|
|
248
248
|
const code = typeof body.code === 'string' ? body.code : '';
|
|
249
|
+
if (code === 'CREATOR_HANDLE_NOT_OWNED') {
|
|
250
|
+
const h = selectedHandle ?? '(unknown)';
|
|
251
|
+
throw new Error(`You do not own the creator handle ${h}. Claim it first with hq creators apply --handle ${h}.`);
|
|
252
|
+
}
|
|
249
253
|
if (code === 'NOT_VERIFIED_CREATOR') {
|
|
250
254
|
throw new Error('Only verified creators can publish to the marketplace. ' +
|
|
251
255
|
'Run `hq creators apply --reason "..."` to request verified-creator access; ' +
|
|
@@ -287,6 +291,12 @@ export async function runPublish(targetPath, deps, opts = {}) {
|
|
|
287
291
|
const publishAsHandle = typeof opts.publishAsHandle === 'string' && opts.publishAsHandle.length > 0
|
|
288
292
|
? opts.publishAsHandle
|
|
289
293
|
: undefined;
|
|
294
|
+
const selectedHandle = typeof opts.creatorHandle === 'string' && opts.creatorHandle.trim().length > 0
|
|
295
|
+
? opts.creatorHandle.trim().replace(/^@/, '')
|
|
296
|
+
: undefined;
|
|
297
|
+
if (publishAsHandle && selectedHandle) {
|
|
298
|
+
throw new Error('Use either --as or --handle, not both. Nothing was uploaded.');
|
|
299
|
+
}
|
|
290
300
|
// Resolve a payload dir. We stamp/validate against a temp copy so we never
|
|
291
301
|
// mutate the user's working tree's package.yaml.
|
|
292
302
|
const absTarget = path.resolve(process.cwd(), targetPath);
|
|
@@ -335,12 +345,13 @@ export async function runPublish(targetPath, deps, opts = {}) {
|
|
|
335
345
|
// server is authoritative for attribution either way). Normal: our
|
|
336
346
|
// resolved handle is a backwards-compat fallback under the server's
|
|
337
347
|
// claimed-handle lookup.
|
|
338
|
-
creatorHandle: publishAsHandle ?? author.handle,
|
|
348
|
+
creatorHandle: publishAsHandle ?? selectedHandle ?? author.handle,
|
|
339
349
|
...(publishAsHandle ? { publishAsHandle } : {}),
|
|
350
|
+
...(selectedHandle ? { ownedCreatorHandle: selectedHandle } : {}),
|
|
340
351
|
tarball: Buffer.from(tarballBytes).toString('base64'),
|
|
341
352
|
};
|
|
342
353
|
const { status, body } = await deps.upload({ token, listing });
|
|
343
|
-
const notice = buildListingNotice(status, body, pkg.name, pkg.version);
|
|
354
|
+
const notice = buildListingNotice(status, body, pkg.name, pkg.version, selectedHandle);
|
|
344
355
|
// Unwrap the `{ listing }` success envelope the server sends so attribution
|
|
345
356
|
// reads the server's resolved handle (fall back to top-level for compat).
|
|
346
357
|
const serverHandle = unwrapListing(body).creatorHandle?.trim();
|
|
@@ -362,6 +373,8 @@ export function registerPublishCommand(program) {
|
|
|
362
373
|
.option('--as <handle>', 'Publish on behalf of a seeded, unclaimed creator by handle (HQ staff only). ' +
|
|
363
374
|
'The listing is attributed to that creator instead of you.')
|
|
364
375
|
.option('--on-behalf <handle>', 'Alias for --as.')
|
|
376
|
+
.option('--handle <handle>', 'Publish as one of your own claimed creator handles (for accounts that own more than one). ' +
|
|
377
|
+
'Defaults to your first claimed handle.')
|
|
365
378
|
.action(async (targetPath, options) => {
|
|
366
379
|
try {
|
|
367
380
|
const publishAsHandle = options.as ?? options.onBehalf;
|
|
@@ -390,7 +403,7 @@ export function registerPublishCommand(program) {
|
|
|
390
403
|
},
|
|
391
404
|
hqRoot,
|
|
392
405
|
hqVersion: readHqVersion(hqRoot),
|
|
393
|
-
}, { publishAsHandle });
|
|
406
|
+
}, { publishAsHandle, creatorHandle: options.handle });
|
|
394
407
|
console.log(chalk.green(result.notice));
|
|
395
408
|
// Only print the locally-resolved author as a fallback when the server
|
|
396
409
|
// did NOT return its own resolved handle (already in the notice).
|
package/dist/commands/report.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** `hq report` — safe channel-report lifecycle commands for bot runbooks. */
|
|
2
|
+
import { snapshotReport } from "../lib/report-snapshot.js";
|
|
2
3
|
import * as fs from "node:fs";
|
|
3
4
|
import * as os from "node:os";
|
|
4
5
|
import * as path from "node:path";
|
|
@@ -339,6 +340,7 @@ async function publishWithOrg(token, report, directory, draft) {
|
|
|
339
340
|
return deployReportStatic(token, { ...input, org });
|
|
340
341
|
},
|
|
341
342
|
now: () => new Date().toISOString(),
|
|
343
|
+
snapshot: (source) => snapshotReport(source),
|
|
342
344
|
});
|
|
343
345
|
rememberReportChannelAfterSuccess(published);
|
|
344
346
|
return published;
|
|
@@ -91,7 +91,17 @@ export interface ReportPublishDependencies {
|
|
|
91
91
|
to: string;
|
|
92
92
|
body: string;
|
|
93
93
|
}): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* PNG of the finished page for the gallery card, or null when none can be
|
|
96
|
+
* made. It is given a staged copy of the files as they will be published. Only asked for when the page does not name its own preview image.
|
|
97
|
+
*/
|
|
98
|
+
snapshot?(directory: string): Promise<Buffer | null>;
|
|
94
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Add a snapshot of the page as its preview when it has none of its own.
|
|
102
|
+
* A snapshot that fails never fails the publish.
|
|
103
|
+
*/
|
|
104
|
+
export declare function withPreviewSnapshot(source: ReportSource, snapshot: ReportPublishDependencies["snapshot"]): Promise<ReportSource>;
|
|
95
105
|
/**
|
|
96
106
|
* Plain owner DM after a failed run. Suppressed for the publish lock
|
|
97
107
|
* ("already running") so a second run does not page the owner.
|
|
@@ -10,12 +10,55 @@ import * as fs from "node:fs";
|
|
|
10
10
|
import * as os from "node:os";
|
|
11
11
|
import * as path from "node:path";
|
|
12
12
|
import { gzipSync } from "node:zlib";
|
|
13
|
+
import { REPORT_PREVIEW_FILE } from "./report-snapshot.js";
|
|
13
14
|
import { loadUpdateReportRunbook } from "./report-templates.js";
|
|
14
15
|
/** Mirrors the deploy skill's static-report guardrails. */
|
|
15
16
|
export const REPORT_SOURCE_MAX_BYTES = 25 * 1024 * 1024;
|
|
16
17
|
export const REPORT_BUNDLE_MAX_BYTES = 10 * 1024 * 1024;
|
|
17
18
|
export const REPORT_SOURCE_MAX_FILES = 100;
|
|
18
19
|
export const REPORT_CSP = "default-src 'none'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; connect-src 'none'; frame-src 'none'; form-action 'none'";
|
|
20
|
+
/**
|
|
21
|
+
* Write the files exactly as they will be published, protected index.html
|
|
22
|
+
* included, so the snapshot renders under the same rules as the live page
|
|
23
|
+
* and never opens the bot's raw page.
|
|
24
|
+
*/
|
|
25
|
+
function stageForSnapshot(files) {
|
|
26
|
+
const stage = fs.mkdtempSync(path.join(os.tmpdir(), "hq-report-stage-"));
|
|
27
|
+
for (const file of files) {
|
|
28
|
+
const target = path.resolve(stage, file.path);
|
|
29
|
+
if (target !== stage && !target.startsWith(stage + path.sep))
|
|
30
|
+
continue;
|
|
31
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
32
|
+
fs.writeFileSync(target, Buffer.from(file.content, file.encoding === "base64" ? "base64" : "utf8"));
|
|
33
|
+
}
|
|
34
|
+
return stage;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Add a snapshot of the page as its preview when it has none of its own.
|
|
38
|
+
* A snapshot that fails never fails the publish.
|
|
39
|
+
*/
|
|
40
|
+
export async function withPreviewSnapshot(source, snapshot) {
|
|
41
|
+
if (source.previewImage || !snapshot)
|
|
42
|
+
return source;
|
|
43
|
+
let image = null;
|
|
44
|
+
let stage = null;
|
|
45
|
+
try {
|
|
46
|
+
stage = stageForSnapshot(source.files);
|
|
47
|
+
image = await snapshot(stage);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
image = null;
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
if (stage)
|
|
54
|
+
fs.rmSync(stage, { recursive: true, force: true });
|
|
55
|
+
}
|
|
56
|
+
if (!image || image.byteLength === 0)
|
|
57
|
+
return source;
|
|
58
|
+
const files = source.files.filter((file) => file.path !== REPORT_PREVIEW_FILE);
|
|
59
|
+
files.push({ path: REPORT_PREVIEW_FILE, content: image.toString("base64"), encoding: "base64" });
|
|
60
|
+
return { ...source, files, previewImage: REPORT_PREVIEW_FILE };
|
|
61
|
+
}
|
|
19
62
|
/**
|
|
20
63
|
* Plain owner DM after a failed run. Suppressed for the publish lock
|
|
21
64
|
* ("already running") so a second run does not page the owner.
|
|
@@ -277,7 +320,7 @@ export async function publishReport(report, directory, draft, deps) {
|
|
|
277
320
|
throw error;
|
|
278
321
|
throw new ReportPublishError("Could not take the report run lock.");
|
|
279
322
|
}
|
|
280
|
-
const source = loadReportSource(directory);
|
|
323
|
+
const source = await withPreviewSnapshot(loadReportSource(directory), deps.snapshot);
|
|
281
324
|
const deployment = await deps.deploy({
|
|
282
325
|
slug: reportDeploySlug(current, draft),
|
|
283
326
|
files: source.files,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** File name of the snapshot inside the published report. */
|
|
2
|
+
export declare const REPORT_PREVIEW_FILE = "hq-preview.png";
|
|
3
|
+
export declare const REPORT_PREVIEW_WIDTH = 1280;
|
|
4
|
+
export declare const REPORT_PREVIEW_HEIGHT = 720;
|
|
5
|
+
/** First installed browser that can take a snapshot, or null. */
|
|
6
|
+
export declare function findSnapshotBrowser(env?: NodeJS.ProcessEnv, platform?: string, exists?: (file: string) => boolean): string | null;
|
|
7
|
+
export declare function snapshotArgs(indexFile: string, outFile: string, profileDir: string): string[];
|
|
8
|
+
export type SnapshotRunner = (browser: string, args: string[], outFile: string, timeoutMs: number) => Promise<void>;
|
|
9
|
+
/** PNG of the report's first screen, or null when one cannot be made. */
|
|
10
|
+
export declare function snapshotReport(directory: string, options?: {
|
|
11
|
+
browser?: string | null;
|
|
12
|
+
run?: SnapshotRunner;
|
|
13
|
+
}): Promise<Buffer | null>;
|
|
14
|
+
//# sourceMappingURL=report-snapshot.d.ts.map
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Report preview snapshot.
|
|
3
|
+
*
|
|
4
|
+
* Renders a report's index.html in a browser already installed on this
|
|
5
|
+
* computer and returns a PNG of the first screen. The snapshot is a nicety:
|
|
6
|
+
* every failure (no browser, timeout, empty file) returns null and the
|
|
7
|
+
* publish carries on without a preview.
|
|
8
|
+
*/
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
import * as fs from "node:fs";
|
|
11
|
+
import * as os from "node:os";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import { pathToFileURL } from "node:url";
|
|
14
|
+
/** File name of the snapshot inside the published report. */
|
|
15
|
+
export const REPORT_PREVIEW_FILE = "hq-preview.png";
|
|
16
|
+
export const REPORT_PREVIEW_WIDTH = 1280;
|
|
17
|
+
export const REPORT_PREVIEW_HEIGHT = 720;
|
|
18
|
+
const SNAPSHOT_TIMEOUT_MS = 30_000;
|
|
19
|
+
const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47]);
|
|
20
|
+
const BROWSER_PATHS = {
|
|
21
|
+
darwin: [
|
|
22
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
23
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
24
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
25
|
+
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
26
|
+
],
|
|
27
|
+
linux: [
|
|
28
|
+
"/usr/bin/google-chrome",
|
|
29
|
+
"/usr/bin/google-chrome-stable",
|
|
30
|
+
"/usr/bin/chromium",
|
|
31
|
+
"/usr/bin/chromium-browser",
|
|
32
|
+
"/snap/bin/chromium",
|
|
33
|
+
],
|
|
34
|
+
win32: [
|
|
35
|
+
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
36
|
+
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
|
37
|
+
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
/** First installed browser that can take a snapshot, or null. */
|
|
41
|
+
export function findSnapshotBrowser(env = process.env, platform = process.platform, exists = fs.existsSync) {
|
|
42
|
+
const override = (env.HQ_REPORT_BROWSER ?? "").trim();
|
|
43
|
+
if (override)
|
|
44
|
+
return exists(override) ? override : null;
|
|
45
|
+
return (BROWSER_PATHS[platform] ?? []).find((file) => exists(file)) ?? null;
|
|
46
|
+
}
|
|
47
|
+
export function snapshotArgs(indexFile, outFile, profileDir) {
|
|
48
|
+
return [
|
|
49
|
+
"--headless=new",
|
|
50
|
+
"--disable-gpu",
|
|
51
|
+
"--hide-scrollbars",
|
|
52
|
+
"--no-first-run",
|
|
53
|
+
"--no-default-browser-check",
|
|
54
|
+
"--disable-extensions",
|
|
55
|
+
`--user-data-dir=${profileDir}`,
|
|
56
|
+
`--window-size=${REPORT_PREVIEW_WIDTH},${REPORT_PREVIEW_HEIGHT}`,
|
|
57
|
+
"--virtual-time-budget=5000",
|
|
58
|
+
`--screenshot=${outFile}`,
|
|
59
|
+
pathToFileURL(indexFile).toString(),
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
function stop(child) {
|
|
63
|
+
if (child.pid === undefined)
|
|
64
|
+
return;
|
|
65
|
+
try {
|
|
66
|
+
// The browser is its own process group, so its helpers stop with it.
|
|
67
|
+
if (process.platform === "win32")
|
|
68
|
+
child.kill("SIGKILL");
|
|
69
|
+
else
|
|
70
|
+
process.kill(-child.pid, "SIGKILL");
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Already gone.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Some browser versions write the snapshot and then never exit. The run is
|
|
78
|
+
* done when the file is complete, so the browser is stopped at that point
|
|
79
|
+
* instead of waited on.
|
|
80
|
+
*/
|
|
81
|
+
const runBrowser = (browser, args, outFile, timeoutMs) => new Promise((resolve, reject) => {
|
|
82
|
+
const child = spawn(browser, args, { stdio: "ignore", detached: process.platform !== "win32" });
|
|
83
|
+
let lastSize = -1;
|
|
84
|
+
let settled = false;
|
|
85
|
+
const finish = (error) => {
|
|
86
|
+
if (settled)
|
|
87
|
+
return;
|
|
88
|
+
settled = true;
|
|
89
|
+
clearInterval(poll);
|
|
90
|
+
clearTimeout(limit);
|
|
91
|
+
stop(child);
|
|
92
|
+
if (error)
|
|
93
|
+
reject(error);
|
|
94
|
+
else
|
|
95
|
+
resolve();
|
|
96
|
+
};
|
|
97
|
+
const poll = setInterval(() => {
|
|
98
|
+
let size = 0;
|
|
99
|
+
try {
|
|
100
|
+
size = fs.statSync(outFile).size;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (size > 0 && size === lastSize)
|
|
106
|
+
finish();
|
|
107
|
+
lastSize = size;
|
|
108
|
+
}, 250);
|
|
109
|
+
const limit = setTimeout(() => finish(new Error("The snapshot timed out.")), timeoutMs);
|
|
110
|
+
child.once("error", (error) => finish(error));
|
|
111
|
+
child.once("exit", () => finish(fs.existsSync(outFile) ? undefined : new Error("The browser wrote no snapshot.")));
|
|
112
|
+
});
|
|
113
|
+
/** PNG of the report's first screen, or null when one cannot be made. */
|
|
114
|
+
export async function snapshotReport(directory, options = {}) {
|
|
115
|
+
const browser = options.browser === undefined ? findSnapshotBrowser() : options.browser;
|
|
116
|
+
if (!browser)
|
|
117
|
+
return null;
|
|
118
|
+
const indexFile = path.join(path.resolve(directory), "index.html");
|
|
119
|
+
if (!fs.existsSync(indexFile))
|
|
120
|
+
return null;
|
|
121
|
+
const work = fs.mkdtempSync(path.join(os.tmpdir(), "hq-report-preview-"));
|
|
122
|
+
try {
|
|
123
|
+
const outFile = path.join(work, REPORT_PREVIEW_FILE);
|
|
124
|
+
await (options.run ?? runBrowser)(browser, snapshotArgs(indexFile, outFile, path.join(work, "profile")), outFile, SNAPSHOT_TIMEOUT_MS);
|
|
125
|
+
const bytes = fs.readFileSync(outFile);
|
|
126
|
+
return bytes.subarray(0, 4).equals(PNG_MAGIC) ? bytes : null;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
fs.rmSync(work, { recursive: true, force: true });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=report-snapshot.js.map
|