@qlover/create-app 0.8.0 → 0.10.0
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.
- package/CHANGELOG.md +32 -0
- package/dist/configs/_common/.github/workflows/general-check.yml +1 -1
- package/dist/configs/_common/.github/workflows/release.yml +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/next-app/.env.template +9 -10
- package/dist/templates/next-app/eslint.config.mjs +5 -0
- package/dist/templates/next-app/next.config.ts +1 -1
- package/dist/templates/next-app/src/app/[locale]/login/page.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/page.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/register/page.tsx +1 -1
- package/dist/templates/next-app/src/app/api/locales/json/route.ts +2 -1
- package/dist/templates/next-app/src/i18n/request.ts +2 -2
- package/dist/templates/react-app/__tests__/__mocks__/{MockAppConfit.ts → MockAppConfig.ts} +1 -1
- package/dist/templates/react-app/__tests__/__mocks__/components/TestApp.tsx +10 -17
- package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +27 -8
- package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +1 -1
- package/dist/templates/react-app/__tests__/__mocks__/i18nextHttpBackend.ts +110 -0
- package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +3 -2
- package/dist/templates/react-app/__tests__/setup/setupGlobal.ts +13 -0
- package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +3 -1
- package/dist/templates/react-app/__tests__/src/uikit/components/chatMessage/ChatRoot.test.tsx +274 -0
- package/dist/templates/react-app/config/IOCIdentifier.ts +9 -3
- package/dist/templates/react-app/config/Identifier/components/component.chatMessage.ts +56 -0
- package/dist/templates/react-app/config/Identifier/components/component.messageBaseList.ts +103 -0
- package/dist/templates/react-app/config/Identifier/pages/index.ts +1 -0
- package/dist/templates/react-app/config/Identifier/pages/page.message.ts +20 -0
- package/dist/templates/react-app/config/app.router.ts +23 -0
- package/dist/templates/react-app/config/common.ts +38 -0
- package/dist/templates/react-app/config/feapi.mock.json +5 -12
- package/dist/templates/react-app/config/i18n/chatMessageI18n.ts +17 -0
- package/dist/templates/react-app/config/i18n/messageBaseListI18n.ts +22 -0
- package/dist/templates/react-app/config/i18n/messageI18n.ts +14 -0
- package/dist/templates/react-app/docs/en/components/chat-message-component.md +314 -0
- package/dist/templates/react-app/docs/en/components/chat-message-refactor.md +270 -0
- package/dist/templates/react-app/docs/en/components/message-base-list-component.md +172 -0
- package/dist/templates/react-app/docs/zh/components/chat-message-component.md +314 -0
- package/dist/templates/react-app/docs/zh/components/chat-message-refactor.md +270 -0
- package/dist/templates/react-app/docs/zh/components/message-base-list-component.md +172 -0
- package/dist/templates/react-app/eslint.config.mjs +6 -5
- package/dist/templates/react-app/package.json +1 -1
- package/dist/templates/react-app/playwright.config.ts +6 -6
- package/dist/templates/react-app/public/locales/en/common.json +44 -1
- package/dist/templates/react-app/public/locales/zh/common.json +44 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +22 -13
- package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +3 -3
- package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +17 -12
- package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +19 -2
- package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +2 -4
- package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +15 -9
- package/dist/templates/react-app/src/base/services/BaseLayoutService.ts +55 -0
- package/dist/templates/react-app/src/base/services/I18nService.ts +1 -0
- package/dist/templates/react-app/src/base/services/UserBootstrap.ts +43 -0
- package/dist/templates/react-app/src/base/services/UserGatewayPlugin.ts +16 -0
- package/dist/templates/react-app/src/base/services/UserService.ts +51 -80
- package/dist/templates/react-app/src/core/bootstraps/BootstrapClient.ts +8 -3
- package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +6 -6
- package/dist/templates/react-app/src/core/bootstraps/SaveAppInfo.ts +28 -0
- package/dist/templates/react-app/src/core/clientIoc/ClientIOCRegister.ts +24 -18
- package/dist/templates/react-app/src/core/globals.ts +10 -11
- package/dist/templates/react-app/src/main.tsx +1 -1
- package/dist/templates/react-app/src/pages/auth/Layout.tsx +4 -4
- package/dist/templates/react-app/src/pages/auth/RegisterPage.tsx +1 -1
- package/dist/templates/react-app/src/pages/base/Layout.tsx +3 -3
- package/dist/templates/react-app/src/pages/base/MessagePage.tsx +40 -0
- package/dist/templates/react-app/src/uikit/components/BaseLayoutProvider.tsx +44 -0
- package/dist/templates/react-app/src/uikit/components/LogoutButton.tsx +1 -3
- package/dist/templates/react-app/src/uikit/components/MessageBaseList.tsx +240 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/ChatMessageBridge.ts +176 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/ChatRoot.tsx +21 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/FocusBar.tsx +106 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/MessageApi.ts +271 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/MessageItem.tsx +102 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/MessagesList.tsx +86 -0
- package/dist/templates/react-app/src/uikit/hooks/useNavigateBridge.ts +9 -0
- package/dist/templates/react-app/src/uikit/hooks/{useI18nGuard.ts → useRouterI18nGuard.ts} +7 -4
- package/dist/templates/react-app/tsconfig.app.json +4 -2
- package/dist/templates/react-app/tsconfig.node.json +4 -0
- package/dist/templates/react-app/tsconfig.test.json +3 -1
- package/package.json +3 -3
- package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +0 -102
- package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +0 -61
- package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +0 -57
- package/dist/templates/react-app/src/uikit/components/ProcessExecutorProvider.tsx +0 -28
- package/dist/templates/react-app/src/uikit/components/UserAuthProvider.tsx +0 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# @qlover/create-app
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
#### ✨ Features
|
|
8
|
+
|
|
9
|
+
- **react-app:** Update TypeScript configurations and enhance user services ([6b33d18](https://github.com/qlover/fe-base/commit/6b33d18bdec4d0943a615d746e929348ea965031)) ([#545](https://github.com/qlover/fe-base/pull/545))
|
|
10
|
+
- Added path mappings for corekit-bridge and fe-corekit in TypeScript configuration files.
|
|
11
|
+
- Refactored integration tests to use updated import paths and improved error handling.
|
|
12
|
+
- Introduced a new BaseLayoutService for managing user authentication and layout rendering.
|
|
13
|
+
- Enhanced UserService with credential management and user information validation.
|
|
14
|
+
- Removed obsolete ProcesserExecutor and UserAuthProvider components to streamline the codebase.
|
|
15
|
+
- Updated mock API configurations for improved endpoint handling and response structures.
|
|
16
|
+
- Added new hooks for router internationalization and layout management.
|
|
17
|
+
|
|
18
|
+
## 0.9.0
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
#### ✨ Features
|
|
23
|
+
|
|
24
|
+
- **react-app:** integrate chat message components and enhance messaging functionality ([84af0f3](https://github.com/qlover/fe-base/commit/84af0f3913f191e79d747bffcc7d26283f6b861f)) ([#537](https://github.com/qlover/fe-base/pull/537))
|
|
25
|
+
- Introduced ChatMessage component system, including ChatRoot, MessagesList, and MessageItem for improved chat interactions.
|
|
26
|
+
- Added MessageBaseList component for testing message gateway functionality.
|
|
27
|
+
- Implemented internationalization support for chat and message components, enhancing user experience.
|
|
28
|
+
- Updated routing configuration to include a new MessagePage for managing chat messages.
|
|
29
|
+
- Enhanced Playwright testing setup for chat components, ensuring robust testing coverage.
|
|
30
|
+
|
|
31
|
+
These changes aim to provide a comprehensive chat experience, streamline message handling, and improve overall application functionality.
|
|
32
|
+
|
|
33
|
+
Co-authored-by: QRJ <github-actions[bot]@users.noreply.github.com>
|
|
34
|
+
|
|
3
35
|
## 0.8.0
|
|
4
36
|
|
|
5
37
|
### Minor Changes
|
|
@@ -31,7 +31,7 @@ jobs:
|
|
|
31
31
|
- name: Install Node.js
|
|
32
32
|
uses: actions/setup-node@v2
|
|
33
33
|
with:
|
|
34
|
-
node-version: '
|
|
34
|
+
node-version: '20.16.0'
|
|
35
35
|
|
|
36
36
|
- run: npm install -g pnpm
|
|
37
37
|
- run: pnpm install --frozen-lockfile
|
|
@@ -66,7 +66,7 @@ jobs:
|
|
|
66
66
|
- name: Install Node.js
|
|
67
67
|
uses: actions/setup-node@v2
|
|
68
68
|
with:
|
|
69
|
-
node-version: '
|
|
69
|
+
node-version: '20.16.0'
|
|
70
70
|
|
|
71
71
|
- run: npm install -g pnpm
|
|
72
72
|
- run: pnpm install --frozen-lockfile
|
package/dist/index.cjs
CHANGED
|
@@ -8,4 +8,4 @@ ${t}`,_n=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),mn=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,l=this.#g(a," "),p=u+i+s+l+`
|
|
10
10
|
`;return this.stop(),this.#u.write(p),this}};function rr(e){return new Ze(e)}async function ur(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=rr(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 bt=require("fs");var P=require("path"),me=require("fs"),fr=h(cr(),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:us,stat:is}=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,fr.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 is(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await us(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"),$D=h(kD(),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,$D.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 UD=["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,bt.existsSync)(r))throw new Error("template path not exit");this.ora=ur,this.context=new WD.ScriptContext("create-app",t),this.subPackages=["node-lib","react-app","next-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 HD.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 UD.includes(t)}async getGeneratorContext(){let t=wt(this.subPackages,UD),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=Pt(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=(a,l)=>(this.logger.debug("copyCallback",a,l),this.compose.composeConfigFile(t,a,l)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}let s=(0,v.join)(D,u);if(!(0,bt.existsSync)(s)){this.logger.debug(`Config path not found: ${s}`);return}await this.copyer.copyPaths({sourcePath:s,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 vt={name:"@qlover/create-app",version:"0.
|
|
11
|
+
`).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,fr.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 is(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await us(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"),$D=h(kD(),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,$D.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 UD=["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,bt.existsSync)(r))throw new Error("template path not exit");this.ora=ur,this.context=new WD.ScriptContext("create-app",t),this.subPackages=["node-lib","react-app","next-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 HD.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 UD.includes(t)}async getGeneratorContext(){let t=wt(this.subPackages,UD),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=Pt(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=(a,l)=>(this.logger.debug("copyCallback",a,l),this.compose.composeConfigFile(t,a,l)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}let s=(0,v.join)(D,u);if(!(0,bt.existsSync)(s)){this.logger.debug(`Config path not found: ${s}`);return}await this.copyer.copyPaths({sourcePath:s,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 vt={name:"@qlover/create-app",version:"0.10.0",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:{lint:"eslint src --fix",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 Wl(){let e=new YD.Command;return e.version(vt.version,"-v, --version","Show version").description(vt.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 VD(e=process.cwd()){let{dryRun:t,verbose:r,...u}=Wl(),i=(0,xt.resolve)(e,"./templates"),D=(0,xt.resolve)(e,"./configs");(0,yt.existsSync)(i)||(console.error("Template is empty!"),process.exit(1)),(0,yt.existsSync)(D)||(console.error("Configs is empty!"),process.exit(1)),await new ye({dryRun:t,verbose:r,options:{...u,templateRootPath:i,configsRootPath:D}}).generate()}VD(__dirname).catch(e=>{console.error(e),process.exit(1)});
|
package/dist/index.js
CHANGED
|
@@ -8,4 +8,4 @@ ${t}`,pD=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),hD=Objec
|
|
|
8
8
|
`))this.#e+=Math.max(1,Math.ceil(Le(n,{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.#n.frames.length,this.#c=t);let{frames:r}=this.#n,u=r[this.#i];this.color&&(u=E[this.color](u));let i=typeof this.#s=="string"&&this.#s!==""?this.#s+" ":"",n=typeof this.text=="string"?" "+this.text:"",o=typeof this.#o=="string"&&this.#o!==""?" "+this.#o:"";return i+u+n+o}clear(){if(!this.#a||!this.#u.isTTY)return this;this.#u.cursorTo(0);for(let t=0;t<this.#D;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.#D=0,this}render(){return this.#F?this:(this.clear(),this.#u.write(this.frame()),this.#D=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??" ",n=t.text??this.text,s=typeof n=="string"?(i?" ":"")+n:"",a=t.suffixText??this.#o,l=this.#g(a," "),p=u+i+s+l+`
|
|
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:n}=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(n===void 0?void 0:typeof n=="string"?n:n(s)),s}}import{existsSync as Nn}from"fs";var nr=ue(ir(),1);import{dirname as QD,join as Ze}from"path";import{existsSync as es,readFileSync as ts}from"fs";import{promises as Dr}from"fs";import{existsSync as JD,mkdirSync as ZD}from"fs";var H=class{static ensureDir(t){JD(t)||ZD(t,{recursive:!0})}};var{copyFile:rs,stat:us}=Dr,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(!es(r))return;let n=ts(r,"utf8").split(`
|
|
11
|
-
`).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,nr.default)().add(n)}async copyFiles(t,r,u,i){let n=await Dr.readdir(t);await Promise.all(n.map(async o=>{let s=Ze(t,o),a=Ze(r,o);if(u&&u.ignores(o))return;if(H.ensureDir(QD(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}){H.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var jn=ue(In(),1);import{readFileSync as $l,writeFileSync as Ul,existsSync as Wl}from"fs";var _e=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 $l(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){Ul(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let n=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof n=="string"?n:JSON.stringify(n))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,jn.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 n=this.getRealTemplateFilePath(u);return Wl(n)?(this.mergeJSONFile(n,JSON.parse(i)),!0):(this.writeFile(n,i),!0)}return this.writeFile(u,i),!0}return!1}};var Gn=["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(!Nn(r))throw new Error("template path not exit");this.ora=Kt,this.context=new Hl("create-app",t),this.subPackages=["node-lib","react-app","next-app"],this.copyer=new Ce(N(this.context.options.configsRootPath,"_common")),this.compose=new _e}get logger(){return this.context.logger}async steps(t){try{return await Yl.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 Gn.includes(t)}async getGeneratorContext(){let t=mt(this.subPackages,Gn),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=_t(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=(a,l)=>(this.logger.debug("copyCallback",a,l),this.compose.composeConfigFile(t,a,l)),{configsRootPath:n,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}let s=N(n,u);if(!Nn(s)){this.logger.debug(`Config path not found: ${s}`);return}await this.copyer.copyPaths({sourcePath:s,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:n}=this.context.options;for(let o of u){let s=N(n,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.
|
|
11
|
+
`).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,nr.default)().add(n)}async copyFiles(t,r,u,i){let n=await Dr.readdir(t);await Promise.all(n.map(async o=>{let s=Ze(t,o),a=Ze(r,o);if(u&&u.ignores(o))return;if(H.ensureDir(QD(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}){H.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var jn=ue(In(),1);import{readFileSync as $l,writeFileSync as Ul,existsSync as Wl}from"fs";var _e=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 $l(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){Ul(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let n=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof n=="string"?n:JSON.stringify(n))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,jn.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 n=this.getRealTemplateFilePath(u);return Wl(n)?(this.mergeJSONFile(n,JSON.parse(i)),!0):(this.writeFile(n,i),!0)}return this.writeFile(u,i),!0}return!1}};var Gn=["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(!Nn(r))throw new Error("template path not exit");this.ora=Kt,this.context=new Hl("create-app",t),this.subPackages=["node-lib","react-app","next-app"],this.copyer=new Ce(N(this.context.options.configsRootPath,"_common")),this.compose=new _e}get logger(){return this.context.logger}async steps(t){try{return await Yl.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 Gn.includes(t)}async getGeneratorContext(){let t=mt(this.subPackages,Gn),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=_t(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=(a,l)=>(this.logger.debug("copyCallback",a,l),this.compose.composeConfigFile(t,a,l)),{configsRootPath:n,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}let s=N(n,u);if(!Nn(s)){this.logger.debug(`Config path not found: ${s}`);return}await this.copyer.copyPaths({sourcePath:s,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:n}=this.context.options;for(let o of u){let s=N(n,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.10.0",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:{lint:"eslint src --fix",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 Kl(){let e=new zl;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 kn(e=process.cwd()){let{dryRun:t,verbose:r,...u}=Kl(),i=Mn(e,"./templates"),n=Mn(e,"./configs");Ln(i)||(console.error("Template is empty!"),process.exit(1)),Ln(n)||(console.error("Configs is empty!"),process.exit(1)),await new Be({dryRun:t,verbose:r,options:{...u,templateRootPath:i,configsRootPath:n}}).generate()}import{fileURLToPath as Xl}from"url";import{dirname as Jl}from"path";var Zl=Xl(import.meta.url),Ql=Jl(Zl);kn(Ql).catch(e=>{console.error(e),process.exit(1)});
|
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
# ci
|
|
2
2
|
NPM_TOKEN=
|
|
3
3
|
GITHUB_TOKEN=
|
|
4
|
-
|
|
5
4
|
# fe-scripts
|
|
6
5
|
FE_RELEASE_BRANCH=master
|
|
7
6
|
FE_RELEASE=false
|
|
8
7
|
FE_RELEASE_ENV=localhost
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
APP_HOST=http://localhost:3100
|
|
8
|
+
# ===== server
|
|
9
|
+
APP_HOST=
|
|
12
10
|
SUPABASE_URL=
|
|
13
11
|
SUPABASE_ANON_KEY=
|
|
14
12
|
JWT_SECRET=
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
CEREBRAS_API_KEY=
|
|
14
|
+
CEREBRAS_BASE_URL=
|
|
15
|
+
# ===== browser
|
|
17
16
|
NEXT_PUBLIC_USER_TOKEN_STORAGE_KEY=fe_user_token
|
|
18
17
|
NEXT_PUBLIC_OPEN_AI_MODELS='["gpt-4o-mini","gpt-3.5-turbo","gpt-3.5-turbo-2","gpt-4","gpt-4-32k"]'
|
|
19
18
|
NEXT_PUBLIC_OPEN_AI_BASE_URL=
|
|
20
19
|
NEXT_PUBLIC_OPEN_AI_TOKEN=sk-proj-1234567890
|
|
21
20
|
NEXT_PUBLIC_OPEN_AI_TOKEN_PREFIX=Bearer
|
|
22
21
|
NEXT_PUBLIC_OPEN_AI_REQUIRE_TOKEN=true
|
|
23
|
-
NEXT_PUBLIC_LOGIN_USER=
|
|
24
|
-
NEXT_PUBLIC_LOGIN_PASSWORD=
|
|
25
|
-
NEXT_PUBLIC_FE_API_BASE_URL=https://
|
|
26
|
-
NEXT_PUBLIC_STRING_ENCRYPT_KEY=
|
|
22
|
+
NEXT_PUBLIC_LOGIN_USER=xxxx
|
|
23
|
+
NEXT_PUBLIC_LOGIN_PASSWORD=xxx
|
|
24
|
+
NEXT_PUBLIC_FE_API_BASE_URL=https://xxx.example.com/
|
|
25
|
+
NEXT_PUBLIC_STRING_ENCRYPT_KEY=xxx!@#
|
|
@@ -173,6 +173,11 @@ const eslintConfig = [
|
|
|
173
173
|
singleQuote: true,
|
|
174
174
|
trailingComma: 'none',
|
|
175
175
|
endOfLine: 'lf'
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
// 仅用于单独部署时对 eslint prettier 插件自动查找 prettierrc 时报错
|
|
179
|
+
// 注意: vscode 等编辑器会失效, 作为单独项目开发时可以去掉
|
|
180
|
+
usePrettierrc: false
|
|
176
181
|
}
|
|
177
182
|
],
|
|
178
183
|
// 默认禁用 export default
|
|
@@ -2,7 +2,7 @@ import createNextIntlPlugin from 'next-intl/plugin';
|
|
|
2
2
|
import { generateLocales } from './make/generateLocales';
|
|
3
3
|
import type { NextConfig } from 'next';
|
|
4
4
|
|
|
5
|
-
const withNextIntl = createNextIntlPlugin();
|
|
5
|
+
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
|
|
6
6
|
|
|
7
7
|
// 在构建开始时生成本地化文件
|
|
8
8
|
generateLocales().catch((error) => {
|
|
@@ -17,7 +17,7 @@ export async function generateStaticParams() {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// Allow Next.js to statically generate this page if possible (default behavior)
|
|
20
|
-
|
|
20
|
+
// Note: 'auto' is not a valid value in Next.js 15, removed to use default behavior
|
|
21
21
|
|
|
22
22
|
// Optional: Use revalidate if you want ISR (Incremental Static Regeneration)
|
|
23
23
|
// export const revalidate = 3600; // Rebuild every hour (optional)
|
|
@@ -23,7 +23,7 @@ export async function generateStaticParams() {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
// Allow Next.js to statically generate this page if possible (default behavior)
|
|
26
|
-
|
|
26
|
+
// Note: 'auto' is not a valid value in Next.js 15, removed to use default behavior
|
|
27
27
|
|
|
28
28
|
// Optional: Use revalidate if you want ISR (Incremental Static Regeneration)
|
|
29
29
|
// export const revalidate = 3600; // Rebuild every hour (optional)
|
|
@@ -17,7 +17,7 @@ export async function generateStaticParams() {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// Allow Next.js to statically generate this page if possible (default behavior)
|
|
20
|
-
|
|
20
|
+
// Note: 'auto' is not a valid value in Next.js 15, removed to use default behavior
|
|
21
21
|
|
|
22
22
|
// Optional: Use revalidate if you want ISR (Incremental Static Regeneration)
|
|
23
23
|
// export const revalidate = 3600; // Rebuild every hour (optional)
|
|
@@ -5,7 +5,8 @@ import { ApiLocaleService } from '@/server/services/ApiLocaleService';
|
|
|
5
5
|
import { i18nConfig } from '@config/i18n';
|
|
6
6
|
import type { LocaleType } from '@config/i18n';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// Use literal value instead of imported config to ensure static analysis
|
|
9
|
+
export const revalidate = 60; // Cache time in seconds (matches i18nConfig.localeCacheTime)
|
|
9
10
|
|
|
10
11
|
export async function GET(req: NextRequest) {
|
|
11
12
|
const searchParams = Object.fromEntries(req.nextUrl.searchParams.entries());
|
|
@@ -27,7 +27,7 @@ export default getRequestConfig(async ({ requestLocale }) => {
|
|
|
27
27
|
onError: (error) => {
|
|
28
28
|
if (error.message.includes('MISSING_MESSAGE')) {
|
|
29
29
|
console.warn(`[i18n] Missing translation: ${error.message}`);
|
|
30
|
-
return error.
|
|
30
|
+
return error.message; // 返回 key 作为 fallback 文本
|
|
31
31
|
}
|
|
32
32
|
throw error; // 其他错误仍然抛出
|
|
33
33
|
}
|
|
@@ -42,7 +42,7 @@ export default getRequestConfig(async ({ requestLocale }) => {
|
|
|
42
42
|
onError: (error) => {
|
|
43
43
|
if (error.message.includes('MISSING_MESSAGE')) {
|
|
44
44
|
console.warn(`[i18n] Missing translation: ${error.message}`);
|
|
45
|
-
return error.
|
|
45
|
+
return error.message; // 返回 key 作为 fallback 文本
|
|
46
46
|
}
|
|
47
47
|
throw error; // 其他错误仍然抛出
|
|
48
48
|
}
|
|
@@ -1,18 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Initial URL path for the router
|
|
7
|
-
* @default ['/en/']
|
|
8
|
-
*/
|
|
9
|
-
routerInitialEntries?: string[];
|
|
10
|
-
/**
|
|
11
|
-
* Initial index of the entries array
|
|
12
|
-
* @default 0
|
|
13
|
-
*/
|
|
14
|
-
routerInitialIndex?: number;
|
|
15
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
TestBootstrapsProvider,
|
|
3
|
+
type TestBootstrapsProviderProps
|
|
4
|
+
} from './TestBootstrapsProvider';
|
|
16
5
|
|
|
17
6
|
/**
|
|
18
7
|
* TestApp - Complete test wrapper with IOC and Router
|
|
@@ -31,13 +20,17 @@ interface TestAppProps {
|
|
|
31
20
|
export function TestApp({
|
|
32
21
|
children,
|
|
33
22
|
routerInitialEntries,
|
|
34
|
-
routerInitialIndex
|
|
35
|
-
|
|
23
|
+
routerInitialIndex,
|
|
24
|
+
bootHref,
|
|
25
|
+
appConfig
|
|
26
|
+
}: TestBootstrapsProviderProps) {
|
|
36
27
|
return (
|
|
37
28
|
<TestBootstrapsProvider
|
|
38
29
|
data-testid="TestApp"
|
|
39
30
|
routerInitialEntries={routerInitialEntries}
|
|
40
31
|
routerInitialIndex={routerInitialIndex}
|
|
32
|
+
bootHref={bootHref}
|
|
33
|
+
appConfig={appConfig}
|
|
41
34
|
>
|
|
42
35
|
{children}
|
|
43
36
|
</TestBootstrapsProvider>
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
+
import { appConfig as globalsAppConfig } from '@/core/globals';
|
|
1
2
|
import { IOCContext } from '@/uikit/contexts/IOCContext';
|
|
2
3
|
import { TestRouter } from './TestRouter';
|
|
3
4
|
import { testIOC } from '../testIOC/TestIOC';
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
children,
|
|
7
|
-
routerInitialEntries,
|
|
8
|
-
routerInitialIndex
|
|
9
|
-
}: {
|
|
5
|
+
import type { EnvConfigInterface } from '@qlover/corekit-bridge';
|
|
6
|
+
export interface TestBootstrapsProviderProps {
|
|
10
7
|
children: React.ReactNode;
|
|
11
8
|
/**
|
|
12
9
|
* Initial URL path for the router
|
|
@@ -18,8 +15,30 @@ export function TestBootstrapsProvider({
|
|
|
18
15
|
* @default 0
|
|
19
16
|
*/
|
|
20
17
|
routerInitialIndex?: number;
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The boot href
|
|
21
|
+
*
|
|
22
|
+
* @default `https://localhost.test:3000/en/`
|
|
23
|
+
*/
|
|
24
|
+
bootHref?: string;
|
|
25
|
+
|
|
26
|
+
appConfig?: EnvConfigInterface;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const defaultBootHref = 'https://localhost.test:3000/en/';
|
|
30
|
+
|
|
31
|
+
export function TestBootstrapsProvider({
|
|
32
|
+
children,
|
|
33
|
+
routerInitialEntries,
|
|
34
|
+
routerInitialIndex,
|
|
35
|
+
bootHref,
|
|
36
|
+
appConfig
|
|
37
|
+
}: TestBootstrapsProviderProps) {
|
|
38
|
+
const IOC = testIOC.create({
|
|
39
|
+
pathname: bootHref ?? defaultBootHref,
|
|
40
|
+
appConfig: appConfig ?? globalsAppConfig
|
|
41
|
+
});
|
|
23
42
|
|
|
24
43
|
return (
|
|
25
44
|
<IOCContext.Provider data-testid="TestBootstrapsProvider" value={IOC}>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock i18next-http-backend for testing
|
|
3
|
+
*
|
|
4
|
+
* This mock prevents network requests while still allowing i18next to work properly.
|
|
5
|
+
* It loads translation data directly from JSON files, enabling translation testing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import enCommon from '../../public/locales/en/common.json';
|
|
9
|
+
import zhCommon from '../../public/locales/zh/common.json';
|
|
10
|
+
import type { i18n as I18nType } from 'i18next';
|
|
11
|
+
|
|
12
|
+
// Translation resources loaded from JSON files
|
|
13
|
+
const resources: Record<string, Record<string, Record<string, string>>> = {
|
|
14
|
+
en: {
|
|
15
|
+
common: enCommon as Record<string, string>
|
|
16
|
+
},
|
|
17
|
+
zh: {
|
|
18
|
+
common: zhCommon as Record<string, string>
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Mock HttpApi backend class for i18next
|
|
24
|
+
* Implements the backend interface to avoid network requests
|
|
25
|
+
*/
|
|
26
|
+
export class MockHttpBackend {
|
|
27
|
+
type = 'backend';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initialize the backend
|
|
31
|
+
* This is called by i18next when .use() is called
|
|
32
|
+
*/
|
|
33
|
+
init(
|
|
34
|
+
_services: unknown,
|
|
35
|
+
_backendOptions: unknown,
|
|
36
|
+
_i18nextOptions: unknown,
|
|
37
|
+
i18nextInstance: I18nType
|
|
38
|
+
): void {
|
|
39
|
+
// Preload resources directly into i18next
|
|
40
|
+
if (
|
|
41
|
+
i18nextInstance &&
|
|
42
|
+
typeof i18nextInstance.addResourceBundle === 'function'
|
|
43
|
+
) {
|
|
44
|
+
// Add resources for all languages and namespaces
|
|
45
|
+
Object.keys(resources).forEach((lng) => {
|
|
46
|
+
Object.keys(resources[lng]).forEach((ns) => {
|
|
47
|
+
i18nextInstance.addResourceBundle(
|
|
48
|
+
lng,
|
|
49
|
+
ns,
|
|
50
|
+
resources[lng][ns],
|
|
51
|
+
true,
|
|
52
|
+
true
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Read translation data for a language and namespace
|
|
61
|
+
* This method is called by i18next to load translations
|
|
62
|
+
*/
|
|
63
|
+
read(
|
|
64
|
+
language: string,
|
|
65
|
+
namespace: string,
|
|
66
|
+
callback: (error: Error | null, data?: Record<string, string>) => void
|
|
67
|
+
): void {
|
|
68
|
+
try {
|
|
69
|
+
const data = resources[language]?.[namespace];
|
|
70
|
+
if (data) {
|
|
71
|
+
// Return immediately with the data
|
|
72
|
+
callback(null, data);
|
|
73
|
+
} else {
|
|
74
|
+
callback(new Error(`Translation not found: ${language}/${namespace}`));
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
callback(error as Error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Load URL - called by i18next-http-backend to load from URL
|
|
83
|
+
* We intercept this and return data from memory instead
|
|
84
|
+
*/
|
|
85
|
+
loadUrl(
|
|
86
|
+
_url: string,
|
|
87
|
+
callback: (error: Error | null, data?: Record<string, string>) => void
|
|
88
|
+
): void {
|
|
89
|
+
// Extract language and namespace from URL if possible
|
|
90
|
+
// Format: /locales/{{lng}}/{{ns}}.json
|
|
91
|
+
const match = _url.match(/locales\/([^/]+)\/([^/]+)\.json/);
|
|
92
|
+
if (match) {
|
|
93
|
+
const [, lng, ns] = match;
|
|
94
|
+
const data = resources[lng]?.[ns];
|
|
95
|
+
if (data) {
|
|
96
|
+
callback(null, data);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Fallback: return empty object
|
|
101
|
+
callback(null, {});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Create - not used in our mock
|
|
106
|
+
*/
|
|
107
|
+
create(): void {
|
|
108
|
+
// No-op
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { baseNoLocaleRoutes, baseRoutes } from '@config/app.router';
|
|
2
|
-
import { useLocaleRoutes } from '@config/common';
|
|
2
|
+
import { routerPrefix, useLocaleRoutes } from '@config/common';
|
|
3
3
|
import { I } from '@config/IOCIdentifier';
|
|
4
4
|
import { themeConfig } from '@config/theme';
|
|
5
5
|
import { ThemeService } from '@qlover/corekit-bridge';
|
|
@@ -54,7 +54,8 @@ export class TestIOCRegister
|
|
|
54
54
|
{
|
|
55
55
|
routes: useLocaleRoutes ? baseRoutes : baseNoLocaleRoutes,
|
|
56
56
|
logger: ioc.get(I.Logger),
|
|
57
|
-
hasLocalRoutes: useLocaleRoutes
|
|
57
|
+
hasLocalRoutes: useLocaleRoutes,
|
|
58
|
+
routerPrefix: routerPrefix
|
|
58
59
|
}
|
|
59
60
|
)
|
|
60
61
|
);
|
|
@@ -49,3 +49,16 @@ global.IntersectionObserver = vi.fn().mockImplementation(() => ({
|
|
|
49
49
|
|
|
50
50
|
// Mock globals
|
|
51
51
|
vi.mock('@/core/globals', () => createMockGlobals());
|
|
52
|
+
|
|
53
|
+
// Mock i18next-http-backend to avoid network requests in tests
|
|
54
|
+
// This prevents timeout issues when I18nService tries to load language files
|
|
55
|
+
// The mock loads translations directly from JSON files, enabling translation testing
|
|
56
|
+
vi.mock('i18next-http-backend', async () => {
|
|
57
|
+
const { MockHttpBackend } = await import('@__mocks__/i18nextHttpBackend');
|
|
58
|
+
return { default: MockHttpBackend };
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Mock i18next-browser-languagedetector to avoid browser API calls
|
|
62
|
+
vi.mock('i18next-browser-languagedetector', () => ({
|
|
63
|
+
default: {}
|
|
64
|
+
}));
|
|
@@ -205,7 +205,8 @@ describe('I18nService', () => {
|
|
|
205
205
|
const result = service.t(key);
|
|
206
206
|
expect(result).toBe('translated_test.key');
|
|
207
207
|
expect(i18n.t).toHaveBeenCalledWith(key, {
|
|
208
|
-
lng: 'en'
|
|
208
|
+
lng: 'en',
|
|
209
|
+
nsSeparator: false
|
|
209
210
|
});
|
|
210
211
|
});
|
|
211
212
|
|
|
@@ -216,6 +217,7 @@ describe('I18nService', () => {
|
|
|
216
217
|
expect(result).toBe('translated_test.key');
|
|
217
218
|
expect(i18n.t).toHaveBeenCalledWith(key, {
|
|
218
219
|
lng: 'en',
|
|
220
|
+
nsSeparator: false,
|
|
219
221
|
...params
|
|
220
222
|
});
|
|
221
223
|
});
|