@ant.sh/colony 0.1.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/LICENSE +21 -0
- package/README.md +172 -0
- package/dist/cjs/cli.js +281 -0
- package/dist/cjs/cli.js.map +7 -0
- package/dist/cjs/index.js +383 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/parser.js +319 -0
- package/dist/cjs/parser.js.map +7 -0
- package/dist/cjs/providers/aws.js +115 -0
- package/dist/cjs/providers/aws.js.map +7 -0
- package/dist/cjs/providers/openbao.js +49 -0
- package/dist/cjs/providers/openbao.js.map +7 -0
- package/dist/cjs/providers/vault-base.js +98 -0
- package/dist/cjs/providers/vault-base.js.map +7 -0
- package/dist/cjs/providers/vault.js +49 -0
- package/dist/cjs/providers/vault.js.map +7 -0
- package/dist/cjs/resolver.js +247 -0
- package/dist/cjs/resolver.js.map +7 -0
- package/dist/cjs/secrets.js +238 -0
- package/dist/cjs/secrets.js.map +7 -0
- package/dist/cjs/strings.js +99 -0
- package/dist/cjs/strings.js.map +7 -0
- package/dist/cjs/util.js +74 -0
- package/dist/cjs/util.js.map +7 -0
- package/dist/esm/cli.js +281 -0
- package/dist/esm/cli.js.map +7 -0
- package/dist/esm/index.d.ts +342 -0
- package/dist/esm/index.js +347 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/parser.js +286 -0
- package/dist/esm/parser.js.map +7 -0
- package/dist/esm/providers/aws.js +82 -0
- package/dist/esm/providers/aws.js.map +7 -0
- package/dist/esm/providers/openbao.js +26 -0
- package/dist/esm/providers/openbao.js.map +7 -0
- package/dist/esm/providers/vault-base.js +75 -0
- package/dist/esm/providers/vault-base.js.map +7 -0
- package/dist/esm/providers/vault.js +26 -0
- package/dist/esm/providers/vault.js.map +7 -0
- package/dist/esm/resolver.js +224 -0
- package/dist/esm/resolver.js.map +7 -0
- package/dist/esm/secrets.js +209 -0
- package/dist/esm/secrets.js.map +7 -0
- package/dist/esm/strings.js +75 -0
- package/dist/esm/strings.js.map +7 -0
- package/dist/esm/util.js +47 -0
- package/dist/esm/util.js.map +7 -0
- package/package.json +66 -0
- package/src/cli.js +353 -0
- package/src/index.d.ts +342 -0
- package/src/index.js +473 -0
- package/src/parser.js +381 -0
- package/src/providers/aws.js +112 -0
- package/src/providers/openbao.js +32 -0
- package/src/providers/vault-base.js +92 -0
- package/src/providers/vault.js +31 -0
- package/src/resolver.js +286 -0
- package/src/secrets.js +313 -0
- package/src/strings.js +84 -0
- package/src/util.js +49 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/providers/vault.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * HashiCorp Vault provider for colony\n */\n\nimport { VaultCompatibleProvider } from \"./vault-base.js\";\n\n/**\n * @class VaultProvider\n * @property {string} prefix - Provider prefix (\"VAULT\")\n */\nexport class VaultProvider extends VaultCompatibleProvider {\n /**\n * @param {object} options\n * @param {string=} options.addr - Vault address (default: process.env.VAULT_ADDR or \"http://127.0.0.1:8200\")\n * @param {string=} options.token - Vault token (default: process.env.VAULT_TOKEN)\n * @param {string=} options.namespace - Vault namespace (default: process.env.VAULT_NAMESPACE)\n * @param {number=} options.timeout - Request timeout in ms (default: 30000)\n */\n constructor(options = {}) {\n super(\n {\n prefix: \"VAULT\",\n addrEnvVar: \"VAULT_ADDR\",\n tokenEnvVar: \"VAULT_TOKEN\",\n namespaceEnvVar: \"VAULT_NAMESPACE\",\n errorPrefix: \"Vault\",\n },\n options\n );\n }\n}\n"],
|
|
5
|
+
"mappings": "AAIA,SAAS,+BAA+B;AAMjC,MAAM,sBAAsB,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQzD,YAAY,UAAU,CAAC,GAAG;AACxB;AAAA,MACE;AAAA,QACE,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,aAAa;AAAA,MACf;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { applyInterpolationDeep } from "./strings.js";
|
|
2
|
+
import { getDeep, setDeep, deepMerge, isPlainObject } from "./util.js";
|
|
3
|
+
function resolveRules({ rules, dims, ctx, vars, allowedEnvVars = null, allowedVars = null, warnings = [] }) {
|
|
4
|
+
const indexed = [];
|
|
5
|
+
for (const r of rules) {
|
|
6
|
+
const scope = r.keySegments.slice(0, dims.length);
|
|
7
|
+
const keyPath = r.keySegments.slice(dims.length);
|
|
8
|
+
if (scope.length !== dims.length || keyPath.length === 0) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
`${r.filePath}:${r.line}: Key must have ${dims.length} scope segments + at least one key segment: ${r.keyRaw}`
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
indexed.push({
|
|
14
|
+
...r,
|
|
15
|
+
scope,
|
|
16
|
+
keyPath,
|
|
17
|
+
keyPathStr: keyPath.join(".")
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const ctxScope = dims.map((d) => String(ctx[d] ?? ""));
|
|
21
|
+
const candidatesByKey = /* @__PURE__ */ new Map();
|
|
22
|
+
const postOps = [];
|
|
23
|
+
for (const r of indexed) {
|
|
24
|
+
if (!matches(r.scope, ctxScope)) continue;
|
|
25
|
+
if (r.op === "+=" || r.op === "-=") postOps.push(r);
|
|
26
|
+
else {
|
|
27
|
+
if (!candidatesByKey.has(r.keyPathStr)) candidatesByKey.set(r.keyPathStr, []);
|
|
28
|
+
candidatesByKey.get(r.keyPathStr).push(r);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const out = {};
|
|
32
|
+
const trace = /* @__PURE__ */ new Map();
|
|
33
|
+
for (const [key, cand] of candidatesByKey.entries()) {
|
|
34
|
+
let winner = cand[0];
|
|
35
|
+
let best = specificity(winner.scope);
|
|
36
|
+
for (let i = 1; i < cand.length; i++) {
|
|
37
|
+
const s = specificity(cand[i].scope);
|
|
38
|
+
if (s > best) {
|
|
39
|
+
best = s;
|
|
40
|
+
winner = cand[i];
|
|
41
|
+
} else if (s === best) {
|
|
42
|
+
winner = cand[i];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const existing = getDeep(out, winner.keyPath);
|
|
46
|
+
if (winner.op === ":=") {
|
|
47
|
+
if (existing === void 0) {
|
|
48
|
+
setDeep(out, winner.keyPath, clone(winner.value));
|
|
49
|
+
trace.set(key, packTrace(winner, best));
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (winner.op === "|=") {
|
|
54
|
+
if (existing === void 0) {
|
|
55
|
+
setDeep(out, winner.keyPath, clone(winner.value));
|
|
56
|
+
} else if (isPlainObject(existing) && isPlainObject(winner.value)) {
|
|
57
|
+
setDeep(out, winner.keyPath, deepMerge(existing, winner.value));
|
|
58
|
+
} else {
|
|
59
|
+
setDeep(out, winner.keyPath, clone(winner.value));
|
|
60
|
+
}
|
|
61
|
+
trace.set(key, packTrace(winner, best));
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
setDeep(out, winner.keyPath, clone(winner.value));
|
|
65
|
+
trace.set(key, packTrace(winner, best));
|
|
66
|
+
}
|
|
67
|
+
postOps.sort((a, b) => specificity(a.scope) - specificity(b.scope));
|
|
68
|
+
for (const r of postOps) {
|
|
69
|
+
const key = r.keyPathStr;
|
|
70
|
+
const best = specificity(r.scope);
|
|
71
|
+
const existing = getDeep(out, r.keyPath);
|
|
72
|
+
const val = clone(r.value);
|
|
73
|
+
if (r.op === "+=") {
|
|
74
|
+
const add = Array.isArray(val) ? val : [val];
|
|
75
|
+
if (existing === void 0) setDeep(out, r.keyPath, add);
|
|
76
|
+
else if (Array.isArray(existing)) setDeep(out, r.keyPath, existing.concat(add));
|
|
77
|
+
else setDeep(out, r.keyPath, [existing].concat(add));
|
|
78
|
+
trace.set(key, packTrace(r, best));
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (r.op === "-=") {
|
|
82
|
+
const remove = new Set(Array.isArray(val) ? val : [val]);
|
|
83
|
+
if (Array.isArray(existing)) {
|
|
84
|
+
setDeep(out, r.keyPath, existing.filter((x) => !remove.has(x)));
|
|
85
|
+
trace.set(key, packTrace(r, best));
|
|
86
|
+
}
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const finalCfg = applyInterpolationDeep(out, { ctx, vars, allowedEnvVars, allowedVars, warnings });
|
|
91
|
+
Object.defineProperties(finalCfg, {
|
|
92
|
+
// Core methods
|
|
93
|
+
get: { enumerable: false, value: (p) => getByPath(finalCfg, p) },
|
|
94
|
+
explain: { enumerable: false, value: (p) => explainByPath(trace, p) },
|
|
95
|
+
// Serialization - returns a plain object copy without non-enumerable methods
|
|
96
|
+
toJSON: {
|
|
97
|
+
enumerable: false,
|
|
98
|
+
value: () => {
|
|
99
|
+
const plain = {};
|
|
100
|
+
for (const [k, v] of Object.entries(finalCfg)) {
|
|
101
|
+
plain[k] = clone(v);
|
|
102
|
+
}
|
|
103
|
+
return plain;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
// List all keys (dot-notation paths)
|
|
107
|
+
keys: {
|
|
108
|
+
enumerable: false,
|
|
109
|
+
value: () => collectKeys(finalCfg)
|
|
110
|
+
},
|
|
111
|
+
// Diff against another config
|
|
112
|
+
diff: {
|
|
113
|
+
enumerable: false,
|
|
114
|
+
value: (other) => diffConfigs(finalCfg, other)
|
|
115
|
+
},
|
|
116
|
+
// Internal trace data
|
|
117
|
+
_trace: { enumerable: false, value: trace }
|
|
118
|
+
});
|
|
119
|
+
return finalCfg;
|
|
120
|
+
}
|
|
121
|
+
function collectKeys(obj, prefix = "") {
|
|
122
|
+
const keys = [];
|
|
123
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
124
|
+
const path = prefix ? `${prefix}.${k}` : k;
|
|
125
|
+
if (isPlainObject(v)) {
|
|
126
|
+
keys.push(...collectKeys(v, path));
|
|
127
|
+
} else {
|
|
128
|
+
keys.push(path);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return keys.sort();
|
|
132
|
+
}
|
|
133
|
+
function diffConfigs(a, b) {
|
|
134
|
+
const aKeys = new Set(collectKeys(a));
|
|
135
|
+
const bKeys = new Set(collectKeys(b));
|
|
136
|
+
const added = [];
|
|
137
|
+
const removed = [];
|
|
138
|
+
const changed = [];
|
|
139
|
+
for (const key of bKeys) {
|
|
140
|
+
if (!aKeys.has(key)) {
|
|
141
|
+
added.push(key);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
for (const key of aKeys) {
|
|
145
|
+
if (!bKeys.has(key)) {
|
|
146
|
+
removed.push(key);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for (const key of aKeys) {
|
|
150
|
+
if (bKeys.has(key)) {
|
|
151
|
+
const aVal = getByPath(a, key);
|
|
152
|
+
const bVal = getByPath(b, key);
|
|
153
|
+
if (!deepEqual(aVal, bVal)) {
|
|
154
|
+
changed.push({ key, from: aVal, to: bVal });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
added: added.sort(),
|
|
160
|
+
removed: removed.sort(),
|
|
161
|
+
changed: changed.sort((x, y) => x.key.localeCompare(y.key))
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function deepEqual(a, b) {
|
|
165
|
+
if (a === b) return true;
|
|
166
|
+
if (typeof a !== typeof b) return false;
|
|
167
|
+
if (a === null || b === null) return a === b;
|
|
168
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
169
|
+
if (a.length !== b.length) return false;
|
|
170
|
+
return a.every((v, i) => deepEqual(v, b[i]));
|
|
171
|
+
}
|
|
172
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
173
|
+
const aKeys = Object.keys(a);
|
|
174
|
+
const bKeys = Object.keys(b);
|
|
175
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
176
|
+
return aKeys.every((k) => deepEqual(a[k], b[k]));
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
function getByPath(obj, p) {
|
|
181
|
+
const segs = String(p).split(".").filter(Boolean);
|
|
182
|
+
return getDeep(obj, segs);
|
|
183
|
+
}
|
|
184
|
+
function explainByPath(trace, p) {
|
|
185
|
+
const key = String(p);
|
|
186
|
+
return trace.get(key) ?? null;
|
|
187
|
+
}
|
|
188
|
+
function matches(ruleScope, ctxScope) {
|
|
189
|
+
for (let i = 0; i < ruleScope.length; i++) {
|
|
190
|
+
const r = String(ruleScope[i]);
|
|
191
|
+
const c = String(ctxScope[i]);
|
|
192
|
+
if (r === "*") continue;
|
|
193
|
+
if (r !== c) return false;
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
function specificity(ruleScope) {
|
|
198
|
+
let s = 0;
|
|
199
|
+
for (const seg of ruleScope) if (seg !== "*") s++;
|
|
200
|
+
return s;
|
|
201
|
+
}
|
|
202
|
+
function packTrace(rule, spec) {
|
|
203
|
+
return {
|
|
204
|
+
op: rule.op,
|
|
205
|
+
scope: rule.scope.map(String),
|
|
206
|
+
specificity: spec,
|
|
207
|
+
filePath: rule.filePath,
|
|
208
|
+
line: rule.line,
|
|
209
|
+
col: rule.col ?? 0,
|
|
210
|
+
keyRaw: rule.keyRaw,
|
|
211
|
+
// Source map style location
|
|
212
|
+
source: `${rule.filePath}:${rule.line}:${rule.col ?? 0}`
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function clone(v) {
|
|
216
|
+
if (v === null || v === void 0) return v;
|
|
217
|
+
if (Array.isArray(v)) return v.map(clone);
|
|
218
|
+
if (typeof v === "object") return structuredClone(v);
|
|
219
|
+
return v;
|
|
220
|
+
}
|
|
221
|
+
export {
|
|
222
|
+
resolveRules
|
|
223
|
+
};
|
|
224
|
+
//# sourceMappingURL=resolver.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/resolver.js"],
|
|
4
|
+
"sourcesContent": ["import { applyInterpolationDeep } from \"./strings.js\";\nimport { getDeep, setDeep, deepMerge, isPlainObject } from \"./util.js\";\n\nexport function resolveRules({ rules, dims, ctx, vars, allowedEnvVars = null, allowedVars = null, warnings = [] }) {\n const indexed = [];\n for (const r of rules) {\n const scope = r.keySegments.slice(0, dims.length);\n const keyPath = r.keySegments.slice(dims.length);\n\n if (scope.length !== dims.length || keyPath.length === 0) {\n throw new Error(\n `${r.filePath}:${r.line}: Key must have ${dims.length} scope segments + at least one key segment: ${r.keyRaw}`\n );\n }\n\n indexed.push({\n ...r,\n scope,\n keyPath,\n keyPathStr: keyPath.join(\".\"),\n });\n }\n\n const ctxScope = dims.map((d) => String(ctx[d] ?? \"\"));\n\n const candidatesByKey = new Map();\n const postOps = [];\n\n for (const r of indexed) {\n if (!matches(r.scope, ctxScope)) continue;\n\n if (r.op === \"+=\" || r.op === \"-=\") postOps.push(r);\n else {\n if (!candidatesByKey.has(r.keyPathStr)) candidatesByKey.set(r.keyPathStr, []);\n candidatesByKey.get(r.keyPathStr).push(r);\n }\n }\n\n const out = {};\n const trace = new Map();\n\n for (const [key, cand] of candidatesByKey.entries()) {\n let winner = cand[0];\n let best = specificity(winner.scope);\n for (let i = 1; i < cand.length; i++) {\n const s = specificity(cand[i].scope);\n if (s > best) {\n best = s;\n winner = cand[i];\n } else if (s === best) {\n winner = cand[i];\n }\n }\n\n const existing = getDeep(out, winner.keyPath);\n\n if (winner.op === \":=\") {\n if (existing === undefined) {\n setDeep(out, winner.keyPath, clone(winner.value));\n trace.set(key, packTrace(winner, best));\n }\n continue;\n }\n\n if (winner.op === \"|=\") {\n if (existing === undefined) {\n setDeep(out, winner.keyPath, clone(winner.value));\n } else if (isPlainObject(existing) && isPlainObject(winner.value)) {\n setDeep(out, winner.keyPath, deepMerge(existing, winner.value));\n } else {\n setDeep(out, winner.keyPath, clone(winner.value));\n }\n trace.set(key, packTrace(winner, best));\n continue;\n }\n\n setDeep(out, winner.keyPath, clone(winner.value));\n trace.set(key, packTrace(winner, best));\n }\n\n postOps.sort((a, b) => specificity(a.scope) - specificity(b.scope));\n\n for (const r of postOps) {\n const key = r.keyPathStr;\n const best = specificity(r.scope);\n\n const existing = getDeep(out, r.keyPath);\n const val = clone(r.value);\n\n if (r.op === \"+=\") {\n const add = Array.isArray(val) ? val : [val];\n if (existing === undefined) setDeep(out, r.keyPath, add);\n else if (Array.isArray(existing)) setDeep(out, r.keyPath, existing.concat(add));\n else setDeep(out, r.keyPath, [existing].concat(add));\n trace.set(key, packTrace(r, best));\n continue;\n }\n\n if (r.op === \"-=\") {\n const remove = new Set(Array.isArray(val) ? val : [val]);\n if (Array.isArray(existing)) {\n setDeep(out, r.keyPath, existing.filter((x) => !remove.has(x)));\n trace.set(key, packTrace(r, best));\n }\n continue;\n }\n }\n\n const finalCfg = applyInterpolationDeep(out, { ctx, vars, allowedEnvVars, allowedVars, warnings });\n\n Object.defineProperties(finalCfg, {\n // Core methods\n get: { enumerable: false, value: (p) => getByPath(finalCfg, p) },\n explain: { enumerable: false, value: (p) => explainByPath(trace, p) },\n\n // Serialization - returns a plain object copy without non-enumerable methods\n toJSON: {\n enumerable: false,\n value: () => {\n const plain = {};\n for (const [k, v] of Object.entries(finalCfg)) {\n plain[k] = clone(v);\n }\n return plain;\n },\n },\n\n // List all keys (dot-notation paths)\n keys: {\n enumerable: false,\n value: () => collectKeys(finalCfg),\n },\n\n // Diff against another config\n diff: {\n enumerable: false,\n value: (other) => diffConfigs(finalCfg, other),\n },\n\n // Internal trace data\n _trace: { enumerable: false, value: trace },\n });\n\n return finalCfg;\n}\n\n/**\n * Collect all leaf keys in dot notation\n * @param {object} obj\n * @param {string} prefix\n * @returns {string[]}\n */\nfunction collectKeys(obj, prefix = \"\") {\n const keys = [];\n\n for (const [k, v] of Object.entries(obj)) {\n const path = prefix ? `${prefix}.${k}` : k;\n\n if (isPlainObject(v)) {\n keys.push(...collectKeys(v, path));\n } else {\n keys.push(path);\n }\n }\n\n return keys.sort();\n}\n\n/**\n * Diff two configs, returning added, removed, and changed keys\n * @param {object} a - First config\n * @param {object} b - Second config\n * @returns {{ added: string[], removed: string[], changed: Array<{key: string, from: any, to: any}> }}\n */\nfunction diffConfigs(a, b) {\n const aKeys = new Set(collectKeys(a));\n const bKeys = new Set(collectKeys(b));\n\n const added = [];\n const removed = [];\n const changed = [];\n\n // Keys in b but not in a\n for (const key of bKeys) {\n if (!aKeys.has(key)) {\n added.push(key);\n }\n }\n\n // Keys in a but not in b\n for (const key of aKeys) {\n if (!bKeys.has(key)) {\n removed.push(key);\n }\n }\n\n // Keys in both - check for changes\n for (const key of aKeys) {\n if (bKeys.has(key)) {\n const aVal = getByPath(a, key);\n const bVal = getByPath(b, key);\n\n if (!deepEqual(aVal, bVal)) {\n changed.push({ key, from: aVal, to: bVal });\n }\n }\n }\n\n return {\n added: added.sort(),\n removed: removed.sort(),\n changed: changed.sort((x, y) => x.key.localeCompare(y.key)),\n };\n}\n\n/**\n * Deep equality check for config values.\n * Note: Does not handle circular references (will stack overflow).\n * Config values should never be circular in practice.\n */\nfunction deepEqual(a, b) {\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null) return a === b;\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((v, i) => deepEqual(v, b[i]));\n }\n\n if (typeof a === \"object\" && typeof b === \"object\") {\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((k) => deepEqual(a[k], b[k]));\n }\n\n return false;\n}\n\nfunction getByPath(obj, p) {\n const segs = String(p).split(\".\").filter(Boolean);\n return getDeep(obj, segs);\n}\n\nfunction explainByPath(trace, p) {\n const key = String(p);\n return trace.get(key) ?? null;\n}\n\nfunction matches(ruleScope, ctxScope) {\n for (let i = 0; i < ruleScope.length; i++) {\n const r = String(ruleScope[i]);\n const c = String(ctxScope[i]);\n if (r === \"*\") continue;\n if (r !== c) return false;\n }\n return true;\n}\n\nfunction specificity(ruleScope) {\n let s = 0;\n for (const seg of ruleScope) if (seg !== \"*\") s++;\n return s;\n}\n\nfunction packTrace(rule, spec) {\n return {\n op: rule.op,\n scope: rule.scope.map(String),\n specificity: spec,\n filePath: rule.filePath,\n line: rule.line,\n col: rule.col ?? 0,\n keyRaw: rule.keyRaw,\n // Source map style location\n source: `${rule.filePath}:${rule.line}:${rule.col ?? 0}`,\n };\n}\n\nfunction clone(v) {\n if (v === null || v === undefined) return v;\n if (Array.isArray(v)) return v.map(clone);\n if (typeof v === \"object\") return structuredClone(v);\n return v;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { isPlainObject } from "./util.js";
|
|
2
|
+
const globalRegistry = /* @__PURE__ */ new Map();
|
|
3
|
+
function registerSecretProvider(provider) {
|
|
4
|
+
if (!provider.prefix || typeof provider.fetch !== "function") {
|
|
5
|
+
throw new Error("Invalid provider: must have prefix and fetch()");
|
|
6
|
+
}
|
|
7
|
+
globalRegistry.set(provider.prefix.toUpperCase(), provider);
|
|
8
|
+
}
|
|
9
|
+
function unregisterSecretProvider(prefix) {
|
|
10
|
+
return globalRegistry.delete(prefix.toUpperCase());
|
|
11
|
+
}
|
|
12
|
+
function clearSecretProviders() {
|
|
13
|
+
globalRegistry.clear();
|
|
14
|
+
}
|
|
15
|
+
function hasGlobalProviders() {
|
|
16
|
+
return globalRegistry.size > 0;
|
|
17
|
+
}
|
|
18
|
+
class SecretCache {
|
|
19
|
+
constructor(maxSize = 100) {
|
|
20
|
+
this.maxSize = maxSize;
|
|
21
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
22
|
+
}
|
|
23
|
+
get(key) {
|
|
24
|
+
const entry = this.cache.get(key);
|
|
25
|
+
if (!entry) return void 0;
|
|
26
|
+
if (Date.now() > entry.expires) {
|
|
27
|
+
this.cache.delete(key);
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
this.cache.delete(key);
|
|
31
|
+
this.cache.set(key, entry);
|
|
32
|
+
return entry.value;
|
|
33
|
+
}
|
|
34
|
+
set(key, value, ttl) {
|
|
35
|
+
if (this.cache.size >= this.maxSize) {
|
|
36
|
+
const firstKey = this.cache.keys().next().value;
|
|
37
|
+
this.cache.delete(firstKey);
|
|
38
|
+
}
|
|
39
|
+
this.cache.set(key, { value, expires: Date.now() + ttl });
|
|
40
|
+
}
|
|
41
|
+
invalidate(pattern) {
|
|
42
|
+
if (!pattern) {
|
|
43
|
+
this.cache.clear();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const regex = new RegExp("^" + globToRegex(pattern) + "$");
|
|
47
|
+
for (const key of this.cache.keys()) {
|
|
48
|
+
if (regex.test(key)) this.cache.delete(key);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function globToRegex(pattern) {
|
|
53
|
+
return pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
54
|
+
}
|
|
55
|
+
const RX_SECRET = /\$\{([A-Z][A-Z0-9_]*):([^}]+)\}/g;
|
|
56
|
+
const RESERVED = /* @__PURE__ */ new Set(["ENV", "VAR"]);
|
|
57
|
+
function collectSecretRefs(value, refs = /* @__PURE__ */ new Map()) {
|
|
58
|
+
if (typeof value === "string") {
|
|
59
|
+
let match;
|
|
60
|
+
RX_SECRET.lastIndex = 0;
|
|
61
|
+
while ((match = RX_SECRET.exec(value)) !== null) {
|
|
62
|
+
const [, provider, key] = match;
|
|
63
|
+
if (RESERVED.has(provider)) continue;
|
|
64
|
+
const fullKey = `${provider}:${key.trim()}`;
|
|
65
|
+
if (!refs.has(fullKey)) {
|
|
66
|
+
refs.set(fullKey, { provider, key: key.trim() });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return refs;
|
|
70
|
+
}
|
|
71
|
+
if (Array.isArray(value)) {
|
|
72
|
+
for (const item of value) {
|
|
73
|
+
collectSecretRefs(item, refs);
|
|
74
|
+
}
|
|
75
|
+
return refs;
|
|
76
|
+
}
|
|
77
|
+
if (isPlainObject(value)) {
|
|
78
|
+
for (const v of Object.values(value)) {
|
|
79
|
+
collectSecretRefs(v, refs);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return refs;
|
|
83
|
+
}
|
|
84
|
+
function isAllowed(fullKey, allowedSecrets) {
|
|
85
|
+
if (allowedSecrets === null || allowedSecrets === void 0) return true;
|
|
86
|
+
for (const pattern of allowedSecrets) {
|
|
87
|
+
if (pattern === fullKey) return true;
|
|
88
|
+
const regex = new RegExp("^" + globToRegex(pattern) + "$");
|
|
89
|
+
if (regex.test(fullKey)) return true;
|
|
90
|
+
if (!pattern.includes(":")) {
|
|
91
|
+
const keyOnly = fullKey.split(":")[1];
|
|
92
|
+
const keyRegex = new RegExp("^" + globToRegex(pattern) + "$");
|
|
93
|
+
if (keyRegex.test(keyOnly)) return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
async function applySecretsDeep(value, options = {}) {
|
|
99
|
+
const {
|
|
100
|
+
providers = [],
|
|
101
|
+
allowedSecrets = null,
|
|
102
|
+
cache = null,
|
|
103
|
+
cacheTtl = 3e5,
|
|
104
|
+
onNotFound = "warn",
|
|
105
|
+
warnings = []
|
|
106
|
+
} = options;
|
|
107
|
+
const registry = new Map(globalRegistry);
|
|
108
|
+
for (const p of providers) {
|
|
109
|
+
registry.set(p.prefix.toUpperCase(), p);
|
|
110
|
+
}
|
|
111
|
+
const refs = collectSecretRefs(value);
|
|
112
|
+
if (refs.size === 0) return value;
|
|
113
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
114
|
+
const fetchPromises = [];
|
|
115
|
+
for (const [fullKey, { provider, key }] of refs) {
|
|
116
|
+
if (!isAllowed(fullKey, allowedSecrets)) {
|
|
117
|
+
warnings.push({
|
|
118
|
+
type: "blocked_secret",
|
|
119
|
+
provider,
|
|
120
|
+
key,
|
|
121
|
+
message: `Access to secret "${fullKey}" blocked by allowedSecrets`
|
|
122
|
+
});
|
|
123
|
+
resolved.set(fullKey, "");
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (cache) {
|
|
127
|
+
const cached = cache.get(fullKey);
|
|
128
|
+
if (cached !== void 0) {
|
|
129
|
+
resolved.set(fullKey, cached);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const providerInstance = registry.get(provider);
|
|
134
|
+
if (!providerInstance) {
|
|
135
|
+
warnings.push({
|
|
136
|
+
type: "unknown_provider",
|
|
137
|
+
provider,
|
|
138
|
+
key,
|
|
139
|
+
message: `No provider registered for "${provider}"`
|
|
140
|
+
});
|
|
141
|
+
resolved.set(fullKey, "");
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
fetchPromises.push(
|
|
145
|
+
providerInstance.fetch(key).then((val) => {
|
|
146
|
+
const strVal = val ?? "";
|
|
147
|
+
if (cache) cache.set(fullKey, strVal, cacheTtl);
|
|
148
|
+
resolved.set(fullKey, strVal);
|
|
149
|
+
}).catch((err) => {
|
|
150
|
+
const isNotFound = err.code === "NOT_FOUND" || err.name === "ResourceNotFoundException" || err.message?.includes("not found");
|
|
151
|
+
if (isNotFound) {
|
|
152
|
+
if (onNotFound === "error") {
|
|
153
|
+
throw new Error(`COLONY: Secret not found: ${fullKey}`);
|
|
154
|
+
}
|
|
155
|
+
warnings.push({
|
|
156
|
+
type: "secret_not_found",
|
|
157
|
+
provider,
|
|
158
|
+
key,
|
|
159
|
+
message: `Secret "${fullKey}" not found`
|
|
160
|
+
});
|
|
161
|
+
resolved.set(fullKey, "");
|
|
162
|
+
} else {
|
|
163
|
+
if (onNotFound === "error") {
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
warnings.push({
|
|
167
|
+
type: "secret_fetch_error",
|
|
168
|
+
provider,
|
|
169
|
+
key,
|
|
170
|
+
message: `Failed to fetch "${fullKey}": ${err.message}`
|
|
171
|
+
});
|
|
172
|
+
resolved.set(fullKey, "");
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
await Promise.all(fetchPromises);
|
|
178
|
+
return replaceSecrets(value, resolved);
|
|
179
|
+
}
|
|
180
|
+
function replaceSecrets(value, resolved) {
|
|
181
|
+
if (typeof value === "string") {
|
|
182
|
+
return value.replace(RX_SECRET, (match, provider, key) => {
|
|
183
|
+
if (RESERVED.has(provider)) return match;
|
|
184
|
+
const fullKey = `${provider}:${key.trim()}`;
|
|
185
|
+
return resolved.get(fullKey) ?? "";
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (Array.isArray(value)) {
|
|
189
|
+
return value.map((v) => replaceSecrets(v, resolved));
|
|
190
|
+
}
|
|
191
|
+
if (isPlainObject(value)) {
|
|
192
|
+
const out = {};
|
|
193
|
+
for (const [k, v] of Object.entries(value)) {
|
|
194
|
+
out[k] = replaceSecrets(v, resolved);
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
return value;
|
|
199
|
+
}
|
|
200
|
+
export {
|
|
201
|
+
SecretCache,
|
|
202
|
+
applySecretsDeep,
|
|
203
|
+
clearSecretProviders,
|
|
204
|
+
collectSecretRefs,
|
|
205
|
+
hasGlobalProviders,
|
|
206
|
+
registerSecretProvider,
|
|
207
|
+
unregisterSecretProvider
|
|
208
|
+
};
|
|
209
|
+
//# sourceMappingURL=secrets.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/secrets.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * Secrets provider system for colony\n */\n\nimport { isPlainObject } from \"./util.js\";\n\n// Global provider registry\nconst globalRegistry = new Map();\n\n/**\n * Register a secret provider globally\n * @param {object} provider - Provider with prefix and fetch()\n */\nexport function registerSecretProvider(provider) {\n if (!provider.prefix || typeof provider.fetch !== \"function\") {\n throw new Error(\"Invalid provider: must have prefix and fetch()\");\n }\n globalRegistry.set(provider.prefix.toUpperCase(), provider);\n}\n\n/**\n * Unregister a provider by prefix\n * @param {string} prefix - Provider prefix to remove\n * @returns {boolean} True if provider was removed\n */\nexport function unregisterSecretProvider(prefix) {\n return globalRegistry.delete(prefix.toUpperCase());\n}\n\n/**\n * Clear all registered providers\n */\nexport function clearSecretProviders() {\n globalRegistry.clear();\n}\n\n/**\n * Check if any global providers are registered\n * @returns {boolean}\n */\nexport function hasGlobalProviders() {\n return globalRegistry.size > 0;\n}\n\n/**\n * Simple LRU cache for secrets\n */\nexport class SecretCache {\n constructor(maxSize = 100) {\n this.maxSize = maxSize;\n this.cache = new Map();\n }\n\n get(key) {\n const entry = this.cache.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expires) {\n this.cache.delete(key);\n return undefined;\n }\n // Move to end for LRU behavior\n this.cache.delete(key);\n this.cache.set(key, entry);\n return entry.value;\n }\n\n set(key, value, ttl) {\n if (this.cache.size >= this.maxSize) {\n // Remove oldest (first) entry\n const firstKey = this.cache.keys().next().value;\n this.cache.delete(firstKey);\n }\n this.cache.set(key, { value, expires: Date.now() + ttl });\n }\n\n invalidate(pattern) {\n if (!pattern) {\n this.cache.clear();\n return;\n }\n const regex = new RegExp(\"^\" + globToRegex(pattern) + \"$\");\n for (const key of this.cache.keys()) {\n if (regex.test(key)) this.cache.delete(key);\n }\n }\n}\n\n/**\n * Convert a glob pattern to regex, escaping special chars except *\n * @param {string} pattern - Glob pattern (e.g., \"myapp/*\")\n * @returns {string} Regex pattern string\n */\nfunction globToRegex(pattern) {\n return pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\") // escape regex special chars\n .replace(/\\*/g, \".*\"); // convert glob * to regex .*\n}\n\n// Regex to match secret interpolations: ${PROVIDER:key}\n// Provider must start with uppercase letter, followed by uppercase letters, digits, or underscores\nconst RX_SECRET = /\\$\\{([A-Z][A-Z0-9_]*):([^}]+)\\}/g;\n\n// Reserved prefixes that are not secrets\nconst RESERVED = new Set([\"ENV\", \"VAR\"]);\n\n/**\n * Collect all secret references from a value tree\n * @param {any} value - Value to scan\n * @param {Map} refs - Map to collect refs into\n * @returns {Map} Map of fullKey -> { provider, key }\n */\nexport function collectSecretRefs(value, refs = new Map()) {\n if (typeof value === \"string\") {\n let match;\n RX_SECRET.lastIndex = 0;\n while ((match = RX_SECRET.exec(value)) !== null) {\n const [, provider, key] = match;\n if (RESERVED.has(provider)) continue;\n\n const fullKey = `${provider}:${key.trim()}`;\n if (!refs.has(fullKey)) {\n refs.set(fullKey, { provider, key: key.trim() });\n }\n }\n return refs;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n collectSecretRefs(item, refs);\n }\n return refs;\n }\n\n if (isPlainObject(value)) {\n for (const v of Object.values(value)) {\n collectSecretRefs(v, refs);\n }\n }\n\n return refs;\n}\n\n/**\n * Check if a secret key matches any allowed pattern\n * @param {string} fullKey - Full key like \"AWS:myapp/db\"\n * @param {string[]|null} allowedSecrets - Allowed patterns\n * @returns {boolean}\n */\nfunction isAllowed(fullKey, allowedSecrets) {\n if (allowedSecrets === null || allowedSecrets === undefined) return true;\n\n for (const pattern of allowedSecrets) {\n // Exact match\n if (pattern === fullKey) return true;\n\n // Glob pattern match on full key\n const regex = new RegExp(\"^\" + globToRegex(pattern) + \"$\");\n if (regex.test(fullKey)) return true;\n\n // Pattern without provider matches any provider\n if (!pattern.includes(\":\")) {\n const keyOnly = fullKey.split(\":\")[1];\n const keyRegex = new RegExp(\"^\" + globToRegex(pattern) + \"$\");\n if (keyRegex.test(keyOnly)) return true;\n }\n }\n\n return false;\n}\n\n/**\n * Fetch all secrets and apply to value tree\n * @param {any} value - Config value tree\n * @param {object} options - Options\n * @returns {Promise<any>} Value tree with secrets replaced\n */\nexport async function applySecretsDeep(value, options = {}) {\n const {\n providers = [],\n allowedSecrets = null,\n cache = null,\n cacheTtl = 300000,\n onNotFound = \"warn\",\n warnings = [],\n } = options;\n\n // Merge local providers with global registry\n const registry = new Map(globalRegistry);\n for (const p of providers) {\n registry.set(p.prefix.toUpperCase(), p);\n }\n\n // Collect all secret references\n const refs = collectSecretRefs(value);\n if (refs.size === 0) return value;\n\n // Fetch all secrets in parallel\n const resolved = new Map();\n const fetchPromises = [];\n\n for (const [fullKey, { provider, key }] of refs) {\n // Check allowlist\n if (!isAllowed(fullKey, allowedSecrets)) {\n warnings.push({\n type: \"blocked_secret\",\n provider,\n key,\n message: `Access to secret \"${fullKey}\" blocked by allowedSecrets`,\n });\n resolved.set(fullKey, \"\");\n continue;\n }\n\n // Check cache\n if (cache) {\n const cached = cache.get(fullKey);\n if (cached !== undefined) {\n resolved.set(fullKey, cached);\n continue;\n }\n }\n\n // Check provider exists\n const providerInstance = registry.get(provider);\n if (!providerInstance) {\n warnings.push({\n type: \"unknown_provider\",\n provider,\n key,\n message: `No provider registered for \"${provider}\"`,\n });\n resolved.set(fullKey, \"\");\n continue;\n }\n\n // Queue fetch\n fetchPromises.push(\n providerInstance\n .fetch(key)\n .then((val) => {\n const strVal = val ?? \"\";\n if (cache) cache.set(fullKey, strVal, cacheTtl);\n resolved.set(fullKey, strVal);\n })\n .catch((err) => {\n const isNotFound =\n err.code === \"NOT_FOUND\" ||\n err.name === \"ResourceNotFoundException\" ||\n err.message?.includes(\"not found\");\n\n if (isNotFound) {\n if (onNotFound === \"error\") {\n throw new Error(`COLONY: Secret not found: ${fullKey}`);\n }\n warnings.push({\n type: \"secret_not_found\",\n provider,\n key,\n message: `Secret \"${fullKey}\" not found`,\n });\n resolved.set(fullKey, \"\");\n } else {\n if (onNotFound === \"error\") {\n throw err;\n }\n warnings.push({\n type: \"secret_fetch_error\",\n provider,\n key,\n message: `Failed to fetch \"${fullKey}\": ${err.message}`,\n });\n resolved.set(fullKey, \"\");\n }\n })\n );\n }\n\n await Promise.all(fetchPromises);\n\n // Apply resolved secrets to value tree\n return replaceSecrets(value, resolved);\n}\n\n/**\n * Replace secret placeholders with resolved values\n * @param {any} value - Value to process\n * @param {Map} resolved - Map of fullKey -> resolved value\n * @returns {any} Value with secrets replaced\n */\nfunction replaceSecrets(value, resolved) {\n if (typeof value === \"string\") {\n return value.replace(RX_SECRET, (match, provider, key) => {\n if (RESERVED.has(provider)) return match;\n const fullKey = `${provider}:${key.trim()}`;\n return resolved.get(fullKey) ?? \"\";\n });\n }\n\n if (Array.isArray(value)) {\n return value.map((v) => replaceSecrets(v, resolved));\n }\n\n if (isPlainObject(value)) {\n const out = {};\n for (const [k, v] of Object.entries(value)) {\n out[k] = replaceSecrets(v, resolved);\n }\n return out;\n }\n\n return value;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { isPlainObject } from "./util.js";
|
|
2
|
+
const RX_SECRET_PROVIDER = /^[A-Z][A-Z0-9_]*:/;
|
|
3
|
+
function applyInterpolationDeep(value, { ctx, vars, allowedEnvVars = null, allowedVars = null, warnings = [] }) {
|
|
4
|
+
if (typeof value === "string") return interpolate(value, { ctx, vars, allowedEnvVars, allowedVars, warnings });
|
|
5
|
+
if (Array.isArray(value)) return value.map((v) => applyInterpolationDeep(v, { ctx, vars, allowedEnvVars, allowedVars, warnings }));
|
|
6
|
+
if (isPlainObject(value)) {
|
|
7
|
+
const out = {};
|
|
8
|
+
for (const [k, v] of Object.entries(value)) {
|
|
9
|
+
out[k] = applyInterpolationDeep(v, { ctx, vars, allowedEnvVars, allowedVars, warnings });
|
|
10
|
+
}
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function interpolate(s, { ctx, vars, allowedEnvVars = null, allowedVars = null, warnings = [] }) {
|
|
16
|
+
return s.replace(/\$\{([^}]+)\}/g, (match, exprRaw) => {
|
|
17
|
+
const expr = exprRaw.trim();
|
|
18
|
+
if (expr.startsWith("ENV:")) {
|
|
19
|
+
const k = expr.slice(4).trim();
|
|
20
|
+
if (allowedEnvVars !== null && !allowedEnvVars.includes(k)) {
|
|
21
|
+
warnings.push({
|
|
22
|
+
type: "blocked_env_var",
|
|
23
|
+
var: k,
|
|
24
|
+
message: `Access to environment variable "${k}" blocked by allowedEnvVars whitelist`
|
|
25
|
+
});
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
return process.env[k] ?? "";
|
|
29
|
+
}
|
|
30
|
+
if (expr.startsWith("VAR:")) {
|
|
31
|
+
const k = expr.slice(4).trim();
|
|
32
|
+
if (allowedVars !== null && !allowedVars.includes(k)) {
|
|
33
|
+
warnings.push({
|
|
34
|
+
type: "blocked_var",
|
|
35
|
+
var: k,
|
|
36
|
+
message: `Access to custom variable "${k}" blocked by allowedVars whitelist`
|
|
37
|
+
});
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
if (vars?.[k] === void 0) {
|
|
41
|
+
warnings.push({
|
|
42
|
+
type: "unknown_var",
|
|
43
|
+
var: k,
|
|
44
|
+
message: `Unknown VAR "${k}" in interpolation ${match}`
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return String(vars?.[k] ?? "");
|
|
48
|
+
}
|
|
49
|
+
if (expr.startsWith("ctx.")) {
|
|
50
|
+
const k = expr.slice(4).trim();
|
|
51
|
+
if (ctx?.[k] === void 0) {
|
|
52
|
+
warnings.push({
|
|
53
|
+
type: "unknown_ctx",
|
|
54
|
+
var: k,
|
|
55
|
+
message: `Unknown ctx dimension "${k}" in interpolation ${match}`
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return String(ctx?.[k] ?? "");
|
|
59
|
+
}
|
|
60
|
+
if (RX_SECRET_PROVIDER.test(expr)) {
|
|
61
|
+
return match;
|
|
62
|
+
}
|
|
63
|
+
warnings.push({
|
|
64
|
+
type: "unknown_interpolation",
|
|
65
|
+
pattern: match,
|
|
66
|
+
message: `Unknown interpolation pattern: ${match}`
|
|
67
|
+
});
|
|
68
|
+
return "";
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
applyInterpolationDeep,
|
|
73
|
+
interpolate
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=strings.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/strings.js"],
|
|
4
|
+
"sourcesContent": ["import { isPlainObject } from \"./util.js\";\n\n// Regex to detect secret provider patterns: ${PROVIDER:key}\n// Provider must start with uppercase letter, followed by uppercase letters, digits, or underscores\nconst RX_SECRET_PROVIDER = /^[A-Z][A-Z0-9_]*:/;\n\nexport function applyInterpolationDeep(value, { ctx, vars, allowedEnvVars = null, allowedVars = null, warnings = [] }) {\n if (typeof value === \"string\") return interpolate(value, { ctx, vars, allowedEnvVars, allowedVars, warnings });\n if (Array.isArray(value)) return value.map((v) => applyInterpolationDeep(v, { ctx, vars, allowedEnvVars, allowedVars, warnings }));\n if (isPlainObject(value)) {\n const out = {};\n for (const [k, v] of Object.entries(value)) {\n out[k] = applyInterpolationDeep(v, { ctx, vars, allowedEnvVars, allowedVars, warnings });\n }\n return out;\n }\n return value;\n}\n\nexport function interpolate(s, { ctx, vars, allowedEnvVars = null, allowedVars = null, warnings = [] }) {\n return s.replace(/\\$\\{([^}]+)\\}/g, (match, exprRaw) => {\n const expr = exprRaw.trim();\n\n if (expr.startsWith(\"ENV:\")) {\n const k = expr.slice(4).trim();\n // Security: check if env var is allowed\n if (allowedEnvVars !== null && !allowedEnvVars.includes(k)) {\n warnings.push({\n type: \"blocked_env_var\",\n var: k,\n message: `Access to environment variable \"${k}\" blocked by allowedEnvVars whitelist`,\n });\n return \"\";\n }\n return process.env[k] ?? \"\";\n }\n\n if (expr.startsWith(\"VAR:\")) {\n const k = expr.slice(4).trim();\n // Security: check if custom var is allowed\n if (allowedVars !== null && !allowedVars.includes(k)) {\n warnings.push({\n type: \"blocked_var\",\n var: k,\n message: `Access to custom variable \"${k}\" blocked by allowedVars whitelist`,\n });\n return \"\";\n }\n if (vars?.[k] === undefined) {\n warnings.push({\n type: \"unknown_var\",\n var: k,\n message: `Unknown VAR \"${k}\" in interpolation ${match}`,\n });\n }\n return String(vars?.[k] ?? \"\");\n }\n\n if (expr.startsWith(\"ctx.\")) {\n const k = expr.slice(4).trim();\n if (ctx?.[k] === undefined) {\n warnings.push({\n type: \"unknown_ctx\",\n var: k,\n message: `Unknown ctx dimension \"${k}\" in interpolation ${match}`,\n });\n }\n return String(ctx?.[k] ?? \"\");\n }\n\n // Secret provider patterns (e.g., ${AWS:...}, ${OPENBAO:...}) - leave for secrets.js\n if (RX_SECRET_PROVIDER.test(expr)) {\n return match; // Keep the pattern intact for later secret processing\n }\n\n // Unknown interpolation pattern\n warnings.push({\n type: \"unknown_interpolation\",\n pattern: match,\n message: `Unknown interpolation pattern: ${match}`,\n });\n return \"\";\n });\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,qBAAqB;AAI9B,MAAM,qBAAqB;AAEpB,SAAS,uBAAuB,OAAO,EAAE,KAAK,MAAM,iBAAiB,MAAM,cAAc,MAAM,WAAW,CAAC,EAAE,GAAG;AACrH,MAAI,OAAO,UAAU,SAAU,QAAO,YAAY,OAAO,EAAE,KAAK,MAAM,gBAAgB,aAAa,SAAS,CAAC;AAC7G,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,CAAC,MAAM,uBAAuB,GAAG,EAAE,KAAK,MAAM,gBAAgB,aAAa,SAAS,CAAC,CAAC;AACjI,MAAI,cAAc,KAAK,GAAG;AACxB,UAAM,MAAM,CAAC;AACb,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,UAAI,CAAC,IAAI,uBAAuB,GAAG,EAAE,KAAK,MAAM,gBAAgB,aAAa,SAAS,CAAC;AAAA,IACzF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,YAAY,GAAG,EAAE,KAAK,MAAM,iBAAiB,MAAM,cAAc,MAAM,WAAW,CAAC,EAAE,GAAG;AACtG,SAAO,EAAE,QAAQ,kBAAkB,CAAC,OAAO,YAAY;AACrD,UAAM,OAAO,QAAQ,KAAK;AAE1B,QAAI,KAAK,WAAW,MAAM,GAAG;AAC3B,YAAM,IAAI,KAAK,MAAM,CAAC,EAAE,KAAK;AAE7B,UAAI,mBAAmB,QAAQ,CAAC,eAAe,SAAS,CAAC,GAAG;AAC1D,iBAAS,KAAK;AAAA,UACZ,MAAM;AAAA,UACN,KAAK;AAAA,UACL,SAAS,mCAAmC,CAAC;AAAA,QAC/C,CAAC;AACD,eAAO;AAAA,MACT;AACA,aAAO,QAAQ,IAAI,CAAC,KAAK;AAAA,IAC3B;AAEA,QAAI,KAAK,WAAW,MAAM,GAAG;AAC3B,YAAM,IAAI,KAAK,MAAM,CAAC,EAAE,KAAK;AAE7B,UAAI,gBAAgB,QAAQ,CAAC,YAAY,SAAS,CAAC,GAAG;AACpD,iBAAS,KAAK;AAAA,UACZ,MAAM;AAAA,UACN,KAAK;AAAA,UACL,SAAS,8BAA8B,CAAC;AAAA,QAC1C,CAAC;AACD,eAAO;AAAA,MACT;AACA,UAAI,OAAO,CAAC,MAAM,QAAW;AAC3B,iBAAS,KAAK;AAAA,UACZ,MAAM;AAAA,UACN,KAAK;AAAA,UACL,SAAS,gBAAgB,CAAC,sBAAsB,KAAK;AAAA,QACvD,CAAC;AAAA,MACH;AACA,aAAO,OAAO,OAAO,CAAC,KAAK,EAAE;AAAA,IAC/B;AAEA,QAAI,KAAK,WAAW,MAAM,GAAG;AAC3B,YAAM,IAAI,KAAK,MAAM,CAAC,EAAE,KAAK;AAC7B,UAAI,MAAM,CAAC,MAAM,QAAW;AAC1B,iBAAS,KAAK;AAAA,UACZ,MAAM;AAAA,UACN,KAAK;AAAA,UACL,SAAS,0BAA0B,CAAC,sBAAsB,KAAK;AAAA,QACjE,CAAC;AAAA,MACH;AACA,aAAO,OAAO,MAAM,CAAC,KAAK,EAAE;AAAA,IAC9B;AAGA,QAAI,mBAAmB,KAAK,IAAI,GAAG;AACjC,aAAO;AAAA,IACT;AAGA,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS,kCAAkC,KAAK;AAAA,IAClD,CAAC;AACD,WAAO;AAAA,EACT,CAAC;AACH;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/esm/util.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
function isPlainObject(x) {
|
|
2
|
+
return x !== null && typeof x === "object" && !Array.isArray(x);
|
|
3
|
+
}
|
|
4
|
+
function getDeep(obj, pathSegs) {
|
|
5
|
+
let cur = obj;
|
|
6
|
+
for (const k of pathSegs) {
|
|
7
|
+
if (!isPlainObject(cur) && !Array.isArray(cur)) return void 0;
|
|
8
|
+
cur = cur[k];
|
|
9
|
+
if (cur === void 0) return void 0;
|
|
10
|
+
}
|
|
11
|
+
return cur;
|
|
12
|
+
}
|
|
13
|
+
function setDeep(obj, pathSegs, value) {
|
|
14
|
+
let cur = obj;
|
|
15
|
+
for (let i = 0; i < pathSegs.length; i++) {
|
|
16
|
+
const k = pathSegs[i];
|
|
17
|
+
if (i === pathSegs.length - 1) {
|
|
18
|
+
cur[k] = value;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (!isPlainObject(cur[k])) cur[k] = {};
|
|
22
|
+
cur = cur[k];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function deepMerge(a, b) {
|
|
26
|
+
if (Array.isArray(a) && Array.isArray(b)) return b;
|
|
27
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
28
|
+
const out = { ...a };
|
|
29
|
+
for (const [k, v] of Object.entries(b)) {
|
|
30
|
+
out[k] = k in out ? deepMerge(out[k], v) : v;
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
return b;
|
|
35
|
+
}
|
|
36
|
+
function getByPath(obj, path) {
|
|
37
|
+
const parts = String(path).split(".").filter(Boolean);
|
|
38
|
+
return getDeep(obj, parts);
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
deepMerge,
|
|
42
|
+
getByPath,
|
|
43
|
+
getDeep,
|
|
44
|
+
isPlainObject,
|
|
45
|
+
setDeep
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=util.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/util.js"],
|
|
4
|
+
"sourcesContent": ["export function isPlainObject(x) {\n return x !== null && typeof x === \"object\" && !Array.isArray(x);\n}\n\nexport function getDeep(obj, pathSegs) {\n let cur = obj;\n for (const k of pathSegs) {\n if (!isPlainObject(cur) && !Array.isArray(cur)) return undefined;\n cur = cur[k];\n if (cur === undefined) return undefined;\n }\n return cur;\n}\n\nexport function setDeep(obj, pathSegs, value) {\n let cur = obj;\n for (let i = 0; i < pathSegs.length; i++) {\n const k = pathSegs[i];\n if (i === pathSegs.length - 1) {\n cur[k] = value;\n return;\n }\n if (!isPlainObject(cur[k])) cur[k] = {};\n cur = cur[k];\n }\n}\n\nexport function deepMerge(a, b) {\n if (Array.isArray(a) && Array.isArray(b)) return b;\n if (isPlainObject(a) && isPlainObject(b)) {\n const out = { ...a };\n for (const [k, v] of Object.entries(b)) {\n out[k] = k in out ? deepMerge(out[k], v) : v;\n }\n return out;\n }\n return b;\n}\n\n/**\n * Get a value from an object using dot-notation path string\n * @param {object} obj - Object to traverse\n * @param {string} path - Dot-separated path (e.g., \"database.password\")\n * @returns {any}\n */\nexport function getByPath(obj, path) {\n const parts = String(path).split(\".\").filter(Boolean);\n return getDeep(obj, parts);\n}\n"],
|
|
5
|
+
"mappings": "AAAO,SAAS,cAAc,GAAG;AAC/B,SAAO,MAAM,QAAQ,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC;AAChE;AAEO,SAAS,QAAQ,KAAK,UAAU;AACrC,MAAI,MAAM;AACV,aAAW,KAAK,UAAU;AACxB,QAAI,CAAC,cAAc,GAAG,KAAK,CAAC,MAAM,QAAQ,GAAG,EAAG,QAAO;AACvD,UAAM,IAAI,CAAC;AACX,QAAI,QAAQ,OAAW,QAAO;AAAA,EAChC;AACA,SAAO;AACT;AAEO,SAAS,QAAQ,KAAK,UAAU,OAAO;AAC5C,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,IAAI,SAAS,CAAC;AACpB,QAAI,MAAM,SAAS,SAAS,GAAG;AAC7B,UAAI,CAAC,IAAI;AACT;AAAA,IACF;AACA,QAAI,CAAC,cAAc,IAAI,CAAC,CAAC,EAAG,KAAI,CAAC,IAAI,CAAC;AACtC,UAAM,IAAI,CAAC;AAAA,EACb;AACF;AAEO,SAAS,UAAU,GAAG,GAAG;AAC9B,MAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,EAAG,QAAO;AACjD,MAAI,cAAc,CAAC,KAAK,cAAc,CAAC,GAAG;AACxC,UAAM,MAAM,EAAE,GAAG,EAAE;AACnB,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,CAAC,GAAG;AACtC,UAAI,CAAC,IAAI,KAAK,MAAM,UAAU,IAAI,CAAC,GAAG,CAAC,IAAI;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAQO,SAAS,UAAU,KAAK,MAAM;AACnC,QAAM,QAAQ,OAAO,IAAI,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO;AACpD,SAAO,QAAQ,KAAK,KAAK;AAC3B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|