@nx/js 0.0.0-pr-29314-e664f92 → 0.0.0-pr-29286-735af1e

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  (The MIT License)
2
2
 
3
- Copyright (c) 2017-2024 Narwhal Technologies Inc.
3
+ Copyright (c) 2017-2025 Narwhal Technologies Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining
6
6
  a copy of this software and associated documentation files (the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/js",
3
- "version": "0.0.0-pr-29314-e664f92",
3
+ "version": "0.0.0-pr-29286-735af1e",
4
4
  "private": false,
5
5
  "description": "The JS plugin for Nx contains executors and generators that provide the best experience for developing JavaScript and TypeScript projects. ",
6
6
  "repository": {
@@ -39,8 +39,8 @@
39
39
  "@babel/preset-env": "^7.23.2",
40
40
  "@babel/preset-typescript": "^7.22.5",
41
41
  "@babel/runtime": "^7.22.6",
42
- "@nx/devkit": "0.0.0-pr-29314-e664f92",
43
- "@nx/workspace": "0.0.0-pr-29314-e664f92",
42
+ "@nx/devkit": "0.0.0-pr-29286-735af1e",
43
+ "@nx/workspace": "0.0.0-pr-29286-735af1e",
44
44
  "@zkochan/js-yaml": "0.0.7",
45
45
  "babel-plugin-const-enum": "^1.0.1",
46
46
  "babel-plugin-macros": "^2.8.0",
@@ -1 +1 @@
1
- export * from './lib/<%= fileName %>';
1
+ export * from './lib/<%= fileNameImport %>';
@@ -22,6 +22,7 @@ const ts_solution_setup_1 = require("../../utils/typescript/ts-solution-setup");
22
22
  const versions_1 = require("../../utils/versions");
23
23
  const init_1 = require("../init/init");
24
24
  const generator_1 = require("../setup-verdaccio/generator");
25
+ const ensure_typescript_1 = require("../../utils/typescript/ensure-typescript");
25
26
  const defaultOutputDirectory = 'dist';
26
27
  async function libraryGenerator(tree, schema) {
27
28
  return await libraryGeneratorInternal(tree, {
@@ -147,12 +148,7 @@ async function libraryGeneratorInternal(tree, schema) {
147
148
  async function configureProject(tree, options) {
148
149
  if (options.hasPlugin) {
149
150
  const nxJson = (0, devkit_1.readNxJson)(tree);
150
- if (options.bundler === 'none') {
151
- (0, plugin_1.ensureProjectIsExcludedFromPluginRegistrations)(nxJson, options.projectRoot);
152
- }
153
- else {
154
- (0, plugin_1.ensureProjectIsIncludedInPluginRegistrations)(nxJson, options.projectRoot);
155
- }
151
+ (0, plugin_1.ensureProjectIsIncludedInPluginRegistrations)(nxJson, options.projectRoot);
156
152
  (0, devkit_1.updateNxJson)(tree, nxJson);
157
153
  }
158
154
  const projectConfiguration = {
@@ -335,6 +331,19 @@ function addBabelRc(tree, options) {
335
331
  function createFiles(tree, options) {
336
332
  const { className, name, propertyName } = (0, devkit_1.names)(options.projectNames.projectFileName);
337
333
  createProjectTsConfigs(tree, options);
334
+ let fileNameImport = options.fileName;
335
+ if (options.bundler === 'vite') {
336
+ const tsConfig = (0, ts_config_1.readTsConfigFromTree)(tree, (0, path_1.join)(options.projectRoot, 'tsconfig.lib.json'));
337
+ const ts = (0, ensure_typescript_1.ensureTypescript)();
338
+ if (tsConfig.options.moduleResolution === ts.ModuleResolutionKind.Node16 ||
339
+ tsConfig.options.moduleResolution === ts.ModuleResolutionKind.NodeNext) {
340
+ // Node16 and NodeNext require explicit file extensions for relative
341
+ // import paths. Since we generate the file with the `.ts` extension,
342
+ // we import it from the same file with the `.js` extension.
343
+ // https://www.typescriptlang.org/docs/handbook/modules/reference.html#file-extension-substitution
344
+ fileNameImport = `${options.fileName}.js`;
345
+ }
346
+ }
338
347
  (0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, './files/lib'), options.projectRoot, {
339
348
  ...options,
340
349
  dot: '.',
@@ -348,6 +357,7 @@ function createFiles(tree, options) {
348
357
  offsetFromRoot: (0, devkit_1.offsetFromRoot)(options.projectRoot),
349
358
  buildable: options.bundler && options.bundler !== 'none',
350
359
  hasUnitTestRunner: options.unitTestRunner !== 'none',
360
+ fileNameImport,
351
361
  });
352
362
  if (!options.rootProject) {
353
363
  (0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, './files/readme'), options.projectRoot, {
@@ -686,6 +696,9 @@ function createProjectTsConfigs(tree, options) {
686
696
  .map(([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`)
687
697
  .join(',\n '),
688
698
  tmpl: '',
699
+ outDir: options.bundler === 'tsc'
700
+ ? 'dist'
701
+ : `out-tsc/${options.projectRoot.split('/').pop()}`,
689
702
  });
690
703
  // tsconfig.json
691
704
  if (options.isUsingTsSolutionConfig) {
@@ -793,14 +806,10 @@ function determineEntryFields(options) {
793
806
  };
794
807
  case 'vite':
795
808
  return {
796
- // Since we're publishing both formats, skip the type field.
797
- // Bundlers or Node will determine the entry point to use.
809
+ type: 'module',
798
810
  main: options.isUsingTsSolutionConfig
799
811
  ? './dist/index.js'
800
812
  : './index.js',
801
- module: options.isUsingTsSolutionConfig
802
- ? './dist/index.mjs'
803
- : './index.mjs',
804
813
  typings: options.isUsingTsSolutionConfig
805
814
  ? './dist/index.d.ts'
806
815
  : './index.d.ts',
@@ -99,7 +99,7 @@ function execLockFileUpdate(command, cwd, env = {}) {
99
99
  `Verify that '${command}' succeeds when run from the workspace root.`,
100
100
  `To configure a string of arguments to be passed to this command, set the 'release.version.generatorOptions.installArgs' property in nx.json.`,
101
101
  `To ignore install lifecycle scripts, set 'release.version.generatorOptions.installIgnoreScripts' to true in nx.json.`,
102
- `To disable this step entirely, set 'release.version.skipLockFileUpdate' to true in nx.json.`,
102
+ `To disable this step entirely, set 'release.version.generatorOptions.skipLockFileUpdate' to true in nx.json.`,
103
103
  ],
104
104
  });
105
105
  throw e;
@@ -55,10 +55,20 @@ async function syncGenerator(tree) {
55
55
  const tsSysFromTree = {
56
56
  ...ts.sys,
57
57
  fileExists(path) {
58
- return tsconfigExists(tree, tsconfigInfoCaches, path);
58
+ // Given ts.System.resolve resolve full path for tsconfig within node_modules
59
+ // We need to remove the workspace root to ensure we don't have double workspace root within the Tree
60
+ const correctPath = path.startsWith(tree.root)
61
+ ? (0, posix_1.relative)(tree.root, path)
62
+ : path;
63
+ return tsconfigExists(tree, tsconfigInfoCaches, correctPath);
59
64
  },
60
65
  readFile(path) {
61
- return readRawTsconfigContents(tree, tsconfigInfoCaches, path);
66
+ // Given ts.System.resolve resolve full path for tsconfig within node_modules
67
+ // We need to remove the workspace root to ensure we don't have double workspace root within the Tree
68
+ const correctPath = path.startsWith(tree.root)
69
+ ? (0, posix_1.relative)(tree.root, path)
70
+ : path;
71
+ return readRawTsconfigContents(tree, tsconfigInfoCaches, correctPath);
62
72
  },
63
73
  };
64
74
  // Track if any changes were made to the tsconfig files. We check the changes
@@ -250,8 +260,8 @@ function collectProjectDependencies(tree, projectName, projectGraph, collectedDe
250
260
  collectedDependencies.set(projectName, []);
251
261
  for (const dep of projectGraph.dependencies[projectName]) {
252
262
  const targetProjectNode = projectGraph.nodes[dep.target];
253
- if (!targetProjectNode) {
254
- // It's an npm dependency
263
+ if (!targetProjectNode || dep.type === 'implicit') {
264
+ // It's an npm or an implicit dependency
255
265
  continue;
256
266
  }
257
267
  // Add the target project node to the list of dependencies for the current project
@@ -325,7 +335,7 @@ function getTsConfigPathFromReferencePath(tree, ownerTsConfigPath, referencePath
325
335
  */
326
336
  function patchTsconfigJsonReferences(tree, tsconfigInfoCaches, tsconfigPath, updatedReferences) {
327
337
  const stringifiedJsonContents = readRawTsconfigContents(tree, tsconfigInfoCaches, tsconfigPath);
328
- const edits = (0, jsonc_parser_1.modify)(stringifiedJsonContents, ['references'], updatedReferences, { formattingOptions: { keepLines: false } });
338
+ const edits = (0, jsonc_parser_1.modify)(stringifiedJsonContents, ['references'], updatedReferences, { formattingOptions: { keepLines: true, insertSpaces: true, tabSize: 2 } });
329
339
  const updatedJsonContents = (0, jsonc_parser_1.applyEdits)(stringifiedJsonContents, edits);
330
340
  // The final contents will be formatted by formatFiles() later
331
341
  tree.write(tsconfigPath, updatedJsonContents);
@@ -161,7 +161,9 @@ function buildTscTargets(configFilePath, projectRoot, options, context) {
161
161
  }
162
162
  }
163
163
  // Build target
164
- if (options.build && (0, node_path_1.basename)(configFilePath) === options.build.configName) {
164
+ if (options.build &&
165
+ (0, node_path_1.basename)(configFilePath) === options.build.configName &&
166
+ isValidPackageJsonBuildConfig(tsConfig, context.workspaceRoot, projectRoot, configFilePath)) {
165
167
  internalProjectReferences ??= resolveInternalProjectReferences(tsConfig, context.workspaceRoot, projectRoot);
166
168
  const targetName = options.build.targetName;
167
169
  targets[targetName] = {
@@ -272,10 +274,16 @@ function getOutputs(configFilePath, tsConfig, internalProjectReferences, workspa
272
274
  }
273
275
  else if (config.options.outDir) {
274
276
  outputs.add(pathToInputOrOutput(config.options.outDir, workspaceRoot, projectRoot));
275
- if (config.options.tsBuildInfoFile &&
276
- !(0, node_path_1.normalize)(config.options.tsBuildInfoFile).startsWith(`${(0, node_path_1.normalize)(config.options.outDir)}${node_path_1.sep}`)) {
277
- // https://www.typescriptlang.org/tsconfig#tsBuildInfoFile
278
- outputs.add(pathToInputOrOutput(config.options.tsBuildInfoFile, workspaceRoot, projectRoot));
277
+ if (config.options.tsBuildInfoFile) {
278
+ if (!(0, node_path_1.normalize)(config.options.tsBuildInfoFile).startsWith(`${(0, node_path_1.normalize)(config.options.outDir)}${node_path_1.sep}`)) {
279
+ // https://www.typescriptlang.org/tsconfig#tsBuildInfoFile
280
+ outputs.add(pathToInputOrOutput(config.options.tsBuildInfoFile, workspaceRoot, projectRoot));
281
+ }
282
+ }
283
+ else if (config.options.rootDir && config.options.rootDir !== '.') {
284
+ // If rootDir is set, then the tsbuildinfo file will be outside the outDir so we need to add it.
285
+ const relativeRootDir = (0, node_path_1.relative)(config.options.rootDir, (0, node_path_1.join)(workspaceRoot, projectRoot));
286
+ outputs.add(pathToInputOrOutput((0, devkit_1.joinPathFragments)(config.options.outDir, relativeRootDir, `*.tsbuildinfo`), workspaceRoot, projectRoot));
279
287
  }
280
288
  }
281
289
  else if (config.fileNames.length) {
@@ -301,6 +309,78 @@ function getOutputs(configFilePath, tsConfig, internalProjectReferences, workspa
301
309
  });
302
310
  return Array.from(outputs);
303
311
  }
312
+ /**
313
+ * Checks whether a `package.json` file has a valid build configuration by ensuring
314
+ * that the `main`, `module`, or `exports` do not include paths from the `rootDir`.
315
+ * Or if `outFile` is defined, it should not be within the `rootDir`.
316
+ *
317
+ * @param tsConfig The TypeScript configuration object.
318
+ * @param workspaceRoot The workspace root path.
319
+ * @param projectRoot The project root path.
320
+ * @param tsConfigPath The path to the TypeScript configuration file.
321
+ * @returns `true` if the package has a valid build configuration; otherwise, `false`.
322
+ */
323
+ function isValidPackageJsonBuildConfig(tsConfig, workspaceRoot, projectRoot, tsConfigPath) {
324
+ if (!(0, node_fs_1.existsSync)((0, devkit_1.joinPathFragments)(projectRoot, 'package.json'))) {
325
+ // If the package.json file does not exist.
326
+ // Assume it's valid because it would be using `project.json` instead.
327
+ return true;
328
+ }
329
+ const packageJson = (0, devkit_1.readJsonFile)((0, devkit_1.joinPathFragments)(projectRoot, 'package.json'));
330
+ const rootDir = tsConfig.options.rootDir ?? 'src/';
331
+ if (!tsConfig.options.rootDir) {
332
+ console.warn(`The 'rootDir' option is not set in the tsconfig file at ${tsConfigPath}. Assuming 'src/' as the root directory.`);
333
+ }
334
+ const isPathWithinSrc = (path) => {
335
+ const resolvedRootDir = (0, node_path_1.resolve)(workspaceRoot, projectRoot, rootDir);
336
+ const pathToCheck = (0, node_path_1.resolve)(workspaceRoot, projectRoot, path);
337
+ return pathToCheck.startsWith(resolvedRootDir);
338
+ };
339
+ // If `outFile` is defined, check the validity of the path.
340
+ if (tsConfig.options.outFile) {
341
+ if (isPathWithinSrc(tsConfig.options.outFile)) {
342
+ return false;
343
+ }
344
+ }
345
+ const buildPaths = ['main', 'module'];
346
+ for (const field of buildPaths) {
347
+ if (packageJson[field] && isPathWithinSrc(packageJson[field])) {
348
+ return false;
349
+ }
350
+ }
351
+ const exports = packageJson?.exports;
352
+ // Checks if the value is a path within the `src` directory.
353
+ const containsInvalidPath = (value) => {
354
+ if (typeof value === 'string') {
355
+ return isPathWithinSrc(value);
356
+ }
357
+ else if (typeof value === 'object') {
358
+ return Object.entries(value).some(([currentKey, subValue]) => {
359
+ // Skip types field
360
+ if (currentKey === 'types') {
361
+ return false;
362
+ }
363
+ if (typeof subValue === 'string') {
364
+ return isPathWithinSrc(subValue);
365
+ }
366
+ return false;
367
+ });
368
+ }
369
+ return false;
370
+ };
371
+ if (typeof exports === 'string' && isPathWithinSrc(exports)) {
372
+ return false;
373
+ }
374
+ // Check nested exports if `exports` is an object.
375
+ if (typeof exports === 'object') {
376
+ for (const key in exports) {
377
+ if (containsInvalidPath(exports[key])) {
378
+ return false;
379
+ }
380
+ }
381
+ }
382
+ return true;
383
+ }
304
384
  function pathToInputOrOutput(path, workspaceRoot, projectRoot) {
305
385
  const pathRelativeToProjectRoot = (0, devkit_1.normalizePath)((0, node_path_1.relative)(projectRoot, path));
306
386
  if (pathRelativeToProjectRoot.startsWith('..')) {
@@ -230,6 +230,9 @@ function computeCompilerOptionsPaths(tsConfig, dependencies) {
230
230
  }
231
231
  function createTmpTsConfig(tsconfigPath, workspaceRoot, projectRoot, dependencies, useWorkspaceAsBaseUrl = false) {
232
232
  const tmpTsConfigPath = (0, path_1.join)(workspaceRoot, 'tmp', projectRoot, process.env.NX_TASK_TARGET_TARGET ?? 'build', 'tsconfig.generated.json');
233
+ if (tsconfigPath === tmpTsConfigPath) {
234
+ return tsconfigPath;
235
+ }
233
236
  const parsedTSConfig = readTsConfigWithRemappedPaths(tsconfigPath, tmpTsConfigPath, dependencies, workspaceRoot);
234
237
  process.on('exit', () => cleanupTmpTsConfigFile(tmpTsConfigPath));
235
238
  if (useWorkspaceAsBaseUrl) {
@@ -1,6 +1,7 @@
1
1
  import { Tree } from '@nx/devkit';
2
2
  import type * as ts from 'typescript';
3
3
  export declare function readTsConfig(tsConfigPath: string, sys?: ts.System): ts.ParsedCommandLine;
4
+ export declare function readTsConfigFromTree(tree: Tree, tsConfigPath: string): ts.ParsedCommandLine;
4
5
  export declare function getRootTsConfigPathInTree(tree: Tree): string | null;
5
6
  export declare function getRelativePathToRootTsConfig(tree: Tree, targetPath: string): string;
6
7
  export declare function getRootTsConfigPath(): string | null;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.readTsConfig = readTsConfig;
4
+ exports.readTsConfigFromTree = readTsConfigFromTree;
4
5
  exports.getRootTsConfigPathInTree = getRootTsConfigPathInTree;
5
6
  exports.getRelativePathToRootTsConfig = getRelativePathToRootTsConfig;
6
7
  exports.getRootTsConfigPath = getRootTsConfigPath;
@@ -20,6 +21,16 @@ function readTsConfig(tsConfigPath, sys) {
20
21
  const readResult = tsModule.readConfigFile(tsConfigPath, sys.readFile);
21
22
  return tsModule.parseJsonConfigFileContent(readResult.config, sys, (0, path_1.dirname)(tsConfigPath));
22
23
  }
24
+ function readTsConfigFromTree(tree, tsConfigPath) {
25
+ if (!tsModule) {
26
+ tsModule = (0, ensure_typescript_1.ensureTypescript)();
27
+ }
28
+ const tsSysFromTree = {
29
+ ...tsModule.sys,
30
+ readFile: (path) => tree.read(path, 'utf-8'),
31
+ };
32
+ return readTsConfig(tsConfigPath, tsSysFromTree);
33
+ }
23
34
  function getRootTsConfigPathInTree(tree) {
24
35
  for (const path of ['tsconfig.base.json', 'tsconfig.json']) {
25
36
  if (tree.exists(path)) {
@@ -4,3 +4,4 @@ export declare function isUsingTsSolutionSetup(tree?: Tree): boolean;
4
4
  export declare function assertNotUsingTsSolutionSetup(tree: Tree, pluginName: string, generatorName: string): void;
5
5
  export declare function findRuntimeTsConfigName(tree: Tree, projectRoot: string): string | null;
6
6
  export declare function updateTsconfigFiles(tree: Tree, projectRoot: string, runtimeTsconfigFileName: string, compilerOptions: Record<string, string | boolean | string[]>, exclude?: string[], rootDir?: string): void;
7
+ export declare function addProjectToTsSolutionWorkspace(tree: Tree, projectDir: string): void;
@@ -5,6 +5,7 @@ exports.isUsingTsSolutionSetup = isUsingTsSolutionSetup;
5
5
  exports.assertNotUsingTsSolutionSetup = assertNotUsingTsSolutionSetup;
6
6
  exports.findRuntimeTsConfigName = findRuntimeTsConfigName;
7
7
  exports.updateTsconfigFiles = updateTsconfigFiles;
8
+ exports.addProjectToTsSolutionWorkspace = addProjectToTsSolutionWorkspace;
8
9
  const devkit_1 = require("@nx/devkit");
9
10
  const tree_1 = require("nx/src/generators/tree");
10
11
  const package_manager_workspaces_1 = require("../package-manager-workspaces");
@@ -66,7 +67,7 @@ function assertNotUsingTsSolutionSetup(tree, pluginName, generatorName) {
66
67
  `We're working hard to support the existing TypeScript setup with the ${artifactString}. We'll soon release a new version of Nx with support for it.`,
67
68
  ],
68
69
  });
69
- process.exit(1);
70
+ throw new Error(`The ${artifactString} doesn't yet support the existing TypeScript setup. See the error above.`);
70
71
  }
71
72
  function findRuntimeTsConfigName(tree, projectRoot) {
72
73
  if (tree.exists((0, devkit_1.joinPathFragments)(projectRoot, 'tsconfig.app.json')))
@@ -94,8 +95,20 @@ function updateTsconfigFiles(tree, projectRoot, runtimeTsconfigFileName, compile
94
95
  rootDir,
95
96
  ...compilerOptions,
96
97
  };
98
+ if (rootDir && rootDir !== '.') {
99
+ // when rootDir is different from '.', the tsbuildinfo file is output
100
+ // at `<outDir>/<relative path to config from rootDir>/`, so we need
101
+ // to set it explicitly to ensure it's output to the outDir
102
+ // https://www.typescriptlang.org/tsconfig/#tsBuildInfoFile
103
+ json.compilerOptions.tsBuildInfoFile = (0, posix_1.join)('out-tsc', projectRoot.split('/').at(-1), (0, posix_1.basename)(runtimeTsconfigFileName, '.json') + '.tsbuildinfo');
104
+ }
105
+ else if (json.compilerOptions.tsBuildInfoFile) {
106
+ // when rootDir is '.' or not set, it would be output to the outDir, so
107
+ // we don't need to set it explicitly
108
+ delete json.compilerOptions.tsBuildInfoFile;
109
+ }
97
110
  const excludeSet = json.exclude
98
- ? new Set(['dist', ...json.exclude, ...exclude])
111
+ ? new Set(['out-tsc', 'dist', ...json.exclude, ...exclude])
99
112
  : new Set(exclude);
100
113
  json.exclude = Array.from(excludeSet);
101
114
  return json;
@@ -115,16 +128,6 @@ function updateTsconfigFiles(tree, projectRoot, runtimeTsconfigFileName, compile
115
128
  return json;
116
129
  });
117
130
  }
118
- if (tree.exists(tsconfigE2E)) {
119
- // tsconfig.json for e2e projects need to have references array
120
- (0, devkit_1.updateJson)(tree, tsconfigE2E, (json) => {
121
- json.references ??= [];
122
- const projectPath = (0, posix_1.relative)(e2eRoot, projectRoot);
123
- if (!json.references.some((x) => x.path === projectPath))
124
- json.references.push({ path: projectPath });
125
- return json;
126
- });
127
- }
128
131
  if (tree.exists('tsconfig.json')) {
129
132
  (0, devkit_1.updateJson)(tree, 'tsconfig.json', (json) => {
130
133
  const projectPath = './' + projectRoot;
@@ -135,3 +138,32 @@ function updateTsconfigFiles(tree, projectRoot, runtimeTsconfigFileName, compile
135
138
  });
136
139
  }
137
140
  }
141
+ function addProjectToTsSolutionWorkspace(tree, projectDir) {
142
+ // If dir is "libs/foo" then use "libs/**" so we don't need so many entries in the workspace file.
143
+ // If the dir is just "foo" then we have to add it as is.
144
+ const baseDir = (0, posix_1.dirname)(projectDir);
145
+ const pattern = baseDir === '.' ? projectDir : `${baseDir}/**`;
146
+ if (tree.exists('pnpm-workspace.yaml')) {
147
+ const { load, dump } = require('@zkochan/js-yaml');
148
+ const workspaceFile = tree.read('pnpm-workspace.yaml', 'utf-8');
149
+ const yamlData = load(workspaceFile);
150
+ if (!yamlData?.packages) {
151
+ yamlData.packages = [];
152
+ }
153
+ if (!yamlData.packages.includes(pattern)) {
154
+ yamlData.packages.push(pattern);
155
+ tree.write('pnpm-workspace.yaml', dump(yamlData, { indent: 2, quotingType: '"', forceQuotes: true }));
156
+ }
157
+ }
158
+ else {
159
+ // Update package.json
160
+ const packageJson = (0, devkit_1.readJson)(tree, 'package.json');
161
+ if (!packageJson.workspaces) {
162
+ packageJson.workspaces = [];
163
+ }
164
+ if (!packageJson.workspaces.includes(pattern)) {
165
+ packageJson.workspaces.push(pattern);
166
+ tree.write('package.json', JSON.stringify(packageJson, null, 2));
167
+ }
168
+ }
169
+ }