@getmonoceros/workbench 1.11.4 → 1.11.6

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,18 +3953,68 @@ 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"]
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");
3961
3968
  });
3962
- child.stdout?.pipe(createSecretMaskStream()).pipe(process.stdout);
3963
3969
  child.stderr?.pipe(createSecretMaskStream()).pipe(process.stderr);
3964
3970
  child.on("error", reject);
3965
- child.on("exit", (code) => resolve(code ?? 0));
3971
+ child.on(
3972
+ "exit",
3973
+ (code) => resolve({ exitCode: code ?? 0, stdout, stderr })
3974
+ );
3966
3975
  });
3967
3976
  };
3977
+ async function findContainerIds(filters, exec = spawnDocker) {
3978
+ const ids = /* @__PURE__ */ new Set();
3979
+ for (const filter of filters) {
3980
+ const result = await exec(["ps", "-aq", "--filter", filter]);
3981
+ if (result.exitCode !== 0) continue;
3982
+ for (const line of result.stdout.split(/\r?\n/)) {
3983
+ const id = line.trim();
3984
+ if (id) ids.add(id);
3985
+ }
3986
+ }
3987
+ return [...ids];
3988
+ }
3989
+ async function cleanupDockerObjects(opts) {
3990
+ const exec = opts.exec ?? spawnDocker;
3991
+ const tag = opts.logTag ?? "cleanup";
3992
+ opts.logger.info(`[${tag}] tearing down docker project ${opts.projectName}\u2026`);
3993
+ const ids = await findContainerIds(opts.filters, exec);
3994
+ let rmExit = 0;
3995
+ if (ids.length > 0) {
3996
+ opts.logger.info(`[${tag}] removing containers: ${ids.join(" ")}`);
3997
+ const rmResult = await exec(["rm", "-f", ...ids]);
3998
+ rmExit = rmResult.exitCode;
3999
+ } else {
4000
+ opts.logger.info(`[${tag}] no containers found`);
4001
+ }
4002
+ if (opts.network) {
4003
+ const netResult = await exec(["network", "rm", opts.network]);
4004
+ if (netResult.exitCode === 0) {
4005
+ opts.logger.info(`[${tag}] network ${opts.network} removed`);
4006
+ }
4007
+ }
4008
+ opts.logger.info(`[${tag}] docker cleanup done`);
4009
+ return { exitCode: rmExit, removedIds: ids };
4010
+ }
4011
+ function dockerLocalFolderLabel(p) {
4012
+ if (process.platform !== "win32") return p;
4013
+ return p.replace(
4014
+ /^([A-Z]):/,
4015
+ (_, drive) => `${drive.toLowerCase()}:`
4016
+ );
4017
+ }
3968
4018
  function composeProjectName(root) {
3969
4019
  return `${path11.basename(root)}_devcontainer`;
3970
4020
  }
@@ -4005,22 +4055,31 @@ async function runContainerCycle(root, opts) {
4005
4055
  logger.info(
4006
4056
  `Force-removing existing ${projectName} containers (volumes preserved)\u2026`
4007
4057
  );
4008
- const cleanupSpawn = opts.cleanupSpawn ?? spawnBash;
4009
- const script = [
4010
- `set -u`,
4011
- `echo "[cleanup] checking project ${projectName}\u2026"`,
4012
- `by_label=$(docker ps -aq --filter "label=com.docker.compose.project=${projectName}" 2>/dev/null || true)`,
4013
- `by_name=$(docker ps -aq --filter "name=^${projectName}-" 2>/dev/null || true)`,
4014
- `to_remove=$(printf "%s\\n%s\\n" "$by_label" "$by_name" | sort -u | grep -v "^$" || true)`,
4015
- `if [ -n "$to_remove" ]; then echo "[cleanup] removing: $(echo $to_remove | tr "\\n" " ")"; docker rm -f $to_remove >/dev/null || true; else echo "[cleanup] no containers to remove"; fi`,
4016
- `docker network rm ${projectName}_default 2>/dev/null && echo "[cleanup] network ${projectName}_default removed" || echo "[cleanup] network ${projectName}_default not present"`,
4017
- `remaining_label=$(docker ps -aq --filter "label=com.docker.compose.project=${projectName}" 2>/dev/null || true)`,
4018
- `remaining_name=$(docker ps -aq --filter "name=^${projectName}-" 2>/dev/null || true)`,
4019
- `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`,
4020
- `echo "[cleanup] done"`
4021
- ].join("; ");
4022
- const cleanupCode = await cleanupSpawn(["-c", script], root);
4023
- if (cleanupCode !== 0) return cleanupCode;
4058
+ const exec = opts.dockerExec ?? spawnDocker;
4059
+ const filters = [
4060
+ `label=com.docker.compose.project=${projectName}`,
4061
+ `name=^${projectName}-`
4062
+ ];
4063
+ const { exitCode: rmExit } = await cleanupDockerObjects({
4064
+ projectName,
4065
+ filters,
4066
+ network: `${projectName}_default`,
4067
+ logger,
4068
+ exec
4069
+ });
4070
+ if (rmExit !== 0) return rmExit;
4071
+ const remaining = await findContainerIds(filters, exec);
4072
+ if (remaining.length > 0) {
4073
+ const warn = logger.warn ?? logger.info;
4074
+ warn(
4075
+ `ERROR: containers under project ${projectName} reappeared after removal.
4076
+ This typically means VS Code's Remote Containers extension is connected
4077
+ to this devcontainer and auto-recreated it. Close the dev container
4078
+ session in VS Code (Cmd+Shift+P \u2192 'Dev Containers: Close Remote Connection')
4079
+ and retry \`monoceros apply\`.`
4080
+ );
4081
+ return 1;
4082
+ }
4024
4083
  return runStart({
4025
4084
  root,
4026
4085
  ...opts.devcontainerSpawn ? { spawn: opts.devcontainerSpawn } : {},
@@ -4593,7 +4652,7 @@ ${sectionLine(label)}
4593
4652
  }
4594
4653
  const exitCode = await runContainerCycle(targetDir, {
4595
4654
  hasCompose: needsCompose(createOpts),
4596
- ...opts.cleanupSpawn !== void 0 ? { cleanupSpawn: opts.cleanupSpawn } : {},
4655
+ ...opts.dockerExec !== void 0 ? { dockerExec: opts.dockerExec } : {},
4597
4656
  ...opts.devcontainerSpawn !== void 0 ? { devcontainerSpawn: opts.devcontainerSpawn } : {},
4598
4657
  logger
4599
4658
  });
@@ -4697,7 +4756,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
4697
4756
  }
4698
4757
 
4699
4758
  // src/version.ts
4700
- var CLI_VERSION = true ? "1.11.4" : "dev";
4759
+ var CLI_VERSION = true ? "1.11.6" : "dev";
4701
4760
 
4702
4761
  // src/commands/_dispatch.ts
4703
4762
  import { consola as consola12 } from "consola";
@@ -6154,30 +6213,20 @@ async function runRemove(opts) {
6154
6213
  );
6155
6214
  }
6156
6215
  const projectName = composeProjectName(containerPath);
6157
- const dockerSpawn = opts.dockerSpawn ?? spawnBash;
6158
- const script = [
6159
- `set -u`,
6160
- `echo "[remove] tearing down docker project ${projectName}\u2026"`,
6161
- // Compose-mode containers, identified by the compose project label.
6162
- `by_label=$(docker ps -aq --filter "label=com.docker.compose.project=${projectName}" 2>/dev/null || true)`,
6163
- // Devcontainer-cli containers (image-mode workspace + feature-
6164
- // build intermediates) all carry this label, value = the absolute
6165
- // container-dir path. Most reliable anchor we have, because
6166
- // @devcontainers/cli lets Docker assign random names like
6167
- // 'kind_cerf' — neither the project-name nor the vsc-<name>-
6168
- // prefix filters below catch those.
6169
- `by_dc_label=$(docker ps -aq --filter "label=devcontainer.local_folder=${containerPath}" 2>/dev/null || true)`,
6170
- // Container-name prefix fallback (catches half-broken state).
6171
- `by_compose_name=$(docker ps -aq --filter "name=^${projectName}-" 2>/dev/null || true)`,
6172
- // Image-mode devcontainer-cli name fallback (only kicks in when
6173
- // the cli used a deterministic name — modern versions don't).
6174
- `by_image_name=$(docker ps -aq --filter "name=^vsc-${opts.name}-" 2>/dev/null || true)`,
6175
- `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)`,
6176
- `if [ -n "$to_remove" ]; then echo "[remove] removing containers: $(echo $to_remove | tr "\\n" " ")"; docker rm -f $to_remove >/dev/null || true; else echo "[remove] no containers found"; fi`,
6177
- `docker network rm ${projectName}_default 2>/dev/null && echo "[remove] network ${projectName}_default removed" || true`,
6178
- `echo "[remove] docker cleanup done"`
6179
- ].join("; ");
6180
- const dockerExitCode = await dockerSpawn(["-c", script], home);
6216
+ const dockerExec = opts.dockerExec ?? spawnDocker;
6217
+ const { exitCode: dockerExitCode } = await cleanupDockerObjects({
6218
+ projectName,
6219
+ filters: [
6220
+ `label=com.docker.compose.project=${projectName}`,
6221
+ `label=devcontainer.local_folder=${dockerLocalFolderLabel(containerPath)}`,
6222
+ `name=^${projectName}-`,
6223
+ `name=^vsc-${opts.name}-`
6224
+ ],
6225
+ network: `${projectName}_default`,
6226
+ logTag: "remove",
6227
+ logger,
6228
+ exec: dockerExec
6229
+ });
6181
6230
  let backupPath = null;
6182
6231
  if (!opts.noBackup && (hasYml || hasContainer)) {
6183
6232
  const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -6207,13 +6256,18 @@ async function runRemove(opts) {
6207
6256
  logger.info(
6208
6257
  `[remove] host-side rm hit ${code} on ${prettyPath(containerPath)}; using a throw-away alpine container to clean root-owned files\u2026`
6209
6258
  );
6210
- const exit = await dockerSpawn(
6211
- [
6212
- "-c",
6213
- `docker run --rm -v "${containerPath}":/target alpine:3.21 find /target -mindepth 1 -delete`
6214
- ],
6215
- home
6216
- );
6259
+ const { exitCode: exit } = await dockerExec([
6260
+ "run",
6261
+ "--rm",
6262
+ "-v",
6263
+ `${containerPath}:/target`,
6264
+ "alpine:3.21",
6265
+ "find",
6266
+ "/target",
6267
+ "-mindepth",
6268
+ "1",
6269
+ "-delete"
6270
+ ]);
6217
6271
  if (exit !== 0) {
6218
6272
  throw new Error(
6219
6273
  `docker-based cleanup of ${containerPath} exited ${exit}. Inspect with \`sudo ls -la ${containerPath}\` and clean manually.`