@kitsy/cnos 1.1.1 → 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-JPJ3S3CO.js → chunk-APCTXRUN.js} +620 -426
- package/dist/{chunk-PBU5NAX4.js → chunk-EIN55XXA.js} +1 -1
- package/dist/chunk-JUHPBAEH.js +20 -0
- package/dist/{chunk-L3HOQHCH.js → chunk-MLQGYCO7.js} +1 -1
- package/dist/chunk-PQ4KSV76.js +50 -0
- package/dist/{chunk-7GNXYEO6.js → chunk-RD5WMHPM.js} +1 -1
- package/dist/chunk-SO5XREEU.js +179 -0
- package/dist/{chunk-QKJ6QLRS.js → chunk-SXTMTACL.js} +2 -2
- package/dist/{chunk-X4GOXEKX.js → chunk-WHUGFPE4.js} +1 -1
- package/dist/{chunk-M4S6PYM5.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 +294 -133
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +14 -132
- package/dist/internal.cjs +479 -61
- 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.cjs +3 -3
- 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.cjs +3 -3
- 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 +9 -9
- 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 +46 -64
- 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 +10 -10
- 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 +9 -9
- 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,158 +156,282 @@ function stringifyYaml(value) {
|
|
|
120
156
|
return stringify(value);
|
|
121
157
|
}
|
|
122
158
|
|
|
123
|
-
// ../core/src/
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
|
164
206
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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}`);
|
|
169
213
|
}
|
|
170
|
-
return `secret.${fromScreamingSnake(stripped)}`;
|
|
171
214
|
}
|
|
172
|
-
|
|
173
|
-
return void 0;
|
|
174
|
-
}
|
|
175
|
-
return `value.${fromScreamingSnake(envVar)}`;
|
|
215
|
+
return resolveFrom;
|
|
176
216
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
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
|
+
);
|
|
188
227
|
}
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
+
};
|
|
200
245
|
}
|
|
201
|
-
function
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
+
})
|
|
206
263
|
);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
const envVar = logicalKeyToEnvVar(entry.key, manifest.envMapping) ?? fallbackLogicalKeyToEnvVar(entry.key);
|
|
215
|
-
output[envVar] = normalizeEnvValue(entry.value);
|
|
264
|
+
}
|
|
265
|
+
function normalizeManifest(manifest) {
|
|
266
|
+
const version = manifest.version ?? 1;
|
|
267
|
+
if (version !== 1) {
|
|
268
|
+
throw new CnosManifestError(`Unsupported CNOS manifest version: ${version}`);
|
|
216
269
|
}
|
|
217
|
-
|
|
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
|
+
};
|
|
218
362
|
}
|
|
219
363
|
|
|
220
|
-
// ../core/src/
|
|
221
|
-
function
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return value;
|
|
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);
|
|
230
373
|
}
|
|
231
|
-
|
|
232
|
-
|
|
374
|
+
const rawManifest = parseYaml(source);
|
|
375
|
+
if (!rawManifest || typeof rawManifest !== "object") {
|
|
376
|
+
throw new CnosManifestError("CNOS manifest must be a YAML object", manifestPath);
|
|
233
377
|
}
|
|
234
|
-
return
|
|
378
|
+
return {
|
|
379
|
+
manifestRoot,
|
|
380
|
+
repoRoot: path2.dirname(manifestRoot),
|
|
381
|
+
manifestPath,
|
|
382
|
+
manifest: normalizeManifest(rawManifest),
|
|
383
|
+
rawManifest
|
|
384
|
+
};
|
|
235
385
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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}`);
|
|
239
396
|
}
|
|
240
|
-
|
|
241
|
-
|
|
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
|
+
);
|
|
242
410
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
411
|
+
if (definition.sensitive) {
|
|
412
|
+
throw new CnosSecurityError(
|
|
413
|
+
`Cannot promote ${key} to ${target} because namespace "${namespace}" is sensitive.`
|
|
414
|
+
);
|
|
246
415
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
throw new CnosManifestError(`public.promote may only contain value.* keys: ${key}`);
|
|
416
|
+
if (!definition.shareable) {
|
|
417
|
+
throw new CnosSecurityError(
|
|
418
|
+
`Cannot promote ${key} to ${target} because namespace "${namespace}" is not shareable.`
|
|
419
|
+
);
|
|
252
420
|
}
|
|
253
421
|
}
|
|
254
|
-
function
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
const baseEnvVar = logicalKeyToEnvVar(key, manifest.envMapping) ?? fallbackValueEnvVar(key);
|
|
265
|
-
const envVar = prefix && !baseEnvVar.startsWith(prefix) ? `${prefix}${baseEnvVar}` : baseEnvVar;
|
|
266
|
-
output[envVar] = normalizeEnvValue2(resolved.value);
|
|
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
|
+
};
|
|
267
432
|
}
|
|
268
|
-
return output;
|
|
269
433
|
}
|
|
270
434
|
|
|
271
|
-
// ../core/src/runtime/dump.ts
|
|
272
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
273
|
-
import path2 from "path";
|
|
274
|
-
|
|
275
435
|
// ../core/src/runtime/projection.ts
|
|
276
436
|
function setNestedValue(target, pathSegments, value) {
|
|
277
437
|
const [head, ...tail] = pathSegments;
|
|
@@ -302,66 +462,29 @@ function toNamespaceObject(graph, namespace) {
|
|
|
302
462
|
return output;
|
|
303
463
|
}
|
|
304
464
|
|
|
305
|
-
// ../core/src/runtime/
|
|
306
|
-
function
|
|
307
|
-
|
|
308
|
-
const values = toNamespaceObject(graph, "value");
|
|
309
|
-
const secrets = toNamespaceObject(graph, "secret");
|
|
310
|
-
const files = [];
|
|
311
|
-
if (Object.keys(values).length > 0) {
|
|
312
|
-
files.push({
|
|
313
|
-
path: path2.posix.join(basePath, "values", graph.profile, "app.yml"),
|
|
314
|
-
namespace: "value",
|
|
315
|
-
content: stringifyYaml(values)
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
if (Object.keys(secrets).length > 0) {
|
|
319
|
-
files.push({
|
|
320
|
-
path: path2.posix.join(basePath, "secrets", graph.profile, "app.yml"),
|
|
321
|
-
namespace: "secret",
|
|
322
|
-
content: stringifyYaml(secrets)
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
return files.sort((left, right) => left.path.localeCompare(right.path));
|
|
326
|
-
}
|
|
327
|
-
function planDump(graph, options = {}) {
|
|
328
|
-
return {
|
|
329
|
-
workspaceId: graph.workspace.workspaceId,
|
|
330
|
-
profile: graph.profile,
|
|
331
|
-
flatten: options.flatten ?? false,
|
|
332
|
-
files: buildDumpFiles(graph, options)
|
|
333
|
-
};
|
|
465
|
+
// ../core/src/runtime/read.ts
|
|
466
|
+
function readValue(graph, key) {
|
|
467
|
+
return graph.entries.get(key)?.value;
|
|
334
468
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
await mkdir(path2.dirname(destination), { recursive: true });
|
|
341
|
-
await writeFile(destination, file.content, "utf8");
|
|
342
|
-
}
|
|
343
|
-
return {
|
|
344
|
-
...plan,
|
|
345
|
-
root
|
|
346
|
-
};
|
|
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;
|
|
347
474
|
}
|
|
348
475
|
|
|
349
|
-
// ../core/src/
|
|
350
|
-
function
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
accumulator[nextKey] = nestedValue;
|
|
358
|
-
return accumulator;
|
|
359
|
-
}, {});
|
|
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;
|
|
360
483
|
}
|
|
361
484
|
|
|
362
485
|
// ../core/src/utils/secretStore.ts
|
|
363
486
|
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
|
|
364
|
-
import { mkdir
|
|
487
|
+
import { mkdir, readdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
365
488
|
import path3 from "path";
|
|
366
489
|
function isObject(value) {
|
|
367
490
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -385,6 +508,38 @@ function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
|
|
|
385
508
|
const vaultToken = vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
386
509
|
return processEnv[`CNOS_SECRET_PASSPHRASE_${vaultToken}`] ?? processEnv.CNOS_SECRET_PASSPHRASE;
|
|
387
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
|
+
}
|
|
388
543
|
function encryptDocument(value, passphrase) {
|
|
389
544
|
const salt = randomBytes(16);
|
|
390
545
|
const iv = randomBytes(12);
|
|
@@ -415,21 +570,21 @@ function decryptDocument(document, passphrase) {
|
|
|
415
570
|
async function createSecretVault(storeRoot, vault, passphrase) {
|
|
416
571
|
const normalizedVault = vault.trim() || "default";
|
|
417
572
|
const filePath = resolveSecretVaultFile(storeRoot, normalizedVault);
|
|
418
|
-
await
|
|
573
|
+
await mkdir(path3.dirname(filePath), { recursive: true });
|
|
419
574
|
const document = {
|
|
420
575
|
version: 1,
|
|
421
576
|
name: normalizedVault,
|
|
422
577
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
423
578
|
verifier: encryptDocument(`cnos-vault:${normalizedVault}`, passphrase)
|
|
424
579
|
};
|
|
425
|
-
await
|
|
580
|
+
await writeFile(filePath, JSON.stringify(document, null, 2), "utf8");
|
|
426
581
|
return filePath;
|
|
427
582
|
}
|
|
428
583
|
async function ensureSecretVault(storeRoot, vault, passphrase) {
|
|
429
584
|
const normalizedVault = vault.trim() || "default";
|
|
430
585
|
const filePath = resolveSecretVaultFile(storeRoot, normalizedVault);
|
|
431
586
|
try {
|
|
432
|
-
await
|
|
587
|
+
await readFile2(filePath, "utf8");
|
|
433
588
|
return filePath;
|
|
434
589
|
} catch (error) {
|
|
435
590
|
if (error.code !== "ENOENT") {
|
|
@@ -450,8 +605,8 @@ async function listSecretVaults(storeRoot) {
|
|
|
450
605
|
async function writeLocalSecret(storeRoot, ref, value, passphrase, vault = "default") {
|
|
451
606
|
await ensureSecretVault(storeRoot, vault, passphrase);
|
|
452
607
|
const filePath = resolveSecretStoreFile(storeRoot, ref, vault);
|
|
453
|
-
await
|
|
454
|
-
await
|
|
608
|
+
await mkdir(path3.dirname(filePath), { recursive: true });
|
|
609
|
+
await writeFile(filePath, JSON.stringify(encryptDocument(value, passphrase), null, 2), "utf8");
|
|
455
610
|
return filePath;
|
|
456
611
|
}
|
|
457
612
|
async function readLocalSecret(storeRoot, ref, passphrase, vault = "default") {
|
|
@@ -461,16 +616,210 @@ async function readLocalSecret(storeRoot, ref, passphrase, vault = "default") {
|
|
|
461
616
|
);
|
|
462
617
|
}
|
|
463
618
|
const filePath = resolveSecretStoreFile(storeRoot, ref, vault);
|
|
464
|
-
const source = await
|
|
619
|
+
const source = await readFile2(filePath, "utf8");
|
|
465
620
|
const document = JSON.parse(source);
|
|
466
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") {
|
|
467
622
|
throw new CnosManifestError("Invalid local secret document", filePath);
|
|
468
623
|
}
|
|
469
|
-
return decryptDocument(document, passphrase);
|
|
624
|
+
return decryptDocument(document, passphrase);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// ../core/src/runtime/toEnv.ts
|
|
628
|
+
function normalizeEnvValue(value) {
|
|
629
|
+
if (value === void 0 || value === null) {
|
|
630
|
+
return "";
|
|
631
|
+
}
|
|
632
|
+
if (typeof value === "string") {
|
|
633
|
+
return value;
|
|
634
|
+
}
|
|
635
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
636
|
+
return String(value);
|
|
637
|
+
}
|
|
638
|
+
return JSON.stringify(value);
|
|
639
|
+
}
|
|
640
|
+
function toEnv(graph, manifest, options = {}) {
|
|
641
|
+
const includeSecrets = options.includeSecrets ?? true;
|
|
642
|
+
const output = {};
|
|
643
|
+
const mappedEntries = Object.entries(manifest.envMapping.explicit).sort(
|
|
644
|
+
([left], [right]) => left.localeCompare(right)
|
|
645
|
+
);
|
|
646
|
+
for (const [envVar, logicalKey] of mappedEntries) {
|
|
647
|
+
const entry = graph.entries.get(logicalKey);
|
|
648
|
+
if (!entry) {
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
const namespaceDefinition = getNamespaceDefinition(manifest, entry.namespace);
|
|
652
|
+
if (namespaceDefinition.kind !== "data" || !namespaceDefinition.shareable || namespaceDefinition.sensitive) {
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
if (entry.namespace === "secret" && !includeSecrets) {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
if (isSecretReference(entry.value)) {
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
output[envVar] = normalizeEnvValue(entry.value);
|
|
662
|
+
}
|
|
663
|
+
return output;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// ../core/src/runtime/toPublicEnv.ts
|
|
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();
|
|
669
|
+
}
|
|
670
|
+
function normalizeEnvValue2(value) {
|
|
671
|
+
if (value === void 0 || value === null) {
|
|
672
|
+
return "";
|
|
673
|
+
}
|
|
674
|
+
if (typeof value === "string") {
|
|
675
|
+
return value;
|
|
676
|
+
}
|
|
677
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
678
|
+
return String(value);
|
|
679
|
+
}
|
|
680
|
+
return JSON.stringify(value);
|
|
681
|
+
}
|
|
682
|
+
function resolvePublicPrefix(manifest, options) {
|
|
683
|
+
if (options.prefix) {
|
|
684
|
+
return options.prefix;
|
|
685
|
+
}
|
|
686
|
+
if (!options.framework) {
|
|
687
|
+
return "";
|
|
688
|
+
}
|
|
689
|
+
const configuredPrefix = manifest.public.frameworks[options.framework];
|
|
690
|
+
if (!configuredPrefix) {
|
|
691
|
+
throw new CnosManifestError(`Unknown public framework prefix: ${options.framework}`);
|
|
692
|
+
}
|
|
693
|
+
return configuredPrefix;
|
|
694
|
+
}
|
|
695
|
+
function toPublicEnv(graph, manifest, options = {}) {
|
|
696
|
+
const prefix = resolvePublicPrefix(manifest, options);
|
|
697
|
+
const output = {};
|
|
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));
|
|
701
|
+
const envVar = prefix && !baseEnvVar.startsWith(prefix) ? `${prefix}${baseEnvVar}` : baseEnvVar;
|
|
702
|
+
output[envVar] = normalizeEnvValue2(resolved.value);
|
|
703
|
+
}
|
|
704
|
+
return output;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// ../core/src/runtime/dump.ts
|
|
708
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
709
|
+
import path4 from "path";
|
|
710
|
+
function buildDumpFiles(graph, options = {}) {
|
|
711
|
+
const basePath = options.flatten ? "" : path4.posix.join("workspaces", graph.workspace.workspaceId);
|
|
712
|
+
const values = toNamespaceObject(graph, "value");
|
|
713
|
+
const secrets = toNamespaceObject(graph, "secret");
|
|
714
|
+
const files = [];
|
|
715
|
+
if (Object.keys(values).length > 0) {
|
|
716
|
+
files.push({
|
|
717
|
+
path: path4.posix.join(basePath, "values", graph.profile, "app.yml"),
|
|
718
|
+
namespace: "value",
|
|
719
|
+
content: stringifyYaml(values)
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
if (Object.keys(secrets).length > 0) {
|
|
723
|
+
files.push({
|
|
724
|
+
path: path4.posix.join(basePath, "secrets", graph.profile, "app.yml"),
|
|
725
|
+
namespace: "secret",
|
|
726
|
+
content: stringifyYaml(secrets)
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
return files.sort((left, right) => left.path.localeCompare(right.path));
|
|
730
|
+
}
|
|
731
|
+
function planDump(graph, options = {}) {
|
|
732
|
+
return {
|
|
733
|
+
workspaceId: graph.workspace.workspaceId,
|
|
734
|
+
profile: graph.profile,
|
|
735
|
+
flatten: options.flatten ?? false,
|
|
736
|
+
files: buildDumpFiles(graph, options)
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
async function writeDump(graph, options) {
|
|
740
|
+
const root = path4.resolve(options.to);
|
|
741
|
+
const plan = planDump(graph, options);
|
|
742
|
+
for (const file of plan.files) {
|
|
743
|
+
const destination = path4.join(root, file.path);
|
|
744
|
+
await mkdir2(path4.dirname(destination), { recursive: true });
|
|
745
|
+
await writeFile2(destination, file.content, "utf8");
|
|
746
|
+
}
|
|
747
|
+
return {
|
|
748
|
+
...plan,
|
|
749
|
+
root
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// ../core/src/utils/flatten.ts
|
|
754
|
+
function flattenObject(value, prefix = "") {
|
|
755
|
+
return Object.entries(value).reduce((accumulator, [key, nestedValue]) => {
|
|
756
|
+
const nextKey = prefix ? `${prefix}.${key}` : key;
|
|
757
|
+
if (nestedValue && typeof nestedValue === "object" && !Array.isArray(nestedValue)) {
|
|
758
|
+
Object.assign(accumulator, flattenObject(nestedValue, nextKey));
|
|
759
|
+
return accumulator;
|
|
760
|
+
}
|
|
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)}`;
|
|
470
819
|
}
|
|
471
820
|
|
|
472
821
|
// ../core/src/validation/envMapping.ts
|
|
473
|
-
function
|
|
822
|
+
function fallbackLogicalKeyToEnvVar(key) {
|
|
474
823
|
return key.replace(/^(value|secret)\./, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
475
824
|
}
|
|
476
825
|
function validateEnvMappingCollisions(manifest, graph) {
|
|
@@ -482,10 +831,11 @@ function validateEnvMappingCollisions(manifest, graph) {
|
|
|
482
831
|
]);
|
|
483
832
|
const collisions = /* @__PURE__ */ new Map();
|
|
484
833
|
for (const key of candidates) {
|
|
485
|
-
|
|
834
|
+
const definition = getNamespaceDefinition(manifest, key);
|
|
835
|
+
if (definition.kind !== "data") {
|
|
486
836
|
continue;
|
|
487
837
|
}
|
|
488
|
-
const envVar = logicalKeyToEnvVar(key, manifest.envMapping) ?? (key.startsWith("value.") || key.startsWith("secret.") ?
|
|
838
|
+
const envVar = logicalKeyToEnvVar(key, manifest.envMapping) ?? (key.startsWith("value.") || key.startsWith("secret.") ? fallbackLogicalKeyToEnvVar(key) : void 0);
|
|
489
839
|
if (!envVar) {
|
|
490
840
|
continue;
|
|
491
841
|
}
|
|
@@ -501,11 +851,7 @@ function validateEnvMappingCollisions(manifest, graph) {
|
|
|
501
851
|
|
|
502
852
|
// ../core/src/validation/publicSafety.ts
|
|
503
853
|
function validatePublicSafety(manifest) {
|
|
504
|
-
return manifest.public.promote.
|
|
505
|
-
code: "public.invalid-promotion",
|
|
506
|
-
key,
|
|
507
|
-
message: `public.promote may only include value.* keys: ${key}`
|
|
508
|
-
}));
|
|
854
|
+
return manifest.public.promote.map((key) => validateProjectionIssue(manifest, key, "public")).filter((issue) => Boolean(issue));
|
|
509
855
|
}
|
|
510
856
|
|
|
511
857
|
// ../core/src/validation/workspaceSafety.ts
|
|
@@ -571,39 +917,6 @@ async function validateRuntime(runtime) {
|
|
|
571
917
|
};
|
|
572
918
|
}
|
|
573
919
|
|
|
574
|
-
// ../core/src/runtime/inspect.ts
|
|
575
|
-
function inspectValue(graph, key) {
|
|
576
|
-
const entry = graph.entries.get(key);
|
|
577
|
-
if (!entry) {
|
|
578
|
-
throw new CnosKeyNotFoundError(key);
|
|
579
|
-
}
|
|
580
|
-
return {
|
|
581
|
-
key: entry.key,
|
|
582
|
-
value: entry.value,
|
|
583
|
-
namespace: entry.namespace,
|
|
584
|
-
profile: graph.profile,
|
|
585
|
-
profileSource: graph.profileSource,
|
|
586
|
-
workspace: {
|
|
587
|
-
id: graph.workspace.workspaceId,
|
|
588
|
-
source: graph.workspace.workspaceSource,
|
|
589
|
-
chain: graph.workspace.workspaceChain
|
|
590
|
-
},
|
|
591
|
-
winner: {
|
|
592
|
-
sourceId: entry.winner.sourceId,
|
|
593
|
-
pluginId: entry.winner.pluginId,
|
|
594
|
-
workspaceId: entry.winner.workspaceId,
|
|
595
|
-
...entry.winner.origin ? { origin: entry.winner.origin } : {}
|
|
596
|
-
},
|
|
597
|
-
overridden: entry.overridden.map((override) => ({
|
|
598
|
-
sourceId: override.sourceId,
|
|
599
|
-
pluginId: override.pluginId,
|
|
600
|
-
workspaceId: override.workspaceId,
|
|
601
|
-
value: override.value,
|
|
602
|
-
...override.origin ? { origin: override.origin } : {}
|
|
603
|
-
}))
|
|
604
|
-
};
|
|
605
|
-
}
|
|
606
|
-
|
|
607
920
|
// ../core/src/inspectors/provenance.ts
|
|
608
921
|
function createProvenanceInspector() {
|
|
609
922
|
return {
|
|
@@ -615,167 +928,6 @@ function createProvenanceInspector() {
|
|
|
615
928
|
};
|
|
616
929
|
}
|
|
617
930
|
|
|
618
|
-
// ../core/src/manifest/loadManifest.ts
|
|
619
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
620
|
-
import path4 from "path";
|
|
621
|
-
|
|
622
|
-
// ../core/src/manifest/normalizeManifest.ts
|
|
623
|
-
var DEFAULT_RESOLVE_FROM = ["cli.profile", "env.CNOS_PROFILE", "default"];
|
|
624
|
-
var DEFAULT_LOADERS = [
|
|
625
|
-
"filesystem-values",
|
|
626
|
-
"filesystem-secrets",
|
|
627
|
-
"dotenv",
|
|
628
|
-
"process-env",
|
|
629
|
-
"cli-args"
|
|
630
|
-
];
|
|
631
|
-
var DEFAULT_VALIDATORS = ["basic-schema"];
|
|
632
|
-
var DEFAULT_EXPORTERS = ["env", "public-env"];
|
|
633
|
-
var DEFAULT_INSPECTORS = ["provenance"];
|
|
634
|
-
var DEFAULT_FRAMEWORK_PREFIXES = {
|
|
635
|
-
next: "NEXT_PUBLIC_",
|
|
636
|
-
vite: "VITE_",
|
|
637
|
-
nuxt: "NUXT_PUBLIC_"
|
|
638
|
-
};
|
|
639
|
-
function validateResolveFrom(resolveFrom) {
|
|
640
|
-
const validValues = ["cli.profile", "env.CNOS_PROFILE", "default"];
|
|
641
|
-
for (const entry of resolveFrom) {
|
|
642
|
-
if (!validValues.includes(entry)) {
|
|
643
|
-
throw new CnosManifestError(`Unsupported profiles.resolveFrom entry: ${entry}`);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
return resolveFrom;
|
|
647
|
-
}
|
|
648
|
-
function normalizeWorkspaceItems(items) {
|
|
649
|
-
return Object.fromEntries(
|
|
650
|
-
Object.entries(items ?? {}).map(([workspaceId, item]) => [
|
|
651
|
-
workspaceId,
|
|
652
|
-
{
|
|
653
|
-
extends: Array.isArray(item?.extends) ? item.extends.map((entry) => entry.trim()).filter(Boolean) : item?.extends ? [item.extends.trim()].filter(Boolean) : [],
|
|
654
|
-
...item?.globalId?.trim() ? { globalId: item.globalId.trim() } : {}
|
|
655
|
-
}
|
|
656
|
-
])
|
|
657
|
-
);
|
|
658
|
-
}
|
|
659
|
-
function normalizeManifest(manifest) {
|
|
660
|
-
const version = manifest.version ?? 1;
|
|
661
|
-
if (version !== 1) {
|
|
662
|
-
throw new CnosManifestError(`Unsupported CNOS manifest version: ${version}`);
|
|
663
|
-
}
|
|
664
|
-
const projectName = manifest.project?.name?.trim();
|
|
665
|
-
if (!projectName) {
|
|
666
|
-
throw new CnosManifestError("Manifest requires project.name");
|
|
667
|
-
}
|
|
668
|
-
const defaultProfile = manifest.profiles?.default?.trim() || "base";
|
|
669
|
-
const workspaceItems = normalizeWorkspaceItems(manifest.workspaces?.items);
|
|
670
|
-
const resolveFrom = validateResolveFrom(manifest.profiles?.resolveFrom ?? DEFAULT_RESOLVE_FROM);
|
|
671
|
-
const filesystemValues = {
|
|
672
|
-
root: "./",
|
|
673
|
-
format: "yaml",
|
|
674
|
-
...manifest.sources?.["filesystem-values"] ?? {}
|
|
675
|
-
};
|
|
676
|
-
const filesystemSecrets = {
|
|
677
|
-
root: "./",
|
|
678
|
-
format: "yaml",
|
|
679
|
-
...manifest.sources?.["filesystem-secrets"] ?? {}
|
|
680
|
-
};
|
|
681
|
-
const dotenv = {
|
|
682
|
-
root: "./env",
|
|
683
|
-
...manifest.sources?.dotenv ?? {}
|
|
684
|
-
};
|
|
685
|
-
return {
|
|
686
|
-
version: 1,
|
|
687
|
-
project: {
|
|
688
|
-
name: projectName
|
|
689
|
-
},
|
|
690
|
-
workspaces: {
|
|
691
|
-
...manifest.workspaces?.default?.trim() ? {
|
|
692
|
-
default: manifest.workspaces.default.trim()
|
|
693
|
-
} : {},
|
|
694
|
-
global: {
|
|
695
|
-
enabled: manifest.workspaces?.global?.enabled ?? false,
|
|
696
|
-
...manifest.workspaces?.global?.root?.trim() ? {
|
|
697
|
-
root: manifest.workspaces.global.root.trim()
|
|
698
|
-
} : {},
|
|
699
|
-
allowWrite: manifest.workspaces?.global?.allowWrite ?? false
|
|
700
|
-
},
|
|
701
|
-
items: workspaceItems
|
|
702
|
-
},
|
|
703
|
-
profiles: {
|
|
704
|
-
default: defaultProfile,
|
|
705
|
-
resolveFrom
|
|
706
|
-
},
|
|
707
|
-
plugins: {
|
|
708
|
-
loaders: manifest.plugins?.loaders ?? DEFAULT_LOADERS,
|
|
709
|
-
resolver: manifest.plugins?.resolver ?? "profile-aware",
|
|
710
|
-
validators: manifest.plugins?.validators ?? DEFAULT_VALIDATORS,
|
|
711
|
-
exporters: manifest.plugins?.exporters ?? DEFAULT_EXPORTERS,
|
|
712
|
-
inspectors: manifest.plugins?.inspectors ?? DEFAULT_INSPECTORS
|
|
713
|
-
},
|
|
714
|
-
sources: {
|
|
715
|
-
...manifest.sources ?? {},
|
|
716
|
-
"filesystem-values": filesystemValues,
|
|
717
|
-
"filesystem-secrets": filesystemSecrets,
|
|
718
|
-
dotenv
|
|
719
|
-
},
|
|
720
|
-
resolution: {
|
|
721
|
-
precedence: manifest.resolution?.precedence ?? [
|
|
722
|
-
"filesystem-values",
|
|
723
|
-
"filesystem-secrets",
|
|
724
|
-
"dotenv",
|
|
725
|
-
"process-env",
|
|
726
|
-
"cli-args"
|
|
727
|
-
],
|
|
728
|
-
arrayPolicy: manifest.resolution?.arrayPolicy ?? "replace"
|
|
729
|
-
},
|
|
730
|
-
envMapping: {
|
|
731
|
-
...manifest.envMapping?.convention ? {
|
|
732
|
-
convention: manifest.envMapping.convention
|
|
733
|
-
} : {},
|
|
734
|
-
explicit: manifest.envMapping?.explicit ?? {}
|
|
735
|
-
},
|
|
736
|
-
public: {
|
|
737
|
-
promote: manifest.public?.promote ?? [],
|
|
738
|
-
frameworks: {
|
|
739
|
-
...DEFAULT_FRAMEWORK_PREFIXES,
|
|
740
|
-
...manifest.public?.frameworks ?? {}
|
|
741
|
-
}
|
|
742
|
-
},
|
|
743
|
-
writePolicy: {
|
|
744
|
-
define: {
|
|
745
|
-
defaultProfile: manifest.writePolicy?.define?.defaultProfile ?? defaultProfile,
|
|
746
|
-
targets: {
|
|
747
|
-
value: manifest.writePolicy?.define?.targets?.value ?? "./values/app.yml",
|
|
748
|
-
secret: manifest.writePolicy?.define?.targets?.secret ?? "./secrets/app.yml"
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
},
|
|
752
|
-
schema: manifest.schema ?? {}
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
// ../core/src/manifest/loadManifest.ts
|
|
757
|
-
async function loadManifest(options = {}) {
|
|
758
|
-
const manifestRoot = await resolveManifestRoot(options.root);
|
|
759
|
-
const manifestPath = path4.join(manifestRoot, "cnos.yml");
|
|
760
|
-
let source;
|
|
761
|
-
try {
|
|
762
|
-
source = await readFile2(manifestPath, "utf8");
|
|
763
|
-
} catch {
|
|
764
|
-
throw new CnosManifestError("Unable to read CNOS manifest", manifestPath);
|
|
765
|
-
}
|
|
766
|
-
const rawManifest = parseYaml(source);
|
|
767
|
-
if (!rawManifest || typeof rawManifest !== "object") {
|
|
768
|
-
throw new CnosManifestError("CNOS manifest must be a YAML object", manifestPath);
|
|
769
|
-
}
|
|
770
|
-
return {
|
|
771
|
-
manifestRoot,
|
|
772
|
-
repoRoot: path4.dirname(manifestRoot),
|
|
773
|
-
manifestPath,
|
|
774
|
-
manifest: normalizeManifest(rawManifest),
|
|
775
|
-
rawManifest
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
|
|
779
931
|
// ../core/src/manifest/loadWorkspaceFile.ts
|
|
780
932
|
import { readFile as readFile3 } from "fs/promises";
|
|
781
933
|
import path5 from "path";
|
|
@@ -943,6 +1095,50 @@ async function expandProfileChain(activeProfile, options = {}) {
|
|
|
943
1095
|
};
|
|
944
1096
|
}
|
|
945
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
|
+
|
|
946
1142
|
// ../core/src/profiles/resolveActiveProfile.ts
|
|
947
1143
|
function resolveActiveProfile(manifest, options = {}) {
|
|
948
1144
|
for (const source of manifest.profiles.resolveFrom) {
|
|
@@ -1377,26 +1573,6 @@ async function runPipeline(options) {
|
|
|
1377
1573
|
return collectedEntries.flat();
|
|
1378
1574
|
}
|
|
1379
1575
|
|
|
1380
|
-
// ../core/src/runtime/read.ts
|
|
1381
|
-
function readValue(graph, key) {
|
|
1382
|
-
return graph.entries.get(key)?.value;
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
// ../core/src/runtime/readOr.ts
|
|
1386
|
-
function readOrValue(graph, key, fallback) {
|
|
1387
|
-
const value = readValue(graph, key);
|
|
1388
|
-
return value === void 0 ? fallback : value;
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
// ../core/src/runtime/require.ts
|
|
1392
|
-
function requireValue(graph, key) {
|
|
1393
|
-
const value = readValue(graph, key);
|
|
1394
|
-
if (value === void 0) {
|
|
1395
|
-
throw new CnosKeyNotFoundError(key);
|
|
1396
|
-
}
|
|
1397
|
-
return value;
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
1576
|
// ../core/src/orchestrator/runtime.ts
|
|
1401
1577
|
function createRuntime(manifest, graph, plugins = []) {
|
|
1402
1578
|
return {
|
|
@@ -1534,6 +1710,9 @@ function appendMetaEntries(graph, cnosVersion) {
|
|
|
1534
1710
|
}
|
|
1535
1711
|
async function createCnos(options = {}) {
|
|
1536
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
|
+
}
|
|
1537
1716
|
const workspaceFile = await loadWorkspaceFile(loadedManifest.repoRoot);
|
|
1538
1717
|
const workspace = await resolveWorkspaceContext(loadedManifest.manifest, {
|
|
1539
1718
|
manifestRoot: loadedManifest.manifestRoot,
|
|
@@ -1573,10 +1752,11 @@ async function createCnos(options = {}) {
|
|
|
1573
1752
|
workspace
|
|
1574
1753
|
});
|
|
1575
1754
|
const schemaApplied = applySchemaRules(graph, loadedManifest.manifest.schema);
|
|
1755
|
+
const promotedGraph = promoteToPublic(schemaApplied.graph, loadedManifest.manifest);
|
|
1576
1756
|
return createRuntime(
|
|
1577
1757
|
loadedManifest.manifest,
|
|
1578
1758
|
appendMetaEntries({
|
|
1579
|
-
...
|
|
1759
|
+
...promotedGraph,
|
|
1580
1760
|
profileSource: activeProfile.source
|
|
1581
1761
|
}, options.cnosVersion),
|
|
1582
1762
|
plugins
|
|
@@ -1585,28 +1765,42 @@ async function createCnos(options = {}) {
|
|
|
1585
1765
|
|
|
1586
1766
|
export {
|
|
1587
1767
|
CnosManifestError,
|
|
1768
|
+
CnosSecurityError,
|
|
1769
|
+
inspectValue,
|
|
1588
1770
|
createProvenanceInspector,
|
|
1771
|
+
resolveManifestRoot,
|
|
1589
1772
|
resolveWorkspaceScopedPath,
|
|
1590
1773
|
resolveConfigDocumentPath,
|
|
1591
1774
|
toPortablePath,
|
|
1592
1775
|
joinConfigPath,
|
|
1776
|
+
toLogicalKey,
|
|
1593
1777
|
parseYaml,
|
|
1594
1778
|
stringifyYaml,
|
|
1779
|
+
loadManifest,
|
|
1780
|
+
ensureProjectionAllowed,
|
|
1595
1781
|
applySchemaRules,
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
planDump,
|
|
1601
|
-
writeDump,
|
|
1602
|
-
flattenObject,
|
|
1782
|
+
toNamespaceObject,
|
|
1783
|
+
readValue,
|
|
1784
|
+
readOrValue,
|
|
1785
|
+
requireValue,
|
|
1603
1786
|
isSecretReference,
|
|
1604
1787
|
resolveSecretStoreRoot,
|
|
1605
1788
|
resolveSecretVaultFile,
|
|
1606
1789
|
resolveSecretPassphrase,
|
|
1790
|
+
getVaultPassphraseEnvVar,
|
|
1791
|
+
isPassphraseEnvRef,
|
|
1792
|
+
resolveConfiguredVaultPassphrase,
|
|
1793
|
+
resolveVaultDefinition,
|
|
1607
1794
|
createSecretVault,
|
|
1608
1795
|
listSecretVaults,
|
|
1609
1796
|
writeLocalSecret,
|
|
1610
1797
|
readLocalSecret,
|
|
1798
|
+
toEnv,
|
|
1799
|
+
toPublicEnv,
|
|
1800
|
+
createCnos,
|
|
1801
|
+
planDump,
|
|
1802
|
+
writeDump,
|
|
1803
|
+
envVarToLogicalKey,
|
|
1804
|
+
flattenObject,
|
|
1611
1805
|
validateRuntime
|
|
1612
1806
|
};
|