@kitsy/cnos-cli 0.0.1 → 1.0.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 +980 -99
- package/package.json +2 -3
package/dist/index.js
CHANGED
|
@@ -7,8 +7,50 @@ var OPTION_KEYS = {
|
|
|
7
7
|
"--profile": "profile",
|
|
8
8
|
"--global-root": "globalRoot"
|
|
9
9
|
};
|
|
10
|
-
var COMMAND_OPTION_KEYS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
11
|
-
|
|
10
|
+
var COMMAND_OPTION_KEYS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
11
|
+
"--format",
|
|
12
|
+
"--framework",
|
|
13
|
+
"--prefix",
|
|
14
|
+
"--target",
|
|
15
|
+
"--to",
|
|
16
|
+
"--provider",
|
|
17
|
+
"--passphrase",
|
|
18
|
+
"--inherit"
|
|
19
|
+
]);
|
|
20
|
+
var COMMAND_FLAG_KEYS = /* @__PURE__ */ new Set(["--flatten", "--public", "--local", "--remote", "--ref"]);
|
|
21
|
+
function normalizeCommand(argv) {
|
|
22
|
+
const [command = "doctor", ...rest] = argv;
|
|
23
|
+
const resource = rest[0];
|
|
24
|
+
const remaining = rest.slice(1);
|
|
25
|
+
if ((command === "create" || command === "add") && resource === "profile") {
|
|
26
|
+
return ["profile", "create", ...remaining];
|
|
27
|
+
}
|
|
28
|
+
if ((command === "delete" || command === "remove") && resource === "profile") {
|
|
29
|
+
return ["profile", "delete", ...remaining];
|
|
30
|
+
}
|
|
31
|
+
if (command === "list" && resource === "profile") {
|
|
32
|
+
return ["profile", "list", ...remaining];
|
|
33
|
+
}
|
|
34
|
+
if ((command === "create" || command === "add") && resource === "secret") {
|
|
35
|
+
return ["secret", "set", ...remaining];
|
|
36
|
+
}
|
|
37
|
+
if ((command === "delete" || command === "remove") && resource === "secret") {
|
|
38
|
+
return ["secret", "delete", ...remaining];
|
|
39
|
+
}
|
|
40
|
+
if (command === "list" && resource === "secret") {
|
|
41
|
+
return ["secret", "list", ...remaining];
|
|
42
|
+
}
|
|
43
|
+
if ((command === "create" || command === "add") && resource === "value") {
|
|
44
|
+
return ["value", "set", ...remaining];
|
|
45
|
+
}
|
|
46
|
+
if ((command === "delete" || command === "remove") && resource === "value") {
|
|
47
|
+
return ["value", "delete", ...remaining];
|
|
48
|
+
}
|
|
49
|
+
if (command === "list" && resource === "value") {
|
|
50
|
+
return ["value", "list", ...remaining];
|
|
51
|
+
}
|
|
52
|
+
return [command, ...rest];
|
|
53
|
+
}
|
|
12
54
|
function setOption(options, key, value) {
|
|
13
55
|
options[key] = value;
|
|
14
56
|
}
|
|
@@ -23,7 +65,18 @@ function parseArgs(argv) {
|
|
|
23
65
|
passthrough: []
|
|
24
66
|
};
|
|
25
67
|
}
|
|
26
|
-
|
|
68
|
+
if (argv[0] === "--version" || argv[0] === "-v") {
|
|
69
|
+
return {
|
|
70
|
+
command: "version",
|
|
71
|
+
args: [],
|
|
72
|
+
options: {
|
|
73
|
+
cliArgs: []
|
|
74
|
+
},
|
|
75
|
+
passthrough: []
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const normalizedArgv = normalizeCommand(argv);
|
|
79
|
+
const [command = "doctor", ...rest] = normalizedArgv;
|
|
27
80
|
const options = {};
|
|
28
81
|
const args = [];
|
|
29
82
|
const cliArgs = [];
|
|
@@ -128,15 +181,24 @@ function printJson(value) {
|
|
|
128
181
|
}
|
|
129
182
|
|
|
130
183
|
// src/services/writes.ts
|
|
131
|
-
import {
|
|
184
|
+
import { randomUUID } from "crypto";
|
|
185
|
+
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
132
186
|
import path from "path";
|
|
133
|
-
import {
|
|
187
|
+
import {
|
|
188
|
+
parseYaml,
|
|
189
|
+
resolveConfigDocumentPath,
|
|
190
|
+
resolveSecretStoreRoot,
|
|
191
|
+
stringifyYaml,
|
|
192
|
+
writeLocalSecret
|
|
193
|
+
} from "@kitsy/cnos/internal";
|
|
134
194
|
|
|
135
195
|
// src/services/runtime.ts
|
|
136
196
|
import { createCnos } from "@kitsy/cnos";
|
|
137
197
|
async function createRuntimeService(options = {}) {
|
|
138
198
|
const createOptions = {
|
|
139
|
-
|
|
199
|
+
...options.root ? {
|
|
200
|
+
root: options.root
|
|
201
|
+
} : {},
|
|
140
202
|
...options.workspace ? {
|
|
141
203
|
workspace: options.workspace
|
|
142
204
|
} : {},
|
|
@@ -181,8 +243,38 @@ function parseScalarValue(rawValue) {
|
|
|
181
243
|
return rawValue;
|
|
182
244
|
}
|
|
183
245
|
}
|
|
184
|
-
|
|
185
|
-
const
|
|
246
|
+
function unsetNestedValue(target, pathSegments) {
|
|
247
|
+
const [head, ...tail] = pathSegments;
|
|
248
|
+
if (!head || !(head in target)) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
if (tail.length === 0) {
|
|
252
|
+
delete target[head];
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
const nextValue = target[head];
|
|
256
|
+
if (!nextValue || typeof nextValue !== "object" || Array.isArray(nextValue)) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
const deleted = unsetNestedValue(nextValue, tail);
|
|
260
|
+
if (deleted && Object.keys(nextValue).length === 0) {
|
|
261
|
+
delete target[head];
|
|
262
|
+
}
|
|
263
|
+
return deleted;
|
|
264
|
+
}
|
|
265
|
+
function isSecretReference(value) {
|
|
266
|
+
return Boolean(
|
|
267
|
+
value && typeof value === "object" && !Array.isArray(value) && typeof value.provider === "string" && typeof value.ref === "string"
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
async function readYamlDocument(filePath) {
|
|
271
|
+
try {
|
|
272
|
+
return parseYaml(await readFile(filePath, "utf8")) ?? {};
|
|
273
|
+
} catch {
|
|
274
|
+
return {};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function getSelectedWorkspaceRoot(options, runtime) {
|
|
186
278
|
const target = options.target ?? "local";
|
|
187
279
|
const workspaceRoot = runtime.graph.workspace.workspaceRoots.find(
|
|
188
280
|
(entry) => entry.scope === target && entry.workspaceId === runtime.graph.workspace.workspaceId
|
|
@@ -193,18 +285,27 @@ async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
|
193
285
|
if (target === "global" && !runtime.manifest.workspaces.global.allowWrite) {
|
|
194
286
|
throw new Error("Global writes require workspaces.global.allowWrite: true");
|
|
195
287
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
288
|
+
return workspaceRoot.path;
|
|
289
|
+
}
|
|
290
|
+
async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
291
|
+
if (namespace === "secret") {
|
|
292
|
+
const secret = await setSecret(configPath, rawValue, {
|
|
293
|
+
...options,
|
|
294
|
+
mode: options.mode ?? "local"
|
|
295
|
+
});
|
|
296
|
+
return {
|
|
297
|
+
filePath: secret.filePath,
|
|
298
|
+
value: {
|
|
299
|
+
provider: secret.provider,
|
|
300
|
+
ref: secret.ref
|
|
301
|
+
}
|
|
302
|
+
};
|
|
207
303
|
}
|
|
304
|
+
const runtime = await createRuntimeService(options);
|
|
305
|
+
const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
|
|
306
|
+
const profile = options.profile ?? runtime.graph.profile;
|
|
307
|
+
const filePath = resolveConfigDocumentPath(workspaceRoot, namespace, configPath, profile);
|
|
308
|
+
const document = await readYamlDocument(filePath);
|
|
208
309
|
const parsedValue = parseScalarValue(rawValue);
|
|
209
310
|
setNestedValue(document, configPath.split("."), parsedValue);
|
|
210
311
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
@@ -214,15 +315,118 @@ async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
|
214
315
|
value: parsedValue
|
|
215
316
|
};
|
|
216
317
|
}
|
|
318
|
+
async function setSecret(configPath, rawValue, options = {}) {
|
|
319
|
+
const runtime = await createRuntimeService(options);
|
|
320
|
+
const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
|
|
321
|
+
const profile = options.profile ?? runtime.graph.profile;
|
|
322
|
+
const filePath = resolveConfigDocumentPath(workspaceRoot, "secret", configPath, profile);
|
|
323
|
+
const document = await readYamlDocument(filePath);
|
|
324
|
+
const mode = options.mode ?? "local";
|
|
325
|
+
let reference;
|
|
326
|
+
let storePath;
|
|
327
|
+
if (mode === "local") {
|
|
328
|
+
const passphrase = options.passphrase ?? options.processEnv?.CNOS_SECRET_PASSPHRASE ?? process.env.CNOS_SECRET_PASSPHRASE;
|
|
329
|
+
if (!passphrase) {
|
|
330
|
+
throw new Error("Local secret writes require --passphrase or CNOS_SECRET_PASSPHRASE");
|
|
331
|
+
}
|
|
332
|
+
const profileSegment = profile && profile !== "base" ? profile : "base";
|
|
333
|
+
const ref = [
|
|
334
|
+
runtime.manifest.project.name,
|
|
335
|
+
runtime.graph.workspace.workspaceId,
|
|
336
|
+
profileSegment,
|
|
337
|
+
...configPath.split("."),
|
|
338
|
+
randomUUID()
|
|
339
|
+
].map((segment) => segment.replace(/[^A-Za-z0-9._-]+/g, "-")).join("/");
|
|
340
|
+
storePath = await writeLocalSecret(resolveSecretStoreRoot(options.processEnv), ref, rawValue, passphrase);
|
|
341
|
+
reference = {
|
|
342
|
+
provider: "local",
|
|
343
|
+
ref
|
|
344
|
+
};
|
|
345
|
+
} else {
|
|
346
|
+
reference = {
|
|
347
|
+
provider: options.provider ?? (mode === "ref" ? "ref" : "remote"),
|
|
348
|
+
ref: rawValue
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
setNestedValue(document, configPath.split("."), reference);
|
|
352
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
353
|
+
await writeFile(filePath, stringifyYaml(document), "utf8");
|
|
354
|
+
return {
|
|
355
|
+
filePath,
|
|
356
|
+
provider: reference.provider,
|
|
357
|
+
ref: reference.ref,
|
|
358
|
+
...storePath ? { storePath } : {}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
async function deleteSecret(configPath, options = {}) {
|
|
362
|
+
const runtime = await createRuntimeService(options);
|
|
363
|
+
const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
|
|
364
|
+
const profile = options.profile ?? runtime.graph.profile;
|
|
365
|
+
const filePath = resolveConfigDocumentPath(workspaceRoot, "secret", configPath, profile);
|
|
366
|
+
const document = await readYamlDocument(filePath);
|
|
367
|
+
const metadata = runtime.graph.entries.get(`secret.${configPath}`)?.winner.metadata;
|
|
368
|
+
const deleted = unsetNestedValue(document, configPath.split("."));
|
|
369
|
+
if (!deleted) {
|
|
370
|
+
return {
|
|
371
|
+
filePath,
|
|
372
|
+
deleted: false
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
await writeFile(filePath, stringifyYaml(document), "utf8");
|
|
376
|
+
let removedStore;
|
|
377
|
+
const secretRef = metadata?.secretRef;
|
|
378
|
+
if (isSecretReference(secretRef) && secretRef.provider === "local") {
|
|
379
|
+
const storePath = path.join(resolveSecretStoreRoot(options.processEnv), "store", ...secretRef.ref.split("/")).concat(".json");
|
|
380
|
+
await rm(storePath, { force: true });
|
|
381
|
+
removedStore = storePath;
|
|
382
|
+
}
|
|
383
|
+
return {
|
|
384
|
+
filePath,
|
|
385
|
+
deleted: true,
|
|
386
|
+
...removedStore ? { removedStore } : {}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
async function deleteValue(namespace, configPath, options = {}) {
|
|
390
|
+
if (namespace === "secret") {
|
|
391
|
+
return deleteSecret(configPath, options);
|
|
392
|
+
}
|
|
393
|
+
const runtime = await createRuntimeService(options);
|
|
394
|
+
const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
|
|
395
|
+
const profile = options.profile ?? runtime.graph.profile;
|
|
396
|
+
const filePath = resolveConfigDocumentPath(workspaceRoot, namespace, configPath, profile);
|
|
397
|
+
const document = await readYamlDocument(filePath);
|
|
398
|
+
const deleted = unsetNestedValue(document, configPath.split("."));
|
|
399
|
+
if (!deleted) {
|
|
400
|
+
return {
|
|
401
|
+
filePath,
|
|
402
|
+
deleted: false
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
await writeFile(filePath, stringifyYaml(document), "utf8");
|
|
406
|
+
return {
|
|
407
|
+
filePath,
|
|
408
|
+
deleted: true
|
|
409
|
+
};
|
|
410
|
+
}
|
|
217
411
|
|
|
218
412
|
// src/commands/define.ts
|
|
219
413
|
async function runDefine(namespace, configPath, rawValue, options = {}) {
|
|
220
414
|
const cliArgs = [...options.cliArgs ?? []];
|
|
221
415
|
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
416
|
+
const local = consumeFlag(cliArgs, "--local");
|
|
417
|
+
const remote = consumeFlag(cliArgs, "--remote");
|
|
418
|
+
const ref = consumeFlag(cliArgs, "--ref");
|
|
419
|
+
const provider = consumeOption(cliArgs, "--provider");
|
|
420
|
+
const passphrase = consumeOption(cliArgs, "--passphrase");
|
|
222
421
|
const result = await defineValue(namespace, configPath, rawValue, {
|
|
223
422
|
...options,
|
|
224
423
|
cliArgs,
|
|
225
|
-
target
|
|
424
|
+
target,
|
|
425
|
+
...namespace === "secret" ? {
|
|
426
|
+
mode: local ? "local" : remote ? "remote" : ref ? "ref" : "local",
|
|
427
|
+
...provider ? { provider } : {},
|
|
428
|
+
...passphrase ? { passphrase } : {}
|
|
429
|
+
} : {}
|
|
226
430
|
});
|
|
227
431
|
if (options.json) {
|
|
228
432
|
return printJson({
|
|
@@ -237,7 +441,7 @@ async function runDefine(namespace, configPath, rawValue, options = {}) {
|
|
|
237
441
|
}
|
|
238
442
|
|
|
239
443
|
// src/commands/diff.ts
|
|
240
|
-
import { flattenObject } from "@kitsy/cnos
|
|
444
|
+
import { flattenObject } from "@kitsy/cnos/internal";
|
|
241
445
|
function flattenRuntime(runtime) {
|
|
242
446
|
return {
|
|
243
447
|
...Object.fromEntries(
|
|
@@ -278,7 +482,7 @@ import { readFile as readFile2 } from "fs/promises";
|
|
|
278
482
|
import path2 from "path";
|
|
279
483
|
|
|
280
484
|
// src/services/validation.ts
|
|
281
|
-
import { validateRuntime } from "@kitsy/cnos
|
|
485
|
+
import { validateRuntime } from "@kitsy/cnos/internal";
|
|
282
486
|
async function createValidationSummary(options = {}) {
|
|
283
487
|
const runtime = await createRuntimeService(options);
|
|
284
488
|
const summary = await validateRuntime(runtime);
|
|
@@ -292,11 +496,14 @@ async function createValidationSummary(options = {}) {
|
|
|
292
496
|
async function checkGitignore(root) {
|
|
293
497
|
const gitignorePath = path2.join(root, ".gitignore");
|
|
294
498
|
const expected = [
|
|
295
|
-
"cnos/
|
|
296
|
-
"cnos/
|
|
297
|
-
"cnos/
|
|
298
|
-
"
|
|
299
|
-
"
|
|
499
|
+
".cnos/env/.env",
|
|
500
|
+
".cnos/env/.env.*",
|
|
501
|
+
"!.cnos/env/.env.example",
|
|
502
|
+
"!.cnos/env/.env.*.example",
|
|
503
|
+
".cnos/workspaces/*/env/.env",
|
|
504
|
+
".cnos/workspaces/*/env/.env.*",
|
|
505
|
+
"!.cnos/workspaces/*/env/.env.example",
|
|
506
|
+
"!.cnos/workspaces/*/env/.env.*.example"
|
|
300
507
|
];
|
|
301
508
|
try {
|
|
302
509
|
const content = await readFile2(gitignorePath, "utf8");
|
|
@@ -448,14 +655,14 @@ var COMMANDS = [
|
|
|
448
655
|
id: "init",
|
|
449
656
|
summary: "Scaffold a workspace-aware CNOS tree in the current project.",
|
|
450
657
|
usage: "cnos init [--workspace <id>] [--root <path>] [--json]",
|
|
451
|
-
description: "Creates cnos/cnos.yml, .cnos-workspace.yml,
|
|
658
|
+
description: "Creates .cnos/cnos.yml, optional .cnos-workspace.yml, config folders, and .gitignore entries without overwriting existing files.",
|
|
452
659
|
examples: ["cnos init", "cnos init --workspace api", "cnos init --root ./apps/api --workspace api --json"]
|
|
453
660
|
},
|
|
454
661
|
{
|
|
455
662
|
id: "onboard",
|
|
456
663
|
summary: "Onboard an existing repo into CNOS and import root dotenv files.",
|
|
457
664
|
usage: "cnos onboard [--workspace <id>] [--root <path>] [--move] [--json]",
|
|
458
|
-
description: "Scaffolds the CNOS workspace tree and imports root-level .env, .env.<profile>, and .env.*.example files into cnos/workspaces/<workspace>/env.",
|
|
665
|
+
description: "Scaffolds the CNOS workspace tree and imports root-level .env, .env.<profile>, and .env.*.example files into .cnos/workspaces/<workspace>/env.",
|
|
459
666
|
options: [
|
|
460
667
|
{
|
|
461
668
|
flag: "--move",
|
|
@@ -480,37 +687,98 @@ var COMMANDS = [
|
|
|
480
687
|
},
|
|
481
688
|
{
|
|
482
689
|
id: "value",
|
|
483
|
-
summary: "Read
|
|
484
|
-
usage: "cnos value <path> [global-options]",
|
|
485
|
-
description: "Reads value.<path> from the selected workspace and profile.",
|
|
690
|
+
summary: "Read, write, list, and delete values.",
|
|
691
|
+
usage: "cnos value [get <path> | set <path> <value> | list | delete <path>] [options] [global-options]",
|
|
692
|
+
description: "Reads and manages value.<path> entries from the selected workspace and profile.",
|
|
486
693
|
arguments: [
|
|
487
694
|
{
|
|
488
695
|
name: "path",
|
|
489
|
-
description: "Value path without the value. namespace prefix."
|
|
490
|
-
|
|
696
|
+
description: "Value path without the value. namespace prefix."
|
|
697
|
+
}
|
|
698
|
+
],
|
|
699
|
+
options: [
|
|
700
|
+
{
|
|
701
|
+
flag: "--target <local|global>",
|
|
702
|
+
description: "Choose whether writes land in the local project workspace or the configured global root."
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
flag: "--prefix <path>",
|
|
706
|
+
description: "Filter value list output to keys that begin with this logical path or key prefix."
|
|
491
707
|
}
|
|
492
708
|
],
|
|
493
|
-
examples: [
|
|
709
|
+
examples: [
|
|
710
|
+
"cnos value app.name",
|
|
711
|
+
"cnos value set server.port 3000",
|
|
712
|
+
"cnos add value app.name demo",
|
|
713
|
+
"cnos value list --prefix app."
|
|
714
|
+
]
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
id: "value set",
|
|
718
|
+
summary: "Write a value.",
|
|
719
|
+
usage: "cnos value set <path> <value> [--target <local|global>] [global-options]",
|
|
720
|
+
description: "Writes a deterministic value document into the selected workspace or explicit global target.",
|
|
721
|
+
examples: ["cnos value set app.name demo", "cnos add value server.port 3000 --target global"]
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
id: "value list",
|
|
725
|
+
summary: "List resolved values.",
|
|
726
|
+
usage: "cnos value list [--prefix <path>] [global-options]",
|
|
727
|
+
description: "Lists resolved value keys for the selected workspace and profile.",
|
|
728
|
+
examples: ["cnos value list", "cnos value list --prefix app."]
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
id: "value delete",
|
|
732
|
+
summary: "Delete a value entry.",
|
|
733
|
+
usage: "cnos value delete <path> [--target <local|global>] [global-options]",
|
|
734
|
+
description: "Deletes a value from the selected workspace or explicit global target.",
|
|
735
|
+
examples: ["cnos value delete app.name", "cnos remove value server.port"]
|
|
494
736
|
},
|
|
495
737
|
{
|
|
496
738
|
id: "secret",
|
|
497
|
-
summary: "Read
|
|
498
|
-
usage: "cnos secret <path> [global-options]",
|
|
499
|
-
description: "Reads secret
|
|
739
|
+
summary: "Read, write, list, and delete secrets.",
|
|
740
|
+
usage: "cnos secret [get <path> | set <path> <value> | list | delete <path>] [options] [global-options]",
|
|
741
|
+
description: "Reads resolved secrets, writes secret refs plus external local-secret material, and manages secret entries for the selected workspace and profile.",
|
|
500
742
|
arguments: [
|
|
501
743
|
{
|
|
502
744
|
name: "path",
|
|
503
|
-
description: "Secret path without the secret. namespace prefix."
|
|
504
|
-
|
|
745
|
+
description: "Secret path without the secret. namespace prefix."
|
|
746
|
+
}
|
|
747
|
+
],
|
|
748
|
+
options: [
|
|
749
|
+
{
|
|
750
|
+
flag: "--local",
|
|
751
|
+
description: "Store encrypted secret material under ~/.cnos/secrets and write a local ref into the repo."
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
flag: "--remote",
|
|
755
|
+
description: "Write a remote secret reference into the repo."
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
flag: "--ref",
|
|
759
|
+
description: "Write a generic secret reference into the repo."
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
flag: "--provider <name>",
|
|
763
|
+
description: "Provider name for --remote or --ref secret writes."
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
flag: "--passphrase <value>",
|
|
767
|
+
description: "Passphrase used to encrypt local secret material when --local is selected."
|
|
505
768
|
}
|
|
506
769
|
],
|
|
507
|
-
examples: [
|
|
770
|
+
examples: [
|
|
771
|
+
"cnos secret app.token",
|
|
772
|
+
"cnos secret set app.token super-secret --local --passphrase dev-pass",
|
|
773
|
+
"cnos add secret app.token APP_TOKEN --ref --provider env",
|
|
774
|
+
"cnos secret list --workspace agents"
|
|
775
|
+
]
|
|
508
776
|
},
|
|
509
777
|
{
|
|
510
778
|
id: "define",
|
|
511
779
|
summary: "Write a value or secret into the selected workspace.",
|
|
512
780
|
usage: "cnos define <value|secret> <path> <rawValue> [--target <local|global>] [global-options]",
|
|
513
|
-
description: "Writes deterministic YAML into the selected workspace. Global writes require allowWrite and an explicit --target global flag.",
|
|
781
|
+
description: "Writes deterministic YAML into the selected workspace. Secret writes default to secure local-secret storage plus a repo ref. Global writes require allowWrite and an explicit --target global flag.",
|
|
514
782
|
arguments: [
|
|
515
783
|
{
|
|
516
784
|
name: "namespace",
|
|
@@ -536,9 +804,99 @@ var COMMANDS = [
|
|
|
536
804
|
],
|
|
537
805
|
examples: [
|
|
538
806
|
"cnos define value server.port 3000 --workspace api",
|
|
539
|
-
"cnos define secret app.token super-secret --workspace api
|
|
807
|
+
"cnos define secret app.token super-secret --workspace api"
|
|
540
808
|
]
|
|
541
809
|
},
|
|
810
|
+
{
|
|
811
|
+
id: "use",
|
|
812
|
+
summary: "Persist repo-local CLI defaults such as workspace and profile.",
|
|
813
|
+
usage: "cnos use [--workspace <id>] [--profile <name>] [--global-root <path>] [--root <path>] [--json]",
|
|
814
|
+
description: "Writes .cnos-workspace.yml so future CLI invocations can omit repeated workspace/profile/global-root flags.",
|
|
815
|
+
examples: ["cnos use --workspace api --profile stage", "cnos use --global-root ~/.cnos"]
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
id: "list",
|
|
819
|
+
summary: "List resolved config entries.",
|
|
820
|
+
usage: "cnos list [value|secret|meta|all] [--prefix <path>] [global-options]",
|
|
821
|
+
description: "Lists resolved config entries across one namespace or the full effective graph, with optional key-prefix filtering.",
|
|
822
|
+
options: [
|
|
823
|
+
{
|
|
824
|
+
flag: "--namespace <value|secret|meta|all>",
|
|
825
|
+
description: "Explicit namespace selector when not using a positional namespace argument."
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
flag: "--prefix <path>",
|
|
829
|
+
description: "Filter list output to entries whose logical keys begin with this prefix."
|
|
830
|
+
}
|
|
831
|
+
],
|
|
832
|
+
examples: ["cnos list", "cnos list value --prefix app.", "cnos list --namespace secret"]
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
id: "profile",
|
|
836
|
+
summary: "Manage CNOS profiles.",
|
|
837
|
+
usage: "cnos profile [create <name> | list | use <name> | delete <name>] [options] [global-options]",
|
|
838
|
+
description: "Creates and lists explicit profiles and stores the active repo-local profile selection for CLI usage.",
|
|
839
|
+
options: [
|
|
840
|
+
{
|
|
841
|
+
flag: "--inherit <name>",
|
|
842
|
+
description: "Parent profile to extend when creating a profile."
|
|
843
|
+
}
|
|
844
|
+
],
|
|
845
|
+
examples: [
|
|
846
|
+
"cnos profile create stage --inherit base",
|
|
847
|
+
"cnos profile list",
|
|
848
|
+
"cnos profile use stage"
|
|
849
|
+
]
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
id: "profile create",
|
|
853
|
+
summary: "Create a profile definition.",
|
|
854
|
+
usage: "cnos profile create <name> [--inherit <name>] [--root <path>] [--json]",
|
|
855
|
+
description: "Creates .cnos/profiles/<name>.yml for an explicit profile overlay.",
|
|
856
|
+
examples: ["cnos profile create stage --inherit base"]
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
id: "profile list",
|
|
860
|
+
summary: "List available profiles.",
|
|
861
|
+
usage: "cnos profile list [--root <path>] [--json]",
|
|
862
|
+
description: "Lists the base profile plus any explicit profile definition files in .cnos/profiles.",
|
|
863
|
+
examples: ["cnos profile list"]
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
id: "profile use",
|
|
867
|
+
summary: "Persist the active profile for this repo.",
|
|
868
|
+
usage: "cnos profile use <name> [--root <path>] [--json]",
|
|
869
|
+
description: "Writes the selected profile into .cnos-workspace.yml.",
|
|
870
|
+
examples: ["cnos profile use stage"]
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
id: "profile delete",
|
|
874
|
+
summary: "Delete a profile definition.",
|
|
875
|
+
usage: "cnos profile delete <name> [--root <path>] [--json]",
|
|
876
|
+
description: "Deletes .cnos/profiles/<name>.yml.",
|
|
877
|
+
examples: ["cnos profile delete stage"]
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
id: "secret set",
|
|
881
|
+
summary: "Write a secret securely.",
|
|
882
|
+
usage: "cnos secret set <path> <value> [--local|--remote|--ref] [--provider <name>] [--passphrase <value>] [global-options]",
|
|
883
|
+
description: "Writes a secret reference into the repo and, when --local is used, stores encrypted secret material outside the repo under ~/.cnos/secrets.",
|
|
884
|
+
examples: ["cnos secret set app.token super-secret --local --passphrase dev-pass"]
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
id: "secret list",
|
|
888
|
+
summary: "List resolved secrets.",
|
|
889
|
+
usage: "cnos secret list [global-options]",
|
|
890
|
+
description: "Lists resolved secret keys for the selected workspace and profile.",
|
|
891
|
+
examples: ["cnos secret list --workspace api"]
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
id: "secret delete",
|
|
895
|
+
summary: "Delete a secret reference.",
|
|
896
|
+
usage: "cnos secret delete <path> [--target <local|global>] [global-options]",
|
|
897
|
+
description: "Deletes the secret reference from the repo and removes local encrypted material when the secret used the local provider.",
|
|
898
|
+
examples: ["cnos secret delete app.token"]
|
|
899
|
+
},
|
|
542
900
|
{
|
|
543
901
|
id: "inspect",
|
|
544
902
|
summary: "Inspect the winning value and provenance for a key.",
|
|
@@ -572,7 +930,11 @@ var COMMANDS = [
|
|
|
572
930
|
required: true
|
|
573
931
|
}
|
|
574
932
|
],
|
|
575
|
-
examples: [
|
|
933
|
+
examples: [
|
|
934
|
+
"cnos export env",
|
|
935
|
+
"cnos export env --public --framework vite --workspace api",
|
|
936
|
+
"cnos export env --public --framework next --workspace webapp"
|
|
937
|
+
]
|
|
576
938
|
},
|
|
577
939
|
{
|
|
578
940
|
id: "export env",
|
|
@@ -586,7 +948,7 @@ var COMMANDS = [
|
|
|
586
948
|
},
|
|
587
949
|
{
|
|
588
950
|
flag: "--framework <name>",
|
|
589
|
-
description: "Apply framework-specific public env conventions such as vite or
|
|
951
|
+
description: "Apply framework-specific public env conventions such as vite, next, or nuxt."
|
|
590
952
|
},
|
|
591
953
|
{
|
|
592
954
|
flag: "--prefix <prefix>",
|
|
@@ -676,6 +1038,31 @@ var COMMANDS = [
|
|
|
676
1038
|
}
|
|
677
1039
|
],
|
|
678
1040
|
examples: ["cnos help-ai --format json", "cnos help-ai export env --format json"]
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
id: "version",
|
|
1044
|
+
summary: "Print the installed CNOS CLI version.",
|
|
1045
|
+
usage: "cnos version",
|
|
1046
|
+
description: "Prints the installed @kitsy/cnos-cli version string.",
|
|
1047
|
+
examples: ["cnos version", "cnos --version"]
|
|
1048
|
+
}
|
|
1049
|
+
];
|
|
1050
|
+
var INTEGRATIONS = [
|
|
1051
|
+
{
|
|
1052
|
+
id: "vite",
|
|
1053
|
+
packageName: "@kitsy/cnos-vite",
|
|
1054
|
+
entrypoint: "@kitsy/cnos-vite",
|
|
1055
|
+
summary: "Inject CNOS public env into Vite define replacements and import.meta.env.",
|
|
1056
|
+
usage: 'import { createCnosVitePlugin } from "@kitsy/cnos-vite"',
|
|
1057
|
+
examples: ["cnos export env --public --framework vite", "vite.config.ts -> plugins: [createCnosVitePlugin()]"]
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
id: "next",
|
|
1061
|
+
packageName: "@kitsy/cnos-next",
|
|
1062
|
+
entrypoint: "@kitsy/cnos-next",
|
|
1063
|
+
summary: "Merge CNOS public env into next.config.* using the NEXT_PUBLIC_ convention.",
|
|
1064
|
+
usage: 'import { withCnosNext } from "@kitsy/cnos-next"',
|
|
1065
|
+
examples: ["cnos export env --public --framework next", "next.config.mjs -> export default withCnosNext({})"]
|
|
679
1066
|
}
|
|
680
1067
|
];
|
|
681
1068
|
var HELP_DOCUMENT = {
|
|
@@ -685,7 +1072,14 @@ var HELP_DOCUMENT = {
|
|
|
685
1072
|
description: "CNOS resolves one active workspace per invocation, layers local and optional global config roots, and exposes read, write, export, dump, validation, and diagnostics commands.",
|
|
686
1073
|
globalOptions: GLOBAL_OPTIONS,
|
|
687
1074
|
commands: COMMANDS,
|
|
688
|
-
|
|
1075
|
+
integrations: INTEGRATIONS,
|
|
1076
|
+
examples: [
|
|
1077
|
+
"cnos use --profile stage",
|
|
1078
|
+
"cnos doctor --workspace api",
|
|
1079
|
+
"cnos export env --public --framework vite",
|
|
1080
|
+
"cnos export env --public --framework next",
|
|
1081
|
+
"cnos help-ai --format json"
|
|
1082
|
+
]
|
|
689
1083
|
};
|
|
690
1084
|
function normalizeHelpTopic(parts) {
|
|
691
1085
|
const cleaned = parts.filter((part) => part.length > 0);
|
|
@@ -739,6 +1133,11 @@ function formatRootHelp() {
|
|
|
739
1133
|
"Commands",
|
|
740
1134
|
...HELP_DOCUMENT.commands.filter((command) => !command.id.includes(" ")).map((command) => ` ${command.id.padEnd(12, " ")} ${command.summary}`),
|
|
741
1135
|
"",
|
|
1136
|
+
"Framework integrations",
|
|
1137
|
+
...HELP_DOCUMENT.integrations.map(
|
|
1138
|
+
(integration) => ` ${integration.id.padEnd(12, " ")} ${integration.packageName} via ${integration.entrypoint}`
|
|
1139
|
+
),
|
|
1140
|
+
"",
|
|
742
1141
|
...formatOptions("Global options", HELP_DOCUMENT.globalOptions),
|
|
743
1142
|
"",
|
|
744
1143
|
"Examples",
|
|
@@ -766,6 +1165,7 @@ function runHelpAi(topic, cliArgs = []) {
|
|
|
766
1165
|
cli: HELP_DOCUMENT.name,
|
|
767
1166
|
summary: HELP_DOCUMENT.summary,
|
|
768
1167
|
globalOptions: HELP_DOCUMENT.globalOptions,
|
|
1168
|
+
integrations: HELP_DOCUMENT.integrations,
|
|
769
1169
|
command
|
|
770
1170
|
} : {
|
|
771
1171
|
cli: HELP_DOCUMENT.name,
|
|
@@ -773,7 +1173,8 @@ function runHelpAi(topic, cliArgs = []) {
|
|
|
773
1173
|
usage: HELP_DOCUMENT.usage,
|
|
774
1174
|
description: HELP_DOCUMENT.description,
|
|
775
1175
|
globalOptions: HELP_DOCUMENT.globalOptions,
|
|
776
|
-
commands: HELP_DOCUMENT.commands
|
|
1176
|
+
commands: HELP_DOCUMENT.commands,
|
|
1177
|
+
integrations: HELP_DOCUMENT.integrations
|
|
777
1178
|
};
|
|
778
1179
|
if (topic && !command) {
|
|
779
1180
|
throw new Error(`Unknown help topic: ${topic}`);
|
|
@@ -794,25 +1195,32 @@ import path4 from "path";
|
|
|
794
1195
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
795
1196
|
import path3 from "path";
|
|
796
1197
|
function scaffoldManifest(projectName, workspace) {
|
|
797
|
-
|
|
1198
|
+
const lines = [
|
|
798
1199
|
"version: 1",
|
|
799
1200
|
"project:",
|
|
800
1201
|
` name: ${projectName}`,
|
|
801
|
-
"workspaces:",
|
|
802
|
-
` default: ${workspace}`,
|
|
803
|
-
" global:",
|
|
804
|
-
" enabled: false",
|
|
805
|
-
" allowWrite: false",
|
|
806
|
-
" items:",
|
|
807
|
-
` ${workspace}: {}`,
|
|
808
1202
|
"profiles:",
|
|
809
|
-
" default:
|
|
1203
|
+
" default: base",
|
|
810
1204
|
"envMapping:",
|
|
811
1205
|
" convention: SCREAMING_SNAKE",
|
|
812
1206
|
"public:",
|
|
813
1207
|
" promote: []",
|
|
814
1208
|
""
|
|
815
|
-
]
|
|
1209
|
+
];
|
|
1210
|
+
if (workspace) {
|
|
1211
|
+
lines.splice(
|
|
1212
|
+
4,
|
|
1213
|
+
0,
|
|
1214
|
+
"workspaces:",
|
|
1215
|
+
` default: ${workspace}`,
|
|
1216
|
+
" global:",
|
|
1217
|
+
" enabled: false",
|
|
1218
|
+
" allowWrite: false",
|
|
1219
|
+
" items:",
|
|
1220
|
+
` ${workspace}: {}`
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
return lines.join("\n");
|
|
816
1224
|
}
|
|
817
1225
|
async function ensureFile(filePath, content) {
|
|
818
1226
|
try {
|
|
@@ -826,11 +1234,14 @@ async function ensureFile(filePath, content) {
|
|
|
826
1234
|
async function ensureGitignore(root) {
|
|
827
1235
|
const gitignorePath = path3.join(root, ".gitignore");
|
|
828
1236
|
const requiredEntries = [
|
|
829
|
-
"cnos/
|
|
830
|
-
"cnos/
|
|
831
|
-
"cnos/
|
|
832
|
-
"
|
|
833
|
-
"
|
|
1237
|
+
".cnos/env/.env",
|
|
1238
|
+
".cnos/env/.env.*",
|
|
1239
|
+
"!.cnos/env/.env.example",
|
|
1240
|
+
"!.cnos/env/.env.*.example",
|
|
1241
|
+
".cnos/workspaces/*/env/.env",
|
|
1242
|
+
".cnos/workspaces/*/env/.env.*",
|
|
1243
|
+
"!.cnos/workspaces/*/env/.env.example",
|
|
1244
|
+
"!.cnos/workspaces/*/env/.env.*.example"
|
|
834
1245
|
];
|
|
835
1246
|
let current = "";
|
|
836
1247
|
try {
|
|
@@ -849,28 +1260,34 @@ async function ensureGitignore(root) {
|
|
|
849
1260
|
return true;
|
|
850
1261
|
}
|
|
851
1262
|
async function scaffoldWorkspace(root, workspace) {
|
|
852
|
-
const cnosRoot = path3.join(root, "cnos");
|
|
853
|
-
const workspaceRoot = path3.join(cnosRoot, "workspaces", workspace);
|
|
1263
|
+
const cnosRoot = path3.join(root, ".cnos");
|
|
1264
|
+
const workspaceRoot = workspace ? path3.join(cnosRoot, "workspaces", workspace) : cnosRoot;
|
|
854
1265
|
const createdPaths = [];
|
|
855
1266
|
await mkdir2(path3.join(workspaceRoot, "profiles"), { recursive: true });
|
|
856
|
-
await mkdir2(path3.join(workspaceRoot, "values"
|
|
857
|
-
await mkdir2(path3.join(workspaceRoot, "secrets"
|
|
1267
|
+
await mkdir2(path3.join(workspaceRoot, "values"), { recursive: true });
|
|
1268
|
+
await mkdir2(path3.join(workspaceRoot, "secrets"), { recursive: true });
|
|
858
1269
|
await mkdir2(path3.join(workspaceRoot, "env"), { recursive: true });
|
|
859
|
-
|
|
1270
|
+
const relativePaths = workspace ? [
|
|
860
1271
|
["workspaces", workspace, "profiles", ".gitkeep"],
|
|
861
|
-
["workspaces", workspace, "values", "
|
|
862
|
-
["workspaces", workspace, "secrets", "
|
|
1272
|
+
["workspaces", workspace, "values", ".gitkeep"],
|
|
1273
|
+
["workspaces", workspace, "secrets", ".gitkeep"],
|
|
863
1274
|
["workspaces", workspace, "env", ".gitkeep"]
|
|
864
|
-
]
|
|
1275
|
+
] : [
|
|
1276
|
+
["profiles", ".gitkeep"],
|
|
1277
|
+
["values", ".gitkeep"],
|
|
1278
|
+
["secrets", ".gitkeep"],
|
|
1279
|
+
["env", ".gitkeep"]
|
|
1280
|
+
];
|
|
1281
|
+
for (const relativePath of relativePaths) {
|
|
865
1282
|
const filePath = path3.join(cnosRoot, ...relativePath);
|
|
866
1283
|
if (await ensureFile(filePath, "")) {
|
|
867
1284
|
createdPaths.push(path3.relative(root, filePath).replace(/\\/g, "/"));
|
|
868
1285
|
}
|
|
869
1286
|
}
|
|
870
1287
|
if (await ensureFile(path3.join(cnosRoot, "cnos.yml"), scaffoldManifest(path3.basename(root), workspace))) {
|
|
871
|
-
createdPaths.push("cnos/cnos.yml");
|
|
1288
|
+
createdPaths.push(".cnos/cnos.yml");
|
|
872
1289
|
}
|
|
873
|
-
if (await ensureFile(path3.join(root, ".cnos-workspace.yml"), `workspace: ${workspace}
|
|
1290
|
+
if (workspace && await ensureFile(path3.join(root, ".cnos-workspace.yml"), `workspace: ${workspace}
|
|
874
1291
|
globalRoot: ~/.cnos
|
|
875
1292
|
`)) {
|
|
876
1293
|
createdPaths.push(".cnos-workspace.yml");
|
|
@@ -880,7 +1297,7 @@ globalRoot: ~/.cnos
|
|
|
880
1297
|
}
|
|
881
1298
|
return {
|
|
882
1299
|
root,
|
|
883
|
-
workspace,
|
|
1300
|
+
...workspace ? { workspace } : {},
|
|
884
1301
|
created: createdPaths
|
|
885
1302
|
};
|
|
886
1303
|
}
|
|
@@ -888,12 +1305,14 @@ globalRoot: ~/.cnos
|
|
|
888
1305
|
// src/commands/init.ts
|
|
889
1306
|
async function runInit(options = {}) {
|
|
890
1307
|
const root = path4.resolve(options.root ?? process.cwd());
|
|
891
|
-
const
|
|
892
|
-
const result = await scaffoldWorkspace(root, workspace);
|
|
1308
|
+
const result = await scaffoldWorkspace(root, options.workspace);
|
|
893
1309
|
if (options.json) {
|
|
894
1310
|
return printJson(result);
|
|
895
1311
|
}
|
|
896
|
-
|
|
1312
|
+
if (result.workspace) {
|
|
1313
|
+
return `initialized CNOS workspace ${result.workspace} at ${root}`;
|
|
1314
|
+
}
|
|
1315
|
+
return `initialized CNOS project at ${root}`;
|
|
897
1316
|
}
|
|
898
1317
|
|
|
899
1318
|
// src/format/printInspect.ts
|
|
@@ -928,8 +1347,76 @@ async function runInspect(key, options = {}) {
|
|
|
928
1347
|
return printInspect(inspectResult);
|
|
929
1348
|
}
|
|
930
1349
|
|
|
1350
|
+
// src/format/printValue.ts
|
|
1351
|
+
function printValue(value, json = false) {
|
|
1352
|
+
if (json) {
|
|
1353
|
+
return printJson(value);
|
|
1354
|
+
}
|
|
1355
|
+
if (typeof value === "string") {
|
|
1356
|
+
return value;
|
|
1357
|
+
}
|
|
1358
|
+
if (value === void 0 || value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
1359
|
+
return String(value);
|
|
1360
|
+
}
|
|
1361
|
+
return printJson(value);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// src/services/listing.ts
|
|
1365
|
+
import { flattenObject as flattenObject2 } from "@kitsy/cnos/internal";
|
|
1366
|
+
async function listConfigEntries(namespace, options = {}) {
|
|
1367
|
+
const runtime = await createRuntimeService(options);
|
|
1368
|
+
const namespaces = namespace === "all" ? ["value", "secret", "meta"] : [namespace];
|
|
1369
|
+
const entries = [];
|
|
1370
|
+
const prefix = options.prefix?.trim();
|
|
1371
|
+
for (const currentNamespace of namespaces) {
|
|
1372
|
+
const projected = flattenObject2(runtime.toNamespace(currentNamespace));
|
|
1373
|
+
for (const [path10, value] of Object.entries(projected)) {
|
|
1374
|
+
const key = `${currentNamespace}.${path10}`;
|
|
1375
|
+
if (prefix && !key.startsWith(prefix) && !path10.startsWith(prefix)) {
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
entries.push({
|
|
1379
|
+
key,
|
|
1380
|
+
value
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
return entries.sort((left, right) => left.key.localeCompare(right.key));
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// src/commands/list.ts
|
|
1388
|
+
function normalizeNamespace(value) {
|
|
1389
|
+
if (!value || value === "all") {
|
|
1390
|
+
return "all";
|
|
1391
|
+
}
|
|
1392
|
+
if (value === "values" || value === "value") {
|
|
1393
|
+
return "value";
|
|
1394
|
+
}
|
|
1395
|
+
if (value === "secrets" || value === "secret") {
|
|
1396
|
+
return "secret";
|
|
1397
|
+
}
|
|
1398
|
+
if (value === "meta") {
|
|
1399
|
+
return "meta";
|
|
1400
|
+
}
|
|
1401
|
+
throw new Error(`Unsupported list namespace: ${value}`);
|
|
1402
|
+
}
|
|
1403
|
+
async function runList(args = [], options = {}) {
|
|
1404
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
1405
|
+
const namespace = normalizeNamespace(args[0] ?? consumeOption(cliArgs, "--namespace"));
|
|
1406
|
+
const prefix = consumeOption(cliArgs, "--prefix");
|
|
1407
|
+
const entries = await listConfigEntries(namespace, {
|
|
1408
|
+
...options,
|
|
1409
|
+
cliArgs,
|
|
1410
|
+
...prefix ? { prefix } : {}
|
|
1411
|
+
});
|
|
1412
|
+
if (options.json) {
|
|
1413
|
+
return printJson(entries);
|
|
1414
|
+
}
|
|
1415
|
+
return entries.map((entry) => `${entry.key}=${printValue(entry.value)}`).join("\n");
|
|
1416
|
+
}
|
|
1417
|
+
|
|
931
1418
|
// src/commands/onboard.ts
|
|
932
|
-
import { copyFile, readdir, rm } from "fs/promises";
|
|
1419
|
+
import { copyFile, readdir, rm as rm2 } from "fs/promises";
|
|
933
1420
|
import path5 from "path";
|
|
934
1421
|
var ROOT_ENV_FILE_PATTERN = /^\.env(?:\.[A-Za-z0-9_-]+)*(?:\.example)?$/;
|
|
935
1422
|
async function listRootEnvFiles(root) {
|
|
@@ -945,7 +1432,7 @@ async function runOnboard(options = {}) {
|
|
|
945
1432
|
throw new Error(`Unsupported onboard arguments: ${cliArgs.join(" ")}`);
|
|
946
1433
|
}
|
|
947
1434
|
const scaffold = await scaffoldWorkspace(root, workspace);
|
|
948
|
-
const envRoot = path5.join(root, "cnos", "workspaces", workspace, "env");
|
|
1435
|
+
const envRoot = path5.join(root, ".cnos", "workspaces", workspace, "env");
|
|
949
1436
|
const rootFiles = await listRootEnvFiles(root);
|
|
950
1437
|
const imported = [];
|
|
951
1438
|
const skipped = [];
|
|
@@ -956,7 +1443,7 @@ async function runOnboard(options = {}) {
|
|
|
956
1443
|
await copyFile(sourcePath, targetPath);
|
|
957
1444
|
imported.push(path5.relative(root, targetPath).replace(/\\/g, "/"));
|
|
958
1445
|
if (move) {
|
|
959
|
-
await
|
|
1446
|
+
await rm2(sourcePath);
|
|
960
1447
|
}
|
|
961
1448
|
} catch {
|
|
962
1449
|
skipped.push(fileName);
|
|
@@ -975,21 +1462,169 @@ async function runOnboard(options = {}) {
|
|
|
975
1462
|
}
|
|
976
1463
|
const importedCount = imported.length;
|
|
977
1464
|
const skippedSuffix = skipped.length > 0 ? ` (${skipped.length} skipped)` : "";
|
|
978
|
-
return `onboarded ${workspace} at ${root}; imported ${importedCount} root env files into cnos/workspaces/${workspace}/env using ${result.mode}${skippedSuffix}`;
|
|
1465
|
+
return `onboarded ${workspace} at ${root}; imported ${importedCount} root env files into .cnos/workspaces/${workspace}/env using ${result.mode}${skippedSuffix}`;
|
|
979
1466
|
}
|
|
980
1467
|
|
|
981
|
-
// src/
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1468
|
+
// src/commands/profile.ts
|
|
1469
|
+
import path8 from "path";
|
|
1470
|
+
|
|
1471
|
+
// src/services/context.ts
|
|
1472
|
+
import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
1473
|
+
import path6 from "path";
|
|
1474
|
+
import { parseYaml as parseYaml2, stringifyYaml as stringifyYaml2 } from "@kitsy/cnos/internal";
|
|
1475
|
+
async function loadCliContext(root = process.cwd()) {
|
|
1476
|
+
const filePath = path6.join(path6.resolve(root), ".cnos-workspace.yml");
|
|
1477
|
+
try {
|
|
1478
|
+
const source = await readFile4(filePath, "utf8");
|
|
1479
|
+
const parsed = parseYaml2(source);
|
|
1480
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1481
|
+
return {};
|
|
1482
|
+
}
|
|
1483
|
+
return parsed;
|
|
1484
|
+
} catch {
|
|
1485
|
+
return {};
|
|
985
1486
|
}
|
|
986
|
-
|
|
987
|
-
|
|
1487
|
+
}
|
|
1488
|
+
async function saveCliContext(options = {}) {
|
|
1489
|
+
const root = path6.resolve(options.root ?? process.cwd());
|
|
1490
|
+
const filePath = path6.join(root, ".cnos-workspace.yml");
|
|
1491
|
+
const current = await loadCliContext(root);
|
|
1492
|
+
const next = {
|
|
1493
|
+
...current.workspace ? { workspace: current.workspace } : {},
|
|
1494
|
+
...current.profile ? { profile: current.profile } : {},
|
|
1495
|
+
...current.globalRoot ? { globalRoot: current.globalRoot } : {},
|
|
1496
|
+
...options.workspace ? { workspace: options.workspace } : {},
|
|
1497
|
+
...options.profile ? { profile: options.profile } : {},
|
|
1498
|
+
...options.globalRoot ? { globalRoot: options.globalRoot } : {}
|
|
1499
|
+
};
|
|
1500
|
+
await writeFile3(filePath, stringifyYaml2(next), "utf8");
|
|
1501
|
+
return {
|
|
1502
|
+
filePath,
|
|
1503
|
+
context: next
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// src/services/profiles.ts
|
|
1508
|
+
import { mkdir as mkdir3, readdir as readdir2, readFile as readFile5, rm as rm3, writeFile as writeFile4 } from "fs/promises";
|
|
1509
|
+
import path7 from "path";
|
|
1510
|
+
import { parseYaml as parseYaml3, stringifyYaml as stringifyYaml3 } from "@kitsy/cnos/internal";
|
|
1511
|
+
async function createProfileDefinition(root = process.cwd(), profile, inherit) {
|
|
1512
|
+
const filePath = path7.join(path7.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
1513
|
+
await mkdir3(path7.dirname(filePath), { recursive: true });
|
|
1514
|
+
const document = inherit && inherit !== "base" ? {
|
|
1515
|
+
name: profile,
|
|
1516
|
+
extends: [inherit]
|
|
1517
|
+
} : {
|
|
1518
|
+
name: profile
|
|
1519
|
+
};
|
|
1520
|
+
await writeFile4(filePath, stringifyYaml3(document), "utf8");
|
|
1521
|
+
return {
|
|
1522
|
+
filePath,
|
|
1523
|
+
profile,
|
|
1524
|
+
...inherit ? { inherit } : {}
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
async function listProfiles(root = process.cwd()) {
|
|
1528
|
+
const profilesRoot = path7.join(path7.resolve(root), ".cnos", "profiles");
|
|
1529
|
+
try {
|
|
1530
|
+
const entries = await readdir2(profilesRoot, { withFileTypes: true });
|
|
1531
|
+
const discovered = /* @__PURE__ */ new Set(["base"]);
|
|
1532
|
+
for (const entry of entries) {
|
|
1533
|
+
if (entry.isFile() && entry.name.endsWith(".yml")) {
|
|
1534
|
+
discovered.add(entry.name.replace(/\.yml$/, ""));
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
return [...discovered].sort((left, right) => left.localeCompare(right));
|
|
1538
|
+
} catch {
|
|
1539
|
+
return ["base"];
|
|
988
1540
|
}
|
|
989
|
-
|
|
990
|
-
|
|
1541
|
+
}
|
|
1542
|
+
async function deleteProfileDefinition(root = process.cwd(), profile) {
|
|
1543
|
+
const filePath = path7.join(path7.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
1544
|
+
try {
|
|
1545
|
+
await rm3(filePath);
|
|
1546
|
+
return {
|
|
1547
|
+
filePath,
|
|
1548
|
+
deleted: true
|
|
1549
|
+
};
|
|
1550
|
+
} catch {
|
|
1551
|
+
return {
|
|
1552
|
+
filePath,
|
|
1553
|
+
deleted: false
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
async function readProfileDefinition(root = process.cwd(), profile = "base") {
|
|
1558
|
+
if (profile === "base") {
|
|
1559
|
+
return {
|
|
1560
|
+
name: "base"
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
const filePath = path7.join(path7.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
1564
|
+
try {
|
|
1565
|
+
return parseYaml3(await readFile5(filePath, "utf8")) ?? void 0;
|
|
1566
|
+
} catch {
|
|
1567
|
+
return void 0;
|
|
991
1568
|
}
|
|
992
|
-
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// src/commands/profile.ts
|
|
1572
|
+
function normalizeProfileAction(args) {
|
|
1573
|
+
const [action = "list", ...tail] = args;
|
|
1574
|
+
if (["create", "list", "use", "delete", "remove"].includes(action)) {
|
|
1575
|
+
return {
|
|
1576
|
+
action: action === "remove" ? "delete" : action,
|
|
1577
|
+
tail
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
return {
|
|
1581
|
+
action: "list",
|
|
1582
|
+
tail: args
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
async function runProfile(args, options = {}) {
|
|
1586
|
+
const { action, tail } = normalizeProfileAction(args);
|
|
1587
|
+
const root = path8.resolve(options.root ?? process.cwd());
|
|
1588
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
1589
|
+
if (action === "create") {
|
|
1590
|
+
const profile = tail[0] ?? "stage";
|
|
1591
|
+
const inherit = consumeOption(cliArgs, "--inherit");
|
|
1592
|
+
const result = await createProfileDefinition(root, profile, inherit);
|
|
1593
|
+
if (options.json) {
|
|
1594
|
+
return printJson(result);
|
|
1595
|
+
}
|
|
1596
|
+
return `created profile ${profile} at ${result.filePath}`;
|
|
1597
|
+
}
|
|
1598
|
+
if (action === "use") {
|
|
1599
|
+
const profile = tail[0] ?? "base";
|
|
1600
|
+
const result = await saveCliContext({
|
|
1601
|
+
root,
|
|
1602
|
+
profile
|
|
1603
|
+
});
|
|
1604
|
+
if (options.json) {
|
|
1605
|
+
return printJson(result);
|
|
1606
|
+
}
|
|
1607
|
+
return `active profile set to ${profile} in ${result.filePath}`;
|
|
1608
|
+
}
|
|
1609
|
+
if (action === "delete") {
|
|
1610
|
+
const profile = tail[0] ?? "base";
|
|
1611
|
+
const result = await deleteProfileDefinition(root, profile);
|
|
1612
|
+
if (options.json) {
|
|
1613
|
+
return printJson(result);
|
|
1614
|
+
}
|
|
1615
|
+
return result.deleted ? `deleted profile ${profile}` : `profile ${profile} was not found`;
|
|
1616
|
+
}
|
|
1617
|
+
const profiles = await listProfiles(root);
|
|
1618
|
+
if (options.json) {
|
|
1619
|
+
const details = await Promise.all(
|
|
1620
|
+
profiles.map(async (profile) => ({
|
|
1621
|
+
profile,
|
|
1622
|
+
definition: await readProfileDefinition(root, profile)
|
|
1623
|
+
}))
|
|
1624
|
+
);
|
|
1625
|
+
return printJson(details);
|
|
1626
|
+
}
|
|
1627
|
+
return profiles.join("\n");
|
|
993
1628
|
}
|
|
994
1629
|
|
|
995
1630
|
// src/commands/read.ts
|
|
@@ -1050,21 +1685,113 @@ async function runCommand(command, options = {}) {
|
|
|
1050
1685
|
}
|
|
1051
1686
|
|
|
1052
1687
|
// src/commands/secret.ts
|
|
1053
|
-
|
|
1688
|
+
function isSecretRef(value) {
|
|
1689
|
+
return Boolean(
|
|
1690
|
+
value && typeof value === "object" && !Array.isArray(value) && typeof value.provider === "string" && typeof value.ref === "string"
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
function normalizeSecretCommand(args) {
|
|
1694
|
+
const [actionOrPath, ...tail] = args;
|
|
1695
|
+
if (!actionOrPath) {
|
|
1696
|
+
return {
|
|
1697
|
+
action: "list",
|
|
1698
|
+
tail: []
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
if (["get", "set", "create", "add", "list", "delete", "remove"].includes(actionOrPath)) {
|
|
1702
|
+
return {
|
|
1703
|
+
action: actionOrPath === "remove" ? "delete" : actionOrPath === "create" || actionOrPath === "add" ? "set" : actionOrPath,
|
|
1704
|
+
tail
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
return {
|
|
1708
|
+
action: "get",
|
|
1709
|
+
tail: args
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
async function runSecret(argsOrPath, options = {}) {
|
|
1713
|
+
const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
|
|
1714
|
+
const { action, tail } = normalizeSecretCommand(args);
|
|
1715
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
1716
|
+
if (action === "list") {
|
|
1717
|
+
const runtime2 = await createRuntimeService(options);
|
|
1718
|
+
const secrets = runtime2.toNamespace("secret");
|
|
1719
|
+
if (options.json) {
|
|
1720
|
+
return printJson(secrets);
|
|
1721
|
+
}
|
|
1722
|
+
return Object.entries(secrets).sort(([left], [right]) => left.localeCompare(right)).map(([key, value2]) => `${key}=${printValue(value2)}`).join("\n");
|
|
1723
|
+
}
|
|
1724
|
+
if (action === "set") {
|
|
1725
|
+
const secretPath = tail[0];
|
|
1726
|
+
const rawValue = tail[1] ?? "";
|
|
1727
|
+
const local = consumeFlag(cliArgs, "--local");
|
|
1728
|
+
const remote = consumeFlag(cliArgs, "--remote");
|
|
1729
|
+
const ref = consumeFlag(cliArgs, "--ref");
|
|
1730
|
+
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
1731
|
+
const provider = consumeOption(cliArgs, "--provider");
|
|
1732
|
+
const passphrase = consumeOption(cliArgs, "--passphrase");
|
|
1733
|
+
const mode = local ? "local" : remote ? "remote" : ref ? "ref" : "local";
|
|
1734
|
+
const result = await setSecret(secretPath ?? "app.token", rawValue, {
|
|
1735
|
+
...options,
|
|
1736
|
+
cliArgs,
|
|
1737
|
+
target,
|
|
1738
|
+
mode,
|
|
1739
|
+
...provider ? { provider } : {},
|
|
1740
|
+
...passphrase ? { passphrase } : {}
|
|
1741
|
+
});
|
|
1742
|
+
if (options.json) {
|
|
1743
|
+
return printJson(result);
|
|
1744
|
+
}
|
|
1745
|
+
return `set secret.${secretPath} via ${result.provider} in ${result.filePath}`;
|
|
1746
|
+
}
|
|
1747
|
+
if (action === "delete") {
|
|
1748
|
+
const secretPath = tail[0];
|
|
1749
|
+
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
1750
|
+
const result = await deleteSecret(secretPath ?? "app.token", {
|
|
1751
|
+
...options,
|
|
1752
|
+
cliArgs,
|
|
1753
|
+
target
|
|
1754
|
+
});
|
|
1755
|
+
if (options.json) {
|
|
1756
|
+
return printJson(result);
|
|
1757
|
+
}
|
|
1758
|
+
return result.deleted ? `deleted secret.${secretPath} from ${result.filePath}` : `no secret.${secretPath} found in ${result.filePath}`;
|
|
1759
|
+
}
|
|
1054
1760
|
const runtime = await createRuntimeService(options);
|
|
1055
|
-
const value = runtime.secret(
|
|
1761
|
+
const value = runtime.secret(tail[0] ?? "app.token");
|
|
1056
1762
|
if (value === void 0) {
|
|
1057
|
-
throw new Error(`Missing CNOS secret path: ${
|
|
1763
|
+
throw new Error(`Missing CNOS secret path: ${tail[0] ?? "app.token"}`);
|
|
1764
|
+
}
|
|
1765
|
+
if (isSecretRef(value)) {
|
|
1766
|
+
throw new Error(
|
|
1767
|
+
`Secret ${tail[0] ?? "app.token"} is stored as an unresolved ${value.provider} reference. Provide the required provider context to resolve it.`
|
|
1768
|
+
);
|
|
1058
1769
|
}
|
|
1059
1770
|
if (options.json) {
|
|
1060
1771
|
return printJson({
|
|
1061
|
-
key: `secret.${
|
|
1772
|
+
key: `secret.${tail[0] ?? "app.token"}`,
|
|
1062
1773
|
value
|
|
1063
1774
|
});
|
|
1064
1775
|
}
|
|
1065
1776
|
return printValue(value);
|
|
1066
1777
|
}
|
|
1067
1778
|
|
|
1779
|
+
// src/commands/use.ts
|
|
1780
|
+
import path9 from "path";
|
|
1781
|
+
async function runUse(options = {}) {
|
|
1782
|
+
const root = path9.resolve(options.root ?? process.cwd());
|
|
1783
|
+
const result = await saveCliContext({
|
|
1784
|
+
root,
|
|
1785
|
+
...options.workspace ? { workspace: options.workspace } : {},
|
|
1786
|
+
...options.profile ? { profile: options.profile } : {},
|
|
1787
|
+
...options.globalRoot ? { globalRoot: options.globalRoot } : {}
|
|
1788
|
+
});
|
|
1789
|
+
if (options.json) {
|
|
1790
|
+
return printJson(result);
|
|
1791
|
+
}
|
|
1792
|
+
return `updated CLI context in ${result.filePath}`;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1068
1795
|
// src/commands/validate.ts
|
|
1069
1796
|
async function runValidate(options = {}) {
|
|
1070
1797
|
const { summary } = await createValidationSummary(options);
|
|
@@ -1077,16 +1804,139 @@ async function runValidate(options = {}) {
|
|
|
1077
1804
|
return summary.valid ? "validation passed" : summary.issues.map((issue) => `${issue.code}: ${issue.message}`).join("\n");
|
|
1078
1805
|
}
|
|
1079
1806
|
|
|
1807
|
+
// package.json
|
|
1808
|
+
var package_default = {
|
|
1809
|
+
name: "@kitsy/cnos-cli",
|
|
1810
|
+
version: "1.0.1",
|
|
1811
|
+
description: "CLI entry point and developer tooling for CNOS.",
|
|
1812
|
+
type: "module",
|
|
1813
|
+
main: "./dist/index.js",
|
|
1814
|
+
types: "./dist/index.d.ts",
|
|
1815
|
+
bin: {
|
|
1816
|
+
cnos: "./dist/index.js"
|
|
1817
|
+
},
|
|
1818
|
+
exports: {
|
|
1819
|
+
".": {
|
|
1820
|
+
types: "./dist/index.d.ts",
|
|
1821
|
+
import: "./dist/index.js"
|
|
1822
|
+
}
|
|
1823
|
+
},
|
|
1824
|
+
files: [
|
|
1825
|
+
"dist"
|
|
1826
|
+
],
|
|
1827
|
+
license: "MIT",
|
|
1828
|
+
repository: {
|
|
1829
|
+
type: "git",
|
|
1830
|
+
url: "https://github.com/kitsyai/cnos.git",
|
|
1831
|
+
directory: "packages/cli"
|
|
1832
|
+
},
|
|
1833
|
+
homepage: "https://github.com/kitsyai/cnos/tree/main/packages/cli",
|
|
1834
|
+
bugs: {
|
|
1835
|
+
url: "https://github.com/kitsyai/cnos/issues"
|
|
1836
|
+
},
|
|
1837
|
+
keywords: [
|
|
1838
|
+
"cnos",
|
|
1839
|
+
"cli",
|
|
1840
|
+
"config"
|
|
1841
|
+
],
|
|
1842
|
+
publishConfig: {
|
|
1843
|
+
access: "public"
|
|
1844
|
+
},
|
|
1845
|
+
dependencies: {
|
|
1846
|
+
"@kitsy/cnos": "workspace:*"
|
|
1847
|
+
},
|
|
1848
|
+
scripts: {
|
|
1849
|
+
build: "tsup src/index.ts --format esm --dts",
|
|
1850
|
+
clean: "rimraf dist",
|
|
1851
|
+
dev: "tsup src/index.ts --watch --format esm --dts",
|
|
1852
|
+
lint: "eslint src test",
|
|
1853
|
+
test: "vitest run",
|
|
1854
|
+
typecheck: "tsc -p tsconfig.json --noEmit"
|
|
1855
|
+
}
|
|
1856
|
+
};
|
|
1857
|
+
|
|
1858
|
+
// src/commands/version.ts
|
|
1859
|
+
function runVersion() {
|
|
1860
|
+
return package_default.version;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1080
1863
|
// src/commands/value.ts
|
|
1081
|
-
|
|
1864
|
+
function normalizeValueCommand(args) {
|
|
1865
|
+
const [actionOrPath, ...tail] = args;
|
|
1866
|
+
if (!actionOrPath) {
|
|
1867
|
+
return {
|
|
1868
|
+
action: "list",
|
|
1869
|
+
tail: []
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
if (["get", "set", "create", "add", "list", "delete", "remove"].includes(actionOrPath)) {
|
|
1873
|
+
return {
|
|
1874
|
+
action: actionOrPath === "remove" ? "delete" : actionOrPath === "create" || actionOrPath === "add" ? "set" : actionOrPath,
|
|
1875
|
+
tail
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
return {
|
|
1879
|
+
action: "get",
|
|
1880
|
+
tail: args
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
async function runValue(argsOrPath, options = {}) {
|
|
1884
|
+
const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
|
|
1885
|
+
const { action, tail } = normalizeValueCommand(args);
|
|
1886
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
1887
|
+
if (action === "list") {
|
|
1888
|
+
const prefix = consumeOption(cliArgs, "--prefix");
|
|
1889
|
+
const entries = await listConfigEntries("value", {
|
|
1890
|
+
...options,
|
|
1891
|
+
cliArgs,
|
|
1892
|
+
...prefix ? { prefix } : {}
|
|
1893
|
+
});
|
|
1894
|
+
if (options.json) {
|
|
1895
|
+
return printJson(entries);
|
|
1896
|
+
}
|
|
1897
|
+
return entries.map((entry) => `${entry.key}=${printValue(entry.value)}`).join("\n");
|
|
1898
|
+
}
|
|
1899
|
+
if (action === "set") {
|
|
1900
|
+
const valuePath = tail[0] ?? "app.name";
|
|
1901
|
+
const rawValue = tail[1] ?? "";
|
|
1902
|
+
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
1903
|
+
const result = await defineValue("value", valuePath, rawValue, {
|
|
1904
|
+
...options,
|
|
1905
|
+
cliArgs,
|
|
1906
|
+
target
|
|
1907
|
+
});
|
|
1908
|
+
if (options.json) {
|
|
1909
|
+
return printJson({
|
|
1910
|
+
namespace: "value",
|
|
1911
|
+
path: valuePath,
|
|
1912
|
+
target,
|
|
1913
|
+
filePath: result.filePath,
|
|
1914
|
+
value: result.value
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
return `set value.${valuePath} in ${result.filePath}`;
|
|
1918
|
+
}
|
|
1919
|
+
if (action === "delete") {
|
|
1920
|
+
const valuePath = tail[0] ?? "app.name";
|
|
1921
|
+
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
1922
|
+
const result = await deleteValue("value", valuePath, {
|
|
1923
|
+
...options,
|
|
1924
|
+
cliArgs,
|
|
1925
|
+
target
|
|
1926
|
+
});
|
|
1927
|
+
if (options.json) {
|
|
1928
|
+
return printJson(result);
|
|
1929
|
+
}
|
|
1930
|
+
return result.deleted ? `deleted value.${valuePath} from ${result.filePath}` : `no value.${valuePath} found in ${result.filePath}`;
|
|
1931
|
+
}
|
|
1082
1932
|
const runtime = await createRuntimeService(options);
|
|
1083
|
-
const value = runtime.value(
|
|
1933
|
+
const value = runtime.value(tail[0] ?? "app.name");
|
|
1084
1934
|
if (value === void 0) {
|
|
1085
|
-
throw new Error(`Missing CNOS value path: ${
|
|
1935
|
+
throw new Error(`Missing CNOS value path: ${tail[0] ?? "app.name"}`);
|
|
1086
1936
|
}
|
|
1087
1937
|
if (options.json) {
|
|
1088
1938
|
return printJson({
|
|
1089
|
-
key: `value.${
|
|
1939
|
+
key: `value.${tail[0] ?? "app.name"}`,
|
|
1090
1940
|
value
|
|
1091
1941
|
});
|
|
1092
1942
|
}
|
|
@@ -1101,6 +1951,21 @@ function resolveHelpTopic(command, args) {
|
|
|
1101
1951
|
if (command === "export" && args[0] === "env") {
|
|
1102
1952
|
return normalizeHelpTopic([command, args[0]]);
|
|
1103
1953
|
}
|
|
1954
|
+
if (command === "secret" && args[0] && ["set", "create", "add", "list", "delete", "remove"].includes(args[0])) {
|
|
1955
|
+
return normalizeHelpTopic([
|
|
1956
|
+
command,
|
|
1957
|
+
args[0] === "remove" ? "delete" : args[0] === "create" || args[0] === "add" ? "set" : args[0]
|
|
1958
|
+
]);
|
|
1959
|
+
}
|
|
1960
|
+
if (command === "value" && args[0] && ["set", "create", "add", "list", "delete", "remove"].includes(args[0])) {
|
|
1961
|
+
return normalizeHelpTopic([
|
|
1962
|
+
command,
|
|
1963
|
+
args[0] === "remove" ? "delete" : args[0] === "create" || args[0] === "add" ? "set" : args[0]
|
|
1964
|
+
]);
|
|
1965
|
+
}
|
|
1966
|
+
if (command === "profile" && args[0] && ["create", "list", "use", "delete", "remove"].includes(args[0])) {
|
|
1967
|
+
return normalizeHelpTopic([command, args[0] === "remove" ? "delete" : args[0]]);
|
|
1968
|
+
}
|
|
1104
1969
|
return normalizeHelpTopic([command]);
|
|
1105
1970
|
}
|
|
1106
1971
|
async function main(argv) {
|
|
@@ -1137,6 +2002,10 @@ async function main(argv) {
|
|
|
1137
2002
|
return;
|
|
1138
2003
|
case "help-ai":
|
|
1139
2004
|
process.stdout.write(`${runHelpAi(resolveHelpTopic(command, args), options.cliArgs)}
|
|
2005
|
+
`);
|
|
2006
|
+
return;
|
|
2007
|
+
case "version":
|
|
2008
|
+
process.stdout.write(`${runVersion()}
|
|
1140
2009
|
`);
|
|
1141
2010
|
return;
|
|
1142
2011
|
case "init":
|
|
@@ -1152,11 +2021,23 @@ async function main(argv) {
|
|
|
1152
2021
|
`);
|
|
1153
2022
|
return;
|
|
1154
2023
|
case "value":
|
|
1155
|
-
process.stdout.write(`${await runValue(args
|
|
2024
|
+
process.stdout.write(`${await runValue(args.length > 0 ? args : ["app.name"], runtimeOptions)}
|
|
1156
2025
|
`);
|
|
1157
2026
|
return;
|
|
1158
2027
|
case "secret":
|
|
1159
|
-
process.stdout.write(`${await runSecret(args
|
|
2028
|
+
process.stdout.write(`${await runSecret(args.length > 0 ? args : ["app.token"], runtimeOptions)}
|
|
2029
|
+
`);
|
|
2030
|
+
return;
|
|
2031
|
+
case "use":
|
|
2032
|
+
process.stdout.write(`${await runUse(runtimeOptions)}
|
|
2033
|
+
`);
|
|
2034
|
+
return;
|
|
2035
|
+
case "profile":
|
|
2036
|
+
process.stdout.write(`${await runProfile(args, runtimeOptions)}
|
|
2037
|
+
`);
|
|
2038
|
+
return;
|
|
2039
|
+
case "list":
|
|
2040
|
+
process.stdout.write(`${await runList(args, runtimeOptions)}
|
|
1160
2041
|
`);
|
|
1161
2042
|
return;
|
|
1162
2043
|
case "define":
|