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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Mops CLI Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.1
|
|
4
|
+
- `moc-wrapper` now adds hostname to the moc path cache(`.mops/moc-*` filename) to avoid errors when running in Dev Containers
|
|
5
|
+
- `mops watch` now deploys canisters with the `--yes` flag to skip data loss confirmation
|
|
6
|
+
|
|
7
|
+
## 1.1.0
|
|
8
|
+
- New `mops watch` command to check for syntax errors, show warnings, run tests, generate declarations and deploy canisters ([docs](https://docs.mops.one/cli/mops-watch))
|
|
9
|
+
- New flag `--no-toolchain` in `mops install` command to skip toolchain installation
|
|
10
|
+
- New lock file format v3 ([docs](https://docs.mops.one/mops.lock))
|
|
11
|
+
- Faster `mops install` from lock file when lock file is up-to-date and there are no cached packages
|
|
12
|
+
- Fixed replica test hanging in watch mode bug
|
|
13
|
+
- Fixed mops failing when dfx is not installed
|
|
14
|
+
- Fixed `mops test` Github Action template
|
|
15
|
+
|
|
3
16
|
## 1.0.1
|
|
4
17
|
- Fixed `mops user *` commands
|
|
5
18
|
|
package/bin/moc-wrapper.sh
CHANGED
package/bundle/cli.tgz
CHANGED
|
Binary file
|
package/check-requirements.ts
CHANGED
|
@@ -11,7 +11,7 @@ export async function checkRequirements({verbose = false} = {}) {
|
|
|
11
11
|
let config = readConfig();
|
|
12
12
|
let mocVersion = config.toolchain?.moc;
|
|
13
13
|
if (!mocVersion) {
|
|
14
|
-
mocVersion = getMocVersion();
|
|
14
|
+
mocVersion = getMocVersion(false);
|
|
15
15
|
}
|
|
16
16
|
if (!mocVersion) {
|
|
17
17
|
return;
|
package/cli.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import events from 'node:events';
|
|
3
4
|
import {Command, Argument, Option} from 'commander';
|
|
4
5
|
|
|
5
6
|
import {init} from './commands/init.js';
|
|
@@ -25,6 +26,7 @@ import {toolchain} from './commands/toolchain/index.js';
|
|
|
25
26
|
import {Tool} from './types.js';
|
|
26
27
|
import * as self from './commands/self.js';
|
|
27
28
|
import {resolvePackages} from './resolve-packages.js';
|
|
29
|
+
import {watch} from './commands/watch/watch.js';
|
|
28
30
|
|
|
29
31
|
declare global {
|
|
30
32
|
// eslint-disable-next-line no-var
|
|
@@ -33,6 +35,8 @@ declare global {
|
|
|
33
35
|
var mopsReplicaTestRunning : boolean;
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
events.setMaxListeners(20);
|
|
39
|
+
|
|
36
40
|
let networkFile = getNetworkFile();
|
|
37
41
|
if (fs.existsSync(networkFile)) {
|
|
38
42
|
globalThis.MOPS_NETWORK = fs.readFileSync(networkFile).toString() || 'ic';
|
|
@@ -398,4 +402,18 @@ selfCommand
|
|
|
398
402
|
|
|
399
403
|
program.addCommand(selfCommand);
|
|
400
404
|
|
|
405
|
+
// watch
|
|
406
|
+
program
|
|
407
|
+
.command('watch')
|
|
408
|
+
.description('Watch *.mo files and check for syntax errors, warnings, run tests, generate declarations and deploy canisters')
|
|
409
|
+
.option('-e, --error', 'Check Motoko canisters or *.mo files for syntax errors')
|
|
410
|
+
.option('-w, --warning', 'Check Motoko canisters or *.mo files for warnings')
|
|
411
|
+
.option('-t, --test', 'Run tests')
|
|
412
|
+
.option('-g, --generate', 'Generate declarations for Motoko canisters')
|
|
413
|
+
.option('-d, --deploy', 'Deploy Motoko canisters')
|
|
414
|
+
.action(async (options) => {
|
|
415
|
+
checkConfigFile(true);
|
|
416
|
+
await watch(options);
|
|
417
|
+
});
|
|
418
|
+
|
|
401
419
|
program.parse();
|
package/commands/bench.ts
CHANGED
|
@@ -55,7 +55,7 @@ export async function bench(filter = '', optionsArg : Partial<BenchOptions> = {}
|
|
|
55
55
|
replica: config.toolchain?.['pocket-ic'] ? 'pocket-ic' : 'dfx',
|
|
56
56
|
replicaVersion: '',
|
|
57
57
|
compiler: 'moc',
|
|
58
|
-
compilerVersion: getMocVersion(),
|
|
58
|
+
compilerVersion: getMocVersion(true),
|
|
59
59
|
gc: 'copying',
|
|
60
60
|
forceGc: true,
|
|
61
61
|
save: false,
|
|
@@ -34,10 +34,10 @@ export async function installAll({verbose = false, silent = false, threads, lock
|
|
|
34
34
|
if (lockFileJson && lockFileJson.version === 3) {
|
|
35
35
|
verbose && console.log('Installing from lock file...');
|
|
36
36
|
installedFromLockFile = true;
|
|
37
|
-
let
|
|
37
|
+
let lockedDeps = Object.entries(lockFileJson.deps).map(([name, version]) => {
|
|
38
38
|
return parseDepValue(name, version);
|
|
39
39
|
});
|
|
40
|
-
let ok = await installDeps(
|
|
40
|
+
let ok = await installDeps(lockedDeps, {silent, verbose, threads, ignoreTransitive: true});
|
|
41
41
|
if (!ok) {
|
|
42
42
|
return false;
|
|
43
43
|
}
|
package/commands/replica.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {ChildProcessWithoutNullStreams, execSync, spawn} from 'node:child_proces
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import {PassThrough} from 'node:stream';
|
|
6
|
+
import {spawn as spawnAsync} from 'promisify-child-process';
|
|
6
7
|
|
|
7
8
|
import {IDL} from '@dfinity/candid';
|
|
8
9
|
import {Actor, HttpAgent} from '@dfinity/agent';
|
|
@@ -126,7 +127,7 @@ export class Replica {
|
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
|
|
129
|
-
async deploy(name : string, wasm : string, idlFactory : IDL.InterfaceFactory, cwd : string = process.cwd()) {
|
|
130
|
+
async deploy(name : string, wasm : string, idlFactory : IDL.InterfaceFactory, cwd : string = process.cwd(), signal ?: AbortSignal) {
|
|
130
131
|
if (this.type === 'dfx') {
|
|
131
132
|
// prepare dfx.json for current canister
|
|
132
133
|
let dfxJson = path.join(this.dir, 'dfx.json');
|
|
@@ -144,8 +145,27 @@ export class Replica {
|
|
|
144
145
|
fs.mkdirSync(this.dir, {recursive: true});
|
|
145
146
|
fs.writeFileSync(dfxJson, JSON.stringify(newDfxJsonData, null, 2));
|
|
146
147
|
|
|
147
|
-
|
|
148
|
-
|
|
148
|
+
await spawnAsync('dfx', ['deploy', name, '--mode', 'reinstall', '--yes', '--identity', 'anonymous'], {cwd: this.dir, signal, stdio: this.verbose ? 'pipe' : ['pipe', 'ignore', 'pipe']}).catch((error) => {
|
|
149
|
+
if (error.code === 'ABORT_ERR') {
|
|
150
|
+
return {stderr: ''};
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (signal?.aborted) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
await spawnAsync('dfx', ['ledger', 'fabricate-cycles', '--canister', name, '--t', '100'], {cwd: this.dir, signal, stdio: this.verbose ? 'pipe' : ['pipe', 'ignore', 'pipe']}).catch((error) => {
|
|
160
|
+
if (error.code === 'ABORT_ERR') {
|
|
161
|
+
return {stderr: ''};
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
if (signal?.aborted) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
149
169
|
|
|
150
170
|
let canisterId = execSync(`dfx canister id ${name}`, {cwd: this.dir}).toString().trim();
|
|
151
171
|
|
|
@@ -170,7 +190,17 @@ export class Replica {
|
|
|
170
190
|
idlFactory,
|
|
171
191
|
wasm,
|
|
172
192
|
});
|
|
193
|
+
|
|
194
|
+
if (signal?.aborted) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
173
198
|
await this.pocketIc.addCycles(canisterId, 1_000_000_000_000);
|
|
199
|
+
|
|
200
|
+
if (signal?.aborted) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
174
204
|
this.canisters[name] = {
|
|
175
205
|
cwd,
|
|
176
206
|
canisterId: canisterId.toText(),
|
package/commands/sources.ts
CHANGED
package/commands/sync.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import process from 'node:process';
|
|
2
1
|
import path from 'node:path';
|
|
3
2
|
import {execSync} from 'node:child_process';
|
|
4
3
|
import {globSync} from 'glob';
|
|
@@ -7,6 +6,7 @@ import {checkConfigFile, getRootDir, readConfig} from '../mops.js';
|
|
|
7
6
|
import {add} from './add.js';
|
|
8
7
|
import {remove} from './remove.js';
|
|
9
8
|
import {checkIntegrity} from '../integrity.js';
|
|
9
|
+
import {getMocPath} from '../helpers/get-moc-path.js';
|
|
10
10
|
|
|
11
11
|
type SyncOptions = {
|
|
12
12
|
lock ?: 'update' | 'ignore';
|
|
@@ -48,25 +48,10 @@ let ignore = [
|
|
|
48
48
|
'**/.mops/**',
|
|
49
49
|
];
|
|
50
50
|
|
|
51
|
-
let mocPath = '';
|
|
52
|
-
function getMocPath() : string {
|
|
53
|
-
if (!mocPath) {
|
|
54
|
-
mocPath = process.env.DFX_MOC_PATH || '';
|
|
55
|
-
}
|
|
56
|
-
if (!mocPath) {
|
|
57
|
-
try {
|
|
58
|
-
mocPath = execSync('dfx cache show').toString().trim() + '/moc';
|
|
59
|
-
}
|
|
60
|
-
catch {}
|
|
61
|
-
}
|
|
62
|
-
if (!mocPath) {
|
|
63
|
-
mocPath = 'moc';
|
|
64
|
-
}
|
|
65
|
-
return mocPath;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
51
|
async function getUsedPackages() : Promise<string[]> {
|
|
69
52
|
let rootDir = getRootDir();
|
|
53
|
+
let mocPath = getMocPath();
|
|
54
|
+
|
|
70
55
|
let files = globSync('**/*.mo', {
|
|
71
56
|
cwd: rootDir,
|
|
72
57
|
nocase: true,
|
|
@@ -76,7 +61,7 @@ async function getUsedPackages() : Promise<string[]> {
|
|
|
76
61
|
let packages : Set<string> = new Set;
|
|
77
62
|
|
|
78
63
|
for (let file of files) {
|
|
79
|
-
let deps : string[] = execSync(`${
|
|
64
|
+
let deps : string[] = execSync(`${mocPath} --print-deps ${path.join(rootDir, file)}`).toString().trim().split('\n');
|
|
80
65
|
|
|
81
66
|
for (let dep of deps) {
|
|
82
67
|
if (dep.startsWith('mo:') && !dep.startsWith('mo:prim') && !dep.startsWith('mo:⛔')) {
|
package/commands/test/mmf1.ts
CHANGED
|
@@ -53,6 +53,10 @@ export class MMF1 {
|
|
|
53
53
|
this.output = [];
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
getErrorMessages() {
|
|
57
|
+
return this.output.filter(out => out.type === 'fail').map(out => out.message);
|
|
58
|
+
}
|
|
59
|
+
|
|
56
60
|
parseLine(line : string) {
|
|
57
61
|
if (line.startsWith('mops:1:start ')) {
|
|
58
62
|
this._testStart(line.split('mops:1:start ')[1] || '');
|
|
@@ -5,14 +5,25 @@ import {Reporter} from './reporter.js';
|
|
|
5
5
|
import {TestMode} from '../../../types.js';
|
|
6
6
|
|
|
7
7
|
export class SilentReporter implements Reporter {
|
|
8
|
+
total = 0;
|
|
8
9
|
passed = 0;
|
|
9
10
|
failed = 0;
|
|
10
11
|
skipped = 0;
|
|
11
12
|
passedFiles = 0;
|
|
12
13
|
failedFiles = 0;
|
|
13
14
|
passedNamesFlat : string[] = [];
|
|
15
|
+
flushOnError = true;
|
|
16
|
+
errorOutput = '';
|
|
17
|
+
onProgress = () => {};
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
constructor(flushOnError = true, onProgress = () => {}) {
|
|
20
|
+
this.flushOnError = flushOnError;
|
|
21
|
+
this.onProgress = onProgress;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
addFiles(files : string[]) {
|
|
25
|
+
this.total = files.length;
|
|
26
|
+
}
|
|
16
27
|
|
|
17
28
|
addRun(file : string, mmf : MMF1, state : Promise<void>, _mode : TestMode) {
|
|
18
29
|
state.then(() => {
|
|
@@ -30,10 +41,17 @@ export class SilentReporter implements Reporter {
|
|
|
30
41
|
this.failedFiles += Number(mmf.failed !== 0);
|
|
31
42
|
|
|
32
43
|
if (mmf.failed) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
let output = `${chalk.red('✖')} ${absToRel(file)}\n${mmf.getErrorMessages().join('\n')}\n${'-'.repeat(50)}`;
|
|
45
|
+
|
|
46
|
+
if (this.flushOnError) {
|
|
47
|
+
console.log(output);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
this.errorOutput = `${this.errorOutput}\n${output}`.trim();
|
|
51
|
+
}
|
|
36
52
|
}
|
|
53
|
+
|
|
54
|
+
this.onProgress();
|
|
37
55
|
});
|
|
38
56
|
}
|
|
39
57
|
|
package/commands/test/test.ts
CHANGED
|
@@ -85,14 +85,28 @@ export async function test(filter = '', options : Partial<TestOptions> = {}) {
|
|
|
85
85
|
process.exit(0);
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
|
+
else {
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
88
91
|
});
|
|
89
92
|
|
|
90
93
|
// todo: run only changed for *.test.mo?
|
|
91
94
|
// todo: run all for *.mo?
|
|
95
|
+
|
|
96
|
+
let curRun = Promise.resolve(true);
|
|
97
|
+
let controller = new AbortController();
|
|
98
|
+
|
|
92
99
|
let run = debounce(async () => {
|
|
100
|
+
controller.abort();
|
|
101
|
+
await curRun;
|
|
102
|
+
|
|
93
103
|
console.clear();
|
|
94
104
|
process.stdout.write('\x1Bc');
|
|
95
|
-
|
|
105
|
+
|
|
106
|
+
controller = new AbortController();
|
|
107
|
+
curRun = runAll(options.reporter, filter, options.mode, replicaType, true, controller.signal);
|
|
108
|
+
await curRun;
|
|
109
|
+
|
|
96
110
|
console.log('-'.repeat(50));
|
|
97
111
|
console.log('Waiting for file changes...');
|
|
98
112
|
console.log(chalk.gray((`Press ${chalk.gray('Ctrl+C')} to exit.`)));
|
|
@@ -122,12 +136,12 @@ export async function test(filter = '', options : Partial<TestOptions> = {}) {
|
|
|
122
136
|
let mocPath = '';
|
|
123
137
|
let wasmtimePath = '';
|
|
124
138
|
|
|
125
|
-
async function runAll(reporterName : ReporterName | undefined, filter = '', mode : TestMode = 'interpreter', replicaType : ReplicaName, watch = false) : Promise<boolean> {
|
|
126
|
-
let done = await testWithReporter(reporterName, filter, mode, replicaType, watch);
|
|
139
|
+
async function runAll(reporterName : ReporterName | undefined, filter = '', mode : TestMode = 'interpreter', replicaType : ReplicaName, watch = false, signal ?: AbortSignal) : Promise<boolean> {
|
|
140
|
+
let done = await testWithReporter(reporterName, filter, mode, replicaType, watch, signal);
|
|
127
141
|
return done;
|
|
128
142
|
}
|
|
129
143
|
|
|
130
|
-
export async function testWithReporter(reporterName : ReporterName | Reporter | undefined, filter = '', defaultMode : TestMode = 'interpreter', replicaType : ReplicaName, watch = false) : Promise<boolean> {
|
|
144
|
+
export async function testWithReporter(reporterName : ReporterName | Reporter | undefined, filter = '', defaultMode : TestMode = 'interpreter', replicaType : ReplicaName, watch = false, signal ?: AbortSignal) : Promise<boolean> {
|
|
131
145
|
let rootDir = getRootDir();
|
|
132
146
|
let files : string[] = [];
|
|
133
147
|
let libFiles = globSync('**/test?(s)/lib.mo', globConfig);
|
|
@@ -191,6 +205,10 @@ export async function testWithReporter(reporterName : ReporterName | Reporter |
|
|
|
191
205
|
fs.mkdirSync(testTempDir, {recursive: true});
|
|
192
206
|
|
|
193
207
|
await parallel(os.cpus().length, files, async (file : string) => {
|
|
208
|
+
if (signal?.aborted) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
194
212
|
let mmf = new MMF1('store', absToRel(file));
|
|
195
213
|
|
|
196
214
|
// mode overrides
|
|
@@ -221,7 +239,13 @@ export async function testWithReporter(reporterName : ReporterName | Reporter |
|
|
|
221
239
|
|
|
222
240
|
// interpret
|
|
223
241
|
if (mode === 'interpreter') {
|
|
224
|
-
let proc = spawn(mocPath, ['-r', '-ref-system-api', ...mocArgs]);
|
|
242
|
+
let proc = spawn(mocPath, ['-r', '-ref-system-api', ...mocArgs], {signal});
|
|
243
|
+
proc.addListener('error', (error : any) => {
|
|
244
|
+
if (error?.code === 'ABORT_ERR') {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
throw error;
|
|
248
|
+
});
|
|
225
249
|
pipeMMF(proc, mmf).then(resolve);
|
|
226
250
|
}
|
|
227
251
|
// build and run wasm
|
|
@@ -229,7 +253,13 @@ export async function testWithReporter(reporterName : ReporterName | Reporter |
|
|
|
229
253
|
let wasmFile = `${path.join(testTempDir, path.parse(file).name)}.wasm`;
|
|
230
254
|
|
|
231
255
|
// build
|
|
232
|
-
let buildProc = spawn(mocPath, [`-o=${wasmFile}`, '-wasi-system-api', ...mocArgs]);
|
|
256
|
+
let buildProc = spawn(mocPath, [`-o=${wasmFile}`, '-wasi-system-api', ...mocArgs], {signal});
|
|
257
|
+
buildProc.addListener('error', (error : any) => {
|
|
258
|
+
if (error?.code === 'ABORT_ERR') {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
throw error;
|
|
262
|
+
});
|
|
233
263
|
pipeMMF(buildProc, mmf).then(async () => {
|
|
234
264
|
if (mmf.failed > 0) {
|
|
235
265
|
return;
|
|
@@ -252,7 +282,14 @@ export async function testWithReporter(reporterName : ReporterName | Reporter |
|
|
|
252
282
|
process.exit(1);
|
|
253
283
|
}
|
|
254
284
|
|
|
255
|
-
let proc = spawn(wasmtimePath, wasmtimeArgs);
|
|
285
|
+
let proc = spawn(wasmtimePath, wasmtimeArgs, {signal});
|
|
286
|
+
proc.addListener('error', (error : any) => {
|
|
287
|
+
if (error?.code === 'ABORT_ERR') {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
throw error;
|
|
291
|
+
});
|
|
292
|
+
|
|
256
293
|
await pipeMMF(proc, mmf);
|
|
257
294
|
}).finally(() => {
|
|
258
295
|
fs.rmSync(wasmFile, {force: true});
|
|
@@ -265,7 +302,13 @@ export async function testWithReporter(reporterName : ReporterName | Reporter |
|
|
|
265
302
|
let wasmFile = `${path.join(testTempDir, path.parse(file).name)}.wasm`;
|
|
266
303
|
|
|
267
304
|
// build
|
|
268
|
-
let buildProc = spawn(mocPath, [`-o=${wasmFile}`, ...mocArgs]);
|
|
305
|
+
let buildProc = spawn(mocPath, [`-o=${wasmFile}`, ...mocArgs], {signal});
|
|
306
|
+
buildProc.addListener('error', (error : any) => {
|
|
307
|
+
if (error?.code === 'ABORT_ERR') {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
throw error;
|
|
311
|
+
});
|
|
269
312
|
|
|
270
313
|
pipeMMF(buildProc, mmf).then(async () => {
|
|
271
314
|
if (mmf.failed > 0) {
|
|
@@ -274,15 +317,23 @@ export async function testWithReporter(reporterName : ReporterName | Reporter |
|
|
|
274
317
|
|
|
275
318
|
await startReplicaOnce(replica, replicaType);
|
|
276
319
|
|
|
320
|
+
if (signal?.aborted) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
277
324
|
let canisterName = path.parse(file).name;
|
|
278
325
|
let idlFactory = ({IDL} : any) => {
|
|
279
326
|
return IDL.Service({'runTests': IDL.Func([], [], [])});
|
|
280
327
|
};
|
|
281
328
|
interface _SERVICE {'runTests' : ActorMethod<[], undefined>;}
|
|
282
329
|
|
|
283
|
-
let
|
|
330
|
+
let canister = await replica.deploy(canisterName, wasmFile, idlFactory, undefined, signal);
|
|
284
331
|
|
|
285
|
-
|
|
332
|
+
if (signal?.aborted || !canister) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
pipeStdoutToMMF(canister.stream, mmf);
|
|
286
337
|
|
|
287
338
|
let actor = await replica.getActor(canisterName) as _SERVICE;
|
|
288
339
|
|
|
@@ -298,6 +349,10 @@ export async function testWithReporter(reporterName : ReporterName | Reporter |
|
|
|
298
349
|
});
|
|
299
350
|
}
|
|
300
351
|
|
|
352
|
+
if (signal?.aborted) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
301
356
|
globalThis.mopsReplicaTestRunning = true;
|
|
302
357
|
await actor.runTests();
|
|
303
358
|
globalThis.mopsReplicaTestRunning = false;
|
|
@@ -310,11 +365,16 @@ export async function testWithReporter(reporterName : ReporterName | Reporter |
|
|
|
310
365
|
stderrStream.write(e.message);
|
|
311
366
|
}
|
|
312
367
|
}).finally(async () => {
|
|
368
|
+
globalThis.mopsReplicaTestRunning = false;
|
|
313
369
|
fs.rmSync(wasmFile, {force: true});
|
|
314
370
|
}).then(resolve);
|
|
315
371
|
}
|
|
316
372
|
});
|
|
317
373
|
|
|
374
|
+
if (signal?.aborted) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
318
378
|
reporter.addRun(file, mmf, promise, mode);
|
|
319
379
|
|
|
320
380
|
await promise;
|
|
@@ -325,6 +385,10 @@ export async function testWithReporter(reporterName : ReporterName | Reporter |
|
|
|
325
385
|
fs.rmSync(testTempDir, {recursive: true, force: true});
|
|
326
386
|
}
|
|
327
387
|
|
|
388
|
+
if (signal?.aborted) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
|
|
328
392
|
return reporter.done();
|
|
329
393
|
}
|
|
330
394
|
|
|
@@ -0,0 +1,155 @@
|
|
|
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
|
+
|
|
6
|
+
import {ErrorChecker} from './error-checker.js';
|
|
7
|
+
import {Generator} from './generator.js';
|
|
8
|
+
import {parallel} from '../../parallel.js';
|
|
9
|
+
import {getRootDir} from '../../mops.js';
|
|
10
|
+
|
|
11
|
+
export class Deployer {
|
|
12
|
+
verbose = false;
|
|
13
|
+
canisters : Record<string, string> = {};
|
|
14
|
+
status : 'pending' | 'running' | 'syntax-error' | 'dfx-error' | 'error' | 'success' = 'pending';
|
|
15
|
+
errorChecker : ErrorChecker;
|
|
16
|
+
generator : Generator;
|
|
17
|
+
success = 0;
|
|
18
|
+
errors : string[] = [];
|
|
19
|
+
aborted = false;
|
|
20
|
+
controllers = new Map<string, AbortController>();
|
|
21
|
+
currentRun : Promise<any> | undefined;
|
|
22
|
+
|
|
23
|
+
constructor({verbose, canisters, errorChecker, generator} : {verbose : boolean, canisters : Record<string, string>, errorChecker : ErrorChecker, generator : Generator}) {
|
|
24
|
+
this.verbose = verbose;
|
|
25
|
+
this.canisters = canisters;
|
|
26
|
+
this.errorChecker = errorChecker;
|
|
27
|
+
this.generator = generator;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
reset() {
|
|
31
|
+
this.status = 'pending';
|
|
32
|
+
this.success = 0;
|
|
33
|
+
this.errors = [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async abortCurrent() {
|
|
37
|
+
this.aborted = true;
|
|
38
|
+
for (let controller of this.controllers.values()) {
|
|
39
|
+
controller.abort();
|
|
40
|
+
}
|
|
41
|
+
this.controllers.clear();
|
|
42
|
+
await this.currentRun;
|
|
43
|
+
this.reset();
|
|
44
|
+
this.aborted = false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async run(onProgress : () => void) {
|
|
48
|
+
await this.abortCurrent();
|
|
49
|
+
|
|
50
|
+
if (this.errorChecker.status === 'error') {
|
|
51
|
+
this.status = 'syntax-error';
|
|
52
|
+
onProgress();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (Object.keys(this.canisters).length === 0) {
|
|
57
|
+
this.status = 'success';
|
|
58
|
+
onProgress();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let rootDir = getRootDir();
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
execSync('dfx ping', {cwd: rootDir});
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
this.status = 'dfx-error';
|
|
69
|
+
onProgress();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.status = 'running';
|
|
74
|
+
onProgress();
|
|
75
|
+
|
|
76
|
+
// create canisters (sequentially to avoid DFX errors)
|
|
77
|
+
let resolve : (() => void) | undefined;
|
|
78
|
+
this.currentRun = new Promise<void>((res) => resolve = res);
|
|
79
|
+
for (let canister of Object.keys(this.canisters)) {
|
|
80
|
+
let controller = new AbortController();
|
|
81
|
+
let {signal} = controller;
|
|
82
|
+
this.controllers.set(canister, controller);
|
|
83
|
+
|
|
84
|
+
await promisify(execFile)('dfx', ['canister', 'create', canister], {cwd: rootDir, signal}).catch((error) => {
|
|
85
|
+
if (error.code === 'ABORT_ERR') {
|
|
86
|
+
return {stderr: ''};
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
this.controllers.delete(canister);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
resolve?.();
|
|
95
|
+
|
|
96
|
+
if (this.aborted) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.currentRun = parallel(os.cpus().length, [...Object.keys(this.canisters)], async (canister) => {
|
|
101
|
+
let controller = new AbortController();
|
|
102
|
+
let {signal} = controller;
|
|
103
|
+
this.controllers.set(canister, controller);
|
|
104
|
+
|
|
105
|
+
// build
|
|
106
|
+
if (this.generator.status !== 'success' || !this.generator.canisters[canister]) {
|
|
107
|
+
await promisify(execFile)('dfx', ['build', canister], {cwd: rootDir, signal}).catch((error) => {
|
|
108
|
+
if (error.code === 'ABORT_ERR') {
|
|
109
|
+
return {stderr: ''};
|
|
110
|
+
}
|
|
111
|
+
throw error;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// install
|
|
116
|
+
await promisify(execFile)('dfx', ['canister', 'install', '--mode=auto', '--yes', canister], {cwd: rootDir, signal}).catch((error) => {
|
|
117
|
+
if (error.code === 'ABORT_ERR') {
|
|
118
|
+
return {stderr: ''};
|
|
119
|
+
}
|
|
120
|
+
throw error;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
this.success += 1;
|
|
124
|
+
this.controllers.delete(canister);
|
|
125
|
+
onProgress();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await this.currentRun;
|
|
129
|
+
|
|
130
|
+
if (!this.aborted) {
|
|
131
|
+
this.status = 'success';
|
|
132
|
+
}
|
|
133
|
+
onProgress();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
getOutput() : string {
|
|
137
|
+
let get = (v : number) => v.toString();
|
|
138
|
+
let count = (this.status === 'running' ? get : chalk.bold[this.errors.length > 0 ? 'redBright' : 'green'])(this.errors.length || this.success);
|
|
139
|
+
|
|
140
|
+
if (this.status === 'pending') {
|
|
141
|
+
return `Deploy: ${chalk.gray('(pending)')}`;
|
|
142
|
+
}
|
|
143
|
+
if (this.status === 'running') {
|
|
144
|
+
return `Deploy: ${count}/${Object.keys(this.canisters).length} ${chalk.gray('(running)')}`;
|
|
145
|
+
}
|
|
146
|
+
if (this.status === 'syntax-error') {
|
|
147
|
+
return `Deploy: ${chalk.gray('(errors)')}`;
|
|
148
|
+
}
|
|
149
|
+
if (this.status === 'dfx-error') {
|
|
150
|
+
return `Deploy: ${chalk.gray('(dfx not running)')}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return `Deploy: ${count}`;
|
|
154
|
+
}
|
|
155
|
+
}
|