@h-ai/cli 0.1.0-alpha.44 → 0.1.0-alpha.46

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@h-ai/cli",
3
3
  "type": "module",
4
- "version": "0.1.0-alpha.44",
4
+ "version": "0.1.0-alpha.46",
5
5
  "description": "Hai Framework CLI for project scaffolding and code generation.",
6
6
  "license": "Apache-2.0",
7
7
  "keywords": [
@@ -34,7 +34,7 @@
34
34
  "ora": "^9.1.0",
35
35
  "prompts": "^2.4.2",
36
36
  "yaml": "^2.7.0",
37
- "@h-ai/core": "0.1.0-alpha.44"
37
+ "@h-ai/core": "0.1.0-alpha.46"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/fs-extra": "^11.0.4",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "{{projectName}}",
3
- "version": "0.1.0-alpha.44",
3
+ "version": "0.1.0-alpha.46",
4
4
  "private": true,
5
5
  "description": "{{#if_eq appType 'admin'}}Enterprise management system built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'website'}}Enterprise website built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'h5'}}Mobile H5 app built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'api'}}REST API service built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'mobile-app'}}Mobile app built with hai Agent Framework{{/if_eq}}",
6
6
  "keywords": ["hai", {{#unless isSvelteOnlyApp}}"sveltekit", {{/unless}}"svelte5", "{{appType}}"],
@@ -39,6 +39,7 @@ description: 使用 @h-ai/serv 将 oRPC contract 挂载为最小 HTTP App 抽象
39
39
  - 通过 `serv.createRuntimeSecurityPolicy(...)` 对生产 CORS Origin、原生 WebView Origin、Secure Cookie 与 API 文档暴露执行 fail-closed 策略
40
40
  - 启用传输加密:`serv.createApp({ transport: { crypto } })`
41
41
  - 高级场景通过 `createApp({ middlewares })` 自定义 HTTP middleware,或通过共享类型自定义 procedure wrapper
42
+ - 公开只读文件通过 `serv.storageAssets(...)` 统一处理安全 key、MIME 白名单、GET/HEAD、ETag 与缓存头
42
43
  - 为 Audio WebSocket 配置一次性 ticket 校验、IAM/模型/配额授权与并发释放钩子
43
44
 
44
45
  ---
@@ -299,7 +300,7 @@ export function createServerApp() {
299
300
  http?, // ServHttpConfigInput — HTTP 端点配置(见下方配置节)
300
301
  middlewares?, // readonly ServMiddlewareMount[] — 自定义 HTTP middleware(日志/CORS/限流/租户头校验)
301
302
  iam?, // ServIam — 顶层 IAM 句柄,同时驱动 access token 校验与 refresh cookie【推荐】
302
- refreshCookie?, // RefreshCookieConfig — httpOnly refresh cookie 刷新路径(见下方 cookie 节)
303
+ refreshCookie?, // RefreshCookieConfig — 浏览器 cookie / 受信原生 body 的 refresh token 通道
303
304
  transport?, // { crypto, keyExchangePath?, excludePaths?, maxClients? } — 统一传输加密
304
305
  verifyToken?, // (token) => Promise<HaiResult<ServSession>> — 逃脱口:不使用 iam 时提供自定义校验
305
306
  createContext?, // CreateServContext — 高级:完全接管上下文构造(设置后 serv 不再自动填充 session)
@@ -453,9 +454,13 @@ const app = serv.createApp({
453
454
  },
454
455
  {
455
456
  path: '/assets/*',
456
- middleware: c => c.body(imageBytes, 200, {
457
- 'Content-Type': 'image/png',
458
- 'Cache-Control': 'public, max-age=31536000, immutable',
457
+ middleware: serv.storageAssets({
458
+ storage,
459
+ pathPrefix: '/assets/',
460
+ keyPattern: /^public\/[\w-]+\.png$/,
461
+ allowedContentTypes: ['image/png'],
462
+ cacheControl: 'public, max-age=31536000, immutable',
463
+ crossOriginResourcePolicy: 'cross-origin',
459
464
  }),
460
465
  },
461
466
  ],
@@ -465,7 +470,8 @@ const app = serv.createApp({
465
470
  执行顺序固定为:`securityHeaders` → `middlewares` → `transport`(若启用)→ health/refresh-cookie/OpenAPI/RPC/docs/oRPC routes。
466
471
 
467
472
  - `middlewares` 先于 `transport` 执行,因此 CORS preflight 或二进制端点可以直接短路;短路响应仍带内置安全头,但不会经过 transport。
468
- - 文件端点的允许方法、路径校验、MIME、缓存和访问授权由应用显式决定;框架不内置头像等业务概念。
473
+ - 公开只读文件优先使用 `serv.storageAssets(...)`;应用仍须显式提供 key/MIME 白名单、缓存与跨域策略,框架不内置头像等业务概念。
474
+ - 私有资源、Range 下载或动态授权继续使用自定义 middleware/procedure。
469
475
  - 若需要读取解密后的业务 body,请改用 context / procedure 层扩展。
470
476
  - 浏览器若需读取自定义响应头(例如 transport 的 `X-Encrypted`),记得通过 `serv.cors({ exposedHeaders: [...] })` 显式暴露。
471
477
 
@@ -695,6 +701,7 @@ procedure 包装器内置以下错误:
695
701
  `serv.parseRequestContext()` / `extractBearerToken()` 解析、`buildAuthContextFactory()` 校验的就是它。
696
702
  - `refreshToken`:长期凭证,启用 `refreshCookie` 后只保存在 httpOnly cookie;
697
703
  它不会走 `Authorization`,也不会被 `extractBearerToken()` 读取,只会在 `/auth/refresh` 被服务端取出。
704
+ 受信原生客户端可通过 `nativeTokenTransport` 改用 JSON body,但必须同时校验客户端标识与 Origin。
698
705
 
699
706
  ### 适用场景
700
707
 
@@ -709,6 +716,9 @@ procedure 包装器内置以下错误:
709
716
  4. serv 读取 cookie → 调用 `iam.session.refresh` → 返回新 access token + 更新 cookie(响应体不暴露 refresh token)
710
717
  5. POST `/auth/logout` → serv 清除 cookie(`Max-Age=0`)
711
718
 
719
+ 原生客户端匹配 `nativeTokenTransport.isRequest` 后,登录/注册/刷新响应保留轮换后的
720
+ `refreshToken`,刷新请求从 `{ refreshToken }` body 读取,且不读写浏览器 Cookie。
721
+
712
722
  若同时启用 `transport: { crypto }`,`/auth/refresh` 也必须走加密链路;cookie-only 刷新请求允许空 body,服务端不要把 `Request.body === null` 误判为需要解密的请求体。
713
723
 
714
724
  ### 配置
@@ -724,6 +734,12 @@ const app = serv.createApp({
724
734
  // maxAge?: number 默认 30 * 24 * 3600(30 天)
725
735
  // secure?: boolean 默认 NODE_ENV=production 时开启
726
736
  // onRefresh?: (token) => ... 可选:覆盖默认的 iam.session.refresh
737
+ nativeTokenTransport: {
738
+ // 必须 fail-closed:同时校验平台标识与允许的原生 Origin。
739
+ isRequest: request =>
740
+ request.headers.get('x-client-platform') === 'capacitor'
741
+ && security.isNativeOrigin(request.headers.get('origin') ?? ''),
742
+ },
727
743
  },
728
744
  // ✅ 顶层 iam 同时为 access token 校验提供默认实现,无需再传 verifyToken
729
745
  })
@@ -279,7 +279,7 @@ const p = usePlatform()
279
279
  | `Form` | `loading`, `disabled`, `onsubmit` | 表单容器 |
280
280
  | `FormField` | `label`, `name`, `error`, `hint`, `required` | 表单字段 |
281
281
  | `Modal` | `open`, `title`, `size`, `closeOnBackdrop` | 模态框 |
282
- | `Drawer` | `open`, `position`, `size` | 抽屉 |
282
+ | `Drawer` | `open`, `position`, `size`, `width`, `resizable`, `widthStorageKey` | 可选拖动并记忆宽度的抽屉 |
283
283
  | `DataTable` | `data`, `columns`, `keyField`, `sortKey`, `sortDir`, `onsort`, snippet slots | 数据表格(列定义 `sortable` 可排序) |
284
284
  | `Combobox` | `options`, `value`, `multiple`, `placeholder`, `error`, `onchange` | 可搜索选择 |
285
285
  | `Calendar` | `value`, `minValue`, `maxValue` | 独立日历 |
@@ -461,7 +461,7 @@ const p = usePlatform()
461
461
  | 配置项 | 说明 |
462
462
  | --- | --- |
463
463
  | `form.variant` | `'drawer'`(抽屉,默认)或 `'modal'`(弹出窗口) |
464
- | `form.drawerSize` / `form.drawerWidth` | 抽屉尺寸预设 / 自定义 CSS 宽度(宽度优先) |
464
+ | `form.drawerSize` / `form.drawerWidth` / `form.drawerResizable` | 抽屉尺寸预设 / 自定义 CSS 宽度 / 是否允许拖动并按资源记忆宽度(默认允许) |
465
465
  | `form.modalSize` / `form.modalWidth` / `form.modalHeight` | 弹窗尺寸预设 / 自定义宽高 |
466
466
  | `pagination.showSizeChanger` | 每页条数选择器(默认开启) |
467
467
  | `pagination.pageSizeOptions` | 每页条数候选项(默认 `[10, 20, 50, 100]`) |
@@ -472,6 +472,7 @@ const p = usePlatform()
472
472
  | `showHeader` / `toolbarLeading` | 关闭默认页头并在工具栏左侧放置标题或说明 |
473
473
  | `toolbarActions` | 工具栏业务操作 snippet |
474
474
  | `card` | 卡片列表项渲染 snippet;仍复用分页和独立滚动容器 |
475
+ | `detailColumns` | 点击后打开详情抽屉的列 key,例如 `['title']` |
475
476
  | `sortableColumns` | 可执行服务端排序的列;为空时使用全部列表列 |
476
477
 
477
478
  注意:`CrudPage` 会占满父级可用高度;表格无论是否有数据都会撑满剩余空间,仅数据区滚动,表头和分页栏固定。
@@ -486,6 +487,7 @@ const p = usePlatform()
486
487
  form={{ variant: 'modal', modalSize: 'lg' }}
487
488
  pagination={{ showSizeChanger: true, showJumper: true, pageSizeOptions: [10, 20, 50] }}
488
489
  density='compact'
490
+ detailColumns={['name']}
489
491
  sortableColumns={[{ key: 'createdAt', label: '创建时间' }]}
490
492
  {nav}
491
493
  />
@@ -497,6 +499,8 @@ const p = usePlatform()
497
499
  form={{ variant: 'drawer', drawerWidth: '40rem' }}
498
500
  ```
499
501
 
502
+ 抽屉默认可拖动左侧边缘调整宽度,并按 `crud.resource.name` 记忆;传 `drawerResizable: false` 可关闭。
503
+
500
504
  ---
501
505
 
502
506
  ## Design Token 系统