@vertz/codegen 0.2.13 → 0.2.15

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/dist/index.js +131 -19
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -116,7 +116,7 @@ async function formatWithBiome(files) {
116
116
  }
117
117
  // src/generate.ts
118
118
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
119
- import { dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
119
+ import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:path";
120
120
 
121
121
  // src/generators/client-generator.ts
122
122
  import { posix } from "node:path";
@@ -666,9 +666,119 @@ ${props};
666
666
  }
667
667
  }
668
668
 
669
+ // src/generators/router-augmentation-generator.ts
670
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
671
+ import { dirname as dirname2, extname, join as join2, relative, resolve as resolve2, sep } from "node:path";
672
+ var FILE_HEADER5 = `// Generated by @vertz/codegen — do not edit
673
+
674
+ `;
675
+ var CANDIDATE_ROUTE_FILES = [
676
+ ["src", "router.ts"],
677
+ ["src", "router.tsx"],
678
+ ["src", "ui", "router.ts"],
679
+ ["src", "ui", "router.tsx"]
680
+ ];
681
+ var SOURCE_EXTENSIONS = new Set([".ts", ".tsx"]);
682
+ var EXPORTED_ROUTES_PATTERN = /export\s+const\s+routes\b/m;
683
+ var REEXPORTED_ROUTES_PATTERN = /const\s+routes\b[\s\S]*?export\s*\{\s*routes\b/m;
684
+
685
+ class RouterAugmentationGenerator {
686
+ name = "router-augmentation";
687
+ generate(_ir, config) {
688
+ const projectRoot = findProjectRoot(config.outputDir);
689
+ if (!projectRoot)
690
+ return [];
691
+ const routeModulePath = findRouteModule(projectRoot);
692
+ if (!routeModulePath)
693
+ return [];
694
+ return [
695
+ {
696
+ path: "router.d.ts",
697
+ content: renderRouterAugmentation(routeModulePath, config.outputDir)
698
+ }
699
+ ];
700
+ }
701
+ }
702
+ function findProjectRoot(outputDir) {
703
+ let current = resolve2(outputDir);
704
+ while (true) {
705
+ if (existsSync(join2(current, "package.json"))) {
706
+ return current;
707
+ }
708
+ const parent = dirname2(current);
709
+ if (parent === current) {
710
+ return null;
711
+ }
712
+ current = parent;
713
+ }
714
+ }
715
+ function findRouteModule(projectRoot) {
716
+ for (const candidate of CANDIDATE_ROUTE_FILES) {
717
+ const candidatePath = join2(projectRoot, ...candidate);
718
+ if (existsSync(candidatePath) && fileExportsRoutes(candidatePath)) {
719
+ return candidatePath;
720
+ }
721
+ }
722
+ return scanForRouteModule(join2(projectRoot, "src"));
723
+ }
724
+ function scanForRouteModule(dir) {
725
+ if (!existsSync(dir))
726
+ return null;
727
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
728
+ const fullPath = join2(dir, entry.name);
729
+ if (entry.isDirectory()) {
730
+ const nested = scanForRouteModule(fullPath);
731
+ if (nested) {
732
+ return nested;
733
+ }
734
+ continue;
735
+ }
736
+ if (!entry.isFile())
737
+ continue;
738
+ const extension = extname(entry.name);
739
+ if (extension === ".d.ts" || !SOURCE_EXTENSIONS.has(extension))
740
+ continue;
741
+ if (fileExportsRoutes(fullPath)) {
742
+ return fullPath;
743
+ }
744
+ }
745
+ return null;
746
+ }
747
+ function fileExportsRoutes(filePath) {
748
+ const source = readFileSync(filePath, "utf-8");
749
+ if (!source.includes("defineRoutes("))
750
+ return false;
751
+ return EXPORTED_ROUTES_PATTERN.test(source) || REEXPORTED_ROUTES_PATTERN.test(source);
752
+ }
753
+ function renderRouterAugmentation(routeModulePath, outputDir) {
754
+ const importPath = toImportPath(relative(resolve2(outputDir), routeModulePath));
755
+ return [
756
+ FILE_HEADER5,
757
+ "import type { InferRouteMap, TypedRouter, UnwrapSignals } from '@vertz/ui';",
758
+ `import type { routes } from '${importPath}';`,
759
+ "",
760
+ "type AppRouteMap = InferRouteMap<typeof routes>;",
761
+ "",
762
+ "declare module '@vertz/ui' {",
763
+ " export function useRouter(): UnwrapSignals<TypedRouter<AppRouteMap>>;",
764
+ "}",
765
+ "",
766
+ "declare module '@vertz/ui/router' {",
767
+ " export function useRouter(): UnwrapSignals<TypedRouter<AppRouteMap>>;",
768
+ "}",
769
+ ""
770
+ ].join(`
771
+ `);
772
+ }
773
+ function toImportPath(pathToModule) {
774
+ const normalized = pathToModule.split(sep).join("/");
775
+ const withoutExtension = normalized.replace(/\.[^.]+$/, "");
776
+ return withoutExtension.startsWith(".") ? withoutExtension : `./${withoutExtension}`;
777
+ }
778
+
669
779
  // src/incremental.ts
670
780
  import { mkdir as mkdir2, readdir, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
671
- import { dirname as dirname2, join as join2, relative, resolve as resolve2 } from "node:path";
781
+ import { dirname as dirname3, join as join3, relative as relative2, resolve as resolve3 } from "node:path";
672
782
 
673
783
  // src/hasher.ts
674
784
  import { createHash } from "node:crypto";
@@ -686,11 +796,11 @@ async function collectFiles(dir, baseDir) {
686
796
  return results;
687
797
  }
688
798
  for (const entry of entries) {
689
- const fullPath = join2(dir, entry.name);
799
+ const fullPath = join3(dir, entry.name);
690
800
  if (entry.isDirectory()) {
691
801
  results.push(...await collectFiles(fullPath, baseDir));
692
802
  } else if (entry.isFile()) {
693
- results.push(relative(baseDir, fullPath));
803
+ results.push(relative2(baseDir, fullPath));
694
804
  }
695
805
  }
696
806
  return results;
@@ -702,12 +812,12 @@ async function writeIncremental(files, outputDir, options) {
702
812
  await mkdir2(outputDir, { recursive: true });
703
813
  const generatedPaths = new Set(files.map((f) => f.path));
704
814
  for (const file of files) {
705
- const filePath = join2(outputDir, file.path);
706
- const resolvedPath = resolve2(filePath);
707
- if (!resolvedPath.startsWith(resolve2(outputDir))) {
815
+ const filePath = join3(outputDir, file.path);
816
+ const resolvedPath = resolve3(filePath);
817
+ if (!resolvedPath.startsWith(resolve3(outputDir))) {
708
818
  throw new Error(`Generated file path "${file.path}" escapes output directory`);
709
819
  }
710
- const dir = dirname2(filePath);
820
+ const dir = dirname3(filePath);
711
821
  await mkdir2(dir, { recursive: true });
712
822
  let existingContent;
713
823
  try {
@@ -724,7 +834,7 @@ async function writeIncremental(files, outputDir, options) {
724
834
  const existingFiles = await collectFiles(outputDir, outputDir);
725
835
  for (const existing of existingFiles) {
726
836
  if (!generatedPaths.has(existing)) {
727
- await rm2(join2(outputDir, existing), { force: true });
837
+ await rm2(join3(outputDir, existing), { force: true });
728
838
  removed.push(existing);
729
839
  }
730
840
  }
@@ -863,6 +973,8 @@ function runTypescriptGenerator(ir, _config) {
863
973
  files.push(...entitySdkGen.generate(ir, generatorConfig));
864
974
  const clientGen = new ClientGenerator;
865
975
  files.push(...clientGen.generate(ir, generatorConfig));
976
+ const routerAugmentationGen = new RouterAugmentationGenerator;
977
+ files.push(...routerAugmentationGen.generate(ir, generatorConfig));
866
978
  return files;
867
979
  }
868
980
  function generateSync(ir, config) {
@@ -889,10 +1001,10 @@ async function mergeImportsToPackageJson(files, outputDir) {
889
1001
  const imports = generated.imports;
890
1002
  if (!imports || Object.keys(imports).length === 0)
891
1003
  return false;
892
- const projectRoot = await findProjectRoot(resolve3(outputDir));
1004
+ const projectRoot = await findProjectRoot2(resolve4(outputDir));
893
1005
  if (!projectRoot)
894
1006
  return false;
895
- const pkgPath = join3(projectRoot, "package.json");
1007
+ const pkgPath = join4(projectRoot, "package.json");
896
1008
  const raw = await readFile3(pkgPath, "utf-8");
897
1009
  const pkg = JSON.parse(raw);
898
1010
  const existing = pkg.imports;
@@ -904,15 +1016,15 @@ async function mergeImportsToPackageJson(files, outputDir) {
904
1016
  `, "utf-8");
905
1017
  return true;
906
1018
  }
907
- async function findProjectRoot(startDir) {
1019
+ async function findProjectRoot2(startDir) {
908
1020
  let dir = startDir;
909
- const root = dirname3(dir);
1021
+ const root = dirname4(dir);
910
1022
  while (dir !== root) {
911
1023
  try {
912
- await readFile3(join3(dir, "package.json"), "utf-8");
1024
+ await readFile3(join4(dir, "package.json"), "utf-8");
913
1025
  return dir;
914
1026
  } catch {
915
- dir = dirname3(dir);
1027
+ dir = dirname4(dir);
916
1028
  }
917
1029
  }
918
1030
  return null;
@@ -932,12 +1044,12 @@ async function generate(appIR, config) {
932
1044
  } else {
933
1045
  await mkdir3(config.outputDir, { recursive: true });
934
1046
  for (const file of files) {
935
- const filePath = join3(config.outputDir, file.path);
936
- const resolvedPath = resolve3(filePath);
937
- if (!resolvedPath.startsWith(resolve3(config.outputDir))) {
1047
+ const filePath = join4(config.outputDir, file.path);
1048
+ const resolvedPath = resolve4(filePath);
1049
+ if (!resolvedPath.startsWith(resolve4(config.outputDir))) {
938
1050
  throw new Error(`Generated file path "${file.path}" escapes output directory`);
939
1051
  }
940
- const dir = dirname3(filePath);
1052
+ const dir = dirname4(filePath);
941
1053
  await mkdir3(dir, { recursive: true });
942
1054
  await writeFile3(filePath, file.content, "utf-8");
943
1055
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/codegen",
3
- "version": "0.2.13",
3
+ "version": "0.2.15",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Vertz code generation — internal, no stability guarantee",
@@ -27,7 +27,7 @@
27
27
  "typecheck": "tsc --noEmit -p tsconfig.typecheck.json"
28
28
  },
29
29
  "dependencies": {
30
- "@vertz/compiler": "^0.2.12"
30
+ "@vertz/compiler": "^0.2.14"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^25.3.1",