@getmonoceros/workbench 1.13.3 → 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 +462 -80
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -254,25 +254,25 @@ function detectHelpRequest(argv, main2) {
|
|
|
254
254
|
const separatorIdx = argv.indexOf("--");
|
|
255
255
|
if (helpIdx === -1) return null;
|
|
256
256
|
if (separatorIdx !== -1 && separatorIdx < helpIdx) return null;
|
|
257
|
-
const
|
|
257
|
+
const path21 = [];
|
|
258
258
|
const tokens = argv.slice(
|
|
259
259
|
0,
|
|
260
260
|
separatorIdx === -1 ? argv.length : separatorIdx
|
|
261
261
|
);
|
|
262
262
|
let cursor = main2;
|
|
263
263
|
const mainName = (main2.meta ?? {}).name ?? "monoceros";
|
|
264
|
-
|
|
264
|
+
path21.push(mainName);
|
|
265
265
|
for (const tok of tokens) {
|
|
266
266
|
if (tok.startsWith("-")) continue;
|
|
267
267
|
const subs = cursor.subCommands ?? {};
|
|
268
268
|
if (tok in subs) {
|
|
269
269
|
cursor = subs[tok];
|
|
270
|
-
|
|
270
|
+
path21.push(tok);
|
|
271
271
|
continue;
|
|
272
272
|
}
|
|
273
273
|
break;
|
|
274
274
|
}
|
|
275
|
-
return { path:
|
|
275
|
+
return { path: path21, cmd: cursor };
|
|
276
276
|
}
|
|
277
277
|
async function maybeRenderHelp(argv, main2) {
|
|
278
278
|
const hit = detectHelpRequest(argv, main2);
|
|
@@ -622,6 +622,9 @@ function containersDir(home = monocerosHome()) {
|
|
|
622
622
|
function containerDir(name, home = monocerosHome()) {
|
|
623
623
|
return path.join(containersDir(home), name);
|
|
624
624
|
}
|
|
625
|
+
function containerLogsDir(name, home = monocerosHome()) {
|
|
626
|
+
return path.join(containerDir(name, home), "logs");
|
|
627
|
+
}
|
|
625
628
|
function monocerosConfigPath(home = monocerosHome()) {
|
|
626
629
|
return path.join(home, "monoceros-config.yml");
|
|
627
630
|
}
|
|
@@ -985,6 +988,10 @@ var ANSI_UNDERLINE2 = `${ESC}4m`;
|
|
|
985
988
|
var ANSI_CYAN2 = `${ESC}36m`;
|
|
986
989
|
var ANSI_GREY2 = `${ESC}90m`;
|
|
987
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
|
+
}
|
|
988
995
|
function makeWrap(isTty2) {
|
|
989
996
|
return (s, ...codes) => isTty2 ? codes.join("") + s + ANSI_RESET2 : s;
|
|
990
997
|
}
|
|
@@ -3170,8 +3177,8 @@ function removeRepoFromDoc(doc, urlOrPath) {
|
|
|
3170
3177
|
if (!isMap2(item)) return false;
|
|
3171
3178
|
const url = item.get("url");
|
|
3172
3179
|
if (url === urlOrPath) return true;
|
|
3173
|
-
const
|
|
3174
|
-
const effectivePath = typeof
|
|
3180
|
+
const path21 = item.get("path");
|
|
3181
|
+
const effectivePath = typeof path21 === "string" ? path21 : typeof url === "string" ? deriveRepoName(url) : void 0;
|
|
3175
3182
|
return effectivePath === urlOrPath;
|
|
3176
3183
|
});
|
|
3177
3184
|
if (idx < 0) return false;
|
|
@@ -3264,7 +3271,7 @@ async function runAddRepo(input) {
|
|
|
3264
3271
|
"Missing repo URL. Usage: monoceros add-repo <containername> <url>."
|
|
3265
3272
|
);
|
|
3266
3273
|
}
|
|
3267
|
-
const
|
|
3274
|
+
const path21 = (input.path ?? deriveRepoName(url)).trim();
|
|
3268
3275
|
const hasName = typeof input.gitName === "string" && input.gitName.trim().length > 0;
|
|
3269
3276
|
const hasEmail = typeof input.gitEmail === "string" && input.gitEmail.trim().length > 0;
|
|
3270
3277
|
if (hasName !== hasEmail) {
|
|
@@ -3301,7 +3308,7 @@ async function runAddRepo(input) {
|
|
|
3301
3308
|
const providerToWrite = !canonical && explicitProvider ? explicitProvider : void 0;
|
|
3302
3309
|
const entry2 = {
|
|
3303
3310
|
url,
|
|
3304
|
-
path:
|
|
3311
|
+
path: path21,
|
|
3305
3312
|
...hasName && hasEmail ? {
|
|
3306
3313
|
gitUser: {
|
|
3307
3314
|
name: input.gitName.trim(),
|
|
@@ -4240,10 +4247,292 @@ function solutionConfigToCreateOptions(config, featureDefaults = {}) {
|
|
|
4240
4247
|
return result;
|
|
4241
4248
|
}
|
|
4242
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
|
+
|
|
4243
4532
|
// src/devcontainer/compose.ts
|
|
4244
4533
|
import { spawn as spawn5 } from "child_process";
|
|
4245
4534
|
import { existsSync as existsSync6 } from "fs";
|
|
4246
|
-
import
|
|
4535
|
+
import path13 from "path";
|
|
4247
4536
|
import { consola as consola9 } from "consola";
|
|
4248
4537
|
|
|
4249
4538
|
// src/util/mask-secrets.ts
|
|
@@ -4306,7 +4595,7 @@ function createSecretMaskStream() {
|
|
|
4306
4595
|
import { spawn as spawn4 } from "child_process";
|
|
4307
4596
|
import { readFileSync as readFileSync4 } from "fs";
|
|
4308
4597
|
import { createRequire } from "module";
|
|
4309
|
-
import
|
|
4598
|
+
import path12 from "path";
|
|
4310
4599
|
|
|
4311
4600
|
// src/devcontainer/runtime-pull-hint.ts
|
|
4312
4601
|
import { Transform as Transform2 } from "stream";
|
|
@@ -4357,7 +4646,7 @@ function devcontainerCliPath() {
|
|
|
4357
4646
|
if (!binEntry) {
|
|
4358
4647
|
throw new Error("Could not resolve @devcontainers/cli bin entry.");
|
|
4359
4648
|
}
|
|
4360
|
-
cachedBinaryPath =
|
|
4649
|
+
cachedBinaryPath = path12.resolve(path12.dirname(pkgJsonPath), binEntry);
|
|
4361
4650
|
return cachedBinaryPath;
|
|
4362
4651
|
}
|
|
4363
4652
|
var spawnDevcontainer = (args, cwd, options = {}) => {
|
|
@@ -4397,8 +4686,20 @@ var spawnDevcontainer = (args, cwd, options = {}) => {
|
|
|
4397
4686
|
return;
|
|
4398
4687
|
}
|
|
4399
4688
|
const pullHint = { hinted: false };
|
|
4400
|
-
child.stdout?.pipe(createSecretMaskStream()).pipe(createRuntimePullHintStream(pullHint))
|
|
4401
|
-
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
|
+
}
|
|
4402
4703
|
child.on("error", reject);
|
|
4403
4704
|
child.on("exit", (code) => resolve(code ?? 0));
|
|
4404
4705
|
});
|
|
@@ -4475,15 +4776,15 @@ async function cleanupDockerObjects(opts) {
|
|
|
4475
4776
|
return { exitCode: rmExit, removedIds: ids };
|
|
4476
4777
|
}
|
|
4477
4778
|
function composeProjectName(root) {
|
|
4478
|
-
return `${
|
|
4779
|
+
return `${path13.basename(root)}_devcontainer`;
|
|
4479
4780
|
}
|
|
4480
4781
|
function resolveCompose(root) {
|
|
4481
|
-
if (!existsSync6(
|
|
4782
|
+
if (!existsSync6(path13.join(root, ".devcontainer"))) {
|
|
4482
4783
|
throw new Error(
|
|
4483
4784
|
`No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
|
|
4484
4785
|
);
|
|
4485
4786
|
}
|
|
4486
|
-
const composeFile =
|
|
4787
|
+
const composeFile = path13.join(root, ".devcontainer", "compose.yaml");
|
|
4487
4788
|
if (!existsSync6(composeFile)) {
|
|
4488
4789
|
throw new Error(
|
|
4489
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.`
|
|
@@ -4504,9 +4805,17 @@ async function runStart(opts) {
|
|
|
4504
4805
|
logger.info(`Bringing devcontainer up at ${opts.root}\u2026`);
|
|
4505
4806
|
return spawnFn(
|
|
4506
4807
|
["up", "--workspace-folder", opts.root, "--mount-workspace-git-root=false"],
|
|
4507
|
-
opts.root
|
|
4808
|
+
opts.root,
|
|
4809
|
+
buildSpawnOptions(opts)
|
|
4508
4810
|
);
|
|
4509
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
|
+
}
|
|
4510
4819
|
async function runContainerCycle(root, opts) {
|
|
4511
4820
|
const { hasCompose, logger } = opts;
|
|
4512
4821
|
if (hasCompose) {
|
|
@@ -4542,6 +4851,9 @@ and retry \`monoceros apply\`.`
|
|
|
4542
4851
|
return runStart({
|
|
4543
4852
|
root,
|
|
4544
4853
|
...opts.devcontainerSpawn ? { spawn: opts.devcontainerSpawn } : {},
|
|
4854
|
+
...opts.logSink ? { logSink: opts.logSink } : {},
|
|
4855
|
+
...opts.progressSink ? { progressSink: opts.progressSink } : {},
|
|
4856
|
+
...opts.silent ? { silent: true } : {},
|
|
4545
4857
|
logger
|
|
4546
4858
|
});
|
|
4547
4859
|
}
|
|
@@ -4555,7 +4867,8 @@ and retry \`monoceros apply\`.`
|
|
|
4555
4867
|
"--mount-workspace-git-root=false",
|
|
4556
4868
|
"--remove-existing-container"
|
|
4557
4869
|
],
|
|
4558
|
-
root
|
|
4870
|
+
root,
|
|
4871
|
+
buildSpawnOptions(opts)
|
|
4559
4872
|
);
|
|
4560
4873
|
}
|
|
4561
4874
|
function runStop(opts) {
|
|
@@ -4647,7 +4960,7 @@ function formatRootlessNotSupportedError() {
|
|
|
4647
4960
|
// src/devcontainer/identity.ts
|
|
4648
4961
|
import { spawn as spawn7 } from "child_process";
|
|
4649
4962
|
import { promises as fs10 } from "fs";
|
|
4650
|
-
import
|
|
4963
|
+
import path14 from "path";
|
|
4651
4964
|
import { consola as consola10 } from "consola";
|
|
4652
4965
|
var realGitConfigGet = (key) => {
|
|
4653
4966
|
return new Promise((resolve, reject) => {
|
|
@@ -4761,8 +5074,8 @@ async function resolveIdentityWithPrompt(options = {}) {
|
|
|
4761
5074
|
};
|
|
4762
5075
|
}
|
|
4763
5076
|
async function collectGitIdentity(devContainerRoot, options = {}) {
|
|
4764
|
-
const gitconfigDir =
|
|
4765
|
-
const gitconfigPath =
|
|
5077
|
+
const gitconfigDir = path14.join(devContainerRoot, ".monoceros");
|
|
5078
|
+
const gitconfigPath = path14.join(gitconfigDir, "gitconfig");
|
|
4766
5079
|
const logger = options.logger ?? { info: () => {
|
|
4767
5080
|
}, warn: () => {
|
|
4768
5081
|
} };
|
|
@@ -4977,48 +5290,111 @@ Fix the value in the env file (or the yml).`
|
|
|
4977
5290
|
);
|
|
4978
5291
|
logger.success(`materialized into ${prettyPath(targetDir)}`);
|
|
4979
5292
|
section("Container");
|
|
4980
|
-
const
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
);
|
|
4989
|
-
const
|
|
4990
|
-
const
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
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;
|
|
4996
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;
|
|
4997
5325
|
if (hasPorts) {
|
|
4998
|
-
await
|
|
4999
|
-
|
|
5000
|
-
...opts.proxyDocker ? { docker: opts.proxyDocker } : {},
|
|
5001
|
-
monocerosHome: home,
|
|
5002
|
-
hostPort: proxyHostPort(globalConfig),
|
|
5003
|
-
logger
|
|
5326
|
+
await preflightHostPort(proxyHostPort(globalConfig), {
|
|
5327
|
+
...opts.proxyDocker ? { docker: opts.proxyDocker } : {}
|
|
5004
5328
|
});
|
|
5005
|
-
} else {
|
|
5006
|
-
await removeDynamicConfig(opts.name, { monocerosHome: home });
|
|
5007
5329
|
}
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
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);
|
|
5022
5398
|
}
|
|
5023
5399
|
return { targetDir, configPath: ymlPath, containerExitCode: exitCode };
|
|
5024
5400
|
}
|
|
@@ -5116,7 +5492,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
|
|
|
5116
5492
|
}
|
|
5117
5493
|
|
|
5118
5494
|
// src/version.ts
|
|
5119
|
-
var CLI_VERSION = true ? "1.
|
|
5495
|
+
var CLI_VERSION = true ? "1.14.0" : "dev";
|
|
5120
5496
|
|
|
5121
5497
|
// src/commands/_dispatch.ts
|
|
5122
5498
|
import { consola as consola12 } from "consola";
|
|
@@ -5142,13 +5518,19 @@ var applyCommand = defineCommand8({
|
|
|
5142
5518
|
type: "positional",
|
|
5143
5519
|
description: "Config name. Resolves to $MONOCEROS_HOME/container-configs/<name>.yml.",
|
|
5144
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
|
|
5145
5526
|
}
|
|
5146
5527
|
},
|
|
5147
5528
|
run({ args }) {
|
|
5148
5529
|
return dispatch(async () => {
|
|
5149
5530
|
const result = await runApply({
|
|
5150
5531
|
name: args.name,
|
|
5151
|
-
cliVersion: CLI_VERSION
|
|
5532
|
+
cliVersion: CLI_VERSION,
|
|
5533
|
+
verbose: args.verbose
|
|
5152
5534
|
});
|
|
5153
5535
|
return result.containerExitCode;
|
|
5154
5536
|
});
|
|
@@ -5281,7 +5663,7 @@ import { defineCommand as defineCommand10 } from "citty";
|
|
|
5281
5663
|
|
|
5282
5664
|
// src/completion/resolve.ts
|
|
5283
5665
|
import { existsSync as existsSync8, promises as fs12 } from "fs";
|
|
5284
|
-
import
|
|
5666
|
+
import path15 from "path";
|
|
5285
5667
|
async function resolveCompletions(line, point, opts = {}) {
|
|
5286
5668
|
const { prev, current } = parseCompletionLine(line, point);
|
|
5287
5669
|
const ctx = { prev, current, opts };
|
|
@@ -5429,7 +5811,7 @@ function filterPrefix(values, fragment) {
|
|
|
5429
5811
|
}
|
|
5430
5812
|
async function listContainerNames(ctx) {
|
|
5431
5813
|
const home = ctx.opts.monocerosHome ?? monocerosHome();
|
|
5432
|
-
const dir =
|
|
5814
|
+
const dir = path15.join(home, "container-configs");
|
|
5433
5815
|
if (!existsSync8(dir)) return [];
|
|
5434
5816
|
const entries = await fs12.readdir(dir);
|
|
5435
5817
|
return entries.filter((e) => e.endsWith(".yml")).map((e) => e.slice(0, -".yml".length)).sort();
|
|
@@ -5689,7 +6071,7 @@ import { consola as consola14 } from "consola";
|
|
|
5689
6071
|
|
|
5690
6072
|
// src/init/index.ts
|
|
5691
6073
|
import { existsSync as existsSync9, promises as fs13 } from "fs";
|
|
5692
|
-
import
|
|
6074
|
+
import path16 from "path";
|
|
5693
6075
|
import { consola as consola13 } from "consola";
|
|
5694
6076
|
|
|
5695
6077
|
// src/init/generator.ts
|
|
@@ -6151,8 +6533,8 @@ async function runInit(opts) {
|
|
|
6151
6533
|
}
|
|
6152
6534
|
await ensureEnvVars(envPath, opts.name, seedVars);
|
|
6153
6535
|
const documented = !anyComposed;
|
|
6154
|
-
const ymlRel =
|
|
6155
|
-
const envRel =
|
|
6536
|
+
const ymlRel = path16.relative(home, dest);
|
|
6537
|
+
const envRel = path16.relative(home, envPath);
|
|
6156
6538
|
if (documented) {
|
|
6157
6539
|
logger.success(`Wrote documented default to ${ymlRel} and ${envRel}.`);
|
|
6158
6540
|
logger.info(
|
|
@@ -6688,7 +7070,7 @@ import { createInterface } from "readline/promises";
|
|
|
6688
7070
|
|
|
6689
7071
|
// src/remove/index.ts
|
|
6690
7072
|
import { existsSync as existsSync10, promises as fs14 } from "fs";
|
|
6691
|
-
import
|
|
7073
|
+
import path17 from "path";
|
|
6692
7074
|
import { consola as consola19 } from "consola";
|
|
6693
7075
|
async function runRemove(opts) {
|
|
6694
7076
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
@@ -6731,16 +7113,16 @@ async function runRemove(opts) {
|
|
|
6731
7113
|
let backupPath = null;
|
|
6732
7114
|
if (!opts.noBackup && (hasYml || hasContainer)) {
|
|
6733
7115
|
const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6734
|
-
backupPath =
|
|
7116
|
+
backupPath = path17.join(home, "container-backups", `${opts.name}-${ts}`);
|
|
6735
7117
|
await fs14.mkdir(backupPath, { recursive: true });
|
|
6736
7118
|
if (hasYml) {
|
|
6737
|
-
await fs14.copyFile(ymlPath,
|
|
7119
|
+
await fs14.copyFile(ymlPath, path17.join(backupPath, `${opts.name}.yml`));
|
|
6738
7120
|
}
|
|
6739
7121
|
if (hasEnv) {
|
|
6740
|
-
await fs14.copyFile(envPath,
|
|
7122
|
+
await fs14.copyFile(envPath, path17.join(backupPath, `${opts.name}.env`));
|
|
6741
7123
|
}
|
|
6742
7124
|
if (hasContainer) {
|
|
6743
|
-
await fs14.cp(containerPath,
|
|
7125
|
+
await fs14.cp(containerPath, path17.join(backupPath, "container"), {
|
|
6744
7126
|
recursive: true
|
|
6745
7127
|
});
|
|
6746
7128
|
}
|
|
@@ -6884,7 +7266,7 @@ import { consola as consola22 } from "consola";
|
|
|
6884
7266
|
|
|
6885
7267
|
// src/restore/index.ts
|
|
6886
7268
|
import { existsSync as existsSync11, promises as fs15 } from "fs";
|
|
6887
|
-
import
|
|
7269
|
+
import path18 from "path";
|
|
6888
7270
|
import { consola as consola21 } from "consola";
|
|
6889
7271
|
async function runRestore(opts) {
|
|
6890
7272
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
@@ -6892,7 +7274,7 @@ async function runRestore(opts) {
|
|
|
6892
7274
|
info: (msg) => consola21.info(msg),
|
|
6893
7275
|
success: (msg) => consola21.success(msg)
|
|
6894
7276
|
};
|
|
6895
|
-
const backup =
|
|
7277
|
+
const backup = path18.resolve(opts.backupPath);
|
|
6896
7278
|
if (!existsSync11(backup)) {
|
|
6897
7279
|
throw new Error(`Backup not found: ${backup}.`);
|
|
6898
7280
|
}
|
|
@@ -6914,9 +7296,9 @@ async function runRestore(opts) {
|
|
|
6914
7296
|
}
|
|
6915
7297
|
const ymlFile = ymlFiles[0];
|
|
6916
7298
|
const name = ymlFile.replace(/\.yml$/, "");
|
|
6917
|
-
const containerInBackup =
|
|
7299
|
+
const containerInBackup = path18.join(backup, "container");
|
|
6918
7300
|
const hasContainer = existsSync11(containerInBackup);
|
|
6919
|
-
const envInBackup =
|
|
7301
|
+
const envInBackup = path18.join(backup, `${name}.env`);
|
|
6920
7302
|
const hasEnv = existsSync11(envInBackup);
|
|
6921
7303
|
const destYml = containerConfigPath(name, home);
|
|
6922
7304
|
const destContainer = containerDir(name, home);
|
|
@@ -6931,7 +7313,7 @@ async function runRestore(opts) {
|
|
|
6931
7313
|
);
|
|
6932
7314
|
}
|
|
6933
7315
|
await fs15.mkdir(containerConfigsDir(home), { recursive: true });
|
|
6934
|
-
await fs15.copyFile(
|
|
7316
|
+
await fs15.copyFile(path18.join(backup, ymlFile), destYml);
|
|
6935
7317
|
if (hasEnv) {
|
|
6936
7318
|
await fs15.copyFile(envInBackup, containerEnvPath(name, home));
|
|
6937
7319
|
}
|
|
@@ -7195,7 +7577,7 @@ import { consola as consola28 } from "consola";
|
|
|
7195
7577
|
|
|
7196
7578
|
// src/devcontainer/shell.ts
|
|
7197
7579
|
import { existsSync as existsSync12 } from "fs";
|
|
7198
|
-
import
|
|
7580
|
+
import path19 from "path";
|
|
7199
7581
|
async function runShell(opts) {
|
|
7200
7582
|
assertContainerExists(opts.root);
|
|
7201
7583
|
const spawnFn = opts.spawn ?? spawnDevcontainer;
|
|
@@ -7218,7 +7600,7 @@ async function runShell(opts) {
|
|
|
7218
7600
|
);
|
|
7219
7601
|
}
|
|
7220
7602
|
function assertContainerExists(root) {
|
|
7221
|
-
if (!existsSync12(
|
|
7603
|
+
if (!existsSync12(path19.join(root, ".devcontainer"))) {
|
|
7222
7604
|
throw new Error(
|
|
7223
7605
|
`No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
|
|
7224
7606
|
);
|
|
@@ -7435,7 +7817,7 @@ import { consola as consola32 } from "consola";
|
|
|
7435
7817
|
|
|
7436
7818
|
// src/tunnel/resolve.ts
|
|
7437
7819
|
import { existsSync as existsSync13 } from "fs";
|
|
7438
|
-
import
|
|
7820
|
+
import path20 from "path";
|
|
7439
7821
|
async function resolveTunnelTarget(opts) {
|
|
7440
7822
|
const ymlPath = containerConfigPath(opts.name, opts.monocerosHome);
|
|
7441
7823
|
if (!existsSync13(ymlPath)) {
|
|
@@ -7451,7 +7833,7 @@ async function resolveTunnelTarget(opts) {
|
|
|
7451
7833
|
`Container '${opts.name}' is not materialised at ${containerRoot}. Run \`monoceros apply ${opts.name}\` first.`
|
|
7452
7834
|
);
|
|
7453
7835
|
}
|
|
7454
|
-
const composePath =
|
|
7836
|
+
const composePath = path20.join(containerRoot, ".devcontainer", "compose.yaml");
|
|
7455
7837
|
const isCompose = existsSync13(composePath);
|
|
7456
7838
|
const parsedTarget = parseTargetArg(opts.target, config);
|
|
7457
7839
|
const docker = opts.docker ?? defaultDockerExec;
|