@ovrdev/cli 0.1.0-alpha.16 → 0.1.0-alpha.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +216 -199
- package/dist/core/config.d.ts +6 -0
- package/dist/core/core.js +1 -1
- package/dist/index-s6rge1qt.js +639 -0
- package/dist/plugins/shell.js +1 -1
- package/package.json +9 -8
package/dist/cli/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
coerceEnv,
|
|
7
7
|
collectRefTargets,
|
|
8
8
|
configAsks,
|
|
9
|
+
defaultRoot,
|
|
9
10
|
evalConfig,
|
|
10
11
|
forkChain,
|
|
11
12
|
forkTreeOrder,
|
|
@@ -32,14 +33,143 @@ import {
|
|
|
32
33
|
serviceRefs,
|
|
33
34
|
withLocalBins,
|
|
34
35
|
workspaceOrder
|
|
35
|
-
} from "../index-
|
|
36
|
+
} from "../index-s6rge1qt.js";
|
|
36
37
|
|
|
37
38
|
// src/cli/cmd.ts
|
|
38
39
|
import * as clack2 from "@clack/prompts";
|
|
39
40
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
40
41
|
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
41
42
|
import { homedir } from "node:os";
|
|
42
|
-
import { join as join9, resolve as resolve2 } from "node:path";
|
|
43
|
+
import { basename as basename2, join as join9, resolve as resolve2 } from "node:path";
|
|
44
|
+
|
|
45
|
+
// src/core/load.ts
|
|
46
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
47
|
+
import { join } from "node:path";
|
|
48
|
+
import { createInterface } from "node:readline/promises";
|
|
49
|
+
import { pathToFileURL } from "node:url";
|
|
50
|
+
var CONFIG_FILES = ["ovr.config.mts", "ovr.config.ts", "ovr.config.js", "ovr.config.mjs"];
|
|
51
|
+
var findConfigFile = (dir) => CONFIG_FILES.map((f) => join(dir, f)).find(existsSync);
|
|
52
|
+
var answersPath = (dir) => join(dir, ".ovr", "answers.json");
|
|
53
|
+
var readAnswers = (dir) => {
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(readFileSync(answersPath(dir), "utf8"));
|
|
56
|
+
} catch {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var writeAnswers = (dir, a) => {
|
|
61
|
+
mkdirSync(join(dir, ".ovr"), { recursive: true });
|
|
62
|
+
writeFileSync(answersPath(dir), `${JSON.stringify(a, null, "\t")}
|
|
63
|
+
`);
|
|
64
|
+
};
|
|
65
|
+
function coerceAnswer(spec, raw) {
|
|
66
|
+
if (spec.kind === "confirm") {
|
|
67
|
+
if (typeof raw === "boolean")
|
|
68
|
+
return raw;
|
|
69
|
+
if (raw === "true" || raw === "y" || raw === "yes" || raw === "1")
|
|
70
|
+
return true;
|
|
71
|
+
if (raw === "false" || raw === "n" || raw === "no" || raw === "0")
|
|
72
|
+
return false;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
return typeof raw === "string" && spec.options.includes(raw) ? raw : undefined;
|
|
76
|
+
}
|
|
77
|
+
async function resolveAsks(asks, opts) {
|
|
78
|
+
const answers = {};
|
|
79
|
+
let changed = false;
|
|
80
|
+
for (const [key, spec] of Object.entries(asks)) {
|
|
81
|
+
const flag = opts.overrides[key] !== undefined ? coerceAnswer(spec, opts.overrides[key]) : undefined;
|
|
82
|
+
if (flag !== undefined) {
|
|
83
|
+
answers[key] = flag;
|
|
84
|
+
changed ||= opts.saved[key] !== flag;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (!opts.reask) {
|
|
88
|
+
const saved = coerceAnswer(spec, opts.saved[key]);
|
|
89
|
+
if (saved !== undefined) {
|
|
90
|
+
answers[key] = saved;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (opts.prompt) {
|
|
95
|
+
answers[key] = coerceAnswer(spec, await opts.prompt(key, spec)) ?? spec.default;
|
|
96
|
+
changed = true;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (spec.default !== undefined) {
|
|
100
|
+
answers[key] = spec.default;
|
|
101
|
+
changed = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { answers, changed };
|
|
105
|
+
}
|
|
106
|
+
async function promptAsk(repo, key, spec) {
|
|
107
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
108
|
+
try {
|
|
109
|
+
if (spec.kind === "confirm") {
|
|
110
|
+
const hint = spec.default === false ? "y/N" : spec.default === true ? "Y/n" : "y/n";
|
|
111
|
+
const raw = (await rl.question(`? ${repo}: ${spec.question} (${hint}) `)).trim().toLowerCase();
|
|
112
|
+
if (!raw && spec.default !== undefined)
|
|
113
|
+
return spec.default;
|
|
114
|
+
return raw === "y" || raw === "yes" || raw === "true" || raw === "1";
|
|
115
|
+
}
|
|
116
|
+
process.stderr.write(`? ${repo}: ${spec.question}
|
|
117
|
+
`);
|
|
118
|
+
spec.options.forEach((o, i) => {
|
|
119
|
+
process.stderr.write(` ${i + 1}) ${o}${o === spec.default ? " (default)" : ""}
|
|
120
|
+
`);
|
|
121
|
+
});
|
|
122
|
+
for (;; ) {
|
|
123
|
+
const raw = (await rl.question("> ")).trim();
|
|
124
|
+
if (!raw && spec.default !== undefined)
|
|
125
|
+
return spec.default;
|
|
126
|
+
const byNum = spec.options[Number.parseInt(raw, 10) - 1];
|
|
127
|
+
if (byNum)
|
|
128
|
+
return byNum;
|
|
129
|
+
if (spec.options.includes(raw))
|
|
130
|
+
return raw;
|
|
131
|
+
process.stderr.write(` pick 1-${spec.options.length}
|
|
132
|
+
`);
|
|
133
|
+
}
|
|
134
|
+
} finally {
|
|
135
|
+
rl.close();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function loadRepoConfig(dir, workspace, repo, opts = {}) {
|
|
139
|
+
const file = findConfigFile(dir);
|
|
140
|
+
if (!file)
|
|
141
|
+
return null;
|
|
142
|
+
const mod = await import(pathToFileURL(file).href);
|
|
143
|
+
const config = mod.default ?? mod;
|
|
144
|
+
const asks = configAsks(config);
|
|
145
|
+
let answers;
|
|
146
|
+
if (Object.keys(asks).length) {
|
|
147
|
+
const interactive = process.stdin.isTTY && process.stderr.isTTY;
|
|
148
|
+
const resolved = await resolveAsks(asks, {
|
|
149
|
+
saved: readAnswers(dir),
|
|
150
|
+
overrides: opts.answers ?? {},
|
|
151
|
+
reask: opts.reask,
|
|
152
|
+
prompt: interactive ? (key, spec) => promptAsk(repo, key, spec) : undefined
|
|
153
|
+
});
|
|
154
|
+
answers = resolved.answers;
|
|
155
|
+
if (resolved.changed)
|
|
156
|
+
writeAnswers(dir, answers);
|
|
157
|
+
}
|
|
158
|
+
return evalConfig(config, workspace, repo, dir, answers);
|
|
159
|
+
}
|
|
160
|
+
function parseAskFlags(args) {
|
|
161
|
+
const answers = {};
|
|
162
|
+
for (let i = 0;i < args.length; i++) {
|
|
163
|
+
if (args[i] === "--mode" && args[i + 1])
|
|
164
|
+
answers.mode = args[++i];
|
|
165
|
+
else if (args[i] === "--answer" && args[i + 1]) {
|
|
166
|
+
const eq = args[++i].indexOf("=");
|
|
167
|
+
if (eq > 0)
|
|
168
|
+
answers[args[i].slice(0, eq)] = args[i].slice(eq + 1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return { answers, reask: args.includes("--ask") };
|
|
172
|
+
}
|
|
43
173
|
|
|
44
174
|
// src/util/color.ts
|
|
45
175
|
var useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
@@ -53,15 +183,15 @@ var magenta = (s) => paint("35", s);
|
|
|
53
183
|
var blue = (s) => paint("34", s);
|
|
54
184
|
|
|
55
185
|
// src/engine/ports.ts
|
|
56
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
186
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
57
187
|
import { createServer } from "node:net";
|
|
58
|
-
import { dirname, join } from "node:path";
|
|
59
|
-
var PORTS_PATH =
|
|
188
|
+
import { dirname, join as join2 } from "node:path";
|
|
189
|
+
var PORTS_PATH = join2(CONFIG_DIR, "ports.json");
|
|
60
190
|
var loadFile = (file) => {
|
|
61
|
-
if (!
|
|
191
|
+
if (!existsSync2(file))
|
|
62
192
|
return {};
|
|
63
193
|
try {
|
|
64
|
-
return JSON.parse(
|
|
194
|
+
return JSON.parse(readFileSync2(file, "utf8"));
|
|
65
195
|
} catch {
|
|
66
196
|
return {};
|
|
67
197
|
}
|
|
@@ -87,7 +217,7 @@ function releasePorts(workspace, file = PORTS_PATH) {
|
|
|
87
217
|
if (!(workspace in all))
|
|
88
218
|
return;
|
|
89
219
|
delete all[workspace];
|
|
90
|
-
|
|
220
|
+
writeFileSync2(file, `${JSON.stringify(all, null, "\t")}
|
|
91
221
|
`);
|
|
92
222
|
}
|
|
93
223
|
function createPortRegistry(workspace, file = PORTS_PATH) {
|
|
@@ -95,8 +225,8 @@ function createPortRegistry(workspace, file = PORTS_PATH) {
|
|
|
95
225
|
const save = () => {
|
|
96
226
|
const all = loadFile(file);
|
|
97
227
|
all[workspace] = recorded;
|
|
98
|
-
|
|
99
|
-
|
|
228
|
+
mkdirSync2(dirname(file), { recursive: true });
|
|
229
|
+
writeFileSync2(file, `${JSON.stringify(all, null, "\t")}
|
|
100
230
|
`);
|
|
101
231
|
};
|
|
102
232
|
const reservedElsewhere = (service, slot) => {
|
|
@@ -138,10 +268,10 @@ function createPortRegistry(workspace, file = PORTS_PATH) {
|
|
|
138
268
|
}
|
|
139
269
|
|
|
140
270
|
// src/engine/paths.ts
|
|
141
|
-
import { mkdirSync as
|
|
271
|
+
import { mkdirSync as mkdirSync3, rmSync as rmSync2 } from "node:fs";
|
|
142
272
|
import { tmpdir } from "node:os";
|
|
143
|
-
import { join as
|
|
144
|
-
var DATA_ROOT =
|
|
273
|
+
import { join as join3 } from "node:path";
|
|
274
|
+
var DATA_ROOT = join3(CONFIG_DIR, "data");
|
|
145
275
|
function safeSegments(label) {
|
|
146
276
|
const segs = label.split(/[/\\]+/).filter((s) => s && s !== ".");
|
|
147
277
|
for (const s of segs)
|
|
@@ -150,14 +280,14 @@ function safeSegments(label) {
|
|
|
150
280
|
return segs;
|
|
151
281
|
}
|
|
152
282
|
function releaseData(workspace, root = DATA_ROOT) {
|
|
153
|
-
rmSync2(
|
|
283
|
+
rmSync2(join3(root, workspace), { recursive: true, force: true });
|
|
154
284
|
}
|
|
155
285
|
function createPathRegistry(workspace, root = DATA_ROOT) {
|
|
156
286
|
return {
|
|
157
287
|
forService(service) {
|
|
158
288
|
return (label) => {
|
|
159
|
-
const dir =
|
|
160
|
-
|
|
289
|
+
const dir = join3(root, workspace, service, ...label ? safeSegments(label) : []);
|
|
290
|
+
mkdirSync3(dir, { recursive: true });
|
|
161
291
|
return dir;
|
|
162
292
|
};
|
|
163
293
|
},
|
|
@@ -166,15 +296,15 @@ function createPathRegistry(workspace, root = DATA_ROOT) {
|
|
|
166
296
|
}
|
|
167
297
|
};
|
|
168
298
|
}
|
|
169
|
-
var dataDirPath = (workspace, service, root = DATA_ROOT) =>
|
|
170
|
-
var SCRATCH_ROOT =
|
|
299
|
+
var dataDirPath = (workspace, service, root = DATA_ROOT) => join3(root, workspace, service);
|
|
300
|
+
var SCRATCH_ROOT = join3(tmpdir(), "ovr-scratch");
|
|
171
301
|
function createScratchRegistry(workspace, root = SCRATCH_ROOT) {
|
|
172
|
-
rmSync2(
|
|
302
|
+
rmSync2(join3(root, workspace), { recursive: true, force: true });
|
|
173
303
|
return {
|
|
174
304
|
forService(service) {
|
|
175
305
|
return (label) => {
|
|
176
|
-
const dir =
|
|
177
|
-
|
|
306
|
+
const dir = join3(root, workspace, service, ...label ? safeSegments(label) : []);
|
|
307
|
+
mkdirSync3(dir, { recursive: true });
|
|
178
308
|
return dir;
|
|
179
309
|
};
|
|
180
310
|
}
|
|
@@ -183,8 +313,8 @@ function createScratchRegistry(workspace, root = SCRATCH_ROOT) {
|
|
|
183
313
|
|
|
184
314
|
// src/engine/keys.ts
|
|
185
315
|
import { createHash, createPublicKey, generateKeyPairSync } from "node:crypto";
|
|
186
|
-
import { existsSync as
|
|
187
|
-
import { dirname as dirname2, join as
|
|
316
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
317
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
188
318
|
var ALG = { ed25519: "EdDSA", rsa: "RS256", ec: "ES256" };
|
|
189
319
|
function jwkThumbprint(jwk) {
|
|
190
320
|
const members = jwk.kty === "RSA" ? ["e", "kty", "n"] : jwk.kty === "EC" ? ["crv", "kty", "x", "y"] : ["crv", "kty", "x"];
|
|
@@ -197,12 +327,12 @@ function sshField(v) {
|
|
|
197
327
|
len.writeUInt32BE(b.length);
|
|
198
328
|
return Buffer.concat([len, b]);
|
|
199
329
|
}
|
|
200
|
-
var KEYS_PATH =
|
|
330
|
+
var KEYS_PATH = join4(CONFIG_DIR, "keys.json");
|
|
201
331
|
var loadFile2 = (file) => {
|
|
202
|
-
if (!
|
|
332
|
+
if (!existsSync3(file))
|
|
203
333
|
return {};
|
|
204
334
|
try {
|
|
205
|
-
return JSON.parse(
|
|
335
|
+
return JSON.parse(readFileSync3(file, "utf8"));
|
|
206
336
|
} catch {
|
|
207
337
|
return {};
|
|
208
338
|
}
|
|
@@ -216,7 +346,7 @@ function releaseKeys(workspace, file = KEYS_PATH) {
|
|
|
216
346
|
if (!(workspace in all))
|
|
217
347
|
return;
|
|
218
348
|
delete all[workspace];
|
|
219
|
-
|
|
349
|
+
writeFileSync3(file, `${JSON.stringify(all, null, "\t")}
|
|
220
350
|
`);
|
|
221
351
|
}
|
|
222
352
|
function createKeyRegistry(workspace, file = KEYS_PATH, dataRoot = DATA_ROOT) {
|
|
@@ -224,8 +354,8 @@ function createKeyRegistry(workspace, file = KEYS_PATH, dataRoot = DATA_ROOT) {
|
|
|
224
354
|
const save = () => {
|
|
225
355
|
const all = loadFile2(file);
|
|
226
356
|
all[workspace] = recorded;
|
|
227
|
-
|
|
228
|
-
|
|
357
|
+
mkdirSync4(dirname2(file), { recursive: true });
|
|
358
|
+
writeFileSync3(file, `${JSON.stringify(all, null, "\t")}
|
|
229
359
|
`);
|
|
230
360
|
};
|
|
231
361
|
return {
|
|
@@ -258,13 +388,13 @@ function createKeyRegistry(workspace, file = KEYS_PATH, dataRoot = DATA_ROOT) {
|
|
|
258
388
|
return { publicKey: `ssh-ed25519 ${wire.toString("base64")} ${sopts.comment ?? "ovr"}`, privateKey };
|
|
259
389
|
},
|
|
260
390
|
write(wopts = {}) {
|
|
261
|
-
const base =
|
|
262
|
-
|
|
391
|
+
const base = join4(dataDirPath(workspace, service, dataRoot), "keys");
|
|
392
|
+
mkdirSync4(base, { recursive: true });
|
|
263
393
|
const nm = wopts.name ?? name;
|
|
264
|
-
const privateKeyPath =
|
|
265
|
-
const publicKeyPath =
|
|
266
|
-
|
|
267
|
-
|
|
394
|
+
const privateKeyPath = join4(base, `${nm}.key`);
|
|
395
|
+
const publicKeyPath = join4(base, `${nm}.pub`);
|
|
396
|
+
writeFileSync3(privateKeyPath, privateKey);
|
|
397
|
+
writeFileSync3(publicKeyPath, publicKey);
|
|
268
398
|
return { privateKeyPath, publicKeyPath };
|
|
269
399
|
}
|
|
270
400
|
};
|
|
@@ -279,21 +409,21 @@ function createKeyRegistry(workspace, file = KEYS_PATH, dataRoot = DATA_ROOT) {
|
|
|
279
409
|
}
|
|
280
410
|
|
|
281
411
|
// src/engine/running.ts
|
|
282
|
-
import { existsSync as
|
|
283
|
-
import { dirname as dirname3, join as
|
|
284
|
-
var RUNNING_PATH =
|
|
412
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
413
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
414
|
+
var RUNNING_PATH = join5(CONFIG_DIR, "running.json");
|
|
285
415
|
var load = (file) => {
|
|
286
|
-
if (!
|
|
416
|
+
if (!existsSync4(file))
|
|
287
417
|
return {};
|
|
288
418
|
try {
|
|
289
|
-
return JSON.parse(
|
|
419
|
+
return JSON.parse(readFileSync4(file, "utf8"));
|
|
290
420
|
} catch {
|
|
291
421
|
return {};
|
|
292
422
|
}
|
|
293
423
|
};
|
|
294
424
|
var save = (file, data) => {
|
|
295
|
-
|
|
296
|
-
|
|
425
|
+
mkdirSync5(dirname3(file), { recursive: true });
|
|
426
|
+
writeFileSync4(file, `${JSON.stringify(data, null, "\t")}
|
|
297
427
|
`);
|
|
298
428
|
};
|
|
299
429
|
var pidAlive = (pid) => {
|
|
@@ -398,137 +528,6 @@ import { execSync } from "node:child_process";
|
|
|
398
528
|
import { createHash as createHash2 } from "node:crypto";
|
|
399
529
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
400
530
|
import { join as join6 } from "node:path";
|
|
401
|
-
|
|
402
|
-
// src/core/load.ts
|
|
403
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
404
|
-
import { join as join5 } from "node:path";
|
|
405
|
-
import { createInterface } from "node:readline/promises";
|
|
406
|
-
import { pathToFileURL } from "node:url";
|
|
407
|
-
var CONFIG_FILES = ["ovr.config.mts", "ovr.config.ts", "ovr.config.js", "ovr.config.mjs"];
|
|
408
|
-
var findConfigFile = (dir) => CONFIG_FILES.map((f) => join5(dir, f)).find(existsSync4);
|
|
409
|
-
var answersPath = (dir) => join5(dir, ".ovr", "answers.json");
|
|
410
|
-
var readAnswers = (dir) => {
|
|
411
|
-
try {
|
|
412
|
-
return JSON.parse(readFileSync4(answersPath(dir), "utf8"));
|
|
413
|
-
} catch {
|
|
414
|
-
return {};
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
|
-
var writeAnswers = (dir, a) => {
|
|
418
|
-
mkdirSync5(join5(dir, ".ovr"), { recursive: true });
|
|
419
|
-
writeFileSync4(answersPath(dir), `${JSON.stringify(a, null, "\t")}
|
|
420
|
-
`);
|
|
421
|
-
};
|
|
422
|
-
function coerceAnswer(spec, raw) {
|
|
423
|
-
if (spec.kind === "confirm") {
|
|
424
|
-
if (typeof raw === "boolean")
|
|
425
|
-
return raw;
|
|
426
|
-
if (raw === "true" || raw === "y" || raw === "yes" || raw === "1")
|
|
427
|
-
return true;
|
|
428
|
-
if (raw === "false" || raw === "n" || raw === "no" || raw === "0")
|
|
429
|
-
return false;
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
return typeof raw === "string" && spec.options.includes(raw) ? raw : undefined;
|
|
433
|
-
}
|
|
434
|
-
async function resolveAsks(asks, opts) {
|
|
435
|
-
const answers = {};
|
|
436
|
-
let changed = false;
|
|
437
|
-
for (const [key, spec] of Object.entries(asks)) {
|
|
438
|
-
const flag = opts.overrides[key] !== undefined ? coerceAnswer(spec, opts.overrides[key]) : undefined;
|
|
439
|
-
if (flag !== undefined) {
|
|
440
|
-
answers[key] = flag;
|
|
441
|
-
changed ||= opts.saved[key] !== flag;
|
|
442
|
-
continue;
|
|
443
|
-
}
|
|
444
|
-
if (!opts.reask) {
|
|
445
|
-
const saved = coerceAnswer(spec, opts.saved[key]);
|
|
446
|
-
if (saved !== undefined) {
|
|
447
|
-
answers[key] = saved;
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
if (opts.prompt) {
|
|
452
|
-
answers[key] = coerceAnswer(spec, await opts.prompt(key, spec)) ?? spec.default;
|
|
453
|
-
changed = true;
|
|
454
|
-
continue;
|
|
455
|
-
}
|
|
456
|
-
if (spec.default !== undefined) {
|
|
457
|
-
answers[key] = spec.default;
|
|
458
|
-
changed = true;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
return { answers, changed };
|
|
462
|
-
}
|
|
463
|
-
async function promptAsk(repo, key, spec) {
|
|
464
|
-
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
465
|
-
try {
|
|
466
|
-
if (spec.kind === "confirm") {
|
|
467
|
-
const hint = spec.default === false ? "y/N" : spec.default === true ? "Y/n" : "y/n";
|
|
468
|
-
const raw = (await rl.question(`? ${repo}: ${spec.question} (${hint}) `)).trim().toLowerCase();
|
|
469
|
-
if (!raw && spec.default !== undefined)
|
|
470
|
-
return spec.default;
|
|
471
|
-
return raw === "y" || raw === "yes" || raw === "true" || raw === "1";
|
|
472
|
-
}
|
|
473
|
-
process.stderr.write(`? ${repo}: ${spec.question}
|
|
474
|
-
`);
|
|
475
|
-
spec.options.forEach((o, i) => {
|
|
476
|
-
process.stderr.write(` ${i + 1}) ${o}${o === spec.default ? " (default)" : ""}
|
|
477
|
-
`);
|
|
478
|
-
});
|
|
479
|
-
for (;; ) {
|
|
480
|
-
const raw = (await rl.question("> ")).trim();
|
|
481
|
-
if (!raw && spec.default !== undefined)
|
|
482
|
-
return spec.default;
|
|
483
|
-
const byNum = spec.options[Number.parseInt(raw, 10) - 1];
|
|
484
|
-
if (byNum)
|
|
485
|
-
return byNum;
|
|
486
|
-
if (spec.options.includes(raw))
|
|
487
|
-
return raw;
|
|
488
|
-
process.stderr.write(` pick 1-${spec.options.length}
|
|
489
|
-
`);
|
|
490
|
-
}
|
|
491
|
-
} finally {
|
|
492
|
-
rl.close();
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
async function loadRepoConfig(dir, workspace, repo, opts = {}) {
|
|
496
|
-
const file = findConfigFile(dir);
|
|
497
|
-
if (!file)
|
|
498
|
-
return null;
|
|
499
|
-
const mod = await import(pathToFileURL(file).href);
|
|
500
|
-
const config = mod.default ?? mod;
|
|
501
|
-
const asks = configAsks(config);
|
|
502
|
-
let answers;
|
|
503
|
-
if (Object.keys(asks).length) {
|
|
504
|
-
const interactive = process.stdin.isTTY && process.stderr.isTTY;
|
|
505
|
-
const resolved = await resolveAsks(asks, {
|
|
506
|
-
saved: readAnswers(dir),
|
|
507
|
-
overrides: opts.answers ?? {},
|
|
508
|
-
reask: opts.reask,
|
|
509
|
-
prompt: interactive ? (key, spec) => promptAsk(repo, key, spec) : undefined
|
|
510
|
-
});
|
|
511
|
-
answers = resolved.answers;
|
|
512
|
-
if (resolved.changed)
|
|
513
|
-
writeAnswers(dir, answers);
|
|
514
|
-
}
|
|
515
|
-
return evalConfig(config, workspace, repo, dir, answers);
|
|
516
|
-
}
|
|
517
|
-
function parseAskFlags(args) {
|
|
518
|
-
const answers = {};
|
|
519
|
-
for (let i = 0;i < args.length; i++) {
|
|
520
|
-
if (args[i] === "--mode" && args[i + 1])
|
|
521
|
-
answers.mode = args[++i];
|
|
522
|
-
else if (args[i] === "--answer" && args[i + 1]) {
|
|
523
|
-
const eq = args[++i].indexOf("=");
|
|
524
|
-
if (eq > 0)
|
|
525
|
-
answers[args[i].slice(0, eq)] = args[i].slice(eq + 1);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
return { answers, reask: args.includes("--ask") };
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// src/core/setup.ts
|
|
532
531
|
var markerPath = (dir) => join6(dir, ".ovr", "setup");
|
|
533
532
|
var stepsHash = (steps) => createHash2("sha1").update(JSON.stringify(steps)).digest("hex").slice(0, 12);
|
|
534
533
|
function provisioned(dir, hash) {
|
|
@@ -809,6 +808,11 @@ function seedServiceConfig(cfg, workspace, repo) {
|
|
|
809
808
|
}
|
|
810
809
|
}
|
|
811
810
|
}
|
|
811
|
+
return seedStarterConfig(dir);
|
|
812
|
+
}
|
|
813
|
+
function seedStarterConfig(dir) {
|
|
814
|
+
if (CONFIG_NAMES.some((f) => existsSync6(join8(dir, f))))
|
|
815
|
+
return;
|
|
812
816
|
writeFileSync7(join8(dir, "ovr.config.mts"), STARTER_CONFIG);
|
|
813
817
|
return { file: "ovr.config.mts" };
|
|
814
818
|
}
|
|
@@ -1517,9 +1521,6 @@ async function readSource(src) {
|
|
|
1517
1521
|
}
|
|
1518
1522
|
return readFileSync7(src, "utf8");
|
|
1519
1523
|
}
|
|
1520
|
-
function defaultRoot() {
|
|
1521
|
-
return join9(CONFIG_DIR, "workspaces");
|
|
1522
|
-
}
|
|
1523
1524
|
async function cmdBootstrap(args) {
|
|
1524
1525
|
const cfg = loadConfig();
|
|
1525
1526
|
let methodFlag;
|
|
@@ -2021,22 +2022,34 @@ function cmdWorkspaceCreate(args) {
|
|
|
2021
2022
|
console.log(dim(" add a repo: ovr repo add <url|org/repo> then ovr run"));
|
|
2022
2023
|
}
|
|
2023
2024
|
async function cmdInit(args) {
|
|
2024
|
-
const positional = args.filter((a) => !a.startsWith("-"));
|
|
2025
|
-
const name = positional[0] || "dev";
|
|
2026
|
-
const root = positional[1];
|
|
2027
|
-
if (!/^[a-z0-9._-]+$/i.test(name))
|
|
2028
|
-
throw new Error(`Invalid workspace name: ${name}`);
|
|
2029
|
-
await cmdBootstrap([...root ? [root] : [], ...args.filter((a) => a.startsWith("-"))]);
|
|
2030
2025
|
const cfg = loadConfig();
|
|
2031
|
-
|
|
2026
|
+
let methodFlag;
|
|
2027
|
+
const positional = [];
|
|
2028
|
+
for (let i = 0;i < args.length; i++) {
|
|
2029
|
+
const a = args[i];
|
|
2030
|
+
if (a === "--ssh")
|
|
2031
|
+
methodFlag = "ssh";
|
|
2032
|
+
else if (a === "--https")
|
|
2033
|
+
methodFlag = "https";
|
|
2034
|
+
else if (!a.startsWith("-"))
|
|
2035
|
+
positional.push(a);
|
|
2036
|
+
}
|
|
2037
|
+
if (!cfg.root) {
|
|
2038
|
+
const root = positional[0] ? resolve2(positional[0]) : defaultRoot();
|
|
2039
|
+
cfg.root = root;
|
|
2040
|
+
cfg.cloneMethod = methodFlag ?? cfg.cloneMethod ?? DEFAULT_CLONE_METHOD;
|
|
2041
|
+
mkdirSync9(root, { recursive: true });
|
|
2042
|
+
saveConfig(cfg);
|
|
2043
|
+
console.log(`✓ bootstrapped — root → ${root} · clone → ${cfg.cloneMethod}`);
|
|
2044
|
+
}
|
|
2045
|
+
const cwd = process.cwd();
|
|
2046
|
+
const existing = findConfigFile(cwd);
|
|
2047
|
+
if (existing) {
|
|
2048
|
+
console.log(`✓ already an ovr project (${basename2(existing)}) — run: ${bold("ovr run")}`);
|
|
2032
2049
|
return;
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
saveConfig(cfg);
|
|
2037
|
-
console.log(`
|
|
2038
|
-
✓ workspace '${name}' ready and active`);
|
|
2039
|
-
console.log(dim(" next: ovr repo add <url|org/repo> · ovr run"));
|
|
2050
|
+
}
|
|
2051
|
+
const seeded = seedStarterConfig(cwd);
|
|
2052
|
+
console.log(`✓ scaffolded ${green(seeded?.file ?? "ovr.config.mts")} — declare your services, then: ${bold("ovr run")}`);
|
|
2040
2053
|
}
|
|
2041
2054
|
function cmdWorkspaceInfo(args, override, inferredRepo) {
|
|
2042
2055
|
const cfg = loadConfig();
|
|
@@ -2396,7 +2409,7 @@ compdef _ovr ovr`;
|
|
|
2396
2409
|
// src/engine/run.ts
|
|
2397
2410
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
2398
2411
|
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync12 } from "node:fs";
|
|
2399
|
-
import { basename as
|
|
2412
|
+
import { basename as basename5, join as join14 } from "node:path";
|
|
2400
2413
|
|
|
2401
2414
|
// src/engine/envname.ts
|
|
2402
2415
|
import { existsSync as existsSync8, mkdirSync as mkdirSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "node:fs";
|
|
@@ -2526,7 +2539,7 @@ function createSecretRegistry(workspace, file = SECRETS_PATH) {
|
|
|
2526
2539
|
|
|
2527
2540
|
// src/engine/prepare-helpers.ts
|
|
2528
2541
|
import { existsSync as existsSync10, mkdirSync as mkdirSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync11 } from "node:fs";
|
|
2529
|
-
import { basename as
|
|
2542
|
+
import { basename as basename3, dirname as dirname6, isAbsolute, join as join13 } from "node:path";
|
|
2530
2543
|
function makeEnv(base) {
|
|
2531
2544
|
return {
|
|
2532
2545
|
get: (key, fallback) => base[key] ?? fallback,
|
|
@@ -2547,7 +2560,7 @@ function makeFiles(repoDir2, dataDir) {
|
|
|
2547
2560
|
if (typeof template === "object") {
|
|
2548
2561
|
const src = isAbsolute(template.from) ? template.from : join13(repoDir2, template.from);
|
|
2549
2562
|
text2 = readFileSync10(src, "utf8");
|
|
2550
|
-
name = opts.name ??
|
|
2563
|
+
name = opts.name ?? basename3(src).replace(/\.(tmpl|hbs|mustache|template)$/, "");
|
|
2551
2564
|
} else {
|
|
2552
2565
|
text2 = template;
|
|
2553
2566
|
}
|
|
@@ -4980,8 +4993,8 @@ function createTui(services, handlers, options = {}) {
|
|
|
4980
4993
|
|
|
4981
4994
|
// src/workspace/onboard.ts
|
|
4982
4995
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
4983
|
-
import { readFileSync as readFileSync11, realpathSync as realpathSync2 } from "node:fs";
|
|
4984
|
-
import { basename as
|
|
4996
|
+
import { mkdirSync as mkdirSync14, readFileSync as readFileSync11, realpathSync as realpathSync2 } from "node:fs";
|
|
4997
|
+
import { basename as basename4, resolve as resolve3 } from "node:path";
|
|
4985
4998
|
import * as clack3 from "@clack/prompts";
|
|
4986
4999
|
var real = (p) => {
|
|
4987
5000
|
try {
|
|
@@ -5002,7 +5015,7 @@ function inferRepoName(dir) {
|
|
|
5002
5015
|
return sanitizeName(base);
|
|
5003
5016
|
}
|
|
5004
5017
|
} catch {}
|
|
5005
|
-
return sanitizeName(
|
|
5018
|
+
return sanitizeName(basename4(dir));
|
|
5006
5019
|
}
|
|
5007
5020
|
var git2 = (dir, args) => {
|
|
5008
5021
|
try {
|
|
@@ -5039,6 +5052,10 @@ function freeName(cfg, base) {
|
|
|
5039
5052
|
return `${base}-${i}`;
|
|
5040
5053
|
}
|
|
5041
5054
|
async function autoOnboard(cfg, cwd) {
|
|
5055
|
+
if (!cfg.root) {
|
|
5056
|
+
cfg.root = defaultRoot();
|
|
5057
|
+
mkdirSync14(cfg.root, { recursive: true });
|
|
5058
|
+
}
|
|
5042
5059
|
const { repo, decl } = onboardDecl(cwd);
|
|
5043
5060
|
const interactive = Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
5044
5061
|
let target = freeName(cfg, repo);
|
|
@@ -5814,7 +5831,7 @@ function cmdLogs(args = [], override) {
|
|
|
5814
5831
|
}
|
|
5815
5832
|
for (const p of paths) {
|
|
5816
5833
|
if (paths.length > 1)
|
|
5817
|
-
process.stdout.write(dim(`── ${
|
|
5834
|
+
process.stdout.write(dim(`── ${basename5(p, ".log")} ──
|
|
5818
5835
|
`));
|
|
5819
5836
|
process.stdout.write(readFileSync12(p, "utf8"));
|
|
5820
5837
|
}
|
|
@@ -5841,8 +5858,8 @@ var tree = {
|
|
|
5841
5858
|
},
|
|
5842
5859
|
{
|
|
5843
5860
|
name: "init",
|
|
5844
|
-
summary: "
|
|
5845
|
-
usage: "ovr init [
|
|
5861
|
+
summary: "Make the current directory an ovr project (bootstrap if needed + scaffold a config)",
|
|
5862
|
+
usage: "ovr init [root] [--ssh|--https]",
|
|
5846
5863
|
run: (c) => cmdInit(c.args)
|
|
5847
5864
|
},
|
|
5848
5865
|
{
|
package/dist/core/config.d.ts
CHANGED
|
@@ -67,6 +67,12 @@ export type TeamConfig = {
|
|
|
67
67
|
};
|
|
68
68
|
export declare const CONFIG_DIR: string;
|
|
69
69
|
export declare const CONFIG_PATH: string;
|
|
70
|
+
/**
|
|
71
|
+
* The default workspaces root — holds ONLY fork worktrees (base repos run in place). Under the
|
|
72
|
+
* config folder so a fresh user owns nothing and chooses nothing. Used by the interactive
|
|
73
|
+
* bootstrap and by the silent bootstrap `init`/`run` do on first use.
|
|
74
|
+
*/
|
|
75
|
+
export declare const defaultRoot: () => string;
|
|
70
76
|
/** Empty by default — nothing exists until you add it. */
|
|
71
77
|
export declare const DEFAULT_CONFIG: GlobalConfig;
|
|
72
78
|
export declare function loadConfig(): GlobalConfig;
|
package/dist/core/core.js
CHANGED
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
// src/plugins/shell.ts
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
// src/util/ready.ts
|
|
5
|
+
import { connect } from "node:net";
|
|
6
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
7
|
+
async function waitUntil(probe, opts = {}) {
|
|
8
|
+
const timeout = opts.timeoutMs ?? 60000;
|
|
9
|
+
const interval = opts.intervalMs ?? 500;
|
|
10
|
+
const deadline = Date.now() + timeout;
|
|
11
|
+
for (;; ) {
|
|
12
|
+
try {
|
|
13
|
+
if (await probe())
|
|
14
|
+
return;
|
|
15
|
+
} catch {}
|
|
16
|
+
if (Date.now() >= deadline) {
|
|
17
|
+
throw new Error(`readiness timed out after ${timeout}ms${opts.label ? `: ${opts.label}` : ""}`);
|
|
18
|
+
}
|
|
19
|
+
await sleep(interval);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function waitHttp(url, opts = {}) {
|
|
23
|
+
return waitUntil(async () => {
|
|
24
|
+
const res = await fetch(url, { redirect: "manual" }).catch(() => null);
|
|
25
|
+
if (!res)
|
|
26
|
+
return false;
|
|
27
|
+
return opts.status ? res.status === opts.status : res.status < 500;
|
|
28
|
+
}, { label: `GET ${url}`, ...opts });
|
|
29
|
+
}
|
|
30
|
+
function waitTcp(port, opts = {}) {
|
|
31
|
+
const host = opts.host ?? "127.0.0.1";
|
|
32
|
+
return waitUntil(() => new Promise((resolve) => {
|
|
33
|
+
const sock = connect({ host, port });
|
|
34
|
+
const done = (ok) => {
|
|
35
|
+
sock.destroy();
|
|
36
|
+
resolve(ok);
|
|
37
|
+
};
|
|
38
|
+
sock.once("connect", () => done(true));
|
|
39
|
+
sock.once("error", () => done(false));
|
|
40
|
+
sock.setTimeout(2000, () => done(false));
|
|
41
|
+
}), { label: `tcp ${host}:${port}`, ...opts });
|
|
42
|
+
}
|
|
43
|
+
// src/core/config.ts
|
|
44
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
45
|
+
import { homedir } from "node:os";
|
|
46
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
47
|
+
var CONFIG_DIR = process.env.OVR_CONFIG_DIR || join(homedir(), ".config", "ovr");
|
|
48
|
+
var CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
49
|
+
var defaultRoot = () => join(CONFIG_DIR, "workspaces");
|
|
50
|
+
var DEFAULT_CONFIG = { workspaces: {} };
|
|
51
|
+
function rename(obj, from, to) {
|
|
52
|
+
if (obj && from in obj) {
|
|
53
|
+
obj[to] = obj[from];
|
|
54
|
+
delete obj[from];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function mapKeys(cfg, kebabToCamel) {
|
|
58
|
+
const swap = (o, kebab, camel) => kebabToCamel ? rename(o, kebab, camel) : rename(o, camel, kebab);
|
|
59
|
+
swap(cfg, "clone-method", "cloneMethod");
|
|
60
|
+
swap(cfg, "infer-from-cwd", "inferFromCwd");
|
|
61
|
+
swap(cfg, "team-config", "teamConfig");
|
|
62
|
+
const tui = cfg.tui;
|
|
63
|
+
swap(tui, "tab-width", "tabWidth");
|
|
64
|
+
swap(tui, "buffer-lines", "bufferLines");
|
|
65
|
+
swap(tui, "history-lines", "historyLines");
|
|
66
|
+
swap(tui, "throttle-ms", "throttleMs");
|
|
67
|
+
swap(cfg.infisical, "project-id", "projectId");
|
|
68
|
+
const wss = cfg.workspaces ?? cfg.projects;
|
|
69
|
+
for (const p of Object.values(wss ?? {})) {
|
|
70
|
+
swap(p?.infisical, "project-id", "projectId");
|
|
71
|
+
}
|
|
72
|
+
return cfg;
|
|
73
|
+
}
|
|
74
|
+
function loadConfig() {
|
|
75
|
+
if (!existsSync(CONFIG_PATH))
|
|
76
|
+
return structuredClone(DEFAULT_CONFIG);
|
|
77
|
+
const raw = mapKeys(JSON.parse(readFileSync(CONFIG_PATH, "utf8")), true);
|
|
78
|
+
const { projects, ...rest } = raw;
|
|
79
|
+
return { ...DEFAULT_CONFIG, ...rest, workspaces: raw.workspaces ?? projects ?? {} };
|
|
80
|
+
}
|
|
81
|
+
function saveConfig(cfg) {
|
|
82
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
83
|
+
const onDisk = mapKeys(structuredClone(cfg), false);
|
|
84
|
+
writeFileSync(CONFIG_PATH, `${JSON.stringify(onDisk, null, 2)}
|
|
85
|
+
`);
|
|
86
|
+
}
|
|
87
|
+
function serializeTeam(cfg) {
|
|
88
|
+
const slice = { workspaces: cfg.workspaces };
|
|
89
|
+
if (cfg.infisical)
|
|
90
|
+
slice.infisical = cfg.infisical;
|
|
91
|
+
return `${JSON.stringify(mapKeys(structuredClone(slice), false), null, 2)}
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
function parseTeam(text) {
|
|
95
|
+
const raw = mapKeys(JSON.parse(text), true);
|
|
96
|
+
return {
|
|
97
|
+
infisical: raw.infisical,
|
|
98
|
+
workspaces: raw.workspaces
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function mergeTeam(cfg, team) {
|
|
102
|
+
if (team.infisical)
|
|
103
|
+
cfg.infisical = { ...cfg.infisical, ...team.infisical };
|
|
104
|
+
const added = [];
|
|
105
|
+
const updated = [];
|
|
106
|
+
const conflicts = [];
|
|
107
|
+
for (const [name, incoming] of Object.entries(team.workspaces ?? {})) {
|
|
108
|
+
const local = cfg.workspaces[name];
|
|
109
|
+
if (!local) {
|
|
110
|
+
cfg.workspaces[name] = incoming;
|
|
111
|
+
added.push(name);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
updated.push(name);
|
|
115
|
+
const localRepos = local.repos ?? {};
|
|
116
|
+
const repos = { ...localRepos };
|
|
117
|
+
for (const [r, decl] of Object.entries(incoming.repos ?? {})) {
|
|
118
|
+
if (!localRepos[r])
|
|
119
|
+
repos[r] = decl;
|
|
120
|
+
else if (JSON.stringify(localRepos[r]) !== JSON.stringify(decl)) {
|
|
121
|
+
conflicts.push({ workspace: name, repo: r, local: localRepos[r], incoming: decl });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const merged = { ...local, ...incoming };
|
|
125
|
+
if (Object.keys(repos).length)
|
|
126
|
+
merged.repos = repos;
|
|
127
|
+
else
|
|
128
|
+
delete merged.repos;
|
|
129
|
+
if (local.infisical || incoming.infisical) {
|
|
130
|
+
merged.infisical = { ...local.infisical, ...incoming.infisical };
|
|
131
|
+
}
|
|
132
|
+
cfg.workspaces[name] = merged;
|
|
133
|
+
}
|
|
134
|
+
return { added, updated, conflicts };
|
|
135
|
+
}
|
|
136
|
+
function getWorkspace(cfg, name) {
|
|
137
|
+
const p = cfg.workspaces[name];
|
|
138
|
+
if (!p) {
|
|
139
|
+
throw new Error(`Unknown workspace '${name}'. Known: ${Object.keys(cfg.workspaces).join(", ") || "(none)"}`);
|
|
140
|
+
}
|
|
141
|
+
return p;
|
|
142
|
+
}
|
|
143
|
+
var activeName = (cfg, override) => override || cfg.active;
|
|
144
|
+
var DEFAULT_BRANCH = "main";
|
|
145
|
+
var DEFAULT_HOST = "github.com";
|
|
146
|
+
var DEFAULT_CLONE_METHOD = "ssh";
|
|
147
|
+
function normalizeRemote(remote, defaultOrg) {
|
|
148
|
+
if (/^(git@|ssh:\/\/|https?:\/\/)/.test(remote))
|
|
149
|
+
return remote;
|
|
150
|
+
if (remote.includes("/"))
|
|
151
|
+
return remote;
|
|
152
|
+
return defaultOrg ? `${defaultOrg}/${remote}` : remote;
|
|
153
|
+
}
|
|
154
|
+
function repoUrl(remote, method = DEFAULT_CLONE_METHOD) {
|
|
155
|
+
if (/^(git@|ssh:\/\/|https?:\/\/|file:\/\/)/.test(remote) || /^[/.~]/.test(remote))
|
|
156
|
+
return remote;
|
|
157
|
+
const parts = remote.replace(/\.git$/, "").split("/");
|
|
158
|
+
const host = parts.length >= 3 ? parts[0] : DEFAULT_HOST;
|
|
159
|
+
const slug = (parts.length >= 3 ? parts.slice(1) : parts).join("/");
|
|
160
|
+
return method === "ssh" ? `git@${host}:${slug}.git` : `https://${host}/${slug}.git`;
|
|
161
|
+
}
|
|
162
|
+
function resolveRepoPath(p) {
|
|
163
|
+
if (p === "~" || p.startsWith("~/"))
|
|
164
|
+
return resolve(join(homedir(), p.slice(1)));
|
|
165
|
+
return resolve(p);
|
|
166
|
+
}
|
|
167
|
+
function forkChain(cfg, name) {
|
|
168
|
+
const chain = [];
|
|
169
|
+
const seen = new Set;
|
|
170
|
+
let cur = name;
|
|
171
|
+
while (cur && !seen.has(cur)) {
|
|
172
|
+
seen.add(cur);
|
|
173
|
+
getWorkspace(cfg, cur);
|
|
174
|
+
chain.push(cur);
|
|
175
|
+
cur = cfg.workspaces[cur].base;
|
|
176
|
+
}
|
|
177
|
+
return chain.reverse();
|
|
178
|
+
}
|
|
179
|
+
var workspaceOrder = (cfg) => (a, b) => (cfg.workspaces[a].protected ? 0 : 1) - (cfg.workspaces[b].protected ? 0 : 1) || a.localeCompare(b);
|
|
180
|
+
function forkTreeOrder(cfg) {
|
|
181
|
+
const names = Object.keys(cfg.workspaces);
|
|
182
|
+
const cmp = workspaceOrder(cfg);
|
|
183
|
+
const childrenOf = (parent) => names.filter((n) => cfg.workspaces[n].base === parent).sort(cmp);
|
|
184
|
+
const roots = names.filter((n) => !cfg.workspaces[n].base || !cfg.workspaces[cfg.workspaces[n].base]).sort(cmp);
|
|
185
|
+
const out = [];
|
|
186
|
+
const seen = new Set;
|
|
187
|
+
const walk = (name) => {
|
|
188
|
+
if (seen.has(name))
|
|
189
|
+
return;
|
|
190
|
+
seen.add(name);
|
|
191
|
+
out.push(name);
|
|
192
|
+
for (const k of childrenOf(name))
|
|
193
|
+
walk(k);
|
|
194
|
+
};
|
|
195
|
+
roots.forEach(walk);
|
|
196
|
+
names.sort(cmp).forEach(walk);
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
function resolveRepos(cfg, name) {
|
|
200
|
+
const out = {};
|
|
201
|
+
for (const proj of forkChain(cfg, name)) {
|
|
202
|
+
Object.assign(out, cfg.workspaces[proj].repos ?? {});
|
|
203
|
+
}
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
function nearest(cfg, name, pick) {
|
|
207
|
+
for (const proj of forkChain(cfg, name).reverse()) {
|
|
208
|
+
const v = pick(cfg.workspaces[proj]);
|
|
209
|
+
if (v !== undefined)
|
|
210
|
+
return v;
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
var resolveBranch = (cfg, name) => nearest(cfg, name, (p) => p.branch) ?? DEFAULT_BRANCH;
|
|
215
|
+
function resolveInfisical(cfg, name) {
|
|
216
|
+
let out = { ...cfg.infisical ?? {} };
|
|
217
|
+
for (const proj of forkChain(cfg, name)) {
|
|
218
|
+
if (cfg.workspaces[proj].infisical)
|
|
219
|
+
out = { ...out, ...cfg.workspaces[proj].infisical };
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
var resolveEnvSlug = (cfg, name) => resolveInfisical(cfg, name).env;
|
|
224
|
+
var repoBranch = (cfg, workspace, decl) => decl.branch ?? resolveBranch(cfg, workspace);
|
|
225
|
+
function inferContext(cfg, cwd = process.cwd()) {
|
|
226
|
+
if (cfg.inferFromCwd === false || !cfg.root)
|
|
227
|
+
return {};
|
|
228
|
+
const real = (p) => {
|
|
229
|
+
try {
|
|
230
|
+
return realpathSync(p);
|
|
231
|
+
} catch {
|
|
232
|
+
return resolve(p);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
const rel = relative(real(cfg.root), real(cwd));
|
|
236
|
+
if (!rel || rel.startsWith("..") || isAbsolute(rel))
|
|
237
|
+
return {};
|
|
238
|
+
const parts = rel.split(sep);
|
|
239
|
+
const workspace = cfg.workspaces[parts[0]] ? parts[0] : undefined;
|
|
240
|
+
const repo = workspace && parts[1] ? parts[1] : undefined;
|
|
241
|
+
return { workspace, repo };
|
|
242
|
+
}
|
|
243
|
+
// src/util/random.ts
|
|
244
|
+
import { randomBytes } from "node:crypto";
|
|
245
|
+
var ALNUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
246
|
+
var SYMBOLS = "!#$%*+-_=?@.";
|
|
247
|
+
function randomString(length = 24, alphabet = ALNUM) {
|
|
248
|
+
if (length <= 0 || alphabet.length === 0)
|
|
249
|
+
return "";
|
|
250
|
+
const max = Math.floor(256 / alphabet.length) * alphabet.length;
|
|
251
|
+
let out = "";
|
|
252
|
+
while (out.length < length) {
|
|
253
|
+
for (const byte of randomBytes(length * 2)) {
|
|
254
|
+
if (byte >= max)
|
|
255
|
+
continue;
|
|
256
|
+
out += alphabet[byte % alphabet.length];
|
|
257
|
+
if (out.length === length)
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
263
|
+
function randomPassword(length = 24, opts = {}) {
|
|
264
|
+
return randomString(length, opts.symbols === false ? ALNUM : ALNUM + SYMBOLS);
|
|
265
|
+
}
|
|
266
|
+
function randomHex(bytes = 24) {
|
|
267
|
+
return randomBytes(bytes).toString("hex");
|
|
268
|
+
}
|
|
269
|
+
// src/util/dotenv.ts
|
|
270
|
+
function parseDotenv(text) {
|
|
271
|
+
const out = {};
|
|
272
|
+
for (const line of text.split(`
|
|
273
|
+
`)) {
|
|
274
|
+
if (line.trimStart().startsWith("#"))
|
|
275
|
+
continue;
|
|
276
|
+
const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
|
|
277
|
+
if (!m)
|
|
278
|
+
continue;
|
|
279
|
+
let v = m[2].trim();
|
|
280
|
+
if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'"))
|
|
281
|
+
v = v.slice(1, -1);
|
|
282
|
+
out[m[1]] = v;
|
|
283
|
+
}
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
// src/env/dotenv-plugin.ts
|
|
287
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
288
|
+
import { join as join2 } from "node:path";
|
|
289
|
+
function dotenv(opts = {}) {
|
|
290
|
+
return {
|
|
291
|
+
name: "dotenv",
|
|
292
|
+
envSource: {
|
|
293
|
+
name: "dotenv",
|
|
294
|
+
load({ dir, env }) {
|
|
295
|
+
const files = opts.files ?? [".env", ".env.local", ...env ? [`.env.${env}`, `.env.${env}.local`] : []];
|
|
296
|
+
let out = {};
|
|
297
|
+
for (const f of files) {
|
|
298
|
+
const p = join2(dir, f);
|
|
299
|
+
if (existsSync2(p))
|
|
300
|
+
out = { ...out, ...parseDotenv(readFileSync2(p, "utf8")) };
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/core/core.ts
|
|
309
|
+
var REF = Symbol.for("ovr.ref");
|
|
310
|
+
var isRef = (v) => typeof v === "object" && v !== null && (REF in v);
|
|
311
|
+
var refTarget = (r) => r[REF];
|
|
312
|
+
var makeRef = (repo, service, key) => ({ [REF]: { repo, service, key } });
|
|
313
|
+
function collectRefTargets(spec, out = []) {
|
|
314
|
+
if (isRef(spec)) {
|
|
315
|
+
out.push(refTarget(spec));
|
|
316
|
+
return out;
|
|
317
|
+
}
|
|
318
|
+
if (Array.isArray(spec)) {
|
|
319
|
+
for (const v of spec)
|
|
320
|
+
collectRefTargets(v, out);
|
|
321
|
+
return out;
|
|
322
|
+
}
|
|
323
|
+
if (spec && typeof spec === "object") {
|
|
324
|
+
for (const v of Object.values(spec))
|
|
325
|
+
collectRefTargets(v, out);
|
|
326
|
+
}
|
|
327
|
+
return out;
|
|
328
|
+
}
|
|
329
|
+
function resolveRefs(spec, resolve2) {
|
|
330
|
+
if (isRef(spec))
|
|
331
|
+
return resolve2(refTarget(spec));
|
|
332
|
+
if (Array.isArray(spec))
|
|
333
|
+
return spec.map((v) => resolveRefs(v, resolve2));
|
|
334
|
+
if (spec && typeof spec === "object") {
|
|
335
|
+
const out = {};
|
|
336
|
+
for (const [k, v] of Object.entries(spec)) {
|
|
337
|
+
if (isRef(v)) {
|
|
338
|
+
const r = resolve2(refTarget(v));
|
|
339
|
+
if (r !== undefined)
|
|
340
|
+
out[k] = r;
|
|
341
|
+
} else {
|
|
342
|
+
out[k] = resolveRefs(v, resolve2);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return out;
|
|
346
|
+
}
|
|
347
|
+
return spec;
|
|
348
|
+
}
|
|
349
|
+
function coerceEnv(obj) {
|
|
350
|
+
const out = {};
|
|
351
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
352
|
+
if (v === undefined || v === null)
|
|
353
|
+
continue;
|
|
354
|
+
out[k] = typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
355
|
+
}
|
|
356
|
+
return out;
|
|
357
|
+
}
|
|
358
|
+
var sym = (p) => typeof p === "symbol";
|
|
359
|
+
function buildWorkspace(name, ownRepo, leaf) {
|
|
360
|
+
const serviceNode = (repo, s) => new Proxy({ [REF]: { repo, service: s, key: "" } }, {
|
|
361
|
+
get: (t, k) => {
|
|
362
|
+
if (k === REF)
|
|
363
|
+
return t[REF];
|
|
364
|
+
if (k === "announce")
|
|
365
|
+
return (payload) => ({ to: `${repo}.${s}`, payload });
|
|
366
|
+
return sym(k) ? undefined : leaf(repo, s, k);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
const services = (repo) => new Proxy({}, { get: (_t, s) => sym(s) ? undefined : serviceNode(repo, s) });
|
|
370
|
+
const repos = new Proxy({}, { get: (_t, r) => sym(r) ? undefined : { services: services(r) } });
|
|
371
|
+
return { name, services: services(ownRepo), repos };
|
|
372
|
+
}
|
|
373
|
+
function makeContext(workspaceName, repo) {
|
|
374
|
+
return {
|
|
375
|
+
workspace: buildWorkspace(workspaceName, repo, (r, s, k) => makeRef(r, s, k)),
|
|
376
|
+
kind: buildKinds([])
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function resolvingWorkspace(name, ownRepo, resolve2) {
|
|
380
|
+
return buildWorkspace(name, ownRepo, (repo, service, key) => resolve2({ repo, service, key }));
|
|
381
|
+
}
|
|
382
|
+
function recordingWorkspace(name, ownRepo) {
|
|
383
|
+
const rec = [];
|
|
384
|
+
const workspace = buildWorkspace(name, ownRepo, (repo, service, key) => {
|
|
385
|
+
rec.push({ repo, service, key });
|
|
386
|
+
return "";
|
|
387
|
+
});
|
|
388
|
+
return { workspace, taken: () => rec };
|
|
389
|
+
}
|
|
390
|
+
var input = {
|
|
391
|
+
text: (label, opts = {}) => ({
|
|
392
|
+
kind: "text",
|
|
393
|
+
label,
|
|
394
|
+
...opts
|
|
395
|
+
}),
|
|
396
|
+
choice: (label, options, opts = {}) => ({
|
|
397
|
+
kind: "choice",
|
|
398
|
+
label,
|
|
399
|
+
options,
|
|
400
|
+
...opts
|
|
401
|
+
}),
|
|
402
|
+
confirm: (label, opts = {}) => ({
|
|
403
|
+
kind: "confirm",
|
|
404
|
+
label,
|
|
405
|
+
...opts
|
|
406
|
+
})
|
|
407
|
+
};
|
|
408
|
+
var ask = {
|
|
409
|
+
choice: (question, options, opts = {}) => ({
|
|
410
|
+
kind: "choice",
|
|
411
|
+
question,
|
|
412
|
+
options,
|
|
413
|
+
...opts
|
|
414
|
+
}),
|
|
415
|
+
confirm: (question, opts = {}) => ({
|
|
416
|
+
kind: "confirm",
|
|
417
|
+
question,
|
|
418
|
+
...opts
|
|
419
|
+
})
|
|
420
|
+
};
|
|
421
|
+
function applyAskDefaults(asks, answers, repo) {
|
|
422
|
+
const out = { ...answers };
|
|
423
|
+
for (const [key, spec] of Object.entries(asks)) {
|
|
424
|
+
if (out[key] !== undefined)
|
|
425
|
+
continue;
|
|
426
|
+
if (spec.default !== undefined) {
|
|
427
|
+
out[key] = spec.default;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const hint = spec.kind === "choice" ? spec.options.join("|") : "true|false";
|
|
431
|
+
throw new Error(`${repo}: '${key}' needs an answer — pass --answer ${key}=<${hint}> (or run interactively)`);
|
|
432
|
+
}
|
|
433
|
+
return out;
|
|
434
|
+
}
|
|
435
|
+
var FACTORY = Symbol.for("ovr.factory");
|
|
436
|
+
var HEAD = Symbol.for("ovr.head");
|
|
437
|
+
var configAsks = (mod) => isConfigModule(mod) ? mod[HEAD]?.asks ?? {} : {};
|
|
438
|
+
function buildKinds(plugins) {
|
|
439
|
+
const kind = { shell };
|
|
440
|
+
for (const p of plugins)
|
|
441
|
+
if (p.kinds)
|
|
442
|
+
kind[p.name] = p.kinds;
|
|
443
|
+
return kind;
|
|
444
|
+
}
|
|
445
|
+
function buildActions(plugins) {
|
|
446
|
+
const out = { ...action };
|
|
447
|
+
for (const p of plugins)
|
|
448
|
+
if (p.actionKinds)
|
|
449
|
+
out[p.name] = p.actionKinds;
|
|
450
|
+
return out;
|
|
451
|
+
}
|
|
452
|
+
function defineConfig(a, b) {
|
|
453
|
+
if (b) {
|
|
454
|
+
const head = a;
|
|
455
|
+
const asks = { ...head.asks ?? {} };
|
|
456
|
+
if (head.modes?.length) {
|
|
457
|
+
asks.mode = {
|
|
458
|
+
kind: "choice",
|
|
459
|
+
question: "How should services run?",
|
|
460
|
+
options: head.modes,
|
|
461
|
+
...head.defaultMode !== undefined ? { default: head.defaultMode } : {}
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
[HEAD]: { asks },
|
|
466
|
+
[FACTORY]: ({ workspace, repo, dir, answers }) => {
|
|
467
|
+
const plugins = typeof head.plugins === "function" ? head.plugins({ workspace: { name: workspace }, repo, dir }) : head.plugins ?? [];
|
|
468
|
+
const resolved = applyAskDefaults(asks, answers ?? {}, repo);
|
|
469
|
+
const { workspace: proj } = makeContext(workspace, repo);
|
|
470
|
+
const body = b({
|
|
471
|
+
workspace: proj,
|
|
472
|
+
kind: buildKinds(plugins),
|
|
473
|
+
action: buildActions(plugins),
|
|
474
|
+
mode: resolved.mode ?? "default",
|
|
475
|
+
answers: resolved
|
|
476
|
+
});
|
|
477
|
+
return { ...body, plugins: [...plugins] };
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
const input2 = a;
|
|
482
|
+
const factory = typeof input2 === "function" ? input2 : () => input2;
|
|
483
|
+
return { [FACTORY]: ({ workspace, repo }) => factory(makeContext(workspace, repo)) };
|
|
484
|
+
}
|
|
485
|
+
var isConfigModule = (v) => typeof v === "object" && v !== null && (FACTORY in v);
|
|
486
|
+
function evalConfig(mod, workspace, repo, dir = "", answers) {
|
|
487
|
+
if (isConfigModule(mod))
|
|
488
|
+
return mod[FACTORY]({ workspace, repo, dir, answers });
|
|
489
|
+
const isPlain = mod && typeof mod === "object" && (("services" in mod) || ("setup" in mod));
|
|
490
|
+
const factory = typeof mod === "function" ? mod : isPlain ? () => ({ services: {}, ...mod }) : null;
|
|
491
|
+
if (!factory)
|
|
492
|
+
throw new Error("config default export must be defineConfig(...) or { services: … }");
|
|
493
|
+
return factory(makeContext(workspace, repo));
|
|
494
|
+
}
|
|
495
|
+
var action = {
|
|
496
|
+
session: (spec) => ({ mode: "session", ...spec }),
|
|
497
|
+
fn: (spec) => ({ mode: "function", ...spec }),
|
|
498
|
+
handover: (spec) => ({ mode: "handover", ...spec })
|
|
499
|
+
};
|
|
500
|
+
async function serviceRefs(def, tools) {
|
|
501
|
+
return def.plugin.refs ? await def.plugin.refs(def.spec, tools) : collectRefTargets(def.spec);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/engine/localbin.ts
|
|
505
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
506
|
+
import { delimiter, join as join3 } from "node:path";
|
|
507
|
+
var LOCAL_BIN_DIRS = [["node_modules", ".bin"]];
|
|
508
|
+
function withLocalBins(dir, env) {
|
|
509
|
+
const bins = LOCAL_BIN_DIRS.map((p) => join3(dir, ...p)).filter(existsSync3);
|
|
510
|
+
if (!bins.length)
|
|
511
|
+
return env;
|
|
512
|
+
return { ...env, PATH: [...bins, env.PATH ?? ""].join(delimiter) };
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// src/plugins/shell.ts
|
|
516
|
+
var placeholderBag = () => new Proxy(() => {
|
|
517
|
+
return;
|
|
518
|
+
}, {
|
|
519
|
+
get: (_t, p) => p === Symbol.toPrimitive ? () => "" : placeholderBag(),
|
|
520
|
+
apply: () => placeholderBag()
|
|
521
|
+
});
|
|
522
|
+
function spawnArgs(cmd) {
|
|
523
|
+
if (Array.isArray(cmd))
|
|
524
|
+
return { command: cmd[0], args: cmd.slice(1), shell: false };
|
|
525
|
+
return { command: cmd, args: [], shell: true };
|
|
526
|
+
}
|
|
527
|
+
var shellPlugin = {
|
|
528
|
+
name: "shell",
|
|
529
|
+
status(handle) {
|
|
530
|
+
const pid = handle?.pid;
|
|
531
|
+
if (!pid)
|
|
532
|
+
return "down";
|
|
533
|
+
try {
|
|
534
|
+
process.kill(pid, 0);
|
|
535
|
+
return "up";
|
|
536
|
+
} catch (e) {
|
|
537
|
+
return e.code === "EPERM" ? "up" : "down";
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
stop(handle) {
|
|
541
|
+
const pid = handle?.pid;
|
|
542
|
+
if (!pid)
|
|
543
|
+
return;
|
|
544
|
+
const sig = (s) => {
|
|
545
|
+
try {
|
|
546
|
+
process.kill(-pid, s);
|
|
547
|
+
} catch {
|
|
548
|
+
try {
|
|
549
|
+
process.kill(pid, s);
|
|
550
|
+
} catch {}
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
sig("SIGTERM");
|
|
554
|
+
const t = setTimeout(() => sig("SIGKILL"), 3000);
|
|
555
|
+
t.unref?.();
|
|
556
|
+
},
|
|
557
|
+
refs(spec) {
|
|
558
|
+
const env = typeof spec.env === "function" ? spec.env(placeholderBag()) : spec.env ?? {};
|
|
559
|
+
return collectRefTargets(env);
|
|
560
|
+
},
|
|
561
|
+
async plan(spec, ctx) {
|
|
562
|
+
const bag = spec.prepare ? await spec.prepare({
|
|
563
|
+
workspace: ctx.workspace,
|
|
564
|
+
scope: ctx.scope,
|
|
565
|
+
ports: { alloc: ctx.allocPort },
|
|
566
|
+
secrets: { get: ctx.secret },
|
|
567
|
+
paths: { get: ctx.path },
|
|
568
|
+
...ctx.caps
|
|
569
|
+
}) : undefined;
|
|
570
|
+
const exp = spec.exports?.(bag) ?? {};
|
|
571
|
+
const clean = {};
|
|
572
|
+
for (const [k, v] of Object.entries(exp)) {
|
|
573
|
+
if (v !== undefined && !JSON.stringify(v).includes(ctx.pending))
|
|
574
|
+
clean[k] = v;
|
|
575
|
+
}
|
|
576
|
+
return clean;
|
|
577
|
+
},
|
|
578
|
+
async start(spec, ctx) {
|
|
579
|
+
const bag = ctx.bag;
|
|
580
|
+
const cmd = typeof spec.command === "function" ? spec.command(bag) : spec.command;
|
|
581
|
+
const { command, args, shell: useShell } = spawnArgs(cmd);
|
|
582
|
+
const rawEnv = typeof spec.env === "function" ? spec.env(bag) : spec.env ?? {};
|
|
583
|
+
const ownEnv = coerceEnv(resolveRefs(rawEnv, ctx.resolve));
|
|
584
|
+
const env = withLocalBins(ctx.scope.dir, { ...ctx.env, ...ownEnv });
|
|
585
|
+
const child = spawn(command, args, {
|
|
586
|
+
cwd: ctx.scope.dir,
|
|
587
|
+
env,
|
|
588
|
+
shell: useShell,
|
|
589
|
+
detached: true,
|
|
590
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
591
|
+
});
|
|
592
|
+
ctx.pipe(child.stdout);
|
|
593
|
+
ctx.pipe(child.stderr);
|
|
594
|
+
let killTimer = null;
|
|
595
|
+
const killGroup = (sig) => {
|
|
596
|
+
try {
|
|
597
|
+
if (child.pid)
|
|
598
|
+
process.kill(-child.pid, sig);
|
|
599
|
+
} catch {
|
|
600
|
+
try {
|
|
601
|
+
child.kill(sig);
|
|
602
|
+
} catch {}
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
child.on("exit", () => {
|
|
606
|
+
if (killTimer)
|
|
607
|
+
clearTimeout(killTimer);
|
|
608
|
+
});
|
|
609
|
+
const exports = spec.exports?.(bag) ?? {};
|
|
610
|
+
ctx.log(`▶ ${Array.isArray(cmd) ? cmd.join(" ") : cmd}${exports.url ? ` (${exports.url})` : ""}`);
|
|
611
|
+
const exited = new Promise((resolve2) => child.on("exit", () => resolve2()));
|
|
612
|
+
const ready = (spec.ready ? spec.ready(bag) : Promise.resolve()).then(() => spec.onReady?.(bag));
|
|
613
|
+
const actions = [
|
|
614
|
+
{
|
|
615
|
+
id: "shell",
|
|
616
|
+
label: "shell here (service env)",
|
|
617
|
+
command: process.env.SHELL || "/bin/sh",
|
|
618
|
+
cwd: ctx.scope.dir,
|
|
619
|
+
env
|
|
620
|
+
}
|
|
621
|
+
];
|
|
622
|
+
return {
|
|
623
|
+
exports,
|
|
624
|
+
info: exports.url != null ? String(exports.url) : exports.port != null ? `:${exports.port}` : undefined,
|
|
625
|
+
actions,
|
|
626
|
+
ready,
|
|
627
|
+
exited,
|
|
628
|
+
handle: { pid: child.pid ?? 0 },
|
|
629
|
+
stop: () => {
|
|
630
|
+
killGroup("SIGTERM");
|
|
631
|
+
killTimer = setTimeout(() => killGroup("SIGKILL"), 3000);
|
|
632
|
+
killTimer.unref?.();
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
var shell = (spec) => ({ plugin: shellPlugin, spec });
|
|
638
|
+
|
|
639
|
+
export { CONFIG_DIR, CONFIG_PATH, defaultRoot, loadConfig, saveConfig, serializeTeam, parseTeam, mergeTeam, getWorkspace, activeName, DEFAULT_CLONE_METHOD, normalizeRemote, repoUrl, resolveRepoPath, forkChain, workspaceOrder, forkTreeOrder, resolveRepos, resolveBranch, resolveInfisical, resolveEnvSlug, repoBranch, inferContext, withLocalBins, shellPlugin, shell, waitUntil, waitHttp, waitTcp, randomString, randomPassword, randomHex, parseDotenv, dotenv, isRef, refTarget, collectRefTargets, resolveRefs, coerceEnv, makeContext, resolvingWorkspace, recordingWorkspace, input, ask, applyAskDefaults, configAsks, defineConfig, isConfigModule, evalConfig, action, serviceRefs };
|
package/dist/plugins/shell.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ovrdev/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.17",
|
|
4
4
|
"description": "Override — the dev control plane: environments, repos, services, tasks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,6 +21,13 @@
|
|
|
21
21
|
},
|
|
22
22
|
"./package.json": "./package.json"
|
|
23
23
|
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "bun build src/cli/index.ts src/core/core.ts src/plugins/shell.ts --root src --outdir=dist --target=node --splitting --format=esm --packages=external && tsc -p tsconfig.build.json && node scripts/fix-dts.mjs && bun build packages/plugin-portless/src/index.ts --outdir=packages/plugin-portless/dist --target=node --format=esm --packages=external && tsc -p packages/plugin-portless/tsconfig.build.json && node scripts/fix-dts.mjs packages/plugin-portless/dist && bun build packages/plugin-docker/src/index.ts --outdir=packages/plugin-docker/dist --target=node --format=esm --packages=external && tsc -p packages/plugin-docker/tsconfig.build.json && node scripts/fix-dts.mjs packages/plugin-docker/dist && bun build packages/plugin-infisical/src/index.ts --outdir=packages/plugin-infisical/dist --target=node --format=esm --packages=external && tsc -p packages/plugin-infisical/tsconfig.build.json && node scripts/fix-dts.mjs packages/plugin-infisical/dist && bun build packages/plugin-js/src/index.ts --outdir=packages/plugin-js/dist --target=node --format=esm --packages=external && tsc -p packages/plugin-js/tsconfig.build.json && node scripts/fix-dts.mjs packages/plugin-js/dist && bun build packages/plugin-postgres/src/index.ts --outdir=packages/plugin-postgres/dist --target=node --format=esm --packages=external && bun build packages/plugin-postgres/src/sql-runner.ts --outdir=packages/plugin-postgres/dist --target=node --format=esm && tsc -p packages/plugin-postgres/tsconfig.build.json && node scripts/fix-dts.mjs packages/plugin-postgres/dist && bun build packages/plugin-supabase/src/index.ts --outdir=packages/plugin-supabase/dist --target=node --format=esm --packages=external && tsc -p packages/plugin-supabase/tsconfig.build.json && node scripts/fix-dts.mjs packages/plugin-supabase/dist",
|
|
26
|
+
"dev": "bun build src/cli/index.ts src/core/core.ts src/plugins/shell.ts --root src --outdir=dist --target=node --splitting --format=esm --packages=external --watch",
|
|
27
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
28
|
+
"prepare": "bun run build",
|
|
29
|
+
"test": "bun run build && bun test"
|
|
30
|
+
},
|
|
24
31
|
"engines": {
|
|
25
32
|
"node": ">=22.6"
|
|
26
33
|
},
|
|
@@ -35,11 +42,5 @@
|
|
|
35
42
|
"@types/node": "^22.10.0",
|
|
36
43
|
"@types/react": "^19.2.17",
|
|
37
44
|
"typescript": "^5.7.2"
|
|
38
|
-
},
|
|
39
|
-
"scripts": {
|
|
40
|
-
"build": "bun build src/cli/index.ts src/core/core.ts src/plugins/shell.ts --root src --outdir=dist --target=node --splitting --format=esm --packages=external && tsc -p tsconfig.build.json && node scripts/fix-dts.mjs && bun build packages/plugin-portless/src/index.ts --outdir=packages/plugin-portless/dist --target=node --format=esm --packages=external && tsc -p packages/plugin-portless/tsconfig.build.json && node scripts/fix-dts.mjs packages/plugin-portless/dist && bun build packages/plugin-docker/src/index.ts --outdir=packages/plugin-docker/dist --target=node --format=esm --packages=external && tsc -p packages/plugin-docker/tsconfig.build.json && node scripts/fix-dts.mjs packages/plugin-docker/dist && bun build packages/plugin-infisical/src/index.ts --outdir=packages/plugin-infisical/dist --target=node --format=esm --packages=external && tsc -p packages/plugin-infisical/tsconfig.build.json && node scripts/fix-dts.mjs packages/plugin-infisical/dist && bun build packages/plugin-js/src/index.ts --outdir=packages/plugin-js/dist --target=node --format=esm --packages=external && tsc -p packages/plugin-js/tsconfig.build.json && node scripts/fix-dts.mjs packages/plugin-js/dist && bun build packages/plugin-postgres/src/index.ts --outdir=packages/plugin-postgres/dist --target=node --format=esm --packages=external && bun build packages/plugin-postgres/src/sql-runner.ts --outdir=packages/plugin-postgres/dist --target=node --format=esm && tsc -p packages/plugin-postgres/tsconfig.build.json && node scripts/fix-dts.mjs packages/plugin-postgres/dist && bun build packages/plugin-supabase/src/index.ts --outdir=packages/plugin-supabase/dist --target=node --format=esm --packages=external && tsc -p packages/plugin-supabase/tsconfig.build.json && node scripts/fix-dts.mjs packages/plugin-supabase/dist",
|
|
41
|
-
"dev": "bun build src/cli/index.ts src/core/core.ts src/plugins/shell.ts --root src --outdir=dist --target=node --splitting --format=esm --packages=external --watch",
|
|
42
|
-
"postinstall": "node scripts/postinstall.mjs",
|
|
43
|
-
"test": "bun run build && bun test"
|
|
44
45
|
}
|
|
45
|
-
}
|
|
46
|
+
}
|