@bndynet/vue-site 1.0.0 → 1.0.2
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/README.md +91 -1
- package/bin/vue-site.mjs +1 -0
- package/dist/auth.d.ts +22 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +941 -915
- package/dist/router.d.ts +2 -1
- package/dist/types.d.ts +84 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@ Configurable Vue 3 site framework: one package, `site.config.ts`, and Markdown p
|
|
|
6
6
|
|
|
7
7
|
- Config-driven nav (Lucide icon names)
|
|
8
8
|
- Permission-gated nav via a `visible` predicate (sync or async; hides item and skips its route)
|
|
9
|
+
- Per-page authorization via `auth` + a central `authorize` policy (navigation guard + login redirect)
|
|
10
|
+
- Hash or HTML5 (`web`) router history, configurable in `site.config.ts`
|
|
9
11
|
- Markdown (`?raw`) or Vue pages
|
|
10
12
|
- highlight.js, light/dark theme + localStorage
|
|
11
13
|
- Project `README.md` as Home
|
|
@@ -74,6 +76,9 @@ Add `"dev": "vue-site dev"` (or `vs dev`) in `package.json` scripts if you like.
|
|
|
74
76
|
| `footer` | Footer text |
|
|
75
77
|
| `readme` | Raw Home content if no `README.md` |
|
|
76
78
|
| `links` | Header links: Lucide `icon` + `link`, optional `title` |
|
|
79
|
+
| `pages` | `StandalonePage[]` — full-screen routes outside the `nav` tree (no top bar/sidebar/footer) |
|
|
80
|
+
| `auth` | Central authorization policy (`AuthConfig`) — see [Per-page authorization](#per-page-authorization-auth) |
|
|
81
|
+
| `router` | History mode (`RouterConfig`) — `hash` (default) or HTML5 `web`; see [Router history](#router-history-router) |
|
|
77
82
|
| `packageRepository` | Usually set by CLI from `package.json`; omit when using `createSiteApp` alone |
|
|
78
83
|
| `env` | Dev/build options — see below |
|
|
79
84
|
| `bootstrap` | Optional path from site root (e.g. `./bootstrap.ts`) — module loaded once before the Vue app |
|
|
@@ -90,6 +95,7 @@ Add `"dev": "vue-site dev"` (or `vs dev`) in `package.json` scripts if you like.
|
|
|
90
95
|
| `children` | Nested group |
|
|
91
96
|
| `link` | Render as a hyperlink (internal route path or external URL) instead of a page route |
|
|
92
97
|
| `visible` | `() => boolean \| Promise<boolean>`, awaited once at startup. Return `false` to hide the item from the nav and skip its route (not reachable by direct URL). A hidden parent hides its subtree; a group with no remaining children is pruned. Not reactive to later changes. |
|
|
98
|
+
| `auth` | Authorization rule (`AuthRule`) interpreted by `auth.authorize`. Keeps the route registered and enforces it via a navigation guard (so direct URLs redirect to login). Requires `SiteConfig.auth`. See [Per-page authorization](#per-page-authorization-auth). |
|
|
93
99
|
|
|
94
100
|
### `ThemeConfig`
|
|
95
101
|
|
|
@@ -100,6 +106,90 @@ Add `"dev": "vue-site dev"` (or `vs dev`) in `package.json` scripts if you like.
|
|
|
100
106
|
| `palettes` | — | Partial overrides for built-in light/dark only |
|
|
101
107
|
| `extraThemes` | — | Extra themes: `id`, `label`, `icon`, optional `basedOn`, `palette`; import `builtinThemePalettes` for full defaults |
|
|
102
108
|
|
|
109
|
+
## Per-page authorization (`auth`)
|
|
110
|
+
|
|
111
|
+
Gate individual pages on the current user. Add an `auth` rule to any `NavItem` or `StandalonePage`, and a single `auth.authorize` policy in `SiteConfig` to decide access.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { defineConfig } from '@bndynet/vue-site'
|
|
115
|
+
|
|
116
|
+
export default defineConfig({
|
|
117
|
+
title: 'My Site',
|
|
118
|
+
auth: {
|
|
119
|
+
loginPath: '/login',
|
|
120
|
+
authorize: ({ rule }) => {
|
|
121
|
+
const user = getCurrentUser() // your own auth state
|
|
122
|
+
if (!user) return '/login' // not signed in -> redirect (string)
|
|
123
|
+
if (rule === true) return true // `auth: true` -> any signed-in user
|
|
124
|
+
if (typeof rule === 'string') return user.roles.includes(rule)
|
|
125
|
+
if (Array.isArray(rule)) return rule.some((r) => user.roles.includes(r))
|
|
126
|
+
return true
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
nav: [
|
|
130
|
+
{ label: 'Home', icon: 'home', page: () => import('../README.md?raw') },
|
|
131
|
+
{ label: 'Dashboard', icon: 'gauge', auth: true, page: () => import('./pages/Dash.vue') },
|
|
132
|
+
{ label: 'Admin', icon: 'shield', auth: ['admin'], page: () => import('./pages/Admin.vue') },
|
|
133
|
+
],
|
|
134
|
+
pages: [
|
|
135
|
+
{ path: '/login', page: () => import('./pages/Login.vue') }, // no `auth` -> always reachable
|
|
136
|
+
],
|
|
137
|
+
})
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### How it works
|
|
141
|
+
|
|
142
|
+
- `authorize` runs **on every navigation** (a Vue Router `beforeEach` guard). It receives `{ rule, item, to, from }` and returns `true` (allow), `false` (deny), or a path `string` (redirect, e.g. to a login page).
|
|
143
|
+
- On `false`, the user is sent to `auth.loginPath` with the requested path as a `redirect` query (`/login?redirect=/admin`); if `loginPath` is unset, the navigation is cancelled.
|
|
144
|
+
- `authorize` also runs **once at startup** (with only `rule` / `item`) to hide unauthorized items from the nav menu. Like `visible`, this menu filtering is not reactive — it reflects the state at app creation, so update it by recreating the app (e.g. a full reload after login).
|
|
145
|
+
- Guarded routes stay **registered**, so visiting a protected URL directly triggers the guard (and your login redirect) rather than silently 404-ing.
|
|
146
|
+
- The login page itself must **not** carry an `auth` rule (and `loginPath` is always allowed by the guard) to avoid redirect loops.
|
|
147
|
+
|
|
148
|
+
### `AuthConfig`
|
|
149
|
+
|
|
150
|
+
| Property | Description |
|
|
151
|
+
|----------|-------------|
|
|
152
|
+
| `authorize` | `(ctx: AuthContext) => boolean \| string \| Promise<boolean \| string>`. `true` allows, `false` denies, a `string` redirects. |
|
|
153
|
+
| `loginPath` | Where to send denied users (with `?redirect=`). Optional; without it, denials cancel navigation. |
|
|
154
|
+
|
|
155
|
+
`AuthRule` is `boolean \| string \| string[] \| ((ctx: AuthContext) => boolean \| Promise<boolean>)`. The framework never inspects the rule; it forwards it to `authorize`, so its meaning is entirely up to you.
|
|
156
|
+
|
|
157
|
+
### `visible` vs `auth`
|
|
158
|
+
|
|
159
|
+
| | `visible` | `auth` |
|
|
160
|
+
|--|-----------|--------|
|
|
161
|
+
| Decides | Whether the item/route **exists** | Whether the **current user** may enter |
|
|
162
|
+
| When | Build/startup (once) | Navigation (every time) + startup for menu filtering |
|
|
163
|
+
| Route registered | No (unreachable by URL) | Yes (guarded; can redirect to login) |
|
|
164
|
+
| Reacts to login/logout | No | Guard yes; menu filtering no |
|
|
165
|
+
| Best for | Env / feature-flag / static trimming | Login state, roles, login redirects |
|
|
166
|
+
|
|
167
|
+
Use `visible` for static existence trimming and `auth` for user-based access. They can be combined on the same item.
|
|
168
|
+
|
|
169
|
+
## Router history (`router`)
|
|
170
|
+
|
|
171
|
+
By default routes use **hash** history (`#/path`), which works on any static host with no server configuration and is independent of the public base. To get clean URLs, switch to HTML5 history:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
export default defineConfig({
|
|
175
|
+
title: 'My Site',
|
|
176
|
+
router: { mode: 'web' }, // /app/admin instead of /app/#/admin
|
|
177
|
+
nav: [/* ... */],
|
|
178
|
+
})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### `RouterConfig`
|
|
182
|
+
|
|
183
|
+
| Property | Default | Description |
|
|
184
|
+
|----------|---------|-------------|
|
|
185
|
+
| `mode` | `hash` | `hash` (`#/path`, no server config) or `web` (HTML5 clean URLs) |
|
|
186
|
+
| `base` | `import.meta.env.BASE_URL` | Base path for `web` mode. The CLI sets this from `--base` / `env.vite.base` automatically; override only when calling `createSiteApp` from a custom entry. |
|
|
187
|
+
|
|
188
|
+
Notes:
|
|
189
|
+
|
|
190
|
+
- **`web` mode requires SPA fallback**: configure your host to serve `index.html` for unknown paths, otherwise deep links / refreshes 404. Hash mode needs nothing.
|
|
191
|
+
- **Subpath deploys**: with the CLI, `--base=/app/` is picked up automatically as the history base in `web` mode. In [library mode](#library-mode), pass `router: { mode: 'web', base: import.meta.env.BASE_URL }` from your own entry.
|
|
192
|
+
|
|
103
193
|
## `env` (`SiteEnvConfig`)
|
|
104
194
|
|
|
105
195
|
| Property | Description |
|
|
@@ -152,7 +242,7 @@ app.mount('#app')
|
|
|
152
242
|
|
|
153
243
|
Use a top-level `await` in your entry (or an async IIFE): `createSiteApp` is async and **awaits** `configureApp` when it returns a `Promise`. If you set optional `bootstrap` in config, that module loads before the app is created; if you omit `bootstrap`, that step is skipped.
|
|
154
244
|
|
|
155
|
-
Exports: `createSiteApp`, `defineConfig`, `useTheme`, `useSiteConfig`, `themeRefKey`. Types: `SiteConfig`, `SiteEnvConfig`, `SiteViteConfig`, `SiteExternalLink`, `NavItem`, `ThemeConfig`, `ThemeOption`, `ThemePaletteVars`, `ResolvedNavItem`.
|
|
245
|
+
Exports: `createSiteApp`, `defineConfig`, `useTheme`, `useSiteConfig`, `themeRefKey`. Types: `SiteConfig`, `SiteEnvConfig`, `SiteViteConfig`, `SiteExternalLink`, `NavItem`, `StandalonePage`, `AuthRule`, `AuthContext`, `AuthConfig`, `RouterConfig`, `ThemeConfig`, `ThemeOption`, `ThemePaletteVars`, `ResolvedNavItem`.
|
|
156
246
|
|
|
157
247
|
### Theme in Vue pages (`useTheme`)
|
|
158
248
|
|
package/bin/vue-site.mjs
CHANGED
|
@@ -205,6 +205,7 @@ function buildBootstrapScript({ siteConfig, siteConfigSpecifier }) {
|
|
|
205
205
|
` ...siteConfig,`,
|
|
206
206
|
` ...(hasThemeQuery ? { theme: { ...(siteConfig.theme || {}), default: resolvedTheme } } : {}),`,
|
|
207
207
|
` packageRepository: repositoryUrl,`,
|
|
208
|
+
` baseUrl: import.meta.env.BASE_URL,`,
|
|
208
209
|
` })`,
|
|
209
210
|
` app.mount('#app')`,
|
|
210
211
|
`})()`,
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Router } from 'vue-router';
|
|
2
|
+
import { AuthConfig, ResolvedNavItem } from './types';
|
|
3
|
+
/** Query param carrying the originally requested path when redirecting to the login page. */
|
|
4
|
+
export declare const AUTH_REDIRECT_QUERY = "redirect";
|
|
5
|
+
/**
|
|
6
|
+
* Startup-once pass that drops nav items the current user is not authorized to see, so they do not
|
|
7
|
+
* appear in the rendered menu. Mirrors the pruning of `filterNavItems`: a hidden parent removes its
|
|
8
|
+
* subtree, and a group left with no children and no own `page` / `link` is pruned. Routes are not
|
|
9
|
+
* affected (they are still registered by `createSiteRouter`); only the menu list is filtered. An
|
|
10
|
+
* item is shown only when `authorize` returns exactly `true`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function pruneNavByAuth(items: ResolvedNavItem[], auth: AuthConfig): Promise<ResolvedNavItem[]>;
|
|
13
|
+
/**
|
|
14
|
+
* Install a global `beforeEach` guard that enforces a route's `meta.auth` rule at navigation time.
|
|
15
|
+
*
|
|
16
|
+
* - Routes without an `auth` rule (or with `false`) are always allowed.
|
|
17
|
+
* - The configured `loginPath` is always allowed (so the login page itself is reachable; prevents loops).
|
|
18
|
+
* - Otherwise `authorize` is called: `true` allows, a string redirects (to that path; allowed as-is
|
|
19
|
+
* when it equals the current path to avoid loops), and `false` redirects to `loginPath` with a
|
|
20
|
+
* `redirect` query of the requested path (or cancels the navigation when no `loginPath` is set).
|
|
21
|
+
*/
|
|
22
|
+
export declare function applyAuthGuard(router: Router, auth: AuthConfig): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export { useTheme, themeRefKey } from './composables/useTheme';
|
|
|
4
4
|
export { useSiteConfig } from './composables/useSiteConfig';
|
|
5
5
|
export { builtinThemePalettes } from './theme/presets';
|
|
6
6
|
export { ElMessage, ElMessageBox, ElNotification, } from 'element-plus';
|
|
7
|
-
export type { SiteConfig, SiteEnvConfig, SiteViteConfig, SiteExternalLink, NavItem, StandalonePage, ThemeConfig, ThemeOption, ThemePaletteVars, ResolvedNavItem, } from './types';
|
|
7
|
+
export type { SiteConfig, SiteEnvConfig, SiteViteConfig, SiteExternalLink, NavItem, StandalonePage, AuthRule, AuthContext, AuthConfig, RouterConfig, ThemeConfig, ThemeOption, ThemePaletteVars, ResolvedNavItem, } from './types';
|
|
8
8
|
export declare function defineConfig(config: SiteConfig): SiteConfig;
|