@launchmatic/cli 0.6.2 → 0.6.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 +360 -304
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -22240,58 +22240,246 @@ function registerDomains(program3) {
22240
22240
  });
22241
22241
  }
22242
22242
 
22243
+ // src/monorepo.ts
22244
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync, writeFileSync as writeFileSync3 } from "fs";
22245
+ import { execFileSync as execFileSync5 } from "child_process";
22246
+ import { join as join2, relative, sep, posix } from "path";
22247
+ var MANIFEST_FILE = "launchmatic.json";
22248
+ function findRepoRoot(start = process.cwd()) {
22249
+ try {
22250
+ return execFileSync5("git", ["rev-parse", "--show-toplevel"], {
22251
+ cwd: start,
22252
+ encoding: "utf-8",
22253
+ stdio: ["pipe", "pipe", "pipe"]
22254
+ }).trim();
22255
+ } catch {
22256
+ return start;
22257
+ }
22258
+ }
22259
+ function manifestPath(repoRoot = findRepoRoot()) {
22260
+ return join2(repoRoot, MANIFEST_FILE);
22261
+ }
22262
+ function readManifest(repoRoot = findRepoRoot()) {
22263
+ const p = manifestPath(repoRoot);
22264
+ if (!existsSync3(p)) return null;
22265
+ try {
22266
+ const parsed = JSON.parse(readFileSync3(p, "utf-8"));
22267
+ if (parsed.version !== 1 || !Array.isArray(parsed.services)) {
22268
+ throw new Error(`${MANIFEST_FILE} has unexpected shape`);
22269
+ }
22270
+ return parsed;
22271
+ } catch (err) {
22272
+ throw new Error(`Could not read ${MANIFEST_FILE}: ${err instanceof Error ? err.message : String(err)}`);
22273
+ }
22274
+ }
22275
+ function writeManifest(manifest, repoRoot = findRepoRoot()) {
22276
+ writeFileSync3(manifestPath(repoRoot), JSON.stringify(manifest, null, 2) + "\n");
22277
+ }
22278
+ function discoverServices(repoRoot = findRepoRoot()) {
22279
+ const globs = readWorkspaceGlobs(repoRoot);
22280
+ const dirs = /* @__PURE__ */ new Set();
22281
+ for (const glob of globs) {
22282
+ for (const dir of expandGlob(repoRoot, glob)) {
22283
+ dirs.add(dir);
22284
+ }
22285
+ }
22286
+ if (globs.length === 0) {
22287
+ for (const conv of ["apps", "services"]) {
22288
+ const base = join2(repoRoot, conv);
22289
+ if (existsSync3(base) && statSync(base).isDirectory()) {
22290
+ for (const entry of readdirSync(base)) {
22291
+ const full = join2(base, entry);
22292
+ if (statSync(full).isDirectory()) dirs.add(full);
22293
+ }
22294
+ }
22295
+ }
22296
+ }
22297
+ const out = [];
22298
+ for (const absDir of dirs) {
22299
+ if (!isLikelyDeployable(absDir)) continue;
22300
+ const detection = detectLocal(absDir);
22301
+ out.push({
22302
+ name: deriveName(repoRoot, absDir),
22303
+ rootDir: toPosix(relative(repoRoot, absDir)),
22304
+ framework: detection.framework,
22305
+ buildCmd: detection.buildCmd,
22306
+ startCmd: detection.startCmd,
22307
+ port: detection.port
22308
+ });
22309
+ }
22310
+ out.sort((a, b) => a.rootDir.localeCompare(b.rootDir));
22311
+ return out;
22312
+ }
22313
+ function readWorkspaceGlobs(repoRoot) {
22314
+ const globs = [];
22315
+ const pnpmFile = join2(repoRoot, "pnpm-workspace.yaml");
22316
+ if (existsSync3(pnpmFile)) {
22317
+ const text = readFileSync3(pnpmFile, "utf-8");
22318
+ let inPackages = false;
22319
+ for (const rawLine of text.split(/\r?\n/)) {
22320
+ const line = rawLine.replace(/#.*$/, "").trimEnd();
22321
+ if (/^packages\s*:/i.test(line)) {
22322
+ inPackages = true;
22323
+ continue;
22324
+ }
22325
+ if (inPackages) {
22326
+ const m = line.match(/^\s*-\s*['"]?([^'"#]+?)['"]?\s*$/);
22327
+ if (m) {
22328
+ globs.push(m[1].trim());
22329
+ continue;
22330
+ }
22331
+ if (line.trim() && !line.startsWith(" ") && !line.startsWith(" ")) {
22332
+ inPackages = false;
22333
+ }
22334
+ }
22335
+ }
22336
+ }
22337
+ const pkgFile = join2(repoRoot, "package.json");
22338
+ if (existsSync3(pkgFile)) {
22339
+ try {
22340
+ const pkg2 = JSON.parse(readFileSync3(pkgFile, "utf-8"));
22341
+ if (Array.isArray(pkg2.workspaces)) {
22342
+ globs.push(...pkg2.workspaces.filter((g) => typeof g === "string"));
22343
+ } else if (pkg2.workspaces && Array.isArray(pkg2.workspaces.packages)) {
22344
+ globs.push(...pkg2.workspaces.packages.filter((g) => typeof g === "string"));
22345
+ }
22346
+ } catch {
22347
+ }
22348
+ }
22349
+ return globs;
22350
+ }
22351
+ function expandGlob(repoRoot, glob) {
22352
+ const cleaned = glob.replace(/^\.\//, "").replace(/\/$/, "");
22353
+ if (cleaned.endsWith("/*")) {
22354
+ const base = join2(repoRoot, cleaned.slice(0, -2));
22355
+ if (!existsSync3(base) || !statSync(base).isDirectory()) return [];
22356
+ return readdirSync(base).map((entry) => join2(base, entry)).filter((p) => {
22357
+ try {
22358
+ return statSync(p).isDirectory();
22359
+ } catch {
22360
+ return false;
22361
+ }
22362
+ });
22363
+ }
22364
+ const abs = join2(repoRoot, cleaned);
22365
+ if (existsSync3(abs) && statSync(abs).isDirectory()) return [abs];
22366
+ return [];
22367
+ }
22368
+ function isLikelyDeployable(absDir) {
22369
+ if (existsSync3(join2(absDir, "Dockerfile"))) return true;
22370
+ const pkgPath = join2(absDir, "package.json");
22371
+ if (existsSync3(pkgPath)) {
22372
+ try {
22373
+ const pkg2 = JSON.parse(readFileSync3(pkgPath, "utf-8"));
22374
+ if (pkg2.scripts?.start || pkg2.scripts?.dev || pkg2.scripts?.serve) return true;
22375
+ if (pkg2.bin) return true;
22376
+ } catch {
22377
+ }
22378
+ }
22379
+ if (existsSync3(join2(absDir, "next.config.js")) || existsSync3(join2(absDir, "next.config.mjs")) || existsSync3(join2(absDir, "next.config.ts")) || existsSync3(join2(absDir, "go.mod")) || existsSync3(join2(absDir, "Cargo.toml")) || existsSync3(join2(absDir, "manage.py")) || existsSync3(join2(absDir, "pyproject.toml")) || existsSync3(join2(absDir, "Gemfile"))) {
22380
+ return true;
22381
+ }
22382
+ return false;
22383
+ }
22384
+ function deriveName(repoRoot, absDir) {
22385
+ const rel = toPosix(relative(repoRoot, absDir));
22386
+ const parts = rel.split("/");
22387
+ return parts[parts.length - 1].toLowerCase().replace(/[^a-z0-9-]/g, "-");
22388
+ }
22389
+ function toPosix(p) {
22390
+ return p.split(sep).join(posix.sep);
22391
+ }
22392
+ function changedFilesSince(repoRoot, baseRef) {
22393
+ const ref = baseRef ?? autoBaseRef(repoRoot);
22394
+ if (!ref) return [];
22395
+ try {
22396
+ const out = execFileSync5("git", ["diff", "--name-only", `${ref}..HEAD`], {
22397
+ cwd: repoRoot,
22398
+ encoding: "utf-8",
22399
+ stdio: ["pipe", "pipe", "pipe"]
22400
+ });
22401
+ return out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
22402
+ } catch {
22403
+ return [];
22404
+ }
22405
+ }
22406
+ function autoBaseRef(repoRoot) {
22407
+ try {
22408
+ const branch = execFileSync5("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
22409
+ cwd: repoRoot,
22410
+ encoding: "utf-8",
22411
+ stdio: ["pipe", "pipe", "pipe"]
22412
+ }).trim();
22413
+ if (branch && branch !== "HEAD") {
22414
+ try {
22415
+ execFileSync5("git", ["rev-parse", "--verify", `origin/${branch}`], {
22416
+ cwd: repoRoot,
22417
+ stdio: ["pipe", "pipe", "pipe"]
22418
+ });
22419
+ return `origin/${branch}`;
22420
+ } catch {
22421
+ }
22422
+ }
22423
+ } catch {
22424
+ }
22425
+ try {
22426
+ execFileSync5("git", ["rev-parse", "--verify", "HEAD~1"], {
22427
+ cwd: repoRoot,
22428
+ stdio: ["pipe", "pipe", "pipe"]
22429
+ });
22430
+ return "HEAD~1";
22431
+ } catch {
22432
+ return null;
22433
+ }
22434
+ }
22435
+ function serviceWasChanged(rootDir, changedPaths) {
22436
+ if (changedPaths.length === 0) return true;
22437
+ const normalized = rootDir.replace(/\\/g, "/").replace(/^\.?\//, "").replace(/\/$/, "");
22438
+ if (normalized === "" || normalized === "." || normalized === "/") return true;
22439
+ const prefix = normalized + "/";
22440
+ return changedPaths.some((p) => p === normalized || p.startsWith(prefix));
22441
+ }
22442
+
22243
22443
  // src/commands/status.ts
22244
22444
  function registerStatus(program3) {
22245
- program3.command("status").description("Show service and deployment status").action(async () => {
22445
+ program3.command("status").description("Show service and deployment status").option(
22446
+ "-s, --service <name>",
22447
+ "In a monorepo, show status for a single service (by name)"
22448
+ ).action(async (opts) => {
22246
22449
  if (!isLoggedIn()) {
22247
22450
  console.error(source_default.red('Not logged in. Run "lm login" first.'));
22248
22451
  process.exitCode = 1;
22249
22452
  return;
22250
22453
  }
22251
- const ctx = readContext();
22454
+ const manifest = readManifest();
22455
+ if (manifest && manifest.services.length > 0) {
22456
+ await renderMonorepoStatus(manifest.services, opts.service);
22457
+ return;
22458
+ }
22459
+ let ctx;
22252
22460
  try {
22253
- const { data } = await api(`/api/services/${ctx.serviceId}`);
22254
- console.log();
22255
- console.log(` ${source_default.bold("Service:")} ${data.name}`);
22256
- console.log(
22257
- ` ${source_default.bold("Status:")} ${statusColor(data.status)}`
22461
+ ctx = readContext();
22462
+ } catch (err) {
22463
+ console.error(
22464
+ source_default.red(
22465
+ err instanceof Error ? err.message : "No Launchmatic context found."
22466
+ )
22258
22467
  );
22259
- if (data.subdomain) {
22260
- console.log(
22261
- ` ${source_default.bold("URL:")} ${source_default.cyan(`https://${data.subdomain}`)}`
22262
- );
22263
- }
22264
- if (data.runtime) {
22265
- console.log(` ${source_default.bold("Runtime:")} ${data.runtime}`);
22266
- }
22267
- if (data.deployments && data.deployments.length > 0) {
22268
- const last = data.deployments[0];
22269
- console.log();
22270
- console.log(source_default.bold(" Last deployment:"));
22271
- console.log(
22272
- ` Status: ${statusColor(last.status)}`
22273
- );
22274
- if (last.branch) {
22275
- console.log(` Branch: ${last.branch}`);
22276
- }
22277
- if (last.commitSha) {
22278
- console.log(
22279
- ` Commit: ${last.commitSha.slice(0, 7)}`
22280
- );
22281
- }
22282
- console.log(
22283
- ` Time: ${new Date(last.createdAt).toLocaleString()}`
22284
- );
22285
- }
22286
- if (data.domains && data.domains.length > 0) {
22287
- console.log();
22288
- console.log(source_default.bold(" Domains:"));
22289
- for (const d of data.domains) {
22290
- const ssl = d.sslStatus === "ACTIVE" ? source_default.green("\u2713 SSL") : source_default.yellow("\u23F3 SSL");
22291
- console.log(` ${d.hostname} ${ssl}`);
22292
- }
22293
- }
22294
- console.log();
22468
+ console.error(
22469
+ source_default.dim(
22470
+ `Run ${source_default.bold("lm init")} to bind a service, or ${source_default.bold(
22471
+ "lm monorepo init"
22472
+ )} to create a ${MANIFEST_FILE} for multi-service repos.`
22473
+ )
22474
+ );
22475
+ process.exitCode = 1;
22476
+ return;
22477
+ }
22478
+ try {
22479
+ const { data } = await api(
22480
+ `/api/services/${ctx.serviceId}`
22481
+ );
22482
+ renderServiceBlock(data, { indent: " " });
22295
22483
  } catch (err) {
22296
22484
  console.error(
22297
22485
  source_default.red(
@@ -22299,10 +22487,80 @@ function registerStatus(program3) {
22299
22487
  )
22300
22488
  );
22301
22489
  process.exitCode = 1;
22302
- return;
22303
22490
  }
22304
22491
  });
22305
22492
  }
22493
+ async function renderMonorepoStatus(services, filter) {
22494
+ const targets = filter ? services.filter((s) => s.name === filter) : services;
22495
+ if (targets.length === 0) {
22496
+ console.error(
22497
+ source_default.red(
22498
+ filter ? `No service named "${filter}" in ${MANIFEST_FILE}.` : `No services declared in ${MANIFEST_FILE}.`
22499
+ )
22500
+ );
22501
+ process.exitCode = 1;
22502
+ return;
22503
+ }
22504
+ console.log();
22505
+ console.log(
22506
+ source_default.bold(`Monorepo`) + source_default.dim(` \u2014 ${targets.length} of ${services.length} service${services.length === 1 ? "" : "s"}`)
22507
+ );
22508
+ const results = await Promise.allSettled(
22509
+ targets.map(async (s) => {
22510
+ if (!s.serviceId) {
22511
+ return { name: s.name, error: `no serviceId \u2014 re-run ${source_default.bold("lm monorepo init")}` };
22512
+ }
22513
+ const { data } = await api(
22514
+ `/api/services/${s.serviceId}`
22515
+ );
22516
+ return { name: s.name, data };
22517
+ })
22518
+ );
22519
+ for (const r of results) {
22520
+ console.log();
22521
+ if (r.status === "fulfilled") {
22522
+ if ("error" in r.value) {
22523
+ console.log(` ${source_default.cyan(r.value.name)} ${source_default.yellow(r.value.error)}`);
22524
+ } else {
22525
+ renderServiceBlock(r.value.data, { indent: " " });
22526
+ }
22527
+ } else {
22528
+ const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
22529
+ console.log(` ${source_default.red("\u2717")} ${source_default.dim(msg)}`);
22530
+ }
22531
+ }
22532
+ console.log();
22533
+ }
22534
+ function renderServiceBlock(data, { indent }) {
22535
+ console.log();
22536
+ console.log(`${indent}${source_default.bold("Service:")} ${data.name}`);
22537
+ console.log(`${indent}${source_default.bold("Status:")} ${statusColor(data.status)}`);
22538
+ if (data.subdomain) {
22539
+ console.log(
22540
+ `${indent}${source_default.bold("URL:")} ${source_default.cyan(`https://${data.subdomain}`)}`
22541
+ );
22542
+ }
22543
+ if (data.runtime) {
22544
+ console.log(`${indent}${source_default.bold("Runtime:")} ${data.runtime}`);
22545
+ }
22546
+ if (data.deployments && data.deployments.length > 0) {
22547
+ const last = data.deployments[0];
22548
+ console.log();
22549
+ console.log(`${indent}${source_default.bold("Last deployment:")}`);
22550
+ console.log(`${indent} Status: ${statusColor(last.status)}`);
22551
+ if (last.branch) console.log(`${indent} Branch: ${last.branch}`);
22552
+ if (last.commitSha) console.log(`${indent} Commit: ${last.commitSha.slice(0, 7)}`);
22553
+ console.log(`${indent} Time: ${new Date(last.createdAt).toLocaleString()}`);
22554
+ }
22555
+ if (data.domains && data.domains.length > 0) {
22556
+ console.log();
22557
+ console.log(`${indent}${source_default.bold("Domains:")}`);
22558
+ for (const d of data.domains) {
22559
+ const ssl = d.sslStatus === "ACTIVE" ? source_default.green("\u2713 SSL") : source_default.yellow("\u23F3 SSL");
22560
+ console.log(`${indent} ${d.hostname} ${ssl}`);
22561
+ }
22562
+ }
22563
+ }
22306
22564
  function statusColor(status) {
22307
22565
  switch (status.toUpperCase()) {
22308
22566
  case "ACTIVE":
@@ -22541,12 +22799,12 @@ function registerLightspeed(program3) {
22541
22799
  }
22542
22800
 
22543
22801
  // src/commands/quicklaunch.ts
22544
- import { execFileSync as execFileSync5 } from "child_process";
22802
+ import { execFileSync as execFileSync6 } from "child_process";
22545
22803
  import { basename } from "path";
22546
22804
 
22547
22805
  // src/connectors.ts
22548
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
22549
- import { join as join2 } from "path";
22806
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
22807
+ import { join as join3 } from "path";
22550
22808
  import { randomBytes } from "crypto";
22551
22809
  var CONNECTOR_REGISTRY = [
22552
22810
  // Stripe
@@ -22595,10 +22853,10 @@ function getConnector(envKey, appName) {
22595
22853
  function detectEnvVars(cwd) {
22596
22854
  const envFiles = [".env.example", ".env.local.example", ".env.sample", ".env"];
22597
22855
  for (const file of envFiles) {
22598
- const filePath = join2(cwd, file);
22599
- if (existsSync3(filePath)) {
22856
+ const filePath = join3(cwd, file);
22857
+ if (existsSync4(filePath)) {
22600
22858
  try {
22601
- const content = readFileSync3(filePath, "utf-8");
22859
+ const content = readFileSync4(filePath, "utf-8");
22602
22860
  const keys = [];
22603
22861
  for (const line of content.split("\n")) {
22604
22862
  const trimmed = line.trim();
@@ -22672,25 +22930,25 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
22672
22930
  const repoSpinner = ora("Creating GitHub repository...").start();
22673
22931
  try {
22674
22932
  try {
22675
- execFileSync5("git", ["rev-parse", "--git-dir"], {
22933
+ execFileSync6("git", ["rev-parse", "--git-dir"], {
22676
22934
  cwd,
22677
22935
  stdio: ["pipe", "pipe", "pipe"]
22678
22936
  });
22679
22937
  } catch {
22680
- execFileSync5("git", ["init"], { cwd, stdio: ["pipe", "pipe", "pipe"] });
22938
+ execFileSync6("git", ["init"], { cwd, stdio: ["pipe", "pipe", "pipe"] });
22681
22939
  }
22682
22940
  const { data: repoData } = await api(`/api/services/${ctx.serviceId}/create-repo`, {
22683
22941
  method: "POST",
22684
22942
  body: JSON.stringify({})
22685
22943
  });
22686
22944
  try {
22687
- execFileSync5("git", ["remote", "add", "origin", repoData.cloneUrl], {
22945
+ execFileSync6("git", ["remote", "add", "origin", repoData.cloneUrl], {
22688
22946
  cwd,
22689
22947
  stdio: ["pipe", "pipe", "pipe"]
22690
22948
  });
22691
22949
  } catch {
22692
22950
  try {
22693
- execFileSync5("git", ["remote", "set-url", "origin", repoData.cloneUrl], {
22951
+ execFileSync6("git", ["remote", "set-url", "origin", repoData.cloneUrl], {
22694
22952
  cwd,
22695
22953
  stdio: ["pipe", "pipe", "pipe"]
22696
22954
  });
@@ -22784,12 +23042,12 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
22784
23042
  const repoSpinner = ora("Creating GitHub repository...").start();
22785
23043
  try {
22786
23044
  try {
22787
- execFileSync5("git", ["rev-parse", "--git-dir"], {
23045
+ execFileSync6("git", ["rev-parse", "--git-dir"], {
22788
23046
  cwd,
22789
23047
  stdio: ["pipe", "pipe", "pipe"]
22790
23048
  });
22791
23049
  } catch {
22792
- execFileSync5("git", ["init"], {
23050
+ execFileSync6("git", ["init"], {
22793
23051
  cwd,
22794
23052
  stdio: ["pipe", "pipe", "pipe"]
22795
23053
  });
@@ -22799,13 +23057,13 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
22799
23057
  body: JSON.stringify({})
22800
23058
  });
22801
23059
  try {
22802
- execFileSync5("git", ["remote", "add", "origin", repoData.cloneUrl], {
23060
+ execFileSync6("git", ["remote", "add", "origin", repoData.cloneUrl], {
22803
23061
  cwd,
22804
23062
  stdio: ["pipe", "pipe", "pipe"]
22805
23063
  });
22806
23064
  } catch {
22807
23065
  try {
22808
- execFileSync5("git", ["remote", "set-url", "origin", repoData.cloneUrl], {
23066
+ execFileSync6("git", ["remote", "set-url", "origin", repoData.cloneUrl], {
22809
23067
  cwd,
22810
23068
  stdio: ["pipe", "pipe", "pipe"]
22811
23069
  });
@@ -22830,8 +23088,8 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
22830
23088
  if (gitInfo.hasUncommitted) {
22831
23089
  const commitSpinner = ora("Committing changes...").start();
22832
23090
  try {
22833
- execFileSync5("git", ["add", "-A"], { cwd, stdio: ["pipe", "pipe", "pipe"] });
22834
- execFileSync5("git", ["commit", "-m", "quicklaunch: initial deploy"], {
23091
+ execFileSync6("git", ["add", "-A"], { cwd, stdio: ["pipe", "pipe", "pipe"] });
23092
+ execFileSync6("git", ["commit", "-m", "quicklaunch: initial deploy"], {
22835
23093
  cwd,
22836
23094
  stdio: ["pipe", "pipe", "pipe"]
22837
23095
  });
@@ -22844,7 +23102,7 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
22844
23102
  if (gitInfo.hasUnpushed || !gitInfo.commitSha) {
22845
23103
  const pushSpinner = ora("Pushing to remote...").start();
22846
23104
  try {
22847
- execFileSync5("git", ["push", "-u", "origin", gitInfo.repoBranch], {
23105
+ execFileSync6("git", ["push", "-u", "origin", gitInfo.repoBranch], {
22848
23106
  cwd,
22849
23107
  stdio: ["pipe", "pipe", "pipe"]
22850
23108
  });
@@ -23024,22 +23282,22 @@ async function pollStatus(deploymentId, spinner) {
23024
23282
  }
23025
23283
 
23026
23284
  // src/commands/browser.ts
23027
- import { resolve as resolve2 } from "path";
23028
- import { existsSync as existsSync4, readdirSync } from "fs";
23029
- import { execFileSync as execFileSync6 } from "child_process";
23285
+ import { resolve as resolve3 } from "path";
23286
+ import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
23287
+ import { execFileSync as execFileSync7 } from "child_process";
23030
23288
  async function ensureChromium() {
23031
23289
  const existing = findChromium();
23032
23290
  if (existing) return existing;
23033
23291
  const spinner = ora("Downloading Chromium (one-time setup)...").start();
23034
23292
  try {
23035
- execFileSync6("npx", ["playwright", "install", "chromium"], {
23293
+ execFileSync7("npx", ["playwright", "install", "chromium"], {
23036
23294
  stdio: ["pipe", "pipe", "pipe"],
23037
23295
  timeout: 12e4
23038
23296
  });
23039
23297
  spinner.succeed("Chromium installed");
23040
23298
  } catch {
23041
23299
  try {
23042
- execFileSync6("npx", ["playwright-core", "install", "chromium"], {
23300
+ execFileSync7("npx", ["playwright-core", "install", "chromium"], {
23043
23301
  stdio: ["pipe", "pipe", "pipe"],
23044
23302
  timeout: 12e4
23045
23303
  });
@@ -23084,27 +23342,27 @@ function findChromium() {
23084
23342
  }
23085
23343
  try {
23086
23344
  const home = process.env.HOME || process.env.USERPROFILE || "";
23087
- const pwBrowsers = resolve2(home, ".cache", "ms-playwright");
23088
- if (existsSync4(pwBrowsers)) {
23089
- const dirs = readdirSync(pwBrowsers).filter((d) => d.startsWith("chromium-"));
23345
+ const pwBrowsers = resolve3(home, ".cache", "ms-playwright");
23346
+ if (existsSync5(pwBrowsers)) {
23347
+ const dirs = readdirSync2(pwBrowsers).filter((d) => d.startsWith("chromium-"));
23090
23348
  if (dirs.length > 0) {
23091
23349
  const latest = dirs.sort().pop();
23092
- const chromePath = process.platform === "win32" ? resolve2(pwBrowsers, latest, "chrome-win", "chrome.exe") : process.platform === "darwin" ? resolve2(pwBrowsers, latest, "chrome-mac", "Chromium.app", "Contents", "MacOS", "Chromium") : resolve2(pwBrowsers, latest, "chrome-linux", "chrome");
23093
- if (existsSync4(chromePath)) return chromePath;
23350
+ const chromePath = process.platform === "win32" ? resolve3(pwBrowsers, latest, "chrome-win", "chrome.exe") : process.platform === "darwin" ? resolve3(pwBrowsers, latest, "chrome-mac", "Chromium.app", "Contents", "MacOS", "Chromium") : resolve3(pwBrowsers, latest, "chrome-linux", "chrome");
23351
+ if (existsSync5(chromePath)) return chromePath;
23094
23352
  }
23095
23353
  }
23096
23354
  } catch {
23097
23355
  }
23098
23356
  for (const c of candidates) {
23099
- if (existsSync4(c)) return c;
23357
+ if (existsSync5(c)) return c;
23100
23358
  }
23101
23359
  if (process.platform !== "win32") {
23102
23360
  try {
23103
- return execFileSync6("which", ["google-chrome"], { encoding: "utf-8" }).trim();
23361
+ return execFileSync7("which", ["google-chrome"], { encoding: "utf-8" }).trim();
23104
23362
  } catch {
23105
23363
  }
23106
23364
  try {
23107
- return execFileSync6("which", ["chromium"], { encoding: "utf-8" }).trim();
23365
+ return execFileSync7("which", ["chromium"], { encoding: "utf-8" }).trim();
23108
23366
  } catch {
23109
23367
  }
23110
23368
  }
@@ -23143,7 +23401,7 @@ function registerBrowser(program3) {
23143
23401
  const page = await context.newPage();
23144
23402
  await page.goto(targetUrl, { waitUntil: "networkidle" });
23145
23403
  if (parseInt(opts.delay) > 0) await page.waitForTimeout(parseInt(opts.delay));
23146
- const output = resolve2(opts.output);
23404
+ const output = resolve3(opts.output);
23147
23405
  await page.screenshot({ path: output, fullPage: opts.full });
23148
23406
  await b.close();
23149
23407
  spinner.succeed(`Screenshot saved \u2192 ${source_default.cyan(output)}`);
@@ -23159,7 +23417,7 @@ function registerBrowser(program3) {
23159
23417
  const b = await launchBrowser(true);
23160
23418
  const page = await b.newPage();
23161
23419
  await page.goto(targetUrl, { waitUntil: "networkidle" });
23162
- const output = resolve2(opts.output);
23420
+ const output = resolve3(opts.output);
23163
23421
  await page.pdf({ path: output, format: opts.format, landscape: opts.landscape, printBackground: true });
23164
23422
  await b.close();
23165
23423
  spinner.succeed(`PDF saved \u2192 ${source_default.cyan(output)}`);
@@ -23368,7 +23626,7 @@ ${actions.map((a) => ` ${a}`).join("\n")}
23368
23626
  }
23369
23627
 
23370
23628
  // src/commands/repo.ts
23371
- import { execFileSync as execFileSync7 } from "child_process";
23629
+ import { execFileSync as execFileSync8 } from "child_process";
23372
23630
  function requireLogin() {
23373
23631
  if (!isLoggedIn()) {
23374
23632
  console.error(source_default.red('Not logged in. Run "lm login" first.'));
@@ -23399,7 +23657,7 @@ function openUrl(url) {
23399
23657
  const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
23400
23658
  const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
23401
23659
  try {
23402
- execFileSync7(cmd, args, { stdio: "pipe" });
23660
+ execFileSync8(cmd, args, { stdio: "pipe" });
23403
23661
  } catch {
23404
23662
  }
23405
23663
  }
@@ -23661,7 +23919,7 @@ function registerRepo(program3) {
23661
23919
  const args = ["clone", "--depth", "1"];
23662
23920
  if (opts.branch) args.push("--branch", opts.branch);
23663
23921
  args.push(url);
23664
- execFileSync7("git", args, { stdio: ["pipe", "pipe", "pipe"] });
23922
+ execFileSync8("git", args, { stdio: ["pipe", "pipe", "pipe"] });
23665
23923
  spinner.succeed(`Cloned ${source_default.cyan(fullName)}`);
23666
23924
  } catch (err) {
23667
23925
  spinner.fail(source_default.red(`Clone failed: ${err.message}`));
@@ -23673,7 +23931,7 @@ function registerRepo(program3) {
23673
23931
  const args = ["diff"];
23674
23932
  if (opts.staged) args.push("--staged");
23675
23933
  args.push("--stat");
23676
- const output = execFileSync7("git", args, { cwd: process.cwd(), encoding: "utf-8" });
23934
+ const output = execFileSync8("git", args, { cwd: process.cwd(), encoding: "utf-8" });
23677
23935
  if (!output.trim()) {
23678
23936
  console.log(source_default.dim("\n No changes.\n"));
23679
23937
  } else {
@@ -23688,7 +23946,7 @@ function registerRepo(program3) {
23688
23946
  try {
23689
23947
  const args = ["stash"];
23690
23948
  if (action) args.push(action);
23691
- const output = execFileSync7("git", args, { cwd: process.cwd(), encoding: "utf-8" });
23949
+ const output = execFileSync8("git", args, { cwd: process.cwd(), encoding: "utf-8" });
23692
23950
  console.log(output || source_default.dim("Done."));
23693
23951
  } catch (err) {
23694
23952
  console.error(source_default.red(`Failed: ${err.message}`));
@@ -24627,8 +24885,8 @@ function validateGeneratedApp(files, ctx = {}) {
24627
24885
  }
24628
24886
 
24629
24887
  // ../../packages/validator/dist/validate-disk.js
24630
- import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync } from "fs";
24631
- import { join as join3, relative } from "path";
24888
+ import { readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
24889
+ import { join as join4, relative as relative2 } from "path";
24632
24890
  var MAX_FILE_SIZE = 512 * 1024;
24633
24891
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next", "dist", "build", "__pycache__", "target"]);
24634
24892
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -24655,22 +24913,22 @@ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
24655
24913
  ".env.example"
24656
24914
  ]);
24657
24915
  function collectFiles(dir, rootDir, out) {
24658
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
24916
+ for (const entry of readdirSync3(dir, { withFileTypes: true })) {
24659
24917
  if (entry.name.startsWith(".") && entry.name !== ".env.example")
24660
24918
  continue;
24661
24919
  if (SKIP_DIRS.has(entry.name))
24662
24920
  continue;
24663
- const fullPath = join3(dir, entry.name);
24921
+ const fullPath = join4(dir, entry.name);
24664
24922
  if (entry.isDirectory()) {
24665
24923
  collectFiles(fullPath, rootDir, out);
24666
24924
  continue;
24667
24925
  }
24668
24926
  if (entry.name === "Dockerfile" || entry.name === "docker-compose.yml") {
24669
24927
  try {
24670
- const stat = statSync(fullPath);
24928
+ const stat = statSync2(fullPath);
24671
24929
  if (stat.size > MAX_FILE_SIZE)
24672
24930
  continue;
24673
- out.push({ path: relative(rootDir, fullPath).replace(/\\/g, "/"), content: readFileSync4(fullPath, "utf-8") });
24931
+ out.push({ path: relative2(rootDir, fullPath).replace(/\\/g, "/"), content: readFileSync5(fullPath, "utf-8") });
24674
24932
  } catch {
24675
24933
  }
24676
24934
  continue;
@@ -24679,10 +24937,10 @@ function collectFiles(dir, rootDir, out) {
24679
24937
  if (!SOURCE_EXTENSIONS.has(ext))
24680
24938
  continue;
24681
24939
  try {
24682
- const stat = statSync(fullPath);
24940
+ const stat = statSync2(fullPath);
24683
24941
  if (stat.size > MAX_FILE_SIZE)
24684
24942
  continue;
24685
- out.push({ path: relative(rootDir, fullPath).replace(/\\/g, "/"), content: readFileSync4(fullPath, "utf-8") });
24943
+ out.push({ path: relative2(rootDir, fullPath).replace(/\\/g, "/"), content: readFileSync5(fullPath, "utf-8") });
24686
24944
  } catch {
24687
24945
  }
24688
24946
  }
@@ -24694,12 +24952,12 @@ function validateFromDisk(sourceDir, ctx = {}) {
24694
24952
  }
24695
24953
 
24696
24954
  // src/commands/doctor.ts
24697
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
24698
- import { resolve as resolve3 } from "path";
24955
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
24956
+ import { resolve as resolve4 } from "path";
24699
24957
  function registerDoctor(program3) {
24700
24958
  program3.command("doctor").description("Scan project for common deployment issues").option("-d, --dir <path>", "Project directory to scan", ".").option("--json", "Output results as JSON").action(async (opts) => {
24701
- const dir = resolve3(opts.dir);
24702
- if (!existsSync5(dir)) {
24959
+ const dir = resolve4(opts.dir);
24960
+ if (!existsSync6(dir)) {
24703
24961
  console.error(source_default.red(`Directory not found: ${dir}`));
24704
24962
  process.exitCode = 1;
24705
24963
  return;
@@ -24723,18 +24981,18 @@ function registerDoctor(program3) {
24723
24981
  }
24724
24982
  function detectContext(dir) {
24725
24983
  const ctx = {};
24726
- const pkgPath = resolve3(dir, "package.json");
24727
- if (existsSync5(pkgPath)) {
24984
+ const pkgPath = resolve4(dir, "package.json");
24985
+ if (existsSync6(pkgPath)) {
24728
24986
  try {
24729
- const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
24987
+ const pkg2 = JSON.parse(readFileSync6(pkgPath, "utf-8"));
24730
24988
  const deps = { ...pkg2.dependencies, ...pkg2.devDependencies };
24731
24989
  if (deps.next) ctx.runtime = "nodejs";
24732
24990
  else if (deps.express || deps.fastify || deps.koa || deps.hapi) ctx.runtime = "nodejs";
24733
24991
  else if (deps.react || deps.vue || deps.svelte) ctx.runtime = "nodejs";
24734
24992
  else if (pkg2.type === "module" || deps.typescript) ctx.runtime = "nodejs";
24735
- if (existsSync5(resolve3(dir, "pnpm-lock.yaml"))) ctx.packageManager = "pnpm";
24736
- else if (existsSync5(resolve3(dir, "yarn.lock"))) ctx.packageManager = "yarn";
24737
- else if (existsSync5(resolve3(dir, "package-lock.json"))) ctx.packageManager = "npm";
24993
+ if (existsSync6(resolve4(dir, "pnpm-lock.yaml"))) ctx.packageManager = "pnpm";
24994
+ else if (existsSync6(resolve4(dir, "yarn.lock"))) ctx.packageManager = "yarn";
24995
+ else if (existsSync6(resolve4(dir, "package-lock.json"))) ctx.packageManager = "npm";
24738
24996
  const startScript = pkg2.scripts?.start || pkg2.scripts?.dev || "";
24739
24997
  const portMatch = startScript.match(/--port\s+(\d+)|-p\s+(\d+)/);
24740
24998
  if (portMatch) ctx.port = parseInt(portMatch[1] || portMatch[2]);
@@ -24743,11 +25001,11 @@ function detectContext(dir) {
24743
25001
  } catch {
24744
25002
  }
24745
25003
  }
24746
- if (existsSync5(resolve3(dir, "requirements.txt")) || existsSync5(resolve3(dir, "pyproject.toml"))) {
25004
+ if (existsSync6(resolve4(dir, "requirements.txt")) || existsSync6(resolve4(dir, "pyproject.toml"))) {
24747
25005
  ctx.runtime = "python";
24748
25006
  }
24749
- if (existsSync5(resolve3(dir, "go.mod"))) ctx.runtime = "go";
24750
- if (existsSync5(resolve3(dir, "Cargo.toml"))) ctx.runtime = "rust";
25007
+ if (existsSync6(resolve4(dir, "go.mod"))) ctx.runtime = "go";
25008
+ if (existsSync6(resolve4(dir, "Cargo.toml"))) ctx.runtime = "rust";
24751
25009
  return ctx;
24752
25010
  }
24753
25011
  function severityIcon(severity) {
@@ -25807,208 +26065,6 @@ import { execFileSync as execFileSync9 } from "child_process";
25807
26065
  import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
25808
26066
  import { join as join5 } from "path";
25809
26067
  import readline7 from "readline";
25810
-
25811
- // src/monorepo.ts
25812
- import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
25813
- import { execFileSync as execFileSync8 } from "child_process";
25814
- import { join as join4, relative as relative2, sep, posix } from "path";
25815
- var MANIFEST_FILE = "launchmatic.json";
25816
- function findRepoRoot(start = process.cwd()) {
25817
- try {
25818
- return execFileSync8("git", ["rev-parse", "--show-toplevel"], {
25819
- cwd: start,
25820
- encoding: "utf-8",
25821
- stdio: ["pipe", "pipe", "pipe"]
25822
- }).trim();
25823
- } catch {
25824
- return start;
25825
- }
25826
- }
25827
- function manifestPath(repoRoot = findRepoRoot()) {
25828
- return join4(repoRoot, MANIFEST_FILE);
25829
- }
25830
- function readManifest(repoRoot = findRepoRoot()) {
25831
- const p = manifestPath(repoRoot);
25832
- if (!existsSync6(p)) return null;
25833
- try {
25834
- const parsed = JSON.parse(readFileSync6(p, "utf-8"));
25835
- if (parsed.version !== 1 || !Array.isArray(parsed.services)) {
25836
- throw new Error(`${MANIFEST_FILE} has unexpected shape`);
25837
- }
25838
- return parsed;
25839
- } catch (err) {
25840
- throw new Error(`Could not read ${MANIFEST_FILE}: ${err instanceof Error ? err.message : String(err)}`);
25841
- }
25842
- }
25843
- function writeManifest(manifest, repoRoot = findRepoRoot()) {
25844
- writeFileSync3(manifestPath(repoRoot), JSON.stringify(manifest, null, 2) + "\n");
25845
- }
25846
- function discoverServices(repoRoot = findRepoRoot()) {
25847
- const globs = readWorkspaceGlobs(repoRoot);
25848
- const dirs = /* @__PURE__ */ new Set();
25849
- for (const glob of globs) {
25850
- for (const dir of expandGlob(repoRoot, glob)) {
25851
- dirs.add(dir);
25852
- }
25853
- }
25854
- if (globs.length === 0) {
25855
- for (const conv of ["apps", "services"]) {
25856
- const base = join4(repoRoot, conv);
25857
- if (existsSync6(base) && statSync2(base).isDirectory()) {
25858
- for (const entry of readdirSync3(base)) {
25859
- const full = join4(base, entry);
25860
- if (statSync2(full).isDirectory()) dirs.add(full);
25861
- }
25862
- }
25863
- }
25864
- }
25865
- const out = [];
25866
- for (const absDir of dirs) {
25867
- if (!isLikelyDeployable(absDir)) continue;
25868
- const detection = detectLocal(absDir);
25869
- out.push({
25870
- name: deriveName(repoRoot, absDir),
25871
- rootDir: toPosix(relative2(repoRoot, absDir)),
25872
- framework: detection.framework,
25873
- buildCmd: detection.buildCmd,
25874
- startCmd: detection.startCmd,
25875
- port: detection.port
25876
- });
25877
- }
25878
- out.sort((a, b) => a.rootDir.localeCompare(b.rootDir));
25879
- return out;
25880
- }
25881
- function readWorkspaceGlobs(repoRoot) {
25882
- const globs = [];
25883
- const pnpmFile = join4(repoRoot, "pnpm-workspace.yaml");
25884
- if (existsSync6(pnpmFile)) {
25885
- const text = readFileSync6(pnpmFile, "utf-8");
25886
- let inPackages = false;
25887
- for (const rawLine of text.split(/\r?\n/)) {
25888
- const line = rawLine.replace(/#.*$/, "").trimEnd();
25889
- if (/^packages\s*:/i.test(line)) {
25890
- inPackages = true;
25891
- continue;
25892
- }
25893
- if (inPackages) {
25894
- const m = line.match(/^\s*-\s*['"]?([^'"#]+?)['"]?\s*$/);
25895
- if (m) {
25896
- globs.push(m[1].trim());
25897
- continue;
25898
- }
25899
- if (line.trim() && !line.startsWith(" ") && !line.startsWith(" ")) {
25900
- inPackages = false;
25901
- }
25902
- }
25903
- }
25904
- }
25905
- const pkgFile = join4(repoRoot, "package.json");
25906
- if (existsSync6(pkgFile)) {
25907
- try {
25908
- const pkg2 = JSON.parse(readFileSync6(pkgFile, "utf-8"));
25909
- if (Array.isArray(pkg2.workspaces)) {
25910
- globs.push(...pkg2.workspaces.filter((g) => typeof g === "string"));
25911
- } else if (pkg2.workspaces && Array.isArray(pkg2.workspaces.packages)) {
25912
- globs.push(...pkg2.workspaces.packages.filter((g) => typeof g === "string"));
25913
- }
25914
- } catch {
25915
- }
25916
- }
25917
- return globs;
25918
- }
25919
- function expandGlob(repoRoot, glob) {
25920
- const cleaned = glob.replace(/^\.\//, "").replace(/\/$/, "");
25921
- if (cleaned.endsWith("/*")) {
25922
- const base = join4(repoRoot, cleaned.slice(0, -2));
25923
- if (!existsSync6(base) || !statSync2(base).isDirectory()) return [];
25924
- return readdirSync3(base).map((entry) => join4(base, entry)).filter((p) => {
25925
- try {
25926
- return statSync2(p).isDirectory();
25927
- } catch {
25928
- return false;
25929
- }
25930
- });
25931
- }
25932
- const abs = join4(repoRoot, cleaned);
25933
- if (existsSync6(abs) && statSync2(abs).isDirectory()) return [abs];
25934
- return [];
25935
- }
25936
- function isLikelyDeployable(absDir) {
25937
- if (existsSync6(join4(absDir, "Dockerfile"))) return true;
25938
- const pkgPath = join4(absDir, "package.json");
25939
- if (existsSync6(pkgPath)) {
25940
- try {
25941
- const pkg2 = JSON.parse(readFileSync6(pkgPath, "utf-8"));
25942
- if (pkg2.scripts?.start || pkg2.scripts?.dev || pkg2.scripts?.serve) return true;
25943
- if (pkg2.bin) return true;
25944
- } catch {
25945
- }
25946
- }
25947
- if (existsSync6(join4(absDir, "next.config.js")) || existsSync6(join4(absDir, "next.config.mjs")) || existsSync6(join4(absDir, "next.config.ts")) || existsSync6(join4(absDir, "go.mod")) || existsSync6(join4(absDir, "Cargo.toml")) || existsSync6(join4(absDir, "manage.py")) || existsSync6(join4(absDir, "pyproject.toml")) || existsSync6(join4(absDir, "Gemfile"))) {
25948
- return true;
25949
- }
25950
- return false;
25951
- }
25952
- function deriveName(repoRoot, absDir) {
25953
- const rel = toPosix(relative2(repoRoot, absDir));
25954
- const parts = rel.split("/");
25955
- return parts[parts.length - 1].toLowerCase().replace(/[^a-z0-9-]/g, "-");
25956
- }
25957
- function toPosix(p) {
25958
- return p.split(sep).join(posix.sep);
25959
- }
25960
- function changedFilesSince(repoRoot, baseRef) {
25961
- const ref = baseRef ?? autoBaseRef(repoRoot);
25962
- if (!ref) return [];
25963
- try {
25964
- const out = execFileSync8("git", ["diff", "--name-only", `${ref}..HEAD`], {
25965
- cwd: repoRoot,
25966
- encoding: "utf-8",
25967
- stdio: ["pipe", "pipe", "pipe"]
25968
- });
25969
- return out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
25970
- } catch {
25971
- return [];
25972
- }
25973
- }
25974
- function autoBaseRef(repoRoot) {
25975
- try {
25976
- const branch = execFileSync8("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
25977
- cwd: repoRoot,
25978
- encoding: "utf-8",
25979
- stdio: ["pipe", "pipe", "pipe"]
25980
- }).trim();
25981
- if (branch && branch !== "HEAD") {
25982
- try {
25983
- execFileSync8("git", ["rev-parse", "--verify", `origin/${branch}`], {
25984
- cwd: repoRoot,
25985
- stdio: ["pipe", "pipe", "pipe"]
25986
- });
25987
- return `origin/${branch}`;
25988
- } catch {
25989
- }
25990
- }
25991
- } catch {
25992
- }
25993
- try {
25994
- execFileSync8("git", ["rev-parse", "--verify", "HEAD~1"], {
25995
- cwd: repoRoot,
25996
- stdio: ["pipe", "pipe", "pipe"]
25997
- });
25998
- return "HEAD~1";
25999
- } catch {
26000
- return null;
26001
- }
26002
- }
26003
- function serviceWasChanged(rootDir, changedPaths) {
26004
- if (changedPaths.length === 0) return true;
26005
- const normalized = rootDir.replace(/\\/g, "/").replace(/^\.?\//, "").replace(/\/$/, "");
26006
- if (normalized === "" || normalized === "." || normalized === "/") return true;
26007
- const prefix = normalized + "/";
26008
- return changedPaths.some((p) => p === normalized || p.startsWith(prefix));
26009
- }
26010
-
26011
- // src/commands/monorepo.ts
26012
26068
  function requireLogin3() {
26013
26069
  if (!isLoggedIn()) {
26014
26070
  console.error(source_default.red('Not logged in. Run "lm login" first.'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@launchmatic/cli",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "description": "Launchmatic CLI — deploy from your terminal",
5
5
  "private": false,
6
6
  "type": "module",