Skip to content

Commit 5c25613

Browse files
committed
feat(minor): customize postinstall behavior
1 parent c641959 commit 5c25613

File tree

6 files changed

+168
-5
lines changed

6 files changed

+168
-5
lines changed

docs/guide/troubleshooting.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,26 @@ Ensure you're not using the `Administrator` user for `npm install` nor to run th
164164
To do that, go to `Settings > Update & Security > For developers` and enable `Developer mode`.
165165

166166
After that, delete the `.cache` folder under your user directory and try building the app again.
167+
168+
## Customizing `postinstall` Behavior {#postinstall-behavior}
169+
When installing `node-llama-cpp`, its `postinstall` script checks whether the prebuilt binaries
170+
are compatible with current machine (which they almost always are, at least the CPU-only ones which are the last resort fallback),
171+
and when not, attempts [building the native bindings from source](./building-from-source.md).
172+
173+
When attempting to [build from source](./building-from-source.md), if the machine lacks the required build tools,
174+
the build will fail and indicative error messages will direct you to the specific commands you need to run
175+
or packages you need to install in order for the build process to succeed.
176+
177+
If you want to customize the `postinstall` behavior, you can do so using any of the following methods:
178+
* Passing the `--node-llama-cpp-postinstall=<behavior>` flag to the `npm install` command.
179+
* Setting the `NODE_LLAMA_CPP_POSTINSTALL` environment variable to `<behavior>` before running `npm install`.
180+
* Configuring `config.nodeLlamaCppPostinstall` on your project's `package.json` to `<behavior>`.
181+
182+
Where `<behavior>` can be one of the following options:
183+
* **`auto` (default)**: the default behavior explained above.
184+
* **`ignoreFailedBuild`**: same as the default behavior,
185+
but a failed build will not throw an error and will be ignored, which means the installation will succeed.
186+
Using [`getLlama`](../api/functions/getLlama.md) for the first time will attempt building from source again by default.
187+
* **`skip`**: skip the entire `postinstall` script.
188+
If the prebuilt binaries are incompatible with the current machine,
189+
using [`getLlama`](../api/functions/getLlama.md) for the first time will attempt building from source by default.

src/cli/commands/OnPostInstallCommand.ts

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1+
import path from "path";
2+
import {fileURLToPath} from "url";
13
import {CommandModule} from "yargs";
24
import chalk from "chalk";
3-
import {defaultSkipDownload, documentationPageUrls} from "../../config.js";
5+
import {defaultSkipDownload, documentationPageUrls, defaultNodeLlamaCppPostinstall} from "../../config.js";
46
import {getLlamaForOptions} from "../../bindings/getLlama.js";
57
import {setForceShowConsoleLogPrefix} from "../../state.js";
68
import {isRunningUnderRosetta} from "../utils/isRunningUnderRosetta.js";
79
import {getConsoleLogPrefix} from "../../utils/getConsoleLogPrefix.js";
10+
import {parsePackageJsonConfig, resolvePackageJsonConfig} from "../utils/packageJsonConfig.js";
11+
import {detectCurrentPackageManager} from "../utils/packageManager.js";
12+
13+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
814

915
type OnPostInstallCommand = null;
1016

@@ -13,7 +19,22 @@ export const OnPostInstallCommand: CommandModule<object, OnPostInstallCommand> =
1319
describe: false,
1420
async handler() {
1521
if (defaultSkipDownload)
16-
return;
22+
return void process.exit(0);
23+
24+
const nlcConfig = parsePackageJsonConfig(await resolvePackageJsonConfig(__dirname));
25+
const postinstallConfig = (defaultNodeLlamaCppPostinstall == null || defaultNodeLlamaCppPostinstall === "auto")
26+
? nlcConfig.nodeLlamaCppPostinstall ?? defaultNodeLlamaCppPostinstall
27+
: defaultNodeLlamaCppPostinstall;
28+
29+
// set via a `--node-llama-cpp-postinstall=skip` flag on an `npm install` command
30+
// (prefer `--node-llama-cpp-postinstall=ignoreFailedBuild` if you really need it)
31+
if (postinstallConfig === "skip") {
32+
console.info(
33+
getConsoleLogPrefix(false, false),
34+
"Skipping node-llama-cpp postinstall due to a 'skip' configuration"
35+
);
36+
return void process.exit(0);
37+
}
1738

1839
setForceShowConsoleLogPrefix(true);
1940

@@ -34,7 +55,10 @@ export const OnPostInstallCommand: CommandModule<object, OnPostInstallCommand> =
3455
"troubleshooting: " + documentationPageUrls.troubleshooting.RosettaIllegalHardwareInstruction
3556
);
3657

37-
process.exit(1);
58+
if (postinstallConfig === "ignoreFailedBuild")
59+
process.exit(0);
60+
else
61+
process.exit(1);
3862
}
3963

4064
try {
@@ -47,7 +71,25 @@ export const OnPostInstallCommand: CommandModule<object, OnPostInstallCommand> =
4771
process.exit(0);
4872
} catch (err) {
4973
console.error(err);
50-
process.exit(1);
74+
75+
const packageManager = detectCurrentPackageManager();
76+
if (postinstallConfig === "auto" && packageManager === "npm")
77+
console.info(
78+
getConsoleLogPrefix(false, false),
79+
"To disable node-llama-cpp's postinstall for this 'npm install', use the '--node-llama-cpp-postinstall=skip' flag when running 'npm install' command"
80+
);
81+
82+
if (postinstallConfig === "auto")
83+
console.info(
84+
getConsoleLogPrefix(false, false),
85+
"To customize node-llama-cpp's postinstall behavior, see the troubleshooting guide: " +
86+
documentationPageUrls.troubleshooting.PostinstallBehavior
87+
);
88+
89+
if (postinstallConfig === "ignoreFailedBuild")
90+
process.exit(0);
91+
else
92+
process.exit(1);
5193
}
5294
}
5395
};

src/cli/utils/packageJsonConfig.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import path from "path";
2+
import fs from "fs-extra";
3+
import {NodeLlamaCppPostinstallBehavior} from "../../types.js";
4+
5+
export async function resolvePackageJsonConfig(startDir: string) {
6+
const currentConfig: Record<string, any> = {};
7+
8+
let currentDirPath = path.resolve(startDir);
9+
while (true) {
10+
const packageJsonPath = path.join(currentDirPath, "package.json");
11+
try {
12+
if (await fs.pathExists(packageJsonPath))
13+
applyConfig(currentConfig, await readPackageJsonConfig(packageJsonPath));
14+
} catch (err) {
15+
// do nothing
16+
}
17+
18+
const parentDirPath = path.dirname(currentDirPath);
19+
if (parentDirPath === currentDirPath)
20+
break;
21+
22+
currentDirPath = parentDirPath;
23+
}
24+
25+
return currentConfig;
26+
}
27+
28+
export function parsePackageJsonConfig(config: Record<string, any>) {
29+
const res: NlcPackageJsonConfig = {};
30+
31+
const castedConfig = config as NlcPackageJsonConfig;
32+
33+
if (castedConfig.nodeLlamaCppPostinstall === "auto" ||
34+
castedConfig.nodeLlamaCppPostinstall === "ignoreFailedBuild" ||
35+
castedConfig.nodeLlamaCppPostinstall === "skip"
36+
)
37+
res.nodeLlamaCppPostinstall = castedConfig.nodeLlamaCppPostinstall;
38+
else
39+
void (castedConfig.nodeLlamaCppPostinstall satisfies undefined);
40+
41+
return res;
42+
}
43+
44+
export type NlcPackageJsonConfig = {
45+
nodeLlamaCppPostinstall?: NodeLlamaCppPostinstallBehavior
46+
};
47+
48+
function readPackageJsonConfig(packageJsonPath: string) {
49+
try {
50+
const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8");
51+
const packageJson = JSON.parse(packageJsonContent);
52+
const config = packageJson?.config;
53+
if (typeof config === "object")
54+
return config;
55+
56+
return {};
57+
} catch (err) {
58+
return {};
59+
}
60+
}
61+
62+
function applyConfig(baseConfig: Record<string, any>, newConfig: Record<string, any>) {
63+
for (const key in newConfig) {
64+
if (key in baseConfig)
65+
continue;
66+
67+
baseConfig[key] = newConfig[key];
68+
}
69+
}

src/cli/utils/packageManager.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export function detectCurrentPackageManager(): "npm" | "bun" | "pnpm" | "deno" | "yarn" | undefined {
2+
const userAgent = (process.env["npm_config_user_agent"] ?? "").toLowerCase();
3+
4+
if (userAgent.startsWith("bun/"))
5+
return "bun";
6+
else if (userAgent.startsWith("pnpm/"))
7+
return "pnpm";
8+
else if (userAgent.startsWith("yarn/"))
9+
return "yarn";
10+
else if (userAgent.startsWith("deno/"))
11+
return "deno";
12+
else if (userAgent.startsWith("npm/"))
13+
return "npm";
14+
15+
return undefined;
16+
}

src/config.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {getBinariesGithubRelease} from "./bindings/utils/binariesGithubRelease.j
88
import {
99
nodeLlamaCppGpuOptions, LlamaLogLevel, LlamaLogLevelValues, parseNodeLlamaCppGpuOption, nodeLlamaCppGpuOffStringOptions
1010
} from "./bindings/types.js";
11+
import type {NodeLlamaCppPostinstallBehavior} from "./types.js";
1112

1213
const __dirname = path.dirname(fileURLToPath(import.meta.url));
1314

@@ -75,6 +76,15 @@ export const defaultLlamaCppDebugMode = env.get("NODE_LLAMA_CPP_DEBUG")
7576
export const defaultSkipDownload = env.get("NODE_LLAMA_CPP_SKIP_DOWNLOAD")
7677
.default("false")
7778
.asBool();
79+
80+
// set via a `--node-llama-cpp-postinstall=ignoreFailedBuild` flag on an `npm install` command
81+
export const defaultNodeLlamaCppPostinstall = env.get("NODE_LLAMA_CPP_POSTINSTALL")
82+
.default(
83+
env.get("npm_config_node_llama_cpp_postinstall")
84+
.default("auto")
85+
.asEnum(["auto", "ignoreFailedBuild", "skip"] as const satisfies NodeLlamaCppPostinstallBehavior[])
86+
)
87+
.asEnum(["auto", "ignoreFailedBuild", "skip"] as const satisfies NodeLlamaCppPostinstallBehavior[]);
7888
export const defaultBindingTestLogLevel = env.get("NODE_LLAMA_CPP_BINDING_TEST_LOG_LEVEL")
7989
.default(LlamaLogLevel.error)
8090
.asEnum(LlamaLogLevelValues);
@@ -125,7 +135,8 @@ export const documentationPageUrls = {
125135
}
126136
},
127137
troubleshooting: {
128-
RosettaIllegalHardwareInstruction: documentationUrl + "/guide/troubleshooting#illegal-hardware-instruction"
138+
RosettaIllegalHardwareInstruction: documentationUrl + "/guide/troubleshooting#illegal-hardware-instruction",
139+
PostinstallBehavior: documentationUrl + "/guide/troubleshooting#postinstall-behavior"
129140
}
130141
} as const;
131142
export const newGithubIssueUrl = "https://github.com/withcatai/node-llama-cpp/issues";

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,3 +477,5 @@ export type LLamaContextualDryRepeatPenalty = {
477477
*/
478478
sequenceBreakers?: string[]
479479
};
480+
481+
export type NodeLlamaCppPostinstallBehavior = "auto" | "ignoreFailedBuild" | "skip";

0 commit comments

Comments
 (0)