@getmonoceros/workbench 1.30.0 → 1.31.1

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.1" : "dev";
8184
8351
  }
8185
8352
  });
8186
8353
 
@@ -8538,6 +8705,50 @@ async function listContainerNames(ctx) {
8538
8705
  const entries = await fs14.readdir(dir);
8539
8706
  return entries.filter((e) => e.endsWith(".yml")).map((e) => e.slice(0, -".yml".length)).sort();
8540
8707
  }
8708
+ function containerNameFromCtx(ctx) {
8709
+ for (let i = 2; i < ctx.prev.length; i++) {
8710
+ const t = ctx.prev[i];
8711
+ if (t === "--") break;
8712
+ if (t.startsWith("-")) continue;
8713
+ return t;
8714
+ }
8715
+ return void 0;
8716
+ }
8717
+ async function collectDirs(dir, maxDepth) {
8718
+ const out = [];
8719
+ async function walk(at, rel, depth) {
8720
+ let entries;
8721
+ try {
8722
+ entries = await fs14.readdir(at, { withFileTypes: true });
8723
+ } catch {
8724
+ return;
8725
+ }
8726
+ for (const e of entries) {
8727
+ if (!e.isDirectory()) continue;
8728
+ if (e.name.startsWith(".")) continue;
8729
+ if (WORKSPACE_DIR_SKIP.has(e.name)) continue;
8730
+ const childRel = rel ? `${rel}/${e.name}` : e.name;
8731
+ out.push(childRel);
8732
+ if (depth + 1 < maxDepth) {
8733
+ await walk(path23.join(at, e.name), childRel, depth + 1);
8734
+ }
8735
+ }
8736
+ }
8737
+ await walk(dir, "", 0);
8738
+ return out;
8739
+ }
8740
+ async function listContainerWorkspaceDirs(home, name) {
8741
+ const root = path23.join(home, "container", name);
8742
+ const top = await collectDirs(root, 1);
8743
+ const projects = (await collectDirs(path23.join(root, "projects"), PROJECTS_DIR_MAX_DEPTH)).map((p) => `projects/${p}`);
8744
+ return [.../* @__PURE__ */ new Set([...top, ...projects])].sort();
8745
+ }
8746
+ async function listRunInDirs(ctx) {
8747
+ const name = containerNameFromCtx(ctx);
8748
+ if (!name) return [];
8749
+ const home = ctx.opts.monocerosHome ?? monocerosHome();
8750
+ return listContainerWorkspaceDirs(home, name);
8751
+ }
8541
8752
  async function listFeatureComponents() {
8542
8753
  const catalog = await loadComponentCatalog();
8543
8754
  return [...catalog.values()].filter((c) => c.file.category === "feature").map((c) => c.name).sort();
@@ -8600,7 +8811,7 @@ async function resolveFeatureRefForCompletion(token) {
8600
8811
  const f = c.file.contributes.features?.[0];
8601
8812
  return f?.ref;
8602
8813
  }
8603
- var ALL_COMMANDS, containerName, COMMAND_SPECS, COMPLETION_COMMAND_SPEC_KEYS;
8814
+ var WORKSPACE_DIR_SKIP, PROJECTS_DIR_MAX_DEPTH, ALL_COMMANDS, containerName, COMMAND_SPECS, COMPLETION_COMMAND_SPEC_KEYS;
8604
8815
  var init_resolve = __esm({
8605
8816
  "src/completion/resolve.ts"() {
8606
8817
  "use strict";
@@ -8610,6 +8821,8 @@ var init_resolve = __esm({
8610
8821
  init_catalog();
8611
8822
  init_schema();
8612
8823
  init_open();
8824
+ WORKSPACE_DIR_SKIP = /* @__PURE__ */ new Set(["node_modules"]);
8825
+ PROJECTS_DIR_MAX_DEPTH = 4;
8613
8826
  ALL_COMMANDS = [
8614
8827
  "init",
8615
8828
  "list-components",
@@ -8686,7 +8899,7 @@ var init_resolve = __esm({
8686
8899
  open: { positionals: [containerName, () => [...OPEN_TOOLS]] },
8687
8900
  run: {
8688
8901
  positionals: [containerName],
8689
- flags: { "--in": { type: "value" } }
8902
+ flags: { "--in": { type: "value", values: (ctx) => listRunInDirs(ctx) } }
8690
8903
  },
8691
8904
  logs: { positionals: [containerName] },
8692
8905
  start: {
@@ -8825,6 +9038,140 @@ var init_complete = __esm({
8825
9038
  }
8826
9039
  });
8827
9040
 
9041
+ // src/update/notifier.ts
9042
+ import { spawn as spawn11 } from "child_process";
9043
+ function isNewerVersion(latest, current) {
9044
+ const parse = (v) => {
9045
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
9046
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
9047
+ };
9048
+ const a = parse(latest);
9049
+ const b = parse(current);
9050
+ if (!a || !b) return false;
9051
+ for (let i = 0; i < 3; i++) {
9052
+ if (a[i] !== b[i]) return a[i] > b[i];
9053
+ }
9054
+ return false;
9055
+ }
9056
+ function formatUpdateNotice(latest, current) {
9057
+ return `
9058
+ \u2B06 Monoceros ${latest} is available (you have ${current}).
9059
+ Update: ${INSTALL_COMMAND}
9060
+ `;
9061
+ }
9062
+ function decideUpdateAction(state, currentVersion, now, intervalMs = CHECK_INTERVAL_MS) {
9063
+ const notice = state.latestVersion && isNewerVersion(state.latestVersion, currentVersion) ? formatUpdateNotice(state.latestVersion, currentVersion) : null;
9064
+ const last = state.lastVersionCheckAt ? new Date(state.lastVersionCheckAt).getTime() : NaN;
9065
+ const refresh = !Number.isFinite(last) || now.getTime() - last > intervalMs;
9066
+ return { notice, refresh };
9067
+ }
9068
+ function scheduleUpdateNotice(opts) {
9069
+ try {
9070
+ if (process.env[OPT_OUT_ENV]) return;
9071
+ if (opts.currentVersion === "dev") return;
9072
+ if (!process.stdout.isTTY) return;
9073
+ if (opts.commandName === void 0) return;
9074
+ if (SKIP_COMMANDS.has(opts.commandName)) return;
9075
+ const argv = opts.argv ?? process.argv.slice(2);
9076
+ if (argv.some((a) => ["-h", "--help", "-v", "--version"].includes(a))) {
9077
+ return;
9078
+ }
9079
+ const state = readMachineStateSync();
9080
+ const { notice, refresh } = decideUpdateAction(
9081
+ state,
9082
+ opts.currentVersion,
9083
+ opts.now ?? /* @__PURE__ */ new Date()
9084
+ );
9085
+ if (notice) {
9086
+ process.on("exit", () => {
9087
+ try {
9088
+ process.stderr.write(notice);
9089
+ } catch {
9090
+ }
9091
+ });
9092
+ }
9093
+ if (refresh) spawnBackgroundCheck();
9094
+ } catch {
9095
+ }
9096
+ }
9097
+ function spawnBackgroundCheck() {
9098
+ const self = process.argv[1];
9099
+ if (!self) return;
9100
+ try {
9101
+ const child = spawn11(process.execPath, [self, "__update-check"], {
9102
+ detached: true,
9103
+ stdio: "ignore"
9104
+ });
9105
+ child.unref();
9106
+ } catch {
9107
+ }
9108
+ }
9109
+ async function runUpdateCheck(now = /* @__PURE__ */ new Date(), home) {
9110
+ let latestVersion;
9111
+ try {
9112
+ const controller = new AbortController();
9113
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
9114
+ try {
9115
+ const res = await fetch(REGISTRY_URL, { signal: controller.signal });
9116
+ if (res.ok) {
9117
+ const json = await res.json();
9118
+ if (typeof json.version === "string") latestVersion = json.version;
9119
+ }
9120
+ } finally {
9121
+ clearTimeout(timer);
9122
+ }
9123
+ } catch {
9124
+ }
9125
+ try {
9126
+ await recordVersionCheck(
9127
+ {
9128
+ ...latestVersion ? { latestVersion } : {},
9129
+ nowIso: now.toISOString()
9130
+ },
9131
+ home
9132
+ );
9133
+ } catch {
9134
+ }
9135
+ }
9136
+ var PACKAGE, REGISTRY_URL, INSTALL_COMMAND, CHECK_INTERVAL_MS, OPT_OUT_ENV, FETCH_TIMEOUT_MS, SKIP_COMMANDS;
9137
+ var init_notifier = __esm({
9138
+ "src/update/notifier.ts"() {
9139
+ "use strict";
9140
+ init_machine_state();
9141
+ PACKAGE = "@getmonoceros/workbench";
9142
+ REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE}/latest`;
9143
+ INSTALL_COMMAND = "curl -fsSL https://raw.githubusercontent.com/getmonoceros/workbench/main/install.sh | bash";
9144
+ CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
9145
+ OPT_OUT_ENV = "MONOCEROS_NO_UPDATE_NOTIFIER";
9146
+ FETCH_TIMEOUT_MS = 5e3;
9147
+ SKIP_COMMANDS = /* @__PURE__ */ new Set(["__complete", "__update-check", "completion"]);
9148
+ }
9149
+ });
9150
+
9151
+ // src/commands/__update-check.ts
9152
+ import { defineCommand as defineCommand11 } from "citty";
9153
+ var __updateCheckCommand;
9154
+ var init_update_check = __esm({
9155
+ "src/commands/__update-check.ts"() {
9156
+ "use strict";
9157
+ init_notifier();
9158
+ __updateCheckCommand = defineCommand11({
9159
+ meta: {
9160
+ name: "__update-check",
9161
+ group: "internal",
9162
+ hidden: true,
9163
+ description: "Internal: refresh the cached latest-version (background)."
9164
+ },
9165
+ async run() {
9166
+ try {
9167
+ await runUpdateCheck();
9168
+ } catch {
9169
+ }
9170
+ }
9171
+ });
9172
+ }
9173
+ });
9174
+
8828
9175
  // src/init/generator.ts
8829
9176
  function generateComposedYml(name, composed, lookupManifest, repoUrls = [], ports = []) {
8830
9177
  const lines = [];
@@ -8990,6 +9337,12 @@ function generateDocumentedYml(name, catalog, lookupManifest, repoUrls = [], por
8990
9337
  const body = renderServiceObjectBody(expandCuratedService(svc));
8991
9338
  lines.push(`# - ${body[0]}`);
8992
9339
  for (const line of body.slice(1)) lines.push(`# ${line}`);
9340
+ const exComment = exampleVolumesComment(
9341
+ curatedServiceExampleVolumes(svc)
9342
+ );
9343
+ if (exComment) {
9344
+ for (const cl of exComment.split("\n")) lines.push(`# ${cl}`);
9345
+ }
8993
9346
  }
8994
9347
  }
8995
9348
  lines.push("");
@@ -9109,6 +9462,12 @@ function pushServiceEntry(out, svc) {
9109
9462
  const body = renderServiceObjectBody(expandCuratedService(svc.name));
9110
9463
  out.push(` - ${body[0]}`);
9111
9464
  for (const line of body.slice(1)) out.push(` ${line}`);
9465
+ const exComment = exampleVolumesComment(
9466
+ curatedServiceExampleVolumes(svc.name)
9467
+ );
9468
+ if (exComment) {
9469
+ for (const cl of exComment.split("\n")) out.push(` #${cl}`);
9470
+ }
9112
9471
  }
9113
9472
  function pushGitIdentityBlock(lines) {
9114
9473
  pushSectionHeader(
@@ -9398,19 +9757,19 @@ function resolveInitAptPackages(entries) {
9398
9757
  }
9399
9758
  function resolveInitServices(entries) {
9400
9759
  const out = [];
9401
- const byName = /* @__PURE__ */ new Map();
9760
+ const byName2 = /* @__PURE__ */ new Map();
9402
9761
  for (const raw of entries) {
9403
9762
  const e = raw.trim();
9404
9763
  if (!e) continue;
9405
9764
  const svc = isCuratedService(e) ? { kind: "curated", name: e } : { kind: "custom", name: deriveServiceName(e), image: e };
9406
- const existing = byName.get(svc.name);
9765
+ const existing = byName2.get(svc.name);
9407
9766
  if (existing) {
9408
9767
  if (existing.kind === svc.kind && existing.image === svc.image) continue;
9409
9768
  throw new Error(
9410
9769
  `Two --with-services entries resolve to the service name '${svc.name}'. Add one after init with \`monoceros add-service ${"<name>"} <image> --as=<other>\`.`
9411
9770
  );
9412
9771
  }
9413
- byName.set(svc.name, svc);
9772
+ byName2.set(svc.name, svc);
9414
9773
  out.push(svc);
9415
9774
  }
9416
9775
  return out;
@@ -9466,7 +9825,7 @@ var init_init = __esm({
9466
9825
  });
9467
9826
 
9468
9827
  // src/commands/init.ts
9469
- import { defineCommand as defineCommand11 } from "citty";
9828
+ import { defineCommand as defineCommand12 } from "citty";
9470
9829
  import { consola as consola17 } from "consola";
9471
9830
  function collectListFlag(flag, rawArgs) {
9472
9831
  const eq = `${flag}=`;
@@ -9532,7 +9891,7 @@ var init_init2 = __esm({
9532
9891
  "src/commands/init.ts"() {
9533
9892
  "use strict";
9534
9893
  init_init();
9535
- initCommand = defineCommand11({
9894
+ initCommand = defineCommand12({
9536
9895
  meta: {
9537
9896
  name: "init",
9538
9897
  group: "lifecycle",
@@ -9640,8 +9999,76 @@ var init_expand = __esm({
9640
9999
  }
9641
10000
  });
9642
10001
 
10002
+ // src/catalog/catalog-json.ts
10003
+ function projectOption(key, spec) {
10004
+ if (spec.surface === "silent") return null;
10005
+ const out = {
10006
+ key,
10007
+ type: spec.type,
10008
+ surface: spec.surface
10009
+ };
10010
+ if (spec.default !== void 0) out.default = spec.default;
10011
+ if (spec.description !== void 0) out.description = spec.description;
10012
+ if (spec.proposals !== void 0) out.proposals = spec.proposals;
10013
+ return out;
10014
+ }
10015
+ function projectOptions(options) {
10016
+ return Object.entries(options).map(([key, spec]) => projectOption(key, spec)).filter((o) => o !== null).sort((a, b) => a.key.localeCompare(b.key));
10017
+ }
10018
+ function byName(a, b) {
10019
+ return a.name.localeCompare(b.name);
10020
+ }
10021
+ function buildCatalogJson(catalog, cliVersion) {
10022
+ const languages = [];
10023
+ const services = [];
10024
+ const features = [];
10025
+ for (const { descriptor: d } of catalog.values()) {
10026
+ const base = {
10027
+ name: d.name ?? d.id,
10028
+ displayName: d.displayName,
10029
+ description: d.description,
10030
+ options: projectOptions(d.options)
10031
+ };
10032
+ if (d.documentationURL !== void 0) {
10033
+ base.documentationURL = d.documentationURL;
10034
+ }
10035
+ if (d.category === "language" && d.language) {
10036
+ const lang = { ...base };
10037
+ if (d.language.defaultVersion !== void 0) {
10038
+ lang.defaultVersion = d.language.defaultVersion;
10039
+ }
10040
+ if (d.language.versions !== void 0) {
10041
+ lang.versions = d.language.versions;
10042
+ }
10043
+ languages.push(lang);
10044
+ } else if (d.category === "service" && d.service) {
10045
+ const svc = { ...base };
10046
+ if (d.service.defaultPort !== void 0) {
10047
+ svc.defaultPort = d.service.defaultPort;
10048
+ }
10049
+ services.push(svc);
10050
+ } else if (d.category === "feature") {
10051
+ features.push({ ...base, presets: Object.keys(d.presets ?? {}).sort() });
10052
+ }
10053
+ }
10054
+ return {
10055
+ schemaVersion: CATALOG_JSON_SCHEMA_VERSION,
10056
+ cliVersion,
10057
+ languages: languages.sort(byName),
10058
+ services: services.sort(byName),
10059
+ features: features.sort(byName)
10060
+ };
10061
+ }
10062
+ var CATALOG_JSON_SCHEMA_VERSION;
10063
+ var init_catalog_json = __esm({
10064
+ "src/catalog/catalog-json.ts"() {
10065
+ "use strict";
10066
+ CATALOG_JSON_SCHEMA_VERSION = 1;
10067
+ }
10068
+ });
10069
+
9643
10070
  // src/commands/list-components.ts
9644
- import { defineCommand as defineCommand12 } from "citty";
10071
+ import { defineCommand as defineCommand13 } from "citty";
9645
10072
  import { consola as consola18 } from "consola";
9646
10073
  var CATEGORY_LABELS, CATEGORY_ORDER, listComponentsCommand;
9647
10074
  var init_list_components = __esm({
@@ -9649,6 +10076,8 @@ var init_list_components = __esm({
9649
10076
  "use strict";
9650
10077
  init_load();
9651
10078
  init_expand();
10079
+ init_catalog_json();
10080
+ init_version();
9652
10081
  init_format();
9653
10082
  CATEGORY_LABELS = {
9654
10083
  language: "Languages",
@@ -9660,16 +10089,28 @@ var init_list_components = __esm({
9660
10089
  "service",
9661
10090
  "feature"
9662
10091
  ];
9663
- listComponentsCommand = defineCommand12({
10092
+ listComponentsCommand = defineCommand13({
9664
10093
  meta: {
9665
10094
  name: "list-components",
9666
10095
  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."
10096
+ 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
10097
  },
9669
- args: {},
9670
- async run() {
10098
+ args: {
10099
+ json: {
10100
+ type: "boolean",
10101
+ description: "Emit the catalog as a JSON document (name, options, versions, presets per component) instead of the human-readable listing."
10102
+ }
10103
+ },
10104
+ async run({ args }) {
9671
10105
  try {
9672
- const catalog = expandSelectable(await loadDescriptorCatalog());
10106
+ const descriptors = await loadDescriptorCatalog();
10107
+ if (args.json) {
10108
+ const doc = buildCatalogJson(descriptors, CLI_VERSION);
10109
+ process.stdout.write(`${JSON.stringify(doc, null, 2)}
10110
+ `);
10111
+ process.exit(0);
10112
+ }
10113
+ const catalog = expandSelectable(descriptors);
9673
10114
  if (catalog.size === 0) {
9674
10115
  consola18.warn(
9675
10116
  "No components found. The workbench checkout looks incomplete."
@@ -9731,14 +10172,14 @@ var init_list_components = __esm({
9731
10172
  });
9732
10173
 
9733
10174
  // src/commands/logs.ts
9734
- import { spawn as spawn11 } from "child_process";
10175
+ import { spawn as spawn12 } from "child_process";
9735
10176
  import { existsSync as existsSync16 } from "fs";
9736
10177
  import path25 from "path";
9737
- import { defineCommand as defineCommand13 } from "citty";
10178
+ import { defineCommand as defineCommand14 } from "citty";
9738
10179
  function tailLogFile(file, follow) {
9739
10180
  const [cmd, args] = follow ? ["tail", ["-F", file]] : ["cat", [file]];
9740
10181
  return new Promise((resolve, reject) => {
9741
- const child = spawn11(cmd, args, { stdio: "inherit" });
10182
+ const child = spawn12(cmd, args, { stdio: "inherit" });
9742
10183
  child.on("error", reject);
9743
10184
  child.on("exit", (code) => resolve(code ?? 0));
9744
10185
  });
@@ -9750,7 +10191,7 @@ var init_logs = __esm({
9750
10191
  init_paths();
9751
10192
  init_compose();
9752
10193
  init_dispatch();
9753
- logsCommand = defineCommand13({
10194
+ logsCommand = defineCommand14({
9754
10195
  meta: {
9755
10196
  name: "logs",
9756
10197
  group: "run",
@@ -9794,14 +10235,14 @@ var init_logs = __esm({
9794
10235
  });
9795
10236
 
9796
10237
  // src/commands/open.ts
9797
- import { defineCommand as defineCommand14 } from "citty";
10238
+ import { defineCommand as defineCommand15 } from "citty";
9798
10239
  var openCommand;
9799
10240
  var init_open2 = __esm({
9800
10241
  "src/commands/open.ts"() {
9801
10242
  "use strict";
9802
10243
  init_open();
9803
10244
  init_dispatch();
9804
- openCommand = defineCommand14({
10245
+ openCommand = defineCommand15({
9805
10246
  meta: {
9806
10247
  name: "open",
9807
10248
  group: "run",
@@ -9827,7 +10268,7 @@ var init_open2 = __esm({
9827
10268
  });
9828
10269
 
9829
10270
  // src/commands/port.ts
9830
- import { defineCommand as defineCommand15 } from "citty";
10271
+ import { defineCommand as defineCommand16 } from "citty";
9831
10272
  import { consola as consola19 } from "consola";
9832
10273
  async function runPortListing(opts) {
9833
10274
  const out = opts.out ?? process.stdout;
@@ -9889,7 +10330,7 @@ var init_port = __esm({
9889
10330
  init_schema();
9890
10331
  init_dynamic();
9891
10332
  init_format();
9892
- portCommand = defineCommand15({
10333
+ portCommand = defineCommand16({
9893
10334
  meta: {
9894
10335
  name: "port",
9895
10336
  group: "discovery",
@@ -9916,7 +10357,7 @@ var init_port = __esm({
9916
10357
  });
9917
10358
 
9918
10359
  // src/commands/remove-apt-packages.ts
9919
- import { defineCommand as defineCommand16 } from "citty";
10360
+ import { defineCommand as defineCommand17 } from "citty";
9920
10361
  import { consola as consola20 } from "consola";
9921
10362
  var removeAptPackagesCommand;
9922
10363
  var init_remove_apt_packages = __esm({
@@ -9924,7 +10365,7 @@ var init_remove_apt_packages = __esm({
9924
10365
  "use strict";
9925
10366
  init_inner_args();
9926
10367
  init_modify();
9927
- removeAptPackagesCommand = defineCommand16({
10368
+ removeAptPackagesCommand = defineCommand17({
9928
10369
  meta: {
9929
10370
  name: "remove-apt-packages",
9930
10371
  group: "edit",
@@ -9968,14 +10409,14 @@ var init_remove_apt_packages = __esm({
9968
10409
  });
9969
10410
 
9970
10411
  // src/commands/remove-feature.ts
9971
- import { defineCommand as defineCommand17 } from "citty";
10412
+ import { defineCommand as defineCommand18 } from "citty";
9972
10413
  import { consola as consola21 } from "consola";
9973
10414
  var removeFeatureCommand;
9974
10415
  var init_remove_feature = __esm({
9975
10416
  "src/commands/remove-feature.ts"() {
9976
10417
  "use strict";
9977
10418
  init_modify();
9978
- removeFeatureCommand = defineCommand17({
10419
+ removeFeatureCommand = defineCommand18({
9979
10420
  meta: {
9980
10421
  name: "remove-feature",
9981
10422
  group: "edit",
@@ -10171,7 +10612,7 @@ var init_remove = __esm({
10171
10612
  });
10172
10613
 
10173
10614
  // src/commands/remove.ts
10174
- import { defineCommand as defineCommand18 } from "citty";
10615
+ import { defineCommand as defineCommand19 } from "citty";
10175
10616
  import { consola as consola23 } from "consola";
10176
10617
  import { createInterface } from "readline/promises";
10177
10618
  var removeCommand;
@@ -10179,7 +10620,7 @@ var init_remove2 = __esm({
10179
10620
  "src/commands/remove.ts"() {
10180
10621
  "use strict";
10181
10622
  init_remove();
10182
- removeCommand = defineCommand18({
10623
+ removeCommand = defineCommand19({
10183
10624
  meta: {
10184
10625
  name: "remove",
10185
10626
  group: "lifecycle",
@@ -10315,14 +10756,14 @@ var init_restore = __esm({
10315
10756
  });
10316
10757
 
10317
10758
  // src/commands/restore.ts
10318
- import { defineCommand as defineCommand19 } from "citty";
10759
+ import { defineCommand as defineCommand20 } from "citty";
10319
10760
  import { consola as consola25 } from "consola";
10320
10761
  var restoreCommand;
10321
10762
  var init_restore2 = __esm({
10322
10763
  "src/commands/restore.ts"() {
10323
10764
  "use strict";
10324
10765
  init_restore();
10325
- restoreCommand = defineCommand19({
10766
+ restoreCommand = defineCommand20({
10326
10767
  meta: {
10327
10768
  name: "restore",
10328
10769
  group: "lifecycle",
@@ -10348,14 +10789,14 @@ var init_restore2 = __esm({
10348
10789
  });
10349
10790
 
10350
10791
  // src/commands/remove-from-url.ts
10351
- import { defineCommand as defineCommand20 } from "citty";
10792
+ import { defineCommand as defineCommand21 } from "citty";
10352
10793
  import { consola as consola26 } from "consola";
10353
10794
  var removeFromUrlCommand;
10354
10795
  var init_remove_from_url = __esm({
10355
10796
  "src/commands/remove-from-url.ts"() {
10356
10797
  "use strict";
10357
10798
  init_modify();
10358
- removeFromUrlCommand = defineCommand20({
10799
+ removeFromUrlCommand = defineCommand21({
10359
10800
  meta: {
10360
10801
  name: "remove-from-url",
10361
10802
  group: "edit",
@@ -10397,14 +10838,14 @@ var init_remove_from_url = __esm({
10397
10838
  });
10398
10839
 
10399
10840
  // src/commands/remove-language.ts
10400
- import { defineCommand as defineCommand21 } from "citty";
10841
+ import { defineCommand as defineCommand22 } from "citty";
10401
10842
  import { consola as consola27 } from "consola";
10402
10843
  var removeLanguageCommand;
10403
10844
  var init_remove_language = __esm({
10404
10845
  "src/commands/remove-language.ts"() {
10405
10846
  "use strict";
10406
10847
  init_modify();
10407
- removeLanguageCommand = defineCommand21({
10848
+ removeLanguageCommand = defineCommand22({
10408
10849
  meta: {
10409
10850
  name: "remove-language",
10410
10851
  group: "edit",
@@ -10446,7 +10887,7 @@ var init_remove_language = __esm({
10446
10887
  });
10447
10888
 
10448
10889
  // src/commands/remove-port.ts
10449
- import { defineCommand as defineCommand22 } from "citty";
10890
+ import { defineCommand as defineCommand23 } from "citty";
10450
10891
  import { consola as consola28 } from "consola";
10451
10892
  function coerceToken2(raw) {
10452
10893
  const n = Number(raw);
@@ -10458,7 +10899,7 @@ var init_remove_port = __esm({
10458
10899
  "use strict";
10459
10900
  init_inner_args();
10460
10901
  init_modify();
10461
- removePortCommand = defineCommand22({
10902
+ removePortCommand = defineCommand23({
10462
10903
  meta: {
10463
10904
  name: "remove-port",
10464
10905
  group: "edit",
@@ -10502,14 +10943,14 @@ var init_remove_port = __esm({
10502
10943
  });
10503
10944
 
10504
10945
  // src/commands/remove-repo.ts
10505
- import { defineCommand as defineCommand23 } from "citty";
10946
+ import { defineCommand as defineCommand24 } from "citty";
10506
10947
  import { consola as consola29 } from "consola";
10507
10948
  var removeRepoCommand;
10508
10949
  var init_remove_repo = __esm({
10509
10950
  "src/commands/remove-repo.ts"() {
10510
10951
  "use strict";
10511
10952
  init_modify();
10512
- removeRepoCommand = defineCommand23({
10953
+ removeRepoCommand = defineCommand24({
10513
10954
  meta: {
10514
10955
  name: "remove-repo",
10515
10956
  group: "edit",
@@ -10551,14 +10992,14 @@ var init_remove_repo = __esm({
10551
10992
  });
10552
10993
 
10553
10994
  // src/commands/remove-service.ts
10554
- import { defineCommand as defineCommand24 } from "citty";
10995
+ import { defineCommand as defineCommand25 } from "citty";
10555
10996
  import { consola as consola30 } from "consola";
10556
10997
  var removeServiceCommand;
10557
10998
  var init_remove_service = __esm({
10558
10999
  "src/commands/remove-service.ts"() {
10559
11000
  "use strict";
10560
11001
  init_modify();
10561
- removeServiceCommand = defineCommand24({
11002
+ removeServiceCommand = defineCommand25({
10562
11003
  meta: {
10563
11004
  name: "remove-service",
10564
11005
  group: "edit",
@@ -10700,7 +11141,7 @@ var init_run = __esm({
10700
11141
  });
10701
11142
 
10702
11143
  // src/commands/run.ts
10703
- import { defineCommand as defineCommand25 } from "citty";
11144
+ import { defineCommand as defineCommand26 } from "citty";
10704
11145
  import { consola as consola31 } from "consola";
10705
11146
  var runCommand;
10706
11147
  var init_run2 = __esm({
@@ -10709,7 +11150,7 @@ var init_run2 = __esm({
10709
11150
  init_paths();
10710
11151
  init_run();
10711
11152
  init_inner_args();
10712
- runCommand = defineCommand25({
11153
+ runCommand = defineCommand26({
10713
11154
  meta: {
10714
11155
  name: "run",
10715
11156
  group: "run",
@@ -10752,7 +11193,7 @@ var init_run2 = __esm({
10752
11193
  });
10753
11194
 
10754
11195
  // src/commands/shell.ts
10755
- import { defineCommand as defineCommand26 } from "citty";
11196
+ import { defineCommand as defineCommand27 } from "citty";
10756
11197
  import { consola as consola32 } from "consola";
10757
11198
  var shellCommand;
10758
11199
  var init_shell2 = __esm({
@@ -10760,7 +11201,7 @@ var init_shell2 = __esm({
10760
11201
  "use strict";
10761
11202
  init_paths();
10762
11203
  init_shell();
10763
- shellCommand = defineCommand26({
11204
+ shellCommand = defineCommand27({
10764
11205
  meta: {
10765
11206
  name: "shell",
10766
11207
  group: "run",
@@ -10790,7 +11231,7 @@ var init_shell2 = __esm({
10790
11231
  });
10791
11232
 
10792
11233
  // src/commands/start.ts
10793
- import { defineCommand as defineCommand27 } from "citty";
11234
+ import { defineCommand as defineCommand28 } from "citty";
10794
11235
  import { consola as consola33 } from "consola";
10795
11236
  var startCommand;
10796
11237
  var init_start = __esm({
@@ -10800,11 +11241,12 @@ var init_start = __esm({
10800
11241
  init_io();
10801
11242
  init_paths();
10802
11243
  init_compose();
11244
+ init_catalog();
10803
11245
  init_open();
10804
11246
  init_proxy();
10805
11247
  init_port_check();
10806
11248
  init_dispatch();
10807
- startCommand = defineCommand27({
11249
+ startCommand = defineCommand28({
10808
11250
  meta: {
10809
11251
  name: "start",
10810
11252
  group: "run",
@@ -10825,6 +11267,7 @@ var init_start = __esm({
10825
11267
  return dispatch(async () => {
10826
11268
  let needsProxy = false;
10827
11269
  let hostPort = 80;
11270
+ let deferred = [];
10828
11271
  try {
10829
11272
  const parsed = await readConfig(containerConfigPath(args.name));
10830
11273
  if ((parsed.config.routing?.ports ?? []).length > 0) {
@@ -10832,6 +11275,7 @@ var init_start = __esm({
10832
11275
  const global = await readMonocerosConfig();
10833
11276
  hostPort = proxyHostPort(global);
10834
11277
  }
11278
+ deferred = (parsed.config.services ?? []).filter((s) => serviceDefersStart(s.name)).map((s) => s.name);
10835
11279
  } catch (err) {
10836
11280
  consola33.warn(
10837
11281
  `Could not read container yml ahead of start: ${err instanceof Error ? err.message : String(err)}. Skipping Traefik pre-flight.`
@@ -10842,6 +11286,24 @@ var init_start = __esm({
10842
11286
  await ensureProxy({ hostPort });
10843
11287
  }
10844
11288
  const exitCode = await runStart({ root: containerDir(args.name) });
11289
+ if (exitCode === 0 && deferred.length > 0) {
11290
+ try {
11291
+ const deferExit = await startDeferredServices({
11292
+ root: containerDir(args.name),
11293
+ services: deferred,
11294
+ logger: consola33
11295
+ });
11296
+ if (deferExit !== 0) {
11297
+ consola33.warn(
11298
+ `Deferred service(s) ${deferred.join(", ")} did not start cleanly (exit ${deferExit}).`
11299
+ );
11300
+ }
11301
+ } catch (err) {
11302
+ consola33.warn(
11303
+ `Could not start deferred service(s) ${deferred.join(", ")}: ${err instanceof Error ? err.message : String(err)}`
11304
+ );
11305
+ }
11306
+ }
10845
11307
  if (args.open && exitCode === 0) {
10846
11308
  try {
10847
11309
  await runOpen({ name: args.name, tool: args.open });
@@ -10857,7 +11319,7 @@ var init_start = __esm({
10857
11319
  });
10858
11320
 
10859
11321
  // src/commands/status.ts
10860
- import { defineCommand as defineCommand28 } from "citty";
11322
+ import { defineCommand as defineCommand29 } from "citty";
10861
11323
  var statusCommand;
10862
11324
  var init_status = __esm({
10863
11325
  "src/commands/status.ts"() {
@@ -10865,7 +11327,7 @@ var init_status = __esm({
10865
11327
  init_paths();
10866
11328
  init_compose();
10867
11329
  init_dispatch();
10868
- statusCommand = defineCommand28({
11330
+ statusCommand = defineCommand29({
10869
11331
  meta: {
10870
11332
  name: "status",
10871
11333
  group: "run",
@@ -10895,7 +11357,7 @@ var init_status = __esm({
10895
11357
  });
10896
11358
 
10897
11359
  // src/commands/stop.ts
10898
- import { defineCommand as defineCommand29 } from "citty";
11360
+ import { defineCommand as defineCommand30 } from "citty";
10899
11361
  import { consola as consola34 } from "consola";
10900
11362
  var stopCommand;
10901
11363
  var init_stop = __esm({
@@ -10905,7 +11367,7 @@ var init_stop = __esm({
10905
11367
  init_compose();
10906
11368
  init_proxy();
10907
11369
  init_dispatch();
10908
- stopCommand = defineCommand29({
11370
+ stopCommand = defineCommand30({
10909
11371
  meta: {
10910
11372
  name: "stop",
10911
11373
  group: "run",
@@ -11198,7 +11660,7 @@ var init_port_check2 = __esm({
11198
11660
  });
11199
11661
 
11200
11662
  // src/tunnel/run.ts
11201
- import { spawn as spawn12 } from "child_process";
11663
+ import { spawn as spawn13 } from "child_process";
11202
11664
  import { consola as consola35 } from "consola";
11203
11665
  function signalNumber(signal) {
11204
11666
  switch (signal) {
@@ -11288,7 +11750,7 @@ var init_run3 = __esm({
11288
11750
  init_port_check2();
11289
11751
  SOCAT_IMAGE = "alpine/socat:1.8.0.3";
11290
11752
  defaultDockerSpawn = (args) => {
11291
- const child = spawn12("docker", args, {
11753
+ const child = spawn13("docker", args, {
11292
11754
  stdio: "inherit"
11293
11755
  });
11294
11756
  const exited = new Promise((resolve, reject) => {
@@ -11319,7 +11781,7 @@ var init_run3 = __esm({
11319
11781
  });
11320
11782
 
11321
11783
  // src/commands/tunnel.ts
11322
- import { defineCommand as defineCommand30 } from "citty";
11784
+ import { defineCommand as defineCommand31 } from "citty";
11323
11785
  import { consola as consola36 } from "consola";
11324
11786
  function parseLocalPort(raw) {
11325
11787
  if (raw === void 0) return void 0;
@@ -11336,7 +11798,7 @@ var init_tunnel = __esm({
11336
11798
  "src/commands/tunnel.ts"() {
11337
11799
  "use strict";
11338
11800
  init_run3();
11339
- tunnelCommand = defineCommand30({
11801
+ tunnelCommand = defineCommand31({
11340
11802
  meta: {
11341
11803
  name: "tunnel",
11342
11804
  group: "discovery",
@@ -11628,7 +12090,7 @@ var init_upgrade = __esm({
11628
12090
  });
11629
12091
 
11630
12092
  // src/commands/upgrade.ts
11631
- import { defineCommand as defineCommand31 } from "citty";
12093
+ import { defineCommand as defineCommand32 } from "citty";
11632
12094
  var upgradeCommand;
11633
12095
  var init_upgrade2 = __esm({
11634
12096
  "src/commands/upgrade.ts"() {
@@ -11636,7 +12098,7 @@ var init_upgrade2 = __esm({
11636
12098
  init_upgrade();
11637
12099
  init_version();
11638
12100
  init_dispatch();
11639
- upgradeCommand = defineCommand31({
12101
+ upgradeCommand = defineCommand32({
11640
12102
  meta: {
11641
12103
  name: "upgrade",
11642
12104
  group: "lifecycle",
@@ -11678,7 +12140,7 @@ var main_exports = {};
11678
12140
  __export(main_exports, {
11679
12141
  main: () => main
11680
12142
  });
11681
- import { defineCommand as defineCommand32 } from "citty";
12143
+ import { defineCommand as defineCommand33 } from "citty";
11682
12144
  var main;
11683
12145
  var init_main = __esm({
11684
12146
  "src/main.ts"() {
@@ -11693,6 +12155,7 @@ var init_main = __esm({
11693
12155
  init_apply2();
11694
12156
  init_completion();
11695
12157
  init_complete();
12158
+ init_update_check();
11696
12159
  init_init2();
11697
12160
  init_list_components();
11698
12161
  init_logs();
@@ -11715,7 +12178,7 @@ var init_main = __esm({
11715
12178
  init_tunnel();
11716
12179
  init_upgrade2();
11717
12180
  init_version();
11718
- main = defineCommand32({
12181
+ main = defineCommand33({
11719
12182
  meta: {
11720
12183
  name: "monoceros",
11721
12184
  version: CLI_VERSION,
@@ -11752,7 +12215,8 @@ var init_main = __esm({
11752
12215
  port: portCommand,
11753
12216
  tunnel: tunnelCommand,
11754
12217
  completion: completionCommand,
11755
- __complete: __completeCommand
12218
+ __complete: __completeCommand,
12219
+ "__update-check": __updateCheckCommand
11756
12220
  }
11757
12221
  });
11758
12222
  }
@@ -12042,9 +12506,15 @@ async function maybeRenderHelp(argv, main2) {
12042
12506
  // src/bin.ts
12043
12507
  init_inner_args();
12044
12508
  init_main();
12509
+ init_notifier();
12510
+ init_version();
12045
12511
  bootstrapDockerGroup();
12046
12512
  consumeInnerArgsFromProcessArgv();
12047
12513
  async function entry() {
12514
+ scheduleUpdateNotice({
12515
+ currentVersion: CLI_VERSION,
12516
+ commandName: process.argv.slice(2).find((a) => !a.startsWith("-"))
12517
+ });
12048
12518
  if (await maybeRenderHelp(process.argv.slice(2), main)) {
12049
12519
  return;
12050
12520
  }