@lpm-registry/cli 0.5.0 → 0.7.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.
@@ -1,32 +1,62 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Download the platform-specific LPM Rust binary.
4
+ * LPM CLI Binary Installer — 3-tier fallback + hard-link optimization.
5
5
  *
6
- * Runs as a postinstall hook. Downloads the pre-built binary from
7
- * lpm.dev/releases and places it in the package's bin/ directory.
6
+ * Runs as a postinstall hook. Resolution order:
8
7
  *
9
- * Falls back gracefully to the JS CLI if the binary isn't available
10
- * for the current platform.
8
+ * 1. optionalDependencies — platform package installed by npm (fastest, cached)
9
+ * 2. GitHub Releases — direct download if optionalDependencies missing (--no-optional)
10
+ * 3. Graceful fallback — JS CLI handles commands if binary unavailable
11
+ *
12
+ * After resolving the binary, hard-links it over bin/lpm.js so subsequent
13
+ * runs execute the native binary directly (zero Node.js overhead).
14
+ *
15
+ * Follows the esbuild pattern: https://github.com/evanw/esbuild
11
16
  */
12
17
 
13
18
  import { execSync } from "node:child_process"
14
19
  import fs from "node:fs"
15
20
  import http from "node:http"
16
21
  import https from "node:https"
22
+ import { createRequire } from "node:module"
23
+ import os from "node:os"
17
24
  import path from "node:path"
18
25
  import { fileURLToPath } from "node:url"
19
26
 
20
27
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
21
28
  const pkgDir = path.join(__dirname, "..")
22
29
  const binDir = path.join(pkgDir, "bin")
23
- const binaryPath = path.join(
24
- binDir,
25
- process.platform === "win32" ? "lpm-bin.exe" : "lpm-bin",
26
- )
30
+ const require = createRequire(import.meta.url)
31
+
32
+ // Platform → { package name, binary filename in package }
33
+ const PLATFORMS = {
34
+ "darwin-arm64": {
35
+ pkg: "@lpm-registry/cli-darwin-arm64",
36
+ binary: "lpm",
37
+ },
38
+ "darwin-x64": {
39
+ pkg: "@lpm-registry/cli-darwin-x64",
40
+ binary: "lpm",
41
+ },
42
+ "linux-x64": {
43
+ pkg: "@lpm-registry/cli-linux-x64",
44
+ binary: "lpm",
45
+ },
46
+ "linux-arm64": {
47
+ pkg: "@lpm-registry/cli-linux-arm64",
48
+ binary: "lpm",
49
+ },
50
+ "win32-x64": {
51
+ pkg: "@lpm-registry/cli-win32-x64",
52
+ binary: "lpm.exe",
53
+ },
54
+ }
27
55
 
28
- // Platform → binary filename mapping
29
- const PLATFORM_MAP = {
56
+ // GitHub Releases fallback URLs
57
+ const GITHUB_RELEASES =
58
+ "https://github.com/lpm-dev/rust-client/releases/download"
59
+ const GITHUB_BINARY_NAMES = {
30
60
  "darwin-arm64": "lpm-darwin-arm64",
31
61
  "darwin-x64": "lpm-darwin-x64",
32
62
  "linux-x64": "lpm-linux-x64",
@@ -34,46 +64,157 @@ const PLATFORM_MAP = {
34
64
  "win32-x64": "lpm-win32-x64.exe",
35
65
  }
36
66
 
37
- const GITHUB_RELEASES =
38
- "https://github.com/lpm-dev/rust-client/releases/download"
39
- const pkg = JSON.parse(
40
- fs.readFileSync(path.join(pkgDir, "package.json"), "utf-8"),
41
- )
42
-
43
67
  async function main() {
44
- const platform = `${process.platform}-${process.arch}`
45
- const binaryName = PLATFORM_MAP[platform]
68
+ const platform = `${process.platform}-${os.arch()}`
69
+ const platformInfo = PLATFORMS[platform]
46
70
 
47
- if (!binaryName) {
48
- console.log(`[lpm] No pre-built binary for ${platform}. Using JS CLI.`)
71
+ if (!platformInfo) {
72
+ console.log(
73
+ `[lpm] No pre-built binary for ${platform}. Using JS CLI fallback.`,
74
+ )
49
75
  return
50
76
  }
51
77
 
52
- // Skip if binary already exists and is executable
53
- if (fs.existsSync(binaryPath)) {
54
- try {
55
- execSync(`"${binaryPath}" --version`, { stdio: "ignore", timeout: 5000 })
56
- return
57
- } catch {
58
- // Binary exists but broken, re-download
59
- fs.unlinkSync(binaryPath)
78
+ const pkg = JSON.parse(
79
+ fs.readFileSync(path.join(pkgDir, "package.json"), "utf-8"),
80
+ )
81
+
82
+ // ── Tier 1: Try optionalDependencies (platform package) ──────────
83
+ let binaryPath = tryOptionalDependency(platformInfo)
84
+
85
+ // ── Tier 2: Direct download from GitHub Releases ─────────────────
86
+ if (!binaryPath) {
87
+ const githubBinaryName = GITHUB_BINARY_NAMES[platform]
88
+ if (githubBinaryName) {
89
+ binaryPath = await tryGitHubDownload(pkg.version, githubBinaryName)
60
90
  }
61
91
  }
62
92
 
63
- const url = `${GITHUB_RELEASES}/v${pkg.version}/${binaryName}`
93
+ if (!binaryPath) {
94
+ console.log("[lpm] Binary not available. Using JS CLI fallback.")
95
+ return
96
+ }
97
+
98
+ // ── Hard-link optimization ───────────────────────────────────────
99
+ // Replace bin/lpm.js with a hard link to the native binary.
100
+ // After this, `lpm` on PATH IS the Rust binary — zero Node.js overhead.
101
+ if (process.platform !== "win32") {
102
+ tryHardLink(binaryPath)
103
+ } else {
104
+ // Windows: copy instead of hard-link (npm creates .cmd shims)
105
+ tryCopyBinary(binaryPath)
106
+ }
107
+ }
64
108
 
109
+ /**
110
+ * Tier 1: Resolve binary from optionalDependencies platform package.
111
+ * Returns the path to the binary, or null if not found.
112
+ */
113
+ function tryOptionalDependency(platformInfo) {
65
114
  try {
66
- console.log(`[lpm] Downloading native binary for ${platform}...`)
67
- await download(url, binaryPath)
68
- fs.chmodSync(binaryPath, 0o755)
69
- console.log("[lpm] Native binary installed.")
115
+ // require.resolve finds the package in node_modules
116
+ const pkgJsonPath = require.resolve(`${platformInfo.pkg}/package.json`)
117
+ const pkgDir = path.dirname(pkgJsonPath)
118
+ const binaryPath = path.join(pkgDir, platformInfo.binary)
119
+
120
+ if (fs.existsSync(binaryPath)) {
121
+ // Verify the binary is executable
122
+ try {
123
+ execSync(`"${binaryPath}" --version`, {
124
+ stdio: "ignore",
125
+ timeout: 5000,
126
+ })
127
+ console.log(`[lpm] Using native binary from ${platformInfo.pkg}`)
128
+ return binaryPath
129
+ } catch {
130
+ console.log(
131
+ `[lpm] Binary from ${platformInfo.pkg} exists but not executable.`,
132
+ )
133
+ }
134
+ }
135
+ } catch {
136
+ // Package not installed (--no-optional, or resolution failed)
137
+ }
138
+
139
+ return null
140
+ }
141
+
142
+ /**
143
+ * Tier 2: Download binary directly from GitHub Releases.
144
+ * Returns the path to the downloaded binary, or null on failure.
145
+ */
146
+ async function tryGitHubDownload(version, githubBinaryName) {
147
+ const url = `${GITHUB_RELEASES}/v${version}/${githubBinaryName}`
148
+ const destName = process.platform === "win32" ? "lpm-bin.exe" : "lpm-bin"
149
+ const destPath = path.join(binDir, destName)
150
+
151
+ try {
152
+ console.log(`[lpm] Platform package not found. Downloading from GitHub...`)
153
+ await download(url, destPath)
154
+ fs.chmodSync(destPath, 0o755)
155
+ console.log("[lpm] Native binary downloaded from GitHub Releases.")
156
+ return destPath
70
157
  } catch (err) {
71
- console.log(`[lpm] Binary not available: ${err.message}. Using JS CLI.`)
158
+ console.log(`[lpm] GitHub download failed: ${err.message}. Using JS CLI.`)
72
159
  // Clean up partial download
73
- if (fs.existsSync(binaryPath)) fs.unlinkSync(binaryPath)
160
+ if (fs.existsSync(destPath)) fs.unlinkSync(destPath)
161
+ return null
74
162
  }
75
163
  }
76
164
 
165
+ /**
166
+ * Hard-link the native binary over bin/lpm.js (Unix only).
167
+ *
168
+ * After this, the `lpm` symlink on PATH points directly to the Rust binary.
169
+ * Node.js is never started. Zero overhead.
170
+ *
171
+ * We keep lpm.js.bak as the JS fallback — if the hard link is broken
172
+ * (npm rebuild, etc.), the JS wrapper can be restored.
173
+ */
174
+ function tryHardLink(binaryPath) {
175
+ const shimPath = path.join(binDir, "lpm.js")
176
+ const backupPath = path.join(binDir, "lpm.js.bak")
177
+
178
+ try {
179
+ // Back up the JS shim (only if not already backed up)
180
+ if (fs.existsSync(shimPath) && !fs.existsSync(backupPath)) {
181
+ fs.copyFileSync(shimPath, backupPath)
182
+ }
183
+
184
+ // Replace JS shim with hard link to native binary
185
+ if (fs.existsSync(shimPath)) {
186
+ fs.unlinkSync(shimPath)
187
+ }
188
+ fs.linkSync(binaryPath, shimPath)
189
+
190
+ console.log("[lpm] Hard-linked native binary (zero Node.js overhead)")
191
+ } catch (err) {
192
+ // Hard link failed (cross-device mount, permissions, etc.)
193
+ // Restore JS shim — it will delegate at runtime (50ms overhead)
194
+ if (fs.existsSync(backupPath) && !fs.existsSync(shimPath)) {
195
+ fs.copyFileSync(backupPath, shimPath)
196
+ }
197
+ console.log(`[lpm] Hard-link failed (${err.code}). Using JS wrapper.`)
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Copy binary to bin/ directory (Windows fallback).
203
+ * npm on Windows creates .cmd shims, so hard-linking doesn't work the same way.
204
+ */
205
+ function tryCopyBinary(binaryPath) {
206
+ const destPath = path.join(binDir, "lpm-bin.exe")
207
+ try {
208
+ fs.copyFileSync(binaryPath, destPath)
209
+ console.log("[lpm] Copied native binary to bin/")
210
+ } catch (err) {
211
+ console.log(`[lpm] Copy failed: ${err.message}. Using JS wrapper.`)
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Download a file from a URL, following redirects (GitHub → S3).
217
+ */
77
218
  function download(url, dest, redirects = 0) {
78
219
  if (redirects > 5) return Promise.reject(new Error("Too many redirects"))
79
220
 
@@ -109,5 +250,5 @@ function download(url, dest, redirects = 0) {
109
250
  }
110
251
 
111
252
  main().catch(() => {
112
- // Silently fail — JS CLI fallback
253
+ // Silently fail — JS CLI fallback will handle all commands
113
254
  })