@corva/create-app 0.0.0-2576df8 → 0.0.0-5fb771a

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 (58) 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 +352 -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 +24 -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 +75 -65
  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 +3 -2
  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 +1 -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/AGENTS.md +10 -8
  38. package/templates/ui/typescript/src/App.completion.tsx +1 -1
  39. package/templates/ui/typescript/src/App.drilling.tsx +1 -1
  40. package/templates/ui/typescript/src/__tests__/AppSettings.test.tsx +1 -1
  41. package/templates/ui/typescript/tsconfig.json +17 -4
  42. package/common/python/requirements.txt +0 -2
  43. package/templates/ui/javascript/config/jest/babelTransform.js +0 -16
  44. package/templates/ui/javascript/config/jest/cssTransform.js +0 -16
  45. package/templates/ui/javascript/config/jest/fileTransform.js +0 -48
  46. package/templates/ui/javascript/config/jest/globalSetup.js +0 -5
  47. package/templates/ui/javascript/config/jest/setupTests.js +0 -30
  48. package/templates/ui/javascript/config-overrides.js +0 -10
  49. package/templates/ui/typescript/config/jest/babelTransform.js +0 -16
  50. package/templates/ui/typescript/config/jest/cssTransform.js +0 -16
  51. package/templates/ui/typescript/config/jest/fileTransform.js +0 -48
  52. package/templates/ui/typescript/config/jest/globalSetup.js +0 -5
  53. package/templates/ui/typescript/config/jest/setupTests.js +0 -30
  54. package/templates/ui/typescript/config-overrides.js +0 -10
  55. /package/templates/ui/javascript/src/{App.scss → App.module.scss} +0 -0
  56. /package/templates/ui/javascript/src/__tests__/{App.test.js → App.test.jsx} +0 -0
  57. /package/templates/ui/javascript/src/__tests__/{TestsExample.test.js → TestsExample.test.jsx} +0 -0
  58. /package/templates/ui/typescript/src/{App.scss → App.module.scss} +0 -0
@@ -0,0 +1,352 @@
1
+ // Ported from corva-monorepo-frontend tools/vite-config. Moves to
2
+ // @corva/dc-platform-shared once the standalone flow is proven, which is why
3
+ // nothing here imports from the app's own source.
4
+ import { createRequire } from 'node:module';
5
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
6
+ import { join, resolve } from 'node:path';
7
+
8
+ import dotenv from 'dotenv';
9
+ import { expand } from 'dotenv-expand';
10
+ import react from '@vitejs/plugin-react';
11
+ import license from 'rollup-plugin-license';
12
+ import { viteStaticCopy } from 'vite-plugin-static-copy';
13
+ import presetEnv from 'postcss-preset-env';
14
+ import { defineConfig } from 'vitest/config';
15
+
16
+ const require = createRequire(import.meta.url);
17
+
18
+ const {
19
+ CORVA_API_ENVS,
20
+ CORVA_API_ENV_VARIABLES,
21
+ CORVA_GLOBAL_DEPENDENCIES,
22
+ } = require('@corva/dc-platform-shared/cjs/devcenter/webpack/constants.js');
23
+ const validateDependencies = require('@corva/dc-platform-shared/cjs/devcenter/webpack/validators/index.js');
24
+ const { updateDotEnv } = require('@corva/dc-platform-shared/cjs/devcenter/webpack/utils/serializeDotEnv.js');
25
+
26
+ const PLATFORM_SHARED_DIR = join(
27
+ require.resolve('@corva/dc-platform-shared/package.json'),
28
+ '..',
29
+ );
30
+ // NOTE: These imports are always bundled, never taken from the platform globals
31
+ const SKIP_IN_EXTERNALS = ['react/jsx-runtime', 'highcharts/'];
32
+
33
+ const isLegalIdentifier = value => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);
34
+
35
+ const toLegalIdentifier = value => {
36
+ const identifier = value.replace(/[^A-Za-z0-9_$]/g, '_');
37
+
38
+ return isLegalIdentifier(identifier) ? identifier : `_${identifier}`;
39
+ };
40
+
41
+ const toGlobalSegmentAccess = segment =>
42
+ /^\d+$/.test(segment) ? `[${segment}]` : `[${JSON.stringify(segment)}]`;
43
+
44
+ const getSegmentedGlobalAssignmentPrefix = appIdentifier => {
45
+ const [rootSegment, ...segments] = appIdentifier.split('.');
46
+ const rootAccessor = `this[${JSON.stringify(rootSegment)}]`;
47
+
48
+ if (segments.length === 0) {
49
+ return `${rootAccessor}=`;
50
+ }
51
+
52
+ let currentAccessor = rootAccessor;
53
+ const initializers = [`${rootAccessor}=${rootAccessor}||{}`];
54
+
55
+ segments.slice(0, -1).forEach(segment => {
56
+ currentAccessor += toGlobalSegmentAccess(segment);
57
+ initializers.push(`${currentAccessor}=${currentAccessor}||{}`);
58
+ });
59
+
60
+ currentAccessor += toGlobalSegmentAccess(segments[segments.length - 1]);
61
+
62
+ return `${initializers.join(',')},${currentAccessor}=`;
63
+ };
64
+
65
+ const getGlobalAssignmentPrefixes = globalName =>
66
+ [
67
+ ...new Set([
68
+ isLegalIdentifier(globalName) ? `var ${globalName}=` : '',
69
+ isLegalIdentifier(globalName)
70
+ ? `this.${globalName}=`
71
+ : `this[${JSON.stringify(globalName)}]=`,
72
+ `this[${JSON.stringify(globalName)}]=`,
73
+ getSegmentedGlobalAssignmentPrefix(globalName),
74
+ ]),
75
+ ].filter(Boolean);
76
+
77
+ const normalizeIifeGlobalAssignment = (jsContent, appIdentifier, emittedGlobalName) => {
78
+ const normalizedAssignment = `this[${JSON.stringify(appIdentifier)}]=`;
79
+
80
+ if (jsContent.includes(normalizedAssignment)) {
81
+ return jsContent;
82
+ }
83
+
84
+ const assignmentPrefix = getGlobalAssignmentPrefixes(emittedGlobalName).find(prefix =>
85
+ jsContent.includes(prefix),
86
+ );
87
+
88
+ if (assignmentPrefix) {
89
+ return jsContent.replace(assignmentPrefix, normalizedAssignment);
90
+ }
91
+
92
+ // Rolldown doesn't emit a global assignment for IIFE output — inject one.
93
+ // Terser rewrites `(function(){})()` → `!function(){}()` when the result is discarded.
94
+ // Replacing `!` with the assignment (`this["name"]=function(){}()`) is valid JS
95
+ // because after `=` the `function` keyword is an expression, not a declaration.
96
+ if (jsContent.trimStart().startsWith('!function')) {
97
+ return jsContent.replace('!function', `${normalizedAssignment}function`);
98
+ }
99
+
100
+ return normalizedAssignment + jsContent;
101
+ };
102
+
103
+ const createCssInjection = cssContent =>
104
+ `(function(){var s=document.createElement('style');s.textContent=${JSON.stringify(
105
+ cssContent,
106
+ )};document.head.appendChild(s)})();`;
107
+
108
+ // The platform loads a single file, so the emitted stylesheets are folded back into it.
109
+ const finalizeJsBundle = ({ outDir, cssFileNames, appIdentifier, emittedGlobalName }) => {
110
+ const jsPath = join(outDir, 'app.js');
111
+ const css = cssFileNames
112
+ .map(fileName => readFileSync(join(outDir, fileName), 'utf-8'))
113
+ .join('');
114
+ const jsContent = readFileSync(jsPath, 'utf-8');
115
+
116
+ writeFileSync(
117
+ jsPath,
118
+ normalizeIifeGlobalAssignment(
119
+ css ? createCssInjection(css) + jsContent : jsContent,
120
+ appIdentifier,
121
+ emittedGlobalName,
122
+ ),
123
+ );
124
+
125
+ cssFileNames.forEach(fileName => unlinkSync(join(outDir, fileName)));
126
+ };
127
+
128
+ const getCorvaApiEnvVariables = () => {
129
+ const apiEnv = process.env.CORVA_API_ENV || CORVA_API_ENVS.production;
130
+
131
+ if (apiEnv in CORVA_API_ENVS) {
132
+ return CORVA_API_ENV_VARIABLES[apiEnv];
133
+ }
134
+
135
+ console.error(`CORVA_API_ENV should be one of [${Object.keys(CORVA_API_ENVS).join(', ')}]`);
136
+
137
+ return process.exit(1);
138
+ };
139
+
140
+ // The DevCenter dev page edits the app's .env through this endpoint.
141
+ const dotEnvEndpointPlugin = appDirectory => ({
142
+ name: 'corva-dotenv-endpoint',
143
+ configureServer(server) {
144
+ server.middlewares.use('/dotenv', (req, res, next) => {
145
+ if (req.method !== 'PATCH') {
146
+ return next();
147
+ }
148
+
149
+ const envPath = resolve(appDirectory, '.env');
150
+ const chunks = [];
151
+
152
+ req.on('data', chunk => chunks.push(chunk));
153
+ req.on('end', () => {
154
+ const currentEnv = existsSync(envPath) ? readFileSync(envPath, 'utf-8') : '';
155
+
156
+ writeFileSync(envPath, updateDotEnv(currentEnv, JSON.parse(Buffer.concat(chunks))));
157
+ res.statusCode = 200;
158
+ res.end();
159
+ });
160
+ });
161
+ },
162
+ });
163
+
164
+ export const createViteConfig = ({ pwd }) => {
165
+ expand(dotenv.config({ path: resolve(pwd, '.env'), quiet: true }));
166
+
167
+ const packageJson = JSON.parse(readFileSync(resolve(pwd, 'package.json'), 'utf-8'));
168
+ const manifestJson = JSON.parse(readFileSync(resolve(pwd, 'manifest.json'), 'utf-8'));
169
+
170
+ const appKey = manifestJson.application.key;
171
+ const version = packageJson.version;
172
+ const appIdentifier = `${appKey.replace(/\./g, '_')}-${version}`;
173
+ const appIifeName = toLegalIdentifier(appIdentifier);
174
+
175
+ const globalDependencies = Object.keys(CORVA_GLOBAL_DEPENDENCIES);
176
+ const externalPatterns = globalDependencies.map(
177
+ libName => new RegExp(`(^${libName}$|^${libName}/)([^]+)?`),
178
+ );
179
+
180
+ const isExternal = source =>
181
+ !SKIP_IN_EXTERNALS.some(skipped => source.startsWith(skipped)) &&
182
+ externalPatterns.some(pattern => pattern.test(source));
183
+
184
+ // Package names are not valid JS identifiers, so every global is read off `this`.
185
+ const toGlobal = id => {
186
+ const staticGlobal = globalDependencies.find(libName => libName === id);
187
+
188
+ if (staticGlobal) {
189
+ return `this["${staticGlobal}"].default || this["${staticGlobal}"]`;
190
+ }
191
+
192
+ const dep = globalDependencies.find(libName => id.startsWith(libName));
193
+
194
+ if (!dep) {
195
+ throw new Error(`Unknown dependency: ${id}`);
196
+ }
197
+
198
+ return `this["${dep}"]${id.replace(dep, '').replace(/\//g, '.')}`;
199
+ };
200
+
201
+ const getAlias = () => {
202
+ const tsconfigPath = resolve(pwd, 'tsconfig.json');
203
+
204
+ if (!existsSync(tsconfigPath)) {
205
+ return {};
206
+ }
207
+
208
+ const { compilerOptions } = JSON.parse(readFileSync(tsconfigPath, 'utf-8'));
209
+
210
+ return Object.fromEntries(
211
+ Object.entries(compilerOptions?.paths || {}).map(([key, [value]]) => [
212
+ key.replace('/*', ''),
213
+ resolve(pwd, value.replace('/*', '')),
214
+ ]),
215
+ );
216
+ };
217
+
218
+ return defineConfig(({ command }) => {
219
+ validateDependencies({ packageJson });
220
+
221
+ const config = {
222
+ root: pwd,
223
+ server: {
224
+ host: process.env.HOST || 'app.local.corva.ai',
225
+ port: Number(process.env.PORT) || 8080,
226
+ },
227
+ css: {
228
+ modules: { localsConvention: 'camelCase' },
229
+ postcss: { plugins: [presetEnv({ stage: 3 })] },
230
+ },
231
+ plugins: [react(), dotEnvEndpointPlugin(pwd)],
232
+ resolve: { alias: getAlias() },
233
+ test: {
234
+ watch: false,
235
+ globals: true,
236
+ environment: 'jsdom',
237
+ setupFiles: ['./vitest.setup.js'],
238
+ globalSetup: ['./vitest.global-setup.js'],
239
+ coverage: {
240
+ provider: 'v8',
241
+ include: ['src/**/*.{js,jsx,ts,tsx}'],
242
+ thresholds: { branches: 60, functions: 60, lines: 60, statements: 60 },
243
+ },
244
+ },
245
+ };
246
+
247
+ if (command === 'serve') {
248
+ return {
249
+ ...config,
250
+ define: {
251
+ global: {},
252
+ // Webpack's EnvironmentPlugin exposed the whole environment, so apps read
253
+ // their own REACT_APP_* vars straight off process.env.
254
+ 'process.env': {
255
+ ...process.env,
256
+ DEV_CENTER_APP_PATH: '',
257
+ NODE_ENV: 'development',
258
+ REACT_APP_ENVIRONMENT: process.env.REACT_APP_ENVIRONMENT || 'development',
259
+ ...getCorvaApiEnvVariables(),
260
+ },
261
+ },
262
+ plugins: [
263
+ ...config.plugins,
264
+ viteStaticCopy({
265
+ watch: { reloadPageOnChange: false },
266
+ targets: [
267
+ { src: `${PLATFORM_SHARED_DIR}/assets/*`, dest: 'static/media' },
268
+ {
269
+ src: `${join(PLATFORM_SHARED_DIR, '../ui/assets')}/*`,
270
+ dest: 'static/media',
271
+ },
272
+ ],
273
+ }),
274
+ ],
275
+ };
276
+ }
277
+
278
+ return {
279
+ ...config,
280
+ define: {
281
+ 'process.env': { NODE_ENV: 'production' },
282
+ 'process.env.NODE_ENV': '"production"',
283
+ },
284
+ plugins: [
285
+ ...config.plugins,
286
+ viteStaticCopy({ targets: [{ src: 'manifest.json', dest: '.' }] }),
287
+ {
288
+ name: 'corva-single-file-bundle',
289
+ writeBundle(_options, bundle) {
290
+ finalizeJsBundle({
291
+ outDir: resolve(pwd, 'dist'),
292
+ cssFileNames: Object.keys(bundle).filter(fileName => fileName.endsWith('.css')),
293
+ appIdentifier,
294
+ emittedGlobalName: appIifeName,
295
+ });
296
+ },
297
+ },
298
+ ],
299
+ build: {
300
+ outDir: 'dist',
301
+ emptyOutDir: true,
302
+ copyPublicDir: false,
303
+ sourcemap: false,
304
+ cssCodeSplit: false,
305
+ minify: 'terser',
306
+ terserOptions: {
307
+ compress: { drop_console: true, drop_debugger: true },
308
+ format: { comments: false },
309
+ },
310
+ // lib mode plus named exports keeps the IIFE compatible with the platform
311
+ // loader, which reads `this["appKey-version"].default.component`.
312
+ lib: {
313
+ entry: resolve(pwd, 'src/index.js'),
314
+ name: appIifeName,
315
+ fileName: () => 'app.js',
316
+ formats: ['iife'],
317
+ },
318
+ rolldownOptions: {
319
+ external: isExternal,
320
+ moduleTypes: { '.cjs': 'js' },
321
+ output: {
322
+ format: 'iife',
323
+ name: appIifeName,
324
+ entryFileNames: 'app.js',
325
+ globals: toGlobal,
326
+ exports: 'named',
327
+ // Rolldown doesn't rewrite `require()` inside bundled CJS modules (e.g.
328
+ // react-is) to their external global references. This shim resolves them
329
+ // to the globals the IIFE receives as params.
330
+ intro:
331
+ 'var require=function(id){var g=globalThis[id];return g&&g.default||g;};',
332
+ },
333
+ plugins: [
334
+ {
335
+ // Rolldown rejects `typeof require` as a define key (not an identifier),
336
+ // so the CJS-detection branches are stripped by transform instead.
337
+ name: 'replace-typeof-require',
338
+ transform(code) {
339
+ if (!code.includes('typeof require')) return null;
340
+
341
+ return { code: code.replaceAll('typeof require', '"undefined"'), map: null };
342
+ },
343
+ },
344
+ license({
345
+ thirdParty: { output: resolve(pwd, 'dist/app.js.LICENSE.txt') },
346
+ }),
347
+ ],
348
+ },
349
+ },
350
+ };
351
+ });
352
+ };
@@ -0,0 +1 @@
1
+ import '@corva/dc-platform-shared/cjs/devcenter/webpack/DevServerEntryPoint.js';
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" href="/favicon.ico" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <meta name="theme-color" content="#000000" />
8
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" />
9
+ <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
10
+ <title>Corva React App</title>
11
+ </head>
12
+ <body data-corva-theme-dark>
13
+ <noscript>You need to enable JavaScript to run this app.</noscript>
14
+ <div id="root"></div>
15
+ <script type="module" src="/devApp.js"></script>
16
+ </body>
17
+ </html>
@@ -0,0 +1 @@
1
+ import '@corva/dc-platform-shared/cjs/devcenter/webpack/DevServerIsolatedAppEntryPoint.js';
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" href="/favicon.ico" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <meta name="theme-color" content="#000000" />
8
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" />
9
+ <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
10
+ <title>Corva React App</title>
11
+ </head>
12
+ <body data-corva-theme-dark>
13
+ <noscript>You need to enable JavaScript to run this app.</noscript>
14
+ <div id="root"></div>
15
+ <!-- Vite only injects the react-refresh preamble into index.html, so this page installs it itself. -->
16
+ <script type="module">
17
+ import RefreshRuntime from '/@react-refresh';
18
+
19
+ RefreshRuntime.injectIntoGlobalHook(window);
20
+ window.$RefreshReg$ = () => {};
21
+ window.$RefreshSig$ = () => type => type;
22
+ window.__vite_plugin_react_preamble_installed__ = true;
23
+ </script>
24
+ <script type="module" src="/isolatedApp.js"></script>
25
+ </body>
26
+ </html>
Binary file
@@ -0,0 +1,3 @@
1
+ import { createViteConfig } from './config/vite/corva-vite-config.mjs';
2
+
3
+ export default createViteConfig({ pwd: import.meta.dirname });
@@ -0,0 +1,4 @@
1
+ export const setup = () => {
2
+ // UTC keeps tests independent of the machine's timezone
3
+ process.env.TZ = 'UTC';
4
+ };
@@ -0,0 +1,24 @@
1
+ import '@testing-library/jest-dom/vitest';
2
+
3
+ class FakeObserver {
4
+ observe() {}
5
+
6
+ unobserve() {}
7
+
8
+ disconnect() {}
9
+ }
10
+
11
+ globalThis.ResizeObserver = FakeObserver;
12
+ globalThis.MutationObserver = FakeObserver;
13
+
14
+ // jsdom 22 cannot parse the container queries @corva/ui's components inject, and
15
+ // reports that straight to stderr on every import. Drop those lines only.
16
+ const writeToStderr = process.stderr.write.bind(process.stderr);
17
+
18
+ process.stderr.write = (chunk, ...rest) => {
19
+ if (String(chunk).includes('Could not parse CSS stylesheet')) {
20
+ return true;
21
+ }
22
+
23
+ return writeToStderr(chunk, ...rest);
24
+ };
@@ -205,6 +205,10 @@ async function addTemplate(root, manifest, runtime, opts) {
205
205
 
206
206
  copyFolderRecursiveSync(templateFolder, root, excludeFiles);
207
207
 
208
+ if (manifest.isUi()) {
209
+ copyFolderRecursiveSync(join(cliRoot, 'common', 'ui'), root);
210
+ }
211
+
208
212
  if (manifest.isNode()) {
209
213
  copyFolderRecursiveSync(join(cliRoot, 'common', 'node'), root);
210
214
  }
@@ -236,7 +240,11 @@ async function addTemplate(root, manifest, runtime, opts) {
236
240
  const extPath = join(cliRoot, 'template_extensions', extension);
237
241
 
238
242
  if (fs.existsSync(extPath)) {
239
- copyFolderRecursiveSync(extPath, root);
243
+ // the extension's .eslintrc is the typescript one; a javascript app keeps
244
+ // its own @babel/eslint-parser config instead
245
+ const excludeFiles = runtime.language === 'javascript' ? ['.eslintrc'] : [];
246
+
247
+ copyFolderRecursiveSync(extPath, root, excludeFiles);
240
248
  }
241
249
  });
242
250
 
@@ -345,7 +353,7 @@ function addPackageJSON(root, manifest, runtime, opts) {
345
353
  private: isPrivate,
346
354
  license,
347
355
  engines: {
348
- node: `^${runtime.version}`,
356
+ node: runtime.enginesNode || `^${runtime.version}`,
349
357
  [runtime.packageManager]: '*',
350
358
  },
351
359
  scripts,
@@ -383,7 +391,7 @@ async function installDependencies(root, manifest, runtime) {
383
391
  args.push('--cache-folder=".yarn-cache"');
384
392
  }
385
393
 
386
- logger.log(chalk.yellow(`Installing template dependencies using ${runtime.packageManager}...`));
394
+ logger.log(chalk.yellow(`Installing template dependencies using ${command}...`));
387
395
 
388
396
  const nvmOpts = {
389
397
  shell: true,
@@ -11,6 +11,8 @@ import { silentOption } from '../options/silent.js';
11
11
  import { ensureBumpVersion } from '../helpers/cli-version.js';
12
12
  import { ERROR_ICON } from '../constants/messages.js';
13
13
  import { appKeyOption } from '../options/app-key.js';
14
+ import { zipFileNameOption } from '../options/zip-file-name.js';
15
+ import { logger } from '../helpers/logger.js';
14
16
 
15
17
  export const releaseCommand = new Command('release')
16
18
  .description('Release app')
@@ -35,7 +37,7 @@ export const releaseCommand = new Command('release')
35
37
  'If package version is already taken - remove the previously published package and upload a new one',
36
38
  ).default(false),
37
39
  )
38
- // .addOption(new Option('--zip-file-name [string]', 'Prebuilt zip file name in dir'))
40
+ .addOption(zipFileNameOption)
39
41
  .addOption(new Option('--author [string]', 'Author name for the audit'))
40
42
  .action(async (dirName, patterns, options) => {
41
43
  // if author is present in CLI, save it to process.env
@@ -43,7 +45,20 @@ export const releaseCommand = new Command('release')
43
45
  process.env.githubUsername = options.author;
44
46
  }
45
47
 
46
- options.bumpVersion = await ensureBumpVersion(options.bumpVersion);
48
+ // Only the zip flow bumps the version, and a prebuilt archive skips it — nothing to prompt for.
49
+ if (options.zipFileName) {
50
+ if (options.bumpVersion && options.bumpVersion !== 'skip') {
51
+ logger.log(
52
+ `⚠️ ${chalk.yellow(
53
+ `--bump-version=${options.bumpVersion} is ignored with --zip-file-name: the version inside ${options.zipFileName} is used as is.`,
54
+ )}`,
55
+ );
56
+ }
57
+
58
+ options.bumpVersion = 'skip';
59
+ } else {
60
+ options.bumpVersion = await ensureBumpVersion(options.bumpVersion);
61
+ }
47
62
 
48
63
  await runFlow(RELEASE_FLOW, {
49
64
  dirName: getRealWorkingDir(dirName, options),
@@ -3,18 +3,15 @@ export const APP_RUNTIMES = {
3
3
  // NODE12: 'nodejs12.x',
4
4
  // NODE14: 'nodejs14.x',
5
5
  // NODE16: 'nodejs16.x',
6
- NODE18: 'nodejs18.x',
7
- NODE20: 'nodejs20.x',
8
- NODE22: 'nodejs22.x',
9
6
  NODE24: 'nodejs24.x',
10
- // PYTHON3_8: 'python3.8',
11
- // PYTHON3_9: 'python3.9',
12
- // PYTHON3_10: 'python3.10',
13
- PYTHON3_11: 'python3.11',
14
- PYTHON3_12: 'python3.12',
15
7
  PYTHON3_13: 'python3.13',
8
+ PYTHON3_14: 'python3.14',
16
9
  };
17
10
 
11
+ export const SUPPORTED_NODE_APP_RUNTIMES = [APP_RUNTIMES.NODE24];
12
+ export const SUPPORTED_PYTHON_APP_RUNTIMES = [APP_RUNTIMES.PYTHON3_14, APP_RUNTIMES.PYTHON3_13];
13
+ export const SUPPORTED_NON_UI_APP_RUNTIMES = [...SUPPORTED_NODE_APP_RUNTIMES, ...SUPPORTED_PYTHON_APP_RUNTIMES];
14
+
18
15
  export const TEMPLATE_TYPES = {
19
16
  UI: 'js',
20
17
  UI_TS: 'ts',
@@ -1,4 +1,4 @@
1
- import { APP_RUNTIMES, APP_TYPES, TEMPLATE_TYPES } from './cli.js';
1
+ import { APP_RUNTIMES, APP_TYPES, SUPPORTED_NON_UI_APP_RUNTIMES, TEMPLATE_TYPES } from './cli.js';
2
2
 
3
3
  export const defaultManifest = {
4
4
  format: 1,
@@ -215,10 +215,10 @@ export const manifestOptions = (projectName = 'Corva Dev Center App') => [
215
215
  message: 'Choose runtime',
216
216
  choices: (answers) => {
217
217
  if (answers && answers.appType !== APP_TYPES.UI) {
218
- return Object.values(APP_RUNTIMES).filter((value) => value !== APP_RUNTIMES.UI);
218
+ return SUPPORTED_NON_UI_APP_RUNTIMES;
219
219
  }
220
220
 
221
- return Object.values(APP_RUNTIMES);
221
+ return [APP_RUNTIMES.UI, ...SUPPORTED_NON_UI_APP_RUNTIMES];
222
222
  },
223
223
  when: (answers) => answers.appType !== APP_TYPES.UI,
224
224
  },