cabloy 5.1.221 → 5.1.223
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/vona-test-sqlite3.yml +1 -1
- package/.github/workflows/vona-tsc.yml +4 -3
- package/CHANGELOG.md +24 -0
- package/package.json +11 -2
- package/repo-agent-governance/managed-assets.json +15 -15
- package/repo-agent-governance/policies/repository.md +1 -0
- package/repo-agent-governance/skills/cabloy-backend-scaffold/SKILL.md +5 -1
- package/repo-agent-governance/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +7 -5
- package/repo-agent-governance/skills/cabloy-frontend-scaffold/SKILL.md +5 -1
- package/repo-agent-governance/skills/cabloy-frontend-scaffold/references/follow-up-checklist.md +6 -4
- package/repo-docs/backend/unit-testing.md +5 -1
- package/repo-docs/frontend/scripts.md +8 -0
- package/repo-docs/reference/repo-scripts.md +31 -0
- package/scripts/release.ts +4 -1
- package/scripts/runIsolatedTest.test.ts +1 -1
- package/scripts/typecheck.test.ts +199 -0
- package/scripts/typecheck.ts +20 -0
- package/vona/package.original.json +3 -0
- package/vona/packages-cli/cabloy-cli/package.json +1 -1
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +2 -2
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +0 -7
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.test.ts +22 -7
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.tsc.ts +34 -5
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/testCleanupProcess.test.ts +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/testCleanupProcess.ts +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/command/bin.tsc.ts +11 -1
- package/vona/packages-cli/cli-set-api/src/lib/typescriptRunner.ts +61 -0
- package/vona/packages-cli/cli-set-api/templates/app/register.js +1 -1
- package/vona/packages-cli/cli-set-api/templates/config/_tsconfig_project.json +0 -3
- package/vona/packages-cli/cli-set-api/templates/config/_tsconfig_source.json +0 -3
- package/vona/packages-cli/cli-set-api/test/binTestPatterns.test.ts +57 -0
- package/vona/packages-utils/process-helper/package.json +1 -1
- package/vona/packages-utils/process-helper/src/index.test.ts +62 -0
- package/vona/packages-utils/process-helper/src/index.ts +9 -4
- 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/core/completedReadiness.test.ts +558 -0
- package/vona/packages-vona/vona-core/src/lib/core/completedReadiness.ts +389 -0
- package/vona/packages-vona/vona-core/src/lib/core/context.ts +3 -0
- package/vona/packages-vona/vona-core/src/lib/core/index.ts +11 -0
- package/vona/packages-vona/vona-core/src/lib/core/meta.ts +5 -0
- package/vona/packages-vona/vona-core/src/lib/framework/start.test.ts +10 -0
- package/vona/packages-vona/vona-core/src/lib/framework/start.ts +8 -0
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +455 -117
- 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 +3 -3
- 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 +124 -4
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/types/executor.ts +7 -0
- package/vona/src/suite-vendor/a-vona/modules/a-executor/test/readyExisting.test.ts +423 -0
- package/vona/src/suite-vendor/a-vona/modules/a-executor/test/readyExistingCommit.test.ts +174 -0
- package/vona/src/suite-vendor/a-vona/modules/a-instance/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-instance/src/bean/bean.instance.ts +3 -1
- package/vona/src/suite-vendor/a-vona/modules/a-instance/src/lib/existingReadyAdmission.ts +125 -0
- package/vona/src/suite-vendor/a-vona/modules/a-instance/src/service/instance.ts +72 -25
- package/vona/src/suite-vendor/a-vona/modules/a-instance/test/existingReadyAdmission.test.ts +949 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +61 -59
- package/vona/src/suite-vendor/a-vona/modules/a-mail/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-mail/src/service/mail.ts +5 -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/common/utils.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/test/redisCleanup.test.ts +1 -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/test/devSeed.test.ts +61 -2
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/vona/tsconfig.json +1 -4
- package/zova/package.original.json +2 -1
- package/zova/packages-cli/cli/package.json +4 -4
- package/zova/packages-cli/cli-set-front/package.json +2 -2
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +53 -88
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
"test": "node ../scripts/runIsolatedTest.ts unit",
|
|
28
28
|
"cov": "node ../scripts/runIsolatedTest.ts unit --coverage",
|
|
29
29
|
"tsc": "npm run prerun && npm run vona :bin:tsc",
|
|
30
|
+
"tsc:native": "npm run prerun && npm run vona :bin:tsc -- --compiler=native",
|
|
31
|
+
"tsc:native:force": "npm run prerun && npm run vona :bin:tsc -- --compiler=native --force",
|
|
32
|
+
"tsc:legacy": "npm run prerun && npm run vona :bin:tsc -- --force",
|
|
30
33
|
"build": "npm run prerun && npm run vona :bin:build -- --flavor=normal",
|
|
31
34
|
"build:docker": "npm run prerun && npm run vona :bin:build -- --flavor=docker",
|
|
32
35
|
"start": "pnpm -C ./dist/normal install && node ./dist/normal/bootstrap.js",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona-cli-set-api",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.167",
|
|
4
4
|
"gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
|
|
5
5
|
"description": "vona cli-set-api",
|
|
6
6
|
"keywords": [
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"@rollup/plugin-replace": "^6.0.3",
|
|
64
64
|
"@rollup/plugin-terser": "^1.0.0",
|
|
65
65
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
66
|
+
"@swc-node/register": "^1.12.1",
|
|
66
67
|
"babel-plugin-transform-typescript-metadata": "^0.3.2",
|
|
67
68
|
"babel-plugin-vona-bean-module": "workspace:^",
|
|
68
69
|
"chalk": "^3.0.0",
|
|
@@ -76,7 +77,6 @@
|
|
|
76
77
|
"randomatic": "^3.1.1",
|
|
77
78
|
"rimraf": "^6.1.3",
|
|
78
79
|
"rollup": "^4.60.1",
|
|
79
|
-
"ts-node-maintained": "^10.9.6",
|
|
80
80
|
"urllib": "^4.9.0",
|
|
81
81
|
"uuid": "^11.1.0",
|
|
82
82
|
"vona-core": "workspace:^",
|
|
@@ -165,12 +165,5 @@ export class CliBinDev extends BeanCliBase {
|
|
|
165
165
|
console.log('App restarted due to: ', files);
|
|
166
166
|
});
|
|
167
167
|
});
|
|
168
|
-
// await this.helper.spawnExe({
|
|
169
|
-
// cmd: 'node',
|
|
170
|
-
// args: ['--experimental-transform-types', '--loader=ts-node/esm', '.vona/bootstrap.ts'],
|
|
171
|
-
// options: {
|
|
172
|
-
// cwd: projectPath,
|
|
173
|
-
// },
|
|
174
|
-
// });
|
|
175
168
|
}
|
|
176
169
|
}
|
|
@@ -4,6 +4,7 @@ import type { VonaConfigMeta, VonaMetaFlavor, VonaMetaMode } from '@cabloy/modul
|
|
|
4
4
|
import { BeanCliBase } from '@cabloy/cli';
|
|
5
5
|
import { catchError } from '@cabloy/utils';
|
|
6
6
|
import fse from 'fs-extra';
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
7
8
|
import path from 'node:path';
|
|
8
9
|
import { rimraf } from 'rimraf';
|
|
9
10
|
import { resolveVonaLocalPaths } from 'vona-core';
|
|
@@ -20,6 +21,26 @@ declare module '@cabloy/cli' {
|
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
export async function runTestChild(args: string[], projectPath: string): Promise<void> {
|
|
25
|
+
const child = spawn(process.execPath, args, { cwd: projectPath, stdio: 'inherit' });
|
|
26
|
+
const onInterrupt = () => child.kill('SIGINT');
|
|
27
|
+
const onTerminate = () => child.kill('SIGTERM');
|
|
28
|
+
process.on('SIGINT', onInterrupt);
|
|
29
|
+
process.on('SIGTERM', onTerminate);
|
|
30
|
+
try {
|
|
31
|
+
await new Promise<void>((resolve, reject) => {
|
|
32
|
+
child.once('error', reject);
|
|
33
|
+
child.once('close', (code, signal) => {
|
|
34
|
+
if (code === 0) resolve();
|
|
35
|
+
else reject(new Error(`Test child exited: ${signal ?? code}`));
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
} finally {
|
|
39
|
+
process.off('SIGINT', onInterrupt);
|
|
40
|
+
process.off('SIGTERM', onTerminate);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
23
44
|
export class CliBinTest extends BeanCliBase {
|
|
24
45
|
async execute() {
|
|
25
46
|
const { argv } = this.context;
|
|
@@ -82,13 +103,7 @@ export class CliBinTest extends BeanCliBase {
|
|
|
82
103
|
patterns.join(','),
|
|
83
104
|
]);
|
|
84
105
|
// args = args.concat(['--experimental-transform-types', getImportEsm(), testFile, projectPath, (!!argv.coverage).toString(), patterns.join(',')]);
|
|
85
|
-
await
|
|
86
|
-
cmd: 'node',
|
|
87
|
-
args,
|
|
88
|
-
options: {
|
|
89
|
-
cwd: projectPath,
|
|
90
|
-
},
|
|
91
|
-
});
|
|
106
|
+
await runTestChild(args, projectPath);
|
|
92
107
|
}
|
|
93
108
|
|
|
94
109
|
async _outputCoverageReportViewer(projectPath: string) {
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { BeanCliBase } from '@cabloy/cli';
|
|
2
2
|
|
|
3
|
+
import type { TypeScriptCompiler } from '../typescriptRunner.ts';
|
|
4
|
+
|
|
5
|
+
import { runTypeScript } from '../typescriptRunner.ts';
|
|
6
|
+
|
|
3
7
|
declare module '@cabloy/cli' {
|
|
4
8
|
interface ICommandArgv {
|
|
5
9
|
force: boolean;
|
|
10
|
+
compiler: TypeScriptCompiler;
|
|
11
|
+
builders: number;
|
|
6
12
|
}
|
|
7
13
|
}
|
|
8
14
|
|
|
@@ -13,10 +19,23 @@ export class CliBinTsc extends BeanCliBase {
|
|
|
13
19
|
await super.execute();
|
|
14
20
|
const projectPath = argv.projectPath;
|
|
15
21
|
const force = argv.force;
|
|
16
|
-
await this._tsc(projectPath, force);
|
|
22
|
+
await this._tsc(projectPath, force, argv.compiler ?? 'typescript', argv.builders);
|
|
17
23
|
}
|
|
18
24
|
|
|
19
|
-
async _tsc(
|
|
25
|
+
async _tsc(
|
|
26
|
+
projectPath: string,
|
|
27
|
+
force: boolean,
|
|
28
|
+
compiler: TypeScriptCompiler = 'typescript',
|
|
29
|
+
builders?: number,
|
|
30
|
+
) {
|
|
31
|
+
if (builders !== undefined) {
|
|
32
|
+
if (!Number.isSafeInteger(builders) || builders < 1) {
|
|
33
|
+
throw new Error('--builders must be a positive safe integer');
|
|
34
|
+
}
|
|
35
|
+
if (compiler !== 'native') {
|
|
36
|
+
throw new Error('--builders requires --compiler=native');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
20
39
|
const suiteNames = Object.keys(this.modulesMeta.suites).filter(
|
|
21
40
|
suiteName => !this.modulesMeta.suites[suiteName].info.node_modules,
|
|
22
41
|
);
|
|
@@ -35,22 +54,32 @@ export class CliBinTsc extends BeanCliBase {
|
|
|
35
54
|
if (force) {
|
|
36
55
|
tscArgs.push('--force');
|
|
37
56
|
}
|
|
57
|
+
if (builders !== undefined) {
|
|
58
|
+
tscArgs.push('--builders', String(builders));
|
|
59
|
+
}
|
|
60
|
+
const check = async (cwd: string) => {
|
|
61
|
+
if (compiler === 'native') {
|
|
62
|
+
await runTypeScript(compiler, tscArgs, { projectPath, cwd });
|
|
63
|
+
} else {
|
|
64
|
+
await this.helper.processHelper.tsc(tscArgs, { cwd });
|
|
65
|
+
}
|
|
66
|
+
};
|
|
38
67
|
// tsc: project
|
|
39
68
|
// eslint-disable-next-line
|
|
40
69
|
console.log(`===> ${++counter}/${count} project`);
|
|
41
|
-
await
|
|
70
|
+
await check(projectPath);
|
|
42
71
|
// suites
|
|
43
72
|
for (const key of suiteNames) {
|
|
44
73
|
const suite = this.modulesMeta.suites[key];
|
|
45
74
|
// eslint-disable-next-line
|
|
46
75
|
console.log(`===> ${++counter}/${count} suite: ${suite.info.originalName}`);
|
|
47
|
-
await
|
|
76
|
+
await check(suite.root);
|
|
48
77
|
}
|
|
49
78
|
// modules
|
|
50
79
|
for (const module of modulesArray) {
|
|
51
80
|
// eslint-disable-next-line
|
|
52
81
|
console.log(`===> ${++counter}/${count} module: ${module.info.originalName}`);
|
|
53
|
-
await
|
|
82
|
+
await check(module.root);
|
|
54
83
|
}
|
|
55
84
|
// end
|
|
56
85
|
const timeEnd = Date.now();
|
|
@@ -17,7 +17,7 @@ test('cleanup helper launches via process.execPath and resolves only fake source
|
|
|
17
17
|
await writeFile(path.join(project, 'package.json'), '{"type":"module"}');
|
|
18
18
|
await writeFile(
|
|
19
19
|
path.join(project, 'tsconfig.json'),
|
|
20
|
-
'{"compilerOptions":{"module":"NodeNext","moduleResolution":"NodeNext","target":"ESNext","allowImportingTsExtensions":true}
|
|
20
|
+
'{"compilerOptions":{"module":"NodeNext","moduleResolution":"NodeNext","target":"ESNext","allowImportingTsExtensions":true}}',
|
|
21
21
|
);
|
|
22
22
|
await writeFile(
|
|
23
23
|
path.join(configDir, 'config.ts'),
|
|
@@ -4,7 +4,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
4
4
|
|
|
5
5
|
// Register before dynamically importing source config/framework (decorators and
|
|
6
6
|
// workspace TS exports). Dependencies resolve from this checkout's Vona install.
|
|
7
|
-
register(import.meta.resolve('
|
|
7
|
+
register(import.meta.resolve('@swc-node/register/esm'), pathToFileURL(`${process.cwd()}/`));
|
|
8
8
|
const require = createRequire(pathToFileURL(path.join(process.cwd(), 'package.json')));
|
|
9
9
|
let queue = Promise.resolve();
|
|
10
10
|
let claim: import('vona-core').TestClaimManifest | undefined;
|
|
@@ -3,9 +3,19 @@ export default {
|
|
|
3
3
|
info: {
|
|
4
4
|
version: '5.0.0',
|
|
5
5
|
title: 'Cli: Bin: Tsc',
|
|
6
|
-
usage: 'npm run vona :bin:tsc -- [--force]',
|
|
6
|
+
usage: 'npm run vona :bin:tsc -- [--force] [--compiler=typescript|native] [--builders=]',
|
|
7
7
|
},
|
|
8
8
|
options: {
|
|
9
|
+
compiler: {
|
|
10
|
+
description:
|
|
11
|
+
'TypeScript compiler (native requires @typescript/native in the project or repository root)',
|
|
12
|
+
type: 'string',
|
|
13
|
+
choices: ['typescript', 'native'],
|
|
14
|
+
},
|
|
15
|
+
builders: {
|
|
16
|
+
description: 'Maximum number of projects to build concurrently (native only)',
|
|
17
|
+
type: 'number',
|
|
18
|
+
},
|
|
9
19
|
force: {
|
|
10
20
|
description: 'force',
|
|
11
21
|
type: 'boolean',
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
export type TypeScriptCompiler = 'native' | 'typescript';
|
|
7
|
+
|
|
8
|
+
export function resolveTypeScriptCompiler(
|
|
9
|
+
compiler: TypeScriptCompiler,
|
|
10
|
+
projectPath: string,
|
|
11
|
+
): string {
|
|
12
|
+
const packageName = compiler === 'native' ? '@typescript/native' : 'typescript';
|
|
13
|
+
const require = createRequire(path.resolve(projectPath, 'package.json'));
|
|
14
|
+
let manifestPath: string;
|
|
15
|
+
try {
|
|
16
|
+
manifestPath = require.resolve(`${packageName}/package.json`);
|
|
17
|
+
} catch (cause) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`Cannot resolve ${packageName}. Install the ${compiler === 'native' ? 'native TypeScript alias (npm:typescript@7.0.2)' : 'TypeScript package'} in the project or its repository root.`,
|
|
20
|
+
{ cause },
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
24
|
+
if (typeof manifest.bin?.tsc !== 'string') {
|
|
25
|
+
throw new TypeError(`${packageName} does not declare a tsc executable.`);
|
|
26
|
+
}
|
|
27
|
+
return path.resolve(path.dirname(manifestPath), manifest.bin.tsc);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function runTypeScript(
|
|
31
|
+
compiler: TypeScriptCompiler,
|
|
32
|
+
args: string[],
|
|
33
|
+
{ projectPath, cwd = projectPath }: { projectPath: string; cwd?: string },
|
|
34
|
+
): Promise<void> {
|
|
35
|
+
const executable = resolveTypeScriptCompiler(compiler, projectPath);
|
|
36
|
+
await new Promise<void>((resolve, reject) => {
|
|
37
|
+
const child = spawn(process.execPath, [executable, ...args], {
|
|
38
|
+
cwd,
|
|
39
|
+
stdio: 'inherit',
|
|
40
|
+
shell: false,
|
|
41
|
+
});
|
|
42
|
+
child.once('error', reject);
|
|
43
|
+
child.once('close', (code, signal) => {
|
|
44
|
+
if (code === 0) {
|
|
45
|
+
resolve();
|
|
46
|
+
} else {
|
|
47
|
+
reject(
|
|
48
|
+
Object.assign(
|
|
49
|
+
new Error(
|
|
50
|
+
`TypeScript (${compiler}) failed: ${signal ? `signal ${signal}` : `exit code ${code}`}`,
|
|
51
|
+
),
|
|
52
|
+
{
|
|
53
|
+
exitCode: code ?? 1,
|
|
54
|
+
signal,
|
|
55
|
+
},
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdtempSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { it } from 'node:test';
|
|
6
|
+
|
|
7
|
+
import { runTestChild } from '../src/lib/bean/cli.bin.test.ts';
|
|
8
|
+
|
|
9
|
+
it('preserves literal test patterns and arguments through a real Node child', async () => {
|
|
10
|
+
const projectPath = mkdtempSync(join(tmpdir(), 'vona test argv '));
|
|
11
|
+
try {
|
|
12
|
+
const probe = join(projectPath, 'argv probe.mjs');
|
|
13
|
+
const output = join(projectPath, 'argv.json');
|
|
14
|
+
const fixtures = join(projectPath, 'test', 'support');
|
|
15
|
+
mkdirSync(fixtures, { recursive: true });
|
|
16
|
+
for (const name of ['first.ts', 'second.ts']) writeFileSync(join(fixtures, name), '');
|
|
17
|
+
writeFileSync(
|
|
18
|
+
probe,
|
|
19
|
+
`import { writeFileSync } from 'node:fs';
|
|
20
|
+
writeFileSync(process.argv[2], JSON.stringify({ argv: process.argv.slice(3), cwd: process.cwd() }));
|
|
21
|
+
`,
|
|
22
|
+
);
|
|
23
|
+
const args = [
|
|
24
|
+
'test/**/*.ts',
|
|
25
|
+
'test/*.ts,test/support/*.ts',
|
|
26
|
+
'',
|
|
27
|
+
'a value with spaces',
|
|
28
|
+
'literal & ; $HOME $(echo unexpected) %PATH% [brackets]',
|
|
29
|
+
];
|
|
30
|
+
await runTestChild([probe, output, ...args], projectPath);
|
|
31
|
+
const observed = JSON.parse(readFileSync(output, 'utf8'));
|
|
32
|
+
assert.deepEqual(observed.argv, args);
|
|
33
|
+
assert.equal(realpathSync(observed.cwd), realpathSync(projectPath));
|
|
34
|
+
} finally {
|
|
35
|
+
rmSync(projectPath, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('rejects child failure and spawn failure without leaking signal listeners', async () => {
|
|
40
|
+
const projectPath = mkdtempSync(join(tmpdir(), 'vona test exit '));
|
|
41
|
+
try {
|
|
42
|
+
const listeners = [process.listenerCount('SIGINT'), process.listenerCount('SIGTERM')];
|
|
43
|
+
await assert.rejects(
|
|
44
|
+
runTestChild(['--eval', 'process.exit(7)'], projectPath),
|
|
45
|
+
/Test child exited: 7/,
|
|
46
|
+
);
|
|
47
|
+
await assert.rejects(runTestChild(['--eval', ''], join(projectPath, 'missing')), {
|
|
48
|
+
code: 'ENOENT',
|
|
49
|
+
});
|
|
50
|
+
assert.deepEqual(
|
|
51
|
+
[process.listenerCount('SIGINT'), process.listenerCount('SIGTERM')],
|
|
52
|
+
listeners,
|
|
53
|
+
);
|
|
54
|
+
} finally {
|
|
55
|
+
rmSync(projectPath, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import ChildProcess from 'node:child_process';
|
|
3
|
+
import { PassThrough } from 'node:stream';
|
|
4
|
+
import { describe, it } from 'node:test';
|
|
5
|
+
|
|
6
|
+
import { ProcessHelper } from './index.ts';
|
|
7
|
+
|
|
8
|
+
describe('ProcessHelper child termination', { concurrency: false }, () => {
|
|
9
|
+
for (const { code, signal } of [
|
|
10
|
+
{ code: 0, signal: null },
|
|
11
|
+
{ code: 7, signal: null },
|
|
12
|
+
{ code: null, signal: 'SIGKILL' },
|
|
13
|
+
{ code: null, signal: null },
|
|
14
|
+
] as const) {
|
|
15
|
+
it(`handles exit code ${code} and signal ${signal}`, async t => {
|
|
16
|
+
const child = new ChildProcess.ChildProcess();
|
|
17
|
+
t.mock.method(ChildProcess, 'spawn', () => child);
|
|
18
|
+
const result = new ProcessHelper().spawn({ cmd: 'test-owned-command' });
|
|
19
|
+
child.emit('exit', code, signal);
|
|
20
|
+
if (code === 0) {
|
|
21
|
+
assert.equal(await result, '');
|
|
22
|
+
} else {
|
|
23
|
+
await assert.rejects(result, (error: Error & { code?: number; signal?: string }) => {
|
|
24
|
+
if (code !== null) {
|
|
25
|
+
assert.equal(error.code, 10000 + code);
|
|
26
|
+
assert.match(error.message, /exit code: 7/);
|
|
27
|
+
} else {
|
|
28
|
+
assert.equal(error.signal, signal);
|
|
29
|
+
assert.match(error.message, signal ? /SIGKILL/ : /exit code: null/);
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
it('retains the termination signal alongside stderr', async t => {
|
|
38
|
+
const child = new ChildProcess.ChildProcess();
|
|
39
|
+
child.stderr = new PassThrough();
|
|
40
|
+
t.mock.method(ChildProcess, 'spawn', () => child);
|
|
41
|
+
const helper = new ProcessHelper();
|
|
42
|
+
t.mock.method(helper.console, 'log', async () => {});
|
|
43
|
+
const result = helper.spawn({ cmd: 'test-owned-command' });
|
|
44
|
+
child.stderr.emit('data', Buffer.from('controlled_stderr'));
|
|
45
|
+
child.emit('exit', null, 'SIGTERM');
|
|
46
|
+
await assert.rejects(result, (error: Error & { signal?: string }) => {
|
|
47
|
+
assert.equal(error.signal, 'SIGTERM');
|
|
48
|
+
assert.match(error.message, /controlled_stderr/);
|
|
49
|
+
assert.match(error.message, /SIGTERM/);
|
|
50
|
+
return true;
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('preserves spawn failures', async t => {
|
|
55
|
+
const child = new ChildProcess.ChildProcess();
|
|
56
|
+
t.mock.method(ChildProcess, 'spawn', () => child);
|
|
57
|
+
const failure = new Error('controlled_spawn_failure');
|
|
58
|
+
const result = new ProcessHelper().spawn({ cmd: 'test-owned-command' });
|
|
59
|
+
child.emit('error', failure);
|
|
60
|
+
await assert.rejects(result, error => error === failure);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -172,12 +172,17 @@ export class ProcessHelper {
|
|
|
172
172
|
proc.once('error', err => {
|
|
173
173
|
reject(err);
|
|
174
174
|
});
|
|
175
|
-
proc.once('exit', code => {
|
|
175
|
+
proc.once('exit', (code, signal) => {
|
|
176
176
|
// if (options.gracefull) childs.delete(proc);
|
|
177
|
-
if (code) {
|
|
178
|
-
const
|
|
177
|
+
if (code !== 0) {
|
|
178
|
+
const status = signal ? `signal: ${signal}` : `exit code: ${code}`;
|
|
179
|
+
const message =
|
|
180
|
+
signal || code === null
|
|
181
|
+
? `${stderr ? `${stderr}\n` : ''}spawn ${cmd} ${args.join(' ')} fail, ${status}`
|
|
182
|
+
: stderr || `spawn ${cmd} ${args.join(' ')} fail, ${status}`;
|
|
179
183
|
const err = new Error(message);
|
|
180
|
-
(err as any).code = 10000 +
|
|
184
|
+
if (code !== null) (err as any).code = 10000 + code;
|
|
185
|
+
(err as any).signal = signal;
|
|
181
186
|
return reject(err);
|
|
182
187
|
}
|
|
183
188
|
resolve(stdout);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.114",
|
|
4
4
|
"gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
|
|
5
5
|
"description": "Vona is an intuitive, elegant and powerful Node.js framework for rapidly developing enterprise applications of any size",
|
|
6
6
|
"keywords": [
|