diff --git a/packages/nx/migrations.json b/packages/nx/migrations.json index 35fa01082dd71..7ffd1c1b92bb3 100644 --- a/packages/nx/migrations.json +++ b/packages/nx/migrations.json @@ -17,6 +17,12 @@ "version": "14.2.0-beta.0", "description": "Remove default collection from configuration to switch to prompts for collection", "factory": "./src/migrations/update-14-2-0/remove-default-collection" + }, + "14-2-0-replace-relative-outputs-with-absolute": { + "cli": "nx", + "version": "14.2.0-beta.5", + "description": "Replace all ./ and ../ in outputs with absolute paths", + "factory": "./src/migrations/update-14-2-0/replace-all-relative-outputs-with-absolute" } } } diff --git a/packages/nx/src/command-line/run-many.ts b/packages/nx/src/command-line/run-many.ts index 670f4961b3fa8..f1043228ed28c 100644 --- a/packages/nx/src/command-line/run-many.ts +++ b/packages/nx/src/command-line/run-many.ts @@ -50,12 +50,14 @@ function projectsToRun( const allProjects = Object.values(projectGraph.nodes); const excludedProjects = new Set(nxArgs.exclude ?? []); if (nxArgs.all) { - return runnableForTarget(allProjects, nxArgs.target).filter( + const res = runnableForTarget(allProjects, nxArgs.target).filter( (proj) => !excludedProjects.has(proj.name) ); + res.sort((a, b) => a.name.localeCompare(b.name)); + return res; } checkForInvalidProjects(nxArgs, allProjects); - let selectedProjects = nxArgs.projects.map((name) => + const selectedProjects = nxArgs.projects.map((name) => allProjects.find((project) => project.name === name) ); return runnableForTarget(selectedProjects, nxArgs.target, true).filter( diff --git a/packages/nx/src/migrations/update-14-2-0/replace-all-relative-outputs-with-absolute.ts b/packages/nx/src/migrations/update-14-2-0/replace-all-relative-outputs-with-absolute.ts new file mode 100644 index 0000000000000..b7d7cd6793043 --- /dev/null +++ b/packages/nx/src/migrations/update-14-2-0/replace-all-relative-outputs-with-absolute.ts @@ -0,0 +1,20 @@ +import { Tree } from '../../generators/tree'; +import { + getProjects, + updateProjectConfiguration, +} from '../../generators/utils/project-configuration'; +import { isRelativePath } from 'nx/src/utils/fileutils'; +import { joinPathFragments } from 'nx/src/utils/path'; + +export default async function (tree: Tree) { + for (const [name, value] of getProjects(tree).entries()) { + for (const t of Object.values(value.targets)) { + if (t.outputs) { + t.outputs = t.outputs.map((o) => + isRelativePath(o) ? joinPathFragments(value.root, o) : o + ); + } + } + updateProjectConfiguration(tree, name, value); + } +} diff --git a/packages/nx/src/tasks-runner/create-task-graph.ts b/packages/nx/src/tasks-runner/create-task-graph.ts index 7f525f37f6bfe..8f2b59014c67a 100644 --- a/packages/nx/src/tasks-runner/create-task-graph.ts +++ b/packages/nx/src/tasks-runner/create-task-graph.ts @@ -1,6 +1,6 @@ import { ProjectGraph, ProjectGraphProjectNode } from '../config/project-graph'; import { TargetDependencyConfig } from '../config/workspace-json-project-json'; -import { getDependencyConfigs } from './utils'; +import { getDependencyConfigs, interpolate } from './utils'; import { projectHasTarget, projectHasTargetAndConfiguration, @@ -34,7 +34,6 @@ export class ProcessTasks { configuration ); const id = this.getId(projectName, target, resolvedConfiguration); - debugger; const task = this.createTask( id, this.projectGraph.nodes[projectName], @@ -219,23 +218,14 @@ export function createTaskGraph( function interpolateOverrides( args: T, projectName: string, - projectMetadata: any + project: any ): T { const interpolatedArgs: T = { ...args }; Object.entries(interpolatedArgs).forEach(([name, value]) => { - if (typeof value === 'string') { - const regex = /{project\.([^}]+)}/g; - interpolatedArgs[name] = value.replace(regex, (_, group: string) => { - if (group.includes('.')) { - throw new Error('Only top-level properties can be interpolated'); - } - - if (group === 'name') { - return projectName; - } - return projectMetadata[group]; - }); - } + interpolatedArgs[name] = + typeof value === 'string' + ? interpolate(value, { project: { ...project, name: projectName } }) + : value; }); return interpolatedArgs; } diff --git a/packages/nx/src/tasks-runner/utils.spec.ts b/packages/nx/src/tasks-runner/utils.spec.ts index 66965a5b6a8a6..d49a9c31c0e69 100644 --- a/packages/nx/src/tasks-runner/utils.spec.ts +++ b/packages/nx/src/tasks-runner/utils.spec.ts @@ -17,6 +17,7 @@ describe('utils', () => { name: 'myapp', type: 'app', data: { + root: '/myapp', targets: { build: { ...build, executor: '' }, }, @@ -37,6 +38,17 @@ describe('utils', () => { ).toEqual(['one', 'two']); }); + it('should return them', () => { + expect( + getOutputsForTargetAndConfiguration( + task, + getNode({ + outputs: ['one', 'two'], + }) + ) + ).toEqual(['one', 'two']); + }); + it('should support interpolation based on options', () => { expect( getOutputsForTargetAndConfiguration( @@ -51,6 +63,34 @@ describe('utils', () => { ).toEqual(['path/one', 'two']); }); + it('should support interpolating root', () => { + expect( + getOutputsForTargetAndConfiguration( + task, + getNode({ + outputs: ['{project.root}/sub', 'two'], + options: { + myVar: 'one', + }, + }) + ) + ).toEqual(['/myapp/sub', 'two']); + }); + + it('should support relative paths', () => { + expect( + getOutputsForTargetAndConfiguration( + task, + getNode({ + outputs: ['./sub', 'two'], + options: { + myVar: 'one', + }, + }) + ) + ).toEqual(['/myapp/sub', 'two']); + }); + it('should support nested interpolation based on options', () => { expect( getOutputsForTargetAndConfiguration( diff --git a/packages/nx/src/tasks-runner/utils.ts b/packages/nx/src/tasks-runner/utils.ts index d0790170df079..989c914f6abf3 100644 --- a/packages/nx/src/tasks-runner/utils.ts +++ b/packages/nx/src/tasks-runner/utils.ts @@ -13,6 +13,8 @@ import { getPackageManagerCommand } from '../utils/package-manager'; import { ProjectGraph, ProjectGraphProjectNode } from '../config/project-graph'; import { TargetDependencyConfig } from '../config/workspace-json-project-json'; import { workspaceRoot } from '../utils/workspace-root'; +import { isRelativePath } from 'nx/src/utils/fileutils'; +import { joinPathFragments } from 'nx/src/utils/path'; export function getCommandAsString(task: Task) { const execCommand = getPackageManagerCommand().exec; @@ -88,7 +90,15 @@ export function getOutputsForTargetAndConfiguration( if (targets?.outputs) { return targets.outputs - .map((output: string) => interpolateOutputs(output, options)) + .map((output: string) => { + const interpolated = interpolate(output, { + options, + project: { ...node.data, name: node.name }, + }); + return isRelativePath(interpolated) + ? joinPathFragments(node.data.root, interpolated) + : interpolated; + }) .filter((output) => !!output); } @@ -150,17 +160,16 @@ function stringShouldBeWrappedIntoQuotes(str: string) { return str.includes(' ') || str.includes('{') || str.includes('"'); } -function interpolateOutputs(template: string, data: any): string { +export function interpolate(template: string, data: any): string { return template.replace(/{([\s\S]+?)}/g, (match: string) => { let value = data; - let path = match.slice(1, -1).trim().split('.').slice(1); + let path = match.slice(1, -1).trim().split('.'); for (let idx = 0; idx < path.length; idx++) { if (!value[path[idx]]) { return; } value = value[path[idx]]; } - return value; }); }