@macroforge/vite-plugin 0.1.78 → 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 +178 -50
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "dependencies": {
3
- "@macroforge/shared": "^0.1.78",
4
- "macroforge": "^0.1.78"
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.78"
33
+ "version": "0.1.79"
34
34
  }
package/src/index.js CHANGED
@@ -42,6 +42,7 @@
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,
@@ -347,6 +348,48 @@ export async function macroforge() {
347
348
  try {
348
349
  const projectRequire = createRequire(process.cwd() + "/");
349
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
+ }
350
393
  } catch (error) {
351
394
  console.warn(
352
395
  "[@macroforge/vite-plugin] Rust binary not found. Please run `npm run build:rust` first.",
@@ -455,17 +498,84 @@ export async function macroforge() {
455
498
  * @returns {string}
456
499
  */
457
500
  function getMacroforgeVersion() {
501
+ const req = createRequire(process.cwd() + "/");
458
502
  try {
459
- const req = createRequire(process.cwd() + "/");
460
- const mainPath = req.resolve("macroforge");
461
- const pkgDir = path.dirname(mainPath);
462
- const pkgJson = JSON.parse(
463
- fs.readFileSync(path.join(pkgDir, "package.json"), "utf-8"),
464
- );
465
- return pkgJson.version;
466
- } catch {
467
- 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);
468
577
  }
578
+ return hasher.digest("hex");
469
579
  }
470
580
 
471
581
  /**
@@ -518,6 +628,17 @@ export async function macroforge() {
518
628
  return null;
519
629
  }
520
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
+
521
642
  return manifest;
522
643
  } catch {
523
644
  return null;
@@ -574,6 +695,7 @@ export async function macroforge() {
574
695
  cacheManifest = {
575
696
  version: macroforgeVersion,
576
697
  configHash: getConfigHash(),
698
+ externalMacroHash: getExternalMacroHash(),
577
699
  entries: {},
578
700
  };
579
701
  }
@@ -727,42 +849,61 @@ export async function macroforge() {
727
849
  },
728
850
 
729
851
  /**
730
- * 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.
731
853
  * The registry is passed to every expandSync call so macros can introspect
732
- * any type in the project (Zig-style type awareness).
854
+ * any type in the project.
733
855
  */
734
856
  buildStart() {
735
- if (!rustTransformer || !rustTransformer.scanProjectSync) {
736
- 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
+ );
737
877
  }
878
+ },
738
879
 
739
- try {
740
- const scanStart = performance.now();
741
- const scanResult = rustTransformer.scanProjectSync(projectRoot, {
742
- exportedOnly: false,
743
- });
744
- 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;
745
891
 
746
- 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
+ });
747
897
 
748
- console.log(
749
- `[@macroforge/vite-plugin] Type scan: ${scanResult.typesFound} types from ${scanResult.filesScanned} files (${scanTime}ms)`,
750
- );
898
+ if (resolved && !resolved.external) return resolved;
751
899
 
752
- for (const diag of scanResult.diagnostics) {
753
- if (diag.level === "error") {
754
- console.error(
755
- `[@macroforge/vite-plugin] Scan error: ${diag.message}`,
756
- );
757
- }
758
- }
759
- } catch (error) {
760
- console.warn(
761
- `[@macroforge/vite-plugin] Type scan failed, macros will run without type awareness:`,
762
- error.message || error,
763
- );
764
- typeRegistryJson = undefined;
765
- }
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;
766
907
  },
767
908
 
768
909
  /**
@@ -802,7 +943,6 @@ export async function macroforge() {
802
943
  if (cached) {
803
944
  let cachedCode = cached.code;
804
945
 
805
- // Apply same post-processing as the normal path
806
946
  cachedCode = cachedCode.replace(
807
947
  /\/\*\*\s*import\s+macro[\s\S]*?\*\/\s*/gi,
808
948
  "",
@@ -814,18 +954,6 @@ export async function macroforge() {
814
954
  );
815
955
  }
816
956
 
817
- // Generate type definitions from cached expanded code
818
- if (generateTypes) {
819
- const emitted = emitDeclarationsFromCode(
820
- cachedCode,
821
- id,
822
- projectRoot,
823
- );
824
- if (emitted) {
825
- writeTypeDefinitions(id, emitted);
826
- }
827
- }
828
-
829
957
  return {
830
958
  code: cachedCode,
831
959
  map: null,