Skip to content

Commit c3105b4

Browse files
author
Test User
committed
Add bin/doplan.js
1 parent ce28434 commit c3105b4

File tree

1 file changed

+218
-0
lines changed

1 file changed

+218
-0
lines changed

bin/doplan.js

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* DoPlan CLI wrapper script
5+
* Downloads and executes the platform-specific binary from GitHub releases
6+
*/
7+
8+
const { execSync } = require('child_process');
9+
const fs = require('fs');
10+
const path = require('path');
11+
const os = require('os');
12+
13+
const BINARY_NAME = 'doplan';
14+
const REPO_OWNER = 'DoPlan-dev';
15+
const REPO_NAME = 'CLI';
16+
const VERSION = require('../package.json').version;
17+
18+
// Get platform information
19+
function getPlatformInfo() {
20+
const platform = os.platform();
21+
const arch = os.arch();
22+
23+
let osName, archName;
24+
25+
switch (platform) {
26+
case 'darwin':
27+
osName = 'darwin';
28+
break;
29+
case 'linux':
30+
osName = 'linux';
31+
break;
32+
case 'win32':
33+
osName = 'windows';
34+
break;
35+
default:
36+
throw new Error(`Unsupported platform: ${platform}`);
37+
}
38+
39+
switch (arch) {
40+
case 'x64':
41+
archName = 'amd64';
42+
break;
43+
case 'arm64':
44+
archName = 'arm64';
45+
break;
46+
default:
47+
throw new Error(`Unsupported architecture: ${arch}`);
48+
}
49+
50+
return { osName, archName, platform, arch };
51+
}
52+
53+
// Get binary path
54+
function getBinaryPath() {
55+
const { osName, archName, platform } = getPlatformInfo();
56+
const binaryDir = path.join(__dirname, '..', 'bin', `${osName}-${archName}`);
57+
const ext = platform === 'win32' ? '.exe' : '';
58+
return path.join(binaryDir, `${BINARY_NAME}${ext}`);
59+
}
60+
61+
// Download binary from GitHub releases
62+
function downloadBinary() {
63+
const { osName, archName, platform } = getPlatformInfo();
64+
const binaryDir = path.join(__dirname, '..', 'bin', `${osName}-${archName}`);
65+
const ext = platform === 'win32' ? '.exe' : '';
66+
const binaryPath = path.join(binaryDir, `${BINARY_NAME}${ext}`);
67+
68+
// Create binary directory if it doesn't exist
69+
if (!fs.existsSync(binaryDir)) {
70+
fs.mkdirSync(binaryDir, { recursive: true });
71+
}
72+
73+
// Check if binary already exists
74+
if (fs.existsSync(binaryPath)) {
75+
try {
76+
// Verify binary is executable
77+
fs.chmodSync(binaryPath, '755');
78+
return binaryPath;
79+
} catch (err) {
80+
// If chmod fails, try to download again
81+
console.warn('Binary exists but may be corrupted, re-downloading...');
82+
}
83+
}
84+
85+
// Download from GitHub releases
86+
const archiveName = `${BINARY_NAME}_${VERSION}_${osName}_${archName}.tar.gz`;
87+
const releaseUrl = `https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download/v${VERSION}/${archiveName}`;
88+
89+
console.log(`Downloading DoPlan CLI v${VERSION} for ${osName}-${archName}...`);
90+
console.log(`URL: ${releaseUrl}`);
91+
92+
try {
93+
// Download and extract
94+
const tempDir = path.join(os.tmpdir(), `doplan-${Date.now()}`);
95+
fs.mkdirSync(tempDir, { recursive: true });
96+
97+
// Use curl or wget to download
98+
let downloadCmd;
99+
if (platform === 'win32') {
100+
// Windows: use PowerShell or curl if available
101+
downloadCmd = `powershell -Command "Invoke-WebRequest -Uri '${releaseUrl}' -OutFile '${path.join(tempDir, archiveName)}'"`;
102+
} else {
103+
// Unix-like: use curl or wget
104+
downloadCmd = `curl -L -o '${path.join(tempDir, archiveName)}' '${releaseUrl}'`;
105+
}
106+
107+
execSync(downloadCmd, { stdio: 'inherit' });
108+
109+
// Extract archive
110+
if (platform === 'win32') {
111+
// Windows: use tar if available, or 7zip
112+
try {
113+
execSync(`tar -xzf '${path.join(tempDir, archiveName)}' -C '${tempDir}'`, { stdio: 'inherit' });
114+
} catch (err) {
115+
// Try PowerShell extraction
116+
execSync(`powershell -Command "Expand-Archive -Path '${path.join(tempDir, archiveName)}' -DestinationPath '${tempDir}'"`, { stdio: 'inherit' });
117+
}
118+
} else {
119+
execSync(`tar -xzf '${path.join(tempDir, archiveName)}' -C '${tempDir}'`, { stdio: 'inherit' });
120+
}
121+
122+
// Find the binary in extracted files (could be in root or subdirectory)
123+
let extractedBinary = path.join(tempDir, BINARY_NAME + ext);
124+
125+
// If not in root, search for it recursively
126+
if (!fs.existsSync(extractedBinary)) {
127+
function findBinary(dir) {
128+
const files = fs.readdirSync(dir);
129+
for (const file of files) {
130+
const filePath = path.join(dir, file);
131+
const stat = fs.statSync(filePath);
132+
if (stat.isDirectory()) {
133+
const found = findBinary(filePath);
134+
if (found) return found;
135+
} else if (file === BINARY_NAME + ext) {
136+
return filePath;
137+
}
138+
}
139+
return null;
140+
}
141+
const found = findBinary(tempDir);
142+
if (found) {
143+
extractedBinary = found;
144+
}
145+
}
146+
147+
if (fs.existsSync(extractedBinary)) {
148+
fs.copyFileSync(extractedBinary, binaryPath);
149+
fs.chmodSync(binaryPath, '755');
150+
151+
// Cleanup
152+
fs.rmSync(tempDir, { recursive: true, force: true });
153+
154+
console.log(`Successfully downloaded DoPlan CLI v${VERSION}`);
155+
return binaryPath;
156+
} else {
157+
throw new Error(`Binary not found in downloaded archive. Searched in: ${tempDir}`);
158+
}
159+
} catch (error) {
160+
console.error(`Failed to download binary: ${error.message}`);
161+
console.error(`\nPlease install manually from: https://github.com/${REPO_OWNER}/${REPO_NAME}/releases`);
162+
process.exit(1);
163+
}
164+
}
165+
166+
// Main execution
167+
function main() {
168+
let binaryPath = getBinaryPath();
169+
170+
// If binary doesn't exist, download it
171+
if (!fs.existsSync(binaryPath)) {
172+
binaryPath = downloadBinary();
173+
}
174+
175+
// Execute the binary with all arguments
176+
const args = process.argv.slice(2);
177+
const spawn = require('child_process').spawn;
178+
179+
const child = spawn(binaryPath, args, {
180+
stdio: 'inherit',
181+
shell: false
182+
});
183+
184+
child.on('error', (error) => {
185+
if (error.code === 'ENOENT') {
186+
console.error(`Binary not found: ${binaryPath}`);
187+
console.error('Attempting to download...');
188+
binaryPath = downloadBinary();
189+
// Retry execution
190+
const retry = spawn(binaryPath, args, {
191+
stdio: 'inherit',
192+
shell: false
193+
});
194+
retry.on('error', (retryError) => {
195+
console.error(`Failed to execute binary: ${retryError.message}`);
196+
process.exit(1);
197+
});
198+
retry.on('exit', (code) => {
199+
process.exit(code || 0);
200+
});
201+
} else {
202+
console.error(`Error executing binary: ${error.message}`);
203+
process.exit(1);
204+
}
205+
});
206+
207+
child.on('exit', (code) => {
208+
process.exit(code || 0);
209+
});
210+
}
211+
212+
// Run if executed directly
213+
if (require.main === module) {
214+
main();
215+
}
216+
217+
module.exports = { getBinaryPath, downloadBinary, getPlatformInfo };
218+

0 commit comments

Comments
 (0)