@launchmatic/cli 0.6.3 → 0.6.5

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 +303 -241
  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) {
@@ -22240,206 +22501,6 @@ function registerDomains(program3) {
22240
22501
  });
22241
22502
  }
22242
22503
 
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
-
22443
22504
  // src/commands/status.ts
22444
22505
  function registerStatus(program3) {
22445
22506
  program3.command("status").description("Show service and deployment status").option(
@@ -22562,6 +22623,7 @@ function renderServiceBlock(data, { indent }) {
22562
22623
  }
22563
22624
  }
22564
22625
  function statusColor(status) {
22626
+ if (!status) return source_default.dim("unknown");
22565
22627
  switch (status.toUpperCase()) {
22566
22628
  case "ACTIVE":
22567
22629
  case "RUNNING":
@@ -24886,7 +24948,7 @@ function validateGeneratedApp(files, ctx = {}) {
24886
24948
 
24887
24949
  // ../../packages/validator/dist/validate-disk.js
24888
24950
  import { readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
24889
- import { join as join4, relative as relative2 } from "path";
24951
+ import { join as join4, relative as relative3 } from "path";
24890
24952
  var MAX_FILE_SIZE = 512 * 1024;
24891
24953
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next", "dist", "build", "__pycache__", "target"]);
24892
24954
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -24928,7 +24990,7 @@ function collectFiles(dir, rootDir, out) {
24928
24990
  const stat = statSync2(fullPath);
24929
24991
  if (stat.size > MAX_FILE_SIZE)
24930
24992
  continue;
24931
- out.push({ path: relative2(rootDir, fullPath).replace(/\\/g, "/"), content: readFileSync5(fullPath, "utf-8") });
24993
+ out.push({ path: relative3(rootDir, fullPath).replace(/\\/g, "/"), content: readFileSync5(fullPath, "utf-8") });
24932
24994
  } catch {
24933
24995
  }
24934
24996
  continue;
@@ -24940,7 +25002,7 @@ function collectFiles(dir, rootDir, out) {
24940
25002
  const stat = statSync2(fullPath);
24941
25003
  if (stat.size > MAX_FILE_SIZE)
24942
25004
  continue;
24943
- out.push({ path: relative2(rootDir, fullPath).replace(/\\/g, "/"), content: readFileSync5(fullPath, "utf-8") });
25005
+ out.push({ path: relative3(rootDir, fullPath).replace(/\\/g, "/"), content: readFileSync5(fullPath, "utf-8") });
24944
25006
  } catch {
24945
25007
  }
24946
25008
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@launchmatic/cli",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "Launchmatic CLI — deploy from your terminal",
5
5
  "private": false,
6
6
  "type": "module",