@lark-codem/codem-core 0.8.2 → 0.8.3
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/bin/lib/control-fd.js +63 -0
- package/bin/lib/launch.js +106 -0
- package/bin/linco +12 -9
- package/package.json +10 -10
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// codem-bridge launches linco with FOUR stdio slots, not three:
|
|
4
|
+
//
|
|
5
|
+
// stdio: ["inherit", "inherit", "inherit", "pipe"] (codem-bridge linco-runner.js)
|
|
6
|
+
// \______ the terminal ______/ \_ the /space control channel
|
|
7
|
+
//
|
|
8
|
+
// fd 3 is an out-of-band channel: on a `/space` selection linco writes one NDJSON
|
|
9
|
+
// line to it so codem-bridge can persist the chosen project without polluting the
|
|
10
|
+
// TUI's screen. The host announces the fd number via `CODEM_PROJECT_CHANNEL_FD`.
|
|
11
|
+
//
|
|
12
|
+
// This shim sits BETWEEN the two processes, and `stdio: "inherit"` is shorthand for
|
|
13
|
+
// ["inherit","inherit","inherit"] — exactly three slots. It therefore dropped fd 3
|
|
14
|
+
// on the floor while still forwarding the env var that advertises it, so linco kept
|
|
15
|
+
// writing to a fd the exec'd process no longer held (ENXIO, not EBADF: libuv fills
|
|
16
|
+
// unlisted slots with an unusable placeholder rather than leaving them closed).
|
|
17
|
+
//
|
|
18
|
+
// All three layers then failed quietly — linco's writer is best-effort and only
|
|
19
|
+
// `tracing::warn!`s, and on the host side an idle pipe is indistinguishable from
|
|
20
|
+
// "an old linco that never writes fd 3" (codem-bridge says so in a comment). Net
|
|
21
|
+
// effect: `/space` reported success and the selection was silently lost.
|
|
22
|
+
//
|
|
23
|
+
// SCOPE — unix only. linco's writer is a `#[cfg(not(unix))]` no-op on Windows
|
|
24
|
+
// (crates/linco-cli/src/space_channel.rs), so there is nothing to forward there and
|
|
25
|
+
// no reason to perturb Windows process creation.
|
|
26
|
+
|
|
27
|
+
/// The env var through which codem-bridge advertises the control fd.
|
|
28
|
+
const CONTROL_FD_ENV = "CODEM_PROJECT_CHANNEL_FD";
|
|
29
|
+
|
|
30
|
+
/// stdio for a plain interactive run: the terminal and nothing else.
|
|
31
|
+
const TERMINAL_STDIO = ["inherit", "inherit", "inherit"];
|
|
32
|
+
|
|
33
|
+
/// Parse the advertised fd, or null when there is nothing to forward.
|
|
34
|
+
///
|
|
35
|
+
/// Only fd 3 is expressible here: `stdio` slots are positional, so the 4th entry
|
|
36
|
+
/// IS fd 3. A host asking for fd 4+ cannot be honoured by appending one entry, and
|
|
37
|
+
/// silently forwarding the wrong fd would be worse than not forwarding at all.
|
|
38
|
+
function controlFdFromEnv(env) {
|
|
39
|
+
const raw = env && env[CONTROL_FD_ENV];
|
|
40
|
+
if (typeof raw !== "string") {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const trimmed = raw.trim();
|
|
44
|
+
// Number.parseInt would accept "3x"/"3.5"; the host writes a bare integer.
|
|
45
|
+
if (!/^-?\d+$/.test(trimmed)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return Number.parseInt(trimmed, 10) === 3 ? 3 : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Build the stdio array to hand the platform binary. Returns a fresh array —
|
|
52
|
+
/// `launch` mutates it when degrading after an EBADF.
|
|
53
|
+
function buildStdio({ env = process.env, platform = process.platform } = {}) {
|
|
54
|
+
if (platform === "win32") {
|
|
55
|
+
return TERMINAL_STDIO.slice();
|
|
56
|
+
}
|
|
57
|
+
if (controlFdFromEnv(env) === null) {
|
|
58
|
+
return TERMINAL_STDIO.slice();
|
|
59
|
+
}
|
|
60
|
+
return [...TERMINAL_STDIO, "inherit"];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { buildStdio, controlFdFromEnv, CONTROL_FD_ENV, TERMINAL_STDIO };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Spawning the platform binary, with the two failure modes that actually reach
|
|
4
|
+
// users spelled out.
|
|
5
|
+
//
|
|
6
|
+
// WHY THIS IS NOT JUST `spawn(...)` + `child.on("error")`:
|
|
7
|
+
// `child_process.spawn` reports ENOENT asynchronously (via the "error" event) but
|
|
8
|
+
// throws SYNCHRONOUSLY for every other errno — see the `throw new ErrnoException(err,
|
|
9
|
+
// 'spawn')` in node:internal/child_process. An `on("error")` handler therefore cannot
|
|
10
|
+
// observe an EACCES/EBADF/UNKNOWN failure, and the user gets a raw Node stack trace
|
|
11
|
+
// naming an internal file instead of a sentence about their own machine. Both paths
|
|
12
|
+
// must funnel into the same reporter.
|
|
13
|
+
|
|
14
|
+
const { TERMINAL_STDIO } = require("./control-fd");
|
|
15
|
+
|
|
16
|
+
/// Human-readable follow-up for the errnos we can actually say something useful
|
|
17
|
+
/// about. Anything else falls through to the bare message.
|
|
18
|
+
function hintsFor(code, platform, binary) {
|
|
19
|
+
switch (code) {
|
|
20
|
+
case "UNKNOWN":
|
|
21
|
+
// libuv's catch-all for Win32 errors it has no mapping for. The common
|
|
22
|
+
// cause in the field is CreateProcessW being denied by security software
|
|
23
|
+
// (ERROR_VIRUS_INFECTED / ERROR_VIRUS_DELETED have no libuv mapping).
|
|
24
|
+
return platform === "win32"
|
|
25
|
+
? [
|
|
26
|
+
"On Windows this usually means antivirus or endpoint security software blocked",
|
|
27
|
+
"the executable, or the file on disk is damaged. Run it directly to see the",
|
|
28
|
+
"underlying OS error:",
|
|
29
|
+
` & "${binary}" --version`,
|
|
30
|
+
]
|
|
31
|
+
: null;
|
|
32
|
+
case "ENOENT":
|
|
33
|
+
return [
|
|
34
|
+
"The platform binary is missing. Reinstall it with:",
|
|
35
|
+
" npm install --include=optional @lark-codem/codem-core",
|
|
36
|
+
];
|
|
37
|
+
case "EACCES":
|
|
38
|
+
return [
|
|
39
|
+
"The platform binary is not executable. Check its file permissions:",
|
|
40
|
+
` ls -l "${binary}"`,
|
|
41
|
+
];
|
|
42
|
+
default:
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function reportFatal({ error, binary, platform, stderr, exit }) {
|
|
48
|
+
stderr(`linco: failed to launch ${binary}`);
|
|
49
|
+
stderr(` ${error && error.message ? error.message : String(error)}`);
|
|
50
|
+
const hints = hintsFor(error && error.code, platform, binary);
|
|
51
|
+
if (hints) {
|
|
52
|
+
for (const line of hints) {
|
|
53
|
+
stderr(` ${line}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Spawn the platform binary. Returns the child process, or undefined when the
|
|
60
|
+
/// launch failed (the caller has already been told via `exit`).
|
|
61
|
+
///
|
|
62
|
+
/// `stdio` may carry a 4th slot for codem-bridge's /space control fd. If the host
|
|
63
|
+
/// advertised that fd but did not actually pass it, spawning fails with EBADF —
|
|
64
|
+
/// degrade to the terminal-only trio and start anyway rather than refusing to run
|
|
65
|
+
/// over a non-essential side channel.
|
|
66
|
+
function launch({
|
|
67
|
+
binary,
|
|
68
|
+
args,
|
|
69
|
+
stdio,
|
|
70
|
+
platform = process.platform,
|
|
71
|
+
spawnImpl = require("node:child_process").spawn,
|
|
72
|
+
stderr = (line) => console.error(line),
|
|
73
|
+
exit = (code) => process.exit(code),
|
|
74
|
+
}) {
|
|
75
|
+
const attempt = (stdioForAttempt) =>
|
|
76
|
+
spawnImpl(binary, args, { stdio: stdioForAttempt, windowsHide: true });
|
|
77
|
+
|
|
78
|
+
let child;
|
|
79
|
+
try {
|
|
80
|
+
child = attempt(stdio);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (stdio.length > 3 && error && error.code === "EBADF") {
|
|
83
|
+
stderr(
|
|
84
|
+
"linco: the host advertised a /space control fd it did not pass; continuing without it"
|
|
85
|
+
);
|
|
86
|
+
try {
|
|
87
|
+
child = attempt(TERMINAL_STDIO.slice());
|
|
88
|
+
} catch (retryError) {
|
|
89
|
+
reportFatal({ error: retryError, binary, platform, stderr, exit });
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
reportFatal({ error, binary, platform, stderr, exit });
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ENOENT lands here instead of in the catch above.
|
|
99
|
+
child.on("error", (error) => {
|
|
100
|
+
reportFatal({ error, binary, platform, stderr, exit });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return child;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = { launch, hintsFor };
|
package/bin/linco
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
const { spawn } = require("node:child_process");
|
|
5
4
|
const fs = require("node:fs");
|
|
6
5
|
const { detectTarget } = require("./lib/detect-target");
|
|
7
6
|
const { resolvePlatformBinary } = require("./lib/resolve-binary");
|
|
7
|
+
const { buildStdio } = require("./lib/control-fd");
|
|
8
|
+
const { launch } = require("./lib/launch");
|
|
8
9
|
|
|
9
10
|
function main() {
|
|
10
11
|
let target;
|
|
@@ -30,15 +31,17 @@ function main() {
|
|
|
30
31
|
// best-effort
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
console.error(`linco: failed to spawn ${binary}: ${error.message}`);
|
|
40
|
-
process.exit(1);
|
|
34
|
+
// Not a bare `stdio: "inherit"`: that is three slots and would drop the /space
|
|
35
|
+
// control fd codem-bridge passes as fd 3. See lib/control-fd.js.
|
|
36
|
+
const child = launch({
|
|
37
|
+
binary,
|
|
38
|
+
args: process.argv.slice(2),
|
|
39
|
+
stdio: buildStdio(),
|
|
41
40
|
});
|
|
41
|
+
if (!child) {
|
|
42
|
+
// launch() already reported the failure and called exit(1).
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
42
45
|
|
|
43
46
|
const forwardSignal = (signal) => {
|
|
44
47
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-codem/codem-core",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "CodeM CLI.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"bin": {
|
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
"LICENSE"
|
|
12
12
|
],
|
|
13
13
|
"optionalDependencies": {
|
|
14
|
-
"@lark-codem/codem-core-darwin-arm64": "0.8.
|
|
15
|
-
"@lark-codem/codem-core-darwin-x64": "0.8.
|
|
16
|
-
"@lark-codem/codem-core-linux-arm64-gnu": "0.8.
|
|
17
|
-
"@lark-codem/codem-core-linux-arm64-musl": "0.8.
|
|
18
|
-
"@lark-codem/codem-core-linux-x64-gnu": "0.8.
|
|
19
|
-
"@lark-codem/codem-core-linux-x64-musl": "0.8.
|
|
20
|
-
"@lark-codem/codem-core-win32-arm64-gnu": "0.8.
|
|
21
|
-
"@lark-codem/codem-core-win32-ia32-gnu": "0.8.
|
|
22
|
-
"@lark-codem/codem-core-win32-x64-gnu": "0.8.
|
|
14
|
+
"@lark-codem/codem-core-darwin-arm64": "0.8.3",
|
|
15
|
+
"@lark-codem/codem-core-darwin-x64": "0.8.3",
|
|
16
|
+
"@lark-codem/codem-core-linux-arm64-gnu": "0.8.3",
|
|
17
|
+
"@lark-codem/codem-core-linux-arm64-musl": "0.8.3",
|
|
18
|
+
"@lark-codem/codem-core-linux-x64-gnu": "0.8.3",
|
|
19
|
+
"@lark-codem/codem-core-linux-x64-musl": "0.8.3",
|
|
20
|
+
"@lark-codem/codem-core-win32-arm64-gnu": "0.8.3",
|
|
21
|
+
"@lark-codem/codem-core-win32-ia32-gnu": "0.8.3",
|
|
22
|
+
"@lark-codem/codem-core-win32-x64-gnu": "0.8.3"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=18"
|