@dooer/dooer-test-env 1.6.0 → 1.7.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/lib/cli.js +1 -0
- package/lib/command/env.js +5 -1
- package/lib/command/setup.js +5 -1
- package/lib/command/validation.js +74 -0
- package/lib/compose/generate.js +31 -10
- package/package.json +1 -1
- package/readme.md +1 -0
package/lib/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ module.exports = async function cli() {
|
|
|
17
17
|
.command(require('./command/customer')) // customer copy|purge
|
|
18
18
|
.command(require('./command/shred')) // shred (localhost only)
|
|
19
19
|
.command(require('./command/bankid')) // bankid pull|status|clear (staging certs → keychain)
|
|
20
|
+
.command(require('./command/validation')) // validation on|off|status (output-schema checks)
|
|
20
21
|
.command(require('./command/logs')) // search logs across all local services (transactionId, errors, …)
|
|
21
22
|
.command(require('./command/measure')) // resource-usage report for the running env
|
|
22
23
|
.demandCommand(1, 'Pick a command group. Try --help.')
|
package/lib/command/env.js
CHANGED
|
@@ -14,7 +14,11 @@ const discovery = require('../discovery/client')
|
|
|
14
14
|
function ensureCompose() {
|
|
15
15
|
rt.ensureRunDir()
|
|
16
16
|
if (!fs.existsSync(rt.COMPOSE_FILE)) {
|
|
17
|
-
const r = generateCompose({
|
|
17
|
+
const r = generateCompose({
|
|
18
|
+
servicesDir: rt.SERVICES_DIR,
|
|
19
|
+
out: rt.COMPOSE_FILE,
|
|
20
|
+
outputValidation: !!rt.readState().outputValidation,
|
|
21
|
+
})
|
|
18
22
|
console.log(chalk.gray(`generated ${rt.COMPOSE_FILE} (${(r && r.serviceCount) || 'n'} services)`))
|
|
19
23
|
}
|
|
20
24
|
return rt.COMPOSE_FILE
|
package/lib/command/setup.js
CHANGED
|
@@ -62,7 +62,11 @@ module.exports = {
|
|
|
62
62
|
|
|
63
63
|
// generate the compose file
|
|
64
64
|
rt.ensureRunDir()
|
|
65
|
-
const r = generateCompose({
|
|
65
|
+
const r = generateCompose({
|
|
66
|
+
servicesDir: rt.SERVICES_DIR,
|
|
67
|
+
out: rt.COMPOSE_FILE,
|
|
68
|
+
outputValidation: !!rt.readState().outputValidation,
|
|
69
|
+
})
|
|
66
70
|
console.log(`\n${ok(true)} generated ${rt.COMPOSE_FILE} (${(r && r.serviceCount) || '?'} services)`)
|
|
67
71
|
|
|
68
72
|
// BankID certs → keychain, so local login matches staging (production BankID). Idempotent; only pulls
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const chalk = require('chalk')
|
|
2
|
+
const rt = require('../runtime')
|
|
3
|
+
const { generateCompose, VALIDATION_ENV } = require('../compose/generate')
|
|
4
|
+
|
|
5
|
+
// Turn OUTPUT-schema validation on/off for the whole local stack.
|
|
6
|
+
//
|
|
7
|
+
// Both @dooer/microservice (a service validating its OWN response, define-route.js:515) and
|
|
8
|
+
// @dooer/json-http-client (a client validating a response it RECEIVED, route-validators.js:46) run that
|
|
9
|
+
// check only when NODE_ENV !== 'production'. Inbound INPUT validation is a separate path
|
|
10
|
+
// (define-route.js:436) and always runs — this switch does not weaken it.
|
|
11
|
+
//
|
|
12
|
+
// Default OFF, so the local env behaves like staging/live where these checks are silent. Turn it ON to hunt
|
|
13
|
+
// stale service definitions (a consumer whose bundled definition predates a producer's added fields 500s on
|
|
14
|
+
// perfectly good data). See lib/compose/generate.js VALIDATION_ENV.
|
|
15
|
+
|
|
16
|
+
function apply(enabled, { profile }) {
|
|
17
|
+
const state = rt.readState()
|
|
18
|
+
state.outputValidation = !!enabled
|
|
19
|
+
rt.writeState(state)
|
|
20
|
+
const r = generateCompose({ servicesDir: rt.SERVICES_DIR, out: rt.COMPOSE_FILE, outputValidation: !!enabled })
|
|
21
|
+
console.log(chalk.gray(`regenerated ${rt.COMPOSE_FILE} (${r.dooerServiceCount} services)`))
|
|
22
|
+
const code = rt.dc(['up', '-d'], { profile })
|
|
23
|
+
console.log(
|
|
24
|
+
code === 0
|
|
25
|
+
? chalk.green(`output validation ${enabled ? 'ON' : 'OFF'} — services recreated.`)
|
|
26
|
+
: chalk.yellow('compose reported an error while recreating services')
|
|
27
|
+
)
|
|
28
|
+
process.exitCode = code
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const profileOption = (y) =>
|
|
32
|
+
y.option('profile', { type: 'string', default: 'full', describe: 'compose profile to recreate' })
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
command: 'validation <command>',
|
|
36
|
+
describe: 'output-schema validation for the local stack (default: off, like staging/live)',
|
|
37
|
+
builder: (y) =>
|
|
38
|
+
y
|
|
39
|
+
.command({
|
|
40
|
+
command: 'on',
|
|
41
|
+
describe: 'ENABLE output-schema validation (surfaces stale service definitions; recreates services)',
|
|
42
|
+
builder: profileOption,
|
|
43
|
+
handler: (argv) => apply(true, { profile: argv.profile }),
|
|
44
|
+
})
|
|
45
|
+
.command({
|
|
46
|
+
command: 'off',
|
|
47
|
+
describe: 'DISABLE output-schema validation (default; behaves like staging/live)',
|
|
48
|
+
builder: profileOption,
|
|
49
|
+
handler: (argv) => apply(false, { profile: argv.profile }),
|
|
50
|
+
})
|
|
51
|
+
.command({
|
|
52
|
+
command: 'status',
|
|
53
|
+
describe: 'show whether output-schema validation is on or off',
|
|
54
|
+
handler: () => {
|
|
55
|
+
const on = !!rt.readState().outputValidation
|
|
56
|
+
console.log(`output validation: ${on ? chalk.green('ON') : chalk.gray('OFF')}`)
|
|
57
|
+
console.log(
|
|
58
|
+
` ${Object.entries(VALIDATION_ENV[on ? 'on' : 'off'])
|
|
59
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
60
|
+
.join(' ')}`
|
|
61
|
+
)
|
|
62
|
+
console.log(
|
|
63
|
+
chalk.gray(
|
|
64
|
+
on
|
|
65
|
+
? ' a consumer with a stale service definition will 500 on valid data — that is the point'
|
|
66
|
+
: ' matches staging/live, where these checks are silent. `validation on` to hunt drift'
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
.demandCommand(1, 'Use: validation on | validation off | validation status')
|
|
72
|
+
.strict(),
|
|
73
|
+
handler: () => {},
|
|
74
|
+
}
|
package/lib/compose/generate.js
CHANGED
|
@@ -82,8 +82,7 @@ const GLOBAL_OVERRIDES = {
|
|
|
82
82
|
// NOTE: never set DOOER_MICROSERVICE_PORT — no staging manifest sets it, so every image defaults to 3000
|
|
83
83
|
// on its own. service-ai (python) reads it as a raw string and does `bind(('', port))`, which throws
|
|
84
84
|
// "an integer is required (got type str)" the moment we inject it. Let the images default.
|
|
85
|
-
NODE_ENV
|
|
86
|
-
DOOER_ENVIRONMENT_NAME: 'local',
|
|
85
|
+
// NODE_ENV / DOOER_ENVIRONMENT_NAME are set per validation mode — see VALIDATION_ENV below.
|
|
87
86
|
DOOER_LOG_LEVEL: 'informational',
|
|
88
87
|
// Auth: force the shared @dooer LOCAL dev PUBLIC key on every verifier. The manifests carry the STAGING
|
|
89
88
|
// public key as a plain value (which would win over config's `local` default), but the only signer —
|
|
@@ -149,8 +148,26 @@ function secretDefault(name) {
|
|
|
149
148
|
return OMIT
|
|
150
149
|
}
|
|
151
150
|
|
|
151
|
+
// OUTPUT-schema validation mode. Both @dooer/microservice (a service validating its OWN response,
|
|
152
|
+
// define-route.js:515) and @dooer/json-http-client (a client validating a response it RECEIVED,
|
|
153
|
+
// route-validators.js:46) run that check only when NODE_ENV !== 'production'. Inbound INPUT validation is
|
|
154
|
+
// a different path (define-route.js:436) and always runs — turning this off does not weaken it.
|
|
155
|
+
//
|
|
156
|
+
// OFF is the default so the local env behaves like staging/live, where these checks are silent: otherwise
|
|
157
|
+
// any consumer carrying a stale service definition 500s on perfectly good data (seen with service-ledger →
|
|
158
|
+
// service-business-entities and service-document-processing → service-core-objects). Turn it ON deliberately
|
|
159
|
+
// to hunt that drift. (Jimmy 2026-09-03.)
|
|
160
|
+
//
|
|
161
|
+
// With NODE_ENV=production, @dooer/config's isDevOrTest() would go false and drop every `local:` value and
|
|
162
|
+
// `localOptional` (crash-looping services) — DOOER_ENVIRONMENT_NAME='environment-local' keeps it true via
|
|
163
|
+
// its second clause. DOOER_DISPLAY_ERRORS keeps error detail, which production mode otherwise hides.
|
|
164
|
+
const VALIDATION_ENV = {
|
|
165
|
+
on: { NODE_ENV: 'development', DOOER_ENVIRONMENT_NAME: 'local', DOOER_DISPLAY_ERRORS: 'true' },
|
|
166
|
+
off: { NODE_ENV: 'production', DOOER_ENVIRONMENT_NAME: 'environment-local', DOOER_DISPLAY_ERRORS: 'true' },
|
|
167
|
+
}
|
|
168
|
+
|
|
152
169
|
// Build the final env map for one dooer service.
|
|
153
|
-
function buildServiceEnv(svc) {
|
|
170
|
+
function buildServiceEnv(svc, { outputValidation = false } = {}) {
|
|
154
171
|
const env = {}
|
|
155
172
|
// 1. plain manifest values (verbatim).
|
|
156
173
|
Object.assign(env, svc.env)
|
|
@@ -160,8 +177,8 @@ function buildServiceEnv(svc) {
|
|
|
160
177
|
if (v === OMIT) return
|
|
161
178
|
env[name] = v
|
|
162
179
|
})
|
|
163
|
-
// 3. global local overrides win.
|
|
164
|
-
Object.assign(env, GLOBAL_OVERRIDES)
|
|
180
|
+
// 3. global local overrides win, plus the validation-mode env (NODE_ENV / environment name).
|
|
181
|
+
Object.assign(env, GLOBAL_OVERRIDES, VALIDATION_ENV[outputValidation ? 'on' : 'off'])
|
|
165
182
|
// 3b. stateless services (service-graphql, service-pdf, …) declare no application user; keep the shared
|
|
166
183
|
// dooer superuser for them so they still connect. Services WITH a manifest user keep it (see GLOBAL_OVERRIDES).
|
|
167
184
|
if (!env.DOOER_SQL_APPLICATION_USER) env.DOOER_SQL_APPLICATION_USER = DEV.sqlUser
|
|
@@ -362,7 +379,7 @@ const SERVICE_ALIASES = {
|
|
|
362
379
|
}
|
|
363
380
|
|
|
364
381
|
// Turn the parsed manifests into the compose object.
|
|
365
|
-
function buildCompose(services, frontends, { profile } = {}) {
|
|
382
|
+
function buildCompose(services, frontends, { profile, outputValidation = false } = {}) {
|
|
366
383
|
const wantedSvc = profile ? services.filter((s) => profilesFor(s.name).includes(profile)) : services
|
|
367
384
|
const portMap = frontendPortMap(frontends)
|
|
368
385
|
|
|
@@ -376,7 +393,10 @@ function buildCompose(services, frontends, { profile } = {}) {
|
|
|
376
393
|
// Fall back to ./CMD-OPF.sh when a manifest sets none, so we never hit the image default ./CMD.sh
|
|
377
394
|
// (Consul path) which hangs on consul-template.
|
|
378
395
|
command: svc.command || ['./CMD-OPF.sh'],
|
|
379
|
-
environment: rewriteBrowserUrls(
|
|
396
|
+
environment: rewriteBrowserUrls(
|
|
397
|
+
{ ...buildServiceEnv(svc, { outputValidation }), ...(SERVICE_ENV_OVERRIDES[svc.name] || {}) },
|
|
398
|
+
portMap
|
|
399
|
+
),
|
|
380
400
|
// publish the browser-facing facade (4000) and service-graphql (4001) on the host
|
|
381
401
|
...(svc.name === 'public-facade' ? { ports: [`${FACADE_HOST_PORT}:3000`] } : {}),
|
|
382
402
|
...(svc.name === 'service-graphql' ? { ports: [`${GRAPHQL_HOST_PORT}:3000`] } : {}),
|
|
@@ -394,7 +414,7 @@ function buildCompose(services, frontends, { profile } = {}) {
|
|
|
394
414
|
// repointed to each frontend's own localhost port where we run it (others stay as-is).
|
|
395
415
|
;(frontends || []).forEach((fe) => {
|
|
396
416
|
const hostPort = portMap[fe.name]
|
|
397
|
-
const env = buildServiceEnv(fe)
|
|
417
|
+
const env = buildServiceEnv(fe, { outputValidation })
|
|
398
418
|
// dooer-frontend's config validation rejects an EMPTY sentryEndpoint for the `local` environment
|
|
399
419
|
// ("No value specified for [sentryEndpoint] ... (ENV: 'DOOER_SENTRY_ENDPOINT')"), even though staging
|
|
400
420
|
// ships it as "". We don't use Sentry (Jimmy 2026-09-01) — inject a well-formed but inert DSN so the
|
|
@@ -430,10 +450,10 @@ function buildCompose(services, frontends, { profile } = {}) {
|
|
|
430
450
|
|
|
431
451
|
// Public entry point. Reads every `service-*.yaml` + `frontend-*.yaml` in `servicesDir`, builds the
|
|
432
452
|
// compose object, writes it as YAML to `out`, and returns a small summary.
|
|
433
|
-
function generateCompose({ profile, servicesDir, out } = {}) {
|
|
453
|
+
function generateCompose({ profile, servicesDir, out, outputValidation = false } = {}) {
|
|
434
454
|
const services = listServices(servicesDir)
|
|
435
455
|
const frontends = listFrontends(servicesDir)
|
|
436
|
-
const compose = buildCompose(services, frontends, { profile })
|
|
456
|
+
const compose = buildCompose(services, frontends, { profile, outputValidation })
|
|
437
457
|
|
|
438
458
|
const yamlText = yaml.safeDump(compose, {
|
|
439
459
|
lineWidth: -1, // never wrap (long base64 public keys must stay one line)
|
|
@@ -459,6 +479,7 @@ function generateCompose({ profile, servicesDir, out } = {}) {
|
|
|
459
479
|
|
|
460
480
|
module.exports = {
|
|
461
481
|
BOOKING_PROFILE_SERVICES,
|
|
482
|
+
VALIDATION_ENV,
|
|
462
483
|
GLOBAL_OVERRIDES,
|
|
463
484
|
secretDefault,
|
|
464
485
|
buildServiceEnv,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dooer/dooer-test-env",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Run the whole Dooer backend locally (staging DB minus customers), copy/purge customers between environments, and shred — one CLI.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": "Dooer/cli-dooer-test-env",
|
package/readme.md
CHANGED
|
@@ -43,6 +43,7 @@ customer new "<name>" --owner <userId> --execute empty functional account: compa
|
|
|
43
43
|
customer copy | customer purge copy / delete one org between environments (emails anonymized)
|
|
44
44
|
shred anonymize the LOCAL db (localhost only)
|
|
45
45
|
bankid pull | status | clear BankID cert (staging → keychain), used by service-accounts
|
|
46
|
+
validation on | off | status output-schema validation (default OFF, like staging/live)
|
|
46
47
|
logs <pattern> [--since 5m] [--service x] [-i] search logs across ALL services (e.g. a transactionId). alias: search
|
|
47
48
|
measure sample docker stats → resource-usage report
|
|
48
49
|
service <start|stop|restart|deploy|local|unlocal|version> <name> per-service control
|