@launchmatic/cli 0.7.1 → 0.7.3

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.
Files changed (2) hide show
  1. package/dist/index.js +151 -37
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -21963,13 +21963,16 @@ function discoverServices(repoRoot = findRepoRoot()) {
21963
21963
  for (const absDir of dirs) {
21964
21964
  if (!isLikelyDeployable(absDir)) continue;
21965
21965
  const detection = detectLocal(absDir);
21966
+ const rootDir = toPosix(relative(repoRoot, absDir));
21967
+ const dockerfile = existsSync3(join2(absDir, "Dockerfile")) ? `${rootDir}/Dockerfile` : void 0;
21966
21968
  out.push({
21967
21969
  name: deriveName(repoRoot, absDir),
21968
- rootDir: toPosix(relative(repoRoot, absDir)),
21970
+ rootDir,
21969
21971
  framework: detection.framework,
21970
21972
  buildCmd: detection.buildCmd,
21971
21973
  startCmd: detection.startCmd,
21972
- port: detection.port
21974
+ port: detection.port,
21975
+ ...dockerfile ? { dockerfile } : {}
21973
21976
  });
21974
21977
  }
21975
21978
  out.sort((a, b) => a.rootDir.localeCompare(b.rootDir));
@@ -22427,42 +22430,124 @@ async function pollDeploymentStatus(deploymentId, spinner) {
22427
22430
  }
22428
22431
 
22429
22432
  // src/commands/logs.ts
22433
+ function resolveServiceId(opts) {
22434
+ try {
22435
+ return readContext().serviceId;
22436
+ } catch {
22437
+ }
22438
+ const manifest = readManifest();
22439
+ if (!manifest || manifest.services.length === 0) {
22440
+ console.error(
22441
+ source_default.red(
22442
+ `No .launchmatic.json here and no ${MANIFEST_FILE} at the repo root.`
22443
+ )
22444
+ );
22445
+ return null;
22446
+ }
22447
+ if (!opts.service) {
22448
+ const names = manifest.services.map((s) => s.name).join(", ");
22449
+ console.error(
22450
+ source_default.red(
22451
+ `Specify which service with --service <name>. Known: ${names}`
22452
+ )
22453
+ );
22454
+ return null;
22455
+ }
22456
+ const match = manifest.services.find((s) => s.name === opts.service);
22457
+ if (!match?.serviceId) {
22458
+ console.error(source_default.red(`No service named "${opts.service}" in ${MANIFEST_FILE}.`));
22459
+ return null;
22460
+ }
22461
+ return match.serviceId;
22462
+ }
22430
22463
  function registerLogs(program3) {
22431
- program3.command("logs").description("Stream live logs from a deployment").action(async () => {
22432
- if (!isLoggedIn()) {
22433
- console.error(source_default.red('Not logged in. Run "lm login" first.'));
22434
- process.exitCode = 1;
22435
- return;
22464
+ program3.command("logs").description(
22465
+ "Stream runtime logs (default) or print build logs for a deployment"
22466
+ ).option(
22467
+ "-b, --build",
22468
+ "Print build logs for a deployment instead of streaming runtime logs (use --deployment to target a specific one; defaults to the latest)"
22469
+ ).option(
22470
+ "-d, --deployment <id>",
22471
+ "Deployment id for --build (defaults to the latest deployment)"
22472
+ ).option(
22473
+ "-s, --service <name>",
22474
+ "Monorepo: which service to read logs for (matches launchmatic.json)"
22475
+ ).action(
22476
+ async (opts) => {
22477
+ if (!isLoggedIn()) {
22478
+ console.error(source_default.red('Not logged in. Run "lm login" first.'));
22479
+ process.exitCode = 1;
22480
+ return;
22481
+ }
22482
+ const serviceId = resolveServiceId({ service: opts.service });
22483
+ if (!serviceId) {
22484
+ process.exitCode = 1;
22485
+ return;
22486
+ }
22487
+ if (opts.build) {
22488
+ await printBuildLogs(serviceId, opts.deployment);
22489
+ return;
22490
+ }
22491
+ await streamRuntimeLogs(serviceId);
22436
22492
  }
22437
- const ctx = readContext();
22438
- const apiUrl = getApiUrl();
22439
- const wsUrl = apiUrl.replace(/^http/, "ws");
22440
- const wsToken = await getWsToken();
22441
- console.log(source_default.dim("Streaming logs (Ctrl+C to stop)...\n"));
22442
- const ws = new wrapper_default(
22443
- `${wsUrl}/ws/runtime-logs/${ctx.serviceId}?token=${wsToken}`
22493
+ );
22494
+ }
22495
+ async function printBuildLogs(serviceId, deploymentId) {
22496
+ let targetId = deploymentId;
22497
+ if (!targetId) {
22498
+ const { data: data2 } = await api(
22499
+ `/api/deployments?serviceId=${serviceId}&page=1&limit=1`
22444
22500
  );
22445
- ws.on("message", (data) => {
22446
- const msg = data.toString();
22447
- try {
22448
- const parsed = JSON.parse(msg);
22449
- const ts = parsed.timestamp ? source_default.dim(`[${new Date(parsed.timestamp).toLocaleTimeString()}]`) : "";
22450
- const line = parsed.message || parsed.line || msg;
22451
- console.log(`${ts} ${line}`);
22452
- } catch {
22453
- console.log(msg);
22454
- }
22455
- });
22456
- ws.on("error", (err) => {
22457
- console.error(source_default.red(`WebSocket error: ${err.message}`));
22458
- process.exitCode = 1;
22459
- });
22460
- ws.on("close", () => {
22461
- console.log(source_default.dim("\nLog stream ended."));
22462
- });
22463
- process.on("SIGINT", () => {
22464
- ws.close();
22465
- });
22501
+ if (!data2 || data2.length === 0) {
22502
+ console.log(source_default.dim("No deployments yet."));
22503
+ return;
22504
+ }
22505
+ targetId = data2[0].id;
22506
+ }
22507
+ const { data } = await api(
22508
+ `/api/deployments/${targetId}`
22509
+ );
22510
+ console.log(source_default.bold("Deployment: ") + data.id);
22511
+ console.log(source_default.bold("Status: ") + data.status);
22512
+ if (data.commitSha) console.log(source_default.bold("Commit: ") + data.commitSha);
22513
+ if (data.branch) console.log(source_default.bold("Branch: ") + data.branch);
22514
+ console.log(source_default.bold("Started: ") + new Date(data.createdAt).toLocaleString());
22515
+ console.log();
22516
+ if (!data.buildLogs) {
22517
+ console.log(source_default.dim("No build logs recorded for this deployment."));
22518
+ return;
22519
+ }
22520
+ console.log(source_default.bold("\u2500\u2500 Build logs \u2500\u2500"));
22521
+ console.log(data.buildLogs);
22522
+ }
22523
+ async function streamRuntimeLogs(serviceId) {
22524
+ const apiUrl = getApiUrl();
22525
+ const wsUrl = apiUrl.replace(/^http/, "ws");
22526
+ const wsToken = await getWsToken();
22527
+ console.log(source_default.dim("Streaming logs (Ctrl+C to stop)...\n"));
22528
+ const ws = new wrapper_default(
22529
+ `${wsUrl}/ws/runtime-logs/${serviceId}?token=${wsToken}`
22530
+ );
22531
+ ws.on("message", (data) => {
22532
+ const msg = data.toString();
22533
+ try {
22534
+ const parsed = JSON.parse(msg);
22535
+ const ts = parsed.timestamp ? source_default.dim(`[${new Date(parsed.timestamp).toLocaleTimeString()}]`) : "";
22536
+ const line = parsed.message || parsed.line || msg;
22537
+ console.log(`${ts} ${line}`);
22538
+ } catch {
22539
+ console.log(msg);
22540
+ }
22541
+ });
22542
+ ws.on("error", (err) => {
22543
+ console.error(source_default.red(`WebSocket error: ${err.message}`));
22544
+ process.exitCode = 1;
22545
+ });
22546
+ ws.on("close", () => {
22547
+ console.log(source_default.dim("\nLog stream ended."));
22548
+ });
22549
+ process.on("SIGINT", () => {
22550
+ ws.close();
22466
22551
  });
22467
22552
  }
22468
22553
 
@@ -25673,6 +25758,14 @@ function registerDeployments(program3) {
25673
25758
  if (data.imageTag) {
25674
25759
  console.log(source_default.bold("Image: ") + data.imageTag);
25675
25760
  }
25761
+ if (data.buildLogs) {
25762
+ console.log();
25763
+ console.log(source_default.bold("\u2500\u2500 Build logs \u2500\u2500"));
25764
+ console.log(data.buildLogs);
25765
+ } else {
25766
+ console.log();
25767
+ console.log(source_default.dim("(no build logs recorded)"));
25768
+ }
25676
25769
  } catch (err) {
25677
25770
  spinner.fail(source_default.red(`Failed: ${err instanceof Error ? err.message : "Unknown error"}`));
25678
25771
  process.exitCode = 1;
@@ -26328,10 +26421,15 @@ Discovered ${discoveredInfra.length} infra dependenc${discoveredInfra.length ===
26328
26421
  discovered = await reviewList(
26329
26422
  `services`,
26330
26423
  discovered,
26331
- (s) => `${source_default.cyan(s.name)} ${source_default.dim(s.rootDir)}${s.framework ? source_default.dim(` [${s.framework}]`) : ""}`,
26424
+ (s) => {
26425
+ const fw = s.framework ? source_default.dim(` [${s.framework}]`) : "";
26426
+ const df = s.dockerfile ? source_default.dim(` Dockerfile=${s.dockerfile}`) : "";
26427
+ return `${source_default.cyan(s.name)} ${source_default.dim(s.rootDir)}${fw}${df}`;
26428
+ },
26332
26429
  [
26333
26430
  { key: "name", label: "name" },
26334
26431
  { key: "rootDir", label: "rootDir" },
26432
+ { key: "dockerfile", label: "dockerfile (path from repo root)" },
26335
26433
  { key: "framework", label: "framework" },
26336
26434
  { key: "buildCmd", label: "buildCmd" },
26337
26435
  { key: "startCmd", label: "startCmd" },
@@ -26426,6 +26524,7 @@ Discovered ${discoveredInfra.length} infra dependenc${discoveredInfra.length ===
26426
26524
  if (s.framework) payload.framework = s.framework;
26427
26525
  if (s.buildCmd) payload.buildCmd = s.buildCmd;
26428
26526
  if (s.startCmd) payload.startCmd = s.startCmd;
26527
+ if (s.dockerfile) payload.dockerfilePath = s.dockerfile;
26429
26528
  if (gitInfo.repoOwner && gitInfo.repoName) {
26430
26529
  payload.repoOwner = gitInfo.repoOwner;
26431
26530
  payload.repoName = gitInfo.repoName;
@@ -26453,7 +26552,8 @@ Discovered ${discoveredInfra.length} infra dependenc${discoveredInfra.length ===
26453
26552
  framework: s.framework,
26454
26553
  buildCmd: s.buildCmd,
26455
26554
  startCmd: s.startCmd,
26456
- port: s.port
26555
+ port: s.port,
26556
+ ...s.dockerfile ? { dockerfile: s.dockerfile } : {}
26457
26557
  });
26458
26558
  }
26459
26559
  const infra = [];
@@ -26612,6 +26712,20 @@ async function up(opts) {
26612
26712
  console.log();
26613
26713
  const results = await Promise.allSettled(
26614
26714
  selected.map(async ({ entry }) => {
26715
+ const patch = {};
26716
+ if (entry.dockerfile !== void 0) patch.dockerfilePath = entry.dockerfile || null;
26717
+ if (entry.rootDir) patch.rootDir = `/${entry.rootDir.replace(/^\/+/, "")}`;
26718
+ if (entry.buildCmd) patch.buildCmd = entry.buildCmd;
26719
+ if (entry.startCmd) patch.startCmd = entry.startCmd;
26720
+ if (Object.keys(patch).length > 0) {
26721
+ try {
26722
+ await api(`/api/services/${entry.serviceId}`, {
26723
+ method: "PATCH",
26724
+ body: JSON.stringify(patch)
26725
+ });
26726
+ } catch {
26727
+ }
26728
+ }
26615
26729
  const res = await api("/api/deployments", {
26616
26730
  method: "POST",
26617
26731
  body: JSON.stringify({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@launchmatic/cli",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "Launchmatic CLI — deploy from your terminal",
5
5
  "private": false,
6
6
  "type": "module",