vona-cli-set-api 1.1.155 → 1.1.158

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/dist/index.js CHANGED
@@ -15,18 +15,19 @@ import { rollup } from 'rollup';
15
15
  import compileTemplate from 'lodash/template.js';
16
16
  import { createRequire } from 'node:module';
17
17
  import { pathToFileURL, fileURLToPath } from 'node:url';
18
+ import { resolveTestRunId, TEST_RUN_ID_ENV, getTestAppName, resolveVonaLocalPaths, assertTestRunOwner, beanFullNameFromOnionName } from 'vona-core';
18
19
  import * as dotenv from '@cabloy/dotenv';
19
20
  import { getEnvFiles } from '@cabloy/dotenv';
20
21
  import { glob } from '@cabloy/module-glob';
21
22
  import os from 'node:os';
22
23
  import chalk from 'chalk';
24
+ import { spawn } from 'node:child_process';
23
25
  import nodemon from 'nodemon';
24
26
  import { catchError, combineApiPathControllerAndActionRaw, replaceTemplate } from '@cabloy/utils';
25
27
  import { getOnionScenesMeta, getOnionMetasMeta, relativeNameToCapitalize } from '@cabloy/module-info';
26
28
  import { toUpperCaseFirstChar, stringToCapitalize, replaceTemplate as replaceTemplate$1 } from '@cabloy/word-utils';
27
29
  import fs from 'node:fs';
28
30
  import gogocode from 'gogocode';
29
- import { beanFullNameFromOnionName } from 'vona-core';
30
31
 
31
32
  function getEnvMeta(configMeta) {
32
33
  return {
@@ -97,9 +98,8 @@ function copyTemplateIfNeed(fileSrc, fileDest) {
97
98
  fse.copyFileSync(fileSrc, fileDest);
98
99
  }
99
100
  }
100
- function getImportEsm() {
101
- // return '--loader=ts-node/esm';
102
- return '--import=./.vona/register.js';
101
+ function getImportEsm(runtimeDir = '.vona') {
102
+ return `--import=./${runtimeDir}/register.js`;
103
103
  }
104
104
 
105
105
  function createConfigUtils(configMeta, configOptions) {
@@ -114,6 +114,10 @@ function createConfigUtils(configMeta, configOptions) {
114
114
  function __loadEnvs() {
115
115
  const meta = getEnvMeta(configMeta);
116
116
  const envDir = path.join(configOptions.appDir, 'env');
117
+ const runId = resolveTestRunId(meta.mode, process.env[TEST_RUN_ID_ENV]);
118
+ if (runId && process.env.VONA_DB_RESET !== undefined) {
119
+ throw new Error('Isolated test cannot run a development database reset');
120
+ }
117
121
  const envs = dotenv.loadEnvs(meta, envDir, '.env');
118
122
  const res = Object.assign({
119
123
  NODE_ENV: getNodeEnv(meta.mode)
@@ -121,6 +125,20 @@ function createConfigUtils(configMeta, configOptions) {
121
125
  META_FLAVOR: meta.flavor,
122
126
  META_MODE: meta.mode
123
127
  });
128
+ if (runId) {
129
+ res[TEST_RUN_ID_ENV] = runId;
130
+ res.APP_NAME = getTestAppName(runId);
131
+ const port = Number(process.env.SERVER_LISTEN_PORT);
132
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
133
+ throw new Error('Isolated test requires an assigned SERVER_LISTEN_PORT');
134
+ }
135
+ res.SERVER_LISTEN_PORT = String(port);
136
+ res.SERVER_LISTEN_HOSTNAME = '127.0.0.1';
137
+ if (process.env.VONA_TEST_SERVER_ORIGIN === 'true') {
138
+ res.SERVER_SERVE_PROTOCOL = 'http';
139
+ res.SERVER_SERVE_HOST = `127.0.0.1:${port}`;
140
+ }
141
+ }
124
142
  if (configOptions.workers !== undefined) {
125
143
  res.SERVER_WORKERS = configOptions.workers.toString();
126
144
  }
@@ -132,8 +150,8 @@ function createConfigUtils(configMeta, configOptions) {
132
150
  res.SERVER_WORKERS = '1';
133
151
  }
134
152
  }
135
- for (const key of ['NODE_ENV', 'SERVER_WORKERS', 'META_FLAVOR', 'META_MODE']) {
136
- if (res[key] !== false) {
153
+ for (const key of ['NODE_ENV', 'SERVER_WORKERS', 'META_FLAVOR', 'META_MODE', TEST_RUN_ID_ENV]) {
154
+ if (res[key] !== undefined && res[key] !== false) {
137
155
  process.env[key] = res[key];
138
156
  }
139
157
  }
@@ -256,6 +274,14 @@ async function generateEntryFiles(configMeta, configOptions, modulesMeta, env) {
256
274
  }
257
275
 
258
276
  async function generateVonaMeta(configMeta, configOptions) {
277
+ const paths = resolveVonaLocalPaths(configOptions.appDir, configMeta.mode);
278
+ if (paths.runId) {
279
+ if (configOptions.runtimeDir !== paths.generatedDir) {
280
+ throw new Error('Isolated test generated directory does not match its run ID');
281
+ }
282
+ assertTestRunOwner(paths.dataRoot, paths.runId);
283
+ assertTestRunOwner(paths.generatedRoot, paths.runId);
284
+ }
259
285
  // config utils
260
286
  const configUtils = createConfigUtils(configMeta, configOptions);
261
287
  // env
@@ -724,29 +750,33 @@ class CliBinDbReset extends BeanCliBase {
724
750
  // super
725
751
  await super.execute();
726
752
  const projectPath = argv.projectPath;
727
- // test
753
+ // recreate the development database
728
754
  await this._dbReset(projectPath);
729
755
  }
730
756
  async _dbReset(projectPath) {
731
757
  const {
732
758
  argv
733
759
  } = this.context;
734
- const mode = 'test';
760
+ const mode = 'dev';
735
761
  const flavor = argv.flavor || 'normal';
736
762
  const configMeta = {
737
763
  flavor,
738
764
  mode
739
765
  };
766
+ const paths = resolveVonaLocalPaths(projectPath, mode);
740
767
  const configOptions = {
741
768
  appDir: projectPath,
742
- runtimeDir: '.vona',
769
+ runtimeDir: paths.generatedDir,
743
770
  workers: 1
744
771
  };
745
- const {
746
- modulesMeta
747
- } = await generateVonaMeta(configMeta, configOptions);
748
- await this._run(projectPath, modulesMeta);
749
- await rimraf(path.join(projectPath, '.vona'));
772
+ try {
773
+ const {
774
+ modulesMeta
775
+ } = await generateVonaMeta(configMeta, configOptions);
776
+ await this._run(projectPath, modulesMeta);
777
+ } finally {
778
+ await rimraf(paths.generatedRoot);
779
+ }
750
780
  }
751
781
  async _run(projectPath, _modulesMeta) {
752
782
  // testFile
@@ -756,7 +786,7 @@ class CliBinDbReset extends BeanCliBase {
756
786
  }
757
787
  // run
758
788
  let args = [];
759
- args = args.concat([getImportEsm(), testFile, projectPath]);
789
+ args = args.concat([getImportEsm(resolveVonaLocalPaths(projectPath, 'dev').generatedDir), testFile, projectPath]);
760
790
  // args = args.concat(['--experimental-transform-types', getImportEsm(), testFile, projectPath]);
761
791
  await this.helper.spawnExe({
762
792
  cmd: 'node',
@@ -768,6 +798,16 @@ class CliBinDbReset extends BeanCliBase {
768
798
  }
769
799
  }
770
800
 
801
+ function resolveDevMode(mode) {
802
+ if (mode === undefined || mode === 'dev') return 'dev';
803
+ if (mode === 'test') return 'test';
804
+ throw new Error('Invalid --mode: expected dev or test');
805
+ }
806
+ function resolveTestWorkers(mode, workers) {
807
+ if (mode !== 'test') return workers;
808
+ if (workers === undefined || workers === 1) return 1;
809
+ throw new Error('--mode=test requires --workers=1');
810
+ }
771
811
  class CliBinDev extends BeanCliBase {
772
812
  async execute() {
773
813
  const {
@@ -783,22 +823,54 @@ class CliBinDev extends BeanCliBase {
783
823
  const {
784
824
  argv
785
825
  } = this.context;
786
- const mode = 'dev';
826
+ const mode = resolveDevMode(argv.mode);
827
+ const workers = resolveTestWorkers(mode, argv.workers);
787
828
  const flavor = argv.flavor || 'normal';
788
829
  const configMeta = {
789
830
  flavor,
790
831
  mode
791
832
  };
833
+ const paths = resolveVonaLocalPaths(projectPath, mode);
792
834
  const configOptions = {
793
835
  appDir: projectPath,
794
- runtimeDir: '.vona',
795
- workers: argv.workers
836
+ runtimeDir: paths.generatedDir,
837
+ workers
796
838
  };
797
- const {
798
- modulesMeta
799
- } = await generateVonaMeta(configMeta, configOptions);
800
- await this._run(projectPath, modulesMeta);
801
- await rimraf(path.join(projectPath, '.vona'));
839
+ try {
840
+ const {
841
+ modulesMeta
842
+ } = await generateVonaMeta(configMeta, configOptions);
843
+ if (mode === 'test') {
844
+ await this._runTest(projectPath);
845
+ } else {
846
+ await this._run(projectPath, modulesMeta);
847
+ }
848
+ } finally {
849
+ if (!paths.runId) await rimraf(paths.generatedRoot);
850
+ }
851
+ }
852
+ async _runTest(projectPath) {
853
+ const generatedDir = resolveVonaLocalPaths(projectPath, 'test').generatedDir;
854
+ const child = spawn(process.execPath, [getImportEsm(generatedDir), `${generatedDir}/bootstrap.ts`], {
855
+ cwd: projectPath,
856
+ stdio: 'inherit'
857
+ });
858
+ const forwardSignal = signal => child.kill(signal);
859
+ const onInterrupt = () => forwardSignal('SIGINT');
860
+ const onTerminate = () => forwardSignal('SIGTERM');
861
+ process.on('SIGINT', onInterrupt);
862
+ process.on('SIGTERM', onTerminate);
863
+ try {
864
+ await new Promise((resolve, reject) => {
865
+ child.once('error', reject);
866
+ child.once('exit', (code, signal) => {
867
+ if (code === 0) resolve();else reject(new Error(`Test-mode dev server exited: ${signal ?? code}`));
868
+ });
869
+ });
870
+ } finally {
871
+ process.off('SIGINT', onInterrupt);
872
+ process.off('SIGTERM', onTerminate);
873
+ }
802
874
  }
803
875
  async _run(projectPath, _modulesMeta) {
804
876
  let closed = false;
@@ -872,7 +944,7 @@ class CliBinPlay extends BeanCliBase {
872
944
  }
873
945
  }
874
946
  async _runAttach(projectPath) {
875
- const runtimeFile = path.join(projectPath, '.app/runtime/-.json');
947
+ const runtimeFile = path.join(resolveVonaLocalPaths(projectPath, process.env.META_MODE ?? 'dev').runtimeRoot, '-.json');
876
948
  if (!fse.existsSync(runtimeFile)) throw new Error('dev server not running');
877
949
  // args
878
950
  let args = [];
@@ -952,19 +1024,23 @@ class CliBinTest extends BeanCliBase {
952
1024
  flavor,
953
1025
  mode
954
1026
  };
1027
+ const paths = resolveVonaLocalPaths(projectPath, mode);
955
1028
  const configOptions = {
956
1029
  appDir: projectPath,
957
- runtimeDir: '.vona',
1030
+ runtimeDir: paths.generatedDir,
958
1031
  workers: 1
959
1032
  };
960
- const {
961
- modulesMeta
962
- } = await generateVonaMeta(configMeta, configOptions);
963
- await this._run(projectPath, modulesMeta);
964
- if (argv.coverage && !argv.ci) {
965
- await this._outputCoverageReportViewer(projectPath);
1033
+ try {
1034
+ const {
1035
+ modulesMeta
1036
+ } = await generateVonaMeta(configMeta, configOptions);
1037
+ await this._run(projectPath, modulesMeta);
1038
+ if (argv.coverage && !argv.ci) {
1039
+ await this._outputCoverageReportViewer(projectPath);
1040
+ }
1041
+ } finally {
1042
+ if (!paths.runId) await rimraf(paths.generatedRoot);
966
1043
  }
967
- await rimraf(path.join(projectPath, '.vona'));
968
1044
  }
969
1045
  async _run(projectPath, modulesMeta) {
970
1046
  const {
@@ -990,7 +1066,7 @@ class CliBinTest extends BeanCliBase {
990
1066
  if (process.env.TEST_WHYISNODERUNNING === 'true') {
991
1067
  args.push('--import=why-is-node-running/include');
992
1068
  }
993
- args = args.concat([getImportEsm(), testFile, projectPath, (!!argv.coverage).toString(), patterns.join(',')]);
1069
+ args = args.concat([getImportEsm(resolveVonaLocalPaths(projectPath, 'test').generatedDir), testFile, projectPath, (!!argv.coverage).toString(), patterns.join(',')]);
994
1070
  // args = args.concat(['--experimental-transform-types', getImportEsm(), testFile, projectPath, (!!argv.coverage).toString(), patterns.join(',')]);
995
1071
  await this.helper.spawnExe({
996
1072
  cmd: 'node',
@@ -3471,7 +3547,7 @@ var binDev = {
3471
3547
  info: {
3472
3548
  version: '5.0.0',
3473
3549
  title: 'Cli: Bin: Dev',
3474
- usage: 'npm run vona :bin:dev -- [--workers=] [--flavor=]'
3550
+ usage: 'npm run vona :bin:dev -- [--workers=] [--flavor=] [--mode=dev|test]'
3475
3551
  },
3476
3552
  options: {
3477
3553
  workers: {
@@ -3481,6 +3557,11 @@ var binDev = {
3481
3557
  flavor: {
3482
3558
  description: 'flavor',
3483
3559
  type: 'string'
3560
+ },
3561
+ mode: {
3562
+ description: 'dev or test',
3563
+ type: 'string',
3564
+ choices: ['dev', 'test']
3484
3565
  }
3485
3566
  }
3486
3567
  };
@@ -1,14 +1,18 @@
1
1
  import type { glob } from '@cabloy/module-glob';
2
- import type { VonaMetaFlavor } from '@cabloy/module-info';
2
+ import type { VonaMetaFlavor, VonaMetaMode } from '@cabloy/module-info';
3
3
  import { BeanCliBase } from '@cabloy/cli';
4
+ export declare function resolveDevMode(mode: unknown): 'dev' | 'test';
5
+ export declare function resolveTestWorkers(mode: 'dev' | 'test', workers: unknown): number | undefined;
4
6
  declare module '@cabloy/cli' {
5
7
  interface ICommandArgv {
6
8
  workers?: number;
7
9
  flavor?: VonaMetaFlavor;
10
+ mode?: VonaMetaMode;
8
11
  }
9
12
  }
10
13
  export declare class CliBinDev extends BeanCliBase {
11
14
  execute(): Promise<void>;
12
15
  _dev(projectPath: string): Promise<void>;
16
+ _runTest(projectPath: string): Promise<void>;
13
17
  _run(projectPath: string, _modulesMeta: Awaited<ReturnType<typeof glob>>): Promise<unknown>;
14
18
  }
@@ -0,0 +1,15 @@
1
+ import type { TestClaimManifest } from 'vona-core';
2
+ import { VonaApplication } from 'vona-core';
3
+ export interface TestCleanupResolvedConfig {
4
+ databaseOptions: Map<string, Record<string, any>>;
5
+ redisOptions: Map<string, Record<string, any>>;
6
+ }
7
+ export interface TestCleanupConfigDependencies {
8
+ loadEnvs(): NodeJS.ProcessEnv;
9
+ files: string[];
10
+ loadConfig(file: string): Promise<{
11
+ default: (app: VonaApplication) => unknown;
12
+ }>;
13
+ createApp(env: NodeJS.ProcessEnv): VonaApplication;
14
+ }
15
+ export declare function resolveTestCleanupConfig(claim: TestClaimManifest, injected?: TestCleanupConfigDependencies): Promise<TestCleanupResolvedConfig>;
@@ -0,0 +1,17 @@
1
+ export interface CleanupSqlConnection {
2
+ raw(sql: string): Promise<unknown>;
3
+ destroy(): Promise<unknown>;
4
+ }
5
+ export interface CleanupRedisConnection {
6
+ scan(cursor: string, match: 'MATCH', pattern: string, count: 'COUNT', size: number): Promise<[string, string[]]>;
7
+ unlink(...keys: string[]): Promise<unknown>;
8
+ del(...keys: string[]): Promise<unknown>;
9
+ disconnect(): unknown;
10
+ }
11
+ export declare function quoteCleanupDatabase(name: string, dialect: string): string;
12
+ export declare function dropExactCleanupDatabase(connection: CleanupSqlConnection, dialect: string, name: string, validate: (name: string) => void): Promise<void>;
13
+ export declare function escapeRedisCleanupPattern(prefix: string): string;
14
+ export declare function clearExactRedisPrefix(connection: CleanupRedisConnection, prefix: string, validate: (prefix: string) => void, options?: {
15
+ batchSize?: number;
16
+ maxScans?: number;
17
+ }): Promise<void>;
@@ -0,0 +1 @@
1
+ export {};
@@ -14,6 +14,11 @@ declare const _default: {
14
14
  description: string;
15
15
  type: string;
16
16
  };
17
+ mode: {
18
+ description: string;
19
+ type: string;
20
+ choices: string[];
21
+ };
17
22
  };
18
23
  };
19
24
  export default _default;
@@ -104,6 +104,11 @@ export declare const commands: {
104
104
  description: string;
105
105
  type: string;
106
106
  };
107
+ mode: {
108
+ description: string;
109
+ type: string;
110
+ choices: string[];
111
+ };
107
112
  };
108
113
  };
109
114
  play: {
@@ -15,4 +15,4 @@ export declare function pathToHref(fileName: string): string;
15
15
  export declare function getOutDir(): string;
16
16
  export declare function getOutReleasesDir(): string;
17
17
  export declare function copyTemplateIfNeed(fileSrc: string, fileDest: string): void;
18
- export declare function getImportEsm(): string;
18
+ export declare function getImportEsm(runtimeDir?: string): string;
@@ -3,6 +3,23 @@ const argv = process.argv.slice(2);
3
3
  const projectPath = argv[0];
4
4
  await dbResetRun(projectPath);
5
5
  async function dbResetRun(projectPath) {
6
- await createGeneralApp(projectPath);
7
- await closeApp();
6
+ if (process.env.META_MODE !== 'dev') {
7
+ throw new Error('db:reset requires dev mode');
8
+ }
9
+ const previousReset = process.env.VONA_DB_RESET;
10
+ process.env.VONA_DB_RESET = 'true';
11
+ try {
12
+ await createGeneralApp(projectPath);
13
+ }
14
+ finally {
15
+ try {
16
+ await closeApp();
17
+ }
18
+ finally {
19
+ if (previousReset === undefined)
20
+ delete process.env.VONA_DB_RESET;
21
+ else
22
+ process.env.VONA_DB_RESET = previousReset;
23
+ }
24
+ }
8
25
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vona-cli-set-api",
3
- "version": "1.1.155",
4
- "gitHead": "8d575dd958be6aa16daa249bdbdb024d20d0789d",
3
+ "version": "1.1.158",
4
+ "gitHead": "bc3034e45e4f728199560c682b3432d47381ceb4",
5
5
  "description": "vona cli-set-api",
6
6
  "keywords": [
7
7
  "framework",
@@ -62,7 +62,7 @@
62
62
  "@rollup/plugin-terser": "^1.0.0",
63
63
  "@rollup/plugin-typescript": "^12.3.0",
64
64
  "babel-plugin-transform-typescript-metadata": "^0.3.2",
65
- "babel-plugin-vona-bean-module": "^1.1.7",
65
+ "babel-plugin-vona-bean-module": "^1.1.9",
66
66
  "chalk": "^3.0.0",
67
67
  "cli-table3": "^0.6.5",
68
68
  "compressing": "^1.10.4",
@@ -77,7 +77,7 @@
77
77
  "ts-node-maintained": "^10.9.6",
78
78
  "urllib": "^4.9.0",
79
79
  "uuid": "^11.1.0",
80
- "vona-core": "^5.1.42",
80
+ "vona-core": "^5.1.44",
81
81
  "why-is-node-running": "^3.2.2",
82
82
  "yargs-parser": "^22.0.0"
83
83
  }
@@ -5,6 +5,7 @@
5
5
  },
6
6
  "include": [
7
7
  ".vona/**/*.ts",
8
+ ".vona-test-*/**/*.ts",
8
9
  "src/backend/package.json",
9
10
  "src/backend/config",
10
11
  "src/backend/play",
@@ -5,6 +5,7 @@
5
5
  },
6
6
  "include": [
7
7
  ".vona/**/*.ts",
8
+ ".vona-test-*/**/*.ts",
8
9
  "src/backend/package.json",
9
10
  "src/backend/config",
10
11
  "src/backend/play",