Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
+352
@@ -0,0 +1,352 @@
|
||||
# nix/lib.nix — Shared helpers for nix stuff
|
||||
#
|
||||
# All npm packages in this repo are workspace members sharing a single
|
||||
# root package-lock.json. mkNpmPassthru provides the shared npmDeps,
|
||||
# npmRoot, and npmConfigHook so individual .nix files don't duplicate them.
|
||||
#
|
||||
# Source filters (pythonSrc, per-package npm srcs) reduce rebuild scope so
|
||||
# that e.g. a .tsx change doesn't trigger a Python venv rebuild, and a .py
|
||||
# change doesn't trigger a TUI/Web/Desktop rebuild. Each derivation gets a
|
||||
# filtered src that only includes files it actually needs, while keeping
|
||||
# the repo-root directory layout intact for buildNpmPackage /
|
||||
# npmConfigHook workspace resolution.
|
||||
#
|
||||
# mkNpmPassthru returns packageJsonPath (e.g. "ui-tui/package.json")
|
||||
# instead of a per-package devShellHook. The root devshell hook
|
||||
# (mkNpmDevShellHook) collects all package.json paths, stamps them,
|
||||
# and if any changed, runs a single `npm i --package-lock-only` from
|
||||
# root to update the lockfile, then `npm ci` if the lockfile changed.
|
||||
{
|
||||
lib,
|
||||
npm-lockfile-fix,
|
||||
importNpmLock,
|
||||
writeShellScriptBin,
|
||||
writeShellScript,
|
||||
coreutils,
|
||||
callPackage,
|
||||
nodejs_26,
|
||||
symlinkJoin,
|
||||
buildNpmPackage,
|
||||
runCommand,
|
||||
}:
|
||||
let
|
||||
repoRoot = ./..;
|
||||
|
||||
npm12 = callPackage ./npm-12-0-2.nix { };
|
||||
node_gyp_11_4_0 = callPackage ./node-gyp-11-4-0.nix { };
|
||||
nodejs_26_npm_12 = symlinkJoin {
|
||||
name = "nodejs-26-npm-12";
|
||||
paths = [
|
||||
npm12
|
||||
nodejs_26
|
||||
];
|
||||
inherit (nodejs_26) meta passthru;
|
||||
};
|
||||
|
||||
nodejs = nodejs_26_npm_12;
|
||||
|
||||
# Patched hook: just a new derivation that copies and patches the script
|
||||
patchedNpmConfigHook = runCommand "npm-config-hook-patched" { } ''
|
||||
mkdir -p $out/nix-support
|
||||
# Copy all support files from the original hook
|
||||
cp -r ${importNpmLock.npmConfigHook}/nix-support/* $out/nix-support/
|
||||
|
||||
# Change the node gyp config var to avoid the warning with npm12
|
||||
# Replace the node-gyp path with the newer one that supports the new config var
|
||||
substituteInPlace $out/nix-support/setup-hook \
|
||||
--replace-fail 'npm_config_nodedir' 'npm_package_config_node_gyp_nodedir' \
|
||||
--replace-fail 'npm_config_node_gyp' 'npm_config_node_gyp=${node_gyp_11_4_0}/bin/node-gyp'
|
||||
'';
|
||||
|
||||
# ── npm workspace discovery ────────────────────────────────────────
|
||||
# Single source of truth: the `workspaces` field of the root
|
||||
# package.json. Everything below (workspace package.json discovery,
|
||||
# the Python source's JS-dir exclusions) is derived from this so the
|
||||
# topology is never duplicated. Add a workspace to package.json and
|
||||
# the nix build picks it up automatically.
|
||||
rootPackageJson = builtins.fromJSON (builtins.readFile (repoRoot + "/package.json"));
|
||||
|
||||
# Expand a workspace glob (e.g. "apps/*") into concrete member dirs
|
||||
# relative to the repo root. Only trailing "*" globs are supported —
|
||||
# that's all npm uses here. Literal patterns (e.g. "ui-tui") pass
|
||||
# through unchanged.
|
||||
expandWorkspace =
|
||||
pattern:
|
||||
let
|
||||
parts = lib.splitString "/" pattern;
|
||||
in
|
||||
if lib.last parts == "*" then
|
||||
let
|
||||
parent = lib.concatStringsSep "/" (lib.init parts);
|
||||
entries = builtins.readDir (repoRoot + "/${parent}");
|
||||
dirs = lib.filterAttrs (_: t: t == "directory") entries;
|
||||
in
|
||||
map (d: "${parent}/${d}") (builtins.attrNames dirs)
|
||||
else
|
||||
[ pattern ];
|
||||
|
||||
# All workspace member directories (relative paths), filtered to those
|
||||
# that actually carry a package.json — a glob like apps/* may match a
|
||||
# dir that isn't really a package.
|
||||
workspaceMemberDirs = builtins.filter (d: builtins.pathExists (repoRoot + "/${d}/package.json")) (
|
||||
lib.concatMap expandWorkspace rootPackageJson.workspaces
|
||||
);
|
||||
|
||||
# Top-level directory of each workspace member, deduplicated. Used to
|
||||
# exclude JS/TS workspace trees from the Python source filter. E.g.
|
||||
# apps/desktop + apps/shared + ui-tui + web → [ "apps" "ui-tui" "web" ].
|
||||
jsWorkspaceTopDirs = lib.unique (
|
||||
map (d: builtins.head (lib.splitString "/" d)) workspaceMemberDirs
|
||||
);
|
||||
|
||||
# ── Source filters for reducing rebuild scope ──────────────────────
|
||||
# Changing a .tsx/.mjs file should NOT trigger a Python venv rebuild,
|
||||
# and changing a .py file should NOT trigger a TUI/Web/Desktop rebuild.
|
||||
|
||||
# Python source: everything except JS/TS/docs/infra directories.
|
||||
pythonSrc = lib.cleanSourceWith {
|
||||
src = repoRoot;
|
||||
name = "hermes-python-source";
|
||||
filter =
|
||||
path: type:
|
||||
let
|
||||
relPath = lib.removePrefix (toString repoRoot + "/") (toString path);
|
||||
components = lib.splitString "/" relPath;
|
||||
topComponent = if components == [ ] then "" else builtins.head components;
|
||||
excludedDirs =
|
||||
# JS/TS workspace directories — derived from the npm workspaces
|
||||
# so a new workspace member is excluded from the Python source
|
||||
# without touching this list.
|
||||
jsWorkspaceTopDirs ++ [
|
||||
# Documentation
|
||||
"docs"
|
||||
"website"
|
||||
# CI/infra
|
||||
"docker"
|
||||
".github"
|
||||
# Content/examples
|
||||
"infographic"
|
||||
"datagen-config-examples"
|
||||
# unused packaging infra
|
||||
"packaging"
|
||||
# Test infrastructure
|
||||
"tests"
|
||||
# Plan/temp files
|
||||
"plans"
|
||||
# Nix build definitions (Python build doesn't need these)
|
||||
"nix"
|
||||
# Skills are shipped via HERMES_BUNDLED_SKILLS /
|
||||
# HERMES_OPTIONAL_SKILLS (see hermes-agent.nix), not via the
|
||||
# wheel's data_files — setup.py's _data_file_tree returns []
|
||||
# for a missing dir, so the wheel builds fine without them.
|
||||
# This keeps SKILL.md edits from rebuilding the Python venv.
|
||||
"skills"
|
||||
"optional-skills"
|
||||
# locales/ and optional-mcps/ are bare data dirs (no
|
||||
# __init__.py) shipped via symlinks + HERMES_BUNDLED_LOCALES
|
||||
# / HERMES_OPTIONAL_MCPS, not via the wheel. Excluding them
|
||||
# keeps catalog edits from rebuilding the Python venv.
|
||||
"locales"
|
||||
"optional-mcps"
|
||||
];
|
||||
excludedFiles = [
|
||||
# JS root manifests
|
||||
"package.json"
|
||||
"package-lock.json"
|
||||
# Docker files
|
||||
"Dockerfile"
|
||||
"docker-compose.yml"
|
||||
"docker-compose.windows.yml"
|
||||
# Nix build definitions — editing the flake shouldn't rebuild
|
||||
# the venv. (Input changes rebuild regardless, via the lock.)
|
||||
"flake.nix"
|
||||
"flake.lock"
|
||||
# Root docs the wheel doesn't consume. README.md and LICENSE
|
||||
# must stay — pyproject.toml references them (readme /
|
||||
# license-files).
|
||||
"AGENTS.md"
|
||||
"CONTRIBUTING.md"
|
||||
"SECURITY.md"
|
||||
"README.zh-CN.md"
|
||||
".gitignore"
|
||||
"setup-hermes.sh"
|
||||
];
|
||||
in
|
||||
if relPath == "" then
|
||||
true
|
||||
else if builtins.elem relPath excludedFiles then
|
||||
false
|
||||
else if builtins.elem topComponent excludedDirs then
|
||||
false
|
||||
else
|
||||
true;
|
||||
};
|
||||
|
||||
# Common npm workspace resolution files needed by all npm builds.
|
||||
# npm ci requires all workspace package.json files to resolve
|
||||
# workspace: protocol dependencies correctly. Discovered from the
|
||||
# root package.json workspaces — root manifests + every member's
|
||||
# package.json.
|
||||
npmWorkspaceFiles = lib.fileset.unions (
|
||||
[
|
||||
(repoRoot + "/package.json")
|
||||
(repoRoot + "/package-lock.json")
|
||||
]
|
||||
++ map (d: repoRoot + "/${d}/package.json") workspaceMemberDirs
|
||||
);
|
||||
|
||||
# npm deps source: just what importNpmLock needs (root manifests +
|
||||
# workspace member package.jsons). Much smaller than the full repo,
|
||||
# so changing source files won't invalidate the npmDeps derivation.
|
||||
npmDepsSrc = lib.fileset.toSource {
|
||||
root = repoRoot;
|
||||
fileset = npmWorkspaceFiles;
|
||||
};
|
||||
|
||||
# npm dependencies for the workspace, shared by all members. importNpmLock
|
||||
# resolves each package from the lockfile's own `integrity` hashes, so the
|
||||
# lockfile is the single source of truth — no separate dependency hash to
|
||||
# keep in sync with it.
|
||||
npmDeps = importNpmLock.importNpmLock {
|
||||
npmRoot = npmDepsSrc;
|
||||
};
|
||||
|
||||
# Build a per-package npm source: workspace resolution files + the
|
||||
# package's own directory tree(s). Source ROOT is always the repo
|
||||
# root, preserving the workspace layout that buildNpmPackage and
|
||||
# npmConfigHook expect. Callers pass the dirs they need (relative to
|
||||
# the repo root), so each package owns its own source scope.
|
||||
testFileFilter = lib.fileset.fileFilter (file: lib.hasInfix ".test." file.name) repoRoot;
|
||||
mkNpmSrc =
|
||||
dirs:
|
||||
lib.fileset.toSource {
|
||||
root = repoRoot;
|
||||
fileset = lib.fileset.difference (lib.fileset.union npmWorkspaceFiles (
|
||||
lib.fileset.unions (map (d: repoRoot + "/${d}") dirs)
|
||||
)) testFileFilter;
|
||||
};
|
||||
|
||||
# Returns a buildNpmPackage-compatible function.
|
||||
|
||||
# `dirs` is the single source of truth for what the package contains:
|
||||
# its first entry is the package's own folder (→ packageJsonPath), and
|
||||
# all entries scope the filtered src. Packages that import source from
|
||||
# another workspace member (file: deps) must list that member's dir too,
|
||||
# e.g. apps/desktop depends on apps/shared.
|
||||
#
|
||||
# Usage:
|
||||
# hermesNpmLib.buildNpmPackage {
|
||||
# dirs = [ "apps/desktop" "apps/shared" ];
|
||||
# buildPhase = '' ... '';
|
||||
# installPhase = '' ... '';
|
||||
# }
|
||||
customBuildNpmPackage =
|
||||
{ dirs, ... }@attrs:
|
||||
let
|
||||
# The package's own folder is the first dir; it carries the
|
||||
# package.json that buildNpmPackage reads.
|
||||
folder = builtins.head dirs;
|
||||
|
||||
# Read package.json from the repo (the filtered src is a store path, but we can read the original)
|
||||
packageJson = builtins.fromJSON (builtins.readFile (repoRoot + "/${folder}/package.json"));
|
||||
defaultPname = packageJson.name or "unknown";
|
||||
defaultVersion = packageJson.version or "0.0.0";
|
||||
|
||||
common = {
|
||||
inherit nodejs npmDeps;
|
||||
# No sourceRoot — the workspace root (with the single package-lock.json)
|
||||
# is auto-detected as sourceRoot by nix. npmRoot stays at "."
|
||||
# so npmConfigHook finds the lockfile there.
|
||||
src = mkNpmSrc dirs;
|
||||
npmConfigHook = patchedNpmConfigHook;
|
||||
npmRoot = ".";
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD = 1;
|
||||
passthru = {
|
||||
packageJsonPath = "${folder}/package.json";
|
||||
};
|
||||
};
|
||||
|
||||
# Remove `dirs` from the passed attrs (buildNpmPackage doesn't need it)
|
||||
attrsWithoutDirs = removeAttrs attrs [ "dirs" ];
|
||||
|
||||
finalAttrs =
|
||||
common
|
||||
// attrsWithoutDirs
|
||||
// {
|
||||
pname = attrs.pname or defaultPname;
|
||||
version = attrs.version or defaultVersion;
|
||||
};
|
||||
in
|
||||
buildNpmPackage finalAttrs;
|
||||
in
|
||||
{
|
||||
inherit pythonSrc nodejs;
|
||||
node-gyp = node_gyp_11_4_0;
|
||||
|
||||
# Regenerate the shared root lockfile from scratch and verify all npm
|
||||
# packages still build. Exposed as a runnable package — `nix run
|
||||
# .#update-npm-lockfile` — so it's actually usable, unlike a bin buried
|
||||
# in a build sandbox's PATH. All workspace packages share one lockfile,
|
||||
# so there's a single script (not one per package).
|
||||
updateNpmLockfile = writeShellScriptBin "update-npm-lockfile" ''
|
||||
set -euo pipefail
|
||||
# DEBUG=1 nix run .#update-npm-lockfile — trace every command
|
||||
[ -n "''${DEBUG:-}" ] && set -x
|
||||
|
||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
rm -rf node_modules/
|
||||
${lib.getExe' nodejs "npm"} cache clean --force
|
||||
CI=true ${lib.getExe' nodejs "npm"} install --workspaces
|
||||
${lib.getExe npm-lockfile-fix} ./package-lock.json
|
||||
|
||||
# importNpmLock reads hashes from the lockfile itself — rebuild every
|
||||
# npm package to verify the new lockfile resolves offline.
|
||||
nix build .#tui .#web .#desktop
|
||||
echo "Lockfile updated and all npm packages built."
|
||||
'';
|
||||
|
||||
buildNpmPackage = customBuildNpmPackage;
|
||||
|
||||
# Single devshell hook for all npm workspace packages.
|
||||
#
|
||||
# Takes a list of package.json relative paths (from mkNpmPassthru .passthru.packageJsonPath),
|
||||
# stamps all of them, and if any changed:
|
||||
# 1. Runs `npm i --package-lock-only` from root to update the lockfile
|
||||
# 2. If the lockfile changed, runs `npm ci`
|
||||
mkNpmDevShellHook =
|
||||
packageJsonPaths:
|
||||
writeShellScript "npm-dev-hook" ''
|
||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
|
||||
# Stamp all workspace package.jsons into one file.
|
||||
STAMP_DIR=".nix-stamps"
|
||||
STAMP="$STAMP_DIR/npm-package-jsons"
|
||||
STAMP_VALUE=$(
|
||||
${coreutils}/bin/sha256sum ${
|
||||
lib.concatMapStringsSep " " (p: "\"$REPO_ROOT/${p}\"") packageJsonPaths
|
||||
} 2>/dev/null | ${coreutils}/bin/sort | ${coreutils}/bin/sha256sum | awk '{print $1}'
|
||||
)
|
||||
|
||||
PKG_CHANGED=false
|
||||
if [ ! -f "$STAMP" ] || [ "$(cat "$STAMP")" != "$STAMP_VALUE" ]; then
|
||||
PKG_CHANGED=true
|
||||
echo "npm: package.json changed, updating lockfile..."
|
||||
( cd "$REPO_ROOT" && ${lib.getExe' nodejs "npm"} i --package-lock-only --silent --no-fund --no-audit 2>/dev/null )
|
||||
mkdir -p "$STAMP_DIR"
|
||||
echo "$STAMP_VALUE" > "$STAMP"
|
||||
fi
|
||||
|
||||
# Check if lockfile changed (either from the npm i above or from an
|
||||
# external edit). Runs npm ci if so.
|
||||
LOCK_STAMP="$STAMP_DIR/root-lockfile"
|
||||
LOCK_STAMP_VALUE=$(sha256sum "$REPO_ROOT/package-lock.json" 2>/dev/null | awk '{print $1}')
|
||||
if [ ! -f "$LOCK_STAMP" ] || [ "$(cat "$LOCK_STAMP")" != "$LOCK_STAMP_VALUE" ]; then
|
||||
echo "npm: package-lock.json changed, running npm ci..."
|
||||
( cd "$REPO_ROOT" && CI=true ${lib.getExe' nodejs "npm"} ci --silent --no-fund --no-audit 2>/dev/null )
|
||||
mkdir -p "$STAMP_DIR"
|
||||
echo "$LOCK_STAMP_VALUE" > "$LOCK_STAMP"
|
||||
fi
|
||||
'';
|
||||
}
|
||||
Reference in New Issue
Block a user