@nx/devkit 17.0.0-beta.1 → 17.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/devkit",
3
- "version": "17.0.0-beta.1",
3
+ "version": "17.0.0-beta.2",
4
4
  "private": false,
5
5
  "description": "The Nx Devkit is used to customize Nx for different technologies and use cases. It contains many utility functions for reading and writing files, updating configuration, working with Abstract Syntax Trees(ASTs), and more.",
6
6
  "repository": {
@@ -34,7 +34,7 @@
34
34
  "tmp": "~0.2.1",
35
35
  "tslib": "^2.3.0",
36
36
  "semver": "7.5.3",
37
- "@nrwl/devkit": "17.0.0-beta.1"
37
+ "@nrwl/devkit": "17.0.0-beta.2"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "nx": ">= 15 <= 17"
@@ -0,0 +1,42 @@
1
+ import type { Tree } from 'nx/src/generators/tree';
2
+ export type NameAndDirectoryFormat = 'as-provided' | 'derived';
3
+ export type ArtifactGenerationOptions = {
4
+ artifactType: string;
5
+ callingGenerator: string | null;
6
+ name: string;
7
+ directory?: string;
8
+ disallowPathInNameForDerived?: boolean;
9
+ fileExtension?: 'js' | 'jsx' | 'ts' | 'tsx';
10
+ fileName?: string;
11
+ flat?: boolean;
12
+ nameAndDirectoryFormat?: NameAndDirectoryFormat;
13
+ pascalCaseDirectory?: boolean;
14
+ pascalCaseFile?: boolean;
15
+ project?: string;
16
+ suffix?: string;
17
+ };
18
+ export type NameAndDirectoryOptions = {
19
+ /**
20
+ * Normalized artifact name.
21
+ */
22
+ artifactName: string;
23
+ /**
24
+ * Normalized directory path where the artifact will be generated.
25
+ */
26
+ directory: string;
27
+ /**
28
+ * Normalized file name of the artifact without the extension.
29
+ */
30
+ fileName: string;
31
+ /**
32
+ * Normalized full file path of the artifact.
33
+ */
34
+ filePath: string;
35
+ /**
36
+ * Project name where the artifact will be generated.
37
+ */
38
+ project: string;
39
+ };
40
+ export declare function determineArtifactNameAndDirectoryOptions(tree: Tree, options: ArtifactGenerationOptions): Promise<NameAndDirectoryOptions & {
41
+ nameAndDirectoryFormat: NameAndDirectoryFormat;
42
+ }>;
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.determineArtifactNameAndDirectoryOptions = void 0;
4
+ const enquirer_1 = require("enquirer");
5
+ const path_1 = require("path");
6
+ const nx_1 = require("../../nx");
7
+ const names_1 = require("../utils/names");
8
+ const { createProjectRootMappingsFromProjectConfigurations, findProjectForPath, getProjects, joinPathFragments, logger, normalizePath, output, workspaceRoot, } = (0, nx_1.requireNx)();
9
+ async function determineArtifactNameAndDirectoryOptions(tree, options) {
10
+ const formats = getNameAndDirectoryOptionFormats(tree, options);
11
+ const format = options.nameAndDirectoryFormat ?? (await determineFormat(formats, options));
12
+ validateResolvedProject(formats[format]?.project, options, formats[format]?.directory);
13
+ return {
14
+ ...formats[format],
15
+ nameAndDirectoryFormat: format,
16
+ };
17
+ }
18
+ exports.determineArtifactNameAndDirectoryOptions = determineArtifactNameAndDirectoryOptions;
19
+ async function determineFormat(formats, options) {
20
+ if (!formats.derived) {
21
+ return 'as-provided';
22
+ }
23
+ if (process.env.NX_INTERACTIVE !== 'true' || !isTTY()) {
24
+ logDeprecationMessage(options, formats);
25
+ return 'derived';
26
+ }
27
+ const asProvidedDescription = `As provided: ${formats['as-provided'].filePath}`;
28
+ const asProvidedSelectedValue = formats['as-provided'].filePath;
29
+ const derivedDescription = `Derived: ${formats['derived'].filePath}`;
30
+ const derivedSelectedValue = formats['derived'].filePath;
31
+ if (asProvidedSelectedValue === derivedSelectedValue) {
32
+ return 'as-provided';
33
+ }
34
+ const result = await (0, enquirer_1.prompt)({
35
+ type: 'select',
36
+ name: 'format',
37
+ message: `Where should the ${options.artifactType} be generated?`,
38
+ choices: [
39
+ {
40
+ message: asProvidedDescription,
41
+ name: asProvidedSelectedValue,
42
+ },
43
+ {
44
+ message: derivedDescription,
45
+ name: derivedSelectedValue,
46
+ },
47
+ ],
48
+ initial: 'as-provided',
49
+ }).then(({ format }) => format === asProvidedSelectedValue ? 'as-provided' : 'derived');
50
+ if (result === 'derived' && options.callingGenerator) {
51
+ logDeprecationMessage(options, formats);
52
+ }
53
+ return result;
54
+ }
55
+ function logDeprecationMessage(options, formats) {
56
+ logger.warn(`
57
+ In Nx 18, generating a ${options.artifactType} will no longer support providing a project and deriving the directory.
58
+ Please provide the exact directory in the future.
59
+ Example: nx g ${options.callingGenerator} ${formats['derived'].artifactName} --directory ${formats['derived'].directory}
60
+ NOTE: The example above assumes the command is being run from the workspace root. If the command is being run from a subdirectory, the directory option should be adjusted accordingly.
61
+ `);
62
+ }
63
+ function getNameAndDirectoryOptionFormats(tree, options) {
64
+ const directory = options.directory
65
+ ? normalizePath(options.directory.replace(/^\.?\//, ''))
66
+ : undefined;
67
+ const fileExtension = options.fileExtension ?? 'ts';
68
+ const { name: extractedName, directory: extractedDirectory } = extractNameAndDirectoryFromName(options.name);
69
+ if (extractedDirectory && directory) {
70
+ throw new Error(`You can't specify both a directory (${options.directory}) and a name with a directory path (${options.name}). ` +
71
+ `Please specify either a directory or a name with a directory path.`);
72
+ }
73
+ const asProvidedOptions = getAsProvidedOptions(tree, {
74
+ ...options,
75
+ directory: directory ?? extractedDirectory,
76
+ fileExtension,
77
+ name: extractedName,
78
+ });
79
+ if (!options.project) {
80
+ validateResolvedProject(asProvidedOptions.project, options, asProvidedOptions.directory);
81
+ }
82
+ if (options.nameAndDirectoryFormat === 'as-provided') {
83
+ return {
84
+ 'as-provided': asProvidedOptions,
85
+ derived: undefined,
86
+ };
87
+ }
88
+ if (options.disallowPathInNameForDerived && options.name.includes('/')) {
89
+ if (!options.nameAndDirectoryFormat) {
90
+ output.warn({
91
+ title: `The provided name "${options.name}" contains a path and this is not supported by the "${options.callingGenerator}" when using the "derived" format.`,
92
+ bodyLines: [
93
+ `The generator will try to generate the ${options.artifactType} using the "as-provided" format at "${asProvidedOptions.filePath}".`,
94
+ ],
95
+ });
96
+ return {
97
+ 'as-provided': asProvidedOptions,
98
+ derived: undefined,
99
+ };
100
+ }
101
+ throw new Error(`The provided name "${options.name}" contains a path and this is not supported by the "${options.callingGenerator}" when using the "derived" format. ` +
102
+ `Please provide a name without a path or use the "as-provided" format.`);
103
+ }
104
+ const derivedOptions = getDerivedOptions(tree, {
105
+ ...options,
106
+ directory,
107
+ fileExtension,
108
+ name: extractedName,
109
+ }, asProvidedOptions, !options.disallowPathInNameForDerived && extractedDirectory
110
+ ? extractedDirectory
111
+ : undefined);
112
+ return {
113
+ 'as-provided': asProvidedOptions,
114
+ derived: derivedOptions,
115
+ };
116
+ }
117
+ function getAsProvidedOptions(tree, options) {
118
+ const relativeCwd = getRelativeCwd();
119
+ const asProvidedDirectory = options.directory
120
+ ? joinPathFragments(relativeCwd, options.directory)
121
+ : relativeCwd;
122
+ const asProvidedProject = findProjectFromPath(tree, asProvidedDirectory);
123
+ const asProvidedFileName = options.fileName ??
124
+ (options.suffix ? `${options.name}.${options.suffix}` : options.name);
125
+ const asProvidedFilePath = joinPathFragments(asProvidedDirectory, `${asProvidedFileName}.${options.fileExtension}`);
126
+ return {
127
+ artifactName: options.name,
128
+ directory: asProvidedDirectory,
129
+ fileName: asProvidedFileName,
130
+ filePath: asProvidedFilePath,
131
+ project: asProvidedProject,
132
+ };
133
+ }
134
+ function getDerivedOptions(tree, options, asProvidedOptions, extractedDirectory) {
135
+ const projects = getProjects(tree);
136
+ if (options.project && !projects.has(options.project)) {
137
+ throw new Error(`The provided project "${options.project}" does not exist! Please provide an existing project name.`);
138
+ }
139
+ const projectName = options.project ?? asProvidedOptions.project;
140
+ const project = projects.get(projectName);
141
+ const derivedName = options.name;
142
+ const baseDirectory = options.directory
143
+ ? (0, names_1.names)(options.directory).fileName
144
+ : joinPathFragments(project.sourceRoot ?? joinPathFragments(project.root, 'src'), project.projectType === 'application' ? 'app' : 'lib', extractedDirectory ?? '');
145
+ const derivedDirectory = options.flat
146
+ ? normalizePath(baseDirectory)
147
+ : joinPathFragments(baseDirectory, options.pascalCaseDirectory
148
+ ? (0, names_1.names)(derivedName).className
149
+ : (0, names_1.names)(derivedName).fileName);
150
+ if (options.directory &&
151
+ !isDirectoryUnderProjectRoot(derivedDirectory, project.root)) {
152
+ if (!options.nameAndDirectoryFormat) {
153
+ output.warn({
154
+ title: `The provided directory "${options.directory}" is not under the provided project root "${project.root}".`,
155
+ bodyLines: [
156
+ `The generator will try to generate the ${options.artifactType} using the "as-provided" format.`,
157
+ `With the "as-provided" format, the "project" option is ignored and the ${options.artifactType} will be generated at "${asProvidedOptions.filePath}" (<cwd>/<provided directory>).`,
158
+ ],
159
+ });
160
+ return undefined;
161
+ }
162
+ throw new Error(`The provided directory "${options.directory}" is not under the provided project root "${project.root}". ` +
163
+ `Please provide a directory that is under the provided project root or use the "as-provided" format and only provide the directory.`);
164
+ }
165
+ let derivedFileName = options.fileName;
166
+ if (!derivedFileName) {
167
+ derivedFileName = options.suffix
168
+ ? `${derivedName}.${options.suffix}`
169
+ : derivedName;
170
+ derivedFileName = options.pascalCaseFile
171
+ ? (0, names_1.names)(derivedFileName).className
172
+ : (0, names_1.names)(derivedFileName).fileName;
173
+ }
174
+ const derivedFilePath = joinPathFragments(derivedDirectory, `${derivedFileName}.${options.fileExtension}`);
175
+ return {
176
+ artifactName: derivedName,
177
+ directory: derivedDirectory,
178
+ fileName: derivedFileName,
179
+ filePath: derivedFilePath,
180
+ project: projectName,
181
+ };
182
+ }
183
+ function validateResolvedProject(project, options, normalizedDirectory) {
184
+ if (project) {
185
+ return;
186
+ }
187
+ if (options.directory) {
188
+ throw new Error(`The provided directory resolved relative to the current working directory "${normalizedDirectory}" does not exist under any project root. ` +
189
+ `Please make sure to navigate to a location or provide a directory that exists under a project root.`);
190
+ }
191
+ throw new Error(`The current working directory "${getRelativeCwd() || '.'}" does not exist under any project root. ` +
192
+ `Please make sure to navigate to a location or provide a directory that exists under a project root.`);
193
+ }
194
+ function findProjectFromPath(tree, path) {
195
+ const projectConfigurations = {};
196
+ const projects = getProjects(tree);
197
+ for (const [projectName, project] of projects) {
198
+ projectConfigurations[projectName] = project;
199
+ }
200
+ const projectRootMappings = createProjectRootMappingsFromProjectConfigurations(projectConfigurations);
201
+ return findProjectForPath(path, projectRootMappings);
202
+ }
203
+ function isDirectoryUnderProjectRoot(directory, projectRoot) {
204
+ const normalizedDirectory = joinPathFragments(workspaceRoot, directory);
205
+ const normalizedProjectRoot = joinPathFragments(workspaceRoot, projectRoot).replace(/\/$/, '');
206
+ return (normalizedDirectory === normalizedProjectRoot ||
207
+ normalizedDirectory.startsWith(`${normalizedProjectRoot}/`));
208
+ }
209
+ function isTTY() {
210
+ return !!process.stdout.isTTY && process.env['CI'] !== 'true';
211
+ }
212
+ function getRelativeCwd() {
213
+ return normalizePath((0, path_1.relative)(workspaceRoot, getCwd()));
214
+ }
215
+ function getCwd() {
216
+ return process.env.INIT_CWD?.startsWith(workspaceRoot)
217
+ ? process.env.INIT_CWD
218
+ : process.cwd();
219
+ }
220
+ function extractNameAndDirectoryFromName(rawName) {
221
+ const parsedName = normalizePath(rawName).split('/');
222
+ const name = parsedName.pop();
223
+ const directory = parsedName.length ? parsedName.join('/') : undefined;
224
+ return { name, directory };
225
+ }