@spfn/core 0.1.0-alpha.7 → 0.1.0-alpha.72
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 +168 -195
- package/dist/auto-loader-JFaZ9gON.d.ts +80 -0
- package/dist/cache/index.d.ts +211 -0
- package/dist/cache/index.js +992 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/client/index.d.ts +92 -92
- package/dist/client/index.js +80 -85
- package/dist/client/index.js.map +1 -1
- package/dist/codegen/generators/index.d.ts +19 -0
- package/dist/codegen/generators/index.js +1491 -0
- package/dist/codegen/generators/index.js.map +1 -0
- package/dist/codegen/index.d.ts +76 -60
- package/dist/codegen/index.js +1479 -736
- package/dist/codegen/index.js.map +1 -1
- package/dist/database-errors-BNNmLTJE.d.ts +86 -0
- package/dist/db/index.d.ts +844 -44
- package/dist/db/index.js +1262 -1309
- package/dist/db/index.js.map +1 -1
- package/dist/env/index.d.ts +508 -0
- package/dist/env/index.js +1106 -0
- package/dist/env/index.js.map +1 -0
- package/dist/error-handler-wjLL3v-a.d.ts +44 -0
- package/dist/errors/index.d.ts +136 -0
- package/dist/errors/index.js +172 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index-DHiAqhKv.d.ts +101 -0
- package/dist/index.d.ts +3 -374
- package/dist/index.js +2394 -2176
- package/dist/index.js.map +1 -1
- package/dist/logger/index.d.ts +94 -0
- package/dist/logger/index.js +774 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/middleware/index.d.ts +33 -0
- package/dist/middleware/index.js +890 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/route/index.d.ts +21 -53
- package/dist/route/index.js +1234 -219
- package/dist/route/index.js.map +1 -1
- package/dist/server/index.d.ts +18 -0
- package/dist/server/index.js +2390 -2058
- package/dist/server/index.js.map +1 -1
- package/dist/types-Dzggq1Yb.d.ts +170 -0
- package/package.json +59 -15
- package/dist/auto-loader-C44TcLmM.d.ts +0 -125
- package/dist/bind-pssq1NRT.d.ts +0 -34
- package/dist/postgres-errors-CY_Es8EJ.d.ts +0 -1703
- package/dist/scripts/index.d.ts +0 -24
- package/dist/scripts/index.js +0 -1201
- package/dist/scripts/index.js.map +0 -1
- package/dist/scripts/templates/api-index.template.txt +0 -10
- package/dist/scripts/templates/api-tag.template.txt +0 -11
- package/dist/scripts/templates/contract.template.txt +0 -87
- package/dist/scripts/templates/entity-type.template.txt +0 -31
- package/dist/scripts/templates/entity.template.txt +0 -19
- package/dist/scripts/templates/index.template.txt +0 -10
- package/dist/scripts/templates/repository.template.txt +0 -37
- package/dist/scripts/templates/routes-id.template.txt +0 -59
- package/dist/scripts/templates/routes-index.template.txt +0 -44
- package/dist/types-SlzTr8ZO.d.ts +0 -143
package/dist/scripts/index.js
DELETED
|
@@ -1,1201 +0,0 @@
|
|
|
1
|
-
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
2
|
-
import { migrate } from 'drizzle-orm/postgres-js/migrator';
|
|
3
|
-
import postgres from 'postgres';
|
|
4
|
-
import { config } from 'dotenv';
|
|
5
|
-
import { fileURLToPath } from 'url';
|
|
6
|
-
import { dirname, join, resolve } from 'path';
|
|
7
|
-
import { mkdir, writeFile, readdir, stat } from 'fs/promises';
|
|
8
|
-
import * as ts from 'typescript';
|
|
9
|
-
import { existsSync, mkdirSync, createWriteStream, readFileSync } from 'fs';
|
|
10
|
-
import { watch } from 'chokidar';
|
|
11
|
-
import pino from 'pino';
|
|
12
|
-
import chalk from 'chalk';
|
|
13
|
-
|
|
14
|
-
// src/scripts/migrate.ts
|
|
15
|
-
config({ path: ".env.local" });
|
|
16
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
17
|
-
var __dirname = dirname(__filename);
|
|
18
|
-
var projectRoot = join(__dirname, "../../..");
|
|
19
|
-
var DATABASE_URL = process.env.DATABASE_URL;
|
|
20
|
-
if (!DATABASE_URL) {
|
|
21
|
-
console.error("\u274C DATABASE_URL environment variable is required");
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
async function runMigrations() {
|
|
25
|
-
console.log("\u{1F504} Starting database migration...");
|
|
26
|
-
console.log(`\u{1F4C2} Migrations folder: ${join(projectRoot, "drizzle")}`);
|
|
27
|
-
const migrationConnection = postgres(DATABASE_URL, { max: 1 });
|
|
28
|
-
const db = drizzle(migrationConnection);
|
|
29
|
-
try {
|
|
30
|
-
console.log("\u23F3 Applying migrations...");
|
|
31
|
-
await migrate(db, {
|
|
32
|
-
migrationsFolder: join(projectRoot, "drizzle")
|
|
33
|
-
});
|
|
34
|
-
console.log("\u2705 Migration completed successfully");
|
|
35
|
-
} catch (error) {
|
|
36
|
-
console.error("\u274C Migration failed:", error);
|
|
37
|
-
throw error;
|
|
38
|
-
} finally {
|
|
39
|
-
await migrationConnection.end();
|
|
40
|
-
console.log("\u{1F50C} Database connection closed");
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
runMigrations().then(() => {
|
|
44
|
-
console.log("\u{1F389} All migrations applied");
|
|
45
|
-
process.exit(0);
|
|
46
|
-
}).catch((error) => {
|
|
47
|
-
console.error("\u{1F4A5} Migration process failed:", error);
|
|
48
|
-
process.exit(1);
|
|
49
|
-
});
|
|
50
|
-
async function scanContracts(routesDir) {
|
|
51
|
-
const contractFiles = await scanContractFiles(routesDir);
|
|
52
|
-
const mappings = [];
|
|
53
|
-
for (let i = 0; i < contractFiles.length; i++) {
|
|
54
|
-
const filePath = contractFiles[i];
|
|
55
|
-
const exports = extractContractExports(filePath);
|
|
56
|
-
const basePath = getBasePathFromFile(filePath, routesDir);
|
|
57
|
-
for (let j = 0; j < exports.length; j++) {
|
|
58
|
-
const contractExport = exports[j];
|
|
59
|
-
const fullPath = combinePaths(basePath, contractExport.path);
|
|
60
|
-
mappings.push({
|
|
61
|
-
method: contractExport.method,
|
|
62
|
-
path: fullPath,
|
|
63
|
-
contractName: contractExport.name,
|
|
64
|
-
contractImportPath: getImportPathFromRoutes(filePath, routesDir),
|
|
65
|
-
routeFile: "",
|
|
66
|
-
// Not needed anymore
|
|
67
|
-
contractFile: filePath
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return mappings;
|
|
72
|
-
}
|
|
73
|
-
async function scanContractFiles(dir, files = []) {
|
|
74
|
-
try {
|
|
75
|
-
const entries = await readdir(dir);
|
|
76
|
-
for (let i = 0; i < entries.length; i++) {
|
|
77
|
-
const entry = entries[i];
|
|
78
|
-
const fullPath = join(dir, entry);
|
|
79
|
-
const fileStat = await stat(fullPath);
|
|
80
|
-
if (fileStat.isDirectory()) {
|
|
81
|
-
await scanContractFiles(fullPath, files);
|
|
82
|
-
} else if (entry === "contract.ts") {
|
|
83
|
-
files.push(fullPath);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
} catch (error) {
|
|
87
|
-
}
|
|
88
|
-
return files;
|
|
89
|
-
}
|
|
90
|
-
function extractContractExports(filePath) {
|
|
91
|
-
const sourceCode = readFileSync(filePath, "utf-8");
|
|
92
|
-
const sourceFile = ts.createSourceFile(
|
|
93
|
-
filePath,
|
|
94
|
-
sourceCode,
|
|
95
|
-
ts.ScriptTarget.Latest,
|
|
96
|
-
true
|
|
97
|
-
);
|
|
98
|
-
const exports = [];
|
|
99
|
-
function visit(node) {
|
|
100
|
-
if (ts.isVariableStatement(node)) {
|
|
101
|
-
const hasExport = node.modifiers?.some(
|
|
102
|
-
(m) => m.kind === ts.SyntaxKind.ExportKeyword
|
|
103
|
-
);
|
|
104
|
-
if (hasExport && node.declarationList.declarations.length > 0) {
|
|
105
|
-
const declaration = node.declarationList.declarations[0];
|
|
106
|
-
if (ts.isVariableDeclaration(declaration) && ts.isIdentifier(declaration.name) && declaration.initializer && ts.isObjectLiteralExpression(declaration.initializer)) {
|
|
107
|
-
const name = declaration.name.text;
|
|
108
|
-
if (isContractName(name)) {
|
|
109
|
-
const contractData = extractContractData(declaration.initializer);
|
|
110
|
-
if (contractData.method && contractData.path) {
|
|
111
|
-
exports.push({
|
|
112
|
-
name,
|
|
113
|
-
method: contractData.method,
|
|
114
|
-
path: contractData.path
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
ts.forEachChild(node, visit);
|
|
122
|
-
}
|
|
123
|
-
visit(sourceFile);
|
|
124
|
-
return exports;
|
|
125
|
-
}
|
|
126
|
-
function extractContractData(objectLiteral) {
|
|
127
|
-
const result = {};
|
|
128
|
-
for (let i = 0; i < objectLiteral.properties.length; i++) {
|
|
129
|
-
const prop = objectLiteral.properties[i];
|
|
130
|
-
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
131
|
-
const propName = prop.name.text;
|
|
132
|
-
if (propName === "method") {
|
|
133
|
-
let value;
|
|
134
|
-
if (ts.isStringLiteral(prop.initializer)) {
|
|
135
|
-
value = prop.initializer.text;
|
|
136
|
-
} else if (ts.isAsExpression(prop.initializer) && ts.isStringLiteral(prop.initializer.expression)) {
|
|
137
|
-
value = prop.initializer.expression.text;
|
|
138
|
-
}
|
|
139
|
-
if (value) result.method = value;
|
|
140
|
-
} else if (propName === "path") {
|
|
141
|
-
let value;
|
|
142
|
-
if (ts.isStringLiteral(prop.initializer)) {
|
|
143
|
-
value = prop.initializer.text;
|
|
144
|
-
} else if (ts.isAsExpression(prop.initializer) && ts.isStringLiteral(prop.initializer.expression)) {
|
|
145
|
-
value = prop.initializer.expression.text;
|
|
146
|
-
}
|
|
147
|
-
if (value) result.path = value;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return result;
|
|
152
|
-
}
|
|
153
|
-
function isContractName(name) {
|
|
154
|
-
return name.indexOf("Contract") !== -1 || name.indexOf("contract") !== -1 || name.endsWith("Schema") || name.endsWith("schema");
|
|
155
|
-
}
|
|
156
|
-
function getBasePathFromFile(filePath, routesDir) {
|
|
157
|
-
let relativePath = filePath.replace(routesDir, "");
|
|
158
|
-
if (relativePath.startsWith("/")) {
|
|
159
|
-
relativePath = relativePath.slice(1);
|
|
160
|
-
}
|
|
161
|
-
relativePath = relativePath.replace("/contract.ts", "");
|
|
162
|
-
if (relativePath === "index" || relativePath === "") {
|
|
163
|
-
return "/";
|
|
164
|
-
}
|
|
165
|
-
const segments = relativePath.split("/");
|
|
166
|
-
const transformed = [];
|
|
167
|
-
for (let i = 0; i < segments.length; i++) {
|
|
168
|
-
const seg = segments[i];
|
|
169
|
-
if (seg === "index") {
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
if (seg.startsWith("[") && seg.endsWith("]")) {
|
|
173
|
-
transformed.push(":" + seg.slice(1, -1));
|
|
174
|
-
} else {
|
|
175
|
-
transformed.push(seg);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (transformed.length === 0) {
|
|
179
|
-
return "/";
|
|
180
|
-
}
|
|
181
|
-
return "/" + transformed.join("/");
|
|
182
|
-
}
|
|
183
|
-
function combinePaths(basePath, contractPath) {
|
|
184
|
-
basePath = basePath || "/";
|
|
185
|
-
contractPath = contractPath || "/";
|
|
186
|
-
if (basePath.endsWith("/") && basePath !== "/") {
|
|
187
|
-
basePath = basePath.slice(0, -1);
|
|
188
|
-
}
|
|
189
|
-
if (contractPath.startsWith("/") && contractPath !== "/") {
|
|
190
|
-
if (basePath === "/") {
|
|
191
|
-
return contractPath;
|
|
192
|
-
}
|
|
193
|
-
return basePath + contractPath;
|
|
194
|
-
}
|
|
195
|
-
if (contractPath === "/") {
|
|
196
|
-
return basePath;
|
|
197
|
-
}
|
|
198
|
-
return basePath + "/" + contractPath;
|
|
199
|
-
}
|
|
200
|
-
function getImportPathFromRoutes(filePath, routesDir) {
|
|
201
|
-
let relativePath = filePath.replace(routesDir, "");
|
|
202
|
-
if (relativePath.startsWith("/")) {
|
|
203
|
-
relativePath = relativePath.slice(1);
|
|
204
|
-
}
|
|
205
|
-
if (relativePath.endsWith(".ts")) {
|
|
206
|
-
relativePath = relativePath.slice(0, -3);
|
|
207
|
-
}
|
|
208
|
-
return "@/server/routes/" + relativePath;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// src/codegen/route-scanner.ts
|
|
212
|
-
function groupByResource(mappings) {
|
|
213
|
-
const grouped = {};
|
|
214
|
-
for (let i = 0; i < mappings.length; i++) {
|
|
215
|
-
const mapping = mappings[i];
|
|
216
|
-
const resource = extractResourceName(mapping.path);
|
|
217
|
-
if (!grouped[resource]) {
|
|
218
|
-
grouped[resource] = [];
|
|
219
|
-
}
|
|
220
|
-
grouped[resource].push(mapping);
|
|
221
|
-
}
|
|
222
|
-
return grouped;
|
|
223
|
-
}
|
|
224
|
-
function extractResourceName(path) {
|
|
225
|
-
const segments = path.slice(1).split("/").filter((s) => s && s !== "*");
|
|
226
|
-
const staticSegments = [];
|
|
227
|
-
for (let i = 0; i < segments.length; i++) {
|
|
228
|
-
const seg = segments[i];
|
|
229
|
-
if (!seg.startsWith(":")) {
|
|
230
|
-
staticSegments.push(seg);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
if (staticSegments.length === 0) {
|
|
234
|
-
return "root";
|
|
235
|
-
}
|
|
236
|
-
if (staticSegments.length === 1) {
|
|
237
|
-
return staticSegments[0];
|
|
238
|
-
}
|
|
239
|
-
const result = [staticSegments[0]];
|
|
240
|
-
for (let i = 1; i < staticSegments.length; i++) {
|
|
241
|
-
const seg = staticSegments[i];
|
|
242
|
-
result.push(seg.charAt(0).toUpperCase() + seg.slice(1));
|
|
243
|
-
}
|
|
244
|
-
return result.join("");
|
|
245
|
-
}
|
|
246
|
-
async function generateClient(mappings, options) {
|
|
247
|
-
const startTime = Date.now();
|
|
248
|
-
const grouped = groupByResource(mappings);
|
|
249
|
-
const resourceNames = Object.keys(grouped);
|
|
250
|
-
const code = generateClientCode(mappings, grouped, options);
|
|
251
|
-
await mkdir(dirname(options.outputPath), { recursive: true });
|
|
252
|
-
await writeFile(options.outputPath, code, "utf-8");
|
|
253
|
-
const stats = {
|
|
254
|
-
routesScanned: mappings.length,
|
|
255
|
-
contractsFound: mappings.length,
|
|
256
|
-
contractFiles: countUniqueContractFiles(mappings),
|
|
257
|
-
resourcesGenerated: resourceNames.length,
|
|
258
|
-
methodsGenerated: mappings.length,
|
|
259
|
-
duration: Date.now() - startTime
|
|
260
|
-
};
|
|
261
|
-
return stats;
|
|
262
|
-
}
|
|
263
|
-
function generateClientCode(mappings, grouped, options) {
|
|
264
|
-
let code = "";
|
|
265
|
-
code += generateHeader();
|
|
266
|
-
code += generateImports(mappings, options);
|
|
267
|
-
code += generateApiObject(grouped, options);
|
|
268
|
-
code += generateFooter();
|
|
269
|
-
return code;
|
|
270
|
-
}
|
|
271
|
-
function generateHeader() {
|
|
272
|
-
return `/**
|
|
273
|
-
* Auto-generated API Client
|
|
274
|
-
*
|
|
275
|
-
* Generated by @spfn/core codegen
|
|
276
|
-
* DO NOT EDIT MANUALLY
|
|
277
|
-
*
|
|
278
|
-
* @generated ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
279
|
-
*/
|
|
280
|
-
|
|
281
|
-
`;
|
|
282
|
-
}
|
|
283
|
-
function generateImports(mappings, options) {
|
|
284
|
-
let code = "";
|
|
285
|
-
code += `import { client } from '@spfn/core/client';
|
|
286
|
-
`;
|
|
287
|
-
if (options.includeTypes !== false) {
|
|
288
|
-
code += `import type { InferContract } from '@spfn/core';
|
|
289
|
-
`;
|
|
290
|
-
}
|
|
291
|
-
code += `
|
|
292
|
-
`;
|
|
293
|
-
const importGroups = groupContractsByImportPath(mappings);
|
|
294
|
-
const importPaths = Object.keys(importGroups);
|
|
295
|
-
for (let i = 0; i < importPaths.length; i++) {
|
|
296
|
-
const importPath = importPaths[i];
|
|
297
|
-
const contracts = importGroups[importPath];
|
|
298
|
-
code += `import { ${contracts.join(", ")} } from '${importPath}';
|
|
299
|
-
`;
|
|
300
|
-
}
|
|
301
|
-
code += `
|
|
302
|
-
`;
|
|
303
|
-
return code;
|
|
304
|
-
}
|
|
305
|
-
function groupContractsByImportPath(mappings) {
|
|
306
|
-
const groups = {};
|
|
307
|
-
for (let i = 0; i < mappings.length; i++) {
|
|
308
|
-
const mapping = mappings[i];
|
|
309
|
-
const path = mapping.contractImportPath;
|
|
310
|
-
if (!groups[path]) {
|
|
311
|
-
groups[path] = /* @__PURE__ */ new Set();
|
|
312
|
-
}
|
|
313
|
-
groups[path].add(mapping.contractName);
|
|
314
|
-
}
|
|
315
|
-
const result = {};
|
|
316
|
-
const keys = Object.keys(groups);
|
|
317
|
-
for (let i = 0; i < keys.length; i++) {
|
|
318
|
-
const key = keys[i];
|
|
319
|
-
result[key] = Array.from(groups[key]);
|
|
320
|
-
}
|
|
321
|
-
return result;
|
|
322
|
-
}
|
|
323
|
-
function generateApiObject(grouped, options) {
|
|
324
|
-
let code = "";
|
|
325
|
-
code += `/**
|
|
326
|
-
* Type-safe API client
|
|
327
|
-
*/
|
|
328
|
-
export const api = {
|
|
329
|
-
`;
|
|
330
|
-
const resourceNames = Object.keys(grouped);
|
|
331
|
-
for (let i = 0; i < resourceNames.length; i++) {
|
|
332
|
-
const resourceName = resourceNames[i];
|
|
333
|
-
const routes = grouped[resourceName];
|
|
334
|
-
code += ` ${resourceName}: {
|
|
335
|
-
`;
|
|
336
|
-
for (let j = 0; j < routes.length; j++) {
|
|
337
|
-
const route = routes[j];
|
|
338
|
-
code += generateMethodCode(route, options);
|
|
339
|
-
}
|
|
340
|
-
code += ` }`;
|
|
341
|
-
if (i < resourceNames.length - 1) {
|
|
342
|
-
code += `,`;
|
|
343
|
-
}
|
|
344
|
-
code += `
|
|
345
|
-
`;
|
|
346
|
-
}
|
|
347
|
-
code += `} as const;
|
|
348
|
-
|
|
349
|
-
`;
|
|
350
|
-
return code;
|
|
351
|
-
}
|
|
352
|
-
function generateMethodCode(mapping, options) {
|
|
353
|
-
const methodName = generateMethodName(mapping);
|
|
354
|
-
const contractType = `typeof ${mapping.contractName}`;
|
|
355
|
-
const hasParams = mapping.path.includes(":");
|
|
356
|
-
const hasBody = ["POST", "PUT", "PATCH"].indexOf(mapping.method) !== -1;
|
|
357
|
-
let code = "";
|
|
358
|
-
if (options.includeJsDoc !== false) {
|
|
359
|
-
code += ` /**
|
|
360
|
-
`;
|
|
361
|
-
code += ` * ${mapping.method} ${mapping.path}
|
|
362
|
-
`;
|
|
363
|
-
code += ` */
|
|
364
|
-
`;
|
|
365
|
-
}
|
|
366
|
-
code += ` ${methodName}: (`;
|
|
367
|
-
const params = [];
|
|
368
|
-
if (hasParams) {
|
|
369
|
-
params.push(`params: InferContract<${contractType}>['params']`);
|
|
370
|
-
}
|
|
371
|
-
if (hasBody) {
|
|
372
|
-
params.push(`body: InferContract<${contractType}>['body']`);
|
|
373
|
-
}
|
|
374
|
-
if (params.length > 0) {
|
|
375
|
-
code += `options: { ${params.join(", ")} }`;
|
|
376
|
-
}
|
|
377
|
-
code += `) => `;
|
|
378
|
-
code += `client.call('${mapping.path}', ${mapping.contractName}, `;
|
|
379
|
-
if (params.length > 0) {
|
|
380
|
-
code += `options`;
|
|
381
|
-
} else {
|
|
382
|
-
code += `{}`;
|
|
383
|
-
}
|
|
384
|
-
code += `),
|
|
385
|
-
`;
|
|
386
|
-
return code;
|
|
387
|
-
}
|
|
388
|
-
function generateMethodName(mapping) {
|
|
389
|
-
const method = mapping.method.toLowerCase();
|
|
390
|
-
if (mapping.path === "/" || mapping.path.match(/^\/[\w-]+$/)) {
|
|
391
|
-
if (method === "get") {
|
|
392
|
-
return "list";
|
|
393
|
-
}
|
|
394
|
-
if (method === "post") {
|
|
395
|
-
return "create";
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
if (mapping.path.includes(":")) {
|
|
399
|
-
if (method === "get") {
|
|
400
|
-
return "getById";
|
|
401
|
-
}
|
|
402
|
-
if (method === "put" || method === "patch") {
|
|
403
|
-
return "update";
|
|
404
|
-
}
|
|
405
|
-
if (method === "delete") {
|
|
406
|
-
return "delete";
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return method;
|
|
410
|
-
}
|
|
411
|
-
function generateFooter() {
|
|
412
|
-
return `/**
|
|
413
|
-
* Export client instance for advanced usage
|
|
414
|
-
*
|
|
415
|
-
* Use this to add interceptors or customize the client:
|
|
416
|
-
*
|
|
417
|
-
* @example
|
|
418
|
-
* \`\`\`ts
|
|
419
|
-
* import { client } from './api';
|
|
420
|
-
* import { createAuthInterceptor } from '@spfn/auth/nextjs';
|
|
421
|
-
* import { NextJSCookieProvider } from '@spfn/auth/nextjs';
|
|
422
|
-
*
|
|
423
|
-
* client.use(createAuthInterceptor({
|
|
424
|
-
* cookieProvider: new NextJSCookieProvider(),
|
|
425
|
-
* encryptionKey: process.env.ENCRYPTION_KEY!
|
|
426
|
-
* }));
|
|
427
|
-
* \`\`\`
|
|
428
|
-
*/
|
|
429
|
-
export { client };
|
|
430
|
-
`;
|
|
431
|
-
}
|
|
432
|
-
function countUniqueContractFiles(mappings) {
|
|
433
|
-
const files = /* @__PURE__ */ new Set();
|
|
434
|
-
for (let i = 0; i < mappings.length; i++) {
|
|
435
|
-
if (mappings[i].contractFile) {
|
|
436
|
-
files.add(mappings[i].contractFile);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
return files.size;
|
|
440
|
-
}
|
|
441
|
-
var PinoAdapter = class _PinoAdapter {
|
|
442
|
-
logger;
|
|
443
|
-
constructor(config2) {
|
|
444
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
445
|
-
const isDevelopment = process.env.NODE_ENV === "development";
|
|
446
|
-
const fileLoggingEnabled = process.env.LOGGER_FILE_ENABLED === "true";
|
|
447
|
-
const targets = [];
|
|
448
|
-
if (!isProduction && isDevelopment) {
|
|
449
|
-
targets.push({
|
|
450
|
-
target: "pino-pretty",
|
|
451
|
-
level: "debug",
|
|
452
|
-
options: {
|
|
453
|
-
colorize: true,
|
|
454
|
-
translateTime: "SYS:yyyy-mm-dd HH:MM:ss.l",
|
|
455
|
-
ignore: "pid,hostname"
|
|
456
|
-
}
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
if (fileLoggingEnabled && isProduction) {
|
|
460
|
-
const logDir = process.env.LOG_DIR || "./logs";
|
|
461
|
-
const maxFileSize = process.env.LOG_MAX_FILE_SIZE || "10M";
|
|
462
|
-
const maxFiles = parseInt(process.env.LOG_MAX_FILES || "10", 10);
|
|
463
|
-
targets.push({
|
|
464
|
-
target: "pino-roll",
|
|
465
|
-
level: "info",
|
|
466
|
-
options: {
|
|
467
|
-
file: `${logDir}/app.log`,
|
|
468
|
-
frequency: "daily",
|
|
469
|
-
size: maxFileSize,
|
|
470
|
-
limit: { count: maxFiles },
|
|
471
|
-
mkdir: true
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
this.logger = pino({
|
|
476
|
-
level: config2.level,
|
|
477
|
-
// Transport 설정 (targets가 있으면 사용, 없으면 기본 stdout)
|
|
478
|
-
transport: targets.length > 0 ? { targets } : void 0,
|
|
479
|
-
// 기본 필드
|
|
480
|
-
base: config2.module ? { module: config2.module } : void 0
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
child(module) {
|
|
484
|
-
const childLogger = new _PinoAdapter({ level: this.logger.level, module });
|
|
485
|
-
childLogger.logger = this.logger.child({ module });
|
|
486
|
-
return childLogger;
|
|
487
|
-
}
|
|
488
|
-
debug(message, context) {
|
|
489
|
-
this.logger.debug(context || {}, message);
|
|
490
|
-
}
|
|
491
|
-
info(message, context) {
|
|
492
|
-
this.logger.info(context || {}, message);
|
|
493
|
-
}
|
|
494
|
-
warn(message, errorOrContext, context) {
|
|
495
|
-
if (errorOrContext instanceof Error) {
|
|
496
|
-
this.logger.warn({ err: errorOrContext, ...context }, message);
|
|
497
|
-
} else {
|
|
498
|
-
this.logger.warn(errorOrContext || {}, message);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
error(message, errorOrContext, context) {
|
|
502
|
-
if (errorOrContext instanceof Error) {
|
|
503
|
-
this.logger.error({ err: errorOrContext, ...context }, message);
|
|
504
|
-
} else {
|
|
505
|
-
this.logger.error(errorOrContext || {}, message);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
fatal(message, errorOrContext, context) {
|
|
509
|
-
if (errorOrContext instanceof Error) {
|
|
510
|
-
this.logger.fatal({ err: errorOrContext, ...context }, message);
|
|
511
|
-
} else {
|
|
512
|
-
this.logger.fatal(errorOrContext || {}, message);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
async close() {
|
|
516
|
-
}
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
// src/logger/logger.ts
|
|
520
|
-
var Logger = class _Logger {
|
|
521
|
-
config;
|
|
522
|
-
module;
|
|
523
|
-
constructor(config2) {
|
|
524
|
-
this.config = config2;
|
|
525
|
-
this.module = config2.module;
|
|
526
|
-
}
|
|
527
|
-
/**
|
|
528
|
-
* Get current log level
|
|
529
|
-
*/
|
|
530
|
-
get level() {
|
|
531
|
-
return this.config.level;
|
|
532
|
-
}
|
|
533
|
-
/**
|
|
534
|
-
* Create child logger (per module)
|
|
535
|
-
*/
|
|
536
|
-
child(module) {
|
|
537
|
-
return new _Logger({
|
|
538
|
-
...this.config,
|
|
539
|
-
module
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
|
-
* Debug log
|
|
544
|
-
*/
|
|
545
|
-
debug(message, context) {
|
|
546
|
-
this.log("debug", message, void 0, context);
|
|
547
|
-
}
|
|
548
|
-
/**
|
|
549
|
-
* Info log
|
|
550
|
-
*/
|
|
551
|
-
info(message, context) {
|
|
552
|
-
this.log("info", message, void 0, context);
|
|
553
|
-
}
|
|
554
|
-
warn(message, errorOrContext, context) {
|
|
555
|
-
if (errorOrContext instanceof Error) {
|
|
556
|
-
this.log("warn", message, errorOrContext, context);
|
|
557
|
-
} else {
|
|
558
|
-
this.log("warn", message, void 0, errorOrContext);
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
error(message, errorOrContext, context) {
|
|
562
|
-
if (errorOrContext instanceof Error) {
|
|
563
|
-
this.log("error", message, errorOrContext, context);
|
|
564
|
-
} else {
|
|
565
|
-
this.log("error", message, void 0, errorOrContext);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
fatal(message, errorOrContext, context) {
|
|
569
|
-
if (errorOrContext instanceof Error) {
|
|
570
|
-
this.log("fatal", message, errorOrContext, context);
|
|
571
|
-
} else {
|
|
572
|
-
this.log("fatal", message, void 0, errorOrContext);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
/**
|
|
576
|
-
* Log processing (internal)
|
|
577
|
-
*/
|
|
578
|
-
log(level, message, error, context) {
|
|
579
|
-
const metadata = {
|
|
580
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
581
|
-
level,
|
|
582
|
-
message,
|
|
583
|
-
module: this.module,
|
|
584
|
-
error,
|
|
585
|
-
context
|
|
586
|
-
};
|
|
587
|
-
this.processTransports(metadata);
|
|
588
|
-
}
|
|
589
|
-
/**
|
|
590
|
-
* Process Transports
|
|
591
|
-
*/
|
|
592
|
-
processTransports(metadata) {
|
|
593
|
-
const promises = this.config.transports.filter((transport) => transport.enabled).map((transport) => this.safeTransportLog(transport, metadata));
|
|
594
|
-
Promise.all(promises).catch((error) => {
|
|
595
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
596
|
-
process.stderr.write(`[Logger] Transport error: ${errorMessage}
|
|
597
|
-
`);
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Transport log (error-safe)
|
|
602
|
-
*/
|
|
603
|
-
async safeTransportLog(transport, metadata) {
|
|
604
|
-
try {
|
|
605
|
-
await transport.log(metadata);
|
|
606
|
-
} catch (error) {
|
|
607
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
608
|
-
process.stderr.write(`[Logger] Transport "${transport.name}" failed: ${errorMessage}
|
|
609
|
-
`);
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
/**
|
|
613
|
-
* Close all Transports
|
|
614
|
-
*/
|
|
615
|
-
async close() {
|
|
616
|
-
const closePromises = this.config.transports.filter((transport) => transport.close).map((transport) => transport.close());
|
|
617
|
-
await Promise.all(closePromises);
|
|
618
|
-
}
|
|
619
|
-
};
|
|
620
|
-
|
|
621
|
-
// src/logger/types.ts
|
|
622
|
-
var LOG_LEVEL_PRIORITY = {
|
|
623
|
-
debug: 0,
|
|
624
|
-
info: 1,
|
|
625
|
-
warn: 2,
|
|
626
|
-
error: 3,
|
|
627
|
-
fatal: 4
|
|
628
|
-
};
|
|
629
|
-
|
|
630
|
-
// src/logger/formatters.ts
|
|
631
|
-
var COLORS = {
|
|
632
|
-
reset: "\x1B[0m",
|
|
633
|
-
bright: "\x1B[1m",
|
|
634
|
-
dim: "\x1B[2m",
|
|
635
|
-
// 로그 레벨 컬러
|
|
636
|
-
debug: "\x1B[36m",
|
|
637
|
-
// cyan
|
|
638
|
-
info: "\x1B[32m",
|
|
639
|
-
// green
|
|
640
|
-
warn: "\x1B[33m",
|
|
641
|
-
// yellow
|
|
642
|
-
error: "\x1B[31m",
|
|
643
|
-
// red
|
|
644
|
-
fatal: "\x1B[35m",
|
|
645
|
-
// magenta
|
|
646
|
-
// 추가 컬러
|
|
647
|
-
gray: "\x1B[90m"
|
|
648
|
-
};
|
|
649
|
-
function colorizeLevel(level) {
|
|
650
|
-
const color = COLORS[level];
|
|
651
|
-
const levelStr = level.toUpperCase().padEnd(5);
|
|
652
|
-
return `${color}${levelStr}${COLORS.reset}`;
|
|
653
|
-
}
|
|
654
|
-
function formatTimestamp(date) {
|
|
655
|
-
return date.toISOString();
|
|
656
|
-
}
|
|
657
|
-
function formatTimestampHuman(date) {
|
|
658
|
-
const year = date.getFullYear();
|
|
659
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
660
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
661
|
-
const hours = String(date.getHours()).padStart(2, "0");
|
|
662
|
-
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
663
|
-
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
664
|
-
const ms = String(date.getMilliseconds()).padStart(3, "0");
|
|
665
|
-
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
|
|
666
|
-
}
|
|
667
|
-
function formatError(error) {
|
|
668
|
-
const lines = [];
|
|
669
|
-
lines.push(`${error.name}: ${error.message}`);
|
|
670
|
-
if (error.stack) {
|
|
671
|
-
const stackLines = error.stack.split("\n").slice(1);
|
|
672
|
-
lines.push(...stackLines);
|
|
673
|
-
}
|
|
674
|
-
return lines.join("\n");
|
|
675
|
-
}
|
|
676
|
-
function formatContext(context) {
|
|
677
|
-
try {
|
|
678
|
-
return JSON.stringify(context, null, 2);
|
|
679
|
-
} catch (error) {
|
|
680
|
-
return "[Context serialization failed]";
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
function formatConsole(metadata, colorize = true) {
|
|
684
|
-
const parts = [];
|
|
685
|
-
const timestamp = formatTimestampHuman(metadata.timestamp);
|
|
686
|
-
if (colorize) {
|
|
687
|
-
parts.push(`${COLORS.gray}${timestamp}${COLORS.reset}`);
|
|
688
|
-
} else {
|
|
689
|
-
parts.push(timestamp);
|
|
690
|
-
}
|
|
691
|
-
if (colorize) {
|
|
692
|
-
parts.push(colorizeLevel(metadata.level));
|
|
693
|
-
} else {
|
|
694
|
-
parts.push(metadata.level.toUpperCase().padEnd(5));
|
|
695
|
-
}
|
|
696
|
-
if (metadata.module) {
|
|
697
|
-
if (colorize) {
|
|
698
|
-
parts.push(`${COLORS.dim}[${metadata.module}]${COLORS.reset}`);
|
|
699
|
-
} else {
|
|
700
|
-
parts.push(`[${metadata.module}]`);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
parts.push(metadata.message);
|
|
704
|
-
let output = parts.join(" ");
|
|
705
|
-
if (metadata.context && Object.keys(metadata.context).length > 0) {
|
|
706
|
-
output += "\n" + formatContext(metadata.context);
|
|
707
|
-
}
|
|
708
|
-
if (metadata.error) {
|
|
709
|
-
output += "\n" + formatError(metadata.error);
|
|
710
|
-
}
|
|
711
|
-
return output;
|
|
712
|
-
}
|
|
713
|
-
function formatJSON(metadata) {
|
|
714
|
-
const obj = {
|
|
715
|
-
timestamp: formatTimestamp(metadata.timestamp),
|
|
716
|
-
level: metadata.level,
|
|
717
|
-
message: metadata.message
|
|
718
|
-
};
|
|
719
|
-
if (metadata.module) {
|
|
720
|
-
obj.module = metadata.module;
|
|
721
|
-
}
|
|
722
|
-
if (metadata.context) {
|
|
723
|
-
obj.context = metadata.context;
|
|
724
|
-
}
|
|
725
|
-
if (metadata.error) {
|
|
726
|
-
obj.error = {
|
|
727
|
-
name: metadata.error.name,
|
|
728
|
-
message: metadata.error.message,
|
|
729
|
-
stack: metadata.error.stack
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
return JSON.stringify(obj);
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// src/logger/transports/console.ts
|
|
736
|
-
var ConsoleTransport = class {
|
|
737
|
-
name = "console";
|
|
738
|
-
level;
|
|
739
|
-
enabled;
|
|
740
|
-
colorize;
|
|
741
|
-
constructor(config2) {
|
|
742
|
-
this.level = config2.level;
|
|
743
|
-
this.enabled = config2.enabled;
|
|
744
|
-
this.colorize = config2.colorize ?? true;
|
|
745
|
-
}
|
|
746
|
-
async log(metadata) {
|
|
747
|
-
if (!this.enabled) {
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
750
|
-
if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level]) {
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
const message = formatConsole(metadata, this.colorize);
|
|
754
|
-
if (metadata.level === "warn" || metadata.level === "error" || metadata.level === "fatal") {
|
|
755
|
-
console.error(message);
|
|
756
|
-
} else {
|
|
757
|
-
console.log(message);
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
};
|
|
761
|
-
var FileTransport = class {
|
|
762
|
-
name = "file";
|
|
763
|
-
level;
|
|
764
|
-
enabled;
|
|
765
|
-
logDir;
|
|
766
|
-
currentStream = null;
|
|
767
|
-
currentFilename = null;
|
|
768
|
-
constructor(config2) {
|
|
769
|
-
this.level = config2.level;
|
|
770
|
-
this.enabled = config2.enabled;
|
|
771
|
-
this.logDir = config2.logDir;
|
|
772
|
-
if (!existsSync(this.logDir)) {
|
|
773
|
-
mkdirSync(this.logDir, { recursive: true });
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
async log(metadata) {
|
|
777
|
-
if (!this.enabled) {
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level]) {
|
|
781
|
-
return;
|
|
782
|
-
}
|
|
783
|
-
const message = formatJSON(metadata);
|
|
784
|
-
const filename = this.getLogFilename(metadata.timestamp);
|
|
785
|
-
if (this.currentFilename !== filename) {
|
|
786
|
-
await this.rotateStream(filename);
|
|
787
|
-
}
|
|
788
|
-
if (this.currentStream) {
|
|
789
|
-
return new Promise((resolve2, reject) => {
|
|
790
|
-
this.currentStream.write(message + "\n", "utf-8", (error) => {
|
|
791
|
-
if (error) {
|
|
792
|
-
process.stderr.write(`[FileTransport] Failed to write log: ${error.message}
|
|
793
|
-
`);
|
|
794
|
-
reject(error);
|
|
795
|
-
} else {
|
|
796
|
-
resolve2();
|
|
797
|
-
}
|
|
798
|
-
});
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
/**
|
|
803
|
-
* 스트림 교체 (날짜 변경 시)
|
|
804
|
-
*/
|
|
805
|
-
async rotateStream(filename) {
|
|
806
|
-
if (this.currentStream) {
|
|
807
|
-
await this.closeStream();
|
|
808
|
-
}
|
|
809
|
-
const filepath = join(this.logDir, filename);
|
|
810
|
-
this.currentStream = createWriteStream(filepath, {
|
|
811
|
-
flags: "a",
|
|
812
|
-
// append mode
|
|
813
|
-
encoding: "utf-8"
|
|
814
|
-
});
|
|
815
|
-
this.currentFilename = filename;
|
|
816
|
-
this.currentStream.on("error", (error) => {
|
|
817
|
-
process.stderr.write(`[FileTransport] Stream error: ${error.message}
|
|
818
|
-
`);
|
|
819
|
-
this.currentStream = null;
|
|
820
|
-
this.currentFilename = null;
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
/**
|
|
824
|
-
* 현재 스트림 닫기
|
|
825
|
-
*/
|
|
826
|
-
async closeStream() {
|
|
827
|
-
if (!this.currentStream) {
|
|
828
|
-
return;
|
|
829
|
-
}
|
|
830
|
-
return new Promise((resolve2, reject) => {
|
|
831
|
-
this.currentStream.end((error) => {
|
|
832
|
-
if (error) {
|
|
833
|
-
reject(error);
|
|
834
|
-
} else {
|
|
835
|
-
this.currentStream = null;
|
|
836
|
-
this.currentFilename = null;
|
|
837
|
-
resolve2();
|
|
838
|
-
}
|
|
839
|
-
});
|
|
840
|
-
});
|
|
841
|
-
}
|
|
842
|
-
/**
|
|
843
|
-
* 날짜별 로그 파일명 생성
|
|
844
|
-
*/
|
|
845
|
-
getLogFilename(date) {
|
|
846
|
-
const year = date.getFullYear();
|
|
847
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
848
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
849
|
-
return `${year}-${month}-${day}.log`;
|
|
850
|
-
}
|
|
851
|
-
async close() {
|
|
852
|
-
await this.closeStream();
|
|
853
|
-
}
|
|
854
|
-
};
|
|
855
|
-
|
|
856
|
-
// src/logger/config.ts
|
|
857
|
-
function getDefaultLogLevel() {
|
|
858
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
859
|
-
const isDevelopment = process.env.NODE_ENV === "development";
|
|
860
|
-
if (isDevelopment) {
|
|
861
|
-
return "debug";
|
|
862
|
-
}
|
|
863
|
-
if (isProduction) {
|
|
864
|
-
return "info";
|
|
865
|
-
}
|
|
866
|
-
return "warn";
|
|
867
|
-
}
|
|
868
|
-
function getConsoleConfig() {
|
|
869
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
870
|
-
return {
|
|
871
|
-
level: "debug",
|
|
872
|
-
enabled: true,
|
|
873
|
-
colorize: !isProduction
|
|
874
|
-
// Dev: colored output, Production: plain text
|
|
875
|
-
};
|
|
876
|
-
}
|
|
877
|
-
function getFileConfig() {
|
|
878
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
879
|
-
return {
|
|
880
|
-
level: "info",
|
|
881
|
-
enabled: isProduction,
|
|
882
|
-
// File logging in production only
|
|
883
|
-
logDir: process.env.LOG_DIR || "./logs",
|
|
884
|
-
maxFileSize: 10 * 1024 * 1024,
|
|
885
|
-
// 10MB
|
|
886
|
-
maxFiles: 10
|
|
887
|
-
};
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
// src/logger/adapters/custom.ts
|
|
891
|
-
function initializeTransports() {
|
|
892
|
-
const transports = [];
|
|
893
|
-
const consoleConfig = getConsoleConfig();
|
|
894
|
-
transports.push(new ConsoleTransport(consoleConfig));
|
|
895
|
-
const fileConfig = getFileConfig();
|
|
896
|
-
if (fileConfig.enabled) {
|
|
897
|
-
transports.push(new FileTransport(fileConfig));
|
|
898
|
-
}
|
|
899
|
-
return transports;
|
|
900
|
-
}
|
|
901
|
-
var CustomAdapter = class _CustomAdapter {
|
|
902
|
-
logger;
|
|
903
|
-
constructor(config2) {
|
|
904
|
-
this.logger = new Logger({
|
|
905
|
-
level: config2.level,
|
|
906
|
-
module: config2.module,
|
|
907
|
-
transports: initializeTransports()
|
|
908
|
-
});
|
|
909
|
-
}
|
|
910
|
-
child(module) {
|
|
911
|
-
const adapter = new _CustomAdapter({ level: this.logger.level, module });
|
|
912
|
-
adapter.logger = this.logger.child(module);
|
|
913
|
-
return adapter;
|
|
914
|
-
}
|
|
915
|
-
debug(message, context) {
|
|
916
|
-
this.logger.debug(message, context);
|
|
917
|
-
}
|
|
918
|
-
info(message, context) {
|
|
919
|
-
this.logger.info(message, context);
|
|
920
|
-
}
|
|
921
|
-
warn(message, errorOrContext, context) {
|
|
922
|
-
if (errorOrContext instanceof Error) {
|
|
923
|
-
this.logger.warn(message, errorOrContext, context);
|
|
924
|
-
} else {
|
|
925
|
-
this.logger.warn(message, errorOrContext);
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
error(message, errorOrContext, context) {
|
|
929
|
-
if (errorOrContext instanceof Error) {
|
|
930
|
-
this.logger.error(message, errorOrContext, context);
|
|
931
|
-
} else {
|
|
932
|
-
this.logger.error(message, errorOrContext);
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
fatal(message, errorOrContext, context) {
|
|
936
|
-
if (errorOrContext instanceof Error) {
|
|
937
|
-
this.logger.fatal(message, errorOrContext, context);
|
|
938
|
-
} else {
|
|
939
|
-
this.logger.fatal(message, errorOrContext);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
async close() {
|
|
943
|
-
await this.logger.close();
|
|
944
|
-
}
|
|
945
|
-
};
|
|
946
|
-
|
|
947
|
-
// src/logger/adapter-factory.ts
|
|
948
|
-
function createAdapter(type) {
|
|
949
|
-
const level = getDefaultLogLevel();
|
|
950
|
-
switch (type) {
|
|
951
|
-
case "pino":
|
|
952
|
-
return new PinoAdapter({ level });
|
|
953
|
-
case "custom":
|
|
954
|
-
return new CustomAdapter({ level });
|
|
955
|
-
default:
|
|
956
|
-
return new PinoAdapter({ level });
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
function getAdapterType() {
|
|
960
|
-
const adapterEnv = process.env.LOGGER_ADAPTER;
|
|
961
|
-
if (adapterEnv === "custom" || adapterEnv === "pino") {
|
|
962
|
-
return adapterEnv;
|
|
963
|
-
}
|
|
964
|
-
return "pino";
|
|
965
|
-
}
|
|
966
|
-
var logger = createAdapter(getAdapterType());
|
|
967
|
-
|
|
968
|
-
// src/codegen/watch-generate.ts
|
|
969
|
-
var codegenLogger = logger.child("codegen");
|
|
970
|
-
async function generateOnce(options) {
|
|
971
|
-
const cwd = process.cwd();
|
|
972
|
-
const routesDir = options.routesDir ?? join(cwd, "src", "server", "routes");
|
|
973
|
-
const outputPath = options.outputPath ?? join(cwd, "src", "lib", "api.ts");
|
|
974
|
-
try {
|
|
975
|
-
const contracts = await scanContracts(routesDir);
|
|
976
|
-
if (contracts.length === 0) {
|
|
977
|
-
if (options.debug) {
|
|
978
|
-
codegenLogger.warn("No contracts found");
|
|
979
|
-
}
|
|
980
|
-
return null;
|
|
981
|
-
}
|
|
982
|
-
const stats = await generateClient(contracts, {
|
|
983
|
-
outputPath,
|
|
984
|
-
includeTypes: true,
|
|
985
|
-
includeJsDoc: true
|
|
986
|
-
});
|
|
987
|
-
if (options.debug) {
|
|
988
|
-
codegenLogger.info("Client generated", {
|
|
989
|
-
endpoints: stats.methodsGenerated,
|
|
990
|
-
resources: stats.resourcesGenerated,
|
|
991
|
-
duration: stats.duration
|
|
992
|
-
});
|
|
993
|
-
}
|
|
994
|
-
return stats;
|
|
995
|
-
} catch (error) {
|
|
996
|
-
codegenLogger.error(
|
|
997
|
-
"Generation failed",
|
|
998
|
-
error instanceof Error ? error : new Error(String(error))
|
|
999
|
-
);
|
|
1000
|
-
return null;
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
async function watchAndGenerate(options = {}) {
|
|
1004
|
-
const cwd = process.cwd();
|
|
1005
|
-
const routesDir = options.routesDir ?? join(cwd, "src", "server", "routes");
|
|
1006
|
-
const outputPath = options.outputPath ?? join(cwd, "src", "lib", "api.ts");
|
|
1007
|
-
const watchMode = options.watch !== false;
|
|
1008
|
-
if (options.debug) {
|
|
1009
|
-
codegenLogger.info("Contract Watcher Started", { routesDir, outputPath, watch: watchMode });
|
|
1010
|
-
}
|
|
1011
|
-
await generateOnce(options);
|
|
1012
|
-
if (watchMode) {
|
|
1013
|
-
let isGenerating = false;
|
|
1014
|
-
let pendingRegeneration = false;
|
|
1015
|
-
const watcher = watch(routesDir, {
|
|
1016
|
-
ignored: /(^|[\/\\])\../,
|
|
1017
|
-
// ignore dotfiles
|
|
1018
|
-
persistent: true,
|
|
1019
|
-
ignoreInitial: true,
|
|
1020
|
-
awaitWriteFinish: {
|
|
1021
|
-
stabilityThreshold: 100,
|
|
1022
|
-
pollInterval: 50
|
|
1023
|
-
}
|
|
1024
|
-
});
|
|
1025
|
-
const regenerate = async () => {
|
|
1026
|
-
if (isGenerating) {
|
|
1027
|
-
pendingRegeneration = true;
|
|
1028
|
-
return;
|
|
1029
|
-
}
|
|
1030
|
-
isGenerating = true;
|
|
1031
|
-
pendingRegeneration = false;
|
|
1032
|
-
if (options.debug) {
|
|
1033
|
-
codegenLogger.info("Contracts changed, regenerating...");
|
|
1034
|
-
}
|
|
1035
|
-
await generateOnce(options);
|
|
1036
|
-
isGenerating = false;
|
|
1037
|
-
if (pendingRegeneration) {
|
|
1038
|
-
await regenerate();
|
|
1039
|
-
}
|
|
1040
|
-
};
|
|
1041
|
-
watcher.on("add", regenerate).on("change", regenerate).on("unlink", regenerate);
|
|
1042
|
-
process.on("SIGINT", () => {
|
|
1043
|
-
watcher.close();
|
|
1044
|
-
process.exit(0);
|
|
1045
|
-
});
|
|
1046
|
-
await new Promise(() => {
|
|
1047
|
-
});
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
1051
|
-
watchAndGenerate({ debug: true });
|
|
1052
|
-
}
|
|
1053
|
-
async function generateClientCommand(options = {}) {
|
|
1054
|
-
const contractsDir = options.contractsDir || resolve(process.cwd(), "src/server/contracts");
|
|
1055
|
-
const outputPath = options.output || resolve(process.cwd(), "src/generated/api-client.ts");
|
|
1056
|
-
const generationOptions = {
|
|
1057
|
-
// Using routesDir field for backward compatibility
|
|
1058
|
-
outputPath,
|
|
1059
|
-
includeTypes: true,
|
|
1060
|
-
includeJsDoc: true
|
|
1061
|
-
};
|
|
1062
|
-
console.log(chalk.blue("\u{1F50D} Scanning contracts..."));
|
|
1063
|
-
console.log(chalk.gray(` Contracts directory: ${contractsDir}`));
|
|
1064
|
-
try {
|
|
1065
|
-
await generateClientOnce(generationOptions, contractsDir);
|
|
1066
|
-
if (options.watch) {
|
|
1067
|
-
console.log(chalk.blue("\n\u{1F440} Watching for changes..."));
|
|
1068
|
-
await watchContracts(generationOptions, contractsDir);
|
|
1069
|
-
}
|
|
1070
|
-
} catch (error) {
|
|
1071
|
-
console.error(chalk.red("\u274C Generation failed:"));
|
|
1072
|
-
console.error(error);
|
|
1073
|
-
process.exit(1);
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
async function generateClientOnce(options, contractsDir) {
|
|
1077
|
-
const startTime = Date.now();
|
|
1078
|
-
const mappings = await scanContracts(contractsDir);
|
|
1079
|
-
if (mappings.length === 0) {
|
|
1080
|
-
console.log(chalk.yellow("\u26A0\uFE0F No contracts found"));
|
|
1081
|
-
console.log(chalk.gray(" Make sure your contracts include method and path"));
|
|
1082
|
-
return;
|
|
1083
|
-
}
|
|
1084
|
-
console.log(chalk.green(`\u2705 Found ${mappings.length} contracts`));
|
|
1085
|
-
const uniqueContracts = new Set(mappings.map((m) => m.contractName)).size;
|
|
1086
|
-
const uniquePaths = new Set(mappings.map((m) => m.contractImportPath)).size;
|
|
1087
|
-
console.log(chalk.gray(` Contracts: ${uniqueContracts}`));
|
|
1088
|
-
console.log(chalk.gray(` Contract files: ${uniquePaths}`));
|
|
1089
|
-
console.log(chalk.blue("\n\u{1F4DD} Generating client..."));
|
|
1090
|
-
const stats = await generateClient(mappings, options);
|
|
1091
|
-
const elapsed = Date.now() - startTime;
|
|
1092
|
-
console.log(chalk.green(`
|
|
1093
|
-
\u2728 Client generated successfully!`));
|
|
1094
|
-
console.log(chalk.gray(` Output: ${options.outputPath}`));
|
|
1095
|
-
console.log(chalk.gray(` Resources: ${stats.resourcesGenerated}`));
|
|
1096
|
-
console.log(chalk.gray(` Methods: ${stats.methodsGenerated}`));
|
|
1097
|
-
console.log(chalk.gray(` Time: ${elapsed}ms`));
|
|
1098
|
-
}
|
|
1099
|
-
async function watchContracts(options, contractsDir) {
|
|
1100
|
-
const watcher = watch(contractsDir, {
|
|
1101
|
-
ignored: /(^|[\/\\])\../,
|
|
1102
|
-
// ignore dotfiles
|
|
1103
|
-
persistent: true,
|
|
1104
|
-
ignoreInitial: true
|
|
1105
|
-
});
|
|
1106
|
-
let isGenerating = false;
|
|
1107
|
-
let pendingRegeneration = false;
|
|
1108
|
-
const regenerate = async () => {
|
|
1109
|
-
if (isGenerating) {
|
|
1110
|
-
pendingRegeneration = true;
|
|
1111
|
-
return;
|
|
1112
|
-
}
|
|
1113
|
-
isGenerating = true;
|
|
1114
|
-
pendingRegeneration = false;
|
|
1115
|
-
try {
|
|
1116
|
-
console.log(chalk.blue("\n\u{1F504} Contracts changed, regenerating..."));
|
|
1117
|
-
await generateClientOnce(options, contractsDir);
|
|
1118
|
-
} catch (error) {
|
|
1119
|
-
console.error(chalk.red("\u274C Regeneration failed:"));
|
|
1120
|
-
console.error(error);
|
|
1121
|
-
} finally {
|
|
1122
|
-
isGenerating = false;
|
|
1123
|
-
if (pendingRegeneration) {
|
|
1124
|
-
await regenerate();
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
};
|
|
1128
|
-
watcher.on("add", (path) => {
|
|
1129
|
-
console.log(chalk.gray(` File added: ${path}`));
|
|
1130
|
-
regenerate();
|
|
1131
|
-
}).on("change", (path) => {
|
|
1132
|
-
console.log(chalk.gray(` File changed: ${path}`));
|
|
1133
|
-
regenerate();
|
|
1134
|
-
}).on("unlink", (path) => {
|
|
1135
|
-
console.log(chalk.gray(` File removed: ${path}`));
|
|
1136
|
-
regenerate();
|
|
1137
|
-
}).on("error", (error) => {
|
|
1138
|
-
console.error(chalk.red("\u274C Watch error:"), error);
|
|
1139
|
-
});
|
|
1140
|
-
process.on("SIGINT", () => {
|
|
1141
|
-
console.log(chalk.blue("\n\n\u{1F44B} Stopping watch mode..."));
|
|
1142
|
-
watcher.close();
|
|
1143
|
-
process.exit(0);
|
|
1144
|
-
});
|
|
1145
|
-
}
|
|
1146
|
-
function parseArgs() {
|
|
1147
|
-
const args = process.argv.slice(2);
|
|
1148
|
-
const options = {};
|
|
1149
|
-
for (let i = 0; i < args.length; i++) {
|
|
1150
|
-
const arg = args[i];
|
|
1151
|
-
if (arg === "--watch" || arg === "-w") {
|
|
1152
|
-
options.watch = true;
|
|
1153
|
-
} else if (arg === "--contracts" || arg === "-c") {
|
|
1154
|
-
options.contractsDir = args[++i];
|
|
1155
|
-
} else if (arg === "--output" || arg === "-o") {
|
|
1156
|
-
options.output = args[++i];
|
|
1157
|
-
} else if (arg === "--base-url" || arg === "-b") {
|
|
1158
|
-
options.baseUrl = args[++i];
|
|
1159
|
-
} else if (arg === "--help" || arg === "-h") {
|
|
1160
|
-
printHelp();
|
|
1161
|
-
process.exit(0);
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
return options;
|
|
1165
|
-
}
|
|
1166
|
-
function printHelp() {
|
|
1167
|
-
console.log(`
|
|
1168
|
-
${chalk.bold("Generate API Client from Contracts")}
|
|
1169
|
-
|
|
1170
|
-
${chalk.bold("USAGE")}
|
|
1171
|
-
node dist/scripts/generate-client.js [options]
|
|
1172
|
-
|
|
1173
|
-
${chalk.bold("OPTIONS")}
|
|
1174
|
-
-c, --contracts <dir> Contracts directory (default: src/server/contracts)
|
|
1175
|
-
-o, --output <file> Output file path (default: src/generated/api-client.ts)
|
|
1176
|
-
-b, --base-url <url> Base URL for API client
|
|
1177
|
-
-w, --watch Watch for changes and regenerate
|
|
1178
|
-
-h, --help Show this help message
|
|
1179
|
-
|
|
1180
|
-
${chalk.bold("EXAMPLES")}
|
|
1181
|
-
# Generate once
|
|
1182
|
-
node dist/scripts/generate-client.js
|
|
1183
|
-
|
|
1184
|
-
# Watch mode
|
|
1185
|
-
node dist/scripts/generate-client.js --watch
|
|
1186
|
-
|
|
1187
|
-
# Custom paths
|
|
1188
|
-
node dist/scripts/generate-client.js --contracts ./contracts --output ./client.ts
|
|
1189
|
-
|
|
1190
|
-
# With base URL
|
|
1191
|
-
node dist/scripts/generate-client.js --base-url https://api.example.com
|
|
1192
|
-
`);
|
|
1193
|
-
}
|
|
1194
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
1195
|
-
const options = parseArgs();
|
|
1196
|
-
generateClientCommand(options);
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
export { generateClientCommand };
|
|
1200
|
-
//# sourceMappingURL=index.js.map
|
|
1201
|
-
//# sourceMappingURL=index.js.map
|