portcall 0.0.0-stage → 0.2.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.
@@ -0,0 +1,2886 @@
1
+ #!/usr/bin/env node
2
+ import { basename, dirname, join, resolve, sep } from "node:path";
3
+ import process from "node:process";
4
+ import { parseArgs, promisify } from "node:util";
5
+ import { execFile, spawn } from "node:child_process";
6
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
7
+ import { closeSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, readSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
8
+ import { Buffer } from "node:buffer";
9
+ import { homedir } from "node:os";
10
+ import { readlink } from "node:fs/promises";
11
+ import { connect } from "node:net";
12
+ import { createInterface } from "node:readline";
13
+ // package.json
14
+ var version$1 = "0.2.0";
15
+ // node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/core.js
16
+ var _a$1;
17
+ const _zodDesc = {
18
+ value: void 0,
19
+ enumerable: false
20
+ };
21
+ let _E = "captureStackTrace" in Error ? Error : null;
22
+ function newError(Definition) {
23
+ const E = _E;
24
+ if (E) {
25
+ const saved = E.stackTraceLimit;
26
+ if (typeof saved === "number") {
27
+ try {
28
+ E.stackTraceLimit = 0;
29
+ } catch {
30
+ _E = null;
31
+ return new Definition();
32
+ }
33
+ try {
34
+ return new Definition();
35
+ } finally {
36
+ E.stackTraceLimit = saved;
37
+ }
38
+ }
39
+ }
40
+ return new Definition();
41
+ }
42
+ /* @__NO_SIDE_EFFECTS__ */
43
+ function $constructor(name, initializer, proto, params) {
44
+ const zodProto = {};
45
+ function Internals(def) {
46
+ this.def = def;
47
+ this.constr = _;
48
+ this.traits = /* @__PURE__ */ new Set();
49
+ }
50
+ Internals.prototype = zodProto;
51
+ const protoMembers = proto;
52
+ const initialized = protoMembers && /* @__PURE__ */ new WeakSet();
53
+ function init(inst, def) {
54
+ if (!inst._zod) {
55
+ _zodDesc.value = new Internals(def);
56
+ try {
57
+ Object.defineProperty(inst, "_zod", _zodDesc);
58
+ } finally {
59
+ _zodDesc.value = void 0;
60
+ }
61
+ } else if (inst._zod.traits.has(name)) return;
62
+ inst._zod.traits.add(name);
63
+ initializer(inst, def);
64
+ if (initialized) {
65
+ const own = Object.getPrototypeOf(inst);
66
+ const ctorProto = inst._zod.constr.prototype;
67
+ let up = own;
68
+ while (up && up !== ctorProto) up = Object.getPrototypeOf(up);
69
+ const target = up ?? own;
70
+ if (!initialized.has(target)) {
71
+ initialized.add(target);
72
+ members(target, protoMembers);
73
+ }
74
+ }
75
+ const proto = _.prototype;
76
+ for (const k in proto) {
77
+ if (!Object.prototype.hasOwnProperty.call(proto, k)) continue;
78
+ if (!(k in inst)) inst[k] = proto[k].bind(inst);
79
+ }
80
+ }
81
+ const Parent = params?.Parent ?? Object;
82
+ class Definition extends Parent {}
83
+ Object.defineProperty(Definition, "name", { value: name });
84
+ function _(def) {
85
+ const inst = params?.Parent ? newError(Definition) : this;
86
+ init(inst, def);
87
+ const deferred = inst._zod.deferred;
88
+ if (deferred) {
89
+ for (const fn of deferred) fn();
90
+ inst._zod.deferred = void 0;
91
+ }
92
+ const pp = globalThis.__zod_globalConfig?.postProcessor;
93
+ if (pp) pp(inst);
94
+ return inst;
95
+ }
96
+ Object.defineProperty(_, "init", { value: init });
97
+ Object.defineProperty(_, Symbol.hasInstance, { value: (inst) => {
98
+ if (params?.Parent && inst instanceof params.Parent) return true;
99
+ return inst?._zod?.traits?.has(name);
100
+ } });
101
+ Object.defineProperty(_, "name", { value: name });
102
+ return _;
103
+ }
104
+ var $ZodAsyncError = class extends Error {
105
+ constructor() {
106
+ super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
107
+ }
108
+ };
109
+ (_a$1 = globalThis).__zod_globalConfig ?? (_a$1.__zod_globalConfig = {});
110
+ const globalConfig = globalThis.__zod_globalConfig;
111
+ function config(newConfig) {
112
+ if (newConfig) Object.assign(globalConfig, newConfig);
113
+ return globalConfig;
114
+ }
115
+ // node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/util.js
116
+ function joinValues(array, separator = "|") {
117
+ return array.map((val) => stringifyPrimitive(val)).join(separator);
118
+ }
119
+ function jsonStringifyReplacer(_, value) {
120
+ if (typeof value === "bigint") return value.toString();
121
+ return value;
122
+ }
123
+ var Cached = class {
124
+ constructor(getter) {
125
+ this._getter = getter;
126
+ this._value = void 0;
127
+ }
128
+ get value() {
129
+ const getter = this._getter;
130
+ if (getter !== void 0) {
131
+ this._value = getter();
132
+ this._getter = void 0;
133
+ }
134
+ return this._value;
135
+ }
136
+ };
137
+ function cached(getter) {
138
+ return new Cached(getter);
139
+ }
140
+ function nullish(input) {
141
+ return input === null || input === void 0;
142
+ }
143
+ function cleanRegex(source) {
144
+ const start = source.startsWith("^") ? 1 : 0;
145
+ const end = source.endsWith("$") ? source.length - 1 : source.length;
146
+ return source.slice(start, end);
147
+ }
148
+ function assignProp(target, prop, value) {
149
+ Object.defineProperty(target, prop, {
150
+ value,
151
+ writable: true,
152
+ enumerable: true,
153
+ configurable: true
154
+ });
155
+ }
156
+ const captureStackTrace = "captureStackTrace" in Error ? Error.captureStackTrace : (..._args) => {};
157
+ function isObject(data) {
158
+ return typeof data === "object" && data !== null && !Array.isArray(data);
159
+ }
160
+ function isPlainObject(o) {
161
+ if (isObject(o) === false) return false;
162
+ const ctor = o.constructor;
163
+ if (ctor === void 0) return true;
164
+ if (typeof ctor !== "function") return true;
165
+ const prot = ctor.prototype;
166
+ if (isObject(prot) === false) return false;
167
+ if (Object.prototype.hasOwnProperty.call(prot, "isPrototypeOf") === false) return false;
168
+ return true;
169
+ }
170
+ function clone(inst, def, params) {
171
+ const cl = new inst._zod.constr(def ?? inst._zod.def);
172
+ if (!def || params?.parent) cl._zod.parent = inst;
173
+ return cl;
174
+ }
175
+ function normalizeParams(_params) {
176
+ const params = _params;
177
+ if (!params) return {};
178
+ if (typeof params === "string") return { error: () => params };
179
+ if (params?.message !== void 0) {
180
+ if (params?.error !== void 0) throw new Error("Cannot specify both `message` and `error` params");
181
+ params.error = params.message;
182
+ }
183
+ delete params.message;
184
+ if (typeof params.error === "string") return {
185
+ ...params,
186
+ error: () => params.error
187
+ };
188
+ return params;
189
+ }
190
+ function stringifyPrimitive(value) {
191
+ if (typeof value === "bigint") return value.toString() + "n";
192
+ if (typeof value === "string") return `"${value}"`;
193
+ return `${value}`;
194
+ }
195
+ function optionalKeys(shape) {
196
+ return Object.keys(shape).filter((k) => {
197
+ return shape[k]._zod.optin !== void 0 && shape[k]._zod.optout === "optional";
198
+ });
199
+ }
200
+ const NUMBER_FORMAT_RANGES = /*@__PURE__*/ (() => ({
201
+ safeint: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
202
+ int32: [-2147483648, 2147483647],
203
+ uint32: [0, 4294967295],
204
+ float32: [-34028234663852886e22, 34028234663852886e22],
205
+ float64: [-Number.MAX_VALUE, Number.MAX_VALUE]
206
+ }))();
207
+ function aborted(x, startIndex = 0) {
208
+ if (x.aborted === true) return true;
209
+ for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue !== true) return true;
210
+ return false;
211
+ }
212
+ function explicitlyAborted(x, startIndex = 0) {
213
+ if (x.aborted === true) return true;
214
+ for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue === false) return true;
215
+ return false;
216
+ }
217
+ function prefixIssues(path, issues) {
218
+ return issues.map((iss) => {
219
+ var _a;
220
+ (_a = iss).path ?? (_a.path = []);
221
+ iss.path.unshift(path);
222
+ return iss;
223
+ });
224
+ }
225
+ function unwrapMessage(message) {
226
+ return typeof message === "string" ? message : message?.message;
227
+ }
228
+ function attachSchema(issues, start, inst) {
229
+ var _a;
230
+ for (let i = start; i < issues.length; i++) (_a = issues[i]).schema ?? (_a.schema = inst);
231
+ }
232
+ function finalizeIssue(iss, ctx, config) {
233
+ var _a;
234
+ const traits = iss.inst?._zod?.traits;
235
+ if (traits?.has("$ZodType")) {
236
+ if (traits.has("$ZodCheck")) (_a = iss).schema ?? (_a.schema = iss.inst);
237
+ else iss.schema = iss.inst;
238
+ }
239
+ const schemaError = iss.schema !== iss.inst ? iss.schema?._zod.def?.error : void 0;
240
+ const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(schemaError?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config.customError?.(iss)) ?? unwrapMessage(config.localeError?.(iss)) ?? "Invalid input";
241
+ const full = {};
242
+ for (const k of Object.keys(iss)) {
243
+ if (k === "inst" || k === "schema" || k === "continue" || k === "input" || k === "__proto__") continue;
244
+ full[k] = iss[k];
245
+ }
246
+ full.path ?? (full.path = []);
247
+ full.message = message;
248
+ if (ctx?.reportInput) full.input = iss.input;
249
+ return full;
250
+ }
251
+ const highSurrogate = /[\uD800-\uDBFF]/;
252
+ function codePointLength(str) {
253
+ const units = str.length;
254
+ if (!highSurrogate.test(str)) return units;
255
+ let count = units;
256
+ for (let i = 0; i < units - 1; i++) if ((str.charCodeAt(i) & 64512) === 55296 && (str.charCodeAt(i + 1) & 64512) === 56320) {
257
+ count--;
258
+ i++;
259
+ }
260
+ return count;
261
+ }
262
+ function getLengthableOrigin(input) {
263
+ if (Array.isArray(input)) return "array";
264
+ if (typeof input === "string") return "string";
265
+ return "unknown";
266
+ }
267
+ function parsedType(data) {
268
+ const t = typeof data;
269
+ switch (t) {
270
+ case "number": return Number.isNaN(data) ? "nan" : "number";
271
+ case "object": {
272
+ if (data === null) return "null";
273
+ if (Array.isArray(data)) return "array";
274
+ const obj = data;
275
+ if (obj && Object.getPrototypeOf(obj) !== Object.prototype && "constructor" in obj && obj.constructor) return obj.constructor.name;
276
+ }
277
+ }
278
+ return t;
279
+ }
280
+ /**
281
+ * Installs a trait's members on its prototype. Each value builds that member for the instance on first read; the built value shadows the accessor as an own property, so a detached `const { parse } = schema` keeps working.
282
+ *
283
+ * Call this from a `proto` initializer, which runs once per prototype — never per instance.
284
+ */
285
+ function members(proto, table) {
286
+ for (const key in table) {
287
+ const desc = Object.getOwnPropertyDescriptor(table, key);
288
+ if (desc.get) Object.defineProperty(proto, key, {
289
+ ...desc,
290
+ enumerable: false
291
+ });
292
+ else defineBound(proto, key, desc.value);
293
+ }
294
+ }
295
+ /** Shadows a prototype member with an own value, so a getter that builds from the instance runs once. */
296
+ function own(inst, key, value, enumerable = true) {
297
+ Object.defineProperty(inst, key, {
298
+ configurable: true,
299
+ writable: true,
300
+ enumerable,
301
+ value
302
+ });
303
+ return value;
304
+ }
305
+ /** Like {@link own}, for a member that was never an own data property and has to stay out of `Object.keys`. */
306
+ function hide(inst, key, value) {
307
+ return own(inst, key, value, false);
308
+ }
309
+ function defineBound(proto, key, fn) {
310
+ Object.defineProperty(proto, key, {
311
+ configurable: true,
312
+ get() {
313
+ return this == null ? fn : own(this, key, fn.bind(this));
314
+ },
315
+ set(value) {
316
+ own(this, key, value);
317
+ }
318
+ });
319
+ }
320
+ /** Returns the prototype to install on, or `undefined` if this group is already installed on it. */
321
+ function claim(inst, sentinel) {
322
+ const proto = Object.getPrototypeOf(inst);
323
+ return sentinel in proto ? void 0 : proto;
324
+ }
325
+ let installing;
326
+ let broke = false;
327
+ const breaker = {
328
+ configurable: true,
329
+ get() {
330
+ broke = true;
331
+ }
332
+ };
333
+ /**
334
+ * Installs a lazily-derived internal on the `_zod` prototype of `inst`'s
335
+ * constructor, computed from the internals object itself and cached there on
336
+ * first read. One accessor per constructor rather than one per instance.
337
+ */
338
+ function defineLazyInternal(inst, key, compute) {
339
+ const proto = Object.getPrototypeOf(inst._zod);
340
+ if (key in proto && installing !== inst._zod) {
341
+ installing = void 0;
342
+ return;
343
+ }
344
+ installing = inst._zod;
345
+ Object.defineProperty(proto, key, {
346
+ configurable: true,
347
+ get() {
348
+ Object.defineProperty(this, key, breaker);
349
+ const outer = broke;
350
+ broke = false;
351
+ try {
352
+ const value = compute(this);
353
+ if (broke) delete this[key];
354
+ else Object.defineProperty(this, key, {
355
+ configurable: true,
356
+ writable: true,
357
+ value
358
+ });
359
+ broke = broke || outer;
360
+ return value;
361
+ } catch (err) {
362
+ delete this[key];
363
+ broke = broke || outer;
364
+ throw err;
365
+ }
366
+ },
367
+ set(value) {
368
+ Object.defineProperty(this, key, {
369
+ configurable: true,
370
+ writable: true,
371
+ value
372
+ });
373
+ }
374
+ });
375
+ }
376
+ /**
377
+ * Installs `key` on `inst`'s prototype, computed by `make` on first read and cached there as an own
378
+ * data property. One accessor per constructor rather than one per instance, because an own accessor
379
+ * puts every instance after the first into v8 dictionary mode. The key doubles as the sentinel.
380
+ */
381
+ function installLazyProp(inst, key, make, enumerable) {
382
+ const proto = claim(inst, key);
383
+ if (!proto) return;
384
+ Object.defineProperty(proto, key, {
385
+ configurable: true,
386
+ get() {
387
+ const desc = {
388
+ configurable: true,
389
+ writable: true,
390
+ enumerable,
391
+ value: void 0
392
+ };
393
+ Object.defineProperty(this, key, desc);
394
+ desc.value = make(this);
395
+ Object.defineProperty(this, key, desc);
396
+ return desc.value;
397
+ },
398
+ set(value) {
399
+ Object.defineProperty(this, key, {
400
+ configurable: true,
401
+ writable: true,
402
+ enumerable,
403
+ value
404
+ });
405
+ }
406
+ });
407
+ }
408
+ // node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/locales/en.js
409
+ const error = () => {
410
+ const Sizable = {
411
+ string: {
412
+ unit: "characters",
413
+ verb: "to have"
414
+ },
415
+ file: {
416
+ unit: "bytes",
417
+ verb: "to have"
418
+ },
419
+ array: {
420
+ unit: "items",
421
+ verb: "to have"
422
+ },
423
+ set: {
424
+ unit: "items",
425
+ verb: "to have"
426
+ },
427
+ map: {
428
+ unit: "entries",
429
+ verb: "to have"
430
+ }
431
+ };
432
+ function getSizing(origin) {
433
+ return Sizable[origin] ?? null;
434
+ }
435
+ const FormatDictionary = {
436
+ regex: "input",
437
+ email: "email address",
438
+ url: "URL",
439
+ emoji: "emoji",
440
+ uuid: "UUID",
441
+ uuidv4: "UUIDv4",
442
+ uuidv6: "UUIDv6",
443
+ nanoid: "nanoid",
444
+ guid: "GUID",
445
+ cuid: "cuid",
446
+ cuid2: "cuid2",
447
+ ulid: "ULID",
448
+ xid: "XID",
449
+ ksuid: "KSUID",
450
+ datetime: "ISO datetime",
451
+ date: "ISO date",
452
+ time: "ISO time",
453
+ duration: "ISO duration",
454
+ ipv4: "IPv4 address",
455
+ ipv6: "IPv6 address",
456
+ mac: "MAC address",
457
+ cidrv4: "IPv4 range",
458
+ cidrv6: "IPv6 range",
459
+ base64: "base64-encoded string",
460
+ base64url: "base64url-encoded string",
461
+ json_string: "JSON string",
462
+ e164: "E.164 number",
463
+ currency_code: "currency code",
464
+ credit_card: "credit card number",
465
+ iban: "IBAN",
466
+ jwt: "JWT",
467
+ template_literal: "input"
468
+ };
469
+ const TypeDictionary = { nan: "NaN" };
470
+ function getTypeName(type, input) {
471
+ if (type === "number" && typeof input === "number" && !Number.isFinite(input)) return String(input);
472
+ return TypeDictionary[type] ?? type;
473
+ }
474
+ return (issue) => {
475
+ switch (issue.code) {
476
+ case "invalid_type": return `Invalid input: expected ${getTypeName(issue.expected)}, received ${getTypeName(parsedType(issue.input), issue.input)}`;
477
+ case "invalid_value":
478
+ if (issue.values.length === 1) return `Invalid input: expected ${stringifyPrimitive(issue.values[0])}`;
479
+ return `Invalid option: expected one of ${joinValues(issue.values, "|")}`;
480
+ case "too_big": {
481
+ const adj = issue.exact ? "exactly " : issue.inclusive ? "<=" : "<";
482
+ const sizing = getSizing(issue.origin);
483
+ if (sizing) return `Too big: expected ${issue.origin ?? "value"} to have ${adj}${issue.maximum.toString()} ${sizing.unit ?? "elements"}`;
484
+ return `Too big: expected ${issue.origin ?? "value"} to be ${adj}${issue.maximum.toString()}`;
485
+ }
486
+ case "too_small": {
487
+ const adj = issue.exact ? "exactly " : issue.inclusive ? ">=" : ">";
488
+ const sizing = getSizing(issue.origin);
489
+ if (sizing) return `Too small: expected ${issue.origin} to have ${adj}${issue.minimum.toString()} ${sizing.unit}`;
490
+ return `Too small: expected ${issue.origin} to be ${adj}${issue.minimum.toString()}`;
491
+ }
492
+ case "invalid_format": {
493
+ const _issue = issue;
494
+ if (_issue.format === "starts_with") return `Invalid string: must start with "${_issue.prefix}"`;
495
+ if (_issue.format === "ends_with") return `Invalid string: must end with "${_issue.suffix}"`;
496
+ if (_issue.format === "includes") return `Invalid string: must include "${_issue.includes}"`;
497
+ if (_issue.format === "regex") return `Invalid string: must match pattern ${_issue.pattern}`;
498
+ return `Invalid ${FormatDictionary[_issue.format] ?? issue.format}`;
499
+ }
500
+ case "not_multiple_of": return `Invalid number: must be a multiple of ${issue.divisor}`;
501
+ case "unrecognized_keys": return `Unrecognized key${issue.keys.length > 1 ? "s" : ""}: ${joinValues(issue.keys, ", ")}`;
502
+ case "invalid_key": return `Invalid key in ${issue.origin}`;
503
+ case "invalid_union":
504
+ if (issue.options && Array.isArray(issue.options) && issue.options.length > 0) return `Invalid discriminator value. Expected ${issue.options.map((o) => `'${o}'`).join(" | ")}`;
505
+ if (issue.inclusive === false) return "Invalid input: more than one option matched";
506
+ return "Invalid input";
507
+ case "invalid_element": return `Invalid value in ${issue.origin}`;
508
+ default: return `Invalid input`;
509
+ }
510
+ };
511
+ };
512
+ function en_default() {
513
+ return { localeError: error() };
514
+ }
515
+ // node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/errors.js
516
+ function _getMessage() {
517
+ const internals = this._zod;
518
+ internals.message ?? (internals.message = JSON.stringify(internals.def, jsonStringifyReplacer, 2));
519
+ return internals.message;
520
+ }
521
+ function _setMessage(value) {
522
+ this._zod.message = value;
523
+ }
524
+ const _messageDesc = {
525
+ get: _getMessage,
526
+ set: _setMessage,
527
+ enumerable: true,
528
+ configurable: true
529
+ };
530
+ const _issuesDesc = {
531
+ value: void 0,
532
+ enumerable: false
533
+ };
534
+ const _installedToString = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);
535
+ const initializer = (inst, def) => {
536
+ inst.name = "$ZodError";
537
+ _issuesDesc.value = def;
538
+ Object.defineProperty(inst, "issues", _issuesDesc);
539
+ _issuesDesc.value = void 0;
540
+ Object.defineProperty(inst, "message", _messageDesc);
541
+ const proto = Object.getPrototypeOf(inst);
542
+ if (!_installedToString.has(proto)) {
543
+ _installedToString.add(proto);
544
+ Object.defineProperty(proto, "toString", {
545
+ configurable: true,
546
+ enumerable: false,
547
+ get() {
548
+ const value = () => this.message;
549
+ Object.defineProperty(this, "toString", {
550
+ value,
551
+ configurable: true,
552
+ writable: true
553
+ });
554
+ return value;
555
+ },
556
+ set(value) {
557
+ Object.defineProperty(this, "toString", {
558
+ value,
559
+ configurable: true,
560
+ writable: true
561
+ });
562
+ }
563
+ });
564
+ }
565
+ };
566
+ const $ZodRealError = /* @__PURE__ */ $constructor("$ZodError", initializer, void 0, { Parent: Error });
567
+ /** Format a ZodError as a human-readable string in the following form.
568
+ *
569
+ * From
570
+ *
571
+ * ```ts
572
+ * ZodError {
573
+ * issues: [
574
+ * {
575
+ * expected: 'string',
576
+ * code: 'invalid_type',
577
+ * path: [ 'username' ],
578
+ * message: 'Invalid input: expected string'
579
+ * },
580
+ * {
581
+ * expected: 'number',
582
+ * code: 'invalid_type',
583
+ * path: [ 'favoriteNumbers', 1 ],
584
+ * message: 'Invalid input: expected number'
585
+ * }
586
+ * ];
587
+ * }
588
+ * ```
589
+ *
590
+ * to
591
+ *
592
+ * ```
593
+ * username
594
+ * ✖ Expected number, received string at "username
595
+ * favoriteNumbers[0]
596
+ * ✖ Invalid input: expected number
597
+ * ```
598
+ */
599
+ function toDotPath(_path) {
600
+ const segs = [];
601
+ const path = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
602
+ for (const seg of path) if (typeof seg === "number") segs.push(`[${seg}]`);
603
+ else if (typeof seg === "symbol") segs.push(`[${JSON.stringify(String(seg))}]`);
604
+ else if (/[^\w$]/.test(seg)) segs.push(`[${JSON.stringify(seg)}]`);
605
+ else {
606
+ if (segs.length) segs.push(".");
607
+ segs.push(seg);
608
+ }
609
+ return segs.join("");
610
+ }
611
+ function prettifyError(error) {
612
+ const lines = [];
613
+ const issues = [...error.issues].sort((a, b) => (a.path ?? []).length - (b.path ?? []).length);
614
+ for (const issue of issues) {
615
+ lines.push(`✖ ${issue.message}`);
616
+ if (issue.path?.length) lines.push(` → at ${toDotPath(issue.path)}`);
617
+ }
618
+ return lines.join("\n");
619
+ }
620
+ // node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/parse.js
621
+ const _parse = (_Err) => {
622
+ const fn = (schema, value, _ctx, _params) => {
623
+ const ctx = _ctx ? {
624
+ ..._ctx,
625
+ async: false
626
+ } : { async: false };
627
+ const result = schema._zod.run({
628
+ value,
629
+ issues: []
630
+ }, ctx);
631
+ if (result instanceof Promise) throw new $ZodAsyncError();
632
+ if (result.issues.length) {
633
+ const e = new ((_params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
634
+ captureStackTrace(e, _params?.callee ?? fn);
635
+ throw e;
636
+ }
637
+ return result.value;
638
+ };
639
+ return fn;
640
+ };
641
+ const parse = /* @__PURE__*/ _parse($ZodRealError);
642
+ const _parseAsync = (_Err) => {
643
+ const fn = async (schema, value, _ctx, params) => {
644
+ const ctx = _ctx ? {
645
+ ..._ctx,
646
+ async: true
647
+ } : { async: true };
648
+ let result = schema._zod.run({
649
+ value,
650
+ issues: []
651
+ }, ctx);
652
+ if (result instanceof Promise) result = await result;
653
+ if (result.issues.length) {
654
+ const e = new ((params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
655
+ captureStackTrace(e, params?.callee ?? fn);
656
+ throw e;
657
+ }
658
+ return result.value;
659
+ };
660
+ return fn;
661
+ };
662
+ const parseAsync = /* @__PURE__*/ _parseAsync($ZodRealError);
663
+ const _safeParse = (_Err) => (schema, value, _ctx) => {
664
+ const ctx = _ctx ? {
665
+ ..._ctx,
666
+ async: false
667
+ } : { async: false };
668
+ const result = schema._zod.run({
669
+ value,
670
+ issues: []
671
+ }, ctx);
672
+ if (result instanceof Promise) throw new $ZodAsyncError();
673
+ return result.issues.length ? failure(_Err, result.issues, ctx) : {
674
+ success: true,
675
+ data: result.value
676
+ };
677
+ };
678
+ const safeParse = /* @__PURE__*/ _safeParse($ZodRealError);
679
+ function failure(Err, issues, ctx) {
680
+ let error;
681
+ return {
682
+ success: false,
683
+ get error() {
684
+ if (!error) {
685
+ error = new Err(issues.map((iss) => finalizeIssue(iss, ctx, config())));
686
+ issues = void 0;
687
+ ctx = void 0;
688
+ }
689
+ return error;
690
+ },
691
+ set error(e) {
692
+ error = e;
693
+ issues = void 0;
694
+ ctx = void 0;
695
+ }
696
+ };
697
+ }
698
+ const _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
699
+ const ctx = _ctx ? {
700
+ ..._ctx,
701
+ async: true
702
+ } : { async: true };
703
+ let result = schema._zod.run({
704
+ value,
705
+ issues: []
706
+ }, ctx);
707
+ if (result instanceof Promise) result = await result;
708
+ return result.issues.length ? failure(_Err, result.issues, ctx) : {
709
+ success: true,
710
+ data: result.value
711
+ };
712
+ };
713
+ const safeParseAsync = /* @__PURE__*/ _safeParseAsync($ZodRealError);
714
+ // node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/regexes.js
715
+ const anyString = /^[\s\S]{0,}$/;
716
+ const number$1 = /^-?\d+(?:\.\d+)?$/;
717
+ const boolean$1 = /^(?:true|false)$/i;
718
+ // node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/checks.js
719
+ const $ZodCheck = /*@__PURE__*/ $constructor("$ZodCheck", (inst, def) => {
720
+ var _a;
721
+ inst._zod ?? (inst._zod = {});
722
+ inst._zod.def = def;
723
+ (_a = inst._zod).onattach ?? (_a.onattach = []);
724
+ });
725
+ /** Default `when` for length-based checks: run only on non-nullish values with a `length`. */
726
+ const _whenHasLength = (payload) => {
727
+ const val = payload.value;
728
+ return !nullish(val) && val.length !== void 0;
729
+ };
730
+ const numericOriginMap = {
731
+ number: "number",
732
+ bigint: "bigint",
733
+ object: "date"
734
+ };
735
+ const $ZodCheckLessThan = /*@__PURE__*/ $constructor("$ZodCheckLessThan", (inst, def) => {
736
+ $ZodCheck.init(inst, def);
737
+ const origin = numericOriginMap[typeof def.value];
738
+ inst._zod.check = (payload) => {
739
+ if (def.inclusive ? payload.value <= def.value : payload.value < def.value) return;
740
+ payload.issues.push({
741
+ origin: numericOriginMap[typeof payload.value] ?? origin,
742
+ code: "too_big",
743
+ maximum: typeof def.value === "object" ? def.value.getTime() : def.value,
744
+ input: payload.value,
745
+ inclusive: def.inclusive,
746
+ inst,
747
+ continue: !def.abort
748
+ });
749
+ };
750
+ });
751
+ const $ZodCheckGreaterThan = /*@__PURE__*/ $constructor("$ZodCheckGreaterThan", (inst, def) => {
752
+ $ZodCheck.init(inst, def);
753
+ const origin = numericOriginMap[typeof def.value];
754
+ inst._zod.check = (payload) => {
755
+ if (def.inclusive ? payload.value >= def.value : payload.value > def.value) return;
756
+ payload.issues.push({
757
+ origin: numericOriginMap[typeof payload.value] ?? origin,
758
+ code: "too_small",
759
+ minimum: typeof def.value === "object" ? def.value.getTime() : def.value,
760
+ input: payload.value,
761
+ inclusive: def.inclusive,
762
+ inst,
763
+ continue: !def.abort
764
+ });
765
+ };
766
+ });
767
+ const $ZodCheckNumberFormat = /*@__PURE__*/ $constructor("$ZodCheckNumberFormat", (inst, def) => {
768
+ $ZodCheck.init(inst, def);
769
+ def.format = def.format || "float64";
770
+ const isInt = def.format?.includes("int");
771
+ const origin = isInt ? "int" : "number";
772
+ const [minimum, maximum] = NUMBER_FORMAT_RANGES[def.format];
773
+ inst._zod.check = (payload) => {
774
+ const input = payload.value;
775
+ if (isInt) {
776
+ if (!Number.isInteger(input)) {
777
+ payload.issues.push({
778
+ expected: origin,
779
+ format: def.format,
780
+ code: "invalid_type",
781
+ continue: false,
782
+ input,
783
+ inst
784
+ });
785
+ return;
786
+ }
787
+ if (!Number.isSafeInteger(input)) {
788
+ if (input > 0) payload.issues.push({
789
+ input,
790
+ code: "too_big",
791
+ maximum: Number.MAX_SAFE_INTEGER,
792
+ note: "Integers must be within the safe integer range.",
793
+ inst,
794
+ origin,
795
+ inclusive: true,
796
+ continue: !def.abort
797
+ });
798
+ else payload.issues.push({
799
+ input,
800
+ code: "too_small",
801
+ minimum: Number.MIN_SAFE_INTEGER,
802
+ note: "Integers must be within the safe integer range.",
803
+ inst,
804
+ origin,
805
+ inclusive: true,
806
+ continue: !def.abort
807
+ });
808
+ return;
809
+ }
810
+ }
811
+ if (input < minimum) payload.issues.push({
812
+ origin: "number",
813
+ input,
814
+ code: "too_small",
815
+ minimum,
816
+ inclusive: true,
817
+ inst,
818
+ continue: !def.abort
819
+ });
820
+ if (input > maximum) payload.issues.push({
821
+ origin: "number",
822
+ input,
823
+ code: "too_big",
824
+ maximum,
825
+ inclusive: true,
826
+ inst,
827
+ continue: !def.abort
828
+ });
829
+ };
830
+ });
831
+ const $ZodCheckMinLength = /*@__PURE__*/ $constructor("$ZodCheckMinLength", (inst, def) => {
832
+ var _a;
833
+ $ZodCheck.init(inst, def);
834
+ (_a = inst._zod.def).when ?? (_a.when = _whenHasLength);
835
+ inst._zod.check = (payload) => {
836
+ const input = payload.value;
837
+ const units = input.length;
838
+ if ((typeof input === "string" && units >= def.minimum && units < def.minimum * 2 ? codePointLength(input) : units) >= def.minimum) return;
839
+ const origin = getLengthableOrigin(input);
840
+ payload.issues.push({
841
+ origin,
842
+ code: "too_small",
843
+ minimum: def.minimum,
844
+ inclusive: true,
845
+ input,
846
+ inst,
847
+ continue: !def.abort
848
+ });
849
+ };
850
+ });
851
+ const $ZodCheckStringFormat = /*@__PURE__*/ $constructor("$ZodCheckStringFormat", (inst, def) => {
852
+ var _a, _b;
853
+ $ZodCheck.init(inst, def);
854
+ if (def.pattern) (_a = inst._zod).check ?? (_a.check = (payload) => {
855
+ def.pattern.lastIndex = 0;
856
+ if (def.pattern.test(payload.value)) return;
857
+ payload.issues.push({
858
+ origin: "string",
859
+ code: "invalid_format",
860
+ format: def.format,
861
+ input: payload.value,
862
+ ...def.pattern ? { pattern: def.pattern.toString() } : {},
863
+ inst,
864
+ continue: !def.abort
865
+ });
866
+ });
867
+ else (_b = inst._zod).check ?? (_b.check = () => {});
868
+ });
869
+ const $ZodCheckRegex = /*@__PURE__*/ $constructor("$ZodCheckRegex", (inst, def) => {
870
+ $ZodCheckStringFormat.init(inst, def);
871
+ inst._zod.check = (payload) => {
872
+ def.pattern.lastIndex = 0;
873
+ if (def.pattern.test(payload.value)) return;
874
+ payload.issues.push({
875
+ origin: "string",
876
+ code: "invalid_format",
877
+ format: "regex",
878
+ input: payload.value,
879
+ pattern: def.pattern.toString(),
880
+ inst,
881
+ continue: !def.abort
882
+ });
883
+ };
884
+ });
885
+ // node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/versions.js
886
+ const version = {
887
+ major: 4,
888
+ minor: 6,
889
+ patch: 7
890
+ };
891
+ // node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/schemas.js
892
+ const $ZodType = /*@__PURE__*/ $constructor("$ZodType", (inst, def) => {
893
+ var _a;
894
+ inst ?? (inst = {});
895
+ inst._zod.def = def;
896
+ inst._zod.bag = inst._zod.bag || {};
897
+ inst._zod.version = version;
898
+ const defChecks = inst._zod.def.checks;
899
+ const checks = inst._zod.traits.has("$ZodCheck") ? [inst, ...defChecks ?? []] : defChecks?.length ? [...defChecks] : [];
900
+ for (const ch of checks) for (const fn of ch._zod.onattach) fn(inst);
901
+ if (checks.length === 0) {
902
+ (_a = inst._zod).deferred ?? (_a.deferred = []);
903
+ inst._zod.deferred?.push(() => {
904
+ inst._zod.run = inst._zod.parse;
905
+ });
906
+ } else {
907
+ const runChecks = (payload, checks, ctx) => {
908
+ if (payload.memo) return payload;
909
+ let isAborted = aborted(payload);
910
+ let asyncResult;
911
+ for (const ch of checks) {
912
+ if (ch._zod.def.when) {
913
+ if (explicitlyAborted(payload)) continue;
914
+ if (!ch._zod.def.when(payload)) continue;
915
+ } else if (isAborted) continue;
916
+ const currLen = payload.issues.length;
917
+ const _ = ch._zod.check(payload);
918
+ if (_ instanceof Promise && ctx?.async === false) throw new $ZodAsyncError();
919
+ if (asyncResult || _ instanceof Promise) asyncResult = (asyncResult ?? Promise.resolve()).then(async () => {
920
+ await _;
921
+ if (payload.issues.length === currLen) return;
922
+ attachSchema(payload.issues, currLen, inst);
923
+ if (!isAborted) isAborted = aborted(payload, currLen);
924
+ });
925
+ else {
926
+ if (payload.issues.length === currLen) continue;
927
+ attachSchema(payload.issues, currLen, inst);
928
+ if (!isAborted) isAborted = aborted(payload, currLen);
929
+ }
930
+ }
931
+ if (asyncResult) return asyncResult.then(() => {
932
+ return payload;
933
+ });
934
+ return payload;
935
+ };
936
+ const handleCanaryResult = (canary, payload, ctx) => {
937
+ if (aborted(canary)) {
938
+ canary.aborted = true;
939
+ return canary;
940
+ }
941
+ const checkResult = runChecks(payload, checks, ctx);
942
+ if (checkResult instanceof Promise) {
943
+ if (ctx.async === false) throw new $ZodAsyncError();
944
+ return checkResult.then((checkResult) => inst._zod.parse(checkResult, ctx));
945
+ }
946
+ return inst._zod.parse(checkResult, ctx);
947
+ };
948
+ inst._zod.run = (payload, ctx) => {
949
+ if (ctx.skipChecks) return inst._zod.parse(payload, ctx);
950
+ if (ctx.direction === "backward") {
951
+ const canary = inst._zod.parse({
952
+ value: payload.value,
953
+ issues: []
954
+ }, {
955
+ ...ctx,
956
+ skipChecks: true
957
+ });
958
+ if (canary instanceof Promise) return canary.then((canary) => {
959
+ return handleCanaryResult(canary, payload, ctx);
960
+ });
961
+ return handleCanaryResult(canary, payload, ctx);
962
+ }
963
+ const result = inst._zod.parse(payload, ctx);
964
+ if (result instanceof Promise) {
965
+ if (ctx.async === false) throw new $ZodAsyncError();
966
+ return result.then((result) => runChecks(result, checks, ctx));
967
+ }
968
+ return runChecks(result, checks, ctx);
969
+ };
970
+ }
971
+ }, {
972
+ get "~standard"() {
973
+ return hide(this, "~standard", standardProps(this));
974
+ },
975
+ set "~standard"(value) {
976
+ own(this, "~standard", value);
977
+ }
978
+ });
979
+ /** The Standard Schema surface for `inst`. Shared so wrappers can extend it without forcing it. */
980
+ const toStandardResult = (r, ctx) => r.issues.length ? { issues: r.issues.map((iss) => finalizeIssue(iss, ctx, config())) } : { value: r.value };
981
+ async function validateAsync(inst, value) {
982
+ const ctx = { async: true };
983
+ return toStandardResult(await inst._zod.run({
984
+ value,
985
+ issues: []
986
+ }, ctx), ctx);
987
+ }
988
+ function standardProps(inst) {
989
+ return {
990
+ validate: (value) => {
991
+ const ctx = { async: false };
992
+ try {
993
+ const r = inst._zod.run({
994
+ value,
995
+ issues: []
996
+ }, ctx);
997
+ if (!(r instanceof Promise)) return toStandardResult(r, ctx);
998
+ } catch (_) {}
999
+ return validateAsync(inst, value);
1000
+ },
1001
+ vendor: "zod",
1002
+ version: 1
1003
+ };
1004
+ }
1005
+ const $ZodString = /*@__PURE__*/ $constructor("$ZodString", (inst, def) => {
1006
+ $ZodType.init(inst, def);
1007
+ inst._zod.pattern = def.pattern ?? anyString;
1008
+ inst._zod.parse = (payload, _) => {
1009
+ if (def.coerce) try {
1010
+ payload.value = String(payload.value);
1011
+ } catch (_) {}
1012
+ if (typeof payload.value === "string") return payload;
1013
+ payload.issues.push({
1014
+ expected: "string",
1015
+ code: "invalid_type",
1016
+ input: payload.value,
1017
+ inst
1018
+ });
1019
+ return payload;
1020
+ };
1021
+ });
1022
+ const $ZodNumber = /*@__PURE__*/ $constructor("$ZodNumber", (inst, def) => {
1023
+ $ZodType.init(inst, def);
1024
+ inst._zod.pattern = number$1;
1025
+ inst._zod.parse = (payload, _ctx) => {
1026
+ if (def.coerce) try {
1027
+ payload.value = Number(payload.value);
1028
+ } catch (_) {}
1029
+ const input = payload.value;
1030
+ if (typeof input === "number" && !Number.isNaN(input) && Number.isFinite(input)) return payload;
1031
+ const received = typeof input === "number" ? Number.isNaN(input) ? "NaN" : !Number.isFinite(input) ? String(input) : void 0 : void 0;
1032
+ payload.issues.push({
1033
+ expected: "number",
1034
+ code: "invalid_type",
1035
+ input,
1036
+ inst,
1037
+ ...received ? { received } : {}
1038
+ });
1039
+ return payload;
1040
+ };
1041
+ });
1042
+ const $ZodNumberFormat = /*@__PURE__*/ $constructor("$ZodNumberFormat", (inst, def) => {
1043
+ $ZodCheckNumberFormat.init(inst, def);
1044
+ $ZodNumber.init(inst, def);
1045
+ });
1046
+ const $ZodBoolean = /*@__PURE__*/ $constructor("$ZodBoolean", (inst, def) => {
1047
+ $ZodType.init(inst, def);
1048
+ inst._zod.pattern = boolean$1;
1049
+ inst._zod.parse = (payload, _ctx) => {
1050
+ if (def.coerce) try {
1051
+ payload.value = Boolean(payload.value);
1052
+ } catch (_) {}
1053
+ const input = payload.value;
1054
+ if (typeof input === "boolean") return payload;
1055
+ payload.issues.push({
1056
+ expected: "boolean",
1057
+ code: "invalid_type",
1058
+ input,
1059
+ inst
1060
+ });
1061
+ return payload;
1062
+ };
1063
+ });
1064
+ const $ZodNever = /*@__PURE__*/ $constructor("$ZodNever", (inst, def) => {
1065
+ $ZodType.init(inst, def);
1066
+ inst._zod.parse = (payload, _ctx) => {
1067
+ payload.issues.push({
1068
+ expected: "never",
1069
+ code: "invalid_type",
1070
+ input: payload.value,
1071
+ inst
1072
+ });
1073
+ return payload;
1074
+ };
1075
+ });
1076
+ function handlePropertyResult(result, final, key, input, optin, optout) {
1077
+ const isPresent = key in input;
1078
+ const isOptionalOut = optout === "optional";
1079
+ if (!isPresent && isOptionalOut && optin === "optional") return;
1080
+ if (result.issues.length) {
1081
+ if (optin !== void 0 && isOptionalOut && !isPresent) return;
1082
+ final.issues.push(...prefixIssues(key, result.issues));
1083
+ }
1084
+ if (!isPresent && optin === void 0) {
1085
+ if (!result.issues.length) final.issues.push({
1086
+ code: "invalid_type",
1087
+ expected: "nonoptional",
1088
+ input: void 0,
1089
+ path: [key]
1090
+ });
1091
+ return;
1092
+ }
1093
+ if (result.value === void 0) {
1094
+ if (isPresent || optin === "defaulted" && !isOptionalOut) final.value[key] = void 0;
1095
+ } else final.value[key] = result.value;
1096
+ }
1097
+ const NO_SYMBOL_KEYS = [];
1098
+ function normalizeDef(def) {
1099
+ const keys = Object.keys(def.shape);
1100
+ const ownSymbols = Object.getOwnPropertySymbols(def.shape);
1101
+ const symbolKeys = ownSymbols.length ? ownSymbols : NO_SYMBOL_KEYS;
1102
+ const allKeys = symbolKeys.length ? [...keys, ...symbolKeys] : keys;
1103
+ for (const k of allKeys) if (!def.shape?.[k]?._zod?.traits?.has("$ZodType")) throw new Error(`Invalid element at key "${String(k)}": expected a Zod schema`);
1104
+ const okeys = optionalKeys(def.shape);
1105
+ return {
1106
+ ...def,
1107
+ allKeys,
1108
+ symbolKeys,
1109
+ keySet: new Set(keys),
1110
+ numKeys: keys.length,
1111
+ optionalKeys: new Set(okeys)
1112
+ };
1113
+ }
1114
+ function handleCatchall(proms, input, payload, ctx, def, inst, abortEarly) {
1115
+ const unrecognized = [];
1116
+ const keySet = def.keySet;
1117
+ const _catchall = def.catchall._zod;
1118
+ const t = _catchall.def.type;
1119
+ const optin = _catchall.optin;
1120
+ const optout = _catchall.optout;
1121
+ let seen = 0;
1122
+ for (const key in input) {
1123
+ if (abortEarly && payload.issues.length !== seen) {
1124
+ if (aborted(payload, seen)) break;
1125
+ seen = payload.issues.length;
1126
+ }
1127
+ if (keySet.has(key)) continue;
1128
+ if (key === "__proto__") {
1129
+ if (t === "never") unrecognized.push(key);
1130
+ continue;
1131
+ }
1132
+ if (t === "never") {
1133
+ unrecognized.push(key);
1134
+ continue;
1135
+ }
1136
+ const r = _catchall.run({
1137
+ value: input[key],
1138
+ issues: []
1139
+ }, ctx);
1140
+ if (r instanceof Promise) proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, optin, optout)));
1141
+ else handlePropertyResult(r, payload, key, input, optin, optout);
1142
+ }
1143
+ if (unrecognized.length) payload.issues.push({
1144
+ code: "unrecognized_keys",
1145
+ keys: unrecognized,
1146
+ input,
1147
+ inst,
1148
+ continue: true
1149
+ });
1150
+ if (!proms.length) return payload;
1151
+ return Promise.all(proms).then(() => {
1152
+ return payload;
1153
+ });
1154
+ }
1155
+ const $ZodObject = /*@__PURE__*/ $constructor("$ZodObject", (inst, def) => {
1156
+ $ZodType.init(inst, def);
1157
+ const desc = Object.getOwnPropertyDescriptor(def, "shape");
1158
+ const sh = desc?.get ? desc.get.raw : def.shape ?? {};
1159
+ if (sh) {
1160
+ const get = () => {
1161
+ const newSh = { ...sh };
1162
+ Object.defineProperty(def, "shape", { value: newSh });
1163
+ get.raw = newSh;
1164
+ return newSh;
1165
+ };
1166
+ get.raw = sh;
1167
+ Object.defineProperty(def, "shape", { get });
1168
+ }
1169
+ const _normalized = cached(() => normalizeDef(def));
1170
+ defineLazyInternal(inst, "propValues", (zod) => {
1171
+ const shape = zod.def.shape;
1172
+ const propValues = {};
1173
+ for (const key in shape) {
1174
+ const field = shape[key]._zod;
1175
+ if (field.values) {
1176
+ if (!Object.prototype.hasOwnProperty.call(propValues, key)) assignProp(propValues, key, /* @__PURE__ */ new Set());
1177
+ for (const v of field.values) propValues[key].add(v);
1178
+ if (field.optin !== void 0) propValues[key].add(void 0);
1179
+ }
1180
+ }
1181
+ return propValues;
1182
+ });
1183
+ const isObject$1 = isObject;
1184
+ const catchall = def.catchall;
1185
+ let value;
1186
+ const memo = globalConfig.memoizer;
1187
+ memo?.attach(inst);
1188
+ inst._zod.parse = (payload, ctx) => {
1189
+ value ?? (value = _normalized.value);
1190
+ const input = payload.value;
1191
+ if (!isObject$1(input)) {
1192
+ payload.issues.push({
1193
+ expected: "object",
1194
+ code: "invalid_type",
1195
+ input,
1196
+ inst
1197
+ });
1198
+ return payload;
1199
+ }
1200
+ payload.value = memo ? memo.alloc(inst, payload, {}, ctx) : {};
1201
+ const proms = [];
1202
+ const shape = value.shape;
1203
+ const abortEarly = ctx?.abortEarly;
1204
+ let seen = payload.issues.length;
1205
+ for (const key of value.allKeys) {
1206
+ if (abortEarly && payload.issues.length !== seen) {
1207
+ if (aborted(payload, seen)) break;
1208
+ seen = payload.issues.length;
1209
+ }
1210
+ if (key === "__proto__") continue;
1211
+ const el = shape[key];
1212
+ const optin = el._zod.optin;
1213
+ const optout = el._zod.optout;
1214
+ const r = el._zod.run({
1215
+ value: input[key],
1216
+ issues: []
1217
+ }, ctx);
1218
+ if (r instanceof Promise) proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, optin, optout)));
1219
+ else handlePropertyResult(r, payload, key, input, optin, optout);
1220
+ }
1221
+ if (!catchall) return proms.length ? Promise.all(proms).then(() => payload) : payload;
1222
+ return handleCatchall(proms, input, payload, ctx, _normalized.value, inst, abortEarly === true);
1223
+ };
1224
+ });
1225
+ const $ZodRecord = /*@__PURE__*/ $constructor("$ZodRecord", (inst, def) => {
1226
+ $ZodType.init(inst, def);
1227
+ const memo = globalConfig.memoizer;
1228
+ memo?.attach(inst);
1229
+ inst._zod.parse = (payload, ctx) => {
1230
+ const input = payload.value;
1231
+ if (!isPlainObject(input)) {
1232
+ payload.issues.push({
1233
+ expected: "record",
1234
+ code: "invalid_type",
1235
+ input,
1236
+ inst
1237
+ });
1238
+ return payload;
1239
+ }
1240
+ const proms = [];
1241
+ const values = def.keyType._zod.values;
1242
+ if (values && !def.partial) {
1243
+ payload.value = memo ? memo.alloc(inst, payload, {}, ctx) : {};
1244
+ const recordKeys = /* @__PURE__ */ new Set();
1245
+ for (const key of values) if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
1246
+ recordKeys.add(typeof key === "number" ? key.toString() : key);
1247
+ if (key === "__proto__") continue;
1248
+ const keyResult = def.keyType._zod.run({
1249
+ value: key,
1250
+ issues: []
1251
+ }, ctx);
1252
+ if (keyResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently");
1253
+ if (keyResult.issues.length) {
1254
+ payload.issues.push({
1255
+ code: "invalid_key",
1256
+ origin: "record",
1257
+ issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
1258
+ input: key,
1259
+ path: [key],
1260
+ inst
1261
+ });
1262
+ continue;
1263
+ }
1264
+ const outKey = keyResult.value;
1265
+ if (outKey === "__proto__") continue;
1266
+ const result = def.valueType._zod.run({
1267
+ value: input[key],
1268
+ issues: []
1269
+ }, ctx);
1270
+ if (result instanceof Promise) proms.push(result.then((result) => {
1271
+ if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));
1272
+ payload.value[outKey] = result.value;
1273
+ }));
1274
+ else {
1275
+ if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));
1276
+ payload.value[outKey] = result.value;
1277
+ }
1278
+ }
1279
+ let unrecognized;
1280
+ for (const key in input) if (!recordKeys.has(key)) {
1281
+ if (def.mode === "loose") {
1282
+ if (key === "__proto__") continue;
1283
+ payload.value[key] = input[key];
1284
+ } else {
1285
+ unrecognized = unrecognized ?? [];
1286
+ unrecognized.push(key);
1287
+ }
1288
+ }
1289
+ if (unrecognized && unrecognized.length > 0) payload.issues.push({
1290
+ code: "unrecognized_keys",
1291
+ input,
1292
+ inst,
1293
+ keys: unrecognized,
1294
+ continue: true
1295
+ });
1296
+ } else {
1297
+ payload.value = memo ? memo.alloc(inst, payload, {}, ctx) : {};
1298
+ let unrecognized;
1299
+ for (const key of Reflect.ownKeys(input)) {
1300
+ if (key === "__proto__") continue;
1301
+ if (!Object.prototype.propertyIsEnumerable.call(input, key)) continue;
1302
+ let keyResult = def.keyType._zod.run({
1303
+ value: key,
1304
+ issues: []
1305
+ }, ctx);
1306
+ if (keyResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently");
1307
+ if (typeof key === "string" && number$1.test(key) && keyResult.issues.length) {
1308
+ const retryResult = def.keyType._zod.run({
1309
+ value: Number(key),
1310
+ issues: []
1311
+ }, ctx);
1312
+ if (retryResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently");
1313
+ if (retryResult.issues.length === 0) keyResult = retryResult;
1314
+ }
1315
+ if (keyResult.issues.length) {
1316
+ if (def.mode === "loose") payload.value[key] = input[key];
1317
+ else if (values) {
1318
+ unrecognized = unrecognized ?? [];
1319
+ unrecognized.push(key);
1320
+ } else payload.issues.push({
1321
+ code: "invalid_key",
1322
+ origin: "record",
1323
+ issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
1324
+ input: key,
1325
+ path: [key],
1326
+ inst
1327
+ });
1328
+ continue;
1329
+ }
1330
+ const outKey = keyResult.value;
1331
+ if (outKey === "__proto__") continue;
1332
+ const result = def.valueType._zod.run({
1333
+ value: input[key],
1334
+ issues: []
1335
+ }, ctx);
1336
+ if (result instanceof Promise) proms.push(result.then((result) => {
1337
+ if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));
1338
+ payload.value[outKey] = result.value;
1339
+ }));
1340
+ else {
1341
+ if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));
1342
+ payload.value[outKey] = result.value;
1343
+ }
1344
+ }
1345
+ if (unrecognized && unrecognized.length > 0) payload.issues.push({
1346
+ code: "unrecognized_keys",
1347
+ input,
1348
+ inst,
1349
+ keys: unrecognized,
1350
+ continue: true
1351
+ });
1352
+ }
1353
+ if (proms.length) return Promise.all(proms).then(() => payload);
1354
+ return payload;
1355
+ };
1356
+ });
1357
+ function handleOptionalResult(payload, result) {
1358
+ payload.value = result.issues.length ? void 0 : result.value;
1359
+ return payload;
1360
+ }
1361
+ const $ZodOptional = /*@__PURE__*/ $constructor("$ZodOptional", (inst, def) => {
1362
+ $ZodType.init(inst, def);
1363
+ defineLazyInternal(inst, "optin", (zod) => zod.def.innerType._zod.optin === "defaulted" ? "defaulted" : "optional");
1364
+ inst._zod.optout = "optional";
1365
+ defineLazyInternal(inst, "values", (zod) => {
1366
+ const values = zod.def.innerType._zod.values;
1367
+ return values ? /* @__PURE__ */ new Set([...values, void 0]) : void 0;
1368
+ });
1369
+ defineLazyInternal(inst, "pattern", (zod) => {
1370
+ const pattern = zod.def.innerType._zod.pattern;
1371
+ return pattern ? new RegExp(`^(${cleanRegex(pattern.source)})?$`) : void 0;
1372
+ });
1373
+ inst._zod.parse = (payload, ctx) => {
1374
+ if (payload.value === void 0) {
1375
+ if (def.innerType._zod.optin !== "defaulted") return payload;
1376
+ const result = def.innerType._zod.run({
1377
+ value: payload.value,
1378
+ issues: []
1379
+ }, ctx);
1380
+ if (result instanceof Promise) return result.then((result) => handleOptionalResult(payload, result));
1381
+ return handleOptionalResult(payload, result);
1382
+ }
1383
+ return def.innerType._zod.run(payload, ctx);
1384
+ };
1385
+ });
1386
+ // node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/registries.js
1387
+ var _a;
1388
+ var $ZodRegistry = class {
1389
+ constructor() {
1390
+ this._map = /* @__PURE__ */ new WeakMap();
1391
+ this._idmap = /* @__PURE__ */ new Map();
1392
+ }
1393
+ add(schema, ..._meta) {
1394
+ const meta = _meta[0];
1395
+ this._map.set(schema, meta);
1396
+ if (meta && typeof meta === "object" && "id" in meta) this._idmap.set(meta.id, schema);
1397
+ return this;
1398
+ }
1399
+ clear() {
1400
+ this._map = /* @__PURE__ */ new WeakMap();
1401
+ this._idmap = /* @__PURE__ */ new Map();
1402
+ return this;
1403
+ }
1404
+ remove(schema) {
1405
+ const meta = this._map.get(schema);
1406
+ if (meta && typeof meta === "object" && "id" in meta) this._idmap.delete(meta.id);
1407
+ this._map.delete(schema);
1408
+ return this;
1409
+ }
1410
+ get(schema) {
1411
+ const p = schema._zod.parent;
1412
+ if (p) {
1413
+ const pm = { ...this.get(p) ?? {} };
1414
+ delete pm.id;
1415
+ const f = {
1416
+ ...pm,
1417
+ ...this._map.get(schema)
1418
+ };
1419
+ return Object.keys(f).length ? f : void 0;
1420
+ }
1421
+ return this._map.get(schema);
1422
+ }
1423
+ has(schema) {
1424
+ return this._map.has(schema);
1425
+ }
1426
+ };
1427
+ function registry() {
1428
+ return new $ZodRegistry();
1429
+ }
1430
+ (_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());
1431
+ const globalRegistry = globalThis.__zod_globalRegistry;
1432
+ // node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/api.js
1433
+ function snapshotChecks(def) {
1434
+ if (def.checks) def.checks = [...def.checks];
1435
+ return def;
1436
+ }
1437
+ // @__NO_SIDE_EFFECTS__
1438
+ function _string(Class, params) {
1439
+ return new Class(snapshotChecks({
1440
+ type: "string",
1441
+ ...normalizeParams(params)
1442
+ }));
1443
+ }
1444
+ // @__NO_SIDE_EFFECTS__
1445
+ function _number(Class, params) {
1446
+ return new Class(snapshotChecks({
1447
+ type: "number",
1448
+ checks: [],
1449
+ ...normalizeParams(params)
1450
+ }));
1451
+ }
1452
+ // @__NO_SIDE_EFFECTS__
1453
+ function _int(Class, params) {
1454
+ return new Class({
1455
+ type: "number",
1456
+ check: "number_format",
1457
+ abort: false,
1458
+ format: "safeint",
1459
+ ...normalizeParams(params)
1460
+ });
1461
+ }
1462
+ // @__NO_SIDE_EFFECTS__
1463
+ function _boolean(Class, params) {
1464
+ return new Class({
1465
+ type: "boolean",
1466
+ ...normalizeParams(params)
1467
+ });
1468
+ }
1469
+ // @__NO_SIDE_EFFECTS__
1470
+ function _never(Class, params) {
1471
+ return new Class({
1472
+ type: "never",
1473
+ ...normalizeParams(params)
1474
+ });
1475
+ }
1476
+ // @__NO_SIDE_EFFECTS__
1477
+ function _lte(value, params) {
1478
+ return new $ZodCheckLessThan({
1479
+ check: "less_than",
1480
+ ...normalizeParams(params),
1481
+ value,
1482
+ inclusive: true
1483
+ });
1484
+ }
1485
+ // @__NO_SIDE_EFFECTS__
1486
+ function _gt(value, params) {
1487
+ return new $ZodCheckGreaterThan({
1488
+ check: "greater_than",
1489
+ ...normalizeParams(params),
1490
+ value,
1491
+ inclusive: false
1492
+ });
1493
+ }
1494
+ // @__NO_SIDE_EFFECTS__
1495
+ function _gte(value, params) {
1496
+ return new $ZodCheckGreaterThan({
1497
+ check: "greater_than",
1498
+ ...normalizeParams(params),
1499
+ value,
1500
+ inclusive: true
1501
+ });
1502
+ }
1503
+ // @__NO_SIDE_EFFECTS__
1504
+ function _positive(params) {
1505
+ return /* @__PURE__ */ _gt(0, params);
1506
+ }
1507
+ // @__NO_SIDE_EFFECTS__
1508
+ function _minLength(minimum, params) {
1509
+ return new $ZodCheckMinLength({
1510
+ check: "min_length",
1511
+ ...normalizeParams(params),
1512
+ minimum
1513
+ });
1514
+ }
1515
+ // @__NO_SIDE_EFFECTS__
1516
+ function _regex(pattern, params) {
1517
+ return new $ZodCheckRegex({
1518
+ check: "string_format",
1519
+ format: "regex",
1520
+ ...normalizeParams(params),
1521
+ pattern
1522
+ });
1523
+ }
1524
+ // @__NO_SIDE_EFFECTS__
1525
+ function describe$2(description) {
1526
+ const ch = new $ZodCheck({ check: "describe" });
1527
+ ch._zod.onattach = [(inst) => {
1528
+ const existing = globalRegistry.get(inst) ?? {};
1529
+ globalRegistry.add(inst, {
1530
+ ...existing,
1531
+ description
1532
+ });
1533
+ }];
1534
+ ch._zod.check = () => {};
1535
+ return ch;
1536
+ }
1537
+ // node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/mini/schemas.js
1538
+ const ZodMiniType = /*@__PURE__*/ $constructor("ZodMiniType", (inst, def) => {
1539
+ if (!inst._zod) throw new Error("Uninitialized schema in ZodMiniType.");
1540
+ $ZodType.init(inst, def);
1541
+ inst.def = def;
1542
+ inst.type = def.type;
1543
+ }, {
1544
+ get with() {
1545
+ return this.check;
1546
+ },
1547
+ set with(value) {
1548
+ own(this, "with", value);
1549
+ },
1550
+ parse(data, params) {
1551
+ return parse(this, data, params, { callee: this.parse });
1552
+ },
1553
+ parseAsync(data, params) {
1554
+ return parseAsync(this, data, params, { callee: this.parseAsync });
1555
+ },
1556
+ safeParse(data, params) {
1557
+ return safeParse(this, data, params);
1558
+ },
1559
+ safeParseAsync(data, params) {
1560
+ return safeParseAsync(this, data, params);
1561
+ },
1562
+ check(...checks) {
1563
+ const def = this.def;
1564
+ return this.clone({
1565
+ ...def,
1566
+ checks: [...def.checks ?? [], ...checks.map((ch) => typeof ch === "function" ? { _zod: {
1567
+ check: ch,
1568
+ def: { check: "custom" },
1569
+ onattach: []
1570
+ } } : ch)]
1571
+ }, { parent: true });
1572
+ },
1573
+ clone(_def, params) {
1574
+ return clone(this, _def, params);
1575
+ },
1576
+ brand() {
1577
+ return this;
1578
+ },
1579
+ register(reg, meta) {
1580
+ reg.add(this, meta);
1581
+ return this;
1582
+ },
1583
+ apply(fn, ...args) {
1584
+ return args.length === 0 ? fn(this) : fn(this, ...args);
1585
+ }
1586
+ });
1587
+ const ZodMiniString = /*@__PURE__*/ $constructor("ZodMiniString", (inst, def) => {
1588
+ $ZodString.init(inst, def);
1589
+ ZodMiniType.init(inst, def);
1590
+ });
1591
+ // @__NO_SIDE_EFFECTS__
1592
+ function string(params) {
1593
+ return /* @__PURE__ */ _string(ZodMiniString, params);
1594
+ }
1595
+ const ZodMiniNumber = /*@__PURE__*/ $constructor("ZodMiniNumber", (inst, def) => {
1596
+ $ZodNumber.init(inst, def);
1597
+ ZodMiniType.init(inst, def);
1598
+ });
1599
+ // @__NO_SIDE_EFFECTS__
1600
+ function number(params) {
1601
+ return /* @__PURE__ */ _number(ZodMiniNumber, params);
1602
+ }
1603
+ const ZodMiniNumberFormat = /*@__PURE__*/ $constructor("ZodMiniNumberFormat", (inst, def) => {
1604
+ $ZodNumberFormat.init(inst, def);
1605
+ ZodMiniNumber.init(inst, def);
1606
+ });
1607
+ function int(params) {
1608
+ return /* @__PURE__ */ _int(ZodMiniNumberFormat, params);
1609
+ }
1610
+ const ZodMiniBoolean = /*@__PURE__*/ $constructor("ZodMiniBoolean", (inst, def) => {
1611
+ $ZodBoolean.init(inst, def);
1612
+ ZodMiniType.init(inst, def);
1613
+ });
1614
+ // @__NO_SIDE_EFFECTS__
1615
+ function boolean(params) {
1616
+ return /* @__PURE__ */ _boolean(ZodMiniBoolean, params);
1617
+ }
1618
+ const ZodMiniNever = /*@__PURE__*/ $constructor("ZodMiniNever", (inst, def) => {
1619
+ $ZodNever.init(inst, def);
1620
+ ZodMiniType.init(inst, def);
1621
+ });
1622
+ // @__NO_SIDE_EFFECTS__
1623
+ function never(params) {
1624
+ return /* @__PURE__ */ _never(ZodMiniNever, params);
1625
+ }
1626
+ const ZodMiniObject = /*@__PURE__*/ $constructor("ZodMiniObject", (inst, def) => {
1627
+ $ZodObject.init(inst, def);
1628
+ ZodMiniType.init(inst, def);
1629
+ installLazyProp(inst, "shape", (self) => self._zod.def.shape, false);
1630
+ });
1631
+ // @__NO_SIDE_EFFECTS__
1632
+ function strictObject(shape, params) {
1633
+ return new ZodMiniObject({
1634
+ type: "object",
1635
+ shape,
1636
+ catchall: /* @__PURE__ */ never(),
1637
+ ...normalizeParams(params)
1638
+ });
1639
+ }
1640
+ const ZodMiniRecord = /*@__PURE__*/ $constructor("ZodMiniRecord", (inst, def) => {
1641
+ $ZodRecord.init(inst, def);
1642
+ ZodMiniType.init(inst, def);
1643
+ });
1644
+ // @__NO_SIDE_EFFECTS__
1645
+ function record(keyType, valueType, params) {
1646
+ if (!valueType || !valueType._zod) return new ZodMiniRecord({
1647
+ type: "record",
1648
+ keyType: /* @__PURE__ */ string(),
1649
+ valueType: keyType,
1650
+ ...normalizeParams(valueType)
1651
+ });
1652
+ return new ZodMiniRecord({
1653
+ type: "record",
1654
+ keyType,
1655
+ valueType,
1656
+ ...normalizeParams(params)
1657
+ });
1658
+ }
1659
+ const ZodMiniOptional = /*@__PURE__*/ $constructor("ZodMiniOptional", (inst, def) => {
1660
+ $ZodOptional.init(inst, def);
1661
+ ZodMiniType.init(inst, def);
1662
+ });
1663
+ // @__NO_SIDE_EFFECTS__
1664
+ function optional(innerType) {
1665
+ return new ZodMiniOptional({
1666
+ type: "optional",
1667
+ innerType
1668
+ });
1669
+ }
1670
+ const describe$1 = describe$2;
1671
+ // src/config.ts
1672
+ config(en_default());
1673
+ const CONFIG_FILE = "portcall.json";
1674
+ const DEFAULT_READY_TIMEOUT = 60;
1675
+ const nameSchema = (/* @__PURE__ */ string()).check(/* @__PURE__ */ _regex(/^\w[\w.-]*$/, "use letters, digits, dot, dash or underscore"));
1676
+ const serviceSchema = /* @__PURE__ */ strictObject({
1677
+ command: (/* @__PURE__ */ optional((/* @__PURE__ */ string()).check(/* @__PURE__ */ _minLength(1)))).check(describe$1("Shell command that runs the service. Omit it for an external service (a database in Docker...): portcall then only checks its port.")),
1678
+ port: int().check(/* @__PURE__ */ _gte(1), /* @__PURE__ */ _lte(65535), describe$1("Port the service listens on. Also passed to the command as PORT.")),
1679
+ cwd: (/* @__PURE__ */ optional(/* @__PURE__ */ string())).check(describe$1("Directory to run the command in, relative to portcall.json. Default: the project root.")),
1680
+ env: (/* @__PURE__ */ optional(/* @__PURE__ */ record(/* @__PURE__ */ string(), /* @__PURE__ */ string()))).check(describe$1("Extra environment. {port} is this service's port, {ports.<service>} another service's.")),
1681
+ readyTimeout: (/* @__PURE__ */ optional((/* @__PURE__ */ number()).check(/* @__PURE__ */ _positive()))).check(describe$1("Seconds `portcall up` waits for the port. Default: 60."))
1682
+ });
1683
+ const configSchema = /* @__PURE__ */ strictObject({
1684
+ $schema: /* @__PURE__ */ optional(/* @__PURE__ */ string()),
1685
+ name: nameSchema.check(describe$1("Project name, used by `portcall -p <name>` and in the logs folder.")),
1686
+ services: (/* @__PURE__ */ record(nameSchema, serviceSchema)).check(describe$1("The project's services, by name.")),
1687
+ worktrees: (/* @__PURE__ */ optional(/* @__PURE__ */ boolean())).check(describe$1("Let each git worktree of the project run its services at the same time, on ports offset from the declared ones (external services keep theirs). Commands must take their port from PORT and {ports.<service>}. Default: false, all checkouts share the declared ports."))
1688
+ });
1689
+ var ConfigError = class extends Error {};
1690
+ function findConfig(from) {
1691
+ let dir = resolve(from);
1692
+ for (;;) {
1693
+ const file = join(dir, CONFIG_FILE);
1694
+ if (existsSync(file)) return file;
1695
+ const parent = dirname(dir);
1696
+ if (parent === dir) return void 0;
1697
+ dir = parent;
1698
+ }
1699
+ }
1700
+ /** The worktree name when `dir` is inside a linked git worktree (not the main checkout). */
1701
+ function linkedWorktree(dir) {
1702
+ for (let current = resolve(dir);;) {
1703
+ const dotGit = join(current, ".git");
1704
+ if (existsSync(dotGit)) {
1705
+ let content;
1706
+ try {
1707
+ content = readFileSync(dotGit, "utf8");
1708
+ } catch {
1709
+ return;
1710
+ }
1711
+ const gitDir = /^gitdir: (.+)$/m.exec(content)?.[1]?.trim();
1712
+ if (!gitDir) return void 0;
1713
+ const resolved = resolve(current, gitDir);
1714
+ return existsSync(join(resolved, "commondir")) ? basename(resolved) : void 0;
1715
+ }
1716
+ const parent = dirname(current);
1717
+ if (parent === current) return void 0;
1718
+ current = parent;
1719
+ }
1720
+ }
1721
+ function loadProject(file, offsets = {}) {
1722
+ let raw;
1723
+ try {
1724
+ raw = JSON.parse(readFileSync(file, "utf8"));
1725
+ } catch (error) {
1726
+ throw new ConfigError(`${file}: ${error.message}`);
1727
+ }
1728
+ return parseProject(raw, join(realpathSync(dirname(file)), basename(file)), offsets);
1729
+ }
1730
+ function parseProject(raw, file, offsets = {}) {
1731
+ const parsed = configSchema.safeParse(raw);
1732
+ if (!parsed.success) throw new ConfigError(`${file}:\n${prettifyError(parsed.error)}`);
1733
+ const config = parsed.data;
1734
+ const root = dirname(file);
1735
+ const entries = Object.entries(config.services);
1736
+ if (entries.length === 0) throw new ConfigError(`${file}: "services" is empty`);
1737
+ const ports = /* @__PURE__ */ new Map();
1738
+ for (const [name, service] of entries) {
1739
+ const other = ports.get(service.port);
1740
+ if (other) throw new ConfigError(`${file}: services "${other}" and "${name}" both use port ${service.port}`);
1741
+ ports.set(service.port, name);
1742
+ }
1743
+ const worktree = config.worktrees ? linkedWorktree(root) : void 0;
1744
+ const portOffset = worktree ? offsets[root] : void 0;
1745
+ const effective = new Map(entries.map(([name, service]) => [name, service.port + (service.command ? portOffset ?? 0 : 0)]));
1746
+ const services = entries.map(([name, service]) => ({
1747
+ name,
1748
+ port: effective.get(name),
1749
+ basePort: service.port,
1750
+ command: service.command,
1751
+ cwd: resolve(root, service.cwd ?? "."),
1752
+ env: expandEnv(service.env ?? {}, effective.get(name), effective, file, name),
1753
+ readyTimeout: service.readyTimeout ?? DEFAULT_READY_TIMEOUT
1754
+ }));
1755
+ const instance = worktree ? `${config.name}@${worktree}` : config.name;
1756
+ return {
1757
+ name: config.name,
1758
+ instance,
1759
+ root,
1760
+ file,
1761
+ services,
1762
+ ...worktree ? {
1763
+ worktree,
1764
+ portOffset
1765
+ } : {}
1766
+ };
1767
+ }
1768
+ /** Fills `{port}` (this service) and `{ports.<service>}` (any service of the project). */
1769
+ function expandEnv(env, port, byName, file, service) {
1770
+ const out = {};
1771
+ for (const [key, value] of Object.entries(env)) out[key] = value.replace(/\{(port|ports\.([\w.-]+))\}/g, (_, whole, other) => {
1772
+ if (whole === "port") return String(port);
1773
+ const found = byName.get(other);
1774
+ if (found === void 0) throw new ConfigError(`${file}: services.${service}.env.${key} refers to unknown service "${other}"`);
1775
+ return String(found);
1776
+ });
1777
+ return out;
1778
+ }
1779
+ // src/logs.ts
1780
+ const LINE = /^(\d{4}-\d\d-\d\dT[\d:.]+Z) \[(\w+)\] (.*)$/;
1781
+ const ERROR = /(?:error|exception)\b|\b(?:fatal|panic|unhandled|traceback|failed)\b|ERR!/i;
1782
+ const CONTINUATION = /^\s|^(?:caused by|\} )/i;
1783
+ const MAX_CONTINUATION = 40;
1784
+ const MAX_READ = 8388608;
1785
+ function parseLine(raw, service) {
1786
+ const match = LINE.exec(raw);
1787
+ if (!match) return {
1788
+ tag: "out",
1789
+ text: raw,
1790
+ service
1791
+ };
1792
+ return {
1793
+ time: new Date(match[1]),
1794
+ tag: match[2],
1795
+ text: match[3],
1796
+ service
1797
+ };
1798
+ }
1799
+ /** `30s`, `5m`, `2h`, `1d`, or anything `Date.parse` reads. Returns epoch ms. */
1800
+ function parseSince(value, now = Date.now()) {
1801
+ const match = /^(\d+)([smhd])$/.exec(value.trim());
1802
+ if (match) {
1803
+ const unit = {
1804
+ s: 1e3,
1805
+ m: 6e4,
1806
+ h: 36e5,
1807
+ d: 864e5
1808
+ }[match[2]];
1809
+ return now - Number(match[1]) * unit;
1810
+ }
1811
+ const time = Date.parse(value);
1812
+ if (Number.isNaN(time)) throw new Error(`--since: can't read "${value}" (try 30s, 5m, 2h or a date)`);
1813
+ return time;
1814
+ }
1815
+ function isError(line) {
1816
+ if (line.tag === "portcall") return /^exited with|^failed to start/.test(line.text);
1817
+ return ERROR.test(line.text);
1818
+ }
1819
+ function filterLines(lines, filter) {
1820
+ let out = lines;
1821
+ if (filter.since !== void 0) out = out.filter((line) => !line.time || line.time.getTime() >= filter.since);
1822
+ if (filter.errors) {
1823
+ const kept = [];
1824
+ for (let i = 0; i < out.length; i++) {
1825
+ if (!isError(out[i])) continue;
1826
+ kept.push(out[i]);
1827
+ let j = i + 1;
1828
+ while (j < out.length && j - i <= MAX_CONTINUATION && CONTINUATION.test(out[j].text) && !isError(out[j])) kept.push(out[j++]);
1829
+ i = j - 1;
1830
+ }
1831
+ out = kept;
1832
+ }
1833
+ if (filter.grep) out = out.filter((line) => filter.grep.test(line.text));
1834
+ if (filter.limit !== void 0) out = out.slice(-filter.limit);
1835
+ return out;
1836
+ }
1837
+ function readTail(file, maxBytes) {
1838
+ const size = statSync(file).size;
1839
+ const start = Math.max(0, size - maxBytes);
1840
+ const buffer = Buffer.alloc(size - start);
1841
+ const fd = openSync(file, "r");
1842
+ try {
1843
+ readSync(fd, buffer, 0, buffer.length, start);
1844
+ } finally {
1845
+ closeSync(fd);
1846
+ }
1847
+ const text = buffer.toString("utf8");
1848
+ return start > 0 ? text.slice(text.indexOf("\n") + 1) : text;
1849
+ }
1850
+ function readLog(file, service) {
1851
+ if (!existsSync(file)) return [];
1852
+ return readTail(file, MAX_READ).split("\n").filter(Boolean).map((raw) => parseLine(raw, service));
1853
+ }
1854
+ /** Several services' logs as one, in time order. */
1855
+ function mergeLogs(logs) {
1856
+ return logs.flat().sort((a, b) => (a.time?.getTime() ?? 0) - (b.time?.getTime() ?? 0));
1857
+ }
1858
+ function pad(n, width = 2) {
1859
+ return String(n).padStart(width, "0");
1860
+ }
1861
+ function formatLine(line, serviceWidth = 0) {
1862
+ return `${line.time ? `${pad(line.time.getHours())}:${pad(line.time.getMinutes())}:${pad(line.time.getSeconds())}.${pad(line.time.getMilliseconds(), 3)} ` : ""}${serviceWidth ? `${line.service.padEnd(serviceWidth)} ` : ""}${line.tag === "out" ? "" : `[${line.tag}] `}${line.text}`;
1863
+ }
1864
+ /** `tail -f` over several logs; a log that shrinks was rotated by a restart and is read again from its start. */
1865
+ async function followLogs(files, onLine, filter) {
1866
+ const offsets = files.map(({ file }) => existsSync(file) ? statSync(file).size : 0);
1867
+ const partial = files.map(() => "");
1868
+ for (;;) {
1869
+ for (const [i, { file, service }] of files.entries()) {
1870
+ if (!existsSync(file)) continue;
1871
+ const size = statSync(file).size;
1872
+ if (size < offsets[i]) {
1873
+ offsets[i] = 0;
1874
+ partial[i] = "";
1875
+ }
1876
+ if (size === offsets[i]) continue;
1877
+ const buffer = Buffer.alloc(size - offsets[i]);
1878
+ const fd = openSync(file, "r");
1879
+ try {
1880
+ readSync(fd, buffer, 0, buffer.length, offsets[i]);
1881
+ } finally {
1882
+ closeSync(fd);
1883
+ }
1884
+ offsets[i] = size;
1885
+ const chunks = (partial[i] + buffer.toString("utf8")).split("\n");
1886
+ partial[i] = chunks.pop();
1887
+ const lines = chunks.filter(Boolean).map((raw) => parseLine(raw, service));
1888
+ for (const line of filterLines(lines, {
1889
+ ...filter,
1890
+ limit: void 0,
1891
+ since: void 0
1892
+ })) onLine(line);
1893
+ }
1894
+ await setTimeout$1(250);
1895
+ }
1896
+ }
1897
+ // src/paths.ts
1898
+ function portcallHome() {
1899
+ return process.env.PORTCALL_HOME || join(homedir(), ".portcall");
1900
+ }
1901
+ function statePath() {
1902
+ return join(portcallHome(), "state.json");
1903
+ }
1904
+ function projectsPath() {
1905
+ return join(portcallHome(), "projects.json");
1906
+ }
1907
+ function logPath(project, service) {
1908
+ return join(portcallHome(), "logs", project, `${service}.log`);
1909
+ }
1910
+ // src/ports.ts
1911
+ const exec = promisify(execFile);
1912
+ function canConnect(host, port, timeoutMs) {
1913
+ return new Promise((resolve) => {
1914
+ const socket = connect({
1915
+ host,
1916
+ port
1917
+ });
1918
+ const done = (ok) => {
1919
+ socket.destroy();
1920
+ resolve(ok);
1921
+ };
1922
+ socket.setTimeout(timeoutMs, () => done(false));
1923
+ socket.once("connect", () => done(true));
1924
+ socket.once("error", () => done(false));
1925
+ });
1926
+ }
1927
+ /** Vite and friends often bind `::1` only, so both loopbacks are tried. */
1928
+ async function isListening(port, timeoutMs = 500) {
1929
+ return (await Promise.all(["127.0.0.1", "::1"].map((host) => canConnect(host, port, timeoutMs)))).some(Boolean);
1930
+ }
1931
+ /** Best effort: needs `lsof`, and only sees processes of the current user. */
1932
+ async function portHolder(port) {
1933
+ let stdout;
1934
+ try {
1935
+ ({stdout} = await exec("lsof", [
1936
+ "-nP",
1937
+ `-iTCP:${port}`,
1938
+ "-sTCP:LISTEN",
1939
+ "-t"
1940
+ ]));
1941
+ } catch {
1942
+ return;
1943
+ }
1944
+ const pid = Number(stdout.trim().split("\n")[0]);
1945
+ if (!pid) return void 0;
1946
+ const [command, cwd] = await Promise.all([processCommand(pid), processCwd(pid)]);
1947
+ return {
1948
+ pid,
1949
+ command,
1950
+ cwd
1951
+ };
1952
+ }
1953
+ async function processCommand(pid) {
1954
+ try {
1955
+ return (await exec("ps", [
1956
+ "-o",
1957
+ "command=",
1958
+ "-p",
1959
+ String(pid)
1960
+ ])).stdout.trim();
1961
+ } catch {
1962
+ return "";
1963
+ }
1964
+ }
1965
+ async function processCwd(pid) {
1966
+ try {
1967
+ if (process.platform === "linux") return await readlink(`/proc/${pid}/cwd`);
1968
+ const { stdout } = await exec("lsof", [
1969
+ "-a",
1970
+ "-p",
1971
+ String(pid),
1972
+ "-d",
1973
+ "cwd",
1974
+ "-Fn"
1975
+ ]);
1976
+ return stdout.split("\n").find((line) => line.startsWith("n"))?.slice(1);
1977
+ } catch {
1978
+ return;
1979
+ }
1980
+ }
1981
+ // src/proc.ts
1982
+ function isAlive(pid) {
1983
+ if (!pid) return false;
1984
+ try {
1985
+ process.kill(pid, 0);
1986
+ return true;
1987
+ } catch (error) {
1988
+ return error.code === "EPERM";
1989
+ }
1990
+ }
1991
+ function signal(pid, sig) {
1992
+ if (!pid) return;
1993
+ try {
1994
+ process.kill(pid, sig);
1995
+ } catch {}
1996
+ }
1997
+ /** Signals the whole process group led by `pid`: `pnpm dev` spawns its own children. */
1998
+ function signalGroup(pid, sig) {
1999
+ if (pid) signal(-pid, sig);
2000
+ }
2001
+ async function waitForDeath(pid, timeoutMs) {
2002
+ const deadline = Date.now() + timeoutMs;
2003
+ while (isAlive(pid)) {
2004
+ if (Date.now() > deadline) return false;
2005
+ await setTimeout$1(50);
2006
+ }
2007
+ return true;
2008
+ }
2009
+ // src/state.ts
2010
+ const LOCK_WAIT_MS = 5e3;
2011
+ const STALE_LOCK_MS = 1e4;
2012
+ function recordKey(project, service) {
2013
+ return `${project}/${service}`;
2014
+ }
2015
+ function readState() {
2016
+ try {
2017
+ const state = JSON.parse(readFileSync(statePath(), "utf8"));
2018
+ if (state && typeof state.services === "object") return state;
2019
+ } catch {}
2020
+ return { services: {} };
2021
+ }
2022
+ async function updateState(fn) {
2023
+ return withLock(() => {
2024
+ const state = readState();
2025
+ const result = fn(state);
2026
+ writeJson(statePath(), state);
2027
+ return result;
2028
+ });
2029
+ }
2030
+ function writeJson(file, value) {
2031
+ mkdirSync(portcallHome(), { recursive: true });
2032
+ const tmp = `${file}.${process.pid}.tmp`;
2033
+ writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`);
2034
+ renameSync(tmp, file);
2035
+ }
2036
+ async function withLock(fn) {
2037
+ mkdirSync(portcallHome(), { recursive: true });
2038
+ const lock = join(portcallHome(), "state.lock");
2039
+ const deadline = Date.now() + LOCK_WAIT_MS;
2040
+ for (;;) try {
2041
+ closeSync(openSync(lock, "wx"));
2042
+ break;
2043
+ } catch (error) {
2044
+ if (error.code !== "EEXIST") throw error;
2045
+ try {
2046
+ if (Date.now() - statSync(lock).mtimeMs > STALE_LOCK_MS) unlinkSync(lock);
2047
+ } catch {}
2048
+ if (Date.now() > deadline) throw new Error(`state is locked (${lock}); delete it if no portcall command is running`);
2049
+ await setTimeout$1(20);
2050
+ }
2051
+ try {
2052
+ return fn();
2053
+ } finally {
2054
+ try {
2055
+ unlinkSync(lock);
2056
+ } catch {}
2057
+ }
2058
+ }
2059
+ function readRoots() {
2060
+ try {
2061
+ const roots = JSON.parse(readFileSync(projectsPath(), "utf8"));
2062
+ if (Array.isArray(roots)) return roots.filter((r) => typeof r === "string");
2063
+ } catch {}
2064
+ return [];
2065
+ }
2066
+ /** Remembers a project so `status --all`, `ports` and `--project` find it from anywhere. */
2067
+ function rememberProject(root) {
2068
+ const roots = readRoots();
2069
+ if (!roots.includes(root)) writeJson(projectsPath(), [...roots, root].sort());
2070
+ }
2071
+ function knownProjects() {
2072
+ const projects = [];
2073
+ const errors = [];
2074
+ const kept = [];
2075
+ const roots = readRoots();
2076
+ const offsets = readState().offsets;
2077
+ for (const root of roots) {
2078
+ const file = join(root, CONFIG_FILE);
2079
+ if (!existsSync(file)) continue;
2080
+ kept.push(root);
2081
+ try {
2082
+ projects.push(loadProject(file, offsets));
2083
+ } catch (error) {
2084
+ errors.push(error.message);
2085
+ }
2086
+ }
2087
+ if (kept.length !== roots.length) writeJson(projectsPath(), kept);
2088
+ return {
2089
+ projects,
2090
+ errors
2091
+ };
2092
+ }
2093
+ // src/status.ts
2094
+ function inside(dir, root) {
2095
+ return !!dir && (dir === root || dir.startsWith(root + sep));
2096
+ }
2097
+ async function serviceStatus(project, service, state) {
2098
+ const base = {
2099
+ project: project.name,
2100
+ service: service.name,
2101
+ port: service.port,
2102
+ ...service.port !== service.basePort ? { basePort: service.basePort } : {},
2103
+ ...project.worktree ? { worktree: project.worktree } : {},
2104
+ url: `http://localhost:${service.port}`,
2105
+ external: !service.command
2106
+ };
2107
+ const record = state.services[recordKey(project.instance, service.name)];
2108
+ if (record) {
2109
+ const alive = isAlive(record.supervisorPid) || isAlive(record.childPid);
2110
+ const managed = {
2111
+ pid: record.childPid ?? record.supervisorPid,
2112
+ startedAt: record.startedAt,
2113
+ log: record.log
2114
+ };
2115
+ if (record.root !== project.root && alive) return {
2116
+ ...base,
2117
+ ...managed,
2118
+ state: "elsewhere",
2119
+ root: record.root
2120
+ };
2121
+ if (record.exitedAt || !alive) return {
2122
+ ...base,
2123
+ ...managed,
2124
+ state: "crashed",
2125
+ exitCode: record.exitCode,
2126
+ exitSignal: record.exitSignal
2127
+ };
2128
+ return {
2129
+ ...base,
2130
+ ...managed,
2131
+ state: await isListening(service.port) ? "running" : "starting"
2132
+ };
2133
+ }
2134
+ const log = service.command ? logPath(project.instance, service.name) : void 0;
2135
+ if (!await isListening(service.port)) return {
2136
+ ...base,
2137
+ log,
2138
+ state: "stopped"
2139
+ };
2140
+ if (base.external) return {
2141
+ ...base,
2142
+ state: "running"
2143
+ };
2144
+ const holder = await portHolder(service.port);
2145
+ return {
2146
+ ...base,
2147
+ log,
2148
+ holder,
2149
+ pid: holder?.pid,
2150
+ state: inside(holder?.cwd, project.root) ? "unmanaged" : "taken"
2151
+ };
2152
+ }
2153
+ async function projectStatus(project, state, only) {
2154
+ return Promise.all((only ?? project.services).map((service) => serviceStatus(project, service, state)));
2155
+ }
2156
+ // src/worktrees.ts
2157
+ const OFFSET_STEP = 10;
2158
+ const MAX_TRIES = 100;
2159
+ /**
2160
+ * Gives a worktree of a `worktrees: true` project its port offset the first time portcall meets it:
2161
+ * the first multiple of 10 that moves its services onto ports no known project declares and nothing listens on.
2162
+ */
2163
+ async function withPortOffset(project) {
2164
+ if (!project.worktree || project.portOffset !== void 0) return project;
2165
+ const claimed = new Set(knownProjects().projects.filter((p) => p.root !== project.root).flatMap((p) => p.services.map((s) => s.port)));
2166
+ for (const service of project.services) claimed.add(service.basePort);
2167
+ const runnable = project.services.filter((s) => s.command);
2168
+ let chosen;
2169
+ for (let i = 1; i <= MAX_TRIES && chosen === void 0; i++) {
2170
+ const offset = i * OFFSET_STEP;
2171
+ const ports = runnable.map((s) => s.basePort + offset);
2172
+ if (ports.some((port) => port > 65535)) break;
2173
+ if (ports.some((port) => claimed.has(port))) continue;
2174
+ if (!(await Promise.all(ports.map((port) => isListening(port)))).some(Boolean)) chosen = offset;
2175
+ }
2176
+ if (chosen === void 0) throw new Error(`no free port offset for ${project.instance}: every candidate port is declared or in use`);
2177
+ await updateState((state) => {
2178
+ const offsets = state.offsets ??= {};
2179
+ for (const root of Object.keys(offsets)) if (!existsSync(join(root, "portcall.json"))) delete offsets[root];
2180
+ offsets[project.root] ??= chosen;
2181
+ });
2182
+ return loadProject(project.file, readState().offsets);
2183
+ }
2184
+ // src/commands.ts
2185
+ const SUPERVISE_COMMAND = "__supervise";
2186
+ const STOP_WAIT_MS = 8e3;
2187
+ var UsageError = class extends Error {};
2188
+ /** `name` is a project name, or `name@worktree` for a worktree with its own ports. */
2189
+ async function resolveProject(cwd, name) {
2190
+ if (name) {
2191
+ const { projects } = knownProjects();
2192
+ const exact = projects.filter((project) => project.instance === name);
2193
+ const matches = exact.length ? exact : projects.filter((project) => project.name === name);
2194
+ if (matches.length === 0) throw new UsageError(`unknown project "${name}": run any portcall command once inside it so portcall knows where it is`);
2195
+ const here = matches.find((project) => cwd === project.root || cwd.startsWith(`${project.root}/`));
2196
+ if (here || matches.length === 1) return withPortOffset(here ?? matches[0]);
2197
+ throw new UsageError(`"${name}" exists in several checkouts, run portcall from the one you want${matches.some((p) => p.worktree) ? " or name it as project@worktree" : ""}:\n${matches.map((p) => ` ${p.instance} ${p.root}`).join("\n")}`);
2198
+ }
2199
+ const file = findConfig(cwd);
2200
+ if (!file) throw new ConfigError(`no portcall.json in ${cwd} or above (see \`portcall help\`)`);
2201
+ const project = await withPortOffset(loadProject(file, readState().offsets));
2202
+ rememberProject(project.root);
2203
+ return project;
2204
+ }
2205
+ function selectServices(project, names, runnableOnly = false) {
2206
+ if (names.length === 0) return runnableOnly ? project.services.filter((s) => s.command) : project.services;
2207
+ return names.map((name) => {
2208
+ const service = project.services.find((s) => s.name === name);
2209
+ if (!service) throw new UsageError(`${project.name} has no service "${name}" (services: ${project.services.map((s) => s.name).join(", ")})`);
2210
+ if (runnableOnly && !service.command) throw new UsageError(`${project.name}/${name} is external (no command): portcall only checks its port`);
2211
+ return service;
2212
+ });
2213
+ }
2214
+ function worktreeFields(project) {
2215
+ return project.worktree ? {
2216
+ worktree: project.worktree,
2217
+ portOffset: project.portOffset
2218
+ } : {};
2219
+ }
2220
+ function table(rows) {
2221
+ const widths = rows[0].map((_, i) => Math.max(...rows.map((row) => row[i].length)));
2222
+ return rows.map((row) => row.map((cell, i) => cell.padEnd(widths[i])).join(" ").trimEnd()).join("\n");
2223
+ }
2224
+ function duration(ms) {
2225
+ const s = Math.floor(ms / 1e3);
2226
+ if (s < 60) return `${s}s`;
2227
+ if (s < 3600) return `${Math.floor(s / 60)}m`;
2228
+ if (s < 86400) return `${Math.floor(s / 3600)}h${String(Math.floor(s % 3600 / 60)).padStart(2, "0")}`;
2229
+ return `${Math.floor(s / 86400)}d`;
2230
+ }
2231
+ function short(text, max = 60) {
2232
+ return text.length > max ? `${text.slice(0, max - 1)}…` : text;
2233
+ }
2234
+ function describe(status) {
2235
+ switch (status.state) {
2236
+ case "crashed": return status.exitSignal ? `killed by ${status.exitSignal}` : `exit code ${status.exitCode ?? "?"}`;
2237
+ case "unmanaged": return `started outside portcall: ${short(status.holder?.command ?? "?")}`;
2238
+ case "taken": return `port held by ${status.holder ? `${short(status.holder.command)} (${status.holder.cwd ?? "?"})` : "another process"}`;
2239
+ case "elsewhere": return `run from ${status.root}`;
2240
+ default: return status.external ? "external" : "";
2241
+ }
2242
+ }
2243
+ function statusRows(statuses, withProject) {
2244
+ const head = [
2245
+ ...withProject ? ["PROJECT"] : [],
2246
+ "SERVICE",
2247
+ "STATE",
2248
+ "PORT",
2249
+ "PID",
2250
+ "UPTIME",
2251
+ "INFO"
2252
+ ];
2253
+ const now = Date.now();
2254
+ return [head, ...statuses.map((s) => [
2255
+ ...withProject ? [s.worktree ? `${s.project}@${s.worktree}` : s.project] : [],
2256
+ s.service,
2257
+ s.state,
2258
+ String(s.port),
2259
+ s.pid ? String(s.pid) : "-",
2260
+ s.startedAt && (s.state === "running" || s.state === "starting") ? duration(now - s.startedAt) : "-",
2261
+ describe(s)
2262
+ ])];
2263
+ }
2264
+ async function status(io, cwd, opts) {
2265
+ const state = readState();
2266
+ if (opts.all) {
2267
+ const { projects, errors } = knownProjects();
2268
+ const statuses = (await Promise.all(projects.map((p) => projectStatus(p, state)))).flat();
2269
+ if (io.json) io.out(JSON.stringify({
2270
+ services: statuses,
2271
+ errors
2272
+ }, null, 2));
2273
+ else {
2274
+ io.out(statuses.length ? table(statusRows(statuses, true)) : "no known projects yet: run portcall once inside a project");
2275
+ errors.forEach((e) => io.err(`portcall: ${e}`));
2276
+ }
2277
+ return 0;
2278
+ }
2279
+ const project = await resolveProject(cwd, opts.project);
2280
+ const statuses = await projectStatus(project, state);
2281
+ io.out(io.json ? JSON.stringify({
2282
+ project: project.name,
2283
+ ...worktreeFields(project),
2284
+ root: project.root,
2285
+ services: statuses
2286
+ }, null, 2) : table(statusRows(statuses, false)));
2287
+ return 0;
2288
+ }
2289
+ const UP_FAILURES = [
2290
+ "crashed",
2291
+ "timeout",
2292
+ "blocked"
2293
+ ];
2294
+ function startService(project, service, state) {
2295
+ const log = logPath(project.instance, service.name);
2296
+ const spec = {
2297
+ project: project.instance,
2298
+ service: service.name,
2299
+ command: service.command,
2300
+ cwd: service.cwd,
2301
+ env: {
2302
+ PORT: String(service.port),
2303
+ ...service.env
2304
+ },
2305
+ log
2306
+ };
2307
+ const child = spawn(process.execPath, [
2308
+ ...process.execArgv,
2309
+ process.argv[1],
2310
+ SUPERVISE_COMMAND,
2311
+ JSON.stringify(spec)
2312
+ ], {
2313
+ cwd: service.cwd,
2314
+ detached: true,
2315
+ stdio: "ignore"
2316
+ });
2317
+ if (!child.pid) throw new Error(`could not start the supervisor for ${service.name}`);
2318
+ let exited = false;
2319
+ child.once("exit", () => {
2320
+ exited = true;
2321
+ });
2322
+ child.unref();
2323
+ state.services[recordKey(project.instance, service.name)] = {
2324
+ project: project.instance,
2325
+ service: service.name,
2326
+ root: project.root,
2327
+ port: service.port,
2328
+ command: service.command,
2329
+ supervisorPid: child.pid,
2330
+ startedAt: Date.now(),
2331
+ log
2332
+ };
2333
+ return {
2334
+ pid: child.pid,
2335
+ exited: () => exited
2336
+ };
2337
+ }
2338
+ async function waitReady(service, exited) {
2339
+ const deadline = Date.now() + service.readyTimeout * 1e3;
2340
+ for (;;) {
2341
+ if (exited()) return "exited";
2342
+ if (await isListening(service.port, 300)) return "ready";
2343
+ if (Date.now() > deadline) return "timeout";
2344
+ await setTimeout$1(200);
2345
+ }
2346
+ }
2347
+ async function up(io, cwd, names, opts) {
2348
+ const project = await resolveProject(cwd, opts.project);
2349
+ const services = selectServices(project, names);
2350
+ const statuses = await projectStatus(project, readState(), services);
2351
+ const results = [];
2352
+ const toStart = [];
2353
+ for (const [i, st] of statuses.entries()) {
2354
+ const base = {
2355
+ service: st.service,
2356
+ port: st.port,
2357
+ url: st.url
2358
+ };
2359
+ switch (st.state) {
2360
+ case "running":
2361
+ case "starting":
2362
+ results.push({
2363
+ ...base,
2364
+ outcome: st.external ? "external-up" : "already-running",
2365
+ pid: st.pid
2366
+ });
2367
+ break;
2368
+ case "unmanaged":
2369
+ results.push({
2370
+ ...base,
2371
+ outcome: "running-outside",
2372
+ pid: st.pid,
2373
+ detail: describe(st)
2374
+ });
2375
+ break;
2376
+ case "elsewhere":
2377
+ case "taken":
2378
+ results.push({
2379
+ ...base,
2380
+ outcome: "blocked",
2381
+ pid: st.pid,
2382
+ detail: describe(st)
2383
+ });
2384
+ break;
2385
+ case "stopped":
2386
+ case "crashed": if (st.external) results.push({
2387
+ ...base,
2388
+ outcome: "external-down",
2389
+ detail: "nothing listens on this port; portcall does not start external services"
2390
+ });
2391
+ else toStart.push(services[i]);
2392
+ }
2393
+ }
2394
+ const started = await updateState((state) => toStart.map((service) => ({
2395
+ service,
2396
+ ...startService(project, service, state)
2397
+ })));
2398
+ results.push(...await Promise.all(started.map(async ({ service, pid, exited }) => {
2399
+ const base = {
2400
+ service: service.name,
2401
+ port: service.port,
2402
+ url: `http://localhost:${service.port}`,
2403
+ pid
2404
+ };
2405
+ if (!opts.wait) return {
2406
+ ...base,
2407
+ outcome: "started",
2408
+ detail: "not waiting for the port"
2409
+ };
2410
+ const t0 = Date.now();
2411
+ const ready = await waitReady(service, exited);
2412
+ if (ready === "ready") return {
2413
+ ...base,
2414
+ outcome: "started",
2415
+ detail: `ready in ${((Date.now() - t0) / 1e3).toFixed(1)}s`
2416
+ };
2417
+ const logTail = readLog(logPath(project.instance, service.name), service.name).slice(-30).map((l) => formatLine(l));
2418
+ if (ready === "exited") {
2419
+ const record = readState().services[recordKey(project.instance, service.name)];
2420
+ return {
2421
+ ...base,
2422
+ outcome: "crashed",
2423
+ detail: `exited before listening (code ${record?.exitCode ?? "?"})`,
2424
+ logTail
2425
+ };
2426
+ }
2427
+ return {
2428
+ ...base,
2429
+ outcome: "timeout",
2430
+ detail: `still running but nothing listens on ${service.port} after ${service.readyTimeout}s (wrong port in portcall.json?)`,
2431
+ logTail
2432
+ };
2433
+ })));
2434
+ const order = new Map(services.map((s, i) => [s.name, i]));
2435
+ results.sort((a, b) => order.get(a.service) - order.get(b.service));
2436
+ const failed = results.some((r) => UP_FAILURES.includes(r.outcome));
2437
+ if (io.json) io.out(JSON.stringify({
2438
+ project: project.name,
2439
+ ...worktreeFields(project),
2440
+ ok: !failed,
2441
+ services: results
2442
+ }, null, 2));
2443
+ else {
2444
+ io.out(table([[
2445
+ "SERVICE",
2446
+ "RESULT",
2447
+ "URL",
2448
+ "PID",
2449
+ "INFO"
2450
+ ], ...results.map((r) => [
2451
+ r.service,
2452
+ r.outcome,
2453
+ r.url,
2454
+ r.pid ? String(r.pid) : "-",
2455
+ r.detail ?? ""
2456
+ ])]));
2457
+ for (const r of results.filter((r) => r.logTail?.length)) io.out(`\n--- ${r.service}: last log lines (portcall logs ${r.service}) ---\n${r.logTail.join("\n")}`);
2458
+ }
2459
+ return failed ? 1 : 0;
2460
+ }
2461
+ async function stopRecord(record) {
2462
+ if (!record.exitedAt) {
2463
+ signal(record.supervisorPid, "SIGTERM");
2464
+ if (!await waitForDeath(record.supervisorPid, STOP_WAIT_MS)) signal(record.supervisorPid, "SIGKILL");
2465
+ if (isAlive(record.childPid)) {
2466
+ signalGroup(record.childPid, "SIGTERM");
2467
+ if (!await waitForDeath(record.childPid, 3e3)) signalGroup(record.childPid, "SIGKILL");
2468
+ }
2469
+ }
2470
+ await updateState((state) => {
2471
+ const key = recordKey(record.project, record.service);
2472
+ if (state.services[key]?.supervisorPid === record.supervisorPid) delete state.services[key];
2473
+ });
2474
+ }
2475
+ async function stopPid(pid) {
2476
+ signal(pid, "SIGTERM");
2477
+ if (!await waitForDeath(pid, STOP_WAIT_MS)) signal(pid, "SIGKILL");
2478
+ }
2479
+ async function down(io, cwd, names, opts) {
2480
+ const project = await resolveProject(cwd, opts.project);
2481
+ const services = selectServices(project, names);
2482
+ const state = readState();
2483
+ const statuses = await projectStatus(project, state, services);
2484
+ const results = await Promise.all(statuses.map(async (st) => {
2485
+ const record = state.services[recordKey(project.instance, st.service)];
2486
+ if (st.external) return {
2487
+ service: st.service,
2488
+ outcome: "skipped",
2489
+ detail: "external, not managed by portcall"
2490
+ };
2491
+ switch (st.state) {
2492
+ case "running":
2493
+ case "starting":
2494
+ case "crashed":
2495
+ await stopRecord(record);
2496
+ return {
2497
+ service: st.service,
2498
+ outcome: "stopped"
2499
+ };
2500
+ case "elsewhere":
2501
+ if (!opts.force) return {
2502
+ service: st.service,
2503
+ outcome: "skipped",
2504
+ detail: `${describe(st)}; --force to stop it from here`
2505
+ };
2506
+ await stopRecord(record);
2507
+ return {
2508
+ service: st.service,
2509
+ outcome: "stopped",
2510
+ detail: describe(st)
2511
+ };
2512
+ case "unmanaged":
2513
+ if (!opts.force) return {
2514
+ service: st.service,
2515
+ outcome: "skipped",
2516
+ detail: `${describe(st)} (pid ${st.pid}); --force to stop it`
2517
+ };
2518
+ await stopPid(st.pid);
2519
+ return {
2520
+ service: st.service,
2521
+ outcome: "stopped",
2522
+ detail: describe(st)
2523
+ };
2524
+ case "taken": return {
2525
+ service: st.service,
2526
+ outcome: "skipped",
2527
+ detail: `${describe(st)}; not part of this project, portcall leaves it alone`
2528
+ };
2529
+ case "stopped": return {
2530
+ service: st.service,
2531
+ outcome: "not-running"
2532
+ };
2533
+ }
2534
+ }));
2535
+ if (!opts.quiet) {
2536
+ if (io.json) io.out(JSON.stringify({
2537
+ project: project.name,
2538
+ services: results
2539
+ }, null, 2));
2540
+ else io.out(table([[
2541
+ "SERVICE",
2542
+ "RESULT",
2543
+ "INFO"
2544
+ ], ...results.map((r) => [
2545
+ r.service,
2546
+ r.outcome,
2547
+ r.detail ?? ""
2548
+ ])]));
2549
+ }
2550
+ return 0;
2551
+ }
2552
+ async function restart(io, cwd, names, opts) {
2553
+ const runnable = selectServices(await resolveProject(cwd, opts.project), names, true).map((s) => s.name);
2554
+ await down(io, cwd, runnable, {
2555
+ ...opts,
2556
+ quiet: true
2557
+ });
2558
+ return up(io, cwd, runnable, opts);
2559
+ }
2560
+ async function logs(io, cwd, names, opts) {
2561
+ const project = await resolveProject(cwd, opts.project);
2562
+ const services = selectServices(project, names, true);
2563
+ const files = services.map((s) => ({
2564
+ file: logPath(project.instance, s.name),
2565
+ service: s.name
2566
+ }));
2567
+ const width = services.length > 1 ? Math.max(...services.map((s) => s.name.length)) : 0;
2568
+ const lines = filterLines(mergeLogs(files.map((f) => readLog(f.file, f.service))), opts.filter);
2569
+ const toJson = (l) => ({
2570
+ time: l.time?.toISOString(),
2571
+ service: l.service,
2572
+ stream: l.tag,
2573
+ text: l.text
2574
+ });
2575
+ if (io.json && !opts.follow) io.out(JSON.stringify({
2576
+ project: project.name,
2577
+ lines: lines.map(toJson)
2578
+ }, null, 2));
2579
+ else if (lines.length) lines.forEach((l) => io.out(io.json ? JSON.stringify(toJson(l)) : formatLine(l, width)));
2580
+ else if (!opts.follow) io.out(files.some((f) => readLog(f.file, f.service).length) ? "(no matching lines)" : "(no logs yet: start it with `portcall up`)");
2581
+ if (opts.follow) await followLogs(files, (l) => io.out(io.json ? JSON.stringify(toJson(l)) : formatLine(l, width)), opts.filter);
2582
+ return 0;
2583
+ }
2584
+ async function ports(io, cwd) {
2585
+ if (findConfig(cwd)) await resolveProject(cwd);
2586
+ const { projects, errors } = knownProjects();
2587
+ const state = readState();
2588
+ const claimant = (project, service) => service.command ? project.instance : project.name;
2589
+ const claims = /* @__PURE__ */ new Map();
2590
+ for (const project of projects) for (const service of project.services) claims.set(service.port, (claims.get(service.port) ?? /* @__PURE__ */ new Set()).add(claimant(project, service)));
2591
+ const seen = /* @__PURE__ */ new Set();
2592
+ const rows = [];
2593
+ for (const project of projects) {
2594
+ if (seen.has(project.instance)) continue;
2595
+ seen.add(project.instance);
2596
+ for (const [i, st] of (await projectStatus(project, state)).entries()) {
2597
+ const own = claimant(project, project.services[i]);
2598
+ const others = [...claims.get(st.port)].filter((name) => name !== own);
2599
+ rows.push({
2600
+ ...st,
2601
+ conflict: others
2602
+ });
2603
+ }
2604
+ }
2605
+ rows.sort((a, b) => a.port - b.port);
2606
+ if (io.json) io.out(JSON.stringify({
2607
+ ports: rows,
2608
+ errors
2609
+ }, null, 2));
2610
+ else {
2611
+ io.out(rows.length ? table([[
2612
+ "PORT",
2613
+ "PROJECT",
2614
+ "SERVICE",
2615
+ "STATE",
2616
+ "INFO"
2617
+ ], ...rows.map((r) => [
2618
+ String(r.port),
2619
+ r.worktree ? `${r.project}@${r.worktree}` : r.project,
2620
+ r.service,
2621
+ r.state,
2622
+ [r.conflict.length ? `CONFLICT with ${r.conflict.join(", ")}` : "", describe(r)].filter(Boolean).join("; ")
2623
+ ])]) : "no known projects yet: run portcall once inside a project");
2624
+ errors.forEach((e) => io.err(`portcall: ${e}`));
2625
+ }
2626
+ return rows.some((r) => r.conflict.length) ? 1 : 0;
2627
+ }
2628
+ const AGENTS_SNIPPET = `## Dev servers
2629
+
2630
+ Dev servers run under portcall (config: \`portcall.json\`). Never start them yourself (no \`pnpm dev\`, \`vite\`, \`tsx watch\`, \`node --watch\`...).
2631
+
2632
+ - \`portcall status\`: what runs, on which port. Check it before assuming a server is down.
2633
+ - \`portcall up [service]\`: start what is not running; a no-op for what already is. It waits for the port and prints the log tail if the server crashes.
2634
+ - \`portcall restart <service>\`: after changing env, config or dependencies (watch mode reloads code by itself).
2635
+ - \`portcall logs <service> --errors\`, \`--since 2m\`, \`--grep <regex>\`, \`-n 200\`: server output, e.g. after a failing request.
2636
+ - Leave servers running when you are done. Add \`--json\` to any command for machine-readable output.
2637
+ `;
2638
+ function agents(io) {
2639
+ io.out(AGENTS_SNIPPET.trimEnd());
2640
+ return 0;
2641
+ }
2642
+ // src/supervise.ts
2643
+ const KILL_AFTER_MS = 5e3;
2644
+ const CLOSE_GRACE_MS = 3e3;
2645
+ const ANSI = /\x1B(?:\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]|\][^\x07]*(?:\x07|\x1B\\)|[\x40-\x5A\x5C-\x5F])/g;
2646
+ /** Plain text for the log: no escape codes, and a progress bar (`\r` rewrites) kept at its last state. */
2647
+ function cleanLine(line) {
2648
+ const text = line.replace(ANSI, "");
2649
+ return text.slice(text.lastIndexOf("\r") + 1).trimEnd();
2650
+ }
2651
+ /**
2652
+ * Runs in its own detached process, one per service: starts the command in a
2653
+ * new process group, timestamps its output into the log, records how it ended.
2654
+ */
2655
+ function supervise(spec) {
2656
+ const key = recordKey(spec.project, spec.service);
2657
+ mkdirSync(dirname(spec.log), { recursive: true });
2658
+ if (existsSync(spec.log)) renameSync(spec.log, `${spec.log}.1`);
2659
+ const out = createWriteStream(spec.log, { flags: "a" });
2660
+ const write = (tag, text) => {
2661
+ out.write(`${(/* @__PURE__ */ new Date()).toISOString()} [${tag}] ${text}\n`);
2662
+ };
2663
+ write("portcall", `$ ${spec.command}`);
2664
+ const child = spawn("/bin/sh", ["-c", spec.command], {
2665
+ cwd: spec.cwd,
2666
+ env: {
2667
+ ...process.env,
2668
+ ...spec.env
2669
+ },
2670
+ detached: true,
2671
+ stdio: [
2672
+ "ignore",
2673
+ "pipe",
2674
+ "pipe"
2675
+ ]
2676
+ });
2677
+ for (const [stream, tag] of [[child.stdout, "out"], [child.stderr, "err"]]) createInterface({
2678
+ input: stream,
2679
+ crlfDelay: 1e400
2680
+ }).on("line", (line) => {
2681
+ const text = cleanLine(line);
2682
+ if (text) write(tag, text);
2683
+ });
2684
+ updateState((state) => {
2685
+ const record = state.services[key];
2686
+ if (record?.supervisorPid === process.pid) record.childPid = child.pid;
2687
+ });
2688
+ let stopping = false;
2689
+ const stop = () => {
2690
+ if (stopping) return;
2691
+ stopping = true;
2692
+ write("portcall", "stopping");
2693
+ signalGroup(child.pid, "SIGTERM");
2694
+ setTimeout(signalGroup, KILL_AFTER_MS, child.pid, "SIGKILL").unref();
2695
+ };
2696
+ process.on("SIGTERM", stop);
2697
+ process.on("SIGINT", stop);
2698
+ process.on("SIGHUP", () => {});
2699
+ let finished = false;
2700
+ const finish = async (code, sig) => {
2701
+ if (finished) return;
2702
+ finished = true;
2703
+ write("portcall", stopping ? "stopped" : `exited with ${sig ? `signal ${sig}` : `code ${code}`}`);
2704
+ try {
2705
+ await updateState((state) => {
2706
+ const record = state.services[key];
2707
+ if (record?.supervisorPid !== process.pid) return;
2708
+ if (stopping) delete state.services[key];
2709
+ else Object.assign(record, {
2710
+ exitCode: code,
2711
+ exitSignal: sig,
2712
+ exitedAt: Date.now()
2713
+ });
2714
+ });
2715
+ } finally {
2716
+ out.end(() => process.exit(0));
2717
+ }
2718
+ };
2719
+ child.on("error", (error) => {
2720
+ write("portcall", `failed to start: ${error.message}`);
2721
+ finish(127, null);
2722
+ });
2723
+ child.on("exit", (code, sig) => {
2724
+ signalGroup(child.pid, "SIGTERM");
2725
+ setTimeout(() => void finish(code, sig), CLOSE_GRACE_MS).unref();
2726
+ child.on("close", () => void finish(code, sig));
2727
+ });
2728
+ }
2729
+ // src/cli.ts
2730
+ const USAGE = `portcall - run your dev servers in the background, on fixed ports, with logs agents can read
2731
+
2732
+ Usage:
2733
+ portcall up [service...] Start services (no-op for the ones already running), wait for their port
2734
+ portcall down [service...] Stop services
2735
+ portcall restart [service...] Stop then start
2736
+ portcall status [--all] What runs, on which port (--all: every project portcall knows)
2737
+ portcall logs [service...] Service output (all services when none named)
2738
+ portcall ports Every known project's ports, with conflicts
2739
+ portcall agents Print an AGENTS.md section telling coding agents how to use portcall
2740
+
2741
+ Options:
2742
+ -p, --project <name> Act on a known project from anywhere (default: the portcall.json at or above the cwd);
2743
+ <name>@<worktree> for a git worktree with its own ports
2744
+ -C, --cwd <dir> Run as if started in <dir>
2745
+ --json Machine-readable output
2746
+ --no-wait up/restart: return without waiting for the port
2747
+ --force down/restart: also stop a server of this project started outside portcall
2748
+ -h, --help This help
2749
+ -v, --version Print the version
2750
+
2751
+ Logs:
2752
+ -n, --lines <n> Last n lines (default 100)
2753
+ --since <time> Only lines after 30s, 5m, 2h, 1d or a date
2754
+ --grep <regex> Only lines matching (case-insensitive)
2755
+ --errors Only error lines, with their stack traces
2756
+ -f, --follow Keep printing new lines
2757
+
2758
+ portcall.json, at the project root:
2759
+ {
2760
+ "name": "myapp",
2761
+ "services": {
2762
+ "server": { "command": "pnpm dev:server", "port": 3000 },
2763
+ "web": { "command": "pnpm dev:web", "port": 5173, "env": { "API_URL": "http://localhost:{ports.server}" } },
2764
+ "db": { "port": 5432 }
2765
+ }
2766
+ }
2767
+ A service without "command" is external (a database in Docker...): portcall only checks its port.
2768
+ Each command gets PORT set to its port. Logs and state live in ~/.portcall (PORTCALL_HOME).
2769
+ "worktrees": true gives each git worktree its own ports (declared + an offset), so they run at once.
2770
+ `;
2771
+ async function main(argv) {
2772
+ if (argv[0] === "__supervise") {
2773
+ supervise(JSON.parse(argv[1]));
2774
+ return -1;
2775
+ }
2776
+ const { values, positionals } = parseArgs({
2777
+ args: argv,
2778
+ allowPositionals: true,
2779
+ options: {
2780
+ "project": {
2781
+ type: "string",
2782
+ short: "p"
2783
+ },
2784
+ "cwd": {
2785
+ type: "string",
2786
+ short: "C"
2787
+ },
2788
+ "json": { type: "boolean" },
2789
+ "all": {
2790
+ type: "boolean",
2791
+ short: "a"
2792
+ },
2793
+ "no-wait": { type: "boolean" },
2794
+ "force": { type: "boolean" },
2795
+ "lines": {
2796
+ type: "string",
2797
+ short: "n"
2798
+ },
2799
+ "since": { type: "string" },
2800
+ "grep": { type: "string" },
2801
+ "errors": { type: "boolean" },
2802
+ "follow": {
2803
+ type: "boolean",
2804
+ short: "f"
2805
+ },
2806
+ "help": {
2807
+ type: "boolean",
2808
+ short: "h"
2809
+ },
2810
+ "version": {
2811
+ type: "boolean",
2812
+ short: "v"
2813
+ }
2814
+ }
2815
+ });
2816
+ const io = {
2817
+ json: !!values.json,
2818
+ out: (text) => process.stdout.write(`${text}\n`),
2819
+ err: (text) => process.stderr.write(`${text}\n`)
2820
+ };
2821
+ if (values.version) {
2822
+ io.out(version$1);
2823
+ return 0;
2824
+ }
2825
+ const [command = "help", ...names] = positionals;
2826
+ if (values.help || command === "help") {
2827
+ io.out(USAGE.trimEnd());
2828
+ return 0;
2829
+ }
2830
+ const cwd = resolve(values.cwd ?? process.cwd());
2831
+ const project = values.project;
2832
+ const wait = !values["no-wait"];
2833
+ switch (command) {
2834
+ case "up":
2835
+ case "start": return up(io, cwd, names, {
2836
+ project,
2837
+ wait
2838
+ });
2839
+ case "down":
2840
+ case "stop": return down(io, cwd, names, {
2841
+ project,
2842
+ force: values.force
2843
+ });
2844
+ case "restart": return restart(io, cwd, names, {
2845
+ project,
2846
+ force: values.force,
2847
+ wait
2848
+ });
2849
+ case "status":
2850
+ case "ps": return status(io, cwd, {
2851
+ project,
2852
+ all: values.all
2853
+ });
2854
+ case "logs": {
2855
+ const lines = values.lines === void 0 ? 100 : Number(values.lines);
2856
+ if (!Number.isInteger(lines) || lines < 1) throw new Error(`--lines: expected a positive number, got "${values.lines}"`);
2857
+ let grep;
2858
+ try {
2859
+ grep = values.grep === void 0 ? void 0 : new RegExp(values.grep, "i");
2860
+ } catch {
2861
+ throw new Error(`--grep: invalid regular expression "${values.grep}"`);
2862
+ }
2863
+ return logs(io, cwd, names, {
2864
+ project,
2865
+ follow: values.follow,
2866
+ filter: {
2867
+ limit: lines,
2868
+ since: values.since === void 0 ? void 0 : parseSince(values.since),
2869
+ grep,
2870
+ errors: values.errors
2871
+ }
2872
+ });
2873
+ }
2874
+ case "ports": return ports(io, cwd);
2875
+ case "agents": return agents(io);
2876
+ default: throw new Error(`unknown command "${command}" (see \`portcall help\`)`);
2877
+ }
2878
+ }
2879
+ main(process.argv.slice(2)).then((code) => {
2880
+ if (code >= 0) process.exitCode = code;
2881
+ }, (error) => {
2882
+ if (process.argv.includes("--json")) process.stdout.write(`${JSON.stringify({ error: error.message })}\n`);
2883
+ else process.stderr.write(`portcall: ${error.message}\n`);
2884
+ process.exitCode = 1;
2885
+ });
2886
+ export {};