@honestjs/rpc-plugin 1.2.0 → 1.4.0
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 +73 -5
- package/dist/index.d.mts +59 -44
- package/dist/index.d.ts +59 -44
- package/dist/index.js +179 -137
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +170 -130
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -6
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
// src/rpc.plugin.ts
|
|
2
2
|
import fs2 from "fs";
|
|
3
|
-
import
|
|
3
|
+
import path3 from "path";
|
|
4
|
+
import { Project } from "ts-morph";
|
|
4
5
|
|
|
5
6
|
// src/constants/defaults.ts
|
|
6
7
|
var DEFAULT_OPTIONS = {
|
|
7
8
|
controllerPattern: "src/modules/*/*.controller.ts",
|
|
8
9
|
tsConfigPath: "tsconfig.json",
|
|
9
10
|
outputDir: "./generated/rpc",
|
|
10
|
-
generateOnInit: true
|
|
11
|
+
generateOnInit: true,
|
|
12
|
+
context: {
|
|
13
|
+
namespace: "rpc",
|
|
14
|
+
keys: {
|
|
15
|
+
artifact: "artifact"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
11
18
|
};
|
|
12
19
|
var LOG_PREFIX = "[ RPCPlugin ]";
|
|
13
20
|
var BUILTIN_UTILITY_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -25,29 +32,66 @@ var BUILTIN_UTILITY_TYPES = /* @__PURE__ */ new Set([
|
|
|
25
32
|
var BUILTIN_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "any", "void", "unknown"]);
|
|
26
33
|
var GENERIC_TYPES = /* @__PURE__ */ new Set(["Array", "Promise", "Partial"]);
|
|
27
34
|
|
|
35
|
+
// src/utils/hash-utils.ts
|
|
36
|
+
import { createHash } from "crypto";
|
|
37
|
+
import { existsSync, readFileSync } from "fs";
|
|
38
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
39
|
+
import path from "path";
|
|
40
|
+
var CHECKSUM_FILENAME = ".rpc-checksum";
|
|
41
|
+
function computeHash(filePaths) {
|
|
42
|
+
const sorted = [...filePaths].sort();
|
|
43
|
+
const hasher = createHash("sha256");
|
|
44
|
+
hasher.update(`files:${sorted.length}
|
|
45
|
+
`);
|
|
46
|
+
for (const filePath of sorted) {
|
|
47
|
+
hasher.update(readFileSync(filePath, "utf-8"));
|
|
48
|
+
hasher.update("\0");
|
|
49
|
+
}
|
|
50
|
+
return hasher.digest("hex");
|
|
51
|
+
}
|
|
52
|
+
function readChecksum(outputDir) {
|
|
53
|
+
const checksumPath = path.join(outputDir, CHECKSUM_FILENAME);
|
|
54
|
+
if (!existsSync(checksumPath)) return null;
|
|
55
|
+
try {
|
|
56
|
+
const raw = readFileSync(checksumPath, "utf-8");
|
|
57
|
+
const data = JSON.parse(raw);
|
|
58
|
+
if (typeof data.hash !== "string" || !Array.isArray(data.files)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return data;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function writeChecksum(outputDir, data) {
|
|
67
|
+
await mkdir(outputDir, { recursive: true });
|
|
68
|
+
const checksumPath = path.join(outputDir, CHECKSUM_FILENAME);
|
|
69
|
+
await writeFile(checksumPath, JSON.stringify(data, null, 2), "utf-8");
|
|
70
|
+
}
|
|
71
|
+
|
|
28
72
|
// src/services/client-generator.service.ts
|
|
29
73
|
import fs from "fs/promises";
|
|
30
|
-
import
|
|
74
|
+
import path2 from "path";
|
|
31
75
|
|
|
32
76
|
// src/utils/path-utils.ts
|
|
33
77
|
function buildFullPath(basePath, parameters) {
|
|
34
78
|
if (!basePath || typeof basePath !== "string") return "/";
|
|
35
|
-
let
|
|
79
|
+
let path4 = basePath;
|
|
36
80
|
if (parameters && Array.isArray(parameters)) {
|
|
37
81
|
for (const param of parameters) {
|
|
38
82
|
if (param.data && typeof param.data === "string" && param.data.startsWith(":")) {
|
|
39
83
|
const paramName = param.data.slice(1);
|
|
40
|
-
|
|
84
|
+
path4 = path4.replace(`:${paramName}`, `\${${paramName}}`);
|
|
41
85
|
}
|
|
42
86
|
}
|
|
43
87
|
}
|
|
44
|
-
return
|
|
88
|
+
return path4;
|
|
45
89
|
}
|
|
46
90
|
function buildFullApiPath(route) {
|
|
47
91
|
const prefix = route.prefix || "";
|
|
48
92
|
const version = route.version || "";
|
|
49
93
|
const routePath = route.route || "";
|
|
50
|
-
const
|
|
94
|
+
const path4 = route.path || "";
|
|
51
95
|
let fullPath = "";
|
|
52
96
|
if (prefix && prefix !== "/") {
|
|
53
97
|
fullPath += prefix.replace(/^\/+|\/+$/g, "");
|
|
@@ -58,11 +102,12 @@ function buildFullApiPath(route) {
|
|
|
58
102
|
if (routePath && routePath !== "/") {
|
|
59
103
|
fullPath += `/${routePath.replace(/^\/+|\/+$/g, "")}`;
|
|
60
104
|
}
|
|
61
|
-
if (
|
|
62
|
-
fullPath += `/${
|
|
63
|
-
} else if (
|
|
105
|
+
if (path4 && path4 !== "/") {
|
|
106
|
+
fullPath += `/${path4.replace(/^\/+|\/+$/g, "")}`;
|
|
107
|
+
} else if (path4 === "/") {
|
|
64
108
|
fullPath += "/";
|
|
65
109
|
}
|
|
110
|
+
if (fullPath && !fullPath.startsWith("/")) fullPath = "/" + fullPath;
|
|
66
111
|
return fullPath || "/";
|
|
67
112
|
}
|
|
68
113
|
|
|
@@ -88,7 +133,7 @@ var ClientGeneratorService = class {
|
|
|
88
133
|
await fs.mkdir(this.outputDir, { recursive: true });
|
|
89
134
|
await this.generateClientFile(routes, schemas);
|
|
90
135
|
const generatedInfo = {
|
|
91
|
-
clientFile:
|
|
136
|
+
clientFile: path2.join(this.outputDir, "client.ts"),
|
|
92
137
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
93
138
|
};
|
|
94
139
|
return generatedInfo;
|
|
@@ -98,7 +143,7 @@ var ClientGeneratorService = class {
|
|
|
98
143
|
*/
|
|
99
144
|
async generateClientFile(routes, schemas) {
|
|
100
145
|
const clientContent = this.generateClientContent(routes, schemas);
|
|
101
|
-
const clientPath =
|
|
146
|
+
const clientPath = path2.join(this.outputDir, "client.ts");
|
|
102
147
|
await fs.writeFile(clientPath, clientContent, "utf-8");
|
|
103
148
|
}
|
|
104
149
|
/**
|
|
@@ -258,6 +303,14 @@ export class ApiClient {
|
|
|
258
303
|
|
|
259
304
|
try {
|
|
260
305
|
const response = await this.fetchFn(url.toString(), requestOptions)
|
|
306
|
+
|
|
307
|
+
if (response.status === 204 || response.headers.get('content-length') === '0') {
|
|
308
|
+
if (!response.ok) {
|
|
309
|
+
throw new ApiError(response.status, 'Request failed')
|
|
310
|
+
}
|
|
311
|
+
return undefined as T
|
|
312
|
+
}
|
|
313
|
+
|
|
261
314
|
const responseData = await response.json()
|
|
262
315
|
|
|
263
316
|
if (!response.ok) {
|
|
@@ -400,71 +453,30 @@ ${this.generateControllerMethods(controllerGroups)}
|
|
|
400
453
|
*/
|
|
401
454
|
analyzeRouteParameters(route) {
|
|
402
455
|
const parameters = route.parameters || [];
|
|
403
|
-
const
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
return !!pathSegment && typeof pathSegment === "string" && route.path.includes(`:${pathSegment}`);
|
|
407
|
-
};
|
|
408
|
-
const pathParams = parameters.filter((p) => isInPath(p)).map((p) => ({ ...p, required: true }));
|
|
409
|
-
const rawBody = parameters.filter((p) => !isInPath(p) && method !== "get");
|
|
410
|
-
const bodyParams = rawBody.map((p) => ({
|
|
411
|
-
...p,
|
|
412
|
-
required: true
|
|
413
|
-
}));
|
|
414
|
-
const queryParams = parameters.filter((p) => !isInPath(p) && method === "get").map((p) => ({
|
|
415
|
-
...p,
|
|
416
|
-
required: p.required === true
|
|
417
|
-
// default false if not provided
|
|
418
|
-
}));
|
|
456
|
+
const pathParams = parameters.filter((p) => p.decoratorType === "param").map((p) => ({ ...p, required: true }));
|
|
457
|
+
const bodyParams = parameters.filter((p) => p.decoratorType === "body").map((p) => ({ ...p, required: true }));
|
|
458
|
+
const queryParams = parameters.filter((p) => p.decoratorType === "query").map((p) => ({ ...p, required: p.required === true }));
|
|
419
459
|
return { pathParams, queryParams, bodyParams };
|
|
420
460
|
}
|
|
421
461
|
};
|
|
422
462
|
|
|
423
463
|
// src/services/route-analyzer.service.ts
|
|
424
464
|
import { RouteRegistry } from "honestjs";
|
|
425
|
-
import { Project } from "ts-morph";
|
|
426
465
|
var RouteAnalyzerService = class {
|
|
427
|
-
constructor(controllerPattern, tsConfigPath) {
|
|
428
|
-
this.controllerPattern = controllerPattern;
|
|
429
|
-
this.tsConfigPath = tsConfigPath;
|
|
430
|
-
}
|
|
431
|
-
// Track projects for cleanup
|
|
432
|
-
projects = [];
|
|
433
466
|
/**
|
|
434
467
|
* Analyzes controller methods to extract type information
|
|
435
468
|
*/
|
|
436
|
-
async analyzeControllerMethods() {
|
|
469
|
+
async analyzeControllerMethods(project) {
|
|
437
470
|
const routes = RouteRegistry.getRoutes();
|
|
438
471
|
if (!routes?.length) {
|
|
439
472
|
return [];
|
|
440
473
|
}
|
|
441
|
-
const project = this.createProject();
|
|
442
474
|
const controllers = this.findControllerClasses(project);
|
|
443
475
|
if (controllers.size === 0) {
|
|
444
476
|
return [];
|
|
445
477
|
}
|
|
446
478
|
return this.processRoutes(routes, controllers);
|
|
447
479
|
}
|
|
448
|
-
/**
|
|
449
|
-
* Creates a new ts-morph project
|
|
450
|
-
*/
|
|
451
|
-
createProject() {
|
|
452
|
-
const project = new Project({
|
|
453
|
-
tsConfigFilePath: this.tsConfigPath
|
|
454
|
-
});
|
|
455
|
-
project.addSourceFilesAtPaths([this.controllerPattern]);
|
|
456
|
-
this.projects.push(project);
|
|
457
|
-
return project;
|
|
458
|
-
}
|
|
459
|
-
/**
|
|
460
|
-
* Cleanup resources to prevent memory leaks
|
|
461
|
-
*/
|
|
462
|
-
dispose() {
|
|
463
|
-
this.projects.forEach((project) => {
|
|
464
|
-
project.getSourceFiles().forEach((file) => project.removeSourceFile(file));
|
|
465
|
-
});
|
|
466
|
-
this.projects = [];
|
|
467
|
-
}
|
|
468
480
|
/**
|
|
469
481
|
* Finds controller classes in the project
|
|
470
482
|
*/
|
|
@@ -487,23 +499,17 @@ var RouteAnalyzerService = class {
|
|
|
487
499
|
*/
|
|
488
500
|
processRoutes(routes, controllers) {
|
|
489
501
|
const analyzedRoutes = [];
|
|
490
|
-
const errors = [];
|
|
491
502
|
for (const route of routes) {
|
|
492
503
|
try {
|
|
493
504
|
const extendedRoute = this.createExtendedRoute(route, controllers);
|
|
494
505
|
analyzedRoutes.push(extendedRoute);
|
|
495
506
|
} catch (routeError) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
console.error(
|
|
499
|
-
`Error processing route ${safeToString(route.controller)}.${safeToString(route.handler)}:`,
|
|
507
|
+
console.warn(
|
|
508
|
+
`${LOG_PREFIX} Skipping route ${safeToString(route.controller)}.${safeToString(route.handler)}:`,
|
|
500
509
|
routeError
|
|
501
510
|
);
|
|
502
511
|
}
|
|
503
512
|
}
|
|
504
|
-
if (errors.length > 0) {
|
|
505
|
-
throw new Error(`Failed to process ${errors.length} routes: ${errors.map((e) => e.message).join(", ")}`);
|
|
506
|
-
}
|
|
507
513
|
return analyzedRoutes;
|
|
508
514
|
}
|
|
509
515
|
/**
|
|
@@ -556,6 +562,7 @@ var RouteAnalyzerService = class {
|
|
|
556
562
|
const sortedParams = [...parameters].sort((a, b) => a.index - b.index);
|
|
557
563
|
for (const param of sortedParams) {
|
|
558
564
|
const index = param.index;
|
|
565
|
+
const decoratorType = param.name;
|
|
559
566
|
if (index < declaredParams.length) {
|
|
560
567
|
const declaredParam = declaredParams[index];
|
|
561
568
|
const paramName = declaredParam.getName();
|
|
@@ -563,6 +570,7 @@ var RouteAnalyzerService = class {
|
|
|
563
570
|
result.push({
|
|
564
571
|
index,
|
|
565
572
|
name: paramName,
|
|
573
|
+
decoratorType,
|
|
566
574
|
type: paramType,
|
|
567
575
|
required: true,
|
|
568
576
|
data: param.data,
|
|
@@ -573,6 +581,7 @@ var RouteAnalyzerService = class {
|
|
|
573
581
|
result.push({
|
|
574
582
|
index,
|
|
575
583
|
name: `param${index}`,
|
|
584
|
+
decoratorType,
|
|
576
585
|
type: param.metatype?.name || "unknown",
|
|
577
586
|
required: true,
|
|
578
587
|
data: param.data,
|
|
@@ -587,7 +596,6 @@ var RouteAnalyzerService = class {
|
|
|
587
596
|
|
|
588
597
|
// src/services/schema-generator.service.ts
|
|
589
598
|
import { createGenerator } from "ts-json-schema-generator";
|
|
590
|
-
import { Project as Project2 } from "ts-morph";
|
|
591
599
|
|
|
592
600
|
// src/utils/schema-utils.ts
|
|
593
601
|
function mapJsonSchemaTypeToTypeScript(schema) {
|
|
@@ -654,32 +662,6 @@ function extractNamedType(type) {
|
|
|
654
662
|
if (BUILTIN_TYPES.has(name)) return null;
|
|
655
663
|
return name;
|
|
656
664
|
}
|
|
657
|
-
function generateTypeImports(routes) {
|
|
658
|
-
const types = /* @__PURE__ */ new Set();
|
|
659
|
-
for (const route of routes) {
|
|
660
|
-
if (route.parameters) {
|
|
661
|
-
for (const param of route.parameters) {
|
|
662
|
-
if (param.type && !["string", "number", "boolean"].includes(param.type)) {
|
|
663
|
-
const typeMatch = param.type.match(/(\w+)(?:<.*>)?/);
|
|
664
|
-
if (typeMatch) {
|
|
665
|
-
const typeName = typeMatch[1];
|
|
666
|
-
if (!BUILTIN_UTILITY_TYPES.has(typeName)) {
|
|
667
|
-
types.add(typeName);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
if (route.returns) {
|
|
674
|
-
const returnType = route.returns.replace(/Promise<(.+)>/, "$1");
|
|
675
|
-
const baseType = returnType.replace(/\[\]$/, "");
|
|
676
|
-
if (!["string", "number", "boolean", "any", "void", "unknown"].includes(baseType)) {
|
|
677
|
-
types.add(baseType);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
return Array.from(types).join(", ");
|
|
682
|
-
}
|
|
683
665
|
|
|
684
666
|
// src/services/schema-generator.service.ts
|
|
685
667
|
var SchemaGeneratorService = class {
|
|
@@ -687,37 +669,14 @@ var SchemaGeneratorService = class {
|
|
|
687
669
|
this.controllerPattern = controllerPattern;
|
|
688
670
|
this.tsConfigPath = tsConfigPath;
|
|
689
671
|
}
|
|
690
|
-
// Track projects for cleanup
|
|
691
|
-
projects = [];
|
|
692
672
|
/**
|
|
693
673
|
* Generates JSON schemas from types used in controllers
|
|
694
674
|
*/
|
|
695
|
-
async generateSchemas() {
|
|
696
|
-
const project = this.createProject();
|
|
675
|
+
async generateSchemas(project) {
|
|
697
676
|
const sourceFiles = project.getSourceFiles(this.controllerPattern);
|
|
698
677
|
const collectedTypes = this.collectTypesFromControllers(sourceFiles);
|
|
699
678
|
return this.processTypes(collectedTypes);
|
|
700
679
|
}
|
|
701
|
-
/**
|
|
702
|
-
* Creates a new ts-morph project
|
|
703
|
-
*/
|
|
704
|
-
createProject() {
|
|
705
|
-
const project = new Project2({
|
|
706
|
-
tsConfigFilePath: this.tsConfigPath
|
|
707
|
-
});
|
|
708
|
-
project.addSourceFilesAtPaths([this.controllerPattern]);
|
|
709
|
-
this.projects.push(project);
|
|
710
|
-
return project;
|
|
711
|
-
}
|
|
712
|
-
/**
|
|
713
|
-
* Cleanup resources to prevent memory leaks
|
|
714
|
-
*/
|
|
715
|
-
dispose() {
|
|
716
|
-
this.projects.forEach((project) => {
|
|
717
|
-
project.getSourceFiles().forEach((file) => project.removeSourceFile(file));
|
|
718
|
-
});
|
|
719
|
-
this.projects = [];
|
|
720
|
-
}
|
|
721
680
|
/**
|
|
722
681
|
* Collects types from controller files
|
|
723
682
|
*/
|
|
@@ -795,20 +754,27 @@ var RPCPlugin = class {
|
|
|
795
754
|
tsConfigPath;
|
|
796
755
|
outputDir;
|
|
797
756
|
generateOnInit;
|
|
757
|
+
contextNamespace;
|
|
758
|
+
contextArtifactKey;
|
|
798
759
|
// Services
|
|
799
760
|
routeAnalyzer;
|
|
800
761
|
schemaGenerator;
|
|
801
762
|
clientGenerator;
|
|
763
|
+
// Shared ts-morph project
|
|
764
|
+
project = null;
|
|
802
765
|
// Internal state
|
|
803
766
|
analyzedRoutes = [];
|
|
804
767
|
analyzedSchemas = [];
|
|
805
768
|
generatedInfo = null;
|
|
769
|
+
app = null;
|
|
806
770
|
constructor(options = {}) {
|
|
807
771
|
this.controllerPattern = options.controllerPattern ?? DEFAULT_OPTIONS.controllerPattern;
|
|
808
|
-
this.tsConfigPath = options.tsConfigPath ??
|
|
809
|
-
this.outputDir = options.outputDir ??
|
|
772
|
+
this.tsConfigPath = options.tsConfigPath ?? path3.resolve(process.cwd(), DEFAULT_OPTIONS.tsConfigPath);
|
|
773
|
+
this.outputDir = options.outputDir ?? path3.resolve(process.cwd(), DEFAULT_OPTIONS.outputDir);
|
|
810
774
|
this.generateOnInit = options.generateOnInit ?? DEFAULT_OPTIONS.generateOnInit;
|
|
811
|
-
this.
|
|
775
|
+
this.contextNamespace = options.context?.namespace ?? DEFAULT_OPTIONS.context.namespace;
|
|
776
|
+
this.contextArtifactKey = options.context?.keys?.artifact ?? DEFAULT_OPTIONS.context.keys.artifact;
|
|
777
|
+
this.routeAnalyzer = new RouteAnalyzerService();
|
|
812
778
|
this.schemaGenerator = new SchemaGeneratorService(this.controllerPattern, this.tsConfigPath);
|
|
813
779
|
this.clientGenerator = new ClientGeneratorService(this.outputDir);
|
|
814
780
|
this.validateConfiguration();
|
|
@@ -831,6 +797,12 @@ var RPCPlugin = class {
|
|
|
831
797
|
if (!this.outputDir?.trim()) {
|
|
832
798
|
errors.push("Output directory cannot be empty");
|
|
833
799
|
}
|
|
800
|
+
if (!this.contextNamespace?.trim()) {
|
|
801
|
+
errors.push("Context namespace cannot be empty");
|
|
802
|
+
}
|
|
803
|
+
if (!this.contextArtifactKey?.trim()) {
|
|
804
|
+
errors.push("Context artifact key cannot be empty");
|
|
805
|
+
}
|
|
834
806
|
if (errors.length > 0) {
|
|
835
807
|
throw new Error(`Configuration validation failed: ${errors.join(", ")}`);
|
|
836
808
|
}
|
|
@@ -842,22 +814,42 @@ var RPCPlugin = class {
|
|
|
842
814
|
* Called after all modules are registered
|
|
843
815
|
*/
|
|
844
816
|
afterModulesRegistered = async (app, hono) => {
|
|
817
|
+
this.app = app;
|
|
845
818
|
if (this.generateOnInit) {
|
|
846
819
|
await this.analyzeEverything();
|
|
820
|
+
this.publishArtifact(app);
|
|
847
821
|
}
|
|
848
822
|
};
|
|
849
823
|
/**
|
|
850
824
|
* Main analysis method that coordinates all three components
|
|
851
825
|
*/
|
|
852
|
-
async analyzeEverything() {
|
|
826
|
+
async analyzeEverything(force = false) {
|
|
853
827
|
try {
|
|
854
828
|
this.log("Starting comprehensive RPC analysis...");
|
|
829
|
+
this.dispose();
|
|
830
|
+
this.project = new Project({ tsConfigFilePath: this.tsConfigPath });
|
|
831
|
+
this.project.addSourceFilesAtPaths([this.controllerPattern]);
|
|
832
|
+
const filePaths = this.project.getSourceFiles().map((f) => f.getFilePath());
|
|
833
|
+
if (!force) {
|
|
834
|
+
const currentHash = computeHash(filePaths);
|
|
835
|
+
const stored = readChecksum(this.outputDir);
|
|
836
|
+
if (stored && stored.hash === currentHash && this.outputFilesExist()) {
|
|
837
|
+
if (this.loadArtifactFromDisk()) {
|
|
838
|
+
this.log("Source files unchanged \u2014 skipping regeneration");
|
|
839
|
+
this.dispose();
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
this.log("Source files unchanged but cached artifact missing/invalid \u2014 regenerating");
|
|
843
|
+
}
|
|
844
|
+
}
|
|
855
845
|
this.analyzedRoutes = [];
|
|
856
846
|
this.analyzedSchemas = [];
|
|
857
847
|
this.generatedInfo = null;
|
|
858
|
-
this.analyzedRoutes = await this.routeAnalyzer.analyzeControllerMethods();
|
|
859
|
-
this.analyzedSchemas = await this.schemaGenerator.generateSchemas();
|
|
848
|
+
this.analyzedRoutes = await this.routeAnalyzer.analyzeControllerMethods(this.project);
|
|
849
|
+
this.analyzedSchemas = await this.schemaGenerator.generateSchemas(this.project);
|
|
860
850
|
this.generatedInfo = await this.clientGenerator.generateClient(this.analyzedRoutes, this.analyzedSchemas);
|
|
851
|
+
await writeChecksum(this.outputDir, { hash: computeHash(filePaths), files: filePaths });
|
|
852
|
+
this.writeArtifactToDisk();
|
|
861
853
|
this.log(
|
|
862
854
|
`\u2705 RPC analysis complete: ${this.analyzedRoutes.length} routes, ${this.analyzedSchemas.length} schemas`
|
|
863
855
|
);
|
|
@@ -868,10 +860,14 @@ var RPCPlugin = class {
|
|
|
868
860
|
}
|
|
869
861
|
}
|
|
870
862
|
/**
|
|
871
|
-
* Manually trigger analysis (useful for testing or re-generation)
|
|
863
|
+
* Manually trigger analysis (useful for testing or re-generation).
|
|
864
|
+
* Defaults to force=true to bypass cache; pass false to use caching.
|
|
872
865
|
*/
|
|
873
|
-
async analyze() {
|
|
874
|
-
await this.analyzeEverything();
|
|
866
|
+
async analyze(force = true) {
|
|
867
|
+
await this.analyzeEverything(force);
|
|
868
|
+
if (this.app) {
|
|
869
|
+
this.publishArtifact(this.app);
|
|
870
|
+
}
|
|
875
871
|
}
|
|
876
872
|
/**
|
|
877
873
|
* Get the analyzed routes
|
|
@@ -891,13 +887,55 @@ var RPCPlugin = class {
|
|
|
891
887
|
getGenerationInfo() {
|
|
892
888
|
return this.generatedInfo;
|
|
893
889
|
}
|
|
890
|
+
/**
|
|
891
|
+
* Checks whether expected output files exist on disk
|
|
892
|
+
*/
|
|
893
|
+
outputFilesExist() {
|
|
894
|
+
return fs2.existsSync(path3.join(this.outputDir, "client.ts")) && fs2.existsSync(path3.join(this.outputDir, "rpc-artifact.json"));
|
|
895
|
+
}
|
|
896
|
+
getArtifactPath() {
|
|
897
|
+
return path3.join(this.outputDir, "rpc-artifact.json");
|
|
898
|
+
}
|
|
899
|
+
writeArtifactToDisk() {
|
|
900
|
+
const artifact = {
|
|
901
|
+
routes: this.analyzedRoutes,
|
|
902
|
+
schemas: this.analyzedSchemas
|
|
903
|
+
};
|
|
904
|
+
fs2.mkdirSync(this.outputDir, { recursive: true });
|
|
905
|
+
fs2.writeFileSync(this.getArtifactPath(), JSON.stringify(artifact));
|
|
906
|
+
}
|
|
907
|
+
loadArtifactFromDisk() {
|
|
908
|
+
try {
|
|
909
|
+
const raw = fs2.readFileSync(this.getArtifactPath(), "utf8");
|
|
910
|
+
const parsed = JSON.parse(raw);
|
|
911
|
+
if (!Array.isArray(parsed.routes) || !Array.isArray(parsed.schemas)) {
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
this.analyzedRoutes = parsed.routes;
|
|
915
|
+
this.analyzedSchemas = parsed.schemas;
|
|
916
|
+
this.generatedInfo = null;
|
|
917
|
+
return true;
|
|
918
|
+
} catch {
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
publishArtifact(app) {
|
|
923
|
+
app.getContext().set(this.getArtifactContextKey(), {
|
|
924
|
+
routes: this.analyzedRoutes,
|
|
925
|
+
schemas: this.analyzedSchemas
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
getArtifactContextKey() {
|
|
929
|
+
return `${this.contextNamespace}.${this.contextArtifactKey}`;
|
|
930
|
+
}
|
|
894
931
|
/**
|
|
895
932
|
* Cleanup resources to prevent memory leaks
|
|
896
933
|
*/
|
|
897
934
|
dispose() {
|
|
898
|
-
this.
|
|
899
|
-
|
|
900
|
-
|
|
935
|
+
if (this.project) {
|
|
936
|
+
this.project.getSourceFiles().forEach((file) => this.project.removeSourceFile(file));
|
|
937
|
+
this.project = null;
|
|
938
|
+
}
|
|
901
939
|
}
|
|
902
940
|
// ============================================================================
|
|
903
941
|
// LOGGING UTILITIES
|
|
@@ -928,10 +966,12 @@ export {
|
|
|
928
966
|
buildFullApiPath,
|
|
929
967
|
buildFullPath,
|
|
930
968
|
camelCase,
|
|
969
|
+
computeHash,
|
|
931
970
|
extractNamedType,
|
|
932
|
-
generateTypeImports,
|
|
933
971
|
generateTypeScriptInterface,
|
|
934
972
|
mapJsonSchemaTypeToTypeScript,
|
|
935
|
-
|
|
973
|
+
readChecksum,
|
|
974
|
+
safeToString,
|
|
975
|
+
writeChecksum
|
|
936
976
|
};
|
|
937
977
|
//# sourceMappingURL=index.mjs.map
|