Skip to content

Commit 1f4b80d

Browse files
Merge pull request #5854 from BitGo/DX-1201.prepareRelease.refactor
refactor(scripts): improve prepare-release script architecture
2 parents e8c3e36 + e18a427 commit 1f4b80d

File tree

3 files changed

+160
-117
lines changed

3 files changed

+160
-117
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@
121121
"update-dockerfile": "ts-node scripts/update-dockerfile.ts",
122122
"precommit": "lint-staged",
123123
"lint-fix": "lerna run lint --parallel -- --fix",
124-
"prepare-commit-msg": "node ./scripts/prepare-commit-msg.js"
124+
"prepare-commit-msg": "node ./scripts/prepare-commit-msg.js",
125+
"test:prepare-release": "mocha --require ts-node/register ./scripts/tests/prepareRelease/prepare-release-main.test.ts"
125126
},
126127
"dependencies": {
127128
"axios": "^1.8.2",

scripts/prepare-release.ts

Lines changed: 152 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,151 +1,192 @@
11
import * as assert from 'node:assert';
22
import { readFileSync, writeFileSync } from 'fs';
33
import * as path from 'path';
4-
import { inc } from 'semver';
4+
import { inc, lt } from 'semver';
5+
import * as yargs from 'yargs';
6+
import { hideBin } from 'yargs/helpers';
7+
58
import {
69
walk,
7-
getDistTagsForModuleLocations,
10+
getDistTagsForModules,
811
getLernaModules,
912
changeScopeInFile,
1013
setDependencyVersion,
14+
DistTags,
15+
LernaModule,
1116
} from './prepareRelease';
1217

13-
let lernaModules: string[] = [];
14-
let lernaModuleLocations: string[] = [];
15-
let TARGET_SCOPE = '@bitgo-beta';
16-
let filesChanged = 0;
17-
// Default to __dirname/.. but allow override via environment variable
18-
const ROOT_DIR = process.env.BITGO_PREPARE_RELEASE_ROOT_DIR || path.join(__dirname, '..');
19-
20-
async function setLernaModules(): Promise<void> {
21-
const modules = await getLernaModules();
22-
lernaModules = modules.map(({ name }) => name);
23-
lernaModuleLocations = modules.map(({ location }) => location);
24-
}
25-
26-
function replacePackageScopes() {
18+
function replacePackageScopes(rootDir: string, lernaModules: LernaModule[], targetScope: string): number {
19+
let filesChanged = 0;
2720
// replace all @bitgo packages & source code with alternate SCOPE
28-
const filePaths = [...walk(path.join(ROOT_DIR, 'modules')), ...walk(path.join(ROOT_DIR, 'webpack'))];
21+
const filePaths = [...walk(path.join(rootDir, 'modules')), ...walk(path.join(rootDir, 'webpack'))];
22+
const moduleNames = lernaModules.map(({ name }) => name);
23+
2924
filePaths.forEach((file) => {
30-
filesChanged += changeScopeInFile(file, lernaModules, TARGET_SCOPE);
25+
filesChanged += changeScopeInFile(file, moduleNames, targetScope);
3126
});
27+
return filesChanged;
3228
}
3329

3430
// modules/bitgo is the only package we publish without an `@bitgo` prefix, so
3531
// we must manually set one
36-
function replaceBitGoPackageScope() {
37-
const cwd = path.join(__dirname, '../', 'modules', 'bitgo');
32+
function replaceBitGoPackageScope(rootDir: string, targetScope: string): void {
33+
const cwd = path.join(rootDir, 'modules', 'bitgo');
3834
const json = JSON.parse(readFileSync(path.join(cwd, 'package.json'), { encoding: 'utf-8' }));
39-
json.name = `${TARGET_SCOPE}/bitgo`;
35+
json.name = `${targetScope}/bitgo`;
4036
writeFileSync(path.join(cwd, 'package.json'), JSON.stringify(json, null, 2) + '\n');
4137
}
4238

43-
/** Small version checkers in place of an npm dependency installation */
44-
function compareversion(version1, version2) {
45-
let result = false;
46-
47-
if (typeof version1 !== 'object') {
48-
version1 = version1.toString().split('.');
49-
}
50-
if (typeof version2 !== 'object') {
51-
version2 = version2.toString().split('.');
52-
}
53-
54-
for (let i = 0; i < Math.max(version1.length, version2.length); i++) {
55-
if (version1[i] === undefined) {
56-
version1[i] = 0;
57-
}
58-
if (version2[i] === undefined) {
59-
version2[i] = 0;
60-
}
39+
/**
40+
* Read package.json for a module
41+
* @param module The module to read package.json from
42+
* @returns The parsed package.json content
43+
*/
44+
function readModulePackageJson(module: LernaModule): any {
45+
return JSON.parse(readFileSync(path.join(module.location, 'package.json'), { encoding: 'utf-8' }));
46+
}
6147

62-
if (Number(version1[i]) < Number(version2[i])) {
63-
result = true;
64-
break;
65-
}
66-
if (version1[i] !== version2[i]) {
67-
break;
68-
}
69-
}
70-
return result;
48+
/**
49+
* Write package.json for a module
50+
* @param module The module to write package.json to
51+
* @param json The content to write
52+
*/
53+
function writeModulePackageJson(module: LernaModule, json: any): void {
54+
writeFileSync(path.join(module.location, 'package.json'), JSON.stringify(json, null, 2) + '\n');
7155
}
7256

7357
/**
74-
* increment the version based on the preid. default to `beta`
58+
* Increment the version for a single module based on the preid.
7559
*
76-
* @param {String | undefined} preid
60+
* @param {String} preid - The prerelease identifier
61+
* @param {LernaModule} module - The module to update
62+
* @param {DistTags|undefined} tags - The dist tags for the module
63+
* @param {LernaModule[]} allModules - All modules for dependency updates
64+
* @returns {String|undefined} - The new version if set, undefined otherwise
7765
*/
78-
async function incrementVersions(preid = 'beta') {
79-
const distTags = await getDistTagsForModuleLocations(lernaModuleLocations);
80-
for (let i = 0; i < lernaModuleLocations.length; i++) {
81-
try {
82-
const modulePath = lernaModuleLocations[i];
83-
const tags = distTags[i];
84-
const json = JSON.parse(readFileSync(path.join(modulePath, 'package.json'), { encoding: 'utf-8' }));
66+
function incrementVersionsForModuleLocation(
67+
preid: string,
68+
module: LernaModule,
69+
tags: DistTags | undefined,
70+
allModules: LernaModule[]
71+
): string | undefined {
72+
const json = readModulePackageJson(module);
8573

86-
let prevTag: string | undefined = undefined;
74+
let prevTag: string | undefined = undefined;
8775

88-
if (typeof tags === 'object') {
89-
if (tags[preid]) {
90-
const version = tags[preid].split('-');
91-
const latest = tags?.latest?.split('-') ?? ['0.0.0'];
92-
prevTag = compareversion(version[0], latest[0]) ? `${tags.latest}-${preid}` : tags[preid];
93-
} else {
94-
prevTag = `${tags.latest}-${preid}`;
95-
}
76+
if (tags) {
77+
if (tags[preid]) {
78+
const version = tags[preid].split('-');
79+
const latest = tags?.latest?.split('-') ?? ['0.0.0'];
80+
prevTag = lt(version[0], latest[0]) ? `${tags.latest}-${preid}` : tags[preid];
81+
} else {
82+
prevTag = `${tags.latest}-${preid}`;
83+
}
84+
}
85+
86+
if (prevTag) {
87+
const next = inc(prevTag, 'prerelease', undefined, preid);
88+
assert(typeof next === 'string', `Failed to increment version for ${json.name} prevTag=${prevTag}`);
89+
console.log(`Setting next version for ${json.name} to ${next}`);
90+
json.version = next;
91+
writeModulePackageJson(module, json);
92+
93+
// since we're manually setting new versions, we must also reconcile all other lerna packages to use the 'next' version for this module
94+
allModules.forEach((otherModule) => {
95+
// skip it for the current version
96+
if (otherModule.location === module.location) {
97+
return;
9698
}
9799

98-
if (prevTag) {
99-
const next = inc(prevTag, 'prerelease', undefined, preid);
100-
assert(typeof next === 'string', `Failed to increment version for ${json.name}`);
101-
console.log(`Setting next version for ${json.name} to ${next}`);
102-
json.version = next;
103-
writeFileSync(path.join(modulePath, 'package.json'), JSON.stringify(json, null, 2) + '\n');
104-
// since we're manually setting new versions, we must also reconcile all other lerna packages to use the 'next' version for this module
105-
lernaModuleLocations.forEach((otherModulePath) => {
106-
// skip it for the current version
107-
if (otherModulePath === modulePath) {
108-
return;
109-
}
110-
const otherJsonContent = readFileSync(path.join(otherModulePath, 'package.json'), { encoding: 'utf-8' });
111-
if (otherJsonContent.includes(json.name)) {
112-
const otherJson = JSON.parse(otherJsonContent);
113-
setDependencyVersion(otherJson, json.name, next as string);
114-
writeFileSync(path.join(otherModulePath, 'package.json'), JSON.stringify(otherJson, null, 2) + '\n');
115-
}
116-
});
100+
// Use readModulePackageJson here instead of direct readFileSync
101+
const otherJson = readModulePackageJson(otherModule);
102+
103+
// Check if this module depends on the one we're updating
104+
const otherJsonString = JSON.stringify(otherJson);
105+
if (otherJsonString.includes(json.name)) {
106+
setDependencyVersion(otherJson, json.name, next);
107+
writeModulePackageJson(otherModule, otherJson);
117108
}
109+
});
110+
111+
return next;
112+
}
113+
return undefined;
114+
}
115+
116+
/**
117+
* increment the version based on the preid.
118+
*
119+
* @param {String} preid - The prerelease identifier
120+
* @param {LernaModule[]} lernaModules - The modules to update
121+
*/
122+
async function incrementVersions(preid: string, lernaModules: LernaModule[]): Promise<void> {
123+
const distTags = await getDistTagsForModules(lernaModules);
124+
125+
for (const m of lernaModules) {
126+
try {
127+
incrementVersionsForModuleLocation(preid, m, distTags.get(m), lernaModules);
118128
} catch (e) {
119129
// it's not necessarily a blocking error. Let lerna try and publish anyways
120-
console.warn(`Couldn't set next version for ${lernaModuleLocations[i]}`, e);
130+
console.warn(`Couldn't set next version for ${m.name} at ${m.location}`, e);
121131
}
122132
}
123133
}
124134

125-
function getArgs() {
126-
const args = process.argv.slice(2) || [];
127-
const scopeArg = args.find((arg) => arg.startsWith('scope='));
128-
if (scopeArg) {
129-
const split = scopeArg.split('=');
130-
TARGET_SCOPE = split[1] || TARGET_SCOPE;
131-
}
132-
console.log(`Preparing to re-target to ${TARGET_SCOPE}`);
133-
console.log(`Using root directory: ${ROOT_DIR}`);
134-
}
135+
yargs(hideBin(process.argv))
136+
.command(
137+
'$0 [preid]',
138+
'Prepare packages for release with a new scope and incremented versions',
139+
(yargs) => {
140+
return yargs
141+
.positional('preid', {
142+
type: 'string',
143+
describe: 'Prerelease identifier',
144+
default: 'beta',
145+
})
146+
.option('scope', {
147+
type: 'string',
148+
description: 'Target scope for packages',
149+
default: '@bitgo-beta',
150+
})
151+
.option('root-dir', {
152+
type: 'string',
153+
description: 'Root directory of the repository',
154+
default: process.env.BITGO_PREPARE_RELEASE_ROOT_DIR || path.join(__dirname, '..'),
155+
});
156+
},
157+
async (argv) => {
158+
const { preid, scope: targetScope, rootDir } = argv;
135159

136-
async function main(preid?: string) {
137-
getArgs();
138-
await setLernaModules();
139-
replacePackageScopes();
140-
replaceBitGoPackageScope();
141-
await incrementVersions(preid);
142-
if (filesChanged) {
143-
console.log(`Successfully re-targeted ${filesChanged} files.`);
144-
process.exit(0);
145-
} else {
146-
console.error('No files were changed, something must have gone wrong.');
147-
process.exit(1);
148-
}
149-
}
160+
console.log(`Preparing to re-target to ${targetScope}`);
161+
console.log(`Using root directory: ${rootDir}`);
162+
console.log(`Using prerelease identifier: ${preid}`);
163+
164+
try {
165+
// Get lerna modules directly
166+
const lernaModules = await getLernaModules();
167+
168+
// Replace package scopes
169+
const filesChanged = replacePackageScopes(rootDir, lernaModules, targetScope);
150170

151-
main(process.argv.slice(2)[0]);
171+
// Replace BitGo package scope
172+
replaceBitGoPackageScope(rootDir, targetScope);
173+
174+
// Increment versions
175+
await incrementVersions(preid, lernaModules);
176+
177+
if (filesChanged) {
178+
console.log(`Successfully re-targeted ${filesChanged} files.`);
179+
process.exit(0);
180+
} else {
181+
console.error('No files were changed, something must have gone wrong.');
182+
process.exit(1);
183+
}
184+
} catch (error) {
185+
console.error('Error in prepare-release script:', error);
186+
process.exit(1);
187+
}
188+
}
189+
)
190+
.help()
191+
.alias('help', 'h')
192+
.parse();

scripts/prepareRelease/distTags.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { readFileSync, writeFileSync, existsSync } from 'fs';
22
import * as path from 'path';
3+
import { LernaModule } from './getLernaModules';
34

45
export type DistTags = Record<string, string>;
56

@@ -69,10 +70,10 @@ export async function getDistTagsForModuleNames(moduleNames: string[]): Promise<
6970
return tagsMap;
7071
}
7172

72-
export async function getDistTagsForModuleLocations(moduleLocations: string[]): Promise<(DistTags | undefined)[]> {
73-
const names: string[] = moduleLocations.map(
74-
(modulePath) => JSON.parse(readFileSync(path.join(modulePath, 'package.json'), { encoding: 'utf-8' })).name
73+
export async function getDistTagsForModules(modules: LernaModule[]): Promise<Map<LernaModule, DistTags | undefined>> {
74+
const names: string[] = modules.map(
75+
(m) => JSON.parse(readFileSync(path.join(m.location, 'package.json'), { encoding: 'utf-8' })).name
7576
);
76-
const map = await getDistTagsForModuleNames(names);
77-
return names.map((name) => map.get(name));
77+
const nameMap = await getDistTagsForModuleNames(names);
78+
return new Map<LernaModule, DistTags | undefined>(modules.map((m, i) => [m, nameMap.get(names[i])]));
7879
}

0 commit comments

Comments
 (0)