Skip to content

Commit 74bbe1c

Browse files
Merge pull request #5798 from BitGo/BTC-1933.refactor-prepareRelease
refactor(scripts): restructure and improve package tooling
2 parents f6bda4f + c89283f commit 74bbe1c

File tree

9 files changed

+151
-100
lines changed

9 files changed

+151
-100
lines changed

commitlint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module.exports = {
88
(commit) => commit.includes('Signed-off-by: dependabot[bot] <[email protected]>'),
99
],
1010
rules: {
11-
'scope-enum': async () => [2, 'always', (await readdir('modules')).concat('root', 'deps')],
11+
'scope-enum': async () => [2, 'always', (await readdir('modules')).concat('root', 'deps', 'scripts')],
1212
'footer-max-line-length': [0, 'always', Infinity],
1313
'references-empty': [2, 'never'],
1414
},

scripts/prepare-release.ts

Lines changed: 18 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,32 @@
1-
import * as execa from 'execa';
2-
import { readFileSync, readdirSync, writeFileSync, statSync } from 'fs';
1+
import * as assert from 'node:assert';
2+
import { readFileSync, writeFileSync } from 'fs';
33
import * as path from 'path';
44
import { inc } from 'semver';
5+
import {
6+
walk,
7+
getDistTagsForModuleLocations,
8+
getLernaModules,
9+
changeScopeInFile,
10+
setDependencyVersion,
11+
} from './prepareRelease';
512

613
let lernaModules: string[] = [];
714
let lernaModuleLocations: string[] = [];
815
let TARGET_SCOPE = '@bitgo-beta';
916
let filesChanged = 0;
1017

11-
/**
12-
* Create a function which can run lerna commands
13-
* @param {String} lernaPath - path to lerna binary
14-
* @returns {function(string, string[], Object.<string, string>): Promise<string>}
15-
*/
16-
function getLernaRunner(lernaPath: string) {
17-
return async (command: string, args: string[] = [], options = {}) => {
18-
const { stdout } = await execa(lernaPath, [command, ...args], options);
19-
return stdout;
20-
};
21-
}
22-
23-
const getLernaModules = async (): Promise<void> => {
24-
const { stdout: lernaBinary } = await execa('yarn', ['bin', 'lerna'], { cwd: process.cwd() });
25-
26-
const lerna = getLernaRunner(lernaBinary);
27-
const modules: Array<{ name: string; location: string }> = JSON.parse(
28-
await lerna('list', ['--loglevel', 'silent', '--json', '--all'])
29-
);
18+
const setLernaModules = async (): Promise<void> => {
19+
const modules = await getLernaModules();
3020
lernaModules = modules.map(({ name }) => name);
3121
lernaModuleLocations = modules.map(({ location }) => location);
3222
};
3323

34-
const walk = (dir: string): string[] => {
35-
let results: string[] = [];
36-
const ignoredFolders = [/node_modules/];
37-
const list = readdirSync(dir);
38-
list.forEach((file) => {
39-
file = path.join(dir, file);
40-
const stat = statSync(file);
41-
if (stat && stat.isDirectory()) {
42-
if (!ignoredFolders.some((folder) => folder.test(file))) {
43-
results = [...results, ...walk(file)];
44-
}
45-
} else if (['.ts', '.tsx', '.js', '.json'].includes(path.extname(file))) {
46-
// Is a file
47-
results.push(file);
48-
}
49-
});
50-
return results;
51-
};
52-
53-
const changeScopeInFile = (filePath: string): void => {
54-
const oldContent = readFileSync(filePath, { encoding: 'utf8' });
55-
let newContent = oldContent;
56-
lernaModules.forEach((moduleName) => {
57-
const newName = `${moduleName.replace('@bitgo/', `${TARGET_SCOPE}/`)}`;
58-
newContent = newContent.replace(new RegExp(moduleName, 'g'), newName);
59-
});
60-
if (newContent !== oldContent) {
61-
writeFileSync(filePath, newContent, { encoding: 'utf-8' });
62-
++filesChanged;
63-
}
64-
};
65-
6624
const replacePackageScopes = () => {
6725
// replace all @bitgo packages & source code with alternate SCOPE
6826
const filePaths = [...walk(path.join(__dirname, '../', 'modules')), ...walk(path.join(__dirname, '../', 'webpack'))];
69-
filePaths.forEach((file) => changeScopeInFile(file));
70-
};
71-
72-
/**
73-
* Makes an HTTP request to fetch all the dist tags for a given package.
74-
*/
75-
type DistTags = Record<string, string>;
76-
const getDistTags = async (packageName: string): Promise<DistTags> => {
77-
console.log(`Fetching dist tags for ${packageName}`);
78-
const url = `https://registry.npmjs.org/-/package/${packageName}/dist-tags`;
79-
const response = await fetch(url);
80-
if (!response.ok) {
81-
throw new Error(`Failed ${url}: ${response.status} ${response.statusText} ${await response.text()}`);
82-
}
83-
return response.json();
27+
filePaths.forEach((file) => {
28+
filesChanged += changeScopeInFile(file, lernaModules, TARGET_SCOPE);
29+
});
8430
};
8531

8632
// modules/bitgo is the only package we publish without an `@bitgo` prefix, so
@@ -122,36 +68,13 @@ function compareversion(version1, version2) {
12268
return result;
12369
}
12470

125-
async function getDistTagsForModules(moduleLocations: string[]): Promise<(DistTags | undefined)[]> {
126-
return await Promise.all(
127-
moduleLocations.map(async (modulePath) => {
128-
const moduleName: string = JSON.parse(
129-
readFileSync(path.join(modulePath, 'package.json'), { encoding: 'utf-8' })
130-
).name;
131-
switch (moduleName) {
132-
case '@bitgo-beta/express':
133-
case '@bitgo-beta/web-demo':
134-
case '@bitgo-beta/sdk-test':
135-
console.warn(`Skipping ${moduleName} as it's not published to npm`);
136-
return undefined;
137-
}
138-
try {
139-
return await getDistTags(moduleName);
140-
} catch (e) {
141-
console.warn(`Failed to fetch dist tags for ${moduleName}`, e);
142-
return undefined;
143-
}
144-
})
145-
);
146-
}
147-
14871
/**
14972
* increment the version based on the preid. default to `beta`
15073
*
15174
* @param {String | undefined} preid
15275
*/
15376
const incrementVersions = async (preid = 'beta') => {
154-
const distTags = await getDistTagsForModules(lernaModuleLocations);
77+
const distTags = await getDistTagsForModuleLocations(lernaModuleLocations);
15578
for (let i = 0; i < lernaModuleLocations.length; i++) {
15679
try {
15780
const modulePath = lernaModuleLocations[i];
@@ -172,6 +95,7 @@ const incrementVersions = async (preid = 'beta') => {
17295

17396
if (prevTag) {
17497
const next = inc(prevTag, 'prerelease', undefined, preid);
98+
assert(typeof next === 'string', `Failed to increment version for ${json.name}`);
17599
console.log(`Setting next version for ${json.name} to ${next}`);
176100
json.version = next;
177101
writeFileSync(path.join(modulePath, 'package.json'), JSON.stringify(json, null, 2) + '\n');
@@ -184,12 +108,7 @@ const incrementVersions = async (preid = 'beta') => {
184108
const otherJsonContent = readFileSync(path.join(otherModulePath, 'package.json'), { encoding: 'utf-8' });
185109
if (otherJsonContent.includes(json.name)) {
186110
const otherJson = JSON.parse(otherJsonContent);
187-
if (otherJson.dependencies && otherJson.dependencies[json.name]) {
188-
otherJson.dependencies[json.name] = next;
189-
}
190-
if (otherJson.devDependencies && otherJson.devDependencies[json.name]) {
191-
otherJson.devDependencies[json.name] = next;
192-
}
111+
setDependencyVersion(otherJson, json.name, next as string);
193112
writeFileSync(path.join(otherModulePath, 'package.json'), JSON.stringify(otherJson, null, 2) + '\n');
194113
}
195114
});
@@ -213,7 +132,7 @@ const getArgs = () => {
213132

214133
const main = async (preid?: string) => {
215134
getArgs();
216-
await getLernaModules();
135+
await setLernaModules();
217136
replacePackageScopes();
218137
replaceBitGoPackageScope();
219138
await incrementVersions(preid);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export function setDependencyVersion(
2+
packageJson: {
3+
dependencies?: Record<string, string>;
4+
devDependencies?: Record<string, string>;
5+
},
6+
dependencyName: string,
7+
version: string
8+
): void {
9+
if (packageJson.dependencies && packageJson.dependencies[dependencyName]) {
10+
packageJson.dependencies[dependencyName] = version;
11+
}
12+
if (packageJson.devDependencies && packageJson.devDependencies[dependencyName]) {
13+
packageJson.devDependencies[dependencyName] = version;
14+
}
15+
// FIXME: also update the peerDependencies, buildDependencies, etc.
16+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { readFileSync, writeFileSync } from 'fs';
2+
3+
export function updateModuleNames(input: string, lernaModules: string[], targetScope: string): string {
4+
lernaModules.forEach((moduleName) => {
5+
const newName = `${moduleName.replace('@bitgo/', `${targetScope}/`)}`;
6+
input = input.replace(new RegExp(moduleName, 'g'), newName);
7+
});
8+
return input;
9+
}
10+
11+
export function changeScopeInFile(filePath: string, lernaModules: string[], targetScope: string): number {
12+
const oldContent = readFileSync(filePath, { encoding: 'utf8' });
13+
const newContent = updateModuleNames(oldContent, lernaModules, targetScope);
14+
if (newContent !== oldContent) {
15+
writeFileSync(filePath, newContent, { encoding: 'utf-8' });
16+
return 1;
17+
}
18+
return 0;
19+
}

scripts/prepareRelease/distTags.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { readFileSync } from 'fs';
2+
import * as path from 'path';
3+
4+
export type DistTags = Record<string, string>;
5+
6+
/**
7+
* Makes an HTTP request to fetch all the dist tags for a given package.
8+
*/
9+
export async function getDistTags(packageName: string): Promise<DistTags> {
10+
console.log(`Fetching dist tags for ${packageName}`);
11+
const url = `https://registry.npmjs.org/-/package/${packageName}/dist-tags`;
12+
const response = await fetch(url);
13+
if (!response.ok) {
14+
throw new Error(`Failed ${url}: ${response.status} ${response.statusText} ${await response.text()}`);
15+
}
16+
return response.json();
17+
}
18+
19+
export async function getDistTagsForModuleNames(moduleNames: string[]): Promise<(DistTags | undefined)[]> {
20+
return Promise.all(
21+
moduleNames.map(async (moduleName) => {
22+
switch (moduleName) {
23+
case '@bitgo-beta/express':
24+
case '@bitgo-beta/web-demo':
25+
case '@bitgo-beta/sdk-test':
26+
console.warn(`Skipping ${moduleName} as it's not published to npm`);
27+
return undefined;
28+
}
29+
try {
30+
return await getDistTags(moduleName);
31+
} catch (e) {
32+
console.warn(`Failed to fetch dist tags for ${moduleName}`, e);
33+
return undefined;
34+
}
35+
})
36+
);
37+
}
38+
39+
export async function getDistTagsForModuleLocations(moduleLocations: string[]): Promise<(DistTags | undefined)[]> {
40+
return getDistTagsForModuleNames(
41+
moduleLocations.map(
42+
(modulePath) => JSON.parse(readFileSync(path.join(modulePath, 'package.json'), { encoding: 'utf-8' })).name
43+
)
44+
);
45+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as execa from 'execa';
2+
3+
/**
4+
* Create a function which can run lerna commands
5+
* @param {String} lernaPath - path to lerna binary
6+
* @returns {function(string, string[], Object.<string, string>): Promise<string>}
7+
*/
8+
function getLernaRunner(lernaPath: string) {
9+
return async (command: string, args: string[] = [], options = {}) => {
10+
const { stdout } = await execa(lernaPath, [command, ...args], options);
11+
return stdout;
12+
};
13+
}
14+
15+
export async function getLernaModules(): Promise<Array<{ name: string; location: string }>> {
16+
const { stdout: lernaBinary } = await execa('yarn', ['bin', 'lerna'], { cwd: process.cwd() });
17+
const lerna = getLernaRunner(lernaBinary);
18+
return JSON.parse(await lerna('list', ['--loglevel', 'silent', '--json', '--all']));
19+
}

scripts/prepareRelease/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export * from './changeScopeInFile';
2+
export * from './changePackageJson';
3+
export * from './distTags';
4+
export * from './getLernaModules';
5+
export * from './walk';

scripts/prepareRelease/walk.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as path from 'path';
2+
import { readdirSync, statSync } from 'fs';
3+
4+
export const walk = (dir: string): string[] => {
5+
let results: string[] = [];
6+
const ignoredFolders = [/node_modules/];
7+
const list = readdirSync(dir);
8+
list.forEach((file) => {
9+
file = path.join(dir, file);
10+
const stat = statSync(file);
11+
if (stat && stat.isDirectory()) {
12+
if (!ignoredFolders.some((folder) => folder.test(file))) {
13+
results = [...results, ...walk(file)];
14+
}
15+
} else if (['.ts', '.tsx', '.js', '.json'].includes(path.extname(file))) {
16+
// Is a file
17+
results.push(file);
18+
}
19+
});
20+
return results;
21+
};

scripts/tsconfig.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
"compilerOptions": {
4+
"noEmit": true
5+
},
6+
"exclude": ["sdk-coin-generator"]
7+
}

0 commit comments

Comments
 (0)