@bndynet/vue-site 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -5
- package/README.zh.md +87 -7
- package/bin/vue-site.mjs +152 -30
- package/dist/auth.d.ts +5 -5
- package/dist/composables/useSiteConfig.d.ts +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +1206 -1195
- package/dist/types.d.ts +19 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -23,6 +23,15 @@ Configurable Vue 3 site framework: one package, `site.config.ts`, and Markdown p
|
|
|
23
23
|
npm install @bndynet/vue-site
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
Create starter files in the current directory:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx vue-site init
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This creates `site.config.ts`, `env.d.ts`, and `README.md`. Existing files are never overwritten.
|
|
33
|
+
You can also create the configuration manually:
|
|
34
|
+
|
|
26
35
|
**`site.config.ts`**
|
|
27
36
|
|
|
28
37
|
```typescript
|
|
@@ -44,6 +53,7 @@ export default defineConfig({
|
|
|
44
53
|
my-site/
|
|
45
54
|
package.json
|
|
46
55
|
site.config.ts
|
|
56
|
+
env.d.ts
|
|
47
57
|
public/favicon.ico
|
|
48
58
|
README.md
|
|
49
59
|
pages/guide.md
|
|
@@ -52,15 +62,32 @@ my-site/
|
|
|
52
62
|
**CLI** (`vue-site` and `vs` are the same)
|
|
53
63
|
|
|
54
64
|
```bash
|
|
65
|
+
npx vue-site init
|
|
55
66
|
npx vue-site dev
|
|
56
67
|
npx vue-site build
|
|
57
68
|
npx vue-site preview
|
|
58
69
|
```
|
|
59
70
|
|
|
71
|
+
`init` is safe to run more than once: it creates only missing starter files and leaves existing
|
|
72
|
+
configuration, type declarations, and README content unchanged.
|
|
73
|
+
|
|
74
|
+
Use a different config for an environment with `--config` / `-c`. The selected file must live in
|
|
75
|
+
the current site root and may use the `.ts`, `.js`, `.mts`, or `.mjs` extension. `dev`, `build`, and
|
|
76
|
+
`preview` all support the option:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx vue-site dev --config site.config.dev.ts
|
|
80
|
+
npx vue-site build --config site.config.prod.ts
|
|
81
|
+
npx vue-site preview -c site.config.prod.ts
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
When `--config` is omitted, the CLI keeps auto-discovering `site.config.ts`, `site.config.js`,
|
|
85
|
+
`site.config.mts`, or `site.config.mjs` in that order.
|
|
86
|
+
|
|
60
87
|
Subpath deploy: pass Vite’s public base on the CLI (overrides `env.vite.base` in `site.config`):
|
|
61
88
|
|
|
62
89
|
```bash
|
|
63
|
-
npx vue-site build --base=/app/
|
|
90
|
+
npx vue-site build --config site.config.prod.ts --base=/app/
|
|
64
91
|
# or
|
|
65
92
|
npx vue-site build --base /app/
|
|
66
93
|
```
|
|
@@ -90,10 +117,67 @@ Add `"dev": "vue-site dev"` (or `vs dev`) in `package.json` scripts if you like.
|
|
|
90
117
|
| `auth` | Central authorization policy (`AuthConfig`) — see [Per-page authorization](#per-page-authorization-auth) |
|
|
91
118
|
| `router` | History mode (`RouterConfig`) — `hash` (default) or HTML5 `web`; see [Router history](#router-history-router) |
|
|
92
119
|
| `packageRepository` | Usually set by CLI from `package.json`; omit when using `createSiteApp` alone |
|
|
120
|
+
| `custom` | Public application-specific values, available from `useSiteConfig().config.custom`; see [Custom client configuration](#custom-client-configuration-custom) |
|
|
93
121
|
| `env` | Dev/build options — see below |
|
|
94
122
|
| `bootstrap` | Optional path from site root (e.g. `./bootstrap.ts`) — module loaded once before the Vue app |
|
|
95
123
|
| `configureApp` | Optional `(app) => void \| Promise<void>` after router install, before `mount` (see [Local packages in `configureApp`](#local-packages-in-configureapp)) |
|
|
96
124
|
|
|
125
|
+
### Custom client configuration (`custom`)
|
|
126
|
+
|
|
127
|
+
Use `custom` for public, application-specific values that the framework should preserve without
|
|
128
|
+
interpreting. They are available to every component through `useSiteConfig()`:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// site.config.ts
|
|
132
|
+
export default defineConfig({
|
|
133
|
+
title: 'My Site',
|
|
134
|
+
nav: [/* ... */],
|
|
135
|
+
custom: {
|
|
136
|
+
apiBaseUrl: 'http://localhost/api/v1',
|
|
137
|
+
},
|
|
138
|
+
})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// Inside a Vue component
|
|
143
|
+
const { config } = useSiteConfig()
|
|
144
|
+
const apiBaseUrl = config.custom?.apiBaseUrl
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Use a different config file when the value changes between builds:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// site.config.prod.ts
|
|
151
|
+
export default defineConfig({
|
|
152
|
+
title: 'My Site',
|
|
153
|
+
nav: [/* ... */],
|
|
154
|
+
custom: {
|
|
155
|
+
apiBaseUrl: 'https://prod.com/api/v1',
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npx vue-site build --config site.config.prod.ts
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`custom` is typed as `SiteCustomConfig`. Application-specific keys can be made type-safe with
|
|
165
|
+
module augmentation:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// site-custom.d.ts
|
|
169
|
+
import '@bndynet/vue-site'
|
|
170
|
+
|
|
171
|
+
declare module '@bndynet/vue-site' {
|
|
172
|
+
interface SiteCustomConfig {
|
|
173
|
+
apiBaseUrl: string
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
All `custom` values are bundled into client code and are visible to users. Never store passwords,
|
|
179
|
+
tokens, private keys, or other secrets in this field.
|
|
180
|
+
|
|
97
181
|
### `NavItem`
|
|
98
182
|
|
|
99
183
|
| Property | Description |
|
|
@@ -454,7 +538,7 @@ export default defineConfig({
|
|
|
454
538
|
|
|
455
539
|
- `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).
|
|
456
540
|
- 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.
|
|
457
|
-
- `authorize` also runs
|
|
541
|
+
- `authorize` also runs when the auth-filtered nav menu is refreshed (with only `rule` / `item`) to hide unauthorized items from the nav menu. The framework refreshes this menu after navigations; after changing auth state without a navigation, call `useSiteConfig().refreshAuthNav()`.
|
|
458
542
|
- Guarded routes stay **registered**, so visiting a protected URL directly triggers the guard (and your login redirect) rather than silently 404-ing.
|
|
459
543
|
- The login page itself must **not** carry an `auth` rule (and `loginPath` is always allowed by the guard) to avoid redirect loops.
|
|
460
544
|
|
|
@@ -472,9 +556,9 @@ export default defineConfig({
|
|
|
472
556
|
| | `visible` | `auth` |
|
|
473
557
|
|--|-----------|--------|
|
|
474
558
|
| Decides | Whether the item/route **exists** | Whether the **current user** may enter |
|
|
475
|
-
| When | Build/startup (once) | Navigation (every time) +
|
|
559
|
+
| When | Build/startup (once) | Navigation (every time) + refreshed menu filtering |
|
|
476
560
|
| Route registered | No (unreachable by URL) | Yes (guarded; can redirect to login) |
|
|
477
|
-
| Reacts to login/logout | No | Guard yes; menu filtering
|
|
561
|
+
| Reacts to login/logout | No | Guard yes; menu filtering yes after navigation or `refreshAuthNav()` |
|
|
478
562
|
| Best for | Env / feature-flag / static trimming | Login state, roles, login redirects |
|
|
479
563
|
|
|
480
564
|
Use `visible` for static existence trimming and `auth` for user-based access. They can be combined on the same item.
|
|
@@ -555,7 +639,7 @@ app.mount('#app')
|
|
|
555
639
|
|
|
556
640
|
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.
|
|
557
641
|
|
|
558
|
-
Exports: `createSiteApp`, `defineConfig`, `useTheme`, `useSiteConfig`, `useLocale`, `useLocalize`, `tk`, `resolveLocalized`, `resolveField`, `resolveMessage`, `mergeCatalog`, `flattenMessages`, `isMessageRef`, `localizedPage`, `builtinMessages`, `themeRefKey`, `localeRefKey`. Types: `SiteConfig`, `SiteEnvConfig`, `SiteViteConfig`, `SiteExternalLink`, `ShellConfig`, `ShellAction`, `ShellActionLoader`, `NavItem`, `StandalonePage`, `PageLayout`, `AuthRule`, `AuthContext`, `AuthConfig`, `RouterConfig`, `ThemeConfig`, `ThemeOption`, `ThemePaletteVars`, `ResolvedNavItem`, `I18nConfig`, `LocaleOption`, `LocaleCode`, `LocalizedString`, `IconRegistry`, `MessageRef`, `MessageTree`, `MessageCatalog`, `PageLoader`, `LocalizedPageOptions`.
|
|
642
|
+
Exports: `createSiteApp`, `defineConfig`, `useTheme`, `useSiteConfig`, `useLocale`, `useLocalize`, `tk`, `resolveLocalized`, `resolveField`, `resolveMessage`, `mergeCatalog`, `flattenMessages`, `isMessageRef`, `localizedPage`, `builtinMessages`, `themeRefKey`, `localeRefKey`. Types: `SiteConfig`, `SiteEnvConfig`, `SiteViteConfig`, `SiteCustomConfig`, `SiteExternalLink`, `ShellConfig`, `ShellAction`, `ShellActionLoader`, `NavItem`, `StandalonePage`, `PageLayout`, `AuthRule`, `AuthContext`, `AuthConfig`, `RouterConfig`, `ThemeConfig`, `ThemeOption`, `ThemePaletteVars`, `ResolvedNavItem`, `I18nConfig`, `LocaleOption`, `LocaleCode`, `LocalizedString`, `IconRegistry`, `MessageRef`, `MessageTree`, `MessageCatalog`, `PageLoader`, `LocalizedPageOptions`.
|
|
559
643
|
|
|
560
644
|
### Theme in Vue pages (`useTheme`)
|
|
561
645
|
|
package/README.zh.md
CHANGED
|
@@ -22,6 +22,15 @@
|
|
|
22
22
|
npm install @bndynet/vue-site
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
在当前目录创建初始文件:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx vue-site init
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
该命令会创建 `site.config.ts`、`env.d.ts` 和 `README.md`,绝不会覆盖已有文件。
|
|
32
|
+
也可以手动创建配置:
|
|
33
|
+
|
|
25
34
|
**`site.config.ts`**
|
|
26
35
|
|
|
27
36
|
```typescript
|
|
@@ -43,6 +52,7 @@ export default defineConfig({
|
|
|
43
52
|
my-site/
|
|
44
53
|
package.json
|
|
45
54
|
site.config.ts
|
|
55
|
+
env.d.ts
|
|
46
56
|
public/favicon.ico
|
|
47
57
|
README.md
|
|
48
58
|
pages/guide.md
|
|
@@ -51,15 +61,30 @@ my-site/
|
|
|
51
61
|
**CLI**(`vue-site` 与 `vs` 等价)
|
|
52
62
|
|
|
53
63
|
```bash
|
|
64
|
+
npx vue-site init
|
|
54
65
|
npx vue-site dev
|
|
55
66
|
npx vue-site build
|
|
56
67
|
npx vue-site preview
|
|
57
68
|
```
|
|
58
69
|
|
|
70
|
+
`init` 可以安全地重复执行:它只创建缺失的初始文件,不会改动已有配置、类型声明或 README。
|
|
71
|
+
|
|
72
|
+
可以通过 `--config` / `-c` 为不同环境选择配置文件。指定的文件必须直接位于当前站点根目录,
|
|
73
|
+
扩展名可以是 `.ts`、`.js`、`.mts` 或 `.mjs`;`dev`、`build`、`preview` 都支持该参数:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx vue-site dev --config site.config.dev.ts
|
|
77
|
+
npx vue-site build --config site.config.prod.ts
|
|
78
|
+
npx vue-site preview -c site.config.prod.ts
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
省略 `--config` 时,CLI 仍会依次自动查找 `site.config.ts`、`site.config.js`、
|
|
82
|
+
`site.config.mts`、`site.config.mjs`。
|
|
83
|
+
|
|
59
84
|
子路径部署:在 CLI 上传入 Vite 的公共 base(会覆盖 `site.config` 中的 `env.vite.base`):
|
|
60
85
|
|
|
61
86
|
```bash
|
|
62
|
-
npx vue-site build --base=/app/
|
|
87
|
+
npx vue-site build --config site.config.prod.ts --base=/app/
|
|
63
88
|
# 或
|
|
64
89
|
npx vue-site build --base /app/
|
|
65
90
|
```
|
|
@@ -87,10 +112,65 @@ npx vue-site build --base /app/
|
|
|
87
112
|
| `auth` | 中心化的授权策略(`AuthConfig`)—— 参见[按页面授权](#按页面授权-auth) |
|
|
88
113
|
| `router` | 历史模式(`RouterConfig`)—— `hash`(默认)或 HTML5 `web`;参见[路由历史](#路由历史-router) |
|
|
89
114
|
| `packageRepository` | 通常由 CLI 从 `package.json` 设置;单独使用 `createSiteApp` 时可省略 |
|
|
115
|
+
| `custom` | 公开的业务自定义配置,可通过 `useSiteConfig().config.custom` 读取;参见[自定义客户端配置](#自定义客户端配置-custom) |
|
|
90
116
|
| `env` | 开发/构建选项 —— 见下文 |
|
|
91
117
|
| `bootstrap` | 可选的站点根目录相对路径(如 `./bootstrap.ts`)—— 在 Vue 应用之前加载一次的模块 |
|
|
92
118
|
| `configureApp` | 可选的 `(app) => void \| Promise<void>`,在路由安装之后、`mount` 之前执行(参见 [`configureApp` 中的本地包](#在-configureapp-中使用本地包)) |
|
|
93
119
|
|
|
120
|
+
### 自定义客户端配置(`custom`)
|
|
121
|
+
|
|
122
|
+
`custom` 用于存放框架无需解释、只需原样保留的公开业务配置。任何组件都可以通过
|
|
123
|
+
`useSiteConfig()` 读取:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// site.config.ts
|
|
127
|
+
export default defineConfig({
|
|
128
|
+
title: '我的站点',
|
|
129
|
+
nav: [/* ... */],
|
|
130
|
+
custom: {
|
|
131
|
+
apiBaseUrl: 'http://localhost/api/v1',
|
|
132
|
+
},
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// Vue 组件中
|
|
138
|
+
const { config } = useSiteConfig()
|
|
139
|
+
const apiBaseUrl = config.custom?.apiBaseUrl
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
如果不同构建环境使用不同的值,可以选择不同的配置文件:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// site.config.prod.ts
|
|
146
|
+
export default defineConfig({
|
|
147
|
+
title: '我的站点',
|
|
148
|
+
nav: [/* ... */],
|
|
149
|
+
custom: {
|
|
150
|
+
apiBaseUrl: 'https://prod.com/api/v1',
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npx vue-site build --config site.config.prod.ts
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`custom` 的类型是 `SiteCustomConfig`。可以通过模块扩充为业务字段提供强类型:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// site-custom.d.ts
|
|
163
|
+
import '@bndynet/vue-site'
|
|
164
|
+
|
|
165
|
+
declare module '@bndynet/vue-site' {
|
|
166
|
+
interface SiteCustomConfig {
|
|
167
|
+
apiBaseUrl: string
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
所有 `custom` 值都会进入客户端代码,用户可以查看。不要在这里存放密码、Token、私钥或其他密钥。
|
|
173
|
+
|
|
94
174
|
### `NavItem`
|
|
95
175
|
|
|
96
176
|
| 属性 | 说明 |
|
|
@@ -378,9 +458,9 @@ export default defineConfig({
|
|
|
378
458
|
并返回 `true`(允许)、`false`(拒绝)或一个路径 `string`(重定向,例如到登录页)。
|
|
379
459
|
- 返回 `false` 时,用户会被发送到 `auth.loginPath`,并把请求的路径作为 `redirect` 查询参数
|
|
380
460
|
(`/login?redirect=/admin`);如果未设置 `loginPath`,则取消该次导航。
|
|
381
|
-
- `authorize`
|
|
382
|
-
|
|
383
|
-
|
|
461
|
+
- `authorize` 也会在 auth 过滤后的导航菜单刷新时运行(仅带 `rule` / `item`),以从导航菜单中隐藏未授权的条目。
|
|
462
|
+
框架会在导航完成后刷新此菜单;如果你在没有导航的情况下改变登录状态,请调用
|
|
463
|
+
`useSiteConfig().refreshAuthNav()`。
|
|
384
464
|
- 受保护的路由仍保持**注册**,因此直接访问受保护 URL 会触发守卫(及你的登录重定向),
|
|
385
465
|
而不是静默地 404。
|
|
386
466
|
- 登录页本身**不能**携带 `auth` 规则(且 `loginPath` 始终被守卫允许),以避免重定向循环。
|
|
@@ -400,9 +480,9 @@ export default defineConfig({
|
|
|
400
480
|
| | `visible` | `auth` |
|
|
401
481
|
|--|-----------|--------|
|
|
402
482
|
| 决定 | 条目/路由是否**存在** | **当前用户**是否可以进入 |
|
|
403
|
-
| 时机 | 构建/启动(一次) | 导航(每次)+
|
|
483
|
+
| 时机 | 构建/启动(一次) | 导航(每次)+ 刷新时的菜单过滤 |
|
|
404
484
|
| 路由是否注册 | 否(无法通过 URL 访问) | 是(受守卫保护;可重定向到登录) |
|
|
405
|
-
| 对登录/登出的响应 | 否 |
|
|
485
|
+
| 对登录/登出的响应 | 否 | 守卫:是;菜单过滤:导航后或 `refreshAuthNav()` 后是 |
|
|
406
486
|
| 最适合 | 环境 / 功能开关 / 静态裁剪 | 登录状态、角色、登录重定向 |
|
|
407
487
|
|
|
408
488
|
用 `visible` 进行静态存在性裁剪,用 `auth` 进行基于用户的访问控制。它们可以组合在同一个条目上。
|
|
@@ -497,7 +577,7 @@ app.mount('#app')
|
|
|
497
577
|
返回 `Promise` 时会 **await** 它。如果你在配置中设置了可选的 `bootstrap`,该模块会在应用创建前加载;
|
|
498
578
|
如果省略 `bootstrap`,则跳过该步骤。
|
|
499
579
|
|
|
500
|
-
导出:`createSiteApp`、`defineConfig`、`useTheme`、`useSiteConfig`、`useLocale`、`useLocalize`、`tk`、`resolveLocalized`、`resolveField`、`resolveMessage`、`mergeCatalog`、`flattenMessages`、`isMessageRef`、`localizedPage`、`builtinMessages`、`themeRefKey`、`localeRefKey`。类型:`SiteConfig`、`SiteEnvConfig`、`SiteViteConfig`、`SiteExternalLink`、`NavItem`、`StandalonePage`、`PageLayout`、`AuthRule`、`AuthContext`、`AuthConfig`、`RouterConfig`、`ThemeConfig`、`ThemeOption`、`ThemePaletteVars`、`ResolvedNavItem`、`I18nConfig`、`LocaleOption`、`LocaleCode`、`LocalizedString`、`MessageRef`、`MessageTree`、`MessageCatalog`、`PageLoader`、`LocalizedPageOptions`。
|
|
580
|
+
导出:`createSiteApp`、`defineConfig`、`useTheme`、`useSiteConfig`、`useLocale`、`useLocalize`、`tk`、`resolveLocalized`、`resolveField`、`resolveMessage`、`mergeCatalog`、`flattenMessages`、`isMessageRef`、`localizedPage`、`builtinMessages`、`themeRefKey`、`localeRefKey`。类型:`SiteConfig`、`SiteEnvConfig`、`SiteViteConfig`、`SiteCustomConfig`、`SiteExternalLink`、`NavItem`、`StandalonePage`、`PageLayout`、`AuthRule`、`AuthContext`、`AuthConfig`、`RouterConfig`、`ThemeConfig`、`ThemeOption`、`ThemePaletteVars`、`ResolvedNavItem`、`I18nConfig`、`LocaleOption`、`LocaleCode`、`LocalizedString`、`MessageRef`、`MessageTree`、`MessageCatalog`、`PageLoader`、`LocalizedPageOptions`。
|
|
501
581
|
|
|
502
582
|
### 在 Vue 页面中使用主题(`useTheme`)
|
|
503
583
|
|
package/bin/vue-site.mjs
CHANGED
|
@@ -45,7 +45,7 @@ const cwdGrandparent = resolve(cwd, '../..')
|
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* @param {string[]} argv
|
|
48
|
-
* @returns {{ command: string, cliBase?: string }}
|
|
48
|
+
* @returns {{ command: string, cliBase?: string, cliConfig?: string }}
|
|
49
49
|
*/
|
|
50
50
|
function parseCliArgv(argv = process.argv) {
|
|
51
51
|
const sub = argv[2]
|
|
@@ -55,6 +55,7 @@ function parseCliArgv(argv = process.argv) {
|
|
|
55
55
|
: 'dev'
|
|
56
56
|
|
|
57
57
|
let cliBase
|
|
58
|
+
let cliConfig
|
|
58
59
|
const flagStart = command === sub && sub ? 3 : 2
|
|
59
60
|
for (let i = flagStart; i < argv.length; i++) {
|
|
60
61
|
const a = argv[i]
|
|
@@ -77,9 +78,28 @@ function parseCliArgv(argv = process.argv) {
|
|
|
77
78
|
process.exit(1)
|
|
78
79
|
}
|
|
79
80
|
cliBase = v
|
|
81
|
+
} else if (a === '--config' || a === '-c') {
|
|
82
|
+
const v = argv[i + 1]
|
|
83
|
+
if (!v || v.startsWith('-')) {
|
|
84
|
+
console.error(
|
|
85
|
+
'[vue-site] --config requires a value (e.g. --config site.config.prod.ts)',
|
|
86
|
+
)
|
|
87
|
+
process.exit(1)
|
|
88
|
+
}
|
|
89
|
+
cliConfig = v
|
|
90
|
+
i++
|
|
91
|
+
} else if (a.startsWith('--config=')) {
|
|
92
|
+
const v = a.slice('--config='.length)
|
|
93
|
+
if (!v) {
|
|
94
|
+
console.error(
|
|
95
|
+
'[vue-site] --config= requires a value (e.g. --config=site.config.prod.ts)',
|
|
96
|
+
)
|
|
97
|
+
process.exit(1)
|
|
98
|
+
}
|
|
99
|
+
cliConfig = v
|
|
80
100
|
}
|
|
81
101
|
}
|
|
82
|
-
return { command, cliBase }
|
|
102
|
+
return { command, cliBase, cliConfig }
|
|
83
103
|
}
|
|
84
104
|
|
|
85
105
|
function isLikelyFilesystemRoot(dir) {
|
|
@@ -105,20 +125,114 @@ const configCandidates = [
|
|
|
105
125
|
'site.config.mts',
|
|
106
126
|
'site.config.mjs',
|
|
107
127
|
]
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
128
|
+
|
|
129
|
+
const initSiteConfig = `import { defineConfig } from '@bndynet/vue-site'
|
|
130
|
+
|
|
131
|
+
export default defineConfig({
|
|
132
|
+
title: 'My Site',
|
|
133
|
+
// Public client configuration. Never place secrets here.
|
|
134
|
+
custom: {
|
|
135
|
+
// apiBaseUrl: 'http://localhost/api/v1',
|
|
136
|
+
},
|
|
137
|
+
nav: [
|
|
138
|
+
{ label: 'Home', icon: 'home', page: './README.md' },
|
|
139
|
+
],
|
|
140
|
+
})
|
|
141
|
+
`
|
|
142
|
+
|
|
143
|
+
const initEnvTypes = `/// <reference types="vite/client" />
|
|
144
|
+
|
|
145
|
+
import '@bndynet/vue-site'
|
|
146
|
+
|
|
147
|
+
declare module '@bndynet/vue-site' {
|
|
148
|
+
interface SiteCustomConfig {
|
|
149
|
+
// apiBaseUrl: string
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
`
|
|
153
|
+
|
|
154
|
+
const initReadme = `# My Site
|
|
155
|
+
|
|
156
|
+
Welcome to your vue-site project.
|
|
157
|
+
`
|
|
158
|
+
|
|
159
|
+
class CliConfigError extends Error {}
|
|
160
|
+
|
|
161
|
+
function createInitFile(name, contents) {
|
|
162
|
+
const filePath = resolve(cwd, name)
|
|
163
|
+
try {
|
|
164
|
+
fs.writeFileSync(filePath, contents, { flag: 'wx' })
|
|
165
|
+
console.log(` created ${name}`)
|
|
166
|
+
return true
|
|
167
|
+
} catch (e) {
|
|
168
|
+
if (e && typeof e === 'object' && e.code === 'EEXIST') {
|
|
169
|
+
console.log(` skipped ${name} (already exists)`)
|
|
170
|
+
return false
|
|
171
|
+
}
|
|
172
|
+
throw e
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function initSite() {
|
|
177
|
+
console.log('[vue-site] Initializing site files:')
|
|
178
|
+
|
|
179
|
+
let created = 0
|
|
180
|
+
const existingConfig = configCandidates.find((file) =>
|
|
181
|
+
fs.existsSync(resolve(cwd, file)),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if (existingConfig) {
|
|
185
|
+
console.log(` skipped site.config.ts (${existingConfig} already exists)`)
|
|
186
|
+
} else if (createInitFile('site.config.ts', initSiteConfig)) {
|
|
187
|
+
created++
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (createInitFile('env.d.ts', initEnvTypes)) created++
|
|
191
|
+
if (createInitFile('README.md', initReadme)) created++
|
|
192
|
+
|
|
193
|
+
console.log(
|
|
194
|
+
created > 0
|
|
195
|
+
? `\n[vue-site] Created ${created} file${created === 1 ? '' : 's'}. Run \`npx vue-site dev\` to start.`
|
|
196
|
+
: '\n[vue-site] Nothing to create; existing files were left unchanged.',
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function resolveSiteConfig(cliConfig) {
|
|
201
|
+
if (cliConfig) {
|
|
202
|
+
const configPath = resolve(cwd, cliConfig)
|
|
203
|
+
if (dirname(configPath) !== cwd) {
|
|
204
|
+
throw new CliConfigError(
|
|
205
|
+
`[vue-site] --config must name a file in the site root (${cwd}): ${cliConfig}`,
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
if (!/\.(?:ts|js|mts|mjs)$/.test(configPath)) {
|
|
209
|
+
throw new CliConfigError(
|
|
210
|
+
`[vue-site] Unsupported config extension: ${cliConfig}. ` +
|
|
211
|
+
'Use a .ts, .js, .mts, or .mjs file.',
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
if (!fs.existsSync(configPath) || !fs.statSync(configPath).isFile()) {
|
|
215
|
+
throw new CliConfigError(`[vue-site] Config file not found: ${configPath}`)
|
|
216
|
+
}
|
|
217
|
+
return basename(configPath)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const foundConfig = configCandidates.find((f) =>
|
|
221
|
+
fs.existsSync(resolve(cwd, f)),
|
|
222
|
+
)
|
|
223
|
+
if (foundConfig) return foundConfig
|
|
224
|
+
|
|
225
|
+
throw new CliConfigError(
|
|
111
226
|
'\x1b[31mError: No site.config.ts found in the current directory.\x1b[0m\n\n' +
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
227
|
+
'Run `npx vue-site init` to create starter files, or create site.config.ts manually:\n\n' +
|
|
228
|
+
' import type { SiteConfig } from \'@bndynet/vue-site\'\n\n' +
|
|
229
|
+
' export default {\n' +
|
|
230
|
+
' title: \'My Site\',\n' +
|
|
231
|
+
' nav: [\n' +
|
|
232
|
+
' { label: \'Home\', icon: \'home\', page: () => import(\'./README.md?raw\') },\n' +
|
|
233
|
+
' ],\n' +
|
|
234
|
+
' } satisfies SiteConfig\n',
|
|
120
235
|
)
|
|
121
|
-
process.exit(1)
|
|
122
236
|
}
|
|
123
237
|
|
|
124
238
|
const VIRTUAL_ENTRY = 'virtual:vue-site-entry'
|
|
@@ -317,10 +431,10 @@ function buildBootstrapScript({ siteConfig, siteConfigSpecifier }) {
|
|
|
317
431
|
].join('\n')
|
|
318
432
|
}
|
|
319
433
|
|
|
320
|
-
function buildEntryCode(siteConfig) {
|
|
434
|
+
function buildEntryCode(siteConfig, configFile) {
|
|
321
435
|
return buildBootstrapScript({
|
|
322
436
|
siteConfig,
|
|
323
|
-
siteConfigSpecifier: `/${
|
|
437
|
+
siteConfigSpecifier: `/${configFile}`,
|
|
324
438
|
})
|
|
325
439
|
}
|
|
326
440
|
|
|
@@ -358,8 +472,8 @@ function preloadAssetStubPlugin() {
|
|
|
358
472
|
}
|
|
359
473
|
}
|
|
360
474
|
|
|
361
|
-
async function loadSiteConfig() {
|
|
362
|
-
const configPath = resolve(cwd,
|
|
475
|
+
async function loadSiteConfig(configFile) {
|
|
476
|
+
const configPath = resolve(cwd, configFile)
|
|
363
477
|
const raw = fs.readFileSync(configPath, 'utf-8')
|
|
364
478
|
|
|
365
479
|
// Stub the framework's value imports so the config evaluates without the real (browser-only)
|
|
@@ -398,7 +512,7 @@ async function loadSiteConfig() {
|
|
|
398
512
|
stubbed.replace(/import\.meta\.glob/g, '__vueSiteGlobStub')
|
|
399
513
|
}
|
|
400
514
|
|
|
401
|
-
const isTs = /\.m?ts$/.test(
|
|
515
|
+
const isTs = /\.m?ts$/.test(configFile)
|
|
402
516
|
// Write the stubbed entry next to the original so its relative imports (`./locales`) resolve.
|
|
403
517
|
const entryFile = resolve(
|
|
404
518
|
dirname(configPath),
|
|
@@ -428,7 +542,7 @@ async function loadSiteConfig() {
|
|
|
428
542
|
return mod.default || {}
|
|
429
543
|
} catch (e) {
|
|
430
544
|
throw new Error(
|
|
431
|
-
`[vue-site] Could not pre-load site config from ${
|
|
545
|
+
`[vue-site] Could not pre-load site config from ${configFile}: ${e.message}\n` +
|
|
432
546
|
` This usually means your config imports modules Node can't resolve directly ` +
|
|
433
547
|
`(path aliases like @/..., or framework APIs other than defineConfig).`,
|
|
434
548
|
)
|
|
@@ -704,8 +818,8 @@ function vueSitePlugin(entryCode, htmlTemplate, iconRegistryCode) {
|
|
|
704
818
|
}
|
|
705
819
|
|
|
706
820
|
async function buildViteConfig(options = {}) {
|
|
707
|
-
const { cliBase, siteConfig: siteConfigOption } = options
|
|
708
|
-
const siteConfig = siteConfigOption ?? (await loadSiteConfig())
|
|
821
|
+
const { cliBase, configFile, siteConfig: siteConfigOption } = options
|
|
822
|
+
const siteConfig = siteConfigOption ?? (await loadSiteConfig(configFile))
|
|
709
823
|
const env = siteConfig.env || {}
|
|
710
824
|
const {
|
|
711
825
|
port,
|
|
@@ -816,7 +930,7 @@ async function buildViteConfig(options = {}) {
|
|
|
816
930
|
}
|
|
817
931
|
}
|
|
818
932
|
|
|
819
|
-
const entryCode = buildEntryCode(siteConfig)
|
|
933
|
+
const entryCode = buildEntryCode(siteConfig, configFile)
|
|
820
934
|
const iconRegistryCode = buildIconRegistryCode(siteConfig)
|
|
821
935
|
const htmlTemplate = buildHtmlShell(
|
|
822
936
|
`<script type="module" src="/@id/__x00__${VIRTUAL_ENTRY}"></script>`,
|
|
@@ -953,9 +1067,15 @@ async function buildViteConfig(options = {}) {
|
|
|
953
1067
|
}
|
|
954
1068
|
|
|
955
1069
|
async function run() {
|
|
956
|
-
const { command, cliBase } = parseCliArgv()
|
|
957
|
-
|
|
958
|
-
|
|
1070
|
+
const { command, cliBase, cliConfig } = parseCliArgv()
|
|
1071
|
+
if (command === 'init') {
|
|
1072
|
+
initSite()
|
|
1073
|
+
return
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
const configFile = resolveSiteConfig(cliConfig)
|
|
1077
|
+
const siteConfig = await loadSiteConfig(configFile)
|
|
1078
|
+
const viteConfig = await buildViteConfig({ cliBase, configFile, siteConfig })
|
|
959
1079
|
|
|
960
1080
|
if (command === 'dev') {
|
|
961
1081
|
const server = await createServer(viteConfig)
|
|
@@ -969,7 +1089,7 @@ async function run() {
|
|
|
969
1089
|
if (!hadHtml) {
|
|
970
1090
|
const bootstrapScript = buildBootstrapScript({
|
|
971
1091
|
siteConfig,
|
|
972
|
-
siteConfigSpecifier: `./${
|
|
1092
|
+
siteConfigSpecifier: `./${configFile}`,
|
|
973
1093
|
})
|
|
974
1094
|
const buildHtml = buildHtmlShell(
|
|
975
1095
|
`<script type="module">\n${bootstrapScript}\n </script>`,
|
|
@@ -992,14 +1112,16 @@ async function run() {
|
|
|
992
1112
|
server.printUrls()
|
|
993
1113
|
} else {
|
|
994
1114
|
console.log(
|
|
995
|
-
'Usage: vue-site|vs <dev|build|preview> [--base=<path>]\n' +
|
|
996
|
-
'
|
|
1115
|
+
'Usage: vue-site|vs <init|dev|build|preview> [--base=<path>] [--config=<file>]\n' +
|
|
1116
|
+
' init Create site.config.ts, env.d.ts, and README.md without overwriting files\n' +
|
|
1117
|
+
' --base Public path for assets (overrides env.vite.base); e.g. --base=/app/\n' +
|
|
1118
|
+
' --config, -c Site config file in the current site root; e.g. site.config.prod.ts',
|
|
997
1119
|
)
|
|
998
1120
|
process.exit(1)
|
|
999
1121
|
}
|
|
1000
1122
|
}
|
|
1001
1123
|
|
|
1002
1124
|
run().catch((err) => {
|
|
1003
|
-
console.error(err)
|
|
1125
|
+
console.error(err instanceof CliConfigError ? err.message : err)
|
|
1004
1126
|
process.exit(1)
|
|
1005
1127
|
})
|
package/dist/auth.d.ts
CHANGED
|
@@ -3,11 +3,11 @@ import { AuthConfig, ResolvedNavItem } from './types';
|
|
|
3
3
|
/** Query param carrying the originally requested path when redirecting to the login page. */
|
|
4
4
|
export declare const AUTH_REDIRECT_QUERY = "redirect";
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* Drop nav items the current user is not authorized to see, so they do not appear in the rendered
|
|
7
|
+
* menu. Mirrors the pruning of `filterNavItems`: a hidden parent removes its subtree, and a group
|
|
8
|
+
* left with no children and no own `page` / `link` is pruned. Routes are not affected (they are
|
|
9
|
+
* still registered by `createSiteRouter`); only the menu list is filtered. An item is shown only
|
|
10
|
+
* when `authorize` returns exactly `true`.
|
|
11
11
|
*/
|
|
12
12
|
export declare function pruneNavByAuth(items: ResolvedNavItem[], auth: AuthConfig): Promise<ResolvedNavItem[]>;
|
|
13
13
|
/**
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { InjectionKey } from 'vue';
|
|
2
2
|
import { SiteConfig, ResolvedNavItem } from '../types';
|
|
3
3
|
export interface SiteContext {
|
|
4
|
+
/** Original site configuration passed to `createSiteApp()`. */
|
|
4
5
|
config: SiteConfig;
|
|
6
|
+
/** Auth-filtered nav tree rendered by the layout. Kept as a reactive array. */
|
|
5
7
|
resolvedNav: ResolvedNavItem[];
|
|
8
|
+
/** Re-run auth menu filtering after consumer auth state changes, such as login/logout. */
|
|
9
|
+
refreshAuthNav: () => Promise<void>;
|
|
6
10
|
}
|
|
7
11
|
/** Use `Symbol.for` so pages still inject context if Vite resolves the package twice. */
|
|
8
12
|
export declare const siteContextKey: InjectionKey<SiteContext>;
|
package/dist/index.d.ts
CHANGED
|
@@ -9,5 +9,5 @@ export { builtinMessages } from './i18n-messages';
|
|
|
9
9
|
export { useSiteConfig } from './composables/useSiteConfig';
|
|
10
10
|
export { builtinThemePalettes } from './theme/presets';
|
|
11
11
|
export { ElMessage, ElMessageBox, ElNotification, } from 'element-plus';
|
|
12
|
-
export type { SiteConfig, SiteEnvConfig, SiteViteConfig, SiteExternalLink, ShellConfig, ShellAction, ShellActionLoader, NavItem, StandalonePage, PageLayout, AuthRule, AuthContext, AuthConfig, RouterConfig, ThemeConfig, ThemeOption, ThemePaletteVars, ResolvedNavItem, LocaleCode, LocalizedString, IconRegistry, MessageRef, MessageTree, LocaleOption, I18nConfig, PageLoader, } from './types';
|
|
12
|
+
export type { SiteConfig, SiteEnvConfig, SiteViteConfig, SiteCustomConfig, SiteExternalLink, ShellConfig, ShellAction, ShellActionLoader, NavItem, StandalonePage, PageLayout, AuthRule, AuthContext, AuthConfig, RouterConfig, ThemeConfig, ThemeOption, ThemePaletteVars, ResolvedNavItem, LocaleCode, LocalizedString, IconRegistry, MessageRef, MessageTree, LocaleOption, I18nConfig, PageLoader, } from './types';
|
|
13
13
|
export declare function defineConfig(config: SiteConfig): SiteConfig;
|