Skip to content

Commit 21fd2f9

Browse files
authored
Merge pull request #511 from modelcontextprotocol/jerome/feature/version-update-and-check-scripts
feat: add version management scripts and CI check
2 parents 9051727 + dc77327 commit 21fd2f9

File tree

4 files changed

+356
-0
lines changed

4 files changed

+356
-0
lines changed

.github/workflows/main.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ jobs:
2626
# - run: npm ci
2727
- run: npm install --no-package-lock
2828

29+
- name: Check version consistency
30+
run: npm run check-version
31+
2932
- name: Check linting
3033
working-directory: ./client
3134
run: npm run lint

scripts/README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Version Management Scripts
2+
3+
This directory contains scripts for managing version consistency across the monorepo.
4+
5+
## Scripts
6+
7+
### update-version.js
8+
9+
Updates the version across all package.json files in the monorepo and updates package-lock.json.
10+
11+
**Usage:**
12+
13+
```bash
14+
npm run update-version <new-version>
15+
# Example:
16+
npm run update-version 0.14.3
17+
```
18+
19+
This script will:
20+
21+
1. Update the version in all package.json files (root, client, server, cli)
22+
2. Update workspace dependencies in the root package.json
23+
3. Run `npm install` to update package-lock.json
24+
4. Provide next steps for committing and tagging
25+
26+
### check-version-consistency.js
27+
28+
Checks that all packages have consistent versions and that package-lock.json is up to date.
29+
30+
**Usage:**
31+
32+
```bash
33+
npm run check-version
34+
```
35+
36+
This script checks:
37+
38+
1. All package.json files have the same version
39+
2. Workspace dependencies in root package.json match the current version
40+
3. package-lock.json version matches package.json
41+
4. Workspace packages in package-lock.json have the correct versions
42+
43+
This check runs automatically in CI on every PR and push to main.
44+
45+
## CI Integration
46+
47+
The version consistency check is integrated into the GitHub Actions workflow (`.github/workflows/main.yml`) and will fail the build if:
48+
49+
- Package versions are inconsistent
50+
- package-lock.json is out of sync
51+
52+
## Common Workflows
53+
54+
### Bumping version for a release:
55+
56+
```bash
57+
# Update to new version
58+
npm run update-version 0.15.0
59+
60+
# Verify everything is correct
61+
npm run check-version
62+
63+
# Commit the changes
64+
git add -A
65+
git commit -m "chore: bump version to 0.15.0"
66+
67+
# Create a tag
68+
git tag 0.15.0
69+
70+
# Push changes and tag
71+
git push && git push --tags
72+
```

scripts/check-version-consistency.js

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#!/usr/bin/env node
2+
3+
import fs from "fs";
4+
import path from "path";
5+
import { fileURLToPath } from "url";
6+
7+
const __filename = fileURLToPath(import.meta.url);
8+
const __dirname = path.dirname(__filename);
9+
10+
/**
11+
* Checks version consistency across all package.json files in the monorepo
12+
* Exits with code 1 if versions are inconsistent
13+
* Usage: node scripts/check-version-consistency.js
14+
*/
15+
16+
console.log("🔍 Checking version consistency across packages...\n");
17+
18+
// List of package.json files to check
19+
const packagePaths = [
20+
"package.json",
21+
"client/package.json",
22+
"server/package.json",
23+
"cli/package.json",
24+
];
25+
26+
const versions = new Map();
27+
const errors = [];
28+
29+
// Read version from each package.json
30+
packagePaths.forEach((packagePath) => {
31+
const fullPath = path.join(__dirname, "..", packagePath);
32+
33+
if (!fs.existsSync(fullPath)) {
34+
console.warn(`⚠️ Skipping ${packagePath} - file not found`);
35+
return;
36+
}
37+
38+
try {
39+
const packageJson = JSON.parse(fs.readFileSync(fullPath, "utf8"));
40+
const version = packageJson.version;
41+
const packageName = packageJson.name || packagePath;
42+
43+
versions.set(packagePath, {
44+
name: packageName,
45+
version: version,
46+
dependencies: packageJson.dependencies || {},
47+
});
48+
49+
console.log(`📦 ${packagePath}:`);
50+
console.log(` Name: ${packageName}`);
51+
console.log(` Version: ${version}`);
52+
} catch (error) {
53+
errors.push(`Failed to read ${packagePath}: ${error.message}`);
54+
}
55+
});
56+
57+
if (errors.length > 0) {
58+
console.error("\n❌ Errors occurred while reading package files:");
59+
errors.forEach((error) => console.error(` - ${error}`));
60+
process.exit(1);
61+
}
62+
63+
// Check if all versions match
64+
const allVersions = Array.from(versions.values()).map((v) => v.version);
65+
const uniqueVersions = [...new Set(allVersions)];
66+
67+
console.log("\n📊 Version Summary:");
68+
console.log(` Total packages: ${versions.size}`);
69+
console.log(` Unique versions: ${uniqueVersions.length}`);
70+
71+
if (uniqueVersions.length > 1) {
72+
console.error("\n❌ Version mismatch detected!");
73+
console.error(" Found versions: " + uniqueVersions.join(", "));
74+
75+
console.error("\n Package versions:");
76+
versions.forEach((info, path) => {
77+
console.error(` - ${path}: ${info.version}`);
78+
});
79+
} else {
80+
console.log(` ✅ All packages are at version: ${uniqueVersions[0]}`);
81+
}
82+
83+
// Check workspace dependencies in root package.json
84+
const rootPackage = versions.get("package.json");
85+
if (rootPackage) {
86+
console.log("\n🔗 Checking workspace dependencies...");
87+
const expectedVersion = rootPackage.version;
88+
let dependencyErrors = false;
89+
90+
Object.entries(rootPackage.dependencies).forEach(([dep, version]) => {
91+
if (dep.startsWith("@modelcontextprotocol/inspector-")) {
92+
const expectedDepVersion = `^${expectedVersion}`;
93+
if (version !== expectedDepVersion) {
94+
console.error(
95+
` ❌ ${dep}: ${version} (expected ${expectedDepVersion})`,
96+
);
97+
dependencyErrors = true;
98+
} else {
99+
console.log(` ✅ ${dep}: ${version}`);
100+
}
101+
}
102+
});
103+
104+
if (dependencyErrors) {
105+
errors.push("Workspace dependency versions do not match package versions");
106+
}
107+
}
108+
109+
// Check if package-lock.json is up to date
110+
console.log("\n🔒 Checking package-lock.json...");
111+
const lockPath = path.join(__dirname, "..", "package-lock.json");
112+
let lockFileError = false;
113+
114+
if (!fs.existsSync(lockPath)) {
115+
console.error(" ❌ package-lock.json not found");
116+
lockFileError = true;
117+
} else {
118+
try {
119+
const lockFile = JSON.parse(fs.readFileSync(lockPath, "utf8"));
120+
const lockVersion = lockFile.version;
121+
const expectedVersion = rootPackage?.version || uniqueVersions[0];
122+
123+
if (lockVersion !== expectedVersion) {
124+
console.error(
125+
` ❌ package-lock.json version (${lockVersion}) does not match package.json version (${expectedVersion})`,
126+
);
127+
lockFileError = true;
128+
} else {
129+
console.log(` ✅ package-lock.json version matches: ${lockVersion}`);
130+
}
131+
132+
// Check workspace package versions in lock file
133+
if (lockFile.packages) {
134+
const workspacePackages = [
135+
{ path: "client", name: "@modelcontextprotocol/inspector-client" },
136+
{ path: "server", name: "@modelcontextprotocol/inspector-server" },
137+
{ path: "cli", name: "@modelcontextprotocol/inspector-cli" },
138+
];
139+
140+
workspacePackages.forEach(({ path, name }) => {
141+
const lockPkgPath = lockFile.packages[path];
142+
if (lockPkgPath && lockPkgPath.version !== expectedVersion) {
143+
console.error(
144+
` ❌ ${name} in lock file: ${lockPkgPath.version} (expected ${expectedVersion})`,
145+
);
146+
lockFileError = true;
147+
}
148+
});
149+
}
150+
} catch (error) {
151+
console.error(` ❌ Failed to parse package-lock.json: ${error.message}`);
152+
lockFileError = true;
153+
}
154+
}
155+
156+
// Final result
157+
console.log("\n🎯 Result:");
158+
if (uniqueVersions.length === 1 && errors.length === 0 && !lockFileError) {
159+
console.log(" ✅ Version consistency check passed!");
160+
process.exit(0);
161+
} else {
162+
console.error(" ❌ Version consistency check failed!");
163+
if (uniqueVersions.length > 1) {
164+
console.error(" - Package versions are not consistent");
165+
}
166+
if (errors.length > 0) {
167+
console.error(" - " + errors.join("\n - "));
168+
}
169+
if (lockFileError) {
170+
console.error(" - package-lock.json is out of sync");
171+
}
172+
console.error(
173+
'\n💡 Run "npm run update-version <new-version>" to fix version inconsistencies',
174+
);
175+
console.error(' or run "npm install" to update package-lock.json');
176+
process.exit(1);
177+
}

scripts/update-version.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env node
2+
3+
import fs from "fs";
4+
import path from "path";
5+
import { execSync } from "child_process";
6+
import { fileURLToPath } from "url";
7+
8+
const __filename = fileURLToPath(import.meta.url);
9+
const __dirname = path.dirname(__filename);
10+
11+
/**
12+
* Updates version across all package.json files in the monorepo
13+
* Usage: node scripts/update-version.js <new-version>
14+
* Example: node scripts/update-version.js 0.14.2
15+
*/
16+
17+
const newVersion = process.argv[2];
18+
19+
if (!newVersion) {
20+
console.error("❌ Please provide a version number");
21+
console.error("Usage: node scripts/update-version.js <new-version>");
22+
console.error("Example: node scripts/update-version.js 0.14.2");
23+
process.exit(1);
24+
}
25+
26+
// Validate version format
27+
const versionRegex = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
28+
if (!versionRegex.test(newVersion)) {
29+
console.error(
30+
"❌ Invalid version format. Please use semantic versioning (e.g., 1.2.3 or 1.2.3-beta.1)",
31+
);
32+
process.exit(1);
33+
}
34+
35+
console.log(`🔄 Updating all packages to version ${newVersion}...`);
36+
37+
// List of package.json files to update
38+
const packagePaths = [
39+
"package.json",
40+
"client/package.json",
41+
"server/package.json",
42+
"cli/package.json",
43+
];
44+
45+
const updatedFiles = [];
46+
47+
// Update version in each package.json
48+
packagePaths.forEach((packagePath) => {
49+
const fullPath = path.join(__dirname, "..", packagePath);
50+
51+
if (!fs.existsSync(fullPath)) {
52+
console.warn(`⚠️ Skipping ${packagePath} - file not found`);
53+
return;
54+
}
55+
56+
try {
57+
const packageJson = JSON.parse(fs.readFileSync(fullPath, "utf8"));
58+
const oldVersion = packageJson.version;
59+
packageJson.version = newVersion;
60+
61+
// Update workspace dependencies in root package.json
62+
if (packagePath === "package.json" && packageJson.dependencies) {
63+
Object.keys(packageJson.dependencies).forEach((dep) => {
64+
if (dep.startsWith("@modelcontextprotocol/inspector-")) {
65+
packageJson.dependencies[dep] = `^${newVersion}`;
66+
}
67+
});
68+
}
69+
70+
fs.writeFileSync(fullPath, JSON.stringify(packageJson, null, 2) + "\n");
71+
updatedFiles.push(packagePath);
72+
console.log(
73+
`✅ Updated ${packagePath} from ${oldVersion} to ${newVersion}`,
74+
);
75+
} catch (error) {
76+
console.error(`❌ Failed to update ${packagePath}:`, error.message);
77+
process.exit(1);
78+
}
79+
});
80+
81+
console.log("\n📝 Summary:");
82+
console.log(`Updated ${updatedFiles.length} files to version ${newVersion}`);
83+
84+
// Update package-lock.json
85+
console.log("\n🔒 Updating package-lock.json...");
86+
try {
87+
execSync("npm install", { stdio: "inherit" });
88+
console.log("✅ package-lock.json updated successfully");
89+
} catch (error) {
90+
console.error("❌ Failed to update package-lock.json:", error.message);
91+
console.error('Please run "npm install" manually');
92+
process.exit(1);
93+
}
94+
95+
console.log("\n✨ Version update complete!");
96+
console.log("\nNext steps:");
97+
console.log("1. Review the changes: git diff");
98+
console.log(
99+
'2. Commit the changes: git add -A && git commit -m "chore: bump version to ' +
100+
newVersion +
101+
'"',
102+
);
103+
console.log("3. Create a git tag: git tag v" + newVersion);
104+
console.log("4. Push changes and tag: git push && git push --tags");

0 commit comments

Comments
 (0)