Skip to content

Commit 3037cd4

Browse files
committed
more
1 parent a9e8d5f commit 3037cd4

File tree

6 files changed

+126
-15
lines changed

6 files changed

+126
-15
lines changed

release/core/promote/index.ts

+99-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { GIT_TAG, getAllPackagesForGitTag, getGitState } from '../../utils/git';
44
import { printHelpDocs } from '../../help/docs';
55
import { Package } from '../../utils/package';
66
import { SEMVER_VERSION } from '../../utils/channel';
7+
import chalk from 'chalk';
8+
import { colorName } from '../publish/steps/print-strategy';
9+
import { exec } from '../../utils/cmd';
10+
import { question } from '../publish/steps/confirm-strategy';
711

812
export async function promoteToLTS(args: string[]) {
913
// get user supplied config
@@ -16,14 +20,11 @@ export async function promoteToLTS(args: string[]) {
1620

1721
const packages = await getAllPackagesForGitTag(gitTag);
1822
const versionsToPromote = getPublicPackageVersions(packages);
19-
const dryRun = config.full.get('dry_run') as boolean;
2023

21-
console.log(config.full);
22-
console.log(gitTag);
23-
console.log(versionsToPromote);
24+
await updateTags(config.full, versionsToPromote);
2425
}
2526

26-
export async function getPublicPackageVersions(packages: Map<string, Package>): Promise<Map<string, SEMVER_VERSION>> {
27+
export function getPublicPackageVersions(packages: Map<string, Package>): Map<string, SEMVER_VERSION> {
2728
const publicPackages = new Map<string, SEMVER_VERSION>();
2829
packages.forEach((pkg, name) => {
2930
if (!pkg.pkgData.private) {
@@ -32,3 +33,96 @@ export async function getPublicPackageVersions(packages: Map<string, Package>):
3233
});
3334
return publicPackages;
3435
}
36+
37+
export async function updateTags(
38+
config: Map<string, string | number | boolean | null>,
39+
packages: Map<string, SEMVER_VERSION>
40+
) {
41+
const distTag = config.get('tag') as string;
42+
const NODE_AUTH_TOKEN = process.env.NODE_AUTH_TOKEN;
43+
const CI = process.env.CI;
44+
let token: string | undefined;
45+
46+
// allow OTP token usage locally
47+
if (!NODE_AUTH_TOKEN) {
48+
if (CI) {
49+
console.log(
50+
chalk.red(
51+
'🚫 NODE_AUTH_TOKEN not found in ENV. NODE_AUTH_TOKEN is required in ENV to publish from CI. Exiting...'
52+
)
53+
);
54+
process.exit(1);
55+
}
56+
token = await getOTPToken(distTag);
57+
} else {
58+
if (!CI) {
59+
const result = await question(
60+
`\n${chalk.cyan('NODE_AUTH_TOKEN')} found in ENV.\nPublish ${config.get('increment')} release in ${config.get(
61+
'channel'
62+
)} channel to the ${config.get('tag')} tag on the npm registry? ${chalk.yellow('[y/n]')}:`
63+
);
64+
const input = result.trim().toLowerCase();
65+
if (input !== 'y' && input !== 'yes') {
66+
console.log(chalk.red('🚫 Publishing not confirmed. Exiting...'));
67+
process.exit(1);
68+
}
69+
}
70+
}
71+
72+
const dryRun = config.get('dry_run') as boolean;
73+
74+
for (const [pkgName, version] of packages) {
75+
token = await updateDistTag(pkgName, version, distTag, dryRun, token);
76+
console.log(chalk.green(`\t✅ ${colorName(pkgName)} ${chalk.green(version)} => ${chalk.magenta(distTag)}`));
77+
}
78+
79+
console.log(
80+
`✅ ` + chalk.cyan(`Moved ${chalk.greenBright(packages.size)} 📦 packages to ${chalk.magenta(distTag)} channel`)
81+
);
82+
}
83+
84+
async function getOTPToken(distTag: string, reprompt?: boolean) {
85+
const prompt = reprompt
86+
? `The provided OTP token has expired. Please enter a new OTP token: `
87+
: `\nℹ️ ${chalk.cyan(
88+
'NODE_AUTH_TOKEN'
89+
)} not found in ENV.\n\nConfiguring NODE_AUTH_TOKEN is the preferred mechanism by which to publish. Alternatively you may continue using an OTP token.\n\nUpdating ${distTag} tag on the npm registry.\n\nEnter your OTP token: `;
90+
91+
let token = await question(prompt);
92+
93+
return token.trim();
94+
}
95+
96+
async function updateDistTag(
97+
pkg: string,
98+
version: string,
99+
distTag: string,
100+
dryRun: boolean,
101+
otp?: string
102+
): Promise<string | undefined> {
103+
let cmd = `npm dist-tag add ${pkg}@${version} ${distTag}`;
104+
105+
if (otp) {
106+
cmd += ` --otp=${otp}`;
107+
}
108+
109+
if (dryRun) {
110+
cmd += ' --dry-run';
111+
}
112+
113+
try {
114+
await exec({ cmd, condense: true });
115+
} catch (e) {
116+
if (!otp || !(e instanceof Error)) {
117+
throw e;
118+
}
119+
if (e.message.includes('E401') || e.message.includes('EOTP')) {
120+
otp = await getOTPToken(distTag, true);
121+
return updateDistTag(pkg, version, distTag, dryRun, otp);
122+
} else {
123+
throw e;
124+
}
125+
}
126+
127+
return otp;
128+
}

release/core/publish/steps/bump-versions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export async function bumpAllPackages(
3333
await pkg.file.write();
3434
}
3535

36-
const willPublish: boolean = config.get('pack') && config.get('publish');
36+
const willPublish: boolean = Boolean(config.get('pack') && config.get('publish'));
3737
const dryRun = config.get('dry_run') as boolean;
3838
const nextVersion = strategy.get('root')?.toVersion;
3939
let commitCommand = `git commit -am "Release v${nextVersion}"`;

release/core/publish/steps/publish-packages.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export async function publishPackages(
4646
console.log(`✅ ` + chalk.cyan(`published ${chalk.greenBright(strategy.size)} 📦 packages to npm`));
4747
}
4848

49-
async function getOTPToken(config: Map<string, string | number | boolean | null>, reprompt?: boolean) {
49+
export async function getOTPToken(config: Map<string, string | number | boolean | null>, reprompt?: boolean) {
5050
const prompt = reprompt
5151
? `The provided OTP token has expired. Please enter a new OTP token: `
5252
: `\nℹ️ ${chalk.cyan(

release/utils/flags-config.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,13 @@ export const promote_flags_config: FlagConfig = merge(
249249
}
250250
},
251251
validate: async (value: unknown, options: Map<string, string | number | boolean | null>) => {
252-
const version = options.get('version') as SEMVER_VERSION;
252+
let version = options.get('version') as SEMVER_VERSION;
253253
const existing = await getPublishedChannelInfo(options);
254254

255+
if (!version) {
256+
version = (await getPublishedChannelInfo(options)).latest;
257+
}
258+
255259
if (value !== 'lts') {
256260
// older lts channels should match lts-<major>-<minor>
257261
if (typeof value !== 'string' || !value.startsWith('lts-')) {
@@ -268,6 +272,11 @@ export const promote_flags_config: FlagConfig = merge(
268272
if (existing[value] === version) {
269273
throw new Error(`Version ${version} is already published to ${value}`);
270274
}
275+
276+
const current = existing[value];
277+
if (current && semver.lt(version, current)) {
278+
throw new Error(`Version ${version} is less than the latest version ${current}`);
279+
}
271280
},
272281
},
273282
}

release/utils/git.ts

+13-5
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,18 @@ export async function getGitState(options: Map<string, boolean | string | number
154154
}
155155

156156
export async function getAllPackagesForGitTag(tag: GIT_TAG): Promise<Map<string, Package>> {
157-
await exec(['mkdir', '-p', `./tmp/${tag}`]);
158-
await exec({ cmd: ['git', 'checkout', tag], env: { ...process.env, GIT_WORK_TREE: `./tmp/${tag}` } });
157+
const relativeTmpDir = `./tmp/${tag}`;
158+
await exec(['mkdir', '-p', relativeTmpDir]);
159+
await exec({ cmd: ['sh', '-c', `git archive ${tag} | tar -xC ${relativeTmpDir}`] });
159160

160-
const tmpDir = path.join(process.cwd(), `./tmp/${tag}`);
161-
const strategy = await loadStrategy(tmpDir);
162-
return gatherPackages(strategy.config, tmpDir);
161+
const tmpDir = path.join(process.cwd(), relativeTmpDir);
162+
try {
163+
const strategy = await loadStrategy(tmpDir);
164+
return gatherPackages(strategy.config, tmpDir);
165+
} catch (e) {
166+
// if strategy does not exist we may be pre-strategy days
167+
// so we will just gather all packages from the packages directory
168+
169+
return gatherPackages({ packageRoots: ['packages/*'] }, tmpDir);
170+
}
163171
}

release/utils/package.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { JSONFile, getFile } from './json-file';
22
import { NPM_DIST_TAG, SEMVER_VERSION, STRATEGY_TYPE, TYPE_STRATEGY } from './channel';
33
import { Glob } from 'bun';
4-
4+
import path from 'path';
55
export class Package {
66
declare filePath: string;
77
declare file: JSONFile<PACKAGEJSON>;
@@ -104,7 +104,7 @@ export async function gatherPackages(config: STRATEGY['config'], cwd: string = p
104104

105105
// Scans the current working directory and each of its sub-directories recursively
106106
for await (const filePath of glob.scan(cwd)) {
107-
const file = getFile<PACKAGEJSON>(filePath);
107+
const file = getFile<PACKAGEJSON>(path.join(cwd, filePath));
108108
const pkgData = await file.read();
109109
packages.set(pkgData.name, new Package(filePath, file, pkgData));
110110
}

0 commit comments

Comments
 (0)