browserwing 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +10 -0
  2. package/install.js +314 -68
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -15,6 +15,16 @@ pnpm add -g browserwing
15
15
  yarn global add browserwing
16
16
  ```
17
17
 
18
+ ### macOS Users - Important
19
+
20
+ If you encounter an error when running BrowserWing on macOS (app gets killed immediately), run this command:
21
+
22
+ ```bash
23
+ xattr -d com.apple.quarantine $(which browserwing)
24
+ ```
25
+
26
+ This removes the quarantine attribute that macOS applies to downloaded executables. [Learn more](https://github.com/browserwing/browserwing/blob/main/docs/MACOS_INSTALLATION_FIX.md)
27
+
18
28
  ## Quick Start
19
29
 
20
30
  ```bash
package/install.js CHANGED
@@ -1,120 +1,366 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ /**
4
+ * BrowserWing npm postinstall script
5
+ * Downloads the appropriate binary for the current platform
6
+ */
7
+
3
8
  const https = require('https');
9
+ const http = require('http');
4
10
  const fs = require('fs');
5
11
  const path = require('path');
6
12
  const { execSync } = require('child_process');
13
+ const os = require('os');
7
14
 
8
15
  const REPO = 'browserwing/browserwing';
9
- const PACKAGE_VERSION = require('./package.json').version;
16
+ const VERSION = require('./package.json').version;
17
+ const TAG = `v${VERSION}`;
10
18
 
11
- // Platform mapping
12
- const PLATFORM_MAP = {
13
- darwin: 'darwin',
14
- linux: 'linux',
15
- win32: 'windows'
19
+ // ANSI color codes
20
+ const colors = {
21
+ reset: '\x1b[0m',
22
+ green: '\x1b[32m',
23
+ yellow: '\x1b[33m',
24
+ red: '\x1b[31m',
25
+ cyan: '\x1b[36m'
16
26
  };
17
27
 
18
- const ARCH_MAP = {
19
- x64: 'amd64',
20
- arm64: 'arm64',
21
- arm: 'armv7'
22
- };
28
+ function log(message, color = 'reset') {
29
+ console.log(`${colors[color]}${message}${colors.reset}`);
30
+ }
23
31
 
24
- function getPlatform() {
25
- const platform = PLATFORM_MAP[process.platform];
26
- const arch = ARCH_MAP[process.arch];
27
-
28
- if (!platform || !arch) {
29
- throw new Error(`Unsupported platform: ${process.platform}-${process.arch}`);
32
+ function logInfo(message) {
33
+ log(`[INFO] ${message}`, 'green');
34
+ }
35
+
36
+ function logWarning(message) {
37
+ log(`[WARNING] ${message}`, 'yellow');
38
+ }
39
+
40
+ function logError(message) {
41
+ log(`[ERROR] ${message}`, 'red');
42
+ }
43
+
44
+ function logDebug(message) {
45
+ log(`[DEBUG] ${message}`, 'cyan');
46
+ }
47
+
48
+ // Detect platform and architecture
49
+ function detectPlatform() {
50
+ const platform = os.platform();
51
+ const arch = os.arch();
52
+
53
+ let platformName;
54
+ let archName;
55
+ let isWindows = false;
56
+
57
+ // Map platform
58
+ switch (platform) {
59
+ case 'darwin':
60
+ platformName = 'darwin';
61
+ break;
62
+ case 'linux':
63
+ platformName = 'linux';
64
+ break;
65
+ case 'win32':
66
+ platformName = 'windows';
67
+ isWindows = true;
68
+ break;
69
+ default:
70
+ throw new Error(`Unsupported platform: ${platform}`);
71
+ }
72
+
73
+ // Map architecture
74
+ switch (arch) {
75
+ case 'x64':
76
+ archName = 'amd64';
77
+ break;
78
+ case 'arm64':
79
+ archName = 'arm64';
80
+ break;
81
+ default:
82
+ throw new Error(`Unsupported architecture: ${arch}`);
83
+ }
84
+
85
+ return { platform: platformName, arch: archName, isWindows };
86
+ }
87
+
88
+ // Test download speed and select fastest mirror
89
+ async function selectMirror(archiveName) {
90
+ logInfo('Testing download mirrors...');
91
+
92
+ const githubUrl = `https://github.com/${REPO}/releases/download/${TAG}/${archiveName}`;
93
+ const giteeUrl = `https://gitee.com/browserwing/browserwing/releases/download/${TAG}/${archiveName}`;
94
+
95
+ // Test GitHub
96
+ const githubTime = await testUrl(githubUrl);
97
+ logDebug(`GitHub response time: ${githubTime}ms`);
98
+
99
+ // Test Gitee
100
+ const giteeTime = await testUrl(giteeUrl);
101
+ logDebug(`Gitee response time: ${giteeTime}ms`);
102
+
103
+ // Select fastest
104
+ if (githubTime < giteeTime) {
105
+ logInfo('Using GitHub mirror (faster)');
106
+ return {
107
+ name: 'github',
108
+ url: `https://github.com/${REPO}/releases/download`
109
+ };
110
+ } else {
111
+ logInfo('Using Gitee mirror (faster)');
112
+ return {
113
+ name: 'gitee',
114
+ url: 'https://gitee.com/browserwing/browserwing/releases/download'
115
+ };
30
116
  }
31
-
32
- return { platform, arch };
33
117
  }
34
118
 
35
- function getDownloadURL(version, platform, arch) {
36
- const binaryName = platform === 'windows'
37
- ? `browserwing-${platform}-${arch}.exe`
38
- : `browserwing-${platform}-${arch}`;
119
+ // Test URL response time
120
+ function testUrl(url) {
121
+ return new Promise((resolve) => {
122
+ const startTime = Date.now();
123
+ const protocol = url.startsWith('https') ? https : http;
124
+
125
+ const request = protocol.get(url, { method: 'HEAD', timeout: 5000 }, (res) => {
126
+ const elapsed = Date.now() - startTime;
127
+ request.abort();
128
+ resolve(elapsed);
129
+ });
130
+
131
+ request.on('error', () => {
132
+ resolve(999999); // Return large number on error
133
+ });
134
+
135
+ request.on('timeout', () => {
136
+ request.abort();
137
+ resolve(999999);
138
+ });
139
+ });
140
+ }
141
+
142
+ // Download file with retry and mirror fallback
143
+ async function downloadFile(url, dest, maxRetries = 3) {
144
+ let mirror = await selectMirror(path.basename(dest));
145
+ let currentUrl = `${mirror.url}/${TAG}/${path.basename(dest)}`;
39
146
 
40
- return `https://github.com/${REPO}/releases/download/v${version}/${binaryName}`;
147
+ logInfo('Downloading BrowserWing...');
148
+ logInfo(`Download URL: ${currentUrl}`);
149
+
150
+ for (let i = 0; i < maxRetries; i++) {
151
+ try {
152
+ await downloadFileOnce(currentUrl, dest);
153
+ logInfo('Download completed');
154
+ return;
155
+ } catch (error) {
156
+ if (i < maxRetries - 1) {
157
+ logWarning(`Download failed, retrying (${i + 1}/${maxRetries})...`);
158
+
159
+ // Switch mirror
160
+ if (mirror.name === 'github') {
161
+ mirror.name = 'gitee';
162
+ mirror.url = 'https://gitee.com/browserwing/browserwing/releases/download';
163
+ logInfo('Switching to Gitee mirror...');
164
+ } else {
165
+ mirror.name = 'github';
166
+ mirror.url = `https://github.com/${REPO}/releases/download`;
167
+ logInfo('Switching to GitHub mirror...');
168
+ }
169
+
170
+ currentUrl = `${mirror.url}/${TAG}/${path.basename(dest)}`;
171
+ await new Promise(resolve => setTimeout(resolve, 2000));
172
+ } else {
173
+ throw new Error(`Failed to download after ${maxRetries} attempts: ${error.message}`);
174
+ }
175
+ }
176
+ }
41
177
  }
42
178
 
43
- function downloadFile(url, dest) {
179
+ // Download file once
180
+ function downloadFileOnce(url, dest) {
44
181
  return new Promise((resolve, reject) => {
45
- console.log(`Downloading BrowserWing from ${url}...`);
46
-
182
+ const protocol = url.startsWith('https') ? https : http;
47
183
  const file = fs.createWriteStream(dest);
48
-
49
- https.get(url, {
50
- headers: { 'User-Agent': 'browserwing-npm-installer' }
51
- }, (response) => {
52
- // Handle redirects
184
+
185
+ const request = protocol.get(url, (response) => {
53
186
  if (response.statusCode === 302 || response.statusCode === 301) {
54
- return https.get(response.headers.location, (redirectResponse) => {
55
- redirectResponse.pipe(file);
56
- file.on('finish', () => {
57
- file.close();
58
- resolve();
59
- });
60
- }).on('error', reject);
187
+ // Handle redirect
188
+ file.close();
189
+ fs.unlinkSync(dest);
190
+ downloadFileOnce(response.headers.location, dest).then(resolve).catch(reject);
191
+ return;
61
192
  }
62
-
193
+
63
194
  if (response.statusCode !== 200) {
64
- reject(new Error(`Failed to download: ${response.statusCode}`));
195
+ file.close();
196
+ fs.unlinkSync(dest);
197
+ reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
65
198
  return;
66
199
  }
67
-
200
+
68
201
  response.pipe(file);
69
-
202
+
70
203
  file.on('finish', () => {
71
204
  file.close();
72
205
  resolve();
73
206
  });
74
- }).on('error', (err) => {
75
- fs.unlink(dest, () => {});
207
+ });
208
+
209
+ request.on('error', (err) => {
210
+ file.close();
211
+ fs.unlinkSync(dest);
212
+ reject(err);
213
+ });
214
+
215
+ file.on('error', (err) => {
216
+ file.close();
217
+ fs.unlinkSync(dest);
76
218
  reject(err);
77
219
  });
78
220
  });
79
221
  }
80
222
 
223
+ // Extract archive
224
+ function extractArchive(archivePath, destDir, isWindows) {
225
+ logInfo('Extracting archive...');
226
+
227
+ try {
228
+ if (isWindows) {
229
+ // Windows: use PowerShell to extract zip
230
+ const psCommand = `Expand-Archive -Path "${archivePath}" -DestinationPath "${destDir}" -Force`;
231
+ execSync(`powershell -command "${psCommand}"`, { stdio: 'ignore' });
232
+ } else {
233
+ // Unix: use tar
234
+ execSync(`tar -xzf "${archivePath}" -C "${destDir}"`, { stdio: 'ignore' });
235
+ }
236
+ logInfo('Extraction completed');
237
+ } catch (error) {
238
+ throw new Error(`Failed to extract archive: ${error.message}`);
239
+ }
240
+ }
241
+
242
+ // Fix macOS code signature issue
243
+ function fixMacOSCodeSignature(binaryPath) {
244
+ logInfo('Fixing macOS code signature...');
245
+
246
+ try {
247
+ // Method 1: Try to remove quarantine attribute
248
+ try {
249
+ execSync(`xattr -d com.apple.quarantine "${binaryPath}" 2>/dev/null`, { stdio: 'ignore' });
250
+ logInfo('✓ Removed quarantine attribute');
251
+ } catch (e) {
252
+ // Quarantine attribute may not exist, continue
253
+ logDebug('No quarantine attribute to remove');
254
+ }
255
+
256
+ // Method 2: Try ad-hoc code signing
257
+ try {
258
+ execSync(`codesign -s - "${binaryPath}" 2>/dev/null`, { stdio: 'ignore' });
259
+ logInfo('✓ Applied ad-hoc code signature');
260
+ } catch (e) {
261
+ // codesign may fail, but that's ok if quarantine removal worked
262
+ logDebug('Ad-hoc signing skipped (may require manual approval)');
263
+ }
264
+
265
+ logInfo('macOS security fix applied');
266
+ } catch (error) {
267
+ // Don't fail installation if this doesn't work
268
+ logWarning('Could not automatically fix macOS security settings');
269
+ logWarning('If the app fails to start, run: xattr -d com.apple.quarantine ' + binaryPath);
270
+ }
271
+ }
272
+
273
+ // Main installation function
81
274
  async function install() {
275
+ console.log('');
276
+ console.log('╔════════════════════════════════════════╗');
277
+ console.log('║ BrowserWing npm Installation ║');
278
+ console.log('╚════════════════════════════════════════╝');
279
+ console.log('');
280
+
82
281
  try {
83
- const { platform, arch } = getPlatform();
84
- console.log(`Installing BrowserWing for ${platform}-${arch}...`);
85
-
86
- // Create bin directory
282
+ // Detect platform
283
+ const { platform, arch, isWindows } = detectPlatform();
284
+ logInfo(`Detected platform: ${platform}-${arch}`);
285
+
286
+ // Determine archive and binary names
287
+ const archiveExt = isWindows ? '.zip' : '.tar.gz';
288
+ const binaryExt = isWindows ? '.exe' : '';
289
+ const archiveName = `browserwing-${platform}-${arch}${archiveExt}`;
290
+ const binaryName = `browserwing-${platform}-${arch}${binaryExt}`;
291
+
292
+ // Download paths
87
293
  const binDir = path.join(__dirname, 'bin');
294
+ const archivePath = path.join(binDir, archiveName);
295
+ const binaryPath = path.join(binDir, `browserwing${binaryExt}`);
296
+
297
+ // Create bin directory
88
298
  if (!fs.existsSync(binDir)) {
89
299
  fs.mkdirSync(binDir, { recursive: true });
90
300
  }
91
-
92
- // Determine binary name
93
- const binaryName = platform === 'windows' ? 'browserwing.exe' : 'browserwing';
94
- const binaryPath = path.join(binDir, binaryName);
95
-
96
- // Download binary
97
- const downloadURL = getDownloadURL(PACKAGE_VERSION, platform, arch);
98
- await downloadFile(downloadURL, binaryPath);
99
-
100
- // Make executable on Unix-like systems
101
- if (platform !== 'windows') {
301
+
302
+ // Download archive
303
+ await downloadFile(archiveName, archivePath);
304
+
305
+ // Extract archive
306
+ extractArchive(archivePath, binDir, isWindows);
307
+
308
+ // Find extracted binary
309
+ const extractedBinary = path.join(binDir, binaryName);
310
+ if (!fs.existsSync(extractedBinary)) {
311
+ throw new Error(`Binary not found after extraction: ${binaryName}`);
312
+ }
313
+
314
+ // Move binary to final location
315
+ if (fs.existsSync(binaryPath)) {
316
+ fs.unlinkSync(binaryPath);
317
+ }
318
+ fs.renameSync(extractedBinary, binaryPath);
319
+
320
+ // Set executable permissions (Unix only)
321
+ if (!isWindows) {
102
322
  fs.chmodSync(binaryPath, 0o755);
103
323
  }
104
-
105
- console.log('BrowserWing installed successfully!');
324
+
325
+ // Fix macOS code signature issue
326
+ if (platform === 'darwin') {
327
+ fixMacOSCodeSignature(binaryPath);
328
+ }
329
+
330
+ // Cleanup archive
331
+ fs.unlinkSync(archivePath);
332
+
333
+ console.log('');
334
+ logInfo('BrowserWing installed successfully!');
106
335
  console.log('');
107
336
  console.log('Quick start:');
108
- console.log(' browserwing --port 8080');
109
- console.log(' Open http://localhost:8080');
337
+ console.log(' 1. Run: browserwing --port 8080');
338
+ console.log(' 2. Open: http://localhost:8080');
339
+ console.log('');
110
340
 
341
+ // macOS specific notice
342
+ if (platform === 'darwin') {
343
+ console.log(colors.yellow + '⚠️ macOS Users:' + colors.reset);
344
+ console.log(' If the app fails to start, run this command:');
345
+ console.log(' xattr -d com.apple.quarantine $(which browserwing)');
346
+ console.log('');
347
+ console.log(' See: https://github.com/' + REPO + '/blob/main/docs/MACOS_INSTALLATION_FIX.md');
348
+ console.log('');
349
+ }
350
+
351
+ console.log('Documentation: https://github.com/' + REPO);
352
+ console.log('中文文档: https://gitee.com/browserwing/browserwing');
353
+ console.log('');
354
+
111
355
  } catch (error) {
112
- console.error('Installation failed:', error.message);
113
- console.error('');
114
- console.error('You can manually download from:');
115
- console.error(`https://github.com/${REPO}/releases`);
356
+ logError(error.message);
357
+ console.log('');
358
+ console.log('Manual installation:');
359
+ console.log(` Visit: https://github.com/${REPO}/releases/tag/${TAG}`);
360
+ console.log('');
116
361
  process.exit(1);
117
362
  }
118
363
  }
119
364
 
365
+ // Run installation
120
366
  install();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browserwing",
3
- "version": "0.0.1",
3
+ "version": "1.0.0",
4
4
  "description": "Native Browser Automation Platform with AI Integration",
5
5
  "keywords": [
6
6
  "browser",