@linxiraos/pi-natives 1.1.27 → 1.2.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 +2 -1
- package/native/embedded-addon.js +4 -13
- package/native/index.d.ts +124 -8
- package/native/index.js +3 -0
- package/native/loader-state.d.ts +8 -12
- package/native/loader-state.js +36 -111
- package/native/version-sentinel.js +1 -5
- package/package.json +7 -9
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ Native Rust functionality via N-API.
|
|
|
11
11
|
- **WebRTC**: Native Opus media, SDP offer/answer negotiation, and data-channel events for live sessions
|
|
12
12
|
- **File locking**: Process-owned cross-process locks with in-memory kernel names on Linux/Windows and `flock(2)` sidecars on other Unix platforms
|
|
13
13
|
- **PDF**: In-memory PDF-to-Markdown extraction with OCR-page classification via `pdf-inspector`
|
|
14
|
+
- **Binary patches**: `applyBinaryPatch(oldPath, patchPath, outPath)` applies an HDiffPatch single-stream patch (HDIFFSF20, zstd or uncompressed), streaming from files; callers verify digests
|
|
14
15
|
|
|
15
16
|
General-purpose image processing (decode/resize/encode for files and buffers)
|
|
16
17
|
lives in [`Bun.Image`](https://bun.com/docs/runtime/image) on the JS side; this
|
|
@@ -77,7 +78,7 @@ crates/pi-natives/ # Rust source (workspace member)
|
|
|
77
78
|
native/ # Core loader files and local/CI native build outputs
|
|
78
79
|
index.js # Public native export surface
|
|
79
80
|
loader-state.js # Platform, ISA variant, and addon resolution
|
|
80
|
-
embedded-addon.js #
|
|
81
|
+
embedded-addon.js # Null embed stub; binary builds replace it in memory
|
|
81
82
|
pi_natives.<platform>-<arch>-modern.node # x64 modern ISA (local/CI artifact)
|
|
82
83
|
pi_natives.<platform>-<arch>-baseline.node # x64 baseline ISA (local/CI artifact)
|
|
83
84
|
pi_natives.<platform>-<arch>.node # non-x64 build artifact
|
package/native/embedded-addon.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// See scripts/embed-native.ts
|
|
1
|
+
// Always null on disk. Standalone binary builds replace this module in memory
|
|
2
|
+
// with the target's manifest and one zstd frame per addon (scripts/embed-native.ts).
|
|
4
3
|
|
|
5
4
|
/** @typedef {"modern" | "baseline" | "default"} EmbeddedAddonVariant */
|
|
6
5
|
|
|
@@ -8,15 +7,8 @@
|
|
|
8
7
|
* @typedef {Object} EmbeddedAddonFile
|
|
9
8
|
* @property {EmbeddedAddonVariant} variant
|
|
10
9
|
* @property {string} filename
|
|
11
|
-
* @property {number} size
|
|
12
|
-
* @property {string
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {Object} EmbeddedAddonArchive
|
|
17
|
-
* @property {"tar.gz"} format
|
|
18
|
-
* @property {string} filename
|
|
19
|
-
* @property {string} filePath
|
|
10
|
+
* @property {number} size Decompressed `.node` size in bytes.
|
|
11
|
+
* @property {string} zstdPath Embedded zstd frame holding the addon bytes.
|
|
20
12
|
*/
|
|
21
13
|
|
|
22
14
|
/**
|
|
@@ -24,7 +16,6 @@
|
|
|
24
16
|
* @property {string} platformTag
|
|
25
17
|
* @property {string} version
|
|
26
18
|
* @property {EmbeddedAddonFile[]} files
|
|
27
|
-
* @property {EmbeddedAddonArchive=} archive
|
|
28
19
|
*/
|
|
29
20
|
|
|
30
21
|
/** @type {EmbeddedAddon|null} */
|
package/native/index.d.ts
CHANGED
|
@@ -38,6 +38,11 @@ export declare class DesktopSession {
|
|
|
38
38
|
* behind it.
|
|
39
39
|
*/
|
|
40
40
|
get capabilities(): DesktopCapabilities
|
|
41
|
+
/**
|
|
42
|
+
* Lock and display-sleep state read on the calling thread, never queued
|
|
43
|
+
* behind the worker, so it answers at once even while a request is stuck.
|
|
44
|
+
*/
|
|
45
|
+
get screenState(): DesktopScreenState
|
|
41
46
|
listDisplays(): Promise<Array<DesktopDisplay>>
|
|
42
47
|
listWindows(): Promise<Array<DesktopWindow>>
|
|
43
48
|
listApplications(options?: ApplicationQuery | undefined | null): Promise<Array<Application>>
|
|
@@ -730,6 +735,13 @@ export declare class VcsRepo {
|
|
|
730
735
|
*/
|
|
731
736
|
export declare function __ompInstallTokioRuntime(): void
|
|
732
737
|
|
|
738
|
+
/**
|
|
739
|
+
* Points the addon at the directory holding downloaded wasm grammars
|
|
740
|
+
* (`<dir>/<file>`). The loader (`native/loader-state.js`) calls this once
|
|
741
|
+
* with `<natives dir>/grammars`; grammars then load lazily from it.
|
|
742
|
+
*/
|
|
743
|
+
export declare function __ompSetGrammarDir(dir: string): void
|
|
744
|
+
|
|
733
745
|
/**
|
|
734
746
|
* Release version stamped into this `.node` after linking.
|
|
735
747
|
*
|
|
@@ -782,6 +794,15 @@ export interface ApplicationQuery {
|
|
|
782
794
|
runningOnly?: boolean
|
|
783
795
|
}
|
|
784
796
|
|
|
797
|
+
/**
|
|
798
|
+
* Applies an `HDiffPatch` single-stream patch to `oldPath`, writing `outPath`.
|
|
799
|
+
*
|
|
800
|
+
* Accepts HDIFFSF20 patches, zstd-compressed or uncompressed; `outPath` is
|
|
801
|
+
* created or truncated. Resolves to the result's size in bytes. Rejects on I/O
|
|
802
|
+
* failure, a damaged or unsupported patch, or a patch made for other old data.
|
|
803
|
+
*/
|
|
804
|
+
export declare function applyBinaryPatch(oldPath: string, patchPath: string, outPath: string): Promise<number>
|
|
805
|
+
|
|
785
806
|
/**
|
|
786
807
|
* Apply ast-grep rewrite rules to matching files; honors `dryRun` and returns
|
|
787
808
|
* a promise.
|
|
@@ -863,6 +884,11 @@ export interface AstFindResult {
|
|
|
863
884
|
limitReached: boolean
|
|
864
885
|
/** Non-fatal parse or pattern errors collected during the run. */
|
|
865
886
|
parseErrors?: Array<string>
|
|
887
|
+
/**
|
|
888
|
+
* Languages whose on-demand grammar is not installed; their files were
|
|
889
|
+
* skipped (see `wasmGrammarFor`).
|
|
890
|
+
*/
|
|
891
|
+
missingGrammars?: Array<string>
|
|
866
892
|
}
|
|
867
893
|
|
|
868
894
|
/**
|
|
@@ -1035,6 +1061,11 @@ export interface AstReplaceResult {
|
|
|
1035
1061
|
limitReached: boolean
|
|
1036
1062
|
/** Parse or pattern errors when not failing the whole operation. */
|
|
1037
1063
|
parseErrors?: Array<string>
|
|
1064
|
+
/**
|
|
1065
|
+
* Languages whose on-demand grammar is not installed; their files were
|
|
1066
|
+
* skipped (see `wasmGrammarFor`).
|
|
1067
|
+
*/
|
|
1068
|
+
missingGrammars?: Array<string>
|
|
1038
1069
|
}
|
|
1039
1070
|
|
|
1040
1071
|
export interface AxNode {
|
|
@@ -1217,6 +1248,16 @@ export interface DesktopCapabilities {
|
|
|
1217
1248
|
inputPermission: string
|
|
1218
1249
|
axPermission: string
|
|
1219
1250
|
displayCount: number
|
|
1251
|
+
/**
|
|
1252
|
+
* The user session is locked (lock screen up). Only macOS detects this;
|
|
1253
|
+
* other backends report `false`.
|
|
1254
|
+
*/
|
|
1255
|
+
screenLocked: boolean
|
|
1256
|
+
/**
|
|
1257
|
+
* The display is asleep, so nothing can be captured until it wakes. Only
|
|
1258
|
+
* macOS detects this; other backends report `false`.
|
|
1259
|
+
*/
|
|
1260
|
+
displayAsleep: boolean
|
|
1220
1261
|
}
|
|
1221
1262
|
|
|
1222
1263
|
export interface DesktopCapture {
|
|
@@ -1242,6 +1283,12 @@ export interface DesktopCapture {
|
|
|
1242
1283
|
displays: Array<DesktopDisplay>
|
|
1243
1284
|
backend: string
|
|
1244
1285
|
displayServer?: string
|
|
1286
|
+
/**
|
|
1287
|
+
* The user session was locked when this frame was taken: a display
|
|
1288
|
+
* capture shows the lock screen, and a window capture shows the window's
|
|
1289
|
+
* last frame behind it. Only macOS detects this; others report `false`.
|
|
1290
|
+
*/
|
|
1291
|
+
screenLocked: boolean
|
|
1245
1292
|
}
|
|
1246
1293
|
|
|
1247
1294
|
export interface DesktopControlState {
|
|
@@ -1290,6 +1337,15 @@ export interface DesktopPoint {
|
|
|
1290
1337
|
y: number
|
|
1291
1338
|
}
|
|
1292
1339
|
|
|
1340
|
+
/**
|
|
1341
|
+
* Lock and display-sleep state of the user session, read live per call.
|
|
1342
|
+
* Only macOS detects either; other platforms report both as `false`.
|
|
1343
|
+
*/
|
|
1344
|
+
export interface DesktopScreenState {
|
|
1345
|
+
screenLocked: boolean
|
|
1346
|
+
displayAsleep: boolean
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1293
1349
|
export interface DesktopSessionOptions {
|
|
1294
1350
|
display?: string
|
|
1295
1351
|
}
|
|
@@ -1908,6 +1964,11 @@ export interface GrepMatch {
|
|
|
1908
1964
|
contextAfter?: Array<ContextLine>
|
|
1909
1965
|
/** Whether the line was truncated. */
|
|
1910
1966
|
truncated?: boolean
|
|
1967
|
+
/**
|
|
1968
|
+
* 1-indexed character column where the first match on a truncated line
|
|
1969
|
+
* starts; the truncated `line` shows a window around it.
|
|
1970
|
+
*/
|
|
1971
|
+
column?: number
|
|
1911
1972
|
/** Per-file match count (count mode only). */
|
|
1912
1973
|
matchCount?: number
|
|
1913
1974
|
}
|
|
@@ -2351,6 +2412,11 @@ export interface Match {
|
|
|
2351
2412
|
contextAfter?: Array<ContextLine>
|
|
2352
2413
|
/** Whether the line was truncated. */
|
|
2353
2414
|
truncated?: boolean
|
|
2415
|
+
/**
|
|
2416
|
+
* 1-indexed character column where the first match on a truncated line
|
|
2417
|
+
* starts; the truncated `line` shows a window around it.
|
|
2418
|
+
*/
|
|
2419
|
+
column?: number
|
|
2354
2420
|
}
|
|
2355
2421
|
|
|
2356
2422
|
/**
|
|
@@ -2729,14 +2795,23 @@ export interface PtyStartOptions {
|
|
|
2729
2795
|
/**
|
|
2730
2796
|
* Rasterize SVG/SVGZ bytes into a bounded PNG without resolving local files.
|
|
2731
2797
|
*
|
|
2732
|
-
*
|
|
2733
|
-
*
|
|
2798
|
+
* The image is drawn at `scale` times the SVG's intrinsic size (default 1;
|
|
2799
|
+
* above 1 renders vector content crisply at display resolution), then shrunk
|
|
2800
|
+
* as needed to fit `max_width_px` x `max_height_px` with its aspect ratio
|
|
2801
|
+
* kept. Conversion runs on the native blocking pool so parsing and rendering
|
|
2802
|
+
* do not stall the JavaScript event loop.
|
|
2803
|
+
*
|
|
2804
|
+
* With `cell`, the limits round down to whole cells and the canvas pads with
|
|
2805
|
+
* transparency, right and bottom, to whole cells: a terminal placing the PNG
|
|
2806
|
+
* over `width / cell.width_px` columns and `height / cell.height_px` rows
|
|
2807
|
+
* shows it 1:1 instead of resampling it.
|
|
2734
2808
|
*
|
|
2735
2809
|
* # Errors
|
|
2736
|
-
* Returns an error for invalid SVG data, zero/oversized limits,
|
|
2737
|
-
* failure, or PNG
|
|
2810
|
+
* Returns an error for invalid SVG data, zero/oversized limits, a zero cell
|
|
2811
|
+
* size, a scale that is not finite and positive, allocation failure, or PNG
|
|
2812
|
+
* encoding failure.
|
|
2738
2813
|
*/
|
|
2739
|
-
export declare function rasterizeSvg(input: Uint8Array, maxWidthPx: number, maxHeightPx: number): Promise<Uint8Array>
|
|
2814
|
+
export declare function rasterizeSvg(input: Uint8Array, maxWidthPx: number, maxHeightPx: number, scale?: number | undefined | null, cell?: SvgCell | undefined | null): Promise<Uint8Array>
|
|
2740
2815
|
|
|
2741
2816
|
/**
|
|
2742
2817
|
* Read an image from the system clipboard.
|
|
@@ -3384,12 +3459,20 @@ export interface SummarySegment {
|
|
|
3384
3459
|
}
|
|
3385
3460
|
|
|
3386
3461
|
/**
|
|
3387
|
-
* Check if a language
|
|
3388
|
-
*
|
|
3389
|
-
* mapping.
|
|
3462
|
+
* Check if a language resolves to a grammar (by name, extension or alias),
|
|
3463
|
+
* i.e. whether `highlightCode` colors it rather than echoing it.
|
|
3390
3464
|
*/
|
|
3391
3465
|
export declare function supportsLanguage(lang: string): boolean
|
|
3392
3466
|
|
|
3467
|
+
/**
|
|
3468
|
+
* Terminal cell size in device pixels, for [`rasterize_svg`] canvases a
|
|
3469
|
+
* terminal shows over whole cells.
|
|
3470
|
+
*/
|
|
3471
|
+
export interface SvgCell {
|
|
3472
|
+
widthPx: number
|
|
3473
|
+
heightPx: number
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3393
3476
|
/** Options for [`TextPredictor::new`]. */
|
|
3394
3477
|
export interface TextPredictorOptions {
|
|
3395
3478
|
/** Engine: `ngram`, `smollm`, or `apple`. */
|
|
@@ -3661,6 +3744,39 @@ export declare function warmBlockParse(options: BlockParseOptions): Promise<unde
|
|
|
3661
3744
|
*/
|
|
3662
3745
|
export declare function warmHighlighter(): Promise<undefined>
|
|
3663
3746
|
|
|
3747
|
+
/**
|
|
3748
|
+
* Wasm grammar backing `lang` (alias) or the language of `path`; `null` for
|
|
3749
|
+
* built-in or unknown languages.
|
|
3750
|
+
*/
|
|
3751
|
+
export declare function wasmGrammarFor(query: WasmGrammarQuery): WasmGrammarInfo | null
|
|
3752
|
+
|
|
3753
|
+
/** A grammar the host downloads on first use. */
|
|
3754
|
+
export interface WasmGrammarInfo {
|
|
3755
|
+
/** Canonical language name, e.g. `verilog`, `csharp`. */
|
|
3756
|
+
language: string
|
|
3757
|
+
/** stencil-hq/wasm-grammars release tag hosting the grammar, e.g. `v1`. */
|
|
3758
|
+
release: string
|
|
3759
|
+
/**
|
|
3760
|
+
* File name inside the grammar directory; the release asset is
|
|
3761
|
+
* `<file>.zst`.
|
|
3762
|
+
*/
|
|
3763
|
+
file: string
|
|
3764
|
+
/** Lowercase hex SHA-256 of the decompressed `.wasm`. */
|
|
3765
|
+
sha256: string
|
|
3766
|
+
/** Byte size of the decompressed `.wasm`. */
|
|
3767
|
+
size: number
|
|
3768
|
+
/** Whether the grammar is downloaded (or already loaded). */
|
|
3769
|
+
installed: boolean
|
|
3770
|
+
}
|
|
3771
|
+
|
|
3772
|
+
/** Language to look up: an alias, or a path to infer it from. */
|
|
3773
|
+
export interface WasmGrammarQuery {
|
|
3774
|
+
/** Language alias (e.g. `kotlin`, `sv`); wins over `path`. */
|
|
3775
|
+
lang?: string
|
|
3776
|
+
/** File whose extension selects the language. */
|
|
3777
|
+
path?: string
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3664
3780
|
/** Profiling results returned to JavaScript. */
|
|
3665
3781
|
export interface WorkProfile {
|
|
3666
3782
|
/** Folded stack format for flamegraph tools. */
|
package/native/index.js
CHANGED
|
@@ -46,10 +46,12 @@ export const VcsRepo = nativeBindings.VcsRepo;
|
|
|
46
46
|
|
|
47
47
|
// functions
|
|
48
48
|
export const __ompInstallTokioRuntime = nativeBindings.__ompInstallTokioRuntime ?? missingNativeExport("__ompInstallTokioRuntime");
|
|
49
|
+
export const __ompSetGrammarDir = nativeBindings.__ompSetGrammarDir ?? missingNativeExport("__ompSetGrammarDir");
|
|
49
50
|
export const __piNativesBuildVersion = nativeBindings.__piNativesBuildVersion;
|
|
50
51
|
export const appleFmAvailability = nativeBindings.appleFmAvailability ?? missingNativeExport("appleFmAvailability");
|
|
51
52
|
export const appleFmCancel = nativeBindings.appleFmCancel ?? missingNativeExport("appleFmCancel");
|
|
52
53
|
export const appleFmGenerate = nativeBindings.appleFmGenerate ?? missingNativeExport("appleFmGenerate");
|
|
54
|
+
export const applyBinaryPatch = nativeBindings.applyBinaryPatch ?? missingNativeExport("applyBinaryPatch");
|
|
53
55
|
export const astEdit = nativeBindings.astEdit ?? missingNativeExport("astEdit");
|
|
54
56
|
export const astGrep = nativeBindings.astGrep ?? missingNativeExport("astGrep");
|
|
55
57
|
export const astMatch = nativeBindings.astMatch ?? missingNativeExport("astMatch");
|
|
@@ -142,6 +144,7 @@ export const vectorIndexTopK = nativeBindings.vectorIndexTopK ?? missingNativeEx
|
|
|
142
144
|
export const visibleWidth = nativeBindings.visibleWidth ?? missingNativeExport("visibleWidth");
|
|
143
145
|
export const warmBlockParse = nativeBindings.warmBlockParse ?? missingNativeExport("warmBlockParse");
|
|
144
146
|
export const warmHighlighter = nativeBindings.warmHighlighter ?? missingNativeExport("warmHighlighter");
|
|
147
|
+
export const wasmGrammarFor = nativeBindings.wasmGrammarFor ?? missingNativeExport("wasmGrammarFor");
|
|
145
148
|
export const wrapTextWithAnsi = nativeBindings.wrapTextWithAnsi ?? missingNativeExport("wrapTextWithAnsi");
|
|
146
149
|
|
|
147
150
|
// string/numeric enums (napi-rs string_enum produces TS-only const enum)
|
package/native/loader-state.d.ts
CHANGED
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
export interface EmbeddedAddonFile {
|
|
2
2
|
variant: "modern" | "baseline" | "default";
|
|
3
|
+
/** Basename of the extracted `.node` file. */
|
|
3
4
|
filename: string;
|
|
4
|
-
size
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export interface EmbeddedAddonArchive {
|
|
9
|
-
format: "tar.gz";
|
|
10
|
-
filename: string;
|
|
11
|
-
filePath: string;
|
|
5
|
+
/** Decompressed addon size in bytes. */
|
|
6
|
+
size: number;
|
|
7
|
+
/** Embedded zstd frame holding the addon bytes. */
|
|
8
|
+
zstdPath: string;
|
|
12
9
|
}
|
|
13
10
|
|
|
14
11
|
export interface EmbeddedAddon {
|
|
15
12
|
platformTag: string;
|
|
16
13
|
version: string;
|
|
17
14
|
files: EmbeddedAddonFile[];
|
|
18
|
-
archive?: EmbeddedAddonArchive;
|
|
19
15
|
}
|
|
20
16
|
|
|
21
17
|
export interface DetectCompiledBinaryInput {
|
|
@@ -90,13 +86,13 @@ export function cleanupStaleNativeVersions(input: CleanupStaleNativeVersionsInpu
|
|
|
90
86
|
|
|
91
87
|
export function prepareNativeVersionDir(versionedDir: string): void;
|
|
92
88
|
|
|
93
|
-
export interface
|
|
94
|
-
archivePath: string;
|
|
89
|
+
export interface ExtractEmbeddedAddonsInput {
|
|
95
90
|
files: EmbeddedAddonFile[];
|
|
96
91
|
targetDir: string;
|
|
97
92
|
}
|
|
98
93
|
|
|
99
|
-
|
|
94
|
+
/** Decompress every manifest addon missing or wrong-sized in `targetDir`; returns the paths written. */
|
|
95
|
+
export function extractEmbeddedAddons(input: ExtractEmbeddedAddonsInput): string[];
|
|
100
96
|
|
|
101
97
|
export interface SelectCpuVariantInput {
|
|
102
98
|
arch: string;
|
package/native/loader-state.js
CHANGED
|
@@ -3,7 +3,6 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
-
import * as zlib from "node:zlib";
|
|
7
6
|
import packageJson from "../package.json" with { type: "json" };
|
|
8
7
|
import { embeddedAddon } from "./embedded-addon.js";
|
|
9
8
|
import { bindingsHaveReleaseIdentity, bindingsReleaseVersion, containsVersionStamp } from "./version-sentinel.js";
|
|
@@ -57,11 +56,19 @@ function startupMarker(text) {
|
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
function getNativesDir() {
|
|
59
|
+
// Match pi-utils directory overrides without depending on pi-utils.
|
|
60
|
+
const override = process.env.PI_NATIVES_DIR?.trim();
|
|
61
|
+
if (override) {
|
|
62
|
+
let dir = override;
|
|
63
|
+
if (dir === "~") dir = os.homedir();
|
|
64
|
+
else if (dir.startsWith("~/") || dir.startsWith("~\\")) dir = os.homedir() + dir.slice(1);
|
|
65
|
+
if (path.isAbsolute(dir)) return path.normalize(dir);
|
|
66
|
+
}
|
|
60
67
|
const xdgDataHome = process.env.XDG_DATA_HOME;
|
|
61
68
|
if (xdgDataHome && fs.existsSync(path.join(xdgDataHome, "omp"))) {
|
|
62
69
|
return path.join(xdgDataHome, "omp", "natives");
|
|
63
70
|
}
|
|
64
|
-
return path.join(os.homedir(), ".
|
|
71
|
+
return path.join(os.homedir(), ".zeta", "natives");
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
function resolveLeafPackageDir(platformTag) {
|
|
@@ -112,9 +119,9 @@ export function getAddonFilenames({ tag, arch, variant }) {
|
|
|
112
119
|
|
|
113
120
|
/**
|
|
114
121
|
* Decide whether the loader should mirror the package's `native/<filename>.node`
|
|
115
|
-
* into the per-version cache directory (`~/.
|
|
122
|
+
* into the per-version cache directory (`~/.zeta/natives/<version>/`) before loading.
|
|
116
123
|
*
|
|
117
|
-
* Windows-only safety net for `bun install -g` updates: when a previous `
|
|
124
|
+
* Windows-only safety net for `bun install -g` updates: when a previous `zetacode`
|
|
118
125
|
* process is running, bun cannot overwrite the locked `.node` inside
|
|
119
126
|
* `node_modules/@linxiraos/pi-natives/native/`, leaving an old binary next to a
|
|
120
127
|
* newer `index.js` and producing `<sym> is not a function` crashes on the next
|
|
@@ -442,36 +449,6 @@ function selectEmbeddedAddonFile(selectedVariant) {
|
|
|
442
449
|
return embeddedAddon.files.find(file => file.variant === "baseline") || null;
|
|
443
450
|
}
|
|
444
451
|
|
|
445
|
-
function readTarString(buffer, offset, length) {
|
|
446
|
-
const end = Math.min(offset + length, buffer.length);
|
|
447
|
-
let stringEnd = offset;
|
|
448
|
-
while (stringEnd < end && buffer[stringEnd] !== 0) stringEnd++;
|
|
449
|
-
return buffer.toString("utf8", offset, stringEnd);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function readTarOctal(buffer, offset, length) {
|
|
453
|
-
const value = readTarString(buffer, offset, length).trim();
|
|
454
|
-
if (!value) return 0;
|
|
455
|
-
const parsed = Number.parseInt(value, 8);
|
|
456
|
-
if (!Number.isFinite(parsed)) {
|
|
457
|
-
throw new Error(`Invalid tar octal value: ${value}`);
|
|
458
|
-
}
|
|
459
|
-
return parsed;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
function isZeroTarBlock(buffer, offset) {
|
|
463
|
-
for (let index = 0; index < 512; index++) {
|
|
464
|
-
if (buffer[offset + index] !== 0) return false;
|
|
465
|
-
}
|
|
466
|
-
return true;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
function getTarEntryName(header) {
|
|
470
|
-
const name = readTarString(header, 0, 100);
|
|
471
|
-
const prefix = readTarString(header, 345, 155);
|
|
472
|
-
return prefix ? `${prefix}/${name}` : name;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
452
|
function isSafeEmbeddedAddonFilename(filename) {
|
|
476
453
|
return filename.length > 0 && path.basename(filename) === filename && !filename.includes("/") && !filename.includes("\\");
|
|
477
454
|
}
|
|
@@ -480,7 +457,7 @@ function isEmbeddedAddonFileCurrent(targetPath, file) {
|
|
|
480
457
|
try {
|
|
481
458
|
const stat = fs.statSync(targetPath);
|
|
482
459
|
if (!stat.isFile()) return false;
|
|
483
|
-
return
|
|
460
|
+
return stat.size === file.size;
|
|
484
461
|
} catch (err) {
|
|
485
462
|
if (err && err.code === "ENOENT") return false;
|
|
486
463
|
throw err;
|
|
@@ -502,60 +479,23 @@ function writeEmbeddedAddonFile(targetPath, content) {
|
|
|
502
479
|
}
|
|
503
480
|
}
|
|
504
481
|
|
|
505
|
-
export function
|
|
506
|
-
const pending = new Map();
|
|
482
|
+
export function extractEmbeddedAddons({ files, targetDir }) {
|
|
507
483
|
for (const file of files) {
|
|
508
484
|
if (!isSafeEmbeddedAddonFilename(file.filename)) {
|
|
509
485
|
throw new Error(`Unsafe embedded addon filename: ${file.filename}`);
|
|
510
486
|
}
|
|
511
|
-
const targetPath = path.join(targetDir, file.filename);
|
|
512
|
-
if (!isEmbeddedAddonFileCurrent(targetPath, file)) {
|
|
513
|
-
pending.set(file.filename, file);
|
|
514
|
-
}
|
|
515
487
|
}
|
|
516
|
-
if (pending.size === 0) return [];
|
|
517
|
-
|
|
518
|
-
const archive = zlib.gunzipSync(fs.readFileSync(archivePath));
|
|
519
488
|
const writtenPaths = [];
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const size = readTarOctal(header, 124, 12);
|
|
527
|
-
const typeflag = header[156] === 0 ? "0" : String.fromCharCode(header[156]);
|
|
528
|
-
offset += 512;
|
|
529
|
-
|
|
530
|
-
if (offset + size > archive.length) {
|
|
531
|
-
throw new Error(`Truncated embedded addon archive entry: ${filename}`);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
if (!isSafeEmbeddedAddonFilename(filename)) {
|
|
535
|
-
throw new Error(`Unsafe embedded addon archive entry: ${filename}`);
|
|
536
|
-
}
|
|
537
|
-
if (typeflag !== "0") {
|
|
538
|
-
throw new Error(`Unsupported embedded addon archive entry type ${typeflag}: ${filename}`);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
const file = pending.get(filename);
|
|
542
|
-
if (file) {
|
|
543
|
-
if (typeof file.size === "number" && file.size !== size) {
|
|
544
|
-
throw new Error(`Embedded addon size mismatch for ${filename}: expected ${file.size}, got ${size}`);
|
|
545
|
-
}
|
|
546
|
-
const targetPath = path.join(targetDir, filename);
|
|
547
|
-
writeEmbeddedAddonFile(targetPath, archive.subarray(offset, offset + size));
|
|
548
|
-
pending.delete(filename);
|
|
549
|
-
writtenPaths.push(targetPath);
|
|
489
|
+
for (const file of files) {
|
|
490
|
+
const targetPath = path.join(targetDir, file.filename);
|
|
491
|
+
if (isEmbeddedAddonFileCurrent(targetPath, file)) continue;
|
|
492
|
+
const content = Bun.zstdDecompressSync(fs.readFileSync(file.zstdPath));
|
|
493
|
+
if (content.length !== file.size) {
|
|
494
|
+
throw new Error(`Embedded addon size mismatch for ${file.filename}: expected ${file.size}, got ${content.length}`);
|
|
550
495
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
if (pending.size > 0) {
|
|
556
|
-
throw new Error(`Embedded addon archive missing: ${[...pending.keys()].join(", ")}`);
|
|
496
|
+
writeEmbeddedAddonFile(targetPath, content);
|
|
497
|
+
writtenPaths.push(targetPath);
|
|
557
498
|
}
|
|
558
|
-
|
|
559
499
|
return writtenPaths;
|
|
560
500
|
}
|
|
561
501
|
|
|
@@ -576,40 +516,12 @@ function maybeExtractEmbeddedAddon(ctx, errors) {
|
|
|
576
516
|
return null;
|
|
577
517
|
}
|
|
578
518
|
|
|
579
|
-
if (embeddedAddon.archive) {
|
|
580
|
-
try {
|
|
581
|
-
extractEmbeddedAddonArchive({
|
|
582
|
-
archivePath: embeddedAddon.archive.filePath,
|
|
583
|
-
files: embeddedAddon.files,
|
|
584
|
-
targetDir: ctx.versionedDir,
|
|
585
|
-
});
|
|
586
|
-
if (isEmbeddedAddonFileCurrent(targetPath, selectedEmbeddedFile)) {
|
|
587
|
-
return targetPath;
|
|
588
|
-
}
|
|
589
|
-
errors.push(`embedded addon archive (${embeddedAddon.archive.filename}): missing ${selectedEmbeddedFile.filename}`);
|
|
590
|
-
return null;
|
|
591
|
-
} catch (err) {
|
|
592
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
593
|
-
errors.push(`embedded addon archive (${embeddedAddon.archive.filename}): ${message}`);
|
|
594
|
-
return null;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
if (isEmbeddedAddonFileCurrent(targetPath, selectedEmbeddedFile)) {
|
|
599
|
-
return targetPath;
|
|
600
|
-
}
|
|
601
|
-
if (!selectedEmbeddedFile.filePath) {
|
|
602
|
-
errors.push(`embedded addon metadata missing file path for ${selectedEmbeddedFile.filename}`);
|
|
603
|
-
return null;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
519
|
try {
|
|
607
|
-
|
|
608
|
-
fs.writeFileSync(targetPath, buffer);
|
|
520
|
+
extractEmbeddedAddons({ files: embeddedAddon.files, targetDir: ctx.versionedDir });
|
|
609
521
|
return targetPath;
|
|
610
522
|
} catch (err) {
|
|
611
523
|
const message = err instanceof Error ? err.message : String(err);
|
|
612
|
-
errors.push(`embedded addon
|
|
524
|
+
errors.push(`embedded addon extraction (${selectedEmbeddedFile.filename}): ${message}`);
|
|
613
525
|
return null;
|
|
614
526
|
}
|
|
615
527
|
}
|
|
@@ -824,6 +736,18 @@ function installNativeTokioRuntime(bindings) {
|
|
|
824
736
|
}
|
|
825
737
|
}
|
|
826
738
|
|
|
739
|
+
/**
|
|
740
|
+
* Point the addon at `<nativesDir>/grammars`, where the agent downloads the
|
|
741
|
+
* tree-sitter grammars that load as WebAssembly on demand (pi-utils
|
|
742
|
+
* `getNativeGrammarsDir()` resolves the same path). Version cleanup never
|
|
743
|
+
* touches it: only release-version directories are removed. Older addons
|
|
744
|
+
* predating the export link every grammar and need no directory.
|
|
745
|
+
*/
|
|
746
|
+
function configureGrammarDir(bindings, nativesDir) {
|
|
747
|
+
const setGrammarDir = bindings.__ompSetGrammarDir;
|
|
748
|
+
if (typeof setGrammarDir === "function") setGrammarDir(path.join(nativesDir, "grammars"));
|
|
749
|
+
}
|
|
750
|
+
|
|
827
751
|
|
|
828
752
|
function buildHelpMessage(ctx) {
|
|
829
753
|
if (ctx.isCompiledBinary) {
|
|
@@ -949,6 +873,7 @@ export function loadNative() {
|
|
|
949
873
|
const bindings = require_(candidate);
|
|
950
874
|
validateLoadedBindings(ctx, bindings, candidate);
|
|
951
875
|
installNativeTokioRuntime(bindings);
|
|
876
|
+
configureGrammarDir(bindings, ctx.nativesDir);
|
|
952
877
|
loadedAddon = describeLoadedAddon(bindings, candidate, ctx);
|
|
953
878
|
cleanupStaleNativeVersions({ nativesDir: ctx.nativesDir, currentVersion: ctx.packageVersion });
|
|
954
879
|
startupMarker("native:loadNative:done");
|
|
@@ -3,11 +3,7 @@
|
|
|
3
3
|
* pipeline, and the post-link stamp tool (`scripts/stamp-native-version.ts`).
|
|
4
4
|
*
|
|
5
5
|
* Kept in its own module so `scripts/embed-native.ts` can reuse them without
|
|
6
|
-
* importing `loader-state.js`
|
|
7
|
-
* `embedded-addon.js` and its `with { type: "file" }` archive import. That
|
|
8
|
-
* chain fails to resolve when the archive is missing, which would break
|
|
9
|
-
* `gen:native:reset` on an inconsistent tree (populated manifest, deleted
|
|
10
|
-
* archive) before it can restore the checked-in null stub.
|
|
6
|
+
* importing `loader-state.js` and the rest of the runtime loader.
|
|
11
7
|
*
|
|
12
8
|
* Current addons carry a fixed-size stamp slot (`VERSION_STAMP_MAGIC` + the
|
|
13
9
|
* release version + NUL padding) written after linking and reported by
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxiraos/pi-natives",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Native Rust bindings for PDF conversion, audio, WebRTC, grep, clipboard, image processing, syntax highlighting, PTY, and shell operations via N-API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://linxira-os.github.io/zeta/",
|
|
@@ -37,8 +37,6 @@
|
|
|
37
37
|
"test": "bun test --parallel",
|
|
38
38
|
"fix": "oxlint --fix --fix-suggestions . && bun run fmt",
|
|
39
39
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'",
|
|
40
|
-
"gen:native": "bun scripts/embed-native.ts",
|
|
41
|
-
"gen:native:reset": "bun scripts/embed-native.ts --reset",
|
|
42
40
|
"gen:npm": "bun scripts/gen-npm-packages.ts",
|
|
43
41
|
"bench": "bun bench/grep.ts",
|
|
44
42
|
"bench:text": "bun bench/text.ts"
|
|
@@ -111,11 +109,11 @@
|
|
|
111
109
|
}
|
|
112
110
|
},
|
|
113
111
|
"optionalDependencies": {
|
|
114
|
-
"@linxiraos/pi-natives-linux-x64": "1.
|
|
115
|
-
"@linxiraos/pi-natives-linux-arm64": "1.
|
|
116
|
-
"@linxiraos/pi-natives-darwin-x64": "1.
|
|
117
|
-
"@linxiraos/pi-natives-darwin-arm64": "1.
|
|
118
|
-
"@linxiraos/pi-natives-win32-x64": "1.
|
|
119
|
-
"@linxiraos/pi-natives-win32-arm64": "1.
|
|
112
|
+
"@linxiraos/pi-natives-linux-x64": "1.2.0",
|
|
113
|
+
"@linxiraos/pi-natives-linux-arm64": "1.2.0",
|
|
114
|
+
"@linxiraos/pi-natives-darwin-x64": "1.2.0",
|
|
115
|
+
"@linxiraos/pi-natives-darwin-arm64": "1.2.0",
|
|
116
|
+
"@linxiraos/pi-natives-win32-x64": "1.2.0",
|
|
117
|
+
"@linxiraos/pi-natives-win32-arm64": "1.2.0"
|
|
120
118
|
}
|
|
121
119
|
}
|