Simplify gulp to just use 'tsc -b' to build, update to gulp@4

This commit is contained in:
Ron Buckton 2019-01-27 21:56:56 -08:00
parent fa74cef81e
commit 47c2708a9c
34 changed files with 1170 additions and 3191 deletions

File diff suppressed because it is too large Load diff

View file

@ -8,10 +8,8 @@ const path = require("path");
const fold = require("travis-fold");
const ts = require("./lib/typescript");
const del = require("del");
const getDirSize = require("./scripts/build/getDirSize");
const { getDirSize, needsUpdate, flatten } = require("./scripts/build/utils");
const { base64VLQFormatEncode } = require("./scripts/build/sourcemaps");
const needsUpdate = require("./scripts/build/needsUpdate");
const { flatten } = require("./scripts/build/project");
// add node_modules to path so we don't need global modules, prefer the modules by adding them first
var nodeModulesPathPrefix = path.resolve("./node_modules/.bin/") + path.delimiter;

View file

@ -35,10 +35,8 @@
"@types/convert-source-map": "latest",
"@types/del": "latest",
"@types/glob": "latest",
"@types/gulp": "3.X",
"@types/gulp": "^4.0.5",
"@types/gulp-concat": "latest",
"@types/gulp-help": "latest",
"@types/gulp-if": "0.0.33",
"@types/gulp-newer": "latest",
"@types/gulp-rename": "0.0.33",
"@types/gulp-sourcemaps": "0.0.32",
@ -50,7 +48,6 @@
"@types/mocha": "latest",
"@types/node": "8.5.5",
"@types/q": "latest",
"@types/run-sequence": "latest",
"@types/source-map-support": "latest",
"@types/through2": "latest",
"@types/travis-fold": "latest",
@ -63,16 +60,12 @@
"del": "latest",
"fancy-log": "latest",
"fs-extra": "^6.0.1",
"gulp": "3.X",
"gulp-clone": "latest",
"gulp": "^4.0.0",
"gulp-concat": "latest",
"gulp-help": "latest",
"gulp-if": "latest",
"gulp-insert": "latest",
"gulp-newer": "latest",
"gulp-rename": "latest",
"gulp-sourcemaps": "latest",
"gulp-typescript": "latest",
"istanbul": "latest",
"jake": "latest",
"lodash": "4.17.10",
@ -86,7 +79,6 @@
"prex": "^0.4.3",
"q": "latest",
"remove-internal": "^2.9.2",
"run-sequence": "latest",
"source-map-support": "latest",
"through2": "latest",
"travis-fold": "latest",

View file

@ -1,24 +0,0 @@
// @ts-check
const merge2 = require("merge2");
const gulp = require("./gulp");
const rename = require("gulp-rename");
const rm = require("./rm");
const { localBaseline, refBaseline } = require("./tests");
module.exports = baselineAccept;
function baselineAccept(subfolder = "") {
return merge2(baselineCopy(subfolder), baselineDelete(subfolder));
}
function baselineCopy(subfolder = "") {
return gulp.src([`${localBaseline}${subfolder ? `${subfolder}/` : ``}**`, `!${localBaseline}${subfolder}/**/*.delete`], { base: localBaseline })
.pipe(gulp.dest(refBaseline));
}
function baselineDelete(subfolder = "") {
return gulp.src([`${localBaseline}${subfolder ? `${subfolder}/` : ``}**/*.delete`], { base: localBaseline, read: false })
.pipe(rm())
.pipe(rename({ extname: "" }))
.pipe(rm(refBaseline));
}

View file

@ -1,12 +1,10 @@
// @ts-check
const browserify = require("browserify");
const Vinyl = require("./vinyl");
const Vinyl = require("vinyl");
const { Transform } = require("stream");
const { streamFromFile } = require("./utils");
const { replaceContents } = require("./sourcemaps");
module.exports = browserifyFile;
/**
* @param {import("browserify").Options} [opts]
*/
@ -31,4 +29,5 @@ function browserifyFile(opts) {
}
}
});
}
}
exports.browserify = browserifyFile;

View file

@ -1,5 +0,0 @@
// @ts-check
// this just fixes the incorrect types for chalk :/
const chalk = /**@type {import("chalk").Chalk}*/(require("chalk").default || require("chalk"));
module.exports = chalk;

View file

@ -1,19 +0,0 @@
// @ts-check
const replace = require("./replace");
module.exports = exports = convertConstEnum;
/**
* This regexp exists to capture our const enums and replace them with normal enums in our public API
* - this is fine since we compile with preserveConstEnums, and ensures our consumers are not locked
* to the TS version they compile with.
*/
const constEnumCaptureRegexp = /^(\s*)(export )?const enum (\S+) {(\s*)$/gm;
const constEnumReplacement = "$1$2enum $3 {$4";
/**
* Converts `const enum` declarations in a .d.ts file into non-const `enum` declarations.
*/
function convertConstEnum() {
return replace(constEnumCaptureRegexp, constEnumReplacement);
}

View file

@ -1,31 +0,0 @@
// @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();
}
}

View file

@ -1,49 +0,0 @@
// @ts-check
const ts = require("../../lib/typescript");
const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util)
/** @type {FormatDiagnosticsHost} */
const formatDiagnosticsHost = exports.formatDiagnosticsHost = {
getCanonicalFileName: fileName => fileName,
getCurrentDirectory: () => process.cwd(),
getNewLine: () => ts.sys.newLine
};
/**
* @param {Diagnostic[]} diagnostics
* @param {{ cwd?: string, pretty?: boolean }} [options]
*/
function formatDiagnostics(diagnostics, options) {
return options && options.pretty
? ts.formatDiagnosticsWithColorAndContext(diagnostics, getFormatDiagnosticsHost(options && options.cwd))
: ts.formatDiagnostics(diagnostics, getFormatDiagnosticsHost(options && options.cwd));
}
exports.formatDiagnostics = formatDiagnostics;
/**
* @param {Diagnostic[]} diagnostics
* @param {{ cwd?: string }} [options]
*/
function reportDiagnostics(diagnostics, options) {
log(formatDiagnostics(diagnostics, { cwd: options && options.cwd, pretty: process.stdout.isTTY }));
}
exports.reportDiagnostics = reportDiagnostics;
/**
* @param {string | undefined} cwd
* @returns {FormatDiagnosticsHost}
*/
function getFormatDiagnosticsHost(cwd) {
if (!cwd || cwd === process.cwd()) return formatDiagnosticsHost;
return {
getCanonicalFileName: formatDiagnosticsHost.getCanonicalFileName,
getCurrentDirectory: () => cwd,
getNewLine: formatDiagnosticsHost.getNewLine
};
}
/**
* @typedef {import("../../lib/typescript").FormatDiagnosticsHost} FormatDiagnosticsHost
* @typedef {import("../../lib/typescript").Diagnostic} Diagnostic
*/
void 0;

View file

@ -1,58 +0,0 @@
// @ts-check
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 { CancellationToken, CancelError } = require("prex");
module.exports = exec;
/**
* Executes the provided command once with the supplied arguments.
* @param {string} cmd
* @param {string[]} args
* @param {ExecOptions} [options]
*
* @typedef ExecOptions
* @property {boolean} [ignoreExitCode]
* @property {import("prex").CancellationToken} [cancelToken]
*/
function exec(cmd, args, options = {}) {
return /**@type {Promise<{exitCode: number}>}*/(new Promise((resolve, reject) => {
const { ignoreExitCode, cancelToken = CancellationToken.none } = options;
cancelToken.throwIfCancellationRequested();
// 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(" ")}`];
log(`> ${chalk.green(cmd)} ${args.join(" ")}`);
const proc = cp.spawn(isWin ? "cmd" : "/bin/sh", [subshellFlag, ...command], { stdio: "inherit", windowsVerbatimArguments: true });
const registration = cancelToken.register(() => {
log(`${chalk.red("killing")} '${chalk.green(cmd)} ${args.join(" ")}'...`);
proc.kill("SIGINT");
proc.kill("SIGTERM");
reject(new CancelError());
});
proc.on("exit", exitCode => {
registration.unregister();
if (exitCode === 0 || ignoreExitCode) {
resolve({ exitCode });
}
else {
reject(new Error(`Process exited with code: ${exitCode}`));
}
});
proc.on("error", error => {
registration.unregister();
reject(error);
});
}));
}
/**
* @param {string} cmd
*/
function possiblyQuote(cmd) {
return cmd.indexOf(" ") >= 0 ? `"${cmd}"` : cmd;
}

View file

@ -1,46 +0,0 @@
// @ts-check
module.exports = finished;
/**
* @param {NodeJS.ReadableStream | NodeJS.WritableStream} stream
* @returns {Promise<void>}
*/
function finished(stream) {
return new Promise((resolve, reject) => {
const readable = "readable" in stream && stream.readable;
const writable = "writable" in stream && stream.writable;
let countdown = 0;
const cleanup = () => {
if (readable) stream.removeListener("end", signal);
if (writable) stream.removeListener("finish", signal);
stream.removeListener("error", onerror);
};
const signal = () => {
if (countdown > 0) {
countdown--;
if (countdown === 0) {
cleanup();
resolve();
}
}
};
const onerror = (error) => {
if (countdown > 0) {
countdown = 0;
cleanup();
reject(error);
}
};
stream.once("error", onerror);
if (readable) {
countdown++;
stream.once("end", signal);
}
if (writable) {
countdown++;
stream.once("finish", signal);
}
if (countdown === 0) signal();
});
}

View file

@ -1,12 +0,0 @@
// @ts-check
const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util)
module.exports = getDiffTool;
function getDiffTool() {
const program = process.env.DIFF;
if (!program) {
log.warn("Add the 'DIFF' environment variable to the path of the program you want to use.");
process.exit(1);
}
return program;
}

View file

@ -1,23 +0,0 @@
// @ts-check
const { lstatSync, readdirSync } = require("fs");
const { join } = require("path");
/**
* Find the size of a directory recursively.
* Symbolic links can cause a loop.
* @param {string} root
* @returns {number} bytes
*/
function getDirSize(root) {
const stats = lstatSync(root);
if (!stats.isDirectory()) {
return stats.size;
}
return readdirSync(root)
.map(file => getDirSize(join(root, file)))
.reduce((acc, num) => acc + num, 0);
}
module.exports = getDirSize;

View file

@ -1,149 +0,0 @@
// @ts-check
const path = require("path");
const child_process = require("child_process");
const fs = require("fs");
const tsc = require("gulp-typescript");
const Vinyl = require("vinyl");
const { Duplex, Readable } = require("stream");
const protocol = require("./protocol");
/**
* @param {string | undefined} tsConfigFileName
* @param {tsc.Settings} settings
* @param {CreateProjectOptions} options
*
* @typedef CreateProjectOptions
* @property {string} [typescript]
* @property {boolean} [parse]
*/
function createProject(tsConfigFileName, settings, options) {
settings = Object.assign({}, settings);
options = Object.assign({}, options);
if (settings.typescript) throw new Error();
const localSettings = Object.assign({}, settings);
if (options.typescript) {
options.typescript = path.resolve(options.typescript);
localSettings.typescript = require(options.typescript);
}
const project = tsConfigFileName === undefined ? tsc.createProject(localSettings) : tsc.createProject(tsConfigFileName, localSettings);
const wrappedProject = /** @type {tsc.Project} */((reporter = tsc.reporter.defaultReporter()) => {
const ts = project.typescript;
const proc = child_process.fork(require.resolve("./worker.js"), [], {
// Prevent errors when debugging gulpfile due to the same debug port being passed to forked children.
execArgv: []
});
/** @type {Map<string, import("vinyl")>} */
const inputs = new Map();
/** @type {Map<string, *>} */
const sourceFiles = new Map();
/** @type {protocol.SourceFileHost & protocol.VinylHost} */
const host = {
getVinyl(path) { return inputs.get(path); },
getSourceFile(fileName) { return sourceFiles.get(fileName); },
createSourceFile(fileName, text, languageVersion) {
if (text === undefined) {
text = fs.readFileSync(fileName, "utf8");
}
/** @type {protocol.SourceFile} */
let file;
if (options.parse) {
file = ts.createSourceFile(fileName, text, languageVersion, /*setParentNodes*/ true);
}
else {
// NOTE: the built-in reporters in gulp-typescript don't actually need a full
// source file, so save time by faking one unless requested.
file = /**@type {protocol.SourceFile}*/({
pos: 0,
end: text.length,
kind: ts.SyntaxKind.SourceFile,
fileName,
text,
languageVersion,
statements: /**@type {*} */([]),
endOfFileToken: { pos: text.length, end: text.length, kind: ts.SyntaxKind.EndOfFileToken },
amdDependencies: /**@type {*} */([]),
referencedFiles: /**@type {*} */([]),
typeReferenceDirectives: /**@type {*} */([]),
libReferenceDirectives: /**@type {*} */([]),
languageVariant: ts.LanguageVariant.Standard,
isDeclarationFile: /\.d\.ts$/.test(fileName),
hasNoDefaultLib: /[\\/]lib\.[^\\/]+\.d\.ts$/.test(fileName)
});
}
sourceFiles.set(fileName, file);
return file;
}
};
/** @type {Duplex & { js?: Readable, dts?: Readable }} */
const compileStream = new Duplex({
objectMode: true,
read() {},
/** @param {*} file */
write(file, _encoding, callback) {
inputs.set(file.path, file);
proc.send(protocol.message.write(file));
callback();
},
final(callback) {
proc.send(protocol.message.final());
callback();
}
});
const jsStream = compileStream.js = new Readable({
objectMode: true,
read() {}
});
const dtsStream = compileStream.dts = new Readable({
objectMode: true,
read() {}
});
proc.send(protocol.message.createProject(tsConfigFileName, settings, options));
proc.on("message", (/**@type {protocol.WorkerMessage}*/ message) => {
switch (message.method) {
case "write": {
const file = protocol.vinylFromJson(message.params);
compileStream.push(file);
if (file.path.endsWith(".d.ts")) {
dtsStream.push(file);
}
else {
jsStream.push(file);
}
break;
}
case "final": {
compileStream.push(null);
jsStream.push(null);
dtsStream.push(null);
proc.kill(); // TODO(rbuckton): pool workers? may not be feasible due to gulp-typescript holding onto memory
break;
}
case "error": {
const error = protocol.errorFromJson(message.params);
compileStream.emit("error", error);
proc.kill(); // TODO(rbuckton): pool workers? may not be feasible due to gulp-typescript holding onto memory
break;
}
case "reporter.error": {
if (reporter.error) {
const error = protocol.typeScriptErrorFromJson(message.params, host);
reporter.error(error, project.typescript);
}
break;
}
case "reporter.finish": {
if (reporter.finish) {
reporter.finish(message.params);
}
}
}
});
return /** @type {*} */(compileStream);
});
return Object.assign(wrappedProject, project);
}
exports.createProject = createProject;

View file

@ -1,281 +0,0 @@
// @ts-check
const Vinyl = require("vinyl");
/**
* @param {File} file
* @returns {*}
*/
function vinylToJson(file) {
if (file.isStream()) throw new TypeError("Streams not supported.");
return {
path: file.path,
cwd: file.cwd,
base: file.base,
contents: file.isBuffer() ? file.contents.toString("utf8") : undefined,
sourceMap: file.sourceMap
};
}
exports.vinylToJson = vinylToJson;
/**
* @param {*} json
* @returns {File}
*/
function vinylFromJson(json) {
return new Vinyl({
path: json.path,
cwd: json.cwd,
base: json.base,
contents: typeof json.contents === "string" ? Buffer.from(json.contents, "utf8") : undefined,
sourceMap: json.sourceMap
});
}
exports.vinylFromJson = vinylFromJson;
/**
* @param {Error} error
* @returns {*}
*/
function errorToJson(error) {
return {
name: error.name,
message: error.message,
stack: error.stack
};
}
exports.errorToJson = errorToJson;
/**
* @param {*} json
* @returns {Error}
*/
function errorFromJson(json) {
const error = new Error();
error.name = json.name;
error.message = json.message;
error.stack = json.stack;
return error;
}
exports.errorFromJson = errorFromJson;
/**
* @param {TypeScriptError} error
* @returns {*}
*/
function typeScriptErrorToJson(error) {
return Object.assign({}, errorToJson(error), {
fullFilename: error.fullFilename,
relativeFilename: error.relativeFilename,
file: error.file && { path: error.file.path },
tsFile: error.tsFile && sourceFileToJson(error.tsFile),
diagnostic: diagnosticToJson(error.diagnostic),
startPosition: error.startPosition,
endPosition: error.endPosition
});
}
exports.typeScriptErrorToJson = typeScriptErrorToJson;
/**
* @param {*} json
* @param {SourceFileHost & VinylHost} host
* @returns {TypeScriptError}
*/
function typeScriptErrorFromJson(json, host) {
const error = /**@type {TypeScriptError}*/(errorFromJson(json));
error.fullFilename = json.fullFilename;
error.relativeFilename = json.relativeFilename;
error.file = json.file && host.getVinyl(json.file.path);
error.tsFile = json.tsFile && sourceFileFromJson(json.tsFile, host);
error.diagnostic = diagnosticFromJson(json.diagnostic, host);
error.startPosition = json.startPosition;
error.endPosition = json.endPosition;
return error;
}
exports.typeScriptErrorFromJson = typeScriptErrorFromJson;
/**
* @param {SourceFile} file
* @returns {*}
*/
function sourceFileToJson(file) {
return {
fileName: file.fileName,
text: file.text,
languageVersion: file.languageVersion
};
}
exports.sourceFileToJson = sourceFileToJson;
/**
* @param {*} json
* @param {SourceFileHost} host
*/
function sourceFileFromJson(json, host) {
return host.getSourceFile(json.fileName)
|| host.createSourceFile(json.fileName, json.text, json.languageVersion);
}
exports.sourceFileFromJson = sourceFileFromJson;
/**
* @param {Diagnostic} diagnostic
* @returns {*}
*/
function diagnosticToJson(diagnostic) {
return Object.assign({}, diagnosticRelatedInformationToJson(diagnostic), {
category: diagnostic.category,
code: diagnostic.code,
source: diagnostic.source,
relatedInformation: diagnostic.relatedInformation && diagnostic.relatedInformation.map(diagnosticRelatedInformationToJson)
});
}
exports.diagnosticToJson = diagnosticToJson;
/**
* @param {*} json
* @param {SourceFileHost} host
* @returns {Diagnostic}
*/
function diagnosticFromJson(json, host) {
return Object.assign({}, diagnosticRelatedInformationFromJson(json, host), {
category: json.category,
code: json.code,
source: json.source,
relatedInformation: json.relatedInformation && json.relatedInformation.map(json => diagnosticRelatedInformationFromJson(json, host))
});
}
exports.diagnosticFromJson = diagnosticFromJson;
/**
* @param {DiagnosticRelatedInformation} diagnostic
* @returns {*}
*/
function diagnosticRelatedInformationToJson(diagnostic) {
return {
file: diagnostic.file && { fileName: diagnostic.file.fileName },
start: diagnostic.start,
length: diagnostic.length,
messageText: diagnostic.messageText
};
}
exports.diagnosticRelatedInformationToJson = diagnosticRelatedInformationToJson;
/**
* @param {*} json
* @param {SourceFileHost} host
* @returns {DiagnosticRelatedInformation}
*/
function diagnosticRelatedInformationFromJson(json, host) {
return {
file: json.file && sourceFileFromJson(json.file, host),
start: json.start,
length: json.length,
messageText: json.messageText,
category: json.category,
code: json.code
};
}
exports.diagnosticRelatedInformationFromJson = diagnosticRelatedInformationFromJson;
exports.message = {};
/**
* @param {string | undefined} tsConfigFileName
* @param {import("gulp-typescript").Settings} settings
* @param {Object} options
* @param {string} [options.typescript]
* @returns {CreateProjectMessage}
*
* @typedef CreateProjectMessage
* @property {"createProject"} method
* @property {CreateProjectParams} params
*
* @typedef CreateProjectParams
* @property {string | undefined} tsConfigFileName
* @property {import("gulp-typescript").Settings} settings
* @property {CreateProjectOptions} options
*
* @typedef CreateProjectOptions
* @property {string} [typescript]
*/
exports.message.createProject = function(tsConfigFileName, settings, options) {
return { method: "createProject", params: { tsConfigFileName, settings, options } };
};
/**
* @param {File} file
* @returns {WriteMessage}
*
* @typedef WriteMessage
* @property {"write"} method
* @property {*} params
*/
exports.message.write = function(file) {
return { method: "write", params: vinylToJson(file) };
};
/**
* @returns {FinalMessage}
*
* @typedef FinalMessage
* @property {"final"} method
*/
exports.message.final = function() {
return { method: "final" };
};
/**
* @param {Error} error
* @returns {ErrorMessage}
*
* @typedef ErrorMessage
* @property {"error"} method
* @property {*} params
*/
exports.message.error = function(error) {
return { method: "error", params: errorToJson(error) };
};
exports.message.reporter = {};
/**
* @param {TypeScriptError} error
* @returns {reporter.ErrorMessage}
*
* @typedef reporter.ErrorMessage
* @property {"reporter.error"} method
* @property {*} params
*/
exports.message.reporter.error = function(error) {
return { method: "reporter.error", params: typeScriptErrorToJson(error) };
};
/**
* @param {*} results
* @returns {reporter.FinishMessage}
*
* @typedef reporter.FinishMessage
* @property {"reporter.finish"} method
* @property {*} params
*/
exports.message.reporter.finish = function(results) {
return { method: "reporter.finish", params: results };
};
/**
* @typedef {import("vinyl")} File
* @typedef {typeof import("typescript")} TypeScriptModule
* @typedef {import("typescript").SourceFile} SourceFile
* @typedef {import("typescript").Diagnostic} Diagnostic
* @typedef {import("typescript").DiagnosticRelatedInformation} DiagnosticRelatedInformation
* @typedef {import("gulp-typescript").reporter.TypeScriptError} TypeScriptError
* @typedef {WriteMessage | FinalMessage | CreateProjectMessage} HostMessage
* @typedef {WriteMessage | FinalMessage | ErrorMessage | reporter.ErrorMessage | reporter.FinishMessage} WorkerMessage
*
* @typedef SourceFileHost
* @property {(fileName: string) => SourceFile | undefined} getSourceFile
* @property {(fileName: string, text: string, languageVersion: number) => SourceFile} createSourceFile
*
* @typedef VinylHost
* @property {(path: string) => File | undefined} getVinyl
*/
void 0;

View file

@ -1,79 +0,0 @@
// @ts-check
const fs = require("fs");
const tsc = require("gulp-typescript");
const { Readable, Writable } = require("stream");
const protocol = require("./protocol");
/** @type {tsc.Project} */
let project;
/** @type {Readable} */
let inputStream;
/** @type {Writable} */
let outputStream;
/** @type {tsc.CompileStream} */
let compileStream;
process.on("message", (/**@type {protocol.HostMessage}*/ message) => {
try {
switch (message.method) {
case "createProject": {
const { tsConfigFileName, settings, options } = message.params;
if (options.typescript) {
settings.typescript = require(options.typescript);
}
project = tsConfigFileName === undefined
? tsc.createProject(settings)
: tsc.createProject(tsConfigFileName, settings);
inputStream = new Readable({
objectMode: true,
read() {}
});
outputStream = new Writable({
objectMode: true,
/**
* @param {*} file
*/
write(file, _, callback) {
process.send(protocol.message.write(file));
callback();
},
final(callback) {
process.send(protocol.message.final());
callback();
}
});
compileStream = project({
error(error) { process.send(protocol.message.reporter.error(error)); },
finish(results) { process.send(protocol.message.reporter.finish(results)); }
});
compileStream.on("error", error => {
process.send(protocol.message.error(error));
});
outputStream.on("error", () => {
/* do nothing */
});
inputStream.pipe(compileStream).pipe(outputStream);
break;
}
case "write": {
const file = protocol.vinylFromJson(message.params);
if (!file.isBuffer()) file.contents = fs.readFileSync(file.path);
inputStream.push(file);
break;
}
case "final": {
inputStream.push(null);
break;
}
}
}
catch (e) {
process.send(protocol.message.error(e));
}
});

View file

@ -1,8 +0,0 @@
// @ts-check
/**
* @typedef {import("gulp").Gulp} Gulp
* @typedef {import("gulp-help").GulpHelp} GulpHelp
* @typedef {GulpHelp & { Gulp: new () => Gulp }} DotGulpModule
* @type {DotGulpModule}
*/
module.exports = require("gulp-help")(require("gulp"));

View file

@ -1,30 +0,0 @@
// @ts-check
const readJson = require("./readJson");
const path = require("path");
const gulp = require("./gulp");
const newer = require("gulp-newer");
const concat = require("gulp-concat");
const merge2 = require("merge2");
/** @type {{ libs: string[], paths?: Record<string, string>, sources?: Record<string, string[]> }} */
const libraries = readJson("./src/lib/libs.json");
const libs = libraries.libs.map(lib => {
const relativeSources = ["header.d.ts"].concat(libraries.sources && libraries.sources[lib] || [lib + ".d.ts"]);
const relativeTarget = libraries.paths && libraries.paths[lib] || ("lib." + lib + ".d.ts");
const sources = relativeSources.map(s => path.posix.join("src/lib", s));
const target = `built/local/${relativeTarget}`;
return { target, relativeTarget, sources };
});
exports.libraryTargets = libs.map(lib => lib.target);
/**
* @param {string[]} prepends
*/
function generateLibs(prepends) {
return merge2(libs.map(({ sources, target, relativeTarget }) =>
gulp.src(prepends.concat(sources))
.pipe(newer(target))
.pipe(concat(relativeTarget, { newLine: "\n\n" }))
.pipe(gulp.dest("built/local"))));
}
exports.generateLibs = generateLibs;

View file

@ -1,14 +0,0 @@
// @ts-check
const mkdirp = require("mkdirp");
module.exports = exports = mkdirpAsync;
/**
* @param {string} dir
* @param {mkdirp.Mode | mkdirp.Options} [opts]
*/
function mkdirpAsync(dir, opts) {
return new Promise((resolve, reject) => mkdirp(dir, opts, (err, made) => err ? reject(err) : resolve(made)));
}
exports.sync = mkdirp.sync;

View file

@ -1,72 +0,0 @@
// @ts-check
const fs = require("fs");
module.exports = needsUpdate;
/**
* @param {string | string[]} source
* @param {string | string[]} dest
* @returns {boolean}
*/
function needsUpdate(source, dest) {
if (typeof source === "string" && typeof dest === "string") {
if (fs.existsSync(dest)) {
const {mtime: outTime} = fs.statSync(dest);
const {mtime: inTime} = fs.statSync(source);
if (+inTime <= +outTime) {
return false;
}
}
}
else if (typeof source === "string" && typeof dest !== "string") {
const {mtime: inTime} = fs.statSync(source);
for (const filepath of dest) {
if (fs.existsSync(filepath)) {
const {mtime: outTime} = fs.statSync(filepath);
if (+inTime > +outTime) {
return true;
}
}
else {
return true;
}
}
return false;
}
else if (typeof source !== "string" && typeof dest === "string") {
if (fs.existsSync(dest)) {
const {mtime: outTime} = fs.statSync(dest);
for (const filepath of source) {
if (fs.existsSync(filepath)) {
const {mtime: inTime} = fs.statSync(filepath);
if (+inTime > +outTime) {
return true;
}
}
else {
return true;
}
}
return false;
}
}
else if (typeof source !== "string" && typeof dest !== "string") {
for (let i = 0; i < source.length; i++) {
if (!dest[i]) {
continue;
}
if (fs.existsSync(dest[i])) {
const {mtime: outTime} = fs.statSync(dest[i]);
const {mtime: inTime} = fs.statSync(source[i]);
if (+inTime > +outTime) {
return true;
}
}
else {
return true;
}
}
return false;
}
return true;
}

View file

@ -4,7 +4,7 @@ const os = require("os");
/** @type {CommandLineOptions} */
module.exports = minimist(process.argv.slice(2), {
boolean: ["debug", "dirty", "inspect", "light", "colors", "lint", "lkg", "soft", "fix", "failed", "keepFailed"],
boolean: ["debug", "dirty", "inspect", "light", "colors", "lint", "lkg", "soft", "fix", "failed", "keepFailed", "force", "built"],
string: ["browser", "tests", "host", "reporter", "stackTraceLimit", "timeout"],
alias: {
"b": "browser",
@ -15,7 +15,7 @@ module.exports = minimist(process.argv.slice(2), {
"r": "reporter",
"c": "colors", "color": "colors",
"w": "workers",
"f": "fix",
"f": "fix"
},
default: {
soft: false,
@ -35,10 +35,15 @@ module.exports = minimist(process.argv.slice(2), {
failed: false,
keepFailed: false,
lkg: true,
dirty: false
dirty: false,
built: false
}
});
if (module.exports.built) {
module.exports.lkg = false;
}
/**
* @typedef TypedOptions
* @property {boolean} debug
@ -48,6 +53,7 @@ module.exports = minimist(process.argv.slice(2), {
* @property {boolean} colors
* @property {boolean} lint
* @property {boolean} lkg
* @property {boolean} built
* @property {boolean} soft
* @property {boolean} fix
* @property {string} browser

View file

@ -1,20 +1,18 @@
// @ts-check
const stream = require("stream");
const Vinyl = require("./vinyl");
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
* @param {string | ((file: import("vinyl")) => string)} data
*/
function prepend(data) {
return new stream.Transform({
objectMode: true,
/**
* @param {string | Buffer | Vinyl} input
* @param {string | Buffer | import("vinyl")} input
* @param {(error: Error, data?: any) => void} cb
*/
transform(input, _, cb) {
@ -56,11 +54,11 @@ function prepend(data) {
exports.prepend = prepend;
/**
* @param {string | ((file: Vinyl) => string)} file
* @param {string | ((file: import("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;
exports.prependFile = prependFile;

File diff suppressed because it is too large Load diff

60
scripts/build/projects.js Normal file
View file

@ -0,0 +1,60 @@
// @ts-check
const { exec, Debouncer } = require("./utils");
class ProjectQueue {
/**
* @param {(projects: string[], lkg: boolean, force: boolean) => Promise<any>} action
*/
constructor(action) {
/** @type {{ lkg: boolean, force: boolean, projects?: string[], debouncer: Debouncer }[]} */
this._debouncers = [];
this._action = action;
}
/**
* @param {string} project
* @param {object} options
*/
enqueue(project, { lkg = true, force = false } = {}) {
let entry = this._debouncers.find(entry => entry.lkg === lkg && entry.force === force);
if (!entry) {
const debouncer = new Debouncer(100, async () => {
const projects = entry.projects;
if (projects) {
entry.projects = undefined;
await this._action(projects, lkg, force);
}
});
this._debouncers.push(entry = { lkg, force, debouncer });
}
if (!entry.projects) entry.projects = [];
entry.projects.push(project);
return entry.debouncer.enqueue();
}
}
const projectBuilder = new ProjectQueue((projects, lkg, force) => exec(process.execPath, [lkg ? "./lib/tsc" : "./built/local/tsc", "-b", ...(force ? ["--force"] : []), ...projects], { hidePrompt: true }));
/**
* @param {string} project
* @param {object} [options]
* @param {boolean} [options.lkg=true]
* @param {boolean} [options.force=false]
*/
exports.buildProject = (project, { lkg, force } = {}) => projectBuilder.enqueue(project, { lkg, force });
const projectCleaner = new ProjectQueue((projects, lkg) => exec(process.execPath, [lkg ? "./lib/tsc" : "./built/local/tsc", "-b", "--clean", ...projects], { hidePrompt: true }));
/**
* @param {string} project
*/
exports.cleanProject = (project) => projectCleaner.enqueue(project);
const projectWatcher = new ProjectQueue((projects) => exec(process.execPath, ["./lib/tsc", "-b", "--watch", ...projects], { hidePrompt: true }));
/**
* @param {string} project
* @param {object} [options]
* @param {boolean} [options.lkg=true]
*/
exports.watchProject = (project, { lkg } = {}) => projectWatcher.enqueue(project, { lkg });

View file

@ -1,17 +0,0 @@
// @ts-check
const ts = require("../../lib/typescript");
const fs = require("fs");
const { reportDiagnostics } = require("./diagnostics");
module.exports = exports = readJson;
/** @param {string} jsonPath */
function readJson(jsonPath) {
const jsonText = fs.readFileSync(jsonPath, "utf8");
const result = ts.parseConfigFileTextToJson(jsonPath, jsonText);
if (result.error) {
reportDiagnostics([result.error]);
throw new Error("An error occurred during parse.");
}
return result.config;
}

View file

@ -1,12 +0,0 @@
// @ts-check
const insert = require("gulp-insert");
/**
* @param {string | RegExp} searchValue
* @param {string | ((...args: string[]) => string)} replacer
*/
function replace(searchValue, replacer) {
return insert.transform(content => content.replace(searchValue, /**@type {string}*/(replacer)));
}
module.exports = replace;

View file

@ -1,84 +0,0 @@
// @ts-check
const { Duplex } = require("stream");
const path = require("path");
const Vinyl = require("vinyl");
const del = require("del");
module.exports = rm;
/**
* @param {string | ((file: File) => string) | Options} [dest]
* @param {Options} [opts]
*/
function rm(dest, opts) {
if (dest && typeof dest === "object") opts = dest, dest = undefined;
let failed = false;
const cwd = path.resolve(opts && opts.cwd || process.cwd());
/** @type {{ file: File, deleted: boolean, promise: Promise<any>, cb: Function }[]} */
const pending = [];
const processDeleted = () => {
if (failed) return;
while (pending.length && pending[0].deleted) {
const { file, cb } = pending.shift();
duplex.push(file);
cb();
}
};
const duplex = new Duplex({
objectMode: true,
/**
* @param {string|Buffer|File} file
*/
write(file, _, cb) {
if (failed) return;
if (typeof file === "string" || Buffer.isBuffer(file)) return cb(new Error("Only Vinyl files are supported."));
const basePath = typeof dest === "string" ? path.resolve(cwd, dest) :
typeof dest === "function" ? path.resolve(cwd, dest(file)) :
file.base;
const filePath = path.resolve(basePath, file.relative);
file.cwd = cwd;
file.base = basePath;
file.path = filePath;
const entry = {
file,
deleted: false,
cb,
promise: del(file.path).then(() => {
entry.deleted = true;
processDeleted();
}, err => {
failed = true;
pending.length = 0;
cb(err);
})
};
pending.push(entry);
},
final(cb) {
processDeleted();
if (pending.length) {
Promise
.all(pending.map(entry => entry.promise))
.then(() => processDeleted())
.then(() => cb(), cb);
return;
}
cb();
},
read() {
}
});
return duplex;
}
/**
* @typedef {import("vinyl")} File
*
* @typedef Options
* @property {string} [cwd]
*/
void 0;

View file

@ -1,12 +1,13 @@
// @ts-check
/// <reference path="../types/ambient.d.ts" />
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 {import("vinyl")} input
* @param {string | Buffer} contents
* @param {string | RawSourceMap} [sourceMap]
*/
@ -16,13 +17,13 @@ function replaceContents(input, contents, sourceMap) {
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));
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());
sourceMap = /** @type {RawSourceMap} */(newSourceMapConverter.toObject());
output.contents = new Buffer(convertMap.removeMapFileComments(stringContents), "utf8");
}
}
@ -31,7 +32,7 @@ function replaceContents(input, contents, sourceMap) {
const base = input.base || cwd;
const sourceRoot = output.sourceMap.sourceRoot;
makeAbsoluteSourceMap(cwd, base, output.sourceMap);
makeAbsoluteSourceMap(cwd, base, sourceMap);
makeAbsoluteSourceMap(cwd, base, /** @type {RawSourceMap} */(sourceMap));
applySourceMap(output, sourceMap);
makeRelativeSourceMap(cwd, base, sourceRoot, output.sourceMap);
}
@ -44,10 +45,12 @@ function replaceContents(input, contents, sourceMap) {
exports.replaceContents = replaceContents;
function removeSourceMaps() {
return through2.obj((/**@type {Vinyl}*/file, _, cb) => {
if (file.sourceMap && file.isBuffer()) {
return through2.obj((/**@type {import("vinyl")}*/file, _, cb) => {
if (file.isBuffer()) {
file.contents = Buffer.from(convertMap.removeMapFileComments(file.contents.toString("utf8")), "utf8");
file.sourceMap = undefined;
if (file.sourceMap) {
file.sourceMap = undefined;
}
}
cb(null, file);
});
@ -59,7 +62,7 @@ exports.removeSourceMaps = removeSourceMaps;
* @param {string | undefined} base
* @param {RawSourceMap} sourceMap
*
* @typedef RawSourceMap
* @typedef {object} RawSourceMap
* @property {string} version
* @property {string} file
* @property {string} [sourceRoot]

View file

@ -1,16 +1,16 @@
// @ts-check
const gulp = require("./gulp");
const gulp = require("gulp");
const del = require("del");
const fs = require("fs");
const os = require("os");
const path = require("path");
const mkdirP = require("./mkdirp");
const mkdirP = require("mkdirp");
const log = require("fancy-log");
const cmdLineOptions = require("./options");
const exec = require("./exec");
const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util)
const { CancellationToken } = require("prex");
const mochaJs = require.resolve("mocha/bin/_mocha");
const { exec } = require("./utils");
const mochaJs = require.resolve("mocha/bin/_mocha");
exports.localBaseline = "tests/baselines/local/";
exports.refBaseline = "tests/baselines/reference/";
exports.localRwcBaseline = "internal/baselines/rwc/local";
@ -27,7 +27,6 @@ exports.localTest262Baseline = "internal/baselines/test262/local";
async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode, cancelToken = CancellationToken.none) {
let testTimeout = cmdLineOptions.timeout;
let tests = cmdLineOptions.tests;
const lintFlag = cmdLineOptions.lint;
const debug = cmdLineOptions.debug;
const inspect = cmdLineOptions.inspect;
const runners = cmdLineOptions.runners;
@ -117,9 +116,6 @@ async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode,
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;
@ -144,10 +140,10 @@ async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode,
}
exports.runConsoleTests = runConsoleTests;
function cleanTestDirs() {
return del([exports.localBaseline, exports.localRwcBaseline])
.then(() => mkdirP(exports.localRwcBaseline))
.then(() => mkdirP(exports.localBaseline));
async function cleanTestDirs() {
await del([exports.localBaseline, exports.localRwcBaseline])
mkdirP.sync(exports.localRwcBaseline);
mkdirP.sync(exports.localBaseline);
}
exports.cleanTestDirs = cleanTestDirs;

View file

@ -1,435 +0,0 @@
// @ts-check
const path = require("path");
const fs = require("fs");
const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util)
const ts = require("../../lib/typescript");
const { Duplex } = require("stream");
const chalk = /**@type {*} */(require("chalk"));
const Vinyl = require("vinyl");
/**
* Creates a stream that passes through its inputs only if the project outputs are not up to date
* with respect to the inputs.
* @param {ParsedCommandLine} parsedProject
* @param {UpToDateOptions} [options]
*
* @typedef UpToDateOptions
* @property {boolean | "minimal"} [verbose]
* @property {(configFilePath: string) => ParsedCommandLine | undefined} [parseProject]
*/
function upToDate(parsedProject, options) {
/** @type {File[]} */
const inputs = [];
/** @type {Map<string, File>} */
const inputMap = new Map();
/** @type {Map<string, fs.Stats>} */
const statCache = new Map();
/** @type {UpToDateHost} */
const upToDateHost = {
fileExists(fileName) {
const stats = getStat(fileName);
return stats ? stats.isFile() : false;
},
getModifiedTime(fileName) {
return getStat(fileName).mtime;
},
parseConfigFile: options && options.parseProject
};
const duplex = new Duplex({
objectMode: true,
/**
* @param {string|Buffer|File} file
*/
write(file, _, cb) {
if (typeof file === "string" || Buffer.isBuffer(file)) return cb(new Error("Only Vinyl files are supported."));
inputs.push(file);
inputMap.set(path.resolve(file.path), file);
cb();
},
final(cb) {
const status = getUpToDateStatus(upToDateHost, parsedProject);
reportStatus(parsedProject, status, options);
if (status.type !== UpToDateStatusType.UpToDate) {
for (const input of inputs) duplex.push(input);
}
duplex.push(null);
inputMap.clear();
statCache.clear();
cb();
},
read() {
}
});
return duplex;
function getStat(fileName) {
fileName = path.resolve(fileName);
const inputFile = inputMap.get(fileName);
if (inputFile && inputFile.stat) return inputFile.stat;
let stats = statCache.get(fileName);
if (!stats && fs.existsSync(fileName)) {
stats = fs.statSync(fileName);
statCache.set(fileName, stats);
}
return stats;
}
}
module.exports = exports = upToDate;
/**
* @param {DiagnosticMessage} message
* @param {...string} args
*/
function formatMessage(message, ...args) {
log.info(formatStringFromArgs(message.message, args));
}
/**
* @param {ParsedCommandLine} project
* @param {UpToDateStatus} status
* @param {{verbose?: boolean | "minimal"}} options
*/
function reportStatus(project, status, options) {
switch (options.verbose) {
case "minimal":
switch (status.type) {
case UpToDateStatusType.UpToDate:
log.info(`Project '${fileName(project.options.configFilePath)}' is up to date.`);
break;
default:
log.info(`Project '${fileName(project.options.configFilePath)}' is out of date, rebuilding...`);
break;
}
break;
case true:
/**@type {*}*/(ts).formatUpToDateStatus(project.options.configFilePath, status, fileName, formatMessage);
break;
}
if (!options.verbose) return;
}
/**
* @param {string} file
* @private
*/
function normalizeSlashes(file) {
return file.replace(/\\/g, "/");
}
/**
* @param {string} file
* @private
*/
function fileName(file) {
return chalk.cyan(normalizeSlashes(path.relative(process.cwd(), path.resolve(file))));
}
/**
* @param {string} text
* @param {string[]} args
* @param {number} [baseIndex]
*/
function formatStringFromArgs(text, args, baseIndex = 0) {
return text.replace(/{(\d+)}/g, (_match, index) => args[+index + baseIndex]);
}
const minimumDate = new Date(-8640000000000000);
const maximumDate = new Date(8640000000000000);
const missingFileModifiedTime = new Date(0);
/**
* @typedef {0} UpToDateStatusType.Unbuildable
* @typedef {1} UpToDateStatusType.UpToDate
* @typedef {2} UpToDateStatusType.UpToDateWithUpstreamTypes
* @typedef {3} UpToDateStatusType.OutputMissing
* @typedef {4} UpToDateStatusType.OutOfDateWithSelf
* @typedef {5} UpToDateStatusType.OutOfDateWithUpstream
* @typedef {6} UpToDateStatusType.UpstreamOutOfDate
* @typedef {7} UpToDateStatusType.UpstreamBlocked
* @typedef {8} UpToDateStatusType.ComputingUpstream
* @typedef {9} UpToDateStatusType.ContainerOnly
* @enum {UpToDateStatusType.Unbuildable | UpToDateStatusType.UpToDate | UpToDateStatusType.UpToDateWithUpstreamTypes | UpToDateStatusType.OutputMissing | UpToDateStatusType.OutOfDateWithSelf | UpToDateStatusType.OutOfDateWithUpstream | UpToDateStatusType.UpstreamOutOfDate | UpToDateStatusType.UpstreamBlocked | UpToDateStatusType.ComputingUpstream | UpToDateStatusType.ContainerOnly}
*/
const UpToDateStatusType = {
Unbuildable: /** @type {0} */(0),
UpToDate: /** @type {1} */(1),
UpToDateWithUpstreamTypes: /** @type {2} */(2),
OutputMissing: /** @type {3} */(3),
OutOfDateWithSelf: /** @type {4} */(4),
OutOfDateWithUpstream: /** @type {5} */(5),
UpstreamOutOfDate: /** @type {6} */(6),
UpstreamBlocked: /** @type {7} */(7),
ComputingUpstream: /** @type {8} */(8),
ContainerOnly: /** @type {9} */(9),
};
/**
* @param {Date} date1
* @param {Date} date2
* @returns {Date}
*/
function newer(date1, date2) {
return date2 > date1 ? date2 : date1;
}
/**
* @param {UpToDateHost} host
* @param {ParsedCommandLine | undefined} project
* @returns {UpToDateStatus}
*/
function getUpToDateStatus(host, project) {
if (project === undefined) return { type: UpToDateStatusType.Unbuildable, reason: "File deleted mid-build" };
const prior = host.getLastStatus ? host.getLastStatus(project.options.configFilePath) : undefined;
if (prior !== undefined) {
return prior;
}
const actual = getUpToDateStatusWorker(host, project);
if (host.setLastStatus) {
host.setLastStatus(project.options.configFilePath, actual);
}
return actual;
}
/**
* @param {UpToDateHost} host
* @param {ParsedCommandLine | undefined} project
* @returns {UpToDateStatus}
*/
function getUpToDateStatusWorker(host, project) {
/** @type {string} */
let newestInputFileName = undefined;
let newestInputFileTime = minimumDate;
// Get timestamps of input files
for (const inputFile of project.fileNames) {
if (!host.fileExists(inputFile)) {
return {
type: UpToDateStatusType.Unbuildable,
reason: `${inputFile} does not exist`
};
}
const inputTime = host.getModifiedTime(inputFile) || missingFileModifiedTime;
if (inputTime > newestInputFileTime) {
newestInputFileName = inputFile;
newestInputFileTime = inputTime;
}
}
// Collect the expected outputs of this project
const outputs = /**@type {string[]}*/(/**@type {*}*/(ts).getAllProjectOutputs(project));
if (outputs.length === 0) {
return {
type: UpToDateStatusType.ContainerOnly
};
}
// Now see if all outputs are newer than the newest input
let oldestOutputFileName = "(none)";
let oldestOutputFileTime = maximumDate;
let newestOutputFileName = "(none)";
let newestOutputFileTime = minimumDate;
/** @type {string | undefined} */
let missingOutputFileName;
let newestDeclarationFileContentChangedTime = minimumDate;
let isOutOfDateWithInputs = false;
for (const output of outputs) {
// Output is missing; can stop checking
// Don't immediately return because we can still be upstream-blocked, which is a higher-priority status
if (!host.fileExists(output)) {
missingOutputFileName = output;
break;
}
const outputTime = host.getModifiedTime(output) || missingFileModifiedTime;
if (outputTime < oldestOutputFileTime) {
oldestOutputFileTime = outputTime;
oldestOutputFileName = output;
}
// If an output is older than the newest input, we can stop checking
// Don't immediately return because we can still be upstream-blocked, which is a higher-priority status
if (outputTime < newestInputFileTime) {
isOutOfDateWithInputs = true;
break;
}
if (outputTime > newestOutputFileTime) {
newestOutputFileTime = outputTime;
newestOutputFileName = output;
}
// Keep track of when the most recent time a .d.ts file was changed.
// In addition to file timestamps, we also keep track of when a .d.ts file
// had its file touched but not had its contents changed - this allows us
// to skip a downstream typecheck
if (path.extname(output) === ".d.ts") {
const unchangedTime = host.getUnchangedTime ? host.getUnchangedTime(output) : undefined;
if (unchangedTime !== undefined) {
newestDeclarationFileContentChangedTime = newer(unchangedTime, newestDeclarationFileContentChangedTime);
}
else {
const outputModifiedTime = host.getModifiedTime(output) || missingFileModifiedTime;
newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, outputModifiedTime);
}
}
}
let pseudoUpToDate = false;
let usesPrepend = false;
/** @type {string | undefined} */
let upstreamChangedProject;
if (project.projectReferences) {
if (host.setLastStatus) host.setLastStatus(project.options.configFilePath, { type: UpToDateStatusType.ComputingUpstream });
for (const ref of project.projectReferences) {
usesPrepend = usesPrepend || !!(ref.prepend);
const resolvedRef = ts.resolveProjectReferencePath(host, ref);
const parsedRef = host.parseConfigFile ? host.parseConfigFile(resolvedRef) : ts.getParsedCommandLineOfConfigFile(resolvedRef, {}, parseConfigHost);
const refStatus = getUpToDateStatus(host, parsedRef);
// Its a circular reference ignore the status of this project
if (refStatus.type === UpToDateStatusType.ComputingUpstream) {
continue;
}
// An upstream project is blocked
if (refStatus.type === UpToDateStatusType.Unbuildable) {
return {
type: UpToDateStatusType.UpstreamBlocked,
upstreamProjectName: ref.path
};
}
// If the upstream project is out of date, then so are we (someone shouldn't have asked, though?)
if (refStatus.type !== UpToDateStatusType.UpToDate) {
return {
type: UpToDateStatusType.UpstreamOutOfDate,
upstreamProjectName: ref.path
};
}
// If the upstream project's newest file is older than our oldest output, we
// can't be out of date because of it
if (refStatus.newestInputFileTime && refStatus.newestInputFileTime <= oldestOutputFileTime) {
continue;
}
// If the upstream project has only change .d.ts files, and we've built
// *after* those files, then we're "psuedo up to date" and eligible for a fast rebuild
if (refStatus.newestDeclarationFileContentChangedTime && refStatus.newestDeclarationFileContentChangedTime <= oldestOutputFileTime) {
pseudoUpToDate = true;
upstreamChangedProject = ref.path;
continue;
}
// We have an output older than an upstream output - we are out of date
return {
type: UpToDateStatusType.OutOfDateWithUpstream,
outOfDateOutputFileName: oldestOutputFileName,
newerProjectName: ref.path
};
}
}
if (missingOutputFileName !== undefined) {
return {
type: UpToDateStatusType.OutputMissing,
missingOutputFileName
};
}
if (isOutOfDateWithInputs) {
return {
type: UpToDateStatusType.OutOfDateWithSelf,
outOfDateOutputFileName: oldestOutputFileName,
newerInputFileName: newestInputFileName
};
}
if (usesPrepend && pseudoUpToDate) {
return {
type: UpToDateStatusType.OutOfDateWithUpstream,
outOfDateOutputFileName: oldestOutputFileName,
newerProjectName: upstreamChangedProject
};
}
// Up to date
return {
type: pseudoUpToDate ? UpToDateStatusType.UpToDateWithUpstreamTypes : UpToDateStatusType.UpToDate,
newestDeclarationFileContentChangedTime,
newestInputFileTime,
newestOutputFileTime,
newestInputFileName,
newestOutputFileName,
oldestOutputFileName
};
}
const parseConfigHost = {
useCaseSensitiveFileNames: true,
getCurrentDirectory: () => process.cwd(),
readDirectory: (file) => fs.readdirSync(file),
fileExists: file => fs.existsSync(file) && fs.statSync(file).isFile(),
readFile: file => fs.readFileSync(file, "utf8"),
onUnRecoverableConfigFileDiagnostic: () => undefined
};
/**
* @typedef {import("vinyl")} File
* @typedef {import("../../lib/typescript").ParsedCommandLine & { options: CompilerOptions }} ParsedCommandLine
* @typedef {import("../../lib/typescript").CompilerOptions & { configFilePath?: string }} CompilerOptions
* @typedef {import("../../lib/typescript").DiagnosticMessage} DiagnosticMessage
* @typedef UpToDateHost
* @property {(fileName: string) => boolean} fileExists
* @property {(fileName: string) => Date} getModifiedTime
* @property {(fileName: string) => Date} [getUnchangedTime]
* @property {(configFilePath: string) => ParsedCommandLine | undefined} parseConfigFile
* @property {(configFilePath: string) => UpToDateStatus} [getLastStatus]
* @property {(configFilePath: string, status: UpToDateStatus) => void} [setLastStatus]
*
* @typedef Status.Unbuildable
* @property {UpToDateStatusType.Unbuildable} type
* @property {string} reason
*
* @typedef Status.ContainerOnly
* @property {UpToDateStatusType.ContainerOnly} type
*
* @typedef Status.UpToDate
* @property {UpToDateStatusType.UpToDate | UpToDateStatusType.UpToDateWithUpstreamTypes} type
* @property {Date} [newestInputFileTime]
* @property {string} [newestInputFileName]
* @property {Date} [newestDeclarationFileContentChangedTime]
* @property {Date} [newestOutputFileTime]
* @property {string} [newestOutputFileName]
* @property {string} [oldestOutputFileName]
*
* @typedef Status.OutputMissing
* @property {UpToDateStatusType.OutputMissing} type
* @property {string} missingOutputFileName
*
* @typedef Status.OutOfDateWithSelf
* @property {UpToDateStatusType.OutOfDateWithSelf} type
* @property {string} outOfDateOutputFileName
* @property {string} newerInputFileName
*
* @typedef Status.UpstreamOutOfDate
* @property {UpToDateStatusType.UpstreamOutOfDate} type
* @property {string} upstreamProjectName
*
* @typedef Status.UpstreamBlocked
* @property {UpToDateStatusType.UpstreamBlocked} type
* @property {string} upstreamProjectName
*
* @typedef Status.ComputingUpstream
* @property {UpToDateStatusType.ComputingUpstream} type
*
* @typedef Status.OutOfDateWithUpstream
* @property {UpToDateStatusType.OutOfDateWithUpstream} type
* @property {string} outOfDateOutputFileName
* @property {string} newerProjectName
* @typedef {Status.Unbuildable | Status.ContainerOnly | Status.UpToDate | Status.OutputMissing | Status.OutOfDateWithSelf | Status.UpstreamOutOfDate | Status.UpstreamBlocked | Status.ComputingUpstream | Status.OutOfDateWithUpstream} UpToDateStatus
*/
void 0;

View file

@ -1,7 +1,119 @@
// @ts-check
/// <reference path="../types/ambient.d.ts" />
const fs = require("fs");
const File = require("./vinyl");
const { Readable } = require("stream");
const path = require("path");
const log = require("fancy-log");
const mkdirp = require("mkdirp");
const del = require("del");
const File = require("vinyl");
const ts = require("../../lib/typescript");
const { default: chalk } = require("chalk");
const { spawn } = require("child_process");
const { CancellationToken, CancelError, Deferred } = require("prex");
const { Readable, Duplex } = require("stream");
const isWindows = /^win/.test(process.platform);
/**
* Executes the provided command once with the supplied arguments.
* @param {string} cmd
* @param {string[]} args
* @param {ExecOptions} [options]
*
* @typedef ExecOptions
* @property {boolean} [ignoreExitCode]
* @property {import("prex").CancellationToken} [cancelToken]
* @property {boolean} [hidePrompt]
*/
function exec(cmd, args, options = {}) {
return /**@type {Promise<{exitCode: number}>}*/(new Promise((resolve, reject) => {
const { ignoreExitCode, cancelToken = CancellationToken.none } = options;
cancelToken.throwIfCancellationRequested();
// TODO (weswig): Update child_process types to add windowsVerbatimArguments to the type definition
const subshellFlag = isWindows ? "/c" : "-c";
const command = isWindows ? [possiblyQuote(cmd), ...args] : [`${cmd} ${args.join(" ")}`];
if (!options.hidePrompt) log(`> ${chalk.green(cmd)} ${args.join(" ")}`);
const proc = spawn(isWindows ? "cmd" : "/bin/sh", [subshellFlag, ...command], { stdio: "inherit", windowsVerbatimArguments: true });
const registration = cancelToken.register(() => {
log(`${chalk.red("killing")} '${chalk.green(cmd)} ${args.join(" ")}'...`);
proc.kill("SIGINT");
proc.kill("SIGTERM");
reject(new CancelError());
});
proc.on("exit", exitCode => {
registration.unregister();
if (exitCode === 0 || ignoreExitCode) {
resolve({ exitCode });
}
else {
reject(new Error(`Process exited with code: ${exitCode}`));
}
});
proc.on("error", error => {
registration.unregister();
reject(error);
});
}));
}
exports.exec = exec;
/**
* @param {string} cmd
*/
function possiblyQuote(cmd) {
return cmd.indexOf(" ") >= 0 ? `"${cmd}"` : cmd;
}
/**
* @param {ts.Diagnostic[]} diagnostics
* @param {{ cwd?: string, pretty?: boolean }} [options]
*/
function formatDiagnostics(diagnostics, options) {
return options && options.pretty
? ts.formatDiagnosticsWithColorAndContext(diagnostics, getFormatDiagnosticsHost(options && options.cwd))
: ts.formatDiagnostics(diagnostics, getFormatDiagnosticsHost(options && options.cwd));
}
exports.formatDiagnostics = formatDiagnostics;
/**
* @param {ts.Diagnostic[]} diagnostics
* @param {{ cwd?: string }} [options]
*/
function reportDiagnostics(diagnostics, options) {
log(formatDiagnostics(diagnostics, { cwd: options && options.cwd, pretty: process.stdout.isTTY }));
}
exports.reportDiagnostics = reportDiagnostics;
/**
* @param {string | undefined} cwd
* @returns {ts.FormatDiagnosticsHost}
*/
function getFormatDiagnosticsHost(cwd) {
return {
getCanonicalFileName: fileName => fileName,
getCurrentDirectory: () => cwd,
getNewLine: () => ts.sys.newLine,
};
}
exports.getFormatDiagnosticsHost = getFormatDiagnosticsHost;
/**
* Reads JSON data with optional comments using the LKG TypeScript compiler
* @param {string} jsonPath
*/
function readJson(jsonPath) {
const jsonText = fs.readFileSync(jsonPath, "utf8");
const result = ts.parseConfigFileTextToJson(jsonPath, jsonText);
if (result.error) {
reportDiagnostics([result.error]);
throw new Error("An error occurred during parse.");
}
return result.config;
}
exports.readJson = readJson;
/**
* @param {File} file
@ -24,4 +136,299 @@ function streamFromBuffer(buffer) {
}
});
}
exports.streamFromBuffer = streamFromBuffer;
exports.streamFromBuffer = streamFromBuffer;
/**
* @param {string | string[]} source
* @param {string | string[]} dest
* @returns {boolean}
*/
function needsUpdate(source, dest) {
if (typeof source === "string" && typeof dest === "string") {
if (fs.existsSync(dest)) {
const {mtime: outTime} = fs.statSync(dest);
const {mtime: inTime} = fs.statSync(source);
if (+inTime <= +outTime) {
return false;
}
}
}
else if (typeof source === "string" && typeof dest !== "string") {
const {mtime: inTime} = fs.statSync(source);
for (const filepath of dest) {
if (fs.existsSync(filepath)) {
const {mtime: outTime} = fs.statSync(filepath);
if (+inTime > +outTime) {
return true;
}
}
else {
return true;
}
}
return false;
}
else if (typeof source !== "string" && typeof dest === "string") {
if (fs.existsSync(dest)) {
const {mtime: outTime} = fs.statSync(dest);
for (const filepath of source) {
if (fs.existsSync(filepath)) {
const {mtime: inTime} = fs.statSync(filepath);
if (+inTime > +outTime) {
return true;
}
}
else {
return true;
}
}
return false;
}
}
else if (typeof source !== "string" && typeof dest !== "string") {
for (let i = 0; i < source.length; i++) {
if (!dest[i]) {
continue;
}
if (fs.existsSync(dest[i])) {
const {mtime: outTime} = fs.statSync(dest[i]);
const {mtime: inTime} = fs.statSync(source[i]);
if (+inTime > +outTime) {
return true;
}
}
else {
return true;
}
}
return false;
}
return true;
}
exports.needsUpdate = needsUpdate;
function getDiffTool() {
const program = process.env.DIFF;
if (!program) {
log.warn("Add the 'DIFF' environment variable to the path of the program you want to use.");
process.exit(1);
}
return program;
}
exports.getDiffTool = getDiffTool;
/**
* Find the size of a directory recursively.
* Symbolic links can cause a loop.
* @param {string} root
* @returns {number} bytes
*/
function getDirSize(root) {
const stats = fs.lstatSync(root);
if (!stats.isDirectory()) {
return stats.size;
}
return fs.readdirSync(root)
.map(file => getDirSize(path.join(root, file)))
.reduce((acc, num) => acc + num, 0);
}
exports.getDirSize = getDirSize;
/**
* Flattens a project with project references into a single project.
* @param {string} projectSpec The path to a tsconfig.json file or its containing directory.
* @param {string} flattenedProjectSpec The output path for the flattened tsconfig.json file.
* @param {FlattenOptions} [options] Options used to flatten a project hierarchy.
*
* @typedef FlattenOptions
* @property {string} [cwd] The path to use for the current working directory. Defaults to `process.cwd()`.
* @property {import("../../lib/typescript").CompilerOptions} [compilerOptions] Compiler option overrides.
* @property {boolean} [force] Forces creation of the output project.
* @property {string[]} [exclude] Files to exclude (relative to `cwd`)
*/
function flatten(projectSpec, flattenedProjectSpec, options = {}) {
const cwd = normalizeSlashes(options.cwd ? path.resolve(options.cwd) : process.cwd());
const files = [];
const resolvedOutputSpec = path.resolve(cwd, flattenedProjectSpec);
const resolvedOutputDirectory = path.dirname(resolvedOutputSpec);
const resolvedProjectSpec = resolveProjectSpec(projectSpec, cwd, undefined);
const project = readJson(resolvedProjectSpec);
const skipProjects = /**@type {Set<string>}*/(new Set());
const skipFiles = new Set(options && options.exclude && options.exclude.map(file => normalizeSlashes(path.resolve(cwd, file))));
recur(resolvedProjectSpec, project);
if (options.force || needsUpdate(files, resolvedOutputSpec)) {
const config = {
extends: normalizeSlashes(path.relative(resolvedOutputDirectory, resolvedProjectSpec)),
compilerOptions: options.compilerOptions || {},
files: files.map(file => normalizeSlashes(path.relative(resolvedOutputDirectory, file)))
};
mkdirp.sync(resolvedOutputDirectory);
fs.writeFileSync(resolvedOutputSpec, JSON.stringify(config, undefined, 2), "utf8");
}
/**
* @param {string} projectSpec
* @param {object} project
*/
function recur(projectSpec, project) {
if (skipProjects.has(projectSpec)) return;
skipProjects.add(project);
if (project.references) {
for (const ref of project.references) {
const referencedSpec = resolveProjectSpec(ref.path, cwd, projectSpec);
const referencedProject = readJson(referencedSpec);
recur(referencedSpec, referencedProject);
}
}
if (project.include) {
throw new Error("Flattened project may not have an 'include' list.");
}
if (!project.files) {
throw new Error("Flattened project must have an explicit 'files' list.");
}
const projectDirectory = path.dirname(projectSpec);
for (let file of project.files) {
file = normalizeSlashes(path.resolve(projectDirectory, file));
if (skipFiles.has(file)) continue;
skipFiles.add(file);
files.push(file);
}
}
}
exports.flatten = flatten;
/**
* @param {string} file
*/
function normalizeSlashes(file) {
return file.replace(/\\/g, "/");
}
/**
* @param {string} projectSpec
* @param {string} cwd
* @param {string | undefined} referrer
* @returns {string}
*/
function resolveProjectSpec(projectSpec, cwd, referrer) {
let projectPath = normalizeSlashes(path.resolve(cwd, referrer ? path.dirname(referrer) : "", projectSpec));
const stats = fs.statSync(projectPath);
if (stats.isFile()) return normalizeSlashes(projectPath);
return normalizeSlashes(path.resolve(cwd, projectPath, "tsconfig.json"));
}
/**
* @param {string | ((file: File) => string) | { cwd?: string }} [dest]
* @param {{ cwd?: string }} [opts]
*/
function rm(dest, opts) {
if (dest && typeof dest === "object") opts = dest, dest = undefined;
let failed = false;
const cwd = path.resolve(opts && opts.cwd || process.cwd());
/** @type {{ file: File, deleted: boolean, promise: Promise<any>, cb: Function }[]} */
const pending = [];
const processDeleted = () => {
if (failed) return;
while (pending.length && pending[0].deleted) {
const { file, cb } = pending.shift();
duplex.push(file);
cb();
}
};
const duplex = new Duplex({
objectMode: true,
/**
* @param {string|Buffer|File} file
*/
write(file, _, cb) {
if (failed) return;
if (typeof file === "string" || Buffer.isBuffer(file)) return cb(new Error("Only Vinyl files are supported."));
const basePath = typeof dest === "string" ? path.resolve(cwd, dest) :
typeof dest === "function" ? path.resolve(cwd, dest(file)) :
file.base;
const filePath = path.resolve(basePath, file.relative);
file.cwd = cwd;
file.base = basePath;
file.path = filePath;
const entry = {
file,
deleted: false,
cb,
promise: del(file.path).then(() => {
entry.deleted = true;
processDeleted();
}, err => {
failed = true;
pending.length = 0;
cb(err);
})
};
pending.push(entry);
},
final(cb) {
processDeleted();
if (pending.length) {
Promise
.all(pending.map(entry => entry.promise))
.then(() => processDeleted())
.then(() => cb(), cb);
return;
}
cb();
},
read() {
}
});
return duplex;
}
exports.rm = rm;
class Debouncer {
/**
* @param {number} timeout
* @param {() => Promise<any>} action
*/
constructor(timeout, action) {
this._timeout = timeout;
this._action = action;
}
enqueue() {
if (this._timer) {
clearTimeout(this._timer);
this._timer = undefined;
}
if (!this._deferred) {
this._deferred = new Deferred();
}
this._timer = setTimeout(() => this.run(), 100);
return this._deferred.promise;
}
run() {
if (this._timer) {
clearTimeout(this._timer);
this._timer = undefined;
}
const deferred = this._deferred;
this._deferred = undefined;
this._projects = undefined;
try {
deferred.resolve(this._action());
}
catch (e) {
deferred.reject(e);
}
}
}
exports.Debouncer = Debouncer;

View file

@ -1,60 +0,0 @@
// 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;
}
}

View file

@ -1 +0,0 @@
module.exports = require("vinyl");

View file

@ -1,3 +1,5 @@
import { TaskFunction } from "gulp";
declare module "gulp-clone" {
function Clone(): NodeJS.ReadWriteStream;
namespace Clone {
@ -14,3 +16,78 @@ declare module "gulp-insert" {
}
declare module "sorcery";
declare module "vinyl" {
// 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;
}
}
}
declare module "undertaker" {
interface TaskFunctionParams {
flags?: Record<string, string>;
}
}
declare module "gulp-sourcemaps" {
interface WriteOptions {
destPath?: string;
}
}