@lpm-registry/cli 0.59.0 → 0.59.1
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.
- package/README.md +6 -1
- package/package.json +8 -6
- package/scripts/install-binary.js +240 -0
package/README.md
CHANGED
|
@@ -8,7 +8,9 @@ npm install -g @lpm-registry/cli
|
|
|
8
8
|
|
|
9
9
|
This package is the npm gateway for the native LPM CLI. It installs a small
|
|
10
10
|
JavaScript launcher plus the matching platform package, such as
|
|
11
|
-
`@lpm-registry/cli-darwin-arm64` or `@lpm-registry/cli-linux-x64`.
|
|
11
|
+
`@lpm-registry/cli-darwin-arm64` or `@lpm-registry/cli-linux-x64`. A tiny
|
|
12
|
+
`postinstall` verifier checks the native binary and wires the global command to
|
|
13
|
+
it where the platform allows that.
|
|
12
14
|
|
|
13
15
|
The JavaScript launcher does not implement package-manager behavior. All CLI
|
|
14
16
|
commands run in the native Rust binary.
|
|
@@ -36,5 +38,8 @@ with optional dependencies enabled:
|
|
|
36
38
|
npm install -g @lpm-registry/cli
|
|
37
39
|
```
|
|
38
40
|
|
|
41
|
+
If install scripts were disabled, reinstall without `--ignore-scripts` so npm
|
|
42
|
+
can validate the native binary during installation.
|
|
43
|
+
|
|
39
44
|
Other install methods are documented at
|
|
40
45
|
[github.com/lpm-dev/rust-client](https://github.com/lpm-dev/rust-client).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lpm-registry/cli",
|
|
3
|
-
"version": "0.59.
|
|
3
|
+
"version": "0.59.1",
|
|
4
4
|
"description": "LPM CLI native binary installer for npm",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,20 +22,22 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"bin",
|
|
25
|
+
"scripts",
|
|
25
26
|
"README.md"
|
|
26
27
|
],
|
|
27
28
|
"optionalDependencies": {
|
|
28
|
-
"@lpm-registry/cli-darwin-arm64": "0.59.
|
|
29
|
-
"@lpm-registry/cli-darwin-x64": "0.59.
|
|
30
|
-
"@lpm-registry/cli-linux-arm64": "0.59.
|
|
31
|
-
"@lpm-registry/cli-linux-x64": "0.59.
|
|
32
|
-
"@lpm-registry/cli-win32-x64": "0.59.
|
|
29
|
+
"@lpm-registry/cli-darwin-arm64": "0.59.1",
|
|
30
|
+
"@lpm-registry/cli-darwin-x64": "0.59.1",
|
|
31
|
+
"@lpm-registry/cli-linux-arm64": "0.59.1",
|
|
32
|
+
"@lpm-registry/cli-linux-x64": "0.59.1",
|
|
33
|
+
"@lpm-registry/cli-win32-x64": "0.59.1"
|
|
33
34
|
},
|
|
34
35
|
"type": "module",
|
|
35
36
|
"engines": {
|
|
36
37
|
"node": ">=18.0.0"
|
|
37
38
|
},
|
|
38
39
|
"scripts": {
|
|
40
|
+
"postinstall": "node scripts/install-binary.js",
|
|
39
41
|
"test": "node --test tests/*.test.mjs",
|
|
40
42
|
"pack:check": "npm pack --dry-run --json"
|
|
41
43
|
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import {
|
|
8
|
+
LpmWrapperError,
|
|
9
|
+
PLATFORMS,
|
|
10
|
+
platformKey,
|
|
11
|
+
resolveNativeBinary,
|
|
12
|
+
} from "../bin/native.js";
|
|
13
|
+
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const defaultWrapperRoot = path.resolve(scriptDir, "..");
|
|
17
|
+
const defaultValidateTimeoutMs = 30_000;
|
|
18
|
+
const commands = ["lpm", "lpx"];
|
|
19
|
+
|
|
20
|
+
export function installNativeBinaries(options = {}) {
|
|
21
|
+
const wrapperRoot = options.wrapperRoot ?? defaultWrapperRoot;
|
|
22
|
+
const platform = options.platform ?? process.platform;
|
|
23
|
+
const arch = options.arch ?? os.arch();
|
|
24
|
+
const env = options.env ?? process.env;
|
|
25
|
+
const requireFn = options.requireFn ?? require;
|
|
26
|
+
const fsModule = options.fsModule ?? fs;
|
|
27
|
+
const spawnSyncFn = options.spawnSyncFn ?? spawnSync;
|
|
28
|
+
const logFn = options.logFn ?? console.log;
|
|
29
|
+
const validateTimeoutMs =
|
|
30
|
+
options.validateTimeoutMs ?? defaultValidateTimeoutMs;
|
|
31
|
+
|
|
32
|
+
const key = platformKey(platform, arch);
|
|
33
|
+
const spec = PLATFORMS[key];
|
|
34
|
+
if (!spec) {
|
|
35
|
+
logFn(
|
|
36
|
+
`[lpm] No pre-built native npm package for ${key}; the JS wrapper will report this at runtime.`,
|
|
37
|
+
);
|
|
38
|
+
return { status: "unsupported", platform: key, optimized: [] };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const expectedVersion = readWrapperVersion(wrapperRoot, fsModule);
|
|
42
|
+
const lpmBinary = resolveNativeBinary({
|
|
43
|
+
command: "lpm",
|
|
44
|
+
platform,
|
|
45
|
+
arch,
|
|
46
|
+
env,
|
|
47
|
+
requireFn,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
validateNativeBinary(lpmBinary.path, {
|
|
51
|
+
expectedVersion,
|
|
52
|
+
env,
|
|
53
|
+
spawnSyncFn,
|
|
54
|
+
validateTimeoutMs,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const optimized = [];
|
|
58
|
+
for (const command of commands) {
|
|
59
|
+
const native =
|
|
60
|
+
command === "lpm"
|
|
61
|
+
? lpmBinary
|
|
62
|
+
: resolveNativeBinary({
|
|
63
|
+
command,
|
|
64
|
+
platform,
|
|
65
|
+
arch,
|
|
66
|
+
env,
|
|
67
|
+
requireFn,
|
|
68
|
+
});
|
|
69
|
+
const target = optimizedTargetPath(wrapperRoot, platform, command);
|
|
70
|
+
const action = stageOptimizedBinary({
|
|
71
|
+
source: native.path,
|
|
72
|
+
target,
|
|
73
|
+
platform,
|
|
74
|
+
fsModule,
|
|
75
|
+
});
|
|
76
|
+
optimized.push({
|
|
77
|
+
command,
|
|
78
|
+
source: native.path,
|
|
79
|
+
target,
|
|
80
|
+
action,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
logFn(`[lpm] Installed native binary from ${spec.pkg}`);
|
|
85
|
+
return { status: "installed", platform: key, package: spec.pkg, optimized };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function validateNativeBinary(
|
|
89
|
+
binaryPath,
|
|
90
|
+
{
|
|
91
|
+
expectedVersion = "",
|
|
92
|
+
env = process.env,
|
|
93
|
+
spawnSyncFn = spawnSync,
|
|
94
|
+
validateTimeoutMs = defaultValidateTimeoutMs,
|
|
95
|
+
} = {},
|
|
96
|
+
) {
|
|
97
|
+
let result;
|
|
98
|
+
try {
|
|
99
|
+
result = spawnSyncFn(binaryPath, ["--version"], {
|
|
100
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
101
|
+
encoding: "utf8",
|
|
102
|
+
env,
|
|
103
|
+
timeout: validateTimeoutMs,
|
|
104
|
+
});
|
|
105
|
+
} catch (error) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Native binary at ${binaryPath} could not be started: ${error.message}`,
|
|
108
|
+
{ cause: error },
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (result.error) {
|
|
113
|
+
const timeout =
|
|
114
|
+
result.error.code === "ETIMEDOUT"
|
|
115
|
+
? ` within ${Math.ceil(validateTimeoutMs / 1000)}s`
|
|
116
|
+
: "";
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Native binary at ${binaryPath} did not report its version${timeout}: ${result.error.message}`,
|
|
119
|
+
{ cause: result.error },
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (result.signal) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Native binary at ${binaryPath} was interrupted by ${result.signal} while reporting its version.`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (result.status !== 0) {
|
|
130
|
+
const stderr = String(result.stderr ?? "").trim();
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Native binary at ${binaryPath} exited with status ${result.status} while reporting its version.${stderr ? ` ${stderr}` : ""}`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
137
|
+
if (expectedVersion && !output.includes(expectedVersion)) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Native binary at ${binaryPath} reported version ${JSON.stringify(output || "<empty>")} instead of ${expectedVersion}.`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return output;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function stageOptimizedBinary({ source, target, platform, fsModule = fs }) {
|
|
147
|
+
fsModule.mkdirSync(path.dirname(target), { recursive: true });
|
|
148
|
+
if (platform === "win32") {
|
|
149
|
+
atomicCopy(source, target, fsModule);
|
|
150
|
+
return "copy";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
atomicLink(source, target, fsModule);
|
|
155
|
+
return "link";
|
|
156
|
+
} catch {
|
|
157
|
+
atomicCopy(source, target, fsModule);
|
|
158
|
+
return "copy";
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function optimizedTargetPath(wrapperRoot, platform, command) {
|
|
163
|
+
const filename = platform === "win32" ? `${command}.exe` : `${command}.js`;
|
|
164
|
+
return path.join(wrapperRoot, "bin", filename);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function readWrapperVersion(wrapperRoot, fsModule) {
|
|
168
|
+
try {
|
|
169
|
+
const manifest = JSON.parse(
|
|
170
|
+
fsModule.readFileSync(path.join(wrapperRoot, "package.json"), "utf8"),
|
|
171
|
+
);
|
|
172
|
+
return typeof manifest.version === "string" ? manifest.version : "";
|
|
173
|
+
} catch {
|
|
174
|
+
return "";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function atomicLink(source, target, fsModule) {
|
|
179
|
+
replaceWithTemp(target, fsModule, temp => {
|
|
180
|
+
fsModule.linkSync(source, temp);
|
|
181
|
+
fsModule.chmodSync(temp, 0o755);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function atomicCopy(source, target, fsModule) {
|
|
186
|
+
replaceWithTemp(target, fsModule, temp => {
|
|
187
|
+
fsModule.copyFileSync(source, temp);
|
|
188
|
+
fsModule.chmodSync(temp, 0o755);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function replaceWithTemp(target, fsModule, writeTemp) {
|
|
193
|
+
const temp = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
194
|
+
let replaced = false;
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
unlinkIfExists(temp, fsModule);
|
|
198
|
+
writeTemp(temp);
|
|
199
|
+
fsModule.renameSync(temp, target);
|
|
200
|
+
replaced = true;
|
|
201
|
+
} finally {
|
|
202
|
+
if (!replaced) {
|
|
203
|
+
unlinkIfExists(temp, fsModule);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function unlinkIfExists(filePath, fsModule) {
|
|
209
|
+
try {
|
|
210
|
+
fsModule.unlinkSync(filePath);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (error?.code !== "ENOENT") {
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function printInstallError(error) {
|
|
219
|
+
if (error instanceof LpmWrapperError) {
|
|
220
|
+
console.error(`[lpm] ${error.message}`);
|
|
221
|
+
for (const hint of error.hints) {
|
|
222
|
+
console.error(`[lpm] ${hint}`);
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
console.error(`[lpm] Failed to install native binary: ${error.message}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.error(
|
|
229
|
+
"[lpm] Reinstall with optional dependencies and install scripts enabled: npm install -g @lpm-registry/cli",
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
234
|
+
try {
|
|
235
|
+
installNativeBinaries();
|
|
236
|
+
} catch (error) {
|
|
237
|
+
printInstallError(error);
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
}
|