claude-code: load plugins without wrapper
Persist managed plugins in Claude Code's personal skills directory so strict-parser subcommands do not receive injected --plugin-dir arguments. Retain version-gated legacy behavior for older and unversioned packages.
This commit is contained in:
parent
79e6082586
commit
1c029fb0dc
12 changed files with 272 additions and 97 deletions
|
|
@ -17,6 +17,17 @@ let
|
|||
|
||||
jsonFormat = pkgs.formats.json { };
|
||||
|
||||
packageVersion = if cfg.package == null then null else lib.getVersion cfg.package;
|
||||
hasPackageVersion = packageVersion != null && packageVersion != "";
|
||||
|
||||
# A null package has no detectable version, so assume the latest Claude Code
|
||||
# and enable version-gated behavior by default. Preserve the legacy wrapper
|
||||
# for non-null packages without version metadata.
|
||||
atLeast =
|
||||
version: cfg.package == null || (hasPackageVersion && lib.versionAtLeast packageVersion version);
|
||||
supportsPluginDir = !hasPackageVersion || atLeast "2.1.76";
|
||||
supportsPersonalPlugins = atLeast "2.1.157";
|
||||
|
||||
upstreamConfigDir = "${config.home.homeDirectory}/.claude";
|
||||
|
||||
isMcpServerEnabled =
|
||||
|
|
@ -222,7 +233,12 @@ in
|
|||
Each entry is either:
|
||||
- A path to the plugin directory
|
||||
- The plugin package, whether a nix package or the output of a fetcher
|
||||
Plugins are enabled via a `--plugin-dir` argument in the wrapper script.
|
||||
With Claude Code 2.1.157 or later, plugins are linked into
|
||||
{option}`programs.claude-code.configDir` and loaded as personal plugins.
|
||||
Versions 2.1.76 through 2.1.156 fall back to a legacy `--plugin-dir`
|
||||
wrapper, as do packages without detectable version metadata.
|
||||
Strict-parser subcommands such as {command}`claude rc` may reject
|
||||
arguments from that compatibility path.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
[
|
||||
|
|
@ -623,8 +639,124 @@ in
|
|||
lastUpdated = "1970-01-01T00:00:00Z";
|
||||
};
|
||||
|
||||
mergedMcpServers =
|
||||
transformedMcpServers
|
||||
// lib.mapAttrs (_: server: removeAttrs (lib.hm.mcp.addType server) [ "enabled" ]) cfg.mcpServers;
|
||||
|
||||
generatedPluginFiles =
|
||||
lib.optional (mergedMcpServers != { }) {
|
||||
name = ".mcp.json";
|
||||
path = jsonFormat.generate "claude-code-mcp.json" { mcpServers = mergedMcpServers; };
|
||||
}
|
||||
++ lib.optional (cfg.lspServers != { }) {
|
||||
name = ".lsp.json";
|
||||
path = jsonFormat.generate "claude-code-lsp.json" cfg.lspServers;
|
||||
};
|
||||
|
||||
generatedPlugin = pkgs.runCommand "claude-code-hm-plugin" { } (
|
||||
''
|
||||
install -Dm644 ${
|
||||
jsonFormat.generate "claude-code-plugin.json" {
|
||||
name = "claude-code-home-manager";
|
||||
}
|
||||
} $out/.claude-plugin/plugin.json
|
||||
''
|
||||
+ lib.concatLines (
|
||||
map (pluginFile: "install -Dm644 ${pluginFile.path} $out/${pluginFile.name}") generatedPluginFiles
|
||||
)
|
||||
);
|
||||
|
||||
mkPluginEntry =
|
||||
plugin:
|
||||
let
|
||||
name = builtins.unsafeDiscardStringContext (
|
||||
lib.strings.sanitizeDerivationName (baseNameOf (toString plugin))
|
||||
);
|
||||
in
|
||||
{
|
||||
inherit name;
|
||||
source = pkgs.symlinkJoin {
|
||||
name = "claude-code-${name}";
|
||||
paths = [ plugin ];
|
||||
postBuild = ''
|
||||
if [[ ! -e $out/.claude-plugin/plugin.json ]]; then
|
||||
install -Dm644 ${
|
||||
jsonFormat.generate "claude-code-${name}.json" {
|
||||
inherit name;
|
||||
}
|
||||
} $out/.claude-plugin/plugin.json
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
pluginEntries =
|
||||
lib.optional (generatedPluginFiles != [ ]) {
|
||||
name = "claude-code-home-manager";
|
||||
source = generatedPlugin;
|
||||
}
|
||||
++ map mkPluginEntry cfg.plugins;
|
||||
|
||||
legacyPluginPaths = lib.optional (generatedPluginFiles != [ ]) generatedPlugin ++ cfg.plugins;
|
||||
|
||||
hasManagedPlugins = legacyPluginPaths != [ ];
|
||||
useLegacyPluginWrapper = hasManagedPlugins && !supportsPersonalPlugins;
|
||||
|
||||
legacyWrapperArgs = lib.flatten (
|
||||
map (plugin: [
|
||||
"--plugin-dir"
|
||||
"${plugin}"
|
||||
]) legacyPluginPaths
|
||||
);
|
||||
|
||||
legacyFinalPackage = pkgs.symlinkJoin {
|
||||
name = "claude-code";
|
||||
paths = [ cfg.package ];
|
||||
postBuild = ''
|
||||
mv $out/bin/claude $out/bin/.claude-wrapped
|
||||
cat > $out/bin/claude <<EOF
|
||||
#! ${pkgs.bash}/bin/bash -e
|
||||
exec -a "\$0" "$out/bin/.claude-wrapped" ${lib.escapeShellArgs legacyWrapperArgs} "\$@"
|
||||
EOF
|
||||
chmod +x $out/bin/claude
|
||||
'';
|
||||
inherit (cfg.package) meta;
|
||||
};
|
||||
|
||||
pluginNames = map (plugin: plugin.name) pluginEntries;
|
||||
|
||||
skillNames =
|
||||
if builtins.isAttrs cfg.skills then
|
||||
lib.attrNames cfg.skills
|
||||
else if lib.hm.strings.isPathLike cfg.skills && lib.pathIsDirectory cfg.skills then
|
||||
lib.attrNames (builtins.readDir cfg.skills)
|
||||
else
|
||||
[ ];
|
||||
|
||||
pluginFileEntries = lib.optionalAttrs supportsPersonalPlugins (
|
||||
lib.listToAttrs (
|
||||
map (
|
||||
plugin:
|
||||
nameValuePair "${cfg.configDir}/skills/${plugin.name}" {
|
||||
inherit (plugin) source;
|
||||
recursive = true;
|
||||
}
|
||||
) pluginEntries
|
||||
)
|
||||
);
|
||||
|
||||
in
|
||||
lib.mkIf cfg.enable {
|
||||
warnings = lib.optional (useLegacyPluginWrapper && supportsPluginDir) ''
|
||||
`programs.claude-code.package` ${
|
||||
if hasPackageVersion then "version ${packageVersion}" else "has no detectable version and"
|
||||
}
|
||||
uses the legacy `--plugin-dir` wrapper. Strict-parser subcommands such
|
||||
as `claude rc` may reject managed MCP, LSP, or plugin arguments. Upgrade
|
||||
Claude Code to version 2.1.157 or later to use persistent personal
|
||||
plugins instead.
|
||||
'';
|
||||
|
||||
assertions =
|
||||
let
|
||||
exclusiveInlineDirNames = [
|
||||
|
|
@ -641,68 +773,28 @@ in
|
|||
in
|
||||
[
|
||||
{
|
||||
assertion =
|
||||
(cfg.mcpServers == { } && cfg.lspServers == { } && !cfg.enableMcpIntegration && cfg.plugins == [ ])
|
||||
|| cfg.package != null;
|
||||
message = "`programs.claude-code.package` cannot be null when `mcpServers`, `lspServers`, `enableMcpIntegration`, or `plugins` is configured";
|
||||
assertion = !hasManagedPlugins || supportsPluginDir;
|
||||
message = "Managed Claude Code MCP, LSP, and plugins require `programs.claude-code.package` version 2.1.76 or later";
|
||||
}
|
||||
{
|
||||
assertion = !lib.hm.strings.isPathLike cfg.skills || lib.pathIsDirectory cfg.skills;
|
||||
message = "`programs.claude-code.skills` must be a directory when set to a path";
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
!supportsPersonalPlugins || lib.length pluginNames == lib.length (lib.unique pluginNames);
|
||||
message = "`programs.claude-code.plugins` entries must resolve to unique personal-plugin directory names";
|
||||
}
|
||||
{
|
||||
assertion = !supportsPersonalPlugins || lib.intersectLists skillNames pluginNames == [ ];
|
||||
message = "`programs.claude-code.skills` and managed plugins must have unique directory names";
|
||||
}
|
||||
]
|
||||
++ map mkExclusiveAssertion exclusiveInlineDirNames;
|
||||
|
||||
programs.claude-code.finalPackage =
|
||||
let
|
||||
mergedMcpServers =
|
||||
transformedMcpServers
|
||||
// lib.mapAttrs (_: server: removeAttrs (lib.hm.mcp.addType server) [ "enabled" ]) cfg.mcpServers;
|
||||
pluginFiles =
|
||||
lib.optional (mergedMcpServers != { }) {
|
||||
name = ".mcp.json";
|
||||
path = jsonFormat.generate "claude-code-mcp.json" { mcpServers = mergedMcpServers; };
|
||||
}
|
||||
++ lib.optional (cfg.lspServers != { }) {
|
||||
name = ".lsp.json";
|
||||
path = jsonFormat.generate "claude-code-lsp.json" cfg.lspServers;
|
||||
};
|
||||
pluginDir = pkgs.runCommand "claude-code-hm-plugin" { } (
|
||||
''
|
||||
install -Dm644 ${
|
||||
jsonFormat.generate "claude-code-plugin.json" {
|
||||
name = "claude-code-home-manager";
|
||||
}
|
||||
} $out/.claude-plugin/plugin.json
|
||||
''
|
||||
+ lib.concatLines (
|
||||
map (pluginFile: "install -Dm644 ${pluginFile.path} $out/${pluginFile.name}") pluginFiles
|
||||
)
|
||||
);
|
||||
allPluginPaths = (if pluginFiles != [ ] then [ pluginDir ] else [ ]) ++ cfg.plugins;
|
||||
wrapperArgs = lib.flatten (
|
||||
map (p: [
|
||||
"--plugin-dir"
|
||||
"${p}"
|
||||
]) allPluginPaths
|
||||
);
|
||||
in
|
||||
if allPluginPaths != [ ] then
|
||||
pkgs.symlinkJoin {
|
||||
name = "claude-code";
|
||||
paths = [ cfg.package ];
|
||||
postBuild = ''
|
||||
mv $out/bin/claude $out/bin/.claude-wrapped
|
||||
cat > $out/bin/claude <<EOF
|
||||
#! ${pkgs.bash}/bin/bash -e
|
||||
exec -a "\$0" "$out/bin/.claude-wrapped" ${lib.escapeShellArgs wrapperArgs} "\$@"
|
||||
EOF
|
||||
chmod +x $out/bin/claude
|
||||
'';
|
||||
inherit (cfg.package) meta;
|
||||
}
|
||||
else
|
||||
cfg.package;
|
||||
programs.claude-code.finalPackage = lib.mkIf (cfg.package != null) (
|
||||
if useLegacyPluginWrapper then legacyFinalPackage else cfg.package
|
||||
);
|
||||
|
||||
home = {
|
||||
packages = lib.mkIf (cfg.package != null) [ cfg.finalPackage ];
|
||||
|
|
@ -757,6 +849,7 @@ in
|
|||
recursive = true;
|
||||
};
|
||||
})
|
||||
pluginFileEntries
|
||||
(mkHookEntries cfg.hooks)
|
||||
(lib.optionalAttrs (builtins.isAttrs cfg.skills) (lib.mapAttrs' mkSkillEntry cfg.skills))
|
||||
(mkMarkdownEntries "output-styles" cfg.outputStyles)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,17 @@
|
|||
{ config, ... }:
|
||||
|
||||
{
|
||||
programs.claude-code = {
|
||||
enable = true;
|
||||
package = null;
|
||||
package = config.lib.test.mkStubPackage {
|
||||
name = "claude-code";
|
||||
version = "2.1.75";
|
||||
buildScript = ''
|
||||
mkdir -p $out/bin
|
||||
touch $out/bin/claude
|
||||
chmod 755 $out/bin/claude
|
||||
'';
|
||||
};
|
||||
|
||||
mcpServers = {
|
||||
filesystem = {
|
||||
|
|
@ -36,7 +46,7 @@
|
|||
};
|
||||
|
||||
test.asserts.assertions.expected = [
|
||||
"`programs.claude-code.package` cannot be null when `mcpServers`, `lspServers`, `enableMcpIntegration`, or `plugins` is configured"
|
||||
"Managed Claude Code MCP, LSP, and plugins require `programs.claude-code.package` version 2.1.76 or later"
|
||||
"Cannot specify both `programs.claude-code.agents` and `programs.claude-code.agentsDir`"
|
||||
"Cannot specify both `programs.claude-code.commands` and `programs.claude-code.commandsDir`"
|
||||
"Cannot specify both `programs.claude-code.hooks` and `programs.claude-code.hooksDir`"
|
||||
|
|
|
|||
|
|
@ -26,5 +26,6 @@
|
|||
claude-code-output-styles = ./output-styles.nix;
|
||||
claude-code-output-styles-not-set = ./output-styles-not-set.nix;
|
||||
claude-code-plugins = ./plugins.nix;
|
||||
claude-code-versionless-package = ./versionless-package.nix;
|
||||
claude-code-marketplacess = ./marketplaces.nix;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
#! @bash-interactive@/bin/bash -e
|
||||
exec -a "$0" "/nix/store/00000000000000000000000000000000-claude-code/bin/.claude-wrapped" --plugin-dir /nix/store/00000000000000000000000000000000-claude-code-hm-plugin "$@"
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
#! @bash-interactive@/bin/bash -e
|
||||
exec -a "$0" "/nix/store/00000000000000000000000000000000-claude-code/bin/.claude-wrapped" --plugin-dir /nix/store/00000000000000000000000000000000-claude-code-hm-plugin "$@"
|
||||
exec -a "$0" "/nix/store/00000000000000000000000000000000-claude-code/bin/.claude-wrapped" --plugin-dir /nix/store/00000000000000000000000000000000-claude-code-hm-plugin --plugin-dir /nix/store/00000000000000000000000000000000-test-plugin "$@"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
programs.claude-code = {
|
||||
package = config.lib.test.mkStubPackage {
|
||||
name = "claude-code";
|
||||
version = "2.1.197";
|
||||
buildScript = ''
|
||||
mkdir -p $out/bin
|
||||
touch $out/bin/claude
|
||||
|
|
@ -32,12 +33,9 @@
|
|||
};
|
||||
|
||||
nmt.script = ''
|
||||
wrapperPath="$TESTED/home-path/bin/claude"
|
||||
normalizedWrapper=$(normalizeStorePaths "$wrapperPath")
|
||||
assertFileContent "$normalizedWrapper" ${./expected-lsp-wrapper}
|
||||
assertPathNotExists "$TESTED/home-path/bin/.claude-wrapped"
|
||||
|
||||
pluginDir=$(grep -o -- '--plugin-dir /nix/store/[^ ]*' "$wrapperPath")
|
||||
pluginDir="''${pluginDir#--plugin-dir }"
|
||||
pluginDir="$TESTED/home-files/.claude/skills/claude-code-home-manager"
|
||||
assertFileContent "$pluginDir/.claude-plugin/plugin.json" ${./expected-plugin-manifest.json}
|
||||
assertFileContent "$pluginDir/.lsp.json" ${./expected-lsp-plugin.json}
|
||||
assertPathNotExists "$pluginDir/.mcp.json"
|
||||
|
|
|
|||
|
|
@ -1,16 +1,7 @@
|
|||
{ config, ... }:
|
||||
|
||||
{
|
||||
programs = {
|
||||
claude-code = {
|
||||
package = config.lib.test.mkStubPackage {
|
||||
name = "claude-code";
|
||||
buildScript = ''
|
||||
mkdir -p $out/bin
|
||||
touch $out/bin/claude
|
||||
chmod 755 $out/bin/claude
|
||||
'';
|
||||
};
|
||||
package = null;
|
||||
enable = true;
|
||||
|
||||
enableMcpIntegration = true;
|
||||
|
|
@ -72,12 +63,9 @@
|
|||
};
|
||||
|
||||
nmt.script = ''
|
||||
wrapperPath="$TESTED/home-path/bin/claude"
|
||||
normalizedWrapper=$(normalizeStorePaths "$wrapperPath")
|
||||
assertFileContent "$normalizedWrapper" ${./expected-mcp-wrapper}
|
||||
assertPathNotExists "$TESTED/home-path/bin/claude"
|
||||
|
||||
pluginDir=$(grep -o -- '--plugin-dir /nix/store/[^ ]*' "$wrapperPath")
|
||||
pluginDir="''${pluginDir#--plugin-dir }"
|
||||
pluginDir="$TESTED/home-files/.claude/skills/claude-code-home-manager"
|
||||
assertFileContent "$pluginDir/.claude-plugin/plugin.json" ${./expected-plugin-manifest.json}
|
||||
assertFileContent "$pluginDir/.mcp.json" ${./expected-mcp-integration-plugin.json}
|
||||
assertPathNotExists "$pluginDir/.lsp.json"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
programs.claude-code = {
|
||||
package = config.lib.test.mkStubPackage {
|
||||
name = "claude-code";
|
||||
version = "2.1.76";
|
||||
buildScript = ''
|
||||
mkdir -p $out/bin
|
||||
touch $out/bin/claude
|
||||
|
|
@ -64,18 +65,32 @@
|
|||
timeout = 5000;
|
||||
};
|
||||
};
|
||||
|
||||
plugins = [ ./test-plugin ];
|
||||
};
|
||||
|
||||
test.asserts.warnings.expected = [
|
||||
''
|
||||
`programs.claude-code.package` version 2.1.76
|
||||
uses the legacy `--plugin-dir` wrapper. Strict-parser subcommands such
|
||||
as `claude rc` may reject managed MCP, LSP, or plugin arguments. Upgrade
|
||||
Claude Code to version 2.1.157 or later to use persistent personal
|
||||
plugins instead.
|
||||
''
|
||||
];
|
||||
|
||||
nmt.script = ''
|
||||
wrapperPath="$TESTED/home-path/bin/claude"
|
||||
normalizedWrapper=$(normalizeStorePaths "$wrapperPath")
|
||||
assertFileContent "$normalizedWrapper" ${./expected-mcp-wrapper}
|
||||
|
||||
test "$(grep -o -- '--plugin-dir ' "$wrapperPath" | wc -l)" -eq 1
|
||||
pluginDir=$(grep -o -- '--plugin-dir /nix/store/[^ ]*' "$wrapperPath")
|
||||
test "$(grep -o -- '--plugin-dir ' "$wrapperPath" | wc -l)" -eq 2
|
||||
pluginDir=$(grep -o -- '--plugin-dir /nix/store/[^ ]*' "$wrapperPath" | head -1)
|
||||
pluginDir="''${pluginDir#--plugin-dir }"
|
||||
assertFileContent "$pluginDir/.claude-plugin/plugin.json" ${./expected-plugin-manifest.json}
|
||||
assertFileContent "$pluginDir/.mcp.json" ${./expected-mcp-plugin.json}
|
||||
assertFileContent "$pluginDir/.lsp.json" ${./expected-lsp-plugin.json}
|
||||
assertPathNotExists "$TESTED/home-files/.claude/skills/claude-code-home-manager"
|
||||
assertPathNotExists "$TESTED/home-files/.claude/skills/test-plugin"
|
||||
'';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
programs.claude-code = {
|
||||
package = config.lib.test.mkStubPackage {
|
||||
name = "claude-code";
|
||||
version = "2.1.157";
|
||||
buildScript = ''
|
||||
mkdir -p $out/bin
|
||||
touch $out/bin/claude
|
||||
|
|
@ -49,12 +50,9 @@
|
|||
};
|
||||
|
||||
nmt.script = ''
|
||||
wrapperPath="$TESTED/home-path/bin/claude"
|
||||
normalizedWrapper=$(normalizeStorePaths "$wrapperPath")
|
||||
assertFileContent "$normalizedWrapper" ${./expected-mcp-wrapper}
|
||||
assertPathNotExists "$TESTED/home-path/bin/.claude-wrapped"
|
||||
|
||||
pluginDir=$(grep -o -- '--plugin-dir /nix/store/[^ ]*' "$wrapperPath")
|
||||
pluginDir="''${pluginDir#--plugin-dir }"
|
||||
pluginDir="$TESTED/home-files/.claude/skills/claude-code-home-manager"
|
||||
assertFileContent "$pluginDir/.claude-plugin/plugin.json" ${./expected-plugin-manifest.json}
|
||||
assertFileContent "$pluginDir/.mcp.json" ${./expected-mcp-plugin.json}
|
||||
assertPathNotExists "$pluginDir/.lsp.json"
|
||||
|
|
|
|||
|
|
@ -1,28 +1,66 @@
|
|||
{ config, ... }:
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
mkPackagePlugin =
|
||||
name:
|
||||
pkgs.runCommand "source" { } ''
|
||||
install -Dm644 ${
|
||||
pkgs.writeText "${name}.json" (
|
||||
builtins.toJSON {
|
||||
inherit name;
|
||||
}
|
||||
)
|
||||
} $out/.claude-plugin/plugin.json
|
||||
'';
|
||||
manifestlessPlugin = pkgs.runCommand "manifestless-plugin" { } ''
|
||||
install -Dm644 ${./test-command.md} $out/commands/test.md
|
||||
'';
|
||||
packagePluginOne = mkPackagePlugin "package-plugin-one";
|
||||
packagePluginTwo = mkPackagePlugin "package-plugin-two";
|
||||
pluginDirName = plugin: baseNameOf (toString plugin);
|
||||
in
|
||||
|
||||
{
|
||||
programs.claude-code = {
|
||||
package = config.lib.test.mkStubPackage {
|
||||
name = "claude-code";
|
||||
version = "2.1.197";
|
||||
buildScript = ''
|
||||
mkdir -p $out/bin
|
||||
touch $out/bin/claude
|
||||
chmod 755 $out/bin/claude
|
||||
cat > $out/bin/claude <<'EOF'
|
||||
#!${pkgs.runtimeShell}
|
||||
test "$#" -eq 1
|
||||
test "$1" = rc
|
||||
EOF
|
||||
chmod +x $out/bin/claude
|
||||
'';
|
||||
};
|
||||
enable = true;
|
||||
|
||||
plugins = [ ./test-plugin ];
|
||||
plugins = [
|
||||
./test-plugin
|
||||
packagePluginOne
|
||||
packagePluginTwo
|
||||
manifestlessPlugin
|
||||
];
|
||||
};
|
||||
|
||||
nmt.script = ''
|
||||
wrapperPath="$TESTED/home-path/bin/claude"
|
||||
normalizedWrapper=$(normalizeStorePaths "$wrapperPath")
|
||||
assertFileContent $normalizedWrapper ${./expected-plugin-wrapper}
|
||||
"$TESTED/home-path/bin/claude" rc
|
||||
assertPathNotExists "$TESTED/home-path/bin/.claude-wrapped"
|
||||
|
||||
test "$(grep -o -- '--plugin-dir ' "$wrapperPath" | wc -l)" -eq 1
|
||||
pluginDir=$(grep -o -- '--plugin-dir /nix/store/[^ ]*' "$wrapperPath")
|
||||
pluginDir="''${pluginDir#--plugin-dir }"
|
||||
pluginDir="$TESTED/home-files/.claude/skills/${pluginDirName ./test-plugin}"
|
||||
assertFileContent "$pluginDir/.claude-plugin/plugin.json" ${./test-plugin/.claude-plugin/plugin.json}
|
||||
assertFileContains \
|
||||
"$TESTED/home-files/.claude/skills/${pluginDirName packagePluginOne}/.claude-plugin/plugin.json" \
|
||||
'"name":"package-plugin-one"'
|
||||
assertFileContains \
|
||||
"$TESTED/home-files/.claude/skills/${pluginDirName packagePluginTwo}/.claude-plugin/plugin.json" \
|
||||
'"name":"package-plugin-two"'
|
||||
assertFileContains \
|
||||
"$TESTED/home-files/.claude/skills/${pluginDirName manifestlessPlugin}/.claude-plugin/plugin.json" \
|
||||
'"name": "${pluginDirName manifestlessPlugin}"'
|
||||
assertFileContent \
|
||||
"$TESTED/home-files/.claude/skills/${pluginDirName manifestlessPlugin}/commands/test.md" \
|
||||
${./test-command.md}
|
||||
'';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
programs.claude-code = {
|
||||
enable = true;
|
||||
skills = ./skills;
|
||||
plugins = [ ./test-plugin ];
|
||||
};
|
||||
|
||||
nmt.script = ''
|
||||
|
|
@ -10,5 +11,8 @@
|
|||
assertFileContent \
|
||||
home-files/.claude/skills/test-skill/SKILL.md \
|
||||
${./skills/test-skill/SKILL.md}
|
||||
assertFileContent \
|
||||
home-files/.claude/skills/${baseNameOf (toString ./test-plugin)}/.claude-plugin/plugin.json \
|
||||
${./test-plugin/.claude-plugin/plugin.json}
|
||||
'';
|
||||
}
|
||||
|
|
|
|||
32
tests/modules/programs/claude-code/versionless-package.nix
Normal file
32
tests/modules/programs/claude-code/versionless-package.nix
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{ config, ... }:
|
||||
|
||||
{
|
||||
programs.claude-code = {
|
||||
enable = true;
|
||||
package = config.lib.test.mkStubPackage {
|
||||
name = "claude-code";
|
||||
buildScript = ''
|
||||
mkdir -p $out/bin
|
||||
touch $out/bin/claude
|
||||
chmod 755 $out/bin/claude
|
||||
'';
|
||||
};
|
||||
plugins = [ ./test-plugin ];
|
||||
};
|
||||
|
||||
test.asserts.warnings.expected = [
|
||||
''
|
||||
`programs.claude-code.package` has no detectable version and
|
||||
uses the legacy `--plugin-dir` wrapper. Strict-parser subcommands such
|
||||
as `claude rc` may reject managed MCP, LSP, or plugin arguments. Upgrade
|
||||
Claude Code to version 2.1.157 or later to use persistent personal
|
||||
plugins instead.
|
||||
''
|
||||
];
|
||||
|
||||
nmt.script = ''
|
||||
wrapperPath="$TESTED/home-path/bin/claude"
|
||||
normalizedWrapper=$(normalizeStorePaths "$wrapperPath")
|
||||
assertFileContent "$normalizedWrapper" ${./expected-plugin-wrapper}
|
||||
'';
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue