mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-09 17:29:12 +00:00
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
302 lines
10 KiB
Nix
302 lines
10 KiB
Nix
{
|
|
description = "Kimi Code CLI";
|
|
|
|
inputs = {
|
|
# Pinned to the 25.11 release channel because nixpkgs-unstable currently
|
|
# ships nodejs_24 = 24.14.1, which trips the >= 24.15.0 floor that the
|
|
# native SEA build enforces (see apps/kimi-code/scripts/native/build.mjs).
|
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
|
|
};
|
|
|
|
outputs =
|
|
{ self, nixpkgs }:
|
|
let
|
|
lib = nixpkgs.lib;
|
|
|
|
systems = [
|
|
"x86_64-linux"
|
|
"aarch64-linux"
|
|
"x86_64-darwin"
|
|
"aarch64-darwin"
|
|
];
|
|
|
|
forAllSystems =
|
|
f:
|
|
lib.genAttrs systems (
|
|
system:
|
|
f (import nixpkgs {
|
|
inherit system;
|
|
})
|
|
);
|
|
|
|
minNodeVersion = "24.15.0";
|
|
requiredNodeMajor = "24";
|
|
|
|
# nodejsFor picks pkgs.nodejs_24 when it satisfies the minimum, otherwise
|
|
# falls back to pkgs.nodejs_latest. The build pipeline (tsdown target
|
|
# `node24`, SEA flags) assumes a 24.x runtime, so we hard-fail if neither
|
|
# candidate is in the 24.x line.
|
|
nodejsFor =
|
|
pkgs:
|
|
let
|
|
candidate =
|
|
if lib.versionAtLeast pkgs.nodejs_24.version minNodeVersion then
|
|
pkgs.nodejs_24
|
|
else
|
|
pkgs.nodejs_latest;
|
|
major = lib.versions.major candidate.version;
|
|
in
|
|
if major == requiredNodeMajor then
|
|
candidate
|
|
else
|
|
throw ''
|
|
Kimi Code requires Node.js ${requiredNodeMajor}.x (>= ${minNodeVersion}),
|
|
but nixpkgs only offers ${candidate.version}.
|
|
Pin a newer nixpkgs revision or update minNodeVersion in flake.nix.
|
|
'';
|
|
|
|
pnpmFor =
|
|
pkgs:
|
|
pkgs.pnpm_10.override {
|
|
nodejs = nodejsFor pkgs;
|
|
};
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Derive workspace members from pnpm-workspace.yaml + each package.json.
|
|
#
|
|
# Source of truth is pnpm-workspace.yaml's `packages:` section. We expand
|
|
# each glob (or literal path) against the repo root, keep only entries
|
|
# that contain a package.json, and read their `name` field. Both the
|
|
# `src` fileset and the `pnpmWorkspaces` filter for pnpmConfigHook are
|
|
# derived from this list, so adding/removing a workspace only requires
|
|
# updating pnpm-workspace.yaml (followed by `nix run .#update-pnpm-deps`).
|
|
# ---------------------------------------------------------------------
|
|
|
|
# Minimal parser for the `packages:` list in pnpm-workspace.yaml. Only
|
|
# handles top-level `- <entry>` items under the `packages:` key; other
|
|
# sections (catalog, overrides) are ignored. Sufficient for the format
|
|
# pnpm produces and we maintain.
|
|
parsePnpmWorkspaceGlobs =
|
|
file:
|
|
let
|
|
lines = lib.splitString "\n" (builtins.readFile file);
|
|
isPackagesHeader = l: builtins.match "^packages:[[:space:]]*$" l != null;
|
|
isTopLevelKey = l: builtins.match "^[^[:space:]#].*:.*$" l != null;
|
|
extractItem =
|
|
l:
|
|
let
|
|
m = builtins.match "^[[:space:]]+-[[:space:]]+['\"]?([^'\"#[:space:]]+)['\"]?[[:space:]]*$" l;
|
|
in
|
|
if m == null then null else builtins.head m;
|
|
step =
|
|
state: line:
|
|
if state.done then
|
|
state
|
|
else if !state.inSection then
|
|
if isPackagesHeader line then state // { inSection = true; } else state
|
|
else
|
|
let
|
|
item = extractItem line;
|
|
in
|
|
if item != null then
|
|
state // { items = state.items ++ [ item ]; }
|
|
else if isTopLevelKey line then
|
|
state // { inSection = false; done = true; }
|
|
else
|
|
state;
|
|
result = lib.foldl' step {
|
|
inSection = false;
|
|
items = [ ];
|
|
done = false;
|
|
} lines;
|
|
in
|
|
result.items;
|
|
|
|
expandWorkspaceGlob =
|
|
root: glob:
|
|
if lib.hasSuffix "/*" glob then
|
|
let
|
|
dir = lib.removeSuffix "/*" glob;
|
|
absDir = root + "/${dir}";
|
|
in
|
|
if builtins.pathExists absDir then
|
|
map (n: "${dir}/${n}") (
|
|
builtins.attrNames (lib.filterAttrs (_: t: t == "directory") (builtins.readDir absDir))
|
|
)
|
|
else
|
|
[ ]
|
|
else if builtins.pathExists (root + "/${glob}") then
|
|
[ glob ]
|
|
else
|
|
[ ];
|
|
|
|
workspaceMembers =
|
|
let
|
|
globs = parsePnpmWorkspaceGlobs ./pnpm-workspace.yaml;
|
|
paths = lib.unique (lib.concatMap (expandWorkspaceGlob ./.) globs);
|
|
in
|
|
builtins.filter (p: builtins.pathExists (./. + "/${p}/package.json")) paths;
|
|
|
|
workspacePaths = map (p: ./. + "/${p}") workspaceMembers;
|
|
|
|
workspaceNames = map (
|
|
p: (builtins.fromJSON (builtins.readFile (./. + "/${p}/package.json"))).name
|
|
) workspaceMembers;
|
|
in
|
|
{
|
|
packages = forAllSystems (
|
|
pkgs:
|
|
let
|
|
nodejs = nodejsFor pkgs;
|
|
pnpm = pnpmFor pkgs;
|
|
appPackageJson = builtins.fromJSON (builtins.readFile ./apps/kimi-code/package.json);
|
|
nativeTarget =
|
|
if pkgs.stdenv.hostPlatform.isLinux && pkgs.stdenv.hostPlatform.isAarch64 then
|
|
"linux-arm64"
|
|
else if pkgs.stdenv.hostPlatform.isLinux then
|
|
"linux-x64"
|
|
else if pkgs.stdenv.hostPlatform.isDarwin && pkgs.stdenv.hostPlatform.isAarch64 then
|
|
"darwin-arm64"
|
|
else if pkgs.stdenv.hostPlatform.isDarwin then
|
|
"darwin-x64"
|
|
else
|
|
throw "Unsupported Kimi Code native target for ${pkgs.stdenv.hostPlatform.system}";
|
|
|
|
kimi-code = pkgs.stdenv.mkDerivation (finalAttrs: {
|
|
pname = "kimi-code";
|
|
version = appPackageJson.version;
|
|
|
|
src = lib.fileset.toSource {
|
|
root = ./.;
|
|
fileset = lib.fileset.unions (
|
|
[
|
|
./build
|
|
./.npmrc
|
|
./.nvmrc
|
|
./package.json
|
|
./pnpm-lock.yaml
|
|
./pnpm-workspace.yaml
|
|
./tsconfig.json
|
|
./vitest.config.ts
|
|
./LICENSE
|
|
]
|
|
++ workspacePaths
|
|
);
|
|
};
|
|
|
|
pnpmWorkspaces = [ "." ] ++ workspaceNames;
|
|
|
|
pnpmDeps = pkgs.fetchPnpmDeps {
|
|
inherit (finalAttrs) pname version src pnpmWorkspaces;
|
|
inherit pnpm;
|
|
fetcherVersion = 3;
|
|
hash = "sha256-HpRlxlXZoVqAzrdMdSWhLcTRM1DvDvytVbzIGBo8QUo=";
|
|
};
|
|
|
|
nativeBuildInputs = [
|
|
nodejs
|
|
pnpm
|
|
(pkgs.pnpmConfigHook.override { inherit pnpm; })
|
|
]
|
|
# The SEA inject step (postject) invalidates the macOS code
|
|
# signature on the copied Node executable; build.mjs then re-applies
|
|
# an ad-hoc signature via `codesign`. The Nix darwin sandbox does
|
|
# not expose /usr/bin/codesign, so we supply nixpkgs' ad-hoc-only
|
|
# replacement instead.
|
|
++ lib.optionals pkgs.stdenv.hostPlatform.isDarwin [
|
|
pkgs.darwin.sigtool
|
|
];
|
|
|
|
# The SEA binary is produced by `postject`-injecting a blob into a
|
|
# plain Node executable. Stripping rewrites section tables and can
|
|
# invalidate the injected blob's offsets, so leave the binary
|
|
# untouched after the build.
|
|
dontStrip = true;
|
|
|
|
buildPhase = ''
|
|
runHook preBuild
|
|
export KIMI_CODE_BUILD_TARGET=${nativeTarget}
|
|
${lib.optionalString pkgs.stdenv.hostPlatform.isDarwin ''
|
|
# pkgs.darwin.sigtool's codesign supports `--sign -` (ad-hoc)
|
|
# but not the inspection mode (`-dv`) that 05-verify.mjs runs
|
|
# afterwards. Disable the verify step for the Nix build; the
|
|
# release CI keeps it via the unmodified script.
|
|
substituteInPlace apps/kimi-code/scripts/native/build.mjs \
|
|
--replace-fail \
|
|
"await runVerifyStep({ requireGatekeeper: false });" \
|
|
"// runVerifyStep skipped in nix sandbox (sigtool lacks -dv)"
|
|
''}
|
|
pnpm --filter=@moonshot-ai/kimi-code run build:native:sea
|
|
runHook postBuild
|
|
'';
|
|
|
|
installPhase = ''
|
|
runHook preInstall
|
|
|
|
install -Dm755 \
|
|
"apps/kimi-code/dist-native/bin/${nativeTarget}/kimi" \
|
|
"$out/bin/kimi"
|
|
|
|
runHook postInstall
|
|
'';
|
|
|
|
meta = {
|
|
description = "Kimi Code CLI";
|
|
homepage = "https://github.com/MoonshotAI/kimi-code";
|
|
license = lib.licenses.mit;
|
|
mainProgram = "kimi";
|
|
platforms = systems;
|
|
};
|
|
});
|
|
|
|
# Expose pnpmDeps as a top-level package so `nix build .#kimi-code-pnpm-deps`
|
|
# (used by the update-pnpm-deps app) is a stable selector that doesn't
|
|
# depend on attribute drilling into a derivation.
|
|
kimi-code-pnpm-deps = kimi-code.pnpmDeps;
|
|
|
|
update-pnpm-deps = pkgs.writeShellApplication {
|
|
name = "update-pnpm-deps";
|
|
runtimeInputs = [
|
|
pkgs.nix
|
|
pkgs.git
|
|
nodejs
|
|
pkgs.gnused
|
|
pkgs.gnugrep
|
|
pkgs.coreutils
|
|
];
|
|
text = builtins.readFile ./build/nix/update-pnpm-deps.sh;
|
|
};
|
|
in
|
|
{
|
|
inherit kimi-code kimi-code-pnpm-deps update-pnpm-deps;
|
|
default = kimi-code;
|
|
}
|
|
);
|
|
|
|
apps = forAllSystems (pkgs: {
|
|
kimi-code = {
|
|
type = "app";
|
|
program = "${self.packages.${pkgs.system}.kimi-code}/bin/kimi";
|
|
};
|
|
update-pnpm-deps = {
|
|
type = "app";
|
|
program = "${self.packages.${pkgs.system}.update-pnpm-deps}/bin/update-pnpm-deps";
|
|
};
|
|
default = self.apps.${pkgs.system}.kimi-code;
|
|
});
|
|
|
|
devShells = forAllSystems (pkgs: {
|
|
default =
|
|
let
|
|
nodejs = nodejsFor pkgs;
|
|
pnpm = pnpmFor pkgs;
|
|
in
|
|
pkgs.mkShell {
|
|
packages = [
|
|
nodejs
|
|
pnpm
|
|
];
|
|
};
|
|
});
|
|
};
|
|
}
|