@itzworking/devkit 0.0.198-canary.20250707-e7f7621
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 +15 -0
- package/dist-cjs/generate-api-clients/api-type-spec-utils.js +160 -0
- package/dist-cjs/generate-api-clients/copy-api-client.js +17 -0
- package/dist-cjs/generate-api-clients/generate-api-client-class.js +161 -0
- package/dist-cjs/generate-api-clients/generate-api-clients.js +70 -0
- package/dist-cjs/generate-api-clients/generate-hook.js +71 -0
- package/dist-cjs/generate-api-clients/handlebars-helpers.js +14 -0
- package/dist-cjs/generate-api-clients/index.js +4 -0
- package/dist-cjs/generate-api-clients/parse-discovered-apis.js +125 -0
- package/dist-cjs/generate-api-clients/sync-api-clients.js +84 -0
- package/dist-cjs/generate-api-clients/template-loader.js +24 -0
- package/dist-cjs/generate-api-clients/templates/api-client.hbs +25 -0
- package/dist-cjs/generate-api-clients/templates/api-client.ts.template +131 -0
- package/dist-cjs/generate-api-clients/templates/hook-mutation.hbs +39 -0
- package/dist-cjs/generate-api-clients/templates/hook-query.hbs +28 -0
- package/dist-cjs/generate-api-clients/types.js +2 -0
- package/dist-cjs/generate-api-clients/utils.js +89 -0
- package/dist-cjs/index.js +4 -0
- package/dist-es/generate-api-clients/api-type-spec-utils.js +155 -0
- package/dist-es/generate-api-clients/copy-api-client.js +12 -0
- package/dist-es/generate-api-clients/generate-api-client-class.js +156 -0
- package/dist-es/generate-api-clients/generate-api-clients.js +65 -0
- package/dist-es/generate-api-clients/generate-hook.js +66 -0
- package/dist-es/generate-api-clients/handlebars-helpers.js +9 -0
- package/dist-es/generate-api-clients/index.js +1 -0
- package/dist-es/generate-api-clients/parse-discovered-apis.js +120 -0
- package/dist-es/generate-api-clients/sync-api-clients.js +79 -0
- package/dist-es/generate-api-clients/template-loader.js +20 -0
- package/dist-es/generate-api-clients/templates/api-client.hbs +25 -0
- package/dist-es/generate-api-clients/templates/api-client.ts.template +131 -0
- package/dist-es/generate-api-clients/templates/hook-mutation.hbs +39 -0
- package/dist-es/generate-api-clients/templates/hook-query.hbs +28 -0
- package/dist-es/generate-api-clients/types.js +1 -0
- package/dist-es/generate-api-clients/utils.js +79 -0
- package/dist-es/index.js +1 -0
- package/dist-types/generate-api-clients/api-type-spec-utils.d.ts +4 -0
- package/dist-types/generate-api-clients/copy-api-client.d.ts +5 -0
- package/dist-types/generate-api-clients/generate-api-client-class.d.ts +11 -0
- package/dist-types/generate-api-clients/generate-api-clients.d.ts +2 -0
- package/dist-types/generate-api-clients/generate-hook.d.ts +11 -0
- package/dist-types/generate-api-clients/handlebars-helpers.d.ts +1 -0
- package/dist-types/generate-api-clients/index.d.ts +1 -0
- package/dist-types/generate-api-clients/parse-discovered-apis.d.ts +8 -0
- package/dist-types/generate-api-clients/sync-api-clients.d.ts +1 -0
- package/dist-types/generate-api-clients/template-loader.d.ts +5 -0
- package/dist-types/generate-api-clients/types.d.ts +113 -0
- package/dist-types/generate-api-clients/utils.d.ts +21 -0
- package/dist-types/index.d.ts +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.syncApiClients = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
const syncApiClients = (webappDataPath) => {
|
|
8
|
+
const sourceDir = path.join(__dirname, "out", "apis");
|
|
9
|
+
const targetDir = path.join(webappDataPath, "apis");
|
|
10
|
+
if (!fs.existsSync(sourceDir)) {
|
|
11
|
+
throw new Error(`Source directory not found: ${sourceDir}`);
|
|
12
|
+
}
|
|
13
|
+
if (!fs.existsSync(targetDir)) {
|
|
14
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
const getHooks = (dir) => {
|
|
17
|
+
const hooks = [];
|
|
18
|
+
const services = fs
|
|
19
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
20
|
+
.filter((dirent) => dirent.isDirectory() && dirent.name !== "node_modules");
|
|
21
|
+
for (const service of services) {
|
|
22
|
+
const hooksPath = path.join(dir, service.name, "hooks");
|
|
23
|
+
if (fs.existsSync(hooksPath)) {
|
|
24
|
+
const hookFiles = fs
|
|
25
|
+
.readdirSync(hooksPath)
|
|
26
|
+
.filter((file) => file.endsWith(".ts") && file !== "index.ts");
|
|
27
|
+
for (const hookFile of hookFiles) {
|
|
28
|
+
hooks.push({
|
|
29
|
+
service: service.name,
|
|
30
|
+
name: hookFile.replace(".ts", ""),
|
|
31
|
+
fullPath: path.join(hooksPath, hookFile),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return hooks;
|
|
37
|
+
};
|
|
38
|
+
const existingHooks = getHooks(targetDir);
|
|
39
|
+
const newHooks = getHooks(sourceDir);
|
|
40
|
+
const addedHooks = newHooks.filter((newHook) => !existingHooks.some((existingHook) => existingHook.service === newHook.service &&
|
|
41
|
+
existingHook.name === newHook.name));
|
|
42
|
+
const removedHooks = existingHooks.filter((existingHook) => !newHooks.some((newHook) => newHook.service === existingHook.service &&
|
|
43
|
+
newHook.name === existingHook.name));
|
|
44
|
+
console.log("Hook changes:");
|
|
45
|
+
if (addedHooks.length === 0 && removedHooks.length === 0) {
|
|
46
|
+
console.log("No changes detected.");
|
|
47
|
+
}
|
|
48
|
+
addedHooks.forEach((hook) => console.log(`+ ${hook.service}/${hook.name}`));
|
|
49
|
+
removedHooks.forEach((hook) => console.log(`- ${hook.service}/${hook.name}`));
|
|
50
|
+
fs.copyFileSync(path.join(sourceDir, "api-client.ts"), path.join(targetDir, "api-client.ts"));
|
|
51
|
+
const services = fs
|
|
52
|
+
.readdirSync(sourceDir, { withFileTypes: true })
|
|
53
|
+
.filter((dirent) => dirent.isDirectory());
|
|
54
|
+
const mainIndexContent = services
|
|
55
|
+
.map((service) => `export * from "./${service.name}";`)
|
|
56
|
+
.join("\n");
|
|
57
|
+
fs.writeFileSync(path.join(targetDir, "index.ts"), mainIndexContent);
|
|
58
|
+
for (const service of services) {
|
|
59
|
+
const sourceServiceDir = path.join(sourceDir, service.name);
|
|
60
|
+
const targetServiceDir = path.join(targetDir, service.name);
|
|
61
|
+
if (!fs.existsSync(targetServiceDir)) {
|
|
62
|
+
fs.mkdirSync(targetServiceDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
fs.copyFileSync(path.join(sourceServiceDir, `${service.name}-api.ts`), path.join(targetServiceDir, `${service.name}-api.ts`));
|
|
65
|
+
fs.copyFileSync(path.join(sourceServiceDir, "index.ts"), path.join(targetServiceDir, "index.ts"));
|
|
66
|
+
const targetHooksDir = path.join(targetServiceDir, "hooks");
|
|
67
|
+
if (!fs.existsSync(targetHooksDir)) {
|
|
68
|
+
fs.mkdirSync(targetHooksDir, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
const serviceHooks = [...addedHooks, ...existingHooks].filter((hook) => hook.service === service.name);
|
|
71
|
+
for (const hook of serviceHooks) {
|
|
72
|
+
const targetHookPath = path.join(targetHooksDir, `${hook.name}.ts`);
|
|
73
|
+
if (!fs.existsSync(targetHookPath)) {
|
|
74
|
+
fs.copyFileSync(hook.fullPath, targetHookPath);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const hooksIndexContent = serviceHooks
|
|
78
|
+
.map((hook) => `export * from "./${hook.name}";`)
|
|
79
|
+
.sort()
|
|
80
|
+
.join("\n") + "\n";
|
|
81
|
+
fs.writeFileSync(path.join(targetHooksDir, "index.ts"), hooksIndexContent);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
exports.syncApiClients = syncApiClients;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadTemplate = loadTemplate;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
function loadTemplate(templateName) {
|
|
8
|
+
const possiblePaths = [
|
|
9
|
+
path.join(__dirname, "templates", templateName),
|
|
10
|
+
path.join(__dirname, "templates", templateName),
|
|
11
|
+
path.join(process.cwd(), "node_modules", "@itzworking", "devkit", "dist-cjs", "generate-api-clients", "templates", templateName),
|
|
12
|
+
path.join(process.cwd(), "node_modules", "@itzworking", "devkit", "dist-es", "generate-api-clients", "templates", templateName),
|
|
13
|
+
];
|
|
14
|
+
for (const templatePath of possiblePaths) {
|
|
15
|
+
try {
|
|
16
|
+
if (fs.existsSync(templatePath)) {
|
|
17
|
+
return fs.readFileSync(templatePath, "utf8");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
throw new Error(`Template not found: ${templateName}. Tried paths: ${possiblePaths.join(", ")}`);
|
|
24
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// prettier-ignore
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
import { ApiClientClass } from "../api-client";
|
|
5
|
+
{{#each imports}}
|
|
6
|
+
import { {{names}} } from "{{path}}";
|
|
7
|
+
{{/each}}
|
|
8
|
+
|
|
9
|
+
class {{className}}Class extends ApiClientClass {
|
|
10
|
+
{{#each methods}}
|
|
11
|
+
async {{name}}({{params}}): Promise<{{returnType}}> {
|
|
12
|
+
{{#if outputType}}
|
|
13
|
+
const responseBody = await this.{{method}}({ path: {{{pathWithQuotes fullPath}}}{{#if inputType}}, options: { body }{{/if}} });
|
|
14
|
+
return new {{outputType.typeName}}(responseBody);
|
|
15
|
+
{{else}}
|
|
16
|
+
await this.{{method}}({ path: {{{pathWithQuotes fullPath}}}{{#if inputType}}, options: { body }{{/if}} });
|
|
17
|
+
{{/if}}
|
|
18
|
+
}
|
|
19
|
+
{{#unless @last}}
|
|
20
|
+
|
|
21
|
+
{{/unless}}
|
|
22
|
+
{{/each}}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const {{className}} = new {{className}}Class();
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
del,
|
|
3
|
+
get,
|
|
4
|
+
head,
|
|
5
|
+
isCancelError,
|
|
6
|
+
patch,
|
|
7
|
+
post,
|
|
8
|
+
put,
|
|
9
|
+
} from "aws-amplify/api";
|
|
10
|
+
import type {
|
|
11
|
+
ApiInput as AmplifyApiInput,
|
|
12
|
+
RestApiOptionsBase as AmplifyRestApiOptionsBase,
|
|
13
|
+
RestApiResponse as AmplifyRestApiResponse,
|
|
14
|
+
} from "@aws-amplify/api-rest/src/types";
|
|
15
|
+
import { fetchAuthSession } from "aws-amplify/auth";
|
|
16
|
+
|
|
17
|
+
type ApiClientInputOption = {
|
|
18
|
+
headers?: Record<string, string>;
|
|
19
|
+
queryParams?: Record<string, string>;
|
|
20
|
+
body?: any;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type ApiClientInput = {
|
|
24
|
+
path: string;
|
|
25
|
+
options?: Omit<ApiClientInputOption, "body">;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type ApiClientInputWithBody = {
|
|
29
|
+
path: string;
|
|
30
|
+
options?: ApiClientInputOption;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type GetInput = ApiClientInputWithBody;
|
|
34
|
+
export type PostInput = ApiClientInputWithBody;
|
|
35
|
+
export type PutInput = ApiClientInputWithBody;
|
|
36
|
+
export type PatchInput = ApiClientInputWithBody;
|
|
37
|
+
export type DeleteInput = ApiClientInput;
|
|
38
|
+
export type HeadInput = ApiClientInput;
|
|
39
|
+
|
|
40
|
+
export class ApiClientClass {
|
|
41
|
+
private readonly apiName = "default";
|
|
42
|
+
|
|
43
|
+
private async formatInput(
|
|
44
|
+
input: ApiClientInput
|
|
45
|
+
): Promise<AmplifyApiInput<AmplifyRestApiOptionsBase>> {
|
|
46
|
+
const { idToken, accessToken } = (await fetchAuthSession()).tokens ?? {};
|
|
47
|
+
return {
|
|
48
|
+
...input,
|
|
49
|
+
apiName: this.apiName,
|
|
50
|
+
options: {
|
|
51
|
+
...input.options,
|
|
52
|
+
headers: {
|
|
53
|
+
...input.options?.headers,
|
|
54
|
+
Authorization: `Bearer ${idToken}`,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private async formatResponseBody(
|
|
61
|
+
response:
|
|
62
|
+
| Promise<AmplifyRestApiResponse>
|
|
63
|
+
| Promise<Omit<AmplifyRestApiResponse, "body">>
|
|
64
|
+
): Promise<any> {
|
|
65
|
+
try {
|
|
66
|
+
const res = await response;
|
|
67
|
+
// Handle 204 No Content or empty body
|
|
68
|
+
if (res.statusCode === 204 || !("body" in res) || !res.body) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
// Try to parse JSON, but handle empty string
|
|
72
|
+
const text = await res.body.text();
|
|
73
|
+
if (!text) return undefined;
|
|
74
|
+
try {
|
|
75
|
+
const json = JSON.parse(text);
|
|
76
|
+
return json;
|
|
77
|
+
} catch (e) {
|
|
78
|
+
throw new Error("Response must be a JSON object or array");
|
|
79
|
+
}
|
|
80
|
+
} catch (e: any) {
|
|
81
|
+
if (e?._response?.body) {
|
|
82
|
+
let parsed: any;
|
|
83
|
+
try {
|
|
84
|
+
parsed = JSON.parse(e._response.body);
|
|
85
|
+
} catch (parseError) {
|
|
86
|
+
// If JSON parsing fails, continue to throw original error
|
|
87
|
+
}
|
|
88
|
+
if (parsed) {
|
|
89
|
+
throw parsed;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
throw e;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async get(input: GetInput) {
|
|
97
|
+
const formattedInput = await this.formatInput(input);
|
|
98
|
+
return this.formatResponseBody(get(formattedInput).response);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async put(input: PutInput) {
|
|
102
|
+
const formattedInput = await this.formatInput(input);
|
|
103
|
+
return this.formatResponseBody(put(formattedInput).response);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async post(input: PostInput) {
|
|
107
|
+
const formattedInput = await this.formatInput(input);
|
|
108
|
+
return this.formatResponseBody(post(formattedInput).response);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async delete(input: DeleteInput) {
|
|
112
|
+
const formattedInput = await this.formatInput(input);
|
|
113
|
+
return this.formatResponseBody(del(formattedInput).response);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async head(input: HeadInput) {
|
|
117
|
+
const formattedInput = await this.formatInput(input);
|
|
118
|
+
return this.formatResponseBody(head(formattedInput).response);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async patch(input: PatchInput) {
|
|
122
|
+
const formattedInput = await this.formatInput(input);
|
|
123
|
+
return this.formatResponseBody(patch(formattedInput).response);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
isCancelError(error: unknown) {
|
|
127
|
+
return isCancelError(error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const ApiClient = new ApiClientClass();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// prettier-ignore
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
5
|
+
{{#each imports}}
|
|
6
|
+
import { {{names}} } from "{{path}}";
|
|
7
|
+
{{/each}}
|
|
8
|
+
|
|
9
|
+
export const {{hookName}} = () => {
|
|
10
|
+
const queryClient = useQueryClient();
|
|
11
|
+
const useMutationResult = useMutation({
|
|
12
|
+
mutationFn: ({{mutationParams}}){{#if outputType}}: Promise<{{outputType.typeName}}>{{/if}} => {{apiClassName}}.{{actionName}}({{#if mutationParams}}variables{{/if}}),
|
|
13
|
+
onMutate: async (variables) => {
|
|
14
|
+
// A mutation is about to happen!
|
|
15
|
+
},
|
|
16
|
+
onError: async (err, variables, context) => {
|
|
17
|
+
// An error happened!
|
|
18
|
+
},
|
|
19
|
+
onSuccess: async (data, variables, context) => {
|
|
20
|
+
// Boom baby!
|
|
21
|
+
},
|
|
22
|
+
onSettled: async () => {
|
|
23
|
+
// Error or success... doesn't matter!
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
{{actionName}}: useMutationResult.mutate,
|
|
29
|
+
{{actionName}}Async: useMutationResult.mutateAsync,
|
|
30
|
+
is{{apiClassName}}Error: useMutationResult.isError,
|
|
31
|
+
is{{apiClassName}}Idle: useMutationResult.isIdle,
|
|
32
|
+
is{{apiClassName}}Paused: useMutationResult.isPaused,
|
|
33
|
+
is{{apiClassName}}Pending: useMutationResult.isPending,
|
|
34
|
+
is{{apiClassName}}Success: useMutationResult.isSuccess,
|
|
35
|
+
{{actionName}}Status: useMutationResult.status,
|
|
36
|
+
{{actionName}}Error: useMutationResult.error,
|
|
37
|
+
reset{{apiClassName}}: useMutationResult.reset
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// prettier-ignore
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
import { useQuery } from "@tanstack/react-query";
|
|
5
|
+
{{#each imports}}
|
|
6
|
+
import { {{names}} } from "{{path}}";
|
|
7
|
+
{{/each}}
|
|
8
|
+
|
|
9
|
+
export const {{relatedDataName}}QueryKey = ({{queryKeyParams}}) => [
|
|
10
|
+
{{{queryKeyArray}}}
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export const {{hookName}} = ({{queryKeyParams}}) => {
|
|
14
|
+
const useQueryResult = useQuery({
|
|
15
|
+
queryKey: {{relatedDataName}}QueryKey({{queryKeyInput}}),
|
|
16
|
+
queryFn: (){{#if outputType}}: Promise<{{outputType.typeName}}>{{/if}} => {{apiClassName}}.{{actionName}}({{queryKeyInput}})
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
{{relatedDataName}}: useQueryResult.data,
|
|
21
|
+
{{relatedDataName}}UpdatedAt: useQueryResult.dataUpdatedAt,
|
|
22
|
+
{{actionName}}Error: useQueryResult.error,
|
|
23
|
+
is{{actionClassName}}Loading: useQueryResult.isLoading,
|
|
24
|
+
is{{actionClassName}}Fetching: useQueryResult.isFetching,
|
|
25
|
+
refetch{{actionClassName}}: useQueryResult.refetch,
|
|
26
|
+
{{actionName}}Status: useQueryResult.status
|
|
27
|
+
};
|
|
28
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeFile = exports.toFileName = exports.toClassName = exports.toPropertyName = exports.prepareImports = exports.getITzWorkingResources = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
const getITzWorkingResources = ({ resourcesFilePath, }) => {
|
|
8
|
+
if (!fs.existsSync(resourcesFilePath)) {
|
|
9
|
+
console.error(`${resourcesFilePath} file not found. Please deploy your application.`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const data = fs.readFileSync(resourcesFilePath, "utf-8");
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(data);
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
console.error(`Error parsing ${resourcesFilePath} file. Please ensure it is a valid JSON file.`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
exports.getITzWorkingResources = getITzWorkingResources;
|
|
22
|
+
const prepareImports = (apis, options) => {
|
|
23
|
+
const formatedApis = Array.isArray(apis) ? apis : [apis];
|
|
24
|
+
const imports = [];
|
|
25
|
+
const importMap = {};
|
|
26
|
+
if (options.typeResolutionMode === "source-imports") {
|
|
27
|
+
for (const api of formatedApis) {
|
|
28
|
+
for (const [importPath, importNames] of Object.entries(api.imports)) {
|
|
29
|
+
if (!importMap[importPath])
|
|
30
|
+
importMap[importPath] = new Set();
|
|
31
|
+
importNames.forEach((name) => importMap[importPath].add(name));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (options.typeResolutionMode === "inline-types") {
|
|
36
|
+
for (const api of formatedApis) {
|
|
37
|
+
const typeSpecs = [api.inputType, api.outputType];
|
|
38
|
+
for (const typeSpec of typeSpecs) {
|
|
39
|
+
if (!typeSpec || !typeSpec.importPath)
|
|
40
|
+
continue;
|
|
41
|
+
if (!importMap["./types"])
|
|
42
|
+
importMap["./types"] = new Set();
|
|
43
|
+
importMap["./types"].add(typeSpec.typeName);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
throw new Error(`Unsupported typeResolutionMode: ${options.typeResolutionMode}.`);
|
|
49
|
+
}
|
|
50
|
+
for (const importPath of Object.keys(importMap).sort()) {
|
|
51
|
+
imports.push({
|
|
52
|
+
path: importPath,
|
|
53
|
+
names: Array.from(importMap[importPath]).sort().join(", "),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return imports.sort((a, b) => b.path.localeCompare(a.path));
|
|
57
|
+
};
|
|
58
|
+
exports.prepareImports = prepareImports;
|
|
59
|
+
const toPropertyName = (s) => {
|
|
60
|
+
return s
|
|
61
|
+
.replace(/([^a-zA-Z0-9])+(.)?/g, (_, __, chr) => chr ? chr.toUpperCase() : "")
|
|
62
|
+
.replace(/[^a-zA-Z\d]/g, "")
|
|
63
|
+
.replace(/^([A-Z])/, (m) => m.toLowerCase());
|
|
64
|
+
};
|
|
65
|
+
exports.toPropertyName = toPropertyName;
|
|
66
|
+
const toClassName = (s) => {
|
|
67
|
+
const propertyName = (0, exports.toPropertyName)(s);
|
|
68
|
+
return propertyName.charAt(0).toUpperCase() + propertyName.slice(1);
|
|
69
|
+
};
|
|
70
|
+
exports.toClassName = toClassName;
|
|
71
|
+
const toFileName = (s) => {
|
|
72
|
+
return s
|
|
73
|
+
.replace(/([a-z\d])([A-Z])/g, "$1_$2")
|
|
74
|
+
.toLowerCase()
|
|
75
|
+
.replace(/(?!^_)[ _]/g, "-");
|
|
76
|
+
};
|
|
77
|
+
exports.toFileName = toFileName;
|
|
78
|
+
const writeFile = (params) => {
|
|
79
|
+
const { filePath, content, allowOverwrite } = params;
|
|
80
|
+
const dir = path.dirname(filePath);
|
|
81
|
+
if (!fs.existsSync(dir)) {
|
|
82
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
if (fs.existsSync(filePath) && !allowOverwrite) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
fs.writeFileSync(filePath, content);
|
|
88
|
+
};
|
|
89
|
+
exports.writeFile = writeFile;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { SyntaxKind, } from "ts-morph";
|
|
2
|
+
function isDecoratedClass(classDecl) {
|
|
3
|
+
let current = classDecl;
|
|
4
|
+
while (current) {
|
|
5
|
+
const base = current.getBaseClass();
|
|
6
|
+
if (!base)
|
|
7
|
+
break;
|
|
8
|
+
if (base.getName() === "DecoratedClass") {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
current = base;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
function isZodSchema(varDecl) {
|
|
16
|
+
if (!varDecl)
|
|
17
|
+
return false;
|
|
18
|
+
const initializer = varDecl.getInitializer();
|
|
19
|
+
if (!initializer)
|
|
20
|
+
return false;
|
|
21
|
+
if (initializer.getKind() === SyntaxKind.CallExpression) {
|
|
22
|
+
const callExpr = initializer.asKind(SyntaxKind.CallExpression);
|
|
23
|
+
if (callExpr) {
|
|
24
|
+
const expr = callExpr.getExpression();
|
|
25
|
+
if (expr.getKind() === SyntaxKind.PropertyAccessExpression ||
|
|
26
|
+
expr.getKind() === SyntaxKind.Identifier) {
|
|
27
|
+
const exprText = expr.getText();
|
|
28
|
+
if (exprText.startsWith("z.")) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
export const setTypeKind = (type, sourceFile) => {
|
|
37
|
+
if (!type)
|
|
38
|
+
return;
|
|
39
|
+
let kind = type.kind;
|
|
40
|
+
const importSourceFile = type.importPath
|
|
41
|
+
? sourceFile
|
|
42
|
+
.getImportDeclarations()
|
|
43
|
+
.find((d) => d.getModuleSpecifierValue() === type.importPath)
|
|
44
|
+
?.getModuleSpecifierSourceFile()
|
|
45
|
+
: sourceFile;
|
|
46
|
+
const inputClass = importSourceFile?.getClass(type.typeName);
|
|
47
|
+
if (isDecoratedClass(inputClass)) {
|
|
48
|
+
kind = "decorated-class";
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const inputVar = importSourceFile?.getVariableDeclaration(type.typeName);
|
|
52
|
+
if (isZodSchema(inputVar)) {
|
|
53
|
+
kind = "zod-schema";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
type.kind = kind;
|
|
57
|
+
};
|
|
58
|
+
export const extractTypeDefinition = (type, sourceFile) => {
|
|
59
|
+
if (!type?.importPath) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const importSourceFile = sourceFile
|
|
63
|
+
.getImportDeclarations()
|
|
64
|
+
.find((d) => d.getModuleSpecifierValue() === type.importPath)
|
|
65
|
+
?.getModuleSpecifierSourceFile();
|
|
66
|
+
if (!importSourceFile) {
|
|
67
|
+
throw new Error(`Could not find import source file for path: ${type.importPath}`);
|
|
68
|
+
}
|
|
69
|
+
const allowedImportLibraries = [
|
|
70
|
+
"@itzworking/decorated-class",
|
|
71
|
+
"zod",
|
|
72
|
+
"zod/v4",
|
|
73
|
+
];
|
|
74
|
+
const allowedImports = [];
|
|
75
|
+
const inlinedFiles = [];
|
|
76
|
+
const filesToProcess = [importSourceFile];
|
|
77
|
+
const processedFiles = new Set();
|
|
78
|
+
while (filesToProcess.length > 0) {
|
|
79
|
+
const currentSourceFile = filesToProcess.pop();
|
|
80
|
+
if (currentSourceFile === undefined) {
|
|
81
|
+
console.log("Source file is undefined, skipping...");
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const filePath = currentSourceFile.getFilePath();
|
|
85
|
+
if (processedFiles.has(filePath)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
processedFiles.add(filePath);
|
|
89
|
+
for (const importDeclarations of currentSourceFile.getImportDeclarations()) {
|
|
90
|
+
const moduleSpecifier = importDeclarations
|
|
91
|
+
.getModuleSpecifier()
|
|
92
|
+
.getLiteralValue();
|
|
93
|
+
if (allowedImportLibraries.includes(moduleSpecifier)) {
|
|
94
|
+
const namespaceImport = importDeclarations
|
|
95
|
+
.getNamespaceImport()
|
|
96
|
+
?.getText();
|
|
97
|
+
if (namespaceImport) {
|
|
98
|
+
const currentNamespaceImport = allowedImports.find((i) => i.namespaceImport && i.importPath === moduleSpecifier);
|
|
99
|
+
if (!currentNamespaceImport) {
|
|
100
|
+
allowedImports.push({
|
|
101
|
+
namespaceImport: true,
|
|
102
|
+
importPath: moduleSpecifier,
|
|
103
|
+
importNames: new Set([namespaceImport]),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
currentNamespaceImport.importNames.add(namespaceImport);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
let currentImport = allowedImports.find((i) => !i.namespaceImport && i.importPath === moduleSpecifier);
|
|
112
|
+
if (!currentImport) {
|
|
113
|
+
currentImport = {
|
|
114
|
+
namespaceImport: false,
|
|
115
|
+
importPath: moduleSpecifier,
|
|
116
|
+
importNames: new Set(),
|
|
117
|
+
};
|
|
118
|
+
allowedImports.push(currentImport);
|
|
119
|
+
}
|
|
120
|
+
for (const namedImport of importDeclarations.getNamedImports()) {
|
|
121
|
+
currentImport.importNames.add(namedImport.getName());
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const importedSourceFile = importDeclarations.getModuleSpecifierSourceFile();
|
|
127
|
+
const importedFilePath = importedSourceFile?.getFilePath();
|
|
128
|
+
if (!importedSourceFile || !importedFilePath) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (!processedFiles.has(importedFilePath)) {
|
|
132
|
+
filesToProcess.push(importedSourceFile);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
let text = currentSourceFile.getFullText();
|
|
137
|
+
const importDeclarations = currentSourceFile.getImportDeclarations();
|
|
138
|
+
for (const importDecl of importDeclarations.reverse()) {
|
|
139
|
+
const start = importDecl.getStart();
|
|
140
|
+
const end = importDecl.getEnd();
|
|
141
|
+
text = (text.slice(0, start) + text.slice(end)).trim();
|
|
142
|
+
}
|
|
143
|
+
inlinedFiles.push({
|
|
144
|
+
filePath: filePath,
|
|
145
|
+
text,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
type.typeDefinition = {
|
|
149
|
+
allowedImports: allowedImports.map((i) => ({
|
|
150
|
+
...i,
|
|
151
|
+
importNames: Array.from(i.importNames),
|
|
152
|
+
})),
|
|
153
|
+
inlinedFiles: inlinedFiles.reverse(),
|
|
154
|
+
};
|
|
155
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { writeFile } from "./utils";
|
|
2
|
+
import { loadTemplate } from "./template-loader";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
export const copyApiClient = (options) => {
|
|
5
|
+
const apiClientContent = loadTemplate("api-client.ts.template");
|
|
6
|
+
writeFile({
|
|
7
|
+
filePath: path.join(options.outputDirectory, "api-client.ts"),
|
|
8
|
+
content: apiClientContent,
|
|
9
|
+
allowOverwrite: options.allowOverwrite,
|
|
10
|
+
verbose: options.verbose,
|
|
11
|
+
});
|
|
12
|
+
};
|