Use semver ranges

This commit is contained in:
Ron Buckton
2018-08-28 17:24:02 -07:00
parent 04a524511e
commit 37c33f4369
27 changed files with 146 additions and 244 deletions

View File

@@ -1,71 +0,0 @@
// @ts-check
const symSource = Symbol("CancelToken.source");
const symToken = Symbol("CancelSource.token");
const symCancellationRequested = Symbol("CancelSource.cancellationRequested");
const symCancellationCallbacks = Symbol("CancelSource.cancellationCallbacks");
class CancelSource {
constructor() {
this[symCancellationRequested] = false;
this[symCancellationCallbacks] = [];
}
/** @type {CancelToken} */
get token() {
return this[symToken] || (this[symToken] = new CancelToken(this));
}
cancel() {
if (!this[symCancellationRequested]) {
this[symCancellationRequested] = true;
for (const callback of this[symCancellationCallbacks]) {
callback();
}
}
}
}
exports.CancelSource = CancelSource;
class CancelToken {
/**
* @param {CancelSource} source
*/
constructor(source) {
if (source[symToken]) return source[symToken];
this[symSource] = source;
}
/** @type {boolean} */
get cancellationRequested() {
return this[symSource][symCancellationRequested];
}
/**
* @param {() => void} callback
*/
subscribe(callback) {
const source = this[symSource];
if (source[symCancellationRequested]) {
callback();
return;
}
source[symCancellationCallbacks].push(callback);
return {
unsubscribe() {
const index = source[symCancellationCallbacks].indexOf(callback);
if (index !== -1) source[symCancellationCallbacks].splice(index, 1);
}
};
}
}
exports.CancelToken = CancelToken;
class CancelError extends Error {
constructor(message = "Operation was canceled") {
super(message);
this.name = "CancelError";
}
}
exports.CancelError = CancelError;

View File

@@ -1,62 +0,0 @@
// @ts-check
const { CancelToken } = require("./cancellation");
class Countdown {
constructor(initialCount = 0) {
if (initialCount < 0) throw new Error();
this._remainingCount = initialCount;
this._promise = undefined;
this._resolve = undefined;
}
get remainingCount() {
return this._remainingCount;
}
add(count = 1) {
if (count < 1 || !isFinite(count) || Math.trunc(count) !== count) throw new Error();
if (this._remainingCount === 0) {
this._promise = undefined;
this._resolve = undefined;
}
this._remainingCount += count;
}
signal(count = 1) {
if (count < 1 || !isFinite(count) || Math.trunc(count) !== count) throw new Error();
if (this._remainingCount - count < 0) throw new Error();
this._remainingCount -= count;
if (this._remainingCount == 0) {
if (this._resolve) {
this._resolve();
}
return true;
}
return false;
}
/** @param {CancelToken} [token] */
wait(token) {
if (!this._promise) {
this._promise = new Promise(resolve => { this._resolve = resolve; });
}
if (this._remainingCount === 0) {
this._resolve();
}
if (!token) return this._promise;
return new Promise((resolve, reject) => {
const subscription = token.subscribe(reject);
this._promise.then(
value => {
subscription.unsubscribe();
resolve(value);
},
error => {
subscription.unsubscribe();
reject(error);
});
});
}
}
exports.Countdown = Countdown;

View File

@@ -3,7 +3,7 @@ const cp = require("child_process");
const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util)
const isWin = /^win/.test(process.platform);
const chalk = require("./chalk");
const { CancelToken, CancelError } = require("./cancellation");
const { CancelError } = require("prex");
module.exports = exec;
@@ -15,16 +15,20 @@ module.exports = exec;
*
* @typedef ExecOptions
* @property {boolean} [ignoreExitCode]
* @property {CancelToken} [cancelToken]
* @property {import("prex").CancellationToken} [cancelToken]
*/
function exec(cmd, args, options = {}) {
return /**@type {Promise<{exitCode: number}>}*/(new Promise((resolve, reject) => {
if (options.cancelToken) {
options.cancelToken.throwIfCancellationRequested();
}
log(`> ${chalk.green(cmd)} ${args.join(" ")}`);
// TODO (weswig): Update child_process types to add windowsVerbatimArguments to the type definition
const subshellFlag = isWin ? "/c" : "-c";
const command = isWin ? [possiblyQuote(cmd), ...args] : [`${cmd} ${args.join(" ")}`];
const ex = cp.spawn(isWin ? "cmd" : "/bin/sh", [subshellFlag, ...command], { stdio: "inherit", windowsVerbatimArguments: true });
const subscription = options.cancelToken && options.cancelToken.subscribe(() => {
const subscription = options.cancelToken && options.cancelToken.register(() => {
log(`${chalk.red("killing")} '${chalk.green(cmd)} ${args.join(" ")}'...`);
ex.kill("SIGINT");
ex.kill("SIGTERM");
@@ -32,7 +36,7 @@ function exec(cmd, args, options = {}) {
reject(new CancelError());
});
ex.on("exit", exitCode => {
subscription && subscription.unsubscribe();
if (subscription) subscription.unregister();
if (exitCode === 0 || options.ignoreExitCode) {
resolve({ exitCode });
}
@@ -41,7 +45,7 @@ function exec(cmd, args, options = {}) {
}
});
ex.on("error", error => {
subscription && subscription.unsubscribe();
if (subscription) subscription.unregister();
reject(error);
});
}));

View File

@@ -3,6 +3,8 @@ const path = require("path");
const fs = require("fs");
const gulp = require("./gulp");
const gulpif = require("gulp-if");
const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util)
const chalk = require("./chalk");
const sourcemaps = require("gulp-sourcemaps");
const merge2 = require("merge2");
const tsc = require("gulp-typescript");
@@ -12,42 +14,51 @@ const ts = require("../../lib/typescript");
const del = require("del");
const needsUpdate = require("./needsUpdate");
const mkdirp = require("./mkdirp");
const prettyTime = require("pretty-hrtime");
const { reportDiagnostics } = require("./diagnostics");
const { Countdown } = require("./countdown");
const { CancelToken } = require("./cancellation");
const { CountdownEvent, Pulsar } = require("prex");
const countdown = new Countdown();
const workStartedEvent = new Pulsar();
const countdown = new CountdownEvent(0);
class CompilationGulp extends gulp.Gulp {
constructor() {
super();
this.on("start", () => {
const onDone = () => {
this.removeListener("stop", onDone);
this.removeListener("err", onDone);
countdown.signal();
};
this.on("stop", onDone);
this.on("err", onDone);
countdown.add();
});
}
/**
* @param {boolean} [verbose]
*/
fork(verbose) {
const child = new ForkedGulp(this.tasks);
if (verbose) {
child.on("task_start", e => gulp.emit("task_start", e));
child.on("task_stop", e => gulp.emit("task_stop", e));
child.on("task_err", e => gulp.emit("task_err", e));
child.on("task_not_found", e => gulp.emit("task_not_found", e));
child.on("task_recursion", e => gulp.emit("task_recursion", e));
}
child.on("task_start", e => {
if (countdown.remainingCount === 0) {
countdown.reset(1);
workStartedEvent.pulseAll();
}
else {
countdown.add();
}
if (verbose) {
log('Starting', `'${chalk.cyan(e.task)}' ${chalk.gray(`(${countdown.remainingCount} remaining)`)}...`);
}
});
child.on("task_stop", e => {
countdown.signal();
if (verbose) {
log('Finished', `'${chalk.cyan(e.task)}' after ${chalk.magenta(prettyTime(/** @type {*}*/(e).hrDuration))} ${chalk.gray(`(${countdown.remainingCount} remaining)`)}`);
}
});
child.on("task_err", e => {
countdown.signal();
if (verbose) {
log(`'${chalk.cyan(e.task)}' ${chalk.red("errored after")} ${chalk.magenta(prettyTime(/** @type {*}*/(e).hrDuration))} ${chalk.gray(`(${countdown.remainingCount} remaining)`)}`);
log(e.err ? e.err.stack : e.message);
}
});
return child;
}
// @ts-ignore
start() {
throw new Error("Not supported, use fork.");
}
}
class ForkedGulp extends gulp.Gulp {
@@ -57,17 +68,6 @@ class ForkedGulp extends gulp.Gulp {
constructor(tasks) {
super();
this.tasks = tasks;
this.on("start", () => {
const onDone = () => {
this.removeListener("stop", onDone);
this.removeListener("err", onDone);
countdown.signal();
};
this.on("stop", onDone);
this.on("err", onDone);
countdown.add();
});
}
// Do not reset tasks
@@ -241,12 +241,26 @@ exports.flatten = flatten;
/**
* Returns a Promise that resolves when all pending build tasks have completed
* @param {CancelToken} [token]
* @param {import("prex").CancellationToken} [token]
*/
function wait(token) {
function waitForWorkToComplete(token) {
return countdown.wait(token);
}
exports.wait = wait;
exports.waitForWorkToComplete = waitForWorkToComplete;
/**
* Returns a Promise that resolves when all pending build tasks have completed
* @param {import("prex").CancellationToken} [token]
*/
function waitForWorkToStart(token) {
return workStartedEvent.wait(token);
}
exports.waitForWorkToStart = waitForWorkToStart;
function getRemainingWork() {
return countdown.remainingCount > 0;
}
exports.hasRemainingWork = getRemainingWork;
/**
* Resolve a TypeScript specifier into a fully-qualified module specifier and any requisite dependencies.

View File

@@ -21,7 +21,7 @@ exports.localTest262Baseline = "internal/baselines/test262/local";
* @param {string} defaultReporter
* @param {boolean} runInParallel
* @param {boolean} watchMode
* @param {InstanceType<typeof import("./cancellation").CancelToken>} [cancelToken]
* @param {import("prex").CancellationToken} [cancelToken]
*/
async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode, cancelToken) {
let testTimeout = cmdLineOptions.timeout;
@@ -37,6 +37,7 @@ async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode,
const keepFailed = cmdLineOptions.keepFailed;
if (!cmdLineOptions.dirty) {
await cleanTestDirs();
if (cancelToken) cancelToken.throwIfCancellationRequested();
}
if (fs.existsSync(testConfigFile)) {