feat(core): accept various task runner options from root of nx.json (#19243)

This commit is contained in:
Craigory Coppola 2023-10-05 12:02:04 -04:00 committed by GitHub
parent 6e60c7143c
commit 94748413a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 348 additions and 59 deletions

View File

@ -19,6 +19,7 @@ Nx.json configuration
### Properties
- [affected](../../devkit/documents/NxJsonConfiguration#affected): NxAffectedConfig
- [cacheDirectory](../../devkit/documents/NxJsonConfiguration#cachedirectory): string
- [cli](../../devkit/documents/NxJsonConfiguration#cli): Object
- [defaultProject](../../devkit/documents/NxJsonConfiguration#defaultproject): string
- [extends](../../devkit/documents/NxJsonConfiguration#extends): string
@ -27,11 +28,14 @@ Nx.json configuration
- [installation](../../devkit/documents/NxJsonConfiguration#installation): NxInstallationConfiguration
- [namedInputs](../../devkit/documents/NxJsonConfiguration#namedinputs): Object
- [npmScope](../../devkit/documents/NxJsonConfiguration#npmscope): string
- [nxCloudAccessToken](../../devkit/documents/NxJsonConfiguration#nxcloudaccesstoken): string
- [parallel](../../devkit/documents/NxJsonConfiguration#parallel): number
- [plugins](../../devkit/documents/NxJsonConfiguration#plugins): string[]
- [pluginsConfig](../../devkit/documents/NxJsonConfiguration#pluginsconfig): Record<string, unknown>
- [release](../../devkit/documents/NxJsonConfiguration#release): NxReleaseConfiguration
- [targetDefaults](../../devkit/documents/NxJsonConfiguration#targetdefaults): TargetDefaults
- [tasksRunnerOptions](../../devkit/documents/NxJsonConfiguration#tasksrunneroptions): Object
- [useDaemonProcess](../../devkit/documents/NxJsonConfiguration#usedaemonprocess): boolean
- [workspaceLayout](../../devkit/documents/NxJsonConfiguration#workspacelayout): Object
## Properties
@ -44,6 +48,14 @@ Default options for `nx affected`
---
### cacheDirectory
`Optional` **cacheDirectory**: `string`
Changes the directory used by Nx to store its cache.
---
### cli
`Optional` **cli**: `Object`
@ -148,6 +160,23 @@ NPM Scope that the workspace uses
---
### nxCloudAccessToken
`Optional` **nxCloudAccessToken**: `string`
If specified Nx will use nx-cloud by default with the given token.
To use a different runner that accepts an access token, define it in [tasksRunnerOptions](../../devkit/documents/Workspace#tasksrunneroptions)
---
### parallel
`Optional` **parallel**: `number`
Specifies how many tasks can be run in parallel.
---
### plugins
`Optional` **plugins**: `string`[]
@ -174,7 +203,7 @@ Configuration for Nx Plugins
### targetDefaults
`Optional` **targetDefaults**: `TargetDefaults`
`Optional` **targetDefaults**: [`TargetDefaults`](../../devkit/documents/TargetDefaults)
Dependencies between different target names across all projects
@ -192,6 +221,14 @@ Available Task Runners
---
### useDaemonProcess
`Optional` **useDaemonProcess**: `boolean`
Set this to false to disable the daemon.
---
### workspaceLayout
`Optional` **workspaceLayout**: `Object`

View File

@ -83,6 +83,7 @@ It only uses language primitives and immutable objects
- [RawProjectGraphDependency](../../devkit/documents/RawProjectGraphDependency)
- [StaticDependency](../../devkit/documents/StaticDependency)
- [StringChange](../../devkit/documents/StringChange)
- [TargetDefaults](../../devkit/documents/TargetDefaults)
- [TaskGraphExecutor](../../devkit/documents/TaskGraphExecutor)
- [WorkspaceConfiguration](../../devkit/documents/WorkspaceConfiguration)
- [WorkspaceJsonConfiguration](../../devkit/documents/WorkspaceJsonConfiguration)

View File

@ -0,0 +1,3 @@
# Type alias: TargetDefaults
Ƭ **TargetDefaults**: `Record`<`string`, `Partial`<[`TargetConfiguration`](../../devkit/documents/TargetConfiguration)\> & { `cache?`: `boolean` }\>

View File

@ -6,6 +6,7 @@ A representation of the invocation of an Executor
### Properties
- [cache](../../devkit/documents/Task#cache): boolean
- [endTime](../../devkit/documents/Task#endtime): number
- [hash](../../devkit/documents/Task#hash): string
- [hashDetails](../../devkit/documents/Task#hashdetails): Object
@ -17,6 +18,14 @@ A representation of the invocation of an Executor
## Properties
### cache
`Optional` **cache**: `boolean`
Determines if a given task should be cacheable.
---
### endTime
`Optional` **endTime**: `number`

View File

@ -17,6 +17,7 @@ use ProjectsConfigurations or NxJsonConfiguration
### Properties
- [affected](../../devkit/documents/Workspace#affected): NxAffectedConfig
- [cacheDirectory](../../devkit/documents/Workspace#cachedirectory): string
- [cli](../../devkit/documents/Workspace#cli): Object
- [defaultProject](../../devkit/documents/Workspace#defaultproject): string
- [extends](../../devkit/documents/Workspace#extends): string
@ -25,12 +26,15 @@ use ProjectsConfigurations or NxJsonConfiguration
- [installation](../../devkit/documents/Workspace#installation): NxInstallationConfiguration
- [namedInputs](../../devkit/documents/Workspace#namedinputs): Object
- [npmScope](../../devkit/documents/Workspace#npmscope): string
- [nxCloudAccessToken](../../devkit/documents/Workspace#nxcloudaccesstoken): string
- [parallel](../../devkit/documents/Workspace#parallel): number
- [plugins](../../devkit/documents/Workspace#plugins): string[]
- [pluginsConfig](../../devkit/documents/Workspace#pluginsconfig): Record&lt;string, unknown&gt;
- [projects](../../devkit/documents/Workspace#projects): Record&lt;string, ProjectConfiguration&gt;
- [release](../../devkit/documents/Workspace#release): NxReleaseConfiguration
- [targetDefaults](../../devkit/documents/Workspace#targetdefaults): TargetDefaults
- [tasksRunnerOptions](../../devkit/documents/Workspace#tasksrunneroptions): Object
- [useDaemonProcess](../../devkit/documents/Workspace#usedaemonprocess): boolean
- [version](../../devkit/documents/Workspace#version): number
- [workspaceLayout](../../devkit/documents/Workspace#workspacelayout): Object
@ -48,6 +52,18 @@ Default options for `nx affected`
---
### cacheDirectory
`Optional` **cacheDirectory**: `string`
Changes the directory used by Nx to store its cache.
#### Inherited from
[NxJsonConfiguration](../../devkit/documents/NxJsonConfiguration).[cacheDirectory](../../devkit/documents/NxJsonConfiguration#cachedirectory)
---
### cli
`Optional` **cli**: `Object`
@ -184,6 +200,31 @@ NPM Scope that the workspace uses
---
### nxCloudAccessToken
`Optional` **nxCloudAccessToken**: `string`
If specified Nx will use nx-cloud by default with the given token.
To use a different runner that accepts an access token, define it in [tasksRunnerOptions](../../devkit/documents/Workspace#tasksrunneroptions)
#### Inherited from
[NxJsonConfiguration](../../devkit/documents/NxJsonConfiguration).[nxCloudAccessToken](../../devkit/documents/NxJsonConfiguration#nxcloudaccesstoken)
---
### parallel
`Optional` **parallel**: `number`
Specifies how many tasks can be run in parallel.
#### Inherited from
[NxJsonConfiguration](../../devkit/documents/NxJsonConfiguration).[parallel](../../devkit/documents/NxJsonConfiguration#parallel)
---
### plugins
`Optional` **plugins**: `string`[]
@ -234,7 +275,7 @@ Projects' projects
### targetDefaults
`Optional` **targetDefaults**: `TargetDefaults`
`Optional` **targetDefaults**: [`TargetDefaults`](../../devkit/documents/TargetDefaults)
Dependencies between different target names across all projects
@ -260,6 +301,18 @@ Available Task Runners
---
### useDaemonProcess
`Optional` **useDaemonProcess**: `boolean`
Set this to false to disable the daemon.
#### Inherited from
[NxJsonConfiguration](../../devkit/documents/NxJsonConfiguration).[useDaemonProcess](../../devkit/documents/NxJsonConfiguration#usedaemonprocess)
---
### version
**version**: `number`

View File

@ -83,6 +83,7 @@ It only uses language primitives and immutable objects
- [RawProjectGraphDependency](../../devkit/documents/RawProjectGraphDependency)
- [StaticDependency](../../devkit/documents/StaticDependency)
- [StringChange](../../devkit/documents/StringChange)
- [TargetDefaults](../../devkit/documents/TargetDefaults)
- [TaskGraphExecutor](../../devkit/documents/TaskGraphExecutor)
- [WorkspaceConfiguration](../../devkit/documents/WorkspaceConfiguration)
- [WorkspaceJsonConfiguration](../../devkit/documents/WorkspaceJsonConfiguration)

View File

@ -81,6 +81,22 @@
"defaultProject": {
"type": "string",
"description": "Default project. When project isn't provided, the default project will be used."
},
"accessToken": {
"type": "string",
"description": "The access token to use for nx-cloud. If set, the default tasks runner will be nx-cloud."
},
"parallel": {
"type": "number",
"description": "Specifies how many tasks are ran in parallel by Nx for the default tasks runner."
},
"cacheDirectory": {
"type": "string",
"description": "Specifies the default location of the cache directory."
},
"useDaemonProcess": {
"type": "boolean",
"description": "Specifies whether the daemon should be used for the default tasks runner."
}
},
"definitions": {

View File

@ -35,7 +35,7 @@ export async function connectToNxCloudIfExplicitlyAsked(opts: {
export async function connectToNxCloudCommand(
promptOverride?: string
): Promise<boolean> {
if (isNxCloudUsed()) {
if (isNxCloudUsed(readNxJson())) {
output.log({
title: '✅ This workspace is already connected to Nx Cloud.',
bodyLines: [

View File

@ -3,9 +3,10 @@ import { execSync } from 'child_process';
import { isNxCloudUsed } from '../../utils/nx-cloud-utils';
import { output } from '../../utils/output';
import { runNxSync } from '../../utils/child-process';
import { readNxJson } from '../../config/nx-json';
export async function viewLogs(): Promise<number> {
const cloudUsed = isNxCloudUsed();
const cloudUsed = isNxCloudUsed(readNxJson());
if (cloudUsed) {
output.error({
title: 'Your workspace is already connected to Nx Cloud',

View File

@ -1203,16 +1203,17 @@ async function generateMigrationsJsonAndUpdatePackageJson(
let originalPackageJson = existsSync(rootPkgJsonPath)
? readJsonFile<PackageJson>(rootPkgJsonPath)
: null;
const originalNxInstallation = readNxJson().installation;
const originalNxJson = readNxJson();
const from =
originalNxInstallation?.version ?? readNxVersion(originalPackageJson);
originalNxJson.installation?.version ??
readNxVersion(originalPackageJson);
try {
if (
['nx', '@nrwl/workspace'].includes(opts.targetPackage) &&
(await isMigratingToNewMajor(from, opts.targetVersion)) &&
!isCI() &&
!isNxCloudUsed()
!isNxCloudUsed(originalNxJson)
) {
const useCloud = await connectToNxCloudCommand(
messages.getPromptMessage('nxCloudMigration')
@ -1237,7 +1238,7 @@ async function generateMigrationsJsonAndUpdatePackageJson(
const migrator = new Migrator({
packageJson: originalPackageJson,
nxInstallation: originalNxInstallation,
nxInstallation: originalNxJson.installation,
getInstalledPackageVersion: createInstalledPackageVersionsResolver(root),
fetch: createFetcher(),
from: opts.from,

View File

@ -25,7 +25,16 @@ export interface NxAffectedConfig {
defaultBase?: string;
}
export type TargetDefaults = Record<string, Partial<TargetConfiguration>>;
export type TargetDefaults = Record<
string,
Partial<TargetConfiguration> & {
/**
* Determines if Nx is able to cache a given target.
* Currently only supported in `targetDefaults`.
*/
cache?: boolean;
}
>;
export type TargetDependencies = Record<
string,
@ -191,6 +200,27 @@ export interface NxJsonConfiguration<T = '*' | string[]> {
* **ALPHA**: Configuration for `nx release` (versioning and publishing of applications and libraries)
*/
release?: NxReleaseConfiguration;
/**
* If specified Nx will use nx-cloud by default with the given token.
* To use a different runner that accepts an access token, define it in {@link tasksRunnerOptions}
*/
nxCloudAccessToken?: string;
/**
* Specifies how many tasks can be run in parallel.
*/
parallel?: number;
/**
* Changes the directory used by Nx to store its cache.
*/
cacheDirectory?: string;
/**
* Set this to false to disable the daemon.
*/
useDaemonProcess?: boolean;
}
export function readNxJson(root: string = workspaceRoot): NxJsonConfiguration {

View File

@ -66,6 +66,11 @@ export interface Task {
* Unix timestamp of when a Batch Task ends
**/
endTime?: number;
/**
* Determines if a given task should be cacheable.
*/
cache?: boolean;
}
/**

View File

@ -64,7 +64,9 @@ export class DaemonClient {
enabled() {
if (this._enabled === undefined) {
// TODO(v18): Add migration to move it out of existing configs and remove the ?? here.
const useDaemonProcessOption =
this.nxJson.useDaemonProcess ??
this.nxJson.tasksRunnerOptions?.['default']?.options?.useDaemonProcess;
const env = process.env.NX_DAEMON;

View File

@ -69,6 +69,7 @@ export type {
ImplicitDependencyEntry,
ImplicitJsonSubsetDependency,
NxJsonConfiguration,
TargetDefaults,
NxAffectedConfig,
} from './config/nx-json';

View File

@ -40,6 +40,10 @@ export function updateWorkspaceConfiguration(
extends: ext,
installation,
release,
nxCloudAccessToken,
cacheDirectory,
parallel,
useDaemonProcess,
} = workspaceConfig;
const nxJson: Required<NxJsonConfiguration> = {
@ -58,6 +62,10 @@ export function updateWorkspaceConfiguration(
extends: ext,
installation,
release,
nxCloudAccessToken,
cacheDirectory,
parallel,
useDaemonProcess,
};
updateNxJson(tree, nxJson);

View File

@ -334,6 +334,8 @@ export class ProcessTasks {
target: qualifiedTarget,
projectRoot: project.data.root,
overrides: interpolateOverrides(overrides, project.name, project.data),
// TODO(v18): Remove cast here after typing is moved back onto TargetConfiguration
cache: (project.data.targets[target] as any).cache,
};
}

View File

@ -1,10 +1,11 @@
import { readNxJson } from '../../config/nx-json';
import { isNxCloudUsed } from '../../utils/nx-cloud-utils';
import { output } from '../../utils/output';
const VIEW_LOGS_MESSAGE = `Hint: Try "nx view-logs" to get structured, searchable errors logs in your browser.`;
export function viewLogsFooterRows(failedTasks: number) {
if (failedTasks >= 2 && !isNxCloudUsed()) {
if (failedTasks >= 2 && !isNxCloudUsed(readNxJson())) {
return [``, output.dim(`${output.X_PADDING} ${VIEW_LOGS_MESSAGE}`)];
} else {
return [];

View File

@ -1,6 +1,7 @@
import { TasksRunner } from './tasks-runner';
import { getRunner } from './run-command';
import { NxJsonConfiguration } from '../config/nx-json';
import { join } from 'path';
describe('getRunner', () => {
let nxJson: NxJsonConfiguration;
@ -73,4 +74,54 @@ describe('getRunner', () => {
expect(tasksRunner).toEqual(mockRunner);
});
it('uses default runner when no tasksRunnerOptions are present', () => {
jest.mock(join(__dirname, './default-tasks-runner.ts'), () => mockRunner);
const { tasksRunner } = getRunner({}, {});
expect(tasksRunner).toEqual(mockRunner);
});
it('uses nx-cloud when no tasksRunnerOptions are present and accessToken is specified', () => {
jest.mock('nx-cloud', () => mockRunner);
const { tasksRunner } = getRunner(
{},
{
nxCloudAccessToken: 'XXXX-XXX-XXXX',
}
);
expect(tasksRunner).toEqual(mockRunner);
});
it('reads options from base properties if no runner options provided', () => {
jest.mock(join(__dirname, './default-tasks-runner.ts'), () => mockRunner);
const { runnerOptions } = getRunner(
{},
{
cacheDirectory: '.nx/cache',
parallel: 3,
useDaemonProcess: false,
targetDefaults: {
build: {
cache: true,
},
},
}
);
expect(runnerOptions).toMatchInlineSnapshot(`
{
"cacheDirectory": ".nx/cache",
"cacheableOperations": [
"build",
],
"parallel": 3,
"useDaemonProcess": false,
}
`);
});
});

View File

@ -390,7 +390,7 @@ function shouldUseDynamicLifeCycle(
if (isCI()) return false;
if (outputStyle === 'static' || outputStyle === 'stream') return false;
return !tasks.find((t) => shouldStreamOutput(t, null, options));
return !tasks.find((t) => shouldStreamOutput(t, null));
}
export function getRunner(
@ -402,35 +402,89 @@ export function getRunner(
} {
let runner = nxArgs.runner;
runner = runner || 'default';
if (!nxJson.tasksRunnerOptions) {
throw new Error(`Could not find any runner configurations in nx.json`);
}
if (nxJson.tasksRunnerOptions[runner]) {
let modulePath: string = nxJson.tasksRunnerOptions[runner].runner;
let tasksRunner;
if (modulePath) {
if (isRelativePath(modulePath)) {
modulePath = join(workspaceRoot, modulePath);
}
tasksRunner = require(modulePath);
// to support both babel and ts formats
if (tasksRunner.default) {
tasksRunner = tasksRunner.default;
}
} else {
tasksRunner = require('./default-tasks-runner').defaultTasksRunner;
}
return {
tasksRunner,
runnerOptions: {
...nxJson.tasksRunnerOptions[runner].options,
...nxArgs,
},
};
} else {
if (runner !== 'default' && !nxJson.tasksRunnerOptions?.[runner]) {
throw new Error(`Could not find runner configuration for ${runner}`);
}
const modulePath: string = getTasksRunnerPath(runner, nxJson);
let tasksRunner = require(modulePath);
// to support both babel and ts formats
if (tasksRunner.default) {
tasksRunner = tasksRunner.default;
}
return {
tasksRunner,
runnerOptions: getRunnerOptions(
runner,
nxJson,
nxArgs,
modulePath === 'nx-cloud'
),
};
}
function getTasksRunnerPath(
runner: string,
nxJson: NxJsonConfiguration<string[] | '*'>
) {
let modulePath: string = nxJson.tasksRunnerOptions?.[runner]?.runner;
if (modulePath) {
if (isRelativePath(modulePath)) {
return join(workspaceRoot, modulePath);
}
return modulePath;
}
const isCloudRunner =
// No tasksRunnerOptions for given --runner
nxJson.nxCloudAccessToken ||
// No runner prop in tasks runner options, check if access token is set.
nxJson.tasksRunnerOptions?.[runner]?.options?.accessToken;
return isCloudRunner ? 'nx-cloud' : require.resolve('./default-tasks-runner');
}
function getRunnerOptions(
runner: string,
nxJson: NxJsonConfiguration<string[] | '*'>,
nxArgs: NxArgs,
isCloudDefault: boolean
): any {
const defaultCacheableOperations = [];
for (const key in nxJson.targetDefaults) {
if (nxJson.targetDefaults[key].cache) {
defaultCacheableOperations.push(key);
}
}
const result = {
...nxJson.tasksRunnerOptions?.[runner]?.options,
...nxArgs,
};
if (nxJson.nxCloudAccessToken && isCloudDefault) {
result.accessToken ??= nxJson.nxCloudAccessToken;
}
if (nxJson.parallel) {
result.parallel ??= nxJson.parallel;
}
if (nxJson.cacheDirectory) {
result.cacheDirectory ??= nxJson.cacheDirectory;
}
if (defaultCacheableOperations.length) {
result.cacheableOperations ??= defaultCacheableOperations;
}
if (nxJson.useDaemonProcess !== undefined) {
result.useDaemonProcess ??= nxJson.useDaemonProcess;
}
return result;
}

View File

@ -282,11 +282,7 @@ export class TaskOrchestrator {
try {
// obtain metadata
const temporaryOutputPath = this.cache.temporaryOutputPath(task);
const streamOutput = shouldStreamOutput(
task,
this.initiatingProject,
this.options
);
const streamOutput = shouldStreamOutput(task, this.initiatingProject);
const pipeOutput = await this.pipeOutputCapture(task);

View File

@ -328,11 +328,7 @@ export function getSerializedArgsForTask(task: Task, isVerbose: boolean) {
export function shouldStreamOutput(
task: Task,
initiatingProject: string | null,
options: {
cacheableOperations?: string[] | null;
cacheableTargets?: string[] | null;
}
initiatingProject: string | null
): boolean {
if (process.env.NX_STREAM_OUTPUT === 'true') return true;
if (longRunningTask(task)) return true;
@ -347,6 +343,14 @@ export function isCacheableTask(
cacheableTargets?: string[] | null;
}
): boolean {
if (
task.cache !== undefined &&
process.env.NX_ALLOW_PROJECT_LEVEL_CACHE === 'true' &&
!longRunningTask(task)
) {
return task.cache;
}
const cacheable = options.cacheableOperations || options.cacheableTargets;
return (
cacheable &&

View File

@ -7,7 +7,10 @@ import { workspaceRoot } from './workspace-root';
function readCacheDirectoryProperty(root: string): string | undefined {
try {
const nxJson = readJsonFile<NxJsonConfiguration>(join(root, 'nx.json'));
return nxJson.tasksRunnerOptions.default.options.cacheDirectory;
return (
nxJson.cacheDirectory ??
nxJson.tasksRunnerOptions.default.options.cacheDirectory
);
} catch {
return undefined;
}

View File

@ -1,19 +1,29 @@
import { readNxJson } from '../config/configuration';
import { NxJsonConfiguration, readNxJson } from '../config/nx-json';
export function isNxCloudUsed() {
const nxJson = readNxJson();
return Object.values(nxJson.tasksRunnerOptions).find(
(r) => r.runner == '@nrwl/nx-cloud' || r.runner == 'nx-cloud'
export function isNxCloudUsed(nxJson: NxJsonConfiguration) {
return (
!!nxJson.nxCloudAccessToken ||
Object.values(nxJson.tasksRunnerOptions ?? {}).find(
(r) => r.runner == '@nrwl/nx-cloud' || r.runner == 'nx-cloud'
)
);
}
export function getNxCloudUrl(): string {
const taskRunner = isNxCloudUsed();
const taskRunner = isNxCloudUsed(readNxJson());
if (!taskRunner) throw new Error('nx-cloud runner not find in nx.json');
return taskRunner.options.url || 'https://nx.app';
return (
(typeof taskRunner === 'object' ? taskRunner.options.url : null) ??
'https://nx.app'
);
}
export function getNxCloudToken(): string {
const taskRunner = isNxCloudUsed();
const nxJson = readNxJson();
const taskRunner = isNxCloudUsed(nxJson);
if (!taskRunner) throw new Error('nx-cloud runner not find in nx.json');
return taskRunner.options.accessToken;
return typeof taskRunner === 'object'
? taskRunner.options.accessToken
: nxJson.nxCloudAccessToken;
}