diff --git a/Gulpfile.js b/Gulpfile.js index 3aed88d3a7070..fb10563067b25 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -1,107 +1,70 @@ -/// // @ts-check const path = require("path"); -const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util) const fs = require("fs"); -const child_process = require("child_process"); -const runSequence = require("run-sequence"); +const log = require("fancy-log"); const newer = require("gulp-newer"); -const insert = require("gulp-insert"); -const { append } = require("gulp-insert"); const sourcemaps = require("gulp-sourcemaps"); const del = require("del"); const fold = require("travis-fold"); const rename = require("gulp-rename"); -const mkdirp = require("./scripts/build/mkdirp"); -const gulp = require("./scripts/build/gulp"); -const getDirSize = require("./scripts/build/getDirSize"); -const project = require("./scripts/build/project"); -const replace = require("./scripts/build/replace"); -const convertConstEnums = require("./scripts/build/convertConstEnum"); -const needsUpdate = require("./scripts/build/needsUpdate"); -const getDiffTool = require("./scripts/build/getDiffTool"); -const baselineAccept = require("./scripts/build/baselineAccept"); -const cmdLineOptions = require("./scripts/build/options"); -const exec = require("./scripts/build/exec"); -const browserify = require("./scripts/build/browserify"); -const prepend = require("./scripts/build/prepend"); -const { removeSourceMaps } = require("./scripts/build/sourcemaps"); -const { CancellationTokenSource, CancelError, delay, Semaphore } = require("prex"); -const { libraryTargets, generateLibs } = require("./scripts/build/lib"); +const concat = require("gulp-concat"); +const merge2 = require("merge2"); +const mkdirp = require("mkdirp"); +const { src, dest, task, parallel, series, watch } = require("gulp"); +const { append, transform } = require("gulp-insert"); +const { browserify } = require("./scripts/build/browserify"); +const { prependFile } = require("./scripts/build/prepend"); +const { exec, readJson, needsUpdate, getDiffTool, getDirSize, flatten, rm } = require("./scripts/build/utils"); const { runConsoleTests, cleanTestDirs, writeTestConfigFile, refBaseline, localBaseline, refRwcBaseline, localRwcBaseline } = require("./scripts/build/tests"); +const { buildProject, cleanProject, watchProject } = require("./scripts/build/projects"); +const cmdLineOptions = require("./scripts/build/options"); -Error.stackTraceLimit = 1000; - -// Constants -const host = cmdLineOptions.host; const copyright = "CopyrightNotice.txt"; +const cleanTasks = []; + +const buildScripts = () => buildProject("scripts"); +const cleanScripts = () => cleanProject("scripts"); +cleanTasks.push(cleanScripts); + +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 }; +}); + +const generateLibs = () => { + return merge2(libs.map(({ sources, target, relativeTarget }) => + src([copyright, ...sources]) + .pipe(newer(target)) + .pipe(concat(relativeTarget, { newLine: "\n\n" })) + .pipe(dest("built/local")))); +}; +task("lib", generateLibs) +task("lib").description = "Builds the library targets"; + +const cleanLib = () => del(libs.map(lib => lib.target)); +cleanTasks.push(cleanLib); + +const watchLib = () => watch(["src/lib/**/*"], generateLibs); -project.addTypeScript("lkg", "./lib/typescript.js"); -project.addTypeScript("built", "./built/local/typescriptServices.js"); -project.addTypeScript("default", "lkg"); // Compile using the LKG compiler by default - -const scriptsProject = "scripts/tsconfig.json"; -const configurePrereleaseJs = "scripts/configurePrerelease.js"; -const processDiagnosticMessagesJs = "scripts/processDiagnosticMessages.js"; -const generateLocalizedDiagnosticMessagesJs = "scripts/generateLocalizedDiagnosticMessages.js"; -const buildProtocolJs = "scripts/buildProtocol.js"; -const produceLKGJs = "scripts/produceLKG.js"; -const word2mdJs = "scripts/word2md.js"; -const scriptsTaskAliases = [configurePrereleaseJs, processDiagnosticMessagesJs, generateLocalizedDiagnosticMessagesJs, produceLKGJs, buildProtocolJs, word2mdJs]; -gulp.task("scripts", /*help*/ false, () => project.compile(scriptsProject), { aliases: scriptsTaskAliases }); -gulp.task("clean:scripts", /*help*/ false, () => project.clean(scriptsProject), { aliases: scriptsTaskAliases.map(alias => `clean:${alias}`)}); - -// Nightly management tasks -gulp.task( - "configure-nightly", - "Runs scripts/configurePrerelease.ts to prepare a build for nightly publishing", - [configurePrereleaseJs], - () => exec(host, [configurePrereleaseJs, "dev", "package.json", "src/compiler/core.ts"])); - -gulp.task( - "publish-nightly", - "Runs `npm publish --tag next` to create a new nightly build on npm", - ["LKG"], - () => runSequence("clean", "runtests-parallel", - () => exec("npm", ["publish", "--tag", "next"]))); - -const importDefinitelyTypedTestsProject = "scripts/importDefinitelyTypedTests/tsconfig.json"; -const importDefinitelyTypedTestsJs = "scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.js"; -gulp.task(importDefinitelyTypedTestsJs, /*help*/ false, () => project.compile(importDefinitelyTypedTestsProject)); -gulp.task(`clean:${importDefinitelyTypedTestsJs}`, /*help*/ false, () => project.clean(importDefinitelyTypedTestsProject)); - -gulp.task( - "importDefinitelyTypedTests", - "Runs scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.ts to copy DT's tests to the TS-internal RWC tests", - [importDefinitelyTypedTestsJs], - () => exec(host, [importDefinitelyTypedTestsJs, "./", "../DefinitelyTyped"])); - -gulp.task( - "lib", - "Builds the library targets", - () => generateLibs([copyright])); - -// The generated diagnostics map; built for the compiler and for the "generate-diagnostics" task const diagnosticInformationMapTs = "src/compiler/diagnosticInformationMap.generated.ts"; const diagnosticMessagesJson = "src/compiler/diagnosticMessages.json"; const diagnosticMessagesGeneratedJson = "src/compiler/diagnosticMessages.generated.json"; -gulp.task(diagnosticInformationMapTs, /*help*/ false, [processDiagnosticMessagesJs], () => { +const generateDiagnostics = async () => { if (needsUpdate(diagnosticMessagesJson, [diagnosticMessagesGeneratedJson, diagnosticInformationMapTs])) { - return exec(host, [processDiagnosticMessagesJs, diagnosticMessagesJson]); + await exec(process.execPath, ["scripts/processDiagnosticMessages.js", diagnosticMessagesJson]); } -}); -gulp.task(`clean:${diagnosticInformationMapTs}`, /*help*/ false, () => del([diagnosticInformationMapTs, diagnosticMessagesGeneratedJson])); +}; +task("generate-diagnostics", series(buildScripts, generateDiagnostics)); +task("generate-diagnostics").description = "Generates a diagnostic file in TypeScript based on an input JSON file"; -const builtGeneratedDiagnosticMessagesJson = "built/local/diagnosticMessages.generated.json"; -gulp.task(builtGeneratedDiagnosticMessagesJson, /*help*/ false, [diagnosticInformationMapTs], () => - gulp.src([diagnosticMessagesGeneratedJson], { base: "src/compiler" }) - .pipe(newer(builtGeneratedDiagnosticMessagesJson)) - .pipe(gulp.dest("built/local"))); +const cleanDiagnostics = () => del([diagnosticInformationMapTs, diagnosticMessagesGeneratedJson]); +cleanTasks.push(cleanDiagnostics); -gulp.task( - "generate-diagnostics", - "Generates a diagnostic file in TypeScript based on an input JSON file", - [diagnosticInformationMapTs]); +const watchDiagnostics = () => watch(["src/compiler/diagnosticMessages.json"], task("generate-diagnostics")); // Localize diagnostics /** @@ -122,183 +85,508 @@ const localizationTargets = ["cs", "de", "es", "fr", "it", "ja", "ko", "pl", "pt .map(f => `built/local/${f}/diagnosticMessages.generated.json`) .concat(generatedLCGFile); -gulp.task(generatedLCGFile, /*help*/ false, [generateLocalizedDiagnosticMessagesJs, diagnosticInformationMapTs], (done) => { +const localize = async () => { if (needsUpdate(diagnosticMessagesGeneratedJson, generatedLCGFile)) { - return exec(host, [generateLocalizedDiagnosticMessagesJs, "src/loc/lcl", "built/local", diagnosticMessagesGeneratedJson], { ignoreExitCode: true }); + return exec(process.execPath, ["scripts/generateLocalizedDiagnosticMessages.js", "src/loc/lcl", "built/local", diagnosticMessagesGeneratedJson], { ignoreExitCode: true }); } -}); +}; + +// Pre-build steps when targeting the LKG compiler +const lkgPreBuild = parallel(generateLibs, series(buildScripts, generateDiagnostics)); + +const buildTsc = () => buildProject("src/tsc"); +task("tsc", series(lkgPreBuild, buildTsc)); +task("tsc").description = "Builds the command-line compiler"; + +const cleanTsc = () => cleanProject("src/tsc"); +cleanTasks.push(cleanTsc); +task("clean-tsc", cleanTsc); +task("clean-tsc").description = "Cleans outputs for the command-line compiler"; + +const watchTsc = () => watchProject("src/tsc"); +task("watch-tsc", series(lkgPreBuild, parallel(watchLib, watchDiagnostics, watchTsc))); +task("watch-tsc").description = "Watch for changes and rebuild the command-line compiler only."; + +// Pre-build steps when targeting the built/local compiler. +const localPreBuild = parallel(generateLibs, series(buildScripts, generateDiagnostics, buildTsc)); -gulp.task("localize", /*help*/ false, [generatedLCGFile]); +// Pre-build steps to use based on supplied options. +const preBuild = cmdLineOptions.lkg ? lkgPreBuild : localPreBuild; -const servicesProject = "src/services/tsconfig.json"; -const typescriptServicesProject = "built/local/typescriptServices.tsconfig.json"; -gulp.task(typescriptServicesProject, /*help*/ false, () => { - // NOTE: flatten services so that we can properly strip @internal - project.flatten(servicesProject, typescriptServicesProject, { +const buildServices = (() => { + // flatten the services project + const flattenServicesConfig = async () => flatten("src/services/tsconfig.json", "built/local/typescriptServices.tsconfig.json", { compilerOptions: { "removeComments": false, "stripInternal": true, - "declaration": true, - "outFile": "typescriptServices.out.js" // must align with same task in jakefile. We fix this name below. + "declarationMap": false, + "outFile": "typescriptServices.out.js" } }); -}); -const typescriptServicesJs = "built/local/typescriptServices.js"; -const typescriptServicesDts = "built/local/typescriptServices.d.ts"; -gulp.task(typescriptServicesJs, /*help*/ false, ["lib", "generate-diagnostics", typescriptServicesProject], () => - project.compile(typescriptServicesProject, { - js: files => files - .pipe(prepend.file(copyright)) - .pipe(rename("typescriptServices.js")), - dts: files => files - .pipe(removeSourceMaps()) - .pipe(prepend.file(copyright)) - .pipe(convertConstEnums()) - .pipe(rename("typescriptServices.d.ts")) - }), - { aliases: [typescriptServicesDts] }); + // build typescriptServices.out.js + const buildTypescriptServicesOut = () => buildProject("built/local/typescriptServices.tsconfig.json", cmdLineOptions); -const typescriptJs = "built/local/typescript.js"; -gulp.task(typescriptJs, /*help*/ false, [typescriptServicesJs], () => - gulp.src([typescriptServicesJs], { base: "built/local" }) - .pipe(newer(typescriptJs)) + // create typescriptServices.js + const createTypescriptServicesJs = () => src("built/local/typescriptServices.out.js") + .pipe(newer("built/local/typescriptServices.js")) + .pipe(sourcemaps.init({ loadMaps: true })) + .pipe(prependFile(copyright)) + .pipe(rename("typescriptServices.js")) + .pipe(sourcemaps.write(".", { includeContent: false, destPath: "built/local" })) + .pipe(dest("built/local")); + + // create typescriptServices.d.ts + const createTypescriptServicesDts = () => src("built/local/typescriptServices.out.d.ts") + .pipe(newer("built/local/typescriptServices.d.ts")) + .pipe(prependFile(copyright)) + .pipe(transform(content => content.replace(/^(\s*)(export )?const enum (\S+) {(\s*)$/gm, "$1$2enum $3 {$4"))) + .pipe(rename("typescriptServices.d.ts")) + .pipe(dest("built/local")); + + // create typescript.js + const createTypescriptJs = () => src("built/local/typescriptServices.js") + .pipe(newer("built/local/typescript.js")) + .pipe(sourcemaps.init({ loadMaps: true })) .pipe(rename("typescript.js")) - .pipe(gulp.dest("built/local"))) + .pipe(sourcemaps.write(".", { includeContent: false, destPath: "built/local" })) + .pipe(dest("built/local")); -const typescriptDts = "built/local/typescript.d.ts"; -gulp.task(typescriptDts, /*help*/ false, [typescriptServicesDts], () => - gulp.src([typescriptServicesDts], { base: "built/local" }) - .pipe(newer(typescriptDts)) + // create typescript.d.ts + const createTypescriptDts = () => src("built/local/typescriptServices.d.ts") + .pipe(newer("built/local/typescript.d.ts")) .pipe(append("\nexport = ts;")) .pipe(rename("typescript.d.ts")) - .pipe(gulp.dest("built/local"))); + .pipe(dest("built/local")); -const typescriptStandaloneDts = "built/local/typescript_standalone.d.ts"; -gulp.task(typescriptStandaloneDts, /*help*/ false, [typescriptServicesDts], () => - gulp.src([typescriptServicesDts], { base: "built/local" }) - .pipe(newer(typescriptStandaloneDts)) - .pipe(replace(/declare (namespace|module) ts/g, 'declare module "typescript"')) + // create typescript_standalone.d.ts + const createTypescriptStandaloneDts = () => src("built/local/typescriptServices.d.ts") + .pipe(newer("built/local/typescript_standalone.d.ts")) + .pipe(transform(content => content.replace(/declare (namespace|module) ts/g, 'declare module "typescript"'))) .pipe(rename("typescript_standalone.d.ts")) - .pipe(gulp.dest("built/local"))); - -// build all 'typescriptServices'-related outputs -gulp.task("services", /*help*/ false, [typescriptServicesJs, typescriptServicesDts, typescriptJs, typescriptDts, typescriptStandaloneDts]); - -const useCompiler = cmdLineOptions.lkg ? "lkg" : "built"; -const useCompilerDeps = cmdLineOptions.lkg ? ["lib", "generate-diagnostics"] : [typescriptServicesJs]; - -const tscProject = "src/tsc/tsconfig.json"; -const tscJs = "built/local/tsc.js"; -gulp.task(tscJs, /*help*/ false, useCompilerDeps, () => - project.compile(tscProject, { - typescript: useCompiler, - js: files => files.pipe(prepend.file(copyright)) - })); - -const tscReleaseProject = "src/tsc/tsconfig.release.json"; -const tscReleaseJs = "built/local/tsc.release.js"; -gulp.task(tscReleaseJs, /*help*/ false, () => - project.compile(tscReleaseProject, { - js: files => files.pipe(prepend.file(copyright)) - })); - -const cancellationTokenProject = "src/cancellationToken/tsconfig.json"; -const cancellationTokenJs = "built/local/cancellationToken.js"; -gulp.task(cancellationTokenJs, /*help*/ false, useCompilerDeps, () => project.compile(cancellationTokenProject, { typescript: useCompiler })); - -const typingsInstallerProject = "src/typingsInstaller/tsconfig.json"; -const typingsInstallerJs = "built/local/typingsInstaller.js"; -gulp.task(typingsInstallerJs, /*help*/ false, useCompilerDeps, () => project.compile(typingsInstallerProject, { typescript: useCompiler })); - -const tsserverProject = "src/tsserver/tsconfig.json"; -const tsserverJs = "built/local/tsserver.js"; -gulp.task(tsserverJs, /*help*/ false, useCompilerDeps, () => project.compile(tsserverProject, { typescript: useCompiler })); - -gulp.task( - "tsserver", - "Builds the language server", - [tsserverJs]); - -const watchGuardProject = "src/watchGuard/tsconfig.json"; -const watchGuardJs = "built/local/watchGuard.js"; -gulp.task(watchGuardJs, /*help*/ false, useCompilerDeps, () => project.compile(watchGuardProject, { typescript: useCompiler })); - -const typesMapJson = "built/local/typesMap.json"; -gulp.task(typesMapJson, /*help*/ false, [], () => - gulp.src("src/server/typesMap.json") - .pipe(newer(typesMapJson)) - .pipe(insert.transform(contents => (JSON.parse(contents), contents))) - .pipe(gulp.dest("built/local"))); - -const tsserverlibraryProject = "built/local/tsserverlibrary.tsconfig.json"; -gulp.task(tsserverlibraryProject, /*help*/ false, () => { - // NOTE: flatten tsserverlibrary so that we can properly strip @internal - project.flatten("src/tsserver/tsconfig.json", tsserverlibraryProject, { + .pipe(dest("built/local")); + + return series( + flattenServicesConfig, + buildTypescriptServicesOut, + createTypescriptServicesJs, + createTypescriptServicesDts, + createTypescriptJs, + createTypescriptDts, + createTypescriptStandaloneDts); +})(); +task("services", series(preBuild, buildServices)); +task("services").description = "Builds the language service"; +task("services").flags = { + " --built": "Compile using the built version of the compiler." +} + +const cleanServices = async () => { + if (fs.existsSync("built/local/typescriptServices.tsconfig.json")) { + await cleanProject("built/local/typescriptServices.tsconfig.json"); + } + await del([ + "built/local/typescriptServices.tsconfig.json", + "built/local/typescriptServices.out.js", + "built/local/typescriptServices.out.d.ts", + "built/local/typescriptServices.js", + "built/local/typescript.js", + "built/local/typescript.d.ts", + "built/local/typescript_standalone.d.ts", + ]); +}; +cleanTasks.push(cleanServices); +task("clean-services", cleanServices); +task("clean-services").description = "Cleans outputs for the language service"; + +const watchServices = () => watch([ + "src/compiler/tsconfig.json", + "src/compiler/**/*.ts", + "src/jsTyping/tsconfig.json", + "src/jsTyping/**/*.ts", + "src/services/tsconfig.json", + "src/services/**/*.ts", +], series(preBuild, buildServices)); +task("watch-services", series(preBuild, parallel(watchLib, watchDiagnostics, watchServices))); +task("watch-services").description = "Watches for changes and rebuild language service only"; +task("watch-services").flags = { + " --built": "Compile using the built version of the compiler." +} + +const buildServer = () => buildProject("src/tsserver", cmdLineOptions); +task("tsserver", series(preBuild, buildServer)); +task("tsserver").description = "Builds the language server"; +task("tsserver").flags = { + " --built": "Compile using the built version of the compiler." +} + +const cleanServer = () => cleanProject("src/tsserver"); +cleanTasks.push(cleanServer); +task("clean-tsserver", cleanServer); +task("clean-tsserver").description = "Cleans outputs for the language server"; + +const watchServer = () => watchProject("src/tsserver", cmdLineOptions); +task("watch-tsserver", series(preBuild, parallel(watchLib, watchDiagnostics, watchServer))); +task("watch-tsserver").description = "Watch for changes and rebuild the language server only"; +task("watch-tsserver").flags = { + " --built": "Compile using the built version of the compiler." +} + +task("min", series(lkgPreBuild, buildTsc, buildServer)); +task("min").description = "Builds only tsc and tsserver"; +task("min").flags = { + " --built": "Compile using the built version of the compiler." +} + +task("clean-min", series(cleanTsc, cleanServer)); +task("clean-min").description = "Cleans outputs for tsc and tsserver"; + +task("watch-min", series(preBuild, parallel(watchLib, watchDiagnostics, watchTsc, watchServer))); +task("watch-min").description = "Watches for changes to a tsc and tsserver only"; +task("watch-min").flags = { + " --built": "Compile using the built version of the compiler." +} + +const buildLssl = (() => { + // flatten the server project + const flattenTsServerProject = async () => flatten("src/tsserver/tsconfig.json", "built/local/tsserverlibrary.tsconfig.json", { exclude: ["src/tsserver/server.ts"], compilerOptions: { "removeComments": false, "stripInternal": true, + "declaration": true, "declarationMap": false, - "outFile": "tsserverlibrary.out.js" // must align with same task in jakefile. We fix this name below. + "outFile": "tsserverlibrary.out.js" } }); -}); -const tsserverlibraryJs = "built/local/tsserverlibrary.js"; -const tsserverlibraryDts = "built/local/tsserverlibrary.d.ts"; -gulp.task(tsserverlibraryJs, /*help*/ false, useCompilerDeps.concat([tsserverlibraryProject]), () => - project.compile(tsserverlibraryProject, { - js: files => files - .pipe(prepend.file(copyright)) - .pipe(rename("tsserverlibrary.js")), - dts: files => files - .pipe(removeSourceMaps()) - .pipe(prepend.file(copyright)) - .pipe(convertConstEnums()) - .pipe(append("\nexport = ts;\nexport as namespace ts;")) - .pipe(rename("tsserverlibrary.d.ts")), - typescript: useCompiler - }), { aliases: [tsserverlibraryDts] }); - -gulp.task( - "lssl", - "Builds language service server library", - [tsserverlibraryDts]); - -gulp.task( - "local", - "Builds the full compiler and services", - [tscJs, "services", tsserverJs, builtGeneratedDiagnosticMessagesJson, tsserverlibraryDts, "localize"]); - -gulp.task( - "tsc", - "Builds only the compiler", - [tscJs]); - -// Generate Markdown spec -const specMd = "doc/spec.md"; -gulp.task(specMd, /*help*/ false, [word2mdJs], () => - exec("cscript", ["//nologo", word2mdJs, path.resolve("doc/TypeScript Language Specification.docx"), path.resolve(specMd)])); - -gulp.task( - "generate-spec", - "Generates a Markdown version of the Language Specification", - [specMd]); - -gulp.task("produce-LKG", /*help*/ false, ["scripts", "local", cancellationTokenJs, typingsInstallerJs, watchGuardJs, tscReleaseJs], () => { + // build tsserverlibrary.out.js + const buildServerLibraryOut = () => buildProject("built/local/tsserverlibrary.tsconfig.json", cmdLineOptions); + + // create tsserverlibrary.js + const createServerLibraryJs = () => src("built/local/tsserverlibrary.out.js") + .pipe(newer("built/local/tsserverlibrary.js")) + .pipe(sourcemaps.init({ loadMaps: true })) + .pipe(prependFile(copyright)) + .pipe(rename("tsserverlibrary.js")) + .pipe(sourcemaps.write(".", { includeContent: false, destPath: "built/local" })) + .pipe(dest("built/local")); + + // create tsserverlibrary.d.ts + const createServerLibraryDts = () => src("built/local/tsserverlibrary.out.d.ts") + .pipe(newer("built/local/tsserverlibrary.d.ts")) + .pipe(prependFile(copyright)) + .pipe(transform(content => content.replace(/^(\s*)(export )?const enum (\S+) {(\s*)$/gm, "$1$2enum $3 {$4"))) + .pipe(append("\nexport = ts;\nexport as namespace ts;")) + .pipe(rename("tsserverlibrary.d.ts")) + .pipe(dest("built/local")); + + return series( + flattenTsServerProject, + buildServerLibraryOut, + createServerLibraryJs, + createServerLibraryDts); +})(); +task("lssl", series(preBuild, buildLssl)); +task("lssl").description = "Builds language service server library"; +task("lssl").flags = { + " --built": "Compile using the built version of the compiler." +} + +const cleanLssl = async () => { + if (fs.existsSync("built/local/tsserverlibrary.tsconfig.json")) { + await cleanProject("built/local/tsserverlibrary.tsconfig.json"); + } + await del([ + "built/local/tsserverlibrary.tsconfig.json", + "built/local/tsserverlibrary.out.js", + "built/local/tsserverlibrary.out.d.ts", + "built/local/tsserverlibrary.js", + "built/local/tsserverlibrary.d.ts", + ]); +}; +cleanTasks.push(cleanLssl); +task("clean-lssl", cleanLssl); +task("clean-lssl").description = "Clean outputs for the language service server library"; + +const watchLssl = () => watch([ + "src/compiler/tsconfig.json", + "src/compiler/**/*.ts", + "src/jsTyping/tsconfig.json", + "src/jsTyping/**/*.ts", + "src/services/tsconfig.json", + "src/services/**/*.ts", + "src/server/tsconfig.json", + "src/server/**/*.ts", + "src/tsserver/tsconfig.json", + "src/tsserver/**/*.ts", +], buildLssl); +task("watch-lssl", series(preBuild, parallel(watchLib, watchDiagnostics, watchLssl))); +task("watch-lssl").description = "Watch for changes and rebuild tsserverlibrary only"; +task("watch-lssl").flags = { + " --built": "Compile using the built version of the compiler." +} + +const buildTests = () => buildProject("src/testRunner"); +task("tests", series(preBuild, parallel(buildLssl, buildTests))); +task("tests").description = "Builds the test infrastructure"; +task("tests").flags = { + " --built": "Compile using the built version of the compiler." +} + +const cleanTests = () => cleanProject("src/testRunner"); +cleanTasks.push(cleanTests); +task("clean-tests", cleanTests); +task("clean-tests").description = "Cleans the outputs for the test infrastructure"; + +const watchTests = () => watchProject("src/testRunner", cmdLineOptions); + +const buildRules = () => buildProject("scripts/tslint"); +task("build-rules", buildRules); +task("build-rules").description = "Compiles tslint rules to js"; + +const cleanRules = () => cleanProject("scripts/tslint"); +cleanTasks.push(cleanRules); +task("clean-rules", cleanRules); +task("clean-rules").description = "Cleans the outputs for the lint rules"; + +const lintFoldStart = async () => { if (fold.isTravis()) console.log(fold.start("lint")); }; +const lintFoldEnd = async () => { if (fold.isTravis()) console.log(fold.end("lint")); }; +const lint = series([ + lintFoldStart, + ...["scripts/tslint/tsconfig.json", "src/tsconfig-base.json"].map(project => { + const lintOne = () => { + const args = ["node_modules/tslint/bin/tslint", "--project", project, "--formatters-dir", "./built/local/tslint/formatters", "--format", "autolinkableStylish"]; + if (cmdLineOptions.fix) args.push("--fix"); + log(`Linting: node ${args.join(" ")}`); + return exec(process.execPath, args); + }; + lintOne.dispayName = `lint(${project})`; + return lintOne; + }), + lintFoldEnd +]); +lint.displayName = "lint"; +task("lint", series(buildRules, lint)); +task("lint").description = "Runs tslint on the compiler sources."; +task("lint").flags = { + " --f[iles]=": "pattern to match files to lint", +}; + +const buildFoldStart = async () => { if (fold.isTravis()) console.log(fold.start("build")); }; +const buildFoldEnd = async () => { if (fold.isTravis()) console.log(fold.end("build")); }; +task("local", series(buildFoldStart, lkgPreBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl), buildFoldEnd)); +task("local").description = "Builds the full compiler and services"; +task("local").flags = { + " --built": "Compile using the built version of the compiler." +} + +task("watch-local", series(preBuild, parallel(watchLib, watchDiagnostics, watchTsc, watchServices, watchServer, watchLssl))); +task("watch-local").description = "Watches for changes to projects in src/ (but does not execute tests)."; +task("watch-local").flags = { + " --built": "Compile using the built version of the compiler." +} + +const generateCodeCoverage = () => exec("istanbul", ["cover", "node_modules/mocha/bin/_mocha", "--", "-R", "min", "-t", "" + cmdLineOptions.testTimeout, "built/local/run.js"]); +task("generate-code-coverage", series(preBuild, buildTests, generateCodeCoverage)); +task("generate-code-coverage").description = "Generates code coverage data via istanbul"; + +const preTest = parallel(buildRules, buildTests, buildServices, buildLssl); +preTest.displayName = "preTest"; + +const postTest = (done) => cmdLineOptions.lint ? lint(done) : done(); + +const runTests = () => runConsoleTests("built/local/run.js", "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ false); +task("runtests", series(preBuild, preTest, runTests, postTest)); +task("runtests").description = "Runs the tests using the built run.js file."; +task("runtests").flags = { + "-t --tests=": "Pattern for tests to run.", + " --failed": "Runs tests listed in '.failed-tests'.", + "-r --reporter=": "The mocha reporter to use.", + "-d --debug": "Runs tests in debug mode (NodeJS 6 and earlier)", + "-i --inspect": "Runs tests in inspector mode (NodeJS 8 and later)", + " --keepFailed": "Keep tests in .failed-tests even if they pass", + " --light": "Run tests in light mode (fewer verifications, but tests run faster)", + " --dirty": "Run tests without first cleaning test output directories", + " --stackTraceLimit=": "Sets the maximum number of stack frames to display. Use 'full' to show all frames.", + " --no-color": "Disables color", + " --no-lint": "Disables lint", + " --timeout=": "Overrides the default test timeout.", + " --built": "Compile using the built version of the compiler.", +} + +const runTestsParallel = () => runConsoleTests("built/local/run.js", "min", /*runInParallel*/ true, /*watchMode*/ false); +task("runtests-parallel", series(preBuild, preTest, runTestsParallel, postTest)); +task("runtests-parallel").description = "Runs all the tests in parallel using the built run.js file."; +task("runtests-parallel").flags = { + " --no-lint": "disables lint.", + " --light": "Run tests in light mode (fewer verifications, but tests run faster).", + " --keepFailed": "Keep tests in .failed-tests even if they pass.", + " --dirty": "Run tests without first cleaning test output directories.", + " --stackTraceLimit=": "Sets the maximum number of stack frames to display. Use 'full' to show all frames.", + " --workers=": "The number of parallel workers to use.", + " --timeout=": "Overrides the default test timeout.", + " --built": "Compile using the built version of the compiler.", +}; + +const buildWebTestServer = () => buildProject("tests/webTestServer.tsconfig.json"); +const cleanWebTestServer = () => cleanProject("tests/webTestServer.tsconfig.json"); +cleanTasks.push(cleanWebTestServer); + +const browserifyTests = () => src(["built/local/run.js"], { base: "built/local" }) + .pipe(newer("built/local/bundle.js")) + .pipe(sourcemaps.init({ loadMaps: true })) + .pipe(browserify()) + .pipe(rename("bundle.js")) + .pipe(sourcemaps.write(".", /**@type {*}*/({ includeContent: false, destPath: "built/local" }))) + .pipe(dest("built/local")); + +const runtestsBrowser = async () => { + await cleanTestDirs(); + const { tests, runners, light } = cmdLineOptions; + const testConfigFile = "test.config"; + await del([testConfigFile]); + if (tests || runners || light) { + writeTestConfigFile(tests, runners, light); + } + const args = ["tests/webTestServer.js"]; + if (cmdLineOptions.browser) { + args.push(cmdLineOptions.browser); + } + if (tests) { + args.push(JSON.stringify(tests)); + } + await exec(process.execPath, args); +}; + +task("runtests-browser", series(preBuild, parallel(buildTests, buildServices, buildLssl, buildWebTestServer), browserifyTests, runtestsBrowser)); +task("runtests-browser").description = "Runs the tests using the built run.js file like 'gulp runtests'."; +task("runtests-browser").flags = { + "-t --tests=": "pattern for tests to run", + "-b --browser=": "Either 'IE' or 'chrome'", + " --built": "Compile using the built version of the compiler.", +}; + +task("diff", () => exec(getDiffTool(), [refBaseline, localBaseline], { ignoreExitCode: true })); +task("diff").description = "Diffs the compiler baselines using the diff tool specified by the 'DIFF' environment variable"; + +task("diff-rwc", () => exec(getDiffTool(), [refRwcBaseline, localRwcBaseline], { ignoreExitCode: true })); +task("diff-rwc").description = "Diffs the RWC baselines using the diff tool specified by the 'DIFF' environment variable"; + +const baselineAccept = subfolder => merge2( + src([`${localBaseline}${subfolder ? `${subfolder}/` : ``}**`, `!${localBaseline}${subfolder}/**/*.delete`], { base: localBaseline }) + .pipe(dest(refBaseline)), + src([`${localBaseline}${subfolder ? `${subfolder}/` : ``}**/*.delete`], { base: localBaseline, read: false }) + .pipe(rm()) + .pipe(rename({ extname: "" })) + .pipe(rm(refBaseline))); +task("baseline-accept", () => baselineAccept("")); +task("baseline-accept").description = "Makes the most recent test results the new baseline, overwriting the old baseline"; + +task("baseline-accept-rwc", () => baselineAccept("rwc")); +task("baseline-accept-rwc").description = "Makes the most recent rwc test results the new baseline, overwriting the old baseline"; + +task("baseline-accept-test262", () => baselineAccept("test262")); +task("baseline-accept-test262").description = "Makes the most recent test262 test results the new baseline, overwriting the old baseline"; + +// TODO(rbuckton): Determine if 'webhost' is still in use. +const buildWebHost = () => buildProject("tests/webhost/webtsc.tsconfig.json"); +task("webhost", series(lkgPreBuild, buildWebHost)); +task("webhost").description = "Builds the tsc web host"; + +const cleanWebHost = () => cleanProject("tests/webhost/webtsc.tsconfig.json"); +cleanTasks.push(cleanWebHost); +task("clean-webhost", cleanWebHost); +task("clean-webhost").description = "Cleans the outputs of the tsc web host"; + +// TODO(rbuckton): Determine if 'perftsc' is still in use. +const buildPerfTsc = () => buildProject("tests/perftsc.tsconfig.json"); +task("perftsc", series(lkgPreBuild, buildPerfTsc)); +task("perftsc").description = "Builds augmented version of the compiler for perf tests"; + +const cleanPerfTsc = () => cleanProject("tests/perftsc.tsconfig.json"); +cleanTasks.push(cleanPerfTsc); +task("clean-perftsc", cleanPerfTsc); +task("clean-perftsc").description = "Cleans the outputs of the perftsc project"; + +const buildLoggedIO = async () => { + mkdirp.sync("built/local/temp"); + await exec(process.execPath, ["lib/tsc", "--types", "--target", "es5", "--lib", "es5", "--outdir", "built/local/temp", "src/harness/loggedIO.ts"]); + fs.renameSync("built/local/temp/harness/loggedIO.js", "built/local/loggedIO.js"); + await del("built/local/temp"); +}; + +const cleanLoggedIO = () => del("built/local/temp/loggedIO.js"); +cleanTasks.push(cleanLoggedIO); + +const buildInstrumenter = () => buildProject("src/instrumenter"); +const cleanInstrumenter = () => cleanProject("src/instrumenter"); +cleanTasks.push(cleanInstrumenter); + +const tscInstrumented = () => exec(process.execPath, ["built/local/instrumenter.js", "record", cmdLineOptions.tests || "iocapture", "built/local"]); +task("tsc-instrumented", series(lkgPreBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildLoggedIO, buildInstrumenter), tscInstrumented)); +task("tsc-instrumented").description = "Builds an instrumented tsc.js"; +task("tsc-instrumented").flags = { + "-t --tests=": "The test to run." +} + +// TODO(rbuckton): Determine if we still need this task. Depending on a relative +// path here seems like a bad idea. +const updateSublime = () => src(["built/local/tsserver.js", "built/local/tsserver.js.map"]) + .pipe(dest("../TypeScript-Sublime-Plugin/tsserver/")); +task("update-sublime", updateSublime); +task("update-sublime").description = "Updates the sublime plugin's tsserver"; + +const buildImportDefinitelyTypedTests = () => buildProject("scripts/importDefinitelyTypedTests"); +const cleanImportDefinitelyTypedTests = () => cleanProject("scripts/importDefinitelyTypedTests"); +cleanTasks.push(cleanImportDefinitelyTypedTests); + +// TODO(rbuckton): Should the path to DefinitelyTyped be configurable via an environment variable? +const importDefinitelyTypedTests = () => exec(process.execPath, ["scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.js", "./", "../DefinitelyTyped"]); +task("importDefinitelyTypedTests", series(buildImportDefinitelyTypedTests, importDefinitelyTypedTests)); +task("importDefinitelyTypedTests").description = "Runs the importDefinitelyTypedTests script to copy DT's tests to the TS-internal RWC tests"; + +const buildReleaseTsc = () => buildProject("src/tsc/tsconfig.release.json"); +const cleanReleaseTsc = () => cleanProject("src/tsc/tsconfig.release.json"); +cleanTasks.push(cleanReleaseTsc); + +const buildCancellationToken = () => buildProject("src/cancellationToken"); +const cleanCancellationToken = () => cleanProject("src/cancellationToken"); +cleanTasks.push(cleanCancellationToken); + +const buildTypingsInstaller = () => buildProject("src/typingsInstaller"); +const cleanTypingsInstaller = () => cleanProject("src/typingsInstaller"); +cleanTasks.push(cleanTypingsInstaller); + +const buildWatchGuard = () => buildProject("src/watchGuard"); +const cleanWatchGuard = () => cleanProject("src/watchGuard"); +cleanTasks.push(cleanWatchGuard); + +// TODO(rbuckton): This task isn't triggered by any other task. Is it still needed? +const generateTypesMap = () => src("src/server/typesMap.json") + .pipe(newer("built/local/typesMap.json")) + .pipe(transform(contents => (JSON.parse(contents), contents))) // validates typesMap.json is valid JSON + .pipe(dest("built/local")); +task("generate-types-map", generateTypesMap); + +const cleanTypesMap = () => del("built/local/typesMap.json"); +cleanTasks.push(cleanTypesMap); + +const cleanBuilt = () => del("built"); + +const produceLKG = async () => { const expectedFiles = [ - tscReleaseJs, - typescriptServicesJs, - tsserverJs, - typescriptJs, - typescriptDts, - typescriptServicesDts, - tsserverlibraryDts, - tsserverlibraryDts, - typingsInstallerJs, - cancellationTokenJs - ].concat(libraryTargets); + "built/local/tsc.release.js", + "built/local/typescriptServices.js", + "built/local/typescriptServices.d.ts", + "built/local/tsserver.js", + "built/local/typescript.js", + "built/local/typescript.d.ts", + "built/local/tsserverlibrary.js", + "built/local/tsserverlibrary.d.ts", + "built/local/typingsInstaller.js", + "built/local/cancellationToken.js" + ].concat(libs.map(lib => lib.target)); const missingFiles = expectedFiles .concat(localizationTargets) .filter(f => !fs.existsSync(f)); @@ -306,335 +594,66 @@ gulp.task("produce-LKG", /*help*/ false, ["scripts", "local", cancellationTokenJ throw new Error("Cannot replace the LKG unless all built targets are present in directory 'built/local/'. The following files are missing:\n" + missingFiles.join("\n")); } const sizeBefore = getDirSize("lib"); - return exec(host, [produceLKGJs]).then(() => { - const sizeAfter = getDirSize("lib"); - if (sizeAfter > (sizeBefore * 1.10)) { - throw new Error("The lib folder increased by 10% or more. This likely indicates a bug."); - } - }); -}); - -gulp.task( - "LKG", - "Makes a new LKG out of the built js files", - () => runSequence("clean-built", "produce-LKG")); - -// Task to build the tests infrastructure using the built compiler -const testRunnerProject = "src/testRunner/tsconfig.json"; -const runJs = "built/local/run.js"; -gulp.task(runJs, /*help*/ false, useCompilerDeps, () => project.compile(testRunnerProject, { typescript: useCompiler })); - -gulp.task( - "tests", - "Builds the test infrastructure using the built compiler", - [runJs, tsserverlibraryDts]); - -gulp.task( - "runtests-parallel", - "Runs all the tests in parallel using the built run.js file. Optional arguments are: --t[ests]=category1|category2|... --d[ebug]=true.", - ["build-rules", "tests", "services", tsserverlibraryDts], - () => runConsoleTests(runJs, "min", /*runInParallel*/ true, /*watchMode*/ false)); - -gulp.task( - "runtests", - "Runs the tests using the built run.js file. Optional arguments are: --t[ests]=regex --r[eporter]=[list|spec|json|] --d[ebug]=true --color[s]=false --lint=true.", - ["build-rules", "tests", "services", tsserverlibraryDts], - () => runConsoleTests(runJs, "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ false)); - -const webTestServerProject = "tests/webTestServer.tsconfig.json"; -const webTestServerJs = "tests/webTestServer.js"; -gulp.task(webTestServerJs, /*help*/ false, useCompilerDeps, () => project.compile(webTestServerProject, { typescript: useCompiler })); -gulp.task(`clean:${webTestServerJs}`, /*help*/ false, () => project.clean(webTestServerProject)); - -const bundlePath = path.resolve("built/local/bundle.js"); - -gulp.task( - "browserify", - "Runs browserify on run.js to produce a file suitable for running tests in the browser", - [runJs], - () => gulp.src([runJs], { base: "built/local" }) - .pipe(newer(bundlePath)) - .pipe(sourcemaps.init({ loadMaps: true })) - .pipe(browserify()) - .pipe(rename("bundle.js")) - .pipe(sourcemaps.write(".", /**@type {*}*/({ includeContent: false, destPath: "built/local" }))) - .pipe(gulp.dest("built/local"))); - -gulp.task( - "runtests-browser", - "Runs the tests using the built run.js file like 'gulp runtests'. Syntax is gulp runtests-browser. Additional optional parameters --tests=[regex], --browser=[chrome|IE]", - ["browserify", webTestServerJs], - () => cleanTestDirs().then(() => { - const tests = cmdLineOptions.tests; - const runners = cmdLineOptions.runners; - const light = cmdLineOptions.light; - const testConfigFile = "test.config"; - if (fs.existsSync(testConfigFile)) { - fs.unlinkSync(testConfigFile); - } - if (tests || runners || light) { - writeTestConfigFile(tests, runners, light); - } - const args = [webTestServerJs]; - if (cmdLineOptions.browser) { - args.push(cmdLineOptions.browser); - } - if (tests) { - args.push(JSON.stringify(tests)); - } - return exec("node", args); - })); - -gulp.task( - "generate-code-coverage", - "Generates code coverage data via istanbul", - ["tests"], - () => exec("istanbul", ["cover", "node_modules/mocha/bin/_mocha", "--", "-R", "min", "-t", "" + cmdLineOptions.testTimeout, runJs])); - - -gulp.task( - "diff", - "Diffs the compiler baselines using the diff tool specified by the 'DIFF' environment variable", - () => exec(getDiffTool(), [refBaseline, localBaseline], { ignoreExitCode: true })); - -gulp.task( - "diff-rwc", - "Diffs the RWC baselines using the diff tool specified by the 'DIFF' environment variable", - () => exec(getDiffTool(), [refRwcBaseline, localRwcBaseline], { ignoreExitCode: true })); - -gulp.task( - "baseline-accept", - "Makes the most recent test results the new baseline, overwriting the old baseline", - () => baselineAccept()); - -gulp.task( - "baseline-accept-rwc", - "Makes the most recent rwc test results the new baseline, overwriting the old baseline", - () => baselineAccept("rwc")); - -gulp.task( - "baseline-accept-test262", - "Makes the most recent test262 test results the new baseline, overwriting the old baseline", - () => baselineAccept("test262")); - -// Webhost -const webtscProject = "tests/webhost/webtsc.tsconfig.json"; -const webtscJs = "tests/webhost/webtsc.js"; -gulp.task(webtscJs, /*help*/ false, useCompilerDeps, () => project.compile(webtscProject, { typescript: useCompiler })); -gulp.task(`clean:${webtscJs}`, /*help*/ false, () => project.clean(webtscProject)); - -gulp.task("webhost", "Builds the tsc web host", [webtscJs], () => - gulp.src("built/local/lib.d.ts") - .pipe(gulp.dest("tests/webhost/"))); - -// Perf compiler -const perftscProject = "tests/perftsc.tsconfig.json"; -const perftscJs = "built/local/perftsc.js"; -gulp.task(perftscJs, /*help*/ false, useCompilerDeps, () => project.compile(perftscProject, { typescript: useCompiler })); -gulp.task(`clean:${perftscJs}`, /*help*/ false, () => project.clean(perftscProject)); - -gulp.task( - "perftsc", - "Builds augmented version of the compiler for perf tests", - [perftscJs]); - -// Instrumented compiler -const loggedIOTs = "src/harness/loggedIO.ts"; -const loggedIOJs = "built/local/loggedIO.js"; -gulp.task(loggedIOJs, /*help*/ false, [], (done) => { - return mkdirp("built/local/temp") - .then(() => exec(host, ["lib/tsc.js", "--types", "--target es5", "--lib es5", "--outdir", "built/local/temp", loggedIOTs])) - .then(() => { fs.renameSync(path.join("built/local/temp", "/harness/loggedIO.js"), loggedIOJs); }) - .then(() => del("built/local/temp")); + await exec(process.execPath, ["scripts/produceLKG.js"]); + const sizeAfter = getDirSize("lib"); + if (sizeAfter > (sizeBefore * 1.10)) { + throw new Error("The lib folder increased by 10% or more. This likely indicates a bug."); + } +}; + +task("LKG", series(lkgPreBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildCancellationToken, buildTypingsInstaller, buildWatchGuard, buildReleaseTsc), produceLKG)); +task("LKG").description = "Makes a new LKG out of the built js files"; +task("LKG").flags = { + " --built": "Compile using the built version of the compiler.", +} + +const generateSpec = () => exec("cscript", ["//nologo", "scripts/word2md.js", path.resolve("doc/TypeScript Language Specification.docx"), path.resolve("doc/spec.md")]); +task("generate-spec", series(buildScripts, generateSpec)); +task("generate-spec").description = "Generates a Markdown version of the Language Specification"; + +task("clean", series(parallel(cleanTasks), cleanBuilt)); +task("clean").description = "Cleans build outputs"; + +const configureNightly = () => exec(process.execPath, ["scripts/configurePrerelease.js", "dev", "package.json", "src/compiler/core.ts"]) +task("configure-nightly", series(buildScripts, configureNightly)); +task("configure-nightly").description = "Runs scripts/configurePrerelease.ts to prepare a build for nightly publishing"; + +const publishNightly = () => exec("npm", ["publish", "--tag", "next"]); +task("publish-nightly", series(task("clean"), task("LKG"), task("clean"), task("runtests-parallel"), publishNightly)); +task("publish-nightly").description = "Runs `npm publish --tag next` to create a new nightly build on npm"; + +// TODO(rbuckton): The problem with watching in this way is that a change in compiler/ will result +// in cascading changes in other projects that may take differing amounts of times to complete. As +// a result, the watch may accidentally trigger early, so we have to set a significant delay. An +// alternative approach would be to leverage a builder API, or to have 'tsc -b' have an option to +// write some kind of trigger file that indicates build completion that we could listen for instead. +const watchRuntests = () => watch(["built/local/*.js", "tests/cases/**/*.ts", "tests/cases/**/tsconfig.json"], { delay: 5000 }, async () => { + if (cmdLineOptions.tests || cmdLineOptions.failed) { + await runConsoleTests("built/local/run.js", "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ true); + } + else { + await runConsoleTests("built/local/run.js", "min", /*runInParallel*/ true, /*watchMode*/ true); + } }); - -const instrumenterProject = "src/instrumenter/tsconfig.json"; -const instrumenterJs = "built/local/instrumenter.js"; -gulp.task(instrumenterJs, /*help*/ false, () => project.compile(instrumenterProject)); -gulp.task(`clean:${instrumenterJs}`, /*help*/ false, () => project.clean(instrumenterProject)); - -gulp.task( - "tsc-instrumented", - "Builds an instrumented tsc.js - run with --test=[testname]", - ["local", loggedIOJs, instrumenterJs, typescriptServicesJs], - () => exec(host, [instrumenterJs, "record", cmdLineOptions.tests || "iocapture", "built/local"])); - -gulp.task( - "update-sublime", - "Updates the sublime plugin's tsserver", - ["local", tsserverJs], - () => - gulp.src([tsserverJs, tsserverJs + ".map"]) - .pipe(gulp.dest("../TypeScript-Sublime-Plugin/tsserver/"))); - -gulp.task( - "build-rules", - "Compiles tslint rules to js", - () => project.compile("scripts/tslint/tsconfig.json")); - -gulp.task("clean-rules", /*help*/ false, () => project.clean("scripts/tslint/tsconfig.json")); - -gulp.task( - "lint", - "Runs tslint on the compiler sources. Optional arguments are: --f[iles]=regex", - ["build-rules"], - () => { - if (fold.isTravis()) console.log(fold.start("lint")); - for (const project of ["scripts/tslint/tsconfig.json", "src/tsconfig-base.json"]) { - const cmd = `node node_modules/tslint/bin/tslint --project ${project} --formatters-dir ./built/local/tslint/formatters --format autolinkableStylish${cmdLineOptions.fix ? " --fix" : ""}`; - log("Linting: " + cmd); - child_process.execSync(cmd, { stdio: [0, 1, 2] }); - } - if (fold.isTravis()) console.log(fold.end("lint")); - }); - -gulp.task( - "default", - "Runs 'local'", - ["local"]); - -gulp.task( - "watch-lib", - /*help*/ false, - () => gulp.watch(["src/lib/**/*"], ["lib"])); - -const watchTscPatterns = [ - "src/tsconfig-base.json", - "src/lib/**/*", - "src/compiler/**/*", - "src/tsc/**/*", -]; -gulp.task( - "watch-tsc", - /*help*/ false, - useCompilerDeps, - () => gulp.watch(watchTscPatterns, ["tsc"])); - -const watchServicesPatterns = [ - "src/compiler/**/*", - "src/jsTypings/**/*", - "src/services/**/*" -]; -gulp.task( - "watch-services", - /*help*/ false, - ["watch-diagnostics", "watch-lib"], - () => gulp.watch(watchServicesPatterns, ["services"])); - -const watchLsslPatterns = [ - ...watchServicesPatterns, - "src/server/**/*", - "src/tsserver/tsconfig.json" -]; -gulp.task( - "watch-lssl", - /*help*/ false, - () => gulp.watch(watchLsslPatterns, ["lssl"])); - -const watchLocalPatterns = [ - "src/tsconfig-base.json", - "src/lib/**/*", - "src/compiler/**/*", - "src/tsc/**/*", - "src/services/**/*", - "src/jsTyping/**/*", - "src/server/**/*", - "src/tsserver/**/*", - "src/typingsInstallerCore/**/*", - "src/harness/**/*", - "src/testRunner/**/*", -]; -gulp.task( - "watch-local", - "Watches for changes to projects in src/ (but does not execute tests).", - () => gulp.watch(watchLocalPatterns, ["local"])); - -const watchPatterns = [ - "src/tsconfig-base.json", - "src/lib/**/*", - "src/compiler/**/*", - "src/services/**/*", - "src/jsTyping/**/*", - "src/server/**/*", - "src/tsserver/**/*", - "src/typingsInstallerCore/**/*", - "src/harness/**/*", - "src/testRunner/**/*", -]; -gulp.task( - "watch", - "Watches for changes to the build inputs for built/local/run.js, then runs tests.", - ["build-rules"], - () => { - const sem = new Semaphore(1); - - gulp.watch(watchPatterns, () => { runTests(); }); - - // NOTE: gulp.watch is far too slow when watching tests/cases/**/* as it first enumerates *every* file - const testFilePattern = /(\.ts|[\\/]tsconfig\.json)$/; - fs.watch("tests/cases", { recursive: true }, (_, file) => { - if (testFilePattern.test(file)) runTests(); - }); - - async function runTests() { - try { - // Ensure only one instance of the test runner is running at any given time. - if (sem.count > 0) { - await sem.wait(); - try { - // Wait for any concurrent recompilations to complete... - try { - await delay(100); - while (project.hasRemainingWork()) { - await project.waitForWorkToComplete(); - await delay(500); - } - } - catch (e) { - if (e instanceof CancelError) return; - throw e; - } - - // cancel any pending or active test run if a new recompilation is triggered - const source = new CancellationTokenSource(); - project.waitForWorkToStart().then(() => { - source.cancel(); - }); - - if (cmdLineOptions.tests || cmdLineOptions.failed) { - await runConsoleTests(runJs, "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ true, source.token); - } - else { - await runConsoleTests(runJs, "min", /*runInParallel*/ true, /*watchMode*/ true, source.token); - } - } - finally { - sem.release(); - } - } - } - catch (e) { - if (e instanceof CancelError) { - log.warn("Operation was canceled"); - } - else { - log.error(e); - } - } - }; - }); - -gulp.task("clean-built", /*help*/ false, [`clean:${diagnosticInformationMapTs}`], () => del(["built"])); -gulp.task( - "clean", - "Cleans the compiler output, declare files, and tests", - [ - `clean:${importDefinitelyTypedTestsJs}`, - `clean:${webtscJs}`, - `clean:${perftscJs}`, - `clean:${instrumenterJs}`, - `clean:${webTestServerJs}`, - "clean:scripts", - "clean-rules", - "clean-built" - ]); +task("watch", series(preBuild, preTest, parallel(watchLib, watchDiagnostics, watchServices, watchLssl, watchTests, watchRuntests))); +task("watch").description = "Watches for changes and rebuilds and runs tests in parallel."; +task("watch").flags = { + "-t --tests=": "Pattern for tests to run. Forces tests to be run in a single worker.", + " --failed": "Runs tests listed in '.failed-tests'. Forces tests to be run in a single worker.", + "-r --reporter=": "The mocha reporter to use.", + " --keepFailed": "Keep tests in .failed-tests even if they pass", + " --light": "Run tests in light mode (fewer verifications, but tests run faster)", + " --dirty": "Run tests without first cleaning test output directories", + " --stackTraceLimit=": "Sets the maximum number of stack frames to display. Use 'full' to show all frames.", + " --no-color": "Disables color", + " --no-lint": "Disables lint", + " --timeout=": "Overrides the default test timeout.", + " --workers=": "The number of parallel workers to use.", + " --built": "Compile using the built version of the compiler.", +}; + +task("default", series("local")); +task("default").description = "Runs 'local'"; + +task("help", () => exec("gulp", ["--tasks", "--depth", "1", "--sort-tasks"], { hidePrompt: true })); +task("help").description = "Prints the top-level tasks."; diff --git a/Jakefile.js b/Jakefile.js index 1a38e4d09e2e7..ea81881e7d9c2 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -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; diff --git a/package.json b/package.json index d8c514363e532..351d1b3af6b4f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/build/baselineAccept.js b/scripts/build/baselineAccept.js deleted file mode 100644 index df61b87cd3267..0000000000000 --- a/scripts/build/baselineAccept.js +++ /dev/null @@ -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)); -} diff --git a/scripts/build/browserify.js b/scripts/build/browserify.js index 1fa5bbdeaf368..ba8ccd158243d 100644 --- a/scripts/build/browserify.js +++ b/scripts/build/browserify.js @@ -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) { } } }); -} \ No newline at end of file +} +exports.browserify = browserifyFile; \ No newline at end of file diff --git a/scripts/build/chalk.js b/scripts/build/chalk.js deleted file mode 100644 index 149c8ea1533c6..0000000000000 --- a/scripts/build/chalk.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/scripts/build/convertConstEnum.js b/scripts/build/convertConstEnum.js deleted file mode 100644 index 00d48fdcc3ff0..0000000000000 --- a/scripts/build/convertConstEnum.js +++ /dev/null @@ -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); -} \ No newline at end of file diff --git a/scripts/build/debounce.js b/scripts/build/debounce.js deleted file mode 100644 index 7020cb61bbd16..0000000000000 --- a/scripts/build/debounce.js +++ /dev/null @@ -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(); - } -} \ No newline at end of file diff --git a/scripts/build/diagnostics.js b/scripts/build/diagnostics.js deleted file mode 100644 index 5ca51c572ea5b..0000000000000 --- a/scripts/build/diagnostics.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/scripts/build/exec.js b/scripts/build/exec.js deleted file mode 100644 index 8e0a058fed036..0000000000000 --- a/scripts/build/exec.js +++ /dev/null @@ -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; -} diff --git a/scripts/build/finished.js b/scripts/build/finished.js deleted file mode 100644 index e642c06419325..0000000000000 --- a/scripts/build/finished.js +++ /dev/null @@ -1,46 +0,0 @@ -// @ts-check -module.exports = finished; - -/** - * @param {NodeJS.ReadableStream | NodeJS.WritableStream} stream - * @returns {Promise} - */ -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(); - }); -} \ No newline at end of file diff --git a/scripts/build/getDiffTool.js b/scripts/build/getDiffTool.js deleted file mode 100644 index 13a30b8933754..0000000000000 --- a/scripts/build/getDiffTool.js +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/scripts/build/getDirSize.js b/scripts/build/getDirSize.js deleted file mode 100644 index 58a65907e4d41..0000000000000 --- a/scripts/build/getDirSize.js +++ /dev/null @@ -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; diff --git a/scripts/build/gulp-typescript-oop/index.js b/scripts/build/gulp-typescript-oop/index.js deleted file mode 100644 index ed7a7d64a0337..0000000000000 --- a/scripts/build/gulp-typescript-oop/index.js +++ /dev/null @@ -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} */ - const inputs = new Map(); - /** @type {Map} */ - 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; \ No newline at end of file diff --git a/scripts/build/gulp-typescript-oop/protocol.js b/scripts/build/gulp-typescript-oop/protocol.js deleted file mode 100644 index 714b94f22a16c..0000000000000 --- a/scripts/build/gulp-typescript-oop/protocol.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/scripts/build/gulp-typescript-oop/worker.js b/scripts/build/gulp-typescript-oop/worker.js deleted file mode 100644 index 5d68bc591e85f..0000000000000 --- a/scripts/build/gulp-typescript-oop/worker.js +++ /dev/null @@ -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)); - } -}); \ No newline at end of file diff --git a/scripts/build/gulp.js b/scripts/build/gulp.js deleted file mode 100644 index 40f27b9526eb1..0000000000000 --- a/scripts/build/gulp.js +++ /dev/null @@ -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")); \ No newline at end of file diff --git a/scripts/build/lib.js b/scripts/build/lib.js deleted file mode 100644 index f8dac7529e053..0000000000000 --- a/scripts/build/lib.js +++ /dev/null @@ -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, sources?: Record }} */ -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; \ No newline at end of file diff --git a/scripts/build/mkdirp.js b/scripts/build/mkdirp.js deleted file mode 100644 index 8a918a387d4ce..0000000000000 --- a/scripts/build/mkdirp.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/scripts/build/needsUpdate.js b/scripts/build/needsUpdate.js deleted file mode 100644 index 436003eab8756..0000000000000 --- a/scripts/build/needsUpdate.js +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/scripts/build/options.js b/scripts/build/options.js index ba1b669188dc5..b7233ba70b0f1 100644 --- a/scripts/build/options.js +++ b/scripts/build/options.js @@ -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 diff --git a/scripts/build/prepend.js b/scripts/build/prepend.js index 6e7b794e79bea..51c8aa84a8ab5 100644 --- a/scripts/build/prepend.js +++ b/scripts/build/prepend.js @@ -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; \ No newline at end of file +exports.prependFile = prependFile; \ No newline at end of file diff --git a/scripts/build/project.js b/scripts/build/project.js deleted file mode 100644 index fb193907bbe11..0000000000000 --- a/scripts/build/project.js +++ /dev/null @@ -1,1067 +0,0 @@ -// @ts-check -const path = require("path"); -const fs = require("fs"); -const gulp = require("./gulp"); -const gulpif = require("gulp-if"); -const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util) -const chalk = require("./chalk"); -const sourcemaps = require("gulp-sourcemaps"); -const merge2 = require("merge2"); -const tsc = require("gulp-typescript"); -const tsc_oop = require("./gulp-typescript-oop"); -const upToDate = require("./upToDate"); -const ts = require("../../lib/typescript"); -const del = require("del"); -const needsUpdate = require("./needsUpdate"); -const mkdirp = require("./mkdirp"); -const { reportDiagnostics } = require("./diagnostics"); -const { CountdownEvent, ManualResetEvent, Semaphore } = require("prex"); - -const workStartedEvent = new ManualResetEvent(); -const countdown = new CountdownEvent(0); - -// internal `Gulp` instance for compilation artifacts. -const compilationGulp = new gulp.Gulp(); - -/** @type {Map} */ -const projectGraphCache = new Map(); - -/** @type {Map} */ -const typescriptAliasMap = new Map(); - -// TODO: allow concurrent outer builds to be run in parallel -const sem = new Semaphore(1); - -/** - * @param {string|string[]} taskName - * @param {() => any} [cb] - */ -function start(taskName, cb) { - return sem.wait().then(() => new Promise((resolve, reject) => { - compilationGulp.start(taskName, err => { - if (err) { - reject(err); - } - else if (cb) { - try { - resolve(cb()); - } - catch (e) { - reject(err); - } - } - else { - resolve(); - } - }); - })).then(() => { - sem.release() - }, e => { - sem.release(); - throw e; - }); -} - -/** - * Defines a gulp orchestration for a TypeScript project, returning a callback that can be used to trigger compilation. - * @param {string} projectSpec The path to a tsconfig.json file or its containing directory. - * @param {CompileOptions} [options] Project compilation options. - * @returns {() => Promise} - */ -function createCompiler(projectSpec, options) { - const resolvedOptions = resolveCompileOptions(options); - const resolvedProjectSpec = resolveProjectSpec(projectSpec, resolvedOptions.paths, /*referrer*/ undefined); - const projectGraph = getOrCreateProjectGraph(resolvedProjectSpec, resolvedOptions.paths); - projectGraph.isRoot = true; - const taskName = compileTaskName(ensureCompileTask(projectGraph, resolvedOptions), resolvedOptions.typescript); - return () => start(taskName); -} -exports.createCompiler = createCompiler; - -/** - * Defines and executes a gulp orchestration for a TypeScript project. - * @param {string} projectSpec The path to a tsconfig.json file or its containing directory. - * @param {CompileOptions} [options] Project compilation options. - * @returns {Promise} - * - * @typedef CompileOptions - * @property {string} [cwd] The path to use for the current working directory. Defaults to `process.cwd()`. - * @property {string} [base] The path to use as the base for relative paths. Defaults to `cwd`. - * @property {string} [typescript] A module specifier or path (relative to gulpfile.js) to the version of TypeScript to use. - * @property {Hook} [js] Pipeline hook for .js file outputs. - * @property {Hook} [dts] Pipeline hook for .d.ts file outputs. - * @property {boolean} [verbose] Indicates whether verbose logging is enabled. - * @property {boolean} [force] Force recompilation (no up-to-date check). - * @property {boolean} [inProcess] Indicates whether to run gulp-typescript in-process or out-of-process (default). - * - * @typedef {(stream: NodeJS.ReadableStream) => NodeJS.ReadWriteStream} Hook - */ -function compile(projectSpec, options) { - const compiler = createCompiler(projectSpec, options); - return compiler(); -} -exports.compile = compile; - -/** - * Defines a gulp orchestration to clean the outputs of a TypeScript project, returning a callback that can be used to trigger compilation. - * @param {string} projectSpec The path to a tsconfig.json file or its containing directory. - * @param {PathOptions} [options] Project clean options. - */ -function createCleaner(projectSpec, options) { - const paths = resolvePathOptions(options); - const resolvedProjectSpec = resolveProjectSpec(projectSpec, paths, /*referrer*/ undefined); - const projectGraph = getOrCreateProjectGraph(resolvedProjectSpec, paths); - projectGraph.isRoot = true; - const taskName = cleanTaskName(ensureCleanTask(projectGraph)); - return () => start(taskName); -} -exports.createCleaner = createCleaner; - -/** - * Defines and executes a gulp orchestration to clean the outputs of a TypeScript project. - * @param {string} projectSpec The path to a tsconfig.json file or its containing directory. - * @param {PathOptions} [options] Project clean options. - */ -function clean(projectSpec, options) { - const cleaner = createCleaner(projectSpec, options); - return cleaner(); -} -exports.clean = clean; - -/** - * Defines a watcher to execute a gulp orchestration to recompile a TypeScript project. - * @param {string} projectSpec - * @param {WatchCallback | string[] | CompileOptions} [options] - * @param {WatchCallback | string[]} [tasks] - * @param {WatchCallback} [callback] - */ -function watch(projectSpec, options, tasks, callback) { - if (typeof tasks === "function") callback = tasks, tasks = /**@type {string[] | undefined}*/(undefined); - if (typeof options === "function") callback = options, tasks = /**@type {string[] | undefined}*/(undefined), options = /**@type {CompileOptions | undefined}*/(undefined); - if (Array.isArray(options)) tasks = options, options = /**@type {CompileOptions | undefined}*/(undefined); - const resolvedOptions = resolveCompileOptions(options); - resolvedOptions.watch = true; - const resolvedProjectSpec = resolveProjectSpec(projectSpec, resolvedOptions.paths, /*referrer*/ undefined); - const projectGraph = getOrCreateProjectGraph(resolvedProjectSpec, resolvedOptions.paths); - projectGraph.isRoot = true; - ensureWatcher(projectGraph, resolvedOptions, tasks, callback); -} -exports.watch = watch; - -/** - * Adds a named alias for a TypeScript language service path - * @param {string} alias An alias for a TypeScript version. - * @param {string} typescript An alias or module specifier for a TypeScript version. - * @param {PathOptions} [options] Options used to resolve the path to `typescript`. - */ -function addTypeScript(alias, typescript, options) { - const paths = resolvePathOptions(options); - typescriptAliasMap.set(alias, { typescript, alias, paths }); -} -exports.addTypeScript = addTypeScript; - -/** - * 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 {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 paths = resolvePathOptions(options); - const files = []; - const resolvedOutputSpec = path.resolve(paths.cwd, flattenedProjectSpec); - const resolvedOutputDirectory = path.dirname(resolvedOutputSpec); - const resolvedProjectSpec = resolveProjectSpec(projectSpec, paths, /*referrer*/ undefined); - const projectGraph = getOrCreateProjectGraph(resolvedProjectSpec, paths); - const skipProjects = /**@type {Set}*/(new Set()); - const skipFiles = new Set(options && options.exclude && options.exclude.map(file => path.resolve(paths.cwd, file))); - recur(projectGraph); - - 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 {ProjectGraph} projectGraph - */ - function recur(projectGraph) { - if (skipProjects.has(projectGraph)) return; - skipProjects.add(projectGraph); - for (const ref of projectGraph.references) { - recur(ref.target); - } - for (let file of projectGraph.project.fileNames) { - file = path.resolve(projectGraph.projectDirectory, file); - if (skipFiles.has(file)) continue; - skipFiles.add(file); - files.push(file); - } - } -} -exports.flatten = flatten; - -/** - * Returns a Promise that resolves when all pending build tasks have completed - * @param {import("prex").CancellationToken} [token] - */ -function waitForWorkToComplete(token) { - return countdown.wait(token); -} -exports.waitForWorkToComplete = waitForWorkToComplete; - -/** - * Returns a Promise that resolves when all pending build tasks have completed - * @param {import("prex").CancellationToken} [token] - */ -function waitForWorkToStart(token) { - return workStartedEvent.wait(token); -} -exports.waitForWorkToStart = waitForWorkToStart; - -function getRemainingWork() { - return countdown.remainingCount > 0; -} -exports.hasRemainingWork = getRemainingWork; - -/** - * Resolve a TypeScript specifier into a fully-qualified module specifier and any requisite dependencies. - * @param {string} typescript An unresolved module specifier to a TypeScript version. - * @param {ResolvedPathOptions} paths Paths used to resolve `typescript`. - * @returns {ResolvedTypeScript} - * - * @typedef {string & {_isResolvedTypeScript: never}} ResolvedTypeScriptSpec - * - * @typedef ResolvedTypeScript - * @property {ResolvedTypeScriptSpec} typescript - * @property {string} [alias] - */ -function resolveTypeScript(typescript = "default", paths) { - let alias; - while (typescriptAliasMap.has(typescript)) { - ({ typescript, alias, paths } = typescriptAliasMap.get(typescript)); - } - - if (typescript === "default") { - typescript = require.resolve("../../lib/typescript"); - } - else if (isPath(typescript)) { - typescript = path.resolve(paths.cwd, typescript); - } - - return { typescript: /**@type {ResolvedTypeScriptSpec}*/(normalizeSlashes(typescript)), alias }; -} - -/** - * Gets a suffix to append to Gulp task names that vary by TypeScript version. - * @param {ResolvedTypeScript} typescript A resolved module specifier to a TypeScript version. - * @param {ResolvedPathOptions} paths Paths used to resolve a relative reference to `typescript`. - */ -function getTaskNameSuffix(typescript, paths) { - return typescript.typescript === resolveTypeScript("default", paths).typescript ? "" : - typescript.alias ? `@${typescript.alias}` : - isPath(typescript.typescript) ? `@${normalizeSlashes(path.relative(paths.base, typescript.typescript))}` : - `@${typescript}`; -} - -/** @type {ResolvedPathOptions} */ -const defaultPaths = (() => { - const cwd = /**@type {AbsolutePath}*/(normalizeSlashes(process.cwd())); - return { cwd, base: cwd }; -})(); - -/** - * @param {PathOptions | undefined} options Path options to resolve and normalize. - * @returns {ResolvedPathOptions} - * - * @typedef PathOptions - * @property {string} [cwd] The path to use for the current working directory. Defaults to `process.cwd()`. - * @property {string} [base] The path to use as the base for relative paths. Defaults to `cwd`. - * - * @typedef ResolvedPathOptions - * @property {AbsolutePath} cwd The path to use for the current working directory. Defaults to `process.cwd()`. - * @property {AbsolutePath} base The path to use as the base for relative paths. Defaults to `cwd`. - */ -function resolvePathOptions(options) { - const cwd = options && options.cwd ? resolvePath(defaultPaths.cwd, options.cwd) : defaultPaths.cwd; - const base = options && options.base ? resolvePath(cwd, options.base) : cwd; - return cwd === defaultPaths.cwd && base === defaultPaths.base ? defaultPaths : { cwd, base }; -} - -/** - * @param {CompileOptions} [options] - * @returns {ResolvedCompileOptions} - * - * @typedef ResolvedCompileOptions - * @property {ResolvedPathOptions} paths - * @property {ResolvedTypeScript} typescript A resolved reference to a TypeScript implementation. - * @property {Hook} [js] Pipeline hook for .js file outputs. - * @property {Hook} [dts] Pipeline hook for .d.ts file outputs. - * @property {boolean} [verbose] Indicates whether verbose logging is enabled. - * @property {boolean} [force] Force recompilation (no up-to-date check). - * @property {boolean} [inProcess] Indicates whether to run gulp-typescript in-process or out-of-process (default). - * @property {boolean} [watch] Indicates the project was created in watch mode - */ -function resolveCompileOptions(options = {}) { - const paths = resolvePathOptions(options); - const typescript = resolveTypeScript(options.typescript, paths); - return { - paths, - typescript, - js: options.js, - dts: options.dts, - verbose: options.verbose || false, - force: options.force || false, - inProcess: options.inProcess || false - }; -} - -/** - * @param {ResolvedCompileOptions} left - * @param {ResolvedCompileOptions} right - * @returns {ResolvedCompileOptions} - */ -function mergeCompileOptions(left, right) { - if (left.typescript.typescript !== right.typescript.typescript) throw new Error("Cannot merge project options targeting different TypeScript packages"); - if (tryReuseCompileOptions(left, right)) return left; - return { - paths: left.paths, - typescript: left.typescript, - js: right.js || left.js, - dts: right.dts || left.dts, - verbose: right.verbose || left.verbose, - force: right.force || left.force, - inProcess: right.inProcess || left.inProcess, - watch: right.watch || left.watch - }; -} - -/** - * @param {ResolvedCompileOptions} left - * @param {ResolvedCompileOptions} right - */ -function tryReuseCompileOptions(left, right) { - return left === right - || left.js === (right.js || left.js) - && left.dts === (right.dts || left.dts) - && !left.verbose === !(right.verbose || left.verbose) - && !left.force === !(right.force || left.force) - && !left.inProcess === !(right.inProcess || left.inProcess); -} - -/** - * @param {ResolvedProjectSpec} projectSpec - * @param {ResolvedPathOptions} paths - * @returns {UnqualifiedProjectName} - * - * @typedef {string & {_isUnqualifiedProjectName:never}} UnqualifiedProjectName - */ -function getUnqualifiedProjectName(projectSpec, paths) { - let projectName = path.relative(paths.base, projectSpec); - if (path.basename(projectName) === "tsconfig.json") projectName = path.dirname(projectName); - return /**@type {UnqualifiedProjectName}*/(normalizeSlashes(projectName)); -} - -/** - * @param {UnqualifiedProjectName} projectName - * @param {ResolvedPathOptions} paths - * @param {ResolvedTypeScript} typescript - * @returns {QualifiedProjectName} - * - * @typedef {string & {_isQualifiedProjectName:never}} QualifiedProjectName - */ -function getQualifiedProjectName(projectName, paths, typescript) { - return /**@type {QualifiedProjectName}*/(projectName + getTaskNameSuffix(typescript, paths)); -} - -/** - * @typedef {import("../../lib/typescript").ParseConfigFileHost} ParseConfigFileHost - * @type {ParseConfigFileHost} - */ -const parseConfigFileHost = { - useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames, - fileExists: fileName => ts.sys.fileExists(fileName), - readFile: fileName => ts.sys.readFile(fileName), - getCurrentDirectory: () => process.cwd(), - readDirectory: (rootDir, extensions, exclude, include, depth) => ts.sys.readDirectory(rootDir, extensions, exclude, include, depth), - onUnRecoverableConfigFileDiagnostic: diagnostic => reportDiagnostics([diagnostic]) -}; - -/** - * @param {AbsolutePath} [cwd] - * @returns {ParseConfigFileHost} - */ -function getParseConfigFileHost(cwd) { - if (!cwd || cwd === defaultPaths.cwd) return parseConfigFileHost; - return { - useCaseSensitiveFileNames: parseConfigFileHost.useCaseSensitiveFileNames, - fileExists: parseConfigFileHost.fileExists, - readFile: parseConfigFileHost.readFile, - getCurrentDirectory: () => cwd, - readDirectory: parseConfigFileHost.readDirectory, - onUnRecoverableConfigFileDiagnostic: diagnostic => reportDiagnostics([diagnostic], { cwd }) - }; -} - -/** - * @param {ResolvedProjectSpec} projectSpec - * @param {ResolvedPathOptions} paths - * @returns {ProjectGraph} - * - * @typedef ProjectGraph - * @property {ResolvedPathOptions} paths - * @property {ResolvedProjectSpec} projectSpec The fully qualified path to the tsconfig.json of the project - * @property {UnqualifiedProjectName} projectName The relative project name, excluding any TypeScript suffix. - * @property {AbsolutePath} projectDirectory The fully qualified path to the project directory. - * @property {ParsedCommandLine} project The parsed tsconfig.json file. - * @property {ProjectGraphReference[]} references An array of project references. - * @property {Set} referrers An array of referring projects. - * @property {Set} inputs A set of compilation inputs. - * @property {Set} outputs A set of compilation outputs. - * @property {Map} configurations TypeScript-specific configurations for the project. - * @property {boolean} cleanTaskCreated A value indicating whether a `clean:` task has been created for this project (not dependent on TypeScript version). - * @property {boolean} watcherCreated A value indicating whether a watcher has been created for this project. - * @property {boolean} isRoot The project graph is a root project reference. - * @property {Set} [allWatchers] Tasks to execute when the compilation has completed after being triggered by a watcher. - * - * @typedef ProjectGraphReference - * @property {ProjectGraph} source The referring project. - * @property {ProjectGraph} target The referenced project. - */ -function getOrCreateProjectGraph(projectSpec, paths) { - let projectGraph = projectGraphCache.get(projectSpec); - if (!projectGraph) { - const project = parseProject(projectSpec, paths); - const projectDirectory = parentDirectory(projectSpec); - projectGraph = { - paths, - projectSpec, - projectName: getUnqualifiedProjectName(projectSpec, paths), - projectDirectory, - project, - references: [], - referrers: new Set(), - inputs: new Set(project.fileNames.map(file => resolvePath(projectDirectory, file))), - outputs: new Set(ts.getAllProjectOutputs(project).map(file => resolvePath(projectDirectory, file))), - configurations: new Map(), - cleanTaskCreated: false, - watcherCreated: false, - isRoot: false - }; - projectGraphCache.set(projectSpec, projectGraph); - if (project.projectReferences) { - for (const projectReference of project.projectReferences) { - const resolvedProjectSpec = resolveProjectSpec(projectReference.path, paths, projectGraph); - const referencedProject = getOrCreateProjectGraph(resolvedProjectSpec, paths); - const reference = { source: projectGraph, target: referencedProject }; - projectGraph.references.push(reference); - referencedProject.referrers.add(projectGraph); - } - } - } - return projectGraph; -} - -/** - * @param {ResolvedPathOptions} paths - */ -function createParseProject(paths) { - /** - * @param {string} configFilePath - */ - function getProject(configFilePath) { - const projectSpec = resolveProjectSpec(configFilePath, paths, /*referrer*/ undefined); - const projectGraph = getOrCreateProjectGraph(projectSpec, defaultPaths); - return projectGraph && projectGraph.project; - } - return getProject; -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ParsedCommandLine} parsedProject - */ -function updateProjectGraph(projectGraph, parsedProject) { - projectGraph.project = parsedProject; - projectGraph.inputs = new Set(projectGraph.project.fileNames.map(file => resolvePath(projectGraph.projectDirectory, file))); - projectGraph.outputs = new Set(ts.getAllProjectOutputs(projectGraph.project).map(file => resolvePath(projectGraph.projectDirectory, file))); - - // Update project references. - const oldReferences = new Set(projectGraph.references.map(ref => ref.target)); - projectGraph.references = []; - if (projectGraph.project.projectReferences) { - for (const projectReference of projectGraph.project.projectReferences) { - const resolvedProjectSpec = resolveProjectSpec(projectReference.path, projectGraph.paths, projectGraph); - const referencedProject = getOrCreateProjectGraph(resolvedProjectSpec, projectGraph.paths); - const reference = { source: projectGraph, target: referencedProject }; - projectGraph.references.push(reference); - referencedProject.referrers.add(projectGraph); - oldReferences.delete(referencedProject); - } - } - - // Remove project references that have been removed from the project - for (const referencedProject of oldReferences) { - referencedProject.referrers.delete(projectGraph); - // If there are no more references to this project and the project was not directly requested, - // remove it from the cache. - if (referencedProject.referrers.size === 0 && !referencedProject.isRoot) { - projectGraphCache.delete(referencedProject.projectSpec); - } - } -} - -/** - * @param {ResolvedProjectSpec} projectSpec - * @param {ResolvedPathOptions} paths - */ -function parseProject(projectSpec, paths) { - return ts.getParsedCommandLineOfConfigFile(projectSpec, {}, getParseConfigFileHost(paths.cwd)); -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedCompileOptions} resolvedOptions - * @returns {ProjectGraphConfiguration} - * - * @typedef ProjectGraphConfiguration - * @property {QualifiedProjectName} projectName - * @property {ResolvedCompileOptions} resolvedOptions - * @property {boolean} compileTaskCreated A value indicating whether a `compile:` task has been created for this project. - * @property {Set} [watchers] Tasks to execute when the compilation has completed after being triggered by a watcher. - */ -function getOrCreateProjectGraphConfiguration(projectGraph, resolvedOptions) { - let projectGraphConfig = projectGraph.configurations.get(resolvedOptions.typescript.typescript); - if (!projectGraphConfig) { - projectGraphConfig = { - projectName: getQualifiedProjectName(projectGraph.projectName, resolvedOptions.paths, resolvedOptions.typescript), - resolvedOptions, - compileTaskCreated: false - }; - projectGraph.configurations.set(resolvedOptions.typescript.typescript, projectGraphConfig); - } - return projectGraphConfig; -} - -/** - * Resolves a series of path steps as a normalized, canonical, and absolute path. - * @param {AbsolutePath} basePath - * @param {...string} paths - * @returns {AbsolutePath} - * - * @typedef {string & {_isResolvedPath:never}} AbsolutePath - */ -function resolvePath(basePath, ...paths) { - return /**@type {AbsolutePath}*/(normalizeSlashes(path.resolve(basePath, ...paths))); -} - -/** - * @param {AbsolutePath} from - * @param {AbsolutePath} to - * @returns {Path} - * - * @typedef {string & {_isRelativePath:never}} RelativePath - * @typedef {RelativePath | AbsolutePath} Path - */ -function relativePath(from, to) { - let relativePath = normalizeSlashes(path.relative(from, to)); - if (!relativePath) relativePath = "."; - if (path.isAbsolute(relativePath)) return /**@type {AbsolutePath}*/(relativePath); - if (relativePath.charAt(0) !== ".") relativePath = "./" + relativePath; - return /**@type {RelativePath}*/(relativePath); -} - -/** - * @param {AbsolutePath} file - * @returns {AbsolutePath} - */ -function parentDirectory(file) { - const dirname = path.dirname(file); - if (!dirname || dirname === file) return file; - return /**@type {AbsolutePath}*/(normalizeSlashes(dirname)); -} - -/** - * @param {string} projectSpec - * @param {ResolvedPathOptions} paths - * @param {ProjectGraph | undefined} referrer - * @returns {ResolvedProjectSpec} - * - * @typedef {AbsolutePath & {_isResolvedProjectSpec: never}} ResolvedProjectSpec - */ -function resolveProjectSpec(projectSpec, paths, referrer) { - let projectPath = resolvePath(paths.cwd, referrer && referrer.projectDirectory || "", projectSpec); - if (!ts.sys.fileExists(projectPath)) projectPath = resolvePath(paths.cwd, projectPath, "tsconfig.json"); - return /**@type {ResolvedProjectSpec}*/(normalizeSlashes(projectPath)); -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedPathOptions} paths - */ -function resolveDestPath(projectGraph, paths) { - /** @type {AbsolutePath} */ - let destPath = projectGraph.projectDirectory; - if (projectGraph.project.options.outDir) { - destPath = resolvePath(paths.cwd, destPath, projectGraph.project.options.outDir); - } - else if (projectGraph.project.options.outFile || projectGraph.project.options.out) { - destPath = parentDirectory(resolvePath(paths.cwd, destPath, projectGraph.project.options.outFile || projectGraph.project.options.out)); - } - return relativePath(paths.base, destPath); -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedCompileOptions} options - */ -function ensureCompileTask(projectGraph, options) { - const projectGraphConfig = getOrCreateProjectGraphConfiguration(projectGraph, options); - projectGraphConfig.resolvedOptions = mergeCompileOptions(projectGraphConfig.resolvedOptions, options); - const hasCompileTask = projectGraphConfig.compileTaskCreated; - projectGraphConfig.compileTaskCreated = true; - const deps = makeProjectReferenceCompileTasks(projectGraph, projectGraphConfig.resolvedOptions.typescript, projectGraphConfig.resolvedOptions.paths, projectGraphConfig.resolvedOptions.watch); - if (!hasCompileTask) { - compilationGulp.task(compileTaskName(projectGraph, projectGraphConfig.resolvedOptions.typescript), deps, () => { - const destPath = resolveDestPath(projectGraph, projectGraphConfig.resolvedOptions.paths); - const { sourceMap, inlineSourceMap, inlineSources = false, sourceRoot, declarationMap } = projectGraph.project.options; - const configFilePath = projectGraph.project.options.configFilePath; - const sourceMapPath = inlineSourceMap ? undefined : "."; - const sourceMapOptions = { includeContent: inlineSources, sourceRoot, destPath }; - const project = projectGraphConfig.resolvedOptions.inProcess - ? tsc.createProject(configFilePath, { typescript: require(projectGraphConfig.resolvedOptions.typescript.typescript) }) - : tsc_oop.createProject(configFilePath, {}, { typescript: projectGraphConfig.resolvedOptions.typescript.typescript }); - const stream = project.src() - .pipe(gulpif(!projectGraphConfig.resolvedOptions.force, upToDate(projectGraph.project, { verbose: projectGraphConfig.resolvedOptions.verbose, parseProject: createParseProject(projectGraphConfig.resolvedOptions.paths) }))) - .pipe(gulpif(sourceMap || inlineSourceMap, sourcemaps.init())) - .pipe(project()); - if (projectGraphConfig.resolvedOptions.watch) { - stream.on("error", error => { - if (error.message === "TypeScript: Compilation failed") { - stream.emit("end"); - stream.js.emit("end"); - stream.dts.emit("end"); - } - }); - } - - const additionalJsOutputs = projectGraphConfig.resolvedOptions.js ? projectGraphConfig.resolvedOptions.js(stream.js) - .pipe(gulpif(sourceMap || inlineSourceMap, sourcemaps.write(sourceMapPath, sourceMapOptions))) : undefined; - const additionalDtsOutputs = projectGraphConfig.resolvedOptions.dts ? projectGraphConfig.resolvedOptions.dts(stream.dts) - .pipe(gulpif(declarationMap, sourcemaps.write(sourceMapPath, sourceMapOptions))) : undefined; - - const js = stream.js - .pipe(gulpif(sourceMap || inlineSourceMap, sourcemaps.write(sourceMapPath, sourceMapOptions))); - const dts = stream.dts - .pipe(gulpif(declarationMap, sourcemaps.write(sourceMapPath, sourceMapOptions))); - return merge2([js, dts, additionalJsOutputs, additionalDtsOutputs].filter(x => !!x)) - .pipe(gulp.dest(destPath)); - }); - } - return projectGraph; -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedTypeScript} typescript - * @param {ResolvedPathOptions} paths - * @param {boolean} watch - */ -function makeProjectReferenceCompileTasks(projectGraph, typescript, paths, watch) { - return projectGraph.references.map(({target}) => compileTaskName(ensureCompileTask(target, { paths, typescript, watch }), typescript)); -} - -/** - * @param {ProjectGraph} projectGraph - */ -function ensureCleanTask(projectGraph) { - if (!projectGraph.cleanTaskCreated) { - const deps = makeProjectReferenceCleanTasks(projectGraph); - compilationGulp.task(cleanTaskName(projectGraph), deps, () => { - let outputs = ts.getAllProjectOutputs(projectGraph.project); - if (!projectGraph.project.options.inlineSourceMap) { - if (projectGraph.project.options.sourceMap) { - outputs = outputs.concat(outputs.filter(file => /\.jsx?$/.test(file)).map(file => file + ".map")); - } - if (projectGraph.project.options.declarationMap) { - outputs = outputs.concat(outputs.filter(file => /\.d.ts$/.test(file)).map(file => file + ".map")); - } - } - return del(outputs); - }); - projectGraph.cleanTaskCreated = true; - } - return projectGraph; -} - -/** - * @param {ProjectGraph} projectGraph - */ -function makeProjectReferenceCleanTasks(projectGraph) { - return projectGraph.references.map(({target}) => cleanTaskName(ensureCleanTask(target))); -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedCompileOptions} options - * @param {string[]} [tasks] - * @param {(err?: any) => void} [callback] - * - * @typedef Watcher - * @property {string[]} [tasks] - * @property {(err?: any) => void} [callback] - * - * @typedef WatcherRegistration - * @property {() => void} end - */ -function ensureWatcher(projectGraph, options, tasks, callback) { - ensureCompileTask(projectGraph, options); - if (!projectGraph.watcherCreated) { - projectGraph.watcherCreated = true; - makeProjectReferenceWatchers(projectGraph, options.typescript, options.paths); - createWatcher(projectGraph, options, () => { - for (const config of projectGraph.configurations.values()) { - const taskName = compileTaskName(projectGraph, config.resolvedOptions.typescript); - const task = compilationGulp.tasks[taskName]; - if (!task) continue; - possiblyTriggerRecompilation(config, task); - } - }); - } - if ((tasks && tasks.length) || callback) { - const projectGraphConfig = getOrCreateProjectGraphConfiguration(projectGraph, options); - if (!projectGraphConfig.watchers) projectGraphConfig.watchers = new Set(); - if (!projectGraph.allWatchers) projectGraph.allWatchers = new Set(); - - /** @type {Watcher} */ - const watcher = { tasks, callback }; - projectGraphConfig.watchers.add(watcher); - projectGraph.allWatchers.add(watcher); - - /** @type {WatcherRegistration} */ - const registration = { - end() { - projectGraphConfig.watchers.delete(watcher); - projectGraph.allWatchers.delete(watcher); - } - }; - return registration; - } -} - -/** - * @param {ProjectGraphConfiguration} config - * @param {import("orchestrator").Task} task - */ -function possiblyTriggerRecompilation(config, task) { - // if any of the task's dependencies are still running, wait until they are complete. - for (const dep of task.dep) { - if (compilationGulp.tasks[dep].running) { - setTimeout(possiblyTriggerRecompilation, 50, config, task); - return; - } - } - - triggerRecompilation(task, config); -} - -/** - * @param {import("orchestrator").Task} task - * @param {ProjectGraphConfiguration} config - */ -function triggerRecompilation(task, config) { - compilationGulp._resetTask(task); - if (config.watchers && config.watchers.size) { - start(task.name, () => { - /** @type {Set} */ - const taskNames = new Set(); - /** @type {((err?: any) => void)[]} */ - const callbacks = []; - for (const { tasks, callback } of config.watchers) { - if (tasks) for (const task of tasks) taskNames.add(task); - if (callback) callbacks.push(callback); - } - if (taskNames.size) { - gulp.start([...taskNames], error => { - for (const callback of callbacks) callback(error); - }); - } - else { - for (const callback of callbacks) callback(); - } - }); - } - else { - start(task.name); - } -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedTypeScript} typescript - * @param {ResolvedPathOptions} paths - */ -function makeProjectReferenceWatchers(projectGraph, typescript, paths) { - for (const { target } of projectGraph.references) { - ensureWatcher(target, { paths, typescript }); - } -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedCompileOptions} options - * @param {() => void} callback - */ -function createWatcher(projectGraph, options, callback) { - let projectRemoved = false; - let patterns = collectWatcherPatterns(projectGraph.projectSpec, projectGraph.project, projectGraph); - let watcher = /**@type {GulpWatcher}*/ (gulp.watch(patterns, { cwd: projectGraph.projectDirectory }, onWatchEvent)); - - /** - * @param {WatchEvent} event - */ - function onWatchEvent(event) { - const file = resolvePath(options.paths.cwd, event.path); - if (file === projectGraph.projectSpec) { - onProjectWatchEvent(event); - } - else { - onInputOrOutputChanged(file); - } - } - - /** - * @param {WatchEvent} event - */ - function onProjectWatchEvent(event) { - if (event.type === "renamed" || event.type === "deleted") { - onProjectRenamedOrDeleted(); - } - else { - onProjectCreatedOrModified(); - } - } - - function onProjectRenamedOrDeleted() { - // stop listening for file changes and wait for the project to be created again - projectRemoved = true; - watcher.end(); - watcher = /**@type {GulpWatcher}*/ (gulp.watch([projectGraph.projectSpec], onWatchEvent)); - } - - function onProjectCreatedOrModified() { - const newParsedProject = parseProject(projectGraph.projectSpec, options.paths); - const newPatterns = collectWatcherPatterns(projectGraph.projectSpec, newParsedProject, projectGraph); - if (projectRemoved || !sameValues(patterns, newPatterns)) { - projectRemoved = false; - watcher.end(); - updateProjectGraph(projectGraph, newParsedProject); - // Ensure we catch up with any added projects - for (const config of projectGraph.configurations.values()) { - if (config.watchers) { - makeProjectReferenceWatchers(projectGraph, config.resolvedOptions.typescript, config.resolvedOptions.paths); - } - } - patterns = newPatterns; - watcher = /**@type {GulpWatcher}*/ (gulp.watch(patterns, onWatchEvent)); - } - onProjectInvalidated(); - } - - function onProjectInvalidated() { - callback(); - } - - /** - * @param {AbsolutePath} file - */ - function onInputOrOutputChanged(file) { - if (projectGraph.inputs.has(file) || - projectGraph.references.some(ref => ref.target.outputs.has(file))) { - onProjectInvalidated(); - } - } -} - -/** - * @param {ResolvedProjectSpec} projectSpec - * @param {ParsedCommandLine} parsedProject - * @param {ProjectGraph} projectGraph - */ -function collectWatcherPatterns(projectSpec, parsedProject, projectGraph) { - const configFileSpecs = parsedProject.configFileSpecs; - - // NOTE: we do not currently handle files from `/// ` tags - const patterns = /**@type {string[]} */([]); - - // Add the project contents. - if (configFileSpecs) { - addIncludeSpecs(patterns, configFileSpecs.validatedIncludeSpecs); - addExcludeSpecs(patterns, configFileSpecs.validatedExcludeSpecs); - addIncludeSpecs(patterns, configFileSpecs.filesSpecs); - } - else { - addWildcardDirectories(patterns, parsedProject.wildcardDirectories); - addIncludeSpecs(patterns, parsedProject.fileNames); - } - - // Add the project itself. - addIncludeSpec(patterns, projectSpec); - - // TODO: Add the project base. - // addExtendsSpec(patterns, project.raw && project.raw.extends); - - // Add project reference outputs. - addProjectReferences(patterns, parsedProject.projectReferences); - - return patterns; - - /** - * @param {string[]} patterns - * @param {string | undefined} includeSpec - */ - function addIncludeSpec(patterns, includeSpec) { - if (!includeSpec) return; - patterns.push(includeSpec); - } - - /** - * @param {string[]} patterns - * @param {ReadonlyArray | undefined} includeSpecs - */ - function addIncludeSpecs(patterns, includeSpecs) { - if (!includeSpecs) return; - for (const includeSpec of includeSpecs) { - addIncludeSpec(patterns, includeSpec); - } - } - - /** - * @param {string[]} patterns - * @param {string | undefined} excludeSpec - */ - function addExcludeSpec(patterns, excludeSpec) { - if (!excludeSpec) return; - patterns.push("!" + excludeSpec); - } - - /** - * @param {string[]} patterns - * @param {ReadonlyArray | undefined} excludeSpecs - */ - function addExcludeSpecs(patterns, excludeSpecs) { - if (!excludeSpecs) return; - for (const excludeSpec of excludeSpecs) { - addExcludeSpec(patterns, excludeSpec); - } - } - - /** - * @param {string[]} patterns - * @param {ts.MapLike | undefined} wildcardDirectories - */ - function addWildcardDirectories(patterns, wildcardDirectories) { - if (!wildcardDirectories) return; - for (const dirname of Object.keys(wildcardDirectories)) { - const flags = wildcardDirectories[dirname]; - patterns.push(path.join(dirname, flags & ts.WatchDirectoryFlags.Recursive ? "**" : "", "*")); - } - } - - // TODO: Add the project base - // /** - // * @param {string[]} patterns - // * @param {string | undefined} base - // */ - // function addExtendsSpec(patterns, base) { - // if (!base) return; - // addIncludeSpec(patterns, base); - // } - - /** - * @param {string[]} patterns - * @param {ReadonlyArray} projectReferences - */ - function addProjectReferences(patterns, projectReferences) { - if (!projectReferences) return; - for (const projectReference of projectReferences) { - const resolvedProjectSpec = resolveProjectSpec(projectReference.path, projectGraph.paths, projectGraph); - const referencedProject = getOrCreateProjectGraph(resolvedProjectSpec, projectGraph.paths); - for (const output of referencedProject.outputs) { - patterns.push(output); - } - } - } -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedTypeScript} typescript - */ -function compileTaskName(projectGraph, typescript) { - return `compile:${projectGraph.configurations.get(typescript.typescript).projectName}`; -} - -/** - * @param {ProjectGraph} projectGraph - */ -function cleanTaskName(projectGraph) { - return `clean:${projectGraph.projectName}`; -} - -/** - * @param {string} file - */ -function normalizeSlashes(file) { - return file.replace(/\\/g, "/"); -} - -/** - * Determines whether a module specifier is a path - * @param {string} moduleSpec - */ -function isPath(moduleSpec) { - return path.isAbsolute(moduleSpec) || /^\.\.?([\\/]|$)/.test(moduleSpec); -} - -/** - * @template T - * @param {ReadonlyArray} left - * @param {ReadonlyArray} right - */ -function sameValues(left, right) { - if (left === right) return true; - if (left.length !== right.length) return false; - for (let i = 0; i < left.length; i++) { - if (left[i] !== right[i]) return false; - } - return true; -} - -/** - * @typedef {import("../../lib/typescript").ParsedCommandLine & { options: CompilerOptions, configFileSpecs?: ConfigFileSpecs }} ParsedCommandLine - * @typedef {import("../../lib/typescript").CompilerOptions & { configFilePath?: string }} CompilerOptions - * @typedef {import("../../lib/typescript").ProjectReference} ProjectReference - * @typedef {import("gulp").WatchEvent} WatchEvent - * @typedef {import("gulp").WatchCallback} WatchCallback - * @typedef {NodeJS.EventEmitter & { end(): void, add(files: string | string[], done?: () => void): void, remove(file: string): void }} GulpWatcher - * - * @typedef ConfigFileSpecs - * @property {ReadonlyArray | undefined} filesSpecs - * @property {ReadonlyArray | undefined} referenceSpecs - * @property {ReadonlyArray | undefined} validatedIncludeSpecs - * @property {ReadonlyArray | undefined} validatedExcludeSpecs - * @property {ts.MapLike} wildcardDirectories - */ -void 0; \ No newline at end of file diff --git a/scripts/build/projects.js b/scripts/build/projects.js new file mode 100644 index 0000000000000..954f37af84eee --- /dev/null +++ b/scripts/build/projects.js @@ -0,0 +1,60 @@ +// @ts-check +const { exec, Debouncer } = require("./utils"); + +class ProjectQueue { + /** + * @param {(projects: string[], lkg: boolean, force: boolean) => Promise} 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 }); diff --git a/scripts/build/readJson.js b/scripts/build/readJson.js deleted file mode 100644 index c9e63419692a8..0000000000000 --- a/scripts/build/readJson.js +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/scripts/build/replace.js b/scripts/build/replace.js deleted file mode 100644 index 1287d082287e5..0000000000000 --- a/scripts/build/replace.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/scripts/build/rm.js b/scripts/build/rm.js deleted file mode 100644 index 0922954b49ddb..0000000000000 --- a/scripts/build/rm.js +++ /dev/null @@ -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, 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; \ No newline at end of file diff --git a/scripts/build/sourcemaps.js b/scripts/build/sourcemaps.js index 66e488287b2b7..2c85b897b09c1 100644 --- a/scripts/build/sourcemaps.js +++ b/scripts/build/sourcemaps.js @@ -1,12 +1,13 @@ // @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 {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] diff --git a/scripts/build/tests.js b/scripts/build/tests.js index 46c31ed8913c7..36a9ea54cb9b5 100644 --- a/scripts/build/tests.js +++ b/scripts/build/tests.js @@ -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; diff --git a/scripts/build/upToDate.js b/scripts/build/upToDate.js deleted file mode 100644 index c3abb5d1013fb..0000000000000 --- a/scripts/build/upToDate.js +++ /dev/null @@ -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} */ - const inputMap = new Map(); - /** @type {Map} */ - 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; \ No newline at end of file diff --git a/scripts/build/utils.js b/scripts/build/utils.js index 06f55d7288ac3..170c36adef6b4 100644 --- a/scripts/build/utils.js +++ b/scripts/build/utils.js @@ -1,7 +1,119 @@ // @ts-check +/// + 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; \ No newline at end of file +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}*/(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, 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} 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; \ No newline at end of file diff --git a/scripts/build/vinyl.d.ts b/scripts/build/vinyl.d.ts deleted file mode 100644 index 1dfb631499194..0000000000000 --- a/scripts/build/vinyl.d.ts +++ /dev/null @@ -1,60 +0,0 @@ -// NOTE: This makes it possible to correctly type vinyl Files under @ts-check. -export = File; - -declare class File { - constructor(options?: File.VinylOptions); - - cwd: string; - base: string; - path: string; - readonly history: ReadonlyArray; - 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 : never; - isStream(): this is T extends NodeJS.ReadableStream ? File : never; - isNull(): this is T extends null ? File : 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 { - cwd?: string; - base?: string; - path?: string; - history?: ReadonlyArray; - 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; - export type BufferFile = File; - export type StreamFile = File; - - 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; - } -} \ No newline at end of file diff --git a/scripts/build/vinyl.js b/scripts/build/vinyl.js deleted file mode 100644 index 6cf68f3cd26ce..0000000000000 --- a/scripts/build/vinyl.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("vinyl"); \ No newline at end of file diff --git a/scripts/types/ambient.d.ts b/scripts/types/ambient.d.ts index 4f9112d236ea9..52ddca6d7c1b9 100644 --- a/scripts/types/ambient.d.ts +++ b/scripts/types/ambient.d.ts @@ -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 { + constructor(options?: File.VinylOptions); + + cwd: string; + base: string; + path: string; + readonly history: ReadonlyArray; + 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 : never; + isStream(): this is T extends NodeJS.ReadableStream ? File : never; + isNull(): this is T extends null ? File : 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 { + cwd?: string; + base?: string; + path?: string; + history?: ReadonlyArray; + 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; + export type BufferFile = File; + export type StreamFile = File; + + 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; + } +} + +declare module "gulp-sourcemaps" { + interface WriteOptions { + destPath?: string; + } +} \ No newline at end of file