@getmonoceros/workbench 1.30.0 → 1.31.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 CHANGED
@@ -727,11 +727,28 @@ var init_descriptor = __esm({
727
727
  * Coerced to string so authors can write bare YAML numbers
728
728
  * (`versions: [latest, 21, 17]`) without quoting.
729
729
  */
730
- versions: z2.array(z2.coerce.string()).optional()
730
+ versions: z2.array(z2.coerce.string()).optional(),
731
+ /**
732
+ * VS Code extensions to *recommend* (not auto-install) when this language
733
+ * is present, written to the `.code-workspace` `extensions.recommendations`
734
+ * (ADR 0016). List every editor variant where editors diverge: VS Code and
735
+ * VSCodium each resolve recommendation IDs against their own registry (MS
736
+ * Marketplace / Open VSX) and silently skip what they can't find, so an
737
+ * ID that only exists for one editor is simply ignored by the other. E.g.
738
+ * `[ms-python.python, ms-python.vscode-pylance]` — Codium drops Pylance.
739
+ */
740
+ vscodeExtensions: z2.array(z2.string()).optional()
731
741
  });
732
742
  ServiceBlockSchema = z2.object({
733
743
  image: z2.string().min(1),
734
744
  defaultPort: z2.number().int().positive().optional(),
745
+ /**
746
+ * Compose `command:` for the service container — the process to run
747
+ * instead of the image's default CMD. Baked into the expanded yml
748
+ * (visible + editable, unlike `deferStart`). E.g. Keycloak needs
749
+ * `start-dev --import-realm` because its image has no auto-start default.
750
+ */
751
+ command: z2.string().optional(),
735
752
  dataMount: z2.string().optional(),
736
753
  /**
737
754
  * Compose `user:` for the service container (e.g. `"0:0"`). Needed for
@@ -769,7 +786,32 @@ var init_descriptor = __esm({
769
786
  apt: z2.array(z2.string()).optional(),
770
787
  npm: z2.array(z2.string()).optional()
771
788
  }).optional(),
772
- vscodeExtensions: z2.array(z2.string()).optional()
789
+ vscodeExtensions: z2.array(z2.string()).optional(),
790
+ /**
791
+ * Example bind-mounts rendered as a COMMENTED `volumes:` scaffold in the
792
+ * generated yml (init / add-service), for the builder to uncomment and
793
+ * edit. NOT active volumes — the catalog can't know the builder's repo
794
+ * path. Used by services that need a project file but can't auto-wire it
795
+ * (e.g. Keycloak's realm.json / theme). Each entry is a compose volume
796
+ * spec, e.g. `projects/<app>/keycloak/realm.json:/opt/keycloak/data/import/<app>.json:ro`.
797
+ */
798
+ exampleVolumes: z2.array(z2.string()).optional(),
799
+ /**
800
+ * Start this service in a SECOND WAVE, host-side, *after* `devcontainer
801
+ * up` (and thus the in-container repo clone in post-create) has
802
+ * finished — instead of together with the workspace at `compose up`.
803
+ * For a service that bind-mounts a file from a cloned repo (e.g.
804
+ * Keycloak's `realm.json`, a Postgres `init.sql`): the file does not
805
+ * exist at the normal parallel start, but is on disk by the time the
806
+ * second wave runs. See ADR 0025.
807
+ *
808
+ * Deliberately a HIDDEN, descriptor-only field: it is NOT exposed in the
809
+ * user-facing yml schema and not baked into the expanded service object.
810
+ * The start paths resolve it by catalog lookup on the service name
811
+ * (`serviceDefersStart`). Caveat: a deferred service is NOT reachable
812
+ * during the workspace's post-create.
813
+ */
814
+ deferStart: z2.boolean().optional()
773
815
  });
774
816
  PersistentHomeFileSchema = z2.object({
775
817
  path: z2.string().min(1),
@@ -2219,6 +2261,12 @@ function knownLanguages() {
2219
2261
  function knownServices() {
2220
2262
  return Object.keys(SERVICE_CATALOG).sort();
2221
2263
  }
2264
+ function serviceDefersStart(name) {
2265
+ return SERVICE_CATALOG[name]?.deferStart === true;
2266
+ }
2267
+ function curatedServiceExampleVolumes(name) {
2268
+ return SERVICE_CATALOG[name]?.exampleVolumes ?? [];
2269
+ }
2222
2270
  function resolveService(entry2) {
2223
2271
  return {
2224
2272
  name: entry2.name,
@@ -2254,6 +2302,7 @@ function expandCuratedService(name) {
2254
2302
  } : {},
2255
2303
  ...def.dataMount ? { volumes: [`data:${def.dataMount}`] } : {},
2256
2304
  ...def.user ? { user: def.user } : {},
2305
+ ...def.command ? { command: def.command } : {},
2257
2306
  ...def.healthcheck ? { healthcheck: def.healthcheck } : {},
2258
2307
  // Bake the connection-env templates into the yml (suffix → template) so
2259
2308
  // they travel with the service: a renamed/duplicated instance keeps them,
@@ -2308,7 +2357,7 @@ function deriveServiceName(image) {
2308
2357
  const noTag = lastSegment.split("@")[0].split(":")[0];
2309
2358
  return noTag.toLowerCase().replace(/[^a-z0-9_-]/g, "-");
2310
2359
  }
2311
- 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;
2360
+ 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, DEFERRED_SERVICE_PROFILE;
2312
2361
  var init_catalog = __esm({
2313
2362
  "src/create/catalog.ts"() {
2314
2363
  "use strict";
@@ -2338,7 +2387,8 @@ var init_catalog = __esm({
2338
2387
  feature: c.descriptor.language.feature,
2339
2388
  ...Object.keys(defaults).length > 0 ? { defaultOptions: defaults } : {},
2340
2389
  ...Object.keys(ymlOptions).length > 0 ? { ymlOptions } : {},
2341
- ...c.descriptor.language.defaultVersion !== void 0 ? { defaultVersion: c.descriptor.language.defaultVersion } : {}
2390
+ ...c.descriptor.language.defaultVersion !== void 0 ? { defaultVersion: c.descriptor.language.defaultVersion } : {},
2391
+ ...c.descriptor.language.vscodeExtensions ? { vscodeExtensions: c.descriptor.language.vscodeExtensions } : {}
2342
2392
  };
2343
2393
  return [key, entry2];
2344
2394
  })
@@ -2364,11 +2414,15 @@ var init_catalog = __esm({
2364
2414
  defaultPort: svc.defaultPort,
2365
2415
  ...svc.vscodeExtensions ? { vscodeExtensions: svc.vscodeExtensions } : {},
2366
2416
  ...svc.connectionEnv ? { connectionEnv: svc.connectionEnv } : {},
2367
- ...svc.client ? { client: svc.client } : {}
2417
+ ...svc.client ? { client: svc.client } : {},
2418
+ ...svc.command ? { command: svc.command } : {},
2419
+ ...svc.exampleVolumes ? { exampleVolumes: svc.exampleVolumes } : {},
2420
+ ...svc.deferStart ? { deferStart: true } : {}
2368
2421
  };
2369
2422
  return [key, entry2];
2370
2423
  })
2371
2424
  );
2425
+ DEFERRED_SERVICE_PROFILE = "monoceros-deferred";
2372
2426
  }
2373
2427
  });
2374
2428
 
@@ -2428,6 +2482,13 @@ function renderCustomService(name, image) {
2428
2482
  ].join("\n");
2429
2483
  return { bodyLines, comment };
2430
2484
  }
2485
+ function exampleVolumesComment(exampleVolumes) {
2486
+ if (exampleVolumes.length === 0) return void 0;
2487
+ return [
2488
+ " volumes: # uncomment + edit to mount your realm/theme",
2489
+ ...exampleVolumes.map((v) => ` - ${v}`)
2490
+ ].join("\n");
2491
+ }
2431
2492
  function customServiceHint(name) {
2432
2493
  return `'${name}' is a custom image \u2014 Monoceros doesn't know its env, ports or volumes. Review the commented block under services[].${name} in the yml and fill in what the image needs.`;
2433
2494
  }
@@ -3109,11 +3170,12 @@ function buildDevcontainerJson(opts, dockerMode = "rootful") {
3109
3170
  const workspaceEnv = featureWorkspaceEnv(resolvedFeatures);
3110
3171
  const containerEnvField = Object.keys(workspaceEnv).length > 0 ? { containerEnv: workspaceEnv } : void 0;
3111
3172
  if (needsCompose(opts)) {
3173
+ const eagerServices = opts.services.filter((s) => !serviceDefersStart(s.name)).map((s) => s.name);
3112
3174
  return {
3113
3175
  name: opts.name,
3114
3176
  dockerComposeFile: "compose.yaml",
3115
3177
  service: "workspace",
3116
- ...opts.services.length > 0 ? { runServices: opts.services.map((s) => s.name) } : {},
3178
+ ...eagerServices.length > 0 ? { runServices: eagerServices } : {},
3117
3179
  workspaceFolder: `/workspaces/${opts.name}`,
3118
3180
  remoteUser: "node",
3119
3181
  forwardPorts: ports,
@@ -3212,6 +3274,10 @@ function buildComposeYaml(opts, dockerMode = "rootful") {
3212
3274
  for (const svc of opts.services) {
3213
3275
  lines.push(` ${svc.name}:`);
3214
3276
  lines.push(` image: ${svc.image}`);
3277
+ if (serviceDefersStart(svc.name)) {
3278
+ lines.push(" profiles:");
3279
+ lines.push(` - ${DEFERRED_SERVICE_PROFILE}`);
3280
+ }
3215
3281
  if (svc.user !== void 0) {
3216
3282
  lines.push(` user: ${composeScalar(svc.user)}`);
3217
3283
  }
@@ -3273,9 +3339,22 @@ function extractRepoHost(url) {
3273
3339
  }
3274
3340
  function computeExtensionRecommendations(opts) {
3275
3341
  const recs = /* @__PURE__ */ new Set();
3342
+ for (const langSpec of opts.languages) {
3343
+ const parsed = parseLanguageSpec(langSpec);
3344
+ if (!parsed) continue;
3345
+ for (const ext of LANGUAGE_CATALOG[parsed.name]?.vscodeExtensions ?? []) {
3346
+ recs.add(ext);
3347
+ }
3348
+ }
3276
3349
  for (const svc of opts.services) {
3277
- const catalogEntry = SERVICE_CATALOG[svc.name];
3278
- for (const ext of catalogEntry?.vscodeExtensions ?? []) {
3350
+ for (const ext of SERVICE_CATALOG[svc.name]?.vscodeExtensions ?? []) {
3351
+ recs.add(ext);
3352
+ }
3353
+ }
3354
+ for (const rawRef of Object.keys(opts.features ?? {})) {
3355
+ const match = matchMonocerosFeature(rawRef);
3356
+ const descriptor = match ? featureDescriptor(match.name) : void 0;
3357
+ for (const ext of descriptor?.feature?.vscodeExtensions ?? []) {
3279
3358
  recs.add(ext);
3280
3359
  }
3281
3360
  }
@@ -4130,7 +4209,7 @@ async function runAddService(input) {
4130
4209
  const image = curated ? expandCuratedService(arg).image : arg;
4131
4210
  const custom = curated ? null : renderCustomService(name, arg);
4132
4211
  const bodyLines = curated ? renderServiceObjectBody({ ...expandCuratedService(arg), name }) : custom.bodyLines;
4133
- const scaffoldComment = curated ? void 0 : custom.comment;
4212
+ const scaffoldComment = curated ? exampleVolumesComment(curatedServiceExampleVolumes(arg)) : custom.comment;
4134
4213
  const result = await mutate(input, (doc) => {
4135
4214
  const r = addServiceEntryToDoc(
4136
4215
  doc,
@@ -6738,6 +6817,25 @@ import { existsSync as existsSync9 } from "fs";
6738
6817
  import path17 from "path";
6739
6818
  import { Writable as Writable3 } from "stream";
6740
6819
  import { consola as consola10 } from "consola";
6820
+ function spawnDockerComposeTo(opts) {
6821
+ return (args, cwd) => new Promise((resolve, reject) => {
6822
+ const child = spawn6("docker", ["compose", ...args], {
6823
+ cwd,
6824
+ stdio: ["inherit", "pipe", "pipe"]
6825
+ });
6826
+ const route = (src, screen) => {
6827
+ if (!src) return;
6828
+ src.pipe(createSecretMaskStream()).on("data", (chunk) => {
6829
+ opts.logSink?.write(chunk);
6830
+ if (!opts.silent) screen.write(chunk);
6831
+ });
6832
+ };
6833
+ route(child.stdout, process.stdout);
6834
+ route(child.stderr, process.stderr);
6835
+ child.on("error", reject);
6836
+ child.on("exit", (code) => resolve(code ?? 0));
6837
+ });
6838
+ }
6741
6839
  async function findContainerIds(filters, exec = spawnDocker) {
6742
6840
  const ids = /* @__PURE__ */ new Set();
6743
6841
  for (const filter of filters) {
@@ -6813,6 +6911,32 @@ async function runComposeAction(buildSubArgs, opts) {
6813
6911
  const subArgs = buildSubArgs(opts.service);
6814
6912
  return spawnFn(["-f", composeFile, "-p", projectName, ...subArgs], opts.root);
6815
6913
  }
6914
+ async function startDeferredServices(opts) {
6915
+ if (opts.services.length === 0) return 0;
6916
+ const { composeFile, projectName } = resolveCompose(opts.root);
6917
+ const spawnFn = opts.spawn ?? spawnDockerComposeTo({
6918
+ ...opts.logSink ? { logSink: opts.logSink } : {},
6919
+ ...opts.silent ? { silent: true } : {}
6920
+ });
6921
+ opts.logger?.info(
6922
+ `Starting deferred service(s): ${opts.services.join(", ")}\u2026`
6923
+ );
6924
+ return spawnFn(
6925
+ [
6926
+ "-f",
6927
+ composeFile,
6928
+ "-p",
6929
+ projectName,
6930
+ "--profile",
6931
+ DEFERRED_SERVICE_PROFILE,
6932
+ "up",
6933
+ "-d",
6934
+ "--quiet-pull",
6935
+ ...opts.services
6936
+ ],
6937
+ opts.root
6938
+ );
6939
+ }
6816
6940
  async function runStart(opts) {
6817
6941
  assertDevcontainer(opts.root);
6818
6942
  const logger = opts.logger ?? { info: (msg) => consola10.info(msg) };
@@ -7040,6 +7164,7 @@ var init_compose = __esm({
7040
7164
  "use strict";
7041
7165
  init_proxy();
7042
7166
  init_mask_secrets();
7167
+ init_catalog();
7043
7168
  init_cli();
7044
7169
  init_proxy();
7045
7170
  spawnDockerCompose = (args, cwd) => {
@@ -7116,7 +7241,7 @@ var init_images = __esm({
7116
7241
  });
7117
7242
 
7118
7243
  // src/config/machine-state.ts
7119
- import { promises as fsp4 } from "fs";
7244
+ import { promises as fsp4, readFileSync as readFileSync6 } from "fs";
7120
7245
  import path18 from "path";
7121
7246
  function machineStatePath(home = monocerosHome()) {
7122
7247
  return path18.join(home, ".machine-state.json");
@@ -7132,6 +7257,18 @@ async function readMachineState(home = monocerosHome()) {
7132
7257
  }
7133
7258
  return {};
7134
7259
  }
7260
+ function readMachineStateSync(home = monocerosHome()) {
7261
+ try {
7262
+ const parsed = JSON.parse(
7263
+ readFileSync6(machineStatePath(home), "utf8")
7264
+ );
7265
+ if (typeof parsed === "object" && parsed !== null) {
7266
+ return parsed;
7267
+ }
7268
+ } catch {
7269
+ }
7270
+ return {};
7271
+ }
7135
7272
  async function writeMachineState(state, home = monocerosHome()) {
7136
7273
  await fsp4.writeFile(
7137
7274
  machineStatePath(home),
@@ -7147,6 +7284,12 @@ async function recordBuiltImage(record, home = monocerosHome()) {
7147
7284
  state.builtImages = [...rest, record];
7148
7285
  await writeMachineState(state, home);
7149
7286
  }
7287
+ async function recordVersionCheck(opts, home = monocerosHome()) {
7288
+ const state = await readMachineState(home);
7289
+ state.lastVersionCheckAt = opts.nowIso;
7290
+ if (opts.latestVersion) state.latestVersion = opts.latestVersion;
7291
+ await writeMachineState(state, home);
7292
+ }
7150
7293
  async function markUpgraded(nowIso, home = monocerosHome()) {
7151
7294
  const state = await readMachineState(home);
7152
7295
  state.lastUpgradeAt = nowIso;
@@ -7686,6 +7829,30 @@ Fix the value in the env file (or the yml).`
7686
7829
  ...progress ? { progressSink: progress.streamSink, silent: true } : {},
7687
7830
  logger: internalLogger
7688
7831
  });
7832
+ if (exitCode === 0) {
7833
+ const deferred = createOpts.services.filter((s) => serviceDefersStart(s.name)).map((s) => s.name);
7834
+ if (deferred.length > 0) {
7835
+ if (progress) progress.setPhase("starting services\u2026");
7836
+ try {
7837
+ const deferExit = await startDeferredServices({
7838
+ root: targetDir,
7839
+ services: deferred,
7840
+ logSink: applyLog.sink,
7841
+ silent: progress !== null,
7842
+ logger: internalLogger
7843
+ });
7844
+ if (deferExit !== 0) {
7845
+ containerLogger.warn?.(
7846
+ `Deferred service(s) ${deferred.join(", ")} did not start cleanly (exit ${deferExit}). The workspace is up; bring them up with \`monoceros start ${opts.name}\`.`
7847
+ );
7848
+ }
7849
+ } catch (err) {
7850
+ containerLogger.warn?.(
7851
+ `Could not start deferred service(s) ${deferred.join(", ")}: ${err instanceof Error ? err.message : String(err)}. The workspace is up.`
7852
+ );
7853
+ }
7854
+ }
7855
+ }
7689
7856
  if (progress) {
7690
7857
  if (exitCode === 0) {
7691
7858
  progress.succeed();
@@ -7880,7 +8047,7 @@ var init_apply = __esm({
7880
8047
 
7881
8048
  // src/devcontainer/browser-bridge.ts
7882
8049
  import { spawn as spawn9 } from "child_process";
7883
- import { existsSync as existsSync11, promises as fsp5, readFileSync as readFileSync6 } from "fs";
8050
+ import { existsSync as existsSync11, promises as fsp5, readFileSync as readFileSync7 } from "fs";
7884
8051
  import http from "http";
7885
8052
  import path20 from "path";
7886
8053
  function parseCallbackTarget(authUrl) {
@@ -7983,7 +8150,7 @@ exit 0
7983
8150
  if (!existsSync11(urlFile)) return;
7984
8151
  let content = "";
7985
8152
  try {
7986
- content = readFileSync6(urlFile, "utf8");
8153
+ content = readFileSync7(urlFile, "utf8");
7987
8154
  } catch {
7988
8155
  return;
7989
8156
  }
@@ -8180,7 +8347,7 @@ var CLI_VERSION;
8180
8347
  var init_version = __esm({
8181
8348
  "src/version.ts"() {
8182
8349
  "use strict";
8183
- CLI_VERSION = true ? "1.30.0" : "dev";
8350
+ CLI_VERSION = true ? "1.31.0" : "dev";
8184
8351
  }
8185
8352
  });
8186
8353
 
@@ -8825,6 +8992,140 @@ var init_complete = __esm({
8825
8992
  }
8826
8993
  });
8827
8994
 
8995
+ // src/update/notifier.ts
8996
+ import { spawn as spawn11 } from "child_process";
8997
+ function isNewerVersion(latest, current) {
8998
+ const parse = (v) => {
8999
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
9000
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
9001
+ };
9002
+ const a = parse(latest);
9003
+ const b = parse(current);
9004
+ if (!a || !b) return false;
9005
+ for (let i = 0; i < 3; i++) {
9006
+ if (a[i] !== b[i]) return a[i] > b[i];
9007
+ }
9008
+ return false;
9009
+ }
9010
+ function formatUpdateNotice(latest, current) {
9011
+ return `
9012
+ \u2B06 Monoceros ${latest} is available (you have ${current}).
9013
+ Update: ${INSTALL_COMMAND}
9014
+ `;
9015
+ }
9016
+ function decideUpdateAction(state, currentVersion, now, intervalMs = CHECK_INTERVAL_MS) {
9017
+ const notice = state.latestVersion && isNewerVersion(state.latestVersion, currentVersion) ? formatUpdateNotice(state.latestVersion, currentVersion) : null;
9018
+ const last = state.lastVersionCheckAt ? new Date(state.lastVersionCheckAt).getTime() : NaN;
9019
+ const refresh = !Number.isFinite(last) || now.getTime() - last > intervalMs;
9020
+ return { notice, refresh };
9021
+ }
9022
+ function scheduleUpdateNotice(opts) {
9023
+ try {
9024
+ if (process.env[OPT_OUT_ENV]) return;
9025
+ if (opts.currentVersion === "dev") return;
9026
+ if (!process.stdout.isTTY) return;
9027
+ if (opts.commandName === void 0) return;
9028
+ if (SKIP_COMMANDS.has(opts.commandName)) return;
9029
+ const argv = opts.argv ?? process.argv.slice(2);
9030
+ if (argv.some((a) => ["-h", "--help", "-v", "--version"].includes(a))) {
9031
+ return;
9032
+ }
9033
+ const state = readMachineStateSync();
9034
+ const { notice, refresh } = decideUpdateAction(
9035
+ state,
9036
+ opts.currentVersion,
9037
+ opts.now ?? /* @__PURE__ */ new Date()
9038
+ );
9039
+ if (notice) {
9040
+ process.on("exit", () => {
9041
+ try {
9042
+ process.stderr.write(notice);
9043
+ } catch {
9044
+ }
9045
+ });
9046
+ }
9047
+ if (refresh) spawnBackgroundCheck();
9048
+ } catch {
9049
+ }
9050
+ }
9051
+ function spawnBackgroundCheck() {
9052
+ const self = process.argv[1];
9053
+ if (!self) return;
9054
+ try {
9055
+ const child = spawn11(process.execPath, [self, "__update-check"], {
9056
+ detached: true,
9057
+ stdio: "ignore"
9058
+ });
9059
+ child.unref();
9060
+ } catch {
9061
+ }
9062
+ }
9063
+ async function runUpdateCheck(now = /* @__PURE__ */ new Date(), home) {
9064
+ let latestVersion;
9065
+ try {
9066
+ const controller = new AbortController();
9067
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
9068
+ try {
9069
+ const res = await fetch(REGISTRY_URL, { signal: controller.signal });
9070
+ if (res.ok) {
9071
+ const json = await res.json();
9072
+ if (typeof json.version === "string") latestVersion = json.version;
9073
+ }
9074
+ } finally {
9075
+ clearTimeout(timer);
9076
+ }
9077
+ } catch {
9078
+ }
9079
+ try {
9080
+ await recordVersionCheck(
9081
+ {
9082
+ ...latestVersion ? { latestVersion } : {},
9083
+ nowIso: now.toISOString()
9084
+ },
9085
+ home
9086
+ );
9087
+ } catch {
9088
+ }
9089
+ }
9090
+ var PACKAGE, REGISTRY_URL, INSTALL_COMMAND, CHECK_INTERVAL_MS, OPT_OUT_ENV, FETCH_TIMEOUT_MS, SKIP_COMMANDS;
9091
+ var init_notifier = __esm({
9092
+ "src/update/notifier.ts"() {
9093
+ "use strict";
9094
+ init_machine_state();
9095
+ PACKAGE = "@getmonoceros/workbench";
9096
+ REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE}/latest`;
9097
+ INSTALL_COMMAND = "curl -fsSL https://raw.githubusercontent.com/getmonoceros/workbench/main/install.sh | bash";
9098
+ CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
9099
+ OPT_OUT_ENV = "MONOCEROS_NO_UPDATE_NOTIFIER";
9100
+ FETCH_TIMEOUT_MS = 5e3;
9101
+ SKIP_COMMANDS = /* @__PURE__ */ new Set(["__complete", "__update-check", "completion"]);
9102
+ }
9103
+ });
9104
+
9105
+ // src/commands/__update-check.ts
9106
+ import { defineCommand as defineCommand11 } from "citty";
9107
+ var __updateCheckCommand;
9108
+ var init_update_check = __esm({
9109
+ "src/commands/__update-check.ts"() {
9110
+ "use strict";
9111
+ init_notifier();
9112
+ __updateCheckCommand = defineCommand11({
9113
+ meta: {
9114
+ name: "__update-check",
9115
+ group: "internal",
9116
+ hidden: true,
9117
+ description: "Internal: refresh the cached latest-version (background)."
9118
+ },
9119
+ async run() {
9120
+ try {
9121
+ await runUpdateCheck();
9122
+ } catch {
9123
+ }
9124
+ }
9125
+ });
9126
+ }
9127
+ });
9128
+
8828
9129
  // src/init/generator.ts
8829
9130
  function generateComposedYml(name, composed, lookupManifest, repoUrls = [], ports = []) {
8830
9131
  const lines = [];
@@ -8990,6 +9291,12 @@ function generateDocumentedYml(name, catalog, lookupManifest, repoUrls = [], por
8990
9291
  const body = renderServiceObjectBody(expandCuratedService(svc));
8991
9292
  lines.push(`# - ${body[0]}`);
8992
9293
  for (const line of body.slice(1)) lines.push(`# ${line}`);
9294
+ const exComment = exampleVolumesComment(
9295
+ curatedServiceExampleVolumes(svc)
9296
+ );
9297
+ if (exComment) {
9298
+ for (const cl of exComment.split("\n")) lines.push(`# ${cl}`);
9299
+ }
8993
9300
  }
8994
9301
  }
8995
9302
  lines.push("");
@@ -9109,6 +9416,12 @@ function pushServiceEntry(out, svc) {
9109
9416
  const body = renderServiceObjectBody(expandCuratedService(svc.name));
9110
9417
  out.push(` - ${body[0]}`);
9111
9418
  for (const line of body.slice(1)) out.push(` ${line}`);
9419
+ const exComment = exampleVolumesComment(
9420
+ curatedServiceExampleVolumes(svc.name)
9421
+ );
9422
+ if (exComment) {
9423
+ for (const cl of exComment.split("\n")) out.push(` #${cl}`);
9424
+ }
9112
9425
  }
9113
9426
  function pushGitIdentityBlock(lines) {
9114
9427
  pushSectionHeader(
@@ -9398,19 +9711,19 @@ function resolveInitAptPackages(entries) {
9398
9711
  }
9399
9712
  function resolveInitServices(entries) {
9400
9713
  const out = [];
9401
- const byName = /* @__PURE__ */ new Map();
9714
+ const byName2 = /* @__PURE__ */ new Map();
9402
9715
  for (const raw of entries) {
9403
9716
  const e = raw.trim();
9404
9717
  if (!e) continue;
9405
9718
  const svc = isCuratedService(e) ? { kind: "curated", name: e } : { kind: "custom", name: deriveServiceName(e), image: e };
9406
- const existing = byName.get(svc.name);
9719
+ const existing = byName2.get(svc.name);
9407
9720
  if (existing) {
9408
9721
  if (existing.kind === svc.kind && existing.image === svc.image) continue;
9409
9722
  throw new Error(
9410
9723
  `Two --with-services entries resolve to the service name '${svc.name}'. Add one after init with \`monoceros add-service ${"<name>"} <image> --as=<other>\`.`
9411
9724
  );
9412
9725
  }
9413
- byName.set(svc.name, svc);
9726
+ byName2.set(svc.name, svc);
9414
9727
  out.push(svc);
9415
9728
  }
9416
9729
  return out;
@@ -9466,7 +9779,7 @@ var init_init = __esm({
9466
9779
  });
9467
9780
 
9468
9781
  // src/commands/init.ts
9469
- import { defineCommand as defineCommand11 } from "citty";
9782
+ import { defineCommand as defineCommand12 } from "citty";
9470
9783
  import { consola as consola17 } from "consola";
9471
9784
  function collectListFlag(flag, rawArgs) {
9472
9785
  const eq = `${flag}=`;
@@ -9532,7 +9845,7 @@ var init_init2 = __esm({
9532
9845
  "src/commands/init.ts"() {
9533
9846
  "use strict";
9534
9847
  init_init();
9535
- initCommand = defineCommand11({
9848
+ initCommand = defineCommand12({
9536
9849
  meta: {
9537
9850
  name: "init",
9538
9851
  group: "lifecycle",
@@ -9640,8 +9953,76 @@ var init_expand = __esm({
9640
9953
  }
9641
9954
  });
9642
9955
 
9956
+ // src/catalog/catalog-json.ts
9957
+ function projectOption(key, spec) {
9958
+ if (spec.surface === "silent") return null;
9959
+ const out = {
9960
+ key,
9961
+ type: spec.type,
9962
+ surface: spec.surface
9963
+ };
9964
+ if (spec.default !== void 0) out.default = spec.default;
9965
+ if (spec.description !== void 0) out.description = spec.description;
9966
+ if (spec.proposals !== void 0) out.proposals = spec.proposals;
9967
+ return out;
9968
+ }
9969
+ function projectOptions(options) {
9970
+ return Object.entries(options).map(([key, spec]) => projectOption(key, spec)).filter((o) => o !== null).sort((a, b) => a.key.localeCompare(b.key));
9971
+ }
9972
+ function byName(a, b) {
9973
+ return a.name.localeCompare(b.name);
9974
+ }
9975
+ function buildCatalogJson(catalog, cliVersion) {
9976
+ const languages = [];
9977
+ const services = [];
9978
+ const features = [];
9979
+ for (const { descriptor: d } of catalog.values()) {
9980
+ const base = {
9981
+ name: d.name ?? d.id,
9982
+ displayName: d.displayName,
9983
+ description: d.description,
9984
+ options: projectOptions(d.options)
9985
+ };
9986
+ if (d.documentationURL !== void 0) {
9987
+ base.documentationURL = d.documentationURL;
9988
+ }
9989
+ if (d.category === "language" && d.language) {
9990
+ const lang = { ...base };
9991
+ if (d.language.defaultVersion !== void 0) {
9992
+ lang.defaultVersion = d.language.defaultVersion;
9993
+ }
9994
+ if (d.language.versions !== void 0) {
9995
+ lang.versions = d.language.versions;
9996
+ }
9997
+ languages.push(lang);
9998
+ } else if (d.category === "service" && d.service) {
9999
+ const svc = { ...base };
10000
+ if (d.service.defaultPort !== void 0) {
10001
+ svc.defaultPort = d.service.defaultPort;
10002
+ }
10003
+ services.push(svc);
10004
+ } else if (d.category === "feature") {
10005
+ features.push({ ...base, presets: Object.keys(d.presets ?? {}).sort() });
10006
+ }
10007
+ }
10008
+ return {
10009
+ schemaVersion: CATALOG_JSON_SCHEMA_VERSION,
10010
+ cliVersion,
10011
+ languages: languages.sort(byName),
10012
+ services: services.sort(byName),
10013
+ features: features.sort(byName)
10014
+ };
10015
+ }
10016
+ var CATALOG_JSON_SCHEMA_VERSION;
10017
+ var init_catalog_json = __esm({
10018
+ "src/catalog/catalog-json.ts"() {
10019
+ "use strict";
10020
+ CATALOG_JSON_SCHEMA_VERSION = 1;
10021
+ }
10022
+ });
10023
+
9643
10024
  // src/commands/list-components.ts
9644
- import { defineCommand as defineCommand12 } from "citty";
10025
+ import { defineCommand as defineCommand13 } from "citty";
9645
10026
  import { consola as consola18 } from "consola";
9646
10027
  var CATEGORY_LABELS, CATEGORY_ORDER, listComponentsCommand;
9647
10028
  var init_list_components = __esm({
@@ -9649,6 +10030,8 @@ var init_list_components = __esm({
9649
10030
  "use strict";
9650
10031
  init_load();
9651
10032
  init_expand();
10033
+ init_catalog_json();
10034
+ init_version();
9652
10035
  init_format();
9653
10036
  CATEGORY_LABELS = {
9654
10037
  language: "Languages",
@@ -9660,16 +10043,28 @@ var init_list_components = __esm({
9660
10043
  "service",
9661
10044
  "feature"
9662
10045
  ];
9663
- listComponentsCommand = defineCommand12({
10046
+ listComponentsCommand = defineCommand13({
9664
10047
  meta: {
9665
10048
  name: "list-components",
9666
10049
  group: "discovery",
9667
- description: "Print the components catalog used by `monoceros init --with-languages=\u2026 / --with-services=\u2026 / --with-features=\u2026`, grouped by category (Languages, Services, Features). Component names render in cyan, descriptions in default colour; when piped, the formatting drops out and lines become `name<TAB>description` for grep/awk-friendly consumption."
10050
+ description: "Print the components catalog used by `monoceros init --with-languages=\u2026 / --with-services=\u2026 / --with-features=\u2026`, grouped by category (Languages, Services, Features). Component names render in cyan, descriptions in default colour; when piped, the formatting drops out and lines become `name<TAB>description` for grep/awk-friendly consumption. `--json` emits the full catalog (options, versions, presets) as a machine-readable document \u2014 the same shape published at getmonoceros.build/catalog.json."
9668
10051
  },
9669
- args: {},
9670
- async run() {
10052
+ args: {
10053
+ json: {
10054
+ type: "boolean",
10055
+ description: "Emit the catalog as a JSON document (name, options, versions, presets per component) instead of the human-readable listing."
10056
+ }
10057
+ },
10058
+ async run({ args }) {
9671
10059
  try {
9672
- const catalog = expandSelectable(await loadDescriptorCatalog());
10060
+ const descriptors = await loadDescriptorCatalog();
10061
+ if (args.json) {
10062
+ const doc = buildCatalogJson(descriptors, CLI_VERSION);
10063
+ process.stdout.write(`${JSON.stringify(doc, null, 2)}
10064
+ `);
10065
+ process.exit(0);
10066
+ }
10067
+ const catalog = expandSelectable(descriptors);
9673
10068
  if (catalog.size === 0) {
9674
10069
  consola18.warn(
9675
10070
  "No components found. The workbench checkout looks incomplete."
@@ -9731,14 +10126,14 @@ var init_list_components = __esm({
9731
10126
  });
9732
10127
 
9733
10128
  // src/commands/logs.ts
9734
- import { spawn as spawn11 } from "child_process";
10129
+ import { spawn as spawn12 } from "child_process";
9735
10130
  import { existsSync as existsSync16 } from "fs";
9736
10131
  import path25 from "path";
9737
- import { defineCommand as defineCommand13 } from "citty";
10132
+ import { defineCommand as defineCommand14 } from "citty";
9738
10133
  function tailLogFile(file, follow) {
9739
10134
  const [cmd, args] = follow ? ["tail", ["-F", file]] : ["cat", [file]];
9740
10135
  return new Promise((resolve, reject) => {
9741
- const child = spawn11(cmd, args, { stdio: "inherit" });
10136
+ const child = spawn12(cmd, args, { stdio: "inherit" });
9742
10137
  child.on("error", reject);
9743
10138
  child.on("exit", (code) => resolve(code ?? 0));
9744
10139
  });
@@ -9750,7 +10145,7 @@ var init_logs = __esm({
9750
10145
  init_paths();
9751
10146
  init_compose();
9752
10147
  init_dispatch();
9753
- logsCommand = defineCommand13({
10148
+ logsCommand = defineCommand14({
9754
10149
  meta: {
9755
10150
  name: "logs",
9756
10151
  group: "run",
@@ -9794,14 +10189,14 @@ var init_logs = __esm({
9794
10189
  });
9795
10190
 
9796
10191
  // src/commands/open.ts
9797
- import { defineCommand as defineCommand14 } from "citty";
10192
+ import { defineCommand as defineCommand15 } from "citty";
9798
10193
  var openCommand;
9799
10194
  var init_open2 = __esm({
9800
10195
  "src/commands/open.ts"() {
9801
10196
  "use strict";
9802
10197
  init_open();
9803
10198
  init_dispatch();
9804
- openCommand = defineCommand14({
10199
+ openCommand = defineCommand15({
9805
10200
  meta: {
9806
10201
  name: "open",
9807
10202
  group: "run",
@@ -9827,7 +10222,7 @@ var init_open2 = __esm({
9827
10222
  });
9828
10223
 
9829
10224
  // src/commands/port.ts
9830
- import { defineCommand as defineCommand15 } from "citty";
10225
+ import { defineCommand as defineCommand16 } from "citty";
9831
10226
  import { consola as consola19 } from "consola";
9832
10227
  async function runPortListing(opts) {
9833
10228
  const out = opts.out ?? process.stdout;
@@ -9889,7 +10284,7 @@ var init_port = __esm({
9889
10284
  init_schema();
9890
10285
  init_dynamic();
9891
10286
  init_format();
9892
- portCommand = defineCommand15({
10287
+ portCommand = defineCommand16({
9893
10288
  meta: {
9894
10289
  name: "port",
9895
10290
  group: "discovery",
@@ -9916,7 +10311,7 @@ var init_port = __esm({
9916
10311
  });
9917
10312
 
9918
10313
  // src/commands/remove-apt-packages.ts
9919
- import { defineCommand as defineCommand16 } from "citty";
10314
+ import { defineCommand as defineCommand17 } from "citty";
9920
10315
  import { consola as consola20 } from "consola";
9921
10316
  var removeAptPackagesCommand;
9922
10317
  var init_remove_apt_packages = __esm({
@@ -9924,7 +10319,7 @@ var init_remove_apt_packages = __esm({
9924
10319
  "use strict";
9925
10320
  init_inner_args();
9926
10321
  init_modify();
9927
- removeAptPackagesCommand = defineCommand16({
10322
+ removeAptPackagesCommand = defineCommand17({
9928
10323
  meta: {
9929
10324
  name: "remove-apt-packages",
9930
10325
  group: "edit",
@@ -9968,14 +10363,14 @@ var init_remove_apt_packages = __esm({
9968
10363
  });
9969
10364
 
9970
10365
  // src/commands/remove-feature.ts
9971
- import { defineCommand as defineCommand17 } from "citty";
10366
+ import { defineCommand as defineCommand18 } from "citty";
9972
10367
  import { consola as consola21 } from "consola";
9973
10368
  var removeFeatureCommand;
9974
10369
  var init_remove_feature = __esm({
9975
10370
  "src/commands/remove-feature.ts"() {
9976
10371
  "use strict";
9977
10372
  init_modify();
9978
- removeFeatureCommand = defineCommand17({
10373
+ removeFeatureCommand = defineCommand18({
9979
10374
  meta: {
9980
10375
  name: "remove-feature",
9981
10376
  group: "edit",
@@ -10171,7 +10566,7 @@ var init_remove = __esm({
10171
10566
  });
10172
10567
 
10173
10568
  // src/commands/remove.ts
10174
- import { defineCommand as defineCommand18 } from "citty";
10569
+ import { defineCommand as defineCommand19 } from "citty";
10175
10570
  import { consola as consola23 } from "consola";
10176
10571
  import { createInterface } from "readline/promises";
10177
10572
  var removeCommand;
@@ -10179,7 +10574,7 @@ var init_remove2 = __esm({
10179
10574
  "src/commands/remove.ts"() {
10180
10575
  "use strict";
10181
10576
  init_remove();
10182
- removeCommand = defineCommand18({
10577
+ removeCommand = defineCommand19({
10183
10578
  meta: {
10184
10579
  name: "remove",
10185
10580
  group: "lifecycle",
@@ -10315,14 +10710,14 @@ var init_restore = __esm({
10315
10710
  });
10316
10711
 
10317
10712
  // src/commands/restore.ts
10318
- import { defineCommand as defineCommand19 } from "citty";
10713
+ import { defineCommand as defineCommand20 } from "citty";
10319
10714
  import { consola as consola25 } from "consola";
10320
10715
  var restoreCommand;
10321
10716
  var init_restore2 = __esm({
10322
10717
  "src/commands/restore.ts"() {
10323
10718
  "use strict";
10324
10719
  init_restore();
10325
- restoreCommand = defineCommand19({
10720
+ restoreCommand = defineCommand20({
10326
10721
  meta: {
10327
10722
  name: "restore",
10328
10723
  group: "lifecycle",
@@ -10348,14 +10743,14 @@ var init_restore2 = __esm({
10348
10743
  });
10349
10744
 
10350
10745
  // src/commands/remove-from-url.ts
10351
- import { defineCommand as defineCommand20 } from "citty";
10746
+ import { defineCommand as defineCommand21 } from "citty";
10352
10747
  import { consola as consola26 } from "consola";
10353
10748
  var removeFromUrlCommand;
10354
10749
  var init_remove_from_url = __esm({
10355
10750
  "src/commands/remove-from-url.ts"() {
10356
10751
  "use strict";
10357
10752
  init_modify();
10358
- removeFromUrlCommand = defineCommand20({
10753
+ removeFromUrlCommand = defineCommand21({
10359
10754
  meta: {
10360
10755
  name: "remove-from-url",
10361
10756
  group: "edit",
@@ -10397,14 +10792,14 @@ var init_remove_from_url = __esm({
10397
10792
  });
10398
10793
 
10399
10794
  // src/commands/remove-language.ts
10400
- import { defineCommand as defineCommand21 } from "citty";
10795
+ import { defineCommand as defineCommand22 } from "citty";
10401
10796
  import { consola as consola27 } from "consola";
10402
10797
  var removeLanguageCommand;
10403
10798
  var init_remove_language = __esm({
10404
10799
  "src/commands/remove-language.ts"() {
10405
10800
  "use strict";
10406
10801
  init_modify();
10407
- removeLanguageCommand = defineCommand21({
10802
+ removeLanguageCommand = defineCommand22({
10408
10803
  meta: {
10409
10804
  name: "remove-language",
10410
10805
  group: "edit",
@@ -10446,7 +10841,7 @@ var init_remove_language = __esm({
10446
10841
  });
10447
10842
 
10448
10843
  // src/commands/remove-port.ts
10449
- import { defineCommand as defineCommand22 } from "citty";
10844
+ import { defineCommand as defineCommand23 } from "citty";
10450
10845
  import { consola as consola28 } from "consola";
10451
10846
  function coerceToken2(raw) {
10452
10847
  const n = Number(raw);
@@ -10458,7 +10853,7 @@ var init_remove_port = __esm({
10458
10853
  "use strict";
10459
10854
  init_inner_args();
10460
10855
  init_modify();
10461
- removePortCommand = defineCommand22({
10856
+ removePortCommand = defineCommand23({
10462
10857
  meta: {
10463
10858
  name: "remove-port",
10464
10859
  group: "edit",
@@ -10502,14 +10897,14 @@ var init_remove_port = __esm({
10502
10897
  });
10503
10898
 
10504
10899
  // src/commands/remove-repo.ts
10505
- import { defineCommand as defineCommand23 } from "citty";
10900
+ import { defineCommand as defineCommand24 } from "citty";
10506
10901
  import { consola as consola29 } from "consola";
10507
10902
  var removeRepoCommand;
10508
10903
  var init_remove_repo = __esm({
10509
10904
  "src/commands/remove-repo.ts"() {
10510
10905
  "use strict";
10511
10906
  init_modify();
10512
- removeRepoCommand = defineCommand23({
10907
+ removeRepoCommand = defineCommand24({
10513
10908
  meta: {
10514
10909
  name: "remove-repo",
10515
10910
  group: "edit",
@@ -10551,14 +10946,14 @@ var init_remove_repo = __esm({
10551
10946
  });
10552
10947
 
10553
10948
  // src/commands/remove-service.ts
10554
- import { defineCommand as defineCommand24 } from "citty";
10949
+ import { defineCommand as defineCommand25 } from "citty";
10555
10950
  import { consola as consola30 } from "consola";
10556
10951
  var removeServiceCommand;
10557
10952
  var init_remove_service = __esm({
10558
10953
  "src/commands/remove-service.ts"() {
10559
10954
  "use strict";
10560
10955
  init_modify();
10561
- removeServiceCommand = defineCommand24({
10956
+ removeServiceCommand = defineCommand25({
10562
10957
  meta: {
10563
10958
  name: "remove-service",
10564
10959
  group: "edit",
@@ -10700,7 +11095,7 @@ var init_run = __esm({
10700
11095
  });
10701
11096
 
10702
11097
  // src/commands/run.ts
10703
- import { defineCommand as defineCommand25 } from "citty";
11098
+ import { defineCommand as defineCommand26 } from "citty";
10704
11099
  import { consola as consola31 } from "consola";
10705
11100
  var runCommand;
10706
11101
  var init_run2 = __esm({
@@ -10709,7 +11104,7 @@ var init_run2 = __esm({
10709
11104
  init_paths();
10710
11105
  init_run();
10711
11106
  init_inner_args();
10712
- runCommand = defineCommand25({
11107
+ runCommand = defineCommand26({
10713
11108
  meta: {
10714
11109
  name: "run",
10715
11110
  group: "run",
@@ -10752,7 +11147,7 @@ var init_run2 = __esm({
10752
11147
  });
10753
11148
 
10754
11149
  // src/commands/shell.ts
10755
- import { defineCommand as defineCommand26 } from "citty";
11150
+ import { defineCommand as defineCommand27 } from "citty";
10756
11151
  import { consola as consola32 } from "consola";
10757
11152
  var shellCommand;
10758
11153
  var init_shell2 = __esm({
@@ -10760,7 +11155,7 @@ var init_shell2 = __esm({
10760
11155
  "use strict";
10761
11156
  init_paths();
10762
11157
  init_shell();
10763
- shellCommand = defineCommand26({
11158
+ shellCommand = defineCommand27({
10764
11159
  meta: {
10765
11160
  name: "shell",
10766
11161
  group: "run",
@@ -10790,7 +11185,7 @@ var init_shell2 = __esm({
10790
11185
  });
10791
11186
 
10792
11187
  // src/commands/start.ts
10793
- import { defineCommand as defineCommand27 } from "citty";
11188
+ import { defineCommand as defineCommand28 } from "citty";
10794
11189
  import { consola as consola33 } from "consola";
10795
11190
  var startCommand;
10796
11191
  var init_start = __esm({
@@ -10800,11 +11195,12 @@ var init_start = __esm({
10800
11195
  init_io();
10801
11196
  init_paths();
10802
11197
  init_compose();
11198
+ init_catalog();
10803
11199
  init_open();
10804
11200
  init_proxy();
10805
11201
  init_port_check();
10806
11202
  init_dispatch();
10807
- startCommand = defineCommand27({
11203
+ startCommand = defineCommand28({
10808
11204
  meta: {
10809
11205
  name: "start",
10810
11206
  group: "run",
@@ -10825,6 +11221,7 @@ var init_start = __esm({
10825
11221
  return dispatch(async () => {
10826
11222
  let needsProxy = false;
10827
11223
  let hostPort = 80;
11224
+ let deferred = [];
10828
11225
  try {
10829
11226
  const parsed = await readConfig(containerConfigPath(args.name));
10830
11227
  if ((parsed.config.routing?.ports ?? []).length > 0) {
@@ -10832,6 +11229,7 @@ var init_start = __esm({
10832
11229
  const global = await readMonocerosConfig();
10833
11230
  hostPort = proxyHostPort(global);
10834
11231
  }
11232
+ deferred = (parsed.config.services ?? []).filter((s) => serviceDefersStart(s.name)).map((s) => s.name);
10835
11233
  } catch (err) {
10836
11234
  consola33.warn(
10837
11235
  `Could not read container yml ahead of start: ${err instanceof Error ? err.message : String(err)}. Skipping Traefik pre-flight.`
@@ -10842,6 +11240,24 @@ var init_start = __esm({
10842
11240
  await ensureProxy({ hostPort });
10843
11241
  }
10844
11242
  const exitCode = await runStart({ root: containerDir(args.name) });
11243
+ if (exitCode === 0 && deferred.length > 0) {
11244
+ try {
11245
+ const deferExit = await startDeferredServices({
11246
+ root: containerDir(args.name),
11247
+ services: deferred,
11248
+ logger: consola33
11249
+ });
11250
+ if (deferExit !== 0) {
11251
+ consola33.warn(
11252
+ `Deferred service(s) ${deferred.join(", ")} did not start cleanly (exit ${deferExit}).`
11253
+ );
11254
+ }
11255
+ } catch (err) {
11256
+ consola33.warn(
11257
+ `Could not start deferred service(s) ${deferred.join(", ")}: ${err instanceof Error ? err.message : String(err)}`
11258
+ );
11259
+ }
11260
+ }
10845
11261
  if (args.open && exitCode === 0) {
10846
11262
  try {
10847
11263
  await runOpen({ name: args.name, tool: args.open });
@@ -10857,7 +11273,7 @@ var init_start = __esm({
10857
11273
  });
10858
11274
 
10859
11275
  // src/commands/status.ts
10860
- import { defineCommand as defineCommand28 } from "citty";
11276
+ import { defineCommand as defineCommand29 } from "citty";
10861
11277
  var statusCommand;
10862
11278
  var init_status = __esm({
10863
11279
  "src/commands/status.ts"() {
@@ -10865,7 +11281,7 @@ var init_status = __esm({
10865
11281
  init_paths();
10866
11282
  init_compose();
10867
11283
  init_dispatch();
10868
- statusCommand = defineCommand28({
11284
+ statusCommand = defineCommand29({
10869
11285
  meta: {
10870
11286
  name: "status",
10871
11287
  group: "run",
@@ -10895,7 +11311,7 @@ var init_status = __esm({
10895
11311
  });
10896
11312
 
10897
11313
  // src/commands/stop.ts
10898
- import { defineCommand as defineCommand29 } from "citty";
11314
+ import { defineCommand as defineCommand30 } from "citty";
10899
11315
  import { consola as consola34 } from "consola";
10900
11316
  var stopCommand;
10901
11317
  var init_stop = __esm({
@@ -10905,7 +11321,7 @@ var init_stop = __esm({
10905
11321
  init_compose();
10906
11322
  init_proxy();
10907
11323
  init_dispatch();
10908
- stopCommand = defineCommand29({
11324
+ stopCommand = defineCommand30({
10909
11325
  meta: {
10910
11326
  name: "stop",
10911
11327
  group: "run",
@@ -11198,7 +11614,7 @@ var init_port_check2 = __esm({
11198
11614
  });
11199
11615
 
11200
11616
  // src/tunnel/run.ts
11201
- import { spawn as spawn12 } from "child_process";
11617
+ import { spawn as spawn13 } from "child_process";
11202
11618
  import { consola as consola35 } from "consola";
11203
11619
  function signalNumber(signal) {
11204
11620
  switch (signal) {
@@ -11288,7 +11704,7 @@ var init_run3 = __esm({
11288
11704
  init_port_check2();
11289
11705
  SOCAT_IMAGE = "alpine/socat:1.8.0.3";
11290
11706
  defaultDockerSpawn = (args) => {
11291
- const child = spawn12("docker", args, {
11707
+ const child = spawn13("docker", args, {
11292
11708
  stdio: "inherit"
11293
11709
  });
11294
11710
  const exited = new Promise((resolve, reject) => {
@@ -11319,7 +11735,7 @@ var init_run3 = __esm({
11319
11735
  });
11320
11736
 
11321
11737
  // src/commands/tunnel.ts
11322
- import { defineCommand as defineCommand30 } from "citty";
11738
+ import { defineCommand as defineCommand31 } from "citty";
11323
11739
  import { consola as consola36 } from "consola";
11324
11740
  function parseLocalPort(raw) {
11325
11741
  if (raw === void 0) return void 0;
@@ -11336,7 +11752,7 @@ var init_tunnel = __esm({
11336
11752
  "src/commands/tunnel.ts"() {
11337
11753
  "use strict";
11338
11754
  init_run3();
11339
- tunnelCommand = defineCommand30({
11755
+ tunnelCommand = defineCommand31({
11340
11756
  meta: {
11341
11757
  name: "tunnel",
11342
11758
  group: "discovery",
@@ -11628,7 +12044,7 @@ var init_upgrade = __esm({
11628
12044
  });
11629
12045
 
11630
12046
  // src/commands/upgrade.ts
11631
- import { defineCommand as defineCommand31 } from "citty";
12047
+ import { defineCommand as defineCommand32 } from "citty";
11632
12048
  var upgradeCommand;
11633
12049
  var init_upgrade2 = __esm({
11634
12050
  "src/commands/upgrade.ts"() {
@@ -11636,7 +12052,7 @@ var init_upgrade2 = __esm({
11636
12052
  init_upgrade();
11637
12053
  init_version();
11638
12054
  init_dispatch();
11639
- upgradeCommand = defineCommand31({
12055
+ upgradeCommand = defineCommand32({
11640
12056
  meta: {
11641
12057
  name: "upgrade",
11642
12058
  group: "lifecycle",
@@ -11678,7 +12094,7 @@ var main_exports = {};
11678
12094
  __export(main_exports, {
11679
12095
  main: () => main
11680
12096
  });
11681
- import { defineCommand as defineCommand32 } from "citty";
12097
+ import { defineCommand as defineCommand33 } from "citty";
11682
12098
  var main;
11683
12099
  var init_main = __esm({
11684
12100
  "src/main.ts"() {
@@ -11693,6 +12109,7 @@ var init_main = __esm({
11693
12109
  init_apply2();
11694
12110
  init_completion();
11695
12111
  init_complete();
12112
+ init_update_check();
11696
12113
  init_init2();
11697
12114
  init_list_components();
11698
12115
  init_logs();
@@ -11715,7 +12132,7 @@ var init_main = __esm({
11715
12132
  init_tunnel();
11716
12133
  init_upgrade2();
11717
12134
  init_version();
11718
- main = defineCommand32({
12135
+ main = defineCommand33({
11719
12136
  meta: {
11720
12137
  name: "monoceros",
11721
12138
  version: CLI_VERSION,
@@ -11752,7 +12169,8 @@ var init_main = __esm({
11752
12169
  port: portCommand,
11753
12170
  tunnel: tunnelCommand,
11754
12171
  completion: completionCommand,
11755
- __complete: __completeCommand
12172
+ __complete: __completeCommand,
12173
+ "__update-check": __updateCheckCommand
11756
12174
  }
11757
12175
  });
11758
12176
  }
@@ -12042,9 +12460,15 @@ async function maybeRenderHelp(argv, main2) {
12042
12460
  // src/bin.ts
12043
12461
  init_inner_args();
12044
12462
  init_main();
12463
+ init_notifier();
12464
+ init_version();
12045
12465
  bootstrapDockerGroup();
12046
12466
  consumeInnerArgsFromProcessArgv();
12047
12467
  async function entry() {
12468
+ scheduleUpdateNotice({
12469
+ currentVersion: CLI_VERSION,
12470
+ commandName: process.argv.slice(2).find((a) => !a.startsWith("-"))
12471
+ });
12048
12472
  if (await maybeRenderHelp(process.argv.slice(2), main)) {
12049
12473
  return;
12050
12474
  }