feat: package npm runtime plugins for Nix
Add a hash-backed npm runtime plugin path that lowers OpenClaw-style npm sources into immutable plugin roots and wires them through the existing Home Manager plugin resolver. Keep flake-backed customPlugins unchanged and document the boundary for agents and maintainers. Tests: nix build .#checks.aarch64-darwin.default-instance --no-link; nix flake check --no-build; git diff --check Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
parent
30002b7ded
commit
11d69d8a1c
@ -49,7 +49,7 @@ Source: https://github.com/orgs/openclaw/people
|
|||||||
- Never add internal ExecPlans or agent scratch history to this repo. `.agent/` is ignored for this reason.
|
- Never add internal ExecPlans or agent scratch history to this repo. `.agent/` is ignored for this reason.
|
||||||
- If a private deployment exposes a public packaging bug, fix the public package here and keep deployment-specific repair elsewhere.
|
- If a private deployment exposes a public packaging bug, fix the public package here and keep deployment-specific repair elsewhere.
|
||||||
- OpenClaw plugin loading belongs here: package curated runtime plugin roots as Nix artifacts, expose curated outputs through package/check outputs for Garnix, and let host repos only enable/configure them.
|
- OpenClaw plugin loading belongs here: package curated runtime plugin roots as Nix artifacts, expose curated outputs through package/check outputs for Garnix, and let host repos only enable/configure them.
|
||||||
- Do not make host config run npm/ClawHub installs at runtime for the batteries-included path. Arbitrary plugin specs need a lock/hash-backed Nix derivation so Nix caches them locally or in the user's configured cache.
|
- Do not make host config run npm/ClawHub installs at runtime for the batteries-included path. `customPlugins.source = "npm:..."` is allowed only when nix-openclaw turns it into an immutable, hash-backed store path and wires it through OpenClaw's normal `plugins.load.paths`.
|
||||||
|
|
||||||
## Packaging Defaults
|
## Packaging Defaults
|
||||||
|
|
||||||
|
|||||||
17
README.md
17
README.md
@ -320,6 +320,23 @@ customPlugins = [
|
|||||||
|
|
||||||
Then run `home-manager switch` to install.
|
Then run `home-manager switch` to install.
|
||||||
|
|
||||||
|
For an OpenClaw native plugin published to npm, keep the source shape close to
|
||||||
|
OpenClaw's own install command and let Nix build the immutable plugin root:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
customPlugins = [
|
||||||
|
{
|
||||||
|
source = "npm:@scope/openclaw-plugin@1.2.3";
|
||||||
|
id = "openclaw-plugin";
|
||||||
|
hash = lib.fakeHash; # replace with the sha256 Nix reports
|
||||||
|
}
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
Use this for OpenClaw runtime plugins with `openclaw.plugin.json` /
|
||||||
|
`package.json.openclaw`. It does not run npm at gateway startup; Nix builds and
|
||||||
|
caches the plugin root, then adds it to OpenClaw's `plugins.load.paths`.
|
||||||
|
|
||||||
### Plugins with configuration
|
### Plugins with configuration
|
||||||
|
|
||||||
Some plugins need settings (auth files, preferences). Here's a simplified example:
|
Some plugins need settings (auth files, preferences). Here's a simplified example:
|
||||||
|
|||||||
@ -73,7 +73,25 @@ programs.openclaw.customPlugins = [
|
|||||||
|
|
||||||
Do not add raw npm package names to host config for the batteries-included path. Curated plugins packaged by this repo or `nix-openclaw-tools` should be exposed through package/check outputs so Garnix caches them.
|
Do not add raw npm package names to host config for the batteries-included path. Curated plugins packaged by this repo or `nix-openclaw-tools` should be exposed through package/check outputs so Garnix caches them.
|
||||||
|
|
||||||
Arbitrary user plugins are a separate product surface. A future config like `plugins = [ "scope/plugin@npm:1.2.3" ]` must resolve through a lock/hash-backed npm/ClawHub builder that produces a normal Nix store path. That means our Garnix does not promise to cache every user plugin, but the user's machine also does not rebuild it on every run: Nix reuses the local store or configured binary cache until the spec, lock, or hash changes. OpenClaw must not reinstall it on every gateway start.
|
OpenClaw native npm plugins use the same host list with an OpenClaw-style source:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
programs.openclaw.customPlugins = [
|
||||||
|
{
|
||||||
|
source = "npm:@scope/openclaw-plugin@1.2.3";
|
||||||
|
id = "openclaw-plugin";
|
||||||
|
hash = lib.fakeHash; # replace with the sha256 Nix reports
|
||||||
|
}
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
- `source`: currently supports registry npm specs with an explicit `npm:` prefix.
|
||||||
|
- `id`: required because the Home Manager module must enable the plugin at eval time without importing the built JavaScript package.
|
||||||
|
- `hash`: recursive output hash for the immutable plugin root; leave as `lib.fakeHash` to have Nix report the expected hash, then commit that value.
|
||||||
|
- Runtime plugin config belongs in `programs.openclaw.config.plugins.entries.<id>.config`, not in `customPlugins.config`.
|
||||||
|
- The module adds the built root to `plugins.load.paths` and writes a default `plugins.entries.<id>.enabled` value. OpenClaw owns runtime loading after that.
|
||||||
|
|
||||||
|
Curated npm plugins can be added to this repo or `nix-openclaw-tools` so Garnix caches them. Arbitrary user npm specs are still deterministic Nix artifacts, but this repo's cache cannot cover every user's private plugin choice. The user's local store or configured binary cache reuses the artifact until the source or hash changes. OpenClaw must not reinstall it on every gateway start.
|
||||||
|
|
||||||
## Dev workflow (fast iteration)
|
## Dev workflow (fast iteration)
|
||||||
- Worktree: build and test plugins outside the core repo; point OpenClaw at a local path source during impure local dev (e.g., `source = "path:/Users/you/code/my-plugin"`). Committed config uses pinned refs.
|
- Worktree: build and test plugins outside the core repo; point OpenClaw at a local path source during impure local dev (e.g., `source = "path:/Users/you/code/my-plugin"`). Committed config uses pinned refs.
|
||||||
|
|||||||
@ -319,6 +319,37 @@ let
|
|||||||
throw "User config could not override OpenClaw plugin enabled=false default."
|
throw "User config could not override OpenClaw plugin enabled=false default."
|
||||||
);
|
);
|
||||||
|
|
||||||
|
npmRuntimePluginEval = moduleEval {
|
||||||
|
customPlugins = [
|
||||||
|
{
|
||||||
|
source = "npm:@tencent-weixin/openclaw-weixin@2.4.2";
|
||||||
|
id = "openclaw-weixin";
|
||||||
|
hash = lib.fakeHash;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
npmRuntimePluginConfig = builtins.fromJSON (
|
||||||
|
builtins.unsafeDiscardStringContext
|
||||||
|
npmRuntimePluginEval.config.home.file.".openclaw/openclaw.json".text
|
||||||
|
);
|
||||||
|
npmRuntimePluginLoadPaths = ((npmRuntimePluginConfig.plugins or { }).load or { }).paths or [ ];
|
||||||
|
npmRuntimePluginEntry =
|
||||||
|
((npmRuntimePluginConfig.plugins or { }).entries or { }).openclaw-weixin or { };
|
||||||
|
npmRuntimePluginCheck =
|
||||||
|
builtins.deepSeq (requireNoAssertionFailures "npm OpenClaw runtime plugin" npmRuntimePluginEval)
|
||||||
|
(
|
||||||
|
if
|
||||||
|
!(lib.any (
|
||||||
|
path: lib.hasInfix "openclaw-runtime-plugin-openclaw-weixin" path
|
||||||
|
) npmRuntimePluginLoadPaths)
|
||||||
|
then
|
||||||
|
throw "npm OpenClaw runtime plugin root was not added to plugins.load.paths."
|
||||||
|
else if (npmRuntimePluginEntry.enabled or false) != true then
|
||||||
|
throw "npm OpenClaw runtime plugin entry default was not enabled."
|
||||||
|
else
|
||||||
|
"ok"
|
||||||
|
);
|
||||||
|
|
||||||
checkKey = builtins.deepSeq [
|
checkKey = builtins.deepSeq [
|
||||||
defaultCheck
|
defaultCheck
|
||||||
customPluginCheck
|
customPluginCheck
|
||||||
@ -330,6 +361,7 @@ let
|
|||||||
openclawPluginCheck
|
openclawPluginCheck
|
||||||
openclawPluginOverrideCheck
|
openclawPluginOverrideCheck
|
||||||
openclawPluginEnableOverrideCheck
|
openclawPluginEnableOverrideCheck
|
||||||
|
npmRuntimePluginCheck
|
||||||
] "ok";
|
] "ok";
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|||||||
48
nix/lib/npm-runtime-plugin.nix
Normal file
48
nix/lib/npm-runtime-plugin.nix
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
stdenvNoCC,
|
||||||
|
nodejs_22,
|
||||||
|
}:
|
||||||
|
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
source,
|
||||||
|
hash ? lib.fakeHash,
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
npmSpec =
|
||||||
|
if lib.hasPrefix "npm:" source then
|
||||||
|
lib.removePrefix "npm:" source
|
||||||
|
else
|
||||||
|
throw "OpenClaw runtime npm plugin source must start with `npm:`: ${source}";
|
||||||
|
safeName = lib.replaceStrings [ "@" "/" ":" ] [ "" "-" "-" ] id;
|
||||||
|
in
|
||||||
|
stdenvNoCC.mkDerivation {
|
||||||
|
pname = "openclaw-runtime-plugin-${safeName}";
|
||||||
|
version = "1";
|
||||||
|
|
||||||
|
nativeBuildInputs = [ nodejs_22 ];
|
||||||
|
|
||||||
|
dontUnpack = true;
|
||||||
|
dontConfigure = true;
|
||||||
|
dontBuild = true;
|
||||||
|
|
||||||
|
outputHashMode = "recursive";
|
||||||
|
outputHashAlgo = "sha256";
|
||||||
|
outputHash = hash;
|
||||||
|
|
||||||
|
env = {
|
||||||
|
OPENCLAW_RUNTIME_PLUGIN_ID = id;
|
||||||
|
OPENCLAW_RUNTIME_PLUGIN_NPM_SPEC = npmSpec;
|
||||||
|
};
|
||||||
|
|
||||||
|
installPhase = "${../scripts/npm-runtime-plugin-install.sh}";
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Nix-packaged OpenClaw runtime plugin ${id} from ${source}";
|
||||||
|
homepage = "https://github.com/openclaw/openclaw";
|
||||||
|
license = licenses.mit;
|
||||||
|
platforms = platforms.darwin ++ platforms.linux;
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,4 +1,8 @@
|
|||||||
{ lib, openclawLib }:
|
{
|
||||||
|
lib,
|
||||||
|
openclawLib,
|
||||||
|
pluginOptionType,
|
||||||
|
}:
|
||||||
|
|
||||||
{ name, config, ... }:
|
{ name, config, ... }:
|
||||||
{
|
{
|
||||||
@ -78,21 +82,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
plugins = lib.mkOption {
|
plugins = lib.mkOption {
|
||||||
type = lib.types.listOf (
|
type = lib.types.listOf pluginOptionType;
|
||||||
lib.types.submodule {
|
|
||||||
options = {
|
|
||||||
source = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "Plugin flake source pointer (e.g., github:owner/repo or path:/...).";
|
|
||||||
};
|
|
||||||
config = lib.mkOption {
|
|
||||||
type = lib.types.attrs;
|
|
||||||
default = { };
|
|
||||||
description = "Plugin-specific configuration (env/files/etc).";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
default = openclawLib.effectivePlugins;
|
default = openclawLib.effectivePlugins;
|
||||||
description = "Plugins enabled for this instance (includes bundled plugin toggles).";
|
description = "Plugins enabled for this instance (includes bundled plugin toggles).";
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,7 +7,35 @@
|
|||||||
|
|
||||||
let
|
let
|
||||||
openclawLib = import ./lib.nix { inherit config lib pkgs; };
|
openclawLib = import ./lib.nix { inherit config lib pkgs; };
|
||||||
instanceModule = import ./options-instance.nix { inherit lib openclawLib; };
|
pluginOptionType = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
source = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Plugin source. Use a plugin flake source (github:/path:) or an OpenClaw npm install source (npm:@scope/package@version).";
|
||||||
|
};
|
||||||
|
config = lib.mkOption {
|
||||||
|
type = lib.types.attrs;
|
||||||
|
default = { };
|
||||||
|
description = "Nix capability plugin configuration (env/files/etc). Runtime OpenClaw plugin config belongs under programs.openclaw.config.plugins.entries.<id>.config.";
|
||||||
|
};
|
||||||
|
id = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = "OpenClaw runtime plugin id. Required for npm: sources so Nix can enable the plugin without build-time introspection.";
|
||||||
|
};
|
||||||
|
enabled = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Default enabled state for an OpenClaw runtime plugin entry.";
|
||||||
|
};
|
||||||
|
hash = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = lib.fakeHash;
|
||||||
|
description = "Recursive output hash for npm: runtime plugin sources. Use the hash Nix reports when this is left as lib.fakeHash.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
instanceModule = import ./options-instance.nix { inherit lib openclawLib pluginOptionType; };
|
||||||
pluginCatalog = import ./plugin-catalog.nix;
|
pluginCatalog = import ./plugin-catalog.nix;
|
||||||
mkSkillOption = lib.types.submodule {
|
mkSkillOption = lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
@ -132,23 +160,9 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
customPlugins = lib.mkOption {
|
customPlugins = lib.mkOption {
|
||||||
type = lib.types.listOf (
|
type = lib.types.listOf pluginOptionType;
|
||||||
lib.types.submodule {
|
|
||||||
options = {
|
|
||||||
source = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "Plugin flake source pointer (e.g., github:owner/repo or path:/...).";
|
|
||||||
};
|
|
||||||
config = lib.mkOption {
|
|
||||||
type = lib.types.attrs;
|
|
||||||
default = { };
|
|
||||||
description = "Plugin-specific configuration (env/files/etc).";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
default = [ ];
|
default = [ ];
|
||||||
description = "Custom/community plugins (merged with bundled plugin toggles).";
|
description = "Custom/community plugins (merged with bundled plugin toggles). Flake sources provide Nix capability plugins; npm: sources provide OpenClaw runtime plugins.";
|
||||||
};
|
};
|
||||||
|
|
||||||
bundledPlugins = lib.mapAttrs (name: plugin: {
|
bundledPlugins = lib.mapAttrs (name: plugin: {
|
||||||
|
|||||||
@ -8,10 +8,75 @@
|
|||||||
let
|
let
|
||||||
resolvePath = openclawLib.resolvePath;
|
resolvePath = openclawLib.resolvePath;
|
||||||
toRelative = openclawLib.toRelative;
|
toRelative = openclawLib.toRelative;
|
||||||
|
mkNpmRuntimePlugin = pkgs.callPackage ../../../lib/npm-runtime-plugin.nix { };
|
||||||
|
|
||||||
resolvePlugin =
|
normalizeOpenClawPlugin =
|
||||||
|
pluginSource: name: entry:
|
||||||
|
let
|
||||||
|
id = entry.id or (throw "openclawPlugin ${name}: plugins entry missing id");
|
||||||
|
path = entry.path or (throw "openclawPlugin ${name}: plugins.${id} missing path");
|
||||||
|
enabled =
|
||||||
|
if entry ? enable && !(entry ? enabled) then
|
||||||
|
throw "openclawPlugin ${name}: plugins.${id}.enable is not supported; use enabled"
|
||||||
|
else if entry ? enabled then
|
||||||
|
if builtins.isBool entry.enabled then
|
||||||
|
entry.enabled
|
||||||
|
else
|
||||||
|
throw "openclawPlugin ${name}: plugins.${id}.enabled must be a boolean"
|
||||||
|
else
|
||||||
|
true;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit id path enabled;
|
||||||
|
source = pluginSource;
|
||||||
|
plugin = name;
|
||||||
|
};
|
||||||
|
|
||||||
|
resolveNpmRuntimePlugin =
|
||||||
plugin:
|
plugin:
|
||||||
let
|
let
|
||||||
|
id = plugin.id or (throw "OpenClaw npm runtime plugin ${plugin.source} requires id");
|
||||||
|
path = mkNpmRuntimePlugin {
|
||||||
|
inherit id;
|
||||||
|
source = plugin.source;
|
||||||
|
hash = plugin.hash or lib.fakeHash;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
if (plugin.config or { }) != { } then
|
||||||
|
throw "OpenClaw npm runtime plugin ${plugin.source} must put runtime config under programs.openclaw.config.plugins.entries.${id}.config, not customPlugins.config"
|
||||||
|
else
|
||||||
|
{
|
||||||
|
source = plugin.source;
|
||||||
|
name = id;
|
||||||
|
skills = [ ];
|
||||||
|
packages = [ ];
|
||||||
|
plugins = [
|
||||||
|
{
|
||||||
|
inherit id path;
|
||||||
|
enabled = plugin.enabled or true;
|
||||||
|
source = plugin.source;
|
||||||
|
plugin = id;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
needs = {
|
||||||
|
stateDirs = [ ];
|
||||||
|
requiredEnv = [ ];
|
||||||
|
};
|
||||||
|
config = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
resolveFlakePlugin =
|
||||||
|
plugin:
|
||||||
|
let
|
||||||
|
_ =
|
||||||
|
if (plugin.id or null) != null then
|
||||||
|
throw "Plugin ${plugin.source}: id is only valid for npm: OpenClaw runtime plugin sources"
|
||||||
|
else if (plugin.hash or lib.fakeHash) != lib.fakeHash then
|
||||||
|
throw "Plugin ${plugin.source}: hash is only valid for npm: OpenClaw runtime plugin sources"
|
||||||
|
else if (plugin.enabled or true) != true then
|
||||||
|
throw "Plugin ${plugin.source}: enabled is only valid for npm: OpenClaw runtime plugin sources"
|
||||||
|
else
|
||||||
|
null;
|
||||||
system = pkgs.stdenv.hostPlatform.system;
|
system = pkgs.stdenv.hostPlatform.system;
|
||||||
flake = builtins.getFlake plugin.source;
|
flake = builtins.getFlake plugin.source;
|
||||||
openclawPluginRaw =
|
openclawPluginRaw =
|
||||||
@ -28,34 +93,13 @@ let
|
|||||||
openclawPlugin;
|
openclawPlugin;
|
||||||
name = resolvedPlugin.name or (throw "openclawPlugin.name missing in ${plugin.source}");
|
name = resolvedPlugin.name or (throw "openclawPlugin.name missing in ${plugin.source}");
|
||||||
needs = resolvedPlugin.needs or { };
|
needs = resolvedPlugin.needs or { };
|
||||||
normalizeOpenClawPlugin =
|
|
||||||
entry:
|
|
||||||
let
|
|
||||||
id = entry.id or (throw "openclawPlugin ${name}: plugins entry missing id");
|
|
||||||
path = entry.path or (throw "openclawPlugin ${name}: plugins.${id} missing path");
|
|
||||||
enabled =
|
|
||||||
if entry ? enable && !(entry ? enabled) then
|
|
||||||
throw "openclawPlugin ${name}: plugins.${id}.enable is not supported; use enabled"
|
|
||||||
else if entry ? enabled then
|
|
||||||
if builtins.isBool entry.enabled then
|
|
||||||
entry.enabled
|
|
||||||
else
|
|
||||||
throw "openclawPlugin ${name}: plugins.${id}.enabled must be a boolean"
|
|
||||||
else
|
|
||||||
true;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit id path enabled;
|
|
||||||
source = plugin.source;
|
|
||||||
plugin = name;
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
{
|
builtins.seq _ {
|
||||||
source = plugin.source;
|
source = plugin.source;
|
||||||
inherit name;
|
inherit name;
|
||||||
skills = resolvedPlugin.skills or [ ];
|
skills = resolvedPlugin.skills or [ ];
|
||||||
packages = resolvedPlugin.packages or [ ];
|
packages = resolvedPlugin.packages or [ ];
|
||||||
plugins = map normalizeOpenClawPlugin (resolvedPlugin.plugins or [ ]);
|
plugins = map (normalizeOpenClawPlugin plugin.source name) (resolvedPlugin.plugins or [ ]);
|
||||||
needs = {
|
needs = {
|
||||||
stateDirs = needs.stateDirs or [ ];
|
stateDirs = needs.stateDirs or [ ];
|
||||||
requiredEnv = needs.requiredEnv or [ ];
|
requiredEnv = needs.requiredEnv or [ ];
|
||||||
@ -63,6 +107,13 @@ let
|
|||||||
config = plugin.config or { };
|
config = plugin.config or { };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
resolvePlugin =
|
||||||
|
plugin:
|
||||||
|
if lib.hasPrefix "npm:" plugin.source then
|
||||||
|
resolveNpmRuntimePlugin plugin
|
||||||
|
else
|
||||||
|
resolveFlakePlugin plugin;
|
||||||
|
|
||||||
resolvedPluginsByInstance = lib.mapAttrs (
|
resolvedPluginsByInstance = lib.mapAttrs (
|
||||||
instName: inst:
|
instName: inst:
|
||||||
let
|
let
|
||||||
|
|||||||
72
nix/scripts/npm-runtime-plugin-install.sh
Executable file
72
nix/scripts/npm-runtime-plugin-install.sh
Executable file
@ -0,0 +1,72 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
spec="${OPENCLAW_RUNTIME_PLUGIN_NPM_SPEC:?OPENCLAW_RUNTIME_PLUGIN_NPM_SPEC is required}"
|
||||||
|
id="${OPENCLAW_RUNTIME_PLUGIN_ID:?OPENCLAW_RUNTIME_PLUGIN_ID is required}"
|
||||||
|
|
||||||
|
package_name="$(
|
||||||
|
node -e '
|
||||||
|
const spec = process.env.OPENCLAW_RUNTIME_PLUGIN_NPM_SPEC || "";
|
||||||
|
const withoutProtocol = spec.startsWith("npm:") ? spec.slice(4) : spec;
|
||||||
|
const at = withoutProtocol.startsWith("@")
|
||||||
|
? withoutProtocol.indexOf("@", 1)
|
||||||
|
: withoutProtocol.indexOf("@");
|
||||||
|
const name = at === -1 ? withoutProtocol : withoutProtocol.slice(0, at);
|
||||||
|
if (!name || name.startsWith("git+") || name.includes("://")) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
process.stdout.write(name);
|
||||||
|
'
|
||||||
|
)" || {
|
||||||
|
echo "Only registry npm package specs are supported for OpenClaw runtime plugins: $spec" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export HOME="$TMPDIR/home"
|
||||||
|
export npm_config_cache="$TMPDIR/npm-cache"
|
||||||
|
export npm_config_ignore_scripts=true
|
||||||
|
export npm_config_audit=false
|
||||||
|
export npm_config_fund=false
|
||||||
|
export npm_config_update_notifier=false
|
||||||
|
|
||||||
|
project="$TMPDIR/openclaw-runtime-plugin"
|
||||||
|
mkdir -p "$HOME" "$npm_config_cache" "$project"
|
||||||
|
cd "$project"
|
||||||
|
|
||||||
|
npm init -y >/dev/null
|
||||||
|
npm install --ignore-scripts --omit=dev --no-audit --no-fund --package-lock=false "$spec"
|
||||||
|
|
||||||
|
package_dir="node_modules/$package_name"
|
||||||
|
if [ ! -d "$package_dir" ]; then
|
||||||
|
echo "npm install did not produce $package_dir for $spec" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$package_dir/openclaw.plugin.json" ] && [ ! -f "$package_dir/package.json" ]; then
|
||||||
|
echo "npm package $spec does not look like an OpenClaw runtime plugin" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$out"
|
||||||
|
cp -R "$package_dir/." "$out/"
|
||||||
|
|
||||||
|
if [ -d node_modules ]; then
|
||||||
|
mkdir -p "$out/node_modules"
|
||||||
|
cp -R node_modules/. "$out/node_modules/"
|
||||||
|
rm -rf "$out/node_modules/$package_name"
|
||||||
|
fi
|
||||||
|
|
||||||
|
find "$out" -name .package-lock.json -type f -delete
|
||||||
|
|
||||||
|
if [ ! -f "$out/openclaw.plugin.json" ]; then
|
||||||
|
node -e '
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(path.join(process.env.out, "package.json"), "utf8"));
|
||||||
|
const entries = pkg.openclaw?.runtimeExtensions || pkg.openclaw?.extensions || [];
|
||||||
|
if (!Array.isArray(entries) || entries.length === 0) process.exit(1);
|
||||||
|
'
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "$spec" > "$out/.nix-openclaw-npm-spec"
|
||||||
|
printf '%s\n' "$id" > "$out/.nix-openclaw-plugin-id"
|
||||||
Loading…
Reference in New Issue
Block a user