@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.
- package/dist/index.js +459 -342
- 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,
|
|
6595
|
+
function resolveComponent(base, relative4, options, skipNormalization) {
|
|
6596
6596
|
const target = {};
|
|
6597
6597
|
if (!skipNormalization) {
|
|
6598
6598
|
base = parse(serialize(base, options), options);
|
|
6599
|
-
|
|
6599
|
+
relative4 = parse(serialize(relative4, options), options);
|
|
6600
6600
|
}
|
|
6601
6601
|
options = options || {};
|
|
6602
|
-
if (!options.tolerant &&
|
|
6603
|
-
target.scheme =
|
|
6604
|
-
target.userinfo =
|
|
6605
|
-
target.host =
|
|
6606
|
-
target.port =
|
|
6607
|
-
target.path = removeDotSegments(
|
|
6608
|
-
target.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 (
|
|
6611
|
-
target.userinfo =
|
|
6612
|
-
target.host =
|
|
6613
|
-
target.port =
|
|
6614
|
-
target.path = removeDotSegments(
|
|
6615
|
-
target.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 (!
|
|
6617
|
+
if (!relative4.path) {
|
|
6618
6618
|
target.path = base.path;
|
|
6619
|
-
if (
|
|
6620
|
-
target.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 (
|
|
6626
|
-
target.path = removeDotSegments(
|
|
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 = "/" +
|
|
6629
|
+
target.path = "/" + relative4.path;
|
|
6630
6630
|
} else if (!base.path) {
|
|
6631
|
-
target.path =
|
|
6631
|
+
target.path = relative4.path;
|
|
6632
6632
|
} else {
|
|
6633
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
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 =
|
|
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 =
|
|
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
|
|
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").
|
|
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
|
-
|
|
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
|
-
|
|
22141
|
+
execFileSync5("git", ["rev-parse", "--git-dir"], { cwd, stdio: "pipe" });
|
|
21881
22142
|
} catch {
|
|
21882
|
-
|
|
22143
|
+
execFileSync5("git", ["init"], { cwd, stdio: "pipe" });
|
|
21883
22144
|
}
|
|
21884
22145
|
try {
|
|
21885
|
-
const status =
|
|
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
|
-
|
|
21892
|
-
|
|
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
|
-
|
|
22166
|
+
execFileSync5("git", ["remote", "add", "origin", repoData.cloneUrl], { cwd, stdio: "pipe" });
|
|
21906
22167
|
} catch {
|
|
21907
|
-
|
|
22168
|
+
execFileSync5("git", ["remote", "set-url", "origin", repoData.cloneUrl], { cwd, stdio: "pipe" });
|
|
21908
22169
|
}
|
|
21909
22170
|
const localBranch = (() => {
|
|
21910
22171
|
try {
|
|
21911
|
-
return
|
|
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
|
-
|
|
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").
|
|
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
|
|
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
|
-
|
|
22254
|
-
|
|
22255
|
-
console.
|
|
22256
|
-
|
|
22257
|
-
|
|
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
|
-
|
|
22260
|
-
|
|
22261
|
-
`
|
|
22262
|
-
|
|
22263
|
-
|
|
22264
|
-
|
|
22265
|
-
|
|
22266
|
-
|
|
22267
|
-
|
|
22268
|
-
|
|
22269
|
-
|
|
22270
|
-
|
|
22271
|
-
|
|
22272
|
-
|
|
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
|
|
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
|
|
22549
|
-
import { join as
|
|
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 =
|
|
22599
|
-
if (
|
|
22917
|
+
const filePath = join3(cwd, file);
|
|
22918
|
+
if (existsSync4(filePath)) {
|
|
22600
22919
|
try {
|
|
22601
|
-
const content =
|
|
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
|
-
|
|
22994
|
+
execFileSync6("git", ["rev-parse", "--git-dir"], {
|
|
22676
22995
|
cwd,
|
|
22677
22996
|
stdio: ["pipe", "pipe", "pipe"]
|
|
22678
22997
|
});
|
|
22679
22998
|
} catch {
|
|
22680
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23106
|
+
execFileSync6("git", ["rev-parse", "--git-dir"], {
|
|
22788
23107
|
cwd,
|
|
22789
23108
|
stdio: ["pipe", "pipe", "pipe"]
|
|
22790
23109
|
});
|
|
22791
23110
|
} catch {
|
|
22792
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22834
|
-
|
|
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
|
-
|
|
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
|
|
23028
|
-
import { existsSync as
|
|
23029
|
-
import { execFileSync as
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
23088
|
-
if (
|
|
23089
|
-
const dirs =
|
|
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" ?
|
|
23093
|
-
if (
|
|
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 (
|
|
23418
|
+
if (existsSync5(c)) return c;
|
|
23100
23419
|
}
|
|
23101
23420
|
if (process.platform !== "win32") {
|
|
23102
23421
|
try {
|
|
23103
|
-
return
|
|
23422
|
+
return execFileSync7("which", ["google-chrome"], { encoding: "utf-8" }).trim();
|
|
23104
23423
|
} catch {
|
|
23105
23424
|
}
|
|
23106
23425
|
try {
|
|
23107
|
-
return
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
24631
|
-
import { join as
|
|
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
|
|
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 =
|
|
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 =
|
|
24989
|
+
const stat = statSync2(fullPath);
|
|
24671
24990
|
if (stat.size > MAX_FILE_SIZE)
|
|
24672
24991
|
continue;
|
|
24673
|
-
out.push({ path:
|
|
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 =
|
|
25001
|
+
const stat = statSync2(fullPath);
|
|
24683
25002
|
if (stat.size > MAX_FILE_SIZE)
|
|
24684
25003
|
continue;
|
|
24685
|
-
out.push({ path:
|
|
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
|
|
24698
|
-
import { resolve as
|
|
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 =
|
|
24702
|
-
if (!
|
|
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 =
|
|
24727
|
-
if (
|
|
25045
|
+
const pkgPath = resolve4(dir, "package.json");
|
|
25046
|
+
if (existsSync6(pkgPath)) {
|
|
24728
25047
|
try {
|
|
24729
|
-
const pkg2 = JSON.parse(
|
|
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 (
|
|
24736
|
-
else if (
|
|
24737
|
-
else if (
|
|
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 (
|
|
25065
|
+
if (existsSync6(resolve4(dir, "requirements.txt")) || existsSync6(resolve4(dir, "pyproject.toml"))) {
|
|
24747
25066
|
ctx.runtime = "python";
|
|
24748
25067
|
}
|
|
24749
|
-
if (
|
|
24750
|
-
if (
|
|
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.'));
|