diff --git a/packages/workspace/src/command-line/report.spec.ts b/packages/workspace/src/command-line/report.spec.ts new file mode 100644 index 0000000000..1b85c7ae64 --- /dev/null +++ b/packages/workspace/src/command-line/report.spec.ts @@ -0,0 +1,122 @@ +import { findInstalledCommunityPlugins } from './report'; +import * as devkit from '@nrwl/devkit'; +import * as fileUtils from '../utilities/fileutils'; +import { join } from 'path'; + +jest.mock('@nrwl/tao/src/utils/app-root', () => ({ + appRootPath: '', +})); + +jest.mock('../utilities/fileutils', () => ({ + ...(jest.requireActual('../utilities/fileutils') as typeof fileUtils), + resolve: (file) => `node_modules/${file}`, +})); + +describe('report', () => { + describe('findInstalledCommunityPlugins', () => { + afterEach(() => jest.resetAllMocks()); + + it('should read angular-devkit plugins', () => { + jest.spyOn(devkit, 'readJsonFile').mockImplementation((path) => { + console.log(path); + if (path === 'package.json') { + return { + dependencies: { + 'plugin-one': '1.0.0', + }, + devDependencies: { + 'plugin-two': '2.0.0', + }, + }; + } else if ( + path.includes(join('node_modules', 'plugin-one', 'package.json')) + ) { + return { + 'ng-update': {}, + version: '1.0.0', + }; + } else if ( + path.includes(join('node_modules', 'plugin-two', 'package.json')) + ) { + return { + schematics: {}, + version: '2.0.0', + }; + } + }); + const plugins = findInstalledCommunityPlugins(); + expect(plugins).toEqual([ + { package: 'plugin-one', version: '1.0.0' }, + { package: 'plugin-two', version: '2.0.0' }, + ]); + }); + + it('should read nx devkit plugins', () => { + jest.spyOn(devkit, 'readJsonFile').mockImplementation((path) => { + if (path === 'package.json') { + return { + dependencies: { + 'plugin-one': '1.0.0', + }, + devDependencies: { + 'plugin-two': '2.0.0', + }, + }; + } else if ( + path.includes(join('node_modules', 'plugin-one', 'package.json')) + ) { + return { + 'nx-migrations': {}, + version: '1.0.0', + }; + } else if ( + path.includes(join('node_modules', 'plugin-two', 'package.json')) + ) { + return { + generators: {}, + version: '2.0.0', + }; + } + }); + const plugins = findInstalledCommunityPlugins(); + expect(plugins).toEqual([ + { package: 'plugin-one', version: '1.0.0' }, + { package: 'plugin-two', version: '2.0.0' }, + ]); + }); + + it('should not include non-plugins', () => { + jest.spyOn(devkit, 'readJsonFile').mockImplementation((path) => { + if (path === 'package.json') { + return { + dependencies: { + 'plugin-one': '1.0.0', + }, + devDependencies: { + 'plugin-two': '2.0.0', + 'other-package': '1.44.0', + }, + }; + } else if ( + path.includes(join('node_modules', 'plugin-one', 'package.json')) + ) { + return { + 'nx-migrations': {}, + }; + } else if ( + path.includes(join('node_modules', 'plugin-two', 'package.json')) + ) { + return { + generators: {}, + }; + } else { + return { + version: '', + }; + } + }); + const plugins = findInstalledCommunityPlugins().map((x) => x.package); + expect(plugins).not.toContain('other-package'); + }); + }); +}); diff --git a/packages/workspace/src/command-line/report.ts b/packages/workspace/src/command-line/report.ts index 5c33967094..bdfed40e3e 100644 --- a/packages/workspace/src/command-line/report.ts +++ b/packages/workspace/src/command-line/report.ts @@ -6,6 +6,8 @@ import { readJsonFile, } from '@nrwl/devkit'; import { output } from '../utilities/output'; +import { join } from 'path'; +import { resolve } from '../utilities/fileutils'; export const packagesWeCareAbout = [ 'nx', @@ -22,6 +24,7 @@ export const packagesWeCareAbout = [ '@nrwl/node', '@nrwl/nx-cloud', '@nrwl/react', + '@nrwl/react-native', '@nrwl/schematics', '@nrwl/tao', '@nrwl/web', @@ -29,8 +32,15 @@ export const packagesWeCareAbout = [ '@nrwl/storybook', '@nrwl/gatsby', 'typescript', + 'rxjs', ]; +export const packagesWeIgnoreInCommunityReport = new Set([ + ...packagesWeCareAbout, + '@schematics/angular', + '@nestjs/schematics', +]); + export const report = { command: 'report', describe: 'Reports useful version numbers to copy into the Nx issue template', @@ -58,14 +68,15 @@ function reportHandler() { ]; packagesWeCareAbout.forEach((p) => { - let status = 'Not Found'; - try { - const packageJsonPath = require.resolve(`${p}/package.json`, { - paths: [appRootPath], - }); - status = readJsonFile(packageJsonPath).version; - } catch {} - bodyLines.push(`${chalk.green(p)} : ${chalk.bold(status)}`); + bodyLines.push(`${chalk.green(p)} : ${chalk.bold(readPackageVersion(p))}`); + }); + + bodyLines.push('---------------------------------------'); + + const communityPlugins = findInstalledCommunityPlugins(); + bodyLines.push('Community plugins:'); + communityPlugins.forEach((p) => { + bodyLines.push(`\t ${chalk.green(p.package)}: ${chalk.bold(p.version)}`); }); output.log({ @@ -73,3 +84,65 @@ function reportHandler() { bodyLines, }); } + +export function readPackageJson(p: string) { + try { + const packageJsonPath = resolve(`${p}/package.json`, { + paths: [appRootPath], + }); + return readJsonFile(packageJsonPath); + } catch { + return {}; + } +} + +export function readPackageVersion(p: string) { + let status = 'Not Found'; + try { + status = readPackageJson(p).version; + } catch {} + return status; +} + +export function findInstalledCommunityPlugins(): { + package: string; + version: string; +}[] { + const { dependencies, devDependencies } = readJsonFile( + join(appRootPath, 'package.json') + ); + const deps = [ + Object.keys(dependencies || {}), + Object.keys(devDependencies || {}), + ].flat(); + + return deps.reduce( + (arr: any[], nextDep: string): { project: string; version: string }[] => { + if (packagesWeIgnoreInCommunityReport.has(nextDep)) { + return arr; + } + try { + const depPackageJson = readPackageJson(nextDep); + if ( + [ + 'ng-update', + 'nx-migrations', + 'schematics', + 'generators', + 'builders', + 'executors', + ].some((field) => field in depPackageJson) + ) { + arr.push({ package: nextDep, version: depPackageJson.version }); + return arr; + } else { + return arr; + } + } catch { + console.warn(`Error parsing packageJson for ${nextDep}`); + return arr; + } + }, + [] + ); +} diff --git a/packages/workspace/src/utilities/fileutils.ts b/packages/workspace/src/utilities/fileutils.ts index 9e9ddc942e..1c296e21ce 100644 --- a/packages/workspace/src/utilities/fileutils.ts +++ b/packages/workspace/src/utilities/fileutils.ts @@ -8,7 +8,7 @@ import { renameSync as fsRenameSync, } from 'fs'; import { ensureDirSync } from 'fs-extra'; -import { basename, dirname, resolve } from 'path'; +import { basename, dirname, resolve as pathResolve } from 'path'; import { parseJson, serializeJson, @@ -40,7 +40,7 @@ export function updateJsonFile(path: string, callback: (a: any) => any) { export function copyFile(file: string, target: string) { const f = basename(file); const source = createReadStream(file); - const dest = createWriteStream(resolve(target, f)); + const dest = createWriteStream(pathResolve(target, f)); source.pipe(dest); source.on('error', (e) => console.error(e)); } @@ -62,7 +62,7 @@ export function fileExists(filePath: string): boolean { } export function createDirectory(directoryPath: string) { - const parentPath = resolve(directoryPath, '..'); + const parentPath = pathResolve(directoryPath, '..'); if (!directoryExists(parentPath)) { createDirectory(parentPath); } @@ -84,7 +84,7 @@ export function renameSync( } // Make sure parent path exists - const parentPath = resolve(to, '..'); + const parentPath = pathResolve(to, '..'); createDirectory(parentPath); fsRenameSync(from, to); @@ -102,3 +102,5 @@ export function isRelativePath(path: string): boolean { path.startsWith('../') ); } + +export const resolve = require.resolve; diff --git a/scripts/check-imports.js b/scripts/check-imports.js index 629ca05d8f..06a7d3ff29 100644 --- a/scripts/check-imports.js +++ b/scripts/check-imports.js @@ -29,6 +29,7 @@ function check() { 'packages/create-nx-workspace/bin/create-nx-workspace.ts', 'packages/create-nx-plugin/bin/create-nx-plugin.ts', 'packages/workspace/src/command-line/affected.ts', + 'packages/workspace/src/command-line/report.ts', 'packages/workspace/src/core/file-utils.ts', 'packages/workspace/src/generators/preset/preset.ts', 'packages/workspace/src/generators/init/init.ts',