@lunora/cli 1.0.0-alpha.57 → 1.0.0-alpha.59
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/bin.mjs +1 -1
- package/dist/index.d.mts +36 -10
- package/dist/index.d.ts +36 -10
- package/dist/index.mjs +7 -7
- package/dist/packem_chunks/handler.mjs +4 -4
- package/dist/packem_chunks/handler10.mjs +2 -2
- package/dist/packem_chunks/handler11.mjs +2 -2
- package/dist/packem_chunks/handler12.mjs +1 -1
- package/dist/packem_chunks/handler13.mjs +1 -1
- package/dist/packem_chunks/handler14.mjs +1 -1
- package/dist/packem_chunks/handler15.mjs +2 -2
- package/dist/packem_chunks/handler16.mjs +2 -2
- package/dist/packem_chunks/handler17.mjs +2 -2
- package/dist/packem_chunks/handler18.mjs +1 -1
- package/dist/packem_chunks/handler19.mjs +2 -2
- package/dist/packem_chunks/handler2.mjs +2 -2
- package/dist/packem_chunks/handler20.mjs +4 -4
- package/dist/packem_chunks/handler21.mjs +2 -2
- package/dist/packem_chunks/handler3.mjs +6 -3
- package/dist/packem_chunks/handler4.mjs +2 -2
- package/dist/packem_chunks/handler5.mjs +2 -2
- package/dist/packem_chunks/handler6.mjs +2 -2
- package/dist/packem_chunks/handler7.mjs +2 -2
- package/dist/packem_chunks/handler8.mjs +1 -1
- package/dist/packem_chunks/handler9.mjs +2 -2
- package/dist/packem_chunks/planDevCommand.mjs +498 -29
- package/dist/packem_chunks/runCodegenCommand.mjs +3 -3
- package/dist/packem_chunks/runDeployCommand.mjs +4 -4
- package/dist/packem_chunks/runInitCommand.mjs +5 -5
- package/dist/packem_chunks/runMigrateGenerateCommand.mjs +5 -4
- package/dist/packem_chunks/runResetCommand.mjs +1 -1
- package/dist/packem_chunks/runRpcCommand.mjs +3 -2
- package/dist/packem_shared/{COMMANDS-B0ftFD_3.mjs → COMMANDS-0BD3K1pb.mjs} +43 -3
- package/dist/packem_shared/{DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs → DEFAULT_IMPORT_BATCH_SIZE-CRQmmBZM.mjs} +3 -2
- package/dist/packem_shared/{api-spec-CtA6ilu4.mjs → api-spec-Bx0iKbxA.mjs} +3 -1
- package/dist/packem_shared/{buildRegistryIndex-BcYe607_.mjs → buildRegistryIndex-CqM5FwGI.mjs} +1 -1
- package/dist/packem_shared/{command-D3lB_4Az.mjs → command-lYnl4QyF.mjs} +1 -1
- package/dist/packem_shared/{commands-BK6Pt3Ni.mjs → commands-vnRRkhNE.mjs} +12 -9
- package/dist/packem_shared/{createLogger-B40gPzQo.mjs → createLogger-CIWSHrTL.mjs} +33 -6
- package/dist/packem_shared/{createRecordingSpawner-DxI3mebw.mjs → createRecordingSpawner-Cw5Iu73G.mjs} +12 -2
- package/dist/packem_shared/{open-url-Dfq6fAyT.mjs → open-url-4PBLY9X0.mjs} +3 -2
- package/dist/packem_shared/{output-format-wUvAN6AL.mjs → output-format-B4642rjE.mjs} +1 -1
- package/dist/packem_shared/{parseManifest--vZf2FY1.mjs → parseManifest-BBrXCg9V.mjs} +16 -9
- package/dist/packem_shared/runAddCommand-BCcJzs_k.mjs +4 -0
- package/dist/packem_shared/{storage-C8SAm5zu.mjs → storage-bGfgwOgF.mjs} +1 -1
- package/package.json +7 -6
- package/skills/lunora-quickstart/SKILL.md +25 -5
- package/dist/packem_shared/runAddCommand-DWjH0Heb.mjs +0 -4
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { detectAgentRules, resolveRemoteEnabled, readProjectRemotePreference, detectFramework, inferLunoraBindings, packageNamesFromBindings, ensureDevVarsExample, ensureDevVariables, isInteractive, DEV_VARS_FILE, DEV_VARS_EXAMPLE_FILE, fillDevSecrets,
|
|
3
|
-
import { p as parseApiSpec } from '../packem_shared/api-spec-
|
|
4
|
-
import { existsSync, watch } from 'node:fs';
|
|
5
|
-
import { join } from 'node:path';
|
|
1
|
+
import { spawnSync, spawn } from 'node:child_process';
|
|
2
|
+
import { detectAgentRules, claimDevServerState, clearDevServerState, readDevServerState, readLiveDevServerState, readProjectDependencyNames, DEV_HANDOFF_ENV, isRecordedProcessCurrent, DEV_LOG_FILE, DEV_LOG_FILE_ENV, DEV_DAEMON_ENV, detectAiAgent, resolveRemoteEnabled, readProjectRemotePreference, detectFramework, inferLunoraBindings, packageNamesFromBindings, ensureDevVarsExample, ensureDevVariables, isInteractive, DEV_VARS_FILE, DEV_VARS_EXAMPLE_FILE, fillDevSecrets, updateDevServerState, claimAgentRulesHint, AGENT_RULES_HINT, discoverContainerInfo, streamContainerLogs, materializeRemoteWranglerConfig, formatLunoraEvent } from '@lunora/config';
|
|
3
|
+
import { p as parseApiSpec } from '../packem_shared/api-spec-Bx0iKbxA.mjs';
|
|
4
|
+
import { existsSync, watch, readFileSync, statSync, openSync, readSync, closeSync, mkdirSync } from 'node:fs';
|
|
5
|
+
import { join, dirname } from 'node:path';
|
|
6
6
|
import { runCodegen } from '@lunora/codegen';
|
|
7
7
|
import { r as renderCodegenFailure } from '../packem_shared/codegen-error-DJG-ghs_.mjs';
|
|
8
|
-
import { d as defineHandler } from '../packem_shared/command-
|
|
8
|
+
import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
|
|
9
9
|
import { d as detectPackageManager, e as execArgsFor, r as runScriptCommand } from '../packem_shared/detect-package-manager-Cxo6Tpyx.mjs';
|
|
10
|
+
import { forceJsonLogging } from '../packem_shared/createLogger-CIWSHrTL.mjs';
|
|
11
|
+
import { spawnShellCompat } from '../packem_shared/createRecordingSpawner-Cw5Iu73G.mjs';
|
|
10
12
|
import { createServer, request } from 'node:http';
|
|
11
13
|
import { connect } from 'node:net';
|
|
12
14
|
import { loadStudioAssets, studioAssetsStamp, renderStudioHtml, resolveAdminToken, SCHEMA_EDIT_ENDPOINT, POLICY_SCAFFOLD_ENDPOINT, SEED_ENDPOINT, serveJsonHandler, isStandaloneModulePath, readStandaloneAsset, assetContentType, handleSchemaEditRequest, handlePolicyScaffoldRequest, handleSeedRequest } from '@lunora/config/studio-host';
|
|
13
15
|
import { c as createTuiConfirm } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
|
|
16
|
+
import { p as printJson } from '../packem_shared/output-format-B4642rjE.mjs';
|
|
14
17
|
|
|
15
18
|
const DEFAULT_DEBOUNCE_MS = 100;
|
|
16
19
|
const PATH_SEGMENT_SEPARATOR = /[/\\]/u;
|
|
@@ -270,8 +273,386 @@ const startStudioServer = async (options) => {
|
|
|
270
273
|
};
|
|
271
274
|
};
|
|
272
275
|
|
|
276
|
+
const DEFAULT_READY_TIMEOUT_MS = 12e4;
|
|
277
|
+
const POLL_INTERVAL_MS = 250;
|
|
278
|
+
const STOP_GRACE_MS = 1e4;
|
|
279
|
+
const DEFAULT_LOG_LINES = 100;
|
|
280
|
+
const FAILURE_LOG_TAIL_LINES = 40;
|
|
281
|
+
const LOG_TAIL_MAX_BYTES = 256 * 1024;
|
|
282
|
+
const READY_TIMEOUT_ENV = "LUNORA_DEV_READY_TIMEOUT_MS";
|
|
283
|
+
const detectDevFlavor = (cwd) => readProjectDependencyNames(cwd).has("@lunora/vite") ? "vite" : "wrangler";
|
|
284
|
+
const viteDevCommand = (cwd) => {
|
|
285
|
+
const manager = detectPackageManager(cwd);
|
|
286
|
+
let script;
|
|
287
|
+
try {
|
|
288
|
+
const raw = readFileSync(join(cwd, "package.json"), "utf8");
|
|
289
|
+
script = JSON.parse(raw).scripts?.dev;
|
|
290
|
+
} catch {
|
|
291
|
+
}
|
|
292
|
+
if (script === void 0 || script.trim() === "" || script.includes("lunora")) {
|
|
293
|
+
const exec = execArgsFor(manager, "vite", ["dev"]);
|
|
294
|
+
return { args: exec.args, command: exec.command };
|
|
295
|
+
}
|
|
296
|
+
return { args: ["run", "dev"], command: manager };
|
|
297
|
+
};
|
|
298
|
+
const sleep = (ms) => new Promise((resolve) => {
|
|
299
|
+
setTimeout(resolve, ms);
|
|
300
|
+
});
|
|
301
|
+
const defaultProbe = async (origin) => {
|
|
302
|
+
try {
|
|
303
|
+
await fetch(new URL("/_lunora/status", origin), { signal: AbortSignal.timeout(1e3) });
|
|
304
|
+
return true;
|
|
305
|
+
} catch {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
const defaultDetachedSpawner = (descriptor) => {
|
|
310
|
+
mkdirSync(dirname(descriptor.logPath), { recursive: true });
|
|
311
|
+
const logFd = openSync(descriptor.logPath, "w", 384);
|
|
312
|
+
let child;
|
|
313
|
+
const exec = spawnShellCompat(descriptor.command, descriptor.args);
|
|
314
|
+
try {
|
|
315
|
+
child = spawn(exec.command, exec.args, {
|
|
316
|
+
cwd: descriptor.cwd,
|
|
317
|
+
detached: true,
|
|
318
|
+
env: descriptor.env,
|
|
319
|
+
shell: exec.shell,
|
|
320
|
+
stdio: ["ignore", logFd, logFd],
|
|
321
|
+
windowsHide: true
|
|
322
|
+
});
|
|
323
|
+
} finally {
|
|
324
|
+
closeSync(logFd);
|
|
325
|
+
}
|
|
326
|
+
child.unref();
|
|
327
|
+
return {
|
|
328
|
+
exited: new Promise((resolve) => {
|
|
329
|
+
child.on("error", () => {
|
|
330
|
+
resolve(1);
|
|
331
|
+
});
|
|
332
|
+
child.on("exit", (code, signal) => {
|
|
333
|
+
resolve(code ?? (signal ? 1 : 0));
|
|
334
|
+
});
|
|
335
|
+
}),
|
|
336
|
+
pid: child.pid
|
|
337
|
+
};
|
|
338
|
+
};
|
|
339
|
+
const readLogWindow = (path) => {
|
|
340
|
+
try {
|
|
341
|
+
const { size } = statSync(path);
|
|
342
|
+
if (size <= LOG_TAIL_MAX_BYTES) {
|
|
343
|
+
return readFileSync(path, "utf8");
|
|
344
|
+
}
|
|
345
|
+
const fd = openSync(path, "r");
|
|
346
|
+
try {
|
|
347
|
+
const buffer = Buffer.alloc(LOG_TAIL_MAX_BYTES);
|
|
348
|
+
const read = readSync(fd, buffer, 0, LOG_TAIL_MAX_BYTES, size - LOG_TAIL_MAX_BYTES);
|
|
349
|
+
const text = buffer.toString("utf8", 0, read);
|
|
350
|
+
return text.slice(text.indexOf("\n") + 1);
|
|
351
|
+
} finally {
|
|
352
|
+
closeSync(fd);
|
|
353
|
+
}
|
|
354
|
+
} catch {
|
|
355
|
+
return void 0;
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
const readLogTail = (path, count) => {
|
|
359
|
+
const text = readLogWindow(path);
|
|
360
|
+
if (text === void 0) {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
const lines = text.split("\n");
|
|
364
|
+
if (lines.at(-1) === "") {
|
|
365
|
+
lines.pop();
|
|
366
|
+
}
|
|
367
|
+
return count > 0 ? lines.slice(-count) : lines;
|
|
368
|
+
};
|
|
369
|
+
const resolveLogPath = (cwd, state) => state?.logFile ?? join(cwd, DEV_LOG_FILE);
|
|
370
|
+
const printLifecycleHints = (logger) => {
|
|
371
|
+
logger.info(" Stop: lunora dev stop");
|
|
372
|
+
logger.info(" Status: lunora dev status");
|
|
373
|
+
logger.info(" Logs: lunora dev logs");
|
|
374
|
+
};
|
|
375
|
+
const reportExistingServer = (logger, existing) => {
|
|
376
|
+
logger.warn(`Dev server already running at ${existing.url} (pid ${String(existing.pid)})`);
|
|
377
|
+
printLifecycleHints(logger);
|
|
378
|
+
};
|
|
379
|
+
const printBackgroundBanner = (logger, state) => {
|
|
380
|
+
logger.success(`Dev server running at ${state.url} (pid ${String(state.pid)})`);
|
|
381
|
+
if (state.studioUrl !== void 0) {
|
|
382
|
+
logger.info(` Studio: ${state.studioUrl}`);
|
|
383
|
+
}
|
|
384
|
+
printLifecycleHints(logger);
|
|
385
|
+
};
|
|
386
|
+
const waitUntilReady = async (parameters) => {
|
|
387
|
+
const { child, cwd, deadline, pollIntervalMs, probe } = parameters;
|
|
388
|
+
const exited = child.exited.then((code) => {
|
|
389
|
+
return { exitCode: code };
|
|
390
|
+
});
|
|
391
|
+
while (Date.now() < deadline) {
|
|
392
|
+
const state = readLiveDevServerState(cwd);
|
|
393
|
+
if (state !== void 0 && state.pid !== process.pid && await probe(state.url)) {
|
|
394
|
+
return { state, status: "ready" };
|
|
395
|
+
}
|
|
396
|
+
const raced = await Promise.race([exited, sleep(pollIntervalMs)]);
|
|
397
|
+
if (raced !== void 0) {
|
|
398
|
+
return { ...raced, status: "exited" };
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return { status: "timeout" };
|
|
402
|
+
};
|
|
403
|
+
const runDevBackground = async (options) => {
|
|
404
|
+
const { cwd, logger } = options;
|
|
405
|
+
const logPath = resolveLogPath(cwd, void 0);
|
|
406
|
+
const environmentTimeout = Number(process.env[READY_TIMEOUT_ENV] ?? "");
|
|
407
|
+
const timeout = options.readyTimeoutMs ?? (Number.isFinite(environmentTimeout) && environmentTimeout > 0 ? environmentTimeout : DEFAULT_READY_TIMEOUT_MS);
|
|
408
|
+
const child = (options.spawnDetached ?? defaultDetachedSpawner)({
|
|
409
|
+
args: options.command.args,
|
|
410
|
+
command: options.command.command,
|
|
411
|
+
cwd,
|
|
412
|
+
env: {
|
|
413
|
+
...process.env,
|
|
414
|
+
...options.env,
|
|
415
|
+
[DEV_DAEMON_ENV]: "1",
|
|
416
|
+
[DEV_LOG_FILE_ENV]: logPath,
|
|
417
|
+
// The capture log is read back by `lunora dev logs` (and often an
|
|
418
|
+
// agent) — ANSI colour codes would only garble it.
|
|
419
|
+
...process.env.FORCE_COLOR === void 0 ? { NO_COLOR: "1" } : {},
|
|
420
|
+
...options.json ? { LUNORA_LOG_JSON: "1" } : {}
|
|
421
|
+
},
|
|
422
|
+
logPath
|
|
423
|
+
});
|
|
424
|
+
const outcome = await waitUntilReady({
|
|
425
|
+
child,
|
|
426
|
+
cwd,
|
|
427
|
+
deadline: Date.now() + timeout,
|
|
428
|
+
pollIntervalMs: options.pollIntervalMs ?? POLL_INTERVAL_MS,
|
|
429
|
+
probe: options.probe ?? defaultProbe
|
|
430
|
+
});
|
|
431
|
+
if (outcome.status === "ready") {
|
|
432
|
+
printBackgroundBanner(logger, outcome.state);
|
|
433
|
+
return { code: 0 };
|
|
434
|
+
}
|
|
435
|
+
if (outcome.status === "exited") {
|
|
436
|
+
logger.error(`dev server exited before becoming ready (exit code ${String(outcome.exitCode)}). Last output:`);
|
|
437
|
+
for (const line of readLogTail(logPath, FAILURE_LOG_TAIL_LINES)) {
|
|
438
|
+
logger.error(` ${line}`);
|
|
439
|
+
}
|
|
440
|
+
clearDevServerState(cwd, child.pid);
|
|
441
|
+
return { code: outcome.exitCode === 0 ? 1 : outcome.exitCode };
|
|
442
|
+
}
|
|
443
|
+
logger.warn(
|
|
444
|
+
`dev server did not confirm ready within ${String(Math.round(timeout / 1e3))}s — it may still be compiling. Check \`lunora dev status\` and \`lunora dev logs\`; \`lunora dev stop\` shuts it down.`
|
|
445
|
+
);
|
|
446
|
+
return { code: 1 };
|
|
447
|
+
};
|
|
448
|
+
const daemonArguments = (options, remote) => {
|
|
449
|
+
const args = ["dev"];
|
|
450
|
+
if (options.apiSpec !== void 0) {
|
|
451
|
+
args.push("--api-spec", options.apiSpec);
|
|
452
|
+
}
|
|
453
|
+
if (options.port !== void 0) {
|
|
454
|
+
args.push("--port", String(options.port));
|
|
455
|
+
}
|
|
456
|
+
if (options.workerPort !== void 0) {
|
|
457
|
+
args.push("--worker-port", String(options.workerPort));
|
|
458
|
+
}
|
|
459
|
+
if (options.codegen === false) {
|
|
460
|
+
args.push("--no-codegen");
|
|
461
|
+
}
|
|
462
|
+
if (options.studio === false) {
|
|
463
|
+
args.push("--no-studio");
|
|
464
|
+
}
|
|
465
|
+
if (remote) {
|
|
466
|
+
args.push("--remote");
|
|
467
|
+
}
|
|
468
|
+
return args;
|
|
469
|
+
};
|
|
470
|
+
const startBackground = async (context) => {
|
|
471
|
+
const { cwd, jsonLogs, logger, options, remote } = context;
|
|
472
|
+
const run = context.run ?? runDevBackground;
|
|
473
|
+
const flavor = detectDevFlavor(cwd);
|
|
474
|
+
const url = flavor === "vite" ? "http://localhost:5173" : `http://localhost:${String(options.workerPort ?? 8787)}`;
|
|
475
|
+
const claim = claimDevServerState(cwd, {
|
|
476
|
+
background: true,
|
|
477
|
+
mode: "cli",
|
|
478
|
+
pid: process.pid,
|
|
479
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
480
|
+
url
|
|
481
|
+
});
|
|
482
|
+
if (!claim.ok) {
|
|
483
|
+
if (claim.existing !== void 0) {
|
|
484
|
+
reportExistingServer(logger, claim.existing);
|
|
485
|
+
}
|
|
486
|
+
return { code: 0 };
|
|
487
|
+
}
|
|
488
|
+
const handoff = { [DEV_HANDOFF_ENV]: String(process.pid) };
|
|
489
|
+
try {
|
|
490
|
+
if (flavor === "vite") {
|
|
491
|
+
return await run({
|
|
492
|
+
command: viteDevCommand(cwd),
|
|
493
|
+
cwd,
|
|
494
|
+
env: { ...remote ? { LUNORA_REMOTE: "1" } : {}, ...handoff },
|
|
495
|
+
json: jsonLogs,
|
|
496
|
+
logger
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
return await run({
|
|
500
|
+
command: { args: [process.argv[1] ?? "lunora", ...daemonArguments(options, remote)], command: process.execPath },
|
|
501
|
+
cwd,
|
|
502
|
+
env: handoff,
|
|
503
|
+
json: jsonLogs,
|
|
504
|
+
logger
|
|
505
|
+
});
|
|
506
|
+
} finally {
|
|
507
|
+
clearDevServerState(cwd, process.pid);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
const processGroupId = (pid) => {
|
|
511
|
+
try {
|
|
512
|
+
const result = spawnSync("ps", ["-o", "pgid=", "-p", String(pid)], { encoding: "utf8" });
|
|
513
|
+
const pgid = Number.parseInt(result.stdout.trim(), 10);
|
|
514
|
+
return Number.isInteger(pgid) && pgid > 0 ? pgid : void 0;
|
|
515
|
+
} catch {
|
|
516
|
+
return void 0;
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
const forceKillRecordedServer = (state, signal) => {
|
|
520
|
+
if (process.platform === "win32") {
|
|
521
|
+
try {
|
|
522
|
+
spawnSync("taskkill", ["/pid", String(state.pid), "/T", "/F"], { stdio: "ignore" });
|
|
523
|
+
} catch {
|
|
524
|
+
}
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (state.background === true) {
|
|
528
|
+
try {
|
|
529
|
+
signal(-(processGroupId(state.pid) ?? state.pid), "SIGKILL");
|
|
530
|
+
return;
|
|
531
|
+
} catch {
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
try {
|
|
535
|
+
signal(state.pid, "SIGKILL");
|
|
536
|
+
} catch {
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
const runDevStop = async (options) => {
|
|
540
|
+
const { cwd, logger } = options;
|
|
541
|
+
const state = readDevServerState(cwd);
|
|
542
|
+
const alive = options.alive ?? ((pid) => pid === state?.pid && isRecordedProcessCurrent(state));
|
|
543
|
+
const signal = options.signal ?? ((pid, sig) => {
|
|
544
|
+
process.kill(pid, sig);
|
|
545
|
+
});
|
|
546
|
+
const pollInterval = options.pollIntervalMs ?? POLL_INTERVAL_MS;
|
|
547
|
+
const grace = options.stopGraceMs ?? STOP_GRACE_MS;
|
|
548
|
+
if (state === void 0 || !alive(state.pid)) {
|
|
549
|
+
if (state !== void 0) {
|
|
550
|
+
clearDevServerState(cwd, state.pid);
|
|
551
|
+
}
|
|
552
|
+
if (options.json) {
|
|
553
|
+
printJson({ running: false, stopped: false });
|
|
554
|
+
} else {
|
|
555
|
+
logger.info("No dev server running.");
|
|
556
|
+
}
|
|
557
|
+
return { code: 0 };
|
|
558
|
+
}
|
|
559
|
+
try {
|
|
560
|
+
signal(state.pid, "SIGTERM");
|
|
561
|
+
} catch {
|
|
562
|
+
}
|
|
563
|
+
const deadline = Date.now() + grace;
|
|
564
|
+
while (alive(state.pid) && Date.now() < deadline) {
|
|
565
|
+
await sleep(pollInterval);
|
|
566
|
+
}
|
|
567
|
+
if (alive(state.pid)) {
|
|
568
|
+
forceKillRecordedServer(state, signal);
|
|
569
|
+
}
|
|
570
|
+
clearDevServerState(cwd, state.pid);
|
|
571
|
+
if (options.json) {
|
|
572
|
+
printJson({ pid: state.pid, stopped: true });
|
|
573
|
+
} else {
|
|
574
|
+
logger.success(`Stopped dev server (pid ${String(state.pid)}).`);
|
|
575
|
+
}
|
|
576
|
+
return { code: 0 };
|
|
577
|
+
};
|
|
578
|
+
const runDevStatus = (options) => {
|
|
579
|
+
const { cwd, logger } = options;
|
|
580
|
+
const state = readLiveDevServerState(cwd);
|
|
581
|
+
if (state === void 0) {
|
|
582
|
+
if (options.json) {
|
|
583
|
+
printJson({ running: false });
|
|
584
|
+
} else {
|
|
585
|
+
logger.info("No dev server running.");
|
|
586
|
+
}
|
|
587
|
+
return { code: 0 };
|
|
588
|
+
}
|
|
589
|
+
const now = options.now ?? Date.now;
|
|
590
|
+
const startedAtMs = state.startedAt === void 0 ? Number.NaN : Date.parse(state.startedAt);
|
|
591
|
+
const uptimeSeconds = Number.isFinite(startedAtMs) ? Math.max(0, Math.round((now() - startedAtMs) / 1e3)) : void 0;
|
|
592
|
+
if (options.json) {
|
|
593
|
+
printJson({
|
|
594
|
+
background: state.background === true,
|
|
595
|
+
logFile: state.logFile,
|
|
596
|
+
mode: state.mode,
|
|
597
|
+
pid: state.pid,
|
|
598
|
+
running: true,
|
|
599
|
+
startedAt: state.startedAt,
|
|
600
|
+
studioUrl: state.studioUrl,
|
|
601
|
+
uptimeSeconds,
|
|
602
|
+
url: state.url
|
|
603
|
+
});
|
|
604
|
+
return { code: 0 };
|
|
605
|
+
}
|
|
606
|
+
const details = [
|
|
607
|
+
`pid ${String(state.pid)}`,
|
|
608
|
+
...uptimeSeconds === void 0 ? [] : [`uptime ${String(uptimeSeconds)}s`],
|
|
609
|
+
state.background === true ? "background" : "foreground"
|
|
610
|
+
];
|
|
611
|
+
logger.success(`Dev server running at ${state.url} (${details.join(", ")})`);
|
|
612
|
+
if (state.studioUrl !== void 0) {
|
|
613
|
+
logger.info(` Studio: ${state.studioUrl}`);
|
|
614
|
+
}
|
|
615
|
+
if (state.logFile !== void 0) {
|
|
616
|
+
logger.info(` Logs: lunora dev logs (${state.logFile})`);
|
|
617
|
+
}
|
|
618
|
+
return { code: 0 };
|
|
619
|
+
};
|
|
620
|
+
const runDevLogs = (options) => {
|
|
621
|
+
const { cwd, logger } = options;
|
|
622
|
+
const state = readDevServerState(cwd);
|
|
623
|
+
const logPath = resolveLogPath(cwd, state);
|
|
624
|
+
if (!existsSync(logPath)) {
|
|
625
|
+
logger.info("No dev server logs found — output is captured when the server runs in background mode (`lunora dev --background`).");
|
|
626
|
+
return { code: 0 };
|
|
627
|
+
}
|
|
628
|
+
const tail = readLogTail(logPath, options.lines ?? DEFAULT_LOG_LINES);
|
|
629
|
+
if (tail.length > 0) {
|
|
630
|
+
process.stdout.write(`${tail.join("\n")}
|
|
631
|
+
`);
|
|
632
|
+
}
|
|
633
|
+
return { code: 0 };
|
|
634
|
+
};
|
|
635
|
+
const runLifecycleSubcommand = (parameters) => {
|
|
636
|
+
const { cwd, json, lines, logger, subcommand } = parameters;
|
|
637
|
+
if (subcommand === "stop") {
|
|
638
|
+
return runDevStop({ cwd, json, logger });
|
|
639
|
+
}
|
|
640
|
+
if (subcommand === "status") {
|
|
641
|
+
return runDevStatus({ cwd, json, logger });
|
|
642
|
+
}
|
|
643
|
+
if (subcommand === "logs") {
|
|
644
|
+
return runDevLogs({ cwd, lines, logger });
|
|
645
|
+
}
|
|
646
|
+
if (subcommand !== void 0) {
|
|
647
|
+
logger.error(`dev: unknown subcommand "${subcommand}" — expected stop | status | logs (or no subcommand to start the dev server)`);
|
|
648
|
+
return { code: 1 };
|
|
649
|
+
}
|
|
650
|
+
return void 0;
|
|
651
|
+
};
|
|
652
|
+
|
|
273
653
|
const DEFAULT_STUDIO_PORT = 6173;
|
|
274
654
|
const DEFAULT_WORKER_PORT = 8787;
|
|
655
|
+
const DEFAULT_VITE_PORT = 5173;
|
|
275
656
|
const SIGINT_GRACE_MS = 5e3;
|
|
276
657
|
const resolveRemotePlan = (options, cwd) => {
|
|
277
658
|
const noopCleanup = () => {
|
|
@@ -290,14 +671,36 @@ const resolveRemotePlan = (options, cwd) => {
|
|
|
290
671
|
};
|
|
291
672
|
const planDevCommand = (options) => {
|
|
292
673
|
const cwd = options.cwd ?? process.cwd();
|
|
293
|
-
const workerPort = options.workerPort ?? DEFAULT_WORKER_PORT;
|
|
294
674
|
const manager = detectPackageManager(cwd);
|
|
675
|
+
const flavor = options.flavor ?? detectDevFlavor(cwd);
|
|
676
|
+
if (flavor === "vite") {
|
|
677
|
+
const exec2 = viteDevCommand(cwd);
|
|
678
|
+
return {
|
|
679
|
+
codegenEnabled: false,
|
|
680
|
+
flavor,
|
|
681
|
+
remote: { bindings: [], cleanup: () => {
|
|
682
|
+
}, enabled: options.remote === true },
|
|
683
|
+
studioEnabled: false,
|
|
684
|
+
studioPort: options.port ?? DEFAULT_STUDIO_PORT,
|
|
685
|
+
workerOrigin: `http://localhost:${String(DEFAULT_VITE_PORT)}`,
|
|
686
|
+
workerPort: DEFAULT_VITE_PORT,
|
|
687
|
+
wrangler: {
|
|
688
|
+
args: exec2.args,
|
|
689
|
+
command: exec2.command,
|
|
690
|
+
cwd,
|
|
691
|
+
...options.remote === true ? { env: { LUNORA_REMOTE: "1" } } : {},
|
|
692
|
+
tag: "vite"
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
}
|
|
295
696
|
const detection = detectFramework(cwd);
|
|
296
697
|
const frameworkHint = detection.framework === "none" ? void 0 : `this project uses ${detection.framework} — the worker runs inside Vite there. run \`${runScriptCommand(manager, "dev")}\` for the full app (frontend + HMR); \`lunora dev\` starts only the worker.`;
|
|
698
|
+
const workerPort = options.workerPort ?? DEFAULT_WORKER_PORT;
|
|
297
699
|
const remote = resolveRemotePlan(options, cwd);
|
|
298
700
|
const exec = execArgsFor(manager, "wrangler", ["dev", "--port", String(workerPort), "--var", "WORKER_ENV:development", ...remote.args]);
|
|
299
701
|
return {
|
|
300
702
|
codegenEnabled: options.codegen !== false,
|
|
703
|
+
flavor,
|
|
301
704
|
frameworkHint,
|
|
302
705
|
remote: remote.plan,
|
|
303
706
|
studioEnabled: options.studio !== false,
|
|
@@ -346,9 +749,11 @@ const pipeChildOutput = (child, tag, logger) => {
|
|
|
346
749
|
pumpStream(child.stderr, "stderr");
|
|
347
750
|
};
|
|
348
751
|
const defaultWorkerSpawner = (descriptor, logger) => {
|
|
349
|
-
const
|
|
752
|
+
const exec = spawnShellCompat(descriptor.command, descriptor.args);
|
|
753
|
+
const child = spawn(exec.command, exec.args, {
|
|
350
754
|
cwd: descriptor.cwd ?? process.cwd(),
|
|
351
|
-
env: process.env,
|
|
755
|
+
env: descriptor.env ? { ...process.env, ...descriptor.env } : process.env,
|
|
756
|
+
shell: exec.shell,
|
|
352
757
|
stdio: ["inherit", "pipe", "pipe"]
|
|
353
758
|
});
|
|
354
759
|
pipeChildOutput(child, descriptor.tag, logger);
|
|
@@ -463,14 +868,60 @@ const offerDevVariablesScaffold = async (options, cwd) => {
|
|
|
463
868
|
} catch {
|
|
464
869
|
}
|
|
465
870
|
};
|
|
871
|
+
const afterWorkerSpawn = (plan, cwd, logger, studioUrl) => {
|
|
872
|
+
if (plan.flavor !== "wrangler") {
|
|
873
|
+
return void 0;
|
|
874
|
+
}
|
|
875
|
+
if (studioUrl !== void 0) {
|
|
876
|
+
updateDevServerState(cwd, { studioUrl });
|
|
877
|
+
}
|
|
878
|
+
let containerLogs;
|
|
879
|
+
try {
|
|
880
|
+
containerLogs = startContainerLogStreaming(cwd, logger);
|
|
881
|
+
} catch {
|
|
882
|
+
}
|
|
883
|
+
printBanner(logger, plan, studioUrl);
|
|
884
|
+
return containerLogs;
|
|
885
|
+
};
|
|
886
|
+
const claimStartRecord = (plan, cwd) => {
|
|
887
|
+
const handoffPid = Number(process.env[DEV_HANDOFF_ENV]);
|
|
888
|
+
const claim = claimDevServerState(
|
|
889
|
+
cwd,
|
|
890
|
+
{
|
|
891
|
+
background: process.env[DEV_DAEMON_ENV] === "1",
|
|
892
|
+
logFile: process.env[DEV_LOG_FILE_ENV],
|
|
893
|
+
mode: "cli",
|
|
894
|
+
pid: process.pid,
|
|
895
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
896
|
+
url: plan.workerOrigin
|
|
897
|
+
},
|
|
898
|
+
Number.isInteger(handoffPid) && handoffPid > 0 ? { supersedePid: handoffPid } : void 0
|
|
899
|
+
);
|
|
900
|
+
return claim.ok ? void 0 : claim.existing;
|
|
901
|
+
};
|
|
466
902
|
const runDevCommand = async (options) => {
|
|
467
903
|
const plan = planDevCommand(options);
|
|
468
904
|
const { logger } = options;
|
|
469
905
|
const cwd = plan.wrangler.cwd ?? process.cwd();
|
|
470
906
|
const handles = { remoteCleanup: plan.remote.cleanup };
|
|
471
907
|
try {
|
|
908
|
+
const existing = readLiveDevServerState(cwd);
|
|
909
|
+
if (existing !== void 0 && existing.pid !== process.pid) {
|
|
910
|
+
reportExistingServer(logger, existing);
|
|
911
|
+
return { code: 0, plan };
|
|
912
|
+
}
|
|
913
|
+
const incumbent = claimStartRecord(plan, cwd);
|
|
914
|
+
if (incumbent !== void 0) {
|
|
915
|
+
reportExistingServer(logger, incumbent);
|
|
916
|
+
return { code: 0, plan };
|
|
917
|
+
}
|
|
918
|
+
if (plan.flavor === "vite") {
|
|
919
|
+
plan.wrangler.env = { ...plan.wrangler.env, [DEV_HANDOFF_ENV]: String(process.pid) };
|
|
920
|
+
}
|
|
472
921
|
await offerDevVariablesScaffold(options, cwd);
|
|
473
|
-
logger.info(
|
|
922
|
+
logger.info(
|
|
923
|
+
plan.flavor === "vite" ? "starting vite dev (worker + studio + codegen run inside Vite via @lunora/vite)" : "starting wrangler dev + studio"
|
|
924
|
+
);
|
|
474
925
|
if (plan.codegenEnabled) {
|
|
475
926
|
handles.codegen = (options.startCodegen ?? startCodegenWatch)({ apiSpec: options.apiSpec, logger, projectRoot: cwd });
|
|
476
927
|
}
|
|
@@ -496,11 +947,7 @@ const runDevCommand = async (options) => {
|
|
|
496
947
|
logger.warn(plan.frameworkHint);
|
|
497
948
|
}
|
|
498
949
|
const worker = (options.startWorker ?? defaultWorkerSpawner)(plan.wrangler, logger);
|
|
499
|
-
|
|
500
|
-
handles.containerLogs = startContainerLogStreaming(cwd, logger);
|
|
501
|
-
} catch {
|
|
502
|
-
}
|
|
503
|
-
printBanner(logger, plan, studioUrl);
|
|
950
|
+
handles.containerLogs = afterWorkerSpawn(plan, cwd, logger, studioUrl);
|
|
504
951
|
printAgentRulesHint(logger, cwd);
|
|
505
952
|
let sigintCount = 0;
|
|
506
953
|
let escalationTimer;
|
|
@@ -530,11 +977,41 @@ const runDevCommand = async (options) => {
|
|
|
530
977
|
process.off("SIGTERM", onSigterm);
|
|
531
978
|
return { code, plan };
|
|
532
979
|
} finally {
|
|
980
|
+
clearDevServerState(cwd, process.pid);
|
|
533
981
|
await teardown(handles);
|
|
534
982
|
}
|
|
535
983
|
};
|
|
536
|
-
const execute = defineHandler(
|
|
537
|
-
|
|
984
|
+
const execute = defineHandler(async ({ argument, cwd, logger, options }) => {
|
|
985
|
+
const json = options.json === true;
|
|
986
|
+
const dispatched = runLifecycleSubcommand({ cwd, json, lines: options.lines, logger, subcommand: argument[0] });
|
|
987
|
+
if (dispatched !== void 0) {
|
|
988
|
+
return dispatched;
|
|
989
|
+
}
|
|
990
|
+
const isDaemon = process.env[DEV_DAEMON_ENV] === "1";
|
|
991
|
+
const agent = isDaemon ? void 0 : detectAiAgent();
|
|
992
|
+
const jsonLogs = json || agent !== void 0;
|
|
993
|
+
if (jsonLogs) {
|
|
994
|
+
forceJsonLogging();
|
|
995
|
+
}
|
|
996
|
+
if (agent !== void 0 && options.background !== true) {
|
|
997
|
+
logger.info(
|
|
998
|
+
`AI agent detected (${agent.name} via ${agent.variable}) — starting the dev server in background mode with JSON logs. Set LUNORA_AGENT_MODE=0 to opt out.`
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
const remote = resolveRemoteEnabled({
|
|
1002
|
+
configPreference: readProjectRemotePreference(cwd),
|
|
1003
|
+
envValue: process.env["LUNORA_REMOTE"],
|
|
1004
|
+
flag: options.remote
|
|
1005
|
+
});
|
|
1006
|
+
if (!isDaemon && (options.background === true || agent !== void 0)) {
|
|
1007
|
+
const existing = readLiveDevServerState(cwd);
|
|
1008
|
+
if (existing !== void 0) {
|
|
1009
|
+
reportExistingServer(logger, existing);
|
|
1010
|
+
return { code: 0 };
|
|
1011
|
+
}
|
|
1012
|
+
return startBackground({ cwd, jsonLogs, logger, options, remote });
|
|
1013
|
+
}
|
|
1014
|
+
return runDevCommand({
|
|
538
1015
|
apiSpec: parseApiSpec(options.apiSpec),
|
|
539
1016
|
// cerebro parses `--no-codegen`/`--no-studio` as the negation of the
|
|
540
1017
|
// `codegen`/`studio` booleans (runtime key drops the `no-` prefix), so a
|
|
@@ -543,18 +1020,10 @@ const execute = defineHandler(
|
|
|
543
1020
|
cwd,
|
|
544
1021
|
logger,
|
|
545
1022
|
port: options.port,
|
|
546
|
-
|
|
547
|
-
// flag wins, then `LUNORA_REMOTE` in the environment, then the `remote`
|
|
548
|
-
// key in the project's `lunora.json` (a project default). See
|
|
549
|
-
// `resolveRemoteEnabled` in @lunora/config.
|
|
550
|
-
remote: resolveRemoteEnabled({
|
|
551
|
-
configPreference: readProjectRemotePreference(cwd),
|
|
552
|
-
envValue: process.env["LUNORA_REMOTE"],
|
|
553
|
-
flag: options.remote
|
|
554
|
-
}),
|
|
1023
|
+
remote,
|
|
555
1024
|
studio: options.studio === false ? false : void 0,
|
|
556
1025
|
workerPort: options.workerPort
|
|
557
|
-
})
|
|
558
|
-
);
|
|
1026
|
+
});
|
|
1027
|
+
});
|
|
559
1028
|
|
|
560
|
-
export { execute, planDevCommand, resolveRemotePlan, runDevCommand };
|
|
1029
|
+
export { detectDevFlavor, execute, planDevCommand, resolveRemotePlan, runDevCommand };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { runCodegen } from '@lunora/codegen';
|
|
2
|
-
import { p as parseApiSpec } from '../packem_shared/api-spec-
|
|
3
|
-
import { d as defineHandler } from '../packem_shared/command-
|
|
4
|
-
import { v as validateOutputFormat, p as printJson, l as loggerForFormat, i as isJsonFormat } from '../packem_shared/output-format-
|
|
2
|
+
import { p as parseApiSpec } from '../packem_shared/api-spec-Bx0iKbxA.mjs';
|
|
3
|
+
import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
|
|
4
|
+
import { v as validateOutputFormat, p as printJson, l as loggerForFormat, i as isJsonFormat } from '../packem_shared/output-format-B4642rjE.mjs';
|
|
5
5
|
|
|
6
6
|
const CRON_TRIGGER_LIMIT = 3;
|
|
7
7
|
const runCodegenCommand = (options) => {
|
|
@@ -4,14 +4,14 @@ import { readLinkedProject, writeLinkedProject, validateWranglerProject, inferLu
|
|
|
4
4
|
import { join } from '@visulima/path';
|
|
5
5
|
import { Spinner } from '@visulima/spinner';
|
|
6
6
|
import { Project } from 'ts-morph';
|
|
7
|
-
import { p as parseApiSpec } from '../packem_shared/api-spec-
|
|
7
|
+
import { p as parseApiSpec } from '../packem_shared/api-spec-Bx0iKbxA.mjs';
|
|
8
8
|
import { r as readWranglerName } from '../packem_shared/wrangler-name-cy4yhm9j.mjs';
|
|
9
|
-
import { d as defineHandler } from '../packem_shared/command-
|
|
9
|
+
import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
|
|
10
10
|
import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-Cxo6Tpyx.mjs';
|
|
11
11
|
import { a as isRailpackAvailable, i as isDockerAvailable } from '../packem_shared/docker-hMQ97KSQ.mjs';
|
|
12
|
-
import { v as validateOutputFormat, i as isJsonFormat, p as printJson, l as loggerForFormat } from '../packem_shared/output-format-
|
|
12
|
+
import { v as validateOutputFormat, i as isJsonFormat, p as printJson, l as loggerForFormat } from '../packem_shared/output-format-B4642rjE.mjs';
|
|
13
13
|
import { containerBuildTag } from '@lunora/container';
|
|
14
|
-
import { defaultSpawner } from '../packem_shared/createRecordingSpawner-
|
|
14
|
+
import { defaultSpawner } from '../packem_shared/createRecordingSpawner-Cw5Iu73G.mjs';
|
|
15
15
|
import { r as resolveWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
|
|
16
16
|
import { r as runSchemaDriftGate } from '../packem_shared/schema-drift-gate-BtBt0as0.mjs';
|
|
17
17
|
import { c as createTuiConfirm } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
|
|
@@ -6,16 +6,16 @@ import { join as join$1, dirname as dirname$1, basename, resolve, relative } fro
|
|
|
6
6
|
import { downloadTemplate } from 'giget';
|
|
7
7
|
import { modify, applyEdits } from 'jsonc-parser';
|
|
8
8
|
import { join, dirname } from 'node:path';
|
|
9
|
-
import { d as defineHandler } from '../packem_shared/command-
|
|
9
|
+
import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
|
|
10
10
|
import { d as detectPackageManager, a as detectInstalledManagers, i as installArgsFor, r as runScriptCommand } from '../packem_shared/detect-package-manager-Cxo6Tpyx.mjs';
|
|
11
11
|
import MagicString from 'magic-string';
|
|
12
12
|
import { Project, SyntaxKind } from 'ts-morph';
|
|
13
13
|
import { P as PromptCancelledError } from '../packem_shared/prompt-cancelled-APzX1Im-.mjs';
|
|
14
|
-
import { c as resolveTagVersions, d as resolveSourceRef, e as resolvePinnedSourceRef, f as resolveDistTag, r as runAddCommand } from '../packem_shared/commands-
|
|
15
|
-
import { defaultSpawner } from '../packem_shared/createRecordingSpawner-
|
|
14
|
+
import { c as resolveTagVersions, d as resolveSourceRef, e as resolvePinnedSourceRef, f as resolveDistTag, r as runAddCommand } from '../packem_shared/commands-vnRRkhNE.mjs';
|
|
15
|
+
import { defaultSpawner } from '../packem_shared/createRecordingSpawner-Cw5Iu73G.mjs';
|
|
16
16
|
import { d as tuiMascot, e as tuiStep, f as tuiMoonrise, t as tuiText, g as tuiHeadline, h as tuiInfo, a as tuiSelect, b as tuiConfirm, w as withTuiSpinner, i as tuiNextSteps, j as tuiTasks, k as tuiMultiSelect, l as withTuiBadgeProgress } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
|
|
17
|
-
import { logStep } from '../packem_shared/createLogger-
|
|
18
|
-
import { E as EMAIL_ITEM, p as promptBucketName, w as withStorageBucketName, M as MAIL_DESTINATION_PROMPT, r as resolveTypedDestination, f as withMailDestination, e as promptAuthProvider, c as promptDatabaseName, g as withAuthDatabaseName } from '../packem_shared/storage-
|
|
17
|
+
import { logStep } from '../packem_shared/createLogger-CIWSHrTL.mjs';
|
|
18
|
+
import { E as EMAIL_ITEM, p as promptBucketName, w as withStorageBucketName, M as MAIL_DESTINATION_PROMPT, r as resolveTypedDestination, f as withMailDestination, e as promptAuthProvider, c as promptDatabaseName, g as withAuthDatabaseName } from '../packem_shared/storage-bGfgwOgF.mjs';
|
|
19
19
|
import dns from 'node:dns/promises';
|
|
20
20
|
|
|
21
21
|
const GITHUB_CONTENT = `name: Deploy
|