Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions tools/generators/star-dev-deps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# star-dev-deps

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`

<!-- toc -->

- [NOTES](#notes)
- [Usage](#usage)
- [Examples](#examples)

<!-- tocstop -->

## 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
- Only updates dependencies listed in `config.ts`

## 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
```
1 change: 1 addition & 0 deletions tools/generators/star-dev-deps/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const dependenciesToUpdate = ['@fluentui/eslint-plugin', '@fluentui/react-conformance'];
160 changes: 160 additions & 0 deletions tools/generators/star-dev-deps/index.spec.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Comment thread
theerebuss marked this conversation as resolved.
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<string, string>;
dependencies: Record<string, string>;
projectConfiguration: Partial<ReturnType<typeof readProjectConfiguration>>;
}>,
) {
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;
}
67 changes: 67 additions & 0 deletions tools/generators/star-dev-deps/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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 = [];

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 &&
dependenciesToUpdate.includes(dependency)
) {
userLog.push({
type: 'info',
message: `Updating dev dependency ${dependency} in ${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<typeof getProjectConfig>;
try {
config = getProjectConfig(host, { packageName });
} catch (err) {
if (!(err as Error).message.startsWith('Cannot find configuration for')) {
throw err;
}

return false;
}

return true;
}
8 changes: 8 additions & 0 deletions tools/generators/star-dev-deps/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "http://json-schema.org/schema",
"cli": "nx",
"id": "star-dev-deps",
"type": "object",
"properties": {},
"required": []
}
1 change: 1 addition & 0 deletions tools/generators/star-dev-deps/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export interface StarDevDepsGeneratorSchema {}