nixos: vendor local package definitions
This commit is contained in:
@@ -225,31 +225,7 @@
|
|||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
# Nixpkgs PR patches - just specify PR number and hash
|
# Nixpkgs PR patches - just specify PR number and hash
|
||||||
nixpkgsPRPatches = [
|
nixpkgsPRPatches = [ ];
|
||||||
# playwright-cli
|
|
||||||
{
|
|
||||||
pr = 490230;
|
|
||||||
hash = "sha256-vQM2mmIgpxFo3ctjk2tXkfhocXnTuHSEy8iYkTexScc=";
|
|
||||||
}
|
|
||||||
# t3code base PR
|
|
||||||
{
|
|
||||||
pr = 497465;
|
|
||||||
hash = "sha256-pFofbc6noAqAq6vF+uxvdAk8cV5lftAREWPJGXue/cg=";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
pr = 492656;
|
|
||||||
hash = "sha256-0TGZ12iIfSYs6cs5kgWDAyiThJdlLMhqRGUscVQv5hU=";
|
|
||||||
}
|
|
||||||
# claude-code
|
|
||||||
# {
|
|
||||||
# pr = 464698;
|
|
||||||
# hash = "sha256-Pe9G6b/rI0874mM7FIOSEKiaubk95NcFhTQ7paAeLTU=";
|
|
||||||
# }
|
|
||||||
# {
|
|
||||||
# pr = 464816;
|
|
||||||
# hash = "sha256-bKEoRy4dzP5TyRBjYskwEzr7tj8/ez/Y1XHiQgu5q5I=";
|
|
||||||
# }
|
|
||||||
];
|
|
||||||
|
|
||||||
# Custom patches that don't fit the PR template
|
# Custom patches that don't fit the PR template
|
||||||
nixpkgsCustomPatches = [ ];
|
nixpkgsCustomPatches = [ ];
|
||||||
@@ -361,10 +337,6 @@
|
|||||||
name = "nixpkgs-patched";
|
name = "nixpkgs-patched";
|
||||||
src = nixpkgs;
|
src = nixpkgs;
|
||||||
patches = map bootstrapPkgs.fetchpatch allNixpkgsPatches;
|
patches = map bootstrapPkgs.fetchpatch allNixpkgsPatches;
|
||||||
prePatch = ''
|
|
||||||
mkdir -p pkgs/by-name/an/antigravity
|
|
||||||
mkdir -p pkgs/by-name/pl/playwright-cli
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
# Get eval-config from patched source
|
# Get eval-config from patched source
|
||||||
evalConfig = import "${patchedSource}/nixos/lib/eval-config.nix";
|
evalConfig = import "${patchedSource}/nixos/lib/eval-config.nix";
|
||||||
|
|||||||
@@ -254,6 +254,10 @@ in
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
happy-coder = final.callPackage ./packages/happy-coder { };
|
||||||
|
playwright-cli = final.callPackage ./packages/playwright-cli { };
|
||||||
|
t3code = final.callPackage ./packages/t3code { };
|
||||||
|
|
||||||
# Custom Waybar fork for workspace taskbar support + external SNI watcher option.
|
# Custom Waybar fork for workspace taskbar support + external SNI watcher option.
|
||||||
waybar = prev.waybar.overrideAttrs (old: {
|
waybar = prev.waybar.overrideAttrs (old: {
|
||||||
src = prev.fetchFromGitHub {
|
src = prev.fetchFromGitHub {
|
||||||
|
|||||||
119
nixos/packages/happy-coder/default.nix
Normal file
119
nixos/packages/happy-coder/default.nix
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
stdenv,
|
||||||
|
fetchFromGitHub,
|
||||||
|
fetchYarnDeps,
|
||||||
|
yarnConfigHook,
|
||||||
|
nodejs,
|
||||||
|
makeWrapper,
|
||||||
|
}:
|
||||||
|
|
||||||
|
stdenv.mkDerivation (
|
||||||
|
finalAttrs:
|
||||||
|
let
|
||||||
|
toolArchiveSuffix =
|
||||||
|
if stdenv.hostPlatform.isLinux then
|
||||||
|
if stdenv.hostPlatform.isAarch64 then
|
||||||
|
"arm64-linux"
|
||||||
|
else if stdenv.hostPlatform.isx86_64 then
|
||||||
|
"x64-linux"
|
||||||
|
else
|
||||||
|
throw "Unsupported Linux architecture for happy-coder: ${stdenv.hostPlatform.system}"
|
||||||
|
else if stdenv.hostPlatform.isDarwin then
|
||||||
|
if stdenv.hostPlatform.isAarch64 then
|
||||||
|
"arm64-darwin"
|
||||||
|
else if stdenv.hostPlatform.isx86_64 then
|
||||||
|
"x64-darwin"
|
||||||
|
else
|
||||||
|
throw "Unsupported Darwin architecture for happy-coder: ${stdenv.hostPlatform.system}"
|
||||||
|
else
|
||||||
|
throw "Unsupported platform for happy-coder: ${stdenv.hostPlatform.system}";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
pname = "happy-coder";
|
||||||
|
version = "0.11.2-unstable-2026-03-26";
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "slopus";
|
||||||
|
repo = "happy";
|
||||||
|
rev = "94a6bdc7b41e96b878a5ca0f8a2becdfe5a7f219";
|
||||||
|
hash = "sha256-kcZq8raSM111wb58Uk3cyhQ5MrwtwV8zUQx+2f6kPXA=";
|
||||||
|
};
|
||||||
|
|
||||||
|
yarnOfflineCache = fetchYarnDeps {
|
||||||
|
yarnLock = finalAttrs.src + "/yarn.lock";
|
||||||
|
hash = "sha256-VjxmoOVKdOtyRAx0zVgdmLWiXzeqHVbgAXc7D3GFbc8=";
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
nodejs
|
||||||
|
yarnConfigHook
|
||||||
|
makeWrapper
|
||||||
|
];
|
||||||
|
|
||||||
|
# Fix a type mismatch in upstream TS sources.
|
||||||
|
postPatch = ''
|
||||||
|
substituteInPlace packages/happy-cli/src/agent/acp/runAcp.ts \
|
||||||
|
--replace-fail 'formatOptionalDetail(mode.description,' \
|
||||||
|
'formatOptionalDetail(mode.description ?? undefined,'
|
||||||
|
'';
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
|
||||||
|
yarn --offline workspace @slopus/happy-wire build
|
||||||
|
yarn --offline workspace happy build
|
||||||
|
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
local packageOut="$out/lib/node_modules/happy-coder"
|
||||||
|
mkdir -p "$packageOut"
|
||||||
|
mkdir -p "$out/bin"
|
||||||
|
|
||||||
|
cp -r packages/happy-cli/dist "$packageOut/dist"
|
||||||
|
cp -r packages/happy-cli/bin "$packageOut/bin"
|
||||||
|
cp -r packages/happy-cli/scripts "$packageOut/scripts"
|
||||||
|
cp packages/happy-cli/package.json "$packageOut/package.json"
|
||||||
|
|
||||||
|
mkdir -p "$packageOut/tools/archives" "$packageOut/tools/licenses"
|
||||||
|
cp -r packages/happy-cli/tools/licenses/. "$packageOut/tools/licenses/"
|
||||||
|
cp packages/happy-cli/tools/archives/difftastic-${toolArchiveSuffix}.tar.gz \
|
||||||
|
"$packageOut/tools/archives/"
|
||||||
|
cp packages/happy-cli/tools/archives/ripgrep-${toolArchiveSuffix}.tar.gz \
|
||||||
|
"$packageOut/tools/archives/"
|
||||||
|
|
||||||
|
find node_modules -mindepth 1 -maxdepth 2 -type l -delete
|
||||||
|
cp -r node_modules "$packageOut/node_modules"
|
||||||
|
|
||||||
|
if [ -d packages/happy-cli/node_modules ]; then
|
||||||
|
find packages/happy-cli/node_modules -mindepth 1 -maxdepth 2 -type l -delete
|
||||||
|
cp -rn packages/happy-cli/node_modules/. "$packageOut/node_modules/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$packageOut/node_modules/@slopus/happy-wire"
|
||||||
|
cp -r packages/happy-wire/dist "$packageOut/node_modules/@slopus/happy-wire/dist"
|
||||||
|
cp packages/happy-wire/package.json "$packageOut/node_modules/@slopus/happy-wire/package.json"
|
||||||
|
|
||||||
|
find "$packageOut/node_modules" -xtype l -delete
|
||||||
|
|
||||||
|
for bin in happy happy-mcp; do
|
||||||
|
makeWrapper ${nodejs}/bin/node "$out/bin/$bin" \
|
||||||
|
--add-flags "$packageOut/bin/$bin.mjs"
|
||||||
|
done
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Mobile and web client wrapper for Claude Code and Codex with end-to-end encryption";
|
||||||
|
homepage = "https://github.com/slopus/happy";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
maintainers = with lib.maintainers; [ onsails ];
|
||||||
|
mainProgram = "happy";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
52
nixos/packages/playwright-cli/default.nix
Normal file
52
nixos/packages/playwright-cli/default.nix
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
buildNpmPackage,
|
||||||
|
fetchFromGitHub,
|
||||||
|
makeBinaryWrapper,
|
||||||
|
playwright-driver,
|
||||||
|
versionCheckHook,
|
||||||
|
writeShellScript,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildNpmPackage (finalAttrs: {
|
||||||
|
pname = "playwright-cli";
|
||||||
|
version = "0.1.1";
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "microsoft";
|
||||||
|
repo = "playwright-cli";
|
||||||
|
tag = "v${finalAttrs.version}";
|
||||||
|
hash = "sha256-Ao3phIPinliFDK04u/V3ouuOfwMDVf/qBUpQPESziFQ=";
|
||||||
|
};
|
||||||
|
|
||||||
|
npmDepsHash = "sha256-4x3ozVrST6LtLoHl9KtmaOKrkYwCK84fwEREaoNaESc=";
|
||||||
|
|
||||||
|
dontNpmBuild = true;
|
||||||
|
|
||||||
|
# playwright-cli imports playwright/lib/cli/client/program, which current
|
||||||
|
# nixpkgs playwright-test does not export, so keep the vendored Playwright
|
||||||
|
# until nixpkgs Playwright is updated to a compatible version.
|
||||||
|
nativeBuildInputs = [ makeBinaryWrapper ];
|
||||||
|
|
||||||
|
postFixup = ''
|
||||||
|
wrapProgram $out/bin/playwright-cli \
|
||||||
|
--set-default PLAYWRIGHT_BROWSERS_PATH ${playwright-driver.browsers}
|
||||||
|
'';
|
||||||
|
|
||||||
|
doInstallCheck = true;
|
||||||
|
nativeInstallCheckInputs = [ versionCheckHook ];
|
||||||
|
versionCheckProgram = writeShellScript "version-check" ''
|
||||||
|
"$1" --version >/dev/null
|
||||||
|
echo "${finalAttrs.version}"
|
||||||
|
'';
|
||||||
|
versionCheckProgramArg = "${placeholder "out"}/bin/playwright-cli";
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Playwright CLI for browser automation";
|
||||||
|
homepage = "https://github.com/microsoft/playwright-cli";
|
||||||
|
changelog = "https://github.com/microsoft/playwright-cli/releases/tag/v${finalAttrs.version}";
|
||||||
|
license = lib.licenses.asl20;
|
||||||
|
maintainers = with lib.maintainers; [ imalison ];
|
||||||
|
mainProgram = "playwright-cli";
|
||||||
|
};
|
||||||
|
})
|
||||||
105
nixos/packages/t3code/canonicalize-node-modules.ts
Normal file
105
nixos/packages/t3code/canonicalize-node-modules.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { lstat, mkdir, readdir, rm, symlink } from "fs/promises";
|
||||||
|
import { join, relative } from "path";
|
||||||
|
|
||||||
|
type Entry = {
|
||||||
|
dir: string;
|
||||||
|
version: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function isDirectory(path: string) {
|
||||||
|
try {
|
||||||
|
const info = await lstat(path);
|
||||||
|
return info.isDirectory();
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidSemver = (version: string) => Bun.semver.satisfies(version, "x.x.x");
|
||||||
|
|
||||||
|
const bunRoot = join(process.cwd(), "node_modules/.bun");
|
||||||
|
const linkRoot = join(bunRoot, "node_modules");
|
||||||
|
const directories = (await readdir(bunRoot)).sort();
|
||||||
|
|
||||||
|
const versions = new Map<string, Entry[]>();
|
||||||
|
|
||||||
|
for (const entry of directories) {
|
||||||
|
const full = join(bunRoot, entry);
|
||||||
|
if (!(await isDirectory(full))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = parseEntry(entry);
|
||||||
|
if (!parsed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = versions.get(parsed.name) ?? [];
|
||||||
|
list.push({ dir: full, version: parsed.version });
|
||||||
|
versions.set(parsed.name, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
const selections = new Map<string, Entry>();
|
||||||
|
|
||||||
|
for (const [slug, list] of versions) {
|
||||||
|
list.sort((a, b) => {
|
||||||
|
const aValid = isValidSemver(a.version);
|
||||||
|
const bValid = isValidSemver(b.version);
|
||||||
|
|
||||||
|
if (aValid && bValid) {
|
||||||
|
return -Bun.semver.order(a.version, b.version);
|
||||||
|
}
|
||||||
|
if (aValid) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (bValid) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return b.version.localeCompare(a.version);
|
||||||
|
});
|
||||||
|
|
||||||
|
const first = list[0];
|
||||||
|
if (first) {
|
||||||
|
selections.set(slug, first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await rm(linkRoot, { recursive: true, force: true });
|
||||||
|
await mkdir(linkRoot, { recursive: true });
|
||||||
|
|
||||||
|
for (const [slug, entry] of Array.from(selections.entries()).sort((a, b) => a[0].localeCompare(b[0]))) {
|
||||||
|
const parts = slug.split("/");
|
||||||
|
const leaf = parts.pop();
|
||||||
|
if (!leaf) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = join(linkRoot, ...parts);
|
||||||
|
await mkdir(parent, { recursive: true });
|
||||||
|
|
||||||
|
const linkPath = join(parent, leaf);
|
||||||
|
const desired = join(entry.dir, "node_modules", slug);
|
||||||
|
if (!(await isDirectory(desired))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const relativeTarget = relative(parent, desired);
|
||||||
|
await rm(linkPath, { recursive: true, force: true });
|
||||||
|
await symlink(relativeTarget.length == 0 ? "." : relativeTarget, linkPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseEntry(label: string) {
|
||||||
|
const marker = label.startsWith("@") ? label.indexOf("@", 1) : label.indexOf("@");
|
||||||
|
if (marker <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = label.slice(0, marker).replace(/\+/g, "/");
|
||||||
|
const version = label.slice(marker + 1);
|
||||||
|
|
||||||
|
if (!name || !version) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { name, version };
|
||||||
|
}
|
||||||
250
nixos/packages/t3code/default.nix
Normal file
250
nixos/packages/t3code/default.nix
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
{
|
||||||
|
buildPackages,
|
||||||
|
lib,
|
||||||
|
stdenv,
|
||||||
|
stdenvNoCC,
|
||||||
|
fetchFromGitHub,
|
||||||
|
nix-update-script,
|
||||||
|
makeDesktopItem,
|
||||||
|
electron_40,
|
||||||
|
nodejs,
|
||||||
|
}:
|
||||||
|
|
||||||
|
stdenv.mkDerivation (
|
||||||
|
finalAttrs:
|
||||||
|
let
|
||||||
|
electron = electron_40;
|
||||||
|
nodeModules = stdenvNoCC.mkDerivation {
|
||||||
|
pname = "${finalAttrs.pname}-node_modules";
|
||||||
|
inherit (finalAttrs) src version strictDeps;
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
buildPackages.bun
|
||||||
|
buildPackages.nodejs
|
||||||
|
buildPackages.writableTmpDirAsHomeHook
|
||||||
|
];
|
||||||
|
|
||||||
|
dontConfigure = true;
|
||||||
|
dontFixup = true;
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
replacePackageVersion() {
|
||||||
|
local packageJson="$1"
|
||||||
|
local currentVersion="$(sed -n 's/.*"version": "\([^"]*\)".*/\1/p' "$packageJson" | head -n1)"
|
||||||
|
local currentVersionPattern
|
||||||
|
|
||||||
|
printf -v currentVersionPattern '"version": "%s"' "$currentVersion"
|
||||||
|
|
||||||
|
substituteInPlace "$packageJson" \
|
||||||
|
--replace-fail "$currentVersionPattern" '"version": "${finalAttrs.version}"'
|
||||||
|
}
|
||||||
|
|
||||||
|
for packageJson in \
|
||||||
|
apps/{desktop,server,web}/package.json \
|
||||||
|
packages/{contracts,shared}/package.json
|
||||||
|
do
|
||||||
|
replacePackageVersion "$packageJson"
|
||||||
|
done
|
||||||
|
|
||||||
|
for packageJson in packages/{contracts,shared}/package.json; do
|
||||||
|
substituteInPlace "$packageJson" \
|
||||||
|
--replace-fail '"prepare": "effect-language-service patch",' '"prepare": "true",'
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
|
||||||
|
bun install \
|
||||||
|
--cpu="*" \
|
||||||
|
--ignore-scripts \
|
||||||
|
--no-progress \
|
||||||
|
--frozen-lockfile \
|
||||||
|
--os="*"
|
||||||
|
|
||||||
|
bun --bun ${./canonicalize-node-modules.ts}
|
||||||
|
bun --bun ${./normalize-bun-binaries.ts}
|
||||||
|
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
mkdir -p $out
|
||||||
|
cp -r node_modules $out
|
||||||
|
find apps packages -type d -name node_modules -exec cp -r --parents {} $out \;
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
|
outputHash = "sha256-yrzdhw+NPYZku10piHoxMy+TUJ8MYySZorMOMOztJY4=";
|
||||||
|
outputHashMode = "recursive";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
pname = "t3code";
|
||||||
|
version = "0.0.15";
|
||||||
|
strictDeps = true;
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "pingdotgg";
|
||||||
|
repo = "t3code";
|
||||||
|
tag = "v${finalAttrs.version}";
|
||||||
|
hash = "sha256-HOPiA8X/FzswKGmOuYKog3YIn5iq5rJ/7kDoGhN11x0=";
|
||||||
|
};
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
replacePackageVersion() {
|
||||||
|
local packageJson="$1"
|
||||||
|
local currentVersion="$(sed -n 's/.*"version": "\([^"]*\)".*/\1/p' "$packageJson" | head -n1)"
|
||||||
|
local currentVersionPattern
|
||||||
|
|
||||||
|
printf -v currentVersionPattern '"version": "%s"' "$currentVersion"
|
||||||
|
|
||||||
|
substituteInPlace "$packageJson" \
|
||||||
|
--replace-fail "$currentVersionPattern" '"version": "${finalAttrs.version}"'
|
||||||
|
}
|
||||||
|
|
||||||
|
for packageJson in \
|
||||||
|
apps/{desktop,server,web}/package.json \
|
||||||
|
packages/contracts/package.json
|
||||||
|
do
|
||||||
|
replacePackageVersion "$packageJson"
|
||||||
|
done
|
||||||
|
|
||||||
|
printf -v resourcePathSearch '%s\n%s' \
|
||||||
|
'Path.join(__dirname, "../prod-resources", fileName),' \
|
||||||
|
' Path.join(process.resourcesPath, "resources", fileName),'
|
||||||
|
printf -v resourcePathReplacement '%s\n%s\n%s' \
|
||||||
|
'Path.join(__dirname, "../prod-resources", fileName),' \
|
||||||
|
' Path.join(ROOT_DIR, "apps", "desktop", "prod-resources", fileName),' \
|
||||||
|
' Path.join(process.resourcesPath, "resources", fileName),'
|
||||||
|
|
||||||
|
substituteInPlace apps/desktop/src/main.ts \
|
||||||
|
--replace-fail "$resourcePathSearch" "$resourcePathReplacement"
|
||||||
|
|
||||||
|
substituteInPlace apps/web/vite.config.ts \
|
||||||
|
--replace-fail ' server: {' $' server: {\n host: "127.0.0.1",' \
|
||||||
|
--replace-fail 'host: "localhost"' 'host: "127.0.0.1"'
|
||||||
|
'';
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
buildPackages.bun
|
||||||
|
buildPackages.copyDesktopItems
|
||||||
|
buildPackages.installShellFiles
|
||||||
|
buildPackages.makeBinaryWrapper
|
||||||
|
buildPackages.node-gyp
|
||||||
|
buildPackages.nodejs
|
||||||
|
buildPackages.python3
|
||||||
|
buildPackages.writableTmpDirAsHomeHook
|
||||||
|
] ++ lib.optionals stdenv.buildPlatform.isDarwin [
|
||||||
|
buildPackages.cctools.libtool
|
||||||
|
buildPackages.xcbuild
|
||||||
|
];
|
||||||
|
|
||||||
|
nativeInstallCheckInputs = [ buildPackages.versionCheckHook ];
|
||||||
|
doInstallCheck = stdenv.buildPlatform.canExecute stdenv.hostPlatform;
|
||||||
|
|
||||||
|
configurePhase = ''
|
||||||
|
runHook preConfigure
|
||||||
|
|
||||||
|
cp -r ${nodeModules}/. .
|
||||||
|
|
||||||
|
chmod -R u+rwX node_modules
|
||||||
|
patchShebangs node_modules
|
||||||
|
|
||||||
|
export npm_config_nodedir=${nodejs}
|
||||||
|
cd node_modules/.bun/node-pty@*/node_modules/node-pty
|
||||||
|
node-gyp rebuild
|
||||||
|
node scripts/post-install.js
|
||||||
|
cd -
|
||||||
|
|
||||||
|
runHook postConfigure
|
||||||
|
'';
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
|
||||||
|
for app in web server desktop; do
|
||||||
|
bun run --cwd apps/"$app" build
|
||||||
|
done
|
||||||
|
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
mkdir -p "$out"/libexec/t3code/apps/desktop "$out"/libexec/t3code/apps/server
|
||||||
|
cp -r --no-preserve=mode node_modules "$out"/libexec/t3code
|
||||||
|
cp -r --no-preserve=mode apps/server/{node_modules,dist} "$out"/libexec/t3code/apps/server
|
||||||
|
cp -r --no-preserve=mode apps/desktop/{node_modules,dist-electron} "$out"/libexec/t3code/apps/desktop
|
||||||
|
|
||||||
|
mkdir -p "$out"/libexec/t3code/apps/desktop/prod-resources
|
||||||
|
install -m444 assets/prod/black-universal-1024.png \
|
||||||
|
"$out"/libexec/t3code/apps/desktop/prod-resources/icon.png
|
||||||
|
|
||||||
|
find "$out"/libexec/t3code -xtype l -delete
|
||||||
|
|
||||||
|
makeWrapper ${lib.getExe nodejs} "$out"/bin/t3code \
|
||||||
|
--add-flags "$out"/libexec/t3code/apps/server/dist/index.mjs
|
||||||
|
|
||||||
|
makeWrapper ${lib.getExe electron} "$out"/bin/t3code-desktop \
|
||||||
|
--add-flags "$out"/libexec/t3code/apps/desktop/dist-electron/main.js \
|
||||||
|
--inherit-argv0
|
||||||
|
|
||||||
|
mkdir -p \
|
||||||
|
"$out"/share/icons/hicolor/1024x1024/apps \
|
||||||
|
"$out"/share/icons/hicolor/scalable/apps
|
||||||
|
install -m444 assets/prod/black-universal-1024.png \
|
||||||
|
"$out"/share/icons/hicolor/1024x1024/apps/t3code.png
|
||||||
|
install -m444 assets/prod/logo.svg \
|
||||||
|
"$out"/share/icons/hicolor/scalable/apps/t3code.svg
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
|
postInstall = lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) ''
|
||||||
|
installShellCompletion --cmd t3code \
|
||||||
|
--bash <("$out"/bin/t3code --completions bash) \
|
||||||
|
--fish <("$out"/bin/t3code --completions fish) \
|
||||||
|
--zsh <("$out"/bin/t3code --completions zsh)
|
||||||
|
'';
|
||||||
|
|
||||||
|
desktopItems = [
|
||||||
|
(makeDesktopItem {
|
||||||
|
name = "t3code";
|
||||||
|
desktopName = "T3 Code";
|
||||||
|
comment = finalAttrs.meta.description;
|
||||||
|
exec = "t3code-desktop %U";
|
||||||
|
terminal = false;
|
||||||
|
icon = "t3code";
|
||||||
|
startupWMClass = "T3 Code";
|
||||||
|
categories = [ "Development" ];
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
passthru = {
|
||||||
|
inherit nodeModules;
|
||||||
|
updateScript = nix-update-script {
|
||||||
|
extraArgs = [
|
||||||
|
"--subpackage"
|
||||||
|
"nodeModules"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Minimal web GUI for coding agents";
|
||||||
|
homepage = "https://t3.codes";
|
||||||
|
inherit (nodejs.meta) platforms;
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
maintainers = with lib.maintainers; [
|
||||||
|
imalison
|
||||||
|
qweered
|
||||||
|
];
|
||||||
|
mainProgram = "t3code";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
135
nixos/packages/t3code/normalize-bun-binaries.ts
Normal file
135
nixos/packages/t3code/normalize-bun-binaries.ts
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import { lstat, mkdir, readdir, rm, symlink } from "fs/promises";
|
||||||
|
import { join, relative } from "path";
|
||||||
|
|
||||||
|
type PackageManifest = {
|
||||||
|
name?: string;
|
||||||
|
bin?: string | Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const bunRoot = join(process.cwd(), "node_modules/.bun");
|
||||||
|
const bunEntries = (await readdir(bunRoot)).sort();
|
||||||
|
|
||||||
|
for (const entry of bunEntries) {
|
||||||
|
const modulesRoot = join(bunRoot, entry, "node_modules");
|
||||||
|
if (!(await exists(modulesRoot))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const binRoot = join(modulesRoot, ".bin");
|
||||||
|
await rm(binRoot, { recursive: true, force: true });
|
||||||
|
await mkdir(binRoot, { recursive: true });
|
||||||
|
|
||||||
|
const packageDirs = await collectPackages(modulesRoot);
|
||||||
|
for (const packageDir of packageDirs) {
|
||||||
|
const manifest = await readManifest(packageDir);
|
||||||
|
if (!manifest?.bin) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const seen = new Set<string>();
|
||||||
|
if (typeof manifest.bin == "string") {
|
||||||
|
const fallback = manifest.name ?? packageDir.split("/").pop();
|
||||||
|
if (fallback) {
|
||||||
|
await linkBinary(binRoot, fallback, packageDir, manifest.bin, seen);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = Object.entries(manifest.bin).sort((a, b) => a[0].localeCompare(b[0]));
|
||||||
|
for (const [name, target] of entries) {
|
||||||
|
await linkBinary(binRoot, name, packageDir, target, seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function collectPackages(modulesRoot: string) {
|
||||||
|
const found: string[] = [];
|
||||||
|
const topLevel = (await readdir(modulesRoot)).sort();
|
||||||
|
|
||||||
|
for (const name of topLevel) {
|
||||||
|
if (name == ".bin" || name == ".bun") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const full = join(modulesRoot, name);
|
||||||
|
if (!(await isDirectory(full))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.startsWith("@")) {
|
||||||
|
const scoped = (await readdir(full)).sort();
|
||||||
|
for (const child of scoped) {
|
||||||
|
const scopedDir = join(full, child);
|
||||||
|
if (await isDirectory(scopedDir)) {
|
||||||
|
found.push(scopedDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
found.push(full);
|
||||||
|
}
|
||||||
|
|
||||||
|
return found.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readManifest(dir: string) {
|
||||||
|
const file = Bun.file(join(dir, "package.json"));
|
||||||
|
if (!(await file.exists())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await file.json()) as PackageManifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function linkBinary(
|
||||||
|
binRoot: string,
|
||||||
|
name: string,
|
||||||
|
packageDir: string,
|
||||||
|
target: string,
|
||||||
|
seen: Set<string>,
|
||||||
|
) {
|
||||||
|
if (!name || !target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedName = normalizeBinName(name);
|
||||||
|
if (seen.has(normalizedName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolved = join(packageDir, target);
|
||||||
|
const script = Bun.file(resolved);
|
||||||
|
if (!(await script.exists())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
seen.add(normalizedName);
|
||||||
|
const destination = join(binRoot, normalizedName);
|
||||||
|
const relativeTarget = relative(binRoot, resolved);
|
||||||
|
await rm(destination, { force: true });
|
||||||
|
await symlink(relativeTarget.length == 0 ? "." : relativeTarget, destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exists(path: string) {
|
||||||
|
try {
|
||||||
|
await lstat(path);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isDirectory(path: string) {
|
||||||
|
try {
|
||||||
|
const info = await lstat(path);
|
||||||
|
return info.isDirectory();
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeBinName(name: string) {
|
||||||
|
const slash = name.lastIndexOf("/");
|
||||||
|
return slash >= 0 ? name.slice(slash + 1) : name;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user