From edb589c1e9fbbc446c4c77d51267374a63c05dc6 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 15 Sep 2021 11:55:33 +0200 Subject: [PATCH 1/8] chore(nx-generator): Replace internal dev dependency versions with `*` Splitting v8/9 releases will have the problem of keeping dev dependencies in sync since the v8 daily release will no longer bump dependency versions in v9. Dev dependencies are shared between v8 and v9 but no actual dependencies are shared. Northstar already uses `*` versions for dev dependencies and since the packages affected are also in the monorepo symlinked this should have no consequence for day to day. By converting all internal dev dependencies to `*` we won't need to bump them anymore. --- tools/generators/star-dev-deps/README.md | 36 +++++ tools/generators/star-dev-deps/index.spec.ts | 160 +++++++++++++++++++ tools/generators/star-dev-deps/index.ts | 62 +++++++ tools/generators/star-dev-deps/schema.json | 8 + tools/generators/star-dev-deps/schema.ts | 1 + 5 files changed, 267 insertions(+) create mode 100644 tools/generators/star-dev-deps/README.md create mode 100644 tools/generators/star-dev-deps/index.spec.ts create mode 100644 tools/generators/star-dev-deps/index.ts create mode 100644 tools/generators/star-dev-deps/schema.json create mode 100644 tools/generators/star-dev-deps/schema.ts diff --git a/tools/generators/star-dev-deps/README.md b/tools/generators/star-dev-deps/README.md new file mode 100644 index 00000000000000..6be734869aadd5 --- /dev/null +++ b/tools/generators/star-dev-deps/README.md @@ -0,0 +1,36 @@ +# star-dev-deps + +For all packages in the monorepo, replace dev dependencies on other internal packages in the monorepo with the `*` +version. + + + +- [NOTES](#notes) +- [Usage](#usage) + - [Examples](#examples) + + + +## NOTES + +- Only affects dev dependencies that are also published from the monorepo +- Does not affect third party dependencies +- Will run for every package in the monorepo + +## Usage + +```sh +yarn nx workspace-generator star-dev-deps +``` + +Show what will be generated without writing to disk: + +```sh +yarn nx workspace-generator star-dev-deps --dry-run +``` + +### Examples + +```sh +yarn nx workspace-generator star-dev-deps +``` diff --git a/tools/generators/star-dev-deps/index.spec.ts b/tools/generators/star-dev-deps/index.spec.ts new file mode 100644 index 00000000000000..098092b210ad49 --- /dev/null +++ b/tools/generators/star-dev-deps/index.spec.ts @@ -0,0 +1,160 @@ +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { + Tree, + readProjectConfiguration, + addProjectConfiguration, + serializeJson, + readWorkspaceConfiguration, + readJson, +} from '@nrwl/devkit'; + +import generator from './index'; +import { StarDevDepsGeneratorSchema } from './schema'; + +describe('star-dev-deps generator', () => { + let tree: Tree; + const options: StarDevDepsGeneratorSchema = { name: 'test' }; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + setupDummyPackage(tree, { + packageName: '@proj/eslint-plugin', + version: '1.0.0', + }); + }); + + it('should change internal package devdeps to * version', async () => { + const devPkgName = '@proj/eslint-plugin'; + + setupDummyPackage(tree, { + packageName: '@proj/react-button', + devDependencies: { [devPkgName]: '1.0.0' }, + }); + + await generator(tree); + const pkgJson = readJson(tree, 'packages/react-button/package.json'); + expect(pkgJson.devDependencies[devPkgName]).toBe('*'); + }); + + it('should run for all packages in the monorepo', async () => { + const devPkgName = '@proj/eslint-plugin'; + + setupDummyPackage(tree, { + packageName: '@proj/react-button', + devDependencies: { [devPkgName]: '1.0.0' }, + }); + + setupDummyPackage(tree, { + packageName: '@proj/react-avatar', + devDependencies: { [devPkgName]: '1.0.0' }, + }); + + setupDummyPackage(tree, { + packageName: '@proj/react-menu', + devDependencies: { [devPkgName]: '1.0.0' }, + }); + + await generator(tree); + + ['react-button', 'react-avatar', 'react-menu'].forEach(name => { + const path = `packages/${name}/package.json`; + const pkgJson = readJson(tree, path); + expect(pkgJson.devDependencies[devPkgName]).toBe('*'); + }); + }); + + it('should also modify non-v9 packages', async () => { + const devPkgName = '@proj/eslint-plugin'; + + setupDummyPackage(tree, { + packageName: '@proj/react-button', + version: '1.0.0', + devDependencies: { [devPkgName]: '1.0.0' }, + }); + + await generator(tree); + const pkgJson = readJson(tree, 'packages/react-button/package.json'); + expect(pkgJson.devDependencies[devPkgName]).toBe('*'); + }); + + it('should only modify dev dependencies', async () => { + const devPkgName = '@proj/eslint-plugin'; + const expectedVersion = '1.0.0'; + + setupDummyPackage(tree, { + packageName: '@proj/react-button', + version: '9.0.0', + dependencies: { [devPkgName]: expectedVersion }, + }); + + await generator(tree); + const pkgJson = readJson(tree, 'packages/react-button/package.json'); + expect(pkgJson.dependencies[devPkgName]).toBe(expectedVersion); + }); + + it('should not modify external dev dependencies', async () => { + const devPkgName = 'jest'; + const expectedVersion = '1.0.0'; + + setupDummyPackage(tree, { + packageName: '@proj/react-button', + version: '9.0.0', + dependencies: { [devPkgName]: expectedVersion }, + }); + + await generator(tree); + const pkgJson = readJson(tree, 'packages/react-button/package.json'); + expect(pkgJson.dependencies[devPkgName]).toBe(expectedVersion); + }); +}); + +function setupDummyPackage( + tree: Tree, + options: Partial<{ + packageName: string; + version: string; + devDependencies: Record; + dependencies: Record; + projectConfiguration: Partial>; + }>, +) { + const workspaceConfig = readWorkspaceConfiguration(tree); + const defaults = { + version: '9.0.0-alpha.40', + dependencies: { + [`@${workspaceConfig.npmScope}/react-make-styles`]: '^9.0.0-alpha.38', + [`@${workspaceConfig.npmScope}/react-theme`]: '^9.0.0-alpha.13', + [`@${workspaceConfig.npmScope}/react-utilities`]: '^9.0.0-alpha.25', + tslib: '^2.1.0', + someThirdPartyDep: '^11.1.2', + }, + }; + + const normalizedOptions = { ...defaults, ...options }; + const pkgName = options.packageName || ''; + const normalizedPkgName = pkgName.replace(`@${workspaceConfig.npmScope}/`, ''); + const paths = { + root: `packages/${normalizedPkgName}`, + }; + + const templates = { + packageJson: { + name: pkgName, + version: normalizedOptions.version, + dependencies: normalizedOptions.dependencies, + devDependencies: normalizedOptions.devDependencies, + }, + }; + + tree.write(`${paths.root}/package.json`, serializeJson(templates.packageJson)); + + addProjectConfiguration(tree, pkgName, { + root: paths.root, + projectType: 'library', + targets: {}, + tags: ['platform:web'], + ...options.projectConfiguration, + }); + + return tree; +} diff --git a/tools/generators/star-dev-deps/index.ts b/tools/generators/star-dev-deps/index.ts new file mode 100644 index 00000000000000..63feadd653a51f --- /dev/null +++ b/tools/generators/star-dev-deps/index.ts @@ -0,0 +1,62 @@ +import { Tree, updateJson, getProjects, formatFiles, readWorkspaceConfiguration, readJson } from '@nrwl/devkit'; +import { getProjectConfig, printUserLogs, UserLog } from '../../utils'; +import { PackageJson } from '../../types'; + +export default async function (host: Tree) { + const userLog: UserLog = []; + + runMigration(host, userLog); + + formatFiles(host); + + return () => { + printUserLogs(userLog); + }; +} + +function runMigrationOnProject(host: Tree, packageName: string, userLog: UserLog) { + const projectConfig = getProjectConfig(host, { packageName }); + const packageJsonPath = projectConfig.paths.packageJson; + + updateJson(host, packageJsonPath, (packageJson: PackageJson) => { + if (packageJson.devDependencies) { + Object.keys(packageJson.devDependencies).forEach(dependency => { + if (isPackageInMonorepo(dependency, host) && packageJson.devDependencies) { + userLog.push({ + type: 'info', + message: `Updating package ${packageName}`, + }); + packageJson.devDependencies[dependency] = '*'; + } + }); + } + + return packageJson; + }); +} + +function runMigration(host: Tree, userLog: UserLog) { + const projects = getProjects(host); + + projects.forEach((project, projectName) => { + runMigrationOnProject(host, projectName, userLog); + }); +} + +/** + * @returns whether the packageName is internally in the monorepo + */ +function isPackageInMonorepo(packageName: string, host: Tree) { + let config: ReturnType; + try { + config = getProjectConfig(host, { packageName }); + } catch (err) { + if (!(err as Error).message.startsWith('Error: Cannot find configuration for')) { + throw err; + } + + return false; + } + + return true; +} diff --git a/tools/generators/star-dev-deps/schema.json b/tools/generators/star-dev-deps/schema.json new file mode 100644 index 00000000000000..1e8340e9594658 --- /dev/null +++ b/tools/generators/star-dev-deps/schema.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/schema", + "cli": "nx", + "id": "star-dev-deps", + "type": "object", + "properties": {}, + "required": [] +} diff --git a/tools/generators/star-dev-deps/schema.ts b/tools/generators/star-dev-deps/schema.ts new file mode 100644 index 00000000000000..014c504d2054ad --- /dev/null +++ b/tools/generators/star-dev-deps/schema.ts @@ -0,0 +1 @@ +export interface StarDevDepsGeneratorSchema {} From 6f8a298dae499e3e839134116f0019ee12826257 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 15 Sep 2021 12:08:16 +0200 Subject: [PATCH 2/8] improve logs --- tools/generators/star-dev-deps/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/generators/star-dev-deps/index.ts b/tools/generators/star-dev-deps/index.ts index 63feadd653a51f..917164af57cf77 100644 --- a/tools/generators/star-dev-deps/index.ts +++ b/tools/generators/star-dev-deps/index.ts @@ -24,7 +24,7 @@ function runMigrationOnProject(host: Tree, packageName: string, userLog: UserLog if (isPackageInMonorepo(dependency, host) && packageJson.devDependencies) { userLog.push({ type: 'info', - message: `Updating package ${packageName}`, + message: `Updating dev dependency ${packageName} version`, }); packageJson.devDependencies[dependency] = '*'; } @@ -51,7 +51,7 @@ function isPackageInMonorepo(packageName: string, host: Tree) { try { config = getProjectConfig(host, { packageName }); } catch (err) { - if (!(err as Error).message.startsWith('Error: Cannot find configuration for')) { + if (!(err as Error).message.startsWith('Cannot find configuration for')) { throw err; } From 2e933d8679dcaef46719b3f49a52785cc88edf34 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 15 Sep 2021 12:08:55 +0200 Subject: [PATCH 3/8] improve logging --- tools/generators/star-dev-deps/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/generators/star-dev-deps/index.ts b/tools/generators/star-dev-deps/index.ts index 917164af57cf77..1d777958ca4acd 100644 --- a/tools/generators/star-dev-deps/index.ts +++ b/tools/generators/star-dev-deps/index.ts @@ -24,7 +24,7 @@ function runMigrationOnProject(host: Tree, packageName: string, userLog: UserLog if (isPackageInMonorepo(dependency, host) && packageJson.devDependencies) { userLog.push({ type: 'info', - message: `Updating dev dependency ${packageName} version`, + message: `Updating dev dependency ${dependency} in ${packageName}`, }); packageJson.devDependencies[dependency] = '*'; } From d3beca9148af6031ca44cd5ddfc805f81eeb6072 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 15 Sep 2021 17:31:57 +0200 Subject: [PATCH 4/8] update version group --- tools/generators/star-dev-deps/index.spec.ts | 158 ++++++++++++++++--- tools/generators/star-dev-deps/index.ts | 92 +++++++++-- 2 files changed, 219 insertions(+), 31 deletions(-) diff --git a/tools/generators/star-dev-deps/index.spec.ts b/tools/generators/star-dev-deps/index.spec.ts index 098092b210ad49..fb77124c63bae2 100644 --- a/tools/generators/star-dev-deps/index.spec.ts +++ b/tools/generators/star-dev-deps/index.spec.ts @@ -3,17 +3,16 @@ import { Tree, readProjectConfiguration, addProjectConfiguration, + removeProjectConfiguration, serializeJson, readWorkspaceConfiguration, readJson, } from '@nrwl/devkit'; import generator from './index'; -import { StarDevDepsGeneratorSchema } from './schema'; describe('star-dev-deps generator', () => { let tree: Tree; - const options: StarDevDepsGeneratorSchema = { name: 'test' }; beforeEach(() => { tree = createTreeWithEmptyWorkspace(); @@ -21,6 +20,15 @@ describe('star-dev-deps generator', () => { packageName: '@proj/eslint-plugin', version: '1.0.0', }); + + tree.write( + 'package.json', + serializeJson({ + syncpack: { + versionGroups: [], + }, + }), + ); }); it('should change internal package devdeps to * version', async () => { @@ -28,6 +36,7 @@ describe('star-dev-deps generator', () => { setupDummyPackage(tree, { packageName: '@proj/react-button', + version: '9.0.0', devDependencies: { [devPkgName]: '1.0.0' }, }); @@ -36,49 +45,54 @@ describe('star-dev-deps generator', () => { expect(pkgJson.devDependencies[devPkgName]).toBe('*'); }); - it('should run for all packages in the monorepo', async () => { + it('should not run for packages not vNext', async () => { const devPkgName = '@proj/eslint-plugin'; + const expectedVersion = '1.0.0'; setupDummyPackage(tree, { - packageName: '@proj/react-button', + packageName: '@proj/react', + version: '1.0.0', devDependencies: { [devPkgName]: '1.0.0' }, }); setupDummyPackage(tree, { - packageName: '@proj/react-avatar', + packageName: '@proj/react-charting', + version: '1.0.0', devDependencies: { [devPkgName]: '1.0.0' }, }); setupDummyPackage(tree, { - packageName: '@proj/react-menu', + packageName: '@proj/react-focus', + version: '1.0.0', devDependencies: { [devPkgName]: '1.0.0' }, }); await generator(tree); - ['react-button', 'react-avatar', 'react-menu'].forEach(name => { + ['react', 'react-charting', 'react-focus'].forEach(name => { const path = `packages/${name}/package.json`; const pkgJson = readJson(tree, path); - expect(pkgJson.devDependencies[devPkgName]).toBe('*'); + expect(pkgJson.devDependencies[devPkgName]).toBe(expectedVersion); }); }); - it('should also modify non-v9 packages', async () => { + it('should only modify dev dependencies', async () => { const devPkgName = '@proj/eslint-plugin'; + const expectedVersion = '1.0.0'; setupDummyPackage(tree, { packageName: '@proj/react-button', - version: '1.0.0', - devDependencies: { [devPkgName]: '1.0.0' }, + version: '9.0.0', + dependencies: { [devPkgName]: expectedVersion }, }); await generator(tree); const pkgJson = readJson(tree, 'packages/react-button/package.json'); - expect(pkgJson.devDependencies[devPkgName]).toBe('*'); + expect(pkgJson.dependencies[devPkgName]).toBe(expectedVersion); }); - it('should only modify dev dependencies', async () => { - const devPkgName = '@proj/eslint-plugin'; + it('should not modify external dev dependencies', async () => { + const devPkgName = 'jest'; const expectedVersion = '1.0.0'; setupDummyPackage(tree, { @@ -92,19 +106,110 @@ describe('star-dev-deps generator', () => { expect(pkgJson.dependencies[devPkgName]).toBe(expectedVersion); }); - it('should not modify external dev dependencies', async () => { - const devPkgName = 'jest'; - const expectedVersion = '1.0.0'; + it('should create new version group in base package.json', async () => { + const devPkgName = '@proj/eslint-plugin'; + setupDummyPackage(tree, { + packageName: '@proj/react-button', + version: '9.0.0', + devDependencies: { [devPkgName]: '1.0.0' }, + }); + await generator(tree); + + const pkgJson = readJson(tree, 'package.json'); + expect(pkgJson.syncpack).toMatchInlineSnapshot(` + Object { + "versionGroups": Array [ + Object { + "dependencies": Array [ + "@proj/eslint-plugin", + ], + "packages": Array [ + "@proj/react-button", + "fluent-ui-vnext", + ], + }, + ], + } + `); + }); + + it('should update a a new package to the existing version group in base package.json', async () => { + const devPkgName = '@proj/eslint-plugin'; setupDummyPackage(tree, { packageName: '@proj/react-button', version: '9.0.0', - dependencies: { [devPkgName]: expectedVersion }, + devDependencies: { [devPkgName]: '1.0.0' }, }); await generator(tree); - const pkgJson = readJson(tree, 'packages/react-button/package.json'); - expect(pkgJson.dependencies[devPkgName]).toBe(expectedVersion); + + setupDummyPackage(tree, { + packageName: '@proj/react-menu', + version: '9.0.0', + devDependencies: { [devPkgName]: '1.0.0' }, + }); + + await generator(tree); + + const pkgJson = readJson(tree, 'package.json'); + expect(pkgJson.syncpack).toMatchInlineSnapshot(` + Object { + "versionGroups": Array [ + Object { + "dependencies": Array [ + "@proj/eslint-plugin", + ], + "packages": Array [ + "@proj/react-button", + "@proj/react-menu", + "fluent-ui-vnext", + ], + }, + ], + } + `); + }); + + it('should add a new dependency to the version group', async () => { + const devPkgName = '@proj/eslint-plugin'; + const newDevPkgName = '@proj/react-conformance'; + setupDummyPackage(tree, { + packageName: '@proj/react-button', + version: '9.0.0', + devDependencies: { [devPkgName]: '1.0.0' }, + }); + + await generator(tree); + removeDummyPackage(tree, '@proj/react-button'); + setupDummyPackage(tree, { + packageName: newDevPkgName, + version: '1.0.0', + }); + setupDummyPackage(tree, { + packageName: '@proj/react-button', + version: '9.0.0', + devDependencies: { [devPkgName]: '1.0.0', [newDevPkgName]: '1.0.0' }, + }); + await generator(tree); + + const pkgJson = readJson(tree, 'package.json'); + expect(pkgJson.syncpack).toMatchInlineSnapshot(` + Object { + "versionGroups": Array [ + Object { + "dependencies": Array [ + "@proj/eslint-plugin", + "@proj/react-conformance", + ], + "packages": Array [ + "@proj/react-button", + "fluent-ui-vnext", + ], + }, + ], + } + `); }); }); @@ -158,3 +263,16 @@ function setupDummyPackage( return tree; } + +function removeDummyPackage(tree: Tree, packageName: string) { + const workspaceConfig = readWorkspaceConfiguration(tree); + const normalizedPkgName = packageName.replace(`@${workspaceConfig.npmScope}/`, ''); + const paths = { + root: `packages/${normalizedPkgName}`, + }; + + tree.delete(paths.root); + + removeProjectConfiguration(tree, packageName); + return tree; +} diff --git a/tools/generators/star-dev-deps/index.ts b/tools/generators/star-dev-deps/index.ts index 1d777958ca4acd..7cc2a49f8e3ecd 100644 --- a/tools/generators/star-dev-deps/index.ts +++ b/tools/generators/star-dev-deps/index.ts @@ -1,11 +1,14 @@ -import { Tree, updateJson, getProjects, formatFiles, readWorkspaceConfiguration, readJson } from '@nrwl/devkit'; +import { Tree, updateJson, getProjects, formatFiles, readJson, addDependenciesToPackageJson } from '@nrwl/devkit'; import { getProjectConfig, printUserLogs, UserLog } from '../../utils'; import { PackageJson } from '../../types'; +const dependenciesUpdated = new Set(); + export default async function (host: Tree) { const userLog: UserLog = []; - runMigration(host, userLog); + const { versionGroupDependencies, versionGroupPackages } = runMigration(host, userLog); + updateVersionGroups(host, versionGroupPackages, versionGroupDependencies); formatFiles(host); @@ -14,39 +17,105 @@ export default async function (host: Tree) { }; } -function runMigrationOnProject(host: Tree, packageName: string, userLog: UserLog) { +type SyncPack = { + syncpack: { + versionGroups: { + packages: string[]; + dependencies: string[]; + }[]; + }; +}; + +function updateVersionGroups(host: Tree, versionGroupPackages: string[], versionGroupDependencies: string[]) { + const vNextVersionGroup = 'fluent-ui-vnext'; + updateJson(host, 'package.json', (packageJson: PackageJson & SyncPack) => { + const existingVersionGroup = Object.values(packageJson.syncpack.versionGroups).find(versionGroup => { + if (versionGroup.packages.includes(vNextVersionGroup)) { + return true; + } + + return false; + }); + + if (existingVersionGroup) { + existingVersionGroup.dependencies = Array.from(versionGroupDependencies); + existingVersionGroup.packages = [...Array.from(versionGroupPackages), vNextVersionGroup]; + } else { + packageJson.syncpack.versionGroups.push({ + dependencies: Array.from(versionGroupDependencies), + packages: [...Array.from(versionGroupPackages), vNextVersionGroup], + }); + } + + return packageJson; + }); +} + +function runMigrationOnProject( + host: Tree, + packageName: string, + versionGroupDependencies: Set, + userLog: UserLog, +) { const projectConfig = getProjectConfig(host, { packageName }); const packageJsonPath = projectConfig.paths.packageJson; updateJson(host, packageJsonPath, (packageJson: PackageJson) => { if (packageJson.devDependencies) { Object.keys(packageJson.devDependencies).forEach(dependency => { - if (isPackageInMonorepo(dependency, host) && packageJson.devDependencies) { + if (shouldUseWildcardVersion(dependency, host) && packageJson.devDependencies) { userLog.push({ type: 'info', - message: `Updating dev dependency ${dependency} in ${packageName}`, + message: `Updating dependency ${dependency} in package ${packageName}`, }); packageJson.devDependencies[dependency] = '*'; + dependenciesUpdated.add(dependency); + versionGroupDependencies.add(dependency); } }); } return packageJson; }); + + return Array.from(versionGroupDependencies); } function runMigration(host: Tree, userLog: UserLog) { const projects = getProjects(host); + const versionGroupPackages = new Set(); + const versionGroupDependencies = new Set(); projects.forEach((project, projectName) => { - runMigrationOnProject(host, projectName, userLog); + if (isPackageConverged(projectName, host)) { + versionGroupPackages.add(projectName); + runMigrationOnProject(host, projectName, versionGroupDependencies, userLog); + } }); + + return { + versionGroupDependencies: Array.from(versionGroupDependencies), + versionGroupPackages: Array.from(versionGroupPackages), + }; +} + +function shouldUseWildcardVersion(packageName: string, host: Tree) { + let config: ReturnType; + try { + config = getProjectConfig(host, { packageName }); + } catch (err) { + if (!(err as Error).message.startsWith('Cannot find configuration for')) { + throw err; + } + + return false; + } + + const pkgJson: PackageJson = readJson(host, config.paths.packageJson); + return !pkgJson.private && !pkgJson.version.startsWith('9.'); } -/** - * @returns whether the packageName is internally in the monorepo - */ -function isPackageInMonorepo(packageName: string, host: Tree) { +function isPackageConverged(packageName: string, host: Tree) { let config: ReturnType; try { config = getProjectConfig(host, { packageName }); @@ -58,5 +127,6 @@ function isPackageInMonorepo(packageName: string, host: Tree) { return false; } - return true; + const packageJson = readJson(host, config.paths.packageJson); + return packageJson.version.startsWith('9.'); } From b92ef3bddb43d0dfcb955e88f12b6e8eb19c256f Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Wed, 15 Sep 2021 19:01:37 +0200 Subject: [PATCH 5/8] increase readability --- tools/generators/star-dev-deps/README.md | 3 ++- tools/generators/star-dev-deps/index.ts | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tools/generators/star-dev-deps/README.md b/tools/generators/star-dev-deps/README.md index 6be734869aadd5..797208d00af1cb 100644 --- a/tools/generators/star-dev-deps/README.md +++ b/tools/generators/star-dev-deps/README.md @@ -15,7 +15,8 @@ version. - Only affects dev dependencies that are also published from the monorepo - Does not affect third party dependencies -- Will run for every package in the monorepo +- Will run for only v9 packages in the monorepo +- Will create or update the syncpack version group ## Usage diff --git a/tools/generators/star-dev-deps/index.ts b/tools/generators/star-dev-deps/index.ts index 7cc2a49f8e3ecd..50f907b11653cf 100644 --- a/tools/generators/star-dev-deps/index.ts +++ b/tools/generators/star-dev-deps/index.ts @@ -112,7 +112,7 @@ function shouldUseWildcardVersion(packageName: string, host: Tree) { } const pkgJson: PackageJson = readJson(host, config.paths.packageJson); - return !pkgJson.private && !pkgJson.version.startsWith('9.'); + return !pkgJson.private && !isVersionConverged(pkgJson.version); } function isPackageConverged(packageName: string, host: Tree) { @@ -128,5 +128,9 @@ function isPackageConverged(packageName: string, host: Tree) { } const packageJson = readJson(host, config.paths.packageJson); - return packageJson.version.startsWith('9.'); + return isVersionConverged(packageJson.version); +} + +function isVersionConverged(version: string) { + return version.startsWith('9.'); } From 3f87685cadaf701a1ee634dc9bcb4e7736680c59 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 16 Sep 2021 21:35:38 +0200 Subject: [PATCH 6/8] run on all packages for limited deps --- tools/generators/star-dev-deps/index.spec.ts | 158 +++---------------- tools/generators/star-dev-deps/index.ts | 104 +++--------- 2 files changed, 40 insertions(+), 222 deletions(-) diff --git a/tools/generators/star-dev-deps/index.spec.ts b/tools/generators/star-dev-deps/index.spec.ts index fb77124c63bae2..098092b210ad49 100644 --- a/tools/generators/star-dev-deps/index.spec.ts +++ b/tools/generators/star-dev-deps/index.spec.ts @@ -3,16 +3,17 @@ import { Tree, readProjectConfiguration, addProjectConfiguration, - removeProjectConfiguration, serializeJson, readWorkspaceConfiguration, readJson, } from '@nrwl/devkit'; import generator from './index'; +import { StarDevDepsGeneratorSchema } from './schema'; describe('star-dev-deps generator', () => { let tree: Tree; + const options: StarDevDepsGeneratorSchema = { name: 'test' }; beforeEach(() => { tree = createTreeWithEmptyWorkspace(); @@ -20,15 +21,6 @@ describe('star-dev-deps generator', () => { packageName: '@proj/eslint-plugin', version: '1.0.0', }); - - tree.write( - 'package.json', - serializeJson({ - syncpack: { - versionGroups: [], - }, - }), - ); }); it('should change internal package devdeps to * version', async () => { @@ -36,7 +28,6 @@ describe('star-dev-deps generator', () => { setupDummyPackage(tree, { packageName: '@proj/react-button', - version: '9.0.0', devDependencies: { [devPkgName]: '1.0.0' }, }); @@ -45,54 +36,49 @@ describe('star-dev-deps generator', () => { expect(pkgJson.devDependencies[devPkgName]).toBe('*'); }); - it('should not run for packages not vNext', async () => { + it('should run for all packages in the monorepo', async () => { const devPkgName = '@proj/eslint-plugin'; - const expectedVersion = '1.0.0'; setupDummyPackage(tree, { - packageName: '@proj/react', - version: '1.0.0', + packageName: '@proj/react-button', devDependencies: { [devPkgName]: '1.0.0' }, }); setupDummyPackage(tree, { - packageName: '@proj/react-charting', - version: '1.0.0', + packageName: '@proj/react-avatar', devDependencies: { [devPkgName]: '1.0.0' }, }); setupDummyPackage(tree, { - packageName: '@proj/react-focus', - version: '1.0.0', + packageName: '@proj/react-menu', devDependencies: { [devPkgName]: '1.0.0' }, }); await generator(tree); - ['react', 'react-charting', 'react-focus'].forEach(name => { + ['react-button', 'react-avatar', 'react-menu'].forEach(name => { const path = `packages/${name}/package.json`; const pkgJson = readJson(tree, path); - expect(pkgJson.devDependencies[devPkgName]).toBe(expectedVersion); + expect(pkgJson.devDependencies[devPkgName]).toBe('*'); }); }); - it('should only modify dev dependencies', async () => { + it('should also modify non-v9 packages', async () => { const devPkgName = '@proj/eslint-plugin'; - const expectedVersion = '1.0.0'; setupDummyPackage(tree, { packageName: '@proj/react-button', - version: '9.0.0', - dependencies: { [devPkgName]: expectedVersion }, + version: '1.0.0', + devDependencies: { [devPkgName]: '1.0.0' }, }); await generator(tree); const pkgJson = readJson(tree, 'packages/react-button/package.json'); - expect(pkgJson.dependencies[devPkgName]).toBe(expectedVersion); + expect(pkgJson.devDependencies[devPkgName]).toBe('*'); }); - it('should not modify external dev dependencies', async () => { - const devPkgName = 'jest'; + it('should only modify dev dependencies', async () => { + const devPkgName = '@proj/eslint-plugin'; const expectedVersion = '1.0.0'; setupDummyPackage(tree, { @@ -106,110 +92,19 @@ describe('star-dev-deps generator', () => { expect(pkgJson.dependencies[devPkgName]).toBe(expectedVersion); }); - it('should create new version group in base package.json', async () => { - const devPkgName = '@proj/eslint-plugin'; - setupDummyPackage(tree, { - packageName: '@proj/react-button', - version: '9.0.0', - devDependencies: { [devPkgName]: '1.0.0' }, - }); - - await generator(tree); - - const pkgJson = readJson(tree, 'package.json'); - expect(pkgJson.syncpack).toMatchInlineSnapshot(` - Object { - "versionGroups": Array [ - Object { - "dependencies": Array [ - "@proj/eslint-plugin", - ], - "packages": Array [ - "@proj/react-button", - "fluent-ui-vnext", - ], - }, - ], - } - `); - }); - - it('should update a a new package to the existing version group in base package.json', async () => { - const devPkgName = '@proj/eslint-plugin'; - setupDummyPackage(tree, { - packageName: '@proj/react-button', - version: '9.0.0', - devDependencies: { [devPkgName]: '1.0.0' }, - }); - - await generator(tree); - - setupDummyPackage(tree, { - packageName: '@proj/react-menu', - version: '9.0.0', - devDependencies: { [devPkgName]: '1.0.0' }, - }); - - await generator(tree); - - const pkgJson = readJson(tree, 'package.json'); - expect(pkgJson.syncpack).toMatchInlineSnapshot(` - Object { - "versionGroups": Array [ - Object { - "dependencies": Array [ - "@proj/eslint-plugin", - ], - "packages": Array [ - "@proj/react-button", - "@proj/react-menu", - "fluent-ui-vnext", - ], - }, - ], - } - `); - }); + it('should not modify external dev dependencies', async () => { + const devPkgName = 'jest'; + const expectedVersion = '1.0.0'; - it('should add a new dependency to the version group', async () => { - const devPkgName = '@proj/eslint-plugin'; - const newDevPkgName = '@proj/react-conformance'; setupDummyPackage(tree, { packageName: '@proj/react-button', version: '9.0.0', - devDependencies: { [devPkgName]: '1.0.0' }, + dependencies: { [devPkgName]: expectedVersion }, }); await generator(tree); - removeDummyPackage(tree, '@proj/react-button'); - setupDummyPackage(tree, { - packageName: newDevPkgName, - version: '1.0.0', - }); - setupDummyPackage(tree, { - packageName: '@proj/react-button', - version: '9.0.0', - devDependencies: { [devPkgName]: '1.0.0', [newDevPkgName]: '1.0.0' }, - }); - await generator(tree); - - const pkgJson = readJson(tree, 'package.json'); - expect(pkgJson.syncpack).toMatchInlineSnapshot(` - Object { - "versionGroups": Array [ - Object { - "dependencies": Array [ - "@proj/eslint-plugin", - "@proj/react-conformance", - ], - "packages": Array [ - "@proj/react-button", - "fluent-ui-vnext", - ], - }, - ], - } - `); + const pkgJson = readJson(tree, 'packages/react-button/package.json'); + expect(pkgJson.dependencies[devPkgName]).toBe(expectedVersion); }); }); @@ -263,16 +158,3 @@ function setupDummyPackage( return tree; } - -function removeDummyPackage(tree: Tree, packageName: string) { - const workspaceConfig = readWorkspaceConfiguration(tree); - const normalizedPkgName = packageName.replace(`@${workspaceConfig.npmScope}/`, ''); - const paths = { - root: `packages/${normalizedPkgName}`, - }; - - tree.delete(paths.root); - - removeProjectConfiguration(tree, packageName); - return tree; -} diff --git a/tools/generators/star-dev-deps/index.ts b/tools/generators/star-dev-deps/index.ts index 50f907b11653cf..71462ed06fdc6f 100644 --- a/tools/generators/star-dev-deps/index.ts +++ b/tools/generators/star-dev-deps/index.ts @@ -1,14 +1,11 @@ -import { Tree, updateJson, getProjects, formatFiles, readJson, addDependenciesToPackageJson } from '@nrwl/devkit'; +import { Tree, updateJson, getProjects, formatFiles, readWorkspaceConfiguration, readJson } from '@nrwl/devkit'; import { getProjectConfig, printUserLogs, UserLog } from '../../utils'; import { PackageJson } from '../../types'; -const dependenciesUpdated = new Set(); - export default async function (host: Tree) { const userLog: UserLog = []; - const { versionGroupDependencies, versionGroupPackages } = runMigration(host, userLog); - updateVersionGroups(host, versionGroupPackages, versionGroupDependencies); + runMigration(host, userLog); formatFiles(host); @@ -17,89 +14,49 @@ export default async function (host: Tree) { }; } -type SyncPack = { - syncpack: { - versionGroups: { - packages: string[]; - dependencies: string[]; - }[]; - }; -}; - -function updateVersionGroups(host: Tree, versionGroupPackages: string[], versionGroupDependencies: string[]) { - const vNextVersionGroup = 'fluent-ui-vnext'; - updateJson(host, 'package.json', (packageJson: PackageJson & SyncPack) => { - const existingVersionGroup = Object.values(packageJson.syncpack.versionGroups).find(versionGroup => { - if (versionGroup.packages.includes(vNextVersionGroup)) { - return true; - } - - return false; - }); +const dependenciesToChange = [ + '@fluentui/eslint-plugin', + '@fluentui/react-conformance', + '@fluentui/react-conformance-make-styles', +]; - if (existingVersionGroup) { - existingVersionGroup.dependencies = Array.from(versionGroupDependencies); - existingVersionGroup.packages = [...Array.from(versionGroupPackages), vNextVersionGroup]; - } else { - packageJson.syncpack.versionGroups.push({ - dependencies: Array.from(versionGroupDependencies), - packages: [...Array.from(versionGroupPackages), vNextVersionGroup], - }); - } - - return packageJson; - }); -} - -function runMigrationOnProject( - host: Tree, - packageName: string, - versionGroupDependencies: Set, - userLog: UserLog, -) { +function runMigrationOnProject(host: Tree, packageName: string, userLog: UserLog) { const projectConfig = getProjectConfig(host, { packageName }); const packageJsonPath = projectConfig.paths.packageJson; updateJson(host, packageJsonPath, (packageJson: PackageJson) => { if (packageJson.devDependencies) { Object.keys(packageJson.devDependencies).forEach(dependency => { - if (shouldUseWildcardVersion(dependency, host) && packageJson.devDependencies) { + if ( + isPackageInMonorepo(dependency, host) && + packageJson.devDependencies && + dependenciesToChange.includes(dependency) + ) { userLog.push({ type: 'info', - message: `Updating dependency ${dependency} in package ${packageName}`, + message: `Updating dev dependency ${dependency} in ${packageName}`, }); packageJson.devDependencies[dependency] = '*'; - dependenciesUpdated.add(dependency); - versionGroupDependencies.add(dependency); } }); } return packageJson; }); - - return Array.from(versionGroupDependencies); } function runMigration(host: Tree, userLog: UserLog) { const projects = getProjects(host); - const versionGroupPackages = new Set(); - const versionGroupDependencies = new Set(); projects.forEach((project, projectName) => { - if (isPackageConverged(projectName, host)) { - versionGroupPackages.add(projectName); - runMigrationOnProject(host, projectName, versionGroupDependencies, userLog); - } + runMigrationOnProject(host, projectName, userLog); }); - - return { - versionGroupDependencies: Array.from(versionGroupDependencies), - versionGroupPackages: Array.from(versionGroupPackages), - }; } -function shouldUseWildcardVersion(packageName: string, host: Tree) { +/** + * @returns whether the packageName is internally in the monorepo + */ +function isPackageInMonorepo(packageName: string, host: Tree) { let config: ReturnType; try { config = getProjectConfig(host, { packageName }); @@ -111,26 +68,5 @@ function shouldUseWildcardVersion(packageName: string, host: Tree) { return false; } - const pkgJson: PackageJson = readJson(host, config.paths.packageJson); - return !pkgJson.private && !isVersionConverged(pkgJson.version); -} - -function isPackageConverged(packageName: string, host: Tree) { - let config: ReturnType; - try { - config = getProjectConfig(host, { packageName }); - } catch (err) { - if (!(err as Error).message.startsWith('Cannot find configuration for')) { - throw err; - } - - return false; - } - - const packageJson = readJson(host, config.paths.packageJson); - return isVersionConverged(packageJson.version); -} - -function isVersionConverged(version: string) { - return version.startsWith('9.'); + return true; } From 930eb451cbe1adb02477f68794a3e39cacc54b2b Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 16 Sep 2021 21:45:47 +0200 Subject: [PATCH 7/8] make configurabl;e --- tools/generators/star-dev-deps/README.md | 8 +++++--- tools/generators/star-dev-deps/config.ts | 5 +++++ tools/generators/star-dev-deps/index.ts | 9 ++------- 3 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 tools/generators/star-dev-deps/config.ts diff --git a/tools/generators/star-dev-deps/README.md b/tools/generators/star-dev-deps/README.md index 797208d00af1cb..dadc74515f9dec 100644 --- a/tools/generators/star-dev-deps/README.md +++ b/tools/generators/star-dev-deps/README.md @@ -1,8 +1,10 @@ # star-dev-deps -For all packages in the monorepo, replace dev dependencies on other internal packages in the monorepo with the `*` +For all packages in the monorepo, replace certain dev dependencies on other internal packages in the monorepo with the `*` version. +The dependencies to be updated can be configured in `config.ts` + - [NOTES](#notes) @@ -15,8 +17,8 @@ version. - Only affects dev dependencies that are also published from the monorepo - Does not affect third party dependencies -- Will run for only v9 packages in the monorepo -- Will create or update the syncpack version group +- Will run for every package in the monorepo +- Only updates dependencies listed in `config.ts` ## Usage diff --git a/tools/generators/star-dev-deps/config.ts b/tools/generators/star-dev-deps/config.ts new file mode 100644 index 00000000000000..4c384090c9103f --- /dev/null +++ b/tools/generators/star-dev-deps/config.ts @@ -0,0 +1,5 @@ +export const dependenciesToUpdate = [ + '@fluentui/eslint-plugin', + '@fluentui/react-conformance', + '@fluentui/react-conformance-make-styles', +]; diff --git a/tools/generators/star-dev-deps/index.ts b/tools/generators/star-dev-deps/index.ts index 71462ed06fdc6f..bb106d8528fdbf 100644 --- a/tools/generators/star-dev-deps/index.ts +++ b/tools/generators/star-dev-deps/index.ts @@ -1,6 +1,7 @@ import { Tree, updateJson, getProjects, formatFiles, readWorkspaceConfiguration, readJson } from '@nrwl/devkit'; import { getProjectConfig, printUserLogs, UserLog } from '../../utils'; import { PackageJson } from '../../types'; +import { dependenciesToUpdate } from './config'; export default async function (host: Tree) { const userLog: UserLog = []; @@ -14,12 +15,6 @@ export default async function (host: Tree) { }; } -const dependenciesToChange = [ - '@fluentui/eslint-plugin', - '@fluentui/react-conformance', - '@fluentui/react-conformance-make-styles', -]; - function runMigrationOnProject(host: Tree, packageName: string, userLog: UserLog) { const projectConfig = getProjectConfig(host, { packageName }); const packageJsonPath = projectConfig.paths.packageJson; @@ -30,7 +25,7 @@ function runMigrationOnProject(host: Tree, packageName: string, userLog: UserLog if ( isPackageInMonorepo(dependency, host) && packageJson.devDependencies && - dependenciesToChange.includes(dependency) + dependenciesToUpdate.includes(dependency) ) { userLog.push({ type: 'info', From 5958d4b875ba0482999ffe534d160b0a91845da4 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Thu, 16 Sep 2021 22:16:10 +0200 Subject: [PATCH 8/8] remove test-utilites --- tools/generators/star-dev-deps/config.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tools/generators/star-dev-deps/config.ts b/tools/generators/star-dev-deps/config.ts index 4c384090c9103f..70ffd3e5ad8b70 100644 --- a/tools/generators/star-dev-deps/config.ts +++ b/tools/generators/star-dev-deps/config.ts @@ -1,5 +1 @@ -export const dependenciesToUpdate = [ - '@fluentui/eslint-plugin', - '@fluentui/react-conformance', - '@fluentui/react-conformance-make-styles', -]; +export const dependenciesToUpdate = ['@fluentui/eslint-plugin', '@fluentui/react-conformance'];