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

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(),
@@ -904,21 +959,32 @@ class CodeGenerator {
904
959
  static getPatchedDir = () => path.join(CliParser.getSwaggersOutputPath(), "patched");
905
960
  static getGeneratedPublicFolder = () => `${CliParser.getOutputPath()}/generated-public`;
906
961
  static getGeneratedAdminFolder = () => `${CliParser.getOutputPath()}/generated-admin`;
907
- static getGeneratedSnippetsFolder = () => `${CliParser.getOutputPath()}/../../../../apps/api-explorer-app/src/generated-snippets`;
962
+ static getGeneratedSnippetsFolder = () => `${CliParser.getSnippetOutputPath()}/generated-snippets`;
963
+ static getServicePrefix = (servicePaths) => servicePaths[servicePaths.length - 1].split("/")[1];
908
964
  static iterateApi = async (api) => {
909
965
  const apiBufferByTag = {};
910
966
  const dependenciesByTag = {};
911
967
  const classImports = {};
912
968
  let arrayDefinitions = [];
913
969
  const snippetMap = {};
914
- for (const path2 in api.paths) {
970
+ const mapClassMethods = {};
971
+ const sortedPathsByLength = new Map(Object.entries(api.paths).sort((a, b) => {
972
+ if (a[0].length === b[0].length) {
973
+ return a[0].localeCompare(b[0]);
974
+ } else {
975
+ return a[0].length - b[0].length;
976
+ }
977
+ }));
978
+ const sortedKeys = Array.from(sortedPathsByLength.keys());
979
+ const servicePrefix = CodeGenerator.getServicePrefix(sortedKeys);
980
+ console.log("ServicePrefix", servicePrefix, ", Paths:", sortedKeys);
981
+ for (const [path2, operation] of sortedPathsByLength) {
915
982
  const isAdminEndpoint = path2.indexOf("/admin/") > -1;
916
983
  if (CliParser.isAdmin() && !isAdminEndpoint) {
917
984
  continue;
918
985
  } else if (!CliParser.isAdmin() && isAdminEndpoint) {
919
986
  continue;
920
987
  }
921
- const operation = api.paths[path2];
922
988
  const httpMethods = Object.keys(operation);
923
989
  for (const httpMethod of httpMethods) {
924
990
  const endpoint = await Endpoint.parseAsync(operation[httpMethod]).catch((error) => {
@@ -928,55 +994,63 @@ class CodeGenerator {
928
994
  if (!endpoint.tags) {
929
995
  continue;
930
996
  }
997
+ if (endpoint.deprecated) {
998
+ continue;
999
+ }
931
1000
  const [tag] = endpoint.tags;
1001
+ mapClassMethods[tag] = mapClassMethods[tag] ? mapClassMethods[tag] : {};
1002
+ const isForm = endpoint.consumes && endpoint.consumes[0] === "application/x-www-form-urlencoded";
1003
+ const classMethod = ParserUtils.generateNaturalLangMethod({
1004
+ servicePrefix,
1005
+ path: path2,
1006
+ httpMethod,
1007
+ isForm,
1008
+ existingMethods: mapClassMethods[tag]
1009
+ });
1010
+ mapClassMethods[tag][classMethod] = `${path2} ${httpMethod}`;
1011
+ snippetMap[path2] = snippetMap[path2] ? snippetMap[path2] : {};
932
1012
  const description = endpoint.description;
933
- const isDeprecated = endpoint.deprecated;
934
1013
  const responseClass = ParserUtils.get2xxResponse(endpoint.responses);
935
1014
  const { className, classGenName } = ParserUtils.generateClassName(tag);
936
1015
  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
- };
1016
+ if (responseClass) {
1017
+ const importTypeClass = ParserUtils.parseRefType(responseClass);
1018
+ classImports[className][importTypeClass] = `import { ${importTypeClass} } from './definitions/${importTypeClass}'`;
1019
+ }
1020
+ if (responseClass && responseClass.endsWith("Array")) {
1021
+ arrayDefinitions.push(responseClass);
1022
+ }
1023
+ const queryParams = ParserUtils.filterQueryParameters(endpoint.parameters);
1024
+ const pathWithBase = `${api.basePath ?? ""}${path2}`;
1025
+ const isFormUrlEncoded = ParserUtils.isFormUrlEncoded(httpMethod, endpoint.consumes);
1026
+ const pathParams = ParserUtils.filterPathParams(endpoint.parameters);
1027
+ let bodyParams = ParserUtils.filterBodyParams(endpoint.parameters);
1028
+ if (endpoint.requestBody) {
1029
+ bodyParams = [
1030
+ {
1031
+ name: "body",
1032
+ in: "body",
1033
+ schema: endpoint.requestBody.content["application/json"].schema
1034
+ }
1035
+ ];
979
1036
  }
1037
+ const [generatedMethodString, importStatements, snippetString, snippetCurl] = templateMethod({
1038
+ classMethod,
1039
+ description,
1040
+ httpMethod,
1041
+ path: pathWithBase,
1042
+ pathParams,
1043
+ bodyParams,
1044
+ queryParams,
1045
+ isFormUrlEncoded,
1046
+ responseClass
1047
+ });
1048
+ apiBufferByTag[tag] = (apiBufferByTag[tag] || "") + generatedMethodString;
1049
+ dependenciesByTag[tag] = dependenciesByTag[tag] ? [.../* @__PURE__ */ new Set([...importStatements, ...dependenciesByTag[tag]])] : [...new Set(importStatements)];
1050
+ snippetMap[path2][httpMethod] = {
1051
+ 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>`,
1052
+ shell: snippetCurl
1053
+ };
980
1054
  }
981
1055
  }
982
1056
  arrayDefinitions = [...new Set(arrayDefinitions)];
@@ -988,7 +1062,6 @@ class CodeGenerator {
988
1062
  const parser = new SwaggerParser();
989
1063
  const generatedFolder = CliParser.isAdmin() ? CodeGenerator.getGeneratedAdminFolder() : CodeGenerator.getGeneratedPublicFolder();
990
1064
  const DIST_DIR = `${generatedFolder}/${serviceName}`;
991
- const DIST_DOCS_DIR = path.join(DIST_DIR, "docs");
992
1065
  const DIST_DEFINITION_DIR = path.join(DIST_DIR, "definitions");
993
1066
  const swaggerFilePath = `${CliParser.getSwaggersOutputPath()}/${swaggerFile}`;
994
1067
  const swaggerPatchedFilePath = `${CodeGenerator.getPatchedDir()}/${swaggerFile}`;
@@ -996,13 +1069,12 @@ class CodeGenerator {
996
1069
  ParserUtils.applyPatchIfExists(swaggerFilePath, swaggerPatchFilePath, swaggerPatchedFilePath, CodeGenerator.getPatchedDir());
997
1070
  const api = await parser.parse(swaggerPatchedFilePath);
998
1071
  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);
1072
+ console.log("----------\nGenerating API:", { title: api.info.title, version: api.info.version });
1000
1073
  ParserUtils.mkdirIfNotExist(DIST_DIR);
1001
- ParserUtils.mkdirIfNotExist(DIST_DOCS_DIR);
1002
1074
  ParserUtils.mkdirIfNotExist(DIST_DEFINITION_DIR);
1003
1075
  ParserUtils.writeVersionFile(DIST_DIR, "Version.ts", serviceName, api.info, BUILD_DATE);
1004
1076
  const { apiBufferByTag, dependenciesByTag, classImports, arrayDefinitions, snippetMap } = await CodeGenerator.iterateApi(api);
1005
- if (!CliParser.isAdmin()) {
1077
+ if (CliParser.getSnippetOutputPath()) {
1006
1078
  ParserUtils.writeSnippetFile(CodeGenerator.getGeneratedSnippetsFolder(), api.info.title, JSON.stringify(snippetMap, null, 2));
1007
1079
  }
1008
1080
  const targetSrcFolder = `${CliParser.getOutputPath()}/`;
@@ -1041,7 +1113,7 @@ class CodeGenerator {
1041
1113
  ParserUtils.writeDefinitionFile(DIST_DEFINITION_DIR, arrayClass, buffer);
1042
1114
  indexImportsSet.add(ParserUtils.getRelativePathToWebSdkSrcFolder(path.join(DIST_DEFINITION_DIR, arrayClass), targetSrcFolder));
1043
1115
  }
1044
- console.log("\n----------\nCOMPLETED.\n----------\n\n");
1116
+ console.log("\nCOMPLETED\n----------\n\n");
1045
1117
  return indexImportsSet;
1046
1118
  };
1047
1119
  }
@@ -1068,7 +1140,7 @@ class SwaggerDownloader {
1068
1140
  });
1069
1141
  });
1070
1142
  request.on("error", (err) => {
1071
- console.log(`SwaggerDownloader dl failed for "${targetFileName}" and "${url}"`, err);
1143
+ console.log(`SwaggerDownloader failed for "${targetFileName}" and "${url}"`, err);
1072
1144
  });
1073
1145
  };
1074
1146
  static main = () => {