@tryinget/runtime-trace-insights 0.1.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/LICENSE +78 -0
- package/README.md +155 -0
- package/package.json +41 -0
- package/src/.gitkeep +1 -0
- package/src/index.mjs +3 -0
- package/src/runtime_record/depdiet_runtime_record_adapter.mjs +80 -0
- package/src/runtime_record/index.mjs +18 -0
- package/src/runtime_record/runtime_record_flow.mjs +1577 -0
- package/src/runtime_trace_bundle/index.mjs +8 -0
- package/src/runtime_trace_bundle/runtime_trace_bundle_builder.mjs +679 -0
- package/src/runtime_trace_bundle/runtime_trace_bundle_flow.mjs +168 -0
|
@@ -0,0 +1,1577 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
RUNTIME_TRACE_BUNDLE_PROFILES,
|
|
8
|
+
RUNTIME_TRACE_BUNDLE_SCHEMA_VERSION,
|
|
9
|
+
buildRuntimeTraceBundle,
|
|
10
|
+
} from "../runtime_trace_bundle/runtime_trace_bundle_builder.mjs";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_RECORD_ARTIFACT_DIR = "out/depdiet/record";
|
|
13
|
+
const DEFAULT_RUNTIME_TRACE_MANIFEST_PATH = "out/depdiet/runtime/trace-samples.manifest.json";
|
|
14
|
+
const DEFAULT_RUNTIME_TRACE_MANIFEST_RETENTION_COUNT = 30;
|
|
15
|
+
const RUNTIME_TRACE_SCHEMA_VERSION = "depdiet.runtime.trace.v1";
|
|
16
|
+
const RUNTIME_TRACE_MANIFEST_SCHEMA_VERSION = "depdiet.runtime.trace.manifest.v1";
|
|
17
|
+
const APPMAP_SETUP_GUIDANCE_SCHEMA_VERSION = "depdiet.appmap.guidance.v1";
|
|
18
|
+
const RUNTIME_RECORD_RESULT_SCHEMA_VERSION = "depdiet.runtime.record.result.v1";
|
|
19
|
+
// dep-diet `record` artifacts; paths and schema are dep-diet's read contract (see dep-diet
|
|
20
|
+
// src/runtime_record/runtime_record_flow.mjs, whose embedded copy this flow replaces, AK #5892).
|
|
21
|
+
const DEFAULT_RECORD_RUNTIME_TRACE_BUNDLE_PATH = "out/depdiet/runtime/runtime-trace-bundle.json";
|
|
22
|
+
const DEFAULT_DEPVIZ_RUNTIME_HANDOFF_PATH = "out/depdiet/runtime/depviz-runtime-overlay-handoff.json";
|
|
23
|
+
const DEPVIZ_RUNTIME_HANDOFF_SCHEMA_VERSION = "depdiet.depviz.runtime-overlay-handoff.v1";
|
|
24
|
+
const DEPVIZ_RUNTIME_HANDOFF_NON_AUTHORITY_NOTE = "Dep-viz handoff is import guidance for runtime overlays; depmodel production remains dep-diet evidence fusion and visualization semantics remain dep-viz authority.";
|
|
25
|
+
|
|
26
|
+
function isPlainObject(value) {
|
|
27
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function asArray(value) {
|
|
31
|
+
return Array.isArray(value) ? value : [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function asNonEmptyString(value) {
|
|
35
|
+
if (typeof value !== "string") {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const normalized = value.trim();
|
|
40
|
+
return normalized.length > 0 ? normalized : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parseOptionalBoolean(value) {
|
|
44
|
+
if (typeof value === "boolean") {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeof value !== "string") {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const normalized = value.trim().toLowerCase();
|
|
53
|
+
|
|
54
|
+
if (normalized === "true") {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (normalized === "false") {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizePathSlashes(value) {
|
|
66
|
+
return String(value).replaceAll("\\", "/");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function toRelativePath(basePath, targetPath) {
|
|
70
|
+
const relative = path.relative(basePath, targetPath);
|
|
71
|
+
return normalizePathSlashes(relative.length > 0 ? relative : ".");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function sanitizeSegment(value, {
|
|
75
|
+
fallback = "record-run",
|
|
76
|
+
maxLength = 80,
|
|
77
|
+
} = {}) {
|
|
78
|
+
const base = asNonEmptyString(value) ?? fallback;
|
|
79
|
+
|
|
80
|
+
const normalized = String(base)
|
|
81
|
+
.toLowerCase()
|
|
82
|
+
.replace(/[^a-z0-9._-]+/gu, "-")
|
|
83
|
+
.replace(/-+/gu, "-")
|
|
84
|
+
.replace(/^-|-$/gu, "")
|
|
85
|
+
.slice(0, maxLength);
|
|
86
|
+
|
|
87
|
+
if (normalized.length > 0) {
|
|
88
|
+
return normalized;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return fallback;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function createDiagnostic({
|
|
95
|
+
code,
|
|
96
|
+
level = "warning",
|
|
97
|
+
message,
|
|
98
|
+
path: diagnosticPath,
|
|
99
|
+
source,
|
|
100
|
+
details,
|
|
101
|
+
}) {
|
|
102
|
+
const diagnostic = {
|
|
103
|
+
code,
|
|
104
|
+
level,
|
|
105
|
+
message,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const normalizedPath = asNonEmptyString(diagnosticPath);
|
|
109
|
+
if (normalizedPath) {
|
|
110
|
+
diagnostic.path = normalizedPath;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const normalizedSource = asNonEmptyString(source);
|
|
114
|
+
if (normalizedSource) {
|
|
115
|
+
diagnostic.source = normalizedSource;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (details !== undefined) {
|
|
119
|
+
diagnostic.details = details;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return diagnostic;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function normalizeDiagnosticList(entries, source) {
|
|
126
|
+
return asArray(entries)
|
|
127
|
+
.filter((entry) => isPlainObject(entry))
|
|
128
|
+
.map((entry) => ({
|
|
129
|
+
...entry,
|
|
130
|
+
...(asNonEmptyString(entry.level) ? {} : { level: "warning" }),
|
|
131
|
+
...(asNonEmptyString(entry.source) ? {} : (asNonEmptyString(source) ? { source } : {})),
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function hasErrorDiagnostic(entries) {
|
|
136
|
+
return asArray(entries)
|
|
137
|
+
.some((entry) => String(entry?.level ?? "").toLowerCase() === "error");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function resolveProjectPathInput(options = {}) {
|
|
141
|
+
return asNonEmptyString(options.projectPath)
|
|
142
|
+
?? asNonEmptyString(options.path)
|
|
143
|
+
?? null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function normalizeCommandToken(value) {
|
|
147
|
+
if (typeof value === "number") {
|
|
148
|
+
return String(value);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return asNonEmptyString(value);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function resolveObservedCommand(options = {}) {
|
|
155
|
+
const candidates = [];
|
|
156
|
+
|
|
157
|
+
if (Array.isArray(options.observedCommand)) {
|
|
158
|
+
candidates.push(options.observedCommand);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (Array.isArray(options.command)) {
|
|
162
|
+
candidates.push(options.command);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (Array.isArray(options.commandArgs)) {
|
|
166
|
+
candidates.push(options.commandArgs);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (Array.isArray(options.argv)) {
|
|
170
|
+
candidates.push(options.argv);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (Array.isArray(options.args)) {
|
|
174
|
+
candidates.push(options.args);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const candidate of candidates) {
|
|
178
|
+
const tokens = candidate
|
|
179
|
+
.map((entry) => normalizeCommandToken(entry))
|
|
180
|
+
.filter(Boolean);
|
|
181
|
+
|
|
182
|
+
if (tokens.length > 0) {
|
|
183
|
+
return tokens;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const commandString = asNonEmptyString(options.commandString)
|
|
188
|
+
?? asNonEmptyString(options.cmd)
|
|
189
|
+
?? null;
|
|
190
|
+
|
|
191
|
+
if (commandString) {
|
|
192
|
+
return commandString.split(/\s+/u).filter(Boolean);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function toCommandLineString(tokens = []) {
|
|
199
|
+
return asArray(tokens)
|
|
200
|
+
.map((token) => {
|
|
201
|
+
if (!/[\s"'`$]/u.test(token)) {
|
|
202
|
+
return token;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return JSON.stringify(token);
|
|
206
|
+
})
|
|
207
|
+
.join(" ");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function isExistingDirectory(targetPath) {
|
|
211
|
+
try {
|
|
212
|
+
return fs.existsSync(targetPath) && fs.statSync(targetPath).isDirectory();
|
|
213
|
+
} catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function resolveAppMapAvailabilityOverride(options = {}) {
|
|
219
|
+
const directCandidates = [
|
|
220
|
+
options.appMapAvailable,
|
|
221
|
+
options.appmapAvailable,
|
|
222
|
+
options.appMapAvailability,
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
for (const candidate of directCandidates) {
|
|
226
|
+
const parsed = parseOptionalBoolean(candidate);
|
|
227
|
+
if (parsed !== null) {
|
|
228
|
+
return {
|
|
229
|
+
available: parsed,
|
|
230
|
+
source: "option",
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const explicitEnv = isPlainObject(options.env)
|
|
236
|
+
? options.env
|
|
237
|
+
: process.env;
|
|
238
|
+
|
|
239
|
+
const envParsed = parseOptionalBoolean(
|
|
240
|
+
explicitEnv.DEPDIET_APPMAP_AVAILABLE,
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
if (envParsed !== null) {
|
|
244
|
+
return {
|
|
245
|
+
available: envParsed,
|
|
246
|
+
source: "env:DEPDIET_APPMAP_AVAILABLE",
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function appMapBinaryCandidates(projectPathAbsolute) {
|
|
254
|
+
const base = path.join(projectPathAbsolute, "node_modules", ".bin");
|
|
255
|
+
|
|
256
|
+
return [
|
|
257
|
+
path.join(base, "appmap"),
|
|
258
|
+
path.join(base, "appmap-node"),
|
|
259
|
+
path.join(base, "appmap-python"),
|
|
260
|
+
path.join(base, "appmap.cmd"),
|
|
261
|
+
path.join(base, "appmap-node.cmd"),
|
|
262
|
+
path.join(base, "appmap-python.cmd"),
|
|
263
|
+
];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function detectAppMapPackage(projectPathAbsolute, diagnostics, probes) {
|
|
267
|
+
const packageJsonAbsolute = path.join(projectPathAbsolute, "package.json");
|
|
268
|
+
|
|
269
|
+
if (!fs.existsSync(packageJsonAbsolute)) {
|
|
270
|
+
probes.push({
|
|
271
|
+
probe: "package-json",
|
|
272
|
+
status: "missing",
|
|
273
|
+
path: packageJsonAbsolute,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
let packageJson;
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonAbsolute, "utf8"));
|
|
283
|
+
} catch (error) {
|
|
284
|
+
diagnostics.push(createDiagnostic({
|
|
285
|
+
code: "runtimeRecord.appMap.packageJsonParseFailed",
|
|
286
|
+
level: "warning",
|
|
287
|
+
source: "runtimeRecord",
|
|
288
|
+
message: "Failed to parse package.json while probing AppMap availability.",
|
|
289
|
+
path: "/appMap/probe/packageJson",
|
|
290
|
+
details: {
|
|
291
|
+
packageJsonPath: packageJsonAbsolute,
|
|
292
|
+
error: error instanceof Error ? error.message : String(error),
|
|
293
|
+
},
|
|
294
|
+
}));
|
|
295
|
+
|
|
296
|
+
probes.push({
|
|
297
|
+
probe: "package-json",
|
|
298
|
+
status: "parse-failed",
|
|
299
|
+
path: packageJsonAbsolute,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const dependencySections = [
|
|
306
|
+
packageJson.dependencies,
|
|
307
|
+
packageJson.devDependencies,
|
|
308
|
+
packageJson.optionalDependencies,
|
|
309
|
+
packageJson.peerDependencies,
|
|
310
|
+
]
|
|
311
|
+
.filter((entry) => isPlainObject(entry));
|
|
312
|
+
|
|
313
|
+
const dependencyNames = dependencySections
|
|
314
|
+
.flatMap((section) => Object.keys(section));
|
|
315
|
+
|
|
316
|
+
const appMapDependency = dependencyNames
|
|
317
|
+
.find((name) => /(^@appland\/appmap$|(^|[-_/])appmap($|[-_/]))/iu.test(String(name)));
|
|
318
|
+
|
|
319
|
+
probes.push({
|
|
320
|
+
probe: "package-json",
|
|
321
|
+
status: appMapDependency ? "found" : "not-found",
|
|
322
|
+
path: packageJsonAbsolute,
|
|
323
|
+
dependencyName: appMapDependency ?? null,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
return Boolean(appMapDependency);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function probeAppMapAvailability({
|
|
330
|
+
projectPathAbsolute,
|
|
331
|
+
options,
|
|
332
|
+
}) {
|
|
333
|
+
const diagnostics = [];
|
|
334
|
+
const probes = [];
|
|
335
|
+
|
|
336
|
+
const override = resolveAppMapAvailabilityOverride(options);
|
|
337
|
+
if (override) {
|
|
338
|
+
probes.push({
|
|
339
|
+
probe: "override",
|
|
340
|
+
status: override.available ? "forced-available" : "forced-missing",
|
|
341
|
+
source: override.source,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
available: override.available,
|
|
346
|
+
reason: override.available
|
|
347
|
+
? "appmap-availability-forced-true"
|
|
348
|
+
: "appmap-availability-forced-false",
|
|
349
|
+
probes,
|
|
350
|
+
diagnostics,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
let binaryDetected = false;
|
|
355
|
+
|
|
356
|
+
for (const candidatePath of appMapBinaryCandidates(projectPathAbsolute)) {
|
|
357
|
+
const exists = fs.existsSync(candidatePath);
|
|
358
|
+
|
|
359
|
+
probes.push({
|
|
360
|
+
probe: "local-binary",
|
|
361
|
+
status: exists ? "found" : "not-found",
|
|
362
|
+
path: candidatePath,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
if (exists) {
|
|
366
|
+
binaryDetected = true;
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const packageDetected = detectAppMapPackage(projectPathAbsolute, diagnostics, probes);
|
|
372
|
+
|
|
373
|
+
if (binaryDetected || packageDetected) {
|
|
374
|
+
return {
|
|
375
|
+
available: true,
|
|
376
|
+
reason: binaryDetected
|
|
377
|
+
? "local-appmap-binary-detected"
|
|
378
|
+
: "appmap-dependency-detected",
|
|
379
|
+
probes,
|
|
380
|
+
diagnostics,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
available: false,
|
|
386
|
+
reason: "appmap-not-detected",
|
|
387
|
+
probes,
|
|
388
|
+
diagnostics,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function executeObservedCommand({
|
|
393
|
+
commandTokens,
|
|
394
|
+
cwd,
|
|
395
|
+
env,
|
|
396
|
+
}) {
|
|
397
|
+
const [command, ...args] = commandTokens;
|
|
398
|
+
|
|
399
|
+
const startedAt = new Date().toISOString();
|
|
400
|
+
const started = Date.now();
|
|
401
|
+
|
|
402
|
+
const result = spawnSync(command, args, {
|
|
403
|
+
cwd,
|
|
404
|
+
shell: false,
|
|
405
|
+
encoding: "utf8",
|
|
406
|
+
env: env ?? process.env,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const completedAt = new Date().toISOString();
|
|
410
|
+
const durationMs = Math.max(0, Date.now() - started);
|
|
411
|
+
|
|
412
|
+
const errorMessage = result.error ? result.error.message : null;
|
|
413
|
+
const exitCode = Number.isInteger(result.status) ? result.status : null;
|
|
414
|
+
const signal = asNonEmptyString(result.signal) ?? null;
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
ok: !errorMessage && exitCode === 0,
|
|
418
|
+
command,
|
|
419
|
+
args,
|
|
420
|
+
commandLine: toCommandLineString(commandTokens),
|
|
421
|
+
cwd,
|
|
422
|
+
exitCode,
|
|
423
|
+
signal,
|
|
424
|
+
stdout: String(result.stdout ?? ""),
|
|
425
|
+
stderr: String(result.stderr ?? ""),
|
|
426
|
+
errorMessage,
|
|
427
|
+
startedAt,
|
|
428
|
+
completedAt,
|
|
429
|
+
durationMs,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function optionEnabled(value) {
|
|
434
|
+
if (value === true) {
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (typeof value !== "string") {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return ["1", "true", "yes", "on", "auto"].includes(value.trim().toLowerCase());
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function nodeOptionsPathToken(value) {
|
|
446
|
+
const normalized = String(value);
|
|
447
|
+
if (!/[\s"']/u.test(normalized)) {
|
|
448
|
+
return normalized;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return JSON.stringify(normalized);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function appendNodeOption(existing, option) {
|
|
455
|
+
const current = asNonEmptyString(existing);
|
|
456
|
+
return current ? `${current} ${option}` : option;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function createPackageAutodiscoveryInstrumentation({ artifactDirectoryAbsolute, diagnostics }) {
|
|
460
|
+
const logAbsolutePath = path.join(artifactDirectoryAbsolute, "package-autodiscovery.jsonl");
|
|
461
|
+
const loaderAbsolutePath = path.join(artifactDirectoryAbsolute, "package-autodiscovery-loader.mjs");
|
|
462
|
+
const preloadAbsolutePath = path.join(artifactDirectoryAbsolute, "package-autodiscovery-preload.cjs");
|
|
463
|
+
|
|
464
|
+
const loaderSource = String.raw`import fs from "node:fs";
|
|
465
|
+
|
|
466
|
+
const logPath = process.env.RUNTIME_TRACE_PACKAGE_AUTODISCOVERY_LOG;
|
|
467
|
+
|
|
468
|
+
function emit(event) {
|
|
469
|
+
if (!logPath) return;
|
|
470
|
+
try {
|
|
471
|
+
fs.appendFileSync(logPath, JSON.stringify({ ...event, pid: process.pid, observedAt: new Date().toISOString() }) + "\n", "utf8");
|
|
472
|
+
} catch {
|
|
473
|
+
// Runtime observation must not alter target command behavior.
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
478
|
+
const result = await nextResolve(specifier, context);
|
|
479
|
+
emit({ kind: "esm-resolve", specifier, parentURL: context.parentURL ?? null, url: result.url ?? null });
|
|
480
|
+
return result;
|
|
481
|
+
}
|
|
482
|
+
`;
|
|
483
|
+
|
|
484
|
+
const preloadSource = String.raw`const fs = require("node:fs");
|
|
485
|
+
const Module = require("node:module");
|
|
486
|
+
const logPath = process.env.RUNTIME_TRACE_PACKAGE_AUTODISCOVERY_LOG;
|
|
487
|
+
const originalLoad = Module._load;
|
|
488
|
+
const originalResolveFilename = Module._resolveFilename;
|
|
489
|
+
|
|
490
|
+
function emit(event) {
|
|
491
|
+
if (!logPath) return;
|
|
492
|
+
try {
|
|
493
|
+
fs.appendFileSync(logPath, JSON.stringify({ ...event, pid: process.pid, observedAt: new Date().toISOString() }) + "\n", "utf8");
|
|
494
|
+
} catch {
|
|
495
|
+
// Runtime observation must not alter target command behavior.
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
Module._load = function runtimeTraceAutodiscoveryLoad(request, parent, isMain) {
|
|
500
|
+
let resolved = null;
|
|
501
|
+
try {
|
|
502
|
+
resolved = originalResolveFilename.call(this, request, parent, isMain);
|
|
503
|
+
} catch {
|
|
504
|
+
resolved = null;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const loaded = originalLoad.apply(this, arguments);
|
|
508
|
+
emit({ kind: "cjs-load", specifier: request, parentFilename: parent?.filename ?? null, resolvedPath: typeof resolved === "string" ? resolved : null });
|
|
509
|
+
return loaded;
|
|
510
|
+
};
|
|
511
|
+
`;
|
|
512
|
+
|
|
513
|
+
try {
|
|
514
|
+
fs.writeFileSync(loaderAbsolutePath, loaderSource, "utf8");
|
|
515
|
+
fs.writeFileSync(preloadAbsolutePath, preloadSource, "utf8");
|
|
516
|
+
fs.writeFileSync(logAbsolutePath, "", "utf8");
|
|
517
|
+
} catch (error) {
|
|
518
|
+
diagnostics.push(createDiagnostic({
|
|
519
|
+
code: "runtimeRecord.packageAutodiscoverySetupFailed",
|
|
520
|
+
level: "warning",
|
|
521
|
+
source: "runtimeRecord",
|
|
522
|
+
message: "Failed to create package autodiscovery instrumentation; command will run without package autodiscovery.",
|
|
523
|
+
details: {
|
|
524
|
+
artifactDirectoryAbsolute,
|
|
525
|
+
error: error instanceof Error ? error.message : String(error),
|
|
526
|
+
},
|
|
527
|
+
}));
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
enabled: false,
|
|
531
|
+
reason: "setup-failed",
|
|
532
|
+
logAbsolutePath,
|
|
533
|
+
loaderAbsolutePath,
|
|
534
|
+
preloadAbsolutePath,
|
|
535
|
+
env: process.env,
|
|
536
|
+
events: [],
|
|
537
|
+
diagnostics,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const env = { ...process.env };
|
|
542
|
+
env.RUNTIME_TRACE_PACKAGE_AUTODISCOVERY_LOG = logAbsolutePath;
|
|
543
|
+
// ESM loaders take URLs: a bare Windows path (C:\\...) is read as a "c:" URL scheme and rejected.
|
|
544
|
+
env.NODE_OPTIONS = appendNodeOption(env.NODE_OPTIONS, `--experimental-loader=${nodeOptionsPathToken(pathToFileURL(loaderAbsolutePath).href)}`);
|
|
545
|
+
env.NODE_OPTIONS = appendNodeOption(env.NODE_OPTIONS, `--require=${nodeOptionsPathToken(preloadAbsolutePath)}`);
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
enabled: true,
|
|
549
|
+
reason: "node-options-loader-and-preload",
|
|
550
|
+
logAbsolutePath,
|
|
551
|
+
loaderAbsolutePath,
|
|
552
|
+
preloadAbsolutePath,
|
|
553
|
+
env,
|
|
554
|
+
events: [],
|
|
555
|
+
diagnostics: [],
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function readPackageAutodiscoveryEvents({ logAbsolutePath, diagnostics }) {
|
|
560
|
+
if (!asNonEmptyString(logAbsolutePath) || !fs.existsSync(logAbsolutePath)) {
|
|
561
|
+
return [];
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const events = [];
|
|
565
|
+
const lines = fs.readFileSync(logAbsolutePath, "utf8")
|
|
566
|
+
.split(/\r?\n/u)
|
|
567
|
+
.filter(Boolean);
|
|
568
|
+
|
|
569
|
+
for (const line of lines) {
|
|
570
|
+
try {
|
|
571
|
+
const parsed = JSON.parse(line);
|
|
572
|
+
if (isPlainObject(parsed)) {
|
|
573
|
+
events.push(parsed);
|
|
574
|
+
}
|
|
575
|
+
} catch (error) {
|
|
576
|
+
diagnostics.push(createDiagnostic({
|
|
577
|
+
code: "runtimeRecord.packageAutodiscoveryEventParseFailed",
|
|
578
|
+
level: "warning",
|
|
579
|
+
source: "runtimeRecord",
|
|
580
|
+
message: "Skipped malformed package autodiscovery event.",
|
|
581
|
+
details: {
|
|
582
|
+
error: error instanceof Error ? error.message : String(error),
|
|
583
|
+
},
|
|
584
|
+
}));
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return events;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function createDepvizRuntimeHandoffArtifact({
|
|
592
|
+
generatedAt,
|
|
593
|
+
runId,
|
|
594
|
+
projectPathInput,
|
|
595
|
+
observedCommand,
|
|
596
|
+
execution,
|
|
597
|
+
diagnostics,
|
|
598
|
+
runtimeTracePath,
|
|
599
|
+
runtimeTraceBundlePath,
|
|
600
|
+
setupGuidancePath,
|
|
601
|
+
runtimeBundle,
|
|
602
|
+
}) {
|
|
603
|
+
const normalizedDiagnostics = normalizeDiagnosticList(diagnostics, "runtimeRecord");
|
|
604
|
+
const overlayImportPermitted = execution.ok && !hasErrorDiagnostic(normalizedDiagnostics);
|
|
605
|
+
|
|
606
|
+
return {
|
|
607
|
+
schemaVersion: DEPVIZ_RUNTIME_HANDOFF_SCHEMA_VERSION,
|
|
608
|
+
schema_version: DEPVIZ_RUNTIME_HANDOFF_SCHEMA_VERSION,
|
|
609
|
+
generatedAt,
|
|
610
|
+
producer: "dep-diet",
|
|
611
|
+
targetConsumers: ["dep-viz", "dep-diet"],
|
|
612
|
+
runId,
|
|
613
|
+
command: {
|
|
614
|
+
name: "record",
|
|
615
|
+
projectPath: projectPathInput,
|
|
616
|
+
observedCommand,
|
|
617
|
+
commandLine: execution.commandLine,
|
|
618
|
+
exitCode: execution.exitCode,
|
|
619
|
+
durationMs: execution.durationMs,
|
|
620
|
+
},
|
|
621
|
+
runtimeEvidence: {
|
|
622
|
+
bundleSchemaVersion: RUNTIME_TRACE_BUNDLE_SCHEMA_VERSION,
|
|
623
|
+
bundlePath: runtimeTraceBundlePath,
|
|
624
|
+
bundleId: runtimeBundle.bundleId,
|
|
625
|
+
observedPackageCount: runtimeBundle.observedPackages.length,
|
|
626
|
+
diagnosticsPermitOverlayImport: overlayImportPermitted,
|
|
627
|
+
limitations: [
|
|
628
|
+
"Runtime observation is command-level evidence only.",
|
|
629
|
+
"Static/runtime classifications must be produced by dep-diet depmodel fusion before visualization.",
|
|
630
|
+
"Dep-viz owns rendering and overlay semantics; this handoff does not grant visualization authority.",
|
|
631
|
+
],
|
|
632
|
+
},
|
|
633
|
+
depmodelHandoff: {
|
|
634
|
+
preferredConsumerInput: "depmodel.v1",
|
|
635
|
+
producerCommand: [
|
|
636
|
+
"depdiet",
|
|
637
|
+
"analyze",
|
|
638
|
+
projectPathInput,
|
|
639
|
+
"--gardener-output",
|
|
640
|
+
"<gardener-output.json>",
|
|
641
|
+
"--runtime-bundle",
|
|
642
|
+
runtimeTraceBundlePath,
|
|
643
|
+
"--out-depmodel",
|
|
644
|
+
"<depmodel.v1.json>",
|
|
645
|
+
],
|
|
646
|
+
note: "Pair this runtime bundle with static evidence to emit depmodel.v1 before dep-viz rendering.",
|
|
647
|
+
},
|
|
648
|
+
artifacts: {
|
|
649
|
+
runtimeTrace: runtimeTracePath,
|
|
650
|
+
runtimeTraceBundle: runtimeTraceBundlePath,
|
|
651
|
+
setupGuidance: setupGuidancePath,
|
|
652
|
+
},
|
|
653
|
+
authority: {
|
|
654
|
+
removalAuthority: false,
|
|
655
|
+
riskAuthority: false,
|
|
656
|
+
visualizationAuthority: false,
|
|
657
|
+
note: DEPVIZ_RUNTIME_HANDOFF_NON_AUTHORITY_NOTE,
|
|
658
|
+
},
|
|
659
|
+
diagnostics: normalizedDiagnostics,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Emits dep-diet's `record` artifacts: the runtime trace bundle and the dep-viz runtime overlay handoff.
|
|
664
|
+
function emitRecordHandoffArtifacts({
|
|
665
|
+
cwd,
|
|
666
|
+
options,
|
|
667
|
+
diagnostics,
|
|
668
|
+
generatedAt,
|
|
669
|
+
runId,
|
|
670
|
+
projectPathInput,
|
|
671
|
+
projectPathAbsolute,
|
|
672
|
+
observedCommand,
|
|
673
|
+
execution,
|
|
674
|
+
packageAutodiscovery,
|
|
675
|
+
runtimeTracePath,
|
|
676
|
+
setupGuidancePath,
|
|
677
|
+
}) {
|
|
678
|
+
const runtimeTraceBundleAbsolutePath = path.resolve(
|
|
679
|
+
cwd,
|
|
680
|
+
asNonEmptyString(options.runtimeTraceBundlePath) ?? DEFAULT_RECORD_RUNTIME_TRACE_BUNDLE_PATH,
|
|
681
|
+
);
|
|
682
|
+
const runtimeTraceBundlePath = toRelativePath(cwd, runtimeTraceBundleAbsolutePath);
|
|
683
|
+
const runtimeTraceBundle = buildRuntimeTraceBundle({
|
|
684
|
+
profile: RUNTIME_TRACE_BUNDLE_PROFILES.depdietRecord,
|
|
685
|
+
diagnostics,
|
|
686
|
+
options,
|
|
687
|
+
generatedAt,
|
|
688
|
+
runId,
|
|
689
|
+
bundleId: options.runtimeTraceBundleId,
|
|
690
|
+
projectPathInput,
|
|
691
|
+
projectPathAbsolute,
|
|
692
|
+
observedCommand,
|
|
693
|
+
commandLine: execution.commandLine,
|
|
694
|
+
exitCode: execution.exitCode,
|
|
695
|
+
durationMs: execution.durationMs,
|
|
696
|
+
packageAutodiscovery,
|
|
697
|
+
references: [
|
|
698
|
+
{ kind: "runtime-trace", path: runtimeTracePath },
|
|
699
|
+
{ kind: "setup-guidance", path: setupGuidancePath },
|
|
700
|
+
],
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
const runtimeTraceBundleWriteOk = writeArtifact(
|
|
704
|
+
runtimeTraceBundleAbsolutePath,
|
|
705
|
+
`${JSON.stringify(runtimeTraceBundle, null, 2)}\n`,
|
|
706
|
+
diagnostics,
|
|
707
|
+
{
|
|
708
|
+
code: "runtimeRecord.runtimeTraceBundleWriteFailed",
|
|
709
|
+
message: "Failed to write dep-viz runtime trace bundle handoff input.",
|
|
710
|
+
details: {
|
|
711
|
+
artifact: "runtime-trace-bundle.json",
|
|
712
|
+
},
|
|
713
|
+
},
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
const depvizHandoffAbsolutePath = path.resolve(
|
|
717
|
+
cwd,
|
|
718
|
+
asNonEmptyString(options.depvizHandoffPath) ?? DEFAULT_DEPVIZ_RUNTIME_HANDOFF_PATH,
|
|
719
|
+
);
|
|
720
|
+
const depvizHandoffPath = toRelativePath(cwd, depvizHandoffAbsolutePath);
|
|
721
|
+
const depvizHandoff = createDepvizRuntimeHandoffArtifact({
|
|
722
|
+
generatedAt,
|
|
723
|
+
runId,
|
|
724
|
+
projectPathInput,
|
|
725
|
+
observedCommand,
|
|
726
|
+
execution,
|
|
727
|
+
diagnostics,
|
|
728
|
+
runtimeTracePath,
|
|
729
|
+
runtimeTraceBundlePath,
|
|
730
|
+
setupGuidancePath,
|
|
731
|
+
runtimeBundle: runtimeTraceBundle,
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
const depvizHandoffWriteOk = writeArtifact(
|
|
735
|
+
depvizHandoffAbsolutePath,
|
|
736
|
+
`${JSON.stringify(depvizHandoff, null, 2)}\n`,
|
|
737
|
+
diagnostics,
|
|
738
|
+
{
|
|
739
|
+
code: "runtimeRecord.depvizHandoffWriteFailed",
|
|
740
|
+
message: "Failed to write dep-viz runtime overlay handoff artifact.",
|
|
741
|
+
details: {
|
|
742
|
+
artifact: "depviz-runtime-overlay-handoff.json",
|
|
743
|
+
},
|
|
744
|
+
},
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
return {
|
|
748
|
+
artifacts: {
|
|
749
|
+
runtimeTraceBundle: runtimeTraceBundleWriteOk
|
|
750
|
+
? {
|
|
751
|
+
path: runtimeTraceBundlePath,
|
|
752
|
+
absolutePath: runtimeTraceBundleAbsolutePath,
|
|
753
|
+
schemaVersion: RUNTIME_TRACE_BUNDLE_SCHEMA_VERSION,
|
|
754
|
+
schema_version: RUNTIME_TRACE_BUNDLE_SCHEMA_VERSION,
|
|
755
|
+
}
|
|
756
|
+
: null,
|
|
757
|
+
depvizHandoff: depvizHandoffWriteOk
|
|
758
|
+
? {
|
|
759
|
+
path: depvizHandoffPath,
|
|
760
|
+
absolutePath: depvizHandoffAbsolutePath,
|
|
761
|
+
schemaVersion: DEPVIZ_RUNTIME_HANDOFF_SCHEMA_VERSION,
|
|
762
|
+
schema_version: DEPVIZ_RUNTIME_HANDOFF_SCHEMA_VERSION,
|
|
763
|
+
}
|
|
764
|
+
: null,
|
|
765
|
+
},
|
|
766
|
+
summary: {
|
|
767
|
+
runtimeTraceBundleEmitted: runtimeTraceBundleWriteOk,
|
|
768
|
+
depvizHandoffEmitted: depvizHandoffWriteOk,
|
|
769
|
+
observedPackageCount: runtimeTraceBundle.observedPackages.length,
|
|
770
|
+
},
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function writeArtifact(absolutePath, content, diagnostics, {
|
|
775
|
+
code,
|
|
776
|
+
message,
|
|
777
|
+
details = {},
|
|
778
|
+
} = {}) {
|
|
779
|
+
try {
|
|
780
|
+
fs.writeFileSync(absolutePath, content, "utf8");
|
|
781
|
+
return true;
|
|
782
|
+
} catch (error) {
|
|
783
|
+
diagnostics.push(createDiagnostic({
|
|
784
|
+
code: code ?? "runtimeRecord.artifactWriteFailed",
|
|
785
|
+
level: "error",
|
|
786
|
+
source: "runtimeRecord",
|
|
787
|
+
message: message ?? "Failed to write runtime record artifact.",
|
|
788
|
+
path: "/artifacts",
|
|
789
|
+
details: {
|
|
790
|
+
artifactPath: absolutePath,
|
|
791
|
+
error: error instanceof Error ? error.message : String(error),
|
|
792
|
+
...details,
|
|
793
|
+
},
|
|
794
|
+
}));
|
|
795
|
+
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function normalizePositiveInteger(value, fallback) {
|
|
801
|
+
const numeric = Number(value);
|
|
802
|
+
|
|
803
|
+
if (!Number.isInteger(numeric) || numeric <= 0) {
|
|
804
|
+
return fallback;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return numeric;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function normalizeRuntimeTraceManifestEntry(rawEntry) {
|
|
811
|
+
if (!isPlainObject(rawEntry)) {
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const runId = asNonEmptyString(rawEntry.runId);
|
|
816
|
+
const tracePath = asNonEmptyString(rawEntry.tracePath)
|
|
817
|
+
?? asNonEmptyString(rawEntry.runtimeTracePath)
|
|
818
|
+
?? null;
|
|
819
|
+
|
|
820
|
+
if (!runId || !tracePath) {
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
return {
|
|
825
|
+
runId,
|
|
826
|
+
generatedAt: asNonEmptyString(rawEntry.generatedAt),
|
|
827
|
+
tracePath,
|
|
828
|
+
commandLine: asNonEmptyString(rawEntry.commandLine),
|
|
829
|
+
projectPath: asNonEmptyString(rawEntry.projectPath),
|
|
830
|
+
appMapAvailable: typeof rawEntry.appMapAvailable === "boolean" ? rawEntry.appMapAvailable : null,
|
|
831
|
+
commandExitCode: Number.isFinite(rawEntry.commandExitCode)
|
|
832
|
+
? Number(rawEntry.commandExitCode)
|
|
833
|
+
: null,
|
|
834
|
+
commandDurationMs: Number.isFinite(rawEntry.commandDurationMs)
|
|
835
|
+
? Number(rawEntry.commandDurationMs)
|
|
836
|
+
: null,
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function parseExistingRuntimeTraceManifestEntries(manifestAbsolutePath, diagnostics = []) {
|
|
841
|
+
if (!fs.existsSync(manifestAbsolutePath)) {
|
|
842
|
+
return [];
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
let parsed;
|
|
846
|
+
|
|
847
|
+
try {
|
|
848
|
+
parsed = JSON.parse(fs.readFileSync(manifestAbsolutePath, "utf8"));
|
|
849
|
+
} catch (error) {
|
|
850
|
+
diagnostics.push(createDiagnostic({
|
|
851
|
+
code: "runtimeRecord.traceManifestReadFailed",
|
|
852
|
+
level: "warning",
|
|
853
|
+
source: "runtimeRecord",
|
|
854
|
+
message: "Could not parse existing runtime trace manifest. A new manifest will be written.",
|
|
855
|
+
path: "/artifacts/runtimeTraceManifest",
|
|
856
|
+
details: {
|
|
857
|
+
manifestAbsolutePath,
|
|
858
|
+
error: error instanceof Error ? error.message : String(error),
|
|
859
|
+
},
|
|
860
|
+
}));
|
|
861
|
+
|
|
862
|
+
return [];
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
return asArray(parsed.entries)
|
|
866
|
+
.map((entry) => normalizeRuntimeTraceManifestEntry(entry))
|
|
867
|
+
.filter(Boolean);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function deduplicateRuntimeTraceManifestEntries(entries = []) {
|
|
871
|
+
const deduplicated = [];
|
|
872
|
+
const seen = new Set();
|
|
873
|
+
|
|
874
|
+
for (const entry of entries) {
|
|
875
|
+
const normalized = normalizeRuntimeTraceManifestEntry(entry);
|
|
876
|
+
|
|
877
|
+
if (!normalized) {
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const key = `${normalized.runId}::${normalized.tracePath}`;
|
|
882
|
+
|
|
883
|
+
if (seen.has(key)) {
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
seen.add(key);
|
|
888
|
+
deduplicated.push(normalized);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
return deduplicated;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function writeRuntimeTraceManifest(options = {}) {
|
|
895
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
896
|
+
const diagnostics = [];
|
|
897
|
+
|
|
898
|
+
const manifestAbsolutePath = path.resolve(
|
|
899
|
+
cwd,
|
|
900
|
+
asNonEmptyString(options.manifestPath) ?? DEFAULT_RUNTIME_TRACE_MANIFEST_PATH,
|
|
901
|
+
);
|
|
902
|
+
|
|
903
|
+
const manifestDirectoryAbsolute = path.dirname(manifestAbsolutePath);
|
|
904
|
+
|
|
905
|
+
try {
|
|
906
|
+
fs.mkdirSync(manifestDirectoryAbsolute, {
|
|
907
|
+
recursive: true,
|
|
908
|
+
});
|
|
909
|
+
} catch (error) {
|
|
910
|
+
diagnostics.push(createDiagnostic({
|
|
911
|
+
code: "runtimeRecord.traceManifestDirectoryCreateFailed",
|
|
912
|
+
level: "warning",
|
|
913
|
+
source: "runtimeRecord",
|
|
914
|
+
message: "Failed to create runtime trace manifest directory.",
|
|
915
|
+
path: "/artifacts/runtimeTraceManifest",
|
|
916
|
+
details: {
|
|
917
|
+
manifestDirectoryAbsolute,
|
|
918
|
+
error: error instanceof Error ? error.message : String(error),
|
|
919
|
+
},
|
|
920
|
+
}));
|
|
921
|
+
|
|
922
|
+
return {
|
|
923
|
+
ok: false,
|
|
924
|
+
artifact: null,
|
|
925
|
+
entryCount: 0,
|
|
926
|
+
droppedEntryCount: 0,
|
|
927
|
+
diagnostics,
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const retentionCount = normalizePositiveInteger(
|
|
932
|
+
options.retentionCount,
|
|
933
|
+
DEFAULT_RUNTIME_TRACE_MANIFEST_RETENTION_COUNT,
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
const existingEntries = parseExistingRuntimeTraceManifestEntries(manifestAbsolutePath, diagnostics);
|
|
937
|
+
|
|
938
|
+
const nextEntry = normalizeRuntimeTraceManifestEntry({
|
|
939
|
+
runId: options.runId,
|
|
940
|
+
generatedAt: options.generatedAt,
|
|
941
|
+
tracePath: options.runtimeTracePath,
|
|
942
|
+
commandLine: options.commandLine,
|
|
943
|
+
projectPath: options.projectPath,
|
|
944
|
+
appMapAvailable: options.appMapAvailable,
|
|
945
|
+
commandExitCode: options.commandExitCode,
|
|
946
|
+
commandDurationMs: options.commandDurationMs,
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
if (!nextEntry) {
|
|
950
|
+
diagnostics.push(createDiagnostic({
|
|
951
|
+
code: "runtimeRecord.traceManifestMissingEntry",
|
|
952
|
+
level: "warning",
|
|
953
|
+
source: "runtimeRecord",
|
|
954
|
+
message: "Runtime trace manifest entry could not be created because runId or runtime trace path is missing.",
|
|
955
|
+
path: "/artifacts/runtimeTraceManifest/entries",
|
|
956
|
+
}));
|
|
957
|
+
|
|
958
|
+
return {
|
|
959
|
+
ok: false,
|
|
960
|
+
artifact: null,
|
|
961
|
+
entryCount: existingEntries.length,
|
|
962
|
+
droppedEntryCount: 0,
|
|
963
|
+
diagnostics,
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
const deduplicated = deduplicateRuntimeTraceManifestEntries([
|
|
968
|
+
nextEntry,
|
|
969
|
+
...existingEntries,
|
|
970
|
+
]);
|
|
971
|
+
|
|
972
|
+
const droppedEntryCount = Math.max(0, deduplicated.length - retentionCount);
|
|
973
|
+
const retainedEntries = deduplicated.slice(0, retentionCount);
|
|
974
|
+
|
|
975
|
+
const manifestRecord = {
|
|
976
|
+
schemaVersion: RUNTIME_TRACE_MANIFEST_SCHEMA_VERSION,
|
|
977
|
+
schema_version: RUNTIME_TRACE_MANIFEST_SCHEMA_VERSION,
|
|
978
|
+
generatedAt: new Date().toISOString(),
|
|
979
|
+
retention: {
|
|
980
|
+
retentionCount,
|
|
981
|
+
entryCount: retainedEntries.length,
|
|
982
|
+
droppedEntryCount,
|
|
983
|
+
},
|
|
984
|
+
entries: retainedEntries,
|
|
985
|
+
};
|
|
986
|
+
|
|
987
|
+
try {
|
|
988
|
+
fs.writeFileSync(
|
|
989
|
+
manifestAbsolutePath,
|
|
990
|
+
`${JSON.stringify(manifestRecord, null, 2)}\n`,
|
|
991
|
+
"utf8",
|
|
992
|
+
);
|
|
993
|
+
} catch (error) {
|
|
994
|
+
diagnostics.push(createDiagnostic({
|
|
995
|
+
code: "runtimeRecord.traceManifestWriteFailed",
|
|
996
|
+
level: "warning",
|
|
997
|
+
source: "runtimeRecord",
|
|
998
|
+
message: "Failed to write runtime trace manifest.",
|
|
999
|
+
path: "/artifacts/runtimeTraceManifest",
|
|
1000
|
+
details: {
|
|
1001
|
+
manifestAbsolutePath,
|
|
1002
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1003
|
+
},
|
|
1004
|
+
}));
|
|
1005
|
+
|
|
1006
|
+
return {
|
|
1007
|
+
ok: false,
|
|
1008
|
+
artifact: null,
|
|
1009
|
+
entryCount: retainedEntries.length,
|
|
1010
|
+
droppedEntryCount,
|
|
1011
|
+
diagnostics,
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return {
|
|
1016
|
+
ok: true,
|
|
1017
|
+
artifact: {
|
|
1018
|
+
path: toRelativePath(cwd, manifestAbsolutePath),
|
|
1019
|
+
absolutePath: manifestAbsolutePath,
|
|
1020
|
+
schemaVersion: RUNTIME_TRACE_MANIFEST_SCHEMA_VERSION,
|
|
1021
|
+
schema_version: RUNTIME_TRACE_MANIFEST_SCHEMA_VERSION,
|
|
1022
|
+
retentionCount,
|
|
1023
|
+
entryCount: retainedEntries.length,
|
|
1024
|
+
},
|
|
1025
|
+
entryCount: retainedEntries.length,
|
|
1026
|
+
droppedEntryCount,
|
|
1027
|
+
diagnostics,
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function createAppMapRerunCommand({
|
|
1032
|
+
projectPathInput,
|
|
1033
|
+
observedCommand,
|
|
1034
|
+
}) {
|
|
1035
|
+
const rerunTokens = [
|
|
1036
|
+
"depdiet",
|
|
1037
|
+
"record",
|
|
1038
|
+
projectPathInput,
|
|
1039
|
+
"--",
|
|
1040
|
+
...(
|
|
1041
|
+
asArray(observedCommand)
|
|
1042
|
+
.map((token) => normalizeCommandToken(token))
|
|
1043
|
+
.filter(Boolean)
|
|
1044
|
+
),
|
|
1045
|
+
];
|
|
1046
|
+
|
|
1047
|
+
if (rerunTokens.length === 4) {
|
|
1048
|
+
rerunTokens.push("<command...>");
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
return toCommandLineString(rerunTokens);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function createAppMapGuidanceInstructions({
|
|
1055
|
+
projectPathInput,
|
|
1056
|
+
observedCommand,
|
|
1057
|
+
}) {
|
|
1058
|
+
const rerunCommand = createAppMapRerunCommand({
|
|
1059
|
+
projectPathInput,
|
|
1060
|
+
observedCommand,
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
return [
|
|
1064
|
+
"AppMap tooling was not detected for this project, so only raw command tracing was captured.",
|
|
1065
|
+
"Node.js setup: npm install --save-dev @appland/appmap",
|
|
1066
|
+
"Node.js verify: npx appmap-node --help",
|
|
1067
|
+
"Python setup: pip install appmap",
|
|
1068
|
+
"Python verify: python -m appmap --help",
|
|
1069
|
+
`Re-run recording once AppMap is installed: ${rerunCommand}`,
|
|
1070
|
+
"Detection override for CI/tests: DEPDIET_APPMAP_AVAILABLE=true|false",
|
|
1071
|
+
];
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
function renderAppMapGuidanceMarkdown({
|
|
1075
|
+
generatedAt,
|
|
1076
|
+
projectPathInput,
|
|
1077
|
+
appMap,
|
|
1078
|
+
instructions,
|
|
1079
|
+
}) {
|
|
1080
|
+
return [
|
|
1081
|
+
"# AppMap setup guidance",
|
|
1082
|
+
"",
|
|
1083
|
+
`Generated at: ${generatedAt}`,
|
|
1084
|
+
`Project path: ${projectPathInput}`,
|
|
1085
|
+
`Probe reason: ${appMap.reason}`,
|
|
1086
|
+
"",
|
|
1087
|
+
"AppMap runtime capture was unavailable for this run.",
|
|
1088
|
+
"",
|
|
1089
|
+
"## Recommended steps",
|
|
1090
|
+
...instructions.map((entry) => `- ${entry}`),
|
|
1091
|
+
"",
|
|
1092
|
+
"## Probe details",
|
|
1093
|
+
"",
|
|
1094
|
+
...appMap.probes.map((probe) => `- ${JSON.stringify(probe)}`),
|
|
1095
|
+
"",
|
|
1096
|
+
].join("\n");
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
function createFailureResult({
|
|
1100
|
+
cwd,
|
|
1101
|
+
generatedAt,
|
|
1102
|
+
projectPath,
|
|
1103
|
+
observedCommand,
|
|
1104
|
+
diagnostics,
|
|
1105
|
+
code,
|
|
1106
|
+
message,
|
|
1107
|
+
}) {
|
|
1108
|
+
return {
|
|
1109
|
+
schemaVersion: RUNTIME_RECORD_RESULT_SCHEMA_VERSION,
|
|
1110
|
+
schema_version: RUNTIME_RECORD_RESULT_SCHEMA_VERSION,
|
|
1111
|
+
ok: false,
|
|
1112
|
+
cwd,
|
|
1113
|
+
generatedAt,
|
|
1114
|
+
command: {
|
|
1115
|
+
name: "record",
|
|
1116
|
+
projectPath,
|
|
1117
|
+
observedCommand,
|
|
1118
|
+
},
|
|
1119
|
+
diagnostics: [
|
|
1120
|
+
createDiagnostic({
|
|
1121
|
+
code,
|
|
1122
|
+
level: "error",
|
|
1123
|
+
source: "runtimeRecord",
|
|
1124
|
+
message,
|
|
1125
|
+
}),
|
|
1126
|
+
...normalizeDiagnosticList(diagnostics, "runtimeRecord"),
|
|
1127
|
+
],
|
|
1128
|
+
artifacts: {
|
|
1129
|
+
runDirectory: null,
|
|
1130
|
+
runtimeTrace: null,
|
|
1131
|
+
runtimeTraceManifest: null,
|
|
1132
|
+
setupGuidance: null,
|
|
1133
|
+
},
|
|
1134
|
+
trace: null,
|
|
1135
|
+
appMap: null,
|
|
1136
|
+
guidance: null,
|
|
1137
|
+
commandsRun: [],
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
export function runRuntimeRecordFlow(options = {}) {
|
|
1142
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
1143
|
+
const generatedAt = asNonEmptyString(options.traceTimestamp) ?? new Date().toISOString();
|
|
1144
|
+
|
|
1145
|
+
const projectPathInput = resolveProjectPathInput(options);
|
|
1146
|
+
const observedCommand = resolveObservedCommand(options);
|
|
1147
|
+
|
|
1148
|
+
if (!projectPathInput) {
|
|
1149
|
+
return createFailureResult({
|
|
1150
|
+
cwd,
|
|
1151
|
+
generatedAt,
|
|
1152
|
+
projectPath: null,
|
|
1153
|
+
observedCommand,
|
|
1154
|
+
diagnostics: [],
|
|
1155
|
+
code: "runtimeRecord.missingProjectPath",
|
|
1156
|
+
message: "Record command requires a target project path.",
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
if (observedCommand.length === 0) {
|
|
1161
|
+
return createFailureResult({
|
|
1162
|
+
cwd,
|
|
1163
|
+
generatedAt,
|
|
1164
|
+
projectPath: projectPathInput,
|
|
1165
|
+
observedCommand,
|
|
1166
|
+
diagnostics: [],
|
|
1167
|
+
code: "runtimeRecord.missingObservedCommand",
|
|
1168
|
+
message: "Record command requires an observed command after '--'.",
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
const projectPathAbsolute = path.resolve(cwd, projectPathInput);
|
|
1173
|
+
|
|
1174
|
+
if (!isExistingDirectory(projectPathAbsolute)) {
|
|
1175
|
+
return createFailureResult({
|
|
1176
|
+
cwd,
|
|
1177
|
+
generatedAt,
|
|
1178
|
+
projectPath: projectPathInput,
|
|
1179
|
+
observedCommand,
|
|
1180
|
+
diagnostics: [],
|
|
1181
|
+
code: "runtimeRecord.projectPathNotFound",
|
|
1182
|
+
message: "Record command requires an existing project directory.",
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
const runId = sanitizeSegment(
|
|
1187
|
+
options.runId ?? `record-${Date.now()}`,
|
|
1188
|
+
{
|
|
1189
|
+
fallback: `record-${Date.now()}`,
|
|
1190
|
+
},
|
|
1191
|
+
);
|
|
1192
|
+
|
|
1193
|
+
const diagnostics = [];
|
|
1194
|
+
|
|
1195
|
+
const artifactDirectoryAbsolute = path.resolve(
|
|
1196
|
+
cwd,
|
|
1197
|
+
asNonEmptyString(options.artifactDirectory) ?? DEFAULT_RECORD_ARTIFACT_DIR,
|
|
1198
|
+
runId,
|
|
1199
|
+
);
|
|
1200
|
+
|
|
1201
|
+
try {
|
|
1202
|
+
fs.mkdirSync(artifactDirectoryAbsolute, { recursive: true });
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
return createFailureResult({
|
|
1205
|
+
cwd,
|
|
1206
|
+
generatedAt,
|
|
1207
|
+
projectPath: projectPathInput,
|
|
1208
|
+
observedCommand,
|
|
1209
|
+
diagnostics: [createDiagnostic({
|
|
1210
|
+
code: "runtimeRecord.artifactDirectoryCreateFailed",
|
|
1211
|
+
level: "error",
|
|
1212
|
+
source: "runtimeRecord",
|
|
1213
|
+
message: "Failed to create runtime record artifact directory.",
|
|
1214
|
+
details: {
|
|
1215
|
+
artifactDirectory: artifactDirectoryAbsolute,
|
|
1216
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1217
|
+
},
|
|
1218
|
+
})],
|
|
1219
|
+
code: "runtimeRecord.artifactDirectoryCreateFailed",
|
|
1220
|
+
message: "Record command aborted because artifact directory creation failed.",
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const appMap = probeAppMapAvailability({
|
|
1225
|
+
projectPathAbsolute,
|
|
1226
|
+
options,
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
diagnostics.push(...normalizeDiagnosticList(appMap.diagnostics, "runtimeRecord"));
|
|
1230
|
+
|
|
1231
|
+
if (!appMap.available) {
|
|
1232
|
+
diagnostics.push(createDiagnostic({
|
|
1233
|
+
code: "runtimeRecord.appMapUnavailable",
|
|
1234
|
+
level: "warning",
|
|
1235
|
+
source: "runtimeRecord",
|
|
1236
|
+
message: "AppMap tooling was not detected. Runtime trace was captured without AppMap instrumentation, and setup guidance was generated.",
|
|
1237
|
+
path: "/appMap",
|
|
1238
|
+
details: {
|
|
1239
|
+
reason: appMap.reason,
|
|
1240
|
+
},
|
|
1241
|
+
}));
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
const packageAutodiscovery = optionEnabled(
|
|
1245
|
+
options.packageAutodiscovery
|
|
1246
|
+
?? options.packageAutoDiscovery
|
|
1247
|
+
?? options.autodiscoverPackages
|
|
1248
|
+
?? options["package-autodiscovery"],
|
|
1249
|
+
)
|
|
1250
|
+
? createPackageAutodiscoveryInstrumentation({
|
|
1251
|
+
artifactDirectoryAbsolute,
|
|
1252
|
+
diagnostics,
|
|
1253
|
+
})
|
|
1254
|
+
: {
|
|
1255
|
+
enabled: false,
|
|
1256
|
+
reason: "not-requested",
|
|
1257
|
+
logAbsolutePath: null,
|
|
1258
|
+
loaderAbsolutePath: null,
|
|
1259
|
+
preloadAbsolutePath: null,
|
|
1260
|
+
env: process.env,
|
|
1261
|
+
events: [],
|
|
1262
|
+
diagnostics: [],
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
diagnostics.push(...normalizeDiagnosticList(packageAutodiscovery.diagnostics, "runtimeRecord"));
|
|
1266
|
+
|
|
1267
|
+
const execution = executeObservedCommand({
|
|
1268
|
+
commandTokens: observedCommand,
|
|
1269
|
+
cwd: projectPathAbsolute,
|
|
1270
|
+
env: packageAutodiscovery.env,
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
packageAutodiscovery.events = packageAutodiscovery.enabled
|
|
1274
|
+
? readPackageAutodiscoveryEvents({
|
|
1275
|
+
logAbsolutePath: packageAutodiscovery.logAbsolutePath,
|
|
1276
|
+
diagnostics,
|
|
1277
|
+
})
|
|
1278
|
+
: [];
|
|
1279
|
+
|
|
1280
|
+
if (execution.errorMessage) {
|
|
1281
|
+
diagnostics.push(createDiagnostic({
|
|
1282
|
+
code: "runtimeRecord.commandExecutionSpawnError",
|
|
1283
|
+
level: "error",
|
|
1284
|
+
source: "runtimeRecord",
|
|
1285
|
+
message: "Observed command failed to start.",
|
|
1286
|
+
path: "/trace/command",
|
|
1287
|
+
details: {
|
|
1288
|
+
command: execution.commandLine,
|
|
1289
|
+
error: execution.errorMessage,
|
|
1290
|
+
},
|
|
1291
|
+
}));
|
|
1292
|
+
} else if (execution.exitCode !== 0) {
|
|
1293
|
+
diagnostics.push(createDiagnostic({
|
|
1294
|
+
code: "runtimeRecord.commandExecutionNonZeroExit",
|
|
1295
|
+
level: "warning",
|
|
1296
|
+
source: "runtimeRecord",
|
|
1297
|
+
message: "Observed command exited with a non-zero status.",
|
|
1298
|
+
path: "/trace/command",
|
|
1299
|
+
details: {
|
|
1300
|
+
command: execution.commandLine,
|
|
1301
|
+
exitCode: execution.exitCode,
|
|
1302
|
+
},
|
|
1303
|
+
}));
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
const runtimeTraceRecord = {
|
|
1307
|
+
schemaVersion: RUNTIME_TRACE_SCHEMA_VERSION,
|
|
1308
|
+
schema_version: RUNTIME_TRACE_SCHEMA_VERSION,
|
|
1309
|
+
generatedAt,
|
|
1310
|
+
runId,
|
|
1311
|
+
command: {
|
|
1312
|
+
name: "record",
|
|
1313
|
+
projectPath: projectPathInput,
|
|
1314
|
+
observedCommand,
|
|
1315
|
+
},
|
|
1316
|
+
appMap: {
|
|
1317
|
+
available: appMap.available,
|
|
1318
|
+
reason: appMap.reason,
|
|
1319
|
+
probes: appMap.probes,
|
|
1320
|
+
},
|
|
1321
|
+
execution: {
|
|
1322
|
+
ok: execution.ok,
|
|
1323
|
+
command: execution.command,
|
|
1324
|
+
args: execution.args,
|
|
1325
|
+
commandLine: execution.commandLine,
|
|
1326
|
+
cwd: toRelativePath(cwd, execution.cwd),
|
|
1327
|
+
exitCode: execution.exitCode,
|
|
1328
|
+
signal: execution.signal,
|
|
1329
|
+
stdout: execution.stdout,
|
|
1330
|
+
stderr: execution.errorMessage
|
|
1331
|
+
? `${execution.stderr}${execution.stderr ? "\n" : ""}${execution.errorMessage}`
|
|
1332
|
+
: execution.stderr,
|
|
1333
|
+
startedAt: execution.startedAt,
|
|
1334
|
+
completedAt: execution.completedAt,
|
|
1335
|
+
durationMs: execution.durationMs,
|
|
1336
|
+
},
|
|
1337
|
+
packageAutodiscovery: {
|
|
1338
|
+
enabled: packageAutodiscovery.enabled,
|
|
1339
|
+
reason: packageAutodiscovery.reason,
|
|
1340
|
+
eventCount: packageAutodiscovery.events.length,
|
|
1341
|
+
events: packageAutodiscovery.events,
|
|
1342
|
+
},
|
|
1343
|
+
diagnostics: normalizeDiagnosticList(diagnostics, "runtimeRecord"),
|
|
1344
|
+
};
|
|
1345
|
+
|
|
1346
|
+
const runtimeTraceAbsolutePath = path.join(
|
|
1347
|
+
artifactDirectoryAbsolute,
|
|
1348
|
+
"runtime-trace.json",
|
|
1349
|
+
);
|
|
1350
|
+
|
|
1351
|
+
const runtimeTraceWriteOk = writeArtifact(
|
|
1352
|
+
runtimeTraceAbsolutePath,
|
|
1353
|
+
`${JSON.stringify(runtimeTraceRecord, null, 2)}\n`,
|
|
1354
|
+
diagnostics,
|
|
1355
|
+
{
|
|
1356
|
+
code: "runtimeRecord.runtimeTraceWriteFailed",
|
|
1357
|
+
message: "Failed to write runtime trace artifact.",
|
|
1358
|
+
details: {
|
|
1359
|
+
artifact: "runtime-trace.json",
|
|
1360
|
+
},
|
|
1361
|
+
},
|
|
1362
|
+
);
|
|
1363
|
+
|
|
1364
|
+
let setupGuidanceAbsolutePath = null;
|
|
1365
|
+
let setupGuidance = null;
|
|
1366
|
+
|
|
1367
|
+
if (!appMap.available) {
|
|
1368
|
+
const rerunCommand = createAppMapRerunCommand({
|
|
1369
|
+
projectPathInput,
|
|
1370
|
+
observedCommand,
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
const instructions = createAppMapGuidanceInstructions({
|
|
1374
|
+
projectPathInput,
|
|
1375
|
+
observedCommand,
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
setupGuidance = {
|
|
1379
|
+
schemaVersion: APPMAP_SETUP_GUIDANCE_SCHEMA_VERSION,
|
|
1380
|
+
schema_version: APPMAP_SETUP_GUIDANCE_SCHEMA_VERSION,
|
|
1381
|
+
generatedAt,
|
|
1382
|
+
runId,
|
|
1383
|
+
reason: appMap.reason,
|
|
1384
|
+
rerunCommand,
|
|
1385
|
+
instructions,
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1388
|
+
setupGuidanceAbsolutePath = path.join(
|
|
1389
|
+
artifactDirectoryAbsolute,
|
|
1390
|
+
"appmap-setup-guidance.md",
|
|
1391
|
+
);
|
|
1392
|
+
|
|
1393
|
+
const markdown = renderAppMapGuidanceMarkdown({
|
|
1394
|
+
generatedAt,
|
|
1395
|
+
projectPathInput,
|
|
1396
|
+
appMap,
|
|
1397
|
+
instructions,
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
writeArtifact(
|
|
1401
|
+
setupGuidanceAbsolutePath,
|
|
1402
|
+
`${markdown.endsWith("\n") ? markdown : `${markdown}\n`}`,
|
|
1403
|
+
diagnostics,
|
|
1404
|
+
{
|
|
1405
|
+
code: "runtimeRecord.setupGuidanceWriteFailed",
|
|
1406
|
+
message: "Failed to write AppMap setup guidance artifact.",
|
|
1407
|
+
details: {
|
|
1408
|
+
artifact: "appmap-setup-guidance.md",
|
|
1409
|
+
},
|
|
1410
|
+
},
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
const runtimeTraceManifest = runtimeTraceWriteOk
|
|
1415
|
+
? writeRuntimeTraceManifest({
|
|
1416
|
+
cwd,
|
|
1417
|
+
runId,
|
|
1418
|
+
generatedAt,
|
|
1419
|
+
projectPath: projectPathInput,
|
|
1420
|
+
runtimeTracePath: toRelativePath(cwd, runtimeTraceAbsolutePath),
|
|
1421
|
+
commandLine: execution.commandLine,
|
|
1422
|
+
appMapAvailable: appMap.available,
|
|
1423
|
+
commandExitCode: execution.exitCode,
|
|
1424
|
+
commandDurationMs: execution.durationMs,
|
|
1425
|
+
manifestPath: asNonEmptyString(options.runtimeTraceManifestPath),
|
|
1426
|
+
retentionCount: options.runtimeTraceManifestRetentionCount,
|
|
1427
|
+
})
|
|
1428
|
+
: {
|
|
1429
|
+
ok: false,
|
|
1430
|
+
artifact: null,
|
|
1431
|
+
entryCount: 0,
|
|
1432
|
+
droppedEntryCount: 0,
|
|
1433
|
+
diagnostics: [createDiagnostic({
|
|
1434
|
+
code: "runtimeRecord.traceManifestSkipped",
|
|
1435
|
+
level: "warning",
|
|
1436
|
+
source: "runtimeRecord",
|
|
1437
|
+
message: "Runtime trace manifest update was skipped because runtime trace artifact emission failed.",
|
|
1438
|
+
path: "/artifacts/runtimeTraceManifest",
|
|
1439
|
+
})],
|
|
1440
|
+
};
|
|
1441
|
+
|
|
1442
|
+
diagnostics.push(...normalizeDiagnosticList(runtimeTraceManifest.diagnostics, "runtimeRecord"));
|
|
1443
|
+
|
|
1444
|
+
const recordHandoff = options.emitRecordHandoffArtifacts === false
|
|
1445
|
+
? null
|
|
1446
|
+
: emitRecordHandoffArtifacts({
|
|
1447
|
+
cwd,
|
|
1448
|
+
options,
|
|
1449
|
+
diagnostics,
|
|
1450
|
+
generatedAt,
|
|
1451
|
+
runId,
|
|
1452
|
+
projectPathInput,
|
|
1453
|
+
projectPathAbsolute,
|
|
1454
|
+
observedCommand,
|
|
1455
|
+
execution,
|
|
1456
|
+
packageAutodiscovery,
|
|
1457
|
+
runtimeTracePath: runtimeTraceWriteOk ? toRelativePath(cwd, runtimeTraceAbsolutePath) : null,
|
|
1458
|
+
setupGuidancePath: setupGuidanceAbsolutePath ? toRelativePath(cwd, setupGuidanceAbsolutePath) : null,
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
const normalizedDiagnostics = normalizeDiagnosticList(diagnostics, "runtimeRecord");
|
|
1462
|
+
|
|
1463
|
+
const ok = (
|
|
1464
|
+
execution.ok
|
|
1465
|
+
&& !hasErrorDiagnostic(normalizedDiagnostics)
|
|
1466
|
+
);
|
|
1467
|
+
|
|
1468
|
+
return {
|
|
1469
|
+
schemaVersion: RUNTIME_RECORD_RESULT_SCHEMA_VERSION,
|
|
1470
|
+
schema_version: RUNTIME_RECORD_RESULT_SCHEMA_VERSION,
|
|
1471
|
+
ok,
|
|
1472
|
+
cwd,
|
|
1473
|
+
generatedAt,
|
|
1474
|
+
runId,
|
|
1475
|
+
command: {
|
|
1476
|
+
name: "record",
|
|
1477
|
+
projectPath: projectPathInput,
|
|
1478
|
+
observedCommand,
|
|
1479
|
+
},
|
|
1480
|
+
appMap: {
|
|
1481
|
+
available: appMap.available,
|
|
1482
|
+
reason: appMap.reason,
|
|
1483
|
+
probes: appMap.probes,
|
|
1484
|
+
},
|
|
1485
|
+
trace: {
|
|
1486
|
+
ok: execution.ok,
|
|
1487
|
+
commandLine: execution.commandLine,
|
|
1488
|
+
cwd: toRelativePath(cwd, execution.cwd),
|
|
1489
|
+
exitCode: execution.exitCode,
|
|
1490
|
+
signal: execution.signal,
|
|
1491
|
+
stdout: execution.stdout,
|
|
1492
|
+
stderr: execution.errorMessage
|
|
1493
|
+
? `${execution.stderr}${execution.stderr ? "\n" : ""}${execution.errorMessage}`
|
|
1494
|
+
: execution.stderr,
|
|
1495
|
+
startedAt: execution.startedAt,
|
|
1496
|
+
completedAt: execution.completedAt,
|
|
1497
|
+
durationMs: execution.durationMs,
|
|
1498
|
+
packageAutodiscovery: {
|
|
1499
|
+
enabled: packageAutodiscovery.enabled,
|
|
1500
|
+
reason: packageAutodiscovery.reason,
|
|
1501
|
+
eventCount: packageAutodiscovery.events.length,
|
|
1502
|
+
events: packageAutodiscovery.events,
|
|
1503
|
+
},
|
|
1504
|
+
},
|
|
1505
|
+
guidance: setupGuidance,
|
|
1506
|
+
artifacts: {
|
|
1507
|
+
runDirectory: toRelativePath(cwd, artifactDirectoryAbsolute),
|
|
1508
|
+
runtimeTrace: {
|
|
1509
|
+
path: toRelativePath(cwd, runtimeTraceAbsolutePath),
|
|
1510
|
+
absolutePath: runtimeTraceAbsolutePath,
|
|
1511
|
+
schemaVersion: RUNTIME_TRACE_SCHEMA_VERSION,
|
|
1512
|
+
schema_version: RUNTIME_TRACE_SCHEMA_VERSION,
|
|
1513
|
+
},
|
|
1514
|
+
runtimeTraceManifest: runtimeTraceManifest.artifact,
|
|
1515
|
+
setupGuidance: setupGuidanceAbsolutePath
|
|
1516
|
+
? {
|
|
1517
|
+
path: toRelativePath(cwd, setupGuidanceAbsolutePath),
|
|
1518
|
+
absolutePath: setupGuidanceAbsolutePath,
|
|
1519
|
+
schemaVersion: APPMAP_SETUP_GUIDANCE_SCHEMA_VERSION,
|
|
1520
|
+
schema_version: APPMAP_SETUP_GUIDANCE_SCHEMA_VERSION,
|
|
1521
|
+
}
|
|
1522
|
+
: null,
|
|
1523
|
+
packageAutodiscovery: packageAutodiscovery.enabled
|
|
1524
|
+
? {
|
|
1525
|
+
logPath: toRelativePath(cwd, packageAutodiscovery.logAbsolutePath),
|
|
1526
|
+
logAbsolutePath: packageAutodiscovery.logAbsolutePath,
|
|
1527
|
+
loaderPath: toRelativePath(cwd, packageAutodiscovery.loaderAbsolutePath),
|
|
1528
|
+
preloadPath: toRelativePath(cwd, packageAutodiscovery.preloadAbsolutePath),
|
|
1529
|
+
eventCount: packageAutodiscovery.events.length,
|
|
1530
|
+
}
|
|
1531
|
+
: null,
|
|
1532
|
+
...(recordHandoff ? recordHandoff.artifacts : {}),
|
|
1533
|
+
},
|
|
1534
|
+
summary: {
|
|
1535
|
+
appMapAvailable: appMap.available,
|
|
1536
|
+
setupGuidanceEmitted: Boolean(setupGuidanceAbsolutePath),
|
|
1537
|
+
observedCommandSucceeded: execution.ok,
|
|
1538
|
+
commandExitCode: execution.exitCode,
|
|
1539
|
+
commandDurationMs: execution.durationMs,
|
|
1540
|
+
runtimeTraceManifestUpdated: runtimeTraceManifest.ok,
|
|
1541
|
+
runtimeTraceManifestEntryCount: runtimeTraceManifest.entryCount,
|
|
1542
|
+
runtimeTraceManifestDroppedEntryCount: runtimeTraceManifest.droppedEntryCount,
|
|
1543
|
+
...(recordHandoff ? recordHandoff.summary : {}),
|
|
1544
|
+
},
|
|
1545
|
+
diagnostics: normalizedDiagnostics,
|
|
1546
|
+
commandsRun: [execution.commandLine],
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
export const RUNTIME_RECORD_INTERNALS = Object.freeze({
|
|
1551
|
+
resolveProjectPathInput,
|
|
1552
|
+
resolveObservedCommand,
|
|
1553
|
+
sanitizeSegment,
|
|
1554
|
+
probeAppMapAvailability,
|
|
1555
|
+
executeObservedCommand,
|
|
1556
|
+
createAppMapRerunCommand,
|
|
1557
|
+
createAppMapGuidanceInstructions,
|
|
1558
|
+
renderAppMapGuidanceMarkdown,
|
|
1559
|
+
writeRuntimeTraceManifest,
|
|
1560
|
+
DEFAULT_RECORD_ARTIFACT_DIR,
|
|
1561
|
+
DEFAULT_RUNTIME_TRACE_MANIFEST_PATH,
|
|
1562
|
+
DEFAULT_RUNTIME_TRACE_MANIFEST_RETENTION_COUNT,
|
|
1563
|
+
RUNTIME_TRACE_SCHEMA_VERSION,
|
|
1564
|
+
RUNTIME_TRACE_MANIFEST_SCHEMA_VERSION,
|
|
1565
|
+
APPMAP_SETUP_GUIDANCE_SCHEMA_VERSION,
|
|
1566
|
+
RUNTIME_RECORD_RESULT_SCHEMA_VERSION,
|
|
1567
|
+
});
|
|
1568
|
+
|
|
1569
|
+
export {
|
|
1570
|
+
DEFAULT_RECORD_ARTIFACT_DIR,
|
|
1571
|
+
DEFAULT_RUNTIME_TRACE_MANIFEST_PATH,
|
|
1572
|
+
DEFAULT_RUNTIME_TRACE_MANIFEST_RETENTION_COUNT,
|
|
1573
|
+
RUNTIME_TRACE_SCHEMA_VERSION,
|
|
1574
|
+
RUNTIME_TRACE_MANIFEST_SCHEMA_VERSION,
|
|
1575
|
+
APPMAP_SETUP_GUIDANCE_SCHEMA_VERSION,
|
|
1576
|
+
RUNTIME_RECORD_RESULT_SCHEMA_VERSION,
|
|
1577
|
+
};
|