Skip to content

Commit 16e3138

Browse files
committed
feat(mcp): add zero-dependency publish support
- Add prepare-publish.js script to backup and clear dependencies before publish - Add restore-deps.js script to restore dependencies after publish - Add emergency-restore.js script for manual recovery - Update package.json with prepublishOnly and postpublish hooks - Add package.json.backup to .gitignore - Add requirements spec for zero-dependency publish optimization This enables zero-dependency npm package publishing, improving installation speed and npx experience by removing dependencies from published package.json while keeping them in development.
1 parent 83cb4f7 commit 16e3138

File tree

6 files changed

+608
-1
lines changed

6 files changed

+608
-1
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
**/*.dxt
55
# Environment variables
66
.env.local
7+
# Package publish backup files
8+
**/package.json.backup

mcp/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"test:ui": "npm run build && vitest --ui",
4040
"test:coverage": "npm run build && vitest run --coverage",
4141
"test:inspector": "npm run build && npx @modelcontextprotocol/inspector node ./dist/cli.js",
42-
"prepublishOnly": "npm run build"
42+
"prepublishOnly": "npm run build && node scripts/prepare-publish.js",
43+
"postpublish": "node scripts/restore-deps.js"
4344
},
4445
"files": [
4546
"dist",

mcp/scripts/emergency-restore.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Emergency restore script
5+
* Use this if dependencies were not restored after a failed publish
6+
*
7+
* Usage: node scripts/emergency-restore.js
8+
*/
9+
10+
const fs = require('fs');
11+
const path = require('path');
12+
13+
const packagePath = path.join(__dirname, '../package.json');
14+
const backupPath = path.join(__dirname, '../package.json.backup');
15+
16+
console.log('🔧 Emergency Dependency Restore Script');
17+
console.log('');
18+
19+
if (!fs.existsSync(backupPath)) {
20+
console.error('❌ No backup file found at:', backupPath);
21+
console.error(' Cannot restore dependencies');
22+
process.exit(1);
23+
}
24+
25+
try {
26+
const backup = JSON.parse(fs.readFileSync(backupPath, 'utf8'));
27+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
28+
29+
if (!backup.dependencies) {
30+
console.error('❌ Backup file does not contain dependencies');
31+
process.exit(1);
32+
}
33+
34+
console.log('📦 Backup information:');
35+
console.log(` Package: ${backup.name || 'unknown'}`);
36+
console.log(` Version: ${backup.version || 'unknown'}`);
37+
console.log(` Timestamp: ${backup.timestamp || 'unknown'}`);
38+
console.log(` Dependencies count: ${Object.keys(backup.dependencies).length}`);
39+
console.log('');
40+
41+
packageJson.dependencies = backup.dependencies;
42+
43+
fs.writeFileSync(
44+
packagePath,
45+
JSON.stringify(packageJson, null, 2) + '\n',
46+
'utf8'
47+
);
48+
49+
console.log('✓ Dependencies restored successfully');
50+
console.log(` Restored ${Object.keys(backup.dependencies).length} dependencies`);
51+
console.log('');
52+
console.log('⚠️ Backup file still exists. Remove it manually if restoration was successful:');
53+
console.log(` rm ${backupPath}`);
54+
55+
} catch (error) {
56+
console.error('❌ Error during emergency restore:', error.message);
57+
process.exit(1);
58+
}
59+

mcp/scripts/prepare-publish.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Prepare package.json for publish by removing dependencies
5+
* This script backs up dependencies and clears them before publishing
6+
*
7+
* Safety features:
8+
* - Validates package.json before and after modification
9+
* - Creates backup with timestamp
10+
* - Handles errors gracefully with automatic cleanup
11+
*/
12+
13+
const fs = require('fs');
14+
const path = require('path');
15+
16+
const packagePath = path.join(__dirname, '../package.json');
17+
const backupPath = path.join(__dirname, '../package.json.backup');
18+
19+
// Validate package.json structure
20+
function validatePackageJson(pkg) {
21+
if (!pkg.name || !pkg.version) {
22+
throw new Error('Invalid package.json: missing name or version');
23+
}
24+
return true;
25+
}
26+
27+
// Read and parse package.json with validation
28+
function readPackageJson() {
29+
if (!fs.existsSync(packagePath)) {
30+
throw new Error(`package.json not found at ${packagePath}`);
31+
}
32+
33+
const content = fs.readFileSync(packagePath, 'utf8');
34+
const pkg = JSON.parse(content);
35+
validatePackageJson(pkg);
36+
return pkg;
37+
}
38+
39+
// Write package.json with validation
40+
function writePackageJson(pkg) {
41+
validatePackageJson(pkg);
42+
const content = JSON.stringify(pkg, null, 2) + '\n';
43+
44+
// Verify we can parse it back
45+
try {
46+
JSON.parse(content);
47+
} catch (e) {
48+
throw new Error(`Generated invalid JSON: ${e.message}`);
49+
}
50+
51+
fs.writeFileSync(packagePath, content, 'utf8');
52+
}
53+
54+
try {
55+
// Read package.json
56+
const packageJson = readPackageJson();
57+
58+
// Check if dependencies exist
59+
if (!packageJson.dependencies || Object.keys(packageJson.dependencies).length === 0) {
60+
console.log('⚠️ No dependencies to clear, skipping backup');
61+
process.exit(0);
62+
}
63+
64+
// Check if backup already exists (from previous failed publish)
65+
if (fs.existsSync(backupPath)) {
66+
console.log('⚠️ Backup file already exists, restoring first...');
67+
try {
68+
const existingBackup = JSON.parse(fs.readFileSync(backupPath, 'utf8'));
69+
if (existingBackup.dependencies) {
70+
packageJson.dependencies = existingBackup.dependencies;
71+
writePackageJson(packageJson);
72+
console.log('✓ Restored dependencies from existing backup');
73+
}
74+
} catch (e) {
75+
console.warn('⚠️ Failed to restore from existing backup, continuing...');
76+
}
77+
}
78+
79+
// Backup original dependencies
80+
const originalDeps = { ...packageJson.dependencies };
81+
const backup = {
82+
dependencies: originalDeps,
83+
timestamp: new Date().toISOString(),
84+
version: packageJson.version,
85+
name: packageJson.name
86+
};
87+
88+
fs.writeFileSync(
89+
backupPath,
90+
JSON.stringify(backup, null, 2),
91+
'utf8'
92+
);
93+
94+
console.log('✓ Dependencies backed up to package.json.backup');
95+
console.log(` Backup contains ${Object.keys(originalDeps).length} dependencies`);
96+
97+
// Clear dependencies for publish
98+
packageJson.dependencies = {};
99+
100+
// Write modified package.json with validation
101+
writePackageJson(packageJson);
102+
103+
console.log('✓ Dependencies cleared for publish');
104+
console.log(` Original dependencies count: ${Object.keys(originalDeps).length}`);
105+
console.log(` Current dependencies count: 0`);
106+
107+
// Verify the modification
108+
const verifyPkg = readPackageJson();
109+
if (Object.keys(verifyPkg.dependencies || {}).length !== 0) {
110+
throw new Error('Failed to clear dependencies');
111+
}
112+
113+
} catch (error) {
114+
console.error('❌ Error preparing for publish:', error.message);
115+
console.error(' Stack:', error.stack);
116+
117+
// Try to restore if we have a backup
118+
if (fs.existsSync(backupPath)) {
119+
try {
120+
const backup = JSON.parse(fs.readFileSync(backupPath, 'utf8'));
121+
const packageJson = readPackageJson();
122+
if (backup.dependencies) {
123+
packageJson.dependencies = backup.dependencies;
124+
writePackageJson(packageJson);
125+
console.log('✓ Attempted to restore dependencies from backup');
126+
}
127+
} catch (restoreError) {
128+
console.error('❌ Failed to restore dependencies:', restoreError.message);
129+
console.error(' Please manually restore from package.json.backup');
130+
}
131+
}
132+
133+
process.exit(1);
134+
}
135+

mcp/scripts/restore-deps.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Restore package.json dependencies after publish
5+
* This script restores dependencies from backup file
6+
*
7+
* Safety features:
8+
* - Validates backup and package.json before restoration
9+
* - Verifies restoration was successful
10+
* - Handles errors gracefully
11+
*/
12+
13+
const fs = require('fs');
14+
const path = require('path');
15+
16+
const packagePath = path.join(__dirname, '../package.json');
17+
const backupPath = path.join(__dirname, '../package.json.backup');
18+
19+
// Validate package.json structure
20+
function validatePackageJson(pkg) {
21+
if (!pkg.name || !pkg.version) {
22+
throw new Error('Invalid package.json: missing name or version');
23+
}
24+
return true;
25+
}
26+
27+
// Read and parse package.json with validation
28+
function readPackageJson() {
29+
if (!fs.existsSync(packagePath)) {
30+
throw new Error(`package.json not found at ${packagePath}`);
31+
}
32+
33+
const content = fs.readFileSync(packagePath, 'utf8');
34+
const pkg = JSON.parse(content);
35+
validatePackageJson(pkg);
36+
return pkg;
37+
}
38+
39+
// Write package.json with validation
40+
function writePackageJson(pkg) {
41+
validatePackageJson(pkg);
42+
const content = JSON.stringify(pkg, null, 2) + '\n';
43+
44+
// Verify we can parse it back
45+
try {
46+
JSON.parse(content);
47+
} catch (e) {
48+
throw new Error(`Generated invalid JSON: ${e.message}`);
49+
}
50+
51+
fs.writeFileSync(packagePath, content, 'utf8');
52+
}
53+
54+
try {
55+
// Check if backup exists
56+
if (!fs.existsSync(backupPath)) {
57+
console.log('⚠️ No backup file found, skipping restore');
58+
console.log(' This is normal if dependencies were already empty');
59+
process.exit(0);
60+
}
61+
62+
// Read backup and package.json
63+
const backup = JSON.parse(fs.readFileSync(backupPath, 'utf8'));
64+
const packageJson = readPackageJson();
65+
66+
// Validate backup structure
67+
if (!backup.dependencies || typeof backup.dependencies !== 'object') {
68+
console.log('⚠️ Backup file exists but contains no valid dependencies');
69+
fs.unlinkSync(backupPath);
70+
process.exit(0);
71+
}
72+
73+
// Verify package name and version match (safety check)
74+
if (backup.name && backup.name !== packageJson.name) {
75+
console.warn(`⚠️ Package name mismatch: backup=${backup.name}, current=${packageJson.name}`);
76+
}
77+
if (backup.version && backup.version !== packageJson.version) {
78+
console.warn(`⚠️ Package version mismatch: backup=${backup.version}, current=${packageJson.version}`);
79+
console.warn(' This might be expected if version was updated during publish');
80+
}
81+
82+
// Restore dependencies
83+
const originalDepsCount = Object.keys(packageJson.dependencies || {}).length;
84+
packageJson.dependencies = { ...backup.dependencies };
85+
86+
// Write restored package.json with validation
87+
writePackageJson(packageJson);
88+
89+
// Verify restoration
90+
const verifyPkg = readPackageJson();
91+
const restoredDepsCount = Object.keys(verifyPkg.dependencies || {}).length;
92+
const expectedDepsCount = Object.keys(backup.dependencies).length;
93+
94+
if (restoredDepsCount !== expectedDepsCount) {
95+
throw new Error(`Restoration verification failed: expected ${expectedDepsCount} dependencies, got ${restoredDepsCount}`);
96+
}
97+
98+
console.log('✓ Dependencies restored');
99+
console.log(` Previous dependencies count: ${originalDepsCount}`);
100+
console.log(` Restored dependencies count: ${restoredDepsCount}`);
101+
if (backup.timestamp) {
102+
console.log(` Backup timestamp: ${backup.timestamp}`);
103+
}
104+
105+
// Remove backup file
106+
fs.unlinkSync(backupPath);
107+
console.log('✓ Backup file removed');
108+
109+
} catch (error) {
110+
console.error('❌ Error restoring dependencies:', error.message);
111+
console.error(' Stack:', error.stack);
112+
console.error('');
113+
console.error('⚠️ IMPORTANT: Dependencies may not be restored!');
114+
console.error(' Please check package.json and manually restore from package.json.backup if needed');
115+
console.error('');
116+
console.error(' To manually restore:');
117+
console.error(' 1. Check if package.json.backup exists');
118+
console.error(' 2. Copy dependencies from backup to package.json');
119+
console.error(' 3. Run: node scripts/restore-deps.js');
120+
121+
process.exit(1);
122+
}
123+

0 commit comments

Comments
 (0)