create-zleap-app 0.10.1 → 0.10.3

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 (27) hide show
  1. package/index.js +24 -2
  2. package/lib/scaffold.js +10 -0
  3. package/package.json +4 -4
  4. package/skills/zleap-cloud-app/references/app-runtime.md +81 -74
  5. package/skills/zleap-cloud-app/references/cloud-functions.md +230 -43
  6. package/skills/zleap-cloud-app/references/console-database.md +8 -0
  7. package/skills/zleap-cloud-app/references/console-members.md +44 -30
  8. package/skills/zleap-cloud-app/references/limits.md +57 -56
  9. package/skills/zleap-cloud-app/references/manifest.md +36 -35
  10. package/skills/zleap-cloud-app/references/permissions.md +40 -47
  11. package/skills/zleap-cloud-app/references/sdk-database-files.md +115 -48
  12. package/skills/zleap-cloud-app/references/status-codes.md +125 -93
  13. package/skills/zleap-cloud-app/references/triggers.md +24 -24
  14. package/skills/zleap-cloud-app-release/SKILL.md +4 -1
  15. package/skills/zleap-cloud-app-release/references/app-center-install.md +64 -52
  16. package/skills/zleap-cloud-app-release/references/app-center-publish.md +118 -49
  17. package/skills/zleap-cloud-app-release/references/console-functions.md +12 -4
  18. package/skills/zleap-cloud-app-release/references/console-legacy-release.md +5 -2
  19. package/skills/zleap-cloud-app-release/references/console-members.md +44 -30
  20. package/skills/zleap-cloud-app-release/references/console-release.md +39 -28
  21. package/skills/zleap-cloud-app-release/references/console-workspace.md +32 -16
  22. package/skills/zleap-cloud-app-release/references/dev-to-production.md +330 -113
  23. package/skills/zleap-cloud-app-release/references/permissions.md +40 -47
  24. package/skills/zleap-cloud-app-release/references/status-codes.md +125 -93
  25. package/templates/react-ts/AGENTS.md +1 -1
  26. package/templates/react-ts/package.json +3 -0
  27. package/templates/react-ts/scripts/zleap.mjs +25 -3
package/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  //
4
4
  // npm create zleap-app@latest my-app
5
5
  // npx create-zleap-app my-app --appid zl3f9a2c7e1b0d4a68 --name "我的应用" -y
6
+ // npx create-zleap-app@latest skills [目录] 给已有项目补装 AI 编程工具 skills
6
7
  //
7
8
  // appid 由平台生成:先在 zleapOS 云开发控制台「创建应用」,复制 appid。
8
9
  import { spawnSync } from 'node:child_process';
@@ -16,6 +17,7 @@ import {
16
17
  detectPackageManager,
17
18
  installSkills,
18
19
  isEmptyDir,
20
+ nodeVersionProblem,
19
21
  parseArgs,
20
22
  scaffold,
21
23
  toPackageName,
@@ -40,6 +42,7 @@ ${bold('create-zleap-app')} ${dim(`v${pkg.version}`)} — 创建 zleapOS 云开
40
42
  用法
41
43
  npm create zleap-app@latest [目录] [-- 选项]
42
44
  npx create-zleap-app [目录] [选项]
45
+ npx create-zleap-app@latest skills [目录] 给已有项目补装 AI 编程工具 skills(默认当前目录)
43
46
 
44
47
  选项
45
48
  --appid <appid> 应用 appid:在 zleapOS 云开发控制台「创建应用」后复制(形如 zl3f9a2c7e1b0d4a68)
@@ -69,10 +72,29 @@ function run(command, args, cwd) {
69
72
  return result.status === 0;
70
73
  }
71
74
 
75
+ /** 已有项目补装 skills:复制随包分发的 skills 到 .agents/skills 与 .claude/skills,不改动其他文件。 */
76
+ function addSkills(dir) {
77
+ const targetDir = resolve(dir ?? '.');
78
+ if (!existsSync(targetDir)) throw new Error(`目录 ${dir} 不存在`);
79
+ const names = installSkills({ skillsDir: join(here, 'skills'), targetDir });
80
+ if (!names.length) throw new Error('这个版本的 create-zleap-app 没有随附 skills');
81
+ console.log(green(`✔ 已在 ${targetDir} 安装 skills:${names.join(', ')}(.agents/skills、.claude/skills)`));
82
+ console.log(dim(' 在 Codex / Claude Code / Pi / OpenCode / Cursor 里打开这个目录即可使用;升级 skills 时重新运行本命令。'));
83
+ }
84
+
72
85
  async function main() {
73
- const options = parseArgs(process.argv.slice(2));
86
+ const argv = process.argv.slice(2);
87
+ if (argv[0] === 'skills') {
88
+ const rest = parseArgs(argv.slice(1));
89
+ if (rest.help) return console.log(HELP);
90
+ return addSkills(rest.dir);
91
+ }
92
+ const options = parseArgs(argv);
74
93
  if (options.help) return console.log(HELP);
75
94
  if (options.version) return console.log(pkg.version);
95
+ // 生成器本身在旧 Node 上也能跑,但生成的项目构建不了:先说清楚,不中断(用户可能稍后再升级)。
96
+ const nodeProblem = nodeVersionProblem();
97
+ if (nodeProblem) console.log(red(`! ${nodeProblem}`));
76
98
  if (!TEMPLATES.includes(options.template)) throw new Error(`未知模板:${options.template}(可选 ${TEMPLATES.join(' / ')})`);
77
99
 
78
100
  const interactive = process.stdin.isTTY && !options.yes;
@@ -147,7 +169,7 @@ ${bold('完成!接下来:')}
147
169
 
148
170
  ${bold('让 AI 编程工具帮你开发')}
149
171
  项目里已带 AGENTS.md 和 skills,直接在 Codex / Claude Code / Pi / OpenCode / Cursor 里打开本目录即可。
150
- 想全局安装 skills:${cyan(`npx skills add ${kit.skillsRepo}`)}
172
+ 已有项目补装 skills:${cyan('npx create-zleap-app@latest skills')}
151
173
  `);
152
174
  } finally {
153
175
  rl?.close();
package/lib/scaffold.js CHANGED
@@ -71,6 +71,16 @@ export function toAppKeySegment(dir) {
71
71
  return segment || 'app';
72
72
  }
73
73
 
74
+ /** 生成的项目用 Vite 7,要求的 Node.js 版本;与模板 package.json 的 engines 保持一致。 */
75
+ export const SUPPORTED_NODE = '^20.19.0 || >=22.12.0';
76
+
77
+ /** 当前 Node.js 能否构建生成的项目:能则返回 null,否则返回给人看的说明。 */
78
+ export function nodeVersionProblem(version = process.versions.node) {
79
+ const [major, minor] = String(version).replace(/^v/, '').split('.').map(Number);
80
+ if (major > 22 || (major === 22 && minor >= 12) || (major === 20 && minor >= 19)) return null;
81
+ return `当前 Node.js ${version} 无法构建生成的项目(Vite 7 需要 Node.js 20.19+ 或 22.12+)。请先升级 Node.js,例如 nvm install 22,或到 https://nodejs.org 下载 LTS 版本。`;
82
+ }
83
+
74
84
  export function parseArgs(argv) {
75
85
  const out = { install: true, skills: true, git: true, yes: false, help: false, version: false, template: 'react-ts' };
76
86
  const positional = [];
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "create-zleap-app",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "一条命令创建 zleapOS 云开发应用(React + TypeScript + Vite),并为 Codex / Claude Code / Pi / OpenCode / Cursor 等 AI 编程工具配好 skills",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/zleap-ai/zleap-app-kit.git",
8
+ "url": "git+https://github.com/RuoFeng2015/zleap-app-kit.git",
9
9
  "directory": "packages/create-zleap-app"
10
10
  },
11
11
  "type": "module",
@@ -19,14 +19,14 @@
19
19
  "skills"
20
20
  ],
21
21
  "engines": {
22
- "node": ">=18.17"
22
+ "node": "^20.19.0 || >=22.12.0"
23
23
  },
24
24
  "zleapKit": {
25
25
  "cliPackage": "@zleap-ai/app-cli",
26
26
  "cliVersion": "0.10.0",
27
27
  "sdkPackage": "@zleap-ai/app-sdk",
28
28
  "sdkVersion": "0.10.0",
29
- "skillsRepo": "zleap-ai/zleap-app-kit"
29
+ "skillsRepo": "RuoFeng2015/zleap-app-kit"
30
30
  },
31
31
  "keywords": [
32
32
  "zleap",
@@ -33,14 +33,17 @@ import { zl, AppError } from '@zleap-ai/app-sdk';
33
33
 
34
34
  成员在控制台 版本管理(文档站 /console/release#版本管理) 里把某个开发版本「发布为线上版本」后:
35
35
 
36
- | 谁 | 怎么换到新版本 |
37
- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
38
- | 普通使用者(不是应用成员) | **下次打开应用时自动跟随**:宿主先把安装切到线上版本,显示「**正在更新到最新版本…**」,再加载应用。回退同样会跟随 |
39
- | 已经开着应用的人 | **不会被打断**,继续用当前版本;应用可以用下面的更新管理器提示用户,调用 `applyUpdate()` 立即切换。新版本发布 **24 小时**后,宿主会把还开着旧版本的窗口自动切到线上版本并重新加载(显示「这个版本已超过 24 小时的使用期限,正在更新到最新版本…」) |
40
- | 应用成员(管理员 / 开发者) | **从不自动切换**,在控制台版本管理里用「我也用这个」自己选 |
36
+ | 谁 | 怎么换到新版本 |
37
+ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
38
+ | 普通使用者(不是应用成员) | **下次打开应用时自动跟随**:宿主先把安装切到线上版本,显示「**正在更新到最新版本…**」,再加载应用。回退同样会跟随 |
39
+ | 已经开着应用的**非成员** | **不会立刻被打断**,继续用当前版本;应用可以用下面的更新管理器提示用户,调用 `applyUpdate()` 立即切换。新版本发布 **24 小时**后,宿主会把还开着旧版本的窗口强制切到线上版本并重新加载(显示「这个版本已超过 24 小时的使用期限,正在更新到最新版本…」) |
40
+ | 应用成员(管理员 / 开发者) | **从不自动切换**,在控制台版本管理里用「我也用这个」自己选 |
41
41
 
42
42
  所以不写任何代码,使用者也会在下次打开时用上新版本。更新管理器只是让「正开着的窗口」也能及时换到新版本。
43
43
 
44
+ 非成员一直开着的旧窗口,**从新线上版本发布起最多再用 24 小时**;到期宿主会强制切换并重新加载。这个截止时间就是
45
+ `UpdateCheckResult.deadline`(毫秒时间戳);应用成员、没有更新时为 `null`。
46
+
44
47
  ### `zl.app.getUpdateManager()`
45
48
 
46
49
  返回全局唯一的更新管理器(多次调用返回同一个):
@@ -62,12 +65,12 @@ interface UpdateCheckResult {
62
65
  }
63
66
  ```
64
67
 
65
- | 方法 | 说明 |
66
- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
67
- | `onCheckForUpdate` | 第一次获取更新管理器时,宿主检查一次,结果交给这个回调。回调注册得晚也会收到上一次的结果 |
68
- | `onUpdateReady` | 有新的线上版本可用时回调:检查时就发现有更新;或者应用**正在使用中**有成员发布了新版本(宿主大约每 20 秒检查一次,同一个新版本只通知一次) |
69
- | `onUpdateFailed` | 连不上宿主(如 `OPEN_IN_ZLEAP_OS`、`HOST_UNAVAILABLE`),或 `applyUpdate()` 切换失败时回调 |
70
- | `applyUpdate()` | 把当前安装切到线上版本,并**重新加载应用**。没有新版本时什么也不做。失败时既回调 `onUpdateFailed`,也以 `AppError` 拒绝 |
68
+ | 方法 | 说明 |
69
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
70
+ | `onCheckForUpdate` | 第一次获取更新管理器时,宿主检查一次,结果交给这个回调。回调注册得晚也会收到上一次的结果 |
71
+ | `onUpdateReady` | 有新的线上版本可用时回调:检查时就发现有更新;或者应用**正在使用中**有成员发布了新版本(宿主大约每 20 秒检查一次,同一个新版本只通知一次) |
72
+ | `onUpdateFailed` | 连不上宿主(如 `OPEN_IN_ZLEAP_OS`、`HOST_UNAVAILABLE`),或 `applyUpdate()` 切换失败时回调 |
73
+ | `applyUpdate()` | 把当前安装切到线上版本,并**重新加载应用**。没有新版本时什么也不做。失败时既回调 `onUpdateFailed`,也以 `AppError` 拒绝 |
71
74
 
72
75
  ```ts
73
76
  const updater = zl.app.getUpdateManager();
@@ -128,11 +131,11 @@ const { hasUpdate, latestVersion } = await zl.app.checkUpdate();
128
131
  }
129
132
  ```
130
133
 
131
- | 声明 | 可以申请 |
132
- | -------------- | ----------------------------- |
133
- | `user.profile` | `profile`(头像和昵称) |
134
- | `user.phone` | `phone`(手机号) |
135
- | `user.email` | `email`(邮箱) |
134
+ | 声明 | 可以申请 |
135
+ | -------------- | ----------------------- |
136
+ | `user.profile` | `profile`(头像和昵称) |
137
+ | `user.phone` | `phone`(手机号) |
138
+ | `user.email` | `email`(邮箱) |
136
139
 
137
140
  声明不等于拿到:安装时用户看到的是「使用时可能申请你的手机号(由你决定是否同意)」,真正给不给由用户在授权框里决定。
138
141
  只声明真正要用的项。
@@ -173,8 +176,8 @@ interface AuthResult {
173
176
  openid: string;
174
177
  scopes: ('profile' | 'phone' | 'email')[]; // 本次实际拿到的范围
175
178
  profile?: { nickname: string; avatarUrl: string | null };
176
- phone?: string;
177
- email?: string;
179
+ phone?: string | null;
180
+ email?: string | null;
178
181
  }
179
182
  ```
180
183
 
@@ -194,17 +197,17 @@ interface AuthResult {
194
197
  下面逐行列出要确认的项目和**用户自己的值**,每行前面有勾选框(默认全选);有 `reason` 时显示「用途:…」。
195
198
  没绑定的手机号 / 邮箱置灰显示「未绑定手机号」/「未绑定邮箱」。底部按钮「**拒绝**」/「**允许**」,全部取消勾选时「允许」不可点。
196
199
 
197
- | 情况 | 行为 |
198
- | -------------------------------------- | ------------------------------------------------------------------------ |
199
- | 申请的范围都同意过 | **不弹框**,直接返回 |
200
- | 部分同意过,又申请了新范围 | 弹框,**只列出新申请的那几项** |
201
- | 用户点「拒绝」 | 以 `AppError`(`errCode` 为 `AUTH_DENIED`)拒绝 |
202
- | 用户取消勾选了一部分再点「允许」 | 成功,`scopes` 里只有勾选的项;取消的项按拒绝记录 |
203
- | 拒绝过的项 24 小时内再申请 | **不弹框**,直接 `AUTH_DENIED`(防止反复打扰)。用户在「已授权信息」里点「允许再次询问」可以提前解除 |
204
- | 不是在用户点击后调用 | `AUTH_GESTURE_REQUIRED` |
205
- | 申请了 Manifest 没声明的项 | `AUTH_SCOPE_UNDECLARED` |
206
- | 授权框还开着时又调用 `authorize` | 以 `AUTH_IN_PROGRESS` 拒绝 |
207
- | `scopes` 为空或含其他值、`reason` 超过 60 字 | 以 `SCHEMA_INVALID` 拒绝 |
200
+ | 情况 | 行为 |
201
+ | -------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
202
+ | 申请的范围都同意过 | **不弹框**,直接返回 |
203
+ | 部分同意过,又申请了新范围 | 弹框,**只列出新申请的那几项** |
204
+ | 用户点「拒绝」 | 以 `AppError`(`errCode` 为 `AUTH_DENIED`)拒绝 |
205
+ | 用户取消勾选了一部分再点「允许」 | 成功,`scopes` 里只有勾选的项;取消的项按拒绝记录 |
206
+ | 拒绝过的项 24 小时内再申请 | **不弹框**,直接 `AUTH_DENIED`(防止反复打扰)。用户在「已授权信息」里点「允许再次询问」可以提前解除 |
207
+ | 不是在用户点击后调用 | `AUTH_GESTURE_REQUIRED` |
208
+ | 申请了 Manifest 没声明的项 | `AUTH_SCOPE_UNDECLARED` |
209
+ | 授权框还开着时又调用 `authorize` | 以 `AUTH_IN_PROGRESS` 拒绝 |
210
+ | `scopes` 为空或含其他值、`reason` 超过 60 字 | 以 `SCHEMA_INVALID` 拒绝 |
208
211
 
209
212
  用户考虑的时间不计入超时,`authorize` 会一直等到用户点按钮。
210
213
 
@@ -223,12 +226,12 @@ if (!scopes.includes('profile')) {
223
226
 
224
227
  ### openid
225
228
 
226
- | 性质 | 说明 |
227
- | -------- | -------------------------------------------------------------------- |
228
- | 格式 | `o_` + 22 个字符 |
229
- | 稳定 | 同一用户在同一应用里不变,**开发版本和线上版本一致** |
230
- | 隔离 | 同一用户在不同应用里的 `openid` 不同,应用之间无法据此关联用户 |
231
- | 不可反推 | 应用拿不到平台用户 ID 和令牌,也不能从 `openid` 还原它们 |
229
+ | 性质 | 说明 |
230
+ | -------- | -------------------------------------------------------------- |
231
+ | 格式 | `o_` + 22 个字符 |
232
+ | 稳定 | 同一用户在同一应用里不变,**开发版本和线上版本一致** |
233
+ | 隔离 | 同一用户在不同应用里的 `openid` 不同,应用之间无法据此关联用户 |
234
+ | 不可反推 | 应用拿不到平台用户 ID 和令牌,也不能从 `openid` 还原它们 |
232
235
 
233
236
  > **和集合里的 `_openid` 不是同一个值**(说明)
234
237
  >
@@ -242,63 +245,67 @@ if (!scopes.includes('profile')) {
242
245
  下一次 `authorize` 会重新弹出授权框;`getIdentity()` 仍返回 `openid`,但不再带之前的信息。
243
246
  同意记录按「用户 × 应用」保存,切换版本不需要重新同意。
244
247
 
245
- 每次同意、拒绝、撤销,以及云函数读取授权信息,平台都会记审计日志(只记范围,不记手机号等具体值)。
248
+ 每次同意、拒绝、撤销,平台都会记审计日志(只记范围,不记手机号等具体值)。
246
249
  应用成员可以在云开发控制台「[用户授权](console-members.md#用户授权)」里看到同意人数和撤销人数,看不到具体用户和值。
247
250
 
248
251
  见 安装与卸载 · 已授权信息(文档站 /app-center/install#已授权信息)。
249
252
 
250
- ### 在云函数里读取
253
+ ### 在云函数里读取(暂不可用)
251
254
 
252
- 云函数里用 `zl.cloud.auth.getUserInfo()` 拿到**调用者**在本应用里的 `openid`,以及他在前端 `authorize` 里同意过的信息,
253
- 返回结构与前端相同(`profile` 里的头像字段是 `avatar_url`)。只要 `openid` 时用 `zl.cloud.auth.getOpenId()`:
255
+ 当前云函数服务端 SDK(`@zleap/app-cloud-server`)只有 `init`、`getContext`、`getLogger`、`os.database`,
256
+ **没有 `zl.cloud.auth` 命名空间**:`getOpenId()` / `getUserInfo()` 都不可用,函数也拿不到用户在前端授权框里同意的信息。
254
257
 
255
- ```js
256
- const { zl } = require('@zleap/app-cloud-server');
258
+ 需要 `openid` 时用下面的替代方式:
257
259
 
258
- exports.main = async () => {
259
- const me = await zl.cloud.auth.getUserInfo();
260
- // me.openid 与前端 getIdentity() 拿到的一致;me.scopes 是用户已同意的范围
261
- return { openid: me.openid, phone: me.phone ?? null };
262
- };
263
- ```
260
+ 1. **页面取,作为输入传**:页面用 `zl.auth.getIdentity()`(或 `authorize()`)拿到 `openid`,放进能力的 `input`:
261
+
262
+ ```js
263
+ const me = await zl.auth.getIdentity();
264
+ await zl.cloud.os.capabilities.invoke({
265
+ id: 'com.example.my-app.record',
266
+ input: { openid: me.openid },
267
+ });
268
+ ```
269
+
270
+ 2. **用文档自带的 `_openid`**:数据库文档都有写入者 `_openid`。示例 `examples/moments` 的 `stats` 就是从私有集合 `me/profile` 里读出 `_openid` 再统计的。
271
+
272
+ 注意:
264
273
 
265
- - 只读,不会弹框——函数在后台运行,没有界面。用户还没同意某项时,结果里**没有**这一项(`scopes` 里也没有)。
266
- - 身份由平台按本次调用决定,函数不能指定查别人。
267
- - 撤销授权后,函数同样拿不到之前的信息。
268
- - 函数上下文里的 `getContext().userId` 是平台内部用户 ID,**已废弃**,新应用用 `openid`。
269
- - 函数拿到的就是平台核实过的信息,不需要(也没有)前端传凭据再校验的步骤:前端想让函数用手机号时,让函数自己 `getUserInfo()`,不要信任前端传来的手机号。
274
+ - 由页面传入的 `openid`、手机号、邮箱都只是**业务参数**,函数无法再向平台核验;不要把前端传来的手机号当成平台认证过的身份。
275
+ - 撤销授权后页面 `getIdentity()` 仍返回 `openid`,但不再带之前同意的信息;云函数没有可读取授权信息的接口。
276
+ - `getContext().userId` 是平台内部用户 ID,**已废弃**,新应用不要用它作为业务用户键。
270
277
 
271
278
  ## 错误码
272
279
 
273
280
  `zl.app` / `zl.auth` 失败时抛出 `AppError`,用 `errCode` 分流(`message` 只用于调试):
274
281
 
275
- | errCode | 含义 | 处理 |
276
- | ----------------------- | ------------------------------------------------------------ | ---------------------------------------- |
277
- | `AUTH_DENIED` | 用户在授权框里点了「拒绝」 | 按未授权处理,等用户主动操作时再申请 |
278
- | `AUTH_IN_PROGRESS` | 授权框已经打开,又发起了一次 `authorize` | 等上一次结果,不要并发调用 |
279
- | `AUTH_GESTURE_REQUIRED` | 需要弹授权框,但调用不是紧跟在用户点击 / 按键之后 | 放到按钮的点击事件里调用 |
280
- | `AUTH_SCOPE_UNDECLARED` | 申请了 Manifest `host_api.requires` 没声明的 `user.*` 项 | 补上声明,重新上传 |
281
- | `SCHEMA_INVALID` | `scopes` 为空或含 `profile` / `phone` / `email` 以外的值,或 `reason` 超过 60 字 | 改代码 |
282
- | `OPEN_IN_ZLEAP_OS` | 不在 zleapOS 窗口里(例如浏览器直接打开、`npm run dev`) | 部署后从启动台打开 |
283
- | `HOST_UNAVAILABLE` | 宿主 15 秒内没有响应(宿主版本过旧) | 升级 zleapOS 后重试 |
284
- | `RUNTIME_UNAVAILABLE` | 通道已关闭、窗口已不是当前实例,或平台接口暂不可用 | 稍后重试;仍失败时重新打开应用 |
285
- | `CALL_TIMEOUT` | 调用超时(一般 30 秒,`applyUpdate` 60 秒) | 可以重试 |
286
- | `UPDATE_FAILED` | `applyUpdate()` 切换失败(宿主报告的失败也可能是 `RUNTIME_UNAVAILABLE`) | 提示用户重新打开应用,或到应用中心手动更新 |
287
- | `UNSUPPORTED_OPERATION` | 宿主不支持这个方法 | 升级 zleapOS |
282
+ | errCode | 含义 | 处理 |
283
+ | ----------------------- | -------------------------------------------------------------------------------- | ------------------------------------------ |
284
+ | `AUTH_DENIED` | 用户在授权框里点了「拒绝」 | 按未授权处理,等用户主动操作时再申请 |
285
+ | `AUTH_IN_PROGRESS` | 授权框已经打开,又发起了一次 `authorize` | 等上一次结果,不要并发调用 |
286
+ | `AUTH_GESTURE_REQUIRED` | 需要弹授权框,但调用不是紧跟在用户点击 / 按键之后 | 放到按钮的点击事件里调用 |
287
+ | `AUTH_SCOPE_UNDECLARED` | 申请了 Manifest `host_api.requires` 没声明的 `user.*` 项 | 补上声明,重新上传 |
288
+ | `SCHEMA_INVALID` | `scopes` 为空或含 `profile` / `phone` / `email` 以外的值,或 `reason` 超过 60 字 | 改代码 |
289
+ | `OPEN_IN_ZLEAP_OS` | 不在 zleapOS 窗口里(例如浏览器直接打开、`npm run dev`) | 部署后从启动台打开 |
290
+ | `HOST_UNAVAILABLE` | 宿主 15 秒内没有响应(宿主版本过旧) | 升级 zleapOS 后重试 |
291
+ | `RUNTIME_UNAVAILABLE` | 通道已关闭、窗口已不是当前实例,或平台接口暂不可用 | 稍后重试;仍失败时重新打开应用 |
292
+ | `CALL_TIMEOUT` | 调用超时(一般 30 秒,`applyUpdate` 60 秒) | 可以重试 |
293
+ | `UPDATE_FAILED` | `applyUpdate()` 切换失败(宿主报告的失败也可能是 `RUNTIME_UNAVAILABLE`) | 提示用户重新打开应用,或到应用中心手动更新 |
294
+ | `UNSUPPORTED_OPERATION` | 宿主不支持这个方法 | 升级 zleapOS |
288
295
 
289
296
  ## 平台接口(供参考)
290
297
 
291
298
  应用**不直接调用**这些接口,下表用于理解行为和排查。
292
299
 
293
- | 接口 | 谁调用 | 说明 |
294
- | ------------------------------------------------------------ | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
295
- | `GET /api/v1/apps/installations/{installation_id}/update-check` | 宿主(安装的所有者) | 返回 `current_version`、`latest_version`、`has_update`、`member`、`notes`、`published_at` 等,宿主的自动跟随和更新管理器都用它 |
296
- | `GET /api/v1/apps/installations/{id}/auth/identity` | 宿主,用用户会话;**应用不能直接调用** | `openid` 与已同意范围的信息 |
297
- | `POST /api/v1/apps/installations/{id}/auth/preview` | 同上 | 请求体 `{scopes}`,返回授权框要展示的内容 |
298
- | `POST /api/v1/apps/installations/{id}/auth/authorize` | 同上 | 请求体 `{scopes}`,用户点「允许」后调用 |
299
- | `POST /api/v1/apps/installations/{id}/auth/deny` | 同上 | 请求体 `{scopes}`,用户拒绝或取消勾选的项;24 小时内同一应用再申请不弹框 |
300
- | `DELETE /api/v1/apps/installations/{id}/auth/consent` | 同上 | 撤销对该应用的授权(「撤销授权」),同时清除拒绝记录 |
301
- | `GET /api/v1/app-cloud/apps/{app_id}/auth-stats` | 控制台(应用成员) | 同意人数、各项人数、撤销人数 |
300
+ | 接口 | 谁调用 | 说明 |
301
+ | --------------------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
302
+ | `GET /api/v1/apps/installations/{installation_id}/update-check` | 宿主(安装的所有者) | 返回 `current_version`、`latest_version`、`has_update`、`member`、`notes`、`published_at` 等,宿主的自动跟随和更新管理器都用它 |
303
+ | `GET /api/v1/apps/installations/{id}/auth/identity` | 宿主,用用户会话;**应用不能直接调用** | `openid` 与已同意范围的信息 |
304
+ | `POST /api/v1/apps/installations/{id}/auth/preview` | 同上 | 请求体 `{scopes}`,返回授权框要展示的内容 |
305
+ | `POST /api/v1/apps/installations/{id}/auth/authorize` | 同上 | 请求体 `{scopes}`,用户点「允许」后调用 |
306
+ | `POST /api/v1/apps/installations/{id}/auth/deny` | 同上 | 请求体 `{scopes}`,用户拒绝或取消勾选的项;24 小时内同一应用再申请不弹框 |
307
+ | `DELETE /api/v1/apps/installations/{id}/auth/consent` | 同上 | 撤销对该应用的授权(「撤销授权」),同时清除拒绝记录 |
308
+ | `GET /api/v1/app-cloud/apps/{app_id}/auth-stats` | 控制台(应用成员) | 同意人数、各项人数、撤销人数 |
302
309
 
303
310
  发布与版本列表接口见 版本管理 · 接口(文档站 /console/release#接口)。
304
311