@lorion-org/runtime-config-node 1.0.0-beta.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 +200 -0
- package/dist/index.cjs +586 -0
- package/dist/index.d.cts +174 -0
- package/dist/index.d.ts +174 -0
- package/dist/index.js +526 -0
- package/examples/env-assignments.ts +21 -0
- package/examples/load-runtime-config-tree.ts +17 -0
- package/examples/query-runtime-config-tree.ts +53 -0
- package/examples/source-and-scope-files.ts +24 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import Ajv from "ajv";
|
|
5
|
+
import {
|
|
6
|
+
getRuntimeConfigScope,
|
|
7
|
+
projectSectionedRuntimeConfig,
|
|
8
|
+
resolveRuntimeConfigValueFromRuntimeConfig,
|
|
9
|
+
projectRuntimeConfigEnvVars,
|
|
10
|
+
toRuntimeConfigFragment,
|
|
11
|
+
runtimeEnvVarsToShellAssignments
|
|
12
|
+
} from "@lorion-org/runtime-config";
|
|
13
|
+
var defaultRuntimeConfigFileName = "runtime.config.json";
|
|
14
|
+
var defaultRuntimeConfigDirName = "runtime-config";
|
|
15
|
+
var defaultRuntimeConfigSchemaFileName = "runtime-config.schema.json";
|
|
16
|
+
function toPosixPath(filePath) {
|
|
17
|
+
return filePath.split(path.sep).join("/");
|
|
18
|
+
}
|
|
19
|
+
function escapeRegex(value) {
|
|
20
|
+
return value.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
21
|
+
}
|
|
22
|
+
function countWildcards(pattern) {
|
|
23
|
+
return (pattern.match(/\*/g) ?? []).length;
|
|
24
|
+
}
|
|
25
|
+
function assertSingleWildcardPattern(pattern) {
|
|
26
|
+
const wildcardCount = countWildcards(pattern);
|
|
27
|
+
if (wildcardCount !== 1) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`RuntimeConfig path pattern must contain exactly one "*" wildcard: "${pattern}"`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function createGlobSegmentRegex(segment) {
|
|
34
|
+
return new RegExp(`^${segment.split("*").map(escapeRegex).join("[^/\\\\]*")}$`);
|
|
35
|
+
}
|
|
36
|
+
function splitAbsolutePattern(pattern) {
|
|
37
|
+
const absolutePattern = path.resolve(pattern);
|
|
38
|
+
const parsed = path.parse(absolutePattern);
|
|
39
|
+
const relativePattern = path.relative(parsed.root, absolutePattern);
|
|
40
|
+
return {
|
|
41
|
+
root: parsed.root,
|
|
42
|
+
segments: relativePattern.split(/[\\/]+/).filter(Boolean)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function expandRuntimeConfigPattern(pattern) {
|
|
46
|
+
assertSingleWildcardPattern(pattern);
|
|
47
|
+
const { root, segments } = splitAbsolutePattern(pattern);
|
|
48
|
+
const visit = (currentDir, index) => {
|
|
49
|
+
const segment = segments[index];
|
|
50
|
+
if (!segment) return [];
|
|
51
|
+
const isLast = index === segments.length - 1;
|
|
52
|
+
if (!segment.includes("*")) {
|
|
53
|
+
const nextPath = path.join(currentDir, segment);
|
|
54
|
+
if (isLast) return existsSync(nextPath) ? [nextPath] : [];
|
|
55
|
+
if (!existsSync(nextPath)) return [];
|
|
56
|
+
return visit(nextPath, index + 1);
|
|
57
|
+
}
|
|
58
|
+
if (!existsSync(currentDir)) return [];
|
|
59
|
+
const matcher = createGlobSegmentRegex(segment);
|
|
60
|
+
return readdirSync(currentDir, { withFileTypes: true }).filter((entry) => entry.name !== "node_modules" && matcher.test(entry.name)).flatMap((entry) => {
|
|
61
|
+
const nextPath = path.join(currentDir, entry.name);
|
|
62
|
+
if (isLast) return entry.isFile() ? [nextPath] : [];
|
|
63
|
+
return entry.isDirectory() ? visit(nextPath, index + 1) : [];
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
return visit(root, 0);
|
|
67
|
+
}
|
|
68
|
+
function getRuntimeConfigScopeIdFromPattern(input) {
|
|
69
|
+
assertSingleWildcardPattern(input.pattern);
|
|
70
|
+
const resolvedPattern = path.resolve(input.pattern);
|
|
71
|
+
const wildcardIndex = resolvedPattern.indexOf("*");
|
|
72
|
+
const beforeWildcard = resolvedPattern.slice(0, wildcardIndex);
|
|
73
|
+
const afterWildcard = resolvedPattern.slice(wildcardIndex + 1);
|
|
74
|
+
const configPath = path.resolve(input.configPath);
|
|
75
|
+
if (!configPath.startsWith(beforeWildcard) || !configPath.endsWith(afterWildcard)) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`RuntimeConfig file "${input.configPath}" does not match pattern "${input.pattern}"`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return toPosixPath(
|
|
81
|
+
configPath.slice(beforeWildcard.length, configPath.length - afterWildcard.length)
|
|
82
|
+
).replace(/^\/+|\/+$/g, "");
|
|
83
|
+
}
|
|
84
|
+
function getRuntimeConfigPatternPublicRoot(pattern) {
|
|
85
|
+
assertSingleWildcardPattern(pattern);
|
|
86
|
+
const resolvedPattern = path.resolve(pattern);
|
|
87
|
+
const beforeWildcard = resolvedPattern.slice(0, resolvedPattern.indexOf("*"));
|
|
88
|
+
const wildcardParent = beforeWildcard.replace(/[\\/]+$/, "");
|
|
89
|
+
return path.resolve(wildcardParent, "public");
|
|
90
|
+
}
|
|
91
|
+
function getFileName(options) {
|
|
92
|
+
return options.fileName ?? defaultRuntimeConfigFileName;
|
|
93
|
+
}
|
|
94
|
+
function getRuntimeConfigDirName(options) {
|
|
95
|
+
return options.runtimeConfigDirName ?? defaultRuntimeConfigDirName;
|
|
96
|
+
}
|
|
97
|
+
function getRuntimeConfigPathOptions(source) {
|
|
98
|
+
return {
|
|
99
|
+
...source.fileName ? { fileName: source.fileName } : {},
|
|
100
|
+
...source.runtimeConfigDirName ? { runtimeConfigDirName: source.runtimeConfigDirName } : {}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function getSchemaFileName(options) {
|
|
104
|
+
return options.schemaFileName ?? defaultRuntimeConfigSchemaFileName;
|
|
105
|
+
}
|
|
106
|
+
function stripJsonBom(text) {
|
|
107
|
+
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
108
|
+
}
|
|
109
|
+
function ensureParentDir(filePath) {
|
|
110
|
+
const dirPath = path.dirname(filePath);
|
|
111
|
+
if (!existsSync(dirPath)) mkdirSync(dirPath, { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
function readTextFile(filePath) {
|
|
114
|
+
return existsSync(filePath) ? readFileSync(filePath, "utf8") : void 0;
|
|
115
|
+
}
|
|
116
|
+
function writeTextFile(filePath, data, options = {}) {
|
|
117
|
+
if (options.createDir !== false) ensureParentDir(filePath);
|
|
118
|
+
writeFileSync(filePath, data, "utf8");
|
|
119
|
+
}
|
|
120
|
+
function readJsonFile(filePath, options = {}) {
|
|
121
|
+
const text = readTextFile(filePath);
|
|
122
|
+
if (text === void 0) return void 0;
|
|
123
|
+
try {
|
|
124
|
+
return JSON.parse(stripJsonBom(text));
|
|
125
|
+
} catch (error) {
|
|
126
|
+
options.onParseError?.(error, filePath);
|
|
127
|
+
return options.defaultValue;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function readRequiredJsonFile(filePath) {
|
|
131
|
+
const text = readTextFile(filePath);
|
|
132
|
+
if (text === void 0) {
|
|
133
|
+
throw new Error(`RuntimeConfig JSON file not found: "${filePath}"`);
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
return JSON.parse(stripJsonBom(text));
|
|
137
|
+
} catch (error) {
|
|
138
|
+
throw new Error(`RuntimeConfig JSON parse error in "${filePath}": ${String(error)}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function writeJsonFile(filePath, data, options = {}) {
|
|
142
|
+
const spacing = options.pretty === false ? void 0 : 2;
|
|
143
|
+
writeTextFile(filePath, `${JSON.stringify(data, void 0, spacing)}
|
|
144
|
+
`, options);
|
|
145
|
+
}
|
|
146
|
+
function resolveRuntimeConfigPaths(varDir, scopeId, options = {}) {
|
|
147
|
+
const runtimeConfigDir = path.join(varDir, getRuntimeConfigDirName(options));
|
|
148
|
+
const scopeDir = path.join(runtimeConfigDir, scopeId);
|
|
149
|
+
const filePath = path.join(scopeDir, getFileName(options));
|
|
150
|
+
return {
|
|
151
|
+
varDir,
|
|
152
|
+
runtimeConfigDir,
|
|
153
|
+
scopeDir,
|
|
154
|
+
filePath
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function resolveRuntimeConfigSource(options = {}) {
|
|
158
|
+
const envKey = options.envKey ?? "RUNTIME_CONFIG_VAR_DIR";
|
|
159
|
+
const varDir = String(
|
|
160
|
+
options.varDir ?? options.env?.[envKey] ?? options.defaultVarDir ?? ""
|
|
161
|
+
).trim();
|
|
162
|
+
if (!varDir) {
|
|
163
|
+
throw new Error(`RuntimeConfig varDir not found. Pass varDir or set ${envKey}.`);
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
varDir,
|
|
167
|
+
...options.fileName ? { fileName: options.fileName } : {},
|
|
168
|
+
...options.runtimeConfigDirName ? { runtimeConfigDirName: options.runtimeConfigDirName } : {}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function resolveRuntimeConfigFilePath(varDir, scopeId, filename, options = {}) {
|
|
172
|
+
const { scopeDir } = resolveRuntimeConfigPaths(varDir, scopeId, options);
|
|
173
|
+
return path.join(scopeDir, filename);
|
|
174
|
+
}
|
|
175
|
+
function resolveRuntimeConfigScopeFilePath(source, scopeId, fileName) {
|
|
176
|
+
return resolveRuntimeConfigFilePath(
|
|
177
|
+
source.varDir,
|
|
178
|
+
scopeId,
|
|
179
|
+
fileName,
|
|
180
|
+
getRuntimeConfigPathOptions(source)
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
function readRuntimeConfigScopeJson(source, scopeId, fileName, options = {}) {
|
|
184
|
+
return readJsonFile(resolveRuntimeConfigScopeFilePath(source, scopeId, fileName), options);
|
|
185
|
+
}
|
|
186
|
+
function writeRuntimeConfigScopeJson(source, scopeId, fileName, data, options = {}) {
|
|
187
|
+
writeJsonFile(resolveRuntimeConfigScopeFilePath(source, scopeId, fileName), data, options);
|
|
188
|
+
}
|
|
189
|
+
function resolveRuntimeConfigPublicRootPath(source) {
|
|
190
|
+
return path.resolve(
|
|
191
|
+
source.varDir,
|
|
192
|
+
getRuntimeConfigDirName(getRuntimeConfigPathOptions(source)),
|
|
193
|
+
"public"
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
function resolveRuntimeConfigPublicFilePath(source, relativePath) {
|
|
197
|
+
return path.resolve(resolveRuntimeConfigPublicRootPath(source), relativePath);
|
|
198
|
+
}
|
|
199
|
+
function resolveRuntimeConfigSourceFiles(source) {
|
|
200
|
+
return source.paths.flatMap(
|
|
201
|
+
(pattern) => expandRuntimeConfigPattern(pattern).map((configPath) => ({
|
|
202
|
+
configPath,
|
|
203
|
+
pattern,
|
|
204
|
+
scopeId: getRuntimeConfigScopeIdFromPattern({ configPath, pattern })
|
|
205
|
+
}))
|
|
206
|
+
).filter((entry, index, entries) => {
|
|
207
|
+
return entries.findIndex((candidate) => candidate.configPath === entry.configPath) === index;
|
|
208
|
+
}).sort((left, right) => left.configPath.localeCompare(right.configPath));
|
|
209
|
+
}
|
|
210
|
+
function loadRuntimeConfigSourceTree(source) {
|
|
211
|
+
const fragments = /* @__PURE__ */ new Map();
|
|
212
|
+
for (const entry of resolveRuntimeConfigSourceFiles(source)) {
|
|
213
|
+
const config = readJsonFile(entry.configPath);
|
|
214
|
+
if (config) fragments.set(entry.scopeId, config);
|
|
215
|
+
}
|
|
216
|
+
return fragments;
|
|
217
|
+
}
|
|
218
|
+
function resolveRuntimeConfigSourcePublicRootPath(source) {
|
|
219
|
+
const pattern = source.paths[0];
|
|
220
|
+
return pattern ? getRuntimeConfigPatternPublicRoot(pattern) : void 0;
|
|
221
|
+
}
|
|
222
|
+
function listRuntimeConfigScopeFiles(varDir, scopeId, subdir, options = {}) {
|
|
223
|
+
const dirPath = resolveRuntimeConfigFilePath(varDir, scopeId, subdir, options);
|
|
224
|
+
if (!existsSync(dirPath)) return [];
|
|
225
|
+
return readdirSync(dirPath, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).filter((name) => options.extension ? name.endsWith(options.extension) : true);
|
|
226
|
+
}
|
|
227
|
+
function collectRuntimeConfigFragmentFiles(runtimeConfigDir, options = {}) {
|
|
228
|
+
const fileName = getFileName(options);
|
|
229
|
+
const files = [];
|
|
230
|
+
if (!existsSync(runtimeConfigDir)) return files;
|
|
231
|
+
const walk = (currentPath) => {
|
|
232
|
+
for (const entry of readdirSync(currentPath, { withFileTypes: true })) {
|
|
233
|
+
const absolutePath = path.resolve(currentPath, entry.name);
|
|
234
|
+
if (entry.isDirectory()) {
|
|
235
|
+
walk(absolutePath);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (entry.isFile() && entry.name === fileName) {
|
|
239
|
+
files.push(absolutePath);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
walk(runtimeConfigDir);
|
|
244
|
+
return files.sort((left, right) => left.localeCompare(right));
|
|
245
|
+
}
|
|
246
|
+
function parseRuntimeConfigFragmentFiles(filePaths, runtimeConfigDir, options = {}) {
|
|
247
|
+
const fileName = getFileName(options);
|
|
248
|
+
const fragments = /* @__PURE__ */ new Map();
|
|
249
|
+
for (const filePath of filePaths) {
|
|
250
|
+
const relativePath = toPosixPath(path.relative(runtimeConfigDir, filePath));
|
|
251
|
+
const scopeId = relativePath.replace(
|
|
252
|
+
new RegExp(`/${fileName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`),
|
|
253
|
+
""
|
|
254
|
+
);
|
|
255
|
+
const config = readJsonFile(filePath);
|
|
256
|
+
if (config) fragments.set(scopeId, config);
|
|
257
|
+
}
|
|
258
|
+
return fragments;
|
|
259
|
+
}
|
|
260
|
+
function loadRuntimeConfigFragment(varDir, scopeId, options = {}) {
|
|
261
|
+
const { filePath } = resolveRuntimeConfigPaths(varDir, scopeId, options);
|
|
262
|
+
return readJsonFile(filePath);
|
|
263
|
+
}
|
|
264
|
+
function writeRuntimeConfigFragment(varDir, scopeId, config, options = {}) {
|
|
265
|
+
const { filePath } = resolveRuntimeConfigPaths(varDir, scopeId, options);
|
|
266
|
+
writeJsonFile(filePath, config, options);
|
|
267
|
+
}
|
|
268
|
+
function formatRuntimeConfigSchemaValidationError(target, validationError) {
|
|
269
|
+
const jsonPath = validationError.instancePath || "/";
|
|
270
|
+
const ajvError = `${validationError.keyword}${validationError.message ? `: ${validationError.message}` : ""}`;
|
|
271
|
+
return new Error(
|
|
272
|
+
[
|
|
273
|
+
"RuntimeConfig schema validation failed.",
|
|
274
|
+
`Scope: ${target.scopeId}`,
|
|
275
|
+
`File: ${target.configPath}`,
|
|
276
|
+
`JSON path: ${jsonPath}`,
|
|
277
|
+
`Schema error: ${ajvError}`
|
|
278
|
+
].join("\n")
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
function validateRuntimeConfigSchemaTargets(targets, options = {}) {
|
|
282
|
+
const ajv = new Ajv({
|
|
283
|
+
strict: false,
|
|
284
|
+
allErrors: false,
|
|
285
|
+
...options.ajvOptions
|
|
286
|
+
});
|
|
287
|
+
const formatError = options.formatError ?? formatRuntimeConfigSchemaValidationError;
|
|
288
|
+
for (const target of targets) {
|
|
289
|
+
if (!existsSync(target.schemaPath) || !existsSync(target.configPath)) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const schema = readRequiredJsonFile(target.schemaPath);
|
|
293
|
+
const runtimeConfig = readRequiredJsonFile(target.configPath);
|
|
294
|
+
const validate = ajv.compile(schema);
|
|
295
|
+
const isValid = validate(runtimeConfig);
|
|
296
|
+
if (!isValid) {
|
|
297
|
+
const validationError = validate.errors?.[0];
|
|
298
|
+
if (validationError) {
|
|
299
|
+
throw formatError(target, validationError);
|
|
300
|
+
}
|
|
301
|
+
throw new Error(
|
|
302
|
+
`RuntimeConfig schema validation failed for "${target.scopeId}" (${target.configPath})`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function loadRuntimeConfigTree(varDir, options = {}) {
|
|
308
|
+
const runtimeConfigDir = path.join(varDir, getRuntimeConfigDirName(options));
|
|
309
|
+
const filePaths = collectRuntimeConfigFragmentFiles(runtimeConfigDir, options);
|
|
310
|
+
return parseRuntimeConfigFragmentFiles(filePaths, runtimeConfigDir, options);
|
|
311
|
+
}
|
|
312
|
+
function loadRuntimeConfigEnvVars(varDir, options = {}) {
|
|
313
|
+
const { fileName, runtimeConfigDirName, ...envOptions } = options;
|
|
314
|
+
return projectRuntimeConfigEnvVars(
|
|
315
|
+
loadRuntimeConfigTree(varDir, {
|
|
316
|
+
...fileName ? { fileName } : {},
|
|
317
|
+
...runtimeConfigDirName ? { runtimeConfigDirName } : {}
|
|
318
|
+
}),
|
|
319
|
+
envOptions
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
function loadRuntimeConfigShellAssignments(varDir, options = {}) {
|
|
323
|
+
return runtimeEnvVarsToShellAssignments(loadRuntimeConfigEnvVars(varDir, options));
|
|
324
|
+
}
|
|
325
|
+
function listRuntimeConfigFragments(varDir, options = {}) {
|
|
326
|
+
const { contextInputKey, ...pathOptions } = options;
|
|
327
|
+
const fragments = loadRuntimeConfigTree(varDir, pathOptions);
|
|
328
|
+
const scopes = Array.from(fragments.entries()).map(([scopeId, config]) => {
|
|
329
|
+
const fragment = contextInputKey ? toRuntimeConfigFragment(config, { contextInputKey }) : config;
|
|
330
|
+
return {
|
|
331
|
+
contextIds: Object.keys(fragment.contexts ?? {}).sort(),
|
|
332
|
+
path: resolveRuntimeConfigPaths(varDir, scopeId, pathOptions).filePath,
|
|
333
|
+
privateKeys: Object.keys(config.private ?? {}).sort(),
|
|
334
|
+
publicKeys: Object.keys(config.public ?? {}).sort(),
|
|
335
|
+
scopeId
|
|
336
|
+
};
|
|
337
|
+
}).sort((left, right) => left.scopeId.localeCompare(right.scopeId));
|
|
338
|
+
return {
|
|
339
|
+
fileName: getFileName(pathOptions),
|
|
340
|
+
scopes,
|
|
341
|
+
varDir
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function showRuntimeConfigFragment(varDir, scopeId, options = {}) {
|
|
345
|
+
const normalizedScopeId = scopeId.trim();
|
|
346
|
+
const config = normalizedScopeId ? loadRuntimeConfigFragment(varDir, normalizedScopeId, options) : void 0;
|
|
347
|
+
return {
|
|
348
|
+
fileName: getFileName(options),
|
|
349
|
+
path: resolveRuntimeConfigPaths(varDir, normalizedScopeId, options).filePath,
|
|
350
|
+
scopeId: normalizedScopeId,
|
|
351
|
+
varDir,
|
|
352
|
+
exists: Boolean(config),
|
|
353
|
+
...config ? { config } : {}
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
function projectRuntimeConfigTree(varDir, options = {}) {
|
|
357
|
+
const fragments = loadRuntimeConfigTree(varDir, options);
|
|
358
|
+
const scopeIds = options.scopeIds?.length ? options.scopeIds : Array.from(fragments.keys()).sort();
|
|
359
|
+
return {
|
|
360
|
+
fileName: getFileName(options),
|
|
361
|
+
runtimeConfig: projectSectionedRuntimeConfig(fragments, {
|
|
362
|
+
...options.contextInputKey ? { contextInputKey: options.contextInputKey } : {},
|
|
363
|
+
...options.contextOutputKey ? { contextOutputKey: options.contextOutputKey } : {},
|
|
364
|
+
scopeIds
|
|
365
|
+
}),
|
|
366
|
+
scopeIds,
|
|
367
|
+
varDir
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function getRuntimeConfigValue(varDir, scopeId, key, options = {}) {
|
|
371
|
+
const normalizedScopeId = scopeId.trim();
|
|
372
|
+
const normalizedKey = key.trim();
|
|
373
|
+
const visibility = options.visibility ?? "public";
|
|
374
|
+
const runtimeConfig = projectRuntimeConfigTree(varDir, {
|
|
375
|
+
...options,
|
|
376
|
+
scopeIds: normalizedScopeId ? [normalizedScopeId] : []
|
|
377
|
+
}).runtimeConfig;
|
|
378
|
+
const value = resolveRuntimeConfigValueFromRuntimeConfig(
|
|
379
|
+
runtimeConfig,
|
|
380
|
+
normalizedScopeId,
|
|
381
|
+
normalizedKey,
|
|
382
|
+
{
|
|
383
|
+
...options.contextId ? { contextId: options.contextId } : {},
|
|
384
|
+
...options.contextOutputKey ? { contextOutputKey: options.contextOutputKey } : {},
|
|
385
|
+
visibility
|
|
386
|
+
}
|
|
387
|
+
);
|
|
388
|
+
return {
|
|
389
|
+
fileName: getFileName(options),
|
|
390
|
+
key: normalizedKey,
|
|
391
|
+
scopeId: normalizedScopeId,
|
|
392
|
+
varDir,
|
|
393
|
+
visibility,
|
|
394
|
+
...options.contextId ? { contextId: options.contextId } : {},
|
|
395
|
+
exists: value !== void 0,
|
|
396
|
+
...value !== void 0 ? { value } : {}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function getRuntimeConfigScopeView(varDir, scopeId, options = {}) {
|
|
400
|
+
const normalizedScopeId = scopeId.trim();
|
|
401
|
+
const visibility = options.visibility ?? "public";
|
|
402
|
+
const runtimeConfig = projectRuntimeConfigTree(varDir, {
|
|
403
|
+
...options,
|
|
404
|
+
scopeIds: normalizedScopeId ? [normalizedScopeId] : []
|
|
405
|
+
}).runtimeConfig;
|
|
406
|
+
const scopeOptions = {
|
|
407
|
+
...options.contextId ? { contextId: options.contextId } : {},
|
|
408
|
+
...options.contextOutputKey ? { contextOutputKey: options.contextOutputKey } : {},
|
|
409
|
+
visibility
|
|
410
|
+
};
|
|
411
|
+
return {
|
|
412
|
+
config: getRuntimeConfigScope(runtimeConfig, normalizedScopeId, scopeOptions),
|
|
413
|
+
fileName: getFileName(options),
|
|
414
|
+
scopeId: normalizedScopeId,
|
|
415
|
+
varDir,
|
|
416
|
+
visibility,
|
|
417
|
+
...options.contextId ? { contextId: options.contextId } : {}
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
function validateRuntimeConfigScopes(varDir, targets, options = {}) {
|
|
421
|
+
const schemaFileName = getSchemaFileName(options);
|
|
422
|
+
const result = {
|
|
423
|
+
fileName: getFileName(options),
|
|
424
|
+
skipped: [],
|
|
425
|
+
validated: [],
|
|
426
|
+
varDir
|
|
427
|
+
};
|
|
428
|
+
for (const target of targets) {
|
|
429
|
+
const scopeId = target.scopeId.trim();
|
|
430
|
+
if (!scopeId) continue;
|
|
431
|
+
const configPath = resolveRuntimeConfigPaths(varDir, scopeId, options).filePath;
|
|
432
|
+
const schemaPath = target.cwd ? path.resolve(target.cwd, schemaFileName) : void 0;
|
|
433
|
+
if (!target.cwd || !schemaPath) {
|
|
434
|
+
result.skipped.push({ configPath, reason: "missing-scope-dir", scopeId });
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
if (!existsSync(configPath)) {
|
|
438
|
+
result.skipped.push({ configPath, reason: "missing-config", schemaPath, scopeId });
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
if (!existsSync(schemaPath)) {
|
|
442
|
+
result.skipped.push({ configPath, reason: "missing-schema", schemaPath, scopeId });
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
result.validated.push({ configPath, schemaPath, scopeId });
|
|
446
|
+
}
|
|
447
|
+
validateRuntimeConfigSchemaTargets(result.validated, {
|
|
448
|
+
...options.formatError ? { formatError: options.formatError } : {}
|
|
449
|
+
});
|
|
450
|
+
return result;
|
|
451
|
+
}
|
|
452
|
+
function validateRuntimeConfigSourceScopes(source, targets, options = {}) {
|
|
453
|
+
const schemaFileName = getSchemaFileName(options);
|
|
454
|
+
const filesByScope = /* @__PURE__ */ new Map();
|
|
455
|
+
const result = {
|
|
456
|
+
fileName: source.paths[0] ?? "",
|
|
457
|
+
skipped: [],
|
|
458
|
+
validated: [],
|
|
459
|
+
varDir: ""
|
|
460
|
+
};
|
|
461
|
+
for (const file of resolveRuntimeConfigSourceFiles(source)) {
|
|
462
|
+
filesByScope.set(file.scopeId, file.configPath);
|
|
463
|
+
}
|
|
464
|
+
for (const target of targets) {
|
|
465
|
+
const scopeId = target.scopeId.trim();
|
|
466
|
+
if (!scopeId) continue;
|
|
467
|
+
const configPath = filesByScope.get(scopeId);
|
|
468
|
+
const schemaPath = target.cwd ? path.resolve(target.cwd, schemaFileName) : void 0;
|
|
469
|
+
if (!target.cwd || !schemaPath) {
|
|
470
|
+
result.skipped.push({
|
|
471
|
+
...configPath ? { configPath } : {},
|
|
472
|
+
reason: "missing-scope-dir",
|
|
473
|
+
scopeId
|
|
474
|
+
});
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (!configPath) {
|
|
478
|
+
result.skipped.push({ reason: "missing-config", schemaPath, scopeId });
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
if (!existsSync(schemaPath)) {
|
|
482
|
+
result.skipped.push({ configPath, reason: "missing-schema", schemaPath, scopeId });
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
result.validated.push({ configPath, schemaPath, scopeId });
|
|
486
|
+
}
|
|
487
|
+
validateRuntimeConfigSchemaTargets(result.validated, {
|
|
488
|
+
...options.formatError ? { formatError: options.formatError } : {}
|
|
489
|
+
});
|
|
490
|
+
return result;
|
|
491
|
+
}
|
|
492
|
+
export {
|
|
493
|
+
collectRuntimeConfigFragmentFiles,
|
|
494
|
+
ensureParentDir,
|
|
495
|
+
getRuntimeConfigScopeView,
|
|
496
|
+
getRuntimeConfigValue,
|
|
497
|
+
listRuntimeConfigFragments,
|
|
498
|
+
listRuntimeConfigScopeFiles,
|
|
499
|
+
loadRuntimeConfigEnvVars,
|
|
500
|
+
loadRuntimeConfigFragment,
|
|
501
|
+
loadRuntimeConfigShellAssignments,
|
|
502
|
+
loadRuntimeConfigSourceTree,
|
|
503
|
+
loadRuntimeConfigTree,
|
|
504
|
+
parseRuntimeConfigFragmentFiles,
|
|
505
|
+
projectRuntimeConfigTree,
|
|
506
|
+
readJsonFile,
|
|
507
|
+
readRequiredJsonFile,
|
|
508
|
+
readRuntimeConfigScopeJson,
|
|
509
|
+
readTextFile,
|
|
510
|
+
resolveRuntimeConfigFilePath,
|
|
511
|
+
resolveRuntimeConfigPaths,
|
|
512
|
+
resolveRuntimeConfigPublicFilePath,
|
|
513
|
+
resolveRuntimeConfigPublicRootPath,
|
|
514
|
+
resolveRuntimeConfigScopeFilePath,
|
|
515
|
+
resolveRuntimeConfigSource,
|
|
516
|
+
resolveRuntimeConfigSourceFiles,
|
|
517
|
+
resolveRuntimeConfigSourcePublicRootPath,
|
|
518
|
+
showRuntimeConfigFragment,
|
|
519
|
+
validateRuntimeConfigSchemaTargets,
|
|
520
|
+
validateRuntimeConfigScopes,
|
|
521
|
+
validateRuntimeConfigSourceScopes,
|
|
522
|
+
writeJsonFile,
|
|
523
|
+
writeRuntimeConfigFragment,
|
|
524
|
+
writeRuntimeConfigScopeJson,
|
|
525
|
+
writeTextFile
|
|
526
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadRuntimeConfigShellAssignments,
|
|
3
|
+
writeRuntimeConfigFragment,
|
|
4
|
+
} from '@lorion-org/runtime-config-node';
|
|
5
|
+
|
|
6
|
+
writeRuntimeConfigFragment('./var', 'billing', {
|
|
7
|
+
public: {
|
|
8
|
+
apiBase: '/api/billing',
|
|
9
|
+
},
|
|
10
|
+
private: {
|
|
11
|
+
token: 'billing-token',
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const assignments = loadRuntimeConfigShellAssignments('./var', {
|
|
16
|
+
prefix: 'APP',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
console.log(assignments);
|
|
20
|
+
// APP_PUBLIC_BILLING_API_BASE='"/api/billing"'
|
|
21
|
+
// APP_PRIVATE_BILLING_TOKEN='"billing-token"'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { projectSectionedRuntimeConfig } from '@lorion-org/runtime-config';
|
|
2
|
+
import { loadRuntimeConfigTree, writeRuntimeConfigFragment } from '@lorion-org/runtime-config-node';
|
|
3
|
+
|
|
4
|
+
writeRuntimeConfigFragment('./var', 'billing', {
|
|
5
|
+
public: {
|
|
6
|
+
apiBase: '/api/billing',
|
|
7
|
+
},
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const fragments = loadRuntimeConfigTree('./var');
|
|
11
|
+
const runtimeConfig = projectSectionedRuntimeConfig(fragments);
|
|
12
|
+
|
|
13
|
+
console.log(runtimeConfig.public);
|
|
14
|
+
// { billingApiBase: '/api/billing' }
|
|
15
|
+
|
|
16
|
+
console.log(runtimeConfig.private);
|
|
17
|
+
// {}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getRuntimeConfigScopeView,
|
|
3
|
+
getRuntimeConfigValue,
|
|
4
|
+
listRuntimeConfigFragments,
|
|
5
|
+
projectRuntimeConfigTree,
|
|
6
|
+
writeRuntimeConfigFragment,
|
|
7
|
+
} from '@lorion-org/runtime-config-node';
|
|
8
|
+
|
|
9
|
+
writeRuntimeConfigFragment('./var', 'billing', {
|
|
10
|
+
public: {
|
|
11
|
+
apiBase: '/api/billing',
|
|
12
|
+
},
|
|
13
|
+
private: {
|
|
14
|
+
token: 'billing-token',
|
|
15
|
+
},
|
|
16
|
+
contexts: {
|
|
17
|
+
tenantA: {
|
|
18
|
+
public: {
|
|
19
|
+
apiBase: '/tenant-a/billing',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const listResult = listRuntimeConfigFragments('./var');
|
|
26
|
+
console.log(listResult.scopes);
|
|
27
|
+
// [{ scopeId: 'billing', publicKeys: ['apiBase'], privateKeys: ['token'], contextIds: ['tenantA'], ... }]
|
|
28
|
+
|
|
29
|
+
const projectResult = projectRuntimeConfigTree('./var', {
|
|
30
|
+
contextOutputKey: '__tenants',
|
|
31
|
+
});
|
|
32
|
+
console.log(projectResult.runtimeConfig.public);
|
|
33
|
+
// {
|
|
34
|
+
// billingApiBase: '/api/billing',
|
|
35
|
+
// __tenants: {
|
|
36
|
+
// tenantA: {
|
|
37
|
+
// billingApiBase: '/tenant-a/billing',
|
|
38
|
+
// },
|
|
39
|
+
// },
|
|
40
|
+
// }
|
|
41
|
+
|
|
42
|
+
const valueResult = getRuntimeConfigValue('./var', 'billing', 'apiBase', {
|
|
43
|
+
contextId: 'tenantA',
|
|
44
|
+
contextOutputKey: '__tenants',
|
|
45
|
+
});
|
|
46
|
+
console.log(valueResult.value);
|
|
47
|
+
// /tenant-a/billing
|
|
48
|
+
|
|
49
|
+
const scopeResult = getRuntimeConfigScopeView('./var', 'billing', {
|
|
50
|
+
visibility: 'private',
|
|
51
|
+
});
|
|
52
|
+
console.log(scopeResult.config);
|
|
53
|
+
// { token: 'billing-token' }
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readRuntimeConfigScopeJson,
|
|
3
|
+
resolveRuntimeConfigPublicFilePath,
|
|
4
|
+
resolveRuntimeConfigSource,
|
|
5
|
+
writeRuntimeConfigScopeJson,
|
|
6
|
+
} from '@lorion-org/runtime-config-node';
|
|
7
|
+
|
|
8
|
+
const source = resolveRuntimeConfigSource({
|
|
9
|
+
defaultVarDir: './var',
|
|
10
|
+
env: process.env,
|
|
11
|
+
envKey: 'APP_VAR_DIR',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
writeRuntimeConfigScopeJson(source, 'billing', 'settings.json', {
|
|
15
|
+
apiBase: '/api/billing',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const settings = readRuntimeConfigScopeJson(source, 'billing', 'settings.json');
|
|
19
|
+
console.log(settings);
|
|
20
|
+
// { apiBase: '/api/billing' }
|
|
21
|
+
|
|
22
|
+
const logoPath = resolveRuntimeConfigPublicFilePath(source, 'billing/logo.svg');
|
|
23
|
+
console.log(logoPath);
|
|
24
|
+
// /absolute/project/path/var/runtime-config/public/billing/logo.svg
|