vona-cli-set-api 1.1.156 → 1.1.159

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,6 +15,7 @@ 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';
@@ -27,7 +28,6 @@ import { getOnionScenesMeta, getOnionMetasMeta, relativeNameToCapitalize } from
27
28
  import { toUpperCaseFirstChar, stringToCapitalize, replaceTemplate as replaceTemplate$1 } from '@cabloy/word-utils';
28
29
  import fs from 'node:fs';
29
30
  import gogocode from 'gogocode';
30
- import { beanFullNameFromOnionName } from 'vona-core';
31
31
 
32
32
  function getEnvMeta(configMeta) {
33
33
  return {
@@ -98,9 +98,8 @@ function copyTemplateIfNeed(fileSrc, fileDest) {
98
98
  fse.copyFileSync(fileSrc, fileDest);
99
99
  }
100
100
  }
101
- function getImportEsm() {
102
- // return '--loader=ts-node/esm';
103
- return '--import=./.vona/register.js';
101
+ function getImportEsm(runtimeDir = '.vona') {
102
+ return `--import=./${runtimeDir}/register.js`;
104
103
  }
105
104
 
106
105
  function createConfigUtils(configMeta, configOptions) {
@@ -115,6 +114,10 @@ function createConfigUtils(configMeta, configOptions) {
115
114
  function __loadEnvs() {
116
115
  const meta = getEnvMeta(configMeta);
117
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
+ }
118
121
  const envs = dotenv.loadEnvs(meta, envDir, '.env');
119
122
  const res = Object.assign({
120
123
  NODE_ENV: getNodeEnv(meta.mode)
@@ -122,6 +125,20 @@ function createConfigUtils(configMeta, configOptions) {
122
125
  META_FLAVOR: meta.flavor,
123
126
  META_MODE: meta.mode
124
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
+ }
125
142
  if (configOptions.workers !== undefined) {
126
143
  res.SERVER_WORKERS = configOptions.workers.toString();
127
144
  }
@@ -133,8 +150,8 @@ function createConfigUtils(configMeta, configOptions) {
133
150
  res.SERVER_WORKERS = '1';
134
151
  }
135
152
  }
136
- for (const key of ['NODE_ENV', 'SERVER_WORKERS', 'META_FLAVOR', 'META_MODE']) {
137
- 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) {
138
155
  process.env[key] = res[key];
139
156
  }
140
157
  }
@@ -257,6 +274,14 @@ async function generateEntryFiles(configMeta, configOptions, modulesMeta, env) {
257
274
  }
258
275
 
259
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
+ }
260
285
  // config utils
261
286
  const configUtils = createConfigUtils(configMeta, configOptions);
262
287
  // env
@@ -725,29 +750,33 @@ class CliBinDbReset extends BeanCliBase {
725
750
  // super
726
751
  await super.execute();
727
752
  const projectPath = argv.projectPath;
728
- // test
753
+ // recreate the development database
729
754
  await this._dbReset(projectPath);
730
755
  }
731
756
  async _dbReset(projectPath) {
732
757
  const {
733
758
  argv
734
759
  } = this.context;
735
- const mode = 'test';
760
+ const mode = 'dev';
736
761
  const flavor = argv.flavor || 'normal';
737
762
  const configMeta = {
738
763
  flavor,
739
764
  mode
740
765
  };
766
+ const paths = resolveVonaLocalPaths(projectPath, mode);
741
767
  const configOptions = {
742
768
  appDir: projectPath,
743
- runtimeDir: '.vona',
769
+ runtimeDir: paths.generatedDir,
744
770
  workers: 1
745
771
  };
746
- const {
747
- modulesMeta
748
- } = await generateVonaMeta(configMeta, configOptions);
749
- await this._run(projectPath, modulesMeta);
750
- 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
+ }
751
780
  }
752
781
  async _run(projectPath, _modulesMeta) {
753
782
  // testFile
@@ -757,7 +786,7 @@ class CliBinDbReset extends BeanCliBase {
757
786
  }
758
787
  // run
759
788
  let args = [];
760
- args = args.concat([getImportEsm(), testFile, projectPath]);
789
+ args = args.concat([getImportEsm(resolveVonaLocalPaths(projectPath, 'dev').generatedDir), testFile, projectPath]);
761
790
  // args = args.concat(['--experimental-transform-types', getImportEsm(), testFile, projectPath]);
762
791
  await this.helper.spawnExe({
763
792
  cmd: 'node',
@@ -801,9 +830,10 @@ class CliBinDev extends BeanCliBase {
801
830
  flavor,
802
831
  mode
803
832
  };
833
+ const paths = resolveVonaLocalPaths(projectPath, mode);
804
834
  const configOptions = {
805
835
  appDir: projectPath,
806
- runtimeDir: '.vona',
836
+ runtimeDir: paths.generatedDir,
807
837
  workers
808
838
  };
809
839
  try {
@@ -816,11 +846,12 @@ class CliBinDev extends BeanCliBase {
816
846
  await this._run(projectPath, modulesMeta);
817
847
  }
818
848
  } finally {
819
- await rimraf(path.join(projectPath, '.vona'));
849
+ if (!paths.runId) await rimraf(paths.generatedRoot);
820
850
  }
821
851
  }
822
852
  async _runTest(projectPath) {
823
- const child = spawn(process.execPath, [getImportEsm(), '.vona/bootstrap.ts'], {
853
+ const generatedDir = resolveVonaLocalPaths(projectPath, 'test').generatedDir;
854
+ const child = spawn(process.execPath, [getImportEsm(generatedDir), `${generatedDir}/bootstrap.ts`], {
824
855
  cwd: projectPath,
825
856
  stdio: 'inherit'
826
857
  });
@@ -843,7 +874,8 @@ class CliBinDev extends BeanCliBase {
843
874
  }
844
875
  async _run(projectPath, _modulesMeta) {
845
876
  let closed = false;
846
- return new Promise((resolve, _reject) => {
877
+ let crashError;
878
+ return new Promise((resolve, reject) => {
847
879
  nodemon({
848
880
  script: '.vona/bootstrap.ts',
849
881
  cwd: projectPath,
@@ -855,9 +887,22 @@ class CliBinDev extends BeanCliBase {
855
887
  watch: ['packages-utils', 'packages-vona', './src'],
856
888
  ignore: ['**/node_modules/**', '**/dist/**', '**/test/**/*.test.ts', 'src/backend/play/**', 'src/backend/typing/**', '**/src/config/errors.ts', '**/src/config/locale/*.ts', '**/src/config/config.ts', '**/src/config/constants.ts', '**/src/types/**', '**/src/controller/*.ts(x)?', '**/src/model/*.ts', '**/src/service/*.ts', '**/src/bean/*.*.ts', '**/src/dto/*.ts(x)?', '**/src/entity/*.ts(x)?']
857
889
  });
858
- nodemon.on('quit', () => {
890
+ nodemon.on('crash', () => {
891
+ if (closed) return;
859
892
  closed = true;
860
- resolve(undefined);
893
+ // nodemon emits crash without an error payload; the child already logged the cause.
894
+ crashError = new Error('Development server crashed; see the startup error above');
895
+ // Let nodemon finish processing the child exit before stopping its watchers.
896
+ setImmediate(() => nodemon.emit('quit'));
897
+ }).on('quit', () => {
898
+ closed = true;
899
+ if (crashError) {
900
+ // The programmatic quit event does not close watchers; reset before propagating
901
+ // the failure to the CLI's error handler, which exits the process immediately.
902
+ nodemon.reset(() => reject(crashError));
903
+ } else {
904
+ resolve(undefined);
905
+ }
861
906
  }).on('restart', files => {
862
907
  if (closed) {
863
908
  // force exit
@@ -913,7 +958,7 @@ class CliBinPlay extends BeanCliBase {
913
958
  }
914
959
  }
915
960
  async _runAttach(projectPath) {
916
- const runtimeFile = path.join(projectPath, '.app/runtime/-.json');
961
+ const runtimeFile = path.join(resolveVonaLocalPaths(projectPath, process.env.META_MODE ?? 'dev').runtimeRoot, '-.json');
917
962
  if (!fse.existsSync(runtimeFile)) throw new Error('dev server not running');
918
963
  // args
919
964
  let args = [];
@@ -993,19 +1038,23 @@ class CliBinTest extends BeanCliBase {
993
1038
  flavor,
994
1039
  mode
995
1040
  };
1041
+ const paths = resolveVonaLocalPaths(projectPath, mode);
996
1042
  const configOptions = {
997
1043
  appDir: projectPath,
998
- runtimeDir: '.vona',
1044
+ runtimeDir: paths.generatedDir,
999
1045
  workers: 1
1000
1046
  };
1001
- const {
1002
- modulesMeta
1003
- } = await generateVonaMeta(configMeta, configOptions);
1004
- await this._run(projectPath, modulesMeta);
1005
- if (argv.coverage && !argv.ci) {
1006
- await this._outputCoverageReportViewer(projectPath);
1047
+ try {
1048
+ const {
1049
+ modulesMeta
1050
+ } = await generateVonaMeta(configMeta, configOptions);
1051
+ await this._run(projectPath, modulesMeta);
1052
+ if (argv.coverage && !argv.ci) {
1053
+ await this._outputCoverageReportViewer(projectPath);
1054
+ }
1055
+ } finally {
1056
+ if (!paths.runId) await rimraf(paths.generatedRoot);
1007
1057
  }
1008
- await rimraf(path.join(projectPath, '.vona'));
1009
1058
  }
1010
1059
  async _run(projectPath, modulesMeta) {
1011
1060
  const {
@@ -1031,7 +1080,7 @@ class CliBinTest extends BeanCliBase {
1031
1080
  if (process.env.TEST_WHYISNODERUNNING === 'true') {
1032
1081
  args.push('--import=why-is-node-running/include');
1033
1082
  }
1034
- args = args.concat([getImportEsm(), testFile, projectPath, (!!argv.coverage).toString(), patterns.join(',')]);
1083
+ args = args.concat([getImportEsm(resolveVonaLocalPaths(projectPath, 'test').generatedDir), testFile, projectPath, (!!argv.coverage).toString(), patterns.join(',')]);
1035
1084
  // args = args.concat(['--experimental-transform-types', getImportEsm(), testFile, projectPath, (!!argv.coverage).toString(), patterns.join(',')]);
1036
1085
  await this.helper.spawnExe({
1037
1086
  cmd: 'node',
@@ -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 {};
@@ -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.156",
4
- "gitHead": "6933fa57c12a517fe3819ccf16f3d619e5a22471",
3
+ "version": "1.1.159",
4
+ "gitHead": "47e1e55106b7c76f6545c8ba474f23d9be830453",
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.45",
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",