@qlover/create-app 0.7.13 → 0.7.15

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 (93) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/templates/next-app/README.en.md +131 -0
  5. package/dist/templates/next-app/README.md +115 -20
  6. package/dist/templates/next-app/config/IOCIdentifier.ts +14 -1
  7. package/dist/templates/next-app/config/Identifier/index.ts +1 -0
  8. package/dist/templates/next-app/config/Identifier/page.admin.ts +48 -0
  9. package/dist/templates/next-app/config/i18n/admin18n.ts +33 -0
  10. package/dist/templates/next-app/config/i18n/index.ts +3 -1
  11. package/dist/templates/next-app/docs/en/api.md +387 -0
  12. package/dist/templates/next-app/docs/en/component.md +544 -0
  13. package/dist/templates/next-app/docs/en/database.md +496 -0
  14. package/dist/templates/next-app/docs/en/development-guide.md +727 -0
  15. package/dist/templates/next-app/docs/en/env.md +563 -0
  16. package/dist/templates/next-app/docs/en/i18n.md +287 -0
  17. package/dist/templates/next-app/docs/en/index.md +166 -0
  18. package/dist/templates/next-app/docs/en/page.md +457 -0
  19. package/dist/templates/next-app/docs/en/project-structure.md +177 -0
  20. package/dist/templates/next-app/docs/en/router.md +427 -0
  21. package/dist/templates/next-app/docs/en/theme.md +532 -0
  22. package/dist/templates/next-app/docs/en/validator.md +478 -0
  23. package/dist/templates/next-app/docs/zh/api.md +387 -0
  24. package/dist/templates/next-app/docs/zh/component.md +544 -0
  25. package/dist/templates/next-app/docs/zh/database.md +496 -0
  26. package/dist/templates/next-app/docs/zh/development-guide.md +727 -0
  27. package/dist/templates/next-app/docs/zh/env.md +563 -0
  28. package/dist/templates/next-app/docs/zh/i18n.md +287 -0
  29. package/dist/templates/next-app/docs/zh/index.md +166 -0
  30. package/dist/templates/next-app/docs/zh/page.md +457 -0
  31. package/dist/templates/next-app/docs/zh/project-structure.md +177 -0
  32. package/dist/templates/next-app/docs/zh/router.md +427 -0
  33. package/dist/templates/next-app/docs/zh/theme.md +532 -0
  34. package/dist/templates/next-app/docs/zh/validator.md +476 -0
  35. package/dist/templates/next-app/migrations/schema/UserSchema.ts +2 -2
  36. package/dist/templates/next-app/next.config.ts +1 -1
  37. package/dist/templates/next-app/package.json +3 -1
  38. package/dist/templates/next-app/public/locales/en.json +8 -1
  39. package/dist/templates/next-app/public/locales/zh.json +8 -1
  40. package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +1 -1
  41. package/dist/templates/next-app/src/app/[locale]/admin/page.tsx +14 -16
  42. package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +10 -3
  43. package/dist/templates/next-app/src/app/[locale]/layout.tsx +1 -1
  44. package/dist/templates/next-app/src/app/[locale]/login/page.tsx +1 -1
  45. package/dist/templates/next-app/src/app/[locale]/page.tsx +2 -3
  46. package/dist/templates/next-app/src/app/[locale]/register/page.tsx +1 -1
  47. package/dist/templates/next-app/src/app/api/ai/completions/route.ts +32 -0
  48. package/dist/templates/next-app/src/base/cases/AppConfig.ts +3 -0
  49. package/dist/templates/next-app/src/base/cases/ChatAction.ts +21 -0
  50. package/dist/templates/next-app/src/base/cases/FocusBarAction.ts +36 -0
  51. package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +1 -3
  52. package/dist/templates/next-app/src/base/services/AdminUserService.ts +1 -1
  53. package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +1 -1
  54. package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +23 -1
  55. package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +2 -2
  56. package/dist/templates/next-app/src/base/types/PageProps.ts +1 -1
  57. package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +1 -0
  58. package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +1 -0
  59. package/dist/templates/next-app/src/core/globals.ts +2 -0
  60. package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +4 -1
  61. package/dist/templates/next-app/src/{base/cases → server}/PageParams.ts +1 -1
  62. package/dist/templates/next-app/src/server/port/DBBridgeInterface.ts +31 -0
  63. package/dist/templates/next-app/src/server/port/DBTableInterface.ts +1 -1
  64. package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +6 -4
  65. package/dist/templates/next-app/src/server/services/AIService.ts +43 -0
  66. package/dist/templates/next-app/src/server/services/ApiUserService.ts +1 -1
  67. package/dist/templates/next-app/src/server/{SupabaseBridge.ts → sqlBridges/SupabaseBridge.ts} +16 -11
  68. package/dist/templates/next-app/src/server/validators/LoginValidator.ts +4 -4
  69. package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +1 -1
  70. package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +32 -25
  71. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +12 -26
  72. package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +37 -5
  73. package/dist/templates/next-app/src/uikit/components/ChatRoot.tsx +17 -0
  74. package/dist/templates/next-app/src/uikit/components/ClientSeo.tsx +36 -0
  75. package/dist/templates/next-app/src/uikit/components/LanguageSwitcher.tsx +5 -6
  76. package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +2 -0
  77. package/dist/templates/next-app/src/uikit/components/With.tsx +17 -0
  78. package/dist/templates/next-app/src/uikit/components/chat/ChatActionInterface.ts +30 -0
  79. package/dist/templates/next-app/src/uikit/components/chat/ChatFocusBar.tsx +65 -0
  80. package/dist/templates/next-app/src/uikit/components/chat/ChatMessages.tsx +59 -0
  81. package/dist/templates/next-app/src/uikit/components/chat/ChatWrap.tsx +28 -0
  82. package/dist/templates/next-app/src/uikit/components/chat/FocusBarActionInterface.ts +19 -0
  83. package/package.json +1 -1
  84. package/dist/templates/next-app/docs/env.md +0 -94
  85. package/dist/templates/next-app/src/base/port/DBBridgeInterface.ts +0 -21
  86. package/dist/templates/next-app/src/base/port/DBMigrationInterface.ts +0 -92
  87. package/dist/templates/next-app/src/base/port/MigrationApiInterface.ts +0 -3
  88. package/dist/templates/next-app/src/base/port/ServerApiResponseInterface.ts +0 -6
  89. package/dist/templates/next-app/src/base/services/migrations/MigrationsApi.ts +0 -43
  90. package/dist/templates/next-app/config/i18n/{HomeI18n .ts → HomeI18n.ts} +0 -0
  91. package/dist/templates/next-app/{build → make}/generateLocales.ts +2 -2
  92. /package/dist/templates/next-app/src/{base → server}/port/PaginationInterface.ts +0 -0
  93. /package/dist/templates/next-app/src/{base → server}/port/ParamsHandlerInterface.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,94 @@
1
1
  # @qlover/create-app
2
2
 
3
+ ## 0.7.15
4
+
5
+ ### Patch Changes
6
+
7
+ #### ✨ Features
8
+
9
+ - **next-app:** enhance README and documentation for full-stack application template ([198e829](https://github.com/qlover/fe-base/commit/198e829719dfabb2261aa939c6ae69cdba45c9f0)) ([#522](https://github.com/qlover/fe-base/pull/522))
10
+ - Translated README.md to Chinese and expanded content to provide a comprehensive overview of the Next.js full-stack application template.
11
+ - Added detailed sections on features, project structure, environment requirements, and quick start instructions.
12
+ - Introduced new documentation files covering API development, component development, database management, internationalization, and validation guidelines.
13
+ - Removed outdated env.md file and replaced it with a more comprehensive environment variable configuration guide.
14
+ - Improved organization and clarity of documentation to facilitate better understanding and onboarding for developers.
15
+
16
+ These changes aim to enhance the usability and accessibility of the project documentation, supporting developers in leveraging the full capabilities of the template.
17
+
18
+ - **next-app:** add comprehensive documentation for full-stack application template ([50029df](https://github.com/qlover/fe-base/commit/50029df11026b5f7e7ea0ae49d0938ed955971d3)) ([#522](https://github.com/qlover/fe-base/pull/522))
19
+ - Introduced new documentation files covering API development, component architecture, database management, internationalization, and validation guidelines.
20
+ - Enhanced the README with detailed project features, structure, and quick start instructions.
21
+ - Added environment variable configuration guide to streamline setup across different environments.
22
+ - Improved organization and clarity of documentation to facilitate better understanding and onboarding for developers.
23
+
24
+ These changes aim to enhance the usability and accessibility of the project documentation, supporting developers in leveraging the full capabilities of the template.
25
+
26
+ ## 0.7.14
27
+
28
+ ### Patch Changes
29
+
30
+ #### ✨ Features
31
+
32
+ - **next-app:** integrate SupabaseBridge and update database handling ([5d84568](https://github.com/qlover/fe-base/commit/5d84568849170c998c81b663f0eb5f5f144d8da3)) ([#518](https://github.com/qlover/fe-base/pull/518))
33
+ - Added SupabaseBridge for managing database interactions, implementing the DBBridgeInterface for standardized operations.
34
+ - Updated UserRepository to utilize the new DBBridgeInterface for database operations.
35
+ - Enhanced AppConfig to switch OpenAI configuration to Cerebras.
36
+ - Refactored UserSchema validation to improve type safety with Zod.
37
+ - Introduced new pagination handling in DBBridgeInterface for better data management.
38
+
39
+ These changes aim to enhance database management capabilities and improve the overall structure of data handling within the application.
40
+
41
+ - **next-app:** add HomeI18n for localization and enhance layout components ([390a637](https://github.com/qlover/fe-base/commit/390a6375b458c57ca69517d3e4cf1b938f7b4aea)) ([#518](https://github.com/qlover/fe-base/pull/518))
42
+ - Introduced HomeI18n interface and constants for home page localization, improving internationalization support.
43
+ - Updated index.ts to export HomeI18n for easier access across the application.
44
+ - Refactored page.tsx to integrate homeI18n, enhancing metadata handling for the home page.
45
+ - Improved AdminLayout and BaseHeader components by adding right action buttons for language switching, theme toggling, and logout functionality.
46
+ - Enhanced BaseLayout to conditionally render admin navigation elements, improving user experience in the admin section.
47
+
48
+ These changes aim to provide better localization support and enhance the overall structure and usability of the application.
49
+
50
+ - **next-app:** add admin page localization and SEO components ([d8ca688](https://github.com/qlover/fe-base/commit/d8ca68861c5b2dc80763622b2f37de6ea0cfb437)) ([#518](https://github.com/qlover/fe-base/pull/518))
51
+ - Introduced new localization constants for the admin page, enhancing internationalization support.
52
+ - Created admin18n interface for managing admin page metadata and content.
53
+ - Added ClientSeo component for improved SEO handling on the admin page.
54
+ - Updated admin page structure to utilize new localization and SEO features, enhancing user experience.
55
+ - Refactored localization files to include new keys for admin page content in both English and Chinese.
56
+
57
+ These changes aim to improve the admin page's usability and visibility through better localization and SEO practices.
58
+
59
+ #### 🐞 Bug Fixes
60
+
61
+ - **create-app:** change build to make ([ea48d14](https://github.com/qlover/fe-base/commit/ea48d140e6f7684efc2e3097046bce82b6448d14)) ([#518](https://github.com/qlover/fe-base/pull/518))
62
+
63
+ #### ♻️ Refactors
64
+
65
+ - **next-app:** remove i18nService dependency from layout components ([c4647fa](https://github.com/qlover/fe-base/commit/c4647fa3a51a72354a70e200dd92439d64548fa2)) ([#520](https://github.com/qlover/fe-base/pull/520))
66
+ - Eliminated the i18nService dependency from AdminLayout and BaseLayout components to simplify the code structure.
67
+ - Updated LanguageSwitcher to directly use the i18nService via IOC, enhancing encapsulation and reducing prop drilling.
68
+ - Adjusted memoization dependencies in right action buttons to reflect the removal of i18nService.
69
+
70
+ These changes aim to streamline the layout components and improve maintainability by reducing unnecessary dependencies.
71
+
72
+ - **next-app:** reorganize imports and migrate PageParams to server ([fb6a47b](https://github.com/qlover/fe-base/commit/fb6a47b5b3a293b7d3e8488690e13b1509e9cfde)) ([#518](https://github.com/qlover/fe-base/pull/518))
73
+ - Updated import paths for PageParams and DBBridgeInterface to reflect new server structure.
74
+ - Removed obsolete migration-related interfaces and classes to streamline the codebase.
75
+ - Introduced new PageParams class in the server directory to handle localization and parameter management.
76
+
77
+ These changes aim to enhance code organization and improve the clarity of parameter handling within the application.
78
+
79
+ - **next-app:** update PaginationInterface imports and add new file ([dfd9866](https://github.com/qlover/fe-base/commit/dfd98660ea51989e604cb7d78d935590473bc05b)) ([#518](https://github.com/qlover/fe-base/pull/518))
80
+ - Changed import paths for PaginationInterface across multiple files to reflect its new location in the server directory.
81
+ - Introduced a new PaginationInterface file in the server port directory, defining the structure for pagination handling.
82
+
83
+ These changes aim to improve code organization and maintainability by centralizing pagination-related definitions.
84
+
85
+ - **next-app:** reorganize imports and update dependencies ([a1049f4](https://github.com/qlover/fe-base/commit/a1049f4bd61b012ca1938c803bdc3a4c43ef0c9a)) ([#518](https://github.com/qlover/fe-base/pull/518))
86
+ - Removed duplicate import statements in generateLocales.ts for cleaner code.
87
+ - Updated useEffect dependency array in UsersPage to include adminUserService, ensuring proper initialization.
88
+ - Cleaned up globals.ts by removing unnecessary comments, enhancing readability.
89
+
90
+ These changes aim to improve code organization and maintainability across the application.
91
+
3
92
  ## 0.7.13
4
93
 
5
94
  ### Patch Changes
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.7.13",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 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)});
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.7.15",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 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.7.13",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 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)});
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.7.15",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 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)});
@@ -0,0 +1,131 @@
1
+ # Next.js Full-Stack Application Template
2
+
3
+ A full-stack application template based on Next.js, implementing a clear front-end and back-end layered architecture using an interface-driven design pattern.
4
+
5
+ [中文](./README.md)
6
+
7
+ ## 🌟 Key Features
8
+
9
+ - 🏗️ Full-stack application architecture based on Next.js
10
+ - 🔌 Interface-Driven Development pattern
11
+ - 🎨 Theme system integrated with Tailwind CSS
12
+ - 🌍 Comprehensive internationalization support (English & Chinese)
13
+ - 🔄 TypeScript-based IOC container
14
+ - 🛡️ Complete authentication and authorization system
15
+ - 📡 Layered API architecture (Controllers, Services, Repositories)
16
+ - 🎮 State management and page controller pattern
17
+ - 🔗 SQL database bridging layer
18
+ - 📦 Package management with pnpm
19
+
20
+ ## 🔧 Requirements
21
+
22
+ - Node.js >= 16
23
+ - pnpm >= 8.0
24
+
25
+ ## 📁 Project Structure
26
+
27
+ ```tree
28
+ ├── config/ # Configuration directory
29
+ │ ├── i18n/ # Internationalization config
30
+ │ ├── Identifier/ # Dependency injection identifiers
31
+ │ ├── common.ts # Common app configuration
32
+ │ ├── IOCIdentifier.ts # IOC container configuration
33
+ │ └── theme.ts # Theme configuration
34
+ ├── public/ # Static assets directory
35
+ ├── src/
36
+ │ ├── app/ # Next.js app directory
37
+ │ │ ├── [locale]/ # Internationalized routes
38
+ │ │ ├── api/ # API route handlers
39
+ │ │ └── layout.tsx # Application layout
40
+ │ ├── base/ # Client-side base code
41
+ │ │ ├── port/ # Client interface definitions
42
+ │ │ ├── cases/ # Business case implementations
43
+ │ │ ├── services/ # Client service implementations
44
+ │ │ └── types/ # Type definitions
45
+ │ ├── server/ # Server-side code
46
+ │ │ ├── port/ # Server interface definitions
47
+ │ │ ├── services/ # Service implementations
48
+ │ │ ├── repositorys/ # Data repositories
49
+ │ │ ├── validators/ # Request validators
50
+ │ │ └── sqlBridges/ # Database bridging layer
51
+ │ ├── uikit/ # UI component library
52
+ │ │ ├── components/ # Reusable components
53
+ │ │ ├── context/ # React Context
54
+ │ │ └── hook/ # React Hooks
55
+ │ └── styles/ # Style files
56
+ └── next.config.ts # Next.js configuration file
57
+ ```
58
+
59
+ ## 🚀 Quick Start
60
+
61
+ ### Install Dependencies
62
+
63
+ ```bash
64
+ pnpm install
65
+ ```
66
+
67
+ ### Development Mode
68
+
69
+ ```bash
70
+ pnpm dev
71
+ # cross-env APP_ENV=localhost next dev --turbopack --port 3100
72
+ # Automatically loads .env.localhost -> .env
73
+
74
+ pnpm dev:staging
75
+ # cross-env APP_ENV=staging next dev --turbopack --port 3100
76
+ # Automatically loads .env.staging -> .env
77
+ ```
78
+
79
+ ### Build Project
80
+
81
+ ```bash
82
+ pnpm build
83
+ ```
84
+
85
+ ## 📚 Documentation Guide
86
+
87
+ The project provides detailed development documentation covering all major features and best practices:
88
+
89
+ ### Basic Documentation
90
+
91
+ - [Project Overview](./docs/en/index.md) - Project introduction and quick start guide
92
+ - [Project Structure](./docs/en/project-structure.md) - Detailed project directory structure explanation
93
+ - [Development Guide](./docs/en/development-guide.md) - Project development standards and best practices
94
+ - [Environment Configuration](./docs/en/env.md) - Environment variables and configuration management
95
+ - [Global Configuration](./docs/en/global.md) - Application global configuration and settings
96
+
97
+ ### Core Features
98
+
99
+ - [Bootstrap Process](./docs/en/bootstrap.md) - Application startup process and lifecycle management
100
+ - [IOC Container](./docs/en/ioc.md) - Dependency injection system usage guide
101
+ - [Router Management](./docs/en/router.md) - Route configuration and page navigation
102
+ - [State Management](./docs/en/store.md) - Application state management solution
103
+ - [Request Handling](./docs/en/request.md) - API request handling mechanism
104
+
105
+ ### Feature Extensions
106
+
107
+ - [Internationalization](./docs/en/i18n.md) - Multi-language support and translation management
108
+ - [Theme System](./docs/en/theme.md) - Theme configuration and dark mode support
109
+ - [TypeScript Guide](./docs/en/typescript-guide.md) - TypeScript usage standards and best practices
110
+
111
+ ## 🔨 Architecture Design
112
+
113
+ ### Interface-Driven Design Pattern
114
+
115
+ The project adopts an interface-driven design pattern, achieving decoupling and testability through interface definitions:
116
+
117
+ #### Client Interfaces (src/base/port)
118
+
119
+ - **AppUserApiInterface**: User authentication related API interface
120
+ - **AdminPageInterface**: Admin page base interface
121
+ - **AsyncStateInterface**: Asynchronous state management interface
122
+ - **RouterInterface**: Router management interface
123
+ - **I18nServiceInterface**: Internationalization service interface
124
+
125
+ #### Server Interfaces (src/server/port)
126
+
127
+ - **ServerAuthInterface**: Server authentication interface
128
+ - **DBBridgeInterface**: Database operation bridging interface
129
+ - **UserRepositoryInterface**: User data repository interface
130
+ - **ValidatorInterface**: Data validation interface
131
+ - **ParamsHandlerInterface**: Parameter handling interface
@@ -1,36 +1,131 @@
1
- This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
1
+ # Next.js Full-Stack Application Template
2
2
 
3
- ## Getting Started
3
+ 一个基于 Next.js 的全栈应用模板,采用面向接口的设计模式,实现了清晰的前后端分层架构。
4
4
 
5
- First, run the development server:
5
+ [English](./README.en.md)
6
+
7
+ ## 🌟 特性亮点
8
+
9
+ - 🏗️ 基于 Next.js 的全栈应用架构
10
+ - 🔌 面向接口的设计模式(Interface-Driven Development)
11
+ - 🎨 集成 Tailwind CSS 的主题系统
12
+ - 🌍 完善的国际化支持(中英文)
13
+ - 🔄 基于 TypeScript 的 IOC 容器
14
+ - 🛡️ 完整的身份验证和授权系统
15
+ - 📡 分层的 API 架构(控制器、服务、仓库)
16
+ - 🎮 状态管理与页面控制器模式
17
+ - 🔗 SQL 数据库桥接层
18
+ - 📦 使用 pnpm 进行包管理
19
+
20
+ ## 🔧 环境要求
21
+
22
+ - Node.js >= 16
23
+ - pnpm >= 8.0
24
+
25
+ ## 📁 项目结构
26
+
27
+ ```tree
28
+ ├── config/ # 配置文件目录
29
+ │ ├── i18n/ # 国际化配置
30
+ │ ├── Identifier/ # 依赖注入标识符
31
+ │ ├── common.ts # 应用通用配置
32
+ │ ├── IOCIdentifier.ts # IOC容器配置
33
+ │ └── theme.ts # 主题配置
34
+ ├── public/ # 静态资源目录
35
+ ├── src/
36
+ │ ├── app/ # Next.js 应用目录
37
+ │ │ ├── [locale]/ # 国际化路由
38
+ │ │ ├── api/ # API 路由处理器
39
+ │ │ └── layout.tsx # 应用布局
40
+ │ ├── base/ # 客户端基础代码
41
+ │ │ ├── port/ # 客户端接口定义
42
+ │ │ ├── cases/ # 业务用例实现
43
+ │ │ ├── services/ # 客户端服务实现
44
+ │ │ └── types/ # 类型定义
45
+ │ ├── server/ # 服务端代码
46
+ │ │ ├── port/ # 服务端接口定义
47
+ │ │ ├── services/ # 服务实现
48
+ │ │ ├── repositorys/ # 数据仓库
49
+ │ │ ├── validators/ # 请求验证器
50
+ │ │ └── sqlBridges/ # 数据库桥接层
51
+ │ ├── uikit/ # UI 组件库
52
+ │ │ ├── components/ # 可复用组件
53
+ │ │ ├── context/ # React Context
54
+ │ │ └── hook/ # React Hooks
55
+ │ └── styles/ # 样式文件
56
+ └── next.config.ts # Next.js 配置文件
57
+ ```
58
+
59
+ ## 🚀 快速开始
60
+
61
+ ### 安装依赖
62
+
63
+ ```bash
64
+ pnpm install
65
+ ```
66
+
67
+ ### 开发模式
6
68
 
7
69
  ```bash
8
- npm run dev
9
- # or
10
- yarn dev
11
- # or
12
70
  pnpm dev
13
- # or
14
- bun dev
71
+ # cross-env APP_ENV=localhost next dev --turbopack --port 3100
72
+ # 自动加载 .env.localhost -> .env
73
+
74
+ pnpm dev:staging
75
+ # cross-env APP_ENV=staging next dev --turbopack --port 3100
76
+ # 自动加载 .env.staging -> .env
77
+ ```
78
+
79
+ ### 构建项目
80
+
81
+ ```bash
82
+ pnpm build
15
83
  ```
16
84
 
17
- Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
85
+ ## 📚 文档指南
86
+
87
+ 项目提供了详细的开发文档,涵盖了所有主要功能和最佳实践:
88
+
89
+ ### 基础文档
90
+
91
+ - [项目概述](./docs/zh/index.md) - 项目整体介绍和快速开始指南
92
+ - [项目结构](./docs/zh/project-structure.md) - 详细的项目目录结构说明
93
+ - [开发指南](./docs/zh/development-guide.md) - 项目开发规范和最佳实践
94
+ - [环境配置](./docs/zh/env.md) - 环境变量和配置管理说明
95
+ - [全局配置](./docs/zh/global.md) - 应用全局配置和设置说明
96
+
97
+ ### 核心功能
98
+
99
+ - [启动流程](./docs/zh/bootstrap.md) - 应用启动流程和生命周期管理
100
+ - [IOC容器](./docs/zh/ioc.md) - 依赖注入系统的使用说明
101
+ - [路由管理](./docs/zh/router.md) - 路由配置和页面导航说明
102
+ - [状态管理](./docs/zh/store.md) - 应用状态管理方案说明
103
+ - [请求处理](./docs/zh/request.md) - API 请求处理机制说明
18
104
 
19
- You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
105
+ ### 功能扩展
20
106
 
21
- This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
107
+ - [国际化](./docs/zh/i18n.md) - 多语言支持和翻译管理
108
+ - [主题系统](./docs/zh/theme.md) - 主题配置和暗色模式支持
109
+ - [TypeScript指南](./docs/zh/typescript-guide.md) - TypeScript 使用规范和最佳实践
22
110
 
23
- ## Learn More
111
+ ## 🔨 架构设计
24
112
 
25
- To learn more about Next.js, take a look at the following resources:
113
+ ### 面向接口的设计模式
26
114
 
27
- - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
- - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
115
+ 项目采用面向接口的设计模式,通过接口定义实现解耦和可测试性:
29
116
 
30
- You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
117
+ #### 客户端接口 (src/base/port)
31
118
 
32
- ## Deploy on Vercel
119
+ - **AppUserApiInterface**: 用户认证相关API接口
120
+ - **AdminPageInterface**: 管理页面基础接口
121
+ - **AsyncStateInterface**: 异步状态管理接口
122
+ - **RouterInterface**: 路由管理接口
123
+ - **I18nServiceInterface**: 国际化服务接口
33
124
 
34
- The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
125
+ #### 服务端接口 (src/server/port)
35
126
 
36
- Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
127
+ - **ServerAuthInterface**: 服务端认证接口
128
+ - **DBBridgeInterface**: 数据库操作桥接接口
129
+ - **UserRepositoryInterface**: 用户数据仓库接口
130
+ - **ValidatorInterface**: 数据验证接口
131
+ - **ParamsHandlerInterface**: 参数处理接口
@@ -1,9 +1,9 @@
1
1
  import type { AppConfig } from '@/base/cases/AppConfig';
2
2
  import type { DialogHandler } from '@/base/cases/DialogHandler';
3
3
  import type { RouterService } from '@/base/cases/RouterService';
4
- import type { DBBridgeInterface } from '@/base/port/DBBridgeInterface';
5
4
  import type { I18nService } from '@/base/services/I18nService';
6
5
  import type { UserService } from '@/base/services/UserService';
6
+ import type { DBBridgeInterface } from '@/server/port/DBBridgeInterface';
7
7
  import type * as CorekitBridge from '@qlover/corekit-bridge';
8
8
  import type * as FeCorekit from '@qlover/fe-corekit';
9
9
  import type { LoggerInterface } from '@qlover/logger';
@@ -22,6 +22,19 @@ export const IOCIdentifier = Object.freeze({
22
22
  UserServiceInterface: 'UserServiceInterface',
23
23
  RouterServiceInterface: 'RouterServiceInterface',
24
24
  I18nServiceInterface: 'I18nServiceInterface',
25
+ /**
26
+ * 数据库桥接接口
27
+ *
28
+ * 你可以实现不同的例如:
29
+ *
30
+ * - Vercel Postgres
31
+ * - supabase
32
+ * - mysql
33
+ * - postgresql
34
+ * - mongodb
35
+ * - redis
36
+ * - sqllite
37
+ */
25
38
  DBBridgeInterface: 'DBBridgeInterface'
26
39
  });
27
40
 
@@ -2,6 +2,7 @@ export * from './api';
2
2
  export * from './common';
3
3
  export * from './common.error';
4
4
  export * from './page.about';
5
+ export * from './page.admin';
5
6
  export * from './page.executor';
6
7
  export * from './page.home';
7
8
  export * from './page.identifiter';
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @description Admin page title
3
+ * @localZh 管理员页面
4
+ * @localEn Admin page
5
+ */
6
+ export const PAGE_ADMIN_TITLE = 'page__admin__title';
7
+
8
+ /**
9
+ * @description Admin page description
10
+ * @localZh 管理员页面
11
+ * @localEn Admin page
12
+ */
13
+ export const PAGE_ADMIN_DESCRIPTION = 'page__admin__description';
14
+
15
+ /**
16
+ * @description Admin page keywords
17
+ * @localZh 管理员页面关键词
18
+ * @localEn Admin page
19
+ */
20
+ export const PAGE_ADMIN_KEYWORDS = 'page__admin__keywords';
21
+
22
+ /**
23
+ * @description Home page welcome message
24
+ * @localZh 欢迎来到管理员页面
25
+ * @localEn Welcome to the admin page
26
+ */
27
+ export const ADMIN_WELCOME = 'admin__welcome';
28
+
29
+ /**
30
+ * @description Admin users page title
31
+ * @localZh 后台管理 - 管理员用户页面
32
+ * @localEn Admin users page
33
+ */
34
+ export const PAGE_ADMIN_USERS_TITLE = 'page__admin__users__title';
35
+
36
+ /**
37
+ * @description Admin users page description
38
+ * @localZh 后台管理 - 管理员用户页面
39
+ * @localEn Admin users page
40
+ */
41
+ export const PAGE_ADMIN_USERS_DESCRIPTION = 'page__admin__users__description';
42
+
43
+ /**
44
+ * @description Admin users page keywords
45
+ * @localZh 后台管理 - 管理员用户页面关键词
46
+ * @localEn Admin users page keywords
47
+ */
48
+ export const PAGE_ADMIN_USERS_KEYWORDS = 'page__admin__users__keywords';
@@ -0,0 +1,33 @@
1
+ import * as i18nKeys from '../Identifier/page.admin';
2
+
3
+ /**
4
+ * Register page i18n interface
5
+ *
6
+ * @description
7
+ * - welcome: welcome message
8
+ */
9
+ export type AdminI18nInterface = typeof admin18n;
10
+
11
+ export const admin18n = Object.freeze({
12
+ // basic meta properties
13
+ title: i18nKeys.PAGE_ADMIN_TITLE,
14
+ description: i18nKeys.PAGE_ADMIN_DESCRIPTION,
15
+ content: i18nKeys.PAGE_ADMIN_DESCRIPTION,
16
+ keywords: i18nKeys.PAGE_ADMIN_KEYWORDS,
17
+
18
+ // admin page
19
+ welcome: i18nKeys.ADMIN_WELCOME
20
+ });
21
+
22
+ export type AdminUsersI18nInterface = typeof adminUsers18n;
23
+
24
+ export const adminUsers18n = Object.freeze({
25
+ // basic meta properties
26
+ title: i18nKeys.PAGE_ADMIN_USERS_TITLE,
27
+ description: i18nKeys.PAGE_ADMIN_USERS_DESCRIPTION,
28
+ content: i18nKeys.PAGE_ADMIN_USERS_DESCRIPTION,
29
+ keywords: i18nKeys.PAGE_ADMIN_USERS_KEYWORDS,
30
+
31
+ // admin page
32
+ welcome: i18nKeys.ADMIN_WELCOME
33
+ });
@@ -1,4 +1,6 @@
1
+ export * from './HomeI18n';
2
+ export * from './admin18n';
1
3
  export * from './i18nConfig';
2
- export * from './PageI18nInterface';
3
4
  export * from './loginI18n';
5
+ export * from './PageI18nInterface';
4
6
  export * from './register18n';