cabloy 5.1.219 → 5.1.221
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/.cabloy-version +1 -1
- package/.github/workflows/agent-governance.yml +2 -2
- package/.github/workflows/create-cabloy.yml +3 -3
- package/.github/workflows/docs-pages.yml +2 -2
- package/.github/workflows/playwright-e2e.yml +4 -4
- package/.github/workflows/vona-cov-pg.yml +2 -2
- package/.github/workflows/vona-test-crud.yml +2 -2
- package/.github/workflows/vona-test-mysql.yml +2 -2
- package/.github/workflows/vona-test-pg.yml +2 -2
- package/.github/workflows/vona-test-sqlite3.yml +2 -2
- package/.github/workflows/vona-tsc.yml +2 -2
- package/.github/workflows/zova-ui.yml +2 -2
- package/CHANGELOG.md +23 -0
- package/README.md +1 -1
- package/package.json +4 -3
- package/repo-docs/frontend/openapi-sdk-guide.md +2 -0
- package/repo-docs/fullstack/quickstart.md +3 -3
- package/repo-docs/reference/repo-scripts.md +16 -0
- package/scripts/generateOpenapiIsolated.test.ts +25 -0
- package/scripts/generateOpenapiIsolated.ts +46 -12
- package/scripts/runIsolatedTest.test.ts +165 -45
- package/scripts/runIsolatedTest.ts +111 -24
- package/scripts/strictGenerationDiagnostics.test.ts +1009 -0
- package/scripts/strictGenerationDiagnostics.ts +61 -0
- package/scripts/strictGenerationStartup.test.ts +212 -0
- package/vona/package.original.json +3 -2
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +14 -4
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateEntryFiles.ts +6 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateVonaMeta.ts +2 -10
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/validateGenerationPaths.ts +27 -0
- package/vona/packages-cli/cli-set-api/test/binDevLifecycle.test.ts +1 -1
- package/vona/packages-cli/cli-set-api/test/strictGenerationPrivacy.test.ts +439 -0
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/bean/beanBase.ts +4 -0
- package/vona/packages-vona/vona-core/src/lib/core/asyncLocalStorage.ts +84 -12
- package/vona/packages-vona/vona-core/src/lib/core/index.ts +14 -0
- package/vona/packages-vona/vona-core/src/lib/core/logger/appLogger.ts +19 -3
- package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.test.ts +346 -0
- package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.ts +138 -0
- package/vona/packages-vona/vona-core/src/lib/core/logger/index.ts +1 -0
- package/vona/packages-vona/vona-core/src/lib/core/logger/utils.ts +25 -5
- package/vona/packages-vona/vona-core/src/lib/core/requestBoundary.ts +89 -0
- package/vona/packages-vona/vona-core/src/lib/core/testFreshness.test.ts +379 -0
- package/vona/packages-vona/vona-core/src/lib/core/testFreshness.ts +232 -0
- package/vona/packages-vona/vona-core/src/lib/core/testStrictMode.ts +9 -0
- package/vona/packages-vona/vona-core/src/lib/framework/start.test.ts +4 -3
- package/vona/packages-vona/vona-core/src/lib/framework/start.ts +2 -0
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +691 -356
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +111 -60
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/service/executor.ts +6 -6
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/types/executor.ts +13 -1
- package/vona/src/suite-vendor/a-vona/modules/a-jwt/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/lib/jwtClientLogging.test.ts +182 -0
- package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/service/jwtClient.ts +2 -8
- package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/aopMethod.log.ts +4 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/bean.logger.ts +9 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/middlewareSystem.httpLog.ts +27 -14
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactHttpLog.test.ts +207 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactHttpLog.ts +30 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +33 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/index.ts +11 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/lib/protectedSqliteRegistry.ts +356 -0
- package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +6 -0
- package/vona/src/suite-vendor/a-vona/modules/a-startup/test/devSeed.test.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/bean.telemetry.ts +8 -1
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/middlewareSystem.trace.ts +9 -14
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/lib/spanDiagnostics.ts +70 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/service/telemetry.ts +37 -23
- package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-version/src/service/database.ts +31 -4
- package/vona/src/suite-vendor/a-vona/package.json +2 -2
- package/zova/package.original.json +3 -2
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/package.json +1 -1
- package/zova/packages-cli/cli-set-front/src/lib/bean/cli.bin.buildRest.ts +11 -101
- package/zova/packages-cli/cli-set-front/src/lib/common/restDependencies.ts +78 -0
- package/zova/packages-cli/cli-set-front/src/lib/common/restDependencyVersions.ts +252 -0
- package/zova/packages-cli/cli-set-front/test/restDependencies.test.ts +568 -0
- package/zova/pnpm-lock.yaml +442 -564
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { StdioOptions } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
import { Writable } from 'node:stream';
|
|
4
|
+
|
|
5
|
+
import { requiresFreshTestResources } from '../vona/packages-vona/vona-core/src/lib/core/testStrictMode.ts';
|
|
6
|
+
|
|
7
|
+
export type StrictGenerationStage =
|
|
8
|
+
| 'arguments'
|
|
9
|
+
| 'preparation'
|
|
10
|
+
| 'startup'
|
|
11
|
+
| 'operation'
|
|
12
|
+
| 'shutdown'
|
|
13
|
+
| 'operation-and-shutdown'
|
|
14
|
+
| 'generator'
|
|
15
|
+
| 'cleanup'
|
|
16
|
+
| 'run';
|
|
17
|
+
|
|
18
|
+
const failureStages = new WeakMap<object, StrictGenerationStage>();
|
|
19
|
+
|
|
20
|
+
export class StrictGenerationError extends Error {
|
|
21
|
+
readonly stage: StrictGenerationStage;
|
|
22
|
+
|
|
23
|
+
constructor(stage: StrictGenerationStage) {
|
|
24
|
+
super(`Strict contract generation failed: ${stage}`);
|
|
25
|
+
this.name = 'StrictGenerationError';
|
|
26
|
+
this.stage = stage;
|
|
27
|
+
failureStages.set(this, stage);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function strictGenerationFailure(stage: StrictGenerationStage, error?: unknown) {
|
|
32
|
+
const previous = error && typeof error === 'object' ? failureStages.get(error) : undefined;
|
|
33
|
+
return new StrictGenerationError(previous ?? stage);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function usesStrictGeneration(env: NodeJS.ProcessEnv): boolean {
|
|
37
|
+
try {
|
|
38
|
+
return requiresFreshTestResources(env);
|
|
39
|
+
} catch {
|
|
40
|
+
throw new StrictGenerationError('arguments');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function guardianStdio(strict: boolean): StdioOptions {
|
|
45
|
+
return strict ? ['ignore', 'ignore', 'ignore', 'ipc'] : ['inherit', 'inherit', 'inherit', 'ipc'];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function discardNativeOutput() {
|
|
49
|
+
const discard = () =>
|
|
50
|
+
new Writable({
|
|
51
|
+
write(_chunk, _encoding, callback) {
|
|
52
|
+
callback();
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
return { stdout: discard(), stderr: discard() };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function reportIsolatedDiagnostic(strict: boolean, category: string, value?: unknown) {
|
|
59
|
+
if (strict) console.error(`Strict contract generation: ${category}`);
|
|
60
|
+
else console.error(value);
|
|
61
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { registerHooks } from 'node:module';
|
|
4
|
+
import { after, test } from 'node:test';
|
|
5
|
+
import ts from 'typescript';
|
|
6
|
+
|
|
7
|
+
const root = new URL('../vona/src/suite-vendor/a-vona/modules/', import.meta.url);
|
|
8
|
+
const databaseUrl = new URL('a-version/src/service/database.ts', root).href;
|
|
9
|
+
const startupUrl = new URL('a-startup/src/service/startup.ts', root).href;
|
|
10
|
+
const helperUrl = new URL('a-version/src/lib/databaseTestName.ts', root).href;
|
|
11
|
+
const isolationUrl = new URL(
|
|
12
|
+
'../vona/packages-vona/vona-core/src/lib/core/testIsolation.ts',
|
|
13
|
+
import.meta.url,
|
|
14
|
+
).href;
|
|
15
|
+
let strict = true;
|
|
16
|
+
let ready = true;
|
|
17
|
+
const mocks = {
|
|
18
|
+
'vona': `
|
|
19
|
+
export class BeanBase {}
|
|
20
|
+
export function assertFreshTestResourcesReady() { globalThis.__strictGenerationTest.ready(); }
|
|
21
|
+
export function requiresFreshTestResources() { return globalThis.__strictGenerationTest.strict(); }
|
|
22
|
+
export function assertIsolatedTestConfig() {}
|
|
23
|
+
export function assertIsolatedTestPath() {}
|
|
24
|
+
export function assertTestRunOwner() {}
|
|
25
|
+
export function resolveVonaLocalPaths() { return { dataRoot: '/synthetic' }; }
|
|
26
|
+
export async function withIsolatedTestDatabaseDiscovery(_app, _name, _client, fn) { return await fn(); }
|
|
27
|
+
export function cast(value) { return value; }
|
|
28
|
+
export function getRuntimePathPhysicalRoot() { throw new Error('must not get mutating runtime path'); }
|
|
29
|
+
export { createDatabaseTestName, getDatabaseTestPrefix } from ${JSON.stringify(helperUrl)};
|
|
30
|
+
export { assertDisposableDatabaseName } from ${JSON.stringify(isolationUrl)};
|
|
31
|
+
`,
|
|
32
|
+
'vona-module-a-bean': 'export function Service() { return value => value; }',
|
|
33
|
+
'fs-extra':
|
|
34
|
+
'export default { async remove() { throw new Error("must not remove directory"); } };',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Execute the real service bodies; replace only framework collaborators. No app or drivers.
|
|
38
|
+
const previous = Object.getOwnPropertyDescriptor(globalThis, '__strictGenerationTest');
|
|
39
|
+
Object.defineProperty(globalThis, '__strictGenerationTest', {
|
|
40
|
+
configurable: true,
|
|
41
|
+
value: {
|
|
42
|
+
strict: () => strict,
|
|
43
|
+
ready: () => {
|
|
44
|
+
if (!ready) throw new Error('freshness missing');
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
const hooks = registerHooks({
|
|
49
|
+
resolve(specifier, context, nextResolve) {
|
|
50
|
+
if ([databaseUrl, startupUrl].includes(context.parentURL ?? '') && mocks[specifier]) {
|
|
51
|
+
return {
|
|
52
|
+
url: `data:text/javascript,${encodeURIComponent(mocks[specifier])}`,
|
|
53
|
+
shortCircuit: true,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (context.parentURL === helperUrl && specifier === 'vona') {
|
|
57
|
+
return { url: isolationUrl, shortCircuit: true };
|
|
58
|
+
}
|
|
59
|
+
return nextResolve(specifier, context);
|
|
60
|
+
},
|
|
61
|
+
load(url, context, nextLoad) {
|
|
62
|
+
if ([databaseUrl, startupUrl].includes(url)) {
|
|
63
|
+
const source = ts.transpileModule(readFileSync(new URL(url), 'utf8'), {
|
|
64
|
+
compilerOptions: {
|
|
65
|
+
experimentalDecorators: true,
|
|
66
|
+
target: ts.ScriptTarget.ESNext,
|
|
67
|
+
module: ts.ModuleKind.ESNext,
|
|
68
|
+
},
|
|
69
|
+
}).outputText;
|
|
70
|
+
return { format: 'module', source, shortCircuit: true };
|
|
71
|
+
}
|
|
72
|
+
return nextLoad(url, context);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
let ServiceDatabase: any;
|
|
76
|
+
let ServiceStartup: any;
|
|
77
|
+
try {
|
|
78
|
+
({ ServiceDatabase } = await import(databaseUrl));
|
|
79
|
+
({ ServiceStartup } = await import(startupUrl));
|
|
80
|
+
} finally {
|
|
81
|
+
hooks.deregister();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function databaseFixture(existing: string[] = []) {
|
|
85
|
+
let current = 'postgres';
|
|
86
|
+
const calls: string[] = [];
|
|
87
|
+
const app = { name: 'synthetic', meta: { isTest: true, isDev: false, isProd: false, env: {} } };
|
|
88
|
+
const client = {
|
|
89
|
+
clientName: 'main',
|
|
90
|
+
db: { dialectName: 'pg' },
|
|
91
|
+
getDatabaseName: () => current,
|
|
92
|
+
connection: {
|
|
93
|
+
schema: {
|
|
94
|
+
fetchDatabases: async () => {
|
|
95
|
+
calls.push('discover');
|
|
96
|
+
return existing.map(name => ({ name }));
|
|
97
|
+
},
|
|
98
|
+
createDatabase: async (name: string) => {
|
|
99
|
+
calls.push('create');
|
|
100
|
+
existing.push(name);
|
|
101
|
+
return name;
|
|
102
|
+
},
|
|
103
|
+
dropDatabase: async () => {
|
|
104
|
+
throw new Error('must not drop');
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
changeConfigConnectionAndReloadWorker: async (name: string) => {
|
|
109
|
+
calls.push('reload');
|
|
110
|
+
current = name;
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
const service = Object.create(ServiceDatabase.prototype);
|
|
114
|
+
Object.defineProperties(service, {
|
|
115
|
+
app: { value: app },
|
|
116
|
+
bean: { value: { database: { current: { client } } } },
|
|
117
|
+
$logger: { value: { silly() {} } },
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
service,
|
|
121
|
+
calls,
|
|
122
|
+
setCurrent: (name: string) => {
|
|
123
|
+
current = name;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// This file owns the collaborator sentinel for its entire isolated node:test process.
|
|
129
|
+
// Restore it after every test has settled, including assertions that fail.
|
|
130
|
+
after(() => {
|
|
131
|
+
if (previous) Object.defineProperty(globalThis, '__strictGenerationTest', previous);
|
|
132
|
+
else Reflect.deleteProperty(globalThis, '__strictGenerationTest');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('strict database startup refuses preexisting matches and unowned selected targets without deletion', async () => {
|
|
136
|
+
strict = true;
|
|
137
|
+
ready = true;
|
|
138
|
+
const f = databaseFixture(['vona-test-synthetic-s-20261019-010203']);
|
|
139
|
+
await assert.rejects(f.service.__prepareDatabaseInner(), /no longer fresh/);
|
|
140
|
+
assert.deepEqual(f.calls, ['discover']);
|
|
141
|
+
f.setCurrent('vona-test-synthetic-s-20261019-010203');
|
|
142
|
+
await assert.rejects(f.service.__prepareDatabaseInner(), /unowned database/);
|
|
143
|
+
assert.deepEqual(f.calls, ['discover']);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('strict database startup creates once and reuses only its successful own creation', async () => {
|
|
147
|
+
strict = true;
|
|
148
|
+
ready = true;
|
|
149
|
+
const f = databaseFixture();
|
|
150
|
+
await f.service.__prepareDatabaseInner();
|
|
151
|
+
await f.service.__prepareDatabaseInner();
|
|
152
|
+
assert.deepEqual(f.calls, ['discover', 'create', 'reload']);
|
|
153
|
+
f.setCurrent('vona-test-synthetic-s-20261019-010203');
|
|
154
|
+
await assert.rejects(f.service.__prepareDatabaseInner(), /unowned database/);
|
|
155
|
+
ready = false;
|
|
156
|
+
await assert.rejects(f.service.__prepareDatabaseInner(), /freshness missing/);
|
|
157
|
+
ready = true;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('strict startup bypasses clearing only with established proof and does not bypass later startups', async () => {
|
|
161
|
+
strict = true;
|
|
162
|
+
ready = true;
|
|
163
|
+
const calls: string[] = [];
|
|
164
|
+
const service = Object.create(ServiceStartup.prototype);
|
|
165
|
+
Object.defineProperties(service, {
|
|
166
|
+
app: { value: { meta: { isTest: true, isDev: false, env: {} } } },
|
|
167
|
+
$scope: {
|
|
168
|
+
value: {
|
|
169
|
+
redis: {
|
|
170
|
+
service: {
|
|
171
|
+
redis: {
|
|
172
|
+
clearAllData: async () => {
|
|
173
|
+
throw new Error('must not clear Redis');
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
bean: {
|
|
181
|
+
value: {
|
|
182
|
+
onion: {
|
|
183
|
+
startup: {
|
|
184
|
+
getOnionsEnabledCached: () => [{ name: 'synthetic', beanOptions: { options: {} } }],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
runStartup: {
|
|
190
|
+
value: async () => {
|
|
191
|
+
calls.push('startup');
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
await service.appStart();
|
|
196
|
+
assert.deepEqual(calls, ['startup']);
|
|
197
|
+
ready = false;
|
|
198
|
+
await assert.rejects(service.appStart(), /freshness missing/);
|
|
199
|
+
assert.deepEqual(calls, ['startup']);
|
|
200
|
+
ready = true;
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('ordinary test database selected-target behavior remains unchanged without strict flag', async () => {
|
|
204
|
+
strict = false;
|
|
205
|
+
ready = false;
|
|
206
|
+
const f = databaseFixture();
|
|
207
|
+
f.setCurrent('vona-test-synthetic-s-20261019-010203');
|
|
208
|
+
await f.service.__prepareDatabaseInner();
|
|
209
|
+
assert.deepEqual(f.calls, []);
|
|
210
|
+
strict = true;
|
|
211
|
+
ready = true;
|
|
212
|
+
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "vona",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"packageManager": "pnpm@
|
|
5
|
+
"packageManager": "pnpm@12.14.0",
|
|
6
6
|
"description": "Vona is an intuitive, elegant and powerful Node.js framework for rapidly developing enterprise applications of any size",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
},
|
|
16
16
|
"homepage": "https://github.com/vonajs/vona#readme",
|
|
17
17
|
"engines": {
|
|
18
|
-
"node": ">=24.4.0"
|
|
18
|
+
"node": ">=24.4.0",
|
|
19
|
+
"pnpm": ">=11.5.2"
|
|
19
20
|
},
|
|
20
21
|
"scripts": {
|
|
21
22
|
"vona": "node ./packages-cli/cli/src/bin/vona.ts",
|
|
@@ -5,7 +5,7 @@ import { BeanCliBase } from '@cabloy/cli';
|
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import nodemon from 'nodemon';
|
|
7
7
|
import { rimraf } from 'rimraf';
|
|
8
|
-
import { resolveVonaLocalPaths } from 'vona-core';
|
|
8
|
+
import { requiresFreshTestResources, resolveVonaLocalPaths } from 'vona-core';
|
|
9
9
|
|
|
10
10
|
import type { VonaBinConfigOptions } from './toolsBin/types.ts';
|
|
11
11
|
|
|
@@ -48,16 +48,21 @@ export class CliBinDev extends BeanCliBase {
|
|
|
48
48
|
const workers = resolveTestWorkers(mode, argv.workers);
|
|
49
49
|
const flavor: VonaMetaFlavor = argv.flavor || 'normal';
|
|
50
50
|
const configMeta: VonaConfigMeta = { flavor, mode };
|
|
51
|
+
const strict = requiresFreshTestResources();
|
|
51
52
|
const paths = resolveVonaLocalPaths(projectPath, mode);
|
|
53
|
+
if (strict && (mode !== 'test' || !paths.runId)) {
|
|
54
|
+
throw new Error('Strict generation requires an isolated test run in test mode');
|
|
55
|
+
}
|
|
52
56
|
const configOptions: VonaBinConfigOptions = {
|
|
53
57
|
appDir: projectPath,
|
|
54
58
|
runtimeDir: paths.generatedDir,
|
|
55
59
|
workers,
|
|
56
60
|
};
|
|
57
61
|
try {
|
|
58
|
-
const { modulesMeta } = await generateVonaMeta(configMeta, configOptions);
|
|
62
|
+
const { env, modulesMeta } = await generateVonaMeta(configMeta, configOptions);
|
|
59
63
|
if (mode === 'test') {
|
|
60
|
-
await this._runTest(projectPath);
|
|
64
|
+
if (strict) await this._runTest(projectPath, env);
|
|
65
|
+
else await this._runTest(projectPath);
|
|
61
66
|
} else {
|
|
62
67
|
await this._run(projectPath, modulesMeta);
|
|
63
68
|
}
|
|
@@ -66,7 +71,11 @@ export class CliBinDev extends BeanCliBase {
|
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
73
|
|
|
69
|
-
async _runTest(projectPath: string) {
|
|
74
|
+
async _runTest(projectPath: string, resolvedEnv?: NodeJS.ProcessEnv) {
|
|
75
|
+
const strict = requiresFreshTestResources();
|
|
76
|
+
if (strict && !resolvedEnv) {
|
|
77
|
+
throw new Error('Strict generation requires resolved environment handoff');
|
|
78
|
+
}
|
|
70
79
|
const generatedDir = resolveVonaLocalPaths(projectPath, 'test').generatedDir;
|
|
71
80
|
const child = spawn(
|
|
72
81
|
process.execPath,
|
|
@@ -74,6 +83,7 @@ export class CliBinDev extends BeanCliBase {
|
|
|
74
83
|
{
|
|
75
84
|
cwd: projectPath,
|
|
76
85
|
stdio: 'inherit',
|
|
86
|
+
...(strict ? { env: { ...process.env, ...resolvedEnv } } : {}),
|
|
77
87
|
},
|
|
78
88
|
);
|
|
79
89
|
const forwardSignal = (signal: NodeJS.Signals) => child.kill(signal);
|
|
@@ -10,6 +10,7 @@ import type { VonaBinConfigOptions } from './types.ts';
|
|
|
10
10
|
|
|
11
11
|
import { resolveTemplatePath } from '../../../utils.ts';
|
|
12
12
|
import { copyTemplateFile, getEnvMeta } from '../../utils.ts';
|
|
13
|
+
import { validateGenerationPaths } from './validateGenerationPaths.ts';
|
|
13
14
|
// import { generateZod } from './generateZod.ts';
|
|
14
15
|
|
|
15
16
|
export async function generateEntryFiles(
|
|
@@ -18,6 +19,7 @@ export async function generateEntryFiles(
|
|
|
18
19
|
modulesMeta: Awaited<ReturnType<typeof glob>>,
|
|
19
20
|
env: NodeJS.ProcessEnv,
|
|
20
21
|
) {
|
|
22
|
+
const strict = validateGenerationPaths(configMeta, configOptions);
|
|
21
23
|
// config
|
|
22
24
|
await __generateConfig();
|
|
23
25
|
// modules meta
|
|
@@ -103,7 +105,10 @@ export async function generateEntryFiles(
|
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
async function __generateEnvJson() {
|
|
106
|
-
|
|
108
|
+
// Strict generation retains the selected app.meta.env surface, never its resolved values.
|
|
109
|
+
const contentDest = strict
|
|
110
|
+
? `const keys = ${JSON.stringify(Object.keys(env).filter(key => env[key] !== undefined))};\nexport default Object.fromEntries(keys.filter(key => process.env[key] !== undefined).map(key => [key, process.env[key]])) as NodeJS.ProcessEnv;\n`
|
|
111
|
+
: `export default ${JSON.stringify(env, null, 2)} as unknown as NodeJS.ProcessEnv;\n`;
|
|
107
112
|
// output
|
|
108
113
|
const fileDest = path.join(configOptions.appDir, configOptions.runtimeDir, 'env.ts');
|
|
109
114
|
fse.ensureFileSync(fileDest);
|
|
@@ -1,24 +1,16 @@
|
|
|
1
1
|
import type { VonaConfigMeta } from '@cabloy/module-info';
|
|
2
2
|
|
|
3
|
-
import { assertTestRunOwner, resolveVonaLocalPaths } from 'vona-core';
|
|
4
|
-
|
|
5
3
|
import type { VonaBinConfigOptions } from './types.ts';
|
|
6
4
|
|
|
7
5
|
import { createConfigUtils } from './configUtils.ts';
|
|
8
6
|
import { generateEntryFiles } from './generateEntryFiles.ts';
|
|
7
|
+
import { validateGenerationPaths } from './validateGenerationPaths.ts';
|
|
9
8
|
|
|
10
9
|
export async function generateVonaMeta(
|
|
11
10
|
configMeta: VonaConfigMeta,
|
|
12
11
|
configOptions: VonaBinConfigOptions,
|
|
13
12
|
) {
|
|
14
|
-
|
|
15
|
-
if (paths.runId) {
|
|
16
|
-
if (configOptions.runtimeDir !== paths.generatedDir) {
|
|
17
|
-
throw new Error('Isolated test generated directory does not match its run ID');
|
|
18
|
-
}
|
|
19
|
-
assertTestRunOwner(paths.dataRoot, paths.runId);
|
|
20
|
-
assertTestRunOwner(paths.generatedRoot, paths.runId);
|
|
21
|
-
}
|
|
13
|
+
validateGenerationPaths(configMeta, configOptions);
|
|
22
14
|
// config utils
|
|
23
15
|
const configUtils = createConfigUtils(configMeta, configOptions);
|
|
24
16
|
// env
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { VonaConfigMeta } from '@cabloy/module-info';
|
|
2
|
+
|
|
3
|
+
import { assertTestRunOwner, requiresFreshTestResources, resolveVonaLocalPaths } from 'vona-core';
|
|
4
|
+
|
|
5
|
+
import type { VonaBinConfigOptions } from './types.ts';
|
|
6
|
+
|
|
7
|
+
export function validateGenerationPaths(
|
|
8
|
+
configMeta: VonaConfigMeta,
|
|
9
|
+
configOptions: VonaBinConfigOptions,
|
|
10
|
+
) {
|
|
11
|
+
const strict = requiresFreshTestResources();
|
|
12
|
+
if (strict && configMeta.mode !== 'test') {
|
|
13
|
+
throw new Error('Strict generation requires test mode');
|
|
14
|
+
}
|
|
15
|
+
const paths = resolveVonaLocalPaths(configOptions.appDir, configMeta.mode);
|
|
16
|
+
if (strict && !paths.runId) {
|
|
17
|
+
throw new Error('Strict generation requires an isolated test run');
|
|
18
|
+
}
|
|
19
|
+
if (paths.runId) {
|
|
20
|
+
if (configOptions.runtimeDir !== paths.generatedDir) {
|
|
21
|
+
throw new Error('Isolated test generated directory does not match its run ID');
|
|
22
|
+
}
|
|
23
|
+
assertTestRunOwner(paths.dataRoot, paths.runId);
|
|
24
|
+
assertTestRunOwner(paths.generatedRoot, paths.runId);
|
|
25
|
+
}
|
|
26
|
+
return strict;
|
|
27
|
+
}
|
|
@@ -26,7 +26,7 @@ const source = `
|
|
|
26
26
|
'@cabloy/cli': 'export class BeanCliBase {}',
|
|
27
27
|
'nodemon': 'export default globalThis.nodemonMock;',
|
|
28
28
|
'rimraf': 'export function rimraf() { throw new Error("Unexpected cleanup"); }',
|
|
29
|
-
'vona-core': 'export function resolveVonaLocalPaths() { throw new Error("Unexpected app access"); }',
|
|
29
|
+
'vona-core': 'export function resolveVonaLocalPaths() { throw new Error("Unexpected app access"); } export function requiresFreshTestResources() { return false; }',
|
|
30
30
|
'../utils.ts': 'export function getImportEsm() { return "--import=./.vona/register.js"; }',
|
|
31
31
|
'./toolsBin/generateVonaMeta.ts': 'export function generateVonaMeta() { throw new Error("Unexpected generation"); }',
|
|
32
32
|
};
|