From 07ad187738ccc1ebe859182e9eb1e9f63f9bc325 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Wed, 12 Jul 2023 15:28:10 +0200 Subject: [PATCH 1/8] feat(tools): bootstrap release-package generator --- tools/generators/release-package/README.md | 38 ++++++++++++++ .../files/constants.ts__tmpl__ | 1 + .../generators/release-package/index.spec.ts | 20 ++++++++ tools/generators/release-package/index.ts | 50 +++++++++++++++++++ .../release-package/lib/utils.spec.ts | 7 +++ tools/generators/release-package/lib/utils.ts | 5 ++ tools/generators/release-package/schema.json | 17 +++++++ tools/generators/release-package/schema.ts | 6 +++ 8 files changed, 144 insertions(+) create mode 100644 tools/generators/release-package/README.md create mode 100644 tools/generators/release-package/files/constants.ts__tmpl__ create mode 100644 tools/generators/release-package/index.spec.ts create mode 100644 tools/generators/release-package/index.ts create mode 100644 tools/generators/release-package/lib/utils.spec.ts create mode 100644 tools/generators/release-package/lib/utils.ts create mode 100644 tools/generators/release-package/schema.json create mode 100644 tools/generators/release-package/schema.ts diff --git a/tools/generators/release-package/README.md b/tools/generators/release-package/README.md new file mode 100644 index 00000000000000..bd4a58260267ef --- /dev/null +++ b/tools/generators/release-package/README.md @@ -0,0 +1,38 @@ +# release-package + +Workspace Generator ...TODO... + + + +- [Usage](#usage) + - [Examples](#examples) +- [Options](#options) + - [`name`](#name) + + + +## Usage + +```sh +yarn nx workspace-generator release-package ... +``` + +Show what will be generated without writing to disk: + +```sh +yarn nx workspace-generator release-package --dry-run +``` + +### Examples + +```sh +yarn nx workspace-generator release-package +``` + +## Options + +#### `name` + +Type: `string` + +TODO... diff --git a/tools/generators/release-package/files/constants.ts__tmpl__ b/tools/generators/release-package/files/constants.ts__tmpl__ new file mode 100644 index 00000000000000..d9913111a9dc76 --- /dev/null +++ b/tools/generators/release-package/files/constants.ts__tmpl__ @@ -0,0 +1 @@ +export const variable = "<%= name %>"; \ No newline at end of file diff --git a/tools/generators/release-package/index.spec.ts b/tools/generators/release-package/index.spec.ts new file mode 100644 index 00000000000000..24504f7121e08a --- /dev/null +++ b/tools/generators/release-package/index.spec.ts @@ -0,0 +1,20 @@ +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { Tree, readProjectConfiguration } from '@nrwl/devkit'; + +import generator from './index'; +import { ReleasePackageGeneratorSchema } from './schema'; + +describe('release-package generator', () => { + let appTree: Tree; + const options: ReleasePackageGeneratorSchema = { name: 'test' }; + + beforeEach(() => { + appTree = createTreeWithEmptyWorkspace(); + }); + + it('should run successfully', async () => { + await generator(appTree, options); + const config = readProjectConfiguration(appTree, 'test'); + expect(config).toBeDefined(); + }); +}); diff --git a/tools/generators/release-package/index.ts b/tools/generators/release-package/index.ts new file mode 100644 index 00000000000000..09e7e280aabfce --- /dev/null +++ b/tools/generators/release-package/index.ts @@ -0,0 +1,50 @@ +import * as path from 'path'; +import { Tree, formatFiles, installPackagesTask, names, generateFiles } from '@nrwl/devkit'; +import { libraryGenerator } from '@nrwl/workspace/generators'; + +import { getProjectConfig } from '../../utils'; + +import { ReleasePackageGeneratorSchema } from './schema'; + +interface NormalizedSchema extends ReturnType {} + +export default async function (tree: Tree, schema: ReleasePackageGeneratorSchema) { + await libraryGenerator(tree, { name: schema.name }); + + const normalizedOptions = normalizeOptions(tree, schema); + + addFiles(tree, normalizedOptions); + + await formatFiles(tree); + + return () => { + installPackagesTask(tree); + }; +} + +function normalizeOptions(tree: Tree, options: ReleasePackageGeneratorSchema) { + const project = getProjectConfig(tree, { packageName: options.name }); + + return { + ...options, + ...project, + ...names(options.name), + }; +} + +/** + * NOTE: remove this if your generator doesn't process any static/dynamic templates + */ +function addFiles(tree: Tree, options: NormalizedSchema) { + const templateOptions = { + ...options, + tmpl: '', + }; + + generateFiles( + tree, + path.join(__dirname, 'files'), + path.join(options.projectConfig.root, options.name), + templateOptions, + ); +} diff --git a/tools/generators/release-package/lib/utils.spec.ts b/tools/generators/release-package/lib/utils.spec.ts new file mode 100644 index 00000000000000..91e05a54a86f53 --- /dev/null +++ b/tools/generators/release-package/lib/utils.spec.ts @@ -0,0 +1,7 @@ +import { dummyHelper } from './utils'; + +describe(`utils`, () => { + it(`should behave...`, () => { + expect(dummyHelper()).toBe(undefined); + }); +}); diff --git a/tools/generators/release-package/lib/utils.ts b/tools/generators/release-package/lib/utils.ts new file mode 100644 index 00000000000000..c340a27c29c8b0 --- /dev/null +++ b/tools/generators/release-package/lib/utils.ts @@ -0,0 +1,5 @@ +// use this module to define any kind of generic utilities that are used in more than 1 place within the generator implementation + +export function dummyHelper() { + return; +} diff --git a/tools/generators/release-package/schema.json b/tools/generators/release-package/schema.json new file mode 100644 index 00000000000000..31321329545d0a --- /dev/null +++ b/tools/generators/release-package/schema.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/schema", + "cli": "nx", + "id": "release-package", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Library name", + "$default": { + "$source": "argv", + "index": 0 + } + } + }, + "required": ["name"] +} diff --git a/tools/generators/release-package/schema.ts b/tools/generators/release-package/schema.ts new file mode 100644 index 00000000000000..8db964bfe2e628 --- /dev/null +++ b/tools/generators/release-package/schema.ts @@ -0,0 +1,6 @@ +export interface ReleasePackageGeneratorSchema { + /** + * Library name + */ + name: string; +} From 94d2e2cd8bc660a7481d0a8dc30dbee74e0d13c2 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Wed, 12 Jul 2023 17:27:47 +0200 Subject: [PATCH 2/8] feat(tools): implement release-package generator --- tools/generators/release-package/README.md | 15 +- .../files/constants.ts__tmpl__ | 1 - .../generators/release-package/index.spec.ts | 431 +++++++++++++++++- tools/generators/release-package/index.ts | 298 +++++++++++- tools/generators/release-package/lib/.gitkeep | 0 .../release-package/lib/utils.spec.ts | 7 - tools/generators/release-package/lib/utils.ts | 5 - tools/generators/release-package/schema.json | 24 +- tools/generators/release-package/schema.ts | 13 +- tools/utils.ts | 1 + 10 files changed, 745 insertions(+), 50 deletions(-) delete mode 100644 tools/generators/release-package/files/constants.ts__tmpl__ create mode 100644 tools/generators/release-package/lib/.gitkeep delete mode 100644 tools/generators/release-package/lib/utils.spec.ts delete mode 100644 tools/generators/release-package/lib/utils.ts diff --git a/tools/generators/release-package/README.md b/tools/generators/release-package/README.md index bd4a58260267ef..a6bec6b4d1d3e0 100644 --- a/tools/generators/release-package/README.md +++ b/tools/generators/release-package/README.md @@ -1,13 +1,14 @@ # release-package -Workspace Generator ...TODO... +Workspace Generator which automates release process stages for @fluentui (v9) package. - [Usage](#usage) - [Examples](#examples) - [Options](#options) - - [`name`](#name) + - [`project`](#project) + - [`phase`](#phase) @@ -31,8 +32,14 @@ yarn nx workspace-generator release-package ## Options -#### `name` +#### `project` Type: `string` -TODO... +Library name to to be released. + +#### `phase` + +Type: `initial` | 'stable' + +Phase of npm release life cycle for monorepo package diff --git a/tools/generators/release-package/files/constants.ts__tmpl__ b/tools/generators/release-package/files/constants.ts__tmpl__ deleted file mode 100644 index d9913111a9dc76..00000000000000 --- a/tools/generators/release-package/files/constants.ts__tmpl__ +++ /dev/null @@ -1 +0,0 @@ -export const variable = "<%= name %>"; \ No newline at end of file diff --git a/tools/generators/release-package/index.spec.ts b/tools/generators/release-package/index.spec.ts index 24504f7121e08a..a07e32e6d3390a 100644 --- a/tools/generators/release-package/index.spec.ts +++ b/tools/generators/release-package/index.spec.ts @@ -1,20 +1,433 @@ import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; -import { Tree, readProjectConfiguration } from '@nrwl/devkit'; +import { + Tree, + addProjectConfiguration, + writeJson, + joinPathFragments, + stripIndents, + readJson, + updateJson, + ProjectGraph, +} from '@nrwl/devkit'; +import * as devkit from '@nrwl/devkit'; +import * as childProcess from 'child_process'; import generator from './index'; -import { ReleasePackageGeneratorSchema } from './schema'; +import { PackageJson, TsConfig } from '../../types'; + +const blankGraphMock = { + dependencies: {}, + nodes: {}, + externalNodes: {}, +}; +let graphMock: ProjectGraph; +const codeownersPath = joinPathFragments('.github', 'CODEOWNERS'); + +jest.mock('@nrwl/devkit', () => { + async function createProjectGraphAsyncMock(): Promise { + return graphMock; + } + + return { + ...jest.requireActual('@nrwl/devkit'), + createProjectGraphAsync: createProjectGraphAsyncMock, + }; +}); + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const noop = () => {}; + +let execSyncSpy: jest.SpyInstance; +let installPackagesTaskSpy: jest.SpyInstance; describe('release-package generator', () => { - let appTree: Tree; - const options: ReleasePackageGeneratorSchema = { name: 'test' }; + let tree: Tree; beforeEach(() => { - appTree = createTreeWithEmptyWorkspace(); + execSyncSpy = jest.spyOn(childProcess, 'execSync').mockImplementation( + // @ts-expect-error - no need to mock whole execSync API + noop, + ); + installPackagesTaskSpy = jest.spyOn(devkit, 'installPackagesTask').mockImplementation(noop); + graphMock = { + ...blankGraphMock, + }; + tree = createTreeWithEmptyWorkspace(); + tree.write(codeownersPath, `@proj/foo @org/all`); + writeJson(tree, 'tsconfig.base.v8.json', { compilerOptions: { paths: {} } }); + writeJson(tree, 'tsconfig.base.v0.json', { compilerOptions: { paths: {} } }); + writeJson(tree, 'tsconfig.base.all.json', { compilerOptions: { paths: {} } }); }); - it('should run successfully', async () => { - await generator(appTree, options); - const config = readProjectConfiguration(appTree, 'test'); - expect(config).toBeDefined(); + it(`should throw error if executed on invalid project`, async () => { + createProject(tree, 'react-one-stable', { + root: 'packages/react-one-stable', + pkgJson: { + version: '9.0.0-alpha.0', + }, + }); + + await expect(generator(tree, { project: '@proj/react-one-stable', phase: 'stable' })).rejects.toMatchInlineSnapshot( + `[Error: @proj/react-one-stable is already prepared for stable release. Please trigger RELEASE pipeline.]`, + ); + + updateJson(tree, 'packages/react-one-stable/package.json', json => { + json.version = '9.0.0'; + return json; + }); + + await expect(generator(tree, { project: '@proj/react-one-stable', phase: 'stable' })).rejects.toMatchInlineSnapshot( + `[Error: @proj/react-one-stable is already released as stable.]`, + ); + }); + + describe(`--phase`, () => { + describe(`initial`, () => { + it(`should prepare preview package for initial release`, async () => { + const utils = { + project: createProject(tree, 'react-one-preview', { + root: 'packages/react-one-preview', + pkgJson: { + version: '0.0.0', + private: true, + }, + renameRoot: false, + }), + docsite: createProject(tree, 'public-docsite-v9', { + root: 'apps/public-docsite-v9', + pkgJson: { version: '9.0.123', private: true }, + renameRoot: false, + }), + }; + + const sideEffects = await generator(tree, { project: '@proj/react-one-preview', phase: 'initial' }); + + expect(utils.project.pkgJson()).toMatchInlineSnapshot(` + Object { + "name": "@proj/react-one-preview", + "version": "0.0.0", + } + `); + + expect(utils.docsite.pkgJson().dependencies).toEqual( + expect.objectContaining({ + '@proj/react-one-preview': '*', + }), + ); + + sideEffects(); + + expect(execSyncSpy.mock.calls.flat()).toMatchInlineSnapshot(` + Array [ + "yarn change --message 'feat: release preview package' --type minor --package @proj/react-one-preview", + ] + `); + }); + }); + + describe(`stable`, () => { + const projectName = '@proj/react-one-preview'; + type Utils = ReturnType; + const utils = { project: {} as Utils, suite: {} as Utils, docsite: {} as Utils, vrTest: {} as Utils }; + + beforeEach(() => { + utils.project = createProject(tree, 'react-one-preview', { + root: 'packages/react-one-preview', + pkgJson: { + version: '0.12.33', + }, + files: [ + { + filePath: 'packages/react-one-preview/stories/One.stories.tsx', + content: stripIndents` + import { One } from '@proj/react-one-preview'; + + export const App = () => { return }; + `, + }, + ], + }); + utils.suite = createProject(tree, 'react-components', { + root: 'packages/react-components/react-components', + pkgJson: { version: '9.0.1' }, + }); + utils.docsite = createProject(tree, 'public-docsite-v9', { + root: 'apps/public-docsite-v9', + pkgJson: { version: '9.0.123', private: true }, + files: [ + { + filePath: 'apps/public-docsite-v9/src/example.stories.tsx', + content: stripIndents` + import { One } from '${projectName}'; + import * as suite from '@proj/react-components'; + + export const Example = () => { return ; } + `, + }, + ], + }); + utils.vrTest = createProject(tree, 'vr-tests-react-components', { + root: 'apps/vr-tests-react-components', + pkgJson: { version: '9.0.77', private: true }, + files: [ + { + filePath: 'apps/vr-tests-react-components/src/stories/One.stories.tsx', + content: stripIndents` + import { One } from '${projectName}'; + import * as suite from '@proj/react-components'; + + export const VrTest = () => { return ; } + `, + }, + ], + }); + }); + + it(`should prepare preview package for stable release`, async () => { + const sideEffects = await generator(tree, { project: projectName, phase: 'stable' }); + + expect(utils.project.pkgJson()).toMatchInlineSnapshot(` + Object { + "name": "@proj/react-one", + "version": "9.0.0-alpha.0", + } + `); + expect(utils.project.projectJson()).toEqual( + expect.objectContaining({ + name: '@proj/react-one', + sourceRoot: 'packages/react-one/src', + }), + ); + expect(utils.project.jest()).toEqual(expect.stringContaining(`displayName: 'react-one'`)); + expect(utils.project.md.readme()).toMatchInlineSnapshot(` + "# @proj/react-one + + **React Tags components for [Fluent UI React](https://react.fluentui.dev/)** + + These are not production-ready components and **should never be used in product**. This space is useful for testing new components whose APIs might change before final release. + " + `); + expect(utils.project.md.api()).toMatchInlineSnapshot(` + "## API Report File for \\"@proj/react-one\\" + + > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + " + `); + expect(tree.read('packages/react-one/stories/One.stories.tsx', 'utf-8')).toMatchInlineSnapshot(` + "import { One } from '@proj/react-one-preview'; + + export const App = () => { + return ; + }; + " + `); + + expect(tree.children('packages/react-one-preview')).toEqual([]); + + expect(utils.project.global.codeowners()).toEqual( + expect.stringContaining('packages/react-one @org/universe @johnwick'), + ); + expect(utils.project.global.tsBase().compilerOptions.paths).toEqual( + expect.objectContaining({ + '@proj/react-one': ['packages/react-one/src/index.ts'], + }), + ); + expect(utils.project.global.tsBaseAll().compilerOptions.paths).toEqual( + expect.objectContaining({ + '@proj/react-one': ['packages/react-one/src/index.ts'], + }), + ); + + // project updates + + expect(utils.docsite.pkgJson().dependencies).not.toEqual( + expect.objectContaining({ '@proj/react-one-preview': '*' }), + ); + expect(tree.read('apps/public-docsite-v9/src/example.stories.tsx', 'utf-8')).toEqual( + expect.stringContaining(stripIndents` + import { One } from '@proj/react-components'; + import * as suite from '@proj/react-components'; + `), + ); + + const vrTestDeps = utils.vrTest.pkgJson().dependencies ?? {}; + expect(vrTestDeps).toEqual(expect.objectContaining({ '@proj/react-one': '*' })); + expect(vrTestDeps[projectName]).toEqual(undefined); + expect(tree.read('apps/vr-tests-react-components/src/stories/One.stories.tsx', 'utf-8')).toEqual( + expect.stringContaining(stripIndents` + import { One } from '@proj/react-one'; + import * as suite from '@proj/react-components'; + `), + ); + + expect(utils.suite.pkgJson().dependencies).toEqual( + expect.objectContaining({ '@proj/react-one': '9.0.0-alpha.0' }), + ); + expect(tree.read('packages/react-components/react-components/src/index.ts', 'utf-8')).toEqual( + expect.stringContaining(stripIndents` + export * from '@proj/react-one'; + `), + ); + + sideEffects(); + + expect(execSyncSpy.mock.calls.flat()).toMatchInlineSnapshot(` + Array [ + "yarn nx run @proj/react-components:lint --fix", + "yarn change --message 'feat: release stable' --type minor --package @proj/react-one", + ] + `); + expect(installPackagesTaskSpy).toHaveBeenCalled(); + }); + + it(`should update also other packages besides known ones if preview was used there`, async () => { + // eslint-disable-next-line @typescript-eslint/no-shadow + const utils = createProject(tree, 'react-another-app', { + root: 'apps/react-another-app', + pkgJson: { version: '9.2.0', dependencies: { '@proj/react-one-preview': '*' } }, + files: [ + { + filePath: 'apps/react-another-app/src/index.ts', + content: stripIndents` + import * as React from 'react'; + import { One } from '@proj/react-one-preview'; + `, + }, + ], + }); + + await generator(tree, { project: projectName, phase: 'stable' }); + + const dependencies = utils.pkgJson().dependencies ?? {}; + expect(dependencies[projectName]).toEqual(undefined); + expect(dependencies).toEqual( + expect.objectContaining({ + '@proj/react-components': '*', + }), + ); + + expect(tree.read('apps/react-another-app/src/index.ts', 'utf-8')).toEqual( + expect.stringContaining(stripIndents` + import { One } from '@proj/react-components'; + `), + ); + }); + }); }); }); + +function createProject( + tree: Tree, + projectName: string, + options: { + root: string; + pkgJson: Partial; + files?: Array<{ filePath: string; content: string }>; + renameRoot?: boolean; + }, +) { + const projectType = options.root.startsWith('apps/') ? 'application' : 'library'; + const npmName = `@proj/${projectName}`; + const pkgJsonPath = joinPathFragments(options.root, 'package.json'); + const sourceRoot = joinPathFragments(options.root, 'src'); + const indexFile = joinPathFragments(sourceRoot, 'index.ts'); + const jestPath = joinPathFragments(options.root, 'jest.config.js'); + const readmePath = joinPathFragments(options.root, 'README.md'); + const apiMdPath = joinPathFragments(options.root, `etc/${projectName}.api.md`); + const tsConfigBaseAllPath = 'tsconfig.base.all.json'; + const tsConfigBasePath = 'tsconfig.base.json'; + + writeJson(tree, pkgJsonPath, { + ...options.pkgJson, + name: npmName, + }); + + addProjectConfiguration(tree, npmName, { root: options.root, sourceRoot, tags: ['vNext'] }); + + tree.write( + indexFile, + stripIndents` + export {}; + `, + ); + + tree.write( + readmePath, + stripIndents` + # ${npmName} + +**React Tags components for [Fluent UI React](https://react.fluentui.dev/)** + +These are not production-ready components and **should never be used in product**. This space is useful for testing new components whose APIs might change before final release. + + `, + ); + tree.write( + apiMdPath, + stripIndents` + ## API Report File for "${npmName}" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + `, + ); + tree.write( + jestPath, + stripIndents` + module.exports = { + displayName: '${projectName}', + }; + `, + ); + + const currentCodeowners = tree.read(codeownersPath, 'utf-8'); + const updatedCodeowners = currentCodeowners + `${options.root} @org/universe @johnwick\n`; + tree.write(codeownersPath, updatedCodeowners); + updateJson(tree, tsConfigBasePath, json => { + json.compilerOptions.paths![npmName] = [indexFile]; + return json; + }); + updateJson(tree, tsConfigBaseAllPath, json => { + json.compilerOptions.paths![npmName] = [indexFile]; + return json; + }); + + const depKeys = [...Object.keys(options.pkgJson.dependencies ?? {})]; + + graphMock.dependencies[npmName] = depKeys.map(value => { + return { source: npmName, target: value, type: 'static' }; + }); + graphMock.nodes[npmName] = { + name: npmName, + type: projectType === 'library' ? 'lib' : 'app', + data: { name: npmName, root: npmName, files: [] }, + }; + + if (options.files) { + options.files.forEach(fileEntry => { + tree.write(fileEntry.filePath, fileEntry.content); + }); + } + + const newRoot = options.renameRoot === false ? options.root : options.root.replace('-preview', ''); + + return { + pkgJson: () => { + return readJson(tree, joinPathFragments(newRoot, 'package.json')); + }, + projectJson: () => { + return readJson(tree, joinPathFragments(newRoot, 'project.json')); + }, + jest: () => { + return tree.read(joinPathFragments(newRoot, 'jest.config.js'), 'utf-8'); + }, + md: { + readme: () => tree.read(joinPathFragments(newRoot, 'README.md'), 'utf-8'), + api: () => tree.read(joinPathFragments(newRoot, `etc/${projectName.replace('-preview', '')}.api.md`), 'utf-8'), + }, + global: { + tsBase: () => readJson(tree, tsConfigBasePath), + tsBaseAll: () => readJson(tree, tsConfigBaseAllPath), + codeowners: () => tree.read(codeownersPath, 'utf-8'), + }, + }; +} diff --git a/tools/generators/release-package/index.ts b/tools/generators/release-package/index.ts index 09e7e280aabfce..8779b380c9250a 100644 --- a/tools/generators/release-package/index.ts +++ b/tools/generators/release-package/index.ts @@ -1,50 +1,306 @@ -import * as path from 'path'; -import { Tree, formatFiles, installPackagesTask, names, generateFiles } from '@nrwl/devkit'; -import { libraryGenerator } from '@nrwl/workspace/generators'; +import { + Tree, + formatFiles, + names, + updateJson, + ProjectConfiguration, + joinPathFragments, + visitNotIgnoredFiles, + createProjectGraphAsync, + reverse, + installPackagesTask, + readJson, +} from '@nrwl/devkit'; -import { getProjectConfig } from '../../utils'; +import { getProjectConfig, workspacePaths } from '../../utils'; + +import { PackageJson, TsConfig } from '../../types'; + +import tsConfigBaseAll from '../tsconfig-base-all'; import { ReleasePackageGeneratorSchema } from './schema'; +import { execSync } from 'child_process'; interface NormalizedSchema extends ReturnType {} export default async function (tree: Tree, schema: ReleasePackageGeneratorSchema) { - await libraryGenerator(tree, { name: schema.name }); + const options = normalizeOptions(tree, schema); - const normalizedOptions = normalizeOptions(tree, schema); + assertProject(tree, options); - addFiles(tree, normalizedOptions); + const tasks: Array<(tree: Tree) => void> = []; + + if (options.phase === 'initial') { + tasks.push(initialRelease(tree, options)); + } + if (options.phase === 'stable') { + tasks.push(await stableRelease(tree, options)); + } await formatFiles(tree); return () => { - installPackagesTask(tree); + tasks.forEach(task => { + task(tree); + }); }; } function normalizeOptions(tree: Tree, options: ReleasePackageGeneratorSchema) { - const project = getProjectConfig(tree, { packageName: options.name }); + const project = getProjectConfig(tree, { packageName: options.project }); return { ...options, ...project, - ...names(options.name), + ...names(options.project), }; } -/** - * NOTE: remove this if your generator doesn't process any static/dynamic templates - */ -function addFiles(tree: Tree, options: NormalizedSchema) { - const templateOptions = { - ...options, - tmpl: '', +function initialRelease(tree: Tree, options: NormalizedSchema) { + updateJson(tree, options.paths.packageJson, json => { + delete json.private; + return json; + }); + + const docsiteProjectName = '@' + options.workspaceConfig.npmScope + '/public-docsite-v9'; + const docsite = getProjectConfig(tree, { packageName: docsiteProjectName }); + + updateJson(tree, docsite.paths.packageJson, json => { + json.dependencies = json.dependencies ?? {}; + json.dependencies[options.project] = '*'; + return json; + }); + + return (_tree: Tree) => { + generateChangefileTask(tree, options.project, { message: 'feat: release preview package' }); + }; +} + +async function stableRelease(tree: Tree, options: NormalizedSchema) { + const suitePackageName = '@' + options.workspaceConfig.npmScope + '/react-components'; + const currentPackageName = options.projectConfig.name as string; + const newPackage = { + name: currentPackageName.replace('-preview', ''), + normalizedName: options.normalizedPkgName.replace('-preview', ''), + version: '9.0.0-alpha.0', + root: options.projectConfig.root.replace('-preview', ''), + sourceRoot: options.projectConfig.sourceRoot?.replace('-preview', '') as string, + }; + + updateJson(tree, options.paths.packageJson, json => { + delete json.private; + json.name = newPackage.name; + json.version = newPackage.version; + return json; + }); + updateJson(tree, options.paths.projectJson, json => { + json.name = newPackage.name; + json.sourceRoot = newPackage.sourceRoot; + + return json; + }); + + const contentNameUpdater = (content: string) => { + const regexp = new RegExp(options.normalizedPkgName, 'g'); + return content.replace(regexp, newPackage.normalizedName); + }; + const contentNameToSuiteUpdater = (content: string) => { + const regexp = new RegExp(options.normalizedPkgName, 'g'); + return content.replace(regexp, 'react-components'); + }; + + updateFileContent(tree, { filePath: options.paths.jestConfig, updater: contentNameUpdater }); + + const mdFilePath = { + readme: joinPathFragments(options.projectConfig.root, 'README.md'), + api: joinPathFragments(options.projectConfig.root, 'etc', options.normalizedPkgName + '.api.md'), + apiNew: joinPathFragments(options.projectConfig.root, 'etc', newPackage.normalizedName + '.api.md'), + }; + + updateFileContent(tree, { + filePath: mdFilePath.readme, + updater: contentNameUpdater, + }); + updateFileContent(tree, { filePath: mdFilePath.api, newFilePath: mdFilePath.apiNew, updater: contentNameUpdater }); + + // update stories + visitNotIgnoredFiles(tree, options.paths.stories, filePath => { + if (filePath.indexOf('index.stories.tsx') !== -1) { + updateFileContent(tree, { + filePath, + updater: content => { + let newContent = content.replace(`'Preview Components/`, 'Components/'); + + newContent = contentNameUpdater(content); + + return newContent; + }, + }); + } + }); + + // global updates + updateJson(tree, options.paths.rootTsconfig, json => { + json.compilerOptions.paths = json.compilerOptions.paths ?? {}; + + delete json.compilerOptions.paths[currentPackageName]; + json.compilerOptions.paths[newPackage.name] = [joinPathFragments(newPackage.sourceRoot, 'index.ts')]; + + return json; + }); + + await tsConfigBaseAll(tree, {}); + + updateFileContent(tree, { filePath: workspacePaths.github.codeowners, updater: contentNameUpdater }); + + // add to suite (react-components) + const reactComponentsProject = getProjectConfig(tree, { + packageName: suitePackageName, + }); + updateJson(tree, reactComponentsProject.paths.packageJson, json => { + json.dependencies = json.dependencies ?? {}; + json.dependencies[newPackage.name] = newPackage.version; + return json; + }); + + updateFileContent(tree, { + filePath: joinPathFragments(reactComponentsProject.projectConfig.sourceRoot as string, 'index.ts'), + updater: content => { + return content + '\n' + `export * from '${newPackage.name}'`; + }, + }); + + const knownProjectsToBeUpdated = { + docsite: '@' + options.workspaceConfig.npmScope + '/public-docsite-v9', + vrTests: '@' + options.workspaceConfig.npmScope + '/vr-tests-react-components', }; - generateFiles( + // update other projects that might still contain dependency to old -preview package + const unknownProjectsToBeUpdated = (await getProjectThatNeedsToBeUpdated(tree, options))?.filter(projectName => { + const knownKeys = Object.values(knownProjectsToBeUpdated); + return !knownKeys.includes(projectName); + }); + + // update public-docsite-v9 + const reactComponentsDocsiteProject = getProjectConfig(tree, { + packageName: knownProjectsToBeUpdated.docsite, + }); + updateJson(tree, reactComponentsDocsiteProject.paths.packageJson, json => { + json.dependencies = json.dependencies ?? {}; + delete json.dependencies[currentPackageName]; + return json; + }); + visitNotIgnoredFiles(tree, joinPathFragments(reactComponentsDocsiteProject.projectConfig.root, 'src'), filePath => { + updateFileContent(tree, { filePath, updater: contentNameToSuiteUpdater }); + }); + + // update vr-tests-react-components + const reactComponentsVrTestsProject = getProjectConfig(tree, { + packageName: knownProjectsToBeUpdated.vrTests, + }); + updateJson(tree, reactComponentsVrTestsProject.paths.packageJson, json => { + json.dependencies = json.dependencies ?? {}; + delete json.dependencies[currentPackageName]; + json.dependencies[newPackage.name] = '*'; + return json; + }); + visitNotIgnoredFiles( tree, - path.join(__dirname, 'files'), - path.join(options.projectConfig.root, options.name), - templateOptions, + joinPathFragments(reactComponentsVrTestsProject.projectConfig.root, 'src/stories'), + filePath => { + updateFileContent(tree, { filePath, updater: contentNameUpdater }); + }, ); + + unknownProjectsToBeUpdated?.forEach(projectName => { + const projectConfig = getProjectConfig(tree, { + packageName: projectName, + }); + visitNotIgnoredFiles(tree, joinPathFragments(projectConfig.projectConfig.root, 'src'), filePath => { + updateFileContent(tree, { filePath, updater: contentNameToSuiteUpdater }); + }); + updateJson(tree, joinPathFragments(projectConfig.projectConfig.root, 'package.json'), json => { + json.dependencies = json.dependencies ?? {}; + delete json.dependencies[currentPackageName]; + json.dependencies[suitePackageName] = '*'; + return json; + }); + }); + + // AFTER updates are done - rename project folder + tree.rename(options.projectConfig.root, newPackage.root); + + return (_tree: Tree) => { + lintFixTask(tree, suitePackageName); + generateChangefileTask(tree, newPackage.name, { message: 'feat: release stable' }); + installPackagesTask(tree); + }; +} + +function updateFileContent( + tree: Tree, + options: { + filePath: string; + updater: (content: string) => string; + newFilePath?: string; + }, +) { + const { filePath, newFilePath, updater } = options; + const oldContent = tree.read(filePath, 'utf-8') as string; + + const newContent = updater(oldContent); + + if (newFilePath) { + tree.rename(filePath, newFilePath); + tree.write(newFilePath, newContent); + } else { + tree.write(filePath, newContent); + } + + return tree; +} + +async function getProjectThatNeedsToBeUpdated(tree: Tree, options: NormalizedSchema) { + const projectName = options.projectConfig.name as string; + + const graph = await createProjectGraphAsync(); + const reverseGraph = reverse(graph); + + const deps = reverseGraph.dependencies[projectName] || []; + + if (deps.length > 0) { + return deps.map(dep => dep.target); + } +} + +function lintFixTask(tree: Tree, projectName: string) { + const cmd = `yarn nx run ${projectName}:lint --fix`; + return execSync(cmd); +} + +function generateChangefileTask(tree: Tree, projectName: string, options: { message: string }) { + const cmd = `yarn change --message '${options.message}' --type minor --package ${projectName}`; + return execSync(cmd); +} + +function assertProject(tree: Tree, options: NormalizedSchema) { + const pkgJson = readJson(tree, options.paths.packageJson); + + const isVnextPackage = options.projectConfig.tags?.includes('vNext'); + const isPreviewPackage = pkgJson.version.startsWith('0') && pkgJson.name.endsWith('-preview'); + const isPreparedForStableAlready = pkgJson.version === '9.0.0-alpha.0'; + + if (!isVnextPackage) { + throw new Error(`${options.project} is not a v9 package.`); + } + + if (isPreviewPackage) { + return; + } + + if (isPreparedForStableAlready) { + throw new Error(`${options.project} is already prepared for stable release. Please trigger RELEASE pipeline.`); + } + + throw new Error(`${options.project} is already released as stable.`); } diff --git a/tools/generators/release-package/lib/.gitkeep b/tools/generators/release-package/lib/.gitkeep new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/tools/generators/release-package/lib/utils.spec.ts b/tools/generators/release-package/lib/utils.spec.ts deleted file mode 100644 index 91e05a54a86f53..00000000000000 --- a/tools/generators/release-package/lib/utils.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { dummyHelper } from './utils'; - -describe(`utils`, () => { - it(`should behave...`, () => { - expect(dummyHelper()).toBe(undefined); - }); -}); diff --git a/tools/generators/release-package/lib/utils.ts b/tools/generators/release-package/lib/utils.ts deleted file mode 100644 index c340a27c29c8b0..00000000000000 --- a/tools/generators/release-package/lib/utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -// use this module to define any kind of generic utilities that are used in more than 1 place within the generator implementation - -export function dummyHelper() { - return; -} diff --git a/tools/generators/release-package/schema.json b/tools/generators/release-package/schema.json index 31321329545d0a..546a02101fa9d7 100644 --- a/tools/generators/release-package/schema.json +++ b/tools/generators/release-package/schema.json @@ -4,14 +4,34 @@ "id": "release-package", "type": "object", "properties": { - "name": { + "project": { "type": "string", "description": "Library name", + "x-prompt": "What project should be released", "$default": { "$source": "argv", "index": 0 } + }, + "phase": { + "type": "string", + "enum": ["initial", "stable"], + "description": "Phase of npm release life cycle for monorepo package", + "x-prompt": { + "message": "Which phase of release life cycle you wanna trigger?", + "type": "list", + "items": [ + { + "value": "initial", + "label": "initial - first release of preview package" + }, + { + "value": "stable", + "label": "stable - propagating package from preview to stable (shipping from react-components suite)" + } + ] + } } }, - "required": ["name"] + "required": ["project", "phase"] } diff --git a/tools/generators/release-package/schema.ts b/tools/generators/release-package/schema.ts index 8db964bfe2e628..2f65f9e706c4d6 100644 --- a/tools/generators/release-package/schema.ts +++ b/tools/generators/release-package/schema.ts @@ -1,6 +1,17 @@ +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + export interface ReleasePackageGeneratorSchema { /** * Library name */ - name: string; + project: string; + /** + * Phase of release life cycle for our packages + */ + phase: 'initial' | 'stable'; } diff --git a/tools/utils.ts b/tools/utils.ts index ac3b8b4cd4da25..7c4bb09914f90f 100644 --- a/tools/utils.ts +++ b/tools/utils.ts @@ -88,6 +88,7 @@ export function getProjectConfig(tree: Tree, options: { packageName: string }) { const paths = { configRoot: joinPathFragments(projectConfig.root, 'config'), packageJson: joinPathFragments(projectConfig.root, 'package.json'), + projectJson: joinPathFragments(projectConfig.root, 'project.json'), tsconfig: { main: joinPathFragments(projectConfig.root, 'tsconfig.json'), lib: joinPathFragments(projectConfig.root, 'tsconfig.lib.json'), From 205f259d494d07213375e8774fea23b510848712 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Fri, 14 Jul 2023 11:17:31 +0200 Subject: [PATCH 3/8] fixup! feat(tools): implement release-package generator --- tools/generators/release-package/index.spec.ts | 1 + tools/generators/release-package/index.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/tools/generators/release-package/index.spec.ts b/tools/generators/release-package/index.spec.ts index a07e32e6d3390a..ddd4b2eca3826f 100644 --- a/tools/generators/release-package/index.spec.ts +++ b/tools/generators/release-package/index.spec.ts @@ -275,6 +275,7 @@ describe('release-package generator', () => { Array [ "yarn nx run @proj/react-components:lint --fix", "yarn change --message 'feat: release stable' --type minor --package @proj/react-one", + "yarn change --message 'feat: add @proj/react-one to suite' --type minor --package @proj/react-components", ] `); expect(installPackagesTaskSpy).toHaveBeenCalled(); diff --git a/tools/generators/release-package/index.ts b/tools/generators/release-package/index.ts index 8779b380c9250a..7caf8129e367b2 100644 --- a/tools/generators/release-package/index.ts +++ b/tools/generators/release-package/index.ts @@ -233,6 +233,7 @@ async function stableRelease(tree: Tree, options: NormalizedSchema) { return (_tree: Tree) => { lintFixTask(tree, suitePackageName); generateChangefileTask(tree, newPackage.name, { message: 'feat: release stable' }); + generateChangefileTask(tree, suitePackageName, { message: `feat: add ${newPackage.name} to suite` }); installPackagesTask(tree); }; } From 04f725d0fd4d0387debece2671427d776b8a275a Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Fri, 14 Jul 2023 12:10:43 +0200 Subject: [PATCH 4/8] feat(tools): implement AST construction of exports within suite on stable release --- package.json | 1 + .../generators/release-package/index.spec.ts | 14 ++++++-- tools/generators/release-package/index.ts | 35 +++++++++++++++---- yarn.lock | 22 ++++++++++++ 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index c5603dd2b7fac4..7f1a326099b895 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "@nrwl/node": "15.9.4", "@nrwl/workspace": "15.9.4", "@octokit/rest": "18.12.0", + "@phenomnomnominal/tsquery": "6.1.2", "@storybook/addon-a11y": "6.5.15", "@storybook/addon-actions": "6.5.15", "@storybook/addon-docs": "6.5.15", diff --git a/tools/generators/release-package/index.spec.ts b/tools/generators/release-package/index.spec.ts index ddd4b2eca3826f..19ebb47f1562b8 100644 --- a/tools/generators/release-package/index.spec.ts +++ b/tools/generators/release-package/index.spec.ts @@ -137,6 +137,16 @@ describe('release-package generator', () => { version: '0.12.33', }, files: [ + { + filePath: 'packages/react-one-preview/src/index.ts', + content: stripIndents` + export {One} from './one'; + export type {OneType} from './one'; + + export {Two} from './two'; + export type {TwoType} from './two'; + `, + }, { filePath: 'packages/react-one-preview/stories/One.stories.tsx', content: stripIndents` @@ -265,7 +275,8 @@ describe('release-package generator', () => { ); expect(tree.read('packages/react-components/react-components/src/index.ts', 'utf-8')).toEqual( expect.stringContaining(stripIndents` - export * from '@proj/react-one'; + export { One, Two } from '@proj/react-one'; + export type { OneType, TwoType } from '@proj/react-one'; `), ); @@ -273,7 +284,6 @@ describe('release-package generator', () => { expect(execSyncSpy.mock.calls.flat()).toMatchInlineSnapshot(` Array [ - "yarn nx run @proj/react-components:lint --fix", "yarn change --message 'feat: release stable' --type minor --package @proj/react-one", "yarn change --message 'feat: add @proj/react-one to suite' --type minor --package @proj/react-components", ] diff --git a/tools/generators/release-package/index.ts b/tools/generators/release-package/index.ts index 7caf8129e367b2..6bce5d915bea95 100644 --- a/tools/generators/release-package/index.ts +++ b/tools/generators/release-package/index.ts @@ -10,8 +10,11 @@ import { reverse, installPackagesTask, readJson, + stripIndents, } from '@nrwl/devkit'; +import * as tsquery from '@phenomnomnominal/tsquery'; + import { getProjectConfig, workspacePaths } from '../../utils'; import { PackageJson, TsConfig } from '../../types'; @@ -166,7 +169,9 @@ async function stableRelease(tree: Tree, options: NormalizedSchema) { updateFileContent(tree, { filePath: joinPathFragments(reactComponentsProject.projectConfig.sourceRoot as string, 'index.ts'), updater: content => { - return content + '\n' + `export * from '${newPackage.name}'`; + const currentBarrelFilePath = joinPathFragments(options.projectConfig.sourceRoot as string, 'index.ts'); + const currentBarrelFile = tree.read(currentBarrelFilePath, 'utf-8') as string; + return content + '\n' + createExportsInSuite(currentBarrelFile, newPackage.name); }, }); @@ -231,7 +236,6 @@ async function stableRelease(tree: Tree, options: NormalizedSchema) { tree.rename(options.projectConfig.root, newPackage.root); return (_tree: Tree) => { - lintFixTask(tree, suitePackageName); generateChangefileTask(tree, newPackage.name, { message: 'feat: release stable' }); generateChangefileTask(tree, suitePackageName, { message: `feat: add ${newPackage.name} to suite` }); installPackagesTask(tree); @@ -274,11 +278,6 @@ async function getProjectThatNeedsToBeUpdated(tree: Tree, options: NormalizedSch } } -function lintFixTask(tree: Tree, projectName: string) { - const cmd = `yarn nx run ${projectName}:lint --fix`; - return execSync(cmd); -} - function generateChangefileTask(tree: Tree, projectName: string, options: { message: string }) { const cmd = `yarn change --message '${options.message}' --type minor --package ${projectName}`; return execSync(cmd); @@ -305,3 +304,25 @@ function assertProject(tree: Tree, options: NormalizedSchema) { throw new Error(`${options.project} is already released as stable.`); } + +function createExportsInSuite(content: string, packageName: string) { + const ast = tsquery.ast(content); + const exports = tsquery.query(ast, 'ExportDeclaration[isTypeOnly=false] ExportSpecifier'); + const exportsTypes = tsquery.query(ast, 'ExportDeclaration[isTypeOnly=true] ExportSpecifier'); + + const exportExpression = exports + .map(exp => { + return tsquery.print(exp); + }) + .join(','); + const exportTypeExpression = exportsTypes + .map(exp => { + return tsquery.print(exp); + }) + .join(','); + + return stripIndents` + export { ${exportExpression} } from '${packageName}'; + export type { ${exportTypeExpression} } from '${packageName}'; + `; +} diff --git a/yarn.lock b/yarn.lock index b3f0952163bbf5..afe5f12ff6c057 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3690,6 +3690,14 @@ dependencies: esquery "^1.0.1" +"@phenomnomnominal/tsquery@6.1.2": + version "6.1.2" + resolved "https://registry.yarnpkg.com/@phenomnomnominal/tsquery/-/tsquery-6.1.2.tgz#37ec13373ec144f524958770ebc294d0b5e2909e" + integrity sha512-NahxUvas4D4iRV1NqlL6Z3mIl2Fo+rw1x77wgZpYyaQjQnS4svv6XoVzjcRRtnP5cfY6XuVKLZki8Zltkz8z0w== + dependencies: + "@types/esquery" "^1.5.0" + esquery "^1.5.0" + "@pmmmwh/react-refresh-webpack-plugin@^0.5.3": version "0.5.7" resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz#58f8217ba70069cc6a73f5d7e05e85b458c150e2" @@ -5351,6 +5359,13 @@ "@types/estree" "*" "@types/json-schema" "*" +"@types/esquery@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@types/esquery/-/esquery-1.5.0.tgz#928ccc6e61786dcd7c0759c06b803589855dd75a" + integrity sha512-MNQ5gCt3j1idWHlj/dEF+WPS1kl6Woe0Agzwy96JvrwDQdDadqeIBhY7mUca51CCUzxf7BsnXzcyKi6ENpEtmQ== + dependencies: + "@types/estree" "*" + "@types/estree@*", "@types/estree@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" @@ -12398,6 +12413,13 @@ esquery@^1.0.1, esquery@^1.4.0: dependencies: estraverse "^5.1.0" +esquery@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + esrecurse@^4.1.0, esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" From c9b9614b0cf87018c5f13d4ca82818cfe27f07cb Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Fri, 14 Jul 2023 12:43:48 +0200 Subject: [PATCH 5/8] fixup! feat(tools): implement AST construction of exports within suite on stable release --- yarn.lock | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index afe5f12ff6c057..4ef8761da63db2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12406,14 +12406,7 @@ esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.0.1, esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== - dependencies: - estraverse "^5.1.0" - -esquery@^1.5.0: +esquery@^1.0.1, esquery@^1.4.0, esquery@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== From ed65f752243395c81bc08b91bfa73d8a0eda0904 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Fri, 14 Jul 2023 12:46:21 +0200 Subject: [PATCH 6/8] fixup! fixup! feat(tools): implement release-package generator --- tools/generators/release-package/schema.json | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/generators/release-package/schema.json b/tools/generators/release-package/schema.json index 546a02101fa9d7..ee19d4516a6333 100644 --- a/tools/generators/release-package/schema.json +++ b/tools/generators/release-package/schema.json @@ -15,7 +15,6 @@ }, "phase": { "type": "string", - "enum": ["initial", "stable"], "description": "Phase of npm release life cycle for monorepo package", "x-prompt": { "message": "Which phase of release life cycle you wanna trigger?", From 1c3cb88b6e21c2319227bec85d3f7acf88351da7 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Mon, 17 Jul 2023 11:42:52 +0200 Subject: [PATCH 7/8] refactor(tools): rename release-package to prepare-initial-release and add release cycle graph docs --- .../prepare-initial-release/README.md | 85 +++++++++++++++++++ .../index.spec.ts | 2 +- .../index.ts | 0 .../lib/.gitkeep | 0 .../schema.json | 12 +-- .../schema.ts | 4 +- tools/generators/release-package/README.md | 45 ---------- 7 files changed, 94 insertions(+), 54 deletions(-) create mode 100644 tools/generators/prepare-initial-release/README.md rename tools/generators/{release-package => prepare-initial-release}/index.spec.ts (99%) rename tools/generators/{release-package => prepare-initial-release}/index.ts (100%) rename tools/generators/{release-package => prepare-initial-release}/lib/.gitkeep (100%) rename tools/generators/{release-package => prepare-initial-release}/schema.json (55%) rename tools/generators/{release-package => prepare-initial-release}/schema.ts (78%) delete mode 100644 tools/generators/release-package/README.md diff --git a/tools/generators/prepare-initial-release/README.md b/tools/generators/prepare-initial-release/README.md new file mode 100644 index 00000000000000..d79d8bdcc65592 --- /dev/null +++ b/tools/generators/prepare-initial-release/README.md @@ -0,0 +1,85 @@ +# prepare-initial-release + +Workspace Generator which automates initial release process steps for `preview` and `stable` stages of core @fluentui (v9) packages. + +### V9 Release process flow: + +```mermaid +flowchart TB + +subgraph IRP[1st release preparation for preview] +GP(nx prepare-initial-release --phase=preview) +RP(released to npm as v0.1.0) +GP--ci:npm publish-->RP +end + +subgraph IRS[1st release preparation for stable] +GS(nx prepare-initial-release --phase=stable) +RS(released to npm as v9.0.0) +GS--ci:npm publish-->RS +end + +subgraph KP[kickoff phase] + AA[bootstrap package] + AB[research] + AB[prototyping] +end + +subgraph PP[preview phase] +BA[ongoing development] +BB[uses 0.x.x semver release pattern] +BC[released to npm as *-preview] +end + +subgraph SP[stable phase] +CA[ongoing development] +CB[released as part of react-components suite] +CC[released to npm as stable 9.0.0] +end + +KP-.->IRP-.->PP-.->IRS-.->SP + + +``` + + + +- [Usage](#usage) + - [Examples](#examples) +- [Options](#options) + - [`project`](#project) + - [`phase`](#phase) + + + +## Usage + +```sh +yarn nx workspace-generator prepare-initial-release ... +``` + +Show what will be generated without writing to disk: + +```sh +yarn nx workspace-generator prepare-initial-release --dry-run +``` + +### Examples + +```sh +yarn nx workspace-generator prepare-initial-release +``` + +## Options + +#### `project` + +Type: `string` + +Library name to to be released. + +#### `phase` + +Type: `preview` | 'stable' + +Phase of npm release life cycle for monorepo package diff --git a/tools/generators/release-package/index.spec.ts b/tools/generators/prepare-initial-release/index.spec.ts similarity index 99% rename from tools/generators/release-package/index.spec.ts rename to tools/generators/prepare-initial-release/index.spec.ts index 19ebb47f1562b8..48571591165912 100644 --- a/tools/generators/release-package/index.spec.ts +++ b/tools/generators/prepare-initial-release/index.spec.ts @@ -40,7 +40,7 @@ const noop = () => {}; let execSyncSpy: jest.SpyInstance; let installPackagesTaskSpy: jest.SpyInstance; -describe('release-package generator', () => { +describe('prepare-initial-release generator', () => { let tree: Tree; beforeEach(() => { diff --git a/tools/generators/release-package/index.ts b/tools/generators/prepare-initial-release/index.ts similarity index 100% rename from tools/generators/release-package/index.ts rename to tools/generators/prepare-initial-release/index.ts diff --git a/tools/generators/release-package/lib/.gitkeep b/tools/generators/prepare-initial-release/lib/.gitkeep similarity index 100% rename from tools/generators/release-package/lib/.gitkeep rename to tools/generators/prepare-initial-release/lib/.gitkeep diff --git a/tools/generators/release-package/schema.json b/tools/generators/prepare-initial-release/schema.json similarity index 55% rename from tools/generators/release-package/schema.json rename to tools/generators/prepare-initial-release/schema.json index ee19d4516a6333..47ed8748844ec5 100644 --- a/tools/generators/release-package/schema.json +++ b/tools/generators/prepare-initial-release/schema.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/schema", "cli": "nx", - "id": "release-package", + "id": "prepare-initial-release", "type": "object", "properties": { "project": { @@ -15,18 +15,18 @@ }, "phase": { "type": "string", - "description": "Phase of npm release life cycle for monorepo package", + "description": "Phase of npm release life cycle for fluent v9 core package", "x-prompt": { - "message": "Which phase of release life cycle you wanna trigger?", + "message": "Which initial phase of release life cycle you wanna trigger?", "type": "list", "items": [ { - "value": "initial", - "label": "initial - first release of preview package" + "value": "preview", + "label": "preview - prepare 1st release for preview phase (shipping as -preview)" }, { "value": "stable", - "label": "stable - propagating package from preview to stable (shipping from react-components suite)" + "label": "stable - prepare 1st release for stable phase (shipping from react-components suite)" } ] } diff --git a/tools/generators/release-package/schema.ts b/tools/generators/prepare-initial-release/schema.ts similarity index 78% rename from tools/generators/release-package/schema.ts rename to tools/generators/prepare-initial-release/schema.ts index 2f65f9e706c4d6..30d61ef1283205 100644 --- a/tools/generators/release-package/schema.ts +++ b/tools/generators/prepare-initial-release/schema.ts @@ -11,7 +11,7 @@ export interface ReleasePackageGeneratorSchema { */ project: string; /** - * Phase of release life cycle for our packages + * Phase of npm release life cycle for fluent v9 core package */ - phase: 'initial' | 'stable'; + phase: 'preview' | 'stable'; } diff --git a/tools/generators/release-package/README.md b/tools/generators/release-package/README.md deleted file mode 100644 index a6bec6b4d1d3e0..00000000000000 --- a/tools/generators/release-package/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# release-package - -Workspace Generator which automates release process stages for @fluentui (v9) package. - - - -- [Usage](#usage) - - [Examples](#examples) -- [Options](#options) - - [`project`](#project) - - [`phase`](#phase) - - - -## Usage - -```sh -yarn nx workspace-generator release-package ... -``` - -Show what will be generated without writing to disk: - -```sh -yarn nx workspace-generator release-package --dry-run -``` - -### Examples - -```sh -yarn nx workspace-generator release-package -``` - -## Options - -#### `project` - -Type: `string` - -Library name to to be released. - -#### `phase` - -Type: `initial` | 'stable' - -Phase of npm release life cycle for monorepo package From 51400bc3a4a10e2fde24defd30465ba663f3c7af Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Mon, 17 Jul 2023 13:51:08 +0200 Subject: [PATCH 8/8] fixup! refactor(tools): rename release-package to prepare-initial-release and add release cycle graph docs --- tools/generators/prepare-initial-release/index.spec.ts | 4 ++-- tools/generators/prepare-initial-release/index.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/generators/prepare-initial-release/index.spec.ts b/tools/generators/prepare-initial-release/index.spec.ts index 48571591165912..abcd4ec7a5cdce 100644 --- a/tools/generators/prepare-initial-release/index.spec.ts +++ b/tools/generators/prepare-initial-release/index.spec.ts @@ -82,7 +82,7 @@ describe('prepare-initial-release generator', () => { }); describe(`--phase`, () => { - describe(`initial`, () => { + describe(`preview`, () => { it(`should prepare preview package for initial release`, async () => { const utils = { project: createProject(tree, 'react-one-preview', { @@ -100,7 +100,7 @@ describe('prepare-initial-release generator', () => { }), }; - const sideEffects = await generator(tree, { project: '@proj/react-one-preview', phase: 'initial' }); + const sideEffects = await generator(tree, { project: '@proj/react-one-preview', phase: 'preview' }); expect(utils.project.pkgJson()).toMatchInlineSnapshot(` Object { diff --git a/tools/generators/prepare-initial-release/index.ts b/tools/generators/prepare-initial-release/index.ts index 6bce5d915bea95..1ce0425b0b100c 100644 --- a/tools/generators/prepare-initial-release/index.ts +++ b/tools/generators/prepare-initial-release/index.ts @@ -33,7 +33,7 @@ export default async function (tree: Tree, schema: ReleasePackageGeneratorSchema const tasks: Array<(tree: Tree) => void> = []; - if (options.phase === 'initial') { + if (options.phase === 'preview') { tasks.push(initialRelease(tree, options)); } if (options.phase === 'stable') {