@lpdjs/firestore-repo-service 2.2.9-beta.9 → 2.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/servers/hono/cli.cjs +92 -34
- package/dist/servers/hono/cli.cjs.map +1 -1
- package/dist/servers/hono/cli.js +92 -34
- package/dist/servers/hono/cli.js.map +1 -1
- package/dist/servers/hono/index.cjs +5 -5
- package/dist/servers/hono/index.cjs.map +1 -1
- package/dist/servers/hono/index.d.cts +90 -51
- package/dist/servers/hono/index.d.ts +90 -51
- package/dist/servers/hono/index.js +5 -5
- package/dist/servers/hono/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';var path=require('path'),fs=require('fs');var
|
|
2
|
+
'use strict';var path=require('path'),fs=require('fs'),promises=require('readline/promises'),process$1=require('process');var b={skipSegments:["useCases","useCase","use-cases","use-case"],casing:"preserve"};function I(e,s=b){let t=new Set(s.skipSegments.map(o=>o.toLowerCase()));return "/"+e.split("/").filter(Boolean).filter(o=>!t.has(o.toLowerCase())).map(o=>s.casing==="kebab"?Z(o):o).join("/")}function Z(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}function T(e,s,t){let r=E(e),o=E(s),i=0;for(;i<r.length&&i<o.length&&r[i]===o[i];)i++;let a=r.length-i,n=o.slice(i),d=(n[n.length-1]??"").replace(/\.[mc]?[tj]sx?$/i,""),c=t===""?d:`${d}${t}`;return n[n.length-1]=c,(a===0?"./":"../".repeat(a))+n.join("/")}function E(e){return e.replace(/\\/g,"/").replace(/\/+$/,"").split("/").filter((t,r)=>!(r===0&&t===""))}var S={routesFile:"routes.ts",excludeSegments:["node_modules","__generated__","tests","__tests__",".turbo","dist","build",".next"]};function N(e,s=S){let t=[];return q(e,e,s,t),t.sort((r,o)=>r.relPath.localeCompare(o.relPath)),t}function q(e,s,t,r){let o;try{o=fs.readdirSync(s);}catch{return}for(let i of o){if(t.excludeSegments.includes(i))continue;let a=path.join(s,i),n;try{n=fs.statSync(a);}catch{continue}if(n.isDirectory())q(e,a,t,r);else if(n.isFile()&&i===t.routesFile){let l=path.relative(e,a).split(path.sep).join("/"),d=l.replace(/\/?[^/]+$/,"");r.push({absPath:a,relPath:l,relDir:d});}}}var ne="/**\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 L(e,s){let t=path.dirname(s.outFile);fs.mkdirSync(t,{recursive:true});let r=s.banner??ne,o=(s.now??new Date).toISOString(),i=s.importExtension,a=[],n=[],l=[];e.forEach((c,h)=>{let g=T(t,c.absPath,i),y=I(c.relDir,s.derive);a.push(`import mod${h} from ${JSON.stringify(g)};`),n.push(` { __derivedPath: ${JSON.stringify(y)}, mod: mod${h} },`),l.push({source:c.relPath,url:y});});let d=`${r}// Generated at ${o} \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
|
|
|
@@ -16,13 +16,22 @@ 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(
|
|
19
|
+
`;return fs.writeFileSync(s.outFile,d,"utf8"),{outFile:s.outFile,routeCount:e.length,derivedPaths:l}}function ce(e){let[s,...t]=e,r={};for(let o=0;o<t.length;o++){let i=t[o];if(!i.startsWith("--"))continue;let a=i.slice(2),n=t[o+1];n&&!n.startsWith("--")?(r[a]=n,o++):r[a]=true;}return {command:s??"help",flags:r}}function B(){console.log(`frs-hono \u2014 Hono file-based codegen
|
|
20
20
|
|
|
21
21
|
Usage:
|
|
22
|
-
frs-hono
|
|
23
|
-
frs-hono
|
|
22
|
+
frs-hono init [flags]
|
|
23
|
+
frs-hono gen [flags]
|
|
24
|
+
frs-hono new <name> [flags]
|
|
24
25
|
frs-hono help
|
|
25
26
|
|
|
27
|
+
Flags (init):
|
|
28
|
+
--root <dir> Domain root to create (default: src/domains)
|
|
29
|
+
--apis-file <path> Path to the apis.ts file to create (default: src/apis.ts)
|
|
30
|
+
--apis <list> Comma-separated API tags to register (default: v1)
|
|
31
|
+
--base-path <prefix> basePath shared by all APIs (default: derived from tag)
|
|
32
|
+
--force Overwrite existing files
|
|
33
|
+
--yes Skip prompts, use defaults / flag values
|
|
34
|
+
|
|
26
35
|
Flags (gen):
|
|
27
36
|
--root <dir> Domain root to scan (required, e.g. src/domains)
|
|
28
37
|
--out <file> Output file relative to --root
|
|
@@ -40,85 +49,134 @@ Flags (gen):
|
|
|
40
49
|
|
|
41
50
|
Flags (new <name>):
|
|
42
51
|
--root <dir> Domain root (default: src/domains)
|
|
43
|
-
--domain <name> Domain name (
|
|
44
|
-
--method <verb> HTTP method (default: post)
|
|
45
|
-
--api <tag> API tag (default: v1)
|
|
52
|
+
--domain <name> Domain name (e.g. posts) \u2014 prompted if missing
|
|
53
|
+
--method <verb> HTTP method (default: post) \u2014 prompted if missing
|
|
54
|
+
--api <tag> API tag (default: v1) \u2014 prompted if missing
|
|
46
55
|
--usecase-folder <name>
|
|
47
56
|
Parent folder under <domain>. Default: useCases
|
|
48
57
|
--with-usecase Also scaffold a sibling useCase.ts file (default: true)
|
|
49
58
|
--with-test Also scaffold a sibling useCase.test.ts (Vitest, default: true)
|
|
59
|
+
--apis-import <path> Import path for the registry (default: auto-detect
|
|
60
|
+
../../../../apis.js \u2014 adjust if your layout differs)
|
|
50
61
|
--force Overwrite if files already exist
|
|
62
|
+
--yes Skip prompts, use defaults / flag values
|
|
51
63
|
|
|
52
64
|
Examples:
|
|
65
|
+
frs-hono init
|
|
53
66
|
frs-hono new createPost --domain posts --method post
|
|
54
|
-
frs-hono new listPosts
|
|
55
|
-
`);}function
|
|
56
|
-
* ${
|
|
67
|
+
frs-hono new listPosts --domain posts --method get --api v1
|
|
68
|
+
`);}function G(e){if(typeof e=="string")return e.split(",").map(s=>s.trim()).filter(Boolean)}function p(e){return typeof e=="string"?e:void 0}function J(e){if(e||!process$1.stdin.isTTY)return {ask:async(t,r)=>r??"",askChoice:async(t,r,o)=>o??"",askBool:async(t,r)=>r,close:()=>{}};let s=promises.createInterface({input:process$1.stdin,output:process$1.stdout});return {async ask(t,r){let o=r?` (${r})`:"";return (await s.question(`? ${t}${o} \u203A `)).trim()||r||""},async askChoice(t,r,o){let i=` [${r.join("/")}${o?`, default: ${o}`:""}]`;for(;;){let a=(await s.question(`? ${t}${i} \u203A `)).trim().toLowerCase();if(!a&&o)return o;if(r.includes(a))return a;console.log(` invalid choice \u2014 pick one of: ${r.join(", ")}`);}},async askBool(t,r){let o=` (${r?"Y/n":"y/N"})`,i=(await s.question(`? ${t}${o} \u203A `)).trim().toLowerCase();return i?i==="y"||i==="yes"||i==="true":r},close:()=>s.close()}}async function ue(e){let s=p(e.root);s||(console.error("[frs-hono] --root is required"),process.exit(2));let t=path.resolve(process.cwd(),s);fs.existsSync(t)||(console.error(`[frs-hono] root not found: ${t}`),process.exit(2));let r=p(e.out)??"__generated__/routes.ts",o=G(e.skip)??b.skipSegments,i=p(e.casing)==="kebab"?"kebab":b.casing,a={skipSegments:o,casing:i},n=p(e.ext)??".js",l=G(e.exclude)??S.excludeSegments,d=p(e["routes-file"])??S.routesFile,h=N(t,{routesFile:d,excludeSegments:l});h.length===0&&console.warn(`[frs-hono] no "${d}" files found under ${t} \u2014 generated an empty manifest.`);let g=L(h,{outFile:path.resolve(t,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:y,url:$}of g.derivedPaths)console.log(` ${$.padEnd(48)} \u2190 ${y}`);}}async function pe(e,s){let t=s.yes===true,r=J(t);try{let o=e&&!e.startsWith("--")?e:void 0;o||(o=(await r.ask("Route name (e.g. createPost)")).trim(),o||(console.error("[frs-hono] route name is required"),process.exit(2)));let i=p(s.domain);i||(i=(await r.ask("Domain name (e.g. posts)")).trim(),i||(console.error("[frs-hono] --domain is required"),process.exit(2)));let a=p(s.root)??"src/domains",n=p(s.method)?.toLowerCase();n||(n=await r.askChoice("HTTP method",["get","post","put","patch","delete"],"post")),["get","post","put","patch","delete"].includes(n)||(console.error(`[frs-hono] invalid --method: ${n}`),process.exit(2));let l=p(s.api);l||(l=(await r.ask("API tag","v1")).trim()||"v1");let d=p(s["usecase-folder"])??"useCases",c=s["with-usecase"]===void 0?t?!0:await r.askBool("Scaffold useCase.ts?",!0):s["with-usecase"]!==!1,h=s["with-test"]===void 0?t||!c?c:await r.askBool("Scaffold useCase.test.ts (Vitest)?",!0):s["with-test"]!==!1,g=s.force===!0,y=path.resolve(process.cwd(),a),$=path.resolve(y,i,d,o),P=path.resolve($,"routes.ts"),A=path.resolve($,"useCase.ts"),k=path.resolve($,"useCase.test.ts");fs.mkdirSync($,{recursive:!0});let f=`${o.charAt(0).toUpperCase()}${o.slice(1)}UseCase`,R=`/**
|
|
69
|
+
* ${f} \u2014 pure business logic, no HTTP awareness.
|
|
57
70
|
* Reusable across multiple routes / cron jobs / triggers.
|
|
58
71
|
*/
|
|
59
72
|
|
|
60
|
-
export interface ${
|
|
73
|
+
export interface ${f}Input {
|
|
61
74
|
// TODO: define the input shape
|
|
62
75
|
example: string;
|
|
63
76
|
}
|
|
64
77
|
|
|
65
|
-
export interface ${
|
|
78
|
+
export interface ${f}Output {
|
|
66
79
|
// TODO: define the output shape
|
|
67
80
|
id: string;
|
|
68
81
|
}
|
|
69
82
|
|
|
70
|
-
export class ${
|
|
83
|
+
export class ${f} {
|
|
71
84
|
// TODO: inject repositories / services via the constructor.
|
|
72
85
|
// constructor(private readonly repo: SomeRepository) {}
|
|
73
86
|
|
|
74
|
-
async execute(input: ${
|
|
87
|
+
async execute(input: ${f}Input): Promise<${f}Output> {
|
|
75
88
|
// TODO: implement
|
|
76
89
|
return { id: input.example };
|
|
77
90
|
}
|
|
78
91
|
}
|
|
79
|
-
`,
|
|
92
|
+
`,C=n==="get"?`z.object({
|
|
80
93
|
// GET \u2192 lu depuis les query params
|
|
81
94
|
example: z.string(),
|
|
82
95
|
})`:`z.object({
|
|
83
|
-
// ${
|
|
96
|
+
// ${n.toUpperCase()} \u2192 lu depuis le body JSON
|
|
84
97
|
example: z.string(),
|
|
85
|
-
})`,
|
|
98
|
+
})`,D=c?` const useCase = new ${f}();
|
|
86
99
|
const data = await useCase.execute(input);
|
|
87
100
|
return data;`:` // TODO: business logic
|
|
88
|
-
return { id: input.example };`,
|
|
89
|
-
import {
|
|
90
|
-
|
|
91
|
-
|
|
101
|
+
return { id: input.example };`,u=c?`import { ${f} } from "./useCase.js";
|
|
102
|
+
`:"",V=`import { z } from "zod";
|
|
103
|
+
import { defineRoute } from "${p(s["apis-import"])??de(y,$)}";
|
|
104
|
+
${u}
|
|
92
105
|
export default defineRoute({
|
|
93
|
-
api: "${
|
|
94
|
-
method: "${
|
|
106
|
+
api: "${l}",
|
|
107
|
+
method: "${n}",
|
|
95
108
|
|
|
96
|
-
input: ${
|
|
109
|
+
input: ${C},
|
|
97
110
|
|
|
98
111
|
output: z.object({
|
|
99
112
|
id: z.string(),
|
|
100
113
|
}),
|
|
101
114
|
|
|
102
|
-
summary: "TODO: ${
|
|
103
|
-
tags: ["${
|
|
115
|
+
summary: "TODO: ${o}",
|
|
116
|
+
tags: ["${i}"],
|
|
104
117
|
|
|
105
118
|
handler: async ({ input }) => {
|
|
106
|
-
${
|
|
119
|
+
${D}
|
|
107
120
|
},
|
|
108
121
|
});
|
|
109
|
-
`,
|
|
110
|
-
import { ${
|
|
122
|
+
`,j=[],F=[],O=(v,Y)=>{if(fs.existsSync(v)&&!g){F.push(v);return}fs.writeFileSync(v,Y,"utf8"),j.push(v);};if(O(P,V),c&&O(A,R),c&&h){let v=`import { describe, it, expect } from "vitest";
|
|
123
|
+
import { ${f} } from "./useCase.js";
|
|
111
124
|
|
|
112
|
-
describe("${
|
|
125
|
+
describe("${f}", () => {
|
|
113
126
|
it("returns a response shaped like the output schema", async () => {
|
|
114
|
-
const useCase = new ${
|
|
127
|
+
const useCase = new ${f}();
|
|
115
128
|
const result = await useCase.execute({ example: "hello" });
|
|
116
129
|
expect(result).toMatchObject({ id: expect.any(String) });
|
|
117
130
|
});
|
|
118
131
|
|
|
119
132
|
// TODO: add error-path tests, repository mocks, etc.
|
|
120
133
|
});
|
|
121
|
-
|
|
122
|
-
[frs-hono] reminder: run "frs-hono gen --root ${
|
|
123
|
-
|
|
134
|
+
`;O(k,v);}for(let v of j)console.log(`[frs-hono] wrote ${v}`);for(let v of F)console.log(`[frs-hono] skipped ${v} (use --force to overwrite)`);console.log(`
|
|
135
|
+
[frs-hono] reminder: run "frs-hono gen --root ${a}" to refresh the manifest.`);}finally{r.close();}}async function le(e){let s=e.yes===true,t=J(s);try{let r=e.force===!0,o=p(e.root);o||(o=(await t.ask("Domain root","src/domains")).trim()||"src/domains");let i=p(e["apis-file"]);i||(i=(await t.ask("apis.ts location","src/apis.ts")).trim()||"src/apis.ts");let a=p(e.apis);a||(a=(await t.ask("API tags (comma-separated)","v1")).trim()||"v1");let n=a.split(",").map(u=>u.trim()).filter(Boolean);n.length===0&&(console.error("[frs-hono] at least one API tag is required"),process.exit(2));let l=p(e["base-path"]),d=path.resolve(process.cwd(),o),c=path.resolve(process.cwd(),i),h=path.resolve(d,"__generated__"),g=path.resolve(h,"routes.ts"),y=[],$=[],P=(u,x)=>{if(fs.mkdirSync(path.dirname(u),{recursive:!0}),fs.existsSync(u)&&!r){$.push(u);return}fs.writeFileSync(u,x,"utf8"),y.push(u);},k=`import { createApiRegistry } from "@lpdjs/firestore-repo-service/servers/hono";
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Single source of truth for every API exposed by this project.
|
|
139
|
+
* Add per-API middlewares, interceptors, OpenAPI metadata here.
|
|
140
|
+
*/
|
|
141
|
+
export const apis = createApiRegistry({
|
|
142
|
+
${n.map(u=>{let x=l??`/${u}`;return ` ${u}: {
|
|
143
|
+
basePath: "${x}",
|
|
144
|
+
openapi: {
|
|
145
|
+
info: { title: "${u.toUpperCase()} API", version: "1.0.0", description: "" },
|
|
146
|
+
},
|
|
147
|
+
verbose: process.env["NODE_ENV"] !== "production",
|
|
148
|
+
},`}).join(`
|
|
149
|
+
`)}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
/** Typed helper used inside every route file. */
|
|
153
|
+
export const defineRoute = apis.defineRoute;
|
|
154
|
+
`;P(c,k);let f=`// AUTO-GENERATED by frs-hono \u2014 do not edit.
|
|
155
|
+
// Run \`frs-hono gen --root ${o}\` to refresh.
|
|
156
|
+
|
|
157
|
+
import type { AnyRouteDef } from "@lpdjs/firestore-repo-service/servers/hono";
|
|
158
|
+
|
|
159
|
+
export const routes: AnyRouteDef[] = [];
|
|
160
|
+
`;P(g,f);let R=z(path.dirname(c),c),C=z(path.dirname(c),g),D=n.length===1?`export const { ${n[0]} } = apis.toFunctions(routes, onRequest, {`:`export const { ${n.join(", ")} } = apis.toFunctions(routes, onRequest, {`;for(let u of y)console.log(`[frs-hono] wrote ${u}`);for(let u of $)console.log(`[frs-hono] skipped ${u} (use --force to overwrite)`);console.log(`
|
|
161
|
+
Next steps:
|
|
162
|
+
|
|
163
|
+
1. Wire the registry in your Functions entrypoint (e.g. src/index.ts):
|
|
164
|
+
|
|
165
|
+
import { onRequest } from "firebase-functions/v2/https";
|
|
166
|
+
import { apis } from "${R}";
|
|
167
|
+
import { routes } from "${C}";
|
|
168
|
+
|
|
169
|
+
${D}
|
|
170
|
+
defaults: { region: "us-central1", invoker: "public" },
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
2. Scaffold a first route:
|
|
174
|
+
|
|
175
|
+
frs-hono new createPost --domain posts --method post --api ${n[0]}
|
|
176
|
+
|
|
177
|
+
3. Refresh the manifest before each build:
|
|
178
|
+
|
|
179
|
+
frs-hono gen --root ${o}
|
|
180
|
+
`);}finally{t.close();}}function z(e,s){let t=path.relative(e,s).replace(/\\/g,"/");return t=t.replace(/\.ts$/,".js"),t.startsWith(".")||(t=`./${t}`),t}function de(e,s){let t=["apis.ts","apis.js","api.ts","api.js"],r=[e,path.dirname(e),path.dirname(path.dirname(e))];for(let o of r)for(let i of t){let a=path.resolve(o,i);if(fs.existsSync(a)){let n=path.relative(s,a).replace(/\\/g,"/");return n=n.replace(/\.ts$/,".js").replace(/\.js$/,".js"),n.startsWith(".")||(n=`./${n}`),n}}return "../../../../apis.js"}async function fe(){let e=process.argv.slice(2),{command:s,flags:t}=ce(e);switch(s){case "init":await le(t);return;case "gen":await ue(t);return;case "new":await pe(e[1],t);return;case "help":case "--help":case "-h":B();return;default:console.error(`[frs-hono] unknown command: ${s}
|
|
181
|
+
`),B(),process.exit(2);}}fe().catch(e=>{console.error(e),process.exit(1);});//# sourceMappingURL=cli.cjs.map
|
|
124
182
|
//# 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","makePrompter","input","_q","def","_c","rl","createInterface","output","question","defaultValue","hint","choices","answer","runGen","resolve","existsSync","casing","derive","exclude","routesFile","scanned","result","source","runNew","skipPrompts","prompter","routeName","domain","method","api","useCaseFolder","withUseCase","withTest","force","dirAbs","useCaseFile","testFile","className","useCaseSrc","inputZodSnippet","handlerBody","useCaseImport","routesSrc","inferApisImportPath","written","skipped","writeIfPossible","file","content","testSrc","f","runInit","apisFile","apisRaw","apis","basePathFlag","apisAbs","generatedDir","generatedFile","apisSrc","tag","basePath","stubSrc","apisImportPath","relativeImport","routesImportPath","exportsLine","rel","routeDirAbs","candidates","searchRoots","c","full","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 + scaffolder for the file-based Hono server.\n *\n * Usage:\n * frs-hono init # interactive project bootstrap\n * frs-hono gen --root src/domains\n * frs-hono new createPost --domain posts --method post\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, mkdirSync, writeFileSync } from \"node:fs\";\nimport { createInterface } from \"node:readline/promises\";\nimport { stdin as input, stdout as output } from \"node:process\";\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 init [flags]\n frs-hono gen [flags]\n frs-hono new <name> [flags]\n frs-hono help\n\nFlags (init):\n --root <dir> Domain root to create (default: src/domains)\n --apis-file <path> Path to the apis.ts file to create (default: src/apis.ts)\n --apis <list> Comma-separated API tags to register (default: v1)\n --base-path <prefix> basePath shared by all APIs (default: derived from tag)\n --force Overwrite existing files\n --yes Skip prompts, use defaults / flag values\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 (e.g. posts) — prompted if missing\n --method <verb> HTTP method (default: post) — prompted if missing\n --api <tag> API tag (default: v1) — prompted if missing\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 --yes Skip prompts, use defaults / flag values\n\nExamples:\n frs-hono init\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\n// ---------------------------------------------------------------------------\n// Interactive prompts\n// ---------------------------------------------------------------------------\n\ninterface Prompter {\n ask(question: string, defaultValue?: string): Promise<string>;\n askChoice(\n question: string,\n choices: readonly string[],\n defaultValue?: string,\n ): Promise<string>;\n askBool(question: string, defaultValue: boolean): Promise<boolean>;\n close(): void;\n}\n\nfunction makePrompter(skip: boolean): Prompter {\n if (skip || !input.isTTY) {\n // Non-interactive: always return the default.\n return {\n ask: async (_q, def) => def ?? \"\",\n askChoice: async (_q, _c, def) => def ?? \"\",\n askBool: async (_q, def) => def,\n close: () => undefined,\n };\n }\n const rl = createInterface({ input, output });\n return {\n async ask(question, defaultValue) {\n const hint = defaultValue ? ` (${defaultValue})` : \"\";\n const answer = (await rl.question(`? ${question}${hint} › `)).trim();\n return answer || defaultValue || \"\";\n },\n async askChoice(question, choices, defaultValue) {\n const hint = ` [${choices.join(\"/\")}${defaultValue ? `, default: ${defaultValue}` : \"\"}]`;\n while (true) {\n const answer = (await rl.question(`? ${question}${hint} › `))\n .trim()\n .toLowerCase();\n if (!answer && defaultValue) return defaultValue;\n if (choices.includes(answer)) return answer;\n // eslint-disable-next-line no-console\n console.log(` invalid choice — pick one of: ${choices.join(\", \")}`);\n }\n },\n async askBool(question, defaultValue) {\n const hint = ` (${defaultValue ? \"Y/n\" : \"y/N\"})`;\n const answer = (await rl.question(`? ${question}${hint} › `))\n .trim()\n .toLowerCase();\n if (!answer) return defaultValue;\n return answer === \"y\" || answer === \"yes\" || answer === \"true\";\n },\n close: () => rl.close(),\n };\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(\n name: string | undefined,\n flags: ParsedArgs[\"flags\"],\n): Promise<void> {\n const skipPrompts = flags.yes === true;\n const prompter = makePrompter(skipPrompts);\n try {\n let routeName = name && !name.startsWith(\"--\") ? name : undefined;\n if (!routeName) {\n routeName = (\n await prompter.ask(\"Route name (e.g. createPost)\")\n ).trim();\n if (!routeName) {\n // eslint-disable-next-line no-console\n console.error(\"[frs-hono] route name is required\");\n process.exit(2);\n }\n }\n\n let domain = asString(flags.domain);\n if (!domain) {\n domain = (await prompter.ask(\"Domain name (e.g. posts)\")).trim();\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\n const root = asString(flags.root) ?? \"src/domains\";\n let method = asString(flags.method)?.toLowerCase();\n if (!method) {\n method = await prompter.askChoice(\n \"HTTP method\",\n [\"get\", \"post\", \"put\", \"patch\", \"delete\"],\n \"post\",\n );\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 let api = asString(flags.api);\n if (!api) {\n api = (await prompter.ask(\"API tag\", \"v1\")).trim() || \"v1\";\n }\n\n const useCaseFolder = asString(flags[\"usecase-folder\"]) ?? \"useCases\";\n const withUseCase =\n flags[\"with-usecase\"] === undefined\n ? skipPrompts\n ? true\n : await prompter.askBool(\"Scaffold useCase.ts?\", true)\n : flags[\"with-usecase\"] !== false;\n const withTest =\n flags[\"with-test\"] === undefined\n ? skipPrompts || !withUseCase\n ? withUseCase\n : await prompter.askBool(\"Scaffold useCase.test.ts (Vitest)?\", true)\n : flags[\"with-test\"] !== false;\n const force = flags.force === true;\n\n const rootAbs = resolve(process.cwd(), root);\n const dirAbs = resolve(rootAbs, domain, useCaseFolder, routeName);\n const routesFile = resolve(dirAbs, \"routes.ts\");\n const useCaseFile = resolve(dirAbs, \"useCase.ts\");\n const testFile = resolve(dirAbs, \"useCase.test.ts\");\n\n mkdirSync(dirAbs, { recursive: true });\n\n const className = `${routeName.charAt(0).toUpperCase()}${routeName.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: ${routeName}\",\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 } finally {\n prompter.close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// `init` — bootstrap a fresh project layout\n// ---------------------------------------------------------------------------\n\nasync function runInit(flags: ParsedArgs[\"flags\"]): Promise<void> {\n const skipPrompts = flags.yes === true;\n const prompter = makePrompter(skipPrompts);\n try {\n const force = flags.force === true;\n\n let root = asString(flags.root);\n if (!root) {\n root = (await prompter.ask(\"Domain root\", \"src/domains\")).trim() || \"src/domains\";\n }\n\n let apisFile = asString(flags[\"apis-file\"]);\n if (!apisFile) {\n apisFile = (await prompter.ask(\"apis.ts location\", \"src/apis.ts\")).trim() || \"src/apis.ts\";\n }\n\n let apisRaw = asString(flags.apis);\n if (!apisRaw) {\n apisRaw = (await prompter.ask(\"API tags (comma-separated)\", \"v1\")).trim() || \"v1\";\n }\n const apis = apisRaw\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n if (apis.length === 0) {\n // eslint-disable-next-line no-console\n console.error(\"[frs-hono] at least one API tag is required\");\n process.exit(2);\n }\n\n const basePathFlag = asString(flags[\"base-path\"]);\n\n const rootAbs = resolve(process.cwd(), root);\n const apisAbs = resolve(process.cwd(), apisFile);\n const generatedDir = resolve(rootAbs, \"__generated__\");\n const generatedFile = resolve(generatedDir, \"routes.ts\");\n\n const written: string[] = [];\n const skipped: string[] = [];\n const writeIfPossible = (file: string, content: string) => {\n mkdirSync(dirname(file), { recursive: true });\n if (existsSync(file) && !force) {\n skipped.push(file);\n return;\n }\n writeFileSync(file, content, \"utf8\");\n written.push(file);\n };\n\n // 1) apis.ts ----------------------------------------------------------\n const apisBody = apis\n .map((tag) => {\n const basePath = basePathFlag ?? `/${tag}`;\n return ` ${tag}: {\n basePath: \"${basePath}\",\n openapi: {\n info: { title: \"${tag.toUpperCase()} API\", version: \"1.0.0\", description: \"\" },\n },\n verbose: process.env[\"NODE_ENV\"] !== \"production\",\n },`;\n })\n .join(\"\\n\");\n\n const apisSrc = `import { createApiRegistry } from \"@lpdjs/firestore-repo-service/servers/hono\";\n\n/**\n * Single source of truth for every API exposed by this project.\n * Add per-API middlewares, interceptors, OpenAPI metadata here.\n */\nexport const apis = createApiRegistry({\n${apisBody}\n});\n\n/** Typed helper used inside every route file. */\nexport const defineRoute = apis.defineRoute;\n`;\n\n writeIfPossible(apisAbs, apisSrc);\n\n // 2) Empty generated manifest stub -----------------------------------\n const stubSrc = `// AUTO-GENERATED by frs-hono — do not edit.\n// Run \\`frs-hono gen --root ${root}\\` to refresh.\n\nimport type { AnyRouteDef } from \"@lpdjs/firestore-repo-service/servers/hono\";\n\nexport const routes: AnyRouteDef[] = [];\n`;\n writeIfPossible(generatedFile, stubSrc);\n\n // 3) index.ts snippet hint -------------------------------------------\n const apisImportPath = relativeImport(dirname(apisAbs), apisAbs);\n const routesImportPath = relativeImport(dirname(apisAbs), generatedFile);\n const exportsLine = apis.length === 1\n ? `export const { ${apis[0]} } = apis.toFunctions(routes, onRequest, {`\n : `export const { ${apis.join(\", \")} } = apis.toFunctions(routes, onRequest, {`;\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\n // eslint-disable-next-line no-console\n console.log(`\nNext steps:\n\n1. Wire the registry in your Functions entrypoint (e.g. src/index.ts):\n\n import { onRequest } from \"firebase-functions/v2/https\";\n import { apis } from \"${apisImportPath}\";\n import { routes } from \"${routesImportPath}\";\n\n ${exportsLine}\n defaults: { region: \"us-central1\", invoker: \"public\" },\n });\n\n2. Scaffold a first route:\n\n frs-hono new createPost --domain posts --method post --api ${apis[0]}\n\n3. Refresh the manifest before each build:\n\n frs-hono gen --root ${root}\n`);\n } finally {\n prompter.close();\n }\n}\n\nfunction relativeImport(fromDir: string, toFile: string): string {\n let rel = relative(fromDir, toFile).replace(/\\\\/g, \"/\");\n rel = rel.replace(/\\.ts$/, \".js\");\n if (!rel.startsWith(\".\")) rel = `./${rel}`;\n return rel;\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 \"init\":\n await runInit(flags);\n return;\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"]}
|