@haystackeditor/cli 0.23.2 → 0.24.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/README.md +4 -1
- package/dist/assets/telemetry/runtime.cjs +8 -0
- package/dist/commands/crawl-contract.js +9 -0
- package/dist/commands/db-profile-upload.js +20 -1
- package/dist/commands/init.js +23 -4
- package/dist/commands/onboarding-notes.js +63 -0
- package/dist/commands/telemetry.d.ts +154 -0
- package/dist/commands/telemetry.js +194 -23
- package/dist/commands/verify-onboarding.js +34 -2
- package/dist/commands/verify-precompute.js +13 -4
- package/dist/commands/verify.js +287 -4
- package/dist/index.js +58 -2
- package/dist/schema.js +4 -3
- package/dist/telemetry/next-loader.cjs +93 -0
- package/dist/telemetry/next.d.ts +33 -0
- package/dist/telemetry/next.js +168 -0
- package/dist/utils/haystack-api.js +14 -9
- package/package.json +9 -2
- package/schemas/init.v1.json +4 -3
- package/schemas/pre-verify.v1.json +178 -0
- package/schemas/verify-answer.v1.json +8 -1
- package/schemas/verify.v1.json +22 -1
package/README.md
CHANGED
|
@@ -159,7 +159,10 @@ Intent is optional there: `--intent-file <path>` accepts JSON with exactly
|
|
|
159
159
|
`problem`, `goal`, and `intended_outcomes`. It waits up to 35 minutes by
|
|
160
160
|
default; pass `--no-wait` to return after the fleet run is queued. Repeating the
|
|
161
161
|
same repository, commits, and intent reuses the same run; choose a new
|
|
162
|
-
`--idempotency-key` when a fresh execution is intentional. A
|
|
162
|
+
`--idempotency-key` when a fresh execution is intentional. A fresh run on the
|
|
163
|
+
same base reuses the repository's onboarding: phases that finished are not
|
|
164
|
+
redone, and a phase that stopped terminally starts again once the fleet runs a
|
|
165
|
+
different engine release (on the same release it stays stopped). A wait that expires
|
|
163
166
|
prints `timed_out: true` and `next_command`, then exits 2. Use the returned
|
|
164
167
|
account-bound `next_command` verbatim on machines with multiple saved accounts.
|
|
165
168
|
|
|
@@ -416,6 +416,14 @@ function loadInstrumentation() {
|
|
|
416
416
|
allowedFields: safeArrayIsArray(site.allowed_fields)
|
|
417
417
|
? safeArraySlice(site.allowed_fields, 0)
|
|
418
418
|
: [],
|
|
419
|
+
// Source position, present only when the build instrumented source
|
|
420
|
+
// itself (bundler loader). Joins to the change's function line ranges.
|
|
421
|
+
...(safeNumberIsSafeInteger(site.line) && site.line >= 1
|
|
422
|
+
? {
|
|
423
|
+
line: site.line,
|
|
424
|
+
...(safeNumberIsSafeInteger(site.column) && site.column >= 0 ? { column: site.column } : {}),
|
|
425
|
+
}
|
|
426
|
+
: {}),
|
|
419
427
|
})]);
|
|
420
428
|
const sites = new SafeMap();
|
|
421
429
|
for (let index = 0; index < siteEntries.length; index++) {
|
|
@@ -4,6 +4,10 @@ export const CRAWL_BUDGET_MIN_MS = 60_000;
|
|
|
4
4
|
export const CRAWL_BUDGET_MAX_MS = 30 * 60_000;
|
|
5
5
|
export const CRAWL_MAX_FINDINGS = 50;
|
|
6
6
|
export const CRAWL_MAX_FINDING_STEPS = 64;
|
|
7
|
+
/** Amendment 17: an intent or one idea is at most this many characters, and a crawl takes at most CRAWL_MAX_IDEAS ideas. */
|
|
8
|
+
export const CRAWL_MAX_TEXT_CHARS = 2000;
|
|
9
|
+
export const CRAWL_MAX_IDEAS = 64;
|
|
10
|
+
export const CRAWL_POOLS = ['fleet', 'freestyle', 'auto'];
|
|
7
11
|
/** Amendment 9: what a crawl has found so far (`exploring`) or its answer at the budget (`answered`), before the sealed manifest;
|
|
8
12
|
* findings carry no images. A read may pass `waitAfter=<updatedAt>` to be held until the crawl changes (at most CRAWL_WAIT_MAX_MS). */
|
|
9
13
|
export const CRAWL_WAIT_MAX_MS = 25_000;
|
|
@@ -11,3 +15,8 @@ export const CRAWL_WAIT_MAX_MS = 25_000;
|
|
|
11
15
|
* (how many times the copy's calls reached the stand-in across the crawl's preparation and all its replays, not user actions). */
|
|
12
16
|
export const CRAWL_MAX_STAND_IN_ROWS = 200;
|
|
13
17
|
export const CRAWL_MAX_STAND_IN_LINES = 20;
|
|
18
|
+
/** Amendment 18: the brief's bounds. */
|
|
19
|
+
export const CRAWL_BRIEF_MAX_FUNCTIONS = 2000;
|
|
20
|
+
export const CRAWL_BRIEF_MAX_GROUPS = 2000;
|
|
21
|
+
/** Amendment 19: rows one brief function keeps per kind of production evidence (the rest counted). */
|
|
22
|
+
export const CRAWL_BRIEF_MAX_PRODUCTION_ROWS = 100;
|
|
@@ -14,7 +14,7 @@ import chalk from 'chalk';
|
|
|
14
14
|
import { DATABASE_PROFILE_UPLOAD_REQUEST_VERSION, parseDatabaseProfileUpload, } from './db-profile-contract.js';
|
|
15
15
|
import { readDatabaseProfileFile } from './db-profile-show.js';
|
|
16
16
|
import { resolveAuthContext } from '../utils/auth.js';
|
|
17
|
-
import { classifyHttpError, haystackApiUrl } from '../utils/haystack-api.js';
|
|
17
|
+
import { classifyHttpError, haystackApiUrl, readWithRetries, transientReadFailure } from '../utils/haystack-api.js';
|
|
18
18
|
export const DATABASE_PROFILES_PATH = '/api/agent/cloud-verifier/database-profiles';
|
|
19
19
|
/** GitHub's owner and repository name alphabets. */
|
|
20
20
|
const REPOSITORY = /^([A-Za-z0-9](?:[A-Za-z0-9-]{0,38}))\/([A-Za-z0-9_.-]{1,100})$/u;
|
|
@@ -43,7 +43,23 @@ export function buildDatabaseProfileUploadRequest(file, options) {
|
|
|
43
43
|
},
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
+
// The service stores a profile by its content hash, so sending the same
|
|
47
|
+
// request again after a dropped connection or a restarting service stores
|
|
48
|
+
// nothing new: the upload is asked again on the same transient failures a
|
|
49
|
+
// read is (readWithRetries: three more tries, 2 s, 5 s, 10 s apart).
|
|
46
50
|
export async function uploadDatabaseProfile(request, token) {
|
|
51
|
+
try {
|
|
52
|
+
return await readWithRetries(() => uploadDatabaseProfileOnce(request, token));
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const transient = transientReadFailure(error);
|
|
56
|
+
if (transient === null)
|
|
57
|
+
throw error;
|
|
58
|
+
throw new Error(`Haystack API ${DATABASE_PROFILES_PATH} did not store the profile after 4 tries (${transient}). `
|
|
59
|
+
+ 'Nothing was stored twice: run the same command again once the network or service is back.', { cause: error });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function uploadDatabaseProfileOnce(request, token) {
|
|
47
63
|
const response = await fetch(haystackApiUrl(DATABASE_PROFILES_PATH), {
|
|
48
64
|
method: 'POST',
|
|
49
65
|
headers: {
|
|
@@ -62,6 +78,9 @@ export async function uploadDatabaseProfile(request, token) {
|
|
|
62
78
|
body = await response.json();
|
|
63
79
|
}
|
|
64
80
|
catch (error) {
|
|
81
|
+
// A connection dropped while the body is read is a transport failure, so the upload is asked again.
|
|
82
|
+
if (transientReadFailure(error) !== null)
|
|
83
|
+
throw error;
|
|
65
84
|
throw new Error(`Haystack API ${DATABASE_PROFILES_PATH} returned a body that is not JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
66
85
|
}
|
|
67
86
|
const upload = parseDatabaseProfileUpload(body);
|
package/dist/commands/init.js
CHANGED
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
* 2. a short marked block in AGENTS.md (and in CLAUDE.md when the repository
|
|
12
12
|
* keeps one) telling coding agents to run `haystack verify` after a change.
|
|
13
13
|
* It then starts onboarding the app at the base `haystack verify` would use, so
|
|
14
|
-
* the app is usually ready by the first verify.
|
|
14
|
+
* the app is usually ready by the first verify. With --notes, the coding agent's
|
|
15
|
+
* notes on how the app runs, signs people in and is used (onboarding-notes.ts)
|
|
16
|
+
* become the repository's onboarding notes first, so onboarding plans from them. Running it again changes only
|
|
15
17
|
* what is missing or out of date and shows where onboarding is.
|
|
16
18
|
*
|
|
17
19
|
* Exit codes: 0 set up (onboarding running or ready), 1 failed, 2 changes shown
|
|
@@ -30,7 +32,8 @@ import { HAYSTACK_APP_INSTALL_URL, HaystackApiError, INSTALLATION_REQUIRED_CODE
|
|
|
30
32
|
import { findGitRoot } from '../utils/hooks.js';
|
|
31
33
|
import { assertNoSymlinkOnPath } from '../utils/safe-write.js';
|
|
32
34
|
import { CLAUDE_LOCAL_SETTINGS, CLAUDE_SHARED_SETTINGS, HAYSTACK_PRECOMPUTE_HOOK_COMMAND, claudeEntries, claudeSettingsPath, isCLIInstalled, isGitIgnored, isInstalledGlobally, isPrecomputeCommand, readClaudeSettings, } from './install-session-hooks.js';
|
|
33
|
-
import {
|
|
35
|
+
import { NOTE_FIELDS, readNotes } from './onboarding-notes.js';
|
|
36
|
+
import { formatOnboarding, reportOnboardingState, startOnboarding, writeOnboardingNotes } from './verify-onboarding.js';
|
|
34
37
|
import { deriveBaseSha, EXPLICIT_WALL_MS, resolveOriginRepository } from './verify-precompute.js';
|
|
35
38
|
const NOTE_BEGIN = '<!-- haystack:begin -->';
|
|
36
39
|
const NOTE_END = '<!-- haystack:end -->';
|
|
@@ -168,7 +171,10 @@ function ignoreRule(gitRoot, path) {
|
|
|
168
171
|
}
|
|
169
172
|
/** Claude Code's Stop hook in the per-developer settings, and the exclude line that keeps that file out of commits. */
|
|
170
173
|
function planStopHook(gitRoot, notices) {
|
|
171
|
-
|
|
174
|
+
// Claude Code sets CLAUDECODE=1 in every command it runs, including from the desktop app and IDE
|
|
175
|
+
// extensions, where `claude` is often not on PATH.
|
|
176
|
+
const claudeCodeInUse = process.env.CLAUDECODE === '1' || isPlainDirectory(join(gitRoot, '.claude')) || isCLIInstalled('claude');
|
|
177
|
+
if (!claudeCodeInUse)
|
|
172
178
|
return [];
|
|
173
179
|
const settingsPath = `.claude/${CLAUDE_LOCAL_SETTINGS}`;
|
|
174
180
|
const localPath = claudeSettingsPath(gitRoot, CLAUDE_LOCAL_SETTINGS);
|
|
@@ -328,11 +334,15 @@ export async function initCommand(options) {
|
|
|
328
334
|
throw new Error('This checkout has no GitHub `origin` remote. Haystack verifies GitHub repositories: add the repository as `origin`, then run `haystack init` again.');
|
|
329
335
|
}
|
|
330
336
|
const repository = `${origin.owner}/${origin.repository}`;
|
|
337
|
+
// Read before anything else, so notes that cannot be used stop init with nothing done.
|
|
338
|
+
const notes = options.notes === undefined ? null : readNotes(options.notes);
|
|
331
339
|
const plan = planInit(gitRoot);
|
|
340
|
+
let notesWritten = null;
|
|
332
341
|
const finish = (status, onboarding, exitCode) => {
|
|
333
342
|
if (options.json) {
|
|
334
343
|
const changes = plan.changes.map(({ path, action, reason, diff }) => ({ path, action, reason, diff }));
|
|
335
|
-
process.stdout.write(`${JSON.stringify(withSchema('init', { status, repository, changes, notices: plan.notices,
|
|
344
|
+
process.stdout.write(`${JSON.stringify(withSchema('init', { status, repository, changes, notices: plan.notices, notes: notesWritten,
|
|
345
|
+
onboarding }), null, 2)}\n`);
|
|
336
346
|
}
|
|
337
347
|
process.exitCode = exitCode;
|
|
338
348
|
};
|
|
@@ -347,6 +357,10 @@ export async function initCommand(options) {
|
|
|
347
357
|
}
|
|
348
358
|
for (const notice of plan.notices)
|
|
349
359
|
say(`\n${chalk.yellow(notice)}`);
|
|
360
|
+
if (notes) {
|
|
361
|
+
const given = NOTE_FIELDS.filter(field => notes[field] !== '');
|
|
362
|
+
say(`\nOnboarding notes from ${options.notes === '-' ? 'stdin' : options.notes}: ${given.join(', ')}`);
|
|
363
|
+
}
|
|
350
364
|
say('');
|
|
351
365
|
let token;
|
|
352
366
|
try {
|
|
@@ -373,6 +387,11 @@ export async function initCommand(options) {
|
|
|
373
387
|
}
|
|
374
388
|
let onboarding;
|
|
375
389
|
try {
|
|
390
|
+
// The notes first: onboarding plans from the facts it starts with, and new notes are new facts (a new onboarding).
|
|
391
|
+
if (notes) {
|
|
392
|
+
notesWritten = await writeOnboardingNotes(repository, notes, token);
|
|
393
|
+
say(notesWritten === 'written' ? chalk.green('✓ Wrote the onboarding notes') : chalk.dim('The onboarding notes are already these.'));
|
|
394
|
+
}
|
|
376
395
|
onboarding = await startOnboarding(repository, deriveBaseSha(gitRoot, Date.now() + EXPLICIT_WALL_MS), token);
|
|
377
396
|
}
|
|
378
397
|
catch (error) {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The notes a coding agent gives onboarding (APP-ONBOARDING-V1 rule 7, amendment 18): what it knows of how this repository's
|
|
3
|
+
* app runs, signs people in and is used, read from a JSON file (`haystack init --notes <file>`, `-` for stdin):
|
|
4
|
+
*
|
|
5
|
+
* { "run": "...", "signIn": "...", "workflow": "..." }
|
|
6
|
+
*
|
|
7
|
+
* Each is plain text; any may be left out, at least one must say something. Onboarding plans from them and still proves
|
|
8
|
+
* everything it takes from them.
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
export const NOTE_FIELDS = ['run', 'signIn', 'workflow'];
|
|
12
|
+
/** ONBOARDING_NOTE_MAX_CHARS in onboarding_contracts.ts: characters (code points) per note. */
|
|
13
|
+
export const NOTE_MAX_CHARS = 8_000;
|
|
14
|
+
export const NOTES_FORMAT = `{ "run": "how production installs, builds and starts the app", "signIn": "how a person gets an account and signs in, and the kinds of users", "workflow": "what a signed-in user mainly does" }`;
|
|
15
|
+
/** The notes in a file's text, trimmed; an error that says what to fix otherwise. */
|
|
16
|
+
export function parseNotes(text, source) {
|
|
17
|
+
let value;
|
|
18
|
+
try {
|
|
19
|
+
value = JSON.parse(text);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw new Error(`${source} is not JSON (${error instanceof Error ? error.message : String(error)}). Write ${NOTES_FORMAT}.`);
|
|
23
|
+
}
|
|
24
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
25
|
+
throw new Error(`${source} is not a JSON object. Write ${NOTES_FORMAT}.`);
|
|
26
|
+
const row = value;
|
|
27
|
+
const unknown = Object.keys(row).filter(key => !NOTE_FIELDS.includes(key));
|
|
28
|
+
if (unknown.length)
|
|
29
|
+
throw new Error(`${source} has ${unknown.map(key => JSON.stringify(key)).join(', ')}; the notes are "run", "signIn" and "workflow".`);
|
|
30
|
+
const notes = { run: '', signIn: '', workflow: '' };
|
|
31
|
+
for (const field of NOTE_FIELDS) {
|
|
32
|
+
const note = row[field];
|
|
33
|
+
if (note === undefined)
|
|
34
|
+
continue;
|
|
35
|
+
if (typeof note !== 'string')
|
|
36
|
+
throw new Error(`${source}: "${field}" is not text.`);
|
|
37
|
+
if (note.includes('\0'))
|
|
38
|
+
throw new Error(`${source}: "${field}" contains a NUL character.`);
|
|
39
|
+
const trimmed = note.trim();
|
|
40
|
+
const length = [...trimmed].length;
|
|
41
|
+
if (length > NOTE_MAX_CHARS)
|
|
42
|
+
throw new Error(`${source}: "${field}" is ${length} characters; a note is at most ${NOTE_MAX_CHARS}.`);
|
|
43
|
+
notes[field] = trimmed;
|
|
44
|
+
}
|
|
45
|
+
if (NOTE_FIELDS.every(field => notes[field] === ''))
|
|
46
|
+
throw new Error(`${source} says nothing: give at least one of "run", "signIn" and "workflow".`);
|
|
47
|
+
return notes;
|
|
48
|
+
}
|
|
49
|
+
/** `--notes <file>`: the notes in that file, or in stdin for `-`. */
|
|
50
|
+
export function readNotes(path) {
|
|
51
|
+
const source = path === '-' ? 'The notes on stdin' : `The notes file ${path}`;
|
|
52
|
+
let text;
|
|
53
|
+
try {
|
|
54
|
+
text = readFileSync(path === '-' ? 0 : path, 'utf8');
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
throw new Error(`${source} could not be read: ${error instanceof Error ? error.message : String(error)}`);
|
|
58
|
+
}
|
|
59
|
+
return parseNotes(text, source);
|
|
60
|
+
}
|
|
61
|
+
export function sameNotes(a, b) {
|
|
62
|
+
return a !== undefined && NOTE_FIELDS.every(field => a[field] === b[field]);
|
|
63
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
export declare function preloadBabel(): Promise<void>;
|
|
2
|
+
declare const MANIFEST_SCHEMA_VERSION = "haystack-node-telemetry-instrumentation-v3";
|
|
3
|
+
export interface TelemetryInstrumentOptions {
|
|
4
|
+
entry: string;
|
|
5
|
+
sourceRoot?: string;
|
|
6
|
+
json?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Experimental exact symbol allow-list. Entries use the same structured
|
|
9
|
+
* sourcePath#qualifiedName coordinates emitted in the instrumentation
|
|
10
|
+
* manifest. No fuzzy name matching is performed.
|
|
11
|
+
*/
|
|
12
|
+
includeSymbols?: Array<{
|
|
13
|
+
sourcePath: string;
|
|
14
|
+
qualifiedName: string;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export interface InstrumentedFileSummary {
|
|
18
|
+
output_path: string;
|
|
19
|
+
source_path: string;
|
|
20
|
+
probes: {
|
|
21
|
+
branches: number;
|
|
22
|
+
parameters: number;
|
|
23
|
+
bindings: number;
|
|
24
|
+
returns: number;
|
|
25
|
+
throws: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export interface TelemetryInstrumentationResult {
|
|
29
|
+
schema_version: typeof MANIFEST_SCHEMA_VERSION;
|
|
30
|
+
build_id: string;
|
|
31
|
+
dist_directory: string;
|
|
32
|
+
entry: string;
|
|
33
|
+
runtime: string;
|
|
34
|
+
instrumented_files: InstrumentedFileSummary[];
|
|
35
|
+
sites: InstrumentedSite[];
|
|
36
|
+
skipped_unmapped_files: string[];
|
|
37
|
+
already_instrumented: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface InstrumentedSite {
|
|
40
|
+
probe_key: string;
|
|
41
|
+
site_id: string;
|
|
42
|
+
source_path: string;
|
|
43
|
+
qualified_name: string;
|
|
44
|
+
probe_kind: 'condition' | 'parameter' | 'binding' | 'return' | 'throw';
|
|
45
|
+
owner_key: string;
|
|
46
|
+
structural_key: string;
|
|
47
|
+
label?: string;
|
|
48
|
+
allowed_fields: string[];
|
|
49
|
+
/**
|
|
50
|
+
* 1-based line and 0-based column of the probed node in `source_path`.
|
|
51
|
+
* Present only when the instrumented text IS the source (the bundler
|
|
52
|
+
* loader). Compiled-output instrumentation has no source map, so its
|
|
53
|
+
* positions would be output coordinates and are omitted rather than
|
|
54
|
+
* mislabelled; consumers join those sites by name only.
|
|
55
|
+
*/
|
|
56
|
+
line?: number;
|
|
57
|
+
column?: number;
|
|
58
|
+
}
|
|
59
|
+
export interface FileProbeCounts {
|
|
60
|
+
branches: number;
|
|
61
|
+
parameters: number;
|
|
62
|
+
bindings: number;
|
|
63
|
+
returns: number;
|
|
64
|
+
throws: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The runtime source bound to one control channel, plus the integrity token
|
|
68
|
+
* generated probes present to it and the hash a bootstrap verifies before
|
|
69
|
+
* evaluating it. Deterministic in the channel, so a bundler loader running in
|
|
70
|
+
* several worker processes and the step that later writes the runtime agree
|
|
71
|
+
* without exchanging anything.
|
|
72
|
+
*/
|
|
73
|
+
export declare function authenticateTelemetryRuntime(controlChannel: string): {
|
|
74
|
+
source: string;
|
|
75
|
+
integrity: string;
|
|
76
|
+
hash: string;
|
|
77
|
+
};
|
|
78
|
+
export declare function instrumentNodeTelemetryBuild(distDirectory: string, options: TelemetryInstrumentOptions): TelemetryInstrumentationResult;
|
|
79
|
+
export declare const BUNDLED_SOURCE_EXTENSIONS: string[];
|
|
80
|
+
export type BundledModuleResult = {
|
|
81
|
+
kind: 'instrumented';
|
|
82
|
+
code: string;
|
|
83
|
+
probes: FileProbeCounts;
|
|
84
|
+
sites: InstrumentedSite[];
|
|
85
|
+
}
|
|
86
|
+
/** Already instrumented, or a "use client" module (its server copy is only the SSR render of browser code). */
|
|
87
|
+
| {
|
|
88
|
+
kind: 'unchanged';
|
|
89
|
+
}
|
|
90
|
+
/** Babel cannot parse it (syntax the bundler's own compiler accepts but Babel does not). */
|
|
91
|
+
| {
|
|
92
|
+
kind: 'unparsed';
|
|
93
|
+
reason: string;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Instrument one source module inside a bundler. `sourcePath` is the
|
|
97
|
+
* repository-relative path every site records, so production counts join the
|
|
98
|
+
* change's files and line ranges exactly. A module Babel cannot parse is
|
|
99
|
+
* reported, not thrown: the bundler's compiler may accept syntax Babel does
|
|
100
|
+
* not, and telemetry must never be the reason a build fails. Any other error
|
|
101
|
+
* is ours and stays loud.
|
|
102
|
+
*/
|
|
103
|
+
export declare function instrumentBundledModule(code: string, options: {
|
|
104
|
+
sourcePath: string;
|
|
105
|
+
controlChannel: string;
|
|
106
|
+
runtimeIntegrity: string;
|
|
107
|
+
}): BundledModuleResult;
|
|
108
|
+
export interface BundledTelemetryManifest {
|
|
109
|
+
schema_version: typeof MANIFEST_SCHEMA_VERSION;
|
|
110
|
+
build_id: string;
|
|
111
|
+
bundler: string;
|
|
112
|
+
source_root: string;
|
|
113
|
+
/** Bundled builds have no instrumented entry file; the preload installs. */
|
|
114
|
+
entry: null;
|
|
115
|
+
runtime: string;
|
|
116
|
+
instrumented_files: Array<{
|
|
117
|
+
source_path: string;
|
|
118
|
+
probes: FileProbeCounts;
|
|
119
|
+
}>;
|
|
120
|
+
/** Server modules shipped uninstrumented because Babel could not parse them, with the parser's reason. */
|
|
121
|
+
skipped_unparsed_files: Array<{
|
|
122
|
+
source_path: string;
|
|
123
|
+
reason: string;
|
|
124
|
+
}>;
|
|
125
|
+
sites: InstrumentedSite[];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Write the runtime, its manifest and the preload that installs it into
|
|
129
|
+
* `<outputDirectory>/.haystack-telemetry/`. The runtime reads the manifest
|
|
130
|
+
* beside its own real file; nothing here depends on where a bundler put the
|
|
131
|
+
* chunks. Returns the preload's absolute path for NODE_OPTIONS=--require.
|
|
132
|
+
*/
|
|
133
|
+
export declare function writeBundledTelemetryRuntime(options: {
|
|
134
|
+
outputDirectory: string;
|
|
135
|
+
controlChannel: string;
|
|
136
|
+
bundler: string;
|
|
137
|
+
sourceRoot: string;
|
|
138
|
+
modules: Array<{
|
|
139
|
+
sourcePath: string;
|
|
140
|
+
probes: FileProbeCounts;
|
|
141
|
+
sites: InstrumentedSite[];
|
|
142
|
+
}>;
|
|
143
|
+
unparsed: Array<{
|
|
144
|
+
sourcePath: string;
|
|
145
|
+
reason: string;
|
|
146
|
+
}>;
|
|
147
|
+
}): {
|
|
148
|
+
registerPath: string;
|
|
149
|
+
manifest: BundledTelemetryManifest;
|
|
150
|
+
};
|
|
151
|
+
export declare function telemetryInstrumentCommand(distDirectory: string, options: TelemetryInstrumentOptions & {
|
|
152
|
+
includeSymbolsFile?: string;
|
|
153
|
+
}): Promise<void>;
|
|
154
|
+
export {};
|