@lorion-org/runtime-config-node 1.0.0-beta.0 → 1.0.0-beta.1

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/dist/index.js CHANGED
@@ -8,7 +8,10 @@ import {
8
8
  resolveRuntimeConfigValueFromRuntimeConfig,
9
9
  projectRuntimeConfigEnvVars,
10
10
  toRuntimeConfigFragment,
11
- runtimeEnvVarsToShellAssignments
11
+ runtimeEnvVarsToShellAssignments,
12
+ resolveRuntimeConfigValidationMode,
13
+ shouldRegisterRuntimeConfigValidationSchema,
14
+ shouldRequireRuntimeConfigAtStartup
12
15
  } from "@lorion-org/runtime-config";
13
16
  var defaultRuntimeConfigFileName = "runtime.config.json";
14
17
  var defaultRuntimeConfigDirName = "runtime-config";
@@ -103,6 +106,35 @@ function getRuntimeConfigPathOptions(source) {
103
106
  function getSchemaFileName(options) {
104
107
  return options.schemaFileName ?? defaultRuntimeConfigSchemaFileName;
105
108
  }
109
+ function formatMissingRuntimeConfigValidationError(skipped) {
110
+ if (skipped.reason === "missing-schema") {
111
+ return new Error(
112
+ [
113
+ `RuntimeConfig schema file missing for scope "${skipped.scopeId}".`,
114
+ ...skipped.schemaPath ? [`missing schema file: ${skipped.schemaPath}`] : [],
115
+ ...skipped.configPath ? [`runtime config file: ${skipped.configPath}`] : []
116
+ ].join(" ")
117
+ );
118
+ }
119
+ if (skipped.reason === "missing-config") {
120
+ return new Error(
121
+ [
122
+ `RuntimeConfig file missing for scope "${skipped.scopeId}".`,
123
+ ...skipped.configPath ? [`expected runtime config file: ${skipped.configPath}`] : [],
124
+ ...skipped.schemaPath ? [`schema file: ${skipped.schemaPath}`] : []
125
+ ].join(" ")
126
+ );
127
+ }
128
+ const details = [
129
+ `RuntimeConfig validation target has no schema directory for scope "${skipped.scopeId}".`
130
+ ];
131
+ if (skipped.configPath) details.push(`runtime config file: ${skipped.configPath}`);
132
+ if (skipped.schemaPath) details.push(`schema file: ${skipped.schemaPath}`);
133
+ return new Error(details.join(" "));
134
+ }
135
+ function shouldReportMissingRuntimeConfig(mode) {
136
+ return mode !== "optional";
137
+ }
106
138
  function stripJsonBom(text) {
107
139
  return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
108
140
  }
@@ -279,12 +311,19 @@ function formatRuntimeConfigSchemaValidationError(target, validationError) {
279
311
  );
280
312
  }
281
313
  function validateRuntimeConfigSchemaTargets(targets, options = {}) {
314
+ const invalid = collectRuntimeConfigSchemaTargetValidationErrors(targets, options);
315
+ if (invalid[0]) {
316
+ throw invalid[0].error;
317
+ }
318
+ }
319
+ function collectRuntimeConfigSchemaTargetValidationErrors(targets, options = {}) {
282
320
  const ajv = new Ajv({
283
321
  strict: false,
284
322
  allErrors: false,
285
323
  ...options.ajvOptions
286
324
  });
287
325
  const formatError = options.formatError ?? formatRuntimeConfigSchemaValidationError;
326
+ const invalid = [];
288
327
  for (const target of targets) {
289
328
  if (!existsSync(target.schemaPath) || !existsSync(target.configPath)) {
290
329
  continue;
@@ -296,13 +335,21 @@ function validateRuntimeConfigSchemaTargets(targets, options = {}) {
296
335
  if (!isValid) {
297
336
  const validationError = validate.errors?.[0];
298
337
  if (validationError) {
299
- throw formatError(target, validationError);
338
+ invalid.push({
339
+ ...target,
340
+ error: formatError(target, validationError)
341
+ });
342
+ continue;
300
343
  }
301
- throw new Error(
302
- `RuntimeConfig schema validation failed for "${target.scopeId}" (${target.configPath})`
303
- );
344
+ invalid.push({
345
+ ...target,
346
+ error: new Error(
347
+ `RuntimeConfig schema validation failed for "${target.scopeId}" (${target.configPath})`
348
+ )
349
+ });
304
350
  }
305
351
  }
352
+ return invalid;
306
353
  }
307
354
  function loadRuntimeConfigTree(varDir, options = {}) {
308
355
  const runtimeConfigDir = path.join(varDir, getRuntimeConfigDirName(options));
@@ -421,21 +468,28 @@ function validateRuntimeConfigScopes(varDir, targets, options = {}) {
421
468
  const schemaFileName = getSchemaFileName(options);
422
469
  const result = {
423
470
  fileName: getFileName(options),
471
+ invalid: [],
424
472
  skipped: [],
425
473
  validated: [],
426
474
  varDir
427
475
  };
428
476
  for (const target of targets) {
477
+ if (!shouldRegisterRuntimeConfigValidationSchema(target.policy)) continue;
429
478
  const scopeId = target.scopeId.trim();
430
479
  if (!scopeId) continue;
480
+ const mode = resolveRuntimeConfigValidationMode(target.policy);
431
481
  const configPath = resolveRuntimeConfigPaths(varDir, scopeId, options).filePath;
432
482
  const schemaPath = target.cwd ? path.resolve(target.cwd, schemaFileName) : void 0;
433
483
  if (!target.cwd || !schemaPath) {
434
- result.skipped.push({ configPath, reason: "missing-scope-dir", scopeId });
484
+ if (shouldReportMissingRuntimeConfig(mode)) {
485
+ result.skipped.push({ configPath, reason: "missing-scope-dir", scopeId });
486
+ }
435
487
  continue;
436
488
  }
437
489
  if (!existsSync(configPath)) {
438
- result.skipped.push({ configPath, reason: "missing-config", schemaPath, scopeId });
490
+ if (shouldReportMissingRuntimeConfig(mode)) {
491
+ result.skipped.push({ configPath, reason: "missing-config", schemaPath, scopeId });
492
+ }
439
493
  continue;
440
494
  }
441
495
  if (!existsSync(schemaPath)) {
@@ -444,16 +498,45 @@ function validateRuntimeConfigScopes(varDir, targets, options = {}) {
444
498
  }
445
499
  result.validated.push({ configPath, schemaPath, scopeId });
446
500
  }
447
- validateRuntimeConfigSchemaTargets(result.validated, {
501
+ result.invalid = collectRuntimeConfigSchemaTargetValidationErrors(result.validated, {
448
502
  ...options.formatError ? { formatError: options.formatError } : {}
449
503
  });
450
504
  return result;
451
505
  }
506
+ function readRuntimeConfigSchemaRegistry(targets, options = {}) {
507
+ const schemaFileName = options.schemaFileName ?? defaultRuntimeConfigSchemaFileName;
508
+ const entries = targets.filter((target) => shouldRegisterRuntimeConfigValidationSchema(target.policy)).flatMap((target) => {
509
+ const scopeId = target.scopeId.trim();
510
+ if (!scopeId || !target.cwd) return [];
511
+ const schemaPath = path.resolve(target.cwd, schemaFileName);
512
+ if (!existsSync(schemaPath)) return [];
513
+ return [[scopeId, readRequiredJsonFile(schemaPath)]];
514
+ });
515
+ return Object.fromEntries(entries);
516
+ }
517
+ function assertRequiredRuntimeConfigValidationTargets(result, targets) {
518
+ const requiredScopeIds = new Set(
519
+ targets.filter((target) => shouldRequireRuntimeConfigAtStartup(target.policy)).map((target) => target.scopeId.trim()).filter(Boolean)
520
+ );
521
+ const requiredFailures = [
522
+ ...result.skipped.filter((entry) => requiredScopeIds.has(entry.scopeId)).map(formatMissingRuntimeConfigValidationError),
523
+ ...result.invalid.filter((entry) => requiredScopeIds.has(entry.scopeId)).map((entry) => entry.error)
524
+ ];
525
+ if (requiredFailures.length) {
526
+ throw new Error(
527
+ [
528
+ "RuntimeConfig startup validation failed.",
529
+ ...requiredFailures.map((error) => error.message)
530
+ ].join("\n\n")
531
+ );
532
+ }
533
+ }
452
534
  function validateRuntimeConfigSourceScopes(source, targets, options = {}) {
453
535
  const schemaFileName = getSchemaFileName(options);
454
536
  const filesByScope = /* @__PURE__ */ new Map();
455
537
  const result = {
456
538
  fileName: source.paths[0] ?? "",
539
+ invalid: [],
457
540
  skipped: [],
458
541
  validated: [],
459
542
  varDir: ""
@@ -462,20 +545,26 @@ function validateRuntimeConfigSourceScopes(source, targets, options = {}) {
462
545
  filesByScope.set(file.scopeId, file.configPath);
463
546
  }
464
547
  for (const target of targets) {
548
+ if (!shouldRegisterRuntimeConfigValidationSchema(target.policy)) continue;
465
549
  const scopeId = target.scopeId.trim();
466
550
  if (!scopeId) continue;
551
+ const mode = resolveRuntimeConfigValidationMode(target.policy);
467
552
  const configPath = filesByScope.get(scopeId);
468
553
  const schemaPath = target.cwd ? path.resolve(target.cwd, schemaFileName) : void 0;
469
554
  if (!target.cwd || !schemaPath) {
470
- result.skipped.push({
471
- ...configPath ? { configPath } : {},
472
- reason: "missing-scope-dir",
473
- scopeId
474
- });
555
+ if (shouldReportMissingRuntimeConfig(mode)) {
556
+ result.skipped.push({
557
+ ...configPath ? { configPath } : {},
558
+ reason: "missing-scope-dir",
559
+ scopeId
560
+ });
561
+ }
475
562
  continue;
476
563
  }
477
564
  if (!configPath) {
478
- result.skipped.push({ reason: "missing-config", schemaPath, scopeId });
565
+ if (shouldReportMissingRuntimeConfig(mode)) {
566
+ result.skipped.push({ reason: "missing-config", schemaPath, scopeId });
567
+ }
479
568
  continue;
480
569
  }
481
570
  if (!existsSync(schemaPath)) {
@@ -484,13 +573,15 @@ function validateRuntimeConfigSourceScopes(source, targets, options = {}) {
484
573
  }
485
574
  result.validated.push({ configPath, schemaPath, scopeId });
486
575
  }
487
- validateRuntimeConfigSchemaTargets(result.validated, {
576
+ result.invalid = collectRuntimeConfigSchemaTargetValidationErrors(result.validated, {
488
577
  ...options.formatError ? { formatError: options.formatError } : {}
489
578
  });
490
579
  return result;
491
580
  }
492
581
  export {
582
+ assertRequiredRuntimeConfigValidationTargets,
493
583
  collectRuntimeConfigFragmentFiles,
584
+ collectRuntimeConfigSchemaTargetValidationErrors,
494
585
  ensureParentDir,
495
586
  getRuntimeConfigScopeView,
496
587
  getRuntimeConfigValue,
@@ -505,6 +596,7 @@ export {
505
596
  projectRuntimeConfigTree,
506
597
  readJsonFile,
507
598
  readRequiredJsonFile,
599
+ readRuntimeConfigSchemaRegistry,
508
600
  readRuntimeConfigScopeJson,
509
601
  readTextFile,
510
602
  resolveRuntimeConfigFilePath,
@@ -3,12 +3,12 @@ import {
3
3
  writeRuntimeConfigFragment,
4
4
  } from '@lorion-org/runtime-config-node';
5
5
 
6
- writeRuntimeConfigFragment('./var', 'billing', {
6
+ writeRuntimeConfigFragment('./var', 'checkout', {
7
7
  public: {
8
- apiBase: '/api/billing',
8
+ successPath: '/orders/confirmed',
9
9
  },
10
10
  private: {
11
- token: 'billing-token',
11
+ signingSecret: 'checkout_signing_secret_demo',
12
12
  },
13
13
  });
14
14
 
@@ -17,5 +17,5 @@ const assignments = loadRuntimeConfigShellAssignments('./var', {
17
17
  });
18
18
 
19
19
  console.log(assignments);
20
- // APP_PUBLIC_BILLING_API_BASE='"/api/billing"'
21
- // APP_PRIVATE_BILLING_TOKEN='"billing-token"'
20
+ // APP_PUBLIC_CHECKOUT_SUCCESS_PATH='"/orders/confirmed"'
21
+ // APP_PRIVATE_CHECKOUT_SIGNING_SECRET='"checkout_signing_secret_demo"'
@@ -1,9 +1,9 @@
1
1
  import { projectSectionedRuntimeConfig } from '@lorion-org/runtime-config';
2
2
  import { loadRuntimeConfigTree, writeRuntimeConfigFragment } from '@lorion-org/runtime-config-node';
3
3
 
4
- writeRuntimeConfigFragment('./var', 'billing', {
4
+ writeRuntimeConfigFragment('./var', 'checkout', {
5
5
  public: {
6
- apiBase: '/api/billing',
6
+ successPath: '/orders/confirmed',
7
7
  },
8
8
  });
9
9
 
@@ -11,7 +11,7 @@ const fragments = loadRuntimeConfigTree('./var');
11
11
  const runtimeConfig = projectSectionedRuntimeConfig(fragments);
12
12
 
13
13
  console.log(runtimeConfig.public);
14
- // { billingApiBase: '/api/billing' }
14
+ // { checkoutSuccessPath: '/orders/confirmed' }
15
15
 
16
16
  console.log(runtimeConfig.private);
17
17
  // {}
@@ -6,17 +6,17 @@ import {
6
6
  writeRuntimeConfigFragment,
7
7
  } from '@lorion-org/runtime-config-node';
8
8
 
9
- writeRuntimeConfigFragment('./var', 'billing', {
9
+ writeRuntimeConfigFragment('./var', 'checkout', {
10
10
  public: {
11
- apiBase: '/api/billing',
11
+ successPath: '/orders/confirmed',
12
12
  },
13
13
  private: {
14
- token: 'billing-token',
14
+ signingSecret: 'checkout_signing_secret_demo',
15
15
  },
16
16
  contexts: {
17
- tenantA: {
17
+ 'eu-store': {
18
18
  public: {
19
- apiBase: '/tenant-a/billing',
19
+ successPath: '/eu-store/orders/confirmed',
20
20
  },
21
21
  },
22
22
  },
@@ -24,30 +24,30 @@ writeRuntimeConfigFragment('./var', 'billing', {
24
24
 
25
25
  const listResult = listRuntimeConfigFragments('./var');
26
26
  console.log(listResult.scopes);
27
- // [{ scopeId: 'billing', publicKeys: ['apiBase'], privateKeys: ['token'], contextIds: ['tenantA'], ... }]
27
+ // [{ scopeId: 'checkout', publicKeys: ['successPath'], privateKeys: ['signingSecret'], contextIds: ['eu-store'], ... }]
28
28
 
29
29
  const projectResult = projectRuntimeConfigTree('./var', {
30
- contextOutputKey: '__tenants',
30
+ contextOutputKey: '__stores',
31
31
  });
32
32
  console.log(projectResult.runtimeConfig.public);
33
33
  // {
34
- // billingApiBase: '/api/billing',
35
- // __tenants: {
36
- // tenantA: {
37
- // billingApiBase: '/tenant-a/billing',
34
+ // checkoutSuccessPath: '/orders/confirmed',
35
+ // __stores: {
36
+ // 'eu-store': {
37
+ // checkoutSuccessPath: '/eu-store/orders/confirmed',
38
38
  // },
39
39
  // },
40
40
  // }
41
41
 
42
- const valueResult = getRuntimeConfigValue('./var', 'billing', 'apiBase', {
43
- contextId: 'tenantA',
44
- contextOutputKey: '__tenants',
42
+ const valueResult = getRuntimeConfigValue('./var', 'checkout', 'successPath', {
43
+ contextId: 'eu-store',
44
+ contextOutputKey: '__stores',
45
45
  });
46
46
  console.log(valueResult.value);
47
- // /tenant-a/billing
47
+ // /eu-store/orders/confirmed
48
48
 
49
- const scopeResult = getRuntimeConfigScopeView('./var', 'billing', {
49
+ const scopeResult = getRuntimeConfigScopeView('./var', 'checkout', {
50
50
  visibility: 'private',
51
51
  });
52
52
  console.log(scopeResult.config);
53
- // { token: 'billing-token' }
53
+ // { signingSecret: 'checkout_signing_secret_demo' }
@@ -11,14 +11,14 @@ const source = resolveRuntimeConfigSource({
11
11
  envKey: 'APP_VAR_DIR',
12
12
  });
13
13
 
14
- writeRuntimeConfigScopeJson(source, 'billing', 'settings.json', {
15
- apiBase: '/api/billing',
14
+ writeRuntimeConfigScopeJson(source, 'checkout', 'settings.json', {
15
+ successPath: '/orders/confirmed',
16
16
  });
17
17
 
18
- const settings = readRuntimeConfigScopeJson(source, 'billing', 'settings.json');
18
+ const settings = readRuntimeConfigScopeJson(source, 'checkout', 'settings.json');
19
19
  console.log(settings);
20
- // { apiBase: '/api/billing' }
20
+ // { successPath: '/orders/confirmed' }
21
21
 
22
- const logoPath = resolveRuntimeConfigPublicFilePath(source, 'billing/logo.svg');
22
+ const logoPath = resolveRuntimeConfigPublicFilePath(source, 'checkout/logo.svg');
23
23
  console.log(logoPath);
24
- // /absolute/project/path/var/runtime-config/public/billing/logo.svg
24
+ // /absolute/project/path/var/runtime-config/public/checkout/logo.svg
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lorion-org/runtime-config-node",
3
- "version": "1.0.0-beta.0",
3
+ "version": "1.0.0-beta.1",
4
4
  "description": "Node file-system helpers for runtime-config directories and files.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -22,6 +22,10 @@
22
22
  "types": "./dist/index.d.ts",
23
23
  "exports": {
24
24
  ".": {
25
+ "lorion-source": {
26
+ "types": "./src/index.ts",
27
+ "default": "./src/index.ts"
28
+ },
25
29
  "import": {
26
30
  "types": "./dist/index.d.ts",
27
31
  "default": "./dist/index.js"
@@ -35,7 +39,9 @@
35
39
  "files": [
36
40
  "dist",
37
41
  "examples",
38
- "LICENSE"
42
+ "LICENSE",
43
+ "src/**/*.ts",
44
+ "!src/**/*.spec.ts"
39
45
  ],
40
46
  "keywords": [
41
47
  "runtime-config",
@@ -48,7 +54,7 @@
48
54
  },
49
55
  "dependencies": {
50
56
  "ajv": "^8.17.1",
51
- "@lorion-org/runtime-config": "^1.0.0-beta.0"
57
+ "@lorion-org/runtime-config": "^1.0.0-beta.1"
52
58
  },
53
59
  "scripts": {
54
60
  "build": "tsup src/index.ts --format esm,cjs --dts",