Skip to content

Commit 43f4122

Browse files
OttoAllmendingerllm-git
andcommitted
feat: add script to upgrade workspace dependencies
Add a new script for upgrading dependencies across workspace packages. The script allows updating a specified dependency to either the latest version or a specific target version across all packages that use it. Features: - Find all packages using a specific dependency - Update to latest or specified version - Support for all dependency types (regular, dev, peer, optional) - Dry run mode to preview changes Issue: BTC-2732 Co-authored-by: llm-git <[email protected]>
1 parent 0e2428f commit 43f4122

File tree

2 files changed

+176
-0
lines changed

2 files changed

+176
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
"check-commits": "yarn commitlint --from=origin/${GITHUB_REPO_BRANCH:-master} -V",
132132
"check-deps": "tsx ./scripts/check-package-dependencies.ts",
133133
"check-versions": "node ./check-package-versions.js",
134+
"upgrade-dep": "tsx ./scripts/upgrade-workspace-dependency.ts upgrade",
134135
"dev": "tsc -b ./tsconfig.packages.json -w",
135136
"prepare": "husky install",
136137
"sdk-coin:new": "yo ./scripts/sdk-coin-generator",
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/**
2+
* Upgrade Workspace Dependency Script
3+
*
4+
* This script automates upgrading a dependency across all workspace packages in the monorepo.
5+
* It discovers all packages that use the specified dependency via lerna, updates their package.json
6+
* files, and runs a single yarn install to minimize yarn.lock changes.
7+
*
8+
* Usage:
9+
* yarn upgrade-dep -p @bitgo/wasm-utxo -v 1.3.0
10+
* yarn upgrade-dep -p @bitgo/wasm-utxo # Upgrades to latest version
11+
* yarn upgrade-dep -p @bitgo/wasm-utxo -v 1.3.0 -d # Dry run mode
12+
*/
13+
import execa from 'execa';
14+
import fs from 'fs/promises';
15+
import path from 'path';
16+
import yargs from 'yargs';
17+
import { getLernaModules } from './prepareRelease/getLernaModules';
18+
19+
interface PackageJson {
20+
name: string;
21+
dependencies?: Record<string, string>;
22+
devDependencies?: Record<string, string>;
23+
peerDependencies?: Record<string, string>;
24+
optionalDependencies?: Record<string, string>;
25+
}
26+
27+
interface PackageWithDep {
28+
packagePath: string;
29+
packageName: string;
30+
currentVersion: string;
31+
depType: 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies';
32+
}
33+
34+
async function findPackagesWithDependency(depName: string): Promise<PackageWithDep[]> {
35+
const modules = await getLernaModules();
36+
const packagesWithDep: PackageWithDep[] = [];
37+
38+
for (const module of modules) {
39+
const packageJsonPath = path.join(module.location, 'package.json');
40+
try {
41+
const content = await fs.readFile(packageJsonPath, 'utf-8');
42+
const packageJson: PackageJson = JSON.parse(content);
43+
44+
for (const depType of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'] as const) {
45+
const deps = packageJson[depType];
46+
if (deps && deps[depName]) {
47+
packagesWithDep.push({
48+
packagePath: packageJsonPath,
49+
packageName: packageJson.name,
50+
currentVersion: deps[depName],
51+
depType,
52+
});
53+
break; // Only record once per package
54+
}
55+
}
56+
} catch (e) {
57+
// Skip if package.json doesn't exist or can't be read
58+
continue;
59+
}
60+
}
61+
62+
return packagesWithDep;
63+
}
64+
65+
async function getLatestVersion(packageName: string): Promise<string> {
66+
console.log(`Fetching latest version for ${packageName}...`);
67+
const { stdout } = await execa('npm', ['view', packageName, 'version']);
68+
return stdout.trim();
69+
}
70+
71+
async function updatePackageJson(
72+
packagePath: string,
73+
depName: string,
74+
newVersion: string,
75+
depType: string
76+
): Promise<void> {
77+
const content = await fs.readFile(packagePath, 'utf-8');
78+
const packageJson: PackageJson = JSON.parse(content);
79+
80+
if (packageJson[depType as keyof PackageJson]) {
81+
const deps = packageJson[depType as keyof PackageJson] as Record<string, string>;
82+
deps[depName] = newVersion;
83+
}
84+
85+
await fs.writeFile(packagePath, JSON.stringify(packageJson, null, 2) + '\n');
86+
}
87+
88+
async function runYarnInstall(): Promise<void> {
89+
console.log('\nRunning yarn install to update lock file...');
90+
await execa('yarn', ['install'], {
91+
stdio: 'inherit',
92+
});
93+
}
94+
95+
async function cmdUpgrade(opts: { package: string; version?: string; dryRun: boolean }): Promise<void> {
96+
const { package: depName, version: targetVersion, dryRun } = opts;
97+
98+
console.log(`\n🔍 Searching for packages with dependency: ${depName}\n`);
99+
100+
const packagesWithDep = await findPackagesWithDependency(depName);
101+
102+
if (packagesWithDep.length === 0) {
103+
console.log(`❌ No packages found with dependency: ${depName}`);
104+
return;
105+
}
106+
107+
console.log(`Found ${packagesWithDep.length} package(s) with ${depName}:\n`);
108+
for (const pkg of packagesWithDep) {
109+
console.log(` • ${pkg.packageName} (${pkg.currentVersion})`);
110+
}
111+
112+
let newVersion: string;
113+
if (targetVersion) {
114+
newVersion = targetVersion;
115+
console.log(`\n📦 Target version: ${newVersion}`);
116+
} else {
117+
newVersion = await getLatestVersion(depName);
118+
console.log(`\n📦 Latest version: ${newVersion}`);
119+
}
120+
121+
if (dryRun) {
122+
console.log('\n🔍 Dry run - no changes will be made\n');
123+
console.log('Would update:');
124+
for (const pkg of packagesWithDep) {
125+
console.log(` ${pkg.packageName}: ${pkg.currentVersion}${newVersion}`);
126+
}
127+
console.log('\nThen run: yarn install');
128+
return;
129+
}
130+
131+
console.log('\n✏️ Updating package.json files...\n');
132+
for (const pkg of packagesWithDep) {
133+
console.log(` Updating ${pkg.packageName}...`);
134+
await updatePackageJson(pkg.packagePath, depName, newVersion, pkg.depType);
135+
}
136+
137+
console.log('\n✅ Updated all package.json files');
138+
139+
await runYarnInstall();
140+
141+
console.log('\n✅ Done!');
142+
}
143+
144+
yargs
145+
.command({
146+
command: 'upgrade',
147+
describe: 'Upgrade a dependency across all workspace packages',
148+
builder(a) {
149+
return a.options({
150+
package: {
151+
type: 'string',
152+
demand: true,
153+
describe: 'Name of the package to upgrade, e.g. @bitgo/wasm-utxo',
154+
alias: 'p',
155+
},
156+
version: {
157+
type: 'string',
158+
describe: 'Target version (defaults to latest from npm registry)',
159+
alias: 'v',
160+
},
161+
dryRun: {
162+
type: 'boolean',
163+
default: false,
164+
describe: 'Show what would be updated without making changes',
165+
alias: 'd',
166+
},
167+
});
168+
},
169+
async handler(a) {
170+
await cmdUpgrade(a);
171+
},
172+
})
173+
.help()
174+
.strict()
175+
.demandCommand().argv;

0 commit comments

Comments
 (0)