@accelbyte/codegen 1.0.0-alpha.8 → 1.0.0-beta.3

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.
@@ -7,8 +7,8 @@ var zod = require('zod');
7
7
  var fs = require('fs');
8
8
  var path = require('path');
9
9
  var SwaggerParser = require('@apidevtools/swagger-parser');
10
- var _ = require('lodash');
11
10
  var fastJsonPatch = require('fast-json-patch');
11
+ var _ = require('lodash');
12
12
  var https = require('https');
13
13
 
14
14
  function _interopNamespaceDefault(e) {
@@ -101,15 +101,15 @@ const generateImports = (body, importStatements) => {
101
101
  ${importStatements.sort().join("\n")}`;
102
102
  };
103
103
  const templateClass = (className, body, importStatements) => `/**
104
- * DON'T EDIT THIS FILE, it is AUTO GENERATED
105
- */
106
- ${generateImports(body, importStatements)}
104
+ * DON'T EDIT THIS FILE, it is AUTO GENERATED
105
+ */
106
+ ${generateImports(body, importStatements)}
107
107
 
108
- export class ${className} {
109
- // @ts-ignore
110
- constructor(private axiosInstance: AxiosInstance, private namespace: string, private cache = false) {}
108
+ export class ${className} {
109
+ // @ts-ignore
110
+ constructor(private axiosInstance: AxiosInstance, private namespace: string, private cache = false) {}
111
111
  ${body}
112
- }
112
+ }
113
113
  `;
114
114
 
115
115
  const templateJsdocFile = (apiName, body) => `
@@ -120,7 +120,13 @@ ${body}
120
120
  \`\`\`
121
121
  `.replace(/, \)/g, ")").trim();
122
122
 
123
+ const SERVICE_PREFIXES = ["/iam/", "/gdpr/", "/sessionbrowser/", "/event/", "/admin/", "/public/"];
123
124
  class ParserUtils {
125
+ static generateClassName = (tag) => {
126
+ const className = _.upperFirst(_.camelCase(tag));
127
+ const classGenName = CliParser.isAdmin() ? className + "Admin$" : className + "$";
128
+ return { className, classGenName };
129
+ };
124
130
  static parseQueryParamAttributeDefault = (definition) => {
125
131
  const attrName = definition.name.slice(definition.name.lastIndexOf(".") + 1);
126
132
  const defaultValue = definition.type === "string" ? `'${definition.default}'` : definition.default;
@@ -261,6 +267,54 @@ class ParserUtils {
261
267
  }
262
268
  return parameters.filter((parameter) => parameter.in === "path");
263
269
  }
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) => {
288
+ path_ = path_.replace(prefix, "/");
289
+ });
290
+ path_ = path_.substring(1);
291
+ const arrLastWords = path_.split("}/");
292
+ let lastWords = arrLastWords[arrLastWords.length - 1];
293
+ lastWords = lastWords.split("/{")[0];
294
+ const listBeforeLastWords = [];
295
+ let foundParam = false;
296
+ const listByParams = [];
297
+ const pathElements = path_.split("/");
298
+ pathElements.slice().reverse().forEach((item) => {
299
+ if (item.indexOf("}") >= 0) {
300
+ foundParam = true;
301
+ let param = item.replace("{", "");
302
+ param = param.replace("}", "");
303
+ param = "By" + _.upperFirst(param);
304
+ listByParams.push(param);
305
+ } else if (!foundParam) {
306
+ if (lastWords.indexOf(item) === -1) {
307
+ listBeforeLastWords.push(item);
308
+ }
309
+ } else {
310
+ foundParam = false;
311
+ }
312
+ });
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
+ }
264
318
  static generateClassMethod({
265
319
  path: path2,
266
320
  endpoint,
@@ -279,8 +333,8 @@ class ParserUtils {
279
333
  }
280
334
  let classMethod = httpMethod + "/" + replacedIdsPath;
281
335
  classMethod = _.camelCase(classMethod);
282
- classMethod = classMethod.replace("PublicNamespacesByNamespace", "");
283
- classMethod = classMethod.replace("AdminNamespacesByNamespace", "Admin");
336
+ classMethod = classMethod.replace("PublicNamespacesByNamespace", "Ns");
337
+ classMethod = classMethod.replace("AdminNamespacesByNamespace", "AdminNs");
284
338
  const searchWord = "NamespacesByNamespace";
285
339
  const nsExistInsideMethod = classMethod.indexOf(searchWord) > 0 && classMethod.indexOf(searchWord) + searchWord.length < classMethod.length;
286
340
  const excludedClasses = ["Policies"];
@@ -314,6 +368,9 @@ class ParserUtils {
314
368
  const jsdocFile = templateJsdocFile(nameArray[0], apiBuffer);
315
369
  fs.writeFileSync(`${distDir}/docs/${nameArray[0]}.md`, jsdocFile);
316
370
  }
371
+ static writeSnippetFile(distDir, name, docBuffer) {
372
+ fs.writeFileSync(`${distDir}/${name}.json`, docBuffer);
373
+ }
317
374
  static writeDefinitionFile(distDir, name, buffer) {
318
375
  ParserUtils.mkdirIfNotExist(distDir);
319
376
  fs.writeFileSync(path.join(distDir, `${name}.ts`), ParserUtils.prependCopyrightHeader(buffer));
@@ -322,6 +379,16 @@ class ParserUtils {
322
379
  ParserUtils.mkdirIfNotExist(distDir);
323
380
  fs.writeFileSync(path.join(distDir, `all-${isAdminWebSdk ? "admin" : "public"}-imports.ts`), ParserUtils.prependCopyrightHeader(buffer));
324
381
  }
382
+ static writeVersionFile(distDir, fileName, serviceName, apiInfo, buildDate) {
383
+ const ver = apiInfo.version ? `'${apiInfo.version}'` : void 0;
384
+ fs.writeFileSync(`${distDir}/${fileName}`, `export default {
385
+ title: '${serviceName}',
386
+ name: '${apiInfo.title}',
387
+ version: ${ver},
388
+ buildDate: '${buildDate}'
389
+ }
390
+ `);
391
+ }
325
392
  static toCamelCase(str) {
326
393
  return str.split("/").map(function(word, index) {
327
394
  if (index === 0) {
@@ -355,6 +422,29 @@ class ParserUtils {
355
422
  }
356
423
  const swaggerContent = JSON.parse(fs.readFileSync(swaggerFilePath, "utf8"));
357
424
  const swaggerPatchFileContent = JSON.parse(fs.readFileSync(possibleSwaggerPatchFilePath, "utf8"));
425
+ for (const patchEntry of swaggerPatchFileContent) {
426
+ const segments = patchEntry.path.split("/").filter(Boolean);
427
+ let currentNode = swaggerContent;
428
+ let aggregatedPath = "";
429
+ for (let i = 0; i < segments.length; i++) {
430
+ const segment = segments[i];
431
+ aggregatedPath += `/${segment}`;
432
+ const effectiveSegment = segment.replace(/(~1)/g, "/").replace(/(~0)/g, "~");
433
+ if (!currentNode[effectiveSegment]) {
434
+ if (i + 1 === segments.length && patchEntry.op === "add") ; else {
435
+ throw new Error([
436
+ `JSON patch error: operation "${patchEntry.op}" on path "${aggregatedPath}" fails because the path doesn't exist in ${swaggerFilePath}. This may be caused by:
437
+ `,
438
+ "1. The related service has patched the service, so patch is no longer needed.",
439
+ "2. There is a breaking change on the service that causes the path to change.\n",
440
+ `In any case, revisit this file: "${possibleSwaggerPatchFilePath}", then try again.
441
+ `
442
+ ].join("\n"));
443
+ }
444
+ }
445
+ currentNode = currentNode[effectiveSegment];
446
+ }
447
+ }
358
448
  const { newDocument } = fastJsonPatch.applyPatch(swaggerContent, swaggerPatchFileContent);
359
449
  fs.writeFileSync(swaggerPatchedFilePath, JSON.stringify(newDocument, null, 2));
360
450
  }
@@ -463,50 +553,6 @@ zod.z.object({
463
553
  }).nullish()
464
554
  });
465
555
 
466
- const templateJsdocMethod = ({
467
- classMethod,
468
- httpMethod,
469
- path,
470
- pathParams,
471
- bodyParams,
472
- queryParams
473
- }) => {
474
- let jsdoc = "";
475
- let methodSignature = "";
476
- let newPath = path;
477
- for (const p of pathParams) {
478
- methodSignature += p.name + ", ";
479
- newPath = newPath.replace("{" + p.name + "}", `' + ${p.name} + '`);
480
- jsdoc += `
481
- * @pathParam '${p.name}${p.required ? "" : "?"}' - ${p.description}`;
482
- }
483
- let payloads = "";
484
- for (const p of bodyParams) {
485
- methodSignature += p.name + ", ";
486
- payloads += p.name;
487
- jsdoc += `
488
- * @payload '${p.name}${p.required ? "" : "?"}' - ${p.description} ${p?.schema?.properties ? "=> " + JSON.stringify(p.schema.properties) : ""}`;
489
- }
490
- for (const p of queryParams) {
491
- const optionalMark = p.required ? "" : "?";
492
- methodSignature += p.name + optionalMark + ", ";
493
- jsdoc += `
494
- * @queryParam '${p.name}${p.required ? "" : "?"}' - ${p.description}`;
495
- }
496
- let queryString = "";
497
- for (const p of queryParams) {
498
- queryString += `${p.name}=__&`;
499
- }
500
- queryString = queryString.length > 0 ? "?" + queryString : "";
501
- return `
502
- /**
503
- * ${httpMethod.toUpperCase()} '${newPath}${queryString}' ${payloads ? "payload: '" + payloads + "'" : ""}
504
- * ${jsdoc}
505
- */
506
- ${classMethod}(${methodSignature}): Promise
507
- `;
508
- };
509
-
510
556
  const templateMethod = ({
511
557
  classMethod,
512
558
  description,
@@ -521,6 +567,9 @@ const templateMethod = ({
521
567
  let methodSignature = "";
522
568
  let newPath = `'${path}'`;
523
569
  let dependencies = [];
570
+ let snippetString = "";
571
+ let snippetMethodSignature = "";
572
+ let snippetCurl = "";
524
573
  for (const pathParam of pathParams) {
525
574
  const type = ParserUtils.parseType(pathParam);
526
575
  if (pathParam.name !== "namespace") {
@@ -535,11 +584,20 @@ const templateMethod = ({
535
584
  }
536
585
  }
537
586
  }
587
+ 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'`;
538
588
  let dataType = null;
539
589
  if (httpMethod !== "get") {
540
590
  dataType = ParserUtils.parseBodyParamsType(bodyParams);
541
591
  dependencies = ParserUtils.parseBodyParamsImports(bodyParams);
542
592
  methodSignature += dataType ? `data: ${dataType},` : "";
593
+ const snippetParams = bodyParams?.map((ob) => {
594
+ return ` <span class='sn-purple'>${ob.name}</span>`;
595
+ });
596
+ snippetMethodSignature += snippetParams ? `data: { ${snippetParams} }` : "";
597
+ const curlParams = bodyParams?.map((ob) => {
598
+ return ` <span class='sn-purple'>"${ob.name}": ""</span>`;
599
+ });
600
+ snippetCurl += ` --data-raw { ${curlParams}}`;
543
601
  }
544
602
  const isAnyRequired = ParserUtils.isAnyQueryParamRequired(queryParams);
545
603
  const queryParamsType = queryParams.length ? `queryParams${isAnyRequired ? "" : "?"}: {${ParserUtils.parseQueryParamsType(queryParams)}}` : "";
@@ -575,20 +633,23 @@ const templateMethod = ({
575
633
  const methodName = httpMethod === "get" ? cachedFetchMethod : ["post", "put", "patch", "delete"].includes(httpMethod) ? classMethod : "";
576
634
  const methodGenerics = resolvedResponseClass !== "unknown" ? `<T = ${resolvedResponseClass}>` : "";
577
635
  const responseType = resolvedResponseClass !== "unknown" ? `T` : "unknown";
636
+ const generateMethodName = () => `${methodName}${methodGenerics}(${parameters}): Promise<${responseSyncType}<${responseType}>>`;
637
+ const generateSnippetMethodName = () => `${methodName}(${snippetMethodSignature})`;
638
+ snippetString += `${generateSnippetMethodName()}`;
578
639
  const responseSyncType = httpMethod === "get" ? "IResponseWithSync" : ["post", "put", "patch", "delete"].includes(httpMethod) ? "IResponse" : "";
579
640
  methodImpl = `${descriptionText}
580
- ${methodName}${methodGenerics}(${parameters}): Promise<${responseSyncType}<${responseType}>> {
641
+ ${generateMethodName()} {
581
642
  ${queryParamsDefault}
582
643
  const url = ${newPath} ${formPayloadString} ${isFileUpload ? "\n// TODO file upload not implemented" : ""}
583
644
  const resultPromise = this.axiosInstance.${httpMethod}(url, ${dataPayload})
584
645
 
585
- ${httpMethod === "get" ? `const res = () => Validate.responseType(() => resultPromise, ${resolvedResponseClassValidated})
646
+ ${httpMethod === "get" ? ` const res = () => Validate.responseType(() => resultPromise, ${resolvedResponseClassValidated})
586
647
 
587
648
  if (!this.cache) {
588
649
  return SdkCache.withoutCache(res)
589
650
  }
590
651
  const cacheKey = url + CodeGenUtil.hashCode(JSON.stringify({ params }))
591
- return SdkCache.withCache(cacheKey, res)` : ""}${["post", "put", "patch", "delete"].includes(httpMethod) ? `return Validate.responseType(() => resultPromise, ${resolvedResponseClassValidated})` : ""}
652
+ return SdkCache.withCache(cacheKey, res)` : ""}${["post", "put", "patch", "delete"].includes(httpMethod) ? ` return Validate.responseType(() => resultPromise, ${resolvedResponseClassValidated})` : ""}
592
653
  }
593
654
  `;
594
655
  if (!isGuardInvoked) {
@@ -601,7 +662,7 @@ ${httpMethod === "get" ? `const res = () => Validate.responseType(() => resultPr
601
662
  }
602
663
  `;
603
664
  }
604
- return [methodImpl, dependencies];
665
+ return [methodImpl, dependencies, snippetString, snippetCurl];
605
666
  };
606
667
 
607
668
  class TemplateZod {
@@ -617,32 +678,32 @@ class TemplateZod {
617
678
  }
618
679
  let imports = "";
619
680
  for (const cl of Array.from(this.importClasses).sort()) {
620
- imports += ` import { ${cl} } from './${cl}'
681
+ imports += `import { ${cl} } from './${cl}'
621
682
  `;
622
683
  }
623
684
  let exportedVariableString;
624
685
  let exportedTypeString;
625
686
  if (containsRecursiveType) {
626
687
  exportedVariableString = `
627
- export const ${fileName}: z.ZodType<${fileName}> = z.lazy(() =>
688
+ export const ${fileName}: z.ZodType<${fileName}> = z.lazy(() =>
628
689
  ${content.schemaString}
629
690
  )
630
691
  `;
631
692
  exportedTypeString = `
632
- export type ${fileName} = {
693
+ export interface ${fileName} {
633
694
  ${content.typeString}
634
- }
635
- `;
695
+ }
696
+ `;
636
697
  } else {
637
698
  exportedVariableString = `export const ${fileName} = ${content.schemaString}`;
638
- exportedTypeString = `export type ${fileName} = z.TypeOf<typeof ${fileName}>`;
699
+ exportedTypeString = `export interface ${fileName} extends z.TypeOf<typeof ${fileName}> {}`;
639
700
  }
640
701
  const template = `import { z } from 'zod'
641
- ${imports}
702
+ ${imports}
642
703
 
643
- ${exportedVariableString}
704
+ ${exportedVariableString}
644
705
 
645
- ${exportedTypeString}
706
+ ${exportedTypeString}
646
707
  `;
647
708
  return { buffer: template, duplicateFound: this.duplicateFound };
648
709
  };
@@ -799,11 +860,11 @@ class TemplateZodArray {
799
860
  render = (name) => {
800
861
  const cls = name.replace("Array", "");
801
862
  const template = `import { z } from 'zod'
802
- import { ${cls} } from './${cls}'
863
+ import { ${cls} } from './${cls}'
803
864
 
804
- export const ${name} = z.array(${cls})
865
+ export const ${name} = z.array(${cls})
805
866
 
806
- export type ${name} = z.TypeOf<typeof ${name}>
867
+ export interface ${name} extends z.TypeOf<typeof ${name}> {}
807
868
  `;
808
869
  return template;
809
870
  };
@@ -838,16 +899,18 @@ const extractEnumObject = (type, isRequired, enumArr) => {
838
899
  };
839
900
  };
840
901
 
902
+ const BUILD_DATE = new Date().toISOString();
841
903
  class CodeGenerator {
842
904
  static getPatchedDir = () => path.join(CliParser.getSwaggersOutputPath(), "patched");
843
905
  static getGeneratedPublicFolder = () => `${CliParser.getOutputPath()}/generated-public`;
844
906
  static getGeneratedAdminFolder = () => `${CliParser.getOutputPath()}/generated-admin`;
907
+ static getGeneratedSnippetsFolder = () => `${CliParser.getOutputPath()}/../../../../apps/api-explorer-app/src/generated-snippets`;
845
908
  static iterateApi = async (api) => {
846
909
  const apiBufferByTag = {};
847
- const jsDocApiBufferByTag = {};
848
910
  const dependenciesByTag = {};
849
911
  const classImports = {};
850
912
  let arrayDefinitions = [];
913
+ const snippetMap = {};
851
914
  for (const path2 in api.paths) {
852
915
  const isAdminEndpoint = path2.indexOf("/admin/") > -1;
853
916
  if (CliParser.isAdmin() && !isAdminEndpoint) {
@@ -869,9 +932,10 @@ class CodeGenerator {
869
932
  const description = endpoint.description;
870
933
  const isDeprecated = endpoint.deprecated;
871
934
  const responseClass = ParserUtils.get2xxResponse(endpoint.responses);
872
- const className = _.upperFirst(_.camelCase(tag));
935
+ const { className, classGenName } = ParserUtils.generateClassName(tag);
873
936
  classImports[className] = classImports[className] ? classImports[className] : {};
874
937
  if (!isDeprecated) {
938
+ snippetMap[path2] = snippetMap[path2] ? snippetMap[path2] : {};
875
939
  if (responseClass) {
876
940
  const importTypeClass = ParserUtils.parseRefType(responseClass);
877
941
  classImports[className][importTypeClass] = `import { ${importTypeClass} } from './definitions/${importTypeClass}'`;
@@ -883,12 +947,8 @@ class CodeGenerator {
883
947
  const queryParams = ParserUtils.filterQueryParameters(endpoint.parameters);
884
948
  const pathParams = ParserUtils.filterPathParams(endpoint.parameters);
885
949
  let bodyParams = ParserUtils.filterBodyParams(endpoint.parameters);
886
- const classMethod = ParserUtils.generateClassMethod({
887
- path: path2,
888
- endpoint,
889
- httpMethod,
890
- className
891
- });
950
+ const isForm = endpoint.consumes && endpoint.consumes[0] === "application/x-www-form-urlencoded";
951
+ const classMethod = ParserUtils.generateHumanReadableMethod({ path: path2, httpMethod, isForm });
892
952
  if (endpoint.requestBody) {
893
953
  bodyParams = [
894
954
  {
@@ -899,7 +959,7 @@ class CodeGenerator {
899
959
  ];
900
960
  }
901
961
  const pathWithBase = `${api.basePath ?? ""}${path2}`;
902
- const [generatedMethodString, importStatements] = templateMethod({
962
+ const [generatedMethodString, importStatements, snippetString, snippetCurl] = templateMethod({
903
963
  classMethod,
904
964
  description,
905
965
  httpMethod,
@@ -911,13 +971,16 @@ class CodeGenerator {
911
971
  responseClass
912
972
  });
913
973
  apiBufferByTag[tag] = (apiBufferByTag[tag] || "") + generatedMethodString;
914
- jsDocApiBufferByTag[tag] = (jsDocApiBufferByTag[tag] || "") + templateJsdocMethod({ classMethod, httpMethod, path: path2, pathParams, bodyParams, queryParams });
915
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
+ };
916
979
  }
917
980
  }
918
981
  }
919
982
  arrayDefinitions = [...new Set(arrayDefinitions)];
920
- return { apiBufferByTag, jsDocApiBufferByTag, dependenciesByTag, classImports, arrayDefinitions };
983
+ return { apiBufferByTag, dependenciesByTag, classImports, arrayDefinitions, snippetMap };
921
984
  };
922
985
  static main = async (nameArray) => {
923
986
  const serviceName = nameArray[0];
@@ -937,13 +1000,16 @@ class CodeGenerator {
937
1000
  ParserUtils.mkdirIfNotExist(DIST_DIR);
938
1001
  ParserUtils.mkdirIfNotExist(DIST_DOCS_DIR);
939
1002
  ParserUtils.mkdirIfNotExist(DIST_DEFINITION_DIR);
940
- const { apiBufferByTag, dependenciesByTag, classImports, arrayDefinitions } = await CodeGenerator.iterateApi(api);
1003
+ ParserUtils.writeVersionFile(DIST_DIR, "Version.ts", serviceName, api.info, BUILD_DATE);
1004
+ const { apiBufferByTag, dependenciesByTag, classImports, arrayDefinitions, snippetMap } = await CodeGenerator.iterateApi(api);
1005
+ if (!CliParser.isAdmin()) {
1006
+ ParserUtils.writeSnippetFile(CodeGenerator.getGeneratedSnippetsFolder(), api.info.title, JSON.stringify(snippetMap, null, 2));
1007
+ }
941
1008
  const targetSrcFolder = `${CliParser.getOutputPath()}/`;
942
1009
  for (const tag in apiBufferByTag) {
943
- const className = _.upperFirst(_.camelCase(tag));
1010
+ const { className, classGenName } = ParserUtils.generateClassName(tag);
944
1011
  const apiBuffer = apiBufferByTag[tag];
945
1012
  const imports = [.../* @__PURE__ */ new Set([...dependenciesByTag[tag], ...Object.values(classImports[className])])];
946
- const classGenName = CliParser.isAdmin() ? className + "Admin$" : className + "$";
947
1013
  ParserUtils.writeClassFile(DIST_DIR, classGenName, apiBuffer, imports);
948
1014
  indexImportsSet.add(ParserUtils.getRelativePathToWebSdkSrcFolder(path.join(DIST_DIR, `${classGenName}`), targetSrcFolder));
949
1015
  }