@gw-tools/gw 0.12.18 → 0.12.20

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 +74 -28
  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,9 +130,20 @@ 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);
144
+
145
+ // Additional delay after chmod to prevent ETXTBSY
146
+ await new Promise((resolve) => setTimeout(resolve, 200));
112
147
  }
113
148
 
114
149
  console.log('✓ Installation complete!');
@@ -118,7 +153,9 @@ async function install() {
118
153
  await installShellIntegration(binaryPath);
119
154
 
120
155
  console.log('\nRun "gw --help" to get started.');
121
- console.log('Tip: Restart your terminal or run "source ~/.zshrc" (or ~/.bashrc) to use "gw cd"');
156
+ console.log(
157
+ 'Tip: Restart your terminal or run "source ~/.zshrc" (or ~/.bashrc) to use "gw cd"',
158
+ );
122
159
  } catch (error) {
123
160
  console.error('\n✗ Installation failed:', error.message);
124
161
  console.error('\nYou can manually download the binary from:');
@@ -134,25 +171,34 @@ async function install() {
134
171
  /**
135
172
  * Install shell integration
136
173
  */
137
- async function installShellIntegration(binaryPath) {
174
+ async function installShellIntegration(binaryPath, retries = 3) {
138
175
  const { spawn } = require('child_process');
139
176
 
140
177
  return new Promise((resolve) => {
141
178
  const child = spawn(binaryPath, ['install-shell', '--quiet'], {
142
- stdio: 'inherit'
179
+ stdio: 'inherit',
143
180
  });
144
181
 
145
182
  child.on('close', (code) => {
146
183
  if (code === 0) {
147
184
  console.log('✓ Shell integration installed!');
148
185
  } else {
149
- console.log(' (Shell integration can be installed later with: gw install-shell)');
186
+ console.log(
187
+ ' (Shell integration can be installed later with: gw install-shell)',
188
+ );
150
189
  }
151
190
  resolve();
152
191
  });
153
192
 
154
- child.on('error', (err) => {
155
- console.log(' (Shell integration can be installed later with: gw install-shell)');
193
+ child.on('error', async (err) => {
194
+ // Retry on ETXTBSY (text file busy) error
195
+ if (err.code === 'ETXTBSY' && retries > 0) {
196
+ await new Promise((r) => setTimeout(r, 200));
197
+ return installShellIntegration(binaryPath, retries - 1).then(resolve);
198
+ }
199
+ console.log(
200
+ ' (Shell integration can be installed later with: gw install-shell)',
201
+ );
156
202
  resolve();
157
203
  });
158
204
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gw-tools/gw",
3
- "version": "0.12.18",
3
+ "version": "0.12.20",
4
4
  "description": "A command-line tool for managing git worktrees - copy files between worktrees with ease",
5
5
  "keywords": [
6
6
  "git",