@accelbyte/codegen 1.0.0-beta.3 → 1.0.0-beta.7

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.
@@ -74,6 +74,9 @@ class CliParser {
74
74
  static isAdmin = () => {
75
75
  return CliParser.instance().argv.admin;
76
76
  };
77
+ static getSnippetOutputPath = () => {
78
+ return CliParser.instance().argv.snippetOutput;
79
+ };
77
80
  }
78
81
 
79
82
  const getImportableVarMap = () => ({
@@ -120,7 +123,18 @@ ${body}
120
123
  \`\`\`
121
124
  `.replace(/, \)/g, ")").trim();
122
125
 
123
- const SERVICE_PREFIXES = ["/iam/", "/gdpr/", "/sessionbrowser/", "/event/", "/admin/", "/public/"];
126
+ const REMOVED_KEYWORDS = [
127
+ "/admin/",
128
+ "/public/",
129
+ "/v1/",
130
+ "/v2/",
131
+ "/v3/",
132
+ "/v4/",
133
+ "/v5/",
134
+ "/namespace/",
135
+ "/namespaces/",
136
+ "/{namespace}/"
137
+ ];
124
138
  class ParserUtils {
125
139
  static generateClassName = (tag) => {
126
140
  const className = _.upperFirst(_.camelCase(tag));
@@ -267,30 +281,34 @@ class ParserUtils {
267
281
  }
268
282
  return parameters.filter((parameter) => parameter.in === "path");
269
283
  }
270
- static mappedMethod = (httpMethod, isForm) => {
271
- if (httpMethod === "get") {
272
- return "fetch";
273
- } else if (httpMethod === "post" && isForm) {
274
- return "post";
275
- } else if (httpMethod === "post") {
276
- return "create";
277
- } else if (httpMethod === "put") {
278
- return "update";
279
- } else if (httpMethod === "patch") {
280
- return "patch";
281
- } else if (httpMethod === "delete") {
282
- return "delete";
283
- }
284
- };
285
- static generateHumanReadableMethod({ path: path2, httpMethod, isForm }) {
286
- let path_ = path2.replace();
287
- SERVICE_PREFIXES.forEach((prefix) => {
284
+ static generateNaturalLangMethod = ({ servicePrefix, path: path2, httpMethod, isForm, existingMethods }) => {
285
+ let path_ = path2;
286
+ path_ = path_.replace(`/${servicePrefix}/`, "/");
287
+ REMOVED_KEYWORDS.forEach((prefix) => {
288
288
  path_ = path_.replace(prefix, "/");
289
289
  });
290
290
  path_ = path_.substring(1);
291
+ const isPlural = httpMethod === "get" && !(path2.slice(-1) === "}");
292
+ if (!isPlural) {
293
+ path_ = replaceAll(path_, "ies/", "y/");
294
+ path_ = replaceAll(path_, "s/", "/");
295
+ if (path_.indexOf("status") < 0) {
296
+ path_ = path_.replace(/ies$/, "y");
297
+ path_ = path_.replace(/s$/, "");
298
+ }
299
+ }
291
300
  const arrLastWords = path_.split("}/");
292
301
  let lastWords = arrLastWords[arrLastWords.length - 1];
293
- lastWords = lastWords.split("/{")[0];
302
+ const extractLastWord = (lastWords_) => {
303
+ let res = lastWords_;
304
+ res = res.split("/{")[0];
305
+ return res;
306
+ };
307
+ lastWords = extractLastWord(lastWords);
308
+ if (lastWords.indexOf("{") >= 0 && arrLastWords.length > 1) {
309
+ lastWords = arrLastWords[arrLastWords.length - 2];
310
+ lastWords = extractLastWord(lastWords);
311
+ }
294
312
  const listBeforeLastWords = [];
295
313
  let foundParam = false;
296
314
  const listByParams = [];
@@ -300,7 +318,7 @@ class ParserUtils {
300
318
  foundParam = true;
301
319
  let param = item.replace("{", "");
302
320
  param = param.replace("}", "");
303
- param = "By" + _.upperFirst(param);
321
+ param = "Byword" + _.upperFirst(param);
304
322
  listByParams.push(param);
305
323
  } else if (!foundParam) {
306
324
  if (lastWords.indexOf(item) === -1) {
@@ -310,39 +328,12 @@ class ParserUtils {
310
328
  foundParam = false;
311
329
  }
312
330
  });
313
- const genPath = _.upperFirst(lastWords) + "/" + listBeforeLastWords.join("/") + "/" + listByParams.join("/");
314
- let res = _.camelCase(ParserUtils.mappedMethod(httpMethod, isForm) + genPath);
315
- res = res.replace("ByNamespace", "");
316
- return res;
317
- }
318
- static generateClassMethod({
319
- path: path2,
320
- endpoint,
321
- httpMethod,
322
- className
323
- }) {
324
- let replacedIdsPath = path2;
325
- if (endpoint.parameters) {
326
- for (const parameter of endpoint.parameters) {
327
- if (parameter.in === "path") {
328
- replacedIdsPath = replacedIdsPath.replace("{" + parameter.name + "}", "By" + ParserUtils.toTitleCaseWord(parameter.name));
329
- replacedIdsPath = replacedIdsPath.replace("/iam", "");
330
- replacedIdsPath = replacedIdsPath.replace("/odin-config", "");
331
- }
332
- }
333
- }
334
- let classMethod = httpMethod + "/" + replacedIdsPath;
335
- classMethod = _.camelCase(classMethod);
336
- classMethod = classMethod.replace("PublicNamespacesByNamespace", "Ns");
337
- classMethod = classMethod.replace("AdminNamespacesByNamespace", "AdminNs");
338
- const searchWord = "NamespacesByNamespace";
339
- const nsExistInsideMethod = classMethod.indexOf(searchWord) > 0 && classMethod.indexOf(searchWord) + searchWord.length < classMethod.length;
340
- const excludedClasses = ["Policies"];
341
- if (nsExistInsideMethod && !excludedClasses.includes(className)) {
342
- classMethod = classMethod.replace(searchWord, "");
343
- }
344
- return classMethod;
345
- }
331
+ const genPath = _.upperFirst(lastWords) + "/" + listBeforeLastWords.join("/") + listByParams.reverse().join("/");
332
+ let generatedMethod = _.camelCase(mappedMethod(httpMethod, isForm) + genPath);
333
+ generatedMethod = replaceAll(generatedMethod, "Byword", "_By");
334
+ generatedMethod = resolveConflicts(path2, generatedMethod, existingMethods);
335
+ return generatedMethod;
336
+ };
346
337
  static filterBodyParams(parameters) {
347
338
  if (Array.isArray(parameters) && parameters.length > 0) {
348
339
  return parameters.filter((parameter) => parameter.in === "body" || parameter.in === "formData");
@@ -462,6 +453,70 @@ class ParserUtils {
462
453
  ${content}`;
463
454
  };
464
455
  }
456
+ const replaceAll = (string, search, replace) => {
457
+ return string.split(search).join(replace);
458
+ };
459
+ const mappedMethod = (httpMethod, isForm) => {
460
+ if (httpMethod === "get") {
461
+ return "fetch";
462
+ } else if (httpMethod === "post" && isForm) {
463
+ return "post";
464
+ } else if (httpMethod === "post") {
465
+ return "create";
466
+ } else if (httpMethod === "put") {
467
+ return "update";
468
+ } else if (httpMethod === "patch") {
469
+ return "patch";
470
+ } else if (httpMethod === "delete") {
471
+ return "delete";
472
+ }
473
+ };
474
+ const resolveConflicts = (path2, generatedMethod, existingMethods) => {
475
+ try {
476
+ testConflict(path2, generatedMethod, existingMethods);
477
+ } catch (e) {
478
+ if (path2.indexOf("/namespaces/") >= 0) {
479
+ generatedMethod += "_ByNS";
480
+ }
481
+ }
482
+ try {
483
+ testConflict(path2, generatedMethod, existingMethods);
484
+ } catch (e) {
485
+ if (path2.indexOf("/v4/") >= 0) {
486
+ generatedMethod += "_v4";
487
+ }
488
+ }
489
+ try {
490
+ testConflict(path2, generatedMethod, existingMethods);
491
+ } catch (e) {
492
+ if (path2.indexOf("/v3/") >= 0) {
493
+ generatedMethod += "_v3";
494
+ }
495
+ }
496
+ try {
497
+ testConflict(path2, generatedMethod, existingMethods);
498
+ } catch (e) {
499
+ if (path2.indexOf("/v2/") >= 0) {
500
+ generatedMethod += "_v2";
501
+ }
502
+ }
503
+ try {
504
+ testConflict(path2, generatedMethod, existingMethods);
505
+ } catch (e) {
506
+ if (path2.indexOf("/admin/") >= 0) {
507
+ generatedMethod += "_admin";
508
+ }
509
+ }
510
+ testConflict(path2, generatedMethod, existingMethods);
511
+ return generatedMethod;
512
+ };
513
+ const testConflict = (path2, generatedMethod, existingMethods) => {
514
+ if (existingMethods[generatedMethod]) {
515
+ const conflictingMethod = { path: path2, generatedMethod };
516
+ throw Error(`Duplicate method conflict in ${JSON.stringify(conflictingMethod)},
517
+ existingMethods: ${JSON.stringify(existingMethods, null, 2)}`);
518
+ }
519
+ };
465
520
 
466
521
  const Schema = zod.z.object({
467
522
  $ref: zod.z.string().nullish(),
@@ -788,7 +843,25 @@ ${exportedTypeString}
788
843
  typeString: refType
789
844
  };
790
845
  } else if (items) {
791
- model2 = this.parseEnumItems(items);
846
+ if (items.type === "array") {
847
+ const ref3 = items.items?.$ref;
848
+ if (ref3) {
849
+ const refType = ParserUtils.parseRefType(ref3);
850
+ this.importClasses.add(refType);
851
+ model2 = {
852
+ schemaString: refType,
853
+ typeString: refType
854
+ };
855
+ } else if (items.items) {
856
+ model2 = this.parseEnumItems(items.items);
857
+ }
858
+ return {
859
+ schemaString: `${schemaAttribute} z.array(z.array(${model2.schemaString}))${schemaRequired}`,
860
+ typeString: `${typeAttribute} ${model2.typeString}[]${typeNullishability}`
861
+ };
862
+ } else {
863
+ model2 = this.parseEnumItems(items);
864
+ }
792
865
  } else {
793
866
  return {
794
867
  schemaString: `${schemaAttribute} z.array(z.any())${schemaRequired}`,
@@ -904,21 +977,32 @@ class CodeGenerator {
904
977
  static getPatchedDir = () => path.join(CliParser.getSwaggersOutputPath(), "patched");
905
978
  static getGeneratedPublicFolder = () => `${CliParser.getOutputPath()}/generated-public`;
906
979
  static getGeneratedAdminFolder = () => `${CliParser.getOutputPath()}/generated-admin`;
907
- static getGeneratedSnippetsFolder = () => `${CliParser.getOutputPath()}/../../../../apps/api-explorer-app/src/generated-snippets`;
980
+ static getGeneratedSnippetsFolder = () => `${CliParser.getSnippetOutputPath()}/generated-snippets`;
981
+ static getServicePrefix = (servicePaths) => servicePaths[servicePaths.length - 1].split("/")[1];
908
982
  static iterateApi = async (api) => {
909
983
  const apiBufferByTag = {};
910
984
  const dependenciesByTag = {};
911
985
  const classImports = {};
912
986
  let arrayDefinitions = [];
913
987
  const snippetMap = {};
914
- for (const path2 in api.paths) {
988
+ const mapClassMethods = {};
989
+ const sortedPathsByLength = new Map(Object.entries(api.paths).sort((a, b) => {
990
+ if (a[0].length === b[0].length) {
991
+ return a[0].localeCompare(b[0]);
992
+ } else {
993
+ return a[0].length - b[0].length;
994
+ }
995
+ }));
996
+ const sortedKeys = Array.from(sortedPathsByLength.keys());
997
+ const servicePrefix = CodeGenerator.getServicePrefix(sortedKeys);
998
+ console.log("ServicePrefix", servicePrefix, ", Paths:", sortedKeys);
999
+ for (const [path2, operation] of sortedPathsByLength) {
915
1000
  const isAdminEndpoint = path2.indexOf("/admin/") > -1;
916
1001
  if (CliParser.isAdmin() && !isAdminEndpoint) {
917
1002
  continue;
918
1003
  } else if (!CliParser.isAdmin() && isAdminEndpoint) {
919
1004
  continue;
920
1005
  }
921
- const operation = api.paths[path2];
922
1006
  const httpMethods = Object.keys(operation);
923
1007
  for (const httpMethod of httpMethods) {
924
1008
  const endpoint = await Endpoint.parseAsync(operation[httpMethod]).catch((error) => {
@@ -928,55 +1012,63 @@ class CodeGenerator {
928
1012
  if (!endpoint.tags) {
929
1013
  continue;
930
1014
  }
1015
+ if (endpoint.deprecated) {
1016
+ continue;
1017
+ }
931
1018
  const [tag] = endpoint.tags;
1019
+ mapClassMethods[tag] = mapClassMethods[tag] ? mapClassMethods[tag] : {};
1020
+ const isForm = endpoint.consumes && endpoint.consumes[0] === "application/x-www-form-urlencoded";
1021
+ const classMethod = ParserUtils.generateNaturalLangMethod({
1022
+ servicePrefix,
1023
+ path: path2,
1024
+ httpMethod,
1025
+ isForm,
1026
+ existingMethods: mapClassMethods[tag]
1027
+ });
1028
+ mapClassMethods[tag][classMethod] = `${path2} ${httpMethod}`;
1029
+ snippetMap[path2] = snippetMap[path2] ? snippetMap[path2] : {};
932
1030
  const description = endpoint.description;
933
- const isDeprecated = endpoint.deprecated;
934
1031
  const responseClass = ParserUtils.get2xxResponse(endpoint.responses);
935
1032
  const { className, classGenName } = ParserUtils.generateClassName(tag);
936
1033
  classImports[className] = classImports[className] ? classImports[className] : {};
937
- if (!isDeprecated) {
938
- snippetMap[path2] = snippetMap[path2] ? snippetMap[path2] : {};
939
- if (responseClass) {
940
- const importTypeClass = ParserUtils.parseRefType(responseClass);
941
- classImports[className][importTypeClass] = `import { ${importTypeClass} } from './definitions/${importTypeClass}'`;
942
- }
943
- if (responseClass && responseClass.endsWith("Array")) {
944
- arrayDefinitions.push(responseClass);
945
- }
946
- const isFormUrlEncoded = ParserUtils.isFormUrlEncoded(httpMethod, endpoint.consumes);
947
- const queryParams = ParserUtils.filterQueryParameters(endpoint.parameters);
948
- const pathParams = ParserUtils.filterPathParams(endpoint.parameters);
949
- let bodyParams = ParserUtils.filterBodyParams(endpoint.parameters);
950
- const isForm = endpoint.consumes && endpoint.consumes[0] === "application/x-www-form-urlencoded";
951
- const classMethod = ParserUtils.generateHumanReadableMethod({ path: path2, httpMethod, isForm });
952
- if (endpoint.requestBody) {
953
- bodyParams = [
954
- {
955
- name: "body",
956
- in: "body",
957
- schema: endpoint.requestBody.content["application/json"].schema
958
- }
959
- ];
960
- }
961
- const pathWithBase = `${api.basePath ?? ""}${path2}`;
962
- const [generatedMethodString, importStatements, snippetString, snippetCurl] = templateMethod({
963
- classMethod,
964
- description,
965
- httpMethod,
966
- path: pathWithBase,
967
- pathParams,
968
- bodyParams,
969
- queryParams,
970
- isFormUrlEncoded,
971
- responseClass
972
- });
973
- apiBufferByTag[tag] = (apiBufferByTag[tag] || "") + generatedMethodString;
974
- dependenciesByTag[tag] = dependenciesByTag[tag] ? [.../* @__PURE__ */ new Set([...importStatements, ...dependenciesByTag[tag]])] : [...new Set(importStatements)];
975
- snippetMap[path2][httpMethod] = {
976
- web: `<span class='sn-blue'>import</span> axios from 'axios'<br/><span class='sn-blue'>import</span> { <span class='sn-purple'>${classGenName}</span> } <span class='sn-blue'>from</span> <span class='sn-green'>'@accelbyte/sdk'</span><br/><br/><span class='sn-blue'>const</span> axios = <span class='sn-blue'>axios</span>.create({<span class='sn-purple'>clientId</span>, <span class='sn-purple'>redirectURI</span>, <span class='sn-purple'>baseURL</span>})<br/><br/><span class='sn-blue'>new</span> ${classGenName}(<span class='sn-purple'>axios</span>, <span class='sn-purple'>namespace</span>).<span class='method-name'>${snippetString}</span>`,
977
- shell: snippetCurl
978
- };
1034
+ if (responseClass) {
1035
+ const importTypeClass = ParserUtils.parseRefType(responseClass);
1036
+ classImports[className][importTypeClass] = `import { ${importTypeClass} } from './definitions/${importTypeClass}'`;
1037
+ }
1038
+ if (responseClass && responseClass.endsWith("Array")) {
1039
+ arrayDefinitions.push(responseClass);
1040
+ }
1041
+ const queryParams = ParserUtils.filterQueryParameters(endpoint.parameters);
1042
+ const pathWithBase = `${api.basePath ?? ""}${path2}`;
1043
+ const isFormUrlEncoded = ParserUtils.isFormUrlEncoded(httpMethod, endpoint.consumes);
1044
+ const pathParams = ParserUtils.filterPathParams(endpoint.parameters);
1045
+ let bodyParams = ParserUtils.filterBodyParams(endpoint.parameters);
1046
+ if (endpoint.requestBody) {
1047
+ bodyParams = [
1048
+ {
1049
+ name: "body",
1050
+ in: "body",
1051
+ schema: endpoint.requestBody.content["application/json"].schema
1052
+ }
1053
+ ];
979
1054
  }
1055
+ const [generatedMethodString, importStatements, snippetString, snippetCurl] = templateMethod({
1056
+ classMethod,
1057
+ description,
1058
+ httpMethod,
1059
+ path: pathWithBase,
1060
+ pathParams,
1061
+ bodyParams,
1062
+ queryParams,
1063
+ isFormUrlEncoded,
1064
+ responseClass
1065
+ });
1066
+ apiBufferByTag[tag] = (apiBufferByTag[tag] || "") + generatedMethodString;
1067
+ dependenciesByTag[tag] = dependenciesByTag[tag] ? [.../* @__PURE__ */ new Set([...importStatements, ...dependenciesByTag[tag]])] : [...new Set(importStatements)];
1068
+ snippetMap[path2][httpMethod] = {
1069
+ web: `<span class='sn-blue'>import</span> axios from 'axios'<br/><span class='sn-blue'>import</span> { <span class='sn-purple'>${classGenName}</span> } <span class='sn-blue'>from</span> <span class='sn-green'>'@accelbyte/sdk'</span><br/><br/><span class='sn-blue'>const</span> axios = <span class='sn-blue'>axios</span>.create({<span class='sn-purple'>clientId</span>, <span class='sn-purple'>redirectURI</span>, <span class='sn-purple'>baseURL</span>})<br/><br/><span class='sn-blue'>new</span> ${classGenName}(<span class='sn-purple'>axios</span>, <span class='sn-purple'>namespace</span>).<span class='method-name'>${snippetString}</span>`,
1070
+ shell: snippetCurl
1071
+ };
980
1072
  }
981
1073
  }
982
1074
  arrayDefinitions = [...new Set(arrayDefinitions)];
@@ -988,7 +1080,6 @@ class CodeGenerator {
988
1080
  const parser = new SwaggerParser();
989
1081
  const generatedFolder = CliParser.isAdmin() ? CodeGenerator.getGeneratedAdminFolder() : CodeGenerator.getGeneratedPublicFolder();
990
1082
  const DIST_DIR = `${generatedFolder}/${serviceName}`;
991
- const DIST_DOCS_DIR = path.join(DIST_DIR, "docs");
992
1083
  const DIST_DEFINITION_DIR = path.join(DIST_DIR, "definitions");
993
1084
  const swaggerFilePath = `${CliParser.getSwaggersOutputPath()}/${swaggerFile}`;
994
1085
  const swaggerPatchedFilePath = `${CodeGenerator.getPatchedDir()}/${swaggerFile}`;
@@ -996,13 +1087,12 @@ class CodeGenerator {
996
1087
  ParserUtils.applyPatchIfExists(swaggerFilePath, swaggerPatchFilePath, swaggerPatchedFilePath, CodeGenerator.getPatchedDir());
997
1088
  const api = await parser.parse(swaggerPatchedFilePath);
998
1089
  const indexImportsSet = /* @__PURE__ */ new Set();
999
- console.log("\n----------\n API name: %s, Version: %s schemes %s", api, api.info.title, api.info.version, api.schemes);
1090
+ console.log("----------\nGenerating API:", { title: api.info.title, version: api.info.version });
1000
1091
  ParserUtils.mkdirIfNotExist(DIST_DIR);
1001
- ParserUtils.mkdirIfNotExist(DIST_DOCS_DIR);
1002
1092
  ParserUtils.mkdirIfNotExist(DIST_DEFINITION_DIR);
1003
1093
  ParserUtils.writeVersionFile(DIST_DIR, "Version.ts", serviceName, api.info, BUILD_DATE);
1004
1094
  const { apiBufferByTag, dependenciesByTag, classImports, arrayDefinitions, snippetMap } = await CodeGenerator.iterateApi(api);
1005
- if (!CliParser.isAdmin()) {
1095
+ if (CliParser.getSnippetOutputPath()) {
1006
1096
  ParserUtils.writeSnippetFile(CodeGenerator.getGeneratedSnippetsFolder(), api.info.title, JSON.stringify(snippetMap, null, 2));
1007
1097
  }
1008
1098
  const targetSrcFolder = `${CliParser.getOutputPath()}/`;
@@ -1041,7 +1131,7 @@ class CodeGenerator {
1041
1131
  ParserUtils.writeDefinitionFile(DIST_DEFINITION_DIR, arrayClass, buffer);
1042
1132
  indexImportsSet.add(ParserUtils.getRelativePathToWebSdkSrcFolder(path.join(DIST_DEFINITION_DIR, arrayClass), targetSrcFolder));
1043
1133
  }
1044
- console.log("\n----------\nCOMPLETED.\n----------\n\n");
1134
+ console.log("\nCOMPLETED\n----------\n\n");
1045
1135
  return indexImportsSet;
1046
1136
  };
1047
1137
  }
@@ -1068,7 +1158,7 @@ class SwaggerDownloader {
1068
1158
  });
1069
1159
  });
1070
1160
  request.on("error", (err) => {
1071
- console.log(`SwaggerDownloader dl failed for "${targetFileName}" and "${url}"`, err);
1161
+ console.log(`SwaggerDownloader failed for "${targetFileName}" and "${url}"`, err);
1072
1162
  });
1073
1163
  };
1074
1164
  static main = () => {