@codenotch/codenotch.cli 1.0.53 → 1.0.55
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 +12 -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/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.js +49 -36
- 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/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 +71 -10
- package/dist/utils/RuntimeUtils.js +218 -69
- 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 +10 -9
- package/dist/utils/TemplateUtils.js.map +1 -1
- package/package.json +4 -1
- package/resources/docs/project.md +21 -8
|
@@ -1,4 +1,26 @@
|
|
|
1
|
-
import { ProjectState } from "../models/Codenotch";
|
|
1
|
+
import { IProcessTestReport, ProjectState } from "../models/Codenotch";
|
|
2
|
+
/** Outcome of a deployment on the runtime, the errors and warnings being the ones the runtime reported */
|
|
3
|
+
export interface IDeployResult {
|
|
4
|
+
success: boolean;
|
|
5
|
+
serviceName: string;
|
|
6
|
+
/** Status the runtime ended in, undefined when nothing reached it */
|
|
7
|
+
status?: string;
|
|
8
|
+
errors: string[];
|
|
9
|
+
warnings: string[];
|
|
10
|
+
}
|
|
11
|
+
/** Outcome of a refresh: the compilation errors when it stopped there, the deployment ones otherwise */
|
|
12
|
+
export interface IRefreshResult extends IDeployResult {
|
|
13
|
+
/** 'deploy' when the runtime did not hold the project yet, 'update' otherwise */
|
|
14
|
+
mode?: 'deploy' | 'update';
|
|
15
|
+
/** Version the runtime held before an update */
|
|
16
|
+
previousVersion?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Outcome of a test run: the report the runtime wrote, when it wrote one */
|
|
19
|
+
export interface ITestResult {
|
|
20
|
+
report?: IProcessTestReport;
|
|
21
|
+
/** Where the report of the project is persisted */
|
|
22
|
+
reportPath: string;
|
|
23
|
+
}
|
|
2
24
|
export default abstract class RuntimeUtils {
|
|
3
25
|
static readonly CODENOTCH_INSTALL_DIR: string;
|
|
4
26
|
static readonly CODENOTCH_DATA_FOLDER = "data";
|
|
@@ -7,20 +29,43 @@ export default abstract class RuntimeUtils {
|
|
|
7
29
|
private static readonly DEFAULT_RUNTIME_HOST;
|
|
8
30
|
private static readonly DEFAULT_RUNTIME_PORT;
|
|
9
31
|
private static readonly ADMIN_USER_EMAIL;
|
|
32
|
+
/** A deployment the runtime does not conclude within this delay is reported as failed rather than waited for ever */
|
|
33
|
+
private static readonly DEPLOY_TIMEOUT_MS;
|
|
10
34
|
private static readRuntimeMeta;
|
|
11
35
|
private static writeRuntimeMeta;
|
|
12
36
|
static getRuntimeBaseUrl(): Promise<string>;
|
|
13
37
|
static runtimeFetch<T>(path: string, includeToken?: boolean, options?: RequestInit): Promise<T>;
|
|
14
38
|
private static getAdminToken;
|
|
15
|
-
|
|
16
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Makes sure the runtime is installed, proposing to install it when it is not.
|
|
41
|
+
* Outside a terminal the proposal is accepted, so scripts and CI keep working unattended.
|
|
42
|
+
* Returns false when the user declined, after a message telling how to install it.
|
|
43
|
+
*/
|
|
44
|
+
static ensureRuntimeInstalled(): Promise<boolean>;
|
|
45
|
+
/**
|
|
46
|
+
* Makes sure the runtime is running, proposing to install it and/or start it when it is not.
|
|
47
|
+
* Outside a terminal the proposal is accepted, so scripts and CI keep working unattended.
|
|
48
|
+
* Returns false when the user declined, after a message telling how to do it by hand.
|
|
49
|
+
*/
|
|
50
|
+
static ensureRuntimeIsRunning(options?: {
|
|
51
|
+
port?: number;
|
|
52
|
+
dataPath?: string;
|
|
53
|
+
}): Promise<boolean>;
|
|
17
54
|
static isRuntimeInstalled(): Promise<boolean>;
|
|
18
55
|
static isRuntimeRunning(): Promise<boolean>;
|
|
19
56
|
static runRuntimeExecutable(args: string[] | undefined, detached: boolean, envVars?: {
|
|
20
57
|
[key: string]: string;
|
|
21
58
|
}): Promise<string>;
|
|
22
59
|
static getRuntimeExecutablePath(): string;
|
|
23
|
-
|
|
60
|
+
/** "v0.1.5", "0.1.5 " and "V0.1.5" name the same release */
|
|
61
|
+
private static normalizeVersion;
|
|
62
|
+
/**
|
|
63
|
+
* Installs the latest runtime release, or updates the installed runtime to it when it is newer.
|
|
64
|
+
* Returns false when the installed runtime already is that release (`force` reinstalls it anyway).
|
|
65
|
+
*/
|
|
66
|
+
static installLatestRuntime(options?: {
|
|
67
|
+
force?: boolean;
|
|
68
|
+
}): Promise<boolean>;
|
|
24
69
|
static startRuntime(options?: {
|
|
25
70
|
port?: number;
|
|
26
71
|
attach?: boolean;
|
|
@@ -35,7 +80,11 @@ export default abstract class RuntimeUtils {
|
|
|
35
80
|
createdAt: string;
|
|
36
81
|
url: string;
|
|
37
82
|
}>;
|
|
38
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Build, deploy and start the project on the runtime, proposing to install/start the runtime when needed.
|
|
85
|
+
* Returns false when the runtime was declined or the deployment failed.
|
|
86
|
+
*/
|
|
87
|
+
static startProject(projectPath: string): Promise<boolean>;
|
|
39
88
|
/**
|
|
40
89
|
* Redeploy the project on the runtime: the sources are packaged and uploaded, the runtime
|
|
41
90
|
* restarts the service with them. The runtime is started when it is not running yet, and a
|
|
@@ -44,16 +93,28 @@ export default abstract class RuntimeUtils {
|
|
|
44
93
|
*/
|
|
45
94
|
static refreshProject(projectPath: string, options?: {
|
|
46
95
|
ignoreErrors?: boolean;
|
|
47
|
-
}): Promise<
|
|
96
|
+
}): Promise<IRefreshResult>;
|
|
48
97
|
/**
|
|
49
98
|
* The deployment state the runtime holds for a service, undefined when it does not know it
|
|
50
99
|
* (never deployed, removed since, or the runtime cannot be reached).
|
|
51
100
|
*/
|
|
52
101
|
static getProjectState(serviceName: string): Promise<ProjectState | undefined>;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Runs the tests of the project with a separate instance of the runtime, proposing to install it when needed.
|
|
104
|
+
* Returns false when the runtime was declined.
|
|
105
|
+
*/
|
|
106
|
+
static testProject(projectPath: string, fileFilter?: string, nameFilter?: string, inPlace?: boolean): Promise<ITestResult | undefined>;
|
|
107
|
+
/**
|
|
108
|
+
* Runs the tests and persists their report. A run the runtime ends in error still yields its
|
|
109
|
+
* report when it wrote one: failing tests are told by the report, not by the exit code.
|
|
110
|
+
*/
|
|
111
|
+
private static runTestsAndPersist;
|
|
112
|
+
static deployProject(serviceName: string, packagePath: string): Promise<IDeployResult>;
|
|
113
|
+
/** Uninstalls the project of the given folder from the runtime, false when nothing was removed. */
|
|
114
|
+
static removeProject(projectPath: string): Promise<boolean>;
|
|
115
|
+
/** Uninstalls the project from the runtime, false when nothing was removed. */
|
|
116
|
+
static uninstallProject(serviceName: string): Promise<boolean>;
|
|
117
|
+
/** 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 */
|
|
57
118
|
private static persistTestReport;
|
|
58
119
|
private static mergeTestReports;
|
|
59
120
|
}
|
|
@@ -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,25 +487,31 @@ 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...`));
|
|
413
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
|
|
414
|
-
|
|
415
|
-
if (
|
|
416
|
-
|
|
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.`));
|
|
417
510
|
}
|
|
418
511
|
else {
|
|
419
|
-
|
|
512
|
+
ConsoleUtils_1.default.error(`Project start failed.`);
|
|
420
513
|
}
|
|
514
|
+
return deployed.success;
|
|
421
515
|
}
|
|
422
516
|
finally {
|
|
423
517
|
// Remove the build
|
|
@@ -432,39 +526,51 @@ class RuntimeUtils {
|
|
|
432
526
|
*/
|
|
433
527
|
static async refreshProject(projectPath, options) {
|
|
434
528
|
const serviceName = await ProjectUtils_1.default.getProjectServiceName(projectPath);
|
|
435
|
-
|
|
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
|
|
436
531
|
if (!await RuntimeUtils.isRuntimeRunning()) {
|
|
437
|
-
|
|
438
|
-
|
|
532
|
+
if (!await RuntimeUtils.ensureRuntimeIsRunning()) {
|
|
533
|
+
result.errors.push('Codenotch runtime is not running');
|
|
534
|
+
return result;
|
|
535
|
+
}
|
|
439
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
|
|
440
537
|
}
|
|
441
538
|
// What the runtime holds right now, only to tell a first deployment from an update
|
|
442
539
|
const currentState = await RuntimeUtils.getProjectState(serviceName);
|
|
540
|
+
result.mode = currentState === undefined ? 'deploy' : 'update';
|
|
541
|
+
result.previousVersion = currentState?.projectVersion;
|
|
443
542
|
const outputPath = node_path_1.default.join(node_os_1.default.tmpdir(), `cn-refresh-${Date.now()}.zip`);
|
|
444
543
|
try {
|
|
445
|
-
|
|
544
|
+
ConsoleUtils_1.default.log(chalk_1.default.blue(`Building project "${serviceName}"...`));
|
|
446
545
|
try {
|
|
447
|
-
await ProjectUtils_1.default.packageProject(projectPath, outputPath, options?.ignoreErrors ?? false);
|
|
546
|
+
const compilation = await ProjectUtils_1.default.packageProject(projectPath, outputPath, options?.ignoreErrors ?? false);
|
|
547
|
+
result.warnings.push(...compilation.warnings);
|
|
448
548
|
}
|
|
449
549
|
catch (error) {
|
|
450
550
|
// The compilation errors are listed by the packaging, only the way out is left to say
|
|
451
|
-
|
|
452
|
-
|
|
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
|
|
453
555
|
? `Nothing was deployed, the runtime still serves the version it holds.`
|
|
454
|
-
: `Nothing was deployed. Fix the errors above, or run with '--ignore-errors' to deploy anyway.`)
|
|
455
|
-
return
|
|
556
|
+
: `Nothing was deployed. Fix the errors above, or run with '--ignore-errors' to deploy anyway.`);
|
|
557
|
+
return result;
|
|
456
558
|
}
|
|
457
|
-
|
|
559
|
+
ConsoleUtils_1.default.log(chalk_1.default.blue(currentState === undefined
|
|
458
560
|
? `Project "${serviceName}" is not on the runtime yet, deploying it...`
|
|
459
561
|
: `Deploying the changes of "${serviceName}" (deployed version ${currentState.projectVersion})...`));
|
|
460
562
|
const deployed = await RuntimeUtils.deployProject(serviceName, outputPath);
|
|
461
|
-
|
|
462
|
-
|
|
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.`));
|
|
463
569
|
}
|
|
464
570
|
else {
|
|
465
|
-
|
|
571
|
+
ConsoleUtils_1.default.error(`Project refresh failed.`);
|
|
466
572
|
}
|
|
467
|
-
return
|
|
573
|
+
return result;
|
|
468
574
|
}
|
|
469
575
|
finally {
|
|
470
576
|
// Remove the build
|
|
@@ -483,8 +589,14 @@ class RuntimeUtils {
|
|
|
483
589
|
return undefined;
|
|
484
590
|
}
|
|
485
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
|
+
*/
|
|
486
596
|
static async testProject(projectPath, fileFilter, nameFilter, inPlace = false) {
|
|
487
|
-
await RuntimeUtils.ensureRuntimeInstalled()
|
|
597
|
+
if (!await RuntimeUtils.ensureRuntimeInstalled()) {
|
|
598
|
+
return undefined;
|
|
599
|
+
}
|
|
488
600
|
// For testing we use a separate instance of the runtime
|
|
489
601
|
// We run it in attach mode so we get the results in the console
|
|
490
602
|
const isPartial = !!(fileFilter || nameFilter);
|
|
@@ -532,13 +644,12 @@ class RuntimeUtils {
|
|
|
532
644
|
await compileFunc(projectPath);
|
|
533
645
|
const tempReportPath = node_path_1.default.join(node_os_1.default.tmpdir(), `cn-report-${Date.now()}.json`);
|
|
534
646
|
try {
|
|
535
|
-
await testFunc
|
|
536
|
-
|
|
647
|
+
const report = await RuntimeUtils.runTestsAndPersist(testFunc, projectPath, tempReportPath, finalReportPath, isPartial);
|
|
648
|
+
return { report, reportPath: finalReportPath };
|
|
537
649
|
}
|
|
538
650
|
finally {
|
|
539
651
|
await promises_1.default.unlink(tempReportPath).catch(() => { });
|
|
540
652
|
}
|
|
541
|
-
return;
|
|
542
653
|
}
|
|
543
654
|
const tempRoot = await promises_1.default.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), "codenotch-"));
|
|
544
655
|
try {
|
|
@@ -546,8 +657,8 @@ class RuntimeUtils {
|
|
|
546
657
|
await FileUtils_1.default.copyFolder(projectPath, tempRoot, ['.git', 'node_modules', 'dist', 'build', 'out']);
|
|
547
658
|
await compileFunc(tempRoot);
|
|
548
659
|
const tempReportPath = node_path_1.default.join(tempRoot, "test-report.json");
|
|
549
|
-
await testFunc
|
|
550
|
-
|
|
660
|
+
const report = await RuntimeUtils.runTestsAndPersist(testFunc, tempRoot, tempReportPath, finalReportPath, isPartial);
|
|
661
|
+
return { report, reportPath: finalReportPath };
|
|
551
662
|
}
|
|
552
663
|
finally {
|
|
553
664
|
// Clean up temp folder
|
|
@@ -555,19 +666,38 @@ class RuntimeUtils {
|
|
|
555
666
|
await FileUtils_1.default.deleteRecursively(tempRoot);
|
|
556
667
|
}
|
|
557
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
|
+
}
|
|
558
687
|
static async deployProject(serviceName, packagePath) {
|
|
688
|
+
const result = { success: false, serviceName, errors: [], warnings: [] };
|
|
559
689
|
// We assume the project is already built and packaged, we just need to deploy it to the runtime
|
|
560
|
-
// Get the information about the project package, we assume it's in the current directory and named "project.zip"
|
|
561
690
|
const zipFilePath = packagePath;
|
|
562
691
|
try {
|
|
563
692
|
await promises_1.default.access(zipFilePath);
|
|
564
693
|
}
|
|
565
694
|
catch (error) {
|
|
566
|
-
console.log(chalk_1.default.red(`Project package not found: ${zipFilePath}`));
|
|
567
695
|
throw new Error(`Project package not found: ${zipFilePath}`);
|
|
568
696
|
}
|
|
569
|
-
|
|
570
|
-
|
|
697
|
+
if (!await RuntimeUtils.ensureRuntimeIsRunning()) {
|
|
698
|
+
result.errors.push('Codenotch runtime is not running');
|
|
699
|
+
return result;
|
|
700
|
+
}
|
|
571
701
|
const zipBuffer = await promises_1.default.readFile(zipFilePath);
|
|
572
702
|
const zipBlob = new Blob([zipBuffer], { type: "application/zip" });
|
|
573
703
|
const form = new FormData();
|
|
@@ -577,33 +707,49 @@ class RuntimeUtils {
|
|
|
577
707
|
method: "POST",
|
|
578
708
|
body: form
|
|
579
709
|
});
|
|
580
|
-
// Wait until the project deployment has completed
|
|
581
|
-
|
|
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) {
|
|
582
713
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
583
714
|
try {
|
|
584
715
|
const projectState = await RuntimeUtils.runtimeFetch(`/${serviceName}/project`, true);
|
|
716
|
+
result.status = projectState.status;
|
|
717
|
+
result.warnings = projectState.warnings ?? [];
|
|
585
718
|
if (projectState.status === "READY") {
|
|
586
|
-
|
|
587
|
-
|
|
719
|
+
ConsoleUtils_1.default.log(chalk_1.default.green(`${serviceName} is ready!`));
|
|
720
|
+
result.success = true;
|
|
721
|
+
return result;
|
|
588
722
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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;
|
|
592
727
|
}
|
|
593
728
|
}
|
|
594
729
|
catch {
|
|
595
730
|
// Keep waiting
|
|
596
731
|
}
|
|
597
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;
|
|
598
736
|
}
|
|
737
|
+
/** Uninstalls the project of the given folder from the runtime, false when nothing was removed. */
|
|
599
738
|
static async removeProject(projectPath) {
|
|
600
739
|
const serviceName = await ProjectUtils_1.default.getProjectServiceName(projectPath);
|
|
601
|
-
await RuntimeUtils.uninstallProject(serviceName);
|
|
740
|
+
return await RuntimeUtils.uninstallProject(serviceName);
|
|
602
741
|
}
|
|
742
|
+
/** Uninstalls the project from the runtime, false when nothing was removed. */
|
|
603
743
|
static async uninstallProject(serviceName) {
|
|
604
|
-
//
|
|
605
|
-
|
|
606
|
-
|
|
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
|
+
}
|
|
607
753
|
await RuntimeUtils.runtimeFetch(`/${serviceName}/project`, true, {
|
|
608
754
|
method: 'DELETE'
|
|
609
755
|
});
|
|
@@ -617,9 +763,11 @@ class RuntimeUtils {
|
|
|
617
763
|
}
|
|
618
764
|
}
|
|
619
765
|
*/
|
|
766
|
+
return true;
|
|
620
767
|
}
|
|
621
768
|
//#endregion
|
|
622
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 */
|
|
623
771
|
static async persistTestReport(sourcePath, destPath, isPartial) {
|
|
624
772
|
let newReport;
|
|
625
773
|
try {
|
|
@@ -628,11 +776,11 @@ class RuntimeUtils {
|
|
|
628
776
|
}
|
|
629
777
|
catch {
|
|
630
778
|
console.warn(chalk_1.default.yellow("Could not read test report from runtime output."));
|
|
631
|
-
return;
|
|
779
|
+
return undefined;
|
|
632
780
|
}
|
|
633
781
|
if (!isPartial) {
|
|
634
782
|
await promises_1.default.writeFile(destPath, JSON.stringify(newReport, null, 2), "utf8");
|
|
635
|
-
return;
|
|
783
|
+
return newReport;
|
|
636
784
|
}
|
|
637
785
|
// Merge partial results into any existing report
|
|
638
786
|
let existingReport = null;
|
|
@@ -647,6 +795,7 @@ class RuntimeUtils {
|
|
|
647
795
|
? RuntimeUtils.mergeTestReports(existingReport, newReport)
|
|
648
796
|
: newReport;
|
|
649
797
|
await promises_1.default.writeFile(destPath, JSON.stringify(merged, null, 2), "utf8");
|
|
798
|
+
return merged;
|
|
650
799
|
}
|
|
651
800
|
static mergeTestReports(existing, partial) {
|
|
652
801
|
const updatedTests = [...existing.Tests];
|