@getmonoceros/workbench 1.26.5 → 1.27.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 +941 -501
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -2146,9 +2146,9 @@ function compareRuntimeVersions(a, b) {
|
|
|
2146
2146
|
}
|
|
2147
2147
|
return 0;
|
|
2148
2148
|
}
|
|
2149
|
-
function
|
|
2149
|
+
function runtimeSupportsSshAttach(version) {
|
|
2150
2150
|
if (!version) return false;
|
|
2151
|
-
return compareRuntimeVersions(version,
|
|
2151
|
+
return compareRuntimeVersions(version, MIN_RUNTIME_FOR_SSH_ATTACH) >= 0;
|
|
2152
2152
|
}
|
|
2153
2153
|
function descriptorOptionDefaults(options) {
|
|
2154
2154
|
const out = {};
|
|
@@ -2269,7 +2269,7 @@ function deriveServiceName(image) {
|
|
|
2269
2269
|
const noTag = lastSegment.split("@")[0].split(":")[0];
|
|
2270
2270
|
return noTag.toLowerCase().replace(/[^a-z0-9_-]/g, "-");
|
|
2271
2271
|
}
|
|
2272
|
-
var DEFAULT_BASE_IMAGE, override, BASE_IMAGE, RUNTIME_IMAGE_REPO, DEFAULT_RUNTIME_VERSION,
|
|
2272
|
+
var DEFAULT_BASE_IMAGE, override, BASE_IMAGE, RUNTIME_IMAGE_REPO, DEFAULT_RUNTIME_VERSION, MIN_RUNTIME_FOR_SSH_ATTACH, DESCRIPTORS, BUILTIN_LANGUAGES, LANGUAGE_CATALOG, LANGUAGE_SPEC_RE, SERVICE_CATALOG;
|
|
2273
2273
|
var init_catalog = __esm({
|
|
2274
2274
|
"src/create/catalog.ts"() {
|
|
2275
2275
|
"use strict";
|
|
@@ -2278,13 +2278,13 @@ var init_catalog = __esm({
|
|
|
2278
2278
|
override = process.env.MONOCEROS_BASE_IMAGE_OVERRIDE?.trim();
|
|
2279
2279
|
BASE_IMAGE = override && override.length > 0 ? override : DEFAULT_BASE_IMAGE;
|
|
2280
2280
|
RUNTIME_IMAGE_REPO = "ghcr.io/getmonoceros/monoceros-runtime";
|
|
2281
|
-
DEFAULT_RUNTIME_VERSION = true ? "1.
|
|
2281
|
+
DEFAULT_RUNTIME_VERSION = true ? "1.2.0" : readFileSync3(
|
|
2282
2282
|
fileURLToPath2(
|
|
2283
2283
|
new URL("../../../../images/runtime/VERSION", import.meta.url)
|
|
2284
2284
|
),
|
|
2285
2285
|
"utf8"
|
|
2286
2286
|
).trim();
|
|
2287
|
-
|
|
2287
|
+
MIN_RUNTIME_FOR_SSH_ATTACH = "1.2.0";
|
|
2288
2288
|
DESCRIPTORS = loadDescriptorCatalogSync();
|
|
2289
2289
|
BUILTIN_LANGUAGES = new Set(
|
|
2290
2290
|
[...DESCRIPTORS.values()].filter((c) => c.category === "language" && c.descriptor.language?.builtin).map(descriptorSelector)
|
|
@@ -2947,14 +2947,32 @@ function ideStateVolumes(name) {
|
|
|
2947
2947
|
return [
|
|
2948
2948
|
{
|
|
2949
2949
|
volume: `monoceros-${name}-vscode-extensions`,
|
|
2950
|
-
target: "/home/node/.vscode-server/extensions"
|
|
2950
|
+
target: "/home/node/.vscode-server/extensions",
|
|
2951
|
+
minRuntime: "1.1.0"
|
|
2951
2952
|
},
|
|
2952
2953
|
{
|
|
2953
2954
|
volume: `monoceros-${name}-vscode-userdata`,
|
|
2954
|
-
target: "/home/node/.vscode-server/data/User"
|
|
2955
|
+
target: "/home/node/.vscode-server/data/User",
|
|
2956
|
+
minRuntime: "1.1.0"
|
|
2957
|
+
},
|
|
2958
|
+
{
|
|
2959
|
+
volume: `monoceros-${name}-vscodium-extensions`,
|
|
2960
|
+
target: "/home/node/.vscodium-server/extensions",
|
|
2961
|
+
minRuntime: "1.2.0"
|
|
2962
|
+
},
|
|
2963
|
+
{
|
|
2964
|
+
volume: `monoceros-${name}-vscodium-userdata`,
|
|
2965
|
+
target: "/home/node/.vscodium-server/data/User",
|
|
2966
|
+
minRuntime: "1.2.0"
|
|
2955
2967
|
}
|
|
2956
2968
|
];
|
|
2957
2969
|
}
|
|
2970
|
+
function ideStateVolumesForRuntime(name, version) {
|
|
2971
|
+
if (!version) return [];
|
|
2972
|
+
return ideStateVolumes(name).filter(
|
|
2973
|
+
(v) => compareRuntimeVersions(version, v.minRuntime) >= 0
|
|
2974
|
+
);
|
|
2975
|
+
}
|
|
2958
2976
|
function buildDevcontainerJson(opts, dockerMode = "rootful") {
|
|
2959
2977
|
const resolvedFeatures = resolveFeatures(opts);
|
|
2960
2978
|
const features = {};
|
|
@@ -2986,6 +3004,7 @@ function buildDevcontainerJson(opts, dockerMode = "rootful") {
|
|
|
2986
3004
|
}
|
|
2987
3005
|
}
|
|
2988
3006
|
} : void 0;
|
|
3007
|
+
const sshPostStart = runtimeSupportsSshAttach(opts.runtimeVersion) ? { postStartCommand: "sudo /usr/local/bin/monoceros-sshd-up.sh" } : {};
|
|
2989
3008
|
if (needsCompose(opts)) {
|
|
2990
3009
|
return {
|
|
2991
3010
|
name: opts.name,
|
|
@@ -2996,20 +3015,22 @@ function buildDevcontainerJson(opts, dockerMode = "rootful") {
|
|
|
2996
3015
|
remoteUser: "node",
|
|
2997
3016
|
forwardPorts: ports,
|
|
2998
3017
|
postCreateCommand: ".devcontainer/post-create.sh",
|
|
3018
|
+
...sshPostStart,
|
|
2999
3019
|
...featuresField ?? {},
|
|
3000
3020
|
...customizationsField ?? {}
|
|
3001
3021
|
};
|
|
3002
3022
|
}
|
|
3003
|
-
const ideMounts =
|
|
3004
|
-
|
|
3005
|
-
|
|
3023
|
+
const ideMounts = ideStateVolumesForRuntime(
|
|
3024
|
+
opts.name,
|
|
3025
|
+
opts.runtimeVersion
|
|
3026
|
+
).map((v) => `source=${v.volume},target=${v.target},type=volume`);
|
|
3006
3027
|
const mounts = [...homeMounts, ...ideMounts];
|
|
3007
3028
|
const mountsField = mounts.length > 0 ? { mounts } : {};
|
|
3008
3029
|
const workspaceMountField = {
|
|
3009
3030
|
workspaceMount: `source=\${localWorkspaceFolder},target=/workspaces/${opts.name},type=bind,consistency=cached`,
|
|
3010
3031
|
workspaceFolder: `/workspaces/${opts.name}`
|
|
3011
3032
|
};
|
|
3012
|
-
const runArgs = ["--cap-add=NET_ADMIN"];
|
|
3033
|
+
const runArgs = ["--cap-add=NET_ADMIN", `--name=monoceros-${opts.name}`];
|
|
3013
3034
|
if (ports.length > 0) {
|
|
3014
3035
|
runArgs.push("--network=monoceros-proxy");
|
|
3015
3036
|
runArgs.push(`--network-alias=${opts.name}`);
|
|
@@ -3023,6 +3044,7 @@ function buildDevcontainerJson(opts, dockerMode = "rootful") {
|
|
|
3023
3044
|
runArgs,
|
|
3024
3045
|
forwardPorts: ports,
|
|
3025
3046
|
postCreateCommand: ".devcontainer/post-create.sh",
|
|
3047
|
+
...sshPostStart,
|
|
3026
3048
|
...featuresField ?? {},
|
|
3027
3049
|
...customizationsField ?? {}
|
|
3028
3050
|
};
|
|
@@ -3043,9 +3065,10 @@ function buildComposeYaml(opts, dockerMode = "rootful") {
|
|
|
3043
3065
|
void dockerMode;
|
|
3044
3066
|
const hasPorts = (opts.ports?.length ?? 0) > 0;
|
|
3045
3067
|
const lines = ["services:"];
|
|
3046
|
-
const ideVolumes =
|
|
3068
|
+
const ideVolumes = ideStateVolumesForRuntime(opts.name, opts.runtimeVersion);
|
|
3047
3069
|
lines.push(" workspace:");
|
|
3048
3070
|
lines.push(` image: ${resolveRuntimeImage(opts.runtimeVersion)}`);
|
|
3071
|
+
lines.push(` container_name: monoceros-${opts.name}`);
|
|
3049
3072
|
lines.push(" command: 'sleep infinity'");
|
|
3050
3073
|
lines.push(" cap_add:");
|
|
3051
3074
|
lines.push(" - NET_ADMIN");
|
|
@@ -3917,8 +3940,8 @@ function removeRepoFromDoc(doc, urlOrPath) {
|
|
|
3917
3940
|
if (!isMap2(item)) return false;
|
|
3918
3941
|
const url = item.get("url");
|
|
3919
3942
|
if (url === urlOrPath) return true;
|
|
3920
|
-
const
|
|
3921
|
-
const effectivePath = typeof
|
|
3943
|
+
const path30 = item.get("path");
|
|
3944
|
+
const effectivePath = typeof path30 === "string" ? path30 : typeof url === "string" ? deriveRepoName(url) : void 0;
|
|
3922
3945
|
return effectivePath === urlOrPath;
|
|
3923
3946
|
});
|
|
3924
3947
|
if (idx < 0) return false;
|
|
@@ -4040,7 +4063,7 @@ async function runAddRepo(input) {
|
|
|
4040
4063
|
"Missing repo URL. Usage: monoceros add-repo <containername> <url>."
|
|
4041
4064
|
);
|
|
4042
4065
|
}
|
|
4043
|
-
const
|
|
4066
|
+
const path30 = (input.path ?? deriveRepoName(url)).trim();
|
|
4044
4067
|
const hasName = typeof input.gitName === "string" && input.gitName.trim().length > 0;
|
|
4045
4068
|
const hasEmail = typeof input.gitEmail === "string" && input.gitEmail.trim().length > 0;
|
|
4046
4069
|
if (hasName !== hasEmail) {
|
|
@@ -4077,7 +4100,7 @@ async function runAddRepo(input) {
|
|
|
4077
4100
|
const providerToWrite = !canonical && explicitProvider ? explicitProvider : void 0;
|
|
4078
4101
|
const entry2 = {
|
|
4079
4102
|
url,
|
|
4080
|
-
path:
|
|
4103
|
+
path: path30,
|
|
4081
4104
|
...hasName && hasEmail ? {
|
|
4082
4105
|
gitUser: {
|
|
4083
4106
|
name: input.gitName.trim(),
|
|
@@ -5120,9 +5143,163 @@ var init_transform = __esm({
|
|
|
5120
5143
|
}
|
|
5121
5144
|
});
|
|
5122
5145
|
|
|
5146
|
+
// src/devcontainer/ssh-attach.ts
|
|
5147
|
+
import { spawn as spawn4 } from "child_process";
|
|
5148
|
+
import { promises as fs10 } from "fs";
|
|
5149
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5150
|
+
import os2 from "os";
|
|
5151
|
+
import path13 from "path";
|
|
5152
|
+
function sshHomeDir(home) {
|
|
5153
|
+
return path13.join(home, "ssh");
|
|
5154
|
+
}
|
|
5155
|
+
function sshConfigEntryPath(home, name) {
|
|
5156
|
+
return path13.join(sshHomeDir(home), "config.d", name);
|
|
5157
|
+
}
|
|
5158
|
+
function sshProxyScriptPath(home, name) {
|
|
5159
|
+
return path13.join(sshHomeDir(home), `exec-${name}.sh`);
|
|
5160
|
+
}
|
|
5161
|
+
function privateKeyPath(targetDir) {
|
|
5162
|
+
return path13.join(targetDir, ".monoceros", "ssh", "id_ed25519");
|
|
5163
|
+
}
|
|
5164
|
+
async function ensureKeypair(targetDir, name, keygen, logger) {
|
|
5165
|
+
const privateKey = privateKeyPath(targetDir);
|
|
5166
|
+
const publicKey = `${privateKey}.pub`;
|
|
5167
|
+
if (existsSync8(privateKey) && existsSync8(publicKey)) {
|
|
5168
|
+
return { privateKey, publicKey };
|
|
5169
|
+
}
|
|
5170
|
+
await fs10.mkdir(path13.dirname(privateKey), { recursive: true });
|
|
5171
|
+
try {
|
|
5172
|
+
const res = await keygen([
|
|
5173
|
+
"-t",
|
|
5174
|
+
"ed25519",
|
|
5175
|
+
"-N",
|
|
5176
|
+
"",
|
|
5177
|
+
"-f",
|
|
5178
|
+
privateKey,
|
|
5179
|
+
"-C",
|
|
5180
|
+
`monoceros-${name}`,
|
|
5181
|
+
"-q"
|
|
5182
|
+
]);
|
|
5183
|
+
if (res.exitCode !== 0) {
|
|
5184
|
+
logger.warn(
|
|
5185
|
+
`ssh-keygen failed (exit ${res.exitCode})${res.stderr ? `: ${res.stderr.trim()}` : ""}; IDE SSH attach not set up.`
|
|
5186
|
+
);
|
|
5187
|
+
return null;
|
|
5188
|
+
}
|
|
5189
|
+
} catch (err) {
|
|
5190
|
+
logger.warn(
|
|
5191
|
+
`ssh-keygen not runnable (${err instanceof Error ? err.message : String(err)}); IDE SSH attach not set up.`
|
|
5192
|
+
);
|
|
5193
|
+
return null;
|
|
5194
|
+
}
|
|
5195
|
+
return { privateKey, publicKey };
|
|
5196
|
+
}
|
|
5197
|
+
function proxyScriptContent(name, targetDir) {
|
|
5198
|
+
return `#!/bin/sh
|
|
5199
|
+
# Generated by Monoceros (ADR 0022) - do not edit.
|
|
5200
|
+
# Portless SSH transport into the running dev container '${name}'.
|
|
5201
|
+
# Resolves the container by the devcontainer.local_folder label (the
|
|
5202
|
+
# same handle 'monoceros shell' uses), so it follows rebuilds.
|
|
5203
|
+
set -e
|
|
5204
|
+
cid=$(docker ps -q \\
|
|
5205
|
+
--filter "label=devcontainer.local_folder=${targetDir}" \\
|
|
5206
|
+
--filter status=running | head -n1)
|
|
5207
|
+
if [ -z "$cid" ]; then
|
|
5208
|
+
echo "monoceros: dev container '${name}' is not running. Start it with: monoceros apply ${name}" >&2
|
|
5209
|
+
exit 1
|
|
5210
|
+
fi
|
|
5211
|
+
exec docker exec -i "$cid" socat - TCP:127.0.0.1:22
|
|
5212
|
+
`;
|
|
5213
|
+
}
|
|
5214
|
+
function configEntryContent(name, hostAlias, privateKey, proxyScript) {
|
|
5215
|
+
return `# Generated by Monoceros (ADR 0022) - do not edit.
|
|
5216
|
+
# Attach an SSH-capable IDE (Codium, IntelliJ, Zed) to host
|
|
5217
|
+
# '${hostAlias}', or run: ssh ${hostAlias}
|
|
5218
|
+
Host ${hostAlias}
|
|
5219
|
+
User node
|
|
5220
|
+
IdentityFile "${privateKey}"
|
|
5221
|
+
IdentitiesOnly yes
|
|
5222
|
+
StrictHostKeyChecking no
|
|
5223
|
+
UserKnownHostsFile /dev/null
|
|
5224
|
+
ProxyCommand "${proxyScript}"
|
|
5225
|
+
`;
|
|
5226
|
+
}
|
|
5227
|
+
async function ensureInclude(userSshDir, home) {
|
|
5228
|
+
const configPath = path13.join(userSshDir, "config");
|
|
5229
|
+
const includeTarget = path13.join(sshHomeDir(home), "config.d", "*");
|
|
5230
|
+
const includeLine = `Include "${includeTarget}"`;
|
|
5231
|
+
await fs10.mkdir(userSshDir, { recursive: true });
|
|
5232
|
+
let existing = "";
|
|
5233
|
+
try {
|
|
5234
|
+
existing = await fs10.readFile(configPath, "utf8");
|
|
5235
|
+
} catch {
|
|
5236
|
+
existing = "";
|
|
5237
|
+
}
|
|
5238
|
+
if (existing.includes(includeTarget)) return;
|
|
5239
|
+
const banner = "# Added by Monoceros (ADR 0022): per-container SSH attach entries.";
|
|
5240
|
+
const prefix = `${banner}
|
|
5241
|
+
${includeLine}
|
|
5242
|
+
`;
|
|
5243
|
+
const next = existing.length > 0 ? `${prefix}
|
|
5244
|
+
${existing}` : prefix;
|
|
5245
|
+
await fs10.writeFile(configPath, next, { mode: 384 });
|
|
5246
|
+
await fs10.chmod(configPath, 384).catch(() => {
|
|
5247
|
+
});
|
|
5248
|
+
}
|
|
5249
|
+
async function setupSshAttach(opts) {
|
|
5250
|
+
const hostAlias = `monoceros-${opts.name}`;
|
|
5251
|
+
const logger = opts.logger ?? { info: () => {
|
|
5252
|
+
}, warn: () => {
|
|
5253
|
+
} };
|
|
5254
|
+
const keygen = opts.keygen ?? realKeygen;
|
|
5255
|
+
const userSshDir = opts.userSshDir ?? path13.join(os2.homedir(), ".ssh");
|
|
5256
|
+
const keys = await ensureKeypair(opts.targetDir, opts.name, keygen, logger);
|
|
5257
|
+
if (!keys) return { hostAlias, configured: false };
|
|
5258
|
+
const proxyScript = sshProxyScriptPath(opts.home, opts.name);
|
|
5259
|
+
const configEntry = sshConfigEntryPath(opts.home, opts.name);
|
|
5260
|
+
await fs10.mkdir(path13.dirname(proxyScript), { recursive: true });
|
|
5261
|
+
await fs10.mkdir(path13.dirname(configEntry), { recursive: true });
|
|
5262
|
+
await fs10.writeFile(
|
|
5263
|
+
proxyScript,
|
|
5264
|
+
proxyScriptContent(opts.name, opts.targetDir),
|
|
5265
|
+
{ mode: 493 }
|
|
5266
|
+
);
|
|
5267
|
+
await fs10.chmod(proxyScript, 493).catch(() => {
|
|
5268
|
+
});
|
|
5269
|
+
await fs10.writeFile(
|
|
5270
|
+
configEntry,
|
|
5271
|
+
configEntryContent(opts.name, hostAlias, keys.privateKey, proxyScript)
|
|
5272
|
+
);
|
|
5273
|
+
await ensureInclude(userSshDir, opts.home);
|
|
5274
|
+
return { hostAlias, configured: true };
|
|
5275
|
+
}
|
|
5276
|
+
async function removeSshAttach(home, name) {
|
|
5277
|
+
await fs10.rm(sshProxyScriptPath(home, name), { force: true });
|
|
5278
|
+
await fs10.rm(sshConfigEntryPath(home, name), { force: true });
|
|
5279
|
+
}
|
|
5280
|
+
var realKeygen;
|
|
5281
|
+
var init_ssh_attach = __esm({
|
|
5282
|
+
"src/devcontainer/ssh-attach.ts"() {
|
|
5283
|
+
"use strict";
|
|
5284
|
+
realKeygen = (args) => {
|
|
5285
|
+
return new Promise((resolve, reject) => {
|
|
5286
|
+
const child = spawn4("ssh-keygen", args, {
|
|
5287
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
5288
|
+
});
|
|
5289
|
+
let stderr = "";
|
|
5290
|
+
child.stderr.on("data", (chunk) => {
|
|
5291
|
+
stderr += chunk.toString();
|
|
5292
|
+
});
|
|
5293
|
+
child.on("error", reject);
|
|
5294
|
+
child.on("exit", (code) => resolve({ exitCode: code ?? 0, stderr }));
|
|
5295
|
+
});
|
|
5296
|
+
};
|
|
5297
|
+
}
|
|
5298
|
+
});
|
|
5299
|
+
|
|
5123
5300
|
// src/apply/apply-log.ts
|
|
5124
5301
|
import { createWriteStream, mkdirSync } from "fs";
|
|
5125
|
-
import
|
|
5302
|
+
import path14 from "path";
|
|
5126
5303
|
import { Writable } from "stream";
|
|
5127
5304
|
function safeIsoStamp(d) {
|
|
5128
5305
|
return d.toISOString().replace(/[:.]/g, "-");
|
|
@@ -5132,7 +5309,7 @@ function createApplyLog(opts) {
|
|
|
5132
5309
|
const dir = containerLogsDir(opts.name, opts.home);
|
|
5133
5310
|
mkdirSync(dir, { recursive: true });
|
|
5134
5311
|
const file = `apply-${opts.name}-${safeIsoStamp(now)}.log`;
|
|
5135
|
-
const fullPath =
|
|
5312
|
+
const fullPath = path14.join(dir, file);
|
|
5136
5313
|
const stream = createWriteStream(fullPath, { flags: "w" });
|
|
5137
5314
|
const header = [
|
|
5138
5315
|
`# monoceros apply log`,
|
|
@@ -5999,8 +6176,8 @@ var init_markers = __esm({
|
|
|
5999
6176
|
});
|
|
6000
6177
|
|
|
6001
6178
|
// src/briefing/index.ts
|
|
6002
|
-
import { promises as
|
|
6003
|
-
import
|
|
6179
|
+
import { promises as fs11 } from "fs";
|
|
6180
|
+
import path15 from "path";
|
|
6004
6181
|
async function writeBriefing(input) {
|
|
6005
6182
|
const subCommands = input.subCommands ?? await loadSubCommandsDynamic();
|
|
6006
6183
|
const manifestLoader = input.manifestLoader ?? ((ref) => loadFeatureManifestSummary(ref));
|
|
@@ -6013,12 +6190,12 @@ async function writeBriefing(input) {
|
|
|
6013
6190
|
);
|
|
6014
6191
|
const claudeBody = generateClaudeMd();
|
|
6015
6192
|
const commandsBody = generateCommandsMd(subCommands);
|
|
6016
|
-
await writeMarkerAware(
|
|
6017
|
-
await writeMarkerAware(
|
|
6018
|
-
const monocerosDir =
|
|
6019
|
-
await
|
|
6020
|
-
await
|
|
6021
|
-
|
|
6193
|
+
await writeMarkerAware(path15.join(input.targetDir, "AGENTS.md"), agentsBody);
|
|
6194
|
+
await writeMarkerAware(path15.join(input.targetDir, "CLAUDE.md"), claudeBody);
|
|
6195
|
+
const monocerosDir = path15.join(input.targetDir, ".monoceros");
|
|
6196
|
+
await fs11.mkdir(monocerosDir, { recursive: true });
|
|
6197
|
+
await fs11.writeFile(
|
|
6198
|
+
path15.join(monocerosDir, "commands.md"),
|
|
6022
6199
|
commandsBody,
|
|
6023
6200
|
"utf8"
|
|
6024
6201
|
);
|
|
@@ -6038,18 +6215,18 @@ function featureDisplayMap(components) {
|
|
|
6038
6215
|
async function writeMarkerAware(filePath, body) {
|
|
6039
6216
|
let existing = null;
|
|
6040
6217
|
try {
|
|
6041
|
-
existing = await
|
|
6218
|
+
existing = await fs11.readFile(filePath, "utf8");
|
|
6042
6219
|
} catch (err) {
|
|
6043
6220
|
if (err.code !== "ENOENT") throw err;
|
|
6044
6221
|
}
|
|
6045
6222
|
if (existing) {
|
|
6046
6223
|
const updated = replaceMarkerBlock(existing, body);
|
|
6047
6224
|
if (updated !== null) {
|
|
6048
|
-
await
|
|
6225
|
+
await fs11.writeFile(filePath, updated, "utf8");
|
|
6049
6226
|
return;
|
|
6050
6227
|
}
|
|
6051
6228
|
}
|
|
6052
|
-
await
|
|
6229
|
+
await fs11.writeFile(filePath, wrapWithMarkers(body), "utf8");
|
|
6053
6230
|
}
|
|
6054
6231
|
async function loadSubCommandsDynamic() {
|
|
6055
6232
|
const mod = await Promise.resolve().then(() => (init_main(), main_exports));
|
|
@@ -6174,10 +6351,10 @@ var init_runtime_pull_hint = __esm({
|
|
|
6174
6351
|
});
|
|
6175
6352
|
|
|
6176
6353
|
// src/devcontainer/cli.ts
|
|
6177
|
-
import { spawn as
|
|
6354
|
+
import { spawn as spawn5 } from "child_process";
|
|
6178
6355
|
import { readFileSync as readFileSync4 } from "fs";
|
|
6179
6356
|
import { createRequire } from "module";
|
|
6180
|
-
import
|
|
6357
|
+
import path16 from "path";
|
|
6181
6358
|
function devcontainerCliPath() {
|
|
6182
6359
|
if (cachedBinaryPath) return cachedBinaryPath;
|
|
6183
6360
|
const pkgJsonPath = require_.resolve("@devcontainers/cli/package.json");
|
|
@@ -6186,7 +6363,7 @@ function devcontainerCliPath() {
|
|
|
6186
6363
|
if (!binEntry) {
|
|
6187
6364
|
throw new Error("Could not resolve @devcontainers/cli bin entry.");
|
|
6188
6365
|
}
|
|
6189
|
-
cachedBinaryPath =
|
|
6366
|
+
cachedBinaryPath = path16.resolve(path16.dirname(pkgJsonPath), binEntry);
|
|
6190
6367
|
return cachedBinaryPath;
|
|
6191
6368
|
}
|
|
6192
6369
|
var require_, cachedBinaryPath, spawnDevcontainer;
|
|
@@ -6202,7 +6379,7 @@ var init_cli = __esm({
|
|
|
6202
6379
|
const env = { ...process.env, DOCKER_CLI_HINTS: "false" };
|
|
6203
6380
|
return new Promise((resolve, reject) => {
|
|
6204
6381
|
if (options.interactive) {
|
|
6205
|
-
const child2 =
|
|
6382
|
+
const child2 = spawn5(process.execPath, [binPath, ...args], {
|
|
6206
6383
|
cwd,
|
|
6207
6384
|
env,
|
|
6208
6385
|
stdio: "inherit"
|
|
@@ -6211,7 +6388,7 @@ var init_cli = __esm({
|
|
|
6211
6388
|
child2.on("exit", (code) => resolve(code ?? 0));
|
|
6212
6389
|
return;
|
|
6213
6390
|
}
|
|
6214
|
-
const child =
|
|
6391
|
+
const child = spawn5(process.execPath, [binPath, ...args], {
|
|
6215
6392
|
cwd,
|
|
6216
6393
|
env,
|
|
6217
6394
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -6259,9 +6436,9 @@ var init_cli = __esm({
|
|
|
6259
6436
|
});
|
|
6260
6437
|
|
|
6261
6438
|
// src/devcontainer/compose.ts
|
|
6262
|
-
import { spawn as
|
|
6263
|
-
import { existsSync as
|
|
6264
|
-
import
|
|
6439
|
+
import { spawn as spawn6 } from "child_process";
|
|
6440
|
+
import { existsSync as existsSync9 } from "fs";
|
|
6441
|
+
import path17 from "path";
|
|
6265
6442
|
import { Writable as Writable3 } from "stream";
|
|
6266
6443
|
import { consola as consola10 } from "consola";
|
|
6267
6444
|
async function findContainerIds(filters, exec = spawnDocker) {
|
|
@@ -6311,18 +6488,24 @@ async function cleanupDockerObjects(opts) {
|
|
|
6311
6488
|
return { exitCode: rmExit, removedIds: ids };
|
|
6312
6489
|
}
|
|
6313
6490
|
function composeProjectName(root) {
|
|
6314
|
-
return `${
|
|
6491
|
+
return `${path17.basename(root)}_devcontainer`;
|
|
6315
6492
|
}
|
|
6316
|
-
function
|
|
6317
|
-
if (!
|
|
6493
|
+
function assertDevcontainer(root) {
|
|
6494
|
+
if (!existsSync9(path17.join(root, ".devcontainer"))) {
|
|
6318
6495
|
throw new Error(
|
|
6319
6496
|
`No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
|
|
6320
6497
|
);
|
|
6321
6498
|
}
|
|
6322
|
-
|
|
6323
|
-
|
|
6499
|
+
}
|
|
6500
|
+
function isComposeMode(root) {
|
|
6501
|
+
return existsSync9(path17.join(root, ".devcontainer", "compose.yaml"));
|
|
6502
|
+
}
|
|
6503
|
+
function resolveCompose(root) {
|
|
6504
|
+
assertDevcontainer(root);
|
|
6505
|
+
const composeFile = path17.join(root, ".devcontainer", "compose.yaml");
|
|
6506
|
+
if (!existsSync9(composeFile)) {
|
|
6324
6507
|
throw new Error(
|
|
6325
|
-
`No compose.yaml at ${composeFile}. \`
|
|
6508
|
+
`No compose.yaml at ${composeFile}. \`monoceros logs\` tails compose service logs, which require services configured via \`monoceros add-service <name> <svc>\`. For a bare container, use \`monoceros shell <name>\` or read logs/<app>.log.`
|
|
6326
6509
|
);
|
|
6327
6510
|
}
|
|
6328
6511
|
return { composeFile, projectName: composeProjectName(root) };
|
|
@@ -6334,7 +6517,7 @@ async function runComposeAction(buildSubArgs, opts) {
|
|
|
6334
6517
|
return spawnFn(["-f", composeFile, "-p", projectName, ...subArgs], opts.root);
|
|
6335
6518
|
}
|
|
6336
6519
|
async function runStart(opts) {
|
|
6337
|
-
|
|
6520
|
+
assertDevcontainer(opts.root);
|
|
6338
6521
|
const logger = opts.logger ?? { info: (msg) => consola10.info(msg) };
|
|
6339
6522
|
const spawnFn = opts.spawn ?? spawnDevcontainer;
|
|
6340
6523
|
logger.info(`Bringing devcontainer up at ${opts.root}\u2026`);
|
|
@@ -6477,16 +6660,71 @@ and retry \`monoceros apply\`.`
|
|
|
6477
6660
|
);
|
|
6478
6661
|
}
|
|
6479
6662
|
function runStop(opts) {
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6663
|
+
assertDevcontainer(opts.root);
|
|
6664
|
+
if (isComposeMode(opts.root)) {
|
|
6665
|
+
return runComposeAction(
|
|
6666
|
+
(service) => ["stop", ...service ? [service] : []],
|
|
6667
|
+
opts
|
|
6668
|
+
);
|
|
6669
|
+
}
|
|
6670
|
+
return stopImageContainer(opts);
|
|
6484
6671
|
}
|
|
6485
6672
|
function runStatus(opts) {
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6673
|
+
assertDevcontainer(opts.root);
|
|
6674
|
+
if (isComposeMode(opts.root)) {
|
|
6675
|
+
return runComposeAction(
|
|
6676
|
+
(service) => ["ps", ...service ? [service] : []],
|
|
6677
|
+
opts
|
|
6678
|
+
);
|
|
6679
|
+
}
|
|
6680
|
+
return statusImageContainer(opts);
|
|
6681
|
+
}
|
|
6682
|
+
function imageContainerFilter(root) {
|
|
6683
|
+
return `label=devcontainer.local_folder=${root}`;
|
|
6684
|
+
}
|
|
6685
|
+
async function stopImageContainer(opts) {
|
|
6686
|
+
const exec = opts.dockerExec ?? spawnDocker;
|
|
6687
|
+
const logger = opts.logger ?? { info: (msg) => consola10.info(msg) };
|
|
6688
|
+
const name = path17.basename(opts.root);
|
|
6689
|
+
const ps = await exec([
|
|
6690
|
+
"ps",
|
|
6691
|
+
"-q",
|
|
6692
|
+
"--filter",
|
|
6693
|
+
imageContainerFilter(opts.root),
|
|
6694
|
+
"--filter",
|
|
6695
|
+
"status=running"
|
|
6696
|
+
]);
|
|
6697
|
+
const id = ps.stdout.split("\n").map((s) => s.trim()).find((s) => s.length > 0);
|
|
6698
|
+
if (!id) {
|
|
6699
|
+
logger.info(`Container '${name}' is not running.`);
|
|
6700
|
+
return 0;
|
|
6701
|
+
}
|
|
6702
|
+
const res = await exec(["stop", id]);
|
|
6703
|
+
if (res.exitCode === 0) logger.info(`Stopped '${name}'.`);
|
|
6704
|
+
return res.exitCode;
|
|
6705
|
+
}
|
|
6706
|
+
async function statusImageContainer(opts) {
|
|
6707
|
+
const exec = opts.dockerExec ?? spawnDocker;
|
|
6708
|
+
const logger = opts.logger ?? { info: (msg) => consola10.info(msg) };
|
|
6709
|
+
const name = path17.basename(opts.root);
|
|
6710
|
+
const res = await exec([
|
|
6711
|
+
"ps",
|
|
6712
|
+
"-a",
|
|
6713
|
+
"--filter",
|
|
6714
|
+
imageContainerFilter(opts.root)
|
|
6715
|
+
]);
|
|
6716
|
+
const rows = res.stdout.split("\n").filter((l) => l.trim().length > 0);
|
|
6717
|
+
if (rows.length <= 1) {
|
|
6718
|
+
logger.info(
|
|
6719
|
+
`Container '${name}' does not exist. Run \`monoceros apply ${name}\`.`
|
|
6720
|
+
);
|
|
6721
|
+
return 0;
|
|
6722
|
+
}
|
|
6723
|
+
process.stdout.write(
|
|
6724
|
+
res.stdout.endsWith("\n") ? res.stdout : `${res.stdout}
|
|
6725
|
+
`
|
|
6489
6726
|
);
|
|
6727
|
+
return res.exitCode;
|
|
6490
6728
|
}
|
|
6491
6729
|
function runLogs(opts) {
|
|
6492
6730
|
const follow = opts.follow ?? true;
|
|
@@ -6509,7 +6747,7 @@ var init_compose = __esm({
|
|
|
6509
6747
|
init_proxy();
|
|
6510
6748
|
spawnDockerCompose = (args, cwd) => {
|
|
6511
6749
|
return new Promise((resolve, reject) => {
|
|
6512
|
-
const child =
|
|
6750
|
+
const child = spawn6("docker", ["compose", ...args], {
|
|
6513
6751
|
cwd,
|
|
6514
6752
|
stdio: ["inherit", "pipe", "pipe"]
|
|
6515
6753
|
});
|
|
@@ -6521,7 +6759,7 @@ var init_compose = __esm({
|
|
|
6521
6759
|
};
|
|
6522
6760
|
spawnDocker = (args) => {
|
|
6523
6761
|
return new Promise((resolve, reject) => {
|
|
6524
|
-
const child =
|
|
6762
|
+
const child = spawn6("docker", args, {
|
|
6525
6763
|
stdio: ["ignore", "pipe", "pipe"]
|
|
6526
6764
|
});
|
|
6527
6765
|
let stdout = "";
|
|
@@ -6582,9 +6820,9 @@ var init_images = __esm({
|
|
|
6582
6820
|
|
|
6583
6821
|
// src/config/machine-state.ts
|
|
6584
6822
|
import { promises as fsp4 } from "fs";
|
|
6585
|
-
import
|
|
6823
|
+
import path18 from "path";
|
|
6586
6824
|
function machineStatePath(home = monocerosHome()) {
|
|
6587
|
-
return
|
|
6825
|
+
return path18.join(home, ".machine-state.json");
|
|
6588
6826
|
}
|
|
6589
6827
|
async function readMachineState(home = monocerosHome()) {
|
|
6590
6828
|
try {
|
|
@@ -6639,7 +6877,7 @@ var init_machine_state = __esm({
|
|
|
6639
6877
|
});
|
|
6640
6878
|
|
|
6641
6879
|
// src/devcontainer/docker-mode.ts
|
|
6642
|
-
import { spawn as
|
|
6880
|
+
import { spawn as spawn7 } from "child_process";
|
|
6643
6881
|
async function detectDockerMode(options = {}) {
|
|
6644
6882
|
const spawnFn = options.spawn ?? realDockerInfo;
|
|
6645
6883
|
try {
|
|
@@ -6689,7 +6927,7 @@ var init_docker_mode = __esm({
|
|
|
6689
6927
|
init_format();
|
|
6690
6928
|
realDockerInfo = () => {
|
|
6691
6929
|
return new Promise((resolve, reject) => {
|
|
6692
|
-
const child =
|
|
6930
|
+
const child = spawn7(
|
|
6693
6931
|
"docker",
|
|
6694
6932
|
["info", "--format", "{{json .SecurityOptions}}"],
|
|
6695
6933
|
{
|
|
@@ -6708,9 +6946,9 @@ var init_docker_mode = __esm({
|
|
|
6708
6946
|
});
|
|
6709
6947
|
|
|
6710
6948
|
// src/devcontainer/identity.ts
|
|
6711
|
-
import { spawn as
|
|
6712
|
-
import { promises as
|
|
6713
|
-
import
|
|
6949
|
+
import { spawn as spawn8 } from "child_process";
|
|
6950
|
+
import { promises as fs12 } from "fs";
|
|
6951
|
+
import path19 from "path";
|
|
6714
6952
|
import { consola as consola11 } from "consola";
|
|
6715
6953
|
async function resolveIdentityWithPrompt(options = {}) {
|
|
6716
6954
|
const spawnFn = options.spawn ?? realGitConfigGet;
|
|
@@ -6766,8 +7004,8 @@ async function resolveIdentityWithPrompt(options = {}) {
|
|
|
6766
7004
|
};
|
|
6767
7005
|
}
|
|
6768
7006
|
async function collectGitIdentity(devContainerRoot, options = {}) {
|
|
6769
|
-
const gitconfigDir =
|
|
6770
|
-
const gitconfigPath =
|
|
7007
|
+
const gitconfigDir = path19.join(devContainerRoot, ".monoceros");
|
|
7008
|
+
const gitconfigPath = path19.join(gitconfigDir, "gitconfig");
|
|
6771
7009
|
const logger = options.logger ?? { info: () => {
|
|
6772
7010
|
}, warn: () => {
|
|
6773
7011
|
} };
|
|
@@ -6780,8 +7018,8 @@ async function collectGitIdentity(devContainerRoot, options = {}) {
|
|
|
6780
7018
|
const lines = ["[user]"];
|
|
6781
7019
|
if (resolved.name !== void 0) lines.push(` name = ${resolved.name}`);
|
|
6782
7020
|
if (resolved.email !== void 0) lines.push(` email = ${resolved.email}`);
|
|
6783
|
-
await
|
|
6784
|
-
await
|
|
7021
|
+
await fs12.mkdir(gitconfigDir, { recursive: true });
|
|
7022
|
+
await fs12.writeFile(gitconfigPath, lines.join("\n") + "\n");
|
|
6785
7023
|
return {
|
|
6786
7024
|
...resolved.name !== void 0 ? { name: resolved.name } : {},
|
|
6787
7025
|
...resolved.email !== void 0 ? { email: resolved.email } : {},
|
|
@@ -6824,7 +7062,7 @@ async function readKeyFromHost(spawnFn, key, logger) {
|
|
|
6824
7062
|
}
|
|
6825
7063
|
async function readExistingGitconfig(filePath) {
|
|
6826
7064
|
try {
|
|
6827
|
-
const content = await
|
|
7065
|
+
const content = await fs12.readFile(filePath, "utf8");
|
|
6828
7066
|
const result = {};
|
|
6829
7067
|
const nameMatch = /^\s*name\s*=\s*(.+?)\s*$/m.exec(content);
|
|
6830
7068
|
const emailMatch = /^\s*email\s*=\s*(.+?)\s*$/m.exec(content);
|
|
@@ -6841,7 +7079,7 @@ var init_identity = __esm({
|
|
|
6841
7079
|
"use strict";
|
|
6842
7080
|
realGitConfigGet = (key) => {
|
|
6843
7081
|
return new Promise((resolve, reject) => {
|
|
6844
|
-
const child =
|
|
7082
|
+
const child = spawn8("git", ["config", "--global", "--get", key], {
|
|
6845
7083
|
stdio: ["ignore", "pipe", "inherit"]
|
|
6846
7084
|
});
|
|
6847
7085
|
let stdout = "";
|
|
@@ -6901,7 +7139,7 @@ var init_identity = __esm({
|
|
|
6901
7139
|
});
|
|
6902
7140
|
|
|
6903
7141
|
// src/apply/index.ts
|
|
6904
|
-
import { existsSync as
|
|
7142
|
+
import { existsSync as existsSync10, promises as fs13 } from "fs";
|
|
6905
7143
|
import { consola as consola12 } from "consola";
|
|
6906
7144
|
async function runApply(opts) {
|
|
6907
7145
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
@@ -6923,7 +7161,7 @@ ${sectionLine(label)}
|
|
|
6923
7161
|
);
|
|
6924
7162
|
}
|
|
6925
7163
|
const ymlPath = containerConfigPath(opts.name, home);
|
|
6926
|
-
if (!
|
|
7164
|
+
if (!existsSync10(ymlPath)) {
|
|
6927
7165
|
throw new Error(
|
|
6928
7166
|
`No such config: ${ymlPath}. Run \`monoceros init <template> ${opts.name}\` first.`
|
|
6929
7167
|
);
|
|
@@ -7041,7 +7279,7 @@ Fix the value in the env file (or the yml).`
|
|
|
7041
7279
|
if (dockerMode === "rootless") {
|
|
7042
7280
|
throw new Error(formatRootlessNotSupportedError());
|
|
7043
7281
|
}
|
|
7044
|
-
await
|
|
7282
|
+
await fs13.mkdir(targetDir, { recursive: true });
|
|
7045
7283
|
await writeScaffold(createOpts, targetDir, { dockerMode });
|
|
7046
7284
|
await writeStateFile(
|
|
7047
7285
|
targetDir,
|
|
@@ -7052,6 +7290,27 @@ Fix the value in the env file (or the yml).`
|
|
|
7052
7290
|
...opts.now ? { now: opts.now } : {}
|
|
7053
7291
|
})
|
|
7054
7292
|
);
|
|
7293
|
+
if (runtimeSupportsSshAttach(createOpts.runtimeVersion)) {
|
|
7294
|
+
try {
|
|
7295
|
+
const ssh = await setupSshAttach({
|
|
7296
|
+
name: opts.name,
|
|
7297
|
+
targetDir,
|
|
7298
|
+
home,
|
|
7299
|
+
...opts.sshKeygen ? { keygen: opts.sshKeygen } : {},
|
|
7300
|
+
...opts.sshUserSshDir ? { userSshDir: opts.sshUserSshDir } : {},
|
|
7301
|
+
logger: idLogger
|
|
7302
|
+
});
|
|
7303
|
+
if (ssh.configured) {
|
|
7304
|
+
logger.info(
|
|
7305
|
+
`SSH attach: \`ssh ${ssh.hostAlias}\` (or pick it in your IDE)`
|
|
7306
|
+
);
|
|
7307
|
+
}
|
|
7308
|
+
} catch (err) {
|
|
7309
|
+
(logger.warn ?? logger.info)(
|
|
7310
|
+
`SSH attach setup skipped: ${err instanceof Error ? err.message : String(err)}`
|
|
7311
|
+
);
|
|
7312
|
+
}
|
|
7313
|
+
}
|
|
7055
7314
|
try {
|
|
7056
7315
|
const components = await loadComponentCatalog();
|
|
7057
7316
|
await writeBriefing({ targetDir, createOpts, components });
|
|
@@ -7196,8 +7455,8 @@ ${stripAnsi(formatted)}
|
|
|
7196
7455
|
return { targetDir, configPath: ymlPath, containerExitCode: exitCode };
|
|
7197
7456
|
}
|
|
7198
7457
|
async function assertSafeTargetDir(targetDir, expectedOrigin) {
|
|
7199
|
-
if (!
|
|
7200
|
-
const entries = await
|
|
7458
|
+
if (!existsSync10(targetDir)) return;
|
|
7459
|
+
const entries = await fs13.readdir(targetDir);
|
|
7201
7460
|
if (entries.length === 0) return;
|
|
7202
7461
|
const state = await readStateFile(targetDir);
|
|
7203
7462
|
if (state) {
|
|
@@ -7267,7 +7526,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
|
|
|
7267
7526
|
}
|
|
7268
7527
|
if (wantContainer) {
|
|
7269
7528
|
try {
|
|
7270
|
-
const text = await
|
|
7529
|
+
const text = await fs13.readFile(ymlPath, "utf8");
|
|
7271
7530
|
const parsed = parseConfig(text, ymlPath);
|
|
7272
7531
|
const changed = setContainerGitUserInDoc(parsed.doc, {
|
|
7273
7532
|
name: prompted.name,
|
|
@@ -7275,7 +7534,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
|
|
|
7275
7534
|
});
|
|
7276
7535
|
if (changed) {
|
|
7277
7536
|
const out = stringifyConfig(parsed.doc);
|
|
7278
|
-
await
|
|
7537
|
+
await fs13.writeFile(ymlPath, out, "utf8");
|
|
7279
7538
|
logger.info(
|
|
7280
7539
|
`Saved identity in this container \u2014 wrote git.user into ${prettyPath(ymlPath)}.`
|
|
7281
7540
|
);
|
|
@@ -7298,6 +7557,7 @@ var init_apply = __esm({
|
|
|
7298
7557
|
init_state();
|
|
7299
7558
|
init_transform();
|
|
7300
7559
|
init_catalog();
|
|
7560
|
+
init_ssh_attach();
|
|
7301
7561
|
init_scaffold();
|
|
7302
7562
|
init_format();
|
|
7303
7563
|
init_ref();
|
|
@@ -7321,90 +7581,386 @@ var init_apply = __esm({
|
|
|
7321
7581
|
}
|
|
7322
7582
|
});
|
|
7323
7583
|
|
|
7324
|
-
// src/
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
}
|
|
7331
|
-
});
|
|
7332
|
-
|
|
7333
|
-
// src/commands/_dispatch.ts
|
|
7334
|
-
import { consola as consola13 } from "consola";
|
|
7335
|
-
async function dispatch(runner) {
|
|
7584
|
+
// src/devcontainer/browser-bridge.ts
|
|
7585
|
+
import { spawn as spawn9 } from "child_process";
|
|
7586
|
+
import { existsSync as existsSync11, promises as fsp5, readFileSync as readFileSync5 } from "fs";
|
|
7587
|
+
import http from "http";
|
|
7588
|
+
import path20 from "path";
|
|
7589
|
+
function parseCallbackTarget(authUrl) {
|
|
7336
7590
|
try {
|
|
7337
|
-
const
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7591
|
+
const u = new URL(authUrl);
|
|
7592
|
+
const redirect = u.searchParams.get("redirect_uri");
|
|
7593
|
+
if (!redirect) return null;
|
|
7594
|
+
const r = new URL(redirect);
|
|
7595
|
+
if (r.hostname !== "localhost" && r.hostname !== "127.0.0.1") return null;
|
|
7596
|
+
const port = Number(r.port);
|
|
7597
|
+
if (!Number.isInteger(port) || port <= 0) return null;
|
|
7598
|
+
return { port, pathname: r.pathname };
|
|
7599
|
+
} catch {
|
|
7600
|
+
return null;
|
|
7342
7601
|
}
|
|
7343
7602
|
}
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7603
|
+
function nextRelayUrl(content, lastOpened) {
|
|
7604
|
+
const url = content.trim();
|
|
7605
|
+
return url && url !== lastOpened ? url : null;
|
|
7606
|
+
}
|
|
7607
|
+
function wrapExec(command, opts) {
|
|
7608
|
+
const leading = [];
|
|
7609
|
+
const stmts = [];
|
|
7610
|
+
if (opts.pathPrepend) {
|
|
7611
|
+
leading.push(opts.pathPrepend);
|
|
7612
|
+
const i = leading.length;
|
|
7613
|
+
stmts.push(`export PATH="$${i}:$PATH"`);
|
|
7614
|
+
stmts.push(`export BROWSER="$${i}/xdg-open"`);
|
|
7347
7615
|
}
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
name: {
|
|
7367
|
-
type: "positional",
|
|
7368
|
-
description: "Config name. Resolves to $MONOCEROS_HOME/container-configs/<name>.yml.",
|
|
7369
|
-
required: true
|
|
7370
|
-
},
|
|
7371
|
-
verbose: {
|
|
7372
|
-
type: "boolean",
|
|
7373
|
-
description: "Stream the raw @devcontainers/cli output to stderr instead of showing a phase spinner. Auto-enabled when stderr is not a TTY.",
|
|
7374
|
-
default: false
|
|
7375
|
-
}
|
|
7376
|
-
},
|
|
7377
|
-
run({ args }) {
|
|
7378
|
-
return dispatch(async () => {
|
|
7379
|
-
const result = await runApply({
|
|
7380
|
-
name: args.name,
|
|
7381
|
-
cliVersion: CLI_VERSION,
|
|
7382
|
-
verbose: args.verbose
|
|
7383
|
-
});
|
|
7384
|
-
return result.containerExitCode;
|
|
7385
|
-
});
|
|
7386
|
-
}
|
|
7616
|
+
if (opts.cwd) {
|
|
7617
|
+
leading.push(opts.cwd);
|
|
7618
|
+
stmts.push(`cd -- "$${leading.length}"`);
|
|
7619
|
+
}
|
|
7620
|
+
if (leading.length === 0) return command;
|
|
7621
|
+
const shift = leading.length === 1 ? "shift" : `shift ${leading.length}`;
|
|
7622
|
+
const script = `${stmts.join(" && ")} && ${shift} && exec "$@"`;
|
|
7623
|
+
return ["bash", "-lc", script, "bash", ...leading, ...command];
|
|
7624
|
+
}
|
|
7625
|
+
function openInBrowser(url) {
|
|
7626
|
+
const platform = process.platform;
|
|
7627
|
+
const [cmd, args] = platform === "darwin" ? ["open", [url]] : platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
7628
|
+
try {
|
|
7629
|
+
const child = spawn9(cmd, args, {
|
|
7630
|
+
stdio: "ignore",
|
|
7631
|
+
detached: true
|
|
7632
|
+
});
|
|
7633
|
+
child.on("error", () => {
|
|
7387
7634
|
});
|
|
7635
|
+
child.unref();
|
|
7636
|
+
} catch {
|
|
7388
7637
|
}
|
|
7389
|
-
}
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7638
|
+
}
|
|
7639
|
+
async function startBrowserBridge(opts) {
|
|
7640
|
+
const relayDir = path20.join(opts.root, RELAY_DIRNAME);
|
|
7641
|
+
const relayScript = path20.join(relayDir, "xdg-open");
|
|
7642
|
+
const urlFile = path20.join(relayDir, "url");
|
|
7643
|
+
await fsp5.mkdir(relayDir, { recursive: true });
|
|
7644
|
+
await fsp5.rm(urlFile, { force: true });
|
|
7645
|
+
await fsp5.writeFile(
|
|
7646
|
+
relayScript,
|
|
7647
|
+
`#!/bin/sh
|
|
7648
|
+
printf '%s\\n' "$1" > "$(dirname "$0")/url"
|
|
7649
|
+
exit 0
|
|
7650
|
+
`,
|
|
7651
|
+
{ mode: 493 }
|
|
7652
|
+
);
|
|
7653
|
+
await fsp5.chmod(relayScript, 493);
|
|
7654
|
+
const servers = [];
|
|
7655
|
+
let lastOpened = null;
|
|
7656
|
+
const onUrl = (url) => {
|
|
7657
|
+
openInBrowser(url);
|
|
7658
|
+
const target = parseCallbackTarget(url);
|
|
7659
|
+
if (!target) return;
|
|
7660
|
+
const server = http.createServer((req, res) => {
|
|
7661
|
+
const reqUrl = req.url ?? target.pathname;
|
|
7662
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
7663
|
+
res.end(
|
|
7664
|
+
'<html><body style="font-family:sans-serif;padding:3rem">You are signed in. You can close this tab and return to the terminal.</body></html>'
|
|
7665
|
+
);
|
|
7666
|
+
void opts.spawn(
|
|
7667
|
+
[
|
|
7668
|
+
"exec",
|
|
7669
|
+
"--workspace-folder",
|
|
7670
|
+
opts.root,
|
|
7671
|
+
"--mount-workspace-git-root=false",
|
|
7672
|
+
"curl",
|
|
7673
|
+
"-fsS",
|
|
7674
|
+
`http://localhost:${target.port}${reqUrl}`
|
|
7675
|
+
],
|
|
7676
|
+
opts.root,
|
|
7677
|
+
{ quiet: true }
|
|
7678
|
+
);
|
|
7679
|
+
});
|
|
7680
|
+
server.on("error", () => {
|
|
7681
|
+
});
|
|
7682
|
+
server.listen(target.port, "127.0.0.1");
|
|
7683
|
+
servers.push(server);
|
|
7684
|
+
};
|
|
7685
|
+
const poll = setInterval(() => {
|
|
7686
|
+
if (!existsSync11(urlFile)) return;
|
|
7687
|
+
let content = "";
|
|
7688
|
+
try {
|
|
7689
|
+
content = readFileSync5(urlFile, "utf8");
|
|
7690
|
+
} catch {
|
|
7691
|
+
return;
|
|
7692
|
+
}
|
|
7693
|
+
const url = nextRelayUrl(content, lastOpened);
|
|
7694
|
+
if (!url) return;
|
|
7695
|
+
lastOpened = url;
|
|
7696
|
+
onUrl(url);
|
|
7697
|
+
}, 250);
|
|
7698
|
+
return {
|
|
7699
|
+
relayDirInContainer: `/workspaces/${opts.name}/${RELAY_DIRNAME}`,
|
|
7700
|
+
async dispose() {
|
|
7701
|
+
clearInterval(poll);
|
|
7702
|
+
for (const s of servers) s.close();
|
|
7703
|
+
await fsp5.rm(relayDir, { recursive: true, force: true });
|
|
7704
|
+
}
|
|
7705
|
+
};
|
|
7706
|
+
}
|
|
7707
|
+
var RELAY_DIRNAME;
|
|
7708
|
+
var init_browser_bridge = __esm({
|
|
7709
|
+
"src/devcontainer/browser-bridge.ts"() {
|
|
7710
|
+
"use strict";
|
|
7711
|
+
RELAY_DIRNAME = ".monoceros-bridge";
|
|
7712
|
+
}
|
|
7713
|
+
});
|
|
7714
|
+
|
|
7715
|
+
// src/devcontainer/shell.ts
|
|
7716
|
+
import { existsSync as existsSync12 } from "fs";
|
|
7717
|
+
import path21 from "path";
|
|
7718
|
+
async function runShell(opts) {
|
|
7719
|
+
assertContainerExists(opts.root);
|
|
7720
|
+
const spawnFn = opts.spawn ?? spawnDevcontainer;
|
|
7721
|
+
const upCode = await spawnFn(
|
|
7722
|
+
["up", "--workspace-folder", opts.root, "--mount-workspace-git-root=false"],
|
|
7723
|
+
opts.root,
|
|
7724
|
+
{ quiet: true }
|
|
7725
|
+
);
|
|
7726
|
+
if (upCode !== 0) return upCode;
|
|
7727
|
+
const name = opts.name ?? path21.basename(opts.root);
|
|
7728
|
+
const isTty2 = opts.isTty ?? process.stdout.isTTY;
|
|
7729
|
+
const bridge = isTty2 ? await startBrowserBridge({ name, root: opts.root, spawn: spawnFn }) : null;
|
|
7730
|
+
try {
|
|
7731
|
+
const innerExec = wrapExec(
|
|
7732
|
+
["bash"],
|
|
7733
|
+
bridge ? { pathPrepend: bridge.relayDirInContainer } : {}
|
|
7734
|
+
);
|
|
7735
|
+
return await spawnFn(
|
|
7736
|
+
[
|
|
7737
|
+
"exec",
|
|
7738
|
+
"--workspace-folder",
|
|
7739
|
+
opts.root,
|
|
7740
|
+
"--mount-workspace-git-root=false",
|
|
7741
|
+
...innerExec
|
|
7742
|
+
],
|
|
7743
|
+
opts.root,
|
|
7744
|
+
{ interactive: true }
|
|
7745
|
+
);
|
|
7746
|
+
} finally {
|
|
7747
|
+
if (bridge) await bridge.dispose();
|
|
7748
|
+
}
|
|
7749
|
+
}
|
|
7750
|
+
function assertContainerExists(root) {
|
|
7751
|
+
if (!existsSync12(path21.join(root, ".devcontainer"))) {
|
|
7752
|
+
throw new Error(
|
|
7753
|
+
`No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
|
|
7754
|
+
);
|
|
7755
|
+
}
|
|
7756
|
+
}
|
|
7757
|
+
var init_shell = __esm({
|
|
7758
|
+
"src/devcontainer/shell.ts"() {
|
|
7759
|
+
"use strict";
|
|
7760
|
+
init_browser_bridge();
|
|
7761
|
+
init_cli();
|
|
7762
|
+
}
|
|
7763
|
+
});
|
|
7764
|
+
|
|
7765
|
+
// src/open/index.ts
|
|
7766
|
+
import { spawn as spawn10 } from "child_process";
|
|
7767
|
+
import { accessSync, constants, existsSync as existsSync13 } from "fs";
|
|
7768
|
+
import path22 from "path";
|
|
7769
|
+
import { consola as consola13 } from "consola";
|
|
7770
|
+
function resolveOnPath(bin) {
|
|
7771
|
+
const dirs = (process.env.PATH ?? "").split(path22.delimiter).filter(Boolean);
|
|
7772
|
+
for (const dir of dirs) {
|
|
7773
|
+
const candidate = path22.join(dir, bin);
|
|
7774
|
+
try {
|
|
7775
|
+
accessSync(candidate, constants.X_OK);
|
|
7776
|
+
return candidate;
|
|
7777
|
+
} catch {
|
|
7778
|
+
}
|
|
7779
|
+
}
|
|
7780
|
+
return null;
|
|
7781
|
+
}
|
|
7782
|
+
function resolveEditorBinary(tool) {
|
|
7783
|
+
if (resolveOnPath(tool.bin)) return tool.bin;
|
|
7784
|
+
if (process.platform === "darwin" && existsSync13(tool.macAppBin)) {
|
|
7785
|
+
return tool.macAppBin;
|
|
7786
|
+
}
|
|
7787
|
+
return null;
|
|
7788
|
+
}
|
|
7789
|
+
function realLaunch(bin, args) {
|
|
7790
|
+
const child = spawn10(bin, args, {
|
|
7791
|
+
detached: true,
|
|
7792
|
+
stdio: "ignore"
|
|
7793
|
+
});
|
|
7794
|
+
child.unref();
|
|
7795
|
+
}
|
|
7796
|
+
async function runOpen(opts) {
|
|
7797
|
+
const home = opts.monocerosHome ?? monocerosHome();
|
|
7798
|
+
const logger = opts.logger ?? {
|
|
7799
|
+
info: (m) => consola13.info(m),
|
|
7800
|
+
warn: (m) => consola13.warn(m)
|
|
7801
|
+
};
|
|
7802
|
+
const root = containerDir(opts.name, home);
|
|
7803
|
+
if (opts.tool === "shell") {
|
|
7804
|
+
const shellRunner = opts.shellRunner ?? runShell;
|
|
7805
|
+
return shellRunner({ root, name: opts.name });
|
|
7806
|
+
}
|
|
7807
|
+
const editor = EDITORS[opts.tool];
|
|
7808
|
+
if (!editor) {
|
|
7809
|
+
throw new Error(
|
|
7810
|
+
`Unknown tool '${opts.tool}'. Supported: ${OPEN_TOOLS.join(", ")}.`
|
|
7811
|
+
);
|
|
7812
|
+
}
|
|
7813
|
+
if (!existsSync13(sshConfigEntryPath(home, opts.name))) {
|
|
7814
|
+
throw new Error(
|
|
7815
|
+
`SSH attach isn't set up for '${opts.name}'. Run \`monoceros apply ${opts.name}\` first (needs a runtime >= 1.2.0).`
|
|
7816
|
+
);
|
|
7817
|
+
}
|
|
7818
|
+
const running = await findRunningContainerByLocalFolder(
|
|
7819
|
+
root,
|
|
7820
|
+
opts.dockerLookup ? { docker: opts.dockerLookup } : {}
|
|
7821
|
+
);
|
|
7822
|
+
if (!running) {
|
|
7823
|
+
throw new Error(
|
|
7824
|
+
`Container '${opts.name}' isn't running. Bring it up with \`monoceros apply ${opts.name}\`, then retry.`
|
|
7825
|
+
);
|
|
7826
|
+
}
|
|
7827
|
+
const resolve = opts.binResolver ?? resolveEditorBinary;
|
|
7828
|
+
const bin = resolve(editor);
|
|
7829
|
+
if (!bin) {
|
|
7830
|
+
const where = process.platform === "darwin" ? " on PATH or in /Applications" : " on PATH";
|
|
7831
|
+
throw new Error(
|
|
7832
|
+
`${editor.label} ('${editor.bin}') not found${where}. In ${editor.label}, ${editor.setupHint}.`
|
|
7833
|
+
);
|
|
7834
|
+
}
|
|
7835
|
+
const uri = `vscode-remote://ssh-remote+monoceros-${opts.name}/workspaces/${opts.name}/${opts.name}.code-workspace`;
|
|
7836
|
+
const launch = opts.launch ?? realLaunch;
|
|
7837
|
+
launch(bin, ["--file-uri", uri]);
|
|
7838
|
+
logger.info(`Opening '${opts.name}' in ${editor.label}...`);
|
|
7839
|
+
return 0;
|
|
7840
|
+
}
|
|
7841
|
+
var EDITORS, OPEN_TOOLS;
|
|
7842
|
+
var init_open = __esm({
|
|
7843
|
+
"src/open/index.ts"() {
|
|
7844
|
+
"use strict";
|
|
7845
|
+
init_paths();
|
|
7846
|
+
init_locate_running();
|
|
7847
|
+
init_shell();
|
|
7848
|
+
init_ssh_attach();
|
|
7849
|
+
EDITORS = {
|
|
7850
|
+
code: {
|
|
7851
|
+
label: "VS Code",
|
|
7852
|
+
bin: "code",
|
|
7853
|
+
macAppBin: "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
|
|
7854
|
+
setupHint: `install the Remote-SSH extension and run "Shell Command: Install 'code' command in PATH"`
|
|
7855
|
+
},
|
|
7856
|
+
codium: {
|
|
7857
|
+
label: "VS Codium",
|
|
7858
|
+
bin: "codium",
|
|
7859
|
+
macAppBin: "/Applications/VSCodium.app/Contents/Resources/app/bin/codium",
|
|
7860
|
+
setupHint: 'install the "Open Remote - SSH" extension (the codium CLI ships with the app)'
|
|
7861
|
+
}
|
|
7862
|
+
};
|
|
7863
|
+
OPEN_TOOLS = [...Object.keys(EDITORS), "shell"];
|
|
7864
|
+
}
|
|
7865
|
+
});
|
|
7866
|
+
|
|
7867
|
+
// src/version.ts
|
|
7868
|
+
var CLI_VERSION;
|
|
7869
|
+
var init_version = __esm({
|
|
7870
|
+
"src/version.ts"() {
|
|
7871
|
+
"use strict";
|
|
7872
|
+
CLI_VERSION = true ? "1.27.0" : "dev";
|
|
7873
|
+
}
|
|
7874
|
+
});
|
|
7875
|
+
|
|
7876
|
+
// src/commands/_dispatch.ts
|
|
7877
|
+
import { consola as consola14 } from "consola";
|
|
7878
|
+
async function dispatch(runner) {
|
|
7879
|
+
try {
|
|
7880
|
+
const exitCode = await runner();
|
|
7881
|
+
process.exit(exitCode);
|
|
7882
|
+
} catch (err) {
|
|
7883
|
+
consola14.error(err instanceof Error ? err.message : String(err));
|
|
7884
|
+
process.exit(1);
|
|
7885
|
+
}
|
|
7886
|
+
}
|
|
7887
|
+
var init_dispatch = __esm({
|
|
7888
|
+
"src/commands/_dispatch.ts"() {
|
|
7889
|
+
"use strict";
|
|
7890
|
+
}
|
|
7891
|
+
});
|
|
7892
|
+
|
|
7893
|
+
// src/commands/apply.ts
|
|
7894
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
7895
|
+
import { consola as consola15 } from "consola";
|
|
7896
|
+
var applyCommand;
|
|
7897
|
+
var init_apply2 = __esm({
|
|
7898
|
+
"src/commands/apply.ts"() {
|
|
7899
|
+
"use strict";
|
|
7900
|
+
init_apply();
|
|
7901
|
+
init_open();
|
|
7902
|
+
init_version();
|
|
7903
|
+
init_dispatch();
|
|
7904
|
+
applyCommand = defineCommand8({
|
|
7905
|
+
meta: {
|
|
7906
|
+
name: "apply",
|
|
7907
|
+
group: "lifecycle",
|
|
7908
|
+
description: "Materialize a container config into $MONOCEROS_HOME/container/<name>/ and bring the dev-container up. Close any VS Code Remote Containers session for the target first \u2014 the extension auto-recreates and races with apply."
|
|
7909
|
+
},
|
|
7910
|
+
args: {
|
|
7911
|
+
name: {
|
|
7912
|
+
type: "positional",
|
|
7913
|
+
description: "Config name. Resolves to $MONOCEROS_HOME/container-configs/<name>.yml.",
|
|
7914
|
+
required: true
|
|
7915
|
+
},
|
|
7916
|
+
verbose: {
|
|
7917
|
+
type: "boolean",
|
|
7918
|
+
description: "Stream the raw @devcontainers/cli output to stderr instead of showing a phase spinner. Auto-enabled when stderr is not a TTY.",
|
|
7919
|
+
default: false
|
|
7920
|
+
},
|
|
7921
|
+
open: {
|
|
7922
|
+
type: "string",
|
|
7923
|
+
description: `After a successful apply, open the container in this tool (${OPEN_TOOLS.join("|")}).`
|
|
7924
|
+
}
|
|
7925
|
+
},
|
|
7926
|
+
run({ args }) {
|
|
7927
|
+
return dispatch(async () => {
|
|
7928
|
+
const result = await runApply({
|
|
7929
|
+
name: args.name,
|
|
7930
|
+
cliVersion: CLI_VERSION,
|
|
7931
|
+
verbose: args.verbose
|
|
7932
|
+
});
|
|
7933
|
+
if (args.open && result.containerExitCode === 0) {
|
|
7934
|
+
try {
|
|
7935
|
+
await runOpen({ name: args.name, tool: args.open });
|
|
7936
|
+
} catch (err) {
|
|
7937
|
+
consola15.warn(err instanceof Error ? err.message : String(err));
|
|
7938
|
+
}
|
|
7939
|
+
}
|
|
7940
|
+
return result.containerExitCode;
|
|
7941
|
+
});
|
|
7942
|
+
}
|
|
7943
|
+
});
|
|
7944
|
+
}
|
|
7945
|
+
});
|
|
7946
|
+
|
|
7947
|
+
// src/commands/completion.ts
|
|
7948
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
7949
|
+
function renderCompletionScript(shell) {
|
|
7950
|
+
if (shell === "bash") {
|
|
7951
|
+
return [
|
|
7952
|
+
"# bash completion for monoceros",
|
|
7953
|
+
"# install: source this file from .bashrc, e.g.",
|
|
7954
|
+
"# monoceros completion bash > ~/.bash_completion.d/monoceros",
|
|
7955
|
+
'# echo "source ~/.bash_completion.d/monoceros" >> ~/.bashrc',
|
|
7956
|
+
"#",
|
|
7957
|
+
"# The work is done by `monoceros __complete --line --point`; this",
|
|
7958
|
+
"# shell wrapper only forwards the cursor view.",
|
|
7959
|
+
"",
|
|
7960
|
+
"_monoceros() {",
|
|
7961
|
+
" local IFS=$'\\n'",
|
|
7962
|
+
" local candidates",
|
|
7963
|
+
' candidates=$(monoceros __complete --line "$COMP_LINE" --point "$COMP_POINT" 2>/dev/null)',
|
|
7408
7964
|
' local cur="${COMP_WORDS[COMP_CWORD]}"',
|
|
7409
7965
|
' COMPREPLY=( $(compgen -W "$candidates" -- "$cur") )',
|
|
7410
7966
|
" # Suppress the trailing space when bash narrowed the candidate",
|
|
@@ -7516,8 +8072,8 @@ var init_completion = __esm({
|
|
|
7516
8072
|
});
|
|
7517
8073
|
|
|
7518
8074
|
// src/completion/resolve.ts
|
|
7519
|
-
import { existsSync as
|
|
7520
|
-
import
|
|
8075
|
+
import { existsSync as existsSync14, promises as fs14 } from "fs";
|
|
8076
|
+
import path23 from "path";
|
|
7521
8077
|
async function resolveCompletions(line, point, opts = {}) {
|
|
7522
8078
|
const { prev, current } = parseCompletionLine(line, point);
|
|
7523
8079
|
const ctx = { prev, current, opts };
|
|
@@ -7666,9 +8222,9 @@ function filterPrefix(values, fragment) {
|
|
|
7666
8222
|
}
|
|
7667
8223
|
async function listContainerNames(ctx) {
|
|
7668
8224
|
const home = ctx.opts.monocerosHome ?? monocerosHome();
|
|
7669
|
-
const dir =
|
|
7670
|
-
if (!
|
|
7671
|
-
const entries = await
|
|
8225
|
+
const dir = path23.join(home, "container-configs");
|
|
8226
|
+
if (!existsSync14(dir)) return [];
|
|
8227
|
+
const entries = await fs14.readdir(dir);
|
|
7672
8228
|
return entries.filter((e) => e.endsWith(".yml")).map((e) => e.slice(0, -".yml".length)).sort();
|
|
7673
8229
|
}
|
|
7674
8230
|
async function listFeatureComponents() {
|
|
@@ -7742,10 +8298,12 @@ var init_resolve = __esm({
|
|
|
7742
8298
|
init_manifest();
|
|
7743
8299
|
init_catalog();
|
|
7744
8300
|
init_schema();
|
|
8301
|
+
init_open();
|
|
7745
8302
|
ALL_COMMANDS = [
|
|
7746
8303
|
"init",
|
|
7747
8304
|
"list-components",
|
|
7748
8305
|
"shell",
|
|
8306
|
+
"open",
|
|
7749
8307
|
"run",
|
|
7750
8308
|
"logs",
|
|
7751
8309
|
"start",
|
|
@@ -7795,7 +8353,10 @@ var init_resolve = __esm({
|
|
|
7795
8353
|
},
|
|
7796
8354
|
apply: {
|
|
7797
8355
|
positionals: [containerName],
|
|
7798
|
-
flags: {
|
|
8356
|
+
flags: {
|
|
8357
|
+
"--yes": { type: "boolean", aliases: ["-y"] },
|
|
8358
|
+
"--open": { type: "value", values: () => [...OPEN_TOOLS] }
|
|
8359
|
+
}
|
|
7799
8360
|
},
|
|
7800
8361
|
upgrade: {
|
|
7801
8362
|
// First positional is a container name; the second is a version
|
|
@@ -7811,12 +8372,16 @@ var init_resolve = __esm({
|
|
|
7811
8372
|
}
|
|
7812
8373
|
},
|
|
7813
8374
|
shell: { positionals: [containerName] },
|
|
8375
|
+
open: { positionals: [containerName, () => [...OPEN_TOOLS]] },
|
|
7814
8376
|
run: {
|
|
7815
8377
|
positionals: [containerName],
|
|
7816
8378
|
flags: { "--in": { type: "value" } }
|
|
7817
8379
|
},
|
|
7818
8380
|
logs: { positionals: [containerName] },
|
|
7819
|
-
start: {
|
|
8381
|
+
start: {
|
|
8382
|
+
positionals: [containerName],
|
|
8383
|
+
flags: { "--open": { type: "value", values: () => [...OPEN_TOOLS] } }
|
|
8384
|
+
},
|
|
7820
8385
|
stop: { positionals: [containerName] },
|
|
7821
8386
|
status: { positionals: [containerName] },
|
|
7822
8387
|
"add-language": {
|
|
@@ -8335,14 +8900,14 @@ var init_generator = __esm({
|
|
|
8335
8900
|
});
|
|
8336
8901
|
|
|
8337
8902
|
// src/init/index.ts
|
|
8338
|
-
import { existsSync as
|
|
8339
|
-
import
|
|
8340
|
-
import { consola as
|
|
8903
|
+
import { existsSync as existsSync15, promises as fs15 } from "fs";
|
|
8904
|
+
import path24 from "path";
|
|
8905
|
+
import { consola as consola16 } from "consola";
|
|
8341
8906
|
async function runInit(opts) {
|
|
8342
8907
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
8343
8908
|
const logger = opts.logger ?? {
|
|
8344
|
-
success: (msg) =>
|
|
8345
|
-
info: (msg) =>
|
|
8909
|
+
success: (msg) => consola16.success(msg),
|
|
8910
|
+
info: (msg) => consola16.info(msg)
|
|
8346
8911
|
};
|
|
8347
8912
|
if (!REGEX.solutionName.test(opts.name)) {
|
|
8348
8913
|
throw new Error(
|
|
@@ -8350,12 +8915,12 @@ async function runInit(opts) {
|
|
|
8350
8915
|
);
|
|
8351
8916
|
}
|
|
8352
8917
|
const dest = containerConfigPath(opts.name, home);
|
|
8353
|
-
if (
|
|
8918
|
+
if (existsSync15(dest)) {
|
|
8354
8919
|
throw new Error(
|
|
8355
8920
|
`Config already exists: ${dest}. Delete it manually before re-running \`monoceros init\` \u2014 this protects any hand-edits.`
|
|
8356
8921
|
);
|
|
8357
8922
|
}
|
|
8358
|
-
const componentsRoot = opts.workbenchRoot ?
|
|
8923
|
+
const componentsRoot = opts.workbenchRoot ? path24.join(opts.workbenchRoot, "components") : componentsRootDir();
|
|
8359
8924
|
const catalog = await loadComponentCatalog(componentsRoot);
|
|
8360
8925
|
if (catalog.size === 0) {
|
|
8361
8926
|
throw new Error(
|
|
@@ -8421,9 +8986,9 @@ async function runInit(opts) {
|
|
|
8421
8986
|
} else {
|
|
8422
8987
|
text = generateComposedYml(opts.name, composed, lookup, repos, ports);
|
|
8423
8988
|
}
|
|
8424
|
-
await
|
|
8989
|
+
await fs15.mkdir(containerConfigsDir(home), { recursive: true });
|
|
8425
8990
|
await ensureEnvGitignored(containerConfigsDir(home));
|
|
8426
|
-
await
|
|
8991
|
+
await fs15.writeFile(dest, text, "utf8");
|
|
8427
8992
|
const envPath = containerEnvPath(opts.name, home);
|
|
8428
8993
|
const seedVars = {};
|
|
8429
8994
|
for (const f of composed.features) {
|
|
@@ -8446,8 +9011,8 @@ async function runInit(opts) {
|
|
|
8446
9011
|
}
|
|
8447
9012
|
await ensureEnvVars(envPath, opts.name, seedVars);
|
|
8448
9013
|
const documented = !anyComposed;
|
|
8449
|
-
const ymlRel =
|
|
8450
|
-
const envRel =
|
|
9014
|
+
const ymlRel = path24.relative(home, dest);
|
|
9015
|
+
const envRel = path24.relative(home, envPath);
|
|
8451
9016
|
if (documented) {
|
|
8452
9017
|
logger.success(`Wrote documented default to ${ymlRel} and ${envRel}.`);
|
|
8453
9018
|
logger.info(
|
|
@@ -8591,7 +9156,7 @@ var init_init = __esm({
|
|
|
8591
9156
|
|
|
8592
9157
|
// src/commands/init.ts
|
|
8593
9158
|
import { defineCommand as defineCommand11 } from "citty";
|
|
8594
|
-
import { consola as
|
|
9159
|
+
import { consola as consola17 } from "consola";
|
|
8595
9160
|
function collectListFlag(flag, rawArgs) {
|
|
8596
9161
|
const eq = `${flag}=`;
|
|
8597
9162
|
const pieces = [];
|
|
@@ -8717,7 +9282,7 @@ var init_init2 = __esm({
|
|
|
8717
9282
|
...ports && ports.length > 0 ? { withPorts: ports } : {}
|
|
8718
9283
|
});
|
|
8719
9284
|
} catch (err) {
|
|
8720
|
-
|
|
9285
|
+
consola17.error(err instanceof Error ? err.message : String(err));
|
|
8721
9286
|
process.exit(1);
|
|
8722
9287
|
}
|
|
8723
9288
|
}
|
|
@@ -8766,7 +9331,7 @@ var init_expand = __esm({
|
|
|
8766
9331
|
|
|
8767
9332
|
// src/commands/list-components.ts
|
|
8768
9333
|
import { defineCommand as defineCommand12 } from "citty";
|
|
8769
|
-
import { consola as
|
|
9334
|
+
import { consola as consola18 } from "consola";
|
|
8770
9335
|
var CATEGORY_LABELS, CATEGORY_ORDER, listComponentsCommand;
|
|
8771
9336
|
var init_list_components = __esm({
|
|
8772
9337
|
"src/commands/list-components.ts"() {
|
|
@@ -8795,7 +9360,7 @@ var init_list_components = __esm({
|
|
|
8795
9360
|
try {
|
|
8796
9361
|
const catalog = expandSelectable(await loadDescriptorCatalog());
|
|
8797
9362
|
if (catalog.size === 0) {
|
|
8798
|
-
|
|
9363
|
+
consola18.warn(
|
|
8799
9364
|
"No components found. The workbench checkout looks incomplete."
|
|
8800
9365
|
);
|
|
8801
9366
|
process.exit(0);
|
|
@@ -8846,7 +9411,7 @@ var init_list_components = __esm({
|
|
|
8846
9411
|
}
|
|
8847
9412
|
process.exit(0);
|
|
8848
9413
|
} catch (err) {
|
|
8849
|
-
|
|
9414
|
+
consola18.error(err instanceof Error ? err.message : String(err));
|
|
8850
9415
|
process.exit(1);
|
|
8851
9416
|
}
|
|
8852
9417
|
}
|
|
@@ -8855,14 +9420,14 @@ var init_list_components = __esm({
|
|
|
8855
9420
|
});
|
|
8856
9421
|
|
|
8857
9422
|
// src/commands/logs.ts
|
|
8858
|
-
import { spawn as
|
|
8859
|
-
import { existsSync as
|
|
8860
|
-
import
|
|
9423
|
+
import { spawn as spawn11 } from "child_process";
|
|
9424
|
+
import { existsSync as existsSync16 } from "fs";
|
|
9425
|
+
import path25 from "path";
|
|
8861
9426
|
import { defineCommand as defineCommand13 } from "citty";
|
|
8862
9427
|
function tailLogFile(file, follow) {
|
|
8863
9428
|
const [cmd, args] = follow ? ["tail", ["-F", file]] : ["cat", [file]];
|
|
8864
9429
|
return new Promise((resolve, reject) => {
|
|
8865
|
-
const child =
|
|
9430
|
+
const child = spawn11(cmd, args, { stdio: "inherit" });
|
|
8866
9431
|
child.on("error", reject);
|
|
8867
9432
|
child.on("exit", (code) => resolve(code ?? 0));
|
|
8868
9433
|
});
|
|
@@ -8900,8 +9465,8 @@ var init_logs = __esm({
|
|
|
8900
9465
|
run({ args }) {
|
|
8901
9466
|
const service = typeof args.service === "string" ? args.service : void 0;
|
|
8902
9467
|
if (service) {
|
|
8903
|
-
const logFile =
|
|
8904
|
-
if (
|
|
9468
|
+
const logFile = path25.join(containerLogsDir(args.name), `${service}.log`);
|
|
9469
|
+
if (existsSync16(logFile)) {
|
|
8905
9470
|
return dispatch(() => tailLogFile(logFile, args.follow));
|
|
8906
9471
|
}
|
|
8907
9472
|
}
|
|
@@ -8917,12 +9482,45 @@ var init_logs = __esm({
|
|
|
8917
9482
|
}
|
|
8918
9483
|
});
|
|
8919
9484
|
|
|
8920
|
-
// src/commands/
|
|
9485
|
+
// src/commands/open.ts
|
|
8921
9486
|
import { defineCommand as defineCommand14 } from "citty";
|
|
8922
|
-
|
|
9487
|
+
var openCommand;
|
|
9488
|
+
var init_open2 = __esm({
|
|
9489
|
+
"src/commands/open.ts"() {
|
|
9490
|
+
"use strict";
|
|
9491
|
+
init_open();
|
|
9492
|
+
init_dispatch();
|
|
9493
|
+
openCommand = defineCommand14({
|
|
9494
|
+
meta: {
|
|
9495
|
+
name: "open",
|
|
9496
|
+
group: "run",
|
|
9497
|
+
description: `Attach an editor to the named dev-container over SSH, or drop into a shell. Tools: ${OPEN_TOOLS.join(", ")}. The container must be applied (runtime >= 1.2.0) and running.`
|
|
9498
|
+
},
|
|
9499
|
+
args: {
|
|
9500
|
+
name: {
|
|
9501
|
+
type: "positional",
|
|
9502
|
+
description: "Container name (yml in $MONOCEROS_HOME/container-configs/).",
|
|
9503
|
+
required: true
|
|
9504
|
+
},
|
|
9505
|
+
tool: {
|
|
9506
|
+
type: "positional",
|
|
9507
|
+
description: `What to open it in: ${OPEN_TOOLS.join(", ")}.`,
|
|
9508
|
+
required: true
|
|
9509
|
+
}
|
|
9510
|
+
},
|
|
9511
|
+
run({ args }) {
|
|
9512
|
+
return dispatch(() => runOpen({ name: args.name, tool: args.tool }));
|
|
9513
|
+
}
|
|
9514
|
+
});
|
|
9515
|
+
}
|
|
9516
|
+
});
|
|
9517
|
+
|
|
9518
|
+
// src/commands/port.ts
|
|
9519
|
+
import { defineCommand as defineCommand15 } from "citty";
|
|
9520
|
+
import { consola as consola19 } from "consola";
|
|
8923
9521
|
async function runPortListing(opts) {
|
|
8924
9522
|
const out = opts.out ?? process.stdout;
|
|
8925
|
-
const info = opts.info ?? ((m) =>
|
|
9523
|
+
const info = opts.info ?? ((m) => consola19.info(m));
|
|
8926
9524
|
const parsed = await readConfig(
|
|
8927
9525
|
containerConfigPath(opts.name, opts.monocerosHome)
|
|
8928
9526
|
);
|
|
@@ -8980,7 +9578,7 @@ var init_port = __esm({
|
|
|
8980
9578
|
init_schema();
|
|
8981
9579
|
init_dynamic();
|
|
8982
9580
|
init_format();
|
|
8983
|
-
portCommand =
|
|
9581
|
+
portCommand = defineCommand15({
|
|
8984
9582
|
meta: {
|
|
8985
9583
|
name: "port",
|
|
8986
9584
|
group: "discovery",
|
|
@@ -8998,7 +9596,7 @@ var init_port = __esm({
|
|
|
8998
9596
|
const code = await runPortListing({ name: args.name });
|
|
8999
9597
|
process.exit(code);
|
|
9000
9598
|
} catch (err) {
|
|
9001
|
-
|
|
9599
|
+
consola19.error(err instanceof Error ? err.message : String(err));
|
|
9002
9600
|
process.exit(1);
|
|
9003
9601
|
}
|
|
9004
9602
|
}
|
|
@@ -9007,15 +9605,15 @@ var init_port = __esm({
|
|
|
9007
9605
|
});
|
|
9008
9606
|
|
|
9009
9607
|
// src/commands/remove-apt-packages.ts
|
|
9010
|
-
import { defineCommand as
|
|
9011
|
-
import { consola as
|
|
9608
|
+
import { defineCommand as defineCommand16 } from "citty";
|
|
9609
|
+
import { consola as consola20 } from "consola";
|
|
9012
9610
|
var removeAptPackagesCommand;
|
|
9013
9611
|
var init_remove_apt_packages = __esm({
|
|
9014
9612
|
"src/commands/remove-apt-packages.ts"() {
|
|
9015
9613
|
"use strict";
|
|
9016
9614
|
init_inner_args();
|
|
9017
9615
|
init_modify();
|
|
9018
|
-
removeAptPackagesCommand =
|
|
9616
|
+
removeAptPackagesCommand = defineCommand16({
|
|
9019
9617
|
meta: {
|
|
9020
9618
|
name: "remove-apt-packages",
|
|
9021
9619
|
group: "edit",
|
|
@@ -9037,7 +9635,7 @@ var init_remove_apt_packages = __esm({
|
|
|
9037
9635
|
async run({ args }) {
|
|
9038
9636
|
const packages = [...getInnerArgs()];
|
|
9039
9637
|
if (packages.length === 0) {
|
|
9040
|
-
|
|
9638
|
+
consola20.error(
|
|
9041
9639
|
"No package names given. Usage: `monoceros remove-apt-packages <containername> [--yes] -- <pkg> [<pkg> \u2026]`."
|
|
9042
9640
|
);
|
|
9043
9641
|
process.exit(1);
|
|
@@ -9050,7 +9648,7 @@ var init_remove_apt_packages = __esm({
|
|
|
9050
9648
|
});
|
|
9051
9649
|
process.exit(result.status === "aborted" ? 1 : 0);
|
|
9052
9650
|
} catch (err) {
|
|
9053
|
-
|
|
9651
|
+
consola20.error(err instanceof Error ? err.message : String(err));
|
|
9054
9652
|
process.exit(1);
|
|
9055
9653
|
}
|
|
9056
9654
|
}
|
|
@@ -9059,14 +9657,14 @@ var init_remove_apt_packages = __esm({
|
|
|
9059
9657
|
});
|
|
9060
9658
|
|
|
9061
9659
|
// src/commands/remove-feature.ts
|
|
9062
|
-
import { defineCommand as
|
|
9063
|
-
import { consola as
|
|
9660
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
9661
|
+
import { consola as consola21 } from "consola";
|
|
9064
9662
|
var removeFeatureCommand;
|
|
9065
9663
|
var init_remove_feature = __esm({
|
|
9066
9664
|
"src/commands/remove-feature.ts"() {
|
|
9067
9665
|
"use strict";
|
|
9068
9666
|
init_modify();
|
|
9069
|
-
removeFeatureCommand =
|
|
9667
|
+
removeFeatureCommand = defineCommand17({
|
|
9070
9668
|
meta: {
|
|
9071
9669
|
name: "remove-feature",
|
|
9072
9670
|
group: "edit",
|
|
@@ -9099,7 +9697,7 @@ var init_remove_feature = __esm({
|
|
|
9099
9697
|
});
|
|
9100
9698
|
process.exit(result.status === "aborted" ? 1 : 0);
|
|
9101
9699
|
} catch (err) {
|
|
9102
|
-
|
|
9700
|
+
consola21.error(err instanceof Error ? err.message : String(err));
|
|
9103
9701
|
process.exit(1);
|
|
9104
9702
|
}
|
|
9105
9703
|
}
|
|
@@ -9108,15 +9706,15 @@ var init_remove_feature = __esm({
|
|
|
9108
9706
|
});
|
|
9109
9707
|
|
|
9110
9708
|
// src/remove/index.ts
|
|
9111
|
-
import { existsSync as
|
|
9112
|
-
import
|
|
9113
|
-
import { consola as
|
|
9709
|
+
import { existsSync as existsSync17, promises as fs16 } from "fs";
|
|
9710
|
+
import path26 from "path";
|
|
9711
|
+
import { consola as consola22 } from "consola";
|
|
9114
9712
|
async function runRemove(opts) {
|
|
9115
9713
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
9116
9714
|
const logger = opts.logger ?? {
|
|
9117
|
-
info: (msg) =>
|
|
9118
|
-
success: (msg) =>
|
|
9119
|
-
warn: (msg) =>
|
|
9715
|
+
info: (msg) => consola22.info(msg),
|
|
9716
|
+
success: (msg) => consola22.success(msg),
|
|
9717
|
+
warn: (msg) => consola22.warn(msg)
|
|
9120
9718
|
};
|
|
9121
9719
|
if (!REGEX.solutionName.test(opts.name)) {
|
|
9122
9720
|
throw new Error(
|
|
@@ -9126,9 +9724,9 @@ async function runRemove(opts) {
|
|
|
9126
9724
|
const ymlPath = containerConfigPath(opts.name, home);
|
|
9127
9725
|
const envPath = containerEnvPath(opts.name, home);
|
|
9128
9726
|
const containerPath = containerDir(opts.name, home);
|
|
9129
|
-
const hasYml =
|
|
9130
|
-
const hasEnv =
|
|
9131
|
-
const hasContainer =
|
|
9727
|
+
const hasYml = existsSync17(ymlPath);
|
|
9728
|
+
const hasEnv = existsSync17(envPath);
|
|
9729
|
+
const hasContainer = existsSync17(containerPath);
|
|
9132
9730
|
if (!hasYml && !hasContainer) {
|
|
9133
9731
|
throw new Error(
|
|
9134
9732
|
`Nothing to remove for '${opts.name}': neither ${ymlPath} nor ${containerPath} exists.`
|
|
@@ -9154,30 +9752,30 @@ async function runRemove(opts) {
|
|
|
9154
9752
|
let backupPath = null;
|
|
9155
9753
|
if (!opts.noBackup && (hasYml || hasContainer)) {
|
|
9156
9754
|
const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9157
|
-
backupPath =
|
|
9158
|
-
await
|
|
9755
|
+
backupPath = path26.join(home, "container-backups", `${opts.name}-${ts}`);
|
|
9756
|
+
await fs16.mkdir(backupPath, { recursive: true });
|
|
9159
9757
|
if (hasYml) {
|
|
9160
|
-
await
|
|
9758
|
+
await fs16.copyFile(ymlPath, path26.join(backupPath, `${opts.name}.yml`));
|
|
9161
9759
|
}
|
|
9162
9760
|
if (hasEnv) {
|
|
9163
|
-
await
|
|
9761
|
+
await fs16.copyFile(envPath, path26.join(backupPath, `${opts.name}.env`));
|
|
9164
9762
|
}
|
|
9165
9763
|
if (hasContainer) {
|
|
9166
|
-
await
|
|
9764
|
+
await fs16.cp(containerPath, path26.join(backupPath, "container"), {
|
|
9167
9765
|
recursive: true
|
|
9168
9766
|
});
|
|
9169
9767
|
}
|
|
9170
9768
|
logger.info(`Backup written to ${prettyPath(backupPath)}.`);
|
|
9171
9769
|
}
|
|
9172
9770
|
if (hasYml) {
|
|
9173
|
-
await
|
|
9771
|
+
await fs16.rm(ymlPath, { force: true });
|
|
9174
9772
|
}
|
|
9175
9773
|
if (hasEnv) {
|
|
9176
|
-
await
|
|
9774
|
+
await fs16.rm(envPath, { force: true });
|
|
9177
9775
|
}
|
|
9178
9776
|
if (hasContainer) {
|
|
9179
9777
|
try {
|
|
9180
|
-
await
|
|
9778
|
+
await fs16.rm(containerPath, { recursive: true, force: true });
|
|
9181
9779
|
} catch (err) {
|
|
9182
9780
|
const code = err.code;
|
|
9183
9781
|
if (code !== "EACCES" && code !== "EPERM") {
|
|
@@ -9199,7 +9797,7 @@ async function runRemove(opts) {
|
|
|
9199
9797
|
"-delete"
|
|
9200
9798
|
]);
|
|
9201
9799
|
try {
|
|
9202
|
-
await
|
|
9800
|
+
await fs16.rm(containerPath, { recursive: true, force: true });
|
|
9203
9801
|
} catch (err2) {
|
|
9204
9802
|
const code2 = err2.code;
|
|
9205
9803
|
throw new Error(
|
|
@@ -9213,7 +9811,14 @@ async function runRemove(opts) {
|
|
|
9213
9811
|
);
|
|
9214
9812
|
if (!backupPath) {
|
|
9215
9813
|
logger.warn?.(
|
|
9216
|
-
"No backup created (--no-backup). The host-side state is gone for good."
|
|
9814
|
+
"No backup created (--no-backup). The host-side state is gone for good."
|
|
9815
|
+
);
|
|
9816
|
+
}
|
|
9817
|
+
try {
|
|
9818
|
+
await removeSshAttach(home, opts.name);
|
|
9819
|
+
} catch (err) {
|
|
9820
|
+
logger.warn?.(
|
|
9821
|
+
`Could not remove SSH attach config for ${opts.name}: ${err instanceof Error ? err.message : String(err)}. Ignored.`
|
|
9217
9822
|
);
|
|
9218
9823
|
}
|
|
9219
9824
|
try {
|
|
@@ -9248,21 +9853,22 @@ var init_remove = __esm({
|
|
|
9248
9853
|
init_schema();
|
|
9249
9854
|
init_compose();
|
|
9250
9855
|
init_scaffold();
|
|
9856
|
+
init_ssh_attach();
|
|
9251
9857
|
init_proxy();
|
|
9252
9858
|
init_dynamic();
|
|
9253
9859
|
}
|
|
9254
9860
|
});
|
|
9255
9861
|
|
|
9256
9862
|
// src/commands/remove.ts
|
|
9257
|
-
import { defineCommand as
|
|
9258
|
-
import { consola as
|
|
9863
|
+
import { defineCommand as defineCommand18 } from "citty";
|
|
9864
|
+
import { consola as consola23 } from "consola";
|
|
9259
9865
|
import { createInterface } from "readline/promises";
|
|
9260
9866
|
var removeCommand;
|
|
9261
9867
|
var init_remove2 = __esm({
|
|
9262
9868
|
"src/commands/remove.ts"() {
|
|
9263
9869
|
"use strict";
|
|
9264
9870
|
init_remove();
|
|
9265
|
-
removeCommand =
|
|
9871
|
+
removeCommand = defineCommand18({
|
|
9266
9872
|
meta: {
|
|
9267
9873
|
name: "remove",
|
|
9268
9874
|
group: "lifecycle",
|
|
@@ -9299,7 +9905,7 @@ var init_remove2 = __esm({
|
|
|
9299
9905
|
const skipPrompt = args.yes === true;
|
|
9300
9906
|
if (!skipPrompt) {
|
|
9301
9907
|
const warning = noBackup ? `About to remove '${args.name}' WITHOUT a backup. Docker objects, container-configs entry, and container directory will all be deleted.` : `About to remove '${args.name}'. A backup will be written to container-backups/ first, then docker objects, container-configs entry, and container directory will all be deleted.`;
|
|
9302
|
-
|
|
9908
|
+
consola23.warn(warning);
|
|
9303
9909
|
const rl = createInterface({
|
|
9304
9910
|
input: process.stdin,
|
|
9305
9911
|
output: process.stdout
|
|
@@ -9307,7 +9913,7 @@ var init_remove2 = __esm({
|
|
|
9307
9913
|
const answer = await rl.question("Continue? [y/N] ");
|
|
9308
9914
|
rl.close();
|
|
9309
9915
|
if (!/^y(es)?$/i.test(answer.trim())) {
|
|
9310
|
-
|
|
9916
|
+
consola23.info("Aborted. Nothing changed.");
|
|
9311
9917
|
process.exit(0);
|
|
9312
9918
|
}
|
|
9313
9919
|
}
|
|
@@ -9316,7 +9922,7 @@ var init_remove2 = __esm({
|
|
|
9316
9922
|
...noBackup ? { noBackup: true } : {}
|
|
9317
9923
|
});
|
|
9318
9924
|
} catch (err) {
|
|
9319
|
-
|
|
9925
|
+
consola23.error(err instanceof Error ? err.message : String(err));
|
|
9320
9926
|
process.exit(1);
|
|
9321
9927
|
}
|
|
9322
9928
|
}
|
|
@@ -9325,24 +9931,24 @@ var init_remove2 = __esm({
|
|
|
9325
9931
|
});
|
|
9326
9932
|
|
|
9327
9933
|
// src/restore/index.ts
|
|
9328
|
-
import { existsSync as
|
|
9329
|
-
import
|
|
9330
|
-
import { consola as
|
|
9934
|
+
import { existsSync as existsSync18, promises as fs17 } from "fs";
|
|
9935
|
+
import path27 from "path";
|
|
9936
|
+
import { consola as consola24 } from "consola";
|
|
9331
9937
|
async function runRestore(opts) {
|
|
9332
9938
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
9333
9939
|
const logger = opts.logger ?? {
|
|
9334
|
-
info: (msg) =>
|
|
9335
|
-
success: (msg) =>
|
|
9940
|
+
info: (msg) => consola24.info(msg),
|
|
9941
|
+
success: (msg) => consola24.success(msg)
|
|
9336
9942
|
};
|
|
9337
|
-
const backup =
|
|
9338
|
-
if (!
|
|
9943
|
+
const backup = path27.resolve(opts.backupPath);
|
|
9944
|
+
if (!existsSync18(backup)) {
|
|
9339
9945
|
throw new Error(`Backup not found: ${backup}.`);
|
|
9340
9946
|
}
|
|
9341
|
-
const stat = await
|
|
9947
|
+
const stat = await fs17.stat(backup);
|
|
9342
9948
|
if (!stat.isDirectory()) {
|
|
9343
9949
|
throw new Error(`Backup path is not a directory: ${backup}.`);
|
|
9344
9950
|
}
|
|
9345
|
-
const entries = await
|
|
9951
|
+
const entries = await fs17.readdir(backup);
|
|
9346
9952
|
const ymlFiles = entries.filter((f) => f.endsWith(".yml"));
|
|
9347
9953
|
if (ymlFiles.length === 0) {
|
|
9348
9954
|
throw new Error(
|
|
@@ -9356,29 +9962,29 @@ async function runRestore(opts) {
|
|
|
9356
9962
|
}
|
|
9357
9963
|
const ymlFile = ymlFiles[0];
|
|
9358
9964
|
const name = ymlFile.replace(/\.yml$/, "");
|
|
9359
|
-
const containerInBackup =
|
|
9360
|
-
const hasContainer =
|
|
9361
|
-
const envInBackup =
|
|
9362
|
-
const hasEnv =
|
|
9965
|
+
const containerInBackup = path27.join(backup, "container");
|
|
9966
|
+
const hasContainer = existsSync18(containerInBackup);
|
|
9967
|
+
const envInBackup = path27.join(backup, `${name}.env`);
|
|
9968
|
+
const hasEnv = existsSync18(envInBackup);
|
|
9363
9969
|
const destYml = containerConfigPath(name, home);
|
|
9364
9970
|
const destContainer = containerDir(name, home);
|
|
9365
|
-
if (
|
|
9971
|
+
if (existsSync18(destYml)) {
|
|
9366
9972
|
throw new Error(
|
|
9367
9973
|
`Refusing to restore: ${destYml} already exists. Remove the current container first (\`monoceros remove ${name}\`) or rename the existing config.`
|
|
9368
9974
|
);
|
|
9369
9975
|
}
|
|
9370
|
-
if (hasContainer &&
|
|
9976
|
+
if (hasContainer && existsSync18(destContainer)) {
|
|
9371
9977
|
throw new Error(
|
|
9372
9978
|
`Refusing to restore: ${destContainer} already exists. Remove the current container first (\`monoceros remove ${name}\`).`
|
|
9373
9979
|
);
|
|
9374
9980
|
}
|
|
9375
|
-
await
|
|
9376
|
-
await
|
|
9981
|
+
await fs17.mkdir(containerConfigsDir(home), { recursive: true });
|
|
9982
|
+
await fs17.copyFile(path27.join(backup, ymlFile), destYml);
|
|
9377
9983
|
if (hasEnv) {
|
|
9378
|
-
await
|
|
9984
|
+
await fs17.copyFile(envInBackup, containerEnvPath(name, home));
|
|
9379
9985
|
}
|
|
9380
9986
|
if (hasContainer) {
|
|
9381
|
-
await
|
|
9987
|
+
await fs17.cp(containerInBackup, destContainer, { recursive: true });
|
|
9382
9988
|
}
|
|
9383
9989
|
logger.success(`Restored '${name}' from ${prettyPath(backup)}.`);
|
|
9384
9990
|
logger.info(
|
|
@@ -9398,14 +10004,14 @@ var init_restore = __esm({
|
|
|
9398
10004
|
});
|
|
9399
10005
|
|
|
9400
10006
|
// src/commands/restore.ts
|
|
9401
|
-
import { defineCommand as
|
|
9402
|
-
import { consola as
|
|
10007
|
+
import { defineCommand as defineCommand19 } from "citty";
|
|
10008
|
+
import { consola as consola25 } from "consola";
|
|
9403
10009
|
var restoreCommand;
|
|
9404
10010
|
var init_restore2 = __esm({
|
|
9405
10011
|
"src/commands/restore.ts"() {
|
|
9406
10012
|
"use strict";
|
|
9407
10013
|
init_restore();
|
|
9408
|
-
restoreCommand =
|
|
10014
|
+
restoreCommand = defineCommand19({
|
|
9409
10015
|
meta: {
|
|
9410
10016
|
name: "restore",
|
|
9411
10017
|
group: "lifecycle",
|
|
@@ -9422,7 +10028,7 @@ var init_restore2 = __esm({
|
|
|
9422
10028
|
try {
|
|
9423
10029
|
await runRestore({ backupPath: args["backup-path"] });
|
|
9424
10030
|
} catch (err) {
|
|
9425
|
-
|
|
10031
|
+
consola25.error(err instanceof Error ? err.message : String(err));
|
|
9426
10032
|
process.exit(1);
|
|
9427
10033
|
}
|
|
9428
10034
|
}
|
|
@@ -9431,14 +10037,14 @@ var init_restore2 = __esm({
|
|
|
9431
10037
|
});
|
|
9432
10038
|
|
|
9433
10039
|
// src/commands/remove-from-url.ts
|
|
9434
|
-
import { defineCommand as
|
|
9435
|
-
import { consola as
|
|
10040
|
+
import { defineCommand as defineCommand20 } from "citty";
|
|
10041
|
+
import { consola as consola26 } from "consola";
|
|
9436
10042
|
var removeFromUrlCommand;
|
|
9437
10043
|
var init_remove_from_url = __esm({
|
|
9438
10044
|
"src/commands/remove-from-url.ts"() {
|
|
9439
10045
|
"use strict";
|
|
9440
10046
|
init_modify();
|
|
9441
|
-
removeFromUrlCommand =
|
|
10047
|
+
removeFromUrlCommand = defineCommand20({
|
|
9442
10048
|
meta: {
|
|
9443
10049
|
name: "remove-from-url",
|
|
9444
10050
|
group: "edit",
|
|
@@ -9471,7 +10077,7 @@ var init_remove_from_url = __esm({
|
|
|
9471
10077
|
});
|
|
9472
10078
|
process.exit(result.status === "aborted" ? 1 : 0);
|
|
9473
10079
|
} catch (err) {
|
|
9474
|
-
|
|
10080
|
+
consola26.error(err instanceof Error ? err.message : String(err));
|
|
9475
10081
|
process.exit(1);
|
|
9476
10082
|
}
|
|
9477
10083
|
}
|
|
@@ -9480,14 +10086,14 @@ var init_remove_from_url = __esm({
|
|
|
9480
10086
|
});
|
|
9481
10087
|
|
|
9482
10088
|
// src/commands/remove-language.ts
|
|
9483
|
-
import { defineCommand as
|
|
9484
|
-
import { consola as
|
|
10089
|
+
import { defineCommand as defineCommand21 } from "citty";
|
|
10090
|
+
import { consola as consola27 } from "consola";
|
|
9485
10091
|
var removeLanguageCommand;
|
|
9486
10092
|
var init_remove_language = __esm({
|
|
9487
10093
|
"src/commands/remove-language.ts"() {
|
|
9488
10094
|
"use strict";
|
|
9489
10095
|
init_modify();
|
|
9490
|
-
removeLanguageCommand =
|
|
10096
|
+
removeLanguageCommand = defineCommand21({
|
|
9491
10097
|
meta: {
|
|
9492
10098
|
name: "remove-language",
|
|
9493
10099
|
group: "edit",
|
|
@@ -9520,7 +10126,7 @@ var init_remove_language = __esm({
|
|
|
9520
10126
|
});
|
|
9521
10127
|
process.exit(result.status === "aborted" ? 1 : 0);
|
|
9522
10128
|
} catch (err) {
|
|
9523
|
-
|
|
10129
|
+
consola27.error(err instanceof Error ? err.message : String(err));
|
|
9524
10130
|
process.exit(1);
|
|
9525
10131
|
}
|
|
9526
10132
|
}
|
|
@@ -9529,8 +10135,8 @@ var init_remove_language = __esm({
|
|
|
9529
10135
|
});
|
|
9530
10136
|
|
|
9531
10137
|
// src/commands/remove-port.ts
|
|
9532
|
-
import { defineCommand as
|
|
9533
|
-
import { consola as
|
|
10138
|
+
import { defineCommand as defineCommand22 } from "citty";
|
|
10139
|
+
import { consola as consola28 } from "consola";
|
|
9534
10140
|
function coerceToken2(raw) {
|
|
9535
10141
|
const n = Number(raw);
|
|
9536
10142
|
return Number.isFinite(n) ? n : raw;
|
|
@@ -9541,7 +10147,7 @@ var init_remove_port = __esm({
|
|
|
9541
10147
|
"use strict";
|
|
9542
10148
|
init_inner_args();
|
|
9543
10149
|
init_modify();
|
|
9544
|
-
removePortCommand =
|
|
10150
|
+
removePortCommand = defineCommand22({
|
|
9545
10151
|
meta: {
|
|
9546
10152
|
name: "remove-port",
|
|
9547
10153
|
group: "edit",
|
|
@@ -9563,7 +10169,7 @@ var init_remove_port = __esm({
|
|
|
9563
10169
|
async run({ args }) {
|
|
9564
10170
|
const tokens = [...getInnerArgs()];
|
|
9565
10171
|
if (tokens.length === 0) {
|
|
9566
|
-
|
|
10172
|
+
consola28.error(
|
|
9567
10173
|
"No ports given. Usage: `monoceros remove-port <containername> [--yes] -- <port> [<port> \u2026]`."
|
|
9568
10174
|
);
|
|
9569
10175
|
process.exit(1);
|
|
@@ -9576,7 +10182,7 @@ var init_remove_port = __esm({
|
|
|
9576
10182
|
});
|
|
9577
10183
|
process.exit(result.status === "aborted" ? 1 : 0);
|
|
9578
10184
|
} catch (err) {
|
|
9579
|
-
|
|
10185
|
+
consola28.error(err instanceof Error ? err.message : String(err));
|
|
9580
10186
|
process.exit(1);
|
|
9581
10187
|
}
|
|
9582
10188
|
}
|
|
@@ -9585,14 +10191,14 @@ var init_remove_port = __esm({
|
|
|
9585
10191
|
});
|
|
9586
10192
|
|
|
9587
10193
|
// src/commands/remove-repo.ts
|
|
9588
|
-
import { defineCommand as
|
|
9589
|
-
import { consola as
|
|
10194
|
+
import { defineCommand as defineCommand23 } from "citty";
|
|
10195
|
+
import { consola as consola29 } from "consola";
|
|
9590
10196
|
var removeRepoCommand;
|
|
9591
10197
|
var init_remove_repo = __esm({
|
|
9592
10198
|
"src/commands/remove-repo.ts"() {
|
|
9593
10199
|
"use strict";
|
|
9594
10200
|
init_modify();
|
|
9595
|
-
removeRepoCommand =
|
|
10201
|
+
removeRepoCommand = defineCommand23({
|
|
9596
10202
|
meta: {
|
|
9597
10203
|
name: "remove-repo",
|
|
9598
10204
|
group: "edit",
|
|
@@ -9625,7 +10231,7 @@ var init_remove_repo = __esm({
|
|
|
9625
10231
|
});
|
|
9626
10232
|
process.exit(result.status === "aborted" ? 1 : 0);
|
|
9627
10233
|
} catch (err) {
|
|
9628
|
-
|
|
10234
|
+
consola29.error(err instanceof Error ? err.message : String(err));
|
|
9629
10235
|
process.exit(1);
|
|
9630
10236
|
}
|
|
9631
10237
|
}
|
|
@@ -9634,14 +10240,14 @@ var init_remove_repo = __esm({
|
|
|
9634
10240
|
});
|
|
9635
10241
|
|
|
9636
10242
|
// src/commands/remove-service.ts
|
|
9637
|
-
import { defineCommand as
|
|
9638
|
-
import { consola as
|
|
10243
|
+
import { defineCommand as defineCommand24 } from "citty";
|
|
10244
|
+
import { consola as consola30 } from "consola";
|
|
9639
10245
|
var removeServiceCommand;
|
|
9640
10246
|
var init_remove_service = __esm({
|
|
9641
10247
|
"src/commands/remove-service.ts"() {
|
|
9642
10248
|
"use strict";
|
|
9643
10249
|
init_modify();
|
|
9644
|
-
removeServiceCommand =
|
|
10250
|
+
removeServiceCommand = defineCommand24({
|
|
9645
10251
|
meta: {
|
|
9646
10252
|
name: "remove-service",
|
|
9647
10253
|
group: "edit",
|
|
@@ -9674,7 +10280,7 @@ var init_remove_service = __esm({
|
|
|
9674
10280
|
});
|
|
9675
10281
|
process.exit(result.status === "aborted" ? 1 : 0);
|
|
9676
10282
|
} catch (err) {
|
|
9677
|
-
|
|
10283
|
+
consola30.error(err instanceof Error ? err.message : String(err));
|
|
9678
10284
|
process.exit(1);
|
|
9679
10285
|
}
|
|
9680
10286
|
}
|
|
@@ -9682,148 +10288,17 @@ var init_remove_service = __esm({
|
|
|
9682
10288
|
}
|
|
9683
10289
|
});
|
|
9684
10290
|
|
|
9685
|
-
// src/devcontainer/browser-bridge.ts
|
|
9686
|
-
import { spawn as spawn9 } from "child_process";
|
|
9687
|
-
import { existsSync as existsSync15, promises as fsp5, readFileSync as readFileSync5 } from "fs";
|
|
9688
|
-
import http from "http";
|
|
9689
|
-
import path24 from "path";
|
|
9690
|
-
function parseCallbackTarget(authUrl) {
|
|
9691
|
-
try {
|
|
9692
|
-
const u = new URL(authUrl);
|
|
9693
|
-
const redirect = u.searchParams.get("redirect_uri");
|
|
9694
|
-
if (!redirect) return null;
|
|
9695
|
-
const r = new URL(redirect);
|
|
9696
|
-
if (r.hostname !== "localhost" && r.hostname !== "127.0.0.1") return null;
|
|
9697
|
-
const port = Number(r.port);
|
|
9698
|
-
if (!Number.isInteger(port) || port <= 0) return null;
|
|
9699
|
-
return { port, pathname: r.pathname };
|
|
9700
|
-
} catch {
|
|
9701
|
-
return null;
|
|
9702
|
-
}
|
|
9703
|
-
}
|
|
9704
|
-
function nextRelayUrl(content, lastOpened) {
|
|
9705
|
-
const url = content.trim();
|
|
9706
|
-
return url && url !== lastOpened ? url : null;
|
|
9707
|
-
}
|
|
9708
|
-
function wrapExec(command, opts) {
|
|
9709
|
-
const leading = [];
|
|
9710
|
-
const stmts = [];
|
|
9711
|
-
if (opts.pathPrepend) {
|
|
9712
|
-
leading.push(opts.pathPrepend);
|
|
9713
|
-
const i = leading.length;
|
|
9714
|
-
stmts.push(`export PATH="$${i}:$PATH"`);
|
|
9715
|
-
stmts.push(`export BROWSER="$${i}/xdg-open"`);
|
|
9716
|
-
}
|
|
9717
|
-
if (opts.cwd) {
|
|
9718
|
-
leading.push(opts.cwd);
|
|
9719
|
-
stmts.push(`cd -- "$${leading.length}"`);
|
|
9720
|
-
}
|
|
9721
|
-
if (leading.length === 0) return command;
|
|
9722
|
-
const shift = leading.length === 1 ? "shift" : `shift ${leading.length}`;
|
|
9723
|
-
const script = `${stmts.join(" && ")} && ${shift} && exec "$@"`;
|
|
9724
|
-
return ["bash", "-lc", script, "bash", ...leading, ...command];
|
|
9725
|
-
}
|
|
9726
|
-
function openInBrowser(url) {
|
|
9727
|
-
const platform = process.platform;
|
|
9728
|
-
const [cmd, args] = platform === "darwin" ? ["open", [url]] : platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
9729
|
-
try {
|
|
9730
|
-
const child = spawn9(cmd, args, {
|
|
9731
|
-
stdio: "ignore",
|
|
9732
|
-
detached: true
|
|
9733
|
-
});
|
|
9734
|
-
child.on("error", () => {
|
|
9735
|
-
});
|
|
9736
|
-
child.unref();
|
|
9737
|
-
} catch {
|
|
9738
|
-
}
|
|
9739
|
-
}
|
|
9740
|
-
async function startBrowserBridge(opts) {
|
|
9741
|
-
const relayDir = path24.join(opts.root, RELAY_DIRNAME);
|
|
9742
|
-
const relayScript = path24.join(relayDir, "xdg-open");
|
|
9743
|
-
const urlFile = path24.join(relayDir, "url");
|
|
9744
|
-
await fsp5.mkdir(relayDir, { recursive: true });
|
|
9745
|
-
await fsp5.rm(urlFile, { force: true });
|
|
9746
|
-
await fsp5.writeFile(
|
|
9747
|
-
relayScript,
|
|
9748
|
-
`#!/bin/sh
|
|
9749
|
-
printf '%s\\n' "$1" > "$(dirname "$0")/url"
|
|
9750
|
-
exit 0
|
|
9751
|
-
`,
|
|
9752
|
-
{ mode: 493 }
|
|
9753
|
-
);
|
|
9754
|
-
await fsp5.chmod(relayScript, 493);
|
|
9755
|
-
const servers = [];
|
|
9756
|
-
let lastOpened = null;
|
|
9757
|
-
const onUrl = (url) => {
|
|
9758
|
-
openInBrowser(url);
|
|
9759
|
-
const target = parseCallbackTarget(url);
|
|
9760
|
-
if (!target) return;
|
|
9761
|
-
const server = http.createServer((req, res) => {
|
|
9762
|
-
const reqUrl = req.url ?? target.pathname;
|
|
9763
|
-
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
9764
|
-
res.end(
|
|
9765
|
-
'<html><body style="font-family:sans-serif;padding:3rem">You are signed in. You can close this tab and return to the terminal.</body></html>'
|
|
9766
|
-
);
|
|
9767
|
-
void opts.spawn(
|
|
9768
|
-
[
|
|
9769
|
-
"exec",
|
|
9770
|
-
"--workspace-folder",
|
|
9771
|
-
opts.root,
|
|
9772
|
-
"--mount-workspace-git-root=false",
|
|
9773
|
-
"curl",
|
|
9774
|
-
"-fsS",
|
|
9775
|
-
`http://localhost:${target.port}${reqUrl}`
|
|
9776
|
-
],
|
|
9777
|
-
opts.root,
|
|
9778
|
-
{ quiet: true }
|
|
9779
|
-
);
|
|
9780
|
-
});
|
|
9781
|
-
server.on("error", () => {
|
|
9782
|
-
});
|
|
9783
|
-
server.listen(target.port, "127.0.0.1");
|
|
9784
|
-
servers.push(server);
|
|
9785
|
-
};
|
|
9786
|
-
const poll = setInterval(() => {
|
|
9787
|
-
if (!existsSync15(urlFile)) return;
|
|
9788
|
-
let content = "";
|
|
9789
|
-
try {
|
|
9790
|
-
content = readFileSync5(urlFile, "utf8");
|
|
9791
|
-
} catch {
|
|
9792
|
-
return;
|
|
9793
|
-
}
|
|
9794
|
-
const url = nextRelayUrl(content, lastOpened);
|
|
9795
|
-
if (!url) return;
|
|
9796
|
-
lastOpened = url;
|
|
9797
|
-
onUrl(url);
|
|
9798
|
-
}, 250);
|
|
9799
|
-
return {
|
|
9800
|
-
relayDirInContainer: `/workspaces/${opts.name}/${RELAY_DIRNAME}`,
|
|
9801
|
-
async dispose() {
|
|
9802
|
-
clearInterval(poll);
|
|
9803
|
-
for (const s of servers) s.close();
|
|
9804
|
-
await fsp5.rm(relayDir, { recursive: true, force: true });
|
|
9805
|
-
}
|
|
9806
|
-
};
|
|
9807
|
-
}
|
|
9808
|
-
var RELAY_DIRNAME;
|
|
9809
|
-
var init_browser_bridge = __esm({
|
|
9810
|
-
"src/devcontainer/browser-bridge.ts"() {
|
|
9811
|
-
"use strict";
|
|
9812
|
-
RELAY_DIRNAME = ".monoceros-bridge";
|
|
9813
|
-
}
|
|
9814
|
-
});
|
|
9815
|
-
|
|
9816
10291
|
// src/devcontainer/claude-trust.ts
|
|
9817
|
-
import { existsSync as
|
|
9818
|
-
import
|
|
10292
|
+
import { existsSync as existsSync19, promises as fsp6 } from "fs";
|
|
10293
|
+
import path28 from "path";
|
|
9819
10294
|
function resolveContainerCwd(name, cwd) {
|
|
9820
10295
|
const workspace = `/workspaces/${name}`;
|
|
9821
10296
|
if (!cwd) return workspace;
|
|
9822
|
-
return
|
|
10297
|
+
return path28.posix.isAbsolute(cwd) ? cwd : path28.posix.join(workspace, cwd);
|
|
9823
10298
|
}
|
|
9824
10299
|
async function preApproveClaudeProject(opts) {
|
|
9825
|
-
const file =
|
|
9826
|
-
if (!
|
|
10300
|
+
const file = path28.join(opts.root, "home", ".claude.json");
|
|
10301
|
+
if (!existsSync19(file)) return;
|
|
9827
10302
|
try {
|
|
9828
10303
|
const raw = await fsp6.readFile(file, "utf8");
|
|
9829
10304
|
const config = raw.trim() ? JSON.parse(raw) : {};
|
|
@@ -9856,56 +10331,6 @@ var init_claude_trust = __esm({
|
|
|
9856
10331
|
}
|
|
9857
10332
|
});
|
|
9858
10333
|
|
|
9859
|
-
// src/devcontainer/shell.ts
|
|
9860
|
-
import { existsSync as existsSync17 } from "fs";
|
|
9861
|
-
import path26 from "path";
|
|
9862
|
-
async function runShell(opts) {
|
|
9863
|
-
assertContainerExists(opts.root);
|
|
9864
|
-
const spawnFn = opts.spawn ?? spawnDevcontainer;
|
|
9865
|
-
const upCode = await spawnFn(
|
|
9866
|
-
["up", "--workspace-folder", opts.root, "--mount-workspace-git-root=false"],
|
|
9867
|
-
opts.root,
|
|
9868
|
-
{ quiet: true }
|
|
9869
|
-
);
|
|
9870
|
-
if (upCode !== 0) return upCode;
|
|
9871
|
-
const name = opts.name ?? path26.basename(opts.root);
|
|
9872
|
-
const isTty2 = opts.isTty ?? process.stdout.isTTY;
|
|
9873
|
-
const bridge = isTty2 ? await startBrowserBridge({ name, root: opts.root, spawn: spawnFn }) : null;
|
|
9874
|
-
try {
|
|
9875
|
-
const innerExec = wrapExec(
|
|
9876
|
-
["bash"],
|
|
9877
|
-
bridge ? { pathPrepend: bridge.relayDirInContainer } : {}
|
|
9878
|
-
);
|
|
9879
|
-
return await spawnFn(
|
|
9880
|
-
[
|
|
9881
|
-
"exec",
|
|
9882
|
-
"--workspace-folder",
|
|
9883
|
-
opts.root,
|
|
9884
|
-
"--mount-workspace-git-root=false",
|
|
9885
|
-
...innerExec
|
|
9886
|
-
],
|
|
9887
|
-
opts.root,
|
|
9888
|
-
{ interactive: true }
|
|
9889
|
-
);
|
|
9890
|
-
} finally {
|
|
9891
|
-
if (bridge) await bridge.dispose();
|
|
9892
|
-
}
|
|
9893
|
-
}
|
|
9894
|
-
function assertContainerExists(root) {
|
|
9895
|
-
if (!existsSync17(path26.join(root, ".devcontainer"))) {
|
|
9896
|
-
throw new Error(
|
|
9897
|
-
`No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
|
|
9898
|
-
);
|
|
9899
|
-
}
|
|
9900
|
-
}
|
|
9901
|
-
var init_shell = __esm({
|
|
9902
|
-
"src/devcontainer/shell.ts"() {
|
|
9903
|
-
"use strict";
|
|
9904
|
-
init_browser_bridge();
|
|
9905
|
-
init_cli();
|
|
9906
|
-
}
|
|
9907
|
-
});
|
|
9908
|
-
|
|
9909
10334
|
// src/devcontainer/run.ts
|
|
9910
10335
|
async function runInContainer(opts) {
|
|
9911
10336
|
if (opts.command.length === 0) {
|
|
@@ -9964,8 +10389,8 @@ var init_run = __esm({
|
|
|
9964
10389
|
});
|
|
9965
10390
|
|
|
9966
10391
|
// src/commands/run.ts
|
|
9967
|
-
import { defineCommand as
|
|
9968
|
-
import { consola as
|
|
10392
|
+
import { defineCommand as defineCommand25 } from "citty";
|
|
10393
|
+
import { consola as consola31 } from "consola";
|
|
9969
10394
|
var runCommand;
|
|
9970
10395
|
var init_run2 = __esm({
|
|
9971
10396
|
"src/commands/run.ts"() {
|
|
@@ -9973,7 +10398,7 @@ var init_run2 = __esm({
|
|
|
9973
10398
|
init_paths();
|
|
9974
10399
|
init_run();
|
|
9975
10400
|
init_inner_args();
|
|
9976
|
-
runCommand =
|
|
10401
|
+
runCommand = defineCommand25({
|
|
9977
10402
|
meta: {
|
|
9978
10403
|
name: "run",
|
|
9979
10404
|
group: "run",
|
|
@@ -9993,7 +10418,7 @@ var init_run2 = __esm({
|
|
|
9993
10418
|
async run({ args }) {
|
|
9994
10419
|
const command = [...getInnerArgs()];
|
|
9995
10420
|
if (command.length === 0) {
|
|
9996
|
-
|
|
10421
|
+
consola31.error(
|
|
9997
10422
|
"No command provided. Usage: `monoceros run <containername> -- <cmd> [args\u2026]`."
|
|
9998
10423
|
);
|
|
9999
10424
|
process.exit(1);
|
|
@@ -10007,7 +10432,7 @@ var init_run2 = __esm({
|
|
|
10007
10432
|
});
|
|
10008
10433
|
process.exit(exitCode);
|
|
10009
10434
|
} catch (err) {
|
|
10010
|
-
|
|
10435
|
+
consola31.error(err instanceof Error ? err.message : String(err));
|
|
10011
10436
|
process.exit(1);
|
|
10012
10437
|
}
|
|
10013
10438
|
}
|
|
@@ -10016,15 +10441,15 @@ var init_run2 = __esm({
|
|
|
10016
10441
|
});
|
|
10017
10442
|
|
|
10018
10443
|
// src/commands/shell.ts
|
|
10019
|
-
import { defineCommand as
|
|
10020
|
-
import { consola as
|
|
10444
|
+
import { defineCommand as defineCommand26 } from "citty";
|
|
10445
|
+
import { consola as consola32 } from "consola";
|
|
10021
10446
|
var shellCommand;
|
|
10022
10447
|
var init_shell2 = __esm({
|
|
10023
10448
|
"src/commands/shell.ts"() {
|
|
10024
10449
|
"use strict";
|
|
10025
10450
|
init_paths();
|
|
10026
10451
|
init_shell();
|
|
10027
|
-
shellCommand =
|
|
10452
|
+
shellCommand = defineCommand26({
|
|
10028
10453
|
meta: {
|
|
10029
10454
|
name: "shell",
|
|
10030
10455
|
group: "run",
|
|
@@ -10045,7 +10470,7 @@ var init_shell2 = __esm({
|
|
|
10045
10470
|
});
|
|
10046
10471
|
process.exit(exitCode);
|
|
10047
10472
|
} catch (err) {
|
|
10048
|
-
|
|
10473
|
+
consola32.error(err instanceof Error ? err.message : String(err));
|
|
10049
10474
|
process.exit(1);
|
|
10050
10475
|
}
|
|
10051
10476
|
}
|
|
@@ -10054,8 +10479,8 @@ var init_shell2 = __esm({
|
|
|
10054
10479
|
});
|
|
10055
10480
|
|
|
10056
10481
|
// src/commands/start.ts
|
|
10057
|
-
import { defineCommand as
|
|
10058
|
-
import { consola as
|
|
10482
|
+
import { defineCommand as defineCommand27 } from "citty";
|
|
10483
|
+
import { consola as consola33 } from "consola";
|
|
10059
10484
|
var startCommand;
|
|
10060
10485
|
var init_start = __esm({
|
|
10061
10486
|
"src/commands/start.ts"() {
|
|
@@ -10064,10 +10489,11 @@ var init_start = __esm({
|
|
|
10064
10489
|
init_io();
|
|
10065
10490
|
init_paths();
|
|
10066
10491
|
init_compose();
|
|
10492
|
+
init_open();
|
|
10067
10493
|
init_proxy();
|
|
10068
10494
|
init_port_check();
|
|
10069
10495
|
init_dispatch();
|
|
10070
|
-
startCommand =
|
|
10496
|
+
startCommand = defineCommand27({
|
|
10071
10497
|
meta: {
|
|
10072
10498
|
name: "start",
|
|
10073
10499
|
group: "run",
|
|
@@ -10078,6 +10504,10 @@ var init_start = __esm({
|
|
|
10078
10504
|
type: "positional",
|
|
10079
10505
|
description: "Container name (yml in $MONOCEROS_HOME/container-configs/).",
|
|
10080
10506
|
required: true
|
|
10507
|
+
},
|
|
10508
|
+
open: {
|
|
10509
|
+
type: "string",
|
|
10510
|
+
description: `After a successful start, open the container in this tool (${OPEN_TOOLS.join("|")}).`
|
|
10081
10511
|
}
|
|
10082
10512
|
},
|
|
10083
10513
|
run({ args }) {
|
|
@@ -10092,7 +10522,7 @@ var init_start = __esm({
|
|
|
10092
10522
|
hostPort = proxyHostPort(global);
|
|
10093
10523
|
}
|
|
10094
10524
|
} catch (err) {
|
|
10095
|
-
|
|
10525
|
+
consola33.warn(
|
|
10096
10526
|
`Could not read container yml ahead of start: ${err instanceof Error ? err.message : String(err)}. Skipping Traefik pre-flight.`
|
|
10097
10527
|
);
|
|
10098
10528
|
}
|
|
@@ -10100,7 +10530,15 @@ var init_start = __esm({
|
|
|
10100
10530
|
await preflightHostPort(hostPort);
|
|
10101
10531
|
await ensureProxy({ hostPort });
|
|
10102
10532
|
}
|
|
10103
|
-
|
|
10533
|
+
const exitCode = await runStart({ root: containerDir(args.name) });
|
|
10534
|
+
if (args.open && exitCode === 0) {
|
|
10535
|
+
try {
|
|
10536
|
+
await runOpen({ name: args.name, tool: args.open });
|
|
10537
|
+
} catch (err) {
|
|
10538
|
+
consola33.warn(err instanceof Error ? err.message : String(err));
|
|
10539
|
+
}
|
|
10540
|
+
}
|
|
10541
|
+
return exitCode;
|
|
10104
10542
|
});
|
|
10105
10543
|
}
|
|
10106
10544
|
});
|
|
@@ -10108,7 +10546,7 @@ var init_start = __esm({
|
|
|
10108
10546
|
});
|
|
10109
10547
|
|
|
10110
10548
|
// src/commands/status.ts
|
|
10111
|
-
import { defineCommand as
|
|
10549
|
+
import { defineCommand as defineCommand28 } from "citty";
|
|
10112
10550
|
var statusCommand;
|
|
10113
10551
|
var init_status = __esm({
|
|
10114
10552
|
"src/commands/status.ts"() {
|
|
@@ -10116,7 +10554,7 @@ var init_status = __esm({
|
|
|
10116
10554
|
init_paths();
|
|
10117
10555
|
init_compose();
|
|
10118
10556
|
init_dispatch();
|
|
10119
|
-
statusCommand =
|
|
10557
|
+
statusCommand = defineCommand28({
|
|
10120
10558
|
meta: {
|
|
10121
10559
|
name: "status",
|
|
10122
10560
|
group: "run",
|
|
@@ -10146,8 +10584,8 @@ var init_status = __esm({
|
|
|
10146
10584
|
});
|
|
10147
10585
|
|
|
10148
10586
|
// src/commands/stop.ts
|
|
10149
|
-
import { defineCommand as
|
|
10150
|
-
import { consola as
|
|
10587
|
+
import { defineCommand as defineCommand29 } from "citty";
|
|
10588
|
+
import { consola as consola34 } from "consola";
|
|
10151
10589
|
var stopCommand;
|
|
10152
10590
|
var init_stop = __esm({
|
|
10153
10591
|
"src/commands/stop.ts"() {
|
|
@@ -10156,7 +10594,7 @@ var init_stop = __esm({
|
|
|
10156
10594
|
init_compose();
|
|
10157
10595
|
init_proxy();
|
|
10158
10596
|
init_dispatch();
|
|
10159
|
-
stopCommand =
|
|
10597
|
+
stopCommand = defineCommand29({
|
|
10160
10598
|
meta: {
|
|
10161
10599
|
name: "stop",
|
|
10162
10600
|
group: "run",
|
|
@@ -10181,10 +10619,10 @@ var init_stop = __esm({
|
|
|
10181
10619
|
});
|
|
10182
10620
|
try {
|
|
10183
10621
|
await maybeStopProxy({
|
|
10184
|
-
logger: { info: (msg) =>
|
|
10622
|
+
logger: { info: (msg) => consola34.info(msg) }
|
|
10185
10623
|
});
|
|
10186
10624
|
} catch (err) {
|
|
10187
|
-
|
|
10625
|
+
consola34.warn(
|
|
10188
10626
|
`Could not tear down the Traefik proxy: ${err instanceof Error ? err.message : String(err)}. Ignored.`
|
|
10189
10627
|
);
|
|
10190
10628
|
}
|
|
@@ -10196,11 +10634,11 @@ var init_stop = __esm({
|
|
|
10196
10634
|
});
|
|
10197
10635
|
|
|
10198
10636
|
// src/tunnel/resolve.ts
|
|
10199
|
-
import { existsSync as
|
|
10200
|
-
import
|
|
10637
|
+
import { existsSync as existsSync20 } from "fs";
|
|
10638
|
+
import path29 from "path";
|
|
10201
10639
|
async function resolveTunnelTarget(opts) {
|
|
10202
10640
|
const ymlPath = containerConfigPath(opts.name, opts.monocerosHome);
|
|
10203
|
-
if (!
|
|
10641
|
+
if (!existsSync20(ymlPath)) {
|
|
10204
10642
|
throw new Error(
|
|
10205
10643
|
`No yml profile for '${opts.name}' at ${ymlPath}. Run \`monoceros init ${opts.name}\` first.`
|
|
10206
10644
|
);
|
|
@@ -10208,13 +10646,13 @@ async function resolveTunnelTarget(opts) {
|
|
|
10208
10646
|
const parsed = await readConfig(ymlPath);
|
|
10209
10647
|
const config = parsed.config;
|
|
10210
10648
|
const containerRoot = containerDir(opts.name, opts.monocerosHome);
|
|
10211
|
-
if (!
|
|
10649
|
+
if (!existsSync20(containerRoot)) {
|
|
10212
10650
|
throw new Error(
|
|
10213
10651
|
`Container '${opts.name}' is not materialised at ${containerRoot}. Run \`monoceros apply ${opts.name}\` first.`
|
|
10214
10652
|
);
|
|
10215
10653
|
}
|
|
10216
|
-
const composePath =
|
|
10217
|
-
const isCompose =
|
|
10654
|
+
const composePath = path29.join(containerRoot, ".devcontainer", "compose.yaml");
|
|
10655
|
+
const isCompose = existsSync20(composePath);
|
|
10218
10656
|
const parsedTarget = parseTargetArg(opts.target, config);
|
|
10219
10657
|
const docker = opts.docker ?? defaultDockerExec;
|
|
10220
10658
|
if (isCompose) {
|
|
@@ -10449,8 +10887,8 @@ var init_port_check2 = __esm({
|
|
|
10449
10887
|
});
|
|
10450
10888
|
|
|
10451
10889
|
// src/tunnel/run.ts
|
|
10452
|
-
import { spawn as
|
|
10453
|
-
import { consola as
|
|
10890
|
+
import { spawn as spawn12 } from "child_process";
|
|
10891
|
+
import { consola as consola35 } from "consola";
|
|
10454
10892
|
function signalNumber(signal) {
|
|
10455
10893
|
switch (signal) {
|
|
10456
10894
|
case "SIGINT":
|
|
@@ -10463,8 +10901,8 @@ function signalNumber(signal) {
|
|
|
10463
10901
|
}
|
|
10464
10902
|
async function runTunnel(opts) {
|
|
10465
10903
|
const log = opts.logger ?? {
|
|
10466
|
-
info: (m) =>
|
|
10467
|
-
warn: (m) =>
|
|
10904
|
+
info: (m) => consola35.info(m),
|
|
10905
|
+
warn: (m) => consola35.warn(m)
|
|
10468
10906
|
};
|
|
10469
10907
|
const resolve = opts.resolve ?? resolveTunnelTarget;
|
|
10470
10908
|
const resolveArgs3 = {
|
|
@@ -10539,7 +10977,7 @@ var init_run3 = __esm({
|
|
|
10539
10977
|
init_port_check2();
|
|
10540
10978
|
SOCAT_IMAGE = "alpine/socat:1.8.0.3";
|
|
10541
10979
|
defaultDockerSpawn = (args) => {
|
|
10542
|
-
const child =
|
|
10980
|
+
const child = spawn12("docker", args, {
|
|
10543
10981
|
stdio: "inherit"
|
|
10544
10982
|
});
|
|
10545
10983
|
const exited = new Promise((resolve, reject) => {
|
|
@@ -10570,8 +11008,8 @@ var init_run3 = __esm({
|
|
|
10570
11008
|
});
|
|
10571
11009
|
|
|
10572
11010
|
// src/commands/tunnel.ts
|
|
10573
|
-
import { defineCommand as
|
|
10574
|
-
import { consola as
|
|
11011
|
+
import { defineCommand as defineCommand30 } from "citty";
|
|
11012
|
+
import { consola as consola36 } from "consola";
|
|
10575
11013
|
function parseLocalPort(raw) {
|
|
10576
11014
|
if (raw === void 0) return void 0;
|
|
10577
11015
|
const n = Number(raw);
|
|
@@ -10587,7 +11025,7 @@ var init_tunnel = __esm({
|
|
|
10587
11025
|
"src/commands/tunnel.ts"() {
|
|
10588
11026
|
"use strict";
|
|
10589
11027
|
init_run3();
|
|
10590
|
-
tunnelCommand =
|
|
11028
|
+
tunnelCommand = defineCommand30({
|
|
10591
11029
|
meta: {
|
|
10592
11030
|
name: "tunnel",
|
|
10593
11031
|
group: "discovery",
|
|
@@ -10624,7 +11062,7 @@ var init_tunnel = __esm({
|
|
|
10624
11062
|
});
|
|
10625
11063
|
process.exit(exitCode);
|
|
10626
11064
|
} catch (err) {
|
|
10627
|
-
|
|
11065
|
+
consola36.error(err instanceof Error ? err.message : String(err));
|
|
10628
11066
|
process.exit(1);
|
|
10629
11067
|
}
|
|
10630
11068
|
}
|
|
@@ -10694,8 +11132,8 @@ var init_prune = __esm({
|
|
|
10694
11132
|
});
|
|
10695
11133
|
|
|
10696
11134
|
// src/upgrade/index.ts
|
|
10697
|
-
import { existsSync as
|
|
10698
|
-
import { consola as
|
|
11135
|
+
import { existsSync as existsSync21, promises as fs18 } from "fs";
|
|
11136
|
+
import { consola as consola37 } from "consola";
|
|
10699
11137
|
async function fetchRuntimeVersions() {
|
|
10700
11138
|
const tokenUrl = `https://ghcr.io/token?service=ghcr.io&scope=repository:${RUNTIME_REPO}:pull`;
|
|
10701
11139
|
const tokenRes = await fetch(tokenUrl);
|
|
@@ -10733,7 +11171,7 @@ ${yml}`;
|
|
|
10733
11171
|
}
|
|
10734
11172
|
async function runUpgrade(opts) {
|
|
10735
11173
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
10736
|
-
const logger = opts.logger ??
|
|
11174
|
+
const logger = opts.logger ?? consola37;
|
|
10737
11175
|
const fetchVersions = opts.fetchVersions ?? fetchRuntimeVersions;
|
|
10738
11176
|
if (opts.list) {
|
|
10739
11177
|
const versions = await fetchVersions();
|
|
@@ -10759,7 +11197,7 @@ async function runUpgrade(opts) {
|
|
|
10759
11197
|
);
|
|
10760
11198
|
}
|
|
10761
11199
|
}
|
|
10762
|
-
if (opts.name && !
|
|
11200
|
+
if (opts.name && !existsSync21(containerConfigPath(opts.name, home))) {
|
|
10763
11201
|
throw new Error(
|
|
10764
11202
|
`No such config: ${containerConfigPath(opts.name, home)}. Run \`monoceros init <template> ${opts.name}\` first.`
|
|
10765
11203
|
);
|
|
@@ -10810,11 +11248,11 @@ async function runUpgrade(opts) {
|
|
|
10810
11248
|
let bumped = 0;
|
|
10811
11249
|
for (const name of targets) {
|
|
10812
11250
|
const ymlPath = containerConfigPath(name, home);
|
|
10813
|
-
if (!
|
|
10814
|
-
const raw = await
|
|
11251
|
+
if (!existsSync21(ymlPath)) continue;
|
|
11252
|
+
const raw = await fs18.readFile(ymlPath, "utf8");
|
|
10815
11253
|
const updated = setRuntimeVersion(raw, pinVersion);
|
|
10816
11254
|
if (updated !== raw) {
|
|
10817
|
-
await
|
|
11255
|
+
await fs18.writeFile(ymlPath, updated);
|
|
10818
11256
|
bumped += 1;
|
|
10819
11257
|
logger.info(`Pinned '${name}' to runtime ${pinVersion}.`);
|
|
10820
11258
|
}
|
|
@@ -10856,7 +11294,7 @@ function formatPruneLine(prune) {
|
|
|
10856
11294
|
}
|
|
10857
11295
|
async function listContainerNames2(home) {
|
|
10858
11296
|
try {
|
|
10859
|
-
const entries = await
|
|
11297
|
+
const entries = await fs18.readdir(containerConfigsDir(home));
|
|
10860
11298
|
return entries.filter((e) => e.endsWith(".yml")).map((e) => e.slice(0, -".yml".length));
|
|
10861
11299
|
} catch {
|
|
10862
11300
|
return [];
|
|
@@ -10879,7 +11317,7 @@ var init_upgrade = __esm({
|
|
|
10879
11317
|
});
|
|
10880
11318
|
|
|
10881
11319
|
// src/commands/upgrade.ts
|
|
10882
|
-
import { defineCommand as
|
|
11320
|
+
import { defineCommand as defineCommand31 } from "citty";
|
|
10883
11321
|
var upgradeCommand;
|
|
10884
11322
|
var init_upgrade2 = __esm({
|
|
10885
11323
|
"src/commands/upgrade.ts"() {
|
|
@@ -10887,7 +11325,7 @@ var init_upgrade2 = __esm({
|
|
|
10887
11325
|
init_upgrade();
|
|
10888
11326
|
init_version();
|
|
10889
11327
|
init_dispatch();
|
|
10890
|
-
upgradeCommand =
|
|
11328
|
+
upgradeCommand = defineCommand31({
|
|
10891
11329
|
meta: {
|
|
10892
11330
|
name: "upgrade",
|
|
10893
11331
|
group: "lifecycle",
|
|
@@ -10929,7 +11367,7 @@ var main_exports = {};
|
|
|
10929
11367
|
__export(main_exports, {
|
|
10930
11368
|
main: () => main
|
|
10931
11369
|
});
|
|
10932
|
-
import { defineCommand as
|
|
11370
|
+
import { defineCommand as defineCommand32 } from "citty";
|
|
10933
11371
|
var main;
|
|
10934
11372
|
var init_main = __esm({
|
|
10935
11373
|
"src/main.ts"() {
|
|
@@ -10947,6 +11385,7 @@ var init_main = __esm({
|
|
|
10947
11385
|
init_init2();
|
|
10948
11386
|
init_list_components();
|
|
10949
11387
|
init_logs();
|
|
11388
|
+
init_open2();
|
|
10950
11389
|
init_port();
|
|
10951
11390
|
init_remove_apt_packages();
|
|
10952
11391
|
init_remove_feature();
|
|
@@ -10965,7 +11404,7 @@ var init_main = __esm({
|
|
|
10965
11404
|
init_tunnel();
|
|
10966
11405
|
init_upgrade2();
|
|
10967
11406
|
init_version();
|
|
10968
|
-
main =
|
|
11407
|
+
main = defineCommand32({
|
|
10969
11408
|
meta: {
|
|
10970
11409
|
name: "monoceros",
|
|
10971
11410
|
version: CLI_VERSION,
|
|
@@ -10975,6 +11414,7 @@ var init_main = __esm({
|
|
|
10975
11414
|
init: initCommand,
|
|
10976
11415
|
"list-components": listComponentsCommand,
|
|
10977
11416
|
shell: shellCommand,
|
|
11417
|
+
open: openCommand,
|
|
10978
11418
|
run: runCommand,
|
|
10979
11419
|
logs: logsCommand,
|
|
10980
11420
|
start: startCommand,
|
|
@@ -11261,25 +11701,25 @@ function detectHelpRequest(argv, main2) {
|
|
|
11261
11701
|
const separatorIdx = argv.indexOf("--");
|
|
11262
11702
|
if (helpIdx === -1) return null;
|
|
11263
11703
|
if (separatorIdx !== -1 && separatorIdx < helpIdx) return null;
|
|
11264
|
-
const
|
|
11704
|
+
const path30 = [];
|
|
11265
11705
|
const tokens = argv.slice(
|
|
11266
11706
|
0,
|
|
11267
11707
|
separatorIdx === -1 ? argv.length : separatorIdx
|
|
11268
11708
|
);
|
|
11269
11709
|
let cursor = main2;
|
|
11270
11710
|
const mainName = (main2.meta ?? {}).name ?? "monoceros";
|
|
11271
|
-
|
|
11711
|
+
path30.push(mainName);
|
|
11272
11712
|
for (const tok of tokens) {
|
|
11273
11713
|
if (tok.startsWith("-")) continue;
|
|
11274
11714
|
const subs = cursor.subCommands ?? {};
|
|
11275
11715
|
if (tok in subs) {
|
|
11276
11716
|
cursor = subs[tok];
|
|
11277
|
-
|
|
11717
|
+
path30.push(tok);
|
|
11278
11718
|
continue;
|
|
11279
11719
|
}
|
|
11280
11720
|
break;
|
|
11281
11721
|
}
|
|
11282
|
-
return { path:
|
|
11722
|
+
return { path: path30, cmd: cursor };
|
|
11283
11723
|
}
|
|
11284
11724
|
async function maybeRenderHelp(argv, main2) {
|
|
11285
11725
|
const hit = detectHelpRequest(argv, main2);
|