@mindstudio-ai/local-model-tunnel 0.5.9 → 0.5.11
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/{chunk-253MU7UA.js → chunk-U2KITVPK.js} +2 -2
- package/dist/{chunk-4CMGJFH3.js → chunk-XP4GPID6.js} +756 -228
- package/dist/chunk-XP4GPID6.js.map +1 -0
- package/dist/chunk-YYWJE5O6.js +500 -0
- package/dist/chunk-YYWJE5O6.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/headless.d.ts +2 -66
- package/dist/headless.js +2 -2
- package/dist/index.js +3 -3
- package/dist/{tui-343LXUFQ.js → tui-YCLMVYAJ.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-4CMGJFH3.js.map +0 -1
- package/dist/chunk-UNG63WXC.js +0 -368
- package/dist/chunk-UNG63WXC.js.map +0 -1
- /package/dist/{chunk-253MU7UA.js.map → chunk-U2KITVPK.js.map} +0 -0
- /package/dist/{tui-343LXUFQ.js.map → tui-YCLMVYAJ.js.map} +0 -0
|
@@ -266,6 +266,147 @@ var DevPollError = class extends Error {
|
|
|
266
266
|
}
|
|
267
267
|
};
|
|
268
268
|
|
|
269
|
+
// src/dev/ndjson-log.ts
|
|
270
|
+
import fs2 from "fs";
|
|
271
|
+
import { join } from "path";
|
|
272
|
+
var NdjsonLog = class {
|
|
273
|
+
constructor(filename, maxLines = 500, keepLines = 300, maxBytes = 2 * 1024 * 1024) {
|
|
274
|
+
this.filename = filename;
|
|
275
|
+
this.maxLines = maxLines;
|
|
276
|
+
this.keepLines = keepLines;
|
|
277
|
+
this.maxBytes = maxBytes;
|
|
278
|
+
}
|
|
279
|
+
fd = null;
|
|
280
|
+
logPath = null;
|
|
281
|
+
lineCount = 0;
|
|
282
|
+
rotating = false;
|
|
283
|
+
init(projectRoot) {
|
|
284
|
+
this.close();
|
|
285
|
+
try {
|
|
286
|
+
const logsDir = join(projectRoot, ".logs");
|
|
287
|
+
fs2.mkdirSync(logsDir, { recursive: true });
|
|
288
|
+
this.logPath = join(logsDir, this.filename);
|
|
289
|
+
if (fs2.existsSync(this.logPath)) {
|
|
290
|
+
const content = fs2.readFileSync(this.logPath, "utf-8");
|
|
291
|
+
this.lineCount = content.split("\n").filter((l) => l.trim()).length;
|
|
292
|
+
} else {
|
|
293
|
+
this.lineCount = 0;
|
|
294
|
+
}
|
|
295
|
+
this.fd = fs2.openSync(this.logPath, "a");
|
|
296
|
+
log.info(`${this.filename} log initialized`, {
|
|
297
|
+
path: this.logPath,
|
|
298
|
+
existingEntries: this.lineCount
|
|
299
|
+
});
|
|
300
|
+
} catch (err) {
|
|
301
|
+
log.warn(`Failed to initialize ${this.filename} log`, {
|
|
302
|
+
error: err instanceof Error ? err.message : String(err)
|
|
303
|
+
});
|
|
304
|
+
this.fd = null;
|
|
305
|
+
this.logPath = null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
append(record) {
|
|
309
|
+
if (this.fd === null) return;
|
|
310
|
+
try {
|
|
311
|
+
const line = JSON.stringify(record) + "\n";
|
|
312
|
+
fs2.writeSync(this.fd, line);
|
|
313
|
+
this.lineCount++;
|
|
314
|
+
this.maybeRotate();
|
|
315
|
+
} catch (err) {
|
|
316
|
+
log.debug(`Failed to write ${this.filename} log entry`, {
|
|
317
|
+
error: err instanceof Error ? err.message : String(err)
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
close() {
|
|
322
|
+
if (this.fd !== null) {
|
|
323
|
+
try {
|
|
324
|
+
fs2.closeSync(this.fd);
|
|
325
|
+
} catch {
|
|
326
|
+
}
|
|
327
|
+
this.fd = null;
|
|
328
|
+
}
|
|
329
|
+
this.logPath = null;
|
|
330
|
+
this.lineCount = 0;
|
|
331
|
+
this.rotating = false;
|
|
332
|
+
}
|
|
333
|
+
maybeRotate() {
|
|
334
|
+
if (this.fd === null || this.logPath === null || this.rotating) return;
|
|
335
|
+
try {
|
|
336
|
+
let needsRotation = this.lineCount > this.maxLines;
|
|
337
|
+
if (!needsRotation) {
|
|
338
|
+
const stat = fs2.fstatSync(this.fd);
|
|
339
|
+
needsRotation = stat.size > this.maxBytes;
|
|
340
|
+
}
|
|
341
|
+
if (!needsRotation) return;
|
|
342
|
+
this.rotating = true;
|
|
343
|
+
const content = fs2.readFileSync(this.logPath, "utf-8");
|
|
344
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
345
|
+
const kept = lines.slice(-this.keepLines);
|
|
346
|
+
fs2.closeSync(this.fd);
|
|
347
|
+
fs2.writeFileSync(this.logPath, kept.join("\n") + "\n", "utf-8");
|
|
348
|
+
this.fd = fs2.openSync(this.logPath, "a");
|
|
349
|
+
this.lineCount = kept.length;
|
|
350
|
+
log.debug(`${this.filename} log rotated`, { kept: this.lineCount });
|
|
351
|
+
} catch (err) {
|
|
352
|
+
log.debug(`${this.filename} log rotation failed`, {
|
|
353
|
+
error: err instanceof Error ? err.message : String(err)
|
|
354
|
+
});
|
|
355
|
+
} finally {
|
|
356
|
+
this.rotating = false;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// src/dev/request-log.ts
|
|
362
|
+
var ndjsonLog = new NdjsonLog("requests.ndjson");
|
|
363
|
+
function initRequestLog(projectRoot) {
|
|
364
|
+
ndjsonLog.init(projectRoot);
|
|
365
|
+
}
|
|
366
|
+
function logMethodExecution(entry) {
|
|
367
|
+
ndjsonLog.append({
|
|
368
|
+
type: "method",
|
|
369
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
370
|
+
requestId: entry.requestId,
|
|
371
|
+
sessionId: entry.sessionId,
|
|
372
|
+
method: entry.methodExport,
|
|
373
|
+
path: entry.methodPath,
|
|
374
|
+
input: entry.input,
|
|
375
|
+
roleOverride: entry.roleOverride ?? null,
|
|
376
|
+
authorizationToken: entry.authorizationToken,
|
|
377
|
+
databases: entry.databases,
|
|
378
|
+
success: entry.result.success,
|
|
379
|
+
output: entry.result.output ?? null,
|
|
380
|
+
error: entry.result.error ?? null,
|
|
381
|
+
stdout: entry.result.stdout ?? [],
|
|
382
|
+
duration: entry.duration,
|
|
383
|
+
stats: entry.result.stats ?? null
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
function logScenarioExecution(entry) {
|
|
387
|
+
ndjsonLog.append({
|
|
388
|
+
type: "scenario",
|
|
389
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
390
|
+
sessionId: entry.sessionId,
|
|
391
|
+
scenario: {
|
|
392
|
+
id: entry.scenario.id,
|
|
393
|
+
name: entry.scenario.name ?? entry.scenario.export,
|
|
394
|
+
export: entry.scenario.export,
|
|
395
|
+
path: entry.scenario.path
|
|
396
|
+
},
|
|
397
|
+
databases: entry.databases,
|
|
398
|
+
success: entry.result?.success ?? false,
|
|
399
|
+
output: entry.result?.output ?? null,
|
|
400
|
+
error: entry.result?.error ?? (entry.infrastructureError ? { message: entry.infrastructureError } : null),
|
|
401
|
+
stdout: entry.result?.stdout ?? [],
|
|
402
|
+
duration: entry.duration,
|
|
403
|
+
stats: entry.result?.stats ?? null
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
function closeRequestLog() {
|
|
407
|
+
ndjsonLog.close();
|
|
408
|
+
}
|
|
409
|
+
|
|
269
410
|
// src/dev/events.ts
|
|
270
411
|
import { EventEmitter } from "events";
|
|
271
412
|
var DevEventEmitter = class extends EventEmitter {
|
|
@@ -352,7 +493,7 @@ var devRequestEvents = new DevEventEmitter();
|
|
|
352
493
|
// src/dev/transpiler.ts
|
|
353
494
|
import { unlink, mkdir, readdir } from "fs/promises";
|
|
354
495
|
import { existsSync } from "fs";
|
|
355
|
-
import { resolve, dirname, basename, join } from "path";
|
|
496
|
+
import { resolve, dirname, basename, join as join2 } from "path";
|
|
356
497
|
import { build } from "esbuild";
|
|
357
498
|
var Transpiler = class {
|
|
358
499
|
projectRoot;
|
|
@@ -378,18 +519,18 @@ var Transpiler = class {
|
|
|
378
519
|
const start = Date.now();
|
|
379
520
|
const absolutePath = resolve(this.projectRoot, methodPath);
|
|
380
521
|
const name = basename(absolutePath).replace(/\.[^.]+$/, "");
|
|
381
|
-
log.debug("
|
|
522
|
+
log.debug("Transpiling method", { methodPath });
|
|
382
523
|
const nodeModulesDir = findNearestNodeModules(dirname(absolutePath));
|
|
383
524
|
if (!nodeModulesDir) {
|
|
384
|
-
log.error("
|
|
525
|
+
log.error("Cannot find node_modules for method", { methodPath, searchStart: dirname(absolutePath) });
|
|
385
526
|
throw new Error(
|
|
386
527
|
`No node_modules found near ${methodPath}. Run npm install first.`
|
|
387
528
|
);
|
|
388
529
|
}
|
|
389
|
-
log.debug("
|
|
390
|
-
const outDir =
|
|
530
|
+
log.debug("Found node_modules", { path: nodeModulesDir });
|
|
531
|
+
const outDir = join2(nodeModulesDir, ".cache", "mindstudio-dev");
|
|
391
532
|
await mkdir(outDir, { recursive: true });
|
|
392
|
-
const outfile =
|
|
533
|
+
const outfile = join2(outDir, `${name}.__ms_dev__.mjs`);
|
|
393
534
|
await build({
|
|
394
535
|
entryPoints: [absolutePath],
|
|
395
536
|
bundle: true,
|
|
@@ -402,14 +543,14 @@ var Transpiler = class {
|
|
|
402
543
|
logLevel: "silent"
|
|
403
544
|
});
|
|
404
545
|
this.outputFiles.add(outfile);
|
|
405
|
-
log.info(`
|
|
546
|
+
log.info(`Method transpiled in ${Date.now() - start}ms`, { methodPath, outfile });
|
|
406
547
|
return outfile;
|
|
407
548
|
}
|
|
408
549
|
/**
|
|
409
550
|
* Clean up all transpiled output files.
|
|
410
551
|
*/
|
|
411
552
|
async cleanup() {
|
|
412
|
-
log.debug("
|
|
553
|
+
log.debug("Cleaning up transpiled files", { fileCount: this.outputFiles.size });
|
|
413
554
|
for (const file of this.outputFiles) {
|
|
414
555
|
await unlink(file).catch(() => {
|
|
415
556
|
});
|
|
@@ -421,11 +562,11 @@ async function removeOrphanedDevFiles(dir) {
|
|
|
421
562
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
422
563
|
for (const entry of entries) {
|
|
423
564
|
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
424
|
-
const fullPath =
|
|
565
|
+
const fullPath = join2(dir, entry.name);
|
|
425
566
|
if (entry.isDirectory()) {
|
|
426
567
|
await removeOrphanedDevFiles(fullPath);
|
|
427
568
|
} else if (entry.name.endsWith(".__ms_dev__.mjs")) {
|
|
428
|
-
log.debug("
|
|
569
|
+
log.debug("Removing orphaned transpiled file", { path: fullPath });
|
|
429
570
|
await unlink(fullPath).catch(() => {
|
|
430
571
|
});
|
|
431
572
|
}
|
|
@@ -434,7 +575,7 @@ async function removeOrphanedDevFiles(dir) {
|
|
|
434
575
|
function findNearestNodeModules(startDir) {
|
|
435
576
|
let dir = startDir;
|
|
436
577
|
while (true) {
|
|
437
|
-
const candidate =
|
|
578
|
+
const candidate = join2(dir, "node_modules");
|
|
438
579
|
if (existsSync(candidate)) {
|
|
439
580
|
return candidate;
|
|
440
581
|
}
|
|
@@ -446,19 +587,18 @@ function findNearestNodeModules(startDir) {
|
|
|
446
587
|
}
|
|
447
588
|
|
|
448
589
|
// src/dev/executor.ts
|
|
449
|
-
import {
|
|
590
|
+
import { fork } from "child_process";
|
|
450
591
|
import { writeFile, unlink as unlink2 } from "fs/promises";
|
|
451
|
-
import { join as
|
|
592
|
+
import { join as join3 } from "path";
|
|
452
593
|
import { tmpdir } from "os";
|
|
453
594
|
import { randomBytes } from "crypto";
|
|
454
595
|
var EXECUTION_TIMEOUT_MS = 3e4;
|
|
455
|
-
|
|
596
|
+
var worker = null;
|
|
597
|
+
var workerScriptPath = null;
|
|
598
|
+
var workerProjectRoot = null;
|
|
599
|
+
var pending = /* @__PURE__ */ new Map();
|
|
600
|
+
function buildWorkerScript() {
|
|
456
601
|
return `
|
|
457
|
-
global.ai = {
|
|
458
|
-
auth: ${JSON.stringify(opts.auth)},
|
|
459
|
-
databases: ${JSON.stringify(opts.databases)},
|
|
460
|
-
};
|
|
461
|
-
|
|
462
602
|
function serializeError(err) {
|
|
463
603
|
if (!err) return { message: 'Unknown error' };
|
|
464
604
|
|
|
@@ -467,7 +607,6 @@ function serializeError(err) {
|
|
|
467
607
|
stack: err.stack,
|
|
468
608
|
};
|
|
469
609
|
|
|
470
|
-
// Capture common extra properties from SDK/HTTP errors
|
|
471
610
|
if (err.code !== undefined) serialized.code = err.code;
|
|
472
611
|
if (err.statusCode !== undefined) serialized.statusCode = err.statusCode;
|
|
473
612
|
if (err.status !== undefined) serialized.status = err.status;
|
|
@@ -481,7 +620,6 @@ function serializeError(err) {
|
|
|
481
620
|
serialized.cause = serializeError(err.cause);
|
|
482
621
|
}
|
|
483
622
|
|
|
484
|
-
// Capture any other enumerable properties
|
|
485
623
|
for (const key of Object.keys(err)) {
|
|
486
624
|
if (!(key in serialized)) {
|
|
487
625
|
try {
|
|
@@ -496,91 +634,162 @@ function serializeError(err) {
|
|
|
496
634
|
return serialized;
|
|
497
635
|
}
|
|
498
636
|
|
|
499
|
-
//
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
console.error = (...args) => _stdout.push(args.map(String).join(' '));
|
|
637
|
+
// Save original console methods so we can restore after each request
|
|
638
|
+
const _origLog = console.log;
|
|
639
|
+
const _origWarn = console.warn;
|
|
640
|
+
const _origError = console.error;
|
|
504
641
|
|
|
505
|
-
|
|
642
|
+
process.on('message', async (msg) => {
|
|
643
|
+
const { id, transpiledPath, methodExport, input, auth, databases, authorizationToken, apiBaseUrl, streamId } = msg;
|
|
506
644
|
|
|
507
|
-
|
|
645
|
+
// Update per-request env vars
|
|
646
|
+
process.env.CALLBACK_TOKEN = authorizationToken;
|
|
647
|
+
process.env.REMOTE_HOSTNAME = apiBaseUrl;
|
|
648
|
+
if (streamId) process.env.STREAM_ID = streamId;
|
|
649
|
+
else delete process.env.STREAM_ID;
|
|
508
650
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
651
|
+
// Update global context
|
|
652
|
+
global.ai = { auth, databases };
|
|
653
|
+
|
|
654
|
+
// Capture console output for this request
|
|
655
|
+
const stdout = [];
|
|
656
|
+
console.log = (...args) => stdout.push(args.map(String).join(' '));
|
|
657
|
+
console.warn = (...args) => stdout.push(args.map(String).join(' '));
|
|
658
|
+
console.error = (...args) => stdout.push(args.map(String).join(' '));
|
|
659
|
+
|
|
660
|
+
const startTime = Date.now();
|
|
661
|
+
|
|
662
|
+
try {
|
|
663
|
+
// Cache-bust so code changes are picked up
|
|
664
|
+
const mod = await import(transpiledPath + '?t=' + Date.now());
|
|
665
|
+
const fn = mod[methodExport];
|
|
666
|
+
if (typeof fn !== 'function') {
|
|
667
|
+
throw new Error(methodExport + ' is not a function (got ' + typeof fn + ')');
|
|
668
|
+
}
|
|
669
|
+
const returnValue = await fn(input);
|
|
670
|
+
const stats = { memoryUsedBytes: process.memoryUsage().heapUsed, executionTimeMs: Date.now() - startTime };
|
|
671
|
+
process.send({ id, success: true, output: returnValue, stdout, stats });
|
|
672
|
+
} catch (err) {
|
|
673
|
+
const stats = { memoryUsedBytes: process.memoryUsage().heapUsed, executionTimeMs: Date.now() - startTime };
|
|
674
|
+
process.send({ id, success: false, error: serializeError(err), stdout, stats });
|
|
675
|
+
} finally {
|
|
676
|
+
// Restore console
|
|
677
|
+
console.log = _origLog;
|
|
678
|
+
console.warn = _origWarn;
|
|
679
|
+
console.error = _origError;
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
// Signal ready
|
|
684
|
+
process.send({ type: 'ready' });
|
|
522
685
|
`;
|
|
523
686
|
}
|
|
524
|
-
async function
|
|
525
|
-
|
|
687
|
+
async function ensureWorker(projectRoot) {
|
|
688
|
+
if (worker?.connected && workerProjectRoot === projectRoot) {
|
|
689
|
+
return worker;
|
|
690
|
+
}
|
|
691
|
+
if (worker) {
|
|
692
|
+
worker.removeAllListeners();
|
|
693
|
+
worker.kill();
|
|
694
|
+
worker = null;
|
|
695
|
+
}
|
|
696
|
+
if (workerScriptPath) {
|
|
697
|
+
await unlink2(workerScriptPath).catch(() => {
|
|
698
|
+
});
|
|
699
|
+
workerScriptPath = null;
|
|
700
|
+
}
|
|
701
|
+
const scriptPath = join3(
|
|
526
702
|
tmpdir(),
|
|
527
|
-
`ms-dev-${randomBytes(
|
|
703
|
+
`ms-dev-worker-${randomBytes(4).toString("hex")}.mjs`
|
|
528
704
|
);
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
705
|
+
await writeFile(scriptPath, buildWorkerScript(), "utf-8");
|
|
706
|
+
workerScriptPath = scriptPath;
|
|
707
|
+
workerProjectRoot = projectRoot;
|
|
708
|
+
log.debug("Spawning method execution process", { cwd: projectRoot, scriptPath });
|
|
709
|
+
const child = fork(scriptPath, [], {
|
|
710
|
+
cwd: projectRoot,
|
|
711
|
+
stdio: ["ignore", "pipe", "pipe", "ipc"],
|
|
712
|
+
env: { ...process.env }
|
|
713
|
+
});
|
|
714
|
+
await new Promise((resolve2, reject) => {
|
|
715
|
+
const onMessage = (msg) => {
|
|
716
|
+
if (msg?.type === "ready") {
|
|
717
|
+
child.off("message", onMessage);
|
|
718
|
+
resolve2();
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
child.on("message", onMessage);
|
|
722
|
+
child.on("error", reject);
|
|
723
|
+
child.on("exit", (code) => reject(new Error(`Worker exited during startup with code ${code}`)));
|
|
724
|
+
});
|
|
725
|
+
child.on("message", (msg) => {
|
|
726
|
+
if (!msg?.id) return;
|
|
727
|
+
const req = pending.get(msg.id);
|
|
728
|
+
if (!req) return;
|
|
729
|
+
pending.delete(msg.id);
|
|
730
|
+
clearTimeout(req.timer);
|
|
731
|
+
req.resolve(msg);
|
|
732
|
+
});
|
|
733
|
+
child.on("exit", (code) => {
|
|
734
|
+
log.warn("Method execution process exited unexpectedly", { code });
|
|
735
|
+
for (const [id, req] of pending) {
|
|
736
|
+
clearTimeout(req.timer);
|
|
737
|
+
req.resolve({ success: false, error: { message: `Worker process exited with code ${code}` } });
|
|
738
|
+
}
|
|
739
|
+
pending.clear();
|
|
740
|
+
worker = null;
|
|
741
|
+
});
|
|
742
|
+
child.stderr?.on("data", (chunk) => {
|
|
743
|
+
const text = chunk.toString().trim();
|
|
744
|
+
if (text) log.warn("Method process stderr", { text: text.slice(0, 500) });
|
|
745
|
+
});
|
|
746
|
+
worker = child;
|
|
747
|
+
log.info("Method execution process ready", { pid: child.pid });
|
|
748
|
+
return child;
|
|
749
|
+
}
|
|
750
|
+
async function executeMethod(opts) {
|
|
751
|
+
const w = await ensureWorker(opts.projectRoot);
|
|
752
|
+
const id = randomBytes(8).toString("hex");
|
|
753
|
+
log.debug("Sending method to execution process", { id, methodExport: opts.methodExport });
|
|
754
|
+
return new Promise((resolve2) => {
|
|
755
|
+
const timer = setTimeout(() => {
|
|
756
|
+
pending.delete(id);
|
|
757
|
+
log.warn("Method execution timed out", { id, methodExport: opts.methodExport });
|
|
758
|
+
resolve2({
|
|
759
|
+
success: false,
|
|
760
|
+
error: { message: "Method execution timed out after 30s" }
|
|
578
761
|
});
|
|
762
|
+
}, EXECUTION_TIMEOUT_MS);
|
|
763
|
+
pending.set(id, { resolve: resolve2, timer });
|
|
764
|
+
w.send({
|
|
765
|
+
id,
|
|
766
|
+
transpiledPath: opts.transpiledPath,
|
|
767
|
+
methodExport: opts.methodExport,
|
|
768
|
+
input: opts.input,
|
|
769
|
+
auth: opts.auth,
|
|
770
|
+
databases: opts.databases,
|
|
771
|
+
authorizationToken: opts.authorizationToken,
|
|
772
|
+
apiBaseUrl: opts.apiBaseUrl,
|
|
773
|
+
streamId: opts.streamId
|
|
579
774
|
});
|
|
580
|
-
}
|
|
581
|
-
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
async function cleanupWorker() {
|
|
778
|
+
if (worker) {
|
|
779
|
+
worker.removeAllListeners();
|
|
780
|
+
worker.kill();
|
|
781
|
+
worker = null;
|
|
782
|
+
}
|
|
783
|
+
if (workerScriptPath) {
|
|
784
|
+
await unlink2(workerScriptPath).catch(() => {
|
|
582
785
|
});
|
|
786
|
+
workerScriptPath = null;
|
|
787
|
+
}
|
|
788
|
+
workerProjectRoot = null;
|
|
789
|
+
for (const [, req] of pending) {
|
|
790
|
+
clearTimeout(req.timer);
|
|
583
791
|
}
|
|
792
|
+
pending.clear();
|
|
584
793
|
}
|
|
585
794
|
|
|
586
795
|
// src/api.ts
|
|
@@ -743,6 +952,33 @@ async function disconnectHeartbeat() {
|
|
|
743
952
|
}
|
|
744
953
|
}
|
|
745
954
|
|
|
955
|
+
// src/dev/runner.ts
|
|
956
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
957
|
+
|
|
958
|
+
// src/dev/format-error.ts
|
|
959
|
+
function formatErrorForDisplay(error) {
|
|
960
|
+
const parts = [];
|
|
961
|
+
if (error.message) {
|
|
962
|
+
parts.push(String(error.message));
|
|
963
|
+
}
|
|
964
|
+
const code = error.code ?? error.statusCode ?? error.status;
|
|
965
|
+
if (code !== void 0) {
|
|
966
|
+
parts.push(`(code: ${code})`);
|
|
967
|
+
}
|
|
968
|
+
if (error.body) {
|
|
969
|
+
parts.push(`Response: ${String(error.body).slice(0, 200)}`);
|
|
970
|
+
} else if (error.response) {
|
|
971
|
+
parts.push(`Response: ${String(error.response).slice(0, 200)}`);
|
|
972
|
+
}
|
|
973
|
+
if (error.cause && typeof error.cause === "object") {
|
|
974
|
+
const cause = error.cause;
|
|
975
|
+
if (cause.message) {
|
|
976
|
+
parts.push(`Caused by: ${cause.message}`);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return parts.join("\n");
|
|
980
|
+
}
|
|
981
|
+
|
|
746
982
|
// src/dev/runner.ts
|
|
747
983
|
var DevRunner = class {
|
|
748
984
|
constructor(appId, projectRoot, startOpts = {}) {
|
|
@@ -771,27 +1007,28 @@ var DevRunner = class {
|
|
|
771
1007
|
if (this.isRunning) {
|
|
772
1008
|
throw new Error("DevRunner is already running");
|
|
773
1009
|
}
|
|
774
|
-
log.info("
|
|
1010
|
+
log.info("Dev session starting", { appId: this.appId, branch: this.startOpts.branch });
|
|
775
1011
|
const session = await startDevSession(this.appId, this.startOpts);
|
|
776
1012
|
this.session = session;
|
|
777
1013
|
this.transpiler = new Transpiler(this.projectRoot);
|
|
778
1014
|
this.isRunning = true;
|
|
779
1015
|
this.backoffMs = 1e3;
|
|
780
|
-
log.info("
|
|
1016
|
+
log.info("Dev session started", { sessionId: session.sessionId, branch: session.branch });
|
|
781
1017
|
this.pollLoop();
|
|
782
1018
|
return session;
|
|
783
1019
|
}
|
|
784
1020
|
async stop() {
|
|
785
|
-
log.info("
|
|
1021
|
+
log.info("Dev session stopping");
|
|
786
1022
|
this.isRunning = false;
|
|
787
1023
|
if (this.session) {
|
|
788
1024
|
try {
|
|
789
1025
|
await stopDevSession(this.appId, this.session.sessionId);
|
|
790
1026
|
} catch (err) {
|
|
791
|
-
log.warn("
|
|
1027
|
+
log.warn("Failed to stop dev session cleanly", { error: err instanceof Error ? err.message : String(err) });
|
|
792
1028
|
}
|
|
793
1029
|
this.session = null;
|
|
794
1030
|
}
|
|
1031
|
+
await cleanupWorker();
|
|
795
1032
|
if (this.transpiler) {
|
|
796
1033
|
await this.transpiler.cleanup();
|
|
797
1034
|
this.transpiler = null;
|
|
@@ -803,7 +1040,7 @@ var DevRunner = class {
|
|
|
803
1040
|
// Set role override for subsequent method executions.
|
|
804
1041
|
async setImpersonation(roles) {
|
|
805
1042
|
if (!this.session) return;
|
|
806
|
-
log.info("
|
|
1043
|
+
log.info("Setting role override", { roles });
|
|
807
1044
|
const result = await impersonate(this.appId, this.session.sessionId, roles);
|
|
808
1045
|
await this.refreshClientContext();
|
|
809
1046
|
devRequestEvents.emitImpersonate({ roles: result.roles });
|
|
@@ -811,7 +1048,7 @@ var DevRunner = class {
|
|
|
811
1048
|
// Clear role override — revert to session's default roles.
|
|
812
1049
|
async clearImpersonation() {
|
|
813
1050
|
if (!this.session) return;
|
|
814
|
-
log.info("
|
|
1051
|
+
log.info("Clearing role override");
|
|
815
1052
|
const result = await impersonate(this.appId, this.session.sessionId, null);
|
|
816
1053
|
await this.refreshClientContext();
|
|
817
1054
|
devRequestEvents.emitImpersonate({ roles: result.roles });
|
|
@@ -825,7 +1062,94 @@ var DevRunner = class {
|
|
|
825
1062
|
this.session.clientContext = context;
|
|
826
1063
|
this.proxy.updateClientContext(context);
|
|
827
1064
|
} catch (err) {
|
|
828
|
-
log.warn("
|
|
1065
|
+
log.warn("Failed to refresh session context after role change", { error: err instanceof Error ? err.message : String(err) });
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
// Run a method directly (not via poll loop). Used by headless stdin commands
|
|
1069
|
+
// and programmatic callers to test methods without a browser.
|
|
1070
|
+
async runMethod(opts) {
|
|
1071
|
+
if (!this.session || !this.transpiler) {
|
|
1072
|
+
return { success: false, error: { message: "Session not started" }, duration: 0 };
|
|
1073
|
+
}
|
|
1074
|
+
const requestId = randomBytes2(8).toString("hex");
|
|
1075
|
+
const startTime = Date.now();
|
|
1076
|
+
devRequestEvents.emitStart({
|
|
1077
|
+
id: requestId,
|
|
1078
|
+
type: "execute",
|
|
1079
|
+
method: opts.methodExport,
|
|
1080
|
+
timestamp: startTime
|
|
1081
|
+
});
|
|
1082
|
+
log.info("Method received (direct)", { requestId, method: opts.methodExport });
|
|
1083
|
+
try {
|
|
1084
|
+
const authorizationToken = await fetchCallbackToken(this.appId, this.session.sessionId);
|
|
1085
|
+
const transpiledPath = await this.transpiler.transpile(opts.methodPath);
|
|
1086
|
+
const result = await executeMethod({
|
|
1087
|
+
transpiledPath,
|
|
1088
|
+
methodExport: opts.methodExport,
|
|
1089
|
+
input: opts.input,
|
|
1090
|
+
auth: this.session.auth,
|
|
1091
|
+
databases: this.session.databases,
|
|
1092
|
+
authorizationToken,
|
|
1093
|
+
apiBaseUrl: getApiBaseUrl(),
|
|
1094
|
+
projectRoot: this.projectRoot
|
|
1095
|
+
});
|
|
1096
|
+
const duration = Date.now() - startTime;
|
|
1097
|
+
if (result.success) {
|
|
1098
|
+
log.info("Method complete", { requestId, method: opts.methodExport, duration });
|
|
1099
|
+
} else {
|
|
1100
|
+
log.warn("Method failed", {
|
|
1101
|
+
requestId,
|
|
1102
|
+
method: opts.methodExport,
|
|
1103
|
+
duration,
|
|
1104
|
+
error: result.error ? formatErrorForDisplay(result.error) : void 0
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
logMethodExecution({
|
|
1108
|
+
requestId,
|
|
1109
|
+
sessionId: this.session.sessionId,
|
|
1110
|
+
methodExport: opts.methodExport,
|
|
1111
|
+
methodPath: opts.methodPath,
|
|
1112
|
+
input: opts.input,
|
|
1113
|
+
authorizationToken,
|
|
1114
|
+
databases: this.session.databases,
|
|
1115
|
+
result,
|
|
1116
|
+
duration
|
|
1117
|
+
});
|
|
1118
|
+
devRequestEvents.emitComplete({
|
|
1119
|
+
id: requestId,
|
|
1120
|
+
success: result.success,
|
|
1121
|
+
duration,
|
|
1122
|
+
error: result.error ? formatErrorForDisplay(result.error) : void 0
|
|
1123
|
+
});
|
|
1124
|
+
return {
|
|
1125
|
+
success: result.success,
|
|
1126
|
+
output: result.output,
|
|
1127
|
+
error: result.error ?? null,
|
|
1128
|
+
stdout: result.stdout,
|
|
1129
|
+
duration
|
|
1130
|
+
};
|
|
1131
|
+
} catch (err) {
|
|
1132
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
1133
|
+
const duration = Date.now() - startTime;
|
|
1134
|
+
log.error("Method error", { requestId, method: opts.methodExport, duration, error: message });
|
|
1135
|
+
logMethodExecution({
|
|
1136
|
+
requestId,
|
|
1137
|
+
sessionId: this.session.sessionId,
|
|
1138
|
+
methodExport: opts.methodExport,
|
|
1139
|
+
methodPath: opts.methodPath,
|
|
1140
|
+
input: opts.input,
|
|
1141
|
+
authorizationToken: "",
|
|
1142
|
+
databases: this.session.databases,
|
|
1143
|
+
result: { success: false, error: { message } },
|
|
1144
|
+
duration
|
|
1145
|
+
});
|
|
1146
|
+
devRequestEvents.emitComplete({
|
|
1147
|
+
id: requestId,
|
|
1148
|
+
success: false,
|
|
1149
|
+
duration,
|
|
1150
|
+
error: message
|
|
1151
|
+
});
|
|
1152
|
+
return { success: false, error: { message }, duration };
|
|
829
1153
|
}
|
|
830
1154
|
}
|
|
831
1155
|
// Run a scenario: truncate tables → execute seed → impersonate roles.
|
|
@@ -841,16 +1165,15 @@ var DevRunner = class {
|
|
|
841
1165
|
name: scenarioName,
|
|
842
1166
|
timestamp: startTime
|
|
843
1167
|
});
|
|
844
|
-
log.info("
|
|
1168
|
+
log.info("Scenario starting", { id: scenario.id, name: scenarioName });
|
|
845
1169
|
try {
|
|
846
|
-
log.
|
|
1170
|
+
log.info("Resetting database for scenario");
|
|
847
1171
|
const databases = await resetDevDatabase(this.appId, this.session.sessionId, "truncate");
|
|
848
1172
|
this.session.databases = databases;
|
|
849
|
-
log.
|
|
1173
|
+
log.info("Transpiling scenario", { path: scenario.path });
|
|
850
1174
|
const transpiledPath = await this.transpiler.transpile(scenario.path);
|
|
851
|
-
log.debug("runner Fetching callback token for scenario");
|
|
852
1175
|
const authorizationToken = await fetchCallbackToken(this.appId, this.session.sessionId);
|
|
853
|
-
log.
|
|
1176
|
+
log.info("Running scenario seed function", { export: scenario.export });
|
|
854
1177
|
const result = await executeMethod({
|
|
855
1178
|
transpiledPath,
|
|
856
1179
|
methodExport: scenario.export,
|
|
@@ -863,7 +1186,14 @@ var DevRunner = class {
|
|
|
863
1186
|
});
|
|
864
1187
|
if (!result.success) {
|
|
865
1188
|
const error = result.error?.message ?? "Scenario seed failed";
|
|
866
|
-
log.error("
|
|
1189
|
+
log.error("Scenario seed function failed", { id: scenario.id, error });
|
|
1190
|
+
logScenarioExecution({
|
|
1191
|
+
sessionId: this.session.sessionId,
|
|
1192
|
+
scenario,
|
|
1193
|
+
databases: this.session.databases,
|
|
1194
|
+
result,
|
|
1195
|
+
duration: Date.now() - startTime
|
|
1196
|
+
});
|
|
867
1197
|
devRequestEvents.emitScenarioComplete({
|
|
868
1198
|
id: scenario.id,
|
|
869
1199
|
success: false,
|
|
@@ -874,12 +1204,19 @@ var DevRunner = class {
|
|
|
874
1204
|
return { success: false, databases, error };
|
|
875
1205
|
}
|
|
876
1206
|
if (scenario.roles.length > 0) {
|
|
877
|
-
log.
|
|
1207
|
+
log.info("Setting role override for scenario", { roles: scenario.roles });
|
|
878
1208
|
await impersonate(this.appId, this.session.sessionId, scenario.roles);
|
|
879
1209
|
await this.refreshClientContext();
|
|
880
1210
|
}
|
|
881
1211
|
const duration = Date.now() - startTime;
|
|
882
|
-
log.info("
|
|
1212
|
+
log.info("Scenario complete", { id: scenario.id, duration, roles: scenario.roles });
|
|
1213
|
+
logScenarioExecution({
|
|
1214
|
+
sessionId: this.session.sessionId,
|
|
1215
|
+
scenario,
|
|
1216
|
+
databases: this.session.databases,
|
|
1217
|
+
result,
|
|
1218
|
+
duration
|
|
1219
|
+
});
|
|
883
1220
|
devRequestEvents.emitScenarioComplete({
|
|
884
1221
|
id: scenario.id,
|
|
885
1222
|
success: true,
|
|
@@ -889,7 +1226,15 @@ var DevRunner = class {
|
|
|
889
1226
|
return { success: true, databases };
|
|
890
1227
|
} catch (err) {
|
|
891
1228
|
const error = err instanceof Error ? err.message : "Unknown error";
|
|
892
|
-
log.error("
|
|
1229
|
+
log.error("Scenario failed", { id: scenario.id, error });
|
|
1230
|
+
logScenarioExecution({
|
|
1231
|
+
sessionId: this.session.sessionId,
|
|
1232
|
+
scenario,
|
|
1233
|
+
databases: this.session.databases,
|
|
1234
|
+
result: null,
|
|
1235
|
+
infrastructureError: error,
|
|
1236
|
+
duration: Date.now() - startTime
|
|
1237
|
+
});
|
|
893
1238
|
devRequestEvents.emitScenarioComplete({
|
|
894
1239
|
id: scenario.id,
|
|
895
1240
|
success: false,
|
|
@@ -910,7 +1255,7 @@ var DevRunner = class {
|
|
|
910
1255
|
);
|
|
911
1256
|
if (this.hadConnectionWarning) {
|
|
912
1257
|
this.hadConnectionWarning = false;
|
|
913
|
-
log.info("
|
|
1258
|
+
log.info("Connection to platform restored");
|
|
914
1259
|
devRequestEvents.emitConnectionRestored();
|
|
915
1260
|
}
|
|
916
1261
|
if (request) {
|
|
@@ -919,31 +1264,31 @@ var DevRunner = class {
|
|
|
919
1264
|
this.backoffMs = 1e3;
|
|
920
1265
|
} catch (error) {
|
|
921
1266
|
if (error instanceof DevPollError && error.statusCode === 404) {
|
|
922
|
-
log.error("
|
|
1267
|
+
log.error("Dev session expired", { statusCode: 404 });
|
|
923
1268
|
devRequestEvents.emitSessionExpired();
|
|
924
1269
|
this.isRunning = false;
|
|
925
1270
|
return;
|
|
926
1271
|
}
|
|
927
1272
|
if ((error instanceof DevPollError || error instanceof ApiError) && error.statusCode === 401) {
|
|
928
|
-
log.warn("
|
|
1273
|
+
log.warn("Session token expired, re-authenticating");
|
|
929
1274
|
const refreshed = await this.refreshAuth();
|
|
930
1275
|
if (refreshed) {
|
|
931
1276
|
this.backoffMs = 1e3;
|
|
932
1277
|
continue;
|
|
933
1278
|
}
|
|
934
|
-
log.error("
|
|
1279
|
+
log.error("Re-authentication failed");
|
|
935
1280
|
devRequestEvents.emitSessionExpired();
|
|
936
1281
|
this.isRunning = false;
|
|
937
1282
|
return;
|
|
938
1283
|
}
|
|
939
1284
|
if (!this.hadConnectionWarning) {
|
|
940
1285
|
this.hadConnectionWarning = true;
|
|
941
|
-
log.warn("
|
|
1286
|
+
log.warn("Lost connection to platform, retrying");
|
|
942
1287
|
devRequestEvents.emitConnectionWarning(
|
|
943
1288
|
"Lost connection to platform, retrying..."
|
|
944
1289
|
);
|
|
945
1290
|
}
|
|
946
|
-
log.debug("
|
|
1291
|
+
log.debug("Backing off", { ms: this.backoffMs });
|
|
947
1292
|
await this.sleep(this.backoffMs);
|
|
948
1293
|
this.backoffMs = Math.min(this.backoffMs * 2, 3e4);
|
|
949
1294
|
}
|
|
@@ -957,9 +1302,9 @@ var DevRunner = class {
|
|
|
957
1302
|
method: request.methodExport,
|
|
958
1303
|
timestamp: startTime
|
|
959
1304
|
});
|
|
960
|
-
log.info("
|
|
1305
|
+
log.info("Method received", { requestId: request.requestId, method: request.methodExport });
|
|
961
1306
|
try {
|
|
962
|
-
log.debug("
|
|
1307
|
+
log.debug("Transpiling method", { path: request.methodPath });
|
|
963
1308
|
const transpiledPath = await this.transpiler.transpile(request.methodPath);
|
|
964
1309
|
const auth = request.roleOverride ? {
|
|
965
1310
|
userId: this.session.auth.userId,
|
|
@@ -994,7 +1339,28 @@ var DevRunner = class {
|
|
|
994
1339
|
devResult
|
|
995
1340
|
);
|
|
996
1341
|
const duration = Date.now() - startTime;
|
|
997
|
-
|
|
1342
|
+
if (result.success) {
|
|
1343
|
+
log.info("Method complete", { requestId: request.requestId, method: request.methodExport, duration });
|
|
1344
|
+
} else {
|
|
1345
|
+
log.warn("Method failed", {
|
|
1346
|
+
requestId: request.requestId,
|
|
1347
|
+
method: request.methodExport,
|
|
1348
|
+
duration,
|
|
1349
|
+
error: result.error ? formatErrorForDisplay(result.error) : void 0
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
logMethodExecution({
|
|
1353
|
+
requestId: request.requestId,
|
|
1354
|
+
sessionId: this.session.sessionId,
|
|
1355
|
+
methodExport: request.methodExport,
|
|
1356
|
+
methodPath: request.methodPath,
|
|
1357
|
+
input: request.input,
|
|
1358
|
+
roleOverride: request.roleOverride,
|
|
1359
|
+
authorizationToken: request.authorizationToken,
|
|
1360
|
+
databases: this.session.databases,
|
|
1361
|
+
result,
|
|
1362
|
+
duration
|
|
1363
|
+
});
|
|
998
1364
|
devRequestEvents.emitComplete({
|
|
999
1365
|
id: request.requestId,
|
|
1000
1366
|
success: result.success,
|
|
@@ -1004,7 +1370,7 @@ var DevRunner = class {
|
|
|
1004
1370
|
} catch (error) {
|
|
1005
1371
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1006
1372
|
const duration = Date.now() - startTime;
|
|
1007
|
-
log.error("
|
|
1373
|
+
log.error("Method error", { requestId: request.requestId, method: request.methodExport, duration, error: message });
|
|
1008
1374
|
try {
|
|
1009
1375
|
await submitDevResult(
|
|
1010
1376
|
this.appId,
|
|
@@ -1017,8 +1383,20 @@ var DevRunner = class {
|
|
|
1017
1383
|
}
|
|
1018
1384
|
);
|
|
1019
1385
|
} catch (submitErr) {
|
|
1020
|
-
log.error("
|
|
1386
|
+
log.error("Failed to report method error to platform", { error: submitErr instanceof Error ? submitErr.message : String(submitErr) });
|
|
1021
1387
|
}
|
|
1388
|
+
logMethodExecution({
|
|
1389
|
+
requestId: request.requestId,
|
|
1390
|
+
sessionId: this.session.sessionId,
|
|
1391
|
+
methodExport: request.methodExport,
|
|
1392
|
+
methodPath: request.methodPath,
|
|
1393
|
+
input: request.input,
|
|
1394
|
+
roleOverride: request.roleOverride,
|
|
1395
|
+
authorizationToken: request.authorizationToken,
|
|
1396
|
+
databases: this.session.databases,
|
|
1397
|
+
result: { success: false, error: { message } },
|
|
1398
|
+
duration: Date.now() - startTime
|
|
1399
|
+
});
|
|
1022
1400
|
devRequestEvents.emitComplete({
|
|
1023
1401
|
id: request.requestId,
|
|
1024
1402
|
success: false,
|
|
@@ -1036,14 +1414,14 @@ var DevRunner = class {
|
|
|
1036
1414
|
const POLL_INTERVAL = 2e3;
|
|
1037
1415
|
const MAX_ATTEMPTS = 30;
|
|
1038
1416
|
try {
|
|
1039
|
-
log.info("
|
|
1417
|
+
log.info("Session token expired, requesting re-authentication");
|
|
1040
1418
|
const { url, token } = await requestDeviceAuth();
|
|
1041
1419
|
devRequestEvents.emitAuthRefreshStart(url);
|
|
1042
1420
|
try {
|
|
1043
1421
|
const open = (await import("open")).default;
|
|
1044
1422
|
await open(url);
|
|
1045
1423
|
} catch {
|
|
1046
|
-
log.warn("
|
|
1424
|
+
log.warn("Could not open browser \u2014 visit URL to re-authenticate");
|
|
1047
1425
|
}
|
|
1048
1426
|
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
|
1049
1427
|
await this.sleep(POLL_INTERVAL);
|
|
@@ -1054,7 +1432,7 @@ var DevRunner = class {
|
|
|
1054
1432
|
if (result.userId) {
|
|
1055
1433
|
setUserId(result.userId);
|
|
1056
1434
|
}
|
|
1057
|
-
log.info("
|
|
1435
|
+
log.info("Re-authentication successful");
|
|
1058
1436
|
devRequestEvents.emitAuthRefreshSuccess();
|
|
1059
1437
|
return true;
|
|
1060
1438
|
}
|
|
@@ -1062,11 +1440,11 @@ var DevRunner = class {
|
|
|
1062
1440
|
break;
|
|
1063
1441
|
}
|
|
1064
1442
|
}
|
|
1065
|
-
log.error("
|
|
1443
|
+
log.error("Re-authentication timed out or was denied");
|
|
1066
1444
|
devRequestEvents.emitAuthRefreshFailed();
|
|
1067
1445
|
return false;
|
|
1068
1446
|
} catch (err) {
|
|
1069
|
-
log.error("
|
|
1447
|
+
log.error("Re-authentication failed", { error: err instanceof Error ? err.message : String(err) });
|
|
1070
1448
|
devRequestEvents.emitAuthRefreshFailed();
|
|
1071
1449
|
return false;
|
|
1072
1450
|
}
|
|
@@ -1075,42 +1453,72 @@ var DevRunner = class {
|
|
|
1075
1453
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1076
1454
|
}
|
|
1077
1455
|
};
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
parts.push(`Response: ${String(error.response).slice(0, 200)}`);
|
|
1091
|
-
}
|
|
1092
|
-
if (error.cause && typeof error.cause === "object") {
|
|
1093
|
-
const cause = error.cause;
|
|
1094
|
-
if (cause.message) {
|
|
1095
|
-
parts.push(`Caused by: ${cause.message}`);
|
|
1096
|
-
}
|
|
1456
|
+
|
|
1457
|
+
// src/dev/browser-log.ts
|
|
1458
|
+
var ndjsonLog2 = new NdjsonLog("browser.ndjson");
|
|
1459
|
+
function initBrowserLog(projectRoot) {
|
|
1460
|
+
ndjsonLog2.init(projectRoot);
|
|
1461
|
+
}
|
|
1462
|
+
function appendBrowserLogEntries(entries) {
|
|
1463
|
+
for (const entry of entries) {
|
|
1464
|
+
ndjsonLog2.append({
|
|
1465
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1466
|
+
...entry
|
|
1467
|
+
});
|
|
1097
1468
|
}
|
|
1098
|
-
|
|
1469
|
+
}
|
|
1470
|
+
function closeBrowserLog() {
|
|
1471
|
+
ndjsonLog2.close();
|
|
1099
1472
|
}
|
|
1100
1473
|
|
|
1101
1474
|
// src/dev/proxy.ts
|
|
1102
1475
|
import http from "http";
|
|
1476
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
1103
1477
|
var DevProxy = class {
|
|
1104
|
-
constructor(upstreamPort, clientContext, bindAddress = "127.0.0.1") {
|
|
1478
|
+
constructor(upstreamPort, clientContext, bindAddress = "127.0.0.1", browserAgentUrl) {
|
|
1105
1479
|
this.upstreamPort = upstreamPort;
|
|
1106
1480
|
this.clientContext = clientContext;
|
|
1107
1481
|
this.bindAddress = bindAddress;
|
|
1482
|
+
this.browserAgentUrl = browserAgentUrl;
|
|
1108
1483
|
}
|
|
1109
1484
|
server = null;
|
|
1110
1485
|
proxyPort = null;
|
|
1486
|
+
commandQueue = [];
|
|
1487
|
+
pendingResults = /* @__PURE__ */ new Map();
|
|
1488
|
+
lastBrowserPoll = 0;
|
|
1111
1489
|
updateClientContext(context) {
|
|
1112
1490
|
this.clientContext = context;
|
|
1113
|
-
log.info("proxy
|
|
1491
|
+
log.info("Dev proxy context updated after role change");
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Whether a browser agent is actively polling for commands.
|
|
1495
|
+
* Based on whether we've seen a poll within the last 500ms.
|
|
1496
|
+
*/
|
|
1497
|
+
isBrowserConnected() {
|
|
1498
|
+
return Date.now() - this.lastBrowserPoll < 500;
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Dispatch a browser command and wait for the result.
|
|
1502
|
+
* The command is queued for the browser agent to pick up via polling.
|
|
1503
|
+
* Returns a promise that resolves when the browser posts the result back.
|
|
1504
|
+
*/
|
|
1505
|
+
dispatchBrowserCommand(steps, timeoutMs = 3e4) {
|
|
1506
|
+
if (!this.isBrowserConnected()) {
|
|
1507
|
+
return Promise.reject(
|
|
1508
|
+
new Error("No browser connected, please refresh the MindStudio preview")
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
const id = randomBytes3(4).toString("hex");
|
|
1512
|
+
log.info("Browser command queued", { id, stepCount: steps.length, commands: steps.map((s) => s.command) });
|
|
1513
|
+
return new Promise((resolve2, reject) => {
|
|
1514
|
+
const timeout = setTimeout(() => {
|
|
1515
|
+
this.pendingResults.delete(id);
|
|
1516
|
+
log.warn("Browser command timed out", { id, pendingCount: this.pendingResults.size, queueLength: this.commandQueue.length });
|
|
1517
|
+
reject(new Error("Browser command timed out"));
|
|
1518
|
+
}, timeoutMs);
|
|
1519
|
+
this.pendingResults.set(id, { resolve: resolve2, timeout });
|
|
1520
|
+
this.commandQueue.push({ id, steps });
|
|
1521
|
+
});
|
|
1114
1522
|
}
|
|
1115
1523
|
async start(preferredPort) {
|
|
1116
1524
|
const server = http.createServer((req, res) => {
|
|
@@ -1125,10 +1533,10 @@ var DevProxy = class {
|
|
|
1125
1533
|
const assignedPort = await this.listenOnPort(server, port);
|
|
1126
1534
|
this.server = server;
|
|
1127
1535
|
this.proxyPort = assignedPort;
|
|
1128
|
-
log.info("proxy
|
|
1536
|
+
log.info("Dev proxy started", { port: assignedPort, bind: this.bindAddress });
|
|
1129
1537
|
return assignedPort;
|
|
1130
1538
|
} catch {
|
|
1131
|
-
log.warn("
|
|
1539
|
+
log.warn("Proxy port in use, trying next", { port });
|
|
1132
1540
|
}
|
|
1133
1541
|
}
|
|
1134
1542
|
throw new Error("Failed to start proxy server");
|
|
@@ -1153,77 +1561,196 @@ var DevProxy = class {
|
|
|
1153
1561
|
}
|
|
1154
1562
|
stop() {
|
|
1155
1563
|
if (this.server) {
|
|
1156
|
-
log.info("proxy
|
|
1564
|
+
log.info("Dev proxy stopping");
|
|
1157
1565
|
this.server.close();
|
|
1158
1566
|
this.server = null;
|
|
1159
1567
|
this.proxyPort = null;
|
|
1160
1568
|
}
|
|
1569
|
+
for (const [id, pending2] of this.pendingResults) {
|
|
1570
|
+
clearTimeout(pending2.timeout);
|
|
1571
|
+
pending2.resolve({ id, steps: [], error: "Proxy stopped" });
|
|
1572
|
+
}
|
|
1573
|
+
this.pendingResults.clear();
|
|
1574
|
+
this.commandQueue.length = 0;
|
|
1161
1575
|
}
|
|
1162
1576
|
getPort() {
|
|
1163
1577
|
return this.proxyPort;
|
|
1164
1578
|
}
|
|
1579
|
+
// ---------------------------------------------------------------------------
|
|
1580
|
+
// CORS helper
|
|
1581
|
+
// ---------------------------------------------------------------------------
|
|
1582
|
+
corsHeaders(req) {
|
|
1583
|
+
const origin = req.headers.origin;
|
|
1584
|
+
if (!origin) return {};
|
|
1585
|
+
return {
|
|
1586
|
+
"access-control-allow-origin": origin,
|
|
1587
|
+
"access-control-allow-private-network": "true"
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
// ---------------------------------------------------------------------------
|
|
1591
|
+
// Request routing
|
|
1592
|
+
// ---------------------------------------------------------------------------
|
|
1165
1593
|
handleRequest(clientReq, clientRes) {
|
|
1166
|
-
|
|
1167
|
-
|
|
1594
|
+
if (clientReq.url?.startsWith("/__mindstudio_dev__/")) {
|
|
1595
|
+
if (clientReq.url === "/__mindstudio_dev__/logs" && clientReq.method === "POST") {
|
|
1596
|
+
this.handleBrowserLogs(clientReq, clientRes);
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
if (clientReq.url === "/__mindstudio_dev__/commands" && clientReq.method === "GET") {
|
|
1600
|
+
this.handleGetCommand(clientReq, clientRes);
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
if (clientReq.url === "/__mindstudio_dev__/results" && clientReq.method === "POST") {
|
|
1604
|
+
this.handlePostResult(clientReq, clientRes);
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
if (clientReq.method === "OPTIONS" && clientReq.headers.origin) {
|
|
1168
1609
|
clientRes.writeHead(204, {
|
|
1169
|
-
|
|
1170
|
-
"
|
|
1171
|
-
"
|
|
1172
|
-
"Access-Control-Allow-Headers": "*"
|
|
1610
|
+
...this.corsHeaders(clientReq),
|
|
1611
|
+
"access-control-allow-methods": "GET, POST, OPTIONS",
|
|
1612
|
+
"access-control-allow-headers": "*"
|
|
1173
1613
|
});
|
|
1174
1614
|
clientRes.end();
|
|
1175
1615
|
return;
|
|
1176
1616
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
const upstreamReq = http.request(
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1617
|
+
this.forwardToUpstream(clientReq, clientRes);
|
|
1618
|
+
}
|
|
1619
|
+
// ---------------------------------------------------------------------------
|
|
1620
|
+
// Upstream forwarding
|
|
1621
|
+
// ---------------------------------------------------------------------------
|
|
1622
|
+
forwardToUpstream(clientReq, clientRes) {
|
|
1623
|
+
const cors = this.corsHeaders(clientReq);
|
|
1624
|
+
const upstreamReq = http.request(
|
|
1625
|
+
{
|
|
1626
|
+
hostname: "127.0.0.1",
|
|
1627
|
+
port: this.upstreamPort,
|
|
1628
|
+
path: clientReq.url,
|
|
1629
|
+
method: clientReq.method,
|
|
1630
|
+
headers: { ...clientReq.headers, host: `localhost:${this.upstreamPort}` }
|
|
1631
|
+
},
|
|
1632
|
+
(upstreamRes) => {
|
|
1633
|
+
const contentType = upstreamRes.headers["content-type"] ?? "";
|
|
1634
|
+
const isHtml = contentType.startsWith("text/html");
|
|
1635
|
+
if (isHtml) {
|
|
1636
|
+
const chunks = [];
|
|
1637
|
+
upstreamRes.on("data", (chunk) => chunks.push(chunk));
|
|
1638
|
+
upstreamRes.on("end", () => {
|
|
1639
|
+
let html = Buffer.concat(chunks).toString("utf-8");
|
|
1640
|
+
html = this.injectScripts(html);
|
|
1641
|
+
const headers = {
|
|
1642
|
+
...upstreamRes.headers,
|
|
1643
|
+
...cors,
|
|
1644
|
+
"content-length": String(Buffer.byteLength(html, "utf-8")),
|
|
1645
|
+
"cache-control": "no-store, no-cache, must-revalidate"
|
|
1646
|
+
};
|
|
1647
|
+
delete headers["content-encoding"];
|
|
1648
|
+
delete headers["etag"];
|
|
1649
|
+
log.debug("Dev proxy injected context into HTML", { path: clientReq.url, size: html.length });
|
|
1650
|
+
clientRes.writeHead(upstreamRes.statusCode ?? 200, headers);
|
|
1651
|
+
clientRes.end(html);
|
|
1652
|
+
});
|
|
1653
|
+
} else {
|
|
1654
|
+
const headers = {
|
|
1655
|
+
...upstreamRes.headers,
|
|
1656
|
+
...cors,
|
|
1657
|
+
"cache-control": "no-store, no-cache, must-revalidate"
|
|
1658
|
+
};
|
|
1197
1659
|
delete headers["etag"];
|
|
1198
|
-
if (origin) {
|
|
1199
|
-
headers["access-control-allow-origin"] = origin;
|
|
1200
|
-
headers["access-control-allow-private-network"] = "true";
|
|
1201
|
-
}
|
|
1202
|
-
log.debug("proxy HTML injected", { path: clientReq.url, size: html.length });
|
|
1203
1660
|
clientRes.writeHead(upstreamRes.statusCode ?? 200, headers);
|
|
1204
|
-
|
|
1205
|
-
});
|
|
1206
|
-
} else {
|
|
1207
|
-
const headers = { ...upstreamRes.headers };
|
|
1208
|
-
headers["cache-control"] = "no-store, no-cache, must-revalidate";
|
|
1209
|
-
delete headers["etag"];
|
|
1210
|
-
if (origin) {
|
|
1211
|
-
headers["access-control-allow-origin"] = origin;
|
|
1212
|
-
headers["access-control-allow-private-network"] = "true";
|
|
1661
|
+
upstreamRes.pipe(clientRes);
|
|
1213
1662
|
}
|
|
1214
|
-
clientRes.writeHead(upstreamRes.statusCode ?? 200, headers);
|
|
1215
|
-
upstreamRes.pipe(clientRes);
|
|
1216
1663
|
}
|
|
1217
|
-
|
|
1664
|
+
);
|
|
1218
1665
|
upstreamReq.on("error", (err) => {
|
|
1219
|
-
log.warn("proxy
|
|
1666
|
+
log.warn("Dev proxy cannot reach dev server", { path: clientReq.url, error: err.message });
|
|
1220
1667
|
clientRes.writeHead(502);
|
|
1221
1668
|
clientRes.end(`Proxy error: ${err.message}`);
|
|
1222
1669
|
});
|
|
1223
1670
|
clientReq.pipe(upstreamReq);
|
|
1224
1671
|
}
|
|
1672
|
+
// ---------------------------------------------------------------------------
|
|
1673
|
+
// Browser agent endpoints
|
|
1674
|
+
// ---------------------------------------------------------------------------
|
|
1675
|
+
handleBrowserLogs(clientReq, clientRes) {
|
|
1676
|
+
const chunks = [];
|
|
1677
|
+
clientReq.on("data", (chunk) => chunks.push(chunk));
|
|
1678
|
+
clientReq.on("end", () => {
|
|
1679
|
+
try {
|
|
1680
|
+
const body = Buffer.concat(chunks).toString("utf-8");
|
|
1681
|
+
const entries = JSON.parse(body);
|
|
1682
|
+
if (Array.isArray(entries)) {
|
|
1683
|
+
appendBrowserLogEntries(entries);
|
|
1684
|
+
}
|
|
1685
|
+
} catch {
|
|
1686
|
+
}
|
|
1687
|
+
clientRes.writeHead(204, this.corsHeaders(clientReq));
|
|
1688
|
+
clientRes.end();
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
handleGetCommand(clientReq, clientRes) {
|
|
1692
|
+
this.lastBrowserPoll = Date.now();
|
|
1693
|
+
const command = this.commandQueue.shift();
|
|
1694
|
+
if (command) {
|
|
1695
|
+
log.info("Browser command dispatched to agent", { id: command.id, commands: command.steps.map((s) => s.command) });
|
|
1696
|
+
clientRes.writeHead(200, {
|
|
1697
|
+
...this.corsHeaders(clientReq),
|
|
1698
|
+
"content-type": "application/json",
|
|
1699
|
+
"cache-control": "no-store"
|
|
1700
|
+
});
|
|
1701
|
+
clientRes.end(JSON.stringify(command));
|
|
1702
|
+
} else {
|
|
1703
|
+
clientRes.writeHead(204, {
|
|
1704
|
+
...this.corsHeaders(clientReq),
|
|
1705
|
+
"cache-control": "no-store"
|
|
1706
|
+
});
|
|
1707
|
+
clientRes.end();
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
handlePostResult(clientReq, clientRes) {
|
|
1711
|
+
const chunks = [];
|
|
1712
|
+
clientReq.on("data", (chunk) => chunks.push(chunk));
|
|
1713
|
+
clientReq.on("end", () => {
|
|
1714
|
+
try {
|
|
1715
|
+
const body = Buffer.concat(chunks).toString("utf-8");
|
|
1716
|
+
const result = JSON.parse(body);
|
|
1717
|
+
if (result?.id) {
|
|
1718
|
+
const pending2 = this.pendingResults.get(result.id);
|
|
1719
|
+
if (pending2) {
|
|
1720
|
+
log.info("Browser command result received", { id: result.id, stepCount: result.steps?.length, duration: result.duration });
|
|
1721
|
+
clearTimeout(pending2.timeout);
|
|
1722
|
+
this.pendingResults.delete(result.id);
|
|
1723
|
+
pending2.resolve(result);
|
|
1724
|
+
} else {
|
|
1725
|
+
log.warn("Browser command result received but no pending command found", { id: result.id, pendingIds: [...this.pendingResults.keys()] });
|
|
1726
|
+
}
|
|
1727
|
+
} else {
|
|
1728
|
+
log.warn("Browser command result received with no id", { bodyLength: body.length });
|
|
1729
|
+
}
|
|
1730
|
+
} catch (err) {
|
|
1731
|
+
log.warn("Browser command result parse error", { error: err instanceof Error ? err.message : String(err) });
|
|
1732
|
+
}
|
|
1733
|
+
clientRes.writeHead(204, this.corsHeaders(clientReq));
|
|
1734
|
+
clientRes.end();
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
/**
|
|
1738
|
+
* Inject window.__MINDSTUDIO__ context and browser agent script tag into HTML.
|
|
1739
|
+
*/
|
|
1740
|
+
injectScripts(html) {
|
|
1741
|
+
const contextScript = `<script>window.__MINDSTUDIO__=${JSON.stringify(this.clientContext)};</script>`;
|
|
1742
|
+
const agentUrl = this.browserAgentUrl || "https://unpkg.com/@mindstudio-ai/browser-agent/dist/index.js";
|
|
1743
|
+
const agentScript = `<script async src="${agentUrl}"></script>`;
|
|
1744
|
+
const injection = `${contextScript}
|
|
1745
|
+
${agentScript}`;
|
|
1746
|
+
if (html.includes("</head>")) {
|
|
1747
|
+
return html.replace("</head>", `${injection}
|
|
1748
|
+
</head>`);
|
|
1749
|
+
}
|
|
1750
|
+
return injection + "\n" + html;
|
|
1751
|
+
}
|
|
1225
1752
|
handleUpgrade(clientReq, clientSocket, head) {
|
|
1226
|
-
log.debug("proxy WebSocket upgrade", { path: clientReq.url });
|
|
1753
|
+
log.debug("Dev proxy WebSocket upgrade", { path: clientReq.url });
|
|
1227
1754
|
const options = {
|
|
1228
1755
|
hostname: "127.0.0.1",
|
|
1229
1756
|
port: this.upstreamPort,
|
|
@@ -1260,22 +1787,14 @@ var DevProxy = class {
|
|
|
1260
1787
|
upstreamReq.end();
|
|
1261
1788
|
}
|
|
1262
1789
|
};
|
|
1263
|
-
function injectClientContext(html, context) {
|
|
1264
|
-
const script = `<script>window.__MINDSTUDIO__=${JSON.stringify(context)};</script>`;
|
|
1265
|
-
if (html.includes("</head>")) {
|
|
1266
|
-
return html.replace("</head>", `${script}
|
|
1267
|
-
</head>`);
|
|
1268
|
-
}
|
|
1269
|
-
return script + "\n" + html;
|
|
1270
|
-
}
|
|
1271
1790
|
|
|
1272
1791
|
// src/dev/app-config.ts
|
|
1273
1792
|
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
1274
|
-
import { join as
|
|
1793
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
1275
1794
|
function detectAppConfig(cwd = process.cwd()) {
|
|
1276
|
-
const appJsonPath =
|
|
1795
|
+
const appJsonPath = join4(cwd, "mindstudio.json");
|
|
1277
1796
|
if (!existsSync2(appJsonPath)) {
|
|
1278
|
-
log.debug("
|
|
1797
|
+
log.debug("mindstudio.json not found", { path: appJsonPath });
|
|
1279
1798
|
return null;
|
|
1280
1799
|
}
|
|
1281
1800
|
try {
|
|
@@ -1294,7 +1813,7 @@ function detectAppConfig(cwd = process.cwd()) {
|
|
|
1294
1813
|
scenarios: parsed.scenarios ?? [],
|
|
1295
1814
|
interfaces: parsed.interfaces ?? []
|
|
1296
1815
|
};
|
|
1297
|
-
log.
|
|
1816
|
+
log.info("Loaded mindstudio.json", {
|
|
1298
1817
|
appId: config2.appId,
|
|
1299
1818
|
roles: config2.roles.length,
|
|
1300
1819
|
methods: config2.methods.length,
|
|
@@ -1304,7 +1823,7 @@ function detectAppConfig(cwd = process.cwd()) {
|
|
|
1304
1823
|
});
|
|
1305
1824
|
return config2;
|
|
1306
1825
|
} catch (err) {
|
|
1307
|
-
log.warn("
|
|
1826
|
+
log.warn("Failed to parse mindstudio.json", { error: err instanceof Error ? err.message : String(err) });
|
|
1308
1827
|
return null;
|
|
1309
1828
|
}
|
|
1310
1829
|
}
|
|
@@ -1315,7 +1834,7 @@ function getWebInterfaceConfig(appConfig, cwd = process.cwd()) {
|
|
|
1315
1834
|
if (!webInterface) {
|
|
1316
1835
|
return null;
|
|
1317
1836
|
}
|
|
1318
|
-
const configPath =
|
|
1837
|
+
const configPath = join4(cwd, webInterface.path);
|
|
1319
1838
|
if (!existsSync2(configPath)) {
|
|
1320
1839
|
return null;
|
|
1321
1840
|
}
|
|
@@ -1341,22 +1860,27 @@ function getWebProjectDir(appConfig, cwd = process.cwd()) {
|
|
|
1341
1860
|
if (!webInterface) {
|
|
1342
1861
|
return null;
|
|
1343
1862
|
}
|
|
1344
|
-
return dirname2(
|
|
1863
|
+
return dirname2(join4(cwd, webInterface.path));
|
|
1345
1864
|
}
|
|
1346
1865
|
function readTableSources(appConfig, cwd = process.cwd()) {
|
|
1347
1866
|
const results = [];
|
|
1348
1867
|
for (const table of appConfig.tables) {
|
|
1349
|
-
const filePath =
|
|
1868
|
+
const filePath = join4(cwd, table.path);
|
|
1350
1869
|
if (!existsSync2(filePath)) {
|
|
1870
|
+
log.warn("Table source file not found", { table: table.export, path: table.path });
|
|
1351
1871
|
continue;
|
|
1352
1872
|
}
|
|
1353
1873
|
try {
|
|
1354
1874
|
const source = readFileSync(filePath, "utf-8");
|
|
1355
1875
|
const name = table.export;
|
|
1356
1876
|
results.push({ name, source });
|
|
1357
|
-
} catch {
|
|
1877
|
+
} catch (err) {
|
|
1878
|
+
log.warn("Table source file unreadable", { table: table.export, path: table.path, error: err instanceof Error ? err.message : String(err) });
|
|
1358
1879
|
}
|
|
1359
1880
|
}
|
|
1881
|
+
if (results.length < appConfig.tables.length) {
|
|
1882
|
+
log.warn("Missing " + (appConfig.tables.length - results.length) + " table source file(s)", { found: results.length, expected: appConfig.tables.length });
|
|
1883
|
+
}
|
|
1360
1884
|
return results;
|
|
1361
1885
|
}
|
|
1362
1886
|
function findDirsNeedingInstall(appConfig, cwd = process.cwd()) {
|
|
@@ -1365,9 +1889,9 @@ function findDirsNeedingInstall(appConfig, cwd = process.cwd()) {
|
|
|
1365
1889
|
const firstMethodPath = appConfig.methods[0].path;
|
|
1366
1890
|
const parts = firstMethodPath.split("/");
|
|
1367
1891
|
for (let i = parts.length - 1; i >= 1; i--) {
|
|
1368
|
-
const candidate =
|
|
1369
|
-
if (existsSync2(
|
|
1370
|
-
if (!existsSync2(
|
|
1892
|
+
const candidate = join4(cwd, ...parts.slice(0, i));
|
|
1893
|
+
if (existsSync2(join4(candidate, "package.json"))) {
|
|
1894
|
+
if (!existsSync2(join4(candidate, "node_modules"))) {
|
|
1371
1895
|
dirs.push(candidate);
|
|
1372
1896
|
}
|
|
1373
1897
|
break;
|
|
@@ -1375,8 +1899,8 @@ function findDirsNeedingInstall(appConfig, cwd = process.cwd()) {
|
|
|
1375
1899
|
}
|
|
1376
1900
|
}
|
|
1377
1901
|
const webProjectDir = getWebProjectDir(appConfig, cwd);
|
|
1378
|
-
if (webProjectDir && existsSync2(
|
|
1379
|
-
if (!existsSync2(
|
|
1902
|
+
if (webProjectDir && existsSync2(join4(webProjectDir, "package.json"))) {
|
|
1903
|
+
if (!existsSync2(join4(webProjectDir, "node_modules"))) {
|
|
1380
1904
|
dirs.push(webProjectDir);
|
|
1381
1905
|
}
|
|
1382
1906
|
}
|
|
@@ -1405,11 +1929,11 @@ function detectGitBranch() {
|
|
|
1405
1929
|
|
|
1406
1930
|
// src/dev/table-watcher.ts
|
|
1407
1931
|
import { watch } from "chokidar";
|
|
1408
|
-
import { join as
|
|
1932
|
+
import { join as join5, dirname as dirname3, basename as basename2 } from "path";
|
|
1409
1933
|
function watchTableFiles(tables, cwd, onChanged) {
|
|
1410
1934
|
if (tables.length === 0) return () => {
|
|
1411
1935
|
};
|
|
1412
|
-
const filePaths = tables.map((t) =>
|
|
1936
|
+
const filePaths = tables.map((t) => join5(cwd, t.path));
|
|
1413
1937
|
let syncTimer;
|
|
1414
1938
|
const watcher = watch(filePaths, {
|
|
1415
1939
|
ignoreInitial: true,
|
|
@@ -1422,13 +1946,13 @@ function watchTableFiles(tables, cwd, onChanged) {
|
|
|
1422
1946
|
});
|
|
1423
1947
|
const dirToFiles = /* @__PURE__ */ new Map();
|
|
1424
1948
|
for (const table of tables) {
|
|
1425
|
-
const absPath =
|
|
1949
|
+
const absPath = join5(cwd, table.path);
|
|
1426
1950
|
const dir = dirname3(absPath);
|
|
1427
1951
|
const file = basename2(absPath);
|
|
1428
1952
|
if (!dirToFiles.has(dir)) dirToFiles.set(dir, /* @__PURE__ */ new Set());
|
|
1429
1953
|
dirToFiles.get(dir).add(file);
|
|
1430
1954
|
}
|
|
1431
|
-
log.info("table
|
|
1955
|
+
log.info("Watching table source files", {
|
|
1432
1956
|
dirs: dirToFiles.size,
|
|
1433
1957
|
tables: tables.length
|
|
1434
1958
|
});
|
|
@@ -1440,9 +1964,9 @@ function watchTableFiles(tables, cwd, onChanged) {
|
|
|
1440
1964
|
|
|
1441
1965
|
// src/dev/config-watcher.ts
|
|
1442
1966
|
import { watch as watch2 } from "chokidar";
|
|
1443
|
-
import { join as
|
|
1967
|
+
import { join as join6 } from "path";
|
|
1444
1968
|
function watchConfigFile(cwd, onChanged) {
|
|
1445
|
-
const configPath =
|
|
1969
|
+
const configPath = join6(cwd, "mindstudio.json");
|
|
1446
1970
|
let debounceTimer;
|
|
1447
1971
|
const watcher = watch2(configPath, {
|
|
1448
1972
|
ignoreInitial: true,
|
|
@@ -1451,11 +1975,10 @@ function watchConfigFile(cwd, onChanged) {
|
|
|
1451
1975
|
watcher.on("all", () => {
|
|
1452
1976
|
clearTimeout(debounceTimer);
|
|
1453
1977
|
debounceTimer = setTimeout(() => {
|
|
1454
|
-
log.info("config-watcher mindstudio.json changed");
|
|
1455
1978
|
onChanged();
|
|
1456
1979
|
}, 500);
|
|
1457
1980
|
});
|
|
1458
|
-
log.info("
|
|
1981
|
+
log.info("Watching mindstudio.json for changes", { path: configPath });
|
|
1459
1982
|
return () => {
|
|
1460
1983
|
clearTimeout(debounceTimer);
|
|
1461
1984
|
watcher.close();
|
|
@@ -1482,6 +2005,7 @@ export {
|
|
|
1482
2005
|
initLoggerHeadless,
|
|
1483
2006
|
initLoggerInteractive,
|
|
1484
2007
|
syncSchema,
|
|
2008
|
+
fetchCallbackToken,
|
|
1485
2009
|
devRequestEvents,
|
|
1486
2010
|
pollForRequest,
|
|
1487
2011
|
submitProgress,
|
|
@@ -1493,7 +2017,11 @@ export {
|
|
|
1493
2017
|
pollDeviceAuth,
|
|
1494
2018
|
getEditorSessions,
|
|
1495
2019
|
disconnectHeartbeat,
|
|
2020
|
+
initRequestLog,
|
|
2021
|
+
closeRequestLog,
|
|
1496
2022
|
DevRunner,
|
|
2023
|
+
initBrowserLog,
|
|
2024
|
+
closeBrowserLog,
|
|
1497
2025
|
DevProxy,
|
|
1498
2026
|
detectAppConfig,
|
|
1499
2027
|
getWebInterfaceConfig,
|
|
@@ -1505,4 +2033,4 @@ export {
|
|
|
1505
2033
|
watchTableFiles,
|
|
1506
2034
|
watchConfigFile
|
|
1507
2035
|
};
|
|
1508
|
-
//# sourceMappingURL=chunk-
|
|
2036
|
+
//# sourceMappingURL=chunk-XP4GPID6.js.map
|