@kitsy/cnos-cli 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +765 -232
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -44,9 +44,17 @@ function normalizeCommand(argv) {
|
|
|
44
44
|
const [command = "doctor", ...rest] = argv;
|
|
45
45
|
const resource = rest[0];
|
|
46
46
|
const remaining = rest.slice(1);
|
|
47
|
+
const dottedNamespace = resource?.includes(".") ? {
|
|
48
|
+
namespace: resource.slice(0, resource.indexOf(".")),
|
|
49
|
+
path: resource.slice(resource.indexOf(".") + 1)
|
|
50
|
+
} : void 0;
|
|
51
|
+
const normalizedVerb = command === "remove" || command === "delete" ? "delete" : command === "create" || command === "add" ? "set" : command === "set" || command === "get" ? command : void 0;
|
|
47
52
|
if ((command === "set" || command === "get") && (resource === "value" || resource === "secret")) {
|
|
48
53
|
return [resource, command, ...remaining];
|
|
49
54
|
}
|
|
55
|
+
if (normalizedVerb && dottedNamespace && dottedNamespace.namespace && dottedNamespace.path) {
|
|
56
|
+
return [dottedNamespace.namespace, normalizedVerb, dottedNamespace.path, ...remaining];
|
|
57
|
+
}
|
|
50
58
|
if ((command === "create" || command === "add") && resource === "profile") {
|
|
51
59
|
return ["profile", "create", ...remaining];
|
|
52
60
|
}
|
|
@@ -86,6 +94,9 @@ function normalizeCommand(argv) {
|
|
|
86
94
|
if ((command === "delete" || command === "remove") && resource === "value") {
|
|
87
95
|
return ["value", "delete", ...remaining];
|
|
88
96
|
}
|
|
97
|
+
if (normalizedVerb && resource && !["profile", "vault", "secret", "value"].includes(resource)) {
|
|
98
|
+
return [resource, normalizedVerb, ...remaining];
|
|
99
|
+
}
|
|
89
100
|
if (command === "list" && resource === "value") {
|
|
90
101
|
return ["value", "list", ...remaining];
|
|
91
102
|
}
|
|
@@ -219,21 +230,29 @@ function consumeOption(args, flag) {
|
|
|
219
230
|
return void 0;
|
|
220
231
|
}
|
|
221
232
|
|
|
233
|
+
// src/format/displayPath.ts
|
|
234
|
+
import path from "path";
|
|
235
|
+
function displayPath(filePath, root = process.cwd()) {
|
|
236
|
+
const absoluteRoot = path.resolve(root);
|
|
237
|
+
const absoluteFile = path.resolve(filePath);
|
|
238
|
+
const relative = path.relative(absoluteRoot, absoluteFile);
|
|
239
|
+
if (!relative || relative === "") {
|
|
240
|
+
return ".";
|
|
241
|
+
}
|
|
242
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
243
|
+
return absoluteFile;
|
|
244
|
+
}
|
|
245
|
+
return relative;
|
|
246
|
+
}
|
|
247
|
+
|
|
222
248
|
// src/format/printJson.ts
|
|
223
249
|
function printJson(value) {
|
|
224
250
|
return JSON.stringify(value, null, 2);
|
|
225
251
|
}
|
|
226
252
|
|
|
227
|
-
// src/services/
|
|
228
|
-
import { mkdir,
|
|
229
|
-
import
|
|
230
|
-
import {
|
|
231
|
-
createSecretVaultProvider,
|
|
232
|
-
parseYaml,
|
|
233
|
-
resolveConfigDocumentPath,
|
|
234
|
-
resolveVaultAuth,
|
|
235
|
-
stringifyYaml
|
|
236
|
-
} from "@kitsy/cnos/internal";
|
|
253
|
+
// src/services/envMaterialization.ts
|
|
254
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
255
|
+
import path2 from "path";
|
|
237
256
|
|
|
238
257
|
// src/services/runtime.ts
|
|
239
258
|
import { createCnos } from "@kitsy/cnos/configure";
|
|
@@ -249,7 +268,92 @@ async function createRuntimeService(options = {}) {
|
|
|
249
268
|
});
|
|
250
269
|
}
|
|
251
270
|
|
|
271
|
+
// src/services/envMaterialization.ts
|
|
272
|
+
function resolveEnvFromRuntime(runtime, cliArgs = []) {
|
|
273
|
+
const args = [...cliArgs];
|
|
274
|
+
const isPublic = consumeFlag(args, "--public");
|
|
275
|
+
const framework = consumeOption(args, "--framework");
|
|
276
|
+
const prefix = consumeOption(args, "--prefix");
|
|
277
|
+
return isPublic ? runtime.toPublicEnv({
|
|
278
|
+
...framework ? { framework } : {},
|
|
279
|
+
...prefix ? { prefix } : {}
|
|
280
|
+
}) : runtime.toEnv();
|
|
281
|
+
}
|
|
282
|
+
function formatEnvOutput(env) {
|
|
283
|
+
return Object.entries(env).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
284
|
+
}
|
|
285
|
+
async function resolveMaterializedEnv(options = {}) {
|
|
286
|
+
const runtime = await createRuntimeService({
|
|
287
|
+
...options,
|
|
288
|
+
cliArgs: [...options.cliArgs ?? []]
|
|
289
|
+
});
|
|
290
|
+
const env = resolveEnvFromRuntime(runtime, options.cliArgs ?? []);
|
|
291
|
+
return {
|
|
292
|
+
runtime,
|
|
293
|
+
env,
|
|
294
|
+
output: formatEnvOutput(env)
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function resolveMaterializedEnvTarget(to, root = process.cwd()) {
|
|
298
|
+
return path2.resolve(root, to);
|
|
299
|
+
}
|
|
300
|
+
async function writeMaterializedEnvFile(to, output, root = process.cwd()) {
|
|
301
|
+
const targetPath = resolveMaterializedEnvTarget(to, root);
|
|
302
|
+
await mkdir(path2.dirname(targetPath), { recursive: true });
|
|
303
|
+
await writeFile(targetPath, output, "utf8");
|
|
304
|
+
return targetPath;
|
|
305
|
+
}
|
|
306
|
+
async function materializeEnvToFile(to, options = {}) {
|
|
307
|
+
const result = await resolveMaterializedEnv(options);
|
|
308
|
+
const targetPath = await writeMaterializedEnvFile(to, result.output, options.root ?? process.cwd());
|
|
309
|
+
return {
|
|
310
|
+
...result,
|
|
311
|
+
targetPath
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/commands/build.ts
|
|
316
|
+
async function runBuild(subcommand, options = {}) {
|
|
317
|
+
if (subcommand !== "env") {
|
|
318
|
+
throw new Error(`Unsupported build target: ${subcommand ?? "(missing)"}`);
|
|
319
|
+
}
|
|
320
|
+
const infoArgs = [...options.cliArgs ?? []];
|
|
321
|
+
const isPublic = consumeFlag(infoArgs, "--public");
|
|
322
|
+
const framework = consumeOption(infoArgs, "--framework");
|
|
323
|
+
consumeOption(infoArgs, "--prefix");
|
|
324
|
+
const to = consumeOption(infoArgs, "--to");
|
|
325
|
+
if (!to) {
|
|
326
|
+
throw new Error("build env requires --to <path>");
|
|
327
|
+
}
|
|
328
|
+
const result = await materializeEnvToFile(to, {
|
|
329
|
+
...options,
|
|
330
|
+
cliArgs: [...options.cliArgs ?? []]
|
|
331
|
+
});
|
|
332
|
+
if (options.json) {
|
|
333
|
+
return printJson({
|
|
334
|
+
to: result.targetPath,
|
|
335
|
+
count: Object.keys(result.env).length,
|
|
336
|
+
public: isPublic,
|
|
337
|
+
...framework ? { framework } : {}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
return `built env artifact at ${displayPath(result.targetPath, options.root ?? process.cwd())}`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// src/commands/define.ts
|
|
344
|
+
import path4 from "path";
|
|
345
|
+
|
|
252
346
|
// src/services/writes.ts
|
|
347
|
+
import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
|
|
348
|
+
import path3 from "path";
|
|
349
|
+
import {
|
|
350
|
+
getNamespaceDefinition,
|
|
351
|
+
createSecretVaultProvider,
|
|
352
|
+
parseYaml,
|
|
353
|
+
resolveConfigDocumentPath,
|
|
354
|
+
resolveVaultAuth,
|
|
355
|
+
stringifyYaml
|
|
356
|
+
} from "@kitsy/cnos/internal";
|
|
253
357
|
function setNestedValue(target, pathSegments, value) {
|
|
254
358
|
const [head, ...tail] = pathSegments;
|
|
255
359
|
if (!head) {
|
|
@@ -330,14 +434,27 @@ async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
|
330
434
|
};
|
|
331
435
|
}
|
|
332
436
|
const runtime = await createRuntimeService(options);
|
|
437
|
+
if (namespace !== "value" && namespace !== "secret" && !runtime.manifest.namespaces[namespace]) {
|
|
438
|
+
throw new Error(`Cannot write ${namespace}.${configPath} because namespace "${namespace}" is not declared in .cnos/cnos.yml.`);
|
|
439
|
+
}
|
|
440
|
+
const namespaceDefinition = getNamespaceDefinition(runtime.manifest, namespace);
|
|
441
|
+
if (namespaceDefinition.kind !== "data") {
|
|
442
|
+
throw new Error(`Cannot write ${namespace}.${configPath} because namespace "${namespace}" is not a data namespace.`);
|
|
443
|
+
}
|
|
444
|
+
if (namespaceDefinition.readonly) {
|
|
445
|
+
throw new Error(`Cannot write ${namespace}.${configPath} because namespace "${namespace}" is readonly.`);
|
|
446
|
+
}
|
|
447
|
+
if (namespaceDefinition.sensitive) {
|
|
448
|
+
throw new Error(`Cannot write ${namespace}.${configPath} with the generic data writer because namespace "${namespace}" is sensitive.`);
|
|
449
|
+
}
|
|
333
450
|
const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
|
|
334
451
|
const profile = options.profile ?? runtime.graph.profile;
|
|
335
452
|
const filePath = resolveConfigDocumentPath(workspaceRoot, namespace, configPath, profile);
|
|
336
453
|
const document = await readYamlDocument(filePath);
|
|
337
454
|
const parsedValue = parseScalarValue(rawValue);
|
|
338
455
|
setNestedValue(document, configPath.split("."), parsedValue);
|
|
339
|
-
await
|
|
340
|
-
await
|
|
456
|
+
await mkdir2(path3.dirname(filePath), { recursive: true });
|
|
457
|
+
await writeFile2(filePath, stringifyYaml(document), "utf8");
|
|
341
458
|
return {
|
|
342
459
|
filePath,
|
|
343
460
|
value: parsedValue
|
|
@@ -374,8 +491,8 @@ async function setSecret(configPath, rawValue, options = {}) {
|
|
|
374
491
|
};
|
|
375
492
|
}
|
|
376
493
|
setNestedValue(document, configPath.split("."), reference);
|
|
377
|
-
await
|
|
378
|
-
await
|
|
494
|
+
await mkdir2(path3.dirname(filePath), { recursive: true });
|
|
495
|
+
await writeFile2(filePath, stringifyYaml(document), "utf8");
|
|
379
496
|
return {
|
|
380
497
|
filePath,
|
|
381
498
|
provider: reference.provider,
|
|
@@ -397,7 +514,7 @@ async function deleteSecret(configPath, options = {}) {
|
|
|
397
514
|
deleted: false
|
|
398
515
|
};
|
|
399
516
|
}
|
|
400
|
-
await
|
|
517
|
+
await writeFile2(filePath, stringifyYaml(document), "utf8");
|
|
401
518
|
const secretRef = metadata?.secretRef;
|
|
402
519
|
if (isSecretReference(secretRef) && secretRef.provider === "local") {
|
|
403
520
|
const definition = runtime.manifest.vaults[secretRef.vault ?? "default"];
|
|
@@ -418,6 +535,19 @@ async function deleteValue(namespace, configPath, options = {}) {
|
|
|
418
535
|
return deleteSecret(configPath, options);
|
|
419
536
|
}
|
|
420
537
|
const runtime = await createRuntimeService(options);
|
|
538
|
+
if (namespace !== "value" && namespace !== "secret" && !runtime.manifest.namespaces[namespace]) {
|
|
539
|
+
throw new Error(`Cannot delete ${namespace}.${configPath} because namespace "${namespace}" is not declared in .cnos/cnos.yml.`);
|
|
540
|
+
}
|
|
541
|
+
const namespaceDefinition = getNamespaceDefinition(runtime.manifest, namespace);
|
|
542
|
+
if (namespaceDefinition.kind !== "data") {
|
|
543
|
+
throw new Error(`Cannot delete ${namespace}.${configPath} because namespace "${namespace}" is not a data namespace.`);
|
|
544
|
+
}
|
|
545
|
+
if (namespaceDefinition.readonly) {
|
|
546
|
+
throw new Error(`Cannot delete ${namespace}.${configPath} because namespace "${namespace}" is readonly.`);
|
|
547
|
+
}
|
|
548
|
+
if (namespaceDefinition.sensitive) {
|
|
549
|
+
throw new Error(`Cannot delete ${namespace}.${configPath} with the generic data writer because namespace "${namespace}" is sensitive.`);
|
|
550
|
+
}
|
|
421
551
|
const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
|
|
422
552
|
const profile = options.profile ?? runtime.graph.profile;
|
|
423
553
|
const filePath = resolveConfigDocumentPath(workspaceRoot, namespace, configPath, profile);
|
|
@@ -429,7 +559,7 @@ async function deleteValue(namespace, configPath, options = {}) {
|
|
|
429
559
|
deleted: false
|
|
430
560
|
};
|
|
431
561
|
}
|
|
432
|
-
await
|
|
562
|
+
await writeFile2(filePath, stringifyYaml(document), "utf8");
|
|
433
563
|
return {
|
|
434
564
|
filePath,
|
|
435
565
|
deleted: true
|
|
@@ -439,6 +569,7 @@ async function deleteValue(namespace, configPath, options = {}) {
|
|
|
439
569
|
// src/commands/define.ts
|
|
440
570
|
async function runDefine(namespace, configPath, rawValue, options = {}) {
|
|
441
571
|
const cliArgs = [...options.cliArgs ?? []];
|
|
572
|
+
const root = path4.resolve(options.root ?? process.cwd());
|
|
442
573
|
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
443
574
|
const local = consumeFlag(cliArgs, "--local");
|
|
444
575
|
const remote = consumeFlag(cliArgs, "--remote");
|
|
@@ -464,7 +595,194 @@ async function runDefine(namespace, configPath, rawValue, options = {}) {
|
|
|
464
595
|
value: result.value
|
|
465
596
|
});
|
|
466
597
|
}
|
|
467
|
-
return `defined ${namespace}.${configPath} in ${result.filePath}`;
|
|
598
|
+
return `defined ${namespace}.${configPath} in ${displayPath(result.filePath, root)}`;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// src/services/spawn.ts
|
|
602
|
+
import { spawn } from "child_process";
|
|
603
|
+
function shouldUseShellForCommand(command) {
|
|
604
|
+
if (process.platform !== "win32") {
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
return !/[\\/]/.test(command);
|
|
608
|
+
}
|
|
609
|
+
function spawnCommand(command, options) {
|
|
610
|
+
const executable = command[0];
|
|
611
|
+
if (!executable) {
|
|
612
|
+
throw new Error("A command is required.");
|
|
613
|
+
}
|
|
614
|
+
return spawn(executable, command.slice(1), {
|
|
615
|
+
cwd: options.cwd,
|
|
616
|
+
env: options.env,
|
|
617
|
+
stdio: options.stdio ?? "inherit",
|
|
618
|
+
shell: shouldUseShellForCommand(executable)
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// src/services/watchLoop.ts
|
|
623
|
+
import { watch } from "fs";
|
|
624
|
+
import { diffGraphs, watchFiles } from "@kitsy/cnos/internal";
|
|
625
|
+
async function startGraphWatchLoop(options) {
|
|
626
|
+
const debounceMs = options.debounceMs ?? 300;
|
|
627
|
+
let current = await createRuntimeService(options);
|
|
628
|
+
const watcherMap = /* @__PURE__ */ new Map();
|
|
629
|
+
let timer;
|
|
630
|
+
let closed = false;
|
|
631
|
+
const attachWatcher = (targetPath, recursive = false) => {
|
|
632
|
+
if (watcherMap.has(targetPath)) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
try {
|
|
636
|
+
const watcher = watch(
|
|
637
|
+
targetPath,
|
|
638
|
+
recursive ? {
|
|
639
|
+
recursive: true
|
|
640
|
+
} : void 0,
|
|
641
|
+
() => {
|
|
642
|
+
if (timer) {
|
|
643
|
+
clearTimeout(timer);
|
|
644
|
+
}
|
|
645
|
+
timer = setTimeout(() => {
|
|
646
|
+
void handleChange();
|
|
647
|
+
}, debounceMs);
|
|
648
|
+
}
|
|
649
|
+
);
|
|
650
|
+
watcherMap.set(targetPath, watcher);
|
|
651
|
+
} catch {
|
|
652
|
+
if (recursive) {
|
|
653
|
+
attachWatcher(targetPath, false);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
const refreshWatchers = async () => {
|
|
658
|
+
const nextTargets = await watchFiles(current, options.root);
|
|
659
|
+
attachWatcher(nextTargets.manifestPath, false);
|
|
660
|
+
for (const workspaceRoot of nextTargets.roots) {
|
|
661
|
+
attachWatcher(workspaceRoot, true);
|
|
662
|
+
}
|
|
663
|
+
for (const filePath of nextTargets.files) {
|
|
664
|
+
attachWatcher(filePath, false);
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
const handleChange = async () => {
|
|
668
|
+
if (closed) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
const next = await createRuntimeService(options);
|
|
672
|
+
const changedKeys = diffGraphs(current.graph, next.graph);
|
|
673
|
+
current = next;
|
|
674
|
+
await refreshWatchers();
|
|
675
|
+
if (changedKeys.length === 0) {
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
await options.onChange?.({
|
|
679
|
+
runtime: next,
|
|
680
|
+
changedKeys
|
|
681
|
+
});
|
|
682
|
+
};
|
|
683
|
+
await refreshWatchers();
|
|
684
|
+
return {
|
|
685
|
+
async close() {
|
|
686
|
+
closed = true;
|
|
687
|
+
if (timer) {
|
|
688
|
+
clearTimeout(timer);
|
|
689
|
+
}
|
|
690
|
+
for (const watcher of watcherMap.values()) {
|
|
691
|
+
watcher.close();
|
|
692
|
+
}
|
|
693
|
+
watcherMap.clear();
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// src/commands/dev.ts
|
|
699
|
+
async function startDevEnvLoop(command, options = {}) {
|
|
700
|
+
if (command.length === 0) {
|
|
701
|
+
throw new Error("dev env requires a command after --");
|
|
702
|
+
}
|
|
703
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
704
|
+
const to = consumeOption(cliArgs, "--to");
|
|
705
|
+
const isSignal = consumeFlag(cliArgs, "--signal");
|
|
706
|
+
const debounceMs = Number(consumeOption(cliArgs, "--debounce") ?? "300");
|
|
707
|
+
if (!to) {
|
|
708
|
+
throw new Error("dev env requires --to <path>");
|
|
709
|
+
}
|
|
710
|
+
const root = options.root ?? process.cwd();
|
|
711
|
+
let child;
|
|
712
|
+
const writeCurrent = async () => {
|
|
713
|
+
await materializeEnvToFile(to, {
|
|
714
|
+
...options,
|
|
715
|
+
cliArgs: [...cliArgs]
|
|
716
|
+
});
|
|
717
|
+
};
|
|
718
|
+
await writeCurrent();
|
|
719
|
+
if (!isSignal) {
|
|
720
|
+
child = spawnCommand(command, {
|
|
721
|
+
cwd: root,
|
|
722
|
+
env: process.env,
|
|
723
|
+
stdio: "inherit"
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
const watcher = await startGraphWatchLoop({
|
|
727
|
+
...options,
|
|
728
|
+
cliArgs,
|
|
729
|
+
debounceMs,
|
|
730
|
+
async onChange(payload) {
|
|
731
|
+
await writeCurrent();
|
|
732
|
+
if (isSignal) {
|
|
733
|
+
process.stdout.write(`${printJson({ changedKeys: payload.changedKeys })}
|
|
734
|
+
`);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (child && !child.killed) {
|
|
738
|
+
await new Promise((resolve) => {
|
|
739
|
+
child?.once("close", () => resolve());
|
|
740
|
+
child?.kill();
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
child = spawnCommand(command, {
|
|
744
|
+
cwd: root,
|
|
745
|
+
env: process.env,
|
|
746
|
+
stdio: "inherit"
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
return {
|
|
751
|
+
async close() {
|
|
752
|
+
await watcher.close();
|
|
753
|
+
if (child && !child.killed) {
|
|
754
|
+
await new Promise((resolve) => {
|
|
755
|
+
child?.once("close", () => resolve());
|
|
756
|
+
child?.kill();
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
async function runDev(subcommand, command, options = {}) {
|
|
763
|
+
if (subcommand !== "env") {
|
|
764
|
+
throw new Error(`Unsupported dev target: ${subcommand ?? "(missing)"}`);
|
|
765
|
+
}
|
|
766
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
767
|
+
const to = consumeOption(cliArgs, "--to");
|
|
768
|
+
const isSignal = consumeFlag(cliArgs, "--signal");
|
|
769
|
+
if (!to) {
|
|
770
|
+
throw new Error("dev env requires --to <path>");
|
|
771
|
+
}
|
|
772
|
+
if (command.length === 0) {
|
|
773
|
+
throw new Error("dev env requires a command after --");
|
|
774
|
+
}
|
|
775
|
+
const handle = await startDevEnvLoop(command, {
|
|
776
|
+
...options,
|
|
777
|
+
cliArgs
|
|
778
|
+
});
|
|
779
|
+
const closeLoop = () => {
|
|
780
|
+
void handle.close();
|
|
781
|
+
};
|
|
782
|
+
process.once("SIGINT", closeLoop);
|
|
783
|
+
process.once("SIGTERM", closeLoop);
|
|
784
|
+
const targetPath = displayPath(to, options.root ?? process.cwd());
|
|
785
|
+
return isSignal ? `watching config changes and rewriting ${targetPath} in signal mode` : `watching config changes, rewriting ${targetPath}, and restarting the child process`;
|
|
468
786
|
}
|
|
469
787
|
|
|
470
788
|
// src/commands/drift.ts
|
|
@@ -517,7 +835,7 @@ async function runDiff(leftProfile, rightProfile, options = {}) {
|
|
|
517
835
|
|
|
518
836
|
// src/services/doctor.ts
|
|
519
837
|
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
520
|
-
import
|
|
838
|
+
import path5 from "path";
|
|
521
839
|
import {
|
|
522
840
|
detectLegacyVaultFormat,
|
|
523
841
|
isSecretReference as isSecretReference2,
|
|
@@ -539,7 +857,7 @@ async function createValidationSummary(options = {}) {
|
|
|
539
857
|
|
|
540
858
|
// src/services/doctor.ts
|
|
541
859
|
async function checkGitignore(root) {
|
|
542
|
-
const gitignorePath =
|
|
860
|
+
const gitignorePath = path5.join(root, ".gitignore");
|
|
543
861
|
const expected = [
|
|
544
862
|
".cnos/env/.env",
|
|
545
863
|
".cnos/env/.env.*",
|
|
@@ -574,12 +892,12 @@ async function collectYamlFiles(root) {
|
|
|
574
892
|
const entries = await readdir(root, { withFileTypes: true });
|
|
575
893
|
const results = [];
|
|
576
894
|
for (const entry of entries) {
|
|
577
|
-
const target =
|
|
895
|
+
const target = path5.join(root, entry.name);
|
|
578
896
|
if (entry.isDirectory()) {
|
|
579
897
|
results.push(...await collectYamlFiles(target));
|
|
580
898
|
continue;
|
|
581
899
|
}
|
|
582
|
-
if (entry.isFile() && [".yml", ".yaml"].includes(
|
|
900
|
+
if (entry.isFile() && [".yml", ".yaml"].includes(path5.extname(entry.name).toLowerCase())) {
|
|
583
901
|
results.push(target);
|
|
584
902
|
}
|
|
585
903
|
}
|
|
@@ -604,7 +922,7 @@ async function checkSecretSecurity(options, runtime) {
|
|
|
604
922
|
);
|
|
605
923
|
const legacyDetected = legacyPaths.filter((entry) => Boolean(entry.path));
|
|
606
924
|
const secretFiles = await Promise.all(
|
|
607
|
-
runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => collectYamlFiles(
|
|
925
|
+
runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => collectYamlFiles(path5.join(root.path, "secrets")))
|
|
608
926
|
);
|
|
609
927
|
const plaintextFiles = [];
|
|
610
928
|
for (const file of secretFiles.flat()) {
|
|
@@ -634,10 +952,11 @@ async function checkSecretSecurity(options, runtime) {
|
|
|
634
952
|
};
|
|
635
953
|
}
|
|
636
954
|
async function evaluateDoctor(options = {}) {
|
|
637
|
-
const root =
|
|
955
|
+
const root = path5.resolve(options.root ?? process.cwd());
|
|
638
956
|
const { runtime, summary } = await createValidationSummary(options);
|
|
639
957
|
const localRoot = runtime.graph.workspace.workspaceRoots.find((entry) => entry.scope === "local");
|
|
640
958
|
const globalRoot = runtime.graph.workspace.workspaceRoots.find((entry) => entry.scope === "global");
|
|
959
|
+
const declaredCustomNamespaces = Object.entries(runtime.manifest.namespaces).filter(([namespace]) => !["value", "secret", "meta", "process", "public", "env"].includes(namespace)).map(([namespace, definition]) => `${namespace}(${definition.kind}${definition.shareable ? ",shareable" : ""}${definition.readonly ? ",readonly" : ""})`).sort((left, right) => left.localeCompare(right));
|
|
641
960
|
return [
|
|
642
961
|
{
|
|
643
962
|
name: "manifest",
|
|
@@ -649,6 +968,11 @@ async function evaluateDoctor(options = {}) {
|
|
|
649
968
|
ok: true,
|
|
650
969
|
details: `${runtime.graph.workspace.workspaceId} via ${runtime.graph.workspace.workspaceSource}`
|
|
651
970
|
},
|
|
971
|
+
{
|
|
972
|
+
name: "namespaces",
|
|
973
|
+
ok: true,
|
|
974
|
+
details: declaredCustomNamespaces.length === 0 ? "built-ins: value, secret, meta, process, public, env" : `built-ins: value, secret, meta, process, public, env | custom: ${declaredCustomNamespaces.join(", ")}`
|
|
975
|
+
},
|
|
652
976
|
{
|
|
653
977
|
name: "source-roots",
|
|
654
978
|
ok: Boolean(localRoot),
|
|
@@ -748,44 +1072,33 @@ async function runCodegen(options = {}) {
|
|
|
748
1072
|
}
|
|
749
1073
|
|
|
750
1074
|
// src/commands/exportEnv.ts
|
|
751
|
-
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
752
|
-
import path3 from "path";
|
|
753
|
-
function formatEnvOutput(env) {
|
|
754
|
-
return Object.entries(env).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
755
|
-
}
|
|
756
1075
|
async function runExportEnv(options = {}) {
|
|
757
|
-
const
|
|
758
|
-
const isPublic = consumeFlag(
|
|
759
|
-
const framework = consumeOption(
|
|
760
|
-
|
|
761
|
-
const to = consumeOption(
|
|
762
|
-
const
|
|
1076
|
+
const infoArgs = [...options.cliArgs ?? []];
|
|
1077
|
+
const isPublic = consumeFlag(infoArgs, "--public");
|
|
1078
|
+
const framework = consumeOption(infoArgs, "--framework");
|
|
1079
|
+
consumeOption(infoArgs, "--prefix");
|
|
1080
|
+
const to = consumeOption(infoArgs, "--to");
|
|
1081
|
+
const baseOptions = {
|
|
763
1082
|
...options,
|
|
764
|
-
cliArgs
|
|
765
|
-
}
|
|
766
|
-
const env = isPublic ? runtime.toPublicEnv({
|
|
767
|
-
...framework ? { framework } : {},
|
|
768
|
-
...prefix ? { prefix } : {}
|
|
769
|
-
}) : runtime.toEnv();
|
|
770
|
-
const output = formatEnvOutput(env);
|
|
1083
|
+
cliArgs: [...options.cliArgs ?? []]
|
|
1084
|
+
};
|
|
771
1085
|
if (to) {
|
|
772
|
-
const
|
|
773
|
-
await mkdir2(path3.dirname(targetPath), { recursive: true });
|
|
774
|
-
await writeFile2(targetPath, output, "utf8");
|
|
1086
|
+
const result2 = await materializeEnvToFile(to, baseOptions);
|
|
775
1087
|
if (options.json) {
|
|
776
1088
|
return printJson({
|
|
777
|
-
to: targetPath,
|
|
778
|
-
count: Object.keys(env).length,
|
|
1089
|
+
to: result2.targetPath,
|
|
1090
|
+
count: Object.keys(result2.env).length,
|
|
779
1091
|
public: isPublic,
|
|
780
1092
|
...framework ? { framework } : {}
|
|
781
1093
|
});
|
|
782
1094
|
}
|
|
783
|
-
return `Wrote ${Object.keys(env).length} env vars to ${targetPath}`;
|
|
1095
|
+
return `Wrote ${Object.keys(result2.env).length} env vars to ${displayPath(result2.targetPath, options.root ?? process.cwd())}`;
|
|
784
1096
|
}
|
|
1097
|
+
const result = await resolveMaterializedEnv(baseOptions);
|
|
785
1098
|
if (options.json) {
|
|
786
|
-
return printJson(env);
|
|
1099
|
+
return printJson(result.env);
|
|
787
1100
|
}
|
|
788
|
-
return output;
|
|
1101
|
+
return result.output;
|
|
789
1102
|
}
|
|
790
1103
|
|
|
791
1104
|
// src/commands/export.ts
|
|
@@ -1001,7 +1314,7 @@ var COMMANDS = [
|
|
|
1001
1314
|
id: "vault create",
|
|
1002
1315
|
summary: "Create a manifest-defined vault.",
|
|
1003
1316
|
usage: "cnos vault create <name> [--provider <local|github-secrets>] [--no-passphrase] [global-options]",
|
|
1004
|
-
description: "Creates a vault definition in .cnos/cnos.yml and, for local vaults, initializes the encrypted store under ~/.cnos/secrets.",
|
|
1317
|
+
description: "Creates a vault definition in .cnos/cnos.yml and, for local vaults, initializes the encrypted store under ~/.cnos/secrets. CNOS prompts for a passphrase when one is not already available from env or keychain.",
|
|
1005
1318
|
examples: [
|
|
1006
1319
|
"cnos vault create local-dev",
|
|
1007
1320
|
"cnos vault create github-ci --provider github-secrets --no-passphrase"
|
|
@@ -1011,7 +1324,7 @@ var COMMANDS = [
|
|
|
1011
1324
|
id: "vault auth",
|
|
1012
1325
|
summary: "Authenticate a vault for the current shell session.",
|
|
1013
1326
|
usage: "cnos vault auth <name> [--store-keychain] [global-options]",
|
|
1014
|
-
description: "Authenticates
|
|
1327
|
+
description: "Authenticates an existing local vault using env, keychain, or prompt-based auth and stores a derived session key for later CNOS commands in the same shell. Wrong passphrases fail authentication.",
|
|
1015
1328
|
examples: ["cnos vault auth local-dev", "cnos vault auth local-dev --store-keychain"]
|
|
1016
1329
|
},
|
|
1017
1330
|
{
|
|
@@ -1084,11 +1397,11 @@ var COMMANDS = [
|
|
|
1084
1397
|
{
|
|
1085
1398
|
id: "list",
|
|
1086
1399
|
summary: "List resolved config entries.",
|
|
1087
|
-
usage: "cnos list [
|
|
1088
|
-
description: "Lists stored config or derived projections across one namespace or the full effective graph, with optional prefix filtering.",
|
|
1400
|
+
usage: "cnos list [<namespace>|all] [--prefix <path>] [--framework <name>] [global-options]",
|
|
1401
|
+
description: "Lists stored config or derived projections across one namespace or the full effective graph, with optional prefix filtering. Custom data namespaces such as flags are supported, and process exposes server-only ambient runtime state.",
|
|
1089
1402
|
options: [
|
|
1090
1403
|
{
|
|
1091
|
-
flag: "--namespace <
|
|
1404
|
+
flag: "--namespace <name>",
|
|
1092
1405
|
description: "Explicit namespace selector when not using a positional namespace argument."
|
|
1093
1406
|
},
|
|
1094
1407
|
{
|
|
@@ -1100,7 +1413,7 @@ var COMMANDS = [
|
|
|
1100
1413
|
description: "When listing public output, apply framework-specific prefixes such as vite or next."
|
|
1101
1414
|
}
|
|
1102
1415
|
],
|
|
1103
|
-
examples: ["cnos list", "cnos list value --prefix app.", "cnos list env", "cnos list public --framework vite"]
|
|
1416
|
+
examples: ["cnos list", "cnos list value --prefix app.", "cnos list flags", "cnos list process --prefix env.PATH", "cnos list env", "cnos list public --framework vite"]
|
|
1104
1417
|
},
|
|
1105
1418
|
{
|
|
1106
1419
|
id: "profile",
|
|
@@ -1110,11 +1423,16 @@ var COMMANDS = [
|
|
|
1110
1423
|
options: [
|
|
1111
1424
|
{
|
|
1112
1425
|
flag: "--inherit <name>",
|
|
1113
|
-
description: "Parent profile to extend when creating a profile."
|
|
1426
|
+
description: "Parent profile to extend when creating a profile. Base inheritance is implicit by default."
|
|
1427
|
+
},
|
|
1428
|
+
{
|
|
1429
|
+
flag: "--no-inherit",
|
|
1430
|
+
description: "Create a clean profile that does not inherit base fallback layers."
|
|
1114
1431
|
}
|
|
1115
1432
|
],
|
|
1116
1433
|
examples: [
|
|
1117
|
-
"cnos profile create stage
|
|
1434
|
+
"cnos profile create stage",
|
|
1435
|
+
"cnos profile create isolated --no-inherit",
|
|
1118
1436
|
"cnos profile list",
|
|
1119
1437
|
"cnos profile use stage"
|
|
1120
1438
|
]
|
|
@@ -1122,9 +1440,9 @@ var COMMANDS = [
|
|
|
1122
1440
|
{
|
|
1123
1441
|
id: "profile create",
|
|
1124
1442
|
summary: "Create a profile definition.",
|
|
1125
|
-
usage: "cnos profile create <name> [--inherit <name>] [--root <path>] [--json]",
|
|
1126
|
-
description: "Creates .cnos/profiles/<name>.yml for an explicit profile overlay.",
|
|
1127
|
-
examples: ["cnos profile create stage --inherit
|
|
1443
|
+
usage: "cnos profile create <name> [--inherit <name> | --no-inherit] [--root <path>] [--json]",
|
|
1444
|
+
description: "Creates .cnos/profiles/<name>.yml for an explicit profile overlay. New profiles inherit base by default unless --no-inherit is set.",
|
|
1445
|
+
examples: ["cnos profile create stage", "cnos profile create isolated --no-inherit"]
|
|
1128
1446
|
},
|
|
1129
1447
|
{
|
|
1130
1448
|
id: "profile list",
|
|
@@ -1151,7 +1469,7 @@ var COMMANDS = [
|
|
|
1151
1469
|
id: "promote",
|
|
1152
1470
|
summary: "Promote shareable config into public or env projection surfaces.",
|
|
1153
1471
|
usage: "cnos promote <key...> --to <public|env> [--as <ENV_VAR>] [global-options]",
|
|
1154
|
-
description: "Adds keys to public.promote or envMapping.explicit in .cnos/cnos.yml. Sensitive or non-shareable namespaces are rejected.",
|
|
1472
|
+
description: "Adds keys to public.promote or envMapping.explicit in .cnos/cnos.yml. Sensitive or non-shareable namespaces are rejected, but declared shareable data namespaces such as flags are allowed.",
|
|
1155
1473
|
options: [
|
|
1156
1474
|
{
|
|
1157
1475
|
flag: "--to <public|env>",
|
|
@@ -1164,6 +1482,7 @@ var COMMANDS = [
|
|
|
1164
1482
|
],
|
|
1165
1483
|
examples: [
|
|
1166
1484
|
"cnos promote value.flag.auth.upi_enabled --to public",
|
|
1485
|
+
"cnos promote flags.upi_enabled --to public",
|
|
1167
1486
|
"cnos promote value.server.port --to env --as PORT"
|
|
1168
1487
|
]
|
|
1169
1488
|
},
|
|
@@ -1240,6 +1559,52 @@ var COMMANDS = [
|
|
|
1240
1559
|
"cnos export env --public --framework next --workspace webapp"
|
|
1241
1560
|
]
|
|
1242
1561
|
},
|
|
1562
|
+
{
|
|
1563
|
+
id: "build",
|
|
1564
|
+
summary: "Build derived configuration artifacts from CNOS.",
|
|
1565
|
+
usage: "cnos build <subcommand> [options] [global-options]",
|
|
1566
|
+
description: "Builds deterministic derived outputs from the selected workspace. Currently supports env artifact generation.",
|
|
1567
|
+
arguments: [
|
|
1568
|
+
{
|
|
1569
|
+
name: "subcommand",
|
|
1570
|
+
description: "Supported value: env.",
|
|
1571
|
+
required: true
|
|
1572
|
+
}
|
|
1573
|
+
],
|
|
1574
|
+
examples: [
|
|
1575
|
+
"cnos build env --profile local --to .env.local",
|
|
1576
|
+
"cnos build env --public --framework vite --profile prod --to .env.production"
|
|
1577
|
+
]
|
|
1578
|
+
},
|
|
1579
|
+
{
|
|
1580
|
+
id: "build env",
|
|
1581
|
+
summary: "Build a flat env-file artifact from CNOS.",
|
|
1582
|
+
usage: "cnos build env --to <path> [--public] [--framework <name>] [--prefix <prefix>] [global-options]",
|
|
1583
|
+
description: "Builds a deterministic KEY=VALUE artifact for legacy build and runtime workflows. The target file is derived output, not the CNOS source of truth.",
|
|
1584
|
+
options: [
|
|
1585
|
+
{
|
|
1586
|
+
flag: "--to <path>",
|
|
1587
|
+
description: "Write the rendered KEY=VALUE output to a file. Required."
|
|
1588
|
+
},
|
|
1589
|
+
{
|
|
1590
|
+
flag: "--public",
|
|
1591
|
+
description: "Build only public values based on manifest promotion rules."
|
|
1592
|
+
},
|
|
1593
|
+
{
|
|
1594
|
+
flag: "--framework <name>",
|
|
1595
|
+
description: "Apply framework-specific public env conventions such as vite or next."
|
|
1596
|
+
},
|
|
1597
|
+
{
|
|
1598
|
+
flag: "--prefix <prefix>",
|
|
1599
|
+
description: "Override the generated public env prefix."
|
|
1600
|
+
}
|
|
1601
|
+
],
|
|
1602
|
+
examples: [
|
|
1603
|
+
"cnos build env --profile local --to .env.local",
|
|
1604
|
+
"cnos build env --profile stage --to .env.stage",
|
|
1605
|
+
"cnos build env --public --framework vite --to .env.local"
|
|
1606
|
+
]
|
|
1607
|
+
},
|
|
1243
1608
|
{
|
|
1244
1609
|
id: "export env",
|
|
1245
1610
|
summary: "Render environment variables for the selected workspace.",
|
|
@@ -1269,6 +1634,60 @@ var COMMANDS = [
|
|
|
1269
1634
|
"cnos export env --public --framework vite --to .env.local --workspace api"
|
|
1270
1635
|
]
|
|
1271
1636
|
},
|
|
1637
|
+
{
|
|
1638
|
+
id: "dev",
|
|
1639
|
+
summary: "Run watched CNOS-driven development workflows.",
|
|
1640
|
+
usage: "cnos dev <subcommand> [options] [global-options] -- <command...>",
|
|
1641
|
+
description: "Runs higher-level development workflows that derive config artifacts from CNOS and keep them up to date while a child process is running.",
|
|
1642
|
+
arguments: [
|
|
1643
|
+
{
|
|
1644
|
+
name: "subcommand",
|
|
1645
|
+
description: "Supported value: env.",
|
|
1646
|
+
required: true
|
|
1647
|
+
}
|
|
1648
|
+
],
|
|
1649
|
+
examples: [
|
|
1650
|
+
"cnos dev env --profile local --to .env.local -- pnpm dev",
|
|
1651
|
+
"cnos dev env --public --framework vite --to .env.local -- pnpm dev"
|
|
1652
|
+
]
|
|
1653
|
+
},
|
|
1654
|
+
{
|
|
1655
|
+
id: "dev env",
|
|
1656
|
+
summary: "Watch CNOS config, rewrite an env file, and restart a child process.",
|
|
1657
|
+
usage: "cnos dev env --to <path> [--public] [--framework <name>] [--prefix <prefix>] [--debounce <ms>] [--signal] [global-options] -- <command...>",
|
|
1658
|
+
description: "Writes a derived env file before first launch, watches CNOS inputs, rewrites the file on change, and restarts the child process by default.",
|
|
1659
|
+
options: [
|
|
1660
|
+
{
|
|
1661
|
+
flag: "--to <path>",
|
|
1662
|
+
description: "Write the rendered KEY=VALUE output to a file. Required."
|
|
1663
|
+
},
|
|
1664
|
+
{
|
|
1665
|
+
flag: "--public",
|
|
1666
|
+
description: "Build only public values based on manifest promotion rules."
|
|
1667
|
+
},
|
|
1668
|
+
{
|
|
1669
|
+
flag: "--framework <name>",
|
|
1670
|
+
description: "Apply framework-specific public env conventions such as vite or next."
|
|
1671
|
+
},
|
|
1672
|
+
{
|
|
1673
|
+
flag: "--prefix <prefix>",
|
|
1674
|
+
description: "Override the generated public env prefix."
|
|
1675
|
+
},
|
|
1676
|
+
{
|
|
1677
|
+
flag: "--debounce <ms>",
|
|
1678
|
+
description: "Debounce config changes before rebuilding the env artifact. Defaults to 300ms."
|
|
1679
|
+
},
|
|
1680
|
+
{
|
|
1681
|
+
flag: "--signal",
|
|
1682
|
+
description: "Rewrite the env artifact and emit changed keys as JSON instead of restarting the child process."
|
|
1683
|
+
}
|
|
1684
|
+
],
|
|
1685
|
+
examples: [
|
|
1686
|
+
"cnos dev env --profile local --to .env.local -- pnpm dev",
|
|
1687
|
+
"cnos dev env --profile stage --to .env.stage -- node server.js",
|
|
1688
|
+
"cnos dev env --public --framework vite --to .env.local --signal -- pnpm dev"
|
|
1689
|
+
]
|
|
1690
|
+
},
|
|
1272
1691
|
{
|
|
1273
1692
|
id: "dump",
|
|
1274
1693
|
summary: "Materialize the selected workspace into files.",
|
|
@@ -1473,6 +1892,8 @@ var HELP_DOCUMENT = {
|
|
|
1473
1892
|
examples: [
|
|
1474
1893
|
"cnos use --profile stage",
|
|
1475
1894
|
"cnos doctor --workspace api",
|
|
1895
|
+
"cnos build env --profile stage --to .env.stage",
|
|
1896
|
+
"cnos dev env --profile local --to .env.local -- pnpm dev",
|
|
1476
1897
|
"cnos export env --public --framework vite",
|
|
1477
1898
|
"cnos export env --public --framework next",
|
|
1478
1899
|
"cnos help-ai --format json"
|
|
@@ -1586,11 +2007,11 @@ function runHelpAi(topic, cliArgs = []) {
|
|
|
1586
2007
|
}
|
|
1587
2008
|
|
|
1588
2009
|
// src/commands/init.ts
|
|
1589
|
-
import
|
|
2010
|
+
import path7 from "path";
|
|
1590
2011
|
|
|
1591
2012
|
// src/services/scaffold.ts
|
|
1592
2013
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
1593
|
-
import
|
|
2014
|
+
import path6 from "path";
|
|
1594
2015
|
function scaffoldManifest(projectName, workspace) {
|
|
1595
2016
|
const lines = [
|
|
1596
2017
|
"version: 1",
|
|
@@ -1629,7 +2050,7 @@ async function ensureFile(filePath, content) {
|
|
|
1629
2050
|
}
|
|
1630
2051
|
}
|
|
1631
2052
|
async function ensureGitignore(root) {
|
|
1632
|
-
const gitignorePath =
|
|
2053
|
+
const gitignorePath = path6.join(root, ".gitignore");
|
|
1633
2054
|
const requiredEntries = [
|
|
1634
2055
|
".cnos/env/.env",
|
|
1635
2056
|
".cnos/env/.env.*",
|
|
@@ -1657,13 +2078,13 @@ async function ensureGitignore(root) {
|
|
|
1657
2078
|
return true;
|
|
1658
2079
|
}
|
|
1659
2080
|
async function scaffoldWorkspace(root, workspace) {
|
|
1660
|
-
const cnosRoot =
|
|
1661
|
-
const workspaceRoot = workspace ?
|
|
2081
|
+
const cnosRoot = path6.join(root, ".cnos");
|
|
2082
|
+
const workspaceRoot = workspace ? path6.join(cnosRoot, "workspaces", workspace) : cnosRoot;
|
|
1662
2083
|
const createdPaths = [];
|
|
1663
|
-
await mkdir3(
|
|
1664
|
-
await mkdir3(
|
|
1665
|
-
await mkdir3(
|
|
1666
|
-
await mkdir3(
|
|
2084
|
+
await mkdir3(path6.join(workspaceRoot, "profiles"), { recursive: true });
|
|
2085
|
+
await mkdir3(path6.join(workspaceRoot, "values"), { recursive: true });
|
|
2086
|
+
await mkdir3(path6.join(workspaceRoot, "secrets"), { recursive: true });
|
|
2087
|
+
await mkdir3(path6.join(workspaceRoot, "env"), { recursive: true });
|
|
1667
2088
|
const relativePaths = workspace ? [
|
|
1668
2089
|
["workspaces", workspace, "profiles", ".gitkeep"],
|
|
1669
2090
|
["workspaces", workspace, "values", ".gitkeep"],
|
|
@@ -1676,15 +2097,15 @@ async function scaffoldWorkspace(root, workspace) {
|
|
|
1676
2097
|
["env", ".gitkeep"]
|
|
1677
2098
|
];
|
|
1678
2099
|
for (const relativePath of relativePaths) {
|
|
1679
|
-
const filePath =
|
|
2100
|
+
const filePath = path6.join(cnosRoot, ...relativePath);
|
|
1680
2101
|
if (await ensureFile(filePath, "")) {
|
|
1681
|
-
createdPaths.push(
|
|
2102
|
+
createdPaths.push(path6.relative(root, filePath).replace(/\\/g, "/"));
|
|
1682
2103
|
}
|
|
1683
2104
|
}
|
|
1684
|
-
if (await ensureFile(
|
|
2105
|
+
if (await ensureFile(path6.join(cnosRoot, "cnos.yml"), scaffoldManifest(path6.basename(root), workspace))) {
|
|
1685
2106
|
createdPaths.push(".cnos/cnos.yml");
|
|
1686
2107
|
}
|
|
1687
|
-
if (workspace && await ensureFile(
|
|
2108
|
+
if (workspace && await ensureFile(path6.join(root, ".cnos-workspace.yml"), `workspace: ${workspace}
|
|
1688
2109
|
globalRoot: ~/.cnos
|
|
1689
2110
|
`)) {
|
|
1690
2111
|
createdPaths.push(".cnos-workspace.yml");
|
|
@@ -1701,7 +2122,7 @@ globalRoot: ~/.cnos
|
|
|
1701
2122
|
|
|
1702
2123
|
// src/commands/init.ts
|
|
1703
2124
|
async function runInit(options = {}) {
|
|
1704
|
-
const root =
|
|
2125
|
+
const root = path7.resolve(options.root ?? process.cwd());
|
|
1705
2126
|
const result = await scaffoldWorkspace(root, options.workspace);
|
|
1706
2127
|
if (options.json) {
|
|
1707
2128
|
return printJson(result);
|
|
@@ -1793,7 +2214,7 @@ function matchesPrefix(key, prefix) {
|
|
|
1793
2214
|
return key.startsWith(prefix) || key.split(".").slice(1).join(".").startsWith(prefix);
|
|
1794
2215
|
}
|
|
1795
2216
|
function toStoredEntry(namespace, entry, filter = {}) {
|
|
1796
|
-
const sourceId = namespace === "
|
|
2217
|
+
const sourceId = namespace === "secret" ? "filesystem-secrets" : "filesystem-values";
|
|
1797
2218
|
const candidates = [entry.winner, ...entry.overridden].filter((candidate) => candidate.sourceId === sourceId);
|
|
1798
2219
|
if (candidates.length === 0) {
|
|
1799
2220
|
return void 0;
|
|
@@ -1814,16 +2235,16 @@ function listStoredNamespace(namespace, options) {
|
|
|
1814
2235
|
}
|
|
1815
2236
|
function listProjectedNamespace(namespace, options) {
|
|
1816
2237
|
return createRuntimeService(options).then((runtime) => {
|
|
1817
|
-
const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? runtime.toEnv() : runtime.toPublicEnv({
|
|
2238
|
+
const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? runtime.toEnv() : namespace === "public" ? runtime.toPublicEnv({
|
|
1818
2239
|
...options.framework ? {
|
|
1819
2240
|
framework: options.framework
|
|
1820
2241
|
} : {}
|
|
1821
|
-
});
|
|
2242
|
+
}) : flattenObject2(runtime.toNamespace(namespace));
|
|
1822
2243
|
const entries = namespace === "env" ? Object.entries(projected).map(([envVar, value]) => ({
|
|
1823
2244
|
key: envVar,
|
|
1824
2245
|
value
|
|
1825
2246
|
})) : Object.entries(projected).map(([key, value]) => ({
|
|
1826
|
-
key: namespace === "meta" ?
|
|
2247
|
+
key: namespace === "meta" || namespace === "process" ? `${namespace}.${key}` : key,
|
|
1827
2248
|
value
|
|
1828
2249
|
}));
|
|
1829
2250
|
return entries.filter((entry) => entry.value !== void 0).filter((entry) => matchesPrefix(entry.key, options.prefix)).sort((left, right) => left.key.localeCompare(right.key));
|
|
@@ -1833,15 +2254,21 @@ async function listConfigEntries(namespace, options = {}) {
|
|
|
1833
2254
|
if (namespace === "value" || namespace === "secret") {
|
|
1834
2255
|
return listStoredNamespace(namespace, options);
|
|
1835
2256
|
}
|
|
1836
|
-
if (namespace === "meta" || namespace === "env" || namespace === "public") {
|
|
2257
|
+
if (namespace === "meta" || namespace === "env" || namespace === "public" || namespace === "process") {
|
|
1837
2258
|
return listProjectedNamespace(namespace, options);
|
|
1838
2259
|
}
|
|
1839
|
-
|
|
1840
|
-
listStoredNamespace(
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
2260
|
+
if (namespace !== "all") {
|
|
2261
|
+
return listStoredNamespace(namespace, options);
|
|
2262
|
+
}
|
|
2263
|
+
const runtime = await createRuntimeService(options);
|
|
2264
|
+
const namespaces = Array.from(
|
|
2265
|
+
new Set(
|
|
2266
|
+
Array.from(runtime.graph.entries.values()).map((entry) => entry.namespace).filter((entry) => entry !== "meta" && entry !== "env" && entry !== "public")
|
|
2267
|
+
)
|
|
2268
|
+
).sort((left, right) => left.localeCompare(right));
|
|
2269
|
+
const stored = await Promise.all(namespaces.map((entry) => listStoredNamespace(entry, options)));
|
|
2270
|
+
const meta = await listProjectedNamespace("meta", options);
|
|
2271
|
+
return [...stored.flat(), ...meta].sort((left, right) => left.key.localeCompare(right.key));
|
|
1845
2272
|
}
|
|
1846
2273
|
|
|
1847
2274
|
// src/commands/list.ts
|
|
@@ -1858,13 +2285,16 @@ function normalizeNamespace(value) {
|
|
|
1858
2285
|
if (value === "meta") {
|
|
1859
2286
|
return "meta";
|
|
1860
2287
|
}
|
|
2288
|
+
if (value === "process") {
|
|
2289
|
+
return "process";
|
|
2290
|
+
}
|
|
1861
2291
|
if (value === "env") {
|
|
1862
2292
|
return "env";
|
|
1863
2293
|
}
|
|
1864
2294
|
if (value === "public") {
|
|
1865
2295
|
return "public";
|
|
1866
2296
|
}
|
|
1867
|
-
|
|
2297
|
+
return value;
|
|
1868
2298
|
}
|
|
1869
2299
|
async function runList(args = [], options = {}) {
|
|
1870
2300
|
const cliArgs = [...options.cliArgs ?? []];
|
|
@@ -1887,7 +2317,7 @@ async function runList(args = [], options = {}) {
|
|
|
1887
2317
|
}
|
|
1888
2318
|
|
|
1889
2319
|
// src/commands/migrate.ts
|
|
1890
|
-
import
|
|
2320
|
+
import path8 from "path";
|
|
1891
2321
|
import {
|
|
1892
2322
|
applyManifestMappings,
|
|
1893
2323
|
loadManifest,
|
|
@@ -1905,7 +2335,7 @@ async function runMigrate(options = {}) {
|
|
|
1905
2335
|
throw new Error(`Unknown migrate options: ${cliArgs.join(" ")}`);
|
|
1906
2336
|
}
|
|
1907
2337
|
const manifest = await loadManifest(options.root ? { root: options.root } : {});
|
|
1908
|
-
const scanRoot =
|
|
2338
|
+
const scanRoot = path8.resolve(manifest.repoRoot, scan ?? "src");
|
|
1909
2339
|
const usages = await scanEnvUsage(scanRoot);
|
|
1910
2340
|
const uniqueProposals = new Map(usages.map((usage) => [usage.envVar, proposeMapping(usage.envVar)]));
|
|
1911
2341
|
const proposals = Array.from(uniqueProposals.values()).sort((left, right) => left.envVar.localeCompare(right.envVar));
|
|
@@ -1966,33 +2396,118 @@ async function runMigrate(options = {}) {
|
|
|
1966
2396
|
return lines.join("\n");
|
|
1967
2397
|
}
|
|
1968
2398
|
|
|
2399
|
+
// src/commands/namespace.ts
|
|
2400
|
+
import path9 from "path";
|
|
2401
|
+
function normalizeCommand2(args) {
|
|
2402
|
+
const [actionOrPath, ...tail] = args;
|
|
2403
|
+
if (!actionOrPath) {
|
|
2404
|
+
return {
|
|
2405
|
+
action: "list",
|
|
2406
|
+
tail: []
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
if (["get", "set", "create", "add", "list", "delete", "remove"].includes(actionOrPath)) {
|
|
2410
|
+
return {
|
|
2411
|
+
action: actionOrPath === "remove" ? "delete" : actionOrPath === "create" || actionOrPath === "add" ? "set" : actionOrPath,
|
|
2412
|
+
tail
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
return {
|
|
2416
|
+
action: "get",
|
|
2417
|
+
tail: args
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
async function runNamespace(namespace, args = [], options = {}) {
|
|
2421
|
+
const { action, tail } = normalizeCommand2(args);
|
|
2422
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
2423
|
+
const root = path9.resolve(options.root ?? process.cwd());
|
|
2424
|
+
if (action === "list") {
|
|
2425
|
+
const prefix = consumeOption(cliArgs, "--prefix");
|
|
2426
|
+
const entries = await listConfigEntries(namespace, {
|
|
2427
|
+
...options,
|
|
2428
|
+
cliArgs,
|
|
2429
|
+
...prefix ? { prefix } : {}
|
|
2430
|
+
});
|
|
2431
|
+
if (options.json) {
|
|
2432
|
+
return printJson(entries);
|
|
2433
|
+
}
|
|
2434
|
+
return entries.map((entry) => `${entry.key}=${printValue(entry.value)}`).join("\n");
|
|
2435
|
+
}
|
|
2436
|
+
if (action === "set") {
|
|
2437
|
+
const configPath2 = tail[0] ?? "app.name";
|
|
2438
|
+
const rawValue = tail[1] ?? "";
|
|
2439
|
+
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
2440
|
+
const result = await defineValue(namespace, configPath2, rawValue, {
|
|
2441
|
+
...options,
|
|
2442
|
+
cliArgs,
|
|
2443
|
+
target
|
|
2444
|
+
});
|
|
2445
|
+
if (options.json) {
|
|
2446
|
+
return printJson({
|
|
2447
|
+
namespace,
|
|
2448
|
+
path: configPath2,
|
|
2449
|
+
target,
|
|
2450
|
+
filePath: result.filePath,
|
|
2451
|
+
value: result.value
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
return `set ${namespace}.${configPath2} in ${displayPath(result.filePath, root)}`;
|
|
2455
|
+
}
|
|
2456
|
+
if (action === "delete") {
|
|
2457
|
+
const configPath2 = tail[0] ?? "app.name";
|
|
2458
|
+
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
2459
|
+
const result = await deleteValue(namespace, configPath2, {
|
|
2460
|
+
...options,
|
|
2461
|
+
cliArgs,
|
|
2462
|
+
target
|
|
2463
|
+
});
|
|
2464
|
+
if (options.json) {
|
|
2465
|
+
return printJson(result);
|
|
2466
|
+
}
|
|
2467
|
+
return result.deleted ? `deleted ${namespace}.${configPath2} from ${displayPath(result.filePath, root)}` : `no ${namespace}.${configPath2} found in ${displayPath(result.filePath, root)}`;
|
|
2468
|
+
}
|
|
2469
|
+
const runtime = await createRuntimeService(options);
|
|
2470
|
+
const configPath = tail[0] ?? "app.name";
|
|
2471
|
+
const value = runtime.read(`${namespace}.${configPath}`);
|
|
2472
|
+
if (value === void 0) {
|
|
2473
|
+
throw new Error(`Missing CNOS ${namespace} path: ${configPath}`);
|
|
2474
|
+
}
|
|
2475
|
+
if (options.json) {
|
|
2476
|
+
return printJson({
|
|
2477
|
+
key: `${namespace}.${configPath}`,
|
|
2478
|
+
value
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
return printValue(value);
|
|
2482
|
+
}
|
|
2483
|
+
|
|
1969
2484
|
// src/commands/onboard.ts
|
|
1970
2485
|
import { copyFile, readdir as readdir2, rm } from "fs/promises";
|
|
1971
|
-
import
|
|
2486
|
+
import path10 from "path";
|
|
1972
2487
|
var ROOT_ENV_FILE_PATTERN = /^\.env(?:\.[A-Za-z0-9_-]+)*(?:\.example)?$/;
|
|
1973
2488
|
async function listRootEnvFiles(root) {
|
|
1974
2489
|
const entries = await readdir2(root, { withFileTypes: true });
|
|
1975
2490
|
return entries.filter((entry) => entry.isFile() && ROOT_ENV_FILE_PATTERN.test(entry.name)).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
|
|
1976
2491
|
}
|
|
1977
2492
|
async function runOnboard(options = {}) {
|
|
1978
|
-
const root =
|
|
1979
|
-
const workspace = options.workspace ??
|
|
2493
|
+
const root = path10.resolve(options.root ?? process.cwd());
|
|
2494
|
+
const workspace = options.workspace ?? path10.basename(root);
|
|
1980
2495
|
const cliArgs = [...options.cliArgs ?? []];
|
|
1981
2496
|
const move = consumeFlag(cliArgs, "--move");
|
|
1982
2497
|
if (cliArgs.length > 0) {
|
|
1983
2498
|
throw new Error(`Unsupported onboard arguments: ${cliArgs.join(" ")}`);
|
|
1984
2499
|
}
|
|
1985
2500
|
const scaffold = await scaffoldWorkspace(root, workspace);
|
|
1986
|
-
const envRoot =
|
|
2501
|
+
const envRoot = path10.join(root, ".cnos", "workspaces", workspace, "env");
|
|
1987
2502
|
const rootFiles = await listRootEnvFiles(root);
|
|
1988
2503
|
const imported = [];
|
|
1989
2504
|
const skipped = [];
|
|
1990
2505
|
for (const fileName of rootFiles) {
|
|
1991
|
-
const sourcePath =
|
|
1992
|
-
const targetPath =
|
|
2506
|
+
const sourcePath = path10.join(root, fileName);
|
|
2507
|
+
const targetPath = path10.join(envRoot, fileName);
|
|
1993
2508
|
try {
|
|
1994
2509
|
await copyFile(sourcePath, targetPath);
|
|
1995
|
-
imported.push(
|
|
2510
|
+
imported.push(path10.relative(root, targetPath).replace(/\\/g, "/"));
|
|
1996
2511
|
if (move) {
|
|
1997
2512
|
await rm(sourcePath);
|
|
1998
2513
|
}
|
|
@@ -2017,14 +2532,14 @@ async function runOnboard(options = {}) {
|
|
|
2017
2532
|
}
|
|
2018
2533
|
|
|
2019
2534
|
// src/commands/profile.ts
|
|
2020
|
-
import
|
|
2535
|
+
import path13 from "path";
|
|
2021
2536
|
|
|
2022
2537
|
// src/services/context.ts
|
|
2023
2538
|
import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
2024
|
-
import
|
|
2539
|
+
import path11 from "path";
|
|
2025
2540
|
import { parseYaml as parseYaml3, stringifyYaml as stringifyYaml2 } from "@kitsy/cnos/internal";
|
|
2026
2541
|
async function loadCliContext(root = process.cwd()) {
|
|
2027
|
-
const filePath =
|
|
2542
|
+
const filePath = path11.join(path11.resolve(root), ".cnos-workspace.yml");
|
|
2028
2543
|
try {
|
|
2029
2544
|
const source = await readFile4(filePath, "utf8");
|
|
2030
2545
|
const parsed = parseYaml3(source);
|
|
@@ -2037,8 +2552,8 @@ async function loadCliContext(root = process.cwd()) {
|
|
|
2037
2552
|
}
|
|
2038
2553
|
}
|
|
2039
2554
|
async function saveCliContext(options = {}) {
|
|
2040
|
-
const root =
|
|
2041
|
-
const filePath =
|
|
2555
|
+
const root = path11.resolve(options.root ?? process.cwd());
|
|
2556
|
+
const filePath = path11.join(root, ".cnos-workspace.yml");
|
|
2042
2557
|
const current = await loadCliContext(root);
|
|
2043
2558
|
const next = {
|
|
2044
2559
|
...current.workspace ? { workspace: current.workspace } : {},
|
|
@@ -2057,12 +2572,19 @@ async function saveCliContext(options = {}) {
|
|
|
2057
2572
|
|
|
2058
2573
|
// src/services/profiles.ts
|
|
2059
2574
|
import { mkdir as mkdir4, readdir as readdir3, readFile as readFile5, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
2060
|
-
import
|
|
2575
|
+
import path12 from "path";
|
|
2061
2576
|
import { parseYaml as parseYaml4, stringifyYaml as stringifyYaml3 } from "@kitsy/cnos/internal";
|
|
2062
|
-
async function createProfileDefinition(root = process.cwd(), profile, inherit) {
|
|
2063
|
-
const filePath =
|
|
2064
|
-
await mkdir4(
|
|
2065
|
-
const document =
|
|
2577
|
+
async function createProfileDefinition(root = process.cwd(), profile, inherit, options = {}) {
|
|
2578
|
+
const filePath = path12.join(path12.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
2579
|
+
await mkdir4(path12.dirname(filePath), { recursive: true });
|
|
2580
|
+
const document = options.noInherit ? {
|
|
2581
|
+
name: profile,
|
|
2582
|
+
activate: {
|
|
2583
|
+
values: [`profiles/${profile}/values`, `values/${profile}`],
|
|
2584
|
+
secrets: [`profiles/${profile}/secrets`, `secrets/${profile}`],
|
|
2585
|
+
envFiles: [`.env.${profile}`]
|
|
2586
|
+
}
|
|
2587
|
+
} : inherit && inherit !== "base" ? {
|
|
2066
2588
|
name: profile,
|
|
2067
2589
|
extends: [inherit]
|
|
2068
2590
|
} : {
|
|
@@ -2072,11 +2594,12 @@ async function createProfileDefinition(root = process.cwd(), profile, inherit) {
|
|
|
2072
2594
|
return {
|
|
2073
2595
|
filePath,
|
|
2074
2596
|
profile,
|
|
2075
|
-
...inherit ? { inherit } : {}
|
|
2597
|
+
...inherit ? { inherit } : {},
|
|
2598
|
+
...options.noInherit ? { noInherit: true } : {}
|
|
2076
2599
|
};
|
|
2077
2600
|
}
|
|
2078
2601
|
async function listProfiles(root = process.cwd()) {
|
|
2079
|
-
const profilesRoot =
|
|
2602
|
+
const profilesRoot = path12.join(path12.resolve(root), ".cnos", "profiles");
|
|
2080
2603
|
try {
|
|
2081
2604
|
const entries = await readdir3(profilesRoot, { withFileTypes: true });
|
|
2082
2605
|
const discovered = /* @__PURE__ */ new Set(["base"]);
|
|
@@ -2091,7 +2614,7 @@ async function listProfiles(root = process.cwd()) {
|
|
|
2091
2614
|
}
|
|
2092
2615
|
}
|
|
2093
2616
|
async function deleteProfileDefinition(root = process.cwd(), profile) {
|
|
2094
|
-
const filePath =
|
|
2617
|
+
const filePath = path12.join(path12.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
2095
2618
|
try {
|
|
2096
2619
|
await rm2(filePath);
|
|
2097
2620
|
return {
|
|
@@ -2111,7 +2634,7 @@ async function readProfileDefinition(root = process.cwd(), profile = "base") {
|
|
|
2111
2634
|
name: "base"
|
|
2112
2635
|
};
|
|
2113
2636
|
}
|
|
2114
|
-
const filePath =
|
|
2637
|
+
const filePath = path12.join(path12.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
2115
2638
|
try {
|
|
2116
2639
|
return parseYaml4(await readFile5(filePath, "utf8")) ?? void 0;
|
|
2117
2640
|
} catch {
|
|
@@ -2135,16 +2658,23 @@ function normalizeProfileAction(args) {
|
|
|
2135
2658
|
}
|
|
2136
2659
|
async function runProfile(args, options = {}) {
|
|
2137
2660
|
const { action, tail } = normalizeProfileAction(args);
|
|
2138
|
-
const root =
|
|
2661
|
+
const root = path13.resolve(options.root ?? process.cwd());
|
|
2139
2662
|
const cliArgs = [...options.cliArgs ?? []];
|
|
2140
2663
|
if (action === "create") {
|
|
2141
2664
|
const profile = tail[0] ?? "stage";
|
|
2142
2665
|
const inherit = consumeOption(cliArgs, "--inherit");
|
|
2143
|
-
const
|
|
2666
|
+
const noInherit = consumeFlag(cliArgs, "--no-inherit");
|
|
2667
|
+
if (inherit && noInherit) {
|
|
2668
|
+
throw new Error("profile create accepts either --inherit <name> or --no-inherit, not both");
|
|
2669
|
+
}
|
|
2670
|
+
const result = await createProfileDefinition(root, profile, inherit, { noInherit });
|
|
2144
2671
|
if (options.json) {
|
|
2145
2672
|
return printJson(result);
|
|
2146
2673
|
}
|
|
2147
|
-
|
|
2674
|
+
if (noInherit) {
|
|
2675
|
+
return `created profile ${profile} at ${displayPath(result.filePath, root)} without inheriting base`;
|
|
2676
|
+
}
|
|
2677
|
+
return `created profile ${profile} at ${displayPath(result.filePath, root)}; inherits values from base by default`;
|
|
2148
2678
|
}
|
|
2149
2679
|
if (action === "use") {
|
|
2150
2680
|
const profile = tail[0] ?? "base";
|
|
@@ -2155,7 +2685,7 @@ async function runProfile(args, options = {}) {
|
|
|
2155
2685
|
if (options.json) {
|
|
2156
2686
|
return printJson(result);
|
|
2157
2687
|
}
|
|
2158
|
-
return `active profile set to ${profile} in ${result.filePath}`;
|
|
2688
|
+
return `active profile set to ${profile} in ${displayPath(result.filePath, root)}`;
|
|
2159
2689
|
}
|
|
2160
2690
|
if (action === "delete") {
|
|
2161
2691
|
const profile = tail[0] ?? "base";
|
|
@@ -2179,6 +2709,7 @@ async function runProfile(args, options = {}) {
|
|
|
2179
2709
|
}
|
|
2180
2710
|
|
|
2181
2711
|
// src/commands/promote.ts
|
|
2712
|
+
import path14 from "path";
|
|
2182
2713
|
import { writeFile as writeFile6 } from "fs/promises";
|
|
2183
2714
|
import {
|
|
2184
2715
|
ensureProjectionAllowed,
|
|
@@ -2195,6 +2726,7 @@ function sortRecord(record) {
|
|
|
2195
2726
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
2196
2727
|
}
|
|
2197
2728
|
async function runPromote(args = [], options = {}) {
|
|
2729
|
+
const root = path14.resolve(options.root ?? process.cwd());
|
|
2198
2730
|
const cliArgs = [...options.cliArgs ?? []];
|
|
2199
2731
|
const target = normalizeTarget(consumeOption(cliArgs, "--to"));
|
|
2200
2732
|
const alias = consumeOption(cliArgs, "--as");
|
|
@@ -2242,7 +2774,7 @@ async function runPromote(args = [], options = {}) {
|
|
|
2242
2774
|
manifestPath: loadedManifest.manifestPath
|
|
2243
2775
|
});
|
|
2244
2776
|
}
|
|
2245
|
-
return target === "public" ? `promoted ${keys.join(", ")} to public in ${loadedManifest.manifestPath}` : `promoted ${keys[0]} to env as ${alias} in ${loadedManifest.manifestPath}`;
|
|
2777
|
+
return target === "public" ? `promoted ${keys.join(", ")} to public in ${displayPath(loadedManifest.manifestPath, root)}` : `promoted ${keys[0]} to env as ${alias} in ${displayPath(loadedManifest.manifestPath, root)}`;
|
|
2246
2778
|
}
|
|
2247
2779
|
|
|
2248
2780
|
// src/commands/read.ts
|
|
@@ -2253,7 +2785,9 @@ async function runRead(key, options = {}) {
|
|
|
2253
2785
|
throw new Error(`Missing CNOS config key: ${key}`);
|
|
2254
2786
|
}
|
|
2255
2787
|
const isSecret = key.startsWith("secret.");
|
|
2256
|
-
const
|
|
2788
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
2789
|
+
const reveal = consumeFlag(cliArgs, "--reveal");
|
|
2790
|
+
const valueForOutput = isSecret && !reveal ? maskSecretValue(value) : value;
|
|
2257
2791
|
if (options.json) {
|
|
2258
2792
|
return printJson({ key, value: valueForOutput });
|
|
2259
2793
|
}
|
|
@@ -2261,7 +2795,6 @@ async function runRead(key, options = {}) {
|
|
|
2261
2795
|
}
|
|
2262
2796
|
|
|
2263
2797
|
// src/commands/run.ts
|
|
2264
|
-
import { spawn } from "child_process";
|
|
2265
2798
|
import {
|
|
2266
2799
|
CNOS_GRAPH_ENV_VAR,
|
|
2267
2800
|
CNOS_SECRET_PAYLOAD_ENV_VAR,
|
|
@@ -2335,11 +2868,10 @@ async function runCommand(command, options = {}) {
|
|
|
2335
2868
|
reject(new Error("run requires a command after --"));
|
|
2336
2869
|
return;
|
|
2337
2870
|
}
|
|
2338
|
-
const child =
|
|
2871
|
+
const child = spawnCommand(command, {
|
|
2339
2872
|
cwd: options.root ?? process.cwd(),
|
|
2340
2873
|
env,
|
|
2341
|
-
stdio: options.stdio === "pipe" ? "pipe" : "inherit"
|
|
2342
|
-
shell: false
|
|
2874
|
+
stdio: options.stdio === "pipe" ? "pipe" : "inherit"
|
|
2343
2875
|
});
|
|
2344
2876
|
let stdout = "";
|
|
2345
2877
|
let stderr = "";
|
|
@@ -2362,14 +2894,21 @@ async function runCommand(command, options = {}) {
|
|
|
2362
2894
|
});
|
|
2363
2895
|
}
|
|
2364
2896
|
|
|
2897
|
+
// src/commands/secret.ts
|
|
2898
|
+
import path17 from "path";
|
|
2899
|
+
|
|
2900
|
+
// src/commands/vault.ts
|
|
2901
|
+
import path16 from "path";
|
|
2902
|
+
|
|
2365
2903
|
// src/services/vaults.ts
|
|
2366
2904
|
import { rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
2367
|
-
import
|
|
2905
|
+
import path15 from "path";
|
|
2368
2906
|
import {
|
|
2369
2907
|
clearAllVaultSessionKeys,
|
|
2370
2908
|
clearVaultSessionKey,
|
|
2371
2909
|
createSecretVault,
|
|
2372
2910
|
deriveVaultKey,
|
|
2911
|
+
listLocalSecrets,
|
|
2373
2912
|
loadManifest as loadManifest3,
|
|
2374
2913
|
listSecretVaults,
|
|
2375
2914
|
readVaultMetadata,
|
|
@@ -2380,6 +2919,21 @@ import {
|
|
|
2380
2919
|
writeKeychain,
|
|
2381
2920
|
writeVaultSessionKey
|
|
2382
2921
|
} from "@kitsy/cnos/internal";
|
|
2922
|
+
function buildVaultDefinition(vault, provider) {
|
|
2923
|
+
return provider === "local" ? {
|
|
2924
|
+
provider: "local",
|
|
2925
|
+
auth: {
|
|
2926
|
+
passphrase: {
|
|
2927
|
+
from: defaultLocalAuthSources(vault)
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
} : {
|
|
2931
|
+
provider,
|
|
2932
|
+
auth: {
|
|
2933
|
+
method: "environment"
|
|
2934
|
+
}
|
|
2935
|
+
};
|
|
2936
|
+
}
|
|
2383
2937
|
function sortVaults(vaults) {
|
|
2384
2938
|
return Object.fromEntries(Object.entries(vaults).sort(([left], [right]) => left.localeCompare(right)));
|
|
2385
2939
|
}
|
|
@@ -2394,27 +2948,27 @@ async function createVaultDefinition(name, options = {}) {
|
|
|
2394
2948
|
throw new Error("Local vaults cannot be passwordless.");
|
|
2395
2949
|
}
|
|
2396
2950
|
const loadedManifest = await loadManifest3(options.root ? { root: options.root } : {});
|
|
2951
|
+
const vaultDefinition = buildVaultDefinition(vault, provider);
|
|
2397
2952
|
const rawManifest = {
|
|
2398
2953
|
...loadedManifest.rawManifest,
|
|
2399
2954
|
vaults: {
|
|
2400
2955
|
...loadedManifest.rawManifest.vaults ?? {},
|
|
2401
|
-
[vault]:
|
|
2402
|
-
provider: "local",
|
|
2403
|
-
auth: {
|
|
2404
|
-
passphrase: {
|
|
2405
|
-
from: defaultLocalAuthSources(vault)
|
|
2406
|
-
}
|
|
2407
|
-
}
|
|
2408
|
-
} : {
|
|
2409
|
-
provider,
|
|
2410
|
-
auth: {
|
|
2411
|
-
method: "environment"
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2956
|
+
[vault]: vaultDefinition
|
|
2414
2957
|
}
|
|
2415
2958
|
};
|
|
2416
2959
|
await writeFile7(loadedManifest.manifestPath, stringifyYaml5(rawManifest), "utf8");
|
|
2417
|
-
const definition = resolveVaultDefinition({ [vault]:
|
|
2960
|
+
const definition = resolveVaultDefinition({ [vault]: vaultDefinition }, vault);
|
|
2961
|
+
if (provider === "local") {
|
|
2962
|
+
const auth = await resolveVaultAuth2(vault, vaultDefinition, options.processEnv ?? process.env);
|
|
2963
|
+
if (!auth.passphrase) {
|
|
2964
|
+
throw new Error(`Vault "${vault}" requires passphrase-based authentication during creation.`);
|
|
2965
|
+
}
|
|
2966
|
+
const storeRoot = resolveSecretStoreRoot2(options.processEnv);
|
|
2967
|
+
const existing = await readVaultMetadata(storeRoot, vault);
|
|
2968
|
+
if (!existing) {
|
|
2969
|
+
await createSecretVault(storeRoot, vault, auth.passphrase);
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2418
2972
|
return {
|
|
2419
2973
|
...definition,
|
|
2420
2974
|
authMethod: definition.auth?.method ?? (provider === "local" ? "passphrase" : "environment"),
|
|
@@ -2454,7 +3008,7 @@ async function removeVaultDefinition(name, options = {}) {
|
|
|
2454
3008
|
delete rawManifest.vaults;
|
|
2455
3009
|
}
|
|
2456
3010
|
await writeFile7(loadedManifest.manifestPath, stringifyYaml5(rawManifest), "utf8");
|
|
2457
|
-
const vaultRoot =
|
|
3011
|
+
const vaultRoot = path15.join(resolveSecretStoreRoot2(options.processEnv), "vaults", vault);
|
|
2458
3012
|
let removedStore;
|
|
2459
3013
|
try {
|
|
2460
3014
|
await rm3(vaultRoot, { recursive: true, force: true });
|
|
@@ -2486,15 +3040,22 @@ async function authenticateVault(name, options = {}) {
|
|
|
2486
3040
|
if (!auth.passphrase) {
|
|
2487
3041
|
throw new Error(`Vault "${vault}" requires passphrase-based authentication.`);
|
|
2488
3042
|
}
|
|
2489
|
-
const existing = await readVaultMetadata(storeRoot, vault);
|
|
2490
|
-
if (!existing) {
|
|
2491
|
-
await createSecretVault(storeRoot, vault, auth.passphrase);
|
|
2492
|
-
}
|
|
2493
3043
|
const metadata = await readVaultMetadata(storeRoot, vault);
|
|
2494
3044
|
if (!metadata) {
|
|
2495
|
-
throw new Error(
|
|
3045
|
+
throw new Error(
|
|
3046
|
+
`Vault "${vault}" has not been initialized yet. Run cnos vault create ${vault} first.`
|
|
3047
|
+
);
|
|
2496
3048
|
}
|
|
2497
3049
|
const derivedKey = deriveVaultKey(auth.passphrase, Buffer.from(metadata.salt, "base64"), metadata.iterations);
|
|
3050
|
+
await listLocalSecrets(
|
|
3051
|
+
storeRoot,
|
|
3052
|
+
{
|
|
3053
|
+
derivedKey,
|
|
3054
|
+
method: auth.method,
|
|
3055
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
3056
|
+
},
|
|
3057
|
+
vault
|
|
3058
|
+
);
|
|
2498
3059
|
const sessionPath2 = await writeVaultSessionKey(vault, derivedKey, options.processEnv);
|
|
2499
3060
|
if (options.storeKeychain) {
|
|
2500
3061
|
await writeKeychain(`cnos/${vault}`, derivedKey.toString("hex"));
|
|
@@ -2541,6 +3102,7 @@ function normalizeVaultAction(args) {
|
|
|
2541
3102
|
async function runVault(args = [], options = {}) {
|
|
2542
3103
|
const { action, tail } = normalizeVaultAction(args);
|
|
2543
3104
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3105
|
+
const root = path16.resolve(options.root ?? process.cwd());
|
|
2544
3106
|
if (consumeOption(cliArgs, "--passphrase")) {
|
|
2545
3107
|
throw new Error("The --passphrase option is not supported in CNOS 1.4. Use env, keychain, or prompt-based auth.");
|
|
2546
3108
|
}
|
|
@@ -2557,7 +3119,7 @@ async function runVault(args = [], options = {}) {
|
|
|
2557
3119
|
if (options.json) {
|
|
2558
3120
|
return printJson(result);
|
|
2559
3121
|
}
|
|
2560
|
-
return `created vault "${result.name}" with provider "${result.provider}" in ${result.manifestPath}`;
|
|
3122
|
+
return `created vault "${result.name}" with provider "${result.provider}" in ${displayPath(result.manifestPath, root)}`;
|
|
2561
3123
|
}
|
|
2562
3124
|
if (action === "auth") {
|
|
2563
3125
|
const result = await authenticateVault(tail[0] ?? "default", {
|
|
@@ -2646,6 +3208,7 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
2646
3208
|
const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
|
|
2647
3209
|
const { action, tail } = normalizeSecretCommand(args);
|
|
2648
3210
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3211
|
+
const root = path17.resolve(options.root ?? process.cwd());
|
|
2649
3212
|
if (consumeOption(cliArgs, "--passphrase")) {
|
|
2650
3213
|
throw new Error("The --passphrase option is not supported in CNOS 1.4. Use env, keychain, or prompt-based auth.");
|
|
2651
3214
|
}
|
|
@@ -2701,7 +3264,7 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
2701
3264
|
if (options.json) {
|
|
2702
3265
|
return printJson(result);
|
|
2703
3266
|
}
|
|
2704
|
-
return result.provider === "local" ? `set secret.${secretPath2} in vault "${result.vault ?? "default"}" with ref "${result.ref}" and repo pointer ${result.filePath}` : `set secret.${secretPath2} via ${result.provider} in ${result.filePath}`;
|
|
3267
|
+
return result.provider === "local" ? `set secret.${secretPath2} in vault "${result.vault ?? "default"}" with ref "${result.ref}" and repo pointer ${displayPath(result.filePath, root)}` : `set secret.${secretPath2} via ${result.provider} in ${displayPath(result.filePath, root)}`;
|
|
2705
3268
|
}
|
|
2706
3269
|
if (action === "delete") {
|
|
2707
3270
|
const secretPath2 = tail[0];
|
|
@@ -2714,7 +3277,7 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
2714
3277
|
if (options.json) {
|
|
2715
3278
|
return printJson(result);
|
|
2716
3279
|
}
|
|
2717
|
-
return result.deleted ? `deleted secret.${secretPath2} from ${result.filePath}` : `no secret.${secretPath2} found in ${result.filePath}`;
|
|
3280
|
+
return result.deleted ? `deleted secret.${secretPath2} from ${displayPath(result.filePath, root)}` : `no secret.${secretPath2} found in ${displayPath(result.filePath, root)}`;
|
|
2718
3281
|
}
|
|
2719
3282
|
const runtime = await createRuntimeService(options);
|
|
2720
3283
|
const secretPath = tail[0] ?? "app.token";
|
|
@@ -2741,12 +3304,12 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
2741
3304
|
}
|
|
2742
3305
|
|
|
2743
3306
|
// src/commands/use.ts
|
|
2744
|
-
import
|
|
3307
|
+
import path18 from "path";
|
|
2745
3308
|
async function runUse(args = [], options = {}) {
|
|
2746
|
-
const root =
|
|
2747
|
-
const action = args[0]
|
|
3309
|
+
const root = path18.resolve(options.root ?? process.cwd());
|
|
3310
|
+
const action = args[0];
|
|
2748
3311
|
const hasUpdates = Boolean(options.workspace || options.profile || options.globalRoot);
|
|
2749
|
-
if (action === "show" || !hasUpdates) {
|
|
3312
|
+
if (action === "show" || !action && !hasUpdates) {
|
|
2750
3313
|
const context = await loadCliContext(root);
|
|
2751
3314
|
if (options.json) {
|
|
2752
3315
|
return printJson(context);
|
|
@@ -2762,7 +3325,7 @@ async function runUse(args = [], options = {}) {
|
|
|
2762
3325
|
if (options.json) {
|
|
2763
3326
|
return printJson(result);
|
|
2764
3327
|
}
|
|
2765
|
-
return `updated CLI context in ${result.filePath}`;
|
|
3328
|
+
return `updated CLI context in ${displayPath(result.filePath, root)}`;
|
|
2766
3329
|
}
|
|
2767
3330
|
|
|
2768
3331
|
// src/commands/validate.ts
|
|
@@ -2780,7 +3343,7 @@ async function runValidate(options = {}) {
|
|
|
2780
3343
|
// package.json
|
|
2781
3344
|
var package_default = {
|
|
2782
3345
|
name: "@kitsy/cnos-cli",
|
|
2783
|
-
version: "1.
|
|
3346
|
+
version: "1.5.0",
|
|
2784
3347
|
description: "CLI entry point and developer tooling for CNOS.",
|
|
2785
3348
|
type: "module",
|
|
2786
3349
|
main: "./dist/index.js",
|
|
@@ -2835,6 +3398,7 @@ function runVersion() {
|
|
|
2835
3398
|
}
|
|
2836
3399
|
|
|
2837
3400
|
// src/commands/value.ts
|
|
3401
|
+
import path19 from "path";
|
|
2838
3402
|
function normalizeValueCommand(args) {
|
|
2839
3403
|
const [actionOrPath, ...tail] = args;
|
|
2840
3404
|
if (!actionOrPath) {
|
|
@@ -2858,6 +3422,7 @@ async function runValue(argsOrPath, options = {}) {
|
|
|
2858
3422
|
const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
|
|
2859
3423
|
const { action, tail } = normalizeValueCommand(args);
|
|
2860
3424
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3425
|
+
const root = path19.resolve(options.root ?? process.cwd());
|
|
2861
3426
|
if (action === "list") {
|
|
2862
3427
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
2863
3428
|
const entries = await listConfigEntries("value", {
|
|
@@ -2888,7 +3453,7 @@ async function runValue(argsOrPath, options = {}) {
|
|
|
2888
3453
|
value: result.value
|
|
2889
3454
|
});
|
|
2890
3455
|
}
|
|
2891
|
-
return `set value.${valuePath} in ${result.filePath}`;
|
|
3456
|
+
return `set value.${valuePath} in ${displayPath(result.filePath, root)}`;
|
|
2892
3457
|
}
|
|
2893
3458
|
if (action === "delete") {
|
|
2894
3459
|
const valuePath = tail[0] ?? "app.name";
|
|
@@ -2901,7 +3466,7 @@ async function runValue(argsOrPath, options = {}) {
|
|
|
2901
3466
|
if (options.json) {
|
|
2902
3467
|
return printJson(result);
|
|
2903
3468
|
}
|
|
2904
|
-
return result.deleted ? `deleted value.${valuePath} from ${result.filePath}` : `no value.${valuePath} found in ${result.filePath}`;
|
|
3469
|
+
return result.deleted ? `deleted value.${valuePath} from ${displayPath(result.filePath, root)}` : `no value.${valuePath} found in ${displayPath(result.filePath, root)}`;
|
|
2905
3470
|
}
|
|
2906
3471
|
const runtime = await createRuntimeService(options);
|
|
2907
3472
|
const value = runtime.value(tail[0] ?? "app.name");
|
|
@@ -2918,16 +3483,12 @@ async function runValue(argsOrPath, options = {}) {
|
|
|
2918
3483
|
}
|
|
2919
3484
|
|
|
2920
3485
|
// src/commands/watch.ts
|
|
2921
|
-
import { watch } from "fs";
|
|
2922
|
-
import { spawn as spawn2 } from "child_process";
|
|
2923
3486
|
import {
|
|
2924
3487
|
CNOS_GRAPH_ENV_VAR as CNOS_GRAPH_ENV_VAR2,
|
|
2925
3488
|
CNOS_SECRET_PAYLOAD_ENV_VAR as CNOS_SECRET_PAYLOAD_ENV_VAR2,
|
|
2926
3489
|
CNOS_SESSION_KEY_ENV_VAR as CNOS_SESSION_KEY_ENV_VAR2,
|
|
2927
|
-
diffGraphs,
|
|
2928
3490
|
serializeRuntimeGraph as serializeRuntimeGraph2,
|
|
2929
|
-
serializeSecretPayload as serializeSecretPayload2
|
|
2930
|
-
watchFiles
|
|
3491
|
+
serializeSecretPayload as serializeSecretPayload2
|
|
2931
3492
|
} from "@kitsy/cnos/internal";
|
|
2932
3493
|
async function buildRunEnvironment(options) {
|
|
2933
3494
|
const cliArgs = [...options.cliArgs ?? []];
|
|
@@ -2964,11 +3525,10 @@ function spawnWatchedChild(command, cwd, env) {
|
|
|
2964
3525
|
if (!executable) {
|
|
2965
3526
|
throw new Error("watch requires a command after -- unless --signal is used");
|
|
2966
3527
|
}
|
|
2967
|
-
return
|
|
3528
|
+
return spawnCommand(command, {
|
|
2968
3529
|
cwd,
|
|
2969
3530
|
env,
|
|
2970
|
-
stdio: "inherit"
|
|
2971
|
-
shell: false
|
|
3531
|
+
stdio: "inherit"
|
|
2972
3532
|
});
|
|
2973
3533
|
}
|
|
2974
3534
|
async function startWatchLoop(options) {
|
|
@@ -2982,85 +3542,39 @@ async function startWatchLoop(options) {
|
|
|
2982
3542
|
cliArgs
|
|
2983
3543
|
});
|
|
2984
3544
|
let child = !isSignal ? spawnWatchedChild(command, root, current.env) : void 0;
|
|
2985
|
-
const watcherMap = /* @__PURE__ */ new Map();
|
|
2986
|
-
let timer;
|
|
2987
3545
|
let closed = false;
|
|
2988
|
-
const
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
recursive ? {
|
|
2996
|
-
recursive: true
|
|
2997
|
-
} : void 0,
|
|
2998
|
-
() => {
|
|
2999
|
-
if (timer) {
|
|
3000
|
-
clearTimeout(timer);
|
|
3001
|
-
}
|
|
3002
|
-
timer = setTimeout(() => {
|
|
3003
|
-
void handleChange();
|
|
3004
|
-
}, debounceMs);
|
|
3005
|
-
}
|
|
3006
|
-
);
|
|
3007
|
-
watcherMap.set(targetPath, watcher);
|
|
3008
|
-
} catch {
|
|
3009
|
-
if (recursive) {
|
|
3010
|
-
attachWatcher(targetPath, false);
|
|
3546
|
+
const watcher = await startGraphWatchLoop({
|
|
3547
|
+
...options,
|
|
3548
|
+
cliArgs,
|
|
3549
|
+
debounceMs,
|
|
3550
|
+
async onChange(payload) {
|
|
3551
|
+
if (closed) {
|
|
3552
|
+
return;
|
|
3011
3553
|
}
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
const nextTargets = await watchFiles(current.runtime, options.root);
|
|
3016
|
-
attachWatcher(nextTargets.manifestPath, false);
|
|
3017
|
-
for (const workspaceRoot of nextTargets.roots) {
|
|
3018
|
-
attachWatcher(workspaceRoot, true);
|
|
3019
|
-
}
|
|
3020
|
-
for (const filePath of nextTargets.files) {
|
|
3021
|
-
attachWatcher(filePath, false);
|
|
3022
|
-
}
|
|
3023
|
-
};
|
|
3024
|
-
const handleChange = async () => {
|
|
3025
|
-
if (closed) {
|
|
3026
|
-
return;
|
|
3027
|
-
}
|
|
3028
|
-
const next = await buildRunEnvironment({
|
|
3029
|
-
...options,
|
|
3030
|
-
cliArgs
|
|
3031
|
-
});
|
|
3032
|
-
const changedKeys = diffGraphs(current.runtime.graph, next.runtime.graph);
|
|
3033
|
-
current = next;
|
|
3034
|
-
await refreshWatchers();
|
|
3035
|
-
if (changedKeys.length === 0) {
|
|
3036
|
-
return;
|
|
3037
|
-
}
|
|
3038
|
-
if (isSignal) {
|
|
3039
|
-
await options.onSignal?.({ changedKeys });
|
|
3040
|
-
process.stdout.write(`${printJson({ changedKeys })}
|
|
3041
|
-
`);
|
|
3042
|
-
return;
|
|
3043
|
-
}
|
|
3044
|
-
if (child && !child.killed) {
|
|
3045
|
-
await new Promise((resolve) => {
|
|
3046
|
-
child?.once("close", () => resolve());
|
|
3047
|
-
child?.kill();
|
|
3554
|
+
current = await buildRunEnvironment({
|
|
3555
|
+
...options,
|
|
3556
|
+
cliArgs
|
|
3048
3557
|
});
|
|
3558
|
+
if (isSignal) {
|
|
3559
|
+
await options.onSignal?.({ changedKeys: payload.changedKeys });
|
|
3560
|
+
process.stdout.write(`${printJson({ changedKeys: payload.changedKeys })}
|
|
3561
|
+
`);
|
|
3562
|
+
return;
|
|
3563
|
+
}
|
|
3564
|
+
if (child && !child.killed) {
|
|
3565
|
+
await new Promise((resolve) => {
|
|
3566
|
+
child?.once("close", () => resolve());
|
|
3567
|
+
child?.kill();
|
|
3568
|
+
});
|
|
3569
|
+
}
|
|
3570
|
+
child = spawnWatchedChild(command, root, current.env);
|
|
3571
|
+
await options.onRestart?.({ changedKeys: payload.changedKeys });
|
|
3049
3572
|
}
|
|
3050
|
-
|
|
3051
|
-
await options.onRestart?.({ changedKeys });
|
|
3052
|
-
};
|
|
3053
|
-
await refreshWatchers();
|
|
3573
|
+
});
|
|
3054
3574
|
return {
|
|
3055
3575
|
async close() {
|
|
3056
3576
|
closed = true;
|
|
3057
|
-
|
|
3058
|
-
clearTimeout(timer);
|
|
3059
|
-
}
|
|
3060
|
-
for (const watcher of watcherMap.values()) {
|
|
3061
|
-
watcher.close();
|
|
3062
|
-
}
|
|
3063
|
-
watcherMap.clear();
|
|
3577
|
+
await watcher.close();
|
|
3064
3578
|
if (child && !child.killed) {
|
|
3065
3579
|
await new Promise((resolve) => {
|
|
3066
3580
|
child?.once("close", () => resolve());
|
|
@@ -3099,6 +3613,12 @@ function resolveHelpTopic(command, args) {
|
|
|
3099
3613
|
if (command === "export" && args[0] === "env") {
|
|
3100
3614
|
return normalizeHelpTopic([command, args[0]]);
|
|
3101
3615
|
}
|
|
3616
|
+
if (command === "build" && args[0] === "env") {
|
|
3617
|
+
return normalizeHelpTopic([command, args[0]]);
|
|
3618
|
+
}
|
|
3619
|
+
if (command === "dev" && args[0] === "env") {
|
|
3620
|
+
return normalizeHelpTopic([command, args[0]]);
|
|
3621
|
+
}
|
|
3102
3622
|
if (command === "vault" && args[0] && ["create", "add", "list", "delete", "remove"].includes(args[0])) {
|
|
3103
3623
|
return normalizeHelpTopic([command, args[0] === "delete" ? "remove" : args[0] === "add" ? "create" : args[0]]);
|
|
3104
3624
|
}
|
|
@@ -3229,6 +3749,14 @@ async function main(argv) {
|
|
|
3229
3749
|
return;
|
|
3230
3750
|
case "export":
|
|
3231
3751
|
process.stdout.write(`${await runExport(args[0], runtimeOptions)}
|
|
3752
|
+
`);
|
|
3753
|
+
return;
|
|
3754
|
+
case "build":
|
|
3755
|
+
process.stdout.write(`${await runBuild(args[0], runtimeOptions)}
|
|
3756
|
+
`);
|
|
3757
|
+
return;
|
|
3758
|
+
case "dev":
|
|
3759
|
+
process.stdout.write(`${await runDev(args[0], passthrough.length > 0 ? passthrough : args.slice(1), runtimeOptions)}
|
|
3232
3760
|
`);
|
|
3233
3761
|
return;
|
|
3234
3762
|
case "dump":
|
|
@@ -3260,6 +3788,11 @@ async function main(argv) {
|
|
|
3260
3788
|
`);
|
|
3261
3789
|
return;
|
|
3262
3790
|
default:
|
|
3791
|
+
if (args.length > 0 && /^[a-z][a-z0-9-]*$/i.test(command)) {
|
|
3792
|
+
process.stdout.write(`${await runNamespace(command, args, runtimeOptions)}
|
|
3793
|
+
`);
|
|
3794
|
+
return;
|
|
3795
|
+
}
|
|
3263
3796
|
process.stderr.write(`Unknown command: ${command}
|
|
3264
3797
|
`);
|
|
3265
3798
|
process.exitCode = 1;
|