@_tc/template-core 0.0.1-bate.51 → 0.0.1-bate.52

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.
@@ -109,6 +109,20 @@ export default components
109
109
 
110
110
  Keep header content compact; the default header already owns brand, title, and menu layout.
111
111
 
112
+ ## Server Router Guard
113
+
114
+ Use `app/router-guard.js` or `app/router-guard.ts` in the generated business project when unmatched server paths need custom fallback behavior. The file lives directly under the business `app/` directory.
115
+
116
+ ```js
117
+ module.exports = (app) => (ctx) => {
118
+ if (!ctx.path.startsWith(app.options.apiPrefix || '/api')) {
119
+ return '/dash'
120
+ }
121
+ }
122
+ ```
123
+
124
+ The guard runs only from the final catch-all router after normal `app/router/*` routes fail to match. Return a string to redirect, return `{ status, body }` to send a custom response, or return nothing to keep the default fallback: API paths return HTTP 200 with `{ code: 404, message }`, while non-API paths return HTTP 404 with `notFound`.
125
+
112
126
  ## Dashboard Custom Routes
113
127
 
114
128
  Use `frontend/extended/dash/customRoutes.tsx` when a `custom` menu item needs a route inside the built-in Dashboard.
@@ -133,6 +147,22 @@ export default extendDashRoutes
133
147
 
134
148
  `topRoutes` are appended under `/dash`. `sidebarRoutes` are appended inside the default sidebar containers.
135
149
 
150
+ ## Dashboard Route Guard
151
+
152
+ Use `frontend/extended/dash/routeGuard.ts` when the built-in Dashboard needs a one-time guard before its default `homePage` or first-menu redirect.
153
+
154
+ ```ts
155
+ import type { DashRouteGuard } from '@_tc/template-core/fe'
156
+
157
+ const dashRouteGuard: DashRouteGuard = ({ isLoggedIn }) => {
158
+ if (!isLoggedIn) return '/login'
159
+ }
160
+
161
+ export default dashRouteGuard
162
+ ```
163
+
164
+ Return a login URL string when the framework should jump with `window.location.href`; the login page does not need to be inside Dash routes. `DashRouteGuardResult` is `string | undefined`, and `undefined` also interrupts the built-in Dashboard redirect, including the implicit return from a logged-in branch.
165
+
136
166
  ## Frontend Helpers
137
167
 
138
168
  Import TemplateCore frontend helpers from `@_tc/template-core/fe`:
package/AGENT_README.md CHANGED
@@ -94,6 +94,7 @@ app/controller/**/*.(js|ts) -> app.controller
94
94
  app/service/**/*.(js|ts) -> app.service
95
95
  app/middleware/**/*.(js|ts) -> app.middlewares
96
96
  app/router/**/*.(js|ts) -> Koa router
97
+ app/router-guard.(js|ts) -> 兜底路由守卫
97
98
  app/router-schema/**/*.(js|ts) -> app.routerSchema
98
99
  app/extend/*.(js|ts) -> app.extends
99
100
  config/config.default.(js|ts) -> app.config
@@ -165,6 +166,21 @@ export default (app: KoaApp, router: Router) => {
165
166
 
166
167
  内置响应约定:`BaseController.success(ctx, data)` 返回 `{ data, code: 0, message: 'ok' }`。
167
168
 
169
+ 兜底路由守卫:
170
+
171
+ ```ts
172
+ // app/router-guard.ts
173
+ import type { Ctx, KoaApp } from '@_tc/template-core'
174
+
175
+ export default (_app: KoaApp) => {
176
+ return (ctx: Ctx) => {
177
+ if (!ctx.path.startsWith('/api/')) return '/dash'
178
+ }
179
+ }
180
+ ```
181
+
182
+ `router-guard` 只在所有正常 router 都没命中时执行。返回字符串会重定向;返回 `{ status, body }` 会直接设置响应;无返回则走默认兜底:API 路径返回 HTTP 200 + `{ code: 404, message }`,非 API 路径返回 HTTP 404 + `notFound`。
183
+
168
184
  内置扩展:
169
185
 
170
186
  - `app.extends.$fetch`:Node 侧 axios 风格 fetch 实例。
@@ -486,7 +502,9 @@ chokidar
486
502
 
487
503
  1. SchemaForm 字段类型、CallCom 组件、Koa app 类型都支持 TypeScript 声明合并。
488
504
  2. 运行时组件通常放在业务 `frontend/extended/`。
489
- 3. 优先查看公开入口 `.d.ts` 和 npm 包根目录 `README.md`;如果当前环境是源码仓库,再继续读 `docs/type-extension.md` 和 `docs/schema-form.md`。
505
+ 3. Dashboard 扩展点包括 `frontend/extended/dash/customRoutes.tsx`、`frontend/extended/dash/components.tsx`、`frontend/extended/dash/routeGuard.ts`。
506
+ 4. `dash/routeGuard` 用来判断登录。返回登录页 URL 字符串时,Dashboard 用 `window.location.href` 跳转;`DashRouteGuardResult` 是 `string | undefined`,`undefined`(包括已登录分支的隐式返回)会打断 Dashboard 内置重定向,业务方可自行跳转。
507
+ 5. 优先查看公开入口 `.d.ts` 和 npm 包根目录 `README.md`;如果当前环境是源码仓库,再继续读 `docs/type-extension.md` 和 `docs/schema-form.md`。
490
508
 
491
509
  ## 8. 继续深入
492
510
 
package/README.md CHANGED
@@ -264,6 +264,7 @@ import type {
264
264
  CallComComponentsMap,
265
265
  CallComRenderer,
266
266
  DashComponentsMap,
267
+ DashRouteGuard,
267
268
  DashRoutesExtender,
268
269
  FormFieldSchema,
269
270
  ApiFreezerRequestMatcher,
@@ -279,7 +280,7 @@ import type {
279
280
  | --- | --- |
280
281
  | 应用启动 | `initApp` |
281
282
  | 内置前端组件 | `AsyncSelect`、`LanguageSwitch`、`ThemeSwitch` |
282
- | Dash 扩展类型 | `DashRoutesExtender`、`DashRoutesContext`、`DashComponentsMap`、`DashHeaderUserAreaProps` |
283
+ | Dash 扩展类型 | `DashRoutesExtender`、`DashRoutesContext`、`DashComponentsMap`、`DashHeaderUserAreaProps`、`DashRouteGuard` |
283
284
  | SchemaPage 类型 | `CallComComponentsMap`、`CallComRenderer` |
284
285
  | SchemaForm 类型 | `FormFieldSchema`、`SchemaFormComponentsMap`、`SchemaFormNamespace`、`SelectProps` |
285
286
  | 请求方法 | `request`、`get`、`post`、`put`、`patch`、`del` |
@@ -1136,6 +1137,29 @@ DELETE {api} -> 删除
1136
1137
 
1137
1138
  `$schema::字段名` 表示从当前表格行读取字段值,常用于删除按钮参数。
1138
1139
 
1140
+ ### 服务端兜底路由守卫
1141
+
1142
+ 业务项目可以创建 `app/router-guard.ts` 或 `app/router-guard.js`。它只在所有正常 `app/router/*` 路由都没有命中时执行。
1143
+
1144
+ ```ts
1145
+ import type { Ctx, KoaApp } from '@_tc/template-core'
1146
+
1147
+ export default (_app: KoaApp) => {
1148
+ return (ctx: Ctx) => {
1149
+ if (ctx.path.startsWith('/api/')) {
1150
+ return {
1151
+ status: 404,
1152
+ body: { code: 404, message: `${ctx.path} not found` },
1153
+ }
1154
+ }
1155
+
1156
+ return '/dash'
1157
+ }
1158
+ }
1159
+ ```
1160
+
1161
+ 返回字符串会 `ctx.redirect()`;返回 `{ status, body }` 会直接设置响应;无返回则使用框架默认兜底:API 路径返回 HTTP 200 + `{ code: 404, message }`,非 API 路径返回 HTTP 404 + `notFound`。
1162
+
1139
1163
  ## 如何扩展组件
1140
1164
 
1141
1165
  ### 扩展 Dashboard 顶部用户区域
@@ -1159,6 +1183,28 @@ export default components
1159
1183
 
1160
1184
  - `HeaderView.userArea`:Dashboard 顶部右侧区域。
1161
1185
 
1186
+ ### 扩展 Dashboard 登录守卫
1187
+
1188
+ 业务项目可以创建 `frontend/extended/dash/routeGuard.ts`,用于在 Dashboard 内置 `homePage` / 首菜单重定向前做登录判断。
1189
+
1190
+ ```ts
1191
+ // frontend/extended/dash/routeGuard.ts
1192
+ import type { DashRouteGuard } from '@_tc/template-core/fe'
1193
+
1194
+ const dashRouteGuard: DashRouteGuard = ({ isLoggedIn }) => {
1195
+ if (!isLoggedIn) return '/login'
1196
+ }
1197
+
1198
+ export default dashRouteGuard
1199
+ ```
1200
+
1201
+ 返回值规则:
1202
+
1203
+ - 返回 `string`:框架使用 `window.location.href` 跳转,登录页不需要在 Dash 路由下。
1204
+ - 返回非 `string`:中断 Dashboard 内置重定向,业务方可自行跳转或处理提示。
1205
+ - `DashRouteGuardResult` 是 `string | undefined`;`undefined`(包括已登录分支的隐式返回)也会打断 Dashboard 内置重定向。
1206
+ - 没有业务 `routeGuard` 文件时,框架使用兜底空对象,不影响默认 Dashboard 重定向。
1207
+
1162
1208
  ### 扩展 SchemaForm 字段组件
1163
1209
 
1164
1210
  1. 声明字段类型。
@@ -1719,5 +1765,7 @@ await buildBE({
1719
1765
  - SchemaForm 和 CallCom 的类型扩展、运行时注册都要做,少一个就只会“类型通过但页面不显示”或“页面能配但类型报错”。
1720
1766
  - `model` 数组合并依赖稳定 `key`。
1721
1767
  - Dashboard 默认需要 URL 带 `?projk=项目key`。
1768
+ - Dashboard 登录守卫放在 `frontend/extended/dash/routeGuard.ts`;登录页通常不在 Dash 路由下,返回登录页 URL 即可。
1769
+ - 服务端兜底守卫放在业务 `app/router-guard.ts|js`,不是 `app/business` 子目录。
1722
1770
  - 前端入口必须命名为 `.entry.tsx`、`.entry.ts`、`.entry.jsx` 或 `.entry.js`。
1723
1771
  - **使用方不建议在 `/frontend/apps/` 下保留 `html` 文件。** Vite 构建时会根据模板文件(`app/view/entry.tpl`)自动生成 html,如果保留 html 文件,可能与模板不同步而导致内容过时。
@@ -0,0 +1 @@
1
+ var e={};module.exports=e;
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.js`),t=require(`../packages/utils/path.js`);require(`../packages/utils/index.js`);const n=require(`../packages/common/log/index.js`),r=require(`./state.js`);let i=require(`path`);i=e.__toESM(i);let a=require(`glob`);a=e.__toESM(a);let o=require(`fs`),s=require(`vite`),c=require(`@tailwindcss/vite`);c=e.__toESM(c);let l=require(`module`),u=require(`url`);var d=typeof __filename==`string`?__filename:(0,u.fileURLToPath)({}.url),f=i.default.dirname(d),p=process.cwd(),m=(0,l.createRequire)(d),h=i.default.resolve(f,`../../`),g=i.default.resolve(f,`../`),_=i.default.resolve(h,`./fe/frontend`),v=i.default.resolve(p,`.`),y=i.default.resolve(v,`./frontend`),b=e=>i.default.dirname(m.resolve(`${e}/package.json`));function x(){n.logWhite(`frameFEPath: `+_),n.logWhite(`runFEPath: `+y);let e=[_,y].reduce((e,n)=>{let r=[`js`,`jsx`,`ts`,`tsx`].map(e=>t.resolve(n,`/**`,`/*.`+e)),i=a.sync(r).filter(e=>e.includes(`.entry.`)).filter(e=>!e.includes(`.d.ts`));return[...e,...i]},[]),r={};if(!e.length)return r;let s;for(let n=0;n<e.length;n++){let c=e[n],l=i.default.resolve(c,`../`),u=a.sync(t.resolve(l,`*.html`))[0];if(u){let e=i.default.basename(u,`.html`);r[e]=i.default.resolve(p,u)}else{s||=(0,o.readFileSync)(i.default.resolve(g,`./app/view/entry.tpl`),`utf-8`);let e=c.split(i.default.sep).at(-1),t=s.replace(`<!-- {{InsertScript}} -->`,`<script type="module" src="./${e}"><\/script>`),n=l.split(i.default.sep).at(-1),a=i.default.join(l,n+`.html`);(0,o.writeFileSync)(a,t),r[n]=a}}return r}function S(){let e=r.buildFESharedState.options.output??`frame`;return e!==`frame`&&n.logRed(`Please pay attention to the build output.`),e===`run`?i.default.join(v,`./app/public/dist`):typeof e==`function`?e():i.default.join(g,`./app/public/dist`)}var C=async e=>{let t=(await import(`chokidar`)).watch(y,{ignored:/(^|[\/\\])\../,persistent:!0,ignoreInitial:!0}),r=!1;t.on(`change`,async t=>{if(!r){r=!0,n.logYellow(`changed: ${t}`);try{await e?.()}catch(e){n.logRed(e)}finally{r=!1}}}),n.logYellow(`watching ${y}/...`)};function w(e=`.html`){let t=[...new Set(Array.isArray(e)?e:[e])];return{name:`flatten-html`,apply:`build`,closeBundle(){let e=S();if(!(0,o.existsSync)(e))return;let r=a.sync(`**/*.html`,{cwd:e,dot:!0});for(let n of r){let r=i.default.join(e,n),a=i.default.basename(n,`.html`),s=`.entry`;a.indexOf(s)===-1&&(a+=s);for(let n of t){let t=a+n,s=i.default.join(e,t);r!==s&&(0,o.copyFileSync)(r,s)}t.some(t=>r===i.default.join(e,a+t))||(0,o.unlinkSync)(r)}let s=[...new Set(r.map(e=>i.default.dirname(e)))].sort().reverse(),c=e=>{if(!((0,o.existsSync)(e)?(0,o.readdirSync)(e):[1]).length){(0,o.rmdirSync)(e);let t=e.split(i.default.sep).slice(0,-1);t.length&&c(t.join(i.default.sep))}};for(let t of s)t&&t!==`.`&&c(i.default.join(e,t));let l=t.flatMap(t=>a.sync(`**/*`+t,{cwd:e,dot:!0}));for(let e of l)n.logYellow(`final file path -----> `+e)}}}function T(e){let t=[``,`.ts`,`.tsx`,`.js`,`.jsx`];return e.reduce((e,[n,r,a=y])=>{let s=t.map(e=>i.default.resolve(a,r+e)).find(e=>(0,o.existsSync)(e)),c=i.default.resolve(f,`./defaultAlias`);return s&&(c=s),e[n]=c,e},{})}var E=(...e)=>new RegExp(e.join(`[\\/]`)),D=e=>`_${e}_tcC`,O=async(e,t)=>{try{await(0,s.build)({configFile:!1,base:`/dist`,plugins:[(await import(`@vitejs/plugin-react`)).default(),w([`.tpl`,`.html`]),(0,c.default)()],build:{outDir:S(),emptyOutDir:!0,sourcemap:t===`dev`?`inline`:`hidden`,rolldownOptions:{input:e,output:{codeSplitting:{groups:[{name:D(`__nm`),test:E(`node_modules`),maxSize:5e5,minShareCount:2,priority:1},{name:D(`r`),test:E(`node_modules`,`react`),minShareCount:2,maxSize:5e5,priority:5},{name:D(`r-d`),test:E(`node_modules`,`react-dom`),minShareCount:2,maxSize:2e5,priority:10},{name:D(`r-r-d`),test:E(`node_modules`,`react-router-dom`),minShareCount:2,maxSize:5e5,priority:10},{name:D(`ajv`),test:E(`node_modules`,`ajv`),minShareCount:2,maxSize:2e5,priority:10},{name:D(`ui-lib`),test:E(`components`),minShareCount:2,maxSize:2e5,priority:5},{name:D(`ui-f-lib`),test:E(`components`,`.*?[Ff]orm.*?`),minShareCount:2,maxSize:5e4,priority:10}]}}}},resolve:{alias:{react:b(`react`),"react-dom":b(`react-dom`),"@tc/ui-react/hooks":i.default.resolve(f,`../../fe/packages/react/hooks/`),"@tc/ui-react":i.default.resolve(f,`../../fe/packages/react/ui/`),...T([[`@tc/scalability/SchemaForm/frameData`,`./extended/SchemaForm/data`,_],[`@tc/scalability/SchemaForm/data`,`./extended/SchemaForm/data`],[`@tc/scalability/SchemaPage/CallCom/data`,`./extended/SchemaPage/CallCom/data`],[`@tc/scalability/dash/customRoutes`,`./extended/dash/customRoutes`],[`@tc/scalability/dash/components`,`./extended/dash/components`]])}}}),n.logJoinColorized(n.colorize(`builded`,`green`),n.colorize(` -> `,`white`),n.colorize(`time: `,`cyan`),n.colorize(new Date,`pink`))}catch(e){if(n.logRed(e),t!==`dev`)throw e}};exports.VBuildFE=O,exports.entries=x,exports.outDir=S,exports.watchFiles=C;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.js`),t=require(`../packages/utils/path.js`);require(`../packages/utils/index.js`);const n=require(`../packages/common/log/index.js`),r=require(`./state.js`);let i=require(`path`);i=e.__toESM(i);let a=require(`glob`);a=e.__toESM(a);let o=require(`fs`),s=require(`vite`),c=require(`@tailwindcss/vite`);c=e.__toESM(c);let l=require(`module`),u=require(`url`);var d=typeof __filename==`string`?__filename:(0,u.fileURLToPath)({}.url),f=i.default.dirname(d),p=process.cwd(),m=(0,l.createRequire)(d),h=i.default.resolve(f,`../../`),g=i.default.resolve(f,`../`),_=i.default.resolve(h,`./fe/frontend`),v=i.default.resolve(p,`.`),y=i.default.resolve(v,`./frontend`),b=e=>i.default.dirname(m.resolve(`${e}/package.json`));function x(){n.logWhite(`frameFEPath: `+_),n.logWhite(`runFEPath: `+y);let e=[_,y].reduce((e,n)=>{let r=[`js`,`jsx`,`ts`,`tsx`].map(e=>t.resolve(n,`/**`,`/*.`+e)),i=a.sync(r).filter(e=>e.includes(`.entry.`)).filter(e=>!e.includes(`.d.ts`));return[...e,...i]},[]),r={};if(!e.length)return r;let s;for(let n=0;n<e.length;n++){let c=e[n],l=i.default.resolve(c,`../`),u=a.sync(t.resolve(l,`*.html`))[0];if(u){let e=i.default.basename(u,`.html`);r[e]=i.default.resolve(p,u)}else{s||=(0,o.readFileSync)(i.default.resolve(g,`./app/view/entry.tpl`),`utf-8`);let e=c.split(i.default.sep).at(-1),t=s.replace(`<!-- {{InsertScript}} -->`,`<script type="module" src="./${e}"><\/script>`),n=l.split(i.default.sep).at(-1),a=i.default.join(l,n+`.html`);(0,o.writeFileSync)(a,t),r[n]=a}}return r}function S(){let e=r.buildFESharedState.options.output??`frame`;return e!==`frame`&&n.logRed(`Please pay attention to the build output.`),e===`run`?i.default.join(v,`./app/public/dist`):typeof e==`function`?e():i.default.join(g,`./app/public/dist`)}var C=async e=>{let t=(await import(`chokidar`)).watch(y,{ignored:/(^|[\/\\])\../,persistent:!0,ignoreInitial:!0}),r=!1;t.on(`change`,async t=>{if(!r){r=!0,n.logYellow(`changed: ${t}`);try{await e?.()}catch(e){n.logRed(e)}finally{r=!1}}}),n.logYellow(`watching ${y}/...`)};function w(e=`.html`){let t=[...new Set(Array.isArray(e)?e:[e])];return{name:`flatten-html`,apply:`build`,closeBundle(){let e=S();if(!(0,o.existsSync)(e))return;let r=a.sync(`**/*.html`,{cwd:e,dot:!0});for(let n of r){let r=i.default.join(e,n),a=i.default.basename(n,`.html`),s=`.entry`;a.indexOf(s)===-1&&(a+=s);for(let n of t){let t=a+n,s=i.default.join(e,t);r!==s&&(0,o.copyFileSync)(r,s)}t.some(t=>r===i.default.join(e,a+t))||(0,o.unlinkSync)(r)}let s=[...new Set(r.map(e=>i.default.dirname(e)))].sort().reverse(),c=e=>{if(!((0,o.existsSync)(e)?(0,o.readdirSync)(e):[1]).length){(0,o.rmdirSync)(e);let t=e.split(i.default.sep).slice(0,-1);t.length&&c(t.join(i.default.sep))}};for(let t of s)t&&t!==`.`&&c(i.default.join(e,t));let l=t.flatMap(t=>a.sync(`**/*`+t,{cwd:e,dot:!0}));for(let e of l)n.logYellow(`final file path -----> `+e)}}}function T(e){let t=[``,`.ts`,`.tsx`,`.js`,`.jsx`];return e.reduce((e,[n,r,a=y,s=`./defaultAlias`])=>{let c=t.map(e=>i.default.resolve(a,r+e)).find(e=>(0,o.existsSync)(e)),l=i.default.resolve(f,s);return c&&(l=c),e[n]=l,e},{})}var E=(...e)=>new RegExp(e.join(`[\\/]`)),D=e=>`_${e}_tcC`,O=async(e,t)=>{try{await(0,s.build)({configFile:!1,base:`/dist`,plugins:[(await import(`@vitejs/plugin-react`)).default(),w([`.tpl`,`.html`]),(0,c.default)()],build:{outDir:S(),emptyOutDir:!0,sourcemap:t===`dev`?`inline`:`hidden`,rolldownOptions:{input:e,output:{codeSplitting:{groups:[{name:D(`__nm`),test:E(`node_modules`),maxSize:5e5,minShareCount:2,priority:1},{name:D(`r`),test:E(`node_modules`,`react`),minShareCount:2,maxSize:5e5,priority:5},{name:D(`r-d`),test:E(`node_modules`,`react-dom`),minShareCount:2,maxSize:2e5,priority:10},{name:D(`r-r-d`),test:E(`node_modules`,`react-router-dom`),minShareCount:2,maxSize:5e5,priority:10},{name:D(`ajv`),test:E(`node_modules`,`ajv`),minShareCount:2,maxSize:2e5,priority:10},{name:D(`ui-lib`),test:E(`components`),minShareCount:2,maxSize:2e5,priority:5},{name:D(`ui-f-lib`),test:E(`components`,`.*?[Ff]orm.*?`),minShareCount:2,maxSize:5e4,priority:10}]}}}},resolve:{alias:{react:b(`react`),"react-dom":b(`react-dom`),"@tc/ui-react/hooks":i.default.resolve(f,`../../fe/packages/react/hooks/`),"@tc/ui-react":i.default.resolve(f,`../../fe/packages/react/ui/`),...T([[`@tc/scalability/SchemaForm/frameData`,`./extended/SchemaForm/data`,_],[`@tc/scalability/SchemaForm/data`,`./extended/SchemaForm/data`],[`@tc/scalability/SchemaPage/CallCom/data`,`./extended/SchemaPage/CallCom/data`],[`@tc/scalability/dash/customRoutes`,`./extended/dash/customRoutes`],[`@tc/scalability/dash/components`,`./extended/dash/components`],[`@tc/scalability/dash/routeGuard`,`./extended/dash/routeGuard`,void 0,`./defaultRouteGuard`]])}}}),n.logJoinColorized(n.colorize(`builded`,`green`),n.colorize(` -> `,`white`),n.colorize(`time: `,`cyan`),n.colorize(new Date,`pink`))}catch(e){if(n.logRed(e),t!==`dev`)throw e}};exports.VBuildFE=O,exports.entries=x,exports.outDir=S,exports.watchFiles=C;
@@ -1 +1 @@
1
- const e=require(`../../../_virtual/_rolldown/runtime.js`),t=require(`../paths.js`),n=require(`../../utils/path.js`),r=require(`../../utils/loadFile.js`),i=require(`../../utils/runFileFn.js`);require(`../../utils/index.js`);let a=require(`fs`),o=require(`koa-router`);o=e.__toESM(o,1);var s=e=>{let s,c=t.getAppPath(e,`business`);if(c)for(let t of[`router-guard.ts`,`router-guard.js`]){let r=n.resolve(c,t);if((0,a.existsSync)(r)){s=i.runFileFn(r)(e);break}}s||console.info(`-- [router-guard] -- not router-guard.(ts|js) file`);let l=[];t.getAppPaths(e).forEach(t=>{r.loadFileFn(t,`router`,{preProcessingFileCallback:t=>{let r=new o.default;r.level=0,l.push(r),i.runFileFn(n.resolve(t))(e,r)}})});let u=new o.default;u.level=99,u.get(`(.*)`,async(t,n)=>{console.info(`\x1B[31m%s\x1B[0m`,`-- notFound path: ${t.path} --`);let r=s?.(t);t.status=302,r?t.redirect(r??`/`):t.path.includes(`${e.options.apiPrefix}/`)?(t.status=200,t.body={code:404,message:`${t.path} not found`}):(t.status=404,t.body=`notFound`)}),l.push(u),l.sort((e,t)=>(e.level??0)<(t.level??0)?-1:1),l.forEach(t=>{e.use(t.routes()),e.use(t.allowedMethods())}),e.currentUseRouter=l};module.exports=s;
1
+ const e=require(`../../../_virtual/_rolldown/runtime.js`),t=require(`../paths.js`),n=require(`../../utils/path.js`),r=require(`../../utils/loadFile.js`),i=require(`../../utils/runFileFn.js`);require(`../../utils/index.js`);let a=require(`fs`),o=require(`koa-router`);o=e.__toESM(o,1);var s=e=>{let s,c=t.getAppPath(e,`business`);if(c)for(let t of[`router-guard.ts`,`router-guard.js`]){let r=n.resolve(c,t);if((0,a.existsSync)(r)){s=i.runFileFn(r)(e);break}}s||console.info(`-- [router-guard] -- not router-guard.(ts|js) file`);let l=[];t.getAppPaths(e).forEach(t=>{r.loadFileFn(t,`router`,{preProcessingFileCallback:t=>{let r=new o.default;r.level=0,l.push(r),i.runFileFn(n.resolve(t))(e,r)}})});let u=new o.default;u.level=99,u.get(`(.*)`,async(t,n)=>{console.info(`\x1B[31m%s\x1B[0m`,`-- notFound path: ${t.path} --`);let r=s?.(t);t.status=302,r?typeof r==`string`?t.redirect(r??`/`):(t.status=r.status,t.body=r.body):t.path.includes(`${e.options.apiPrefix}/`)?(t.status=200,t.body={code:404,message:`${t.path} not found`}):(t.status=404,t.body=`notFound`)}),l.push(u),l.sort((e,t)=>(e.level??0)<(t.level??0)?-1:1),l.forEach(t=>{e.use(t.routes()),e.use(t.allowedMethods())}),e.currentUseRouter=l};module.exports=s;
@@ -0,0 +1,4 @@
1
+ //#region bundler/defaultRouteGuard.ts
2
+ var e = {};
3
+ //#endregion
4
+ export { e as default };
@@ -105,9 +105,9 @@ function P(e) {
105
105
  ".js",
106
106
  ".jsx"
107
107
  ];
108
- return e.reduce((e, [n, r, i = O]) => {
109
- let a = t.map((e) => s.resolve(i, r + e)).find((e) => u(e)), o = s.resolve(x, "./defaultAlias");
110
- return a && (o = a), e[n] = o, e;
108
+ return e.reduce((e, [n, r, i = O, a = "./defaultAlias"]) => {
109
+ let o = t.map((e) => s.resolve(i, r + e)).find((e) => u(e)), c = s.resolve(x, a);
110
+ return o && (c = o), e[n] = c, e;
111
111
  }, {});
112
112
  }
113
113
  var F = (...e) => new RegExp(e.join("[\\/]")), I = (e) => `_${e}_tcC`, L = async (e, i) => {
@@ -193,7 +193,13 @@ var F = (...e) => new RegExp(e.join("[\\/]")), I = (e) => `_${e}_tcC`, L = async
193
193
  ["@tc/scalability/SchemaForm/data", "./extended/SchemaForm/data"],
194
194
  ["@tc/scalability/SchemaPage/CallCom/data", "./extended/SchemaPage/CallCom/data"],
195
195
  ["@tc/scalability/dash/customRoutes", "./extended/dash/customRoutes"],
196
- ["@tc/scalability/dash/components", "./extended/dash/components"]
196
+ ["@tc/scalability/dash/components", "./extended/dash/components"],
197
+ [
198
+ "@tc/scalability/dash/routeGuard",
199
+ "./extended/dash/routeGuard",
200
+ void 0,
201
+ "./defaultRouteGuard"
202
+ ]
197
203
  ])
198
204
  } }
199
205
  }), n(t("builded", "green"), t(" -> ", "white"), t("time: ", "cyan"), t(/* @__PURE__ */ new Date(), "pink"));
@@ -27,7 +27,7 @@ var s = (s) => {
27
27
  d.level = 99, d.get("(.*)", async (e, t) => {
28
28
  console.info("\x1B[31m%s\x1B[0m", `-- notFound path: ${e.path} --`);
29
29
  let n = c?.(e);
30
- e.status = 302, n ? e.redirect(n ?? "/") : e.path.includes(`${s.options.apiPrefix}/`) ? (e.status = 200, e.body = {
30
+ e.status = 302, n ? typeof n == "string" ? e.redirect(n ?? "/") : (e.status = n.status, e.body = n.body) : e.path.includes(`${s.options.apiPrefix}/`) ? (e.status = 200, e.body = {
31
31
  code: 404,
32
32
  message: `${e.path} not found`
33
33
  }) : (e.status = 404, e.body = "notFound");
@@ -0,0 +1,4 @@
1
+ //#region bundler/defaultRouteGuard.ts
2
+ var defaultRouteGuard_default = {};
3
+ //#endregion
4
+ export { defaultRouteGuard_default as default };
@@ -1,5 +1,6 @@
1
1
  import { getText } from "../../src/common/language.js";
2
2
  import { generateMenuItemData } from "../../src/common/generateMenuData.js";
3
+ import { getAuthTokenSync } from "../../src/common/auth/index.js";
3
4
  import defaultAlias_default from "../../../bundler/defaultAlias.js";
4
5
  import { Menu } from "../../../packages/react/ui/components/Menu/Menu.js";
5
6
  import "../../../packages/react/ui/components/index.js";
@@ -7,6 +8,7 @@ import { useModeStore } from "../../src/stores/mode.js";
7
8
  import { findMenuItem, leftSidebarBasePath } from "../../src/common/menu.js";
8
9
  import HeaderView from "../../src/components/BasePage/HeaderView.js";
9
10
  import useRouterParams from "../../src/hooks/useRouterParams.js";
11
+ import defaultRouteGuard_default from "../../../bundler/defaultRouteGuard.js";
10
12
  import useInit from "../../../packages/react/hooks/useInit.js";
11
13
  import { useEffect, useMemo, useRef, useState } from "react";
12
14
  import { Outlet, useNavigate } from "react-router-dom";
@@ -110,6 +112,25 @@ var Dashboard = () => {
110
112
  useEffect(() => {
111
113
  if (initData && projectInfo) {
112
114
  if (isInit.current) return;
115
+ if (typeof defaultRouteGuard_default === "function") {
116
+ const authToken = getAuthTokenSync();
117
+ const loginUrl = defaultRouteGuard_default({
118
+ projectInfo,
119
+ params,
120
+ pathname: location.pathname,
121
+ search: location.search,
122
+ hash: location.hash,
123
+ authToken,
124
+ isLoggedIn: Boolean(authToken)
125
+ });
126
+ if (typeof loginUrl === "string") {
127
+ isInit.current = true;
128
+ window.location.href = loginUrl;
129
+ return;
130
+ }
131
+ isInit.current = true;
132
+ return;
133
+ }
113
134
  const homePage = getRenderableHomePage(projectInfo);
114
135
  if (homePage) {
115
136
  homePage.selectedKey && uSK(homePage.selectedKey);
@@ -12,3 +12,14 @@ export type DashHeaderUserAreaProps = {
12
12
  export type DashComponentsMap = {
13
13
  'HeaderView.userArea'?: ComponentType<DashHeaderUserAreaProps> | LazyExoticComponent<ComponentType<DashHeaderUserAreaProps>>;
14
14
  };
15
+ export type DashRouteGuardContext = {
16
+ projectInfo: ProjectInfo;
17
+ params: Record<string, string | undefined>;
18
+ pathname: string;
19
+ search: string;
20
+ hash: string;
21
+ authToken: string | null;
22
+ isLoggedIn: boolean;
23
+ };
24
+ export type DashRouteGuardResult = string | undefined;
25
+ export type DashRouteGuard = (context: DashRouteGuardContext) => DashRouteGuardResult;
@@ -1,4 +1,5 @@
1
1
  export declare const AUTH_HEADER_KEY = "Authorization";
2
+ export declare function getAuthTokenSync(): string | null;
2
3
  export declare function getAuthToken(): Promise<string | null>;
3
4
  export declare function clearAuthToken(): Promise<void>;
4
5
  export declare const API_AUTH_HEADER_KEY = "projk";
@@ -2,10 +2,13 @@
2
2
  var AUTH_LOCAL_KEY = "auth_token";
3
3
  var API_AUTH_LOCAL_KEY = "p_J_k";
4
4
  var AUTH_HEADER_KEY = "Authorization";
5
- async function getAuthToken() {
5
+ function getAuthTokenSync() {
6
6
  if (typeof localStorage === "undefined") return null;
7
7
  return localStorage.getItem(AUTH_LOCAL_KEY);
8
8
  }
9
+ async function getAuthToken() {
10
+ return getAuthTokenSync();
11
+ }
9
12
  async function clearAuthToken() {
10
13
  if (typeof localStorage === "undefined") return;
11
14
  localStorage.removeItem(AUTH_LOCAL_KEY);
@@ -18,4 +21,4 @@ var setApiAuth = (v) => {
18
21
  return localStorage.setItem(API_AUTH_LOCAL_KEY, v);
19
22
  };
20
23
  //#endregion
21
- export { API_AUTH_HEADER_KEY, AUTH_HEADER_KEY, clearAuthToken, getApiAuth, getAuthToken, setApiAuth };
24
+ export { API_AUTH_HEADER_KEY, AUTH_HEADER_KEY, clearAuthToken, getApiAuth, getAuthToken, getAuthTokenSync, setApiAuth };
@@ -1,8 +1,8 @@
1
1
  import { frontendLangKeys } from "../language/index.js";
2
2
  import { getText } from "./language.js";
3
+ import { API_AUTH_HEADER_KEY, AUTH_HEADER_KEY, clearAuthToken, getApiAuth, getAuthToken } from "./auth/index.js";
3
4
  import { FreezeState, apiFreezerStore } from "../stores/apiFreezer.js";
4
5
  import FetchAxios from "../../../packages/common/http/index.js";
5
- import { API_AUTH_HEADER_KEY, AUTH_HEADER_KEY, clearAuthToken, getApiAuth, getAuthToken } from "./auth/index.js";
6
6
  import md5 from "md5";
7
7
  //#region frontend/src/common/request.ts
8
8
  var BASE_URL = "/api";
@@ -1,4 +1,5 @@
1
1
  export declare const AUTH_HEADER_KEY = "Authorization";
2
+ export declare function getAuthTokenSync(): string | null;
2
3
  export declare function getAuthToken(): Promise<string | null>;
3
4
  export declare function clearAuthToken(): Promise<void>;
4
5
  export declare const API_AUTH_HEADER_KEY = "projk";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@_tc/template-core",
3
- "version": "0.0.1-bate.51",
3
+ "version": "0.0.1-bate.52",
4
4
  "description": "A full-stack TypeScript admin framework powered by Koa, React, and Vite - monorepo root",
5
5
  "types": "./types/index.d.ts",
6
6
  "exports": {
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;