Skip to content
Closed
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
47 changes: 47 additions & 0 deletions tools/generators/migrate-prerelease-pkg-to-preview/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# migrate-prerelease-pkg-to-preview

Workspace Generator to migrate v9 pre-release packages to new [preview versioning scheme](https://github.com/microsoft/fluentui/issues/28471).

<!-- toc -->

- [Usage](#usage)
- [Examples](#examples)
- [Options](#options)
- [`project`](#project)
- [`force`](#force)

<!-- tocstop -->

## Usage

```sh
yarn nx workspace-generator migrate-prerelease-pkg-to-preview ...
```

Show what will be generated without writing to disk:

```sh
yarn nx workspace-generator migrate-prerelease-pkg-to-preview --dry-run
```

### Examples

```sh
yarn nx workspace-generator migrate-prerelease-pkg-to-preview
```

## Options

#### `project`

Type: `string`

Library name

#### `force`

> Optional [default:false]

Type: `boolean`

When `true`, forces migration operations even if the project is still in use.
278 changes: 278 additions & 0 deletions tools/generators/migrate-prerelease-pkg-to-preview/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import {
Tree,
writeJson,
joinPathFragments,
addProjectConfiguration,
stripIndents,
readJson,
ProjectGraph,
} from '@nrwl/devkit';

import generator from './index';

const blankGraphMock = {
dependencies: {},
nodes: {},
externalNodes: {},
};

let graphMock: ProjectGraph;

jest.mock('@nrwl/devkit', () => {
async function createProjectGraphAsyncMock(): Promise<ProjectGraph> {
return graphMock;
}

return {
...jest.requireActual('@nrwl/devkit'),
createProjectGraphAsync: createProjectGraphAsyncMock,
};
});

describe('migrate-prerelease-pkg-to-preview generator', () => {
let tree: Tree;

beforeEach(() => {
graphMock = {
...blankGraphMock,
};
tree = createTreeWithEmptyWorkspace();
writeJson(tree, 'tsconfig.base.v0.json', { compilerOptions: { paths: {} } });
writeJson(tree, 'tsconfig.base.v8.json', { compilerOptions: { paths: {} } });
writeJson(tree, 'tsconfig.base.all.json', { compilerOptions: { paths: {} } });
});

it('should migrate package to preview package pattern', async () => {
createPackage(tree, 'react-foo');

await generator(tree, { project: '@proj/react-foo' });

const pkgJson = readJson(tree, 'packages/react-foo-preview/package.json');
const projectJson = readJson(tree, 'packages/react-foo-preview/project.json');
const jestConfig = tree.read('packages/react-foo-preview/jest.config.js', 'utf-8');
const readmeMd = tree.read('packages/react-foo-preview/README.md', 'utf-8');
const apiMd = tree.read('packages/react-foo-preview/etc/react-foo-preview.api.md', 'utf-8');
const storyFile = tree.read('packages/react-foo-preview/stories/index.stories.tsx', 'utf-8');
const workspaceTsConfig = {
v9: readJson(tree, 'tsconfig.base.json'),
all: readJson(tree, 'tsconfig.base.all.json'),
};

expect(pkgJson).toMatchInlineSnapshot(`
Object {
"beachball": Object {
"disallowedChangeTypes": Array [
"major",
"prerelease",
],
},
"name": "@proj/react-foo-preview",
"version": "0.1.0",
}
`);
expect(projectJson).toMatchInlineSnapshot(`
Object {
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"name": "@proj/react-foo-preview",
"sourceRoot": "packages/react-foo-preview/src",
"tags": Array [
"vNext",
],
}
`);
expect(jestConfig).toMatchInlineSnapshot(`
"module.exports = {
displayproject: 'react-foo-preview',
preset: '../../../jest.preset.js',
};
"
`);
expect(readmeMd).toMatchInlineSnapshot(`
"# @proj/react-foo-preview

**React Foo component for [Fluent UI React](https://react.fluentui.dev)**
"
`);
expect(apiMd).toMatchInlineSnapshot(`
"## API Report File for \\"@proj/react-foo-preview\\"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
"
`);
expect(storyFile).toMatchInlineSnapshot(`
"import { Foo } from '@proj/react-foo-preview';
import type { FooDef } from '@proj/react-foo-preview';

export default {
title: 'Preview Components/Foo',
component: Foo,
};
"
`);

expect(workspaceTsConfig.v9.compilerOptions.paths).toMatchInlineSnapshot(`
Object {
"@proj/react-foo-preview": Array [
"packages/react-foo-preview/src",
],
}
`);
expect(workspaceTsConfig.all.compilerOptions.paths).toMatchInlineSnapshot(`
Object {
"@proj/react-foo-preview": Array [
"packages/react-foo-preview/src",
],
}
`);
});

it(`should not execute migration and notify user about usage in other projects that need to be migrated manually`, async () => {
createPackage(tree, 'react-foo');
createPackage(tree, 'react-app', {
version: '1.0.0',
projectType: 'application',
dependencies: {
'@proj/react-foo': '9.0.0-beta.1',
},
files: [
{
filePath: 'apps/react-app/src/index.tsx',
content: stripIndents`
import { Foo } from '@proj/react-foo';

function App(){
return <div><Foo/></div>;
}
`,
},
],
});

await expect(generator(tree, { project: '@proj/react-foo' })).rejects.toMatchInlineSnapshot(`
[Error: @proj/react-foo is still depended on by the following projects:
@proj/react-app

!!! 👉 Migrate those projects to use '@proj/react-foo-preview' 👈 !!!]
`);
});

it(`should execute migration if package is used in other projects and --force flag is enabled`, async () => {
createPackage(tree, 'react-foo');
createPackage(tree, 'react-app', {
version: '1.0.0',
projectType: 'application',
dependencies: {
'@proj/react-foo': '9.0.0-beta.1',
},
files: [
{
filePath: 'apps/react-app/src/index.tsx',
content: stripIndents`
import { Foo } from '@proj/react-foo';

function App(){
return <div><Foo/></div>;
}
`,
},
],
});

try {
await generator(tree, { project: '@proj/react-foo', force: true });
} catch {
expect(true).toBe(false);
}
});
});

function createPackage(
tree: Tree,
projectName: string,
options: Partial<{
projectType: 'application' | 'library';
version: string;
dependencies: Record<string, string>;
files: [{ filePath: string; content: string }];
}> = {},
) {
const packageName = `@proj/${projectName}`;
const projectRootDir = options.projectType === 'application' ? 'apps' : 'packages';
const rootPath = `${projectRootDir}/${projectName}`;

const normalizedOptions = { ...options, version: '9.0.0-beta.1' };

writeJson(tree, joinPathFragments(rootPath, 'package.json'), {
name: packageName,
version: normalizedOptions.version,
beachball: {
disallowedChangeTypes: ['major', 'minor', 'patch'],
},
...(normalizedOptions.dependencies ? { dependencies: normalizedOptions.dependencies } : null),
});
addProjectConfiguration(tree, packageName, {
root: rootPath,
sourceRoot: joinPathFragments(rootPath, 'src'),
tags: ['vNext'],
});

tree.write(
joinPathFragments(rootPath, 'README.md'),
stripIndents`
# ${packageName}

**React Foo component for [Fluent UI React](https://react.fluentui.dev)**
`,
);
tree.write(
joinPathFragments(rootPath, `etc/${projectName}.api.md`),
stripIndents`
## API Report File for "${packageName}"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

`,
);

tree.write(
joinPathFragments(rootPath, `jest.config.js`),
stripIndents`
module.exports = {
displayproject: '${projectName}',
preset: '../../../jest.preset.js',
}`,
);

tree.write(
joinPathFragments(rootPath, 'stories', 'index.stories.tsx'),
stripIndents`
import { Foo } from '@proj/${projectName}';
import type { FooDef } from '@proj/${projectName}';

export default {
title: 'Preview Components/Foo',
component: Foo
};
`,
);

if (normalizedOptions.files) {
normalizedOptions.files.forEach(fileEntry => {
tree.write(fileEntry.filePath, fileEntry.content);
});
}

const depKeys = [...Object.keys(normalizedOptions.dependencies ?? {})];

graphMock.dependencies[packageName] = depKeys.map(value => {
return { source: packageName, target: value, type: 'static' };
});
graphMock.nodes[packageName] = {
name: packageName,
type: normalizedOptions.projectType === 'library' ? 'lib' : 'app',
data: { name: packageName, root: rootPath, files: [] },
};

return tree;
}
Loading