@lpdjs/firestore-repo-service 2.6.3 → 2.6.5
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/README.md +5 -1
- package/dist/servers/hono/cli.cjs +64 -61
- package/dist/servers/hono/cli.cjs.map +1 -1
- package/dist/servers/hono/cli.js +64 -61
- package/dist/servers/hono/cli.js.map +1 -1
- package/package.json +5 -2
package/dist/servers/hono/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {existsSync,mkdirSync,writeFileSync,readFileSync,readdirSync,statSync}from'fs';import {resolve,dirname,relative,join,sep}from'path';import {stdin,stdout}from'process';import {createInterface}from'readline/promises';var
|
|
2
|
+
import {existsSync,mkdirSync,writeFileSync,readFileSync,readdirSync,statSync}from'fs';import {resolve,dirname,relative,join,sep}from'path';import {stdin,stdout}from'process';import {createInterface}from'readline/promises';var O={skipSegments:["useCases","useCase","use-cases","use-case"],casing:"preserve"};function M(e,t=O){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"?se(o):o).join("/")}function se(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}function H(e,t,s){let r=B(e),o=B(t),n=0;for(;n<r.length&&n<o.length&&r[n]===o[n];)n++;let c=r.length-n,i=o.slice(n),d=(i[i.length-1]??"").replace(/\.[mc]?[tj]sx?$/i,""),f=s===""?d:`${d}${s}`;return i[i.length-1]=f,(c===0?"./":"../".repeat(c))+i.join("/")}function B(e){return e.replace(/\\/g,"/").replace(/\/+$/,"").split("/").filter((s,r)=>!(r===0&&s===""))}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 W(e,t){let s=dirname(t.outFile);mkdirSync(s,{recursive:true});let r=t.banner??ne,o=(t.now??new Date).toISOString(),n=t.importExtension,c=[],i=[],a=[];e.forEach((f,u)=>{let x=H(s,f.absPath,n),v=M(f.relDir,t.derive);c.push(`import mod${u} from ${JSON.stringify(x)};`),i.push(` { __derivedPath: ${JSON.stringify(v)}, mod: mod${u} },`),a.push({source:f.relPath,url:v});});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
|
|
|
@@ -8,16 +8,16 @@ import type { AnyRouteDef, RouteModuleDefault } from "@lpdjs/firestore-repo-serv
|
|
|
8
8
|
|
|
9
9
|
`:`
|
|
10
10
|
`)+`const __defs: { __derivedPath: string; mod: RouteModuleDefault }[] = [
|
|
11
|
-
`+
|
|
12
|
-
`)+(
|
|
11
|
+
`+i.join(`
|
|
12
|
+
`)+(i.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,
|
|
20
|
-
`,"utf8"),s}function
|
|
19
|
+
`;return writeFileSync(t.outFile,d,"utf8"),{outFile:t.outFile,routeCount:e.length,derivedPaths:a}}var I={routesFile:"routes.ts",excludeSegments:["node_modules","__generated__","tests","__tests__",".turbo","dist","build",".next"]};function J(e,t=I){let s=[];return K(e,e,t,s),s.sort((r,o)=>r.relPath.localeCompare(o.relPath)),s}function K(e,t,s,r){let o;try{o=readdirSync(t);}catch{return}for(let n of o){if(s.excludeSegments.includes(n))continue;let c=join(t,n),i;try{i=statSync(c);}catch{continue}if(i.isDirectory())K(e,c,s,r);else if(i.isFile()&&n===s.routesFile){let a=relative(e,c).split(sep).join("/"),d=a.replace(/\/?[^/]+$/,"");r.push({absPath:c,relPath:a,relDir:d});}}}var X=".frsrc.json";function T(e=process.cwd()){let t=resolve(e,X);if(!existsSync(t))return {};try{return JSON.parse(readFileSync(t,"utf8"))}catch{return {}}}function me(e,t=process.cwd()){let s=resolve(t,X),o={...T(t),...e};return writeFileSync(s,`${JSON.stringify(o,null,2)}
|
|
20
|
+
`,"utf8"),s}function ge(e){let[t,...s]=e,r={};for(let o=0;o<s.length;o++){let n=s[o];if(!n.startsWith("--"))continue;let c=n.slice(2),i=s[o+1];i&&!i.startsWith("--")?(r[c]=i,o++):r[c]=true;}return {command:t??"help",flags:r}}function Y(){console.log(`frs \u2014 Hono file-based codegen
|
|
21
21
|
|
|
22
22
|
Usage:
|
|
23
23
|
frs init [flags]
|
|
@@ -38,9 +38,10 @@ Flags (init):
|
|
|
38
38
|
--yes Skip prompts, use defaults / flag values
|
|
39
39
|
|
|
40
40
|
Flags (gen):
|
|
41
|
-
--root <dir> Domain root to scan (
|
|
41
|
+
--root <dir> Domain root to scan (e.g. src/domains)
|
|
42
|
+
\u2014 falls back to "root" in .frsrc.json
|
|
42
43
|
--out <file> Output file relative to --root
|
|
43
|
-
(default: __generated__/routes.ts)
|
|
44
|
+
(default: .frsrc.json "out" or __generated__/routes.ts)
|
|
44
45
|
--routes-file <name> Filename to look for (default: routes.ts)
|
|
45
46
|
--skip <list> Comma-separated path segments to drop from URLs
|
|
46
47
|
(default: useCases,useCase,use-cases,use-case)
|
|
@@ -53,12 +54,14 @@ Flags (gen):
|
|
|
53
54
|
--silent Do not print the generated route table
|
|
54
55
|
|
|
55
56
|
Flags (new <name>):
|
|
56
|
-
--root <dir> Domain root (default: src/domains)
|
|
57
|
+
--root <dir> Domain root (default: .frsrc.json "root" or src/domains)
|
|
57
58
|
--domain <name> Domain name (e.g. posts) \u2014 prompted if missing
|
|
58
59
|
--method <verb> HTTP method (default: post) \u2014 prompted if missing
|
|
59
|
-
--api <tag> API tag (default:
|
|
60
|
+
--api <tag> API tag (default: .frsrc.json first "apis" or v1)
|
|
61
|
+
\u2014 prompted if missing
|
|
60
62
|
--usecase-folder <name>
|
|
61
|
-
Parent folder under <domain>.
|
|
63
|
+
Parent folder under <domain>.
|
|
64
|
+
Default: .frsrc.json "useCaseFolder" or useCases
|
|
62
65
|
--with-usecase Also scaffold a sibling useCase.ts file (default: true)
|
|
63
66
|
--with-test Also scaffold a sibling useCase.test.ts (Vitest, default: true)
|
|
64
67
|
--apis-import <path> Import path for the registry (default: auto-detect
|
|
@@ -78,8 +81,8 @@ Examples:
|
|
|
78
81
|
frs new createPost --domain posts --method post
|
|
79
82
|
frs new listPosts --domain posts --method get --api v1
|
|
80
83
|
frs add service postRepo
|
|
81
|
-
`);}function
|
|
82
|
-
* ${
|
|
84
|
+
`);}function Z(e){if(typeof e=="string")return e.split(",").map(t=>t.trim()).filter(Boolean)}function m(e){return typeof e=="string"?e:void 0}function ee(e){if(e||!stdin.isTTY)return {ask:async(s,r)=>r??"",askChoice:async(s,r,o)=>o??"",askBool:async(s,r)=>r,close:()=>{}};let t=createInterface({input:stdin,output:stdout});return {async ask(s,r){let o=r?` (${r})`:"";return (await t.question(`? ${s}${o} \u203A `)).trim()||r||""},async askChoice(s,r,o){let n=` [${r.join("/")}${o?`, default: ${o}`:""}]`;for(;;){let c=(await t.question(`? ${s}${n} \u203A `)).trim().toLowerCase();if(!c&&o)return o;if(r.includes(c))return c;console.log(` invalid choice \u2014 pick one of: ${r.join(", ")}`);}},async askBool(s,r){let o=` (${r?"Y/n":"y/N"})`,n=(await t.question(`? ${s}${o} \u203A `)).trim().toLowerCase();return n?n==="y"||n==="yes"||n==="true":r},close:()=>t.close()}}async function he(e){let t=T(),s=m(e.root)??t.root;s||(console.error("[frs] --root is required (or run `frs init` to write it to .frsrc.json)"),process.exit(2));let r=resolve(process.cwd(),s);existsSync(r)||(console.error(`[frs] root not found: ${r}`),process.exit(2));let o=m(e.out)??t.out??"__generated__/routes.ts",n=Z(e.skip)??O.skipSegments,c=m(e.casing)==="kebab"?"kebab":O.casing,i={skipSegments:n,casing:c},a=m(e.ext)??".js",d=Z(e.exclude)??I.excludeSegments,f=m(e["routes-file"])??I.routesFile,x=J(r,{routesFile:f,excludeSegments:d});x.length===0&&console.warn(`[frs] no "${f}" files found under ${r} \u2014 generated an empty manifest.`);let v=W(x,{outFile:resolve(r,o),derive:i,importExtension:a});if(!e.silent){console.log(`[frs] wrote ${v.outFile} (${v.routeCount} route${v.routeCount===1?"":"s"})`);for(let{source:S,url:g}of v.derivedPaths)console.log(` ${g.padEnd(48)} \u2190 ${S}`);}}async function ve(e,t){let s=t.yes===true,r=ee(s),o=T();try{let n=e&&!e.startsWith("--")?e:void 0;n||(n=(await r.ask("Route name (e.g. createPost)")).trim(),n||(console.error("[frs] route name is required"),process.exit(2)));let c=m(t.domain);c||(c=(await r.ask("Domain name (e.g. posts)")).trim(),c||(console.error("[frs] --domain is required"),process.exit(2)));let i=m(t.root)??o.root??"src/domains",a=m(t.method)?.toLowerCase();a||(a=await r.askChoice("HTTP method",["get","post","put","patch","delete"],"post")),["get","post","put","patch","delete"].includes(a)||(console.error(`[frs] invalid --method: ${a}`),process.exit(2));let d=m(t.api);if(!d){let $=o.apis?.[0]??"v1";d=(await r.ask("API tag",$)).trim()||$;}let f=m(t["usecase-folder"])??o.useCaseFolder??"useCases",u=t["with-usecase"]===void 0?s?!0:await r.askBool("Scaffold useCase.ts?",!0):t["with-usecase"]!==!1,x=t["with-test"]===void 0?s||!u?u:await r.askBool("Scaffold useCase.test.ts (Vitest)?",!0):t["with-test"]!==!1,v=t.force===!0,S=resolve(process.cwd(),i),g=resolve(S,c,f,n),D=resolve(g,"routes.ts"),b=resolve(g,"useCase.ts"),k=resolve(g,"useCase.test.ts");mkdirSync(g,{recursive:!0});let y=`${n.charAt(0).toUpperCase()}${n.slice(1)}UseCase`,P="@lpdjs/firestore-repo-service/servers/hono",F=xe(S,g),_=a==="get"?"// GET \u2192 lu depuis les query params":`// ${a.toUpperCase()} \u2192 lu depuis le body JSON`,h=`/**
|
|
85
|
+
* ${y} \u2014 pure business logic, no HTTP awareness.
|
|
83
86
|
*
|
|
84
87
|
* Owns its Zod \`input\` / \`output\` schemas (declared as \`static\` members, the
|
|
85
88
|
* single source of truth shared with \`routes.ts\`) and runs the logic in
|
|
@@ -89,10 +92,10 @@ Examples:
|
|
|
89
92
|
|
|
90
93
|
import { z } from "zod";
|
|
91
94
|
import { UseCase } from "${P}";
|
|
92
|
-
import type { Services } from "${
|
|
95
|
+
import type { Services } from "${F}";
|
|
93
96
|
|
|
94
97
|
const input = z.object({
|
|
95
|
-
${
|
|
98
|
+
${_}
|
|
96
99
|
example: z.string(),
|
|
97
100
|
});
|
|
98
101
|
|
|
@@ -100,7 +103,7 @@ const output = z.object({
|
|
|
100
103
|
id: z.string(),
|
|
101
104
|
});
|
|
102
105
|
|
|
103
|
-
export class ${
|
|
106
|
+
export class ${y} extends UseCase<typeof input, typeof output, Services> {
|
|
104
107
|
static readonly input = input;
|
|
105
108
|
static readonly output = output;
|
|
106
109
|
|
|
@@ -111,30 +114,30 @@ export class ${$} extends UseCase<typeof input, typeof output, Services> {
|
|
|
111
114
|
return { id: payload.example };
|
|
112
115
|
}
|
|
113
116
|
}
|
|
114
|
-
`,
|
|
115
|
-
source: "query",`:"",
|
|
116
|
-
`:"",
|
|
117
|
-
import { useCaseRoute } from "${
|
|
118
|
-
${
|
|
117
|
+
`,C=a==="get"?`
|
|
118
|
+
source: "query",`:"",N=u?`import { ${y} } from "./useCase.js";
|
|
119
|
+
`:"",p=m(t["apis-import"])??we(S,g),j=u?`import { defineRoutes } from "${P}";
|
|
120
|
+
import { useCaseRoute } from "${p}";
|
|
121
|
+
${N}
|
|
119
122
|
export default defineRoutes([
|
|
120
|
-
useCaseRoute(${
|
|
121
|
-
api: "${
|
|
122
|
-
method: "${
|
|
123
|
-
summary: "TODO: ${
|
|
124
|
-
tags: ["${
|
|
123
|
+
useCaseRoute(${y}, {
|
|
124
|
+
api: "${d}",
|
|
125
|
+
method: "${a}",${C}
|
|
126
|
+
summary: "TODO: ${n}",
|
|
127
|
+
tags: ["${c}"],
|
|
125
128
|
}),
|
|
126
129
|
]);
|
|
127
130
|
`:`import { z } from "zod";
|
|
128
131
|
import { defineRoutes } from "${P}";
|
|
129
|
-
import { defineRoute } from "${
|
|
132
|
+
import { defineRoute } from "${p}";
|
|
130
133
|
|
|
131
134
|
export default defineRoutes([
|
|
132
135
|
defineRoute({
|
|
133
|
-
api: "${
|
|
134
|
-
method: "${
|
|
136
|
+
api: "${d}",
|
|
137
|
+
method: "${a}",
|
|
135
138
|
|
|
136
139
|
input: z.object({
|
|
137
|
-
${
|
|
140
|
+
${_}
|
|
138
141
|
example: z.string(),
|
|
139
142
|
}),
|
|
140
143
|
|
|
@@ -142,8 +145,8 @@ export default defineRoutes([
|
|
|
142
145
|
id: z.string(),
|
|
143
146
|
}),
|
|
144
147
|
|
|
145
|
-
summary: "TODO: ${
|
|
146
|
-
tags: ["${
|
|
148
|
+
summary: "TODO: ${n}",
|
|
149
|
+
tags: ["${c}"],
|
|
147
150
|
|
|
148
151
|
handler: async ({ input }) => {
|
|
149
152
|
// TODO: business logic
|
|
@@ -151,32 +154,32 @@ export default defineRoutes([
|
|
|
151
154
|
},
|
|
152
155
|
}),
|
|
153
156
|
]);
|
|
154
|
-
`,
|
|
155
|
-
import type { Services } from "${
|
|
156
|
-
import { ${
|
|
157
|
+
`,z=[],G=[],q=($,te)=>{if(existsSync($)&&!v){G.push($);return}writeFileSync($,te,"utf8"),z.push($);};if(q(D,j),u&&q(b,h),u&&x){let $=`import { describe, it, expect } from "vitest";
|
|
158
|
+
import type { Services } from "${F}";
|
|
159
|
+
import { ${y} } from "./useCase.js";
|
|
157
160
|
|
|
158
|
-
describe("${
|
|
161
|
+
describe("${y}", () => {
|
|
159
162
|
it("returns a response shaped like the output schema", async () => {
|
|
160
163
|
// TODO: replace with real mocks for the services the useCase consumes.
|
|
161
164
|
const services = {} as unknown as Services;
|
|
162
165
|
|
|
163
|
-
const useCase = new ${
|
|
166
|
+
const useCase = new ${y}(services);
|
|
164
167
|
const result = await useCase.execute({ example: "hello" });
|
|
165
168
|
expect(result).toMatchObject({ id: expect.any(String) });
|
|
166
169
|
});
|
|
167
170
|
|
|
168
171
|
// TODO: add error-path tests, repository mocks, etc.
|
|
169
172
|
});
|
|
170
|
-
`;q(
|
|
171
|
-
[frs] reminder: run "frs gen --root ${
|
|
172
|
-
basePath: "${
|
|
173
|
+
`;q(k,$);}for(let $ of z)console.log(`[frs] wrote ${$}`);for(let $ of G)console.log(`[frs] skipped ${$} (use --force to overwrite)`);console.log(`
|
|
174
|
+
[frs] reminder: run "frs gen --root ${i}" to refresh the manifest.`);}finally{r.close();}}async function ye(e){let t=e.yes===true,s=ee(t);try{let r=e.force===!0,o=m(e.root);o||(o=(await s.ask("Domain root","src/domains")).trim()||"src/domains");let n=m(e["apis-file"]);n||(n=(await s.ask("apis.ts location","src/apis.ts")).trim()||"src/apis.ts");let c=m(e["services-file"]);if(!c){let p=n.replace(/apis\.ts$/,"services.ts")||"src/services.ts";c=(await s.ask("services.ts location",p)).trim()||p;}let i=m(e.apis);i||(i=(await s.ask("API tags (comma-separated)","v1")).trim()||"v1");let a=i.split(",").map(p=>p.trim()).filter(Boolean);a.length===0&&(console.error("[frs] at least one API tag is required"),process.exit(2));let d=m(e["base-path"]),f=resolve(process.cwd(),o),u=resolve(process.cwd(),n),x=resolve(process.cwd(),c),v=resolve(f,"__generated__"),S=resolve(v,"routes.ts"),g=[],D=[],b=(p,j)=>{if(mkdirSync(dirname(p),{recursive:!0}),existsSync(p)&&!r){D.push(p);return}writeFileSync(p,j,"utf8"),g.push(p);},k=a.map(p=>{let j=d??`/${p}`;return ` ${p}: {
|
|
175
|
+
basePath: "${j}",
|
|
173
176
|
openapi: {
|
|
174
|
-
info: { title: "${
|
|
177
|
+
info: { title: "${p.toUpperCase()} API", version: "1.0.0", description: "" },
|
|
175
178
|
},
|
|
176
179
|
verbose: process.env["NODE_ENV"] !== "production",
|
|
177
180
|
},`}).join(`
|
|
178
|
-
`),
|
|
179
|
-
import { services } from "${
|
|
181
|
+
`),y=`import { createApiRegistry } from "@lpdjs/firestore-repo-service/servers/hono";
|
|
182
|
+
import { services } from "${E(dirname(u),x)}";
|
|
180
183
|
|
|
181
184
|
/**
|
|
182
185
|
* Single source of truth for every API exposed by this project.
|
|
@@ -188,7 +191,7 @@ import { services } from "${T(dirname(l),h)}";
|
|
|
188
191
|
*/
|
|
189
192
|
export const apis = createApiRegistry(
|
|
190
193
|
{
|
|
191
|
-
${
|
|
194
|
+
${k}
|
|
192
195
|
},
|
|
193
196
|
{ services },
|
|
194
197
|
);
|
|
@@ -196,7 +199,7 @@ ${$}
|
|
|
196
199
|
/** Typed helpers used inside every route file. */
|
|
197
200
|
export const defineRoute = apis.defineRoute;
|
|
198
201
|
export const useCaseRoute = apis.useCaseRoute;
|
|
199
|
-
`;
|
|
202
|
+
`;b(u,y),b(x,`import { createServices } from "@lpdjs/firestore-repo-service/servers/hono";
|
|
200
203
|
|
|
201
204
|
/**
|
|
202
205
|
* Global DI container \u2014 declare every singleton (repositories, SDK
|
|
@@ -229,22 +232,22 @@ export const services = createServices({
|
|
|
229
232
|
|
|
230
233
|
/** Convenience type \u2014 \`function fn(svc: Services) { ... }\`. */
|
|
231
234
|
export type Services = typeof services;
|
|
232
|
-
`);let
|
|
233
|
-
// Run \`frs gen --root ${
|
|
235
|
+
`);let F=`// AUTO-GENERATED by frs \u2014 do not edit.
|
|
236
|
+
// Run \`frs gen --root ${o}\` to refresh.
|
|
234
237
|
|
|
235
238
|
import type { AnyRouteDef } from "@lpdjs/firestore-repo-service/servers/hono";
|
|
236
239
|
|
|
237
240
|
export const routes: AnyRouteDef[] = [];
|
|
238
|
-
`;
|
|
241
|
+
`;b(S,F);let _=me({root:o,apisFile:n,servicesFile:c,apis:a});g.push(_);let h=E(dirname(u),u),C=E(dirname(u),S),N=a.length===1?`export const { ${a[0]} } = apis.toFunctions(routes, onRequest, {`:`export const { ${a.join(", ")} } = apis.toFunctions(routes, onRequest, {`;for(let p of g)console.log(`[frs] wrote ${p}`);for(let p of D)console.log(`[frs] skipped ${p} (use --force to overwrite)`);console.log(`
|
|
239
242
|
Next steps:
|
|
240
243
|
|
|
241
244
|
1. Wire the registry in your Functions entrypoint (e.g. src/index.ts):
|
|
242
245
|
|
|
243
246
|
import { onRequest } from "firebase-functions/v2/https";
|
|
244
|
-
import { apis } from "${
|
|
247
|
+
import { apis } from "${h}";
|
|
245
248
|
import { routes } from "${C}";
|
|
246
249
|
|
|
247
|
-
${
|
|
250
|
+
${N}
|
|
248
251
|
defaults: { region: "us-central1", invoker: "public" },
|
|
249
252
|
});
|
|
250
253
|
|
|
@@ -254,18 +257,18 @@ Next steps:
|
|
|
254
257
|
|
|
255
258
|
3. Refresh the manifest before each build:
|
|
256
259
|
|
|
257
|
-
frs gen --root ${
|
|
258
|
-
`);}finally{s.close();}}function
|
|
260
|
+
frs gen --root ${o}
|
|
261
|
+
`);}finally{s.close();}}function E(e,t){let s=relative(e,t).replace(/\\/g,"/");return s=s.replace(/\.ts$/,".js"),s.startsWith(".")||(s=`./${s}`),s}async function $e(e,t,s){e!=="service"&&(console.error(`[frs] unknown "add" target: ${e??"(missing)"} \u2014 supported: service`),process.exit(2)),t||(console.error("[frs] service name is required: frs add service <name>"),process.exit(2));let r=s.force===true,o=T(),c=[m(s["services-file"]),o.servicesFile,"src/services.ts","services.ts"].filter(h=>typeof h=="string"&&h.length>0),i;for(let h of c){let C=resolve(process.cwd(),h);if(existsSync(C)){i=C;break}}if(!i){let h=c.map(C=>resolve(process.cwd(),C)).join(`
|
|
259
262
|
`);console.error(`[frs] services file not found. Tried:
|
|
260
|
-
${
|
|
261
|
-
Run \`frs init\` first or pass --services-file <path>.`),process.exit(2);}let a=
|
|
263
|
+
${h}
|
|
264
|
+
Run \`frs init\` first or pass --services-file <path>.`),process.exit(2);}let a=m(s["services-dir"])??o.servicesDir??resolve(dirname(i),"services"),d=resolve(process.cwd(),a);mkdirSync(d,{recursive:true});let f=`${t.charAt(0).toUpperCase()}${t.slice(1)}Service`,u=resolve(d,`${t}.ts`),x=`import type { RequestContext } from "@lpdjs/firestore-repo-service/servers/hono";
|
|
262
265
|
|
|
263
266
|
/**
|
|
264
|
-
* ${
|
|
267
|
+
* ${f} \u2014 generated by \`frs add service ${t}\`.
|
|
265
268
|
*
|
|
266
269
|
* Registered with a **factory** in \`services.ts\` so dependencies are
|
|
267
270
|
* destructured at registration time. Add new constructor parameters here
|
|
268
|
-
* and update the factory line (\`({ ctx, otherSvc }) => new ${
|
|
271
|
+
* and update the factory line (\`({ ctx, otherSvc }) => new ${f}(ctx, otherSvc)\`)
|
|
269
272
|
* \u2014 TypeScript will tell you when something is missing.
|
|
270
273
|
*
|
|
271
274
|
* Async resources (DB connections, SDK clients) should stay lazy-loaded
|
|
@@ -279,7 +282,7 @@ Next steps:
|
|
|
279
282
|
* }
|
|
280
283
|
* \`\`\`
|
|
281
284
|
*/
|
|
282
|
-
export class ${
|
|
285
|
+
export class ${f} {
|
|
283
286
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
|
284
287
|
constructor(private readonly ctx: RequestContext) {}
|
|
285
288
|
|
|
@@ -287,9 +290,9 @@ export class ${u} {
|
|
|
287
290
|
return \`hello from ${t} \u2014 user=\${this.ctx.maybeC?.get("user")?.id ?? "anonymous"}\`;
|
|
288
291
|
}
|
|
289
292
|
}
|
|
290
|
-
`;existsSync(
|
|
291
|
-
`)
|
|
292
|
-
`),
|
|
293
|
-
`+
|
|
294
|
-
`),
|
|
293
|
+
`;existsSync(u)&&!r?console.log(`[frs] skipped ${u} (use --force to overwrite)`):(writeFileSync(u,x,"utf8"),console.log(`[frs] wrote ${u}`));let v=readFileSync(i,"utf8"),S=E(dirname(i),u),g=`import { ${f} } from "${S}";`,D=` ${t}: ({ ctx }) => new ${f}(ctx),`;if(v.includes(g)){console.log(`[frs] services.ts already registers "${t}" \u2014 skipping.`);return}let b=v.split(`
|
|
294
|
+
`),k=-1;for(let h=0;h<b.length;h++)/^import\s/.test(b[h])&&(k=h);k>=0?b.splice(k+1,0,g):b.unshift(g);let y=b.join(`
|
|
295
|
+
`),P=y.match(/createServices\s*\(\s*\{/);if(!P){console.error(`[frs] could not find \`createServices({\` in ${i} \u2014 register "${t}" manually.`);return}let F=P.index+P[0].length,_=y.slice(0,F)+`
|
|
296
|
+
`+D+y.slice(F);writeFileSync(i,_,"utf8"),console.log(`[frs] updated ${i} (+ ${t})`);}function we(e,t){let s=["apis.ts","apis.js","api.ts","api.js"],r=[e,dirname(e),dirname(dirname(e))];for(let o of r)for(let n of s){let c=resolve(o,n);if(existsSync(c)){let i=relative(t,c).replace(/\\/g,"/");return i=i.replace(/\.ts$/,".js").replace(/\.js$/,".js"),i.startsWith(".")||(i=`./${i}`),i}}return "../../../../apis.js"}function xe(e,t){let s=["services.ts","services.js"],r=[e,dirname(e),dirname(dirname(e))];for(let o of r)for(let n of s){let c=resolve(o,n);if(existsSync(c)){let i=relative(t,c).replace(/\\/g,"/");return i=i.replace(/\.ts$/,".js"),i.startsWith(".")||(i=`./${i}`),i}}return "../../../../services.js"}async function be(){let e=process.argv.slice(2),{command:t,flags:s}=ge(e);switch(t){case "init":await ye(s);return;case "gen":await he(s);return;case "new":await ve(e[1],s);return;case "add":await $e(e[1],e[2],s);return;case "help":case "--help":case "-h":Y();return;default:console.error(`[frs] unknown command: ${t}
|
|
297
|
+
`),Y(),process.exit(2);}}be().catch(e=>{console.error(e),process.exit(1);});//# sourceMappingURL=cli.js.map
|
|
295
298
|
//# sourceMappingURL=cli.js.map
|