@kitsy/cnos-cli 1.4.0 → 1.5.1
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 +450 -137
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -203,9 +203,6 @@ function parseArgs(argv) {
|
|
|
203
203
|
};
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
// src/commands/define.ts
|
|
207
|
-
import path3 from "path";
|
|
208
|
-
|
|
209
206
|
// src/cli/commandOptions.ts
|
|
210
207
|
function consumeFlag(args, flag) {
|
|
211
208
|
const index = args.indexOf(flag);
|
|
@@ -253,17 +250,9 @@ function printJson(value) {
|
|
|
253
250
|
return JSON.stringify(value, null, 2);
|
|
254
251
|
}
|
|
255
252
|
|
|
256
|
-
// src/services/
|
|
257
|
-
import { mkdir,
|
|
253
|
+
// src/services/envMaterialization.ts
|
|
254
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
258
255
|
import path2 from "path";
|
|
259
|
-
import {
|
|
260
|
-
getNamespaceDefinition,
|
|
261
|
-
createSecretVaultProvider,
|
|
262
|
-
parseYaml,
|
|
263
|
-
resolveConfigDocumentPath,
|
|
264
|
-
resolveVaultAuth,
|
|
265
|
-
stringifyYaml
|
|
266
|
-
} from "@kitsy/cnos/internal";
|
|
267
256
|
|
|
268
257
|
// src/services/runtime.ts
|
|
269
258
|
import { createCnos } from "@kitsy/cnos/configure";
|
|
@@ -279,7 +268,92 @@ async function createRuntimeService(options = {}) {
|
|
|
279
268
|
});
|
|
280
269
|
}
|
|
281
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
|
+
|
|
282
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";
|
|
283
357
|
function setNestedValue(target, pathSegments, value) {
|
|
284
358
|
const [head, ...tail] = pathSegments;
|
|
285
359
|
if (!head) {
|
|
@@ -379,8 +453,8 @@ async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
|
379
453
|
const document = await readYamlDocument(filePath);
|
|
380
454
|
const parsedValue = parseScalarValue(rawValue);
|
|
381
455
|
setNestedValue(document, configPath.split("."), parsedValue);
|
|
382
|
-
await
|
|
383
|
-
await
|
|
456
|
+
await mkdir2(path3.dirname(filePath), { recursive: true });
|
|
457
|
+
await writeFile2(filePath, stringifyYaml(document), "utf8");
|
|
384
458
|
return {
|
|
385
459
|
filePath,
|
|
386
460
|
value: parsedValue
|
|
@@ -417,8 +491,8 @@ async function setSecret(configPath, rawValue, options = {}) {
|
|
|
417
491
|
};
|
|
418
492
|
}
|
|
419
493
|
setNestedValue(document, configPath.split("."), reference);
|
|
420
|
-
await
|
|
421
|
-
await
|
|
494
|
+
await mkdir2(path3.dirname(filePath), { recursive: true });
|
|
495
|
+
await writeFile2(filePath, stringifyYaml(document), "utf8");
|
|
422
496
|
return {
|
|
423
497
|
filePath,
|
|
424
498
|
provider: reference.provider,
|
|
@@ -440,7 +514,7 @@ async function deleteSecret(configPath, options = {}) {
|
|
|
440
514
|
deleted: false
|
|
441
515
|
};
|
|
442
516
|
}
|
|
443
|
-
await
|
|
517
|
+
await writeFile2(filePath, stringifyYaml(document), "utf8");
|
|
444
518
|
const secretRef = metadata?.secretRef;
|
|
445
519
|
if (isSecretReference(secretRef) && secretRef.provider === "local") {
|
|
446
520
|
const definition = runtime.manifest.vaults[secretRef.vault ?? "default"];
|
|
@@ -485,7 +559,7 @@ async function deleteValue(namespace, configPath, options = {}) {
|
|
|
485
559
|
deleted: false
|
|
486
560
|
};
|
|
487
561
|
}
|
|
488
|
-
await
|
|
562
|
+
await writeFile2(filePath, stringifyYaml(document), "utf8");
|
|
489
563
|
return {
|
|
490
564
|
filePath,
|
|
491
565
|
deleted: true
|
|
@@ -495,7 +569,7 @@ async function deleteValue(namespace, configPath, options = {}) {
|
|
|
495
569
|
// src/commands/define.ts
|
|
496
570
|
async function runDefine(namespace, configPath, rawValue, options = {}) {
|
|
497
571
|
const cliArgs = [...options.cliArgs ?? []];
|
|
498
|
-
const root =
|
|
572
|
+
const root = path4.resolve(options.root ?? process.cwd());
|
|
499
573
|
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
500
574
|
const local = consumeFlag(cliArgs, "--local");
|
|
501
575
|
const remote = consumeFlag(cliArgs, "--remote");
|
|
@@ -524,6 +598,193 @@ async function runDefine(namespace, configPath, rawValue, options = {}) {
|
|
|
524
598
|
return `defined ${namespace}.${configPath} in ${displayPath(result.filePath, root)}`;
|
|
525
599
|
}
|
|
526
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`;
|
|
786
|
+
}
|
|
787
|
+
|
|
527
788
|
// src/commands/drift.ts
|
|
528
789
|
import { compareSchemaToGraph, formatDriftReport } from "@kitsy/cnos/internal";
|
|
529
790
|
async function runDrift(options = {}) {
|
|
@@ -574,7 +835,7 @@ async function runDiff(leftProfile, rightProfile, options = {}) {
|
|
|
574
835
|
|
|
575
836
|
// src/services/doctor.ts
|
|
576
837
|
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
577
|
-
import
|
|
838
|
+
import path5 from "path";
|
|
578
839
|
import {
|
|
579
840
|
detectLegacyVaultFormat,
|
|
580
841
|
isSecretReference as isSecretReference2,
|
|
@@ -596,7 +857,7 @@ async function createValidationSummary(options = {}) {
|
|
|
596
857
|
|
|
597
858
|
// src/services/doctor.ts
|
|
598
859
|
async function checkGitignore(root) {
|
|
599
|
-
const gitignorePath =
|
|
860
|
+
const gitignorePath = path5.join(root, ".gitignore");
|
|
600
861
|
const expected = [
|
|
601
862
|
".cnos/env/.env",
|
|
602
863
|
".cnos/env/.env.*",
|
|
@@ -631,12 +892,12 @@ async function collectYamlFiles(root) {
|
|
|
631
892
|
const entries = await readdir(root, { withFileTypes: true });
|
|
632
893
|
const results = [];
|
|
633
894
|
for (const entry of entries) {
|
|
634
|
-
const target =
|
|
895
|
+
const target = path5.join(root, entry.name);
|
|
635
896
|
if (entry.isDirectory()) {
|
|
636
897
|
results.push(...await collectYamlFiles(target));
|
|
637
898
|
continue;
|
|
638
899
|
}
|
|
639
|
-
if (entry.isFile() && [".yml", ".yaml"].includes(
|
|
900
|
+
if (entry.isFile() && [".yml", ".yaml"].includes(path5.extname(entry.name).toLowerCase())) {
|
|
640
901
|
results.push(target);
|
|
641
902
|
}
|
|
642
903
|
}
|
|
@@ -661,7 +922,7 @@ async function checkSecretSecurity(options, runtime) {
|
|
|
661
922
|
);
|
|
662
923
|
const legacyDetected = legacyPaths.filter((entry) => Boolean(entry.path));
|
|
663
924
|
const secretFiles = await Promise.all(
|
|
664
|
-
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")))
|
|
665
926
|
);
|
|
666
927
|
const plaintextFiles = [];
|
|
667
928
|
for (const file of secretFiles.flat()) {
|
|
@@ -691,7 +952,7 @@ async function checkSecretSecurity(options, runtime) {
|
|
|
691
952
|
};
|
|
692
953
|
}
|
|
693
954
|
async function evaluateDoctor(options = {}) {
|
|
694
|
-
const root =
|
|
955
|
+
const root = path5.resolve(options.root ?? process.cwd());
|
|
695
956
|
const { runtime, summary } = await createValidationSummary(options);
|
|
696
957
|
const localRoot = runtime.graph.workspace.workspaceRoots.find((entry) => entry.scope === "local");
|
|
697
958
|
const globalRoot = runtime.graph.workspace.workspaceRoots.find((entry) => entry.scope === "global");
|
|
@@ -811,44 +1072,33 @@ async function runCodegen(options = {}) {
|
|
|
811
1072
|
}
|
|
812
1073
|
|
|
813
1074
|
// src/commands/exportEnv.ts
|
|
814
|
-
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
815
|
-
import path5 from "path";
|
|
816
|
-
function formatEnvOutput(env) {
|
|
817
|
-
return Object.entries(env).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
818
|
-
}
|
|
819
1075
|
async function runExportEnv(options = {}) {
|
|
820
|
-
const
|
|
821
|
-
const isPublic = consumeFlag(
|
|
822
|
-
const framework = consumeOption(
|
|
823
|
-
|
|
824
|
-
const to = consumeOption(
|
|
825
|
-
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 = {
|
|
826
1082
|
...options,
|
|
827
|
-
cliArgs
|
|
828
|
-
}
|
|
829
|
-
const env = isPublic ? runtime.toPublicEnv({
|
|
830
|
-
...framework ? { framework } : {},
|
|
831
|
-
...prefix ? { prefix } : {}
|
|
832
|
-
}) : runtime.toEnv();
|
|
833
|
-
const output = formatEnvOutput(env);
|
|
1083
|
+
cliArgs: [...options.cliArgs ?? []]
|
|
1084
|
+
};
|
|
834
1085
|
if (to) {
|
|
835
|
-
const
|
|
836
|
-
await mkdir2(path5.dirname(targetPath), { recursive: true });
|
|
837
|
-
await writeFile2(targetPath, output, "utf8");
|
|
1086
|
+
const result2 = await materializeEnvToFile(to, baseOptions);
|
|
838
1087
|
if (options.json) {
|
|
839
1088
|
return printJson({
|
|
840
|
-
to: targetPath,
|
|
841
|
-
count: Object.keys(env).length,
|
|
1089
|
+
to: result2.targetPath,
|
|
1090
|
+
count: Object.keys(result2.env).length,
|
|
842
1091
|
public: isPublic,
|
|
843
1092
|
...framework ? { framework } : {}
|
|
844
1093
|
});
|
|
845
1094
|
}
|
|
846
|
-
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())}`;
|
|
847
1096
|
}
|
|
1097
|
+
const result = await resolveMaterializedEnv(baseOptions);
|
|
848
1098
|
if (options.json) {
|
|
849
|
-
return printJson(env);
|
|
1099
|
+
return printJson(result.env);
|
|
850
1100
|
}
|
|
851
|
-
return output;
|
|
1101
|
+
return result.output;
|
|
852
1102
|
}
|
|
853
1103
|
|
|
854
1104
|
// src/commands/export.ts
|
|
@@ -1309,6 +1559,52 @@ var COMMANDS = [
|
|
|
1309
1559
|
"cnos export env --public --framework next --workspace webapp"
|
|
1310
1560
|
]
|
|
1311
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
|
+
},
|
|
1312
1608
|
{
|
|
1313
1609
|
id: "export env",
|
|
1314
1610
|
summary: "Render environment variables for the selected workspace.",
|
|
@@ -1338,6 +1634,60 @@ var COMMANDS = [
|
|
|
1338
1634
|
"cnos export env --public --framework vite --to .env.local --workspace api"
|
|
1339
1635
|
]
|
|
1340
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
|
+
},
|
|
1341
1691
|
{
|
|
1342
1692
|
id: "dump",
|
|
1343
1693
|
summary: "Materialize the selected workspace into files.",
|
|
@@ -1542,6 +1892,8 @@ var HELP_DOCUMENT = {
|
|
|
1542
1892
|
examples: [
|
|
1543
1893
|
"cnos use --profile stage",
|
|
1544
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",
|
|
1545
1897
|
"cnos export env --public --framework vite",
|
|
1546
1898
|
"cnos export env --public --framework next",
|
|
1547
1899
|
"cnos help-ai --format json"
|
|
@@ -2443,7 +2795,6 @@ async function runRead(key, options = {}) {
|
|
|
2443
2795
|
}
|
|
2444
2796
|
|
|
2445
2797
|
// src/commands/run.ts
|
|
2446
|
-
import { spawn } from "child_process";
|
|
2447
2798
|
import {
|
|
2448
2799
|
CNOS_GRAPH_ENV_VAR,
|
|
2449
2800
|
CNOS_SECRET_PAYLOAD_ENV_VAR,
|
|
@@ -2517,11 +2868,10 @@ async function runCommand(command, options = {}) {
|
|
|
2517
2868
|
reject(new Error("run requires a command after --"));
|
|
2518
2869
|
return;
|
|
2519
2870
|
}
|
|
2520
|
-
const child =
|
|
2871
|
+
const child = spawnCommand(command, {
|
|
2521
2872
|
cwd: options.root ?? process.cwd(),
|
|
2522
2873
|
env,
|
|
2523
|
-
stdio: options.stdio === "pipe" ? "pipe" : "inherit"
|
|
2524
|
-
shell: false
|
|
2874
|
+
stdio: options.stdio === "pipe" ? "pipe" : "inherit"
|
|
2525
2875
|
});
|
|
2526
2876
|
let stdout = "";
|
|
2527
2877
|
let stderr = "";
|
|
@@ -2993,7 +3343,7 @@ async function runValidate(options = {}) {
|
|
|
2993
3343
|
// package.json
|
|
2994
3344
|
var package_default = {
|
|
2995
3345
|
name: "@kitsy/cnos-cli",
|
|
2996
|
-
version: "1.
|
|
3346
|
+
version: "1.5.1",
|
|
2997
3347
|
description: "CLI entry point and developer tooling for CNOS.",
|
|
2998
3348
|
type: "module",
|
|
2999
3349
|
main: "./dist/index.js",
|
|
@@ -3133,16 +3483,12 @@ async function runValue(argsOrPath, options = {}) {
|
|
|
3133
3483
|
}
|
|
3134
3484
|
|
|
3135
3485
|
// src/commands/watch.ts
|
|
3136
|
-
import { watch } from "fs";
|
|
3137
|
-
import { spawn as spawn2 } from "child_process";
|
|
3138
3486
|
import {
|
|
3139
3487
|
CNOS_GRAPH_ENV_VAR as CNOS_GRAPH_ENV_VAR2,
|
|
3140
3488
|
CNOS_SECRET_PAYLOAD_ENV_VAR as CNOS_SECRET_PAYLOAD_ENV_VAR2,
|
|
3141
3489
|
CNOS_SESSION_KEY_ENV_VAR as CNOS_SESSION_KEY_ENV_VAR2,
|
|
3142
|
-
diffGraphs,
|
|
3143
3490
|
serializeRuntimeGraph as serializeRuntimeGraph2,
|
|
3144
|
-
serializeSecretPayload as serializeSecretPayload2
|
|
3145
|
-
watchFiles
|
|
3491
|
+
serializeSecretPayload as serializeSecretPayload2
|
|
3146
3492
|
} from "@kitsy/cnos/internal";
|
|
3147
3493
|
async function buildRunEnvironment(options) {
|
|
3148
3494
|
const cliArgs = [...options.cliArgs ?? []];
|
|
@@ -3179,11 +3525,10 @@ function spawnWatchedChild(command, cwd, env) {
|
|
|
3179
3525
|
if (!executable) {
|
|
3180
3526
|
throw new Error("watch requires a command after -- unless --signal is used");
|
|
3181
3527
|
}
|
|
3182
|
-
return
|
|
3528
|
+
return spawnCommand(command, {
|
|
3183
3529
|
cwd,
|
|
3184
3530
|
env,
|
|
3185
|
-
stdio: "inherit"
|
|
3186
|
-
shell: false
|
|
3531
|
+
stdio: "inherit"
|
|
3187
3532
|
});
|
|
3188
3533
|
}
|
|
3189
3534
|
async function startWatchLoop(options) {
|
|
@@ -3197,85 +3542,39 @@ async function startWatchLoop(options) {
|
|
|
3197
3542
|
cliArgs
|
|
3198
3543
|
});
|
|
3199
3544
|
let child = !isSignal ? spawnWatchedChild(command, root, current.env) : void 0;
|
|
3200
|
-
const watcherMap = /* @__PURE__ */ new Map();
|
|
3201
|
-
let timer;
|
|
3202
3545
|
let closed = false;
|
|
3203
|
-
const
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
recursive ? {
|
|
3211
|
-
recursive: true
|
|
3212
|
-
} : void 0,
|
|
3213
|
-
() => {
|
|
3214
|
-
if (timer) {
|
|
3215
|
-
clearTimeout(timer);
|
|
3216
|
-
}
|
|
3217
|
-
timer = setTimeout(() => {
|
|
3218
|
-
void handleChange();
|
|
3219
|
-
}, debounceMs);
|
|
3220
|
-
}
|
|
3221
|
-
);
|
|
3222
|
-
watcherMap.set(targetPath, watcher);
|
|
3223
|
-
} catch {
|
|
3224
|
-
if (recursive) {
|
|
3225
|
-
attachWatcher(targetPath, false);
|
|
3546
|
+
const watcher = await startGraphWatchLoop({
|
|
3547
|
+
...options,
|
|
3548
|
+
cliArgs,
|
|
3549
|
+
debounceMs,
|
|
3550
|
+
async onChange(payload) {
|
|
3551
|
+
if (closed) {
|
|
3552
|
+
return;
|
|
3226
3553
|
}
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
const nextTargets = await watchFiles(current.runtime, options.root);
|
|
3231
|
-
attachWatcher(nextTargets.manifestPath, false);
|
|
3232
|
-
for (const workspaceRoot of nextTargets.roots) {
|
|
3233
|
-
attachWatcher(workspaceRoot, true);
|
|
3234
|
-
}
|
|
3235
|
-
for (const filePath of nextTargets.files) {
|
|
3236
|
-
attachWatcher(filePath, false);
|
|
3237
|
-
}
|
|
3238
|
-
};
|
|
3239
|
-
const handleChange = async () => {
|
|
3240
|
-
if (closed) {
|
|
3241
|
-
return;
|
|
3242
|
-
}
|
|
3243
|
-
const next = await buildRunEnvironment({
|
|
3244
|
-
...options,
|
|
3245
|
-
cliArgs
|
|
3246
|
-
});
|
|
3247
|
-
const changedKeys = diffGraphs(current.runtime.graph, next.runtime.graph);
|
|
3248
|
-
current = next;
|
|
3249
|
-
await refreshWatchers();
|
|
3250
|
-
if (changedKeys.length === 0) {
|
|
3251
|
-
return;
|
|
3252
|
-
}
|
|
3253
|
-
if (isSignal) {
|
|
3254
|
-
await options.onSignal?.({ changedKeys });
|
|
3255
|
-
process.stdout.write(`${printJson({ changedKeys })}
|
|
3256
|
-
`);
|
|
3257
|
-
return;
|
|
3258
|
-
}
|
|
3259
|
-
if (child && !child.killed) {
|
|
3260
|
-
await new Promise((resolve) => {
|
|
3261
|
-
child?.once("close", () => resolve());
|
|
3262
|
-
child?.kill();
|
|
3554
|
+
current = await buildRunEnvironment({
|
|
3555
|
+
...options,
|
|
3556
|
+
cliArgs
|
|
3263
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 });
|
|
3264
3572
|
}
|
|
3265
|
-
|
|
3266
|
-
await options.onRestart?.({ changedKeys });
|
|
3267
|
-
};
|
|
3268
|
-
await refreshWatchers();
|
|
3573
|
+
});
|
|
3269
3574
|
return {
|
|
3270
3575
|
async close() {
|
|
3271
3576
|
closed = true;
|
|
3272
|
-
|
|
3273
|
-
clearTimeout(timer);
|
|
3274
|
-
}
|
|
3275
|
-
for (const watcher of watcherMap.values()) {
|
|
3276
|
-
watcher.close();
|
|
3277
|
-
}
|
|
3278
|
-
watcherMap.clear();
|
|
3577
|
+
await watcher.close();
|
|
3279
3578
|
if (child && !child.killed) {
|
|
3280
3579
|
await new Promise((resolve) => {
|
|
3281
3580
|
child?.once("close", () => resolve());
|
|
@@ -3314,6 +3613,12 @@ function resolveHelpTopic(command, args) {
|
|
|
3314
3613
|
if (command === "export" && args[0] === "env") {
|
|
3315
3614
|
return normalizeHelpTopic([command, args[0]]);
|
|
3316
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
|
+
}
|
|
3317
3622
|
if (command === "vault" && args[0] && ["create", "add", "list", "delete", "remove"].includes(args[0])) {
|
|
3318
3623
|
return normalizeHelpTopic([command, args[0] === "delete" ? "remove" : args[0] === "add" ? "create" : args[0]]);
|
|
3319
3624
|
}
|
|
@@ -3444,6 +3749,14 @@ async function main(argv) {
|
|
|
3444
3749
|
return;
|
|
3445
3750
|
case "export":
|
|
3446
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)}
|
|
3447
3760
|
`);
|
|
3448
3761
|
return;
|
|
3449
3762
|
case "dump":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitsy/cnos-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "CLI entry point and developer tooling for CNOS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@kitsy/cnos": "1.
|
|
39
|
+
"@kitsy/cnos": "1.5.1"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsup src/index.ts --format esm --dts",
|