@getmonoceros/workbench 1.13.2 → 1.14.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/bin.js +502 -357
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -129,19 +129,19 @@ function wrapText(text, width, continuationIndent) {
|
|
|
129
129
|
if (current.length > 0) lines.push(current.replace(/\s+$/, ""));
|
|
130
130
|
return lines.map((l, i) => i === 0 ? l : continuationIndent + l).join("\n");
|
|
131
131
|
}
|
|
132
|
-
function alignTable(rows, indent) {
|
|
132
|
+
function alignTable(rows, indent, opts = {}) {
|
|
133
133
|
if (rows.length === 0) return "";
|
|
134
|
-
const labelWidth = Math.max(...rows.map((r) => visibleLen(r[0])));
|
|
134
|
+
const labelWidth = opts.fixedLabelWidth ?? Math.max(...rows.map((r) => visibleLen(r[0])));
|
|
135
135
|
const gutter = " ";
|
|
136
136
|
const descWidth = terminalWidth() - indent.length - labelWidth - gutter.length;
|
|
137
137
|
const continuationIndent = " ".repeat(
|
|
138
138
|
indent.length + labelWidth + gutter.length
|
|
139
139
|
);
|
|
140
140
|
return rows.map(([left, right]) => {
|
|
141
|
-
const pad = " ".repeat(labelWidth - visibleLen(left));
|
|
141
|
+
const pad = " ".repeat(Math.max(0, labelWidth - visibleLen(left)));
|
|
142
142
|
const wrapped = wrapText(right, descWidth, continuationIndent);
|
|
143
143
|
return `${indent}${left}${pad}${gutter}${wrapped}`;
|
|
144
|
-
}).join("\n");
|
|
144
|
+
}).join(opts.rowGap ? "\n\n" : "\n");
|
|
145
145
|
}
|
|
146
146
|
function collectSubCommands(cmd) {
|
|
147
147
|
const subs = cmd.subCommands ?? {};
|
|
@@ -167,6 +167,7 @@ function renderCommandsBlock(entries) {
|
|
|
167
167
|
arr.push(entry2);
|
|
168
168
|
byGroup.set(entry2.group, arr);
|
|
169
169
|
}
|
|
170
|
+
const labelWidth = Math.max(...entries.map((e) => visibleLen(cyan(e.name))));
|
|
170
171
|
const renderSection = (label, items) => {
|
|
171
172
|
if (items.length === 0) return;
|
|
172
173
|
lines.push("");
|
|
@@ -176,7 +177,9 @@ function renderCommandsBlock(entries) {
|
|
|
176
177
|
cyan(e.name),
|
|
177
178
|
e.description
|
|
178
179
|
]);
|
|
179
|
-
lines.push(
|
|
180
|
+
lines.push(
|
|
181
|
+
alignTable(rows, "", { fixedLabelWidth: labelWidth, rowGap: true })
|
|
182
|
+
);
|
|
180
183
|
};
|
|
181
184
|
for (const { key, label } of GROUPS) {
|
|
182
185
|
renderSection(label, byGroup.get(key) ?? []);
|
|
@@ -619,6 +622,9 @@ function containersDir(home = monocerosHome()) {
|
|
|
619
622
|
function containerDir(name, home = monocerosHome()) {
|
|
620
623
|
return path.join(containersDir(home), name);
|
|
621
624
|
}
|
|
625
|
+
function containerLogsDir(name, home = monocerosHome()) {
|
|
626
|
+
return path.join(containerDir(name, home), "logs");
|
|
627
|
+
}
|
|
622
628
|
function monocerosConfigPath(home = monocerosHome()) {
|
|
623
629
|
return path.join(home, "monoceros-config.yml");
|
|
624
630
|
}
|
|
@@ -982,6 +988,10 @@ var ANSI_UNDERLINE2 = `${ESC}4m`;
|
|
|
982
988
|
var ANSI_CYAN2 = `${ESC}36m`;
|
|
983
989
|
var ANSI_GREY2 = `${ESC}90m`;
|
|
984
990
|
var ANSI_RESET2 = `${ESC}0m`;
|
|
991
|
+
var ANSI_RE2 = /\x1b\[[0-9;]*m/g;
|
|
992
|
+
function stripAnsi(s) {
|
|
993
|
+
return s.replace(ANSI_RE2, "");
|
|
994
|
+
}
|
|
985
995
|
function makeWrap(isTty2) {
|
|
986
996
|
return (s, ...codes) => isTty2 ? codes.join("") + s + ANSI_RESET2 : s;
|
|
987
997
|
}
|
|
@@ -4137,7 +4147,7 @@ var addServiceCommand = defineCommand7({
|
|
|
4137
4147
|
import { defineCommand as defineCommand8 } from "citty";
|
|
4138
4148
|
|
|
4139
4149
|
// src/apply/index.ts
|
|
4140
|
-
import { existsSync as
|
|
4150
|
+
import { existsSync as existsSync7, promises as fs11 } from "fs";
|
|
4141
4151
|
import { consola as consola11 } from "consola";
|
|
4142
4152
|
|
|
4143
4153
|
// src/config/state.ts
|
|
@@ -4237,10 +4247,292 @@ function solutionConfigToCreateOptions(config, featureDefaults = {}) {
|
|
|
4237
4247
|
return result;
|
|
4238
4248
|
}
|
|
4239
4249
|
|
|
4250
|
+
// src/apply/apply-log.ts
|
|
4251
|
+
import { createWriteStream, mkdirSync } from "fs";
|
|
4252
|
+
import path11 from "path";
|
|
4253
|
+
import { Writable } from "stream";
|
|
4254
|
+
function safeIsoStamp(d) {
|
|
4255
|
+
return d.toISOString().replace(/[:.]/g, "-");
|
|
4256
|
+
}
|
|
4257
|
+
function createApplyLog(opts) {
|
|
4258
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
4259
|
+
const dir = containerLogsDir(opts.name, opts.home);
|
|
4260
|
+
mkdirSync(dir, { recursive: true });
|
|
4261
|
+
const file = `apply-${opts.name}-${safeIsoStamp(now)}.log`;
|
|
4262
|
+
const fullPath = path11.join(dir, file);
|
|
4263
|
+
const stream = createWriteStream(fullPath, { flags: "w" });
|
|
4264
|
+
const header = [
|
|
4265
|
+
`# monoceros apply log`,
|
|
4266
|
+
`# command: monoceros apply ${opts.name}`,
|
|
4267
|
+
`# started: ${now.toISOString()}`,
|
|
4268
|
+
`# cli-version: ${opts.cliVersion}`,
|
|
4269
|
+
`# config: ${opts.configPath}`,
|
|
4270
|
+
`# host: ${process.platform}/${process.arch} node ${process.version}`,
|
|
4271
|
+
``,
|
|
4272
|
+
``
|
|
4273
|
+
].join("\n");
|
|
4274
|
+
stream.write(header);
|
|
4275
|
+
const sink = new Writable({
|
|
4276
|
+
write(chunk, _enc, cb) {
|
|
4277
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
4278
|
+
stream.write(stripAnsi(text), cb);
|
|
4279
|
+
}
|
|
4280
|
+
});
|
|
4281
|
+
let closed = false;
|
|
4282
|
+
return {
|
|
4283
|
+
path: fullPath,
|
|
4284
|
+
stream,
|
|
4285
|
+
sink,
|
|
4286
|
+
close: () => new Promise((resolve) => {
|
|
4287
|
+
if (closed) {
|
|
4288
|
+
resolve();
|
|
4289
|
+
return;
|
|
4290
|
+
}
|
|
4291
|
+
closed = true;
|
|
4292
|
+
sink.end(() => {
|
|
4293
|
+
stream.end(() => resolve());
|
|
4294
|
+
});
|
|
4295
|
+
})
|
|
4296
|
+
};
|
|
4297
|
+
}
|
|
4298
|
+
function teeApplyLogger(base, sink) {
|
|
4299
|
+
const write = (level, msg) => {
|
|
4300
|
+
sink.write(`[${level}] ${msg}
|
|
4301
|
+
`);
|
|
4302
|
+
};
|
|
4303
|
+
const wrapped = {
|
|
4304
|
+
info: (msg) => {
|
|
4305
|
+
base.info(msg);
|
|
4306
|
+
write("info", msg);
|
|
4307
|
+
},
|
|
4308
|
+
success: (msg) => {
|
|
4309
|
+
base.success(msg);
|
|
4310
|
+
write("ok", msg);
|
|
4311
|
+
},
|
|
4312
|
+
warn: (msg) => {
|
|
4313
|
+
(base.warn ?? base.info)(msg);
|
|
4314
|
+
write("warn", msg);
|
|
4315
|
+
}
|
|
4316
|
+
};
|
|
4317
|
+
if (base.section) wrapped.section = base.section.bind(base);
|
|
4318
|
+
return wrapped;
|
|
4319
|
+
}
|
|
4320
|
+
|
|
4321
|
+
// src/apply/apply-progress.ts
|
|
4322
|
+
import { Writable as Writable2 } from "stream";
|
|
4323
|
+
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4324
|
+
var FRAME_INTERVAL_MS = 80;
|
|
4325
|
+
var TAIL_LINES = 15;
|
|
4326
|
+
var PHASE_TRIGGERS = [
|
|
4327
|
+
// Compose mode triggers a feature/layer build before the container
|
|
4328
|
+
// is created — distinct phase, often the longest single step.
|
|
4329
|
+
{ pattern: /Start: Run: docker build/i, label: "building feature layers\u2026" },
|
|
4330
|
+
// Image mode jumps straight from "preparing…" into the docker run
|
|
4331
|
+
// that pulls (if needed) + creates + starts the container.
|
|
4332
|
+
{ pattern: /Start: Run: docker run/i, label: "starting container\u2026" },
|
|
4333
|
+
{ pattern: /Running the postCreateCommand/i, label: "running postCreate\u2026" }
|
|
4334
|
+
];
|
|
4335
|
+
function createApplyProgress(opts) {
|
|
4336
|
+
const out = opts.out;
|
|
4337
|
+
const now = opts.now ?? (() => Date.now());
|
|
4338
|
+
const startedAt = now();
|
|
4339
|
+
let phase = "preparing\u2026";
|
|
4340
|
+
let frameIdx = 0;
|
|
4341
|
+
let timer = null;
|
|
4342
|
+
let stopped = false;
|
|
4343
|
+
const tail = [];
|
|
4344
|
+
let lineBuf = "";
|
|
4345
|
+
const writeSpinner = () => {
|
|
4346
|
+
if (!opts.interactive || stopped) return;
|
|
4347
|
+
out.write(`\r\x1B[K${FRAMES[frameIdx]} ${phase}`);
|
|
4348
|
+
};
|
|
4349
|
+
const clearLine = () => {
|
|
4350
|
+
if (!opts.interactive) return;
|
|
4351
|
+
out.write("\r\x1B[K");
|
|
4352
|
+
};
|
|
4353
|
+
const setPhase = (label) => {
|
|
4354
|
+
if (phase === label) return;
|
|
4355
|
+
phase = label;
|
|
4356
|
+
if (opts.interactive) {
|
|
4357
|
+
writeSpinner();
|
|
4358
|
+
} else {
|
|
4359
|
+
out.write(`> ${label}
|
|
4360
|
+
`);
|
|
4361
|
+
}
|
|
4362
|
+
};
|
|
4363
|
+
const println = (line) => {
|
|
4364
|
+
clearLine();
|
|
4365
|
+
const withNewline = line.endsWith("\n") ? line : `${line}
|
|
4366
|
+
`;
|
|
4367
|
+
out.write(withNewline);
|
|
4368
|
+
writeSpinner();
|
|
4369
|
+
};
|
|
4370
|
+
const fmtElapsed = () => {
|
|
4371
|
+
const ms = now() - startedAt;
|
|
4372
|
+
const totalSec = Math.max(0, Math.round(ms / 1e3));
|
|
4373
|
+
const m = Math.floor(totalSec / 60);
|
|
4374
|
+
const s = totalSec % 60;
|
|
4375
|
+
return m > 0 ? `${m}m ${s}s` : `${s}s`;
|
|
4376
|
+
};
|
|
4377
|
+
const stop = () => {
|
|
4378
|
+
if (timer) {
|
|
4379
|
+
clearInterval(timer);
|
|
4380
|
+
timer = null;
|
|
4381
|
+
}
|
|
4382
|
+
if (!stopped) {
|
|
4383
|
+
stopped = true;
|
|
4384
|
+
clearLine();
|
|
4385
|
+
}
|
|
4386
|
+
};
|
|
4387
|
+
const succeed = (label) => {
|
|
4388
|
+
stop();
|
|
4389
|
+
const text = label ?? `container ready (${fmtElapsed()})`;
|
|
4390
|
+
out.write(`\u2714 ${text}
|
|
4391
|
+
`);
|
|
4392
|
+
};
|
|
4393
|
+
const fail = () => {
|
|
4394
|
+
stop();
|
|
4395
|
+
return { tailLines: [...tail] };
|
|
4396
|
+
};
|
|
4397
|
+
const streamSink = new Writable2({
|
|
4398
|
+
write(chunk, _enc, cb) {
|
|
4399
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
4400
|
+
lineBuf += stripAnsi(text);
|
|
4401
|
+
let nl;
|
|
4402
|
+
while ((nl = lineBuf.indexOf("\n")) !== -1) {
|
|
4403
|
+
const line = lineBuf.slice(0, nl);
|
|
4404
|
+
lineBuf = lineBuf.slice(nl + 1);
|
|
4405
|
+
if (line.length === 0) continue;
|
|
4406
|
+
tail.push(line);
|
|
4407
|
+
if (tail.length > TAIL_LINES) tail.shift();
|
|
4408
|
+
for (const trig of PHASE_TRIGGERS) {
|
|
4409
|
+
if (trig.pattern.test(line)) {
|
|
4410
|
+
setPhase(trig.label);
|
|
4411
|
+
break;
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
cb();
|
|
4416
|
+
}
|
|
4417
|
+
});
|
|
4418
|
+
if (opts.interactive) {
|
|
4419
|
+
writeSpinner();
|
|
4420
|
+
timer = setInterval(() => {
|
|
4421
|
+
frameIdx = (frameIdx + 1) % FRAMES.length;
|
|
4422
|
+
writeSpinner();
|
|
4423
|
+
}, FRAME_INTERVAL_MS);
|
|
4424
|
+
timer.unref?.();
|
|
4425
|
+
}
|
|
4426
|
+
return {
|
|
4427
|
+
setPhase,
|
|
4428
|
+
println,
|
|
4429
|
+
succeed,
|
|
4430
|
+
fail,
|
|
4431
|
+
streamSink
|
|
4432
|
+
};
|
|
4433
|
+
}
|
|
4434
|
+
function progressTeeLogger(progress, sink) {
|
|
4435
|
+
const fileLine = (level, msg) => {
|
|
4436
|
+
sink.write(`[${level}] ${msg}
|
|
4437
|
+
`);
|
|
4438
|
+
};
|
|
4439
|
+
return {
|
|
4440
|
+
info: (msg) => {
|
|
4441
|
+
progress.println(msg);
|
|
4442
|
+
fileLine("info", msg);
|
|
4443
|
+
},
|
|
4444
|
+
success: (msg) => {
|
|
4445
|
+
progress.println(`\u2714 ${msg}`);
|
|
4446
|
+
fileLine("ok", msg);
|
|
4447
|
+
},
|
|
4448
|
+
warn: (msg) => {
|
|
4449
|
+
progress.println(`! ${msg}`);
|
|
4450
|
+
fileLine("warn", msg);
|
|
4451
|
+
}
|
|
4452
|
+
};
|
|
4453
|
+
}
|
|
4454
|
+
function logFileOnlyLogger(sink) {
|
|
4455
|
+
const fileLine = (level, msg) => {
|
|
4456
|
+
sink.write(`[${level}] ${msg}
|
|
4457
|
+
`);
|
|
4458
|
+
};
|
|
4459
|
+
return {
|
|
4460
|
+
info: (msg) => fileLine("info", msg),
|
|
4461
|
+
success: (msg) => fileLine("ok", msg),
|
|
4462
|
+
warn: (msg) => fileLine("warn", msg)
|
|
4463
|
+
};
|
|
4464
|
+
}
|
|
4465
|
+
function createSigintAbort(deps) {
|
|
4466
|
+
let aborted = false;
|
|
4467
|
+
return () => {
|
|
4468
|
+
if (aborted) return;
|
|
4469
|
+
aborted = true;
|
|
4470
|
+
if (deps.progress) deps.progress.fail();
|
|
4471
|
+
deps.out.write("\n\u23F9 aborted\n");
|
|
4472
|
+
deps.log.stream.write("\n[abort] SIGINT received\n");
|
|
4473
|
+
void deps.log.close().finally(() => {
|
|
4474
|
+
deps.out.write(`
|
|
4475
|
+
${deps.formatLogPointer(deps.log.path)}
|
|
4476
|
+
`);
|
|
4477
|
+
deps.onExit();
|
|
4478
|
+
});
|
|
4479
|
+
};
|
|
4480
|
+
}
|
|
4481
|
+
|
|
4482
|
+
// src/apply/apply-summary.ts
|
|
4483
|
+
function shortFeatureName(ref) {
|
|
4484
|
+
const withoutTag = ref.replace(/:[^:/@]+$/, "");
|
|
4485
|
+
const idx = withoutTag.lastIndexOf("/");
|
|
4486
|
+
return idx >= 0 ? withoutTag.slice(idx + 1) : withoutTag;
|
|
4487
|
+
}
|
|
4488
|
+
function shortRepoName(repo) {
|
|
4489
|
+
const last = repo.path.split("/").filter(Boolean).pop();
|
|
4490
|
+
return last && last.length > 0 ? last : repo.url;
|
|
4491
|
+
}
|
|
4492
|
+
function buildApplySummary(opts) {
|
|
4493
|
+
const lines = [];
|
|
4494
|
+
if (opts.languages.length > 0) {
|
|
4495
|
+
lines.push({ label: "Languages", values: opts.languages });
|
|
4496
|
+
}
|
|
4497
|
+
if (opts.services.length > 0) {
|
|
4498
|
+
lines.push({
|
|
4499
|
+
label: "Services",
|
|
4500
|
+
values: opts.services.map((s) => s.name)
|
|
4501
|
+
});
|
|
4502
|
+
}
|
|
4503
|
+
if (opts.features && Object.keys(opts.features).length > 0) {
|
|
4504
|
+
lines.push({
|
|
4505
|
+
label: "Features",
|
|
4506
|
+
values: Object.keys(opts.features).map(shortFeatureName)
|
|
4507
|
+
});
|
|
4508
|
+
}
|
|
4509
|
+
if (opts.repos && opts.repos.length > 0) {
|
|
4510
|
+
lines.push({
|
|
4511
|
+
label: "Repositories",
|
|
4512
|
+
values: opts.repos.map(shortRepoName)
|
|
4513
|
+
});
|
|
4514
|
+
}
|
|
4515
|
+
if (opts.ports && opts.ports.length > 0) {
|
|
4516
|
+
lines.push({ label: "Ports", values: opts.ports.map(String) });
|
|
4517
|
+
}
|
|
4518
|
+
if (opts.aptPackages && opts.aptPackages.length > 0) {
|
|
4519
|
+
lines.push({ label: "APT packages", values: opts.aptPackages });
|
|
4520
|
+
}
|
|
4521
|
+
if (opts.installUrls && opts.installUrls.length > 0) {
|
|
4522
|
+
lines.push({ label: "Install URLs", values: opts.installUrls });
|
|
4523
|
+
}
|
|
4524
|
+
return lines;
|
|
4525
|
+
}
|
|
4526
|
+
function formatApplySummary(lines) {
|
|
4527
|
+
if (lines.length === 0) return "";
|
|
4528
|
+
const labelWidth = Math.max(...lines.map((l) => l.label.length));
|
|
4529
|
+
return lines.map((l) => ` ${l.label.padEnd(labelWidth)} ${cyan2(l.values.join(", "))}`).join("\n");
|
|
4530
|
+
}
|
|
4531
|
+
|
|
4240
4532
|
// src/devcontainer/compose.ts
|
|
4241
4533
|
import { spawn as spawn5 } from "child_process";
|
|
4242
4534
|
import { existsSync as existsSync6 } from "fs";
|
|
4243
|
-
import
|
|
4535
|
+
import path13 from "path";
|
|
4244
4536
|
import { consola as consola9 } from "consola";
|
|
4245
4537
|
|
|
4246
4538
|
// src/util/mask-secrets.ts
|
|
@@ -4303,7 +4595,7 @@ function createSecretMaskStream() {
|
|
|
4303
4595
|
import { spawn as spawn4 } from "child_process";
|
|
4304
4596
|
import { readFileSync as readFileSync4 } from "fs";
|
|
4305
4597
|
import { createRequire } from "module";
|
|
4306
|
-
import
|
|
4598
|
+
import path12 from "path";
|
|
4307
4599
|
|
|
4308
4600
|
// src/devcontainer/runtime-pull-hint.ts
|
|
4309
4601
|
import { Transform as Transform2 } from "stream";
|
|
@@ -4354,7 +4646,7 @@ function devcontainerCliPath() {
|
|
|
4354
4646
|
if (!binEntry) {
|
|
4355
4647
|
throw new Error("Could not resolve @devcontainers/cli bin entry.");
|
|
4356
4648
|
}
|
|
4357
|
-
cachedBinaryPath =
|
|
4649
|
+
cachedBinaryPath = path12.resolve(path12.dirname(pkgJsonPath), binEntry);
|
|
4358
4650
|
return cachedBinaryPath;
|
|
4359
4651
|
}
|
|
4360
4652
|
var spawnDevcontainer = (args, cwd, options = {}) => {
|
|
@@ -4394,8 +4686,20 @@ var spawnDevcontainer = (args, cwd, options = {}) => {
|
|
|
4394
4686
|
return;
|
|
4395
4687
|
}
|
|
4396
4688
|
const pullHint = { hinted: false };
|
|
4397
|
-
child.stdout?.pipe(createSecretMaskStream()).pipe(createRuntimePullHintStream(pullHint))
|
|
4398
|
-
child.stderr?.pipe(createSecretMaskStream()).pipe(createRuntimePullHintStream(pullHint))
|
|
4689
|
+
const stdoutPipe = child.stdout?.pipe(createSecretMaskStream()).pipe(createRuntimePullHintStream(pullHint));
|
|
4690
|
+
const stderrPipe = child.stderr?.pipe(createSecretMaskStream()).pipe(createRuntimePullHintStream(pullHint));
|
|
4691
|
+
if (!options.silent) {
|
|
4692
|
+
stdoutPipe?.pipe(process.stdout);
|
|
4693
|
+
stderrPipe?.pipe(process.stderr);
|
|
4694
|
+
}
|
|
4695
|
+
if (options.logSink) {
|
|
4696
|
+
stdoutPipe?.pipe(options.logSink, { end: false });
|
|
4697
|
+
stderrPipe?.pipe(options.logSink, { end: false });
|
|
4698
|
+
}
|
|
4699
|
+
if (options.progressSink) {
|
|
4700
|
+
stdoutPipe?.pipe(options.progressSink, { end: false });
|
|
4701
|
+
stderrPipe?.pipe(options.progressSink, { end: false });
|
|
4702
|
+
}
|
|
4399
4703
|
child.on("error", reject);
|
|
4400
4704
|
child.on("exit", (code) => resolve(code ?? 0));
|
|
4401
4705
|
});
|
|
@@ -4472,15 +4776,15 @@ async function cleanupDockerObjects(opts) {
|
|
|
4472
4776
|
return { exitCode: rmExit, removedIds: ids };
|
|
4473
4777
|
}
|
|
4474
4778
|
function composeProjectName(root) {
|
|
4475
|
-
return `${
|
|
4779
|
+
return `${path13.basename(root)}_devcontainer`;
|
|
4476
4780
|
}
|
|
4477
4781
|
function resolveCompose(root) {
|
|
4478
|
-
if (!existsSync6(
|
|
4782
|
+
if (!existsSync6(path13.join(root, ".devcontainer"))) {
|
|
4479
4783
|
throw new Error(
|
|
4480
4784
|
`No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
|
|
4481
4785
|
);
|
|
4482
4786
|
}
|
|
4483
|
-
const composeFile =
|
|
4787
|
+
const composeFile = path13.join(root, ".devcontainer", "compose.yaml");
|
|
4484
4788
|
if (!existsSync6(composeFile)) {
|
|
4485
4789
|
throw new Error(
|
|
4486
4790
|
`No compose.yaml at ${composeFile}. \`start\` / \`stop\` / \`status\` / \`logs\` require services configured via \`monoceros add-service <name> <svc>\`. Use \`monoceros shell <name>\` to enter the container directly.`
|
|
@@ -4501,9 +4805,17 @@ async function runStart(opts) {
|
|
|
4501
4805
|
logger.info(`Bringing devcontainer up at ${opts.root}\u2026`);
|
|
4502
4806
|
return spawnFn(
|
|
4503
4807
|
["up", "--workspace-folder", opts.root, "--mount-workspace-git-root=false"],
|
|
4504
|
-
opts.root
|
|
4808
|
+
opts.root,
|
|
4809
|
+
buildSpawnOptions(opts)
|
|
4505
4810
|
);
|
|
4506
4811
|
}
|
|
4812
|
+
function buildSpawnOptions(opts) {
|
|
4813
|
+
const out = {};
|
|
4814
|
+
if (opts.logSink) out.logSink = opts.logSink;
|
|
4815
|
+
if (opts.progressSink) out.progressSink = opts.progressSink;
|
|
4816
|
+
if (opts.silent) out.silent = true;
|
|
4817
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
4818
|
+
}
|
|
4507
4819
|
async function runContainerCycle(root, opts) {
|
|
4508
4820
|
const { hasCompose, logger } = opts;
|
|
4509
4821
|
if (hasCompose) {
|
|
@@ -4539,6 +4851,9 @@ and retry \`monoceros apply\`.`
|
|
|
4539
4851
|
return runStart({
|
|
4540
4852
|
root,
|
|
4541
4853
|
...opts.devcontainerSpawn ? { spawn: opts.devcontainerSpawn } : {},
|
|
4854
|
+
...opts.logSink ? { logSink: opts.logSink } : {},
|
|
4855
|
+
...opts.progressSink ? { progressSink: opts.progressSink } : {},
|
|
4856
|
+
...opts.silent ? { silent: true } : {},
|
|
4542
4857
|
logger
|
|
4543
4858
|
});
|
|
4544
4859
|
}
|
|
@@ -4552,7 +4867,8 @@ and retry \`monoceros apply\`.`
|
|
|
4552
4867
|
"--mount-workspace-git-root=false",
|
|
4553
4868
|
"--remove-existing-container"
|
|
4554
4869
|
],
|
|
4555
|
-
root
|
|
4870
|
+
root,
|
|
4871
|
+
buildSpawnOptions(opts)
|
|
4556
4872
|
);
|
|
4557
4873
|
}
|
|
4558
4874
|
function runStop(opts) {
|
|
@@ -4579,230 +4895,11 @@ function runLogs(opts) {
|
|
|
4579
4895
|
);
|
|
4580
4896
|
}
|
|
4581
4897
|
|
|
4582
|
-
// src/devcontainer/repo-reachability.ts
|
|
4583
|
-
import { spawn as spawn6 } from "child_process";
|
|
4584
|
-
var realGitLsRemote = (url) => {
|
|
4585
|
-
return new Promise((resolve, reject) => {
|
|
4586
|
-
const child = spawn6("git", ["ls-remote", "--heads", "--", url], {
|
|
4587
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
4588
|
-
env: {
|
|
4589
|
-
...process.env,
|
|
4590
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
4591
|
-
}
|
|
4592
|
-
});
|
|
4593
|
-
let stdout = "";
|
|
4594
|
-
let stderr = "";
|
|
4595
|
-
child.stdout.on("data", (chunk) => {
|
|
4596
|
-
stdout += chunk.toString();
|
|
4597
|
-
});
|
|
4598
|
-
child.stderr.on("data", (chunk) => {
|
|
4599
|
-
stderr += chunk.toString();
|
|
4600
|
-
});
|
|
4601
|
-
child.on("error", reject);
|
|
4602
|
-
child.on(
|
|
4603
|
-
"exit",
|
|
4604
|
-
(code) => resolve({ stdout, stderr, exitCode: code ?? 0 })
|
|
4605
|
-
);
|
|
4606
|
-
});
|
|
4607
|
-
};
|
|
4608
|
-
function classifyStderr(stderr) {
|
|
4609
|
-
const s = stderr.toLowerCase();
|
|
4610
|
-
if (s.includes("could not resolve host") || s.includes("name or service not known") || s.includes("temporary failure in name resolution") || s.includes("no address associated with hostname")) {
|
|
4611
|
-
return "dns";
|
|
4612
|
-
}
|
|
4613
|
-
if (s.includes("repository not found") || s.includes("may not have access") || s.includes("no longer exists") || s.includes("don't have permission") || s.includes("could not be found") || s.includes("the requested url returned error: 404")) {
|
|
4614
|
-
return "not-found-or-no-access";
|
|
4615
|
-
}
|
|
4616
|
-
if (s.includes("authentication failed") || s.includes("could not read username") || s.includes("incorrect username or password") || s.includes("the requested url returned error: 401") || s.includes("the requested url returned error: 403")) {
|
|
4617
|
-
return "auth-failed";
|
|
4618
|
-
}
|
|
4619
|
-
return "unknown";
|
|
4620
|
-
}
|
|
4621
|
-
async function checkRepoReachability(repos, options = {}) {
|
|
4622
|
-
const spawnFn = options.spawn ?? realGitLsRemote;
|
|
4623
|
-
const results = [];
|
|
4624
|
-
for (const repo of repos) {
|
|
4625
|
-
let result;
|
|
4626
|
-
try {
|
|
4627
|
-
result = await spawnFn(repo.url);
|
|
4628
|
-
} catch (err) {
|
|
4629
|
-
results.push({
|
|
4630
|
-
url: repo.url,
|
|
4631
|
-
ok: false,
|
|
4632
|
-
kind: "unknown",
|
|
4633
|
-
detail: err instanceof Error ? err.message : String(err)
|
|
4634
|
-
});
|
|
4635
|
-
continue;
|
|
4636
|
-
}
|
|
4637
|
-
if (result.exitCode === 0) {
|
|
4638
|
-
results.push({ url: repo.url, ok: true, detail: "" });
|
|
4639
|
-
continue;
|
|
4640
|
-
}
|
|
4641
|
-
results.push({
|
|
4642
|
-
url: repo.url,
|
|
4643
|
-
ok: false,
|
|
4644
|
-
kind: classifyStderr(result.stderr),
|
|
4645
|
-
detail: result.stderr.trim()
|
|
4646
|
-
});
|
|
4647
|
-
}
|
|
4648
|
-
return results;
|
|
4649
|
-
}
|
|
4650
|
-
function formatUnreachableReposError(failures) {
|
|
4651
|
-
const byKind = /* @__PURE__ */ new Map();
|
|
4652
|
-
for (const f of failures) {
|
|
4653
|
-
const kind = f.kind ?? "unknown";
|
|
4654
|
-
const list = byKind.get(kind) ?? [];
|
|
4655
|
-
list.push(f);
|
|
4656
|
-
byKind.set(kind, list);
|
|
4657
|
-
}
|
|
4658
|
-
const totalUrls = failures.length;
|
|
4659
|
-
const lines = [
|
|
4660
|
-
totalUrls === 1 ? `Cannot reach declared repo: ${failures[0].url}` : `Cannot reach ${totalUrls} declared repos:`,
|
|
4661
|
-
""
|
|
4662
|
-
];
|
|
4663
|
-
const sectionOrder = [
|
|
4664
|
-
"not-found-or-no-access",
|
|
4665
|
-
"auth-failed",
|
|
4666
|
-
"dns",
|
|
4667
|
-
"unknown"
|
|
4668
|
-
];
|
|
4669
|
-
for (const kind of sectionOrder) {
|
|
4670
|
-
const entries = byKind.get(kind);
|
|
4671
|
-
if (!entries || entries.length === 0) continue;
|
|
4672
|
-
lines.push(headerForKind(kind));
|
|
4673
|
-
for (const e of entries) {
|
|
4674
|
-
lines.push(` \u2022 ${e.url}`);
|
|
4675
|
-
if (e.detail) {
|
|
4676
|
-
for (const detailLine of e.detail.split("\n")) {
|
|
4677
|
-
const trimmed = detailLine.trim();
|
|
4678
|
-
if (trimmed) lines.push(` git: ${trimmed}`);
|
|
4679
|
-
}
|
|
4680
|
-
}
|
|
4681
|
-
}
|
|
4682
|
-
for (const advice of adviceForKind(kind)) {
|
|
4683
|
-
lines.push(` - ${advice}`);
|
|
4684
|
-
}
|
|
4685
|
-
lines.push("");
|
|
4686
|
-
}
|
|
4687
|
-
lines.push(`Then re-run ${cyan2("monoceros apply")}.`);
|
|
4688
|
-
return lines.join("\n");
|
|
4689
|
-
}
|
|
4690
|
-
function headerForKind(kind) {
|
|
4691
|
-
switch (kind) {
|
|
4692
|
-
case "not-found-or-no-access":
|
|
4693
|
-
return "Repository not found (or your credentials don't grant access):";
|
|
4694
|
-
case "auth-failed":
|
|
4695
|
-
return "Authentication failed (credentials are present but rejected):";
|
|
4696
|
-
case "dns":
|
|
4697
|
-
return "Host unreachable (DNS / VPN / offline \u2014 git couldn't resolve the hostname):";
|
|
4698
|
-
case "unknown":
|
|
4699
|
-
return "Unrecognised git error:";
|
|
4700
|
-
}
|
|
4701
|
-
}
|
|
4702
|
-
function adviceForKind(kind) {
|
|
4703
|
-
switch (kind) {
|
|
4704
|
-
case "not-found-or-no-access":
|
|
4705
|
-
return [
|
|
4706
|
-
"Re-check the URL for typos (case-sensitive on most hosts).",
|
|
4707
|
-
"Verify the repo still exists / is not archived in a way that hides it.",
|
|
4708
|
-
"Ensure your token covers this org / workspace and has read scope (GitHub: `repo`; GitLab: `read_repository`; Bitbucket: repo read)."
|
|
4709
|
-
];
|
|
4710
|
-
case "auth-failed":
|
|
4711
|
-
return [
|
|
4712
|
-
"Token may be expired or revoked \u2014 regenerate it from the provider UI.",
|
|
4713
|
-
"Re-run the provider CLI login (gh auth login / glab auth login) \u2014 Monoceros picks up the refreshed token on the next apply."
|
|
4714
|
-
];
|
|
4715
|
-
case "dns":
|
|
4716
|
-
return [
|
|
4717
|
-
"Check your internet / VPN \u2014 corporate Git hosts often require VPN.",
|
|
4718
|
-
"Verify the hostname spelling in the yml."
|
|
4719
|
-
];
|
|
4720
|
-
case "unknown":
|
|
4721
|
-
return [
|
|
4722
|
-
"Run `git ls-remote <url>` manually on the host to see the raw error."
|
|
4723
|
-
];
|
|
4724
|
-
}
|
|
4725
|
-
}
|
|
4726
|
-
|
|
4727
|
-
// src/devcontainer/repo-clone.ts
|
|
4728
|
-
import { spawn as spawn7 } from "child_process";
|
|
4729
|
-
import { existsSync as existsSync7, promises as fs10 } from "fs";
|
|
4730
|
-
import path13 from "path";
|
|
4731
|
-
var realGitClone = (url, dest) => {
|
|
4732
|
-
return new Promise((resolve, reject) => {
|
|
4733
|
-
const child = spawn7("git", ["clone", "--", url, dest], {
|
|
4734
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
4735
|
-
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
|
|
4736
|
-
});
|
|
4737
|
-
let stdout = "";
|
|
4738
|
-
let stderr = "";
|
|
4739
|
-
child.stdout.on("data", (c) => {
|
|
4740
|
-
stdout += c.toString();
|
|
4741
|
-
});
|
|
4742
|
-
child.stderr.on("data", (c) => {
|
|
4743
|
-
stderr += c.toString();
|
|
4744
|
-
});
|
|
4745
|
-
child.on("error", reject);
|
|
4746
|
-
child.on(
|
|
4747
|
-
"exit",
|
|
4748
|
-
(code) => resolve({ stdout, stderr, exitCode: code ?? 0 })
|
|
4749
|
-
);
|
|
4750
|
-
});
|
|
4751
|
-
};
|
|
4752
|
-
async function cloneReposHostSide(containerRoot, repos, options = {}) {
|
|
4753
|
-
const spawnFn = options.spawn ?? realGitClone;
|
|
4754
|
-
const results = [];
|
|
4755
|
-
for (const repo of repos) {
|
|
4756
|
-
const dest = path13.join(containerRoot, "projects", repo.path);
|
|
4757
|
-
if (existsSync7(dest)) {
|
|
4758
|
-
results.push({ path: repo.path, url: repo.url, status: "skipped" });
|
|
4759
|
-
continue;
|
|
4760
|
-
}
|
|
4761
|
-
await fs10.mkdir(path13.dirname(dest), { recursive: true });
|
|
4762
|
-
let r;
|
|
4763
|
-
try {
|
|
4764
|
-
r = await spawnFn(repo.url, dest);
|
|
4765
|
-
} catch (err) {
|
|
4766
|
-
results.push({
|
|
4767
|
-
path: repo.path,
|
|
4768
|
-
url: repo.url,
|
|
4769
|
-
status: "failed",
|
|
4770
|
-
detail: err instanceof Error ? err.message : String(err)
|
|
4771
|
-
});
|
|
4772
|
-
continue;
|
|
4773
|
-
}
|
|
4774
|
-
results.push(
|
|
4775
|
-
r.exitCode === 0 ? { path: repo.path, url: repo.url, status: "cloned" } : {
|
|
4776
|
-
path: repo.path,
|
|
4777
|
-
url: repo.url,
|
|
4778
|
-
status: "failed",
|
|
4779
|
-
detail: r.stderr.trim()
|
|
4780
|
-
}
|
|
4781
|
-
);
|
|
4782
|
-
}
|
|
4783
|
-
return results;
|
|
4784
|
-
}
|
|
4785
|
-
function formatCloneFailuresError(failures) {
|
|
4786
|
-
const lines = failures.length === 1 ? [`Failed to clone declared repo: ${failures[0].url}`, ""] : [`Failed to clone ${failures.length} declared repos:`, ""];
|
|
4787
|
-
for (const f of failures) {
|
|
4788
|
-
lines.push(` \u2022 ${f.url} \u2192 projects/${f.path}`);
|
|
4789
|
-
if (f.detail) lines.push(` ${f.detail}`);
|
|
4790
|
-
}
|
|
4791
|
-
lines.push("");
|
|
4792
|
-
lines.push(
|
|
4793
|
-
"Reachability was confirmed earlier, so this is usually a local issue"
|
|
4794
|
-
);
|
|
4795
|
-
lines.push(
|
|
4796
|
-
"(disk space, a leftover non-empty target dir). Fix it and re-run " + cyan2("monoceros apply") + "."
|
|
4797
|
-
);
|
|
4798
|
-
return lines.join("\n");
|
|
4799
|
-
}
|
|
4800
|
-
|
|
4801
4898
|
// src/devcontainer/docker-mode.ts
|
|
4802
|
-
import { spawn as
|
|
4899
|
+
import { spawn as spawn6 } from "child_process";
|
|
4803
4900
|
var realDockerInfo = () => {
|
|
4804
4901
|
return new Promise((resolve, reject) => {
|
|
4805
|
-
const child =
|
|
4902
|
+
const child = spawn6(
|
|
4806
4903
|
"docker",
|
|
4807
4904
|
["info", "--format", "{{json .SecurityOptions}}"],
|
|
4808
4905
|
{
|
|
@@ -4861,13 +4958,13 @@ function formatRootlessNotSupportedError() {
|
|
|
4861
4958
|
}
|
|
4862
4959
|
|
|
4863
4960
|
// src/devcontainer/identity.ts
|
|
4864
|
-
import { spawn as
|
|
4865
|
-
import { promises as
|
|
4961
|
+
import { spawn as spawn7 } from "child_process";
|
|
4962
|
+
import { promises as fs10 } from "fs";
|
|
4866
4963
|
import path14 from "path";
|
|
4867
4964
|
import { consola as consola10 } from "consola";
|
|
4868
4965
|
var realGitConfigGet = (key) => {
|
|
4869
4966
|
return new Promise((resolve, reject) => {
|
|
4870
|
-
const child =
|
|
4967
|
+
const child = spawn7("git", ["config", "--global", "--get", key], {
|
|
4871
4968
|
stdio: ["ignore", "pipe", "inherit"]
|
|
4872
4969
|
});
|
|
4873
4970
|
let stdout = "";
|
|
@@ -4991,8 +5088,8 @@ async function collectGitIdentity(devContainerRoot, options = {}) {
|
|
|
4991
5088
|
const lines = ["[user]"];
|
|
4992
5089
|
if (resolved.name !== void 0) lines.push(` name = ${resolved.name}`);
|
|
4993
5090
|
if (resolved.email !== void 0) lines.push(` email = ${resolved.email}`);
|
|
4994
|
-
await
|
|
4995
|
-
await
|
|
5091
|
+
await fs10.mkdir(gitconfigDir, { recursive: true });
|
|
5092
|
+
await fs10.writeFile(gitconfigPath, lines.join("\n") + "\n");
|
|
4996
5093
|
return {
|
|
4997
5094
|
...resolved.name !== void 0 ? { name: resolved.name } : {},
|
|
4998
5095
|
...resolved.email !== void 0 ? { email: resolved.email } : {},
|
|
@@ -5035,7 +5132,7 @@ async function readKeyFromHost(spawnFn, key, logger) {
|
|
|
5035
5132
|
}
|
|
5036
5133
|
async function readExistingGitconfig(filePath) {
|
|
5037
5134
|
try {
|
|
5038
|
-
const content = await
|
|
5135
|
+
const content = await fs10.readFile(filePath, "utf8");
|
|
5039
5136
|
const result = {};
|
|
5040
5137
|
const nameMatch = /^\s*name\s*=\s*(.+?)\s*$/m.exec(content);
|
|
5041
5138
|
const emailMatch = /^\s*email\s*=\s*(.+?)\s*$/m.exec(content);
|
|
@@ -5068,7 +5165,7 @@ ${sectionLine(label)}
|
|
|
5068
5165
|
);
|
|
5069
5166
|
}
|
|
5070
5167
|
const ymlPath = containerConfigPath(opts.name, home);
|
|
5071
|
-
if (!
|
|
5168
|
+
if (!existsSync7(ymlPath)) {
|
|
5072
5169
|
throw new Error(
|
|
5073
5170
|
`No such config: ${ymlPath}. Run \`monoceros init <template> ${opts.name}\` first.`
|
|
5074
5171
|
);
|
|
@@ -5174,16 +5271,6 @@ Fix the value in the env file (or the yml).`
|
|
|
5174
5271
|
throw new Error(formatMissingCredentialsError(missing));
|
|
5175
5272
|
}
|
|
5176
5273
|
}
|
|
5177
|
-
const declaredRepos = createOpts.repos ?? [];
|
|
5178
|
-
if (declaredRepos.length > 0) {
|
|
5179
|
-
const reachability = await checkRepoReachability(declaredRepos, {
|
|
5180
|
-
...opts.reachabilitySpawn ? { spawn: opts.reachabilitySpawn } : {}
|
|
5181
|
-
});
|
|
5182
|
-
const unreachable = reachability.filter((r) => !r.ok);
|
|
5183
|
-
if (unreachable.length > 0) {
|
|
5184
|
-
throw new Error(formatUnreachableReposError(unreachable));
|
|
5185
|
-
}
|
|
5186
|
-
}
|
|
5187
5274
|
section("Scaffold");
|
|
5188
5275
|
const dockerMode = await detectDockerMode({
|
|
5189
5276
|
...opts.dockerInfoSpawn ? { spawn: opts.dockerInfoSpawn } : {}
|
|
@@ -5191,7 +5278,7 @@ Fix the value in the env file (or the yml).`
|
|
|
5191
5278
|
if (dockerMode === "rootless") {
|
|
5192
5279
|
throw new Error(formatRootlessNotSupportedError());
|
|
5193
5280
|
}
|
|
5194
|
-
await
|
|
5281
|
+
await fs11.mkdir(targetDir, { recursive: true });
|
|
5195
5282
|
await writeScaffold(createOpts, targetDir, { dockerMode });
|
|
5196
5283
|
await writeStateFile(
|
|
5197
5284
|
targetDir,
|
|
@@ -5202,72 +5289,118 @@ Fix the value in the env file (or the yml).`
|
|
|
5202
5289
|
})
|
|
5203
5290
|
);
|
|
5204
5291
|
logger.success(`materialized into ${prettyPath(targetDir)}`);
|
|
5205
|
-
const reposToClone = createOpts.repos ?? [];
|
|
5206
|
-
if (reposToClone.length > 0) {
|
|
5207
|
-
const cloneResults = await cloneReposHostSide(targetDir, reposToClone, {
|
|
5208
|
-
...opts.cloneSpawn ? { spawn: opts.cloneSpawn } : {}
|
|
5209
|
-
});
|
|
5210
|
-
for (const r of cloneResults) {
|
|
5211
|
-
if (r.status === "cloned") {
|
|
5212
|
-
logger.success(`cloned ${cyan2(r.path)} ${dim(`(${r.url})`)}`);
|
|
5213
|
-
} else if (r.status === "skipped") {
|
|
5214
|
-
logger.info(`projects/${r.path} already present \u2014 skipped clone`);
|
|
5215
|
-
}
|
|
5216
|
-
}
|
|
5217
|
-
const cloneFailures = cloneResults.filter((r) => r.status === "failed");
|
|
5218
|
-
if (cloneFailures.length > 0) {
|
|
5219
|
-
throw new Error(formatCloneFailuresError(cloneFailures));
|
|
5220
|
-
}
|
|
5221
|
-
}
|
|
5222
5292
|
section("Container");
|
|
5223
|
-
const
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
);
|
|
5232
|
-
const
|
|
5233
|
-
const
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5293
|
+
const applyLog = createApplyLog({
|
|
5294
|
+
name: opts.name,
|
|
5295
|
+
home,
|
|
5296
|
+
cliVersion: opts.cliVersion,
|
|
5297
|
+
configPath: ymlPath,
|
|
5298
|
+
...opts.now ? { now: opts.now } : {}
|
|
5299
|
+
});
|
|
5300
|
+
const progressOut = opts.progressOut ?? process.stderr;
|
|
5301
|
+
const interactive = (progressOut.isTTY ?? false) && !opts.verbose;
|
|
5302
|
+
const progress = interactive ? createApplyProgress({ out: progressOut, interactive: true }) : null;
|
|
5303
|
+
const containerLogger = progress ? progressTeeLogger(progress, applyLog.sink) : teeApplyLogger(logger, applyLog.sink);
|
|
5304
|
+
const internalLogger = progress ? logFileOnlyLogger(applyLog.sink) : containerLogger;
|
|
5305
|
+
const onSigint = createSigintAbort({
|
|
5306
|
+
progress,
|
|
5307
|
+
out: progressOut,
|
|
5308
|
+
log: applyLog,
|
|
5309
|
+
formatLogPointer: (p) => dim(`log: ${prettyPath(p)}`),
|
|
5310
|
+
onExit: () => process.exit(130)
|
|
5311
|
+
});
|
|
5312
|
+
process.on("SIGINT", onSigint);
|
|
5313
|
+
let exitCode;
|
|
5239
5314
|
try {
|
|
5315
|
+
const pullWarning = 'Pulling runtime image and building feature layers. First apply takes ~1\u20132 min (Docker downloads the multi-arch base); subsequent applies are cached and fast. devcontainer-cli may log a "No manifest found" line \u2014 harmless, the pull continues.';
|
|
5316
|
+
if (progress) {
|
|
5317
|
+
applyLog.stream.write(`# note: ${pullWarning}
|
|
5318
|
+
|
|
5319
|
+
`);
|
|
5320
|
+
} else {
|
|
5321
|
+
containerLogger.info(dim(pullWarning));
|
|
5322
|
+
}
|
|
5323
|
+
const ports = createOpts.ports ?? [];
|
|
5324
|
+
const hasPorts = ports.length > 0;
|
|
5240
5325
|
if (hasPorts) {
|
|
5241
|
-
await
|
|
5242
|
-
|
|
5243
|
-
...opts.proxyDocker ? { docker: opts.proxyDocker } : {},
|
|
5244
|
-
monocerosHome: home,
|
|
5245
|
-
hostPort: proxyHostPort(globalConfig),
|
|
5246
|
-
logger
|
|
5326
|
+
await preflightHostPort(proxyHostPort(globalConfig), {
|
|
5327
|
+
...opts.proxyDocker ? { docker: opts.proxyDocker } : {}
|
|
5247
5328
|
});
|
|
5248
|
-
} else {
|
|
5249
|
-
await removeDynamicConfig(opts.name, { monocerosHome: home });
|
|
5250
5329
|
}
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5330
|
+
try {
|
|
5331
|
+
if (hasPorts) {
|
|
5332
|
+
await writeDynamicConfig(opts.name, ports, { monocerosHome: home });
|
|
5333
|
+
await ensureProxy({
|
|
5334
|
+
...opts.proxyDocker ? { docker: opts.proxyDocker } : {},
|
|
5335
|
+
monocerosHome: home,
|
|
5336
|
+
hostPort: proxyHostPort(globalConfig),
|
|
5337
|
+
logger: containerLogger
|
|
5338
|
+
});
|
|
5339
|
+
} else {
|
|
5340
|
+
await removeDynamicConfig(opts.name, { monocerosHome: home });
|
|
5341
|
+
}
|
|
5342
|
+
} catch (err) {
|
|
5343
|
+
containerLogger.warn?.(
|
|
5344
|
+
`Could not sync Traefik routes: ${err instanceof Error ? err.message : String(err)}. The container will start, but \`<name>.localhost\` routing may not work until the next \`monoceros apply\`.`
|
|
5345
|
+
);
|
|
5346
|
+
}
|
|
5347
|
+
if (progress) {
|
|
5348
|
+
progress.setPhase(
|
|
5349
|
+
needsCompose(createOpts) ? "cleaning up previous containers\u2026" : "starting container\u2026"
|
|
5350
|
+
);
|
|
5351
|
+
}
|
|
5352
|
+
exitCode = await runContainerCycle(targetDir, {
|
|
5353
|
+
hasCompose: needsCompose(createOpts),
|
|
5354
|
+
...opts.dockerExec !== void 0 ? { dockerExec: opts.dockerExec } : {},
|
|
5355
|
+
...opts.devcontainerSpawn !== void 0 ? { devcontainerSpawn: opts.devcontainerSpawn } : {},
|
|
5356
|
+
logSink: applyLog.sink,
|
|
5357
|
+
...progress ? { progressSink: progress.streamSink, silent: true } : {},
|
|
5358
|
+
logger: internalLogger
|
|
5359
|
+
});
|
|
5360
|
+
if (progress) {
|
|
5361
|
+
if (exitCode === 0) {
|
|
5362
|
+
progress.succeed();
|
|
5363
|
+
} else {
|
|
5364
|
+
const { tailLines } = progress.fail();
|
|
5365
|
+
progressOut.write(`
|
|
5366
|
+
\u2718 apply failed (exit ${exitCode})
|
|
5367
|
+
|
|
5368
|
+
`);
|
|
5369
|
+
for (const line of tailLines) {
|
|
5370
|
+
progressOut.write(` ${line}
|
|
5371
|
+
`);
|
|
5372
|
+
}
|
|
5373
|
+
if (tailLines.length > 0) progressOut.write("\n");
|
|
5374
|
+
}
|
|
5375
|
+
}
|
|
5376
|
+
if (exitCode === 0) {
|
|
5377
|
+
const summaryLines = buildApplySummary(createOpts);
|
|
5378
|
+
if (summaryLines.length > 0) {
|
|
5379
|
+
const formatted = formatApplySummary(summaryLines);
|
|
5380
|
+
progressOut.write(`
|
|
5381
|
+
${formatted}
|
|
5382
|
+
`);
|
|
5383
|
+
applyLog.stream.write(`
|
|
5384
|
+
${stripAnsi(formatted)}
|
|
5385
|
+
`);
|
|
5386
|
+
}
|
|
5387
|
+
}
|
|
5388
|
+
await applyLog.close();
|
|
5389
|
+
progressOut.write(`
|
|
5390
|
+
${dim(`log: ${prettyPath(applyLog.path)}`)}
|
|
5391
|
+
`);
|
|
5392
|
+
if (exitCode === 0) {
|
|
5393
|
+
section("Next steps");
|
|
5394
|
+
logger.info(` ${cyan2(`monoceros shell ${opts.name}`)}`);
|
|
5395
|
+
}
|
|
5396
|
+
} finally {
|
|
5397
|
+
process.off("SIGINT", onSigint);
|
|
5265
5398
|
}
|
|
5266
5399
|
return { targetDir, configPath: ymlPath, containerExitCode: exitCode };
|
|
5267
5400
|
}
|
|
5268
5401
|
async function assertSafeTargetDir(targetDir, expectedOrigin) {
|
|
5269
|
-
if (!
|
|
5270
|
-
const entries = await
|
|
5402
|
+
if (!existsSync7(targetDir)) return;
|
|
5403
|
+
const entries = await fs11.readdir(targetDir);
|
|
5271
5404
|
if (entries.length === 0) return;
|
|
5272
5405
|
const state = await readStateFile(targetDir);
|
|
5273
5406
|
if (state) {
|
|
@@ -5337,7 +5470,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
|
|
|
5337
5470
|
}
|
|
5338
5471
|
if (wantContainer) {
|
|
5339
5472
|
try {
|
|
5340
|
-
const text = await
|
|
5473
|
+
const text = await fs11.readFile(ymlPath, "utf8");
|
|
5341
5474
|
const parsed = parseConfig(text, ymlPath);
|
|
5342
5475
|
const changed = setContainerGitUserInDoc(parsed.doc, {
|
|
5343
5476
|
name: prompted.name,
|
|
@@ -5345,7 +5478,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
|
|
|
5345
5478
|
});
|
|
5346
5479
|
if (changed) {
|
|
5347
5480
|
const out = stringifyConfig(parsed.doc);
|
|
5348
|
-
await
|
|
5481
|
+
await fs11.writeFile(ymlPath, out, "utf8");
|
|
5349
5482
|
logger.info(
|
|
5350
5483
|
`Saved identity in this container \u2014 wrote git.user into ${prettyPath(ymlPath)}.`
|
|
5351
5484
|
);
|
|
@@ -5359,7 +5492,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
|
|
|
5359
5492
|
}
|
|
5360
5493
|
|
|
5361
5494
|
// src/version.ts
|
|
5362
|
-
var CLI_VERSION = true ? "1.
|
|
5495
|
+
var CLI_VERSION = true ? "1.14.0" : "dev";
|
|
5363
5496
|
|
|
5364
5497
|
// src/commands/_dispatch.ts
|
|
5365
5498
|
import { consola as consola12 } from "consola";
|
|
@@ -5385,13 +5518,19 @@ var applyCommand = defineCommand8({
|
|
|
5385
5518
|
type: "positional",
|
|
5386
5519
|
description: "Config name. Resolves to $MONOCEROS_HOME/container-configs/<name>.yml.",
|
|
5387
5520
|
required: true
|
|
5521
|
+
},
|
|
5522
|
+
verbose: {
|
|
5523
|
+
type: "boolean",
|
|
5524
|
+
description: "Stream the raw @devcontainers/cli output to stderr instead of showing a phase spinner. Auto-enabled when stderr is not a TTY.",
|
|
5525
|
+
default: false
|
|
5388
5526
|
}
|
|
5389
5527
|
},
|
|
5390
5528
|
run({ args }) {
|
|
5391
5529
|
return dispatch(async () => {
|
|
5392
5530
|
const result = await runApply({
|
|
5393
5531
|
name: args.name,
|
|
5394
|
-
cliVersion: CLI_VERSION
|
|
5532
|
+
cliVersion: CLI_VERSION,
|
|
5533
|
+
verbose: args.verbose
|
|
5395
5534
|
});
|
|
5396
5535
|
return result.containerExitCode;
|
|
5397
5536
|
});
|
|
@@ -5420,7 +5559,7 @@ function renderCompletionScript(shell) {
|
|
|
5420
5559
|
' COMPREPLY=( $(compgen -W "$candidates" -- "$cur") )',
|
|
5421
5560
|
" # Suppress the trailing space when bash narrowed the candidate",
|
|
5422
5561
|
" # set to a single token that ends with `=` \u2014 those are value-",
|
|
5423
|
-
" # flags (`--with=`, `--with-ports=`, \u2026) where the user types the",
|
|
5562
|
+
" # flags (`--with-features=`, `--with-ports=`, \u2026) where the user types the",
|
|
5424
5563
|
" # value immediately after.",
|
|
5425
5564
|
' if [[ ${#COMPREPLY[@]} -eq 1 && "${COMPREPLY[0]}" == *= ]]; then',
|
|
5426
5565
|
" compopt -o nospace",
|
|
@@ -5493,6 +5632,10 @@ var completionCommand = defineCommand9({
|
|
|
5493
5632
|
meta: {
|
|
5494
5633
|
name: "completion",
|
|
5495
5634
|
group: "tooling",
|
|
5635
|
+
// Hidden from `monoceros --help`: the install scripts wire up
|
|
5636
|
+
// completion automatically; manual setup is documented in
|
|
5637
|
+
// docs/commands/completion.md. Still runnable directly.
|
|
5638
|
+
hidden: true,
|
|
5496
5639
|
description: "Print a shell completion script for bash, zsh or PowerShell to stdout. Pipe the output into a file your shell loads at startup. The install scripts (install.sh / install.ps1) call this automatically."
|
|
5497
5640
|
},
|
|
5498
5641
|
args: {
|
|
@@ -5519,7 +5662,7 @@ var completionCommand = defineCommand9({
|
|
|
5519
5662
|
import { defineCommand as defineCommand10 } from "citty";
|
|
5520
5663
|
|
|
5521
5664
|
// src/completion/resolve.ts
|
|
5522
|
-
import { existsSync as
|
|
5665
|
+
import { existsSync as existsSync8, promises as fs12 } from "fs";
|
|
5523
5666
|
import path15 from "path";
|
|
5524
5667
|
async function resolveCompletions(line, point, opts = {}) {
|
|
5525
5668
|
const { prev, current } = parseCompletionLine(line, point);
|
|
@@ -5669,8 +5812,8 @@ function filterPrefix(values, fragment) {
|
|
|
5669
5812
|
async function listContainerNames(ctx) {
|
|
5670
5813
|
const home = ctx.opts.monocerosHome ?? monocerosHome();
|
|
5671
5814
|
const dir = path15.join(home, "container-configs");
|
|
5672
|
-
if (!
|
|
5673
|
-
const entries = await
|
|
5815
|
+
if (!existsSync8(dir)) return [];
|
|
5816
|
+
const entries = await fs12.readdir(dir);
|
|
5674
5817
|
return entries.filter((e) => e.endsWith(".yml")).map((e) => e.slice(0, -".yml".length)).sort();
|
|
5675
5818
|
}
|
|
5676
5819
|
async function listFeatureComponents() {
|
|
@@ -5888,6 +6031,8 @@ var __completeCommand = defineCommand10({
|
|
|
5888
6031
|
meta: {
|
|
5889
6032
|
name: "__complete",
|
|
5890
6033
|
group: "internal",
|
|
6034
|
+
// Internal plumbing for the shell wrappers — never shown in help.
|
|
6035
|
+
hidden: true,
|
|
5891
6036
|
description: "Internal \u2014 shell completion engine. Used by the wrappers emitted by `monoceros completion <shell>`. Output one candidate completion per line."
|
|
5892
6037
|
},
|
|
5893
6038
|
args: {
|
|
@@ -5925,7 +6070,7 @@ import { defineCommand as defineCommand11 } from "citty";
|
|
|
5925
6070
|
import { consola as consola14 } from "consola";
|
|
5926
6071
|
|
|
5927
6072
|
// src/init/index.ts
|
|
5928
|
-
import { existsSync as
|
|
6073
|
+
import { existsSync as existsSync9, promises as fs13 } from "fs";
|
|
5929
6074
|
import path16 from "path";
|
|
5930
6075
|
import { consola as consola13 } from "consola";
|
|
5931
6076
|
|
|
@@ -6292,7 +6437,7 @@ async function runInit(opts) {
|
|
|
6292
6437
|
);
|
|
6293
6438
|
}
|
|
6294
6439
|
const dest = containerConfigPath(opts.name, home);
|
|
6295
|
-
if (
|
|
6440
|
+
if (existsSync9(dest)) {
|
|
6296
6441
|
throw new Error(
|
|
6297
6442
|
`Config already exists: ${dest}. Delete it manually before re-running \`monoceros init\` \u2014 this protects any hand-edits.`
|
|
6298
6443
|
);
|
|
@@ -6363,9 +6508,9 @@ async function runInit(opts) {
|
|
|
6363
6508
|
} else {
|
|
6364
6509
|
text = generateComposedYml(opts.name, composed, lookup, repos, ports);
|
|
6365
6510
|
}
|
|
6366
|
-
await
|
|
6511
|
+
await fs13.mkdir(containerConfigsDir(home), { recursive: true });
|
|
6367
6512
|
await ensureEnvGitignored(containerConfigsDir(home));
|
|
6368
|
-
await
|
|
6513
|
+
await fs13.writeFile(dest, text, "utf8");
|
|
6369
6514
|
const envPath = containerEnvPath(opts.name, home);
|
|
6370
6515
|
const seedVars = {};
|
|
6371
6516
|
for (const f of composed.features) {
|
|
@@ -6655,7 +6800,7 @@ var listComponentsCommand = defineCommand12({
|
|
|
6655
6800
|
meta: {
|
|
6656
6801
|
name: "list-components",
|
|
6657
6802
|
group: "discovery",
|
|
6658
|
-
description: "Print the components catalog used by `monoceros init --with=\u2026`, grouped by category (Languages, Services, Features). Component names render in cyan, descriptions in default colour; when piped, the formatting drops out and lines become `name<TAB>description` for grep/awk-friendly consumption."
|
|
6803
|
+
description: "Print the components catalog used by `monoceros init --with-languages=\u2026 / --with-services=\u2026 / --with-features=\u2026`, grouped by category (Languages, Services, Features). Component names render in cyan, descriptions in default colour; when piped, the formatting drops out and lines become `name<TAB>description` for grep/awk-friendly consumption."
|
|
6659
6804
|
},
|
|
6660
6805
|
args: {},
|
|
6661
6806
|
async run() {
|
|
@@ -6924,7 +7069,7 @@ import { consola as consola20 } from "consola";
|
|
|
6924
7069
|
import { createInterface } from "readline/promises";
|
|
6925
7070
|
|
|
6926
7071
|
// src/remove/index.ts
|
|
6927
|
-
import { existsSync as
|
|
7072
|
+
import { existsSync as existsSync10, promises as fs14 } from "fs";
|
|
6928
7073
|
import path17 from "path";
|
|
6929
7074
|
import { consola as consola19 } from "consola";
|
|
6930
7075
|
async function runRemove(opts) {
|
|
@@ -6942,9 +7087,9 @@ async function runRemove(opts) {
|
|
|
6942
7087
|
const ymlPath = containerConfigPath(opts.name, home);
|
|
6943
7088
|
const envPath = containerEnvPath(opts.name, home);
|
|
6944
7089
|
const containerPath = containerDir(opts.name, home);
|
|
6945
|
-
const hasYml =
|
|
6946
|
-
const hasEnv =
|
|
6947
|
-
const hasContainer =
|
|
7090
|
+
const hasYml = existsSync10(ymlPath);
|
|
7091
|
+
const hasEnv = existsSync10(envPath);
|
|
7092
|
+
const hasContainer = existsSync10(containerPath);
|
|
6948
7093
|
if (!hasYml && !hasContainer) {
|
|
6949
7094
|
throw new Error(
|
|
6950
7095
|
`Nothing to remove for '${opts.name}': neither ${ymlPath} nor ${containerPath} exists.`
|
|
@@ -6969,29 +7114,29 @@ async function runRemove(opts) {
|
|
|
6969
7114
|
if (!opts.noBackup && (hasYml || hasContainer)) {
|
|
6970
7115
|
const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6971
7116
|
backupPath = path17.join(home, "container-backups", `${opts.name}-${ts}`);
|
|
6972
|
-
await
|
|
7117
|
+
await fs14.mkdir(backupPath, { recursive: true });
|
|
6973
7118
|
if (hasYml) {
|
|
6974
|
-
await
|
|
7119
|
+
await fs14.copyFile(ymlPath, path17.join(backupPath, `${opts.name}.yml`));
|
|
6975
7120
|
}
|
|
6976
7121
|
if (hasEnv) {
|
|
6977
|
-
await
|
|
7122
|
+
await fs14.copyFile(envPath, path17.join(backupPath, `${opts.name}.env`));
|
|
6978
7123
|
}
|
|
6979
7124
|
if (hasContainer) {
|
|
6980
|
-
await
|
|
7125
|
+
await fs14.cp(containerPath, path17.join(backupPath, "container"), {
|
|
6981
7126
|
recursive: true
|
|
6982
7127
|
});
|
|
6983
7128
|
}
|
|
6984
7129
|
logger.info(`Backup written to ${prettyPath(backupPath)}.`);
|
|
6985
7130
|
}
|
|
6986
7131
|
if (hasYml) {
|
|
6987
|
-
await
|
|
7132
|
+
await fs14.rm(ymlPath, { force: true });
|
|
6988
7133
|
}
|
|
6989
7134
|
if (hasEnv) {
|
|
6990
|
-
await
|
|
7135
|
+
await fs14.rm(envPath, { force: true });
|
|
6991
7136
|
}
|
|
6992
7137
|
if (hasContainer) {
|
|
6993
7138
|
try {
|
|
6994
|
-
await
|
|
7139
|
+
await fs14.rm(containerPath, { recursive: true, force: true });
|
|
6995
7140
|
} catch (err) {
|
|
6996
7141
|
const code = err.code;
|
|
6997
7142
|
if (code !== "EACCES" && code !== "EPERM") {
|
|
@@ -7017,7 +7162,7 @@ async function runRemove(opts) {
|
|
|
7017
7162
|
`docker-based cleanup of ${containerPath} exited ${exit}. Inspect with \`sudo ls -la ${containerPath}\` and clean manually.`
|
|
7018
7163
|
);
|
|
7019
7164
|
}
|
|
7020
|
-
await
|
|
7165
|
+
await fs14.rm(containerPath, { recursive: true, force: true });
|
|
7021
7166
|
}
|
|
7022
7167
|
}
|
|
7023
7168
|
logger.success(
|
|
@@ -7120,7 +7265,7 @@ import { defineCommand as defineCommand18 } from "citty";
|
|
|
7120
7265
|
import { consola as consola22 } from "consola";
|
|
7121
7266
|
|
|
7122
7267
|
// src/restore/index.ts
|
|
7123
|
-
import { existsSync as
|
|
7268
|
+
import { existsSync as existsSync11, promises as fs15 } from "fs";
|
|
7124
7269
|
import path18 from "path";
|
|
7125
7270
|
import { consola as consola21 } from "consola";
|
|
7126
7271
|
async function runRestore(opts) {
|
|
@@ -7130,14 +7275,14 @@ async function runRestore(opts) {
|
|
|
7130
7275
|
success: (msg) => consola21.success(msg)
|
|
7131
7276
|
};
|
|
7132
7277
|
const backup = path18.resolve(opts.backupPath);
|
|
7133
|
-
if (!
|
|
7278
|
+
if (!existsSync11(backup)) {
|
|
7134
7279
|
throw new Error(`Backup not found: ${backup}.`);
|
|
7135
7280
|
}
|
|
7136
|
-
const stat = await
|
|
7281
|
+
const stat = await fs15.stat(backup);
|
|
7137
7282
|
if (!stat.isDirectory()) {
|
|
7138
7283
|
throw new Error(`Backup path is not a directory: ${backup}.`);
|
|
7139
7284
|
}
|
|
7140
|
-
const entries = await
|
|
7285
|
+
const entries = await fs15.readdir(backup);
|
|
7141
7286
|
const ymlFiles = entries.filter((f) => f.endsWith(".yml"));
|
|
7142
7287
|
if (ymlFiles.length === 0) {
|
|
7143
7288
|
throw new Error(
|
|
@@ -7152,28 +7297,28 @@ async function runRestore(opts) {
|
|
|
7152
7297
|
const ymlFile = ymlFiles[0];
|
|
7153
7298
|
const name = ymlFile.replace(/\.yml$/, "");
|
|
7154
7299
|
const containerInBackup = path18.join(backup, "container");
|
|
7155
|
-
const hasContainer =
|
|
7300
|
+
const hasContainer = existsSync11(containerInBackup);
|
|
7156
7301
|
const envInBackup = path18.join(backup, `${name}.env`);
|
|
7157
|
-
const hasEnv =
|
|
7302
|
+
const hasEnv = existsSync11(envInBackup);
|
|
7158
7303
|
const destYml = containerConfigPath(name, home);
|
|
7159
7304
|
const destContainer = containerDir(name, home);
|
|
7160
|
-
if (
|
|
7305
|
+
if (existsSync11(destYml)) {
|
|
7161
7306
|
throw new Error(
|
|
7162
7307
|
`Refusing to restore: ${destYml} already exists. Remove the current container first (\`monoceros remove ${name}\`) or rename the existing config.`
|
|
7163
7308
|
);
|
|
7164
7309
|
}
|
|
7165
|
-
if (hasContainer &&
|
|
7310
|
+
if (hasContainer && existsSync11(destContainer)) {
|
|
7166
7311
|
throw new Error(
|
|
7167
7312
|
`Refusing to restore: ${destContainer} already exists. Remove the current container first (\`monoceros remove ${name}\`).`
|
|
7168
7313
|
);
|
|
7169
7314
|
}
|
|
7170
|
-
await
|
|
7171
|
-
await
|
|
7315
|
+
await fs15.mkdir(containerConfigsDir(home), { recursive: true });
|
|
7316
|
+
await fs15.copyFile(path18.join(backup, ymlFile), destYml);
|
|
7172
7317
|
if (hasEnv) {
|
|
7173
|
-
await
|
|
7318
|
+
await fs15.copyFile(envInBackup, containerEnvPath(name, home));
|
|
7174
7319
|
}
|
|
7175
7320
|
if (hasContainer) {
|
|
7176
|
-
await
|
|
7321
|
+
await fs15.cp(containerInBackup, destContainer, { recursive: true });
|
|
7177
7322
|
}
|
|
7178
7323
|
logger.success(`Restored '${name}' from ${prettyPath(backup)}.`);
|
|
7179
7324
|
logger.info(
|
|
@@ -7431,7 +7576,7 @@ import { defineCommand as defineCommand24 } from "citty";
|
|
|
7431
7576
|
import { consola as consola28 } from "consola";
|
|
7432
7577
|
|
|
7433
7578
|
// src/devcontainer/shell.ts
|
|
7434
|
-
import { existsSync as
|
|
7579
|
+
import { existsSync as existsSync12 } from "fs";
|
|
7435
7580
|
import path19 from "path";
|
|
7436
7581
|
async function runShell(opts) {
|
|
7437
7582
|
assertContainerExists(opts.root);
|
|
@@ -7455,7 +7600,7 @@ async function runShell(opts) {
|
|
|
7455
7600
|
);
|
|
7456
7601
|
}
|
|
7457
7602
|
function assertContainerExists(root) {
|
|
7458
|
-
if (!
|
|
7603
|
+
if (!existsSync12(path19.join(root, ".devcontainer"))) {
|
|
7459
7604
|
throw new Error(
|
|
7460
7605
|
`No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
|
|
7461
7606
|
);
|
|
@@ -7667,15 +7812,15 @@ import { defineCommand as defineCommand29 } from "citty";
|
|
|
7667
7812
|
import { consola as consola33 } from "consola";
|
|
7668
7813
|
|
|
7669
7814
|
// src/tunnel/run.ts
|
|
7670
|
-
import { spawn as
|
|
7815
|
+
import { spawn as spawn8 } from "child_process";
|
|
7671
7816
|
import { consola as consola32 } from "consola";
|
|
7672
7817
|
|
|
7673
7818
|
// src/tunnel/resolve.ts
|
|
7674
|
-
import { existsSync as
|
|
7819
|
+
import { existsSync as existsSync13 } from "fs";
|
|
7675
7820
|
import path20 from "path";
|
|
7676
7821
|
async function resolveTunnelTarget(opts) {
|
|
7677
7822
|
const ymlPath = containerConfigPath(opts.name, opts.monocerosHome);
|
|
7678
|
-
if (!
|
|
7823
|
+
if (!existsSync13(ymlPath)) {
|
|
7679
7824
|
throw new Error(
|
|
7680
7825
|
`No yml profile for '${opts.name}' at ${ymlPath}. Run \`monoceros init ${opts.name}\` first.`
|
|
7681
7826
|
);
|
|
@@ -7683,13 +7828,13 @@ async function resolveTunnelTarget(opts) {
|
|
|
7683
7828
|
const parsed = await readConfig(ymlPath);
|
|
7684
7829
|
const config = parsed.config;
|
|
7685
7830
|
const containerRoot = containerDir(opts.name, opts.monocerosHome);
|
|
7686
|
-
if (!
|
|
7831
|
+
if (!existsSync13(containerRoot)) {
|
|
7687
7832
|
throw new Error(
|
|
7688
7833
|
`Container '${opts.name}' is not materialised at ${containerRoot}. Run \`monoceros apply ${opts.name}\` first.`
|
|
7689
7834
|
);
|
|
7690
7835
|
}
|
|
7691
7836
|
const composePath = path20.join(containerRoot, ".devcontainer", "compose.yaml");
|
|
7692
|
-
const isCompose =
|
|
7837
|
+
const isCompose = existsSync13(composePath);
|
|
7693
7838
|
const parsedTarget = parseTargetArg(opts.target, config);
|
|
7694
7839
|
const docker = opts.docker ?? defaultDockerExec;
|
|
7695
7840
|
if (isCompose) {
|
|
@@ -7910,7 +8055,7 @@ function formatLocalPortHeldError(port, address, result) {
|
|
|
7910
8055
|
// src/tunnel/run.ts
|
|
7911
8056
|
var SOCAT_IMAGE = "alpine/socat:1.8.0.3";
|
|
7912
8057
|
var defaultDockerSpawn = (args) => {
|
|
7913
|
-
const child =
|
|
8058
|
+
const child = spawn8("docker", args, {
|
|
7914
8059
|
stdio: "inherit"
|
|
7915
8060
|
});
|
|
7916
8061
|
const exited = new Promise((resolve, reject) => {
|