@oh-my-pi/pi-natives 12.7.6 → 12.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/native/pi_natives.darwin-arm64.node +0 -0
- package/native/pi_natives.darwin-x64-baseline.node +0 -0
- package/native/{pi_natives.darwin-x64.node → pi_natives.darwin-x64-modern.node} +0 -0
- package/native/pi_natives.linux-arm64.node +0 -0
- package/native/pi_natives.linux-x64-baseline.node +0 -0
- package/native/{pi_natives.linux-x64.node → pi_natives.linux-x64-modern.node} +0 -0
- package/native/pi_natives.win32-x64-baseline.node +0 -0
- package/native/{pi_natives.win32-x64.node → pi_natives.win32-x64-modern.node} +0 -0
- package/package.json +2 -2
- package/src/embedded-addon.ts +10 -2
- package/src/native.ts +129 -50
package/README.md
CHANGED
|
@@ -52,8 +52,9 @@ crates/pi-natives/ # Rust source (workspace member)
|
|
|
52
52
|
src/image.rs # Image processing (photon-rs)
|
|
53
53
|
Cargo.toml # Rust dependencies
|
|
54
54
|
native/ # Native addon binaries
|
|
55
|
-
pi_natives.<platform>-<arch
|
|
56
|
-
pi_natives.node
|
|
55
|
+
pi_natives.<platform>-<arch>-modern.node # x64 modern ISA (AVX2)
|
|
56
|
+
pi_natives.<platform>-<arch>-baseline.node # x64 baseline ISA
|
|
57
|
+
pi_natives.<platform>-<arch>.node # non-x64 build artifact
|
|
57
58
|
src/ # TypeScript wrappers
|
|
58
59
|
native.ts # Native addon loader
|
|
59
60
|
index.ts # Public API
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-natives",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.8.0",
|
|
4
4
|
"description": "Native Rust functionality via N-API",
|
|
5
5
|
"keywords": ["napi", "rust", "native", "grep", "text-processing"],
|
|
6
6
|
"type": "module",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"url": "https://github.com/can1357/oh-my-pi/issues"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/pi-utils": "12.
|
|
40
|
+
"@oh-my-pi/pi-utils": "12.8.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/bun": "^1.3.9"
|
package/src/embedded-addon.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
export type EmbeddedAddonVariant = "modern" | "baseline" | "default";
|
|
2
|
+
|
|
3
|
+
export interface EmbeddedAddonFile {
|
|
4
|
+
variant: EmbeddedAddonVariant;
|
|
5
|
+
filename: string;
|
|
6
|
+
filePath: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
1
9
|
export interface EmbeddedAddon {
|
|
2
|
-
|
|
10
|
+
platformTag: string;
|
|
3
11
|
version: string;
|
|
4
|
-
|
|
12
|
+
files: EmbeddedAddonFile[];
|
|
5
13
|
}
|
|
6
14
|
|
|
7
15
|
export const embeddedAddon: EmbeddedAddon | null = null;
|
package/src/native.ts
CHANGED
|
@@ -3,19 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Each module extends NativeBindings via declaration merging in its types.ts.
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
6
|
import * as fs from "node:fs";
|
|
8
7
|
import { createRequire } from "node:module";
|
|
9
8
|
import * as os from "node:os";
|
|
10
9
|
import * as path from "node:path";
|
|
11
10
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
12
11
|
import { getNativesDir } from "@oh-my-pi/pi-utils/dirs";
|
|
13
|
-
|
|
14
12
|
import packageJson from "../package.json";
|
|
15
13
|
import type { NativeBindings } from "./bindings";
|
|
16
14
|
import { embeddedAddon } from "./embedded-addon";
|
|
17
|
-
|
|
18
|
-
// Import types to trigger declaration merging
|
|
19
15
|
import "./clipboard/types";
|
|
20
16
|
import "./glob/types";
|
|
21
17
|
import "./grep/types";
|
|
@@ -32,50 +28,137 @@ import "./work/types";
|
|
|
32
28
|
|
|
33
29
|
export type { NativeBindings, TsFunc } from "./bindings";
|
|
34
30
|
|
|
31
|
+
type CpuVariant = "modern" | "baseline";
|
|
35
32
|
const require = createRequire(import.meta.url);
|
|
33
|
+
const textDecoder = new TextDecoder();
|
|
36
34
|
const platformTag = `${process.platform}-${process.arch}`;
|
|
37
|
-
const addonFilename = `pi_natives.${platformTag}.node`;
|
|
38
35
|
const packageVersion = (packageJson as { version: string }).version;
|
|
39
36
|
const nativeDir = path.join(import.meta.dir, "..", "native");
|
|
40
37
|
const execDir = path.dirname(process.execPath);
|
|
41
38
|
const versionedDir = path.join(getNativesDir(), packageVersion);
|
|
42
|
-
const
|
|
43
|
-
const legacyUserDataDir =
|
|
39
|
+
const userDataDir =
|
|
44
40
|
process.platform === "win32"
|
|
45
41
|
? path.join(Bun.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "omp")
|
|
46
42
|
: path.join(os.homedir(), ".local", "bin");
|
|
47
|
-
const downloadUrl = `https://github.com/can1357/oh-my-pi/releases/latest/download/${addonFilename}`;
|
|
48
43
|
const isCompiledBinary =
|
|
49
44
|
Bun.env.PI_COMPILED ||
|
|
50
45
|
import.meta.url.includes("$bunfs") ||
|
|
51
46
|
import.meta.url.includes("~BUN") ||
|
|
52
47
|
import.meta.url.includes("%7EBUN");
|
|
53
|
-
|
|
54
48
|
const SUPPORTED_PLATFORMS = ["linux-x64", "linux-arm64", "darwin-x64", "darwin-arm64", "win32-x64"];
|
|
55
49
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
path.join(nativeDir, addonFilename),
|
|
61
|
-
path.join(execDir, addonFilename),
|
|
62
|
-
// Fallback untagged (only created for native builds, not cross-compilation)
|
|
63
|
-
path.join(nativeDir, "pi_natives.node"),
|
|
64
|
-
path.join(execDir, "pi_natives.node"),
|
|
65
|
-
];
|
|
66
|
-
|
|
67
|
-
const compiledCandidates = [
|
|
68
|
-
versionedAddonPath,
|
|
69
|
-
path.join(legacyUserDataDir, addonFilename),
|
|
70
|
-
path.join(legacyUserDataDir, "pi_natives.node"),
|
|
71
|
-
];
|
|
50
|
+
const variantOverride = getVariantOverride();
|
|
51
|
+
const selectedVariant = resolveCpuVariant(variantOverride);
|
|
52
|
+
const addonFilenames = getAddonFilenames(platformTag, selectedVariant);
|
|
53
|
+
const addonLabel = selectedVariant ? `${platformTag} (${selectedVariant})` : platformTag;
|
|
72
54
|
|
|
55
|
+
const debugCandidates = [path.join(nativeDir, "pi_natives.dev.node"), path.join(execDir, "pi_natives.dev.node")];
|
|
56
|
+
const baseReleaseCandidates = addonFilenames.flatMap(filename => [
|
|
57
|
+
path.join(nativeDir, filename),
|
|
58
|
+
path.join(execDir, filename),
|
|
59
|
+
]);
|
|
60
|
+
const compiledCandidates = addonFilenames.flatMap(filename => [
|
|
61
|
+
path.join(versionedDir, filename),
|
|
62
|
+
path.join(userDataDir, filename),
|
|
63
|
+
]);
|
|
73
64
|
const releaseCandidates = isCompiledBinary ? [...compiledCandidates, ...baseReleaseCandidates] : baseReleaseCandidates;
|
|
74
65
|
const candidates = $env.PI_DEV ? [...debugCandidates, ...releaseCandidates] : releaseCandidates;
|
|
66
|
+
const dedupedCandidates = [...new Set(candidates)];
|
|
67
|
+
|
|
68
|
+
function decodeOutput(output: string | ArrayBufferView | ArrayBuffer | null | undefined): string {
|
|
69
|
+
if (!output) return "";
|
|
70
|
+
if (typeof output === "string") return output;
|
|
71
|
+
if (ArrayBuffer.isView(output))
|
|
72
|
+
return textDecoder.decode(new Uint8Array(output.buffer, output.byteOffset, output.byteLength));
|
|
73
|
+
return textDecoder.decode(new Uint8Array(output));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function runCommand(command: string, args: string[]): string | null {
|
|
77
|
+
try {
|
|
78
|
+
const result = Bun.spawnSync([command, ...args], { stdout: "pipe", stderr: "pipe" });
|
|
79
|
+
if (result.exitCode !== 0) return null;
|
|
80
|
+
return decodeOutput(result.stdout).trim();
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getVariantOverride(): CpuVariant | null {
|
|
87
|
+
const value = Bun.env.PI_NATIVE_VARIANT;
|
|
88
|
+
if (!value) return null;
|
|
89
|
+
if (value === "modern" || value === "baseline") return value;
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function detectAvx2Support(): boolean {
|
|
94
|
+
if (process.arch !== "x64") return false;
|
|
95
|
+
|
|
96
|
+
if (process.platform === "linux") {
|
|
97
|
+
try {
|
|
98
|
+
const cpuInfo = fs.readFileSync("/proc/cpuinfo", "utf8");
|
|
99
|
+
return /\bavx2\b/i.test(cpuInfo);
|
|
100
|
+
} catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (process.platform === "darwin") {
|
|
106
|
+
const leaf7 = runCommand("sysctl", ["-n", "machdep.cpu.leaf7_features"]);
|
|
107
|
+
if (leaf7 && /\bAVX2\b/i.test(leaf7)) return true;
|
|
108
|
+
const features = runCommand("sysctl", ["-n", "machdep.cpu.features"]);
|
|
109
|
+
return Boolean(features && /\bAVX2\b/i.test(features));
|
|
110
|
+
}
|
|
75
111
|
|
|
112
|
+
if (process.platform === "win32") {
|
|
113
|
+
const output = runCommand("powershell.exe", [
|
|
114
|
+
"-NoProfile",
|
|
115
|
+
"-NonInteractive",
|
|
116
|
+
"-Command",
|
|
117
|
+
"[System.Runtime.Intrinsics.X86.Avx2]::IsSupported",
|
|
118
|
+
]);
|
|
119
|
+
return output?.toLowerCase() === "true";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function resolveCpuVariant(override: CpuVariant | null): CpuVariant | null {
|
|
126
|
+
if (process.arch !== "x64") return null;
|
|
127
|
+
if (override) return override;
|
|
128
|
+
return detectAvx2Support() ? "modern" : "baseline";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getAddonFilenames(tag: string, variant: CpuVariant | null): string[] {
|
|
132
|
+
const defaultFilename = `pi_natives.${tag}.node`;
|
|
133
|
+
if (process.arch !== "x64" || !variant) return [defaultFilename];
|
|
134
|
+
const baselineFilename = `pi_natives.${tag}-baseline.node`;
|
|
135
|
+
const modernFilename = `pi_natives.${tag}-modern.node`;
|
|
136
|
+
if (variant === "modern") {
|
|
137
|
+
return [modernFilename, baselineFilename];
|
|
138
|
+
}
|
|
139
|
+
return [baselineFilename];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function selectEmbeddedAddonFile(): { filename: string; filePath: string } | null {
|
|
143
|
+
if (!embeddedAddon) return null;
|
|
144
|
+
const defaultFile = embeddedAddon.files.find(file => file.variant === "default") ?? null;
|
|
145
|
+
if (process.arch !== "x64") return defaultFile ?? embeddedAddon.files[0] ?? null;
|
|
146
|
+
if (selectedVariant === "modern") {
|
|
147
|
+
return (
|
|
148
|
+
embeddedAddon.files.find(file => file.variant === "modern") ??
|
|
149
|
+
embeddedAddon.files.find(file => file.variant === "baseline") ??
|
|
150
|
+
null
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return embeddedAddon.files.find(file => file.variant === "baseline") ?? null;
|
|
154
|
+
}
|
|
76
155
|
function maybeExtractEmbeddedAddon(errors: string[]): string | null {
|
|
77
156
|
if (!isCompiledBinary || !embeddedAddon) return null;
|
|
78
|
-
if (embeddedAddon.
|
|
157
|
+
if (embeddedAddon.platformTag !== platformTag || embeddedAddon.version !== packageVersion) return null;
|
|
158
|
+
|
|
159
|
+
const selectedEmbeddedFile = selectEmbeddedAddonFile();
|
|
160
|
+
if (!selectedEmbeddedFile) return null;
|
|
161
|
+
const targetPath = path.join(versionedDir, selectedEmbeddedFile.filename);
|
|
79
162
|
|
|
80
163
|
try {
|
|
81
164
|
fs.mkdirSync(versionedDir, { recursive: true });
|
|
@@ -85,26 +168,24 @@ function maybeExtractEmbeddedAddon(errors: string[]): string | null {
|
|
|
85
168
|
return null;
|
|
86
169
|
}
|
|
87
170
|
|
|
88
|
-
if (fs.existsSync(
|
|
89
|
-
return
|
|
171
|
+
if (fs.existsSync(targetPath)) {
|
|
172
|
+
return targetPath;
|
|
90
173
|
}
|
|
91
174
|
|
|
92
175
|
try {
|
|
93
|
-
const buffer = fs.readFileSync(
|
|
94
|
-
fs.writeFileSync(
|
|
95
|
-
return
|
|
176
|
+
const buffer = fs.readFileSync(selectedEmbeddedFile.filePath);
|
|
177
|
+
fs.writeFileSync(targetPath, buffer);
|
|
178
|
+
return targetPath;
|
|
96
179
|
} catch (err) {
|
|
97
180
|
const message = err instanceof Error ? err.message : String(err);
|
|
98
|
-
errors.push(`embedded addon write: ${message}`);
|
|
181
|
+
errors.push(`embedded addon write (${selectedEmbeddedFile.filename}): ${message}`);
|
|
99
182
|
return null;
|
|
100
183
|
}
|
|
101
184
|
}
|
|
102
|
-
|
|
103
185
|
function loadNative(): NativeBindings {
|
|
104
186
|
const errors: string[] = [];
|
|
105
187
|
const embeddedCandidate = maybeExtractEmbeddedAddon(errors);
|
|
106
|
-
const runtimeCandidates = embeddedCandidate ? [embeddedCandidate, ...
|
|
107
|
-
|
|
188
|
+
const runtimeCandidates = embeddedCandidate ? [embeddedCandidate, ...dedupedCandidates] : dedupedCandidates;
|
|
108
189
|
for (const candidate of runtimeCandidates) {
|
|
109
190
|
try {
|
|
110
191
|
const bindings = require(candidate) as NativeBindings;
|
|
@@ -121,7 +202,6 @@ function loadNative(): NativeBindings {
|
|
|
121
202
|
errors.push(`${candidate}: ${message}`);
|
|
122
203
|
}
|
|
123
204
|
}
|
|
124
|
-
|
|
125
205
|
// Check if this is an unsupported platform
|
|
126
206
|
if (!SUPPORTED_PLATFORMS.includes(platformTag)) {
|
|
127
207
|
throw new Error(
|
|
@@ -130,26 +210,29 @@ function loadNative(): NativeBindings {
|
|
|
130
210
|
"If you need support for this platform, please open an issue.",
|
|
131
211
|
);
|
|
132
212
|
}
|
|
133
|
-
|
|
134
213
|
const details = errors.map(error => `- ${error}`).join("\n");
|
|
135
214
|
let helpMessage: string;
|
|
136
215
|
if (isCompiledBinary) {
|
|
216
|
+
const expectedPaths = addonFilenames.map(filename => ` ${path.join(versionedDir, filename)}`).join("\n");
|
|
217
|
+
const downloadHints = addonFilenames
|
|
218
|
+
.map(filename => {
|
|
219
|
+
const downloadUrl = `https://github.com/can1357/oh-my-pi/releases/latest/download/${filename}`;
|
|
220
|
+
const targetPath = path.join(versionedDir, filename);
|
|
221
|
+
return ` curl -fsSL "${downloadUrl}" -o "${targetPath}"`;
|
|
222
|
+
})
|
|
223
|
+
.join("\n");
|
|
137
224
|
helpMessage =
|
|
138
|
-
`The compiled binary should extract
|
|
139
|
-
`
|
|
140
|
-
`If it is missing, delete ${versionedDir} and re-run, or download manually:\n` +
|
|
141
|
-
` curl -fsSL "${downloadUrl}" -o "${versionedAddonPath}"`;
|
|
225
|
+
`The compiled binary should extract one of:\n${expectedPaths}\n\n` +
|
|
226
|
+
`If missing, delete ${versionedDir} and re-run, or download manually:\n${downloadHints}`;
|
|
142
227
|
} else {
|
|
143
228
|
helpMessage =
|
|
144
229
|
"If installed via npm/bun, try reinstalling: bun install @oh-my-pi/pi-natives\n" +
|
|
145
|
-
"If developing locally, build with: bun --cwd=packages/natives run build:native"
|
|
230
|
+
"If developing locally, build with: bun --cwd=packages/natives run build:native\n" +
|
|
231
|
+
"Optional x64 variants: TARGET_VARIANT=baseline|modern bun --cwd=packages/natives run build:native";
|
|
146
232
|
}
|
|
147
233
|
|
|
148
|
-
throw new Error(
|
|
149
|
-
`Failed to load pi_natives native addon for ${platformTag}.\n\n` + `Tried:\n${details}\n\n${helpMessage}`,
|
|
150
|
-
);
|
|
234
|
+
throw new Error(`Failed to load pi_natives native addon for ${addonLabel}.\n\nTried:\n${details}\n\n${helpMessage}`);
|
|
151
235
|
}
|
|
152
|
-
|
|
153
236
|
function validateNative(bindings: NativeBindings, source: string): void {
|
|
154
237
|
const missing: string[] = [];
|
|
155
238
|
const checkFn = (name: keyof NativeBindings) => {
|
|
@@ -157,7 +240,6 @@ function validateNative(bindings: NativeBindings, source: string): void {
|
|
|
157
240
|
missing.push(name);
|
|
158
241
|
}
|
|
159
242
|
};
|
|
160
|
-
|
|
161
243
|
checkFn("copyToClipboard");
|
|
162
244
|
checkFn("readImageFromClipboard");
|
|
163
245
|
checkFn("glob");
|
|
@@ -171,7 +253,6 @@ function validateNative(bindings: NativeBindings, source: string): void {
|
|
|
171
253
|
checkFn("getSupportedLanguages");
|
|
172
254
|
checkFn("truncateToWidth");
|
|
173
255
|
checkFn("sanitizeText");
|
|
174
|
-
|
|
175
256
|
checkFn("wrapTextWithAnsi");
|
|
176
257
|
checkFn("sliceWithWidth");
|
|
177
258
|
checkFn("extractSegments");
|
|
@@ -189,7 +270,6 @@ function validateNative(bindings: NativeBindings, source: string): void {
|
|
|
189
270
|
checkFn("getSystemInfo");
|
|
190
271
|
checkFn("getWorkProfile");
|
|
191
272
|
checkFn("invalidateFsScanCache");
|
|
192
|
-
|
|
193
273
|
if (missing.length) {
|
|
194
274
|
throw new Error(
|
|
195
275
|
`Native addon missing exports (${source}). Missing: ${missing.join(", ")}. ` +
|
|
@@ -197,5 +277,4 @@ function validateNative(bindings: NativeBindings, source: string): void {
|
|
|
197
277
|
);
|
|
198
278
|
}
|
|
199
279
|
}
|
|
200
|
-
|
|
201
280
|
export const native = loadNative();
|