Skip to content

Commit c032034

Browse files
committed
fix: locate bundled lame binary in flattened dist builds
1 parent 38fc124 commit c032034

6 files changed

Lines changed: 128 additions & 7 deletions

File tree

.github/workflows/ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ jobs:
4343
- name: Build package
4444
run: pnpm run build
4545

46+
- name: Ensure bundled LAME binary
47+
env:
48+
LAME_FORCE_DOWNLOAD: "1"
49+
run: node ./scripts/install-lame.mjs
50+
51+
- name: Diagnose bundled LAME binary
52+
run: node ./scripts/diagnose-lame.mjs
53+
4654
- name: Run examples
4755
run: |
4856
pnpm run example:wav-to-mp3

scripts/diagnose-lame.mjs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/usr/bin/env node
2+
3+
import { statSync } from "node:fs";
4+
import { spawnSync } from "node:child_process";
5+
import { platform } from "node:os";
6+
7+
const {
8+
resolveLameBinary,
9+
} = await import(new URL("../dist/index.cjs", import.meta.url));
10+
11+
function logSection(title) {
12+
console.log(`[lame-diagnostics] ${title}`);
13+
}
14+
15+
const binaryPath = resolveLameBinary();
16+
logSection(`Resolved binary: ${binaryPath}`);
17+
18+
try {
19+
const stats = statSync(binaryPath);
20+
logSection(
21+
`File stats -> size=${stats.size} mode=${stats.mode.toString(8)} mtime=${stats.mtime.toISOString()}`,
22+
);
23+
} catch (error) {
24+
logSection(`stat() failed: ${error instanceof Error ? error.message : error}`);
25+
}
26+
27+
const runCommand = (cmd, args) => {
28+
const result = spawnSync(cmd, args, {
29+
encoding: "utf-8",
30+
});
31+
32+
logSection(
33+
`${cmd} ${args.join(" ")} exited ${result.status} (signal: ${result.signal ?? "none"})`,
34+
);
35+
36+
if (result.stdout) {
37+
console.log(result.stdout.trim());
38+
}
39+
40+
if (result.stderr) {
41+
console.error(result.stderr.trim());
42+
}
43+
};
44+
45+
runCommand(binaryPath, ["--version"]);
46+
47+
if (platform() === "linux") {
48+
runCommand("ldd", [binaryPath]);
49+
}

src/core/lame-process.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,10 @@ function processProgressChunk(
168168
return {};
169169
}
170170

171-
function getExitError(code: number | null): Error | null {
171+
function getExitError(
172+
code: number | null,
173+
executable: string,
174+
): Error | null {
172175
if (code === 0) {
173176
return null;
174177
}
@@ -179,6 +182,12 @@ function getExitError(code: number | null): Error | null {
179182
);
180183
}
181184

185+
if (code === 127) {
186+
return new Error(
187+
`lame: Failed to execute '${executable}'. Exit code 127 usually indicates missing shared libraries or an unreadable binary. Run scripts/diagnose-lame.mjs for details.`,
188+
);
189+
}
190+
182191
if (code !== null) {
183192
return new Error(`lame: Process exited with code ${code}`);
184193
}
@@ -323,7 +332,7 @@ function spawnLameProcess(
323332

324333
child.on("error", emitCliError);
325334
child.on("close", (code) => {
326-
const exitError = getExitError(code);
335+
const exitError = getExitError(code, executable);
327336
if (exitError) {
328337
const bufferedLines = stderrBuffer
329338
.split(/\r?\n/)

src/internal/binary/resolve-binary.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { existsSync } from "node:fs";
2-
import { join } from "node:path";
2+
import { dirname, join } from "node:path";
33
import { fileURLToPath, pathToFileURL } from "node:url";
44

55
declare const __dirname: string | undefined;
@@ -36,10 +36,41 @@ const moduleUrl = (() => {
3636
return deriveModuleUrl(meta, resolvedFilename);
3737
})();
3838

39+
function findPackageRootFrom(startDir: string): string | null {
40+
let current = startDir;
41+
42+
while (true) {
43+
const packageJsonPath = join(current, "package.json");
44+
if (existsSync(packageJsonPath)) {
45+
return current;
46+
}
47+
48+
const parent = dirname(current);
49+
if (parent === current) {
50+
return null;
51+
}
52+
53+
current = parent;
54+
}
55+
}
56+
3957
function resolvePackageRoot(
4058
moduleHref: string | undefined,
4159
dirname: string | undefined,
4260
): string {
61+
const normalizedDir =
62+
dirname ??
63+
(moduleHref != null
64+
? fileURLToPath(new URL(".", moduleHref))
65+
: undefined);
66+
67+
if (normalizedDir) {
68+
const detectedRoot = findPackageRootFrom(normalizedDir);
69+
if (detectedRoot) {
70+
return detectedRoot;
71+
}
72+
}
73+
4374
if (dirname) {
4475
return join(dirname, "..", "..", "..");
4576
}

tests/unit/lame-process.test.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,22 +184,30 @@ describe("processProgressChunk", () => {
184184
});
185185

186186
describe("getExitError", () => {
187+
const binary = "/tmp/lame";
188+
187189
it("returns null for successful exit", () => {
188-
expect(getExitError(0)).toBeNull();
190+
expect(getExitError(0, binary)).toBeNull();
189191
});
190192

191193
it("returns descriptive error for exit code 255", () => {
192-
const error = getExitError(255);
194+
const error = getExitError(255, binary);
193195
expect(error?.message).toContain("Unexpected termination of the process");
194196
});
195197

198+
it("returns descriptive error for exit code 127", () => {
199+
const error = getExitError(127, binary);
200+
expect(error?.message).toContain(binary);
201+
expect(error?.message).toContain("127");
202+
});
203+
196204
it("returns error for other exit codes", () => {
197-
const error = getExitError(3);
205+
const error = getExitError(3, binary);
198206
expect(error?.message).toBe("lame: Process exited with code 3");
199207
});
200208

201209
it("handles null exit codes", () => {
202-
const error = getExitError(null);
210+
const error = getExitError(null, binary);
203211
expect(error?.message).toBe("lame: Process exited unexpectedly");
204212
});
205213
});

tests/unit/resolve-binary.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,20 @@ describe("resolve-binary", () => {
105105
const derivedFromCwd = resolvePackageRoot(undefined, undefined);
106106
expect(derivedFromCwd).toBe(process.cwd());
107107
});
108+
109+
it("detects package root when compiled assets live directly inside dist", async () => {
110+
const projectRoot = join(sharedTempDir, "pkg-flat");
111+
const distDir = join(projectRoot, "dist");
112+
113+
await mkdir(distDir, { recursive: true });
114+
await writeFile(join(projectRoot, "package.json"), "{}");
115+
116+
const { resolvePackageRoot } = await importResolver();
117+
const derived = resolvePackageRoot(
118+
pathToFileURL(join(distDir, "index.cjs")).href,
119+
distDir,
120+
);
121+
122+
expect(derived).toBe(projectRoot);
123+
});
108124
});

0 commit comments

Comments
 (0)