@launchmatic/cli 0.6.4 → 0.7.0
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 +160 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21863,6 +21863,83 @@ function readManifest(repoRoot = findRepoRoot()) {
|
|
|
21863
21863
|
function writeManifest(manifest, repoRoot = findRepoRoot()) {
|
|
21864
21864
|
writeFileSync3(manifestPath(repoRoot), JSON.stringify(manifest, null, 2) + "\n");
|
|
21865
21865
|
}
|
|
21866
|
+
var COMPOSE_FILES = ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"];
|
|
21867
|
+
var INFRA_IMAGE_MATCHERS = [
|
|
21868
|
+
{ engine: "POSTGIS", pattern: /^(postgis\/postgis|imresamu\/postgis|.*\/postgis)$/i },
|
|
21869
|
+
{ engine: "POSTGRESQL", pattern: /^(library\/)?(postgres|postgresql)$/i },
|
|
21870
|
+
{ engine: "REDIS", pattern: /^(library\/)?(redis|valkey\/valkey)$/i },
|
|
21871
|
+
{ engine: "MONGODB", pattern: /^(library\/)?mongo(db)?$/i },
|
|
21872
|
+
{ engine: "MYSQL", pattern: /^(library\/)?(mysql|mariadb)$/i }
|
|
21873
|
+
];
|
|
21874
|
+
function matchInfraImage(image) {
|
|
21875
|
+
const [imageRef, tag] = image.split(":");
|
|
21876
|
+
const lastSlash = imageRef.lastIndexOf("/");
|
|
21877
|
+
const namespace = lastSlash > 0 ? imageRef.slice(0, lastSlash) : "library";
|
|
21878
|
+
const repo = lastSlash > 0 ? imageRef.slice(lastSlash + 1) : imageRef;
|
|
21879
|
+
const candidate = `${namespace}/${repo}`;
|
|
21880
|
+
for (const m of INFRA_IMAGE_MATCHERS) {
|
|
21881
|
+
if (m.pattern.test(candidate) || m.pattern.test(repo)) {
|
|
21882
|
+
const version = tag ? tag.replace(/-(alpine|bullseye|bookworm|slim|debian).*$/i, "") : void 0;
|
|
21883
|
+
return { engine: m.engine, version };
|
|
21884
|
+
}
|
|
21885
|
+
}
|
|
21886
|
+
return null;
|
|
21887
|
+
}
|
|
21888
|
+
function discoverInfra(repoRoot = findRepoRoot()) {
|
|
21889
|
+
for (const file of COMPOSE_FILES) {
|
|
21890
|
+
const full = join2(repoRoot, file);
|
|
21891
|
+
if (!existsSync3(full)) continue;
|
|
21892
|
+
try {
|
|
21893
|
+
return parseComposeInfra(readFileSync3(full, "utf-8"));
|
|
21894
|
+
} catch {
|
|
21895
|
+
return [];
|
|
21896
|
+
}
|
|
21897
|
+
}
|
|
21898
|
+
return [];
|
|
21899
|
+
}
|
|
21900
|
+
function parseComposeInfra(yaml) {
|
|
21901
|
+
const lines = yaml.split(/\r?\n/);
|
|
21902
|
+
const out = [];
|
|
21903
|
+
let inServices = false;
|
|
21904
|
+
let currentService = null;
|
|
21905
|
+
let servicesIndent = -1;
|
|
21906
|
+
for (const raw of lines) {
|
|
21907
|
+
const line = raw.replace(/#.*$/, "").replace(/\s+$/, "");
|
|
21908
|
+
if (!line.trim()) continue;
|
|
21909
|
+
const indent = line.match(/^\s*/)?.[0].length ?? 0;
|
|
21910
|
+
if (!inServices) {
|
|
21911
|
+
if (/^services\s*:\s*$/.test(line)) {
|
|
21912
|
+
inServices = true;
|
|
21913
|
+
servicesIndent = indent;
|
|
21914
|
+
}
|
|
21915
|
+
continue;
|
|
21916
|
+
}
|
|
21917
|
+
if (indent <= servicesIndent && !/^\s*-/.test(line)) {
|
|
21918
|
+
inServices = false;
|
|
21919
|
+
currentService = null;
|
|
21920
|
+
continue;
|
|
21921
|
+
}
|
|
21922
|
+
const isServiceName = /^\s*([A-Za-z0-9_-]+)\s*:\s*$/.test(line);
|
|
21923
|
+
if (isServiceName && indent === servicesIndent + 2) {
|
|
21924
|
+
currentService = line.trim().replace(/:$/, "");
|
|
21925
|
+
continue;
|
|
21926
|
+
}
|
|
21927
|
+
if (currentService) {
|
|
21928
|
+
const m = line.match(/^\s*image\s*:\s*['"]?([^'"\s]+)['"]?\s*$/);
|
|
21929
|
+
if (m) {
|
|
21930
|
+
const matched = matchInfraImage(m[1]);
|
|
21931
|
+
if (matched) {
|
|
21932
|
+
out.push({
|
|
21933
|
+
name: currentService,
|
|
21934
|
+
engine: matched.engine,
|
|
21935
|
+
version: matched.version
|
|
21936
|
+
});
|
|
21937
|
+
}
|
|
21938
|
+
}
|
|
21939
|
+
}
|
|
21940
|
+
}
|
|
21941
|
+
return out;
|
|
21942
|
+
}
|
|
21866
21943
|
function discoverServices(repoRoot = findRepoRoot()) {
|
|
21867
21944
|
const globs = readWorkspaceGlobs(repoRoot);
|
|
21868
21945
|
const dirs = /* @__PURE__ */ new Set();
|
|
@@ -22623,6 +22700,7 @@ function renderServiceBlock(data, { indent }) {
|
|
|
22623
22700
|
}
|
|
22624
22701
|
}
|
|
22625
22702
|
function statusColor(status) {
|
|
22703
|
+
if (!status) return source_default.dim("unknown");
|
|
22626
22704
|
switch (status.toUpperCase()) {
|
|
22627
22705
|
case "ACTIVE":
|
|
22628
22706
|
case "RUNNING":
|
|
@@ -26153,8 +26231,9 @@ async function initManifest(opts) {
|
|
|
26153
26231
|
process.exitCode = 1;
|
|
26154
26232
|
return;
|
|
26155
26233
|
}
|
|
26156
|
-
const spin = ora("Discovering services...").start();
|
|
26234
|
+
const spin = ora("Discovering services and infra...").start();
|
|
26157
26235
|
const discovered = discoverServices(repoRoot);
|
|
26236
|
+
const discoveredInfra = discoverInfra(repoRoot);
|
|
26158
26237
|
spin.stop();
|
|
26159
26238
|
if (discovered.length === 0) {
|
|
26160
26239
|
console.error(source_default.red("No services discovered."));
|
|
@@ -26169,6 +26248,14 @@ Discovered ${discovered.length} service${discovered.length === 1 ? "" : "s"}:`))
|
|
|
26169
26248
|
const fw = s.framework ? source_default.dim(` [${s.framework}]`) : "";
|
|
26170
26249
|
console.log(` ${source_default.cyan(s.name)} ${source_default.dim(s.rootDir)}${fw}`);
|
|
26171
26250
|
}
|
|
26251
|
+
if (discoveredInfra.length > 0) {
|
|
26252
|
+
console.log(source_default.bold(`
|
|
26253
|
+
Discovered ${discoveredInfra.length} infra dependenc${discoveredInfra.length === 1 ? "y" : "ies"} (from docker-compose):`));
|
|
26254
|
+
for (const i of discoveredInfra) {
|
|
26255
|
+
const v = i.version ? source_default.dim(` ${i.version}`) : "";
|
|
26256
|
+
console.log(` ${source_default.magenta(i.name)} ${source_default.dim(i.engine)}${v}`);
|
|
26257
|
+
}
|
|
26258
|
+
}
|
|
26172
26259
|
const { data: teams } = await api("/api/teams");
|
|
26173
26260
|
if (!teams || teams.length === 0) {
|
|
26174
26261
|
console.error(source_default.red("No teams found on your account."));
|
|
@@ -26269,15 +26356,76 @@ Discovered ${discovered.length} service${discovered.length === 1 ? "" : "s"}:`))
|
|
|
26269
26356
|
port: s.port
|
|
26270
26357
|
});
|
|
26271
26358
|
}
|
|
26359
|
+
const infra = [];
|
|
26360
|
+
if (discoveredInfra.length > 0) {
|
|
26361
|
+
const shouldProvision = opts.yes || (await prompt6(
|
|
26362
|
+
`
|
|
26363
|
+
Provision ${discoveredInfra.length} database${discoveredInfra.length === 1 ? "" : "s"} on Launchmatic? [Y/n]: `
|
|
26364
|
+
)).toLowerCase().replace(/^$/, "y").startsWith("y");
|
|
26365
|
+
if (shouldProvision) {
|
|
26366
|
+
const { data: existingDbs } = await api(`/api/databases?projectId=${projectId}`);
|
|
26367
|
+
for (const inf of discoveredInfra) {
|
|
26368
|
+
const dbName = `${projectSlug}-${inf.name}`.slice(0, 100);
|
|
26369
|
+
const already = existingDbs.find(
|
|
26370
|
+
(d) => d.name === dbName && d.engine === inf.engine
|
|
26371
|
+
);
|
|
26372
|
+
let databaseId = already?.id;
|
|
26373
|
+
if (!databaseId) {
|
|
26374
|
+
try {
|
|
26375
|
+
const created = await api(
|
|
26376
|
+
"/api/databases",
|
|
26377
|
+
{
|
|
26378
|
+
method: "POST",
|
|
26379
|
+
body: JSON.stringify({
|
|
26380
|
+
name: dbName,
|
|
26381
|
+
engine: inf.engine,
|
|
26382
|
+
projectId,
|
|
26383
|
+
// version is optional — let the API pick the default if undefined
|
|
26384
|
+
...inf.version ? { version: inf.version } : {}
|
|
26385
|
+
})
|
|
26386
|
+
}
|
|
26387
|
+
);
|
|
26388
|
+
databaseId = created.data.id;
|
|
26389
|
+
console.log(source_default.green(`Provisioned ${source_default.bold(inf.engine)} ${source_default.dim(`(${dbName})`)}`));
|
|
26390
|
+
} catch (err) {
|
|
26391
|
+
console.warn(
|
|
26392
|
+
source_default.yellow(
|
|
26393
|
+
`Could not provision ${inf.name}: ${err instanceof Error ? err.message : String(err)}`
|
|
26394
|
+
)
|
|
26395
|
+
);
|
|
26396
|
+
}
|
|
26397
|
+
} else {
|
|
26398
|
+
console.log(source_default.dim(`Using existing ${inf.engine} ${dbName}`));
|
|
26399
|
+
}
|
|
26400
|
+
infra.push({
|
|
26401
|
+
name: inf.name,
|
|
26402
|
+
engine: inf.engine,
|
|
26403
|
+
...inf.version ? { version: inf.version } : {},
|
|
26404
|
+
...databaseId ? { databaseId } : {}
|
|
26405
|
+
});
|
|
26406
|
+
}
|
|
26407
|
+
} else {
|
|
26408
|
+
for (const inf of discoveredInfra) {
|
|
26409
|
+
infra.push({
|
|
26410
|
+
name: inf.name,
|
|
26411
|
+
engine: inf.engine,
|
|
26412
|
+
...inf.version ? { version: inf.version } : {}
|
|
26413
|
+
});
|
|
26414
|
+
}
|
|
26415
|
+
console.log(source_default.dim("Skipped provisioning \u2014 entries recorded in manifest only."));
|
|
26416
|
+
}
|
|
26417
|
+
}
|
|
26272
26418
|
const manifest = {
|
|
26273
26419
|
version: 1,
|
|
26274
26420
|
teamId,
|
|
26275
26421
|
projectId,
|
|
26276
|
-
services
|
|
26422
|
+
services,
|
|
26423
|
+
...infra.length > 0 ? { infra } : {}
|
|
26277
26424
|
};
|
|
26278
26425
|
writeManifest(manifest, repoRoot);
|
|
26279
26426
|
console.log();
|
|
26280
|
-
|
|
26427
|
+
const infraStr = infra.length > 0 ? `, ${infra.length} infra` : "";
|
|
26428
|
+
console.log(source_default.green(`\u2713 Wrote ${source_default.bold(MANIFEST_FILE)} (${services.length} services${infraStr})`));
|
|
26281
26429
|
console.log(source_default.dim("Next: ") + source_default.bold("lm up") + source_default.dim(" to deploy everything."));
|
|
26282
26430
|
}
|
|
26283
26431
|
async function listManifest() {
|
|
@@ -26293,6 +26441,15 @@ async function listManifest() {
|
|
|
26293
26441
|
const sid = s.serviceId ? source_default.dim(` ${s.serviceId}`) : source_default.yellow(" (not yet created)");
|
|
26294
26442
|
console.log(` ${source_default.cyan(s.name.padEnd(16))} ${source_default.dim(s.rootDir)}${fw}${sid}`);
|
|
26295
26443
|
}
|
|
26444
|
+
if (manifest.infra && manifest.infra.length > 0) {
|
|
26445
|
+
console.log(source_default.bold(`
|
|
26446
|
+
${manifest.infra.length} infra:`));
|
|
26447
|
+
for (const i of manifest.infra) {
|
|
26448
|
+
const ver = i.version ? source_default.dim(` ${i.version}`) : "";
|
|
26449
|
+
const did = i.databaseId ? source_default.dim(` ${i.databaseId}`) : source_default.yellow(" (not yet provisioned)");
|
|
26450
|
+
console.log(` ${source_default.magenta(i.name.padEnd(16))} ${source_default.dim(i.engine)}${ver}${did}`);
|
|
26451
|
+
}
|
|
26452
|
+
}
|
|
26296
26453
|
}
|
|
26297
26454
|
async function up(opts) {
|
|
26298
26455
|
if (!requireLogin3()) return;
|