@pezkuwi/dev 0.84.2
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/.skip-deno +0 -0
- package/README.md +547 -0
- package/config/eslint.js +160 -0
- package/config/eslint.rules.js +214 -0
- package/config/prettier.cjs +22 -0
- package/config/rollup.js +113 -0
- package/config/tsconfig.json +32 -0
- package/config/typedoc.cjs +18 -0
- package/package.json +107 -0
- package/scripts/polkadot-ci-ghact-build.mjs +540 -0
- package/scripts/polkadot-ci-ghact-docs.mjs +14 -0
- package/scripts/polkadot-ci-ghpages-force.mjs +43 -0
- package/scripts/polkadot-dev-build-docs.mjs +19 -0
- package/scripts/polkadot-dev-build-ts.mjs +1518 -0
- package/scripts/polkadot-dev-circular.mjs +29 -0
- package/scripts/polkadot-dev-clean-build.mjs +61 -0
- package/scripts/polkadot-dev-contrib.mjs +74 -0
- package/scripts/polkadot-dev-copy-dir.mjs +44 -0
- package/scripts/polkadot-dev-copy-to.mjs +53 -0
- package/scripts/polkadot-dev-deno-map.mjs +35 -0
- package/scripts/polkadot-dev-run-lint.mjs +40 -0
- package/scripts/polkadot-dev-run-node-ts.mjs +9 -0
- package/scripts/polkadot-dev-run-test.mjs +163 -0
- package/scripts/polkadot-dev-version.mjs +143 -0
- package/scripts/polkadot-dev-yarn-only.mjs +11 -0
- package/scripts/polkadot-exec-eslint.mjs +7 -0
- package/scripts/polkadot-exec-ghpages.mjs +11 -0
- package/scripts/polkadot-exec-ghrelease.mjs +7 -0
- package/scripts/polkadot-exec-node-test.mjs +368 -0
- package/scripts/polkadot-exec-rollup.mjs +7 -0
- package/scripts/polkadot-exec-tsc.mjs +7 -0
- package/scripts/polkadot-exec-webpack.mjs +7 -0
- package/scripts/util.mjs +540 -0
- package/tsconfig.build.json +18 -0
- package/tsconfig.config.json +14 -0
- package/tsconfig.scripts.json +14 -0
- package/tsconfig.spec.json +18 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copyright 2017-2025 @polkadot/dev authors & contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
// For Node 18, earliest usable is 18.14:
|
|
6
|
+
//
|
|
7
|
+
// - node:test added in 18.0,
|
|
8
|
+
// - run method exposed in 18.9,
|
|
9
|
+
// - mock in 18.13,
|
|
10
|
+
// - diagnostics changed in 18.14
|
|
11
|
+
//
|
|
12
|
+
// Node 16 is not supported:
|
|
13
|
+
//
|
|
14
|
+
// - node:test added is 16.17,
|
|
15
|
+
// - run method exposed in 16.19,
|
|
16
|
+
// - mock not available
|
|
17
|
+
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import process from 'node:process';
|
|
22
|
+
import { run } from 'node:test';
|
|
23
|
+
import { isMainThread, parentPort, Worker, workerData } from 'node:worker_threads';
|
|
24
|
+
|
|
25
|
+
// NOTE error should be defined as "Error", however the @types/node definitions doesn't include all
|
|
26
|
+
/** @typedef {{ file?: string; message?: string; }} DiagStat */
|
|
27
|
+
/** @typedef {{ details: { type: string; duration_ms: number; error: { message: string; failureType: unknown; stack: string; cause: { code: number; message: string; stack: string; generatedMessage?: any; }; code: number; } }; file?: string; name: string; testNumber: number; nesting: number; }} FailStat */
|
|
28
|
+
/** @typedef {{ details: { duration_ms: number }; name: string; }} PassStat */
|
|
29
|
+
/** @typedef {{ diag: DiagStat[]; fail: FailStat[]; pass: PassStat[]; skip: unknown[]; todo: unknown[]; total: number; [key: string]: any; }} Stats */
|
|
30
|
+
|
|
31
|
+
console.time('\t elapsed :');
|
|
32
|
+
|
|
33
|
+
const WITH_DEBUG = false;
|
|
34
|
+
|
|
35
|
+
const args = process.argv.slice(2);
|
|
36
|
+
/** @type {string[]} */
|
|
37
|
+
const files = [];
|
|
38
|
+
|
|
39
|
+
/** @type {Stats} */
|
|
40
|
+
const stats = {
|
|
41
|
+
diag: [],
|
|
42
|
+
fail: [],
|
|
43
|
+
pass: [],
|
|
44
|
+
skip: [],
|
|
45
|
+
todo: [],
|
|
46
|
+
total: 0
|
|
47
|
+
};
|
|
48
|
+
/** @type {string | null} */
|
|
49
|
+
let logFile = null;
|
|
50
|
+
/** @type {number} */
|
|
51
|
+
let startAt = 0;
|
|
52
|
+
/** @type {boolean} */
|
|
53
|
+
let bail = false;
|
|
54
|
+
/** @type {boolean} */
|
|
55
|
+
let toConsole = false;
|
|
56
|
+
/** @type {number} */
|
|
57
|
+
let progressRowCount = 0;
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < args.length; i++) {
|
|
60
|
+
if (args[i] === '--bail') {
|
|
61
|
+
bail = true;
|
|
62
|
+
} else if (args[i] === '--console') {
|
|
63
|
+
toConsole = true;
|
|
64
|
+
} else if (args[i] === '--logfile') {
|
|
65
|
+
logFile = args[++i];
|
|
66
|
+
} else {
|
|
67
|
+
files.push(args[i]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @internal
|
|
73
|
+
*
|
|
74
|
+
* Performs an indent of the line (and containing lines) with the specific count
|
|
75
|
+
*
|
|
76
|
+
* @param {number} count
|
|
77
|
+
* @param {string} str
|
|
78
|
+
* @param {string} start
|
|
79
|
+
* @returns {string}
|
|
80
|
+
*/
|
|
81
|
+
function indent (count, str = '', start = '') {
|
|
82
|
+
let pre = '\n';
|
|
83
|
+
|
|
84
|
+
switch (count) {
|
|
85
|
+
case 0:
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
case 1:
|
|
89
|
+
pre += '\t';
|
|
90
|
+
break;
|
|
91
|
+
|
|
92
|
+
case 2:
|
|
93
|
+
pre += '\t\t';
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
default:
|
|
97
|
+
pre += '\t\t\t';
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
pre += ' ';
|
|
102
|
+
|
|
103
|
+
return `${pre}${start}${
|
|
104
|
+
str
|
|
105
|
+
.split('\n')
|
|
106
|
+
.map((l) => l.trim())
|
|
107
|
+
.join(`${pre}${start ? ' '.padStart(start.length, ' ') : ''}`)
|
|
108
|
+
}\n`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {FailStat} r
|
|
113
|
+
* @return {string | undefined}
|
|
114
|
+
*/
|
|
115
|
+
function getFilename (r) {
|
|
116
|
+
if (r.file?.includes('.spec.') || r.file?.includes('.test.')) {
|
|
117
|
+
return r.file;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (r.details.error.cause.stack) {
|
|
121
|
+
const stack = r.details.error.cause.stack
|
|
122
|
+
.split('\n')
|
|
123
|
+
.map((l) => l.trim())
|
|
124
|
+
.filter((l) => l.startsWith('at ') && (l.includes('.spec.') || l.includes('.test.')))
|
|
125
|
+
.map((l) => l.match(/\(.*:\d\d?:\d\d?\)$/)?.[0])
|
|
126
|
+
.map((l) => l?.replace('(', '')?.replace(')', ''));
|
|
127
|
+
|
|
128
|
+
if (stack.length) {
|
|
129
|
+
return stack[0];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return r.file;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function complete () {
|
|
137
|
+
process.stdout.write('\n');
|
|
138
|
+
|
|
139
|
+
let logError = '';
|
|
140
|
+
|
|
141
|
+
stats.fail.forEach((r) => {
|
|
142
|
+
WITH_DEBUG && console.error(JSON.stringify(r, null, 2));
|
|
143
|
+
|
|
144
|
+
let item = '';
|
|
145
|
+
|
|
146
|
+
item += indent(1, [getFilename(r), r.name].filter((s) => !!s).join('\n'), 'x ');
|
|
147
|
+
item += indent(2, `${r.details.error.failureType} / ${r.details.error.code}${r.details.error.cause.code && r.details.error.cause.code !== r.details.error.code ? ` / ${r.details.error.cause.code}` : ''}`);
|
|
148
|
+
|
|
149
|
+
if (r.details.error.cause.message) {
|
|
150
|
+
item += indent(2, r.details.error.cause.message);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
logError += item;
|
|
154
|
+
|
|
155
|
+
if (r.details.error.cause.stack) {
|
|
156
|
+
item += indent(2, r.details.error.cause.stack);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
process.stdout.write(item);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (logFile && logError) {
|
|
163
|
+
try {
|
|
164
|
+
fs.appendFileSync(path.join(process.cwd(), logFile), logError);
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.error(e);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log();
|
|
171
|
+
console.log('\t passed ::', stats.pass.length);
|
|
172
|
+
console.log('\t failed ::', stats.fail.length);
|
|
173
|
+
console.log('\t skipped ::', stats.skip.length);
|
|
174
|
+
console.log('\t todo ::', stats.todo.length);
|
|
175
|
+
console.log('\t total ::', stats.total);
|
|
176
|
+
console.timeEnd('\t elapsed :');
|
|
177
|
+
console.log();
|
|
178
|
+
|
|
179
|
+
// The full error information can be quite useful in the case of overall failures
|
|
180
|
+
if ((stats.fail.length || toConsole) && stats.diag.length) {
|
|
181
|
+
/** @type {string | undefined} */
|
|
182
|
+
let lastFilename = '';
|
|
183
|
+
|
|
184
|
+
stats.diag.forEach((r) => {
|
|
185
|
+
WITH_DEBUG && console.error(JSON.stringify(r, null, 2));
|
|
186
|
+
|
|
187
|
+
if (typeof r === 'string') {
|
|
188
|
+
console.log(r); // Node.js <= 18.14
|
|
189
|
+
} else if (r.file && r.file.includes('@polkadot/dev/scripts')) {
|
|
190
|
+
// Ignore internal diagnostics
|
|
191
|
+
} else {
|
|
192
|
+
if (lastFilename !== r.file) {
|
|
193
|
+
lastFilename = r.file;
|
|
194
|
+
|
|
195
|
+
console.log(lastFilename ? `\n${lastFilename}::\n` : '\n');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Edge case: We don't need additional noise that is not useful.
|
|
199
|
+
if (!r.message?.split(' ').includes('tests')) {
|
|
200
|
+
console.log(`\t${r.message?.split('\n').join('\n\t')}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (toConsole) {
|
|
207
|
+
stats.pass.forEach((r) => {
|
|
208
|
+
console.log(`pass ${r.name} ${r.details.duration_ms} ms`);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
console.log();
|
|
212
|
+
|
|
213
|
+
stats.fail.forEach((r) => {
|
|
214
|
+
console.log(`fail ${r.name}`);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
console.log();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (stats.total === 0) {
|
|
221
|
+
console.error('FATAL: No tests executed');
|
|
222
|
+
console.error();
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
process.exit(stats.fail.length);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Prints the progress in real-time as data is passed from the worker.
|
|
231
|
+
*
|
|
232
|
+
* @param {string} symbol
|
|
233
|
+
*/
|
|
234
|
+
function printProgress (symbol) {
|
|
235
|
+
if (!progressRowCount) {
|
|
236
|
+
progressRowCount = 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!startAt) {
|
|
240
|
+
startAt = performance.now();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// If starting a new row, calculate and print the elapsed time
|
|
244
|
+
if (progressRowCount === 0) {
|
|
245
|
+
const now = performance.now();
|
|
246
|
+
const elapsed = (now - startAt) / 1000;
|
|
247
|
+
const minutes = Math.floor(elapsed / 60);
|
|
248
|
+
const seconds = elapsed - minutes * 60;
|
|
249
|
+
|
|
250
|
+
process.stdout.write(
|
|
251
|
+
`${`${minutes}:${seconds.toFixed(3).padStart(6, '0')}`.padStart(11)} `
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Print the symbol with formatting
|
|
256
|
+
process.stdout.write(symbol);
|
|
257
|
+
|
|
258
|
+
progressRowCount++;
|
|
259
|
+
|
|
260
|
+
// Add spaces for readability
|
|
261
|
+
if (progressRowCount % 10 === 0) {
|
|
262
|
+
process.stdout.write(' '); // Double space every 10 symbols
|
|
263
|
+
} else if (progressRowCount % 5 === 0) {
|
|
264
|
+
process.stdout.write(' '); // Single space every 5 symbols
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// If the row reaches 100 symbols, start a new row
|
|
268
|
+
if (progressRowCount >= 100) {
|
|
269
|
+
process.stdout.write('\n');
|
|
270
|
+
progressRowCount = 0;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function runParallel () {
|
|
275
|
+
const MAX_WORKERS = Math.min(os.cpus().length, files.length);
|
|
276
|
+
const chunks = Math.ceil(files.length / MAX_WORKERS);
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
// Create and manage worker threads
|
|
280
|
+
const results = await Promise.all(
|
|
281
|
+
Array.from({ length: MAX_WORKERS }, (_, i) => {
|
|
282
|
+
const fileSubset = files.slice(i * chunks, (i + 1) * chunks);
|
|
283
|
+
|
|
284
|
+
return new Promise((resolve, reject) => {
|
|
285
|
+
const worker = new Worker(new URL(import.meta.url), {
|
|
286
|
+
workerData: { files: fileSubset }
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
worker.on('message', (message) => {
|
|
290
|
+
if (message.type === 'progress') {
|
|
291
|
+
printProgress(message.data);
|
|
292
|
+
} else if (message.type === 'result') {
|
|
293
|
+
resolve(message.data);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
worker.on('error', reject);
|
|
298
|
+
worker.on('exit', (code) => {
|
|
299
|
+
if (code !== 0) {
|
|
300
|
+
reject(new Error(`Worker stopped with exit code ${code}`));
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
})
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
// Aggregate results from workers
|
|
308
|
+
results.forEach((result) => {
|
|
309
|
+
Object.keys(stats).forEach((key) => {
|
|
310
|
+
if (Array.isArray(stats[key])) {
|
|
311
|
+
stats[key] = stats[key].concat(result[key]);
|
|
312
|
+
} else if (typeof stats[key] === 'number') {
|
|
313
|
+
stats[key] += result[key];
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
complete();
|
|
319
|
+
} catch (err) {
|
|
320
|
+
console.error('Error during parallel execution:', err);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (isMainThread) {
|
|
326
|
+
console.time('\tElapsed:');
|
|
327
|
+
runParallel().catch((err) => console.error(err));
|
|
328
|
+
} else {
|
|
329
|
+
run({ files: workerData.files, timeout: 3_600_000 })
|
|
330
|
+
.on('data', () => undefined)
|
|
331
|
+
.on('end', () => parentPort && parentPort.postMessage(stats))
|
|
332
|
+
.on('test:coverage', () => undefined)
|
|
333
|
+
.on('test:diagnostic', (/** @type {DiagStat} */data) => {
|
|
334
|
+
stats.diag.push(data);
|
|
335
|
+
parentPort && parentPort.postMessage({ data: stats, type: 'result' });
|
|
336
|
+
})
|
|
337
|
+
.on('test:fail', (/** @type {FailStat} */ data) => {
|
|
338
|
+
const statFail = structuredClone(data);
|
|
339
|
+
|
|
340
|
+
if (data.details.error.cause?.stack) {
|
|
341
|
+
statFail.details.error.cause.stack = data.details.error.cause.stack;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
stats.fail.push(statFail);
|
|
345
|
+
stats.total++;
|
|
346
|
+
parentPort && parentPort.postMessage({ data: 'x', type: 'progress' });
|
|
347
|
+
|
|
348
|
+
if (bail) {
|
|
349
|
+
complete();
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
.on('test:pass', (data) => {
|
|
353
|
+
const symbol = typeof data.skip !== 'undefined' ? '>' : typeof data.todo !== 'undefined' ? '!' : '·';
|
|
354
|
+
|
|
355
|
+
if (symbol === '>') {
|
|
356
|
+
stats.skip.push(data);
|
|
357
|
+
} else if (symbol === '!') {
|
|
358
|
+
stats.todo.push(data);
|
|
359
|
+
} else {
|
|
360
|
+
stats.pass.push(data);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
stats.total++;
|
|
364
|
+
parentPort && parentPort.postMessage({ data: symbol, type: 'progress' });
|
|
365
|
+
})
|
|
366
|
+
.on('test:plan', () => undefined)
|
|
367
|
+
.on('test:start', () => undefined);
|
|
368
|
+
}
|