@onlineapps/conn-orch-validator 6.0.1 → 8.0.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/CHANGELOG.md +2591 -2
- package/README.md +1075 -7
- package/docs/DESIGN.md +3 -1
- package/manifests/biz-service.manifest.json +658 -0
- package/manifests/library.manifest.json +324 -0
- package/package.json +12 -6
- package/src/CookbookTestRunner.js +422 -104
- package/src/CookbookTestUtils.js +7 -8
- package/src/ServiceReadinessValidator.js +78 -42
- package/src/ValidationOrchestrator.js +298 -75
- package/src/cli/biz-ci-gate.js +176 -33
- package/src/cli/oa-lint-scripts.js +221 -0
- package/src/cli/oa-sync-template.js +1020 -0
- package/src/cli/oa-validate.js +474 -0
- package/src/helpers/README.md +12 -2
- package/src/helpers/createServiceReadinessTests.js +75 -6
- package/src/index.js +33 -3
- package/src/lint/scripts/lintScripts.js +298 -0
- package/src/manifest/checks/composeRunnerBlock.js +222 -0
- package/src/manifest/checks/composeShape.js +165 -0
- package/src/manifest/checks/contractBridge.js +181 -0
- package/src/manifest/checks/discoveryOrphan.js +50 -0
- package/src/manifest/checks/docsLintBridge.js +553 -0
- package/src/manifest/checks/fileAbsent.js +35 -0
- package/src/manifest/checks/gitTracked.js +204 -0
- package/src/manifest/checks/index.js +111 -0
- package/src/manifest/checks/libraryContext.js +226 -0
- package/src/manifest/checks/libraryDocs.js +75 -0
- package/src/manifest/checks/libraryPackage.js +272 -0
- package/src/manifest/checks/librarySource.js +274 -0
- package/src/manifest/checks/libraryTests.js +121 -0
- package/src/manifest/checks/libraryWorkspace.js +293 -0
- package/src/manifest/checks/readmeRegion.js +135 -0
- package/src/manifest/checks/scriptHeaders.js +79 -0
- package/src/manifest/checks/serviceConfig.js +390 -0
- package/src/manifest/checks/serviceConnectors.js +81 -0
- package/src/manifest/checks/serviceDb.js +388 -0
- package/src/manifest/checks/serviceFiles.js +754 -0
- package/src/manifest/checks/serviceIdentityRows.js +351 -0
- package/src/manifest/checks/serviceRuntime.js +295 -0
- package/src/manifest/checks/serviceScripts.js +213 -0
- package/src/manifest/deployabilitySignal.js +121 -0
- package/src/manifest/discovery.js +386 -0
- package/src/manifest/loadManifest.js +62 -0
- package/src/manifest/manifestShape.js +446 -0
- package/src/manifest/report.js +245 -0
- package/src/manifest/runManifest.js +449 -0
- package/src/manifest/serviceIdentity.js +140 -0
- package/src/manifest/walk.js +74 -0
- package/src/manifest/workspaceRoot.js +242 -0
- package/src/mocks/MockMQClient.js +13 -30
- package/src/mocks/MockRegistry.js +4 -2
- package/src/mocks/MockStorage.js +4 -2
- package/src/sync/docsRegion.js +463 -0
- package/src/sync/generatedRegion.js +228 -0
- package/src/sync/readmeLocation.js +182 -0
- package/src/sync/readmePointer.js +477 -0
- package/src/sync/serviceTemplate.js +583 -0
- package/src/sync/sharedEnv.js +162 -0
- package/src/sync/uniformFiles.js +474 -0
- package/src/utils/bizCiGateContract.js +131 -7
- package/src/utils/connectorContract.js +97 -7
- package/src/utils/cookbookFormat.js +81 -40
- package/src/utils/deployContract.js +213 -13
- package/src/utils/envContract.js +57 -1
- package/src/utils/handlerRef.js +181 -0
- package/src/utils/installContract.js +287 -41
- package/src/utils/libCompat.js +29 -7
- package/src/utils/migrationOrder.js +163 -0
- package/src/utils/preValidation.js +20 -7
- package/src/utils/setupDatabase.js +194 -13
- package/src/utils/testCoverageContract.js +539 -0
- package/src/utils/testNamespace.js +247 -23
- package/src/utils/throwawaySchema.js +207 -0
- package/src/validators/ServiceStructureValidator.js +21 -20
- package/templates/business-service/.dockerignore +42 -0
- package/templates/business-service/.gitlab-ci.yml +290 -0
- package/templates/business-service/Dockerfile +27 -0
- package/templates/business-service/README.md +213 -0
- package/templates/business-service/config/biz-docs-lint.tree.json +10 -0
- package/templates/business-service/config/env-templates/__SERVICE_NAME__.env +4 -0
- package/templates/business-service/config/env-templates/shared.env +65 -0
- package/templates/business-service/config/service/config.json +14 -0
- package/templates/business-service/config/service/integration-contract.json +12 -0
- package/templates/business-service/config/service/operations.json +41 -0
- package/templates/business-service/docker-compose.production.yml +60 -0
- package/templates/business-service/docker-compose.yml +93 -0
- package/templates/business-service/docs/80-setup/INSTALL.md +101 -0
- package/templates/business-service/docs/80-setup/PLATFORM_MATRIX.md +65 -0
- package/templates/business-service/docs/80-setup/README.md +18 -0
- package/templates/business-service/docs/80-setup/VALIDATION.md +78 -0
- package/templates/business-service/docs/README.md +18 -0
- package/templates/business-service/gitignore +42 -0
- package/templates/business-service/index.js +10 -0
- package/templates/business-service/init.sh +54 -0
- package/templates/business-service/jest.config.js +6 -0
- package/templates/business-service/package.json.template +31 -0
- package/templates/business-service/scripts/verify-deploy-uniform.sh +180 -0
- package/templates/business-service/src/handlers/v3/echo.js +39 -0
- package/templates/business-service/tests/cookbooks/echo.json +36 -0
- package/templates/business-service/tests/unit/handler.test.js +78 -0
- package/src/WorkflowTestRunner.js +0 -402
package/src/CookbookTestUtils.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const {
|
|
4
4
|
MIN_COOKBOOK_FORMAT_VERSION,
|
|
5
5
|
checkCookbookFormatVersion,
|
|
6
|
-
|
|
6
|
+
readCookbookSteps,
|
|
7
7
|
stepIdentityOf
|
|
8
8
|
} = require('./utils/cookbookFormat');
|
|
9
9
|
|
|
@@ -85,13 +85,12 @@ class CookbookTestUtils {
|
|
|
85
85
|
errors.push(versionProblem);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
// that was 100 % of the tree.
|
|
88
|
+
// The one shape the format allows, through the one owner of that rule
|
|
89
|
+
// (`utils/cookbookFormat`), so this entry point and the Tier-1 runner cannot
|
|
90
|
+
// disagree about what a cookbook's steps are.
|
|
92
91
|
let steps;
|
|
93
92
|
try {
|
|
94
|
-
steps =
|
|
93
|
+
steps = readCookbookSteps(cookbook.steps);
|
|
95
94
|
} catch (error) {
|
|
96
95
|
// The refusal is the finding. This entry point answers with a list, not
|
|
97
96
|
// an exception, so the message becomes one more error in that list.
|
|
@@ -137,8 +136,8 @@ class CookbookTestUtils {
|
|
|
137
136
|
}
|
|
138
137
|
|
|
139
138
|
// Compare steps
|
|
140
|
-
const steps1 =
|
|
141
|
-
const steps2 =
|
|
139
|
+
const steps1 = readCookbookSteps(cookbook1.steps);
|
|
140
|
+
const steps2 = readCookbookSteps(cookbook2.steps);
|
|
142
141
|
|
|
143
142
|
if (steps1.length !== steps2.length) {
|
|
144
143
|
differences.push({
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { assertLogger } = require('@onlineapps/logger-contract');
|
|
3
4
|
const CookbookTestUtils = require('./CookbookTestUtils');
|
|
5
|
+
const { HANDLER_REF_PATTERN } = require('./utils/handlerRef');
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The semver 2.0.0 grammar, verbatim from semver.org's own published regular
|
|
9
|
+
* expression (anchored, no leading `v`, no surrounding whitespace, no leading
|
|
10
|
+
* zeroes in a numeric identifier).
|
|
11
|
+
*
|
|
12
|
+
* Written out rather than pulled from the `semver` package: this is the only
|
|
13
|
+
* version rule in the package, one anchored pattern expresses it completely,
|
|
14
|
+
* and a dependency added for a single `test()` call is a supply chain the gate
|
|
15
|
+
* does not need. If a second version rule ever appears here, that trade changes
|
|
16
|
+
* — and the change is then visible, because it is this comment that has to go.
|
|
17
|
+
*/
|
|
18
|
+
const SEMVER_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
|
|
8
19
|
|
|
9
20
|
/**
|
|
10
21
|
* ServiceReadinessValidator - Orchestrates complete service validation.
|
|
@@ -25,16 +36,9 @@ const LOGGER_METHODS = ['info', 'warn', 'error', 'debug'];
|
|
|
25
36
|
* previously spent on `health` fold into `operations` so total stays
|
|
26
37
|
* at 100.
|
|
27
38
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* naming an endpoint that does not exist. The only caller that still
|
|
32
|
-
* passed one was Tier-1's readiness step, and that step is gone
|
|
33
|
-
* (see ValidationOrchestrator: its verdict was a strict function of
|
|
34
|
-
* steps 2 and 3). What is left has exactly one consumer:
|
|
35
|
-
* `helpers/createServiceReadinessTests`, and through it the
|
|
36
|
-
* `tests/bootstrap/` suites of the biz repos — which pass name, version,
|
|
37
|
-
* operations, testCookbook and registry, and never passed a url.
|
|
39
|
+
* `validateReadiness(service)` reads `name`, `version`, `operations`,
|
|
40
|
+
* `testCookbook` and `registry`. Those five are what it reads, and it
|
|
41
|
+
* reads no others.
|
|
38
42
|
*
|
|
39
43
|
* @see api/docs/biz/30-operations/schema-v3.md
|
|
40
44
|
* @see /api/docs/biz/40-cookbooks/test-runner-flow.md (input probe contract)
|
|
@@ -48,26 +52,11 @@ class ServiceReadinessValidator {
|
|
|
48
52
|
// caller that did not print the returned object learned nothing about why a
|
|
49
53
|
// service was refused. Owner confirmation:
|
|
50
54
|
// docs/governance/confirmations/connector-logger-contract.md 001.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
+ 'Fix: pass options.logger (e.g. the logger your service already built).'
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const missingLoggerMethods = LOGGER_METHODS.filter(
|
|
60
|
-
(method) => typeof options.logger[method] !== 'function'
|
|
55
|
+
this.logger = assertLogger(
|
|
56
|
+
'ServiceReadinessValidator',
|
|
57
|
+
options.logger,
|
|
58
|
+
'the readiness verdict leaves the process'
|
|
61
59
|
);
|
|
62
|
-
if (missingLoggerMethods.length > 0) {
|
|
63
|
-
throw new Error(
|
|
64
|
-
'[ServiceReadinessValidator] Logger is incomplete - Expected: info, warn, error, debug as functions; '
|
|
65
|
-
+ `missing: ${missingLoggerMethods.join(', ')}. `
|
|
66
|
-
+ 'Fix: pass a logger implementing all four.'
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
this.logger = options.logger;
|
|
71
60
|
|
|
72
61
|
// Readiness checks: core (80 points) + optional (20 points) = 100 points max
|
|
73
62
|
// Core checks ALWAYS run, optional checks run if testCookbook/registry provided
|
|
@@ -240,7 +229,6 @@ class ServiceReadinessValidator {
|
|
|
240
229
|
}
|
|
241
230
|
|
|
242
231
|
const validScopes = ['platform', 'tenant', 'workspace'];
|
|
243
|
-
const handlerPattern = /^handlers\/[a-zA-Z0-9_\/-]+#[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
244
232
|
const forbiddenV2Fields = ['endpoint', 'method', 'path'];
|
|
245
233
|
|
|
246
234
|
for (const [name, operation] of Object.entries(operations)) {
|
|
@@ -249,7 +237,7 @@ class ServiceReadinessValidator {
|
|
|
249
237
|
}
|
|
250
238
|
if (!operation.handler) {
|
|
251
239
|
errors.push(`Operation '${name}' missing handler (v3 — 'handlers/<path>#<export>')`);
|
|
252
|
-
} else if (!
|
|
240
|
+
} else if (!HANDLER_REF_PATTERN.test(operation.handler)) {
|
|
253
241
|
errors.push(`Operation '${name}' has invalid handler ref: '${operation.handler}'`);
|
|
254
242
|
}
|
|
255
243
|
if (!operation.bundle_scope) {
|
|
@@ -311,16 +299,63 @@ class ServiceReadinessValidator {
|
|
|
311
299
|
}
|
|
312
300
|
|
|
313
301
|
/**
|
|
314
|
-
* Check registry compatibility
|
|
302
|
+
* Check registry compatibility.
|
|
303
|
+
*
|
|
304
|
+
* `canRegister` used to be `!!(name && version && operations)`. Truthiness is
|
|
305
|
+
* the wrong question about a version: the line below it compares this value
|
|
306
|
+
* against what the registry already holds, and a comparison between two
|
|
307
|
+
* strings that are not versions decides nothing. Measured (BIZ-ingest): every
|
|
308
|
+
* biz `config/service/config.json` declares
|
|
309
|
+
* `"service": { "version": "${npm_package_version}" }`, the placeholder was
|
|
310
|
+
* handed here unexpanded, and — being a non-empty string — it passed. The
|
|
311
|
+
* gate reported `canRegister: true` about a value that is not a version.
|
|
312
|
+
*
|
|
313
|
+
* The grammar is semver 2.0.0, checked here and nowhere else: this is the one
|
|
314
|
+
* place that decides `canRegister`. Callers supply the value (the `version`
|
|
315
|
+
* field of the service package.json, which is what ConfigLoader reads at
|
|
316
|
+
* runtime); they do not re-check it.
|
|
315
317
|
*/
|
|
316
318
|
async checkRegistryCompatibility(service, registry) {
|
|
317
319
|
try {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
320
|
+
const errors = [];
|
|
321
|
+
|
|
322
|
+
if (!service.name) {
|
|
323
|
+
errors.push(
|
|
324
|
+
'[ServiceReadinessValidator] Service name is missing - '
|
|
325
|
+
+ 'validateReadiness() received no `name`. '
|
|
326
|
+
+ 'Expected: the service name, e.g. biz-ingest. '
|
|
327
|
+
+ 'Fix: pass it in the object handed to validateReadiness().'
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (service.version === undefined || service.version === null) {
|
|
332
|
+
errors.push(
|
|
333
|
+
'[ServiceReadinessValidator] Service version is missing - '
|
|
334
|
+
+ 'validateReadiness() received no `version`. '
|
|
335
|
+
+ 'Expected: the `version` field of the service package.json, e.g. 1.4.2. '
|
|
336
|
+
+ 'Fix: pass it in the object handed to validateReadiness().'
|
|
337
|
+
);
|
|
338
|
+
} else if (!SEMVER_PATTERN.test(service.version)) {
|
|
339
|
+
errors.push(
|
|
340
|
+
'[ServiceReadinessValidator] Service version is not a semantic version - '
|
|
341
|
+
+ `received "${service.version}". `
|
|
342
|
+
+ 'Expected: MAJOR.MINOR.PATCH per semver 2.0.0, e.g. 1.4.2 or 1.4.2-rc.1. '
|
|
343
|
+
+ 'Fix: pass the `version` field of the service package.json — the single source of '
|
|
344
|
+
+ 'truth ConfigLoader reads at runtime. An unresolved "${...}" means the value came '
|
|
345
|
+
+ 'from config.json instead, where the key is never expanded.'
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!service.operations) {
|
|
350
|
+
errors.push(
|
|
351
|
+
'[ServiceReadinessValidator] Operations are missing - '
|
|
352
|
+
+ 'validateReadiness() received no `operations`. '
|
|
353
|
+
+ 'Expected: the flat operations object from config/service/operations.json. '
|
|
354
|
+
+ 'Fix: pass it in the object handed to validateReadiness().'
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const canRegister = errors.length === 0;
|
|
324
359
|
|
|
325
360
|
// Check if service operations match registry expectations
|
|
326
361
|
const registeredService = registry.getService(service.name);
|
|
@@ -331,7 +366,8 @@ class ServiceReadinessValidator {
|
|
|
331
366
|
passed: canRegister && compatible,
|
|
332
367
|
canRegister,
|
|
333
368
|
compatible,
|
|
334
|
-
existingVersion: registeredService?.version
|
|
369
|
+
existingVersion: registeredService?.version,
|
|
370
|
+
errors
|
|
335
371
|
};
|
|
336
372
|
} catch (error) {
|
|
337
373
|
return {
|