@corva/create-app 0.0.0-2576df8 → 0.0.0-62a5f2b

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 (59) hide show
  1. package/README.md +8 -6
  2. package/common/python/Makefile +8 -5
  3. package/common/python/pyproject.toml +25 -0
  4. package/common/python/uv.lock +381 -0
  5. package/common/ui/config/vite/corva-vite-config.mjs +396 -0
  6. package/common/ui/devApp.js +1 -0
  7. package/common/ui/index.html +17 -0
  8. package/common/ui/isolatedApp.js +1 -0
  9. package/common/ui/public/DevCenterIsolatedAppPage.html +26 -0
  10. package/common/ui/public/favicon.ico +0 -0
  11. package/common/ui/vite.config.mjs +3 -0
  12. package/common/ui/vitest.global-setup.js +4 -0
  13. package/common/ui/vitest.setup.js +26 -0
  14. package/lib/commands/create.js +11 -3
  15. package/lib/commands/release.js +17 -2
  16. package/lib/constants/cli.js +5 -8
  17. package/lib/constants/manifest.js +3 -3
  18. package/lib/constants/package.js +78 -66
  19. package/lib/flow.js +6 -0
  20. package/lib/flows/lib/manifest.js +1 -1
  21. package/lib/flows/release.js +4 -0
  22. package/lib/flows/steps/release/resolve-prebuilt-zip.js +35 -0
  23. package/lib/flows/steps/zip-file-list-resolve.js +8 -1
  24. package/lib/flows/zip-simple.js +2 -0
  25. package/lib/helpers/cli-version.js +14 -1
  26. package/lib/helpers/resolve-app-runtime.js +25 -14
  27. package/lib/helpers/utils.js +7 -3
  28. package/lib/main.js +2 -3
  29. package/lib/options/zip-file-name.js +18 -0
  30. package/package.json +1 -1
  31. package/templates/ui/javascript/.eslintrc +4 -2
  32. package/templates/ui/javascript/AGENTS.md +13 -11
  33. package/templates/ui/javascript/src/{App.completion.js → App.completion.jsx} +3 -3
  34. package/templates/ui/javascript/src/{App.drilling.js → App.drilling.jsx} +3 -3
  35. package/templates/ui/javascript/src/{AppSettings.js → AppSettings.jsx} +1 -1
  36. package/templates/ui/javascript/src/__tests__/{AppSettings.test.js → AppSettings.test.jsx} +1 -1
  37. package/templates/ui/typescript/.eslintrc +3 -0
  38. package/templates/ui/typescript/AGENTS.md +11 -8
  39. package/templates/ui/typescript/src/App.completion.tsx +1 -1
  40. package/templates/ui/typescript/src/App.drilling.tsx +1 -1
  41. package/templates/ui/typescript/src/__tests__/AppSettings.test.tsx +1 -1
  42. package/templates/ui/typescript/tsconfig.json +17 -4
  43. package/common/python/requirements.txt +0 -2
  44. package/templates/ui/javascript/config/jest/babelTransform.js +0 -16
  45. package/templates/ui/javascript/config/jest/cssTransform.js +0 -16
  46. package/templates/ui/javascript/config/jest/fileTransform.js +0 -48
  47. package/templates/ui/javascript/config/jest/globalSetup.js +0 -5
  48. package/templates/ui/javascript/config/jest/setupTests.js +0 -30
  49. package/templates/ui/javascript/config-overrides.js +0 -10
  50. package/templates/ui/typescript/config/jest/babelTransform.js +0 -16
  51. package/templates/ui/typescript/config/jest/cssTransform.js +0 -16
  52. package/templates/ui/typescript/config/jest/fileTransform.js +0 -48
  53. package/templates/ui/typescript/config/jest/globalSetup.js +0 -5
  54. package/templates/ui/typescript/config/jest/setupTests.js +0 -30
  55. package/templates/ui/typescript/config-overrides.js +0 -10
  56. /package/templates/ui/javascript/src/{App.scss → App.module.scss} +0 -0
  57. /package/templates/ui/javascript/src/__tests__/{App.test.js → App.test.jsx} +0 -0
  58. /package/templates/ui/javascript/src/__tests__/{TestsExample.test.js → TestsExample.test.jsx} +0 -0
  59. /package/templates/ui/typescript/src/{App.scss → App.module.scss} +0 -0
@@ -12,47 +12,49 @@ const uiDependencies = {
12
12
  'corva-convert-units': '1.10.33',
13
13
  'highcharts': '10.3.3',
14
14
  'highcharts-react-official': '2.2.2',
15
- 'lodash': '^4.17.21',
16
- 'mapbox.js': '3.2.1',
15
+ 'lodash': '^4.18.1',
16
+ 'mapbox-gl': '3.16.0',
17
17
  'moment': '2.29.4',
18
18
  'moment-timezone': '0.5.43',
19
- 'react': '17.0.1',
20
- 'react-dom': '17.0.1',
19
+ 'react': '18.3.1',
20
+ 'react-dom': '18.3.1',
21
21
  };
22
22
 
23
23
  const jsUiDevDependencies = {
24
24
  '@corva/dc-platform-shared': 'latest',
25
25
  '@corva/eslint-config-browser': '0.2.2',
26
- '@testing-library/jest-dom': '^5.14.1',
27
- '@testing-library/react': '^12.1.5',
28
- '@testing-library/react-hooks': '^8.0.1',
29
- '@testing-library/user-event': '^13.2.1',
30
- 'babel-jest': '^27.4.2',
31
- 'babel-preset-react-app': '^10.0.1',
26
+ // @testing-library/react 16 moved this from a dependency to a peer
27
+ '@testing-library/dom': '10.4.1',
28
+ '@testing-library/jest-dom': '7.0.1',
29
+ '@testing-library/react': '16.3.2',
30
+ '@testing-library/user-event': '14.6.6',
31
+ '@vitejs/plugin-react': '6.1.1',
32
+ '@vitest/coverage-v8': '4.1.11',
33
+ 'dotenv': '16.4.7',
34
+ 'dotenv-expand': '11.0.7',
32
35
  'eslint': '7.32.0',
33
- 'identity-obj-proxy': '^3.0.0',
34
- 'jest': '^27.4.3',
35
- 'jest-watch-typeahead': '^1.0.0',
36
- 'postcss-loader': '4.1.0',
37
- 'sass': '^1.89.2',
38
- 'sass-loader': '^16.0.5',
36
+ 'jsdom': '22.1.0',
37
+ 'postcss-preset-env': '11.3.0',
38
+ 'rollup-plugin-license': '3.6.0',
39
+ 'sass-embedded': '1.100.0',
40
+ 'terser': '5.47.1',
41
+ 'vite': '8.3.0',
42
+ 'vite-plugin-static-copy': '4.1.0',
43
+ 'vitest': '4.1.11',
39
44
  };
40
45
 
41
46
  const tsUiDevDependencies = {
42
47
  ...jsUiDevDependencies,
43
- '@tsconfig/create-react-app': '1.0.2',
44
- '@types/lodash': '4.17.5',
48
+ '@types/lodash': '^4.17.25',
45
49
  '@types/material-ui': '0.21.9',
46
50
  '@types/minimatch': '3.0.5',
47
51
  '@types/mime': '3.0.1',
48
- '@types/node': '18.19.47',
49
- '@types/react': '^17.0.22',
50
- '@types/react-dom': '^17.0.9',
51
- '@types/jest': '^27.0.1',
52
+ '@types/node': '24.12.2',
53
+ '@types/react': '18.2.42',
54
+ '@types/react-dom': '^18.2',
52
55
  '@typescript-eslint/eslint-plugin': '^4.31.2',
53
56
  '@typescript-eslint/parser': '^4.31.2',
54
- 'ts-loader': '8.2.0',
55
- 'typescript': '4.6.3',
57
+ 'typescript': '^5.9.3',
56
58
  };
57
59
 
58
60
  const corvaUiExtension = {
@@ -90,13 +92,13 @@ function applyUiExtension(packageJson, extensionNames) {
90
92
  }
91
93
 
92
94
  const uiScripts = {
93
- build: 'webpack --config=./config-overrides.js --mode production',
94
- start: 'webpack-dev-server --config=./config-overrides.js --open --mode development',
95
+ build: 'vite build',
96
+ start: 'vite --open',
95
97
  zip: 'create-corva-app zip .',
96
- lint: 'eslint --cache ./src/',
98
+ lint: 'eslint --cache --ext .js,.jsx,.ts,.tsx ./src/',
97
99
  release: 'create-corva-app release .',
98
- test: 'jest',
99
- coverage: 'jest --coverage',
100
+ test: 'vitest run',
101
+ coverage: 'vitest run --coverage',
100
102
  };
101
103
 
102
104
  const uiPackage = {
@@ -105,49 +107,18 @@ const uiPackage = {
105
107
  'scripts': uiScripts,
106
108
  'dependencies': uiDependencies,
107
109
  'devDependencies': jsUiDevDependencies,
108
- 'jest': {
109
- roots: ['<rootDir>/src'],
110
- collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.d.ts'],
111
- coverageThreshold: {
112
- global: {
113
- branches: 60,
114
- functions: 60,
115
- lines: 60,
116
- statements: 60,
117
- },
118
- },
119
- setupFilesAfterEnv: ['<rootDir>/config/jest/setupTests.js'],
120
- globalSetup: '<rootDir>/config/jest/globalSetup.js',
121
- testMatch: ['<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}', '<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}'],
122
- testEnvironment: 'jsdom',
123
- transform: {
124
- '^.+\\.(js|jsx|mjs|cjs|ts|tsx)$': '<rootDir>/config/jest/babelTransform.js',
125
- '^.+\\.css$': '<rootDir>/config/jest/cssTransform.js',
126
- '^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)': '<rootDir>/config/jest/fileTransform.js',
127
- },
128
- transformIgnorePatterns: [
129
- '/node_modules/(?!.*@babel/runtime|@icon-park/react/es).+\\.(js|jsx|mjs|cjs|ts|tsx)$',
130
- '^.+\\.(css|sass|scss)$',
131
- ],
132
- modulePaths: [],
133
- moduleNameMapper: {
134
- '~(.*)': '<rootDir>/src/$1',
135
- '^.+\\.(css|sass|scss)$': 'identity-obj-proxy',
136
- '@corva/ui(.*)': '@corva/ui/cjs-bundle/$1',
137
- },
138
- watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],
139
- resetMocks: true,
140
- },
141
110
  'lint-staged': {
142
111
  'src/**/*.{js,jsx,ts,tsx}': 'eslint --fix',
143
112
  },
144
113
  'resolutions': {
145
114
  '@noble/hashes': '1.8.0',
115
+ 'lodash': '4.18.1',
146
116
  },
147
117
  };
148
118
 
149
119
  const tsUiPackage = {
150
120
  ...uiPackage,
121
+ scripts: { ...uiScripts, typecheck: 'tsc --noEmit' },
151
122
  devDependencies: tsUiDevDependencies,
152
123
  // todo: temporary solution, ref https://github.com/oppia/oppia/issues/22283#issuecomment-2756641371
153
124
  resolutions: {
@@ -165,16 +136,19 @@ const nodeNpmScripts = {
165
136
  };
166
137
 
167
138
  const nodeDependencies = {
168
- '@corva/node-sdk': '^8.3.0',
139
+ '@corva/node-sdk': '^8.5.0',
169
140
  };
170
141
 
171
142
  const nodeDevDependencies = {
172
- '@corva/eslint-config-node': '^5.1.1',
143
+ '@corva/eslint-config-node': '^6.0.0',
144
+ '@typescript-eslint/eslint-plugin': '^8.58.1',
145
+ '@typescript-eslint/parser': '^8.58.1',
173
146
  'jest': '^29.7.0',
174
147
  'dotenv': '^16.0.3',
175
148
  'eslint': '^8.2.0',
149
+ 'eslint-plugin-jest': '^27.1.5',
176
150
  'prettier': '^2.0.0',
177
- 'typescript': '^5.5.4',
151
+ 'typescript': '^5.9.3',
178
152
  };
179
153
 
180
154
  const nodeTsDevDependencies = {
@@ -219,12 +193,47 @@ const nodeNpmPackage = {
219
193
  eslintConfig: {
220
194
  root: true,
221
195
  extends: '@corva/eslint-config-node',
196
+ overrides: [
197
+ {
198
+ files: ['**/*.ts'],
199
+ parser: '@typescript-eslint/parser',
200
+ parserOptions: {
201
+ project: 'tsconfig.json',
202
+ sourceType: 'module',
203
+ },
204
+ plugins: ['@typescript-eslint/eslint-plugin'],
205
+ rules: {
206
+ '@typescript-eslint/padding-line-between-statements': 'off',
207
+ },
208
+ },
209
+ {
210
+ files: [
211
+ '**/*.spec.js',
212
+ '**/*.spec.ts',
213
+ '**/*.spec.mjs',
214
+ '**/*.spec.cjs',
215
+ '**/__mocks__/**/*.js',
216
+ '**/__mocks__/**/*.ts',
217
+ '**/__mocks__/**/*.cjs',
218
+ '**/__mocks__/**/*.mjs',
219
+ ],
220
+ plugins: ['jest'],
221
+ env: {
222
+ jest: true,
223
+ },
224
+ },
225
+ ],
226
+ },
227
+ overrides: {
228
+ lodash: '4.18.1',
222
229
  },
223
230
  };
224
231
 
225
232
  const nodeYarnPackage = {
226
233
  ...nodeNpmPackage,
227
234
  scripts: nodeYarnScripts,
235
+ overrides: undefined,
236
+ resolutions: nodeNpmPackage.overrides,
228
237
  };
229
238
 
230
239
  const nodeTsNpmPackage = {
@@ -241,11 +250,14 @@ const nodeTsNpmPackage = {
241
250
  const nodeTsYarnPackage = {
242
251
  ...nodeTsNpmPackage,
243
252
  scripts: nodeTsYarnScripts,
253
+ overrides: undefined,
254
+ resolutions: nodeNpmPackage.overrides,
244
255
  };
245
256
 
246
257
  const extendWithTsConfig = (packageJson, version) => {
247
258
  packageJson.devDependencies = {
248
- [`@tsconfig/node${version}`]: `^${version === '12' ? '1.0.9' : '1.0.1'}`,
259
+ // New @tsconfig/nodeX package versioning: it starts with X major version
260
+ [`@tsconfig/node${version}`]: `^${version}`,
249
261
  ...packageJson.devDependencies,
250
262
  };
251
263
 
package/lib/flow.js CHANGED
@@ -29,6 +29,12 @@ export const runFlow = async (flow, context, indent = '') => {
29
29
 
30
30
  const runSteps = async (steps = [], context = {}, indent = '') => {
31
31
  for (const step of steps) {
32
+ if (typeof step.skip === 'function' && step.skip(context)) {
33
+ debug('Skipping %s', step.name || step.message);
34
+
35
+ continue;
36
+ }
37
+
32
38
  if (step.name) {
33
39
  const result = await runFlow(step, context, indent);
34
40
 
@@ -11,7 +11,7 @@ const SCHEDULER_MAPPING = [SCHEDULER_TYPE_DATA_TIME, SCHEDULER_TYPE_DEPTH, SCHED
11
11
  {},
12
12
  );
13
13
 
14
- const NODE_RUNTIMES = [APP_RUNTIMES.NODE18, APP_RUNTIMES.NODE20, APP_RUNTIMES.NODE22, APP_RUNTIMES.NODE24];
14
+ const NODE_RUNTIMES = [APP_RUNTIMES.NODE24];
15
15
 
16
16
  export class Manifest {
17
17
  constructor(manifest) {
@@ -8,6 +8,7 @@ import { ADD_LABEL_STEP } from './steps/release/add-label.js';
8
8
  import { ADD_NOTES_STEP } from './steps/release/add-notes.js';
9
9
  import { PUBLISH_PACKAGE_STEP } from './steps/release/publish.js';
10
10
  import { RELEASE_PREPARE_DATA_STEP } from './steps/release/prepare-data.js';
11
+ import { RESOLVE_PREBUILT_ZIP_STEP } from './steps/release/resolve-prebuilt-zip.js';
11
12
 
12
13
  export const RELEASE_FLOW = {
13
14
  name: 'release',
@@ -15,6 +16,9 @@ export const RELEASE_FLOW = {
15
16
  PREPARE_FLOW,
16
17
  SETUP_API_CLIENT_STEP,
17
18
  RELEASE_PREPARE_DATA_STEP,
19
+ // With --zip-file-name the archive already exists, so the zip flow is skipped entirely. That
20
+ // also lets runtimes the zip flow has no file list for (e.g. `provided.*`) be released.
21
+ RESOLVE_PREBUILT_ZIP_STEP,
18
22
  ZIP_SIMPLE_FLOW,
19
23
  UPLOAD_ZIP_TO_CORVA_STEP,
20
24
  WAIT_FOR_BUILD_FINISH_STEP,
@@ -0,0 +1,35 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+
4
+ import { StepError } from '../../lib/step-error.js';
5
+
6
+ /**
7
+ * Handles `--zip-file-name`: the caller has already built the package archive, so this records it
8
+ * as the archive to upload and marks the zip flow as a no-op.
9
+ */
10
+ export const RESOLVE_PREBUILT_ZIP_STEP = {
11
+ message: 'Resolving prebuilt archive...',
12
+ fn: async ({ dirName, options }) => {
13
+ const { zipFileName } = options;
14
+
15
+ if (!zipFileName) {
16
+ return {};
17
+ }
18
+
19
+ const zipPath = resolve(dirName, zipFileName);
20
+
21
+ try {
22
+ await fs.access(zipPath);
23
+ } catch {
24
+ throw new StepError(`Prebuilt archive ${zipFileName} not found in ${dirName}`);
25
+ }
26
+
27
+ return {
28
+ zipFileName,
29
+ skipZip: true,
30
+ // The archive belongs to the caller. Neither flag reaches the context today; this is defensive.
31
+ removeOnSuccess: false,
32
+ removeOnFail: false,
33
+ };
34
+ },
35
+ };
@@ -110,7 +110,13 @@ const resolveDataToZipUiApp = async (itemsToZip = [], { options, pkg, dirName, m
110
110
 
111
111
  itemsToZip.push(
112
112
  'manifest.json',
113
+ // both build systems are listed: missing entries are filtered out in
114
+ // createZipArchive, so one CLI serves webpack apps and Vite apps alike
113
115
  'config-overrides.js',
116
+ 'vite.config.mjs',
117
+ 'index.html',
118
+ 'devApp.js',
119
+ 'isolatedApp.js',
114
120
  'eslint.config.js',
115
121
  'eslint.config.mjs',
116
122
  'tsconfig.json',
@@ -125,6 +131,7 @@ const resolveDataToZipUiApp = async (itemsToZip = [], { options, pkg, dirName, m
125
131
  },
126
132
  ...(await transformPatternsIntoFileNames(dirName, ['src/**/*'], options.ignoredFiles)),
127
133
  ...(await transformPatternsIntoFileNames(dirName, ['config/**/*'], options.ignoredFiles)),
134
+ ...(await transformPatternsIntoFileNames(dirName, ['public/**/*'], options.ignoredFiles)),
128
135
  );
129
136
 
130
137
  if (options.appKey) {
@@ -255,7 +262,7 @@ const resolveDataForZipPythonApp = async (itemsToZip = [], { manifest, dirName,
255
262
  );
256
263
  }
257
264
 
258
- itemsToZip.push('manifest.json', 'requirements.txt');
265
+ itemsToZip.push('manifest.json', 'pyproject.toml', 'uv.lock', 'requirements.txt');
259
266
 
260
267
  return {
261
268
  zipFileName,
@@ -2,5 +2,7 @@ import { ZIP_STEPS } from './steps/zip.js';
2
2
 
3
3
  export const ZIP_SIMPLE_FLOW = {
4
4
  name: 'zip (simple)',
5
+ // Set by RESOLVE_PREBUILT_ZIP_STEP when --zip-file-name names an existing archive.
6
+ skip: ({ skipZip }) => Boolean(skipZip),
5
7
  steps: ZIP_STEPS,
6
8
  };
@@ -75,7 +75,20 @@ export async function warnIfOutdated() {
75
75
  logger.write('Checking for updates...\n');
76
76
 
77
77
  const currentVersion = await getCurrentVersion();
78
- const latestVersion = await getLatestVersion();
78
+
79
+ let latestVersion;
80
+
81
+ try {
82
+ latestVersion = await getLatestVersion();
83
+ } catch (caughtError) {
84
+ if (!(caughtError instanceof StepError)) {
85
+ throw caughtError;
86
+ }
87
+
88
+ logger.write(' ⚠️ Skipping version check (could not reach npm registry).\n');
89
+
90
+ return;
91
+ }
79
92
 
80
93
  // Skip version check if current version is a prerelease (dev or next)
81
94
  if (!isRegularVersion(currentVersion)) {
@@ -6,6 +6,8 @@ import os from 'os';
6
6
  import semver from 'semver';
7
7
 
8
8
  const debug = debugFn('cca:resolve-app-runtime');
9
+ const DEFAULT_UI_NODE_VERSION = '24';
10
+ const DEFAULT_UI_NODE_RANGE = '>=24 <25';
9
11
 
10
12
  /**
11
13
  *
@@ -37,11 +39,14 @@ const checkCliVersion = async (command, version) =>
37
39
 
38
40
  debug(`%s version output: %s`, command, data.toString());
39
41
 
40
- resolve(semver.satisfies(semver.coerce(data.toString('utf-8')), version));
42
+ const currentVersion = semver.coerce(data.toString('utf-8'));
43
+ const requiredVersion = semver.coerce(semverVersionsMapping.node[version] || version);
44
+
45
+ resolve(semver.gte(currentVersion, requiredVersion));
41
46
  });
42
47
  });
43
48
 
44
- const checkNodeVersion = (version) => async () => {
49
+ const checkNodeVersion = (range) => async () => {
45
50
  try {
46
51
  await fs.access(`${os.homedir()}/.nvm/nvm.sh`);
47
52
 
@@ -52,7 +57,7 @@ const checkNodeVersion = (version) => async () => {
52
57
  debug(e);
53
58
  debug('nvm is not installed, checking node version');
54
59
 
55
- return checkCliVersion('node', version);
60
+ return checkCliVersion('node', range);
56
61
  }
57
62
  };
58
63
 
@@ -60,19 +65,22 @@ export const IS_WINDOWS = process.platform === 'win32';
60
65
 
61
66
  const semverVersionsMapping = {
62
67
  node: {
63
- 18: '18.14.0',
64
- 20: '20.16.0',
65
- 24: '24.14.0',
68
+ 18: '18.20.8',
69
+ 20: '20.20.1',
70
+ 24: '24.15.0',
66
71
  },
67
72
  python: {
68
- '3.8': '3.8.16',
69
- '3.9': '3.9.16',
70
- '3.10': '3.10.9',
71
- '3.11': '3.11.1',
72
- '3.13': '3.13.7',
73
+ 3.13: '3.13.13',
74
+ 3.14: '3.14.4',
73
75
  },
74
76
  };
75
77
 
78
+ const nodeVersionRanges = {
79
+ 18: '>=18 <19',
80
+ 20: '>=20 <21',
81
+ 24: '>=24 <25',
82
+ };
83
+
76
84
  /**
77
85
  * @typedef {Object} Runtime
78
86
  * @property {string} language
@@ -80,18 +88,20 @@ const semverVersionsMapping = {
80
88
  * @property {string} packageManager
81
89
  * @property {string} version
82
90
  * @property {string} semver
91
+ * @property {string} [enginesNode]
83
92
  */
84
93
  export const resolveAppRuntime = (opts) => {
85
94
  if (opts.appType === APP_TYPES.UI) {
86
- const version = '20';
95
+ const version = DEFAULT_UI_NODE_VERSION;
87
96
 
88
97
  return {
89
98
  environment: 'node',
90
99
  language: opts.useTypescript ? 'typescript' : 'javascript',
91
- isRuntimeAvailable: checkNodeVersion(version),
100
+ isRuntimeAvailable: checkNodeVersion(DEFAULT_UI_NODE_RANGE),
92
101
  packageManager: opts.packageManager,
93
102
  version,
94
103
  semver: semverVersionsMapping.node[version],
104
+ enginesNode: DEFAULT_UI_NODE_RANGE,
95
105
  };
96
106
  }
97
107
 
@@ -101,10 +111,11 @@ export const resolveAppRuntime = (opts) => {
101
111
  return {
102
112
  environment: 'node',
103
113
  language: opts.useTypescript ? 'typescript' : 'javascript',
104
- isRuntimeAvailable: checkNodeVersion(version),
114
+ isRuntimeAvailable: checkNodeVersion(nodeVersionRanges[version]),
105
115
  packageManager: opts.packageManager,
106
116
  version,
107
117
  semver: semverVersionsMapping.node[version],
118
+ enginesNode: `^${version}`,
108
119
  };
109
120
  }
110
121
 
@@ -7,7 +7,7 @@ export const addUiAppFile = (templateFolder, root, runtime, manifest, opts) => {
7
7
  try {
8
8
  logger.log(chalk.green('adding app file'));
9
9
 
10
- const fileExtension = runtime.language === 'typescript' ? 'tsx' : 'js';
10
+ const fileExtension = runtime.language === 'typescript' ? 'tsx' : 'jsx';
11
11
 
12
12
  const segment =
13
13
  {
@@ -26,9 +26,13 @@ export const addUiAppFile = (templateFolder, root, runtime, manifest, opts) => {
26
26
  };
27
27
 
28
28
  export const getExcludedFiles = (manifest) => {
29
- if (manifest.isUi()) return ['App.drilling.js', 'App.completion.js', 'App.drilling.tsx', 'App.completion.tsx'];
29
+ const excludedFiles = ['.ruff_cache', '.venv', '.pytest_cache', '__pycache__'];
30
30
 
31
- return [];
31
+ if (manifest.isUi()) {
32
+ return [...excludedFiles, 'App.drilling.jsx', 'App.completion.jsx', 'App.drilling.tsx', 'App.completion.tsx'];
33
+ }
34
+
35
+ return excludedFiles;
32
36
  };
33
37
 
34
38
  export function copyFileSync(source, target) {
package/lib/main.js CHANGED
@@ -17,17 +17,16 @@ import { StepError } from './flows/lib/step-error.js';
17
17
  function checkNodeVersion() {
18
18
  logger.write('Checking node version...');
19
19
 
20
- const unsupportedNodeVersion = !semver.satisfies(process.version, '>=16');
20
+ const unsupportedNodeVersion = !semver.satisfies(process.version, '>=24 <25');
21
21
 
22
22
  if (unsupportedNodeVersion) {
23
23
  logger.log(
24
24
  chalk.red(
25
25
  `\nYou are using Node ${process.version}.\n\n` +
26
- `Please update to Node 16 or higher for a better, fully supported experience.\n`,
26
+ `Please update to Node 24.x for a better, fully supported experience.\n`,
27
27
  ),
28
28
  );
29
29
 
30
- // Fall back to latest supported react-scripts on Node 4
31
30
  return process.exit(1);
32
31
  }
33
32
 
@@ -0,0 +1,18 @@
1
+ import { InvalidArgumentError, Option } from 'commander';
2
+
3
+ const flags = '--zip-file-name <string>';
4
+ const description =
5
+ 'Upload an already-built .zip from the project directory instead of zipping it here. ' +
6
+ 'The archive belongs to the caller and is never deleted after upload.';
7
+
8
+ // An unset CI variable arrives as `--zip-file-name=`, and commander 9 takes the next token as the
9
+ // value even when it is another flag — neither should pass for an archive name.
10
+ function argParser(value) {
11
+ if (typeof value !== 'string' || !value.trim() || value.startsWith('-')) {
12
+ throw new InvalidArgumentError('Expected a file name, e.g. --zip-file-name=package.zip.');
13
+ }
14
+
15
+ return value;
16
+ }
17
+
18
+ export const zipFileNameOption = new Option(flags, description).argParser(argParser);
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@corva/create-app","version":"0.0.0-2576df8","private":false,"description":"Create an app to use it in CORVA.AI","keywords":["react"],"bugs":{"url":"https://github.com/facebook/create-react-app/issues"},"repository":{"type":"git","url":"https://github.com/corva-ai/create-corva-app","directory":"@corva/create-app"},"license":"MIT","type":"module","bin":{"create-corva-app":"./bin/create-corva-app.cjs"},"files":["bin/**/*.cjs","bin/**/*.js","lib/**/*.js","templates","template_extensions","common"],"scripts":{"build":"echo \"build is not configured for package \\\"×\\\", skipping.\"","get-changelog":"conventional-changelog -r 2 -p angular","lint":"eslint . --resolve-plugins-relative-to $(pwd) --max-warnings 0","lint:fix":"eslint . --fix","release":"git add -A && standard-version -a","release-rc":"npm run release -- --prerelease","run:local-ui-ts-completion":"node ./bin/create-corva-app.cjs ../test-app-ui-ts-completions --appName 'TestAppUiTsCompletions' --segments 'completion' --category 'analytics' --appKey 'corva.testappuitscompletions.ui' --appType 'ui' --runtime 'ui' -t --extensions corva","run:local-ui-ts-drilling":"node ./bin/create-corva-app.cjs ../test-app-ui-ts-drilling --appName 'TestAppUiTsDrilling' --segments 'drilling' --category 'analytics' --appKey 'corva.testappuitsdrilling.ui' --appType 'ui' --runtime 'ui' -t --extensions corva","run:local-ui-ts-intervention":"node ./bin/create-corva-app.cjs ../test-app-ui-ts-intervention --appName 'TestAppUiTsIntervention' --segments 'intervention' --category 'analytics' --appKey 'corva.testappuitsintervention.ui' --appType 'ui' --runtime 'ui' -t --extensions corva","test":"NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest"},"lint-staged":{"*.js":"npm run lint:fix"},"dependencies":{"archiver":"^5.3.0","chalk":"^4.1.0","commander":"^9.1.0","cross-spawn":"^7.0.3","debug":"^4.3.4","dotenv":"^16.0.0","figlet":"^1.5.0","form-data":"^4.0.0","fs-extra":"^9.0.1","glob":"^8.0.1","got":"^12.5.1","inquirer":"^8.2.3","is-glob":"^4.0.3","lodash":"^4.17.21","npm-api":"^1.0.0","semver":"^7.3.2","terminal-link":"^3.0.0"},"devDependencies":{"@babel/core":"^7.11.0","@babel/eslint-parser":"^7.19.1","@babel/plugin-proposal-class-properties":"^7.18.6","@babel/plugin-syntax-import-assertions":"^7.20.0","@babel/preset-react":"^7.18.6","@commitlint/cli":"^17.3.0","@commitlint/config-conventional":"^17.3.0","@corva/eslint-config-browser":"^0.1.7","@corva/eslint-config-node":"^5.1.1","@corva/node-sdk":"^8.0.1","@types/cross-spawn":"^6.0.2","@types/jest":"^29.5.4","@typescript-eslint/eslint-plugin":"^5.42.1","@typescript-eslint/parser":"^5.42.1","conventional-changelog-cli":"^2.1.0","eslint":"^8.2.0","eslint-config-google":"^0.14.0","eslint-config-prettier":"^8.5.0","eslint-plugin-import":"^2.26.0","eslint-plugin-jest":"^27.1.5","eslint-plugin-jsx-a11y":"^6.7.1","eslint-plugin-prettier":"^4.2.1","eslint-plugin-react":"^7.32.0","eslint-plugin-react-hooks":"^4.6.0","eslint-plugin-require-sort":"^1.3.0","husky":"^8.0.2","jest":"^29.6.4","prettier":"^2.0.0","prettier-plugin-packagejson":"^2.3.0","standard-version":"^9.0.0","typescript":"^4.8.4","zx":"^7.2.3"},"packageManager":"yarn@1.22.19+sha512.ff4579ab459bb25aa7c0ff75b62acebe576f6084b36aa842971cf250a5d8c6cd3bc9420b22ce63c7f93a0857bc6ef29291db39c3e7a23aab5adfd5a4dd6c5d71","engines":{"node":">=18"},"standard-version":{"skip":{"tag":true}}}
1
+ {"name":"@corva/create-app","version":"0.0.0-62a5f2b","private":false,"description":"Create an app to use it in CORVA.AI","keywords":["react"],"bugs":{"url":"https://github.com/facebook/create-react-app/issues"},"repository":{"type":"git","url":"https://github.com/corva-ai/create-corva-app"},"license":"MIT","type":"module","bin":{"create-corva-app":"./bin/create-corva-app.cjs"},"files":["bin/**/*.cjs","bin/**/*.js","lib/**/*.js","templates","template_extensions","common"],"scripts":{"build":"echo \"build is not configured for package \\\"×\\\", skipping.\"","get-changelog":"conventional-changelog -r 2 -p angular","lint":"eslint . --resolve-plugins-relative-to $(pwd) --max-warnings 0","lint:fix":"eslint . --fix","release":"git add -A && standard-version -a","release-rc":"npm run release -- --prerelease","run:local-ui-ts-completion":"node ./bin/create-corva-app.cjs ../test-app-ui-ts-completions --appName 'TestAppUiTsCompletions' --segments 'completion' --category 'analytics' --appKey 'corva.testappuitscompletions.ui' --appType 'ui' --runtime 'ui' -t --extensions corva","run:local-ui-ts-drilling":"node ./bin/create-corva-app.cjs ../test-app-ui-ts-drilling --appName 'TestAppUiTsDrilling' --segments 'drilling' --category 'analytics' --appKey 'corva.testappuitsdrilling.ui' --appType 'ui' --runtime 'ui' -t --extensions corva","run:local-ui-ts-intervention":"node ./bin/create-corva-app.cjs ../test-app-ui-ts-intervention --appName 'TestAppUiTsIntervention' --segments 'intervention' --category 'analytics' --appKey 'corva.testappuitsintervention.ui' --appType 'ui' --runtime 'ui' -t --extensions corva","test":"NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest"},"lint-staged":{"*.js":"npm run lint:fix"},"resolutions":{"lodash":"4.18.1"},"dependencies":{"archiver":"^5.3.0","chalk":"^4.1.0","commander":"^9.1.0","cross-spawn":"^7.0.3","debug":"^4.3.4","dotenv":"^16.0.0","figlet":"^1.5.0","form-data":"^4.0.0","fs-extra":"^9.0.1","glob":"^8.0.1","got":"^12.5.1","inquirer":"^8.2.3","is-glob":"^4.0.3","lodash":"^4.18.1","npm-api":"^1.0.0","semver":"^7.3.2","terminal-link":"^3.0.0"},"devDependencies":{"@babel/core":"^7.11.0","@babel/eslint-parser":"^7.19.1","@babel/plugin-proposal-class-properties":"^7.18.6","@babel/plugin-syntax-import-assertions":"^7.20.0","@babel/preset-react":"^7.18.6","@commitlint/cli":"^17.3.0","@commitlint/config-conventional":"^17.3.0","@corva/eslint-config-browser":"^0.1.7","@corva/eslint-config-node":"^6.0.0","@corva/node-sdk":"^8.5.0","@types/cross-spawn":"^6.0.2","@types/jest":"^29.5.4","@typescript-eslint/eslint-plugin":"^8.58.1","@typescript-eslint/parser":"^8.58.1","conventional-changelog-cli":"^2.1.0","eslint":"^8.2.0","eslint-config-google":"^0.14.0","eslint-config-prettier":"^8.5.0","eslint-plugin-import":"^2.26.0","eslint-plugin-jest":"^27.1.5","eslint-plugin-jsx-a11y":"^6.7.1","eslint-plugin-prettier":"^4.2.1","eslint-plugin-react":"^7.32.0","eslint-plugin-react-hooks":"^4.6.0","eslint-plugin-require-sort":"^1.3.0","husky":"^8.0.2","jest":"^29.6.4","prettier":"^2.0.0","prettier-plugin-packagejson":"^2.3.0","standard-version":"^9.0.0","typescript":"^5.9.3","zx":"^7.2.3"},"packageManager":"yarn@1.22.19+sha512.ff4579ab459bb25aa7c0ff75b62acebe576f6084b36aa842971cf250a5d8c6cd3bc9420b22ce63c7f93a0857bc6ef29291db39c3e7a23aab5adfd5a4dd6c5d71","standard-version":{"skip":{"tag":true}}}
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "root": true,
3
3
  "extends": "@corva/eslint-config-browser",
4
+ "globals": {
5
+ "vi": "readonly"
6
+ },
4
7
  "parserOptions": {
5
8
  "requireConfigFile": false,
6
9
  "babelOptions": {
7
- "presets": ["@babel/preset-react"],
8
- "plugins": ["@babel/plugin-proposal-class-properties"]
10
+ "presets": ["@babel/preset-react"]
9
11
  }
10
12
  }
11
13
  }
@@ -10,22 +10,24 @@ These constraints are enforced by the platform. Violating them breaks the build
10
10
  1. **Root export** — `src/index.js` MUST default-export `{ component: App, settings: AppSettings }`. The component may also be a `ParentApp` wrapper (e.g., `{ component: ParentApp, settings: AppSettings }`) that wraps `App` with Context.Providers and prop initialization. This is the platform loader contract.
11
11
  2. **UI components** — Use only `@corva/ui` components. Import from `@corva/ui/componentsV2`; fall back to `@corva/ui/components` only when a V2 version doesn't exist yet. `@material-ui/core` v4 is available as the underlying library.
12
12
  3. **HTTP clients** — Use `corvaDataAPI` / `corvaAPI` from `@corva/ui/clients` for all network requests.
13
- 4. **Do not rename or delete** `App.js`, `AppSettings.js`, or `index.js` — they are platform entry points.
13
+ 4. **Do not rename or delete** `App.jsx`, `AppSettings.jsx`, or `index.js` — they are platform entry points.
14
14
 
15
15
  ## Project Structure
16
16
 
17
17
  ```
18
18
  src/
19
- App.js — Main app component (default export required)
20
- AppSettings.js — Settings panel component (default export required)
19
+ App.jsx — Main app component (default export required)
20
+ AppSettings.jsx — Settings panel component (default export required)
21
21
  index.js — Root export: { component, settings }
22
22
  constants.js — Default settings values
23
- App.scss — Component styles
24
- __tests__/ — Jest test files
23
+ App.module.scss — Component styles
24
+ __tests__/ — Vitest test files
25
25
  __mocks__/ — Mock data (mockAppProps.js, mockAppSettingsProps.js)
26
26
  assets/ — Static assets (SVGs, images)
27
- config/jest/ — Jest setup (setupTests, transforms)
28
- config-overrides.js — Webpack 5 customization
27
+ config/vite/ — Shared Corva Vite config
28
+ vite.config.mjs — Vite entry config
29
+ vitest.setup.js — Test environment setup
30
+ index.html — Dev server host page
29
31
  manifest.json — Corva platform app metadata (generated at scaffold time)
30
32
  ```
31
33
 
@@ -98,7 +100,7 @@ transition: transition(color, background-color);
98
100
  **Import in component:**
99
101
 
100
102
  ```javascript
101
- import styles from './App.scss';
103
+ import styles from './App.module.scss';
102
104
  // Use: <div className={styles.container}>
103
105
  ```
104
106
 
@@ -265,11 +267,11 @@ General rules:
265
267
 
266
268
  ## Testing
267
269
 
268
- - **Framework:** Jest + React Testing Library
270
+ - **Framework:** Vitest + React Testing Library
269
271
  - **Wrapper:** Use `AppTestWrapper` from `@corva/ui/testing` to wrap components
270
- - **Mocking:** Mock network requests (jest mocks or msw), never call real APIs
272
+ - **Mocking:** Mock network requests (`vi.mock`/`vi.fn` or msw), never call real APIs
271
273
  - **Timezone:** UTC is enforced (`process.env.TZ = 'UTC'`)
272
- - **Mocked globals:** `ResizeObserver`, `MutationObserver` (in `setupTests.js`)
274
+ - **Mocked globals:** `ResizeObserver`, `MutationObserver` (in `vitest.setup.js`)
273
275
 
274
276
  ## Commands
275
277