@codenotch/codenotch.cli 1.0.36 → 1.0.37

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 (105) hide show
  1. package/README.md +2 -2
  2. package/dist/commands/generate/app.d.ts +2 -0
  3. package/dist/commands/generate/app.js +45 -0
  4. package/dist/commands/generate/app.js.map +1 -0
  5. package/dist/commands/generate/appmanifest.d.ts +2 -0
  6. package/dist/commands/generate/appmanifest.js +34 -0
  7. package/dist/commands/generate/appmanifest.js.map +1 -0
  8. package/dist/commands/generate/dbcontext.d.ts +2 -0
  9. package/dist/commands/generate/dbcontext.js +32 -0
  10. package/dist/commands/generate/dbcontext.js.map +1 -0
  11. package/dist/commands/generate/i18n.d.ts +2 -0
  12. package/dist/commands/generate/i18n.js +29 -0
  13. package/dist/commands/generate/i18n.js.map +1 -0
  14. package/dist/commands/generate/index.d.ts +0 -1
  15. package/dist/commands/generate/index.js +14 -33
  16. package/dist/commands/generate/index.js.map +1 -1
  17. package/dist/commands/generate/manifest.d.ts +2 -0
  18. package/dist/commands/generate/manifest.js +29 -0
  19. package/dist/commands/generate/manifest.js.map +1 -0
  20. package/dist/commands/generate/openapi.d.ts +2 -0
  21. package/dist/commands/generate/openapi.js +32 -0
  22. package/dist/commands/generate/openapi.js.map +1 -0
  23. package/dist/commands/generate/pkg.d.ts +2 -0
  24. package/dist/commands/generate/pkg.js +34 -0
  25. package/dist/commands/generate/pkg.js.map +1 -0
  26. package/dist/commands/generate/shared.d.ts +17 -0
  27. package/dist/commands/generate/shared.js +70 -0
  28. package/dist/commands/generate/shared.js.map +1 -0
  29. package/dist/commands/project/new.js +108 -96
  30. package/dist/commands/project/new.js.map +1 -1
  31. package/dist/commands/react/build.js +11 -5
  32. package/dist/commands/react/build.js.map +1 -1
  33. package/dist/commands/react/clean.d.ts +2 -0
  34. package/dist/commands/react/clean.js +46 -0
  35. package/dist/commands/react/clean.js.map +1 -0
  36. package/dist/commands/react/dev.d.ts +2 -0
  37. package/dist/commands/react/dev.js +27 -0
  38. package/dist/commands/react/dev.js.map +1 -0
  39. package/dist/commands/react/index.js +5 -1
  40. package/dist/commands/react/index.js.map +1 -1
  41. package/dist/commands/react/serve.js +33 -4
  42. package/dist/commands/react/serve.js.map +1 -1
  43. package/dist/commands/react/shared.d.ts +21 -0
  44. package/dist/commands/react/shared.js +80 -0
  45. package/dist/commands/react/shared.js.map +1 -0
  46. package/dist/commands/validation/ValidationUtils.d.ts +1 -1
  47. package/dist/commands/validation/ValidationUtils.js +3 -0
  48. package/dist/commands/validation/ValidationUtils.js.map +1 -1
  49. package/dist/commands/validation/files/I18nValidator.d.ts +29 -0
  50. package/dist/commands/validation/files/I18nValidator.js +198 -0
  51. package/dist/commands/validation/files/I18nValidator.js.map +1 -0
  52. package/dist/commands/validation/files/ProjectManifestValidator.d.ts +5 -0
  53. package/dist/commands/validation/files/ProjectManifestValidator.js +31 -0
  54. package/dist/commands/validation/files/ProjectManifestValidator.js.map +1 -0
  55. package/dist/commands/validation/files/ScriptValidator.js +1 -1
  56. package/dist/commands/validation/files/ScriptValidator.js.map +1 -1
  57. package/dist/commands/validation/validate.js +11 -6
  58. package/dist/commands/validation/validate.js.map +1 -1
  59. package/dist/utils/BuildUtils.d.ts +61 -7
  60. package/dist/utils/BuildUtils.js +289 -222
  61. package/dist/utils/BuildUtils.js.map +1 -1
  62. package/dist/utils/ConsoleUtils.d.ts +8 -0
  63. package/dist/utils/ConsoleUtils.js +25 -0
  64. package/dist/utils/ConsoleUtils.js.map +1 -1
  65. package/dist/utils/DbSchemaUtils.d.ts +49 -0
  66. package/dist/utils/DbSchemaUtils.js +246 -0
  67. package/dist/utils/DbSchemaUtils.js.map +1 -0
  68. package/dist/utils/FileUtils.d.ts +104 -15
  69. package/dist/utils/FileUtils.js +285 -100
  70. package/dist/utils/FileUtils.js.map +1 -1
  71. package/dist/utils/I18nTsUtils.d.ts +66 -0
  72. package/dist/utils/I18nTsUtils.js +221 -0
  73. package/dist/utils/I18nTsUtils.js.map +1 -0
  74. package/dist/utils/OpenApiUtils.d.ts +81 -0
  75. package/dist/utils/OpenApiUtils.js +123 -0
  76. package/dist/utils/OpenApiUtils.js.map +1 -0
  77. package/dist/utils/ProjectCompilationUtils.d.ts +0 -1
  78. package/dist/utils/ProjectCompilationUtils.js +4 -52
  79. package/dist/utils/ProjectCompilationUtils.js.map +1 -1
  80. package/dist/utils/ProjectGeneratorUtils.d.ts +35 -0
  81. package/dist/utils/ProjectGeneratorUtils.js +119 -0
  82. package/dist/utils/ProjectGeneratorUtils.js.map +1 -0
  83. package/dist/utils/ProjectTemplates.d.ts +102 -0
  84. package/dist/utils/ProjectTemplates.js +284 -0
  85. package/dist/utils/ProjectTemplates.js.map +1 -0
  86. package/dist/utils/ProjectUtils.d.ts +216 -3
  87. package/dist/utils/ProjectUtils.js +387 -22
  88. package/dist/utils/ProjectUtils.js.map +1 -1
  89. package/dist/utils/RuntimeUtils.js +14 -8
  90. package/dist/utils/RuntimeUtils.js.map +1 -1
  91. package/dist/utils/ScriptDeclarationUtils.d.ts +169 -0
  92. package/dist/utils/ScriptDeclarationUtils.js +398 -0
  93. package/dist/utils/ScriptDeclarationUtils.js.map +1 -0
  94. package/dist/utils/ServeUtils.js +10 -5
  95. package/dist/utils/ServeUtils.js.map +1 -1
  96. package/dist/utils/ShellUtils.d.ts +6 -0
  97. package/dist/utils/ShellUtils.js +13 -0
  98. package/dist/utils/ShellUtils.js.map +1 -1
  99. package/dist/utils/TemplateUtils.d.ts +62 -0
  100. package/dist/utils/TemplateUtils.js +218 -0
  101. package/dist/utils/TemplateUtils.js.map +1 -0
  102. package/dist/utils/TypeSchemaUtils.d.ts +42 -0
  103. package/dist/utils/TypeSchemaUtils.js +171 -0
  104. package/dist/utils/TypeSchemaUtils.js.map +1 -0
  105. package/package.json +3 -2
@@ -4,17 +4,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const fs_1 = require("fs");
7
+ const path_1 = require("path");
7
8
  const StringUtils_1 = __importDefault(require("./StringUtils"));
8
9
  const FileUtils_1 = __importDefault(require("./FileUtils"));
9
10
  const chalk_1 = __importDefault(require("chalk"));
10
11
  const fs_2 = __importDefault(require("fs"));
11
12
  const fs_extra_1 = __importDefault(require("fs-extra"));
12
- const node_fetch_1 = __importDefault(require("node-fetch"));
13
13
  const ShellUtils_1 = __importDefault(require("./ShellUtils"));
14
- const os_1 = require("os");
15
14
  const ConsoleUtils_1 = __importDefault(require("./ConsoleUtils"));
16
15
  class BuildUtils {
17
- static CN_REACT_VERSION = '2.0.0';
16
+ static CN_REACT_VERSION = '2.0.1';
18
17
  static findWorkspaceFromFile(childPath) {
19
18
  let parts = childPath.split(FileUtils_1.default.slash());
20
19
  parts = parts.slice(0, parts.length - 1); // Remove file name
@@ -30,20 +29,47 @@ class BuildUtils {
30
29
  return workspacePath;
31
30
  }
32
31
  /**
33
- * cn react build <target_file>
32
+ * Builds one React app of a Codenotch project into a ready-to-serve bundle.
33
+ * Behind the command: cn react build <target_file>
34
+ *
35
+ * Everything is staged under the `.codenotch/` folder of the workspace, the sources of the
36
+ * project are never touched:
37
+ * - `.codenotch/<appName>-source` — the Vite project: generated package.json, index.html,
38
+ * index.jsx (mounts the app component with react 19's createRoot), vite.config.mjs,
39
+ * tsconfig.json, the workspace `.npmrc` when there is one, and a copy of every source file of the workspace,
40
+ * mirrored flat so that root config files (postcss.config.js, ...) stay where Vite looks.
41
+ * - `.codenotch/<appName>-dist` — the bundle Vite emits, plus a `.env` holding the Codenotch
42
+ * environment (app name, manifests, cluster url) read by the served page.
43
+ * Both folders are deleted first when a previous build left them behind.
44
+ *
45
+ * The build itself runs `npm install` then `npm run build` (vite alone: the copy holds server
46
+ * files too, typechecking is the job of 'cn validate') inside `-source`, with the Node the CLI
47
+ * runs under — Node 20 or newer is expected (Vite 8), and npm needs the network.
48
+ *
49
+ * On success, prints the `cn react serve '<dist>'` command to try the bundle.
50
+ *
51
+ * @param appFilePath Path (absolute, or relative to the current directory) to the app
52
+ * component, e.g. `./apps/index.tsx`. NOT the manifest: the sibling
53
+ * `<name>.manifest.json` is derived from it, and must exist and declare
54
+ * `app.name`, which names the staging folders. The file must live inside
55
+ * a Codenotch workspace (a `manifest.json` in one of its parent folders).
56
+ * @param clusterUrl Base URL of the Codenotch cluster the bundle will talk to, written into
57
+ * the `.env` of the dist. Without it the bundle is built all the same,
58
+ * but not bound to a cluster.
59
+ *
60
+ * Validation failures (missing file, no workspace, no app name...) print the reason and leave
61
+ * with `process.exit(1)`: this method is the command, not a library call. See `buildApp2` for
62
+ * the throwing, CI oriented variant.
34
63
  */
35
- static async buildApp(appFilePath, clusterUrl) {
64
+ static async buildReactApp(appFilePath, clusterUrl) {
36
65
  //appFilePath = '/home/fabian/Documents/Scenario/test-with-jsx/apps/AppWithFC.tsx';
37
66
  //clusterUrl = 'https://fabric.echino.com:19081/lemollqa/scenario';
38
67
  if (`${appFilePath ?? ''}`.trim() === '') {
39
68
  console.log(chalk_1.default.red(`Error: No target file provided for build command.`));
40
69
  process.exit(1);
41
70
  }
42
- let isRelative = !appFilePath.startsWith('/') && !appFilePath.match(/^[a-zA-Z]:\\/);
43
- if (isRelative) {
44
- let currentPath = process.cwd();
45
- appFilePath = StringUtils_1.default.normalizePath(`${currentPath}/${appFilePath}`);
46
- }
71
+ // resolve() already turns a relative path into an absolute one, based on the current directory
72
+ appFilePath = StringUtils_1.default.normalizePath((0, path_1.resolve)(appFilePath));
47
73
  // Check arguments
48
74
  if ((0, fs_1.existsSync)(appFilePath ?? '') === false) {
49
75
  console.log(chalk_1.default.red(`Error: File "${appFilePath}" not found`));
@@ -54,8 +80,6 @@ class BuildUtils {
54
80
  console.log(chalk_1.default.red(`Error: Could not find workspace manifest for file "${appFilePath}".`));
55
81
  process.exit(1);
56
82
  }
57
- // Tenant name
58
- let tenantName = await this.fetchTenantName(clusterUrl);
59
83
  // Load workspace
60
84
  console.log("Loading workspace manifest...");
61
85
  let workspace = {
@@ -81,16 +105,18 @@ class BuildUtils {
81
105
  }
82
106
  ConsoleUtils_1.default.check(`App manifest loaded successfully (App name: "${appName}").\n`);
83
107
  // Define paths
108
+ // The workspace layout is mirrored directly under outDir (no 'src/' wrapper), so root
109
+ // config files (postcss.config.js, tailwind, ...) land where Vite looks for them
84
110
  let outDir = StringUtils_1.default.normalizePath(`${workspacePath}/.codenotch/${appName}-source`);
85
111
  let distDir = StringUtils_1.default.normalizePath(`${workspacePath}/.codenotch/${appName}-dist`);
86
112
  let fileName = StringUtils_1.default.firstUpper(StringUtils_1.default.getNameWithoutExtention(appFilename));
87
- let outFilePath = StringUtils_1.default.normalizePath(`${appFilePath.replace(workspacePath, outDir + '/src')}`);
113
+ let outFilePath = StringUtils_1.default.normalizePath(`${appFilePath.replace(workspacePath, outDir)}`);
88
114
  let relativeOutFilePath = StringUtils_1.default.toRelativePath(outFilePath, outDir).replaceAll('\\', '/');
89
115
  // Clean previous builds
90
116
  if ((0, fs_1.existsSync)(outDir) || (0, fs_1.existsSync)(distDir)) {
91
117
  console.log("Cleaning previous builds...");
92
- FileUtils_1.default.delete(outDir);
93
- FileUtils_1.default.delete(distDir);
118
+ FileUtils_1.default.deleteRecursively(outDir);
119
+ FileUtils_1.default.deleteRecursively(distDir);
94
120
  ConsoleUtils_1.default.check(`Output directory cleaned (${outDir})\n`);
95
121
  }
96
122
  console.log("Creating build files...");
@@ -106,16 +132,12 @@ class BuildUtils {
106
132
  let indexJsx = this.createIndexJsx(relativeOutFilePath, fileName);
107
133
  FileUtils_1.default.writeFileSync(outDir + '/index.jsx', indexJsx);
108
134
  ConsoleUtils_1.default.check(`index.jsx created successfully`);
109
- // vite.config.js
135
+ // vite.config.mjs (.mjs so it always loads as ESM, regardless of package.json "type")
110
136
  let viteConfig = this.createViteConfig(appName);
111
- FileUtils_1.default.writeFileSync(outDir + '/vite.config.js', viteConfig);
112
- ConsoleUtils_1.default.check(`vite.config.js created successfully`);
113
- // empty-module.js
114
- let emptyModule = this.createEmptyModule();
115
- FileUtils_1.default.writeFileSync(outDir + '/empty-module.js', emptyModule);
116
- ConsoleUtils_1.default.check(`empty-module.js created successfully`);
117
- // tsconfig.json
118
- let tsconfig = this.createTsConfig();
137
+ FileUtils_1.default.writeFileSync(outDir + '/vite.config.mjs', viteConfig);
138
+ ConsoleUtils_1.default.check(`vite.config.mjs created successfully`);
139
+ // tsconfig.json - layout is flat (mirrors the workspace), so typecheck the source trees
140
+ let tsconfig = this.createTsConfig(["**/*.ts", "**/*.tsx"]);
119
141
  FileUtils_1.default.writeFileSync(outDir + '/tsconfig.json', tsconfig);
120
142
  ConsoleUtils_1.default.check(`tsconfig.json created successfully`);
121
143
  let npmRcContent = fs_2.default.existsSync(StringUtils_1.default.normalizePath(`${workspacePath}/.npmrc`)) ?
@@ -128,41 +150,40 @@ class BuildUtils {
128
150
  // Find files to copy
129
151
  console.log("\n");
130
152
  console.log("Reading files...");
131
- let includedExts = ['.css', '.scss', '.tsx', '.jsx', '.ts', '.js'];
132
- let includedFiles = [];
133
- for (let file of FileUtils_1.default.getAllFilesFromPath(workspacePath)) {
134
- if (includedExts.some(ext => file.toLowerCase().endsWith(ext))) {
135
- includedFiles.push(file);
136
- }
137
- }
153
+ let includedFiles = FileUtils_1.default.listFilesRecursively(workspacePath, {
154
+ fileExtensions: ['.css', '.scss', '.tsx', '.jsx', '.ts', '.js', '.cjs', '.mjs'],
155
+ // Without these the copy would swallow the dependencies, and the staging folder itself
156
+ ignoredFolderNames: ['.codenotch', 'node_modules', '.git']
157
+ });
138
158
  if (includedFiles.length === 0) {
139
159
  console.log(chalk_1.default.red(`Error: No files found in "${workspacePath}" to include in the build`));
140
160
  return;
141
161
  }
142
162
  for (let includedFile of includedFiles) {
143
- let targetPath = StringUtils_1.default.normalizePath(`${includedFile.replace(workspacePath, outDir + '/src')}`);
163
+ let targetPath = StringUtils_1.default.normalizePath(`${includedFile.replace(workspacePath, outDir)}`);
144
164
  fs_extra_1.default.copySync(includedFile, targetPath);
145
165
  }
146
166
  ConsoleUtils_1.default.check(`${includedFiles.length} files copied successfully\n`);
147
167
  // execute npm i in the outDir
148
168
  await ShellUtils_1.default.exec(outDir, 'npm install', 'Installing dependencies', true);
149
- // Install and use Node.js 20 with nvm (platform-specific)
150
- if ((0, os_1.platform)() === 'win32') {
151
- await ShellUtils_1.default.exec(outDir, 'nvm install 20 && nvm use 20 && npm run build', 'Setting up Node.js 20 and building project', true);
152
- }
153
- else {
154
- await ShellUtils_1.default.exec(outDir, 'bash -c ". ~/.nvm/nvm.sh && nvm install 20 && nvm use 20 && npm run build"', 'Setting up Node.js 20 and building project', true);
169
+ // Vite 8 needs Node 20+: warn up front, its own error would be cryptic
170
+ const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
171
+ if (nodeMajor < 20) {
172
+ ConsoleUtils_1.default.checkWarning(`Node ${process.versions.node} detected, the build needs Node 20 or newer and may fail.`);
155
173
  }
156
- ConsoleUtils_1.default.check(`Nvm and build executed successfully\n`);
174
+ await ShellUtils_1.default.exec(outDir, 'npm run build', 'Building project', true);
175
+ ConsoleUtils_1.default.check(`Build executed successfully\n`);
157
176
  // create .env file
158
177
  console.log("Creating .env file...");
159
178
  let envVariables = await this.getCodenotchEnvVariables(workspace, {
160
179
  appName: appName,
161
180
  app: appManifest,
162
- tenantName: tenantName,
163
181
  clusterUrl: clusterUrl
164
182
  });
165
- let envContent = Object.keys(envVariables).map(c => `${c}=${envVariables[c]}`).join('\n');
183
+ // A variable without value is left out, otherwise the page would read the string "undefined"
184
+ let envContent = Object.keys(envVariables)
185
+ .filter(c => envVariables[c] !== undefined && envVariables[c] !== null)
186
+ .map(c => `${c}=${envVariables[c]}`).join('\n');
166
187
  FileUtils_1.default.writeFileSync(distDir + '/.env', envContent);
167
188
  ConsoleUtils_1.default.check(`.env file created successfully\n`);
168
189
  let bundlePath = StringUtils_1.default.normalizePath(`${distDir}`);
@@ -183,11 +204,8 @@ class BuildUtils {
183
204
  if (`${appFilePath ?? ''}`.trim() === '') {
184
205
  throw new Error(`No target file provided for build command.`);
185
206
  }
186
- let isRelative = !appFilePath.startsWith('/') && !appFilePath.match(/^[a-zA-Z]:\\/);
187
- if (isRelative) {
188
- let currentPath = process.cwd();
189
- appFilePath = StringUtils_1.default.normalizePath(`${currentPath}/${appFilePath}`);
190
- }
207
+ // resolve() already turns a relative path into an absolute one, based on the current directory
208
+ appFilePath = StringUtils_1.default.normalizePath((0, path_1.resolve)(appFilePath));
191
209
  // Check arguments
192
210
  if ((0, fs_1.existsSync)(appFilePath ?? '') === false) {
193
211
  throw new Error(`File "${appFilePath}" not found`);
@@ -230,9 +248,6 @@ class BuildUtils {
230
248
  // vite.config.mjs (.mjs so it always loads as ESM, regardless of package.json "type")
231
249
  let viteConfig = this.createViteConfig(appName);
232
250
  FileUtils_1.default.writeFileSync(outDir + '/vite.config.mjs', viteConfig);
233
- // empty-module.js , needed to mock some modules crypto-browserify -> can we avoid this ?
234
- let emptyModule = this.createEmptyModule();
235
- FileUtils_1.default.writeFileSync(outDir + '/empty-module.js', emptyModule);
236
251
  // tsconfig.json — layout is flat (mirrors the workspace), so typecheck the source trees
237
252
  let tsconfig = this.createTsConfig(["**/*.ts", "**/*.tsx"]);
238
253
  FileUtils_1.default.writeFileSync(outDir + '/tsconfig.json', tsconfig);
@@ -244,18 +259,10 @@ class BuildUtils {
244
259
  }
245
260
  // Find files to copy
246
261
  //TODO what if we have 2 apps in the /apps folder ? if we build one it will copy the file from the other as they are not separated in different folders
247
- let includedExts = ['.css', '.scss', '.tsx', '.jsx', '.ts', '.js', '.cjs', '.mjs'];
248
- let includedFiles = [];
249
- for (let file of FileUtils_1.default.getAllFilesFromPath(workspacePath)) {
250
- // Skip generated/dependency trees so we don't copy the build output back into itself.
251
- let normalized = file.replaceAll('\\', '/');
252
- if (normalized.includes('/.codenotch/') || normalized.includes('/node_modules/')) {
253
- continue;
254
- }
255
- if (includedExts.some(ext => file.toLowerCase().endsWith(ext))) {
256
- includedFiles.push(file);
257
- }
258
- }
262
+ let includedFiles = FileUtils_1.default.listFilesRecursively(workspacePath, {
263
+ fileExtensions: ['.css', '.scss', '.tsx', '.jsx', '.ts', '.js', '.cjs', '.mjs'],
264
+ ignoredFolderNames: ['.codenotch', 'node_modules']
265
+ });
259
266
  if (includedFiles.length === 0) {
260
267
  throw new Error(`No files found in "${workspacePath}" to include in the build`);
261
268
  }
@@ -280,67 +287,49 @@ class BuildUtils {
280
287
  }
281
288
  }
282
289
  static createIndexJsx(relativeOutFilePath, fileName) {
283
- return `
284
- import React from 'react';
285
- import ReactDOM from 'react-dom';
286
- import ${fileName} from './${relativeOutFilePath}';
287
- import { init } from 'codenotch-react';
288
-
289
- fetch('/api/env')
290
- .then(response => response.json())
291
- .then(data => {
292
- init(data);
293
- ReactDOM.render(<${fileName} />, document.getElementById('root'));
294
- })
295
- .catch(error => {
296
- console.error('Error fetching environment variables:', error);
297
- init(null);
298
- ReactDOM.render(<${fileName} />, document.getElementById('root'));
299
- });
290
+ return `
291
+ import { createRoot } from 'react-dom/client';
292
+ import ${fileName} from './${relativeOutFilePath}';
293
+ import { init } from '@codenotch/codenotch.react';
294
+
295
+ // The environment is exposed by the server hosting the bundle (see 'cn react serve')
296
+ const root = createRoot(document.getElementById('root'));
297
+ fetch('/api/env')
298
+ .then(response => response.json())
299
+ .then(data => {
300
+ init(data);
301
+ root.render(<${fileName} />);
302
+ })
303
+ .catch(error => {
304
+ console.error('Error fetching environment variables:', error);
305
+ init({});
306
+ root.render(<${fileName} />);
307
+ });
300
308
  `.trim();
301
309
  }
302
310
  /**
303
311
  * When served from the runtime env variables are not available, the variables are injected into the html file instead
304
312
  */
305
313
  static createIndexJsxDeployment(relativeOutFilePath, fileName) {
306
- return `
307
- import React from 'react';
308
- import ReactDOM from 'react-dom';
309
- import ${fileName} from './${relativeOutFilePath}';
310
- import { init } from 'codenotch-react';
311
-
312
- // variables are injected into the html by the runtime, we can access them on the window object
313
- const data = {
314
- clusterUrl: tenant.clusterUrl,
315
- serviceName: serviceName,
316
- theme: 'dark',
317
- language: languages[0] ?? 'en',
318
- appManifest: appManifest,
319
- tenantName: tenant.name,
320
- appName: appName
321
- };
322
- init(data);
323
- ReactDOM.render(<${fileName} />, document.getElementById('root'));
324
-
314
+ return `
315
+ import { createRoot } from 'react-dom/client';
316
+ import ${fileName} from './${relativeOutFilePath}';
317
+ import { init } from '@codenotch/codenotch.react';
318
+
319
+ // variables are injected into the html by the runtime, we can access them on the window object
320
+ const data = {
321
+ clusterUrl: tenant.clusterUrl,
322
+ serviceName: serviceName,
323
+ theme: 'dark',
324
+ language: languages[0] ?? 'en',
325
+ appManifest: appManifest,
326
+ appName: appName
327
+ };
328
+ init(data);
329
+ createRoot(document.getElementById('root')).render(<${fileName} />);
330
+
325
331
  `.trim();
326
332
  }
327
- static async fetchTenantName(clusterUrl) {
328
- let tenantName = undefined;
329
- if (clusterUrl !== undefined) {
330
- console.log('Fetching tenant name from cluster URL...');
331
- tenantName = await this.getTenantName(clusterUrl);
332
- if (tenantName) {
333
- ConsoleUtils_1.default.check(`Tenant name "${tenantName}" fetched successfully\n`);
334
- }
335
- else {
336
- console.log(chalk_1.default.yellow(`Warning: Unable to fetch tenant name from cluster URL "${clusterUrl}".\n`));
337
- }
338
- }
339
- else {
340
- console.log(chalk_1.default.yellow(`Warning: No cluster URL provided.\n`));
341
- }
342
- return tenantName;
343
- }
344
333
  static async getCodenotchEnvVariables(workpace, options) {
345
334
  let result = {
346
335
  clusterUrl: options.clusterUrl,
@@ -352,12 +341,6 @@ ReactDOM.render(<${fileName} />, document.getElementById('root'));
352
341
  if (options.app) {
353
342
  result.appManifest = JSON.stringify(options.app);
354
343
  }
355
- if (options.tenantName) {
356
- result.tenantName = options.tenantName;
357
- }
358
- let tenantName = options.tenantName;
359
- if (tenantName)
360
- result.tenantName = tenantName;
361
344
  if (options.appName)
362
345
  result.appName = options.appName;
363
346
  if (options.accessToken)
@@ -366,19 +349,6 @@ ReactDOM.render(<${fileName} />, document.getElementById('root'));
366
349
  result.baseUrl = options.baseUrl;
367
350
  return result;
368
351
  }
369
- static async getTenantName(cluster) {
370
- try {
371
- let obj = await (await (0, node_fetch_1.default)(`${cluster}/portal/tenant`)).json();
372
- if (obj && obj.name && typeof obj.name === 'string') {
373
- return obj.name;
374
- }
375
- return undefined;
376
- }
377
- catch (e) {
378
- console.error("Error while fetching tenant name:", e);
379
- return undefined;
380
- }
381
- }
382
352
  /** Compiler options shared by the generated build tsconfig and `cn validate` on script files. */
383
353
  static defaultCompilerOptions() {
384
354
  return {
@@ -391,11 +361,11 @@ ReactDOM.render(<${fileName} />, document.getElementById('root'));
391
361
  "strict": true,
392
362
  "forceConsistentCasingInFileNames": true,
393
363
  "module": "ESNext",
394
- "moduleResolution": "Node",
364
+ "moduleResolution": "Bundler",
395
365
  "resolveJsonModule": true,
396
366
  "isolatedModules": true,
397
367
  "noEmit": true, // On demande à tsc de ne PAS générer de fichiers
398
- "jsx": "react" // "react" correspond au mode classic pour React 16
368
+ "jsx": "react-jsx" // automatic runtime, the react 19 default
399
369
  };
400
370
  }
401
371
  static createTsConfig(include = ["src"]) {
@@ -405,97 +375,46 @@ ReactDOM.render(<${fileName} />, document.getElementById('root'));
405
375
  }, null, 4);
406
376
  return tsconfig;
407
377
  }
408
- static createEmptyModule() {
409
- let emptyModule = `
410
- const deepMock = new Proxy(() => null, {
411
- get: (target, prop) => {
412
- console.warn('Empty module proxy accessed property: ', prop);
413
- if (prop === '__esModule') return true;
414
- return deepMock;
415
- }
416
- });
417
- export default deepMock;
418
- export class ComponentDescription {
419
- constructor() {
420
- this.tag = '';
421
- this.props = {};
422
- this.parentInheritedProps = {};
423
- this.children = [];
424
- this.storedData = {};
425
- }
426
- };
427
- export const DeclarationState = {
428
- IDLE : "idle",
429
- LOADING : "loading",
430
- SUCCESS : "success",
431
- ERROR : "error"
432
- };
433
- export const DspVarType = {
434
- STRING : "string",
435
- BOOLEAN : "boolean",
436
- NUMBER : "number",
437
- INTEGER : "integer",
438
- COLOR : "color",
439
- THICKNESS : "thickness",
440
- SIZE : "size",
441
- BORDER : "border",
442
- OBJECT : "object",
443
- ARRAY : "array",
444
- FUNCTION : "function",
445
- ACTION : "action",
446
- REACT_NODE : "reactNode",
447
- RENDERER : "renderer"
448
- };`.trim();
449
- return emptyModule;
450
- }
451
378
  static createViteConfig(appName) {
452
- let debugOptions = `
453
- sourcemap: true,
454
- terserOptions: {
455
- mangle: false,
456
- keep_fnames: true,
457
- keep_classnames: true,
379
+ let debugOptions = `
380
+ sourcemap: true,
381
+ terserOptions: {
382
+ mangle: false,
383
+ keep_fnames: true,
384
+ keep_classnames: true,
458
385
  }`;
459
- let viteConfig = `
460
- import { defineConfig } from 'vite'
461
- import react from '@vitejs/plugin-react'
462
- import path from 'path';
463
- export default defineConfig({
464
- plugins: [
465
- react({ jsxRuntime: 'classic' }),
466
- ],
467
- base: './',
468
- build: {
469
- outDir: '../${appName}-dist',
470
- emptyOutDir: true,
471
- ${debugOptions}
472
- },
473
- resolve: {
474
- alias: [
475
- { find: 'crypto-browserify', replacement: './empty-module.js' },
476
- { find: /^@echino\\/codenotch\\.ui\\.monaco(\\/.*)?$/, replacement: path.resolve(__dirname, './empty-module.js') },
477
- { find: /^@echino\\/echino\\.ui\\.sdk(\\/.*)?$/, replacement: path.resolve(__dirname, './empty-module.js') },
478
- ],
479
- },
386
+ let viteConfig = `
387
+ import { defineConfig } from 'vite'
388
+ import react from '@vitejs/plugin-react'
389
+ import path from 'path';
390
+ export default defineConfig({
391
+ plugins: [
392
+ react(),
393
+ ],
394
+ base: './',
395
+ build: {
396
+ outDir: '../${appName}-dist',
397
+ emptyOutDir: true,
398
+ ${debugOptions}
399
+ }
480
400
  })`.trim();
481
401
  return viteConfig;
482
402
  }
483
403
  static createIndexHtml(appManifest, appName) {
484
- let indexHtml = `<!DOCTYPE html>
485
- <html lang="fr">
486
- <head>
487
- <meta charset="${appManifest.html?.charset ?? 'UTF-8'}" />
488
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
489
- <title>${appManifest.html?.title ?? appName}</title>
490
- <meta name="description" content="${appManifest.html?.description ?? ''}" />
491
- <meta name="author" content="${appManifest.html?.author ?? ''}" />
492
- <meta name="keywords" content="${appManifest.html?.keywords ?? ''}" />
493
- <script src="https://kit.fontawesome.com/cd9ce0b05e.js" crossorigin="anonymous" data-auto-replace-svg="nest"></script>
494
- </head>
495
- <body>
496
- <div id="root"></div>
497
- <script type="module" src="/index.jsx"></script>
498
- </body>
404
+ let indexHtml = `<!DOCTYPE html>
405
+ <html lang="fr">
406
+ <head>
407
+ <meta charset="${appManifest.html?.charset ?? 'UTF-8'}" />
408
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
409
+ <title>${appManifest.html?.title ?? appName}</title>
410
+ <meta name="description" content="${appManifest.html?.description ?? ''}" />
411
+ <meta name="author" content="${appManifest.html?.author ?? ''}" />
412
+ <meta name="keywords" content="${appManifest.html?.keywords ?? ''}" />
413
+ </head>
414
+ <body>
415
+ <div id="root"></div>
416
+ <script type="module" src="/index.jsx"></script>
417
+ </body>
499
418
  </html>`;
500
419
  return indexHtml;
501
420
  }
@@ -513,7 +432,9 @@ export default defineConfig({
513
432
  "type": currentPkg.type ?? "commonjs",
514
433
  "scripts": {
515
434
  "dev": "vite",
516
- "build": "tsc && vite build",
435
+ // No tsc here: the copy holds the whole workspace, and its server files import
436
+ // packages a browser build must not need. Typechecking is the job of 'cn validate'.
437
+ "build": "vite build",
517
438
  "preview": "vite preview"
518
439
  },
519
440
  "dependencies": currentPkg.dependencies ?? {},
@@ -526,7 +447,8 @@ export default defineConfig({
526
447
  }
527
448
  }
528
449
  if (pgkJson.devDependencies['vite'] === undefined) {
529
- pgkJson.devDependencies['vite'] = "^7.3.1";
450
+ // Vite 8: what @vitejs/plugin-react 6 declares as peer
451
+ pgkJson.devDependencies['vite'] = "^8.0.0";
530
452
  }
531
453
  else {
532
454
  ConsoleUtils_1.default.checkWarning(`Warning: Using custom Vite version: ${pgkJson.devDependencies['vite']}`);
@@ -539,6 +461,151 @@ export default defineConfig({
539
461
  }
540
462
  return pgkJson;
541
463
  }
464
+ /**
465
+ * Starts the Vite dev server of one app, with hot reload on the real sources of the project.
466
+ * Behind the command: cn react dev <app>
467
+ *
468
+ * Unlike {@link BuildUtils.buildReactApp}, nothing of the project is copied: the staging
469
+ * folder `.codenotch/<appName>-dev` only holds the generated files (package.json, index.html,
470
+ * index.jsx, vite.config.mjs), and the config aliases the app component to its real path in
471
+ * the workspace. Editing a project file is therefore picked up live by Vite.
472
+ *
473
+ * The `/api/env` endpoint the app reads at startup is served by the dev server itself,
474
+ * through a small plugin of the generated config: the page behaves like the served bundle.
475
+ *
476
+ * Dependencies are installed in the staging on the first run only; delete the folder
477
+ * (or run 'cn react clean <app>') to force a fresh install.
478
+ *
479
+ * Blocks until the server is stopped (Ctrl+C), and returns the exit code it left.
480
+ */
481
+ static async devReactApp(appFilePath, options) {
482
+ // resolve() already turns a relative path into an absolute one, based on the current directory
483
+ appFilePath = StringUtils_1.default.normalizePath((0, path_1.resolve)(appFilePath));
484
+ if ((0, fs_1.existsSync)(appFilePath) === false) {
485
+ console.log(chalk_1.default.red(`Error: File "${appFilePath}" not found`));
486
+ return 1;
487
+ }
488
+ let workspacePath = this.findWorkspaceFromFile(appFilePath) ?? '';
489
+ if (workspacePath === '' || (0, fs_1.existsSync)(workspacePath) === false) {
490
+ console.log(chalk_1.default.red(`Error: Could not find workspace manifest for file "${appFilePath}".`));
491
+ return 1;
492
+ }
493
+ // App manifest, named like the component
494
+ let appFilename = StringUtils_1.default.getFilenameFromUri(appFilePath);
495
+ let appManifestPath = StringUtils_1.default.normalizePath(`${StringUtils_1.default.getFolder(appFilePath)}/${appFilename.replace(/\.[^/.]+$/, '.manifest.json')}`);
496
+ if ((0, fs_1.existsSync)(appManifestPath) === false) {
497
+ console.log(chalk_1.default.red(`Error: App manifest file not found at "${appManifestPath}"`));
498
+ return 1;
499
+ }
500
+ let appManifest = JSON.parse(fs_2.default.readFileSync(appManifestPath, 'utf-8'));
501
+ let appName = `${appManifest.app?.name ?? ''}`.trim();
502
+ if (appName === '') {
503
+ console.log(chalk_1.default.red(`Error: App name not found in manifest at "${appManifestPath}"`));
504
+ return 1;
505
+ }
506
+ // Same environment as the one a built bundle reads, served by the dev server itself
507
+ let workspace = { manifest: this.getFullManifestAt(workspacePath), path: workspacePath };
508
+ let envVariables = await this.getCodenotchEnvVariables(workspace, {
509
+ appName: appName,
510
+ app: appManifest,
511
+ clusterUrl: options.clusterUrl
512
+ });
513
+ // Only generated files live in the staging: the sources stay, and are watched, in the workspace
514
+ let devDir = StringUtils_1.default.normalizePath(`${workspacePath}/.codenotch/${appName}-dev`);
515
+ console.log("Creating dev files...");
516
+ FileUtils_1.default.writeFileSync(devDir + '/package.json', JSON.stringify(this.createPkgJson(workspacePath, appName), null, 4));
517
+ FileUtils_1.default.writeFileSync(devDir + '/index.html', this.createIndexHtml(appManifest, appName));
518
+ FileUtils_1.default.writeFileSync(devDir + '/index.jsx', this.createIndexJsxDev());
519
+ FileUtils_1.default.writeFileSync(devDir + '/vite.config.mjs', this.createViteDevConfig(workspacePath, devDir, appFilePath, envVariables, options.port));
520
+ ConsoleUtils_1.default.check(`Dev files created in ${devDir}\n`);
521
+ // Vite 8 needs Node 20+: warn up front, its own error would be cryptic
522
+ const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
523
+ if (nodeMajor < 20) {
524
+ ConsoleUtils_1.default.checkWarning(`Node ${process.versions.node} detected, the dev server needs Node 20 or newer and may fail.`);
525
+ }
526
+ if ((0, fs_1.existsSync)(StringUtils_1.default.normalizePath(`${devDir}/node_modules`)) === false) {
527
+ console.log("Installing dependencies...");
528
+ const installCode = await ShellUtils_1.default.run(devDir, 'npm install --no-audit --no-fund');
529
+ if (installCode !== 0) {
530
+ console.log(chalk_1.default.red(`Error: npm install failed (exit code ${installCode}).`));
531
+ return installCode;
532
+ }
533
+ }
534
+ console.log(chalk_1.default.green(`Starting dev server for "${appName}" (hot reload on ${workspacePath})...`));
535
+ // Vite runs FROM the workspace: tailwind 4 scans its sources from the working directory,
536
+ // and the staging holds none of them. The config still points everything at the staging.
537
+ const devDirPosix = devDir.replaceAll('\\', '/');
538
+ const viteCommand = `node "${devDirPosix}/node_modules/vite/bin/vite.js" --config "${devDirPosix}/vite.config.mjs"`;
539
+ return await ShellUtils_1.default.run(workspacePath, viteCommand);
540
+ }
541
+ /** The dev entry: same contract as the built one, the app itself comes through the '@app' alias */
542
+ static createIndexJsxDev() {
543
+ return `
544
+ import { createRoot } from 'react-dom/client';
545
+ import App from '@app';
546
+ import { init } from '@codenotch/codenotch.react';
547
+
548
+ // The environment is exposed by the dev server (see the codenotch-env plugin of vite.config.mjs)
549
+ const root = createRoot(document.getElementById('root'));
550
+ fetch('/api/env')
551
+ \t.then(response => response.json())
552
+ \t.then(data => {
553
+ \t\tinit(data);
554
+ \t\troot.render(<App />);
555
+ \t})
556
+ \t.catch(error => {
557
+ \t\tconsole.error('Error fetching environment variables:', error);
558
+ \t\tinit({});
559
+ \t\troot.render(<App />);
560
+ \t});
561
+ `.trim();
562
+ }
563
+ /**
564
+ * The dev flavoured Vite config: the app is aliased to its real file in the workspace so that
565
+ * hot reload watches the project sources, and a tiny plugin serves the Codenotch environment.
566
+ */
567
+ static createViteDevConfig(workspacePath, devDir, appFilePath, envVariables, port) {
568
+ const workspacePosix = workspacePath.replaceAll('\\', '/');
569
+ const devDirPosix = devDir.replaceAll('\\', '/');
570
+ const appFilePosix = appFilePath.replaceAll('\\', '/');
571
+ return `
572
+ import { defineConfig } from 'vite'
573
+ import react from '@vitejs/plugin-react'
574
+
575
+ // The same environment a built bundle reads from its .env, served here by the dev server
576
+ const codenotchEnv = ${JSON.stringify(envVariables, null, 4)};
577
+
578
+ export default defineConfig({
579
+ // The server is rooted here (index.html, node_modules), but runs from the workspace
580
+ root: ${JSON.stringify(devDirPosix)},
581
+ plugins: [
582
+ react(),
583
+ {
584
+ name: 'codenotch-env',
585
+ configureServer(server) {
586
+ server.middlewares.use('/api/env', (req, res) => {
587
+ res.setHeader('Content-Type', 'application/json');
588
+ res.end(JSON.stringify(codenotchEnv));
589
+ });
590
+ }
591
+ },
592
+ ],
593
+ resolve: {
594
+ alias: [
595
+ // The app is NOT copied here: it is loaded, and watched, from the workspace
596
+ { find: '@app', replacement: ${JSON.stringify(appFilePosix)} },
597
+ ],
598
+ },
599
+ server: {
600
+ port: ${port},
601
+ strictPort: true,
602
+ fs: {
603
+ // The sources live outside the vite root, in the workspace
604
+ allow: [${JSON.stringify(workspacePosix)}],
605
+ },
606
+ },
607
+ })`.trim();
608
+ }
542
609
  static getFullManifestAt(folderPath) {
543
610
  let manifest = this.getManifestAt(folderPath);
544
611
  return manifest;