@onlineapps/conn-orch-validator 2.0.29 → 2.0.31

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/conn-orch-validator",
3
- "version": "2.0.29",
3
+ "version": "2.0.31",
4
4
  "description": "Validation orchestrator for OA Drive microservices - coordinates validation across all layers (base, infra, orch, business)",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -364,13 +364,16 @@ class CookbookTestRunner {
364
364
  * Resolve operation to HTTP endpoint
365
365
  */
366
366
  async resolveOperation(serviceName, operationName) {
367
- // Load operations.json from service path
368
- const operationsPath = this.servicePath
369
- ? path.join(this.servicePath, 'conn-config', 'operations.json')
370
- : null;
367
+ // Load operations.json prefer config/service/, fall back to conn-config/
368
+ let operationsPath = null;
369
+ if (this.servicePath) {
370
+ const newPath = path.join(this.servicePath, 'config', 'service', 'operations.json');
371
+ const legacyPath = path.join(this.servicePath, 'conn-config', 'operations.json');
372
+ operationsPath = fs.existsSync(newPath) ? newPath : (fs.existsSync(legacyPath) ? legacyPath : null);
373
+ }
371
374
 
372
- if (!operationsPath || !fs.existsSync(operationsPath)) {
373
- throw new Error(`Operations file not found for service: ${serviceName}`);
375
+ if (!operationsPath) {
376
+ throw new Error(`Operations file not found for service: ${serviceName} (checked config/service/ and conn-config/)`);
374
377
  }
375
378
 
376
379
  const operations = JSON.parse(fs.readFileSync(operationsPath, 'utf8'));
@@ -29,9 +29,14 @@ class ValidationOrchestrator {
29
29
  throw new Error('serviceUrl is required for ValidationOrchestrator');
30
30
  }
31
31
 
32
- // Paths
33
- this.configPath = path.join(this.serviceRoot, 'conn-config');
34
- this.runtimePath = path.join(this.serviceRoot, 'conn-runtime');
32
+ // Paths — prefer new config/service/ layout, fall back to legacy conn-config/
33
+ const newConfigDir = path.join(this.serviceRoot, 'config', 'service');
34
+ const legacyConfigDir = path.join(this.serviceRoot, 'conn-config');
35
+ this.configPath = fs.existsSync(newConfigDir) ? newConfigDir : legacyConfigDir;
36
+
37
+ const newRuntimeDir = path.join(this.serviceRoot, 'config', 'runtime');
38
+ const legacyRuntimeDir = path.join(this.serviceRoot, 'conn-runtime');
39
+ this.runtimePath = fs.existsSync(newRuntimeDir) ? newRuntimeDir : legacyRuntimeDir;
35
40
  this.proofPath = path.join(this.runtimePath, 'validation-proof.json');
36
41
 
37
42
  // Validators
@@ -136,7 +141,7 @@ class ValidationOrchestrator {
136
141
  const packageFile = path.join(this.serviceRoot, 'package.json');
137
142
  const dockerFile = path.join(this.serviceRoot, 'Dockerfile');
138
143
  const dockerComposeFile = path.join(this.serviceRoot, 'docker-compose.yml');
139
- const envExampleFile = path.join(this.serviceRoot, '.env.example');
144
+ const envTemplateFile = path.join(this.serviceRoot, '..', '..', 'config', 'env-templates', `${path.basename(this.serviceRoot)}.env`);
140
145
 
141
146
  if (!fs.existsSync(configFile)) {
142
147
  throw new Error('config.json not found');
@@ -154,8 +159,8 @@ class ValidationOrchestrator {
154
159
  if (fs.existsSync(dockerComposeFile)) {
155
160
  infra.dockerCompose = fs.readFileSync(dockerComposeFile, 'utf8');
156
161
  }
157
- if (fs.existsSync(envExampleFile)) {
158
- infra.envExample = fs.readFileSync(envExampleFile, 'utf8');
162
+ if (fs.existsSync(envTemplateFile)) {
163
+ infra.envTemplate = fs.readFileSync(envTemplateFile, 'utf8');
159
164
  }
160
165
 
161
166
  // Extract @onlineapps/* dependencies
@@ -69,9 +69,14 @@ function createPreValidationTests(testsDir, options = {}) {
69
69
  );
70
70
  }
71
71
 
72
- // Load configuration
73
- const configPath = path.join(serviceRoot, 'conn-config/config.json');
74
- const operationsPath = path.join(serviceRoot, 'conn-config/operations.json');
72
+ // Load configuration — prefer config/service/, fall back to conn-config/
73
+ const resolveConfig = (filename) => {
74
+ const newPath = path.join(serviceRoot, 'config', 'service', filename);
75
+ const legacyPath = path.join(serviceRoot, 'conn-config', filename);
76
+ return fs.existsSync(newPath) ? newPath : legacyPath;
77
+ };
78
+ const configPath = resolveConfig('config.json');
79
+ const operationsPath = resolveConfig('operations.json');
75
80
  const proofPath = path.join(serviceRoot, '.validation-proof.json');
76
81
 
77
82
  const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
@@ -64,9 +64,14 @@ function createServiceReadinessTests(testsDir, options = {}) {
64
64
  );
65
65
  }
66
66
 
67
- // Load configuration files (validated above)
68
- const configPath = path.join(serviceRoot, 'conn-config/config.json');
69
- const operationsPath = path.join(serviceRoot, 'conn-config/operations.json');
67
+ // Load configuration files prefer config/service/, fall back to conn-config/
68
+ const resolveConfig = (filename) => {
69
+ const newPath = path.join(serviceRoot, 'config', 'service', filename);
70
+ const legacyPath = path.join(serviceRoot, 'conn-config', filename);
71
+ return fs.existsSync(newPath) ? newPath : legacyPath;
72
+ };
73
+ const configPath = resolveConfig('config.json');
74
+ const operationsPath = resolveConfig('operations.json');
70
75
  const appPath = path.join(serviceRoot, 'src/app.js');
71
76
 
72
77
  const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
@@ -15,6 +15,28 @@
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
17
 
18
+ /**
19
+ * Try reading from new path first (config/service/), fall back to legacy (conn-config/).
20
+ * @param {string} root - Service root
21
+ * @param {string} filename - e.g. 'config.json'
22
+ * @returns {string|null} File contents or null
23
+ */
24
+ function readConfigFile(root, filename) {
25
+ const candidates = [
26
+ path.join(root, 'config', 'service', filename),
27
+ path.join(root, 'conn-config', filename)
28
+ ];
29
+ for (const p of candidates) {
30
+ try { return fs.readFileSync(p, 'utf-8'); } catch { /* try next */ }
31
+ }
32
+ return null;
33
+ }
34
+
35
+ function hasConfigDir(root) {
36
+ return fs.existsSync(path.join(root, 'config', 'service')) ||
37
+ fs.existsSync(path.join(root, 'conn-config'));
38
+ }
39
+
18
40
  const STANDARD_LEVELS = [
19
41
  {
20
42
  level: 'v1.0',
@@ -25,13 +47,13 @@ const STANDARD_LEVELS = [
25
47
  const has = (p) => fs.existsSync(path.join(root, p));
26
48
  const read = (p) => { try { return fs.readFileSync(path.join(root, p), 'utf-8'); } catch { return null; } };
27
49
 
28
- results.push({ passed: has('conn-config'), id: 'dir_conn_config', message: 'conn-config/ directory' });
50
+ results.push({ passed: hasConfigDir(root), id: 'dir_conn_config', message: 'config/service/ or conn-config/ directory' });
29
51
  results.push({ passed: has('src'), id: 'dir_src', message: 'src/ directory' });
30
52
  results.push({ passed: has('tests'), id: 'dir_tests', message: 'tests/ directory' });
31
53
  results.push({ passed: has('src/app.js'), id: 'file_app', message: 'src/app.js' });
32
54
  results.push({ passed: has('index.js'), id: 'file_index', message: 'index.js entry point' });
33
55
 
34
- const configRaw = read('conn-config/config.json');
56
+ const configRaw = readConfigFile(root, 'config.json');
35
57
  let configValid = false;
36
58
  if (configRaw) {
37
59
  try {
@@ -41,7 +63,7 @@ const STANDARD_LEVELS = [
41
63
  }
42
64
  results.push({ passed: configValid, id: 'config_valid', message: 'Valid config.json with service.name + service.port' });
43
65
 
44
- const opsRaw = read('conn-config/operations.json');
66
+ const opsRaw = readConfigFile(root, 'operations.json');
45
67
  let opsValid = false;
46
68
  if (opsRaw) {
47
69
  try {
@@ -69,9 +91,7 @@ const STANDARD_LEVELS = [
69
91
  name: 'Multitenancy Standard',
70
92
  since: '2026-03-01',
71
93
  checks: (root) => {
72
- const read = (p) => { try { return fs.readFileSync(path.join(root, p), 'utf-8'); } catch { return null; } };
73
-
74
- const configRaw = read('conn-config/config.json');
94
+ const configRaw = readConfigFile(root, 'config.json');
75
95
  let hasTenantContext = false;
76
96
  if (configRaw) {
77
97
  try {
@@ -224,11 +244,22 @@ class ServiceStructureValidator {
224
244
  */
225
245
  validateDirectoryStructure() {
226
246
  const requiredDirs = [
227
- { path: 'conn-config', description: 'Configuration directory' },
228
247
  { path: 'src', description: 'Source code directory' },
229
248
  { path: 'tests', description: 'Tests directory' }
230
249
  ];
231
250
 
251
+ if (!hasConfigDir(this.serviceRoot)) {
252
+ this.errors.push({
253
+ type: 'MISSING_DIRECTORY',
254
+ path: 'config/service/ (or conn-config/)',
255
+ message: 'Required directory missing: config/service/ (or legacy conn-config/)',
256
+ description: 'Configuration directory',
257
+ fix: 'Create directory: mkdir -p config/service'
258
+ });
259
+ } else {
260
+ this.info.push('✓ Found Configuration directory');
261
+ }
262
+
232
263
  const recommendedDirs = [
233
264
  { path: 'tests/unit', description: 'Unit tests' },
234
265
  { path: 'tests/integration', description: 'Integration tests' },
@@ -268,48 +299,46 @@ class ServiceStructureValidator {
268
299
  * Validate configuration files
269
300
  */
270
301
  validateConfigurationFiles() {
271
- // 1. Validate conn-config/config.json
272
- const configPath = path.join(this.serviceRoot, 'conn-config/config.json');
273
- if (!fs.existsSync(configPath)) {
302
+ const configRaw = readConfigFile(this.serviceRoot, 'config.json');
303
+ if (!configRaw) {
274
304
  this.errors.push({
275
305
  type: 'MISSING_CONFIG',
276
- path: 'conn-config/config.json',
277
- message: 'Service configuration missing: conn-config/config.json',
306
+ path: 'config/service/config.json (or conn-config/config.json)',
307
+ message: 'Service configuration missing: config/service/config.json (or conn-config/config.json)',
278
308
  fix: 'Create config.json with service metadata. See: /docs/standards/SERVICE_TEMPLATE.md'
279
309
  });
280
310
  } else {
281
311
  try {
282
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
312
+ const config = JSON.parse(configRaw);
283
313
  this.validateConfigStructure(config);
284
314
  this.info.push('✓ Found valid config.json');
285
315
  } catch (error) {
286
316
  this.errors.push({
287
317
  type: 'INVALID_CONFIG',
288
- path: 'conn-config/config.json',
318
+ path: 'config.json',
289
319
  message: `Invalid JSON in config.json: ${error.message}`,
290
320
  fix: 'Fix JSON syntax errors in config.json'
291
321
  });
292
322
  }
293
323
  }
294
324
 
295
- // 2. Validate conn-config/operations.json
296
- const operationsPath = path.join(this.serviceRoot, 'conn-config/operations.json');
297
- if (!fs.existsSync(operationsPath)) {
325
+ const opsRaw = readConfigFile(this.serviceRoot, 'operations.json');
326
+ if (!opsRaw) {
298
327
  this.errors.push({
299
328
  type: 'MISSING_OPERATIONS',
300
- path: 'conn-config/operations.json',
301
- message: 'Operations specification missing: conn-config/operations.json',
329
+ path: 'config/service/operations.json (or conn-config/operations.json)',
330
+ message: 'Operations specification missing: config/service/operations.json (or conn-config/operations.json)',
302
331
  fix: 'Create operations.json. See: /docs/standards/OPERATIONS.md'
303
332
  });
304
333
  } else {
305
334
  try {
306
- const operations = JSON.parse(fs.readFileSync(operationsPath, 'utf-8'));
335
+ const operations = JSON.parse(opsRaw);
307
336
  this.validateOperationsStructure(operations);
308
337
  this.info.push('✓ Found valid operations.json');
309
338
  } catch (error) {
310
339
  this.errors.push({
311
340
  type: 'INVALID_OPERATIONS',
312
- path: 'conn-config/operations.json',
341
+ path: 'operations.json',
313
342
  message: `Invalid JSON in operations.json: ${error.message}`,
314
343
  fix: 'Fix JSON syntax errors in operations.json'
315
344
  });
@@ -338,7 +367,7 @@ class ServiceStructureValidator {
338
367
  if (!value) {
339
368
  this.errors.push({
340
369
  type: 'MISSING_CONFIG_FIELD',
341
- path: 'conn-config/config.json',
370
+ path: 'config/service/config.json',
342
371
  field: field,
343
372
  message: `Required field missing in config.json: ${field}`,
344
373
  fix: `Add "${field}" to config.json`
@@ -351,7 +380,7 @@ class ServiceStructureValidator {
351
380
  if (!/^[a-z][a-z0-9-]*$/.test(config.service.name)) {
352
381
  this.warnings.push({
353
382
  type: 'INVALID_SERVICE_NAME',
354
- path: 'conn-config/config.json',
383
+ path: 'config/service/config.json',
355
384
  field: 'service.name',
356
385
  value: config.service.name,
357
386
  message: `Service name should be lowercase kebab-case: ${config.service.name}`,
@@ -387,7 +416,7 @@ class ServiceStructureValidator {
387
416
  if (!operations.operations) {
388
417
  this.errors.push({
389
418
  type: 'INVALID_OPERATIONS_STRUCTURE',
390
- path: 'conn-config/operations.json',
419
+ path: 'config/service/operations.json',
391
420
  message: 'operations.json must have "operations" key',
392
421
  fix: 'Wrap operations in {"operations": {...}}. See: /docs/standards/OPERATIONS.md'
393
422
  });
@@ -398,7 +427,7 @@ class ServiceStructureValidator {
398
427
  if (typeof ops !== 'object' || Array.isArray(ops)) {
399
428
  this.errors.push({
400
429
  type: 'INVALID_OPERATIONS_TYPE',
401
- path: 'conn-config/operations.json',
430
+ path: 'config/service/operations.json',
402
431
  message: 'operations must be an object',
403
432
  fix: 'operations should be key-value pairs: {"operation-name": {...}}'
404
433
  });
@@ -408,7 +437,7 @@ class ServiceStructureValidator {
408
437
  if (Object.keys(ops).length === 0) {
409
438
  this.warnings.push({
410
439
  type: 'NO_OPERATIONS',
411
- path: 'conn-config/operations.json',
440
+ path: 'config/service/operations.json',
412
441
  message: 'No operations defined',
413
442
  fix: 'Add at least one operation to operations.json'
414
443
  });
@@ -431,7 +460,7 @@ class ServiceStructureValidator {
431
460
  if (!spec[field]) {
432
461
  this.errors.push({
433
462
  type: 'MISSING_OPERATION_FIELD',
434
- path: 'conn-config/operations.json',
463
+ path: 'config/service/operations.json',
435
464
  operation: name,
436
465
  field: field,
437
466
  message: `Operation "${name}" missing required field: ${field}`,
@@ -444,7 +473,7 @@ class ServiceStructureValidator {
444
473
  if (spec.endpoint && !spec.endpoint.startsWith('/')) {
445
474
  this.errors.push({
446
475
  type: 'INVALID_ENDPOINT',
447
- path: 'conn-config/operations.json',
476
+ path: 'config/service/operations.json',
448
477
  operation: name,
449
478
  field: 'endpoint',
450
479
  value: spec.endpoint,
@@ -458,7 +487,7 @@ class ServiceStructureValidator {
458
487
  if (spec.method && !validMethods.includes(spec.method)) {
459
488
  this.errors.push({
460
489
  type: 'INVALID_HTTP_METHOD',
461
- path: 'conn-config/operations.json',
490
+ path: 'config/service/operations.json',
462
491
  operation: name,
463
492
  field: 'method',
464
493
  value: spec.method,