@ovrdev/cli 0.1.0-alpha.16 → 0.1.0-alpha.18
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 +282 -204
- 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) {
|
|
@@ -782,6 +781,22 @@ import { defineConfig } from "@ovrdev/cli"
|
|
|
782
781
|
|
|
783
782
|
export default defineConfig({ plugins: [] }, ({ workspace, kind }) => ({
|
|
784
783
|
services: {
|
|
784
|
+
// A placeholder so \`ovr run\` boots something right away. It prints a few tips and
|
|
785
|
+
// stays up until you quit (qq). Replace it with your real service(s) — see the example.
|
|
786
|
+
hello: kind.shell({
|
|
787
|
+
command: [
|
|
788
|
+
"echo ''",
|
|
789
|
+
"echo ' \uD83D\uDC4B ovr is running this repo.'",
|
|
790
|
+
"echo ''",
|
|
791
|
+
"echo ' • Edit ovr.config.mts to declare your real services, then rerun ovr run.'",
|
|
792
|
+
"echo ' • A service is a process: kind.shell({ command, env, ready, exports }).'",
|
|
793
|
+
"echo ' • Every service gets its own allocated port — cross-wire with refs.'",
|
|
794
|
+
"echo ' • Press qq to quit; a service that stays up keeps the run alive.'",
|
|
795
|
+
"echo ''",
|
|
796
|
+
"tail -f /dev/null", // hang until ovr stops it (SIGTERM on quit)
|
|
797
|
+
].join(" && "),
|
|
798
|
+
}),
|
|
799
|
+
|
|
785
800
|
// web: kind.shell({
|
|
786
801
|
// prepare: async ({ ports }) => ({ port: await ports.alloc() }),
|
|
787
802
|
// command: ({ port }) => \`pnpm dev --port \${port}\`,
|
|
@@ -809,6 +824,11 @@ function seedServiceConfig(cfg, workspace, repo) {
|
|
|
809
824
|
}
|
|
810
825
|
}
|
|
811
826
|
}
|
|
827
|
+
return seedStarterConfig(dir);
|
|
828
|
+
}
|
|
829
|
+
function seedStarterConfig(dir) {
|
|
830
|
+
if (CONFIG_NAMES.some((f) => existsSync6(join8(dir, f))))
|
|
831
|
+
return;
|
|
812
832
|
writeFileSync7(join8(dir, "ovr.config.mts"), STARTER_CONFIG);
|
|
813
833
|
return { file: "ovr.config.mts" };
|
|
814
834
|
}
|
|
@@ -893,6 +913,46 @@ async function cmdSetup(args = [], override) {
|
|
|
893
913
|
if (!touched)
|
|
894
914
|
console.log(dim("Nothing to provision (no setup declared, or nothing overridden)."));
|
|
895
915
|
}
|
|
916
|
+
function methodOfUrl(url) {
|
|
917
|
+
if (/^(git@|ssh:\/\/)/.test(url))
|
|
918
|
+
return "ssh";
|
|
919
|
+
if (/^https?:\/\//.test(url))
|
|
920
|
+
return "https";
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
function inferCloneMethod(cfg) {
|
|
924
|
+
for (const def of Object.values(cfg.workspaces)) {
|
|
925
|
+
for (const decl of Object.values(def.repos ?? {})) {
|
|
926
|
+
if (decl.path) {
|
|
927
|
+
const m = methodOfUrl(decl.remote);
|
|
928
|
+
if (m)
|
|
929
|
+
return m;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
async function ensureCloneMethod(cfg, remote) {
|
|
936
|
+
if (cfg.cloneMethod)
|
|
937
|
+
return;
|
|
938
|
+
if (/^(git@|ssh:\/\/|https?:\/\/|file:\/\/)/.test(remote) || /^[/.~]/.test(remote))
|
|
939
|
+
return;
|
|
940
|
+
if (!process.stdin.isTTY)
|
|
941
|
+
return;
|
|
942
|
+
const initialValue = inferCloneMethod(cfg) ?? DEFAULT_CLONE_METHOD;
|
|
943
|
+
const sel = await clack.select({
|
|
944
|
+
message: "Clone method (how org/repo slugs expand)",
|
|
945
|
+
options: [
|
|
946
|
+
{ value: "ssh", label: "ssh", hint: "git@github.com:org/repo.git" },
|
|
947
|
+
{ value: "https", label: "https" }
|
|
948
|
+
],
|
|
949
|
+
initialValue
|
|
950
|
+
});
|
|
951
|
+
if (clack.isCancel(sel))
|
|
952
|
+
return;
|
|
953
|
+
cfg.cloneMethod = sel;
|
|
954
|
+
saveConfig(cfg);
|
|
955
|
+
}
|
|
896
956
|
function cloneInto(cfg, workspace, name, decl) {
|
|
897
957
|
const dir = repoDir(cfg, workspace, name);
|
|
898
958
|
if (existsSync6(dir))
|
|
@@ -903,7 +963,7 @@ function cloneInto(cfg, workspace, name, decl) {
|
|
|
903
963
|
console.log(`Cloning ${name} (${url}) → ${dir}`);
|
|
904
964
|
execFileSync2("git", ["clone", "--branch", branch, url, dir], { stdio: "inherit" });
|
|
905
965
|
}
|
|
906
|
-
function cmdRepoAdd(args, override) {
|
|
966
|
+
async function cmdRepoAdd(args, override) {
|
|
907
967
|
let name;
|
|
908
968
|
let branch;
|
|
909
969
|
let noClone = false;
|
|
@@ -932,8 +992,10 @@ function cmdRepoAdd(args, override) {
|
|
|
932
992
|
const decl = { remote };
|
|
933
993
|
if (branch)
|
|
934
994
|
decl.branch = branch;
|
|
935
|
-
if (!noClone)
|
|
995
|
+
if (!noClone) {
|
|
996
|
+
await ensureCloneMethod(cfg, remote);
|
|
936
997
|
cloneInto(cfg, workspace, repoName, decl);
|
|
998
|
+
}
|
|
937
999
|
proj.repos ??= {};
|
|
938
1000
|
proj.repos[repoName] = decl;
|
|
939
1001
|
saveConfig(cfg);
|
|
@@ -1257,7 +1319,7 @@ ${bold(name)} ${dim("$ git fetch --prune")}`);
|
|
|
1257
1319
|
console.log(fetched.size ? `
|
|
1258
1320
|
✓ fetched ${fetched.size} repo(s)` : "Nothing to fetch.");
|
|
1259
1321
|
}
|
|
1260
|
-
function cmdRepoClone(args, override) {
|
|
1322
|
+
async function cmdRepoClone(args, override) {
|
|
1261
1323
|
const all = args.includes("--all") || args.includes("-a");
|
|
1262
1324
|
const name = args.find((a) => !a.startsWith("-"));
|
|
1263
1325
|
const cfg = loadConfig();
|
|
@@ -1278,8 +1340,10 @@ function cmdRepoClone(args, override) {
|
|
|
1278
1340
|
}
|
|
1279
1341
|
if (!targets.length)
|
|
1280
1342
|
return console.log("Nothing to clone — all declared repos are present.");
|
|
1281
|
-
for (const r of targets)
|
|
1343
|
+
for (const r of targets) {
|
|
1344
|
+
await ensureCloneMethod(cfg, repos[r].remote);
|
|
1282
1345
|
cloneInto(cfg, workspace, r, repos[r]);
|
|
1346
|
+
}
|
|
1283
1347
|
console.log(`✓ cloned ${targets.length} repo(s) into '${workspace}'`);
|
|
1284
1348
|
}
|
|
1285
1349
|
async function cmdFork(args, override) {
|
|
@@ -1517,9 +1581,6 @@ async function readSource(src) {
|
|
|
1517
1581
|
}
|
|
1518
1582
|
return readFileSync7(src, "utf8");
|
|
1519
1583
|
}
|
|
1520
|
-
function defaultRoot() {
|
|
1521
|
-
return join9(CONFIG_DIR, "workspaces");
|
|
1522
|
-
}
|
|
1523
1584
|
async function cmdBootstrap(args) {
|
|
1524
1585
|
const cfg = loadConfig();
|
|
1525
1586
|
let methodFlag;
|
|
@@ -1666,7 +1727,7 @@ Shell integration — add to ${rc.display}:`);
|
|
|
1666
1727
|
seeded.active = base;
|
|
1667
1728
|
saveConfig(seeded);
|
|
1668
1729
|
}
|
|
1669
|
-
cmdRepoClone(["--all"], base);
|
|
1730
|
+
await cmdRepoClone(["--all"], base);
|
|
1670
1731
|
}
|
|
1671
1732
|
}
|
|
1672
1733
|
clack2.outro("Bootstrap complete.");
|
|
@@ -2021,22 +2082,35 @@ function cmdWorkspaceCreate(args) {
|
|
|
2021
2082
|
console.log(dim(" add a repo: ovr repo add <url|org/repo> then ovr run"));
|
|
2022
2083
|
}
|
|
2023
2084
|
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
2085
|
const cfg = loadConfig();
|
|
2031
|
-
|
|
2086
|
+
let methodFlag;
|
|
2087
|
+
const positional = [];
|
|
2088
|
+
for (let i = 0;i < args.length; i++) {
|
|
2089
|
+
const a = args[i];
|
|
2090
|
+
if (a === "--ssh")
|
|
2091
|
+
methodFlag = "ssh";
|
|
2092
|
+
else if (a === "--https")
|
|
2093
|
+
methodFlag = "https";
|
|
2094
|
+
else if (!a.startsWith("-"))
|
|
2095
|
+
positional.push(a);
|
|
2096
|
+
}
|
|
2097
|
+
if (!cfg.root) {
|
|
2098
|
+
const root = positional[0] ? resolve2(positional[0]) : defaultRoot();
|
|
2099
|
+
cfg.root = root;
|
|
2100
|
+
if (methodFlag)
|
|
2101
|
+
cfg.cloneMethod = methodFlag;
|
|
2102
|
+
mkdirSync9(root, { recursive: true });
|
|
2103
|
+
saveConfig(cfg);
|
|
2104
|
+
console.log(`✓ bootstrapped — root → ${root}${cfg.cloneMethod ? ` · clone → ${cfg.cloneMethod}` : ""}`);
|
|
2105
|
+
}
|
|
2106
|
+
const cwd = process.cwd();
|
|
2107
|
+
const existing = findConfigFile(cwd);
|
|
2108
|
+
if (existing) {
|
|
2109
|
+
console.log(`✓ already an ovr project (${basename2(existing)}) — run: ${bold("ovr run")}`);
|
|
2032
2110
|
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"));
|
|
2111
|
+
}
|
|
2112
|
+
const seeded = seedStarterConfig(cwd);
|
|
2113
|
+
console.log(`✓ scaffolded ${green(seeded?.file ?? "ovr.config.mts")} — declare your services, then: ${bold("ovr run")}`);
|
|
2040
2114
|
}
|
|
2041
2115
|
function cmdWorkspaceInfo(args, override, inferredRepo) {
|
|
2042
2116
|
const cfg = loadConfig();
|
|
@@ -2396,7 +2470,7 @@ compdef _ovr ovr`;
|
|
|
2396
2470
|
// src/engine/run.ts
|
|
2397
2471
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
2398
2472
|
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync12 } from "node:fs";
|
|
2399
|
-
import { basename as
|
|
2473
|
+
import { basename as basename5, join as join14 } from "node:path";
|
|
2400
2474
|
|
|
2401
2475
|
// src/engine/envname.ts
|
|
2402
2476
|
import { existsSync as existsSync8, mkdirSync as mkdirSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "node:fs";
|
|
@@ -2526,7 +2600,7 @@ function createSecretRegistry(workspace, file = SECRETS_PATH) {
|
|
|
2526
2600
|
|
|
2527
2601
|
// src/engine/prepare-helpers.ts
|
|
2528
2602
|
import { existsSync as existsSync10, mkdirSync as mkdirSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync11 } from "node:fs";
|
|
2529
|
-
import { basename as
|
|
2603
|
+
import { basename as basename3, dirname as dirname6, isAbsolute, join as join13 } from "node:path";
|
|
2530
2604
|
function makeEnv(base) {
|
|
2531
2605
|
return {
|
|
2532
2606
|
get: (key, fallback) => base[key] ?? fallback,
|
|
@@ -2547,7 +2621,7 @@ function makeFiles(repoDir2, dataDir) {
|
|
|
2547
2621
|
if (typeof template === "object") {
|
|
2548
2622
|
const src = isAbsolute(template.from) ? template.from : join13(repoDir2, template.from);
|
|
2549
2623
|
text2 = readFileSync10(src, "utf8");
|
|
2550
|
-
name = opts.name ??
|
|
2624
|
+
name = opts.name ?? basename3(src).replace(/\.(tmpl|hbs|mustache|template)$/, "");
|
|
2551
2625
|
} else {
|
|
2552
2626
|
text2 = template;
|
|
2553
2627
|
}
|
|
@@ -4980,8 +5054,8 @@ function createTui(services, handlers, options = {}) {
|
|
|
4980
5054
|
|
|
4981
5055
|
// src/workspace/onboard.ts
|
|
4982
5056
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
4983
|
-
import { readFileSync as readFileSync11, realpathSync as realpathSync2 } from "node:fs";
|
|
4984
|
-
import { basename as
|
|
5057
|
+
import { mkdirSync as mkdirSync14, readFileSync as readFileSync11, realpathSync as realpathSync2 } from "node:fs";
|
|
5058
|
+
import { basename as basename4, resolve as resolve3 } from "node:path";
|
|
4985
5059
|
import * as clack3 from "@clack/prompts";
|
|
4986
5060
|
var real = (p) => {
|
|
4987
5061
|
try {
|
|
@@ -5002,7 +5076,7 @@ function inferRepoName(dir) {
|
|
|
5002
5076
|
return sanitizeName(base);
|
|
5003
5077
|
}
|
|
5004
5078
|
} catch {}
|
|
5005
|
-
return sanitizeName(
|
|
5079
|
+
return sanitizeName(basename4(dir));
|
|
5006
5080
|
}
|
|
5007
5081
|
var git2 = (dir, args) => {
|
|
5008
5082
|
try {
|
|
@@ -5039,6 +5113,10 @@ function freeName(cfg, base) {
|
|
|
5039
5113
|
return `${base}-${i}`;
|
|
5040
5114
|
}
|
|
5041
5115
|
async function autoOnboard(cfg, cwd) {
|
|
5116
|
+
if (!cfg.root) {
|
|
5117
|
+
cfg.root = defaultRoot();
|
|
5118
|
+
mkdirSync14(cfg.root, { recursive: true });
|
|
5119
|
+
}
|
|
5042
5120
|
const { repo, decl } = onboardDecl(cwd);
|
|
5043
5121
|
const interactive = Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
5044
5122
|
let target = freeName(cfg, repo);
|
|
@@ -5814,7 +5892,7 @@ function cmdLogs(args = [], override) {
|
|
|
5814
5892
|
}
|
|
5815
5893
|
for (const p of paths) {
|
|
5816
5894
|
if (paths.length > 1)
|
|
5817
|
-
process.stdout.write(dim(`── ${
|
|
5895
|
+
process.stdout.write(dim(`── ${basename5(p, ".log")} ──
|
|
5818
5896
|
`));
|
|
5819
5897
|
process.stdout.write(readFileSync12(p, "utf8"));
|
|
5820
5898
|
}
|
|
@@ -5841,8 +5919,8 @@ var tree = {
|
|
|
5841
5919
|
},
|
|
5842
5920
|
{
|
|
5843
5921
|
name: "init",
|
|
5844
|
-
summary: "
|
|
5845
|
-
usage: "ovr init [
|
|
5922
|
+
summary: "Make the current directory an ovr project (bootstrap if needed + scaffold a config)",
|
|
5923
|
+
usage: "ovr init [root] [--ssh|--https]",
|
|
5846
5924
|
run: (c) => cmdInit(c.args)
|
|
5847
5925
|
},
|
|
5848
5926
|
{
|