@qlover/create-app 0.6.1 → 0.6.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.
Files changed (68) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/templates/react-app/README.en.md +257 -0
  5. package/dist/templates/react-app/README.md +29 -231
  6. package/dist/templates/react-app/config/IOCIdentifier.ts +13 -0
  7. package/dist/templates/react-app/docs/en/bootstrap.md +562 -0
  8. package/dist/templates/react-app/docs/en/development-guide.md +523 -0
  9. package/dist/templates/react-app/docs/en/env.md +482 -0
  10. package/dist/templates/react-app/docs/en/global.md +509 -0
  11. package/dist/templates/react-app/docs/en/i18n.md +268 -0
  12. package/dist/templates/react-app/docs/en/index.md +173 -0
  13. package/dist/templates/react-app/docs/en/ioc.md +424 -0
  14. package/dist/templates/react-app/docs/en/project-structure.md +434 -0
  15. package/dist/templates/react-app/docs/en/request.md +425 -0
  16. package/dist/templates/react-app/docs/en/router.md +404 -0
  17. package/dist/templates/react-app/docs/en/store.md +321 -0
  18. package/dist/templates/react-app/docs/en/theme.md +424 -0
  19. package/dist/templates/react-app/docs/en/typescript-guide.md +473 -0
  20. package/dist/templates/react-app/docs/zh/bootstrap.md +562 -0
  21. package/dist/templates/react-app/docs/zh/development-guide.md +523 -0
  22. package/dist/templates/react-app/docs/zh/env.md +479 -0
  23. package/dist/templates/react-app/docs/zh/global.md +511 -0
  24. package/dist/templates/react-app/docs/zh/i18n.md +268 -0
  25. package/dist/templates/react-app/docs/zh/index.md +173 -0
  26. package/dist/templates/react-app/docs/zh/ioc.md +422 -0
  27. package/dist/templates/react-app/docs/zh/project-structure.md +434 -0
  28. package/dist/templates/react-app/docs/zh/request.md +429 -0
  29. package/dist/templates/react-app/docs/zh/router.md +408 -0
  30. package/dist/templates/react-app/docs/zh/store.md +321 -0
  31. package/dist/templates/react-app/docs/zh/theme.md +424 -0
  32. package/dist/templates/react-app/docs/zh/typescript-guide.md +473 -0
  33. package/dist/templates/react-app/package.json +2 -2
  34. package/dist/templates/react-app/src/base/apis/AiApi.ts +10 -5
  35. package/dist/templates/react-app/src/base/apis/feApi/FeApiAdapter.ts +1 -1
  36. package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +1 -1
  37. package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +10 -17
  38. package/dist/templates/react-app/src/base/apis/userApi/UserApiAdapter.ts +1 -1
  39. package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +2 -1
  40. package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +7 -5
  41. package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +3 -2
  42. package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +33 -0
  43. package/dist/templates/react-app/src/base/cases/RequestLogger.ts +1 -1
  44. package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +2 -2
  45. package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +1 -1
  46. package/dist/templates/react-app/src/base/services/RouteService.ts +5 -2
  47. package/dist/templates/react-app/src/base/services/UserService.ts +8 -10
  48. package/dist/templates/react-app/src/core/IOC.ts +73 -83
  49. package/dist/templates/react-app/src/core/bootstraps/BootstrapApp.ts +52 -4
  50. package/dist/templates/react-app/src/core/bootstraps/{index.ts → BootstrapsRegistry.ts} +2 -3
  51. package/dist/templates/react-app/src/core/registers/IocRegisterImpl.ts +25 -0
  52. package/dist/templates/react-app/src/core/registers/RegisterCommon.ts +11 -17
  53. package/dist/templates/react-app/src/core/registers/RegisterControllers.ts +10 -4
  54. package/dist/templates/react-app/src/core/registers/RegisterGlobals.ts +6 -15
  55. package/dist/templates/react-app/src/main.tsx +2 -5
  56. package/dist/templates/react-app/src/styles/css/antd-themes/dark.css +3 -1
  57. package/dist/templates/react-app/src/styles/css/antd-themes/index.css +1 -1
  58. package/dist/templates/react-app/src/styles/css/antd-themes/pink.css +6 -1
  59. package/dist/templates/react-app/src/styles/css/page.css +1 -1
  60. package/dist/templates/react-app/src/uikit/controllers/JSONStorageController.ts +1 -1
  61. package/dist/templates/react-app/tsconfig.app.json +2 -1
  62. package/dist/templates/react-app/tsconfig.node.json +2 -1
  63. package/package.json +2 -2
  64. package/dist/templates/react-app/src/base/port/ApiTransactionInterface.ts +0 -7
  65. package/dist/templates/react-app/src/base/port/RequestCatcherInterface.ts +0 -12
  66. package/dist/templates/react-app/src/core/bootstrap.ts +0 -58
  67. package/dist/templates/react-app/src/core/registers/RegisterApi.ts +0 -5
  68. package/dist/templates/react-app/src/core/registers/index.ts +0 -32
package/CHANGELOG.md CHANGED
@@ -1,5 +1,66 @@
1
1
  # @qlover/create-app
2
2
 
3
+ ## 0.6.3
4
+
5
+ ### Patch Changes
6
+
7
+ #### ✨ Features
8
+
9
+ - **react-app:** add comprehensive documentation for Bootstrap, environment variable injection, global variable injection, and IOC container ([6c9b8e2](https://github.com/qlover/fe-base/commit/6c9b8e220b4b246f4593f25bc6830381157c0744)) ([#473](https://github.com/qlover/fe-base/pull/473))
10
+ - Introduced detailed documentation for the Bootstrap system, explaining its purpose, implementation, and advantages in managing application initialization logic.
11
+ - Added sections on environment variable injection, outlining the process and configuration for managing different environments.
12
+ - Documented the global variable injection mechanism, detailing how core services can be accessed globally within the application.
13
+ - Included a thorough explanation of the IOC container, its implementation, and best practices for dependency management.
14
+
15
+ These additions aim to enhance developer understanding and usability of the framework, promoting best practices in application architecture.
16
+
17
+ - **react-app:** enhance project documentation and update scripts ([2a07fc8](https://github.com/qlover/fe-base/commit/2a07fc82647b4965121df9c6d70cd5d1841ad2d4)) ([#473](https://github.com/qlover/fe-base/pull/473))
18
+ - Added a comprehensive English README file to provide an overview of the React app template, including features, requirements, and project structure.
19
+ - Introduced a new documentation structure with detailed guides on Bootstrap, environment variable injection, global variable injection, and the IOC container.
20
+ - Updated the existing Chinese README to include links to the new English documentation and expanded sections on development guidelines and core functionalities.
21
+ - Modified the package.json to include additional directories in the Prettier configuration for improved formatting.
22
+ - Enhanced CSS files for better readability and maintainability by formatting CSS variables.
23
+
24
+ These changes aim to improve developer experience and provide clearer guidance on using the React app template effectively.
25
+
26
+ #### ♻️ Refactors
27
+
28
+ - **react-app:** enhance build process and update TypeScript configurations ([c2065b9](https://github.com/qlover/fe-base/commit/c2065b9ac847fb53776b871fc5bf62ac948801b6)) ([#473](https://github.com/qlover/fe-base/pull/473))
29
+ - Updated the build script in package.json to run linting before building the project.
30
+ - Modified tsconfig files to exclude node_modules and dist directories, improving TypeScript compilation efficiency.
31
+ - Introduced a new IOCIdentifier.ts file to centralize dependency identifiers.
32
+ - Refactored imports across various files to utilize the new IOCIdentifier.
33
+ - Removed obsolete ApiTransactionInterface and RequestCatcherInterface files to streamline the codebase.
34
+ - Implemented a new InversifyContainer class for better dependency injection management.
35
+
36
+ These changes aim to improve the overall structure, maintainability, and build process of the react-app template.
37
+
38
+ ## 0.6.2
39
+
40
+ ### Patch Changes
41
+
42
+ #### ✨ Features
43
+
44
+ - **react-app:** add comprehensive documentation for Bootstrap, environment variable injection, global variable injection, and IOC container ([6c9b8e2](https://github.com/qlover/fe-base/commit/6c9b8e220b4b246f4593f25bc6830381157c0744)) ([#472](https://github.com/qlover/fe-base/pull/472))
45
+ - Introduced detailed documentation for the Bootstrap system, explaining its purpose, implementation, and advantages in managing application initialization logic.
46
+ - Added sections on environment variable injection, outlining the process and configuration for managing different environments.
47
+ - Documented the global variable injection mechanism, detailing how core services can be accessed globally within the application.
48
+ - Included a thorough explanation of the IOC container, its implementation, and best practices for dependency management.
49
+
50
+ These additions aim to enhance developer understanding and usability of the framework, promoting best practices in application architecture.
51
+
52
+ #### ♻️ Refactors
53
+
54
+ - **react-app:** enhance build process and update TypeScript configurations ([c2065b9](https://github.com/qlover/fe-base/commit/c2065b9ac847fb53776b871fc5bf62ac948801b6)) ([#472](https://github.com/qlover/fe-base/pull/472))
55
+ - Updated the build script in package.json to run linting before building the project.
56
+ - Modified tsconfig files to exclude node_modules and dist directories, improving TypeScript compilation efficiency.
57
+ - Introduced a new IOCIdentifier.ts file to centralize dependency identifiers.
58
+ - Refactored imports across various files to utilize the new IOCIdentifier.
59
+ - Removed obsolete ApiTransactionInterface and RequestCatcherInterface files to streamline the codebase.
60
+ - Implemented a new InversifyContainer class for better dependency injection management.
61
+
62
+ These changes aim to improve the overall structure, maintainability, and build process of the react-app template.
63
+
3
64
  ## 0.6.1
4
65
 
5
66
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -8,4 +8,4 @@ ${t}`,dn=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),_n=Objec
8
8
  `))this.#e+=Math.max(1,Math.ceil(Ye(D,{countAnsiEscapeCodes:!0})/t))}get isEnabled(){return this.#a&&!this.#F}set isEnabled(t){if(typeof t!="boolean")throw new TypeError("The `isEnabled` option must be a boolean");this.#a=t}get isSilent(){return this.#F}set isSilent(t){if(typeof t!="boolean")throw new TypeError("The `isSilent` option must be a boolean");this.#F=t}frame(){let t=Date.now();(this.#i===-1||t-this.#c>=this.interval)&&(this.#i=++this.#i%this.#D.frames.length,this.#c=t);let{frames:r}=this.#D,u=r[this.#i];this.color&&(u=d[this.color](u));let i=typeof this.#s=="string"&&this.#s!==""?this.#s+" ":"",D=typeof this.text=="string"?" "+this.text:"",o=typeof this.#o=="string"&&this.#o!==""?" "+this.#o:"";return i+u+D+o}clear(){if(!this.#a||!this.#u.isTTY)return this;this.#u.cursorTo(0);for(let t=0;t<this.#n;t++)t>0&&this.#u.moveCursor(0,-1),this.#u.clearLine(1);return(this.#l||this.lastIndent!==this.#l)&&this.#u.cursorTo(this.#l),this.lastIndent=this.#l,this.#n=0,this}render(){return this.#F?this:(this.clear(),this.#u.write(this.frame()),this.#n=this.#e,this)}start(t){return t&&(this.text=t),this.#F?this:this.#a?this.isSpinning?this:(this.#t.hideCursor&&$e.hide(this.#u),this.#t.discardStdin&&V.default.stdin.isTTY&&(this.#r=!0,Je.start()),this.render(),this.#f=setInterval(this.render.bind(this),this.interval),this):(this.text&&this.#u.write(`- ${this.text}
9
9
  `),this)}stop(){return this.#a?(clearInterval(this.#f),this.#f=void 0,this.#i=0,this.clear(),this.#t.hideCursor&&$e.show(this.#u),this.#t.discardStdin&&V.default.stdin.isTTY&&this.#r&&(Je.stop(),this.#r=!1),this):this}succeed(t){return this.stopAndPersist({symbol:H.success,text:t})}fail(t){return this.stopAndPersist({symbol:H.error,text:t})}warn(t){return this.stopAndPersist({symbol:H.warning,text:t})}info(t){return this.stopAndPersist({symbol:H.info,text:t})}stopAndPersist(t={}){if(this.#F)return this;let r=t.prefixText??this.#s,u=this.#E(r," "),i=t.symbol??" ",D=t.text??this.text,s=typeof D=="string"?(i?" ":"")+D:"",a=t.suffixText??this.#o,f=this.#g(a," "),p=u+i+s+f+`
10
10
  `;return this.stop(),this.#u.write(p),this}};function tr(e){return new Ze(e)}async function rr(e,t){let r=typeof e=="function",u=typeof e.then=="function";if(!r&&!u)throw new TypeError("Parameter `action` must be a Function or a Promise");let{successText:i,failText:D}=typeof t=="object"?t:{successText:void 0,failText:void 0},o=tr(t).start();try{let a=await(r?e(o):e);return o.succeed(i===void 0?void 0:typeof i=="string"?i:i(a)),a}catch(s){throw o.fail(D===void 0?void 0:typeof D=="string"?D:D(s)),s}}var HD=require("fs");var P=require("path"),me=require("fs"),cr=h(lr(),1),Dt=require("fs");var de=require("fs"),X=class{static ensureDir(t){(0,de.existsSync)(t)||(0,de.mkdirSync)(t,{recursive:!0})}};var{copyFile:rs,stat:us}=Dt.promises,_e=class e{constructor(t,r=e.IGNORE_FILE){this.ignoreTargetPath=t;this.ignoreFile=r}static IGNORE_FILE=".gitignore.template";getIg(t=this.ignoreTargetPath){let r=(0,P.join)(t,this.ignoreFile);if(!(0,me.existsSync)(r))return;let D=(0,me.readFileSync)(r,"utf8").split(`
11
- `).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,cr.default)().add(D)}async copyFiles(t,r,u,i){let D=await Dt.promises.readdir(t);await Promise.all(D.map(async o=>{let s=(0,P.join)(t,o),a=(0,P.join)(r,o);if(u&&u.ignores(o))return;if(X.ensureDir((0,P.dirname)(a)),(await us(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await rs(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){X.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var L=require("fs"),kD=h(LD(),1),xe=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return(0,L.readFileSync)(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){(0,L.writeFileSync)(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,kD.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return(0,L.existsSync)(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var $D=["pack-app"],ye=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!(0,HD.existsSync)(r))throw new Error("template path not exit");this.ora=rr,this.context=new UD.FeScriptContext(t),this.subPackages=["node-lib","react-app"],this.copyer=new _e((0,v.join)(this.context.options.configsRootPath,"_common")),this.compose=new xe}get logger(){return this.context.logger}async steps(t){try{return await WD.default.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return $D.includes(t)}async getGeneratorContext(){let t=Ot(this.subPackages,$D),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=wt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=(0,v.join)(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:(0,v.join)(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:(0,v.join)(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=(0,v.join)(D,o),a=(0,v.join)(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var bt={name:"@qlover/create-app",version:"0.6.1",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function Ul(){let e=new YD.Command;return e.version(bt.version,"-v, --version","Show version").description(bt.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function Wl(){let{dryRun:e,verbose:t,...r}=Ul(),u=(0,vt.resolve)("./templates"),i=(0,vt.resolve)("./configs");(0,xt.existsSync)(u)||(console.error("Template is empty!"),process.exit(1)),(0,xt.existsSync)(i)||(console.error("Configs is empty!"),process.exit(1)),await new ye({dryRun:e,verbose:t,options:{...r,templateRootPath:u,configsRootPath:i}}).generate()}Wl().catch(e=>{console.error(e),process.exit(1)});
11
+ `).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,cr.default)().add(D)}async copyFiles(t,r,u,i){let D=await Dt.promises.readdir(t);await Promise.all(D.map(async o=>{let s=(0,P.join)(t,o),a=(0,P.join)(r,o);if(u&&u.ignores(o))return;if(X.ensureDir((0,P.dirname)(a)),(await us(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await rs(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){X.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var L=require("fs"),kD=h(LD(),1),xe=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return(0,L.readFileSync)(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){(0,L.writeFileSync)(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,kD.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return(0,L.existsSync)(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var $D=["pack-app"],ye=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!(0,HD.existsSync)(r))throw new Error("template path not exit");this.ora=rr,this.context=new UD.FeScriptContext(t),this.subPackages=["node-lib","react-app"],this.copyer=new _e((0,v.join)(this.context.options.configsRootPath,"_common")),this.compose=new xe}get logger(){return this.context.logger}async steps(t){try{return await WD.default.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return $D.includes(t)}async getGeneratorContext(){let t=Ot(this.subPackages,$D),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=wt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=(0,v.join)(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:(0,v.join)(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:(0,v.join)(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=(0,v.join)(D,o),a=(0,v.join)(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var bt={name:"@qlover/create-app",version:"0.6.3",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function Ul(){let e=new YD.Command;return e.version(bt.version,"-v, --version","Show version").description(bt.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function Wl(){let{dryRun:e,verbose:t,...r}=Ul(),u=(0,vt.resolve)("./templates"),i=(0,vt.resolve)("./configs");(0,xt.existsSync)(u)||(console.error("Template is empty!"),process.exit(1)),(0,xt.existsSync)(i)||(console.error("Configs is empty!"),process.exit(1)),await new ye({dryRun:e,verbose:t,options:{...r,templateRootPath:u,configsRootPath:i}}).generate()}Wl().catch(e=>{console.error(e),process.exit(1)});
package/dist/index.js CHANGED
@@ -8,4 +8,4 @@ ${t}`,cn=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),fn=Objec
8
8
  `))this.#e+=Math.max(1,Math.ceil(Le(D,{countAnsiEscapeCodes:!0})/t))}get isEnabled(){return this.#a&&!this.#F}set isEnabled(t){if(typeof t!="boolean")throw new TypeError("The `isEnabled` option must be a boolean");this.#a=t}get isSilent(){return this.#F}set isSilent(t){if(typeof t!="boolean")throw new TypeError("The `isSilent` option must be a boolean");this.#F=t}frame(){let t=Date.now();(this.#i===-1||t-this.#c>=this.interval)&&(this.#i=++this.#i%this.#D.frames.length,this.#c=t);let{frames:r}=this.#D,u=r[this.#i];this.color&&(u=E[this.color](u));let i=typeof this.#s=="string"&&this.#s!==""?this.#s+" ":"",D=typeof this.text=="string"?" "+this.text:"",o=typeof this.#o=="string"&&this.#o!==""?" "+this.#o:"";return i+u+D+o}clear(){if(!this.#a||!this.#u.isTTY)return this;this.#u.cursorTo(0);for(let t=0;t<this.#n;t++)t>0&&this.#u.moveCursor(0,-1),this.#u.clearLine(1);return(this.#l||this.lastIndent!==this.#l)&&this.#u.cursorTo(this.#l),this.lastIndent=this.#l,this.#n=0,this}render(){return this.#F?this:(this.clear(),this.#u.write(this.frame()),this.#n=this.#e,this)}start(t){return t&&(this.text=t),this.#F?this:this.#a?this.isSpinning?this:(this.#t.hideCursor&&je.hide(this.#u),this.#t.discardStdin&&ce.stdin.isTTY&&(this.#r=!0,We.start()),this.render(),this.#f=setInterval(this.render.bind(this),this.interval),this):(this.text&&this.#u.write(`- ${this.text}
9
9
  `),this)}stop(){return this.#a?(clearInterval(this.#f),this.#f=void 0,this.#i=0,this.clear(),this.#t.hideCursor&&je.show(this.#u),this.#t.discardStdin&&ce.stdin.isTTY&&this.#r&&(We.stop(),this.#r=!1),this):this}succeed(t){return this.stopAndPersist({symbol:k.success,text:t})}fail(t){return this.stopAndPersist({symbol:k.error,text:t})}warn(t){return this.stopAndPersist({symbol:k.warning,text:t})}info(t){return this.stopAndPersist({symbol:k.info,text:t})}stopAndPersist(t={}){if(this.#F)return this;let r=t.prefixText??this.#s,u=this.#E(r," "),i=t.symbol??" ",D=t.text??this.text,s=typeof D=="string"?(i?" ":"")+D:"",a=t.suffixText??this.#o,f=this.#g(a," "),p=u+i+s+f+`
10
10
  `;return this.stop(),this.#u.write(p),this}};function zt(e){return new He(e)}async function Kt(e,t){let r=typeof e=="function",u=typeof e.then=="function";if(!r&&!u)throw new TypeError("Parameter `action` must be a Function or a Promise");let{successText:i,failText:D}=typeof t=="object"?t:{successText:void 0,failText:void 0},o=zt(t).start();try{let a=await(r?e(o):e);return o.succeed(i===void 0?void 0:typeof i=="string"?i:i(a)),a}catch(s){throw o.fail(D===void 0?void 0:typeof D=="string"?D:D(s)),s}}import{existsSync as Hl}from"fs";var Dr=ue(ir(),1);import{dirname as Jn,join as Ze}from"path";import{existsSync as Zn,readFileSync as Qn}from"fs";import{promises as nr}from"fs";import{existsSync as Kn,mkdirSync as Xn}from"fs";var H=class{static ensureDir(t){Kn(t)||Xn(t,{recursive:!0})}};var{copyFile:es,stat:ts}=nr,Ce=class e{constructor(t,r=e.IGNORE_FILE){this.ignoreTargetPath=t;this.ignoreFile=r}static IGNORE_FILE=".gitignore.template";getIg(t=this.ignoreTargetPath){let r=Ze(t,this.ignoreFile);if(!Zn(r))return;let D=Qn(r,"utf8").split(`
11
- `).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,Dr.default)().add(D)}async copyFiles(t,r,u,i){let D=await nr.readdir(t);await Promise.all(D.map(async o=>{let s=Ze(t,o),a=Ze(r,o);if(u&&u.ignores(o))return;if(H.ensureDir(Jn(a)),(await ts(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await es(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){H.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var ID=ue(qD(),1);import{readFileSync as Ll,writeFileSync as kl,existsSync as $l}from"fs";var me=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return Ll(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){kl(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,ID.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return $l(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var jD=["pack-app"],Be=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!Hl(r))throw new Error("template path not exit");this.ora=Kt,this.context=new Ul(t),this.subPackages=["node-lib","react-app"],this.copyer=new Ce(N(this.context.options.configsRootPath,"_common")),this.compose=new me}get logger(){return this.context.logger}async steps(t){try{return await Wl.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return jD.includes(t)}async getGeneratorContext(){let t=_t(this.subPackages,jD),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=mt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=N(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:N(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:N(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=N(D,o),a=N(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var Et={name:"@qlover/create-app",version:"0.6.1",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function zl(){let e=new Vl;return e.version(Et.version,"-v, --version","Show version").description(Et.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function Kl(){let{dryRun:e,verbose:t,...r}=zl(),u=ND("./templates"),i=ND("./configs");GD(u)||(console.error("Template is empty!"),process.exit(1)),GD(i)||(console.error("Configs is empty!"),process.exit(1)),await new Be({dryRun:e,verbose:t,options:{...r,templateRootPath:u,configsRootPath:i}}).generate()}Kl().catch(e=>{console.error(e),process.exit(1)});
11
+ `).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,Dr.default)().add(D)}async copyFiles(t,r,u,i){let D=await nr.readdir(t);await Promise.all(D.map(async o=>{let s=Ze(t,o),a=Ze(r,o);if(u&&u.ignores(o))return;if(H.ensureDir(Jn(a)),(await ts(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await es(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){H.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var ID=ue(qD(),1);import{readFileSync as Ll,writeFileSync as kl,existsSync as $l}from"fs";var me=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return Ll(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){kl(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,ID.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return $l(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var jD=["pack-app"],Be=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!Hl(r))throw new Error("template path not exit");this.ora=Kt,this.context=new Ul(t),this.subPackages=["node-lib","react-app"],this.copyer=new Ce(N(this.context.options.configsRootPath,"_common")),this.compose=new me}get logger(){return this.context.logger}async steps(t){try{return await Wl.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return jD.includes(t)}async getGeneratorContext(){let t=_t(this.subPackages,jD),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=mt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=N(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:N(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:N(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=N(D,o),a=N(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var Et={name:"@qlover/create-app",version:"0.6.3",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function zl(){let e=new Vl;return e.version(Et.version,"-v, --version","Show version").description(Et.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function Kl(){let{dryRun:e,verbose:t,...r}=zl(),u=ND("./templates"),i=ND("./configs");GD(u)||(console.error("Template is empty!"),process.exit(1)),GD(i)||(console.error("Configs is empty!"),process.exit(1)),await new Be({dryRun:e,verbose:t,options:{...r,templateRootPath:u,configsRootPath:i}}).generate()}Kl().catch(e=>{console.error(e),process.exit(1)});
@@ -0,0 +1,257 @@
1
+ # FE-React Template
2
+
3
+ A modern React frontend project template with integrated practical features and best practices.
4
+
5
+ [中文](./README.md)
6
+
7
+ ## 🌟 Features
8
+
9
+ - 🚀 Fast development experience with Vite
10
+ - 🎨 Theme system integrated with Tailwind CSS
11
+ - 🌍 Comprehensive internationalization support (Chinese & English)
12
+ - 🔄 TypeScript-based IOC container
13
+ - 📡 Unified API request handling
14
+ - 🎮 Controller pattern for state management
15
+ - 📦 Package management with pnpm
16
+ - 🧪 Built-in test support
17
+
18
+ ## 🔧 Requirements
19
+
20
+ - Node.js >= 16
21
+ - pnpm >= 8.0
22
+
23
+ ## 📁 Project Structure
24
+
25
+ ```tree
26
+ ├── config/ # Configuration directory
27
+ │ ├── app.router.json # Router page configuration
28
+ │ ├── common.ts # Application common configuration
29
+ │ ├── app.router.json # Router configuration
30
+ │ ├── i18n.ts # I18n configuration
31
+ │ └── theme.json # Theme configuration
32
+ ├── lib/ # Common library directory
33
+ ├── public/ # Static resources directory
34
+ ├── src/
35
+ │ ├── base/ # Base code
36
+ │ ├── core/ # Core code
37
+ │ ├── pages/ # Page components
38
+ │ ├── services/ # Service layer
39
+ │ └── uikit/ # UI component library
40
+ └── vite.config.ts # Vite configuration file
41
+ ```
42
+
43
+ ## 🚀 Quick Start
44
+
45
+ ### Install Dependencies
46
+
47
+ ```bash
48
+ pnpm install
49
+ ```
50
+
51
+ ### Development Mode
52
+
53
+ ```bash
54
+ pnpm dev
55
+ ```
56
+
57
+ ### Build Project
58
+
59
+ ```bash
60
+ pnpm build
61
+ ```
62
+
63
+ ### Run Tests
64
+
65
+ ```bash
66
+ pnpm test
67
+ ```
68
+
69
+ ## 📚 Documentation Guide
70
+
71
+ The project provides detailed development documentation covering all major features and best practices:
72
+
73
+ ### Basic Documentation
74
+ - [Project Overview](./docs/en/index.md) - Project introduction and quick start guide
75
+ - [Project Structure](./docs/en/project-structure.md) - Detailed project directory structure explanation
76
+ - [Development Guide](./docs/en/development-guide.md) - Project development specifications and best practices
77
+ - [Environment Configuration](./docs/en/env.md) - Environment variables and configuration management
78
+ - [Global Configuration](./docs/en/global.md) - Application global configuration and settings
79
+
80
+ ### Core Features
81
+ - [Bootstrap Process](./docs/en/bootstrap.md) - Application startup process and lifecycle management
82
+ - [IOC Container](./docs/en/ioc.md) - Dependency injection system usage guide
83
+ - [Router Management](./docs/en/router.md) - Router configuration and page navigation
84
+ - [State Management](./docs/en/store.md) - Application state management solution
85
+ - [Request Handling](./docs/en/request.md) - API request handling mechanism
86
+
87
+ ### Feature Extensions
88
+ - [Internationalization](./docs/en/i18n.md) - Multi-language support and translation management
89
+ - [Theme System](./docs/en/theme.md) - Theme configuration and dark mode support
90
+ - [TypeScript Guide](./docs/en/typescript-guide.md) - TypeScript usage specifications and best practices
91
+
92
+ ## 🔨 Core Features
93
+
94
+ ### IOC Container
95
+
96
+ - TypeScript-based dependency injection system
97
+ - Support for automatic service registration and dependency management
98
+ - Complete type inference
99
+
100
+ ### Environment Configuration
101
+
102
+ [Vite Environment Variables and Modes](https://vitejs.dev/guide/env-and-mode.html)
103
+
104
+ `vite dev` default NODE_ENV is 'development', it will load possible `.env[mode]` files, e.g., .env.local -> .env
105
+
106
+ `vite build` default NODE_ENV is 'production', it will load possible `.env[mode]` files, e.g., .env.production -> .env
107
+
108
+ Node.js NODE_ENV only supports development, production, test
109
+
110
+ This is completely different from mode in vite. Mode can be specified with `--mode` to load different env configurations.
111
+
112
+ For example:
113
+
114
+ ```bash
115
+ vite dev --mode staging # Load .env.staging
116
+ vite dev --mode local # Load .env.local
117
+ ```
118
+
119
+ ### Internationalization Support
120
+
121
+ - Complete i18n solution based on i18next
122
+ - Support for Chinese (zh) and English (en) language switching
123
+ - Automatic i18n resource generation based on TypeScript comments
124
+ - URL path language detection and switching
125
+ - Built-in language switcher component
126
+
127
+ ### Theme System
128
+
129
+ - Theme configuration based on Tailwind CSS
130
+ - Support for dark/light mode
131
+ - Custom design token system
132
+
133
+ ### API Integration
134
+
135
+ The project provides a powerful API request handling mechanism based on a plugin architecture:
136
+
137
+ #### Request Controller
138
+
139
+ Use `RequestController` to manage API requests uniformly:
140
+
141
+ ```typescript
142
+ @injectable()
143
+ export class RequestController extends StoreInterface<RequestControllerState> {
144
+ constructor(
145
+ @inject(FeApi) private readonly feApi: FeApi,
146
+ @inject(UserApi) private readonly userApi: UserApi
147
+ ) {
148
+ super(createDefaultState);
149
+ }
150
+
151
+ // API call example
152
+ async onRandomUser() {
153
+ if (this.state.randomUserState.loading) return;
154
+
155
+ this.setState({
156
+ randomUserState: { loading: true, result: null, error: null }
157
+ });
158
+
159
+ try {
160
+ const result = await this.userApi.getRandomUser();
161
+ this.setState({
162
+ randomUserState: { loading: false, result, error: null }
163
+ });
164
+ } catch (error) {
165
+ this.setState({
166
+ randomUserState: { loading: false, result: null, error }
167
+ });
168
+ }
169
+ }
170
+ }
171
+ ```
172
+
173
+ ### Controller Pattern
174
+
175
+ Provides multiple out-of-the-box controllers:
176
+
177
+ - JSONStorageController
178
+ - RequestController
179
+ - RouterController
180
+ - UserController
181
+ - ThemeController
182
+
183
+ ## 🛠️ Development Guide
184
+
185
+ ### API Development Standards
186
+
187
+ 1. Define interfaces in `src/base/apis`
188
+ 2. Support for Mock data configuration
189
+
190
+ ### Adding New Pages
191
+
192
+ 1. Create page component in `src/pages`
193
+ 2. Update `config/app.router.json`
194
+ 3. Add corresponding controller (if needed)
195
+
196
+ ### Build Optimization
197
+
198
+ The project uses Vite for building and includes the following optimizations:
199
+
200
+ #### Code Splitting
201
+
202
+ Automatic intelligent code splitting into the following main chunks:
203
+
204
+ - react-vendor: React core library
205
+ - antd-core: Ant Design core components
206
+ - antd-basic: Basic UI components
207
+ - antd-advanced: Advanced UI components
208
+ - utils: Utility functions
209
+ - i18n: Internationalization related
210
+
211
+ #### Build Optimization Configuration
212
+
213
+ ```typescript
214
+ build: {
215
+ // Chunk size warning limit set to 600kb
216
+ chunkSizeWarningLimit: 600,
217
+ // Use terser for code compression
218
+ minify: 'terser',
219
+ terserOptions: {
220
+ compress: {
221
+ // Remove console output and debugger statements
222
+ drop_console: true,
223
+ drop_debugger: true,
224
+ // Remove specific console function calls
225
+ pure_funcs: ['console.log', 'console.info', 'console.debug']
226
+ }
227
+ }
228
+ }
229
+ ```
230
+
231
+ ### Project Configuration
232
+
233
+ #### Environment Variables
234
+
235
+ - Use `@qlover/corekit-bridge/vite-env-config` to manage environment variables
236
+ - Automatically inject application name and version information
237
+ - Support for environment variable prefix configuration
238
+
239
+ #### Style Configuration
240
+
241
+ - Integrate Tailwind CSS
242
+ - Load Ant Design component styles on demand
243
+ - Support theme mode override
244
+
245
+ #### Development Server
246
+
247
+ ```bash
248
+ # Default port 3200, configurable via environment variable
249
+ VITE_SERVER_PORT=3000 pnpm dev
250
+ ```
251
+
252
+ #### Test Configuration
253
+
254
+ - Use Vitest for unit testing
255
+ - Support JSDOM environment
256
+ - Built-in test tool configuration
257
+ ```