diff --git a/docs/generated/devkit/Task.md b/docs/generated/devkit/Task.md index bd068534e58e4e..e4eb949a530569 100644 --- a/docs/generated/devkit/Task.md +++ b/docs/generated/devkit/Task.md @@ -11,6 +11,7 @@ A representation of the invocation of an Executor - [hash](../../devkit/documents/Task#hash): string - [hashDetails](../../devkit/documents/Task#hashdetails): Object - [id](../../devkit/documents/Task#id): string +- [outputs](../../devkit/documents/Task#outputs): string[] - [overrides](../../devkit/documents/Task#overrides): any - [projectRoot](../../devkit/documents/Task#projectroot): string - [startTime](../../devkit/documents/Task#starttime): number @@ -67,6 +68,14 @@ Unique ID --- +### outputs + +• **outputs**: `string`[] + +The outputs the task may produce + +--- + ### overrides • **overrides**: `any` diff --git a/docs/generated/devkit/getOutputsForTargetAndConfiguration.md b/docs/generated/devkit/getOutputsForTargetAndConfiguration.md index 80540c33b68290..54d813607b689b 100644 --- a/docs/generated/devkit/getOutputsForTargetAndConfiguration.md +++ b/docs/generated/devkit/getOutputsForTargetAndConfiguration.md @@ -2,14 +2,32 @@ ▸ **getOutputsForTargetAndConfiguration**(`task`, `node`): `string`[] +**`Deprecated`** + +Pass the target and overrides instead. This will be removed in v18. + +#### Parameters + +| Name | Type | +| :----- | :-------------------------------------------------------------------------- | +| `task` | [`Task`](../../devkit/documents/Task) | +| `node` | [`ProjectGraphProjectNode`](../../devkit/documents/ProjectGraphProjectNode) | + +#### Returns + +`string`[] + +▸ **getOutputsForTargetAndConfiguration**(`target`, `overrides`, `node`): `string`[] + Returns the list of outputs that will be cached. #### Parameters -| Name | Type | Description | -| :----- | :-------------------------------------------------------------------------- | :-------------------------------------------------------- | -| `task` | `Pick`<[`Task`](../../devkit/documents/Task), `"overrides"` \| `"target"`\> | target + overrides | -| `node` | [`ProjectGraphProjectNode`](../../devkit/documents/ProjectGraphProjectNode) | ProjectGraphProjectNode object that the task runs against | +| Name | Type | +| :---------- | :----------------------------------------------------------------------------------------------------------------- | +| `target` | [`Task`](../../devkit/documents/Task) \| { `configuration?`: `string` ; `project`: `string` ; `target`: `string` } | +| `overrides` | `any` | +| `node` | [`ProjectGraphProjectNode`](../../devkit/documents/ProjectGraphProjectNode) | #### Returns diff --git a/packages/angular/src/generators/move/lib/update-ng-package.ts b/packages/angular/src/generators/move/lib/update-ng-package.ts index 01c158f410e757..221b8a40a6978b 100644 --- a/packages/angular/src/generators/move/lib/update-ng-package.ts +++ b/packages/angular/src/generators/move/lib/update-ng-package.ts @@ -26,12 +26,10 @@ export function updateNgPackage(tree: Tree, schema: MoveImplOptions): void { ); const outputs = getOutputsForTargetAndConfiguration( { - target: { - project: schema.newProjectName, - target: 'build', - }, - overrides: {}, + project: schema.newProjectName, + target: 'build', }, + {}, { name: schema.newProjectName, type: 'lib', diff --git a/packages/js/src/utils/buildable-libs-utils.ts b/packages/js/src/utils/buildable-libs-utils.ts index 862a529cbf95d6..1615417c0a98dc 100644 --- a/packages/js/src/utils/buildable-libs-utils.ts +++ b/packages/js/src/utils/buildable-libs-utils.ts @@ -124,13 +124,11 @@ export function calculateProjectDependencies( : dep, outputs: getOutputsForTargetAndConfiguration( { - overrides: {}, - target: { - project: projectName, - target: targetName, - configuration: configurationName, - }, + project: projectName, + target: targetName, + configuration: configurationName, }, + {}, depNode ), node: depNode, @@ -260,7 +258,11 @@ export function calculateDependenciesFromTaskGraph( return null; } - let outputs = getOutputsForTargetAndConfiguration(depTask, depProjectNode); + let outputs = getOutputsForTargetAndConfiguration( + depTask.target, + depTask.overrides, + depProjectNode + ); if (outputs.length === 0) { nonBuildableDependencies.push(depTask.target.project); @@ -558,13 +560,11 @@ export function updateBuildableProjectPackageJsonDependencies( ) { const outputs = getOutputsForTargetAndConfiguration( { - overrides: {}, - target: { - project: projectName, - target: targetName, - configuration: configurationName, - }, + project: projectName, + target: targetName, + configuration: configurationName, }, + {}, node ); @@ -598,13 +598,11 @@ export function updateBuildableProjectPackageJsonDependencies( if (entry.node.type === 'lib') { const outputs = getOutputsForTargetAndConfiguration( { - overrides: {}, - target: { - project: projectName, - target: targetName, - configuration: configurationName, - }, + project: projectName, + target: targetName, + configuration: configurationName, }, + {}, entry.node ); 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 41630fc895be20..33ae926d5e64d9 100644 --- a/packages/js/src/utils/package-json/update-package-json.ts +++ b/packages/js/src/utils/package-json/update-package-json.ts @@ -175,13 +175,11 @@ function addMissingDependencies( ) { const outputs = getOutputsForTargetAndConfiguration( { - overrides: {}, - target: { - project: projectName, - target: targetName, - configuration: configurationName, - }, + project: projectName, + target: targetName, + configuration: configurationName, }, + {}, entry.node ); diff --git a/packages/nx/src/command-line/affected/print-affected.ts b/packages/nx/src/command-line/affected/print-affected.ts index 17675c763cc637..d73ff952f4c1b4 100644 --- a/packages/nx/src/command-line/affected/print-affected.ts +++ b/packages/nx/src/command-line/affected/print-affected.ts @@ -1,4 +1,4 @@ -import { getCommandAsString, getOutputs } from '../../tasks-runner/utils'; +import { getCommandAsString } from '../../tasks-runner/utils'; import * as yargs from 'yargs'; import type { NxArgs } from '../../utils/command-line-utils'; import { @@ -95,7 +95,7 @@ async function createTasks( target: task.target, hash: task.hash, command: getCommandAsString(execCommand, task), - outputs: getOutputs(projectGraph.nodes, task), + outputs: task.outputs, })); } diff --git a/packages/nx/src/config/task-graph.ts b/packages/nx/src/config/task-graph.ts index 00ec80f94ec280..573f691ccc220d 100644 --- a/packages/nx/src/config/task-graph.ts +++ b/packages/nx/src/config/task-graph.ts @@ -27,6 +27,11 @@ export interface Task { * Overrides for the configured options of the target */ overrides: any; + + /** + * The outputs the task may produce + */ + outputs: string[]; /** * Root of the project the task belongs to */ diff --git a/packages/nx/src/hasher/task-hasher.ts b/packages/nx/src/hasher/task-hasher.ts index 9b53384e95e8ff..b562895fa777b6 100644 --- a/packages/nx/src/hasher/task-hasher.ts +++ b/packages/nx/src/hasher/task-hasher.ts @@ -459,7 +459,8 @@ class TaskHasherImpl { for (const d of taskGraph.dependencies[task.id]) { const childTask = taskGraph.tasks[d]; const outputs = getOutputsForTargetAndConfiguration( - childTask, + childTask.target, + childTask.overrides, this.projectGraph.nodes[childTask.target.project] ); const { getFilesForOutputs } = diff --git a/packages/nx/src/tasks-runner/create-task-graph.ts b/packages/nx/src/tasks-runner/create-task-graph.ts index 412bc2329f62c8..8ed4e75c341149 100644 --- a/packages/nx/src/tasks-runner/create-task-graph.ts +++ b/packages/nx/src/tasks-runner/create-task-graph.ts @@ -1,5 +1,5 @@ import { ProjectGraph, ProjectGraphProjectNode } from '../config/project-graph'; -import { getDependencyConfigs, interpolate } from './utils'; +import { getDependencyConfigs, getOutputs, interpolate } from './utils'; import { projectHasTarget, projectHasTargetAndConfiguration, @@ -329,11 +329,22 @@ export class ProcessTasks { configuration: resolvedConfiguration, }; + const interpolatedOverrides = interpolateOverrides( + overrides, + project.name, + project.data + ); + return { id, target: qualifiedTarget, projectRoot: project.data.root, - overrides: interpolateOverrides(overrides, project.name, project.data), + overrides: interpolatedOverrides, + outputs: getOutputs( + this.projectGraph.nodes, + qualifiedTarget, + interpolatedOverrides + ), // TODO(v18): Remove cast here after typing is moved back onto TargetConfiguration cache: (project.data.targets[target] as any).cache, }; diff --git a/packages/nx/src/tasks-runner/task-orchestrator.ts b/packages/nx/src/tasks-runner/task-orchestrator.ts index ac5395059efb44..9c25ce755f59ae 100644 --- a/packages/nx/src/tasks-runner/task-orchestrator.ts +++ b/packages/nx/src/tasks-runner/task-orchestrator.ts @@ -8,7 +8,6 @@ import { TaskStatus } from './tasks-runner'; import { calculateReverseDeps, getExecutorForTask, - getOutputs, isCacheableTask, removeTasksFromTaskGraph, shouldStreamOutput, @@ -147,7 +146,7 @@ export class TaskOrchestrator { const cachedResult = await this.cache.get(task); if (!cachedResult || cachedResult.code !== 0) return null; - const outputs = getOutputs(this.projectGraph.nodes, task); + const outputs = task.outputs; const shouldCopyOutputsFromCache = !!outputs.length && (await this.shouldCopyOutputsFromCache(outputs, task.hash)); @@ -422,7 +421,7 @@ export class TaskOrchestrator { result.status === 'success' ? 0 : 1, - outputs: getOutputs(this.projectGraph.nodes, result.task), + outputs: result.task.outputs, })) .filter(({ task, code }) => this.shouldCacheTaskResult(task, code)) .filter(({ terminalOutput, outputs }) => terminalOutput || outputs) @@ -551,10 +550,7 @@ export class TaskOrchestrator { private async recordOutputsHash(task: Task) { if (this.daemon?.enabled()) { - return this.daemon.recordOutputsHash( - getOutputs(this.projectGraph.nodes, task), - task.hash - ); + return this.daemon.recordOutputsHash(task.outputs, task.hash); } } diff --git a/packages/nx/src/tasks-runner/utils.spec.ts b/packages/nx/src/tasks-runner/utils.spec.ts index 999be6a45bc083..3c3109bacfed63 100644 --- a/packages/nx/src/tasks-runner/utils.spec.ts +++ b/packages/nx/src/tasks-runner/utils.spec.ts @@ -33,7 +33,8 @@ describe('utils', () => { it('should return empty arrays', () => { expect( getOutputsForTargetAndConfiguration( - task, + task.target, + task.overrides, getNode({ outputs: [], }) @@ -44,7 +45,8 @@ describe('utils', () => { it('should interpolate {workspaceRoot}, {projectRoot} and {projectName}', () => { expect( getOutputsForTargetAndConfiguration( - task, + task.target, + task.overrides, getNode({ outputs: [ '{workspaceRoot}/one', @@ -59,7 +61,8 @@ describe('utils', () => { it('should interpolate {projectRoot} when it is not at the beginning', () => { expect( getOutputsForTargetAndConfiguration( - task, + task.target, + task.overrides, getNode({ outputs: ['{workspaceRoot}/dist/{projectRoot}'], }) @@ -70,7 +73,8 @@ describe('utils', () => { it('should throw when {workspaceRoot} is used not at the beginning', () => { expect(() => getOutputsForTargetAndConfiguration( - task, + task.target, + task.overrides, getNode({ outputs: ['test/{workspaceRoot}/dist'], }) @@ -92,9 +96,13 @@ describe('utils', () => { files: [], }, }; - expect(getOutputsForTargetAndConfiguration(task, data as any)).toEqual([ - 'dist', - ]); + expect( + getOutputsForTargetAndConfiguration( + task.target, + task.overrides, + data as any + ) + ).toEqual(['dist']); }); it('should interpolate {workspaceRoot} when {projectRoot} = . by removing the slash after it', () => { @@ -111,9 +119,13 @@ describe('utils', () => { files: [], }, }; - expect(getOutputsForTargetAndConfiguration(task, data as any)).toEqual([ - 'dist', - ]); + expect( + getOutputsForTargetAndConfiguration( + task.target, + task.overrides, + data as any + ) + ).toEqual(['dist']); }); it('should throw when {projectRoot} is used not at the beginning and the value is .', () => { @@ -131,14 +143,19 @@ describe('utils', () => { }, }; expect(() => - getOutputsForTargetAndConfiguration(task, data as any) + getOutputsForTargetAndConfiguration( + task.target, + task.overrides, + data as any + ) ).toThrow(); }); it('should support interpolation based on options', () => { expect( getOutputsForTargetAndConfiguration( - task, + task.target, + task.overrides, getNode({ outputs: ['{workspaceRoot}/path/{options.myVar}'], options: { @@ -152,7 +169,8 @@ describe('utils', () => { it('should support nested interpolation based on options', () => { expect( getOutputsForTargetAndConfiguration( - task, + task.target, + task.overrides, getNode({ outputs: ['{options.nested.myVar}'], options: { @@ -168,7 +186,8 @@ describe('utils', () => { it('should support interpolation for non-existing options', () => { expect( getOutputsForTargetAndConfiguration( - task, + task.target, + task.overrides, getNode({ outputs: ['{options.outputFile}'], options: {}, @@ -180,7 +199,8 @@ describe('utils', () => { it('should support interpolation based on configuration-specific options', () => { expect( getOutputsForTargetAndConfiguration( - task, + task.target, + task.overrides, getNode({ outputs: ['{options.myVar}'], options: { @@ -199,11 +219,9 @@ describe('utils', () => { it('should support interpolation outputs from overrides', () => { expect( getOutputsForTargetAndConfiguration( + task.target, { - ...task, - overrides: { - myVar: 'value/override', - }, + myVar: 'value/override', }, getNode({ outputs: ['{options.myVar}'], @@ -224,7 +242,8 @@ describe('utils', () => { it('should return the outputPath option', () => { expect( getOutputsForTargetAndConfiguration( - task, + task.target, + task.overrides, getNode({ options: { outputPath: 'value', @@ -237,11 +256,9 @@ describe('utils', () => { it('should handle outputPath overrides', () => { expect( getOutputsForTargetAndConfiguration( + task.target, { - ...task, - overrides: { - outputPath: 'overrideOutputPath', - }, + outputPath: 'overrideOutputPath', }, getNode({ options: { @@ -255,7 +272,8 @@ describe('utils', () => { it('should return configuration-specific outputPath when defined', () => { expect( getOutputsForTargetAndConfiguration( - task, + task.target, + task.overrides, getNode({ options: { outputPath: 'value', @@ -273,7 +291,8 @@ describe('utils', () => { it('should return configuration-independent outputPath when defined', () => { expect( getOutputsForTargetAndConfiguration( - task, + task.target, + task.overrides, getNode({ options: { outputPath: 'value', @@ -288,7 +307,7 @@ describe('utils', () => { it('should return default output paths when nothing else is defined', () => { expect( - getOutputsForTargetAndConfiguration(task, { + getOutputsForTargetAndConfiguration(task.target, task.overrides, { name: 'myapp', type: 'app', data: { @@ -313,7 +332,8 @@ describe('utils', () => { it('should transform non-prefixed paths', () => { expect( getOutputsForTargetAndConfiguration( - task, + task.target, + task.overrides, getNode({ outputs: ['dist'], }) @@ -323,7 +343,8 @@ describe('utils', () => { it('should transform non-prefixed paths that use interpolation', () => { expect( getOutputsForTargetAndConfiguration( - task, + task.target, + task.overrides, getNode({ outputs: ['dist/{projectRoot}'], }) @@ -334,7 +355,8 @@ describe('utils', () => { it('should transform relative paths', () => { expect( getOutputsForTargetAndConfiguration( - task, + task.target, + task.overrides, getNode({ outputs: ['./sub'], }) @@ -345,7 +367,8 @@ describe('utils', () => { it('should transform unix-absolute paths', () => { expect( getOutputsForTargetAndConfiguration( - task, + task.target, + task.overrides, getNode({ outputs: ['/dist'], }) diff --git a/packages/nx/src/tasks-runner/utils.ts b/packages/nx/src/tasks-runner/utils.ts index 0be79f270f7c8b..d709a094d0b82a 100644 --- a/packages/nx/src/tasks-runner/utils.ts +++ b/packages/nx/src/tasks-runner/utils.ts @@ -81,9 +81,14 @@ export function expandDependencyConfigSyntaxSugar( export function getOutputs( p: Record, - task: Task + target: Task['target'], + overrides: Task['overrides'] ) { - return getOutputsForTargetAndConfiguration(task, p[task.target.project]); + return getOutputsForTargetAndConfiguration( + target, + overrides, + p[target.project] + ); } class InvalidOutputsError extends Error { @@ -133,39 +138,43 @@ export function transformLegacyOutputs( } /** - * Returns the list of outputs that will be cached. - * @param task target + overrides - * @param node ProjectGraphProjectNode object that the task runs against + * @deprecated Pass the target and overrides instead. This will be removed in v18. */ export function getOutputsForTargetAndConfiguration( - task: Pick, + task: Task, + node: ProjectGraphProjectNode +): string[]; +export function getOutputsForTargetAndConfiguration( + target: Task['target'] | Task, + overrides: Task['overrides'] | ProjectGraphProjectNode, node: ProjectGraphProjectNode +): string[]; +/** + * Returns the list of outputs that will be cached. + */ +export function getOutputsForTargetAndConfiguration( + taskTargetOrTask: Task['target'] | Task, + overridesOrNode: Task['overrides'] | ProjectGraphProjectNode, + node?: ProjectGraphProjectNode ): string[] { - const { target, configuration } = task.target; + const taskTarget = + 'id' in taskTargetOrTask ? taskTargetOrTask.target : taskTargetOrTask; + const overrides = + 'id' in taskTargetOrTask ? taskTargetOrTask.overrides : overridesOrNode; + node = 'id' in taskTargetOrTask ? overridesOrNode : node; + + const { target, configuration } = taskTarget; const targetConfiguration = node.data.targets[target]; const options = { ...targetConfiguration.options, ...targetConfiguration?.configurations?.[configuration], - ...task.overrides, + ...overrides, }; if (targetConfiguration?.outputs) { - try { - validateOutputs(targetConfiguration.outputs); - } catch (error) { - if (error instanceof InvalidOutputsError) { - // TODO(@FrozenPandaz): In v17, throw this error and do not transform. - console.warn(error.message); - targetConfiguration.outputs = transformLegacyOutputs( - node.data.root, - error - ); - } else { - throw error; - } - } + validateOutputs(targetConfiguration.outputs); return targetConfiguration.outputs .map((output: string) => { diff --git a/packages/workspace/src/utilities/buildable-libs-utils.spec.ts b/packages/workspace/src/utilities/buildable-libs-utils.spec.ts deleted file mode 100644 index 6a44a9449443b7..00000000000000 --- a/packages/workspace/src/utilities/buildable-libs-utils.spec.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { DependencyType, ProjectGraph } from '@nx/devkit'; -import { - calculateProjectDependencies, - DependentBuildableProjectNode, - updatePaths, -} from './buildable-libs-utils'; - -describe('updatePaths', () => { - const deps: DependentBuildableProjectNode[] = [ - { name: '@proj/lib', node: {} as any, outputs: ['dist/libs/lib'] }, - ]; - - it('should add path', () => { - const paths: Record = { - '@proj/test': ['libs/test/src/index.ts'], - }; - updatePaths(deps, paths); - expect(paths).toEqual({ - '@proj/lib': ['dist/libs/lib'], - '@proj/test': ['libs/test/src/index.ts'], - }); - }); - - it('should replace paths', () => { - const paths: Record = { - '@proj/lib': ['libs/lib/src/index.ts'], - '@proj/lib/sub': ['libs/lib/sub/src/index.ts'], - }; - updatePaths(deps, paths); - expect(paths).toEqual({ - '@proj/lib': ['dist/libs/lib'], - '@proj/lib/sub': ['dist/libs/lib/sub'], - }); - }); -}); - -describe('calculateProjectDependencies', () => { - it('should include npm packages in dependency list', async () => { - const graph: ProjectGraph = { - nodes: { - example: { - type: 'lib', - name: 'example', - data: { - root: '/root/example', - }, - }, - }, - externalNodes: { - 'npm:formik': { - type: 'npm', - name: 'npm:formik', - data: { - packageName: 'formik', - version: '0.0.0', - }, - }, - }, - dependencies: { - example: [ - { - source: 'example', - target: 'npm:formik', - type: DependencyType.static, - }, - ], - }, - }; - - const results = calculateProjectDependencies( - graph, - 'root', - 'example', - 'build', - undefined - ); - expect(results).toMatchObject({ - target: { - type: 'lib', - name: 'example', - }, - dependencies: [{ name: 'formik' }], - }); - }); - - it('should include npm packages in dependency list and sort them correctly', async () => { - const graph: ProjectGraph = { - nodes: { - example: { - type: 'lib', - name: 'example', - data: { - root: '/root/example', - }, - }, - }, - externalNodes: { - 'npm:some-lib': { - type: 'npm', - name: 'npm:some-lib', - data: { - packageName: 'some-lib', - version: '0.0.0', - }, - }, - 'npm:formik': { - type: 'npm', - name: 'npm:formik', - data: { - packageName: 'formik', - version: '0.0.0', - }, - }, - 'npm:@prefixed-lib': { - type: 'npm', - name: 'npm:@prefixed-lib', - data: { - packageName: '@prefixed-lib', - version: '0.0.0', - }, - }, - }, - dependencies: { - example: [ - { - source: 'example', - target: 'npm:some-lib', - type: DependencyType.static, - }, - { - source: 'example', - target: 'npm:formik', - type: DependencyType.static, - }, - { - source: 'example', - target: 'npm:@prefixed-lib', - type: DependencyType.static, - }, - ], - }, - }; - - const results = await calculateProjectDependencies( - graph, - 'root', - 'example', - 'build', - undefined - ); - expect(results).toMatchObject({ - target: { - type: 'lib', - name: 'example', - }, - dependencies: [ - { name: '@prefixed-lib' }, - { name: 'formik' }, - { name: 'some-lib' }, - ], - }); - }); - - it('should include all top-level dependencies, even ones that are also transitive', async () => { - const graph: ProjectGraph = { - nodes: { - example: { - type: 'lib', - name: 'example', - data: { - root: '/root/example', - targets: { - build: { - executor: 'x', - }, - }, - }, - }, - example2: { - type: 'lib', - name: 'example2', - data: { - root: '/root/example2', - targets: { - build: { - executor: 'x', - }, - }, - }, - }, - }, - externalNodes: { - 'npm:formik': { - type: 'npm', - name: 'npm:formik', - data: { - packageName: 'formik', - version: '0.0.0', - }, - }, - 'npm:foo': { - type: 'npm', - name: 'npm:foo', - data: { - packageName: 'foo', - version: '0.0.0', - }, - }, - }, - dependencies: { - example: [ - // when example2 dependency is listed first - { - source: 'example', - target: 'example2', - type: DependencyType.static, - }, - { - source: 'example', - target: 'npm:formik', - type: DependencyType.static, - }, - ], - example2: [ - // and example2 also depends on npm:formik - { - source: 'example2', - target: 'npm:formik', - type: DependencyType.static, - }, - { - source: 'example2', - target: 'npm:foo', - type: DependencyType.static, - }, - ], - }, - }; - - const results = calculateProjectDependencies( - graph, - 'root', - 'example', - 'build', - undefined - ); - expect(results).toMatchObject({ - target: { - name: 'example', - }, - topLevelDependencies: [ - // expect example2 and formik as top-level deps, but not foo - expect.objectContaining({ name: 'example2' }), - expect.objectContaining({ name: 'formik' }), - ], - }); - }); -}); - -describe('missingDependencies', () => { - it('should throw an error if dependency is missing', async () => { - const graph: ProjectGraph = { - nodes: { - example: { - type: 'lib', - name: 'example', - data: { - root: '/root/example', - }, - }, - }, - externalNodes: {}, - dependencies: { - example: [ - { - source: 'example', - target: 'missing', - type: DependencyType.static, - }, - ], - }, - }; - - expect(() => - calculateProjectDependencies(graph, 'root', 'example', 'build', undefined) - ).toThrow(); - }); -}); diff --git a/packages/workspace/src/utilities/buildable-libs-utils.ts b/packages/workspace/src/utilities/buildable-libs-utils.ts deleted file mode 100644 index 17745dab7ff2fe..00000000000000 --- a/packages/workspace/src/utilities/buildable-libs-utils.ts +++ /dev/null @@ -1,468 +0,0 @@ -import { dirname, join, relative } from 'path'; -import { directoryExists, fileExists } from './fileutils'; -import type { ProjectGraph, ProjectGraphProjectNode } from '@nx/devkit'; -import { - getOutputsForTargetAndConfiguration, - ProjectGraphExternalNode, - readJsonFile, - stripIndents, - writeJsonFile, -} from '@nx/devkit'; -import type * as ts from 'typescript'; -import { unlinkSync } from 'fs'; -import { output } from './output'; -import { isNpmProject } from 'nx/src/project-graph/operators'; -import { ensureTypescript } from './typescript'; - -let tsModule: typeof import('typescript'); - -function isBuildable(target: string, node: ProjectGraphProjectNode): boolean { - return ( - node.data.targets && - node.data.targets[target] && - node.data.targets[target].executor !== '' - ); -} - -/** - * @deprecated This type will be removed from @nx/workspace in version 17. Prefer importing from @nx/js. - */ -export type DependentBuildableProjectNode = { - name: string; - outputs: string[]; - node: ProjectGraphProjectNode | ProjectGraphExternalNode; -}; - -/** - * @deprecated This function will be removed from @nx/workspace in version 17. Prefer importing from @nx/js. - */ -export function calculateProjectDependencies( - projGraph: ProjectGraph, - root: string, - projectName: string, - targetName: string, - configurationName: string, - shallow?: boolean -): { - target: ProjectGraphProjectNode; - dependencies: DependentBuildableProjectNode[]; - nonBuildableDependencies: string[]; - topLevelDependencies: DependentBuildableProjectNode[]; -} { - const target = projGraph.nodes[projectName]; - // gather the library dependencies - const nonBuildableDependencies = []; - const topLevelDependencies: DependentBuildableProjectNode[] = []; - const collectedDeps = collectDependencies( - projectName, - projGraph, - [], - shallow - ); - const missing = collectedDeps.reduce( - (missing: string[] | undefined, { name: dep }) => { - const depNode = projGraph.nodes[dep] || projGraph.externalNodes[dep]; - if (!depNode) { - missing = missing || []; - missing.push(dep); - } - return missing; - }, - null - ); - if (missing) { - throw new Error(`Unable to find ${missing.join(', ')} in project graph.`); - } - const dependencies = collectedDeps - .map(({ name: dep, isTopLevel }) => { - let project: DependentBuildableProjectNode = null; - const depNode = projGraph.nodes[dep] || projGraph.externalNodes[dep]; - if (depNode.type === 'lib') { - if (isBuildable(targetName, depNode)) { - const libPackageJsonPath = join( - root, - depNode.data.root, - 'package.json' - ); - - project = { - name: fileExists(libPackageJsonPath) - ? readJsonFile(libPackageJsonPath).name // i.e. @workspace/mylib - : dep, - outputs: getOutputsForTargetAndConfiguration( - { - overrides: {}, - target: { - project: projectName, - target: targetName, - configuration: configurationName, - }, - }, - depNode - ), - node: depNode, - }; - } else { - nonBuildableDependencies.push(dep); - } - } else if (depNode.type === 'npm') { - project = { - name: depNode.data.packageName, - outputs: [], - node: depNode, - }; - } - - if (project && isTopLevel) { - topLevelDependencies.push(project); - } - - return project; - }) - .filter((x) => !!x); - - dependencies.sort((a, b) => (a.name > b.name ? 1 : b.name > a.name ? -1 : 0)); - - return { - target, - dependencies, - nonBuildableDependencies, - topLevelDependencies, - }; -} - -function collectDependencies( - project: string, - projGraph: ProjectGraph, - acc: { name: string; isTopLevel: boolean }[], - shallow?: boolean, - areTopLevelDeps = true -): { name: string; isTopLevel: boolean }[] { - (projGraph.dependencies[project] || []).forEach((dependency) => { - const existingEntry = acc.find((dep) => dep.name === dependency.target); - if (!existingEntry) { - // Temporary skip this. Currently the set of external nodes is built from package.json, not lock file. - // As a result, some nodes might be missing. This should not cause any issues, we can just skip them. - if ( - dependency.target.startsWith('npm:') && - !projGraph.externalNodes[dependency.target] - ) - return; - - acc.push({ name: dependency.target, isTopLevel: areTopLevelDeps }); - const isInternalTarget = projGraph.nodes[dependency.target]; - if (!shallow && isInternalTarget) { - collectDependencies(dependency.target, projGraph, acc, shallow, false); - } - } else if (areTopLevelDeps && !existingEntry.isTopLevel) { - existingEntry.isTopLevel = true; - } - }); - return acc; -} - -function readTsConfigWithRemappedPaths( - tsConfig: string, - generatedTsConfigPath: string, - dependencies: DependentBuildableProjectNode[] -) { - const generatedTsConfig: any = { compilerOptions: {} }; - generatedTsConfig.extends = relative( - dirname(generatedTsConfigPath), - tsConfig - ); - generatedTsConfig.compilerOptions.paths = computeCompilerOptionsPaths( - tsConfig, - dependencies - ); - - if (process.env.NX_VERBOSE_LOGGING_PATH_MAPPINGS === 'true') { - output.log({ - title: 'TypeScript path mappings have been rewritten.', - }); - console.log( - JSON.stringify(generatedTsConfig.compilerOptions.paths, null, 2) - ); - } - return generatedTsConfig; -} - -function computeCompilerOptionsPaths( - tsConfig: string | ts.ParsedCommandLine, - dependencies: DependentBuildableProjectNode[] -) { - const paths = readPaths(tsConfig) || {}; - updatePaths(dependencies, paths); - return paths; -} - -function readPaths(tsConfig: string | ts.ParsedCommandLine) { - if (!tsModule) { - tsModule = ensureTypescript(); - } - try { - let config: ts.ParsedCommandLine; - if (typeof tsConfig === 'string') { - const configFile = tsModule.readConfigFile( - tsConfig, - tsModule.sys.readFile - ); - config = tsModule.parseJsonConfigFileContent( - configFile.config, - tsModule.sys, - dirname(tsConfig) - ); - } else { - config = tsConfig; - } - if (config.options?.paths) { - return config.options.paths; - } else { - return null; - } - } catch (e) { - return null; - } -} - -/** - * @deprecated This function will be removed from @nx/workspace in version 17. Prefer importing from @nx/js. - */ -export function createTmpTsConfig( - tsconfigPath: string, - workspaceRoot: string, - projectRoot: string, - dependencies: DependentBuildableProjectNode[] -) { - const tmpTsConfigPath = join( - workspaceRoot, - 'tmp', - projectRoot, - 'tsconfig.generated.json' - ); - const parsedTSConfig = readTsConfigWithRemappedPaths( - tsconfigPath, - tmpTsConfigPath, - dependencies - ); - process.on('exit', () => cleanupTmpTsConfigFile(tmpTsConfigPath)); - writeJsonFile(tmpTsConfigPath, parsedTSConfig); - return join(tmpTsConfigPath); -} - -function cleanupTmpTsConfigFile(tmpTsConfigPath) { - try { - if (tmpTsConfigPath) { - unlinkSync(tmpTsConfigPath); - } - } catch (e) {} -} - -/** - * @deprecated This function will be removed from @nx/workspace in version 17. Prefer importing from @nx/js. - */ -export function checkDependentProjectsHaveBeenBuilt( - root: string, - projectName: string, - targetName: string, - projectDependencies: DependentBuildableProjectNode[] -): boolean { - const missing = findMissingBuildDependencies( - root, - projectName, - targetName, - projectDependencies - ); - if (missing.length > 0) { - console.error(stripIndents` - It looks like all of ${projectName}'s dependencies have not been built yet: - ${missing.map((x) => ` - ${x.node.name}`).join('\n')} - - You might be missing a "targetDefaults" configuration in your root nx.json (https://nx.dev/reference/project-configuration#target-defaults), - or "dependsOn" configured in ${projectName}'s project.json (https://nx.dev/reference/project-configuration#dependson) - `); - return false; - } else { - return true; - } -} - -/** - * @deprecated This function will be removed from @nx/workspace in version 17. Prefer importing from @nx/js. - */ -export function findMissingBuildDependencies( - root: string, - projectName: string, - targetName: string, - projectDependencies: DependentBuildableProjectNode[] -): DependentBuildableProjectNode[] { - const depLibsToBuildFirst: DependentBuildableProjectNode[] = []; - - // verify whether all dependent libraries have been built - projectDependencies.forEach((dep) => { - if (dep.node.type !== 'lib') { - return; - } - - const paths = dep.outputs.map((p) => join(root, p)); - - if (!paths.some(directoryExists)) { - depLibsToBuildFirst.push(dep); - } - }); - - return depLibsToBuildFirst; -} - -/** - * @deprecated This function will be removed from @nx/workspace in version 17. Prefer importing from @nx/js. - */ -export function updatePaths( - dependencies: DependentBuildableProjectNode[], - paths: Record -) { - const pathsKeys = Object.keys(paths); - // For each registered dependency - dependencies.forEach((dep) => { - // If there are outputs - if (dep.outputs && dep.outputs.length > 0) { - // Directly map the dependency name to the output paths (dist/packages/..., etc.) - paths[dep.name] = dep.outputs; - - // check for secondary entrypoints - // For each registered path - for (const path of pathsKeys) { - const nestedName = `${dep.name}/`; - - // If the path points to the current dependency and is nested (/) - if (path.startsWith(nestedName)) { - const nestedPart = path.slice(nestedName.length); - - // Bind secondary endpoints for ng-packagr projects - let mappedPaths = dep.outputs.map( - (output) => `${output}/${nestedPart}` - ); - - // Get the dependency's package name - const { root } = (dep.node?.data || {}) as any; - if (root) { - // Update nested mappings to point to the dependency's output paths - mappedPaths = mappedPaths.concat( - paths[path].flatMap((path) => - dep.outputs.map((output) => path.replace(root, output)) - ) - ); - } - - paths[path] = mappedPaths; - } - } - } - }); -} - -/** - * Updates the peerDependencies section in the `dist/lib/xyz/package.json` with - * the proper dependency and version - * @deprecated This function will be removed from @nx/workspace in version 17. Prefer importing from @nx/js. - */ -export function updateBuildableProjectPackageJsonDependencies( - root: string, - projectName: string, - targetName: string, - configurationName: string, - node: ProjectGraphProjectNode, - dependencies: DependentBuildableProjectNode[], - typeOfDependency: 'dependencies' | 'peerDependencies' = 'dependencies' -) { - const outputs = getOutputsForTargetAndConfiguration( - { - overrides: {}, - target: { - project: projectName, - target: targetName, - configuration: configurationName, - }, - }, - node - ); - - const packageJsonPath = `${outputs[0]}/package.json`; - let packageJson; - let workspacePackageJson; - try { - packageJson = readJsonFile(packageJsonPath); - workspacePackageJson = readJsonFile(`${root}/package.json`); - } catch (e) { - // cannot find or invalid package.json - return; - } - - packageJson.dependencies = packageJson.dependencies || {}; - packageJson.peerDependencies = packageJson.peerDependencies || {}; - - let updatePackageJson = false; - dependencies.forEach((entry) => { - const packageName = isNpmProject(entry.node) - ? entry.node.data.packageName - : entry.name; - - if ( - !hasDependency(packageJson, 'dependencies', packageName) && - !hasDependency(packageJson, 'devDependencies', packageName) && - !hasDependency(packageJson, 'peerDependencies', packageName) - ) { - try { - let depVersion; - if (entry.node.type === 'lib') { - const outputs = getOutputsForTargetAndConfiguration( - { - overrides: {}, - target: { - project: projectName, - target: targetName, - configuration: configurationName, - }, - }, - entry.node - ); - - const depPackageJsonPath = join(root, outputs[0], 'package.json'); - depVersion = readJsonFile(depPackageJsonPath).version; - - packageJson[typeOfDependency][packageName] = depVersion; - } else if (isNpmProject(entry.node)) { - // If an npm dep is part of the workspace devDependencies, do not include it the library - if ( - !!workspacePackageJson.devDependencies?.[ - entry.node.data.packageName - ] - ) { - return; - } - - depVersion = entry.node.data.version; - - packageJson[typeOfDependency][entry.node.data.packageName] = - depVersion; - } - updatePackageJson = true; - } catch (e) { - // skip if cannot find package.json - } - } - }); - - if (updatePackageJson) { - writeJsonFile(packageJsonPath, packageJson); - } -} - -// verify whether the package.json already specifies the dep -function hasDependency(outputJson, depConfigName: string, packageName: string) { - if (outputJson[depConfigName]) { - return outputJson[depConfigName][packageName]; - } else { - return false; - } -}