@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.
Files changed (49) hide show
  1. package/README.md +15 -0
  2. package/dist-cjs/generate-api-clients/api-type-spec-utils.js +160 -0
  3. package/dist-cjs/generate-api-clients/copy-api-client.js +17 -0
  4. package/dist-cjs/generate-api-clients/generate-api-client-class.js +161 -0
  5. package/dist-cjs/generate-api-clients/generate-api-clients.js +70 -0
  6. package/dist-cjs/generate-api-clients/generate-hook.js +71 -0
  7. package/dist-cjs/generate-api-clients/handlebars-helpers.js +14 -0
  8. package/dist-cjs/generate-api-clients/index.js +4 -0
  9. package/dist-cjs/generate-api-clients/parse-discovered-apis.js +125 -0
  10. package/dist-cjs/generate-api-clients/sync-api-clients.js +84 -0
  11. package/dist-cjs/generate-api-clients/template-loader.js +24 -0
  12. package/dist-cjs/generate-api-clients/templates/api-client.hbs +25 -0
  13. package/dist-cjs/generate-api-clients/templates/api-client.ts.template +131 -0
  14. package/dist-cjs/generate-api-clients/templates/hook-mutation.hbs +39 -0
  15. package/dist-cjs/generate-api-clients/templates/hook-query.hbs +28 -0
  16. package/dist-cjs/generate-api-clients/types.js +2 -0
  17. package/dist-cjs/generate-api-clients/utils.js +89 -0
  18. package/dist-cjs/index.js +4 -0
  19. package/dist-es/generate-api-clients/api-type-spec-utils.js +155 -0
  20. package/dist-es/generate-api-clients/copy-api-client.js +12 -0
  21. package/dist-es/generate-api-clients/generate-api-client-class.js +156 -0
  22. package/dist-es/generate-api-clients/generate-api-clients.js +65 -0
  23. package/dist-es/generate-api-clients/generate-hook.js +66 -0
  24. package/dist-es/generate-api-clients/handlebars-helpers.js +9 -0
  25. package/dist-es/generate-api-clients/index.js +1 -0
  26. package/dist-es/generate-api-clients/parse-discovered-apis.js +120 -0
  27. package/dist-es/generate-api-clients/sync-api-clients.js +79 -0
  28. package/dist-es/generate-api-clients/template-loader.js +20 -0
  29. package/dist-es/generate-api-clients/templates/api-client.hbs +25 -0
  30. package/dist-es/generate-api-clients/templates/api-client.ts.template +131 -0
  31. package/dist-es/generate-api-clients/templates/hook-mutation.hbs +39 -0
  32. package/dist-es/generate-api-clients/templates/hook-query.hbs +28 -0
  33. package/dist-es/generate-api-clients/types.js +1 -0
  34. package/dist-es/generate-api-clients/utils.js +79 -0
  35. package/dist-es/index.js +1 -0
  36. package/dist-types/generate-api-clients/api-type-spec-utils.d.ts +4 -0
  37. package/dist-types/generate-api-clients/copy-api-client.d.ts +5 -0
  38. package/dist-types/generate-api-clients/generate-api-client-class.d.ts +11 -0
  39. package/dist-types/generate-api-clients/generate-api-clients.d.ts +2 -0
  40. package/dist-types/generate-api-clients/generate-hook.d.ts +11 -0
  41. package/dist-types/generate-api-clients/handlebars-helpers.d.ts +1 -0
  42. package/dist-types/generate-api-clients/index.d.ts +1 -0
  43. package/dist-types/generate-api-clients/parse-discovered-apis.d.ts +8 -0
  44. package/dist-types/generate-api-clients/sync-api-clients.d.ts +1 -0
  45. package/dist-types/generate-api-clients/template-loader.d.ts +5 -0
  46. package/dist-types/generate-api-clients/types.d.ts +113 -0
  47. package/dist-types/generate-api-clients/utils.d.ts +21 -0
  48. package/dist-types/index.d.ts +1 -0
  49. 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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./generate-api-clients"), exports);
@@ -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
+ };