@accelbyte/codegen 1.0.0-beta.6 → 1.0.1-experimental.1

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.
@@ -1,7 +1,7 @@
1
1
  import yargs from 'yargs';
2
2
  import { z } from 'zod';
3
3
  import * as fs from 'fs';
4
- import fs__default from 'fs';
4
+ import fs__default, { readFileSync, writeFileSync } from 'fs';
5
5
  import * as path from 'path';
6
6
  import path__default from 'path';
7
7
  import SwaggerParser from '@apidevtools/swagger-parser';
@@ -56,18 +56,18 @@ class CliParser {
56
56
  };
57
57
  }
58
58
 
59
- const getImportableVarMap = () => ({
59
+ const getImportableVarMap$1 = () => ({
60
60
  "@accelbyte/sdk": ["CodeGenUtil", "SdkCache", "IResponse", "IResponseWithSync", "Validate"],
61
61
  axios: ["AxiosRequestConfig", "AxiosResponse"],
62
62
  zod: ["z"]
63
63
  });
64
- const makeNewImportVarMap = () => ({
64
+ const makeNewImportVarMap$1 = () => ({
65
65
  axios: ["AxiosInstance"],
66
66
  "@accelbyte/sdk": ["SDKRequestConfig"]
67
67
  });
68
- const generateImports = (body, importStatements) => {
69
- const usedImportVarMap = makeNewImportVarMap();
70
- const importableVarMap = getImportableVarMap();
68
+ const generateImports = (body, importStatements, makeNewImportVarMap2, getImportableVarMap2) => {
69
+ const usedImportVarMap = makeNewImportVarMap2;
70
+ const importableVarMap = getImportableVarMap2;
71
71
  for (const [moduleSource, importableVars] of Object.entries(importableVarMap)) {
72
72
  for (const importableVar of importableVars) {
73
73
  const importVarRegex = new RegExp(`(?<![\\d\\w_])${importableVar}(?![\\d\\w_])`);
@@ -80,10 +80,11 @@ const generateImports = (body, importStatements) => {
80
80
  return `${generatedImports}
81
81
  ${importStatements.sort().join("\n")}`;
82
82
  };
83
- const templateClass = (className, body, importStatements) => `/**
84
- * DON'T EDIT THIS FILE, it is AUTO GENERATED
83
+ const templateClass = (className, body, importStatements) => {
84
+ return `/**
85
+ * AUTO GENERATED
85
86
  */
86
- ${generateImports(body, importStatements)}
87
+ ${generateImports(body, importStatements, makeNewImportVarMap$1(), getImportableVarMap$1())}
87
88
 
88
89
  export class ${className} {
89
90
  // @ts-ignore
@@ -91,15 +92,37 @@ export class ${className} {
91
92
  ${body}
92
93
  }
93
94
  `;
95
+ };
94
96
 
95
- const templateJsdocFile = (apiName, body) => `
96
- \`\`\`
97
- SDK.getService('${apiName}', authHeader):
98
- ${body}
97
+ const getImportableVarMap = () => ({
98
+ "@accelbyte/sdk": ["CodeGenUtil", "SdkCache", "IResponse", "IResponseWithSync", "Validate", "ApiArgs", "Network", "AccelbyteSDK"]
99
+ });
100
+ const makeNewImportVarMap = () => ({
101
+ "@accelbyte/sdk": ["AccelbyteSDK", "ApiArgs", "ApiUtils"]
102
+ });
103
+ const templateApiClass = (className, body, importStatements, returnMethods) => {
104
+ return `/**
105
+ * AUTO GENERATED
106
+ */
107
+ /* eslint-disable camelcase */
108
+ ${generateImports(body, importStatements, makeNewImportVarMap(), getImportableVarMap())}
99
109
 
100
- \`\`\`
101
- `.replace(/, \)/g, ")").trim();
110
+ export function ${className}(sdk: AccelbyteSDK, args?: ApiArgs) {
111
+ const sdkAssembly = sdk.assembly()
112
+
113
+ const namespace = args?.namespace ? args?.namespace : sdkAssembly.namespace
114
+ const cache = args?.cache ? args?.cache : sdkAssembly.cache
115
+ const requestConfig = ApiUtils.mergedConfigs(sdkAssembly.config, args)
116
+ ${body}
117
+
118
+ return {
119
+ ${returnMethods}
120
+ }
121
+ }
122
+ `;
123
+ };
102
124
 
125
+ const UNDEFINED_SWAGGER_SEMVER = "0.0.0";
103
126
  const REMOVED_KEYWORDS = [
104
127
  "/admin/",
105
128
  "/public/",
@@ -113,39 +136,62 @@ const REMOVED_KEYWORDS = [
113
136
  "/{namespace}/"
114
137
  ];
115
138
  class ParserUtils {
139
+ static replaceAll = (string, search, replace) => {
140
+ return string.split(search).join(replace);
141
+ };
116
142
  static generateClassName = (tag) => {
117
143
  const className = _.upperFirst(_.camelCase(tag));
118
144
  const classGenName = CliParser.isAdmin() ? className + "Admin$" : className + "$";
119
145
  return { className, classGenName };
120
146
  };
147
+ static generateApiName = (tag) => {
148
+ const apiName = _.upperFirst(_.camelCase(tag));
149
+ const apiGenName = CliParser.isAdmin() ? apiName + "AdminApi" : apiName + "Api";
150
+ return { apiName, apiGenName };
151
+ };
121
152
  static parseQueryParamAttributeDefault = (definition) => {
122
153
  const attrName = definition.name.slice(definition.name.lastIndexOf(".") + 1);
123
154
  const defaultValue = definition.type === "string" ? `'${definition.default}'` : definition.default;
124
155
  return `${attrName}: ${defaultValue}`;
125
156
  };
126
157
  static parseType = (pathParam) => {
127
- if (pathParam.type === "int" || pathParam.type === "integer" || pathParam?.schema?.type === "integer")
158
+ if (isSwaggerIntegerType(pathParam.type || pathParam?.schema?.type))
128
159
  return "number";
129
- if (pathParam.type === "array")
160
+ if (pathParam.type === "array") {
161
+ if (isSwaggerIntegerType(pathParam.items.type))
162
+ return "number[]";
130
163
  return `${pathParam.items.type ?? "any"}[]`;
131
- if (pathParam?.schema?.type === "array")
164
+ }
165
+ if (pathParam?.schema?.type === "array") {
166
+ if (isSwaggerIntegerType(pathParam.schema.items.type))
167
+ return "number[]";
132
168
  return `${pathParam.schema.items.type ?? "any"}[]`;
169
+ }
133
170
  if (pathParam?.schema?.type)
134
171
  return pathParam.schema.type;
135
172
  return pathParam.type;
136
173
  };
137
174
  static parseQueryParamsType = (queryParams) => {
138
- return queryParams.map((queryParam) => ParserUtils.parseAttributeType(queryParam)).join(",");
175
+ return queryParams.map((queryParam) => ParserUtils.parseAttributeType(queryParam)).join(", ");
139
176
  };
140
177
  static isAnyQueryParamRequired = (queryParams) => {
141
178
  return queryParams.some((queryParam) => queryParam.required);
142
179
  };
180
+ static convertDashesToTitleCase = (str) => {
181
+ const result = str.split("-").map((word, index) => {
182
+ if (index === 0) {
183
+ return word.charAt(0).toUpperCase() + word.slice(1);
184
+ }
185
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
186
+ }).join("");
187
+ return result;
188
+ };
143
189
  static parseQueryParamsDefault = (queryParams) => {
144
190
  const result = queryParams.filter((queryParam) => !!queryParam.default && !queryParam.required).map(ParserUtils.parseQueryParamAttributeDefault).join(",");
145
191
  return result ? `${result},` : "";
146
192
  };
147
193
  static parseBodyParamsImports = (bodyParams) => {
148
- return bodyParams.map(ParserUtils.parseRefImport).filter(Boolean);
194
+ return bodyParams.map((bodyParams2) => ParserUtils.parseRefImport(bodyParams2)).filter(Boolean);
149
195
  };
150
196
  static parseImportDir = ($ref) => {
151
197
  let ref = $ref.replace(".", "/");
@@ -164,7 +210,7 @@ class ParserUtils {
164
210
  return null;
165
211
  }
166
212
  const type = ParserUtils.parseRefType($ref);
167
- return `import { ${type} } from './definitions/${type}'`;
213
+ return `import { ${type} } from '../definitions/${type}.js'`;
168
214
  };
169
215
  static parseRefType = ($ref) => {
170
216
  let ref = $ref.replace(".", "/");
@@ -211,6 +257,8 @@ class ParserUtils {
211
257
  return retBodyParams;
212
258
  }
213
259
  if (bodyParam?.schema?.type === "array" && !bodyParam?.schema?.items?.$ref) {
260
+ if (isSwaggerIntegerType(bodyParam.schema.items.type))
261
+ return "number[]";
214
262
  return `${bodyParam.schema.items.type ?? "any"}[]`;
215
263
  }
216
264
  if (bodyParam?.schema?.type === "array" && bodyParam?.schema?.items?.$ref) {
@@ -267,8 +315,8 @@ class ParserUtils {
267
315
  path_ = path_.substring(1);
268
316
  const isPlural = httpMethod === "get" && !(path2.slice(-1) === "}");
269
317
  if (!isPlural) {
270
- path_ = replaceAll(path_, "ies/", "y/");
271
- path_ = replaceAll(path_, "s/", "/");
318
+ path_ = ParserUtils.replaceAll(path_, "ies/", "y/");
319
+ path_ = ParserUtils.replaceAll(path_, "s/", "/");
272
320
  if (path_.indexOf("status") < 0) {
273
321
  path_ = path_.replace(/ies$/, "y");
274
322
  path_ = path_.replace(/s$/, "");
@@ -307,7 +355,7 @@ class ParserUtils {
307
355
  });
308
356
  const genPath = _.upperFirst(lastWords) + "/" + listBeforeLastWords.join("/") + listByParams.reverse().join("/");
309
357
  let generatedMethod = _.camelCase(mappedMethod(httpMethod, isForm) + genPath);
310
- generatedMethod = replaceAll(generatedMethod, "Byword", "_By");
358
+ generatedMethod = ParserUtils.replaceAll(generatedMethod, "Byword", "_By");
311
359
  generatedMethod = resolveConflicts(path2, generatedMethod, existingMethods);
312
360
  return generatedMethod;
313
361
  };
@@ -332,12 +380,22 @@ class ParserUtils {
332
380
  const fileContent = templateClass(apiName, apiBuffer, imports);
333
381
  fs__default.writeFileSync(`${distDir}/${apiName}.ts`, ParserUtils.prependCopyrightHeader(fileContent));
334
382
  }
335
- static writeJsdocFile(distDir, nameArray, apiBuffer) {
336
- const jsdocFile = templateJsdocFile(nameArray[0], apiBuffer);
337
- fs__default.writeFileSync(`${distDir}/docs/${nameArray[0]}.md`, jsdocFile);
383
+ static writeApiFile(distDir, apiName, apiBuffer, imports, returnMethods) {
384
+ const newImports = [];
385
+ imports.forEach((el, index) => {
386
+ newImports.push(el.replace("../definitions", "./definitions"));
387
+ });
388
+ const fileContent = templateApiClass(apiName, apiBuffer, newImports, returnMethods);
389
+ fs__default.writeFileSync(`${distDir}/${apiName}.ts`, ParserUtils.prependCopyrightHeader(fileContent));
390
+ }
391
+ static writeApiMainFile(distDir, serviceName, fileContent) {
392
+ fs__default.writeFileSync(`${distDir}/${serviceName}.ts`, ParserUtils.prependCopyrightHeader(fileContent));
338
393
  }
339
394
  static writeSnippetFile(distDir, name, docBuffer) {
340
- fs__default.writeFileSync(`${distDir}/${name}.json`, docBuffer);
395
+ let snippetFileName = ParserUtils.replaceAll(name, " ", "-").toLowerCase();
396
+ snippetFileName = snippetFileName.replace("justice-", "");
397
+ snippetFileName = "snippet-" + snippetFileName + ".json";
398
+ fs__default.writeFileSync(`${distDir}/${snippetFileName}`, docBuffer);
341
399
  }
342
400
  static writeDefinitionFile(distDir, name, buffer) {
343
401
  ParserUtils.mkdirIfNotExist(distDir);
@@ -347,15 +405,36 @@ class ParserUtils {
347
405
  ParserUtils.mkdirIfNotExist(distDir);
348
406
  fs__default.writeFileSync(path__default.join(distDir, `all-${isAdminWebSdk ? "admin" : "public"}-imports.ts`), ParserUtils.prependCopyrightHeader(buffer));
349
407
  }
350
- static writeVersionFile(distDir, fileName, serviceName, apiInfo, buildDate) {
351
- const ver = apiInfo.version ? `'${apiInfo.version}'` : void 0;
352
- fs__default.writeFileSync(`${distDir}/${fileName}`, `export default {
353
- title: '${serviceName}',
354
- name: '${apiInfo.title}',
355
- version: ${ver},
356
- buildDate: '${buildDate}'
357
- }
358
- `);
408
+ static syncChangelog(packageVersion) {
409
+ const currDir = process.cwd();
410
+ const pathToChangelog = path__default.join(currDir, "./CHANGELOG.md");
411
+ let fileContent = readFileSync(pathToChangelog, "utf-8");
412
+ const date = new Date().toISOString().slice(0, 10);
413
+ fileContent = "### " + packageVersion + " - " + date + `
414
+
415
+ - code-generated update
416
+
417
+ ` + fileContent.trim();
418
+ fs__default.writeFileSync(pathToChangelog, fileContent, "utf-8");
419
+ }
420
+ static syncPackageVersion(apiInfo, isAdminWebSdk) {
421
+ if (isAdminWebSdk) {
422
+ return;
423
+ }
424
+ const currDir = process.cwd();
425
+ const { packageJSON, pathToPackageJSON } = ParserUtils.getPackageJSONInfo(currDir);
426
+ let swaggerVersion = apiInfo.version ? apiInfo.version : UNDEFINED_SWAGGER_SEMVER;
427
+ swaggerVersion = Number(swaggerVersion.replace(".", "").replace(".", ""));
428
+ swaggerVersion = isNaN(swaggerVersion) ? 0 : swaggerVersion;
429
+ const semver = packageJSON.version.split(".").map((numStr) => Number(numStr));
430
+ const newSemver = [semver[0], swaggerVersion, ++semver[2]];
431
+ packageJSON.version = newSemver[0] + "." + newSemver[1] + "." + newSemver[2];
432
+ writeFileSync(pathToPackageJSON, JSON.stringify(packageJSON, null, 2));
433
+ ParserUtils.syncChangelog(packageJSON.version);
434
+ }
435
+ static getPackageJSONInfo(dir) {
436
+ const pathToPackageJSON = path__default.join(dir, "./package.json");
437
+ return { packageJSON: JSON.parse(readFileSync(pathToPackageJSON, "utf-8")), pathToPackageJSON };
359
438
  }
360
439
  static toCamelCase(str) {
361
440
  return str.split("/").map(function(word, index) {
@@ -430,12 +509,9 @@ class ParserUtils {
430
509
  ${content}`;
431
510
  };
432
511
  }
433
- const replaceAll = (string, search, replace) => {
434
- return string.split(search).join(replace);
435
- };
436
512
  const mappedMethod = (httpMethod, isForm) => {
437
513
  if (httpMethod === "get") {
438
- return "fetch";
514
+ return "get";
439
515
  } else if (httpMethod === "post" && isForm) {
440
516
  return "post";
441
517
  } else if (httpMethod === "post") {
@@ -494,6 +570,9 @@ const testConflict = (path2, generatedMethod, existingMethods) => {
494
570
  existingMethods: ${JSON.stringify(existingMethods, null, 2)}`);
495
571
  }
496
572
  };
573
+ const isSwaggerIntegerType = (type) => {
574
+ return type === "integer" || type === "int";
575
+ };
497
576
 
498
577
  const Schema = z.object({
499
578
  $ref: z.string().nullish(),
@@ -596,16 +675,15 @@ const templateMethod = ({
596
675
  isFormUrlEncoded,
597
676
  responseClass
598
677
  }) => {
599
- let methodSignature = "";
678
+ let methodParams = "";
679
+ let methodParamsNoTypes = "";
600
680
  let newPath = `'${path}'`;
601
- let dependencies = [];
602
- let snippetString = "";
603
- let snippetMethodSignature = "";
604
- let snippetCurl = "";
681
+ let importStatements = [];
605
682
  for (const pathParam of pathParams) {
606
683
  const type = ParserUtils.parseType(pathParam);
607
684
  if (pathParam.name !== "namespace") {
608
- methodSignature += pathParam.name + `:${type}, `;
685
+ methodParams += pathParam.name + `:${type}, `;
686
+ methodParamsNoTypes += pathParam.name + ", ";
609
687
  }
610
688
  const pName = pathParam.name === "namespace" ? "this.namespace" : pathParam.name;
611
689
  if (path.match(`{${pathParam.name}}`)) {
@@ -616,20 +694,12 @@ const templateMethod = ({
616
694
  }
617
695
  }
618
696
  }
619
- snippetCurl = `<span class='sn-blue'>curl</span> --location --request <span class='sn-blue'>${httpMethod}</span> '<span class='sn-green'>__DOMAIN__${path}</span>' --header 'accept: application/json'`;
620
697
  let dataType = null;
621
698
  if (httpMethod !== "get") {
622
699
  dataType = ParserUtils.parseBodyParamsType(bodyParams);
623
- dependencies = ParserUtils.parseBodyParamsImports(bodyParams);
624
- methodSignature += dataType ? `data: ${dataType},` : "";
625
- const snippetParams = bodyParams?.map((ob) => {
626
- return ` <span class='sn-purple'>${ob.name}</span>`;
627
- });
628
- snippetMethodSignature += snippetParams ? `data: { ${snippetParams} }` : "";
629
- const curlParams = bodyParams?.map((ob) => {
630
- return ` <span class='sn-purple'>"${ob.name}": ""</span>`;
631
- });
632
- snippetCurl += ` --data-raw { ${curlParams}}`;
700
+ importStatements = ParserUtils.parseBodyParamsImports(bodyParams);
701
+ methodParams += dataType ? `data: ${dataType},` : "";
702
+ methodParamsNoTypes += dataType ? `data,` : "";
633
703
  }
634
704
  const isAnyRequired = ParserUtils.isAnyQueryParamRequired(queryParams);
635
705
  const queryParamsType = queryParams.length ? `queryParams${isAnyRequired ? "" : "?"}: {${ParserUtils.parseQueryParamsType(queryParams)}}` : "";
@@ -639,7 +709,7 @@ const templateMethod = ({
639
709
  let dataPayload = "{params}";
640
710
  const descriptionText = description ? `
641
711
  /**
642
- * ${(description || "").replace(/\n/g, "\n * ")}
712
+ * ${description.replace(/\n/g, "\n * ")}
643
713
  */` : "";
644
714
  let formPayloadString = "";
645
715
  if (isFormUrlEncoded) {
@@ -651,23 +721,21 @@ const templateMethod = ({
651
721
  } else if (isDelete) {
652
722
  dataPayload = dataType ? `{data, params}` : "{params}";
653
723
  }
654
- const isFileUpload = methodSignature.indexOf("data: {file") > -1;
724
+ const isFileUpload = methodParams.indexOf("data: {file") > -1;
655
725
  const resolvedResponseClass = responseClass || "unknown";
656
726
  const resolvedResponseClassValidated = responseClass || "z.unknown()";
657
- const parameters = (queryParamsType ? `${methodSignature} ${queryParamsType}` : methodSignature).replace(/,\s*$/, "");
727
+ methodParams = (queryParamsType ? `${methodParams} ${queryParamsType}` : methodParams).replace(/,\s*$/, "");
728
+ methodParamsNoTypes = queryParamsType ? `${methodParamsNoTypes} queryParams` : methodParamsNoTypes;
658
729
  let methodImpl = "";
659
730
  const isCacheFetch = ["get"].includes(httpMethod) && resolvedResponseClass !== "unknown";
660
- const cachedFetchMethod = classMethod.replace("get", "fetch");
731
+ const cachedFetchMethod = classMethod;
661
732
  const deprecateTag = isCacheFetch ? `/**
662
- * @deprecated Use "${cachedFetchMethod}()" instead.
733
+ * @deprecated Use "${classMethod}()" instead.
663
734
  */` : "";
664
735
  const isGuardInvoked = ["get", "post", "put", "patch", "delete"].includes(httpMethod);
665
736
  const methodName = httpMethod === "get" ? cachedFetchMethod : ["post", "put", "patch", "delete"].includes(httpMethod) ? classMethod : "";
666
- const methodGenerics = resolvedResponseClass !== "unknown" ? `<T = ${resolvedResponseClass}>` : "";
667
- const responseType = resolvedResponseClass !== "unknown" ? `T` : "unknown";
668
- const generateMethodName = () => `${methodName}${methodGenerics}(${parameters}): Promise<${responseSyncType}<${responseType}>>`;
669
- const generateSnippetMethodName = () => `${methodName}(${snippetMethodSignature})`;
670
- snippetString += `${generateSnippetMethodName()}`;
737
+ const responseType = resolvedResponseClass !== "unknown" ? `${resolvedResponseClass}` : "unknown";
738
+ const generateMethodName = () => `${methodName}(${methodParams}): Promise<${responseSyncType}<${responseType}>>`;
671
739
  const responseSyncType = httpMethod === "get" ? "IResponseWithSync" : ["post", "put", "patch", "delete"].includes(httpMethod) ? "IResponse" : "";
672
740
  methodImpl = `${descriptionText}
673
741
  ${generateMethodName()} {
@@ -675,26 +743,32 @@ const templateMethod = ({
675
743
  const url = ${newPath} ${formPayloadString} ${isFileUpload ? "\n// TODO file upload not implemented" : ""}
676
744
  const resultPromise = this.axiosInstance.${httpMethod}(url, ${dataPayload})
677
745
 
678
- ${httpMethod === "get" ? ` const res = () => Validate.responseType(() => resultPromise, ${resolvedResponseClassValidated})
746
+ ${httpMethod === "get" ? ` const res = () => Validate.responseType(() => resultPromise, ${resolvedResponseClassValidated}, '${resolvedResponseClassValidated}')
679
747
 
680
748
  if (!this.cache) {
681
749
  return SdkCache.withoutCache(res)
682
750
  }
683
751
  const cacheKey = url + CodeGenUtil.hashCode(JSON.stringify({ params }))
684
- return SdkCache.withCache(cacheKey, res)` : ""}${["post", "put", "patch", "delete"].includes(httpMethod) ? ` return Validate.responseType(() => resultPromise, ${resolvedResponseClassValidated})` : ""}
752
+ return SdkCache.withCache(cacheKey, res)` : ""}${["post", "put", "patch", "delete"].includes(httpMethod) ? ` return Validate.responseType(() => resultPromise, ${resolvedResponseClassValidated}, '${resolvedResponseClassValidated}')` : ""}
685
753
  }
686
754
  `;
687
755
  if (!isGuardInvoked) {
688
756
  methodImpl = `${descriptionText}
689
757
  ${deprecateTag}
690
- TODO_${classMethod}<T = ${resolvedResponseClass}>(${parameters}): Promise<AxiosResponse<T>> {
758
+ TODO_${classMethod}(${methodParams}): Promise<AxiosResponse<${resolvedResponseClass}>> {
691
759
  ${queryParamsDefault}
692
760
  const url = ${newPath} ${formPayloadString} ${isFileUpload ? "\n// TODO file upload not implemented" : ""}
693
761
  return this.axiosInstance.${httpMethod}(url, ${dataPayload})
694
762
  }
695
763
  `;
696
764
  }
697
- return [methodImpl, dependencies, snippetString, snippetCurl];
765
+ const res = {
766
+ methodImpl,
767
+ methodParams,
768
+ methodParamsNoTypes,
769
+ importStatements
770
+ };
771
+ return res;
698
772
  };
699
773
 
700
774
  class TemplateZod {
@@ -710,7 +784,7 @@ class TemplateZod {
710
784
  }
711
785
  let imports = "";
712
786
  for (const cl of Array.from(this.importClasses).sort()) {
713
- imports += `import { ${cl} } from './${cl}'
787
+ imports += `import { ${cl} } from './${cl}.js'
714
788
  `;
715
789
  }
716
790
  let exportedVariableString;
@@ -820,7 +894,25 @@ ${exportedTypeString}
820
894
  typeString: refType
821
895
  };
822
896
  } else if (items) {
823
- model2 = this.parseEnumItems(items);
897
+ if (items.type === "array") {
898
+ const ref3 = items.items?.$ref;
899
+ if (ref3) {
900
+ const refType = ParserUtils.parseRefType(ref3);
901
+ this.importClasses.add(refType);
902
+ model2 = {
903
+ schemaString: refType,
904
+ typeString: refType
905
+ };
906
+ } else if (items.items) {
907
+ model2 = this.parseEnumItems(items.items);
908
+ }
909
+ return {
910
+ schemaString: `${schemaAttribute} z.array(z.array(${model2.schemaString}))${schemaRequired}`,
911
+ typeString: `${typeAttribute} ${model2.typeString}[]${typeNullishability}`
912
+ };
913
+ } else {
914
+ model2 = this.parseEnumItems(items);
915
+ }
824
916
  } else {
825
917
  return {
826
918
  schemaString: `${schemaAttribute} z.array(z.any())${schemaRequired}`,
@@ -892,7 +984,7 @@ class TemplateZodArray {
892
984
  render = (name) => {
893
985
  const cls = name.replace("Array", "");
894
986
  const template = `import { z } from 'zod'
895
- import { ${cls} } from './${cls}'
987
+ import { ${cls} } from './${cls}.js'
896
988
 
897
989
  export const ${name} = z.array(${cls})
898
990
 
@@ -931,15 +1023,138 @@ const extractEnumObject = (type, isRequired, enumArr) => {
931
1023
  };
932
1024
  };
933
1025
 
934
- const BUILD_DATE = new Date().toISOString();
1026
+ const templateApiMethod = ({
1027
+ classMethod,
1028
+ description,
1029
+ httpMethod,
1030
+ path,
1031
+ pathParams,
1032
+ bodyParams,
1033
+ responseClass,
1034
+ classGenName,
1035
+ methodParams,
1036
+ methodParamsNoTypes
1037
+ }) => {
1038
+ let methodSignature = "";
1039
+ let newPath = `'${path}'`;
1040
+ let snippetSdk = "";
1041
+ let snippetShell = "";
1042
+ for (const pathParam of pathParams) {
1043
+ const type = ParserUtils.parseType(pathParam);
1044
+ if (pathParam.name !== "namespace") {
1045
+ methodSignature += pathParam.name + `:${type}, `;
1046
+ }
1047
+ const pName = pathParam.name === "namespace" ? "this.namespace" : pathParam.name;
1048
+ if (path.match(`{${pathParam.name}}`)) {
1049
+ if (type === "string") {
1050
+ newPath = `${newPath}.replace('{${pathParam.name}}', ${pName})`;
1051
+ } else {
1052
+ newPath = `${newPath}.replace('{${pathParam.name}}', String(${pName}))`;
1053
+ }
1054
+ }
1055
+ }
1056
+ snippetShell = `curl --location --request \\
1057
+ ${httpMethod} '__DOMAIN__${path}' \\
1058
+ --header 'accept: application/json'`;
1059
+ if (httpMethod !== "get") {
1060
+ const curlParams = bodyParams?.map((ob) => {
1061
+ return ` "${ob.name}": ""`;
1062
+ });
1063
+ snippetShell += ` \\
1064
+ --data-raw '{ ${curlParams}}'`;
1065
+ }
1066
+ const descriptionText = description ? `
1067
+ /**
1068
+ * ${description.replace(/\n/g, "\n * ")}
1069
+ */` : "";
1070
+ const resolvedResponseClass = responseClass || "unknown";
1071
+ const responseType = resolvedResponseClass !== "unknown" ? `${resolvedResponseClass}` : "unknown";
1072
+ const methodImpl = `
1073
+ ${descriptionText}
1074
+ async function ${classMethod}(${methodParams}): Promise<${responseType}> {
1075
+ const $ = new ${classGenName}(Network.create(requestConfig), namespace, cache)
1076
+ const resp = await $.${classMethod}(${methodParamsNoTypes})
1077
+ if (resp.error) throw resp.error
1078
+ return resp.response.data
1079
+ }
1080
+ `;
1081
+ const snippetPromiseString = responseType !== "unknown" ? `Promise<${responseType}>` : "Promise";
1082
+ snippetSdk += `${classMethod}(${methodParams})
1083
+ // return ${snippetPromiseString}`;
1084
+ return [methodImpl, snippetSdk, snippetShell];
1085
+ };
1086
+
1087
+ const templateApiIndex = (serviceName, serviceNameTitle, apiList) => {
1088
+ let imports = "";
1089
+ let returnStatement = "";
1090
+ for (const cl of apiList) {
1091
+ imports += `
1092
+ import { ${cl} } from './${serviceName}/${cl}.js'`;
1093
+ returnStatement += `
1094
+ ${cl}, `;
1095
+ }
1096
+ return `/**
1097
+ * AUTO GENERATED
1098
+ */
1099
+ ${imports}
1100
+
1101
+ const apis = {
1102
+ ${returnStatement}
1103
+ }
1104
+
1105
+ export const ${ParserUtils.convertDashesToTitleCase(serviceNameTitle)} = apis
1106
+ `;
1107
+ };
1108
+
1109
+ const templateSdkSnippet = (serviceNameTitle, apiName, methodSnippet) => {
1110
+ const methodArr = methodSnippet.split("//");
1111
+ let normMethod = normalizeMethodSnippet(methodArr[0].trim(), "data:");
1112
+ normMethod = normalizeMethodSnippet(normMethod, "queryParams:");
1113
+ normMethod = normalizeMethodSnippet(normMethod, "queryParams?:");
1114
+ normMethod += "\n\n//" + methodArr[1];
1115
+ const sdkSnippet = `import { Accelbyte } from '@accelbyte/sdk'
1116
+ import { ${serviceNameTitle} } from '@accelbyte/sdk-${serviceNameTitle.toLowerCase()}'
1117
+
1118
+ const sdk = Accelbyte.SDK({
1119
+ baseURL: 'https://demo.accelbyte.io',
1120
+ clientId: '77f88506b6174c3ea4d925f5b4096ce8',
1121
+ namespace: 'accelbyte',
1122
+ redirectURI: 'http://localhost:3030'
1123
+ })
1124
+
1125
+ ${serviceNameTitle}.${apiName}(sdk)
1126
+ .${normMethod}`;
1127
+ return sdkSnippet;
1128
+ };
1129
+ const normalizeMethodSnippet = (methodInput, splitWord) => {
1130
+ const split1 = methodInput.split(splitWord);
1131
+ if (!split1[1]) {
1132
+ return methodInput;
1133
+ }
1134
+ let split2 = split1[1].trim();
1135
+ split2 = ParserUtils.replaceAll(split2, "{", "");
1136
+ split2 = ParserUtils.replaceAll(split2, "})", "");
1137
+ split2 = split2.split(",");
1138
+ let params = "";
1139
+ split2.forEach((p) => {
1140
+ params += "\n " + ParserUtils.replaceAll(p.trim(), ")", "") + ",";
1141
+ });
1142
+ params = params.slice(0, -1);
1143
+ const result = split1[0] + splitWord + " {" + params + "\n })";
1144
+ return result;
1145
+ };
1146
+
1147
+ const GIT_URL = "https://github.com/AccelByte/accelbyte-web-sdk/blob/main/packages";
935
1148
  class CodeGenerator {
936
1149
  static getPatchedDir = () => path__default.join(CliParser.getSwaggersOutputPath(), "patched");
937
1150
  static getGeneratedPublicFolder = () => `${CliParser.getOutputPath()}/generated-public`;
938
1151
  static getGeneratedAdminFolder = () => `${CliParser.getOutputPath()}/generated-admin`;
939
1152
  static getGeneratedSnippetsFolder = () => `${CliParser.getSnippetOutputPath()}/generated-snippets`;
940
1153
  static getServicePrefix = (servicePaths) => servicePaths[servicePaths.length - 1].split("/")[1];
941
- static iterateApi = async (api) => {
1154
+ static iterateApi = async (api, serviceName) => {
942
1155
  const apiBufferByTag = {};
1156
+ const apiArgumentsByTag = {};
1157
+ const classBufferByTag = {};
943
1158
  const dependenciesByTag = {};
944
1159
  const classImports = {};
945
1160
  let arrayDefinitions = [];
@@ -954,7 +1169,6 @@ class CodeGenerator {
954
1169
  }));
955
1170
  const sortedKeys = Array.from(sortedPathsByLength.keys());
956
1171
  const servicePrefix = CodeGenerator.getServicePrefix(sortedKeys);
957
- console.log("ServicePrefix", servicePrefix, ", Paths:", sortedKeys);
958
1172
  for (const [path2, operation] of sortedPathsByLength) {
959
1173
  const isAdminEndpoint = path2.indexOf("/admin/") > -1;
960
1174
  if (CliParser.isAdmin() && !isAdminEndpoint) {
@@ -986,13 +1200,15 @@ class CodeGenerator {
986
1200
  });
987
1201
  mapClassMethods[tag][classMethod] = `${path2} ${httpMethod}`;
988
1202
  snippetMap[path2] = snippetMap[path2] ? snippetMap[path2] : {};
989
- const description = endpoint.description;
1203
+ let description = endpoint.description;
1204
+ description = description || "";
1205
+ description = description.replace(/\s+/g, " ");
990
1206
  const responseClass = ParserUtils.get2xxResponse(endpoint.responses);
991
1207
  const { className, classGenName } = ParserUtils.generateClassName(tag);
992
1208
  classImports[className] = classImports[className] ? classImports[className] : {};
993
1209
  if (responseClass) {
994
1210
  const importTypeClass = ParserUtils.parseRefType(responseClass);
995
- classImports[className][importTypeClass] = `import { ${importTypeClass} } from './definitions/${importTypeClass}'`;
1211
+ classImports[className][importTypeClass] = `import { ${importTypeClass} } from '../definitions/${importTypeClass}.js'`;
996
1212
  }
997
1213
  if (responseClass && responseClass.endsWith("Array")) {
998
1214
  arrayDefinitions.push(responseClass);
@@ -1011,7 +1227,7 @@ class CodeGenerator {
1011
1227
  }
1012
1228
  ];
1013
1229
  }
1014
- const [generatedMethodString, importStatements, snippetString, snippetCurl] = templateMethod({
1230
+ const { methodImpl, methodParams, methodParamsNoTypes, importStatements } = templateMethod({
1015
1231
  classMethod,
1016
1232
  description,
1017
1233
  httpMethod,
@@ -1022,16 +1238,42 @@ class CodeGenerator {
1022
1238
  isFormUrlEncoded,
1023
1239
  responseClass
1024
1240
  });
1025
- apiBufferByTag[tag] = (apiBufferByTag[tag] || "") + generatedMethodString;
1241
+ classBufferByTag[tag] = (classBufferByTag[tag] || "") + methodImpl;
1242
+ const [generatedMethodString1, snippetMethod, snippetShell] = templateApiMethod({
1243
+ classMethod,
1244
+ description,
1245
+ httpMethod,
1246
+ path: pathWithBase,
1247
+ pathParams,
1248
+ bodyParams,
1249
+ responseClass,
1250
+ classGenName,
1251
+ methodParams,
1252
+ methodParamsNoTypes
1253
+ });
1254
+ apiBufferByTag[tag] = (apiBufferByTag[tag] || "") + generatedMethodString1;
1255
+ apiArgumentsByTag[tag] = (apiArgumentsByTag[tag] || "") + classMethod + ",";
1026
1256
  dependenciesByTag[tag] = dependenciesByTag[tag] ? [.../* @__PURE__ */ new Set([...importStatements, ...dependenciesByTag[tag]])] : [...new Set(importStatements)];
1257
+ const serviceNameTitle = ParserUtils.convertDashesToTitleCase(serviceName);
1258
+ const { apiGenName } = ParserUtils.generateApiName(tag);
1259
+ const resultSnippet = templateSdkSnippet(serviceNameTitle, apiGenName, snippetMethod);
1027
1260
  snippetMap[path2][httpMethod] = {
1028
- 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>`,
1029
- shell: snippetCurl
1261
+ web: resultSnippet,
1262
+ webGit: GIT_URL + `/sdk-${serviceName}/src/generated-public/${serviceName}/${apiGenName}.ts`,
1263
+ shell: snippetShell
1030
1264
  };
1031
1265
  }
1032
1266
  }
1033
1267
  arrayDefinitions = [...new Set(arrayDefinitions)];
1034
- return { apiBufferByTag, dependenciesByTag, classImports, arrayDefinitions, snippetMap };
1268
+ return {
1269
+ apiArgumentsByTag,
1270
+ apiBufferByTag,
1271
+ classBufferByTag,
1272
+ dependenciesByTag,
1273
+ classImports,
1274
+ arrayDefinitions,
1275
+ snippetMap
1276
+ };
1035
1277
  };
1036
1278
  static main = async (nameArray) => {
1037
1279
  const serviceName = nameArray[0];
@@ -1039,6 +1281,7 @@ class CodeGenerator {
1039
1281
  const parser = new SwaggerParser();
1040
1282
  const generatedFolder = CliParser.isAdmin() ? CodeGenerator.getGeneratedAdminFolder() : CodeGenerator.getGeneratedPublicFolder();
1041
1283
  const DIST_DIR = `${generatedFolder}/${serviceName}`;
1284
+ const DIST_ENDPOINTS_DIR = path__default.join(DIST_DIR, "endpoints");
1042
1285
  const DIST_DEFINITION_DIR = path__default.join(DIST_DIR, "definitions");
1043
1286
  const swaggerFilePath = `${CliParser.getSwaggersOutputPath()}/${swaggerFile}`;
1044
1287
  const swaggerPatchedFilePath = `${CodeGenerator.getPatchedDir()}/${swaggerFile}`;
@@ -1049,19 +1292,32 @@ class CodeGenerator {
1049
1292
  console.log("----------\nGenerating API:", { title: api.info.title, version: api.info.version });
1050
1293
  ParserUtils.mkdirIfNotExist(DIST_DIR);
1051
1294
  ParserUtils.mkdirIfNotExist(DIST_DEFINITION_DIR);
1052
- ParserUtils.writeVersionFile(DIST_DIR, "Version.ts", serviceName, api.info, BUILD_DATE);
1053
- const { apiBufferByTag, dependenciesByTag, classImports, arrayDefinitions, snippetMap } = await CodeGenerator.iterateApi(api);
1295
+ ParserUtils.mkdirIfNotExist(DIST_ENDPOINTS_DIR);
1296
+ ParserUtils.syncPackageVersion(api.info, CliParser.isAdmin());
1297
+ const { apiArgumentsByTag, apiBufferByTag, classBufferByTag, dependenciesByTag, classImports, arrayDefinitions, snippetMap } = await CodeGenerator.iterateApi(api, serviceName);
1054
1298
  if (CliParser.getSnippetOutputPath()) {
1055
1299
  ParserUtils.writeSnippetFile(CodeGenerator.getGeneratedSnippetsFolder(), api.info.title, JSON.stringify(snippetMap, null, 2));
1056
1300
  }
1057
1301
  const targetSrcFolder = `${CliParser.getOutputPath()}/`;
1058
- for (const tag in apiBufferByTag) {
1302
+ const apiList = [];
1303
+ for (const tag in classBufferByTag) {
1059
1304
  const { className, classGenName } = ParserUtils.generateClassName(tag);
1060
- const apiBuffer = apiBufferByTag[tag];
1305
+ const classBuffer = classBufferByTag[tag];
1061
1306
  const imports = [.../* @__PURE__ */ new Set([...dependenciesByTag[tag], ...Object.values(classImports[className])])];
1062
- ParserUtils.writeClassFile(DIST_DIR, classGenName, apiBuffer, imports);
1063
- indexImportsSet.add(ParserUtils.getRelativePathToWebSdkSrcFolder(path__default.join(DIST_DIR, `${classGenName}`), targetSrcFolder));
1307
+ const apiImports = [.../* @__PURE__ */ new Set([...dependenciesByTag[tag], ...Object.values(classImports[className])])];
1308
+ apiImports.push(`import { ${classGenName} } from './endpoints/${classGenName}.js'`);
1309
+ ParserUtils.writeClassFile(DIST_ENDPOINTS_DIR, classGenName, classBuffer, imports);
1310
+ const apiBuffer = apiBufferByTag[tag];
1311
+ const { apiGenName } = ParserUtils.generateApiName(tag);
1312
+ ParserUtils.writeApiFile(DIST_DIR, apiGenName, apiBuffer, apiImports, apiArgumentsByTag[tag]);
1313
+ apiList.push(apiGenName);
1314
+ indexImportsSet.add(ParserUtils.getRelativePathToWebSdkSrcFolder(path__default.join(DIST_ENDPOINTS_DIR, `${classGenName}`), targetSrcFolder));
1315
+ indexImportsSet.add(ParserUtils.getRelativePathToWebSdkSrcFolder(path__default.join(DIST_DIR, `${apiGenName}`), targetSrcFolder));
1064
1316
  }
1317
+ const serviceNameTitle = ParserUtils.convertDashesToTitleCase(serviceName);
1318
+ const apiIndexBuff = templateApiIndex(serviceName, serviceNameTitle, apiList);
1319
+ ParserUtils.writeApiMainFile(generatedFolder, serviceNameTitle, apiIndexBuff);
1320
+ indexImportsSet.add(ParserUtils.getRelativePathToWebSdkSrcFolder(path__default.join(generatedFolder, serviceNameTitle), targetSrcFolder));
1065
1321
  const duplicates = /* @__PURE__ */ new Map();
1066
1322
  const definitions = api?.components?.schemas || api.definitions;
1067
1323
  for (const ref in definitions) {
@@ -1106,6 +1362,22 @@ class SwaggerDownloader {
1106
1362
  fs.writeFileSync(destFile, "");
1107
1363
  return destFile;
1108
1364
  };
1365
+ static postSanitizeDownloadedFile = (filePath) => {
1366
+ const searchStr = ["%5B", "%5D", "%20", "%7B", "%7D"];
1367
+ fs.readFile(filePath, "utf8", (err, data) => {
1368
+ if (err)
1369
+ throw err;
1370
+ let result = data;
1371
+ searchStr.forEach((s) => {
1372
+ result = result.replace(new RegExp(s, "g"), " ");
1373
+ });
1374
+ fs.writeFile(filePath, result, "utf8", (err2) => {
1375
+ if (err2)
1376
+ throw err2;
1377
+ console.log("File updated successfully.");
1378
+ });
1379
+ });
1380
+ };
1109
1381
  static downloadFile = (targetFileName, url) => {
1110
1382
  const destFile = SwaggerDownloader.getDestFile(targetFileName);
1111
1383
  const file = fs.createWriteStream(destFile);
@@ -1113,6 +1385,7 @@ class SwaggerDownloader {
1113
1385
  response.pipe(file);
1114
1386
  file.on("finish", () => {
1115
1387
  file.close();
1388
+ SwaggerDownloader.postSanitizeDownloadedFile(destFile);
1116
1389
  console.log(`SwaggerDownload ${url} completed`);
1117
1390
  });
1118
1391
  });