diff --git a/e2e/nx-misc/src/encapsulated.test.ts b/e2e/nx-misc/src/encapsulated.test.ts index fa1ddc6174..b02c491eae 100644 --- a/e2e/nx-misc/src/encapsulated.test.ts +++ b/e2e/nx-misc/src/encapsulated.test.ts @@ -7,6 +7,7 @@ import { checkFilesExist, cleanupProject, getPublishedVersion, + uniq, } from '@nrwl/e2e/utils'; describe('encapsulated nx', () => { @@ -69,6 +70,18 @@ describe('encapsulated nx', () => { new RegExp(`nx.*:.*${getPublishedVersion()}`) ); }); + + it('should work with basic generators', () => { + updateJson('nx.json', (j) => { + j.installation.plugins ??= {}; + j.installation.plugins['@nrwl/workspace'] = getPublishedVersion(); + return j; + }); + expect(() => + runEncapsulatedNx(`g npm-package ${uniq('pkg')}`) + ).not.toThrow(); + expect(() => checkFilesExist()); + }); }); function assertNoRootPackages() { diff --git a/packages/nx/src/command-line/generate.ts b/packages/nx/src/command-line/generate.ts index db279324a9..4fe9c2d8a7 100644 --- a/packages/nx/src/command-line/generate.ts +++ b/packages/nx/src/command-line/generate.ts @@ -21,6 +21,7 @@ import { getLocalWorkspacePlugins } from '../utils/plugins/local-plugins'; import { printHelp } from '../utils/print-help'; import { workspaceRoot } from '../utils/workspace-root'; import { NxJsonConfiguration } from '../config/nx-json'; +import { findInstalledPlugins } from '../utils/plugins/installed-plugins'; export interface GenerateOptions { collectionName: string; @@ -50,14 +51,10 @@ async function promptForCollection( interactive: boolean, projectsConfiguration: ProjectsConfigurations ): Promise { - const packageJson = readJsonFile(`${workspaceRoot}/package.json`); const localPlugins = getLocalWorkspacePlugins(projectsConfiguration); const installedCollections = Array.from( - new Set([ - ...Object.keys(packageJson.dependencies || {}), - ...Object.keys(packageJson.devDependencies || {}), - ]) + new Set(findInstalledPlugins().map((x) => x.name)) ); const choicesMap = new Set(); diff --git a/packages/nx/src/command-line/init.ts b/packages/nx/src/command-line/init.ts index 7195bcdb03..b36a09a36d 100644 --- a/packages/nx/src/command-line/init.ts +++ b/packages/nx/src/command-line/init.ts @@ -6,6 +6,7 @@ import { directoryExists, readJsonFile } from '../utils/fileutils'; import { PackageJson } from '../utils/package-json'; import * as parser from 'yargs-parser'; import { generateEncapsulatedNxSetup } from '../nx-init/encapsulated/add-nx-scripts'; +import { prerelease } from 'semver'; export async function initHandler() { const args = process.argv.slice(2).join(' '); @@ -16,7 +17,10 @@ export async function initHandler() { }, }) as any as { encapsulated: boolean }; - const version = process.env.NX_VERSION ?? 'latest'; + const version = + process.env.NX_VERSION ?? prerelease(require('../../package.json').version) + ? 'next' + : 'latest'; if (process.env.NX_VERSION) { console.log(`Using version ${process.env.NX_VERSION}`); } diff --git a/packages/nx/src/command-line/list.ts b/packages/nx/src/command-line/list.ts index c134cbd697..80f333aea6 100644 --- a/packages/nx/src/command-line/list.ts +++ b/packages/nx/src/command-line/list.ts @@ -3,7 +3,7 @@ import { output } from '../utils/output'; import { fetchCommunityPlugins, fetchCorePlugins, - getInstalledPluginsFromPackageJson, + getInstalledPluginsAndCapabilities, listCommunityPlugins, listCorePlugins, listInstalledPlugins, @@ -49,7 +49,7 @@ export async function listHandler(args: ListArgs): Promise { const localPlugins = getLocalWorkspacePlugins( readProjectsConfigurationFromProjectGraph(projectGraph) ); - const installedPlugins = getInstalledPluginsFromPackageJson( + const installedPlugins = getInstalledPluginsAndCapabilities( workspaceRoot, corePlugins, communityPlugins diff --git a/packages/nx/src/command-line/report.spec.ts b/packages/nx/src/command-line/report.spec.ts index c95baf5ea4..80fa4fd22f 100644 --- a/packages/nx/src/command-line/report.spec.ts +++ b/packages/nx/src/command-line/report.spec.ts @@ -79,7 +79,7 @@ describe('report', () => { ); const plugins = findInstalledCommunityPlugins(); expect(plugins).toEqual([ - expect.objectContaining({ package: 'plugin-two', version: '2.0.0' }), + expect.objectContaining({ name: 'plugin-two', version: '2.0.0' }), ]); }); @@ -112,8 +112,8 @@ describe('report', () => { ); const plugins = findInstalledCommunityPlugins(); expect(plugins).toEqual([ - expect.objectContaining({ package: 'plugin-one', version: '1.0.0' }), - expect.objectContaining({ package: 'plugin-two', version: '2.0.0' }), + expect.objectContaining({ name: 'plugin-one', version: '1.0.0' }), + expect.objectContaining({ name: 'plugin-two', version: '2.0.0' }), ]); }); @@ -152,8 +152,8 @@ describe('report', () => { const plugins = findInstalledCommunityPlugins(); expect(plugins).toEqual( expect.arrayContaining([ - expect.objectContaining({ package: 'plugin-one', version: '1.0.0' }), - expect.objectContaining({ package: 'plugin-two', version: '2.0.0' }), + expect.objectContaining({ name: 'plugin-one', version: '1.0.0' }), + expect.objectContaining({ name: 'plugin-two', version: '2.0.0' }), ]) ); }); @@ -189,7 +189,7 @@ describe('report', () => { }, }) ); - const plugins = findInstalledCommunityPlugins().map((x) => x.package); + const plugins = findInstalledCommunityPlugins().map((x) => x.name); expect(plugins).not.toContain('other-package'); }); }); diff --git a/packages/nx/src/command-line/report.ts b/packages/nx/src/command-line/report.ts index c2d8f5c1dc..859f0ccf65 100644 --- a/packages/nx/src/command-line/report.ts +++ b/packages/nx/src/command-line/report.ts @@ -20,6 +20,7 @@ import { } from '../project-graph/project-graph'; import { gt, valid } from 'semver'; import { NxJsonConfiguration } from '../config/nx-json'; +import { findInstalledPlugins } from '../utils/plugins/installed-plugins'; const nxPackageJson = readJsonFile( join(__dirname, '../../package.json') @@ -79,11 +80,11 @@ export async function reportHandler() { if (communityPlugins.length) { bodyLines.push(LINE_SEPARATOR); - padding = Math.max(...communityPlugins.map((x) => x.package.length)) + 1; + padding = Math.max(...communityPlugins.map((x) => x.name.length)) + 1; bodyLines.push('Community plugins:'); communityPlugins.forEach((p) => { bodyLines.push( - `${chalk.green(p.package.padEnd(padding))}: ${chalk.bold(p.version)}` + `${chalk.green(p.name.padEnd(padding))}: ${chalk.bold(p.version)}` ); }); } @@ -129,9 +130,7 @@ export interface ReportData { pm: PackageManager; pmVersion: string; localPlugins: string[]; - communityPlugins: (PackageJson & { - package: string; - })[]; + communityPlugins: PackageJson[]; packageVersionsWeCareAbout: { package: string; version: string; @@ -249,47 +248,15 @@ export function findMisalignedPackagesForPackage( : undefined; } -export function findInstalledCommunityPlugins(): (PackageJson & { - package: string; -})[] { - const packageJsonDeps = getDependenciesFromPackageJson(); - const nxJsonDeps = getDependenciesFromNxJson(); - const deps = packageJsonDeps.concat(nxJsonDeps); - return deps.reduce( - (arr: any[], nextDep: string): { project: string; version: string }[] => { - if ( - patternsWeIgnoreInCommunityReport.some((pattern) => - typeof pattern === 'string' - ? pattern === nextDep - : pattern.test(nextDep) - ) - ) { - return arr; - } - try { - const depPackageJson: Partial = - readPackageJson(nextDep) || {}; - if ( - [ - 'ng-update', - 'nx-migrations', - 'schematics', - 'generators', - 'builders', - 'executors', - ].some((field) => field in depPackageJson) - ) { - arr.push({ package: nextDep, ...depPackageJson }); - return arr; - } else { - return arr; - } - } catch { - console.warn(`Error parsing packageJson for ${nextDep}`); - return arr; - } - }, - [] +export function findInstalledCommunityPlugins(): PackageJson[] { + const installedPlugins = findInstalledPlugins(); + return installedPlugins.filter( + (dep) => + !patternsWeIgnoreInCommunityReport.some((pattern) => + typeof pattern === 'string' + ? pattern === dep.name + : pattern.test(dep.name) + ) ); } export function findInstalledPackagesWeCareAbout() { @@ -301,25 +268,3 @@ export function findInstalledPackagesWeCareAbout() { return acc; }, [] as { package: string; version: string }[]); } - -function getDependenciesFromPackageJson( - packageJsonPath = 'package.json' -): string[] { - try { - const { dependencies, devDependencies } = readJsonFile( - join(workspaceRoot, packageJsonPath) - ); - return Object.keys({ ...dependencies, ...devDependencies }); - } catch {} - return []; -} - -function getDependenciesFromNxJson(): string[] { - const { installation } = readJsonFile( - join(workspaceRoot, 'nx.json') - ); - if (!installation) { - return []; - } - return ['nx', ...Object.keys(installation.plugins || {})]; -} diff --git a/packages/nx/src/nx-init/encapsulated/add-nx-scripts.ts b/packages/nx/src/nx-init/encapsulated/add-nx-scripts.ts index 8a76d7dbf6..1c2a36fbaf 100644 --- a/packages/nx/src/nx-init/encapsulated/add-nx-scripts.ts +++ b/packages/nx/src/nx-init/encapsulated/add-nx-scripts.ts @@ -1,6 +1,5 @@ import { execSync } from 'child_process'; import { readFileSync, constants as FsConstants } from 'fs'; -import { stripIndent } from 'nx/src/utils/logger'; import * as path from 'path'; import { valid } from 'semver'; import { NxJsonConfiguration } from '../../config/nx-json'; @@ -20,22 +19,18 @@ const NODE_MISSING_ERR = const NPM_MISSING_ERR = 'Nx requires npm to be available. To install NodeJS and NPM, see: https://nodejs.org/en/download/ .'; -const BATCH_SCRIPT_CONTENTS = stripIndent( - `set path_to_root=%~dp0 - WHERE node >nul 2>nul - IF %ERRORLEVEL% NEQ 0 (ECHO ${NODE_MISSING_ERR}; EXIT 1) - WHERE npm >nul 2>nul - IF %ERRORLEVEL% NEQ 0 (ECHO ${NPM_MISSING_ERR}; EXIT 1) - node ${path.win32.join('%path_to_root%', nxWrapperPath(path.win32))} %*` -); +const BATCH_SCRIPT_CONTENTS = `set path_to_root=%~dp0 +WHERE node >nul 2>nul +IF %ERRORLEVEL% NEQ 0 (ECHO ${NODE_MISSING_ERR}; EXIT 1) +WHERE npm >nul 2>nul +IF %ERRORLEVEL% NEQ 0 (ECHO ${NPM_MISSING_ERR}; EXIT 1) +node ${path.win32.join('%path_to_root%', nxWrapperPath(path.win32))} %*`; -const SHELL_SCRIPT_CONTENTS = stripIndent( - `#!/bin/bash - command -v node >/dev/null 2>&1 || { echo >&2 "${NODE_MISSING_ERR}"; exit 1; } - command -v npm >/dev/null 2>&1 || { echo >&2 "${NPM_MISSING_ERR}"; exit 1; } - path_to_root=$(dirname $BASH_SOURCE) - node ${path.posix.join('$path_to_root', nxWrapperPath(path.posix))} $@` -); +const SHELL_SCRIPT_CONTENTS = `#!/bin/bash +command -v node >/dev/null 2>&1 || { echo >&2 "${NODE_MISSING_ERR}"; exit 1; } +command -v npm >/dev/null 2>&1 || { echo >&2 "${NPM_MISSING_ERR}"; exit 1; } +path_to_root=$(dirname $BASH_SOURCE) +node ${path.posix.join('$path_to_root', nxWrapperPath(path.posix))} $@`; export function generateEncapsulatedNxSetup(version?: string) { const host = new FsTree(process.cwd(), false); @@ -81,9 +76,10 @@ export function updateGitIgnore(host: Tree) { } function getNodeScriptContents() { - // Read nxw.js, but remove any empty comments or comments that start with `INTERNAL: ` + // Read nxw.js, but remove any empty comments or comments that start with `//#: ` + // This removes the sourceMapUrl since it is invalid, as well as any internal comments. return readFileSync(path.join(__dirname, 'nxw.js'), 'utf-8').replace( - /(\/\/ INTERNAL: .*)|(\/\/\w*)$/gm, + /(\/\/# .*)|(\/\/\w*)$/gm, '' ); } diff --git a/packages/nx/src/nx-init/encapsulated/nxw.ts b/packages/nx/src/nx-init/encapsulated/nxw.ts index 93a76f4600..bd29c73935 100644 --- a/packages/nx/src/nx-init/encapsulated/nxw.ts +++ b/packages/nx/src/nx-init/encapsulated/nxw.ts @@ -2,9 +2,9 @@ // that your local installation matches nx.json. // See: https://nx.dev/more-concepts/encapsulated-nx-and-the-wrapper for more info. // -// INTERNAL: The contents of this file are executed before packages are installed. -// INTERNAL: As such, we should not import anything from nx, other @nrwl packages, -// INTERNAL: or any other npm packages. Only import node builtins. +//# The contents of this file are executed before packages are installed. +//# As such, we should not import anything from nx, other @nrwl packages, +//# or any other npm packages. Only import node builtins. const fs: typeof import('fs') = require('fs'); const path: typeof import('path') = require('path'); @@ -19,24 +19,23 @@ function matchesCurrentNxInstall( nxJsonInstallation: NxJsonConfiguration['installation'] ) { try { - const currentInstallation: PackageJson = JSON.parse( - fs.readFileSync(installationPath, 'utf-8') - ); + const currentInstallation: PackageJson = require(installationPath); if ( - currentInstallation.dependencies['nx'] !== nxJsonInstallation.version || - JSON.parse( - fs.readFileSync( - path.join(installationPath, 'node_modules', 'nx', 'package.json'), - 'utf-8' - ) - ).version !== nxJsonInstallation.version + currentInstallation.devDependencies['nx'] !== + nxJsonInstallation.version || + require(path.join( + path.dirname(installationPath), + 'node_modules', + 'nx', + 'package.json' + )).version !== nxJsonInstallation.version ) { return false; } for (const [plugin, desiredVersion] of Object.entries( nxJsonInstallation.plugins || {} )) { - if (currentInstallation.dependencies[plugin] !== desiredVersion) { + if (currentInstallation.devDependencies[plugin] !== desiredVersion) { return false; } } @@ -58,7 +57,7 @@ function ensureUpToDateInstallation() { let nxJson: NxJsonConfiguration; try { - nxJson = JSON.parse(fs.readFileSync(nxJsonPath, 'utf-8')); + nxJson = require(nxJsonPath); } catch { console.error( '[NX]: nx.json is required when running in encapsulated mode. Run `npx nx init --encapsulated` to restore it.' diff --git a/packages/nx/src/utils/plugins/index.ts b/packages/nx/src/utils/plugins/index.ts index 9ac357e92d..5ff8fa7243 100644 --- a/packages/nx/src/utils/plugins/index.ts +++ b/packages/nx/src/utils/plugins/index.ts @@ -4,7 +4,7 @@ export { } from './community-plugins'; export { fetchCorePlugins, listCorePlugins } from './core-plugins'; export { - getInstalledPluginsFromPackageJson, + getInstalledPluginsAndCapabilities, listInstalledPlugins, } from './installed-plugins'; export { diff --git a/packages/nx/src/utils/plugins/installed-plugins.ts b/packages/nx/src/utils/plugins/installed-plugins.ts index 9f32d388fa..573ea621bf 100644 --- a/packages/nx/src/utils/plugins/installed-plugins.ts +++ b/packages/nx/src/utils/plugins/installed-plugins.ts @@ -4,20 +4,78 @@ import type { CommunityPlugin, CorePlugin, PluginCapabilities } from './models'; import { getPluginCapabilities } from './plugin-capabilities'; import { hasElements } from './shared'; import { readJsonFile } from '../fileutils'; -import { readModulePackageJson } from '../package-json'; +import { PackageJson, readModulePackageJson } from '../package-json'; +import { workspaceRoot } from '../workspace-root'; +import { join } from 'path'; +import { NxJsonConfiguration } from '../../config/nx-json'; -export function getInstalledPluginsFromPackageJson( +export function findInstalledPlugins(): PackageJson[] { + const packageJsonDeps = getDependenciesFromPackageJson(); + const nxJsonDeps = getDependenciesFromNxJson(); + const deps = packageJsonDeps.concat(nxJsonDeps); + const result: PackageJson[] = []; + for (const dep of deps) { + const pluginPackageJson = getNxPluginPackageJsonOrNull(dep); + if (pluginPackageJson) { + result.push(pluginPackageJson); + } + } + return result; +} + +function getNxPluginPackageJsonOrNull(pkg: string): PackageJson | null { + try { + const { packageJson } = readModulePackageJson(pkg, [ + workspaceRoot, + join(workspaceRoot, '.nx', ' installation'), + ]); + return packageJson && + [ + 'ng-update', + 'nx-migrations', + 'schematics', + 'generators', + 'builders', + 'executors', + ].some((field) => field in packageJson) + ? packageJson + : null; + } catch { + return null; + } +} + +function getDependenciesFromPackageJson( + packageJsonPath = 'package.json' +): string[] { + try { + const { dependencies, devDependencies } = readJsonFile( + join(workspaceRoot, packageJsonPath) + ); + return Object.keys({ ...dependencies, ...devDependencies }); + } catch {} + return []; +} + +function getDependenciesFromNxJson(): string[] { + const { installation } = readJsonFile( + join(workspaceRoot, 'nx.json') + ); + if (!installation) { + return []; + } + return ['nx', ...Object.keys(installation.plugins || {})]; +} + +export function getInstalledPluginsAndCapabilities( workspaceRoot: string, corePlugins: CorePlugin[], communityPlugins: CommunityPlugin[] = [] ): Map { - const packageJson = readJsonFile(`${workspaceRoot}/package.json`); - const plugins = new Set([ ...corePlugins.map((p) => p.name), ...communityPlugins.map((p) => p.name), - ...Object.keys(packageJson.dependencies || {}), - ...Object.keys(packageJson.devDependencies || {}), + ...findInstalledPlugins().map((p) => p.name), ]); return new Map(