nx/scripts/nx-release.ts
2023-05-04 10:29:45 -04:00

267 lines
7.6 KiB
JavaScript
Executable File

#!/usr/bin/env node
import * as yargs from 'yargs';
import { execSync } from 'child_process';
import { existsSync, readFileSync, rmSync, writeFileSync } from 'fs';
import { URL } from 'url';
import { join } from 'path';
import { parse } from 'semver';
import * as version from '@lerna/version';
import * as publish from '@lerna/publish';
const lernaJsonPath = join(__dirname, '../lerna.json');
const originalLernaJson = readFileSync(lernaJsonPath);
function hideFromGitIndex(uncommittedFiles: string[]) {
execSync(`git update-index --assume-unchanged ${uncommittedFiles.join(' ')}`);
return () =>
execSync(
`git update-index --no-assume-unchanged ${uncommittedFiles.join(' ')}`
);
}
(async () => {
const options = parseArgs();
if (options.clearLocalRegistry) {
rmSync(join(__dirname, '../build/local-registry/storage'), {
recursive: true,
force: true,
});
}
const currentLatestVersion = execSync('npm view nx version')
.toString()
.trim();
const parsedVersion = parse(options.version);
const parsedCurrentLatestVersion = parse(currentLatestVersion);
const distTag =
parsedVersion?.prerelease.length > 0
? 'next'
: parsedVersion?.major < parsedCurrentLatestVersion.major
? 'previous'
: 'latest';
const buildCommand = 'pnpm build';
console.log(`> ${buildCommand}`);
execSync(buildCommand, {
stdio: [0, 1, 2],
});
if (options.local) {
updateLernaJsonVersion(currentLatestVersion);
}
if (options.local) {
// Force all projects to be not private
const projects = JSON.parse(
execSync('npx lerna list --json --all').toString()
);
for (const proj of projects) {
if (proj.private) {
console.log('Publishing private package locally:', proj.name);
const packageJsonPath = join(proj.location, 'package.json');
const original = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
writeFileSync(
packageJsonPath,
JSON.stringify({ ...original, private: false })
);
}
}
}
if (!options.local && process.env.NPM_TOKEN) {
// Delete all .node files that were built during the previous steps
// Always run before the artifacts step because we still need the .node files for native-packages
execSync('find ./build -name "*.node" -delete', {
stdio: [0, 1, 2],
});
execSync('npx nx run-many --target=artifacts', {
stdio: [0, 1, 2],
});
}
const versionOptions = {
bump: options.version ? options.version : undefined,
conventionalCommits: true,
conventionalPrerelease: options.tag === 'next',
preid: options.preid,
forcePublish: true,
createRelease: 'github',
tagVersionPrefix: '',
exact: true,
gitRemote: options.gitRemote,
gitTagVersion: !process.env.NPM_TOKEN,
message: 'chore(misc): publish %v',
loglevel: options.loglevel ?? 'info',
yes: !!process.env.NPM_TOKEN,
};
if (options.local) {
versionOptions.conventionalCommits = false;
delete versionOptions.createRelease;
versionOptions.gitTagVersion = false;
versionOptions.loglevel = options.loglevel ?? 'error';
versionOptions.yes = true;
versionOptions.bump = options.version ? options.version : 'minor';
}
if (options.local) {
/**
* Hide changes from Lerna
*/
const uncommittedFiles = execSync('git diff --name-only --relative HEAD .')
.toString()
.split('\n')
.filter((i) => i.length > 0)
.filter((f) => existsSync(f));
const unhideFromGitIndex = hideFromGitIndex(uncommittedFiles);
process.on('exit', unhideFromGitIndex);
process.on('SIGTERM', unhideFromGitIndex);
process.on('SIGINT', unhideFromGitIndex);
}
const publishOptions: Record<string, boolean | string | undefined> = {
gitReset: false,
distTag: distTag,
};
if (!options.local && !process.env.NPM_TOKEN) {
execSync('git status --ahead-behind');
await version(versionOptions);
console.log(
'Check github: https://github.com/nrwl/nx/actions/workflows/publish.yml'
);
} else if (!options.skipPublish) {
await publish({ ...versionOptions, ...publishOptions });
} else {
await version(versionOptions);
console.warn('Not Publishing because --dryRun was passed');
}
if (options.local) {
restoreOriginalLernaJson();
}
})();
function parseArgs() {
const parsedArgs = yargs
.scriptName('pnpm nx-release')
.wrap(144)
.strictOptions()
.version(false)
.command(
'$0 [version]',
'This script is for publishing Nx both locally and publically'
)
.option('skipPublish', {
type: 'boolean',
description: 'Skips the actual publishing for testing out versioning',
})
.option('clearLocalRegistry', {
type: 'boolean',
description:
'Clear existing versions in the local registry so that you can republish the same version',
default: true,
})
.option('local', {
type: 'boolean',
description: 'Publish Nx locally, not to actual NPM',
alias: 'l',
default: true,
})
.option('force', {
type: 'boolean',
description: "Don't use this unless you really know what it does",
hidden: true,
})
.positional('version', {
type: 'string',
description:
'The version to publish. This does not need to be passed and can be inferred.',
})
.option('gitRemote', {
type: 'string',
description:
'Alternate git remote name to publish tags to (useful for testing changelog)',
default: 'origin',
})
.option('loglevel', {
type: 'string',
description: 'Log Level',
choices: ['error', 'info', 'debug'],
})
.example(
'$0',
`By default, this will locally publish a minor version bump as latest. Great for local development. Most developers should only need this.`
)
.example(
'$0 --local false 2.3.4-beta.0',
`This will really publish a new version to npm as next.`
)
.example(
'$0 --local false 2.3.4',
`Given the current latest major version on npm is 2, this will really publish a new version to npm as latest.`
)
.example(
'$0 --local false 1.3.4-beta.0',
`Given the current latest major version on npm is 2, this will really publish a new version to npm as previous.`
)
.group(
['local', 'clearLocalRegistry'],
'Local Publishing Options for most developers'
)
.group(
['gitRemote', 'force'],
'Real Publishing Options for actually publishing to NPM'
)
.check((args) => {
const registry = getRegistry();
const registryIsLocalhost = registry.hostname === 'localhost';
if (!args.local) {
if (!process.env.GH_TOKEN) {
throw new Error('process.env.GH_TOKEN is not set');
}
if (!args.force && registryIsLocalhost) {
throw new Error(
'Registry is still set to localhost! Run "pnpm local-registry disable" or pass --force'
);
}
} else {
if (!args.force && !registryIsLocalhost) {
throw new Error('--local was passed and registry is not localhost');
}
}
return true;
})
.parseSync();
parsedArgs.tag ??= parsedArgs.local ? 'latest' : 'next';
return parsedArgs;
}
function updateLernaJsonVersion(version: string) {
const json = JSON.parse(readFileSync(lernaJsonPath).toString());
json.version = version;
writeFileSync(lernaJsonPath, JSON.stringify(json));
}
function restoreOriginalLernaJson() {
writeFileSync(lernaJsonPath, originalLernaJson);
}
function getRegistry() {
return new URL(execSync('npm config get registry').toString().trim());
}