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,110 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import ts from 'typescript';
|
|
5
|
+
|
|
6
|
+
const fixturePath = fileURLToPath(new URL('./managedTest.ts', import.meta.url));
|
|
7
|
+
const specRoot = fileURLToPath(new URL('../specs', import.meta.url));
|
|
8
|
+
|
|
9
|
+
export function managedTestImportViolations(source: string, filename: string): string[] {
|
|
10
|
+
const file = ts.createSourceFile(
|
|
11
|
+
filename,
|
|
12
|
+
source,
|
|
13
|
+
ts.ScriptTarget.Latest,
|
|
14
|
+
true,
|
|
15
|
+
ts.ScriptKind.TS,
|
|
16
|
+
);
|
|
17
|
+
const errors: string[] = [];
|
|
18
|
+
let usesManagedTest = false;
|
|
19
|
+
const isPlaywright = (value: string) =>
|
|
20
|
+
/^(?:@playwright\/test|playwright(?:-core)?)(?:\/|$)/.test(value);
|
|
21
|
+
const isFixture = (value: string) =>
|
|
22
|
+
value.startsWith('.') &&
|
|
23
|
+
resolve(dirname(filename), value.replace(/\.js$/, '.ts')) === fixturePath;
|
|
24
|
+
function visit(node: ts.Node) {
|
|
25
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
26
|
+
const module = node.moduleSpecifier.text;
|
|
27
|
+
const clause = node.importClause;
|
|
28
|
+
const bindings = clause?.namedBindings;
|
|
29
|
+
const typeOnly =
|
|
30
|
+
clause?.isTypeOnly ||
|
|
31
|
+
(!clause?.name &&
|
|
32
|
+
bindings &&
|
|
33
|
+
ts.isNamedImports(bindings) &&
|
|
34
|
+
bindings.elements.every(item => item.isTypeOnly));
|
|
35
|
+
if (isPlaywright(module) && !typeOnly)
|
|
36
|
+
errors.push('runtime Playwright import bypasses managedTest');
|
|
37
|
+
if (
|
|
38
|
+
isFixture(module) &&
|
|
39
|
+
!clause?.isTypeOnly &&
|
|
40
|
+
bindings &&
|
|
41
|
+
ts.isNamedImports(bindings) &&
|
|
42
|
+
bindings.elements.some(
|
|
43
|
+
item => !item.isTypeOnly && (item.propertyName ?? item.name).text === 'test',
|
|
44
|
+
)
|
|
45
|
+
) {
|
|
46
|
+
usesManagedTest = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (
|
|
50
|
+
ts.isExportDeclaration(node) &&
|
|
51
|
+
node.moduleSpecifier &&
|
|
52
|
+
ts.isStringLiteral(node.moduleSpecifier) &&
|
|
53
|
+
isPlaywright(node.moduleSpecifier.text)
|
|
54
|
+
) {
|
|
55
|
+
const typeOnly =
|
|
56
|
+
node.isTypeOnly ||
|
|
57
|
+
(node.exportClause &&
|
|
58
|
+
ts.isNamedExports(node.exportClause) &&
|
|
59
|
+
node.exportClause.elements.every(item => item.isTypeOnly));
|
|
60
|
+
if (!typeOnly) errors.push('runtime Playwright export bypasses managedTest');
|
|
61
|
+
}
|
|
62
|
+
if (
|
|
63
|
+
ts.isCallExpression(node) &&
|
|
64
|
+
(node.expression.kind === ts.SyntaxKind.ImportKeyword ||
|
|
65
|
+
(ts.isIdentifier(node.expression) && node.expression.text === 'require'))
|
|
66
|
+
) {
|
|
67
|
+
const module = node.arguments[0];
|
|
68
|
+
if (module && ts.isStringLiteral(module) && isPlaywright(module.text))
|
|
69
|
+
errors.push('dynamic Playwright load bypasses managedTest');
|
|
70
|
+
}
|
|
71
|
+
if (
|
|
72
|
+
ts.isImportEqualsDeclaration(node) &&
|
|
73
|
+
!node.isTypeOnly &&
|
|
74
|
+
ts.isExternalModuleReference(node.moduleReference)
|
|
75
|
+
) {
|
|
76
|
+
const module = node.moduleReference.expression;
|
|
77
|
+
if (module && ts.isStringLiteral(module) && isPlaywright(module.text))
|
|
78
|
+
errors.push('runtime Playwright import assignment bypasses managedTest');
|
|
79
|
+
}
|
|
80
|
+
ts.forEachChild(node, visit);
|
|
81
|
+
}
|
|
82
|
+
visit(file);
|
|
83
|
+
if (!usesManagedTest) errors.push('application spec must import test from managedTest');
|
|
84
|
+
return errors;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function applicationSpecs(root: string): Promise<string[]> {
|
|
88
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
89
|
+
const files = await Promise.all(
|
|
90
|
+
entries.map(async entry => {
|
|
91
|
+
const path = resolve(root, entry.name);
|
|
92
|
+
if (entry.isSymbolicLink()) throw new Error('Managed E2E spec tree cannot contain symlinks');
|
|
93
|
+
if (entry.isDirectory()) return applicationSpecs(path);
|
|
94
|
+
return entry.isFile() && entry.name.endsWith('.spec.ts') ? [path] : [];
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
return files.flat().sort();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Check every application spec before Playwright can launch a worker/browser. */
|
|
101
|
+
export async function assertManagedTestImports(root = specRoot): Promise<void> {
|
|
102
|
+
const specs = await applicationSpecs(root);
|
|
103
|
+
if (specs.length === 0)
|
|
104
|
+
throw new Error('Managed E2E application spec discovery returned no specs');
|
|
105
|
+
for (const spec of specs) {
|
|
106
|
+
const errors = managedTestImportViolations(await readFile(spec, 'utf8'), spec);
|
|
107
|
+
if (errors.length)
|
|
108
|
+
throw new Error(`Managed E2E fixture import violation in ${spec}: ${errors.join('; ')}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
E2E_ROOT_DIR,
|
|
10
10
|
E2E_SPECS_DIR,
|
|
11
11
|
} from './e2e.ts';
|
|
12
|
+
import { assertManagedTestImports } from './managedTestImports.ts';
|
|
12
13
|
import { combineGreps, parseE2eArgs } from './runE2eArgs.ts';
|
|
13
14
|
|
|
14
15
|
function run(command: string, args: string[]): void {
|
|
@@ -34,7 +35,17 @@ async function assertPortAvailable(): Promise<void> {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
const parsed = parseE2eArgs(process.argv.slice(2), E2E_SPECS_DIR);
|
|
37
|
-
|
|
38
|
+
if (
|
|
39
|
+
process.env.VONA_TEST_RUN_ID &&
|
|
40
|
+
process.env.APP_NAME !== `cabtest${process.env.VONA_TEST_RUN_ID}`
|
|
41
|
+
) {
|
|
42
|
+
throw new Error('Managed E2E test identity does not match its run ID');
|
|
43
|
+
}
|
|
44
|
+
if (process.env.VONA_TEST_RUN_ID) {
|
|
45
|
+
await assertManagedTestImports();
|
|
46
|
+
} else {
|
|
47
|
+
await assertPortAvailable();
|
|
48
|
+
}
|
|
38
49
|
|
|
39
50
|
const playwrightCommand = resolve(
|
|
40
51
|
E2E_ROOT_DIR,
|
|
@@ -5,6 +5,10 @@ import { resolve } from 'node:path';
|
|
|
5
5
|
|
|
6
6
|
import { E2E_LOCAL_BASE_URL, E2E_ROOT_DIR } from './e2e.ts';
|
|
7
7
|
|
|
8
|
+
// Root isolated E2E owns the server directly. This wrapper remains for direct, legacy E2E.
|
|
9
|
+
if (process.env.VONA_TEST_RUN_ID) {
|
|
10
|
+
throw new Error('Root isolated E2E starts its server through the root runner');
|
|
11
|
+
}
|
|
8
12
|
const child: ChildProcess = spawn(
|
|
9
13
|
process.execPath,
|
|
10
14
|
[
|
|
@@ -32,11 +36,39 @@ let stopping = false;
|
|
|
32
36
|
let forced = false;
|
|
33
37
|
let shutdownTimer: NodeJS.Timeout | undefined;
|
|
34
38
|
|
|
35
|
-
function
|
|
39
|
+
async function waitForGroupExit(timeoutMs: number): Promise<boolean> {
|
|
40
|
+
if (!child.pid || process.platform === 'win32') return true;
|
|
41
|
+
const deadline = Date.now() + timeoutMs;
|
|
42
|
+
while (true) {
|
|
43
|
+
try {
|
|
44
|
+
process.kill(-child.pid, 0);
|
|
45
|
+
} catch (error: any) {
|
|
46
|
+
if (error.code === 'ESRCH') return true;
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
if (Date.now() >= deadline) return false;
|
|
50
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let finishing = false;
|
|
55
|
+
async function finish(code: number) {
|
|
56
|
+
if (finishing) return;
|
|
57
|
+
finishing = true;
|
|
36
58
|
if (shutdownTimer) {
|
|
37
59
|
clearTimeout(shutdownTimer);
|
|
38
60
|
shutdownTimer = undefined;
|
|
39
61
|
}
|
|
62
|
+
if (child.pid && process.platform !== 'win32' && !(await waitForGroupExit(1000))) {
|
|
63
|
+
signalChild('SIGINT');
|
|
64
|
+
if (!(await waitForGroupExit(3500))) {
|
|
65
|
+
signalChild('SIGKILL');
|
|
66
|
+
if (!(await waitForGroupExit(1000))) {
|
|
67
|
+
console.error(`E2E Vona process group is still active: ${child.pid}`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
40
72
|
process.exit(code);
|
|
41
73
|
}
|
|
42
74
|
|
|
@@ -64,7 +96,7 @@ function forceStop() {
|
|
|
64
96
|
`E2E Vona process group did not stop within ${gracefulShutdownTimeout}ms; forcing termination: ${child.pid}`,
|
|
65
97
|
);
|
|
66
98
|
signalChild('SIGKILL');
|
|
67
|
-
finish(1);
|
|
99
|
+
void finish(1);
|
|
68
100
|
}
|
|
69
101
|
|
|
70
102
|
function stop() {
|
|
@@ -85,10 +117,10 @@ process.on('SIGTERM', stop);
|
|
|
85
117
|
child.on('error', error => {
|
|
86
118
|
// eslint-disable-next-line
|
|
87
119
|
console.error(error);
|
|
88
|
-
finish(1);
|
|
120
|
+
void finish(1);
|
|
89
121
|
});
|
|
90
122
|
|
|
91
|
-
child.on('
|
|
123
|
+
child.on('close', (code, signal) => {
|
|
92
124
|
// Playwright waits for this wrapper to exit, not only for Vona's workers to stop.
|
|
93
|
-
finish(code ?? (signal ? 1 : 0));
|
|
125
|
+
void finish(code ?? (signal ? 1 : 0));
|
|
94
126
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { APIRequestContext, Browser, Locator, Page, TestInfo } from '@playwright/test';
|
|
2
2
|
|
|
3
|
-
import { expect, test } from '
|
|
3
|
+
import { expect, test } from '../scripts/managedTest.ts';
|
|
4
4
|
|
|
5
5
|
const privateMarkers = ['cart', 'address', 'order', 'coupon', 'payment'];
|
|
6
6
|
const addressResourceUrl =
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Locator, Page, Request, Route } from '@playwright/test';
|
|
2
2
|
|
|
3
|
-
import { expect, test } from '
|
|
3
|
+
import { expect, test } from '../scripts/managedTest.ts';
|
|
4
4
|
|
|
5
5
|
const studentResourceUrl =
|
|
6
6
|
/\/admin\/rest\/resource\/training-student(?:%3A|:|%253A)student(?:[/?#]|$)/;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { APIRequestContext, Page, TestInfo } from '@playwright/test';
|
|
2
2
|
|
|
3
|
-
import { expect, test } from '@playwright/test';
|
|
4
3
|
import path from 'node:path';
|
|
5
4
|
|
|
5
|
+
import { expect, test } from '../scripts/managedTest.ts';
|
|
6
|
+
|
|
6
7
|
const accountPath = '/home/user/account';
|
|
7
8
|
const passwordSetPath = '/home/user/password-set';
|
|
8
9
|
const passwordResetPath = '/home/user/password-reset';
|