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.
Files changed (80) hide show
  1. package/.cabloy-version +1 -1
  2. package/.github/workflows/vona-test-sqlite3.yml +1 -1
  3. package/.github/workflows/vona-tsc.yml +4 -3
  4. package/CHANGELOG.md +24 -0
  5. package/package.json +11 -2
  6. package/repo-agent-governance/managed-assets.json +15 -15
  7. package/repo-agent-governance/policies/repository.md +1 -0
  8. package/repo-agent-governance/skills/cabloy-backend-scaffold/SKILL.md +5 -1
  9. package/repo-agent-governance/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +7 -5
  10. package/repo-agent-governance/skills/cabloy-frontend-scaffold/SKILL.md +5 -1
  11. package/repo-agent-governance/skills/cabloy-frontend-scaffold/references/follow-up-checklist.md +6 -4
  12. package/repo-docs/backend/unit-testing.md +5 -1
  13. package/repo-docs/frontend/scripts.md +8 -0
  14. package/repo-docs/reference/repo-scripts.md +31 -0
  15. package/scripts/release.ts +4 -1
  16. package/scripts/runIsolatedTest.test.ts +1 -1
  17. package/scripts/typecheck.test.ts +199 -0
  18. package/scripts/typecheck.ts +20 -0
  19. package/vona/package.original.json +3 -0
  20. package/vona/packages-cli/cabloy-cli/package.json +1 -1
  21. package/vona/packages-cli/cli/package.json +1 -1
  22. package/vona/packages-cli/cli-set-api/package.json +2 -2
  23. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +0 -7
  24. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.test.ts +22 -7
  25. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.tsc.ts +34 -5
  26. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/testCleanupProcess.test.ts +1 -1
  27. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/testCleanupProcess.ts +1 -1
  28. package/vona/packages-cli/cli-set-api/src/lib/command/bin.tsc.ts +11 -1
  29. package/vona/packages-cli/cli-set-api/src/lib/typescriptRunner.ts +61 -0
  30. package/vona/packages-cli/cli-set-api/templates/app/register.js +1 -1
  31. package/vona/packages-cli/cli-set-api/templates/config/_tsconfig_project.json +0 -3
  32. package/vona/packages-cli/cli-set-api/templates/config/_tsconfig_source.json +0 -3
  33. package/vona/packages-cli/cli-set-api/test/binTestPatterns.test.ts +57 -0
  34. package/vona/packages-utils/process-helper/package.json +1 -1
  35. package/vona/packages-utils/process-helper/src/index.test.ts +62 -0
  36. package/vona/packages-utils/process-helper/src/index.ts +9 -4
  37. package/vona/packages-vona/vona/package.json +1 -1
  38. package/vona/packages-vona/vona-core/package.json +1 -1
  39. package/vona/packages-vona/vona-core/src/lib/core/completedReadiness.test.ts +558 -0
  40. package/vona/packages-vona/vona-core/src/lib/core/completedReadiness.ts +389 -0
  41. package/vona/packages-vona/vona-core/src/lib/core/context.ts +3 -0
  42. package/vona/packages-vona/vona-core/src/lib/core/index.ts +11 -0
  43. package/vona/packages-vona/vona-core/src/lib/core/meta.ts +5 -0
  44. package/vona/packages-vona/vona-core/src/lib/framework/start.test.ts +10 -0
  45. package/vona/packages-vona/vona-core/src/lib/framework/start.ts +8 -0
  46. package/vona/packages-vona/vona-mock/package.json +1 -1
  47. package/vona/pnpm-lock.yaml +455 -117
  48. package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
  49. package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
  50. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +3 -3
  51. package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
  52. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +124 -4
  53. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/types/executor.ts +7 -0
  54. package/vona/src/suite-vendor/a-vona/modules/a-executor/test/readyExisting.test.ts +423 -0
  55. package/vona/src/suite-vendor/a-vona/modules/a-executor/test/readyExistingCommit.test.ts +174 -0
  56. package/vona/src/suite-vendor/a-vona/modules/a-instance/package.json +1 -1
  57. package/vona/src/suite-vendor/a-vona/modules/a-instance/src/bean/bean.instance.ts +3 -1
  58. package/vona/src/suite-vendor/a-vona/modules/a-instance/src/lib/existingReadyAdmission.ts +125 -0
  59. package/vona/src/suite-vendor/a-vona/modules/a-instance/src/service/instance.ts +72 -25
  60. package/vona/src/suite-vendor/a-vona/modules/a-instance/test/existingReadyAdmission.test.ts +949 -0
  61. package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
  62. package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +61 -59
  63. package/vona/src/suite-vendor/a-vona/modules/a-mail/package.json +1 -1
  64. package/vona/src/suite-vendor/a-vona/modules/a-mail/src/service/mail.ts +5 -0
  65. package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
  66. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/common/utils.ts +2 -1
  67. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  68. package/vona/src/suite-vendor/a-vona/modules/a-redis/package.json +1 -1
  69. package/vona/src/suite-vendor/a-vona/modules/a-redis/test/redisCleanup.test.ts +1 -1
  70. package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
  71. package/vona/src/suite-vendor/a-vona/modules/a-startup/test/devSeed.test.ts +61 -2
  72. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  73. package/vona/tsconfig.json +1 -4
  74. package/zova/package.original.json +2 -1
  75. package/zova/packages-cli/cli/package.json +4 -4
  76. package/zova/packages-cli/cli-set-front/package.json +2 -2
  77. package/zova/packages-zova/zova/package.json +2 -2
  78. package/zova/pnpm-lock.yaml +53 -88
  79. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  80. 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": "@cabloy/cli",
3
- "version": "3.1.29",
3
+ "version": "3.1.30",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "@cabloy/cli",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-cli",
3
- "version": "1.1.167",
3
+ "version": "1.1.169",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona cli",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-cli-set-api",
3
- "version": "1.1.165",
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 this.helper.spawnExe({
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(projectPath: string, force: boolean) {
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 this.helper.processHelper.tsc(tscArgs, { cwd: projectPath });
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 this.helper.processHelper.tsc(tscArgs, { cwd: suite.root });
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 this.helper.processHelper.tsc(tscArgs, { cwd: module.root });
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},"ts-node":{"transpileOnly":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('ts-node-maintained/esm'), pathToFileURL(`${process.cwd()}/`));
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
+ }
@@ -1,4 +1,4 @@
1
1
  import { register } from 'node:module';
2
2
  import { pathToFileURL } from 'node:url';
3
3
 
4
- register('ts-node-maintained/esm', pathToFileURL('./'));
4
+ register('@swc-node/register/esm', pathToFileURL('./'));
@@ -12,8 +12,5 @@
12
12
  "src/backend/typing"
13
13
  ],
14
14
  "exclude": [],
15
- "ts-node": {
16
- "swc": true
17
- },
18
15
  "references": []
19
16
  }
@@ -12,9 +12,6 @@
12
12
  "src/backend/typing"
13
13
  ],
14
14
  "exclude": [],
15
- "ts-node": {
16
- "swc": true
17
- },
18
15
  "references": [
19
16
  {
20
17
  "path": "packages-utils/compose"
@@ -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
+ });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabloy/process-helper",
3
- "version": "3.1.8",
3
+ "version": "3.1.9",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "cabloy process-helper",
6
6
  "keywords": [
@@ -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 message = stderr || `spawn ${cmd} ${args.join(' ')} fail, exit code: ${code}`;
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 + Number(code);
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.112",
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": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-core",
3
- "version": "5.1.49",
3
+ "version": "5.1.50",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona",
6
6
  "keywords": [