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.
- package/LICENSE +21 -0
- package/README.md +97 -2
- package/dist/portcall.mjs +2886 -0
- package/package.json +49 -3
- package/schema.json +71 -0
|
@@ -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 {};
|