macroforge 0.1.78 → 0.1.80

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 CHANGED
@@ -5,9 +5,13 @@ TypeScript macro expansion engine - write compile-time macros in Rust
5
5
  [![Crates.io](https://img.shields.io/crates/v/macroforge_ts.svg)](https://crates.io/crates/macroforge_ts)
6
6
  [![Documentation](https://docs.rs/macroforge_ts/badge.svg)](https://docs.rs/macroforge_ts)
7
7
 
8
+ ## Overview
9
+
8
10
  This crate provides a TypeScript macro expansion engine that brings Rust-like derive macros to
9
- TypeScript. It is designed to be used via NAPI bindings from Node.js, enabling compile-time code
10
- generation for TypeScript projects.
11
+ TypeScript. It supports multiple output targets via feature flags:
12
+
13
+ - `wasm`: (Default) Universal WebAssembly module via wasm-bindgen for browser and edge environments.
14
+ - `node`: Optional native Node.js bindings via NAPI-RS.
11
15
 
12
16
  ## Overview
13
17
 
@@ -19,31 +23,30 @@ concrete implementations. For example, a class decorated with `@derive(Debug, Cl
19
23
 
20
24
  The crate is organized into several key components:
21
25
 
22
- - **NAPI Bindings** (`NativePlugin`, `expand_sync`, `transform_sync`): Entry points for Node.js
23
- - **Position Mapping** (`NativePositionMapper`, `NativeMapper`): Bidirectional source mapping for
24
- IDE integration
25
- - **Macro Host** (`host` module): Core expansion engine with registry and dispatcher
26
- - **Built-in Macros** (`builtin` module): Standard derive macros (Debug, Clone, Serialize, etc.)
27
-
28
- ## Performance Considerations
26
+ - **Unified API** (`api` module): An output-agnostic trait-based interface (`MacroforgeApi`) that
27
+ defines all macro operations.
28
+ - **Target Bindings**:
29
+ - `bindings_napi`: Node.js specific entry points using NAPI-RS.
30
+ - `bindings_wasm`: Universal entry points using `wasm-bindgen`.
31
+ - **Position Mapping** (`api_types::SourceMappingResult`): Bidirectional source mapping for IDE
32
+ integration.
33
+ - **Macro Host** (`host` module): Core expansion engine with registry and dispatcher.
34
+ - **Built-in Macros** (`builtin` module): Standard derive macros (Debug, Clone, Serialize, etc.).
29
35
 
30
- - Uses a 32MB thread stack to prevent stack overflow during deep SWC AST recursion
31
- - Implements early bailout for files without `@derive` decorators
32
- - Caches expansion results keyed by filepath and version
33
- - Uses binary search for O(log n) position mapping lookups
36
+ ## Usage
34
37
 
35
- ## Usage from Node.js
38
+ ### From Node.js
36
39
 
37
40
  ```javascript
38
- const { NativePlugin, expand_sync } = require('macroforge-ts');
39
-
40
- // Create a plugin instance with caching
41
- const plugin = new NativePlugin();
41
+ const { expandSync } = require('macroforge');
42
+ const result = expandSync(code, filepath, { keep_decorators: false });
43
+ ```
42
44
 
43
- // Process a file (uses cache if version matches)
44
- const result = plugin.process_file(filepath, code, { version: '1.0' });
45
+ ### From WASM
45
46
 
46
- // Or use the sync function directly
47
+ ```javascript
48
+ import init, { expand_sync } from './pkg/macroforge_ts.js';
49
+ await init();
47
50
  const result = expand_sync(code, filepath, { keep_decorators: false });
48
51
  ```
49
52
 
@@ -53,7 +56,7 @@ This crate re-exports several dependencies for convenience when writing custom m
53
56
 
54
57
  - `ts_syn`: TypeScript syntax types for AST manipulation
55
58
  - `macros`: Macro attributes and quote templates
56
- - `swc_core`, `swc_common`, `swc_ecma_ast`: SWC compiler infrastructure
59
+ - `swc_core`, `swc_common`, `swc_ecma_ast`: SWC compatibility infrastructure
57
60
 
58
61
  ## Installation
59
62
 
@@ -61,41 +64,15 @@ Add this to your `Cargo.toml`:
61
64
 
62
65
  ```toml
63
66
  [dependencies]
64
- macroforge_ts = "0.1.38"
67
+ macroforge_ts = "0.1.79"
65
68
  ```
66
69
 
67
70
  ## Key Exports
68
71
 
69
- ### Structs
70
-
71
- - **`TransformResult`** - Result of transforming TypeScript code through the macro system.
72
- - **`MacroDiagnostic`** - A diagnostic message produced during macro expansion.
73
- - **`MappingSegmentResult`** - A segment mapping a range in the original source to a range in the
74
- expanded source.
75
- - **`GeneratedRegionResult`** - A region in the expanded source that was generated by a macro.
76
- - **`SourceMappingResult`** - Complete source mapping information for a macro expansion.
77
- - **`ExpandResult`** - Result of expanding macros in TypeScript source code.
78
- - **`ImportSourceResult`** - Information about an imported identifier from a TypeScript module.
79
- - **`SyntaxCheckResult`** - Result of checking TypeScript syntax validity.
80
- - **`SpanResult`** - A span (range) in source code, represented as start position and length.
81
- - **`JsDiagnostic`** - A diagnostic from the TypeScript/JavaScript compiler or IDE.
82
- - ... and 8 more
83
-
84
72
  ### Functions
85
73
 
86
- - **`unchanged`** - Creates a no-op result with the original code unchanged.
87
- - **`new`** - Creates a new position mapper from source mapping data.
88
- - **`is_empty`** - Checks if this mapper has no mapping data.
89
- - **`original_to_expanded`** - Converts a position in the original source to the corresponding
90
- position in expanded source.
91
- - **`expanded_to_original`** - Converts a position in the expanded source back to the original
92
- source position.
93
- - **`generated_by`** - Returns the name of the macro that generated code at the given position.
94
- - **`map_span_to_original`** - Maps a span (start + length) from expanded source to original source.
95
- - **`map_span_to_expanded`** - Maps a span (start + length) from original source to expanded source.
96
- - **`is_in_generated`** - Checks if a position is inside macro-generated code.
97
- - **`new`** - Creates a new mapper wrapping the given source mapping.
98
- - ... and 23 more
74
+ - **`__macroforge_ffi_free`** - Free a buffer allocated by an FFI function.
75
+ - **`__macroforge_ffi_get_manifest`** - Returns the full MacroManifest as JSON via FFI.
99
76
 
100
77
  ## API Reference
101
78
 
@@ -0,0 +1,107 @@
1
+ /**
2
+ * # Macroforge Buildtime Module
3
+ *
4
+ * Compile-time JavaScript evaluation — the Zig-comptime primitive for
5
+ * TypeScript. Annotate a top-level `const` or `function` declaration with
6
+ * `/** @buildtime *\/` and the macroforge build pass evaluates it in a
7
+ * sandboxed JS context, serializes the result, and splices a plain TS
8
+ * literal back into the module.
9
+ *
10
+ * ```ts
11
+ * /** @buildtime *\/
12
+ * const BUILT_AT = buildtime.time.iso();
13
+ *
14
+ * /** @buildtime *\/
15
+ * const SCHEMA = buildtime.fs.readJson("./schema.json");
16
+ *
17
+ * /** @buildtime *\/
18
+ * function generateValidators() {
19
+ * const schema = buildtime.fs.readJson("./schema.json");
20
+ * return schema.fields.map(f =>
21
+ * `export function is_${f.name}(v: unknown): v is ${f.type} {
22
+ * return typeof v === "${f.jsType}";
23
+ * }`
24
+ * ).join("\n");
25
+ * }
26
+ * ```
27
+ *
28
+ * ## Runtime behavior
29
+ *
30
+ * Every export from this module is a sentinel. Calling any of them at
31
+ * runtime throws — they should have been resolved by the macroforge
32
+ * build pass and no longer exist in the output. If you see the runtime
33
+ * error, the build pass is not running on this file.
34
+ *
35
+ * @module macroforge/buildtime
36
+ */
37
+ export interface BuildtimeFs {
38
+ /** Read a file as UTF-8 text. Path is resolved relative to the file the
39
+ * @buildtime declaration lives in. Throws if the path is not in the
40
+ * `buildtime.capabilities.filesystem.read` allowlist. */
41
+ readText(path: string): string;
42
+ /** Read a file and parse its content as JSON. Same capability rules
43
+ * as `readText`. */
44
+ readJson(path: string): unknown;
45
+ /** Return whether the path exists on disk. Counts as a read for
46
+ * dependency tracking — if the file appears later, the cache
47
+ * invalidates. */
48
+ exists(path: string): boolean;
49
+ /** Return the names of the entries in a directory, sorted. Counts as
50
+ * a read. */
51
+ listDir(path: string): string[];
52
+ }
53
+ export interface BuildtimeCrypto {
54
+ /** SHA-256 of the input, lowercase hex. Pure — always allowed. */
55
+ sha256(input: string): string;
56
+ /** SHA-512 of the input, lowercase hex. Pure — always allowed. */
57
+ sha512(input: string): string;
58
+ }
59
+ export interface BuildtimeTime {
60
+ /** Current wall-clock time as an ISO 8601 string. Makes builds
61
+ * non-deterministic — prefer recording a fixed timestamp if
62
+ * determinism matters. */
63
+ now(): string;
64
+ /** Current wall-clock time in unix seconds. */
65
+ unix(): number;
66
+ /** Alias for {@link BuildtimeTime.now}. */
67
+ iso(): string;
68
+ }
69
+ export interface BuildtimeFlags {
70
+ /** True if the named flag was passed to the build (e.g. from a
71
+ * `--define` argument or from `config.buildtime.flags`). */
72
+ has(flag: string): boolean;
73
+ /** Value of the named flag, or undefined if not set. */
74
+ get(flag: string): string | undefined;
75
+ }
76
+ export interface BuildtimeLocation {
77
+ /** Absolute path of the source file the @buildtime declaration
78
+ * lives in. */
79
+ readonly file: string;
80
+ /** 1-based line number of the `/** @buildtime *\/` annotation. */
81
+ readonly line: number;
82
+ /** 1-based column number of the annotation. */
83
+ readonly column: number;
84
+ }
85
+ export interface Buildtime {
86
+ readonly fs: BuildtimeFs;
87
+ readonly crypto: BuildtimeCrypto;
88
+ readonly time: BuildtimeTime;
89
+ /** Environment variables the build was allowed to read. Populated
90
+ * from `buildtime.capabilities.env` in macroforge.config.js. Reads
91
+ * of variables not in the allowlist return `undefined`. */
92
+ readonly env: Record<string, string | undefined>;
93
+ readonly flags: BuildtimeFlags;
94
+ readonly location: BuildtimeLocation;
95
+ }
96
+ /**
97
+ * The compile-time API. Inside a `@buildtime` declaration, calls
98
+ * against this object are routed to native implementations. At
99
+ * runtime, every access throws — see module docs for why.
100
+ */
101
+ export declare const buildtime: Buildtime;
102
+ /**
103
+ * Re-export with the name `$buildtime` for users who prefer the macroforge
104
+ * convention of prefixing compile-time identifiers with `$`. Points at the
105
+ * same object.
106
+ */
107
+ export declare const $buildtime: Buildtime;
@@ -0,0 +1,65 @@
1
+ // js/buildtime/index.ts
2
+ var runtimeError = (method) => new Error(
3
+ `macroforge/buildtime.${method}: @buildtime APIs are only available inside @buildtime declarations evaluated by macroforge. If you're seeing this at runtime, the macroforge plugin is not installed or not running on this file.`
4
+ );
5
+ var buildtime = {
6
+ fs: {
7
+ readText(_path) {
8
+ throw runtimeError("fs.readText");
9
+ },
10
+ readJson(_path) {
11
+ throw runtimeError("fs.readJson");
12
+ },
13
+ exists(_path) {
14
+ throw runtimeError("fs.exists");
15
+ },
16
+ listDir(_path) {
17
+ throw runtimeError("fs.listDir");
18
+ }
19
+ },
20
+ crypto: {
21
+ sha256(_input) {
22
+ throw runtimeError("crypto.sha256");
23
+ },
24
+ sha512(_input) {
25
+ throw runtimeError("crypto.sha512");
26
+ }
27
+ },
28
+ time: {
29
+ now() {
30
+ throw runtimeError("time.now");
31
+ },
32
+ unix() {
33
+ throw runtimeError("time.unix");
34
+ },
35
+ iso() {
36
+ throw runtimeError("time.iso");
37
+ }
38
+ },
39
+ env: new Proxy(
40
+ {},
41
+ {
42
+ get(_target, _prop) {
43
+ throw runtimeError("env");
44
+ }
45
+ }
46
+ ),
47
+ flags: {
48
+ has(_flag) {
49
+ throw runtimeError("flags.has");
50
+ },
51
+ get(_flag) {
52
+ throw runtimeError("flags.get");
53
+ }
54
+ },
55
+ location: new Proxy({}, {
56
+ get(_target, _prop) {
57
+ throw runtimeError("location");
58
+ }
59
+ })
60
+ };
61
+ var $buildtime = buildtime;
62
+ export {
63
+ $buildtime,
64
+ buildtime
65
+ };
@@ -0,0 +1,176 @@
1
+ /**
2
+ * # Macroforge Buildtime Module
3
+ *
4
+ * Compile-time JavaScript evaluation — the Zig-comptime primitive for
5
+ * TypeScript. Annotate a top-level `const` or `function` declaration with
6
+ * `/** @buildtime *\/` and the macroforge build pass evaluates it in a
7
+ * sandboxed JS context, serializes the result, and splices a plain TS
8
+ * literal back into the module.
9
+ *
10
+ * ```ts
11
+ * /** @buildtime *\/
12
+ * const BUILT_AT = buildtime.time.iso();
13
+ *
14
+ * /** @buildtime *\/
15
+ * const SCHEMA = buildtime.fs.readJson("./schema.json");
16
+ *
17
+ * /** @buildtime *\/
18
+ * function generateValidators() {
19
+ * const schema = buildtime.fs.readJson("./schema.json");
20
+ * return schema.fields.map(f =>
21
+ * `export function is_${f.name}(v: unknown): v is ${f.type} {
22
+ * return typeof v === "${f.jsType}";
23
+ * }`
24
+ * ).join("\n");
25
+ * }
26
+ * ```
27
+ *
28
+ * ## Runtime behavior
29
+ *
30
+ * Every export from this module is a sentinel. Calling any of them at
31
+ * runtime throws — they should have been resolved by the macroforge
32
+ * build pass and no longer exist in the output. If you see the runtime
33
+ * error, the build pass is not running on this file.
34
+ *
35
+ * @module macroforge/buildtime
36
+ */
37
+
38
+ const runtimeError = (method: string) =>
39
+ new Error(
40
+ `macroforge/buildtime.${method}: @buildtime APIs are only available inside @buildtime declarations evaluated by macroforge. ` +
41
+ `If you're seeing this at runtime, the macroforge plugin is not installed or not running on this file.`,
42
+ );
43
+
44
+ export interface BuildtimeFs {
45
+ /** Read a file as UTF-8 text. Path is resolved relative to the file the
46
+ * @buildtime declaration lives in. Throws if the path is not in the
47
+ * `buildtime.capabilities.filesystem.read` allowlist. */
48
+ readText(path: string): string;
49
+ /** Read a file and parse its content as JSON. Same capability rules
50
+ * as `readText`. */
51
+ readJson(path: string): unknown;
52
+ /** Return whether the path exists on disk. Counts as a read for
53
+ * dependency tracking — if the file appears later, the cache
54
+ * invalidates. */
55
+ exists(path: string): boolean;
56
+ /** Return the names of the entries in a directory, sorted. Counts as
57
+ * a read. */
58
+ listDir(path: string): string[];
59
+ }
60
+
61
+ export interface BuildtimeCrypto {
62
+ /** SHA-256 of the input, lowercase hex. Pure — always allowed. */
63
+ sha256(input: string): string;
64
+ /** SHA-512 of the input, lowercase hex. Pure — always allowed. */
65
+ sha512(input: string): string;
66
+ }
67
+
68
+ export interface BuildtimeTime {
69
+ /** Current wall-clock time as an ISO 8601 string. Makes builds
70
+ * non-deterministic — prefer recording a fixed timestamp if
71
+ * determinism matters. */
72
+ now(): string;
73
+ /** Current wall-clock time in unix seconds. */
74
+ unix(): number;
75
+ /** Alias for {@link BuildtimeTime.now}. */
76
+ iso(): string;
77
+ }
78
+
79
+ export interface BuildtimeFlags {
80
+ /** True if the named flag was passed to the build (e.g. from a
81
+ * `--define` argument or from `config.buildtime.flags`). */
82
+ has(flag: string): boolean;
83
+ /** Value of the named flag, or undefined if not set. */
84
+ get(flag: string): string | undefined;
85
+ }
86
+
87
+ export interface BuildtimeLocation {
88
+ /** Absolute path of the source file the @buildtime declaration
89
+ * lives in. */
90
+ readonly file: string;
91
+ /** 1-based line number of the `/** @buildtime *\/` annotation. */
92
+ readonly line: number;
93
+ /** 1-based column number of the annotation. */
94
+ readonly column: number;
95
+ }
96
+
97
+ export interface Buildtime {
98
+ readonly fs: BuildtimeFs;
99
+ readonly crypto: BuildtimeCrypto;
100
+ readonly time: BuildtimeTime;
101
+ /** Environment variables the build was allowed to read. Populated
102
+ * from `buildtime.capabilities.env` in macroforge.config.js. Reads
103
+ * of variables not in the allowlist return `undefined`. */
104
+ readonly env: Record<string, string | undefined>;
105
+ readonly flags: BuildtimeFlags;
106
+ readonly location: BuildtimeLocation;
107
+ }
108
+
109
+ /**
110
+ * The compile-time API. Inside a `@buildtime` declaration, calls
111
+ * against this object are routed to native implementations. At
112
+ * runtime, every access throws — see module docs for why.
113
+ */
114
+ export const buildtime: Buildtime = {
115
+ fs: {
116
+ readText(_path: string): string {
117
+ throw runtimeError("fs.readText");
118
+ },
119
+ readJson(_path: string): unknown {
120
+ throw runtimeError("fs.readJson");
121
+ },
122
+ exists(_path: string): boolean {
123
+ throw runtimeError("fs.exists");
124
+ },
125
+ listDir(_path: string): string[] {
126
+ throw runtimeError("fs.listDir");
127
+ },
128
+ },
129
+ crypto: {
130
+ sha256(_input: string): string {
131
+ throw runtimeError("crypto.sha256");
132
+ },
133
+ sha512(_input: string): string {
134
+ throw runtimeError("crypto.sha512");
135
+ },
136
+ },
137
+ time: {
138
+ now(): string {
139
+ throw runtimeError("time.now");
140
+ },
141
+ unix(): number {
142
+ throw runtimeError("time.unix");
143
+ },
144
+ iso(): string {
145
+ throw runtimeError("time.iso");
146
+ },
147
+ },
148
+ env: new Proxy(
149
+ {},
150
+ {
151
+ get(_target, _prop): never {
152
+ throw runtimeError("env");
153
+ },
154
+ },
155
+ ),
156
+ flags: {
157
+ has(_flag: string): boolean {
158
+ throw runtimeError("flags.has");
159
+ },
160
+ get(_flag: string): string | undefined {
161
+ throw runtimeError("flags.get");
162
+ },
163
+ },
164
+ location: new Proxy({} as BuildtimeLocation, {
165
+ get(_target, _prop): never {
166
+ throw runtimeError("location");
167
+ },
168
+ }),
169
+ };
170
+
171
+ /**
172
+ * Re-export with the name `$buildtime` for users who prefer the macroforge
173
+ * convention of prefixing compile-time identifiers with `$`. Points at the
174
+ * same object.
175
+ */
176
+ export const $buildtime = buildtime;
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Svelte-check wrapper — spawned by the CLI's `run_svelte_check_wrapper`.
3
+ *
4
+ * Patches `ts.sys.readFile` to expand macros before svelte-check sees the files.
5
+ * Since svelte-check uses TypeScript as a peer dependency and Node.js caches
6
+ * modules, the patched `ts.sys.readFile` is shared with svelte-check's internal
7
+ * TypeScript language service.
8
+ *
9
+ * Arguments:
10
+ * argv[2..] — forwarded to svelte-check CLI
11
+ *
12
+ * Environment:
13
+ * MACROFORGE_TYPE_REGISTRY_PATH — path to pre-built type registry JSON
14
+ */
15
+ const { createRequire } = require("module");
16
+ const fs = require("fs");
17
+ const path = require("path");
18
+ const cwdRequire = createRequire(process.cwd() + "/package.json");
19
+ let ts;
20
+ let macros;
21
+ try {
22
+ ts = cwdRequire("typescript");
23
+ } catch {
24
+ console.error(
25
+ "[macroforge] error: typescript is not installed in this project",
26
+ );
27
+ process.exit(1);
28
+ }
29
+ try {
30
+ macros = cwdRequire("macroforge");
31
+ } catch {
32
+ console.error(
33
+ "[macroforge] error: macroforge is not installed in this project",
34
+ );
35
+ process.exit(1);
36
+ }
37
+ if (macros.setupExternalMacros) {
38
+ let resolveDecoratorNames = function (packagePath) {
39
+ const candidates = [packagePath];
40
+ for (const id of candidates) {
41
+ try {
42
+ const pkg = req(id);
43
+ const names = [];
44
+ if (pkg.__macroforgeGetManifest) {
45
+ names.push(
46
+ ...(pkg.__macroforgeGetManifest().decorators || []).map(
47
+ (d) => d.export,
48
+ ),
49
+ );
50
+ }
51
+ for (const key of Object.keys(pkg)) {
52
+ if (
53
+ key.startsWith("__macroforgeGetManifest_") &&
54
+ typeof pkg[key] === "function"
55
+ ) {
56
+ names.push(...(pkg[key]().decorators || []).map((d) => d.export));
57
+ }
58
+ }
59
+ if (names.length > 0) return [...new Set(names)];
60
+ } catch {}
61
+ }
62
+ return [];
63
+ },
64
+ runMacro = function (ctxJson) {
65
+ const ctx = JSON.parse(ctxJson);
66
+ const fnName = `__macroforgeRun${ctx.macro_name}`;
67
+ const candidates = [ctx.module_path];
68
+ for (const id of candidates) {
69
+ try {
70
+ const pkg = req(id);
71
+ const fn_ = pkg?.[fnName] || pkg?.default?.[fnName];
72
+ if (typeof fn_ === "function") return fn_(ctxJson);
73
+ } catch {}
74
+ }
75
+ throw new Error(`Macro ${fnName} not found in ${ctx.module_path}`);
76
+ };
77
+ var resolveDecoratorNames2 = resolveDecoratorNames,
78
+ runMacro2 = runMacro;
79
+ const req = createRequire(process.cwd() + "/package.json");
80
+ macros.setupExternalMacros(resolveDecoratorNames, runMacro);
81
+ }
82
+ const CONFIG_FILES = [
83
+ "macroforge.config.ts",
84
+ "macroforge.config.mts",
85
+ "macroforge.config.js",
86
+ "macroforge.config.mjs",
87
+ "macroforge.config.cjs",
88
+ ];
89
+ let macroConfigPath = null;
90
+ let currentDir = process.cwd();
91
+ while (true) {
92
+ for (const filename of CONFIG_FILES) {
93
+ const candidate = path.join(currentDir, filename);
94
+ if (fs.existsSync(candidate)) {
95
+ macroConfigPath = candidate;
96
+ break;
97
+ }
98
+ }
99
+ if (macroConfigPath) break;
100
+ if (fs.existsSync(path.join(currentDir, "package.json"))) break;
101
+ const parent = path.dirname(currentDir);
102
+ if (parent === currentDir) break;
103
+ currentDir = parent;
104
+ }
105
+ if (macroConfigPath) {
106
+ try {
107
+ const configContent = fs.readFileSync(macroConfigPath, "utf8");
108
+ macros.loadConfig(configContent, macroConfigPath);
109
+ } catch {}
110
+ }
111
+ const typeRegistryPath = process.env.MACROFORGE_TYPE_REGISTRY_PATH;
112
+ let typeRegistryJson = void 0;
113
+ if (typeRegistryPath) {
114
+ try {
115
+ typeRegistryJson = fs.readFileSync(typeRegistryPath, "utf8");
116
+ } catch {}
117
+ }
118
+ const declarativeRegistryPath =
119
+ process.env.MACROFORGE_DECLARATIVE_REGISTRY_PATH;
120
+ let declarativeRegistryJson = void 0;
121
+ if (declarativeRegistryPath) {
122
+ try {
123
+ declarativeRegistryJson = fs.readFileSync(declarativeRegistryPath, "utf8");
124
+ } catch {}
125
+ }
126
+ const plugin = new macros.NativePlugin();
127
+ const expandOpts = {};
128
+ if (macroConfigPath) expandOpts.configPath = macroConfigPath;
129
+ if (typeRegistryJson) expandOpts.typeRegistryJson = typeRegistryJson;
130
+ if (declarativeRegistryJson) {
131
+ expandOpts.declarativeRegistryJson = declarativeRegistryJson;
132
+ }
133
+ const tsSys = ts.sys;
134
+ const origReadFile = tsSys.readFile.bind(tsSys);
135
+ // Text-level fast path matching the tsc wrapper — covers derive macros
136
+ // plus both sides of the declarative macro system (defining and consuming).
137
+ function hasMacroMarkers(sourceText) {
138
+ if (!sourceText) return false;
139
+ if (sourceText.includes("@derive")) return true;
140
+ if (sourceText.includes("macroforge/rules")) return true;
141
+ if (sourceText.includes("import macro")) return true;
142
+ return false;
143
+ }
144
+ tsSys.readFile = (filePath, encoding) => {
145
+ const content = origReadFile(filePath, encoding);
146
+ if (content == null) return content;
147
+ try {
148
+ if (
149
+ (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) &&
150
+ !filePath.endsWith(".d.ts") &&
151
+ hasMacroMarkers(content)
152
+ ) {
153
+ const result = plugin.processFile(filePath, content, expandOpts);
154
+ return result.code || content;
155
+ }
156
+ } catch {}
157
+ return content;
158
+ };
159
+ const args = ["svelte-check"];
160
+ for (let i = 2; i < process.argv.length; i++) {
161
+ args.push(process.argv[i]);
162
+ }
163
+ process.argv = [process.argv[0], ...args];
164
+ try {
165
+ cwdRequire("svelte-check");
166
+ } catch (e) {
167
+ if (e.code === "MODULE_NOT_FOUND") {
168
+ console.error(
169
+ "[macroforge] error: svelte-check is not installed in this project",
170
+ );
171
+ console.error(
172
+ "[macroforge] install it with: npm install --save-dev svelte-check",
173
+ );
174
+ process.exit(1);
175
+ }
176
+ throw e;
177
+ }