UNPKG

@namcaodev/postman-codegen

Version:

Auto generate all file typescript, query options, mutation options of tanstack query from postman json

468 lines (467 loc) 25.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = default_1; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const quicktype_core_1 = require("quicktype-core"); const fast_glob_1 = __importDefault(require("fast-glob")); const lodash_1 = __importDefault(require("lodash")); const network_1 = require("./utils/network"); const utils_1 = require("./utils"); console.log("Codegen made by Nam Cao 1"); const LIBRARY_ROOT = path_1.default.resolve(__dirname); const configPath = path_1.default.resolve(process.cwd(), "codegen.config.cjs"); if (!fs_1.default.existsSync(configPath)) { console.error("❌ Missing codegen.config.js file. Please create one."); process.exit(1); } let codegenConfig; if (fs_1.default.existsSync(configPath)) { if (configPath.endsWith(".cjs")) { codegenConfig = require(configPath); } else if (configPath.endsWith(".json")) { codegenConfig = JSON.parse(fs_1.default.readFileSync(configPath, "utf-8")); } else { console.error("❌ Unsupported config format. Use .js or .json."); process.exit(1); } } else { console.error("❌ Missing codegen.config file."); process.exit(1); } if (!codegenConfig) { throw new Error("❌ codegenConfig is undefined. Check config loading logic."); } try { // Validate config const validatedConfig = utils_1.CodegenConfigSchema.parse(codegenConfig); console.log("✅ Codegen Config Loaded:", validatedConfig); } catch (err) { console.error("❌ Config error:", err.errors); process.exit(1); // Exit process } // Base config Nodejs const BUFFER_ENDCODING = "utf-8"; // Generate Mode const GENERATE_TYPE = (_a = codegenConfig.generateType) !== null && _a !== void 0 ? _a : utils_1.GenerateTypeEnum.Seperate; const GENERATE_MODE = codegenConfig.generateMode; // Common Path Generate Config const POSTMAN_JSON_PATH = codegenConfig === null || codegenConfig === void 0 ? void 0 : codegenConfig.postmanJsonPath; const POSTMAN_FETCH_CONFIGS = { collectionId: (_b = codegenConfig === null || codegenConfig === void 0 ? void 0 : codegenConfig.postmanFetchConfigs) === null || _b === void 0 ? void 0 : _b.collectionId, collectionAccessKey: (_c = codegenConfig === null || codegenConfig === void 0 ? void 0 : codegenConfig.postmanFetchConfigs) === null || _c === void 0 ? void 0 : _c.collectionAccessKey, }; const GENERATE_PATH = codegenConfig.generateOutputPath; // Files Name Generate config const REQUEST_TYPE_FILE_NAME = (_e = (_d = codegenConfig === null || codegenConfig === void 0 ? void 0 : codegenConfig.generateFileNames) === null || _d === void 0 ? void 0 : _d.requestType) !== null && _e !== void 0 ? _e : "apiRequests.ts"; const QUERY_TYPE_FILE_NAME = (_g = (_f = codegenConfig === null || codegenConfig === void 0 ? void 0 : codegenConfig.generateFileNames) === null || _f === void 0 ? void 0 : _f.queryType) !== null && _g !== void 0 ? _g : "apiQueries.ts"; const RESPONSE_TYPE_FILE_NAME = (_j = (_h = codegenConfig === null || codegenConfig === void 0 ? void 0 : codegenConfig.generateFileNames) === null || _h === void 0 ? void 0 : _h.responseType) !== null && _j !== void 0 ? _j : "apiResponses.ts"; const QUERY_GENERATE_FILE_NAME = (_l = (_k = codegenConfig === null || codegenConfig === void 0 ? void 0 : codegenConfig.generateFileNames) === null || _k === void 0 ? void 0 : _k.queryOptions) !== null && _l !== void 0 ? _l : "query.ts"; const MUTATION_GENERATE_FILE_NAME = (_o = (_m = codegenConfig === null || codegenConfig === void 0 ? void 0 : codegenConfig.generateFileNames) === null || _m === void 0 ? void 0 : _m.mutationOptions) !== null && _o !== void 0 ? _o : "mutation.ts"; const COMBINE_TYPE_FILE_NAME = "types.gen.ts"; const COMBINE_QUERY_FILE_NAME = "tanstack-query.gen.ts"; // Types Configs const TYPE_CONFIGS = codegenConfig === null || codegenConfig === void 0 ? void 0 : codegenConfig.typeConfigs; // Plop Generate Config const PLOP_TEMPLATE_FOLDER_PATH = path_1.default.join(LIBRARY_ROOT, "/plop-templates"); const PLOP_TEMPLATE_QUERY_PATH = path_1.default.join(LIBRARY_ROOT, "/plop-templates/query.hbs"); const PLOP_TEMPLATE_QUERY_WITH_PARAMS_PATH = path_1.default.join(LIBRARY_ROOT, "/plop-templates/queryWithParams.hbs"); const PLOP_TEMPLATE_MUTATION_PATH = path_1.default.join(LIBRARY_ROOT, "/plop-templates/mutation.hbs"); const PLOP_TEMPLATE_COMBINE_QUERY_PATH = path_1.default.join(LIBRARY_ROOT, "/plop-templates/combineQuery.hbs"); const PLOP_ACTION_GENERATE_NAME = "generate-queries" /* CONFIG_ARGS_NAME.PLOP_ACTION */; const PLOP_DESCRIPTION_GENERATE = "Generate TanStack QueryOptions, MutationOptions, and QueryParams"; const PROPERTY_API_GET_LIST = codegenConfig.propertyApiGetList; const FETCHER_LINK = codegenConfig.fetcher; // Check Config options const IS_MATCH_PLOP_ACTION_ARG = process.argv.includes(`${"generate-queries" /* CONFIG_ARGS_NAME.PLOP_ACTION */}`); const IS_GENERATE_ZOD_FILE = (_p = codegenConfig.enableZodGeneration) !== null && _p !== void 0 ? _p : false; const generateTypeScriptType = (jsonData, typeName) => __awaiter(void 0, void 0, void 0, function* () { try { const jsonInput = (0, quicktype_core_1.jsonInputForTargetLanguage)("typescript"); const safeJsonString = (0, utils_1.safeStringify)(jsonData); yield jsonInput.addSource({ name: typeName, samples: [safeJsonString], }); const inputData = new quicktype_core_1.InputData(); inputData.addInput(jsonInput); const { lines } = yield (0, quicktype_core_1.quicktype)(Object.assign({ inputData, lang: "typescript", rendererOptions: { "just-types": "true", } }, TYPE_CONFIGS)); if (TYPE_CONFIGS === null || TYPE_CONFIGS === void 0 ? void 0 : TYPE_CONFIGS.allPropertiesOptional) { return lines .join("\n") .replace(/:(\s+)null;/gm, ":$1unknown;") .replace(/:((?!.*(null|unknown).*).*);/gm, ":$1 | null;"); } return lines.join("\n"); } catch (err) { console.error("❌ Error in generateTypeScriptType:", err); console.error("❌ Stack Trace:", err.stack); } }); const reduceDataFromPostmanData = (postmanDataObj) => { if (lodash_1.default.isObject(postmanDataObj)) { postmanDataObj[postmanDataObj["name"]] = Object.assign({}, postmanDataObj); return Object.assign({}, Object.entries(postmanDataObj).reduce((acc, [key, value]) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t; if (value.request) { acc[key] = { method: value.request.method, formdata: !lodash_1.default.isEmpty((_a = value.request.body) === null || _a === void 0 ? void 0 : _a.formdata) ? (_b = value.request.body) === null || _b === void 0 ? void 0 : _b.formdata : (_f = (_e = (_d = (_c = value.response) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.originalRequest) === null || _e === void 0 ? void 0 : _e.body) === null || _f === void 0 ? void 0 : _f.formdata, rawBodyRequest: (0, utils_1.isValidJSON)((_g = value.request.body) === null || _g === void 0 ? void 0 : _g.raw) ? (_h = value.request.body) === null || _h === void 0 ? void 0 : _h.raw : (0, utils_1.isValidJSON)((_m = (_l = (_k = (_j = value.response) === null || _j === void 0 ? void 0 : _j[0]) === null || _k === void 0 ? void 0 : _k.originalRequest) === null || _l === void 0 ? void 0 : _l.body) === null || _m === void 0 ? void 0 : _m.raw) ? (_r = (_q = (_p = (_o = value.response) === null || _o === void 0 ? void 0 : _o[0]) === null || _p === void 0 ? void 0 : _p.originalRequest) === null || _q === void 0 ? void 0 : _q.body) === null || _r === void 0 ? void 0 : _r.raw : null, queryParams: value.request.url.query || null, response: ((_t = (_s = value.response) === null || _s === void 0 ? void 0 : _s[0]) === null || _t === void 0 ? void 0 : _t.body) || null, url: value.request.url.raw, }; } return acc; }, {})); } }; const handleApiEndpoints = (postmanData) => { let endpoints = {}; if (postmanData.item) { postmanData.item.forEach((childItem) => { endpoints = Object.assign(Object.assign({}, endpoints), handleApiEndpoints(childItem)); }); } else { endpoints = Object.assign(Object.assign({}, endpoints), lodash_1.default.cloneDeep(reduceDataFromPostmanData(postmanData))); } return endpoints; }; const getPlopSeperateActions = (apiEndpoints, outputDir) => __awaiter(void 0, void 0, void 0, function* () { const actions = []; for (const [entity, apiData] of Object.entries(apiEndpoints)) { const entityTextValid = (0, utils_1.cleanSpecialCharacter)(entity, { transformSpace: true, pascalCase: true, }); const folderPath = path_1.default.join(outputDir, (0, utils_1.convertToKebabCase)(entityTextValid)); let apiDataHasItems = false; if (!fs_1.default.existsSync(folderPath)) { fs_1.default.mkdirSync(folderPath, { recursive: true }); } if (!lodash_1.default.isEmpty(apiData.formdata)) { const requestTypeContent = yield generateTypeScriptType((0, utils_1.transformFormDataToPayloadObject)(apiData.formdata), `${entityTextValid}Request`); fs_1.default.writeFileSync(path_1.default.join(folderPath, REQUEST_TYPE_FILE_NAME), (utils_1.IGNORE_CHECK_STRING + requestTypeContent), BUFFER_ENDCODING); } if (lodash_1.default.isEmpty(apiData.formdata) && !lodash_1.default.isEmpty(apiData.rawBodyRequest)) { const requestTypeContent = yield generateTypeScriptType(JSON.parse(apiData.rawBodyRequest), `${entityTextValid}Request`); fs_1.default.writeFileSync(path_1.default.join(folderPath, REQUEST_TYPE_FILE_NAME), (utils_1.IGNORE_CHECK_STRING + requestTypeContent), BUFFER_ENDCODING); } if (apiData.queryParams) { const queryParamsTypeContent = yield generateTypeScriptType((0, utils_1.transformFormDataToPayloadObject)(apiData.queryParams), `${entityTextValid}QueryParams`); fs_1.default.writeFileSync(path_1.default.join(folderPath, QUERY_TYPE_FILE_NAME), (utils_1.IGNORE_CHECK_STRING + queryParamsTypeContent), BUFFER_ENDCODING); } if (!lodash_1.default.isEmpty(apiData.response)) { const responseTypeContent = yield generateTypeScriptType(JSON.parse(apiData.response), `${entityTextValid}Response`); if (typeof responseTypeContent === "string" && responseTypeContent.includes(`${PROPERTY_API_GET_LIST}:`)) { apiDataHasItems = true; } fs_1.default.writeFileSync(path_1.default.join(folderPath, RESPONSE_TYPE_FILE_NAME), (utils_1.IGNORE_CHECK_STRING + responseTypeContent), BUFFER_ENDCODING); } if (apiData.method === "GET" || apiData.method === "DELETE") { if (apiData.queryParams) { actions.push({ type: "add", path: `${folderPath}/${QUERY_GENERATE_FILE_NAME}`, templateFile: PLOP_TEMPLATE_QUERY_WITH_PARAMS_PATH, force: true, data: { generateType: GENERATE_TYPE, name: entityTextValid, method: apiData.method, queryParamsType: `${entityTextValid}QueryParams`, responseType: !lodash_1.default.isEmpty(apiData.response) ? `${entityTextValid}Response` : null, apiPath: (0, utils_1.cleanUrl)(apiData.url), infiniteQueryName: `${entityTextValid}Infinite`, hasItems: apiDataHasItems, isGenerateZod: IS_GENERATE_ZOD_FILE, fetcher: FETCHER_LINK, }, }); } else { actions.push({ type: "add", path: `${folderPath}/${QUERY_GENERATE_FILE_NAME}`, templateFile: PLOP_TEMPLATE_QUERY_PATH, force: true, data: { generateType: GENERATE_TYPE, name: entityTextValid, method: apiData.method, responseType: !lodash_1.default.isEmpty(apiData.response) ? `${entityTextValid}Response` : null, apiPath: (0, utils_1.cleanUrl)(apiData.url), infiniteQueryName: `${entityTextValid}Infinite`, hasItems: apiDataHasItems, isGenerateZod: IS_GENERATE_ZOD_FILE, fetcher: FETCHER_LINK, }, }); } } if (apiData.method === "POST" || apiData.method === "PATCH") { actions.push({ type: "add", path: `${folderPath}/${MUTATION_GENERATE_FILE_NAME}`, templateFile: PLOP_TEMPLATE_MUTATION_PATH, force: true, data: { generateType: GENERATE_TYPE, name: entityTextValid, requestType: lodash_1.default.isEmpty(apiData.rawBodyRequest) && lodash_1.default.isEmpty(apiData.formdata) ? null : `${entityTextValid}Request`, responseType: !lodash_1.default.isEmpty(apiData.response) ? `${entityTextValid}Response` : null, apiPath: (0, utils_1.cleanUrl)(apiData.url), method: apiData.method, isGenerateZod: IS_GENERATE_ZOD_FILE, fetcher: FETCHER_LINK, }, }); } } return actions; }); const getPlopCombineActions = (apiEndpoints, outputDir) => __awaiter(void 0, void 0, void 0, function* () { const actions = []; const actionsData = []; const folderPath = path_1.default.join(outputDir); let allTypeContent = utils_1.IGNORE_CHECK_STRING; for (const [entity, apiData] of Object.entries(apiEndpoints)) { const entityTextValid = (0, utils_1.cleanSpecialCharacter)(entity, { transformSpace: true, pascalCase: true, }); let apiDataHasItems = false; if (!fs_1.default.existsSync(folderPath)) { fs_1.default.mkdirSync(folderPath, { recursive: true }); } if (!lodash_1.default.isEmpty(apiData.formdata)) { const requestTypeContent = yield generateTypeScriptType((0, utils_1.transformFormDataToPayloadObject)(apiData.formdata), `${entityTextValid}Request`); const requestTypeContentValid = (0, utils_1.fixDuplicateInterfacesBetweenStrings)(allTypeContent, requestTypeContent, entityTextValid); allTypeContent = allTypeContent.concat("\n", requestTypeContentValid); } if (lodash_1.default.isEmpty(apiData.formdata) && !lodash_1.default.isEmpty(apiData.rawBodyRequest)) { const requestTypeContent = yield generateTypeScriptType(JSON.parse(apiData.rawBodyRequest), `${entityTextValid}Request`); const requestTypeContentValid = (0, utils_1.fixDuplicateInterfacesBetweenStrings)(allTypeContent, requestTypeContent, entityTextValid); allTypeContent = allTypeContent.concat("\n", requestTypeContentValid); } if (apiData.queryParams) { const queryParamsTypeContent = yield generateTypeScriptType((0, utils_1.transformFormDataToPayloadObject)(apiData.queryParams), `${entityTextValid}QueryParams`); const queryParamsTypeContentValid = (0, utils_1.fixDuplicateInterfacesBetweenStrings)(allTypeContent, queryParamsTypeContent, entityTextValid); allTypeContent = allTypeContent.concat("\n", queryParamsTypeContentValid); } if (!lodash_1.default.isEmpty(apiData.response)) { const responseTypeContent = yield generateTypeScriptType(JSON.parse(apiData.response), `${entityTextValid}Response`); if (typeof responseTypeContent === "string" && responseTypeContent.includes(`${PROPERTY_API_GET_LIST}:`)) { apiDataHasItems = true; } const responseTypeContentValid = (0, utils_1.fixDuplicateInterfacesBetweenStrings)(allTypeContent, responseTypeContent, entityTextValid); allTypeContent = allTypeContent.concat("\n", responseTypeContentValid); } if (apiData.method === "GET" || apiData.method === "DELETE") { if (apiData.queryParams) { actionsData.push({ generateType: GENERATE_TYPE, name: entityTextValid, method: apiData.method, queryParamsType: `${entityTextValid}QueryParams`, responseType: !lodash_1.default.isEmpty(apiData.response) ? `${entityTextValid}Response` : null, apiPath: (0, utils_1.cleanUrl)(apiData.url), infiniteQueryName: `${entityTextValid}Infinite`, hasItems: apiDataHasItems, isGenerateZod: IS_GENERATE_ZOD_FILE, fetcher: FETCHER_LINK, template: "queryWithParams", }); } else { actionsData.push({ generateType: GENERATE_TYPE, name: entityTextValid, method: apiData.method, responseType: !lodash_1.default.isEmpty(apiData.response) ? `${entityTextValid}Response` : null, apiPath: (0, utils_1.cleanUrl)(apiData.url), infiniteQueryName: `${entityTextValid}Infinite`, hasItems: apiDataHasItems, isGenerateZod: IS_GENERATE_ZOD_FILE, fetcher: FETCHER_LINK, template: "query", }); } } if (apiData.method === "POST" || apiData.method === "PATCH") { actionsData.push({ generateType: GENERATE_TYPE, name: entityTextValid, requestType: lodash_1.default.isEmpty(apiData.rawBodyRequest) && lodash_1.default.isEmpty(apiData.formdata) ? null : `${entityTextValid}Request`, responseType: !lodash_1.default.isEmpty(apiData.response) ? `${entityTextValid}Response` : null, apiPath: (0, utils_1.cleanUrl)(apiData.url), method: apiData.method, isGenerateZod: IS_GENERATE_ZOD_FILE, fetcher: FETCHER_LINK, template: "mutation", }); } } // write all typescipt in a file fs_1.default.writeFileSync(path_1.default.join(folderPath, COMBINE_TYPE_FILE_NAME), (0, utils_1.replaceTypeDuplicateString)(allTypeContent), BUFFER_ENDCODING); // combine tanstack query a action actions.push({ type: "add", path: `${folderPath}/${COMBINE_QUERY_FILE_NAME}`, templateFile: PLOP_TEMPLATE_COMBINE_QUERY_PATH, force: true, data: { genList: actionsData, fetcher: FETCHER_LINK, }, }); return actions; }); const processGenerateFileZodSchema = (generateType) => __awaiter(void 0, void 0, void 0, function* () { const arrFiles = generateType === utils_1.GenerateTypeEnum.Combine ? [`${GENERATE_PATH}/${COMBINE_TYPE_FILE_NAME}`] : [ `${GENERATE_PATH}/**/${REQUEST_TYPE_FILE_NAME}`, `${GENERATE_PATH}/**/${QUERY_TYPE_FILE_NAME}`, `${GENERATE_PATH}/**/${RESPONSE_TYPE_FILE_NAME}`, ]; const files = yield (0, fast_glob_1.default)(arrFiles); if (files.length === 0) { console.log("⚠️ File do not exact"); return; } try { console.log("Start generate all zod schema files!"); yield (0, utils_1.runTsToZod)(files); console.log("🎉 Finish generate all files!"); } catch (_a) { console.error("❌ Error when run process ts-to-zod"); } }); function default_1(plop) { return __awaiter(this, void 0, void 0, function* () { if (!IS_MATCH_PLOP_ACTION_ARG) { console.error(`❌ Plop action not correct! Let's try --${"generate-queries" /* CONFIG_ARGS_NAME.PLOP_ACTION */}`); return; } const postmanJsonFile = GENERATE_MODE === utils_1.GenerateModeEnum.JsonFile ? path_1.default.join(process.cwd(), POSTMAN_JSON_PATH) : null; const outputDir = path_1.default.join(process.cwd(), GENERATE_PATH); let postmanData; if (GENERATE_MODE === utils_1.GenerateModeEnum.Fetch) { const postmanDataInfo = yield (0, network_1.fetchPostmanApiDocument)({ collectionId: POSTMAN_FETCH_CONFIGS.collectionId, collectionAccessKey: POSTMAN_FETCH_CONFIGS.collectionAccessKey, }); postmanData = postmanDataInfo === null || postmanDataInfo === void 0 ? void 0 : postmanDataInfo.collection; } else { postmanData = JSON.parse(fs_1.default.readFileSync(postmanJsonFile, BUFFER_ENDCODING)); } if (!postmanData) { console.error(`❌ Postman Data wrong, Please try again!`); return; } const apiEndpoints = handleApiEndpoints(postmanData); yield (0, utils_1.cleanGeneratedFolder)(GENERATE_PATH); const actions = GENERATE_TYPE === utils_1.GenerateTypeEnum.Combine ? yield getPlopCombineActions(apiEndpoints, outputDir) : yield getPlopSeperateActions(apiEndpoints, outputDir); if (IS_GENERATE_ZOD_FILE) { processGenerateFileZodSchema(GENERATE_TYPE); } plop.setHelper("zodPascalCase", (text) => { return (0, utils_1.cleanSpecialCharacter)(text).replace(/^([A-Z])/, (match) => match.toLowerCase()); }); plop.setHelper("eq", (a, b) => a === b); plop.setHelper("and", function (a, b) { return !!(a && b); }); plop.setHelper("or", (a, b) => a || b); plop.setHelper("isEqualOrMoreThanOne", (list, options) => { return list.length >= 1 ? options.fn(this) : options.inverse(this); }); plop.setHelper("filter", (list, key) => { if (!Array.isArray(list)) return []; return list.filter((item) => item[key]); }); plop.setHelper("join", (list, separator, key) => { if (!Array.isArray(list)) return ""; const filtered = list.map((item) => item[key]).filter(Boolean); return filtered.length > 0 ? filtered.join(separator) : ""; }); plop.setHelper("with", (value, options) => { if (Array.isArray(value) && value.length > 0) { return options.fn(value); } return ""; }); plop.setHelper("joinZod", (list, separator, key, conditionKey, conditionValue, transformSuffix) => { return list .filter((item) => item[conditionKey] === conditionValue && item[key]) .map((item) => plop.getHelper("zodPascalCase")(item[key]) + transformSuffix) .join(separator); }); const partialsDir = PLOP_TEMPLATE_FOLDER_PATH; fs_1.default.readdirSync(partialsDir).forEach((file) => { const partialName = path_1.default.basename(file, ".hbs"); const partialContent = fs_1.default.readFileSync(path_1.default.join(partialsDir, file), "utf8"); plop.setPartial(partialName, partialContent); }); plop.setGenerator(PLOP_ACTION_GENERATE_NAME, { description: PLOP_DESCRIPTION_GENERATE, prompts: [], actions: actions, }); }); }