@gw-tools/gw 0.12.19 → 0.12.22

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 (2) hide show
  1. package/install.js +91 -34
  2. package/package.json +1 -1
package/install.js CHANGED
@@ -5,7 +5,14 @@
5
5
  * Downloads the appropriate binary for the current platform
6
6
  */
7
7
 
8
- const { existsSync, mkdirSync, chmodSync } = require('fs');
8
+ const {
9
+ existsSync,
10
+ mkdirSync,
11
+ chmodSync,
12
+ openSync,
13
+ fsyncSync,
14
+ closeSync,
15
+ } = require('fs');
9
16
  const { join } = require('path');
10
17
  const { arch, platform } = require('os');
11
18
  const https = require('https');
@@ -39,7 +46,7 @@ function getBinaryName() {
39
46
  if (!os || !cpu) {
40
47
  console.error(
41
48
  `Unsupported platform: ${platform()}-${arch()}\n` +
42
- 'Supported platforms: macOS (x64, arm64), Linux (x64, arm64), Windows (x64, arm64)'
49
+ 'Supported platforms: macOS (x64, arm64), Linux (x64, arm64), Windows (x64, arm64)',
43
50
  );
44
51
  process.exit(1);
45
52
  }
@@ -55,26 +62,43 @@ function download(url, dest) {
55
62
  return new Promise((resolve, reject) => {
56
63
  const file = createWriteStream(dest);
57
64
 
58
- https.get(url, { headers: { 'User-Agent': 'gw-npm-installer' } }, (response) => {
59
- // Handle redirects
60
- if (response.statusCode === 302 || response.statusCode === 301) {
61
- return download(response.headers.location, dest).then(resolve).catch(reject);
62
- }
63
-
64
- if (response.statusCode !== 200) {
65
- reject(new Error(`Failed to download: ${response.statusCode} ${response.statusMessage}`));
66
- return;
67
- }
68
-
69
- response.pipe(file);
70
-
71
- file.on('finish', () => {
72
- file.close();
73
- resolve();
65
+ https
66
+ .get(
67
+ url,
68
+ { headers: { 'User-Agent': 'gw-npm-installer' } },
69
+ (response) => {
70
+ // Handle redirects
71
+ if (response.statusCode === 302 || response.statusCode === 301) {
72
+ return download(response.headers.location, dest)
73
+ .then(resolve)
74
+ .catch(reject);
75
+ }
76
+
77
+ if (response.statusCode !== 200) {
78
+ reject(
79
+ new Error(
80
+ `Failed to download: ${response.statusCode} ${response.statusMessage}`,
81
+ ),
82
+ );
83
+ return;
84
+ }
85
+
86
+ response.pipe(file);
87
+
88
+ file.on('finish', () => {
89
+ file.close((err) => {
90
+ if (err) {
91
+ reject(err);
92
+ } else {
93
+ resolve();
94
+ }
95
+ });
96
+ });
97
+ },
98
+ )
99
+ .on('error', (err) => {
100
+ reject(err);
74
101
  });
75
- }).on('error', (err) => {
76
- reject(err);
77
- });
78
102
 
79
103
  file.on('error', (err) => {
80
104
  reject(err);
@@ -106,22 +130,34 @@ async function install() {
106
130
  console.log(`Downloading from: ${downloadUrl}`);
107
131
  await download(downloadUrl, binaryPath);
108
132
 
109
- // Make binary executable on Unix-like systems
133
+ // Ensure file is fully written to disk before chmod
134
+ // This prevents ETXTBSY errors on Linux systems
110
135
  if (platform() !== 'win32') {
136
+ const fd = openSync(binaryPath, 'r+');
137
+ fsyncSync(fd);
138
+ closeSync(fd);
139
+
140
+ // Small delay after fsync before chmod
141
+ await new Promise((resolve) => setTimeout(resolve, 100));
142
+
111
143
  chmodSync(binaryPath, 0o755);
112
- // Small delay to prevent ETXTBSY race condition on Linux
113
- // The file can be "text busy" immediately after chmod
114
- await new Promise(resolve => setTimeout(resolve, 100));
144
+
145
+ // Additional delay after chmod to prevent ETXTBSY
146
+ await new Promise((resolve) => setTimeout(resolve, 200));
115
147
  }
116
148
 
117
149
  console.log('✓ Installation complete!');
118
150
 
119
- // Install shell integration
151
+ // Try to install shell integration, but don't fail if it errors
120
152
  console.log('\n⚙️ Setting up shell integration...');
121
- await installShellIntegration(binaryPath);
153
+ try {
154
+ await installShellIntegration(binaryPath);
155
+ } catch (error) {
156
+ console.log(' Shell integration setup encountered an issue.');
157
+ console.log(' You can install it manually later with: gw install-shell');
158
+ }
122
159
 
123
160
  console.log('\nRun "gw --help" to get started.');
124
- console.log('Tip: Restart your terminal or run "source ~/.zshrc" (or ~/.bashrc) to use "gw cd"');
125
161
  } catch (error) {
126
162
  console.error('\n✗ Installation failed:', error.message);
127
163
  console.error('\nYou can manually download the binary from:');
@@ -141,15 +177,34 @@ async function installShellIntegration(binaryPath, retries = 3) {
141
177
  const { spawn } = require('child_process');
142
178
 
143
179
  return new Promise((resolve) => {
144
- const child = spawn(binaryPath, ['install-shell', '--quiet'], {
145
- stdio: 'inherit'
146
- });
180
+ let child;
181
+
182
+ try {
183
+ child = spawn(binaryPath, ['install-shell', '--quiet'], {
184
+ stdio: 'inherit',
185
+ });
186
+ } catch (err) {
187
+ // Catch synchronous spawn errors (e.g., ETXTBSY thrown immediately)
188
+ if (err.code === 'ETXTBSY' && retries > 0) {
189
+ setTimeout(() => {
190
+ installShellIntegration(binaryPath, retries - 1).then(resolve);
191
+ }, 200);
192
+ return;
193
+ }
194
+ console.log(
195
+ ' (Shell integration can be installed later with: gw install-shell)',
196
+ );
197
+ resolve();
198
+ return;
199
+ }
147
200
 
148
201
  child.on('close', (code) => {
149
202
  if (code === 0) {
150
203
  console.log('✓ Shell integration installed!');
151
204
  } else {
152
- console.log(' (Shell integration can be installed later with: gw install-shell)');
205
+ console.log(
206
+ ' (Shell integration can be installed later with: gw install-shell)',
207
+ );
153
208
  }
154
209
  resolve();
155
210
  });
@@ -157,10 +212,12 @@ async function installShellIntegration(binaryPath, retries = 3) {
157
212
  child.on('error', async (err) => {
158
213
  // Retry on ETXTBSY (text file busy) error
159
214
  if (err.code === 'ETXTBSY' && retries > 0) {
160
- await new Promise(r => setTimeout(r, 200));
215
+ await new Promise((r) => setTimeout(r, 200));
161
216
  return installShellIntegration(binaryPath, retries - 1).then(resolve);
162
217
  }
163
- console.log(' (Shell integration can be installed later with: gw install-shell)');
218
+ console.log(
219
+ ' (Shell integration can be installed later with: gw install-shell)',
220
+ );
164
221
  resolve();
165
222
  });
166
223
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gw-tools/gw",
3
- "version": "0.12.19",
3
+ "version": "0.12.22",
4
4
  "description": "A command-line tool for managing git worktrees - copy files between worktrees with ease",
5
5
  "keywords": [
6
6
  "git",