@getmonoceros/workbench 1.11.5 → 1.11.7

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 CHANGED
@@ -3953,23 +3953,63 @@ var spawnDockerCompose = (args, cwd) => {
3953
3953
  child.on("exit", (code) => resolve(code ?? 0));
3954
3954
  });
3955
3955
  };
3956
- var spawnBash = (args, cwd) => {
3956
+ var spawnDocker = (args) => {
3957
3957
  return new Promise((resolve, reject) => {
3958
- const child = spawn5("bash", args, {
3959
- cwd,
3960
- stdio: ["inherit", "pipe", "pipe"],
3961
- env: {
3962
- ...process.env,
3963
- MSYS_NO_PATHCONV: "1",
3964
- MSYS2_ARG_CONV_EXCL: "*"
3965
- }
3958
+ const child = spawn5("docker", args, {
3959
+ stdio: ["ignore", "pipe", "pipe"]
3960
+ });
3961
+ let stdout = "";
3962
+ let stderr = "";
3963
+ child.stdout?.on("data", (chunk) => {
3964
+ stdout += chunk.toString("utf8");
3965
+ });
3966
+ child.stderr?.on("data", (chunk) => {
3967
+ stderr += chunk.toString("utf8");
3966
3968
  });
3967
- child.stdout?.pipe(createSecretMaskStream()).pipe(process.stdout);
3968
- child.stderr?.pipe(createSecretMaskStream()).pipe(process.stderr);
3969
3969
  child.on("error", reject);
3970
- child.on("exit", (code) => resolve(code ?? 0));
3970
+ child.on(
3971
+ "exit",
3972
+ (code) => resolve({ exitCode: code ?? 0, stdout, stderr })
3973
+ );
3971
3974
  });
3972
3975
  };
3976
+ async function findContainerIds(filters, exec = spawnDocker) {
3977
+ const ids = /* @__PURE__ */ new Set();
3978
+ for (const filter of filters) {
3979
+ const result = await exec(["ps", "-aq", "--filter", filter]);
3980
+ if (result.exitCode !== 0) continue;
3981
+ for (const line of result.stdout.split(/\r?\n/)) {
3982
+ const id = line.trim();
3983
+ if (id) ids.add(id);
3984
+ }
3985
+ }
3986
+ return [...ids];
3987
+ }
3988
+ async function cleanupDockerObjects(opts) {
3989
+ const exec = opts.exec ?? spawnDocker;
3990
+ const tag = opts.logTag ?? "cleanup";
3991
+ opts.logger.info(`[${tag}] tearing down docker project ${opts.projectName}\u2026`);
3992
+ const ids = await findContainerIds(opts.filters, exec);
3993
+ let rmExit = 0;
3994
+ if (ids.length > 0) {
3995
+ opts.logger.info(`[${tag}] removing containers: ${ids.join(" ")}`);
3996
+ const rmResult = await exec(["rm", "-f", ...ids]);
3997
+ rmExit = rmResult.exitCode;
3998
+ if (rmExit !== 0 && rmResult.stderr.trim()) {
3999
+ opts.logger.info(`[${tag}] ${rmResult.stderr.trim()}`);
4000
+ }
4001
+ } else {
4002
+ opts.logger.info(`[${tag}] no containers found`);
4003
+ }
4004
+ if (opts.network) {
4005
+ const netResult = await exec(["network", "rm", opts.network]);
4006
+ if (netResult.exitCode === 0) {
4007
+ opts.logger.info(`[${tag}] network ${opts.network} removed`);
4008
+ }
4009
+ }
4010
+ opts.logger.info(`[${tag}] docker cleanup done`);
4011
+ return { exitCode: rmExit, removedIds: ids };
4012
+ }
3973
4013
  function dockerLocalFolderLabel(p) {
3974
4014
  if (process.platform !== "win32") return p;
3975
4015
  return p.replace(
@@ -4017,26 +4057,31 @@ async function runContainerCycle(root, opts) {
4017
4057
  logger.info(
4018
4058
  `Force-removing existing ${projectName} containers (volumes preserved)\u2026`
4019
4059
  );
4020
- const cleanupSpawn = opts.cleanupSpawn ?? spawnBash;
4021
- const script = [
4022
- `set -u`,
4023
- `echo "[cleanup] checking project ${projectName}\u2026"`,
4024
- `by_label=$(docker ps -aq --filter "label=com.docker.compose.project=${projectName}" 2>/dev/null || true)`,
4025
- `by_name=$(docker ps -aq --filter "name=^${projectName}-" 2>/dev/null || true)`,
4026
- `to_remove=$(printf "%s\\n%s\\n" "$by_label" "$by_name" | sort -u | grep -v "^$" || true)`,
4027
- // Unquoted `$to_remove` so bash word-splitting joins the
4028
- // newline-separated IDs with single spaces on echo. A `tr "\n" " "`
4029
- // pipe here used to do the same job but tripped MSYS2's arg
4030
- // translation on Git Bash for Windows ("tr: extra operand").
4031
- `if [ -n "$to_remove" ]; then echo "[cleanup] removing:" $to_remove; docker rm -f $to_remove >/dev/null || true; else echo "[cleanup] no containers to remove"; fi`,
4032
- `docker network rm ${projectName}_default 2>/dev/null && echo "[cleanup] network ${projectName}_default removed" || echo "[cleanup] network ${projectName}_default not present"`,
4033
- `remaining_label=$(docker ps -aq --filter "label=com.docker.compose.project=${projectName}" 2>/dev/null || true)`,
4034
- `remaining_name=$(docker ps -aq --filter "name=^${projectName}-" 2>/dev/null || true)`,
4035
- `if [ -n "$remaining_label" ] || [ -n "$remaining_name" ]; then echo "" >&2; echo "ERROR: containers under project ${projectName} reappeared after removal." >&2; echo "This typically means VS Code's Remote Containers extension is connected to" >&2; echo "this devcontainer and auto-recreated it. Close the dev container session" >&2; echo "in VS Code (Cmd+Shift+P \u2192 'Dev Containers: Close Remote Connection')" >&2; echo "and retry \\\`monoceros apply\\\`." >&2; exit 1; fi`,
4036
- `echo "[cleanup] done"`
4037
- ].join("; ");
4038
- const cleanupCode = await cleanupSpawn(["-c", script], root);
4039
- if (cleanupCode !== 0) return cleanupCode;
4060
+ const exec = opts.dockerExec ?? spawnDocker;
4061
+ const filters = [
4062
+ `label=com.docker.compose.project=${projectName}`,
4063
+ `name=^${projectName}-`
4064
+ ];
4065
+ const { exitCode: rmExit } = await cleanupDockerObjects({
4066
+ projectName,
4067
+ filters,
4068
+ network: `${projectName}_default`,
4069
+ logger,
4070
+ exec
4071
+ });
4072
+ if (rmExit !== 0) return rmExit;
4073
+ const remaining = await findContainerIds(filters, exec);
4074
+ if (remaining.length > 0) {
4075
+ const warn = logger.warn ?? logger.info;
4076
+ warn(
4077
+ `ERROR: containers under project ${projectName} reappeared after removal.
4078
+ This typically means VS Code's Remote Containers extension is connected
4079
+ to this devcontainer and auto-recreated it. Close the dev container
4080
+ session in VS Code (Cmd+Shift+P \u2192 'Dev Containers: Close Remote Connection')
4081
+ and retry \`monoceros apply\`.`
4082
+ );
4083
+ return 1;
4084
+ }
4040
4085
  return runStart({
4041
4086
  root,
4042
4087
  ...opts.devcontainerSpawn ? { spawn: opts.devcontainerSpawn } : {},
@@ -4609,7 +4654,7 @@ ${sectionLine(label)}
4609
4654
  }
4610
4655
  const exitCode = await runContainerCycle(targetDir, {
4611
4656
  hasCompose: needsCompose(createOpts),
4612
- ...opts.cleanupSpawn !== void 0 ? { cleanupSpawn: opts.cleanupSpawn } : {},
4657
+ ...opts.dockerExec !== void 0 ? { dockerExec: opts.dockerExec } : {},
4613
4658
  ...opts.devcontainerSpawn !== void 0 ? { devcontainerSpawn: opts.devcontainerSpawn } : {},
4614
4659
  logger
4615
4660
  });
@@ -4713,7 +4758,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
4713
4758
  }
4714
4759
 
4715
4760
  // src/version.ts
4716
- var CLI_VERSION = true ? "1.11.5" : "dev";
4761
+ var CLI_VERSION = true ? "1.11.7" : "dev";
4717
4762
 
4718
4763
  // src/commands/_dispatch.ts
4719
4764
  import { consola as consola12 } from "consola";
@@ -6170,39 +6215,20 @@ async function runRemove(opts) {
6170
6215
  );
6171
6216
  }
6172
6217
  const projectName = composeProjectName(containerPath);
6173
- const dockerSpawn = opts.dockerSpawn ?? spawnBash;
6174
- const script = [
6175
- `set -u`,
6176
- `echo "[remove] tearing down docker project ${projectName}\u2026"`,
6177
- // Compose-mode containers, identified by the compose project label.
6178
- `by_label=$(docker ps -aq --filter "label=com.docker.compose.project=${projectName}" 2>/dev/null || true)`,
6179
- // Devcontainer-cli containers (image-mode workspace + feature-
6180
- // build intermediates) all carry this label, value = the absolute
6181
- // container-dir path. Most reliable anchor we have, because
6182
- // @devcontainers/cli lets Docker assign random names like
6183
- // 'kind_cerf' — neither the project-name nor the vsc-<name>-
6184
- // prefix filters below catch those.
6185
- // dockerLocalFolderLabel() lowercases the drive letter on Windows
6186
- // to match exactly what @devcontainers/cli stamps (`c:\…` vs our
6187
- // `path.join`-built `C:\…`). Docker label filters are strict
6188
- // byte-equality, so the case difference was leaving containers
6189
- // alive on `monoceros remove`.
6190
- `by_dc_label=$(docker ps -aq --filter "label=devcontainer.local_folder=${dockerLocalFolderLabel(containerPath)}" 2>/dev/null || true)`,
6191
- // Container-name prefix fallback (catches half-broken state).
6192
- `by_compose_name=$(docker ps -aq --filter "name=^${projectName}-" 2>/dev/null || true)`,
6193
- // Image-mode devcontainer-cli name fallback (only kicks in when
6194
- // the cli used a deterministic name — modern versions don't).
6195
- `by_image_name=$(docker ps -aq --filter "name=^vsc-${opts.name}-" 2>/dev/null || true)`,
6196
- `to_remove=$(printf "%s\\n%s\\n%s\\n%s\\n" "$by_label" "$by_dc_label" "$by_compose_name" "$by_image_name" | sort -u | grep -v "^$" || true)`,
6197
- // Unquoted `$to_remove` so bash word-splitting joins the
6198
- // newline-separated IDs with single spaces on echo. A `tr "\n" " "`
6199
- // pipe here used to do the same job but tripped MSYS2's arg
6200
- // translation on Git Bash for Windows ("tr: extra operand").
6201
- `if [ -n "$to_remove" ]; then echo "[remove] removing containers:" $to_remove; docker rm -f $to_remove >/dev/null || true; else echo "[remove] no containers found"; fi`,
6202
- `docker network rm ${projectName}_default 2>/dev/null && echo "[remove] network ${projectName}_default removed" || true`,
6203
- `echo "[remove] docker cleanup done"`
6204
- ].join("; ");
6205
- const dockerExitCode = await dockerSpawn(["-c", script], home);
6218
+ const dockerExec = opts.dockerExec ?? spawnDocker;
6219
+ const { exitCode: dockerExitCode } = await cleanupDockerObjects({
6220
+ projectName,
6221
+ filters: [
6222
+ `label=com.docker.compose.project=${projectName}`,
6223
+ `label=devcontainer.local_folder=${dockerLocalFolderLabel(containerPath)}`,
6224
+ `name=^${projectName}-`,
6225
+ `name=^vsc-${opts.name}-`
6226
+ ],
6227
+ network: `${projectName}_default`,
6228
+ logTag: "remove",
6229
+ logger,
6230
+ exec: dockerExec
6231
+ });
6206
6232
  let backupPath = null;
6207
6233
  if (!opts.noBackup && (hasYml || hasContainer)) {
6208
6234
  const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -6232,13 +6258,18 @@ async function runRemove(opts) {
6232
6258
  logger.info(
6233
6259
  `[remove] host-side rm hit ${code} on ${prettyPath(containerPath)}; using a throw-away alpine container to clean root-owned files\u2026`
6234
6260
  );
6235
- const exit = await dockerSpawn(
6236
- [
6237
- "-c",
6238
- `docker run --rm -v "${containerPath}":/target alpine:3.21 find /target -mindepth 1 -delete`
6239
- ],
6240
- home
6241
- );
6261
+ const { exitCode: exit } = await dockerExec([
6262
+ "run",
6263
+ "--rm",
6264
+ "-v",
6265
+ `${containerPath}:/target`,
6266
+ "alpine:3.21",
6267
+ "find",
6268
+ "/target",
6269
+ "-mindepth",
6270
+ "1",
6271
+ "-delete"
6272
+ ]);
6242
6273
  if (exit !== 0) {
6243
6274
  throw new Error(
6244
6275
  `docker-based cleanup of ${containerPath} exited ${exit}. Inspect with \`sudo ls -la ${containerPath}\` and clean manually.`