@macroforge/vite-plugin 0.1.77 → 0.1.79

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.
Files changed (2) hide show
  1. package/package.json +4 -4
  2. package/src/index.js +181 -52
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "dependencies": {
3
- "@macroforge/shared": "^0.1.77",
4
- "macroforge": "^0.1.77"
3
+ "@macroforge/shared": "file:../shared",
4
+ "macroforge": "file:../../crates/macroforge_ts"
5
5
  },
6
6
  "devDependencies": {
7
7
  "@types/node": "^22.0.0"
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "repository": {
24
24
  "type": "git",
25
- "url": "git+https://github.com/macroforge-ts/vite-plugin.git"
25
+ "url": "git+https://github.com/macroforge-ts/macroforge-ts.git"
26
26
  },
27
27
  "scripts": {
28
28
  "build": "echo 'No build needed'",
@@ -30,5 +30,5 @@
30
30
  },
31
31
  "type": "module",
32
32
  "types": "src/index.d.ts",
33
- "version": "0.1.77"
33
+ "version": "0.1.79"
34
34
  }
package/src/index.js CHANGED
@@ -42,9 +42,11 @@
42
42
  import { createRequire } from "node:module";
43
43
  import { createHash } from "node:crypto";
44
44
  import * as fs from "node:fs";
45
+ import * as os from "node:os";
45
46
  import * as path from "node:path";
46
47
  import {
47
48
  collectExternalDecoratorModules,
49
+ hasMacroAnnotations,
48
50
  loadMacroConfig,
49
51
  } from "@macroforge/shared";
50
52
 
@@ -346,6 +348,48 @@ export async function macroforge() {
346
348
  try {
347
349
  const projectRequire = createRequire(process.cwd() + "/");
348
350
  rustTransformer = projectRequire("macroforge");
351
+
352
+ // Register external macro callbacks for WASM builds.
353
+ // The WASM build cannot spawn Node subprocesses to resolve external macros,
354
+ // so we provide JS-side resolve/run callbacks. No-op for NAPI builds.
355
+ if (rustTransformer.setupExternalMacros) {
356
+ const req = createRequire(process.cwd() + "/package.json");
357
+
358
+ const resolveDecoratorNames = function (packagePath) {
359
+ const pkg = req(packagePath);
360
+ const names = [];
361
+ if (pkg.__macroforgeGetManifest) {
362
+ names.push(
363
+ ...(pkg.__macroforgeGetManifest().decorators || []).map(
364
+ (d) => d.export,
365
+ ),
366
+ );
367
+ }
368
+ for (const key of Object.keys(pkg)) {
369
+ if (
370
+ key.startsWith("__macroforgeGetManifest_") &&
371
+ typeof pkg[key] === "function"
372
+ ) {
373
+ names.push(
374
+ ...(pkg[key]().decorators || []).map((d) => d.export),
375
+ );
376
+ }
377
+ }
378
+ if (names.length > 0) return [...new Set(names)];
379
+ return [];
380
+ };
381
+
382
+ const runMacro = function (ctxJson) {
383
+ const ctx = JSON.parse(ctxJson);
384
+ const fnName = `__macroforgeRun${ctx.macro_name}`;
385
+ const pkg = req(ctx.module_path);
386
+ const fn_ = pkg?.[fnName] || pkg?.default?.[fnName];
387
+ if (typeof fn_ === "function") return fn_(ctxJson);
388
+ throw new Error(`Macro ${fnName} not found in ${ctx.module_path}`);
389
+ };
390
+
391
+ rustTransformer.setupExternalMacros(resolveDecoratorNames, runMacro);
392
+ }
349
393
  } catch (error) {
350
394
  console.warn(
351
395
  "[@macroforge/vite-plugin] Rust binary not found. Please run `npm run build:rust` first.",
@@ -454,17 +498,84 @@ export async function macroforge() {
454
498
  * @returns {string}
455
499
  */
456
500
  function getMacroforgeVersion() {
501
+ const req = createRequire(process.cwd() + "/");
457
502
  try {
458
- const req = createRequire(process.cwd() + "/");
459
- const mainPath = req.resolve("macroforge");
460
- const pkgDir = path.dirname(mainPath);
461
- const pkgJson = JSON.parse(
462
- fs.readFileSync(path.join(pkgDir, "package.json"), "utf-8"),
463
- );
464
- return pkgJson.version;
465
- } catch {
466
- return "unknown";
503
+ return JSON.parse(
504
+ fs.readFileSync(req.resolve("macroforge/package.json"), "utf-8"),
505
+ ).version;
506
+ } catch { /* exports map may block ./package.json */ }
507
+ try {
508
+ return JSON.parse(
509
+ fs.readFileSync(
510
+ path.join(process.cwd(), "node_modules", "macroforge", "package.json"),
511
+ "utf-8",
512
+ ),
513
+ ).version;
514
+ } catch { /* not in local node_modules */ }
515
+ return "unknown";
516
+ }
517
+
518
+ /**
519
+ * Computes a hash over external macro package binaries (`.node`, `.wasm`)
520
+ * so the cache invalidates when a local macro package is rebuilt.
521
+ * @returns {string}
522
+ */
523
+ function getExternalMacroHash() {
524
+ const nodeModules = path.join(projectRoot || process.cwd(), "node_modules");
525
+ if (!fs.existsSync(nodeModules)) return "none";
526
+
527
+ // Collect path:size:mtime_seconds parts, sort for deterministic ordering
528
+ // (readdir order varies across Node/Deno/Rust), then hash.
529
+ const parts = [];
530
+
531
+ const checkPackage = (pkgDir) => {
532
+ const indexJs = path.join(pkgDir, "index.js");
533
+ try {
534
+ const content = fs.readFileSync(indexJs, "utf-8");
535
+ if (!content.includes("__macroforgeRun")) return;
536
+ } catch {
537
+ return;
538
+ }
539
+ try {
540
+ for (const entry of fs.readdirSync(pkgDir)) {
541
+ const ext = path.extname(entry);
542
+ if (ext === ".node" || ext === ".wasm" || entry === "index.js") {
543
+ const full = path.join(pkgDir, entry);
544
+ try {
545
+ const stat = fs.statSync(full);
546
+ parts.push(
547
+ `${full}:${stat.size}:${Math.floor(stat.mtimeMs / 1000)}`,
548
+ );
549
+ } catch { /* expected */ }
550
+ }
551
+ }
552
+ } catch { /* expected */ }
553
+ };
554
+
555
+ try {
556
+ for (const entry of fs.readdirSync(nodeModules)) {
557
+ const full = path.join(nodeModules, entry);
558
+ if (!fs.statSync(full).isDirectory()) continue;
559
+ if (entry.startsWith("@")) {
560
+ try {
561
+ for (const sub of fs.readdirSync(full)) {
562
+ const subFull = path.join(full, sub);
563
+ if (fs.statSync(subFull).isDirectory()) checkPackage(subFull);
564
+ }
565
+ } catch { /* expected */ }
566
+ } else if (!entry.startsWith(".")) {
567
+ checkPackage(full);
568
+ }
569
+ }
570
+ } catch { /* expected */ }
571
+
572
+ if (parts.length === 0) return "none";
573
+ parts.sort();
574
+ const hasher = createHash("sha256");
575
+ for (const part of parts) {
576
+ hasher.update(part);
467
577
  }
578
+ return hasher.digest("hex");
468
579
  }
469
580
 
470
581
  /**
@@ -517,6 +628,17 @@ export async function macroforge() {
517
628
  return null;
518
629
  }
519
630
 
631
+ const currentExternalHash = getExternalMacroHash();
632
+ if (
633
+ manifest.externalMacroHash &&
634
+ manifest.externalMacroHash !== currentExternalHash
635
+ ) {
636
+ console.log(
637
+ "[@macroforge/vite-plugin] Cache invalidated: external macro binary changed",
638
+ );
639
+ return null;
640
+ }
641
+
520
642
  return manifest;
521
643
  } catch {
522
644
  return null;
@@ -573,6 +695,7 @@ export async function macroforge() {
573
695
  cacheManifest = {
574
696
  version: macroforgeVersion,
575
697
  configHash: getConfigHash(),
698
+ externalMacroHash: getExternalMacroHash(),
576
699
  entries: {},
577
700
  };
578
701
  }
@@ -726,42 +849,61 @@ export async function macroforge() {
726
849
  },
727
850
 
728
851
  /**
729
- * Pre-scan the project to build a type registry for compile-time type awareness.
852
+ * Load the type registry from the CLI cache for compile-time type awareness.
730
853
  * The registry is passed to every expandSync call so macros can introspect
731
- * any type in the project (Zig-style type awareness).
854
+ * any type in the project.
732
855
  */
733
856
  buildStart() {
734
- if (!rustTransformer || !rustTransformer.scanProjectSync) {
735
- return;
857
+ const localRegistry = path.join(
858
+ projectRoot,
859
+ ".macroforge",
860
+ "type-registry.json",
861
+ );
862
+ if (fs.existsSync(localRegistry)) {
863
+ typeRegistryJson = fs.readFileSync(localRegistry, "utf-8");
864
+ try {
865
+ const parsed = JSON.parse(typeRegistryJson);
866
+ const count = Object.keys(parsed.types ?? parsed).length;
867
+ console.log(
868
+ `[@macroforge/vite-plugin] Type registry loaded: ${count} types`,
869
+ );
870
+ } catch {
871
+ // JSON is passed as-is to expandSync, no need to parse here
872
+ }
873
+ } else {
874
+ console.warn(
875
+ `[@macroforge/vite-plugin] No type registry found at .macroforge/type-registry.json. Run \`macroforge watch\` to generate it.`,
876
+ );
736
877
  }
878
+ },
737
879
 
738
- try {
739
- const scanStart = performance.now();
740
- const scanResult = rustTransformer.scanProjectSync(projectRoot, {
741
- exportedOnly: false,
742
- });
743
- const scanTime = (performance.now() - scanStart).toFixed(0);
880
+ /**
881
+ * Resolve `.svelte` imports to `.svelte.ts` when the `.svelte` file
882
+ * does not exist. Macroforge type files use the `.svelte.ts` extension
883
+ * (Svelte 5 runes modules) but are imported with just `.svelte`.
884
+ *
885
+ * @param {string} source
886
+ * @param {string | undefined} importer
887
+ * @param {object} options
888
+ */
889
+ async resolveId(source, importer, options) {
890
+ if (!source.endsWith(".svelte") || !importer) return null;
744
891
 
745
- typeRegistryJson = scanResult.registryJson;
892
+ // Let other plugins (SvelteKit, etc.) try to resolve it first
893
+ const resolved = await this.resolve(source, importer, {
894
+ ...options,
895
+ skipSelf: true,
896
+ });
746
897
 
747
- console.log(
748
- `[@macroforge/vite-plugin] Type scan: ${scanResult.typesFound} types from ${scanResult.filesScanned} files (${scanTime}ms)`,
749
- );
898
+ if (resolved && !resolved.external) return resolved;
750
899
 
751
- for (const diag of scanResult.diagnostics) {
752
- if (diag.level === "error") {
753
- console.error(
754
- `[@macroforge/vite-plugin] Scan error: ${diag.message}`,
755
- );
756
- }
757
- }
758
- } catch (error) {
759
- console.warn(
760
- `[@macroforge/vite-plugin] Type scan failed, macros will run without type awareness:`,
761
- error.message || error,
762
- );
763
- typeRegistryJson = undefined;
764
- }
900
+ // Fall back: try appending .ts
901
+ const resolvedTs = await this.resolve(source + ".ts", importer, {
902
+ ...options,
903
+ skipSelf: true,
904
+ });
905
+
906
+ return resolvedTs || null;
765
907
  },
766
908
 
767
909
  /**
@@ -789,8 +931,8 @@ export async function macroforge() {
789
931
  return null;
790
932
  }
791
933
 
792
- // Quick check: files without @derive can't have macros — skip entirely
793
- if (!code.includes("@derive")) {
934
+ // Quick check: skip files without a real @derive directive
935
+ if (!hasMacroAnnotations(code)) {
794
936
  return null;
795
937
  }
796
938
 
@@ -801,7 +943,6 @@ export async function macroforge() {
801
943
  if (cached) {
802
944
  let cachedCode = cached.code;
803
945
 
804
- // Apply same post-processing as the normal path
805
946
  cachedCode = cachedCode.replace(
806
947
  /\/\*\*\s*import\s+macro[\s\S]*?\*\/\s*/gi,
807
948
  "",
@@ -813,18 +954,6 @@ export async function macroforge() {
813
954
  );
814
955
  }
815
956
 
816
- // Generate type definitions from cached expanded code
817
- if (generateTypes) {
818
- const emitted = emitDeclarationsFromCode(
819
- cachedCode,
820
- id,
821
- projectRoot,
822
- );
823
- if (emitted) {
824
- writeTypeDefinitions(id, emitted);
825
- }
826
- }
827
-
828
957
  return {
829
958
  code: cachedCode,
830
959
  map: null,