procdeck 0.5.0 → 1.1.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/cli.js CHANGED
@@ -45446,6 +45446,12 @@ var ProcSpecSchema = Schema_exports.Struct({
45446
45446
  examples: ["http://localhost:${port}"]
45447
45447
  })
45448
45448
  ),
45449
+ port: Schema_exports.optionalKey(
45450
+ Schema_exports.Number.annotate({
45451
+ description: "Pin this proc's assigned `${port}` to a fixed public number instead of a random free one \u2014 dependents outside the deck (dotenv files, mobile simulators, teammates' scripts) keep their hardcoded addresses, and an observed proc still binds a hidden internal port behind the HTTP observer. Requires `${port}` somewhere in the spec.",
45452
+ examples: [8787]
45453
+ }).pipe(Schema_exports.check(Schema_exports.isInt(), Schema_exports.isBetween({ minimum: 1, maximum: 65535 })))
45454
+ ),
45449
45455
  needs: Schema_exports.optionalKey(
45450
45456
  Schema_exports.Array(Schema_exports.String).annotate({
45451
45457
  description: "Ids of procs that must be ready before this one spawns. Declarative: readiness is auto-detected, no port numbers in the config.",
@@ -45501,7 +45507,11 @@ var resolveSpec = (spec, assigned, internal) => {
45501
45507
  ...spec,
45502
45508
  ...spec.shell === void 0 ? {} : { shell: bind6(spec.shell) },
45503
45509
  ...spec.cmd === void 0 ? {} : { cmd: spec.cmd.map(bind6) },
45504
- ...spec.env === void 0 ? {} : { env: Object.fromEntries(Object.entries(spec.env).map(([key, value3]) => [key, bind6(value3)])) },
45510
+ ...spec.env === void 0 ? {} : {
45511
+ env: Object.fromEntries(
45512
+ Object.entries(spec.env).map(([key, value3]) => [key, bind6(value3)])
45513
+ )
45514
+ },
45505
45515
  ...spec.url === void 0 ? {} : { url: publicOnly(spec.url) }
45506
45516
  };
45507
45517
  };
@@ -45549,6 +45559,25 @@ var validatePortTemplates = (procs) => {
45549
45559
  }
45550
45560
  return void 0;
45551
45561
  };
45562
+ var validatePinnedPorts = (config) => {
45563
+ const uiPort = config.port ?? DEFAULT_UI_PORT;
45564
+ const holders = /* @__PURE__ */ new Map();
45565
+ for (const proc of config.procs) {
45566
+ if (proc.port === void 0) continue;
45567
+ if (!usesOwnPort(proc)) {
45568
+ return `proc "${proc.id}" pins port ${proc.port} but does not use \${port} \u2014 the pin is the public side of an assigned port`;
45569
+ }
45570
+ if (proc.port === uiPort) {
45571
+ return `proc "${proc.id}" pins port ${proc.port}, which is the deck UI's own port`;
45572
+ }
45573
+ const holder = holders.get(proc.port);
45574
+ if (holder !== void 0) {
45575
+ return `procs "${holder}" and "${proc.id}" pin the same port ${proc.port}`;
45576
+ }
45577
+ holders.set(proc.port, proc.id);
45578
+ }
45579
+ return void 0;
45580
+ };
45552
45581
  var ProcdeckConfigSchema = Schema_exports.Struct({
45553
45582
  // JSON configs point at the published schema for editor completion; the key
45554
45583
  // is meaningless at runtime but must be accepted, because Struct is exact.
@@ -45582,7 +45611,7 @@ var ProcdeckConfigSchema = Schema_exports.Struct({
45582
45611
  }).pipe(
45583
45612
  Schema_exports.check(
45584
45613
  Schema_exports.makeFilter(
45585
- (config) => validateGraph(config.procs) ?? validatePortTemplates(config.procs)
45614
+ (config) => validateGraph(config.procs) ?? validatePortTemplates(config.procs) ?? validatePinnedPorts(config)
45586
45615
  )
45587
45616
  )
45588
45617
  ).annotate({
@@ -46120,7 +46149,7 @@ var selfLogs = (follow) => Effect_exports.gen(function* () {
46120
46149
  }),
46121
46150
  (onChange) => Effect_exports.sync(() => unwatchFile(file6, onChange))
46122
46151
  );
46123
- yield* Effect_exports.never;
46152
+ return yield* Effect_exports.never;
46124
46153
  }).pipe(Effect_exports.scoped);
46125
46154
  var logsQuery = (options) => Effect_exports.gen(function* () {
46126
46155
  let sinceMs;
@@ -46299,7 +46328,9 @@ var errors2 = Command_exports.make(
46299
46328
  const render = (group2) => {
46300
46329
  const head = `[${group2.proc}] ${group2.count}\xD7 \xB7 last ${ago(group2.lastTs)}${group2.count > 1 ? ` \xB7 first ${ago(group2.firstTs)}` : ""}`;
46301
46330
  const sample = group2.sample.slice(0, SAMPLE_SHOWN).map((line) => ` ${line}`);
46302
- const more = group2.sample.length > SAMPLE_SHOWN ? [` \u2026 +${group2.sample.length - SAMPLE_SHOWN} more lines (\`procdeck logs ${group2.proc}\`)`] : [];
46331
+ const more = group2.sample.length > SAMPLE_SHOWN ? [
46332
+ ` \u2026 +${group2.sample.length - SAMPLE_SHOWN} more lines (\`procdeck logs ${group2.proc}\`)`
46333
+ ] : [];
46303
46334
  return [head, ...sample, ...more].join("\n");
46304
46335
  };
46305
46336
  yield* Console_exports.log(groups.map(render).join("\n\n"));
@@ -46317,9 +46348,10 @@ var agents = Command_exports.make(
46317
46348
  const root = path11.dirname(configPath);
46318
46349
  const actions = setupAgents(root);
46319
46350
  yield* Console_exports.log(
46320
- ["procdeck: introducing the deck to coding agents", ...actions.map((line) => ` ${line}`)].join(
46321
- "\n"
46322
- )
46351
+ [
46352
+ "procdeck: introducing the deck to coding agents",
46353
+ ...actions.map((line) => ` ${line}`)
46354
+ ].join("\n")
46323
46355
  );
46324
46356
  yield* Console_exports.log(
46325
46357
  "tip: `claude mcp add procdeck -- procdeck mcp` adds the MCP tools in every procdeck project at once"
@@ -46334,9 +46366,7 @@ var mcp = Command_exports.make(
46334
46366
  "mcp",
46335
46367
  {
46336
46368
  mutations: Flag_exports.boolean("mutations").pipe(
46337
- Flag_exports.withDescription(
46338
- "also expose restart_proc / stop_proc / start_proc (default: read-only)"
46339
- )
46369
+ Flag_exports.withDescription("also expose restart_proc / stop_proc / start_proc (default: read-only)")
46340
46370
  )
46341
46371
  },
46342
46372
  // The server owns stdout from here on: no Console.log in this branch,
@@ -46782,7 +46812,9 @@ var planInit = (root) => {
46782
46812
  );
46783
46813
  }
46784
46814
  const globs = workspaceGlobs(root);
46785
- const dirs = [...new Set(globs.filter((g) => !g.startsWith("!")).flatMap((g) => expandGlob(root, g)))];
46815
+ const dirs = [
46816
+ ...new Set(globs.filter((g) => !g.startsWith("!")).flatMap((g) => expandGlob(root, g)))
46817
+ ];
46786
46818
  const fromWorkspaces = [];
46787
46819
  const workspaceNotes = [];
46788
46820
  for (const dir2 of dirs) {
@@ -46812,7 +46844,9 @@ var planInit = (root) => {
46812
46844
  }
46813
46845
  return done4(
46814
46846
  [{ id: "app", shell: "echo 'edit procdeck.config.json'; sleep 1000" }],
46815
- ["nothing recognisable here (package.json scripts, Procfile, Django/Go/Rust/Rails/compose) \u2014 wrote a template to edit"],
46847
+ [
46848
+ "nothing recognisable here (package.json scripts, Procfile, Django/Go/Rust/Rails/compose) \u2014 wrote a template to edit"
46849
+ ],
46816
46850
  "template"
46817
46851
  );
46818
46852
  };
@@ -47311,12 +47345,18 @@ var make63 = Effect_exports.fn("procdeck.supervisor")(function* (loaded) {
47311
47345
  const live = yield* PubSub_exports.sliding({ capacity: LIVE_BUFFER });
47312
47346
  const wantsPort = loaded.config.procs.filter(usesOwnPort);
47313
47347
  const observedSpecs = wantsPort.filter((spec) => spec.observe !== false);
47348
+ const unpinned = wantsPort.filter((spec) => spec.port === void 0);
47314
47349
  const freePorts = yield* Effect_exports.promise(
47315
- () => findFreePorts(wantsPort.length + observedSpecs.length)
47350
+ () => findFreePorts(unpinned.length + observedSpecs.length)
47316
47351
  );
47317
- const assigned = new Map(wantsPort.map((spec, index2) => [spec.id, freePorts[index2]]));
47352
+ const assigned = new Map([
47353
+ ...wantsPort.flatMap(
47354
+ (spec) => spec.port === void 0 ? [] : [[spec.id, spec.port]]
47355
+ ),
47356
+ ...unpinned.map((spec, index2) => [spec.id, freePorts[index2]])
47357
+ ]);
47318
47358
  const internal = new Map(
47319
- observedSpecs.map((spec, index2) => [spec.id, freePorts[wantsPort.length + index2]])
47359
+ observedSpecs.map((spec, index2) => [spec.id, freePorts[unpinned.length + index2]])
47320
47360
  );
47321
47361
  const runtimes = /* @__PURE__ */ new Map();
47322
47362
  for (const spec of loaded.config.procs) {
@@ -47325,9 +47365,13 @@ var make63 = Effect_exports.fn("procdeck.supervisor")(function* (loaded) {
47325
47365
  const resolved2 = resolveSpec(spec, assigned, internal);
47326
47366
  runtimes.set(spec.id, {
47327
47367
  spec,
47328
- resolved: assignedPort === void 0 ? resolved2 : { ...resolved2, env: { PORT: String(internalPort ?? assignedPort), ...resolved2.env } },
47368
+ resolved: assignedPort === void 0 ? resolved2 : (
47369
+ // The PaaS contract: the port to bind also arrives as `PORT`.
47370
+ { ...resolved2, env: { PORT: String(internalPort ?? assignedPort), ...resolved2.env } }
47371
+ ),
47329
47372
  assignedPort,
47330
47373
  internalPort,
47374
+ observerClose: void 0,
47331
47375
  status: { id: spec.id, state: "stopped" },
47332
47376
  proc: void 0,
47333
47377
  ready: Latch_exports.makeUnsafe(false),
@@ -47344,19 +47388,14 @@ var make63 = Effect_exports.fn("procdeck.supervisor")(function* (loaded) {
47344
47388
  rows: DEFAULT_ROWS
47345
47389
  });
47346
47390
  }
47347
- for (const [id2, internalPort] of internal) {
47348
- const runtime = runtimes.get(id2);
47349
- yield* Effect_exports.acquireRelease(
47350
- Effect_exports.promise(
47351
- () => startObserver({
47352
- publicPort: assigned.get(id2),
47353
- internalPort,
47354
- record: (capture) => runtime.http.record(capture)
47355
- })
47356
- ),
47357
- (close4) => Effect_exports.promise(close4)
47358
- );
47359
- }
47391
+ yield* Effect_exports.acquireRelease(
47392
+ Effect_exports.void,
47393
+ () => Effect_exports.promise(
47394
+ () => Promise.all(
47395
+ [...runtimes.values()].map((runtime) => runtime.observerClose?.() ?? Promise.resolve())
47396
+ ).then(() => void 0)
47397
+ )
47398
+ );
47360
47399
  const emit = (event) => {
47361
47400
  PubSub_exports.publishUnsafe(live, event);
47362
47401
  };
@@ -47385,9 +47424,7 @@ var make63 = Effect_exports.fn("procdeck.supervisor")(function* (loaded) {
47385
47424
  return Stream_exports.concat(
47386
47425
  Stream_exports.fromIterable(backlog),
47387
47426
  Stream_exports.fromSubscription(subscription).pipe(
47388
- Stream_exports.filter(
47389
- (event) => event.type !== "log" || event.seq >= (seen.get(event.id) ?? 0)
47390
- )
47427
+ Stream_exports.filter((event) => event.type !== "log" || event.seq >= (seen.get(event.id) ?? 0))
47391
47428
  )
47392
47429
  );
47393
47430
  })
@@ -47421,6 +47458,28 @@ var make63 = Effect_exports.fn("procdeck.supervisor")(function* (loaded) {
47421
47458
  }
47422
47459
  }
47423
47460
  };
47461
+ const bindObserver = (runtime) => startObserver({
47462
+ publicPort: runtime.assignedPort,
47463
+ internalPort: runtime.internalPort,
47464
+ record: (capture) => runtime.http.record(capture)
47465
+ }).then(
47466
+ (close4) => {
47467
+ runtime.observerClose = close4;
47468
+ return true;
47469
+ },
47470
+ () => false
47471
+ );
47472
+ const parkOnTakenPort = (runtime) => {
47473
+ const hint = `free :${runtime.assignedPort} (is "${runtime.spec.id}" already running outside procdeck?), then Start`;
47474
+ say(
47475
+ runtime,
47476
+ `\r
47477
+ procdeck: pinned port :${runtime.assignedPort} is taken \u2014 cannot observe "${runtime.spec.id}"\r
47478
+ procdeck: hint: ${hint}\r
47479
+ `
47480
+ );
47481
+ setStatus2(runtime, { id: runtime.spec.id, state: "blocked", hint });
47482
+ };
47424
47483
  const startSync = (runtime) => {
47425
47484
  if (runtime.proc !== void 0) return;
47426
47485
  setStatus2(runtime, { id: runtime.spec.id, state: "starting" });
@@ -47496,6 +47555,13 @@ ${lines2.join("\r\n")}\r
47496
47555
  const start = Effect_exports.fn("procdeck.start")(function* (id2) {
47497
47556
  const runtime = require_(id2);
47498
47557
  if (runtime.proc !== void 0 || runtime.waiter !== void 0) return;
47558
+ if (runtime.internalPort !== void 0 && runtime.observerClose === void 0) {
47559
+ const bound = yield* Effect_exports.promise(() => bindObserver(runtime));
47560
+ if (!bound) {
47561
+ parkOnTakenPort(runtime);
47562
+ return;
47563
+ }
47564
+ }
47499
47565
  const needs = runtime.spec.needs ?? [];
47500
47566
  if (needs.length === 0 && runtime.spec.preflight === void 0) {
47501
47567
  return yield* Effect_exports.sync(() => startSync(runtime));
@@ -47571,7 +47637,9 @@ ${lines2.join("\r\n")}\r
47571
47637
  list: () => [...runtimes.values()].map((runtime) => ({
47572
47638
  id: runtime.spec.id,
47573
47639
  command: describeCommand(runtime.resolved),
47574
- ...runtime.resolved.url === void 0 ? {} : { url: runtime.resolved.url },
47640
+ // A pinned proc has a stable public address even with no `url` in
47641
+ // the config — derive the link instead of making the user repeat it.
47642
+ ...runtime.resolved.url !== void 0 ? { url: runtime.resolved.url } : runtime.spec.port === void 0 ? {} : { url: `http://localhost:${runtime.spec.port}` },
47575
47643
  ...runtime.assignedPort === void 0 ? {} : { assignedPort: runtime.assignedPort },
47576
47644
  proxyUrl: `http://${runtime.spec.id}.localhost:${uiPort}`,
47577
47645
  status: runtime.status
@@ -47626,7 +47694,14 @@ ${lines2.join("\r\n")}\r
47626
47694
  discard: true
47627
47695
  });
47628
47696
  for (const runtime of runtimes.values()) {
47629
- if (runtime.spec.autostart !== false) yield* start(runtime.spec.id);
47697
+ if (runtime.internalPort === void 0) continue;
47698
+ const bound = yield* Effect_exports.promise(() => bindObserver(runtime));
47699
+ if (!bound) parkOnTakenPort(runtime);
47700
+ }
47701
+ for (const runtime of runtimes.values()) {
47702
+ if (runtime.spec.autostart !== false && runtime.status.state !== "blocked") {
47703
+ yield* start(runtime.spec.id);
47704
+ }
47630
47705
  }
47631
47706
  return { supervisor, stopAll };
47632
47707
  });
@@ -47840,6 +47915,7 @@ var routes = (supervisor, deck, hooks) => HttpRouter_exports.addAll([
47840
47915
  root: instance.root,
47841
47916
  port: instance.port,
47842
47917
  startedAt: instance.startedAt,
47918
+ version: instance.version,
47843
47919
  self: instance.root === deck.root
47844
47920
  }))
47845
47921
  )
@@ -48078,7 +48154,7 @@ var runServer = (loaded, configPath) => Effect_exports.gen(function* () {
48078
48154
  yield* Effect_exports.log(
48079
48155
  `procdeck ${VERSION} "${loaded.name}" listening on http://localhost:${port}${host === DEFAULT_UI_HOST ? "" : ` (bound to ${host})`} (pid ${process.pid}, ${mode})`
48080
48156
  );
48081
- yield* Effect_exports.never;
48157
+ return yield* Effect_exports.never;
48082
48158
  }).pipe(Effect_exports.scoped);
48083
48159
  var fgFlag = Flag_exports.boolean("fg").pipe(
48084
48160
  Flag_exports.withDescription("run in the foreground \u2014 Ctrl-C stops the deck")
@@ -48100,6 +48176,7 @@ var upHandler = ({
48100
48176
  yield* Console_exports.log(
48101
48177
  `procdeck: "${running.name}" is already up \u2192 ${deckUrl(running)} (pid ${running.pid})`
48102
48178
  );
48179
+ yield* versionNotice(running);
48103
48180
  if (open5) openBrowser(deckUrl(running));
48104
48181
  return;
48105
48182
  }
@@ -48122,6 +48199,9 @@ var upHandler = ({
48122
48199
  yield* Console_exports.log(" procdeck down \xB7 status \xB7 logs -f \xB7 open");
48123
48200
  if (open5) openBrowser(deckUrl(instance));
48124
48201
  });
48202
+ var versionNotice = (instance) => instance.version === VERSION ? Effect_exports.void : Console_exports.log(
48203
+ `procdeck: deck runs v${instance.version}, CLI is v${VERSION} \u2014 \`procdeck restart\` picks up the new version`
48204
+ );
48125
48205
  var downHandler = ({ config }) => Effect_exports.gen(function* () {
48126
48206
  const root = yield* resolveRoot(config);
48127
48207
  const instance = findInstance(root);
@@ -48133,7 +48213,11 @@ var downHandler = ({ config }) => Effect_exports.gen(function* () {
48133
48213
  const outcome = yield* Effect_exports.promise(() => shutdown3(instance));
48134
48214
  yield* Console_exports.log(outcome === "stopped" ? "down" : "did not stop in time, killed");
48135
48215
  });
48136
- var up = Command_exports.make("up", { config: configArgument, fg: fgFlag, open: openFlag }, upHandler).pipe(
48216
+ var up = Command_exports.make(
48217
+ "up",
48218
+ { config: configArgument, fg: fgFlag, open: openFlag },
48219
+ upHandler
48220
+ ).pipe(
48137
48221
  Command_exports.withDescription(
48138
48222
  "Start the deck and open its UI. Detaches by default: the deck keeps running after the terminal closes (`procdeck down` stops it). Idempotent \u2014 an already-running deck is just opened."
48139
48223
  )
@@ -48141,6 +48225,34 @@ var up = Command_exports.make("up", { config: configArgument, fg: fgFlag, open:
48141
48225
  var down = Command_exports.make("down", { config: configArgument }, downHandler).pipe(
48142
48226
  Command_exports.withDescription("Stop the deck: every process tree is terminated, then procdeck exits.")
48143
48227
  );
48228
+ var restartAllHandler = Effect_exports.gen(function* () {
48229
+ const instances = listInstances();
48230
+ if (instances.length === 0) return yield* Console_exports.log("procdeck: no decks are up");
48231
+ let failures = 0;
48232
+ for (const instance of instances) {
48233
+ if (instance.mode === "foreground") {
48234
+ yield* Console_exports.log(
48235
+ `procdeck: skipping "${instance.name}" (${pretty2(instance.root)}) \u2014 foreground deck, restart it in its own terminal`
48236
+ );
48237
+ continue;
48238
+ }
48239
+ process.stdout.write(`procdeck: restarting "${instance.name}" (${pretty2(instance.root)})\u2026 `);
48240
+ yield* Effect_exports.promise(() => shutdown3(instance));
48241
+ const outcome = yield* Effect_exports.promise(async () => {
48242
+ try {
48243
+ const fresh2 = await detach(process.argv[1], instance.config, instance.root);
48244
+ return `up \u2192 ${deckUrl(fresh2)} (v${fresh2.version})`;
48245
+ } catch (cause) {
48246
+ failures += 1;
48247
+ return `failed: ${cause.message}`;
48248
+ }
48249
+ });
48250
+ yield* Console_exports.log(outcome);
48251
+ }
48252
+ if (failures > 0) {
48253
+ return yield* fail11(`${failures} deck${failures === 1 ? "" : "s"} did not come back \u2014 see above`);
48254
+ }
48255
+ });
48144
48256
  var restart = Command_exports.make(
48145
48257
  "restart",
48146
48258
  {
@@ -48148,10 +48260,13 @@ var restart = Command_exports.make(
48148
48260
  Argument_exports.optional,
48149
48261
  Argument_exports.withDescription("proc id \u2014 restart just this process (default: the whole deck)")
48150
48262
  ),
48263
+ all: Flag_exports.boolean("all").pipe(
48264
+ Flag_exports.withDescription("restart every deck on this machine (e.g. after updating procdeck)")
48265
+ ),
48151
48266
  fg: fgFlag,
48152
48267
  open: openFlag
48153
48268
  },
48154
- ({ fg, open: open5, proc }) => Option_exports.isSome(proc) ? Effect_exports.gen(function* () {
48269
+ ({ all: all4, fg, open: open5, proc }) => all4 ? Option_exports.isSome(proc) ? fail11(`--all restarts every deck \u2014 drop "${proc.value}"`) : restartAllHandler : Option_exports.isSome(proc) ? Effect_exports.gen(function* () {
48155
48270
  const instance = yield* requireInstance(Option_exports.none());
48156
48271
  const procs = yield* callApi(() => apiGet(instance, "/procs"));
48157
48272
  if (!procs.some((info2) => info2.id === proc.value)) {
@@ -48170,7 +48285,7 @@ var restart = Command_exports.make(
48170
48285
  )
48171
48286
  ).pipe(
48172
48287
  Command_exports.withDescription(
48173
- "Restart one proc (`restart api`), or the whole deck (`restart`) \u2014 the latter after updating procdeck or editing the config."
48288
+ "Restart one proc (`restart api`), the whole deck (`restart`), or every deck on this machine (`restart --all`) \u2014 the deck-wide forms after updating procdeck or editing the config."
48174
48289
  )
48175
48290
  );
48176
48291
  var status = Command_exports.make(
@@ -48192,8 +48307,9 @@ var status = Command_exports.make(
48192
48307
  }
48193
48308
  const procs = yield* fetchProcs2(instance);
48194
48309
  yield* Console_exports.log(
48195
- `${instance.name} \xB7 ${deckUrl(instance)} \xB7 up ${describeUptime(instance.startedAt)} \xB7 pid ${instance.pid} \xB7 ${countRunning(procs)}${instance.mode === "foreground" ? " \xB7 foreground" : ""}`
48310
+ `${instance.name} \xB7 ${deckUrl(instance)} \xB7 up ${describeUptime(instance.startedAt)} \xB7 pid ${instance.pid} \xB7 ${countRunning(procs)} \xB7 v${instance.version}${instance.mode === "foreground" ? " \xB7 foreground" : ""}`
48196
48311
  );
48312
+ yield* versionNotice(instance);
48197
48313
  if (procs === void 0 || procs.length === 0) return;
48198
48314
  yield* Console_exports.log(
48199
48315
  table2(
@@ -48225,10 +48341,16 @@ var ls = Command_exports.make(
48225
48341
  `:${instance.port}`,
48226
48342
  `up ${describeUptime(instance.startedAt)}`,
48227
48343
  countRunning(procs[i]),
48344
+ `v${instance.version}`,
48228
48345
  pretty2(instance.root)
48229
48346
  ])
48230
48347
  )
48231
48348
  );
48349
+ if (instances.some((instance) => instance.version !== VERSION)) {
48350
+ yield* Console_exports.log(
48351
+ `procdeck: some decks run a different procdeck than the CLI (v${VERSION}) \u2014 \`procdeck restart --all\` brings them over`
48352
+ );
48353
+ }
48232
48354
  })
48233
48355
  ).pipe(Command_exports.withDescription("Every running deck on this machine."));
48234
48356
  var open4 = Command_exports.make(
@@ -48318,12 +48440,7 @@ var NodeLayer = layer2.pipe(
48318
48440
  layer5,
48319
48441
  layer6,
48320
48442
  CliConfig_exports.layer({
48321
- builtIns: [
48322
- GlobalFlag_exports.Help,
48323
- GlobalFlag_exports.Version,
48324
- GlobalFlag_exports.Completions,
48325
- GlobalFlag_exports.LogLevel
48326
- ]
48443
+ builtIns: [GlobalFlag_exports.Help, GlobalFlag_exports.Version, GlobalFlag_exports.Completions, GlobalFlag_exports.LogLevel]
48327
48444
  })
48328
48445
  )
48329
48446
  )
@@ -48333,9 +48450,11 @@ procdeck.pipe(
48333
48450
  Effect_exports.catchTag(
48334
48451
  "CliFailure",
48335
48452
  (failure) => Console_exports.error(`procdeck: ${failure.message}`).pipe(
48336
- Effect_exports.andThen(Effect_exports.sync(() => {
48337
- process.exitCode = failure.code ?? 1;
48338
- }))
48453
+ Effect_exports.andThen(
48454
+ Effect_exports.sync(() => {
48455
+ process.exitCode = failure.code ?? 1;
48456
+ })
48457
+ )
48339
48458
  )
48340
48459
  ),
48341
48460
  Effect_exports.provide(NodeLayer),
package/dist/public.d.ts CHANGED
@@ -42,6 +42,13 @@ export type ProcSpec = {
42
42
  readonly autostart?: boolean;
43
43
  /** URL this process serves, shown in the UI as a link. */
44
44
  readonly url?: string;
45
+ /**
46
+ * Pin this proc's assigned `${port}` to a fixed public number instead of a
47
+ * random free one — dependents outside the deck keep their hardcoded
48
+ * addresses, and an observed proc still binds a hidden internal port.
49
+ * Requires `${port}` somewhere in the spec.
50
+ */
51
+ readonly port?: number;
45
52
  /** Ids of procs that must be ready before this one spawns. */
46
53
  readonly needs?: ReadonlyArray<string>;
47
54
  /** What "ready" means for dependents. Defaults to "listening". */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "procdeck",
3
- "version": "0.5.0",
3
+ "version": "1.1.0",
4
4
  "description": "Interactive dev-process multiplexer with a web UI: terminal panes in a browser tab, declarative dependencies, assigned ports and a *.localhost reverse proxy.",
5
5
  "keywords": [
6
6
  "cli",
@@ -24,18 +24,15 @@
24
24
  "url": "https://github.com/kondaurovDev/procdeck",
25
25
  "directory": "packages/procdeck"
26
26
  },
27
- "type": "module",
28
- "engines": {
29
- "node": ">=22"
27
+ "bin": {
28
+ "procdeck": "./dist/cli.js"
30
29
  },
31
30
  "files": [
32
31
  "dist",
33
32
  "schema.json",
34
33
  "ui/dist"
35
34
  ],
36
- "bin": {
37
- "procdeck": "./dist/cli.js"
38
- },
35
+ "type": "module",
39
36
  "exports": {
40
37
  ".": {
41
38
  "types": "./dist/public.d.ts",
@@ -63,6 +60,9 @@
63
60
  "vite": "^6.2.0",
64
61
  "vitest": "^3.2.4"
65
62
  },
63
+ "engines": {
64
+ "node": ">=22"
65
+ },
66
66
  "scripts": {
67
67
  "ui:dev": "vite ui",
68
68
  "build": "pnpm run build:meta && pnpm run build:schema && pnpm run build:ui && pnpm run build:js && pnpm run build:types",