feat(natives): fixed binary installs with embedded native addon extraction
- Added automatic extraction of embedded native addon payload to ~/.omp/natives/<version> on first run of compiled binaries. - Added embed:native build script for generating platform-specific native addon payloads embedded in compiled binaries. - Enhanced native addon loading to prioritize extracted embedded addon over downloaded versions. - Improved error messages with platform-specific guidance for compiled vs npm-installed binaries. - Refactored CI workflow to use reusable build_binary() function for cross-platform binary compilation.
This commit is contained in:
@@ -167,11 +167,20 @@ jobs:
|
||||
mkdir -p packages/coding-agent/binaries
|
||||
ENTRYPOINT="./packages/coding-agent/src/cli.ts"
|
||||
DEFINES="--define OMP_COMPILED=true --root ."
|
||||
bun build --compile $DEFINES --target=bun-darwin-arm64 $ENTRYPOINT --outfile packages/coding-agent/binaries/omp-darwin-arm64
|
||||
bun build --compile $DEFINES --target=bun-darwin-x64 $ENTRYPOINT --outfile packages/coding-agent/binaries/omp-darwin-x64
|
||||
bun build --compile $DEFINES --target=bun-linux-x64 $ENTRYPOINT --outfile packages/coding-agent/binaries/omp-linux-x64
|
||||
bun build --compile $DEFINES --target=bun-linux-arm64 $ENTRYPOINT --outfile packages/coding-agent/binaries/omp-linux-arm64
|
||||
bun build --compile $DEFINES --target=bun-windows-x64 $ENTRYPOINT --outfile packages/coding-agent/binaries/omp-windows-x64.exe
|
||||
build_binary() {
|
||||
PLATFORM="$1"
|
||||
ARCH="$2"
|
||||
TARGET="$3"
|
||||
OUTFILE="$4"
|
||||
TARGET_PLATFORM="$PLATFORM" TARGET_ARCH="$ARCH" bun --cwd=packages/natives run embed:native
|
||||
bun build --compile $DEFINES --target="$TARGET" "$ENTRYPOINT" --outfile "$OUTFILE"
|
||||
}
|
||||
build_binary darwin arm64 bun-darwin-arm64 packages/coding-agent/binaries/omp-darwin-arm64
|
||||
build_binary darwin x64 bun-darwin-x64 packages/coding-agent/binaries/omp-darwin-x64
|
||||
build_binary linux x64 bun-linux-x64 packages/coding-agent/binaries/omp-linux-x64
|
||||
build_binary linux arm64 bun-linux-arm64 packages/coding-agent/binaries/omp-linux-arm64
|
||||
build_binary win32 x64 bun-windows-x64 packages/coding-agent/binaries/omp-windows-x64.exe
|
||||
bun --cwd=packages/natives run embed:native --reset
|
||||
- name: Stage native addons for release
|
||||
run: cp packages/natives/native/*.node packages/coding-agent/binaries/
|
||||
- name: Create GitHub Release
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
- Migrated bash command execution from ptree-based persistent sessions to native shell bindings with streaming support
|
||||
- Simplified bash executor to use brush-core native API instead of managing long-lived shell processes
|
||||
- Routed clipboard copy and image paste through native arboard bindings instead of shell commands
|
||||
- Embedded native addon payload for compiled binaries and extract to `~/.omp/natives/<version>` on first run
|
||||
|
||||
### Removed
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
"scripts": {
|
||||
"check": "tsgo -p tsconfig.json",
|
||||
"format-prompts": "bun scripts/format-prompts.ts",
|
||||
"build:binary": "cd ../.. && bun build --compile --define OMP_COMPILED=true --root . ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/dist/omp",
|
||||
"build:binary": "cd ../.. && bun --cwd=packages/natives run embed:native && bun build --compile --define OMP_COMPILED=true --root . ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/dist/omp && bun --cwd=packages/natives run embed:native --reset",
|
||||
"generate-template": "bun scripts/generate-template.ts",
|
||||
"test": "bun test"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Removed `resize()` function; use `PhotonImage.resize()` method instead
|
||||
@@ -14,6 +13,8 @@
|
||||
|
||||
### Added
|
||||
|
||||
- Added automatic extraction of embedded native addon to `~/.omp/natives/<version>` on first run for compiled binaries
|
||||
- Added `embed:native` build script to embed platform-specific native addon payloads into compiled binaries
|
||||
- Exported `Shell` class for creating persistent shell sessions with `run()` method and session options
|
||||
- Exported `ShellOptions`, `ShellRunOptions`, and `ShellRunResult` types for shell session management
|
||||
- Exported `find()` function for file discovery with glob patterns and .gitignore support
|
||||
@@ -30,12 +31,15 @@
|
||||
|
||||
### Changed
|
||||
|
||||
- Enhanced native addon loading to prioritize extracted embedded addon for compiled binaries before falling back to system paths
|
||||
- Improved error messages to provide platform-specific guidance for addon loading failures, including manual download instructions for compiled binaries
|
||||
- Reorganized native bindings into modular type files with declaration merging via `NativeBindings` interface
|
||||
- Moved type definitions from implementation files to dedicated `types.ts` modules for better separation of concerns
|
||||
- Enhanced `SystemInfo` type with additional fields: `os`, `arch`, `hostname`, `shell`, `terminal`, `de`, `wm`, and `gpu`
|
||||
- Refactored module exports to use direct destructuring from native bindings instead of wrapper functions
|
||||
- Changed `PhotonImage` API to use instance methods (`resize()`, `encode()`) instead of standalone functions
|
||||
- Changed `PhotonImage` to use property accessors for `width` and `height` instead of getter methods
|
||||
- Embedded native addon payload for compiled binaries and extract to `~/.omp/natives/<version>` on first run
|
||||
|
||||
## [9.7.0] - 2026-02-01
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"scripts": {
|
||||
"build:native": "bun scripts/build-native.ts",
|
||||
"dev:native": "bun scripts/build-native.ts --dev",
|
||||
"embed:native": "bun scripts/embed-native.ts",
|
||||
"check": "biome check . && tsgo -p tsconfig.json",
|
||||
"fix": "biome check --write --unsafe .",
|
||||
"test": "bun run build:native && bun test",
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as path from "node:path";
|
||||
|
||||
const reset = process.argv.includes("--reset");
|
||||
|
||||
const outputPath = path.join(import.meta.dir, "../src/embedded-addon.ts");
|
||||
const packageJsonPath = path.join(import.meta.dir, "../package.json");
|
||||
|
||||
const stubContent = "export interface EmbeddedAddon {\n\tplatform: string;\n\tversion: string;\n\tfilePath: string;\n}\n\nexport const embeddedAddon: EmbeddedAddon | null = null;\n";
|
||||
|
||||
if (reset) {
|
||||
await Bun.write(outputPath, stubContent);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const targetPlatform = process.env.TARGET_PLATFORM || process.platform;
|
||||
const targetArch = process.env.TARGET_ARCH || process.arch;
|
||||
const platformTag = `${targetPlatform}-${targetArch}`;
|
||||
const addonFilename = `pi_natives.${platformTag}.node`;
|
||||
|
||||
const packageJson = (await Bun.file(packageJsonPath).json()) as { version: string };
|
||||
|
||||
const content = `import addonPath from ${JSON.stringify("../native/" + addonFilename)} with { type: \"file\" };\n\nexport interface EmbeddedAddon {\n\tplatform: string;\n\tversion: string;\n\tfilePath: string;\n}\n\nexport const embeddedAddon: EmbeddedAddon | null = {\n\tplatform: ${JSON.stringify(platformTag)},\n\tversion: ${JSON.stringify(packageJson.version)},\n\tfilePath: addonPath,\n};\n`;
|
||||
|
||||
await Bun.write(outputPath, content);
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface EmbeddedAddon {
|
||||
platform: string;
|
||||
version: string;
|
||||
filePath: string;
|
||||
}
|
||||
|
||||
export const embeddedAddon: EmbeddedAddon | null = null;
|
||||
@@ -4,9 +4,13 @@
|
||||
* Each module extends NativeBindings via declaration merging in its types.ts.
|
||||
*/
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import packageJson from "../package.json" with { type: "json" };
|
||||
import type { NativeBindings } from "./bindings";
|
||||
import { embeddedAddon } from "./embedded-addon";
|
||||
|
||||
// Import types to trigger declaration merging
|
||||
import "./clipboard/types";
|
||||
@@ -25,35 +29,87 @@ export type { NativeBindings, TsFunc } from "./bindings";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const platformTag = `${process.platform}-${process.arch}`;
|
||||
const addonFilename = `pi_natives.${platformTag}.node`;
|
||||
const packageVersion = (packageJson as { version: string }).version;
|
||||
const nativeDir = path.join(import.meta.dir, "..", "native");
|
||||
const repoRoot = path.join(import.meta.dir, "..", "..", "..");
|
||||
const execDir = path.dirname(process.execPath);
|
||||
const versionedDir = path.join(os.homedir(), ".omp", "natives", packageVersion);
|
||||
const versionedAddonPath = path.join(versionedDir, addonFilename);
|
||||
const legacyUserDataDir =
|
||||
process.platform === "win32"
|
||||
? path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "omp")
|
||||
: path.join(os.homedir(), ".local", "bin");
|
||||
const downloadUrl = `https://github.com/can1357/oh-my-pi/releases/latest/download/${addonFilename}`;
|
||||
const isCompiledBinary =
|
||||
import.meta.url.includes("$bunfs") || import.meta.url.includes("~BUN") || import.meta.url.includes("%7EBUN");
|
||||
|
||||
const SUPPORTED_PLATFORMS = ["linux-x64", "linux-arm64", "darwin-x64", "darwin-arm64", "win32-x64"];
|
||||
|
||||
const debugCandidates = [path.join(nativeDir, "pi_natives.dev.node"), path.join(execDir, "pi_natives.dev.node")];
|
||||
|
||||
const releaseCandidates = [
|
||||
const baseReleaseCandidates = [
|
||||
// Platform-tagged builds (preferred - always correct platform)
|
||||
path.join(nativeDir, `pi_natives.${platformTag}.node`),
|
||||
path.join(execDir, `pi_natives.${platformTag}.node`),
|
||||
path.join(nativeDir, addonFilename),
|
||||
path.join(execDir, addonFilename),
|
||||
// Fallback untagged (only created for native builds, not cross-compilation)
|
||||
path.join(nativeDir, "pi_natives.node"),
|
||||
path.join(execDir, "pi_natives.node"),
|
||||
];
|
||||
|
||||
const compiledCandidates = [
|
||||
versionedAddonPath,
|
||||
path.join(legacyUserDataDir, addonFilename),
|
||||
path.join(legacyUserDataDir, "pi_natives.node"),
|
||||
];
|
||||
|
||||
const releaseCandidates = isCompiledBinary ? [...compiledCandidates, ...baseReleaseCandidates] : baseReleaseCandidates;
|
||||
const candidates = process.env.OMP_DEV ? [...debugCandidates, ...releaseCandidates] : releaseCandidates;
|
||||
|
||||
function maybeExtractEmbeddedAddon(errors: string[]): string | null {
|
||||
if (!isCompiledBinary || !embeddedAddon) return null;
|
||||
if (embeddedAddon.platform !== platformTag || embeddedAddon.version !== packageVersion) return null;
|
||||
|
||||
try {
|
||||
fs.mkdirSync(versionedDir, { recursive: true });
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
errors.push(`embedded addon dir: ${message}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
fs.statSync(versionedAddonPath);
|
||||
return versionedAddonPath;
|
||||
} catch (err) {
|
||||
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
errors.push(`embedded addon stat: ${message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const buffer = fs.readFileSync(embeddedAddon.filePath);
|
||||
fs.writeFileSync(versionedAddonPath, buffer);
|
||||
return versionedAddonPath;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
errors.push(`embedded addon write: ${message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function loadNative(): NativeBindings {
|
||||
const errors: string[] = [];
|
||||
const embeddedCandidate = maybeExtractEmbeddedAddon(errors);
|
||||
const runtimeCandidates = embeddedCandidate ? [embeddedCandidate, ...candidates] : candidates;
|
||||
|
||||
for (const candidate of candidates) {
|
||||
for (const candidate of runtimeCandidates) {
|
||||
try {
|
||||
const bindings = require(candidate) as NativeBindings;
|
||||
validateNative(bindings, candidate);
|
||||
if (process.env.OMP_DEV) {
|
||||
console.log(`Loaded native addon from ${candidate}`);
|
||||
console.log(` - Root: ${repoRoot}`);
|
||||
}
|
||||
return bindings;
|
||||
} catch (err) {
|
||||
@@ -75,11 +131,21 @@ function loadNative(): NativeBindings {
|
||||
}
|
||||
|
||||
const details = errors.map(error => `- ${error}`).join("\n");
|
||||
throw new Error(
|
||||
`Failed to load pi_natives native addon for ${platformTag}.\n\n` +
|
||||
`Tried:\n${details}\n\n` +
|
||||
let helpMessage: string;
|
||||
if (isCompiledBinary) {
|
||||
helpMessage =
|
||||
`The compiled binary should extract the native addon to:\n` +
|
||||
` ${versionedAddonPath}\n\n` +
|
||||
`If it is missing, delete ${versionedDir} and re-run, or download manually:\n` +
|
||||
` curl -fsSL "${downloadUrl}" -o "${versionedAddonPath}"`;
|
||||
} else {
|
||||
helpMessage =
|
||||
"If installed via npm/bun, try reinstalling: bun install @oh-my-pi/pi-natives\n" +
|
||||
"If developing locally, build with: bun --cwd=packages/natives run build:native",
|
||||
"If developing locally, build with: bun --cwd=packages/natives run build:native";
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Failed to load pi_natives native addon for ${platformTag}.\n\n` + `Tried:\n${details}\n\n${helpMessage}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user