@codenotch/codenotch.cli 1.0.52 → 1.0.54
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 +14 -5
- package/dist/commands/generate/app.js +22 -14
- package/dist/commands/generate/app.js.map +1 -1
- package/dist/commands/generate/appmanifest.js +14 -2
- package/dist/commands/generate/appmanifest.js.map +1 -1
- package/dist/commands/generate/dbcontext.js +9 -2
- package/dist/commands/generate/dbcontext.js.map +1 -1
- package/dist/commands/generate/i18n.js +15 -0
- package/dist/commands/generate/i18n.js.map +1 -1
- package/dist/commands/generate/manifest.js +7 -2
- package/dist/commands/generate/manifest.js.map +1 -1
- package/dist/commands/generate/openapi.js +9 -2
- package/dist/commands/generate/openapi.js.map +1 -1
- package/dist/commands/generate/pkg.js +7 -2
- package/dist/commands/generate/pkg.js.map +1 -1
- package/dist/commands/generate/process.js +26 -18
- package/dist/commands/generate/process.js.map +1 -1
- package/dist/commands/generate/shared.d.ts +5 -0
- package/dist/commands/generate/shared.js +12 -0
- package/dist/commands/generate/shared.js.map +1 -1
- package/dist/commands/project/bump.d.ts +2 -0
- package/dist/commands/project/bump.js +130 -0
- package/dist/commands/project/bump.js.map +1 -0
- package/dist/commands/project/compile.js +11 -2
- package/dist/commands/project/compile.js.map +1 -1
- package/dist/commands/project/deploy.js +10 -2
- package/dist/commands/project/deploy.js.map +1 -1
- package/dist/commands/project/index.js +3 -0
- package/dist/commands/project/index.js.map +1 -1
- package/dist/commands/project/open.js +4 -4
- package/dist/commands/project/open.js.map +1 -1
- package/dist/commands/project/package.js +60 -4
- package/dist/commands/project/package.js.map +1 -1
- package/dist/commands/project/refresh.js +49 -3
- package/dist/commands/project/refresh.js.map +1 -1
- package/dist/commands/project/remove.js +4 -1
- package/dist/commands/project/remove.js.map +1 -1
- package/dist/commands/project/start.js +8 -4
- package/dist/commands/project/start.js.map +1 -1
- package/dist/commands/project/test.js +41 -3
- package/dist/commands/project/test.js.map +1 -1
- package/dist/commands/react/index.js +1 -4
- package/dist/commands/react/index.js.map +1 -1
- package/dist/commands/runtime/clear.js +5 -0
- package/dist/commands/runtime/clear.js.map +1 -1
- package/dist/commands/runtime/info.js +1 -1
- package/dist/commands/runtime/info.js.map +1 -1
- package/dist/commands/runtime/portal.js +4 -4
- package/dist/commands/runtime/portal.js.map +1 -1
- package/dist/commands/runtime/start.js +10 -1
- package/dist/commands/runtime/start.js.map +1 -1
- package/dist/commands/runtime/stop.js +5 -0
- package/dist/commands/runtime/stop.js.map +1 -1
- package/dist/commands/runtime/update.js +4 -3
- package/dist/commands/runtime/update.js.map +1 -1
- package/dist/commands/system/completion.d.ts +7 -0
- package/dist/commands/system/completion.js +145 -0
- package/dist/commands/system/completion.js.map +1 -0
- package/dist/commands/system/index.js +2 -0
- package/dist/commands/system/index.js.map +1 -1
- package/dist/commands/system/info.js +42 -8
- package/dist/commands/system/info.js.map +1 -1
- package/dist/commands/validation/validate.js +3 -3
- package/dist/commands/validation/validate.js.map +1 -1
- package/dist/index.js +32 -4
- package/dist/index.js.map +1 -1
- package/dist/utils/BuildUtils.d.ts +6 -5
- package/dist/utils/BuildUtils.js +61 -45
- package/dist/utils/BuildUtils.js.map +1 -1
- package/dist/utils/ConsoleUtils.d.ts +27 -0
- package/dist/utils/ConsoleUtils.js +53 -4
- package/dist/utils/ConsoleUtils.js.map +1 -1
- package/dist/utils/FileUtils.js +1 -1
- package/dist/utils/FileUtils.js.map +1 -1
- package/dist/utils/GitHubAuthUtils.js +5 -5
- package/dist/utils/GitHubAuthUtils.js.map +1 -1
- package/dist/utils/PackageUtils.js +15 -15
- package/dist/utils/PackageUtils.js.map +1 -1
- package/dist/utils/ProjectCompilationUtils.d.ts +0 -1
- package/dist/utils/ProjectCompilationUtils.js +22 -23
- package/dist/utils/ProjectCompilationUtils.js.map +1 -1
- package/dist/utils/ProjectTemplates.js +2 -2
- package/dist/utils/ProjectTemplates.js.map +1 -1
- package/dist/utils/ProjectUtils.d.ts +6 -0
- package/dist/utils/ProjectUtils.js +34 -1
- package/dist/utils/ProjectUtils.js.map +1 -1
- package/dist/utils/RuntimeUtils.d.ts +84 -9
- package/dist/utils/RuntimeUtils.js +263 -58
- package/dist/utils/RuntimeUtils.js.map +1 -1
- package/dist/utils/ShellUtils.js +6 -2
- package/dist/utils/ShellUtils.js.map +1 -1
- package/dist/utils/TemplateUtils.js +26 -14
- package/dist/utils/TemplateUtils.js.map +1 -1
- package/package.json +4 -3
- package/resources/docs/apps.md +0 -1
- package/resources/docs/project.md +21 -8
- package/dist/commands/react/serve.d.ts +0 -2
- package/dist/commands/react/serve.js +0 -52
- package/dist/commands/react/serve.js.map +0 -1
- package/dist/utils/ServeUtils.d.ts +0 -9
- package/dist/utils/ServeUtils.js +0 -172
- package/dist/utils/ServeUtils.js.map +0 -1
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -13,9 +46,10 @@ const node_child_process_1 = require("node:child_process");
|
|
|
13
46
|
const chalk_1 = __importDefault(require("chalk"));
|
|
14
47
|
const decompress_1 = __importDefault(require("decompress"));
|
|
15
48
|
const FileUtils_1 = __importDefault(require("./FileUtils"));
|
|
16
|
-
const ProjectUtils_1 =
|
|
49
|
+
const ProjectUtils_1 = __importStar(require("./ProjectUtils"));
|
|
17
50
|
const GitHubAuthUtils_1 = __importDefault(require("./GitHubAuthUtils"));
|
|
18
51
|
const ProjectCompilationUtils_1 = __importDefault(require("./ProjectCompilationUtils"));
|
|
52
|
+
const ConsoleUtils_1 = __importDefault(require("./ConsoleUtils"));
|
|
19
53
|
class RuntimeUtils {
|
|
20
54
|
/// The directory where the runtime is installed, we will store the executable and other files there
|
|
21
55
|
/// TODO not sure about that, should we store globally, or for each "project" ? Both ? like npm
|
|
@@ -28,6 +62,8 @@ class RuntimeUtils {
|
|
|
28
62
|
static DEFAULT_RUNTIME_HOST = "127.0.0.1";
|
|
29
63
|
static DEFAULT_RUNTIME_PORT = 5005;
|
|
30
64
|
static ADMIN_USER_EMAIL = "admin@example.com";
|
|
65
|
+
/** A deployment the runtime does not conclude within this delay is reported as failed rather than waited for ever */
|
|
66
|
+
static DEPLOY_TIMEOUT_MS = 5 * 60 * 1000;
|
|
31
67
|
//#region Runtime
|
|
32
68
|
static async readRuntimeMeta() {
|
|
33
69
|
try {
|
|
@@ -97,19 +133,49 @@ class RuntimeUtils {
|
|
|
97
133
|
//TODO some way to cache and reuse this
|
|
98
134
|
return response.access_token;
|
|
99
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Makes sure the runtime is installed, proposing to install it when it is not.
|
|
138
|
+
* Outside a terminal the proposal is accepted, so scripts and CI keep working unattended.
|
|
139
|
+
* Returns false when the user declined, after a message telling how to install it.
|
|
140
|
+
*/
|
|
100
141
|
static async ensureRuntimeInstalled() {
|
|
142
|
+
if (await RuntimeUtils.isRuntimeInstalled()) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
if (!await ConsoleUtils_1.default.confirm('Codenotch runtime is not installed. Install it now?', true)) {
|
|
146
|
+
console.error(chalk_1.default.yellow(`Codenotch runtime is not installed, run 'cn runtime install' to install it.`));
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
//TODO Here we assume NodeJs is installed and available in PATH, we should check that and propose to install it if not
|
|
150
|
+
await RuntimeUtils.installLatestRuntime();
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Makes sure the runtime is running, proposing to install it and/or start it when it is not.
|
|
155
|
+
* Outside a terminal the proposal is accepted, so scripts and CI keep working unattended.
|
|
156
|
+
* Returns false when the user declined, after a message telling how to do it by hand.
|
|
157
|
+
*/
|
|
158
|
+
static async ensureRuntimeIsRunning(options) {
|
|
159
|
+
if (await RuntimeUtils.isRuntimeRunning()) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
// A missing runtime is one question, not two: install and start go together
|
|
101
163
|
const isInstalled = await RuntimeUtils.isRuntimeInstalled();
|
|
164
|
+
const question = isInstalled
|
|
165
|
+
? 'Codenotch runtime is not running. Start it now?'
|
|
166
|
+
: 'Codenotch runtime is not installed. Install and start it now?';
|
|
167
|
+
if (!await ConsoleUtils_1.default.confirm(question, true)) {
|
|
168
|
+
console.error(chalk_1.default.yellow(isInstalled
|
|
169
|
+
? `Codenotch runtime is not running, run 'cn runtime start' to start it.`
|
|
170
|
+
: `Codenotch runtime is not installed, run 'cn runtime install' to install it.`));
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
102
173
|
if (!isInstalled) {
|
|
103
174
|
//TODO Here we assume NodeJs is installed and available in PATH, we should check that and propose to install it if not
|
|
104
175
|
await RuntimeUtils.installLatestRuntime();
|
|
105
176
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const isRunning = await RuntimeUtils.isRuntimeRunning();
|
|
109
|
-
if (isRunning) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
await RuntimeUtils.startRuntime();
|
|
177
|
+
await RuntimeUtils.startRuntime(options);
|
|
178
|
+
return true;
|
|
113
179
|
}
|
|
114
180
|
static async isRuntimeInstalled() {
|
|
115
181
|
const targetExe = RuntimeUtils.getRuntimeExecutablePath();
|
|
@@ -167,7 +233,7 @@ class RuntimeUtils {
|
|
|
167
233
|
child.stdout?.on("data", (chunk) => {
|
|
168
234
|
const text = chunk.toString();
|
|
169
235
|
stdout += text;
|
|
170
|
-
process.
|
|
236
|
+
process.stderr.write(text); // the output of the runtime is a log, stdout stays for the results of the CLI
|
|
171
237
|
});
|
|
172
238
|
child.stderr?.on("data", (chunk) => {
|
|
173
239
|
const text = chunk.toString();
|
|
@@ -198,11 +264,15 @@ class RuntimeUtils {
|
|
|
198
264
|
const exeName = platform === "win32" ? "codenotch.exe" : "codenotch";
|
|
199
265
|
return node_path_1.default.join(RuntimeUtils.CODENOTCH_INSTALL_DIR, exeName);
|
|
200
266
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
267
|
+
/** "v0.1.5", "0.1.5 " and "V0.1.5" name the same release */
|
|
268
|
+
static normalizeVersion(version) {
|
|
269
|
+
return version.trim().replace(/^v/i, '');
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Installs the latest runtime release, or updates the installed runtime to it when it is newer.
|
|
273
|
+
* Returns false when the installed runtime already is that release (`force` reinstalls it anyway).
|
|
274
|
+
*/
|
|
275
|
+
static async installLatestRuntime(options) {
|
|
206
276
|
const platform = process.platform; // win32 | linux | darwin
|
|
207
277
|
const exeName = platform === "win32" ? "codenotch.exe" : "codenotch";
|
|
208
278
|
let assetName;
|
|
@@ -219,7 +289,7 @@ class RuntimeUtils {
|
|
|
219
289
|
const headers = {
|
|
220
290
|
Authorization: `Bearer ${token}`,
|
|
221
291
|
};
|
|
222
|
-
|
|
292
|
+
ConsoleUtils_1.default.log(chalk_1.default.blue(`Getting latest Codenotch runtime release metadata...`));
|
|
223
293
|
const latestReleaseRes = await fetch("https://api.github.com/repos/EchinoHub/service-core/releases/latest", {
|
|
224
294
|
headers: {
|
|
225
295
|
...headers,
|
|
@@ -237,7 +307,21 @@ class RuntimeUtils {
|
|
|
237
307
|
throw new Error(`Asset not found: ${assetName}.`);
|
|
238
308
|
}
|
|
239
309
|
const version = release.tag_name;
|
|
240
|
-
|
|
310
|
+
// Downloading the release already installed would only cost time and a restart
|
|
311
|
+
if (!options?.force && await RuntimeUtils.isRuntimeInstalled()) {
|
|
312
|
+
const installedVersion = await RuntimeUtils.getRuntimeVersion();
|
|
313
|
+
if (RuntimeUtils.normalizeVersion(installedVersion) === RuntimeUtils.normalizeVersion(version)) {
|
|
314
|
+
ConsoleUtils_1.default.log(chalk_1.default.green(`Codenotch runtime is already up to date (${version}).`));
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
ConsoleUtils_1.default.log(chalk_1.default.blue(`Updating Codenotch runtime ${installedVersion} to ${version}...`));
|
|
318
|
+
}
|
|
319
|
+
// The executable cannot be replaced under a running runtime
|
|
320
|
+
const wasRunning = await RuntimeUtils.isRuntimeRunning();
|
|
321
|
+
if (wasRunning) {
|
|
322
|
+
await RuntimeUtils.stopRuntime();
|
|
323
|
+
}
|
|
324
|
+
ConsoleUtils_1.default.log(chalk_1.default.blue(`Downloading Codenotch runtime from ${asset.url}...`));
|
|
241
325
|
const res = await fetch(asset.url, {
|
|
242
326
|
redirect: "follow",
|
|
243
327
|
headers: {
|
|
@@ -246,7 +330,7 @@ class RuntimeUtils {
|
|
|
246
330
|
}
|
|
247
331
|
});
|
|
248
332
|
if (!res.ok || !res.body) {
|
|
249
|
-
console.
|
|
333
|
+
console.error(chalk_1.default.red(`Failed to download runtime from ${asset.url}`));
|
|
250
334
|
throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
251
335
|
}
|
|
252
336
|
const tempRoot = await promises_1.default.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), "codenotch-"));
|
|
@@ -270,7 +354,7 @@ class RuntimeUtils {
|
|
|
270
354
|
version: version,
|
|
271
355
|
dataPath: existingMeta?.dataPath
|
|
272
356
|
});
|
|
273
|
-
|
|
357
|
+
ConsoleUtils_1.default.log(chalk_1.default.blue(`The runtime has been installed successfully.`));
|
|
274
358
|
}
|
|
275
359
|
finally {
|
|
276
360
|
FileUtils_1.default.deleteRecursively(tempRoot);
|
|
@@ -278,19 +362,23 @@ class RuntimeUtils {
|
|
|
278
362
|
if (wasRunning) {
|
|
279
363
|
await RuntimeUtils.startRuntime();
|
|
280
364
|
}
|
|
365
|
+
return true;
|
|
281
366
|
}
|
|
282
367
|
static async startRuntime(options) {
|
|
283
368
|
const isRunning = await RuntimeUtils.isRuntimeRunning();
|
|
284
369
|
if (isRunning) {
|
|
285
370
|
return;
|
|
286
371
|
}
|
|
287
|
-
|
|
372
|
+
// Installing is a decision of the caller (see ensureRuntimeInstalled / ensureRuntimeIsRunning), not a side effect of starting
|
|
373
|
+
if (!await RuntimeUtils.isRuntimeInstalled()) {
|
|
374
|
+
throw new Error(`Codenotch runtime is not installed, run 'cn runtime install' to install it.`);
|
|
375
|
+
}
|
|
288
376
|
// Start the runtime process in detached mode by default
|
|
289
377
|
let detached = true;
|
|
290
378
|
if (options?.attach) {
|
|
291
379
|
detached = false;
|
|
292
380
|
}
|
|
293
|
-
|
|
381
|
+
ConsoleUtils_1.default.log(chalk_1.default.blue(`Starting Codenotch runtime ${detached ? "in detached mode" : "in attached mode"}...`));
|
|
294
382
|
const port = options?.port ?? RuntimeUtils.DEFAULT_RUNTIME_PORT;
|
|
295
383
|
const dataPath = options?.dataPath;
|
|
296
384
|
const version = await RuntimeUtils.getRuntimeVersion();
|
|
@@ -309,7 +397,7 @@ class RuntimeUtils {
|
|
|
309
397
|
while (attempts < maxAttempts) {
|
|
310
398
|
try {
|
|
311
399
|
await RuntimeUtils.runtimeFetch("/health");
|
|
312
|
-
|
|
400
|
+
ConsoleUtils_1.default.log(chalk_1.default.green(`Runtime is ready!`));
|
|
313
401
|
return; // Runtime is responsive
|
|
314
402
|
}
|
|
315
403
|
catch (e) {
|
|
@@ -317,7 +405,7 @@ class RuntimeUtils {
|
|
|
317
405
|
attempts++;
|
|
318
406
|
}
|
|
319
407
|
}
|
|
320
|
-
console.
|
|
408
|
+
console.error(chalk_1.default.red(`Failed to start runtime: runtime did not become responsive within expected time.`));
|
|
321
409
|
throw new Error(`Runtime did not become responsive within expected time.`);
|
|
322
410
|
}
|
|
323
411
|
static async stopRuntime() {
|
|
@@ -357,10 +445,10 @@ class RuntimeUtils {
|
|
|
357
445
|
const dataDir = node_path_1.default.join(RuntimeUtils.CODENOTCH_INSTALL_DIR, RuntimeUtils.CODENOTCH_DATA_FOLDER);
|
|
358
446
|
try {
|
|
359
447
|
await promises_1.default.rm(dataDir, { recursive: true, force: true });
|
|
360
|
-
|
|
448
|
+
ConsoleUtils_1.default.log(chalk_1.default.green(`Runtime data cleared successfully.`));
|
|
361
449
|
}
|
|
362
450
|
catch (error) {
|
|
363
|
-
console.
|
|
451
|
+
console.error(chalk_1.default.red(`Failed to clear runtime data: ${error}`));
|
|
364
452
|
throw error;
|
|
365
453
|
}
|
|
366
454
|
}
|
|
@@ -399,33 +487,116 @@ class RuntimeUtils {
|
|
|
399
487
|
}
|
|
400
488
|
//#endregion
|
|
401
489
|
//#region Project management
|
|
490
|
+
/**
|
|
491
|
+
* Build, deploy and start the project on the runtime, proposing to install/start the runtime when needed.
|
|
492
|
+
* Returns false when the runtime was declined or the deployment failed.
|
|
493
|
+
*/
|
|
402
494
|
static async startProject(projectPath) {
|
|
403
495
|
//TODO multiproject support
|
|
404
496
|
//TODO --watch mode
|
|
405
497
|
const serviceName = await ProjectUtils_1.default.getProjectServiceName(projectPath);
|
|
406
|
-
|
|
407
|
-
|
|
498
|
+
if (!await RuntimeUtils.ensureRuntimeIsRunning()) {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
408
501
|
const outputPath = node_path_1.default.join(node_os_1.default.tmpdir(), `cn-build-${Date.now()}.zip`);
|
|
409
502
|
try {
|
|
410
|
-
|
|
503
|
+
ConsoleUtils_1.default.log(chalk_1.default.blue(`Building project "${serviceName}"...`));
|
|
411
504
|
await ProjectUtils_1.default.packageProject(projectPath, outputPath, true);
|
|
412
|
-
|
|
505
|
+
ConsoleUtils_1.default.log(chalk_1.default.blue(`Deploying project "${serviceName}" to runtime...`));
|
|
506
|
+
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait a bit before deploying to ensure the runtime is fully ready, only temporary until runtime fix the ready endpoint to be more reliable
|
|
507
|
+
const deployed = await RuntimeUtils.deployProject(serviceName, outputPath);
|
|
508
|
+
if (deployed.success) {
|
|
509
|
+
ConsoleUtils_1.default.log(chalk_1.default.green(`Project "${serviceName}" is now running.`));
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
ConsoleUtils_1.default.error(`Project start failed.`);
|
|
513
|
+
}
|
|
514
|
+
return deployed.success;
|
|
515
|
+
}
|
|
516
|
+
finally {
|
|
517
|
+
// Remove the build
|
|
518
|
+
FileUtils_1.default.deleteRecursively(outputPath);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Redeploy the project on the runtime: the sources are packaged and uploaded, the runtime
|
|
523
|
+
* restarts the service with them. The runtime is started when it is not running yet, and a
|
|
524
|
+
* project the runtime does not know yet is deployed as a whole.
|
|
525
|
+
* Returns false when the deployment failed, the errors being printed by the runtime call.
|
|
526
|
+
*/
|
|
527
|
+
static async refreshProject(projectPath, options) {
|
|
528
|
+
const serviceName = await ProjectUtils_1.default.getProjectServiceName(projectPath);
|
|
529
|
+
const result = { success: false, serviceName, errors: [], warnings: [] };
|
|
530
|
+
// 'refresh' syncs a running runtime, a stopped one is no reason to give up: propose to start it
|
|
531
|
+
if (!await RuntimeUtils.isRuntimeRunning()) {
|
|
532
|
+
if (!await RuntimeUtils.ensureRuntimeIsRunning()) {
|
|
533
|
+
result.errors.push('Codenotch runtime is not running');
|
|
534
|
+
return result;
|
|
535
|
+
}
|
|
413
536
|
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait a bit before deploying to ensure the runtime is fully ready, only temporary until runtime fix the ready endpoint to be more reliable
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
537
|
+
}
|
|
538
|
+
// What the runtime holds right now, only to tell a first deployment from an update
|
|
539
|
+
const currentState = await RuntimeUtils.getProjectState(serviceName);
|
|
540
|
+
result.mode = currentState === undefined ? 'deploy' : 'update';
|
|
541
|
+
result.previousVersion = currentState?.projectVersion;
|
|
542
|
+
const outputPath = node_path_1.default.join(node_os_1.default.tmpdir(), `cn-refresh-${Date.now()}.zip`);
|
|
543
|
+
try {
|
|
544
|
+
ConsoleUtils_1.default.log(chalk_1.default.blue(`Building project "${serviceName}"...`));
|
|
545
|
+
try {
|
|
546
|
+
const compilation = await ProjectUtils_1.default.packageProject(projectPath, outputPath, options?.ignoreErrors ?? false);
|
|
547
|
+
result.warnings.push(...compilation.warnings);
|
|
548
|
+
}
|
|
549
|
+
catch (error) {
|
|
550
|
+
// The compilation errors are listed by the packaging, only the way out is left to say
|
|
551
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
552
|
+
result.errors.push(...(error instanceof ProjectUtils_1.CompilationError ? error.errors : [message]));
|
|
553
|
+
ConsoleUtils_1.default.error(message);
|
|
554
|
+
ConsoleUtils_1.default.warn(options?.ignoreErrors
|
|
555
|
+
? `Nothing was deployed, the runtime still serves the version it holds.`
|
|
556
|
+
: `Nothing was deployed. Fix the errors above, or run with '--ignore-errors' to deploy anyway.`);
|
|
557
|
+
return result;
|
|
558
|
+
}
|
|
559
|
+
ConsoleUtils_1.default.log(chalk_1.default.blue(currentState === undefined
|
|
560
|
+
? `Project "${serviceName}" is not on the runtime yet, deploying it...`
|
|
561
|
+
: `Deploying the changes of "${serviceName}" (deployed version ${currentState.projectVersion})...`));
|
|
562
|
+
const deployed = await RuntimeUtils.deployProject(serviceName, outputPath);
|
|
563
|
+
result.success = deployed.success;
|
|
564
|
+
result.status = deployed.status;
|
|
565
|
+
result.errors.push(...deployed.errors);
|
|
566
|
+
result.warnings.push(...deployed.warnings);
|
|
567
|
+
if (deployed.success) {
|
|
568
|
+
ConsoleUtils_1.default.log(chalk_1.default.green(`Project "${serviceName}" is in sync with its sources.`));
|
|
417
569
|
}
|
|
418
570
|
else {
|
|
419
|
-
|
|
571
|
+
ConsoleUtils_1.default.error(`Project refresh failed.`);
|
|
420
572
|
}
|
|
573
|
+
return result;
|
|
421
574
|
}
|
|
422
575
|
finally {
|
|
423
576
|
// Remove the build
|
|
424
577
|
FileUtils_1.default.deleteRecursively(outputPath);
|
|
425
578
|
}
|
|
426
579
|
}
|
|
580
|
+
/**
|
|
581
|
+
* The deployment state the runtime holds for a service, undefined when it does not know it
|
|
582
|
+
* (never deployed, removed since, or the runtime cannot be reached).
|
|
583
|
+
*/
|
|
584
|
+
static async getProjectState(serviceName) {
|
|
585
|
+
try {
|
|
586
|
+
return await RuntimeUtils.runtimeFetch(`/${serviceName}/project`, true);
|
|
587
|
+
}
|
|
588
|
+
catch {
|
|
589
|
+
return undefined;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Runs the tests of the project with a separate instance of the runtime, proposing to install it when needed.
|
|
594
|
+
* Returns false when the runtime was declined.
|
|
595
|
+
*/
|
|
427
596
|
static async testProject(projectPath, fileFilter, nameFilter, inPlace = false) {
|
|
428
|
-
await RuntimeUtils.ensureRuntimeInstalled()
|
|
597
|
+
if (!await RuntimeUtils.ensureRuntimeInstalled()) {
|
|
598
|
+
return undefined;
|
|
599
|
+
}
|
|
429
600
|
// For testing we use a separate instance of the runtime
|
|
430
601
|
// We run it in attach mode so we get the results in the console
|
|
431
602
|
const isPartial = !!(fileFilter || nameFilter);
|
|
@@ -473,13 +644,12 @@ class RuntimeUtils {
|
|
|
473
644
|
await compileFunc(projectPath);
|
|
474
645
|
const tempReportPath = node_path_1.default.join(node_os_1.default.tmpdir(), `cn-report-${Date.now()}.json`);
|
|
475
646
|
try {
|
|
476
|
-
await testFunc
|
|
477
|
-
|
|
647
|
+
const report = await RuntimeUtils.runTestsAndPersist(testFunc, projectPath, tempReportPath, finalReportPath, isPartial);
|
|
648
|
+
return { report, reportPath: finalReportPath };
|
|
478
649
|
}
|
|
479
650
|
finally {
|
|
480
651
|
await promises_1.default.unlink(tempReportPath).catch(() => { });
|
|
481
652
|
}
|
|
482
|
-
return;
|
|
483
653
|
}
|
|
484
654
|
const tempRoot = await promises_1.default.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), "codenotch-"));
|
|
485
655
|
try {
|
|
@@ -487,8 +657,8 @@ class RuntimeUtils {
|
|
|
487
657
|
await FileUtils_1.default.copyFolder(projectPath, tempRoot, ['.git', 'node_modules', 'dist', 'build', 'out']);
|
|
488
658
|
await compileFunc(tempRoot);
|
|
489
659
|
const tempReportPath = node_path_1.default.join(tempRoot, "test-report.json");
|
|
490
|
-
await testFunc
|
|
491
|
-
|
|
660
|
+
const report = await RuntimeUtils.runTestsAndPersist(testFunc, tempRoot, tempReportPath, finalReportPath, isPartial);
|
|
661
|
+
return { report, reportPath: finalReportPath };
|
|
492
662
|
}
|
|
493
663
|
finally {
|
|
494
664
|
// Clean up temp folder
|
|
@@ -496,19 +666,38 @@ class RuntimeUtils {
|
|
|
496
666
|
await FileUtils_1.default.deleteRecursively(tempRoot);
|
|
497
667
|
}
|
|
498
668
|
}
|
|
669
|
+
/**
|
|
670
|
+
* Runs the tests and persists their report. A run the runtime ends in error still yields its
|
|
671
|
+
* report when it wrote one: failing tests are told by the report, not by the exit code.
|
|
672
|
+
*/
|
|
673
|
+
static async runTestsAndPersist(testFunc, workPath, tempReportPath, finalReportPath, isPartial) {
|
|
674
|
+
let runError;
|
|
675
|
+
try {
|
|
676
|
+
await testFunc(workPath, tempReportPath);
|
|
677
|
+
}
|
|
678
|
+
catch (error) {
|
|
679
|
+
runError = error;
|
|
680
|
+
}
|
|
681
|
+
const report = await RuntimeUtils.persistTestReport(tempReportPath, finalReportPath, isPartial);
|
|
682
|
+
if (report === undefined && runError !== undefined) {
|
|
683
|
+
throw runError;
|
|
684
|
+
}
|
|
685
|
+
return report;
|
|
686
|
+
}
|
|
499
687
|
static async deployProject(serviceName, packagePath) {
|
|
688
|
+
const result = { success: false, serviceName, errors: [], warnings: [] };
|
|
500
689
|
// We assume the project is already built and packaged, we just need to deploy it to the runtime
|
|
501
|
-
// Get the information about the project package, we assume it's in the current directory and named "project.zip"
|
|
502
690
|
const zipFilePath = packagePath;
|
|
503
691
|
try {
|
|
504
692
|
await promises_1.default.access(zipFilePath);
|
|
505
693
|
}
|
|
506
694
|
catch (error) {
|
|
507
|
-
console.log(chalk_1.default.red(`Project package not found: ${zipFilePath}`));
|
|
508
695
|
throw new Error(`Project package not found: ${zipFilePath}`);
|
|
509
696
|
}
|
|
510
|
-
|
|
511
|
-
|
|
697
|
+
if (!await RuntimeUtils.ensureRuntimeIsRunning()) {
|
|
698
|
+
result.errors.push('Codenotch runtime is not running');
|
|
699
|
+
return result;
|
|
700
|
+
}
|
|
512
701
|
const zipBuffer = await promises_1.default.readFile(zipFilePath);
|
|
513
702
|
const zipBlob = new Blob([zipBuffer], { type: "application/zip" });
|
|
514
703
|
const form = new FormData();
|
|
@@ -518,36 +707,49 @@ class RuntimeUtils {
|
|
|
518
707
|
method: "POST",
|
|
519
708
|
body: form
|
|
520
709
|
});
|
|
521
|
-
// Wait until the project deployment has completed
|
|
522
|
-
|
|
710
|
+
// Wait until the project deployment has completed, a runtime stuck in between is reported rather than waited for ever
|
|
711
|
+
const deadline = Date.now() + RuntimeUtils.DEPLOY_TIMEOUT_MS;
|
|
712
|
+
while (Date.now() < deadline) {
|
|
523
713
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
524
714
|
try {
|
|
525
715
|
const projectState = await RuntimeUtils.runtimeFetch(`/${serviceName}/project`, true);
|
|
716
|
+
result.status = projectState.status;
|
|
717
|
+
result.warnings = projectState.warnings ?? [];
|
|
526
718
|
if (projectState.status === "READY") {
|
|
527
|
-
|
|
528
|
-
|
|
719
|
+
ConsoleUtils_1.default.log(chalk_1.default.green(`${serviceName} is ready!`));
|
|
720
|
+
result.success = true;
|
|
721
|
+
return result;
|
|
529
722
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
723
|
+
if (projectState.status === "FAILURE" || projectState.status === "REMOVING") {
|
|
724
|
+
result.errors = projectState.errors ?? [];
|
|
725
|
+
ConsoleUtils_1.default.error(`Failed to deploy ${serviceName}. Errors: ${result.errors.join(", ")}`);
|
|
726
|
+
return result;
|
|
533
727
|
}
|
|
534
728
|
}
|
|
535
729
|
catch {
|
|
536
730
|
// Keep waiting
|
|
537
731
|
}
|
|
538
732
|
}
|
|
733
|
+
result.errors.push(`The runtime did not report '${serviceName}' as ready within ${RuntimeUtils.DEPLOY_TIMEOUT_MS / 1000} seconds (last status: ${result.status ?? 'unknown'}).`);
|
|
734
|
+
ConsoleUtils_1.default.error(result.errors[result.errors.length - 1]);
|
|
735
|
+
return result;
|
|
539
736
|
}
|
|
540
|
-
|
|
541
|
-
throw new Error('Method not implemented.');
|
|
542
|
-
}
|
|
737
|
+
/** Uninstalls the project of the given folder from the runtime, false when nothing was removed. */
|
|
543
738
|
static async removeProject(projectPath) {
|
|
544
739
|
const serviceName = await ProjectUtils_1.default.getProjectServiceName(projectPath);
|
|
545
|
-
await RuntimeUtils.uninstallProject(serviceName);
|
|
740
|
+
return await RuntimeUtils.uninstallProject(serviceName);
|
|
546
741
|
}
|
|
742
|
+
/** Uninstalls the project from the runtime, false when nothing was removed. */
|
|
547
743
|
static async uninstallProject(serviceName) {
|
|
548
|
-
//
|
|
549
|
-
|
|
550
|
-
|
|
744
|
+
// Installing a runtime only to delete a project on it makes no sense: without one there is nothing to remove
|
|
745
|
+
if (!await RuntimeUtils.isRuntimeInstalled()) {
|
|
746
|
+
console.error(chalk_1.default.yellow(`Codenotch runtime is not installed, there is no project to remove.`));
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
// The removal is done by the runtime itself, so it has to run
|
|
750
|
+
if (!await RuntimeUtils.ensureRuntimeIsRunning()) {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
551
753
|
await RuntimeUtils.runtimeFetch(`/${serviceName}/project`, true, {
|
|
552
754
|
method: 'DELETE'
|
|
553
755
|
});
|
|
@@ -561,9 +763,11 @@ class RuntimeUtils {
|
|
|
561
763
|
}
|
|
562
764
|
}
|
|
563
765
|
*/
|
|
766
|
+
return true;
|
|
564
767
|
}
|
|
565
768
|
//#endregion
|
|
566
769
|
//#region Test report helpers
|
|
770
|
+
/** Writes the report of a run to `destPath` (merged into the previous one for a partial run) and returns it, undefined when the runtime wrote none */
|
|
567
771
|
static async persistTestReport(sourcePath, destPath, isPartial) {
|
|
568
772
|
let newReport;
|
|
569
773
|
try {
|
|
@@ -572,11 +776,11 @@ class RuntimeUtils {
|
|
|
572
776
|
}
|
|
573
777
|
catch {
|
|
574
778
|
console.warn(chalk_1.default.yellow("Could not read test report from runtime output."));
|
|
575
|
-
return;
|
|
779
|
+
return undefined;
|
|
576
780
|
}
|
|
577
781
|
if (!isPartial) {
|
|
578
782
|
await promises_1.default.writeFile(destPath, JSON.stringify(newReport, null, 2), "utf8");
|
|
579
|
-
return;
|
|
783
|
+
return newReport;
|
|
580
784
|
}
|
|
581
785
|
// Merge partial results into any existing report
|
|
582
786
|
let existingReport = null;
|
|
@@ -591,6 +795,7 @@ class RuntimeUtils {
|
|
|
591
795
|
? RuntimeUtils.mergeTestReports(existingReport, newReport)
|
|
592
796
|
: newReport;
|
|
593
797
|
await promises_1.default.writeFile(destPath, JSON.stringify(merged, null, 2), "utf8");
|
|
798
|
+
return merged;
|
|
594
799
|
}
|
|
595
800
|
static mergeTestReports(existing, partial) {
|
|
596
801
|
const updatedTests = [...existing.Tests];
|