@launchmatic/cli 0.4.0 → 0.6.1
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/README.md +178 -178
- package/dist/c.js +0 -0
- package/dist/index.js +1109 -155
- package/package.json +46 -37
package/dist/index.js
CHANGED
|
@@ -972,7 +972,7 @@ var require_command = __commonJS({
|
|
|
972
972
|
"use strict";
|
|
973
973
|
var EventEmitter = __require("events").EventEmitter;
|
|
974
974
|
var childProcess2 = __require("child_process");
|
|
975
|
-
var
|
|
975
|
+
var path7 = __require("path");
|
|
976
976
|
var fs8 = __require("fs");
|
|
977
977
|
var process22 = __require("process");
|
|
978
978
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
@@ -1905,9 +1905,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1905
1905
|
let launchWithNode = false;
|
|
1906
1906
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1907
1907
|
function findFile(baseDir, baseName) {
|
|
1908
|
-
const localBin =
|
|
1908
|
+
const localBin = path7.resolve(baseDir, baseName);
|
|
1909
1909
|
if (fs8.existsSync(localBin)) return localBin;
|
|
1910
|
-
if (sourceExt.includes(
|
|
1910
|
+
if (sourceExt.includes(path7.extname(baseName))) return void 0;
|
|
1911
1911
|
const foundExt = sourceExt.find(
|
|
1912
1912
|
(ext) => fs8.existsSync(`${localBin}${ext}`)
|
|
1913
1913
|
);
|
|
@@ -1925,17 +1925,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1925
1925
|
} catch (err) {
|
|
1926
1926
|
resolvedScriptPath = this._scriptPath;
|
|
1927
1927
|
}
|
|
1928
|
-
executableDir =
|
|
1929
|
-
|
|
1928
|
+
executableDir = path7.resolve(
|
|
1929
|
+
path7.dirname(resolvedScriptPath),
|
|
1930
1930
|
executableDir
|
|
1931
1931
|
);
|
|
1932
1932
|
}
|
|
1933
1933
|
if (executableDir) {
|
|
1934
1934
|
let localFile = findFile(executableDir, executableFile);
|
|
1935
1935
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
1936
|
-
const legacyName =
|
|
1936
|
+
const legacyName = path7.basename(
|
|
1937
1937
|
this._scriptPath,
|
|
1938
|
-
|
|
1938
|
+
path7.extname(this._scriptPath)
|
|
1939
1939
|
);
|
|
1940
1940
|
if (legacyName !== this._name) {
|
|
1941
1941
|
localFile = findFile(
|
|
@@ -1946,7 +1946,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1946
1946
|
}
|
|
1947
1947
|
executableFile = localFile || executableFile;
|
|
1948
1948
|
}
|
|
1949
|
-
launchWithNode = sourceExt.includes(
|
|
1949
|
+
launchWithNode = sourceExt.includes(path7.extname(executableFile));
|
|
1950
1950
|
let proc;
|
|
1951
1951
|
if (process22.platform !== "win32") {
|
|
1952
1952
|
if (launchWithNode) {
|
|
@@ -2786,7 +2786,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2786
2786
|
* @return {Command}
|
|
2787
2787
|
*/
|
|
2788
2788
|
nameFromFilename(filename) {
|
|
2789
|
-
this._name =
|
|
2789
|
+
this._name = path7.basename(filename, path7.extname(filename));
|
|
2790
2790
|
return this;
|
|
2791
2791
|
}
|
|
2792
2792
|
/**
|
|
@@ -2800,9 +2800,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2800
2800
|
* @param {string} [path]
|
|
2801
2801
|
* @return {(string|null|Command)}
|
|
2802
2802
|
*/
|
|
2803
|
-
executableDir(
|
|
2804
|
-
if (
|
|
2805
|
-
this._executableDir =
|
|
2803
|
+
executableDir(path8) {
|
|
2804
|
+
if (path8 === void 0) return this._executableDir;
|
|
2805
|
+
this._executableDir = path8;
|
|
2806
2806
|
return this;
|
|
2807
2807
|
}
|
|
2808
2808
|
/**
|
|
@@ -5797,16 +5797,16 @@ var require_validate = __commonJS({
|
|
|
5797
5797
|
const matches = RELATIVE_JSON_POINTER.exec($data);
|
|
5798
5798
|
if (!matches)
|
|
5799
5799
|
throw new Error(`Invalid JSON-pointer: ${$data}`);
|
|
5800
|
-
const
|
|
5800
|
+
const up2 = +matches[1];
|
|
5801
5801
|
jsonPointer = matches[2];
|
|
5802
5802
|
if (jsonPointer === "#") {
|
|
5803
|
-
if (
|
|
5804
|
-
throw new Error(errorMsg("property/index",
|
|
5805
|
-
return dataPathArr[dataLevel -
|
|
5803
|
+
if (up2 >= dataLevel)
|
|
5804
|
+
throw new Error(errorMsg("property/index", up2));
|
|
5805
|
+
return dataPathArr[dataLevel - up2];
|
|
5806
5806
|
}
|
|
5807
|
-
if (
|
|
5808
|
-
throw new Error(errorMsg("data",
|
|
5809
|
-
data = dataNames[dataLevel -
|
|
5807
|
+
if (up2 > dataLevel)
|
|
5808
|
+
throw new Error(errorMsg("data", up2));
|
|
5809
|
+
data = dataNames[dataLevel - up2];
|
|
5810
5810
|
if (!jsonPointer)
|
|
5811
5811
|
return data;
|
|
5812
5812
|
}
|
|
@@ -5819,8 +5819,8 @@ var require_validate = __commonJS({
|
|
|
5819
5819
|
}
|
|
5820
5820
|
}
|
|
5821
5821
|
return expr;
|
|
5822
|
-
function errorMsg(pointerType,
|
|
5823
|
-
return `Cannot access ${pointerType} ${
|
|
5822
|
+
function errorMsg(pointerType, up2) {
|
|
5823
|
+
return `Cannot access ${pointerType} ${up2} levels up, current level is ${dataLevel}`;
|
|
5824
5824
|
}
|
|
5825
5825
|
}
|
|
5826
5826
|
exports.getData = getData;
|
|
@@ -5984,7 +5984,7 @@ var require_compile = __commonJS({
|
|
|
5984
5984
|
const schOrFunc = root.refs[ref];
|
|
5985
5985
|
if (schOrFunc)
|
|
5986
5986
|
return schOrFunc;
|
|
5987
|
-
let _sch =
|
|
5987
|
+
let _sch = resolve5.call(this, root, ref);
|
|
5988
5988
|
if (_sch === void 0) {
|
|
5989
5989
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
5990
5990
|
const { schemaId } = this.opts;
|
|
@@ -6011,7 +6011,7 @@ var require_compile = __commonJS({
|
|
|
6011
6011
|
function sameSchemaEnv(s1, s2) {
|
|
6012
6012
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
6013
6013
|
}
|
|
6014
|
-
function
|
|
6014
|
+
function resolve5(root, ref) {
|
|
6015
6015
|
let sch;
|
|
6016
6016
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
6017
6017
|
ref = sch;
|
|
@@ -6226,8 +6226,8 @@ var require_utils = __commonJS({
|
|
|
6226
6226
|
}
|
|
6227
6227
|
return ind;
|
|
6228
6228
|
}
|
|
6229
|
-
function removeDotSegments(
|
|
6230
|
-
let input =
|
|
6229
|
+
function removeDotSegments(path7) {
|
|
6230
|
+
let input = path7;
|
|
6231
6231
|
const output = [];
|
|
6232
6232
|
let nextSlash = -1;
|
|
6233
6233
|
let len = 0;
|
|
@@ -6426,8 +6426,8 @@ var require_schemes = __commonJS({
|
|
|
6426
6426
|
wsComponent.secure = void 0;
|
|
6427
6427
|
}
|
|
6428
6428
|
if (wsComponent.resourceName) {
|
|
6429
|
-
const [
|
|
6430
|
-
wsComponent.path =
|
|
6429
|
+
const [path7, query] = wsComponent.resourceName.split("?");
|
|
6430
|
+
wsComponent.path = path7 && path7 !== "/" ? path7 : void 0;
|
|
6431
6431
|
wsComponent.query = query;
|
|
6432
6432
|
wsComponent.resourceName = void 0;
|
|
6433
6433
|
}
|
|
@@ -6586,55 +6586,55 @@ var require_fast_uri = __commonJS({
|
|
|
6586
6586
|
}
|
|
6587
6587
|
return uri;
|
|
6588
6588
|
}
|
|
6589
|
-
function
|
|
6589
|
+
function resolve5(baseURI, relativeURI, options) {
|
|
6590
6590
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
6591
6591
|
const resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
6592
6592
|
schemelessOptions.skipEscape = true;
|
|
6593
6593
|
return serialize(resolved, schemelessOptions);
|
|
6594
6594
|
}
|
|
6595
|
-
function resolveComponent(base,
|
|
6595
|
+
function resolveComponent(base, relative3, options, skipNormalization) {
|
|
6596
6596
|
const target = {};
|
|
6597
6597
|
if (!skipNormalization) {
|
|
6598
6598
|
base = parse(serialize(base, options), options);
|
|
6599
|
-
|
|
6599
|
+
relative3 = parse(serialize(relative3, 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 && 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;
|
|
6609
6609
|
} else {
|
|
6610
|
-
if (
|
|
6611
|
-
target.userinfo =
|
|
6612
|
-
target.host =
|
|
6613
|
-
target.port =
|
|
6614
|
-
target.path = removeDotSegments(
|
|
6615
|
-
target.query =
|
|
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;
|
|
6616
6616
|
} else {
|
|
6617
|
-
if (!
|
|
6617
|
+
if (!relative3.path) {
|
|
6618
6618
|
target.path = base.path;
|
|
6619
|
-
if (
|
|
6620
|
-
target.query =
|
|
6619
|
+
if (relative3.query !== void 0) {
|
|
6620
|
+
target.query = relative3.query;
|
|
6621
6621
|
} else {
|
|
6622
6622
|
target.query = base.query;
|
|
6623
6623
|
}
|
|
6624
6624
|
} else {
|
|
6625
|
-
if (
|
|
6626
|
-
target.path = removeDotSegments(
|
|
6625
|
+
if (relative3.path[0] === "/") {
|
|
6626
|
+
target.path = removeDotSegments(relative3.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 = "/" + relative3.path;
|
|
6630
6630
|
} else if (!base.path) {
|
|
6631
|
-
target.path =
|
|
6631
|
+
target.path = relative3.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) + relative3.path;
|
|
6634
6634
|
}
|
|
6635
6635
|
target.path = removeDotSegments(target.path);
|
|
6636
6636
|
}
|
|
6637
|
-
target.query =
|
|
6637
|
+
target.query = relative3.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 = relative3.fragment;
|
|
6646
6646
|
return target;
|
|
6647
6647
|
}
|
|
6648
6648
|
function equal(uriA, uriB, options) {
|
|
@@ -6813,7 +6813,7 @@ var require_fast_uri = __commonJS({
|
|
|
6813
6813
|
var fastUri = {
|
|
6814
6814
|
SCHEMES,
|
|
6815
6815
|
normalize,
|
|
6816
|
-
resolve:
|
|
6816
|
+
resolve: resolve5,
|
|
6817
6817
|
resolveComponent,
|
|
6818
6818
|
equal,
|
|
6819
6819
|
serialize,
|
|
@@ -17834,6 +17834,11 @@ var {
|
|
|
17834
17834
|
Help
|
|
17835
17835
|
} = import_index.default;
|
|
17836
17836
|
|
|
17837
|
+
// src/index.ts
|
|
17838
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
17839
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
17840
|
+
import { dirname, join as join6 } from "path";
|
|
17841
|
+
|
|
17837
17842
|
// src/commands/login.ts
|
|
17838
17843
|
import http from "http";
|
|
17839
17844
|
import { createInterface } from "readline";
|
|
@@ -18766,14 +18771,14 @@ var baseOpen = async (options) => {
|
|
|
18766
18771
|
}
|
|
18767
18772
|
const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
|
|
18768
18773
|
if (options.wait) {
|
|
18769
|
-
return new Promise((
|
|
18774
|
+
return new Promise((resolve5, reject) => {
|
|
18770
18775
|
subprocess.once("error", reject);
|
|
18771
18776
|
subprocess.once("close", (exitCode) => {
|
|
18772
18777
|
if (!options.allowNonzeroExitCode && exitCode > 0) {
|
|
18773
18778
|
reject(new Error(`Exited with code ${exitCode}`));
|
|
18774
18779
|
return;
|
|
18775
18780
|
}
|
|
18776
|
-
|
|
18781
|
+
resolve5(subprocess);
|
|
18777
18782
|
});
|
|
18778
18783
|
});
|
|
18779
18784
|
}
|
|
@@ -18866,12 +18871,12 @@ var disallowedKeys = /* @__PURE__ */ new Set([
|
|
|
18866
18871
|
"constructor"
|
|
18867
18872
|
]);
|
|
18868
18873
|
var digits = new Set("0123456789");
|
|
18869
|
-
function getPathSegments(
|
|
18874
|
+
function getPathSegments(path7) {
|
|
18870
18875
|
const parts = [];
|
|
18871
18876
|
let currentSegment = "";
|
|
18872
18877
|
let currentPart = "start";
|
|
18873
18878
|
let isIgnoring = false;
|
|
18874
|
-
for (const character of
|
|
18879
|
+
for (const character of path7) {
|
|
18875
18880
|
switch (character) {
|
|
18876
18881
|
case "\\": {
|
|
18877
18882
|
if (currentPart === "index") {
|
|
@@ -18993,11 +18998,11 @@ function assertNotStringIndex(object, key) {
|
|
|
18993
18998
|
throw new Error("Cannot use string index");
|
|
18994
18999
|
}
|
|
18995
19000
|
}
|
|
18996
|
-
function getProperty(object,
|
|
18997
|
-
if (!isObject(object) || typeof
|
|
19001
|
+
function getProperty(object, path7, value) {
|
|
19002
|
+
if (!isObject(object) || typeof path7 !== "string") {
|
|
18998
19003
|
return value === void 0 ? object : value;
|
|
18999
19004
|
}
|
|
19000
|
-
const pathArray = getPathSegments(
|
|
19005
|
+
const pathArray = getPathSegments(path7);
|
|
19001
19006
|
if (pathArray.length === 0) {
|
|
19002
19007
|
return value;
|
|
19003
19008
|
}
|
|
@@ -19017,12 +19022,12 @@ function getProperty(object, path6, value) {
|
|
|
19017
19022
|
}
|
|
19018
19023
|
return object === void 0 ? value : object;
|
|
19019
19024
|
}
|
|
19020
|
-
function setProperty(object,
|
|
19021
|
-
if (!isObject(object) || typeof
|
|
19025
|
+
function setProperty(object, path7, value) {
|
|
19026
|
+
if (!isObject(object) || typeof path7 !== "string") {
|
|
19022
19027
|
return object;
|
|
19023
19028
|
}
|
|
19024
19029
|
const root = object;
|
|
19025
|
-
const pathArray = getPathSegments(
|
|
19030
|
+
const pathArray = getPathSegments(path7);
|
|
19026
19031
|
for (let index = 0; index < pathArray.length; index++) {
|
|
19027
19032
|
const key = pathArray[index];
|
|
19028
19033
|
assertNotStringIndex(object, key);
|
|
@@ -19035,11 +19040,11 @@ function setProperty(object, path6, value) {
|
|
|
19035
19040
|
}
|
|
19036
19041
|
return root;
|
|
19037
19042
|
}
|
|
19038
|
-
function deleteProperty(object,
|
|
19039
|
-
if (!isObject(object) || typeof
|
|
19043
|
+
function deleteProperty(object, path7) {
|
|
19044
|
+
if (!isObject(object) || typeof path7 !== "string") {
|
|
19040
19045
|
return false;
|
|
19041
19046
|
}
|
|
19042
|
-
const pathArray = getPathSegments(
|
|
19047
|
+
const pathArray = getPathSegments(path7);
|
|
19043
19048
|
for (let index = 0; index < pathArray.length; index++) {
|
|
19044
19049
|
const key = pathArray[index];
|
|
19045
19050
|
assertNotStringIndex(object, key);
|
|
@@ -19053,11 +19058,11 @@ function deleteProperty(object, path6) {
|
|
|
19053
19058
|
}
|
|
19054
19059
|
}
|
|
19055
19060
|
}
|
|
19056
|
-
function hasProperty(object,
|
|
19057
|
-
if (!isObject(object) || typeof
|
|
19061
|
+
function hasProperty(object, path7) {
|
|
19062
|
+
if (!isObject(object) || typeof path7 !== "string") {
|
|
19058
19063
|
return false;
|
|
19059
19064
|
}
|
|
19060
|
-
const pathArray = getPathSegments(
|
|
19065
|
+
const pathArray = getPathSegments(path7);
|
|
19061
19066
|
if (pathArray.length === 0) {
|
|
19062
19067
|
return false;
|
|
19063
19068
|
}
|
|
@@ -19176,7 +19181,7 @@ var retryifyAsync = (fn, options) => {
|
|
|
19176
19181
|
throw error;
|
|
19177
19182
|
const delay = Math.round(interval * Math.random());
|
|
19178
19183
|
if (delay > 0) {
|
|
19179
|
-
const delayPromise = new Promise((
|
|
19184
|
+
const delayPromise = new Promise((resolve5) => setTimeout(resolve5, delay));
|
|
19180
19185
|
return delayPromise.then(() => attempt.apply(void 0, args));
|
|
19181
19186
|
} else {
|
|
19182
19187
|
return attempt.apply(void 0, args);
|
|
@@ -20180,9 +20185,9 @@ async function getWsToken() {
|
|
|
20180
20185
|
);
|
|
20181
20186
|
return data.token;
|
|
20182
20187
|
}
|
|
20183
|
-
async function api(
|
|
20188
|
+
async function api(path7, options = {}) {
|
|
20184
20189
|
const { accessToken } = getTokens();
|
|
20185
|
-
const url = `${getApiUrl()}${
|
|
20190
|
+
const url = `${getApiUrl()}${path7}`;
|
|
20186
20191
|
const headers = {
|
|
20187
20192
|
"Content-Type": "application/json",
|
|
20188
20193
|
...options.headers || {}
|
|
@@ -20211,9 +20216,9 @@ async function api(path6, options = {}) {
|
|
|
20211
20216
|
}
|
|
20212
20217
|
return res.json();
|
|
20213
20218
|
}
|
|
20214
|
-
async function* streamApi(
|
|
20219
|
+
async function* streamApi(path7, options = {}) {
|
|
20215
20220
|
const { accessToken } = getTokens();
|
|
20216
|
-
const url = `${getApiUrl()}${
|
|
20221
|
+
const url = `${getApiUrl()}${path7}`;
|
|
20217
20222
|
const headers = {
|
|
20218
20223
|
"Content-Type": "application/json",
|
|
20219
20224
|
Accept: "text/event-stream",
|
|
@@ -20351,7 +20356,7 @@ function registerLogin(program3) {
|
|
|
20351
20356
|
});
|
|
20352
20357
|
}
|
|
20353
20358
|
function waitForOAuth(apiUrl) {
|
|
20354
|
-
return new Promise((
|
|
20359
|
+
return new Promise((resolve5, reject) => {
|
|
20355
20360
|
let settled = false;
|
|
20356
20361
|
function settle() {
|
|
20357
20362
|
if (settled) return false;
|
|
@@ -20373,7 +20378,7 @@ function waitForOAuth(apiUrl) {
|
|
|
20373
20378
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
20374
20379
|
res.end(authPage(true), () => {
|
|
20375
20380
|
if (settle()) {
|
|
20376
|
-
|
|
20381
|
+
resolve5({ accessToken, refreshToken });
|
|
20377
20382
|
}
|
|
20378
20383
|
});
|
|
20379
20384
|
} else {
|
|
@@ -20397,7 +20402,7 @@ function waitForOAuth(apiUrl) {
|
|
|
20397
20402
|
const rt = pastedUrl.searchParams.get("refresh_token");
|
|
20398
20403
|
if (at && rt) {
|
|
20399
20404
|
if (settle()) {
|
|
20400
|
-
|
|
20405
|
+
resolve5({ accessToken: at, refreshToken: rt });
|
|
20401
20406
|
}
|
|
20402
20407
|
}
|
|
20403
20408
|
} catch {
|
|
@@ -21317,13 +21322,13 @@ function configPath() {
|
|
|
21317
21322
|
return resolve(process.cwd(), CONFIG_FILE);
|
|
21318
21323
|
}
|
|
21319
21324
|
function readContext() {
|
|
21320
|
-
const
|
|
21321
|
-
if (!existsSync(
|
|
21325
|
+
const path7 = configPath();
|
|
21326
|
+
if (!existsSync(path7)) {
|
|
21322
21327
|
throw new Error(
|
|
21323
21328
|
`No ${CONFIG_FILE} found in current directory. Run "lm init" first.`
|
|
21324
21329
|
);
|
|
21325
21330
|
}
|
|
21326
|
-
const raw = readFileSync(
|
|
21331
|
+
const raw = readFileSync(path7, "utf-8");
|
|
21327
21332
|
try {
|
|
21328
21333
|
return JSON.parse(raw);
|
|
21329
21334
|
} catch {
|
|
@@ -21339,9 +21344,9 @@ function contextExists() {
|
|
|
21339
21344
|
return existsSync(configPath());
|
|
21340
21345
|
}
|
|
21341
21346
|
function removeContext() {
|
|
21342
|
-
const
|
|
21343
|
-
if (existsSync(
|
|
21344
|
-
unlinkSync(
|
|
21347
|
+
const path7 = configPath();
|
|
21348
|
+
if (existsSync(path7)) {
|
|
21349
|
+
unlinkSync(path7);
|
|
21345
21350
|
}
|
|
21346
21351
|
}
|
|
21347
21352
|
|
|
@@ -21354,14 +21359,14 @@ function detectLocal(cwd) {
|
|
|
21354
21359
|
return { runtime: "docker", port: 8080, confidence: "high" };
|
|
21355
21360
|
}
|
|
21356
21361
|
if (existsSync2(join(cwd, "bun.lockb")) || existsSync2(join(cwd, "bunfig.toml"))) {
|
|
21357
|
-
const
|
|
21358
|
-
const framework = detectFramework(
|
|
21362
|
+
const pkg2 = readPkg(cwd);
|
|
21363
|
+
const framework = detectFramework(pkg2, cwd);
|
|
21359
21364
|
const port = frameworkPort(framework) ?? 3e3;
|
|
21360
21365
|
return {
|
|
21361
21366
|
runtime: "bun",
|
|
21362
21367
|
framework,
|
|
21363
|
-
buildCmd:
|
|
21364
|
-
startCmd:
|
|
21368
|
+
buildCmd: pkg2?.scripts?.build ? "bun run build" : void 0,
|
|
21369
|
+
startCmd: pkg2?.scripts?.start ? "bun run start" : "bun index.ts",
|
|
21365
21370
|
port,
|
|
21366
21371
|
confidence: "high"
|
|
21367
21372
|
};
|
|
@@ -21382,8 +21387,8 @@ function detectLocal(cwd) {
|
|
|
21382
21387
|
};
|
|
21383
21388
|
}
|
|
21384
21389
|
if (existsSync2(join(cwd, "package.json"))) {
|
|
21385
|
-
const
|
|
21386
|
-
const deps = { ...
|
|
21390
|
+
const pkg2 = readPkg(cwd);
|
|
21391
|
+
const deps = { ...pkg2.dependencies, ...pkg2.devDependencies };
|
|
21387
21392
|
if (deps?.["stirrup-ai"]) {
|
|
21388
21393
|
return {
|
|
21389
21394
|
runtime: "nodejs",
|
|
@@ -21411,9 +21416,9 @@ function detectLocal(cwd) {
|
|
|
21411
21416
|
if (existsSync2(join(cwd, "pnpm-lock.yaml"))) pm = "pnpm";
|
|
21412
21417
|
else if (existsSync2(join(cwd, "yarn.lock"))) pm = "yarn";
|
|
21413
21418
|
const prefix = pm === "pnpm" ? "pnpm" : pm === "yarn" ? "yarn" : "npm";
|
|
21414
|
-
const buildCmd =
|
|
21415
|
-
const startCmd =
|
|
21416
|
-
const framework = detectFramework(
|
|
21419
|
+
const buildCmd = pkg2.scripts?.build ? `${prefix} run build` : void 0;
|
|
21420
|
+
const startCmd = pkg2.scripts?.start ? `${prefix} start` : "node index.js";
|
|
21421
|
+
const framework = detectFramework(pkg2, cwd);
|
|
21417
21422
|
const port = frameworkPort(framework) ?? 3e3;
|
|
21418
21423
|
return {
|
|
21419
21424
|
runtime: "nodejs",
|
|
@@ -21562,9 +21567,9 @@ function readPkg(cwd) {
|
|
|
21562
21567
|
return void 0;
|
|
21563
21568
|
}
|
|
21564
21569
|
}
|
|
21565
|
-
function detectFramework(
|
|
21566
|
-
if (!
|
|
21567
|
-
const deps = { ...
|
|
21570
|
+
function detectFramework(pkg2, cwd) {
|
|
21571
|
+
if (!pkg2) return void 0;
|
|
21572
|
+
const deps = { ...pkg2.dependencies, ...pkg2.devDependencies };
|
|
21568
21573
|
if (existsSync2(join(cwd, "next.config.js")) || existsSync2(join(cwd, "next.config.mjs")) || existsSync2(join(cwd, "next.config.ts")))
|
|
21569
21574
|
return "Next.js";
|
|
21570
21575
|
if (existsSync2(join(cwd, "nuxt.config.js")) || existsSync2(join(cwd, "nuxt.config.ts")))
|
|
@@ -21803,10 +21808,10 @@ function prompt(question) {
|
|
|
21803
21808
|
input: process.stdin,
|
|
21804
21809
|
output: process.stdout
|
|
21805
21810
|
});
|
|
21806
|
-
return new Promise((
|
|
21811
|
+
return new Promise((resolve5) => {
|
|
21807
21812
|
rl.question(question, (answer) => {
|
|
21808
21813
|
rl.close();
|
|
21809
|
-
|
|
21814
|
+
resolve5(answer.trim());
|
|
21810
21815
|
});
|
|
21811
21816
|
});
|
|
21812
21817
|
}
|
|
@@ -21824,7 +21829,13 @@ var wrapper_default = import_websocket.default;
|
|
|
21824
21829
|
|
|
21825
21830
|
// src/commands/deploy.ts
|
|
21826
21831
|
function registerDeploy(program3) {
|
|
21827
|
-
program3.command("deploy").description("Build and deploy the current service").
|
|
21832
|
+
program3.command("deploy").description("Build and deploy the current service").option(
|
|
21833
|
+
"--dockerfile <path>",
|
|
21834
|
+
"Path to Dockerfile relative to repo root (e.g. apps/web/Dockerfile). Use when build context must be the repo root but the Dockerfile lives in a subdirectory (monorepos). Pass an empty string to clear."
|
|
21835
|
+
).option(
|
|
21836
|
+
"--root-dir <path>",
|
|
21837
|
+
"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) => {
|
|
21828
21839
|
if (!isLoggedIn()) {
|
|
21829
21840
|
console.error(source_default.red('Not logged in. Run "lm login" first.'));
|
|
21830
21841
|
process.exitCode = 1;
|
|
@@ -21863,13 +21874,60 @@ function registerDeploy(program3) {
|
|
|
21863
21874
|
}
|
|
21864
21875
|
}
|
|
21865
21876
|
if (!gitInfo.hasRemote) {
|
|
21866
|
-
|
|
21867
|
-
|
|
21868
|
-
|
|
21869
|
-
|
|
21870
|
-
|
|
21871
|
-
|
|
21872
|
-
|
|
21877
|
+
const repoSpinner = ora("No git remote \u2014 creating GitHub repository...").start();
|
|
21878
|
+
try {
|
|
21879
|
+
try {
|
|
21880
|
+
execFileSync4("git", ["rev-parse", "--git-dir"], { cwd, stdio: "pipe" });
|
|
21881
|
+
} catch {
|
|
21882
|
+
execFileSync4("git", ["init"], { cwd, stdio: "pipe" });
|
|
21883
|
+
}
|
|
21884
|
+
try {
|
|
21885
|
+
const status = execFileSync4("git", ["status", "--porcelain"], {
|
|
21886
|
+
cwd,
|
|
21887
|
+
encoding: "utf-8",
|
|
21888
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
21889
|
+
});
|
|
21890
|
+
if (status.trim().length > 0) {
|
|
21891
|
+
execFileSync4("git", ["add", "-A"], { cwd, stdio: "pipe" });
|
|
21892
|
+
execFileSync4(
|
|
21893
|
+
"git",
|
|
21894
|
+
["commit", "-m", "Initial commit"],
|
|
21895
|
+
{ cwd, stdio: "pipe" }
|
|
21896
|
+
);
|
|
21897
|
+
}
|
|
21898
|
+
} catch {
|
|
21899
|
+
}
|
|
21900
|
+
const { data: repoData } = await api(`/api/services/${ctx.serviceId}/create-repo`, {
|
|
21901
|
+
method: "POST",
|
|
21902
|
+
body: JSON.stringify({})
|
|
21903
|
+
});
|
|
21904
|
+
try {
|
|
21905
|
+
execFileSync4("git", ["remote", "add", "origin", repoData.cloneUrl], { cwd, stdio: "pipe" });
|
|
21906
|
+
} catch {
|
|
21907
|
+
execFileSync4("git", ["remote", "set-url", "origin", repoData.cloneUrl], { cwd, stdio: "pipe" });
|
|
21908
|
+
}
|
|
21909
|
+
const localBranch = (() => {
|
|
21910
|
+
try {
|
|
21911
|
+
return execFileSync4("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd, encoding: "utf-8" }).trim();
|
|
21912
|
+
} catch {
|
|
21913
|
+
return "main";
|
|
21914
|
+
}
|
|
21915
|
+
})();
|
|
21916
|
+
execFileSync4("git", ["push", "-u", "origin", localBranch], { cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
21917
|
+
repoSpinner.succeed(`Repository created: ${source_default.dim(repoData.repoFullName)} (private)`);
|
|
21918
|
+
gitInfo = getGitInfo(cwd);
|
|
21919
|
+
} catch (err) {
|
|
21920
|
+
repoSpinner.fail(
|
|
21921
|
+
`Could not auto-create repo: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
21922
|
+
);
|
|
21923
|
+
console.error(
|
|
21924
|
+
source_default.red(
|
|
21925
|
+
"Add a GitHub remote manually (git remote add origin ...) or re-run 'lm init'."
|
|
21926
|
+
)
|
|
21927
|
+
);
|
|
21928
|
+
process.exitCode = 1;
|
|
21929
|
+
return;
|
|
21930
|
+
}
|
|
21873
21931
|
}
|
|
21874
21932
|
const commitSha = gitInfo.commitSha || "";
|
|
21875
21933
|
const shortSha = commitSha ? commitSha.slice(0, 7) : "unknown";
|
|
@@ -21894,6 +21952,12 @@ function registerDeploy(program3) {
|
|
|
21894
21952
|
if (detection.startCmd) patch.startCmd = detection.startCmd;
|
|
21895
21953
|
if (detection.port) patch.port = detection.port;
|
|
21896
21954
|
if (detection.framework) patch.framework = detection.framework;
|
|
21955
|
+
if (opts.dockerfile !== void 0) {
|
|
21956
|
+
patch.dockerfilePath = opts.dockerfile === "" ? null : opts.dockerfile;
|
|
21957
|
+
}
|
|
21958
|
+
if (opts.rootDir) patch.rootDir = opts.rootDir;
|
|
21959
|
+
if (opts.buildCmd) patch.buildCmd = opts.buildCmd;
|
|
21960
|
+
if (opts.startCmd) patch.startCmd = opts.startCmd;
|
|
21897
21961
|
if (Object.keys(patch).length > 0) {
|
|
21898
21962
|
try {
|
|
21899
21963
|
await api(`/api/services/${ctx.serviceId}`, {
|
|
@@ -21917,7 +21981,7 @@ function registerDeploy(program3) {
|
|
|
21917
21981
|
const apiUrl = getApiUrl();
|
|
21918
21982
|
const wsUrl = apiUrl.replace(/^http/, "ws");
|
|
21919
21983
|
const wsToken = await getWsToken();
|
|
21920
|
-
await new Promise((
|
|
21984
|
+
await new Promise((resolve5, reject) => {
|
|
21921
21985
|
let statusResolved = false;
|
|
21922
21986
|
const logWs = new wrapper_default(
|
|
21923
21987
|
`${wsUrl}/ws/logs/${deployment.id}?token=${wsToken}`
|
|
@@ -21958,7 +22022,7 @@ function registerDeploy(program3) {
|
|
|
21958
22022
|
spinner.succeed(
|
|
21959
22023
|
source_default.green(`Deployed \u2192 ${source_default.bold(`https://${url}`)}`)
|
|
21960
22024
|
);
|
|
21961
|
-
|
|
22025
|
+
resolve5();
|
|
21962
22026
|
} else if (parsed.status === "FAILED" && !statusResolved) {
|
|
21963
22027
|
statusResolved = true;
|
|
21964
22028
|
logWs.close();
|
|
@@ -21983,7 +22047,7 @@ function registerDeploy(program3) {
|
|
|
21983
22047
|
if (!statusResolved) {
|
|
21984
22048
|
statusResolved = true;
|
|
21985
22049
|
logWs.close();
|
|
21986
|
-
pollDeploymentStatus(deployment.id, spinner).then(
|
|
22050
|
+
pollDeploymentStatus(deployment.id, spinner).then(resolve5).catch(reject);
|
|
21987
22051
|
}
|
|
21988
22052
|
});
|
|
21989
22053
|
});
|
|
@@ -22306,10 +22370,10 @@ function prompt2(question) {
|
|
|
22306
22370
|
input: process.stdin,
|
|
22307
22371
|
output: process.stdout
|
|
22308
22372
|
});
|
|
22309
|
-
return new Promise((
|
|
22373
|
+
return new Promise((resolve5) => {
|
|
22310
22374
|
rl.question(question, (answer) => {
|
|
22311
22375
|
rl.close();
|
|
22312
|
-
|
|
22376
|
+
resolve5(answer.trim());
|
|
22313
22377
|
});
|
|
22314
22378
|
});
|
|
22315
22379
|
}
|
|
@@ -22324,10 +22388,10 @@ function prompt3(question) {
|
|
|
22324
22388
|
input: process.stdin,
|
|
22325
22389
|
output: process.stdout
|
|
22326
22390
|
});
|
|
22327
|
-
return new Promise((
|
|
22391
|
+
return new Promise((resolve5) => {
|
|
22328
22392
|
rl.question(question, (answer) => {
|
|
22329
22393
|
rl.close();
|
|
22330
|
-
|
|
22394
|
+
resolve5(answer.trim());
|
|
22331
22395
|
});
|
|
22332
22396
|
});
|
|
22333
22397
|
}
|
|
@@ -22573,10 +22637,10 @@ function toSlug2(name) {
|
|
|
22573
22637
|
}
|
|
22574
22638
|
function askInput(question) {
|
|
22575
22639
|
const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
|
|
22576
|
-
return new Promise((
|
|
22640
|
+
return new Promise((resolve5) => {
|
|
22577
22641
|
rl.question(question, (answer) => {
|
|
22578
22642
|
rl.close();
|
|
22579
|
-
|
|
22643
|
+
resolve5(answer.trim());
|
|
22580
22644
|
});
|
|
22581
22645
|
});
|
|
22582
22646
|
}
|
|
@@ -22864,7 +22928,7 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
|
|
|
22864
22928
|
const apiUrl = getApiUrl();
|
|
22865
22929
|
const wsUrl = apiUrl.replace(/^http/, "ws");
|
|
22866
22930
|
const wsToken = await getWsToken();
|
|
22867
|
-
await new Promise((
|
|
22931
|
+
await new Promise((resolve5, reject) => {
|
|
22868
22932
|
let done = false;
|
|
22869
22933
|
const logWs = new wrapper_default(
|
|
22870
22934
|
`${wsUrl}/ws/logs/${deployment.id}?token=${wsToken}`
|
|
@@ -22901,7 +22965,7 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
|
|
|
22901
22965
|
deploySpinner.succeed(
|
|
22902
22966
|
source_default.green(`Live \u2192 ${source_default.bold(`https://${url}`)}`)
|
|
22903
22967
|
);
|
|
22904
|
-
|
|
22968
|
+
resolve5();
|
|
22905
22969
|
} else if (parsed.status === "FAILED" && !done) {
|
|
22906
22970
|
done = true;
|
|
22907
22971
|
logWs.close();
|
|
@@ -22924,7 +22988,7 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
|
|
|
22924
22988
|
if (!done) {
|
|
22925
22989
|
done = true;
|
|
22926
22990
|
logWs.close();
|
|
22927
|
-
pollStatus(deployment.id, deploySpinner).then(
|
|
22991
|
+
pollStatus(deployment.id, deploySpinner).then(resolve5).catch(reject);
|
|
22928
22992
|
}
|
|
22929
22993
|
});
|
|
22930
22994
|
});
|
|
@@ -22986,12 +23050,12 @@ async function ensureChromium() {
|
|
|
22986
23050
|
process.exit(1);
|
|
22987
23051
|
}
|
|
22988
23052
|
}
|
|
22989
|
-
const
|
|
22990
|
-
if (!
|
|
23053
|
+
const path7 = findChromium();
|
|
23054
|
+
if (!path7) {
|
|
22991
23055
|
console.error(source_default.red("Chromium installed but not found on disk."));
|
|
22992
23056
|
process.exit(1);
|
|
22993
23057
|
}
|
|
22994
|
-
return
|
|
23058
|
+
return path7;
|
|
22995
23059
|
}
|
|
22996
23060
|
async function launchBrowser(headless = true) {
|
|
22997
23061
|
const pw = await import("playwright-core");
|
|
@@ -23319,8 +23383,8 @@ function getRepo() {
|
|
|
23319
23383
|
}
|
|
23320
23384
|
return { owner: info.repoOwner, name: info.repoName, branch: info.repoBranch };
|
|
23321
23385
|
}
|
|
23322
|
-
async function ghApi(
|
|
23323
|
-
return api(`/api/github${
|
|
23386
|
+
async function ghApi(path7, options) {
|
|
23387
|
+
return api(`/api/github${path7}`, options);
|
|
23324
23388
|
}
|
|
23325
23389
|
function timeAgo(date) {
|
|
23326
23390
|
const diff = Date.now() - new Date(date).getTime();
|
|
@@ -23410,9 +23474,9 @@ function registerRepo(program3) {
|
|
|
23410
23474
|
}
|
|
23411
23475
|
const spinner = ora("Fetching issues...").start();
|
|
23412
23476
|
try {
|
|
23413
|
-
let
|
|
23414
|
-
if (opts.labels)
|
|
23415
|
-
const { data } = await ghApi(
|
|
23477
|
+
let path7 = `/repos/${owner}/${name}/issues?state=${opts.state}&limit=${opts.limit}`;
|
|
23478
|
+
if (opts.labels) path7 += `&labels=${encodeURIComponent(opts.labels)}`;
|
|
23479
|
+
const { data } = await ghApi(path7);
|
|
23416
23480
|
spinner.stop();
|
|
23417
23481
|
const issues = (data || []).filter((i) => !i.pull_request);
|
|
23418
23482
|
if (issues.length === 0) {
|
|
@@ -23580,11 +23644,11 @@ function registerRepo(program3) {
|
|
|
23580
23644
|
process.exitCode = 1;
|
|
23581
23645
|
}
|
|
23582
23646
|
});
|
|
23583
|
-
repo.command("browse").alias("web").description("Open repository in browser").argument("[path]", "File or path to open").option("-b, --branch <branch>", "Branch name").action(async (
|
|
23647
|
+
repo.command("browse").alias("web").description("Open repository in browser").argument("[path]", "File or path to open").option("-b, --branch <branch>", "Branch name").action(async (path7, opts) => {
|
|
23584
23648
|
const { owner, name, branch: currentBranch } = getRepo();
|
|
23585
23649
|
const branch = opts.branch || currentBranch;
|
|
23586
23650
|
let url = `https://github.com/${owner}/${name}`;
|
|
23587
|
-
if (
|
|
23651
|
+
if (path7) url += `/blob/${branch}/${path7}`;
|
|
23588
23652
|
else if (branch !== "main" && branch !== "master") url += `/tree/${branch}`;
|
|
23589
23653
|
openUrl(url);
|
|
23590
23654
|
console.log(source_default.dim(`Opened ${url}`));
|
|
@@ -23648,10 +23712,10 @@ async function getTeamId() {
|
|
|
23648
23712
|
}
|
|
23649
23713
|
function prompt4(question) {
|
|
23650
23714
|
const rl = readline5.createInterface({ input: process.stdin, output: process.stdout });
|
|
23651
|
-
return new Promise((
|
|
23715
|
+
return new Promise((resolve5) => {
|
|
23652
23716
|
rl.question(question, (answer) => {
|
|
23653
23717
|
rl.close();
|
|
23654
|
-
|
|
23718
|
+
resolve5(answer.trim());
|
|
23655
23719
|
});
|
|
23656
23720
|
});
|
|
23657
23721
|
}
|
|
@@ -23747,13 +23811,13 @@ function registerDb(program3) {
|
|
|
23747
23811
|
const url = data.connectionUrl;
|
|
23748
23812
|
if (opts.copy) {
|
|
23749
23813
|
try {
|
|
23750
|
-
const { execFileSync:
|
|
23814
|
+
const { execFileSync: execFileSync10 } = await import("child_process");
|
|
23751
23815
|
if (process.platform === "win32") {
|
|
23752
|
-
|
|
23816
|
+
execFileSync10("cmd", ["/c", `echo|set /p="${url}"| clip`], { stdio: "pipe" });
|
|
23753
23817
|
} else if (process.platform === "darwin") {
|
|
23754
|
-
|
|
23818
|
+
execFileSync10("pbcopy", [], { input: url, stdio: ["pipe", "pipe", "pipe"] });
|
|
23755
23819
|
} else {
|
|
23756
|
-
|
|
23820
|
+
execFileSync10("xclip", ["-selection", "clipboard"], { input: url, stdio: ["pipe", "pipe", "pipe"] });
|
|
23757
23821
|
}
|
|
23758
23822
|
console.log(source_default.green("\u2713 Connection URL copied to clipboard"));
|
|
23759
23823
|
} catch {
|
|
@@ -23916,7 +23980,7 @@ function registerDb(program3) {
|
|
|
23916
23980
|
process.exitCode = 1;
|
|
23917
23981
|
}
|
|
23918
23982
|
});
|
|
23919
|
-
db.command("create <name>").description("Create a new database").option("-e, --engine <engine>", "Engine: postgresql, redis, mongodb", "postgresql").option("-v, --version <version>", "Engine version").option("-s, --storage <mb>", "Storage size in MB", "1024").option("--service <serviceId>", "Link to a service").option("--project <projectId>", "Project to attach to").action(async (name, opts) => {
|
|
23983
|
+
db.command("create <name>").description("Create a new database").option("-e, --engine <engine>", "Engine: postgresql, postgis, redis, mongodb", "postgresql").option("-v, --version <version>", "Engine version").option("-s, --storage <mb>", "Storage size in MB", "1024").option("--service <serviceId>", "Link to a service").option("--project <projectId>", "Project to attach to").action(async (name, opts) => {
|
|
23920
23984
|
requireLogin2();
|
|
23921
23985
|
const spinner = ora(`Creating ${opts.engine} database "${name}"...`).start();
|
|
23922
23986
|
try {
|
|
@@ -23991,8 +24055,8 @@ function registerDb(program3) {
|
|
|
23991
24055
|
body: JSON.stringify({ command: ["pg_dump", "-U", "postgres", "--no-owner", "--no-acl"] })
|
|
23992
24056
|
});
|
|
23993
24057
|
if (opts.output) {
|
|
23994
|
-
const { writeFileSync:
|
|
23995
|
-
|
|
24058
|
+
const { writeFileSync: writeFileSync4 } = await import("fs");
|
|
24059
|
+
writeFileSync4(opts.output, data.output);
|
|
23996
24060
|
spinner?.succeed(`Dump saved \u2192 ${source_default.cyan(opts.output)}`);
|
|
23997
24061
|
} else {
|
|
23998
24062
|
process.stdout.write(data.output);
|
|
@@ -24020,8 +24084,8 @@ function registerDb(program3) {
|
|
|
24020
24084
|
requireLogin2();
|
|
24021
24085
|
const spinner = ora(`Running ${file}...`).start();
|
|
24022
24086
|
try {
|
|
24023
|
-
const { readFileSync:
|
|
24024
|
-
const sql =
|
|
24087
|
+
const { readFileSync: readFileSync9 } = await import("fs");
|
|
24088
|
+
const sql = readFileSync9(file, "utf-8");
|
|
24025
24089
|
const { data } = await api(`/api/databases/${dbId}/query`, {
|
|
24026
24090
|
method: "POST",
|
|
24027
24091
|
body: JSON.stringify({ sql })
|
|
@@ -24156,9 +24220,9 @@ function validatePackageJson(files, ctx) {
|
|
|
24156
24220
|
}
|
|
24157
24221
|
return issues;
|
|
24158
24222
|
}
|
|
24159
|
-
let
|
|
24223
|
+
let pkg2;
|
|
24160
24224
|
try {
|
|
24161
|
-
|
|
24225
|
+
pkg2 = JSON.parse(pkgFile.content);
|
|
24162
24226
|
} catch (e) {
|
|
24163
24227
|
issues.push({
|
|
24164
24228
|
category: "package-json",
|
|
@@ -24169,7 +24233,7 @@ function validatePackageJson(files, ctx) {
|
|
|
24169
24233
|
});
|
|
24170
24234
|
return issues;
|
|
24171
24235
|
}
|
|
24172
|
-
if (!
|
|
24236
|
+
if (!pkg2.name || typeof pkg2.name !== "string") {
|
|
24173
24237
|
issues.push({
|
|
24174
24238
|
category: "package-json",
|
|
24175
24239
|
severity: "warning",
|
|
@@ -24179,7 +24243,7 @@ function validatePackageJson(files, ctx) {
|
|
|
24179
24243
|
});
|
|
24180
24244
|
}
|
|
24181
24245
|
for (const depKey of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
24182
|
-
const deps =
|
|
24246
|
+
const deps = pkg2[depKey];
|
|
24183
24247
|
if (!deps || typeof deps !== "object")
|
|
24184
24248
|
continue;
|
|
24185
24249
|
for (const [name, version] of Object.entries(deps)) {
|
|
@@ -24202,7 +24266,7 @@ function validatePackageJson(files, ctx) {
|
|
|
24202
24266
|
}
|
|
24203
24267
|
}
|
|
24204
24268
|
}
|
|
24205
|
-
const scripts =
|
|
24269
|
+
const scripts = pkg2.scripts;
|
|
24206
24270
|
if (ctx.startCmd) {
|
|
24207
24271
|
const scriptMatch = ctx.startCmd.match(/^(?:npm|pnpm|yarn)\s+(?:run\s+)?(\S+)/);
|
|
24208
24272
|
if (scriptMatch) {
|
|
@@ -24662,20 +24726,20 @@ function detectContext(dir) {
|
|
|
24662
24726
|
const pkgPath = resolve3(dir, "package.json");
|
|
24663
24727
|
if (existsSync5(pkgPath)) {
|
|
24664
24728
|
try {
|
|
24665
|
-
const
|
|
24666
|
-
const deps = { ...
|
|
24729
|
+
const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
24730
|
+
const deps = { ...pkg2.dependencies, ...pkg2.devDependencies };
|
|
24667
24731
|
if (deps.next) ctx.runtime = "nodejs";
|
|
24668
24732
|
else if (deps.express || deps.fastify || deps.koa || deps.hapi) ctx.runtime = "nodejs";
|
|
24669
24733
|
else if (deps.react || deps.vue || deps.svelte) ctx.runtime = "nodejs";
|
|
24670
|
-
else if (
|
|
24734
|
+
else if (pkg2.type === "module" || deps.typescript) ctx.runtime = "nodejs";
|
|
24671
24735
|
if (existsSync5(resolve3(dir, "pnpm-lock.yaml"))) ctx.packageManager = "pnpm";
|
|
24672
24736
|
else if (existsSync5(resolve3(dir, "yarn.lock"))) ctx.packageManager = "yarn";
|
|
24673
24737
|
else if (existsSync5(resolve3(dir, "package-lock.json"))) ctx.packageManager = "npm";
|
|
24674
|
-
const startScript =
|
|
24738
|
+
const startScript = pkg2.scripts?.start || pkg2.scripts?.dev || "";
|
|
24675
24739
|
const portMatch = startScript.match(/--port\s+(\d+)|-p\s+(\d+)/);
|
|
24676
24740
|
if (portMatch) ctx.port = parseInt(portMatch[1] || portMatch[2]);
|
|
24677
24741
|
if (deps.prisma || deps["@prisma/client"]) ctx.hasPrisma = true;
|
|
24678
|
-
if (
|
|
24742
|
+
if (pkg2.scripts?.start) ctx.startCmd = `npm run start`;
|
|
24679
24743
|
} catch {
|
|
24680
24744
|
}
|
|
24681
24745
|
}
|
|
@@ -25345,10 +25409,10 @@ function printToolTrace(calls) {
|
|
|
25345
25409
|
}
|
|
25346
25410
|
function prompt5(question) {
|
|
25347
25411
|
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
25348
|
-
return new Promise((
|
|
25412
|
+
return new Promise((resolve5) => {
|
|
25349
25413
|
rl.question(question, (answer) => {
|
|
25350
25414
|
rl.close();
|
|
25351
|
-
|
|
25415
|
+
resolve5(answer);
|
|
25352
25416
|
});
|
|
25353
25417
|
});
|
|
25354
25418
|
}
|
|
@@ -25465,9 +25529,9 @@ function registerAgent(program3) {
|
|
|
25465
25529
|
};
|
|
25466
25530
|
let done = false;
|
|
25467
25531
|
while (!done) {
|
|
25468
|
-
const line = await new Promise((
|
|
25469
|
-
rl.question(source_default.cyan("> "), (answer) =>
|
|
25470
|
-
rl.once("close", () =>
|
|
25532
|
+
const line = await new Promise((resolve5) => {
|
|
25533
|
+
rl.question(source_default.cyan("> "), (answer) => resolve5(answer));
|
|
25534
|
+
rl.once("close", () => resolve5(null));
|
|
25471
25535
|
});
|
|
25472
25536
|
if (line === null) {
|
|
25473
25537
|
done = true;
|
|
@@ -25481,9 +25545,894 @@ function registerAgent(program3) {
|
|
|
25481
25545
|
});
|
|
25482
25546
|
}
|
|
25483
25547
|
|
|
25548
|
+
// src/commands/workflows.ts
|
|
25549
|
+
async function loadStirrupServices() {
|
|
25550
|
+
const teams = await api("/api/teams");
|
|
25551
|
+
const teamIds = teams.success ? teams.data.map((t) => t.id) : [];
|
|
25552
|
+
const allServices = [];
|
|
25553
|
+
for (const teamId of teamIds) {
|
|
25554
|
+
const projects = await api(
|
|
25555
|
+
`/api/projects?teamId=${teamId}`
|
|
25556
|
+
);
|
|
25557
|
+
if (!projects.success) continue;
|
|
25558
|
+
for (const project of projects.data) {
|
|
25559
|
+
const services = await api(
|
|
25560
|
+
`/api/services?projectId=${project.id}`
|
|
25561
|
+
);
|
|
25562
|
+
if (!services.success) continue;
|
|
25563
|
+
for (const svc of services.data) {
|
|
25564
|
+
if (svc.framework === "Stirrup") {
|
|
25565
|
+
allServices.push({ ...svc, projectName: project.name });
|
|
25566
|
+
}
|
|
25567
|
+
}
|
|
25568
|
+
}
|
|
25569
|
+
}
|
|
25570
|
+
return allServices;
|
|
25571
|
+
}
|
|
25572
|
+
async function listAllWorkflows() {
|
|
25573
|
+
const spin = ora("Discovering Stirrup services...").start();
|
|
25574
|
+
let services;
|
|
25575
|
+
try {
|
|
25576
|
+
services = await loadStirrupServices();
|
|
25577
|
+
} catch (err) {
|
|
25578
|
+
spin.fail(`Could not list services: ${err instanceof Error ? err.message : String(err)}`);
|
|
25579
|
+
process.exit(1);
|
|
25580
|
+
}
|
|
25581
|
+
spin.succeed(`Found ${services.length} Stirrup service${services.length === 1 ? "" : "s"}`);
|
|
25582
|
+
if (services.length === 0) {
|
|
25583
|
+
console.log(source_default.dim("\nNo Stirrup services deployed. Push a stirrup-ai project and try again."));
|
|
25584
|
+
return;
|
|
25585
|
+
}
|
|
25586
|
+
for (const svc of services) {
|
|
25587
|
+
console.log();
|
|
25588
|
+
console.log(source_default.bold(`${svc.projectName} / ${svc.slug}`) + source_default.dim(` (${svc.id})`));
|
|
25589
|
+
try {
|
|
25590
|
+
const res = await api(`/api/services/${svc.id}/workflows`);
|
|
25591
|
+
if (!res.success || !res.data) {
|
|
25592
|
+
console.log(source_default.red(" could not load workflows"));
|
|
25593
|
+
continue;
|
|
25594
|
+
}
|
|
25595
|
+
if (res.data.requiresAuth) {
|
|
25596
|
+
console.log(source_default.yellow(" needs STIRRUP_API_TOKEN env var"));
|
|
25597
|
+
if (res.data.note) console.log(source_default.dim(" " + res.data.note));
|
|
25598
|
+
continue;
|
|
25599
|
+
}
|
|
25600
|
+
if (res.data.workflows.length === 0) {
|
|
25601
|
+
console.log(source_default.dim(" no workflows defined"));
|
|
25602
|
+
continue;
|
|
25603
|
+
}
|
|
25604
|
+
for (const wf of res.data.workflows) {
|
|
25605
|
+
console.log(
|
|
25606
|
+
` ${source_default.cyan(wf.id)} ${source_default.dim(`(${wf.nodeCount} nodes${wf.triggers.length ? `, triggers: ${wf.triggers.join(",")}` : ""})`)}`
|
|
25607
|
+
);
|
|
25608
|
+
if (wf.description) console.log(source_default.dim(` ${wf.description}`));
|
|
25609
|
+
}
|
|
25610
|
+
} catch (err) {
|
|
25611
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
25612
|
+
console.log(source_default.red(` unreachable: ${msg}`));
|
|
25613
|
+
}
|
|
25614
|
+
}
|
|
25615
|
+
console.log();
|
|
25616
|
+
console.log(source_default.dim("Run a workflow:") + " " + source_default.bold("lm workflows run <serviceId>/<workflowId> --input '{...}'"));
|
|
25617
|
+
}
|
|
25618
|
+
async function runWorkflow(target, opts) {
|
|
25619
|
+
const m = target.match(/^([^/]+)\/(.+)$/);
|
|
25620
|
+
if (!m) {
|
|
25621
|
+
console.error(source_default.red("Target must be <serviceId>/<workflowId>"));
|
|
25622
|
+
process.exit(1);
|
|
25623
|
+
}
|
|
25624
|
+
const [, serviceId, workflowId] = m;
|
|
25625
|
+
let input = {};
|
|
25626
|
+
if (opts.input) {
|
|
25627
|
+
try {
|
|
25628
|
+
input = JSON.parse(opts.input);
|
|
25629
|
+
} catch (err) {
|
|
25630
|
+
console.error(source_default.red(`Input must be valid JSON: ${err instanceof Error ? err.message : String(err)}`));
|
|
25631
|
+
process.exit(1);
|
|
25632
|
+
}
|
|
25633
|
+
}
|
|
25634
|
+
const spin = ora(`Triggering ${source_default.cyan(workflowId)}...`).start();
|
|
25635
|
+
try {
|
|
25636
|
+
const res = await api(
|
|
25637
|
+
`/api/services/${serviceId}/workflows/${encodeURIComponent(workflowId)}/run`,
|
|
25638
|
+
{ method: "POST", body: JSON.stringify(input) }
|
|
25639
|
+
);
|
|
25640
|
+
if (!res.success) {
|
|
25641
|
+
spin.fail(`Trigger failed: ${typeof res.error === "string" ? res.error : JSON.stringify(res.error)}`);
|
|
25642
|
+
process.exit(1);
|
|
25643
|
+
}
|
|
25644
|
+
spin.succeed("Triggered");
|
|
25645
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
25646
|
+
} catch (err) {
|
|
25647
|
+
spin.fail(`Trigger failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
25648
|
+
process.exit(1);
|
|
25649
|
+
}
|
|
25650
|
+
}
|
|
25651
|
+
async function showRuns(serviceId) {
|
|
25652
|
+
const spin = ora("Loading recent runs...").start();
|
|
25653
|
+
try {
|
|
25654
|
+
const res = await api(
|
|
25655
|
+
`/api/services/${serviceId}/workflows/runs`
|
|
25656
|
+
);
|
|
25657
|
+
spin.stop();
|
|
25658
|
+
if (!res.success || !res.data) {
|
|
25659
|
+
console.log(source_default.red("Could not load runs."));
|
|
25660
|
+
return;
|
|
25661
|
+
}
|
|
25662
|
+
if (res.data.runs.length === 0) {
|
|
25663
|
+
console.log(source_default.dim("No runs yet."));
|
|
25664
|
+
return;
|
|
25665
|
+
}
|
|
25666
|
+
for (const r of res.data.runs) {
|
|
25667
|
+
const dur = r.durationMs != null ? `${(r.durationMs / 1e3).toFixed(1)}s` : "\u2014";
|
|
25668
|
+
console.log(
|
|
25669
|
+
`${source_default.cyan(r.workflowId)} ${source_default.dim(r.id)} ${source_default.bold(r.status)} ${source_default.dim(dur)} ${source_default.dim(r.startedAt ?? "")}`
|
|
25670
|
+
);
|
|
25671
|
+
}
|
|
25672
|
+
} catch (err) {
|
|
25673
|
+
spin.fail(`Could not load runs: ${err instanceof Error ? err.message : String(err)}`);
|
|
25674
|
+
}
|
|
25675
|
+
}
|
|
25676
|
+
function registerWorkflows(program3) {
|
|
25677
|
+
const cmd = program3.command("workflows").alias("wf").description("List and trigger Stirrup workflows on your deployed services");
|
|
25678
|
+
cmd.command("list").alias("ls").description("List Stirrup workflows across all your services").action(listAllWorkflows);
|
|
25679
|
+
cmd.command("run <target>").description("Trigger a workflow. Target format: <serviceId>/<workflowId>").option("-i, --input <json>", "JSON input payload to send to the workflow").action(runWorkflow);
|
|
25680
|
+
cmd.command("runs <serviceId>").description("Show recent runs for the workflows on a service").action(showRuns);
|
|
25681
|
+
}
|
|
25682
|
+
|
|
25683
|
+
// src/commands/image.ts
|
|
25684
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
25685
|
+
import path6 from "path";
|
|
25686
|
+
function extFromMime(mime) {
|
|
25687
|
+
if (mime.includes("png")) return "png";
|
|
25688
|
+
if (mime.includes("jpeg") || mime.includes("jpg")) return "jpg";
|
|
25689
|
+
if (mime.includes("webp")) return "webp";
|
|
25690
|
+
if (mime.includes("gif")) return "gif";
|
|
25691
|
+
return "bin";
|
|
25692
|
+
}
|
|
25693
|
+
function slugifyForFilename(s) {
|
|
25694
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "image";
|
|
25695
|
+
}
|
|
25696
|
+
async function generateCmd(promptWords, opts) {
|
|
25697
|
+
if (!isLoggedIn()) {
|
|
25698
|
+
console.error(source_default.red('Not logged in. Run "lm login" first.'));
|
|
25699
|
+
process.exitCode = 1;
|
|
25700
|
+
return;
|
|
25701
|
+
}
|
|
25702
|
+
const userPrompt = promptWords.join(" ").trim();
|
|
25703
|
+
if (!userPrompt) {
|
|
25704
|
+
console.error(source_default.red('Prompt is required. Usage: lm image gen "a watercolor mountain"'));
|
|
25705
|
+
process.exitCode = 1;
|
|
25706
|
+
return;
|
|
25707
|
+
}
|
|
25708
|
+
const count = Math.min(Math.max(parseInt(opts.count ?? "1") || 1, 1), 4);
|
|
25709
|
+
const validRatios = ["1:1", "16:9", "9:16", "4:3", "3:4"];
|
|
25710
|
+
const aspectRatio = opts.aspectRatio && validRatios.includes(opts.aspectRatio) ? opts.aspectRatio : "1:1";
|
|
25711
|
+
const outDir = path6.resolve(opts.outDir ?? process.cwd());
|
|
25712
|
+
const spinner = ora(`Generating ${count} image${count === 1 ? "" : "s"} (${aspectRatio})...`).start();
|
|
25713
|
+
try {
|
|
25714
|
+
const res = await api("/api/nanobanana2/generate", {
|
|
25715
|
+
method: "POST",
|
|
25716
|
+
body: JSON.stringify({ prompt: userPrompt, count, aspectRatio })
|
|
25717
|
+
});
|
|
25718
|
+
spinner.text = "Writing files...";
|
|
25719
|
+
await mkdir(outDir, { recursive: true });
|
|
25720
|
+
const baseName = opts.name ? slugifyForFilename(opts.name) : slugifyForFilename(userPrompt);
|
|
25721
|
+
const written = [];
|
|
25722
|
+
for (let i = 0; i < res.data.images.length; i++) {
|
|
25723
|
+
const img = res.data.images[i];
|
|
25724
|
+
const ext = extFromMime(img.mimeType);
|
|
25725
|
+
const suffix = res.data.images.length === 1 ? "" : `-${i + 1}`;
|
|
25726
|
+
const fileName = `${baseName}${suffix}.${ext}`;
|
|
25727
|
+
const filePath = path6.join(outDir, fileName);
|
|
25728
|
+
await writeFile(filePath, Buffer.from(img.data, "base64"));
|
|
25729
|
+
written.push(filePath);
|
|
25730
|
+
}
|
|
25731
|
+
spinner.succeed(
|
|
25732
|
+
source_default.green(
|
|
25733
|
+
`Wrote ${written.length} image${written.length === 1 ? "" : "s"} ${source_default.dim(`(${res.data.usage.totalTokens} tokens)`)}`
|
|
25734
|
+
)
|
|
25735
|
+
);
|
|
25736
|
+
for (const p of written) console.log(" " + source_default.dim(p));
|
|
25737
|
+
if (res.data.text?.trim()) {
|
|
25738
|
+
console.log();
|
|
25739
|
+
console.log(source_default.dim(res.data.text.trim()));
|
|
25740
|
+
}
|
|
25741
|
+
} catch (err) {
|
|
25742
|
+
spinner.fail(source_default.red(`Generate failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
25743
|
+
process.exitCode = 1;
|
|
25744
|
+
}
|
|
25745
|
+
}
|
|
25746
|
+
async function usageCmd() {
|
|
25747
|
+
if (!isLoggedIn()) {
|
|
25748
|
+
console.error(source_default.red('Not logged in. Run "lm login" first.'));
|
|
25749
|
+
process.exitCode = 1;
|
|
25750
|
+
return;
|
|
25751
|
+
}
|
|
25752
|
+
const spin = ora("Fetching usage...").start();
|
|
25753
|
+
try {
|
|
25754
|
+
const res = await api("/api/nanobanana2/usage");
|
|
25755
|
+
spin.stop();
|
|
25756
|
+
const d = res.data;
|
|
25757
|
+
console.log(source_default.bold(`Plan: ${d.planTier}`));
|
|
25758
|
+
console.log(
|
|
25759
|
+
`Tokens: ${source_default.cyan(d.tokens.used.toLocaleString())} / ${d.tokens.limit.toLocaleString()} ` + source_default.dim(`(${d.tokens.remaining.toLocaleString()} remaining)`)
|
|
25760
|
+
);
|
|
25761
|
+
console.log(
|
|
25762
|
+
`Images: ${source_default.cyan(d.images.used.toString())} / ${d.images.limit.toString()} ` + source_default.dim(`(${d.images.remaining} remaining)`)
|
|
25763
|
+
);
|
|
25764
|
+
console.log(source_default.dim(`Resets: ${new Date(d.resetAt).toLocaleString()}`));
|
|
25765
|
+
} catch (err) {
|
|
25766
|
+
spin.fail(source_default.red(`Usage failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
25767
|
+
process.exitCode = 1;
|
|
25768
|
+
}
|
|
25769
|
+
}
|
|
25770
|
+
async function historyCmd(opts) {
|
|
25771
|
+
if (!isLoggedIn()) {
|
|
25772
|
+
console.error(source_default.red('Not logged in. Run "lm login" first.'));
|
|
25773
|
+
process.exitCode = 1;
|
|
25774
|
+
return;
|
|
25775
|
+
}
|
|
25776
|
+
const limit = Math.min(Math.max(parseInt(opts.limit ?? "20") || 20, 1), 50);
|
|
25777
|
+
const spin = ora("Loading history...").start();
|
|
25778
|
+
try {
|
|
25779
|
+
const res = await api(`/api/nanobanana2/history?limit=${limit}`);
|
|
25780
|
+
spin.stop();
|
|
25781
|
+
if (res.data.items.length === 0) {
|
|
25782
|
+
console.log(source_default.dim("No generations yet."));
|
|
25783
|
+
return;
|
|
25784
|
+
}
|
|
25785
|
+
for (const item of res.data.items) {
|
|
25786
|
+
const when = new Date(item.createdAt).toLocaleString();
|
|
25787
|
+
const truncated = item.prompt.length > 80 ? item.prompt.slice(0, 77) + "..." : item.prompt;
|
|
25788
|
+
console.log(
|
|
25789
|
+
`${source_default.dim(item.id)} ${source_default.cyan(item.status.padEnd(10))} ${source_default.bold(`${item.imageCount} img`)} ${source_default.dim(when)}`
|
|
25790
|
+
);
|
|
25791
|
+
console.log(" " + truncated);
|
|
25792
|
+
}
|
|
25793
|
+
} catch (err) {
|
|
25794
|
+
spin.fail(source_default.red(`History failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
25795
|
+
process.exitCode = 1;
|
|
25796
|
+
}
|
|
25797
|
+
}
|
|
25798
|
+
function registerImage(program3) {
|
|
25799
|
+
const cmd = program3.command("image").alias("img").description("Generate images via Launchmatic (NanoBanana2 / Gemini 2.5 Flash Image)");
|
|
25800
|
+
cmd.command("generate <prompt...>").alias("gen").description('Generate images from a prompt. Usage: lm image gen "watercolor mountain"').option("-c, --count <n>", "How many images (1\u20134)", "1").option("-a, --aspect-ratio <ratio>", "1:1, 16:9, 9:16, 4:3, or 3:4", "1:1").option("-o, --out-dir <dir>", "Where to write files (default: cwd)").option("-n, --name <name>", "Base filename (default: derived from prompt)").action(generateCmd);
|
|
25801
|
+
cmd.command("usage").description("Show current image/token quota").action(usageCmd);
|
|
25802
|
+
cmd.command("history").description("List recent image generations (metadata only)").option("-l, --limit <n>", "Max entries to show (1\u201350)", "20").action(historyCmd);
|
|
25803
|
+
}
|
|
25804
|
+
|
|
25805
|
+
// src/commands/monorepo.ts
|
|
25806
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
25807
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
25808
|
+
import { join as join5 } from "path";
|
|
25809
|
+
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
|
+
function requireLogin3() {
|
|
26013
|
+
if (!isLoggedIn()) {
|
|
26014
|
+
console.error(source_default.red('Not logged in. Run "lm login" first.'));
|
|
26015
|
+
process.exitCode = 1;
|
|
26016
|
+
return false;
|
|
26017
|
+
}
|
|
26018
|
+
return true;
|
|
26019
|
+
}
|
|
26020
|
+
function prompt6(question) {
|
|
26021
|
+
const rl = readline7.createInterface({ input: process.stdin, output: process.stdout });
|
|
26022
|
+
return new Promise((resolve5) => {
|
|
26023
|
+
rl.question(question, (a) => {
|
|
26024
|
+
rl.close();
|
|
26025
|
+
resolve5(a.trim());
|
|
26026
|
+
});
|
|
26027
|
+
});
|
|
26028
|
+
}
|
|
26029
|
+
async function initManifest(opts) {
|
|
26030
|
+
if (!requireLogin3()) return;
|
|
26031
|
+
const repoRoot = findRepoRoot();
|
|
26032
|
+
const path7 = manifestPath(repoRoot);
|
|
26033
|
+
if (existsSync7(path7)) {
|
|
26034
|
+
console.error(source_default.yellow(`${MANIFEST_FILE} already exists at ${path7}`));
|
|
26035
|
+
console.error(source_default.dim("Edit it manually or delete and re-run."));
|
|
26036
|
+
process.exitCode = 1;
|
|
26037
|
+
return;
|
|
26038
|
+
}
|
|
26039
|
+
const spin = ora("Discovering services...").start();
|
|
26040
|
+
const discovered = discoverServices(repoRoot);
|
|
26041
|
+
spin.stop();
|
|
26042
|
+
if (discovered.length === 0) {
|
|
26043
|
+
console.error(source_default.red("No services discovered."));
|
|
26044
|
+
console.error(source_default.dim("Looked for: pnpm-workspace.yaml, package.json#workspaces, apps/*, services/*"));
|
|
26045
|
+
console.error(source_default.dim("If your services live elsewhere, create launchmatic.json by hand."));
|
|
26046
|
+
process.exitCode = 1;
|
|
26047
|
+
return;
|
|
26048
|
+
}
|
|
26049
|
+
console.log(source_default.bold(`
|
|
26050
|
+
Discovered ${discovered.length} service${discovered.length === 1 ? "" : "s"}:`));
|
|
26051
|
+
for (const s of discovered) {
|
|
26052
|
+
const fw = s.framework ? source_default.dim(` [${s.framework}]`) : "";
|
|
26053
|
+
console.log(` ${source_default.cyan(s.name)} ${source_default.dim(s.rootDir)}${fw}`);
|
|
26054
|
+
}
|
|
26055
|
+
const { data: teams } = await api("/api/teams");
|
|
26056
|
+
if (!teams || teams.length === 0) {
|
|
26057
|
+
console.error(source_default.red("No teams found on your account."));
|
|
26058
|
+
process.exitCode = 1;
|
|
26059
|
+
return;
|
|
26060
|
+
}
|
|
26061
|
+
let teamId = opts.team;
|
|
26062
|
+
if (!teamId) {
|
|
26063
|
+
if (teams.length === 1 || opts.yes) {
|
|
26064
|
+
teamId = teams[0].id;
|
|
26065
|
+
} else {
|
|
26066
|
+
console.log(source_default.bold("\nTeams:"));
|
|
26067
|
+
teams.forEach((t, i) => console.log(` ${i + 1}. ${t.name} ${source_default.dim(`(${t.slug})`)}`));
|
|
26068
|
+
const ans = await prompt6("Pick a team (number): ");
|
|
26069
|
+
teamId = teams[parseInt(ans) - 1]?.id ?? teams[0].id;
|
|
26070
|
+
}
|
|
26071
|
+
}
|
|
26072
|
+
const projectName = opts.project || opts.name || (existsSync7(join5(repoRoot, "package.json")) ? (() => {
|
|
26073
|
+
try {
|
|
26074
|
+
const pkg2 = JSON.parse(readFileSync7(join5(repoRoot, "package.json"), "utf-8"));
|
|
26075
|
+
return pkg2.name?.replace(/^@.*\//, "") || "monorepo";
|
|
26076
|
+
} catch {
|
|
26077
|
+
return "monorepo";
|
|
26078
|
+
}
|
|
26079
|
+
})() : "monorepo");
|
|
26080
|
+
const projectSlug = projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "monorepo";
|
|
26081
|
+
const { data: existingProjects } = await api(
|
|
26082
|
+
`/api/projects?teamId=${teamId}`
|
|
26083
|
+
);
|
|
26084
|
+
let projectId = existingProjects.find((p) => p.slug === projectSlug)?.id;
|
|
26085
|
+
if (!projectId) {
|
|
26086
|
+
const create = await api("/api/projects", {
|
|
26087
|
+
method: "POST",
|
|
26088
|
+
body: JSON.stringify({ name: projectName, slug: projectSlug, teamId })
|
|
26089
|
+
});
|
|
26090
|
+
projectId = create.data.id;
|
|
26091
|
+
console.log(source_default.green(`Created project ${source_default.bold(projectName)}`));
|
|
26092
|
+
} else {
|
|
26093
|
+
console.log(source_default.dim(`Using existing project ${projectName}`));
|
|
26094
|
+
}
|
|
26095
|
+
const { data: existingServices } = await api(`/api/services?projectId=${projectId}`);
|
|
26096
|
+
const gitInfo = getGitInfo(repoRoot);
|
|
26097
|
+
const services = [];
|
|
26098
|
+
for (const s of discovered) {
|
|
26099
|
+
const slug = s.name;
|
|
26100
|
+
let svcId = existingServices.find((es) => es.slug === slug || es.rootDir === `/${s.rootDir}` || es.rootDir === s.rootDir)?.id;
|
|
26101
|
+
if (!svcId) {
|
|
26102
|
+
const payload = {
|
|
26103
|
+
name: s.name,
|
|
26104
|
+
slug,
|
|
26105
|
+
type: "WEB",
|
|
26106
|
+
projectId,
|
|
26107
|
+
rootDir: `/${s.rootDir}`,
|
|
26108
|
+
port: s.port
|
|
26109
|
+
};
|
|
26110
|
+
if (s.framework) payload.framework = s.framework;
|
|
26111
|
+
if (s.buildCmd) payload.buildCmd = s.buildCmd;
|
|
26112
|
+
if (s.startCmd) payload.startCmd = s.startCmd;
|
|
26113
|
+
if (gitInfo.repoOwner && gitInfo.repoName) {
|
|
26114
|
+
payload.repoOwner = gitInfo.repoOwner;
|
|
26115
|
+
payload.repoName = gitInfo.repoName;
|
|
26116
|
+
payload.repoBranch = gitInfo.repoBranch;
|
|
26117
|
+
payload.repoUrl = `https://github.com/${gitInfo.repoOwner}/${gitInfo.repoName}.git`;
|
|
26118
|
+
}
|
|
26119
|
+
try {
|
|
26120
|
+
const created = await api("/api/services", {
|
|
26121
|
+
method: "POST",
|
|
26122
|
+
body: JSON.stringify(payload)
|
|
26123
|
+
});
|
|
26124
|
+
svcId = created.data.id;
|
|
26125
|
+
console.log(source_default.green(`Created service ${source_default.bold(s.name)}`));
|
|
26126
|
+
} catch (err) {
|
|
26127
|
+
console.warn(source_default.yellow(`Could not create service ${s.name}: ${err instanceof Error ? err.message : String(err)}`));
|
|
26128
|
+
continue;
|
|
26129
|
+
}
|
|
26130
|
+
} else {
|
|
26131
|
+
console.log(source_default.dim(`Using existing service ${s.name}`));
|
|
26132
|
+
}
|
|
26133
|
+
services.push({
|
|
26134
|
+
name: s.name,
|
|
26135
|
+
rootDir: s.rootDir,
|
|
26136
|
+
serviceId: svcId,
|
|
26137
|
+
framework: s.framework,
|
|
26138
|
+
buildCmd: s.buildCmd,
|
|
26139
|
+
startCmd: s.startCmd,
|
|
26140
|
+
port: s.port
|
|
26141
|
+
});
|
|
26142
|
+
}
|
|
26143
|
+
const manifest = {
|
|
26144
|
+
version: 1,
|
|
26145
|
+
teamId,
|
|
26146
|
+
projectId,
|
|
26147
|
+
services
|
|
26148
|
+
};
|
|
26149
|
+
writeManifest(manifest, repoRoot);
|
|
26150
|
+
console.log();
|
|
26151
|
+
console.log(source_default.green(`\u2713 Wrote ${source_default.bold(MANIFEST_FILE)} (${services.length} services)`));
|
|
26152
|
+
console.log(source_default.dim("Next: ") + source_default.bold("lm up") + source_default.dim(" to deploy everything."));
|
|
26153
|
+
}
|
|
26154
|
+
async function listManifest() {
|
|
26155
|
+
const manifest = readManifest();
|
|
26156
|
+
if (!manifest) {
|
|
26157
|
+
console.error(source_default.red(`No ${MANIFEST_FILE} in this repo. Run ${source_default.bold("lm monorepo init")}.`));
|
|
26158
|
+
process.exitCode = 1;
|
|
26159
|
+
return;
|
|
26160
|
+
}
|
|
26161
|
+
console.log(source_default.bold(`${manifest.services.length} service${manifest.services.length === 1 ? "" : "s"}:`));
|
|
26162
|
+
for (const s of manifest.services) {
|
|
26163
|
+
const fw = s.framework ? source_default.dim(` [${s.framework}]`) : "";
|
|
26164
|
+
const sid = s.serviceId ? source_default.dim(` ${s.serviceId}`) : source_default.yellow(" (not yet created)");
|
|
26165
|
+
console.log(` ${source_default.cyan(s.name.padEnd(16))} ${source_default.dim(s.rootDir)}${fw}${sid}`);
|
|
26166
|
+
}
|
|
26167
|
+
}
|
|
26168
|
+
async function up(opts) {
|
|
26169
|
+
if (!requireLogin3()) return;
|
|
26170
|
+
const repoRoot = findRepoRoot();
|
|
26171
|
+
const manifest = readManifest(repoRoot);
|
|
26172
|
+
if (!manifest) {
|
|
26173
|
+
console.error(source_default.red(`No ${MANIFEST_FILE} found. Run ${source_default.bold("lm monorepo init")} first.`));
|
|
26174
|
+
process.exitCode = 1;
|
|
26175
|
+
return;
|
|
26176
|
+
}
|
|
26177
|
+
const gitInfo = getGitInfo(repoRoot);
|
|
26178
|
+
if (gitInfo.hasUnpushed && gitInfo.hasRemote) {
|
|
26179
|
+
const pushSpin = ora("Pushing commits to remote...").start();
|
|
26180
|
+
try {
|
|
26181
|
+
execFileSync9("git", ["push", "origin", gitInfo.repoBranch], {
|
|
26182
|
+
cwd: repoRoot,
|
|
26183
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
26184
|
+
});
|
|
26185
|
+
pushSpin.succeed("Pushed commits to remote");
|
|
26186
|
+
} catch (err) {
|
|
26187
|
+
pushSpin.fail(`Push failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
26188
|
+
process.exitCode = 1;
|
|
26189
|
+
return;
|
|
26190
|
+
}
|
|
26191
|
+
}
|
|
26192
|
+
const wantNames = opts.service && opts.service.length > 0 ? new Set(opts.service) : null;
|
|
26193
|
+
const changed = opts.all ? [] : changedFilesSince(repoRoot, opts.base);
|
|
26194
|
+
if (!opts.all && changed.length > 0) {
|
|
26195
|
+
console.log(source_default.dim(`Diff base: ${opts.base ?? "auto"} \u2014 ${changed.length} file${changed.length === 1 ? "" : "s"} changed`));
|
|
26196
|
+
}
|
|
26197
|
+
const selected = [];
|
|
26198
|
+
const skipped = [];
|
|
26199
|
+
for (const s of manifest.services) {
|
|
26200
|
+
if (wantNames && !wantNames.has(s.name)) {
|
|
26201
|
+
skipped.push({ name: s.name, reason: "not in --service filter" });
|
|
26202
|
+
continue;
|
|
26203
|
+
}
|
|
26204
|
+
if (!s.serviceId) {
|
|
26205
|
+
skipped.push({ name: s.name, reason: "no serviceId \u2014 re-run lm monorepo init" });
|
|
26206
|
+
continue;
|
|
26207
|
+
}
|
|
26208
|
+
if (!opts.all && changed.length > 0 && !serviceWasChanged(s.rootDir, changed)) {
|
|
26209
|
+
skipped.push({ name: s.name, reason: "no changes in rootDir" });
|
|
26210
|
+
continue;
|
|
26211
|
+
}
|
|
26212
|
+
selected.push({ entry: s, reason: opts.all ? "--all" : changed.length === 0 ? "no diff base" : "rootDir touched" });
|
|
26213
|
+
}
|
|
26214
|
+
if (selected.length === 0) {
|
|
26215
|
+
console.log(source_default.yellow("Nothing to deploy."));
|
|
26216
|
+
for (const s of skipped) console.log(source_default.dim(` - ${s.name}: ${s.reason}`));
|
|
26217
|
+
console.log(source_default.dim(`Force a deploy of all services with ${source_default.bold("lm up --all")}.`));
|
|
26218
|
+
return;
|
|
26219
|
+
}
|
|
26220
|
+
console.log(source_default.bold(`Deploying ${selected.length} service${selected.length === 1 ? "" : "s"}:`));
|
|
26221
|
+
for (const s of selected) console.log(` ${source_default.cyan(s.entry.name)} ${source_default.dim(`(${s.reason})`)}`);
|
|
26222
|
+
if (skipped.length > 0) {
|
|
26223
|
+
console.log(source_default.dim(`Skipped:`));
|
|
26224
|
+
for (const s of skipped) console.log(source_default.dim(` - ${s.name}: ${s.reason}`));
|
|
26225
|
+
}
|
|
26226
|
+
console.log();
|
|
26227
|
+
const results = await Promise.allSettled(
|
|
26228
|
+
selected.map(async ({ entry }) => {
|
|
26229
|
+
const res = await api("/api/deployments", {
|
|
26230
|
+
method: "POST",
|
|
26231
|
+
body: JSON.stringify({
|
|
26232
|
+
serviceId: entry.serviceId,
|
|
26233
|
+
branch: gitInfo.repoBranch,
|
|
26234
|
+
commitSha: gitInfo.commitSha
|
|
26235
|
+
})
|
|
26236
|
+
});
|
|
26237
|
+
return { name: entry.name, deploymentId: res.data.id };
|
|
26238
|
+
})
|
|
26239
|
+
);
|
|
26240
|
+
let okCount = 0;
|
|
26241
|
+
let failCount = 0;
|
|
26242
|
+
for (let i = 0; i < results.length; i++) {
|
|
26243
|
+
const r = results[i];
|
|
26244
|
+
const name = selected[i].entry.name;
|
|
26245
|
+
if (r.status === "fulfilled") {
|
|
26246
|
+
okCount++;
|
|
26247
|
+
console.log(source_default.green(`\u2713 ${name}`) + source_default.dim(` queued (deployment ${r.value.deploymentId})`));
|
|
26248
|
+
} else {
|
|
26249
|
+
failCount++;
|
|
26250
|
+
const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
|
|
26251
|
+
console.log(source_default.red(`\u2717 ${name}`) + source_default.dim(` ${msg}`));
|
|
26252
|
+
}
|
|
26253
|
+
}
|
|
26254
|
+
console.log();
|
|
26255
|
+
console.log(
|
|
26256
|
+
source_default.bold(`Deployed ${okCount}/${selected.length}`) + source_default.dim(` Track progress: lm status / lm logs --service <name>`)
|
|
26257
|
+
);
|
|
26258
|
+
if (failCount > 0) process.exitCode = 1;
|
|
26259
|
+
}
|
|
26260
|
+
function registerMonorepo(program3) {
|
|
26261
|
+
const cmd = program3.command("monorepo").alias("mono").description("Manage the launchmatic.json manifest for multi-service repos");
|
|
26262
|
+
cmd.command("init").description("Auto-discover services and write launchmatic.json").option("-y, --yes", "Don't prompt \u2014 accept all defaults").option("--team <id>", "Team ID to attach the project to").option("--project <name>", "Project name (default: derived from repo)").option("-n, --name <name>", "Alias for --project").action(initManifest);
|
|
26263
|
+
cmd.command("list").alias("ls").description("Print services declared in launchmatic.json").action(listManifest);
|
|
26264
|
+
}
|
|
26265
|
+
function registerUp(program3) {
|
|
26266
|
+
program3.command("up").description("Deploy every service in launchmatic.json (Railway-style multi-service deploy)").option("-s, --service <name...>", "Only deploy these services (by name)").option("-a, --all", "Skip the changed-only filter \u2014 deploy every service").option("--base <ref>", "Git ref to diff against (default: origin/<branch> or HEAD~1)").action(up);
|
|
26267
|
+
}
|
|
26268
|
+
|
|
26269
|
+
// src/commands/template.ts
|
|
26270
|
+
import readline8 from "readline";
|
|
26271
|
+
function requireLogin4() {
|
|
26272
|
+
if (!isLoggedIn()) {
|
|
26273
|
+
console.error(source_default.red('Not logged in. Run "lm login" first.'));
|
|
26274
|
+
process.exitCode = 1;
|
|
26275
|
+
return false;
|
|
26276
|
+
}
|
|
26277
|
+
return true;
|
|
26278
|
+
}
|
|
26279
|
+
function prompt7(question) {
|
|
26280
|
+
const rl = readline8.createInterface({ input: process.stdin, output: process.stdout });
|
|
26281
|
+
return new Promise((resolve5) => {
|
|
26282
|
+
rl.question(question, (a) => {
|
|
26283
|
+
rl.close();
|
|
26284
|
+
resolve5(a.trim());
|
|
26285
|
+
});
|
|
26286
|
+
});
|
|
26287
|
+
}
|
|
26288
|
+
async function listCmd(opts) {
|
|
26289
|
+
const spin = ora("Loading templates...").start();
|
|
26290
|
+
try {
|
|
26291
|
+
const res = await api("/api/templates");
|
|
26292
|
+
spin.stop();
|
|
26293
|
+
const filtered = opts.category ? res.data.filter((t) => t.category === opts.category.toLowerCase()) : res.data;
|
|
26294
|
+
if (filtered.length === 0) {
|
|
26295
|
+
console.log(source_default.dim("No templates match."));
|
|
26296
|
+
return;
|
|
26297
|
+
}
|
|
26298
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
26299
|
+
for (const t of filtered) {
|
|
26300
|
+
const arr = byCategory.get(t.category) ?? [];
|
|
26301
|
+
arr.push(t);
|
|
26302
|
+
byCategory.set(t.category, arr);
|
|
26303
|
+
}
|
|
26304
|
+
for (const [cat, templates] of byCategory) {
|
|
26305
|
+
console.log();
|
|
26306
|
+
console.log(source_default.bold.underline(cat));
|
|
26307
|
+
for (const t of templates) {
|
|
26308
|
+
console.log(` ${source_default.cyan(t.slug.padEnd(20))} ${source_default.bold(t.name)}`);
|
|
26309
|
+
console.log(` ${source_default.dim(" ".repeat(20))} ${source_default.dim(t.description)}`);
|
|
26310
|
+
const chips = [...t.tags];
|
|
26311
|
+
if (t.databases.length > 0) chips.push(...t.databases.map((d) => `db:${d}`));
|
|
26312
|
+
if (chips.length > 0) console.log(` ${source_default.dim(" ".repeat(20))} ${source_default.dim(chips.join(" \xB7 "))}`);
|
|
26313
|
+
}
|
|
26314
|
+
}
|
|
26315
|
+
console.log();
|
|
26316
|
+
console.log(source_default.dim(`Deploy with: ${source_default.bold("lm template deploy <slug>")}`));
|
|
26317
|
+
} catch (err) {
|
|
26318
|
+
spin.fail(`Could not load templates: ${err instanceof Error ? err.message : String(err)}`);
|
|
26319
|
+
process.exitCode = 1;
|
|
26320
|
+
}
|
|
26321
|
+
}
|
|
26322
|
+
async function deployCmd(slug, opts) {
|
|
26323
|
+
if (!requireLogin4()) return;
|
|
26324
|
+
let detail;
|
|
26325
|
+
try {
|
|
26326
|
+
const res = await api(`/api/templates/${slug}`);
|
|
26327
|
+
detail = res.data;
|
|
26328
|
+
} catch (err) {
|
|
26329
|
+
console.error(source_default.red(`Could not find template "${slug}": ${err instanceof Error ? err.message : String(err)}`));
|
|
26330
|
+
process.exitCode = 1;
|
|
26331
|
+
return;
|
|
26332
|
+
}
|
|
26333
|
+
let projectId = opts.project;
|
|
26334
|
+
if (!projectId && contextExists()) {
|
|
26335
|
+
try {
|
|
26336
|
+
projectId = readContext().projectId;
|
|
26337
|
+
} catch {
|
|
26338
|
+
}
|
|
26339
|
+
}
|
|
26340
|
+
if (!projectId) {
|
|
26341
|
+
const { data: teams } = await api("/api/teams");
|
|
26342
|
+
const teamId = teams[0]?.id;
|
|
26343
|
+
if (!teamId) {
|
|
26344
|
+
console.error(source_default.red("No teams found on your account."));
|
|
26345
|
+
process.exitCode = 1;
|
|
26346
|
+
return;
|
|
26347
|
+
}
|
|
26348
|
+
const { data: projects } = await api(
|
|
26349
|
+
`/api/projects?teamId=${teamId}`
|
|
26350
|
+
);
|
|
26351
|
+
if (projects.length === 0) {
|
|
26352
|
+
console.error(source_default.red("No projects yet. Pass --project <projectId> or create one in the dashboard."));
|
|
26353
|
+
process.exitCode = 1;
|
|
26354
|
+
return;
|
|
26355
|
+
}
|
|
26356
|
+
if (projects.length === 1 || opts.yes) {
|
|
26357
|
+
projectId = projects[0].id;
|
|
26358
|
+
} else {
|
|
26359
|
+
console.log(source_default.bold("Pick a project:"));
|
|
26360
|
+
projects.forEach((p, i) => console.log(` ${i + 1}. ${p.name} ${source_default.dim(`(${p.slug})`)}`));
|
|
26361
|
+
const ans = await prompt7("Project number: ");
|
|
26362
|
+
projectId = projects[parseInt(ans) - 1]?.id ?? projects[0].id;
|
|
26363
|
+
}
|
|
26364
|
+
}
|
|
26365
|
+
const envVars = {};
|
|
26366
|
+
for (const pair of opts.env ?? []) {
|
|
26367
|
+
const eq = pair.indexOf("=");
|
|
26368
|
+
if (eq < 0) {
|
|
26369
|
+
console.error(source_default.red(`--env expects KEY=value, got ${pair}`));
|
|
26370
|
+
process.exitCode = 1;
|
|
26371
|
+
return;
|
|
26372
|
+
}
|
|
26373
|
+
envVars[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
26374
|
+
}
|
|
26375
|
+
for (const v of detail.envVars.filter((e) => e.required)) {
|
|
26376
|
+
if (envVars[v.key]) continue;
|
|
26377
|
+
if (opts.yes) {
|
|
26378
|
+
console.error(source_default.red(`--yes set but required env var ${v.key} missing.`));
|
|
26379
|
+
process.exitCode = 1;
|
|
26380
|
+
return;
|
|
26381
|
+
}
|
|
26382
|
+
console.log(source_default.dim(`
|
|
26383
|
+
${v.description}`));
|
|
26384
|
+
const example = v.example ? source_default.dim(` (e.g. ${v.example})`) : "";
|
|
26385
|
+
const ans = await prompt7(`${source_default.cyan(v.key)}${example}: `);
|
|
26386
|
+
if (!ans) {
|
|
26387
|
+
console.error(source_default.red(`Required env var ${v.key} cannot be empty.`));
|
|
26388
|
+
process.exitCode = 1;
|
|
26389
|
+
return;
|
|
26390
|
+
}
|
|
26391
|
+
envVars[v.key] = ans;
|
|
26392
|
+
}
|
|
26393
|
+
console.log();
|
|
26394
|
+
console.log(source_default.bold(`Deploying template ${source_default.cyan(detail.slug)}: ${detail.name}`));
|
|
26395
|
+
if (detail.databases.length > 0) {
|
|
26396
|
+
console.log(source_default.dim(` + ${detail.databases.join(", ")} ${detail.databases.length === 1 ? "database" : "databases"}`));
|
|
26397
|
+
}
|
|
26398
|
+
console.log();
|
|
26399
|
+
const spin = ora("Generating starter code...").start();
|
|
26400
|
+
try {
|
|
26401
|
+
const res = await api(`/api/templates/${slug}/deploy`, {
|
|
26402
|
+
method: "POST",
|
|
26403
|
+
body: JSON.stringify({
|
|
26404
|
+
projectId,
|
|
26405
|
+
name: opts.name,
|
|
26406
|
+
envVars
|
|
26407
|
+
})
|
|
26408
|
+
});
|
|
26409
|
+
spin.succeed(source_default.green("Template queued"));
|
|
26410
|
+
console.log();
|
|
26411
|
+
console.log(` Service ID: ${source_default.cyan(res.data.serviceId)}`);
|
|
26412
|
+
console.log(` Generation ID: ${source_default.dim(res.data.generationId)}`);
|
|
26413
|
+
console.log();
|
|
26414
|
+
console.log(
|
|
26415
|
+
source_default.dim(`Track progress: ${source_default.bold(`lm deployments list --service ${res.data.serviceId}`)}`)
|
|
26416
|
+
);
|
|
26417
|
+
console.log(source_default.dim(`Stream logs: ${source_default.bold(`lm logs --service ${res.data.serviceId}`)}`));
|
|
26418
|
+
} catch (err) {
|
|
26419
|
+
spin.fail(`Template deploy failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
26420
|
+
process.exitCode = 1;
|
|
26421
|
+
}
|
|
26422
|
+
}
|
|
26423
|
+
function registerTemplate(program3) {
|
|
26424
|
+
const cmd = program3.command("template").alias("tpl").description("Curated starter templates \u2014 Railway-style one-shot deploy");
|
|
26425
|
+
cmd.command("list").alias("ls").description("List available templates").option("-c, --category <cat>", "Filter by category (fullstack, backend, frontend, ai, data, devtools)").action(listCmd);
|
|
26426
|
+
cmd.command("deploy <slug>").description("Deploy a template into a project").option("-p, --project <projectId>", "Project to deploy into (default: from .launchmatic.json or first project)").option("-n, --name <name>", "Service name override (default: template name)").option("-e, --env <KEY=value...>", "Set required env vars inline").option("-y, --yes", "Don't prompt \u2014 fail if required env vars are missing").action(deployCmd);
|
|
26427
|
+
}
|
|
26428
|
+
|
|
25484
26429
|
// src/index.ts
|
|
26430
|
+
var __dirname2 = dirname(fileURLToPath2(import.meta.url));
|
|
26431
|
+
var pkg = JSON.parse(
|
|
26432
|
+
readFileSync8(join6(__dirname2, "..", "package.json"), "utf-8")
|
|
26433
|
+
);
|
|
25485
26434
|
var program2 = new Command();
|
|
25486
|
-
program2.name("lm").description("Launchmatic CLI \u2014 deploy from your terminal").version(
|
|
26435
|
+
program2.name("lm").description("Launchmatic CLI \u2014 deploy from your terminal").version(pkg.version);
|
|
25487
26436
|
registerLogin(program2);
|
|
25488
26437
|
registerInit(program2);
|
|
25489
26438
|
registerDeploy(program2);
|
|
@@ -25504,4 +26453,9 @@ registerPreview(program2);
|
|
|
25504
26453
|
registerApiKey(program2);
|
|
25505
26454
|
registerDeployments(program2);
|
|
25506
26455
|
registerAgent(program2);
|
|
26456
|
+
registerWorkflows(program2);
|
|
26457
|
+
registerImage(program2);
|
|
26458
|
+
registerMonorepo(program2);
|
|
26459
|
+
registerUp(program2);
|
|
26460
|
+
registerTemplate(program2);
|
|
25507
26461
|
program2.parse();
|