@kitsy/cnos 1.1.2 → 1.3.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 +6 -3
- 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 +2889 -0
- package/dist/build/index.d.cts +5 -0
- package/dist/build/index.d.ts +5 -0
- package/dist/build/index.js +26 -0
- package/dist/{chunk-53HXUSM6.js → chunk-CDXJISGB.js} +1 -1
- package/dist/{chunk-33ZDYDQJ.js → chunk-DRKDNY4I.js} +1470 -462
- package/dist/chunk-E7SE6N26.js +189 -0
- package/dist/chunk-EDCLLCNL.js +200 -0
- package/dist/{chunk-7FBRVJD6.js → chunk-FC3IV6A7.js} +1 -31
- package/dist/{chunk-JQGGSNCL.js → chunk-JDII6O72.js} +1 -1
- package/dist/chunk-K6QYI2T4.js +105 -0
- package/dist/{chunk-IHSV5AFX.js → chunk-OOKFRWTN.js} +1 -1
- package/dist/{chunk-HOS4E7XO.js → chunk-OWUZQ4OH.js} +1 -1
- package/dist/{chunk-IQOUWY6T.js → chunk-QTKXPY3N.js} +1 -1
- package/dist/configure/index.cjs +2928 -0
- package/dist/configure/index.d.cts +12 -0
- package/dist/configure/index.d.ts +12 -0
- package/dist/configure/index.js +24 -0
- package/dist/{envNaming-BrOk5ndZ.d.cts → envNaming-D6k66myh.d.cts} +1 -1
- package/dist/{envNaming-DCaNdnrF.d.ts → envNaming-Dy3WYiGK.d.ts} +1 -1
- package/dist/index.cjs +1396 -264
- package/dist/index.d.cts +2 -12
- package/dist/index.d.ts +2 -12
- package/dist/index.js +13 -143
- package/dist/internal.cjs +1913 -63
- package/dist/internal.d.cts +190 -8
- package/dist/internal.d.ts +190 -8
- package/dist/internal.js +669 -3
- package/dist/plugin/basic-schema.cjs +29 -2
- 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 +29 -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 +38 -11
- 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 +60 -48
- 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 +46 -91
- 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 +33 -6
- 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-CyNkf7Dm.d.cts} +42 -2
- package/dist/{plugin-BVNEHj19.d.ts → plugin-CyNkf7Dm.d.ts} +42 -2
- package/dist/runtime/index.cjs +3116 -0
- package/dist/runtime/index.d.cts +23 -0
- package/dist/runtime/index.d.ts +23 -0
- package/dist/runtime/index.js +15 -0
- package/dist/{toPublicEnv-Dd152fFy.d.cts → toPublicEnv-Cz72m6y0.d.cts} +1 -1
- package/dist/{toPublicEnv-Gwz3xTK0.d.ts → toPublicEnv-D2PZkaN-.d.ts} +1 -1
- package/package.json +26 -1
package/dist/internal.cjs
CHANGED
|
@@ -30,20 +30,197 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/internal.ts
|
|
31
31
|
var internal_exports = {};
|
|
32
32
|
__export(internal_exports, {
|
|
33
|
+
CNOS_GRAPH_ENV_VAR: () => CNOS_GRAPH_ENV_VAR,
|
|
34
|
+
CNOS_SECRET_PAYLOAD_ENV_VAR: () => CNOS_SECRET_PAYLOAD_ENV_VAR,
|
|
35
|
+
CNOS_SESSION_KEY_ENV_VAR: () => CNOS_SESSION_KEY_ENV_VAR,
|
|
36
|
+
CnosAuthenticationError: () => CnosAuthenticationError,
|
|
37
|
+
CnosSecurityError: () => CnosSecurityError,
|
|
38
|
+
applyManifestMappings: () => applyManifestMappings,
|
|
39
|
+
clearAllVaultSessionKeys: () => clearAllVaultSessionKeys,
|
|
40
|
+
clearVaultSessionKey: () => clearVaultSessionKey,
|
|
41
|
+
compareSchemaToGraph: () => compareSchemaToGraph,
|
|
33
42
|
createSecretVault: () => createSecretVault,
|
|
43
|
+
createSecretVaultProvider: () => createSecretVaultProvider,
|
|
44
|
+
deleteLocalSecret: () => deleteLocalSecret,
|
|
45
|
+
deriveVaultKey: () => deriveVaultKey,
|
|
46
|
+
deserializeRuntimeGraph: () => deserializeRuntimeGraph,
|
|
47
|
+
detectLegacyVaultFormat: () => detectLegacyVaultFormat,
|
|
48
|
+
diffGraphs: () => diffGraphs,
|
|
49
|
+
ensureProjectionAllowed: () => ensureProjectionAllowed,
|
|
34
50
|
flattenObject: () => flattenObject,
|
|
51
|
+
formatDriftReport: () => formatDriftReport,
|
|
52
|
+
generateCodegenContent: () => generateCodegenContent,
|
|
53
|
+
getVaultPassphraseEnvVar: () => getVaultPassphraseEnvVar,
|
|
54
|
+
getVaultSessionKeyEnvVar: () => getVaultSessionKeyEnvVar,
|
|
55
|
+
graphRequiresSecretHydration: () => graphRequiresSecretHydration,
|
|
56
|
+
isPassphraseEnvRef: () => isPassphraseEnvRef,
|
|
57
|
+
isSecretReference: () => isSecretReference,
|
|
58
|
+
listLocalSecrets: () => listLocalSecrets,
|
|
35
59
|
listSecretVaults: () => listSecretVaults,
|
|
60
|
+
loadManifest: () => loadManifest,
|
|
36
61
|
parseYaml: () => parseYaml,
|
|
62
|
+
proposeMapping: () => proposeMapping,
|
|
63
|
+
readKeychain: () => readKeychain,
|
|
64
|
+
readLocalSecret: () => readLocalSecret,
|
|
65
|
+
readRuntimeGraphFromEnv: () => readRuntimeGraphFromEnv,
|
|
66
|
+
readVaultMetadata: () => readVaultMetadata,
|
|
67
|
+
removeLocalVaultFiles: () => removeLocalVaultFiles,
|
|
68
|
+
resolveCodegenPaths: () => resolveCodegenPaths,
|
|
37
69
|
resolveConfigDocumentPath: () => resolveConfigDocumentPath,
|
|
70
|
+
resolveConfiguredVaultPassphrase: () => resolveConfiguredVaultPassphrase,
|
|
71
|
+
resolveManifestRoot: () => resolveManifestRoot,
|
|
38
72
|
resolveSecretPassphrase: () => resolveSecretPassphrase,
|
|
39
73
|
resolveSecretStoreRoot: () => resolveSecretStoreRoot,
|
|
40
74
|
resolveSecretVaultFile: () => resolveSecretVaultFile,
|
|
75
|
+
resolveVaultAccessKey: () => resolveVaultAccessKey,
|
|
76
|
+
resolveVaultAuth: () => resolveVaultAuth,
|
|
77
|
+
resolveVaultDefinition: () => resolveVaultDefinition,
|
|
78
|
+
rewriteSourceFiles: () => rewriteSourceFiles,
|
|
79
|
+
scanEnvUsage: () => scanEnvUsage,
|
|
80
|
+
serializeRuntimeGraph: () => serializeRuntimeGraph,
|
|
81
|
+
serializeSecretPayload: () => serializeSecretPayload,
|
|
41
82
|
stringifyYaml: () => stringifyYaml,
|
|
42
83
|
validateRuntime: () => validateRuntime,
|
|
43
|
-
|
|
84
|
+
watchFiles: () => watchFiles,
|
|
85
|
+
watchSchema: () => watchSchema,
|
|
86
|
+
writeCodegenOutput: () => writeCodegenOutput,
|
|
87
|
+
writeKeychain: () => writeKeychain,
|
|
88
|
+
writeLocalSecret: () => writeLocalSecret,
|
|
89
|
+
writeVaultSessionKey: () => writeVaultSessionKey
|
|
44
90
|
});
|
|
45
91
|
module.exports = __toCommonJS(internal_exports);
|
|
46
92
|
|
|
93
|
+
// ../core/src/errors.ts
|
|
94
|
+
var CnosError = class extends Error {
|
|
95
|
+
constructor(message) {
|
|
96
|
+
super(message);
|
|
97
|
+
this.name = new.target.name;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var CnosManifestError = class extends CnosError {
|
|
101
|
+
constructor(message, manifestPath) {
|
|
102
|
+
super(manifestPath ? `${message} (${manifestPath})` : message);
|
|
103
|
+
this.manifestPath = manifestPath;
|
|
104
|
+
}
|
|
105
|
+
manifestPath;
|
|
106
|
+
};
|
|
107
|
+
var CnosSecurityError = class extends CnosError {
|
|
108
|
+
constructor(message) {
|
|
109
|
+
super(message);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
var CnosAuthenticationError = class extends CnosError {
|
|
113
|
+
constructor(message) {
|
|
114
|
+
super(message);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// ../core/src/keychain/linux.ts
|
|
119
|
+
var import_node_child_process = require("child_process");
|
|
120
|
+
var import_node_util = require("util");
|
|
121
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
122
|
+
async function readLinuxKeychain(entry) {
|
|
123
|
+
try {
|
|
124
|
+
const { stdout } = await execFileAsync("secret-tool", ["lookup", "service", "cnos", "account", entry]);
|
|
125
|
+
const value = stdout.trim();
|
|
126
|
+
return value.length > 0 ? value : void 0;
|
|
127
|
+
} catch {
|
|
128
|
+
return void 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function writeLinuxKeychain(entry, value) {
|
|
132
|
+
await new Promise((resolve, reject) => {
|
|
133
|
+
const child = (0, import_node_child_process.spawn)("secret-tool", ["store", "--label", `CNOS ${entry}`, "service", "cnos", "account", entry], {
|
|
134
|
+
stdio: ["pipe", "ignore", "pipe"]
|
|
135
|
+
});
|
|
136
|
+
let stderr = "";
|
|
137
|
+
child.stdin?.end(value);
|
|
138
|
+
child.stderr?.on("data", (chunk) => {
|
|
139
|
+
stderr += chunk.toString();
|
|
140
|
+
});
|
|
141
|
+
child.on("error", reject);
|
|
142
|
+
child.on("close", (code) => {
|
|
143
|
+
if (code === 0) {
|
|
144
|
+
resolve();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
reject(new Error(stderr || `secret-tool exited with code ${code ?? 1}`));
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ../core/src/keychain/macos.ts
|
|
153
|
+
var import_node_child_process2 = require("child_process");
|
|
154
|
+
var import_node_util2 = require("util");
|
|
155
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
156
|
+
async function readMacosKeychain(entry) {
|
|
157
|
+
try {
|
|
158
|
+
const { stdout } = await execFileAsync2("security", ["find-generic-password", "-a", "cnos", "-s", entry, "-w"]);
|
|
159
|
+
const value = stdout.trim();
|
|
160
|
+
return value.length > 0 ? value : void 0;
|
|
161
|
+
} catch {
|
|
162
|
+
return void 0;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function writeMacosKeychain(entry, value) {
|
|
166
|
+
await execFileAsync2("security", ["add-generic-password", "-a", "cnos", "-s", entry, "-w", value, "-U"]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ../core/src/keychain/windows.ts
|
|
170
|
+
var import_node_child_process3 = require("child_process");
|
|
171
|
+
var import_node_util3 = require("util");
|
|
172
|
+
var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process3.execFile);
|
|
173
|
+
function wrap(script) {
|
|
174
|
+
return ["-NoProfile", "-Command", script];
|
|
175
|
+
}
|
|
176
|
+
async function readWindowsKeychain(entry) {
|
|
177
|
+
try {
|
|
178
|
+
const { stdout } = await execFileAsync3(
|
|
179
|
+
"powershell",
|
|
180
|
+
wrap(`Add-Type -AssemblyName System.Runtime.WindowsRuntime; [Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime] > $null; $vault = New-Object Windows.Security.Credentials.PasswordVault; $credential = $vault.Retrieve('cnos','${entry}'); $credential.RetrievePassword(); Write-Output $credential.Password`)
|
|
181
|
+
);
|
|
182
|
+
const value = stdout.trim();
|
|
183
|
+
return value.length > 0 ? value : void 0;
|
|
184
|
+
} catch {
|
|
185
|
+
return void 0;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async function writeWindowsKeychain(entry, value) {
|
|
189
|
+
await execFileAsync3(
|
|
190
|
+
"powershell",
|
|
191
|
+
wrap(`Add-Type -AssemblyName System.Runtime.WindowsRuntime; [Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime] > $null; $vault = New-Object Windows.Security.Credentials.PasswordVault; try { $existing = $vault.Retrieve('cnos','${entry}'); $vault.Remove($existing) } catch {}; $credential = New-Object Windows.Security.Credentials.PasswordCredential('cnos','${entry}','${value}'); $vault.Add($credential)`)
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ../core/src/keychain/index.ts
|
|
196
|
+
async function readKeychain(entry) {
|
|
197
|
+
if (process.platform === "win32") {
|
|
198
|
+
return readWindowsKeychain(entry);
|
|
199
|
+
}
|
|
200
|
+
if (process.platform === "darwin") {
|
|
201
|
+
return readMacosKeychain(entry);
|
|
202
|
+
}
|
|
203
|
+
if (process.platform === "linux") {
|
|
204
|
+
return readLinuxKeychain(entry);
|
|
205
|
+
}
|
|
206
|
+
return void 0;
|
|
207
|
+
}
|
|
208
|
+
async function writeKeychain(entry, value) {
|
|
209
|
+
if (process.platform === "win32") {
|
|
210
|
+
await writeWindowsKeychain(entry, value);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (process.platform === "darwin") {
|
|
214
|
+
await writeMacosKeychain(entry, value);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (process.platform === "linux") {
|
|
218
|
+
await writeLinuxKeychain(entry, value);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
throw new CnosAuthenticationError(`OS keychain is not supported on platform "${process.platform}".`);
|
|
222
|
+
}
|
|
223
|
+
|
|
47
224
|
// ../core/src/manifest/loadManifest.ts
|
|
48
225
|
var import_promises2 = require("fs/promises");
|
|
49
226
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
@@ -52,6 +229,35 @@ var import_node_path2 = __toESM(require("path"), 1);
|
|
|
52
229
|
var import_promises = require("fs/promises");
|
|
53
230
|
var import_node_os = __toESM(require("os"), 1);
|
|
54
231
|
var import_node_path = __toESM(require("path"), 1);
|
|
232
|
+
var PRIMARY_CNOS_DIR = ".cnos";
|
|
233
|
+
var LEGACY_CNOS_DIR = "cnos";
|
|
234
|
+
async function exists(filePath) {
|
|
235
|
+
try {
|
|
236
|
+
await (0, import_promises.access)(filePath);
|
|
237
|
+
return true;
|
|
238
|
+
} catch {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async function resolveCnosRoot(root = process.cwd()) {
|
|
243
|
+
const basePath = import_node_path.default.resolve(root);
|
|
244
|
+
const candidates = [
|
|
245
|
+
import_node_path.default.join(basePath, PRIMARY_CNOS_DIR),
|
|
246
|
+
import_node_path.default.join(basePath, LEGACY_CNOS_DIR),
|
|
247
|
+
basePath
|
|
248
|
+
];
|
|
249
|
+
for (const candidate of candidates) {
|
|
250
|
+
if (await exists(import_node_path.default.join(candidate, "cnos.yml"))) {
|
|
251
|
+
return candidate;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
throw new CnosManifestError(
|
|
255
|
+
`Could not locate .cnos/cnos.yml or cnos/cnos.yml from root: ${basePath}`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
async function resolveManifestRoot(root = process.cwd()) {
|
|
259
|
+
return resolveCnosRoot(root);
|
|
260
|
+
}
|
|
55
261
|
function expandHomePath(targetPath) {
|
|
56
262
|
if (targetPath === "~") {
|
|
57
263
|
return import_node_os.default.homedir();
|
|
@@ -83,6 +289,290 @@ function stringifyYaml(value) {
|
|
|
83
289
|
return (0, import_yaml.stringify)(value);
|
|
84
290
|
}
|
|
85
291
|
|
|
292
|
+
// ../core/src/manifest/normalizeManifest.ts
|
|
293
|
+
var DEFAULT_RESOLVE_FROM = ["cli.profile", "env.CNOS_PROFILE", "default"];
|
|
294
|
+
var DEFAULT_LOADERS = [
|
|
295
|
+
"filesystem-values",
|
|
296
|
+
"filesystem-secrets",
|
|
297
|
+
"dotenv",
|
|
298
|
+
"process-env",
|
|
299
|
+
"cli-args"
|
|
300
|
+
];
|
|
301
|
+
var DEFAULT_VALIDATORS = ["basic-schema"];
|
|
302
|
+
var DEFAULT_EXPORTERS = ["env", "public-env"];
|
|
303
|
+
var DEFAULT_INSPECTORS = ["provenance"];
|
|
304
|
+
var DEFAULT_FRAMEWORK_PREFIXES = {
|
|
305
|
+
next: "NEXT_PUBLIC_",
|
|
306
|
+
vite: "VITE_",
|
|
307
|
+
nuxt: "NUXT_PUBLIC_"
|
|
308
|
+
};
|
|
309
|
+
var DEFAULT_NAMESPACES = {
|
|
310
|
+
value: {
|
|
311
|
+
kind: "data",
|
|
312
|
+
shareable: true
|
|
313
|
+
},
|
|
314
|
+
secret: {
|
|
315
|
+
kind: "data",
|
|
316
|
+
shareable: false,
|
|
317
|
+
sensitive: true
|
|
318
|
+
},
|
|
319
|
+
meta: {
|
|
320
|
+
kind: "system",
|
|
321
|
+
shareable: false,
|
|
322
|
+
readonly: true
|
|
323
|
+
},
|
|
324
|
+
public: {
|
|
325
|
+
kind: "projection",
|
|
326
|
+
source: "promote",
|
|
327
|
+
shareable: true,
|
|
328
|
+
readonly: true
|
|
329
|
+
},
|
|
330
|
+
env: {
|
|
331
|
+
kind: "projection",
|
|
332
|
+
source: "envMapping",
|
|
333
|
+
shareable: true,
|
|
334
|
+
readonly: true
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
function validateResolveFrom(resolveFrom) {
|
|
338
|
+
const validValues = ["cli.profile", "env.CNOS_PROFILE", "default"];
|
|
339
|
+
for (const entry of resolveFrom) {
|
|
340
|
+
if (!validValues.includes(entry)) {
|
|
341
|
+
throw new CnosManifestError(`Unsupported profiles.resolveFrom entry: ${entry}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return resolveFrom;
|
|
345
|
+
}
|
|
346
|
+
function normalizeWorkspaceItems(items) {
|
|
347
|
+
return Object.fromEntries(
|
|
348
|
+
Object.entries(items ?? {}).map(([workspaceId, item]) => [
|
|
349
|
+
workspaceId,
|
|
350
|
+
{
|
|
351
|
+
extends: Array.isArray(item?.extends) ? item.extends.map((entry) => entry.trim()).filter(Boolean) : item?.extends ? [item.extends.trim()].filter(Boolean) : [],
|
|
352
|
+
...item?.globalId?.trim() ? { globalId: item.globalId.trim() } : {}
|
|
353
|
+
}
|
|
354
|
+
])
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
function normalizeNamespaces(namespaces) {
|
|
358
|
+
const normalized = Object.fromEntries(
|
|
359
|
+
Object.entries(namespaces ?? {}).map(([namespace, definition]) => [
|
|
360
|
+
namespace,
|
|
361
|
+
{
|
|
362
|
+
kind: definition.kind ?? "data",
|
|
363
|
+
shareable: definition.shareable ?? false,
|
|
364
|
+
...definition.sensitive !== void 0 ? { sensitive: definition.sensitive } : {},
|
|
365
|
+
...definition.readonly !== void 0 ? { readonly: definition.readonly } : {},
|
|
366
|
+
...definition.source ? { source: definition.source } : {}
|
|
367
|
+
}
|
|
368
|
+
])
|
|
369
|
+
);
|
|
370
|
+
return {
|
|
371
|
+
...DEFAULT_NAMESPACES,
|
|
372
|
+
...normalized
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function normalizeVaults(vaults) {
|
|
376
|
+
return Object.fromEntries(
|
|
377
|
+
Object.entries(vaults ?? {}).map(([name, definition]) => {
|
|
378
|
+
const legacyPassphrase = definition.passphrase;
|
|
379
|
+
if (legacyPassphrase !== void 0) {
|
|
380
|
+
throw new CnosManifestError(
|
|
381
|
+
`Vault "${name}" uses legacy passphrase configuration. Use vaults.${name}.auth instead.`
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
const provider = definition.provider?.trim();
|
|
385
|
+
if (!provider) {
|
|
386
|
+
throw new CnosManifestError(`Vault "${name}" requires a provider`);
|
|
387
|
+
}
|
|
388
|
+
const normalizedAuth = normalizeVaultAuth(name, provider, definition.auth);
|
|
389
|
+
const normalizedMapping = Object.fromEntries(
|
|
390
|
+
Object.entries(definition.mapping ?? {}).filter(
|
|
391
|
+
(entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
|
|
392
|
+
).map(([envVar, logicalRef]) => [envVar.trim(), logicalRef.trim()]).filter(([envVar, logicalRef]) => envVar.length > 0 && logicalRef.length > 0)
|
|
393
|
+
);
|
|
394
|
+
return [
|
|
395
|
+
name,
|
|
396
|
+
{
|
|
397
|
+
provider,
|
|
398
|
+
auth: normalizedAuth,
|
|
399
|
+
...Object.keys(normalizedMapping).length > 0 ? {
|
|
400
|
+
mapping: normalizedMapping
|
|
401
|
+
} : {}
|
|
402
|
+
}
|
|
403
|
+
];
|
|
404
|
+
})
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
function normalizeAuthSources(value) {
|
|
408
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
409
|
+
return void 0;
|
|
410
|
+
}
|
|
411
|
+
const sources = Array.isArray(value.from) ? value.from : void 0;
|
|
412
|
+
const normalized = (sources ?? []).map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
|
|
413
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
414
|
+
}
|
|
415
|
+
function normalizeVaultAuth(vaultName, provider, auth) {
|
|
416
|
+
if (provider === "local") {
|
|
417
|
+
const passphraseSources = normalizeAuthSources(auth?.passphrase);
|
|
418
|
+
const defaultToken = vaultName.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
419
|
+
const defaultSources = [
|
|
420
|
+
...defaultToken ? [`env:CNOS_SECRET_PASSPHRASE_${defaultToken}`] : [],
|
|
421
|
+
"env:CNOS_SECRET_PASSPHRASE",
|
|
422
|
+
`keychain:cnos/${vaultName}`,
|
|
423
|
+
"prompt"
|
|
424
|
+
];
|
|
425
|
+
return {
|
|
426
|
+
method: auth?.method ?? "passphrase",
|
|
427
|
+
passphrase: {
|
|
428
|
+
from: passphraseSources ?? defaultSources
|
|
429
|
+
},
|
|
430
|
+
...auth?.config ? { config: auth.config } : {}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
if (provider === "github-secrets") {
|
|
434
|
+
return {
|
|
435
|
+
method: auth?.method ?? "environment",
|
|
436
|
+
...auth?.config ? { config: auth.config } : {}
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
...auth?.method ? { method: auth.method } : {},
|
|
441
|
+
...normalizeAuthSources(auth?.passphrase) ? {
|
|
442
|
+
passphrase: {
|
|
443
|
+
from: normalizeAuthSources(auth?.passphrase) ?? []
|
|
444
|
+
}
|
|
445
|
+
} : {},
|
|
446
|
+
...normalizeAuthSources(auth?.token) ? {
|
|
447
|
+
token: {
|
|
448
|
+
from: normalizeAuthSources(auth?.token) ?? []
|
|
449
|
+
}
|
|
450
|
+
} : {},
|
|
451
|
+
...auth?.config ? { config: auth.config } : {}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function normalizeManifest(manifest) {
|
|
455
|
+
const version = manifest.version ?? 1;
|
|
456
|
+
if (version !== 1) {
|
|
457
|
+
throw new CnosManifestError(`Unsupported CNOS manifest version: ${version}`);
|
|
458
|
+
}
|
|
459
|
+
const projectName = manifest.project?.name?.trim();
|
|
460
|
+
if (!projectName) {
|
|
461
|
+
throw new CnosManifestError("Manifest requires project.name");
|
|
462
|
+
}
|
|
463
|
+
const defaultProfile = manifest.profiles?.default?.trim() || "base";
|
|
464
|
+
const workspaceItems = normalizeWorkspaceItems(manifest.workspaces?.items);
|
|
465
|
+
const resolveFrom = validateResolveFrom(manifest.profiles?.resolveFrom ?? DEFAULT_RESOLVE_FROM);
|
|
466
|
+
const filesystemValues = {
|
|
467
|
+
root: "./",
|
|
468
|
+
format: "yaml",
|
|
469
|
+
...manifest.sources?.["filesystem-values"] ?? {}
|
|
470
|
+
};
|
|
471
|
+
const filesystemSecrets = {
|
|
472
|
+
root: "./",
|
|
473
|
+
format: "yaml",
|
|
474
|
+
...manifest.sources?.["filesystem-secrets"] ?? {}
|
|
475
|
+
};
|
|
476
|
+
const dotenv = {
|
|
477
|
+
root: "./env",
|
|
478
|
+
...manifest.sources?.dotenv ?? {}
|
|
479
|
+
};
|
|
480
|
+
return {
|
|
481
|
+
version: 1,
|
|
482
|
+
project: {
|
|
483
|
+
name: projectName
|
|
484
|
+
},
|
|
485
|
+
workspaces: {
|
|
486
|
+
...manifest.workspaces?.default?.trim() ? {
|
|
487
|
+
default: manifest.workspaces.default.trim()
|
|
488
|
+
} : {},
|
|
489
|
+
global: {
|
|
490
|
+
enabled: manifest.workspaces?.global?.enabled ?? false,
|
|
491
|
+
...manifest.workspaces?.global?.root?.trim() ? {
|
|
492
|
+
root: manifest.workspaces.global.root.trim()
|
|
493
|
+
} : {},
|
|
494
|
+
allowWrite: manifest.workspaces?.global?.allowWrite ?? false
|
|
495
|
+
},
|
|
496
|
+
items: workspaceItems
|
|
497
|
+
},
|
|
498
|
+
profiles: {
|
|
499
|
+
default: defaultProfile,
|
|
500
|
+
resolveFrom
|
|
501
|
+
},
|
|
502
|
+
plugins: {
|
|
503
|
+
loaders: manifest.plugins?.loaders ?? DEFAULT_LOADERS,
|
|
504
|
+
resolver: manifest.plugins?.resolver ?? "profile-aware",
|
|
505
|
+
validators: manifest.plugins?.validators ?? DEFAULT_VALIDATORS,
|
|
506
|
+
exporters: manifest.plugins?.exporters ?? DEFAULT_EXPORTERS,
|
|
507
|
+
inspectors: manifest.plugins?.inspectors ?? DEFAULT_INSPECTORS
|
|
508
|
+
},
|
|
509
|
+
sources: {
|
|
510
|
+
...manifest.sources ?? {},
|
|
511
|
+
"filesystem-values": filesystemValues,
|
|
512
|
+
"filesystem-secrets": filesystemSecrets,
|
|
513
|
+
dotenv
|
|
514
|
+
},
|
|
515
|
+
resolution: {
|
|
516
|
+
precedence: manifest.resolution?.precedence ?? [
|
|
517
|
+
"filesystem-values",
|
|
518
|
+
"filesystem-secrets",
|
|
519
|
+
"dotenv",
|
|
520
|
+
"process-env",
|
|
521
|
+
"cli-args"
|
|
522
|
+
],
|
|
523
|
+
arrayPolicy: manifest.resolution?.arrayPolicy ?? "replace"
|
|
524
|
+
},
|
|
525
|
+
envMapping: {
|
|
526
|
+
...manifest.envMapping?.convention ? {
|
|
527
|
+
convention: manifest.envMapping.convention
|
|
528
|
+
} : {},
|
|
529
|
+
explicit: manifest.envMapping?.explicit ?? {}
|
|
530
|
+
},
|
|
531
|
+
public: {
|
|
532
|
+
promote: manifest.public?.promote ?? [],
|
|
533
|
+
frameworks: {
|
|
534
|
+
...DEFAULT_FRAMEWORK_PREFIXES,
|
|
535
|
+
...manifest.public?.frameworks ?? {}
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
namespaces: normalizeNamespaces(manifest.namespaces),
|
|
539
|
+
vaults: normalizeVaults(manifest.vaults),
|
|
540
|
+
writePolicy: {
|
|
541
|
+
define: {
|
|
542
|
+
defaultProfile: manifest.writePolicy?.define?.defaultProfile ?? defaultProfile,
|
|
543
|
+
targets: {
|
|
544
|
+
value: manifest.writePolicy?.define?.targets?.value ?? "./values/app.yml",
|
|
545
|
+
secret: manifest.writePolicy?.define?.targets?.secret ?? "./secrets/app.yml"
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
schema: manifest.schema ?? {}
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// ../core/src/manifest/loadManifest.ts
|
|
554
|
+
async function loadManifest(options = {}) {
|
|
555
|
+
const manifestRoot = await resolveManifestRoot(options.root);
|
|
556
|
+
const manifestPath = import_node_path2.default.join(manifestRoot, "cnos.yml");
|
|
557
|
+
let source;
|
|
558
|
+
try {
|
|
559
|
+
source = await (0, import_promises2.readFile)(manifestPath, "utf8");
|
|
560
|
+
} catch {
|
|
561
|
+
throw new CnosManifestError("Unable to read CNOS manifest", manifestPath);
|
|
562
|
+
}
|
|
563
|
+
const rawManifest = parseYaml(source);
|
|
564
|
+
if (!rawManifest || typeof rawManifest !== "object") {
|
|
565
|
+
throw new CnosManifestError("CNOS manifest must be a YAML object", manifestPath);
|
|
566
|
+
}
|
|
567
|
+
return {
|
|
568
|
+
manifestRoot,
|
|
569
|
+
repoRoot: import_node_path2.default.dirname(manifestRoot),
|
|
570
|
+
manifestPath,
|
|
571
|
+
manifest: normalizeManifest(rawManifest),
|
|
572
|
+
rawManifest
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
86
576
|
// ../core/src/manifest/loadWorkspaceFile.ts
|
|
87
577
|
var import_promises3 = require("fs/promises");
|
|
88
578
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
@@ -91,89 +581,735 @@ var import_node_path3 = __toESM(require("path"), 1);
|
|
|
91
581
|
var import_promises4 = require("fs/promises");
|
|
92
582
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
93
583
|
|
|
584
|
+
// ../core/src/promotions/validatePromotion.ts
|
|
585
|
+
var DEFAULT_DATA_NAMESPACE = {
|
|
586
|
+
kind: "data",
|
|
587
|
+
shareable: false
|
|
588
|
+
};
|
|
589
|
+
function getNamespaceNameForKey(key) {
|
|
590
|
+
const [namespace] = key.split(".");
|
|
591
|
+
if (!namespace || !key.includes(".")) {
|
|
592
|
+
throw new CnosManifestError(`Logical key must be namespace-qualified: ${key}`);
|
|
593
|
+
}
|
|
594
|
+
return namespace;
|
|
595
|
+
}
|
|
596
|
+
function getNamespaceDefinition(manifest, namespaceOrKey) {
|
|
597
|
+
const namespace = namespaceOrKey.includes(".") ? getNamespaceNameForKey(namespaceOrKey) : namespaceOrKey;
|
|
598
|
+
return manifest.namespaces[namespace] ?? DEFAULT_DATA_NAMESPACE;
|
|
599
|
+
}
|
|
600
|
+
function ensureProjectionAllowed(manifest, key, target) {
|
|
601
|
+
const namespace = getNamespaceNameForKey(key);
|
|
602
|
+
const definition = getNamespaceDefinition(manifest, namespace);
|
|
603
|
+
if (definition.kind !== "data") {
|
|
604
|
+
throw new CnosManifestError(
|
|
605
|
+
`Cannot promote ${key} to ${target} because namespace "${namespace}" is not a data namespace.`
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
if (definition.sensitive) {
|
|
609
|
+
throw new CnosSecurityError(
|
|
610
|
+
`Cannot promote ${key} to ${target} because namespace "${namespace}" is sensitive.`
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
if (!definition.shareable) {
|
|
614
|
+
throw new CnosSecurityError(
|
|
615
|
+
`Cannot promote ${key} to ${target} because namespace "${namespace}" is not shareable.`
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function validateProjectionIssue(manifest, key, target) {
|
|
620
|
+
try {
|
|
621
|
+
ensureProjectionAllowed(manifest, key, target);
|
|
622
|
+
return void 0;
|
|
623
|
+
} catch (error) {
|
|
624
|
+
return {
|
|
625
|
+
code: target === "public" ? "public.invalid-promotion" : "env.invalid-mapping",
|
|
626
|
+
key,
|
|
627
|
+
message: error instanceof Error ? error.message : String(error)
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
94
632
|
// ../core/src/workspaces/resolveWorkspaceContext.ts
|
|
95
633
|
var import_promises5 = require("fs/promises");
|
|
96
634
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
97
635
|
|
|
636
|
+
// ../core/src/secrets/auditLog.ts
|
|
637
|
+
var import_promises8 = require("fs/promises");
|
|
638
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
639
|
+
|
|
98
640
|
// ../core/src/utils/secretStore.ts
|
|
99
641
|
var import_node_crypto = require("crypto");
|
|
642
|
+
var import_promises7 = require("fs/promises");
|
|
643
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
644
|
+
|
|
645
|
+
// ../core/src/secrets/sessionStore.ts
|
|
100
646
|
var import_promises6 = require("fs/promises");
|
|
101
647
|
var import_node_path6 = __toESM(require("path"), 1);
|
|
648
|
+
function buildSessionRoot(processEnv = process.env) {
|
|
649
|
+
return import_node_path6.default.join(import_node_path6.default.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets")), "sessions");
|
|
650
|
+
}
|
|
651
|
+
function buildSessionPath(vault, processEnv) {
|
|
652
|
+
return import_node_path6.default.join(buildSessionRoot(processEnv), `${vault}.json`);
|
|
653
|
+
}
|
|
654
|
+
async function writeVaultSessionKey(vault, derivedKey, processEnv) {
|
|
655
|
+
const filePath = buildSessionPath(vault, processEnv);
|
|
656
|
+
await (0, import_promises6.mkdir)(import_node_path6.default.dirname(filePath), { recursive: true });
|
|
657
|
+
const document = {
|
|
658
|
+
version: 1,
|
|
659
|
+
vault,
|
|
660
|
+
derivedKey: derivedKey.toString("hex"),
|
|
661
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
662
|
+
};
|
|
663
|
+
await (0, import_promises6.writeFile)(filePath, JSON.stringify(document, null, 2), "utf8");
|
|
664
|
+
return filePath;
|
|
665
|
+
}
|
|
666
|
+
async function readVaultSessionKey(vault, processEnv) {
|
|
667
|
+
try {
|
|
668
|
+
const source = await (0, import_promises6.readFile)(buildSessionPath(vault, processEnv), "utf8");
|
|
669
|
+
const document = JSON.parse(source);
|
|
670
|
+
if (document.version !== 1 || typeof document.derivedKey !== "string") {
|
|
671
|
+
return void 0;
|
|
672
|
+
}
|
|
673
|
+
const key = Buffer.from(document.derivedKey, "hex");
|
|
674
|
+
return key.length > 0 ? key : void 0;
|
|
675
|
+
} catch {
|
|
676
|
+
return void 0;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
async function clearVaultSessionKey(vault, processEnv) {
|
|
680
|
+
await (0, import_promises6.rm)(buildSessionPath(vault, processEnv), { force: true });
|
|
681
|
+
}
|
|
682
|
+
async function clearAllVaultSessionKeys(processEnv) {
|
|
683
|
+
const root = buildSessionRoot(processEnv);
|
|
684
|
+
try {
|
|
685
|
+
const entries = await (0, import_promises6.readdir)(root);
|
|
686
|
+
await Promise.all(entries.map((entry) => (0, import_promises6.rm)(import_node_path6.default.join(root, entry), { force: true })));
|
|
687
|
+
} catch {
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// ../core/src/utils/secretStore.ts
|
|
692
|
+
var KEY_LENGTH = 32;
|
|
693
|
+
var SALT_LENGTH = 32;
|
|
694
|
+
var IV_LENGTH = 12;
|
|
695
|
+
var AUTH_TAG_LENGTH = 16;
|
|
696
|
+
var PBKDF2_ITERATIONS = 6e5;
|
|
697
|
+
var KEYSTORE_VERSION = 1;
|
|
698
|
+
var METADATA_VERSION = 1;
|
|
699
|
+
var META_FILENAME = "meta.yml";
|
|
700
|
+
var KEYSTORE_FILENAME = "keystore.enc";
|
|
701
|
+
function isObject(value) {
|
|
702
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
703
|
+
}
|
|
704
|
+
function isSecretReference(value) {
|
|
705
|
+
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));
|
|
706
|
+
}
|
|
102
707
|
function resolveSecretStoreRoot(processEnv = process.env) {
|
|
103
|
-
return
|
|
708
|
+
return import_node_path7.default.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
|
|
104
709
|
}
|
|
105
|
-
function
|
|
106
|
-
return
|
|
710
|
+
function normalizeVaultToken(vault = "default") {
|
|
711
|
+
return vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
712
|
+
}
|
|
713
|
+
function getVaultPassphraseEnvVar(vault = "default") {
|
|
714
|
+
const vaultToken = normalizeVaultToken(vault);
|
|
715
|
+
return vaultToken && vaultToken !== "DEFAULT" ? `CNOS_SECRET_PASSPHRASE_${vaultToken}` : "CNOS_SECRET_PASSPHRASE";
|
|
107
716
|
}
|
|
108
|
-
function
|
|
109
|
-
return
|
|
717
|
+
function isPassphraseEnvRef(value) {
|
|
718
|
+
return typeof value === "string" && value.startsWith("env:") && value.length > 4;
|
|
110
719
|
}
|
|
111
|
-
function
|
|
112
|
-
|
|
720
|
+
function getVaultSessionKeyEnvVar(vault = "default") {
|
|
721
|
+
const vaultToken = normalizeVaultToken(vault);
|
|
722
|
+
return `__CNOS_VAULT_KEY_${vaultToken || "DEFAULT"}__`;
|
|
113
723
|
}
|
|
114
724
|
function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
|
|
115
|
-
|
|
116
|
-
|
|
725
|
+
return processEnv[getVaultPassphraseEnvVar(vault)] ?? processEnv.CNOS_SECRET_PASSPHRASE;
|
|
726
|
+
}
|
|
727
|
+
function resolveVaultSessionKey(vault = "default", processEnv = process.env) {
|
|
728
|
+
const encoded = processEnv[getVaultSessionKeyEnvVar(vault)];
|
|
729
|
+
if (!encoded) {
|
|
730
|
+
return readVaultSessionKey(vault, processEnv);
|
|
731
|
+
}
|
|
732
|
+
try {
|
|
733
|
+
const key = Buffer.from(encoded, "hex");
|
|
734
|
+
return key.length === KEY_LENGTH ? key : void 0;
|
|
735
|
+
} catch {
|
|
736
|
+
return void 0;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function deriveVaultKey(passphrase, salt, iterations = PBKDF2_ITERATIONS) {
|
|
740
|
+
return (0, import_node_crypto.pbkdf2Sync)(passphrase, salt, iterations, KEY_LENGTH, "sha512");
|
|
741
|
+
}
|
|
742
|
+
function buildMetaPath(storeRoot, vault = "default") {
|
|
743
|
+
return import_node_path7.default.join(storeRoot, "vaults", vault, META_FILENAME);
|
|
117
744
|
}
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
745
|
+
function resolveSecretVaultFile(storeRoot, vault = "default") {
|
|
746
|
+
return buildMetaPath(storeRoot, vault);
|
|
747
|
+
}
|
|
748
|
+
function buildKeystorePath(storeRoot, vault = "default") {
|
|
749
|
+
return import_node_path7.default.join(storeRoot, "vaults", vault, KEYSTORE_FILENAME);
|
|
750
|
+
}
|
|
751
|
+
function buildLegacyVaultFile(storeRoot, vault = "default") {
|
|
752
|
+
return import_node_path7.default.join(storeRoot, "vaults", `${vault}.json`);
|
|
753
|
+
}
|
|
754
|
+
function buildLegacyVaultStoreRoot(storeRoot, vault = "default") {
|
|
755
|
+
return import_node_path7.default.join(storeRoot, "vaults", vault, "store");
|
|
756
|
+
}
|
|
757
|
+
function assertVaultMetadata(value, filePath) {
|
|
758
|
+
if (!isObject(value)) {
|
|
759
|
+
throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
|
|
760
|
+
}
|
|
761
|
+
if (value.version !== METADATA_VERSION || value.algorithm !== "aes-256-gcm" || value.kdf !== "pbkdf2-sha512" || typeof value.iterations !== "number" || typeof value.salt !== "string" || typeof value.createdAt !== "string" || typeof value.secretCount !== "number") {
|
|
762
|
+
throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
|
|
763
|
+
}
|
|
764
|
+
return value;
|
|
765
|
+
}
|
|
766
|
+
async function exists2(targetPath) {
|
|
767
|
+
try {
|
|
768
|
+
await (0, import_promises7.stat)(targetPath);
|
|
769
|
+
return true;
|
|
770
|
+
} catch {
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
async function detectLegacyVaultFormat(storeRoot, vault = "default") {
|
|
775
|
+
const legacyFile = buildLegacyVaultFile(storeRoot, vault);
|
|
776
|
+
const legacyStore = buildLegacyVaultStoreRoot(storeRoot, vault);
|
|
777
|
+
if (await exists2(legacyFile)) {
|
|
778
|
+
return legacyFile;
|
|
779
|
+
}
|
|
780
|
+
if (await exists2(legacyStore)) {
|
|
781
|
+
return legacyStore;
|
|
782
|
+
}
|
|
783
|
+
return void 0;
|
|
784
|
+
}
|
|
785
|
+
async function assertNoLegacyVaultFormat(storeRoot, vault = "default") {
|
|
786
|
+
const legacyPath = await detectLegacyVaultFormat(storeRoot, vault);
|
|
787
|
+
if (!legacyPath) {
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
throw new CnosSecurityError(
|
|
791
|
+
`Legacy CNOS local vault format detected for vault "${vault}" at ${legacyPath}. CNOS 1.4 requires the new keystore format. Remove and recreate the vault.`
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
function encryptPayload(payload, key) {
|
|
795
|
+
const iv = (0, import_node_crypto.randomBytes)(IV_LENGTH);
|
|
122
796
|
const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", key, iv);
|
|
123
|
-
const
|
|
797
|
+
const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
|
|
798
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
124
799
|
const tag = cipher.getAuthTag();
|
|
800
|
+
return Buffer.concat([
|
|
801
|
+
Buffer.from(Uint32Array.of(KEYSTORE_VERSION).buffer),
|
|
802
|
+
iv,
|
|
803
|
+
tag,
|
|
804
|
+
ciphertext
|
|
805
|
+
]);
|
|
806
|
+
}
|
|
807
|
+
function decryptPayload(buffer, key) {
|
|
808
|
+
if (buffer.length < 4 + IV_LENGTH + AUTH_TAG_LENGTH) {
|
|
809
|
+
throw new CnosSecurityError("Invalid CNOS local vault keystore");
|
|
810
|
+
}
|
|
811
|
+
const version = buffer.readUInt32LE(0);
|
|
812
|
+
if (version !== KEYSTORE_VERSION) {
|
|
813
|
+
throw new CnosSecurityError(`Unsupported CNOS local vault keystore version: ${version}`);
|
|
814
|
+
}
|
|
815
|
+
const ivOffset = 4;
|
|
816
|
+
const tagOffset = ivOffset + IV_LENGTH;
|
|
817
|
+
const cipherOffset = tagOffset + AUTH_TAG_LENGTH;
|
|
818
|
+
const iv = buffer.subarray(ivOffset, tagOffset);
|
|
819
|
+
const tag = buffer.subarray(tagOffset, cipherOffset);
|
|
820
|
+
const ciphertext = buffer.subarray(cipherOffset);
|
|
821
|
+
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", key, iv);
|
|
822
|
+
decipher.setAuthTag(tag);
|
|
823
|
+
try {
|
|
824
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
825
|
+
const payload = JSON.parse(plaintext);
|
|
826
|
+
if (!payload || !isObject(payload.secrets) || !isObject(payload.metadata)) {
|
|
827
|
+
throw new Error("invalid");
|
|
828
|
+
}
|
|
829
|
+
return {
|
|
830
|
+
secrets: Object.fromEntries(
|
|
831
|
+
Object.entries(payload.secrets).filter((entry) => typeof entry[1] === "string")
|
|
832
|
+
),
|
|
833
|
+
metadata: Object.fromEntries(
|
|
834
|
+
Object.entries(payload.metadata).filter(
|
|
835
|
+
(entry) => isObject(entry[1]) && typeof entry[1].createdAt === "string" && typeof entry[1].updatedAt === "string"
|
|
836
|
+
)
|
|
837
|
+
)
|
|
838
|
+
};
|
|
839
|
+
} catch {
|
|
840
|
+
throw new CnosAuthenticationError("Failed to decrypt CNOS local vault. Check vault authentication.");
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
function buildInitialPayload() {
|
|
125
844
|
return {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
salt: salt.toString("base64"),
|
|
129
|
-
iv: iv.toString("base64"),
|
|
130
|
-
tag: tag.toString("base64"),
|
|
131
|
-
ciphertext: ciphertext.toString("base64")
|
|
845
|
+
secrets: {},
|
|
846
|
+
metadata: {}
|
|
132
847
|
};
|
|
133
848
|
}
|
|
134
|
-
async function
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
await (0,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
name: normalizedVault,
|
|
141
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
142
|
-
verifier: encryptDocument(`cnos-vault:${normalizedVault}`, passphrase)
|
|
143
|
-
};
|
|
144
|
-
await (0, import_promises6.writeFile)(filePath, JSON.stringify(document, null, 2), "utf8");
|
|
145
|
-
return filePath;
|
|
849
|
+
async function writeVaultFiles(storeRoot, vault, meta, payload, key) {
|
|
850
|
+
const metaPath = buildMetaPath(storeRoot, vault);
|
|
851
|
+
const keystorePath = buildKeystorePath(storeRoot, vault);
|
|
852
|
+
await (0, import_promises7.mkdir)(import_node_path7.default.dirname(metaPath), { recursive: true });
|
|
853
|
+
await (0, import_promises7.writeFile)(metaPath, stringifyYaml(meta), "utf8");
|
|
854
|
+
await (0, import_promises7.writeFile)(keystorePath, encryptPayload(payload, key));
|
|
146
855
|
}
|
|
147
|
-
async function
|
|
148
|
-
|
|
149
|
-
const
|
|
856
|
+
async function readVaultMetadata(storeRoot, vault = "default") {
|
|
857
|
+
await assertNoLegacyVaultFormat(storeRoot, vault);
|
|
858
|
+
const metaPath = buildMetaPath(storeRoot, vault);
|
|
150
859
|
try {
|
|
151
|
-
await (0,
|
|
152
|
-
return
|
|
860
|
+
const source = await (0, import_promises7.readFile)(metaPath, "utf8");
|
|
861
|
+
return assertVaultMetadata(parseYaml(source), metaPath);
|
|
153
862
|
} catch (error) {
|
|
154
|
-
if (error.code
|
|
155
|
-
|
|
863
|
+
if (error.code === "ENOENT") {
|
|
864
|
+
return void 0;
|
|
156
865
|
}
|
|
866
|
+
throw error;
|
|
157
867
|
}
|
|
158
|
-
return createSecretVault(storeRoot, normalizedVault, passphrase);
|
|
159
868
|
}
|
|
160
869
|
async function listSecretVaults(storeRoot) {
|
|
161
|
-
const vaultRoot =
|
|
870
|
+
const vaultRoot = import_node_path7.default.join(storeRoot, "vaults");
|
|
162
871
|
try {
|
|
163
|
-
const entries = await (0,
|
|
164
|
-
|
|
872
|
+
const entries = await (0, import_promises7.readdir)(vaultRoot, { withFileTypes: true });
|
|
873
|
+
const vaults = await Promise.all(
|
|
874
|
+
entries.filter((entry) => entry.isDirectory()).map(async (entry) => await exists2(import_node_path7.default.join(vaultRoot, entry.name, META_FILENAME)) ? entry.name : void 0)
|
|
875
|
+
);
|
|
876
|
+
return vaults.filter((value) => Boolean(value)).sort((left, right) => left.localeCompare(right));
|
|
165
877
|
} catch {
|
|
166
878
|
return [];
|
|
167
879
|
}
|
|
168
880
|
}
|
|
169
|
-
async function
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
881
|
+
async function createSecretVault(storeRoot, vault, passphrase) {
|
|
882
|
+
const normalizedVault = vault.trim() || "default";
|
|
883
|
+
await assertNoLegacyVaultFormat(storeRoot, normalizedVault);
|
|
884
|
+
const salt = (0, import_node_crypto.randomBytes)(SALT_LENGTH);
|
|
885
|
+
const key = deriveVaultKey(passphrase, salt, PBKDF2_ITERATIONS);
|
|
886
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
887
|
+
const meta = {
|
|
888
|
+
version: METADATA_VERSION,
|
|
889
|
+
algorithm: "aes-256-gcm",
|
|
890
|
+
kdf: "pbkdf2-sha512",
|
|
891
|
+
iterations: PBKDF2_ITERATIONS,
|
|
892
|
+
salt: salt.toString("base64"),
|
|
893
|
+
createdAt,
|
|
894
|
+
secretCount: 0
|
|
895
|
+
};
|
|
896
|
+
await writeVaultFiles(storeRoot, normalizedVault, meta, buildInitialPayload(), key);
|
|
897
|
+
return buildMetaPath(storeRoot, normalizedVault);
|
|
898
|
+
}
|
|
899
|
+
async function ensureSecretVault(storeRoot, vault, passphrase) {
|
|
900
|
+
const normalizedVault = vault.trim() || "default";
|
|
901
|
+
const meta = await readVaultMetadata(storeRoot, normalizedVault);
|
|
902
|
+
if (meta) {
|
|
903
|
+
return buildMetaPath(storeRoot, normalizedVault);
|
|
904
|
+
}
|
|
905
|
+
return createSecretVault(storeRoot, normalizedVault, passphrase);
|
|
906
|
+
}
|
|
907
|
+
function resolveConfiguredVaultPassphrase(definition, vault = "default", processEnv = process.env) {
|
|
908
|
+
if (definition?.provider !== "local") {
|
|
909
|
+
return void 0;
|
|
910
|
+
}
|
|
911
|
+
const configuredSources = definition.auth?.passphrase?.from ?? [];
|
|
912
|
+
for (const source of configuredSources) {
|
|
913
|
+
if (source.startsWith("env:")) {
|
|
914
|
+
const value = processEnv[source.slice(4)];
|
|
915
|
+
if (value) {
|
|
916
|
+
return value;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return resolveSecretPassphrase(vault, processEnv);
|
|
921
|
+
}
|
|
922
|
+
async function resolveVaultAccessKey(storeRoot, definition, vault = "default", processEnv = process.env) {
|
|
923
|
+
if (definition?.provider !== "local") {
|
|
924
|
+
return definition?.provider === "github-secrets" ? {
|
|
925
|
+
method: definition.auth?.method ?? "environment",
|
|
926
|
+
...definition?.auth?.config ? { config: definition.auth.config } : {}
|
|
927
|
+
} : void 0;
|
|
928
|
+
}
|
|
929
|
+
const sessionKey = await resolveVaultSessionKey(vault, processEnv);
|
|
930
|
+
if (sessionKey) {
|
|
931
|
+
return {
|
|
932
|
+
derivedKey: sessionKey,
|
|
933
|
+
method: "keychain",
|
|
934
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
const passphrase = resolveConfiguredVaultPassphrase(definition, vault, processEnv);
|
|
938
|
+
if (passphrase) {
|
|
939
|
+
return {
|
|
940
|
+
passphrase,
|
|
941
|
+
method: "passphrase",
|
|
942
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
const metadata = await readVaultMetadata(storeRoot, vault);
|
|
946
|
+
if (!metadata) {
|
|
947
|
+
return void 0;
|
|
948
|
+
}
|
|
949
|
+
throw new CnosAuthenticationError(
|
|
950
|
+
`Cannot authenticate to vault "${vault}". Set ${getVaultPassphraseEnvVar(vault)} or run cnos vault auth ${vault}.`
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
async function loadVaultPayload(storeRoot, vault, auth) {
|
|
954
|
+
const meta = await readVaultMetadata(storeRoot, vault);
|
|
955
|
+
if (!meta) {
|
|
956
|
+
throw new CnosManifestError(`Missing CNOS vault metadata for "${vault}"`);
|
|
957
|
+
}
|
|
958
|
+
const salt = Buffer.from(meta.salt, "base64");
|
|
959
|
+
const key = auth.derivedKey ?? (auth.passphrase ? deriveVaultKey(auth.passphrase, salt, meta.iterations) : void 0);
|
|
960
|
+
if (!key) {
|
|
961
|
+
throw new CnosAuthenticationError(`Vault "${vault}" requires authentication before access.`);
|
|
962
|
+
}
|
|
963
|
+
const buffer = await (0, import_promises7.readFile)(buildKeystorePath(storeRoot, vault));
|
|
964
|
+
return {
|
|
965
|
+
meta,
|
|
966
|
+
payload: decryptPayload(buffer, key),
|
|
967
|
+
key
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
async function writeLocalSecret(storeRoot, ref, value, authOrPassphrase, vault = "default") {
|
|
971
|
+
const auth = typeof authOrPassphrase === "string" ? {
|
|
972
|
+
passphrase: authOrPassphrase,
|
|
973
|
+
method: "passphrase"
|
|
974
|
+
} : authOrPassphrase;
|
|
975
|
+
if (auth.passphrase) {
|
|
976
|
+
await ensureSecretVault(storeRoot, vault, auth.passphrase);
|
|
977
|
+
} else {
|
|
978
|
+
const meta2 = await readVaultMetadata(storeRoot, vault);
|
|
979
|
+
if (!meta2) {
|
|
980
|
+
throw new CnosAuthenticationError(`Vault "${vault}" requires passphrase-based authentication for initial creation.`);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
|
|
984
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
985
|
+
const existing = payload.metadata[ref];
|
|
986
|
+
payload.secrets[ref] = value;
|
|
987
|
+
payload.metadata[ref] = {
|
|
988
|
+
createdAt: existing?.createdAt ?? now,
|
|
989
|
+
updatedAt: now
|
|
990
|
+
};
|
|
991
|
+
const nextMeta = {
|
|
992
|
+
...meta,
|
|
993
|
+
secretCount: Object.keys(payload.secrets).length
|
|
994
|
+
};
|
|
995
|
+
await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
|
|
996
|
+
return buildKeystorePath(storeRoot, vault);
|
|
997
|
+
}
|
|
998
|
+
async function deleteLocalSecret(storeRoot, ref, auth, vault = "default") {
|
|
999
|
+
const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
|
|
1000
|
+
if (!(ref in payload.secrets)) {
|
|
1001
|
+
return false;
|
|
1002
|
+
}
|
|
1003
|
+
delete payload.secrets[ref];
|
|
1004
|
+
delete payload.metadata[ref];
|
|
1005
|
+
const nextMeta = {
|
|
1006
|
+
...meta,
|
|
1007
|
+
secretCount: Object.keys(payload.secrets).length
|
|
1008
|
+
};
|
|
1009
|
+
await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
|
|
1010
|
+
return true;
|
|
1011
|
+
}
|
|
1012
|
+
async function readLocalSecret(storeRoot, ref, auth, vault = "default") {
|
|
1013
|
+
const { payload } = await loadVaultPayload(storeRoot, vault, auth);
|
|
1014
|
+
const value = payload.secrets[ref];
|
|
1015
|
+
if (value === void 0) {
|
|
1016
|
+
throw new CnosManifestError(`Missing local secret ref "${ref}" in vault "${vault}"`);
|
|
1017
|
+
}
|
|
1018
|
+
return value;
|
|
1019
|
+
}
|
|
1020
|
+
async function listLocalSecrets(storeRoot, auth, vault = "default") {
|
|
1021
|
+
const { payload } = await loadVaultPayload(storeRoot, vault, auth);
|
|
1022
|
+
return Object.keys(payload.secrets).sort((left, right) => left.localeCompare(right));
|
|
1023
|
+
}
|
|
1024
|
+
function resolveVaultDefinition(vaults, vault = "default") {
|
|
1025
|
+
const definition = vaults?.[vault];
|
|
1026
|
+
const provider = definition?.provider ?? "local";
|
|
1027
|
+
return {
|
|
1028
|
+
name: vault,
|
|
1029
|
+
provider,
|
|
1030
|
+
...definition?.auth ? { auth: definition.auth } : {},
|
|
1031
|
+
...definition?.mapping ? { mapping: definition.mapping } : {},
|
|
1032
|
+
requiresAuthentication: provider === "local"
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
async function removeLocalVaultFiles(storeRoot, vault = "default") {
|
|
1036
|
+
await (0, import_promises7.rm)(import_node_path7.default.join(storeRoot, "vaults", vault), { recursive: true, force: true });
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// ../core/src/secrets/auditLog.ts
|
|
1040
|
+
async function appendAuditEvent(event, processEnv = process.env) {
|
|
1041
|
+
const auditFile = processEnv.CNOS_AUDIT_FILE ?? import_node_path8.default.join(resolveSecretStoreRoot(processEnv), "audit", "access.log");
|
|
1042
|
+
await (0, import_promises8.mkdir)(import_node_path8.default.dirname(auditFile), { recursive: true });
|
|
1043
|
+
await (0, import_promises8.appendFile)(
|
|
1044
|
+
auditFile,
|
|
1045
|
+
`${JSON.stringify({
|
|
1046
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1047
|
+
...event
|
|
1048
|
+
})}
|
|
1049
|
+
`,
|
|
1050
|
+
"utf8"
|
|
1051
|
+
);
|
|
175
1052
|
}
|
|
176
1053
|
|
|
1054
|
+
// ../core/src/secrets/providers/github.ts
|
|
1055
|
+
var GithubSecretsVaultProvider = class {
|
|
1056
|
+
constructor(vaultId, definition, processEnv = process.env) {
|
|
1057
|
+
this.vaultId = vaultId;
|
|
1058
|
+
this.definition = definition;
|
|
1059
|
+
this.processEnv = processEnv;
|
|
1060
|
+
}
|
|
1061
|
+
vaultId;
|
|
1062
|
+
definition;
|
|
1063
|
+
processEnv;
|
|
1064
|
+
authenticated = false;
|
|
1065
|
+
async authenticate(_authConfig) {
|
|
1066
|
+
void _authConfig;
|
|
1067
|
+
this.authenticated = true;
|
|
1068
|
+
}
|
|
1069
|
+
isAuthenticated() {
|
|
1070
|
+
return this.authenticated;
|
|
1071
|
+
}
|
|
1072
|
+
resolveEnvVar(ref) {
|
|
1073
|
+
if (this.processEnv[ref] !== void 0) {
|
|
1074
|
+
return ref;
|
|
1075
|
+
}
|
|
1076
|
+
return Object.entries(this.definition.mapping ?? {}).find(([, logicalRef]) => logicalRef === ref)?.[0];
|
|
1077
|
+
}
|
|
1078
|
+
async batchGet(refs) {
|
|
1079
|
+
this.authenticated = true;
|
|
1080
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1081
|
+
for (const ref of Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right))) {
|
|
1082
|
+
const envVar = this.resolveEnvVar(ref);
|
|
1083
|
+
const value = envVar ? this.processEnv[envVar] : void 0;
|
|
1084
|
+
if (value !== void 0) {
|
|
1085
|
+
resolved.set(ref, value);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
return resolved;
|
|
1089
|
+
}
|
|
1090
|
+
async get(ref) {
|
|
1091
|
+
const envVar = this.resolveEnvVar(ref);
|
|
1092
|
+
this.authenticated = true;
|
|
1093
|
+
return envVar ? this.processEnv[envVar] : void 0;
|
|
1094
|
+
}
|
|
1095
|
+
async set(ref, value) {
|
|
1096
|
+
void ref;
|
|
1097
|
+
void value;
|
|
1098
|
+
throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be written by CNOS.`);
|
|
1099
|
+
}
|
|
1100
|
+
async delete(ref) {
|
|
1101
|
+
void ref;
|
|
1102
|
+
throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be mutated by CNOS.`);
|
|
1103
|
+
}
|
|
1104
|
+
async list() {
|
|
1105
|
+
return Object.values(this.definition.mapping ?? {}).sort((left, right) => left.localeCompare(right));
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
// ../core/src/secrets/providers/local.ts
|
|
1110
|
+
var LocalSecretVaultProvider = class _LocalSecretVaultProvider {
|
|
1111
|
+
constructor(vaultId, definition, processEnv = process.env, storeRoot = resolveSecretStoreRoot(processEnv)) {
|
|
1112
|
+
this.vaultId = vaultId;
|
|
1113
|
+
this.processEnv = processEnv;
|
|
1114
|
+
this.storeRoot = storeRoot;
|
|
1115
|
+
this.definition = definition;
|
|
1116
|
+
}
|
|
1117
|
+
vaultId;
|
|
1118
|
+
processEnv;
|
|
1119
|
+
storeRoot;
|
|
1120
|
+
authConfig;
|
|
1121
|
+
definition;
|
|
1122
|
+
static fromVaults(vaults, vaultId, processEnv) {
|
|
1123
|
+
const definition = resolveVaultDefinition(vaults, vaultId);
|
|
1124
|
+
return new _LocalSecretVaultProvider(vaultId, definition, processEnv);
|
|
1125
|
+
}
|
|
1126
|
+
async authenticate(authConfig) {
|
|
1127
|
+
this.authConfig = authConfig;
|
|
1128
|
+
await this.list();
|
|
1129
|
+
}
|
|
1130
|
+
isAuthenticated() {
|
|
1131
|
+
return Boolean(this.authConfig);
|
|
1132
|
+
}
|
|
1133
|
+
async requireAuth() {
|
|
1134
|
+
if (this.authConfig) {
|
|
1135
|
+
return this.authConfig;
|
|
1136
|
+
}
|
|
1137
|
+
const resolved = await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
|
|
1138
|
+
if (!resolved) {
|
|
1139
|
+
throw new CnosAuthenticationError(
|
|
1140
|
+
`Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
this.authConfig = resolved;
|
|
1144
|
+
return resolved;
|
|
1145
|
+
}
|
|
1146
|
+
async batchGet(refs) {
|
|
1147
|
+
const auth = await this.requireAuth();
|
|
1148
|
+
const entries = await Promise.all(
|
|
1149
|
+
Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right)).map(async (ref) => [ref, await readLocalSecret(this.storeRoot, ref, auth, this.vaultId)])
|
|
1150
|
+
);
|
|
1151
|
+
return new Map(entries);
|
|
1152
|
+
}
|
|
1153
|
+
async get(ref) {
|
|
1154
|
+
const auth = await this.requireAuth();
|
|
1155
|
+
try {
|
|
1156
|
+
return await readLocalSecret(this.storeRoot, ref, auth, this.vaultId);
|
|
1157
|
+
} catch {
|
|
1158
|
+
return void 0;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
async set(ref, value) {
|
|
1162
|
+
const auth = await this.requireAuth();
|
|
1163
|
+
await writeLocalSecret(this.storeRoot, ref, value, auth, this.vaultId);
|
|
1164
|
+
await appendAuditEvent(
|
|
1165
|
+
{
|
|
1166
|
+
action: "write",
|
|
1167
|
+
vault: this.vaultId,
|
|
1168
|
+
ref,
|
|
1169
|
+
caller: "cli"
|
|
1170
|
+
},
|
|
1171
|
+
this.processEnv
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
async delete(ref) {
|
|
1175
|
+
const auth = await this.requireAuth();
|
|
1176
|
+
await deleteLocalSecret(this.storeRoot, ref, auth, this.vaultId);
|
|
1177
|
+
await appendAuditEvent(
|
|
1178
|
+
{
|
|
1179
|
+
action: "delete",
|
|
1180
|
+
vault: this.vaultId,
|
|
1181
|
+
ref,
|
|
1182
|
+
caller: "cli"
|
|
1183
|
+
},
|
|
1184
|
+
this.processEnv
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
async list() {
|
|
1188
|
+
const auth = this.authConfig ?? await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
|
|
1189
|
+
if (!auth) {
|
|
1190
|
+
throw new CnosAuthenticationError(
|
|
1191
|
+
`Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
this.authConfig = auth;
|
|
1195
|
+
return listLocalSecrets(this.storeRoot, auth, this.vaultId);
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
// ../core/src/secrets/providers/registry.ts
|
|
1200
|
+
function createSecretVaultProvider(vaultId, definition, processEnv) {
|
|
1201
|
+
if (definition.provider === "local") {
|
|
1202
|
+
return new LocalSecretVaultProvider(vaultId, definition, processEnv);
|
|
1203
|
+
}
|
|
1204
|
+
if (definition.provider === "github-secrets") {
|
|
1205
|
+
return new GithubSecretsVaultProvider(vaultId, definition, processEnv);
|
|
1206
|
+
}
|
|
1207
|
+
throw new CnosManifestError(`Unsupported vault provider: ${definition.provider}`);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// ../core/src/secrets/prompt.ts
|
|
1211
|
+
var import_node_readline = __toESM(require("readline"), 1);
|
|
1212
|
+
var import_node_stream = require("stream");
|
|
1213
|
+
async function promptHidden(message) {
|
|
1214
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1215
|
+
return void 0;
|
|
1216
|
+
}
|
|
1217
|
+
const mutableStdout = new WritableMask();
|
|
1218
|
+
const rl = import_node_readline.default.createInterface({
|
|
1219
|
+
input: process.stdin,
|
|
1220
|
+
output: mutableStdout,
|
|
1221
|
+
terminal: true
|
|
1222
|
+
});
|
|
1223
|
+
try {
|
|
1224
|
+
mutableStdout.muted = true;
|
|
1225
|
+
const value = await new Promise((resolve) => {
|
|
1226
|
+
rl.question(message, resolve);
|
|
1227
|
+
});
|
|
1228
|
+
process.stdout.write("\n");
|
|
1229
|
+
return value;
|
|
1230
|
+
} finally {
|
|
1231
|
+
rl.close();
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
var WritableMask = class extends import_node_stream.Writable {
|
|
1235
|
+
muted = false;
|
|
1236
|
+
_write(chunk, _encoding, callback) {
|
|
1237
|
+
if (!this.muted) {
|
|
1238
|
+
process.stdout.write(chunk);
|
|
1239
|
+
}
|
|
1240
|
+
callback();
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
// ../core/src/secrets/resolveAuth.ts
|
|
1245
|
+
function toAuthError(vaultId, sources) {
|
|
1246
|
+
return new CnosAuthenticationError(
|
|
1247
|
+
`Cannot authenticate to vault "${vaultId}". Tried: ${sources.join(", ")}. Set ${getVaultPassphraseEnvVar(vaultId)} or run cnos vault auth ${vaultId}.`
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
async function resolveVaultAuth(vaultId, definition, processEnv = process.env) {
|
|
1251
|
+
const sessionKey = await resolveVaultSessionKey(vaultId, processEnv);
|
|
1252
|
+
if (sessionKey) {
|
|
1253
|
+
return {
|
|
1254
|
+
derivedKey: sessionKey,
|
|
1255
|
+
method: "keychain",
|
|
1256
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
if (definition.provider === "github-secrets") {
|
|
1260
|
+
return {
|
|
1261
|
+
method: definition.auth?.method ?? "environment",
|
|
1262
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
const sources = definition.auth?.passphrase?.from ?? [getVaultPassphraseEnvVar(vaultId)];
|
|
1266
|
+
for (const source of sources) {
|
|
1267
|
+
if (source.startsWith("env:")) {
|
|
1268
|
+
const value = processEnv[source.slice(4)];
|
|
1269
|
+
if (value) {
|
|
1270
|
+
return {
|
|
1271
|
+
passphrase: value,
|
|
1272
|
+
method: "passphrase",
|
|
1273
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
if (source.startsWith("keychain:")) {
|
|
1278
|
+
const value = await readKeychain(source.slice("keychain:".length));
|
|
1279
|
+
if (value) {
|
|
1280
|
+
return {
|
|
1281
|
+
derivedKey: Buffer.from(value, "hex"),
|
|
1282
|
+
method: "keychain",
|
|
1283
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
if (source === "prompt") {
|
|
1288
|
+
const value = await promptHidden(`Enter passphrase for vault "${vaultId}": `);
|
|
1289
|
+
if (value) {
|
|
1290
|
+
return {
|
|
1291
|
+
passphrase: value,
|
|
1292
|
+
method: "passphrase",
|
|
1293
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
const fallback = resolveSecretPassphrase(vaultId, processEnv);
|
|
1299
|
+
if (fallback) {
|
|
1300
|
+
return {
|
|
1301
|
+
passphrase: fallback,
|
|
1302
|
+
method: "passphrase",
|
|
1303
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
throw toAuthError(vaultId, [getVaultSessionKeyEnvVar(vaultId), ...sources]);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// ../core/src/runtime/dump.ts
|
|
1310
|
+
var import_promises9 = require("fs/promises");
|
|
1311
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
1312
|
+
|
|
177
1313
|
// ../core/src/utils/envNaming.ts
|
|
178
1314
|
function normalizeMappingConfig(config = {}) {
|
|
179
1315
|
return {
|
|
@@ -184,8 +1320,8 @@ function normalizeMappingConfig(config = {}) {
|
|
|
184
1320
|
function toScreamingSnakeSegment(segment) {
|
|
185
1321
|
return segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
186
1322
|
}
|
|
187
|
-
function toScreamingSnake(
|
|
188
|
-
return
|
|
1323
|
+
function toScreamingSnake(path13) {
|
|
1324
|
+
return path13.split(".").map((segment) => toScreamingSnakeSegment(segment)).filter(Boolean).join("_");
|
|
189
1325
|
}
|
|
190
1326
|
function logicalKeyToEnvVar(key, config = {}) {
|
|
191
1327
|
const normalized = normalizeMappingConfig(config);
|
|
@@ -205,10 +1341,6 @@ function logicalKeyToEnvVar(key, config = {}) {
|
|
|
205
1341
|
return void 0;
|
|
206
1342
|
}
|
|
207
1343
|
|
|
208
|
-
// ../core/src/runtime/dump.ts
|
|
209
|
-
var import_promises7 = require("fs/promises");
|
|
210
|
-
var import_node_path7 = __toESM(require("path"), 1);
|
|
211
|
-
|
|
212
1344
|
// ../core/src/utils/flatten.ts
|
|
213
1345
|
function flattenObject(value, prefix = "") {
|
|
214
1346
|
return Object.entries(value).reduce((accumulator, [key, nestedValue]) => {
|
|
@@ -235,7 +1367,8 @@ function validateEnvMappingCollisions(manifest, graph) {
|
|
|
235
1367
|
]);
|
|
236
1368
|
const collisions = /* @__PURE__ */ new Map();
|
|
237
1369
|
for (const key of candidates) {
|
|
238
|
-
|
|
1370
|
+
const definition = getNamespaceDefinition(manifest, key);
|
|
1371
|
+
if (definition.kind !== "data") {
|
|
239
1372
|
continue;
|
|
240
1373
|
}
|
|
241
1374
|
const envVar = logicalKeyToEnvVar(key, manifest.envMapping) ?? (key.startsWith("value.") || key.startsWith("secret.") ? fallbackLogicalKeyToEnvVar(key) : void 0);
|
|
@@ -254,11 +1387,7 @@ function validateEnvMappingCollisions(manifest, graph) {
|
|
|
254
1387
|
|
|
255
1388
|
// ../core/src/validation/publicSafety.ts
|
|
256
1389
|
function validatePublicSafety(manifest) {
|
|
257
|
-
return manifest.public.promote.
|
|
258
|
-
code: "public.invalid-promotion",
|
|
259
|
-
key,
|
|
260
|
-
message: `public.promote may only include value.* keys: ${key}`
|
|
261
|
-
}));
|
|
1390
|
+
return manifest.public.promote.map((key) => validateProjectionIssue(manifest, key, "public")).filter((issue) => Boolean(issue));
|
|
262
1391
|
}
|
|
263
1392
|
|
|
264
1393
|
// ../core/src/validation/workspaceSafety.ts
|
|
@@ -323,17 +1452,738 @@ async function validateRuntime(runtime) {
|
|
|
323
1452
|
results
|
|
324
1453
|
};
|
|
325
1454
|
}
|
|
1455
|
+
|
|
1456
|
+
// src/runtime/bootstrap.ts
|
|
1457
|
+
var import_node_crypto2 = require("crypto");
|
|
1458
|
+
var CNOS_GRAPH_ENV_VAR = "__CNOS_GRAPH__";
|
|
1459
|
+
var CNOS_SECRET_PAYLOAD_ENV_VAR = "__CNOS_SECRET_PAYLOAD__";
|
|
1460
|
+
var CNOS_SESSION_KEY_ENV_VAR = "__CNOS_SESSION_KEY__";
|
|
1461
|
+
function serializeRuntimeGraph(graph) {
|
|
1462
|
+
const payload = {
|
|
1463
|
+
entries: Array.from(graph.entries.values()),
|
|
1464
|
+
profile: graph.profile,
|
|
1465
|
+
resolvedAt: graph.resolvedAt,
|
|
1466
|
+
profileSource: graph.profileSource,
|
|
1467
|
+
workspace: graph.workspace
|
|
1468
|
+
};
|
|
1469
|
+
return JSON.stringify(payload);
|
|
1470
|
+
}
|
|
1471
|
+
function deserializeRuntimeGraph(source) {
|
|
1472
|
+
const payload = JSON.parse(source);
|
|
1473
|
+
if (!payload || !Array.isArray(payload.entries) || typeof payload.profile !== "string" || typeof payload.resolvedAt !== "string" || !payload.profileSource || !payload.workspace || typeof payload.workspace.workspaceId !== "string" || !Array.isArray(payload.workspace.workspaceChain) || !Array.isArray(payload.workspace.workspaceRoots)) {
|
|
1474
|
+
throw new Error("Invalid CNOS runtime bootstrap payload");
|
|
1475
|
+
}
|
|
1476
|
+
return {
|
|
1477
|
+
entries: new Map(
|
|
1478
|
+
payload.entries.map((entry) => [
|
|
1479
|
+
entry.key,
|
|
1480
|
+
{
|
|
1481
|
+
key: entry.key,
|
|
1482
|
+
value: entry.value,
|
|
1483
|
+
namespace: entry.namespace,
|
|
1484
|
+
winner: entry.winner,
|
|
1485
|
+
overridden: entry.overridden ?? []
|
|
1486
|
+
}
|
|
1487
|
+
])
|
|
1488
|
+
),
|
|
1489
|
+
profile: payload.profile,
|
|
1490
|
+
resolvedAt: payload.resolvedAt,
|
|
1491
|
+
profileSource: payload.profileSource,
|
|
1492
|
+
workspace: payload.workspace
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
function decryptSecretPayload(serialized, sessionKey) {
|
|
1496
|
+
const payload = JSON.parse(serialized);
|
|
1497
|
+
if (!payload || typeof payload.iv !== "string" || typeof payload.tag !== "string" || typeof payload.ciphertext !== "string") {
|
|
1498
|
+
throw new Error("Invalid CNOS secret payload");
|
|
1499
|
+
}
|
|
1500
|
+
const key = Buffer.from(sessionKey, "hex");
|
|
1501
|
+
const iv = Buffer.from(payload.iv, "base64");
|
|
1502
|
+
const tag = Buffer.from(payload.tag, "base64");
|
|
1503
|
+
const ciphertext = Buffer.from(payload.ciphertext, "base64");
|
|
1504
|
+
const decipher = (0, import_node_crypto2.createDecipheriv)("aes-256-gcm", key, iv);
|
|
1505
|
+
decipher.setAuthTag(tag);
|
|
1506
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
1507
|
+
return JSON.parse(plaintext);
|
|
1508
|
+
}
|
|
1509
|
+
function serializeSecretPayload(values) {
|
|
1510
|
+
const key = (0, import_node_crypto2.randomBytes)(32);
|
|
1511
|
+
const iv = (0, import_node_crypto2.randomBytes)(12);
|
|
1512
|
+
const cipher = (0, import_node_crypto2.createCipheriv)("aes-256-gcm", key, iv);
|
|
1513
|
+
const ciphertext = Buffer.concat([cipher.update(JSON.stringify(values), "utf8"), cipher.final()]);
|
|
1514
|
+
const tag = cipher.getAuthTag();
|
|
1515
|
+
return {
|
|
1516
|
+
payload: JSON.stringify({
|
|
1517
|
+
iv: iv.toString("base64"),
|
|
1518
|
+
tag: tag.toString("base64"),
|
|
1519
|
+
ciphertext: ciphertext.toString("base64")
|
|
1520
|
+
}),
|
|
1521
|
+
sessionKey: key.toString("hex")
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
function readRuntimeGraphFromEnv(processEnv = process.env) {
|
|
1525
|
+
const serialized = processEnv[CNOS_GRAPH_ENV_VAR];
|
|
1526
|
+
if (!serialized) {
|
|
1527
|
+
return void 0;
|
|
1528
|
+
}
|
|
1529
|
+
const graph = deserializeRuntimeGraph(serialized);
|
|
1530
|
+
const secretPayload = processEnv[CNOS_SECRET_PAYLOAD_ENV_VAR];
|
|
1531
|
+
const sessionKey = processEnv[CNOS_SESSION_KEY_ENV_VAR];
|
|
1532
|
+
if (secretPayload && sessionKey) {
|
|
1533
|
+
const decrypted = decryptSecretPayload(secretPayload, sessionKey);
|
|
1534
|
+
for (const [key, value] of Object.entries(decrypted)) {
|
|
1535
|
+
const entry = graph.entries.get(key);
|
|
1536
|
+
if (entry) {
|
|
1537
|
+
entry.value = value;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
return graph;
|
|
1542
|
+
}
|
|
1543
|
+
function graphRequiresSecretHydration(graph) {
|
|
1544
|
+
return Array.from(graph.entries.values()).some((entry) => entry.namespace === "secret" && isSecretReference(entry.value));
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// src/codegen/generateTypes.ts
|
|
1548
|
+
function toPascalCase(value) {
|
|
1549
|
+
return value.split(/[^A-Za-z0-9]+/).filter((segment) => segment.length > 0).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
|
|
1550
|
+
}
|
|
1551
|
+
function mapSchemaType(rule) {
|
|
1552
|
+
switch (rule?.type) {
|
|
1553
|
+
case "number":
|
|
1554
|
+
return "number";
|
|
1555
|
+
case "string":
|
|
1556
|
+
return "string";
|
|
1557
|
+
case "boolean":
|
|
1558
|
+
return "boolean";
|
|
1559
|
+
case "object":
|
|
1560
|
+
return "Record<string, unknown>";
|
|
1561
|
+
case "array":
|
|
1562
|
+
return "unknown[]";
|
|
1563
|
+
default:
|
|
1564
|
+
return "unknown";
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
function isOptional(rule) {
|
|
1568
|
+
return !(rule?.required ?? false) && rule?.default === void 0;
|
|
1569
|
+
}
|
|
1570
|
+
function buildNamespaceInterfaces(schema) {
|
|
1571
|
+
const namespaceGroups = /* @__PURE__ */ new Map();
|
|
1572
|
+
for (const [logicalKey, rule] of Object.entries(schema).sort(([left], [right]) => left.localeCompare(right))) {
|
|
1573
|
+
const separatorIndex = logicalKey.indexOf(".");
|
|
1574
|
+
if (separatorIndex <= 0 || separatorIndex >= logicalKey.length - 1) {
|
|
1575
|
+
continue;
|
|
1576
|
+
}
|
|
1577
|
+
const namespace = logicalKey.slice(0, separatorIndex);
|
|
1578
|
+
const path13 = logicalKey.slice(separatorIndex + 1);
|
|
1579
|
+
const existing = namespaceGroups.get(namespace) ?? [];
|
|
1580
|
+
existing.push({
|
|
1581
|
+
key: path13,
|
|
1582
|
+
rule
|
|
1583
|
+
});
|
|
1584
|
+
namespaceGroups.set(namespace, existing);
|
|
1585
|
+
}
|
|
1586
|
+
const orderedNamespaces = Array.from(namespaceGroups.keys()).sort((left, right) => {
|
|
1587
|
+
if (left === "value") {
|
|
1588
|
+
return -1;
|
|
1589
|
+
}
|
|
1590
|
+
if (right === "value") {
|
|
1591
|
+
return 1;
|
|
1592
|
+
}
|
|
1593
|
+
if (left === "secret") {
|
|
1594
|
+
return -1;
|
|
1595
|
+
}
|
|
1596
|
+
if (right === "secret") {
|
|
1597
|
+
return 1;
|
|
1598
|
+
}
|
|
1599
|
+
return left.localeCompare(right);
|
|
1600
|
+
});
|
|
1601
|
+
if (orderedNamespaces.length === 0) {
|
|
1602
|
+
return [
|
|
1603
|
+
"export interface CnosValueConfig {}",
|
|
1604
|
+
"export interface CnosSecretConfig {}",
|
|
1605
|
+
"",
|
|
1606
|
+
"export interface CnosConfig {",
|
|
1607
|
+
" value: CnosValueConfig;",
|
|
1608
|
+
" secret: CnosSecretConfig;",
|
|
1609
|
+
"}"
|
|
1610
|
+
];
|
|
1611
|
+
}
|
|
1612
|
+
const blocks = [];
|
|
1613
|
+
for (const namespace of orderedNamespaces) {
|
|
1614
|
+
const entries = namespaceGroups.get(namespace) ?? [];
|
|
1615
|
+
const interfaceName = namespace === "value" ? "CnosValueConfig" : namespace === "secret" ? "CnosSecretConfig" : `Cnos${toPascalCase(namespace)}Config`;
|
|
1616
|
+
blocks.push(`export interface ${interfaceName} {`);
|
|
1617
|
+
for (const entry of entries) {
|
|
1618
|
+
const optional = isOptional(entry.rule) ? "?" : "";
|
|
1619
|
+
blocks.push(` "${entry.key}"${optional}: ${mapSchemaType(entry.rule)};`);
|
|
1620
|
+
}
|
|
1621
|
+
blocks.push("}");
|
|
1622
|
+
blocks.push("");
|
|
1623
|
+
}
|
|
1624
|
+
const configEntries = orderedNamespaces.map((namespace) => {
|
|
1625
|
+
const interfaceName = namespace === "value" ? "CnosValueConfig" : namespace === "secret" ? "CnosSecretConfig" : `Cnos${toPascalCase(namespace)}Config`;
|
|
1626
|
+
return ` ${namespace}: ${interfaceName};`;
|
|
1627
|
+
});
|
|
1628
|
+
blocks.push("export interface CnosConfig {");
|
|
1629
|
+
blocks.push(...configEntries);
|
|
1630
|
+
blocks.push("}");
|
|
1631
|
+
if (!orderedNamespaces.includes("value")) {
|
|
1632
|
+
blocks.push("");
|
|
1633
|
+
blocks.push("export interface CnosValueConfig {}");
|
|
1634
|
+
}
|
|
1635
|
+
if (!orderedNamespaces.includes("secret")) {
|
|
1636
|
+
blocks.push("");
|
|
1637
|
+
blocks.push("export interface CnosSecretConfig {}");
|
|
1638
|
+
}
|
|
1639
|
+
return blocks;
|
|
1640
|
+
}
|
|
1641
|
+
function generateCodegenContent(manifest, sourcePath, typeModuleImport = "./cnos") {
|
|
1642
|
+
const schema = manifest.schema ?? {};
|
|
1643
|
+
const hasSchema = Object.keys(schema).length > 0;
|
|
1644
|
+
const interfaceBlocks = buildNamespaceInterfaces(schema);
|
|
1645
|
+
const typesLines = [
|
|
1646
|
+
"// Auto-generated by cnos codegen. Do not edit.",
|
|
1647
|
+
`// Source: ${sourcePath}`,
|
|
1648
|
+
"",
|
|
1649
|
+
...interfaceBlocks,
|
|
1650
|
+
"",
|
|
1651
|
+
'import type { CnosRuntime } from "@kitsy/cnos";',
|
|
1652
|
+
"",
|
|
1653
|
+
"export interface TypedCnosRuntime extends CnosRuntime {",
|
|
1654
|
+
" value<K extends keyof CnosValueConfig>(path: K): CnosValueConfig[K] | undefined;",
|
|
1655
|
+
" secret<K extends keyof CnosSecretConfig>(path: K): CnosSecretConfig[K] | undefined;",
|
|
1656
|
+
" require(key: `value.${keyof CnosValueConfig}`): CnosValueConfig[keyof CnosValueConfig];",
|
|
1657
|
+
" require(key: `secret.${keyof CnosSecretConfig}`): CnosSecretConfig[keyof CnosSecretConfig];",
|
|
1658
|
+
"}",
|
|
1659
|
+
""
|
|
1660
|
+
];
|
|
1661
|
+
if (!hasSchema) {
|
|
1662
|
+
typesLines.push("// Hint: add a schema section to .cnos/cnos.yml for typed key generation.");
|
|
1663
|
+
typesLines.push("");
|
|
1664
|
+
}
|
|
1665
|
+
const runtimeLines = [
|
|
1666
|
+
"// Auto-generated by cnos codegen. Do not edit.",
|
|
1667
|
+
`// Source: ${sourcePath}`,
|
|
1668
|
+
"",
|
|
1669
|
+
'import { createCnos as _createCnos, type CnosCreateOptions } from "@kitsy/cnos/configure";',
|
|
1670
|
+
`import type { TypedCnosRuntime } from "${typeModuleImport}";`,
|
|
1671
|
+
"",
|
|
1672
|
+
"export async function createCnos(options: CnosCreateOptions = {}): Promise<TypedCnosRuntime> {",
|
|
1673
|
+
" return (await _createCnos(options)) as TypedCnosRuntime;",
|
|
1674
|
+
"}",
|
|
1675
|
+
""
|
|
1676
|
+
];
|
|
1677
|
+
return {
|
|
1678
|
+
typesContent: typesLines.join("\n"),
|
|
1679
|
+
runtimeContent: runtimeLines.join("\n"),
|
|
1680
|
+
schemaEntryCount: Object.keys(schema).length,
|
|
1681
|
+
hasSchema
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// src/codegen/writeOutput.ts
|
|
1686
|
+
var import_promises10 = require("fs/promises");
|
|
1687
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
1688
|
+
function stripTsExtension(filePath) {
|
|
1689
|
+
return filePath.replace(/(\.d)?\.[cm]?tsx?$/i, "").replace(/\.[cm]?jsx?$/i, "");
|
|
1690
|
+
}
|
|
1691
|
+
function resolveCodegenPaths(repoRoot, out) {
|
|
1692
|
+
const typesPath = out ? import_node_path10.default.resolve(repoRoot, out) : import_node_path10.default.join(repoRoot, ".cnos", "types", "cnos.d.ts");
|
|
1693
|
+
const runtimePath = import_node_path10.default.join(import_node_path10.default.dirname(typesPath), "runtime.ts");
|
|
1694
|
+
const typeImportPath = `./${import_node_path10.default.basename(stripTsExtension(typesPath))}`;
|
|
1695
|
+
return {
|
|
1696
|
+
typesPath,
|
|
1697
|
+
runtimePath,
|
|
1698
|
+
typeImportPath
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
async function writeCodegenOutput(options = {}) {
|
|
1702
|
+
const loadedManifest = await loadManifest(options.root ? { root: options.root } : {});
|
|
1703
|
+
const paths = resolveCodegenPaths(loadedManifest.repoRoot, options.out);
|
|
1704
|
+
const generated = generateCodegenContent(loadedManifest.manifest, loadedManifest.manifestPath, paths.typeImportPath);
|
|
1705
|
+
await (0, import_promises10.mkdir)(import_node_path10.default.dirname(paths.typesPath), { recursive: true });
|
|
1706
|
+
await (0, import_promises10.mkdir)(import_node_path10.default.dirname(paths.runtimePath), { recursive: true });
|
|
1707
|
+
await (0, import_promises10.writeFile)(paths.typesPath, generated.typesContent, "utf8");
|
|
1708
|
+
await (0, import_promises10.writeFile)(paths.runtimePath, generated.runtimeContent, "utf8");
|
|
1709
|
+
return {
|
|
1710
|
+
manifestPath: loadedManifest.manifestPath,
|
|
1711
|
+
typesPath: paths.typesPath,
|
|
1712
|
+
runtimePath: paths.runtimePath,
|
|
1713
|
+
schemaEntryCount: generated.schemaEntryCount,
|
|
1714
|
+
hasSchema: generated.hasSchema
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// src/codegen/watchSchema.ts
|
|
1719
|
+
var import_node_fs = require("fs");
|
|
1720
|
+
async function watchSchema(options = {}) {
|
|
1721
|
+
const loadedManifest = await loadManifest(options.root ? { root: options.root } : {});
|
|
1722
|
+
let timeout;
|
|
1723
|
+
const debounceMs = options.debounceMs ?? 300;
|
|
1724
|
+
const runWrite = async () => {
|
|
1725
|
+
try {
|
|
1726
|
+
const result = await writeCodegenOutput(options);
|
|
1727
|
+
await options.onWrite?.(result);
|
|
1728
|
+
} catch (error) {
|
|
1729
|
+
await options.onError?.(error);
|
|
1730
|
+
}
|
|
1731
|
+
};
|
|
1732
|
+
await runWrite();
|
|
1733
|
+
const watcher = (0, import_node_fs.watch)(loadedManifest.manifestPath, () => {
|
|
1734
|
+
if (timeout) {
|
|
1735
|
+
clearTimeout(timeout);
|
|
1736
|
+
}
|
|
1737
|
+
timeout = setTimeout(() => {
|
|
1738
|
+
void runWrite();
|
|
1739
|
+
}, debounceMs);
|
|
1740
|
+
});
|
|
1741
|
+
watcher.on("close", () => {
|
|
1742
|
+
if (timeout) {
|
|
1743
|
+
clearTimeout(timeout);
|
|
1744
|
+
}
|
|
1745
|
+
});
|
|
1746
|
+
return watcher;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
// src/drift/compareSchemaToGraph.ts
|
|
1750
|
+
function describeValueType(value) {
|
|
1751
|
+
if (Array.isArray(value)) {
|
|
1752
|
+
return "array";
|
|
1753
|
+
}
|
|
1754
|
+
if (value === null) {
|
|
1755
|
+
return "null";
|
|
1756
|
+
}
|
|
1757
|
+
return typeof value;
|
|
1758
|
+
}
|
|
1759
|
+
function matchesType(value, type) {
|
|
1760
|
+
if (!type) {
|
|
1761
|
+
return true;
|
|
1762
|
+
}
|
|
1763
|
+
switch (type) {
|
|
1764
|
+
case "array":
|
|
1765
|
+
return Array.isArray(value);
|
|
1766
|
+
case "object":
|
|
1767
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1768
|
+
default:
|
|
1769
|
+
return typeof value === type;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
function isSchemaDefault(entry) {
|
|
1773
|
+
return entry.winner.metadata?.schemaDefault === true;
|
|
1774
|
+
}
|
|
1775
|
+
function shouldTrackKey(key) {
|
|
1776
|
+
return key.startsWith("value.") || key.startsWith("secret.");
|
|
1777
|
+
}
|
|
1778
|
+
function compareSchemaToGraph(runtime) {
|
|
1779
|
+
const schema = runtime.manifest.schema;
|
|
1780
|
+
const missing = [];
|
|
1781
|
+
const mismatches = [];
|
|
1782
|
+
const defaultsApplied = [];
|
|
1783
|
+
for (const [key, rule] of Object.entries(schema).sort(([left], [right]) => left.localeCompare(right))) {
|
|
1784
|
+
const entry = runtime.graph.entries.get(key);
|
|
1785
|
+
if (!entry) {
|
|
1786
|
+
if (rule.required && rule.default === void 0) {
|
|
1787
|
+
missing.push({
|
|
1788
|
+
key,
|
|
1789
|
+
...rule.type ? {
|
|
1790
|
+
expectedType: rule.type
|
|
1791
|
+
} : {}
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
continue;
|
|
1795
|
+
}
|
|
1796
|
+
if (isSchemaDefault(entry)) {
|
|
1797
|
+
defaultsApplied.push({
|
|
1798
|
+
key,
|
|
1799
|
+
value: entry.value
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
const actualValue = entry.winner.value;
|
|
1803
|
+
if (!matchesType(actualValue, rule.type)) {
|
|
1804
|
+
mismatches.push({
|
|
1805
|
+
key,
|
|
1806
|
+
...rule.type ? {
|
|
1807
|
+
expectedType: rule.type
|
|
1808
|
+
} : {},
|
|
1809
|
+
actualType: describeValueType(actualValue),
|
|
1810
|
+
value: actualValue,
|
|
1811
|
+
...entry.winner.origin?.file ? {
|
|
1812
|
+
sourceFile: entry.winner.origin.file
|
|
1813
|
+
} : {}
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
const undeclared = Array.from(runtime.graph.entries.values()).filter((entry) => shouldTrackKey(entry.key) && !schema[entry.key] && !isSchemaDefault(entry)).map((entry) => {
|
|
1818
|
+
const issue = {
|
|
1819
|
+
key: entry.key,
|
|
1820
|
+
value: entry.winner.value,
|
|
1821
|
+
actualType: describeValueType(entry.winner.value)
|
|
1822
|
+
};
|
|
1823
|
+
if (entry.winner.origin?.file) {
|
|
1824
|
+
issue.sourceFile = entry.winner.origin.file;
|
|
1825
|
+
}
|
|
1826
|
+
return issue;
|
|
1827
|
+
}).sort((left, right) => left.key.localeCompare(right.key));
|
|
1828
|
+
return {
|
|
1829
|
+
profile: runtime.graph.profile,
|
|
1830
|
+
workspace: runtime.graph.workspace.workspaceId,
|
|
1831
|
+
missing,
|
|
1832
|
+
undeclared,
|
|
1833
|
+
mismatches,
|
|
1834
|
+
defaultsApplied
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
// src/drift/formatDriftReport.ts
|
|
1839
|
+
function formatIssueList(title, marker, issues, formatter) {
|
|
1840
|
+
if (issues.length === 0) {
|
|
1841
|
+
return [];
|
|
1842
|
+
}
|
|
1843
|
+
return [
|
|
1844
|
+
`${title}:`,
|
|
1845
|
+
...issues.map((issue) => ` ${marker} ${formatter(issue)}`),
|
|
1846
|
+
""
|
|
1847
|
+
];
|
|
1848
|
+
}
|
|
1849
|
+
function formatDriftReport(report) {
|
|
1850
|
+
const lines = [
|
|
1851
|
+
`Schema vs resolved config (${report.workspace} / ${report.profile}):`,
|
|
1852
|
+
""
|
|
1853
|
+
];
|
|
1854
|
+
lines.push(
|
|
1855
|
+
...formatIssueList("Missing (required, not defined)", "x", report.missing, (issue) => issue.key)
|
|
1856
|
+
);
|
|
1857
|
+
lines.push(
|
|
1858
|
+
...formatIssueList(
|
|
1859
|
+
"Undeclared (defined, not in schema)",
|
|
1860
|
+
"?",
|
|
1861
|
+
report.undeclared,
|
|
1862
|
+
(issue) => issue.sourceFile ? `${issue.key} (found in ${issue.sourceFile})` : issue.key
|
|
1863
|
+
)
|
|
1864
|
+
);
|
|
1865
|
+
lines.push(
|
|
1866
|
+
...formatIssueList("Type mismatches", "x", report.mismatches, (issue) => {
|
|
1867
|
+
const actual = issue.value === void 0 ? issue.actualType : `${issue.actualType} ${JSON.stringify(issue.value)}`;
|
|
1868
|
+
return `${issue.key} (schema: ${issue.expectedType}, actual: ${actual})`;
|
|
1869
|
+
})
|
|
1870
|
+
);
|
|
1871
|
+
lines.push(
|
|
1872
|
+
...formatIssueList(
|
|
1873
|
+
"Defaults applied",
|
|
1874
|
+
"i",
|
|
1875
|
+
report.defaultsApplied,
|
|
1876
|
+
(issue) => `${issue.key} (using default: ${JSON.stringify(issue.value)})`
|
|
1877
|
+
)
|
|
1878
|
+
);
|
|
1879
|
+
if (lines[lines.length - 1] === "") {
|
|
1880
|
+
lines.pop();
|
|
1881
|
+
}
|
|
1882
|
+
if (lines.length === 2) {
|
|
1883
|
+
lines.push("No drift detected.");
|
|
1884
|
+
}
|
|
1885
|
+
return lines.join("\n");
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
// src/migrate/applyManifest.ts
|
|
1889
|
+
var import_promises11 = require("fs/promises");
|
|
1890
|
+
function sortRecord(record) {
|
|
1891
|
+
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
1892
|
+
}
|
|
1893
|
+
async function applyManifestMappings(proposals, root) {
|
|
1894
|
+
const loadedManifest = await loadManifest(root ? { root } : {});
|
|
1895
|
+
const rawManifest = {
|
|
1896
|
+
...loadedManifest.rawManifest
|
|
1897
|
+
};
|
|
1898
|
+
const explicit = {
|
|
1899
|
+
...rawManifest.envMapping?.explicit ?? {}
|
|
1900
|
+
};
|
|
1901
|
+
const promoted = new Set(rawManifest.public?.promote ?? []);
|
|
1902
|
+
let appliedMappings = 0;
|
|
1903
|
+
let appliedPromotions = 0;
|
|
1904
|
+
for (const proposal of proposals) {
|
|
1905
|
+
if (explicit[proposal.envVar] !== proposal.logicalKey) {
|
|
1906
|
+
explicit[proposal.envVar] = proposal.logicalKey;
|
|
1907
|
+
appliedMappings += 1;
|
|
1908
|
+
}
|
|
1909
|
+
if (proposal.public && !promoted.has(proposal.logicalKey)) {
|
|
1910
|
+
promoted.add(proposal.logicalKey);
|
|
1911
|
+
appliedPromotions += 1;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
rawManifest.envMapping = {
|
|
1915
|
+
...rawManifest.envMapping ?? {},
|
|
1916
|
+
explicit: sortRecord(explicit)
|
|
1917
|
+
};
|
|
1918
|
+
if (promoted.size > 0) {
|
|
1919
|
+
rawManifest.public = {
|
|
1920
|
+
...rawManifest.public ?? {},
|
|
1921
|
+
promote: Array.from(promoted).sort((left, right) => left.localeCompare(right))
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
await (0, import_promises11.writeFile)(loadedManifest.manifestPath, stringifyYaml(rawManifest), "utf8");
|
|
1925
|
+
return {
|
|
1926
|
+
manifestPath: loadedManifest.manifestPath,
|
|
1927
|
+
appliedMappings,
|
|
1928
|
+
appliedPromotions
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
// src/migrate/proposeMapping.ts
|
|
1933
|
+
var SECRET_TOKENS = ["PASSWORD", "SECRET", "KEY", "TOKEN"];
|
|
1934
|
+
function normalizeSegments(value) {
|
|
1935
|
+
return value.split("_").map((segment) => segment.trim().toLowerCase()).filter((segment) => segment.length > 0);
|
|
1936
|
+
}
|
|
1937
|
+
function isSecretEnvVar(value) {
|
|
1938
|
+
return SECRET_TOKENS.some((token) => value.includes(`_${token}`) || value.endsWith(token));
|
|
1939
|
+
}
|
|
1940
|
+
function proposeMapping(envVar) {
|
|
1941
|
+
const framework = envVar.startsWith("VITE_") ? "vite" : envVar.startsWith("NEXT_PUBLIC_") ? "next" : void 0;
|
|
1942
|
+
const strippedEnvVar = framework === "vite" ? envVar.slice("VITE_".length) : framework === "next" ? envVar.slice("NEXT_PUBLIC_".length) : envVar;
|
|
1943
|
+
const namespace = isSecretEnvVar(strippedEnvVar) && !framework ? "secret" : "value";
|
|
1944
|
+
const segments = normalizeSegments(strippedEnvVar);
|
|
1945
|
+
const logicalPath = segments.join(".");
|
|
1946
|
+
return {
|
|
1947
|
+
envVar,
|
|
1948
|
+
namespace,
|
|
1949
|
+
logicalPath,
|
|
1950
|
+
logicalKey: `${namespace}.${logicalPath}`,
|
|
1951
|
+
public: Boolean(framework),
|
|
1952
|
+
...framework ? {
|
|
1953
|
+
framework
|
|
1954
|
+
} : {}
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
// src/migrate/rewriteSource.ts
|
|
1959
|
+
var import_promises12 = require("fs/promises");
|
|
1960
|
+
function importStatementFor(kind) {
|
|
1961
|
+
return kind === "import-meta-env" ? "import cnos from '@kitsy/cnos/browser';" : "import cnos from '@kitsy/cnos';";
|
|
1962
|
+
}
|
|
1963
|
+
function replacementFor(proposal) {
|
|
1964
|
+
if (proposal.public) {
|
|
1965
|
+
return `cnos.read(${JSON.stringify(`public.${proposal.logicalPath}`)})`;
|
|
1966
|
+
}
|
|
1967
|
+
return proposal.namespace === "secret" ? `cnos.secret(${JSON.stringify(proposal.logicalPath)})` : `cnos.value(${JSON.stringify(proposal.logicalPath)})`;
|
|
1968
|
+
}
|
|
1969
|
+
async function rewriteSourceFiles(usages, proposals) {
|
|
1970
|
+
const fileGroups = /* @__PURE__ */ new Map();
|
|
1971
|
+
for (const usage of usages) {
|
|
1972
|
+
const existing = fileGroups.get(usage.filePath) ?? [];
|
|
1973
|
+
existing.push(usage);
|
|
1974
|
+
fileGroups.set(usage.filePath, existing);
|
|
1975
|
+
}
|
|
1976
|
+
const rewrittenFiles = [];
|
|
1977
|
+
const backupFiles = [];
|
|
1978
|
+
const skippedUsages = [];
|
|
1979
|
+
for (const [filePath, fileUsages] of fileGroups.entries()) {
|
|
1980
|
+
const original = await (0, import_promises12.readFile)(filePath, "utf8");
|
|
1981
|
+
let nextSource = original;
|
|
1982
|
+
let changed = false;
|
|
1983
|
+
const importKinds = /* @__PURE__ */ new Set();
|
|
1984
|
+
for (const usage of fileUsages) {
|
|
1985
|
+
const proposal = proposals.get(usage.envVar);
|
|
1986
|
+
if (!proposal) {
|
|
1987
|
+
skippedUsages.push(`${filePath}:${usage.source}`);
|
|
1988
|
+
continue;
|
|
1989
|
+
}
|
|
1990
|
+
if (usage.kind === "import-meta-env" && proposal.namespace === "secret") {
|
|
1991
|
+
skippedUsages.push(`${filePath}:${usage.source}`);
|
|
1992
|
+
continue;
|
|
1993
|
+
}
|
|
1994
|
+
const replacement = replacementFor(proposal);
|
|
1995
|
+
if (!nextSource.includes(usage.source)) {
|
|
1996
|
+
skippedUsages.push(`${filePath}:${usage.source}`);
|
|
1997
|
+
continue;
|
|
1998
|
+
}
|
|
1999
|
+
nextSource = nextSource.split(usage.source).join(replacement);
|
|
2000
|
+
changed = true;
|
|
2001
|
+
importKinds.add(usage.kind);
|
|
2002
|
+
}
|
|
2003
|
+
if (!changed) {
|
|
2004
|
+
continue;
|
|
2005
|
+
}
|
|
2006
|
+
const backupPath = `${filePath}.bak`;
|
|
2007
|
+
await (0, import_promises12.copyFile)(filePath, backupPath);
|
|
2008
|
+
backupFiles.push(backupPath);
|
|
2009
|
+
for (const kind of Array.from(importKinds)) {
|
|
2010
|
+
const importStatement = importStatementFor(kind);
|
|
2011
|
+
if (!nextSource.includes(importStatement)) {
|
|
2012
|
+
nextSource = `${importStatement}
|
|
2013
|
+
${nextSource}`;
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
await (0, import_promises12.writeFile)(filePath, nextSource, "utf8");
|
|
2017
|
+
rewrittenFiles.push(filePath);
|
|
2018
|
+
}
|
|
2019
|
+
return {
|
|
2020
|
+
rewrittenFiles: rewrittenFiles.sort((left, right) => left.localeCompare(right)),
|
|
2021
|
+
backupFiles: backupFiles.sort((left, right) => left.localeCompare(right)),
|
|
2022
|
+
skippedUsages: skippedUsages.sort((left, right) => left.localeCompare(right))
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
// src/migrate/scanEnvUsage.ts
|
|
2027
|
+
var import_promises13 = require("fs/promises");
|
|
2028
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
2029
|
+
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"]);
|
|
2030
|
+
var PROCESS_ENV_DOT = /process\.env\.([A-Z][A-Z0-9_]*)/g;
|
|
2031
|
+
var PROCESS_ENV_BRACKET = /process\.env\[['"]([A-Z][A-Z0-9_]*)['"]\]/g;
|
|
2032
|
+
var IMPORT_META_ENV_DOT = /import\.meta\.env\.([A-Z][A-Z0-9_]*)/g;
|
|
2033
|
+
var IMPORT_META_ENV_BRACKET = /import\.meta\.env\[['"]([A-Z][A-Z0-9_]*)['"]\]/g;
|
|
2034
|
+
async function collectFiles(root) {
|
|
2035
|
+
const entries = await (0, import_promises13.readdir)(root, { withFileTypes: true });
|
|
2036
|
+
const files = [];
|
|
2037
|
+
for (const entry of entries) {
|
|
2038
|
+
const filePath = import_node_path11.default.join(root, entry.name);
|
|
2039
|
+
if (entry.isDirectory()) {
|
|
2040
|
+
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") {
|
|
2041
|
+
continue;
|
|
2042
|
+
}
|
|
2043
|
+
files.push(...await collectFiles(filePath));
|
|
2044
|
+
continue;
|
|
2045
|
+
}
|
|
2046
|
+
if (SOURCE_EXTENSIONS.has(import_node_path11.default.extname(entry.name))) {
|
|
2047
|
+
files.push(filePath);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
return files;
|
|
2051
|
+
}
|
|
2052
|
+
function collectMatches(filePath, source, pattern, kind) {
|
|
2053
|
+
const matches = [];
|
|
2054
|
+
for (const match of source.matchAll(pattern)) {
|
|
2055
|
+
const envVar = match[1];
|
|
2056
|
+
if (!envVar) {
|
|
2057
|
+
continue;
|
|
2058
|
+
}
|
|
2059
|
+
matches.push({
|
|
2060
|
+
filePath,
|
|
2061
|
+
envVar,
|
|
2062
|
+
source: match[0],
|
|
2063
|
+
kind
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
return matches;
|
|
2067
|
+
}
|
|
2068
|
+
async function scanEnvUsage(scanRoot) {
|
|
2069
|
+
const files = await collectFiles(scanRoot);
|
|
2070
|
+
const usages = [];
|
|
2071
|
+
for (const filePath of files) {
|
|
2072
|
+
const source = await (0, import_promises13.readFile)(filePath, "utf8");
|
|
2073
|
+
usages.push(...collectMatches(filePath, source, PROCESS_ENV_DOT, "process-env"));
|
|
2074
|
+
usages.push(...collectMatches(filePath, source, PROCESS_ENV_BRACKET, "process-env"));
|
|
2075
|
+
usages.push(...collectMatches(filePath, source, IMPORT_META_ENV_DOT, "import-meta-env"));
|
|
2076
|
+
usages.push(...collectMatches(filePath, source, IMPORT_META_ENV_BRACKET, "import-meta-env"));
|
|
2077
|
+
}
|
|
2078
|
+
return usages.sort((left, right) => {
|
|
2079
|
+
const byFile = left.filePath.localeCompare(right.filePath);
|
|
2080
|
+
if (byFile !== 0) {
|
|
2081
|
+
return byFile;
|
|
2082
|
+
}
|
|
2083
|
+
return left.envVar.localeCompare(right.envVar);
|
|
2084
|
+
});
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
// src/watch/diffGraphs.ts
|
|
2088
|
+
function stableStringify(value) {
|
|
2089
|
+
if (Array.isArray(value)) {
|
|
2090
|
+
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
2091
|
+
}
|
|
2092
|
+
if (value && typeof value === "object") {
|
|
2093
|
+
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`).join(",")}}`;
|
|
2094
|
+
}
|
|
2095
|
+
return JSON.stringify(value);
|
|
2096
|
+
}
|
|
2097
|
+
function diffGraphs(previous, next) {
|
|
2098
|
+
const keys = /* @__PURE__ */ new Set([
|
|
2099
|
+
...Array.from(previous.entries.keys()),
|
|
2100
|
+
...Array.from(next.entries.keys())
|
|
2101
|
+
]);
|
|
2102
|
+
return Array.from(keys).filter((key) => !key.startsWith("meta.")).filter((key) => {
|
|
2103
|
+
const previousEntry = previous.entries.get(key);
|
|
2104
|
+
const nextEntry = next.entries.get(key);
|
|
2105
|
+
if (!previousEntry || !nextEntry) {
|
|
2106
|
+
return previousEntry?.namespace !== "meta" && nextEntry?.namespace !== "meta";
|
|
2107
|
+
}
|
|
2108
|
+
return stableStringify(previousEntry.value) !== stableStringify(nextEntry.value);
|
|
2109
|
+
}).sort((left, right) => left.localeCompare(right));
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
// src/watch/watchFiles.ts
|
|
2113
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
2114
|
+
async function watchFiles(runtime, root) {
|
|
2115
|
+
const manifest = await loadManifest(root ? { root } : {});
|
|
2116
|
+
const roots = Array.from(
|
|
2117
|
+
new Set(runtime.graph.workspace.workspaceRoots.map((workspaceRoot) => workspaceRoot.path))
|
|
2118
|
+
).sort((left, right) => left.localeCompare(right));
|
|
2119
|
+
const files = Array.from(
|
|
2120
|
+
new Set(
|
|
2121
|
+
Array.from(runtime.graph.entries.values()).map((entry) => entry.winner.origin?.file).filter((file) => Boolean(file)).map((file) => import_node_path12.default.resolve(manifest.repoRoot, file))
|
|
2122
|
+
)
|
|
2123
|
+
).sort((left, right) => left.localeCompare(right));
|
|
2124
|
+
return {
|
|
2125
|
+
manifestPath: manifest.manifestPath,
|
|
2126
|
+
roots,
|
|
2127
|
+
files
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
326
2130
|
// Annotate the CommonJS export names for ESM import in node:
|
|
327
2131
|
0 && (module.exports = {
|
|
2132
|
+
CNOS_GRAPH_ENV_VAR,
|
|
2133
|
+
CNOS_SECRET_PAYLOAD_ENV_VAR,
|
|
2134
|
+
CNOS_SESSION_KEY_ENV_VAR,
|
|
2135
|
+
CnosAuthenticationError,
|
|
2136
|
+
CnosSecurityError,
|
|
2137
|
+
applyManifestMappings,
|
|
2138
|
+
clearAllVaultSessionKeys,
|
|
2139
|
+
clearVaultSessionKey,
|
|
2140
|
+
compareSchemaToGraph,
|
|
328
2141
|
createSecretVault,
|
|
2142
|
+
createSecretVaultProvider,
|
|
2143
|
+
deleteLocalSecret,
|
|
2144
|
+
deriveVaultKey,
|
|
2145
|
+
deserializeRuntimeGraph,
|
|
2146
|
+
detectLegacyVaultFormat,
|
|
2147
|
+
diffGraphs,
|
|
2148
|
+
ensureProjectionAllowed,
|
|
329
2149
|
flattenObject,
|
|
2150
|
+
formatDriftReport,
|
|
2151
|
+
generateCodegenContent,
|
|
2152
|
+
getVaultPassphraseEnvVar,
|
|
2153
|
+
getVaultSessionKeyEnvVar,
|
|
2154
|
+
graphRequiresSecretHydration,
|
|
2155
|
+
isPassphraseEnvRef,
|
|
2156
|
+
isSecretReference,
|
|
2157
|
+
listLocalSecrets,
|
|
330
2158
|
listSecretVaults,
|
|
2159
|
+
loadManifest,
|
|
331
2160
|
parseYaml,
|
|
2161
|
+
proposeMapping,
|
|
2162
|
+
readKeychain,
|
|
2163
|
+
readLocalSecret,
|
|
2164
|
+
readRuntimeGraphFromEnv,
|
|
2165
|
+
readVaultMetadata,
|
|
2166
|
+
removeLocalVaultFiles,
|
|
2167
|
+
resolveCodegenPaths,
|
|
332
2168
|
resolveConfigDocumentPath,
|
|
2169
|
+
resolveConfiguredVaultPassphrase,
|
|
2170
|
+
resolveManifestRoot,
|
|
333
2171
|
resolveSecretPassphrase,
|
|
334
2172
|
resolveSecretStoreRoot,
|
|
335
2173
|
resolveSecretVaultFile,
|
|
2174
|
+
resolveVaultAccessKey,
|
|
2175
|
+
resolveVaultAuth,
|
|
2176
|
+
resolveVaultDefinition,
|
|
2177
|
+
rewriteSourceFiles,
|
|
2178
|
+
scanEnvUsage,
|
|
2179
|
+
serializeRuntimeGraph,
|
|
2180
|
+
serializeSecretPayload,
|
|
336
2181
|
stringifyYaml,
|
|
337
2182
|
validateRuntime,
|
|
338
|
-
|
|
2183
|
+
watchFiles,
|
|
2184
|
+
watchSchema,
|
|
2185
|
+
writeCodegenOutput,
|
|
2186
|
+
writeKeychain,
|
|
2187
|
+
writeLocalSecret,
|
|
2188
|
+
writeVaultSessionKey
|
|
339
2189
|
});
|