UNPKG

turbo-gulp

Version:

Gulp tasks to boost high-quality projects.

503 lines (442 loc) 16.5 kB
/** * This module defines the _lib_ target type used to create libraries for other projects. * * In the following list of tasks, `{target}` represents the name of the target as defined by the `name` property * of the target options. * The _lib_ target provides the following tasks: * * ## {target}:build * * Performs a full build of the library to the build directory, used for development. * This copies the static assets and compiles the scripts. * * The following sub-tasks are available: * - `{target}:build:copy`: Only copy the static assets * - `{target}:build:script`: Only compile the scripts * * For distribution builds, use `{target:dist}` * * ## {target}:watch * * Watch the files and run incremental builds on change. * This useful during development to get build errors reported immediately or accelerate the code/test cycle. * You can combine it with _Nodemon_ to continuously restart your Node process when changing the source. * * ## {target}:dist * * Performs a full build of the library to the dist directory, used for distribution (ie. publication to _npm_). * This build creates a fully autonomous directory with its own `package.json`, source code, license file, etc. * This allows to use a different structure for distribution rather than structure of the repo, the main benefit is * to provide support for deep package imports (`import * as mod from "my-lib/deep/module"`) by placing the build * at the root of the package. * This build also allows you to remap the `package.json`, for example to set the version dynamically. * * The following sub-tasks are available: * - `{target}:dist:publish`: Publish the package to an _npm_ registry (it honors the `registry` option, to publish * to private _npm_ registries such as _Verdaccio_). It uses the authentication token of the current user, this * token is in `~/.npmrc`. For CI, you can use the following command to set the token the registry `npm.example.com`. * (for the official registry, use `//registry.npmjs.org`): * ``` * echo "//npm.example.com/:_authToken=\"${NPM_TOKEN}\"" > ~/.npmrc * ``` * - `{target}:dist:copy-src`: Only copy the source files to the build directory. * - `{target}:dist:package.json`: Copy (and eventually transform) the root `package.json` to the build directory. * * For development builds, use `{target:build}`. * * ## {target}:typedoc * * Generate _Typedoc_ documentation. * * ## {target}:typedoc:deploy * * Deploy the _Typedoc_ documentation using _git_. This can be used to easily deploy the documentation to the * `gh-pages` branch. * * ## {target}:clean * * Remove both the build and dist directories corresponding to this target. * * ## {target}:tsconfig.json * * Emit a `tsconfig.json` file corresponding to the configuration for this target. This allows to compile it using * the command line `tsc` program. This is also useful for IDE to auto-detect the configuration of the project. * * @module targets/lib */ /** (Placeholder comment, see christopherthielen/typedoc-plugin-external-module-name#6) */ import { Gulp, TaskFunction } from "gulp"; import { Minimatch } from "minimatch"; import path from "path"; import { CleanOptions } from "../options/clean"; import { CopyOptions } from "../options/copy"; import { CompilerOptionsJson } from "../options/tsc"; import { OutModules } from "../options/typescript"; import { ResolvedProject } from "../project"; import { TypescriptConfig } from "../target-tasks/_typescript"; import { getBuildTypescriptTask } from "../target-tasks/build-typescript"; import { getTypedocTask } from "../target-tasks/typedoc"; import { AbsPosixPath, RelPosixPath } from "../types"; import { branchPublish } from "../utils/branch-publish"; import { getHeadHash } from "../utils/git"; import * as matcher from "../utils/matcher"; import { npmPublish } from "../utils/npm-publish"; import { PackageJson, readJsonFile } from "../utils/project"; import { BaseTasks, generateBaseTasks, getCopy, gulpBufferSrc, nameTask, ResolvedBaseDependencies, ResolvedTargetBase, resolveTargetBase, TargetBase, } from "./_base"; /** * Represents a Typescript library. * This is compatible with both browsers and Node. */ export interface LibTarget extends TargetBase { /** * Relative path for the main module (entry point of the lib) WITHOUT EXTENSION, relative to `project.srcDir`. * Default: `"index"`. */ mainModule: RelPosixPath; /** * Path to the `typedoc` directory, relative to `project.rootDir`. * Use `null` to not generate a `typedoc` task. * Default: `join(project.rootDir, "typedoc")`. */ typedoc?: TypedocOptions; /** * Options for distribution builds. * `false`: No distribution build * `true`: Distribution build with defaults * `DistOptions`: Provide custom options * Default: `false`, no distribution build. */ dist?: true | false | DistOptions; } /** * Library with fully resolved paths and dependencies. */ interface ResolvedLibTarget extends LibTarget, ResolvedTargetBase { readonly project: ResolvedProject; readonly srcDir: AbsPosixPath; readonly buildDir: AbsPosixPath; readonly scripts: Iterable<string>; readonly customTypingsDir: AbsPosixPath | null; readonly tscOptions: CompilerOptionsJson; /** * TODO */ readonly outModules: OutModules; readonly tsconfigJson: AbsPosixPath | null; readonly typedoc?: ResolvedTypedocOptions; readonly dependencies: ResolvedBaseDependencies; readonly copy?: CopyOptions[]; readonly clean?: CleanOptions; readonly dist: false | ResolvedDistOptions; } export interface DistOptions { /** * Directory used where the distribution builds will be written. * Default: `project.distDir` */ readonly distDir?: RelPosixPath; /** * Copy the sources from `target.srcDir` to `target.dist.distDir`. Default: `true`. */ readonly copySrc?: boolean; /** * Copy operations to perform when distributing the package. * The default copies the Markdown files at the project root (so you get `README.md`, `LICENSE.md`, ...). * * The base values are: * - `src`: `project.root` * - `dest`: `dist.distDir` */ readonly copy?: CopyOptions[]; readonly npmPublish?: NpmPublishOptions; /** * Optional function to apply when copying the `package.json` file to the dist directory. */ packageJsonMap?(old: PackageJson): PackageJson; } export interface ResolvedDistOptions extends DistOptions { /** * Directory used for distribution builds. */ readonly distDir: AbsPosixPath; /** * Depending on the value: * - `false`: Do not copy the source `.ts` files * - `true`: Copy the source `.ts` file from `target.srcDir` to `${target.dist.distDir}/_src`. The custom typings are * copied to `_custom-typings`. * * Default: `true`. */ readonly copySrc: boolean; /** * Optional function to apply when copying the `package.json` file to the dist directory. */ packageJsonMap(old: PackageJson): PackageJson; } export interface TypedocOptions { /** * Path to the `typedoc` directory, relative to `project.rootDir`. * Use `null` to not generate a `typedoc` task. * Default: `join(project.rootDir, "typedoc")`. */ readonly dir: RelPosixPath; readonly name: string; readonly deploy?: GitDeployOptions; } export interface ResolvedTypedocOptions extends TypedocOptions { readonly dir: AbsPosixPath; } export interface NpmPublishOptions { /** * Tag to use for this publication. * * Default: `"latest"`. */ readonly tag?: string; /** * Path to the npm command-line program. * * Default: `"npm"` (assumes that `npm` is in the `$PATH`) */ readonly command?: string; } export interface GitDeployOptions { readonly repository: string; readonly branch: string; readonly commitAuthor?: string; } /** * Resolve absolute paths and dependencies for the provided target. * * @param target Non-resolved target. * @return Resolved target. */ function resolveLibTarget(target: LibTarget): ResolvedLibTarget { const base: ResolvedTargetBase = resolveTargetBase(target); let typedoc: ResolvedTypedocOptions | undefined = undefined; if (target.typedoc !== undefined) { typedoc = { dir: path.posix.join(base.project.absRoot, target.typedoc.dir), name: target.typedoc.name, deploy: target.typedoc.deploy, }; } let dist: ResolvedDistOptions | false; if (target.dist === undefined || target.dist === false) { dist = false; } else { const defaultDistDir: AbsPosixPath = path.posix.join(base.project.absDistDir, target.name); const defaultPackageJsonMap: (pkg: PackageJson) => PackageJson = pkg => pkg; const defaultCopy: (dest: AbsPosixPath) => CopyOptions[] = (dest: AbsPosixPath) => [{ files: ["./*.md"], }]; if (target.dist === true) { // `true` literal dist = { distDir: defaultDistDir, packageJsonMap: defaultPackageJsonMap, npmPublish: {}, copySrc: true, copy: defaultCopy(defaultDistDir), }; } else { const distDir: AbsPosixPath = target.dist.distDir !== undefined ? target.dist.distDir : defaultDistDir; dist = { distDir, packageJsonMap: target.dist.packageJsonMap !== undefined ? target.dist.packageJsonMap : defaultPackageJsonMap, npmPublish: target.dist.npmPublish, copySrc: target.dist.copySrc !== undefined ? target.dist.copySrc : true, copy: defaultCopy(distDir), }; } } return {...base, mainModule: target.mainModule, typedoc, dist}; } export interface LibTasks extends BaseTasks { typedoc?: TaskFunction; typedocDeploy?: TaskFunction; dist?: TaskFunction; distCopy?: TaskFunction; distPublish?: TaskFunction; distPackageJson?: TaskFunction; } /** * Generates gulp tasks for the provided lib target. * * @param gulp Gulp instance used to generate tasks manipulating files. * @param targetOptions Target configuration. */ export function generateLibTasks(gulp: Gulp, targetOptions: LibTarget): LibTasks { const target: ResolvedLibTarget = resolveLibTarget(targetOptions); const result: LibTasks = <LibTasks> generateBaseTasks(gulp, targetOptions); const tsOptions: TypescriptConfig = { tscOptions: target.tscOptions, tsconfigJson: target.tsconfigJson, customTypingsDir: target.customTypingsDir, packageJson: target.project.absPackageJson, buildDir: target.buildDir, srcDir: target.srcDir, scripts: target.scripts, outModules: target.outModules, }; // typedoc if (target.typedoc !== undefined) { const typedocOptions: TypedocOptions = target.typedoc; result.typedoc = nameTask( `${target.name}:typedoc`, gulp.series([result.tsconfigJson, getTypedocTask(gulp, tsOptions, typedocOptions)]), ); // typedoc:deploy if (typedocOptions.deploy !== undefined) { const deploy: GitDeployOptions = typedocOptions.deploy; async function deployTypedocTask(): Promise<void> { const commitMessage: string = `Deploy documentation for ${await getHeadHash()}`; return branchPublish({...deploy, dir: typedocOptions.dir, commitMessage}); } result.typedocDeploy = nameTask(`${target.name}:typedoc:deploy`, gulp.series(result.typedoc!, deployTypedocTask)); } } // dist if (target.dist !== false) { const dist: ResolvedDistOptions = target.dist; const distTasks: TaskFunction[] = []; const copyTasks: TaskFunction[] = []; // Locations for compilation: default to the original sources but compile the copied files if copySrc is used let srcDir: AbsPosixPath = target.srcDir; let customTypingsDir: AbsPosixPath | null = target.customTypingsDir; // dist:copy:scripts if (target.dist.copySrc) { srcDir = path.posix.join(dist.distDir, "_src"); copyTasks.push(nameTask( `${target.name}:dist:copy:scripts`, (): NodeJS.ReadableStream => { return gulp .src([...target.scripts], {base: target.srcDir}) .pipe(gulp.dest(srcDir)); }, )); // dist:copy:custom-typings if (target.customTypingsDir !== null) { const srcCustomTypingsDir: AbsPosixPath = target.customTypingsDir; const destCustomTypingsDir: AbsPosixPath = path.posix.join(dist.distDir, "_custom-typings"); customTypingsDir = destCustomTypingsDir; copyTasks.push(nameTask( `${target.name}:dist:copy:custom-typings`, (): NodeJS.ReadableStream => { return gulp .src([path.posix.join(srcCustomTypingsDir, "**/*.d.ts")], {base: srcCustomTypingsDir}) .pipe(gulp.dest(destCustomTypingsDir!)); }, )); } // dist:copy:dist if (target.dist.copy !== undefined) { const [copyBaseTask, copyBaseWatchTask]: [TaskFunction, TaskFunction] = getCopy( gulp, target.project.absRoot, target.dist.distDir, target.dist.copy, ); copyTasks.push(nameTask(`${target.name}:dist:copy:dist`, copyBaseTask)); } } // dist:copy:lib if (target.copy !== undefined) { const [copyBaseTask, copyBaseWatchTask]: [TaskFunction, TaskFunction] = getCopy( gulp, target.srcDir, target.dist.distDir, target.copy, ); copyTasks.push(nameTask(`${target.name}:dist:copy:lib`, copyBaseTask)); } result.distCopy = nameTask(`${target.name}:dist:copy`, gulp.parallel(copyTasks)); // Resolve tsconfig for `dist` const tsconfigJson: AbsPosixPath | null = target.tsconfigJson !== null ? path.posix.join(srcDir, "tsconfig.json") : null; let scripts: string[] = []; if (srcDir !== target.srcDir) { for (const script of target.scripts) { scripts.push( matcher.asString(matcher.join(srcDir, matcher.relative(target.srcDir, new Minimatch(script)))), ); } } else { scripts = [...target.scripts]; } const tsOptions: TypescriptConfig = { tscOptions: target.tscOptions, tsconfigJson, customTypingsDir, packageJson: target.project.absPackageJson, buildDir: dist.distDir, srcDir, scripts, outModules: target.outModules, }; // dist:scripts distTasks.push(nameTask( `${target.name}:dist:scripts`, gulp.series(result.distCopy, getBuildTypescriptTask(gulp, tsOptions)), )); // dist:package.json { async function distPackageJsonTask(): Promise<NodeJS.ReadableStream> { let pkg: PackageJson = await readJsonFile(target.project.absPackageJson); if (typeof target.mainModule === "string") { pkg.main = target.mainModule; pkg.types = `${target.mainModule}.d.ts`; } pkg.gitHead = await getHeadHash(); pkg = dist.packageJsonMap(pkg); return gulpBufferSrc("package.json", Buffer.from(JSON.stringify(pkg, null, 2))) .pipe(gulp.dest(dist.distDir)); } result.distPackageJson = nameTask(`${target.name}:dist:package.json`, distPackageJsonTask); distTasks.push(result.distPackageJson); } const distTask: TaskFunction = result.clean !== undefined ? gulp.series(result.clean, gulp.parallel(distTasks)) : gulp.parallel(distTasks); result.dist = nameTask(`${target.name}:dist`, distTask); if (dist.npmPublish !== undefined) { const npmPublishOptions: NpmPublishOptions = dist.npmPublish; const npmPublishTask: TaskFunction = async (): Promise<void> => { return npmPublish({ ...npmPublishOptions, directory: dist.distDir, }); }; npmPublishTask.displayName = `${target.name}:dist:publish`; gulp.task(npmPublishTask); result.distPublish = nameTask(`${target.name}:dist:publish`, gulp.series(distTask, npmPublishTask)); } } return result; } /** * Generates and registers gulp tasks for the provided lib target. * * @param gulp Gulp instance where the tasks will be registered. * @param targetOptions Target configuration. */ export function registerLibTasks(gulp: Gulp, targetOptions: LibTarget): LibTasks { const tasks: LibTasks = generateLibTasks(gulp, targetOptions); for (const key in tasks) { const task: TaskFunction | undefined = (<any> tasks)[key]; if (task !== undefined) { gulp.task(task); } } return tasks; }