mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-03-15 14:05:47 -05:00
Build script updates
This commit is contained in:
@@ -1,120 +1,34 @@
|
||||
// @ts-check
|
||||
const Browserify = require("browserify");
|
||||
const Vinyl = require("vinyl");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const convertMap = require("convert-source-map");
|
||||
const applySourceMap = require("vinyl-sourcemaps-apply");
|
||||
const { Transform, Readable } = require("stream");
|
||||
const browserify = require("browserify");
|
||||
const Vinyl = require("./vinyl");
|
||||
const { Transform } = require("stream");
|
||||
const { streamFromFile } = require("./utils");
|
||||
const { replaceContents } = require("./sourcemaps");
|
||||
|
||||
module.exports = browserify;
|
||||
module.exports = browserifyFile;
|
||||
|
||||
/**
|
||||
* @param {import("browserify").Options} [opts]
|
||||
*/
|
||||
function browserify(opts) {
|
||||
function browserifyFile(opts) {
|
||||
return new Transform({
|
||||
objectMode: true,
|
||||
/**
|
||||
* @param {string | Buffer | File} input
|
||||
* @param {string | Buffer | Vinyl} input
|
||||
*/
|
||||
transform(input, _, cb) {
|
||||
if (typeof input === "string" || Buffer.isBuffer(input)) return cb(new Error("Only Vinyl files are supported."));
|
||||
try {
|
||||
const sourceMap = input.sourceMap;
|
||||
const cwd = input.cwd || process.cwd();
|
||||
const base = input.base || cwd;
|
||||
const output = /**@type {File}*/(new Vinyl({ path: input.path, base: input.base }));
|
||||
const stream = streamFromFile(input);
|
||||
const b = new Browserify(Object.assign({}, opts, { debug: !!sourceMap, basedir: input.base }));
|
||||
b.add(stream, { file: input.path, basedir: input.base });
|
||||
b.bundle((err, contents) => {
|
||||
if (err) return cb(err);
|
||||
output.contents = contents;
|
||||
if (sourceMap) {
|
||||
output.sourceMap = typeof sourceMap === "string" ? JSON.parse(sourceMap) : sourceMap;
|
||||
const sourceRoot = output.sourceMap.sourceRoot;
|
||||
makeAbsoluteSourceMap(cwd, base, output.sourceMap);
|
||||
const stringContents = contents.toString("utf8");
|
||||
const newSourceMapConverter = convertMap.fromSource(stringContents);
|
||||
if (newSourceMapConverter) {
|
||||
const newSourceMap = newSourceMapConverter.toObject();
|
||||
makeAbsoluteSourceMap(cwd, base, newSourceMap);
|
||||
applySourceMap(output, newSourceMap);
|
||||
makeRelativeSourceMap(cwd, base, sourceRoot, output.sourceMap);
|
||||
output.contents = new Buffer(convertMap.removeComments(stringContents), "utf8");
|
||||
}
|
||||
}
|
||||
cb(null, output);
|
||||
});
|
||||
browserify(Object.assign({}, opts, { debug: !!input.sourceMap, basedir: input.base }))
|
||||
.add(streamFromFile(input), { file: input.path, basedir: input.base })
|
||||
.bundle((err, contents) => {
|
||||
if (err) return cb(err);
|
||||
cb(null, replaceContents(input, contents));
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
cb(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | undefined} cwd
|
||||
* @param {string | undefined} base
|
||||
* @param {RawSourceMap} sourceMap
|
||||
*
|
||||
* @typedef RawSourceMap
|
||||
* @property {string} version
|
||||
* @property {string} file
|
||||
* @property {string} [sourceRoot]
|
||||
* @property {string[]} sources
|
||||
* @property {string[]} [sourcesContents]
|
||||
* @property {string} mappings
|
||||
* @property {string[]} [names]
|
||||
*/
|
||||
function makeAbsoluteSourceMap(cwd = process.cwd(), base = "", sourceMap) {
|
||||
const sourceRoot = sourceMap.sourceRoot || "";
|
||||
const resolvedBase = path.resolve(cwd, base);
|
||||
const resolvedSourceRoot = path.resolve(resolvedBase, sourceRoot);
|
||||
sourceMap.file = path.resolve(resolvedBase, sourceMap.file).replace(/\\/g, "/");
|
||||
sourceMap.sources = sourceMap.sources.map(source => path.resolve(resolvedSourceRoot, source).replace(/\\/g, "/"));
|
||||
sourceMap.sourceRoot = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | undefined} cwd
|
||||
* @param {string | undefined} base
|
||||
* @param {string} sourceRoot
|
||||
* @param {RawSourceMap} sourceMap
|
||||
*/
|
||||
function makeRelativeSourceMap(cwd = process.cwd(), base = "", sourceRoot, sourceMap) {
|
||||
makeAbsoluteSourceMap(cwd, base, sourceMap);
|
||||
const resolvedBase = path.resolve(cwd, base);
|
||||
const resolvedSourceRoot = path.resolve(resolvedBase, sourceRoot);
|
||||
sourceMap.file = path.relative(resolvedBase, sourceMap.file).replace(/\\/g, "/");
|
||||
sourceMap.sources = sourceMap.sources.map(source => path.relative(resolvedSourceRoot, source).replace(/\\/g, "/"));
|
||||
sourceMap.sourceRoot = sourceRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {File} file
|
||||
*/
|
||||
function streamFromFile(file) {
|
||||
return file.isBuffer() ? streamFromBuffer(file.contents) :
|
||||
file.isStream() ? file.contents :
|
||||
fs.createReadStream(file.path, { autoClose: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Buffer} buffer
|
||||
*/
|
||||
function streamFromBuffer(buffer) {
|
||||
return new Readable({
|
||||
read() {
|
||||
this.push(buffer);
|
||||
this.push(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import("vinyl") & { sourceMap?: any }} File
|
||||
*/
|
||||
void 0;
|
||||
}
|
||||
71
scripts/build/cancellation.js
Normal file
71
scripts/build/cancellation.js
Normal file
@@ -0,0 +1,71 @@
|
||||
// @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;
|
||||
31
scripts/build/debounce.js
Normal file
31
scripts/build/debounce.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// @ts-check
|
||||
module.exports = debounce;
|
||||
|
||||
/**
|
||||
* @param {() => void} cb
|
||||
* @param {number} timeout
|
||||
* @param {DebounceOptions} [opts]
|
||||
*
|
||||
* @typedef DebounceOptions
|
||||
* @property {number} [max]
|
||||
*/
|
||||
function debounce(cb, timeout, opts = {}) {
|
||||
if (timeout < 10) timeout = 10;
|
||||
let max = opts.max || 10;
|
||||
if (max < timeout) max = timeout;
|
||||
let minTimer;
|
||||
let maxTimer;
|
||||
return trigger;
|
||||
|
||||
function trigger() {
|
||||
if (max > timeout && !maxTimer) maxTimer = setTimeout(done, max);
|
||||
if (minTimer) clearTimeout(minTimer);
|
||||
minTimer = setTimeout(done, timeout);
|
||||
}
|
||||
|
||||
function done() {
|
||||
if (maxTimer) maxTimer = void clearTimeout(maxTimer);
|
||||
if (minTimer) minTimer = void clearTimeout(minTimer);
|
||||
cb();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +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");
|
||||
|
||||
module.exports = exec;
|
||||
|
||||
@@ -10,8 +11,11 @@ module.exports = exec;
|
||||
* Executes the provided command once with the supplied arguments.
|
||||
* @param {string} cmd
|
||||
* @param {string[]} args
|
||||
* @param {object} [options]
|
||||
* @param {boolean} [options.ignoreExitCode]
|
||||
* @param {ExecOptions} [options]
|
||||
*
|
||||
* @typedef ExecOptions
|
||||
* @property {boolean} [ignoreExitCode]
|
||||
* @property {CancelToken} [cancelToken]
|
||||
*/
|
||||
function exec(cmd, args, options = {}) {
|
||||
return /**@type {Promise<{exitCode: number}>}*/(new Promise((resolve, reject) => {
|
||||
@@ -20,7 +24,13 @@ function exec(cmd, args, options = {}) {
|
||||
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(() => {
|
||||
ex.kill("SIGINT");
|
||||
ex.kill("SIGTERM");
|
||||
reject(new CancelError());
|
||||
});
|
||||
ex.on("exit", exitCode => {
|
||||
subscription && subscription.unsubscribe();
|
||||
if (exitCode === 0 || options.ignoreExitCode) {
|
||||
resolve({ exitCode });
|
||||
}
|
||||
@@ -28,7 +38,10 @@ function exec(cmd, args, options = {}) {
|
||||
reject(new Error(`Process exited with code: ${exitCode}`));
|
||||
}
|
||||
});
|
||||
ex.on("error", reject);
|
||||
ex.on("error", error => {
|
||||
subscription && subscription.unsubscribe();
|
||||
reject(error);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ const os = require("os");
|
||||
|
||||
/** @type {CommandLineOptions} */
|
||||
module.exports = minimist(process.argv.slice(2), {
|
||||
boolean: ["debug", "inspect", "light", "colors", "lint", "soft", "fix", "failed", "keepFailed"],
|
||||
boolean: ["debug", "dirty", "inspect", "light", "colors", "lint", "lkg", "soft", "fix", "failed", "keepFailed"],
|
||||
string: ["browser", "tests", "host", "reporter", "stackTraceLimit", "timeout"],
|
||||
alias: {
|
||||
"b": "browser",
|
||||
@@ -33,17 +33,21 @@ module.exports = minimist(process.argv.slice(2), {
|
||||
fix: process.env.fix || process.env.f,
|
||||
workers: process.env.workerCount || os.cpus().length,
|
||||
failed: false,
|
||||
keepFailed: false
|
||||
keepFailed: false,
|
||||
lkg: false,
|
||||
dirty: false
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @typedef TypedOptions
|
||||
* @property {boolean} debug
|
||||
* @property {boolean} dirty
|
||||
* @property {boolean} inspect
|
||||
* @property {boolean} light
|
||||
* @property {boolean} colors
|
||||
* @property {boolean} lint
|
||||
* @property {boolean} lkg
|
||||
* @property {boolean} soft
|
||||
* @property {boolean} fix
|
||||
* @property {string} browser
|
||||
@@ -56,7 +60,7 @@ module.exports = minimist(process.argv.slice(2), {
|
||||
* @property {string|number} timeout
|
||||
* @property {boolean} failed
|
||||
* @property {boolean} keepFailed
|
||||
*
|
||||
*
|
||||
* @typedef {import("minimist").ParsedArgs & TypedOptions} CommandLineOptions
|
||||
*/
|
||||
void 0;
|
||||
66
scripts/build/prepend.js
Normal file
66
scripts/build/prepend.js
Normal file
@@ -0,0 +1,66 @@
|
||||
// @ts-check
|
||||
const stream = require("stream");
|
||||
const Vinyl = require("./vinyl");
|
||||
const ts = require("../../lib/typescript");
|
||||
const fs = require("fs");
|
||||
const { base64VLQFormatEncode } = require("./sourcemaps");
|
||||
|
||||
module.exports = exports = prepend;
|
||||
|
||||
/**
|
||||
* @param {string | ((file: Vinyl) => string)} data
|
||||
*/
|
||||
function prepend(data) {
|
||||
return new stream.Transform({
|
||||
objectMode: true,
|
||||
/**
|
||||
* @param {string | Buffer | Vinyl} input
|
||||
* @param {(error: Error, data?: any) => void} cb
|
||||
*/
|
||||
transform(input, _, cb) {
|
||||
if (typeof input === "string" || Buffer.isBuffer(input)) return cb(new Error("Only Vinyl files are supported."));
|
||||
if (!input.isBuffer()) return cb(new Error("Streams not supported."));
|
||||
try {
|
||||
const output = input.clone();
|
||||
const prependContent = typeof data === "function" ? data(input) : data;
|
||||
output.contents = Buffer.concat([Buffer.from(prependContent, "utf8"), input.contents]);
|
||||
if (input.sourceMap) {
|
||||
if (typeof input.sourceMap === "string") input.sourceMap = /**@type {import("./sourcemaps").RawSourceMap}*/(JSON.parse(input.sourceMap));
|
||||
const lineStarts = /**@type {*}*/(ts).computeLineStarts(prependContent);
|
||||
let prependMappings = "";
|
||||
for (let i = 1; i < lineStarts.length; i++) {
|
||||
prependMappings += ";";
|
||||
}
|
||||
const offset = prependContent.length - lineStarts[lineStarts.length - 1];
|
||||
if (offset > 0) {
|
||||
prependMappings += base64VLQFormatEncode(offset) + ",";
|
||||
}
|
||||
output.sourceMap = {
|
||||
version: input.sourceMap.version,
|
||||
file: input.sourceMap.file,
|
||||
sources: input.sourceMap.sources,
|
||||
sourceRoot: input.sourceMap.sourceRoot,
|
||||
mappings: prependMappings + input.sourceMap.mappings,
|
||||
names: input.names,
|
||||
sourcesContent: input.sourcesContent
|
||||
};
|
||||
}
|
||||
return cb(null, output);
|
||||
}
|
||||
catch (e) {
|
||||
return cb(e);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
exports.prepend = prepend;
|
||||
|
||||
/**
|
||||
* @param {string | ((file: Vinyl) => string)} file
|
||||
*/
|
||||
function prependFile(file) {
|
||||
const data = typeof file === "string" ? fs.readFileSync(file, "utf8") :
|
||||
vinyl => fs.readFileSync(file(vinyl), "utf8");
|
||||
return prepend(data)
|
||||
}
|
||||
exports.file = prependFile;
|
||||
@@ -209,6 +209,27 @@ function flatten(projectSpec, flattenedProjectSpec, options = {}) {
|
||||
}
|
||||
exports.flatten = flatten;
|
||||
|
||||
/**
|
||||
* Returns a Promise that resolves when all pending build tasks have completed
|
||||
*/
|
||||
function wait() {
|
||||
return new Promise(resolve => {
|
||||
if (compilationGulp.allDone()) {
|
||||
resolve();
|
||||
}
|
||||
else {
|
||||
const onDone = () => {
|
||||
compilationGulp.removeListener("onDone", onDone);
|
||||
compilationGulp.removeListener("err", onDone);
|
||||
resolve();
|
||||
};
|
||||
compilationGulp.on("stop", onDone);
|
||||
compilationGulp.on("err", onDone);
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.wait = wait;
|
||||
|
||||
/**
|
||||
* Resolve a TypeScript specifier into a fully-qualified module specifier and any requisite dependencies.
|
||||
* @param {string} typescript An unresolved module specifier to a TypeScript version.
|
||||
|
||||
143
scripts/build/sourcemaps.js
Normal file
143
scripts/build/sourcemaps.js
Normal file
@@ -0,0 +1,143 @@
|
||||
// @ts-check
|
||||
const path = require("path");
|
||||
const Vinyl = require("./vinyl");
|
||||
const convertMap = require("convert-source-map");
|
||||
const applySourceMap = require("vinyl-sourcemaps-apply");
|
||||
const through2 = require("through2");
|
||||
|
||||
/**
|
||||
* @param {Vinyl} input
|
||||
* @param {string | Buffer} contents
|
||||
* @param {string | RawSourceMap} [sourceMap]
|
||||
*/
|
||||
function replaceContents(input, contents, sourceMap) {
|
||||
const output = input.clone();
|
||||
output.contents = typeof contents === "string" ? Buffer.from(contents, "utf8") : contents;
|
||||
if (input.sourceMap) {
|
||||
output.sourceMap = typeof input.sourceMap === "string" ? /**@type {RawSourceMap}*/(JSON.parse(input.sourceMap)) : input.sourceMap;
|
||||
if (typeof sourceMap === "string") {
|
||||
sourceMap = /**@type {RawSourceMap}*/(JSON.parse(sourceMap));
|
||||
}
|
||||
else if (sourceMap === undefined) {
|
||||
const stringContents = typeof contents === "string" ? contents : contents.toString("utf8");
|
||||
const newSourceMapConverter = convertMap.fromSource(stringContents);
|
||||
if (newSourceMapConverter) {
|
||||
sourceMap = /**@type {RawSourceMap}*/(newSourceMapConverter.toObject());
|
||||
output.contents = new Buffer(convertMap.removeMapFileComments(stringContents), "utf8");
|
||||
}
|
||||
}
|
||||
if (sourceMap) {
|
||||
const cwd = input.cwd || process.cwd();
|
||||
const base = input.base || cwd;
|
||||
const sourceRoot = output.sourceMap.sourceRoot;
|
||||
makeAbsoluteSourceMap(cwd, base, output.sourceMap);
|
||||
makeAbsoluteSourceMap(cwd, base, sourceMap);
|
||||
applySourceMap(output, sourceMap);
|
||||
makeRelativeSourceMap(cwd, base, sourceRoot, output.sourceMap);
|
||||
}
|
||||
else {
|
||||
output.sourceMap = undefined;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
exports.replaceContents = replaceContents;
|
||||
|
||||
function removeSourceMaps() {
|
||||
return through2.obj((/**@type {Vinyl}*/file, _, cb) => {
|
||||
if (file.sourceMap && file.isBuffer()) {
|
||||
file.contents = Buffer.from(convertMap.removeMapFileComments(file.contents.toString("utf8")), "utf8");
|
||||
file.sourceMap = undefined;
|
||||
}
|
||||
cb(null, file);
|
||||
});
|
||||
}
|
||||
exports.removeSourceMaps = removeSourceMaps;
|
||||
|
||||
/**
|
||||
* @param {string | undefined} cwd
|
||||
* @param {string | undefined} base
|
||||
* @param {RawSourceMap} sourceMap
|
||||
*
|
||||
* @typedef RawSourceMap
|
||||
* @property {string} version
|
||||
* @property {string} file
|
||||
* @property {string} [sourceRoot]
|
||||
* @property {string[]} sources
|
||||
* @property {string[]} [sourcesContent]
|
||||
* @property {string} mappings
|
||||
* @property {string[]} [names]
|
||||
*/
|
||||
function makeAbsoluteSourceMap(cwd = process.cwd(), base = "", sourceMap) {
|
||||
const sourceRoot = sourceMap.sourceRoot || "";
|
||||
const resolvedBase = path.resolve(cwd, base);
|
||||
const resolvedSourceRoot = path.resolve(resolvedBase, sourceRoot);
|
||||
sourceMap.file = path.resolve(resolvedBase, sourceMap.file).replace(/\\/g, "/");
|
||||
sourceMap.sources = sourceMap.sources.map(source => path.resolve(resolvedSourceRoot, source).replace(/\\/g, "/"));
|
||||
sourceMap.sourceRoot = "";
|
||||
}
|
||||
exports.makeAbsoluteSourceMap = makeAbsoluteSourceMap;
|
||||
|
||||
/**
|
||||
* @param {string | undefined} cwd
|
||||
* @param {string | undefined} base
|
||||
* @param {string} sourceRoot
|
||||
* @param {RawSourceMap} sourceMap
|
||||
*/
|
||||
function makeRelativeSourceMap(cwd = process.cwd(), base = "", sourceRoot, sourceMap) {
|
||||
makeAbsoluteSourceMap(cwd, base, sourceMap);
|
||||
const resolvedBase = path.resolve(cwd, base);
|
||||
const resolvedSourceRoot = path.resolve(resolvedBase, sourceRoot);
|
||||
sourceMap.file = path.relative(resolvedBase, sourceMap.file).replace(/\\/g, "/");
|
||||
sourceMap.sources = sourceMap.sources.map(source => path.relative(resolvedSourceRoot, source).replace(/\\/g, "/"));
|
||||
sourceMap.sourceRoot = sourceRoot;
|
||||
}
|
||||
exports.makeRelativeSourceMap = makeRelativeSourceMap;
|
||||
|
||||
/**
|
||||
* @param {string} message
|
||||
* @returns {never}
|
||||
*/
|
||||
function fail(message) {
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
*/
|
||||
function base64FormatEncode(value) {
|
||||
return value < 0 ? fail("Invalid value") :
|
||||
value < 26 ? 0x41 /*A*/ + value :
|
||||
value < 52 ? 0x61 /*a*/ + value - 26 :
|
||||
value < 62 ? 0x30 /*0*/ + value - 52 :
|
||||
value === 62 ? 0x2B /*+*/ :
|
||||
value === 63 ? 0x2F /*/*/ :
|
||||
fail("Invalid value");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
*/
|
||||
function base64VLQFormatEncode(value) {
|
||||
if (value < 0) {
|
||||
value = ((-value) << 1) + 1;
|
||||
}
|
||||
else {
|
||||
value = value << 1;
|
||||
}
|
||||
|
||||
// Encode 5 bits at a time starting from least significant bits
|
||||
let result = "";
|
||||
do {
|
||||
let currentDigit = value & 31; // 11111
|
||||
value = value >> 5;
|
||||
if (value > 0) {
|
||||
// There are still more digits to decode, set the msb (6th bit)
|
||||
currentDigit = currentDigit | 32;
|
||||
}
|
||||
result += String.fromCharCode(base64FormatEncode(currentDigit));
|
||||
} while (value > 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
exports.base64VLQFormatEncode = base64VLQFormatEncode;
|
||||
@@ -1,4 +1,5 @@
|
||||
// @ts-check
|
||||
const gulp = require("./gulp");
|
||||
const del = require("del");
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
@@ -6,13 +7,8 @@ const path = require("path");
|
||||
const mkdirP = require("./mkdirp");
|
||||
const cmdLineOptions = require("./options");
|
||||
const exec = require("./exec");
|
||||
const runSequence = require("run-sequence");
|
||||
const finished = require("./finished");
|
||||
const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util)
|
||||
|
||||
const nodeModulesPathPrefix = path.resolve("./node_modules/.bin/");
|
||||
const isWin = /^win/.test(process.platform);
|
||||
const mocha = path.join(nodeModulesPathPrefix, "mocha") + (isWin ? ".cmd" : "");
|
||||
const mochaJs = require.resolve("mocha/bin/_mocha");
|
||||
|
||||
exports.localBaseline = "tests/baselines/local/";
|
||||
exports.refBaseline = "tests/baselines/reference/";
|
||||
@@ -24,8 +20,10 @@ exports.localTest262Baseline = "internal/baselines/test262/local";
|
||||
* @param {string} runJs
|
||||
* @param {string} defaultReporter
|
||||
* @param {boolean} runInParallel
|
||||
* @param {boolean} watchMode
|
||||
* @param {InstanceType<typeof import("./cancellation").CancelToken>} [cancelToken]
|
||||
*/
|
||||
function runConsoleTests(runJs, defaultReporter, runInParallel) {
|
||||
async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode, cancelToken) {
|
||||
let testTimeout = cmdLineOptions.timeout;
|
||||
let tests = cmdLineOptions.tests;
|
||||
const lintFlag = cmdLineOptions.lint;
|
||||
@@ -36,112 +34,116 @@ function runConsoleTests(runJs, defaultReporter, runInParallel) {
|
||||
const stackTraceLimit = cmdLineOptions.stackTraceLimit;
|
||||
const testConfigFile = "test.config";
|
||||
const failed = cmdLineOptions.failed;
|
||||
const keepFailed = cmdLineOptions.keepFailed || failed;
|
||||
return cleanTestDirs()
|
||||
.then(() => {
|
||||
if (fs.existsSync(testConfigFile)) {
|
||||
fs.unlinkSync(testConfigFile);
|
||||
}
|
||||
|
||||
let workerCount, taskConfigsFolder;
|
||||
if (runInParallel) {
|
||||
// generate name to store task configuration files
|
||||
const prefix = os.tmpdir() + "/ts-tests";
|
||||
let i = 1;
|
||||
do {
|
||||
taskConfigsFolder = prefix + i;
|
||||
i++;
|
||||
} while (fs.existsSync(taskConfigsFolder));
|
||||
fs.mkdirSync(taskConfigsFolder);
|
||||
|
||||
workerCount = cmdLineOptions.workers;
|
||||
}
|
||||
|
||||
if (tests && tests.toLocaleLowerCase() === "rwc") {
|
||||
testTimeout = 400000;
|
||||
}
|
||||
|
||||
if (tests || runners || light || testTimeout || taskConfigsFolder || keepFailed) {
|
||||
writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, testTimeout, keepFailed);
|
||||
}
|
||||
|
||||
const colors = cmdLineOptions.colors;
|
||||
const reporter = cmdLineOptions.reporter || defaultReporter;
|
||||
|
||||
/** @type {string} */
|
||||
let host = "node";
|
||||
|
||||
/** @type {string[]} */
|
||||
let args = [];
|
||||
|
||||
// timeout normally isn"t necessary but Travis-CI has been timing out on compiler baselines occasionally
|
||||
// default timeout is 2sec which really should be enough, but maybe we just need a small amount longer
|
||||
if (!runInParallel) {
|
||||
args.push("-R", "scripts/failed-tests");
|
||||
args.push("-O", '"reporter=' + reporter + (keepFailed ? ",keepFailed=true" : "") + '"');
|
||||
if (tests) {
|
||||
args.push("-g", `"${tests}"`);
|
||||
}
|
||||
if (colors) {
|
||||
args.push("--colors");
|
||||
}
|
||||
else {
|
||||
args.push("--no-colors");
|
||||
}
|
||||
if (inspect) {
|
||||
args.unshift("--inspect-brk");
|
||||
}
|
||||
else if (debug) {
|
||||
args.unshift("--debug-brk");
|
||||
}
|
||||
else {
|
||||
args.push("-t", "" + testTimeout);
|
||||
}
|
||||
args.push(runJs);
|
||||
host = mocha;
|
||||
}
|
||||
else {
|
||||
// run task to load all tests and partition them between workers
|
||||
host = "node";
|
||||
args.push(runJs);
|
||||
}
|
||||
setNodeEnvToDevelopment();
|
||||
if (failed) {
|
||||
return exec(host, ["scripts/run-failed-tests.js"].concat(args));
|
||||
}
|
||||
else {
|
||||
return exec(host, args);
|
||||
}
|
||||
})
|
||||
.then(({ exitCode }) => {
|
||||
if (exitCode !== 0) return finish(undefined, exitCode);
|
||||
if (lintFlag) return finished(runSequence("lint")).then(() => finish(), finish);
|
||||
return finish();
|
||||
}, finish);
|
||||
|
||||
/**
|
||||
* @param {any=} error
|
||||
* @param {number=} errorStatus
|
||||
*/
|
||||
function finish(error, errorStatus) {
|
||||
restoreSavedNodeEnv();
|
||||
return deleteTestConfig()
|
||||
.then(deleteTemporaryProjectOutput)
|
||||
.then(() => {
|
||||
if (error !== undefined || errorStatus !== undefined) {
|
||||
process.exit(typeof errorStatus === "number" ? errorStatus : 2);
|
||||
}
|
||||
});
|
||||
const keepFailed = cmdLineOptions.keepFailed;
|
||||
if (!cmdLineOptions.dirty) {
|
||||
await cleanTestDirs();
|
||||
}
|
||||
|
||||
function deleteTestConfig() {
|
||||
return del("test.config");
|
||||
if (fs.existsSync(testConfigFile)) {
|
||||
fs.unlinkSync(testConfigFile);
|
||||
}
|
||||
|
||||
let workerCount, taskConfigsFolder;
|
||||
if (runInParallel) {
|
||||
// generate name to store task configuration files
|
||||
const prefix = os.tmpdir() + "/ts-tests";
|
||||
let i = 1;
|
||||
do {
|
||||
taskConfigsFolder = prefix + i;
|
||||
i++;
|
||||
} while (fs.existsSync(taskConfigsFolder));
|
||||
fs.mkdirSync(taskConfigsFolder);
|
||||
|
||||
workerCount = cmdLineOptions.workers;
|
||||
}
|
||||
|
||||
if (tests && tests.toLocaleLowerCase() === "rwc") {
|
||||
testTimeout = 400000;
|
||||
}
|
||||
|
||||
if (tests || runners || light || testTimeout || taskConfigsFolder || keepFailed) {
|
||||
writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, testTimeout, keepFailed);
|
||||
}
|
||||
|
||||
const colors = cmdLineOptions.colors;
|
||||
const reporter = cmdLineOptions.reporter || defaultReporter;
|
||||
|
||||
/** @type {string[]} */
|
||||
let args = [];
|
||||
|
||||
// timeout normally isn"t necessary but Travis-CI has been timing out on compiler baselines occasionally
|
||||
// default timeout is 2sec which really should be enough, but maybe we just need a small amount longer
|
||||
if (!runInParallel) {
|
||||
args.push(failed ? "scripts/run-failed-tests.js" : mochaJs);
|
||||
args.push("-R", "scripts/failed-tests");
|
||||
args.push("-O", '"reporter=' + reporter + (keepFailed ? ",keepFailed=true" : "") + '"');
|
||||
if (tests) {
|
||||
args.push("-g", `"${tests}"`);
|
||||
}
|
||||
if (colors) {
|
||||
args.push("--colors");
|
||||
}
|
||||
else {
|
||||
args.push("--no-colors");
|
||||
}
|
||||
if (inspect) {
|
||||
args.unshift("--inspect-brk");
|
||||
}
|
||||
else if (debug) {
|
||||
args.unshift("--debug-brk");
|
||||
}
|
||||
else {
|
||||
args.push("-t", "" + testTimeout);
|
||||
}
|
||||
args.push(runJs);
|
||||
}
|
||||
else {
|
||||
// run task to load all tests and partition them between workers
|
||||
args.push(runJs);
|
||||
}
|
||||
|
||||
/** @type {number | undefined} */
|
||||
let errorStatus;
|
||||
|
||||
/** @type {Error | undefined} */
|
||||
let error;
|
||||
|
||||
try {
|
||||
setNodeEnvToDevelopment();
|
||||
const { exitCode } = await exec("node", args, { cancelToken });
|
||||
if (exitCode !== 0) {
|
||||
errorStatus = exitCode;
|
||||
error = new Error(`Process exited with status code ${errorStatus}.`);
|
||||
}
|
||||
else if (lintFlag) {
|
||||
await new Promise((resolve, reject) => gulp.start(["lint"], error => error ? reject(error) : resolve()));
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
errorStatus = undefined;
|
||||
error = e;
|
||||
}
|
||||
finally {
|
||||
restoreSavedNodeEnv();
|
||||
}
|
||||
|
||||
await del("test.config");
|
||||
await deleteTemporaryProjectOutput();
|
||||
|
||||
if (error !== undefined) {
|
||||
if (watchMode) {
|
||||
throw error;
|
||||
}
|
||||
else {
|
||||
log.error(error);
|
||||
process.exit(typeof errorStatus === "number" ? errorStatus : 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.runConsoleTests = runConsoleTests;
|
||||
|
||||
function cleanTestDirs() {
|
||||
return del([exports.localBaseline, exports.localRwcBaseline,])
|
||||
return del([exports.localBaseline, exports.localRwcBaseline])
|
||||
.then(() => mkdirP(exports.localRwcBaseline))
|
||||
.then(() => mkdirP(exports.localBaseline));
|
||||
}
|
||||
|
||||
27
scripts/build/utils.js
Normal file
27
scripts/build/utils.js
Normal file
@@ -0,0 +1,27 @@
|
||||
// @ts-check
|
||||
const fs = require("fs");
|
||||
const File = require("./vinyl");
|
||||
const { Readable } = require("stream");
|
||||
|
||||
/**
|
||||
* @param {File} file
|
||||
*/
|
||||
function streamFromFile(file) {
|
||||
return file.isBuffer() ? streamFromBuffer(file.contents) :
|
||||
file.isStream() ? file.contents :
|
||||
fs.createReadStream(file.path, { autoClose: true });
|
||||
}
|
||||
exports.streamFromFile = streamFromFile;
|
||||
|
||||
/**
|
||||
* @param {Buffer} buffer
|
||||
*/
|
||||
function streamFromBuffer(buffer) {
|
||||
return new Readable({
|
||||
read() {
|
||||
this.push(buffer);
|
||||
this.push(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.streamFromBuffer = streamFromBuffer;
|
||||
60
scripts/build/vinyl.d.ts
vendored
Normal file
60
scripts/build/vinyl.d.ts
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// NOTE: This makes it possible to correctly type vinyl Files under @ts-check.
|
||||
export = File;
|
||||
|
||||
declare class File<T extends File.Contents = File.Contents> {
|
||||
constructor(options?: File.VinylOptions<T>);
|
||||
|
||||
cwd: string;
|
||||
base: string;
|
||||
path: string;
|
||||
readonly history: ReadonlyArray<string>;
|
||||
contents: T;
|
||||
relative: string;
|
||||
dirname: string;
|
||||
basename: string;
|
||||
stem: string;
|
||||
extname: string;
|
||||
symlink: string | null;
|
||||
stat: import("fs").Stats | null;
|
||||
sourceMap?: import("./sourcemaps").RawSourceMap | string;
|
||||
|
||||
[custom: string]: any;
|
||||
|
||||
isBuffer(): this is T extends Buffer ? File<Buffer> : never;
|
||||
isStream(): this is T extends NodeJS.ReadableStream ? File<NodeJS.ReadableStream> : never;
|
||||
isNull(): this is T extends null ? File<null> : never;
|
||||
isDirectory(): this is T extends null ? File.Directory : never;
|
||||
isSymbolic(): this is T extends null ? File.Symbolic : never;
|
||||
clone(opts?: { contents?: boolean, deep?: boolean }): this;
|
||||
}
|
||||
|
||||
namespace File {
|
||||
export interface VinylOptions<T extends Contents = Contents> {
|
||||
cwd?: string;
|
||||
base?: string;
|
||||
path?: string;
|
||||
history?: ReadonlyArray<string>;
|
||||
stat?: import("fs").Stats;
|
||||
contents?: T;
|
||||
sourceMap?: import("./sourcemaps").RawSourceMap | string;
|
||||
[custom: string]: any;
|
||||
}
|
||||
|
||||
export type Contents = Buffer | NodeJS.ReadableStream | null;
|
||||
export type File = import("./vinyl");
|
||||
export type NullFile = File<null>;
|
||||
export type BufferFile = File<Buffer>;
|
||||
export type StreamFile = File<NodeJS.ReadableStream>;
|
||||
|
||||
export interface Directory extends NullFile {
|
||||
isNull(): true;
|
||||
isDirectory(): true;
|
||||
isSymbolic(): this is never;
|
||||
}
|
||||
|
||||
export interface Symbolic extends NullFile {
|
||||
isNull(): true;
|
||||
isDirectory(): this is never;
|
||||
isSymbolic(): true;
|
||||
}
|
||||
}
|
||||
1
scripts/build/vinyl.js
Normal file
1
scripts/build/vinyl.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require("vinyl");
|
||||
@@ -4,9 +4,11 @@ const path = require("path");
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
|
||||
const failingHookRegExp = /^(.*) "(before|after) (all|each)" hook$/;
|
||||
|
||||
/**
|
||||
* .failed-tests reporter
|
||||
*
|
||||
*
|
||||
* @typedef {Object} ReporterOptions
|
||||
* @property {string} [file]
|
||||
* @property {boolean} [keepFailed]
|
||||
@@ -15,7 +17,7 @@ const os = require("os");
|
||||
*/
|
||||
class FailedTestsReporter extends Mocha.reporters.Base {
|
||||
/**
|
||||
* @param {Mocha.Runner} runner
|
||||
* @param {Mocha.Runner} runner
|
||||
* @param {{ reporterOptions?: ReporterOptions }} [options]
|
||||
*/
|
||||
constructor(runner, options) {
|
||||
@@ -49,35 +51,58 @@ class FailedTestsReporter extends Mocha.reporters.Base {
|
||||
|
||||
/** @type {Mocha.Test[]} */
|
||||
this.passes = [];
|
||||
|
||||
/** @type {Mocha.Test[]} */
|
||||
|
||||
/** @type {(Mocha.Test)[]} */
|
||||
this.failures = [];
|
||||
|
||||
|
||||
runner.on("pass", test => this.passes.push(test));
|
||||
runner.on("fail", test => this.failures.push(test));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @param {ReadonlyArray<Mocha.Test>} passes
|
||||
* @param {ReadonlyArray<Mocha.Test>} failures
|
||||
* @param {boolean} keepFailed
|
||||
* @param {(err?: NodeJS.ErrnoException) => void} done
|
||||
* @param {string} file
|
||||
* @param {ReadonlyArray<Mocha.Test>} passes
|
||||
* @param {ReadonlyArray<Mocha.Test | Mocha.Hook>} failures
|
||||
* @param {boolean} keepFailed
|
||||
* @param {(err?: NodeJS.ErrnoException) => void} done
|
||||
*/
|
||||
static writeFailures(file, passes, failures, keepFailed, done) {
|
||||
const failingTests = new Set(fs.existsSync(file) ? readTests() : undefined);
|
||||
if (failingTests.size > 0) {
|
||||
const possiblyPassingSuites = /**@type {Set<string>}*/(new Set());
|
||||
|
||||
// Remove tests that are now passing and track suites that are now
|
||||
// possibly passing.
|
||||
if (failingTests.size > 0 && !keepFailed) {
|
||||
for (const test of passes) {
|
||||
const title = test.fullTitle().trim();
|
||||
if (title) failingTests.delete(title);
|
||||
failingTests.delete(test.fullTitle().trim());
|
||||
possiblyPassingSuites.add(test.parent.fullTitle().trim());
|
||||
}
|
||||
}
|
||||
|
||||
// Add tests that are now failing. If a hook failed, track its
|
||||
// containing suite as failing. If the suite for a test or hook was
|
||||
// possibly passing then it is now definitely failing.
|
||||
for (const test of failures) {
|
||||
const title = test.fullTitle().trim();
|
||||
if (title) failingTests.add(title);
|
||||
const suiteTitle = test.parent.fullTitle().trim();
|
||||
if (test.type === "test") {
|
||||
failingTests.add(test.fullTitle().trim());
|
||||
}
|
||||
else {
|
||||
failingTests.add(suiteTitle);
|
||||
}
|
||||
possiblyPassingSuites.delete(suiteTitle);
|
||||
}
|
||||
|
||||
// Remove all definitely passing suites.
|
||||
for (const suite of possiblyPassingSuites) {
|
||||
failingTests.delete(suite);
|
||||
}
|
||||
|
||||
if (failingTests.size > 0) {
|
||||
const failed = Array.from(failingTests).join(os.EOL);
|
||||
const failed = Array
|
||||
.from(failingTests)
|
||||
.sort()
|
||||
.join(os.EOL);
|
||||
fs.writeFile(file, failed, "utf8", done);
|
||||
}
|
||||
else if (!keepFailed && fs.existsSync(file)) {
|
||||
@@ -96,7 +121,7 @@ class FailedTestsReporter extends Mocha.reporters.Base {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} failures
|
||||
* @param {number} failures
|
||||
* @param {(failures: number) => void} [fn]
|
||||
*/
|
||||
done(failures, fn) {
|
||||
|
||||
@@ -54,16 +54,17 @@ async function copyScriptOutputs() {
|
||||
await copyWithCopyright("cancellationToken.js");
|
||||
await copyWithCopyright("tsc.release.js", "tsc.js");
|
||||
await copyWithCopyright("tsserver.js");
|
||||
await copyWithCopyright("typescript.js");
|
||||
await copyWithCopyright("typescriptServices.js");
|
||||
await copyFromBuiltLocal("tsserverlibrary.js"); // copyright added by build
|
||||
await copyFromBuiltLocal("typescript.js"); // copyright added by build
|
||||
await copyFromBuiltLocal("typescriptServices.js"); // copyright added by build
|
||||
await copyWithCopyright("typingsInstaller.js");
|
||||
await copyWithCopyright("watchGuard.js");
|
||||
}
|
||||
|
||||
async function copyDeclarationOutputs() {
|
||||
await copyWithCopyright("tsserverlibrary.d.ts");
|
||||
await copyWithCopyright("typescript.d.ts");
|
||||
await copyWithCopyright("typescriptServices.d.ts");
|
||||
await copyFromBuiltLocal("tsserverlibrary.d.ts"); // copyright added by build
|
||||
await copyFromBuiltLocal("typescript.d.ts"); // copyright added by build
|
||||
await copyFromBuiltLocal("typescriptServices.d.ts"); // copyright added by build
|
||||
}
|
||||
|
||||
async function writeGitAttributes() {
|
||||
|
||||
@@ -69,7 +69,12 @@ const proc = spawn(process.execPath, args, {
|
||||
proc.on('exit', (code, signal) => {
|
||||
process.on('exit', () => {
|
||||
if (grepFile) {
|
||||
fs.unlinkSync(grepFile);
|
||||
try {
|
||||
fs.unlinkSync(grepFile);
|
||||
}
|
||||
catch (e) {
|
||||
if (e.code !== "ENOENT") throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (signal) {
|
||||
|
||||
Reference in New Issue
Block a user