Run prettier, convert to standard eslint, add CI (#138)

* Run prettier and convert to standard eslint

* Add CI, fix broken test

* Update package.json

Co-authored-by: Lee Robinson <me@leerob.io>

* switch to single quote

* remove tests for now

Co-authored-by: Lee Robinson <me@leerob.io>
This commit is contained in:
Andrew Lisowski 2021-03-26 16:51:31 -07:00 committed by GitHub
parent 93431471c6
commit f7a75173f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 2262 additions and 1034 deletions

20
.eslintrc Normal file
View File

@ -0,0 +1,20 @@
{
"extends": ["airbnb-base", "prettier"],
"parser": "@babel/eslint-parser",
"parserOptions": {
"sourceType": "module"
},
"rules": {
"wrap-iife": "off",
"no-bitwise": "off",
"no-continue": "off",
"class-methods-use-this": "off",
"no-await-in-loop": "off",
"no-constant-condition": "off",
"no-param-reassign": "off",
"consistent-return": "off",
"no-restricted-syntax": "off",
"import/prefer-default-export": "off",
"camelcase": "off"
}
}

39
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: CI
on:
push:
branches:
- master
pull_request:
jobs:
test:
strategy:
fail-fast: false # prevent test to stop if one fails
matrix:
node-version: [10.x, 12.x, 14.x]
os: [ubuntu-latest] # Skip macos-latest, windows-latest for now
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.1.5
with:
node-version: ${{ matrix.node-version }}
- uses: actions/cache@v2
with:
path: ~/.pkg-cache/
key: ${{ matrix.os }}-${{ matrix.node-version }}
- name: Install deps
run: yarn install
- name: Lint
if: matrix['node-version'] == '14.x' && matrix['os'] == 'ubuntu-latest'
run: yarn lint
- name: Build
run: yarn build

1
.prettierignore Normal file
View File

@ -0,0 +1 @@
lib-es5

View File

@ -1,21 +1,26 @@
#!/usr/bin/env node
import { log } from './log.js';
import minimist from 'minimist';
import { need } from './index.js';
import { log } from './log';
import { need } from './index';
async function main () {
async function main() {
const argv = minimist(process.argv.slice(2), {
boolean: [ 'f', 'b' ],
string: [ 'n', 'p', 'a' ]
boolean: ['f', 'b'],
string: ['n', 'p', 'a'],
});
const nodeRange = argv.n || argv._.shift();
const platform = argv.p || argv._.shift();
const arch = argv.a || argv._.shift();
const forceFetch = argv.f;
const forceBuild = argv.b;
const local = await need({ nodeRange, platform,
arch, forceFetch, forceBuild });
const local = await need({
nodeRange,
platform,
arch,
forceFetch,
forceBuild,
});
log.info(local);
}

View File

@ -1,12 +1,13 @@
import os from 'os';
import { mkdirp, remove } from 'fs-extra';
import { progress, spawn } from './spawn.js';
import { copyFile } from './copy-file.js';
import { hostPlatform } from './system.js';
import { log } from './log.js';
import patchesJson from '../patches/patches.json';
import path from 'path';
import { tempPath } from './temp-path.js';
import thresholds from './thresholds.js';
import { progress, spawn } from './spawn';
import { copyFile } from './copy-file';
import { hostPlatform } from './system';
import { log } from './log';
import patchesJson from '../patches/patches.json';
import { tempPath } from './temp-path';
import thresholds from './thresholds';
let buildPath;
if (process.env.GITHUB_USERNAME) {
@ -19,35 +20,46 @@ const nodePath = path.join(buildPath, 'node');
const patchesPath = path.resolve(__dirname, '../patches');
const nodeRepo = 'https://github.com/nodejs/node';
async function gitClone (nodeVersion) {
async function gitClone(nodeVersion) {
log.info('Cloning Node.js repository from GitHub...');
const args = [ 'clone', '-b', nodeVersion, '--depth', '1', '--single-branch', '--bare', '--progress', nodeRepo, 'node/.git' ];
const args = [
'clone',
'-b',
nodeVersion,
'--depth',
'1',
'--single-branch',
'--bare',
'--progress',
nodeRepo,
'node/.git',
];
const promise = spawn('git', args, { cwd: buildPath });
progress(promise, thresholds('clone'));
await promise;
}
async function gitResetHard (nodeVersion) {
async function gitResetHard(nodeVersion) {
log.info(`Checking out ${nodeVersion}`);
const patches = patchesJson[nodeVersion];
const commit = patches.commit || nodeVersion;
const args = [ '--work-tree', '.', 'reset', '--hard', commit ];
const args = ['--work-tree', '.', 'reset', '--hard', commit];
await spawn('git', args, { cwd: nodePath });
}
async function applyPatches (nodeVersion) {
async function applyPatches(nodeVersion) {
log.info('Applying patches');
let patches = patchesJson[nodeVersion];
patches = patches.patches || patches;
if (patches.sameAs) patches = patchesJson[patches.sameAs];
for (const patch of patches) {
const patchPath = path.join(patchesPath, patch);
const args = [ '-p1', '-i', patchPath ];
const args = ['-p1', '-i', patchPath];
await spawn('patch', args, { cwd: nodePath });
}
}
async function compileOnWindows (nodeVersion, targetArch) {
async function compileOnWindows(nodeVersion, targetArch) {
const args = [];
args.push('/c', 'vcbuild.bat', targetArch, 'noetw');
const major = nodeVersion.match(/^v?(\d+)/)[1] | 0;
@ -59,12 +71,19 @@ async function compileOnWindows (nodeVersion, targetArch) {
return path.join(nodePath, 'out/Release/node.exe');
}
const { MAKE_JOB_COUNT = require('os').cpus().length } = process.env;
const { MAKE_JOB_COUNT = os.cpus().length } = process.env;
async function compileOnUnix (nodeVersion, targetArch) {
async function compileOnUnix(nodeVersion, targetArch) {
const args = [];
const cpu = { x86: 'ia32', x64: 'x64',
armv6: 'arm', armv7: 'arm', arm64: 'arm64', ppc64: 'ppc64', s390x: 's390x' }[targetArch];
const cpu = {
x86: 'ia32',
x64: 'x64',
armv6: 'arm',
armv7: 'arm',
arm64: 'arm64',
ppc64: 'ppc64',
s390x: 's390x',
}[targetArch];
args.push('--dest-cpu', cpu);
// first of all v8_inspector introduces the use
// of `prime_rehash_policy` symbol that requires
@ -78,25 +97,23 @@ async function compileOnUnix (nodeVersion, targetArch) {
// TODO same for windows?
await spawn('./configure', args, { cwd: nodePath });
const make = hostPlatform === 'freebsd' ? 'gmake' : 'make';
const promise = spawn(make, [ '-j', MAKE_JOB_COUNT ], { cwd: nodePath });
const promise = spawn(make, ['-j', MAKE_JOB_COUNT], { cwd: nodePath });
progress(promise, thresholds('make', nodeVersion));
await promise;
const output = path.join(nodePath, 'out/Release/node');
// https://github.com/mhart/alpine-node/blob/base-7.4.0/Dockerfile#L36
if (hostPlatform === 'alpine') await spawn('paxctl', [ '-cm', output ]);
if (hostPlatform === 'alpine') await spawn('paxctl', ['-cm', output]);
return output;
}
async function compile (nodeVersion, targetArch) {
async function compile(nodeVersion, targetArch) {
log.info('Compiling Node.js from sources...');
const win = hostPlatform === 'win';
if (win) return await compileOnWindows(nodeVersion, targetArch);
return await compileOnUnix(nodeVersion, targetArch);
if (win) return compileOnWindows(nodeVersion, targetArch);
return compileOnUnix(nodeVersion, targetArch);
}
export default async function build (
nodeVersion, targetArch, local
) {
export default async function build(nodeVersion, targetArch, local) {
await remove(buildPath);
await mkdirp(buildPath);
await gitClone(nodeVersion);

View File

@ -1,6 +1,6 @@
import { chmod, stat } from 'fs-extra';
export async function plusx (file) {
export async function plusx(file) {
const s = await stat(file);
const newMode = s.mode | 64 | 8 | 1;
if (s.mode === newMode) return;

View File

@ -1,10 +1,12 @@
import { mkdirp, remove } from 'fs-extra';
import { GitHub } from './github.js';
import assert from 'assert';
import { moveFile } from './copy-file.js';
import path from 'path';
/* eslint-disable no-underscore-dangle */
function uniqueName (name, names) {
import { mkdirp, remove } from 'fs-extra';
import assert from 'assert';
import path from 'path';
import { GitHub } from './github';
import { moveFile } from './copy-file';
function uniqueName(name, names) {
if (names.indexOf(name) < 0) return name;
let newName;
let counter = 0;
@ -16,18 +18,18 @@ function uniqueName (name, names) {
}
export class Cloud {
constructor ({ owner, repo }) {
constructor({ owner, repo }) {
this.gh = new GitHub({ owner, repo });
}
async _findRelease (tag) {
async _findRelease(tag) {
let release = await this.gh.getRelease(tag);
if (!release) release = await this.gh.getReleaseDraft(tag);
if (!release) release = await this.gh.createRelease(tag);
return release;
}
async alreadyUploaded (remote) {
async alreadyUploaded(remote) {
const release = await this._findRelease(remote.tag);
return release.assets.some(({ name }) => {
assert(name);
@ -35,7 +37,7 @@ export class Cloud {
});
}
async upload (local, remote) {
async upload(local, remote) {
const release = await this._findRelease(remote.tag);
const names = release.assets.map(({ name }) => {
assert(name);
@ -45,16 +47,16 @@ export class Cloud {
await this.gh.uploadAsset(local, release, name);
}
async uploadMany (items) {
async uploadMany(items) {
for (const item of items) {
const { local, remote } = item;
await this.upload(local, remote);
}
}
async download (remote, local) {
async download(remote, local) {
const { tag } = remote;
const tempFile = local + '.downloading';
const tempFile = `${local}.downloading`;
await mkdirp(path.dirname(tempFile));
const short = path.basename(local);
const ok = await this.gh.tryDirectly(tag, remote.name, tempFile, short);
@ -77,7 +79,7 @@ export class Cloud {
return true;
}
async downloadMany (items) {
async downloadMany(items) {
for (const item of items) {
const { remote, local } = item;
await this.download(remote, local);

View File

@ -1,9 +1,9 @@
import fs from 'fs-extra';
export function copyFile (src, dest) {
export function copyFile(src, dest) {
return fs.copy(src, dest);
}
export function moveFile (src, dest) {
export function moveFile(src, dest) {
return fs.move(src, dest);
}

View File

@ -1,25 +1,27 @@
/* eslint-disable camelcase */
import { log, wasReported } from './log.js';
import assert from 'assert';
import fs from 'fs';
import progress from 'request-progress';
import request from 'request';
import { log, wasReported } from './log';
export class GitHub {
constructor ({ owner, repo }) {
constructor({ owner, repo }) {
this.owner = owner;
this.repo = repo;
const { GITHUB_USERNAME, GITHUB_PASSWORD } = process.env;
const auth = { user: GITHUB_USERNAME, pass: GITHUB_PASSWORD };
this.request = request.defaults({
auth: auth.user ? auth : undefined,
headers: { 'User-Agent': `${this.owner}/${this.repo}/${GITHUB_USERNAME}` },
timeout: 30 * 1000
headers: {
'User-Agent': `${this.owner}/${this.repo}/${GITHUB_USERNAME}`,
},
timeout: 30 * 1000,
});
}
getRelease (tag) {
getRelease(tag) {
return new Promise((resolve, reject) => {
const url = `https://api.github.com/repos/${this.owner}/${this.repo}/releases/tags/${tag}`;
this.request(url, (error, response, body) => {
@ -33,7 +35,7 @@ export class GitHub {
});
}
getReleaseDraft (tag) {
getReleaseDraft(tag) {
return new Promise((resolve, reject) => {
const url = `https://api.github.com/repos/${this.owner}/${this.repo}/releases`;
this.request(url, (error, response, body) => {
@ -52,14 +54,14 @@ export class GitHub {
});
}
createRelease (tag) {
createRelease(tag) {
return new Promise((resolve, reject) => {
const form = JSON.stringify({
tag_name: tag,
target_commitish: 'master', // TODO maybe git rev-parse HEAD
name: tag,
draft: true,
prerelease: true
prerelease: true,
});
const url = `https://api.github.com/repos/${this.owner}/${this.repo}/releases`;
this.request.post(url, { form }, (error, response, body) => {
@ -71,54 +73,65 @@ export class GitHub {
});
}
uploadAsset (file, release, name) {
assert(!(/[\\/]/.test(name)));
uploadAsset(file, release, name) {
assert(!/[\\/]/.test(name));
return new Promise((resolve, reject) => {
fs.stat(file, (error, stat) => {
if (error) return reject(error);
const headers = {
'Content-Length': stat.size,
'Content-Type': 'application/octet-stream'
'Content-Type': 'application/octet-stream',
};
const rs = fs.createReadStream(file);
const subst = `?name=${name}`;
const url = release.upload_url.replace(/\{\?name,label\}/, subst);
const req = this.request.post(url, {
headers, timeout: 30 * 60 * 1000
}, (error2, response, body) => {
if (error2) return reject(wasReported(error2.message));
const asset = JSON.parse(body);
const { errors } = asset;
if (errors && errors[0]) return reject(wasReported(errors[0].code));
if (asset.message) return reject(wasReported(asset.message));
resolve(asset);
});
const req = this.request.post(
url,
{
headers,
timeout: 30 * 60 * 1000,
},
(error2, response, body) => {
if (error2) return reject(wasReported(error2.message));
const asset = JSON.parse(body);
const { errors } = asset;
if (errors && errors[0]) return reject(wasReported(errors[0].code));
if (asset.message) return reject(wasReported(asset.message));
resolve(asset);
}
);
rs.pipe(req);
});
});
}
downloadUrl (url, file, short) {
downloadUrl(url, file, short) {
log.enableProgress(short);
log.showProgress(0);
return new Promise((resolve, reject) => {
const headers = { Accept: 'application/octet-stream' };
const ws = fs.createWriteStream(file);
let result;
const req = progress(this.request.get(url, {
headers
}, (error, response) => {
if (error) {
log.disableProgress();
return reject(wasReported(error.message));
}
if (response.statusCode !== 200) {
log.disableProgress();
const message = `${response.statusCode} ${response.body}`;
return reject(wasReported(message, url));
}
result = response;
}));
const req = progress(
this.request.get(
url,
{
headers,
},
(error, response) => {
if (error) {
log.disableProgress();
return reject(wasReported(error.message));
}
if (response.statusCode !== 200) {
log.disableProgress();
const message = `${response.statusCode} ${response.body}`;
return reject(wasReported(message, url));
}
result = response;
}
)
);
req.on('progress', (state) => {
let p;
if (state.size && state.size.transferred && state.size.total) {
@ -140,13 +153,16 @@ export class GitHub {
});
}
async tryDirectly (tag, name, file, short) {
async tryDirectly(tag, name, file, short) {
try {
const url = `https://github.com/${this.owner}/${this.repo}/releases/download/${tag}/${name}`;
await this.downloadUrl(url, file, short);
return true;
} catch (error) {
log.info('Asset not found by direct link:', JSON.stringify({ tag, name }));
log.info(
'Asset not found by direct link:',
JSON.stringify({ tag, name })
);
return false;
}
}

View File

@ -1,50 +1,72 @@
import * as system from './system.js';
import { abiToNodeRange, hostArch, hostPlatform, // eslint-disable-line no-duplicate-imports
isValidNodeRange, knownArchs, toFancyArch, toFancyPlatform } from './system.js';
import { localPlace, remotePlace } from './places.js';
import { log, wasReported } from './log.js';
import { Cloud } from './cloud.js';
import build from './build.js';
import { exists } from 'fs-extra';
import patchesJson from '../patches/patches.json';
import path from 'path';
import semver from 'semver';
import {
abiToNodeRange,
hostArch,
hostPlatform, // eslint-disable-line no-duplicate-imports
isValidNodeRange,
knownArchs,
toFancyArch,
toFancyPlatform,
} from './system';
import * as system from './system';
import { localPlace, remotePlace } from './places';
import { log, wasReported } from './log';
import { Cloud } from './cloud';
import build from './build';
import patchesJson from '../patches/patches.json';
import { version } from '../package.json';
const cloud = new Cloud({ owner: 'zeit', repo: 'pkg-fetch' });
export async function need (opts = {}) { // eslint-disable-line complexity
let { nodeRange, platform, arch, forceFetch, forceBuild, dryRun } = opts;
export async function need(opts = {}) {
// eslint-disable-line complexity
const { forceFetch, forceBuild, dryRun } = opts;
let { nodeRange, platform, arch } = opts;
if (!nodeRange) throw wasReported('nodeRange not specified');
if (!platform) throw wasReported('platform not specified');
if (!arch) throw wasReported('arch not specified');
nodeRange = abiToNodeRange(nodeRange); // 'm48' -> 'node6'
if (!isValidNodeRange(nodeRange)) {
throw wasReported('nodeRange must start with \'node\'');
throw wasReported("nodeRange must start with 'node'");
}
if (nodeRange !== 'latest') {
nodeRange = 'v' + nodeRange.slice(4); // 'node6' -> 'v6' for semver
nodeRange = `v${nodeRange.slice(4)}`; // 'node6' -> 'v6' for semver
}
platform = toFancyPlatform(platform); // win32 -> win
arch = toFancyArch(arch); // ia32 -> x86
function satisfyingNodeVersion () {
function satisfyingNodeVersion() {
const versions = Object.keys(patchesJson)
.filter((nv) => semver.satisfies(nv, nodeRange) ||
nodeRange === 'latest')
.filter((nv) => semver.satisfies(nv, nodeRange) || nodeRange === 'latest')
.sort((nv1, nv2) => (semver.gt(nv1, nv2) ? 1 : -1));
return versions.pop();
}
const nodeVersion = satisfyingNodeVersion();
if (!nodeVersion) {
throw wasReported(`No available node version satisfies '${opts.nodeRange}'`);
throw wasReported(
`No available node version satisfies '${opts.nodeRange}'`
);
}
const fetched = localPlace({ from: 'fetched', arch, nodeVersion, platform, version });
const built = localPlace({ from: 'built', arch, nodeVersion, platform, version });
const fetched = localPlace({
from: 'fetched',
arch,
nodeVersion,
platform,
version,
});
const built = localPlace({
from: 'built',
arch,
nodeVersion,
platform,
version,
});
const remote = remotePlace({ arch, nodeVersion, platform, version });
let fetchFailed;
@ -73,13 +95,19 @@ export async function need (opts = {}) { // eslint-disable-line complexity
log.info('Building base binary from source:', path.basename(built));
}
if (hostPlatform !== platform) {
throw wasReported(`Not able to build for '${opts.platform}' here, only for '${hostPlatform}'`);
throw wasReported(
`Not able to build for '${opts.platform}' here, only for '${hostPlatform}'`
);
}
if (hostArch !== arch) {
throw wasReported(`Not able to build for '${opts.arch}' here, only for '${hostArch}'`);
throw wasReported(
`Not able to build for '${opts.arch}' here, only for '${hostArch}'`
);
}
if (knownArchs.indexOf(arch) < 0) {
throw wasReported(`Unknown arch '${opts.arch}'. Specify ${knownArchs.join(', ')}`);
throw wasReported(
`Unknown arch '${opts.arch}'. Specify ${knownArchs.join(', ')}`
);
}
if (dryRun) return 'built';

View File

@ -1,9 +1,11 @@
/* eslint-disable no-underscore-dangle, no-console */
import Progress from 'progress';
import assert from 'assert';
import chalk from 'chalk';
class Log {
_lines (lines) {
_lines(lines) {
if (lines === undefined) return;
if (!Array.isArray(lines)) {
console.log(` ${lines}`);
@ -14,29 +16,29 @@ class Log {
}
}
debug (text, lines) {
debug(text, lines) {
if (!this.debugMode) return;
console.log(`> ${chalk.green('[debug]')} ${text}`);
this._lines(lines);
}
info (text, lines) {
info(text, lines) {
console.log(`> ${text}`);
this._lines(lines);
}
warn (text, lines) {
warn(text, lines) {
console.log(`> ${chalk.blue('Warning')} ${text}`);
this._lines(lines);
}
error (text, lines) {
error(text, lines) {
if (text.stack) text = text.stack;
console.log(`> ${chalk.red('Error!')} ${text}`);
this._lines(lines);
}
enableProgress (text) {
enableProgress(text) {
assert(!this.bar);
text += ' '.repeat(28 - text.length);
this.bar = new Progress(` ${text} [:bar] :percent`, {
@ -44,16 +46,16 @@ class Log {
width: 20,
complete: '=',
incomplete: ' ',
total: 100
total: 100,
});
}
showProgress (percentage) {
showProgress(percentage) {
if (!this.bar) return;
this.bar.update(percentage / 100);
}
disableProgress () {
disableProgress() {
if (!this.bar) return;
// avoid empty line
if (!this.bar.complete) {
@ -65,11 +67,10 @@ class Log {
export const log = new Log();
export function wasReported (error, lines) {
export function wasReported(error, lines) {
if (error === undefined) {
error = new Error('No message');
} else
if (typeof error === 'string') {
} else if (typeof error === 'string') {
log.error(error, lines);
error = new Error(error);
}

View File

@ -3,28 +3,30 @@ import expandTemplate from 'expand-template';
import os from 'os';
import path from 'path';
import placesJson from '../places.json';
const expand = expandTemplate();
const { PKG_CACHE_PATH } = process.env;
const IGNORE_TAG = Boolean(process.env.PKG_IGNORE_TAG);
const cachePath = PKG_CACHE_PATH ||
path.join(os.homedir(), '.pkg-cache');
const cachePath = PKG_CACHE_PATH || path.join(os.homedir(), '.pkg-cache');
function tagFromVersion (version) {
function tagFromVersion(version) {
const mj = major(version);
const mn = minor(version);
return `v${mj}.${mn}`;
}
export function localPlace (opts) {
export function localPlace(opts) {
const p = placesJson.localPlace;
const { version } = opts;
const atHome = IGNORE_TAG ? path.join(cachePath, p) : path.join(cachePath, tagFromVersion(version), p);
const atHome = IGNORE_TAG
? path.join(cachePath, p)
: path.join(cachePath, tagFromVersion(version), p);
return expand(path.resolve(atHome), opts);
}
export function remotePlace (opts) {
export function remotePlace(opts) {
const p = placesJson.remotePlace;
const { version } = opts;
const tag = tagFromVersion(version);

View File

@ -1,25 +1,27 @@
import byline from 'byline';
import chip from 'child_process';
import fs from 'fs';
import { log } from './log.js';
import { log } from './log';
const MAX_LINES = 20;
const DEBUG_THRESHOLDS = false;
function errorLines (lines) {
return lines.slice(-MAX_LINES)
.map((line) => line[1]).join('\n');
function errorLines(lines) {
return lines
.slice(-MAX_LINES)
.map((line) => line[1])
.join('\n');
}
export function spawn (cmd, args, opts) {
export function spawn(cmd, args, opts) {
const child = chip.spawn(cmd, args, opts);
const stdout = byline(child.stdout);
const stderr = byline(child.stderr);
const lines = [];
let onData = function (data) {
const time = (new Date()).getTime();
lines.push([ time, data.toString() ]); // TODO chalk stdout/stderr?
let onData = (data) => {
const time = new Date().getTime();
lines.push([time, data.toString()]); // TODO chalk stdout/stderr?
const { thresholds } = this; // eslint-disable-line no-invalid-this
if (thresholds) {
for (const key in thresholds) {
@ -27,9 +29,9 @@ export function spawn (cmd, args, opts) {
const p = thresholds[key];
log.showProgress(p);
if (DEBUG_THRESHOLDS) {
lines.push([ time, '************' ]);
lines.push([ time, p + ': ' + key ]);
lines.push([ time, '************' ]);
lines.push([time, '************']);
lines.push([time, `${p}: ${key}`]);
lines.push([time, '************']);
}
}
}
@ -38,11 +40,13 @@ export function spawn (cmd, args, opts) {
const promise = new Promise((resolve, reject) => {
child.on('error', (error) => {
// eslint-disable-next-line no-console
console.error(errorLines(lines)); // dont use `log` here
reject(error);
});
child.on('close', (code) => {
if (code) {
// eslint-disable-next-line no-console
console.error(errorLines(lines)); // dont use `log` here
return reject(new Error(`${cmd} failed with code ${code}`));
}
@ -59,19 +63,22 @@ export function spawn (cmd, args, opts) {
return promise;
}
export function progress (promise, thresholds) {
export function progress(promise, thresholds) {
promise.thresholds = thresholds;
const { child, lines } = promise;
log.enableProgress(promise.child.spawnfile);
log.showProgress(0);
const start = (new Date()).getTime();
const start = new Date().getTime();
child.on('close', () => {
if (DEBUG_THRESHOLDS) {
const finish = (new Date()).getTime();
const content = lines.map((line) =>
((100 * (line[0] - start) / (finish - start)) | 0) + ': ' + line[1]
).join('\n');
fs.writeFileSync(child.spawnfile + '.debug', content);
const finish = new Date().getTime();
const content = lines
.map(
(line) =>
`${((100 * (line[0] - start)) / (finish - start)) | 0}: ${line[1]}`
)
.join('\n');
fs.writeFileSync(`${child.spawnfile}.debug`, content);
}
log.showProgress(100);
log.disableProgress();

View File

@ -1,11 +1,11 @@
import fs from 'fs';
import { spawnSync } from 'child_process';
function getHostAbi () {
return 'm' + process.versions.modules;
function getHostAbi() {
return `m${process.versions.modules}`;
}
export function abiToNodeRange (abi) {
export function abiToNodeRange(abi) {
if (/^m?14/.test(abi)) return 'node0.12';
if (/^m?46/.test(abi)) return 'node4';
if (/^m?47/.test(abi)) return 'node5';
@ -21,13 +21,13 @@ export function abiToNodeRange (abi) {
return abi;
}
export function isValidNodeRange (nodeRange) {
export function isValidNodeRange(nodeRange) {
if (nodeRange === 'latest') return true;
if (!(/^node/.test(nodeRange))) return false;
if (!/^node/.test(nodeRange)) return false;
return true;
}
export function toFancyPlatform (platform) {
export function toFancyPlatform(platform) {
if (platform === 'darwin') return 'macos';
if (platform === 'lin') return 'linux';
if (platform === 'mac') return 'macos';
@ -37,36 +37,36 @@ export function toFancyPlatform (platform) {
return platform;
}
function detectAlpine () {
function detectAlpine() {
const { platform } = process;
if (platform !== 'linux') return false;
// https://github.com/sass/node-sass/issues/1589#issuecomment-265292579
const ldd = spawnSync('ldd').stderr.toString();
if (/\bmusl\b/.test(ldd)) return true;
const lddNode = spawnSync('ldd', [ process.execPath ]).stdout.toString();
const lddNode = spawnSync('ldd', [process.execPath]).stdout.toString();
return /\bmusl\b/.test(lddNode);
}
const isAlpine = detectAlpine();
function getHostPlatform () {
function getHostPlatform() {
const { platform } = process;
if (isAlpine) return 'alpine';
return toFancyPlatform(platform);
}
function getKnownPlatforms () {
return [ 'alpine', 'freebsd', 'linux', 'macos', 'win' ];
function getKnownPlatforms() {
return ['alpine', 'freebsd', 'linux', 'macos', 'win'];
}
export function toFancyArch (arch) {
export function toFancyArch(arch) {
if (arch === 'ia32') return 'x86';
if (arch === 'x86_64') return 'x64';
return arch;
}
function getArmUnameArch () {
const uname = spawnSync('uname', [ '-a' ]);
function getArmUnameArch() {
const uname = spawnSync('uname', ['-a']);
if (uname.error) return '';
let unameOut = uname.stdout && uname.stdout.toString();
unameOut = (unameOut || '').toLowerCase();
@ -76,30 +76,30 @@ function getArmUnameArch () {
return '';
}
function getArmHostArch () {
function getArmHostArch() {
const cpu = fs.readFileSync('/proc/cpuinfo', 'utf8');
if (cpu.indexOf('vfpv3') >= 0) return 'armv7';
let name = cpu.split('model name')[1];
if (name) name = name.split(':')[1];
if (name) name = name.split('\n')[0];
if (name) [, name] = name.split(':');
if (name) [name] = name.split('\n');
if (name && name.indexOf('ARMv7') >= 0) return 'armv7';
return 'armv6';
}
function getHostArch () {
function getHostArch() {
const { arch } = process;
if (arch === 'arm') return getArmUnameArch() || getArmHostArch();
return toFancyArch(arch);
}
function getTargetArchs () {
function getTargetArchs() {
const arch = getHostArch();
if (arch === 'x64') return [ 'x64', 'x86' ];
return [ arch ];
if (arch === 'x64') return ['x64', 'x86'];
return [arch];
}
function getKnownArchs () {
return [ 'x64', 'x86', 'armv6', 'armv7', 'arm64', 'ppc64', 's390x' ];
function getKnownArchs() {
return ['x64', 'x86', 'armv6', 'armv7', 'arm64', 'ppc64', 's390x'];
}
export const hostAbi = getHostAbi();

View File

@ -1,5 +1,5 @@
import uniqueTempDir from 'unique-temp-dir';
export function tempPath (...args) {
export function tempPath(...args) {
return uniqueTempDir(...args);
}

View File

@ -3,73 +3,108 @@
import assert from 'assert';
export default function thresholds (cmd, nodeVersion) {
export default function thresholds(cmd, nodeVersion) {
if (cmd === 'clone') {
return {
'ving objects: 0%': 0, 'ving objects: 1%': 1, 'ving objects: 6%': 5,
'ving objects: 12%': 10, 'ving objects: 25%': 20, 'ving objects: 50%': 40,
'ving objects: 75%': 60, 'deltas: 0%': 80, 'deltas: 50%': 90
'ving objects: 0%': 0,
'ving objects: 1%': 1,
'ving objects: 6%': 5,
'ving objects: 12%': 10,
'ving objects: 25%': 20,
'ving objects: 50%': 40,
'ving objects: 75%': 60,
'deltas: 0%': 80,
'deltas: 50%': 90,
};
} else
}
if (cmd === 'vcbuild') {
if (/^v?0/.test(nodeVersion)) {
return {
'http_parser.vcxproj ->': 1, 'openssl.vcxproj ->': 9,
'v8_base.vcxproj ->': 55, 'mksnapshot.vcxproj ->': 76,
'node\\Release\\node.exp': 90
'http_parser.vcxproj ->': 1,
'openssl.vcxproj ->': 9,
'v8_base.vcxproj ->': 55,
'mksnapshot.vcxproj ->': 76,
'node\\Release\\node.exp': 90,
};
} else
}
if (/^v?4/.test(nodeVersion)) {
return {
'http_parser.vcxproj ->': 1, 'hydrogen-representation-changes.cc': 13,
'openssl.vcxproj ->': 21, 'v8_base_0.vcxproj ->': 35,
'build\\Release\\mksnapshot.lib': 57, 'mksnapshot.vcxproj ->': 67,
'node\\Release\\node.exp': 85, 'cctest.vcxproj ->': 97
'http_parser.vcxproj ->': 1,
'hydrogen-representation-changes.cc': 13,
'openssl.vcxproj ->': 21,
'v8_base_0.vcxproj ->': 35,
'build\\Release\\mksnapshot.lib': 57,
'mksnapshot.vcxproj ->': 67,
'node\\Release\\node.exp': 85,
'cctest.vcxproj ->': 97,
};
} else
}
if (/^v?6/.test(nodeVersion)) {
return {
'http_parser.vcxproj ->': 1, 'openssl.vcxproj ->': 4,
'icudata.vcxproj ->': 10, 'hydrogen-representation-changes.cc': 15,
'interface-descriptors-x64.cc': 27, 'v8_base_0.vcxproj ->': 41,
'build\\Release\\mksnapshot.lib': 55, 'mksnapshot.vcxproj ->': 66,
'node\\Release\\node.exp': 82, 'cctest.vcxproj ->': 95
'http_parser.vcxproj ->': 1,
'openssl.vcxproj ->': 4,
'icudata.vcxproj ->': 10,
'hydrogen-representation-changes.cc': 15,
'interface-descriptors-x64.cc': 27,
'v8_base_0.vcxproj ->': 41,
'build\\Release\\mksnapshot.lib': 55,
'mksnapshot.vcxproj ->': 66,
'node\\Release\\node.exp': 82,
'cctest.vcxproj ->': 95,
};
} else {
return {};
}
} else
return {};
}
if (cmd === 'make') {
if (/^v?0/.test(nodeVersion)) {
return {
'openssl/crypto/ex_data.o.d.raw': 10, 'v8/src/api.o.d.raw': 20,
'v8/src/compiler/js-graph.o.d.raw': 30, 'v8/src/debug.o.d.raw': 40,
'v8/src/heap/spaces.o.d.raw': 50, 'v8/src/hydrogen-sce.o.d.raw': 60,
'v8/src/parser.o.d.raw': 70, 'v8/src/token.o.d.raw': 80,
'v8/src/x64/stub-cache-x64.o.d.raw': 90
'openssl/crypto/ex_data.o.d.raw': 10,
'v8/src/api.o.d.raw': 20,
'v8/src/compiler/js-graph.o.d.raw': 30,
'v8/src/debug.o.d.raw': 40,
'v8/src/heap/spaces.o.d.raw': 50,
'v8/src/hydrogen-sce.o.d.raw': 60,
'v8/src/parser.o.d.raw': 70,
'v8/src/token.o.d.raw': 80,
'v8/src/x64/stub-cache-x64.o.d.raw': 90,
};
} else
}
if (/^v?4/.test(nodeVersion)) {
return {
'v8/src/compiler/code-generator.o.d.raw': 10, 'v8/src/compiler/operator.o.d.raw': 20,
'v8/src/factory.o.d.raw': 30, 'v8/src/hydrogen.o.d.raw': 40,
'v8/src/liveedit.o.d.raw': 50, 'v8/src/runtime/runtime-function.o.d.raw': 60,
'v8/src/v8.o.d.raw': 70, 'v8_nosnapshot/gen/libraries.o.d.raw': 80,
'openssl/crypto/ex_data.o.d.raw': 90
'v8/src/compiler/code-generator.o.d.raw': 10,
'v8/src/compiler/operator.o.d.raw': 20,
'v8/src/factory.o.d.raw': 30,
'v8/src/hydrogen.o.d.raw': 40,
'v8/src/liveedit.o.d.raw': 50,
'v8/src/runtime/runtime-function.o.d.raw': 60,
'v8/src/v8.o.d.raw': 70,
'v8_nosnapshot/gen/libraries.o.d.raw': 80,
'openssl/crypto/ex_data.o.d.raw': 90,
};
} else
}
if (/^v?6/.test(nodeVersion)) {
return {
'icuucx/deps/icu-small/source/common/parsepos.o.d.raw': 10, 'v8/src/api.o.d.raw': 20,
'v8/src/compiler/graph-replay.o.d.raw': 30, 'v8/src/compiler.o.d.raw': 40,
'v8/src/date.o.d.raw': 50, 'v8/src/isolate.o.d.raw': 60,
'v8/src/runtime/runtime-function.o.d.raw': 70, 'v8/src/x64/assembler-x64.o.d.raw': 80,
'icui18n/deps/icu-small/source/i18n/search.o.d.raw': 90
'icuucx/deps/icu-small/source/common/parsepos.o.d.raw': 10,
'v8/src/api.o.d.raw': 20,
'v8/src/compiler/graph-replay.o.d.raw': 30,
'v8/src/compiler.o.d.raw': 40,
'v8/src/date.o.d.raw': 50,
'v8/src/isolate.o.d.raw': 60,
'v8/src/runtime/runtime-function.o.d.raw': 70,
'v8/src/x64/assembler-x64.o.d.raw': 80,
'icui18n/deps/icu-small/source/i18n/search.o.d.raw': 90,
};
} else {
return {};
}
} else {
assert(false);
return {};
}
assert(false);
}

View File

@ -1,16 +1,16 @@
import { hostPlatform, targetArchs } from './system.js';
import { localPlace, remotePlace } from './places.js';
import { log, wasReported } from './log.js';
import { Cloud } from './cloud.js';
import build from './build.js';
import patchesJson from '../patches/patches.json';
import path from 'path';
import { verify } from './verify.js';
import { hostPlatform, targetArchs } from './system';
import { localPlace, remotePlace } from './places';
import { log, wasReported } from './log';
import { Cloud } from './cloud';
import build from './build';
import patchesJson from '../patches/patches.json';
import { verify } from './verify';
import { version } from '../package.json';
const cloud = new Cloud({ owner: 'zeit', repo: 'pkg-fetch' });
export function dontBuild (nodeVersion, targetPlatform, targetArch) {
export function dontBuild(nodeVersion, targetPlatform, targetArch) {
// binaries are not provided for x86 anymore
if (targetPlatform !== 'win' && targetArch === 'x86') return true;
// https://support.apple.com/en-us/HT201948
@ -20,23 +20,36 @@ export function dontBuild (nodeVersion, targetPlatform, targetArch) {
// node 0.12 does not compile on arm
if (/^arm/.test(targetArch) && major === 0) return true;
if (targetPlatform === 'freebsd' && major < 4) return true;
if (targetPlatform === 'alpine' &&
(targetArch !== 'x64' || major < 6)) return true;
if (targetPlatform === 'alpine' && (targetArch !== 'x64' || major < 6))
return true;
return false;
}
export async function main () {
export async function main() {
if (!process.env.GITHUB_USERNAME) {
throw wasReported('No github credentials. Upload will fail!');
}
for (const nodeVersion in patchesJson) {
if (!patchesJson[nodeVersion]) {
continue;
}
for (const targetArch of targetArchs) {
if (dontBuild(nodeVersion, hostPlatform, targetArch)) continue;
const local = localPlace({ from: 'built', arch: targetArch,
nodeVersion, platform: hostPlatform, version });
const remote = remotePlace({ arch: targetArch,
nodeVersion, platform: hostPlatform, version });
const local = localPlace({
from: 'built',
arch: targetArch,
nodeVersion,
platform: hostPlatform,
version,
});
const remote = remotePlace({
arch: targetArch,
nodeVersion,
platform: hostPlatform,
version,
});
if (await cloud.alreadyUploaded(remote)) continue;
const short = path.basename(local);
log.info(`Building ${short}...`);

View File

@ -1,5 +1,5 @@
import { plusx } from './chmod.js';
import { spawn } from './spawn.js';
import { plusx } from './chmod';
import { spawn } from './spawn';
const script = `
var vm = require('vm');
@ -93,8 +93,9 @@ const script = `
console.log('ok');
`;
export async function verify (local) {
export async function verify(local) {
await plusx(local);
await spawn(local, [ '-e', script ],
{ env: { PKG_EXECPATH: 'PKG_INVOKE_NODEJS' } });
await spawn(local, ['-e', script], {
env: { PKG_EXECPATH: 'PKG_INVOKE_NODEJS' },
});
}

View File

@ -43,30 +43,32 @@
"unique-temp-dir": "^1.0.0"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.9.0",
"@babel/plugin-transform-async-to-generator": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.9.0",
"@babel/preset-env": "^7.9.0",
"@babel/register": "^7.9.0",
"@babel/cli": "^7.13.10",
"@babel/core": "^7.13.10",
"@babel/eslint-parser": "^7.13.10",
"@babel/plugin-transform-async-to-generator": "^7.13.0",
"@babel/plugin-transform-runtime": "^7.13.10",
"@babel/preset-env": "^7.13.12",
"@babel/register": "^7.13.8",
"ava": "^2.4.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.8.0",
"eslint-config-klopov": "^1.0.2"
"eslint": "^7.22.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-import": "^2.22.1",
"lint-staged": ">=10",
"prettier": "^2.2.1",
"simple-git-hooks": ">=2.0.3"
},
"scripts": {
"babel": "node test/rimraf-es5.js && babel lib --out-dir lib-es5",
"build": "node test/rimraf-es5.js && babel lib --out-dir lib-es5",
"bin": "node lib-es5/bin.js",
"lint": "eslint-klopov . || true",
"prepare": "npm run babel",
"prepublishOnly": "eslint-klopov . && npm test",
"lint": "eslint lib || true",
"prepare": "npm run build",
"prepublishOnly": "npm run lint",
"start": "node lib-es5/upload.js",
"test": "ava"
},
"eslintConfig": {
"extends": "klopov",
"parser": "babel-eslint"
},
"ava": {
"failFast": true,
"files": [
@ -75,5 +77,14 @@
"require": [
"@babel/register"
]
},
"prettier": {
"singleQuote": true
},
"simple-git-hooks": {
"pre-commit": "npx lint-staged"
},
"lint-staged": {
"*.{js,css,md}": "prettier --write"
}
}

View File

@ -1,52 +1,20 @@
{
"v14.16.0": [
"node.v14.16.0.cpp.patch"
],
"v14.4.0": [
"node.v14.4.0.cpp.patch"
],
"v14.0.0": [
"node.v14.0.0.cpp.patch"
],
"v13.12.0": [
"node.v13.12.0.cpp.patch"
],
"v12.21.0": [
"node.v12.21.0.cpp.patch"
],
"v12.18.1": [
"node.v12.18.1.cpp.patch"
],
"v12.16.1": [
"node.v12.16.1.cpp.patch"
],
"v12.13.1": [
"node.v12.13.1.cpp.patch"
],
"v12.2.0": [
"node.v12.2.0.cpp.patch"
],
"v10.21.0": [
"node.v10.21.0.cpp.patch"
],
"v10.17.0": [
"node.v10.17.0.cpp.patch"
],
"v10.15.3": [
"node.v10.15.3.cpp.patch"
],
"v8.17.0": [
"node.v8.17.0.cpp.patch"
],
"v8.16.2": [
"node.v8.16.2.cpp.patch"
],
"v8.16.0": [
"node.v8.16.0.cpp.patch"
],
"v6.17.1": [
"node.v6.17.1.cpp.patch"
],
"v14.16.0": ["node.v14.16.0.cpp.patch"],
"v14.4.0": ["node.v14.4.0.cpp.patch"],
"v14.0.0": ["node.v14.0.0.cpp.patch"],
"v13.12.0": ["node.v13.12.0.cpp.patch"],
"v12.21.0": ["node.v12.21.0.cpp.patch"],
"v12.18.1": ["node.v12.18.1.cpp.patch"],
"v12.16.1": ["node.v12.16.1.cpp.patch"],
"v12.13.1": ["node.v12.13.1.cpp.patch"],
"v12.2.0": ["node.v12.2.0.cpp.patch"],
"v10.21.0": ["node.v10.21.0.cpp.patch"],
"v10.17.0": ["node.v10.17.0.cpp.patch"],
"v10.15.3": ["node.v10.15.3.cpp.patch"],
"v8.17.0": ["node.v8.17.0.cpp.patch"],
"v8.16.2": ["node.v8.16.2.cpp.patch"],
"v8.16.0": ["node.v8.16.0.cpp.patch"],
"v6.17.1": ["node.v6.17.1.cpp.patch"],
"v4.9.1": [
"backport.R32768.patch",
"backport.PR4777.for.N4.patch",

View File

@ -1,29 +1,26 @@
class LogMock {
constructor (actions) {
constructor(actions) {
this.actions = actions;
}
info (text) {
info(text) {
this.actions.push(`> ${text}`);
}
warn (text) {
warn(text) {
this.actions.push(`> WARN ${text}`);
}
error (text) {
error(text) {
if (text.message) text = text.message;
this.actions.push(`> ERR! ${text}`);
}
enableProgress () {
}
enableProgress() {}
showProgress () {
}
showProgress() {}
disableProgress () {
}
disableProgress() {}
}
export default LogMock;

View File

@ -16,11 +16,6 @@
"backport.R25444.patch",
"node.v0.12.15.patch"
],
"v4.4.7": [
"backport.R32768.v8=4.5.patch",
"node.v4.4.7.patch"
],
"v6.3.1": [
"node.v6.3.1.patch"
]
"v4.4.7": ["backport.R32768.v8=4.5.patch", "node.v4.4.7.patch"],
"v6.3.1": ["node.v6.3.1.patch"]
}

View File

@ -1,14 +1,14 @@
/* eslint-disable camelcase */
import LogMock from './log-mock.js';
import assert from 'assert';
import fs from 'fs';
import path from 'path';
import test from 'ava';
import LogMock from './log-mock';
process.env.GITHUB_USERNAME = 'suppress upload error';
function relative (p) {
function relative(p) {
const p2 = path.relative(__dirname, p);
return p2.replace(/\\/g, '/');
}
@ -23,16 +23,20 @@ const patchesJson = require('../patches/patches.json');
const newPatchesJson = require('./patches.json');
for (const nodeVersion in patchesJson) {
delete patchesJson[nodeVersion];
if (patchesJson[nodeVersion]) {
delete patchesJson[nodeVersion];
}
}
for (const nodeVersion in newPatchesJson) {
patchesJson[nodeVersion] = newPatchesJson[nodeVersion];
if (newPatchesJson[nodeVersion]) {
patchesJson[nodeVersion] = newPatchesJson[nodeVersion];
}
}
require('../lib/log.js').log = new LogMock(actions);
require('../lib/spawn.js').spawn = function (cmd, args, opts) {
require('../lib/spawn.js').spawn = (cmd, args, opts) => {
assert(opts);
assert(opts.cwd);
if (cmd === 'git' && args[0] === 'clone') {
@ -53,53 +57,56 @@ require('../lib/spawn.js').spawn = function (cmd, args, opts) {
if (opts.cwd) {
opts.cwd = relative(opts.cwd);
}
actions.push([ cmd, args.join(' '), JSON.stringify(opts) ].join(' '));
actions.push([cmd, args.join(' '), JSON.stringify(opts)].join(' '));
};
require('../lib/spawn.js').progress = function () {
};
require('../lib/spawn.js').progress = () => {};
require('../lib/verify.js').verify = function () {
require('../lib/verify.js').verify = () => {
actions.push('verify');
};
require('../lib/copy-file.js').copyFile = function (src, dest) {
require('../lib/copy-file.js').copyFile = (src, dest) => {
src = relative(src);
const shortDest = path.basename(path.dirname(dest)) + '/' + path.basename(dest);
actions.push([ 'copyFile', src, shortDest ].join(' ')); // full dest is flaky
const shortDest = `${path.basename(path.dirname(dest))}/${path.basename(
dest
)}`;
actions.push(['copyFile', src, shortDest].join(' ')); // full dest is flaky
lastLocal = dest;
};
require('../lib/github.js').GitHub = class {
getRelease (tag) {
actions.push([ 'getRelease', tag ].join(' '));
getRelease(tag) {
actions.push(['getRelease', tag].join(' '));
return undefined;
}
getReleaseDraft (tag) {
actions.push([ 'getReleaseDraft', tag ].join(' '));
getReleaseDraft(tag) {
actions.push(['getReleaseDraft', tag].join(' '));
return undefined;
}
createRelease (tag) {
actions.push([ 'createRelease', tag ].join(' '));
createRelease(tag) {
actions.push(['createRelease', tag].join(' '));
return { upload_url: 'https://example.com/assets{?name,label}', assets };
}
uploadAsset (local, release, name) {
uploadAsset(local, release, name) {
assert(local === lastLocal); // test it here. too flaky to push to actions
actions.push([ 'uploadAsset', JSON.stringify(release), name ].join(' '));
actions.push(['uploadAsset', JSON.stringify(release), name].join(' '));
assets.push({ name });
}
};
test('upload', async (t) => {
if (process.platform !== 'darwin' ||
process.arch !== 'x64') {
if (process.platform !== 'darwin' || process.arch !== 'x64') {
throw new Error('RUN THE TEST ONLY ON MACOS-X64');
}
const { main } = require('../lib/upload.js');
process.env.MAKE_JOB_COUNT = 1;
// eslint-disable-next-line global-require
const { main } = require('../lib/upload');
await main();
const mustBe = [
'getRelease v1337.2',
@ -179,10 +186,10 @@ test('upload', async (t) => {
'getRelease v1337.2',
'getReleaseDraft v1337.2',
'createRelease v1337.2',
'uploadAsset {"upload_url":"https://example.com/assets{?name,label}","assets":[{"name":"uploaded-v1337.2-node-v0.12.15-macos-x64"},{"name":"uploaded-v1337.2-node-v4.4.7-macos-x64"}]} uploaded-v1337.2-node-v6.3.1-macos-x64'
'uploadAsset {"upload_url":"https://example.com/assets{?name,label}","assets":[{"name":"uploaded-v1337.2-node-v0.12.15-macos-x64"},{"name":"uploaded-v1337.2-node-v4.4.7-macos-x64"}]} uploaded-v1337.2-node-v6.3.1-macos-x64',
];
for (let i = 0; i < actions.length; i += 1) {
t.is(actions[i] + ` [[[${i}]]]`, mustBe[i] + ` [[[${i}]]]`);
t.is(`${actions[i]} [[[${i}]]]`, `${mustBe[i]} [[[${i}]]]`);
}
t.is(actions.length, mustBe.length);
});

2455
yarn.lock

File diff suppressed because it is too large Load Diff