@toktokhan-dev/cli-plugin-gen-api-react-query 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineCommand } from '@toktokhan-dev/cli';
|
|
2
|
-
import { createPackageRoot,
|
|
2
|
+
import { createPackageRoot, withLoading, cwd } from '@toktokhan-dev/node';
|
|
3
3
|
import omit from 'lodash/omit.js';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { generateApi } from 'swagger-typescript-api';
|
|
@@ -43,6 +43,8 @@ const parseSwagger = (config) => generateApi({
|
|
|
43
43
|
input: config.swaggerSchemaUrl,
|
|
44
44
|
httpClientType: config.httpClientType, // "axios" or "fetch"
|
|
45
45
|
typeSuffix: 'Type',
|
|
46
|
+
sortTypes: true,
|
|
47
|
+
sortRoutes: true,
|
|
46
48
|
prettier: {
|
|
47
49
|
printWidth: 120,
|
|
48
50
|
},
|
|
@@ -394,12 +396,12 @@ const writeSwaggerApiFile = async (params) => {
|
|
|
394
396
|
processedContent = rewriteDataContractsImport(content, filename, classificationResult);
|
|
395
397
|
}
|
|
396
398
|
const { apiContents, hookParts } = splitHookContents(filename, processedContent);
|
|
397
|
-
|
|
399
|
+
generate(path.resolve(targetFolder, `${filename}.api.ts`), apiContents);
|
|
398
400
|
if (config.includeReactQuery) {
|
|
399
|
-
|
|
401
|
+
generate(path.resolve(targetFolder, `${filename}.query.ts`), hookParts[0]);
|
|
400
402
|
}
|
|
401
403
|
if (config.includeReactSuspenseQuery) {
|
|
402
|
-
|
|
404
|
+
generate(path.resolve(targetFolder, `${filename}.suspenseQuery.ts`), hookParts[1]);
|
|
403
405
|
}
|
|
404
406
|
continue;
|
|
405
407
|
}
|
|
@@ -498,23 +500,6 @@ function extractImportedTypeNames(content) {
|
|
|
498
500
|
.map((s) => s.trim())
|
|
499
501
|
.filter(Boolean));
|
|
500
502
|
}
|
|
501
|
-
async function generatePretty(path, contents) {
|
|
502
|
-
let organized = contents;
|
|
503
|
-
try {
|
|
504
|
-
organized = await prettierString(contents, {
|
|
505
|
-
parser: 'babel-ts',
|
|
506
|
-
plugins: ['prettier-plugin-organize-imports'],
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
catch (err) {
|
|
510
|
-
console.warn('⚠️ prettier-plugin-organize-imports not found, skipping import organization');
|
|
511
|
-
}
|
|
512
|
-
const formatted = await prettierString(organized, {
|
|
513
|
-
parser: 'typescript',
|
|
514
|
-
configPath: 'auto',
|
|
515
|
-
});
|
|
516
|
-
generate(path, formatted);
|
|
517
|
-
}
|
|
518
503
|
function generate(path, contents) {
|
|
519
504
|
let existingContent = '';
|
|
520
505
|
try {
|
|
@@ -654,7 +639,9 @@ const genApi = defineCommand({
|
|
|
654
639
|
await withLoading('Prettier format', covered.output, async () => {
|
|
655
640
|
const fs = await import('fs');
|
|
656
641
|
const pathMod = await import('path');
|
|
657
|
-
const { prettierString } = await import('@toktokhan-dev/node');
|
|
642
|
+
const { prettierString, findFileToTop } = await import('@toktokhan-dev/node');
|
|
643
|
+
// output 디렉토리 기준으로 prettier config를 탐색 (cwd 의존 제거)
|
|
644
|
+
const configPath = findFileToTop(covered.output, '.prettierrc.js') || 'auto';
|
|
658
645
|
const listTsFiles = (dir) => {
|
|
659
646
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
660
647
|
const files = [];
|
|
@@ -673,9 +660,19 @@ const genApi = defineCommand({
|
|
|
673
660
|
for (const file of files) {
|
|
674
661
|
try {
|
|
675
662
|
const raw = fs.readFileSync(file, 'utf8');
|
|
676
|
-
|
|
663
|
+
let organized = raw;
|
|
664
|
+
try {
|
|
665
|
+
organized = await prettierString(raw, {
|
|
666
|
+
parser: 'babel-ts',
|
|
667
|
+
plugins: ['prettier-plugin-organize-imports'],
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
catch {
|
|
671
|
+
// prettier-plugin-organize-imports not installed, skip
|
|
672
|
+
}
|
|
673
|
+
const formatted = await prettierString(organized, {
|
|
677
674
|
parser: 'typescript',
|
|
678
|
-
configPath
|
|
675
|
+
configPath,
|
|
679
676
|
});
|
|
680
677
|
fs.writeFileSync(file, formatted);
|
|
681
678
|
}
|
|
@@ -693,8 +690,9 @@ const genApi = defineCommand({
|
|
|
693
690
|
// 스마트 타입 병합 함수
|
|
694
691
|
function mergeTypeScriptContent(existing, newContent) {
|
|
695
692
|
// 1) import 구문 보존 및 병합 (양쪽 모두에서 수집)
|
|
696
|
-
|
|
697
|
-
const
|
|
693
|
+
// 멀티라인 import도 매칭: import { \n A, \n B \n } from '...';
|
|
694
|
+
const importRegex = /^\s*import\s+[\s\S]*?from\s*['"][^'"]*['"];?/gm;
|
|
695
|
+
const sideEffectImportRegex = /^\s*import\s*['"][^'"]+['"];\s*$/gm;
|
|
698
696
|
const collectImports = (content) => {
|
|
699
697
|
const imports = new Set();
|
|
700
698
|
const matchedA = content.match(importRegex) ?? [];
|
|
@@ -710,14 +708,17 @@ function mergeTypeScriptContent(existing, newContent) {
|
|
|
710
708
|
// import 병합: 새 파일 기준 우선 순서 + 기존에만 있는 import 추가
|
|
711
709
|
const mergedImportSet = new Set(newImports);
|
|
712
710
|
existingImports.forEach((imp) => mergedImportSet.add(imp));
|
|
713
|
-
const mergedImports = Array.from(mergedImportSet);
|
|
711
|
+
const mergedImports = Array.from(mergedImportSet).sort();
|
|
714
712
|
// 2) 타입 선언 병합 (중복 제거)
|
|
715
713
|
const existingTypes = parseTypeDefinitions(existingBody);
|
|
716
714
|
const newTypes = parseTypeDefinitions(newBody);
|
|
717
715
|
// 새 타입 기준으로 병합: 새 버전이 source of truth (swagger 스키마)
|
|
718
716
|
// 기존에만 있는 타입은 보존 (사용자가 수동 추가한 것)
|
|
719
717
|
const mergedTypes = { ...existingTypes, ...newTypes };
|
|
720
|
-
const mergedTypesString = Object.
|
|
718
|
+
const mergedTypesString = Object.entries(mergedTypes)
|
|
719
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
720
|
+
.map(([, v]) => v)
|
|
721
|
+
.join('\n\n');
|
|
721
722
|
// 3) 기타 코드(타입/임포트 외)는 "새로운 내용"을 기준으로 유지
|
|
722
723
|
const removeHeaderComment = (content) => {
|
|
723
724
|
// 파일 상단의 모든 블록 코멘트와 라인 코멘트를 제거 (재귀적 처리)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toktokhan-dev/cli-plugin-gen-api-react-query",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "A CLI plugin for generating API hooks with React Query built by TOKTOKHAN.DEV",
|
|
5
5
|
"author": "TOKTOKHAN.DEV <fe-system@toktokhan.dev>",
|
|
6
6
|
"license": "ISC",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"lodash": "^4.17.21",
|
|
39
39
|
"prettier-plugin-organize-imports": "^3.2.4",
|
|
40
40
|
"swagger-typescript-api": "13.0.22",
|
|
41
|
-
"@toktokhan-dev/
|
|
42
|
-
"@toktokhan-dev/
|
|
41
|
+
"@toktokhan-dev/cli": "0.0.11",
|
|
42
|
+
"@toktokhan-dev/node": "0.0.10"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/lodash": "^4.17.0",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
return `${keyName} : "${enumNames?.[idx]}"`;
|
|
37
37
|
}).join(", ");
|
|
38
38
|
|
|
39
|
-
return `type ${contract.name} = keyof typeof ${mapName}; export const ${mapName} = {\
|
|
39
|
+
return `type ${contract.name} = keyof typeof ${mapName}; export const ${mapName} = {\n${map} \n } as const`;
|
|
40
40
|
|
|
41
41
|
},
|
|
42
42
|
interface: (contract) => {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
content = content.replace(field, `${field} | null`)
|
|
50
50
|
})
|
|
51
51
|
|
|
52
|
-
return `interface ${contract.name} {\
|
|
52
|
+
return `interface ${contract.name} {\n${content}}`;
|
|
53
53
|
},
|
|
54
54
|
type: (contract) => {
|
|
55
55
|
return `type ${contract.name} = ${contract.content}`;
|
|
@@ -83,8 +83,7 @@
|
|
|
83
83
|
<% if (description.length) { %>
|
|
84
84
|
/**
|
|
85
85
|
<%~ description.map(part => `* ${part}`).join("\n") %>
|
|
86
|
-
|
|
87
|
-
*/
|
|
86
|
+
*/
|
|
88
87
|
<% } %>
|
|
89
88
|
export <%~ (dataContractTemplates[contract.typeIdentifier] || dataContractTemplates.type)(contract) %>
|
|
90
89
|
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
return `${keyName} : "${enumNames?.[idx]}"`;
|
|
37
37
|
}).join(", ");
|
|
38
38
|
|
|
39
|
-
return `type ${contract.name} = keyof typeof ${mapName}; export const ${mapName} = {\
|
|
39
|
+
return `type ${contract.name} = keyof typeof ${mapName}; export const ${mapName} = {\n${map} \n } as const`;
|
|
40
40
|
|
|
41
41
|
},
|
|
42
42
|
interface: (contract) => {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
content = content.replace(field, `${field} | null`)
|
|
50
50
|
})
|
|
51
51
|
|
|
52
|
-
return `interface ${contract.name} {\
|
|
52
|
+
return `interface ${contract.name} {\n${content}}`;
|
|
53
53
|
},
|
|
54
54
|
type: (contract) => {
|
|
55
55
|
return `type ${contract.name} = ${contract.content}`;
|
|
@@ -85,8 +85,7 @@
|
|
|
85
85
|
<% if (description.length) { %>
|
|
86
86
|
/**
|
|
87
87
|
<%~ description.map(part => `* ${part}`).join("\n") %>
|
|
88
|
-
|
|
89
|
-
*/
|
|
88
|
+
*/
|
|
90
89
|
<% } %>
|
|
91
90
|
export <%~ (dataContractTemplates[contract.typeIdentifier] || dataContractTemplates.type)(contract) %>
|
|
92
91
|
|
|
@@ -60,7 +60,7 @@ export enum ContentType {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
export class HttpClient<SecurityDataType = unknown> {
|
|
63
|
-
public baseUrl: string = "<%~ apiConfig.baseUrl %>";
|
|
63
|
+
public baseUrl: string = "<%~ apiConfig.baseUrl.replace(/\/$/, '') %>";
|
|
64
64
|
private securityData: SecurityDataType | null = null;
|
|
65
65
|
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
|
|
66
66
|
private abortControllers = new Map<CancelToken, AbortController>();
|
|
@@ -44,7 +44,7 @@ export type InfiniteQueryHookParams<
|
|
|
44
44
|
> = {
|
|
45
45
|
options?: Partial<
|
|
46
46
|
Omit<
|
|
47
|
-
UseInfiniteQueryOptions<OriginData, Error, TData,
|
|
47
|
+
UseInfiniteQueryOptions<OriginData, Error, TData, any, TPageParam>,
|
|
48
48
|
'queryKey' | 'queryFn'
|
|
49
49
|
>
|
|
50
50
|
>;
|
|
@@ -85,7 +85,7 @@ export type SuspenseInfiniteQueryHookParams<
|
|
|
85
85
|
> = {
|
|
86
86
|
options?: Partial<
|
|
87
87
|
Omit<
|
|
88
|
-
UseSuspenseInfiniteQueryOptions<OriginData, Error, TData,
|
|
88
|
+
UseSuspenseInfiniteQueryOptions<OriginData, Error, TData, any, TPageParam>,
|
|
89
89
|
'queryKey' | 'queryFn'
|
|
90
90
|
>
|
|
91
91
|
>;
|