@qlover/create-app 0.7.3 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/templates/react-app/.env.template +0 -2
  5. package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +1 -1
  6. package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +6 -31
  7. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +1 -1
  8. package/dist/templates/react-app/config/IOCIdentifier.ts +77 -5
  9. package/dist/templates/react-app/config/app.router.ts +2 -2
  10. package/dist/templates/react-app/package.json +4 -7
  11. package/dist/templates/react-app/public/locales/en/common.json +1 -1
  12. package/dist/templates/react-app/public/locales/zh/common.json +1 -1
  13. package/dist/templates/react-app/src/App.tsx +9 -4
  14. package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +1 -1
  15. package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +4 -0
  16. package/dist/templates/react-app/src/base/cases/DialogHandler.ts +16 -13
  17. package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +4 -3
  18. package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +2 -2
  19. package/dist/templates/react-app/src/base/cases/RequestLanguages.ts +39 -0
  20. package/dist/templates/react-app/src/base/cases/RequestState.ts +20 -0
  21. package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +2 -2
  22. package/dist/templates/react-app/src/base/cases/RouterLoader.ts +8 -2
  23. package/dist/templates/react-app/src/base/port/AsyncStateInterface.ts +7 -0
  24. package/dist/templates/react-app/src/base/port/ExecutorPageBridgeInterface.ts +24 -0
  25. package/dist/templates/react-app/src/base/port/I18nServiceInterface.ts +10 -0
  26. package/dist/templates/react-app/src/base/port/JSONStoragePageBridgeInterface.ts +20 -0
  27. package/dist/templates/react-app/src/base/port/ProcesserExecutorInterface.ts +20 -0
  28. package/dist/templates/react-app/src/base/port/RequestPageBridgeInterface.ts +23 -0
  29. package/dist/templates/react-app/src/base/port/RequestStatusInterface.ts +5 -0
  30. package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +27 -0
  31. package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +12 -0
  32. package/dist/templates/react-app/src/base/services/I18nService.ts +10 -6
  33. package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +23 -5
  34. package/dist/templates/react-app/src/base/services/RouteService.ts +25 -54
  35. package/dist/templates/react-app/src/base/services/UserService.ts +10 -20
  36. package/dist/templates/react-app/src/core/IOC.ts +1 -26
  37. package/dist/templates/react-app/src/core/IocRegisterImpl.ts +125 -0
  38. package/dist/templates/react-app/src/core/bootstraps/BootstrapApp.ts +4 -6
  39. package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +8 -6
  40. package/dist/templates/react-app/src/core/bootstraps/IocIdentifierTest.ts +26 -0
  41. package/dist/templates/react-app/src/pages/auth/Layout.tsx +2 -2
  42. package/dist/templates/react-app/src/pages/auth/LoginPage.tsx +5 -6
  43. package/dist/templates/react-app/src/pages/auth/RegisterPage.tsx +8 -7
  44. package/dist/templates/react-app/src/pages/base/ExecutorPage.tsx +8 -19
  45. package/dist/templates/react-app/src/pages/base/{ErrorIdentifierPage.tsx → IdentifierPage.tsx} +1 -1
  46. package/dist/templates/react-app/src/pages/base/JSONStoragePage.tsx +11 -15
  47. package/dist/templates/react-app/src/pages/base/Layout.tsx +1 -1
  48. package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +2 -2
  49. package/dist/templates/react-app/src/pages/base/RequestPage.tsx +34 -46
  50. package/dist/templates/react-app/src/styles/css/antd-themes/_default.css +2 -2
  51. package/dist/templates/react-app/src/styles/css/antd-themes/dark.css +2 -2
  52. package/dist/templates/react-app/src/styles/css/antd-themes/pink.css +2 -2
  53. package/dist/templates/react-app/src/styles/css/index.css +1 -0
  54. package/dist/templates/react-app/src/styles/css/page.css +8 -0
  55. package/dist/templates/react-app/src/styles/css/zIndex.css +9 -0
  56. package/dist/templates/react-app/src/uikit/{controllers/ExecutorController.ts → bridges/ExecutorPageBridge.ts} +13 -36
  57. package/dist/templates/react-app/src/uikit/bridges/JSONStoragePageBridge.ts +41 -0
  58. package/dist/templates/react-app/src/uikit/bridges/NavigateBridge.ts +16 -0
  59. package/dist/templates/react-app/src/uikit/bridges/RequestPageBridge.ts +136 -0
  60. package/dist/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +3 -2
  61. package/dist/templates/react-app/src/uikit/components/LogoutButton.tsx +2 -2
  62. package/dist/templates/react-app/src/uikit/{providers → components}/ProcessExecutorProvider.tsx +4 -4
  63. package/dist/templates/react-app/src/uikit/components/RouterRenderComponent.tsx +1 -1
  64. package/dist/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +3 -5
  65. package/dist/templates/react-app/src/uikit/{providers → components}/UserAuthProvider.tsx +3 -3
  66. package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +5 -2
  67. package/dist/templates/react-app/src/uikit/hooks/{userRouterService.ts → useNavigateBridge.ts} +3 -3
  68. package/dist/templates/react-app/src/uikit/hooks/useStore.ts +5 -2
  69. package/dist/templates/react-app/tsconfig.json +1 -4
  70. package/package.json +2 -2
  71. package/dist/templates/react-app/src/base/port/InteractionHubInterface.ts +0 -94
  72. package/dist/templates/react-app/src/base/port/UIDependenciesInterface.ts +0 -37
  73. package/dist/templates/react-app/src/core/registers/IocRegisterImpl.ts +0 -25
  74. package/dist/templates/react-app/src/core/registers/RegisterCommon.ts +0 -74
  75. package/dist/templates/react-app/src/core/registers/RegisterControllers.ts +0 -26
  76. package/dist/templates/react-app/src/core/registers/RegisterGlobals.ts +0 -30
  77. package/dist/templates/react-app/src/uikit/controllers/JSONStorageController.ts +0 -49
  78. package/dist/templates/react-app/src/uikit/controllers/RequestController.ts +0 -158
  79. /package/dist/templates/react-app/src/uikit/{providers → components}/BaseRouteProvider.tsx +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # @qlover/create-app
2
2
 
3
+ ## 0.7.6
4
+
5
+ ### Patch Changes
6
+
7
+ #### ✨ Features
8
+
9
+ - **ui:** introduce UI bridge interfaces and notification system ([6969561](https://github.com/qlover/fe-base/commit/6969561b3f4603bb51ae7ad5fdca9e539d75209b)) ([#499](https://github.com/qlover/fe-base/pull/499))
10
+ - Added UIBridgeInterface for decoupling business logic from UI components.
11
+ - Implemented UINotificationInterface and UIDialogInterface for standardized notification handling.
12
+ - Created NavigateBridge to facilitate navigation integration with React Router.
13
+ - Updated RouteService to utilize the new UIBridgeInterface for improved dependency management.
14
+ - Introduced useNavigateBridge hook for setting up navigation in React components.
15
+ - Removed obsolete UIDependenciesInterface to streamline the codebase.
16
+
17
+ - **dialog:** enhance UIDialogInterface and integrate into DialogHandler ([d9d2cdd](https://github.com/qlover/fe-base/commit/d9d2cdd68ff2a68675a2abdd7339c7be0c706724)) ([#499](https://github.com/qlover/fe-base/pull/499))
18
+ - Updated UIDialogInterface to use generic types for notification options, improving flexibility.
19
+ - Integrated UIDialogInterface into DialogHandler for better type safety and consistency in dialog handling.
20
+ - Adjusted RouteService to streamline navigation handling and improve code clarity.
21
+ - Updated package.json to reference local paths for corekit-bridge and fe-corekit for development.
22
+
23
+ ## 0.7.5
24
+
25
+ ### Patch Changes
26
+
27
+ #### ♻️ Refactors
28
+
29
+ - **docs:** update references from @qlover/eslint-plugin-fe-dev to @qlover/eslint-plugin ([3c682cd](https://github.com/qlover/fe-base/commit/3c682cdbac17cc0e96166a45934899bad7c565cc)) ([#489](https://github.com/qlover/fe-base/pull/489))
30
+ - Replaced all instances of @qlover/eslint-plugin-fe-dev with @qlover/eslint-plugin in documentation and configuration files to reflect the new plugin structure.
31
+ - Ensured consistency across README and documentation files in both English and Chinese.
32
+ - Updated ESLint configuration files to use the new plugin name, enhancing clarity and maintainability.
33
+
3
34
  ## 0.7.3
4
35
 
5
36
  ### 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.7.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)});
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.7.6",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.7.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)});
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.7.6",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)});
@@ -1,5 +1,3 @@
1
- NODE_ENV=production
2
-
3
1
  # ci
4
2
  NPM_TOKEN=
5
3
  GITHUB_TOKEN=
@@ -87,7 +87,7 @@ describe('I18nService', () => {
87
87
  expect.objectContaining({
88
88
  debug: false,
89
89
  detection: {
90
- order: ['pathLanguageDetector', 'navigator'],
90
+ order: ['pathLanguageDetector', 'navigator', 'localStorage'],
91
91
  caches: []
92
92
  }
93
93
  })
@@ -1,6 +1,6 @@
1
- import { IOC, type IOCContainer, type IOCIdentifierMap } from '@/core/IOC';
1
+ import { IOC, type IOCContainer } from '@/core/IOC';
2
2
  import { InversifyContainer } from '@/base/cases/InversifyContainer';
3
- import { IocRegisterImpl } from '@/core/registers/IocRegisterImpl';
3
+ import { IocRegisterImpl } from '@/core/IocRegisterImpl';
4
4
  import { IOCIdentifier } from '@config/IOCIdentifier';
5
5
  import type { AppConfig } from '@/base/cases/AppConfig';
6
6
 
@@ -35,7 +35,8 @@ describe('IOC Container Tests', () => {
35
35
  aiApiToken: '',
36
36
  aiApiTokenPrefix: 'Bearer',
37
37
  aiApiRequireToken: true,
38
- bootHref: ''
38
+ bootHref: '',
39
+ isProduction: false
39
40
  };
40
41
  });
41
42
 
@@ -102,12 +103,6 @@ describe('IOC Container Tests', () => {
102
103
  expect(registerImpl).toBeInstanceOf(IocRegisterImpl);
103
104
  });
104
105
 
105
- it('should return register list', () => {
106
- const registerList = registerImpl.getRegisterList();
107
- expect(Array.isArray(registerList)).toBe(true);
108
- expect(registerList.length).toBeGreaterThan(0);
109
- });
110
-
111
106
  it('should register services without throwing', () => {
112
107
  const mockManager = {
113
108
  get: vi.fn(),
@@ -125,34 +120,14 @@ describe('IOC Container Tests', () => {
125
120
  describe('IOCIdentifierMap Type Safety', () => {
126
121
  it('should have correct type mapping for JSON', () => {
127
122
  // This test verifies that the type mapping is correct
128
- const identifierMap: IOCIdentifierMap = {} as IOCIdentifierMap;
123
+ const identifierMap = {};
129
124
 
130
125
  // TypeScript should enforce that these keys exist
131
- expect(IOCIdentifier.JSON in identifierMap).toBeDefined();
126
+ expect(IOCIdentifier.JSONSerializer in identifierMap).toBeDefined();
132
127
  expect(IOCIdentifier.LocalStorage in identifierMap).toBeDefined();
133
128
  expect(IOCIdentifier.Logger in identifierMap).toBeDefined();
134
129
  expect(IOCIdentifier.AppConfig in identifierMap).toBeDefined();
135
130
  });
136
-
137
- it('should have all required identifiers', () => {
138
- const requiredIdentifiers = [
139
- 'JSON',
140
- 'LocalStorage',
141
- 'LocalStorageEncrypt',
142
- 'CookieStorage',
143
- 'Logger',
144
- 'FeApiToken',
145
- 'FeApiCommonPlugin',
146
- 'AppConfig',
147
- 'ApiMockPlugin',
148
- 'ApiCatchPlugin',
149
- 'DialogHandler'
150
- ];
151
-
152
- requiredIdentifiers.forEach((identifier) => {
153
- expect(IOCIdentifier).toHaveProperty(identifier);
154
- });
155
- });
156
131
  });
157
132
 
158
133
  describe('Service Registration Integration', () => {
@@ -2,7 +2,7 @@ import { InversifyContainer } from '@/base/cases/InversifyContainer';
2
2
  import { BootstrapApp } from '@/core/bootstraps/BootstrapApp';
3
3
  import type { BootstrapAppArgs } from '@/core/bootstraps/BootstrapApp';
4
4
  import { createIOCFunction } from '@qlover/corekit-bridge';
5
- import type { IOCIdentifierMap } from '@/core/IOC';
5
+ import type { IOCIdentifierMap } from '@config/IOCIdentifier';
6
6
  import { name, version } from '../../../../package.json';
7
7
  import { browserGlobalsName } from '@config/common';
8
8
 
@@ -1,13 +1,85 @@
1
+ import type * as CorekitBridge from '@qlover/corekit-bridge';
2
+ import type * as FeCorekit from '@qlover/fe-corekit';
3
+ import type * as Logger from '@qlover/logger';
4
+ import type { DialogHandler } from '@/base/cases/DialogHandler';
5
+ import type { AppConfig } from '@/base/cases/AppConfig';
6
+ import type { RequestStatusCatcher } from '@/base/cases/RequestStatusCatcher';
7
+ import type { I18nService } from '@/base/services/I18nService';
8
+ import type { ProcesserExecutor } from '@/base/services/ProcesserExecutor';
9
+ import type { RouteService } from '@/base/services/RouteService';
10
+ import type { UserService } from '@/base/services/UserService';
11
+ import type { I18nKeyErrorPlugin } from '@/base/cases/I18nKeyErrorPlugin';
12
+ import type { ExecutorPageBridgeInterface } from '@/base/port/ExecutorPageBridgeInterface';
13
+ import type { JSONStoragePageBridgeInterface } from '@/base/port/JSONStoragePageBridgeInterface';
14
+ import type { RequestPageBridgeInterface } from '@/base/port/RequestPageBridgeInterface';
15
+
16
+ /**
17
+ * IOC identifier
18
+ */
1
19
  export const IOCIdentifier = Object.freeze({
2
- JSON: 'JSON',
20
+ JSONSerializer: 'JSONSerializer',
21
+ Logger: 'Logger',
22
+ AppConfig: 'AppConfig',
23
+ DialogHandler: 'DialogHandler',
3
24
  LocalStorage: 'LocalStorage',
4
25
  LocalStorageEncrypt: 'LocalStorageEncrypt',
5
26
  CookieStorage: 'CookieStorage',
6
- Logger: 'Logger',
7
- FeApiToken: 'FeApiToken',
27
+ EnvConfigInterface: 'EnvConfigInterface',
28
+ UIDialogInterface: 'UIDialogInterface',
29
+ AntdStaticApiInterface: 'AntdStaticApiInterface',
30
+ RequestCatcherInterface: 'RequestCatcherInterface',
31
+ I18nServiceInterface: 'I18nServiceInterface',
32
+ ProcesserExecutorInterface: 'ProcesserExecutorInterface',
33
+ RouteServiceInterface: 'RouteServiceInterface',
34
+ UserServiceInterface: 'UserServiceInterface',
35
+ I18nKeyErrorPlugin: 'I18nKeyErrorPlugin',
8
36
  FeApiCommonPlugin: 'FeApiCommonPlugin',
9
- AppConfig: 'AppConfig',
10
37
  ApiMockPlugin: 'ApiMockPlugin',
11
38
  ApiCatchPlugin: 'ApiCatchPlugin',
12
- DialogHandler: 'DialogHandler'
39
+ ThemeService: 'ThemeService',
40
+ ExecutorPageBridgeInterface: 'ExecutorPageBridgeInterface',
41
+ JSONStoragePageInterface: 'JSONStoragePageInterface',
42
+ RequestPageBridgeInterface: 'RequestPageBridgeInterface'
13
43
  });
44
+
45
+ export const I = IOCIdentifier;
46
+
47
+ /**
48
+ * IOC identifier map
49
+ *
50
+ * Define the implementation class corresponding to the string identifier
51
+ *
52
+ * - key: interface alias or name
53
+ * - value: implementation class
54
+ */
55
+ export interface IOCIdentifierMap {
56
+ [IOCIdentifier.JSONSerializer]: FeCorekit.JSONSerializer;
57
+ [IOCIdentifier.Logger]: Logger.Logger;
58
+ [IOCIdentifier.AppConfig]: AppConfig;
59
+ [IOCIdentifier.DialogHandler]: DialogHandler;
60
+ [IOCIdentifier.LocalStorage]: FeCorekit.SyncStorage<
61
+ unknown,
62
+ FeCorekit.ObjectStorageOptions
63
+ >;
64
+ [IOCIdentifier.LocalStorageEncrypt]: FeCorekit.SyncStorage<
65
+ unknown,
66
+ FeCorekit.ObjectStorageOptions
67
+ >;
68
+ [IOCIdentifier.CookieStorage]: CorekitBridge.CookieStorage;
69
+ [IOCIdentifier.EnvConfigInterface]: AppConfig;
70
+ [IOCIdentifier.UIDialogInterface]: DialogHandler;
71
+ [IOCIdentifier.AntdStaticApiInterface]: DialogHandler;
72
+ [IOCIdentifier.RequestCatcherInterface]: RequestStatusCatcher;
73
+ [IOCIdentifier.I18nServiceInterface]: I18nService;
74
+ [IOCIdentifier.ProcesserExecutorInterface]: ProcesserExecutor;
75
+ [IOCIdentifier.RouteServiceInterface]: RouteService;
76
+ [IOCIdentifier.UserServiceInterface]: UserService;
77
+ [IOCIdentifier.I18nKeyErrorPlugin]: I18nKeyErrorPlugin;
78
+ [IOCIdentifier.FeApiCommonPlugin]: CorekitBridge.RequestCommonPlugin;
79
+ [IOCIdentifier.ApiMockPlugin]: CorekitBridge.ApiMockPlugin;
80
+ [IOCIdentifier.ApiCatchPlugin]: CorekitBridge.ApiCatchPlugin;
81
+ [IOCIdentifier.ThemeService]: CorekitBridge.ThemeService;
82
+ [IOCIdentifier.ExecutorPageBridgeInterface]: ExecutorPageBridgeInterface;
83
+ [IOCIdentifier.JSONStoragePageInterface]: JSONStoragePageBridgeInterface;
84
+ [IOCIdentifier.RequestPageBridgeInterface]: RequestPageBridgeInterface;
85
+ }
@@ -64,8 +64,8 @@ export const baseRoutes: RouteConfigValue[] = [
64
64
  }
65
65
  },
66
66
  {
67
- path: 'errorIdentifier',
68
- element: 'base/ErrorIdentifierPage',
67
+ path: 'identifier',
68
+ element: 'base/IdentifierPage',
69
69
  meta: {
70
70
  title: identifier.PAGE_ERROR_IDENTIFIER_TITLE,
71
71
  description: identifier.PAGE_ERROR_IDENTIFIER_DESCRIPTION,
@@ -4,7 +4,7 @@
4
4
  "version": "0.0.0",
5
5
  "type": "module",
6
6
  "private": true,
7
- "homepage": "",
7
+ "homepage": "https://github.com/qlover/fe-base/tree/master/packages/create-app/templates/react-app#readme",
8
8
  "author": "qlover",
9
9
  "license": "ISC",
10
10
  "repository": {
@@ -26,9 +26,6 @@
26
26
  "publishConfig": {
27
27
  "access": "public"
28
28
  },
29
- "bin": {
30
- "fe-release": "./bin/release.js"
31
- },
32
29
  "scripts": {
33
30
  "dev": "vite --mode localhost",
34
31
  "dev:staging": "vite --mode staging",
@@ -44,7 +41,7 @@
44
41
  "@qlover/corekit-bridge": "latest",
45
42
  "@qlover/fe-corekit": "latest",
46
43
  "@qlover/logger": "^0.1.1",
47
- "@qlover/slice-store-react": "^1.2.6",
44
+ "@qlover/slice-store-react": "^1.4.1",
48
45
  "@tailwindcss/postcss": "^4.1.8",
49
46
  "@tailwindcss/vite": "^4.1.8",
50
47
  "antd": "^5.25.3",
@@ -64,9 +61,9 @@
64
61
  "devDependencies": {
65
62
  "@brain-toolkit/ts2locales": "^0.2.3",
66
63
  "@qlover/env-loader": "latest",
67
- "@qlover/eslint-plugin": "^0.2.0",
64
+ "@qlover/eslint-plugin": "latest",
68
65
  "@qlover/fe-scripts": "latest",
69
- "@qlover/fe-standard": "^0.0.4",
66
+ "@qlover/fe-standard": "latest",
70
67
  "@testing-library/react": "^16.3.0",
71
68
  "@types/lodash": "^4.17.13",
72
69
  "@types/react": "^18.3.11",
@@ -178,4 +178,4 @@
178
178
  "page.request.stop_abort": "Stop Abort Request",
179
179
  "page.request.trigger_api_catch": "Trigger API Catch Result",
180
180
  "page.request.stop_api_catch": "Stop API Catch Result"
181
- }
181
+ }
@@ -178,4 +178,4 @@
178
178
  "page.request.stop_abort": "停止中止请求",
179
179
  "page.request.trigger_api_catch": "触发 API 捕获结果",
180
180
  "page.request.stop_api_catch": "停止 API 捕获结果"
181
- }
181
+ }
@@ -3,11 +3,12 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom';
3
3
  import { lazy, useMemo } from 'react';
4
4
  import { RouterRenderComponent } from './uikit/components/RouterRenderComponent';
5
5
  import { IOC } from './core/IOC';
6
- import { RouteService } from './base/services/RouteService';
7
6
  import { RouterLoader, type ComponentValue } from '@/base/cases/RouterLoader';
8
7
  import { AntdThemeProvider } from '@brain-toolkit/antd-theme-override/react';
9
8
  import { routerPrefix } from '@config/common';
10
9
  import { useStore } from './uikit/hooks/useStore';
10
+ import { IOCIdentifier } from '@config/IOCIdentifier';
11
+ import { logger } from './core/globals';
11
12
 
12
13
  function getAllPages() {
13
14
  const modules = import.meta.glob('./pages/**/*.tsx');
@@ -25,11 +26,15 @@ function getAllPages() {
25
26
 
26
27
  const routerLoader = new RouterLoader({
27
28
  componentMaps: getAllPages(),
28
- render: RouterRenderComponent
29
+ render: RouterRenderComponent,
30
+ logger: logger
29
31
  });
30
32
 
31
33
  function App() {
32
- const routes = useStore(IOC(RouteService), (state) => state.routes);
34
+ const routes = useStore(
35
+ IOC(IOCIdentifier.RouteServiceInterface),
36
+ (state) => state.routes
37
+ );
33
38
 
34
39
  const routerBase = useMemo(() => {
35
40
  const routeList = routes.map((route) => routerLoader.toRoute(route));
@@ -41,7 +46,7 @@ function App() {
41
46
 
42
47
  return (
43
48
  <AntdThemeProvider
44
- staticApi={IOC('DialogHandler')}
49
+ staticApi={IOC('AntdStaticApiInterface')}
45
50
  theme={{
46
51
  cssVar: {
47
52
  key: 'fe-theme',
@@ -37,7 +37,7 @@ export class UserApi
37
37
  protected store: UserAuthStoreInterface<UserInfo> | null = null;
38
38
 
39
39
  constructor(
40
- @inject(FetchAbortPlugin) private abortPlugin: FetchAbortPlugin,
40
+ @inject(FetchAbortPlugin) protected abortPlugin: FetchAbortPlugin,
41
41
  @inject(UserApiAdapter) adapter: RequestAdapterFetch
42
42
  ) {
43
43
  super(adapter);
@@ -16,6 +16,7 @@ import {
16
16
  type ApiCatchPluginResponse
17
17
  } from '@qlover/corekit-bridge';
18
18
  import { UserApi } from './UserApi';
19
+ import { RequestLanguages } from '../../cases/RequestLanguages';
19
20
 
20
21
  /**
21
22
  * UserApiConfig
@@ -68,6 +69,9 @@ export class UserApiBootstarp implements BootstrapExecutorPlugin {
68
69
  .get<UserApi>(UserApi)
69
70
  .usePlugin(new FetchURLPlugin())
70
71
  .usePlugin(IOC.get(IOCIdentifier.FeApiCommonPlugin))
72
+ .usePlugin(
73
+ new RequestLanguages(ioc.get(IOCIdentifier.I18nServiceInterface))
74
+ )
71
75
  .usePlugin(IOC.get(IOCIdentifier.ApiMockPlugin))
72
76
  .usePlugin(IOC.get(RequestLogger))
73
77
  .usePlugin(IOC.get(FetchAbortPlugin))
@@ -4,11 +4,14 @@ import type {
4
4
  ModalApi,
5
5
  NotificationApi
6
6
  } from '@brain-toolkit/antd-theme-override/react';
7
- import type {
8
- InteractionHubInterface,
9
- InteractionOptions,
10
- ConfirmOptions
11
- } from '../port/InteractionHubInterface';
7
+ import { UIDialogInterface, NotificationOptions } from '@qlover/corekit-bridge';
8
+ import { ModalFuncProps } from 'antd';
9
+
10
+ export interface DialogHandlerOptions
11
+ extends NotificationOptions,
12
+ ModalFuncProps {
13
+ content: string;
14
+ }
12
15
 
13
16
  /**
14
17
  * Dialog Handler Implementation
@@ -32,9 +35,9 @@ import type {
32
35
  * });
33
36
  */
34
37
  export class DialogHandler
35
- implements InteractionHubInterface, AntdStaticApiInterface
38
+ implements UIDialogInterface<DialogHandlerOptions>, AntdStaticApiInterface
36
39
  {
37
- private antds: {
40
+ protected antds: {
38
41
  message?: MessageApi;
39
42
  modal?: ModalApi;
40
43
  notification?: NotificationApi;
@@ -55,32 +58,32 @@ export class DialogHandler
55
58
  /**
56
59
  * Formats error message from various error types
57
60
  */
58
- private formatErrorMessage(error: unknown): string {
61
+ protected formatErrorMessage(error: unknown): string {
59
62
  if (error instanceof Error) return error.message;
60
63
  if (typeof error === 'string') return error;
61
64
  return 'An unknown error occurred';
62
65
  }
63
66
 
64
- public success(msg: string, options?: InteractionOptions): void {
67
+ public success(msg: string, options?: NotificationOptions): void {
65
68
  this.antds.message?.success({ content: msg, ...options });
66
69
  }
67
70
 
68
- public error(msg: string, options?: InteractionOptions): void {
71
+ public error(msg: string, options?: NotificationOptions): void {
69
72
  this.antds.message?.error({
70
73
  content: options?.error ? this.formatErrorMessage(options.error) : msg,
71
74
  ...options
72
75
  });
73
76
  }
74
77
 
75
- public info(msg: string, options?: InteractionOptions): void {
78
+ public info(msg: string, options?: NotificationOptions): void {
76
79
  this.antds.message?.info({ content: msg, ...options });
77
80
  }
78
81
 
79
- public warn(msg: string, options?: InteractionOptions): void {
82
+ public warn(msg: string, options?: NotificationOptions): void {
80
83
  this.antds.message?.warning({ content: msg, ...options });
81
84
  }
82
85
 
83
- public confirm(options: ConfirmOptions): void {
86
+ public confirm(options: DialogHandlerOptions): void {
84
87
  this.antds.modal?.confirm(options);
85
88
  }
86
89
  }
@@ -1,8 +1,8 @@
1
1
  import type { ExecutorContext, ExecutorPlugin } from '@qlover/fe-corekit';
2
2
  import type { LoggerInterface } from '@qlover/logger';
3
3
  import { inject, injectable } from 'inversify';
4
- import { I18nService } from '../services/I18nService';
5
4
  import { IOCIdentifier } from '@config/IOCIdentifier';
5
+ import type { I18nServiceInterface } from '../port/I18nServiceInterface';
6
6
 
7
7
  /**
8
8
  * When throw error, it will be converted to i18n key
@@ -14,8 +14,9 @@ export class I18nKeyErrorPlugin implements ExecutorPlugin {
14
14
  readonly pluginName = 'I18nKeyErrorPlugin';
15
15
 
16
16
  constructor(
17
- @inject(IOCIdentifier.Logger) private logger: LoggerInterface,
18
- @inject(I18nService) private i18nService: I18nService
17
+ @inject(IOCIdentifier.Logger) protected logger: LoggerInterface,
18
+ @inject(IOCIdentifier.I18nServiceInterface)
19
+ protected i18nService: I18nServiceInterface
19
20
  ) {}
20
21
 
21
22
  onError(context: ExecutorContext<unknown>): Error | void {
@@ -1,4 +1,4 @@
1
- import { IOCIdentifierMap } from '@/core/IOC';
1
+ import { IOCIdentifierMap } from '@config/IOCIdentifier';
2
2
  import {
3
3
  IOCContainerInterface,
4
4
  ServiceIdentifier
@@ -6,7 +6,7 @@ import {
6
6
  import { Container } from 'inversify';
7
7
 
8
8
  export class InversifyContainer implements IOCContainerInterface {
9
- private container: Container;
9
+ protected container: Container;
10
10
 
11
11
  constructor() {
12
12
  this.container = new Container({
@@ -0,0 +1,39 @@
1
+ import { I18nServiceInterface } from '@/base/port/I18nServiceInterface';
2
+ import {
3
+ ExecutorContext,
4
+ ExecutorPlugin,
5
+ RequestAdapterConfig
6
+ } from '@qlover/fe-corekit';
7
+
8
+ export class RequestLanguages implements ExecutorPlugin {
9
+ readonly pluginName = 'RequestLanguages';
10
+
11
+ constructor(
12
+ protected i18nService: I18nServiceInterface,
13
+ protected headerName = 'accept-language'
14
+ ) {}
15
+
16
+ buildAcceptLanguage(langs: string[]): string {
17
+ return langs
18
+ .map((lang, index) => {
19
+ const q = Math.max(1 - index * 0.1, 0.1).toFixed(1);
20
+ return index === 0 ? lang : `${lang};q=${q}`;
21
+ })
22
+ .join(',');
23
+ }
24
+
25
+ onBefore(context: ExecutorContext<RequestAdapterConfig>): void {
26
+ const currentLanguage = this.i18nService.getCurrentLanguage();
27
+ const languages = this.i18nService.getSupportedLanguages();
28
+
29
+ if (!context.parameters.headers) {
30
+ context.parameters.headers = {};
31
+ }
32
+
33
+ const languageValue = this.buildAcceptLanguage(
34
+ Array.from(new Set([currentLanguage, ...languages]))
35
+ );
36
+
37
+ context.parameters.headers[this.headerName] = languageValue;
38
+ }
39
+ }
@@ -0,0 +1,20 @@
1
+ import { AsyncStateInterface } from '@/base/port/AsyncStateInterface';
2
+
3
+ export class RequestState<T> implements AsyncStateInterface<T> {
4
+ startTime: number;
5
+ endTime: number;
6
+
7
+ constructor(
8
+ public loading: boolean = false,
9
+ public result: T | null = null,
10
+ public error: unknown | null = null
11
+ ) {
12
+ this.startTime = Date.now();
13
+ this.endTime = 0;
14
+ }
15
+
16
+ end(): this {
17
+ this.endTime = Date.now();
18
+ return this;
19
+ }
20
+ }
@@ -9,9 +9,9 @@ export class RequestStatusCatcher
9
9
  implements RequestCatcherInterface<RequestAdapterResponse>
10
10
  {
11
11
  constructor(
12
- @inject(IOCIdentifier.Logger)
13
- private readonly logger: LoggerInterface
12
+ @inject(IOCIdentifier.Logger) protected logger: LoggerInterface
14
13
  ) {}
14
+
15
15
  /**
16
16
  * default handler
17
17
  * @override