cabloy 5.1.204 → 5.1.205
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/.gitignore +2 -0
- package/CHANGELOG.md +11 -0
- package/lint-staged.config.mjs +2 -0
- package/oxfmt.config.ts +2 -0
- package/oxlint.config.ts +2 -0
- package/package.json +2 -3
- package/repo-agent-governance/managed-assets.json +6 -6
- package/repo-agent-governance/policies/repository.md +3 -2
- package/repo-agent-governance/skills/cabloy-workflow/SKILL.md +2 -0
- package/repo-docs/backend/migration-and-changes.md +13 -12
- package/repo-docs/backend/runtime-and-flavors.md +2 -1
- package/repo-docs/backend/unit-testing.md +12 -8
- package/repo-docs/blogs/ai-coding-one-prompt-crud-ssr-multitasking/index.md +9 -9
- package/repo-docs/frontend/scripts.md +1 -1
- package/repo-docs/fullstack/parallel-worktree-environment.md +6 -6
- package/repo-docs/fullstack/ssr-entry-modes.md +19 -0
- package/repo-docs/reference/repo-scripts.md +22 -1
- package/repo-e2e/config/playwright.config.ts +32 -14
- package/repo-e2e/scripts/e2e.test.ts +39 -0
- package/repo-e2e/scripts/e2e.ts +19 -1
- package/repo-e2e/scripts/managedTest.test.ts +389 -0
- package/repo-e2e/scripts/managedTest.ts +210 -0
- package/repo-e2e/scripts/managedTestImports.test.ts +74 -0
- package/repo-e2e/scripts/managedTestImports.ts +110 -0
- package/repo-e2e/scripts/runE2e.ts +12 -1
- package/repo-e2e/scripts/startE2eVona.ts +37 -5
- package/repo-e2e/specs/a-commerce.spec.ts +1 -1
- package/repo-e2e/specs/cabloy-basic.spec.ts +1 -1
- package/repo-e2e/specs/controller-boundary.spec.ts +1 -1
- package/repo-e2e/specs/home-user-account.spec.ts +2 -1
- package/repo-e2e/specs/markdown.spec.ts +1 -1
- package/repo-e2e/specs/routed-dialog.spec.ts +1 -1
- package/scripts/runIsolatedTest.test.ts +426 -0
- package/scripts/runIsolatedTest.ts +454 -0
- package/scripts/testClaimCleanup.test.ts +346 -0
- package/scripts/testClaimCleanup.ts +375 -0
- package/scripts/testClaimProcesses.test.ts +106 -0
- package/scripts/testClaimProcesses.ts +129 -0
- package/scripts/testProcessGuardian.ts +163 -0
- package/scripts/upgrade.ts +1 -16
- package/vona/oxfmt.config.ts +2 -0
- package/vona/oxlint.config.ts +2 -0
- package/vona/package.original.json +2 -2
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli/src/play.test.ts +37 -0
- package/vona/packages-cli/cli/src/play.ts +23 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dbReset.ts +16 -7
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +13 -7
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.play.ts +5 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.test.ts +12 -7
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/configUtils.ts +21 -2
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateVonaMeta.ts +10 -0
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/resolveTestCleanupConfig.test.ts +153 -0
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/resolveTestCleanupConfig.ts +141 -0
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/testCleanupAdapters.test.ts +148 -0
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/testCleanupAdapters.ts +127 -0
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/testCleanupProcess.test.ts +47 -0
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/testCleanupProcess.ts +139 -0
- package/vona/packages-cli/cli-set-api/src/lib/utils.ts +2 -3
- package/vona/packages-cli/cli-set-api/templates/config/_tsconfig_project.json +1 -0
- package/vona/packages-cli/cli-set-api/templates/config/_tsconfig_source.json +1 -0
- package/vona/packages-cli/cli-set-api/test/binDevMode.test.ts +18 -0
- package/vona/packages-cli/cli-set-api/toolsIsolate/dbReset.ts +15 -2
- package/vona/packages-cli/cli-set-api/tsconfig.build.json +2 -1
- package/vona/packages-utils/babel-plugin-vona-bean-module/package.json +1 -1
- package/vona/packages-utils/babel-plugin-vona-bean-module/src/index.test.ts +16 -0
- package/vona/packages-utils/babel-plugin-vona-bean-module/src/index.ts +3 -2
- 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/addon/sqlite3.ts +1 -1
- package/vona/packages-vona/vona-core/src/lib/core/config.ts +44 -6
- package/vona/packages-vona/vona-core/src/lib/core/configCleanup.test.ts +40 -0
- package/vona/packages-vona/vona-core/src/lib/core/index.ts +3 -0
- package/vona/packages-vona/vona-core/src/lib/core/isolationGuard.test.ts +301 -0
- package/vona/packages-vona/vona-core/src/lib/core/isolationGuard.ts +312 -0
- package/vona/packages-vona/vona-core/src/lib/core/testClaim.test.ts +577 -0
- package/vona/packages-vona/vona-core/src/lib/core/testClaim.ts +985 -0
- package/vona/packages-vona/vona-core/src/lib/core/testIsolation.ts +129 -0
- package/vona/packages-vona/vona-core/src/lib/framework/createApp.ts +2 -0
- package/vona/packages-vona/vona-core/src/lib/framework/start.ts +4 -0
- package/vona/packages-vona/vona-core/src/lib/framework/useApp.ts +5 -1
- package/vona/packages-vona/vona-core/src/types/application/app.ts +2 -0
- package/vona/packages-vona/vona-core/src/types/utils/env.ts +1 -0
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +1 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/test/paymentIndexes.test.ts +73 -10
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- 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/middlewareSystem.httpLog.ts +15 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactIsolatedReadyHeader.test.ts +24 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactIsolatedReadyHeader.ts +11 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/bean/middlewareSystem.health.ts +15 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/extend/schemaBuilder.ts +11 -4
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/databaseClient_.ts +6 -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/bean/databaseDialect.betterSqlite3.ts +41 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/bean/databaseDialect.mysql.ts +4 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/bean/databaseDialect.pg.ts +2 -1
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-redis/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-redis/src/lib/redis.ts +8 -0
- package/vona/src/suite-vendor/a-vona/modules/a-redis/src/service/redis.ts +23 -6
- package/vona/src/suite-vendor/a-vona/modules/a-redis/src/service/redisClient_.ts +18 -1
- 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 +43 -15
- 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/lib/databaseTestName.ts +3 -37
- package/vona/src/suite-vendor/a-vona/modules/a-version/src/service/database.ts +93 -15
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/vona/tsconfig.json +1 -0
- package/scripts/initTestData.ts +0 -25
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type { ChildProcess } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
import type { TestProcessIdentity } from './testClaimProcesses.ts';
|
|
6
|
+
|
|
7
|
+
import { sendTestClaimEvent } from '../vona/packages-vona/vona-core/src/lib/core/testClaim.ts';
|
|
8
|
+
|
|
9
|
+
interface StartMessage {
|
|
10
|
+
type: 'start';
|
|
11
|
+
command: string;
|
|
12
|
+
args: string[];
|
|
13
|
+
cwd: string;
|
|
14
|
+
env: NodeJS.ProcessEnv;
|
|
15
|
+
processId?: string;
|
|
16
|
+
identity?: TestProcessIdentity;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (process.platform === 'win32' || !process.send) {
|
|
20
|
+
throw new Error('Isolated test guardian requires a POSIX process group and parent IPC');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let child: ChildProcess | undefined;
|
|
24
|
+
let stopping = false;
|
|
25
|
+
let finishing = false;
|
|
26
|
+
let finished = false;
|
|
27
|
+
let failed = false;
|
|
28
|
+
let childResult: { code: number | null; signal: NodeJS.Signals | null } | undefined;
|
|
29
|
+
let forceTimer: NodeJS.Timeout | undefined;
|
|
30
|
+
let launch: StartMessage | undefined;
|
|
31
|
+
|
|
32
|
+
async function groupMembers(): Promise<number[]> {
|
|
33
|
+
const output = await new Promise<string>((resolvePromise, reject) => {
|
|
34
|
+
const inspector = spawn('ps', ['-A', '-o', 'pid=', '-o', 'pgid=', '-o', 'state='], {
|
|
35
|
+
detached: true,
|
|
36
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
37
|
+
});
|
|
38
|
+
let stdout = '';
|
|
39
|
+
let stderr = '';
|
|
40
|
+
inspector.stdout?.on('data', data => {
|
|
41
|
+
stdout += data;
|
|
42
|
+
});
|
|
43
|
+
inspector.stderr?.on('data', data => {
|
|
44
|
+
stderr += data;
|
|
45
|
+
});
|
|
46
|
+
inspector.once('error', reject);
|
|
47
|
+
inspector.once('close', code => {
|
|
48
|
+
if (code === 0) resolvePromise(stdout);
|
|
49
|
+
else reject(new Error(`Cannot inspect isolated process group: ${stderr}`));
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
return output
|
|
53
|
+
.split('\n')
|
|
54
|
+
.map(line => line.trim().split(/\s+/))
|
|
55
|
+
.filter(
|
|
56
|
+
([pid, pgid, state]) =>
|
|
57
|
+
pid &&
|
|
58
|
+
pgid &&
|
|
59
|
+
state &&
|
|
60
|
+
Number(pgid) === process.pid &&
|
|
61
|
+
Number(pid) !== process.pid &&
|
|
62
|
+
!state.startsWith('Z'),
|
|
63
|
+
)
|
|
64
|
+
.map(([pid]) => Number(pid));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function requestStop() {
|
|
68
|
+
if (stopping || finished) return;
|
|
69
|
+
stopping = true;
|
|
70
|
+
forceTimer = setTimeout(() => {
|
|
71
|
+
// The guardian remains its own group leader while issuing the last resort signal.
|
|
72
|
+
process.kill(-process.pid, 'SIGKILL');
|
|
73
|
+
}, 12_000);
|
|
74
|
+
try {
|
|
75
|
+
process.kill(-process.pid, 'SIGINT');
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if ((error as NodeJS.ErrnoException).code !== 'ESRCH') throw error;
|
|
78
|
+
}
|
|
79
|
+
if (!child) void drain();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function drain() {
|
|
83
|
+
if (finishing || finished) return;
|
|
84
|
+
finishing = true;
|
|
85
|
+
try {
|
|
86
|
+
let deadline = Date.now() + (stopping ? 10_000 : 1_000);
|
|
87
|
+
while ((await groupMembers()).length > 0) {
|
|
88
|
+
if (Date.now() >= deadline) {
|
|
89
|
+
if (stopping) {
|
|
90
|
+
throw new Error('Isolated process group did not drain after shutdown');
|
|
91
|
+
}
|
|
92
|
+
requestStop();
|
|
93
|
+
deadline = Date.now() + 10_000;
|
|
94
|
+
}
|
|
95
|
+
await new Promise(resolvePromise => setTimeout(resolvePromise, 100));
|
|
96
|
+
}
|
|
97
|
+
if (forceTimer) clearTimeout(forceTimer);
|
|
98
|
+
if (launch?.processId && launch.identity) {
|
|
99
|
+
try {
|
|
100
|
+
await sendTestClaimEvent(launch.env, {
|
|
101
|
+
type: 'process',
|
|
102
|
+
process: {
|
|
103
|
+
id: launch.processId,
|
|
104
|
+
role: 'guardian',
|
|
105
|
+
stage: 'drained',
|
|
106
|
+
pid: launch.identity.pid,
|
|
107
|
+
pgid: launch.identity.pgid,
|
|
108
|
+
startTime: launch.identity.startTime,
|
|
109
|
+
host: launch.identity.host,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
} catch {
|
|
113
|
+
failed = true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
finished = true;
|
|
117
|
+
process.send?.({ type: 'drained', result: childResult, failed }, error => {
|
|
118
|
+
if (error) process.exit(1);
|
|
119
|
+
else process.exit(0);
|
|
120
|
+
});
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error(error);
|
|
123
|
+
requestStop();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function start(message: StartMessage) {
|
|
128
|
+
if (child || stopping) return;
|
|
129
|
+
launch = message;
|
|
130
|
+
try {
|
|
131
|
+
child = spawn(message.command, message.args, {
|
|
132
|
+
cwd: message.cwd,
|
|
133
|
+
env: message.env,
|
|
134
|
+
stdio: 'inherit',
|
|
135
|
+
detached: false,
|
|
136
|
+
});
|
|
137
|
+
child.once('error', error => {
|
|
138
|
+
failed = true;
|
|
139
|
+
console.error(error);
|
|
140
|
+
});
|
|
141
|
+
child.once('close', (code, signal) => {
|
|
142
|
+
childResult = { code, signal };
|
|
143
|
+
void drain();
|
|
144
|
+
});
|
|
145
|
+
if (stopping) child.kill('SIGINT');
|
|
146
|
+
process.send?.({ type: 'started' });
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error(error);
|
|
149
|
+
failed = true;
|
|
150
|
+
childResult = { code: 1, signal: null };
|
|
151
|
+
void drain();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
process.on('SIGINT', requestStop);
|
|
156
|
+
process.on('SIGTERM', requestStop);
|
|
157
|
+
process.on('disconnect', requestStop);
|
|
158
|
+
process.on('message', message => {
|
|
159
|
+
if (!message || typeof message !== 'object') return;
|
|
160
|
+
if ('type' in message && message.type === 'stop') requestStop();
|
|
161
|
+
if ('type' in message && message.type === 'start') start(message as StartMessage);
|
|
162
|
+
});
|
|
163
|
+
process.send?.({ type: 'ready' });
|
package/scripts/upgrade.ts
CHANGED
|
@@ -549,17 +549,7 @@ function runInit(dryRun: boolean, version: string): void {
|
|
|
549
549
|
exec('npm run init', { CABLOY_VERSION: version } as any);
|
|
550
550
|
}
|
|
551
551
|
|
|
552
|
-
// --- Step 5:
|
|
553
|
-
|
|
554
|
-
function runInitTestData(dryRun: boolean): void {
|
|
555
|
-
if (dryRun) {
|
|
556
|
-
log(' [dry-run] Run: npm run init:test-data');
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
exec('npm run init:test-data');
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// --- Step 6: Cleanup ---
|
|
552
|
+
// --- Step 5: Cleanup ---
|
|
563
553
|
|
|
564
554
|
function cleanup(dryRun?: boolean): void {
|
|
565
555
|
if (existsSync(TEMP_DIR)) {
|
|
@@ -637,11 +627,6 @@ async function main(): Promise<void> {
|
|
|
637
627
|
runInit(dryRun, latestPackageInfo.version);
|
|
638
628
|
log('');
|
|
639
629
|
|
|
640
|
-
// 6. Run init:test-data
|
|
641
|
-
log('Running npm run init:test-data...');
|
|
642
|
-
runInitTestData(dryRun);
|
|
643
|
-
log('');
|
|
644
|
-
|
|
645
630
|
if (dryRun) {
|
|
646
631
|
log(`[dry-run] Current Cabloy version would become: ${latestPackageInfo.version}`);
|
|
647
632
|
} else {
|
package/vona/oxfmt.config.ts
CHANGED
package/vona/oxlint.config.ts
CHANGED
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"db:reset": "npm run prerun && npm run vona :bin:dbReset -- --flavor=normal",
|
|
24
24
|
"dev": "npm run prerun && npm run vona :bin:dev -- --workers=2 --flavor=normal",
|
|
25
25
|
"dev:one": "npm run prerun && npm run vona :bin:dev -- --workers=1 --flavor=normal",
|
|
26
|
-
"test": "
|
|
27
|
-
"cov": "
|
|
26
|
+
"test": "node ../scripts/runIsolatedTest.ts unit",
|
|
27
|
+
"cov": "node ../scripts/runIsolatedTest.ts unit --coverage",
|
|
28
28
|
"tsc": "npm run prerun && npm run vona :bin:tsc",
|
|
29
29
|
"build": "npm run prerun && npm run vona :bin:build -- --flavor=normal",
|
|
30
30
|
"build:docker": "npm run prerun && npm run vona :bin:build -- --flavor=docker",
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { test } from 'node:test';
|
|
4
|
+
|
|
5
|
+
const script = `
|
|
6
|
+
import { playAttach } from './play.ts';
|
|
7
|
+
await playAttach('/nonexistent-vona-project', []);
|
|
8
|
+
`;
|
|
9
|
+
|
|
10
|
+
test('play attach accepts the timestamped test identity before locating its runtime', () => {
|
|
11
|
+
const result = spawnSync(process.execPath, ['--input-type=module', '-e', script], {
|
|
12
|
+
cwd: import.meta.dirname,
|
|
13
|
+
env: {
|
|
14
|
+
...process.env,
|
|
15
|
+
META_MODE: 'test',
|
|
16
|
+
VONA_TEST_RUN_ID: '20261014010203-a1b2c3d4',
|
|
17
|
+
},
|
|
18
|
+
encoding: 'utf8',
|
|
19
|
+
});
|
|
20
|
+
assert.notEqual(result.status, 0);
|
|
21
|
+
assert.match(result.stderr, /dev server not running/);
|
|
22
|
+
assert.doesNotMatch(result.stderr, /VONA_TEST_RUN_ID requires/);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('play attach rejects impossible UTC seconds', () => {
|
|
26
|
+
const result = spawnSync(process.execPath, ['--input-type=module', '-e', script], {
|
|
27
|
+
cwd: import.meta.dirname,
|
|
28
|
+
env: {
|
|
29
|
+
...process.env,
|
|
30
|
+
META_MODE: 'test',
|
|
31
|
+
VONA_TEST_RUN_ID: '20260230010203-a1b2c3d4',
|
|
32
|
+
},
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
});
|
|
35
|
+
assert.notEqual(result.status, 0);
|
|
36
|
+
assert.match(result.stderr, /VONA_TEST_RUN_ID requires/);
|
|
37
|
+
});
|
|
@@ -4,7 +4,29 @@ import path from 'node:path';
|
|
|
4
4
|
import { loadJSONFile } from './utils.ts';
|
|
5
5
|
|
|
6
6
|
export async function playAttach(projectPath: string, args: string[]) {
|
|
7
|
-
const
|
|
7
|
+
const runId = process.env.VONA_TEST_RUN_ID;
|
|
8
|
+
if (runId) {
|
|
9
|
+
const match = /^(\d{14})-[a-f0-9]{8}$/.exec(runId);
|
|
10
|
+
const timestamp = match?.[1];
|
|
11
|
+
const date = timestamp
|
|
12
|
+
? new Date(
|
|
13
|
+
`${timestamp.slice(0, 4)}-${timestamp.slice(4, 6)}-${timestamp.slice(6, 8)}T${timestamp.slice(8, 10)}:${timestamp.slice(10, 12)}:${timestamp.slice(12, 14)}Z`,
|
|
14
|
+
)
|
|
15
|
+
: undefined;
|
|
16
|
+
if (
|
|
17
|
+
process.env.META_MODE !== 'test' ||
|
|
18
|
+
!timestamp ||
|
|
19
|
+
!date ||
|
|
20
|
+
Number.isNaN(date.getTime()) ||
|
|
21
|
+
date.toISOString().slice(0, 19).replace(/[-:T]/g, '') !== timestamp
|
|
22
|
+
) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
'VONA_TEST_RUN_ID requires test mode and a UTC timestamp plus eight hex digits',
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const dataDir = runId ? `.app-test-${runId}` : '.app';
|
|
29
|
+
const runtimeFile = path.join(projectPath, dataDir, 'runtime/-.json');
|
|
8
30
|
if (!fse.existsSync(runtimeFile)) throw new Error('dev server not running');
|
|
9
31
|
// body
|
|
10
32
|
const body = { args, projectPath };
|
|
@@ -5,6 +5,7 @@ import { BeanCliBase } from '@cabloy/cli';
|
|
|
5
5
|
import fse from 'fs-extra';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { rimraf } from 'rimraf';
|
|
8
|
+
import { resolveVonaLocalPaths } from 'vona-core';
|
|
8
9
|
|
|
9
10
|
import type { VonaBinConfigOptions } from './toolsBin/types.ts';
|
|
10
11
|
|
|
@@ -23,23 +24,27 @@ export class CliBinDbReset extends BeanCliBase {
|
|
|
23
24
|
// super
|
|
24
25
|
await super.execute();
|
|
25
26
|
const projectPath = argv.projectPath;
|
|
26
|
-
//
|
|
27
|
+
// recreate the development database
|
|
27
28
|
await this._dbReset(projectPath);
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
async _dbReset(projectPath: string) {
|
|
31
32
|
const { argv } = this.context;
|
|
32
|
-
const mode: VonaMetaMode = '
|
|
33
|
+
const mode: VonaMetaMode = 'dev';
|
|
33
34
|
const flavor: VonaMetaFlavor = argv.flavor || 'normal';
|
|
34
35
|
const configMeta: VonaConfigMeta = { flavor, mode };
|
|
36
|
+
const paths = resolveVonaLocalPaths(projectPath, mode);
|
|
35
37
|
const configOptions: VonaBinConfigOptions = {
|
|
36
38
|
appDir: projectPath,
|
|
37
|
-
runtimeDir:
|
|
39
|
+
runtimeDir: paths.generatedDir,
|
|
38
40
|
workers: 1,
|
|
39
41
|
};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
try {
|
|
43
|
+
const { modulesMeta } = await generateVonaMeta(configMeta, configOptions);
|
|
44
|
+
await this._run(projectPath, modulesMeta);
|
|
45
|
+
} finally {
|
|
46
|
+
await rimraf(paths.generatedRoot);
|
|
47
|
+
}
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
async _run(projectPath: string, _modulesMeta: Awaited<ReturnType<typeof glob>>) {
|
|
@@ -50,7 +55,11 @@ export class CliBinDbReset extends BeanCliBase {
|
|
|
50
55
|
}
|
|
51
56
|
// run
|
|
52
57
|
let args: string[] = [];
|
|
53
|
-
args = args.concat([
|
|
58
|
+
args = args.concat([
|
|
59
|
+
getImportEsm(resolveVonaLocalPaths(projectPath, 'dev').generatedDir),
|
|
60
|
+
testFile,
|
|
61
|
+
projectPath,
|
|
62
|
+
]);
|
|
54
63
|
// args = args.concat(['--experimental-transform-types', getImportEsm(), testFile, projectPath]);
|
|
55
64
|
await this.helper.spawnExe({
|
|
56
65
|
cmd: 'node',
|
|
@@ -3,9 +3,9 @@ import type { VonaConfigMeta, VonaMetaFlavor, VonaMetaMode } from '@cabloy/modul
|
|
|
3
3
|
|
|
4
4
|
import { BeanCliBase } from '@cabloy/cli';
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
|
-
import path from 'node:path';
|
|
7
6
|
import nodemon from 'nodemon';
|
|
8
7
|
import { rimraf } from 'rimraf';
|
|
8
|
+
import { resolveVonaLocalPaths } from 'vona-core';
|
|
9
9
|
|
|
10
10
|
import type { VonaBinConfigOptions } from './toolsBin/types.ts';
|
|
11
11
|
|
|
@@ -48,9 +48,10 @@ 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 paths = resolveVonaLocalPaths(projectPath, mode);
|
|
51
52
|
const configOptions: VonaBinConfigOptions = {
|
|
52
53
|
appDir: projectPath,
|
|
53
|
-
runtimeDir:
|
|
54
|
+
runtimeDir: paths.generatedDir,
|
|
54
55
|
workers,
|
|
55
56
|
};
|
|
56
57
|
try {
|
|
@@ -61,15 +62,20 @@ export class CliBinDev extends BeanCliBase {
|
|
|
61
62
|
await this._run(projectPath, modulesMeta);
|
|
62
63
|
}
|
|
63
64
|
} finally {
|
|
64
|
-
await rimraf(
|
|
65
|
+
if (!paths.runId) await rimraf(paths.generatedRoot);
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
async _runTest(projectPath: string) {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
const generatedDir = resolveVonaLocalPaths(projectPath, 'test').generatedDir;
|
|
71
|
+
const child = spawn(
|
|
72
|
+
process.execPath,
|
|
73
|
+
[getImportEsm(generatedDir), `${generatedDir}/bootstrap.ts`],
|
|
74
|
+
{
|
|
75
|
+
cwd: projectPath,
|
|
76
|
+
stdio: 'inherit',
|
|
77
|
+
},
|
|
78
|
+
);
|
|
73
79
|
const forwardSignal = (signal: NodeJS.Signals) => child.kill(signal);
|
|
74
80
|
const onInterrupt = () => forwardSignal('SIGINT');
|
|
75
81
|
const onTerminate = () => forwardSignal('SIGTERM');
|
|
@@ -3,6 +3,7 @@ import type { VonaConfigMeta, VonaMetaFlavor, VonaMetaMode } from '@cabloy/modul
|
|
|
3
3
|
import { BeanCliBase } from '@cabloy/cli';
|
|
4
4
|
import fse from 'fs-extra';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import { resolveVonaLocalPaths } from 'vona-core';
|
|
6
7
|
|
|
7
8
|
import type { VonaBinConfigOptions } from './toolsBin/types.ts';
|
|
8
9
|
|
|
@@ -49,7 +50,10 @@ export class CliBinPlay extends BeanCliBase {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
async _runAttach(projectPath: string) {
|
|
52
|
-
const runtimeFile = path.join(
|
|
53
|
+
const runtimeFile = path.join(
|
|
54
|
+
resolveVonaLocalPaths(projectPath, process.env.META_MODE ?? 'dev').runtimeRoot,
|
|
55
|
+
'-.json',
|
|
56
|
+
);
|
|
53
57
|
if (!fse.existsSync(runtimeFile)) throw new Error('dev server not running');
|
|
54
58
|
// args
|
|
55
59
|
let args: string[] = [];
|
|
@@ -6,6 +6,7 @@ import { catchError } from '@cabloy/utils';
|
|
|
6
6
|
import fse from 'fs-extra';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { rimraf } from 'rimraf';
|
|
9
|
+
import { resolveVonaLocalPaths } from 'vona-core';
|
|
9
10
|
|
|
10
11
|
import type { VonaBinConfigOptions } from './toolsBin/types.ts';
|
|
11
12
|
|
|
@@ -34,17 +35,21 @@ export class CliBinTest extends BeanCliBase {
|
|
|
34
35
|
const mode: VonaMetaMode = 'test';
|
|
35
36
|
const flavor: VonaMetaFlavor = argv.flavor || 'normal';
|
|
36
37
|
const configMeta: VonaConfigMeta = { flavor, mode };
|
|
38
|
+
const paths = resolveVonaLocalPaths(projectPath, mode);
|
|
37
39
|
const configOptions: VonaBinConfigOptions = {
|
|
38
40
|
appDir: projectPath,
|
|
39
|
-
runtimeDir:
|
|
41
|
+
runtimeDir: paths.generatedDir,
|
|
40
42
|
workers: 1,
|
|
41
43
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
try {
|
|
45
|
+
const { modulesMeta } = await generateVonaMeta(configMeta, configOptions);
|
|
46
|
+
await this._run(projectPath, modulesMeta);
|
|
47
|
+
if (argv.coverage && !argv.ci) {
|
|
48
|
+
await this._outputCoverageReportViewer(projectPath);
|
|
49
|
+
}
|
|
50
|
+
} finally {
|
|
51
|
+
if (!paths.runId) await rimraf(paths.generatedRoot);
|
|
46
52
|
}
|
|
47
|
-
await rimraf(path.join(projectPath, '.vona'));
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
async _run(projectPath: string, modulesMeta: Awaited<ReturnType<typeof glob>>) {
|
|
@@ -70,7 +75,7 @@ export class CliBinTest extends BeanCliBase {
|
|
|
70
75
|
args.push('--import=why-is-node-running/include');
|
|
71
76
|
}
|
|
72
77
|
args = args.concat([
|
|
73
|
-
getImportEsm(),
|
|
78
|
+
getImportEsm(resolveVonaLocalPaths(projectPath, 'test').generatedDir),
|
|
74
79
|
testFile,
|
|
75
80
|
projectPath,
|
|
76
81
|
(!!argv.coverage).toString(),
|
|
@@ -4,6 +4,7 @@ import * as dotenv from '@cabloy/dotenv';
|
|
|
4
4
|
import { glob } from '@cabloy/module-glob';
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
+
import { getTestAppName, resolveTestRunId, TEST_RUN_ID_ENV } from 'vona-core';
|
|
7
8
|
|
|
8
9
|
import type { VonaBinConfigOptions } from './types.ts';
|
|
9
10
|
|
|
@@ -27,6 +28,10 @@ export function createConfigUtils(
|
|
|
27
28
|
function __loadEnvs(): NodeJS.ProcessEnv {
|
|
28
29
|
const meta = getEnvMeta(configMeta);
|
|
29
30
|
const envDir = path.join(configOptions.appDir, 'env');
|
|
31
|
+
const runId = resolveTestRunId(meta.mode, process.env[TEST_RUN_ID_ENV]);
|
|
32
|
+
if (runId && process.env.VONA_DB_RESET !== undefined) {
|
|
33
|
+
throw new Error('Isolated test cannot run a development database reset');
|
|
34
|
+
}
|
|
30
35
|
const envs = dotenv.loadEnvs(meta, envDir, '.env');
|
|
31
36
|
const res = Object.assign(
|
|
32
37
|
{
|
|
@@ -38,6 +43,20 @@ export function createConfigUtils(
|
|
|
38
43
|
META_MODE: meta.mode,
|
|
39
44
|
},
|
|
40
45
|
);
|
|
46
|
+
if (runId) {
|
|
47
|
+
res[TEST_RUN_ID_ENV] = runId;
|
|
48
|
+
res.APP_NAME = getTestAppName(runId);
|
|
49
|
+
const port = Number(process.env.SERVER_LISTEN_PORT);
|
|
50
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
51
|
+
throw new Error('Isolated test requires an assigned SERVER_LISTEN_PORT');
|
|
52
|
+
}
|
|
53
|
+
res.SERVER_LISTEN_PORT = String(port);
|
|
54
|
+
res.SERVER_LISTEN_HOSTNAME = '127.0.0.1';
|
|
55
|
+
if (process.env.VONA_TEST_SERVER_ORIGIN === 'true') {
|
|
56
|
+
res.SERVER_SERVE_PROTOCOL = 'http';
|
|
57
|
+
res.SERVER_SERVE_HOST = `127.0.0.1:${port}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
41
60
|
if (configOptions.workers !== undefined) {
|
|
42
61
|
res.SERVER_WORKERS = configOptions.workers.toString();
|
|
43
62
|
}
|
|
@@ -49,8 +68,8 @@ export function createConfigUtils(
|
|
|
49
68
|
res.SERVER_WORKERS = '1';
|
|
50
69
|
}
|
|
51
70
|
}
|
|
52
|
-
for (const key of ['NODE_ENV', 'SERVER_WORKERS', 'META_FLAVOR', 'META_MODE']) {
|
|
53
|
-
if ((res[key] as any) !== false) {
|
|
71
|
+
for (const key of ['NODE_ENV', 'SERVER_WORKERS', 'META_FLAVOR', 'META_MODE', TEST_RUN_ID_ENV]) {
|
|
72
|
+
if (res[key] !== undefined && (res[key] as any) !== false) {
|
|
54
73
|
process.env[key] = res[key];
|
|
55
74
|
}
|
|
56
75
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { VonaConfigMeta } from '@cabloy/module-info';
|
|
2
2
|
|
|
3
|
+
import { assertTestRunOwner, resolveVonaLocalPaths } from 'vona-core';
|
|
4
|
+
|
|
3
5
|
import type { VonaBinConfigOptions } from './types.ts';
|
|
4
6
|
|
|
5
7
|
import { createConfigUtils } from './configUtils.ts';
|
|
@@ -9,6 +11,14 @@ export async function generateVonaMeta(
|
|
|
9
11
|
configMeta: VonaConfigMeta,
|
|
10
12
|
configOptions: VonaBinConfigOptions,
|
|
11
13
|
) {
|
|
14
|
+
const paths = resolveVonaLocalPaths(configOptions.appDir, configMeta.mode);
|
|
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
|
+
}
|
|
12
22
|
// config utils
|
|
13
23
|
const configUtils = createConfigUtils(configMeta, configOptions);
|
|
14
24
|
// env
|