@bitstack/ng-query-codegen-openapi 0.0.30

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.
Files changed (45) hide show
  1. package/README.md +77 -0
  2. package/lib/bin/cli.mjs +186 -0
  3. package/lib/bin/cli.mjs.map +1 -0
  4. package/lib/index.d.mts +191 -0
  5. package/lib/index.d.ts +191 -0
  6. package/lib/index.js +1392 -0
  7. package/lib/index.js.map +1 -0
  8. package/lib/index.mjs +1376 -0
  9. package/lib/index.mjs.map +1 -0
  10. package/package.json +91 -0
  11. package/src/bin/cli.ts +77 -0
  12. package/src/bin/utils.ts +85 -0
  13. package/src/generators/api-service-generator.ts +112 -0
  14. package/src/generators/cache-keys-generator.ts +43 -0
  15. package/src/generators/common-types-generator.ts +33 -0
  16. package/src/generators/component-schema-generator.ts +25 -0
  17. package/src/generators/do-not-modify-generator.ts +27 -0
  18. package/src/generators/index-generator.ts +11 -0
  19. package/src/generators/query-service-generator.ts +108 -0
  20. package/src/generators/types-generator.ts +285 -0
  21. package/src/generators/utils-generator.ts +33 -0
  22. package/src/index.ts +21 -0
  23. package/src/services/api-code-generator.ts +157 -0
  24. package/src/services/api-service-generator.ts +24 -0
  25. package/src/services/endpoint-info-extractor.ts +119 -0
  26. package/src/services/file-writer-service.ts +148 -0
  27. package/src/services/group-service.ts +84 -0
  28. package/src/services/openapi-parser-service.ts +72 -0
  29. package/src/services/openapi-service.ts +61 -0
  30. package/src/services/query-code-generator.ts +24 -0
  31. package/src/services/unified-code-generator.ts +353 -0
  32. package/src/types.ts +248 -0
  33. package/src/utils/capitalize.ts +3 -0
  34. package/src/utils/directory.ts +75 -0
  35. package/src/utils/downloadSchema.ts +33 -0
  36. package/src/utils/factory.ts +29 -0
  37. package/src/utils/getOperationDefinitions.ts +20 -0
  38. package/src/utils/getV3Doc.ts +24 -0
  39. package/src/utils/http.ts +86 -0
  40. package/src/utils/index.ts +9 -0
  41. package/src/utils/isQuery.ts +16 -0
  42. package/src/utils/isValidUrl.ts +9 -0
  43. package/src/utils/messages.ts +7 -0
  44. package/src/utils/prettier.ts +51 -0
  45. package/src/utils/removeUndefined.ts +3 -0
@@ -0,0 +1,353 @@
1
+ import path from 'node:path';
2
+ import ts from 'typescript';
3
+ import type { OpenAPIV3 } from 'openapi-types';
4
+ import { OpenApiService } from './openapi-service';
5
+ import { GroupService, type GroupConfig } from './group-service';
6
+ import { FileWriterService, type FileWriteResult } from './file-writer-service';
7
+ export interface EndpointCacheKey {
8
+ operationName: string;
9
+ queryKeyName: string;
10
+ groupKey: string;
11
+ }
12
+ import { OpenApiParserService } from './openapi-parser-service';
13
+ import { generateCacheKeysFile } from '../generators/cache-keys-generator';
14
+ import { generateCommonTypesFile } from '../generators/common-types-generator';
15
+ import { generateComponentSchemaFile } from '../generators/component-schema-generator';
16
+ import { generateDoNotModifyFile } from '../generators/do-not-modify-generator';
17
+ import type { GenerationOptions, CommonOptions } from '../types';
18
+ import { ApiCodeGenerator } from './api-code-generator';
19
+ import { generateUtilsFile } from '../generators/utils-generator';
20
+
21
+ /**
22
+ * 統一代碼生成器選項
23
+ */
24
+ export interface UnifiedGenerationOptions extends CommonOptions {
25
+ outputFiles: GroupConfig;
26
+ remoteFile?: string;
27
+ }
28
+
29
+ /**
30
+ * 統一代碼生成器結果
31
+ */
32
+ export interface UnifiedGenerationResult {
33
+ success: boolean;
34
+ writtenFiles: FileWriteResult[];
35
+ errors: Error[];
36
+ generatedGroups: string[];
37
+ }
38
+
39
+ /**
40
+ * 統一代碼生成器
41
+ *
42
+ * 設計理念:
43
+ * - 統一管理所有生成流程
44
+ * - 內建 schema 處理和存儲
45
+ * - 提供準備、生成、發佈的分階段操作
46
+ * - 避免重複初始化和處理
47
+ *
48
+ * 使用方式:
49
+ * 1. prepare() - 準備階段:解析 schema、初始化服務
50
+ * 2. generateContent() - 生成階段:產生所有內容但不寫檔
51
+ * 3. release() - 發佈階段:統一寫入所有檔案
52
+ */
53
+ export class UnifiedCodeGenerator {
54
+ private _options: UnifiedGenerationOptions;
55
+
56
+ private openApiService = new OpenApiService();
57
+ private groupService = new GroupService();
58
+ private fileWriterService = new FileWriterService();
59
+
60
+ // 內部狀態存儲
61
+ private openApiDoc: OpenAPIV3.Document | null = null;
62
+ private parserService: OpenApiParserService | null = null;
63
+ private schemaInterfaces: Record<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration> = {};
64
+ private allEndpointCacheKeys: EndpointCacheKey[] = [];
65
+ private actualSchemaFile: string = '';
66
+
67
+ // 生成內容存儲
68
+ private generatedContent: {
69
+ groups: Array<{
70
+ groupKey: string;
71
+ outputPath: string;
72
+ content: any;
73
+ }>;
74
+ cacheKeys: string | null;
75
+ commonTypes: string;
76
+ componentSchema: string;
77
+ doNotModify: string;
78
+ utils: string;
79
+ } = {
80
+ groups: [],
81
+ cacheKeys: null,
82
+ commonTypes: '',
83
+ componentSchema: '',
84
+ doNotModify: '',
85
+ utils: ''
86
+ };
87
+
88
+
89
+ constructor(options: UnifiedGenerationOptions) {
90
+ this._options = options;
91
+ }
92
+
93
+
94
+
95
+ /**
96
+ * 一次性生成(整合所有階段)
97
+ */
98
+ async generateAll(): Promise<UnifiedGenerationResult> {
99
+ await this.prepare();
100
+
101
+ // 生成各API文件
102
+ await this.generateApi();
103
+ // await this.generateQuery();
104
+
105
+ // 生成共用
106
+ this.generateCacheKeysContent()
107
+ this.generateCommonTypesContent()
108
+ this.generateSchemaContent()
109
+ this.generateUtilsContent()
110
+ this.generateDoNotModifyContent()
111
+
112
+ return await this.release();
113
+ }
114
+
115
+
116
+
117
+ /**
118
+ * 準備階段:解析 schema 並初始化所有服務
119
+ */
120
+ async prepare(): Promise<void> {
121
+ // console.log('UnifiedCodeGenerator: 準備階段開始...');
122
+
123
+ // 步驟 1: 解析實際的 schema 檔案路徑
124
+ this.actualSchemaFile = this._options.schemaFile;
125
+ if (this._options.remoteFile) {
126
+ this.actualSchemaFile = await this.openApiService.downloadSchema(
127
+ this._options.remoteFile,
128
+ this._options.schemaFile
129
+ );
130
+ }
131
+
132
+ // 步驟 2: 獲取 OpenAPI 文檔(只初始化一次)
133
+ this.openApiDoc = await this.openApiService.getDocument(
134
+ this.actualSchemaFile,
135
+ this._options.httpResolverOptions
136
+ );
137
+
138
+ // 步驟 3: 初始化解析器服務並處理 schema
139
+ this.parserService = new OpenApiParserService(this.openApiDoc, this._options);
140
+ this.parserService.initialize();
141
+
142
+ // 步驟 4: 提取並儲存 schema interfaces
143
+ const apiGen = this.parserService.getApiGenerator();
144
+ this.schemaInterfaces = apiGen.aliases.reduce<Record<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration>>((curr, alias) => {
145
+ if (ts.isInterfaceDeclaration(alias) || ts.isTypeAliasDeclaration(alias)) {
146
+ const name = alias.name.text;
147
+ return {
148
+ ...curr,
149
+ [name]: alias,
150
+ };
151
+ }
152
+ return curr;
153
+ }, {});
154
+
155
+ // console.log('UnifiedCodeGenerator: 準備階段完成');
156
+ }
157
+
158
+
159
+
160
+ /**
161
+ * 生成階段:產生所有內容但不寫檔
162
+ */
163
+ async generateApi(): Promise<void> {
164
+ if (!this.openApiDoc || !this.parserService) {
165
+ throw new Error('請先調用 prepare() 方法');
166
+ }
167
+
168
+ // console.log('UnifiedCodeGenerator: 內容生成階段開始...');
169
+
170
+ // 獲取所有 API 接口Path並分組
171
+ const paths = this.openApiService.getPaths(this.openApiDoc);
172
+ const groupInfos = this.groupService.groupPaths(paths, this._options.outputFiles);
173
+
174
+ // 為每個群組生成內容
175
+ for (const groupInfo of Object.values(groupInfos)) {
176
+ try {
177
+ const groupContent = await this.generateApiGroupContent(
178
+ this._options,
179
+ groupInfo
180
+ );
181
+
182
+ // 檢查群組是否有任何有效的 endpoint
183
+ if (groupContent.operationNames.length > 0) {
184
+ this.generatedContent.groups.push({
185
+ groupKey: groupInfo.groupKey,
186
+ outputPath: groupInfo.outputPath,
187
+ content: groupContent
188
+ });
189
+ }
190
+ // 如果沒有任何 endpoint,則跳過此群組,不創建資料夾
191
+ } catch (error) {
192
+ throw new Error(`群組 ${groupInfo.groupKey} 生成失敗: ${error}`);
193
+ }
194
+ }
195
+
196
+ // console.log('UnifiedCodeGenerator: 內容生成階段完成');
197
+ }
198
+
199
+
200
+ /**
201
+ * 生成 cache keys
202
+ */
203
+ private async generateCacheKeysContent(): Promise<void> {
204
+ this.generatedContent.cacheKeys = generateCacheKeysFile(this.allEndpointCacheKeys);
205
+ }
206
+
207
+ /**
208
+ * 生成 common types
209
+ */
210
+ private async generateCommonTypesContent(): Promise<void> {
211
+ this.generatedContent.commonTypes = generateCommonTypesFile();
212
+ }
213
+
214
+ /**
215
+ * 生成Schema
216
+ */
217
+ private async generateSchemaContent(): Promise<void> {
218
+ this.generatedContent.componentSchema = generateComponentSchemaFile(this.schemaInterfaces);
219
+ }
220
+
221
+ /**
222
+ * 生成 DO_NOT_MODIFY.md
223
+ */
224
+ private async generateDoNotModifyContent(): Promise<void> {
225
+ this.generatedContent.doNotModify = generateDoNotModifyFile();
226
+ }
227
+
228
+
229
+ /**
230
+ * 生成 Utils Function
231
+ */
232
+ private async generateUtilsContent(): Promise<void> {
233
+ this.generatedContent.utils = generateUtilsFile();
234
+ }
235
+
236
+
237
+
238
+
239
+ /**
240
+ * 發佈階段:統一寫入所有檔案
241
+ */
242
+ async release(): Promise<UnifiedGenerationResult> {
243
+ const results: FileWriteResult[] = [];
244
+ const errors: Error[] = [];
245
+ const generatedGroups: string[] = [];
246
+
247
+ // console.log('UnifiedCodeGenerator: 發佈階段開始...');
248
+
249
+ try {
250
+ // 寫入群組檔案
251
+ for (const group of this.generatedContent.groups) {
252
+ try {
253
+ if (group.content?.files) {
254
+ const groupOutputDir = path.dirname(group.outputPath);
255
+ const groupResults = await this.fileWriterService.writeGroupFiles(
256
+ groupOutputDir,
257
+ {
258
+ types: group.content.files.types,
259
+ apiService: group.content.files.apiService,
260
+ queryService: group.content.files.queryService,
261
+ index: group.content.files.index
262
+ }
263
+ );
264
+ results.push(...groupResults);
265
+ generatedGroups.push(group.groupKey);
266
+ }
267
+ } catch (error) {
268
+ errors.push(new Error(`寫入群組 ${group.groupKey} 失敗: ${error}`));
269
+ }
270
+ }
271
+
272
+ // 寫入共用檔案
273
+ const outputDir = this.generatedContent.groups[0] ?
274
+ path.dirname(path.dirname(this.generatedContent.groups[0].outputPath)) :
275
+ './generated';
276
+
277
+ // 寫入共用檔案 (包含 DO_NOT_MODIFY.md)
278
+ if (this.generatedContent.cacheKeys || this.generatedContent.commonTypes || this.generatedContent.doNotModify || this.generatedContent.utils) {
279
+ const sharedResults = await this.fileWriterService.writeSharedFiles(
280
+ outputDir,
281
+ {
282
+ cacheKeys: this.generatedContent.cacheKeys || undefined,
283
+ commonTypes: this.generatedContent.commonTypes || undefined,
284
+ doNotModify: this.generatedContent.doNotModify || undefined,
285
+ utils: this.generatedContent.utils || undefined
286
+ }
287
+ );
288
+ results.push(...sharedResults);
289
+ }
290
+
291
+ // 寫入 component schema
292
+ if (this.generatedContent.componentSchema) {
293
+ const schemaResults = await this.fileWriterService.writeSchemaFile(
294
+ outputDir,
295
+ this.generatedContent.componentSchema
296
+ );
297
+ results.push(...schemaResults);
298
+ }
299
+
300
+ } catch (error) {
301
+ errors.push(error as Error);
302
+ }
303
+
304
+ // console.log('UnifiedCodeGenerator: 發佈階段完成');
305
+
306
+ return {
307
+ success: errors.length === 0,
308
+ writtenFiles: results,
309
+ errors,
310
+ generatedGroups
311
+ };
312
+ }
313
+
314
+
315
+ /**
316
+ * 為單一群組生成內容
317
+ */
318
+ private async generateApiGroupContent(
319
+ options: UnifiedGenerationOptions,
320
+ groupInfo: { groupKey: string; paths: string[]; outputPath: string }
321
+ ): Promise<any> {
322
+ const { outputFiles, ...commonConfig } = options;
323
+
324
+ // 建立群組特定的生成選項
325
+ const groupOptions: GenerationOptions = {
326
+ ...commonConfig,
327
+ schemaFile: this.actualSchemaFile,
328
+ outputFile: groupInfo.outputPath,
329
+ sharedTypesFile: `${outputFiles.outputDir}/common-types.ts`,
330
+ filterEndpoints: this.groupService.createGroupFilter(groupInfo.groupKey, outputFiles),
331
+ queryMatch: outputFiles.queryMatch,
332
+ groupKey: groupInfo.groupKey,
333
+ };
334
+
335
+ // 使用新的 ApiCodeGenerator 生成程式碼,重用已處理的 v3Doc
336
+ if (!this.openApiDoc || !this.parserService) {
337
+ throw new Error('OpenAPI 文檔未初始化,請先調用 prepare()');
338
+ }
339
+
340
+ const apiGenerator = new ApiCodeGenerator(this.parserService, groupOptions);
341
+ const result = await apiGenerator.generate();
342
+
343
+ // 提取並儲存快取鍵
344
+ if (result.files && 'allEndpointCacheKeys' in result.files) {
345
+ const cacheKeys = result.files.allEndpointCacheKeys as EndpointCacheKey[];
346
+ this.allEndpointCacheKeys.push(...cacheKeys);
347
+ }
348
+
349
+ return result;
350
+ }
351
+
352
+
353
+ }
package/src/types.ts ADDED
@@ -0,0 +1,248 @@
1
+ import type SwaggerParser from '@apidevtools/swagger-parser';
2
+ import type { OpenAPIV3 } from 'openapi-types';
3
+ import ts from 'typescript';
4
+
5
+ // 重新匯出服務相關類型
6
+ export type {
7
+ GroupConfig,
8
+ GroupInfo
9
+ } from './services/group-service';
10
+
11
+ export type {
12
+ FileWriteResult
13
+ } from './services/file-writer-service';
14
+
15
+ export type {
16
+ EndpointCacheKey
17
+ } from './services/unified-code-generator';
18
+
19
+ export type {
20
+ UnifiedGenerationOptions as EndpointGenerationOptions,
21
+ UnifiedGenerationResult as EndpointGenerationResult
22
+ } from './services/unified-code-generator';
23
+
24
+ export type OperationDefinition = {
25
+ path: string;
26
+ verb: (typeof operationKeys)[number];
27
+ pathItem: OpenAPIV3.PathItemObject;
28
+ operation: OpenAPIV3.OperationObject;
29
+ };
30
+
31
+ export type ParameterDefinition = OpenAPIV3.ParameterObject;
32
+
33
+ type Require<T, K extends keyof T> = { [k in K]-?: NonNullable<T[k]> } & Omit<T, K>;
34
+ type Optional<T, K extends keyof T> = { [k in K]?: NonNullable<T[k]> } & Omit<T, K>;
35
+ type Id<T> = { [K in keyof T]: T[K] } & {};
36
+ type AtLeastOneKey<T> = {
37
+ [K in keyof T]-?: Pick<T, K> & Partial<T>;
38
+ }[keyof T];
39
+
40
+ export const operationKeys = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'] as const;
41
+
42
+ export type GenerationOptions = Id<
43
+ CommonOptions &
44
+ Optional<OutputFileOptions, 'outputFile'> & {
45
+ isDataResponse?(
46
+ code: string,
47
+ includeDefault: boolean,
48
+ response: OpenAPIV3.ResponseObject,
49
+ allResponses: OpenAPIV3.ResponsesObject
50
+ ): boolean;
51
+ }
52
+ >;
53
+
54
+ export interface CommonOptions {
55
+ /**
56
+ * local schema file path (only supports local files)
57
+ */
58
+ schemaFile: string;
59
+ /**
60
+ * remote schema file URL (when provided, will download to schemaFile path)
61
+ */
62
+ remoteFile?: string;
63
+ /**
64
+ * Configuration for WebApiConfiguration import
65
+ * defaults to { file: "@core/api/web-api-configuration", importName: "WebApiConfiguration" }
66
+ */
67
+ apiConfiguration?: {
68
+ file: string;
69
+ importName: string;
70
+ };
71
+ /**
72
+ * HttpClient for WebApiConfiguration import
73
+ * defaults to { file: "@core/httpClient/webapi/webapi-http-client.providers", importName: "WEBAPI_HTTP_CLIENT" }
74
+ */
75
+ httpClient?: {
76
+ file: string;
77
+ importName: string;
78
+ };
79
+ /**
80
+ * defaults to "enhancedApi"
81
+ */
82
+ exportName?: string;
83
+ /**
84
+ * defaults to "Req"
85
+ */
86
+ argSuffix?: string;
87
+ /**
88
+ * defaults to "Res"
89
+ */
90
+ responseSuffix?: string;
91
+ /**
92
+ * defaults to empty
93
+ */
94
+ operationNameSuffix?: string;
95
+ /**
96
+ * defaults to `false`
97
+ * `true` will generate hooks for queries and mutations, but no lazyQueries
98
+ */
99
+ hooks?: boolean | { queries: boolean; lazyQueries: boolean; mutations: boolean };
100
+ /**
101
+ * defaults to false
102
+ * `true` will generate a union type for `undefined` properties like: `{ id?: string | undefined }` instead of `{ id?: string }`
103
+ */
104
+ unionUndefined?: boolean;
105
+ /**
106
+ * defaults to false
107
+ * `true` will result in all generated endpoints having `providesTags`/`invalidatesTags` declarations for the `tags` of their respective operation definition
108
+ * @see https://redux-toolkit.js.org/rtk-query/usage/code-generation for more information
109
+ */
110
+ tag?: boolean;
111
+ /**
112
+ * defaults to false
113
+ * `true` will add `encodeURIComponent` to the generated path parameters
114
+ */
115
+ encodePathParams?: boolean;
116
+ /**
117
+ * defaults to false
118
+ * `true` will add `encodeURIComponent` to the generated query parameters
119
+ */
120
+ encodeQueryParams?: boolean;
121
+ /**
122
+ * defaults to false
123
+ * `true` will "flatten" the arg so that you can do things like `useGetEntityById(1)` instead of `useGetEntityById({ entityId: 1 })`
124
+ */
125
+ flattenArg?: boolean;
126
+ /**
127
+ * default to false
128
+ * If set to `true`, the default response type will be included in the generated code for all endpoints.
129
+ * @see https://swagger.io/docs/specification/describing-responses/#default
130
+ */
131
+ includeDefault?: boolean;
132
+ /**
133
+ * default to false
134
+ * `true` will not generate separate types for read-only and write-only properties.
135
+ */
136
+ mergeReadWriteOnly?: boolean;
137
+ /**
138
+ *
139
+ * HTTPResolverOptions object that is passed to the SwaggerParser bundle function.
140
+ */
141
+ httpResolverOptions?: SwaggerParser.HTTPResolverOptions;
142
+
143
+ /**
144
+ * defaults to undefined
145
+ * If present the given file will be used as prettier config when formatting the generated code. If undefined the default prettier config
146
+ * resolution mechanism will be used.
147
+ */
148
+ prettierConfigFile?: string;
149
+ /**
150
+ * defaults to "@acrool/react-fetcher"
151
+ * File path for importing IRestFulEndpointsQueryReturn type
152
+ */
153
+ endpointsQueryReturnTypeFile?: string;
154
+ /**
155
+ * defaults to 60 (seconds)
156
+ * Number of seconds to wait before refetching data when component mounts or args change
157
+ */
158
+ refetchOnMountOrArgChange?: number;
159
+ }
160
+
161
+ export type TextMatcher = string | RegExp | (string | RegExp)[];
162
+
163
+ export type EndpointMatcherFunction = (operationName: string, operationDefinition: OperationDefinition) => boolean;
164
+
165
+ export type EndpointMatcher = TextMatcher | EndpointMatcherFunction;
166
+
167
+ export type ParameterMatcherFunction = (parameterName: string, parameterDefinition: ParameterDefinition) => boolean;
168
+
169
+ export type ParameterMatcher = TextMatcher | ParameterMatcherFunction;
170
+
171
+ export interface OutputFileOptions extends Partial<CommonOptions> {
172
+ outputFile: string;
173
+ filterEndpoints?: EndpointMatcher;
174
+ endpointOverrides?: EndpointOverrides[];
175
+ queryMatch?: (method: string, path: string) => boolean;
176
+ /**
177
+ * defaults to false
178
+ * If passed as true it will generate TS enums instead of union of strings
179
+ */
180
+ useEnumType?: boolean;
181
+ sharedTypesFile?: string;
182
+ /**
183
+ * groupKey for service class naming, e.g., "room" -> "RoomService"
184
+ */
185
+ groupKey?: string;
186
+ }
187
+
188
+ export type EndpointOverrides = {
189
+ pattern: EndpointMatcher;
190
+ } & AtLeastOneKey<{
191
+ type: 'mutation' | 'query';
192
+ parameterFilter: ParameterMatcher;
193
+ }>;
194
+
195
+ export type OutputFilesConfig = {
196
+ groupKeyMatch: (path: string) => string;
197
+ outputDir: string;
198
+ queryMatch?: (method: string, path: string) => boolean;
199
+ filterEndpoint?: (operationName: string, path: string, groupKey: string) => boolean;
200
+ };
201
+
202
+ export type ConfigFile =
203
+ | Id<Require<CommonOptions & OutputFileOptions, 'outputFile'>>
204
+ | Id<
205
+ Omit<CommonOptions, 'outputFile'> & {
206
+ // outputFiles: { [outputFile: string]: Omit<OutputFileOptions, 'outputFile'> };
207
+ outputFiles: OutputFilesConfig
208
+ }
209
+ >;
210
+
211
+ export type GenerateApiResult = {
212
+ operationNames: string[];
213
+ files: {
214
+ types: string;
215
+ apiService: string;
216
+ queryService: string;
217
+ index: string;
218
+ commonTypes?: string;
219
+ cacheKeys?: string;
220
+ componentSchema?: string;
221
+ allEndpointCacheKeys?: Array<{
222
+ operationName: string
223
+ queryKeyName: string
224
+ groupKey: string
225
+ }>;
226
+ };
227
+ };
228
+
229
+
230
+
231
+ export type QueryArgDefinition = {
232
+ name: string;
233
+ originalName: string;
234
+ type: ts.TypeNode;
235
+ required?: boolean;
236
+ param?: OpenAPIV3.ParameterObject;
237
+ } & (
238
+ | {
239
+ origin: 'param';
240
+ param: OpenAPIV3.ParameterObject;
241
+ }
242
+ | {
243
+ origin: 'body';
244
+ body: OpenAPIV3.RequestBodyObject;
245
+ }
246
+ );
247
+
248
+ export type QueryArgDefinitions = Record<string, QueryArgDefinition>;
@@ -0,0 +1,3 @@
1
+ export function capitalize(str: string) {
2
+ return str.replace(str[0], str[0].toUpperCase());
3
+ }
@@ -0,0 +1,75 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+
4
+ /**
5
+ * 確保目錄存在的函數
6
+ * @param filePath
7
+ */
8
+ export async function ensureDirectoryExists(filePath: string) {
9
+ const dirname = path.dirname(filePath);
10
+ if (!fs.existsSync(dirname)) {
11
+ await fs.promises.mkdir(dirname, { recursive: true });
12
+ }
13
+ }
14
+
15
+ /**
16
+ * 檢查檔案是否存在的函數
17
+ * @param filePath
18
+ */
19
+ export function fileExists(filePath: string): boolean {
20
+ try {
21
+ return fs.statSync(filePath).isFile();
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * 獲取資料夾名稱並轉換為 API 名稱
29
+ * @param dirPath
30
+ */
31
+ export function getApiNameFromDir(dirPath: string): string {
32
+ const dirName = path.basename(dirPath);
33
+ return `${dirName}Api`;
34
+ }
35
+
36
+
37
+
38
+
39
+ /**
40
+ * 確保基礎文件存在的函數
41
+ * @param outputDir
42
+ * @param operationNames
43
+ */
44
+ export async function ensureBaseFilesExist(outputDir: string, operationNames: string[]) {
45
+ const enhanceEndpointsPath = path.join(outputDir, 'enhanceEndpoints.ts');
46
+ const indexPath = path.join(outputDir, 'index.ts');
47
+
48
+ // 如果 enhanceEndpoints.ts 不存在,創建它
49
+ if (!fileExists(enhanceEndpointsPath)) {
50
+ // 生成操作名稱的字符串
51
+ const operationNamesString = operationNames
52
+ .map(name => ` ${name}: {},`)
53
+ .join('\n');
54
+
55
+ const enhanceEndpointsContent = `import api from './query.service';
56
+
57
+ const enhancedApi = api.enhanceEndpoints({
58
+ endpoints: {
59
+ ${operationNamesString}
60
+ },
61
+ });
62
+
63
+ export default enhancedApi;
64
+ `;
65
+ await fs.promises.writeFile(enhanceEndpointsPath, enhanceEndpointsContent, 'utf-8');
66
+ }
67
+ // 如果文件已存在,不做任何修改
68
+
69
+ // 如果 index.ts 不存在,創建它
70
+ if (!fileExists(indexPath)) {
71
+ const indexContent = `export * from './query.service';
72
+ `;
73
+ await fs.promises.writeFile(indexPath, indexContent, 'utf-8');
74
+ }
75
+ }
@@ -0,0 +1,33 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { isValidUrl } from './isValidUrl';
4
+
5
+ export async function downloadSchemaFile(remoteFile: string, targetPath: string): Promise<string> {
6
+ // 如果不是網址,拋出錯誤
7
+ if (!isValidUrl(remoteFile)) {
8
+ throw new Error(`remoteFile must be a valid URL: ${remoteFile}`);
9
+ }
10
+
11
+ try {
12
+ // 確保目錄存在
13
+ const dir = path.dirname(targetPath);
14
+ if (!fs.existsSync(dir)) {
15
+ await fs.promises.mkdir(dir, { recursive: true });
16
+ }
17
+
18
+ // 下載檔案
19
+ const response = await fetch(remoteFile);
20
+ if (!response.ok) {
21
+ throw new Error(`Failed to download schema from ${remoteFile}: ${response.statusText}`);
22
+ }
23
+
24
+ const content = await response.text();
25
+ await fs.promises.writeFile(targetPath, content, 'utf-8');
26
+
27
+ console.log(`Schema downloaded from ${remoteFile} to ${targetPath}`);
28
+ return targetPath;
29
+ } catch (error) {
30
+ console.error(`Error downloading schema from ${remoteFile}:`, error);
31
+ throw error;
32
+ }
33
+ }