@lpdjs/firestore-repo-service 2.2.9-beta.11 → 2.2.9-beta.13

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';var path=require('path'),fs=require('fs');var v={skipSegments:["useCases","useCase","use-cases","use-case"],casing:"preserve"};function P(e,t=v){let s=new Set(t.skipSegments.map(o=>o.toLowerCase()));return "/"+e.split("/").filter(Boolean).filter(o=>!s.has(o.toLowerCase())).map(o=>t.casing==="kebab"?G(o):o).join("/")}function G(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}function O(e,t,s){let r=_(e),o=_(t),i=0;for(;i<r.length&&i<o.length&&r[i]===o[i];)i++;let a=r.length-i,n=o.slice(i),c=(n[n.length-1]??"").replace(/\.[mc]?[tj]sx?$/i,""),m=s===""?c:`${c}${s}`;return n[n.length-1]=m,(a===0?"./":"../".repeat(a))+n.join("/")}function _(e){return e.replace(/\\/g,"/").replace(/\/+$/,"").split("/").filter((s,r)=>!(r===0&&s===""))}var b={routesFile:"routes.ts",excludeSegments:["node_modules","__generated__","tests","__tests__",".turbo","dist","build",".next"]};function D(e,t=b){let s=[];return C(e,e,t,s),s.sort((r,o)=>r.relPath.localeCompare(o.relPath)),s}function C(e,t,s,r){let o;try{o=fs.readdirSync(t);}catch{return}for(let i of o){if(s.excludeSegments.includes(i))continue;let a=path.join(t,i),n;try{n=fs.statSync(a);}catch{continue}if(n.isDirectory())C(e,a,s,r);else if(n.isFile()&&i===s.routesFile){let d=path.relative(e,a).split(path.sep).join("/"),c=d.replace(/\/?[^/]+$/,"");r.push({absPath:a,relPath:d,relDir:c});}}}var Z="/**\n * AUTO-GENERATED by `@lpdjs/firestore-repo-service` Hono codegen.\n * Do not edit by hand \u2014 re-run `hono:gen` after adding / removing route files.\n */\n";function R(e,t){let s=path.dirname(t.outFile);fs.mkdirSync(s,{recursive:true});let r=t.banner??Z,o=(t.now??new Date).toISOString(),i=t.importExtension,a=[],n=[],d=[];e.forEach((m,u)=>{let g=O(s,m.absPath,i),x=P(m.relDir,t.derive);a.push(`import mod${u} from ${JSON.stringify(g)};`),n.push(` { __derivedPath: ${JSON.stringify(x)}, mod: mod${u} },`),d.push({source:m.relPath,url:x});});let c=`${r}// Generated at ${o} \u2014 ${e.length} route file${e.length===1?"":"s"}.
2
+ 'use strict';var path=require('path'),fs=require('fs');var $={skipSegments:["useCases","useCase","use-cases","use-case"],casing:"preserve"};function O(e,t=$){let s=new Set(t.skipSegments.map(n=>n.toLowerCase()));return "/"+e.split("/").filter(Boolean).filter(n=>!s.has(n.toLowerCase())).map(n=>t.casing==="kebab"?q(n):n).join("/")}function q(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}function D(e,t,s){let r=_(e),n=_(t),i=0;for(;i<r.length&&i<n.length&&r[i]===n[i];)i++;let a=r.length-i,o=n.slice(i),u=(o[o.length-1]??"").replace(/\.[mc]?[tj]sx?$/i,""),m=s===""?u:`${u}${s}`;return o[o.length-1]=m,(a===0?"./":"../".repeat(a))+o.join("/")}function _(e){return e.replace(/\\/g,"/").replace(/\/+$/,"").split("/").filter((s,r)=>!(r===0&&s===""))}var v={routesFile:"routes.ts",excludeSegments:["node_modules","__generated__","tests","__tests__",".turbo","dist","build",".next"]};function C(e,t=v){let s=[];return R(e,e,t,s),s.sort((r,n)=>r.relPath.localeCompare(n.relPath)),s}function R(e,t,s,r){let n;try{n=fs.readdirSync(t);}catch{return}for(let i of n){if(s.excludeSegments.includes(i))continue;let a=path.join(t,i),o;try{o=fs.statSync(a);}catch{continue}if(o.isDirectory())R(e,a,s,r);else if(o.isFile()&&i===s.routesFile){let d=path.relative(e,a).split(path.sep).join("/"),u=d.replace(/\/?[^/]+$/,"");r.push({absPath:a,relPath:d,relDir:u});}}}var X="/**\n * AUTO-GENERATED by `@lpdjs/firestore-repo-service` Hono codegen.\n * Do not edit by hand \u2014 re-run `hono:gen` after adding / removing route files.\n */\n";function j(e,t){let s=path.dirname(t.outFile);fs.mkdirSync(s,{recursive:true});let r=t.banner??X,n=(t.now??new Date).toISOString(),i=t.importExtension,a=[],o=[],d=[];e.forEach((m,c)=>{let g=D(s,m.absPath,i),x=O(m.relDir,t.derive);a.push(`import mod${c} from ${JSON.stringify(g)};`),o.push(` { __derivedPath: ${JSON.stringify(x)}, mod: mod${c} },`),d.push({source:m.relPath,url:x});});let u=`${r}// Generated at ${n} \u2014 ${e.length} route file${e.length===1?"":"s"}.
3
3
 
4
4
  import type { AnyRouteDef, RouteModuleDefault } from "@lpdjs/firestore-repo-service/servers/hono";
5
5
 
@@ -8,15 +8,15 @@ import type { AnyRouteDef, RouteModuleDefault } from "@lpdjs/firestore-repo-serv
8
8
 
9
9
  `:`
10
10
  `)+`const __defs: { __derivedPath: string; mod: RouteModuleDefault }[] = [
11
- `+n.join(`
12
- `)+(n.length?`
11
+ `+o.join(`
12
+ `)+(o.length?`
13
13
  `:"")+`];
14
14
 
15
15
  export const routes: AnyRouteDef[] = __defs.flatMap(({ __derivedPath, mod }) => {
16
16
  const list = Array.isArray(mod) ? mod : [mod];
17
17
  return list.map((route) => ({ ...route, path: route.path ?? __derivedPath }));
18
18
  });
19
- `;return fs.writeFileSync(t.outFile,c,"utf8"),{outFile:t.outFile,routeCount:e.length,derivedPaths:d}}function Q(e){let[t,...s]=e,r={};for(let o=0;o<s.length;o++){let i=s[o];if(!i.startsWith("--"))continue;let a=i.slice(2),n=s[o+1];n&&!n.startsWith("--")?(r[a]=n,o++):r[a]=true;}return {command:t??"help",flags:r}}function F(){console.log(`frs-hono \u2014 Hono file-based codegen
19
+ `;return fs.writeFileSync(t.outFile,u,"utf8"),{outFile:t.outFile,routeCount:e.length,derivedPaths:d}}function ee(e){let[t,...s]=e,r={};for(let n=0;n<s.length;n++){let i=s[n];if(!i.startsWith("--"))continue;let a=i.slice(2),o=s[n+1];o&&!o.startsWith("--")?(r[a]=o,n++):r[a]=true;}return {command:t??"help",flags:r}}function F(){console.log(`frs-hono \u2014 Hono file-based codegen
20
20
 
21
21
  Usage:
22
22
  frs-hono gen [flags]
@@ -47,12 +47,14 @@ Flags (new <name>):
47
47
  Parent folder under <domain>. Default: useCases
48
48
  --with-usecase Also scaffold a sibling useCase.ts file (default: true)
49
49
  --with-test Also scaffold a sibling useCase.test.ts (Vitest, default: true)
50
+ --apis-import <path> Import path for the registry (default: auto-detect
51
+ ../../../../apis.js \u2014 adjust if your layout differs)
50
52
  --force Overwrite if files already exist
51
53
 
52
54
  Examples:
53
55
  frs-hono new createPost --domain posts --method post
54
56
  frs-hono new listPosts --domain posts --method get --api v1
55
- `);}function A(e){if(typeof e=="string")return e.split(",").map(t=>t.trim()).filter(Boolean)}function f(e){return typeof e=="string"?e:void 0}async function X(e){let t=f(e.root);t||(console.error("[frs-hono] --root is required"),process.exit(2));let s=path.resolve(process.cwd(),t);fs.existsSync(s)||(console.error(`[frs-hono] root not found: ${s}`),process.exit(2));let r=f(e.out)??"__generated__/routes.ts",o=A(e.skip)??v.skipSegments,i=f(e.casing)==="kebab"?"kebab":v.casing,a={skipSegments:o,casing:i},n=f(e.ext)??".js",d=A(e.exclude)??b.excludeSegments,c=f(e["routes-file"])??b.routesFile,u=D(s,{routesFile:c,excludeSegments:d});u.length===0&&console.warn(`[frs-hono] no "${c}" files found under ${s} \u2014 generated an empty manifest.`);let g=R(u,{outFile:path.resolve(s,r),derive:a,importExtension:n});if(!e.silent){console.log(`[frs-hono] wrote ${g.outFile} (${g.routeCount} route${g.routeCount===1?"":"s"})`);for(let{source:x,url:S}of g.derivedPaths)console.log(` ${S.padEnd(48)} \u2190 ${x}`);}}async function Y(e,t){(!e||e.startsWith("--"))&&(console.error("[frs-hono] usage: frs-hono new <name> --domain <domain> [flags]"),process.exit(2));let s=f(t.domain);s||(console.error("[frs-hono] --domain is required"),process.exit(2));let r=f(t.root)??"src/domains",o=(f(t.method)??"post").toLowerCase(),i=f(t.api)??"v1",a=f(t["usecase-folder"])??"useCases",n=t["with-usecase"]!==false,d=t["with-test"]!==false,c=t.force===true;["get","post","put","patch","delete"].includes(o)||(console.error(`[frs-hono] invalid --method: ${o}`),process.exit(2));let m=path.resolve(process.cwd(),r),u=path.resolve(m,s,a,e),g=path.resolve(u,"routes.ts"),x=path.resolve(u,"useCase.ts"),S=path.resolve(u,"useCase.test.ts"),{mkdirSync:k,existsSync:E,writeFileSync:j}=await import('fs');k(u,{recursive:true});let p=`${e.charAt(0).toUpperCase()}${e.slice(1)}UseCase`,T=`/**
57
+ `);}function k(e){if(typeof e=="string")return e.split(",").map(t=>t.trim()).filter(Boolean)}function l(e){return typeof e=="string"?e:void 0}async function te(e){let t=l(e.root);t||(console.error("[frs-hono] --root is required"),process.exit(2));let s=path.resolve(process.cwd(),t);fs.existsSync(s)||(console.error(`[frs-hono] root not found: ${s}`),process.exit(2));let r=l(e.out)??"__generated__/routes.ts",n=k(e.skip)??$.skipSegments,i=l(e.casing)==="kebab"?"kebab":$.casing,a={skipSegments:n,casing:i},o=l(e.ext)??".js",d=k(e.exclude)??v.excludeSegments,u=l(e["routes-file"])??v.routesFile,c=C(s,{routesFile:u,excludeSegments:d});c.length===0&&console.warn(`[frs-hono] no "${u}" files found under ${s} \u2014 generated an empty manifest.`);let g=j(c,{outFile:path.resolve(s,r),derive:a,importExtension:o});if(!e.silent){console.log(`[frs-hono] wrote ${g.outFile} (${g.routeCount} route${g.routeCount===1?"":"s"})`);for(let{source:x,url:S}of g.derivedPaths)console.log(` ${S.padEnd(48)} \u2190 ${x}`);}}async function se(e,t){(!e||e.startsWith("--"))&&(console.error("[frs-hono] usage: frs-hono new <name> --domain <domain> [flags]"),process.exit(2));let s=l(t.domain);s||(console.error("[frs-hono] --domain is required"),process.exit(2));let r=l(t.root)??"src/domains",n=(l(t.method)??"post").toLowerCase(),i=l(t.api)??"v1",a=l(t["usecase-folder"])??"useCases",o=t["with-usecase"]!==false,d=t["with-test"]!==false,u=t.force===true;["get","post","put","patch","delete"].includes(n)||(console.error(`[frs-hono] invalid --method: ${n}`),process.exit(2));let m=path.resolve(process.cwd(),r),c=path.resolve(m,s,a,e),g=path.resolve(c,"routes.ts"),x=path.resolve(c,"useCase.ts"),S=path.resolve(c,"useCase.test.ts"),{mkdirSync:E,existsSync:T,writeFileSync:I}=await import('fs');E(c,{recursive:true});let p=`${e.charAt(0).toUpperCase()}${e.slice(1)}UseCase`,L=`/**
56
58
  * ${p} \u2014 pure business logic, no HTTP awareness.
57
59
  * Reusable across multiple routes / cron jobs / triggers.
58
60
  */
@@ -76,24 +78,24 @@ export class ${p} {
76
78
  return { id: input.example };
77
79
  }
78
80
  }
79
- `,L=o==="get"?`z.object({
81
+ `,N=n==="get"?`z.object({
80
82
  // GET \u2192 lu depuis les query params
81
83
  example: z.string(),
82
84
  })`:`z.object({
83
- // ${o.toUpperCase()} \u2192 lu depuis le body JSON
85
+ // ${n.toUpperCase()} \u2192 lu depuis le body JSON
84
86
  example: z.string(),
85
- })`,N=n?` const useCase = new ${p}();
87
+ })`,U=o?` const useCase = new ${p}();
86
88
  const data = await useCase.execute(input);
87
89
  return data;`:` // TODO: business logic
88
- return { id: input.example };`,I=`import { z } from "zod";
89
- import { defineRoute } from "@lpdjs/firestore-repo-service/servers/hono";
90
- ${n?`import { ${p} } from "./useCase.js";
91
- `:""}
90
+ return { id: input.example };`,G=o?`import { ${p} } from "./useCase.js";
91
+ `:"",z=`import { z } from "zod";
92
+ import { defineRoute } from "${l(t["apis-import"])??ne(m,c)}";
93
+ ${G}
92
94
  export default defineRoute({
93
95
  api: "${i}",
94
- method: "${o}",
96
+ method: "${n}",
95
97
 
96
- input: ${L},
98
+ input: ${N},
97
99
 
98
100
  output: z.object({
99
101
  id: z.string(),
@@ -103,10 +105,10 @@ export default defineRoute({
103
105
  tags: ["${s}"],
104
106
 
105
107
  handler: async ({ input }) => {
106
- ${N}
108
+ ${U}
107
109
  },
108
110
  });
109
- `,w=[],y=[],$=(l,U)=>{if(E(l)&&!c){y.push(l);return}j(l,U,"utf8"),w.push(l);};if($(g,I),n&&$(x,T),n&&d){let l=`import { describe, it, expect } from "vitest";
111
+ `,w=[],P=[],b=(f,M)=>{if(T(f)&&!u){P.push(f);return}I(f,M,"utf8"),w.push(f);};if(b(g,z),o&&b(x,L),o&&d){let f=`import { describe, it, expect } from "vitest";
110
112
  import { ${p} } from "./useCase.js";
111
113
 
112
114
  describe("${p}", () => {
@@ -118,7 +120,7 @@ describe("${p}", () => {
118
120
 
119
121
  // TODO: add error-path tests, repository mocks, etc.
120
122
  });
121
- `;$(S,l);}for(let l of w)console.log(`[frs-hono] wrote ${l}`);for(let l of y)console.log(`[frs-hono] skipped ${l} (use --force to overwrite)`);console.log(`
122
- [frs-hono] reminder: run "frs-hono gen --root ${r}" to refresh the manifest.`);}async function ee(){let e=process.argv.slice(2),{command:t,flags:s}=Q(e);switch(t){case "gen":await X(s);return;case "new":await Y(e[1],s);return;case "help":case "--help":case "-h":F();return;default:console.error(`[frs-hono] unknown command: ${t}
123
- `),F(),process.exit(2);}}ee().catch(e=>{console.error(e),process.exit(1);});//# sourceMappingURL=cli.cjs.map
123
+ `;b(S,f);}for(let f of w)console.log(`[frs-hono] wrote ${f}`);for(let f of P)console.log(`[frs-hono] skipped ${f} (use --force to overwrite)`);console.log(`
124
+ [frs-hono] reminder: run "frs-hono gen --root ${r}" to refresh the manifest.`);}function ne(e,t){let s=["apis.ts","apis.js","api.ts","api.js"],r=[e,path.dirname(e),path.dirname(path.dirname(e))];for(let n of r)for(let i of s){let a=path.resolve(n,i);if(fs.existsSync(a)){let o=path.relative(t,a).replace(/\\/g,"/");return o=o.replace(/\.ts$/,".js").replace(/\.js$/,".js"),o.startsWith(".")||(o=`./${o}`),o}}return "../../../../apis.js"}async function oe(){let e=process.argv.slice(2),{command:t,flags:s}=ee(e);switch(t){case "gen":await te(s);return;case "new":await se(e[1],s);return;case "help":case "--help":case "-h":F();return;default:console.error(`[frs-hono] unknown command: ${t}
125
+ `),F(),process.exit(2);}}oe().catch(e=>{console.error(e),process.exit(1);});//# sourceMappingURL=cli.cjs.map
124
126
  //# sourceMappingURL=cli.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/servers/hono/codegen/path-utils.ts","../../../src/servers/hono/codegen/scanner.ts","../../../src/servers/hono/codegen/generator.ts","../../../src/servers/hono/cli.ts"],"names":["DEFAULT_DERIVE","derivePath","relativeDir","options","skip","s","p","kebab","toImportSpecifier","fromDir","toFile","ext","fromParts","splitAbs","toParts","common","up","down","stripped","finalLast","part","i","DEFAULT_SCANNER","scanRoutes","rootAbs","found","walk","a","b","root","dir","opts","out","entries","readdirSync","name","abs","join","st","statSync","relPath","relative","sep","relDir","DEFAULT_GENERATOR_BANNER","generateRoutesManifest","routes","outDir","dirname","mkdirSync","banner","now","importLines","entryLines","derivedPaths","r","importPath","url","body","writeFileSync","parseArgs","argv","command","rest","flags","arg","key","next","printHelp","asList","v","asString","runGen","resolve","existsSync","casing","derive","exclude","routesFile","scanned","result","source","runNew","domain","method","api","useCaseFolder","withUseCase","withTest","force","dirAbs","useCaseFile","testFile","className","useCaseSrc","inputZodSnippet","handlerBody","routesSrc","written","skipped","writeIfPossible","file","content","testSrc","f","main","err"],"mappings":"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","file":"cli.cjs","sourcesContent":["/**\n * URL path inference from filesystem layout.\n *\n * Convention: every `routes.ts` file under the configured root contributes\n * one route. The URL path is derived from its directory chain, optionally\n * skipping segments such as `useCases` so that\n *\n * domains/activities/useCases/createOrUpdateCustom/routes.ts\n *\n * becomes\n *\n * /activities/createOrUpdateCustom\n */\n\nexport interface PathDeriveOptions {\n /** Segments to drop from the derived path (case-insensitive). */\n skipSegments: string[];\n /**\n * Casing convention applied to each remaining segment.\n * - `\"preserve\"` — keep the directory name as-is (default),\n * - `\"kebab\"` — convert camelCase / PascalCase to kebab-case.\n */\n casing: \"preserve\" | \"kebab\";\n}\n\nexport const DEFAULT_DERIVE: PathDeriveOptions = {\n skipSegments: [\"useCases\", \"useCase\", \"use-cases\", \"use-case\"],\n casing: \"preserve\",\n};\n\n/**\n * @param relativeDir POSIX-style directory path of the routes file relative\n * to the codegen root (no leading slash, no `routes.ts`).\n */\nexport function derivePath(\n relativeDir: string,\n options: PathDeriveOptions = DEFAULT_DERIVE,\n): string {\n const skip = new Set(options.skipSegments.map((s) => s.toLowerCase()));\n const parts = relativeDir\n .split(\"/\")\n .filter(Boolean)\n .filter((p) => !skip.has(p.toLowerCase()))\n .map((p) => (options.casing === \"kebab\" ? kebab(p) : p));\n return \"/\" + parts.join(\"/\");\n}\n\nfunction kebab(s: string): string {\n return s\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .replace(/[\\s_]+/g, \"-\")\n .toLowerCase();\n}\n\n/**\n * Convert an absolute filesystem path to a POSIX-style import specifier\n * relative to a directory (the generated file's directory). Always uses\n * forward slashes and prefixes with `./` or `../` as needed.\n */\nexport function toImportSpecifier(\n fromDir: string,\n toFile: string,\n ext: string,\n): string {\n // Both paths are absolute POSIX (the CLI normalises them).\n const fromParts = splitAbs(fromDir);\n const toParts = splitAbs(toFile);\n let common = 0;\n while (\n common < fromParts.length &&\n common < toParts.length &&\n fromParts[common] === toParts[common]\n ) {\n common++;\n }\n const up = fromParts.length - common;\n const down = toParts.slice(common);\n const last = down[down.length - 1] ?? \"\";\n const stripped = last.replace(/\\.[mc]?[tj]sx?$/i, \"\");\n const finalLast = ext === \"\" ? stripped : `${stripped}${ext}`;\n down[down.length - 1] = finalLast;\n const prefix = up === 0 ? \"./\" : \"../\".repeat(up);\n return prefix + down.join(\"/\");\n}\n\nfunction splitAbs(p: string): string[] {\n const norm = p.replace(/\\\\/g, \"/\").replace(/\\/+$/, \"\");\n return norm.split(\"/\").filter((part, i) => !(i === 0 && part === \"\"));\n}\n","/**\n * Filesystem scanner — walks the configured root and yields every route file.\n * Synchronous and dependency-free (no `fast-glob` etc.) for a tiny CLI footprint.\n */\n\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join, relative, sep } from \"node:path\";\n\nexport interface ScannerOptions {\n /** Filename to look for (default: `routes.ts`). */\n routesFile: string;\n /** Glob-like exclude segments (matched against any path part). */\n excludeSegments: string[];\n}\n\nexport const DEFAULT_SCANNER: ScannerOptions = {\n routesFile: \"routes.ts\",\n excludeSegments: [\n \"node_modules\",\n \"__generated__\",\n \"tests\",\n \"__tests__\",\n \".turbo\",\n \"dist\",\n \"build\",\n \".next\",\n ],\n};\n\nexport interface ScannedRoute {\n /** Absolute path to the routes file. */\n absPath: string;\n /** Path relative to the scan root (POSIX style). */\n relPath: string;\n /** Directory portion of `relPath` (what {@link derivePath} consumes). */\n relDir: string;\n}\n\nexport function scanRoutes(\n rootAbs: string,\n options: ScannerOptions = DEFAULT_SCANNER,\n): ScannedRoute[] {\n const found: ScannedRoute[] = [];\n walk(rootAbs, rootAbs, options, found);\n // Stable, deterministic order — important for reproducible builds.\n found.sort((a, b) => a.relPath.localeCompare(b.relPath));\n return found;\n}\n\nfunction walk(\n root: string,\n dir: string,\n opts: ScannerOptions,\n out: ScannedRoute[],\n): void {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n if (opts.excludeSegments.includes(name)) continue;\n const abs = join(dir, name);\n let st;\n try {\n st = statSync(abs);\n } catch {\n continue;\n }\n if (st.isDirectory()) {\n walk(root, abs, opts, out);\n } else if (st.isFile() && name === opts.routesFile) {\n const relPath = relative(root, abs).split(sep).join(\"/\");\n const relDir = relPath.replace(/\\/?[^/]+$/, \"\");\n out.push({ absPath: abs, relPath, relDir });\n }\n }\n}\n","/**\n * Generator — emits `__generated__/routes.ts` from a list of {@link ScannedRoute}s.\n *\n * The generated module statically imports every `routes.ts` so that bundlers\n * (esbuild/tsup) can tree-shake unused dependencies and Cloud Functions get\n * the smallest possible cold-start footprint.\n */\n\nimport { dirname, join } from \"node:path\";\nimport { mkdirSync, writeFileSync } from \"node:fs\";\n\nimport {\n derivePath,\n toImportSpecifier,\n type PathDeriveOptions,\n} from \"./path-utils\";\nimport type { ScannedRoute } from \"./scanner\";\n\nexport interface GeneratorOptions {\n /** Absolute path of the file to write (e.g. `…/__generated__/routes.ts`). */\n outFile: string;\n /** Path-derivation options (skipSegments, casing). */\n derive: PathDeriveOptions;\n /**\n * Import extension used in the generated file:\n * - `\".js\"` (default) — required for ESM Node.js with `\"type\": \"module\"`,\n * - `\"\"` — bundler-friendly (Vite, no extension),\n * - `\".ts\"` — for TS-ESM runners (`tsx`, `bun`).\n */\n importExtension: string;\n /** Friendly banner placed at the top of the generated file. */\n banner?: string;\n /** Generation timestamp included in the banner. Default: `new Date()`. */\n now?: Date;\n}\n\nexport const DEFAULT_GENERATOR_BANNER =\n \"/**\\n\" +\n \" * AUTO-GENERATED by `@lpdjs/firestore-repo-service` Hono codegen.\\n\" +\n \" * Do not edit by hand — re-run `hono:gen` after adding / removing route files.\\n\" +\n \" */\\n\";\n\nexport interface GenerationResult {\n /** Absolute path of the file written. */\n outFile: string;\n /** Number of routes captured in the manifest. */\n routeCount: number;\n /** Human-readable summary of the derived URLs. */\n derivedPaths: { source: string; url: string }[];\n}\n\nexport function generateRoutesManifest(\n routes: ScannedRoute[],\n opts: GeneratorOptions,\n): GenerationResult {\n const outDir = dirname(opts.outFile);\n mkdirSync(outDir, { recursive: true });\n\n const banner = opts.banner ?? DEFAULT_GENERATOR_BANNER;\n const now = (opts.now ?? new Date()).toISOString();\n const ext = opts.importExtension;\n\n const importLines: string[] = [];\n const entryLines: string[] = [];\n const derivedPaths: GenerationResult[\"derivedPaths\"] = [];\n\n routes.forEach((r, i) => {\n const importPath = toImportSpecifier(outDir, r.absPath, ext);\n const url = derivePath(r.relDir, opts.derive);\n importLines.push(\n `import mod${i} from ${JSON.stringify(importPath)};`,\n );\n entryLines.push(` { __derivedPath: ${JSON.stringify(url)}, mod: mod${i} },`);\n derivedPaths.push({ source: r.relPath, url });\n });\n\n const body =\n `${banner}` +\n `// Generated at ${now} — ${routes.length} route file${routes.length === 1 ? \"\" : \"s\"}.\\n` +\n `\\n` +\n `import type { AnyRouteDef, RouteModuleDefault } from \"@lpdjs/firestore-repo-service/servers/hono\";\\n` +\n `\\n` +\n importLines.join(\"\\n\") +\n (importLines.length ? \"\\n\\n\" : \"\\n\") +\n `const __defs: { __derivedPath: string; mod: RouteModuleDefault }[] = [\\n` +\n entryLines.join(\"\\n\") +\n (entryLines.length ? \"\\n\" : \"\") +\n `];\\n\\n` +\n `export const routes: AnyRouteDef[] = __defs.flatMap(({ __derivedPath, mod }) => {\\n` +\n ` const list = Array.isArray(mod) ? mod : [mod];\\n` +\n ` return list.map((route) => ({ ...route, path: route.path ?? __derivedPath }));\\n` +\n `});\\n`;\n\n writeFileSync(opts.outFile, body, \"utf8\");\n return {\n outFile: opts.outFile,\n routeCount: routes.length,\n derivedPaths,\n };\n}\n\n/** Convenience helper used by the CLI — combines scan + generate in one call. */\nexport function generateFromRoot(\n rootAbs: string,\n outFileRel: string,\n derive: PathDeriveOptions,\n importExtension: string,\n scan: (root: string) => ScannedRoute[],\n): GenerationResult {\n const routes = scan(rootAbs);\n const outFile = join(rootAbs, outFileRel);\n return generateRoutesManifest(routes, {\n outFile,\n derive,\n importExtension,\n });\n}\n","#!/usr/bin/env node\n/**\n * `frs-hono` CLI — codegen for the file-based Hono server.\n *\n * Usage:\n * frs-hono gen --root src/domains\n * frs-hono gen --root src/domains --out __generated__/routes.ts \\\n * --skip useCases,useCase --casing preserve --ext .js\n *\n * Designed to be a **prebuild step** (e.g. wired into `npm run build`).\n * Outputs a manifest with static imports — no runtime filesystem scanning.\n */\n\nimport { resolve } from \"node:path\";\nimport { existsSync } from \"node:fs\";\n\nimport { DEFAULT_DERIVE, type PathDeriveOptions } from \"./codegen/path-utils\";\nimport {\n DEFAULT_SCANNER,\n scanRoutes,\n type ScannerOptions,\n} from \"./codegen/scanner\";\nimport { generateRoutesManifest } from \"./codegen/generator\";\n\ninterface ParsedArgs {\n command: string;\n flags: Record<string, string | boolean>;\n}\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const [command, ...rest] = argv;\n const flags: Record<string, string | boolean> = {};\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i]!;\n if (!arg.startsWith(\"--\")) continue;\n const key = arg.slice(2);\n const next = rest[i + 1];\n if (next && !next.startsWith(\"--\")) {\n flags[key] = next;\n i++;\n } else {\n flags[key] = true;\n }\n }\n return { command: command ?? \"help\", flags };\n}\n\nfunction printHelp(): void {\n // eslint-disable-next-line no-console\n console.log(`frs-hono — Hono file-based codegen\n\nUsage:\n frs-hono gen [flags]\n frs-hono new <name> [flags]\n frs-hono help\n\nFlags (gen):\n --root <dir> Domain root to scan (required, e.g. src/domains)\n --out <file> Output file relative to --root\n (default: __generated__/routes.ts)\n --routes-file <name> Filename to look for (default: routes.ts)\n --skip <list> Comma-separated path segments to drop from URLs\n (default: useCases,useCase,use-cases,use-case)\n --casing <preserve|kebab>\n Casing applied to remaining segments (default: preserve)\n --ext <.js|.ts|''> Import extension in the generated file\n (default: .js — required for ESM Node.js)\n --exclude <list> Comma-separated directories to skip\n (default: node_modules,__generated__,tests,__tests__,dist,build)\n --silent Do not print the generated route table\n\nFlags (new <name>):\n --root <dir> Domain root (default: src/domains)\n --domain <name> Domain name (required, e.g. posts)\n --method <verb> HTTP method (default: post)\n --api <tag> API tag (default: v1)\n --usecase-folder <name>\n Parent folder under <domain>. Default: useCases\n --with-usecase Also scaffold a sibling useCase.ts file (default: true)\n --with-test Also scaffold a sibling useCase.test.ts (Vitest, default: true)\n --force Overwrite if files already exist\n\nExamples:\n frs-hono new createPost --domain posts --method post\n frs-hono new listPosts --domain posts --method get --api v1\n`);\n}\n\nfunction asList(v: string | boolean | undefined): string[] | undefined {\n if (typeof v !== \"string\") return undefined;\n return v\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n}\n\nfunction asString(v: string | boolean | undefined): string | undefined {\n return typeof v === \"string\" ? v : undefined;\n}\n\nasync function runGen(flags: ParsedArgs[\"flags\"]): Promise<void> {\n const root = asString(flags.root);\n if (!root) {\n // eslint-disable-next-line no-console\n console.error(\"[frs-hono] --root is required\");\n process.exit(2);\n }\n const rootAbs = resolve(process.cwd(), root);\n if (!existsSync(rootAbs)) {\n // eslint-disable-next-line no-console\n console.error(`[frs-hono] root not found: ${rootAbs}`);\n process.exit(2);\n }\n\n const out = asString(flags.out) ?? \"__generated__/routes.ts\";\n\n const skip = asList(flags.skip) ?? DEFAULT_DERIVE.skipSegments;\n const casing =\n asString(flags.casing) === \"kebab\" ? \"kebab\" : DEFAULT_DERIVE.casing;\n const derive: PathDeriveOptions = { skipSegments: skip, casing };\n\n const ext = asString(flags.ext) ?? \".js\";\n const exclude = asList(flags.exclude) ?? DEFAULT_SCANNER.excludeSegments;\n const routesFile = asString(flags[\"routes-file\"]) ?? DEFAULT_SCANNER.routesFile;\n const scannerOpts: ScannerOptions = { routesFile, excludeSegments: exclude };\n\n const scanned = scanRoutes(rootAbs, scannerOpts);\n if (scanned.length === 0) {\n // eslint-disable-next-line no-console\n console.warn(\n `[frs-hono] no \"${routesFile}\" files found under ${rootAbs} — generated an empty manifest.`,\n );\n }\n\n const result = generateRoutesManifest(scanned, {\n outFile: resolve(rootAbs, out),\n derive,\n importExtension: ext,\n });\n\n if (!flags.silent) {\n // eslint-disable-next-line no-console\n console.log(\n `[frs-hono] wrote ${result.outFile} (${result.routeCount} route${\n result.routeCount === 1 ? \"\" : \"s\"\n })`,\n );\n for (const { source, url } of result.derivedPaths) {\n // eslint-disable-next-line no-console\n console.log(` ${url.padEnd(48)} ← ${source}`);\n }\n }\n}\n\nasync function runNew(name: string | undefined, flags: ParsedArgs[\"flags\"]): Promise<void> {\n if (!name || name.startsWith(\"--\")) {\n // eslint-disable-next-line no-console\n console.error(\"[frs-hono] usage: frs-hono new <name> --domain <domain> [flags]\");\n process.exit(2);\n }\n const domain = asString(flags.domain);\n if (!domain) {\n // eslint-disable-next-line no-console\n console.error(\"[frs-hono] --domain is required\");\n process.exit(2);\n }\n\n const root = asString(flags.root) ?? \"src/domains\";\n const method = (asString(flags.method) ?? \"post\").toLowerCase();\n const api = asString(flags.api) ?? \"v1\";\n const useCaseFolder = asString(flags[\"usecase-folder\"]) ?? \"useCases\";\n const withUseCase = flags[\"with-usecase\"] !== false;\n const withTest = flags[\"with-test\"] !== false;\n const force = flags.force === true;\n\n if (![\"get\", \"post\", \"put\", \"patch\", \"delete\"].includes(method)) {\n // eslint-disable-next-line no-console\n console.error(`[frs-hono] invalid --method: ${method}`);\n process.exit(2);\n }\n\n const rootAbs = resolve(process.cwd(), root);\n const dirAbs = resolve(rootAbs, domain, useCaseFolder, name);\n const routesFile = resolve(dirAbs, \"routes.ts\");\n const useCaseFile = resolve(dirAbs, \"useCase.ts\");\n const testFile = resolve(dirAbs, \"useCase.test.ts\");\n\n const { mkdirSync, existsSync, writeFileSync } = await import(\"node:fs\");\n mkdirSync(dirAbs, { recursive: true });\n\n const className = `${name.charAt(0).toUpperCase()}${name.slice(1)}UseCase`;\n\n const useCaseSrc = `/**\n * ${className} — pure business logic, no HTTP awareness.\n * Reusable across multiple routes / cron jobs / triggers.\n */\n\nexport interface ${className}Input {\n // TODO: define the input shape\n example: string;\n}\n\nexport interface ${className}Output {\n // TODO: define the output shape\n id: string;\n}\n\nexport class ${className} {\n // TODO: inject repositories / services via the constructor.\n // constructor(private readonly repo: SomeRepository) {}\n\n async execute(input: ${className}Input): Promise<${className}Output> {\n // TODO: implement\n return { id: input.example };\n }\n}\n`;\n\n const inputZodSnippet =\n method === \"get\"\n ? `z.object({\\n // GET → lu depuis les query params\\n example: z.string(),\\n })`\n : `z.object({\\n // ${method.toUpperCase()} → lu depuis le body JSON\\n example: z.string(),\\n })`;\n\n const handlerBody = withUseCase\n ? ` const useCase = new ${className}();\\n const data = await useCase.execute(input);\\n return data;`\n : ` // TODO: business logic\\n return { id: input.example };`;\n\n const useCaseImport = withUseCase\n ? `import { ${className} } from \"./useCase.js\";\\n`\n : \"\";\n\n const routesSrc = `import { z } from \"zod\";\nimport { defineRoute } from \"@lpdjs/firestore-repo-service/servers/hono\";\n${useCaseImport}\nexport default defineRoute({\n api: \"${api}\",\n method: \"${method}\",\n\n input: ${inputZodSnippet},\n\n output: z.object({\n id: z.string(),\n }),\n\n summary: \"TODO: ${name}\",\n tags: [\"${domain}\"],\n\n handler: async ({ input }) => {\n${handlerBody}\n },\n});\n`;\n\n const written: string[] = [];\n const skipped: string[] = [];\n\n const writeIfPossible = (file: string, content: string) => {\n if (existsSync(file) && !force) {\n skipped.push(file);\n return;\n }\n writeFileSync(file, content, \"utf8\");\n written.push(file);\n };\n\n writeIfPossible(routesFile, routesSrc);\n if (withUseCase) writeIfPossible(useCaseFile, useCaseSrc);\n if (withUseCase && withTest) {\n const testSrc = `import { describe, it, expect } from \"vitest\";\nimport { ${className} } from \"./useCase.js\";\n\ndescribe(\"${className}\", () => {\n it(\"returns a response shaped like the output schema\", async () => {\n const useCase = new ${className}();\n const result = await useCase.execute({ example: \"hello\" });\n expect(result).toMatchObject({ id: expect.any(String) });\n });\n\n // TODO: add error-path tests, repository mocks, etc.\n});\n`;\n writeIfPossible(testFile, testSrc);\n }\n\n // eslint-disable-next-line no-console\n for (const f of written) console.log(`[frs-hono] wrote ${f}`);\n for (const f of skipped)\n // eslint-disable-next-line no-console\n console.log(`[frs-hono] skipped ${f} (use --force to overwrite)`);\n // eslint-disable-next-line no-console\n console.log(\n `\\n[frs-hono] reminder: run \"frs-hono gen --root ${root}\" to refresh the manifest.`,\n );\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n const { command, flags } = parseArgs(argv);\n switch (command) {\n case \"gen\":\n await runGen(flags);\n return;\n case \"new\":\n // First positional after `new` is the route name.\n await runNew(argv[1], flags);\n return;\n case \"help\":\n case \"--help\":\n case \"-h\":\n printHelp();\n return;\n default:\n // eslint-disable-next-line no-console\n console.error(`[frs-hono] unknown command: ${command}\\n`);\n printHelp();\n process.exit(2);\n }\n}\n\nmain().catch((err) => {\n // eslint-disable-next-line no-console\n console.error(err);\n process.exit(1);\n});\n"]}
1
+ {"version":3,"sources":["../../../src/servers/hono/codegen/path-utils.ts","../../../src/servers/hono/codegen/scanner.ts","../../../src/servers/hono/codegen/generator.ts","../../../src/servers/hono/cli.ts"],"names":["DEFAULT_DERIVE","derivePath","relativeDir","options","skip","s","p","kebab","toImportSpecifier","fromDir","toFile","ext","fromParts","splitAbs","toParts","common","up","down","stripped","finalLast","part","i","DEFAULT_SCANNER","scanRoutes","rootAbs","found","walk","a","b","root","dir","opts","out","entries","readdirSync","name","abs","join","st","statSync","relPath","relative","sep","relDir","DEFAULT_GENERATOR_BANNER","generateRoutesManifest","routes","outDir","dirname","mkdirSync","banner","now","importLines","entryLines","derivedPaths","r","importPath","url","body","writeFileSync","parseArgs","argv","command","rest","flags","arg","key","next","printHelp","asList","v","asString","runGen","resolve","existsSync","casing","derive","exclude","routesFile","scanned","result","source","runNew","domain","method","api","useCaseFolder","withUseCase","withTest","force","dirAbs","useCaseFile","testFile","className","useCaseSrc","inputZodSnippet","handlerBody","useCaseImport","routesSrc","inferApisImportPath","written","skipped","writeIfPossible","file","content","testSrc","routeDirAbs","candidates","searchRoots","c","full","rel","main","err"],"mappings":"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","file":"cli.cjs","sourcesContent":["/**\n * URL path inference from filesystem layout.\n *\n * Convention: every `routes.ts` file under the configured root contributes\n * one route. The URL path is derived from its directory chain, optionally\n * skipping segments such as `useCases` so that\n *\n * domains/activities/useCases/createOrUpdateCustom/routes.ts\n *\n * becomes\n *\n * /activities/createOrUpdateCustom\n */\n\nexport interface PathDeriveOptions {\n /** Segments to drop from the derived path (case-insensitive). */\n skipSegments: string[];\n /**\n * Casing convention applied to each remaining segment.\n * - `\"preserve\"` — keep the directory name as-is (default),\n * - `\"kebab\"` — convert camelCase / PascalCase to kebab-case.\n */\n casing: \"preserve\" | \"kebab\";\n}\n\nexport const DEFAULT_DERIVE: PathDeriveOptions = {\n skipSegments: [\"useCases\", \"useCase\", \"use-cases\", \"use-case\"],\n casing: \"preserve\",\n};\n\n/**\n * @param relativeDir POSIX-style directory path of the routes file relative\n * to the codegen root (no leading slash, no `routes.ts`).\n */\nexport function derivePath(\n relativeDir: string,\n options: PathDeriveOptions = DEFAULT_DERIVE,\n): string {\n const skip = new Set(options.skipSegments.map((s) => s.toLowerCase()));\n const parts = relativeDir\n .split(\"/\")\n .filter(Boolean)\n .filter((p) => !skip.has(p.toLowerCase()))\n .map((p) => (options.casing === \"kebab\" ? kebab(p) : p));\n return \"/\" + parts.join(\"/\");\n}\n\nfunction kebab(s: string): string {\n return s\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .replace(/[\\s_]+/g, \"-\")\n .toLowerCase();\n}\n\n/**\n * Convert an absolute filesystem path to a POSIX-style import specifier\n * relative to a directory (the generated file's directory). Always uses\n * forward slashes and prefixes with `./` or `../` as needed.\n */\nexport function toImportSpecifier(\n fromDir: string,\n toFile: string,\n ext: string,\n): string {\n // Both paths are absolute POSIX (the CLI normalises them).\n const fromParts = splitAbs(fromDir);\n const toParts = splitAbs(toFile);\n let common = 0;\n while (\n common < fromParts.length &&\n common < toParts.length &&\n fromParts[common] === toParts[common]\n ) {\n common++;\n }\n const up = fromParts.length - common;\n const down = toParts.slice(common);\n const last = down[down.length - 1] ?? \"\";\n const stripped = last.replace(/\\.[mc]?[tj]sx?$/i, \"\");\n const finalLast = ext === \"\" ? stripped : `${stripped}${ext}`;\n down[down.length - 1] = finalLast;\n const prefix = up === 0 ? \"./\" : \"../\".repeat(up);\n return prefix + down.join(\"/\");\n}\n\nfunction splitAbs(p: string): string[] {\n const norm = p.replace(/\\\\/g, \"/\").replace(/\\/+$/, \"\");\n return norm.split(\"/\").filter((part, i) => !(i === 0 && part === \"\"));\n}\n","/**\n * Filesystem scanner — walks the configured root and yields every route file.\n * Synchronous and dependency-free (no `fast-glob` etc.) for a tiny CLI footprint.\n */\n\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join, relative, sep } from \"node:path\";\n\nexport interface ScannerOptions {\n /** Filename to look for (default: `routes.ts`). */\n routesFile: string;\n /** Glob-like exclude segments (matched against any path part). */\n excludeSegments: string[];\n}\n\nexport const DEFAULT_SCANNER: ScannerOptions = {\n routesFile: \"routes.ts\",\n excludeSegments: [\n \"node_modules\",\n \"__generated__\",\n \"tests\",\n \"__tests__\",\n \".turbo\",\n \"dist\",\n \"build\",\n \".next\",\n ],\n};\n\nexport interface ScannedRoute {\n /** Absolute path to the routes file. */\n absPath: string;\n /** Path relative to the scan root (POSIX style). */\n relPath: string;\n /** Directory portion of `relPath` (what {@link derivePath} consumes). */\n relDir: string;\n}\n\nexport function scanRoutes(\n rootAbs: string,\n options: ScannerOptions = DEFAULT_SCANNER,\n): ScannedRoute[] {\n const found: ScannedRoute[] = [];\n walk(rootAbs, rootAbs, options, found);\n // Stable, deterministic order — important for reproducible builds.\n found.sort((a, b) => a.relPath.localeCompare(b.relPath));\n return found;\n}\n\nfunction walk(\n root: string,\n dir: string,\n opts: ScannerOptions,\n out: ScannedRoute[],\n): void {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n if (opts.excludeSegments.includes(name)) continue;\n const abs = join(dir, name);\n let st;\n try {\n st = statSync(abs);\n } catch {\n continue;\n }\n if (st.isDirectory()) {\n walk(root, abs, opts, out);\n } else if (st.isFile() && name === opts.routesFile) {\n const relPath = relative(root, abs).split(sep).join(\"/\");\n const relDir = relPath.replace(/\\/?[^/]+$/, \"\");\n out.push({ absPath: abs, relPath, relDir });\n }\n }\n}\n","/**\n * Generator — emits `__generated__/routes.ts` from a list of {@link ScannedRoute}s.\n *\n * The generated module statically imports every `routes.ts` so that bundlers\n * (esbuild/tsup) can tree-shake unused dependencies and Cloud Functions get\n * the smallest possible cold-start footprint.\n */\n\nimport { dirname, join } from \"node:path\";\nimport { mkdirSync, writeFileSync } from \"node:fs\";\n\nimport {\n derivePath,\n toImportSpecifier,\n type PathDeriveOptions,\n} from \"./path-utils\";\nimport type { ScannedRoute } from \"./scanner\";\n\nexport interface GeneratorOptions {\n /** Absolute path of the file to write (e.g. `…/__generated__/routes.ts`). */\n outFile: string;\n /** Path-derivation options (skipSegments, casing). */\n derive: PathDeriveOptions;\n /**\n * Import extension used in the generated file:\n * - `\".js\"` (default) — required for ESM Node.js with `\"type\": \"module\"`,\n * - `\"\"` — bundler-friendly (Vite, no extension),\n * - `\".ts\"` — for TS-ESM runners (`tsx`, `bun`).\n */\n importExtension: string;\n /** Friendly banner placed at the top of the generated file. */\n banner?: string;\n /** Generation timestamp included in the banner. Default: `new Date()`. */\n now?: Date;\n}\n\nexport const DEFAULT_GENERATOR_BANNER =\n \"/**\\n\" +\n \" * AUTO-GENERATED by `@lpdjs/firestore-repo-service` Hono codegen.\\n\" +\n \" * Do not edit by hand — re-run `hono:gen` after adding / removing route files.\\n\" +\n \" */\\n\";\n\nexport interface GenerationResult {\n /** Absolute path of the file written. */\n outFile: string;\n /** Number of routes captured in the manifest. */\n routeCount: number;\n /** Human-readable summary of the derived URLs. */\n derivedPaths: { source: string; url: string }[];\n}\n\nexport function generateRoutesManifest(\n routes: ScannedRoute[],\n opts: GeneratorOptions,\n): GenerationResult {\n const outDir = dirname(opts.outFile);\n mkdirSync(outDir, { recursive: true });\n\n const banner = opts.banner ?? DEFAULT_GENERATOR_BANNER;\n const now = (opts.now ?? new Date()).toISOString();\n const ext = opts.importExtension;\n\n const importLines: string[] = [];\n const entryLines: string[] = [];\n const derivedPaths: GenerationResult[\"derivedPaths\"] = [];\n\n routes.forEach((r, i) => {\n const importPath = toImportSpecifier(outDir, r.absPath, ext);\n const url = derivePath(r.relDir, opts.derive);\n importLines.push(\n `import mod${i} from ${JSON.stringify(importPath)};`,\n );\n entryLines.push(` { __derivedPath: ${JSON.stringify(url)}, mod: mod${i} },`);\n derivedPaths.push({ source: r.relPath, url });\n });\n\n const body =\n `${banner}` +\n `// Generated at ${now} — ${routes.length} route file${routes.length === 1 ? \"\" : \"s\"}.\\n` +\n `\\n` +\n `import type { AnyRouteDef, RouteModuleDefault } from \"@lpdjs/firestore-repo-service/servers/hono\";\\n` +\n `\\n` +\n importLines.join(\"\\n\") +\n (importLines.length ? \"\\n\\n\" : \"\\n\") +\n `const __defs: { __derivedPath: string; mod: RouteModuleDefault }[] = [\\n` +\n entryLines.join(\"\\n\") +\n (entryLines.length ? \"\\n\" : \"\") +\n `];\\n\\n` +\n `export const routes: AnyRouteDef[] = __defs.flatMap(({ __derivedPath, mod }) => {\\n` +\n ` const list = Array.isArray(mod) ? mod : [mod];\\n` +\n ` return list.map((route) => ({ ...route, path: route.path ?? __derivedPath }));\\n` +\n `});\\n`;\n\n writeFileSync(opts.outFile, body, \"utf8\");\n return {\n outFile: opts.outFile,\n routeCount: routes.length,\n derivedPaths,\n };\n}\n\n/** Convenience helper used by the CLI — combines scan + generate in one call. */\nexport function generateFromRoot(\n rootAbs: string,\n outFileRel: string,\n derive: PathDeriveOptions,\n importExtension: string,\n scan: (root: string) => ScannedRoute[],\n): GenerationResult {\n const routes = scan(rootAbs);\n const outFile = join(rootAbs, outFileRel);\n return generateRoutesManifest(routes, {\n outFile,\n derive,\n importExtension,\n });\n}\n","#!/usr/bin/env node\n/**\n * `frs-hono` CLI — codegen for the file-based Hono server.\n *\n * Usage:\n * frs-hono gen --root src/domains\n * frs-hono gen --root src/domains --out __generated__/routes.ts \\\n * --skip useCases,useCase --casing preserve --ext .js\n *\n * Designed to be a **prebuild step** (e.g. wired into `npm run build`).\n * Outputs a manifest with static imports — no runtime filesystem scanning.\n */\n\nimport { resolve, relative, dirname } from \"node:path\";\nimport { existsSync } from \"node:fs\";\n\nimport { DEFAULT_DERIVE, type PathDeriveOptions } from \"./codegen/path-utils\";\nimport {\n DEFAULT_SCANNER,\n scanRoutes,\n type ScannerOptions,\n} from \"./codegen/scanner\";\nimport { generateRoutesManifest } from \"./codegen/generator\";\n\ninterface ParsedArgs {\n command: string;\n flags: Record<string, string | boolean>;\n}\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const [command, ...rest] = argv;\n const flags: Record<string, string | boolean> = {};\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i]!;\n if (!arg.startsWith(\"--\")) continue;\n const key = arg.slice(2);\n const next = rest[i + 1];\n if (next && !next.startsWith(\"--\")) {\n flags[key] = next;\n i++;\n } else {\n flags[key] = true;\n }\n }\n return { command: command ?? \"help\", flags };\n}\n\nfunction printHelp(): void {\n // eslint-disable-next-line no-console\n console.log(`frs-hono — Hono file-based codegen\n\nUsage:\n frs-hono gen [flags]\n frs-hono new <name> [flags]\n frs-hono help\n\nFlags (gen):\n --root <dir> Domain root to scan (required, e.g. src/domains)\n --out <file> Output file relative to --root\n (default: __generated__/routes.ts)\n --routes-file <name> Filename to look for (default: routes.ts)\n --skip <list> Comma-separated path segments to drop from URLs\n (default: useCases,useCase,use-cases,use-case)\n --casing <preserve|kebab>\n Casing applied to remaining segments (default: preserve)\n --ext <.js|.ts|''> Import extension in the generated file\n (default: .js — required for ESM Node.js)\n --exclude <list> Comma-separated directories to skip\n (default: node_modules,__generated__,tests,__tests__,dist,build)\n --silent Do not print the generated route table\n\nFlags (new <name>):\n --root <dir> Domain root (default: src/domains)\n --domain <name> Domain name (required, e.g. posts)\n --method <verb> HTTP method (default: post)\n --api <tag> API tag (default: v1)\n --usecase-folder <name>\n Parent folder under <domain>. Default: useCases\n --with-usecase Also scaffold a sibling useCase.ts file (default: true)\n --with-test Also scaffold a sibling useCase.test.ts (Vitest, default: true)\n --apis-import <path> Import path for the registry (default: auto-detect\n ../../../../apis.js — adjust if your layout differs)\n --force Overwrite if files already exist\n\nExamples:\n frs-hono new createPost --domain posts --method post\n frs-hono new listPosts --domain posts --method get --api v1\n`);\n}\n\nfunction asList(v: string | boolean | undefined): string[] | undefined {\n if (typeof v !== \"string\") return undefined;\n return v\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n}\n\nfunction asString(v: string | boolean | undefined): string | undefined {\n return typeof v === \"string\" ? v : undefined;\n}\n\nasync function runGen(flags: ParsedArgs[\"flags\"]): Promise<void> {\n const root = asString(flags.root);\n if (!root) {\n // eslint-disable-next-line no-console\n console.error(\"[frs-hono] --root is required\");\n process.exit(2);\n }\n const rootAbs = resolve(process.cwd(), root);\n if (!existsSync(rootAbs)) {\n // eslint-disable-next-line no-console\n console.error(`[frs-hono] root not found: ${rootAbs}`);\n process.exit(2);\n }\n\n const out = asString(flags.out) ?? \"__generated__/routes.ts\";\n\n const skip = asList(flags.skip) ?? DEFAULT_DERIVE.skipSegments;\n const casing =\n asString(flags.casing) === \"kebab\" ? \"kebab\" : DEFAULT_DERIVE.casing;\n const derive: PathDeriveOptions = { skipSegments: skip, casing };\n\n const ext = asString(flags.ext) ?? \".js\";\n const exclude = asList(flags.exclude) ?? DEFAULT_SCANNER.excludeSegments;\n const routesFile = asString(flags[\"routes-file\"]) ?? DEFAULT_SCANNER.routesFile;\n const scannerOpts: ScannerOptions = { routesFile, excludeSegments: exclude };\n\n const scanned = scanRoutes(rootAbs, scannerOpts);\n if (scanned.length === 0) {\n // eslint-disable-next-line no-console\n console.warn(\n `[frs-hono] no \"${routesFile}\" files found under ${rootAbs} — generated an empty manifest.`,\n );\n }\n\n const result = generateRoutesManifest(scanned, {\n outFile: resolve(rootAbs, out),\n derive,\n importExtension: ext,\n });\n\n if (!flags.silent) {\n // eslint-disable-next-line no-console\n console.log(\n `[frs-hono] wrote ${result.outFile} (${result.routeCount} route${\n result.routeCount === 1 ? \"\" : \"s\"\n })`,\n );\n for (const { source, url } of result.derivedPaths) {\n // eslint-disable-next-line no-console\n console.log(` ${url.padEnd(48)} ← ${source}`);\n }\n }\n}\n\nasync function runNew(name: string | undefined, flags: ParsedArgs[\"flags\"]): Promise<void> {\n if (!name || name.startsWith(\"--\")) {\n // eslint-disable-next-line no-console\n console.error(\"[frs-hono] usage: frs-hono new <name> --domain <domain> [flags]\");\n process.exit(2);\n }\n const domain = asString(flags.domain);\n if (!domain) {\n // eslint-disable-next-line no-console\n console.error(\"[frs-hono] --domain is required\");\n process.exit(2);\n }\n\n const root = asString(flags.root) ?? \"src/domains\";\n const method = (asString(flags.method) ?? \"post\").toLowerCase();\n const api = asString(flags.api) ?? \"v1\";\n const useCaseFolder = asString(flags[\"usecase-folder\"]) ?? \"useCases\";\n const withUseCase = flags[\"with-usecase\"] !== false;\n const withTest = flags[\"with-test\"] !== false;\n const force = flags.force === true;\n\n if (![\"get\", \"post\", \"put\", \"patch\", \"delete\"].includes(method)) {\n // eslint-disable-next-line no-console\n console.error(`[frs-hono] invalid --method: ${method}`);\n process.exit(2);\n }\n\n const rootAbs = resolve(process.cwd(), root);\n const dirAbs = resolve(rootAbs, domain, useCaseFolder, name);\n const routesFile = resolve(dirAbs, \"routes.ts\");\n const useCaseFile = resolve(dirAbs, \"useCase.ts\");\n const testFile = resolve(dirAbs, \"useCase.test.ts\");\n\n const { mkdirSync, existsSync, writeFileSync } = await import(\"node:fs\");\n mkdirSync(dirAbs, { recursive: true });\n\n const className = `${name.charAt(0).toUpperCase()}${name.slice(1)}UseCase`;\n\n const useCaseSrc = `/**\n * ${className} — pure business logic, no HTTP awareness.\n * Reusable across multiple routes / cron jobs / triggers.\n */\n\nexport interface ${className}Input {\n // TODO: define the input shape\n example: string;\n}\n\nexport interface ${className}Output {\n // TODO: define the output shape\n id: string;\n}\n\nexport class ${className} {\n // TODO: inject repositories / services via the constructor.\n // constructor(private readonly repo: SomeRepository) {}\n\n async execute(input: ${className}Input): Promise<${className}Output> {\n // TODO: implement\n return { id: input.example };\n }\n}\n`;\n\n const inputZodSnippet =\n method === \"get\"\n ? `z.object({\\n // GET → lu depuis les query params\\n example: z.string(),\\n })`\n : `z.object({\\n // ${method.toUpperCase()} → lu depuis le body JSON\\n example: z.string(),\\n })`;\n\n const handlerBody = withUseCase\n ? ` const useCase = new ${className}();\\n const data = await useCase.execute(input);\\n return data;`\n : ` // TODO: business logic\\n return { id: input.example };`;\n\n const useCaseImport = withUseCase\n ? `import { ${className} } from \"./useCase.js\";\\n`\n : \"\";\n\n const apisImport =\n asString(flags[\"apis-import\"]) ??\n inferApisImportPath(rootAbs, dirAbs);\n\n const routesSrc = `import { z } from \"zod\";\nimport { defineRoute } from \"${apisImport}\";\n${useCaseImport}\nexport default defineRoute({\n api: \"${api}\",\n method: \"${method}\",\n\n input: ${inputZodSnippet},\n\n output: z.object({\n id: z.string(),\n }),\n\n summary: \"TODO: ${name}\",\n tags: [\"${domain}\"],\n\n handler: async ({ input }) => {\n${handlerBody}\n },\n});\n`;\n\n const written: string[] = [];\n const skipped: string[] = [];\n\n const writeIfPossible = (file: string, content: string) => {\n if (existsSync(file) && !force) {\n skipped.push(file);\n return;\n }\n writeFileSync(file, content, \"utf8\");\n written.push(file);\n };\n\n writeIfPossible(routesFile, routesSrc);\n if (withUseCase) writeIfPossible(useCaseFile, useCaseSrc);\n if (withUseCase && withTest) {\n const testSrc = `import { describe, it, expect } from \"vitest\";\nimport { ${className} } from \"./useCase.js\";\n\ndescribe(\"${className}\", () => {\n it(\"returns a response shaped like the output schema\", async () => {\n const useCase = new ${className}();\n const result = await useCase.execute({ example: \"hello\" });\n expect(result).toMatchObject({ id: expect.any(String) });\n });\n\n // TODO: add error-path tests, repository mocks, etc.\n});\n`;\n writeIfPossible(testFile, testSrc);\n }\n\n // eslint-disable-next-line no-console\n for (const f of written) console.log(`[frs-hono] wrote ${f}`);\n for (const f of skipped)\n // eslint-disable-next-line no-console\n console.log(`[frs-hono] skipped ${f} (use --force to overwrite)`);\n // eslint-disable-next-line no-console\n console.log(\n `\\n[frs-hono] reminder: run \"frs-hono gen --root ${root}\" to refresh the manifest.`,\n );\n}\n\n/**\n * Try to find the user's `apis.ts` (or similar) file and return a relative\n * import path from the new route file. Falls back to a sensible placeholder.\n */\nfunction inferApisImportPath(rootAbs: string, routeDirAbs: string): string {\n const candidates = [\"apis.ts\", \"apis.js\", \"api.ts\", \"api.js\"];\n // Search upwards from rootAbs's parent (typical layout: src/apis.ts + src/domains/…)\n const searchRoots = [\n rootAbs,\n dirname(rootAbs),\n dirname(dirname(rootAbs)),\n ];\n for (const dir of searchRoots) {\n for (const c of candidates) {\n const full = resolve(dir, c);\n if (existsSync(full)) {\n let rel = relative(routeDirAbs, full).replace(/\\\\/g, \"/\");\n rel = rel.replace(/\\.ts$/, \".js\").replace(/\\.js$/, \".js\");\n if (!rel.startsWith(\".\")) rel = `./${rel}`;\n return rel;\n }\n }\n }\n return \"../../../../apis.js\";\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n const { command, flags } = parseArgs(argv);\n switch (command) {\n case \"gen\":\n await runGen(flags);\n return;\n case \"new\":\n // First positional after `new` is the route name.\n await runNew(argv[1], flags);\n return;\n case \"help\":\n case \"--help\":\n case \"-h\":\n printHelp();\n return;\n default:\n // eslint-disable-next-line no-console\n console.error(`[frs-hono] unknown command: ${command}\\n`);\n printHelp();\n process.exit(2);\n }\n}\n\nmain().catch((err) => {\n // eslint-disable-next-line no-console\n console.error(err);\n process.exit(1);\n});\n"]}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import {resolve,dirname,join,relative,sep}from'path';import {existsSync,mkdirSync,writeFileSync,readdirSync,statSync}from'fs';var v={skipSegments:["useCases","useCase","use-cases","use-case"],casing:"preserve"};function P(e,t=v){let s=new Set(t.skipSegments.map(o=>o.toLowerCase()));return "/"+e.split("/").filter(Boolean).filter(o=>!s.has(o.toLowerCase())).map(o=>t.casing==="kebab"?G(o):o).join("/")}function G(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}function O(e,t,s){let r=_(e),o=_(t),i=0;for(;i<r.length&&i<o.length&&r[i]===o[i];)i++;let a=r.length-i,n=o.slice(i),c=(n[n.length-1]??"").replace(/\.[mc]?[tj]sx?$/i,""),m=s===""?c:`${c}${s}`;return n[n.length-1]=m,(a===0?"./":"../".repeat(a))+n.join("/")}function _(e){return e.replace(/\\/g,"/").replace(/\/+$/,"").split("/").filter((s,r)=>!(r===0&&s===""))}var b={routesFile:"routes.ts",excludeSegments:["node_modules","__generated__","tests","__tests__",".turbo","dist","build",".next"]};function D(e,t=b){let s=[];return C(e,e,t,s),s.sort((r,o)=>r.relPath.localeCompare(o.relPath)),s}function C(e,t,s,r){let o;try{o=readdirSync(t);}catch{return}for(let i of o){if(s.excludeSegments.includes(i))continue;let a=join(t,i),n;try{n=statSync(a);}catch{continue}if(n.isDirectory())C(e,a,s,r);else if(n.isFile()&&i===s.routesFile){let d=relative(e,a).split(sep).join("/"),c=d.replace(/\/?[^/]+$/,"");r.push({absPath:a,relPath:d,relDir:c});}}}var Z="/**\n * AUTO-GENERATED by `@lpdjs/firestore-repo-service` Hono codegen.\n * Do not edit by hand \u2014 re-run `hono:gen` after adding / removing route files.\n */\n";function R(e,t){let s=dirname(t.outFile);mkdirSync(s,{recursive:true});let r=t.banner??Z,o=(t.now??new Date).toISOString(),i=t.importExtension,a=[],n=[],d=[];e.forEach((m,u)=>{let g=O(s,m.absPath,i),x=P(m.relDir,t.derive);a.push(`import mod${u} from ${JSON.stringify(g)};`),n.push(` { __derivedPath: ${JSON.stringify(x)}, mod: mod${u} },`),d.push({source:m.relPath,url:x});});let c=`${r}// Generated at ${o} \u2014 ${e.length} route file${e.length===1?"":"s"}.
2
+ import {resolve,dirname,relative,join,sep}from'path';import {existsSync,mkdirSync,writeFileSync,readdirSync,statSync}from'fs';var $={skipSegments:["useCases","useCase","use-cases","use-case"],casing:"preserve"};function O(e,t=$){let s=new Set(t.skipSegments.map(n=>n.toLowerCase()));return "/"+e.split("/").filter(Boolean).filter(n=>!s.has(n.toLowerCase())).map(n=>t.casing==="kebab"?q(n):n).join("/")}function q(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}function D(e,t,s){let r=_(e),n=_(t),i=0;for(;i<r.length&&i<n.length&&r[i]===n[i];)i++;let a=r.length-i,o=n.slice(i),u=(o[o.length-1]??"").replace(/\.[mc]?[tj]sx?$/i,""),m=s===""?u:`${u}${s}`;return o[o.length-1]=m,(a===0?"./":"../".repeat(a))+o.join("/")}function _(e){return e.replace(/\\/g,"/").replace(/\/+$/,"").split("/").filter((s,r)=>!(r===0&&s===""))}var v={routesFile:"routes.ts",excludeSegments:["node_modules","__generated__","tests","__tests__",".turbo","dist","build",".next"]};function C(e,t=v){let s=[];return R(e,e,t,s),s.sort((r,n)=>r.relPath.localeCompare(n.relPath)),s}function R(e,t,s,r){let n;try{n=readdirSync(t);}catch{return}for(let i of n){if(s.excludeSegments.includes(i))continue;let a=join(t,i),o;try{o=statSync(a);}catch{continue}if(o.isDirectory())R(e,a,s,r);else if(o.isFile()&&i===s.routesFile){let d=relative(e,a).split(sep).join("/"),u=d.replace(/\/?[^/]+$/,"");r.push({absPath:a,relPath:d,relDir:u});}}}var X="/**\n * AUTO-GENERATED by `@lpdjs/firestore-repo-service` Hono codegen.\n * Do not edit by hand \u2014 re-run `hono:gen` after adding / removing route files.\n */\n";function j(e,t){let s=dirname(t.outFile);mkdirSync(s,{recursive:true});let r=t.banner??X,n=(t.now??new Date).toISOString(),i=t.importExtension,a=[],o=[],d=[];e.forEach((m,c)=>{let g=D(s,m.absPath,i),x=O(m.relDir,t.derive);a.push(`import mod${c} from ${JSON.stringify(g)};`),o.push(` { __derivedPath: ${JSON.stringify(x)}, mod: mod${c} },`),d.push({source:m.relPath,url:x});});let u=`${r}// Generated at ${n} \u2014 ${e.length} route file${e.length===1?"":"s"}.
3
3
 
4
4
  import type { AnyRouteDef, RouteModuleDefault } from "@lpdjs/firestore-repo-service/servers/hono";
5
5
 
@@ -8,15 +8,15 @@ import type { AnyRouteDef, RouteModuleDefault } from "@lpdjs/firestore-repo-serv
8
8
 
9
9
  `:`
10
10
  `)+`const __defs: { __derivedPath: string; mod: RouteModuleDefault }[] = [
11
- `+n.join(`
12
- `)+(n.length?`
11
+ `+o.join(`
12
+ `)+(o.length?`
13
13
  `:"")+`];
14
14
 
15
15
  export const routes: AnyRouteDef[] = __defs.flatMap(({ __derivedPath, mod }) => {
16
16
  const list = Array.isArray(mod) ? mod : [mod];
17
17
  return list.map((route) => ({ ...route, path: route.path ?? __derivedPath }));
18
18
  });
19
- `;return writeFileSync(t.outFile,c,"utf8"),{outFile:t.outFile,routeCount:e.length,derivedPaths:d}}function Q(e){let[t,...s]=e,r={};for(let o=0;o<s.length;o++){let i=s[o];if(!i.startsWith("--"))continue;let a=i.slice(2),n=s[o+1];n&&!n.startsWith("--")?(r[a]=n,o++):r[a]=true;}return {command:t??"help",flags:r}}function F(){console.log(`frs-hono \u2014 Hono file-based codegen
19
+ `;return writeFileSync(t.outFile,u,"utf8"),{outFile:t.outFile,routeCount:e.length,derivedPaths:d}}function ee(e){let[t,...s]=e,r={};for(let n=0;n<s.length;n++){let i=s[n];if(!i.startsWith("--"))continue;let a=i.slice(2),o=s[n+1];o&&!o.startsWith("--")?(r[a]=o,n++):r[a]=true;}return {command:t??"help",flags:r}}function F(){console.log(`frs-hono \u2014 Hono file-based codegen
20
20
 
21
21
  Usage:
22
22
  frs-hono gen [flags]
@@ -47,12 +47,14 @@ Flags (new <name>):
47
47
  Parent folder under <domain>. Default: useCases
48
48
  --with-usecase Also scaffold a sibling useCase.ts file (default: true)
49
49
  --with-test Also scaffold a sibling useCase.test.ts (Vitest, default: true)
50
+ --apis-import <path> Import path for the registry (default: auto-detect
51
+ ../../../../apis.js \u2014 adjust if your layout differs)
50
52
  --force Overwrite if files already exist
51
53
 
52
54
  Examples:
53
55
  frs-hono new createPost --domain posts --method post
54
56
  frs-hono new listPosts --domain posts --method get --api v1
55
- `);}function A(e){if(typeof e=="string")return e.split(",").map(t=>t.trim()).filter(Boolean)}function f(e){return typeof e=="string"?e:void 0}async function X(e){let t=f(e.root);t||(console.error("[frs-hono] --root is required"),process.exit(2));let s=resolve(process.cwd(),t);existsSync(s)||(console.error(`[frs-hono] root not found: ${s}`),process.exit(2));let r=f(e.out)??"__generated__/routes.ts",o=A(e.skip)??v.skipSegments,i=f(e.casing)==="kebab"?"kebab":v.casing,a={skipSegments:o,casing:i},n=f(e.ext)??".js",d=A(e.exclude)??b.excludeSegments,c=f(e["routes-file"])??b.routesFile,u=D(s,{routesFile:c,excludeSegments:d});u.length===0&&console.warn(`[frs-hono] no "${c}" files found under ${s} \u2014 generated an empty manifest.`);let g=R(u,{outFile:resolve(s,r),derive:a,importExtension:n});if(!e.silent){console.log(`[frs-hono] wrote ${g.outFile} (${g.routeCount} route${g.routeCount===1?"":"s"})`);for(let{source:x,url:S}of g.derivedPaths)console.log(` ${S.padEnd(48)} \u2190 ${x}`);}}async function Y(e,t){(!e||e.startsWith("--"))&&(console.error("[frs-hono] usage: frs-hono new <name> --domain <domain> [flags]"),process.exit(2));let s=f(t.domain);s||(console.error("[frs-hono] --domain is required"),process.exit(2));let r=f(t.root)??"src/domains",o=(f(t.method)??"post").toLowerCase(),i=f(t.api)??"v1",a=f(t["usecase-folder"])??"useCases",n=t["with-usecase"]!==false,d=t["with-test"]!==false,c=t.force===true;["get","post","put","patch","delete"].includes(o)||(console.error(`[frs-hono] invalid --method: ${o}`),process.exit(2));let m=resolve(process.cwd(),r),u=resolve(m,s,a,e),g=resolve(u,"routes.ts"),x=resolve(u,"useCase.ts"),S=resolve(u,"useCase.test.ts"),{mkdirSync:k,existsSync:E,writeFileSync:j}=await import('fs');k(u,{recursive:true});let p=`${e.charAt(0).toUpperCase()}${e.slice(1)}UseCase`,T=`/**
57
+ `);}function k(e){if(typeof e=="string")return e.split(",").map(t=>t.trim()).filter(Boolean)}function l(e){return typeof e=="string"?e:void 0}async function te(e){let t=l(e.root);t||(console.error("[frs-hono] --root is required"),process.exit(2));let s=resolve(process.cwd(),t);existsSync(s)||(console.error(`[frs-hono] root not found: ${s}`),process.exit(2));let r=l(e.out)??"__generated__/routes.ts",n=k(e.skip)??$.skipSegments,i=l(e.casing)==="kebab"?"kebab":$.casing,a={skipSegments:n,casing:i},o=l(e.ext)??".js",d=k(e.exclude)??v.excludeSegments,u=l(e["routes-file"])??v.routesFile,c=C(s,{routesFile:u,excludeSegments:d});c.length===0&&console.warn(`[frs-hono] no "${u}" files found under ${s} \u2014 generated an empty manifest.`);let g=j(c,{outFile:resolve(s,r),derive:a,importExtension:o});if(!e.silent){console.log(`[frs-hono] wrote ${g.outFile} (${g.routeCount} route${g.routeCount===1?"":"s"})`);for(let{source:x,url:S}of g.derivedPaths)console.log(` ${S.padEnd(48)} \u2190 ${x}`);}}async function se(e,t){(!e||e.startsWith("--"))&&(console.error("[frs-hono] usage: frs-hono new <name> --domain <domain> [flags]"),process.exit(2));let s=l(t.domain);s||(console.error("[frs-hono] --domain is required"),process.exit(2));let r=l(t.root)??"src/domains",n=(l(t.method)??"post").toLowerCase(),i=l(t.api)??"v1",a=l(t["usecase-folder"])??"useCases",o=t["with-usecase"]!==false,d=t["with-test"]!==false,u=t.force===true;["get","post","put","patch","delete"].includes(n)||(console.error(`[frs-hono] invalid --method: ${n}`),process.exit(2));let m=resolve(process.cwd(),r),c=resolve(m,s,a,e),g=resolve(c,"routes.ts"),x=resolve(c,"useCase.ts"),S=resolve(c,"useCase.test.ts"),{mkdirSync:E,existsSync:T,writeFileSync:I}=await import('fs');E(c,{recursive:true});let p=`${e.charAt(0).toUpperCase()}${e.slice(1)}UseCase`,L=`/**
56
58
  * ${p} \u2014 pure business logic, no HTTP awareness.
57
59
  * Reusable across multiple routes / cron jobs / triggers.
58
60
  */
@@ -76,24 +78,24 @@ export class ${p} {
76
78
  return { id: input.example };
77
79
  }
78
80
  }
79
- `,L=o==="get"?`z.object({
81
+ `,N=n==="get"?`z.object({
80
82
  // GET \u2192 lu depuis les query params
81
83
  example: z.string(),
82
84
  })`:`z.object({
83
- // ${o.toUpperCase()} \u2192 lu depuis le body JSON
85
+ // ${n.toUpperCase()} \u2192 lu depuis le body JSON
84
86
  example: z.string(),
85
- })`,N=n?` const useCase = new ${p}();
87
+ })`,U=o?` const useCase = new ${p}();
86
88
  const data = await useCase.execute(input);
87
89
  return data;`:` // TODO: business logic
88
- return { id: input.example };`,I=`import { z } from "zod";
89
- import { defineRoute } from "@lpdjs/firestore-repo-service/servers/hono";
90
- ${n?`import { ${p} } from "./useCase.js";
91
- `:""}
90
+ return { id: input.example };`,G=o?`import { ${p} } from "./useCase.js";
91
+ `:"",z=`import { z } from "zod";
92
+ import { defineRoute } from "${l(t["apis-import"])??ne(m,c)}";
93
+ ${G}
92
94
  export default defineRoute({
93
95
  api: "${i}",
94
- method: "${o}",
96
+ method: "${n}",
95
97
 
96
- input: ${L},
98
+ input: ${N},
97
99
 
98
100
  output: z.object({
99
101
  id: z.string(),
@@ -103,10 +105,10 @@ export default defineRoute({
103
105
  tags: ["${s}"],
104
106
 
105
107
  handler: async ({ input }) => {
106
- ${N}
108
+ ${U}
107
109
  },
108
110
  });
109
- `,w=[],y=[],$=(l,U)=>{if(E(l)&&!c){y.push(l);return}j(l,U,"utf8"),w.push(l);};if($(g,I),n&&$(x,T),n&&d){let l=`import { describe, it, expect } from "vitest";
111
+ `,w=[],P=[],b=(f,M)=>{if(T(f)&&!u){P.push(f);return}I(f,M,"utf8"),w.push(f);};if(b(g,z),o&&b(x,L),o&&d){let f=`import { describe, it, expect } from "vitest";
110
112
  import { ${p} } from "./useCase.js";
111
113
 
112
114
  describe("${p}", () => {
@@ -118,7 +120,7 @@ describe("${p}", () => {
118
120
 
119
121
  // TODO: add error-path tests, repository mocks, etc.
120
122
  });
121
- `;$(S,l);}for(let l of w)console.log(`[frs-hono] wrote ${l}`);for(let l of y)console.log(`[frs-hono] skipped ${l} (use --force to overwrite)`);console.log(`
122
- [frs-hono] reminder: run "frs-hono gen --root ${r}" to refresh the manifest.`);}async function ee(){let e=process.argv.slice(2),{command:t,flags:s}=Q(e);switch(t){case "gen":await X(s);return;case "new":await Y(e[1],s);return;case "help":case "--help":case "-h":F();return;default:console.error(`[frs-hono] unknown command: ${t}
123
- `),F(),process.exit(2);}}ee().catch(e=>{console.error(e),process.exit(1);});//# sourceMappingURL=cli.js.map
123
+ `;b(S,f);}for(let f of w)console.log(`[frs-hono] wrote ${f}`);for(let f of P)console.log(`[frs-hono] skipped ${f} (use --force to overwrite)`);console.log(`
124
+ [frs-hono] reminder: run "frs-hono gen --root ${r}" to refresh the manifest.`);}function ne(e,t){let s=["apis.ts","apis.js","api.ts","api.js"],r=[e,dirname(e),dirname(dirname(e))];for(let n of r)for(let i of s){let a=resolve(n,i);if(existsSync(a)){let o=relative(t,a).replace(/\\/g,"/");return o=o.replace(/\.ts$/,".js").replace(/\.js$/,".js"),o.startsWith(".")||(o=`./${o}`),o}}return "../../../../apis.js"}async function oe(){let e=process.argv.slice(2),{command:t,flags:s}=ee(e);switch(t){case "gen":await te(s);return;case "new":await se(e[1],s);return;case "help":case "--help":case "-h":F();return;default:console.error(`[frs-hono] unknown command: ${t}
125
+ `),F(),process.exit(2);}}oe().catch(e=>{console.error(e),process.exit(1);});//# sourceMappingURL=cli.js.map
124
126
  //# sourceMappingURL=cli.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/servers/hono/codegen/path-utils.ts","../../../src/servers/hono/codegen/scanner.ts","../../../src/servers/hono/codegen/generator.ts","../../../src/servers/hono/cli.ts"],"names":["DEFAULT_DERIVE","derivePath","relativeDir","options","skip","s","p","kebab","toImportSpecifier","fromDir","toFile","ext","fromParts","splitAbs","toParts","common","up","down","stripped","finalLast","part","i","DEFAULT_SCANNER","scanRoutes","rootAbs","found","walk","a","b","root","dir","opts","out","entries","readdirSync","name","abs","join","st","statSync","relPath","relative","sep","relDir","DEFAULT_GENERATOR_BANNER","generateRoutesManifest","routes","outDir","dirname","mkdirSync","banner","now","importLines","entryLines","derivedPaths","r","importPath","url","body","writeFileSync","parseArgs","argv","command","rest","flags","arg","key","next","printHelp","asList","v","asString","runGen","resolve","existsSync","casing","derive","exclude","routesFile","scanned","result","source","runNew","domain","method","api","useCaseFolder","withUseCase","withTest","force","dirAbs","useCaseFile","testFile","className","useCaseSrc","inputZodSnippet","handlerBody","routesSrc","written","skipped","writeIfPossible","file","content","testSrc","f","main","err"],"mappings":"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","file":"cli.js","sourcesContent":["/**\n * URL path inference from filesystem layout.\n *\n * Convention: every `routes.ts` file under the configured root contributes\n * one route. The URL path is derived from its directory chain, optionally\n * skipping segments such as `useCases` so that\n *\n * domains/activities/useCases/createOrUpdateCustom/routes.ts\n *\n * becomes\n *\n * /activities/createOrUpdateCustom\n */\n\nexport interface PathDeriveOptions {\n /** Segments to drop from the derived path (case-insensitive). */\n skipSegments: string[];\n /**\n * Casing convention applied to each remaining segment.\n * - `\"preserve\"` — keep the directory name as-is (default),\n * - `\"kebab\"` — convert camelCase / PascalCase to kebab-case.\n */\n casing: \"preserve\" | \"kebab\";\n}\n\nexport const DEFAULT_DERIVE: PathDeriveOptions = {\n skipSegments: [\"useCases\", \"useCase\", \"use-cases\", \"use-case\"],\n casing: \"preserve\",\n};\n\n/**\n * @param relativeDir POSIX-style directory path of the routes file relative\n * to the codegen root (no leading slash, no `routes.ts`).\n */\nexport function derivePath(\n relativeDir: string,\n options: PathDeriveOptions = DEFAULT_DERIVE,\n): string {\n const skip = new Set(options.skipSegments.map((s) => s.toLowerCase()));\n const parts = relativeDir\n .split(\"/\")\n .filter(Boolean)\n .filter((p) => !skip.has(p.toLowerCase()))\n .map((p) => (options.casing === \"kebab\" ? kebab(p) : p));\n return \"/\" + parts.join(\"/\");\n}\n\nfunction kebab(s: string): string {\n return s\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .replace(/[\\s_]+/g, \"-\")\n .toLowerCase();\n}\n\n/**\n * Convert an absolute filesystem path to a POSIX-style import specifier\n * relative to a directory (the generated file's directory). Always uses\n * forward slashes and prefixes with `./` or `../` as needed.\n */\nexport function toImportSpecifier(\n fromDir: string,\n toFile: string,\n ext: string,\n): string {\n // Both paths are absolute POSIX (the CLI normalises them).\n const fromParts = splitAbs(fromDir);\n const toParts = splitAbs(toFile);\n let common = 0;\n while (\n common < fromParts.length &&\n common < toParts.length &&\n fromParts[common] === toParts[common]\n ) {\n common++;\n }\n const up = fromParts.length - common;\n const down = toParts.slice(common);\n const last = down[down.length - 1] ?? \"\";\n const stripped = last.replace(/\\.[mc]?[tj]sx?$/i, \"\");\n const finalLast = ext === \"\" ? stripped : `${stripped}${ext}`;\n down[down.length - 1] = finalLast;\n const prefix = up === 0 ? \"./\" : \"../\".repeat(up);\n return prefix + down.join(\"/\");\n}\n\nfunction splitAbs(p: string): string[] {\n const norm = p.replace(/\\\\/g, \"/\").replace(/\\/+$/, \"\");\n return norm.split(\"/\").filter((part, i) => !(i === 0 && part === \"\"));\n}\n","/**\n * Filesystem scanner — walks the configured root and yields every route file.\n * Synchronous and dependency-free (no `fast-glob` etc.) for a tiny CLI footprint.\n */\n\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join, relative, sep } from \"node:path\";\n\nexport interface ScannerOptions {\n /** Filename to look for (default: `routes.ts`). */\n routesFile: string;\n /** Glob-like exclude segments (matched against any path part). */\n excludeSegments: string[];\n}\n\nexport const DEFAULT_SCANNER: ScannerOptions = {\n routesFile: \"routes.ts\",\n excludeSegments: [\n \"node_modules\",\n \"__generated__\",\n \"tests\",\n \"__tests__\",\n \".turbo\",\n \"dist\",\n \"build\",\n \".next\",\n ],\n};\n\nexport interface ScannedRoute {\n /** Absolute path to the routes file. */\n absPath: string;\n /** Path relative to the scan root (POSIX style). */\n relPath: string;\n /** Directory portion of `relPath` (what {@link derivePath} consumes). */\n relDir: string;\n}\n\nexport function scanRoutes(\n rootAbs: string,\n options: ScannerOptions = DEFAULT_SCANNER,\n): ScannedRoute[] {\n const found: ScannedRoute[] = [];\n walk(rootAbs, rootAbs, options, found);\n // Stable, deterministic order — important for reproducible builds.\n found.sort((a, b) => a.relPath.localeCompare(b.relPath));\n return found;\n}\n\nfunction walk(\n root: string,\n dir: string,\n opts: ScannerOptions,\n out: ScannedRoute[],\n): void {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n if (opts.excludeSegments.includes(name)) continue;\n const abs = join(dir, name);\n let st;\n try {\n st = statSync(abs);\n } catch {\n continue;\n }\n if (st.isDirectory()) {\n walk(root, abs, opts, out);\n } else if (st.isFile() && name === opts.routesFile) {\n const relPath = relative(root, abs).split(sep).join(\"/\");\n const relDir = relPath.replace(/\\/?[^/]+$/, \"\");\n out.push({ absPath: abs, relPath, relDir });\n }\n }\n}\n","/**\n * Generator — emits `__generated__/routes.ts` from a list of {@link ScannedRoute}s.\n *\n * The generated module statically imports every `routes.ts` so that bundlers\n * (esbuild/tsup) can tree-shake unused dependencies and Cloud Functions get\n * the smallest possible cold-start footprint.\n */\n\nimport { dirname, join } from \"node:path\";\nimport { mkdirSync, writeFileSync } from \"node:fs\";\n\nimport {\n derivePath,\n toImportSpecifier,\n type PathDeriveOptions,\n} from \"./path-utils\";\nimport type { ScannedRoute } from \"./scanner\";\n\nexport interface GeneratorOptions {\n /** Absolute path of the file to write (e.g. `…/__generated__/routes.ts`). */\n outFile: string;\n /** Path-derivation options (skipSegments, casing). */\n derive: PathDeriveOptions;\n /**\n * Import extension used in the generated file:\n * - `\".js\"` (default) — required for ESM Node.js with `\"type\": \"module\"`,\n * - `\"\"` — bundler-friendly (Vite, no extension),\n * - `\".ts\"` — for TS-ESM runners (`tsx`, `bun`).\n */\n importExtension: string;\n /** Friendly banner placed at the top of the generated file. */\n banner?: string;\n /** Generation timestamp included in the banner. Default: `new Date()`. */\n now?: Date;\n}\n\nexport const DEFAULT_GENERATOR_BANNER =\n \"/**\\n\" +\n \" * AUTO-GENERATED by `@lpdjs/firestore-repo-service` Hono codegen.\\n\" +\n \" * Do not edit by hand — re-run `hono:gen` after adding / removing route files.\\n\" +\n \" */\\n\";\n\nexport interface GenerationResult {\n /** Absolute path of the file written. */\n outFile: string;\n /** Number of routes captured in the manifest. */\n routeCount: number;\n /** Human-readable summary of the derived URLs. */\n derivedPaths: { source: string; url: string }[];\n}\n\nexport function generateRoutesManifest(\n routes: ScannedRoute[],\n opts: GeneratorOptions,\n): GenerationResult {\n const outDir = dirname(opts.outFile);\n mkdirSync(outDir, { recursive: true });\n\n const banner = opts.banner ?? DEFAULT_GENERATOR_BANNER;\n const now = (opts.now ?? new Date()).toISOString();\n const ext = opts.importExtension;\n\n const importLines: string[] = [];\n const entryLines: string[] = [];\n const derivedPaths: GenerationResult[\"derivedPaths\"] = [];\n\n routes.forEach((r, i) => {\n const importPath = toImportSpecifier(outDir, r.absPath, ext);\n const url = derivePath(r.relDir, opts.derive);\n importLines.push(\n `import mod${i} from ${JSON.stringify(importPath)};`,\n );\n entryLines.push(` { __derivedPath: ${JSON.stringify(url)}, mod: mod${i} },`);\n derivedPaths.push({ source: r.relPath, url });\n });\n\n const body =\n `${banner}` +\n `// Generated at ${now} — ${routes.length} route file${routes.length === 1 ? \"\" : \"s\"}.\\n` +\n `\\n` +\n `import type { AnyRouteDef, RouteModuleDefault } from \"@lpdjs/firestore-repo-service/servers/hono\";\\n` +\n `\\n` +\n importLines.join(\"\\n\") +\n (importLines.length ? \"\\n\\n\" : \"\\n\") +\n `const __defs: { __derivedPath: string; mod: RouteModuleDefault }[] = [\\n` +\n entryLines.join(\"\\n\") +\n (entryLines.length ? \"\\n\" : \"\") +\n `];\\n\\n` +\n `export const routes: AnyRouteDef[] = __defs.flatMap(({ __derivedPath, mod }) => {\\n` +\n ` const list = Array.isArray(mod) ? mod : [mod];\\n` +\n ` return list.map((route) => ({ ...route, path: route.path ?? __derivedPath }));\\n` +\n `});\\n`;\n\n writeFileSync(opts.outFile, body, \"utf8\");\n return {\n outFile: opts.outFile,\n routeCount: routes.length,\n derivedPaths,\n };\n}\n\n/** Convenience helper used by the CLI — combines scan + generate in one call. */\nexport function generateFromRoot(\n rootAbs: string,\n outFileRel: string,\n derive: PathDeriveOptions,\n importExtension: string,\n scan: (root: string) => ScannedRoute[],\n): GenerationResult {\n const routes = scan(rootAbs);\n const outFile = join(rootAbs, outFileRel);\n return generateRoutesManifest(routes, {\n outFile,\n derive,\n importExtension,\n });\n}\n","#!/usr/bin/env node\n/**\n * `frs-hono` CLI — codegen for the file-based Hono server.\n *\n * Usage:\n * frs-hono gen --root src/domains\n * frs-hono gen --root src/domains --out __generated__/routes.ts \\\n * --skip useCases,useCase --casing preserve --ext .js\n *\n * Designed to be a **prebuild step** (e.g. wired into `npm run build`).\n * Outputs a manifest with static imports — no runtime filesystem scanning.\n */\n\nimport { resolve } from \"node:path\";\nimport { existsSync } from \"node:fs\";\n\nimport { DEFAULT_DERIVE, type PathDeriveOptions } from \"./codegen/path-utils\";\nimport {\n DEFAULT_SCANNER,\n scanRoutes,\n type ScannerOptions,\n} from \"./codegen/scanner\";\nimport { generateRoutesManifest } from \"./codegen/generator\";\n\ninterface ParsedArgs {\n command: string;\n flags: Record<string, string | boolean>;\n}\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const [command, ...rest] = argv;\n const flags: Record<string, string | boolean> = {};\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i]!;\n if (!arg.startsWith(\"--\")) continue;\n const key = arg.slice(2);\n const next = rest[i + 1];\n if (next && !next.startsWith(\"--\")) {\n flags[key] = next;\n i++;\n } else {\n flags[key] = true;\n }\n }\n return { command: command ?? \"help\", flags };\n}\n\nfunction printHelp(): void {\n // eslint-disable-next-line no-console\n console.log(`frs-hono — Hono file-based codegen\n\nUsage:\n frs-hono gen [flags]\n frs-hono new <name> [flags]\n frs-hono help\n\nFlags (gen):\n --root <dir> Domain root to scan (required, e.g. src/domains)\n --out <file> Output file relative to --root\n (default: __generated__/routes.ts)\n --routes-file <name> Filename to look for (default: routes.ts)\n --skip <list> Comma-separated path segments to drop from URLs\n (default: useCases,useCase,use-cases,use-case)\n --casing <preserve|kebab>\n Casing applied to remaining segments (default: preserve)\n --ext <.js|.ts|''> Import extension in the generated file\n (default: .js — required for ESM Node.js)\n --exclude <list> Comma-separated directories to skip\n (default: node_modules,__generated__,tests,__tests__,dist,build)\n --silent Do not print the generated route table\n\nFlags (new <name>):\n --root <dir> Domain root (default: src/domains)\n --domain <name> Domain name (required, e.g. posts)\n --method <verb> HTTP method (default: post)\n --api <tag> API tag (default: v1)\n --usecase-folder <name>\n Parent folder under <domain>. Default: useCases\n --with-usecase Also scaffold a sibling useCase.ts file (default: true)\n --with-test Also scaffold a sibling useCase.test.ts (Vitest, default: true)\n --force Overwrite if files already exist\n\nExamples:\n frs-hono new createPost --domain posts --method post\n frs-hono new listPosts --domain posts --method get --api v1\n`);\n}\n\nfunction asList(v: string | boolean | undefined): string[] | undefined {\n if (typeof v !== \"string\") return undefined;\n return v\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n}\n\nfunction asString(v: string | boolean | undefined): string | undefined {\n return typeof v === \"string\" ? v : undefined;\n}\n\nasync function runGen(flags: ParsedArgs[\"flags\"]): Promise<void> {\n const root = asString(flags.root);\n if (!root) {\n // eslint-disable-next-line no-console\n console.error(\"[frs-hono] --root is required\");\n process.exit(2);\n }\n const rootAbs = resolve(process.cwd(), root);\n if (!existsSync(rootAbs)) {\n // eslint-disable-next-line no-console\n console.error(`[frs-hono] root not found: ${rootAbs}`);\n process.exit(2);\n }\n\n const out = asString(flags.out) ?? \"__generated__/routes.ts\";\n\n const skip = asList(flags.skip) ?? DEFAULT_DERIVE.skipSegments;\n const casing =\n asString(flags.casing) === \"kebab\" ? \"kebab\" : DEFAULT_DERIVE.casing;\n const derive: PathDeriveOptions = { skipSegments: skip, casing };\n\n const ext = asString(flags.ext) ?? \".js\";\n const exclude = asList(flags.exclude) ?? DEFAULT_SCANNER.excludeSegments;\n const routesFile = asString(flags[\"routes-file\"]) ?? DEFAULT_SCANNER.routesFile;\n const scannerOpts: ScannerOptions = { routesFile, excludeSegments: exclude };\n\n const scanned = scanRoutes(rootAbs, scannerOpts);\n if (scanned.length === 0) {\n // eslint-disable-next-line no-console\n console.warn(\n `[frs-hono] no \"${routesFile}\" files found under ${rootAbs} — generated an empty manifest.`,\n );\n }\n\n const result = generateRoutesManifest(scanned, {\n outFile: resolve(rootAbs, out),\n derive,\n importExtension: ext,\n });\n\n if (!flags.silent) {\n // eslint-disable-next-line no-console\n console.log(\n `[frs-hono] wrote ${result.outFile} (${result.routeCount} route${\n result.routeCount === 1 ? \"\" : \"s\"\n })`,\n );\n for (const { source, url } of result.derivedPaths) {\n // eslint-disable-next-line no-console\n console.log(` ${url.padEnd(48)} ← ${source}`);\n }\n }\n}\n\nasync function runNew(name: string | undefined, flags: ParsedArgs[\"flags\"]): Promise<void> {\n if (!name || name.startsWith(\"--\")) {\n // eslint-disable-next-line no-console\n console.error(\"[frs-hono] usage: frs-hono new <name> --domain <domain> [flags]\");\n process.exit(2);\n }\n const domain = asString(flags.domain);\n if (!domain) {\n // eslint-disable-next-line no-console\n console.error(\"[frs-hono] --domain is required\");\n process.exit(2);\n }\n\n const root = asString(flags.root) ?? \"src/domains\";\n const method = (asString(flags.method) ?? \"post\").toLowerCase();\n const api = asString(flags.api) ?? \"v1\";\n const useCaseFolder = asString(flags[\"usecase-folder\"]) ?? \"useCases\";\n const withUseCase = flags[\"with-usecase\"] !== false;\n const withTest = flags[\"with-test\"] !== false;\n const force = flags.force === true;\n\n if (![\"get\", \"post\", \"put\", \"patch\", \"delete\"].includes(method)) {\n // eslint-disable-next-line no-console\n console.error(`[frs-hono] invalid --method: ${method}`);\n process.exit(2);\n }\n\n const rootAbs = resolve(process.cwd(), root);\n const dirAbs = resolve(rootAbs, domain, useCaseFolder, name);\n const routesFile = resolve(dirAbs, \"routes.ts\");\n const useCaseFile = resolve(dirAbs, \"useCase.ts\");\n const testFile = resolve(dirAbs, \"useCase.test.ts\");\n\n const { mkdirSync, existsSync, writeFileSync } = await import(\"node:fs\");\n mkdirSync(dirAbs, { recursive: true });\n\n const className = `${name.charAt(0).toUpperCase()}${name.slice(1)}UseCase`;\n\n const useCaseSrc = `/**\n * ${className} — pure business logic, no HTTP awareness.\n * Reusable across multiple routes / cron jobs / triggers.\n */\n\nexport interface ${className}Input {\n // TODO: define the input shape\n example: string;\n}\n\nexport interface ${className}Output {\n // TODO: define the output shape\n id: string;\n}\n\nexport class ${className} {\n // TODO: inject repositories / services via the constructor.\n // constructor(private readonly repo: SomeRepository) {}\n\n async execute(input: ${className}Input): Promise<${className}Output> {\n // TODO: implement\n return { id: input.example };\n }\n}\n`;\n\n const inputZodSnippet =\n method === \"get\"\n ? `z.object({\\n // GET → lu depuis les query params\\n example: z.string(),\\n })`\n : `z.object({\\n // ${method.toUpperCase()} → lu depuis le body JSON\\n example: z.string(),\\n })`;\n\n const handlerBody = withUseCase\n ? ` const useCase = new ${className}();\\n const data = await useCase.execute(input);\\n return data;`\n : ` // TODO: business logic\\n return { id: input.example };`;\n\n const useCaseImport = withUseCase\n ? `import { ${className} } from \"./useCase.js\";\\n`\n : \"\";\n\n const routesSrc = `import { z } from \"zod\";\nimport { defineRoute } from \"@lpdjs/firestore-repo-service/servers/hono\";\n${useCaseImport}\nexport default defineRoute({\n api: \"${api}\",\n method: \"${method}\",\n\n input: ${inputZodSnippet},\n\n output: z.object({\n id: z.string(),\n }),\n\n summary: \"TODO: ${name}\",\n tags: [\"${domain}\"],\n\n handler: async ({ input }) => {\n${handlerBody}\n },\n});\n`;\n\n const written: string[] = [];\n const skipped: string[] = [];\n\n const writeIfPossible = (file: string, content: string) => {\n if (existsSync(file) && !force) {\n skipped.push(file);\n return;\n }\n writeFileSync(file, content, \"utf8\");\n written.push(file);\n };\n\n writeIfPossible(routesFile, routesSrc);\n if (withUseCase) writeIfPossible(useCaseFile, useCaseSrc);\n if (withUseCase && withTest) {\n const testSrc = `import { describe, it, expect } from \"vitest\";\nimport { ${className} } from \"./useCase.js\";\n\ndescribe(\"${className}\", () => {\n it(\"returns a response shaped like the output schema\", async () => {\n const useCase = new ${className}();\n const result = await useCase.execute({ example: \"hello\" });\n expect(result).toMatchObject({ id: expect.any(String) });\n });\n\n // TODO: add error-path tests, repository mocks, etc.\n});\n`;\n writeIfPossible(testFile, testSrc);\n }\n\n // eslint-disable-next-line no-console\n for (const f of written) console.log(`[frs-hono] wrote ${f}`);\n for (const f of skipped)\n // eslint-disable-next-line no-console\n console.log(`[frs-hono] skipped ${f} (use --force to overwrite)`);\n // eslint-disable-next-line no-console\n console.log(\n `\\n[frs-hono] reminder: run \"frs-hono gen --root ${root}\" to refresh the manifest.`,\n );\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n const { command, flags } = parseArgs(argv);\n switch (command) {\n case \"gen\":\n await runGen(flags);\n return;\n case \"new\":\n // First positional after `new` is the route name.\n await runNew(argv[1], flags);\n return;\n case \"help\":\n case \"--help\":\n case \"-h\":\n printHelp();\n return;\n default:\n // eslint-disable-next-line no-console\n console.error(`[frs-hono] unknown command: ${command}\\n`);\n printHelp();\n process.exit(2);\n }\n}\n\nmain().catch((err) => {\n // eslint-disable-next-line no-console\n console.error(err);\n process.exit(1);\n});\n"]}
1
+ {"version":3,"sources":["../../../src/servers/hono/codegen/path-utils.ts","../../../src/servers/hono/codegen/scanner.ts","../../../src/servers/hono/codegen/generator.ts","../../../src/servers/hono/cli.ts"],"names":["DEFAULT_DERIVE","derivePath","relativeDir","options","skip","s","p","kebab","toImportSpecifier","fromDir","toFile","ext","fromParts","splitAbs","toParts","common","up","down","stripped","finalLast","part","i","DEFAULT_SCANNER","scanRoutes","rootAbs","found","walk","a","b","root","dir","opts","out","entries","readdirSync","name","abs","join","st","statSync","relPath","relative","sep","relDir","DEFAULT_GENERATOR_BANNER","generateRoutesManifest","routes","outDir","dirname","mkdirSync","banner","now","importLines","entryLines","derivedPaths","r","importPath","url","body","writeFileSync","parseArgs","argv","command","rest","flags","arg","key","next","printHelp","asList","v","asString","runGen","resolve","existsSync","casing","derive","exclude","routesFile","scanned","result","source","runNew","domain","method","api","useCaseFolder","withUseCase","withTest","force","dirAbs","useCaseFile","testFile","className","useCaseSrc","inputZodSnippet","handlerBody","useCaseImport","routesSrc","inferApisImportPath","written","skipped","writeIfPossible","file","content","testSrc","routeDirAbs","candidates","searchRoots","c","full","rel","main","err"],"mappings":"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","file":"cli.js","sourcesContent":["/**\n * URL path inference from filesystem layout.\n *\n * Convention: every `routes.ts` file under the configured root contributes\n * one route. The URL path is derived from its directory chain, optionally\n * skipping segments such as `useCases` so that\n *\n * domains/activities/useCases/createOrUpdateCustom/routes.ts\n *\n * becomes\n *\n * /activities/createOrUpdateCustom\n */\n\nexport interface PathDeriveOptions {\n /** Segments to drop from the derived path (case-insensitive). */\n skipSegments: string[];\n /**\n * Casing convention applied to each remaining segment.\n * - `\"preserve\"` — keep the directory name as-is (default),\n * - `\"kebab\"` — convert camelCase / PascalCase to kebab-case.\n */\n casing: \"preserve\" | \"kebab\";\n}\n\nexport const DEFAULT_DERIVE: PathDeriveOptions = {\n skipSegments: [\"useCases\", \"useCase\", \"use-cases\", \"use-case\"],\n casing: \"preserve\",\n};\n\n/**\n * @param relativeDir POSIX-style directory path of the routes file relative\n * to the codegen root (no leading slash, no `routes.ts`).\n */\nexport function derivePath(\n relativeDir: string,\n options: PathDeriveOptions = DEFAULT_DERIVE,\n): string {\n const skip = new Set(options.skipSegments.map((s) => s.toLowerCase()));\n const parts = relativeDir\n .split(\"/\")\n .filter(Boolean)\n .filter((p) => !skip.has(p.toLowerCase()))\n .map((p) => (options.casing === \"kebab\" ? kebab(p) : p));\n return \"/\" + parts.join(\"/\");\n}\n\nfunction kebab(s: string): string {\n return s\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .replace(/[\\s_]+/g, \"-\")\n .toLowerCase();\n}\n\n/**\n * Convert an absolute filesystem path to a POSIX-style import specifier\n * relative to a directory (the generated file's directory). Always uses\n * forward slashes and prefixes with `./` or `../` as needed.\n */\nexport function toImportSpecifier(\n fromDir: string,\n toFile: string,\n ext: string,\n): string {\n // Both paths are absolute POSIX (the CLI normalises them).\n const fromParts = splitAbs(fromDir);\n const toParts = splitAbs(toFile);\n let common = 0;\n while (\n common < fromParts.length &&\n common < toParts.length &&\n fromParts[common] === toParts[common]\n ) {\n common++;\n }\n const up = fromParts.length - common;\n const down = toParts.slice(common);\n const last = down[down.length - 1] ?? \"\";\n const stripped = last.replace(/\\.[mc]?[tj]sx?$/i, \"\");\n const finalLast = ext === \"\" ? stripped : `${stripped}${ext}`;\n down[down.length - 1] = finalLast;\n const prefix = up === 0 ? \"./\" : \"../\".repeat(up);\n return prefix + down.join(\"/\");\n}\n\nfunction splitAbs(p: string): string[] {\n const norm = p.replace(/\\\\/g, \"/\").replace(/\\/+$/, \"\");\n return norm.split(\"/\").filter((part, i) => !(i === 0 && part === \"\"));\n}\n","/**\n * Filesystem scanner — walks the configured root and yields every route file.\n * Synchronous and dependency-free (no `fast-glob` etc.) for a tiny CLI footprint.\n */\n\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join, relative, sep } from \"node:path\";\n\nexport interface ScannerOptions {\n /** Filename to look for (default: `routes.ts`). */\n routesFile: string;\n /** Glob-like exclude segments (matched against any path part). */\n excludeSegments: string[];\n}\n\nexport const DEFAULT_SCANNER: ScannerOptions = {\n routesFile: \"routes.ts\",\n excludeSegments: [\n \"node_modules\",\n \"__generated__\",\n \"tests\",\n \"__tests__\",\n \".turbo\",\n \"dist\",\n \"build\",\n \".next\",\n ],\n};\n\nexport interface ScannedRoute {\n /** Absolute path to the routes file. */\n absPath: string;\n /** Path relative to the scan root (POSIX style). */\n relPath: string;\n /** Directory portion of `relPath` (what {@link derivePath} consumes). */\n relDir: string;\n}\n\nexport function scanRoutes(\n rootAbs: string,\n options: ScannerOptions = DEFAULT_SCANNER,\n): ScannedRoute[] {\n const found: ScannedRoute[] = [];\n walk(rootAbs, rootAbs, options, found);\n // Stable, deterministic order — important for reproducible builds.\n found.sort((a, b) => a.relPath.localeCompare(b.relPath));\n return found;\n}\n\nfunction walk(\n root: string,\n dir: string,\n opts: ScannerOptions,\n out: ScannedRoute[],\n): void {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n if (opts.excludeSegments.includes(name)) continue;\n const abs = join(dir, name);\n let st;\n try {\n st = statSync(abs);\n } catch {\n continue;\n }\n if (st.isDirectory()) {\n walk(root, abs, opts, out);\n } else if (st.isFile() && name === opts.routesFile) {\n const relPath = relative(root, abs).split(sep).join(\"/\");\n const relDir = relPath.replace(/\\/?[^/]+$/, \"\");\n out.push({ absPath: abs, relPath, relDir });\n }\n }\n}\n","/**\n * Generator — emits `__generated__/routes.ts` from a list of {@link ScannedRoute}s.\n *\n * The generated module statically imports every `routes.ts` so that bundlers\n * (esbuild/tsup) can tree-shake unused dependencies and Cloud Functions get\n * the smallest possible cold-start footprint.\n */\n\nimport { dirname, join } from \"node:path\";\nimport { mkdirSync, writeFileSync } from \"node:fs\";\n\nimport {\n derivePath,\n toImportSpecifier,\n type PathDeriveOptions,\n} from \"./path-utils\";\nimport type { ScannedRoute } from \"./scanner\";\n\nexport interface GeneratorOptions {\n /** Absolute path of the file to write (e.g. `…/__generated__/routes.ts`). */\n outFile: string;\n /** Path-derivation options (skipSegments, casing). */\n derive: PathDeriveOptions;\n /**\n * Import extension used in the generated file:\n * - `\".js\"` (default) — required for ESM Node.js with `\"type\": \"module\"`,\n * - `\"\"` — bundler-friendly (Vite, no extension),\n * - `\".ts\"` — for TS-ESM runners (`tsx`, `bun`).\n */\n importExtension: string;\n /** Friendly banner placed at the top of the generated file. */\n banner?: string;\n /** Generation timestamp included in the banner. Default: `new Date()`. */\n now?: Date;\n}\n\nexport const DEFAULT_GENERATOR_BANNER =\n \"/**\\n\" +\n \" * AUTO-GENERATED by `@lpdjs/firestore-repo-service` Hono codegen.\\n\" +\n \" * Do not edit by hand — re-run `hono:gen` after adding / removing route files.\\n\" +\n \" */\\n\";\n\nexport interface GenerationResult {\n /** Absolute path of the file written. */\n outFile: string;\n /** Number of routes captured in the manifest. */\n routeCount: number;\n /** Human-readable summary of the derived URLs. */\n derivedPaths: { source: string; url: string }[];\n}\n\nexport function generateRoutesManifest(\n routes: ScannedRoute[],\n opts: GeneratorOptions,\n): GenerationResult {\n const outDir = dirname(opts.outFile);\n mkdirSync(outDir, { recursive: true });\n\n const banner = opts.banner ?? DEFAULT_GENERATOR_BANNER;\n const now = (opts.now ?? new Date()).toISOString();\n const ext = opts.importExtension;\n\n const importLines: string[] = [];\n const entryLines: string[] = [];\n const derivedPaths: GenerationResult[\"derivedPaths\"] = [];\n\n routes.forEach((r, i) => {\n const importPath = toImportSpecifier(outDir, r.absPath, ext);\n const url = derivePath(r.relDir, opts.derive);\n importLines.push(\n `import mod${i} from ${JSON.stringify(importPath)};`,\n );\n entryLines.push(` { __derivedPath: ${JSON.stringify(url)}, mod: mod${i} },`);\n derivedPaths.push({ source: r.relPath, url });\n });\n\n const body =\n `${banner}` +\n `// Generated at ${now} — ${routes.length} route file${routes.length === 1 ? \"\" : \"s\"}.\\n` +\n `\\n` +\n `import type { AnyRouteDef, RouteModuleDefault } from \"@lpdjs/firestore-repo-service/servers/hono\";\\n` +\n `\\n` +\n importLines.join(\"\\n\") +\n (importLines.length ? \"\\n\\n\" : \"\\n\") +\n `const __defs: { __derivedPath: string; mod: RouteModuleDefault }[] = [\\n` +\n entryLines.join(\"\\n\") +\n (entryLines.length ? \"\\n\" : \"\") +\n `];\\n\\n` +\n `export const routes: AnyRouteDef[] = __defs.flatMap(({ __derivedPath, mod }) => {\\n` +\n ` const list = Array.isArray(mod) ? mod : [mod];\\n` +\n ` return list.map((route) => ({ ...route, path: route.path ?? __derivedPath }));\\n` +\n `});\\n`;\n\n writeFileSync(opts.outFile, body, \"utf8\");\n return {\n outFile: opts.outFile,\n routeCount: routes.length,\n derivedPaths,\n };\n}\n\n/** Convenience helper used by the CLI — combines scan + generate in one call. */\nexport function generateFromRoot(\n rootAbs: string,\n outFileRel: string,\n derive: PathDeriveOptions,\n importExtension: string,\n scan: (root: string) => ScannedRoute[],\n): GenerationResult {\n const routes = scan(rootAbs);\n const outFile = join(rootAbs, outFileRel);\n return generateRoutesManifest(routes, {\n outFile,\n derive,\n importExtension,\n });\n}\n","#!/usr/bin/env node\n/**\n * `frs-hono` CLI — codegen for the file-based Hono server.\n *\n * Usage:\n * frs-hono gen --root src/domains\n * frs-hono gen --root src/domains --out __generated__/routes.ts \\\n * --skip useCases,useCase --casing preserve --ext .js\n *\n * Designed to be a **prebuild step** (e.g. wired into `npm run build`).\n * Outputs a manifest with static imports — no runtime filesystem scanning.\n */\n\nimport { resolve, relative, dirname } from \"node:path\";\nimport { existsSync } from \"node:fs\";\n\nimport { DEFAULT_DERIVE, type PathDeriveOptions } from \"./codegen/path-utils\";\nimport {\n DEFAULT_SCANNER,\n scanRoutes,\n type ScannerOptions,\n} from \"./codegen/scanner\";\nimport { generateRoutesManifest } from \"./codegen/generator\";\n\ninterface ParsedArgs {\n command: string;\n flags: Record<string, string | boolean>;\n}\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const [command, ...rest] = argv;\n const flags: Record<string, string | boolean> = {};\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i]!;\n if (!arg.startsWith(\"--\")) continue;\n const key = arg.slice(2);\n const next = rest[i + 1];\n if (next && !next.startsWith(\"--\")) {\n flags[key] = next;\n i++;\n } else {\n flags[key] = true;\n }\n }\n return { command: command ?? \"help\", flags };\n}\n\nfunction printHelp(): void {\n // eslint-disable-next-line no-console\n console.log(`frs-hono — Hono file-based codegen\n\nUsage:\n frs-hono gen [flags]\n frs-hono new <name> [flags]\n frs-hono help\n\nFlags (gen):\n --root <dir> Domain root to scan (required, e.g. src/domains)\n --out <file> Output file relative to --root\n (default: __generated__/routes.ts)\n --routes-file <name> Filename to look for (default: routes.ts)\n --skip <list> Comma-separated path segments to drop from URLs\n (default: useCases,useCase,use-cases,use-case)\n --casing <preserve|kebab>\n Casing applied to remaining segments (default: preserve)\n --ext <.js|.ts|''> Import extension in the generated file\n (default: .js — required for ESM Node.js)\n --exclude <list> Comma-separated directories to skip\n (default: node_modules,__generated__,tests,__tests__,dist,build)\n --silent Do not print the generated route table\n\nFlags (new <name>):\n --root <dir> Domain root (default: src/domains)\n --domain <name> Domain name (required, e.g. posts)\n --method <verb> HTTP method (default: post)\n --api <tag> API tag (default: v1)\n --usecase-folder <name>\n Parent folder under <domain>. Default: useCases\n --with-usecase Also scaffold a sibling useCase.ts file (default: true)\n --with-test Also scaffold a sibling useCase.test.ts (Vitest, default: true)\n --apis-import <path> Import path for the registry (default: auto-detect\n ../../../../apis.js — adjust if your layout differs)\n --force Overwrite if files already exist\n\nExamples:\n frs-hono new createPost --domain posts --method post\n frs-hono new listPosts --domain posts --method get --api v1\n`);\n}\n\nfunction asList(v: string | boolean | undefined): string[] | undefined {\n if (typeof v !== \"string\") return undefined;\n return v\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n}\n\nfunction asString(v: string | boolean | undefined): string | undefined {\n return typeof v === \"string\" ? v : undefined;\n}\n\nasync function runGen(flags: ParsedArgs[\"flags\"]): Promise<void> {\n const root = asString(flags.root);\n if (!root) {\n // eslint-disable-next-line no-console\n console.error(\"[frs-hono] --root is required\");\n process.exit(2);\n }\n const rootAbs = resolve(process.cwd(), root);\n if (!existsSync(rootAbs)) {\n // eslint-disable-next-line no-console\n console.error(`[frs-hono] root not found: ${rootAbs}`);\n process.exit(2);\n }\n\n const out = asString(flags.out) ?? \"__generated__/routes.ts\";\n\n const skip = asList(flags.skip) ?? DEFAULT_DERIVE.skipSegments;\n const casing =\n asString(flags.casing) === \"kebab\" ? \"kebab\" : DEFAULT_DERIVE.casing;\n const derive: PathDeriveOptions = { skipSegments: skip, casing };\n\n const ext = asString(flags.ext) ?? \".js\";\n const exclude = asList(flags.exclude) ?? DEFAULT_SCANNER.excludeSegments;\n const routesFile = asString(flags[\"routes-file\"]) ?? DEFAULT_SCANNER.routesFile;\n const scannerOpts: ScannerOptions = { routesFile, excludeSegments: exclude };\n\n const scanned = scanRoutes(rootAbs, scannerOpts);\n if (scanned.length === 0) {\n // eslint-disable-next-line no-console\n console.warn(\n `[frs-hono] no \"${routesFile}\" files found under ${rootAbs} — generated an empty manifest.`,\n );\n }\n\n const result = generateRoutesManifest(scanned, {\n outFile: resolve(rootAbs, out),\n derive,\n importExtension: ext,\n });\n\n if (!flags.silent) {\n // eslint-disable-next-line no-console\n console.log(\n `[frs-hono] wrote ${result.outFile} (${result.routeCount} route${\n result.routeCount === 1 ? \"\" : \"s\"\n })`,\n );\n for (const { source, url } of result.derivedPaths) {\n // eslint-disable-next-line no-console\n console.log(` ${url.padEnd(48)} ← ${source}`);\n }\n }\n}\n\nasync function runNew(name: string | undefined, flags: ParsedArgs[\"flags\"]): Promise<void> {\n if (!name || name.startsWith(\"--\")) {\n // eslint-disable-next-line no-console\n console.error(\"[frs-hono] usage: frs-hono new <name> --domain <domain> [flags]\");\n process.exit(2);\n }\n const domain = asString(flags.domain);\n if (!domain) {\n // eslint-disable-next-line no-console\n console.error(\"[frs-hono] --domain is required\");\n process.exit(2);\n }\n\n const root = asString(flags.root) ?? \"src/domains\";\n const method = (asString(flags.method) ?? \"post\").toLowerCase();\n const api = asString(flags.api) ?? \"v1\";\n const useCaseFolder = asString(flags[\"usecase-folder\"]) ?? \"useCases\";\n const withUseCase = flags[\"with-usecase\"] !== false;\n const withTest = flags[\"with-test\"] !== false;\n const force = flags.force === true;\n\n if (![\"get\", \"post\", \"put\", \"patch\", \"delete\"].includes(method)) {\n // eslint-disable-next-line no-console\n console.error(`[frs-hono] invalid --method: ${method}`);\n process.exit(2);\n }\n\n const rootAbs = resolve(process.cwd(), root);\n const dirAbs = resolve(rootAbs, domain, useCaseFolder, name);\n const routesFile = resolve(dirAbs, \"routes.ts\");\n const useCaseFile = resolve(dirAbs, \"useCase.ts\");\n const testFile = resolve(dirAbs, \"useCase.test.ts\");\n\n const { mkdirSync, existsSync, writeFileSync } = await import(\"node:fs\");\n mkdirSync(dirAbs, { recursive: true });\n\n const className = `${name.charAt(0).toUpperCase()}${name.slice(1)}UseCase`;\n\n const useCaseSrc = `/**\n * ${className} — pure business logic, no HTTP awareness.\n * Reusable across multiple routes / cron jobs / triggers.\n */\n\nexport interface ${className}Input {\n // TODO: define the input shape\n example: string;\n}\n\nexport interface ${className}Output {\n // TODO: define the output shape\n id: string;\n}\n\nexport class ${className} {\n // TODO: inject repositories / services via the constructor.\n // constructor(private readonly repo: SomeRepository) {}\n\n async execute(input: ${className}Input): Promise<${className}Output> {\n // TODO: implement\n return { id: input.example };\n }\n}\n`;\n\n const inputZodSnippet =\n method === \"get\"\n ? `z.object({\\n // GET → lu depuis les query params\\n example: z.string(),\\n })`\n : `z.object({\\n // ${method.toUpperCase()} → lu depuis le body JSON\\n example: z.string(),\\n })`;\n\n const handlerBody = withUseCase\n ? ` const useCase = new ${className}();\\n const data = await useCase.execute(input);\\n return data;`\n : ` // TODO: business logic\\n return { id: input.example };`;\n\n const useCaseImport = withUseCase\n ? `import { ${className} } from \"./useCase.js\";\\n`\n : \"\";\n\n const apisImport =\n asString(flags[\"apis-import\"]) ??\n inferApisImportPath(rootAbs, dirAbs);\n\n const routesSrc = `import { z } from \"zod\";\nimport { defineRoute } from \"${apisImport}\";\n${useCaseImport}\nexport default defineRoute({\n api: \"${api}\",\n method: \"${method}\",\n\n input: ${inputZodSnippet},\n\n output: z.object({\n id: z.string(),\n }),\n\n summary: \"TODO: ${name}\",\n tags: [\"${domain}\"],\n\n handler: async ({ input }) => {\n${handlerBody}\n },\n});\n`;\n\n const written: string[] = [];\n const skipped: string[] = [];\n\n const writeIfPossible = (file: string, content: string) => {\n if (existsSync(file) && !force) {\n skipped.push(file);\n return;\n }\n writeFileSync(file, content, \"utf8\");\n written.push(file);\n };\n\n writeIfPossible(routesFile, routesSrc);\n if (withUseCase) writeIfPossible(useCaseFile, useCaseSrc);\n if (withUseCase && withTest) {\n const testSrc = `import { describe, it, expect } from \"vitest\";\nimport { ${className} } from \"./useCase.js\";\n\ndescribe(\"${className}\", () => {\n it(\"returns a response shaped like the output schema\", async () => {\n const useCase = new ${className}();\n const result = await useCase.execute({ example: \"hello\" });\n expect(result).toMatchObject({ id: expect.any(String) });\n });\n\n // TODO: add error-path tests, repository mocks, etc.\n});\n`;\n writeIfPossible(testFile, testSrc);\n }\n\n // eslint-disable-next-line no-console\n for (const f of written) console.log(`[frs-hono] wrote ${f}`);\n for (const f of skipped)\n // eslint-disable-next-line no-console\n console.log(`[frs-hono] skipped ${f} (use --force to overwrite)`);\n // eslint-disable-next-line no-console\n console.log(\n `\\n[frs-hono] reminder: run \"frs-hono gen --root ${root}\" to refresh the manifest.`,\n );\n}\n\n/**\n * Try to find the user's `apis.ts` (or similar) file and return a relative\n * import path from the new route file. Falls back to a sensible placeholder.\n */\nfunction inferApisImportPath(rootAbs: string, routeDirAbs: string): string {\n const candidates = [\"apis.ts\", \"apis.js\", \"api.ts\", \"api.js\"];\n // Search upwards from rootAbs's parent (typical layout: src/apis.ts + src/domains/…)\n const searchRoots = [\n rootAbs,\n dirname(rootAbs),\n dirname(dirname(rootAbs)),\n ];\n for (const dir of searchRoots) {\n for (const c of candidates) {\n const full = resolve(dir, c);\n if (existsSync(full)) {\n let rel = relative(routeDirAbs, full).replace(/\\\\/g, \"/\");\n rel = rel.replace(/\\.ts$/, \".js\").replace(/\\.js$/, \".js\");\n if (!rel.startsWith(\".\")) rel = `./${rel}`;\n return rel;\n }\n }\n }\n return \"../../../../apis.js\";\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n const { command, flags } = parseArgs(argv);\n switch (command) {\n case \"gen\":\n await runGen(flags);\n return;\n case \"new\":\n // First positional after `new` is the route name.\n await runNew(argv[1], flags);\n return;\n case \"help\":\n case \"--help\":\n case \"-h\":\n printHelp();\n return;\n default:\n // eslint-disable-next-line no-console\n console.error(`[frs-hono] unknown command: ${command}\\n`);\n printHelp();\n process.exit(2);\n }\n}\n\nmain().catch((err) => {\n // eslint-disable-next-line no-console\n console.error(err);\n process.exit(1);\n});\n"]}