From 99b245b08d918e335562551dbd277f9fb2e9706a Mon Sep 17 00:00:00 2001 From: Jay Tavares Date: Wed, 8 Jun 2022 17:48:45 -0400 Subject: [PATCH] feat(node): add deleteOutputPath option to @nrwl/node:webpack executor (#10073) --- docs/generated/packages/node.json | 5 + e2e/node/src/node.test.ts | 94 +++++++++++++------ .../node/src/executors/webpack/schema.json | 5 + .../src/executors/webpack/webpack.impl.ts | 6 ++ packages/node/src/utils/fs.ts | 14 +++ packages/node/src/utils/normalize.ts | 1 + packages/node/src/utils/types.ts | 1 + 7 files changed, 97 insertions(+), 29 deletions(-) create mode 100644 packages/node/src/utils/fs.ts diff --git a/docs/generated/packages/node.json b/docs/generated/packages/node.json index a8b6de20a022f..aba8267776732 100644 --- a/docs/generated/packages/node.json +++ b/docs/generated/packages/node.json @@ -291,6 +291,11 @@ "type": "string", "description": "The output path of the generated files." }, + "deleteOutputPath": { + "type": "boolean", + "description": "Delete the output path before building.", + "default": true + }, "watch": { "type": "boolean", "description": "Run build when files change.", diff --git a/e2e/node/src/node.test.ts b/e2e/node/src/node.test.ts index e5e30904d38c4..db9b82804307f 100644 --- a/e2e/node/src/node.test.ts +++ b/e2e/node/src/node.test.ts @@ -1,5 +1,6 @@ import { stripIndents } from '@angular-devkit/core/src/utils/literals'; import { + createFile, checkFilesDoNotExist, checkFilesExist, expectJestTestsToPass, @@ -309,6 +310,41 @@ ${jslib}(); expect(nodeAppPackageJson['dependencies']['tslib']).toBeTruthy(); }, 300000); + it('should remove previous output before building with the --deleteOutputPath option set', async () => { + const appName = uniq('app'); + + runCLI(`generate @nrwl/node:app ${appName} --no-interactive`); + + // deleteOutputPath should default to true + createFile(`dist/apps/${appName}/_should_remove.txt`); + createFile(`dist/apps/_should_not_remove.txt`); + checkFilesExist( + `dist/apps/${appName}/_should_remove.txt`, + `dist/apps/_should_not_remove.txt` + ); + runCLI(`build ${appName} --outputHashing none`); // no explicit deleteOutputPath option set + checkFilesDoNotExist(`dist/apps/${appName}/_should_remove.txt`); + checkFilesExist(`dist/apps/_should_not_remove.txt`); + + // Explicitly set `deleteOutputPath` to true + createFile(`dist/apps/${appName}/_should_remove.txt`); + createFile(`dist/apps/_should_not_remove.txt`); + checkFilesExist( + `dist/apps/${appName}/_should_remove.txt`, + `dist/apps/_should_not_remove.txt` + ); + runCLI(`build ${appName} --outputHashing none --deleteOutputPath`); + checkFilesDoNotExist(`dist/apps/${appName}/_should_remove.txt`); + checkFilesExist(`dist/apps/_should_not_remove.txt`); + + // Explicitly set `deleteOutputPath` to false + createFile(`dist/apps/${appName}/_should_keep.txt`); + createFile(`dist/apps/_should_keep.txt`); + runCLI(`build ${appName} --deleteOutputPath=false --outputHashing none`); + checkFilesExist(`dist/apps/${appName}/_should_keep.txt`); + checkFilesExist(`dist/apps/_should_keep.txt`); + }, 120000); + describe('NestJS', () => { it('should have plugin output if specified in `tsPlugins`', async () => { newProject(); @@ -325,35 +361,35 @@ ${jslib}(); updateFile( `apps/${nestapp}/src/app/foo.dto.ts`, ` -export class FooDto { - foo: string; - bar: number; -}` + export class FooDto { + foo: string; + bar: number; + }` ); updateFile( `apps/${nestapp}/src/app/app.controller.ts`, ` -import { Controller, Get } from '@nestjs/common'; -import { FooDto } from './foo.dto'; -import { AppService } from './app.service'; + import { Controller, Get } from '@nestjs/common'; + import { FooDto } from './foo.dto'; + import { AppService } from './app.service'; -@Controller() -export class AppController { - constructor(private readonly appService: AppService) {} + @Controller() + export class AppController { + constructor(private readonly appService: AppService) {} - @Get() - getData() { - return this.appService.getData(); - } + @Get() + getData() { + return this.appService.getData(); + } - @Get('foo') - getFoo(): Promise { - return Promise.resolve({ - foo: 'foo', - bar: 123 - }) - } -}` + @Get('foo') + getFoo(): Promise { + return Promise.resolve({ + foo: 'foo', + bar: 123 + }) + } + }` ); await runCLIAsync(`build ${nestapp}`); @@ -361,13 +397,13 @@ export class AppController { const mainJs = readFile(`dist/apps/${nestapp}/main.js`); expect(stripIndents`${mainJs}`).toContain( stripIndents` -class FooDto { - static _OPENAPI_METADATA_FACTORY() { - return { foo: { required: true, type: () => String }, bar: { required: true, type: () => Number } }; - } -} -exports.FooDto = FooDto; - ` + class FooDto { + static _OPENAPI_METADATA_FACTORY() { + return { foo: { required: true, type: () => String }, bar: { required: true, type: () => Number } }; + } + } + exports.FooDto = FooDto; + ` ); }, 300000); }); diff --git a/packages/node/src/executors/webpack/schema.json b/packages/node/src/executors/webpack/schema.json index d5ad246ed3ba9..923723cec9a77 100644 --- a/packages/node/src/executors/webpack/schema.json +++ b/packages/node/src/executors/webpack/schema.json @@ -16,6 +16,11 @@ "type": "string", "description": "The output path of the generated files." }, + "deleteOutputPath": { + "type": "boolean", + "description": "Delete the output path before building.", + "default": true + }, "watch": { "type": "boolean", "description": "Run build when files change.", diff --git a/packages/node/src/executors/webpack/webpack.impl.ts b/packages/node/src/executors/webpack/webpack.impl.ts index bdc71d4392d5c..76e6307c917a6 100644 --- a/packages/node/src/executors/webpack/webpack.impl.ts +++ b/packages/node/src/executors/webpack/webpack.impl.ts @@ -17,6 +17,7 @@ import { register } from 'ts-node'; import { getNodeWebpackConfig } from '../../utils/node.config'; import { BuildNodeBuilderOptions } from '../../utils/types'; import { normalizeBuildOptions } from '../../utils/normalize'; +import { deleteOutputDir } from '../../utils/fs'; import { runWebpack } from '../../utils/run-webpack'; export type NodeBuildEvent = { @@ -77,6 +78,11 @@ export async function* webpackExecutor( } } + // Delete output path before bundling + if (options.deleteOutputPath) { + deleteOutputDir(context.root, options.outputPath); + } + const config = await options.webpackConfig.reduce( async (currentConfig, plugin) => { return require(plugin)(await currentConfig, { diff --git a/packages/node/src/utils/fs.ts b/packages/node/src/utils/fs.ts new file mode 100644 index 0000000000000..76948da59bd61 --- /dev/null +++ b/packages/node/src/utils/fs.ts @@ -0,0 +1,14 @@ +import * as path from 'path'; +import { removeSync } from 'fs-extra'; + +/** + * Delete an output directory, but error out if it's the root of the project. + */ +export function deleteOutputDir(root: string, outputPath: string) { + const resolvedOutputPath = path.resolve(root, outputPath); + if (resolvedOutputPath === root) { + throw new Error('Output path MUST not be project root directory!'); + } + + removeSync(resolvedOutputPath); +} diff --git a/packages/node/src/utils/normalize.ts b/packages/node/src/utils/normalize.ts index 8dbd7281e5db1..413cb7349ab2c 100644 --- a/packages/node/src/utils/normalize.ts +++ b/packages/node/src/utils/normalize.ts @@ -37,6 +37,7 @@ export function normalizeBuildOptions( options.additionalEntryPoints ?? [] ), outputFileName: options.outputFileName ?? 'main.js', + deleteOutputPath: options.deleteOutputPath ?? true, }; } diff --git a/packages/node/src/utils/types.ts b/packages/node/src/utils/types.ts index a56934941bf37..cd51238ca7e27 100644 --- a/packages/node/src/utils/types.ts +++ b/packages/node/src/utils/types.ts @@ -96,6 +96,7 @@ export interface BuildNodeBuilderOptions extends BuildBuilderOptions { externalDependencies: 'all' | 'none' | string[]; buildLibsFromSource?: boolean; generatePackageJson?: boolean; + deleteOutputPath?: boolean; } export interface NormalizedBuildNodeBuilderOptions