2026-03-13 12:21:09 -06:00
|
|
|
/**
|
|
|
|
|
* Native addon loader.
|
|
|
|
|
*
|
|
|
|
|
* Locates and loads the compiled Rust N-API addon (`.node` file).
|
2026-03-13 14:36:18 -06:00
|
|
|
* Resolution order:
|
2026-03-13 14:46:57 -06:00
|
|
|
* 1. @gsd-build/engine-{platform} npm optional dependency (production install)
|
2026-03-13 14:36:18 -06:00
|
|
|
* 2. native/addon/gsd_engine.{platform}.node (local release build)
|
|
|
|
|
* 3. native/addon/gsd_engine.dev.node (local debug build)
|
2026-03-13 12:21:09 -06:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import * as path from "node:path";
|
|
|
|
|
|
2026-03-30 15:51:57 -04:00
|
|
|
// __dirname and require are available in both execution contexts:
|
|
|
|
|
// - CJS (production build via tsc): provided natively by Node
|
|
|
|
|
// - ESM (CI test loader): injected by the dist-redirect.mjs preamble
|
|
|
|
|
const _dirname = __dirname;
|
|
|
|
|
const _require = require;
|
2026-03-13 12:21:09 -06:00
|
|
|
|
2026-03-30 15:51:57 -04:00
|
|
|
const addonDir = path.resolve(_dirname, "..", "..", "..", "native", "addon");
|
2026-03-13 12:21:09 -06:00
|
|
|
const platformTag = `${process.platform}-${process.arch}`;
|
|
|
|
|
|
2026-03-13 14:36:18 -06:00
|
|
|
/** Map Node.js platform/arch to the npm package suffix */
|
|
|
|
|
const platformPackageMap: Record<string, string> = {
|
|
|
|
|
"darwin-arm64": "darwin-arm64",
|
|
|
|
|
"darwin-x64": "darwin-x64",
|
|
|
|
|
"linux-x64": "linux-x64-gnu",
|
|
|
|
|
"linux-arm64": "linux-arm64-gnu",
|
|
|
|
|
"win32-x64": "win32-x64-msvc",
|
|
|
|
|
};
|
2026-03-13 12:21:09 -06:00
|
|
|
|
fix: graceful fallback when native addon is unavailable on unsupported platforms (#1225)
* fix: graceful fallback when native addon is unavailable on unsupported platforms
On platforms without a pre-built native binary (e.g., win32-arm64),
the native loader threw at import time, crashing the entire application.
Now returns a Proxy object that:
- Allows the import to succeed (no startup crash)
- Throws per-function when a native function is actually called
- Individual consumers (GSD parser bridge, fuzzy find, autocomplete)
already wrap native calls in try/catch and fall back to JS
implementations
Also exports nativeAvailable boolean for consumers that want to check
upfront. Prints a one-line warning to stderr on startup.
GSD is now fully functional on unsupported platforms — just slower for
file parsing, grep, and fuzzy search.
Fixes #1223
* fix: remove __nativeUnavailable from exported type to fix TS2352 cast errors
The __nativeUnavailable boolean property on the native type caused
TS2352 errors in consumers that cast native as Record<string, Function>
(ast/index.ts, diff/index.ts, gsd-parser/index.ts).
Replaced with a module-level _loadedSuccessfully flag and exported
nativeAvailable boolean. The proxy no longer needs a sentinel property.
2026-03-18 15:28:01 -04:00
|
|
|
let _loadedSuccessfully = false;
|
|
|
|
|
|
2026-03-13 12:21:09 -06:00
|
|
|
function loadNative(): Record<string, unknown> {
|
|
|
|
|
const errors: string[] = [];
|
|
|
|
|
|
2026-03-13 14:36:18 -06:00
|
|
|
// 1. Try the platform-specific npm optional dependency
|
|
|
|
|
const packageSuffix = platformPackageMap[platformTag];
|
|
|
|
|
if (packageSuffix) {
|
2026-03-13 12:21:09 -06:00
|
|
|
try {
|
2026-03-30 15:51:57 -04:00
|
|
|
_loadedSuccessfully = true; return _require(`@gsd-build/engine-${packageSuffix}`) as Record<string, unknown>;
|
2026-03-13 12:21:09 -06:00
|
|
|
} catch (err) {
|
|
|
|
|
const message = err instanceof Error ? err.message : String(err);
|
2026-03-13 14:46:57 -06:00
|
|
|
errors.push(`@gsd-build/engine-${packageSuffix}: ${message}`);
|
2026-03-13 12:21:09 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 14:36:18 -06:00
|
|
|
// 2. Try local release build (native/addon/gsd_engine.{platform}.node)
|
|
|
|
|
const releasePath = path.join(addonDir, `gsd_engine.${platformTag}.node`);
|
|
|
|
|
try {
|
2026-03-30 15:51:57 -04:00
|
|
|
_loadedSuccessfully = true; return _require(releasePath) as Record<string, unknown>;
|
2026-03-13 14:36:18 -06:00
|
|
|
} catch (err) {
|
|
|
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
|
|
|
errors.push(`${releasePath}: ${message}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. Try local dev build (native/addon/gsd_engine.dev.node)
|
|
|
|
|
const devPath = path.join(addonDir, "gsd_engine.dev.node");
|
|
|
|
|
try {
|
2026-03-30 15:51:57 -04:00
|
|
|
_loadedSuccessfully = true; return _require(devPath) as Record<string, unknown>;
|
2026-03-13 14:36:18 -06:00
|
|
|
} catch (err) {
|
|
|
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
|
|
|
errors.push(`${devPath}: ${message}`);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 12:21:09 -06:00
|
|
|
const details = errors.map((e) => ` - ${e}`).join("\n");
|
2026-03-13 14:36:18 -06:00
|
|
|
const supportedPlatforms = Object.keys(platformPackageMap);
|
fix: graceful fallback when native addon is unavailable on unsupported platforms (#1225)
* fix: graceful fallback when native addon is unavailable on unsupported platforms
On platforms without a pre-built native binary (e.g., win32-arm64),
the native loader threw at import time, crashing the entire application.
Now returns a Proxy object that:
- Allows the import to succeed (no startup crash)
- Throws per-function when a native function is actually called
- Individual consumers (GSD parser bridge, fuzzy find, autocomplete)
already wrap native calls in try/catch and fall back to JS
implementations
Also exports nativeAvailable boolean for consumers that want to check
upfront. Prints a one-line warning to stderr on startup.
GSD is now fully functional on unsupported platforms — just slower for
file parsing, grep, and fuzzy search.
Fixes #1223
* fix: remove __nativeUnavailable from exported type to fix TS2352 cast errors
The __nativeUnavailable boolean property on the native type caused
TS2352 errors in consumers that cast native as Record<string, Function>
(ast/index.ts, diff/index.ts, gsd-parser/index.ts).
Replaced with a module-level _loadedSuccessfully flag and exported
nativeAvailable boolean. The proxy no longer needs a sentinel property.
2026-03-18 15:28:01 -04:00
|
|
|
|
|
|
|
|
// Graceful fallback: on unsupported platforms (e.g., win32-arm64), return a
|
|
|
|
|
// proxy that throws on individual function calls rather than crashing the
|
|
|
|
|
// entire import chain at startup (#1223). Consumers with JS fallbacks
|
|
|
|
|
// (parseRoadmap, parsePlan, fuzzyFind, etc.) catch these and degrade gracefully.
|
|
|
|
|
process.stderr.write(
|
|
|
|
|
`[gsd] Native addon not available for ${platformTag}. Falling back to JS implementations (slower).\n` +
|
|
|
|
|
` Supported native platforms: ${supportedPlatforms.join(", ")}\n`,
|
2026-03-13 12:21:09 -06:00
|
|
|
);
|
fix: graceful fallback when native addon is unavailable on unsupported platforms (#1225)
* fix: graceful fallback when native addon is unavailable on unsupported platforms
On platforms without a pre-built native binary (e.g., win32-arm64),
the native loader threw at import time, crashing the entire application.
Now returns a Proxy object that:
- Allows the import to succeed (no startup crash)
- Throws per-function when a native function is actually called
- Individual consumers (GSD parser bridge, fuzzy find, autocomplete)
already wrap native calls in try/catch and fall back to JS
implementations
Also exports nativeAvailable boolean for consumers that want to check
upfront. Prints a one-line warning to stderr on startup.
GSD is now fully functional on unsupported platforms — just slower for
file parsing, grep, and fuzzy search.
Fixes #1223
* fix: remove __nativeUnavailable from exported type to fix TS2352 cast errors
The __nativeUnavailable boolean property on the native type caused
TS2352 errors in consumers that cast native as Record<string, Function>
(ast/index.ts, diff/index.ts, gsd-parser/index.ts).
Replaced with a module-level _loadedSuccessfully flag and exported
nativeAvailable boolean. The proxy no longer needs a sentinel property.
2026-03-18 15:28:01 -04:00
|
|
|
return new Proxy({} as Record<string, unknown>, {
|
|
|
|
|
get(_target, prop) {
|
|
|
|
|
return (..._args: unknown[]) => {
|
|
|
|
|
throw new Error(`Native function '${String(prop)}' is not available on ${platformTag}`);
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
});
|
2026-03-13 12:21:09 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const native = loadNative() as {
|
|
|
|
|
search: (content: Buffer | Uint8Array, options: unknown) => unknown;
|
|
|
|
|
grep: (options: unknown) => unknown;
|
2026-03-13 12:43:50 -06:00
|
|
|
killTree: (pid: number, signal: number) => number;
|
|
|
|
|
listDescendants: (pid: number) => number[];
|
|
|
|
|
processGroupId: (pid: number) => number | null;
|
|
|
|
|
killProcessGroup: (pgid: number, signal: number) => boolean;
|
feat: add native glob and fs_cache modules with gitignore-aware discovery (#226)
Port glob, glob_util, and fs_cache modules from Oh My Pi's pi-natives crate,
adapted for napi-rs v2. Provides gitignore-respecting filesystem discovery
with a TTL-based scan cache, mtime sorting, file-type filtering, and
node_modules exclusion.
Includes a task module for async N-API work scheduling with cooperative
cancellation (timeout-based), TypeScript type declarations and wrapper,
and 12 integration tests covering pattern matching, recursion, gitignore,
maxResults, sortByMtime, fileType filtering, and cache invalidation.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 12:45:56 -06:00
|
|
|
glob: (
|
|
|
|
|
options: unknown,
|
|
|
|
|
onMatch?: ((match: unknown) => void) | undefined | null,
|
|
|
|
|
) => Promise<unknown>;
|
|
|
|
|
invalidateFsScanCache: (path?: string) => void;
|
feat: add syntect-based syntax highlighting module to native engine (#227)
Port the highlight module from Oh My Pi's pi-natives crate. Provides
ANSI-colored syntax highlighting with scope-based semantic token matching
across 11 categories (comment, keyword, function, variable, string, number,
type, operator, punctuation, inserted, deleted).
Exposed N-API functions:
- highlightCode(code, lang, colors) -> ANSI-highlighted string
- supportsLanguage(lang) -> boolean
- getSupportedLanguages() -> string[]
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 12:47:02 -06:00
|
|
|
highlightCode: (code: string, lang: string | null, colors: unknown) => unknown;
|
|
|
|
|
supportsLanguage: (lang: string) => unknown;
|
|
|
|
|
getSupportedLanguages: () => unknown;
|
2026-03-13 12:48:27 -06:00
|
|
|
copyToClipboard: (text: string) => void;
|
|
|
|
|
readTextFromClipboard: () => string | null;
|
|
|
|
|
readImageFromClipboard: () => Promise<unknown>;
|
2026-03-13 13:01:41 -06:00
|
|
|
astGrep: (options: unknown) => unknown;
|
|
|
|
|
astEdit: (options: unknown) => unknown;
|
2026-03-13 12:40:22 -06:00
|
|
|
htmlToMarkdown: (html: string, options: unknown) => unknown;
|
2026-03-13 12:42:42 -06:00
|
|
|
wrapTextWithAnsi: (text: string, width: number, tabWidth?: number) => string[];
|
|
|
|
|
truncateToWidth: (
|
|
|
|
|
text: string,
|
|
|
|
|
maxWidth: number,
|
|
|
|
|
ellipsisKind: number,
|
|
|
|
|
pad: boolean,
|
|
|
|
|
tabWidth?: number,
|
|
|
|
|
) => string;
|
|
|
|
|
sliceWithWidth: (
|
|
|
|
|
line: string,
|
|
|
|
|
startCol: number,
|
|
|
|
|
length: number,
|
|
|
|
|
strict: boolean,
|
|
|
|
|
tabWidth?: number,
|
|
|
|
|
) => unknown;
|
|
|
|
|
extractSegments: (
|
|
|
|
|
line: string,
|
|
|
|
|
beforeEnd: number,
|
|
|
|
|
afterStart: number,
|
|
|
|
|
afterLen: number,
|
|
|
|
|
strictAfter: boolean,
|
|
|
|
|
tabWidth?: number,
|
|
|
|
|
) => unknown;
|
|
|
|
|
sanitizeText: (text: string) => string;
|
|
|
|
|
visibleWidth: (text: string, tabWidth?: number) => number;
|
2026-03-13 13:13:43 -06:00
|
|
|
fuzzyFind: (options: unknown) => unknown;
|
2026-03-13 14:00:39 -06:00
|
|
|
normalizeForFuzzyMatch: (text: string) => string;
|
|
|
|
|
fuzzyFindText: (content: string, oldText: string) => unknown;
|
|
|
|
|
generateDiff: (oldContent: string, newContent: string, contextLines?: number) => unknown;
|
feat: add native image module — decode, encode, and resize via Rust image crate
Port image processing from Oh My Pi's pi-natives crate, adapted for napi-rs v2.
Exposes NativeImage class with async parse/encode/resize methods backed by the
Rust `image` crate (PNG, JPEG, WebP, GIF support).
Includes:
- task.rs: lightweight async task scheduling for libuv thread pool
- image.rs: NativeImage class with SamplingFilter enum
- TypeScript types and wrapper (parseImage, ImageFormat, SamplingFilter)
- 8 passing tests covering decode, encode, resize, round-trip, error cases
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 12:51:49 -06:00
|
|
|
NativeImage: unknown;
|
2026-03-13 13:57:12 -06:00
|
|
|
ttsrCompileRules: (rules: unknown[]) => number;
|
|
|
|
|
ttsrCheckBuffer: (handle: number, buffer: string) => string[];
|
|
|
|
|
ttsrFreeRules: (handle: number) => void;
|
2026-03-13 16:34:17 -06:00
|
|
|
processStreamChunk: (chunk: Buffer, state?: unknown) => unknown;
|
|
|
|
|
stripAnsiNative: (text: string) => string;
|
|
|
|
|
sanitizeBinaryOutputNative: (text: string) => string;
|
2026-03-13 14:01:11 -06:00
|
|
|
parseFrontmatter: (content: string) => unknown;
|
|
|
|
|
extractSection: (content: string, heading: string, level?: number) => unknown;
|
|
|
|
|
extractAllSections: (content: string, level?: number) => string;
|
|
|
|
|
batchParseGsdFiles: (directory: string) => unknown;
|
|
|
|
|
parseRoadmapFile: (content: string) => unknown;
|
2026-03-13 16:48:49 -06:00
|
|
|
truncateTail: (text: string, maxBytes: number) => unknown;
|
|
|
|
|
truncateHead: (text: string, maxBytes: number) => unknown;
|
|
|
|
|
truncateOutput: (text: string, maxBytes: number, mode?: string) => unknown;
|
2026-03-13 16:21:58 -06:00
|
|
|
parseJson: (text: string) => unknown;
|
|
|
|
|
parsePartialJson: (text: string) => unknown;
|
|
|
|
|
parseStreamingJson: (text: string) => unknown;
|
2026-03-13 16:46:08 -06:00
|
|
|
xxHash32: (input: string, seed: number) => number;
|
2026-03-13 12:21:09 -06:00
|
|
|
};
|