trickle-cli 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-client.js +1 -1
- package/dist/commands/init.js +146 -20
- package/dist/commands/run.js +47 -14
- package/dist/commands/vars.d.ts +12 -0
- package/dist/commands/vars.js +305 -0
- package/dist/index.js +12 -0
- package/package.json +1 -1
- package/src/api-client.ts +1 -1
- package/src/commands/init.ts +150 -18
- package/src/commands/run.ts +52 -14
- package/src/commands/vars.ts +321 -0
- package/src/index.ts +13 -0
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ const unpack_1 = require("./commands/unpack");
|
|
|
39
39
|
const run_1 = require("./commands/run");
|
|
40
40
|
const annotate_1 = require("./commands/annotate");
|
|
41
41
|
const stubs_1 = require("./commands/stubs");
|
|
42
|
+
const vars_1 = require("./commands/vars");
|
|
42
43
|
const program = new commander_1.Command();
|
|
43
44
|
program
|
|
44
45
|
.name("trickle")
|
|
@@ -387,6 +388,17 @@ program
|
|
|
387
388
|
.action(async (dir, opts) => {
|
|
388
389
|
await (0, stubs_1.stubsCommand)(dir, opts);
|
|
389
390
|
});
|
|
391
|
+
// trickle vars
|
|
392
|
+
program
|
|
393
|
+
.command("vars")
|
|
394
|
+
.description("Show captured variable types and sample values from runtime observations")
|
|
395
|
+
.option("-f, --file <file>", "Filter by file path or module name")
|
|
396
|
+
.option("-m, --module <module>", "Filter by module name")
|
|
397
|
+
.option("--json", "Output raw JSON")
|
|
398
|
+
.option("--tensors", "Show only tensor/ndarray variables")
|
|
399
|
+
.action(async (opts) => {
|
|
400
|
+
await (0, vars_1.varsCommand)(opts);
|
|
401
|
+
});
|
|
390
402
|
// trickle annotate <file>
|
|
391
403
|
program
|
|
392
404
|
.command("annotate <file>")
|
package/package.json
CHANGED
package/src/api-client.ts
CHANGED
|
@@ -256,7 +256,7 @@ export async function fetchFunctionSamples(): Promise<FunctionSample[]> {
|
|
|
256
256
|
for (const fn of functions) {
|
|
257
257
|
try {
|
|
258
258
|
const data = await fetchJson<{ snapshots: Array<{ sample_input: unknown; sample_output: unknown }> }>(
|
|
259
|
-
`/api/types/${
|
|
259
|
+
`/api/types/${fn.id}`
|
|
260
260
|
);
|
|
261
261
|
if (data.snapshots && data.snapshots.length > 0) {
|
|
262
262
|
const snap = data.snapshots[0];
|
package/src/commands/init.ts
CHANGED
|
@@ -46,11 +46,13 @@ function detectProject(dir: string, forcePython: boolean): ProjectInfo {
|
|
|
46
46
|
if (fs.existsSync(tsPath)) {
|
|
47
47
|
info.hasTsConfig = true;
|
|
48
48
|
try {
|
|
49
|
-
// Strip comments
|
|
49
|
+
// Strip comments but preserve glob patterns like /**/*.ts
|
|
50
50
|
const raw = fs.readFileSync(tsPath, "utf-8");
|
|
51
|
+
// Only strip // comments that are NOT inside strings
|
|
52
|
+
// And /* */ comments that are NOT inside strings (careful with globs)
|
|
51
53
|
const cleaned = raw
|
|
52
54
|
.replace(/\/\/.*$/gm, "")
|
|
53
|
-
.replace(
|
|
55
|
+
.replace(/("[^"]*")|\/\*[\s\S]*?\*\//g, (match, str) => str || "");
|
|
54
56
|
info.tsConfig = JSON.parse(cleaned);
|
|
55
57
|
} catch {
|
|
56
58
|
// ignore parse errors
|
|
@@ -206,7 +208,7 @@ function writeInitialTypes(trickleDir: string, isPython: boolean): void {
|
|
|
206
208
|
function updateTsConfig(dir: string, info: ProjectInfo): boolean {
|
|
207
209
|
const tsConfigPath = path.join(dir, "tsconfig.json");
|
|
208
210
|
|
|
209
|
-
if (!info.hasTsConfig
|
|
211
|
+
if (!info.hasTsConfig) {
|
|
210
212
|
// No tsconfig — create a minimal one that includes .trickle
|
|
211
213
|
if (!info.isPython) {
|
|
212
214
|
const newConfig = {
|
|
@@ -227,25 +229,52 @@ function updateTsConfig(dir: string, info: ProjectInfo): boolean {
|
|
|
227
229
|
return false;
|
|
228
230
|
}
|
|
229
231
|
|
|
230
|
-
//
|
|
232
|
+
// Use text-based insertion to preserve formatting and glob patterns
|
|
233
|
+
// (JSON.parse + stringify corrupts /**/ globs by treating them as comments)
|
|
231
234
|
const raw = fs.readFileSync(tsConfigPath, "utf-8");
|
|
232
|
-
const config = info.tsConfig;
|
|
233
235
|
|
|
234
236
|
// Check if .trickle is already included
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
237
|
+
if (raw.includes(".trickle")) return false;
|
|
238
|
+
|
|
239
|
+
// Find the "include" array and insert ".trickle" at the end
|
|
240
|
+
const includeMatch = raw.match(/"include"\s*:\s*\[([^\]]*)\]/);
|
|
241
|
+
if (includeMatch) {
|
|
242
|
+
const bracket = includeMatch.index! + includeMatch[0].length - 1; // position of ]
|
|
243
|
+
const inside = includeMatch[1];
|
|
244
|
+
// Detect if it's single-line or multi-line
|
|
245
|
+
if (inside.includes("\n")) {
|
|
246
|
+
// Multi-line — add before the closing bracket with same indentation
|
|
247
|
+
const lastEntry = inside.match(/.*\S[^\n]*/g);
|
|
248
|
+
const indent = lastEntry ? lastEntry[lastEntry.length - 1].match(/^(\s*)/)?.[1] || " " : " ";
|
|
249
|
+
const updated = raw.slice(0, bracket) + `,\n${indent}".trickle"` + raw.slice(bracket);
|
|
250
|
+
fs.writeFileSync(tsConfigPath, updated, "utf-8");
|
|
251
|
+
} else {
|
|
252
|
+
// Single-line: ["src/**/*.ts", "tmp/**/*.ts"] → add ".trickle"
|
|
253
|
+
const updated = raw.slice(0, bracket) + ', ".trickle"' + raw.slice(bracket);
|
|
254
|
+
fs.writeFileSync(tsConfigPath, updated, "utf-8");
|
|
255
|
+
}
|
|
256
|
+
return true;
|
|
238
257
|
}
|
|
239
258
|
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
259
|
+
// No include array — add one after compilerOptions closing brace
|
|
260
|
+
const compilerEnd = raw.match(/"compilerOptions"\s*:\s*\{/);
|
|
261
|
+
if (compilerEnd) {
|
|
262
|
+
// Find the closing brace of compilerOptions
|
|
263
|
+
const startBrace = raw.indexOf("{", compilerEnd.index! + compilerEnd[0].length - 1);
|
|
264
|
+
let depth = 1;
|
|
265
|
+
let pos = startBrace + 1;
|
|
266
|
+
while (pos < raw.length && depth > 0) {
|
|
267
|
+
if (raw[pos] === "{") depth++;
|
|
268
|
+
else if (raw[pos] === "}") depth--;
|
|
269
|
+
pos++;
|
|
270
|
+
}
|
|
271
|
+
// pos is now right after the closing brace of compilerOptions
|
|
272
|
+
const updated = raw.slice(0, pos) + `,\n "include": ["src", ".trickle"]` + raw.slice(pos);
|
|
273
|
+
fs.writeFileSync(tsConfigPath, updated, "utf-8");
|
|
274
|
+
return true;
|
|
245
275
|
}
|
|
246
276
|
|
|
247
|
-
|
|
248
|
-
return true;
|
|
277
|
+
return false;
|
|
249
278
|
}
|
|
250
279
|
|
|
251
280
|
function updatePackageJson(dir: string, info: ProjectInfo): { scriptsAdded: string[] } {
|
|
@@ -302,6 +331,100 @@ function updatePackageJson(dir: string, info: ProjectInfo): { scriptsAdded: stri
|
|
|
302
331
|
return { scriptsAdded: added };
|
|
303
332
|
}
|
|
304
333
|
|
|
334
|
+
function updateVitestConfig(dir: string): boolean {
|
|
335
|
+
// Look for vitest.config.ts, vitest.config.js, vitest.config.mts, vite.config.ts, vite.config.js
|
|
336
|
+
const candidates = [
|
|
337
|
+
"vitest.config.ts",
|
|
338
|
+
"vitest.config.js",
|
|
339
|
+
"vitest.config.mts",
|
|
340
|
+
"vite.config.ts",
|
|
341
|
+
"vite.config.js",
|
|
342
|
+
];
|
|
343
|
+
|
|
344
|
+
let configFile: string | null = null;
|
|
345
|
+
for (const c of candidates) {
|
|
346
|
+
if (fs.existsSync(path.join(dir, c))) {
|
|
347
|
+
configFile = c;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (!configFile) return false;
|
|
353
|
+
|
|
354
|
+
const configPath = path.join(dir, configFile);
|
|
355
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
356
|
+
|
|
357
|
+
// Already has tricklePlugin
|
|
358
|
+
if (content.includes("tricklePlugin")) return false;
|
|
359
|
+
|
|
360
|
+
// Find the import section end and plugins array
|
|
361
|
+
const lines = content.split("\n");
|
|
362
|
+
let lastImportLine = -1;
|
|
363
|
+
let pluginsLine = -1;
|
|
364
|
+
let pluginsArrayContent = "";
|
|
365
|
+
|
|
366
|
+
for (let i = 0; i < lines.length; i++) {
|
|
367
|
+
const line = lines[i];
|
|
368
|
+
if (/^\s*import\s/.test(line)) {
|
|
369
|
+
// Track multi-line imports
|
|
370
|
+
lastImportLine = i;
|
|
371
|
+
if (!line.includes(";") && !line.includes("from")) {
|
|
372
|
+
// Multi-line import — find the closing line
|
|
373
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
374
|
+
if (lines[j].includes("from")) {
|
|
375
|
+
lastImportLine = j;
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (/plugins\s*:\s*\[/.test(line)) {
|
|
382
|
+
pluginsLine = i;
|
|
383
|
+
pluginsArrayContent = line;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (lastImportLine === -1) return false;
|
|
388
|
+
|
|
389
|
+
// Add import after last import
|
|
390
|
+
const importLine = `import { tricklePlugin } from "trickle-observe/vite-plugin";`;
|
|
391
|
+
lines.splice(lastImportLine + 1, 0, importLine);
|
|
392
|
+
|
|
393
|
+
// Adjust pluginsLine index since we inserted a line
|
|
394
|
+
if (pluginsLine > lastImportLine) {
|
|
395
|
+
pluginsLine += 1;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Add tricklePlugin() to plugins array
|
|
399
|
+
if (pluginsLine !== -1) {
|
|
400
|
+
const line = lines[pluginsLine];
|
|
401
|
+
// Check if plugins array is on one line: plugins: [something()],
|
|
402
|
+
if (/plugins\s*:\s*\[.*\]/.test(line)) {
|
|
403
|
+
// Insert tricklePlugin() before the closing bracket
|
|
404
|
+
lines[pluginsLine] = line.replace(/\]/, ", tricklePlugin()]");
|
|
405
|
+
} else {
|
|
406
|
+
// Multi-line plugins — add after the opening bracket line
|
|
407
|
+
const indent = line.match(/^(\s*)/)?.[1] || "";
|
|
408
|
+
const innerIndent = indent + " ";
|
|
409
|
+
lines.splice(pluginsLine + 1, 0, `${innerIndent}tricklePlugin(),`);
|
|
410
|
+
}
|
|
411
|
+
} else {
|
|
412
|
+
// No plugins array found — need to add one
|
|
413
|
+
// Find defineConfig({ and add plugins after it
|
|
414
|
+
for (let i = 0; i < lines.length; i++) {
|
|
415
|
+
if (/defineConfig\s*\(\s*\{/.test(lines[i])) {
|
|
416
|
+
const indent = lines[i].match(/^(\s*)/)?.[1] || "";
|
|
417
|
+
const innerIndent = indent + " ";
|
|
418
|
+
lines.splice(i + 1, 0, `${innerIndent}plugins: [tricklePlugin()],`);
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
fs.writeFileSync(configPath, lines.join("\n"), "utf-8");
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
|
|
305
428
|
function updateGitignore(dir: string): boolean {
|
|
306
429
|
const giPath = path.join(dir, ".gitignore");
|
|
307
430
|
let content = "";
|
|
@@ -379,7 +502,15 @@ export async function initCommand(opts: InitOptions): Promise<void> {
|
|
|
379
502
|
}
|
|
380
503
|
}
|
|
381
504
|
|
|
382
|
-
// Step 5: Update
|
|
505
|
+
// Step 5: Update vitest/vite config with tricklePlugin
|
|
506
|
+
if (!info.isPython) {
|
|
507
|
+
const vitestUpdated = updateVitestConfig(dir);
|
|
508
|
+
if (vitestUpdated) {
|
|
509
|
+
console.log(` ${chalk.green("~")} Updated ${chalk.bold("vitest.config.ts")} — added tricklePlugin() for variable tracing`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Step 6: Update package.json scripts
|
|
383
514
|
if (info.hasPackageJson) {
|
|
384
515
|
const { scriptsAdded } = updatePackageJson(dir, info);
|
|
385
516
|
if (scriptsAdded.length > 0) {
|
|
@@ -389,13 +520,13 @@ export async function initCommand(opts: InitOptions): Promise<void> {
|
|
|
389
520
|
}
|
|
390
521
|
}
|
|
391
522
|
|
|
392
|
-
// Step
|
|
523
|
+
// Step 7: Update .gitignore
|
|
393
524
|
const giUpdated = updateGitignore(dir);
|
|
394
525
|
if (giUpdated) {
|
|
395
526
|
console.log(` ${chalk.green("~")} Updated ${chalk.bold(".gitignore")} — added .trickle/`);
|
|
396
527
|
}
|
|
397
528
|
|
|
398
|
-
// Step
|
|
529
|
+
// Step 8: Print next steps
|
|
399
530
|
console.log("");
|
|
400
531
|
console.log(chalk.bold(" Next steps:"));
|
|
401
532
|
console.log("");
|
|
@@ -412,6 +543,7 @@ export async function initCommand(opts: InitOptions): Promise<void> {
|
|
|
412
543
|
console.log("");
|
|
413
544
|
console.log(chalk.gray(" Other commands:"));
|
|
414
545
|
console.log(chalk.gray(" trickle functions — list observed functions"));
|
|
546
|
+
console.log(chalk.gray(" trickle vars — list captured variable types + values"));
|
|
415
547
|
console.log(chalk.gray(" trickle types <name> — see types + sample data"));
|
|
416
548
|
console.log(chalk.gray(" trickle annotate src/ — add type annotations to source files"));
|
|
417
549
|
|
package/src/commands/run.ts
CHANGED
|
@@ -151,10 +151,17 @@ function autoDetectCommand(input: string): string {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
function findTsRunner(): string {
|
|
154
|
+
const { execSync } = require("child_process");
|
|
155
|
+
|
|
156
|
+
// Add node_modules/.bin to PATH so local binaries are found
|
|
157
|
+
const binPath = path.join(process.cwd(), "node_modules", ".bin");
|
|
158
|
+
const currentPath = process.env.PATH || "";
|
|
159
|
+
const augmentedPath = currentPath.includes(binPath) ? currentPath : `${binPath}${path.delimiter}${currentPath}`;
|
|
160
|
+
const execOpts = { stdio: "ignore" as const, env: { ...process.env, PATH: augmentedPath } };
|
|
161
|
+
|
|
154
162
|
// Check for tsx (fastest, most compatible)
|
|
155
163
|
try {
|
|
156
|
-
|
|
157
|
-
execSync("tsx --version", { stdio: "ignore" });
|
|
164
|
+
execSync("tsx --version", execOpts);
|
|
158
165
|
return "tsx";
|
|
159
166
|
} catch {
|
|
160
167
|
// not available
|
|
@@ -162,8 +169,7 @@ function findTsRunner(): string {
|
|
|
162
169
|
|
|
163
170
|
// Check for ts-node
|
|
164
171
|
try {
|
|
165
|
-
|
|
166
|
-
execSync("ts-node --version", { stdio: "ignore" });
|
|
172
|
+
execSync("ts-node --version", execOpts);
|
|
167
173
|
return "ts-node";
|
|
168
174
|
} catch {
|
|
169
175
|
// not available
|
|
@@ -171,8 +177,7 @@ function findTsRunner(): string {
|
|
|
171
177
|
|
|
172
178
|
// Check for bun (supports TS natively)
|
|
173
179
|
try {
|
|
174
|
-
|
|
175
|
-
execSync("bun --version", { stdio: "ignore" });
|
|
180
|
+
execSync("bun --version", execOpts);
|
|
176
181
|
return "bun";
|
|
177
182
|
} catch {
|
|
178
183
|
// not available
|
|
@@ -447,6 +452,17 @@ async function executeSingleRun(
|
|
|
447
452
|
}
|
|
448
453
|
}
|
|
449
454
|
|
|
455
|
+
// Show variable/tensor summary if variables.jsonl exists
|
|
456
|
+
const varsJsonlPath = path.join(localDir, "variables.jsonl");
|
|
457
|
+
if (fs.existsSync(varsJsonlPath)) {
|
|
458
|
+
try {
|
|
459
|
+
const { showVarsSummary } = await import("./vars");
|
|
460
|
+
showVarsSummary(varsJsonlPath);
|
|
461
|
+
} catch {
|
|
462
|
+
// vars module not available, skip
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
450
466
|
console.log(chalk.gray(" " + "─".repeat(50)));
|
|
451
467
|
console.log("");
|
|
452
468
|
|
|
@@ -538,6 +554,9 @@ function startLiveBackendTypes(sourceFile: string): () => void {
|
|
|
538
554
|
const baseName = path.basename(sourceFile, ext);
|
|
539
555
|
const sidecarName = isPython ? `${baseName}.pyi` : `${baseName}.d.ts`;
|
|
540
556
|
const sidecarPath = path.join(dir, sidecarName);
|
|
557
|
+
// Also check .trickle/types/ where auto-codegen now writes
|
|
558
|
+
const trickleDir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
|
|
559
|
+
const trickleTypesPath = path.join(trickleDir, 'types', `${baseName}.d.ts`);
|
|
541
560
|
|
|
542
561
|
const poll = async () => {
|
|
543
562
|
if (stopped) return;
|
|
@@ -545,8 +564,10 @@ function startLiveBackendTypes(sourceFile: string): () => void {
|
|
|
545
564
|
const { stubsCommand } = await import("./stubs");
|
|
546
565
|
await stubsCommand(dir, { silent: true });
|
|
547
566
|
|
|
548
|
-
|
|
549
|
-
|
|
567
|
+
// Check both old sidecar path and new .trickle/types/ path
|
|
568
|
+
const effectivePath = fs.existsSync(trickleTypesPath) ? trickleTypesPath : sidecarPath;
|
|
569
|
+
if (fs.existsSync(effectivePath)) {
|
|
570
|
+
const content = fs.readFileSync(effectivePath, "utf-8");
|
|
550
571
|
const funcCount = (content.match(/export declare function/g) || []).length;
|
|
551
572
|
|
|
552
573
|
if (funcCount > lastFunctionCount) {
|
|
@@ -635,12 +656,16 @@ async function autoGenerateSidecar(filePath: string): Promise<void> {
|
|
|
635
656
|
const { stubsCommand } = await import("./stubs");
|
|
636
657
|
await stubsCommand(dir, { silent: true });
|
|
637
658
|
|
|
638
|
-
// Check if
|
|
639
|
-
|
|
640
|
-
|
|
659
|
+
// Check if types were generated (either sidecar or .trickle/types/)
|
|
660
|
+
const tDir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
|
|
661
|
+
const tTypesPath = path.join(tDir, 'types', `${baseName}.d.ts`);
|
|
662
|
+
const effectiveSidecar = fs.existsSync(tTypesPath) ? tTypesPath : sidecarPath;
|
|
663
|
+
const displayName = fs.existsSync(tTypesPath) ? `${baseName}.d.ts` : sidecarName;
|
|
664
|
+
if (fs.existsSync(effectiveSidecar)) {
|
|
665
|
+
const stats = fs.statSync(effectiveSidecar);
|
|
641
666
|
if (stats.size > 0) {
|
|
642
667
|
console.log(
|
|
643
|
-
chalk.green(`\n Types written to ${chalk.bold(
|
|
668
|
+
chalk.green(`\n Types written to ${chalk.bold(displayName)}`),
|
|
644
669
|
);
|
|
645
670
|
}
|
|
646
671
|
}
|
|
@@ -953,9 +978,17 @@ function injectObservation(
|
|
|
953
978
|
const useEsm = isEsmFile(command) && observeEsmPath;
|
|
954
979
|
|
|
955
980
|
if (useEsm) {
|
|
981
|
+
// Use both ESM hooks (for exported functions) and CJS hook (for Express auto-detection)
|
|
982
|
+
const modified = command.replace(
|
|
983
|
+
new RegExp(`^${runner}\\s`),
|
|
984
|
+
`${runner} -r ${observePath} --import ${observeEsmPath} `,
|
|
985
|
+
);
|
|
986
|
+
return { instrumentedCommand: modified, env };
|
|
987
|
+
} else if (runner === "tsx") {
|
|
988
|
+
// tsx always uses ESM internally — inject both CJS and ESM hooks
|
|
956
989
|
const modified = command.replace(
|
|
957
990
|
new RegExp(`^${runner}\\s`),
|
|
958
|
-
`${runner} --import ${observeEsmPath} `,
|
|
991
|
+
`${runner} -r ${observePath} --import ${observeEsmPath} `,
|
|
959
992
|
);
|
|
960
993
|
return { instrumentedCommand: modified, env };
|
|
961
994
|
} else {
|
|
@@ -1060,10 +1093,15 @@ function runProcess(
|
|
|
1060
1093
|
env: Record<string, string>,
|
|
1061
1094
|
): Promise<number> {
|
|
1062
1095
|
return new Promise((resolve) => {
|
|
1096
|
+
// Add node_modules/.bin to PATH so local binaries (tsx, ts-node, etc.) are found
|
|
1097
|
+
const binPath = path.join(process.cwd(), "node_modules", ".bin");
|
|
1098
|
+
const currentPath = process.env.PATH || "";
|
|
1099
|
+
const augmentedPath = currentPath.includes(binPath) ? currentPath : `${binPath}${path.delimiter}${currentPath}`;
|
|
1100
|
+
|
|
1063
1101
|
const proc = spawn(command, [], {
|
|
1064
1102
|
stdio: "inherit",
|
|
1065
1103
|
shell: true,
|
|
1066
|
-
env: { ...process.env, ...env },
|
|
1104
|
+
env: { ...process.env, ...env, PATH: augmentedPath },
|
|
1067
1105
|
});
|
|
1068
1106
|
|
|
1069
1107
|
proc.on("error", (err) => {
|