@kitsy/cnos-cli 1.3.0 → 1.4.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 +332 -112
- 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
|
}
|
|
@@ -192,6 +203,9 @@ function parseArgs(argv) {
|
|
|
192
203
|
};
|
|
193
204
|
}
|
|
194
205
|
|
|
206
|
+
// src/commands/define.ts
|
|
207
|
+
import path3 from "path";
|
|
208
|
+
|
|
195
209
|
// src/cli/commandOptions.ts
|
|
196
210
|
function consumeFlag(args, flag) {
|
|
197
211
|
const index = args.indexOf(flag);
|
|
@@ -219,6 +233,21 @@ function consumeOption(args, flag) {
|
|
|
219
233
|
return void 0;
|
|
220
234
|
}
|
|
221
235
|
|
|
236
|
+
// src/format/displayPath.ts
|
|
237
|
+
import path from "path";
|
|
238
|
+
function displayPath(filePath, root = process.cwd()) {
|
|
239
|
+
const absoluteRoot = path.resolve(root);
|
|
240
|
+
const absoluteFile = path.resolve(filePath);
|
|
241
|
+
const relative = path.relative(absoluteRoot, absoluteFile);
|
|
242
|
+
if (!relative || relative === "") {
|
|
243
|
+
return ".";
|
|
244
|
+
}
|
|
245
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
246
|
+
return absoluteFile;
|
|
247
|
+
}
|
|
248
|
+
return relative;
|
|
249
|
+
}
|
|
250
|
+
|
|
222
251
|
// src/format/printJson.ts
|
|
223
252
|
function printJson(value) {
|
|
224
253
|
return JSON.stringify(value, null, 2);
|
|
@@ -226,8 +255,9 @@ function printJson(value) {
|
|
|
226
255
|
|
|
227
256
|
// src/services/writes.ts
|
|
228
257
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
229
|
-
import
|
|
258
|
+
import path2 from "path";
|
|
230
259
|
import {
|
|
260
|
+
getNamespaceDefinition,
|
|
231
261
|
createSecretVaultProvider,
|
|
232
262
|
parseYaml,
|
|
233
263
|
resolveConfigDocumentPath,
|
|
@@ -330,13 +360,26 @@ async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
|
330
360
|
};
|
|
331
361
|
}
|
|
332
362
|
const runtime = await createRuntimeService(options);
|
|
363
|
+
if (namespace !== "value" && namespace !== "secret" && !runtime.manifest.namespaces[namespace]) {
|
|
364
|
+
throw new Error(`Cannot write ${namespace}.${configPath} because namespace "${namespace}" is not declared in .cnos/cnos.yml.`);
|
|
365
|
+
}
|
|
366
|
+
const namespaceDefinition = getNamespaceDefinition(runtime.manifest, namespace);
|
|
367
|
+
if (namespaceDefinition.kind !== "data") {
|
|
368
|
+
throw new Error(`Cannot write ${namespace}.${configPath} because namespace "${namespace}" is not a data namespace.`);
|
|
369
|
+
}
|
|
370
|
+
if (namespaceDefinition.readonly) {
|
|
371
|
+
throw new Error(`Cannot write ${namespace}.${configPath} because namespace "${namespace}" is readonly.`);
|
|
372
|
+
}
|
|
373
|
+
if (namespaceDefinition.sensitive) {
|
|
374
|
+
throw new Error(`Cannot write ${namespace}.${configPath} with the generic data writer because namespace "${namespace}" is sensitive.`);
|
|
375
|
+
}
|
|
333
376
|
const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
|
|
334
377
|
const profile = options.profile ?? runtime.graph.profile;
|
|
335
378
|
const filePath = resolveConfigDocumentPath(workspaceRoot, namespace, configPath, profile);
|
|
336
379
|
const document = await readYamlDocument(filePath);
|
|
337
380
|
const parsedValue = parseScalarValue(rawValue);
|
|
338
381
|
setNestedValue(document, configPath.split("."), parsedValue);
|
|
339
|
-
await mkdir(
|
|
382
|
+
await mkdir(path2.dirname(filePath), { recursive: true });
|
|
340
383
|
await writeFile(filePath, stringifyYaml(document), "utf8");
|
|
341
384
|
return {
|
|
342
385
|
filePath,
|
|
@@ -374,7 +417,7 @@ async function setSecret(configPath, rawValue, options = {}) {
|
|
|
374
417
|
};
|
|
375
418
|
}
|
|
376
419
|
setNestedValue(document, configPath.split("."), reference);
|
|
377
|
-
await mkdir(
|
|
420
|
+
await mkdir(path2.dirname(filePath), { recursive: true });
|
|
378
421
|
await writeFile(filePath, stringifyYaml(document), "utf8");
|
|
379
422
|
return {
|
|
380
423
|
filePath,
|
|
@@ -418,6 +461,19 @@ async function deleteValue(namespace, configPath, options = {}) {
|
|
|
418
461
|
return deleteSecret(configPath, options);
|
|
419
462
|
}
|
|
420
463
|
const runtime = await createRuntimeService(options);
|
|
464
|
+
if (namespace !== "value" && namespace !== "secret" && !runtime.manifest.namespaces[namespace]) {
|
|
465
|
+
throw new Error(`Cannot delete ${namespace}.${configPath} because namespace "${namespace}" is not declared in .cnos/cnos.yml.`);
|
|
466
|
+
}
|
|
467
|
+
const namespaceDefinition = getNamespaceDefinition(runtime.manifest, namespace);
|
|
468
|
+
if (namespaceDefinition.kind !== "data") {
|
|
469
|
+
throw new Error(`Cannot delete ${namespace}.${configPath} because namespace "${namespace}" is not a data namespace.`);
|
|
470
|
+
}
|
|
471
|
+
if (namespaceDefinition.readonly) {
|
|
472
|
+
throw new Error(`Cannot delete ${namespace}.${configPath} because namespace "${namespace}" is readonly.`);
|
|
473
|
+
}
|
|
474
|
+
if (namespaceDefinition.sensitive) {
|
|
475
|
+
throw new Error(`Cannot delete ${namespace}.${configPath} with the generic data writer because namespace "${namespace}" is sensitive.`);
|
|
476
|
+
}
|
|
421
477
|
const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
|
|
422
478
|
const profile = options.profile ?? runtime.graph.profile;
|
|
423
479
|
const filePath = resolveConfigDocumentPath(workspaceRoot, namespace, configPath, profile);
|
|
@@ -439,6 +495,7 @@ async function deleteValue(namespace, configPath, options = {}) {
|
|
|
439
495
|
// src/commands/define.ts
|
|
440
496
|
async function runDefine(namespace, configPath, rawValue, options = {}) {
|
|
441
497
|
const cliArgs = [...options.cliArgs ?? []];
|
|
498
|
+
const root = path3.resolve(options.root ?? process.cwd());
|
|
442
499
|
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
443
500
|
const local = consumeFlag(cliArgs, "--local");
|
|
444
501
|
const remote = consumeFlag(cliArgs, "--remote");
|
|
@@ -464,7 +521,7 @@ async function runDefine(namespace, configPath, rawValue, options = {}) {
|
|
|
464
521
|
value: result.value
|
|
465
522
|
});
|
|
466
523
|
}
|
|
467
|
-
return `defined ${namespace}.${configPath} in ${result.filePath}`;
|
|
524
|
+
return `defined ${namespace}.${configPath} in ${displayPath(result.filePath, root)}`;
|
|
468
525
|
}
|
|
469
526
|
|
|
470
527
|
// src/commands/drift.ts
|
|
@@ -517,7 +574,7 @@ async function runDiff(leftProfile, rightProfile, options = {}) {
|
|
|
517
574
|
|
|
518
575
|
// src/services/doctor.ts
|
|
519
576
|
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
520
|
-
import
|
|
577
|
+
import path4 from "path";
|
|
521
578
|
import {
|
|
522
579
|
detectLegacyVaultFormat,
|
|
523
580
|
isSecretReference as isSecretReference2,
|
|
@@ -539,7 +596,7 @@ async function createValidationSummary(options = {}) {
|
|
|
539
596
|
|
|
540
597
|
// src/services/doctor.ts
|
|
541
598
|
async function checkGitignore(root) {
|
|
542
|
-
const gitignorePath =
|
|
599
|
+
const gitignorePath = path4.join(root, ".gitignore");
|
|
543
600
|
const expected = [
|
|
544
601
|
".cnos/env/.env",
|
|
545
602
|
".cnos/env/.env.*",
|
|
@@ -574,12 +631,12 @@ async function collectYamlFiles(root) {
|
|
|
574
631
|
const entries = await readdir(root, { withFileTypes: true });
|
|
575
632
|
const results = [];
|
|
576
633
|
for (const entry of entries) {
|
|
577
|
-
const target =
|
|
634
|
+
const target = path4.join(root, entry.name);
|
|
578
635
|
if (entry.isDirectory()) {
|
|
579
636
|
results.push(...await collectYamlFiles(target));
|
|
580
637
|
continue;
|
|
581
638
|
}
|
|
582
|
-
if (entry.isFile() && [".yml", ".yaml"].includes(
|
|
639
|
+
if (entry.isFile() && [".yml", ".yaml"].includes(path4.extname(entry.name).toLowerCase())) {
|
|
583
640
|
results.push(target);
|
|
584
641
|
}
|
|
585
642
|
}
|
|
@@ -604,7 +661,7 @@ async function checkSecretSecurity(options, runtime) {
|
|
|
604
661
|
);
|
|
605
662
|
const legacyDetected = legacyPaths.filter((entry) => Boolean(entry.path));
|
|
606
663
|
const secretFiles = await Promise.all(
|
|
607
|
-
runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => collectYamlFiles(
|
|
664
|
+
runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => collectYamlFiles(path4.join(root.path, "secrets")))
|
|
608
665
|
);
|
|
609
666
|
const plaintextFiles = [];
|
|
610
667
|
for (const file of secretFiles.flat()) {
|
|
@@ -634,10 +691,11 @@ async function checkSecretSecurity(options, runtime) {
|
|
|
634
691
|
};
|
|
635
692
|
}
|
|
636
693
|
async function evaluateDoctor(options = {}) {
|
|
637
|
-
const root =
|
|
694
|
+
const root = path4.resolve(options.root ?? process.cwd());
|
|
638
695
|
const { runtime, summary } = await createValidationSummary(options);
|
|
639
696
|
const localRoot = runtime.graph.workspace.workspaceRoots.find((entry) => entry.scope === "local");
|
|
640
697
|
const globalRoot = runtime.graph.workspace.workspaceRoots.find((entry) => entry.scope === "global");
|
|
698
|
+
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
699
|
return [
|
|
642
700
|
{
|
|
643
701
|
name: "manifest",
|
|
@@ -649,6 +707,11 @@ async function evaluateDoctor(options = {}) {
|
|
|
649
707
|
ok: true,
|
|
650
708
|
details: `${runtime.graph.workspace.workspaceId} via ${runtime.graph.workspace.workspaceSource}`
|
|
651
709
|
},
|
|
710
|
+
{
|
|
711
|
+
name: "namespaces",
|
|
712
|
+
ok: true,
|
|
713
|
+
details: declaredCustomNamespaces.length === 0 ? "built-ins: value, secret, meta, process, public, env" : `built-ins: value, secret, meta, process, public, env | custom: ${declaredCustomNamespaces.join(", ")}`
|
|
714
|
+
},
|
|
652
715
|
{
|
|
653
716
|
name: "source-roots",
|
|
654
717
|
ok: Boolean(localRoot),
|
|
@@ -749,7 +812,7 @@ async function runCodegen(options = {}) {
|
|
|
749
812
|
|
|
750
813
|
// src/commands/exportEnv.ts
|
|
751
814
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
752
|
-
import
|
|
815
|
+
import path5 from "path";
|
|
753
816
|
function formatEnvOutput(env) {
|
|
754
817
|
return Object.entries(env).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
755
818
|
}
|
|
@@ -769,8 +832,8 @@ async function runExportEnv(options = {}) {
|
|
|
769
832
|
}) : runtime.toEnv();
|
|
770
833
|
const output = formatEnvOutput(env);
|
|
771
834
|
if (to) {
|
|
772
|
-
const targetPath =
|
|
773
|
-
await mkdir2(
|
|
835
|
+
const targetPath = path5.resolve(options.root ?? process.cwd(), to);
|
|
836
|
+
await mkdir2(path5.dirname(targetPath), { recursive: true });
|
|
774
837
|
await writeFile2(targetPath, output, "utf8");
|
|
775
838
|
if (options.json) {
|
|
776
839
|
return printJson({
|
|
@@ -1001,7 +1064,7 @@ var COMMANDS = [
|
|
|
1001
1064
|
id: "vault create",
|
|
1002
1065
|
summary: "Create a manifest-defined vault.",
|
|
1003
1066
|
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.",
|
|
1067
|
+
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
1068
|
examples: [
|
|
1006
1069
|
"cnos vault create local-dev",
|
|
1007
1070
|
"cnos vault create github-ci --provider github-secrets --no-passphrase"
|
|
@@ -1011,7 +1074,7 @@ var COMMANDS = [
|
|
|
1011
1074
|
id: "vault auth",
|
|
1012
1075
|
summary: "Authenticate a vault for the current shell session.",
|
|
1013
1076
|
usage: "cnos vault auth <name> [--store-keychain] [global-options]",
|
|
1014
|
-
description: "Authenticates
|
|
1077
|
+
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
1078
|
examples: ["cnos vault auth local-dev", "cnos vault auth local-dev --store-keychain"]
|
|
1016
1079
|
},
|
|
1017
1080
|
{
|
|
@@ -1084,11 +1147,11 @@ var COMMANDS = [
|
|
|
1084
1147
|
{
|
|
1085
1148
|
id: "list",
|
|
1086
1149
|
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.",
|
|
1150
|
+
usage: "cnos list [<namespace>|all] [--prefix <path>] [--framework <name>] [global-options]",
|
|
1151
|
+
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
1152
|
options: [
|
|
1090
1153
|
{
|
|
1091
|
-
flag: "--namespace <
|
|
1154
|
+
flag: "--namespace <name>",
|
|
1092
1155
|
description: "Explicit namespace selector when not using a positional namespace argument."
|
|
1093
1156
|
},
|
|
1094
1157
|
{
|
|
@@ -1100,7 +1163,7 @@ var COMMANDS = [
|
|
|
1100
1163
|
description: "When listing public output, apply framework-specific prefixes such as vite or next."
|
|
1101
1164
|
}
|
|
1102
1165
|
],
|
|
1103
|
-
examples: ["cnos list", "cnos list value --prefix app.", "cnos list env", "cnos list public --framework vite"]
|
|
1166
|
+
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
1167
|
},
|
|
1105
1168
|
{
|
|
1106
1169
|
id: "profile",
|
|
@@ -1110,11 +1173,16 @@ var COMMANDS = [
|
|
|
1110
1173
|
options: [
|
|
1111
1174
|
{
|
|
1112
1175
|
flag: "--inherit <name>",
|
|
1113
|
-
description: "Parent profile to extend when creating a profile."
|
|
1176
|
+
description: "Parent profile to extend when creating a profile. Base inheritance is implicit by default."
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
flag: "--no-inherit",
|
|
1180
|
+
description: "Create a clean profile that does not inherit base fallback layers."
|
|
1114
1181
|
}
|
|
1115
1182
|
],
|
|
1116
1183
|
examples: [
|
|
1117
|
-
"cnos profile create stage
|
|
1184
|
+
"cnos profile create stage",
|
|
1185
|
+
"cnos profile create isolated --no-inherit",
|
|
1118
1186
|
"cnos profile list",
|
|
1119
1187
|
"cnos profile use stage"
|
|
1120
1188
|
]
|
|
@@ -1122,9 +1190,9 @@ var COMMANDS = [
|
|
|
1122
1190
|
{
|
|
1123
1191
|
id: "profile create",
|
|
1124
1192
|
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
|
|
1193
|
+
usage: "cnos profile create <name> [--inherit <name> | --no-inherit] [--root <path>] [--json]",
|
|
1194
|
+
description: "Creates .cnos/profiles/<name>.yml for an explicit profile overlay. New profiles inherit base by default unless --no-inherit is set.",
|
|
1195
|
+
examples: ["cnos profile create stage", "cnos profile create isolated --no-inherit"]
|
|
1128
1196
|
},
|
|
1129
1197
|
{
|
|
1130
1198
|
id: "profile list",
|
|
@@ -1151,7 +1219,7 @@ var COMMANDS = [
|
|
|
1151
1219
|
id: "promote",
|
|
1152
1220
|
summary: "Promote shareable config into public or env projection surfaces.",
|
|
1153
1221
|
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.",
|
|
1222
|
+
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
1223
|
options: [
|
|
1156
1224
|
{
|
|
1157
1225
|
flag: "--to <public|env>",
|
|
@@ -1164,6 +1232,7 @@ var COMMANDS = [
|
|
|
1164
1232
|
],
|
|
1165
1233
|
examples: [
|
|
1166
1234
|
"cnos promote value.flag.auth.upi_enabled --to public",
|
|
1235
|
+
"cnos promote flags.upi_enabled --to public",
|
|
1167
1236
|
"cnos promote value.server.port --to env --as PORT"
|
|
1168
1237
|
]
|
|
1169
1238
|
},
|
|
@@ -1586,11 +1655,11 @@ function runHelpAi(topic, cliArgs = []) {
|
|
|
1586
1655
|
}
|
|
1587
1656
|
|
|
1588
1657
|
// src/commands/init.ts
|
|
1589
|
-
import
|
|
1658
|
+
import path7 from "path";
|
|
1590
1659
|
|
|
1591
1660
|
// src/services/scaffold.ts
|
|
1592
1661
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
1593
|
-
import
|
|
1662
|
+
import path6 from "path";
|
|
1594
1663
|
function scaffoldManifest(projectName, workspace) {
|
|
1595
1664
|
const lines = [
|
|
1596
1665
|
"version: 1",
|
|
@@ -1629,7 +1698,7 @@ async function ensureFile(filePath, content) {
|
|
|
1629
1698
|
}
|
|
1630
1699
|
}
|
|
1631
1700
|
async function ensureGitignore(root) {
|
|
1632
|
-
const gitignorePath =
|
|
1701
|
+
const gitignorePath = path6.join(root, ".gitignore");
|
|
1633
1702
|
const requiredEntries = [
|
|
1634
1703
|
".cnos/env/.env",
|
|
1635
1704
|
".cnos/env/.env.*",
|
|
@@ -1657,13 +1726,13 @@ async function ensureGitignore(root) {
|
|
|
1657
1726
|
return true;
|
|
1658
1727
|
}
|
|
1659
1728
|
async function scaffoldWorkspace(root, workspace) {
|
|
1660
|
-
const cnosRoot =
|
|
1661
|
-
const workspaceRoot = workspace ?
|
|
1729
|
+
const cnosRoot = path6.join(root, ".cnos");
|
|
1730
|
+
const workspaceRoot = workspace ? path6.join(cnosRoot, "workspaces", workspace) : cnosRoot;
|
|
1662
1731
|
const createdPaths = [];
|
|
1663
|
-
await mkdir3(
|
|
1664
|
-
await mkdir3(
|
|
1665
|
-
await mkdir3(
|
|
1666
|
-
await mkdir3(
|
|
1732
|
+
await mkdir3(path6.join(workspaceRoot, "profiles"), { recursive: true });
|
|
1733
|
+
await mkdir3(path6.join(workspaceRoot, "values"), { recursive: true });
|
|
1734
|
+
await mkdir3(path6.join(workspaceRoot, "secrets"), { recursive: true });
|
|
1735
|
+
await mkdir3(path6.join(workspaceRoot, "env"), { recursive: true });
|
|
1667
1736
|
const relativePaths = workspace ? [
|
|
1668
1737
|
["workspaces", workspace, "profiles", ".gitkeep"],
|
|
1669
1738
|
["workspaces", workspace, "values", ".gitkeep"],
|
|
@@ -1676,15 +1745,15 @@ async function scaffoldWorkspace(root, workspace) {
|
|
|
1676
1745
|
["env", ".gitkeep"]
|
|
1677
1746
|
];
|
|
1678
1747
|
for (const relativePath of relativePaths) {
|
|
1679
|
-
const filePath =
|
|
1748
|
+
const filePath = path6.join(cnosRoot, ...relativePath);
|
|
1680
1749
|
if (await ensureFile(filePath, "")) {
|
|
1681
|
-
createdPaths.push(
|
|
1750
|
+
createdPaths.push(path6.relative(root, filePath).replace(/\\/g, "/"));
|
|
1682
1751
|
}
|
|
1683
1752
|
}
|
|
1684
|
-
if (await ensureFile(
|
|
1753
|
+
if (await ensureFile(path6.join(cnosRoot, "cnos.yml"), scaffoldManifest(path6.basename(root), workspace))) {
|
|
1685
1754
|
createdPaths.push(".cnos/cnos.yml");
|
|
1686
1755
|
}
|
|
1687
|
-
if (workspace && await ensureFile(
|
|
1756
|
+
if (workspace && await ensureFile(path6.join(root, ".cnos-workspace.yml"), `workspace: ${workspace}
|
|
1688
1757
|
globalRoot: ~/.cnos
|
|
1689
1758
|
`)) {
|
|
1690
1759
|
createdPaths.push(".cnos-workspace.yml");
|
|
@@ -1701,7 +1770,7 @@ globalRoot: ~/.cnos
|
|
|
1701
1770
|
|
|
1702
1771
|
// src/commands/init.ts
|
|
1703
1772
|
async function runInit(options = {}) {
|
|
1704
|
-
const root =
|
|
1773
|
+
const root = path7.resolve(options.root ?? process.cwd());
|
|
1705
1774
|
const result = await scaffoldWorkspace(root, options.workspace);
|
|
1706
1775
|
if (options.json) {
|
|
1707
1776
|
return printJson(result);
|
|
@@ -1793,7 +1862,7 @@ function matchesPrefix(key, prefix) {
|
|
|
1793
1862
|
return key.startsWith(prefix) || key.split(".").slice(1).join(".").startsWith(prefix);
|
|
1794
1863
|
}
|
|
1795
1864
|
function toStoredEntry(namespace, entry, filter = {}) {
|
|
1796
|
-
const sourceId = namespace === "
|
|
1865
|
+
const sourceId = namespace === "secret" ? "filesystem-secrets" : "filesystem-values";
|
|
1797
1866
|
const candidates = [entry.winner, ...entry.overridden].filter((candidate) => candidate.sourceId === sourceId);
|
|
1798
1867
|
if (candidates.length === 0) {
|
|
1799
1868
|
return void 0;
|
|
@@ -1814,16 +1883,16 @@ function listStoredNamespace(namespace, options) {
|
|
|
1814
1883
|
}
|
|
1815
1884
|
function listProjectedNamespace(namespace, options) {
|
|
1816
1885
|
return createRuntimeService(options).then((runtime) => {
|
|
1817
|
-
const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? runtime.toEnv() : runtime.toPublicEnv({
|
|
1886
|
+
const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? runtime.toEnv() : namespace === "public" ? runtime.toPublicEnv({
|
|
1818
1887
|
...options.framework ? {
|
|
1819
1888
|
framework: options.framework
|
|
1820
1889
|
} : {}
|
|
1821
|
-
});
|
|
1890
|
+
}) : flattenObject2(runtime.toNamespace(namespace));
|
|
1822
1891
|
const entries = namespace === "env" ? Object.entries(projected).map(([envVar, value]) => ({
|
|
1823
1892
|
key: envVar,
|
|
1824
1893
|
value
|
|
1825
1894
|
})) : Object.entries(projected).map(([key, value]) => ({
|
|
1826
|
-
key: namespace === "meta" ?
|
|
1895
|
+
key: namespace === "meta" || namespace === "process" ? `${namespace}.${key}` : key,
|
|
1827
1896
|
value
|
|
1828
1897
|
}));
|
|
1829
1898
|
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 +1902,21 @@ async function listConfigEntries(namespace, options = {}) {
|
|
|
1833
1902
|
if (namespace === "value" || namespace === "secret") {
|
|
1834
1903
|
return listStoredNamespace(namespace, options);
|
|
1835
1904
|
}
|
|
1836
|
-
if (namespace === "meta" || namespace === "env" || namespace === "public") {
|
|
1905
|
+
if (namespace === "meta" || namespace === "env" || namespace === "public" || namespace === "process") {
|
|
1837
1906
|
return listProjectedNamespace(namespace, options);
|
|
1838
1907
|
}
|
|
1839
|
-
|
|
1840
|
-
listStoredNamespace(
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1908
|
+
if (namespace !== "all") {
|
|
1909
|
+
return listStoredNamespace(namespace, options);
|
|
1910
|
+
}
|
|
1911
|
+
const runtime = await createRuntimeService(options);
|
|
1912
|
+
const namespaces = Array.from(
|
|
1913
|
+
new Set(
|
|
1914
|
+
Array.from(runtime.graph.entries.values()).map((entry) => entry.namespace).filter((entry) => entry !== "meta" && entry !== "env" && entry !== "public")
|
|
1915
|
+
)
|
|
1916
|
+
).sort((left, right) => left.localeCompare(right));
|
|
1917
|
+
const stored = await Promise.all(namespaces.map((entry) => listStoredNamespace(entry, options)));
|
|
1918
|
+
const meta = await listProjectedNamespace("meta", options);
|
|
1919
|
+
return [...stored.flat(), ...meta].sort((left, right) => left.key.localeCompare(right.key));
|
|
1845
1920
|
}
|
|
1846
1921
|
|
|
1847
1922
|
// src/commands/list.ts
|
|
@@ -1858,13 +1933,16 @@ function normalizeNamespace(value) {
|
|
|
1858
1933
|
if (value === "meta") {
|
|
1859
1934
|
return "meta";
|
|
1860
1935
|
}
|
|
1936
|
+
if (value === "process") {
|
|
1937
|
+
return "process";
|
|
1938
|
+
}
|
|
1861
1939
|
if (value === "env") {
|
|
1862
1940
|
return "env";
|
|
1863
1941
|
}
|
|
1864
1942
|
if (value === "public") {
|
|
1865
1943
|
return "public";
|
|
1866
1944
|
}
|
|
1867
|
-
|
|
1945
|
+
return value;
|
|
1868
1946
|
}
|
|
1869
1947
|
async function runList(args = [], options = {}) {
|
|
1870
1948
|
const cliArgs = [...options.cliArgs ?? []];
|
|
@@ -1887,7 +1965,7 @@ async function runList(args = [], options = {}) {
|
|
|
1887
1965
|
}
|
|
1888
1966
|
|
|
1889
1967
|
// src/commands/migrate.ts
|
|
1890
|
-
import
|
|
1968
|
+
import path8 from "path";
|
|
1891
1969
|
import {
|
|
1892
1970
|
applyManifestMappings,
|
|
1893
1971
|
loadManifest,
|
|
@@ -1905,7 +1983,7 @@ async function runMigrate(options = {}) {
|
|
|
1905
1983
|
throw new Error(`Unknown migrate options: ${cliArgs.join(" ")}`);
|
|
1906
1984
|
}
|
|
1907
1985
|
const manifest = await loadManifest(options.root ? { root: options.root } : {});
|
|
1908
|
-
const scanRoot =
|
|
1986
|
+
const scanRoot = path8.resolve(manifest.repoRoot, scan ?? "src");
|
|
1909
1987
|
const usages = await scanEnvUsage(scanRoot);
|
|
1910
1988
|
const uniqueProposals = new Map(usages.map((usage) => [usage.envVar, proposeMapping(usage.envVar)]));
|
|
1911
1989
|
const proposals = Array.from(uniqueProposals.values()).sort((left, right) => left.envVar.localeCompare(right.envVar));
|
|
@@ -1966,33 +2044,118 @@ async function runMigrate(options = {}) {
|
|
|
1966
2044
|
return lines.join("\n");
|
|
1967
2045
|
}
|
|
1968
2046
|
|
|
2047
|
+
// src/commands/namespace.ts
|
|
2048
|
+
import path9 from "path";
|
|
2049
|
+
function normalizeCommand2(args) {
|
|
2050
|
+
const [actionOrPath, ...tail] = args;
|
|
2051
|
+
if (!actionOrPath) {
|
|
2052
|
+
return {
|
|
2053
|
+
action: "list",
|
|
2054
|
+
tail: []
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
if (["get", "set", "create", "add", "list", "delete", "remove"].includes(actionOrPath)) {
|
|
2058
|
+
return {
|
|
2059
|
+
action: actionOrPath === "remove" ? "delete" : actionOrPath === "create" || actionOrPath === "add" ? "set" : actionOrPath,
|
|
2060
|
+
tail
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
2063
|
+
return {
|
|
2064
|
+
action: "get",
|
|
2065
|
+
tail: args
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
async function runNamespace(namespace, args = [], options = {}) {
|
|
2069
|
+
const { action, tail } = normalizeCommand2(args);
|
|
2070
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
2071
|
+
const root = path9.resolve(options.root ?? process.cwd());
|
|
2072
|
+
if (action === "list") {
|
|
2073
|
+
const prefix = consumeOption(cliArgs, "--prefix");
|
|
2074
|
+
const entries = await listConfigEntries(namespace, {
|
|
2075
|
+
...options,
|
|
2076
|
+
cliArgs,
|
|
2077
|
+
...prefix ? { prefix } : {}
|
|
2078
|
+
});
|
|
2079
|
+
if (options.json) {
|
|
2080
|
+
return printJson(entries);
|
|
2081
|
+
}
|
|
2082
|
+
return entries.map((entry) => `${entry.key}=${printValue(entry.value)}`).join("\n");
|
|
2083
|
+
}
|
|
2084
|
+
if (action === "set") {
|
|
2085
|
+
const configPath2 = tail[0] ?? "app.name";
|
|
2086
|
+
const rawValue = tail[1] ?? "";
|
|
2087
|
+
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
2088
|
+
const result = await defineValue(namespace, configPath2, rawValue, {
|
|
2089
|
+
...options,
|
|
2090
|
+
cliArgs,
|
|
2091
|
+
target
|
|
2092
|
+
});
|
|
2093
|
+
if (options.json) {
|
|
2094
|
+
return printJson({
|
|
2095
|
+
namespace,
|
|
2096
|
+
path: configPath2,
|
|
2097
|
+
target,
|
|
2098
|
+
filePath: result.filePath,
|
|
2099
|
+
value: result.value
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
return `set ${namespace}.${configPath2} in ${displayPath(result.filePath, root)}`;
|
|
2103
|
+
}
|
|
2104
|
+
if (action === "delete") {
|
|
2105
|
+
const configPath2 = tail[0] ?? "app.name";
|
|
2106
|
+
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
2107
|
+
const result = await deleteValue(namespace, configPath2, {
|
|
2108
|
+
...options,
|
|
2109
|
+
cliArgs,
|
|
2110
|
+
target
|
|
2111
|
+
});
|
|
2112
|
+
if (options.json) {
|
|
2113
|
+
return printJson(result);
|
|
2114
|
+
}
|
|
2115
|
+
return result.deleted ? `deleted ${namespace}.${configPath2} from ${displayPath(result.filePath, root)}` : `no ${namespace}.${configPath2} found in ${displayPath(result.filePath, root)}`;
|
|
2116
|
+
}
|
|
2117
|
+
const runtime = await createRuntimeService(options);
|
|
2118
|
+
const configPath = tail[0] ?? "app.name";
|
|
2119
|
+
const value = runtime.read(`${namespace}.${configPath}`);
|
|
2120
|
+
if (value === void 0) {
|
|
2121
|
+
throw new Error(`Missing CNOS ${namespace} path: ${configPath}`);
|
|
2122
|
+
}
|
|
2123
|
+
if (options.json) {
|
|
2124
|
+
return printJson({
|
|
2125
|
+
key: `${namespace}.${configPath}`,
|
|
2126
|
+
value
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
return printValue(value);
|
|
2130
|
+
}
|
|
2131
|
+
|
|
1969
2132
|
// src/commands/onboard.ts
|
|
1970
2133
|
import { copyFile, readdir as readdir2, rm } from "fs/promises";
|
|
1971
|
-
import
|
|
2134
|
+
import path10 from "path";
|
|
1972
2135
|
var ROOT_ENV_FILE_PATTERN = /^\.env(?:\.[A-Za-z0-9_-]+)*(?:\.example)?$/;
|
|
1973
2136
|
async function listRootEnvFiles(root) {
|
|
1974
2137
|
const entries = await readdir2(root, { withFileTypes: true });
|
|
1975
2138
|
return entries.filter((entry) => entry.isFile() && ROOT_ENV_FILE_PATTERN.test(entry.name)).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
|
|
1976
2139
|
}
|
|
1977
2140
|
async function runOnboard(options = {}) {
|
|
1978
|
-
const root =
|
|
1979
|
-
const workspace = options.workspace ??
|
|
2141
|
+
const root = path10.resolve(options.root ?? process.cwd());
|
|
2142
|
+
const workspace = options.workspace ?? path10.basename(root);
|
|
1980
2143
|
const cliArgs = [...options.cliArgs ?? []];
|
|
1981
2144
|
const move = consumeFlag(cliArgs, "--move");
|
|
1982
2145
|
if (cliArgs.length > 0) {
|
|
1983
2146
|
throw new Error(`Unsupported onboard arguments: ${cliArgs.join(" ")}`);
|
|
1984
2147
|
}
|
|
1985
2148
|
const scaffold = await scaffoldWorkspace(root, workspace);
|
|
1986
|
-
const envRoot =
|
|
2149
|
+
const envRoot = path10.join(root, ".cnos", "workspaces", workspace, "env");
|
|
1987
2150
|
const rootFiles = await listRootEnvFiles(root);
|
|
1988
2151
|
const imported = [];
|
|
1989
2152
|
const skipped = [];
|
|
1990
2153
|
for (const fileName of rootFiles) {
|
|
1991
|
-
const sourcePath =
|
|
1992
|
-
const targetPath =
|
|
2154
|
+
const sourcePath = path10.join(root, fileName);
|
|
2155
|
+
const targetPath = path10.join(envRoot, fileName);
|
|
1993
2156
|
try {
|
|
1994
2157
|
await copyFile(sourcePath, targetPath);
|
|
1995
|
-
imported.push(
|
|
2158
|
+
imported.push(path10.relative(root, targetPath).replace(/\\/g, "/"));
|
|
1996
2159
|
if (move) {
|
|
1997
2160
|
await rm(sourcePath);
|
|
1998
2161
|
}
|
|
@@ -2017,14 +2180,14 @@ async function runOnboard(options = {}) {
|
|
|
2017
2180
|
}
|
|
2018
2181
|
|
|
2019
2182
|
// src/commands/profile.ts
|
|
2020
|
-
import
|
|
2183
|
+
import path13 from "path";
|
|
2021
2184
|
|
|
2022
2185
|
// src/services/context.ts
|
|
2023
2186
|
import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
2024
|
-
import
|
|
2187
|
+
import path11 from "path";
|
|
2025
2188
|
import { parseYaml as parseYaml3, stringifyYaml as stringifyYaml2 } from "@kitsy/cnos/internal";
|
|
2026
2189
|
async function loadCliContext(root = process.cwd()) {
|
|
2027
|
-
const filePath =
|
|
2190
|
+
const filePath = path11.join(path11.resolve(root), ".cnos-workspace.yml");
|
|
2028
2191
|
try {
|
|
2029
2192
|
const source = await readFile4(filePath, "utf8");
|
|
2030
2193
|
const parsed = parseYaml3(source);
|
|
@@ -2037,8 +2200,8 @@ async function loadCliContext(root = process.cwd()) {
|
|
|
2037
2200
|
}
|
|
2038
2201
|
}
|
|
2039
2202
|
async function saveCliContext(options = {}) {
|
|
2040
|
-
const root =
|
|
2041
|
-
const filePath =
|
|
2203
|
+
const root = path11.resolve(options.root ?? process.cwd());
|
|
2204
|
+
const filePath = path11.join(root, ".cnos-workspace.yml");
|
|
2042
2205
|
const current = await loadCliContext(root);
|
|
2043
2206
|
const next = {
|
|
2044
2207
|
...current.workspace ? { workspace: current.workspace } : {},
|
|
@@ -2057,12 +2220,19 @@ async function saveCliContext(options = {}) {
|
|
|
2057
2220
|
|
|
2058
2221
|
// src/services/profiles.ts
|
|
2059
2222
|
import { mkdir as mkdir4, readdir as readdir3, readFile as readFile5, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
2060
|
-
import
|
|
2223
|
+
import path12 from "path";
|
|
2061
2224
|
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 =
|
|
2225
|
+
async function createProfileDefinition(root = process.cwd(), profile, inherit, options = {}) {
|
|
2226
|
+
const filePath = path12.join(path12.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
2227
|
+
await mkdir4(path12.dirname(filePath), { recursive: true });
|
|
2228
|
+
const document = options.noInherit ? {
|
|
2229
|
+
name: profile,
|
|
2230
|
+
activate: {
|
|
2231
|
+
values: [`profiles/${profile}/values`, `values/${profile}`],
|
|
2232
|
+
secrets: [`profiles/${profile}/secrets`, `secrets/${profile}`],
|
|
2233
|
+
envFiles: [`.env.${profile}`]
|
|
2234
|
+
}
|
|
2235
|
+
} : inherit && inherit !== "base" ? {
|
|
2066
2236
|
name: profile,
|
|
2067
2237
|
extends: [inherit]
|
|
2068
2238
|
} : {
|
|
@@ -2072,11 +2242,12 @@ async function createProfileDefinition(root = process.cwd(), profile, inherit) {
|
|
|
2072
2242
|
return {
|
|
2073
2243
|
filePath,
|
|
2074
2244
|
profile,
|
|
2075
|
-
...inherit ? { inherit } : {}
|
|
2245
|
+
...inherit ? { inherit } : {},
|
|
2246
|
+
...options.noInherit ? { noInherit: true } : {}
|
|
2076
2247
|
};
|
|
2077
2248
|
}
|
|
2078
2249
|
async function listProfiles(root = process.cwd()) {
|
|
2079
|
-
const profilesRoot =
|
|
2250
|
+
const profilesRoot = path12.join(path12.resolve(root), ".cnos", "profiles");
|
|
2080
2251
|
try {
|
|
2081
2252
|
const entries = await readdir3(profilesRoot, { withFileTypes: true });
|
|
2082
2253
|
const discovered = /* @__PURE__ */ new Set(["base"]);
|
|
@@ -2091,7 +2262,7 @@ async function listProfiles(root = process.cwd()) {
|
|
|
2091
2262
|
}
|
|
2092
2263
|
}
|
|
2093
2264
|
async function deleteProfileDefinition(root = process.cwd(), profile) {
|
|
2094
|
-
const filePath =
|
|
2265
|
+
const filePath = path12.join(path12.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
2095
2266
|
try {
|
|
2096
2267
|
await rm2(filePath);
|
|
2097
2268
|
return {
|
|
@@ -2111,7 +2282,7 @@ async function readProfileDefinition(root = process.cwd(), profile = "base") {
|
|
|
2111
2282
|
name: "base"
|
|
2112
2283
|
};
|
|
2113
2284
|
}
|
|
2114
|
-
const filePath =
|
|
2285
|
+
const filePath = path12.join(path12.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
2115
2286
|
try {
|
|
2116
2287
|
return parseYaml4(await readFile5(filePath, "utf8")) ?? void 0;
|
|
2117
2288
|
} catch {
|
|
@@ -2135,16 +2306,23 @@ function normalizeProfileAction(args) {
|
|
|
2135
2306
|
}
|
|
2136
2307
|
async function runProfile(args, options = {}) {
|
|
2137
2308
|
const { action, tail } = normalizeProfileAction(args);
|
|
2138
|
-
const root =
|
|
2309
|
+
const root = path13.resolve(options.root ?? process.cwd());
|
|
2139
2310
|
const cliArgs = [...options.cliArgs ?? []];
|
|
2140
2311
|
if (action === "create") {
|
|
2141
2312
|
const profile = tail[0] ?? "stage";
|
|
2142
2313
|
const inherit = consumeOption(cliArgs, "--inherit");
|
|
2143
|
-
const
|
|
2314
|
+
const noInherit = consumeFlag(cliArgs, "--no-inherit");
|
|
2315
|
+
if (inherit && noInherit) {
|
|
2316
|
+
throw new Error("profile create accepts either --inherit <name> or --no-inherit, not both");
|
|
2317
|
+
}
|
|
2318
|
+
const result = await createProfileDefinition(root, profile, inherit, { noInherit });
|
|
2144
2319
|
if (options.json) {
|
|
2145
2320
|
return printJson(result);
|
|
2146
2321
|
}
|
|
2147
|
-
|
|
2322
|
+
if (noInherit) {
|
|
2323
|
+
return `created profile ${profile} at ${displayPath(result.filePath, root)} without inheriting base`;
|
|
2324
|
+
}
|
|
2325
|
+
return `created profile ${profile} at ${displayPath(result.filePath, root)}; inherits values from base by default`;
|
|
2148
2326
|
}
|
|
2149
2327
|
if (action === "use") {
|
|
2150
2328
|
const profile = tail[0] ?? "base";
|
|
@@ -2155,7 +2333,7 @@ async function runProfile(args, options = {}) {
|
|
|
2155
2333
|
if (options.json) {
|
|
2156
2334
|
return printJson(result);
|
|
2157
2335
|
}
|
|
2158
|
-
return `active profile set to ${profile} in ${result.filePath}`;
|
|
2336
|
+
return `active profile set to ${profile} in ${displayPath(result.filePath, root)}`;
|
|
2159
2337
|
}
|
|
2160
2338
|
if (action === "delete") {
|
|
2161
2339
|
const profile = tail[0] ?? "base";
|
|
@@ -2179,6 +2357,7 @@ async function runProfile(args, options = {}) {
|
|
|
2179
2357
|
}
|
|
2180
2358
|
|
|
2181
2359
|
// src/commands/promote.ts
|
|
2360
|
+
import path14 from "path";
|
|
2182
2361
|
import { writeFile as writeFile6 } from "fs/promises";
|
|
2183
2362
|
import {
|
|
2184
2363
|
ensureProjectionAllowed,
|
|
@@ -2195,6 +2374,7 @@ function sortRecord(record) {
|
|
|
2195
2374
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
2196
2375
|
}
|
|
2197
2376
|
async function runPromote(args = [], options = {}) {
|
|
2377
|
+
const root = path14.resolve(options.root ?? process.cwd());
|
|
2198
2378
|
const cliArgs = [...options.cliArgs ?? []];
|
|
2199
2379
|
const target = normalizeTarget(consumeOption(cliArgs, "--to"));
|
|
2200
2380
|
const alias = consumeOption(cliArgs, "--as");
|
|
@@ -2242,7 +2422,7 @@ async function runPromote(args = [], options = {}) {
|
|
|
2242
2422
|
manifestPath: loadedManifest.manifestPath
|
|
2243
2423
|
});
|
|
2244
2424
|
}
|
|
2245
|
-
return target === "public" ? `promoted ${keys.join(", ")} to public in ${loadedManifest.manifestPath}` : `promoted ${keys[0]} to env as ${alias} in ${loadedManifest.manifestPath}`;
|
|
2425
|
+
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
2426
|
}
|
|
2247
2427
|
|
|
2248
2428
|
// src/commands/read.ts
|
|
@@ -2253,7 +2433,9 @@ async function runRead(key, options = {}) {
|
|
|
2253
2433
|
throw new Error(`Missing CNOS config key: ${key}`);
|
|
2254
2434
|
}
|
|
2255
2435
|
const isSecret = key.startsWith("secret.");
|
|
2256
|
-
const
|
|
2436
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
2437
|
+
const reveal = consumeFlag(cliArgs, "--reveal");
|
|
2438
|
+
const valueForOutput = isSecret && !reveal ? maskSecretValue(value) : value;
|
|
2257
2439
|
if (options.json) {
|
|
2258
2440
|
return printJson({ key, value: valueForOutput });
|
|
2259
2441
|
}
|
|
@@ -2362,14 +2544,21 @@ async function runCommand(command, options = {}) {
|
|
|
2362
2544
|
});
|
|
2363
2545
|
}
|
|
2364
2546
|
|
|
2547
|
+
// src/commands/secret.ts
|
|
2548
|
+
import path17 from "path";
|
|
2549
|
+
|
|
2550
|
+
// src/commands/vault.ts
|
|
2551
|
+
import path16 from "path";
|
|
2552
|
+
|
|
2365
2553
|
// src/services/vaults.ts
|
|
2366
2554
|
import { rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
2367
|
-
import
|
|
2555
|
+
import path15 from "path";
|
|
2368
2556
|
import {
|
|
2369
2557
|
clearAllVaultSessionKeys,
|
|
2370
2558
|
clearVaultSessionKey,
|
|
2371
2559
|
createSecretVault,
|
|
2372
2560
|
deriveVaultKey,
|
|
2561
|
+
listLocalSecrets,
|
|
2373
2562
|
loadManifest as loadManifest3,
|
|
2374
2563
|
listSecretVaults,
|
|
2375
2564
|
readVaultMetadata,
|
|
@@ -2380,6 +2569,21 @@ import {
|
|
|
2380
2569
|
writeKeychain,
|
|
2381
2570
|
writeVaultSessionKey
|
|
2382
2571
|
} from "@kitsy/cnos/internal";
|
|
2572
|
+
function buildVaultDefinition(vault, provider) {
|
|
2573
|
+
return provider === "local" ? {
|
|
2574
|
+
provider: "local",
|
|
2575
|
+
auth: {
|
|
2576
|
+
passphrase: {
|
|
2577
|
+
from: defaultLocalAuthSources(vault)
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
} : {
|
|
2581
|
+
provider,
|
|
2582
|
+
auth: {
|
|
2583
|
+
method: "environment"
|
|
2584
|
+
}
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2383
2587
|
function sortVaults(vaults) {
|
|
2384
2588
|
return Object.fromEntries(Object.entries(vaults).sort(([left], [right]) => left.localeCompare(right)));
|
|
2385
2589
|
}
|
|
@@ -2394,27 +2598,27 @@ async function createVaultDefinition(name, options = {}) {
|
|
|
2394
2598
|
throw new Error("Local vaults cannot be passwordless.");
|
|
2395
2599
|
}
|
|
2396
2600
|
const loadedManifest = await loadManifest3(options.root ? { root: options.root } : {});
|
|
2601
|
+
const vaultDefinition = buildVaultDefinition(vault, provider);
|
|
2397
2602
|
const rawManifest = {
|
|
2398
2603
|
...loadedManifest.rawManifest,
|
|
2399
2604
|
vaults: {
|
|
2400
2605
|
...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
|
-
}
|
|
2606
|
+
[vault]: vaultDefinition
|
|
2414
2607
|
}
|
|
2415
2608
|
};
|
|
2416
2609
|
await writeFile7(loadedManifest.manifestPath, stringifyYaml5(rawManifest), "utf8");
|
|
2417
|
-
const definition = resolveVaultDefinition({ [vault]:
|
|
2610
|
+
const definition = resolveVaultDefinition({ [vault]: vaultDefinition }, vault);
|
|
2611
|
+
if (provider === "local") {
|
|
2612
|
+
const auth = await resolveVaultAuth2(vault, vaultDefinition, options.processEnv ?? process.env);
|
|
2613
|
+
if (!auth.passphrase) {
|
|
2614
|
+
throw new Error(`Vault "${vault}" requires passphrase-based authentication during creation.`);
|
|
2615
|
+
}
|
|
2616
|
+
const storeRoot = resolveSecretStoreRoot2(options.processEnv);
|
|
2617
|
+
const existing = await readVaultMetadata(storeRoot, vault);
|
|
2618
|
+
if (!existing) {
|
|
2619
|
+
await createSecretVault(storeRoot, vault, auth.passphrase);
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2418
2622
|
return {
|
|
2419
2623
|
...definition,
|
|
2420
2624
|
authMethod: definition.auth?.method ?? (provider === "local" ? "passphrase" : "environment"),
|
|
@@ -2454,7 +2658,7 @@ async function removeVaultDefinition(name, options = {}) {
|
|
|
2454
2658
|
delete rawManifest.vaults;
|
|
2455
2659
|
}
|
|
2456
2660
|
await writeFile7(loadedManifest.manifestPath, stringifyYaml5(rawManifest), "utf8");
|
|
2457
|
-
const vaultRoot =
|
|
2661
|
+
const vaultRoot = path15.join(resolveSecretStoreRoot2(options.processEnv), "vaults", vault);
|
|
2458
2662
|
let removedStore;
|
|
2459
2663
|
try {
|
|
2460
2664
|
await rm3(vaultRoot, { recursive: true, force: true });
|
|
@@ -2486,15 +2690,22 @@ async function authenticateVault(name, options = {}) {
|
|
|
2486
2690
|
if (!auth.passphrase) {
|
|
2487
2691
|
throw new Error(`Vault "${vault}" requires passphrase-based authentication.`);
|
|
2488
2692
|
}
|
|
2489
|
-
const existing = await readVaultMetadata(storeRoot, vault);
|
|
2490
|
-
if (!existing) {
|
|
2491
|
-
await createSecretVault(storeRoot, vault, auth.passphrase);
|
|
2492
|
-
}
|
|
2493
2693
|
const metadata = await readVaultMetadata(storeRoot, vault);
|
|
2494
2694
|
if (!metadata) {
|
|
2495
|
-
throw new Error(
|
|
2695
|
+
throw new Error(
|
|
2696
|
+
`Vault "${vault}" has not been initialized yet. Run cnos vault create ${vault} first.`
|
|
2697
|
+
);
|
|
2496
2698
|
}
|
|
2497
2699
|
const derivedKey = deriveVaultKey(auth.passphrase, Buffer.from(metadata.salt, "base64"), metadata.iterations);
|
|
2700
|
+
await listLocalSecrets(
|
|
2701
|
+
storeRoot,
|
|
2702
|
+
{
|
|
2703
|
+
derivedKey,
|
|
2704
|
+
method: auth.method,
|
|
2705
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
2706
|
+
},
|
|
2707
|
+
vault
|
|
2708
|
+
);
|
|
2498
2709
|
const sessionPath2 = await writeVaultSessionKey(vault, derivedKey, options.processEnv);
|
|
2499
2710
|
if (options.storeKeychain) {
|
|
2500
2711
|
await writeKeychain(`cnos/${vault}`, derivedKey.toString("hex"));
|
|
@@ -2541,6 +2752,7 @@ function normalizeVaultAction(args) {
|
|
|
2541
2752
|
async function runVault(args = [], options = {}) {
|
|
2542
2753
|
const { action, tail } = normalizeVaultAction(args);
|
|
2543
2754
|
const cliArgs = [...options.cliArgs ?? []];
|
|
2755
|
+
const root = path16.resolve(options.root ?? process.cwd());
|
|
2544
2756
|
if (consumeOption(cliArgs, "--passphrase")) {
|
|
2545
2757
|
throw new Error("The --passphrase option is not supported in CNOS 1.4. Use env, keychain, or prompt-based auth.");
|
|
2546
2758
|
}
|
|
@@ -2557,7 +2769,7 @@ async function runVault(args = [], options = {}) {
|
|
|
2557
2769
|
if (options.json) {
|
|
2558
2770
|
return printJson(result);
|
|
2559
2771
|
}
|
|
2560
|
-
return `created vault "${result.name}" with provider "${result.provider}" in ${result.manifestPath}`;
|
|
2772
|
+
return `created vault "${result.name}" with provider "${result.provider}" in ${displayPath(result.manifestPath, root)}`;
|
|
2561
2773
|
}
|
|
2562
2774
|
if (action === "auth") {
|
|
2563
2775
|
const result = await authenticateVault(tail[0] ?? "default", {
|
|
@@ -2646,6 +2858,7 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
2646
2858
|
const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
|
|
2647
2859
|
const { action, tail } = normalizeSecretCommand(args);
|
|
2648
2860
|
const cliArgs = [...options.cliArgs ?? []];
|
|
2861
|
+
const root = path17.resolve(options.root ?? process.cwd());
|
|
2649
2862
|
if (consumeOption(cliArgs, "--passphrase")) {
|
|
2650
2863
|
throw new Error("The --passphrase option is not supported in CNOS 1.4. Use env, keychain, or prompt-based auth.");
|
|
2651
2864
|
}
|
|
@@ -2701,7 +2914,7 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
2701
2914
|
if (options.json) {
|
|
2702
2915
|
return printJson(result);
|
|
2703
2916
|
}
|
|
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}`;
|
|
2917
|
+
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
2918
|
}
|
|
2706
2919
|
if (action === "delete") {
|
|
2707
2920
|
const secretPath2 = tail[0];
|
|
@@ -2714,7 +2927,7 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
2714
2927
|
if (options.json) {
|
|
2715
2928
|
return printJson(result);
|
|
2716
2929
|
}
|
|
2717
|
-
return result.deleted ? `deleted secret.${secretPath2} from ${result.filePath}` : `no secret.${secretPath2} found in ${result.filePath}`;
|
|
2930
|
+
return result.deleted ? `deleted secret.${secretPath2} from ${displayPath(result.filePath, root)}` : `no secret.${secretPath2} found in ${displayPath(result.filePath, root)}`;
|
|
2718
2931
|
}
|
|
2719
2932
|
const runtime = await createRuntimeService(options);
|
|
2720
2933
|
const secretPath = tail[0] ?? "app.token";
|
|
@@ -2741,12 +2954,12 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
2741
2954
|
}
|
|
2742
2955
|
|
|
2743
2956
|
// src/commands/use.ts
|
|
2744
|
-
import
|
|
2957
|
+
import path18 from "path";
|
|
2745
2958
|
async function runUse(args = [], options = {}) {
|
|
2746
|
-
const root =
|
|
2747
|
-
const action = args[0]
|
|
2959
|
+
const root = path18.resolve(options.root ?? process.cwd());
|
|
2960
|
+
const action = args[0];
|
|
2748
2961
|
const hasUpdates = Boolean(options.workspace || options.profile || options.globalRoot);
|
|
2749
|
-
if (action === "show" || !hasUpdates) {
|
|
2962
|
+
if (action === "show" || !action && !hasUpdates) {
|
|
2750
2963
|
const context = await loadCliContext(root);
|
|
2751
2964
|
if (options.json) {
|
|
2752
2965
|
return printJson(context);
|
|
@@ -2762,7 +2975,7 @@ async function runUse(args = [], options = {}) {
|
|
|
2762
2975
|
if (options.json) {
|
|
2763
2976
|
return printJson(result);
|
|
2764
2977
|
}
|
|
2765
|
-
return `updated CLI context in ${result.filePath}`;
|
|
2978
|
+
return `updated CLI context in ${displayPath(result.filePath, root)}`;
|
|
2766
2979
|
}
|
|
2767
2980
|
|
|
2768
2981
|
// src/commands/validate.ts
|
|
@@ -2780,7 +2993,7 @@ async function runValidate(options = {}) {
|
|
|
2780
2993
|
// package.json
|
|
2781
2994
|
var package_default = {
|
|
2782
2995
|
name: "@kitsy/cnos-cli",
|
|
2783
|
-
version: "1.
|
|
2996
|
+
version: "1.4.0",
|
|
2784
2997
|
description: "CLI entry point and developer tooling for CNOS.",
|
|
2785
2998
|
type: "module",
|
|
2786
2999
|
main: "./dist/index.js",
|
|
@@ -2835,6 +3048,7 @@ function runVersion() {
|
|
|
2835
3048
|
}
|
|
2836
3049
|
|
|
2837
3050
|
// src/commands/value.ts
|
|
3051
|
+
import path19 from "path";
|
|
2838
3052
|
function normalizeValueCommand(args) {
|
|
2839
3053
|
const [actionOrPath, ...tail] = args;
|
|
2840
3054
|
if (!actionOrPath) {
|
|
@@ -2858,6 +3072,7 @@ async function runValue(argsOrPath, options = {}) {
|
|
|
2858
3072
|
const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
|
|
2859
3073
|
const { action, tail } = normalizeValueCommand(args);
|
|
2860
3074
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3075
|
+
const root = path19.resolve(options.root ?? process.cwd());
|
|
2861
3076
|
if (action === "list") {
|
|
2862
3077
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
2863
3078
|
const entries = await listConfigEntries("value", {
|
|
@@ -2888,7 +3103,7 @@ async function runValue(argsOrPath, options = {}) {
|
|
|
2888
3103
|
value: result.value
|
|
2889
3104
|
});
|
|
2890
3105
|
}
|
|
2891
|
-
return `set value.${valuePath} in ${result.filePath}`;
|
|
3106
|
+
return `set value.${valuePath} in ${displayPath(result.filePath, root)}`;
|
|
2892
3107
|
}
|
|
2893
3108
|
if (action === "delete") {
|
|
2894
3109
|
const valuePath = tail[0] ?? "app.name";
|
|
@@ -2901,7 +3116,7 @@ async function runValue(argsOrPath, options = {}) {
|
|
|
2901
3116
|
if (options.json) {
|
|
2902
3117
|
return printJson(result);
|
|
2903
3118
|
}
|
|
2904
|
-
return result.deleted ? `deleted value.${valuePath} from ${result.filePath}` : `no value.${valuePath} found in ${result.filePath}`;
|
|
3119
|
+
return result.deleted ? `deleted value.${valuePath} from ${displayPath(result.filePath, root)}` : `no value.${valuePath} found in ${displayPath(result.filePath, root)}`;
|
|
2905
3120
|
}
|
|
2906
3121
|
const runtime = await createRuntimeService(options);
|
|
2907
3122
|
const value = runtime.value(tail[0] ?? "app.name");
|
|
@@ -3260,6 +3475,11 @@ async function main(argv) {
|
|
|
3260
3475
|
`);
|
|
3261
3476
|
return;
|
|
3262
3477
|
default:
|
|
3478
|
+
if (args.length > 0 && /^[a-z][a-z0-9-]*$/i.test(command)) {
|
|
3479
|
+
process.stdout.write(`${await runNamespace(command, args, runtimeOptions)}
|
|
3480
|
+
`);
|
|
3481
|
+
return;
|
|
3482
|
+
}
|
|
3263
3483
|
process.stderr.write(`Unknown command: ${command}
|
|
3264
3484
|
`);
|
|
3265
3485
|
process.exitCode = 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitsy/cnos-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
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.4.0"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsup src/index.ts --format esm --dts",
|