ic-mops 1.1.0-pre.0 → 1.1.1
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/CHANGELOG.md +13 -0
- package/bin/moc-wrapper.sh +1 -1
- package/bundle/cli.tgz +0 -0
- package/check-requirements.ts +1 -1
- package/cli.ts +18 -0
- package/commands/bench.ts +1 -1
- package/commands/install/install-all.ts +2 -2
- package/commands/replica.ts +33 -3
- package/commands/sources.ts +1 -1
- package/commands/sync.ts +4 -19
- package/commands/test/mmf1.ts +4 -0
- package/commands/test/reporters/silent-reporter.ts +22 -4
- package/commands/test/test.ts +74 -10
- package/commands/watch/deployer.ts +155 -0
- package/commands/watch/error-checker.ts +87 -0
- package/commands/watch/generator.ts +99 -0
- package/commands/watch/globMoFiles.ts +16 -0
- package/commands/watch/parseDfxJson.ts +64 -0
- package/commands/watch/tester.ts +81 -0
- package/commands/watch/warning-checker.ts +133 -0
- package/commands/watch/watch.ts +90 -0
- package/declarations/main/main.did +16 -10
- package/declarations/main/main.did.d.ts +19 -10
- package/declarations/main/main.did.js +25 -11
- package/dist/bin/moc-wrapper.sh +1 -1
- package/dist/check-requirements.js +1 -1
- package/dist/cli.js +16 -0
- package/dist/commands/bench.js +1 -1
- package/dist/commands/install/install-all.js +2 -2
- package/dist/commands/replica.d.ts +2 -2
- package/dist/commands/replica.js +26 -3
- package/dist/commands/sources.d.ts +1 -1
- package/dist/commands/sources.js +1 -1
- package/dist/commands/sync.js +3 -18
- package/dist/commands/test/mmf1.d.ts +1 -0
- package/dist/commands/test/mmf1.js +3 -0
- package/dist/commands/test/reporters/silent-reporter.d.ts +6 -1
- package/dist/commands/test/reporters/silent-reporter.js +18 -5
- package/dist/commands/test/test.d.ts +1 -1
- package/dist/commands/test/test.js +62 -10
- package/dist/commands/watch/deployer.d.ts +24 -0
- package/dist/commands/watch/deployer.js +125 -0
- package/dist/commands/watch/error-checker.d.ts +13 -0
- package/dist/commands/watch/error-checker.js +76 -0
- package/dist/commands/watch/generator.d.ts +21 -0
- package/dist/commands/watch/generator.js +79 -0
- package/dist/commands/watch/globMoFiles.d.ts +1 -0
- package/dist/commands/watch/globMoFiles.js +14 -0
- package/dist/commands/watch/parseDfxJson.d.ts +2 -0
- package/dist/commands/watch/parseDfxJson.js +22 -0
- package/dist/commands/watch/tester.d.ts +19 -0
- package/dist/commands/watch/tester.js +63 -0
- package/dist/commands/watch/warning-checker.d.ts +20 -0
- package/dist/commands/watch/warning-checker.js +111 -0
- package/dist/commands/watch/watch.d.ts +7 -0
- package/dist/commands/watch/watch.js +79 -0
- package/dist/declarations/main/main.did +16 -10
- package/dist/declarations/main/main.did.d.ts +19 -10
- package/dist/declarations/main/main.did.js +25 -11
- package/dist/helpers/get-moc-path.d.ts +1 -1
- package/dist/helpers/get-moc-path.js +17 -3
- package/dist/helpers/get-moc-version.d.ts +1 -1
- package/dist/helpers/get-moc-version.js +17 -5
- package/dist/integrity.js +3 -2
- package/dist/package.json +3 -2
- package/dist/parallel.d.ts +1 -1
- package/dist/templates/mops-test.yml +4 -2
- package/helpers/get-moc-path.ts +19 -3
- package/helpers/get-moc-version.ts +17 -5
- package/integrity.ts +3 -2
- package/package.json +3 -2
- package/parallel.ts +2 -2
- package/templates/mops-test.yml +4 -2
- package/test +0 -4
|
@@ -59,13 +59,22 @@ export async function test(filter = '', options = {}) {
|
|
|
59
59
|
process.exit(0);
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
|
+
else {
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
62
65
|
});
|
|
63
66
|
// todo: run only changed for *.test.mo?
|
|
64
67
|
// todo: run all for *.mo?
|
|
68
|
+
let curRun = Promise.resolve(true);
|
|
69
|
+
let controller = new AbortController();
|
|
65
70
|
let run = debounce(async () => {
|
|
71
|
+
controller.abort();
|
|
72
|
+
await curRun;
|
|
66
73
|
console.clear();
|
|
67
74
|
process.stdout.write('\x1Bc');
|
|
68
|
-
|
|
75
|
+
controller = new AbortController();
|
|
76
|
+
curRun = runAll(options.reporter, filter, options.mode, replicaType, true, controller.signal);
|
|
77
|
+
await curRun;
|
|
69
78
|
console.log('-'.repeat(50));
|
|
70
79
|
console.log('Waiting for file changes...');
|
|
71
80
|
console.log(chalk.gray((`Press ${chalk.gray('Ctrl+C')} to exit.`)));
|
|
@@ -91,11 +100,11 @@ export async function test(filter = '', options = {}) {
|
|
|
91
100
|
}
|
|
92
101
|
let mocPath = '';
|
|
93
102
|
let wasmtimePath = '';
|
|
94
|
-
async function runAll(reporterName, filter = '', mode = 'interpreter', replicaType, watch = false) {
|
|
95
|
-
let done = await testWithReporter(reporterName, filter, mode, replicaType, watch);
|
|
103
|
+
async function runAll(reporterName, filter = '', mode = 'interpreter', replicaType, watch = false, signal) {
|
|
104
|
+
let done = await testWithReporter(reporterName, filter, mode, replicaType, watch, signal);
|
|
96
105
|
return done;
|
|
97
106
|
}
|
|
98
|
-
export async function testWithReporter(reporterName, filter = '', defaultMode = 'interpreter', replicaType, watch = false) {
|
|
107
|
+
export async function testWithReporter(reporterName, filter = '', defaultMode = 'interpreter', replicaType, watch = false, signal) {
|
|
99
108
|
let rootDir = getRootDir();
|
|
100
109
|
let files = [];
|
|
101
110
|
let libFiles = globSync('**/test?(s)/lib.mo', globConfig);
|
|
@@ -149,6 +158,9 @@ export async function testWithReporter(reporterName, filter = '', defaultMode =
|
|
|
149
158
|
replica.dir = testTempDir;
|
|
150
159
|
fs.mkdirSync(testTempDir, { recursive: true });
|
|
151
160
|
await parallel(os.cpus().length, files, async (file) => {
|
|
161
|
+
if (signal?.aborted) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
152
164
|
let mmf = new MMF1('store', absToRel(file));
|
|
153
165
|
// mode overrides
|
|
154
166
|
let lines = fs.readFileSync(file, 'utf8').split('\n');
|
|
@@ -175,14 +187,26 @@ export async function testWithReporter(reporterName, filter = '', defaultMode =
|
|
|
175
187
|
let mocArgs = ['--hide-warnings', '--error-detail=2', ...sourcesArr.join(' ').split(' '), file].filter(x => x);
|
|
176
188
|
// interpret
|
|
177
189
|
if (mode === 'interpreter') {
|
|
178
|
-
let proc = spawn(mocPath, ['-r', '-ref-system-api', ...mocArgs]);
|
|
190
|
+
let proc = spawn(mocPath, ['-r', '-ref-system-api', ...mocArgs], { signal });
|
|
191
|
+
proc.addListener('error', (error) => {
|
|
192
|
+
if (error?.code === 'ABORT_ERR') {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
throw error;
|
|
196
|
+
});
|
|
179
197
|
pipeMMF(proc, mmf).then(resolve);
|
|
180
198
|
}
|
|
181
199
|
// build and run wasm
|
|
182
200
|
else if (mode === 'wasi') {
|
|
183
201
|
let wasmFile = `${path.join(testTempDir, path.parse(file).name)}.wasm`;
|
|
184
202
|
// build
|
|
185
|
-
let buildProc = spawn(mocPath, [`-o=${wasmFile}`, '-wasi-system-api', ...mocArgs]);
|
|
203
|
+
let buildProc = spawn(mocPath, [`-o=${wasmFile}`, '-wasi-system-api', ...mocArgs], { signal });
|
|
204
|
+
buildProc.addListener('error', (error) => {
|
|
205
|
+
if (error?.code === 'ABORT_ERR') {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
throw error;
|
|
209
|
+
});
|
|
186
210
|
pipeMMF(buildProc, mmf).then(async () => {
|
|
187
211
|
if (mmf.failed > 0) {
|
|
188
212
|
return;
|
|
@@ -204,7 +228,13 @@ export async function testWithReporter(reporterName, filter = '', defaultMode =
|
|
|
204
228
|
console.error(chalk.red('Minimum wasmtime version is 14.0.0. Please update wasmtime to the latest version'));
|
|
205
229
|
process.exit(1);
|
|
206
230
|
}
|
|
207
|
-
let proc = spawn(wasmtimePath, wasmtimeArgs);
|
|
231
|
+
let proc = spawn(wasmtimePath, wasmtimeArgs, { signal });
|
|
232
|
+
proc.addListener('error', (error) => {
|
|
233
|
+
if (error?.code === 'ABORT_ERR') {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
throw error;
|
|
237
|
+
});
|
|
208
238
|
await pipeMMF(proc, mmf);
|
|
209
239
|
}).finally(() => {
|
|
210
240
|
fs.rmSync(wasmFile, { force: true });
|
|
@@ -215,18 +245,30 @@ export async function testWithReporter(reporterName, filter = '', defaultMode =
|
|
|
215
245
|
// mmf.strategy = 'print'; // because we run replica tests one-by-one
|
|
216
246
|
let wasmFile = `${path.join(testTempDir, path.parse(file).name)}.wasm`;
|
|
217
247
|
// build
|
|
218
|
-
let buildProc = spawn(mocPath, [`-o=${wasmFile}`, ...mocArgs]);
|
|
248
|
+
let buildProc = spawn(mocPath, [`-o=${wasmFile}`, ...mocArgs], { signal });
|
|
249
|
+
buildProc.addListener('error', (error) => {
|
|
250
|
+
if (error?.code === 'ABORT_ERR') {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
throw error;
|
|
254
|
+
});
|
|
219
255
|
pipeMMF(buildProc, mmf).then(async () => {
|
|
220
256
|
if (mmf.failed > 0) {
|
|
221
257
|
return;
|
|
222
258
|
}
|
|
223
259
|
await startReplicaOnce(replica, replicaType);
|
|
260
|
+
if (signal?.aborted) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
224
263
|
let canisterName = path.parse(file).name;
|
|
225
264
|
let idlFactory = ({ IDL }) => {
|
|
226
265
|
return IDL.Service({ 'runTests': IDL.Func([], [], []) });
|
|
227
266
|
};
|
|
228
|
-
let
|
|
229
|
-
|
|
267
|
+
let canister = await replica.deploy(canisterName, wasmFile, idlFactory, undefined, signal);
|
|
268
|
+
if (signal?.aborted || !canister) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
pipeStdoutToMMF(canister.stream, mmf);
|
|
230
272
|
let actor = await replica.getActor(canisterName);
|
|
231
273
|
try {
|
|
232
274
|
if (globalThis.mopsReplicaTestRunning) {
|
|
@@ -239,6 +281,9 @@ export async function testWithReporter(reporterName, filter = '', defaultMode =
|
|
|
239
281
|
}, Math.random() * 1000 | 0);
|
|
240
282
|
});
|
|
241
283
|
}
|
|
284
|
+
if (signal?.aborted) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
242
287
|
globalThis.mopsReplicaTestRunning = true;
|
|
243
288
|
await actor.runTests();
|
|
244
289
|
globalThis.mopsReplicaTestRunning = false;
|
|
@@ -250,10 +295,14 @@ export async function testWithReporter(reporterName, filter = '', defaultMode =
|
|
|
250
295
|
stderrStream.write(e.message);
|
|
251
296
|
}
|
|
252
297
|
}).finally(async () => {
|
|
298
|
+
globalThis.mopsReplicaTestRunning = false;
|
|
253
299
|
fs.rmSync(wasmFile, { force: true });
|
|
254
300
|
}).then(resolve);
|
|
255
301
|
}
|
|
256
302
|
});
|
|
303
|
+
if (signal?.aborted) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
257
306
|
reporter.addRun(file, mmf, promise, mode);
|
|
258
307
|
await promise;
|
|
259
308
|
});
|
|
@@ -261,6 +310,9 @@ export async function testWithReporter(reporterName, filter = '', defaultMode =
|
|
|
261
310
|
await replica.stop();
|
|
262
311
|
fs.rmSync(testTempDir, { recursive: true, force: true });
|
|
263
312
|
}
|
|
313
|
+
if (signal?.aborted) {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
264
316
|
return reporter.done();
|
|
265
317
|
}
|
|
266
318
|
function pipeStdoutToMMF(stdout, mmf) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ErrorChecker } from './error-checker.js';
|
|
2
|
+
import { Generator } from './generator.js';
|
|
3
|
+
export declare class Deployer {
|
|
4
|
+
verbose: boolean;
|
|
5
|
+
canisters: Record<string, string>;
|
|
6
|
+
status: 'pending' | 'running' | 'syntax-error' | 'dfx-error' | 'error' | 'success';
|
|
7
|
+
errorChecker: ErrorChecker;
|
|
8
|
+
generator: Generator;
|
|
9
|
+
success: number;
|
|
10
|
+
errors: string[];
|
|
11
|
+
aborted: boolean;
|
|
12
|
+
controllers: Map<string, AbortController>;
|
|
13
|
+
currentRun: Promise<any> | undefined;
|
|
14
|
+
constructor({ verbose, canisters, errorChecker, generator }: {
|
|
15
|
+
verbose: boolean;
|
|
16
|
+
canisters: Record<string, string>;
|
|
17
|
+
errorChecker: ErrorChecker;
|
|
18
|
+
generator: Generator;
|
|
19
|
+
});
|
|
20
|
+
reset(): void;
|
|
21
|
+
abortCurrent(): Promise<void>;
|
|
22
|
+
run(onProgress: () => void): Promise<void>;
|
|
23
|
+
getOutput(): string;
|
|
24
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { execFile, execSync } from 'node:child_process';
|
|
5
|
+
import { parallel } from '../../parallel.js';
|
|
6
|
+
import { getRootDir } from '../../mops.js';
|
|
7
|
+
export class Deployer {
|
|
8
|
+
constructor({ verbose, canisters, errorChecker, generator }) {
|
|
9
|
+
this.verbose = false;
|
|
10
|
+
this.canisters = {};
|
|
11
|
+
this.status = 'pending';
|
|
12
|
+
this.success = 0;
|
|
13
|
+
this.errors = [];
|
|
14
|
+
this.aborted = false;
|
|
15
|
+
this.controllers = new Map();
|
|
16
|
+
this.verbose = verbose;
|
|
17
|
+
this.canisters = canisters;
|
|
18
|
+
this.errorChecker = errorChecker;
|
|
19
|
+
this.generator = generator;
|
|
20
|
+
}
|
|
21
|
+
reset() {
|
|
22
|
+
this.status = 'pending';
|
|
23
|
+
this.success = 0;
|
|
24
|
+
this.errors = [];
|
|
25
|
+
}
|
|
26
|
+
async abortCurrent() {
|
|
27
|
+
this.aborted = true;
|
|
28
|
+
for (let controller of this.controllers.values()) {
|
|
29
|
+
controller.abort();
|
|
30
|
+
}
|
|
31
|
+
this.controllers.clear();
|
|
32
|
+
await this.currentRun;
|
|
33
|
+
this.reset();
|
|
34
|
+
this.aborted = false;
|
|
35
|
+
}
|
|
36
|
+
async run(onProgress) {
|
|
37
|
+
await this.abortCurrent();
|
|
38
|
+
if (this.errorChecker.status === 'error') {
|
|
39
|
+
this.status = 'syntax-error';
|
|
40
|
+
onProgress();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (Object.keys(this.canisters).length === 0) {
|
|
44
|
+
this.status = 'success';
|
|
45
|
+
onProgress();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
let rootDir = getRootDir();
|
|
49
|
+
try {
|
|
50
|
+
execSync('dfx ping', { cwd: rootDir });
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
this.status = 'dfx-error';
|
|
54
|
+
onProgress();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.status = 'running';
|
|
58
|
+
onProgress();
|
|
59
|
+
// create canisters (sequentially to avoid DFX errors)
|
|
60
|
+
let resolve;
|
|
61
|
+
this.currentRun = new Promise((res) => resolve = res);
|
|
62
|
+
for (let canister of Object.keys(this.canisters)) {
|
|
63
|
+
let controller = new AbortController();
|
|
64
|
+
let { signal } = controller;
|
|
65
|
+
this.controllers.set(canister, controller);
|
|
66
|
+
await promisify(execFile)('dfx', ['canister', 'create', canister], { cwd: rootDir, signal }).catch((error) => {
|
|
67
|
+
if (error.code === 'ABORT_ERR') {
|
|
68
|
+
return { stderr: '' };
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
});
|
|
72
|
+
this.controllers.delete(canister);
|
|
73
|
+
}
|
|
74
|
+
resolve?.();
|
|
75
|
+
if (this.aborted) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
this.currentRun = parallel(os.cpus().length, [...Object.keys(this.canisters)], async (canister) => {
|
|
79
|
+
let controller = new AbortController();
|
|
80
|
+
let { signal } = controller;
|
|
81
|
+
this.controllers.set(canister, controller);
|
|
82
|
+
// build
|
|
83
|
+
if (this.generator.status !== 'success' || !this.generator.canisters[canister]) {
|
|
84
|
+
await promisify(execFile)('dfx', ['build', canister], { cwd: rootDir, signal }).catch((error) => {
|
|
85
|
+
if (error.code === 'ABORT_ERR') {
|
|
86
|
+
return { stderr: '' };
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// install
|
|
92
|
+
await promisify(execFile)('dfx', ['canister', 'install', '--mode=auto', '--yes', canister], { cwd: rootDir, signal }).catch((error) => {
|
|
93
|
+
if (error.code === 'ABORT_ERR') {
|
|
94
|
+
return { stderr: '' };
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
});
|
|
98
|
+
this.success += 1;
|
|
99
|
+
this.controllers.delete(canister);
|
|
100
|
+
onProgress();
|
|
101
|
+
});
|
|
102
|
+
await this.currentRun;
|
|
103
|
+
if (!this.aborted) {
|
|
104
|
+
this.status = 'success';
|
|
105
|
+
}
|
|
106
|
+
onProgress();
|
|
107
|
+
}
|
|
108
|
+
getOutput() {
|
|
109
|
+
let get = (v) => v.toString();
|
|
110
|
+
let count = (this.status === 'running' ? get : chalk.bold[this.errors.length > 0 ? 'redBright' : 'green'])(this.errors.length || this.success);
|
|
111
|
+
if (this.status === 'pending') {
|
|
112
|
+
return `Deploy: ${chalk.gray('(pending)')}`;
|
|
113
|
+
}
|
|
114
|
+
if (this.status === 'running') {
|
|
115
|
+
return `Deploy: ${count}/${Object.keys(this.canisters).length} ${chalk.gray('(running)')}`;
|
|
116
|
+
}
|
|
117
|
+
if (this.status === 'syntax-error') {
|
|
118
|
+
return `Deploy: ${chalk.gray('(errors)')}`;
|
|
119
|
+
}
|
|
120
|
+
if (this.status === 'dfx-error') {
|
|
121
|
+
return `Deploy: ${chalk.gray('(dfx not running)')}`;
|
|
122
|
+
}
|
|
123
|
+
return `Deploy: ${count}`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare class ErrorChecker {
|
|
2
|
+
verbose: boolean;
|
|
3
|
+
canisters: Record<string, string>;
|
|
4
|
+
status: 'pending' | 'running' | 'error' | 'success';
|
|
5
|
+
errors: string[];
|
|
6
|
+
constructor({ verbose, canisters }: {
|
|
7
|
+
verbose: boolean;
|
|
8
|
+
canisters: Record<string, string>;
|
|
9
|
+
});
|
|
10
|
+
reset(): void;
|
|
11
|
+
run(onProgress: () => void): Promise<void>;
|
|
12
|
+
getOutput(): string;
|
|
13
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { getMocPath } from '../../helpers/get-moc-path.js';
|
|
6
|
+
import { getRootDir } from '../../mops.js';
|
|
7
|
+
import { sources } from '../sources.js';
|
|
8
|
+
import { parallel } from '../../parallel.js';
|
|
9
|
+
import { globMoFiles } from './globMoFiles.js';
|
|
10
|
+
export class ErrorChecker {
|
|
11
|
+
constructor({ verbose, canisters }) {
|
|
12
|
+
this.verbose = false;
|
|
13
|
+
this.canisters = {};
|
|
14
|
+
this.status = 'pending';
|
|
15
|
+
this.errors = [];
|
|
16
|
+
this.verbose = verbose;
|
|
17
|
+
this.canisters = canisters;
|
|
18
|
+
}
|
|
19
|
+
reset() {
|
|
20
|
+
this.status = 'pending';
|
|
21
|
+
this.errors = [];
|
|
22
|
+
}
|
|
23
|
+
async run(onProgress) {
|
|
24
|
+
this.reset();
|
|
25
|
+
this.status = 'running';
|
|
26
|
+
onProgress();
|
|
27
|
+
let rootDir = getRootDir();
|
|
28
|
+
let mocPath = getMocPath();
|
|
29
|
+
let deps = await sources({ cwd: rootDir });
|
|
30
|
+
let paths = [...Object.values(this.canisters)];
|
|
31
|
+
if (!paths.length) {
|
|
32
|
+
paths = globMoFiles(rootDir);
|
|
33
|
+
}
|
|
34
|
+
await parallel(os.cpus().length, paths, async (file) => {
|
|
35
|
+
try {
|
|
36
|
+
await promisify(execFile)(mocPath, ['--check', ...deps.flatMap(x => x.split(' ')), file], { cwd: rootDir });
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
error.message.split('\n').forEach((line) => {
|
|
40
|
+
if (line.match(/: \w+ error \[/)) {
|
|
41
|
+
// better formatting
|
|
42
|
+
let str = line
|
|
43
|
+
.replace(/: (\w+ error) \[/, (_, m1) => `: ${chalk.red(m1)} [`)
|
|
44
|
+
.replace(/unbound type (\w+)/, `unbound type ${chalk.bold('$1')}`)
|
|
45
|
+
.replace(/unbound variable (\w+)/, `unbound variable ${chalk.bold('$1')}`)
|
|
46
|
+
.trim();
|
|
47
|
+
this.errors.push(str);
|
|
48
|
+
}
|
|
49
|
+
else if (line.startsWith(' ') && this.errors.length) {
|
|
50
|
+
this.errors[this.errors.length - 1] += '\n ' + line;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// console.log('UNKNOWN ERROR', line);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
onProgress();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
this.status = this.errors.length ? 'error' : 'success';
|
|
60
|
+
onProgress();
|
|
61
|
+
}
|
|
62
|
+
getOutput() {
|
|
63
|
+
if (this.status === 'pending') {
|
|
64
|
+
return `Errors: ${chalk.gray('(pending)')}`;
|
|
65
|
+
}
|
|
66
|
+
if (this.status === 'running') {
|
|
67
|
+
return `Errors: ${chalk.gray('(running)')}`;
|
|
68
|
+
}
|
|
69
|
+
let output = '';
|
|
70
|
+
output += `Errors: ${chalk.bold[this.errors.length ? 'redBright' : 'green'](this.errors.length)}`;
|
|
71
|
+
if (this.verbose && this.errors.length) {
|
|
72
|
+
output += `\n ${this.errors.join('\n ')}`;
|
|
73
|
+
}
|
|
74
|
+
return output;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ErrorChecker } from './error-checker.js';
|
|
2
|
+
export declare class Generator {
|
|
3
|
+
verbose: boolean;
|
|
4
|
+
canisters: Record<string, string>;
|
|
5
|
+
status: 'pending' | 'running' | 'syntax-error' | 'error' | 'success';
|
|
6
|
+
errorChecker: ErrorChecker;
|
|
7
|
+
success: number;
|
|
8
|
+
errors: string[];
|
|
9
|
+
aborted: boolean;
|
|
10
|
+
controllers: Map<string, AbortController>;
|
|
11
|
+
currentRun: Promise<any> | undefined;
|
|
12
|
+
constructor({ verbose, canisters, errorChecker }: {
|
|
13
|
+
verbose: boolean;
|
|
14
|
+
canisters: Record<string, string>;
|
|
15
|
+
errorChecker: ErrorChecker;
|
|
16
|
+
});
|
|
17
|
+
reset(): void;
|
|
18
|
+
abortCurrent(): Promise<void>;
|
|
19
|
+
run(onProgress: () => void): Promise<void>;
|
|
20
|
+
getOutput(): string;
|
|
21
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { execFile } from 'node:child_process';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { parallel } from '../../parallel.js';
|
|
6
|
+
import { getRootDir } from '../../mops.js';
|
|
7
|
+
export class Generator {
|
|
8
|
+
constructor({ verbose, canisters, errorChecker }) {
|
|
9
|
+
this.verbose = false;
|
|
10
|
+
this.canisters = {};
|
|
11
|
+
this.status = 'pending';
|
|
12
|
+
this.success = 0;
|
|
13
|
+
this.errors = [];
|
|
14
|
+
this.aborted = false;
|
|
15
|
+
this.controllers = new Map();
|
|
16
|
+
this.verbose = verbose;
|
|
17
|
+
this.canisters = canisters;
|
|
18
|
+
this.errorChecker = errorChecker;
|
|
19
|
+
}
|
|
20
|
+
reset() {
|
|
21
|
+
this.status = 'pending';
|
|
22
|
+
this.success = 0;
|
|
23
|
+
this.errors = [];
|
|
24
|
+
}
|
|
25
|
+
async abortCurrent() {
|
|
26
|
+
this.aborted = true;
|
|
27
|
+
for (let controller of this.controllers.values()) {
|
|
28
|
+
controller.abort();
|
|
29
|
+
}
|
|
30
|
+
this.controllers.clear();
|
|
31
|
+
await this.currentRun;
|
|
32
|
+
this.reset();
|
|
33
|
+
this.aborted = false;
|
|
34
|
+
}
|
|
35
|
+
async run(onProgress) {
|
|
36
|
+
await this.abortCurrent();
|
|
37
|
+
if (this.errorChecker.status === 'error') {
|
|
38
|
+
this.status = 'syntax-error';
|
|
39
|
+
onProgress();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
this.status = 'running';
|
|
43
|
+
onProgress();
|
|
44
|
+
let rootDir = getRootDir();
|
|
45
|
+
this.currentRun = parallel(os.cpus().length, [...Object.keys(this.canisters)], async (canister) => {
|
|
46
|
+
let controller = new AbortController();
|
|
47
|
+
let { signal } = controller;
|
|
48
|
+
this.controllers.set(canister, controller);
|
|
49
|
+
await promisify(execFile)('dfx', ['generate', canister], { cwd: rootDir, signal }).catch((error) => {
|
|
50
|
+
if (error.code === 'ABORT_ERR') {
|
|
51
|
+
return { stderr: '' };
|
|
52
|
+
}
|
|
53
|
+
throw error;
|
|
54
|
+
});
|
|
55
|
+
this.success += 1;
|
|
56
|
+
this.controllers.delete(canister);
|
|
57
|
+
onProgress();
|
|
58
|
+
});
|
|
59
|
+
await this.currentRun;
|
|
60
|
+
if (!this.aborted) {
|
|
61
|
+
this.status = 'success';
|
|
62
|
+
}
|
|
63
|
+
onProgress();
|
|
64
|
+
}
|
|
65
|
+
getOutput() {
|
|
66
|
+
let get = (v) => v.toString();
|
|
67
|
+
let count = (this.status === 'running' ? get : chalk.bold[this.errors.length > 0 ? 'redBright' : 'green'])(this.errors.length || this.success);
|
|
68
|
+
if (this.status === 'pending') {
|
|
69
|
+
return `Generate: ${chalk.gray('(pending)')}`;
|
|
70
|
+
}
|
|
71
|
+
if (this.status === 'running') {
|
|
72
|
+
return `Generate: ${count}/${Object.keys(this.canisters).length} ${chalk.gray('(running)')}`;
|
|
73
|
+
}
|
|
74
|
+
if (this.status === 'syntax-error') {
|
|
75
|
+
return `Generate: ${chalk.gray('(errors)')}`;
|
|
76
|
+
}
|
|
77
|
+
return `Generate: ${count}`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function globMoFiles(rootDir: string): string[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { globSync } from 'glob';
|
|
2
|
+
let globConfig = {
|
|
3
|
+
nocase: true,
|
|
4
|
+
ignore: [
|
|
5
|
+
'**/node_modules/**',
|
|
6
|
+
'**/.mops/**',
|
|
7
|
+
'**/.vessel/**',
|
|
8
|
+
'**/.git/**',
|
|
9
|
+
],
|
|
10
|
+
};
|
|
11
|
+
export function globMoFiles(rootDir) {
|
|
12
|
+
// return globSync('{src,test?(s)}/**/*.mo', {cwd: rootDir, ...globConfig});
|
|
13
|
+
return globSync('src/**/*.mo', { cwd: rootDir, ...globConfig });
|
|
14
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getRootDir } from '../../mops.js';
|
|
4
|
+
function readDfxJson() {
|
|
5
|
+
let dfxJsonPath = path.resolve(getRootDir(), 'dfx.json');
|
|
6
|
+
if (!existsSync(dfxJsonPath)) {
|
|
7
|
+
return {};
|
|
8
|
+
}
|
|
9
|
+
return JSON.parse(readFileSync(dfxJsonPath, 'utf8'));
|
|
10
|
+
}
|
|
11
|
+
export function getMotokoCanisters() {
|
|
12
|
+
let dfxJson = readDfxJson();
|
|
13
|
+
return Object.fromEntries(Object.entries(dfxJson.canisters)
|
|
14
|
+
.filter(([_, canister]) => canister.type === 'motoko')
|
|
15
|
+
.map(([name, canister]) => [name, canister.main ?? '']));
|
|
16
|
+
}
|
|
17
|
+
export function getMotokoCanistersWithDeclarations() {
|
|
18
|
+
let dfxJson = readDfxJson();
|
|
19
|
+
return Object.fromEntries(Object.entries(dfxJson.canisters)
|
|
20
|
+
.filter(([_, canister]) => canister.type === 'motoko' && canister.declarations)
|
|
21
|
+
.map(([name, canister]) => [name, canister.main ?? '']));
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ErrorChecker } from './error-checker.js';
|
|
2
|
+
import { SilentReporter } from '../test/reporters/silent-reporter.js';
|
|
3
|
+
export declare class Tester {
|
|
4
|
+
verbose: boolean;
|
|
5
|
+
status: 'pending' | 'running' | 'syntax-error' | 'error' | 'success';
|
|
6
|
+
errorChecker: ErrorChecker;
|
|
7
|
+
reporter: SilentReporter;
|
|
8
|
+
aborted: boolean;
|
|
9
|
+
controller: AbortController;
|
|
10
|
+
currentRun: Promise<any> | undefined;
|
|
11
|
+
constructor({ verbose, errorChecker }: {
|
|
12
|
+
verbose: boolean;
|
|
13
|
+
errorChecker: ErrorChecker;
|
|
14
|
+
});
|
|
15
|
+
reset(): void;
|
|
16
|
+
abortCurrent(): Promise<void>;
|
|
17
|
+
run(onProgress: () => void): Promise<void>;
|
|
18
|
+
getOutput(): string;
|
|
19
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { readConfig } from '../../mops.js';
|
|
3
|
+
import { testWithReporter } from '../test/test.js';
|
|
4
|
+
import { SilentReporter } from '../test/reporters/silent-reporter.js';
|
|
5
|
+
export class Tester {
|
|
6
|
+
constructor({ verbose, errorChecker }) {
|
|
7
|
+
this.verbose = false;
|
|
8
|
+
this.status = 'pending';
|
|
9
|
+
this.reporter = new SilentReporter(false);
|
|
10
|
+
this.aborted = false;
|
|
11
|
+
this.controller = new AbortController();
|
|
12
|
+
this.verbose = verbose;
|
|
13
|
+
this.errorChecker = errorChecker;
|
|
14
|
+
}
|
|
15
|
+
reset() {
|
|
16
|
+
this.status = 'pending';
|
|
17
|
+
}
|
|
18
|
+
async abortCurrent() {
|
|
19
|
+
this.aborted = true;
|
|
20
|
+
this.controller.abort();
|
|
21
|
+
await this.currentRun;
|
|
22
|
+
this.reset();
|
|
23
|
+
this.aborted = false;
|
|
24
|
+
}
|
|
25
|
+
async run(onProgress) {
|
|
26
|
+
await this.abortCurrent();
|
|
27
|
+
if (this.errorChecker.status === 'error') {
|
|
28
|
+
this.status = 'syntax-error';
|
|
29
|
+
onProgress();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this.status = 'running';
|
|
33
|
+
onProgress();
|
|
34
|
+
this.reporter = new SilentReporter(false, onProgress);
|
|
35
|
+
this.controller = new AbortController();
|
|
36
|
+
let config = readConfig();
|
|
37
|
+
this.currentRun = testWithReporter(this.reporter, '', 'interpreter', config.toolchain?.['pocket-ic'] ? 'pocket-ic' : 'dfx', true, this.controller.signal);
|
|
38
|
+
await this.currentRun;
|
|
39
|
+
if (!this.aborted) {
|
|
40
|
+
this.status = this.reporter.failed > 0 ? 'error' : 'success';
|
|
41
|
+
}
|
|
42
|
+
onProgress();
|
|
43
|
+
}
|
|
44
|
+
getOutput() {
|
|
45
|
+
let get = (v) => v.toString();
|
|
46
|
+
let count = (this.status === 'running' ? get : chalk.bold[this.reporter.failed > 0 ? 'redBright' : 'green'])(this.reporter.failedFiles || this.reporter.passedFiles);
|
|
47
|
+
if (this.status === 'pending') {
|
|
48
|
+
return `Tests: ${chalk.gray('(pending)')}`;
|
|
49
|
+
}
|
|
50
|
+
if (this.status === 'running') {
|
|
51
|
+
return `Tests: ${count}/${this.reporter.total} ${chalk.gray('(running)')}`;
|
|
52
|
+
}
|
|
53
|
+
if (this.status === 'syntax-error') {
|
|
54
|
+
return `Tests: ${chalk.gray('(errors)')}`;
|
|
55
|
+
}
|
|
56
|
+
let output = '';
|
|
57
|
+
output += `Tests: ${count}`;
|
|
58
|
+
if (this.reporter.errorOutput) {
|
|
59
|
+
output += '\n' + this.reporter.errorOutput;
|
|
60
|
+
}
|
|
61
|
+
return output;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ErrorChecker } from './error-checker.js';
|
|
2
|
+
export declare class WarningChecker {
|
|
3
|
+
verbose: boolean;
|
|
4
|
+
canisters: Record<string, string>;
|
|
5
|
+
status: 'pending' | 'running' | 'syntax-error' | 'error' | 'success';
|
|
6
|
+
warnings: string[];
|
|
7
|
+
errorChecker: ErrorChecker;
|
|
8
|
+
aborted: boolean;
|
|
9
|
+
controllers: Map<string, AbortController>;
|
|
10
|
+
currentRun: Promise<any> | undefined;
|
|
11
|
+
constructor({ verbose, canisters, errorChecker }: {
|
|
12
|
+
verbose: boolean;
|
|
13
|
+
canisters: Record<string, string>;
|
|
14
|
+
errorChecker: ErrorChecker;
|
|
15
|
+
});
|
|
16
|
+
reset(): void;
|
|
17
|
+
abortCurrent(): Promise<void>;
|
|
18
|
+
run(onProgress: () => void): Promise<void>;
|
|
19
|
+
getOutput(): string;
|
|
20
|
+
}
|