Skip to content

Commit 6cd8631

Browse files
committed
Automated release, first try
1 parent d85d750 commit 6cd8631

File tree

5 files changed

+279
-260
lines changed

5 files changed

+279
-260
lines changed

PUBLISH.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,41 @@
22

33
This document outlines the complete process for publishing new versions of Desktop Commander to both NPM and the MCP Registry.
44

5+
## 🚀 Automated Release (Recommended)
6+
7+
We now have an automated release script that handles the entire process!
8+
9+
```bash
10+
# Patch release (0.2.16 → 0.2.17) - Bug fixes, small improvements
11+
npm run release
12+
13+
# Minor release (0.2.16 → 0.3.0) - New features
14+
npm run release:minor
15+
16+
# Major release (0.2.16 → 1.0.0) - Breaking changes
17+
npm run release:major
18+
19+
# Test without publishing
20+
npm run release:dry
21+
```
22+
23+
**See [scripts/README-RELEASE.md](scripts/README-RELEASE.md) for full documentation of the automated release process.**
24+
25+
The script automatically handles:
26+
- ✅ Version bumping
27+
- ✅ Building project and MCPB bundle
28+
- ✅ Running tests
29+
- ✅ Git commit and tagging
30+
- ✅ NPM publishing
31+
- ✅ MCP Registry publishing
32+
- ✅ Publication verification
33+
34+
---
35+
36+
## Manual Release Process
37+
38+
If you prefer to release manually or need to troubleshoot, follow these steps:
39+
540
## Prerequisites
641

742
- Node.js 18+ installed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"release:minor": "node scripts/publish-release.cjs --minor",
4848
"release:major": "node scripts/publish-release.cjs --major",
4949
"release:dry": "node scripts/publish-release.cjs --dry-run",
50+
"release:mcp": "node scripts/publish-release.cjs --mcp-only",
5051
"logs:view": "npm run build && node scripts/view-fuzzy-logs.js",
5152
"logs:analyze": "npm run build && node scripts/analyze-fuzzy-logs.js",
5253
"logs:clear": "npm run build && node scripts/clear-fuzzy-logs.js",

scripts/publish-release.cjs

Lines changed: 155 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ function parseArgs() {
7171
skipTests: false,
7272
dryRun: false,
7373
help: false,
74+
skipBump: false,
75+
skipBuild: false,
76+
skipMcpb: false,
77+
skipGit: false,
78+
skipNpm: false,
7479
};
7580

7681
for (const arg of args) {
@@ -84,6 +89,29 @@ function parseArgs() {
8489
case '--skip-tests':
8590
options.skipTests = true;
8691
break;
92+
case '--skip-bump':
93+
options.skipBump = true;
94+
break;
95+
case '--skip-build':
96+
options.skipBuild = true;
97+
break;
98+
case '--skip-mcpb':
99+
options.skipMcpb = true;
100+
break;
101+
case '--skip-git':
102+
options.skipGit = true;
103+
break;
104+
case '--skip-npm':
105+
options.skipNpm = true;
106+
break;
107+
case '--mcp-only':
108+
// Skip everything except MCP Registry publish
109+
options.skipBump = true;
110+
options.skipBuild = true;
111+
options.skipMcpb = true;
112+
options.skipGit = true;
113+
options.skipNpm = true;
114+
break;
87115
case '--dry-run':
88116
options.dryRun = true;
89117
break;
@@ -109,6 +137,12 @@ function showHelp() {
109137
console.log(' --minor Bump minor version (default: patch)');
110138
console.log(' --major Bump major version (default: patch)');
111139
console.log(' --skip-tests Skip running tests');
140+
console.log(' --skip-bump Skip version bumping');
141+
console.log(' --skip-build Skip building (if tests also skipped)');
142+
console.log(' --skip-mcpb Skip building MCPB bundle');
143+
console.log(' --skip-git Skip git commit and tag');
144+
console.log(' --skip-npm Skip NPM publishing');
145+
console.log(' --mcp-only Only publish to MCP Registry (skip all other steps)');
112146
console.log(' --dry-run Simulate the release without publishing');
113147
console.log(' --help, -h Show this help message');
114148
console.log('');
@@ -117,6 +151,7 @@ function showHelp() {
117151
console.log(' node scripts/publish-release.cjs --minor # Minor release (0.2.16 -> 0.3.0)');
118152
console.log(' node scripts/publish-release.cjs --major # Major release (0.2.16 -> 1.0.0)');
119153
console.log(' node scripts/publish-release.cjs --dry-run # Test without publishing');
154+
console.log(' node scripts/publish-release.cjs --mcp-only # Only publish to MCP Registry');
120155
}
121156

122157
// Main release function
@@ -153,140 +188,169 @@ async function publishRelease() {
153188
}
154189

155190
try {
191+
let newVersion = currentVersion;
192+
156193
// Step 1: Bump version
157-
printStep('Step 1/7: Bumping version...');
158-
const bumpCommand = options.bumpType === 'minor' ? 'npm run bump:minor' :
159-
options.bumpType === 'major' ? 'npm run bump:major' :
160-
'npm run bump';
161-
exec(bumpCommand);
162-
163-
const newPackageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
164-
const newVersion = newPackageJson.version;
165-
printSuccess(`Version bumped: ${currentVersion}${newVersion}`);
166-
console.log('');
167-
168-
// Step 2: Build project
169-
printStep('Step 2/7: Building project...');
170-
exec('npm run build');
171-
printSuccess('Project built successfully');
194+
if (!options.skipBump) {
195+
printStep('Step 1/6: Bumping version...');
196+
const bumpCommand = options.bumpType === 'minor' ? 'npm run bump:minor' :
197+
options.bumpType === 'major' ? 'npm run bump:major' :
198+
'npm run bump';
199+
exec(bumpCommand);
200+
201+
const newPackageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
202+
newVersion = newPackageJson.version;
203+
printSuccess(`Version bumped: ${currentVersion}${newVersion}`);
204+
console.log('');
205+
} else {
206+
printWarning('Step 1/6: Version bump skipped');
207+
console.log('');
208+
}
172209
console.log('');
173210

174-
// Step 3: Run tests (unless skipped)
175-
if (!options.skipTests) {
176-
printStep('Step 3/7: Running tests...');
211+
// Step 2: Run tests (unless skipped) - tests also build the project
212+
if (!options.skipTests && !options.skipBuild) {
213+
printStep('Step 2/6: Running tests (includes build)...');
177214
exec('npm test');
178215
printSuccess('All tests passed');
216+
} else if (!options.skipBuild) {
217+
printWarning('Step 2/6: Tests skipped - building project...');
218+
exec('npm run build');
219+
printSuccess('Project built successfully');
179220
} else {
180-
printWarning('Step 3/7: Tests skipped');
221+
printWarning('Step 2/6: Tests and build skipped');
181222
}
182223
console.log('');
183224

184-
// Step 4: Build MCPB bundle
185-
printStep('Step 4/7: Building MCPB bundle...');
186-
exec('npm run build:mcpb');
187-
printSuccess('MCPB bundle created');
225+
// Step 3: Build MCPB bundle
226+
if (!options.skipMcpb) {
227+
printStep('Step 3/6: Building MCPB bundle...');
228+
exec('npm run build:mcpb');
229+
printSuccess('MCPB bundle created');
230+
} else {
231+
printWarning('Step 3/6: MCPB bundle build skipped');
232+
}
188233
console.log('');
189234

190-
// Step 5: Commit and tag
191-
printStep('Step 5/7: Creating git commit and tag...');
235+
// Step 4: Commit and tag
236+
if (!options.skipGit) {
237+
printStep('Step 4/6: Creating git commit and tag...');
192238

193-
// Check if there are changes to commit
194-
const gitStatus = execSilent('git status --porcelain', { ignoreError: true });
195-
const hasChanges = gitStatus.includes('package.json') ||
196-
gitStatus.includes('server.json') ||
197-
gitStatus.includes('src/version.ts');
198-
199-
if (!hasChanges) {
200-
printWarning('No changes to commit (version files already committed)');
201-
} else {
202-
exec('git add package.json server.json src/version.ts');
203-
204-
const commitMsg = `Release v${newVersion}
239+
// Check if there are changes to commit
240+
const gitStatus = execSilent('git status --porcelain', { ignoreError: true });
241+
const hasChanges = gitStatus.includes('package.json') ||
242+
gitStatus.includes('server.json') ||
243+
gitStatus.includes('src/version.ts');
244+
245+
if (!hasChanges) {
246+
printWarning('No changes to commit (version files already committed)');
247+
} else {
248+
exec('git add package.json server.json src/version.ts');
249+
250+
const commitMsg = `Release v${newVersion}
205251
206252
Automated release commit with version bump from ${currentVersion} to ${newVersion}`;
207253

254+
if (options.dryRun) {
255+
printWarning(`Would commit: ${commitMsg.split('\n')[0]}`);
256+
} else {
257+
exec(`git commit -m "${commitMsg}"`);
258+
printSuccess('Changes committed');
259+
}
260+
}
261+
262+
// Create and push tag
263+
const tagName = `v${newVersion}`;
264+
208265
if (options.dryRun) {
209-
printWarning(`Would commit: ${commitMsg.split('\n')[0]}`);
266+
printWarning(`Would create tag: ${tagName}`);
267+
printWarning(`Would push to origin: main and ${tagName}`);
210268
} else {
211-
exec(`git commit -m "${commitMsg}"`);
212-
printSuccess('Changes committed');
269+
exec(`git tag ${tagName}`);
270+
exec('git push origin main');
271+
exec(`git push origin ${tagName}`);
272+
printSuccess(`Tag ${tagName} created and pushed`);
213273
}
214-
}
215-
216-
// Create and push tag
217-
const tagName = `v${newVersion}`;
218-
219-
if (options.dryRun) {
220-
printWarning(`Would create tag: ${tagName}`);
221-
printWarning(`Would push to origin: main and ${tagName}`);
222274
} else {
223-
exec(`git tag ${tagName}`);
224-
exec('git push origin main');
225-
exec(`git push origin ${tagName}`);
226-
printSuccess(`Tag ${tagName} created and pushed`);
275+
printWarning('Step 4/6: Git commit and tag skipped');
227276
}
228277
console.log('');
229278

230-
// Step 6: Publish to NPM
231-
printStep('Step 6/7: Publishing to NPM...');
232-
233-
// Check NPM authentication
234-
const npmUser = execSilent('npm whoami', { ignoreError: true }).trim();
235-
if (!npmUser) {
236-
printError('Not logged into NPM. Please run "npm login" first.');
237-
process.exit(1);
238-
}
239-
printSuccess(`NPM user: ${npmUser}`);
240-
241-
if (options.dryRun) {
242-
printWarning('Would publish to NPM: npm publish');
243-
printWarning('Skipping NPM publish (dry run)');
244-
} else {
245-
exec('npm publish');
246-
printSuccess('Published to NPM');
279+
// Step 5: Publish to NPM
280+
if (!options.skipNpm) {
281+
printStep('Step 5/6: Publishing to NPM...');
247282

248-
// Verify NPM publication
249-
await new Promise(resolve => setTimeout(resolve, 3000)); // Wait 3 seconds
250-
const npmVersion = execSilent('npm view @wonderwhy-er/desktop-commander version', { ignoreError: true }).trim();
251-
if (npmVersion === newVersion) {
252-
printSuccess(`NPM publication verified: v${npmVersion}`);
283+
// Check NPM authentication
284+
const npmUser = execSilent('npm whoami', { ignoreError: true }).trim();
285+
if (!npmUser) {
286+
printError('Not logged into NPM. Please run "npm login" first.');
287+
process.exit(1);
288+
}
289+
printSuccess(`NPM user: ${npmUser}`);
290+
291+
if (options.dryRun) {
292+
printWarning('Would publish to NPM: npm publish');
293+
printWarning('Skipping NPM publish (dry run)');
253294
} else {
254-
printWarning(`NPM version mismatch: expected ${newVersion}, got ${npmVersion} (may take a moment to propagate)`);
295+
exec('npm publish');
296+
printSuccess('Published to NPM');
297+
298+
// Verify NPM publication
299+
await new Promise(resolve => setTimeout(resolve, 3000)); // Wait 3 seconds
300+
const npmVersion = execSilent('npm view @wonderwhy-er/desktop-commander version', { ignoreError: true }).trim();
301+
if (npmVersion === newVersion) {
302+
printSuccess(`NPM publication verified: v${npmVersion}`);
303+
} else {
304+
printWarning(`NPM version mismatch: expected ${newVersion}, got ${npmVersion} (may take a moment to propagate)`);
305+
}
255306
}
307+
} else {
308+
printWarning('Step 5/6: NPM publish skipped');
256309
}
257310
console.log('');
258311

259-
// Step 7: Publish to MCP Registry
260-
printStep('Step 7/7: Publishing to MCP Registry...');
312+
// Step 6: Publish to MCP Registry
313+
printStep('Step 6/6: Publishing to MCP Registry...');
261314

262315
// Check if mcp-publisher is installed
263-
const hasMcpPublisher = execSilent('mcp-publisher --version', { ignoreError: true });
316+
const hasMcpPublisher = execSilent('which mcp-publisher', { ignoreError: true });
264317
if (!hasMcpPublisher) {
265318
printError('mcp-publisher not found. Install it with: brew install mcp-publisher');
319+
printError('Or check your PATH if already installed.');
266320
process.exit(1);
267321
}
268322

269323
if (options.dryRun) {
270324
printWarning('Would publish to MCP Registry: mcp-publisher publish');
271325
printWarning('Skipping MCP Registry publish (dry run)');
272326
} else {
273-
exec('mcp-publisher publish');
274-
printSuccess('Published to MCP Registry');
275-
276-
// Verify MCP Registry publication
277-
await new Promise(resolve => setTimeout(resolve, 3000)); // Wait 3 seconds
278327
try {
279-
const mcpResponse = execSilent('curl -s "https://registry.modelcontextprotocol.io/v0/servers?search=io.github.wonderwhy-er/desktop-commander"');
280-
const mcpData = JSON.parse(mcpResponse);
281-
const mcpVersion = mcpData.servers?.[0]?.version || 'unknown';
328+
exec('mcp-publisher publish');
329+
printSuccess('Published to MCP Registry');
282330

283-
if (mcpVersion === newVersion) {
284-
printSuccess(`MCP Registry publication verified: v${mcpVersion}`);
285-
} else {
286-
printWarning(`MCP Registry version: ${mcpVersion} (expected ${newVersion}, may take a moment to propagate)`);
331+
// Verify MCP Registry publication
332+
await new Promise(resolve => setTimeout(resolve, 3000)); // Wait 3 seconds
333+
try {
334+
const mcpResponse = execSilent('curl -s "https://registry.modelcontextprotocol.io/v0/servers?search=io.github.wonderwhy-er/desktop-commander"');
335+
const mcpData = JSON.parse(mcpResponse);
336+
const mcpVersion = mcpData.servers?.[0]?.version || 'unknown';
337+
338+
if (mcpVersion === newVersion) {
339+
printSuccess(`MCP Registry publication verified: v${mcpVersion}`);
340+
} else {
341+
printWarning(`MCP Registry version: ${mcpVersion} (expected ${newVersion}, may take a moment to propagate)`);
342+
}
343+
} catch (error) {
344+
printWarning('Could not verify MCP Registry publication');
287345
}
288346
} catch (error) {
289-
printWarning('Could not verify MCP Registry publication');
347+
printError('MCP Registry publish failed!');
348+
if (error.message.includes('401') || error.message.includes('expired')) {
349+
printError('Authentication token expired. Please run: mcp-publisher login github');
350+
} else if (error.message.includes('422')) {
351+
printError('Validation error in server.json. Check the error message above for details.');
352+
}
353+
throw error;
290354
}
291355
}
292356
console.log('');

0 commit comments

Comments
 (0)