From e6a544a6666664dedde384e88ec6abdb766df343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CJamesHenry=E2=80=9D?= Date: Wed, 29 Nov 2023 21:28:07 +0400 Subject: [PATCH 1/4] fix(core): various release fixes --- packages/nx/changelog-renderer/index.spec.ts | 44 ++++++++++++++++-- packages/nx/changelog-renderer/index.ts | 21 ++++++--- .../nx/src/command-line/release/changelog.ts | 46 +++++++++++-------- .../nx/src/command-line/release/utils/git.ts | 1 - .../src/command-line/release/utils/github.ts | 17 ++++++- .../release/utils/resolve-semver-specifier.ts | 28 ++--------- .../src/command-line/release/utils/shared.ts | 34 +++++++++++++- 7 files changed, 135 insertions(+), 56 deletions(-) diff --git a/packages/nx/changelog-renderer/index.spec.ts b/packages/nx/changelog-renderer/index.spec.ts index 8c6d3728c5d51..7b3beb6df91f9 100644 --- a/packages/nx/changelog-renderer/index.spec.ts +++ b/packages/nx/changelog-renderer/index.spec.ts @@ -1,7 +1,33 @@ import type { GitCommit } from '../src/command-line/release/utils/git'; import defaultChangelogRenderer from './index'; +jest.mock('../src/project-graph/file-map-utils', () => ({ + createFileMapUsingProjectGraph: jest.fn().mockImplementation(() => { + return Promise.resolve({ + allWorkspaceFiles: [], + fileMap: { + nonProjectFiles: [], + projectFileMap: { + 'pkg-a': [ + { + file: 'packages/pkg-a/src/index.ts', + }, + ], + 'pkg-b': [ + { + file: 'packages/pkg-b/src/index.ts', + }, + ], + }, + }, + }); + }), +})); + describe('defaultChangelogRenderer()', () => { + const projectGraph = { + nodes: {}, + } as any; const commits: GitCommit[] = [ { message: 'fix: all packages fixed', @@ -27,7 +53,10 @@ describe('defaultChangelogRenderer()', () => { }, ], isBreaking: false, - affectedFiles: [], + affectedFiles: [ + 'packages/pkg-a/src/index.ts', + 'packages/pkg-b/src/index.ts', + ], }, { message: 'feat(pkg-b): and another new capability', @@ -53,7 +82,7 @@ describe('defaultChangelogRenderer()', () => { }, ], isBreaking: false, - affectedFiles: [], + affectedFiles: ['packages/pkg-b/src/index.ts'], }, { message: 'feat(pkg-a): new hotness', @@ -79,7 +108,7 @@ describe('defaultChangelogRenderer()', () => { }, ], isBreaking: false, - affectedFiles: [], + affectedFiles: ['packages/pkg-a/src/index.ts'], }, { message: 'feat(pkg-b): brand new thing', @@ -105,7 +134,7 @@ describe('defaultChangelogRenderer()', () => { }, ], isBreaking: false, - affectedFiles: [], + affectedFiles: ['packages/pkg-b/src/index.ts'], }, { message: 'fix(pkg-a): squashing bugs', @@ -131,13 +160,14 @@ describe('defaultChangelogRenderer()', () => { }, ], isBreaking: false, - affectedFiles: [], + affectedFiles: ['packages/pkg-a/src/index.ts'], }, ]; describe('workspaceChangelog', () => { it('should generate markdown for all projects by organizing commits by type, then grouped by scope within the type (sorted alphabetically), then chronologically within the scope group', async () => { const markdown = await defaultChangelogRenderer({ + projectGraph, commits, releaseVersion: 'v1.1.0', project: null, @@ -169,6 +199,7 @@ describe('defaultChangelogRenderer()', () => { it('should not generate a Thank You section when changelogRenderOptions.includeAuthors is false', async () => { const markdown = await defaultChangelogRenderer({ + projectGraph, commits, releaseVersion: 'v1.1.0', project: null, @@ -198,6 +229,7 @@ describe('defaultChangelogRenderer()', () => { describe('project level configs', () => { it('should generate markdown for the given project by organizing commits by type, then chronologically', async () => { const otherOpts = { + projectGraph, commits, releaseVersion: 'v1.1.0', entryWhenNoChanges: false as const, @@ -288,6 +320,7 @@ describe('defaultChangelogRenderer()', () => { describe('entryWhenNoChanges', () => { it('should respect the entryWhenNoChanges option for the workspace changelog', async () => { const otherOpts = { + projectGraph, commits: [], releaseVersion: 'v1.1.0', project: null, // workspace changelog @@ -317,6 +350,7 @@ describe('defaultChangelogRenderer()', () => { it('should respect the entryWhenNoChanges option for project changelogs', async () => { const otherOpts = { + projectGraph, commits: [], releaseVersion: 'v1.1.0', project: 'pkg-a', diff --git a/packages/nx/changelog-renderer/index.ts b/packages/nx/changelog-renderer/index.ts index 63bdd21b1da00..85f9743344801 100644 --- a/packages/nx/changelog-renderer/index.ts +++ b/packages/nx/changelog-renderer/index.ts @@ -3,6 +3,8 @@ import { RepoSlug, formatReferences, } from '../src/command-line/release/utils/github'; +import { getCommitsRelevantToProjects } from '../src/command-line/release/utils/shared'; +import type { ProjectGraph } from '../src/config/project-graph'; // axios types and values don't seem to match import _axios = require('axios'); @@ -19,6 +21,7 @@ export type ChangelogRenderOptions = Record; * and returns a string, or a Promise of a string of changelog contents (usually markdown). * * @param {Object} config The configuration object for the ChangelogRenderer + * @param {ProjectGraph} config.projectGraph The project graph for the workspace * @param {GitCommit[]} config.commits The collection of extracted commits to generate a changelog for * @param {string} config.releaseVersion The version that is being released * @param {string | null} config.project The name of specific project to generate a changelog for, or `null` if the overall workspace changelog @@ -26,6 +29,7 @@ export type ChangelogRenderOptions = Record; * @param {ChangelogRenderOptions} config.changelogRenderOptions The options specific to the ChangelogRenderer implementation */ export type ChangelogRenderer = (config: { + projectGraph: ProjectGraph; commits: GitCommit[]; releaseVersion: string; project: string | null; @@ -51,6 +55,7 @@ export interface DefaultChangelogRenderOptions extends ChangelogRenderOptions { * from the given commits and other metadata. */ const defaultChangelogRenderer: ChangelogRenderer = async ({ + projectGraph, commits, releaseVersion, project, @@ -129,13 +134,14 @@ const defaultChangelogRenderer: ChangelogRenderer = async ({ } } else { // project level changelog - const scopeGroups = groupBy(commits, 'scope'); - - // Treat unscoped commits as "global", and therefore also relevant to include in the project level changelog - const unscopedCommits = scopeGroups[''] || []; + const relevantCommits = await getCommitsRelevantToProjects( + projectGraph, + commits, + [project] + ); - // Generating for a named project, but that project has no changes in the current set of commits, exit early - if (!scopeGroups[project] && unscopedCommits.length === 0) { + // Generating for a named project, but that project has no relevant changes in the current set of commits, exit early + if (relevantCommits.length === 0) { if (entryWhenNoChanges) { markdownLines.push( '', @@ -149,7 +155,8 @@ const defaultChangelogRenderer: ChangelogRenderer = async ({ markdownLines.push('', `## ${releaseVersion}`, ''); const typeGroups = groupBy( - [...(scopeGroups[project] || []), ...unscopedCommits], + // Sort the relevant commits to have the unscoped commits first, before grouping by type + relevantCommits.sort((a, b) => (b.scope ? 1 : 0) - (a.scope ? 1 : 0)), 'type' ); for (const type of Object.keys(commitTypes)) { diff --git a/packages/nx/src/command-line/release/changelog.ts b/packages/nx/src/command-line/release/changelog.ts index 9c9e7d887988d..008016e258827 100644 --- a/packages/nx/src/command-line/release/changelog.ts +++ b/packages/nx/src/command-line/release/changelog.ts @@ -4,7 +4,10 @@ import { valid } from 'semver'; import { dirSync } from 'tmp'; import type { ChangelogRenderer } from '../../../changelog-renderer'; import { readNxJson } from '../../config/nx-json'; -import { ProjectGraphProjectNode } from '../../config/project-graph'; +import { + ProjectGraph, + ProjectGraphProjectNode, +} from '../../config/project-graph'; import { FsTree, Tree } from '../../generators/tree'; import { registerTsProject } from '../../plugins/js/utils/register'; import { createProjectGraphAsync } from '../../project-graph/project-graph'; @@ -188,6 +191,7 @@ export async function releaseChangelog( await generateChangelogForWorkspace( tree, args, + projectGraph, nxReleaseConfig, workspaceChangelogVersion, commits, @@ -206,6 +210,7 @@ export async function releaseChangelog( await generateChangelogForProjects( tree, args, + projectGraph, commits, projectsVersionData, postGitTasks, @@ -236,6 +241,7 @@ export async function releaseChangelog( await generateChangelogForProjects( tree, args, + projectGraph, commits, projectsVersionData, postGitTasks, @@ -404,6 +410,7 @@ function resolveChangelogRenderer( async function generateChangelogForWorkspace( tree: Tree, args: ChangelogOptions, + projectGraph: ProjectGraph, nxReleaseConfig: NxReleaseConfig, workspaceChangelogVersion: (string | null) | undefined, commits: GitCommit[], @@ -448,11 +455,11 @@ async function generateChangelogForWorkspace( releaseTagPattern: nxReleaseConfig.releaseTagPattern, }); - // We are either creating/previewing a changelog file, a Github release, or both + // We are either creating/previewing a changelog file, a GitHub release, or both let logTitle = dryRun ? 'Previewing a' : 'Generating a'; switch (true) { case interpolatedTreePath && config.createRelease === 'github': - logTitle += ` Github release and an entry in ${interpolatedTreePath} for ${chalk.white( + logTitle += ` GitHub release and an entry in ${interpolatedTreePath} for ${chalk.white( releaseVersion.gitTag )}`; break; @@ -462,7 +469,7 @@ async function generateChangelogForWorkspace( )}`; break; case config.createRelease === 'github': - logTitle += ` Github release for ${chalk.white(releaseVersion.gitTag)}`; + logTitle += ` GitHub release for ${chalk.white(releaseVersion.gitTag)}`; } output.log({ @@ -475,6 +482,7 @@ async function generateChangelogForWorkspace( : undefined; let contents = await changelogRenderer({ + projectGraph, commits, releaseVersion: releaseVersion.rawVersion, project: null, @@ -501,7 +509,7 @@ async function generateChangelogForWorkspace( /** * The exact logic we use for printing the summary/diff to the user is dependent upon whether they are creating - * a changelog file, a Github release, or both. + * a changelog file, a GitHub release, or both. */ let printSummary = () => {}; const noDiffInChangelogMessage = chalk.yellow( @@ -544,9 +552,9 @@ async function generateChangelogForWorkspace( if (config.createRelease === 'github') { if (!githubRepoSlug) { output.error({ - title: `Unable to create a Github release because the Github repo slug could not be determined.`, + title: `Unable to create a GitHub release because the GitHub repo slug could not be determined.`, bodyLines: [ - `Please ensure you have a valid Github remote configured. You can run \`git remote -v\` to list your current remotes.`, + `Please ensure you have a valid GitHub remote configured. You can run \`git remote -v\` to list your current remotes.`, ], }); process.exit(1); @@ -565,11 +573,11 @@ async function generateChangelogForWorkspace( releaseVersion.gitTag ); } catch (err) { - if (err.response?.status === 401) { + if (err.response?.status === 401 || !token) { output.error({ - title: `Unable to resolve data via the Github API. You can use any of the following options to resolve this:`, + title: `Unable to resolve data via the GitHub API. You can use any of the following options to resolve this:`, bodyLines: [ - '- Set the `GITHUB_TOKEN` or `GH_TOKEN` environment variable to a valid Github token with `repo` scope', + '- Set the `GITHUB_TOKEN` or `GH_TOKEN` environment variable to a valid GitHub token with `repo` scope', '- Have an active session via the official gh CLI tool (https://cli.github.com) in your current terminal', ], }); @@ -640,6 +648,7 @@ async function generateChangelogForWorkspace( async function generateChangelogForProjects( tree: Tree, args: ChangelogOptions, + projectGraph: ProjectGraph, commits: GitCommit[], projectsVersionData: VersionData, postGitTasks: PostGitTask[], @@ -684,11 +693,11 @@ async function generateChangelogForProjects( projectName: project.name, }); - // We are either creating/previewing a changelog file, a Github release, or both + // We are either creating/previewing a changelog file, a GitHub release, or both let logTitle = dryRun ? 'Previewing a' : 'Generating a'; switch (true) { case interpolatedTreePath && config.createRelease === 'github': - logTitle += ` Github release and an entry in ${interpolatedTreePath} for ${chalk.white( + logTitle += ` GitHub release and an entry in ${interpolatedTreePath} for ${chalk.white( releaseVersion.gitTag )}`; break; @@ -698,7 +707,7 @@ async function generateChangelogForProjects( )}`; break; case config.createRelease === 'github': - logTitle += ` Github release for ${chalk.white(releaseVersion.gitTag)}`; + logTitle += ` GitHub release for ${chalk.white(releaseVersion.gitTag)}`; } output.log({ @@ -711,6 +720,7 @@ async function generateChangelogForProjects( : undefined; let contents = await changelogRenderer({ + projectGraph, commits, releaseVersion: releaseVersion.rawVersion, project: project.name, @@ -744,7 +754,7 @@ async function generateChangelogForProjects( /** * The exact logic we use for printing the summary/diff to the user is dependent upon whether they are creating - * a changelog file, a Github release, or both. + * a changelog file, a GitHub release, or both. */ let printSummary = () => {}; const noDiffInChangelogMessage = chalk.yellow( @@ -794,9 +804,9 @@ async function generateChangelogForProjects( if (config.createRelease === 'github') { if (!githubRepoSlug) { output.error({ - title: `Unable to create a Github release because the Github repo slug could not be determined.`, + title: `Unable to create a GitHub release because the GitHub repo slug could not be determined.`, bodyLines: [ - `Please ensure you have a valid Github remote configured. You can run \`git remote -v\` to list your current remotes.`, + `Please ensure you have a valid GitHub remote configured. You can run \`git remote -v\` to list your current remotes.`, ], }); process.exit(1); @@ -817,9 +827,9 @@ async function generateChangelogForProjects( } catch (err) { if (err.response?.status === 401) { output.error({ - title: `Unable to resolve data via the Github API. You can use any of the following options to resolve this:`, + title: `Unable to resolve data via the GitHub API. You can use any of the following options to resolve this:`, bodyLines: [ - '- Set the `GITHUB_TOKEN` or `GH_TOKEN` environment variable to a valid Github token with `repo` scope', + '- Set the `GITHUB_TOKEN` or `GH_TOKEN` environment variable to a valid GitHub token with `repo` scope', '- Have an active session via the official gh CLI tool (https://cli.github.com) in your current terminal', ], }); diff --git a/packages/nx/src/command-line/release/utils/git.ts b/packages/nx/src/command-line/release/utils/git.ts index 1f2511607052a..fbcd854907b17 100644 --- a/packages/nx/src/command-line/release/utils/git.ts +++ b/packages/nx/src/command-line/release/utils/git.ts @@ -3,7 +3,6 @@ * https://github.com/unjs/changelogen */ import { interpolate } from '../../../tasks-runner/utils'; -import { output } from '../../../utils/output'; import { execCommand } from './exec-command'; export interface GitCommitAuthor { diff --git a/packages/nx/src/command-line/release/utils/github.ts b/packages/nx/src/command-line/release/utils/github.ts index 34bd9252b59d0..1463c19730ae2 100644 --- a/packages/nx/src/command-line/release/utils/github.ts +++ b/packages/nx/src/command-line/release/utils/github.ts @@ -150,7 +150,22 @@ export async function resolveGithubToken(): Promise { const yamlContents = await fsp.readFile(ghCLIPath, 'utf8'); const { load } = require('@zkochan/js-yaml'); const ghCLIConfig = load(yamlContents); - return ghCLIConfig['github.com'].oauth_token; + if (ghCLIConfig['github.com']) { + // Web based session (the token is already embedded in the config) + if (ghCLIConfig['github.com'].oauth_token) { + return ghCLIConfig['github.com'].oauth_token; + } + // SSH based session (we need to dynamically resolve a token using the CLI) + if ( + ghCLIConfig['github.com'].user && + ghCLIConfig['github.com'].git_protocol === 'ssh' + ) { + return execSync(`gh auth token`, { + encoding: 'utf8', + stdio: 'pipe', + }).trim(); + } + } } return null; } diff --git a/packages/nx/src/command-line/release/utils/resolve-semver-specifier.ts b/packages/nx/src/command-line/release/utils/resolve-semver-specifier.ts index b952c63d756b6..946cca4f45be3 100644 --- a/packages/nx/src/command-line/release/utils/resolve-semver-specifier.ts +++ b/packages/nx/src/command-line/release/utils/resolve-semver-specifier.ts @@ -4,6 +4,7 @@ import { ProjectGraph } from '../../../config/project-graph'; import { createFileMapUsingProjectGraph } from '../../../project-graph/file-map-utils'; import { getGitDiff, parseCommits } from './git'; import { ConventionalCommitsConfig, determineSemverChange } from './semver'; +import { getCommitsRelevantToProjects } from './shared'; // TODO: Extract config to nx.json configuration when adding changelog customization const CONVENTIONAL_COMMITS_CONFIG: ConventionalCommitsConfig = { @@ -24,30 +25,11 @@ export async function resolveSemverSpecifierFromConventionalCommits( ): Promise { const commits = await getGitDiff(from); const parsedCommits = parseCommits(commits); - const { fileMap } = await createFileMapUsingProjectGraph(projectGraph); - const filesInReleaseGroup = new Set( - projectNames.reduce( - (files, p) => [...files, ...fileMap.projectFileMap[p].map((f) => f.file)], - [] as string[] - ) + const relevantCommits = await getCommitsRelevantToProjects( + projectGraph, + parsedCommits, + projectNames ); - - /** - * The relevant commits are those that either: - * - touch project files which are contained within the release group directly - * - touch non-project files and the commit is not scoped - */ - const relevantCommits = parsedCommits.filter((c) => - c.affectedFiles.some( - (f) => - filesInReleaseGroup.has(f) || - (!c.scope && - fileMap.nonProjectFiles.some( - (nonProjectFile) => nonProjectFile.file === f - )) - ) - ); - return determineSemverChange(relevantCommits, CONVENTIONAL_COMMITS_CONFIG); } diff --git a/packages/nx/src/command-line/release/utils/shared.ts b/packages/nx/src/command-line/release/utils/shared.ts index ad88af5de19e5..ab3843d87b54a 100644 --- a/packages/nx/src/command-line/release/utils/shared.ts +++ b/packages/nx/src/command-line/release/utils/shared.ts @@ -1,8 +1,10 @@ import { prerelease } from 'semver'; +import { ProjectGraph } from '../../../config/project-graph'; +import { createFileMapUsingProjectGraph } from '../../../project-graph/file-map-utils'; import { interpolate } from '../../../tasks-runner/utils'; import { output } from '../../../utils/output'; import type { ReleaseGroupWithName } from '../config/filter-release-groups'; -import { gitAdd, gitCommit } from './git'; +import { GitCommit, gitAdd, gitCommit } from './git'; export type VersionData = Record< string, @@ -215,3 +217,33 @@ export function handleDuplicateGitTags(gitTagValues: string[]): void { process.exit(1); } } + +export async function getCommitsRelevantToProjects( + projectGraph: ProjectGraph, + commits: GitCommit[], + projects: string[] +): Promise { + const { fileMap } = await createFileMapUsingProjectGraph(projectGraph); + const filesInReleaseGroup = new Set( + projects.reduce( + (files, p) => [...files, ...fileMap.projectFileMap[p].map((f) => f.file)], + [] as string[] + ) + ); + + /** + * The relevant commits are those that either: + * - touch project files which are contained within the list of projects directly + * - touch non-project files and the commit is not scoped + */ + return commits.filter((c) => + c.affectedFiles.some( + (f) => + filesInReleaseGroup.has(f) || + (!c.scope && + fileMap.nonProjectFiles.some( + (nonProjectFile) => nonProjectFile.file === f + )) + ) + ); +} From 8d79d13419e8064dcd2a2eeec053b04b9dab9e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CJamesHenry=E2=80=9D?= Date: Wed, 29 Nov 2023 21:35:31 +0400 Subject: [PATCH 2/4] chore(core): remove tmp debug --- packages/nx/src/command-line/release/changelog.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nx/src/command-line/release/changelog.ts b/packages/nx/src/command-line/release/changelog.ts index 008016e258827..a8f338cdf2286 100644 --- a/packages/nx/src/command-line/release/changelog.ts +++ b/packages/nx/src/command-line/release/changelog.ts @@ -573,7 +573,7 @@ async function generateChangelogForWorkspace( releaseVersion.gitTag ); } catch (err) { - if (err.response?.status === 401 || !token) { + if (err.response?.status === 401) { output.error({ title: `Unable to resolve data via the GitHub API. You can use any of the following options to resolve this:`, bodyLines: [ From 3498eeb4025f7eeeda53b47904160eb8a5698220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CJamesHenry=E2=80=9D?= Date: Wed, 29 Nov 2023 21:59:47 +0400 Subject: [PATCH 3/4] chore(js): update snapshot in unit test --- .../generators/library/__snapshots__/library.spec.ts.snap | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/js/src/generators/library/__snapshots__/library.spec.ts.snap b/packages/js/src/generators/library/__snapshots__/library.spec.ts.snap index 794f23a83bce0..e85c88435fdea 100644 --- a/packages/js/src/generators/library/__snapshots__/library.spec.ts.snap +++ b/packages/js/src/generators/library/__snapshots__/library.spec.ts.snap @@ -8,6 +8,7 @@ import * as path from 'path'; import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; export default defineConfig({ + root: __dirname, cacheDir: '../node_modules/.vite/my-lib', plugins: [ @@ -27,6 +28,7 @@ export default defineConfig({ // Configuration for building your library. // See: https://vitejs.dev/guide/build.html#library-mode build: { + outDir: '../dist/my-lib', lib: { // Could also be a dictionary or array of multiple entry points. entry: 'src/index.ts', @@ -49,6 +51,11 @@ export default defineConfig({ }, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + + coverage: { + reportsDirectory: '../coverage/my-lib', + provider: 'v8', + }, }, }); " From 48c31e04e91e7529c97ccfe7c5f9e2a3192b7924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CJamesHenry=E2=80=9D?= Date: Wed, 29 Nov 2023 22:27:50 +0400 Subject: [PATCH 4/4] chore(core): fix e2e snapshots --- e2e/release/src/release.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/release/src/release.test.ts b/e2e/release/src/release.test.ts index 0197314069790..ab85a180d464f 100644 --- a/e2e/release/src/release.test.ts +++ b/e2e/release/src/release.test.ts @@ -679,7 +679,7 @@ describe('nx release', () => { - > NX Previewing a Github release and an entry in {project-name}/CHANGELOG.md for v1000.0.0-next.0 + > NX Previewing a GitHub release and an entry in {project-name}/CHANGELOG.md for v1000.0.0-next.0 + ## 1000.0.0-next.0 @@ -690,7 +690,7 @@ describe('nx release', () => { + - an awesome new feature ([{COMMIT_SHA}](https://github.com/nrwl/fake-repo/commit/{COMMIT_SHA})) - > NX Previewing a Github release and an entry in {project-name}/CHANGELOG.md for v1000.0.0-next.0 + > NX Previewing a GitHub release and an entry in {project-name}/CHANGELOG.md for v1000.0.0-next.0 + ## 1000.0.0-next.0 @@ -701,7 +701,7 @@ describe('nx release', () => { + - an awesome new feature ([{COMMIT_SHA}](https://github.com/nrwl/fake-repo/commit/{COMMIT_SHA})) - > NX Previewing a Github release and an entry in {project-name}/CHANGELOG.md for v1000.0.0-next.0 + > NX Previewing a GitHub release and an entry in {project-name}/CHANGELOG.md for v1000.0.0-next.0 + ## 1000.0.0-next.0