@kitsy/cnos 1.1.2 → 1.2.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/README.md +4 -1
- package/dist/browser/index.cjs +94 -0
- package/dist/browser/index.d.cts +16 -0
- package/dist/browser/index.d.ts +16 -0
- package/dist/browser/index.js +67 -0
- package/dist/build/index.cjs +2100 -0
- package/dist/build/index.d.cts +5 -0
- package/dist/build/index.d.ts +5 -0
- package/dist/build/index.js +14 -0
- package/dist/{chunk-33ZDYDQJ.js → chunk-APCTXRUN.js} +550 -349
- package/dist/{chunk-IHSV5AFX.js → chunk-EIN55XXA.js} +1 -1
- package/dist/chunk-JUHPBAEH.js +20 -0
- package/dist/{chunk-JQGGSNCL.js → chunk-MLQGYCO7.js} +1 -1
- package/dist/chunk-PQ4KSV76.js +50 -0
- package/dist/{chunk-IQOUWY6T.js → chunk-RD5WMHPM.js} +1 -1
- package/dist/chunk-SO5XREEU.js +179 -0
- package/dist/{chunk-7FBRVJD6.js → chunk-SXTMTACL.js} +2 -2
- package/dist/{chunk-53HXUSM6.js → chunk-WHUGFPE4.js} +1 -1
- package/dist/{chunk-HOS4E7XO.js → chunk-ZA74BO47.js} +1 -1
- package/dist/{envNaming-BrOk5ndZ.d.cts → envNaming-BTJpH93W.d.cts} +1 -1
- package/dist/{envNaming-DCaNdnrF.d.ts → envNaming-CcsqAel3.d.ts} +1 -1
- package/dist/index.cjs +242 -74
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +14 -132
- package/dist/internal.cjs +428 -10
- package/dist/internal.d.cts +29 -3
- package/dist/internal.d.ts +29 -3
- package/dist/internal.js +27 -1
- package/dist/plugin/basic-schema.d.cts +1 -1
- package/dist/plugin/basic-schema.d.ts +1 -1
- package/dist/plugin/basic-schema.js +2 -2
- package/dist/plugin/cli-args.d.cts +1 -1
- package/dist/plugin/cli-args.d.ts +1 -1
- package/dist/plugin/cli-args.js +2 -2
- package/dist/plugin/dotenv.cjs +4 -4
- package/dist/plugin/dotenv.d.cts +2 -2
- package/dist/plugin/dotenv.d.ts +2 -2
- package/dist/plugin/dotenv.js +2 -2
- package/dist/plugin/env-export.cjs +29 -46
- package/dist/plugin/env-export.d.cts +2 -2
- package/dist/plugin/env-export.d.ts +2 -2
- package/dist/plugin/env-export.js +2 -2
- package/dist/plugin/filesystem.cjs +1 -1
- package/dist/plugin/filesystem.d.cts +1 -1
- package/dist/plugin/filesystem.d.ts +1 -1
- package/dist/plugin/filesystem.js +2 -2
- package/dist/plugin/process-env.cjs +4 -4
- package/dist/plugin/process-env.d.cts +2 -2
- package/dist/plugin/process-env.d.ts +2 -2
- package/dist/plugin/process-env.js +2 -2
- package/dist/{plugin-BVNEHj19.d.cts → plugin-DkOIT5uI.d.cts} +30 -2
- package/dist/{plugin-BVNEHj19.d.ts → plugin-DkOIT5uI.d.ts} +30 -2
- package/dist/runtime/index.cjs +2288 -0
- package/dist/runtime/index.d.cts +23 -0
- package/dist/runtime/index.d.ts +23 -0
- package/dist/runtime/index.js +190 -0
- package/dist/{toPublicEnv-Gwz3xTK0.d.ts → toPublicEnv-C9clvXLo.d.ts} +1 -1
- package/dist/{toPublicEnv-Dd152fFy.d.cts → toPublicEnv-DvFeV3qG.d.cts} +1 -1
- package/package.json +16 -1
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// ../core/src/utils/path.ts
|
|
2
|
-
import { access } from "fs/promises";
|
|
3
|
-
import os from "os";
|
|
4
|
-
import path from "path";
|
|
5
|
-
|
|
6
1
|
// ../core/src/errors.ts
|
|
7
2
|
var CnosError = class extends Error {
|
|
8
3
|
constructor(message) {
|
|
@@ -17,6 +12,11 @@ var CnosManifestError = class extends CnosError {
|
|
|
17
12
|
}
|
|
18
13
|
manifestPath;
|
|
19
14
|
};
|
|
15
|
+
var CnosSecurityError = class extends CnosError {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
20
|
var CnosKeyNotFoundError = class extends CnosError {
|
|
21
21
|
constructor(key) {
|
|
22
22
|
super(`Missing required CNOS config key: ${key}`);
|
|
@@ -25,7 +25,43 @@ var CnosKeyNotFoundError = class extends CnosError {
|
|
|
25
25
|
key;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
// ../core/src/runtime/inspect.ts
|
|
29
|
+
function inspectValue(graph, key) {
|
|
30
|
+
const entry = graph.entries.get(key);
|
|
31
|
+
if (!entry) {
|
|
32
|
+
throw new CnosKeyNotFoundError(key);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
key: entry.key,
|
|
36
|
+
value: entry.value,
|
|
37
|
+
namespace: entry.namespace,
|
|
38
|
+
profile: graph.profile,
|
|
39
|
+
profileSource: graph.profileSource,
|
|
40
|
+
workspace: {
|
|
41
|
+
id: graph.workspace.workspaceId,
|
|
42
|
+
source: graph.workspace.workspaceSource,
|
|
43
|
+
chain: graph.workspace.workspaceChain
|
|
44
|
+
},
|
|
45
|
+
winner: {
|
|
46
|
+
sourceId: entry.winner.sourceId,
|
|
47
|
+
pluginId: entry.winner.pluginId,
|
|
48
|
+
workspaceId: entry.winner.workspaceId,
|
|
49
|
+
...entry.winner.origin ? { origin: entry.winner.origin } : {}
|
|
50
|
+
},
|
|
51
|
+
overridden: entry.overridden.map((override) => ({
|
|
52
|
+
sourceId: override.sourceId,
|
|
53
|
+
pluginId: override.pluginId,
|
|
54
|
+
workspaceId: override.workspaceId,
|
|
55
|
+
value: override.value,
|
|
56
|
+
...override.origin ? { origin: override.origin } : {}
|
|
57
|
+
}))
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
28
61
|
// ../core/src/utils/path.ts
|
|
62
|
+
import { access } from "fs/promises";
|
|
63
|
+
import os from "os";
|
|
64
|
+
import path from "path";
|
|
29
65
|
var PRIMARY_CNOS_DIR = ".cnos";
|
|
30
66
|
var LEGACY_CNOS_DIR = "cnos";
|
|
31
67
|
async function exists(filePath) {
|
|
@@ -120,10 +156,336 @@ function stringifyYaml(value) {
|
|
|
120
156
|
return stringify(value);
|
|
121
157
|
}
|
|
122
158
|
|
|
159
|
+
// ../core/src/manifest/loadManifest.ts
|
|
160
|
+
import { readFile } from "fs/promises";
|
|
161
|
+
import path2 from "path";
|
|
162
|
+
|
|
163
|
+
// ../core/src/manifest/normalizeManifest.ts
|
|
164
|
+
var DEFAULT_RESOLVE_FROM = ["cli.profile", "env.CNOS_PROFILE", "default"];
|
|
165
|
+
var DEFAULT_LOADERS = [
|
|
166
|
+
"filesystem-values",
|
|
167
|
+
"filesystem-secrets",
|
|
168
|
+
"dotenv",
|
|
169
|
+
"process-env",
|
|
170
|
+
"cli-args"
|
|
171
|
+
];
|
|
172
|
+
var DEFAULT_VALIDATORS = ["basic-schema"];
|
|
173
|
+
var DEFAULT_EXPORTERS = ["env", "public-env"];
|
|
174
|
+
var DEFAULT_INSPECTORS = ["provenance"];
|
|
175
|
+
var DEFAULT_FRAMEWORK_PREFIXES = {
|
|
176
|
+
next: "NEXT_PUBLIC_",
|
|
177
|
+
vite: "VITE_",
|
|
178
|
+
nuxt: "NUXT_PUBLIC_"
|
|
179
|
+
};
|
|
180
|
+
var DEFAULT_NAMESPACES = {
|
|
181
|
+
value: {
|
|
182
|
+
kind: "data",
|
|
183
|
+
shareable: true
|
|
184
|
+
},
|
|
185
|
+
secret: {
|
|
186
|
+
kind: "data",
|
|
187
|
+
shareable: false,
|
|
188
|
+
sensitive: true
|
|
189
|
+
},
|
|
190
|
+
meta: {
|
|
191
|
+
kind: "system",
|
|
192
|
+
shareable: false,
|
|
193
|
+
readonly: true
|
|
194
|
+
},
|
|
195
|
+
public: {
|
|
196
|
+
kind: "projection",
|
|
197
|
+
source: "promote",
|
|
198
|
+
shareable: true,
|
|
199
|
+
readonly: true
|
|
200
|
+
},
|
|
201
|
+
env: {
|
|
202
|
+
kind: "projection",
|
|
203
|
+
source: "envMapping",
|
|
204
|
+
shareable: true,
|
|
205
|
+
readonly: true
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
function validateResolveFrom(resolveFrom) {
|
|
209
|
+
const validValues = ["cli.profile", "env.CNOS_PROFILE", "default"];
|
|
210
|
+
for (const entry of resolveFrom) {
|
|
211
|
+
if (!validValues.includes(entry)) {
|
|
212
|
+
throw new CnosManifestError(`Unsupported profiles.resolveFrom entry: ${entry}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return resolveFrom;
|
|
216
|
+
}
|
|
217
|
+
function normalizeWorkspaceItems(items) {
|
|
218
|
+
return Object.fromEntries(
|
|
219
|
+
Object.entries(items ?? {}).map(([workspaceId, item]) => [
|
|
220
|
+
workspaceId,
|
|
221
|
+
{
|
|
222
|
+
extends: Array.isArray(item?.extends) ? item.extends.map((entry) => entry.trim()).filter(Boolean) : item?.extends ? [item.extends.trim()].filter(Boolean) : [],
|
|
223
|
+
...item?.globalId?.trim() ? { globalId: item.globalId.trim() } : {}
|
|
224
|
+
}
|
|
225
|
+
])
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
function normalizeNamespaces(namespaces) {
|
|
229
|
+
const normalized = Object.fromEntries(
|
|
230
|
+
Object.entries(namespaces ?? {}).map(([namespace, definition]) => [
|
|
231
|
+
namespace,
|
|
232
|
+
{
|
|
233
|
+
kind: definition.kind ?? "data",
|
|
234
|
+
shareable: definition.shareable ?? false,
|
|
235
|
+
...definition.sensitive !== void 0 ? { sensitive: definition.sensitive } : {},
|
|
236
|
+
...definition.readonly !== void 0 ? { readonly: definition.readonly } : {},
|
|
237
|
+
...definition.source ? { source: definition.source } : {}
|
|
238
|
+
}
|
|
239
|
+
])
|
|
240
|
+
);
|
|
241
|
+
return {
|
|
242
|
+
...DEFAULT_NAMESPACES,
|
|
243
|
+
...normalized
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function normalizeVaults(vaults) {
|
|
247
|
+
return Object.fromEntries(
|
|
248
|
+
Object.entries(vaults ?? {}).map(([name, definition]) => {
|
|
249
|
+
const provider = definition.provider?.trim();
|
|
250
|
+
if (!provider) {
|
|
251
|
+
throw new CnosManifestError(`Vault "${name}" requires a provider`);
|
|
252
|
+
}
|
|
253
|
+
return [
|
|
254
|
+
name,
|
|
255
|
+
{
|
|
256
|
+
provider,
|
|
257
|
+
...definition.passphrase?.trim() ? {
|
|
258
|
+
passphrase: definition.passphrase.trim()
|
|
259
|
+
} : {}
|
|
260
|
+
}
|
|
261
|
+
];
|
|
262
|
+
})
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
function normalizeManifest(manifest) {
|
|
266
|
+
const version = manifest.version ?? 1;
|
|
267
|
+
if (version !== 1) {
|
|
268
|
+
throw new CnosManifestError(`Unsupported CNOS manifest version: ${version}`);
|
|
269
|
+
}
|
|
270
|
+
const projectName = manifest.project?.name?.trim();
|
|
271
|
+
if (!projectName) {
|
|
272
|
+
throw new CnosManifestError("Manifest requires project.name");
|
|
273
|
+
}
|
|
274
|
+
const defaultProfile = manifest.profiles?.default?.trim() || "base";
|
|
275
|
+
const workspaceItems = normalizeWorkspaceItems(manifest.workspaces?.items);
|
|
276
|
+
const resolveFrom = validateResolveFrom(manifest.profiles?.resolveFrom ?? DEFAULT_RESOLVE_FROM);
|
|
277
|
+
const filesystemValues = {
|
|
278
|
+
root: "./",
|
|
279
|
+
format: "yaml",
|
|
280
|
+
...manifest.sources?.["filesystem-values"] ?? {}
|
|
281
|
+
};
|
|
282
|
+
const filesystemSecrets = {
|
|
283
|
+
root: "./",
|
|
284
|
+
format: "yaml",
|
|
285
|
+
...manifest.sources?.["filesystem-secrets"] ?? {}
|
|
286
|
+
};
|
|
287
|
+
const dotenv = {
|
|
288
|
+
root: "./env",
|
|
289
|
+
...manifest.sources?.dotenv ?? {}
|
|
290
|
+
};
|
|
291
|
+
return {
|
|
292
|
+
version: 1,
|
|
293
|
+
project: {
|
|
294
|
+
name: projectName
|
|
295
|
+
},
|
|
296
|
+
workspaces: {
|
|
297
|
+
...manifest.workspaces?.default?.trim() ? {
|
|
298
|
+
default: manifest.workspaces.default.trim()
|
|
299
|
+
} : {},
|
|
300
|
+
global: {
|
|
301
|
+
enabled: manifest.workspaces?.global?.enabled ?? false,
|
|
302
|
+
...manifest.workspaces?.global?.root?.trim() ? {
|
|
303
|
+
root: manifest.workspaces.global.root.trim()
|
|
304
|
+
} : {},
|
|
305
|
+
allowWrite: manifest.workspaces?.global?.allowWrite ?? false
|
|
306
|
+
},
|
|
307
|
+
items: workspaceItems
|
|
308
|
+
},
|
|
309
|
+
profiles: {
|
|
310
|
+
default: defaultProfile,
|
|
311
|
+
resolveFrom
|
|
312
|
+
},
|
|
313
|
+
plugins: {
|
|
314
|
+
loaders: manifest.plugins?.loaders ?? DEFAULT_LOADERS,
|
|
315
|
+
resolver: manifest.plugins?.resolver ?? "profile-aware",
|
|
316
|
+
validators: manifest.plugins?.validators ?? DEFAULT_VALIDATORS,
|
|
317
|
+
exporters: manifest.plugins?.exporters ?? DEFAULT_EXPORTERS,
|
|
318
|
+
inspectors: manifest.plugins?.inspectors ?? DEFAULT_INSPECTORS
|
|
319
|
+
},
|
|
320
|
+
sources: {
|
|
321
|
+
...manifest.sources ?? {},
|
|
322
|
+
"filesystem-values": filesystemValues,
|
|
323
|
+
"filesystem-secrets": filesystemSecrets,
|
|
324
|
+
dotenv
|
|
325
|
+
},
|
|
326
|
+
resolution: {
|
|
327
|
+
precedence: manifest.resolution?.precedence ?? [
|
|
328
|
+
"filesystem-values",
|
|
329
|
+
"filesystem-secrets",
|
|
330
|
+
"dotenv",
|
|
331
|
+
"process-env",
|
|
332
|
+
"cli-args"
|
|
333
|
+
],
|
|
334
|
+
arrayPolicy: manifest.resolution?.arrayPolicy ?? "replace"
|
|
335
|
+
},
|
|
336
|
+
envMapping: {
|
|
337
|
+
...manifest.envMapping?.convention ? {
|
|
338
|
+
convention: manifest.envMapping.convention
|
|
339
|
+
} : {},
|
|
340
|
+
explicit: manifest.envMapping?.explicit ?? {}
|
|
341
|
+
},
|
|
342
|
+
public: {
|
|
343
|
+
promote: manifest.public?.promote ?? [],
|
|
344
|
+
frameworks: {
|
|
345
|
+
...DEFAULT_FRAMEWORK_PREFIXES,
|
|
346
|
+
...manifest.public?.frameworks ?? {}
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
namespaces: normalizeNamespaces(manifest.namespaces),
|
|
350
|
+
vaults: normalizeVaults(manifest.vaults),
|
|
351
|
+
writePolicy: {
|
|
352
|
+
define: {
|
|
353
|
+
defaultProfile: manifest.writePolicy?.define?.defaultProfile ?? defaultProfile,
|
|
354
|
+
targets: {
|
|
355
|
+
value: manifest.writePolicy?.define?.targets?.value ?? "./values/app.yml",
|
|
356
|
+
secret: manifest.writePolicy?.define?.targets?.secret ?? "./secrets/app.yml"
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
schema: manifest.schema ?? {}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ../core/src/manifest/loadManifest.ts
|
|
365
|
+
async function loadManifest(options = {}) {
|
|
366
|
+
const manifestRoot = await resolveManifestRoot(options.root);
|
|
367
|
+
const manifestPath = path2.join(manifestRoot, "cnos.yml");
|
|
368
|
+
let source;
|
|
369
|
+
try {
|
|
370
|
+
source = await readFile(manifestPath, "utf8");
|
|
371
|
+
} catch {
|
|
372
|
+
throw new CnosManifestError("Unable to read CNOS manifest", manifestPath);
|
|
373
|
+
}
|
|
374
|
+
const rawManifest = parseYaml(source);
|
|
375
|
+
if (!rawManifest || typeof rawManifest !== "object") {
|
|
376
|
+
throw new CnosManifestError("CNOS manifest must be a YAML object", manifestPath);
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
manifestRoot,
|
|
380
|
+
repoRoot: path2.dirname(manifestRoot),
|
|
381
|
+
manifestPath,
|
|
382
|
+
manifest: normalizeManifest(rawManifest),
|
|
383
|
+
rawManifest
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ../core/src/promotions/validatePromotion.ts
|
|
388
|
+
var DEFAULT_DATA_NAMESPACE = {
|
|
389
|
+
kind: "data",
|
|
390
|
+
shareable: false
|
|
391
|
+
};
|
|
392
|
+
function getNamespaceNameForKey(key) {
|
|
393
|
+
const [namespace] = key.split(".");
|
|
394
|
+
if (!namespace || !key.includes(".")) {
|
|
395
|
+
throw new CnosManifestError(`Logical key must be namespace-qualified: ${key}`);
|
|
396
|
+
}
|
|
397
|
+
return namespace;
|
|
398
|
+
}
|
|
399
|
+
function getNamespaceDefinition(manifest, namespaceOrKey) {
|
|
400
|
+
const namespace = namespaceOrKey.includes(".") ? getNamespaceNameForKey(namespaceOrKey) : namespaceOrKey;
|
|
401
|
+
return manifest.namespaces[namespace] ?? DEFAULT_DATA_NAMESPACE;
|
|
402
|
+
}
|
|
403
|
+
function ensureProjectionAllowed(manifest, key, target) {
|
|
404
|
+
const namespace = getNamespaceNameForKey(key);
|
|
405
|
+
const definition = getNamespaceDefinition(manifest, namespace);
|
|
406
|
+
if (definition.kind !== "data") {
|
|
407
|
+
throw new CnosManifestError(
|
|
408
|
+
`Cannot promote ${key} to ${target} because namespace "${namespace}" is not a data namespace.`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
if (definition.sensitive) {
|
|
412
|
+
throw new CnosSecurityError(
|
|
413
|
+
`Cannot promote ${key} to ${target} because namespace "${namespace}" is sensitive.`
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
if (!definition.shareable) {
|
|
417
|
+
throw new CnosSecurityError(
|
|
418
|
+
`Cannot promote ${key} to ${target} because namespace "${namespace}" is not shareable.`
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function validateProjectionIssue(manifest, key, target) {
|
|
423
|
+
try {
|
|
424
|
+
ensureProjectionAllowed(manifest, key, target);
|
|
425
|
+
return void 0;
|
|
426
|
+
} catch (error) {
|
|
427
|
+
return {
|
|
428
|
+
code: target === "public" ? "public.invalid-promotion" : "env.invalid-mapping",
|
|
429
|
+
key,
|
|
430
|
+
message: error instanceof Error ? error.message : String(error)
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ../core/src/runtime/projection.ts
|
|
436
|
+
function setNestedValue(target, pathSegments, value) {
|
|
437
|
+
const [head, ...tail] = pathSegments;
|
|
438
|
+
if (!head) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (tail.length === 0) {
|
|
442
|
+
target[head] = value;
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const current = target[head];
|
|
446
|
+
const nextTarget = current && typeof current === "object" && !Array.isArray(current) ? current : {};
|
|
447
|
+
target[head] = nextTarget;
|
|
448
|
+
setNestedValue(nextTarget, tail, value);
|
|
449
|
+
}
|
|
450
|
+
function toNamespaceObject(graph, namespace) {
|
|
451
|
+
const output = {};
|
|
452
|
+
const resolvedEntries = Array.from(graph.entries.values()).sort(
|
|
453
|
+
(left, right) => left.key.localeCompare(right.key)
|
|
454
|
+
);
|
|
455
|
+
for (const entry of resolvedEntries) {
|
|
456
|
+
if (namespace && entry.namespace !== namespace) {
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const valuePath = namespace ? stripNamespace(entry.key) : entry.key;
|
|
460
|
+
setNestedValue(output, valuePath.split("."), entry.value);
|
|
461
|
+
}
|
|
462
|
+
return output;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ../core/src/runtime/read.ts
|
|
466
|
+
function readValue(graph, key) {
|
|
467
|
+
return graph.entries.get(key)?.value;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// ../core/src/runtime/readOr.ts
|
|
471
|
+
function readOrValue(graph, key, fallback) {
|
|
472
|
+
const value = readValue(graph, key);
|
|
473
|
+
return value === void 0 ? fallback : value;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ../core/src/runtime/require.ts
|
|
477
|
+
function requireValue(graph, key) {
|
|
478
|
+
const value = readValue(graph, key);
|
|
479
|
+
if (value === void 0) {
|
|
480
|
+
throw new CnosKeyNotFoundError(key);
|
|
481
|
+
}
|
|
482
|
+
return value;
|
|
483
|
+
}
|
|
484
|
+
|
|
123
485
|
// ../core/src/utils/secretStore.ts
|
|
124
486
|
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
|
|
125
|
-
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
126
|
-
import
|
|
487
|
+
import { mkdir, readdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
488
|
+
import path3 from "path";
|
|
127
489
|
function isObject(value) {
|
|
128
490
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
129
491
|
}
|
|
@@ -131,13 +493,13 @@ function isSecretReference(value) {
|
|
|
131
493
|
return isObject(value) && typeof value.provider === "string" && value.provider.trim().length > 0 && typeof value.ref === "string" && value.ref.trim().length > 0 && (value.vault === void 0 && true || typeof value.vault === "string" && value.vault.trim().length > 0) && Object.keys(value).every((key) => ["provider", "ref", "vault"].includes(key));
|
|
132
494
|
}
|
|
133
495
|
function resolveSecretStoreRoot(processEnv = process.env) {
|
|
134
|
-
return
|
|
496
|
+
return path3.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
|
|
135
497
|
}
|
|
136
498
|
function resolveSecretVaultFile(storeRoot, vault = "default") {
|
|
137
|
-
return
|
|
499
|
+
return path3.join(storeRoot, "vaults", `${vault}.json`);
|
|
138
500
|
}
|
|
139
501
|
function resolveSecretStoreFile(storeRoot, ref, vault = "default") {
|
|
140
|
-
return
|
|
502
|
+
return path3.join(storeRoot, "vaults", vault, "store", ...ref.split("/")).concat(".json");
|
|
141
503
|
}
|
|
142
504
|
function deriveKey(passphrase, salt) {
|
|
143
505
|
return scryptSync(passphrase, salt, 32);
|
|
@@ -146,6 +508,38 @@ function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
|
|
|
146
508
|
const vaultToken = vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
147
509
|
return processEnv[`CNOS_SECRET_PASSPHRASE_${vaultToken}`] ?? processEnv.CNOS_SECRET_PASSPHRASE;
|
|
148
510
|
}
|
|
511
|
+
function getVaultPassphraseEnvVar(vault = "default") {
|
|
512
|
+
const vaultToken = vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
513
|
+
return vaultToken && vaultToken !== "DEFAULT" ? `CNOS_SECRET_PASSPHRASE_${vaultToken}` : "CNOS_SECRET_PASSPHRASE";
|
|
514
|
+
}
|
|
515
|
+
function isPassphraseEnvRef(value) {
|
|
516
|
+
return typeof value === "string" && value.startsWith("env:") && value.length > 4;
|
|
517
|
+
}
|
|
518
|
+
function resolveConfiguredVaultPassphrase(definition, vault = "default", processEnv = process.env) {
|
|
519
|
+
if (definition?.provider !== "local") {
|
|
520
|
+
return void 0;
|
|
521
|
+
}
|
|
522
|
+
const passphraseRef = definition.passphrase;
|
|
523
|
+
if (typeof passphraseRef === "string" && passphraseRef.startsWith("env:") && passphraseRef.length > 4) {
|
|
524
|
+
return processEnv[passphraseRef.slice(4)];
|
|
525
|
+
}
|
|
526
|
+
if (passphraseRef) {
|
|
527
|
+
return passphraseRef;
|
|
528
|
+
}
|
|
529
|
+
return resolveSecretPassphrase(vault, processEnv);
|
|
530
|
+
}
|
|
531
|
+
function resolveVaultDefinition(vaults, vault = "default") {
|
|
532
|
+
const definition = vaults?.[vault];
|
|
533
|
+
const provider = definition?.provider ?? "local";
|
|
534
|
+
return {
|
|
535
|
+
name: vault,
|
|
536
|
+
provider,
|
|
537
|
+
...definition?.passphrase ? {
|
|
538
|
+
passphrase: definition.passphrase
|
|
539
|
+
} : {},
|
|
540
|
+
requiresPassphrase: provider === "local"
|
|
541
|
+
};
|
|
542
|
+
}
|
|
149
543
|
function encryptDocument(value, passphrase) {
|
|
150
544
|
const salt = randomBytes(16);
|
|
151
545
|
const iv = randomBytes(12);
|
|
@@ -176,7 +570,7 @@ function decryptDocument(document, passphrase) {
|
|
|
176
570
|
async function createSecretVault(storeRoot, vault, passphrase) {
|
|
177
571
|
const normalizedVault = vault.trim() || "default";
|
|
178
572
|
const filePath = resolveSecretVaultFile(storeRoot, normalizedVault);
|
|
179
|
-
await mkdir(
|
|
573
|
+
await mkdir(path3.dirname(filePath), { recursive: true });
|
|
180
574
|
const document = {
|
|
181
575
|
version: 1,
|
|
182
576
|
name: normalizedVault,
|
|
@@ -190,7 +584,7 @@ async function ensureSecretVault(storeRoot, vault, passphrase) {
|
|
|
190
584
|
const normalizedVault = vault.trim() || "default";
|
|
191
585
|
const filePath = resolveSecretVaultFile(storeRoot, normalizedVault);
|
|
192
586
|
try {
|
|
193
|
-
await
|
|
587
|
+
await readFile2(filePath, "utf8");
|
|
194
588
|
return filePath;
|
|
195
589
|
} catch (error) {
|
|
196
590
|
if (error.code !== "ENOENT") {
|
|
@@ -200,7 +594,7 @@ async function ensureSecretVault(storeRoot, vault, passphrase) {
|
|
|
200
594
|
return createSecretVault(storeRoot, normalizedVault, passphrase);
|
|
201
595
|
}
|
|
202
596
|
async function listSecretVaults(storeRoot) {
|
|
203
|
-
const vaultRoot =
|
|
597
|
+
const vaultRoot = path3.join(storeRoot, "vaults");
|
|
204
598
|
try {
|
|
205
599
|
const entries = await readdir(vaultRoot, { withFileTypes: true });
|
|
206
600
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.replace(/\.json$/, "")).sort((left, right) => left.localeCompare(right));
|
|
@@ -211,7 +605,7 @@ async function listSecretVaults(storeRoot) {
|
|
|
211
605
|
async function writeLocalSecret(storeRoot, ref, value, passphrase, vault = "default") {
|
|
212
606
|
await ensureSecretVault(storeRoot, vault, passphrase);
|
|
213
607
|
const filePath = resolveSecretStoreFile(storeRoot, ref, vault);
|
|
214
|
-
await mkdir(
|
|
608
|
+
await mkdir(path3.dirname(filePath), { recursive: true });
|
|
215
609
|
await writeFile(filePath, JSON.stringify(encryptDocument(value, passphrase), null, 2), "utf8");
|
|
216
610
|
return filePath;
|
|
217
611
|
}
|
|
@@ -222,7 +616,7 @@ async function readLocalSecret(storeRoot, ref, passphrase, vault = "default") {
|
|
|
222
616
|
);
|
|
223
617
|
}
|
|
224
618
|
const filePath = resolveSecretStoreFile(storeRoot, ref, vault);
|
|
225
|
-
const source = await
|
|
619
|
+
const source = await readFile2(filePath, "utf8");
|
|
226
620
|
const document = JSON.parse(source);
|
|
227
621
|
if (document.version !== 1 || document.algorithm !== "aes-256-gcm" || typeof document.salt !== "string" || typeof document.iv !== "string" || typeof document.tag !== "string" || typeof document.ciphertext !== "string") {
|
|
228
622
|
throw new CnosManifestError("Invalid local secret document", filePath);
|
|
@@ -254,6 +648,10 @@ function toEnv(graph, manifest, options = {}) {
|
|
|
254
648
|
if (!entry) {
|
|
255
649
|
continue;
|
|
256
650
|
}
|
|
651
|
+
const namespaceDefinition = getNamespaceDefinition(manifest, entry.namespace);
|
|
652
|
+
if (namespaceDefinition.kind !== "data" || !namespaceDefinition.shareable || namespaceDefinition.sensitive) {
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
257
655
|
if (entry.namespace === "secret" && !includeSecrets) {
|
|
258
656
|
continue;
|
|
259
657
|
}
|
|
@@ -265,64 +663,9 @@ function toEnv(graph, manifest, options = {}) {
|
|
|
265
663
|
return output;
|
|
266
664
|
}
|
|
267
665
|
|
|
268
|
-
// ../core/src/utils/envNaming.ts
|
|
269
|
-
function normalizeMappingConfig(config = {}) {
|
|
270
|
-
return {
|
|
271
|
-
convention: config.convention,
|
|
272
|
-
explicit: config.explicit ?? {}
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
function toScreamingSnakeSegment(segment) {
|
|
276
|
-
return segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
277
|
-
}
|
|
278
|
-
function toScreamingSnake(path8) {
|
|
279
|
-
return path8.split(".").map((segment) => toScreamingSnakeSegment(segment)).filter(Boolean).join("_");
|
|
280
|
-
}
|
|
281
|
-
function fromScreamingSnake(path8) {
|
|
282
|
-
return path8.split("_").map((segment) => segment.trim().toLowerCase()).filter(Boolean).join(".");
|
|
283
|
-
}
|
|
284
|
-
function logicalKeyToEnvVar(key, config = {}) {
|
|
285
|
-
const normalized = normalizeMappingConfig(config);
|
|
286
|
-
const explicitEntry = Object.entries(normalized.explicit).find(([, logicalKey]) => logicalKey === key);
|
|
287
|
-
if (explicitEntry) {
|
|
288
|
-
return explicitEntry[0];
|
|
289
|
-
}
|
|
290
|
-
if (normalized.convention !== "SCREAMING_SNAKE") {
|
|
291
|
-
return void 0;
|
|
292
|
-
}
|
|
293
|
-
if (key.startsWith("value.")) {
|
|
294
|
-
return toScreamingSnake(key.slice("value.".length));
|
|
295
|
-
}
|
|
296
|
-
if (key.startsWith("secret.")) {
|
|
297
|
-
return `SECRET_${toScreamingSnake(key.slice("secret.".length))}`;
|
|
298
|
-
}
|
|
299
|
-
return void 0;
|
|
300
|
-
}
|
|
301
|
-
function envVarToLogicalKey(envVar, config = {}) {
|
|
302
|
-
const normalized = normalizeMappingConfig(config);
|
|
303
|
-
const explicitMatch = normalized.explicit[envVar];
|
|
304
|
-
if (explicitMatch) {
|
|
305
|
-
return explicitMatch;
|
|
306
|
-
}
|
|
307
|
-
if (normalized.convention !== "SCREAMING_SNAKE") {
|
|
308
|
-
return void 0;
|
|
309
|
-
}
|
|
310
|
-
if (envVar.startsWith("SECRET_")) {
|
|
311
|
-
const stripped = envVar.slice("SECRET_".length);
|
|
312
|
-
if (!stripped) {
|
|
313
|
-
return void 0;
|
|
314
|
-
}
|
|
315
|
-
return `secret.${fromScreamingSnake(stripped)}`;
|
|
316
|
-
}
|
|
317
|
-
if (!/^[A-Z][A-Z0-9_]*$/.test(envVar)) {
|
|
318
|
-
return void 0;
|
|
319
|
-
}
|
|
320
|
-
return `value.${fromScreamingSnake(envVar)}`;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
666
|
// ../core/src/runtime/toPublicEnv.ts
|
|
324
|
-
function
|
|
325
|
-
return
|
|
667
|
+
function fallbackPublicEnvVar(valuePath) {
|
|
668
|
+
return valuePath.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
326
669
|
}
|
|
327
670
|
function normalizeEnvValue2(value) {
|
|
328
671
|
if (value === void 0 || value === null) {
|
|
@@ -349,22 +692,12 @@ function resolvePublicPrefix(manifest, options) {
|
|
|
349
692
|
}
|
|
350
693
|
return configuredPrefix;
|
|
351
694
|
}
|
|
352
|
-
function ensurePublicPromotionKey(key) {
|
|
353
|
-
if (!key.startsWith("value.")) {
|
|
354
|
-
throw new CnosManifestError(`public.promote may only contain value.* keys: ${key}`);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
695
|
function toPublicEnv(graph, manifest, options = {}) {
|
|
358
696
|
const prefix = resolvePublicPrefix(manifest, options);
|
|
359
697
|
const output = {};
|
|
360
|
-
const promotions =
|
|
361
|
-
for (const
|
|
362
|
-
|
|
363
|
-
const resolved = graph.entries.get(key);
|
|
364
|
-
if (!resolved) {
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
const baseEnvVar = logicalKeyToEnvVar(key, manifest.envMapping) ?? fallbackValueEnvVar(key);
|
|
698
|
+
const promotions = Array.from(graph.entries.values()).filter((entry) => entry.namespace === "public").sort((left, right) => left.key.localeCompare(right.key));
|
|
699
|
+
for (const resolved of promotions) {
|
|
700
|
+
const baseEnvVar = fallbackPublicEnvVar(stripNamespace(resolved.key));
|
|
368
701
|
const envVar = prefix && !baseEnvVar.startsWith(prefix) ? `${prefix}${baseEnvVar}` : baseEnvVar;
|
|
369
702
|
output[envVar] = normalizeEnvValue2(resolved.value);
|
|
370
703
|
}
|
|
@@ -373,54 +706,22 @@ function toPublicEnv(graph, manifest, options = {}) {
|
|
|
373
706
|
|
|
374
707
|
// ../core/src/runtime/dump.ts
|
|
375
708
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
376
|
-
import
|
|
377
|
-
|
|
378
|
-
// ../core/src/runtime/projection.ts
|
|
379
|
-
function setNestedValue(target, pathSegments, value) {
|
|
380
|
-
const [head, ...tail] = pathSegments;
|
|
381
|
-
if (!head) {
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
if (tail.length === 0) {
|
|
385
|
-
target[head] = value;
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
const current = target[head];
|
|
389
|
-
const nextTarget = current && typeof current === "object" && !Array.isArray(current) ? current : {};
|
|
390
|
-
target[head] = nextTarget;
|
|
391
|
-
setNestedValue(nextTarget, tail, value);
|
|
392
|
-
}
|
|
393
|
-
function toNamespaceObject(graph, namespace) {
|
|
394
|
-
const output = {};
|
|
395
|
-
const resolvedEntries = Array.from(graph.entries.values()).sort(
|
|
396
|
-
(left, right) => left.key.localeCompare(right.key)
|
|
397
|
-
);
|
|
398
|
-
for (const entry of resolvedEntries) {
|
|
399
|
-
if (namespace && entry.namespace !== namespace) {
|
|
400
|
-
continue;
|
|
401
|
-
}
|
|
402
|
-
const valuePath = namespace ? stripNamespace(entry.key) : entry.key;
|
|
403
|
-
setNestedValue(output, valuePath.split("."), entry.value);
|
|
404
|
-
}
|
|
405
|
-
return output;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// ../core/src/runtime/dump.ts
|
|
709
|
+
import path4 from "path";
|
|
409
710
|
function buildDumpFiles(graph, options = {}) {
|
|
410
|
-
const basePath = options.flatten ? "" :
|
|
711
|
+
const basePath = options.flatten ? "" : path4.posix.join("workspaces", graph.workspace.workspaceId);
|
|
411
712
|
const values = toNamespaceObject(graph, "value");
|
|
412
713
|
const secrets = toNamespaceObject(graph, "secret");
|
|
413
714
|
const files = [];
|
|
414
715
|
if (Object.keys(values).length > 0) {
|
|
415
716
|
files.push({
|
|
416
|
-
path:
|
|
717
|
+
path: path4.posix.join(basePath, "values", graph.profile, "app.yml"),
|
|
417
718
|
namespace: "value",
|
|
418
719
|
content: stringifyYaml(values)
|
|
419
720
|
});
|
|
420
721
|
}
|
|
421
722
|
if (Object.keys(secrets).length > 0) {
|
|
422
723
|
files.push({
|
|
423
|
-
path:
|
|
724
|
+
path: path4.posix.join(basePath, "secrets", graph.profile, "app.yml"),
|
|
424
725
|
namespace: "secret",
|
|
425
726
|
content: stringifyYaml(secrets)
|
|
426
727
|
});
|
|
@@ -436,11 +737,11 @@ function planDump(graph, options = {}) {
|
|
|
436
737
|
};
|
|
437
738
|
}
|
|
438
739
|
async function writeDump(graph, options) {
|
|
439
|
-
const root =
|
|
740
|
+
const root = path4.resolve(options.to);
|
|
440
741
|
const plan = planDump(graph, options);
|
|
441
742
|
for (const file of plan.files) {
|
|
442
|
-
const destination =
|
|
443
|
-
await mkdir2(
|
|
743
|
+
const destination = path4.join(root, file.path);
|
|
744
|
+
await mkdir2(path4.dirname(destination), { recursive: true });
|
|
444
745
|
await writeFile2(destination, file.content, "utf8");
|
|
445
746
|
}
|
|
446
747
|
return {
|
|
@@ -457,9 +758,64 @@ function flattenObject(value, prefix = "") {
|
|
|
457
758
|
Object.assign(accumulator, flattenObject(nestedValue, nextKey));
|
|
458
759
|
return accumulator;
|
|
459
760
|
}
|
|
460
|
-
accumulator[nextKey] = nestedValue;
|
|
461
|
-
return accumulator;
|
|
462
|
-
}, {});
|
|
761
|
+
accumulator[nextKey] = nestedValue;
|
|
762
|
+
return accumulator;
|
|
763
|
+
}, {});
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// ../core/src/utils/envNaming.ts
|
|
767
|
+
function normalizeMappingConfig(config = {}) {
|
|
768
|
+
return {
|
|
769
|
+
convention: config.convention,
|
|
770
|
+
explicit: config.explicit ?? {}
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function toScreamingSnakeSegment(segment) {
|
|
774
|
+
return segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
775
|
+
}
|
|
776
|
+
function toScreamingSnake(path8) {
|
|
777
|
+
return path8.split(".").map((segment) => toScreamingSnakeSegment(segment)).filter(Boolean).join("_");
|
|
778
|
+
}
|
|
779
|
+
function fromScreamingSnake(path8) {
|
|
780
|
+
return path8.split("_").map((segment) => segment.trim().toLowerCase()).filter(Boolean).join(".");
|
|
781
|
+
}
|
|
782
|
+
function logicalKeyToEnvVar(key, config = {}) {
|
|
783
|
+
const normalized = normalizeMappingConfig(config);
|
|
784
|
+
const explicitEntry = Object.entries(normalized.explicit).find(([, logicalKey]) => logicalKey === key);
|
|
785
|
+
if (explicitEntry) {
|
|
786
|
+
return explicitEntry[0];
|
|
787
|
+
}
|
|
788
|
+
if (normalized.convention !== "SCREAMING_SNAKE") {
|
|
789
|
+
return void 0;
|
|
790
|
+
}
|
|
791
|
+
if (key.startsWith("value.")) {
|
|
792
|
+
return toScreamingSnake(key.slice("value.".length));
|
|
793
|
+
}
|
|
794
|
+
if (key.startsWith("secret.")) {
|
|
795
|
+
return `SECRET_${toScreamingSnake(key.slice("secret.".length))}`;
|
|
796
|
+
}
|
|
797
|
+
return void 0;
|
|
798
|
+
}
|
|
799
|
+
function envVarToLogicalKey(envVar, config = {}) {
|
|
800
|
+
const normalized = normalizeMappingConfig(config);
|
|
801
|
+
const explicitMatch = normalized.explicit[envVar];
|
|
802
|
+
if (explicitMatch) {
|
|
803
|
+
return explicitMatch;
|
|
804
|
+
}
|
|
805
|
+
if (normalized.convention !== "SCREAMING_SNAKE") {
|
|
806
|
+
return void 0;
|
|
807
|
+
}
|
|
808
|
+
if (envVar.startsWith("SECRET_")) {
|
|
809
|
+
const stripped = envVar.slice("SECRET_".length);
|
|
810
|
+
if (!stripped) {
|
|
811
|
+
return void 0;
|
|
812
|
+
}
|
|
813
|
+
return `secret.${fromScreamingSnake(stripped)}`;
|
|
814
|
+
}
|
|
815
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(envVar)) {
|
|
816
|
+
return void 0;
|
|
817
|
+
}
|
|
818
|
+
return `value.${fromScreamingSnake(envVar)}`;
|
|
463
819
|
}
|
|
464
820
|
|
|
465
821
|
// ../core/src/validation/envMapping.ts
|
|
@@ -475,7 +831,8 @@ function validateEnvMappingCollisions(manifest, graph) {
|
|
|
475
831
|
]);
|
|
476
832
|
const collisions = /* @__PURE__ */ new Map();
|
|
477
833
|
for (const key of candidates) {
|
|
478
|
-
|
|
834
|
+
const definition = getNamespaceDefinition(manifest, key);
|
|
835
|
+
if (definition.kind !== "data") {
|
|
479
836
|
continue;
|
|
480
837
|
}
|
|
481
838
|
const envVar = logicalKeyToEnvVar(key, manifest.envMapping) ?? (key.startsWith("value.") || key.startsWith("secret.") ? fallbackLogicalKeyToEnvVar(key) : void 0);
|
|
@@ -494,11 +851,7 @@ function validateEnvMappingCollisions(manifest, graph) {
|
|
|
494
851
|
|
|
495
852
|
// ../core/src/validation/publicSafety.ts
|
|
496
853
|
function validatePublicSafety(manifest) {
|
|
497
|
-
return manifest.public.promote.
|
|
498
|
-
code: "public.invalid-promotion",
|
|
499
|
-
key,
|
|
500
|
-
message: `public.promote may only include value.* keys: ${key}`
|
|
501
|
-
}));
|
|
854
|
+
return manifest.public.promote.map((key) => validateProjectionIssue(manifest, key, "public")).filter((issue) => Boolean(issue));
|
|
502
855
|
}
|
|
503
856
|
|
|
504
857
|
// ../core/src/validation/workspaceSafety.ts
|
|
@@ -564,39 +917,6 @@ async function validateRuntime(runtime) {
|
|
|
564
917
|
};
|
|
565
918
|
}
|
|
566
919
|
|
|
567
|
-
// ../core/src/runtime/inspect.ts
|
|
568
|
-
function inspectValue(graph, key) {
|
|
569
|
-
const entry = graph.entries.get(key);
|
|
570
|
-
if (!entry) {
|
|
571
|
-
throw new CnosKeyNotFoundError(key);
|
|
572
|
-
}
|
|
573
|
-
return {
|
|
574
|
-
key: entry.key,
|
|
575
|
-
value: entry.value,
|
|
576
|
-
namespace: entry.namespace,
|
|
577
|
-
profile: graph.profile,
|
|
578
|
-
profileSource: graph.profileSource,
|
|
579
|
-
workspace: {
|
|
580
|
-
id: graph.workspace.workspaceId,
|
|
581
|
-
source: graph.workspace.workspaceSource,
|
|
582
|
-
chain: graph.workspace.workspaceChain
|
|
583
|
-
},
|
|
584
|
-
winner: {
|
|
585
|
-
sourceId: entry.winner.sourceId,
|
|
586
|
-
pluginId: entry.winner.pluginId,
|
|
587
|
-
workspaceId: entry.winner.workspaceId,
|
|
588
|
-
...entry.winner.origin ? { origin: entry.winner.origin } : {}
|
|
589
|
-
},
|
|
590
|
-
overridden: entry.overridden.map((override) => ({
|
|
591
|
-
sourceId: override.sourceId,
|
|
592
|
-
pluginId: override.pluginId,
|
|
593
|
-
workspaceId: override.workspaceId,
|
|
594
|
-
value: override.value,
|
|
595
|
-
...override.origin ? { origin: override.origin } : {}
|
|
596
|
-
}))
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
|
|
600
920
|
// ../core/src/inspectors/provenance.ts
|
|
601
921
|
function createProvenanceInspector() {
|
|
602
922
|
return {
|
|
@@ -608,167 +928,6 @@ function createProvenanceInspector() {
|
|
|
608
928
|
};
|
|
609
929
|
}
|
|
610
930
|
|
|
611
|
-
// ../core/src/manifest/loadManifest.ts
|
|
612
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
613
|
-
import path4 from "path";
|
|
614
|
-
|
|
615
|
-
// ../core/src/manifest/normalizeManifest.ts
|
|
616
|
-
var DEFAULT_RESOLVE_FROM = ["cli.profile", "env.CNOS_PROFILE", "default"];
|
|
617
|
-
var DEFAULT_LOADERS = [
|
|
618
|
-
"filesystem-values",
|
|
619
|
-
"filesystem-secrets",
|
|
620
|
-
"dotenv",
|
|
621
|
-
"process-env",
|
|
622
|
-
"cli-args"
|
|
623
|
-
];
|
|
624
|
-
var DEFAULT_VALIDATORS = ["basic-schema"];
|
|
625
|
-
var DEFAULT_EXPORTERS = ["env", "public-env"];
|
|
626
|
-
var DEFAULT_INSPECTORS = ["provenance"];
|
|
627
|
-
var DEFAULT_FRAMEWORK_PREFIXES = {
|
|
628
|
-
next: "NEXT_PUBLIC_",
|
|
629
|
-
vite: "VITE_",
|
|
630
|
-
nuxt: "NUXT_PUBLIC_"
|
|
631
|
-
};
|
|
632
|
-
function validateResolveFrom(resolveFrom) {
|
|
633
|
-
const validValues = ["cli.profile", "env.CNOS_PROFILE", "default"];
|
|
634
|
-
for (const entry of resolveFrom) {
|
|
635
|
-
if (!validValues.includes(entry)) {
|
|
636
|
-
throw new CnosManifestError(`Unsupported profiles.resolveFrom entry: ${entry}`);
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
return resolveFrom;
|
|
640
|
-
}
|
|
641
|
-
function normalizeWorkspaceItems(items) {
|
|
642
|
-
return Object.fromEntries(
|
|
643
|
-
Object.entries(items ?? {}).map(([workspaceId, item]) => [
|
|
644
|
-
workspaceId,
|
|
645
|
-
{
|
|
646
|
-
extends: Array.isArray(item?.extends) ? item.extends.map((entry) => entry.trim()).filter(Boolean) : item?.extends ? [item.extends.trim()].filter(Boolean) : [],
|
|
647
|
-
...item?.globalId?.trim() ? { globalId: item.globalId.trim() } : {}
|
|
648
|
-
}
|
|
649
|
-
])
|
|
650
|
-
);
|
|
651
|
-
}
|
|
652
|
-
function normalizeManifest(manifest) {
|
|
653
|
-
const version = manifest.version ?? 1;
|
|
654
|
-
if (version !== 1) {
|
|
655
|
-
throw new CnosManifestError(`Unsupported CNOS manifest version: ${version}`);
|
|
656
|
-
}
|
|
657
|
-
const projectName = manifest.project?.name?.trim();
|
|
658
|
-
if (!projectName) {
|
|
659
|
-
throw new CnosManifestError("Manifest requires project.name");
|
|
660
|
-
}
|
|
661
|
-
const defaultProfile = manifest.profiles?.default?.trim() || "base";
|
|
662
|
-
const workspaceItems = normalizeWorkspaceItems(manifest.workspaces?.items);
|
|
663
|
-
const resolveFrom = validateResolveFrom(manifest.profiles?.resolveFrom ?? DEFAULT_RESOLVE_FROM);
|
|
664
|
-
const filesystemValues = {
|
|
665
|
-
root: "./",
|
|
666
|
-
format: "yaml",
|
|
667
|
-
...manifest.sources?.["filesystem-values"] ?? {}
|
|
668
|
-
};
|
|
669
|
-
const filesystemSecrets = {
|
|
670
|
-
root: "./",
|
|
671
|
-
format: "yaml",
|
|
672
|
-
...manifest.sources?.["filesystem-secrets"] ?? {}
|
|
673
|
-
};
|
|
674
|
-
const dotenv = {
|
|
675
|
-
root: "./env",
|
|
676
|
-
...manifest.sources?.dotenv ?? {}
|
|
677
|
-
};
|
|
678
|
-
return {
|
|
679
|
-
version: 1,
|
|
680
|
-
project: {
|
|
681
|
-
name: projectName
|
|
682
|
-
},
|
|
683
|
-
workspaces: {
|
|
684
|
-
...manifest.workspaces?.default?.trim() ? {
|
|
685
|
-
default: manifest.workspaces.default.trim()
|
|
686
|
-
} : {},
|
|
687
|
-
global: {
|
|
688
|
-
enabled: manifest.workspaces?.global?.enabled ?? false,
|
|
689
|
-
...manifest.workspaces?.global?.root?.trim() ? {
|
|
690
|
-
root: manifest.workspaces.global.root.trim()
|
|
691
|
-
} : {},
|
|
692
|
-
allowWrite: manifest.workspaces?.global?.allowWrite ?? false
|
|
693
|
-
},
|
|
694
|
-
items: workspaceItems
|
|
695
|
-
},
|
|
696
|
-
profiles: {
|
|
697
|
-
default: defaultProfile,
|
|
698
|
-
resolveFrom
|
|
699
|
-
},
|
|
700
|
-
plugins: {
|
|
701
|
-
loaders: manifest.plugins?.loaders ?? DEFAULT_LOADERS,
|
|
702
|
-
resolver: manifest.plugins?.resolver ?? "profile-aware",
|
|
703
|
-
validators: manifest.plugins?.validators ?? DEFAULT_VALIDATORS,
|
|
704
|
-
exporters: manifest.plugins?.exporters ?? DEFAULT_EXPORTERS,
|
|
705
|
-
inspectors: manifest.plugins?.inspectors ?? DEFAULT_INSPECTORS
|
|
706
|
-
},
|
|
707
|
-
sources: {
|
|
708
|
-
...manifest.sources ?? {},
|
|
709
|
-
"filesystem-values": filesystemValues,
|
|
710
|
-
"filesystem-secrets": filesystemSecrets,
|
|
711
|
-
dotenv
|
|
712
|
-
},
|
|
713
|
-
resolution: {
|
|
714
|
-
precedence: manifest.resolution?.precedence ?? [
|
|
715
|
-
"filesystem-values",
|
|
716
|
-
"filesystem-secrets",
|
|
717
|
-
"dotenv",
|
|
718
|
-
"process-env",
|
|
719
|
-
"cli-args"
|
|
720
|
-
],
|
|
721
|
-
arrayPolicy: manifest.resolution?.arrayPolicy ?? "replace"
|
|
722
|
-
},
|
|
723
|
-
envMapping: {
|
|
724
|
-
...manifest.envMapping?.convention ? {
|
|
725
|
-
convention: manifest.envMapping.convention
|
|
726
|
-
} : {},
|
|
727
|
-
explicit: manifest.envMapping?.explicit ?? {}
|
|
728
|
-
},
|
|
729
|
-
public: {
|
|
730
|
-
promote: manifest.public?.promote ?? [],
|
|
731
|
-
frameworks: {
|
|
732
|
-
...DEFAULT_FRAMEWORK_PREFIXES,
|
|
733
|
-
...manifest.public?.frameworks ?? {}
|
|
734
|
-
}
|
|
735
|
-
},
|
|
736
|
-
writePolicy: {
|
|
737
|
-
define: {
|
|
738
|
-
defaultProfile: manifest.writePolicy?.define?.defaultProfile ?? defaultProfile,
|
|
739
|
-
targets: {
|
|
740
|
-
value: manifest.writePolicy?.define?.targets?.value ?? "./values/app.yml",
|
|
741
|
-
secret: manifest.writePolicy?.define?.targets?.secret ?? "./secrets/app.yml"
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
},
|
|
745
|
-
schema: manifest.schema ?? {}
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
// ../core/src/manifest/loadManifest.ts
|
|
750
|
-
async function loadManifest(options = {}) {
|
|
751
|
-
const manifestRoot = await resolveManifestRoot(options.root);
|
|
752
|
-
const manifestPath = path4.join(manifestRoot, "cnos.yml");
|
|
753
|
-
let source;
|
|
754
|
-
try {
|
|
755
|
-
source = await readFile2(manifestPath, "utf8");
|
|
756
|
-
} catch {
|
|
757
|
-
throw new CnosManifestError("Unable to read CNOS manifest", manifestPath);
|
|
758
|
-
}
|
|
759
|
-
const rawManifest = parseYaml(source);
|
|
760
|
-
if (!rawManifest || typeof rawManifest !== "object") {
|
|
761
|
-
throw new CnosManifestError("CNOS manifest must be a YAML object", manifestPath);
|
|
762
|
-
}
|
|
763
|
-
return {
|
|
764
|
-
manifestRoot,
|
|
765
|
-
repoRoot: path4.dirname(manifestRoot),
|
|
766
|
-
manifestPath,
|
|
767
|
-
manifest: normalizeManifest(rawManifest),
|
|
768
|
-
rawManifest
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
|
|
772
931
|
// ../core/src/manifest/loadWorkspaceFile.ts
|
|
773
932
|
import { readFile as readFile3 } from "fs/promises";
|
|
774
933
|
import path5 from "path";
|
|
@@ -936,6 +1095,50 @@ async function expandProfileChain(activeProfile, options = {}) {
|
|
|
936
1095
|
};
|
|
937
1096
|
}
|
|
938
1097
|
|
|
1098
|
+
// ../core/src/promotions/promoteToPublic.ts
|
|
1099
|
+
function toPublicKey(key) {
|
|
1100
|
+
const namespace = getNamespaceNameForKey(key);
|
|
1101
|
+
return namespace === "value" ? `public.${stripNamespace(key)}` : `public.${key}`;
|
|
1102
|
+
}
|
|
1103
|
+
function toPromotedConfigEntry(entry, key, promotedFrom) {
|
|
1104
|
+
return {
|
|
1105
|
+
...entry,
|
|
1106
|
+
key,
|
|
1107
|
+
namespace: "public",
|
|
1108
|
+
sourceId: "public-promote",
|
|
1109
|
+
pluginId: "core",
|
|
1110
|
+
metadata: {
|
|
1111
|
+
...entry.metadata ?? {},
|
|
1112
|
+
promotedFrom
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
function toPromotedResolvedEntry(entry) {
|
|
1117
|
+
const key = toPublicKey(entry.key);
|
|
1118
|
+
return {
|
|
1119
|
+
key,
|
|
1120
|
+
value: entry.value,
|
|
1121
|
+
namespace: "public",
|
|
1122
|
+
winner: toPromotedConfigEntry(entry.winner, key, entry.key),
|
|
1123
|
+
overridden: entry.overridden.map((override) => toPromotedConfigEntry(override, key, entry.key))
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
function promoteToPublic(graph, manifest) {
|
|
1127
|
+
const entries = new Map(graph.entries);
|
|
1128
|
+
for (const key of manifest.public.promote) {
|
|
1129
|
+
ensureProjectionAllowed(manifest, key, "public");
|
|
1130
|
+
const resolved = graph.entries.get(key);
|
|
1131
|
+
if (!resolved) {
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
entries.set(toPublicKey(key), toPromotedResolvedEntry(resolved));
|
|
1135
|
+
}
|
|
1136
|
+
return {
|
|
1137
|
+
...graph,
|
|
1138
|
+
entries
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
|
|
939
1142
|
// ../core/src/profiles/resolveActiveProfile.ts
|
|
940
1143
|
function resolveActiveProfile(manifest, options = {}) {
|
|
941
1144
|
for (const source of manifest.profiles.resolveFrom) {
|
|
@@ -1370,26 +1573,6 @@ async function runPipeline(options) {
|
|
|
1370
1573
|
return collectedEntries.flat();
|
|
1371
1574
|
}
|
|
1372
1575
|
|
|
1373
|
-
// ../core/src/runtime/read.ts
|
|
1374
|
-
function readValue(graph, key) {
|
|
1375
|
-
return graph.entries.get(key)?.value;
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
// ../core/src/runtime/readOr.ts
|
|
1379
|
-
function readOrValue(graph, key, fallback) {
|
|
1380
|
-
const value = readValue(graph, key);
|
|
1381
|
-
return value === void 0 ? fallback : value;
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
// ../core/src/runtime/require.ts
|
|
1385
|
-
function requireValue(graph, key) {
|
|
1386
|
-
const value = readValue(graph, key);
|
|
1387
|
-
if (value === void 0) {
|
|
1388
|
-
throw new CnosKeyNotFoundError(key);
|
|
1389
|
-
}
|
|
1390
|
-
return value;
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
1576
|
// ../core/src/orchestrator/runtime.ts
|
|
1394
1577
|
function createRuntime(manifest, graph, plugins = []) {
|
|
1395
1578
|
return {
|
|
@@ -1527,6 +1710,9 @@ function appendMetaEntries(graph, cnosVersion) {
|
|
|
1527
1710
|
}
|
|
1528
1711
|
async function createCnos(options = {}) {
|
|
1529
1712
|
const loadedManifest = await loadManifest(options.root ? { root: options.root } : {});
|
|
1713
|
+
for (const key of loadedManifest.manifest.public.promote) {
|
|
1714
|
+
ensureProjectionAllowed(loadedManifest.manifest, key, "public");
|
|
1715
|
+
}
|
|
1530
1716
|
const workspaceFile = await loadWorkspaceFile(loadedManifest.repoRoot);
|
|
1531
1717
|
const workspace = await resolveWorkspaceContext(loadedManifest.manifest, {
|
|
1532
1718
|
manifestRoot: loadedManifest.manifestRoot,
|
|
@@ -1566,10 +1752,11 @@ async function createCnos(options = {}) {
|
|
|
1566
1752
|
workspace
|
|
1567
1753
|
});
|
|
1568
1754
|
const schemaApplied = applySchemaRules(graph, loadedManifest.manifest.schema);
|
|
1755
|
+
const promotedGraph = promoteToPublic(schemaApplied.graph, loadedManifest.manifest);
|
|
1569
1756
|
return createRuntime(
|
|
1570
1757
|
loadedManifest.manifest,
|
|
1571
1758
|
appendMetaEntries({
|
|
1572
|
-
...
|
|
1759
|
+
...promotedGraph,
|
|
1573
1760
|
profileSource: activeProfile.source
|
|
1574
1761
|
}, options.cnosVersion),
|
|
1575
1762
|
plugins
|
|
@@ -1578,28 +1765,42 @@ async function createCnos(options = {}) {
|
|
|
1578
1765
|
|
|
1579
1766
|
export {
|
|
1580
1767
|
CnosManifestError,
|
|
1768
|
+
CnosSecurityError,
|
|
1769
|
+
inspectValue,
|
|
1581
1770
|
createProvenanceInspector,
|
|
1771
|
+
resolveManifestRoot,
|
|
1582
1772
|
resolveWorkspaceScopedPath,
|
|
1583
1773
|
resolveConfigDocumentPath,
|
|
1584
1774
|
toPortablePath,
|
|
1585
1775
|
joinConfigPath,
|
|
1776
|
+
toLogicalKey,
|
|
1586
1777
|
parseYaml,
|
|
1587
1778
|
stringifyYaml,
|
|
1779
|
+
loadManifest,
|
|
1780
|
+
ensureProjectionAllowed,
|
|
1588
1781
|
applySchemaRules,
|
|
1782
|
+
toNamespaceObject,
|
|
1783
|
+
readValue,
|
|
1784
|
+
readOrValue,
|
|
1785
|
+
requireValue,
|
|
1589
1786
|
isSecretReference,
|
|
1590
1787
|
resolveSecretStoreRoot,
|
|
1591
1788
|
resolveSecretVaultFile,
|
|
1592
1789
|
resolveSecretPassphrase,
|
|
1790
|
+
getVaultPassphraseEnvVar,
|
|
1791
|
+
isPassphraseEnvRef,
|
|
1792
|
+
resolveConfiguredVaultPassphrase,
|
|
1793
|
+
resolveVaultDefinition,
|
|
1593
1794
|
createSecretVault,
|
|
1594
1795
|
listSecretVaults,
|
|
1595
1796
|
writeLocalSecret,
|
|
1596
1797
|
readLocalSecret,
|
|
1597
1798
|
toEnv,
|
|
1598
|
-
envVarToLogicalKey,
|
|
1599
1799
|
toPublicEnv,
|
|
1600
1800
|
createCnos,
|
|
1601
1801
|
planDump,
|
|
1602
1802
|
writeDump,
|
|
1803
|
+
envVarToLogicalKey,
|
|
1603
1804
|
flattenObject,
|
|
1604
1805
|
validateRuntime
|
|
1605
1806
|
};
|