@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.
- package/README.md +2 -2
- package/dist/commands/generate/app.d.ts +2 -0
- package/dist/commands/generate/app.js +45 -0
- package/dist/commands/generate/app.js.map +1 -0
- package/dist/commands/generate/appmanifest.d.ts +2 -0
- package/dist/commands/generate/appmanifest.js +34 -0
- package/dist/commands/generate/appmanifest.js.map +1 -0
- package/dist/commands/generate/dbcontext.d.ts +2 -0
- package/dist/commands/generate/dbcontext.js +32 -0
- package/dist/commands/generate/dbcontext.js.map +1 -0
- package/dist/commands/generate/i18n.d.ts +2 -0
- package/dist/commands/generate/i18n.js +29 -0
- package/dist/commands/generate/i18n.js.map +1 -0
- package/dist/commands/generate/index.d.ts +0 -1
- package/dist/commands/generate/index.js +14 -33
- package/dist/commands/generate/index.js.map +1 -1
- package/dist/commands/generate/manifest.d.ts +2 -0
- package/dist/commands/generate/manifest.js +29 -0
- package/dist/commands/generate/manifest.js.map +1 -0
- package/dist/commands/generate/openapi.d.ts +2 -0
- package/dist/commands/generate/openapi.js +32 -0
- package/dist/commands/generate/openapi.js.map +1 -0
- package/dist/commands/generate/pkg.d.ts +2 -0
- package/dist/commands/generate/pkg.js +34 -0
- package/dist/commands/generate/pkg.js.map +1 -0
- package/dist/commands/generate/shared.d.ts +17 -0
- package/dist/commands/generate/shared.js +70 -0
- package/dist/commands/generate/shared.js.map +1 -0
- package/dist/commands/project/new.js +108 -96
- package/dist/commands/project/new.js.map +1 -1
- package/dist/commands/react/build.js +11 -5
- package/dist/commands/react/build.js.map +1 -1
- package/dist/commands/react/clean.d.ts +2 -0
- package/dist/commands/react/clean.js +46 -0
- package/dist/commands/react/clean.js.map +1 -0
- package/dist/commands/react/dev.d.ts +2 -0
- package/dist/commands/react/dev.js +27 -0
- package/dist/commands/react/dev.js.map +1 -0
- package/dist/commands/react/index.js +5 -1
- package/dist/commands/react/index.js.map +1 -1
- package/dist/commands/react/serve.js +33 -4
- package/dist/commands/react/serve.js.map +1 -1
- package/dist/commands/react/shared.d.ts +21 -0
- package/dist/commands/react/shared.js +80 -0
- package/dist/commands/react/shared.js.map +1 -0
- package/dist/commands/validation/ValidationUtils.d.ts +1 -1
- package/dist/commands/validation/ValidationUtils.js +3 -0
- package/dist/commands/validation/ValidationUtils.js.map +1 -1
- package/dist/commands/validation/files/I18nValidator.d.ts +29 -0
- package/dist/commands/validation/files/I18nValidator.js +198 -0
- package/dist/commands/validation/files/I18nValidator.js.map +1 -0
- package/dist/commands/validation/files/ProjectManifestValidator.d.ts +5 -0
- package/dist/commands/validation/files/ProjectManifestValidator.js +31 -0
- package/dist/commands/validation/files/ProjectManifestValidator.js.map +1 -0
- package/dist/commands/validation/files/ScriptValidator.js +1 -1
- package/dist/commands/validation/files/ScriptValidator.js.map +1 -1
- package/dist/commands/validation/validate.js +11 -6
- package/dist/commands/validation/validate.js.map +1 -1
- package/dist/utils/BuildUtils.d.ts +61 -7
- package/dist/utils/BuildUtils.js +289 -222
- package/dist/utils/BuildUtils.js.map +1 -1
- package/dist/utils/ConsoleUtils.d.ts +8 -0
- package/dist/utils/ConsoleUtils.js +25 -0
- package/dist/utils/ConsoleUtils.js.map +1 -1
- package/dist/utils/DbSchemaUtils.d.ts +49 -0
- package/dist/utils/DbSchemaUtils.js +246 -0
- package/dist/utils/DbSchemaUtils.js.map +1 -0
- package/dist/utils/FileUtils.d.ts +104 -15
- package/dist/utils/FileUtils.js +285 -100
- package/dist/utils/FileUtils.js.map +1 -1
- package/dist/utils/I18nTsUtils.d.ts +66 -0
- package/dist/utils/I18nTsUtils.js +221 -0
- package/dist/utils/I18nTsUtils.js.map +1 -0
- package/dist/utils/OpenApiUtils.d.ts +81 -0
- package/dist/utils/OpenApiUtils.js +123 -0
- package/dist/utils/OpenApiUtils.js.map +1 -0
- package/dist/utils/ProjectCompilationUtils.d.ts +0 -1
- package/dist/utils/ProjectCompilationUtils.js +4 -52
- package/dist/utils/ProjectCompilationUtils.js.map +1 -1
- package/dist/utils/ProjectGeneratorUtils.d.ts +35 -0
- package/dist/utils/ProjectGeneratorUtils.js +119 -0
- package/dist/utils/ProjectGeneratorUtils.js.map +1 -0
- package/dist/utils/ProjectTemplates.d.ts +102 -0
- package/dist/utils/ProjectTemplates.js +284 -0
- package/dist/utils/ProjectTemplates.js.map +1 -0
- package/dist/utils/ProjectUtils.d.ts +216 -3
- package/dist/utils/ProjectUtils.js +387 -22
- package/dist/utils/ProjectUtils.js.map +1 -1
- package/dist/utils/RuntimeUtils.js +14 -8
- package/dist/utils/RuntimeUtils.js.map +1 -1
- package/dist/utils/ScriptDeclarationUtils.d.ts +169 -0
- package/dist/utils/ScriptDeclarationUtils.js +398 -0
- package/dist/utils/ScriptDeclarationUtils.js.map +1 -0
- package/dist/utils/ServeUtils.js +10 -5
- package/dist/utils/ServeUtils.js.map +1 -1
- package/dist/utils/ShellUtils.d.ts +6 -0
- package/dist/utils/ShellUtils.js +13 -0
- package/dist/utils/ShellUtils.js.map +1 -1
- package/dist/utils/TemplateUtils.d.ts +62 -0
- package/dist/utils/TemplateUtils.js +218 -0
- package/dist/utils/TemplateUtils.js.map +1 -0
- package/dist/utils/TypeSchemaUtils.d.ts +42 -0
- package/dist/utils/TypeSchemaUtils.js +171 -0
- package/dist/utils/TypeSchemaUtils.js.map +1 -0
- package/package.json +3 -2
package/dist/utils/BuildUtils.js
CHANGED
|
@@ -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.
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
43
|
-
|
|
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
|
|
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.
|
|
93
|
-
FileUtils_1.default.
|
|
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.
|
|
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.
|
|
112
|
-
ConsoleUtils_1.default.check(`vite.config.
|
|
113
|
-
//
|
|
114
|
-
let
|
|
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
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
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
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
187
|
-
|
|
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
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
|
285
|
-
import
|
|
286
|
-
import
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
.then(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
|
308
|
-
import
|
|
309
|
-
import
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
};
|
|
322
|
-
|
|
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": "
|
|
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" //
|
|
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(
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
<
|
|
497
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|