From 47d23c843617727040365c4f3ce27d78a3cfabe0 Mon Sep 17 00:00:00 2001 From: Denis Frenademetz Date: Wed, 5 Jul 2023 12:10:15 +0200 Subject: [PATCH] fix(js): js:node executor resolves main.js and build options correctly (#17884) Co-authored-by: Katerina Skroumpelou --- e2e/js/src/js-node.test.ts | 119 ++++++++++++++++++ packages/js/src/executors/node/node.impl.ts | 70 +++++++---- packages/js/src/executors/swc/swc.impl.ts | 3 +- packages/js/src/utils/get-main-file-dir.ts | 11 ++ .../utils/package-json/update-package-json.ts | 12 +- 5 files changed, 181 insertions(+), 34 deletions(-) create mode 100644 packages/js/src/utils/get-main-file-dir.ts diff --git a/e2e/js/src/js-node.test.ts b/e2e/js/src/js-node.test.ts index eda9bf0ddaf02..edef1cc115b36 100644 --- a/e2e/js/src/js-node.test.ts +++ b/e2e/js/src/js-node.test.ts @@ -47,4 +47,123 @@ describe('js:node error handling', () => { expect(output).toContain('Hello from my library!'); expect(output).toContain('This is an error'); }, 240_000); + + it('should execute library compiled with rollup', () => { + const rollupLib = uniq('rolluplib'); + + runCLI( + `generate @nx/js:lib ${rollupLib} --bundler=rollup --no-interactive` + ); + + updateFile(`libs/${rollupLib}/src/index.ts`, () => { + return ` + console.log('Hello from my library!'); + `; + }); + + updateProjectConfig(rollupLib, (config) => { + config.targets['run-node'] = { + executor: '@nx/js:node', + options: { + buildTarget: `${rollupLib}:build`, + watch: false, + }, + }; + return config; + }); + + const output = runCLI(`run ${rollupLib}:run-node`); + expect(output).toContain('Hello from my library!'); + }, 240_000); + + it('should execute library compiled with tsc', () => { + const tscLib = uniq('tsclib'); + + runCLI(`generate @nx/js:lib ${tscLib} --bundler=tsc --no-interactive`); + + updateFile(`libs/${tscLib}/src/index.ts`, () => { + return ` + console.log('Hello from my tsc library!'); + `; + }); + + updateProjectConfig(tscLib, (config) => { + config.targets['run-node'] = { + executor: '@nx/js:node', + options: { + buildTarget: `${tscLib}:build`, + watch: false, + }, + }; + return config; + }); + + const output = runCLI(`run ${tscLib}:run-node`); + expect(output).toContain('Hello from my tsc library!'); + }, 240_000); + + it('should execute library compiled with swc', () => { + const swcLib = uniq('swclib'); + + runCLI(`generate @nx/js:lib ${swcLib} --bundler=swc --no-interactive`); + + updateFile(`libs/${swcLib}/src/index.ts`, () => { + return ` + console.log('Hello from my swc library!'); + `; + }); + + updateProjectConfig(swcLib, (config) => { + config.targets['run-node'] = { + executor: '@nx/js:node', + options: { + buildTarget: `${swcLib}:build`, + watch: false, + }, + }; + return config; + }); + + const output = runCLI(`run ${swcLib}:run-node`); + expect(output).toContain('Hello from my swc library!'); + }, 240_000); + + it('should execute webpack app', () => { + const webpackProject = uniq('webpackproject'); + + runCLI( + `generate @nx/node:application ${webpackProject} --bundler=webpack --no-interactive` + ); + + updateFile(`apps/${webpackProject}/src/main.ts`, () => { + return ` + console.log('Hello from my webpack app!'); + `; + }); + + updateProjectConfig(webpackProject, (config) => { + config.targets['run-node'] = { + executor: '@nx/js:node', + options: { + buildTarget: `${webpackProject}:build`, + watch: false, + }, + }; + config.targets.build = { + ...config.targets.build, + configurations: { + development: { + outputPath: 'dist/packages/api-dev', + }, + production: { + outputPath: 'dist/packages/api-prod', + }, + }, + }; + return config; + }); + + const output = runCLI(`run ${webpackProject}:run-node`); + expect(output).toContain('Hello from my webpack app!'); + }, 240_000); }); diff --git a/packages/js/src/executors/node/node.impl.ts b/packages/js/src/executors/node/node.impl.ts index 562d498b6f816..ded44973ea31c 100644 --- a/packages/js/src/executors/node/node.impl.ts +++ b/packages/js/src/executors/node/node.impl.ts @@ -5,18 +5,21 @@ import { joinPathFragments, logger, parseTargetString, + ProjectGraphProjectNode, + readTargetOptions, runExecutor, } from '@nx/devkit'; +import { createAsyncIterable } from '@nx/devkit/src/utils/async-iterable'; import { daemonClient } from 'nx/src/daemon/client/client'; import { randomUUID } from 'crypto'; import * as path from 'path'; import { join } from 'path'; import { InspectType, NodeExecutorOptions } from './schema'; -import { createAsyncIterable } from '@nx/devkit/src/utils/async-iterable'; import { calculateProjectDependencies } from '../../utils/buildable-libs-utils'; import { killTree } from './lib/kill-tree'; import { fileExists } from 'nx/src/utils/fileutils'; +import { getMainFileDirRelativeToProjectRoot } from '../../utils/get-main-file-dir'; interface ActiveTask { id: string; @@ -46,13 +49,7 @@ export async function* nodeExecutor( context.projectGraph ); - let buildOptions: Record; - if (project.data.targets[buildTarget.target]) { - buildOptions = { - ...project.data.targets[buildTarget.target]?.options, - ...options.buildTargetOptions, - }; - } else { + if (!project.data.targets[buildTarget.target]) { throw new Error( `Cannot find build target ${chalk.bold( options.buildTarget @@ -60,6 +57,14 @@ export async function* nodeExecutor( ); } + const buildTargetExecutor = + project.data.targets[buildTarget.target]?.executor; + + const buildOptions: Record = { + ...readTargetOptions(buildTarget, context), + ...options.buildTargetOptions, + }; + if (options.waitUntilTargets && options.waitUntilTargets.length > 0) { const results = await runWaitUntilTargets(options, context); for (const [i, result] of results.entries()) { @@ -73,21 +78,13 @@ export async function* nodeExecutor( // Re-map buildable workspace projects to their output directory. const mappings = calculateResolveMappings(context, options); - let outputFileName = - buildOptions.outputFileName || `${path.parse(buildOptions.main).name}.js`; - - if (!buildOptions.outputFileName) { - const matches = buildOptions.main.match(/^(?!.*src)(.*)\/([^/]*)$/); //ignore strings that contain src and split paths into [folders before main, main.ts] - if (matches) { - const [mainFolder, mainFileName] = matches.slice(1); - outputFileName = path.join( - mainFolder, - `${path.parse(mainFileName).name}.js` - ); - } - } + const fileToRun = getFileToRun( + context, + project, + buildOptions, + buildTargetExecutor + ); - const fileToRun = join(context.root, buildOptions.outputPath, outputFileName); const tasks: ActiveTask[] = []; let currentTask: ActiveTask = null; @@ -318,6 +315,35 @@ function runWaitUntilTargets( ); } +function getFileToRun( + context: ExecutorContext, + project: ProjectGraphProjectNode, + buildOptions: Record, + buildTargetExecutor: string +): string { + let outputFileName = buildOptions.outputFileName; + + if (!outputFileName) { + const fileName = `${path.parse(buildOptions.main).name}.js`; + if ( + buildTargetExecutor === '@nx/js:tsc' || + buildTargetExecutor === '@nx/js:swc' + ) { + outputFileName = path.join( + getMainFileDirRelativeToProjectRoot( + buildOptions.main, + project.data.root + ), + fileName + ); + } else { + outputFileName = fileName; + } + } + + return join(context.root, buildOptions.outputPath, outputFileName); +} + function fileToRunCorrectPath(fileToRun: string): string { if (!fileExists(fileToRun)) { const cjsFile = fileToRun.replace(/\.js$/, '.cjs'); diff --git a/packages/js/src/executors/swc/swc.impl.ts b/packages/js/src/executors/swc/swc.impl.ts index 3549a2112d5d9..91319542bad62 100644 --- a/packages/js/src/executors/swc/swc.impl.ts +++ b/packages/js/src/executors/swc/swc.impl.ts @@ -1,4 +1,4 @@ -import { ExecutorContext, readJsonFile, writeJsonFile } from '@nx/devkit'; +import { ExecutorContext } from '@nx/devkit'; import { assetGlobsToFiles, FileInputOutput } from '../../utils/assets/assets'; import { removeSync } from 'fs-extra'; import { dirname, join, relative, resolve } from 'path'; @@ -21,7 +21,6 @@ import { import { compileSwc, compileSwcWatch } from '../../utils/swc/compile-swc'; import { getSwcrcPath } from '../../utils/swc/get-swcrc-path'; import { generateTmpSwcrc } from '../../utils/swc/inline'; -import type { Options } from '@swc/core'; export function normalizeOptions( options: SwcExecutorOptions, diff --git a/packages/js/src/utils/get-main-file-dir.ts b/packages/js/src/utils/get-main-file-dir.ts new file mode 100644 index 0000000000000..e95e3145c6674 --- /dev/null +++ b/packages/js/src/utils/get-main-file-dir.ts @@ -0,0 +1,11 @@ +import { dirname, relative } from 'path'; +import { normalizePath } from 'nx/src/utils/path'; + +export function getMainFileDirRelativeToProjectRoot( + main: string, + projectRoot: string +): string { + const mainFileDir = dirname(main); + const relativeDir = normalizePath(relative(projectRoot, mainFileDir)); + return relativeDir === '' ? `./` : `./${relativeDir}/`; +} diff --git a/packages/js/src/utils/package-json/update-package-json.ts b/packages/js/src/utils/package-json/update-package-json.ts index 548a98851a858..66f6800e3f2ff 100644 --- a/packages/js/src/utils/package-json/update-package-json.ts +++ b/packages/js/src/utils/package-json/update-package-json.ts @@ -9,7 +9,6 @@ import { ExecutorContext, getOutputsForTargetAndConfiguration, joinPathFragments, - normalizePath, ProjectFileMap, ProjectGraphProjectNode, readJsonFile, @@ -17,7 +16,7 @@ import { writeJsonFile, } from '@nx/devkit'; import { DependentBuildableProjectNode } from '../buildable-libs-utils'; -import { basename, dirname, join, parse, relative } from 'path'; +import { basename, join, parse } from 'path'; import { writeFileSync } from 'fs-extra'; import { isNpmProject } from 'nx/src/project-graph/operators'; import { fileExists } from 'nx/src/utils/fileutils'; @@ -25,14 +24,7 @@ import type { PackageJson } from 'nx/src/utils/package-json'; import { existsSync } from 'fs'; import { readProjectFileMapCache } from 'nx/src/project-graph/nx-deps-cache'; -function getMainFileDirRelativeToProjectRoot( - main: string, - projectRoot: string -): string { - const mainFileDir = dirname(main); - const relativeDir = normalizePath(relative(projectRoot, mainFileDir)); - return relativeDir === '' ? `./` : `./${relativeDir}/`; -} +import { getMainFileDirRelativeToProjectRoot } from '../get-main-file-dir'; export type SupportedFormat = 'cjs' | 'esm';