houdini-react 2.0.0-go.6 → 2.0.0-go.7

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/package.json +8 -8
  2. package/postInstall.js +226 -22
  3. package/shim.cjs +61 -41
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "houdini-react",
3
- "version": "2.0.0-go.6",
3
+ "version": "2.0.0-go.7",
4
4
  "description": "The React plugin for houdini",
5
5
  "keywords": [
6
6
  "typescript",
@@ -35,7 +35,7 @@
35
35
  "express": "^4.18.2",
36
36
  "graphql": "^15.8.0",
37
37
  "graphql-yoga": "^4.0.4",
38
- "houdini": "^2.0.0-go.6",
38
+ "houdini": "^2.0.0-go.7",
39
39
  "react": "^19.0.0",
40
40
  "react-dom": "^19.0.0",
41
41
  "react-streaming-compat": "^0.3.18",
@@ -80,12 +80,12 @@
80
80
  }
81
81
  },
82
82
  "optionalDependencies": {
83
- "houdini-react-darwin-x64": "2.0.0-go.6",
84
- "houdini-react-darwin-arm64": "2.0.0-go.6",
85
- "houdini-react-linux-x64": "2.0.0-go.6",
86
- "houdini-react-linux-arm64": "2.0.0-go.6",
87
- "houdini-react-win32-x64": "2.0.0-go.6",
88
- "houdini-react-win32-arm64": "2.0.0-go.6"
83
+ "houdini-react-darwin-x64": "2.0.0-go.7",
84
+ "houdini-react-darwin-arm64": "2.0.0-go.7",
85
+ "houdini-react-linux-x64": "2.0.0-go.7",
86
+ "houdini-react-linux-arm64": "2.0.0-go.7",
87
+ "houdini-react-win32-x64": "2.0.0-go.7",
88
+ "houdini-react-win32-arm64": "2.0.0-go.7"
89
89
  },
90
90
  "bin": "./shim.cjs",
91
91
  "scripts": {
package/postInstall.js CHANGED
@@ -2,9 +2,10 @@ const fs = require('fs')
2
2
  const path = require('path')
3
3
  const zlib = require('zlib')
4
4
  const https = require('https')
5
+ const child_process = require('child_process')
5
6
 
6
7
  // Adjust the version you want to install. You can also make this dynamic.
7
- const BINARY_DISTRIBUTION_VERSION = '2.0.0-go.6'
8
+ const BINARY_DISTRIBUTION_VERSION = '2.0.0-go.7'
8
9
 
9
10
  // Windows binaries end with .exe so we need to special case them.
10
11
  const binaryName = process.platform === 'win32' ? 'houdini-react.exe' : 'houdini-react'
@@ -15,6 +16,16 @@ const platformSpecificPackageName = `houdini-react-${process.platform}-${process
15
16
  // Compute the path we want to emit the fallback binary to
16
17
  const fallbackBinaryPath = path.join(__dirname, binaryName)
17
18
 
19
+ // Manual binary path override support
20
+ const MANUAL_BINARY_PATH = process.env.HOUDINI_BINARY_PATH || process.env.HOUDINI_REACT_BINARY_PATH
21
+
22
+ // Package version for validation
23
+ const packageJSON = require(path.join(__dirname, 'package.json'))
24
+ const expectedVersion = packageJSON.version
25
+
26
+ // Track if shim is still JavaScript
27
+ let isShimJS = true
28
+
18
29
  function makeRequest(url) {
19
30
  return new Promise((resolve, reject) => {
20
31
  https
@@ -69,7 +80,70 @@ function extractFileFromTarball(tarballBuffer, filepath) {
69
80
  }
70
81
  }
71
82
 
83
+ function installUsingNPM() {
84
+ // Erase "npm_config_global" so that "npm install --global" works.
85
+ // Otherwise this nested "npm install" will also be global, and the install
86
+ // will deadlock waiting for the global installation lock.
87
+ const env = { ...process.env, npm_config_global: undefined };
88
+
89
+ // Create a temporary directory with an empty package.json
90
+ const tempDir = path.join(__dirname, 'npm-install-temp');
91
+
92
+ try {
93
+ fs.mkdirSync(tempDir);
94
+ fs.writeFileSync(path.join(tempDir, 'package.json'), '{}');
95
+
96
+ // Run npm install in the temporary directory
97
+ child_process.execSync(
98
+ `npm install --loglevel=error --prefer-offline --no-audit --progress=false ${platformSpecificPackageName}@${BINARY_DISTRIBUTION_VERSION}`,
99
+ { cwd: tempDir, stdio: 'pipe', env }
100
+ );
101
+
102
+ // Move the downloaded binary to the fallback location
103
+ const installedBinaryPath = path.join(tempDir, 'node_modules', platformSpecificPackageName, 'bin', binaryName);
104
+ fs.renameSync(installedBinaryPath, fallbackBinaryPath);
105
+
106
+ return true;
107
+ } catch (err) {
108
+ console.error(`[${packageJSON.name}] npm install fallback failed: ${err.message}`);
109
+ return false;
110
+ } finally {
111
+ // Clean up temporary directory
112
+ try {
113
+ removeRecursive(tempDir);
114
+ } catch {
115
+ // Ignore cleanup errors
116
+ }
117
+ }
118
+ }
119
+
120
+ function removeRecursive(dir) {
121
+ if (!fs.existsSync(dir)) return;
122
+
123
+ for (const entry of fs.readdirSync(dir)) {
124
+ const entryPath = path.join(dir, entry);
125
+ const stats = fs.lstatSync(entryPath);
126
+
127
+ if (stats.isDirectory()) {
128
+ removeRecursive(entryPath);
129
+ } else {
130
+ fs.unlinkSync(entryPath);
131
+ }
132
+ }
133
+
134
+ fs.rmdirSync(dir);
135
+ }
136
+
72
137
  async function downloadBinaryFromNpm() {
138
+ // First try nested npm install (like esbuild does)
139
+ if (installUsingNPM()) {
140
+ console.log(`[${packageJSON.name}] Downloaded binary via npm install`);
141
+ return;
142
+ }
143
+
144
+ // Fallback to direct HTTP download
145
+ console.log(`[${packageJSON.name}] Trying direct download from npm registry...`);
146
+
73
147
  // Download the tarball of the right binary distribution package
74
148
  const tarballDownloadBuffer = await makeRequest(
75
149
  `https://registry.npmjs.org/${platformSpecificPackageName}/-/${platformSpecificPackageName}-${BINARY_DISTRIBUTION_VERSION}.tgz`
@@ -83,6 +157,8 @@ async function downloadBinaryFromNpm() {
83
157
  extractFileFromTarball(tarballBuffer, `package/bin/${binaryName}`),
84
158
  { mode: 0o755 } // Make binary file executable
85
159
  )
160
+
161
+ console.log(`[${packageJSON.name}] Downloaded binary via direct download`);
86
162
  }
87
163
 
88
164
  function isPlatformSpecificPackageInstalled() {
@@ -102,46 +178,174 @@ if (!platformSpecificPackageName) {
102
178
  throw new Error('Platform not supported!')
103
179
  }
104
180
 
105
- // once we've confirmed the required package is installed we want to overwrite the bin entry of our package.json
106
- // to point to the correct binary for optimal performance (skip Node.js overhead)
107
- function overwriteBinary() {
108
- const packageJsonPath = path.join(__dirname, 'package.json')
109
- const packageJson = require(packageJsonPath)
181
+ // Replace the JavaScript shim with the actual binary for optimal performance (skip Node.js overhead)
182
+ // This is inspired by esbuild's approach: https://github.com/evanw/esbuild/blob/main/lib/npm/node-install.ts
183
+ function maybeOptimizePackage() {
184
+ // This optimization doesn't work on Windows because the binary must be called with .exe extension
185
+ // It also doesn't work with Yarn due to various compatibility issues
186
+ if (process.platform === 'win32' || isYarn()) {
187
+ return
188
+ }
110
189
 
111
190
  let binaryPath = null
112
191
 
113
192
  try {
114
- // Method 1: Use require.resolve to find the actual path to the platform-specific package
115
- // This works with pnpm and other package managers that use symlinks
193
+ // Method 1: Use require.resolve to find the platform-specific package
116
194
  const platformPackagePath = require.resolve(`${platformSpecificPackageName}/package.json`)
117
195
  const platformPackageDir = path.dirname(platformPackagePath)
118
196
  binaryPath = path.join(platformPackageDir, 'bin', binaryName)
119
197
  } catch (error) {
120
198
  // Method 2: Check if platform package is installed as a sibling directory
121
- // This works with npm and local installs
122
199
  const siblingPath = path.join(__dirname, '..', platformSpecificPackageName)
123
- const siblingBinaryPath = path.join(siblingPath, 'bin', binaryName)
200
+ binaryPath = path.join(siblingPath, 'bin', binaryName)
124
201
 
125
- if (fs.existsSync(siblingBinaryPath)) {
126
- binaryPath = siblingBinaryPath
127
- } else {
128
- // Use shim as fallback
129
- return
202
+ if (!fs.existsSync(binaryPath)) {
203
+ // Method 3: pnpm-specific structure
204
+ const pnpmMatch = __dirname.match(/(.+\/node_modules\/)\.pnpm\/([^\/]+)\/node_modules\//)
205
+ if (pnpmMatch) {
206
+ const [, nodeModulesRoot] = pnpmMatch
207
+ const pnpmDir = path.join(nodeModulesRoot, '.pnpm')
208
+
209
+ try {
210
+ const pnpmEntries = fs.readdirSync(pnpmDir)
211
+ const platformEntry = pnpmEntries.find(entry => entry.startsWith(platformSpecificPackageName + '@'))
212
+
213
+ if (platformEntry) {
214
+ binaryPath = path.join(pnpmDir, platformEntry, 'node_modules', platformSpecificPackageName, 'bin', binaryName)
215
+ }
216
+ } catch (err) {
217
+ // Ignore errors
218
+ }
219
+ }
130
220
  }
131
221
  }
132
222
 
133
- if (binaryPath) {
134
- // Make the path relative to the main package directory
135
- const relativeBinaryPath = path.relative(__dirname, binaryPath)
136
- packageJson.bin = relativeBinaryPath
223
+ // If we found the binary, try to replace the shim with it
224
+ if (binaryPath && fs.existsSync(binaryPath)) {
225
+ // First validate the binary works correctly
226
+ if (!validateBinaryVersion(binaryPath)) {
227
+ console.error(`[${packageJSON.name}] Binary validation failed, keeping JavaScript shim`);
228
+ return;
229
+ }
230
+
231
+ const shimPath = path.join(__dirname, 'shim.cjs')
232
+ const tempPath = path.join(__dirname, 'shim-temp')
233
+
234
+ try {
235
+ // First create a hard link to avoid taking up additional disk space
236
+ fs.linkSync(binaryPath, tempPath)
237
+
238
+ // Then atomically replace the shim with the binary
239
+ fs.renameSync(tempPath, shimPath)
240
+
241
+ // Make sure it's executable
242
+ fs.chmodSync(shimPath, 0o755)
243
+
244
+ // Update state tracking
245
+ isShimJS = false
246
+
247
+ console.log(`[${packageJSON.name}] Binary optimization successful`);
248
+ } catch (err) {
249
+ console.error(`[${packageJSON.name}] Binary optimization failed: ${err.message}`);
250
+ // If optimization fails, clean up and continue with the shim
251
+ try {
252
+ fs.unlinkSync(tempPath)
253
+ } catch {}
254
+ }
255
+ }
256
+ }
137
257
 
138
- fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
258
+ function validateBinaryVersion(binaryPath) {
259
+ try {
260
+ // For our Go binaries, we just check if they execute without crashing
261
+ // We use -h flag which should work and exit cleanly
262
+ child_process.execFileSync(binaryPath, ['-h'], {
263
+ stdio: 'pipe',
264
+ timeout: 5000,
265
+ encoding: 'utf8'
266
+ });
267
+ return true;
268
+ } catch (err) {
269
+ // If the binary exits with code 0 or 2 (help), that's fine
270
+ if (err.status === 0 || err.status === 2) {
271
+ return true;
272
+ }
273
+ console.error(`[${packageJSON.name}] Binary validation failed: ${err.message}`);
274
+ return false;
275
+ }
276
+ }
277
+
278
+ function applyManualBinaryPathOverride(overridePath) {
279
+ console.log(`[${packageJSON.name}] Using manual binary path: ${overridePath}`);
280
+
281
+ const shimPath = path.join(__dirname, 'shim.cjs');
282
+ const shimContent = `#!/usr/bin/env node
283
+ require('child_process').execFileSync(${JSON.stringify(overridePath)}, process.argv.slice(2), { stdio: 'inherit' });
284
+ `;
285
+
286
+ try {
287
+ fs.writeFileSync(shimPath, shimContent, { mode: 0o755 });
288
+ isShimJS = true; // Keep as JS since it's a wrapper
289
+ return true;
290
+ } catch (err) {
291
+ console.error(`[${packageJSON.name}] Failed to apply manual binary override: ${err.message}`);
292
+ return false;
293
+ }
294
+ }
295
+
296
+ function isYarn() {
297
+ const { npm_config_user_agent } = process.env
298
+ if (npm_config_user_agent) {
299
+ return /\byarn\//.test(npm_config_user_agent)
300
+ }
301
+ return false
302
+ }
303
+
304
+ // Check for manual binary path override first
305
+ if (MANUAL_BINARY_PATH) {
306
+ if (fs.existsSync(MANUAL_BINARY_PATH)) {
307
+ console.log(`[${packageJSON.name}] Using manual binary path override`);
308
+ if (applyManualBinaryPathOverride(MANUAL_BINARY_PATH)) {
309
+ process.exit(0);
310
+ }
311
+ } else {
312
+ console.warn(`[${packageJSON.name}] Ignoring invalid manual binary path: ${MANUAL_BINARY_PATH}`);
139
313
  }
140
314
  }
141
315
 
142
316
  // Skip downloading the binary if it was already installed via optionalDependencies
143
317
  if (!isPlatformSpecificPackageInstalled()) {
144
- downloadBinaryFromNpm().then(overwriteBinary)
318
+ console.log(`[${packageJSON.name}] Platform package not found, downloading binary...`);
319
+ downloadBinaryFromNpm().then(() => {
320
+ maybeOptimizePackage();
321
+
322
+ // Final validation
323
+ const shimPath = path.join(__dirname, 'shim.cjs');
324
+ if (isShimJS) {
325
+ // Validate the JavaScript shim can find and run the binary
326
+ if (!validateBinaryVersion(shimPath)) {
327
+ console.error(`[${packageJSON.name}] Installation may be incomplete`);
328
+ }
329
+ }
330
+ }).catch(err => {
331
+ console.error(`[${packageJSON.name}] Failed to download binary: ${err.message}`);
332
+ process.exit(1);
333
+ });
145
334
  } else {
146
- overwriteBinary()
335
+ console.log(`[${packageJSON.name}] Platform package found, optimizing...`);
336
+ maybeOptimizePackage();
337
+
338
+ // Final validation
339
+ const shimPath = path.join(__dirname, 'shim.cjs');
340
+ if (isShimJS) {
341
+ // Validate the JavaScript shim can find and run the binary
342
+ if (!validateBinaryVersion(shimPath)) {
343
+ console.error(`[${packageJSON.name}] Installation may be incomplete`);
344
+ }
345
+ } else {
346
+ // Binary was optimized, validate it directly
347
+ if (!validateBinaryVersion(shimPath)) {
348
+ console.error(`[${packageJSON.name}] Binary optimization may have failed`);
349
+ }
350
+ }
147
351
  }
package/shim.cjs CHANGED
@@ -1,7 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // Simple shim that can be replaced with the actual binary for optimal performance
4
+ // This follows esbuild's approach: the postInstall script will replace this entire file
5
+ // with the native binary when possible, eliminating Node.js startup overhead
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { execFileSync } = require('child_process');
10
+
11
+ // Manual binary path override support
12
+ const MANUAL_BINARY_PATH = process.env.HOUDINI_BINARY_PATH || process.env.HOUDINI_REACT_BINARY_PATH;
13
+
3
14
  function getBinaryPath() {
4
- // lookup table for all platforms and binary distribution packages
15
+ // Check for manual override first
16
+ if (MANUAL_BINARY_PATH && fs.existsSync(MANUAL_BINARY_PATH)) {
17
+ return MANUAL_BINARY_PATH;
18
+ }
19
+
20
+ // Platform-specific package lookup
5
21
  const BINARY_DISTRIBUTION_PACKAGES = {
6
22
  'linux-x64': 'houdini-react-linux-x64',
7
23
  'linux-arm64': 'houdini-react-linux-arm64',
@@ -11,54 +27,58 @@ function getBinaryPath() {
11
27
  'darwin-arm64': 'houdini-react-darwin-arm64',
12
28
  }
13
29
 
14
- // windows binaries end with .exe so we need to special case them
15
30
  const binaryName = process.platform === 'win32' ? 'houdini-react.exe' : 'houdini-react'
31
+ const platformSpecificPackageName = BINARY_DISTRIBUTION_PACKAGES[`${process.platform}-${process.arch}`]
16
32
 
17
- // determine package name for this platform
18
- const platformSpecificPackageName =
19
- BINARY_DISTRIBUTION_PACKAGES[`${process.platform}-${process.arch}`]
33
+ if (!platformSpecificPackageName) {
34
+ // Fallback to downloaded binary if platform not supported
35
+ return path.join(__dirname, binaryName)
36
+ }
20
37
 
21
38
  try {
22
- // resolving will fail if the optionalDependency was not installed
23
- return require.resolve(`../${platformSpecificPackageName}/bin/${binaryName}`)
24
- } catch (e) {
25
- return require('path').join(__dirname, binaryName)
26
- }
27
- }
28
- // instead of execFileSync, use spawn to handle the process more gracefully
29
- const childProcess = require('child_process').spawn(getBinaryPath(), process.argv.slice(2), {
30
- stdio: 'inherit',
31
- })
39
+ // Method 1: Use require.resolve to find the platform-specific package
40
+ const platformPackagePath = require.resolve(`${platformSpecificPackageName}/package.json`)
41
+ const platformPackageDir = path.dirname(platformPackagePath)
42
+ return path.join(platformPackageDir, 'bin', binaryName)
43
+ } catch (error) {
44
+ // Method 2: Check sibling directory (npm structure)
45
+ const siblingPath = path.join(__dirname, '..', platformSpecificPackageName)
46
+ const siblingBinaryPath = path.join(siblingPath, 'bin', binaryName)
47
+
48
+ if (fs.existsSync(siblingBinaryPath)) {
49
+ return siblingBinaryPath
50
+ }
51
+
52
+ // Method 3: Check pnpm structure
53
+ const pnpmMatch = __dirname.match(/(.+\/node_modules\/)\.pnpm\/([^\/]+)\/node_modules\//)
54
+ if (pnpmMatch) {
55
+ const [, nodeModulesRoot] = pnpmMatch
56
+ const pnpmDir = path.join(nodeModulesRoot, '.pnpm')
32
57
 
33
- // array of signals we want to handle
34
- const signals = ['SIGTERM', 'SIGINT', 'SIGQUIT', 'SIGHUP']
58
+ try {
59
+ const pnpmEntries = fs.readdirSync(pnpmDir)
60
+ const platformEntry = pnpmEntries.find(entry => entry.startsWith(platformSpecificPackageName + '@'))
35
61
 
36
- // handle each signal
37
- signals.forEach((signal) => {
38
- process.on(signal, () => {
39
- if (childProcess) {
40
- // on windows, we need to use taskkill for proper tree killing
41
- if (process.platform === 'win32') {
42
- require('child_process').spawn('taskkill', ['/pid', childProcess.pid, '/f', '/t'])
43
- } else {
44
- try {
45
- childProcess.kill(signal)
46
- } catch (err) {
47
- // if the process is already gone, that's fine
48
- if (err.code !== 'ESRCH') throw err
62
+ if (platformEntry) {
63
+ const pnpmBinaryPath = path.join(pnpmDir, platformEntry, 'node_modules', platformSpecificPackageName, 'bin', binaryName)
64
+ if (fs.existsSync(pnpmBinaryPath)) {
65
+ return pnpmBinaryPath
66
+ }
49
67
  }
68
+ } catch (err) {
69
+ // Ignore pnpm detection errors
50
70
  }
51
71
  }
52
- process.exit(0)
53
- })
54
- })
55
72
 
56
- // handle child process exit
57
- childProcess.on('exit', (code, signal) => {
58
- // if the child was terminated due to a signal, exit with the same signal
59
- if (signal) {
60
- process.exit(0)
61
- } else {
62
- process.exit(code)
73
+ // Method 4: Fallback to downloaded binary in main package
74
+ return path.join(__dirname, binaryName)
63
75
  }
64
- })
76
+ }
77
+
78
+ // Execute the binary directly (this entire file may be replaced with the actual binary)
79
+ try {
80
+ execFileSync(getBinaryPath(), process.argv.slice(2), { stdio: 'inherit' });
81
+ } catch (error) {
82
+ // If execFileSync fails, exit with the same code
83
+ process.exit(error.status || 1);
84
+ }