@gajae-code/natives 0.18.10 → 0.18.12

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.
@@ -0,0 +1,5 @@
1
+ {
2
+ "schema": "gjc.diagnostic-artifact",
3
+ "version": "",
4
+ "artifacts": {}
5
+ }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schema": "gjc.diagnostic-artifact",
3
- "version": "0.18.10",
3
+ "version": "0.18.12",
4
4
  "artifacts": {
5
- "pi_natives.darwin-arm64.node": "729fb9c2ac67e67dfc9f2df710376b34dfa8683b47664aa2f600216e2198efb4",
6
- "pi_natives.darwin-x64-baseline.node": "0054eb5350bbf7f57c7e16925230698aaeb52d61943033a10fed4b55f81ce779",
7
- "pi_natives.linux-arm64.node": "5fd16f75027973ea9f5bff259ff17c99653518e362f3bc535248e14e7c89881f",
8
- "pi_natives.linux-x64-baseline.node": "e74e62b0906da9d7c26993abeedf880bd5434251b18b3735df86f65b7b2763f0",
9
- "pi_natives.linux-x64-modern.node": "73d5e3fdf9defd8e09731d64a81490c012061d8aa8dd81e55a7e0abba6e3fe22",
10
- "pi_natives.win32-x64-baseline.node": "b17314ce5ccf7be241e7d3c42476fe4b3dd789660d9da181d420a85f8116ed5e"
5
+ "pi_natives.darwin-arm64.node": "afc623540979280ece88b16c57c5a5941407a98bc0344f6fd851537d886dcfee",
6
+ "pi_natives.darwin-x64-baseline.node": "a1aee260794c49ed6ce8590f23ac89e899e20d93dae95900bb0b1503ff4ea159",
7
+ "pi_natives.linux-arm64.node": "2d695e63e7d1f89eff9a0809bb2f417cb65c91ae75161e5fd58a405d29689322",
8
+ "pi_natives.linux-x64-baseline.node": "7cada23a1841fe89670a80853ca8c35905f80783d8bb19a284a3f45821d9e56e",
9
+ "pi_natives.linux-x64-modern.node": "75e7f945bf27526f1d833f6c80490d9600fabc6700de70e5eb83eea6ef8cd8ea",
10
+ "pi_natives.win32-x64-baseline.node": "a88258561c1027a3d6e8477de335f903182f300091700cd3d6cfd96abc53ce95"
11
11
  }
12
12
  }
@@ -19,10 +19,12 @@
19
19
  * - proves the trusted namespace before reading: every component is owned by the
20
20
  * current user or by root, is not a symlink, and grants no group/other write,
21
21
  * so an account that owns none of the path cannot swap the artifact out;
22
- * - proves the bytes on a retained descriptor: SHA-256 equal to the digest this
23
- * package records in `diagnostic-artifact.json`, which is part of the module
24
- * graph so a compiled binary carries it too. An addon's own
25
- * `nativeBuildInfo()` is self-reported by code that has already executed, so
22
+ * - proves the bytes on a retained descriptor: SHA-256 equal to the shipped
23
+ * `diagnostic-artifact.json` digest, a compiled-binary digest captured from the
24
+ * verified embed input, or (in a source checkout only) the gitignored local
25
+ * manifest when adjacent build metadata explicitly identifies a local build.
26
+ * Each manifest belongs to the package and is checked before use. An addon's own
27
+ * `nativeBuildInfo()` is self-reported by code that has already been executed, so
26
28
  * it is only a consistency check afterwards, never the trust anchor;
27
29
  * - activates the verified bytes through that same retained descriptor
28
30
  * (`process.dlopen` on `/dev/fd/<fd>`), so a pathname replaced between
@@ -75,6 +77,8 @@ import * as fs from "node:fs";
75
77
  import { createRequire } from "node:module";
76
78
  import * as os from "node:os";
77
79
  import * as nodePath from "node:path";
80
+ import { fileURLToPath } from "node:url";
81
+ import compiledArtifacts from "./diagnostic-artifact-embedded.json" with { type: "json" };
78
82
  import trustedArtifacts from "./diagnostic-artifact.json" with { type: "json" };
79
83
  import packageManifest from "../package.json" with { type: "json" };
80
84
 
@@ -127,17 +131,126 @@ function cachedArtifactPath() {
127
131
  }
128
132
 
129
133
  /**
130
- * The package-owned expected digest for this artifact, or null when this package
131
- * ships no trusted record for it.
134
+ * The package-owned expected digest, or null when this package has no trusted
135
+ * record. Compiled binaries prefer the digest recorded at their embed boundary;
136
+ * source checkouts may use the gitignored local manifest for an explicitly local
137
+ * build. Installed packages use the shipped record.
132
138
  *
139
+ * @param {string} file
133
140
  * @returns {string | null}
134
141
  */
135
- function trustedDigest() {
142
+ function trustedDigest(file) {
143
+ const compiledDigest = compiledArtifactDigest(file);
144
+ if (compiledDigest !== undefined) return compiledDigest;
145
+ const localDigest = sourceLocalBuildDigest(file);
146
+ if (localDigest !== undefined) return localDigest;
136
147
  if (trustedArtifacts?.version !== packageManifest.version) return null;
137
148
  const digest = trustedArtifacts?.artifacts?.[ARTIFACT_BASENAME];
138
149
  return typeof digest === "string" && /^[0-9a-f]{64}$/.test(digest) ? digest : null;
139
150
  }
140
151
 
152
+ /**
153
+ * A compiled binary carries the digest of the exact addon verified before embedding.
154
+ * An empty stub falls back to the shipped record; a populated but stale or malformed
155
+ * compiled record fails closed.
156
+ *
157
+ * @param {string} file
158
+ * @returns {string | null | undefined} Undefined means no compiled digest is present.
159
+ */
160
+ function compiledArtifactDigest(file) {
161
+ if (!runningFromCompiledBinary() || compiledArtifacts?.version === "") return undefined;
162
+ if (compiledArtifacts?.schema !== "gjc.diagnostic-artifact" || compiledArtifacts?.version !== packageManifest.version) {
163
+ return null;
164
+ }
165
+ const digest = compiledArtifacts?.artifacts?.[nodePath.basename(file)];
166
+ if (digest === undefined) return undefined;
167
+ return typeof digest === "string" && /^[0-9a-f]{64}$/.test(digest) ? digest : null;
168
+ }
169
+
170
+ /**
171
+ * Read one bounded JSON file only after proving its path and retained descriptor are
172
+ * in the same trusted namespace used for the native artifact.
173
+ *
174
+ * @param {string} file
175
+ * @returns {Record<string, unknown> | null}
176
+ */
177
+ function readTrustedLocalJson(file) {
178
+ let fd = -1;
179
+ try {
180
+ if (!trustedNamespace(file)) return null;
181
+ fd = fs.openSync(file, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
182
+ const stat = fs.fstatSync(fd);
183
+ if (!stat.isFile() || stat.size === 0 || stat.size > 1 << 20) return null;
184
+ if ((stat.mode & 0o022) !== 0) return null;
185
+ if (stat.uid !== process.geteuid?.() && stat.uid !== 0) return null;
186
+ if (inspectAclOnDescriptor(fd) !== "absent") return null;
187
+ const bytes = Buffer.allocUnsafe(stat.size);
188
+ let offset = 0;
189
+ while (offset < bytes.length) {
190
+ const read = fs.readSync(fd, bytes, offset, bytes.length - offset, offset);
191
+ if (read === 0) return null;
192
+ offset += read;
193
+ }
194
+ if (fs.fstatSync(fd).size !== stat.size) return null;
195
+ const value = JSON.parse(bytes.toString("utf8"));
196
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
197
+ } catch {
198
+ return null;
199
+ } finally {
200
+ if (fd >= 0) closeDescriptor(fd);
201
+ }
202
+ }
203
+
204
+ /**
205
+ * An unversioned local build is accepted only when the package-owned record proves
206
+ * these exact bytes for the current package version. This is the migration path for
207
+ * builds produced before local sidecars carried their version.
208
+ *
209
+ * @param {unknown} record
210
+ * @param {string} version
211
+ * @param {string} artifact
212
+ * @param {string} digest
213
+ * @returns {boolean}
214
+ */
215
+ function trustedRecordHasDigest(record, version, artifact, digest) {
216
+ if (typeof record !== "object" || record === null || Array.isArray(record)) return false;
217
+ const candidate = /** @type {Record<string, unknown>} */ (record);
218
+ if (candidate.schema !== "gjc.diagnostic-artifact" || candidate.version !== version) return false;
219
+ const artifacts = candidate.artifacts;
220
+ if (typeof artifacts !== "object" || artifacts === null || Array.isArray(artifacts)) return false;
221
+ return /** @type {Record<string, unknown>} */ (artifacts)[artifact] === digest;
222
+ }
223
+
224
+ /**
225
+ * A source-mode local build has an explicit profile marker and a gitignored digest
226
+ * record. Never consult these files for a compiled binary, an installed platform
227
+ * package, or a release/CI addon.
228
+ *
229
+ * @param {string} file
230
+ * @returns {string | null | undefined} Undefined means this is not a local source build.
231
+ */
232
+ function sourceLocalBuildDigest(file) {
233
+ if (runningFromCompiledBinary()) return undefined;
234
+ const sourceAddon = nodePath.join(nodePath.dirname(fileURLToPath(import.meta.url)), ARTIFACT_BASENAME);
235
+ if (nodePath.resolve(file) !== sourceAddon) return undefined;
236
+ const buildMetadata = readTrustedLocalJson(`${sourceAddon}.build.json`);
237
+ if (buildMetadata?.profile !== "local") return undefined;
238
+
239
+ const localManifest = readTrustedLocalJson(
240
+ nodePath.join(nodePath.dirname(sourceAddon), "diagnostic-artifact.local.json"),
241
+ );
242
+ if (localManifest?.schema !== "gjc.diagnostic-artifact") return null;
243
+ if (localManifest.version !== packageManifest.version) return null;
244
+ const artifacts = localManifest.artifacts;
245
+ if (typeof artifacts !== "object" || artifacts === null || Array.isArray(artifacts)) return null;
246
+ const digest = /** @type {Record<string, unknown>} */ (artifacts)[ARTIFACT_BASENAME];
247
+ if (typeof digest !== "string" || !/^[0-9a-f]{64}$/.test(digest)) return null;
248
+ if (buildMetadata.version !== undefined) {
249
+ return buildMetadata.version === packageManifest.version ? digest : null;
250
+ }
251
+ return trustedRecordHasDigest(trustedArtifacts, packageManifest.version, ARTIFACT_BASENAME, digest) ? digest : null;
252
+ }
253
+
141
254
  /**
142
255
  * Select the single applicable layout. Exactly one distribution shape applies per
143
256
  * install, and a failure of the selected layout is never a reason to try another.
@@ -674,7 +787,7 @@ function consumeHandle(handle) {
674
787
  * @returns {{ ok: true, token: string } | { ok: false, reason: "unsupported" }}
675
788
  */
676
789
  export function verifyDiagnosticArtifact(file) {
677
- const expectedDigest = trustedDigest();
790
+ const expectedDigest = trustedDigest(file);
678
791
  if (expectedDigest === null) return unsupported;
679
792
  let fd = -1;
680
793
  try {
package/native/index.d.ts CHANGED
@@ -687,7 +687,7 @@ export declare function __piNativesPublishOutcomeV1(): void
687
687
  * `packages/natives/native/index.js` (which derives the name from
688
688
  * `package.json#version`).
689
689
  */
690
- export declare function __piNativesV0_18_10(): void
690
+ export declare function __piNativesV0_18_12(): void
691
691
 
692
692
  /**
693
693
  * Apply conservative pre-execution rewrites to a bash command.
@@ -1239,9 +1239,10 @@ export declare function exactReplacePath(sourcePath: string, destinationPath: st
1239
1239
  * single-link ownership; hard-linked or symlinked source/destination are
1240
1240
  * rejected) before anything is mutated. On Windows a pre-mutation
1241
1241
  * `STATUS_SHARING_VIOLATION` on the destination open is reported as a
1242
- * distinct `sharing_violation` code with `windows_error_code` set -- exactly
1243
- * as [`exact_replace_path`] already reports it -- and is always surfaced
1244
- * before any rename, so it is never mistaken for a post-effect failure.
1242
+ * distinct `sharing_violation` code, while `STATUS_ACCESS_DENIED` on a
1243
+ * retained open or rename is reported as `access_denied`. Pre-mutation
1244
+ * failures include the original `windows_error_code`; only known-safe rename
1245
+ * operations retry transient access-denied or sharing-violation statuses.
1245
1246
  */
1246
1247
  export declare function exactReplaceRetained(sourcePath: string, destinationPath: string, backupName: string, expectedSource: NativeExactFileIdentity, expectedDestination: NativeExactFileIdentity): NativeExactUnlinkResult
1247
1248
 
@@ -2578,16 +2579,22 @@ export interface PtyStartOptions {
2578
2579
  export declare function ptyTimeoutCount(): bigint
2579
2580
 
2580
2581
  /**
2581
- * Rasterize SVG/SVGZ bytes into a bounded PNG without resolving local files.
2582
+ * Rasterize bounded SVG/SVGZ bytes into a PNG without resolving local files.
2583
+ *
2584
+ * DTD/entity declarations and SVG image elements are rejected rather than
2585
+ * allowing unbounded expansion or silently returning an incomplete preview.
2582
2586
  *
2583
2587
  * Conversion runs on the native blocking pool so parsing and rendering do not
2584
2588
  * stall the JavaScript event loop.
2585
2589
  *
2590
+ * When both cell dimensions are provided, the PNG is padded on a transparent
2591
+ * canvas to whole terminal cells without embedding or decoding another image.
2592
+ *
2586
2593
  * # Errors
2587
- * Returns an error for invalid SVG data, zero/oversized limits, allocation
2588
- * failure, or PNG encoding failure.
2594
+ * Returns an error for invalid SVG data, mismatched/zero cell dimensions,
2595
+ * zero/oversized limits, allocation failure, or PNG encoding failure.
2589
2596
  */
2590
- export declare function rasterizeSvg(input: Uint8Array, maxWidthPx: number, maxHeightPx: number): Promise<Uint8Array>
2597
+ export declare function rasterizeSvg(input: Uint8Array, maxWidthPx: number, maxHeightPx: number, cellWidthPx?: number | undefined | null, cellHeightPx?: number | undefined | null): Promise<Uint8Array>
2591
2598
 
2592
2599
  /**
2593
2600
  * Read an image from the system clipboard.
package/native/index.js CHANGED
@@ -35,7 +35,7 @@ export const TtyWriter = nativeBindings.TtyWriter;
35
35
  // functions
36
36
  export const __gjcInstallTokioRuntime = nativeBindings.__gjcInstallTokioRuntime;
37
37
  export const __piNativesPublishOutcomeV1 = nativeBindings.__piNativesPublishOutcomeV1;
38
- export const __piNativesV0_18_10 = nativeBindings.__piNativesV0_18_10;
38
+ export const __piNativesV0_18_12 = nativeBindings.__piNativesV0_18_12;
39
39
  export const applyBashFixups = nativeBindings.applyBashFixups;
40
40
  export const applyOwnerOnlyFdSecurity = nativeBindings.applyOwnerOnlyFdSecurity;
41
41
  export const applyOwnerOnlyPathSecurity = nativeBindings.applyOwnerOnlyPathSecurity;
@@ -88,7 +88,9 @@ export function loadFromCandidates<T>(input: LoadFromCandidatesInput<T>): LoadFr
88
88
  export interface CachedEmbeddedExtractionIsFreshInput {
89
89
  targetPath: string;
90
90
  embeddedPath: string;
91
+ expectedDigest: string | null | undefined;
91
92
  sizeOf: (path: string) => number | null;
93
+ digestOf: (path: string) => string | null;
92
94
  }
93
95
 
94
96
  export function cachedEmbeddedExtractionIsFresh(input: CachedEmbeddedExtractionIsFreshInput): boolean;
@@ -136,6 +138,7 @@ export interface LoadNativeOptions {
136
138
  acquireStagedCandidateLease?: (candidate: string) => () => void;
137
139
  requireCandidate?: (candidate: string) => Record<string, unknown>;
138
140
  validateCandidate?: (bindings: Record<string, unknown>, candidate: string) => void;
141
+ loadNativeLibrary?: (runtime: string) => bigint | number | null;
139
142
  }
140
143
 
141
144
  export function loadNative(options?: LoadNativeOptions): Record<string, unknown>;
@@ -5,6 +5,7 @@ import * as fs from "node:fs";
5
5
  import { createRequire } from "node:module";
6
6
  import * as os from "node:os";
7
7
  import * as path from "node:path";
8
+ import embeddedDiagnosticArtifacts from "./diagnostic-artifact-embedded.json" with { type: "json" };
8
9
  import packageJson from "../package.json" with { type: "json" };
9
10
  import { embeddedAddon } from "./embedded-addon.js";
10
11
 
@@ -32,13 +33,14 @@ import { embeddedAddon } from "./embedded-addon.js";
32
33
  * post-build `--reset` stub) is the authoritative compiled-mode signal.
33
34
  */
34
35
 
35
- const SUPPORTED_PLATFORMS = ["linux-x64", "linux-arm64", "darwin-x64", "darwin-arm64", "win32-x64"];
36
+ const SUPPORTED_PLATFORMS = ["linux-x64", "linux-arm64", "darwin-x64", "darwin-arm64", "win32-x64", "win32-arm64"];
36
37
  const OPTIONAL_PACKAGE_BY_PLATFORM_TAG = {
37
38
  "darwin-arm64": "@gajae-code/natives-darwin-arm64",
38
39
  "darwin-x64": "@gajae-code/natives-darwin-x64",
39
40
  "linux-arm64": "@gajae-code/natives-linux-arm64",
40
41
  "linux-x64": "@gajae-code/natives-linux-x64",
41
42
  "win32-x64": "@gajae-code/natives-win32-x64",
43
+ "win32-arm64": "@gajae-code/natives-win32-arm64",
42
44
  };
43
45
  const WINDOWS_GENERIC_READ = 0x80000000;
44
46
  const WINDOWS_FILE_SHARE_READ = 0x00000001;
@@ -138,6 +140,32 @@ function validateStagedCandidate(ctx, candidate) {
138
140
  }
139
141
  }
140
142
 
143
+ function preloadSiblingNativeRuntime(ctx, candidate, loadLibrary) {
144
+ if (process.platform !== "win32") return;
145
+ // Staging copies only the .node into the versioned cache and drops the
146
+ // package directory from the candidate list. The DLL stays beside the
147
+ // recorded source addon, not beside that cache copy or bun.exe.
148
+ const sourcePath = ctx.stagedSourceSnapshots?.get(candidate)?.sourcePath;
149
+ const runtimes = [
150
+ typeof sourcePath === "string" ? path.join(path.dirname(sourcePath), "libunwind.dll") : null,
151
+ path.join(path.dirname(candidate), "libunwind.dll"),
152
+ path.join(path.dirname(process.execPath), "libunwind.dll"),
153
+ ];
154
+ const seen = new Set();
155
+ for (const runtime of runtimes) {
156
+ if (!runtime || seen.has(runtime) || !fs.existsSync(runtime)) continue;
157
+ seen.add(runtime);
158
+ const handle = loadLibrary(runtime);
159
+ if (handle !== 0 && handle !== 0n && handle != null) return;
160
+ }
161
+ }
162
+
163
+ function loadSiblingNativeLibrary(runtime) {
164
+ const widePath = new Uint16Array(runtime.length + 1);
165
+ for (let index = 0; index < runtime.length; index++) widePath[index] = runtime.charCodeAt(index);
166
+ return windowsKernel32Bindings().symbols.LoadLibraryW(ptr(widePath));
167
+ }
168
+
141
169
  function windowsKernel32Bindings() {
142
170
  if (windowsKernel32) return windowsKernel32;
143
171
  windowsKernel32 = dlopen("kernel32.dll", {
@@ -147,6 +175,7 @@ function windowsKernel32Bindings() {
147
175
  },
148
176
  CloseHandle: { args: ["i64"], returns: "u32" },
149
177
  GetLastError: { args: [], returns: "u32" },
178
+ LoadLibraryW: { args: ["ptr"], returns: "ptr" },
150
179
  });
151
180
  return windowsKernel32;
152
181
  }
@@ -368,18 +397,60 @@ export function loadFromCandidates({ candidates, requireCandidate, validateCandi
368
397
  /**
369
398
  * Decide whether a previously extracted embedded addon may be reused. A cached
370
399
  * extraction from an earlier build of the same version carries the same version
371
- * sentinel yet can expose a different native surface, so it is fresh only when
372
- * its byte size matches the embedded payload. `sizeOf` returns the byte size of
373
- * a path, or `null` when it cannot be inspected.
374
- * @param {{ targetPath: string; embeddedPath: string; sizeOf: (path: string) => number | null }} input
400
+ * sentinel and may have the same byte size, so freshness also requires its digest
401
+ * to match the package-owned embedded build record. `sizeOf` returns a path's
402
+ * byte size; `digestOf` returns a SHA-256 hex digest. Both return `null` when the
403
+ * path cannot be inspected.
404
+ * @param {{ targetPath: string; embeddedPath: string; expectedDigest: string | null | undefined; sizeOf: (path: string) => number | null; digestOf: (path: string) => string | null }} input
375
405
  * @returns {boolean}
376
406
  */
377
- export function cachedEmbeddedExtractionIsFresh({ targetPath, embeddedPath, sizeOf }) {
407
+ export function cachedEmbeddedExtractionIsFresh({ targetPath, embeddedPath, expectedDigest, sizeOf, digestOf }) {
408
+ if (typeof expectedDigest !== "string" || !/^[0-9a-f]{64}$/.test(expectedDigest)) return false;
378
409
  const cachedSize = sizeOf(targetPath);
379
410
  if (cachedSize === null) return false;
380
411
  const embeddedSize = sizeOf(embeddedPath);
381
412
  if (embeddedSize === null) return false;
382
- return cachedSize === embeddedSize;
413
+ if (cachedSize !== embeddedSize) return false;
414
+ return digestOf(targetPath) === expectedDigest;
415
+ }
416
+
417
+ function sha256File(file) {
418
+ let fd = -1;
419
+ try {
420
+ const noFollow = fs.constants.O_NOFOLLOW ?? 0;
421
+ fd = fs.openSync(file, fs.constants.O_RDONLY | noFollow);
422
+ const before = fs.fstatSync(fd);
423
+ if (!before.isFile()) return null;
424
+ const hash = createHash("sha256");
425
+ const buffer = Buffer.allocUnsafe(1 << 20);
426
+ let offset = 0;
427
+ while (offset < before.size) {
428
+ const read = fs.readSync(fd, buffer, 0, Math.min(buffer.length, before.size - offset), offset);
429
+ if (read === 0) return null;
430
+ hash.update(buffer.subarray(0, read));
431
+ offset += read;
432
+ }
433
+ const after = fs.fstatSync(fd);
434
+ if (
435
+ after.dev !== before.dev ||
436
+ after.ino !== before.ino ||
437
+ after.size !== before.size ||
438
+ after.mtimeMs !== before.mtimeMs
439
+ ) {
440
+ return null;
441
+ }
442
+ return hash.digest("hex");
443
+ } catch {
444
+ return null;
445
+ } finally {
446
+ if (fd >= 0) {
447
+ try {
448
+ fs.closeSync(fd);
449
+ } catch {
450
+ // A failed close cannot make an unverified digest admissible.
451
+ }
452
+ }
453
+ }
383
454
  }
384
455
 
385
456
  // =========================================================================
@@ -549,12 +620,16 @@ function maybeExtractEmbeddedAddons(ctx, errors) {
549
620
  const extracted = [];
550
621
  for (const embeddedFile of embeddedAddonCandidates(ctx.selectedVariant)) {
551
622
  const targetPath = path.join(ctx.versionedDir, embeddedFile.filename);
623
+ const expectedDigest =
624
+ embeddedDiagnosticArtifacts?.schema === "gjc.diagnostic-artifact" &&
625
+ embeddedDiagnosticArtifacts?.version === embeddedAddon.version
626
+ ? embeddedDiagnosticArtifacts?.artifacts?.[embeddedFile.filename]
627
+ : undefined;
552
628
  if (fs.existsSync(targetPath)) {
553
629
  // Guard against intra-version drift: a cached extraction written by an earlier
554
- // build of the same version carries the same version sentinel but can expose a
555
- // different native surface (e.g. a symbol added mid-cycle). The embedded addon
556
- // is the source of truth, so reuse the cached file only when it matches the
557
- // embedded payload size and re-extract otherwise.
630
+ // build of the same version carries the same version sentinel and may even have
631
+ // the same byte size. The embedded digest is the source of truth, so reuse the
632
+ // cached file only when its bytes match that exact build.
558
633
  const sizeOf = candidate => {
559
634
  try {
560
635
  return fs.statSync(candidate).size;
@@ -562,7 +637,15 @@ function maybeExtractEmbeddedAddons(ctx, errors) {
562
637
  return null;
563
638
  }
564
639
  };
565
- if (cachedEmbeddedExtractionIsFresh({ targetPath, embeddedPath: embeddedFile.filePath, sizeOf })) {
640
+ if (
641
+ cachedEmbeddedExtractionIsFresh({
642
+ targetPath,
643
+ embeddedPath: embeddedFile.filePath,
644
+ expectedDigest,
645
+ sizeOf,
646
+ digestOf: sha256File,
647
+ })
648
+ ) {
566
649
  extracted.push(targetPath);
567
650
  continue;
568
651
  }
@@ -910,6 +993,7 @@ export function loadNative(options = {}) {
910
993
  }
911
994
  try {
912
995
  validateStagedCandidate(ctx, candidate);
996
+ preloadSiblingNativeRuntime(ctx, candidate, options.loadNativeLibrary ?? loadSiblingNativeLibrary);
913
997
  const bindings = options.requireCandidate ? options.requireCandidate(candidate) : require_(candidate);
914
998
  if (typeof bindings.__gjcInstallTokioRuntime === "function") {
915
999
  bindings.__gjcInstallTokioRuntime();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gajae-code/natives",
3
- "version": "0.18.10",
3
+ "version": "0.18.12",
4
4
  "description": "Native Rust bindings for grep, clipboard, image processing, syntax highlighting, PTY, and shell operations via N-API",
5
5
  "type": "module",
6
6
  "homepage": "https://gajae-code.com",
@@ -31,7 +31,7 @@
31
31
  "scripts": {
32
32
  "build": "bun scripts/build-native.ts",
33
33
  "check": "biome check . && bun run check:types",
34
- "check:types": "tsc -p tsconfig.json --noEmit",
34
+ "check:types": "bun ../coding-agent/scripts/generate-docs-index.ts && tsc -p tsconfig.json --noEmit",
35
35
  "lint": "biome lint .",
36
36
  "test": "bun test",
37
37
  "fix": "biome check --write --unsafe .",
@@ -61,15 +61,16 @@
61
61
  "native/diagnostic-loader.d.ts",
62
62
  "native/diagnostic-loader.js",
63
63
  "native/diagnostic-artifact.json",
64
+ "native/diagnostic-artifact-embedded.json",
64
65
  "native/embedded-addon.js",
65
66
  "README.md"
66
67
  ],
67
68
  "optionalDependencies": {
68
- "@gajae-code/natives-darwin-arm64": "0.18.10",
69
- "@gajae-code/natives-darwin-x64": "0.18.10",
70
- "@gajae-code/natives-linux-arm64": "0.18.10",
71
- "@gajae-code/natives-linux-x64": "0.18.10",
72
- "@gajae-code/natives-win32-x64": "0.18.10"
69
+ "@gajae-code/natives-darwin-arm64": "0.18.12",
70
+ "@gajae-code/natives-darwin-x64": "0.18.12",
71
+ "@gajae-code/natives-linux-arm64": "0.18.12",
72
+ "@gajae-code/natives-linux-x64": "0.18.12",
73
+ "@gajae-code/natives-win32-x64": "0.18.12"
73
74
  },
74
75
  "exports": {
75
76
  ".": {