@kitsy/cnos 1.2.0 → 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 +3 -3
- package/dist/build/index.cjs +902 -113
- package/dist/build/index.d.cts +1 -1
- package/dist/build/index.d.ts +1 -1
- package/dist/build/index.js +22 -10
- package/dist/{chunk-WHUGFPE4.js → chunk-CDXJISGB.js} +1 -1
- package/dist/{chunk-APCTXRUN.js → chunk-DRKDNY4I.js} +998 -191
- package/dist/chunk-E7SE6N26.js +189 -0
- package/dist/{chunk-SO5XREEU.js → chunk-EDCLLCNL.js} +32 -11
- package/dist/{chunk-SXTMTACL.js → chunk-FC3IV6A7.js} +1 -31
- package/dist/{chunk-MLQGYCO7.js → chunk-JDII6O72.js} +1 -1
- package/dist/chunk-K6QYI2T4.js +105 -0
- package/dist/{chunk-EIN55XXA.js → chunk-OOKFRWTN.js} +1 -1
- package/dist/{chunk-ZA74BO47.js → chunk-OWUZQ4OH.js} +1 -1
- package/dist/{chunk-RD5WMHPM.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-BTJpH93W.d.cts → envNaming-D6k66myh.d.cts} +1 -1
- package/dist/{envNaming-CcsqAel3.d.ts → envNaming-Dy3WYiGK.d.ts} +1 -1
- package/dist/index.cjs +1142 -178
- package/dist/index.d.cts +2 -13
- package/dist/index.d.ts +2 -13
- package/dist/index.js +13 -25
- package/dist/internal.cjs +1512 -80
- package/dist/internal.d.cts +170 -14
- package/dist/internal.d.ts +170 -14
- package/dist/internal.js +645 -5
- 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 +36 -9
- package/dist/plugin/dotenv.d.cts +2 -2
- package/dist/plugin/dotenv.d.ts +2 -2
- package/dist/plugin/dotenv.js +2 -2
- package/dist/plugin/env-export.cjs +31 -2
- 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 +31 -4
- package/dist/plugin/process-env.d.cts +2 -2
- package/dist/plugin/process-env.d.ts +2 -2
- package/dist/plugin/process-env.js +2 -2
- package/dist/{plugin-DkOIT5uI.d.cts → plugin-CyNkf7Dm.d.cts} +14 -2
- package/dist/{plugin-DkOIT5uI.d.ts → plugin-CyNkf7Dm.d.ts} +14 -2
- package/dist/runtime/index.cjs +956 -128
- package/dist/runtime/index.d.cts +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +11 -186
- package/dist/{toPublicEnv-DvFeV3qG.d.cts → toPublicEnv-Cz72m6y0.d.cts} +1 -1
- package/dist/{toPublicEnv-C9clvXLo.d.ts → toPublicEnv-D2PZkaN-.d.ts} +1 -1
- package/package.json +11 -1
- package/dist/chunk-JUHPBAEH.js +0 -20
- package/dist/chunk-PQ4KSV76.js +0 -50
package/dist/internal.js
CHANGED
|
@@ -1,52 +1,692 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CNOS_GRAPH_ENV_VAR,
|
|
3
|
+
CNOS_SECRET_PAYLOAD_ENV_VAR,
|
|
4
|
+
CNOS_SESSION_KEY_ENV_VAR,
|
|
3
5
|
deserializeRuntimeGraph,
|
|
6
|
+
graphRequiresSecretHydration,
|
|
4
7
|
readRuntimeGraphFromEnv,
|
|
5
|
-
serializeRuntimeGraph
|
|
6
|
-
|
|
8
|
+
serializeRuntimeGraph,
|
|
9
|
+
serializeSecretPayload
|
|
10
|
+
} from "./chunk-K6QYI2T4.js";
|
|
7
11
|
import {
|
|
12
|
+
CnosAuthenticationError,
|
|
8
13
|
CnosSecurityError,
|
|
14
|
+
clearAllVaultSessionKeys,
|
|
15
|
+
clearVaultSessionKey,
|
|
9
16
|
createSecretVault,
|
|
17
|
+
createSecretVaultProvider,
|
|
18
|
+
deleteLocalSecret,
|
|
19
|
+
deriveVaultKey,
|
|
20
|
+
detectLegacyVaultFormat,
|
|
10
21
|
ensureProjectionAllowed,
|
|
11
22
|
flattenObject,
|
|
12
23
|
getVaultPassphraseEnvVar,
|
|
24
|
+
getVaultSessionKeyEnvVar,
|
|
13
25
|
isPassphraseEnvRef,
|
|
26
|
+
isSecretReference,
|
|
27
|
+
listLocalSecrets,
|
|
14
28
|
listSecretVaults,
|
|
15
29
|
loadManifest,
|
|
16
30
|
parseYaml,
|
|
31
|
+
readKeychain,
|
|
32
|
+
readLocalSecret,
|
|
33
|
+
readVaultMetadata,
|
|
34
|
+
removeLocalVaultFiles,
|
|
17
35
|
resolveConfigDocumentPath,
|
|
18
36
|
resolveConfiguredVaultPassphrase,
|
|
19
37
|
resolveManifestRoot,
|
|
20
38
|
resolveSecretPassphrase,
|
|
21
39
|
resolveSecretStoreRoot,
|
|
22
40
|
resolveSecretVaultFile,
|
|
41
|
+
resolveVaultAccessKey,
|
|
42
|
+
resolveVaultAuth,
|
|
23
43
|
resolveVaultDefinition,
|
|
24
44
|
stringifyYaml,
|
|
25
45
|
validateRuntime,
|
|
26
|
-
|
|
27
|
-
|
|
46
|
+
writeKeychain,
|
|
47
|
+
writeLocalSecret,
|
|
48
|
+
writeVaultSessionKey
|
|
49
|
+
} from "./chunk-DRKDNY4I.js";
|
|
50
|
+
|
|
51
|
+
// src/codegen/generateTypes.ts
|
|
52
|
+
function toPascalCase(value) {
|
|
53
|
+
return value.split(/[^A-Za-z0-9]+/).filter((segment) => segment.length > 0).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
|
|
54
|
+
}
|
|
55
|
+
function mapSchemaType(rule) {
|
|
56
|
+
switch (rule?.type) {
|
|
57
|
+
case "number":
|
|
58
|
+
return "number";
|
|
59
|
+
case "string":
|
|
60
|
+
return "string";
|
|
61
|
+
case "boolean":
|
|
62
|
+
return "boolean";
|
|
63
|
+
case "object":
|
|
64
|
+
return "Record<string, unknown>";
|
|
65
|
+
case "array":
|
|
66
|
+
return "unknown[]";
|
|
67
|
+
default:
|
|
68
|
+
return "unknown";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function isOptional(rule) {
|
|
72
|
+
return !(rule?.required ?? false) && rule?.default === void 0;
|
|
73
|
+
}
|
|
74
|
+
function buildNamespaceInterfaces(schema) {
|
|
75
|
+
const namespaceGroups = /* @__PURE__ */ new Map();
|
|
76
|
+
for (const [logicalKey, rule] of Object.entries(schema).sort(([left], [right]) => left.localeCompare(right))) {
|
|
77
|
+
const separatorIndex = logicalKey.indexOf(".");
|
|
78
|
+
if (separatorIndex <= 0 || separatorIndex >= logicalKey.length - 1) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const namespace = logicalKey.slice(0, separatorIndex);
|
|
82
|
+
const path4 = logicalKey.slice(separatorIndex + 1);
|
|
83
|
+
const existing = namespaceGroups.get(namespace) ?? [];
|
|
84
|
+
existing.push({
|
|
85
|
+
key: path4,
|
|
86
|
+
rule
|
|
87
|
+
});
|
|
88
|
+
namespaceGroups.set(namespace, existing);
|
|
89
|
+
}
|
|
90
|
+
const orderedNamespaces = Array.from(namespaceGroups.keys()).sort((left, right) => {
|
|
91
|
+
if (left === "value") {
|
|
92
|
+
return -1;
|
|
93
|
+
}
|
|
94
|
+
if (right === "value") {
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
if (left === "secret") {
|
|
98
|
+
return -1;
|
|
99
|
+
}
|
|
100
|
+
if (right === "secret") {
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
return left.localeCompare(right);
|
|
104
|
+
});
|
|
105
|
+
if (orderedNamespaces.length === 0) {
|
|
106
|
+
return [
|
|
107
|
+
"export interface CnosValueConfig {}",
|
|
108
|
+
"export interface CnosSecretConfig {}",
|
|
109
|
+
"",
|
|
110
|
+
"export interface CnosConfig {",
|
|
111
|
+
" value: CnosValueConfig;",
|
|
112
|
+
" secret: CnosSecretConfig;",
|
|
113
|
+
"}"
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
const blocks = [];
|
|
117
|
+
for (const namespace of orderedNamespaces) {
|
|
118
|
+
const entries = namespaceGroups.get(namespace) ?? [];
|
|
119
|
+
const interfaceName = namespace === "value" ? "CnosValueConfig" : namespace === "secret" ? "CnosSecretConfig" : `Cnos${toPascalCase(namespace)}Config`;
|
|
120
|
+
blocks.push(`export interface ${interfaceName} {`);
|
|
121
|
+
for (const entry of entries) {
|
|
122
|
+
const optional = isOptional(entry.rule) ? "?" : "";
|
|
123
|
+
blocks.push(` "${entry.key}"${optional}: ${mapSchemaType(entry.rule)};`);
|
|
124
|
+
}
|
|
125
|
+
blocks.push("}");
|
|
126
|
+
blocks.push("");
|
|
127
|
+
}
|
|
128
|
+
const configEntries = orderedNamespaces.map((namespace) => {
|
|
129
|
+
const interfaceName = namespace === "value" ? "CnosValueConfig" : namespace === "secret" ? "CnosSecretConfig" : `Cnos${toPascalCase(namespace)}Config`;
|
|
130
|
+
return ` ${namespace}: ${interfaceName};`;
|
|
131
|
+
});
|
|
132
|
+
blocks.push("export interface CnosConfig {");
|
|
133
|
+
blocks.push(...configEntries);
|
|
134
|
+
blocks.push("}");
|
|
135
|
+
if (!orderedNamespaces.includes("value")) {
|
|
136
|
+
blocks.push("");
|
|
137
|
+
blocks.push("export interface CnosValueConfig {}");
|
|
138
|
+
}
|
|
139
|
+
if (!orderedNamespaces.includes("secret")) {
|
|
140
|
+
blocks.push("");
|
|
141
|
+
blocks.push("export interface CnosSecretConfig {}");
|
|
142
|
+
}
|
|
143
|
+
return blocks;
|
|
144
|
+
}
|
|
145
|
+
function generateCodegenContent(manifest, sourcePath, typeModuleImport = "./cnos") {
|
|
146
|
+
const schema = manifest.schema ?? {};
|
|
147
|
+
const hasSchema = Object.keys(schema).length > 0;
|
|
148
|
+
const interfaceBlocks = buildNamespaceInterfaces(schema);
|
|
149
|
+
const typesLines = [
|
|
150
|
+
"// Auto-generated by cnos codegen. Do not edit.",
|
|
151
|
+
`// Source: ${sourcePath}`,
|
|
152
|
+
"",
|
|
153
|
+
...interfaceBlocks,
|
|
154
|
+
"",
|
|
155
|
+
'import type { CnosRuntime } from "@kitsy/cnos";',
|
|
156
|
+
"",
|
|
157
|
+
"export interface TypedCnosRuntime extends CnosRuntime {",
|
|
158
|
+
" value<K extends keyof CnosValueConfig>(path: K): CnosValueConfig[K] | undefined;",
|
|
159
|
+
" secret<K extends keyof CnosSecretConfig>(path: K): CnosSecretConfig[K] | undefined;",
|
|
160
|
+
" require(key: `value.${keyof CnosValueConfig}`): CnosValueConfig[keyof CnosValueConfig];",
|
|
161
|
+
" require(key: `secret.${keyof CnosSecretConfig}`): CnosSecretConfig[keyof CnosSecretConfig];",
|
|
162
|
+
"}",
|
|
163
|
+
""
|
|
164
|
+
];
|
|
165
|
+
if (!hasSchema) {
|
|
166
|
+
typesLines.push("// Hint: add a schema section to .cnos/cnos.yml for typed key generation.");
|
|
167
|
+
typesLines.push("");
|
|
168
|
+
}
|
|
169
|
+
const runtimeLines = [
|
|
170
|
+
"// Auto-generated by cnos codegen. Do not edit.",
|
|
171
|
+
`// Source: ${sourcePath}`,
|
|
172
|
+
"",
|
|
173
|
+
'import { createCnos as _createCnos, type CnosCreateOptions } from "@kitsy/cnos/configure";',
|
|
174
|
+
`import type { TypedCnosRuntime } from "${typeModuleImport}";`,
|
|
175
|
+
"",
|
|
176
|
+
"export async function createCnos(options: CnosCreateOptions = {}): Promise<TypedCnosRuntime> {",
|
|
177
|
+
" return (await _createCnos(options)) as TypedCnosRuntime;",
|
|
178
|
+
"}",
|
|
179
|
+
""
|
|
180
|
+
];
|
|
181
|
+
return {
|
|
182
|
+
typesContent: typesLines.join("\n"),
|
|
183
|
+
runtimeContent: runtimeLines.join("\n"),
|
|
184
|
+
schemaEntryCount: Object.keys(schema).length,
|
|
185
|
+
hasSchema
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/codegen/writeOutput.ts
|
|
190
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
191
|
+
import path from "path";
|
|
192
|
+
function stripTsExtension(filePath) {
|
|
193
|
+
return filePath.replace(/(\.d)?\.[cm]?tsx?$/i, "").replace(/\.[cm]?jsx?$/i, "");
|
|
194
|
+
}
|
|
195
|
+
function resolveCodegenPaths(repoRoot, out) {
|
|
196
|
+
const typesPath = out ? path.resolve(repoRoot, out) : path.join(repoRoot, ".cnos", "types", "cnos.d.ts");
|
|
197
|
+
const runtimePath = path.join(path.dirname(typesPath), "runtime.ts");
|
|
198
|
+
const typeImportPath = `./${path.basename(stripTsExtension(typesPath))}`;
|
|
199
|
+
return {
|
|
200
|
+
typesPath,
|
|
201
|
+
runtimePath,
|
|
202
|
+
typeImportPath
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
async function writeCodegenOutput(options = {}) {
|
|
206
|
+
const loadedManifest = await loadManifest(options.root ? { root: options.root } : {});
|
|
207
|
+
const paths = resolveCodegenPaths(loadedManifest.repoRoot, options.out);
|
|
208
|
+
const generated = generateCodegenContent(loadedManifest.manifest, loadedManifest.manifestPath, paths.typeImportPath);
|
|
209
|
+
await mkdir(path.dirname(paths.typesPath), { recursive: true });
|
|
210
|
+
await mkdir(path.dirname(paths.runtimePath), { recursive: true });
|
|
211
|
+
await writeFile(paths.typesPath, generated.typesContent, "utf8");
|
|
212
|
+
await writeFile(paths.runtimePath, generated.runtimeContent, "utf8");
|
|
213
|
+
return {
|
|
214
|
+
manifestPath: loadedManifest.manifestPath,
|
|
215
|
+
typesPath: paths.typesPath,
|
|
216
|
+
runtimePath: paths.runtimePath,
|
|
217
|
+
schemaEntryCount: generated.schemaEntryCount,
|
|
218
|
+
hasSchema: generated.hasSchema
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/codegen/watchSchema.ts
|
|
223
|
+
import { watch } from "fs";
|
|
224
|
+
async function watchSchema(options = {}) {
|
|
225
|
+
const loadedManifest = await loadManifest(options.root ? { root: options.root } : {});
|
|
226
|
+
let timeout;
|
|
227
|
+
const debounceMs = options.debounceMs ?? 300;
|
|
228
|
+
const runWrite = async () => {
|
|
229
|
+
try {
|
|
230
|
+
const result = await writeCodegenOutput(options);
|
|
231
|
+
await options.onWrite?.(result);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
await options.onError?.(error);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
await runWrite();
|
|
237
|
+
const watcher = watch(loadedManifest.manifestPath, () => {
|
|
238
|
+
if (timeout) {
|
|
239
|
+
clearTimeout(timeout);
|
|
240
|
+
}
|
|
241
|
+
timeout = setTimeout(() => {
|
|
242
|
+
void runWrite();
|
|
243
|
+
}, debounceMs);
|
|
244
|
+
});
|
|
245
|
+
watcher.on("close", () => {
|
|
246
|
+
if (timeout) {
|
|
247
|
+
clearTimeout(timeout);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
return watcher;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/drift/compareSchemaToGraph.ts
|
|
254
|
+
function describeValueType(value) {
|
|
255
|
+
if (Array.isArray(value)) {
|
|
256
|
+
return "array";
|
|
257
|
+
}
|
|
258
|
+
if (value === null) {
|
|
259
|
+
return "null";
|
|
260
|
+
}
|
|
261
|
+
return typeof value;
|
|
262
|
+
}
|
|
263
|
+
function matchesType(value, type) {
|
|
264
|
+
if (!type) {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
switch (type) {
|
|
268
|
+
case "array":
|
|
269
|
+
return Array.isArray(value);
|
|
270
|
+
case "object":
|
|
271
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
272
|
+
default:
|
|
273
|
+
return typeof value === type;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function isSchemaDefault(entry) {
|
|
277
|
+
return entry.winner.metadata?.schemaDefault === true;
|
|
278
|
+
}
|
|
279
|
+
function shouldTrackKey(key) {
|
|
280
|
+
return key.startsWith("value.") || key.startsWith("secret.");
|
|
281
|
+
}
|
|
282
|
+
function compareSchemaToGraph(runtime) {
|
|
283
|
+
const schema = runtime.manifest.schema;
|
|
284
|
+
const missing = [];
|
|
285
|
+
const mismatches = [];
|
|
286
|
+
const defaultsApplied = [];
|
|
287
|
+
for (const [key, rule] of Object.entries(schema).sort(([left], [right]) => left.localeCompare(right))) {
|
|
288
|
+
const entry = runtime.graph.entries.get(key);
|
|
289
|
+
if (!entry) {
|
|
290
|
+
if (rule.required && rule.default === void 0) {
|
|
291
|
+
missing.push({
|
|
292
|
+
key,
|
|
293
|
+
...rule.type ? {
|
|
294
|
+
expectedType: rule.type
|
|
295
|
+
} : {}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (isSchemaDefault(entry)) {
|
|
301
|
+
defaultsApplied.push({
|
|
302
|
+
key,
|
|
303
|
+
value: entry.value
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
const actualValue = entry.winner.value;
|
|
307
|
+
if (!matchesType(actualValue, rule.type)) {
|
|
308
|
+
mismatches.push({
|
|
309
|
+
key,
|
|
310
|
+
...rule.type ? {
|
|
311
|
+
expectedType: rule.type
|
|
312
|
+
} : {},
|
|
313
|
+
actualType: describeValueType(actualValue),
|
|
314
|
+
value: actualValue,
|
|
315
|
+
...entry.winner.origin?.file ? {
|
|
316
|
+
sourceFile: entry.winner.origin.file
|
|
317
|
+
} : {}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const undeclared = Array.from(runtime.graph.entries.values()).filter((entry) => shouldTrackKey(entry.key) && !schema[entry.key] && !isSchemaDefault(entry)).map((entry) => {
|
|
322
|
+
const issue = {
|
|
323
|
+
key: entry.key,
|
|
324
|
+
value: entry.winner.value,
|
|
325
|
+
actualType: describeValueType(entry.winner.value)
|
|
326
|
+
};
|
|
327
|
+
if (entry.winner.origin?.file) {
|
|
328
|
+
issue.sourceFile = entry.winner.origin.file;
|
|
329
|
+
}
|
|
330
|
+
return issue;
|
|
331
|
+
}).sort((left, right) => left.key.localeCompare(right.key));
|
|
332
|
+
return {
|
|
333
|
+
profile: runtime.graph.profile,
|
|
334
|
+
workspace: runtime.graph.workspace.workspaceId,
|
|
335
|
+
missing,
|
|
336
|
+
undeclared,
|
|
337
|
+
mismatches,
|
|
338
|
+
defaultsApplied
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/drift/formatDriftReport.ts
|
|
343
|
+
function formatIssueList(title, marker, issues, formatter) {
|
|
344
|
+
if (issues.length === 0) {
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
347
|
+
return [
|
|
348
|
+
`${title}:`,
|
|
349
|
+
...issues.map((issue) => ` ${marker} ${formatter(issue)}`),
|
|
350
|
+
""
|
|
351
|
+
];
|
|
352
|
+
}
|
|
353
|
+
function formatDriftReport(report) {
|
|
354
|
+
const lines = [
|
|
355
|
+
`Schema vs resolved config (${report.workspace} / ${report.profile}):`,
|
|
356
|
+
""
|
|
357
|
+
];
|
|
358
|
+
lines.push(
|
|
359
|
+
...formatIssueList("Missing (required, not defined)", "x", report.missing, (issue) => issue.key)
|
|
360
|
+
);
|
|
361
|
+
lines.push(
|
|
362
|
+
...formatIssueList(
|
|
363
|
+
"Undeclared (defined, not in schema)",
|
|
364
|
+
"?",
|
|
365
|
+
report.undeclared,
|
|
366
|
+
(issue) => issue.sourceFile ? `${issue.key} (found in ${issue.sourceFile})` : issue.key
|
|
367
|
+
)
|
|
368
|
+
);
|
|
369
|
+
lines.push(
|
|
370
|
+
...formatIssueList("Type mismatches", "x", report.mismatches, (issue) => {
|
|
371
|
+
const actual = issue.value === void 0 ? issue.actualType : `${issue.actualType} ${JSON.stringify(issue.value)}`;
|
|
372
|
+
return `${issue.key} (schema: ${issue.expectedType}, actual: ${actual})`;
|
|
373
|
+
})
|
|
374
|
+
);
|
|
375
|
+
lines.push(
|
|
376
|
+
...formatIssueList(
|
|
377
|
+
"Defaults applied",
|
|
378
|
+
"i",
|
|
379
|
+
report.defaultsApplied,
|
|
380
|
+
(issue) => `${issue.key} (using default: ${JSON.stringify(issue.value)})`
|
|
381
|
+
)
|
|
382
|
+
);
|
|
383
|
+
if (lines[lines.length - 1] === "") {
|
|
384
|
+
lines.pop();
|
|
385
|
+
}
|
|
386
|
+
if (lines.length === 2) {
|
|
387
|
+
lines.push("No drift detected.");
|
|
388
|
+
}
|
|
389
|
+
return lines.join("\n");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/migrate/applyManifest.ts
|
|
393
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
394
|
+
function sortRecord(record) {
|
|
395
|
+
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
396
|
+
}
|
|
397
|
+
async function applyManifestMappings(proposals, root) {
|
|
398
|
+
const loadedManifest = await loadManifest(root ? { root } : {});
|
|
399
|
+
const rawManifest = {
|
|
400
|
+
...loadedManifest.rawManifest
|
|
401
|
+
};
|
|
402
|
+
const explicit = {
|
|
403
|
+
...rawManifest.envMapping?.explicit ?? {}
|
|
404
|
+
};
|
|
405
|
+
const promoted = new Set(rawManifest.public?.promote ?? []);
|
|
406
|
+
let appliedMappings = 0;
|
|
407
|
+
let appliedPromotions = 0;
|
|
408
|
+
for (const proposal of proposals) {
|
|
409
|
+
if (explicit[proposal.envVar] !== proposal.logicalKey) {
|
|
410
|
+
explicit[proposal.envVar] = proposal.logicalKey;
|
|
411
|
+
appliedMappings += 1;
|
|
412
|
+
}
|
|
413
|
+
if (proposal.public && !promoted.has(proposal.logicalKey)) {
|
|
414
|
+
promoted.add(proposal.logicalKey);
|
|
415
|
+
appliedPromotions += 1;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
rawManifest.envMapping = {
|
|
419
|
+
...rawManifest.envMapping ?? {},
|
|
420
|
+
explicit: sortRecord(explicit)
|
|
421
|
+
};
|
|
422
|
+
if (promoted.size > 0) {
|
|
423
|
+
rawManifest.public = {
|
|
424
|
+
...rawManifest.public ?? {},
|
|
425
|
+
promote: Array.from(promoted).sort((left, right) => left.localeCompare(right))
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
await writeFile2(loadedManifest.manifestPath, stringifyYaml(rawManifest), "utf8");
|
|
429
|
+
return {
|
|
430
|
+
manifestPath: loadedManifest.manifestPath,
|
|
431
|
+
appliedMappings,
|
|
432
|
+
appliedPromotions
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/migrate/proposeMapping.ts
|
|
437
|
+
var SECRET_TOKENS = ["PASSWORD", "SECRET", "KEY", "TOKEN"];
|
|
438
|
+
function normalizeSegments(value) {
|
|
439
|
+
return value.split("_").map((segment) => segment.trim().toLowerCase()).filter((segment) => segment.length > 0);
|
|
440
|
+
}
|
|
441
|
+
function isSecretEnvVar(value) {
|
|
442
|
+
return SECRET_TOKENS.some((token) => value.includes(`_${token}`) || value.endsWith(token));
|
|
443
|
+
}
|
|
444
|
+
function proposeMapping(envVar) {
|
|
445
|
+
const framework = envVar.startsWith("VITE_") ? "vite" : envVar.startsWith("NEXT_PUBLIC_") ? "next" : void 0;
|
|
446
|
+
const strippedEnvVar = framework === "vite" ? envVar.slice("VITE_".length) : framework === "next" ? envVar.slice("NEXT_PUBLIC_".length) : envVar;
|
|
447
|
+
const namespace = isSecretEnvVar(strippedEnvVar) && !framework ? "secret" : "value";
|
|
448
|
+
const segments = normalizeSegments(strippedEnvVar);
|
|
449
|
+
const logicalPath = segments.join(".");
|
|
450
|
+
return {
|
|
451
|
+
envVar,
|
|
452
|
+
namespace,
|
|
453
|
+
logicalPath,
|
|
454
|
+
logicalKey: `${namespace}.${logicalPath}`,
|
|
455
|
+
public: Boolean(framework),
|
|
456
|
+
...framework ? {
|
|
457
|
+
framework
|
|
458
|
+
} : {}
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/migrate/rewriteSource.ts
|
|
463
|
+
import { copyFile, readFile, writeFile as writeFile3 } from "fs/promises";
|
|
464
|
+
function importStatementFor(kind) {
|
|
465
|
+
return kind === "import-meta-env" ? "import cnos from '@kitsy/cnos/browser';" : "import cnos from '@kitsy/cnos';";
|
|
466
|
+
}
|
|
467
|
+
function replacementFor(proposal) {
|
|
468
|
+
if (proposal.public) {
|
|
469
|
+
return `cnos.read(${JSON.stringify(`public.${proposal.logicalPath}`)})`;
|
|
470
|
+
}
|
|
471
|
+
return proposal.namespace === "secret" ? `cnos.secret(${JSON.stringify(proposal.logicalPath)})` : `cnos.value(${JSON.stringify(proposal.logicalPath)})`;
|
|
472
|
+
}
|
|
473
|
+
async function rewriteSourceFiles(usages, proposals) {
|
|
474
|
+
const fileGroups = /* @__PURE__ */ new Map();
|
|
475
|
+
for (const usage of usages) {
|
|
476
|
+
const existing = fileGroups.get(usage.filePath) ?? [];
|
|
477
|
+
existing.push(usage);
|
|
478
|
+
fileGroups.set(usage.filePath, existing);
|
|
479
|
+
}
|
|
480
|
+
const rewrittenFiles = [];
|
|
481
|
+
const backupFiles = [];
|
|
482
|
+
const skippedUsages = [];
|
|
483
|
+
for (const [filePath, fileUsages] of fileGroups.entries()) {
|
|
484
|
+
const original = await readFile(filePath, "utf8");
|
|
485
|
+
let nextSource = original;
|
|
486
|
+
let changed = false;
|
|
487
|
+
const importKinds = /* @__PURE__ */ new Set();
|
|
488
|
+
for (const usage of fileUsages) {
|
|
489
|
+
const proposal = proposals.get(usage.envVar);
|
|
490
|
+
if (!proposal) {
|
|
491
|
+
skippedUsages.push(`${filePath}:${usage.source}`);
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (usage.kind === "import-meta-env" && proposal.namespace === "secret") {
|
|
495
|
+
skippedUsages.push(`${filePath}:${usage.source}`);
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const replacement = replacementFor(proposal);
|
|
499
|
+
if (!nextSource.includes(usage.source)) {
|
|
500
|
+
skippedUsages.push(`${filePath}:${usage.source}`);
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
nextSource = nextSource.split(usage.source).join(replacement);
|
|
504
|
+
changed = true;
|
|
505
|
+
importKinds.add(usage.kind);
|
|
506
|
+
}
|
|
507
|
+
if (!changed) {
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
const backupPath = `${filePath}.bak`;
|
|
511
|
+
await copyFile(filePath, backupPath);
|
|
512
|
+
backupFiles.push(backupPath);
|
|
513
|
+
for (const kind of Array.from(importKinds)) {
|
|
514
|
+
const importStatement = importStatementFor(kind);
|
|
515
|
+
if (!nextSource.includes(importStatement)) {
|
|
516
|
+
nextSource = `${importStatement}
|
|
517
|
+
${nextSource}`;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
await writeFile3(filePath, nextSource, "utf8");
|
|
521
|
+
rewrittenFiles.push(filePath);
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
rewrittenFiles: rewrittenFiles.sort((left, right) => left.localeCompare(right)),
|
|
525
|
+
backupFiles: backupFiles.sort((left, right) => left.localeCompare(right)),
|
|
526
|
+
skippedUsages: skippedUsages.sort((left, right) => left.localeCompare(right))
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// src/migrate/scanEnvUsage.ts
|
|
531
|
+
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
532
|
+
import path2 from "path";
|
|
533
|
+
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"]);
|
|
534
|
+
var PROCESS_ENV_DOT = /process\.env\.([A-Z][A-Z0-9_]*)/g;
|
|
535
|
+
var PROCESS_ENV_BRACKET = /process\.env\[['"]([A-Z][A-Z0-9_]*)['"]\]/g;
|
|
536
|
+
var IMPORT_META_ENV_DOT = /import\.meta\.env\.([A-Z][A-Z0-9_]*)/g;
|
|
537
|
+
var IMPORT_META_ENV_BRACKET = /import\.meta\.env\[['"]([A-Z][A-Z0-9_]*)['"]\]/g;
|
|
538
|
+
async function collectFiles(root) {
|
|
539
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
540
|
+
const files = [];
|
|
541
|
+
for (const entry of entries) {
|
|
542
|
+
const filePath = path2.join(root, entry.name);
|
|
543
|
+
if (entry.isDirectory()) {
|
|
544
|
+
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
files.push(...await collectFiles(filePath));
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (SOURCE_EXTENSIONS.has(path2.extname(entry.name))) {
|
|
551
|
+
files.push(filePath);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return files;
|
|
555
|
+
}
|
|
556
|
+
function collectMatches(filePath, source, pattern, kind) {
|
|
557
|
+
const matches = [];
|
|
558
|
+
for (const match of source.matchAll(pattern)) {
|
|
559
|
+
const envVar = match[1];
|
|
560
|
+
if (!envVar) {
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
matches.push({
|
|
564
|
+
filePath,
|
|
565
|
+
envVar,
|
|
566
|
+
source: match[0],
|
|
567
|
+
kind
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
return matches;
|
|
571
|
+
}
|
|
572
|
+
async function scanEnvUsage(scanRoot) {
|
|
573
|
+
const files = await collectFiles(scanRoot);
|
|
574
|
+
const usages = [];
|
|
575
|
+
for (const filePath of files) {
|
|
576
|
+
const source = await readFile2(filePath, "utf8");
|
|
577
|
+
usages.push(...collectMatches(filePath, source, PROCESS_ENV_DOT, "process-env"));
|
|
578
|
+
usages.push(...collectMatches(filePath, source, PROCESS_ENV_BRACKET, "process-env"));
|
|
579
|
+
usages.push(...collectMatches(filePath, source, IMPORT_META_ENV_DOT, "import-meta-env"));
|
|
580
|
+
usages.push(...collectMatches(filePath, source, IMPORT_META_ENV_BRACKET, "import-meta-env"));
|
|
581
|
+
}
|
|
582
|
+
return usages.sort((left, right) => {
|
|
583
|
+
const byFile = left.filePath.localeCompare(right.filePath);
|
|
584
|
+
if (byFile !== 0) {
|
|
585
|
+
return byFile;
|
|
586
|
+
}
|
|
587
|
+
return left.envVar.localeCompare(right.envVar);
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/watch/diffGraphs.ts
|
|
592
|
+
function stableStringify(value) {
|
|
593
|
+
if (Array.isArray(value)) {
|
|
594
|
+
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
595
|
+
}
|
|
596
|
+
if (value && typeof value === "object") {
|
|
597
|
+
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`).join(",")}}`;
|
|
598
|
+
}
|
|
599
|
+
return JSON.stringify(value);
|
|
600
|
+
}
|
|
601
|
+
function diffGraphs(previous, next) {
|
|
602
|
+
const keys = /* @__PURE__ */ new Set([
|
|
603
|
+
...Array.from(previous.entries.keys()),
|
|
604
|
+
...Array.from(next.entries.keys())
|
|
605
|
+
]);
|
|
606
|
+
return Array.from(keys).filter((key) => !key.startsWith("meta.")).filter((key) => {
|
|
607
|
+
const previousEntry = previous.entries.get(key);
|
|
608
|
+
const nextEntry = next.entries.get(key);
|
|
609
|
+
if (!previousEntry || !nextEntry) {
|
|
610
|
+
return previousEntry?.namespace !== "meta" && nextEntry?.namespace !== "meta";
|
|
611
|
+
}
|
|
612
|
+
return stableStringify(previousEntry.value) !== stableStringify(nextEntry.value);
|
|
613
|
+
}).sort((left, right) => left.localeCompare(right));
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// src/watch/watchFiles.ts
|
|
617
|
+
import path3 from "path";
|
|
618
|
+
async function watchFiles(runtime, root) {
|
|
619
|
+
const manifest = await loadManifest(root ? { root } : {});
|
|
620
|
+
const roots = Array.from(
|
|
621
|
+
new Set(runtime.graph.workspace.workspaceRoots.map((workspaceRoot) => workspaceRoot.path))
|
|
622
|
+
).sort((left, right) => left.localeCompare(right));
|
|
623
|
+
const files = Array.from(
|
|
624
|
+
new Set(
|
|
625
|
+
Array.from(runtime.graph.entries.values()).map((entry) => entry.winner.origin?.file).filter((file) => Boolean(file)).map((file) => path3.resolve(manifest.repoRoot, file))
|
|
626
|
+
)
|
|
627
|
+
).sort((left, right) => left.localeCompare(right));
|
|
628
|
+
return {
|
|
629
|
+
manifestPath: manifest.manifestPath,
|
|
630
|
+
roots,
|
|
631
|
+
files
|
|
632
|
+
};
|
|
633
|
+
}
|
|
28
634
|
export {
|
|
29
635
|
CNOS_GRAPH_ENV_VAR,
|
|
636
|
+
CNOS_SECRET_PAYLOAD_ENV_VAR,
|
|
637
|
+
CNOS_SESSION_KEY_ENV_VAR,
|
|
638
|
+
CnosAuthenticationError,
|
|
30
639
|
CnosSecurityError,
|
|
640
|
+
applyManifestMappings,
|
|
641
|
+
clearAllVaultSessionKeys,
|
|
642
|
+
clearVaultSessionKey,
|
|
643
|
+
compareSchemaToGraph,
|
|
31
644
|
createSecretVault,
|
|
645
|
+
createSecretVaultProvider,
|
|
646
|
+
deleteLocalSecret,
|
|
647
|
+
deriveVaultKey,
|
|
32
648
|
deserializeRuntimeGraph,
|
|
649
|
+
detectLegacyVaultFormat,
|
|
650
|
+
diffGraphs,
|
|
33
651
|
ensureProjectionAllowed,
|
|
34
652
|
flattenObject,
|
|
653
|
+
formatDriftReport,
|
|
654
|
+
generateCodegenContent,
|
|
35
655
|
getVaultPassphraseEnvVar,
|
|
656
|
+
getVaultSessionKeyEnvVar,
|
|
657
|
+
graphRequiresSecretHydration,
|
|
36
658
|
isPassphraseEnvRef,
|
|
659
|
+
isSecretReference,
|
|
660
|
+
listLocalSecrets,
|
|
37
661
|
listSecretVaults,
|
|
38
662
|
loadManifest,
|
|
39
663
|
parseYaml,
|
|
664
|
+
proposeMapping,
|
|
665
|
+
readKeychain,
|
|
666
|
+
readLocalSecret,
|
|
40
667
|
readRuntimeGraphFromEnv,
|
|
668
|
+
readVaultMetadata,
|
|
669
|
+
removeLocalVaultFiles,
|
|
670
|
+
resolveCodegenPaths,
|
|
41
671
|
resolveConfigDocumentPath,
|
|
42
672
|
resolveConfiguredVaultPassphrase,
|
|
43
673
|
resolveManifestRoot,
|
|
44
674
|
resolveSecretPassphrase,
|
|
45
675
|
resolveSecretStoreRoot,
|
|
46
676
|
resolveSecretVaultFile,
|
|
677
|
+
resolveVaultAccessKey,
|
|
678
|
+
resolveVaultAuth,
|
|
47
679
|
resolveVaultDefinition,
|
|
680
|
+
rewriteSourceFiles,
|
|
681
|
+
scanEnvUsage,
|
|
48
682
|
serializeRuntimeGraph,
|
|
683
|
+
serializeSecretPayload,
|
|
49
684
|
stringifyYaml,
|
|
50
685
|
validateRuntime,
|
|
51
|
-
|
|
686
|
+
watchFiles,
|
|
687
|
+
watchSchema,
|
|
688
|
+
writeCodegenOutput,
|
|
689
|
+
writeKeychain,
|
|
690
|
+
writeLocalSecret,
|
|
691
|
+
writeVaultSessionKey
|
|
52
692
|
};
|