@launchmatic/cli 0.6.2 → 0.6.4

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 +459 -342
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6592,49 +6592,49 @@ var require_fast_uri = __commonJS({
6592
6592
  schemelessOptions.skipEscape = true;
6593
6593
  return serialize(resolved, schemelessOptions);
6594
6594
  }
6595
- function resolveComponent(base, relative3, options, skipNormalization) {
6595
+ function resolveComponent(base, relative4, options, skipNormalization) {
6596
6596
  const target = {};
6597
6597
  if (!skipNormalization) {
6598
6598
  base = parse(serialize(base, options), options);
6599
- relative3 = parse(serialize(relative3, options), options);
6599
+ relative4 = parse(serialize(relative4, options), options);
6600
6600
  }
6601
6601
  options = options || {};
6602
- if (!options.tolerant && relative3.scheme) {
6603
- target.scheme = relative3.scheme;
6604
- target.userinfo = relative3.userinfo;
6605
- target.host = relative3.host;
6606
- target.port = relative3.port;
6607
- target.path = removeDotSegments(relative3.path || "");
6608
- target.query = relative3.query;
6602
+ if (!options.tolerant && relative4.scheme) {
6603
+ target.scheme = relative4.scheme;
6604
+ target.userinfo = relative4.userinfo;
6605
+ target.host = relative4.host;
6606
+ target.port = relative4.port;
6607
+ target.path = removeDotSegments(relative4.path || "");
6608
+ target.query = relative4.query;
6609
6609
  } else {
6610
- if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
6611
- target.userinfo = relative3.userinfo;
6612
- target.host = relative3.host;
6613
- target.port = relative3.port;
6614
- target.path = removeDotSegments(relative3.path || "");
6615
- target.query = relative3.query;
6610
+ if (relative4.userinfo !== void 0 || relative4.host !== void 0 || relative4.port !== void 0) {
6611
+ target.userinfo = relative4.userinfo;
6612
+ target.host = relative4.host;
6613
+ target.port = relative4.port;
6614
+ target.path = removeDotSegments(relative4.path || "");
6615
+ target.query = relative4.query;
6616
6616
  } else {
6617
- if (!relative3.path) {
6617
+ if (!relative4.path) {
6618
6618
  target.path = base.path;
6619
- if (relative3.query !== void 0) {
6620
- target.query = relative3.query;
6619
+ if (relative4.query !== void 0) {
6620
+ target.query = relative4.query;
6621
6621
  } else {
6622
6622
  target.query = base.query;
6623
6623
  }
6624
6624
  } else {
6625
- if (relative3.path[0] === "/") {
6626
- target.path = removeDotSegments(relative3.path);
6625
+ if (relative4.path[0] === "/") {
6626
+ target.path = removeDotSegments(relative4.path);
6627
6627
  } else {
6628
6628
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
6629
- target.path = "/" + relative3.path;
6629
+ target.path = "/" + relative4.path;
6630
6630
  } else if (!base.path) {
6631
- target.path = relative3.path;
6631
+ target.path = relative4.path;
6632
6632
  } else {
6633
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
6633
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative4.path;
6634
6634
  }
6635
6635
  target.path = removeDotSegments(target.path);
6636
6636
  }
6637
- target.query = relative3.query;
6637
+ target.query = relative4.query;
6638
6638
  }
6639
6639
  target.userinfo = base.userinfo;
6640
6640
  target.host = base.host;
@@ -6642,7 +6642,7 @@ var require_fast_uri = __commonJS({
6642
6642
  }
6643
6643
  target.scheme = base.scheme;
6644
6644
  }
6645
- target.fragment = relative3.fragment;
6645
+ target.fragment = relative4.fragment;
6646
6646
  return target;
6647
6647
  }
6648
6648
  function equal(uriA, uriB, options) {
@@ -21817,7 +21817,8 @@ function prompt(question) {
21817
21817
  }
21818
21818
 
21819
21819
  // src/commands/deploy.ts
21820
- import { execFileSync as execFileSync4 } from "child_process";
21820
+ import { execFileSync as execFileSync5 } from "child_process";
21821
+ import { relative as relative2 } from "path";
21821
21822
 
21822
21823
  // ../../node_modules/.pnpm/ws@8.19.0/node_modules/ws/wrapper.mjs
21823
21824
  var import_stream = __toESM(require_stream(), 1);
@@ -21827,7 +21828,253 @@ var import_websocket = __toESM(require_websocket(), 1);
21827
21828
  var import_websocket_server = __toESM(require_websocket_server(), 1);
21828
21829
  var wrapper_default = import_websocket.default;
21829
21830
 
21831
+ // src/monorepo.ts
21832
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync, writeFileSync as writeFileSync3 } from "fs";
21833
+ import { execFileSync as execFileSync4 } from "child_process";
21834
+ import { join as join2, relative, sep, posix } from "path";
21835
+ var MANIFEST_FILE = "launchmatic.json";
21836
+ function findRepoRoot(start = process.cwd()) {
21837
+ try {
21838
+ return execFileSync4("git", ["rev-parse", "--show-toplevel"], {
21839
+ cwd: start,
21840
+ encoding: "utf-8",
21841
+ stdio: ["pipe", "pipe", "pipe"]
21842
+ }).trim();
21843
+ } catch {
21844
+ return start;
21845
+ }
21846
+ }
21847
+ function manifestPath(repoRoot = findRepoRoot()) {
21848
+ return join2(repoRoot, MANIFEST_FILE);
21849
+ }
21850
+ function readManifest(repoRoot = findRepoRoot()) {
21851
+ const p = manifestPath(repoRoot);
21852
+ if (!existsSync3(p)) return null;
21853
+ try {
21854
+ const parsed = JSON.parse(readFileSync3(p, "utf-8"));
21855
+ if (parsed.version !== 1 || !Array.isArray(parsed.services)) {
21856
+ throw new Error(`${MANIFEST_FILE} has unexpected shape`);
21857
+ }
21858
+ return parsed;
21859
+ } catch (err) {
21860
+ throw new Error(`Could not read ${MANIFEST_FILE}: ${err instanceof Error ? err.message : String(err)}`);
21861
+ }
21862
+ }
21863
+ function writeManifest(manifest, repoRoot = findRepoRoot()) {
21864
+ writeFileSync3(manifestPath(repoRoot), JSON.stringify(manifest, null, 2) + "\n");
21865
+ }
21866
+ function discoverServices(repoRoot = findRepoRoot()) {
21867
+ const globs = readWorkspaceGlobs(repoRoot);
21868
+ const dirs = /* @__PURE__ */ new Set();
21869
+ for (const glob of globs) {
21870
+ for (const dir of expandGlob(repoRoot, glob)) {
21871
+ dirs.add(dir);
21872
+ }
21873
+ }
21874
+ if (globs.length === 0) {
21875
+ for (const conv of ["apps", "services"]) {
21876
+ const base = join2(repoRoot, conv);
21877
+ if (existsSync3(base) && statSync(base).isDirectory()) {
21878
+ for (const entry of readdirSync(base)) {
21879
+ const full = join2(base, entry);
21880
+ if (statSync(full).isDirectory()) dirs.add(full);
21881
+ }
21882
+ }
21883
+ }
21884
+ }
21885
+ const out = [];
21886
+ for (const absDir of dirs) {
21887
+ if (!isLikelyDeployable(absDir)) continue;
21888
+ const detection = detectLocal(absDir);
21889
+ out.push({
21890
+ name: deriveName(repoRoot, absDir),
21891
+ rootDir: toPosix(relative(repoRoot, absDir)),
21892
+ framework: detection.framework,
21893
+ buildCmd: detection.buildCmd,
21894
+ startCmd: detection.startCmd,
21895
+ port: detection.port
21896
+ });
21897
+ }
21898
+ out.sort((a, b) => a.rootDir.localeCompare(b.rootDir));
21899
+ return out;
21900
+ }
21901
+ function readWorkspaceGlobs(repoRoot) {
21902
+ const globs = [];
21903
+ const pnpmFile = join2(repoRoot, "pnpm-workspace.yaml");
21904
+ if (existsSync3(pnpmFile)) {
21905
+ const text = readFileSync3(pnpmFile, "utf-8");
21906
+ let inPackages = false;
21907
+ for (const rawLine of text.split(/\r?\n/)) {
21908
+ const line = rawLine.replace(/#.*$/, "").trimEnd();
21909
+ if (/^packages\s*:/i.test(line)) {
21910
+ inPackages = true;
21911
+ continue;
21912
+ }
21913
+ if (inPackages) {
21914
+ const m = line.match(/^\s*-\s*['"]?([^'"#]+?)['"]?\s*$/);
21915
+ if (m) {
21916
+ globs.push(m[1].trim());
21917
+ continue;
21918
+ }
21919
+ if (line.trim() && !line.startsWith(" ") && !line.startsWith(" ")) {
21920
+ inPackages = false;
21921
+ }
21922
+ }
21923
+ }
21924
+ }
21925
+ const pkgFile = join2(repoRoot, "package.json");
21926
+ if (existsSync3(pkgFile)) {
21927
+ try {
21928
+ const pkg2 = JSON.parse(readFileSync3(pkgFile, "utf-8"));
21929
+ if (Array.isArray(pkg2.workspaces)) {
21930
+ globs.push(...pkg2.workspaces.filter((g) => typeof g === "string"));
21931
+ } else if (pkg2.workspaces && Array.isArray(pkg2.workspaces.packages)) {
21932
+ globs.push(...pkg2.workspaces.packages.filter((g) => typeof g === "string"));
21933
+ }
21934
+ } catch {
21935
+ }
21936
+ }
21937
+ return globs;
21938
+ }
21939
+ function expandGlob(repoRoot, glob) {
21940
+ const cleaned = glob.replace(/^\.\//, "").replace(/\/$/, "");
21941
+ if (cleaned.endsWith("/*")) {
21942
+ const base = join2(repoRoot, cleaned.slice(0, -2));
21943
+ if (!existsSync3(base) || !statSync(base).isDirectory()) return [];
21944
+ return readdirSync(base).map((entry) => join2(base, entry)).filter((p) => {
21945
+ try {
21946
+ return statSync(p).isDirectory();
21947
+ } catch {
21948
+ return false;
21949
+ }
21950
+ });
21951
+ }
21952
+ const abs = join2(repoRoot, cleaned);
21953
+ if (existsSync3(abs) && statSync(abs).isDirectory()) return [abs];
21954
+ return [];
21955
+ }
21956
+ function isLikelyDeployable(absDir) {
21957
+ if (existsSync3(join2(absDir, "Dockerfile"))) return true;
21958
+ const pkgPath = join2(absDir, "package.json");
21959
+ if (existsSync3(pkgPath)) {
21960
+ try {
21961
+ const pkg2 = JSON.parse(readFileSync3(pkgPath, "utf-8"));
21962
+ if (pkg2.scripts?.start || pkg2.scripts?.dev || pkg2.scripts?.serve) return true;
21963
+ if (pkg2.bin) return true;
21964
+ } catch {
21965
+ }
21966
+ }
21967
+ 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"))) {
21968
+ return true;
21969
+ }
21970
+ return false;
21971
+ }
21972
+ function deriveName(repoRoot, absDir) {
21973
+ const rel = toPosix(relative(repoRoot, absDir));
21974
+ const parts = rel.split("/");
21975
+ return parts[parts.length - 1].toLowerCase().replace(/[^a-z0-9-]/g, "-");
21976
+ }
21977
+ function toPosix(p) {
21978
+ return p.split(sep).join(posix.sep);
21979
+ }
21980
+ function changedFilesSince(repoRoot, baseRef) {
21981
+ const ref = baseRef ?? autoBaseRef(repoRoot);
21982
+ if (!ref) return [];
21983
+ try {
21984
+ const out = execFileSync4("git", ["diff", "--name-only", `${ref}..HEAD`], {
21985
+ cwd: repoRoot,
21986
+ encoding: "utf-8",
21987
+ stdio: ["pipe", "pipe", "pipe"]
21988
+ });
21989
+ return out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
21990
+ } catch {
21991
+ return [];
21992
+ }
21993
+ }
21994
+ function autoBaseRef(repoRoot) {
21995
+ try {
21996
+ const branch = execFileSync4("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
21997
+ cwd: repoRoot,
21998
+ encoding: "utf-8",
21999
+ stdio: ["pipe", "pipe", "pipe"]
22000
+ }).trim();
22001
+ if (branch && branch !== "HEAD") {
22002
+ try {
22003
+ execFileSync4("git", ["rev-parse", "--verify", `origin/${branch}`], {
22004
+ cwd: repoRoot,
22005
+ stdio: ["pipe", "pipe", "pipe"]
22006
+ });
22007
+ return `origin/${branch}`;
22008
+ } catch {
22009
+ }
22010
+ }
22011
+ } catch {
22012
+ }
22013
+ try {
22014
+ execFileSync4("git", ["rev-parse", "--verify", "HEAD~1"], {
22015
+ cwd: repoRoot,
22016
+ stdio: ["pipe", "pipe", "pipe"]
22017
+ });
22018
+ return "HEAD~1";
22019
+ } catch {
22020
+ return null;
22021
+ }
22022
+ }
22023
+ function serviceWasChanged(rootDir, changedPaths) {
22024
+ if (changedPaths.length === 0) return true;
22025
+ const normalized = rootDir.replace(/\\/g, "/").replace(/^\.?\//, "").replace(/\/$/, "");
22026
+ if (normalized === "" || normalized === "." || normalized === "/") return true;
22027
+ const prefix = normalized + "/";
22028
+ return changedPaths.some((p) => p === normalized || p.startsWith(prefix));
22029
+ }
22030
+
21830
22031
  // src/commands/deploy.ts
22032
+ function resolveDeployTarget(cwd, opts) {
22033
+ try {
22034
+ const ctx = readContext();
22035
+ return { serviceId: ctx.serviceId };
22036
+ } catch {
22037
+ }
22038
+ const repoRoot = findRepoRoot(cwd);
22039
+ const manifest = readManifest(repoRoot);
22040
+ if (!manifest || manifest.services.length === 0) {
22041
+ throw new Error(
22042
+ `No .launchmatic.json in this directory and no ${MANIFEST_FILE} at the repo root.
22043
+ Run ${source_default.bold("lm init")} to bind a service, or ${source_default.bold("lm monorepo init")} for a multi-service repo.`
22044
+ );
22045
+ }
22046
+ const findByName = (name) => manifest.services.find((s) => s.name === name);
22047
+ if (opts.service) {
22048
+ const match = findByName(opts.service);
22049
+ if (!match) {
22050
+ const names2 = manifest.services.map((s) => s.name).join(", ");
22051
+ throw new Error(
22052
+ `No service named "${opts.service}" in ${MANIFEST_FILE}. Known services: ${names2}`
22053
+ );
22054
+ }
22055
+ if (!match.serviceId) {
22056
+ throw new Error(
22057
+ `Service "${match.name}" has no serviceId \u2014 re-run ${source_default.bold("lm monorepo init")}.`
22058
+ );
22059
+ }
22060
+ return { serviceId: match.serviceId, entryName: match.name };
22061
+ }
22062
+ const relCwd = relative2(repoRoot, cwd).split(/[\\/]/).filter(Boolean).join("/");
22063
+ const auto = manifest.services.find((s) => {
22064
+ const rd = s.rootDir.replace(/^\/+|\/+$/g, "");
22065
+ return rd === relCwd;
22066
+ });
22067
+ if (auto?.serviceId) {
22068
+ return { serviceId: auto.serviceId, entryName: auto.name };
22069
+ }
22070
+ const names = manifest.services.map((s) => s.name).join(", ");
22071
+ throw new Error(
22072
+ `Couldn't auto-detect which service to deploy from this directory.
22073
+ cwd: ${cwd}
22074
+ repo root: ${repoRoot}
22075
+ Try ${source_default.bold(`lm deploy --service <name>`)} (${names}) or ${source_default.bold("lm up")} to deploy everything.`
22076
+ );
22077
+ }
21831
22078
  function registerDeploy(program3) {
21832
22079
  program3.command("deploy").description("Build and deploy the current service").option(
21833
22080
  "--dockerfile <path>",
@@ -21835,14 +22082,28 @@ function registerDeploy(program3) {
21835
22082
  ).option(
21836
22083
  "--root-dir <path>",
21837
22084
  "Root directory for the build (default: /)"
21838
- ).option("--build-cmd <cmd>", "Override the build command").option("--start-cmd <cmd>", "Override the start command").action(async (opts) => {
22085
+ ).option("--build-cmd <cmd>", "Override the build command").option("--start-cmd <cmd>", "Override the start command").option(
22086
+ "-s, --service <name>",
22087
+ "In a monorepo, deploy a service by name from anywhere in the repo (reads launchmatic.json at the repo root)"
22088
+ ).action(async (opts) => {
21839
22089
  if (!isLoggedIn()) {
21840
22090
  console.error(source_default.red('Not logged in. Run "lm login" first.'));
21841
22091
  process.exitCode = 1;
21842
22092
  return;
21843
22093
  }
21844
- const ctx = readContext();
21845
22094
  const cwd = process.cwd();
22095
+ let target;
22096
+ try {
22097
+ target = resolveDeployTarget(cwd, { service: opts.service });
22098
+ } catch (err) {
22099
+ console.error(source_default.red(err instanceof Error ? err.message : String(err)));
22100
+ process.exitCode = 1;
22101
+ return;
22102
+ }
22103
+ const ctx = { serviceId: target.serviceId, projectId: "", teamId: "" };
22104
+ if (target.entryName) {
22105
+ console.log(source_default.dim(`Service: ${target.entryName} (from ${MANIFEST_FILE})`));
22106
+ }
21846
22107
  let gitInfo = getGitInfo(cwd);
21847
22108
  const branch = gitInfo.repoBranch;
21848
22109
  if (gitInfo.hasUncommitted) {
@@ -21853,7 +22114,7 @@ function registerDeploy(program3) {
21853
22114
  if (gitInfo.hasUnpushed && gitInfo.hasRemote) {
21854
22115
  const pushSpinner = ora("Pushing commits to remote...").start();
21855
22116
  try {
21856
- execFileSync4("git", ["push", "origin", branch], {
22117
+ execFileSync5("git", ["push", "origin", branch], {
21857
22118
  cwd,
21858
22119
  encoding: "utf-8",
21859
22120
  stdio: ["pipe", "pipe", "pipe"]
@@ -21877,19 +22138,19 @@ function registerDeploy(program3) {
21877
22138
  const repoSpinner = ora("No git remote \u2014 creating GitHub repository...").start();
21878
22139
  try {
21879
22140
  try {
21880
- execFileSync4("git", ["rev-parse", "--git-dir"], { cwd, stdio: "pipe" });
22141
+ execFileSync5("git", ["rev-parse", "--git-dir"], { cwd, stdio: "pipe" });
21881
22142
  } catch {
21882
- execFileSync4("git", ["init"], { cwd, stdio: "pipe" });
22143
+ execFileSync5("git", ["init"], { cwd, stdio: "pipe" });
21883
22144
  }
21884
22145
  try {
21885
- const status = execFileSync4("git", ["status", "--porcelain"], {
22146
+ const status = execFileSync5("git", ["status", "--porcelain"], {
21886
22147
  cwd,
21887
22148
  encoding: "utf-8",
21888
22149
  stdio: ["pipe", "pipe", "pipe"]
21889
22150
  });
21890
22151
  if (status.trim().length > 0) {
21891
- execFileSync4("git", ["add", "-A"], { cwd, stdio: "pipe" });
21892
- execFileSync4(
22152
+ execFileSync5("git", ["add", "-A"], { cwd, stdio: "pipe" });
22153
+ execFileSync5(
21893
22154
  "git",
21894
22155
  ["commit", "-m", "Initial commit"],
21895
22156
  { cwd, stdio: "pipe" }
@@ -21902,18 +22163,18 @@ function registerDeploy(program3) {
21902
22163
  body: JSON.stringify({})
21903
22164
  });
21904
22165
  try {
21905
- execFileSync4("git", ["remote", "add", "origin", repoData.cloneUrl], { cwd, stdio: "pipe" });
22166
+ execFileSync5("git", ["remote", "add", "origin", repoData.cloneUrl], { cwd, stdio: "pipe" });
21906
22167
  } catch {
21907
- execFileSync4("git", ["remote", "set-url", "origin", repoData.cloneUrl], { cwd, stdio: "pipe" });
22168
+ execFileSync5("git", ["remote", "set-url", "origin", repoData.cloneUrl], { cwd, stdio: "pipe" });
21908
22169
  }
21909
22170
  const localBranch = (() => {
21910
22171
  try {
21911
- return execFileSync4("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd, encoding: "utf-8" }).trim();
22172
+ return execFileSync5("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd, encoding: "utf-8" }).trim();
21912
22173
  } catch {
21913
22174
  return "main";
21914
22175
  }
21915
22176
  })();
21916
- execFileSync4("git", ["push", "-u", "origin", localBranch], { cwd, stdio: ["pipe", "pipe", "pipe"] });
22177
+ execFileSync5("git", ["push", "-u", "origin", localBranch], { cwd, stdio: ["pipe", "pipe", "pipe"] });
21917
22178
  repoSpinner.succeed(`Repository created: ${source_default.dim(repoData.repoFullName)} (private)`);
21918
22179
  gitInfo = getGitInfo(cwd);
21919
22180
  } catch (err) {
@@ -22242,56 +22503,44 @@ function registerDomains(program3) {
22242
22503
 
22243
22504
  // src/commands/status.ts
22244
22505
  function registerStatus(program3) {
22245
- program3.command("status").description("Show service and deployment status").action(async () => {
22506
+ program3.command("status").description("Show service and deployment status").option(
22507
+ "-s, --service <name>",
22508
+ "In a monorepo, show status for a single service (by name)"
22509
+ ).action(async (opts) => {
22246
22510
  if (!isLoggedIn()) {
22247
22511
  console.error(source_default.red('Not logged in. Run "lm login" first.'));
22248
22512
  process.exitCode = 1;
22249
22513
  return;
22250
22514
  }
22251
- const ctx = readContext();
22515
+ const manifest = readManifest();
22516
+ if (manifest && manifest.services.length > 0) {
22517
+ await renderMonorepoStatus(manifest.services, opts.service);
22518
+ return;
22519
+ }
22520
+ let ctx;
22252
22521
  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)}`
22522
+ ctx = readContext();
22523
+ } catch (err) {
22524
+ console.error(
22525
+ source_default.red(
22526
+ err instanceof Error ? err.message : "No Launchmatic context found."
22527
+ )
22258
22528
  );
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();
22529
+ console.error(
22530
+ source_default.dim(
22531
+ `Run ${source_default.bold("lm init")} to bind a service, or ${source_default.bold(
22532
+ "lm monorepo init"
22533
+ )} to create a ${MANIFEST_FILE} for multi-service repos.`
22534
+ )
22535
+ );
22536
+ process.exitCode = 1;
22537
+ return;
22538
+ }
22539
+ try {
22540
+ const { data } = await api(
22541
+ `/api/services/${ctx.serviceId}`
22542
+ );
22543
+ renderServiceBlock(data, { indent: " " });
22295
22544
  } catch (err) {
22296
22545
  console.error(
22297
22546
  source_default.red(
@@ -22299,10 +22548,80 @@ function registerStatus(program3) {
22299
22548
  )
22300
22549
  );
22301
22550
  process.exitCode = 1;
22302
- return;
22303
22551
  }
22304
22552
  });
22305
22553
  }
22554
+ async function renderMonorepoStatus(services, filter) {
22555
+ const targets = filter ? services.filter((s) => s.name === filter) : services;
22556
+ if (targets.length === 0) {
22557
+ console.error(
22558
+ source_default.red(
22559
+ filter ? `No service named "${filter}" in ${MANIFEST_FILE}.` : `No services declared in ${MANIFEST_FILE}.`
22560
+ )
22561
+ );
22562
+ process.exitCode = 1;
22563
+ return;
22564
+ }
22565
+ console.log();
22566
+ console.log(
22567
+ source_default.bold(`Monorepo`) + source_default.dim(` \u2014 ${targets.length} of ${services.length} service${services.length === 1 ? "" : "s"}`)
22568
+ );
22569
+ const results = await Promise.allSettled(
22570
+ targets.map(async (s) => {
22571
+ if (!s.serviceId) {
22572
+ return { name: s.name, error: `no serviceId \u2014 re-run ${source_default.bold("lm monorepo init")}` };
22573
+ }
22574
+ const { data } = await api(
22575
+ `/api/services/${s.serviceId}`
22576
+ );
22577
+ return { name: s.name, data };
22578
+ })
22579
+ );
22580
+ for (const r of results) {
22581
+ console.log();
22582
+ if (r.status === "fulfilled") {
22583
+ if ("error" in r.value) {
22584
+ console.log(` ${source_default.cyan(r.value.name)} ${source_default.yellow(r.value.error)}`);
22585
+ } else {
22586
+ renderServiceBlock(r.value.data, { indent: " " });
22587
+ }
22588
+ } else {
22589
+ const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
22590
+ console.log(` ${source_default.red("\u2717")} ${source_default.dim(msg)}`);
22591
+ }
22592
+ }
22593
+ console.log();
22594
+ }
22595
+ function renderServiceBlock(data, { indent }) {
22596
+ console.log();
22597
+ console.log(`${indent}${source_default.bold("Service:")} ${data.name}`);
22598
+ console.log(`${indent}${source_default.bold("Status:")} ${statusColor(data.status)}`);
22599
+ if (data.subdomain) {
22600
+ console.log(
22601
+ `${indent}${source_default.bold("URL:")} ${source_default.cyan(`https://${data.subdomain}`)}`
22602
+ );
22603
+ }
22604
+ if (data.runtime) {
22605
+ console.log(`${indent}${source_default.bold("Runtime:")} ${data.runtime}`);
22606
+ }
22607
+ if (data.deployments && data.deployments.length > 0) {
22608
+ const last = data.deployments[0];
22609
+ console.log();
22610
+ console.log(`${indent}${source_default.bold("Last deployment:")}`);
22611
+ console.log(`${indent} Status: ${statusColor(last.status)}`);
22612
+ if (last.branch) console.log(`${indent} Branch: ${last.branch}`);
22613
+ if (last.commitSha) console.log(`${indent} Commit: ${last.commitSha.slice(0, 7)}`);
22614
+ console.log(`${indent} Time: ${new Date(last.createdAt).toLocaleString()}`);
22615
+ }
22616
+ if (data.domains && data.domains.length > 0) {
22617
+ console.log();
22618
+ console.log(`${indent}${source_default.bold("Domains:")}`);
22619
+ for (const d of data.domains) {
22620
+ const ssl = d.sslStatus === "ACTIVE" ? source_default.green("\u2713 SSL") : source_default.yellow("\u23F3 SSL");
22621
+ console.log(`${indent} ${d.hostname} ${ssl}`);
22622
+ }
22623
+ }
22624
+ }
22306
22625
  function statusColor(status) {
22307
22626
  switch (status.toUpperCase()) {
22308
22627
  case "ACTIVE":
@@ -22541,12 +22860,12 @@ function registerLightspeed(program3) {
22541
22860
  }
22542
22861
 
22543
22862
  // src/commands/quicklaunch.ts
22544
- import { execFileSync as execFileSync5 } from "child_process";
22863
+ import { execFileSync as execFileSync6 } from "child_process";
22545
22864
  import { basename } from "path";
22546
22865
 
22547
22866
  // src/connectors.ts
22548
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
22549
- import { join as join2 } from "path";
22867
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
22868
+ import { join as join3 } from "path";
22550
22869
  import { randomBytes } from "crypto";
22551
22870
  var CONNECTOR_REGISTRY = [
22552
22871
  // Stripe
@@ -22595,10 +22914,10 @@ function getConnector(envKey, appName) {
22595
22914
  function detectEnvVars(cwd) {
22596
22915
  const envFiles = [".env.example", ".env.local.example", ".env.sample", ".env"];
22597
22916
  for (const file of envFiles) {
22598
- const filePath = join2(cwd, file);
22599
- if (existsSync3(filePath)) {
22917
+ const filePath = join3(cwd, file);
22918
+ if (existsSync4(filePath)) {
22600
22919
  try {
22601
- const content = readFileSync3(filePath, "utf-8");
22920
+ const content = readFileSync4(filePath, "utf-8");
22602
22921
  const keys = [];
22603
22922
  for (const line of content.split("\n")) {
22604
22923
  const trimmed = line.trim();
@@ -22672,25 +22991,25 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
22672
22991
  const repoSpinner = ora("Creating GitHub repository...").start();
22673
22992
  try {
22674
22993
  try {
22675
- execFileSync5("git", ["rev-parse", "--git-dir"], {
22994
+ execFileSync6("git", ["rev-parse", "--git-dir"], {
22676
22995
  cwd,
22677
22996
  stdio: ["pipe", "pipe", "pipe"]
22678
22997
  });
22679
22998
  } catch {
22680
- execFileSync5("git", ["init"], { cwd, stdio: ["pipe", "pipe", "pipe"] });
22999
+ execFileSync6("git", ["init"], { cwd, stdio: ["pipe", "pipe", "pipe"] });
22681
23000
  }
22682
23001
  const { data: repoData } = await api(`/api/services/${ctx.serviceId}/create-repo`, {
22683
23002
  method: "POST",
22684
23003
  body: JSON.stringify({})
22685
23004
  });
22686
23005
  try {
22687
- execFileSync5("git", ["remote", "add", "origin", repoData.cloneUrl], {
23006
+ execFileSync6("git", ["remote", "add", "origin", repoData.cloneUrl], {
22688
23007
  cwd,
22689
23008
  stdio: ["pipe", "pipe", "pipe"]
22690
23009
  });
22691
23010
  } catch {
22692
23011
  try {
22693
- execFileSync5("git", ["remote", "set-url", "origin", repoData.cloneUrl], {
23012
+ execFileSync6("git", ["remote", "set-url", "origin", repoData.cloneUrl], {
22694
23013
  cwd,
22695
23014
  stdio: ["pipe", "pipe", "pipe"]
22696
23015
  });
@@ -22784,12 +23103,12 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
22784
23103
  const repoSpinner = ora("Creating GitHub repository...").start();
22785
23104
  try {
22786
23105
  try {
22787
- execFileSync5("git", ["rev-parse", "--git-dir"], {
23106
+ execFileSync6("git", ["rev-parse", "--git-dir"], {
22788
23107
  cwd,
22789
23108
  stdio: ["pipe", "pipe", "pipe"]
22790
23109
  });
22791
23110
  } catch {
22792
- execFileSync5("git", ["init"], {
23111
+ execFileSync6("git", ["init"], {
22793
23112
  cwd,
22794
23113
  stdio: ["pipe", "pipe", "pipe"]
22795
23114
  });
@@ -22799,13 +23118,13 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
22799
23118
  body: JSON.stringify({})
22800
23119
  });
22801
23120
  try {
22802
- execFileSync5("git", ["remote", "add", "origin", repoData.cloneUrl], {
23121
+ execFileSync6("git", ["remote", "add", "origin", repoData.cloneUrl], {
22803
23122
  cwd,
22804
23123
  stdio: ["pipe", "pipe", "pipe"]
22805
23124
  });
22806
23125
  } catch {
22807
23126
  try {
22808
- execFileSync5("git", ["remote", "set-url", "origin", repoData.cloneUrl], {
23127
+ execFileSync6("git", ["remote", "set-url", "origin", repoData.cloneUrl], {
22809
23128
  cwd,
22810
23129
  stdio: ["pipe", "pipe", "pipe"]
22811
23130
  });
@@ -22830,8 +23149,8 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
22830
23149
  if (gitInfo.hasUncommitted) {
22831
23150
  const commitSpinner = ora("Committing changes...").start();
22832
23151
  try {
22833
- execFileSync5("git", ["add", "-A"], { cwd, stdio: ["pipe", "pipe", "pipe"] });
22834
- execFileSync5("git", ["commit", "-m", "quicklaunch: initial deploy"], {
23152
+ execFileSync6("git", ["add", "-A"], { cwd, stdio: ["pipe", "pipe", "pipe"] });
23153
+ execFileSync6("git", ["commit", "-m", "quicklaunch: initial deploy"], {
22835
23154
  cwd,
22836
23155
  stdio: ["pipe", "pipe", "pipe"]
22837
23156
  });
@@ -22844,7 +23163,7 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
22844
23163
  if (gitInfo.hasUnpushed || !gitInfo.commitSha) {
22845
23164
  const pushSpinner = ora("Pushing to remote...").start();
22846
23165
  try {
22847
- execFileSync5("git", ["push", "-u", "origin", gitInfo.repoBranch], {
23166
+ execFileSync6("git", ["push", "-u", "origin", gitInfo.repoBranch], {
22848
23167
  cwd,
22849
23168
  stdio: ["pipe", "pipe", "pipe"]
22850
23169
  });
@@ -23024,22 +23343,22 @@ async function pollStatus(deploymentId, spinner) {
23024
23343
  }
23025
23344
 
23026
23345
  // 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";
23346
+ import { resolve as resolve3 } from "path";
23347
+ import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
23348
+ import { execFileSync as execFileSync7 } from "child_process";
23030
23349
  async function ensureChromium() {
23031
23350
  const existing = findChromium();
23032
23351
  if (existing) return existing;
23033
23352
  const spinner = ora("Downloading Chromium (one-time setup)...").start();
23034
23353
  try {
23035
- execFileSync6("npx", ["playwright", "install", "chromium"], {
23354
+ execFileSync7("npx", ["playwright", "install", "chromium"], {
23036
23355
  stdio: ["pipe", "pipe", "pipe"],
23037
23356
  timeout: 12e4
23038
23357
  });
23039
23358
  spinner.succeed("Chromium installed");
23040
23359
  } catch {
23041
23360
  try {
23042
- execFileSync6("npx", ["playwright-core", "install", "chromium"], {
23361
+ execFileSync7("npx", ["playwright-core", "install", "chromium"], {
23043
23362
  stdio: ["pipe", "pipe", "pipe"],
23044
23363
  timeout: 12e4
23045
23364
  });
@@ -23084,27 +23403,27 @@ function findChromium() {
23084
23403
  }
23085
23404
  try {
23086
23405
  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-"));
23406
+ const pwBrowsers = resolve3(home, ".cache", "ms-playwright");
23407
+ if (existsSync5(pwBrowsers)) {
23408
+ const dirs = readdirSync2(pwBrowsers).filter((d) => d.startsWith("chromium-"));
23090
23409
  if (dirs.length > 0) {
23091
23410
  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;
23411
+ 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");
23412
+ if (existsSync5(chromePath)) return chromePath;
23094
23413
  }
23095
23414
  }
23096
23415
  } catch {
23097
23416
  }
23098
23417
  for (const c of candidates) {
23099
- if (existsSync4(c)) return c;
23418
+ if (existsSync5(c)) return c;
23100
23419
  }
23101
23420
  if (process.platform !== "win32") {
23102
23421
  try {
23103
- return execFileSync6("which", ["google-chrome"], { encoding: "utf-8" }).trim();
23422
+ return execFileSync7("which", ["google-chrome"], { encoding: "utf-8" }).trim();
23104
23423
  } catch {
23105
23424
  }
23106
23425
  try {
23107
- return execFileSync6("which", ["chromium"], { encoding: "utf-8" }).trim();
23426
+ return execFileSync7("which", ["chromium"], { encoding: "utf-8" }).trim();
23108
23427
  } catch {
23109
23428
  }
23110
23429
  }
@@ -23143,7 +23462,7 @@ function registerBrowser(program3) {
23143
23462
  const page = await context.newPage();
23144
23463
  await page.goto(targetUrl, { waitUntil: "networkidle" });
23145
23464
  if (parseInt(opts.delay) > 0) await page.waitForTimeout(parseInt(opts.delay));
23146
- const output = resolve2(opts.output);
23465
+ const output = resolve3(opts.output);
23147
23466
  await page.screenshot({ path: output, fullPage: opts.full });
23148
23467
  await b.close();
23149
23468
  spinner.succeed(`Screenshot saved \u2192 ${source_default.cyan(output)}`);
@@ -23159,7 +23478,7 @@ function registerBrowser(program3) {
23159
23478
  const b = await launchBrowser(true);
23160
23479
  const page = await b.newPage();
23161
23480
  await page.goto(targetUrl, { waitUntil: "networkidle" });
23162
- const output = resolve2(opts.output);
23481
+ const output = resolve3(opts.output);
23163
23482
  await page.pdf({ path: output, format: opts.format, landscape: opts.landscape, printBackground: true });
23164
23483
  await b.close();
23165
23484
  spinner.succeed(`PDF saved \u2192 ${source_default.cyan(output)}`);
@@ -23368,7 +23687,7 @@ ${actions.map((a) => ` ${a}`).join("\n")}
23368
23687
  }
23369
23688
 
23370
23689
  // src/commands/repo.ts
23371
- import { execFileSync as execFileSync7 } from "child_process";
23690
+ import { execFileSync as execFileSync8 } from "child_process";
23372
23691
  function requireLogin() {
23373
23692
  if (!isLoggedIn()) {
23374
23693
  console.error(source_default.red('Not logged in. Run "lm login" first.'));
@@ -23399,7 +23718,7 @@ function openUrl(url) {
23399
23718
  const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
23400
23719
  const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
23401
23720
  try {
23402
- execFileSync7(cmd, args, { stdio: "pipe" });
23721
+ execFileSync8(cmd, args, { stdio: "pipe" });
23403
23722
  } catch {
23404
23723
  }
23405
23724
  }
@@ -23661,7 +23980,7 @@ function registerRepo(program3) {
23661
23980
  const args = ["clone", "--depth", "1"];
23662
23981
  if (opts.branch) args.push("--branch", opts.branch);
23663
23982
  args.push(url);
23664
- execFileSync7("git", args, { stdio: ["pipe", "pipe", "pipe"] });
23983
+ execFileSync8("git", args, { stdio: ["pipe", "pipe", "pipe"] });
23665
23984
  spinner.succeed(`Cloned ${source_default.cyan(fullName)}`);
23666
23985
  } catch (err) {
23667
23986
  spinner.fail(source_default.red(`Clone failed: ${err.message}`));
@@ -23673,7 +23992,7 @@ function registerRepo(program3) {
23673
23992
  const args = ["diff"];
23674
23993
  if (opts.staged) args.push("--staged");
23675
23994
  args.push("--stat");
23676
- const output = execFileSync7("git", args, { cwd: process.cwd(), encoding: "utf-8" });
23995
+ const output = execFileSync8("git", args, { cwd: process.cwd(), encoding: "utf-8" });
23677
23996
  if (!output.trim()) {
23678
23997
  console.log(source_default.dim("\n No changes.\n"));
23679
23998
  } else {
@@ -23688,7 +24007,7 @@ function registerRepo(program3) {
23688
24007
  try {
23689
24008
  const args = ["stash"];
23690
24009
  if (action) args.push(action);
23691
- const output = execFileSync7("git", args, { cwd: process.cwd(), encoding: "utf-8" });
24010
+ const output = execFileSync8("git", args, { cwd: process.cwd(), encoding: "utf-8" });
23692
24011
  console.log(output || source_default.dim("Done."));
23693
24012
  } catch (err) {
23694
24013
  console.error(source_default.red(`Failed: ${err.message}`));
@@ -24627,8 +24946,8 @@ function validateGeneratedApp(files, ctx = {}) {
24627
24946
  }
24628
24947
 
24629
24948
  // ../../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";
24949
+ import { readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
24950
+ import { join as join4, relative as relative3 } from "path";
24632
24951
  var MAX_FILE_SIZE = 512 * 1024;
24633
24952
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next", "dist", "build", "__pycache__", "target"]);
24634
24953
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -24655,22 +24974,22 @@ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
24655
24974
  ".env.example"
24656
24975
  ]);
24657
24976
  function collectFiles(dir, rootDir, out) {
24658
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
24977
+ for (const entry of readdirSync3(dir, { withFileTypes: true })) {
24659
24978
  if (entry.name.startsWith(".") && entry.name !== ".env.example")
24660
24979
  continue;
24661
24980
  if (SKIP_DIRS.has(entry.name))
24662
24981
  continue;
24663
- const fullPath = join3(dir, entry.name);
24982
+ const fullPath = join4(dir, entry.name);
24664
24983
  if (entry.isDirectory()) {
24665
24984
  collectFiles(fullPath, rootDir, out);
24666
24985
  continue;
24667
24986
  }
24668
24987
  if (entry.name === "Dockerfile" || entry.name === "docker-compose.yml") {
24669
24988
  try {
24670
- const stat = statSync(fullPath);
24989
+ const stat = statSync2(fullPath);
24671
24990
  if (stat.size > MAX_FILE_SIZE)
24672
24991
  continue;
24673
- out.push({ path: relative(rootDir, fullPath).replace(/\\/g, "/"), content: readFileSync4(fullPath, "utf-8") });
24992
+ out.push({ path: relative3(rootDir, fullPath).replace(/\\/g, "/"), content: readFileSync5(fullPath, "utf-8") });
24674
24993
  } catch {
24675
24994
  }
24676
24995
  continue;
@@ -24679,10 +24998,10 @@ function collectFiles(dir, rootDir, out) {
24679
24998
  if (!SOURCE_EXTENSIONS.has(ext))
24680
24999
  continue;
24681
25000
  try {
24682
- const stat = statSync(fullPath);
25001
+ const stat = statSync2(fullPath);
24683
25002
  if (stat.size > MAX_FILE_SIZE)
24684
25003
  continue;
24685
- out.push({ path: relative(rootDir, fullPath).replace(/\\/g, "/"), content: readFileSync4(fullPath, "utf-8") });
25004
+ out.push({ path: relative3(rootDir, fullPath).replace(/\\/g, "/"), content: readFileSync5(fullPath, "utf-8") });
24686
25005
  } catch {
24687
25006
  }
24688
25007
  }
@@ -24694,12 +25013,12 @@ function validateFromDisk(sourceDir, ctx = {}) {
24694
25013
  }
24695
25014
 
24696
25015
  // src/commands/doctor.ts
24697
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
24698
- import { resolve as resolve3 } from "path";
25016
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
25017
+ import { resolve as resolve4 } from "path";
24699
25018
  function registerDoctor(program3) {
24700
25019
  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)) {
25020
+ const dir = resolve4(opts.dir);
25021
+ if (!existsSync6(dir)) {
24703
25022
  console.error(source_default.red(`Directory not found: ${dir}`));
24704
25023
  process.exitCode = 1;
24705
25024
  return;
@@ -24723,18 +25042,18 @@ function registerDoctor(program3) {
24723
25042
  }
24724
25043
  function detectContext(dir) {
24725
25044
  const ctx = {};
24726
- const pkgPath = resolve3(dir, "package.json");
24727
- if (existsSync5(pkgPath)) {
25045
+ const pkgPath = resolve4(dir, "package.json");
25046
+ if (existsSync6(pkgPath)) {
24728
25047
  try {
24729
- const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
25048
+ const pkg2 = JSON.parse(readFileSync6(pkgPath, "utf-8"));
24730
25049
  const deps = { ...pkg2.dependencies, ...pkg2.devDependencies };
24731
25050
  if (deps.next) ctx.runtime = "nodejs";
24732
25051
  else if (deps.express || deps.fastify || deps.koa || deps.hapi) ctx.runtime = "nodejs";
24733
25052
  else if (deps.react || deps.vue || deps.svelte) ctx.runtime = "nodejs";
24734
25053
  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";
25054
+ if (existsSync6(resolve4(dir, "pnpm-lock.yaml"))) ctx.packageManager = "pnpm";
25055
+ else if (existsSync6(resolve4(dir, "yarn.lock"))) ctx.packageManager = "yarn";
25056
+ else if (existsSync6(resolve4(dir, "package-lock.json"))) ctx.packageManager = "npm";
24738
25057
  const startScript = pkg2.scripts?.start || pkg2.scripts?.dev || "";
24739
25058
  const portMatch = startScript.match(/--port\s+(\d+)|-p\s+(\d+)/);
24740
25059
  if (portMatch) ctx.port = parseInt(portMatch[1] || portMatch[2]);
@@ -24743,11 +25062,11 @@ function detectContext(dir) {
24743
25062
  } catch {
24744
25063
  }
24745
25064
  }
24746
- if (existsSync5(resolve3(dir, "requirements.txt")) || existsSync5(resolve3(dir, "pyproject.toml"))) {
25065
+ if (existsSync6(resolve4(dir, "requirements.txt")) || existsSync6(resolve4(dir, "pyproject.toml"))) {
24747
25066
  ctx.runtime = "python";
24748
25067
  }
24749
- if (existsSync5(resolve3(dir, "go.mod"))) ctx.runtime = "go";
24750
- if (existsSync5(resolve3(dir, "Cargo.toml"))) ctx.runtime = "rust";
25068
+ if (existsSync6(resolve4(dir, "go.mod"))) ctx.runtime = "go";
25069
+ if (existsSync6(resolve4(dir, "Cargo.toml"))) ctx.runtime = "rust";
24751
25070
  return ctx;
24752
25071
  }
24753
25072
  function severityIcon(severity) {
@@ -25807,208 +26126,6 @@ import { execFileSync as execFileSync9 } from "child_process";
25807
26126
  import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
25808
26127
  import { join as join5 } from "path";
25809
26128
  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
26129
  function requireLogin3() {
26013
26130
  if (!isLoggedIn()) {
26014
26131
  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.4",
4
4
  "description": "Launchmatic CLI — deploy from your terminal",
5
5
  "private": false,
6
6
  "type": "module",