diff --git a/docs/generated/devkit/index.md b/docs/generated/devkit/index.md index 584a32d423e01..757bdb1949c00 100644 --- a/docs/generated/devkit/index.md +++ b/docs/generated/devkit/index.md @@ -434,7 +434,13 @@ A plugin for Nx ### TargetConfiguration -• **TargetConfiguration**: `Object` +• **TargetConfiguration**<`T`\>: `Object` + +#### Type parameters + +| Name | Type | +| :--- | :---- | +| `T` | `any` | --- diff --git a/docs/generated/packages/devkit.json b/docs/generated/packages/devkit.json index a4b5b9da2a79f..ab97301578e36 100644 --- a/docs/generated/packages/devkit.json +++ b/docs/generated/packages/devkit.json @@ -9,7 +9,7 @@ "id": "index", "name": "Overview", "file": "generated/devkit/index", - "content": "# Module: index\n\nThe Nx Devkit is the underlying technology used to customize Nx to support\ndifferent technologies and custom use-cases. It contains many utility\nfunctions for reading and writing files, updating configuration,\nworking with Abstract Syntax Trees(ASTs), and more.\n\nAs with most things in Nx, the core of Nx Devkit is very simple.\nIt only uses language primitives and immutable objects\n(the tree being the only exception).\n\n## Table of contents\n\n### Project Graph Enumerations\n\n- [DependencyType](../../devkit/index#dependencytype)\n\n### Utils Enumerations\n\n- [ChangeType](../../devkit/index#changetype)\n\n### Project Graph Classes\n\n- [ProjectGraphBuilder](../../devkit/index#projectgraphbuilder)\n\n### Utils Classes\n\n- [Hasher](../../devkit/index#hasher)\n\n### Workspace Classes\n\n- [Workspaces](../../devkit/index#workspaces)\n\n### Commands Interfaces\n\n- [Target](../../devkit/index#target)\n\n### Other Interfaces\n\n- [NxPlugin](../../devkit/index#nxplugin)\n\n### Project Graph Interfaces\n\n- [FileData](../../devkit/index#filedata)\n- [ProjectFileMap](../../devkit/index#projectfilemap)\n- [ProjectGraph](../../devkit/index#projectgraph)\n- [ProjectGraphDependency](../../devkit/index#projectgraphdependency)\n- [ProjectGraphExternalNode](../../devkit/index#projectgraphexternalnode)\n- [ProjectGraphProcessorContext](../../devkit/index#projectgraphprocessorcontext)\n- [ProjectGraphProjectNode](../../devkit/index#projectgraphprojectnode)\n- [ProjectGraphV4](../../devkit/index#projectgraphv4)\n\n### Tree Interfaces\n\n- [FileChange](../../devkit/index#filechange)\n- [Tree](../../devkit/index#tree)\n\n### Utils Interfaces\n\n- [DefaultTasksRunnerOptions](../../devkit/index#defaulttasksrunneroptions)\n- [Hash](../../devkit/index#hash)\n- [JsonParseOptions](../../devkit/index#jsonparseoptions)\n- [JsonSerializeOptions](../../devkit/index#jsonserializeoptions)\n- [RemoteCache](../../devkit/index#remotecache)\n- [StringDeletion](../../devkit/index#stringdeletion)\n- [StringInsertion](../../devkit/index#stringinsertion)\n\n### Workspace Interfaces\n\n- [ExecutorContext](../../devkit/index#executorcontext)\n- [ExecutorsJson](../../devkit/index#executorsjson)\n- [GeneratorsJson](../../devkit/index#generatorsjson)\n- [HasherContext](../../devkit/index#hashercontext)\n- [ImplicitJsonSubsetDependency](../../devkit/index#implicitjsonsubsetdependency)\n- [MigrationsJson](../../devkit/index#migrationsjson)\n- [NxAffectedConfig](../../devkit/index#nxaffectedconfig)\n- [NxJsonConfiguration](../../devkit/index#nxjsonconfiguration)\n- [ProjectConfiguration](../../devkit/index#projectconfiguration)\n- [ProjectsConfigurations](../../devkit/index#projectsconfigurations)\n- [TargetConfiguration](../../devkit/index#targetconfiguration)\n- [TargetDependencyConfig](../../devkit/index#targetdependencyconfig)\n- [Task](../../devkit/index#task)\n- [TaskGraph](../../devkit/index#taskgraph)\n- [Workspace](../../devkit/index#workspace)\n\n### Generators Type aliases\n\n- [WorkspaceConfiguration](../../devkit/index#workspaceconfiguration)\n\n### Other Type aliases\n\n- [ProjectTargetConfigurator](../../devkit/index#projecttargetconfigurator)\n\n### Package Manager Type aliases\n\n- [PackageManager](../../devkit/index#packagemanager)\n\n### Project Graph Type aliases\n\n- [ProjectGraphNode](../../devkit/index#projectgraphnode)\n\n### Utils Type aliases\n\n- [StringChange](../../devkit/index#stringchange)\n\n### Workspace Type aliases\n\n- [CustomHasher](../../devkit/index#customhasher)\n- [Executor](../../devkit/index#executor)\n- [Generator](../../devkit/index#generator)\n- [GeneratorCallback](../../devkit/index#generatorcallback)\n- [ImplicitDependencyEntry](../../devkit/index#implicitdependencyentry)\n- [ProjectType](../../devkit/index#projecttype)\n- [TaskGraphExecutor](../../devkit/index#taskgraphexecutor)\n- [WorkspaceJsonConfiguration](../../devkit/index#workspacejsonconfiguration)\n\n### Logger Variables\n\n- [logger](../../devkit/index#logger)\n\n### Utils Variables\n\n- [appRootPath](../../devkit/index#approotpath)\n- [cacheDir](../../devkit/index#cachedir)\n- [output](../../devkit/index#output)\n- [workspaceRoot](../../devkit/index#workspaceroot)\n\n### Functions\n\n- [addDependenciesToPackageJson](../../devkit/index#adddependenciestopackagejson)\n- [addProjectConfiguration](../../devkit/index#addprojectconfiguration)\n- [applyChangesToString](../../devkit/index#applychangestostring)\n- [convertNxExecutor](../../devkit/index#convertnxexecutor)\n- [convertNxGenerator](../../devkit/index#convertnxgenerator)\n- [createProjectGraphAsync](../../devkit/index#createprojectgraphasync)\n- [defaultTasksRunner](../../devkit/index#defaulttasksrunner)\n- [detectPackageManager](../../devkit/index#detectpackagemanager)\n- [formatFiles](../../devkit/index#formatfiles)\n- [generateFiles](../../devkit/index#generatefiles)\n- [getOutputsForTargetAndConfiguration](../../devkit/index#getoutputsfortargetandconfiguration)\n- [getPackageManagerCommand](../../devkit/index#getpackagemanagercommand)\n- [getPackageManagerVersion](../../devkit/index#getpackagemanagerversion)\n- [getProjects](../../devkit/index#getprojects)\n- [getWorkspaceLayout](../../devkit/index#getworkspacelayout)\n- [getWorkspacePath](../../devkit/index#getworkspacepath)\n- [installPackagesTask](../../devkit/index#installpackagestask)\n- [isStandaloneProject](../../devkit/index#isstandaloneproject)\n- [joinPathFragments](../../devkit/index#joinpathfragments)\n- [moveFilesToNewDirectory](../../devkit/index#movefilestonewdirectory)\n- [names](../../devkit/index#names)\n- [normalizePath](../../devkit/index#normalizepath)\n- [offsetFromRoot](../../devkit/index#offsetfromroot)\n- [parseJson](../../devkit/index#parsejson)\n- [parseTargetString](../../devkit/index#parsetargetstring)\n- [readAllWorkspaceConfiguration](../../devkit/index#readallworkspaceconfiguration)\n- [readCachedProjectGraph](../../devkit/index#readcachedprojectgraph)\n- [readJson](../../devkit/index#readjson)\n- [readJsonFile](../../devkit/index#readjsonfile)\n- [readNxJson](../../devkit/index#readnxjson)\n- [readProjectConfiguration](../../devkit/index#readprojectconfiguration)\n- [readTargetOptions](../../devkit/index#readtargetoptions)\n- [readWorkspaceConfiguration](../../devkit/index#readworkspaceconfiguration)\n- [removeDependenciesFromPackageJson](../../devkit/index#removedependenciesfrompackagejson)\n- [removeProjectConfiguration](../../devkit/index#removeprojectconfiguration)\n- [reverse](../../devkit/index#reverse)\n- [runExecutor](../../devkit/index#runexecutor)\n- [serializeJson](../../devkit/index#serializejson)\n- [stripIndents](../../devkit/index#stripindents)\n- [stripJsonComments](../../devkit/index#stripjsoncomments)\n- [targetToTargetString](../../devkit/index#targettotargetstring)\n- [toJS](../../devkit/index#tojs)\n- [updateJson](../../devkit/index#updatejson)\n- [updateProjectConfiguration](../../devkit/index#updateprojectconfiguration)\n- [updateTsConfigsToJs](../../devkit/index#updatetsconfigstojs)\n- [updateWorkspaceConfiguration](../../devkit/index#updateworkspaceconfiguration)\n- [visitNotIgnoredFiles](../../devkit/index#visitnotignoredfiles)\n- [workspaceLayout](../../devkit/index#workspacelayout)\n- [writeJson](../../devkit/index#writejson)\n- [writeJsonFile](../../devkit/index#writejsonfile)\n\n## Project Graph Enumerations\n\n### DependencyType\n\n• **DependencyType**: `Object`\n\n---\n\n## Utils Enumerations\n\n### ChangeType\n\n• **ChangeType**: `Object`\n\n## Project Graph Classes\n\n### ProjectGraphBuilder\n\n• **ProjectGraphBuilder**: `Object`\n\n---\n\n## Utils Classes\n\n### Hasher\n\n• **Hasher**: `Object`\n\n---\n\n## Workspace Classes\n\n### Workspaces\n\n• **Workspaces**: `Object`\n\n## Commands Interfaces\n\n### Target\n\n• **Target**: `Object`\n\n---\n\n## Other Interfaces\n\n### NxPlugin\n\n• **NxPlugin**: `Object`\n\nA plugin for Nx\n\n---\n\n## Project Graph Interfaces\n\n### FileData\n\n• **FileData**: `Object`\n\n---\n\n### ProjectFileMap\n\n• **ProjectFileMap**: `Object`\n\n---\n\n### ProjectGraph\n\n• **ProjectGraph**<`T`\\>: `Object`\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n---\n\n### ProjectGraphDependency\n\n• **ProjectGraphDependency**: `Object`\n\n---\n\n### ProjectGraphExternalNode\n\n• **ProjectGraphExternalNode**: `Object`\n\n---\n\n### ProjectGraphProcessorContext\n\n• **ProjectGraphProcessorContext**: `Object`\n\n---\n\n### ProjectGraphProjectNode\n\n• **ProjectGraphProjectNode**<`T`\\>: `Object`\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n---\n\n### ProjectGraphV4\n\n• **ProjectGraphV4**<`T`\\>: `Object`\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n---\n\n## Tree Interfaces\n\n### FileChange\n\n• **FileChange**: `Object`\n\n---\n\n### Tree\n\n• **Tree**: `Object`\n\n---\n\n## Utils Interfaces\n\n### DefaultTasksRunnerOptions\n\n• **DefaultTasksRunnerOptions**: `Object`\n\n---\n\n### Hash\n\n• **Hash**: `Object`\n\n---\n\n### JsonParseOptions\n\n• **JsonParseOptions**: `Object`\n\n---\n\n### JsonSerializeOptions\n\n• **JsonSerializeOptions**: `Object`\n\n---\n\n### RemoteCache\n\n• **RemoteCache**: `Object`\n\n---\n\n### StringDeletion\n\n• **StringDeletion**: `Object`\n\n---\n\n### StringInsertion\n\n• **StringInsertion**: `Object`\n\n---\n\n## Workspace Interfaces\n\n### ExecutorContext\n\n• **ExecutorContext**: `Object`\n\n---\n\n### ExecutorsJson\n\n• **ExecutorsJson**: `Object`\n\n---\n\n### GeneratorsJson\n\n• **GeneratorsJson**: `Object`\n\n---\n\n### HasherContext\n\n• **HasherContext**: `Object`\n\n---\n\n### ImplicitJsonSubsetDependency\n\n• **ImplicitJsonSubsetDependency**<`T`\\>: `Object`\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :------------------ |\n| `T` | `\"*\"` \\| `string`[] |\n\n---\n\n### MigrationsJson\n\n• **MigrationsJson**: `Object`\n\n---\n\n### NxAffectedConfig\n\n• **NxAffectedConfig**: `Object`\n\n---\n\n### NxJsonConfiguration\n\n• **NxJsonConfiguration**<`T`\\>: `Object`\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :------------------ |\n| `T` | `\"*\"` \\| `string`[] |\n\n---\n\n### ProjectConfiguration\n\n• **ProjectConfiguration**: `Object`\n\n---\n\n### ProjectsConfigurations\n\n• **ProjectsConfigurations**: `Object`\n\n---\n\n### TargetConfiguration\n\n• **TargetConfiguration**: `Object`\n\n---\n\n### TargetDependencyConfig\n\n• **TargetDependencyConfig**: `Object`\n\n---\n\n### Task\n\n• **Task**: `Object`\n\n---\n\n### TaskGraph\n\n• **TaskGraph**: `Object`\n\n---\n\n### Workspace\n\n• **Workspace**: `Object`\n\n## Generators Type aliases\n\n### WorkspaceConfiguration\n\nƬ **WorkspaceConfiguration**: `Omit`<[`ProjectsConfigurations`](../../devkit/index#projectsconfigurations), `\"projects\"`\\> & `Partial`<[`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)\\>\n\n---\n\n## Other Type aliases\n\n### ProjectTargetConfigurator\n\nƬ **ProjectTargetConfigurator**: (`file`: `string`) => `Record`<`string`, [`TargetConfiguration`](../../devkit/index#targetconfiguration)\\>\n\n#### Type declaration\n\n▸ (`file`): `Record`<`string`, [`TargetConfiguration`](../../devkit/index#targetconfiguration)\\>\n\n##### Parameters\n\n| Name | Type |\n| :----- | :------- |\n| `file` | `string` |\n\n##### Returns\n\n`Record`<`string`, [`TargetConfiguration`](../../devkit/index#targetconfiguration)\\>\n\n---\n\n## Package Manager Type aliases\n\n### PackageManager\n\nƬ **PackageManager**: `\"yarn\"` \\| `\"pnpm\"` \\| `\"npm\"`\n\n---\n\n## Project Graph Type aliases\n\n### ProjectGraphNode\n\nƬ **ProjectGraphNode**<`T`\\>: [`ProjectGraphProjectNode`](../../devkit/index#projectgraphprojectnode)<`T`\\> \\| [`ProjectGraphExternalNode`](../../devkit/index#projectgraphexternalnode)\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n---\n\n## Utils Type aliases\n\n### StringChange\n\nƬ **StringChange**: [`StringInsertion`](../../devkit/index#stringinsertion) \\| [`StringDeletion`](../../devkit/index#stringdeletion)\n\n---\n\n## Workspace Type aliases\n\n### CustomHasher\n\nƬ **CustomHasher**: (`task`: [`Task`](../../devkit/index#task), `context`: [`HasherContext`](../../devkit/index#hashercontext)) => `Promise`<[`Hash`](../../devkit/index#hash)\\>\n\n#### Type declaration\n\n▸ (`task`, `context`): `Promise`<[`Hash`](../../devkit/index#hash)\\>\n\n##### Parameters\n\n| Name | Type |\n| :-------- | :-------------------------------------------------- |\n| `task` | [`Task`](../../devkit/index#task) |\n| `context` | [`HasherContext`](../../devkit/index#hashercontext) |\n\n##### Returns\n\n`Promise`<[`Hash`](../../devkit/index#hash)\\>\n\n---\n\n### Executor\n\nƬ **Executor**<`T`\\>: (`options`: `T`, `context`: [`ExecutorContext`](../../devkit/index#executorcontext)) => `Promise`<`Object`\\> \\| `AsyncIterableIterator`<`Object`\\>\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n#### Type declaration\n\n▸ (`options`, `context`): `Promise`<`Object`\\> \\| `AsyncIterableIterator`<`Object`\\>\n\nImplementation of a target of a project\n\n##### Parameters\n\n| Name | Type |\n| :-------- | :------------------------------------------------------ |\n| `options` | `T` |\n| `context` | [`ExecutorContext`](../../devkit/index#executorcontext) |\n\n##### Returns\n\n`Promise`<`Object`\\> \\| `AsyncIterableIterator`<`Object`\\>\n\n---\n\n### Generator\n\nƬ **Generator**<`T`\\>: (`tree`: `any`, `schema`: `T`) => `void` \\| [`GeneratorCallback`](../../devkit/index#generatorcallback) \\| `Promise`<`void` \\| [`GeneratorCallback`](../../devkit/index#generatorcallback)\\>\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :-------- |\n| `T` | `unknown` |\n\n#### Type declaration\n\n▸ (`tree`, `schema`): `void` \\| [`GeneratorCallback`](../../devkit/index#generatorcallback) \\| `Promise`<`void` \\| [`GeneratorCallback`](../../devkit/index#generatorcallback)\\>\n\nA function that schedules updates to the filesystem to be done atomically\n\n##### Parameters\n\n| Name | Type |\n| :------- | :---- |\n| `tree` | `any` |\n| `schema` | `T` |\n\n##### Returns\n\n`void` \\| [`GeneratorCallback`](../../devkit/index#generatorcallback) \\| `Promise`<`void` \\| [`GeneratorCallback`](../../devkit/index#generatorcallback)\\>\n\n---\n\n### GeneratorCallback\n\nƬ **GeneratorCallback**: () => `void` \\| `Promise`<`void`\\>\n\n#### Type declaration\n\n▸ (): `void` \\| `Promise`<`void`\\>\n\nA callback function that is executed after changes are made to the file system\n\n##### Returns\n\n`void` \\| `Promise`<`void`\\>\n\n---\n\n### ImplicitDependencyEntry\n\nƬ **ImplicitDependencyEntry**<`T`\\>: `Object`\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :------------------ |\n| `T` | `\"*\"` \\| `string`[] |\n\n#### Index signature\n\n▪ [key: `string`]: `T` \\| [`ImplicitJsonSubsetDependency`](../../devkit/index#implicitjsonsubsetdependency)<`T`\\>\n\n---\n\n### ProjectType\n\nƬ **ProjectType**: `\"library\"` \\| `\"application\"`\n\n---\n\n### TaskGraphExecutor\n\nƬ **TaskGraphExecutor**<`T`\\>: (`taskGraph`: [`TaskGraph`](../../devkit/index#taskgraph), `options`: `Record`<`string`, `T`\\>, `overrides`: `T`, `context`: [`ExecutorContext`](../../devkit/index#executorcontext)) => `Promise`<`Record`<`string`, `Object`\\>\\>\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n#### Type declaration\n\n▸ (`taskGraph`, `options`, `overrides`, `context`): `Promise`<`Record`<`string`, `Object`\\>\\>\n\nImplementation of a target of a project that handles multiple projects to be batched\n\n##### Parameters\n\n| Name | Type |\n| :---------- | :------------------------------------------------------ |\n| `taskGraph` | [`TaskGraph`](../../devkit/index#taskgraph) |\n| `options` | `Record`<`string`, `T`\\> |\n| `overrides` | `T` |\n| `context` | [`ExecutorContext`](../../devkit/index#executorcontext) |\n\n##### Returns\n\n`Promise`<`Record`<`string`, `Object`\\>\\>\n\n---\n\n### WorkspaceJsonConfiguration\n\nƬ **WorkspaceJsonConfiguration**: [`ProjectsConfigurations`](../../devkit/index#projectsconfigurations)\n\n## Logger Variables\n\n### logger\n\n• **logger**: `Object`\n\n#### Type declaration\n\n| Name | Type |\n| :------ | :-------------------------- |\n| `debug` | (...`s`: `any`[]) => `void` |\n| `error` | (`s`: `any`) => `void` |\n| `fatal` | (...`s`: `any`[]) => `void` |\n| `info` | (`s`: `any`) => `void` |\n| `log` | (...`s`: `any`[]) => `void` |\n| `warn` | (`s`: `any`) => `void` |\n\n---\n\n## Utils Variables\n\n### appRootPath\n\n• **appRootPath**: `string` = `workspaceRoot`\n\n---\n\n### cacheDir\n\n• **cacheDir**: `string`\n\n---\n\n### output\n\n• **output**: `CLIOutput`\n\n---\n\n### workspaceRoot\n\n• **workspaceRoot**: `string`\n\n## Functions\n\n### addDependenciesToPackageJson\n\n▸ **addDependenciesToPackageJson**(`tree`, `dependencies`, `devDependencies`, `packageJsonPath?`): [`GeneratorCallback`](../../devkit/index#generatorcallback)\n\nAdd Dependencies and Dev Dependencies to package.json\n\nFor example:\n\n```typescript\naddDependenciesToPackageJson(tree, { react: 'latest' }, { jest: 'latest' });\n```\n\nThis will **add** `react` and `jest` to the dependencies and devDependencies sections of package.json respectively.\n\n#### Parameters\n\n| Name | Type | Default value | Description |\n| :---------------- | :-------------------------------- | :--------------- | :---------------------------------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | `undefined` | Tree representing file system to modify |\n| `dependencies` | `Record`<`string`, `string`\\> | `undefined` | Dependencies to be added to the dependencies section of package.json |\n| `devDependencies` | `Record`<`string`, `string`\\> | `undefined` | Dependencies to be added to the devDependencies section of package.json |\n| `packageJsonPath` | `string` | `'package.json'` | Path to package.json |\n\n#### Returns\n\n[`GeneratorCallback`](../../devkit/index#generatorcallback)\n\nCallback to install dependencies only if necessary. undefined is returned if changes are not necessary.\n\n---\n\n### addProjectConfiguration\n\n▸ **addProjectConfiguration**(`tree`, `projectName`, `projectConfiguration`, `standalone?`): `void`\n\nAdds project configuration to the Nx workspace.\n\nThe project configuration is stored in workspace.json or the associated project.json file.\nThe utility will update either files.\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------------------- | :---------------------------------------------------------------- | :----------------------------------------------------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | the file system tree |\n| `projectName` | `string` | unique name. Often directories are part of the name (e.g., mydir-mylib) |\n| `projectConfiguration` | [`ProjectConfiguration`](../../devkit/index#projectconfiguration) | project configuration |\n| `standalone?` | `boolean` | should the project use package.json? If false, the project config is inside workspace.json |\n\n#### Returns\n\n`void`\n\n---\n\n### applyChangesToString\n\n▸ **applyChangesToString**(`text`, `changes`): `string`\n\nApplies a list of changes to a string's original value.\n\nThis is useful when working with ASTs.\n\nFor Example, to rename a property in a method's options:\n\n```typescript\nconst code = `bootstrap({\n target: document.querySelector('#app')\n})`;\n\nconst indexOfPropertyName = 13; // Usually determined by analyzing an AST.\nconst updatedCode = applyChangesToString(code, [\n {\n type: ChangeType.Insert,\n index: indexOfPropertyName,\n text: 'element',\n },\n {\n type: ChangeType.Delete,\n start: indexOfPropertyName,\n length: 6,\n },\n]);\n\nbootstrap({\n element: document.querySelector('#app'),\n});\n```\n\n#### Parameters\n\n| Name | Type |\n| :-------- | :-------------------------------------------------- |\n| `text` | `string` |\n| `changes` | [`StringChange`](../../devkit/index#stringchange)[] |\n\n#### Returns\n\n`string`\n\n---\n\n### convertNxExecutor\n\n▸ **convertNxExecutor**(`executor`): `any`\n\nConvert an Nx Executor into an Angular Devkit Builder\n\nUse this to expose a compatible Angular Builder\n\n#### Parameters\n\n| Name | Type |\n| :--------- | :------------------------------------------------ |\n| `executor` | [`Executor`](../../devkit/index#executor)<`any`\\> |\n\n#### Returns\n\n`any`\n\n---\n\n### convertNxGenerator\n\n▸ **convertNxGenerator**<`T`\\>(`generator`, `skipWritingConfigInOldFormat?`): (`generatorOptions`: `T`) => (`tree`: `any`, `context`: `any`) => `Promise`<`any`\\>\n\nConvert an Nx Generator into an Angular Devkit Schematic.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n#### Parameters\n\n| Name | Type | Default value | Description |\n| :----------------------------- | :------------------------------------------------ | :------------ | :------------------------------------------------------------------------------------------------ |\n| `generator` | [`Generator`](../../devkit/index#generator)<`T`\\> | `undefined` | The Nx generator to convert to an Angular Devkit Schematic. |\n| `skipWritingConfigInOldFormat` | `boolean` | `false` | Whether to skip writing the configuration in the old format (the one used by the Angular DevKit). |\n\n#### Returns\n\n`fn`\n\n▸ (`generatorOptions`): (`tree`: `any`, `context`: `any`) => `Promise`<`any`\\>\n\n##### Parameters\n\n| Name | Type |\n| :----------------- | :--- |\n| `generatorOptions` | `T` |\n\n##### Returns\n\n`fn`\n\n▸ (`tree`, `context`): `Promise`<`any`\\>\n\n##### Parameters\n\n| Name | Type |\n| :-------- | :---- |\n| `tree` | `any` |\n| `context` | `any` |\n\n##### Returns\n\n`Promise`<`any`\\>\n\n---\n\n### createProjectGraphAsync\n\n▸ **createProjectGraphAsync**(): `Promise`<[`ProjectGraph`](../../devkit/index#projectgraph)\\>\n\nComputes and returns a ProjectGraph.\n\nNx will compute the graph either in a daemon process or in the current process.\n\nNx will compute it in the current process if:\n\n- The process is running in CI (CI env variable is to true or other common variables used by CI providers are set).\n- It is running in the docker container.\n- The daemon process is disabled because of the previous error when starting the daemon.\n- `NX_DAEMON` is set to `false`.\n- `useDaemon` is set to false in `nx.json`\n\n`NX_DAEMON` env variable takes precedence:\n\n- If it is set to true, the daemon will always be used.\n- If it is set to false, the graph will always be computed in the current process.\n\nTip: If you want to debug project graph creation, run your command with NX_DAEMON=false.\n\nNx uses two layers of caching: the information about explicit dependencies stored on the disk and the information\nstored in the daemon process. To reset both run: `nx reset`.\n\n#### Returns\n\n`Promise`<[`ProjectGraph`](../../devkit/index#projectgraph)\\>\n\n---\n\n### defaultTasksRunner\n\n▸ `Const` **defaultTasksRunner**(`tasks`, `options`, `context?`): `any`\n\n#### Parameters\n\n| Name | Type |\n| :--------------------------- | :------------------------------------------------------------------------------------ |\n| `tasks` | [`Task`](../../devkit/index#task)[] |\n| `options` | [`DefaultTasksRunnerOptions`](../../devkit/index#defaulttasksrunneroptions) |\n| `context?` | `Object` |\n| `context.initiatingProject?` | `string` |\n| `context.nxArgs` | `NxArgs` |\n| `context.nxJson` | [`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)<`string`[] \\| `\"*\"`\\> |\n| `context.projectGraph` | [`ProjectGraph`](../../devkit/index#projectgraph)<`any`\\> |\n| `context.target?` | `string` |\n| `context.taskGraph?` | [`TaskGraph`](../../devkit/index#taskgraph) |\n\n#### Returns\n\n`any`\n\n---\n\n### detectPackageManager\n\n▸ **detectPackageManager**(`dir?`): [`PackageManager`](../../devkit/index#packagemanager)\n\nDetects which package manager is used in the workspace based on the lock file.\n\n#### Parameters\n\n| Name | Type | Default value |\n| :---- | :------- | :------------ |\n| `dir` | `string` | `''` |\n\n#### Returns\n\n[`PackageManager`](../../devkit/index#packagemanager)\n\n---\n\n### formatFiles\n\n▸ **formatFiles**(`tree`): `Promise`<`void`\\>\n\nFormats all the created or updated files using Prettier\n\n#### Parameters\n\n| Name | Type | Description |\n| :----- | :-------------------------------- | :------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | the file system tree |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### generateFiles\n\n▸ **generateFiles**(`tree`, `srcFolder`, `target`, `substitutions`): `void`\n\nGenerates a folder of files based on provided templates.\n\nWhile doing so it performs two substitutions:\n\n- Substitutes segments of file names surrounded by \\_\\_\n- Uses ejs to substitute values in templates\n\nExamples:\n\n```typescript\ngenerateFiles(tree, path.join(__dirname, 'files'), './tools/scripts', {\n tmpl: '',\n name: 'myscript',\n});\n```\n\nThis command will take all the files from the `files` directory next to the place where the command is invoked from.\nIt will replace all `__tmpl__` with '' and all `__name__` with 'myscript' in the file names, and will replace all\n`<%= name %>` with `myscript` in the files themselves.\n`tmpl: ''` is a common pattern. With it you can name files like this: `index.ts__tmpl__`, so your editor\ndoesn't get confused about incorrect TypeScript files.\n\n#### Parameters\n\n| Name | Type | Description |\n| :-------------- | :-------------------------------- | :-------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | the file system tree |\n| `srcFolder` | `string` | the source folder of files (absolute path) |\n| `target` | `string` | the target folder (relative to the tree root) |\n| `substitutions` | `Object` | an object of key-value pairs |\n\n#### Returns\n\n`void`\n\n---\n\n### getOutputsForTargetAndConfiguration\n\n▸ **getOutputsForTargetAndConfiguration**(`task`, `node`): `any`\n\nReturns the list of outputs that will be cached.\n\n#### Parameters\n\n| Name | Type | Description |\n| :----- | :------------------------------------------------------------------------------ | :-------------------------------------------------------- |\n| `task` | `Pick`<[`Task`](../../devkit/index#task), `\"target\"` \\| `\"overrides\"`\\> | target + overrides |\n| `node` | [`ProjectGraphProjectNode`](../../devkit/index#projectgraphprojectnode)<`any`\\> | ProjectGraphProjectNode object that the task runs against |\n\n#### Returns\n\n`any`\n\n---\n\n### getPackageManagerCommand\n\n▸ **getPackageManagerCommand**(`packageManager?`): `PackageManagerCommands`\n\nReturns commands for the package manager used in the workspace.\nBy default, the package manager is derived based on the lock file,\nbut it can also be passed in explicitly.\n\nExample:\n\n```javascript\nexecSync(`${getPackageManagerCommand().addDev} my-dev-package`);\n```\n\n#### Parameters\n\n| Name | Type |\n| :--------------- | :---------------------------------------------------- |\n| `packageManager` | [`PackageManager`](../../devkit/index#packagemanager) |\n\n#### Returns\n\n`PackageManagerCommands`\n\n---\n\n### getPackageManagerVersion\n\n▸ **getPackageManagerVersion**(`packageManager?`): `string`\n\nReturns the version of the package manager used in the workspace.\nBy default, the package manager is derived based on the lock file,\nbut it can also be passed in explicitly.\n\n#### Parameters\n\n| Name | Type |\n| :--------------- | :---------------------------------------------------- |\n| `packageManager` | [`PackageManager`](../../devkit/index#packagemanager) |\n\n#### Returns\n\n`string`\n\n---\n\n### getProjects\n\n▸ **getProjects**(`tree`): `Map`<`string`, [`ProjectConfiguration`](../../devkit/index#projectconfiguration)\\>\n\nGet a map of all projects in a workspace.\n\nUse [readProjectConfiguration](../../devkit/index#readprojectconfiguration) if only one project is needed.\n\n#### Parameters\n\n| Name | Type |\n| :----- | :-------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n\n#### Returns\n\n`Map`<`string`, [`ProjectConfiguration`](../../devkit/index#projectconfiguration)\\>\n\n---\n\n### getWorkspaceLayout\n\n▸ **getWorkspaceLayout**(`tree`): `Object`\n\nReturns workspace defaults. It includes defaults folders for apps and libs,\nand the default scope.\n\nExample:\n\n```typescript\n{ appsDir: 'apps', libsDir: 'libs', npmScope: 'myorg' }\n```\n\n#### Parameters\n\n| Name | Type | Description |\n| :----- | :-------------------------------- | :--------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | file system tree |\n\n#### Returns\n\n`Object`\n\n| Name | Type |\n| :-------------------- | :-------- |\n| `appsDir` | `string` |\n| `libsDir` | `string` |\n| `npmScope` | `string` |\n| `standaloneAsDefault` | `boolean` |\n\n---\n\n### getWorkspacePath\n\n▸ **getWorkspacePath**(`tree`): `\"/angular.json\"` \\| `\"/workspace.json\"` \\| `null`\n\n#### Parameters\n\n| Name | Type |\n| :----- | :-------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n\n#### Returns\n\n`\"/angular.json\"` \\| `\"/workspace.json\"` \\| `null`\n\n---\n\n### installPackagesTask\n\n▸ **installPackagesTask**(`tree`, `alwaysRun?`, `cwd?`, `packageManager?`): `void`\n\nRuns `npm install` or `yarn install`. It will skip running the install if\n`package.json` hasn't changed at all or it hasn't changed since the last invocation.\n\n#### Parameters\n\n| Name | Type | Default value | Description |\n| :--------------- | :---------------------------------------------------- | :------------ | :------------------------------------------------------------ |\n| `tree` | [`Tree`](../../devkit/index#tree) | `undefined` | the file system tree |\n| `alwaysRun` | `boolean` | `false` | always run the command even if `package.json` hasn't changed. |\n| `cwd` | `string` | `''` | - |\n| `packageManager` | [`PackageManager`](../../devkit/index#packagemanager) | `undefined` | - |\n\n#### Returns\n\n`void`\n\n---\n\n### isStandaloneProject\n\n▸ **isStandaloneProject**(`tree`, `project`): `boolean`\n\nReturns if a project has a standalone configuration (project.json).\n\n#### Parameters\n\n| Name | Type | Description |\n| :-------- | :-------------------------------- | :------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | the file system tree |\n| `project` | `string` | the project name |\n\n#### Returns\n\n`boolean`\n\n---\n\n### joinPathFragments\n\n▸ **joinPathFragments**(...`fragments`): `string`\n\nNormalized path fragments and joins them\n\n#### Parameters\n\n| Name | Type |\n| :------------- | :--------- |\n| `...fragments` | `string`[] |\n\n#### Returns\n\n`string`\n\n---\n\n### moveFilesToNewDirectory\n\n▸ **moveFilesToNewDirectory**(`tree`, `oldDir`, `newDir`): `void`\n\nAnalogous to cp -r oldDir newDir\n\n#### Parameters\n\n| Name | Type |\n| :------- | :-------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n| `oldDir` | `string` |\n| `newDir` | `string` |\n\n#### Returns\n\n`void`\n\n---\n\n### names\n\n▸ **names**(`name`): `Object`\n\nUtil function to generate different strings based off the provided name.\n\nExamples:\n\n```typescript\nnames('my-name'); // {name: 'my-name', className: 'MyName', propertyName: 'myName', constantName: 'MY_NAME', fileName: 'my-name'}\nnames('myName'); // {name: 'myName', className: 'MyName', propertyName: 'myName', constantName: 'MY_NAME', fileName: 'my-name'}\n```\n\n#### Parameters\n\n| Name | Type |\n| :----- | :------- |\n| `name` | `string` |\n\n#### Returns\n\n`Object`\n\n| Name | Type |\n| :------------- | :------- |\n| `className` | `string` |\n| `constantName` | `string` |\n| `fileName` | `string` |\n| `name` | `string` |\n| `propertyName` | `string` |\n\n---\n\n### normalizePath\n\n▸ **normalizePath**(`osSpecificPath`): `string`\n\nCoverts an os specific path to a unix style path\n\n#### Parameters\n\n| Name | Type |\n| :--------------- | :------- |\n| `osSpecificPath` | `string` |\n\n#### Returns\n\n`string`\n\n---\n\n### offsetFromRoot\n\n▸ **offsetFromRoot**(`fullPathToDir`): `string`\n\nCalculates an offset from the root of the workspace, which is useful for\nconstructing relative URLs.\n\nExamples:\n\n```typescript\noffsetFromRoot('apps/mydir/myapp/'); // returns \"../../../\"\n```\n\n#### Parameters\n\n| Name | Type | Description |\n| :-------------- | :------- | :------------- |\n| `fullPathToDir` | `string` | directory path |\n\n#### Returns\n\n`string`\n\n---\n\n### parseJson\n\n▸ **parseJson**<`T`\\>(`input`, `options?`): `T`\n\nParses the given JSON string and returns the object the JSON content represents.\nBy default javascript-style comments are allowed.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :----------------------- |\n| `T` | extends `object` = `any` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------- | :-------------------------------------------------------- | :--------------------- |\n| `input` | `string` | JSON content as string |\n| `options?` | [`JsonParseOptions`](../../devkit/index#jsonparseoptions) | JSON parse options |\n\n#### Returns\n\n`T`\n\nObject the JSON content represents\n\n---\n\n### parseTargetString\n\n▸ **parseTargetString**(`targetString`): [`Target`](../../devkit/index#target)\n\nParses a target string into {project, target, configuration}\n\nExamples:\n\n```typescript\nparseTargetString('proj:test'); // returns { project: \"proj\", target: \"test\" }\nparseTargetString('proj:test:production'); // returns { project: \"proj\", target: \"test\", configuration: \"production\" }\n```\n\n#### Parameters\n\n| Name | Type | Description |\n| :------------- | :------- | :--------------- |\n| `targetString` | `string` | target reference |\n\n#### Returns\n\n[`Target`](../../devkit/index#target)\n\n---\n\n### readAllWorkspaceConfiguration\n\n▸ **readAllWorkspaceConfiguration**(): [`ProjectsConfigurations`](../../devkit/index#projectsconfigurations) & [`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)\n\n#### Returns\n\n[`ProjectsConfigurations`](../../devkit/index#projectsconfigurations) & [`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)\n\n---\n\n### readCachedProjectGraph\n\n▸ **readCachedProjectGraph**(): [`ProjectGraph`](../../devkit/index#projectgraph)\n\nSynchronously reads the latest cached copy of the workspace's ProjectGraph.\n\n**`throws`** {Error} if there is no cached ProjectGraph to read from\n\n#### Returns\n\n[`ProjectGraph`](../../devkit/index#projectgraph)\n\n---\n\n### readJson\n\n▸ **readJson**<`T`\\>(`tree`, `path`, `options?`): `T`\n\nReads a json file, removes all comments and parses JSON.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :----------------------- |\n| `T` | extends `object` = `any` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------- | :-------------------------------------------------------- | :-------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | file system tree |\n| `path` | `string` | file path |\n| `options?` | [`JsonParseOptions`](../../devkit/index#jsonparseoptions) | Optional JSON Parse Options |\n\n#### Returns\n\n`T`\n\n---\n\n### readJsonFile\n\n▸ **readJsonFile**<`T`\\>(`path`, `options?`): `T`\n\nReads a JSON file and returns the object the JSON content represents.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :----------------------- |\n| `T` | extends `object` = `any` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------- | :---------------- | :----------------- |\n| `path` | `string` | A path to a file. |\n| `options?` | `JsonReadOptions` | JSON parse options |\n\n#### Returns\n\n`T`\n\nObject the JSON content of the file represents\n\n---\n\n### readNxJson\n\n▸ **readNxJson**(): [`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)\n\n#### Returns\n\n[`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)\n\n---\n\n### readProjectConfiguration\n\n▸ **readProjectConfiguration**(`tree`, `projectName`): [`ProjectConfiguration`](../../devkit/index#projectconfiguration)\n\nReads a project configuration.\n\nThe project configuration is stored in workspace.json or the associated project.json file.\nThe utility will read from either file.\n\n**`throws`** If supplied projectName cannot be found\n\n#### Parameters\n\n| Name | Type | Description |\n| :------------ | :-------------------------------- | :---------------------------------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | the file system tree |\n| `projectName` | `string` | unique name. Often directories are part of the name (e.g., mydir-mylib) |\n\n#### Returns\n\n[`ProjectConfiguration`](../../devkit/index#projectconfiguration)\n\n---\n\n### readTargetOptions\n\n▸ **readTargetOptions**<`T`\\>(`__namedParameters`, `context`): `T`\n\nReads and combines options for a given target.\n\nWorks as if you invoked the target yourself without passing any command lint overrides.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n#### Parameters\n\n| Name | Type |\n| :------------------ | :------------------------------------------------------ |\n| `__namedParameters` | [`Target`](../../devkit/index#target) |\n| `context` | [`ExecutorContext`](../../devkit/index#executorcontext) |\n\n#### Returns\n\n`T`\n\n---\n\n### readWorkspaceConfiguration\n\n▸ **readWorkspaceConfiguration**(`tree`): [`WorkspaceConfiguration`](../../devkit/index#workspaceconfiguration)\n\nRead general workspace configuration such as the default project or cli settings\n\nThis does _not_ provide projects configuration, use [readProjectConfiguration](../../devkit/index#readprojectconfiguration) instead.\n\n#### Parameters\n\n| Name | Type |\n| :----- | :-------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n\n#### Returns\n\n[`WorkspaceConfiguration`](../../devkit/index#workspaceconfiguration)\n\n---\n\n### removeDependenciesFromPackageJson\n\n▸ **removeDependenciesFromPackageJson**(`tree`, `dependencies`, `devDependencies`, `packageJsonPath?`): [`GeneratorCallback`](../../devkit/index#generatorcallback)\n\nRemove Dependencies and Dev Dependencies from package.json\n\nFor example:\n\n```typescript\nremoveDependenciesFromPackageJson(tree, ['react'], ['jest']);\n```\n\nThis will **remove** `react` and `jest` from the dependencies and devDependencies sections of package.json respectively.\n\n#### Parameters\n\n| Name | Type | Default value | Description |\n| :---------------- | :-------------------------------- | :--------------- | :-------------------------------------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | `undefined` | - |\n| `dependencies` | `string`[] | `undefined` | Dependencies to be removed from the dependencies section of package.json |\n| `devDependencies` | `string`[] | `undefined` | Dependencies to be removed from the devDependencies section of package.json |\n| `packageJsonPath` | `string` | `'package.json'` | - |\n\n#### Returns\n\n[`GeneratorCallback`](../../devkit/index#generatorcallback)\n\nCallback to uninstall dependencies only if necessary. undefined is returned if changes are not necessary.\n\n---\n\n### removeProjectConfiguration\n\n▸ **removeProjectConfiguration**(`tree`, `projectName`): `void`\n\nRemoves the configuration of an existing project.\n\nThe project configuration is stored in workspace.json or the associated project.json file.\nThe utility will update either file.\n\n#### Parameters\n\n| Name | Type |\n| :------------ | :-------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n| `projectName` | `string` |\n\n#### Returns\n\n`void`\n\n---\n\n### reverse\n\n▸ **reverse**(`graph`): [`ProjectGraph`](../../devkit/index#projectgraph)\n\nReturns a new project graph where all the edges are reversed.\n\nFor instance, if project A depends on B, in the reversed graph\nB will depend on A.\n\n#### Parameters\n\n| Name | Type |\n| :------ | :-------------------------------------------------------- |\n| `graph` | [`ProjectGraph`](../../devkit/index#projectgraph)<`any`\\> |\n\n#### Returns\n\n[`ProjectGraph`](../../devkit/index#projectgraph)\n\n---\n\n### runExecutor\n\n▸ **runExecutor**<`T`\\>(`targetDescription`, `options`, `context`): `Promise`<`AsyncIterableIterator`<`T`\\>\\>\n\nLoads and invokes executor.\n\nThis is analogous to invoking executor from the terminal, with the exception\nthat the params aren't parsed from the string, but instead provided parsed already.\n\nApart from that, it works the same way:\n\n- it will load the workspace configuration\n- it will resolve the target\n- it will load the executor and the schema\n- it will load the options for the appropriate configuration\n- it will run the validations and will set the default\n- and, of course, it will invoke the executor\n\nExample:\n\n```typescript\nfor await (const s of await runExecutor(\n { project: 'myproj', target: 'serve' },\n { watch: true },\n context\n)) {\n // s.success\n}\n```\n\nNote that the return value is a promise of an iterator, so you need to await before iterating over it.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :--------------- |\n| `T` | extends `Object` |\n\n#### Parameters\n\n| Name | Type |\n| :--------------------------------- | :------------------------------------------------------ |\n| `targetDescription` | `Object` |\n| `targetDescription.configuration?` | `string` |\n| `targetDescription.project` | `string` |\n| `targetDescription.target` | `string` |\n| `options` | `Object` |\n| `context` | [`ExecutorContext`](../../devkit/index#executorcontext) |\n\n#### Returns\n\n`Promise`<`AsyncIterableIterator`<`T`\\>\\>\n\n---\n\n### serializeJson\n\n▸ **serializeJson**<`T`\\>(`input`, `options?`): `string`\n\nSerializes the given data to a JSON string.\nBy default the JSON string is formatted with a 2 space intendation to be easy readable.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :-------------------------- |\n| `T` | extends `object` = `object` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------- | :---------------------------------------------------------------- | :---------------------------------------- |\n| `input` | `T` | Object which should be serialized to JSON |\n| `options?` | [`JsonSerializeOptions`](../../devkit/index#jsonserializeoptions) | JSON serialize options |\n\n#### Returns\n\n`string`\n\nthe formatted JSON representation of the object\n\n---\n\n### stripIndents\n\n▸ **stripIndents**(`strings`, ...`values`): `string`\n\nRemoves indents, which is useful for printing warning and messages.\n\nExample:\n\n```typescript\nstripIndents`\n Options:\n - option1\n - option2\n`;\n```\n\n#### Parameters\n\n| Name | Type |\n| :---------- | :--------------------- |\n| `strings` | `TemplateStringsArray` |\n| `...values` | `any`[] |\n\n#### Returns\n\n`string`\n\n---\n\n### stripJsonComments\n\n▸ `Const` **stripJsonComments**(`text`, `replaceCh?`): `string`\n\nTakes JSON with JavaScript-style comments and remove\nthem. Optionally replaces every none-newline character\nof comments with a replaceCharacter\n\n#### Parameters\n\n| Name | Type |\n| :----------- | :------- |\n| `text` | `string` |\n| `replaceCh?` | `string` |\n\n#### Returns\n\n`string`\n\n---\n\n### targetToTargetString\n\n▸ **targetToTargetString**(`target`): `string`\n\nReturns a string in the format \"project:target[:configuration]\" for the target\n\n#### Parameters\n\n| Name | Type | Description |\n| :------- | :------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `target` | [`Target`](../../devkit/index#target) | target object Examples: `typescript targetToTargetString({ project: \"proj\", target: \"test\" }) // returns \"proj:test\" targetToTargetString({ project: \"proj\", target: \"test\", configuration: \"production\" }) // returns \"proj:test:production\" ` |\n\n#### Returns\n\n`string`\n\n---\n\n### toJS\n\n▸ **toJS**(`tree`): `void`\n\nRename and transpile any new typescript files created to javascript files\n\n#### Parameters\n\n| Name | Type |\n| :----- | :-------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n\n#### Returns\n\n`void`\n\n---\n\n### updateJson\n\n▸ **updateJson**<`T`, `U`\\>(`tree`, `path`, `updater`, `options?`): `void`\n\nUpdates a JSON value to the file system tree\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :----------------------- |\n| `T` | extends `object` = `any` |\n| `U` | extends `object` = `T` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------- | :---------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | File system tree |\n| `path` | `string` | Path of JSON file in the Tree |\n| `updater` | (`value`: `T`) => `U` | Function that maps the current value of a JSON document to a new value to be written to the document |\n| `options?` | [`JsonParseOptions`](../../devkit/index#jsonparseoptions) & [`JsonSerializeOptions`](../../devkit/index#jsonserializeoptions) | Optional JSON Parse and Serialize Options |\n\n#### Returns\n\n`void`\n\n---\n\n### updateProjectConfiguration\n\n▸ **updateProjectConfiguration**(`tree`, `projectName`, `projectConfiguration`): `void`\n\nUpdates the configuration of an existing project.\n\nThe project configuration is stored in workspace.json or the associated project.json file.\nThe utility will update either files.\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------------------- | :---------------------------------------------------------------- | :---------------------------------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | the file system tree |\n| `projectName` | `string` | unique name. Often directories are part of the name (e.g., mydir-mylib) |\n| `projectConfiguration` | [`ProjectConfiguration`](../../devkit/index#projectconfiguration) | project configuration |\n\n#### Returns\n\n`void`\n\n---\n\n### updateTsConfigsToJs\n\n▸ **updateTsConfigsToJs**(`tree`, `options`): `void`\n\n#### Parameters\n\n| Name | Type |\n| :-------------------- | :-------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n| `options` | `Object` |\n| `options.projectRoot` | `string` |\n\n#### Returns\n\n`void`\n\n---\n\n### updateWorkspaceConfiguration\n\n▸ **updateWorkspaceConfiguration**(`tree`, `workspaceConfig`): `void`\n\nUpdate general workspace configuration such as the default project or cli settings.\n\nThis does _not_ update projects configuration, use [updateProjectConfiguration](../../devkit/index#updateprojectconfiguration) or [addProjectConfiguration](../../devkit/index#addprojectconfiguration) instead.\n\n#### Parameters\n\n| Name | Type |\n| :---------------- | :-------------------------------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n| `workspaceConfig` | [`WorkspaceConfiguration`](../../devkit/index#workspaceconfiguration) |\n\n#### Returns\n\n`void`\n\n---\n\n### visitNotIgnoredFiles\n\n▸ **visitNotIgnoredFiles**(`tree`, `dirPath?`, `visitor`): `void`\n\nUtility to act on all files in a tree that are not ignored by git.\n\n#### Parameters\n\n| Name | Type | Default value |\n| :-------- | :-------------------------------- | :------------ |\n| `tree` | [`Tree`](../../devkit/index#tree) | `undefined` |\n| `dirPath` | `string` | `tree.root` |\n| `visitor` | (`path`: `string`) => `void` | `undefined` |\n\n#### Returns\n\n`void`\n\n---\n\n### workspaceLayout\n\n▸ **workspaceLayout**(): `Object`\n\nReturns information about where apps and libs will be created.\n\n#### Returns\n\n`Object`\n\n| Name | Type |\n| :-------- | :------- |\n| `appsDir` | `string` |\n| `libsDir` | `string` |\n\n---\n\n### writeJson\n\n▸ **writeJson**<`T`\\>(`tree`, `path`, `value`, `options?`): `void`\n\nWrites a JSON value to the file system tree\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :-------------------------- |\n| `T` | extends `object` = `object` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------- | :---------------------------------------------------------------- | :------------------------------ |\n| `tree` | [`Tree`](../../devkit/index#tree) | File system tree |\n| `path` | `string` | Path of JSON file in the Tree |\n| `value` | `T` | Serializable value to write |\n| `options?` | [`JsonSerializeOptions`](../../devkit/index#jsonserializeoptions) | Optional JSON Serialize Options |\n\n#### Returns\n\n`void`\n\n---\n\n### writeJsonFile\n\n▸ **writeJsonFile**<`T`\\>(`path`, `data`, `options?`): `void`\n\nSerializes the given data to JSON and writes it to a file.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :-------------------------- |\n| `T` | extends `object` = `object` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------- | :----------------- | :-------------------------------------------------------------- |\n| `path` | `string` | A path to a file. |\n| `data` | `T` | data which should be serialized to JSON and written to the file |\n| `options?` | `JsonWriteOptions` | JSON serialize options |\n\n#### Returns\n\n`void`\n" + "content": "# Module: index\n\nThe Nx Devkit is the underlying technology used to customize Nx to support\ndifferent technologies and custom use-cases. It contains many utility\nfunctions for reading and writing files, updating configuration,\nworking with Abstract Syntax Trees(ASTs), and more.\n\nAs with most things in Nx, the core of Nx Devkit is very simple.\nIt only uses language primitives and immutable objects\n(the tree being the only exception).\n\n## Table of contents\n\n### Project Graph Enumerations\n\n- [DependencyType](../../devkit/index#dependencytype)\n\n### Utils Enumerations\n\n- [ChangeType](../../devkit/index#changetype)\n\n### Project Graph Classes\n\n- [ProjectGraphBuilder](../../devkit/index#projectgraphbuilder)\n\n### Utils Classes\n\n- [Hasher](../../devkit/index#hasher)\n\n### Workspace Classes\n\n- [Workspaces](../../devkit/index#workspaces)\n\n### Commands Interfaces\n\n- [Target](../../devkit/index#target)\n\n### Other Interfaces\n\n- [NxPlugin](../../devkit/index#nxplugin)\n\n### Project Graph Interfaces\n\n- [FileData](../../devkit/index#filedata)\n- [ProjectFileMap](../../devkit/index#projectfilemap)\n- [ProjectGraph](../../devkit/index#projectgraph)\n- [ProjectGraphDependency](../../devkit/index#projectgraphdependency)\n- [ProjectGraphExternalNode](../../devkit/index#projectgraphexternalnode)\n- [ProjectGraphProcessorContext](../../devkit/index#projectgraphprocessorcontext)\n- [ProjectGraphProjectNode](../../devkit/index#projectgraphprojectnode)\n- [ProjectGraphV4](../../devkit/index#projectgraphv4)\n\n### Tree Interfaces\n\n- [FileChange](../../devkit/index#filechange)\n- [Tree](../../devkit/index#tree)\n\n### Utils Interfaces\n\n- [DefaultTasksRunnerOptions](../../devkit/index#defaulttasksrunneroptions)\n- [Hash](../../devkit/index#hash)\n- [JsonParseOptions](../../devkit/index#jsonparseoptions)\n- [JsonSerializeOptions](../../devkit/index#jsonserializeoptions)\n- [RemoteCache](../../devkit/index#remotecache)\n- [StringDeletion](../../devkit/index#stringdeletion)\n- [StringInsertion](../../devkit/index#stringinsertion)\n\n### Workspace Interfaces\n\n- [ExecutorContext](../../devkit/index#executorcontext)\n- [ExecutorsJson](../../devkit/index#executorsjson)\n- [GeneratorsJson](../../devkit/index#generatorsjson)\n- [HasherContext](../../devkit/index#hashercontext)\n- [ImplicitJsonSubsetDependency](../../devkit/index#implicitjsonsubsetdependency)\n- [MigrationsJson](../../devkit/index#migrationsjson)\n- [NxAffectedConfig](../../devkit/index#nxaffectedconfig)\n- [NxJsonConfiguration](../../devkit/index#nxjsonconfiguration)\n- [ProjectConfiguration](../../devkit/index#projectconfiguration)\n- [ProjectsConfigurations](../../devkit/index#projectsconfigurations)\n- [TargetConfiguration](../../devkit/index#targetconfiguration)\n- [TargetDependencyConfig](../../devkit/index#targetdependencyconfig)\n- [Task](../../devkit/index#task)\n- [TaskGraph](../../devkit/index#taskgraph)\n- [Workspace](../../devkit/index#workspace)\n\n### Generators Type aliases\n\n- [WorkspaceConfiguration](../../devkit/index#workspaceconfiguration)\n\n### Other Type aliases\n\n- [ProjectTargetConfigurator](../../devkit/index#projecttargetconfigurator)\n\n### Package Manager Type aliases\n\n- [PackageManager](../../devkit/index#packagemanager)\n\n### Project Graph Type aliases\n\n- [ProjectGraphNode](../../devkit/index#projectgraphnode)\n\n### Utils Type aliases\n\n- [StringChange](../../devkit/index#stringchange)\n\n### Workspace Type aliases\n\n- [CustomHasher](../../devkit/index#customhasher)\n- [Executor](../../devkit/index#executor)\n- [Generator](../../devkit/index#generator)\n- [GeneratorCallback](../../devkit/index#generatorcallback)\n- [ImplicitDependencyEntry](../../devkit/index#implicitdependencyentry)\n- [ProjectType](../../devkit/index#projecttype)\n- [TaskGraphExecutor](../../devkit/index#taskgraphexecutor)\n- [WorkspaceJsonConfiguration](../../devkit/index#workspacejsonconfiguration)\n\n### Logger Variables\n\n- [logger](../../devkit/index#logger)\n\n### Utils Variables\n\n- [appRootPath](../../devkit/index#approotpath)\n- [cacheDir](../../devkit/index#cachedir)\n- [output](../../devkit/index#output)\n- [workspaceRoot](../../devkit/index#workspaceroot)\n\n### Functions\n\n- [addDependenciesToPackageJson](../../devkit/index#adddependenciestopackagejson)\n- [addProjectConfiguration](../../devkit/index#addprojectconfiguration)\n- [applyChangesToString](../../devkit/index#applychangestostring)\n- [convertNxExecutor](../../devkit/index#convertnxexecutor)\n- [convertNxGenerator](../../devkit/index#convertnxgenerator)\n- [createProjectGraphAsync](../../devkit/index#createprojectgraphasync)\n- [defaultTasksRunner](../../devkit/index#defaulttasksrunner)\n- [detectPackageManager](../../devkit/index#detectpackagemanager)\n- [formatFiles](../../devkit/index#formatfiles)\n- [generateFiles](../../devkit/index#generatefiles)\n- [getOutputsForTargetAndConfiguration](../../devkit/index#getoutputsfortargetandconfiguration)\n- [getPackageManagerCommand](../../devkit/index#getpackagemanagercommand)\n- [getPackageManagerVersion](../../devkit/index#getpackagemanagerversion)\n- [getProjects](../../devkit/index#getprojects)\n- [getWorkspaceLayout](../../devkit/index#getworkspacelayout)\n- [getWorkspacePath](../../devkit/index#getworkspacepath)\n- [installPackagesTask](../../devkit/index#installpackagestask)\n- [isStandaloneProject](../../devkit/index#isstandaloneproject)\n- [joinPathFragments](../../devkit/index#joinpathfragments)\n- [moveFilesToNewDirectory](../../devkit/index#movefilestonewdirectory)\n- [names](../../devkit/index#names)\n- [normalizePath](../../devkit/index#normalizepath)\n- [offsetFromRoot](../../devkit/index#offsetfromroot)\n- [parseJson](../../devkit/index#parsejson)\n- [parseTargetString](../../devkit/index#parsetargetstring)\n- [readAllWorkspaceConfiguration](../../devkit/index#readallworkspaceconfiguration)\n- [readCachedProjectGraph](../../devkit/index#readcachedprojectgraph)\n- [readJson](../../devkit/index#readjson)\n- [readJsonFile](../../devkit/index#readjsonfile)\n- [readNxJson](../../devkit/index#readnxjson)\n- [readProjectConfiguration](../../devkit/index#readprojectconfiguration)\n- [readTargetOptions](../../devkit/index#readtargetoptions)\n- [readWorkspaceConfiguration](../../devkit/index#readworkspaceconfiguration)\n- [removeDependenciesFromPackageJson](../../devkit/index#removedependenciesfrompackagejson)\n- [removeProjectConfiguration](../../devkit/index#removeprojectconfiguration)\n- [reverse](../../devkit/index#reverse)\n- [runExecutor](../../devkit/index#runexecutor)\n- [serializeJson](../../devkit/index#serializejson)\n- [stripIndents](../../devkit/index#stripindents)\n- [stripJsonComments](../../devkit/index#stripjsoncomments)\n- [targetToTargetString](../../devkit/index#targettotargetstring)\n- [toJS](../../devkit/index#tojs)\n- [updateJson](../../devkit/index#updatejson)\n- [updateProjectConfiguration](../../devkit/index#updateprojectconfiguration)\n- [updateTsConfigsToJs](../../devkit/index#updatetsconfigstojs)\n- [updateWorkspaceConfiguration](../../devkit/index#updateworkspaceconfiguration)\n- [visitNotIgnoredFiles](../../devkit/index#visitnotignoredfiles)\n- [workspaceLayout](../../devkit/index#workspacelayout)\n- [writeJson](../../devkit/index#writejson)\n- [writeJsonFile](../../devkit/index#writejsonfile)\n\n## Project Graph Enumerations\n\n### DependencyType\n\n• **DependencyType**: `Object`\n\n---\n\n## Utils Enumerations\n\n### ChangeType\n\n• **ChangeType**: `Object`\n\n## Project Graph Classes\n\n### ProjectGraphBuilder\n\n• **ProjectGraphBuilder**: `Object`\n\n---\n\n## Utils Classes\n\n### Hasher\n\n• **Hasher**: `Object`\n\n---\n\n## Workspace Classes\n\n### Workspaces\n\n• **Workspaces**: `Object`\n\n## Commands Interfaces\n\n### Target\n\n• **Target**: `Object`\n\n---\n\n## Other Interfaces\n\n### NxPlugin\n\n• **NxPlugin**: `Object`\n\nA plugin for Nx\n\n---\n\n## Project Graph Interfaces\n\n### FileData\n\n• **FileData**: `Object`\n\n---\n\n### ProjectFileMap\n\n• **ProjectFileMap**: `Object`\n\n---\n\n### ProjectGraph\n\n• **ProjectGraph**<`T`\\>: `Object`\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n---\n\n### ProjectGraphDependency\n\n• **ProjectGraphDependency**: `Object`\n\n---\n\n### ProjectGraphExternalNode\n\n• **ProjectGraphExternalNode**: `Object`\n\n---\n\n### ProjectGraphProcessorContext\n\n• **ProjectGraphProcessorContext**: `Object`\n\n---\n\n### ProjectGraphProjectNode\n\n• **ProjectGraphProjectNode**<`T`\\>: `Object`\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n---\n\n### ProjectGraphV4\n\n• **ProjectGraphV4**<`T`\\>: `Object`\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n---\n\n## Tree Interfaces\n\n### FileChange\n\n• **FileChange**: `Object`\n\n---\n\n### Tree\n\n• **Tree**: `Object`\n\n---\n\n## Utils Interfaces\n\n### DefaultTasksRunnerOptions\n\n• **DefaultTasksRunnerOptions**: `Object`\n\n---\n\n### Hash\n\n• **Hash**: `Object`\n\n---\n\n### JsonParseOptions\n\n• **JsonParseOptions**: `Object`\n\n---\n\n### JsonSerializeOptions\n\n• **JsonSerializeOptions**: `Object`\n\n---\n\n### RemoteCache\n\n• **RemoteCache**: `Object`\n\n---\n\n### StringDeletion\n\n• **StringDeletion**: `Object`\n\n---\n\n### StringInsertion\n\n• **StringInsertion**: `Object`\n\n---\n\n## Workspace Interfaces\n\n### ExecutorContext\n\n• **ExecutorContext**: `Object`\n\n---\n\n### ExecutorsJson\n\n• **ExecutorsJson**: `Object`\n\n---\n\n### GeneratorsJson\n\n• **GeneratorsJson**: `Object`\n\n---\n\n### HasherContext\n\n• **HasherContext**: `Object`\n\n---\n\n### ImplicitJsonSubsetDependency\n\n• **ImplicitJsonSubsetDependency**<`T`\\>: `Object`\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :------------------ |\n| `T` | `\"*\"` \\| `string`[] |\n\n---\n\n### MigrationsJson\n\n• **MigrationsJson**: `Object`\n\n---\n\n### NxAffectedConfig\n\n• **NxAffectedConfig**: `Object`\n\n---\n\n### NxJsonConfiguration\n\n• **NxJsonConfiguration**<`T`\\>: `Object`\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :------------------ |\n| `T` | `\"*\"` \\| `string`[] |\n\n---\n\n### ProjectConfiguration\n\n• **ProjectConfiguration**: `Object`\n\n---\n\n### ProjectsConfigurations\n\n• **ProjectsConfigurations**: `Object`\n\n---\n\n### TargetConfiguration\n\n• **TargetConfiguration**<`T`\\>: `Object`\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n---\n\n### TargetDependencyConfig\n\n• **TargetDependencyConfig**: `Object`\n\n---\n\n### Task\n\n• **Task**: `Object`\n\n---\n\n### TaskGraph\n\n• **TaskGraph**: `Object`\n\n---\n\n### Workspace\n\n• **Workspace**: `Object`\n\n## Generators Type aliases\n\n### WorkspaceConfiguration\n\nƬ **WorkspaceConfiguration**: `Omit`<[`ProjectsConfigurations`](../../devkit/index#projectsconfigurations), `\"projects\"`\\> & `Partial`<[`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)\\>\n\n---\n\n## Other Type aliases\n\n### ProjectTargetConfigurator\n\nƬ **ProjectTargetConfigurator**: (`file`: `string`) => `Record`<`string`, [`TargetConfiguration`](../../devkit/index#targetconfiguration)\\>\n\n#### Type declaration\n\n▸ (`file`): `Record`<`string`, [`TargetConfiguration`](../../devkit/index#targetconfiguration)\\>\n\n##### Parameters\n\n| Name | Type |\n| :----- | :------- |\n| `file` | `string` |\n\n##### Returns\n\n`Record`<`string`, [`TargetConfiguration`](../../devkit/index#targetconfiguration)\\>\n\n---\n\n## Package Manager Type aliases\n\n### PackageManager\n\nƬ **PackageManager**: `\"yarn\"` \\| `\"pnpm\"` \\| `\"npm\"`\n\n---\n\n## Project Graph Type aliases\n\n### ProjectGraphNode\n\nƬ **ProjectGraphNode**<`T`\\>: [`ProjectGraphProjectNode`](../../devkit/index#projectgraphprojectnode)<`T`\\> \\| [`ProjectGraphExternalNode`](../../devkit/index#projectgraphexternalnode)\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n---\n\n## Utils Type aliases\n\n### StringChange\n\nƬ **StringChange**: [`StringInsertion`](../../devkit/index#stringinsertion) \\| [`StringDeletion`](../../devkit/index#stringdeletion)\n\n---\n\n## Workspace Type aliases\n\n### CustomHasher\n\nƬ **CustomHasher**: (`task`: [`Task`](../../devkit/index#task), `context`: [`HasherContext`](../../devkit/index#hashercontext)) => `Promise`<[`Hash`](../../devkit/index#hash)\\>\n\n#### Type declaration\n\n▸ (`task`, `context`): `Promise`<[`Hash`](../../devkit/index#hash)\\>\n\n##### Parameters\n\n| Name | Type |\n| :-------- | :-------------------------------------------------- |\n| `task` | [`Task`](../../devkit/index#task) |\n| `context` | [`HasherContext`](../../devkit/index#hashercontext) |\n\n##### Returns\n\n`Promise`<[`Hash`](../../devkit/index#hash)\\>\n\n---\n\n### Executor\n\nƬ **Executor**<`T`\\>: (`options`: `T`, `context`: [`ExecutorContext`](../../devkit/index#executorcontext)) => `Promise`<`Object`\\> \\| `AsyncIterableIterator`<`Object`\\>\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n#### Type declaration\n\n▸ (`options`, `context`): `Promise`<`Object`\\> \\| `AsyncIterableIterator`<`Object`\\>\n\nImplementation of a target of a project\n\n##### Parameters\n\n| Name | Type |\n| :-------- | :------------------------------------------------------ |\n| `options` | `T` |\n| `context` | [`ExecutorContext`](../../devkit/index#executorcontext) |\n\n##### Returns\n\n`Promise`<`Object`\\> \\| `AsyncIterableIterator`<`Object`\\>\n\n---\n\n### Generator\n\nƬ **Generator**<`T`\\>: (`tree`: `any`, `schema`: `T`) => `void` \\| [`GeneratorCallback`](../../devkit/index#generatorcallback) \\| `Promise`<`void` \\| [`GeneratorCallback`](../../devkit/index#generatorcallback)\\>\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :-------- |\n| `T` | `unknown` |\n\n#### Type declaration\n\n▸ (`tree`, `schema`): `void` \\| [`GeneratorCallback`](../../devkit/index#generatorcallback) \\| `Promise`<`void` \\| [`GeneratorCallback`](../../devkit/index#generatorcallback)\\>\n\nA function that schedules updates to the filesystem to be done atomically\n\n##### Parameters\n\n| Name | Type |\n| :------- | :---- |\n| `tree` | `any` |\n| `schema` | `T` |\n\n##### Returns\n\n`void` \\| [`GeneratorCallback`](../../devkit/index#generatorcallback) \\| `Promise`<`void` \\| [`GeneratorCallback`](../../devkit/index#generatorcallback)\\>\n\n---\n\n### GeneratorCallback\n\nƬ **GeneratorCallback**: () => `void` \\| `Promise`<`void`\\>\n\n#### Type declaration\n\n▸ (): `void` \\| `Promise`<`void`\\>\n\nA callback function that is executed after changes are made to the file system\n\n##### Returns\n\n`void` \\| `Promise`<`void`\\>\n\n---\n\n### ImplicitDependencyEntry\n\nƬ **ImplicitDependencyEntry**<`T`\\>: `Object`\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :------------------ |\n| `T` | `\"*\"` \\| `string`[] |\n\n#### Index signature\n\n▪ [key: `string`]: `T` \\| [`ImplicitJsonSubsetDependency`](../../devkit/index#implicitjsonsubsetdependency)<`T`\\>\n\n---\n\n### ProjectType\n\nƬ **ProjectType**: `\"library\"` \\| `\"application\"`\n\n---\n\n### TaskGraphExecutor\n\nƬ **TaskGraphExecutor**<`T`\\>: (`taskGraph`: [`TaskGraph`](../../devkit/index#taskgraph), `options`: `Record`<`string`, `T`\\>, `overrides`: `T`, `context`: [`ExecutorContext`](../../devkit/index#executorcontext)) => `Promise`<`Record`<`string`, `Object`\\>\\>\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n#### Type declaration\n\n▸ (`taskGraph`, `options`, `overrides`, `context`): `Promise`<`Record`<`string`, `Object`\\>\\>\n\nImplementation of a target of a project that handles multiple projects to be batched\n\n##### Parameters\n\n| Name | Type |\n| :---------- | :------------------------------------------------------ |\n| `taskGraph` | [`TaskGraph`](../../devkit/index#taskgraph) |\n| `options` | `Record`<`string`, `T`\\> |\n| `overrides` | `T` |\n| `context` | [`ExecutorContext`](../../devkit/index#executorcontext) |\n\n##### Returns\n\n`Promise`<`Record`<`string`, `Object`\\>\\>\n\n---\n\n### WorkspaceJsonConfiguration\n\nƬ **WorkspaceJsonConfiguration**: [`ProjectsConfigurations`](../../devkit/index#projectsconfigurations)\n\n## Logger Variables\n\n### logger\n\n• **logger**: `Object`\n\n#### Type declaration\n\n| Name | Type |\n| :------ | :-------------------------- |\n| `debug` | (...`s`: `any`[]) => `void` |\n| `error` | (`s`: `any`) => `void` |\n| `fatal` | (...`s`: `any`[]) => `void` |\n| `info` | (`s`: `any`) => `void` |\n| `log` | (...`s`: `any`[]) => `void` |\n| `warn` | (`s`: `any`) => `void` |\n\n---\n\n## Utils Variables\n\n### appRootPath\n\n• **appRootPath**: `string` = `workspaceRoot`\n\n---\n\n### cacheDir\n\n• **cacheDir**: `string`\n\n---\n\n### output\n\n• **output**: `CLIOutput`\n\n---\n\n### workspaceRoot\n\n• **workspaceRoot**: `string`\n\n## Functions\n\n### addDependenciesToPackageJson\n\n▸ **addDependenciesToPackageJson**(`tree`, `dependencies`, `devDependencies`, `packageJsonPath?`): [`GeneratorCallback`](../../devkit/index#generatorcallback)\n\nAdd Dependencies and Dev Dependencies to package.json\n\nFor example:\n\n```typescript\naddDependenciesToPackageJson(tree, { react: 'latest' }, { jest: 'latest' });\n```\n\nThis will **add** `react` and `jest` to the dependencies and devDependencies sections of package.json respectively.\n\n#### Parameters\n\n| Name | Type | Default value | Description |\n| :---------------- | :-------------------------------- | :--------------- | :---------------------------------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | `undefined` | Tree representing file system to modify |\n| `dependencies` | `Record`<`string`, `string`\\> | `undefined` | Dependencies to be added to the dependencies section of package.json |\n| `devDependencies` | `Record`<`string`, `string`\\> | `undefined` | Dependencies to be added to the devDependencies section of package.json |\n| `packageJsonPath` | `string` | `'package.json'` | Path to package.json |\n\n#### Returns\n\n[`GeneratorCallback`](../../devkit/index#generatorcallback)\n\nCallback to install dependencies only if necessary. undefined is returned if changes are not necessary.\n\n---\n\n### addProjectConfiguration\n\n▸ **addProjectConfiguration**(`tree`, `projectName`, `projectConfiguration`, `standalone?`): `void`\n\nAdds project configuration to the Nx workspace.\n\nThe project configuration is stored in workspace.json or the associated project.json file.\nThe utility will update either files.\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------------------- | :---------------------------------------------------------------- | :----------------------------------------------------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | the file system tree |\n| `projectName` | `string` | unique name. Often directories are part of the name (e.g., mydir-mylib) |\n| `projectConfiguration` | [`ProjectConfiguration`](../../devkit/index#projectconfiguration) | project configuration |\n| `standalone?` | `boolean` | should the project use package.json? If false, the project config is inside workspace.json |\n\n#### Returns\n\n`void`\n\n---\n\n### applyChangesToString\n\n▸ **applyChangesToString**(`text`, `changes`): `string`\n\nApplies a list of changes to a string's original value.\n\nThis is useful when working with ASTs.\n\nFor Example, to rename a property in a method's options:\n\n```typescript\nconst code = `bootstrap({\n target: document.querySelector('#app')\n})`;\n\nconst indexOfPropertyName = 13; // Usually determined by analyzing an AST.\nconst updatedCode = applyChangesToString(code, [\n {\n type: ChangeType.Insert,\n index: indexOfPropertyName,\n text: 'element',\n },\n {\n type: ChangeType.Delete,\n start: indexOfPropertyName,\n length: 6,\n },\n]);\n\nbootstrap({\n element: document.querySelector('#app'),\n});\n```\n\n#### Parameters\n\n| Name | Type |\n| :-------- | :-------------------------------------------------- |\n| `text` | `string` |\n| `changes` | [`StringChange`](../../devkit/index#stringchange)[] |\n\n#### Returns\n\n`string`\n\n---\n\n### convertNxExecutor\n\n▸ **convertNxExecutor**(`executor`): `any`\n\nConvert an Nx Executor into an Angular Devkit Builder\n\nUse this to expose a compatible Angular Builder\n\n#### Parameters\n\n| Name | Type |\n| :--------- | :------------------------------------------------ |\n| `executor` | [`Executor`](../../devkit/index#executor)<`any`\\> |\n\n#### Returns\n\n`any`\n\n---\n\n### convertNxGenerator\n\n▸ **convertNxGenerator**<`T`\\>(`generator`, `skipWritingConfigInOldFormat?`): (`generatorOptions`: `T`) => (`tree`: `any`, `context`: `any`) => `Promise`<`any`\\>\n\nConvert an Nx Generator into an Angular Devkit Schematic.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n#### Parameters\n\n| Name | Type | Default value | Description |\n| :----------------------------- | :------------------------------------------------ | :------------ | :------------------------------------------------------------------------------------------------ |\n| `generator` | [`Generator`](../../devkit/index#generator)<`T`\\> | `undefined` | The Nx generator to convert to an Angular Devkit Schematic. |\n| `skipWritingConfigInOldFormat` | `boolean` | `false` | Whether to skip writing the configuration in the old format (the one used by the Angular DevKit). |\n\n#### Returns\n\n`fn`\n\n▸ (`generatorOptions`): (`tree`: `any`, `context`: `any`) => `Promise`<`any`\\>\n\n##### Parameters\n\n| Name | Type |\n| :----------------- | :--- |\n| `generatorOptions` | `T` |\n\n##### Returns\n\n`fn`\n\n▸ (`tree`, `context`): `Promise`<`any`\\>\n\n##### Parameters\n\n| Name | Type |\n| :-------- | :---- |\n| `tree` | `any` |\n| `context` | `any` |\n\n##### Returns\n\n`Promise`<`any`\\>\n\n---\n\n### createProjectGraphAsync\n\n▸ **createProjectGraphAsync**(): `Promise`<[`ProjectGraph`](../../devkit/index#projectgraph)\\>\n\nComputes and returns a ProjectGraph.\n\nNx will compute the graph either in a daemon process or in the current process.\n\nNx will compute it in the current process if:\n\n- The process is running in CI (CI env variable is to true or other common variables used by CI providers are set).\n- It is running in the docker container.\n- The daemon process is disabled because of the previous error when starting the daemon.\n- `NX_DAEMON` is set to `false`.\n- `useDaemon` is set to false in `nx.json`\n\n`NX_DAEMON` env variable takes precedence:\n\n- If it is set to true, the daemon will always be used.\n- If it is set to false, the graph will always be computed in the current process.\n\nTip: If you want to debug project graph creation, run your command with NX_DAEMON=false.\n\nNx uses two layers of caching: the information about explicit dependencies stored on the disk and the information\nstored in the daemon process. To reset both run: `nx reset`.\n\n#### Returns\n\n`Promise`<[`ProjectGraph`](../../devkit/index#projectgraph)\\>\n\n---\n\n### defaultTasksRunner\n\n▸ `Const` **defaultTasksRunner**(`tasks`, `options`, `context?`): `any`\n\n#### Parameters\n\n| Name | Type |\n| :--------------------------- | :------------------------------------------------------------------------------------ |\n| `tasks` | [`Task`](../../devkit/index#task)[] |\n| `options` | [`DefaultTasksRunnerOptions`](../../devkit/index#defaulttasksrunneroptions) |\n| `context?` | `Object` |\n| `context.initiatingProject?` | `string` |\n| `context.nxArgs` | `NxArgs` |\n| `context.nxJson` | [`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)<`string`[] \\| `\"*\"`\\> |\n| `context.projectGraph` | [`ProjectGraph`](../../devkit/index#projectgraph)<`any`\\> |\n| `context.target?` | `string` |\n| `context.taskGraph?` | [`TaskGraph`](../../devkit/index#taskgraph) |\n\n#### Returns\n\n`any`\n\n---\n\n### detectPackageManager\n\n▸ **detectPackageManager**(`dir?`): [`PackageManager`](../../devkit/index#packagemanager)\n\nDetects which package manager is used in the workspace based on the lock file.\n\n#### Parameters\n\n| Name | Type | Default value |\n| :---- | :------- | :------------ |\n| `dir` | `string` | `''` |\n\n#### Returns\n\n[`PackageManager`](../../devkit/index#packagemanager)\n\n---\n\n### formatFiles\n\n▸ **formatFiles**(`tree`): `Promise`<`void`\\>\n\nFormats all the created or updated files using Prettier\n\n#### Parameters\n\n| Name | Type | Description |\n| :----- | :-------------------------------- | :------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | the file system tree |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### generateFiles\n\n▸ **generateFiles**(`tree`, `srcFolder`, `target`, `substitutions`): `void`\n\nGenerates a folder of files based on provided templates.\n\nWhile doing so it performs two substitutions:\n\n- Substitutes segments of file names surrounded by \\_\\_\n- Uses ejs to substitute values in templates\n\nExamples:\n\n```typescript\ngenerateFiles(tree, path.join(__dirname, 'files'), './tools/scripts', {\n tmpl: '',\n name: 'myscript',\n});\n```\n\nThis command will take all the files from the `files` directory next to the place where the command is invoked from.\nIt will replace all `__tmpl__` with '' and all `__name__` with 'myscript' in the file names, and will replace all\n`<%= name %>` with `myscript` in the files themselves.\n`tmpl: ''` is a common pattern. With it you can name files like this: `index.ts__tmpl__`, so your editor\ndoesn't get confused about incorrect TypeScript files.\n\n#### Parameters\n\n| Name | Type | Description |\n| :-------------- | :-------------------------------- | :-------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | the file system tree |\n| `srcFolder` | `string` | the source folder of files (absolute path) |\n| `target` | `string` | the target folder (relative to the tree root) |\n| `substitutions` | `Object` | an object of key-value pairs |\n\n#### Returns\n\n`void`\n\n---\n\n### getOutputsForTargetAndConfiguration\n\n▸ **getOutputsForTargetAndConfiguration**(`task`, `node`): `any`\n\nReturns the list of outputs that will be cached.\n\n#### Parameters\n\n| Name | Type | Description |\n| :----- | :------------------------------------------------------------------------------ | :-------------------------------------------------------- |\n| `task` | `Pick`<[`Task`](../../devkit/index#task), `\"target\"` \\| `\"overrides\"`\\> | target + overrides |\n| `node` | [`ProjectGraphProjectNode`](../../devkit/index#projectgraphprojectnode)<`any`\\> | ProjectGraphProjectNode object that the task runs against |\n\n#### Returns\n\n`any`\n\n---\n\n### getPackageManagerCommand\n\n▸ **getPackageManagerCommand**(`packageManager?`): `PackageManagerCommands`\n\nReturns commands for the package manager used in the workspace.\nBy default, the package manager is derived based on the lock file,\nbut it can also be passed in explicitly.\n\nExample:\n\n```javascript\nexecSync(`${getPackageManagerCommand().addDev} my-dev-package`);\n```\n\n#### Parameters\n\n| Name | Type |\n| :--------------- | :---------------------------------------------------- |\n| `packageManager` | [`PackageManager`](../../devkit/index#packagemanager) |\n\n#### Returns\n\n`PackageManagerCommands`\n\n---\n\n### getPackageManagerVersion\n\n▸ **getPackageManagerVersion**(`packageManager?`): `string`\n\nReturns the version of the package manager used in the workspace.\nBy default, the package manager is derived based on the lock file,\nbut it can also be passed in explicitly.\n\n#### Parameters\n\n| Name | Type |\n| :--------------- | :---------------------------------------------------- |\n| `packageManager` | [`PackageManager`](../../devkit/index#packagemanager) |\n\n#### Returns\n\n`string`\n\n---\n\n### getProjects\n\n▸ **getProjects**(`tree`): `Map`<`string`, [`ProjectConfiguration`](../../devkit/index#projectconfiguration)\\>\n\nGet a map of all projects in a workspace.\n\nUse [readProjectConfiguration](../../devkit/index#readprojectconfiguration) if only one project is needed.\n\n#### Parameters\n\n| Name | Type |\n| :----- | :-------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n\n#### Returns\n\n`Map`<`string`, [`ProjectConfiguration`](../../devkit/index#projectconfiguration)\\>\n\n---\n\n### getWorkspaceLayout\n\n▸ **getWorkspaceLayout**(`tree`): `Object`\n\nReturns workspace defaults. It includes defaults folders for apps and libs,\nand the default scope.\n\nExample:\n\n```typescript\n{ appsDir: 'apps', libsDir: 'libs', npmScope: 'myorg' }\n```\n\n#### Parameters\n\n| Name | Type | Description |\n| :----- | :-------------------------------- | :--------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | file system tree |\n\n#### Returns\n\n`Object`\n\n| Name | Type |\n| :-------------------- | :-------- |\n| `appsDir` | `string` |\n| `libsDir` | `string` |\n| `npmScope` | `string` |\n| `standaloneAsDefault` | `boolean` |\n\n---\n\n### getWorkspacePath\n\n▸ **getWorkspacePath**(`tree`): `\"/angular.json\"` \\| `\"/workspace.json\"` \\| `null`\n\n#### Parameters\n\n| Name | Type |\n| :----- | :-------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n\n#### Returns\n\n`\"/angular.json\"` \\| `\"/workspace.json\"` \\| `null`\n\n---\n\n### installPackagesTask\n\n▸ **installPackagesTask**(`tree`, `alwaysRun?`, `cwd?`, `packageManager?`): `void`\n\nRuns `npm install` or `yarn install`. It will skip running the install if\n`package.json` hasn't changed at all or it hasn't changed since the last invocation.\n\n#### Parameters\n\n| Name | Type | Default value | Description |\n| :--------------- | :---------------------------------------------------- | :------------ | :------------------------------------------------------------ |\n| `tree` | [`Tree`](../../devkit/index#tree) | `undefined` | the file system tree |\n| `alwaysRun` | `boolean` | `false` | always run the command even if `package.json` hasn't changed. |\n| `cwd` | `string` | `''` | - |\n| `packageManager` | [`PackageManager`](../../devkit/index#packagemanager) | `undefined` | - |\n\n#### Returns\n\n`void`\n\n---\n\n### isStandaloneProject\n\n▸ **isStandaloneProject**(`tree`, `project`): `boolean`\n\nReturns if a project has a standalone configuration (project.json).\n\n#### Parameters\n\n| Name | Type | Description |\n| :-------- | :-------------------------------- | :------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | the file system tree |\n| `project` | `string` | the project name |\n\n#### Returns\n\n`boolean`\n\n---\n\n### joinPathFragments\n\n▸ **joinPathFragments**(...`fragments`): `string`\n\nNormalized path fragments and joins them\n\n#### Parameters\n\n| Name | Type |\n| :------------- | :--------- |\n| `...fragments` | `string`[] |\n\n#### Returns\n\n`string`\n\n---\n\n### moveFilesToNewDirectory\n\n▸ **moveFilesToNewDirectory**(`tree`, `oldDir`, `newDir`): `void`\n\nAnalogous to cp -r oldDir newDir\n\n#### Parameters\n\n| Name | Type |\n| :------- | :-------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n| `oldDir` | `string` |\n| `newDir` | `string` |\n\n#### Returns\n\n`void`\n\n---\n\n### names\n\n▸ **names**(`name`): `Object`\n\nUtil function to generate different strings based off the provided name.\n\nExamples:\n\n```typescript\nnames('my-name'); // {name: 'my-name', className: 'MyName', propertyName: 'myName', constantName: 'MY_NAME', fileName: 'my-name'}\nnames('myName'); // {name: 'myName', className: 'MyName', propertyName: 'myName', constantName: 'MY_NAME', fileName: 'my-name'}\n```\n\n#### Parameters\n\n| Name | Type |\n| :----- | :------- |\n| `name` | `string` |\n\n#### Returns\n\n`Object`\n\n| Name | Type |\n| :------------- | :------- |\n| `className` | `string` |\n| `constantName` | `string` |\n| `fileName` | `string` |\n| `name` | `string` |\n| `propertyName` | `string` |\n\n---\n\n### normalizePath\n\n▸ **normalizePath**(`osSpecificPath`): `string`\n\nCoverts an os specific path to a unix style path\n\n#### Parameters\n\n| Name | Type |\n| :--------------- | :------- |\n| `osSpecificPath` | `string` |\n\n#### Returns\n\n`string`\n\n---\n\n### offsetFromRoot\n\n▸ **offsetFromRoot**(`fullPathToDir`): `string`\n\nCalculates an offset from the root of the workspace, which is useful for\nconstructing relative URLs.\n\nExamples:\n\n```typescript\noffsetFromRoot('apps/mydir/myapp/'); // returns \"../../../\"\n```\n\n#### Parameters\n\n| Name | Type | Description |\n| :-------------- | :------- | :------------- |\n| `fullPathToDir` | `string` | directory path |\n\n#### Returns\n\n`string`\n\n---\n\n### parseJson\n\n▸ **parseJson**<`T`\\>(`input`, `options?`): `T`\n\nParses the given JSON string and returns the object the JSON content represents.\nBy default javascript-style comments are allowed.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :----------------------- |\n| `T` | extends `object` = `any` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------- | :-------------------------------------------------------- | :--------------------- |\n| `input` | `string` | JSON content as string |\n| `options?` | [`JsonParseOptions`](../../devkit/index#jsonparseoptions) | JSON parse options |\n\n#### Returns\n\n`T`\n\nObject the JSON content represents\n\n---\n\n### parseTargetString\n\n▸ **parseTargetString**(`targetString`): [`Target`](../../devkit/index#target)\n\nParses a target string into {project, target, configuration}\n\nExamples:\n\n```typescript\nparseTargetString('proj:test'); // returns { project: \"proj\", target: \"test\" }\nparseTargetString('proj:test:production'); // returns { project: \"proj\", target: \"test\", configuration: \"production\" }\n```\n\n#### Parameters\n\n| Name | Type | Description |\n| :------------- | :------- | :--------------- |\n| `targetString` | `string` | target reference |\n\n#### Returns\n\n[`Target`](../../devkit/index#target)\n\n---\n\n### readAllWorkspaceConfiguration\n\n▸ **readAllWorkspaceConfiguration**(): [`ProjectsConfigurations`](../../devkit/index#projectsconfigurations) & [`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)\n\n#### Returns\n\n[`ProjectsConfigurations`](../../devkit/index#projectsconfigurations) & [`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)\n\n---\n\n### readCachedProjectGraph\n\n▸ **readCachedProjectGraph**(): [`ProjectGraph`](../../devkit/index#projectgraph)\n\nSynchronously reads the latest cached copy of the workspace's ProjectGraph.\n\n**`throws`** {Error} if there is no cached ProjectGraph to read from\n\n#### Returns\n\n[`ProjectGraph`](../../devkit/index#projectgraph)\n\n---\n\n### readJson\n\n▸ **readJson**<`T`\\>(`tree`, `path`, `options?`): `T`\n\nReads a json file, removes all comments and parses JSON.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :----------------------- |\n| `T` | extends `object` = `any` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------- | :-------------------------------------------------------- | :-------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | file system tree |\n| `path` | `string` | file path |\n| `options?` | [`JsonParseOptions`](../../devkit/index#jsonparseoptions) | Optional JSON Parse Options |\n\n#### Returns\n\n`T`\n\n---\n\n### readJsonFile\n\n▸ **readJsonFile**<`T`\\>(`path`, `options?`): `T`\n\nReads a JSON file and returns the object the JSON content represents.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :----------------------- |\n| `T` | extends `object` = `any` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------- | :---------------- | :----------------- |\n| `path` | `string` | A path to a file. |\n| `options?` | `JsonReadOptions` | JSON parse options |\n\n#### Returns\n\n`T`\n\nObject the JSON content of the file represents\n\n---\n\n### readNxJson\n\n▸ **readNxJson**(): [`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)\n\n#### Returns\n\n[`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)\n\n---\n\n### readProjectConfiguration\n\n▸ **readProjectConfiguration**(`tree`, `projectName`): [`ProjectConfiguration`](../../devkit/index#projectconfiguration)\n\nReads a project configuration.\n\nThe project configuration is stored in workspace.json or the associated project.json file.\nThe utility will read from either file.\n\n**`throws`** If supplied projectName cannot be found\n\n#### Parameters\n\n| Name | Type | Description |\n| :------------ | :-------------------------------- | :---------------------------------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | the file system tree |\n| `projectName` | `string` | unique name. Often directories are part of the name (e.g., mydir-mylib) |\n\n#### Returns\n\n[`ProjectConfiguration`](../../devkit/index#projectconfiguration)\n\n---\n\n### readTargetOptions\n\n▸ **readTargetOptions**<`T`\\>(`__namedParameters`, `context`): `T`\n\nReads and combines options for a given target.\n\nWorks as if you invoked the target yourself without passing any command lint overrides.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :---- |\n| `T` | `any` |\n\n#### Parameters\n\n| Name | Type |\n| :------------------ | :------------------------------------------------------ |\n| `__namedParameters` | [`Target`](../../devkit/index#target) |\n| `context` | [`ExecutorContext`](../../devkit/index#executorcontext) |\n\n#### Returns\n\n`T`\n\n---\n\n### readWorkspaceConfiguration\n\n▸ **readWorkspaceConfiguration**(`tree`): [`WorkspaceConfiguration`](../../devkit/index#workspaceconfiguration)\n\nRead general workspace configuration such as the default project or cli settings\n\nThis does _not_ provide projects configuration, use [readProjectConfiguration](../../devkit/index#readprojectconfiguration) instead.\n\n#### Parameters\n\n| Name | Type |\n| :----- | :-------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n\n#### Returns\n\n[`WorkspaceConfiguration`](../../devkit/index#workspaceconfiguration)\n\n---\n\n### removeDependenciesFromPackageJson\n\n▸ **removeDependenciesFromPackageJson**(`tree`, `dependencies`, `devDependencies`, `packageJsonPath?`): [`GeneratorCallback`](../../devkit/index#generatorcallback)\n\nRemove Dependencies and Dev Dependencies from package.json\n\nFor example:\n\n```typescript\nremoveDependenciesFromPackageJson(tree, ['react'], ['jest']);\n```\n\nThis will **remove** `react` and `jest` from the dependencies and devDependencies sections of package.json respectively.\n\n#### Parameters\n\n| Name | Type | Default value | Description |\n| :---------------- | :-------------------------------- | :--------------- | :-------------------------------------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | `undefined` | - |\n| `dependencies` | `string`[] | `undefined` | Dependencies to be removed from the dependencies section of package.json |\n| `devDependencies` | `string`[] | `undefined` | Dependencies to be removed from the devDependencies section of package.json |\n| `packageJsonPath` | `string` | `'package.json'` | - |\n\n#### Returns\n\n[`GeneratorCallback`](../../devkit/index#generatorcallback)\n\nCallback to uninstall dependencies only if necessary. undefined is returned if changes are not necessary.\n\n---\n\n### removeProjectConfiguration\n\n▸ **removeProjectConfiguration**(`tree`, `projectName`): `void`\n\nRemoves the configuration of an existing project.\n\nThe project configuration is stored in workspace.json or the associated project.json file.\nThe utility will update either file.\n\n#### Parameters\n\n| Name | Type |\n| :------------ | :-------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n| `projectName` | `string` |\n\n#### Returns\n\n`void`\n\n---\n\n### reverse\n\n▸ **reverse**(`graph`): [`ProjectGraph`](../../devkit/index#projectgraph)\n\nReturns a new project graph where all the edges are reversed.\n\nFor instance, if project A depends on B, in the reversed graph\nB will depend on A.\n\n#### Parameters\n\n| Name | Type |\n| :------ | :-------------------------------------------------------- |\n| `graph` | [`ProjectGraph`](../../devkit/index#projectgraph)<`any`\\> |\n\n#### Returns\n\n[`ProjectGraph`](../../devkit/index#projectgraph)\n\n---\n\n### runExecutor\n\n▸ **runExecutor**<`T`\\>(`targetDescription`, `options`, `context`): `Promise`<`AsyncIterableIterator`<`T`\\>\\>\n\nLoads and invokes executor.\n\nThis is analogous to invoking executor from the terminal, with the exception\nthat the params aren't parsed from the string, but instead provided parsed already.\n\nApart from that, it works the same way:\n\n- it will load the workspace configuration\n- it will resolve the target\n- it will load the executor and the schema\n- it will load the options for the appropriate configuration\n- it will run the validations and will set the default\n- and, of course, it will invoke the executor\n\nExample:\n\n```typescript\nfor await (const s of await runExecutor(\n { project: 'myproj', target: 'serve' },\n { watch: true },\n context\n)) {\n // s.success\n}\n```\n\nNote that the return value is a promise of an iterator, so you need to await before iterating over it.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :--------------- |\n| `T` | extends `Object` |\n\n#### Parameters\n\n| Name | Type |\n| :--------------------------------- | :------------------------------------------------------ |\n| `targetDescription` | `Object` |\n| `targetDescription.configuration?` | `string` |\n| `targetDescription.project` | `string` |\n| `targetDescription.target` | `string` |\n| `options` | `Object` |\n| `context` | [`ExecutorContext`](../../devkit/index#executorcontext) |\n\n#### Returns\n\n`Promise`<`AsyncIterableIterator`<`T`\\>\\>\n\n---\n\n### serializeJson\n\n▸ **serializeJson**<`T`\\>(`input`, `options?`): `string`\n\nSerializes the given data to a JSON string.\nBy default the JSON string is formatted with a 2 space intendation to be easy readable.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :-------------------------- |\n| `T` | extends `object` = `object` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------- | :---------------------------------------------------------------- | :---------------------------------------- |\n| `input` | `T` | Object which should be serialized to JSON |\n| `options?` | [`JsonSerializeOptions`](../../devkit/index#jsonserializeoptions) | JSON serialize options |\n\n#### Returns\n\n`string`\n\nthe formatted JSON representation of the object\n\n---\n\n### stripIndents\n\n▸ **stripIndents**(`strings`, ...`values`): `string`\n\nRemoves indents, which is useful for printing warning and messages.\n\nExample:\n\n```typescript\nstripIndents`\n Options:\n - option1\n - option2\n`;\n```\n\n#### Parameters\n\n| Name | Type |\n| :---------- | :--------------------- |\n| `strings` | `TemplateStringsArray` |\n| `...values` | `any`[] |\n\n#### Returns\n\n`string`\n\n---\n\n### stripJsonComments\n\n▸ `Const` **stripJsonComments**(`text`, `replaceCh?`): `string`\n\nTakes JSON with JavaScript-style comments and remove\nthem. Optionally replaces every none-newline character\nof comments with a replaceCharacter\n\n#### Parameters\n\n| Name | Type |\n| :----------- | :------- |\n| `text` | `string` |\n| `replaceCh?` | `string` |\n\n#### Returns\n\n`string`\n\n---\n\n### targetToTargetString\n\n▸ **targetToTargetString**(`target`): `string`\n\nReturns a string in the format \"project:target[:configuration]\" for the target\n\n#### Parameters\n\n| Name | Type | Description |\n| :------- | :------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `target` | [`Target`](../../devkit/index#target) | target object Examples: `typescript targetToTargetString({ project: \"proj\", target: \"test\" }) // returns \"proj:test\" targetToTargetString({ project: \"proj\", target: \"test\", configuration: \"production\" }) // returns \"proj:test:production\" ` |\n\n#### Returns\n\n`string`\n\n---\n\n### toJS\n\n▸ **toJS**(`tree`): `void`\n\nRename and transpile any new typescript files created to javascript files\n\n#### Parameters\n\n| Name | Type |\n| :----- | :-------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n\n#### Returns\n\n`void`\n\n---\n\n### updateJson\n\n▸ **updateJson**<`T`, `U`\\>(`tree`, `path`, `updater`, `options?`): `void`\n\nUpdates a JSON value to the file system tree\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :----------------------- |\n| `T` | extends `object` = `any` |\n| `U` | extends `object` = `T` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------- | :---------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | File system tree |\n| `path` | `string` | Path of JSON file in the Tree |\n| `updater` | (`value`: `T`) => `U` | Function that maps the current value of a JSON document to a new value to be written to the document |\n| `options?` | [`JsonParseOptions`](../../devkit/index#jsonparseoptions) & [`JsonSerializeOptions`](../../devkit/index#jsonserializeoptions) | Optional JSON Parse and Serialize Options |\n\n#### Returns\n\n`void`\n\n---\n\n### updateProjectConfiguration\n\n▸ **updateProjectConfiguration**(`tree`, `projectName`, `projectConfiguration`): `void`\n\nUpdates the configuration of an existing project.\n\nThe project configuration is stored in workspace.json or the associated project.json file.\nThe utility will update either files.\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------------------- | :---------------------------------------------------------------- | :---------------------------------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) | the file system tree |\n| `projectName` | `string` | unique name. Often directories are part of the name (e.g., mydir-mylib) |\n| `projectConfiguration` | [`ProjectConfiguration`](../../devkit/index#projectconfiguration) | project configuration |\n\n#### Returns\n\n`void`\n\n---\n\n### updateTsConfigsToJs\n\n▸ **updateTsConfigsToJs**(`tree`, `options`): `void`\n\n#### Parameters\n\n| Name | Type |\n| :-------------------- | :-------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n| `options` | `Object` |\n| `options.projectRoot` | `string` |\n\n#### Returns\n\n`void`\n\n---\n\n### updateWorkspaceConfiguration\n\n▸ **updateWorkspaceConfiguration**(`tree`, `workspaceConfig`): `void`\n\nUpdate general workspace configuration such as the default project or cli settings.\n\nThis does _not_ update projects configuration, use [updateProjectConfiguration](../../devkit/index#updateprojectconfiguration) or [addProjectConfiguration](../../devkit/index#addprojectconfiguration) instead.\n\n#### Parameters\n\n| Name | Type |\n| :---------------- | :-------------------------------------------------------------------- |\n| `tree` | [`Tree`](../../devkit/index#tree) |\n| `workspaceConfig` | [`WorkspaceConfiguration`](../../devkit/index#workspaceconfiguration) |\n\n#### Returns\n\n`void`\n\n---\n\n### visitNotIgnoredFiles\n\n▸ **visitNotIgnoredFiles**(`tree`, `dirPath?`, `visitor`): `void`\n\nUtility to act on all files in a tree that are not ignored by git.\n\n#### Parameters\n\n| Name | Type | Default value |\n| :-------- | :-------------------------------- | :------------ |\n| `tree` | [`Tree`](../../devkit/index#tree) | `undefined` |\n| `dirPath` | `string` | `tree.root` |\n| `visitor` | (`path`: `string`) => `void` | `undefined` |\n\n#### Returns\n\n`void`\n\n---\n\n### workspaceLayout\n\n▸ **workspaceLayout**(): `Object`\n\nReturns information about where apps and libs will be created.\n\n#### Returns\n\n`Object`\n\n| Name | Type |\n| :-------- | :------- |\n| `appsDir` | `string` |\n| `libsDir` | `string` |\n\n---\n\n### writeJson\n\n▸ **writeJson**<`T`\\>(`tree`, `path`, `value`, `options?`): `void`\n\nWrites a JSON value to the file system tree\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :-------------------------- |\n| `T` | extends `object` = `object` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------- | :---------------------------------------------------------------- | :------------------------------ |\n| `tree` | [`Tree`](../../devkit/index#tree) | File system tree |\n| `path` | `string` | Path of JSON file in the Tree |\n| `value` | `T` | Serializable value to write |\n| `options?` | [`JsonSerializeOptions`](../../devkit/index#jsonserializeoptions) | Optional JSON Serialize Options |\n\n#### Returns\n\n`void`\n\n---\n\n### writeJsonFile\n\n▸ **writeJsonFile**<`T`\\>(`path`, `data`, `options?`): `void`\n\nSerializes the given data to JSON and writes it to a file.\n\n#### Type parameters\n\n| Name | Type |\n| :--- | :-------------------------- |\n| `T` | extends `object` = `object` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :--------- | :----------------- | :-------------------------------------------------------------- |\n| `path` | `string` | A path to a file. |\n| `data` | `T` | data which should be serialized to JSON and written to the file |\n| `options?` | `JsonWriteOptions` | JSON serialize options |\n\n#### Returns\n\n`void`\n" }, { "id": "ngcli_adapter", diff --git a/docs/generated/packages/nx-plugin.json b/docs/generated/packages/nx-plugin.json index 4d215be8c37ca..4bf90da35cae0 100644 --- a/docs/generated/packages/nx-plugin.json +++ b/docs/generated/packages/nx-plugin.json @@ -50,7 +50,8 @@ "description": "The tool to use for running lint checks.", "type": "string", "enum": ["eslint", "tslint"], - "default": "eslint" + "default": "eslint", + "x-deprecated": "TSLint support is deprecated and will be removed" }, "unitTestRunner": { "type": "string", @@ -73,6 +74,11 @@ "default": false, "description": "Do not update tsconfig.json for development experience." }, + "skipLintChecks": { + "type": "boolean", + "default": false, + "description": "Do not eslint configuration for plugin json files." + }, "standaloneConfig": { "description": "Split the project configuration into `/project.json` rather than including it inside `workspace.json`.", "type": "boolean" @@ -315,6 +321,32 @@ "aliases": [], "hidden": false, "path": "/packages/nx-plugin/src/generators/executor/schema.json" + }, + { + "name": "plugin-lint-checks", + "factory": "./src/generators/lint-checks/generator", + "schema": { + "$schema": "http://json-schema.org/schema", + "cli": "nx", + "$id": "PluginLint", + "title": "", + "type": "object", + "description": "Adds linting configuration to validate common json files for nx plugins.", + "properties": { + "projectName": { + "type": "string", + "description": "Which project should be the configuration be added to?", + "$default": { "$source": "projectName" } + } + }, + "required": ["projectName"], + "presets": [] + }, + "description": "Adds linting configuration to validate common json files for nx plugins.", + "implementation": "/packages/nx-plugin/src/generators/lint-checks/generator.ts", + "aliases": [], + "hidden": false, + "path": "/packages/nx-plugin/src/generators/lint-checks/schema.json" } ], "executors": [ diff --git a/docs/packages.json b/docs/packages.json index 148d4bc9eef01..8cefca65e3d01 100644 --- a/docs/packages.json +++ b/docs/packages.json @@ -188,7 +188,8 @@ "e2e-project", "migration", "generator", - "executor" + "executor", + "plugin-lint-checks" ] } }, diff --git a/e2e/nx-plugin/src/nx-plugin.test.ts b/e2e/nx-plugin/src/nx-plugin.test.ts index cf9545a9b9cb2..6c5a1b4da9332 100644 --- a/e2e/nx-plugin/src/nx-plugin.test.ts +++ b/e2e/nx-plugin/src/nx-plugin.test.ts @@ -185,6 +185,94 @@ describe('Nx Plugin', () => { }); }, 90000); + it('should catch invalid implementations, schemas, and version in lint', async () => { + const plugin = uniq('plugin'); + const goodGenerator = uniq('good-generator'); + const goodExecutor = uniq('good-executor'); + const goodMigration = uniq('good-migration'); + const badMigrationVersion = uniq('bad-version'); + const missingMigrationVersion = uniq('missing-version'); + + // Generating the plugin results in a generator also called {plugin}, + // as well as an executor called "build" + runCLI(`generate @nrwl/nx-plugin:plugin ${plugin} --linter=eslint`); + + runCLI( + `generate @nrwl/nx-plugin:generator ${goodGenerator} --project=${plugin}` + ); + + runCLI( + `generate @nrwl/nx-plugin:executor ${goodExecutor} --project=${plugin}` + ); + + runCLI( + `generate @nrwl/nx-plugin:migration ${badMigrationVersion} --project=${plugin} --packageVersion="invalid"` + ); + + runCLI( + `generate @nrwl/nx-plugin:migration ${missingMigrationVersion} --project=${plugin} --packageVersion="0.1.0"` + ); + + runCLI( + `generate @nrwl/nx-plugin:migration ${goodMigration} --project=${plugin} --packageVersion="0.1.0"` + ); + + updateFile(`libs/${plugin}/generators.json`, (f) => { + const json = JSON.parse(f); + // @proj/plugin:plugin has an invalid implementation path + json.generators[plugin].factory = `./generators/${plugin}/bad-path`; + // @proj/plugin:non-existant has a missing implementation path amd schema + json.generators['non-existant-generator'] = {}; + return JSON.stringify(json); + }); + + updateFile(`libs/${plugin}/executors.json`, (f) => { + const json = JSON.parse(f); + // @proj/plugin:build has an invalid implementation path + json.executors['build'].implementation = './executors/build/bad-path'; + // @proj/plugin:non-existant has a missing implementation path amd schema + json.executors['non-existant-executor'] = {}; + return JSON.stringify(json); + }); + + updateFile(`libs/${plugin}/migrations.json`, (f) => { + const json = JSON.parse(f); + delete json.generators[missingMigrationVersion].version; + return JSON.stringify(json); + }); + + const results = runCLI(`lint ${plugin}`, { silenceError: true }); + expect(results).toContain( + `${plugin}: Implementation path should point to a valid file` + ); + expect(results).toContain( + `non-existant-generator: Missing required property - \`schema\`` + ); + expect(results).toContain( + `non-existant-generator: Missing required property - \`implementation\`` + ); + expect(results).not.toContain(goodGenerator); + + expect(results).toContain( + `build: Implementation path should point to a valid file` + ); + expect(results).toContain( + `non-existant-executor: Missing required property - \`schema\`` + ); + expect(results).toContain( + `non-existant-executor: Missing required property - \`implementation\`` + ); + expect(results).not.toContain(goodExecutor); + + expect(results).toContain( + `${missingMigrationVersion}: Missing required property - \`version\`` + ); + expect(results).toContain( + `${badMigrationVersion}: Version should be a valid semver` + ); + expect(results).not.toContain(goodMigration); + }); + describe('local plugins', () => { const plugin = uniq('plugin'); beforeEach(() => { diff --git a/nx.json b/nx.json index 5b331eb58c18b..76df2889d9486 100644 --- a/nx.json +++ b/nx.json @@ -20,6 +20,7 @@ "build-base", "test", "lint", + "lint-base", "e2e", "sitemap" ], diff --git a/packages/eslint-plugin-nx/package.json b/packages/eslint-plugin-nx/package.json index a938183cdee30..584169b762013 100644 --- a/packages/eslint-plugin-nx/package.json +++ b/packages/eslint-plugin-nx/package.json @@ -36,6 +36,7 @@ "@nrwl/workspace": "file:../workspace", "@typescript-eslint/experimental-utils": "~5.24.0", "chalk": "4.1.0", - "confusing-browser-globals": "^1.0.9" + "confusing-browser-globals": "^1.0.9", + "semver": "7.3.4" } } diff --git a/packages/eslint-plugin-nx/src/index.ts b/packages/eslint-plugin-nx/src/index.ts index 7d8c77452e982..052a053a43cfa 100644 --- a/packages/eslint-plugin-nx/src/index.ts +++ b/packages/eslint-plugin-nx/src/index.ts @@ -11,6 +11,10 @@ import enforceModuleBoundaries, { RULE_NAME as enforceModuleBoundariesRuleName, } from './rules/enforce-module-boundaries'; +import nxPluginChecksRule, { + RULE_NAME as nxPluginChecksRuleName, +} from './rules/nx-plugin-checks'; + // Resolve any custom rules that might exist in the current workspace import { workspaceRules } from './resolve-workspace-rules'; @@ -27,6 +31,7 @@ module.exports = { }, rules: { [enforceModuleBoundariesRuleName]: enforceModuleBoundaries, + [nxPluginChecksRuleName]: nxPluginChecksRule, ...workspaceRules, }, }; diff --git a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts index 984da7af67be5..8e7459c45175a 100644 --- a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts +++ b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts @@ -3,8 +3,6 @@ import { joinPathFragments, normalizePath, ProjectGraphExternalNode, - readCachedProjectGraph, - readNxJson, } from '@nrwl/devkit'; import { DepConstraint, @@ -21,10 +19,8 @@ import { isAbsoluteImportIntoAnotherProject, isAngularSecondaryEntrypoint, isDirectDependency, - isTerminalRun, MappedProjectGraph, MappedProjectGraphNode, - mapProjectGraphFiles, matchImportWithWildcard, onlyLoadChildren, stringifyTags, @@ -40,13 +36,16 @@ import { findFilesInCircularPath, } from '@nrwl/workspace/src/utils/graph-utils'; import { isRelativePath } from '@nrwl/workspace/src/utilities/fileutils'; -import * as chalk from 'chalk'; import { basename, dirname, relative } from 'path'; import { getBarrelEntryPointByImportScope, getBarrelEntryPointProjectNode, getRelativeImportPath, } from '../utils/ast-utils'; +import { + ensureGlobalProjectGraph, + readProjectGraph, +} from '../utils/project-graph-utils'; type Options = [ { @@ -148,40 +147,14 @@ export default createESLintRule({ const projectPath = normalizePath( (global as any).projectPath || workspaceRoot ); - /** - * Only reuse graph when running from terminal - * Enforce every IDE change to get a fresh nxdeps.json - */ - if (!(global as any).projectGraph || !isTerminalRun()) { - const nxJson = readNxJson(); - (global as any).workspaceLayout = nxJson.workspaceLayout; - - /** - * Because there are a number of ways in which the rule can be invoked (executor vs ESLint CLI vs IDE Plugin), - * the ProjectGraph may or may not exist by the time the lint rule is invoked for the first time. - */ - try { - (global as any).projectGraph = mapProjectGraphFiles( - readCachedProjectGraph() - ); - } catch { - const WARNING_PREFIX = `${chalk.reset.keyword('orange')('warning')}`; - const RULE_NAME_SUFFIX = `${chalk.reset.dim( - '@nrwl/nx/enforce-module-boundaries' - )}`; - process.stdout - .write(`${WARNING_PREFIX} No cached ProjectGraph is available. The rule will be skipped. - If you encounter this error as part of running standard \`nx\` commands then please open an issue on https://github.com/nrwl/nx - ${RULE_NAME_SUFFIX}\n`); - } - } - if (!(global as any).projectGraph) { + const projectGraph = readProjectGraph(RULE_NAME); + + if (!projectGraph) { return {}; } const workspaceLayout = (global as any).workspaceLayout; - const projectGraph = (global as any).projectGraph as MappedProjectGraph; if (!(global as any).targetProjectLocator) { (global as any).targetProjectLocator = new TargetProjectLocator( diff --git a/packages/eslint-plugin-nx/src/rules/nx-plugin-checks.ts b/packages/eslint-plugin-nx/src/rules/nx-plugin-checks.ts new file mode 100644 index 0000000000000..904f2e105e220 --- /dev/null +++ b/packages/eslint-plugin-nx/src/rules/nx-plugin-checks.ts @@ -0,0 +1,480 @@ +import type { AST } from 'jsonc-eslint-parser'; +import type { TSESLint } from '@typescript-eslint/utils'; + +import { readJsonFile, workspaceRoot } from '@nrwl/devkit'; +import { + findSourceProject, + getSourceFilePath, + MappedProjectGraphNode, +} from '@nrwl/workspace/src/utils/runtime-lint-utils'; +import { existsSync } from 'fs'; +import { registerTsProject } from 'nx/src/utils/register'; +import * as path from 'path'; + +import { createESLintRule } from '../utils/create-eslint-rule'; +import { readProjectGraph } from '../utils/project-graph-utils'; +import { valid } from 'semver'; + +type Options = [ + { + generatorsJson?: string; + executorsJson?: string; + migrationsJson?: string; + packageJson?: string; + } +]; + +const DEFAULT_OPTIONS = { + generatorsJson: 'generators.json', + executorsJson: 'executors.json', + migrationsJson: 'migrations.json', + packageJson: 'package.json', +}; + +export type MessageIds = + | 'missingRequiredSchema' + | 'invalidSchemaPath' + | 'missingImplementation' + | 'invalidImplementationPath' + | 'invalidImplementationModule' + | 'unableToReadImplementationExports' + | 'invalidVersion' + | 'missingVersion' + | 'noGeneratorsOrSchematicsFound' + | 'noExecutorsOrBuildersFound' + | 'valueShouldBeObject'; + +export const RULE_NAME = 'nx-plugin-checks'; + +export default createESLintRule({ + name: RULE_NAME, + meta: { + docs: { + description: 'Checks common nx-plugin configuration files for validity', + recommended: 'error', + }, + schema: {}, + type: 'problem', + messages: { + invalidSchemaPath: 'Schema path should point to a valid file', + invalidImplementationPath: + '{{ key }}: Implementation path should point to a valid file', + invalidImplementationModule: + '{{ key }}: Unable to find export {{ identifier }} in implementation module', + unableToReadImplementationExports: + '{{ key }}: Unable to read exports for implementation module', + invalidVersion: '{{ key }}: Version should be a valid semver', + noGeneratorsOrSchematicsFound: + 'Unable to find `generators` or `schematics` property', + noExecutorsOrBuildersFound: + 'Unable to find `executors` or `builders` property', + valueShouldBeObject: '{{ key }} should be an object', + missingRequiredSchema: '{{ key }}: Missing required property - `schema`', + missingImplementation: + '{{ key }}: Missing required property - `implementation`', + missingVersion: '{{ key }}: Missing required property - `version`', + }, + }, + defaultOptions: [DEFAULT_OPTIONS], + create(context) { + // jsonc-eslint-parser adds this property to parserServices where appropriate + if (!(context.parserServices as any).isJSON) { + return {}; + } + + const projectGraph = readProjectGraph(RULE_NAME); + + const sourceFilePath = getSourceFilePath( + context.getFilename(), + workspaceRoot + ); + + const sourceProject = findSourceProject(projectGraph, sourceFilePath); + // If source is not part of an nx workspace, return. + if (!sourceProject) { + return {}; + } + + const { generatorsJson, executorsJson, migrationsJson, packageJson } = + normalizeOptions(sourceProject, context.options[0]); + + if ( + ![generatorsJson, executorsJson, migrationsJson, packageJson].includes( + sourceFilePath + ) + ) { + return {}; + } + + if (!(global as any).tsProjectRegistered) { + registerTsProject(workspaceRoot, 'tsconfig.base.json'); + (global as any).tsProjectRegistered = true; + } + + return { + ['JSONExpressionStatement > JSONObjectExpression']( + node: AST.JSONObjectExpression + ) { + if (sourceFilePath === generatorsJson) { + checkCollectionFileNode(node, 'generator', context); + } else if (sourceFilePath === migrationsJson) { + checkCollectionFileNode(node, 'migration', context); + } else if (sourceFilePath === executorsJson) { + checkCollectionFileNode(node, 'executor', context); + } else if (sourceFilePath === packageJson) { + validatePackageGroup(node, context); + } + }, + }; + }, +}); + +function normalizeOptions( + sourceProject: MappedProjectGraphNode<{}>, + options: Options[0] +): Options[0] { + const base = { ...DEFAULT_OPTIONS, ...options }; + return { + executorsJson: base.executorsJson + ? `${sourceProject.data.root}/${base.executorsJson}` + : undefined, + generatorsJson: base.generatorsJson + ? `${sourceProject.data.root}/${base.generatorsJson}` + : undefined, + migrationsJson: base.migrationsJson + ? `${sourceProject.data.root}/${base.migrationsJson}` + : undefined, + packageJson: base.packageJson + ? `${sourceProject.data.root}/${base.packageJson}` + : undefined, + }; +} + +export function checkCollectionFileNode( + baseNode: AST.JSONObjectExpression, + mode: 'migration' | 'generator' | 'executor', + context: TSESLint.RuleContext +) { + const schematicsRootNode = baseNode.properties.find( + (x) => x.key.type === 'JSONLiteral' && x.key.value === 'schematics' + ); + const generatorsRootNode = baseNode.properties.find( + (x) => x.key.type === 'JSONLiteral' && x.key.value === 'generators' + ); + + const executorsRootNode = baseNode.properties.find( + (x) => x.key.type === 'JSONLiteral' && x.key.value === 'executors' + ); + const buildersRootNode = baseNode.properties.find( + (x) => x.key.type === 'JSONLiteral' && x.key.value === 'builders' + ); + + if (!schematicsRootNode && !generatorsRootNode && mode !== 'executor') { + context.report({ + messageId: 'noGeneratorsOrSchematicsFound', + node: baseNode as any, + }); + return; + } + + if (!executorsRootNode && !buildersRootNode && mode === 'executor') { + context.report({ + messageId: 'noExecutorsOrBuildersFound', + node: baseNode as any, + }); + return; + } + + const collectionNodes = [ + { collectionNode: schematicsRootNode, key: 'schematics' }, + { collectionNode: generatorsRootNode, key: 'generators' }, + { collectionNode: executorsRootNode, key: 'executors' }, + { collectionNode: buildersRootNode, key: 'builders' }, + ].filter(({ collectionNode }) => !!collectionNode); + + for (const { collectionNode, key } of collectionNodes) { + if (collectionNode.value.type !== 'JSONObjectExpression') { + context.report({ + messageId: 'valueShouldBeObject', + data: { key }, + node: schematicsRootNode as any, + }); + } else { + checkCollectionNode(collectionNode.value, mode, context); + } + } +} + +export function checkCollectionNode( + baseNode: AST.JSONObjectExpression, + mode: 'migration' | 'generator' | 'executor', + context: TSESLint.RuleContext +) { + const entries = baseNode.properties; + + for (const entryNode of entries) { + if (entryNode.value.type !== 'JSONObjectExpression') { + context.report({ + messageId: 'valueShouldBeObject', + data: { key: (entryNode.key as AST.JSONLiteral).value }, + node: entryNode as any, + }); + } else if (entryNode.key.type === 'JSONLiteral') { + validateEntry( + entryNode.value, + entryNode.key.value.toString(), + mode, + context + ); + } + } +} + +export function validateEntry( + baseNode: AST.JSONObjectExpression, + key: string, + mode: 'migration' | 'generator' | 'executor', + context: TSESLint.RuleContext +) { + const schemaNode = baseNode.properties.find( + (x) => x.key.type === 'JSONLiteral' && x.key.value === 'schema' + ); + if (mode !== 'migration' && !schemaNode) { + context.report({ + messageId: 'missingRequiredSchema', + data: { + key, + }, + node: baseNode as any, + }); + } else if (schemaNode) { + if ( + schemaNode.value.type !== 'JSONLiteral' || + typeof schemaNode.value.value !== 'string' + ) { + context.report({ + messageId: 'invalidSchemaPath', + node: schemaNode.value as any, + }); + } else { + const schemaFilePath = path.join( + path.dirname(context.getFilename()), + schemaNode.value.value + ); + if (!existsSync(schemaFilePath)) { + context.report({ + messageId: 'invalidSchemaPath', + node: schemaNode.value as any, + }); + } else { + try { + readJsonFile(schemaFilePath); + } catch (e) { + context.report({ + messageId: 'invalidSchemaPath', + node: schemaNode.value as any, + }); + } + } + } + } + + const implementationNode = baseNode.properties.find( + (x) => + x.key.type === 'JSONLiteral' && + (x.key.value === 'implementation' || x.key.value === 'factory') + ); + if (!implementationNode) { + context.report({ + messageId: 'missingImplementation', + data: { + key, + }, + node: baseNode as any, + }); + } else { + validateImplemenationNode(implementationNode, key, context); + } + + if (mode === 'migration') { + const versionNode = baseNode.properties.find( + (x) => x.key.type === 'JSONLiteral' && x.key.value === 'version' + ); + if (!versionNode) { + context.report({ + messageId: 'missingVersion', + data: { + key, + }, + node: baseNode as any, + }); + } else if ( + versionNode.value.type !== 'JSONLiteral' || + typeof versionNode.value.value !== 'string' + ) { + context.report({ + messageId: 'invalidVersion', + data: { + key, + }, + node: versionNode.value as any, + }); + } else { + const specifiedVersion = versionNode.value.value; + if (!valid(specifiedVersion)) { + context.report({ + messageId: 'invalidVersion', + data: { + key, + }, + node: versionNode.value as any, + }); + } + } + } +} + +export function validateImplemenationNode( + implementationNode: AST.JSONProperty, + key: string, + context: TSESLint.RuleContext +) { + if ( + implementationNode.value.type !== 'JSONLiteral' || + typeof implementationNode.value.value !== 'string' + ) { + context.report({ + messageId: 'invalidImplementationPath', + data: { + key, + }, + node: implementationNode.value as any, + }); + } else { + const [implementationPath, identifier] = + implementationNode.value.value.split('#'); + let resolvedPath: string; + + const modulePath = path.join( + path.dirname(context.getFilename()), + implementationPath + ); + + try { + resolvedPath = require.resolve(modulePath); + } catch (e) { + context.report({ + messageId: 'invalidImplementationPath', + data: { + key, + }, + node: implementationNode.value as any, + }); + } + + if (identifier) { + try { + const m = require(resolvedPath); + if (!(identifier in m && typeof m[identifier] === 'function')) { + context.report({ + messageId: 'invalidImplementationModule', + node: implementationNode.value as any, + data: { + identifier, + key, + }, + }); + } + } catch { + context.report({ + messageId: 'unableToReadImplementationExports', + node: implementationNode.value as any, + data: { + key, + }, + }); + } + } + } +} + +export function validatePackageGroup( + baseNode: AST.JSONObjectExpression, + context: TSESLint.RuleContext +) { + const migrationsNode = baseNode.properties.find( + (x) => + x.key.type === 'JSONLiteral' && + x.value.type === 'JSONObjectExpression' && + (x.key.value === 'nx-migrations' || + x.key.value === 'ng-update' || + x.key.value === 'migrations') + )?.value as AST.JSONObjectExpression; + + const packageGroupNode = migrationsNode?.properties.find( + (x) => x.key.type === 'JSONLiteral' && x.key.value === 'packageGroup' + ); + + if (packageGroupNode) { + // Package group is defined as an array + if (packageGroupNode.value.type === 'JSONArrayExpression') { + // Look at entries which are an object + const members = packageGroupNode.value.elements.filter( + (x) => x.type === 'JSONObjectExpression' + ); + // validate that the version property exists and is valid + for (const member of members) { + const versionPropertyNode = ( + member as AST.JSONObjectExpression + ).properties.find( + (x) => x.key.type === 'JSONLiteral' && x.key.value === 'version' + ); + const packageNode = ( + member as AST.JSONObjectExpression + ).properties.find( + (x) => x.key.type === 'JSONLiteral' && x.key.value === 'package' + ); + const key = (packageNode?.value as AST.JSONLiteral)?.value ?? 'unknown'; + + if (versionPropertyNode) { + if (!validateVersionJsonExpression(versionPropertyNode.value)) + context.report({ + messageId: 'invalidVersion', + data: { key }, + node: versionPropertyNode.value as any, + }); + } else { + context.report({ + messageId: 'missingVersion', + data: { key }, + node: member as any, + }); + } + } + // Package group is defined as an object (Record) + } else if (packageGroupNode.value.type === 'JSONObjectExpression') { + const properties = packageGroupNode.value.properties; + // For each property, ensure its value is a valid version + for (const propertyNode of properties) { + if (!validateVersionJsonExpression(propertyNode.value)) { + context.report({ + messageId: 'invalidVersion', + data: { + key: (propertyNode.key as AST.JSONLiteral).value, + }, + node: propertyNode.value as any, + }); + } + } + } + } +} + +export function validateVersionJsonExpression(node: AST.JSONExpression) { + return ( + node && + node.type === 'JSONLiteral' && + typeof node.value === 'string' && + valid(node.value) + ); +} diff --git a/packages/eslint-plugin-nx/src/utils/project-graph-utils.ts b/packages/eslint-plugin-nx/src/utils/project-graph-utils.ts new file mode 100644 index 0000000000000..4661ac35a8139 --- /dev/null +++ b/packages/eslint-plugin-nx/src/utils/project-graph-utils.ts @@ -0,0 +1,40 @@ +import { readCachedProjectGraph, readNxJson } from '@nrwl/devkit'; +import { + isTerminalRun, + MappedProjectGraph, + mapProjectGraphFiles, +} from '@nrwl/workspace/src/utils/runtime-lint-utils'; +import * as chalk from 'chalk'; + +export function ensureGlobalProjectGraph(ruleName: string) { + /** + * Only reuse graph when running from terminal + * Enforce every IDE change to get a fresh nxdeps.json + */ + if (!(global as any).projectGraph || !isTerminalRun()) { + const nxJson = readNxJson(); + (global as any).workspaceLayout = nxJson.workspaceLayout; + + /** + * Because there are a number of ways in which the rule can be invoked (executor vs ESLint CLI vs IDE Plugin), + * the ProjectGraph may or may not exist by the time the lint rule is invoked for the first time. + */ + try { + (global as any).projectGraph = mapProjectGraphFiles( + readCachedProjectGraph() + ); + } catch { + const WARNING_PREFIX = `${chalk.reset.keyword('orange')('warning')}`; + const RULE_NAME_SUFFIX = `${chalk.reset.dim(`@nrwl/nx/${ruleName}`)}`; + process.stdout + .write(`${WARNING_PREFIX} No cached ProjectGraph is available. The rule will be skipped. + If you encounter this error as part of running standard \`nx\` commands then please open an issue on https://github.com/nrwl/nx + ${RULE_NAME_SUFFIX}\n`); + } + } +} + +export function readProjectGraph(ruleName: string) { + ensureGlobalProjectGraph(ruleName); + return (global as any).projectGraph as MappedProjectGraph; +} diff --git a/packages/js/src/generators/library/library.ts b/packages/js/src/generators/library/library.ts index 223d8af9b16cb..c4c90b6247477 100644 --- a/packages/js/src/generators/library/library.ts +++ b/packages/js/src/generators/library/library.ts @@ -16,7 +16,6 @@ import { writeJson, } from '@nrwl/devkit'; import { jestProjectGenerator } from '@nrwl/jest'; -import { findRootJestPreset } from '@nrwl/jest/src/utils/config/find-root-jest-files'; import { Linter, lintProjectGenerator } from '@nrwl/linter'; import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import { diff --git a/packages/linter/src/executors/lint/compat.ts b/packages/linter/src/executors/lint/compat.ts new file mode 100644 index 0000000000000..09df2fa800a11 --- /dev/null +++ b/packages/linter/src/executors/lint/compat.ts @@ -0,0 +1,4 @@ +import { convertNxExecutor } from '@nrwl/devkit'; +import executor from './lint.impl'; + +export default convertNxExecutor(executor); diff --git a/packages/nx-plugin/generators.json b/packages/nx-plugin/generators.json index 86f4365e09a1d..7a6df0c196b8e 100644 --- a/packages/nx-plugin/generators.json +++ b/packages/nx-plugin/generators.json @@ -28,6 +28,11 @@ "factory": "./src/generators/executor/executor", "schema": "./src/generators/executor/schema.json", "description": "Create a executor for an Nx Plugin." + }, + "plugin-lint-checks": { + "factory": "./src/generators/lint-checks/generator", + "schema": "./src/generators/lint-checks/schema.json", + "description": "Adds linting configuration to validate common json files for nx plugins." } }, "schematics": { diff --git a/packages/nx-plugin/package.json b/packages/nx-plugin/package.json index 828add726981f..8cc5a7457d226 100644 --- a/packages/nx-plugin/package.json +++ b/packages/nx-plugin/package.json @@ -33,6 +33,7 @@ "@nrwl/linter": "file:../linter", "fs-extra": "^10.1.0", "rxjs": "^6.5.4", + "semver": "7.3.4", "tslib": "^2.3.0" } } diff --git a/packages/nx-plugin/src/generators/lint-checks/generator.spec.ts b/packages/nx-plugin/src/generators/lint-checks/generator.spec.ts new file mode 100644 index 0000000000000..06c665e8ad5b1 --- /dev/null +++ b/packages/nx-plugin/src/generators/lint-checks/generator.spec.ts @@ -0,0 +1,148 @@ +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { + Tree, + readProjectConfiguration, + readJson, + updateJson, + joinPathFragments, + writeJson, +} from '@nrwl/devkit'; + +import type { Linter as ESLint } from 'eslint'; +import { Schema as EsLintExecutorOptions } from '@nrwl/linter/src/executors/eslint/schema'; + +import generator from './generator'; +import pluginGenerator from '../plugin/plugin'; +import { Linter } from '@nrwl/linter'; +import { PackageJson } from 'nx/src/utils/package-json'; + +describe('lint-checks generator', () => { + let appTree: Tree; + + beforeEach(async () => { + appTree = createTreeWithEmptyWorkspace(2); + await pluginGenerator(appTree, { + name: 'plugin', + importPath: '@acme/plugin', + compiler: 'tsc', + linter: Linter.EsLint, + skipFormat: false, + skipTsConfig: false, + skipLintChecks: true, // we manually call it s.t. we can update config files first + unitTestRunner: 'jest', + }); + }); + + it('should update configuration files for default plugin', async () => { + await generator(appTree, { projectName: 'plugin' }); + const projectConfig = readProjectConfiguration(appTree, 'plugin'); + const targetConfig = projectConfig.targets?.['lint']; + const eslintConfig: ESLint.Config = readJson( + appTree, + `${projectConfig.root}/.eslintrc.json` + ); + + expect(targetConfig.options.lintFilePatterns).toContain( + `${projectConfig.root}/generators.json` + ); + expect(targetConfig.options.lintFilePatterns).toContain( + `${projectConfig.root}/executors.json` + ); + expect(targetConfig.options.lintFilePatterns).toContain( + `${projectConfig.root}/package.json` + ); + expect(eslintConfig.overrides).toContainEqual( + expect.objectContaining({ + files: expect.arrayContaining([ + './executors.json', + './package.json', + './generators.json', + ]), + rules: { + '@nrwl/nx/nx-plugin-checks': 'error', + }, + }) + ); + }); + + it('should not duplicate configuration', async () => { + await generator(appTree, { projectName: 'plugin' }); + await generator(appTree, { projectName: 'plugin' }); + const projectConfig = readProjectConfiguration(appTree, 'plugin'); + const targetConfig = projectConfig.targets?.['lint'] + .options as EsLintExecutorOptions; + const eslintConfig: ESLint.Config = readJson( + appTree, + `${projectConfig.root}/.eslintrc.json` + ); + + const uniqueLintFilePatterns = new Set(targetConfig.lintFilePatterns); + + expect(targetConfig.lintFilePatterns).toHaveLength( + uniqueLintFilePatterns.size + ); + + expect( + eslintConfig.overrides.filter( + (x) => '@nrwl/nx/nx-plugin-checks' in x.rules + ) + ).toHaveLength(1); + }); + + it('should update configuration files for angular-style plugin', async () => { + const startingProjectConfig = readProjectConfiguration(appTree, 'plugin'); + updateJson( + appTree, + joinPathFragments(startingProjectConfig.root, 'package.json'), + (json: PackageJson) => { + json.schematics = './collection.json'; + delete json.generators; + json.builders = './builders.json'; + delete json.executors; + json['ng-update'] = './migrations.json'; + return json; + } + ); + writeJson( + appTree, + joinPathFragments(startingProjectConfig.root, 'migrations.json'), + {} + ); + await generator(appTree, { projectName: 'plugin' }); + const projectConfig = readProjectConfiguration(appTree, 'plugin'); + const targetConfig = projectConfig.targets?.['lint']; + const eslintConfig: ESLint.Config = readJson( + appTree, + `${projectConfig.root}/.eslintrc.json` + ); + + expect(targetConfig.options.lintFilePatterns).not.toContain( + `${projectConfig.root}/generators.json` + ); + expect(targetConfig.options.lintFilePatterns).toContain( + `${projectConfig.root}/collection.json` + ); + expect(targetConfig.options.lintFilePatterns).not.toContain( + `${projectConfig.root}/executors.json` + ); + expect(targetConfig.options.lintFilePatterns).toContain( + `${projectConfig.root}/builders.json` + ); + expect(targetConfig.options.lintFilePatterns).toContain( + `${projectConfig.root}/migrations.json` + ); + expect(eslintConfig.overrides).toContainEqual( + expect.objectContaining({ + files: expect.arrayContaining([ + './collection.json', + './package.json', + './builders.json', + './migrations.json', + ]), + rules: { + '@nrwl/nx/nx-plugin-checks': 'error', + }, + }) + ); + }); +}); diff --git a/packages/nx-plugin/src/generators/lint-checks/generator.ts b/packages/nx-plugin/src/generators/lint-checks/generator.ts new file mode 100644 index 0000000000000..62dac38b268df --- /dev/null +++ b/packages/nx-plugin/src/generators/lint-checks/generator.ts @@ -0,0 +1,255 @@ +import { + addDependenciesToPackageJson, + joinPathFragments, + logger, + ProjectConfiguration, + readJson, + readProjectConfiguration, + TargetConfiguration, + Tree, + updateJson, + updateProjectConfiguration, + writeJson, +} from '@nrwl/devkit'; + +import type { Linter as ESLint } from 'eslint'; + +import { Schema as EsLintExecutorOptions } from '@nrwl/linter/src/executors/eslint/schema'; + +import { jsoncEslintParserVersion } from '../../utils/versions'; +import { PluginLintChecksGeneratorSchema } from './schema'; +import { NX_PREFIX } from 'nx/src/utils/logger'; +import { PackageJson, readNxMigrateConfig } from 'nx/src/utils/package-json'; + +export default async function pluginLintCheckGenerator( + host: Tree, + options: PluginLintChecksGeneratorSchema +) { + const project = readProjectConfiguration(host, options.projectName); + const packageJson = readJson( + host, + joinPathFragments(project.root, 'package.json') + ); + + // This rule is eslint **only** + if (projectIsEsLintEnabled(project)) { + updateRootEslintConfig(host); + updateProjectEslintConfig(host, project, packageJson); + updateProjectTarget(host, options, packageJson); + + // Project is setup for vscode + if (host.exists('.vscode')) { + setupVsCodeLintingForJsonFiles(host); + } + + // Project contains migrations.json + const migrationsPath = readNxMigrateConfig(packageJson).migrations; + if ( + migrationsPath && + host.exists(joinPathFragments(project.root, migrationsPath)) + ) { + addMigrationJsonChecks(host, options, packageJson); + } + } else { + logger.error( + `${NX_PREFIX} plugin lint checks can only be added to plugins which use eslint for linting` + ); + } + const installTask = addDependenciesToPackageJson( + host, + {}, + { 'jsonc-eslint-parser': jsoncEslintParserVersion } + ); + return () => installTask; +} + +export function addMigrationJsonChecks( + host: Tree, + options: PluginLintChecksGeneratorSchema, + packageJson: PackageJson +) { + const projectConfiguration = readProjectConfiguration( + host, + options.projectName + ); + + const [eslintTarget, eslintTargetConfiguration] = + getEsLintOptions(projectConfiguration); + + const relativeMigrationsJsonPath = + readNxMigrateConfig(packageJson).migrations; + + if (!relativeMigrationsJsonPath) { + return; + } + + const migrationsJsonPath = joinPathFragments( + projectConfiguration.root, + relativeMigrationsJsonPath + ); + + if ( + eslintTarget && + !eslintTargetConfiguration.options?.lintFilePatterns?.includes( + migrationsJsonPath + ) + ) { + // Add to lintFilePatterns + eslintTargetConfiguration.options.lintFilePatterns.push(migrationsJsonPath); + updateProjectConfiguration(host, options.projectName, projectConfiguration); + + // Update project level eslintrc + updateJson( + host, + `${projectConfiguration.root}/.eslintrc.json`, + (c) => { + const override = c.overrides.find((o) => + Object.keys(o.rules ?? {})?.includes('@nrwl/nx/nx-plugin-checks') + ); + if ( + Array.isArray(override?.files) && + !override.files.includes(relativeMigrationsJsonPath) + ) { + override.files.push(relativeMigrationsJsonPath); + } + return c; + } + ); + } +} + +function updateProjectTarget( + host: Tree, + options: PluginLintChecksGeneratorSchema, + packageJson: PackageJson +) { + const project = readProjectConfiguration(host, options.projectName); + if (!project.targets) { + return; + } + + for (const [target, configuration] of Object.entries(project.targets)) { + if (configuration.executor === '@nrwl/linter:eslint') { + const opts: EsLintExecutorOptions = configuration.options ?? {}; + opts.lintFilePatterns ??= []; + + if (packageJson.generators) { + opts.lintFilePatterns.push( + joinPathFragments(project.root, packageJson.generators) + ); + } + if ( + packageJson.schematics && + packageJson.schematics !== packageJson.generators + ) { + opts.lintFilePatterns.push( + joinPathFragments(project.root, packageJson.schematics) + ); + } + if (packageJson.executors) { + opts.lintFilePatterns.push( + joinPathFragments(project.root, packageJson.executors) + ); + } + if ( + packageJson.builders && + packageJson.builders !== packageJson.executors + ) { + opts.lintFilePatterns.push( + joinPathFragments(project.root, packageJson.builders) + ); + } + opts.lintFilePatterns.push(`${project.root}/package.json`); + opts.lintFilePatterns = [...new Set(opts.lintFilePatterns)]; + project.targets[target].options = opts; + } + } + updateProjectConfiguration(host, options.projectName, project); +} + +function updateProjectEslintConfig( + host: Tree, + options: ProjectConfiguration, + packageJson: PackageJson +) { + // Update the project level lint configuration to specify + // the plugin schema rule for generated files + const eslintPath = `${options.root}/.eslintrc.json`; + const eslintConfig = readJson(host, eslintPath); + eslintConfig.overrides ??= []; + if ( + !eslintConfig.overrides.some((x) => + Object.keys(x.rules ?? {}).includes('@nrwl/nx/nx-plugin-checks') + ) + ) { + eslintConfig.overrides.push({ + files: [ + './package.json', + packageJson.generators, + packageJson.executors, + packageJson.schematics, + packageJson.builders, + ].filter((f) => !!f), + parser: 'jsonc-eslint-parser', + rules: { + '@nrwl/nx/nx-plugin-checks': 'error', + }, + }); + } + writeJson(host, eslintPath, eslintConfig); +} + +// Update the root eslint to specify a parser for json files +// This is required, otherwise every json file that is not overriden +// will display false errors in the IDE +function updateRootEslintConfig(host: Tree) { + const rootESLint = readJson(host, '.eslintrc.json'); + rootESLint.overrides ??= []; + if (!eslintConfigContainsJsonOverride(rootESLint)) { + rootESLint.overrides.push({ + files: '*.json', + parser: 'jsonc-eslint-parser', + rules: {}, + }); + writeJson(host, '.eslintrc.json', rootESLint); + } +} + +function setupVsCodeLintingForJsonFiles(host: Tree) { + let existing: Record = {}; + if (host.exists('.vscode/settings.json')) { + existing = readJson>(host, '.vscode/settings.json'); + } else { + logger.info( + `${NX_PREFIX} We've updated the vscode settings for this repository to ensure that plugin lint checks show up inside your IDE. This created .vscode/settings.json. To read more about this file, check vscode's documentation. It is frequently not commited, so other developers may need to add similar settings if they'd like to see the lint checks in the IDE rather than only during linting.` + ); + } + + // setup eslint validation for json files + const eslintValidate = (existing['eslint.validate'] as string[]) ?? []; + if (!eslintValidate.includes('json')) { + existing['eslint.validate'] = [...eslintValidate, 'json']; + } + writeJson(host, '.vscode/settings.json', existing); +} + +function eslintConfigContainsJsonOverride(eslintConfig: ESLint.Config) { + return eslintConfig.overrides.some((x) => { + if (typeof x.files === 'string' && x.files.includes('.json')) { + return true; + } + return Array.isArray(x.files) && x.files.some((f) => f.includes('.json')); + }); +} + +function projectIsEsLintEnabled(project: ProjectConfiguration) { + return !!getEsLintOptions(project); +} + +export function getEsLintOptions( + project: ProjectConfiguration +): [target: string, configuration: TargetConfiguration] { + return Object.entries(project.targets || {}).find( + ([, x]) => x.executor === '@nrwl/linter:eslint' + ); +} diff --git a/packages/nx-plugin/src/generators/lint-checks/schema.d.ts b/packages/nx-plugin/src/generators/lint-checks/schema.d.ts new file mode 100644 index 0000000000000..e268defed66be --- /dev/null +++ b/packages/nx-plugin/src/generators/lint-checks/schema.d.ts @@ -0,0 +1,3 @@ +export interface PluginLintChecksGeneratorSchema { + projectName: string; +} diff --git a/packages/nx-plugin/src/generators/lint-checks/schema.json b/packages/nx-plugin/src/generators/lint-checks/schema.json new file mode 100644 index 0000000000000..1872957d8531c --- /dev/null +++ b/packages/nx-plugin/src/generators/lint-checks/schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/schema", + "cli": "nx", + "$id": "PluginLint", + "title": "", + "type": "object", + "description": "Adds linting configuration to validate common json files for nx plugins.", + "properties": { + "projectName": { + "type": "string", + "description": "Which project should be the configuration be added to?", + "$default": { + "$source": "projectName" + } + } + }, + "required": ["projectName"] +} diff --git a/packages/nx-plugin/src/generators/migration/migration.ts b/packages/nx-plugin/src/generators/migration/migration.ts index ca679997e8ac3..bfc7e2f099015 100644 --- a/packages/nx-plugin/src/generators/migration/migration.ts +++ b/packages/nx-plugin/src/generators/migration/migration.ts @@ -7,11 +7,14 @@ import { updateJson, readJson, writeJson, + joinPathFragments, } from '@nrwl/devkit'; import type { Tree } from '@nrwl/devkit'; import type { Schema } from './schema'; import * as path from 'path'; - +import { addMigrationJsonChecks } from '../lint-checks/generator'; +import type { Linter as EsLint } from 'eslint'; +import { PackageJson } from 'nx/src/utils/package-json'; interface NormalizedSchema extends Schema { projectRoot: string; projectSourceRoot: string; @@ -128,6 +131,18 @@ export async function migrationGenerator(host: Tree, schema: Schema) { updateWorkspaceJson(host, options); updateMigrationsJson(host, options); updatePackageJson(host, options); + + if (!host.exists('migrations.json')) { + const packageJsonPath = joinPathFragments( + options.projectRoot, + 'package.json' + ); + addMigrationJsonChecks( + host, + { projectName: schema.project }, + readJson(host, packageJsonPath) + ); + } } export default migrationGenerator; diff --git a/packages/nx-plugin/src/generators/plugin/plugin.spec.ts b/packages/nx-plugin/src/generators/plugin/plugin.spec.ts index 0a8daac42e4a3..e053f6b3c02c6 100644 --- a/packages/nx-plugin/src/generators/plugin/plugin.spec.ts +++ b/packages/nx-plugin/src/generators/plugin/plugin.spec.ts @@ -16,6 +16,7 @@ const getSchema: (overrides?: Partial) => Schema = ( compiler: 'tsc', skipTsConfig: false, skipFormat: false, + skipLintChecks: false, linter: Linter.EsLint, unitTestRunner: 'jest', ...overrides, @@ -68,7 +69,12 @@ describe('NxPlugin Plugin Generator', () => { executor: '@nrwl/linter:eslint', outputs: ['{options.outputFile}'], options: { - lintFilePatterns: ['libs/my-plugin/**/*.ts'], + lintFilePatterns: expect.arrayContaining([ + 'libs/my-plugin/**/*.ts', + 'libs/my-plugin/generators.json', + 'libs/my-plugin/package.json', + 'libs/my-plugin/executors.json', + ]), }, }); expect(project.targets.test).toEqual({ diff --git a/packages/nx-plugin/src/generators/plugin/plugin.ts b/packages/nx-plugin/src/generators/plugin/plugin.ts index 4a08e07c5486a..b142bb194b9de 100644 --- a/packages/nx-plugin/src/generators/plugin/plugin.ts +++ b/packages/nx-plugin/src/generators/plugin/plugin.ts @@ -3,17 +3,14 @@ import { convertNxGenerator, formatFiles, generateFiles, - GeneratorCallback, - getWorkspaceLayout, installPackagesTask, - joinPathFragments, - names, normalizePath, readProjectConfiguration, Tree, updateProjectConfiguration, } from '@nrwl/devkit'; import { libraryGenerator } from '@nrwl/js'; +import { Linter } from '@nrwl/linter'; import { addSwcDependencies } from '@nrwl/js/src/utils/swc/add-swc-dependencies'; import { swcNodeVersion } from 'nx/src/utils/versions'; import * as path from 'path'; @@ -22,49 +19,10 @@ import { nxVersion } from '../../utils/versions'; import { e2eProjectGenerator } from '../e2e-project/e2e'; import { executorGenerator } from '../executor/executor'; import { generatorGenerator } from '../generator/generator'; +import pluginLintCheckGenerator from '../lint-checks/generator'; +import { NormalizedSchema, normalizeOptions } from './utils/normalize-schema'; import type { Schema } from './schema'; -interface NormalizedSchema extends Schema { - name: string; - fileName: string; - libsDir: string; - projectRoot: string; - projectDirectory: string; - parsedTags: string[]; - npmScope: string; - npmPackageName: string; -} - -function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { - const { npmScope, libsDir } = getWorkspaceLayout(host); - const name = names(options.name).fileName; - const projectDirectory = options.directory - ? `${names(options.directory).fileName}/${name}` - : name; - - const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); - const fileName = projectName; - const projectRoot = joinPathFragments(libsDir, projectDirectory); - - const parsedTags = options.tags - ? options.tags.split(',').map((s) => s.trim()) - : []; - - const npmPackageName = - options.importPath || resolvePackageName(npmScope, name); - - return { - ...options, - fileName, - npmScope, - libsDir, - name: projectName, - projectRoot, - projectDirectory, - parsedTags, - npmPackageName, - }; -} async function addFiles(host: Tree, options: NormalizedSchema) { host.delete(normalizePath(`${options.projectRoot}/src/lib`)); @@ -129,7 +87,7 @@ function updateWorkspaceJson(host: Tree, options: NormalizedSchema) { export async function pluginGenerator(host: Tree, schema: Schema) { const options = normalizeOptions(host, schema); - const libraryTask = await libraryGenerator(host, { + await libraryGenerator(host, { ...schema, config: options.standaloneConfig !== false ? 'project' : 'workspace', buildable: true, @@ -154,7 +112,6 @@ export async function pluginGenerator(host: Tree, schema: Schema) { await addFiles(host, options); updateWorkspaceJson(host, options); - await e2eProjectGenerator(host, { pluginName: options.name, projectDirectory: options.projectDirectory, @@ -162,19 +119,14 @@ export async function pluginGenerator(host: Tree, schema: Schema) { npmPackageName: options.npmPackageName, standaloneConfig: options.standaloneConfig ?? true, }); + if (options.linter === Linter.EsLint && !options.skipLintChecks) { + await pluginLintCheckGenerator(host, { projectName: options.name }); + } await formatFiles(host); return () => installPackagesTask(host); } -function resolvePackageName(npmScope: string, name: string): string { - if (npmScope && npmScope !== '') { - return `@${npmScope}/${name}`; - } else { - return name; - } -} - export default pluginGenerator; export const pluginSchematic = convertNxGenerator(pluginGenerator); diff --git a/packages/nx-plugin/src/generators/plugin/schema.d.ts b/packages/nx-plugin/src/generators/plugin/schema.d.ts index db309f41c1d03..04ce184d49e0d 100644 --- a/packages/nx-plugin/src/generators/plugin/schema.d.ts +++ b/packages/nx-plugin/src/generators/plugin/schema.d.ts @@ -6,6 +6,7 @@ export interface Schema { importPath?: string; skipTsConfig: boolean; skipFormat: boolean; + skipLintChecks: boolean; tags?: string; unitTestRunner: 'jest' | 'none'; linter: Linter; diff --git a/packages/nx-plugin/src/generators/plugin/schema.json b/packages/nx-plugin/src/generators/plugin/schema.json index 494b502fb2502..87a5ef4856d3a 100644 --- a/packages/nx-plugin/src/generators/plugin/schema.json +++ b/packages/nx-plugin/src/generators/plugin/schema.json @@ -34,7 +34,8 @@ "description": "The tool to use for running lint checks.", "type": "string", "enum": ["eslint", "tslint"], - "default": "eslint" + "default": "eslint", + "x-deprecated": "TSLint support is deprecated and will be removed" }, "unitTestRunner": { "type": "string", @@ -57,6 +58,11 @@ "default": false, "description": "Do not update tsconfig.json for development experience." }, + "skipLintChecks": { + "type": "boolean", + "default": false, + "description": "Do not eslint configuration for plugin json files." + }, "standaloneConfig": { "description": "Split the project configuration into `/project.json` rather than including it inside `workspace.json`.", "type": "boolean" diff --git a/packages/nx-plugin/src/generators/plugin/utils/normalize-schema.ts b/packages/nx-plugin/src/generators/plugin/utils/normalize-schema.ts new file mode 100644 index 0000000000000..ff2fd1423da10 --- /dev/null +++ b/packages/nx-plugin/src/generators/plugin/utils/normalize-schema.ts @@ -0,0 +1,59 @@ +import { + getWorkspaceLayout, + joinPathFragments, + names, + Tree, +} from '@nrwl/devkit'; +import { Schema } from '../schema'; + +export interface NormalizedSchema extends Schema { + name: string; + fileName: string; + libsDir: string; + projectRoot: string; + projectDirectory: string; + parsedTags: string[]; + npmScope: string; + npmPackageName: string; +} +export function normalizeOptions( + host: Tree, + options: Schema +): NormalizedSchema { + const { npmScope, libsDir } = getWorkspaceLayout(host); + const name = names(options.name).fileName; + const projectDirectory = options.directory + ? `${names(options.directory).fileName}/${name}` + : name; + + const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); + const fileName = projectName; + const projectRoot = joinPathFragments(libsDir, projectDirectory); + + const parsedTags = options.tags + ? options.tags.split(',').map((s) => s.trim()) + : []; + + const npmPackageName = + options.importPath || resolvePackageName(npmScope, name); + + return { + ...options, + fileName, + npmScope, + libsDir, + name: projectName, + projectRoot, + projectDirectory, + parsedTags, + npmPackageName, + }; +} + +function resolvePackageName(npmScope: string, name: string): string { + if (npmScope && npmScope !== '') { + return `@${npmScope}/${name}`; + } else { + return name; + } +} diff --git a/packages/nx-plugin/src/utils/versions.ts b/packages/nx-plugin/src/utils/versions.ts index e268dc8f82dd3..2d830ff6e4d0a 100644 --- a/packages/nx-plugin/src/utils/versions.ts +++ b/packages/nx-plugin/src/utils/versions.ts @@ -1 +1,2 @@ export const nxVersion = require('../../package.json').version; +export const jsoncEslintParserVersion = '^2.1.0'; diff --git a/packages/nx/src/command-line/migrate.ts b/packages/nx/src/command-line/migrate.ts index 27b35a5ff167a..d94918be4b59d 100644 --- a/packages/nx/src/command-line/migrate.ts +++ b/packages/nx/src/command-line/migrate.ts @@ -20,6 +20,7 @@ import { NxMigrationsConfiguration, PackageGroup, PackageJson, + readNxMigrateConfig, } from '../utils/package-json'; import { createTempNpmDirectory, @@ -593,33 +594,6 @@ async function getPackageMigrationsUsingRegistry( ); } -function resolveNxMigrationConfig(json: Partial) { - const parseNxMigrationsConfig = ( - fromJson?: string | NxMigrationsConfiguration - ): NxMigrationsConfiguration => { - if (!fromJson) { - return {}; - } - if (typeof fromJson === 'string') { - return { migrations: fromJson, packageGroup: [] }; - } - - return { - ...(fromJson.migrations ? { migrations: fromJson.migrations } : {}), - ...(fromJson.packageGroup ? { packageGroup: fromJson.packageGroup } : {}), - }; - }; - - const config: NxMigrationsConfiguration = { - ...parseNxMigrationsConfig(json['ng-update']), - ...parseNxMigrationsConfig(json['nx-migrations']), - // In case there's a `migrations` field in `package.json` - ...parseNxMigrationsConfig(json as any), - }; - - return config; -} - async function getPackageMigrationsConfigFromRegistry( packageName: string, packageVersion: string @@ -634,7 +608,7 @@ async function getPackageMigrationsConfigFromRegistry( return null; } - return resolveNxMigrationConfig(JSON.parse(result)); + return readNxMigrateConfig(JSON.parse(result)); } async function downloadPackageMigrationsFromRegistry( diff --git a/packages/nx/src/config/workspace-json-project-json.ts b/packages/nx/src/config/workspace-json-project-json.ts index 026a74828f0c6..6b0e3e2af43a9 100644 --- a/packages/nx/src/config/workspace-json-project-json.ts +++ b/packages/nx/src/config/workspace-json-project-json.ts @@ -112,7 +112,7 @@ export interface TargetDependencyConfig { /** * Target's configuration */ -export interface TargetConfiguration { +export interface TargetConfiguration { /** * The executor/builder used to implement the target. * @@ -134,7 +134,7 @@ export interface TargetConfiguration { /** * Target's options. They are passed in to the executor. */ - options?: any; + options?: T; /** * Sets of options diff --git a/packages/nx/src/utils/package-json.ts b/packages/nx/src/utils/package-json.ts index 8085730a30756..af4be4c23138f 100644 --- a/packages/nx/src/utils/package-json.ts +++ b/packages/nx/src/utils/package-json.ts @@ -54,6 +54,33 @@ export interface PackageJson { 'ng-update'?: string | NxMigrationsConfiguration; } +export function readNxMigrateConfig( + json: Partial +): NxMigrationsConfiguration { + const parseNxMigrationsConfig = ( + fromJson?: string | NxMigrationsConfiguration + ): NxMigrationsConfiguration => { + if (!fromJson) { + return {}; + } + if (typeof fromJson === 'string') { + return { migrations: fromJson, packageGroup: [] }; + } + + return { + ...(fromJson.migrations ? { migrations: fromJson.migrations } : {}), + ...(fromJson.packageGroup ? { packageGroup: fromJson.packageGroup } : {}), + }; + }; + + return { + ...parseNxMigrationsConfig(json['ng-update']), + ...parseNxMigrationsConfig(json['nx-migrations']), + // In case there's a `migrations` field in `package.json` + ...parseNxMigrationsConfig(json as any), + }; +} + export function buildTargetFromScript( script: string, nx: NxProjectPackageJsonConfiguration diff --git a/packages/react/generators.json b/packages/react/generators.json index abe43e94623fb..12d62a175dd74 100644 --- a/packages/react/generators.json +++ b/packages/react/generators.json @@ -70,7 +70,7 @@ }, "hook": { - "factory": "./src/generators/hook/hook#chookSchematic", + "factory": "./src/generators/hook/hook#hookSchematic", "schema": "./src/generators/hook/schema.json", "description": "Create a hook.", "aliases": ["h"]