e10-ebuilder-prototype 0.5.0 → 0.5.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 +12 -2
- package/dist/api.d.ts +3 -1
- package/dist/api.js +2 -2
- package/dist/common.d.ts +1 -1
- package/dist/common.js +1 -1
- package/dist/forms.js +53 -12
- package/dist/templates/form-guide.md +11 -9
- package/docs/PROTOCOL.md +20 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# e10-ebuilder-prototype
|
|
2
2
|
|
|
3
3
|
根据 E10 **应用 ID 或后台应用地址生成应用原型,包含已发布菜单中的页面、建模列表和流程页面**。
|
|
4
|
-
当前版本 0.5.
|
|
4
|
+
当前版本 0.5.2。保持我们的 TypeScript CLI 为主:CLI 管登录、菜单发现、配置采集、截图、任务队列、校验、恢复与打包;宿主 AI 根据完整截图或建模配置生成独立 HTML。CLI 不调用 LLM,不要求额外模型 API Key,也不调用 ui-code-agent 生成。
|
|
5
5
|
|
|
6
6
|
## 范围与交付
|
|
7
7
|
|
|
@@ -31,7 +31,7 @@ ZIP 名为 `应用页面-<appId>.zip`,位于任务目录 artifacts/;本地
|
|
|
31
31
|
需要 Node >=22.5、npm 和已安装 Google Chrome,playwright-core 固定 1.62.1,不下载浏览器。
|
|
32
32
|
|
|
33
33
|
```sh
|
|
34
|
-
npm install -g e10-ebuilder-prototype@0.5.
|
|
34
|
+
npm install -g e10-ebuilder-prototype@0.5.2 --registry=https://registry.npmjs.org/
|
|
35
35
|
e10-ebuilder-prototype auth set --base-url https://your-e10.example.com --eteamsid-stdin
|
|
36
36
|
e10-ebuilder-prototype run --app-id 1234567890123456789 --dir ./work/app-demo --json
|
|
37
37
|
# 或使用后台应用地址:
|
|
@@ -56,6 +56,16 @@ e10-ebuilder-prototype pack --dir ./work/app-demo --json
|
|
|
56
56
|
|
|
57
57
|
生成文件为真实 DOM、内联 CSS/JS/SVG,无外部资源或后台调用。页面忠实还原视觉结构,空数据用一致 Mock 内容填充。表单按当前模式/字段/选项/按钮及布局依据生成,固定种子约 20 条主记录、明细每条 2–5 行;所有统计由数据推导。完整规范随 CLI 打包,不能用“能打开表格”代替验收。
|
|
58
58
|
|
|
59
|
+
## 独立部署检测兼容(0.5.1)
|
|
60
|
+
|
|
61
|
+
isIndependentDeploy 请求失败时,默认按非独立部署继续采集,并记录兼容提示。需要部署路由的接口使用 `/api/ebuilder/form/`;检测成功返回 true 时使用 `/api/ebuilder{appId}/form/`。后续配置接口的真实错误照常报告,采集未成功不会标为完成。
|
|
62
|
+
|
|
63
|
+
## 传统列表配置(0.5.2)
|
|
64
|
+
|
|
65
|
+
基础设置、显示列、搜索、统计和固定条件统一使用 `/api/bs/ebuilder/form/` 接口,URL 在原参数基础上增加小写 `appid`。这些接口固定走后台路径,需要账号具备应用后台访问权限,不受独立部署检测结果影响。列表初始化、排序、按钮等接口沿用各自路径。
|
|
66
|
+
|
|
67
|
+
固定条件接口成功但未返回 `data` 时按未配置条件处理;权限、HTTP 和业务错误仍明确报错。已有失败任务可在升级后执行 `retry --dir <原任务目录>`。
|
|
68
|
+
|
|
59
69
|
## 参考布局与临时记录
|
|
60
70
|
|
|
61
71
|
默认只读业务记录:先读取至多一条现有记录,若可用则请求导出 HTML 作为私有布局参考;没有参考时根据全部自定义字段生成布局,报告缺项。导出可能在源站生成参考文件。
|
package/dist/api.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { E10AuthContext } from './vendor/environment-auth.js';
|
|
2
2
|
/** Preserve integer identifiers before JSON.parse can round them. */
|
|
3
3
|
export declare function parseApiJson(text: string): unknown;
|
|
4
|
-
export declare function apiData(payload: unknown, label: string
|
|
4
|
+
export declare function apiData(payload: unknown, label: string, options?: {
|
|
5
|
+
allowMissingData?: boolean;
|
|
6
|
+
}): any;
|
|
5
7
|
export declare class E10Client {
|
|
6
8
|
private auth;
|
|
7
9
|
constructor(auth: E10AuthContext);
|
package/dist/api.js
CHANGED
|
@@ -10,7 +10,7 @@ export function parseApiJson(text) {
|
|
|
10
10
|
});
|
|
11
11
|
return JSON.parse(quoted);
|
|
12
12
|
}
|
|
13
|
-
export function apiData(payload, label) {
|
|
13
|
+
export function apiData(payload, label, options = {}) {
|
|
14
14
|
const p = payload;
|
|
15
15
|
if (p && ['401', '403'].includes(String(p.code)))
|
|
16
16
|
throw new CaptureError('E10_LOGIN_REQUIRED', '登录或权限已失效,请重新执行 auth set');
|
|
@@ -22,7 +22,7 @@ export function apiData(payload, label) {
|
|
|
22
22
|
p.fail === true ||
|
|
23
23
|
(typeof p.message === 'object' && p.message && String(p.message.code ?? '0') !== '0') ||
|
|
24
24
|
(p.data?.datajson && p.data.datajson.status !== true) ||
|
|
25
|
-
!('data' in p))
|
|
25
|
+
(!('data' in p) && (!options.allowMissingData || !['0', '200'].includes(String(p.code)))))
|
|
26
26
|
throw new CaptureError('API_RESPONSE_INVALID', `${label} 返回结构或业务状态异常`);
|
|
27
27
|
return p.data;
|
|
28
28
|
}
|
package/dist/common.d.ts
CHANGED
package/dist/common.js
CHANGED
|
@@ -3,7 +3,7 @@ import { renameWithRetry } from './runtime-support.mjs';
|
|
|
3
3
|
import { createReadStream } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
|
-
export const VERSION = '0.5.
|
|
6
|
+
export const VERSION = '0.5.2';
|
|
7
7
|
export class CaptureError extends Error {
|
|
8
8
|
code;
|
|
9
9
|
details;
|
package/dist/forms.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { atomicJson, CaptureError, errorInfo, fileDigest, mapLimit } from './common.js';
|
|
4
|
-
import { E10Client, parseApiJson } from './api.js';
|
|
4
|
+
import { E10Client, apiData, parseApiJson } from './api.js';
|
|
5
5
|
import { recoverTemporaryRecords, withTemporaryRecord, SeedUnavailable, temporarySummary, } from './temporary-records.js';
|
|
6
6
|
import { navigationKey } from './menus.js';
|
|
7
7
|
import { APPROVAL_PATH, normalizeWorkflows, workflowPreset, workflowType } from './workflows.js';
|
|
@@ -45,6 +45,30 @@ export function configObject(value) {
|
|
|
45
45
|
throw new CaptureError('FORM_CONFIG_INVALID', '表单配置不是对象');
|
|
46
46
|
return result;
|
|
47
47
|
}
|
|
48
|
+
function listConditions(conditionId, raw) {
|
|
49
|
+
let rootData = typeof raw === 'string'
|
|
50
|
+
? raw
|
|
51
|
+
: raw && Object.hasOwn(raw, 'rootdata')
|
|
52
|
+
? raw.rootdata
|
|
53
|
+
: raw?.rootData;
|
|
54
|
+
if (typeof rootData === 'string' && rootData.trim()) {
|
|
55
|
+
try {
|
|
56
|
+
rootData = parseApiJson(rootData);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Preserve unrecognized condition text for the host, as the source collector does.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const configured = rootData != null &&
|
|
63
|
+
rootData !== '' &&
|
|
64
|
+
!(typeof rootData === 'object' && Object.keys(rootData).length === 0);
|
|
65
|
+
return {
|
|
66
|
+
conditionId: conditionId == null ? '' : String(conditionId),
|
|
67
|
+
configured,
|
|
68
|
+
rootData: configured ? rootData : {},
|
|
69
|
+
raw: raw ?? {},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
48
72
|
const systems = new Set([
|
|
49
73
|
'1:name',
|
|
50
74
|
'2:creator',
|
|
@@ -189,7 +213,13 @@ export async function collectForms(store, state, auth, retryFailed = false) {
|
|
|
189
213
|
}
|
|
190
214
|
if (!jobs.length && !(await temporarySummary(store)).pending)
|
|
191
215
|
return;
|
|
192
|
-
const
|
|
216
|
+
const deploymentWarnings = [];
|
|
217
|
+
const independent = await client
|
|
218
|
+
.request('GET', `/api/ebuilder/form/app/isIndependentDeploy?appId=${state.appId}`)
|
|
219
|
+
.catch(() => {
|
|
220
|
+
deploymentWarnings.push('isIndependentDeploy 请求失败,已默认按非独立部署应用采集');
|
|
221
|
+
return false;
|
|
222
|
+
});
|
|
193
223
|
if (typeof independent !== 'boolean')
|
|
194
224
|
throw new CaptureError('DEPLOYMENT_INVALID', '独立部署状态不是布尔值');
|
|
195
225
|
const prefix = '/api/ebuilder' + (independent ? state.appId : '');
|
|
@@ -395,6 +425,7 @@ export async function collectForms(store, state, auth, retryFailed = false) {
|
|
|
395
425
|
status: 'running',
|
|
396
426
|
attempt: (previous?.attempt || 0) + 1,
|
|
397
427
|
updatedAt: new Date().toISOString(),
|
|
428
|
+
...(deploymentWarnings.length ? { warnings: [...deploymentWarnings] } : {}),
|
|
398
429
|
};
|
|
399
430
|
await atomicJson(collectionPath(store, item.id), receipt);
|
|
400
431
|
try {
|
|
@@ -443,7 +474,9 @@ export async function collectForms(store, state, auth, retryFailed = false) {
|
|
|
443
474
|
};
|
|
444
475
|
for (const button of buttons.items)
|
|
445
476
|
button.pageButtonRef = JSON.stringify([item.id, button.buttonRef]);
|
|
446
|
-
const warnings = [
|
|
477
|
+
const warnings = [
|
|
478
|
+
...new Set([...deploymentWarnings, ...forms.flatMap((f) => f.warnings)]),
|
|
479
|
+
];
|
|
447
480
|
if (catalog.skipped.length)
|
|
448
481
|
warnings.push(`流程目录跳过 ${catalog.skipped.length} 个非 EB 或归属/配置无效的流程;详见私有 workflowCatalog.skipped`);
|
|
449
482
|
if (!catalog.templates.length)
|
|
@@ -498,19 +531,26 @@ export async function collectForms(store, state, auth, retryFailed = false) {
|
|
|
498
531
|
throw new CaptureError('FORM_ID_MISMATCH', '菜单与列表表单不匹配');
|
|
499
532
|
objId = resolved;
|
|
500
533
|
const query = `apid=${state.appId}&tpaid=${item.viewId}&objId=${objId}`;
|
|
534
|
+
const bs = '/api/bs/ebuilder/form';
|
|
535
|
+
const bsQuery = `${query}&appid=${state.appId}`;
|
|
501
536
|
const routes = {
|
|
502
|
-
basicSettings:
|
|
503
|
-
displayColumns:
|
|
504
|
-
sorting:
|
|
505
|
-
search:
|
|
506
|
-
statistics:
|
|
507
|
-
buttons:
|
|
537
|
+
basicSettings: `${bs}/list/getBaseInfo/${item.viewId}?${bsQuery}`,
|
|
538
|
+
displayColumns: `${bs}/list/getListFields/${item.viewId}?${bsQuery}&isVirtualForm=false&clientType=PC`,
|
|
539
|
+
sorting: `${prefix}/form/viewlist/getViewOrder?${query}&viewType=tableView&viewId=${item.viewId}&isVirtualForm=false`,
|
|
540
|
+
search: `${bs}/listFilter/getSearchFilters/${item.viewId}?${bsQuery}&isVirtualForm=false&viewType=tableView`,
|
|
541
|
+
statistics: `${bs}/listStatistics/getStatisticsList/${item.viewId}?${bsQuery}&isVirtualForm=false`,
|
|
542
|
+
buttons: `${prefix}/form/list/getListButtons/${item.viewId}?passid=${objId}`,
|
|
508
543
|
};
|
|
509
544
|
if (context.conditionId && String(context.conditionId) !== '0')
|
|
510
|
-
routes.conditions =
|
|
545
|
+
routes.conditions = `${bs}/plugin/conditionEditor/getById?apid=${state.appId}&id=${decimal(context.conditionId, '条件')}&type=search&objId=${objId}&appid=${state.appId}`;
|
|
511
546
|
const responses = await Promise.allSettled(Object.entries(routes).map(async ([key, route]) => [
|
|
512
547
|
key,
|
|
513
|
-
await cached(route, () =>
|
|
548
|
+
await cached(route, async () => {
|
|
549
|
+
if (key !== 'conditions')
|
|
550
|
+
return client.request('GET', route);
|
|
551
|
+
const payload = await client.request('GET', route, undefined, false, true);
|
|
552
|
+
return apiData(payload, route.split('?')[0], { allowMissingData: true });
|
|
553
|
+
}),
|
|
514
554
|
]));
|
|
515
555
|
for (const result of responses)
|
|
516
556
|
if (result.status === 'rejected')
|
|
@@ -518,6 +558,7 @@ export async function collectForms(store, state, auth, retryFailed = false) {
|
|
|
518
558
|
const config = Object.fromEntries(responses.map((r) => r.value));
|
|
519
559
|
listButtons = config.buttons;
|
|
520
560
|
delete config.buttons;
|
|
561
|
+
config.conditions = listConditions(context.conditionId, config.conditions);
|
|
521
562
|
list = { kind: 'list', listId: item.viewId, config };
|
|
522
563
|
}
|
|
523
564
|
if (!objId || !sourceForms.has(objId))
|
|
@@ -526,7 +567,7 @@ export async function collectForms(store, state, auth, retryFailed = false) {
|
|
|
526
567
|
receipt.objId = objId;
|
|
527
568
|
const formId = objId;
|
|
528
569
|
const shared = await collectObject(formId, true);
|
|
529
|
-
const warnings = [...shared.warnings];
|
|
570
|
+
const warnings = [...deploymentWarnings, ...shared.warnings];
|
|
530
571
|
const formButtons = structuredClone(shared.contexts[item.kind === 'layout' ? item.id : ''] || shared.contexts['']);
|
|
531
572
|
if (formButtons.view.status !== 'ready' || formButtons.edit.status !== 'ready')
|
|
532
573
|
warnings.push('查看/编辑模式按钮缺少记录上下文,不能借用列表按钮');
|
|
@@ -282,16 +282,16 @@ POST /api/ebuilder/form/list/listInit
|
|
|
282
282
|
|
|
283
283
|
从 `data` 动态取得 `appId`、`objid`、`name`、`conditionId` 和 `viewType`,随后读取六类配置:
|
|
284
284
|
|
|
285
|
-
|
|
285
|
+
基础设置、显示列、搜索、统计、条件固定通过后台接口 `/api/bs/ebuilder/form/...` 读取,URL 额外携带小写 `appid={appId}`,需要应用后台权限。排序沿用 `/api/ebuilder/form/...` 的部署路由。
|
|
286
286
|
|
|
287
|
-
| 分类 | 接口
|
|
288
|
-
| -------- |
|
|
289
|
-
| 基础设置 | `GET /api/ebuilder/form/list/getBaseInfo/{tableId}` | `data` |
|
|
290
|
-
| 显示列 | `GET /api/ebuilder/form/list/getListFields/{tableId}` | `data.pc`、`data.mobile` 等 |
|
|
291
|
-
| 排序 | `GET /api/ebuilder/form/viewlist/getViewOrder`
|
|
292
|
-
| 搜索 | `GET /api/ebuilder/form/listFilter/getSearchFilters/{tableId}` | `data` |
|
|
293
|
-
| 统计 | `GET /api/ebuilder/form/listStatistics/getStatisticsList/{tableId}` | `data` |
|
|
294
|
-
| 条件 | `GET /api/ebuilder/form/plugin/conditionEditor/getById` | `data.rootdata` |
|
|
287
|
+
| 分类 | 接口 | 有效数据 |
|
|
288
|
+
| -------- | ---------------------------------------------------------------------- | --------------------------- |
|
|
289
|
+
| 基础设置 | `GET /api/bs/ebuilder/form/list/getBaseInfo/{tableId}` | `data` |
|
|
290
|
+
| 显示列 | `GET /api/bs/ebuilder/form/list/getListFields/{tableId}` | `data.pc`、`data.mobile` 等 |
|
|
291
|
+
| 排序 | `GET /api/ebuilder/form/viewlist/getViewOrder` | `data[]` |
|
|
292
|
+
| 搜索 | `GET /api/bs/ebuilder/form/listFilter/getSearchFilters/{tableId}` | `data` |
|
|
293
|
+
| 统计 | `GET /api/bs/ebuilder/form/listStatistics/getStatisticsList/{tableId}` | `data` |
|
|
294
|
+
| 条件 | `GET /api/bs/ebuilder/form/plugin/conditionEditor/getById` | `data.rootdata` |
|
|
295
295
|
|
|
296
296
|
配置接口的公共参数为 `apid={appId}`、`tpaid={tableId}`、`objId={objId}`。显示列还传 `clientType=PC`;显示列、排序、搜索、统计传 `isVirtualForm=false`;排序传 `viewType=tableView&viewId={tableId}`;搜索传 `viewType=tableView`;条件传 `id={conditionId}&type=search`。
|
|
297
297
|
|
|
@@ -376,6 +376,8 @@ POST /api/ebuilder/form/list/listInit
|
|
|
376
376
|
- `config.conditions.configured=true`:`rootData` 是解析后的条件树。
|
|
377
377
|
- `config.conditions.configured=false`:未配置固定条件,即使 `conditionId` 本身非空也不要误判。
|
|
378
378
|
|
|
379
|
+
条件接口明确成功但没有 `data`,或 `data=null`,也按未配置条件处理。只有此接口允许成功响应缺少 `data`;权限或业务失败不能当作空条件。
|
|
380
|
+
|
|
379
381
|
条件树保留服务端节点、逻辑关系、比较符和值,不擅自改写为搜索配置。
|
|
380
382
|
|
|
381
383
|
# 验收与交付
|
package/docs/PROTOCOL.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Contract v5 — e10-ebuilder-prototype 0.5.
|
|
1
|
+
# Contract v5 — e10-ebuilder-prototype 0.5.2
|
|
2
2
|
|
|
3
3
|
Product, npm package, CLI and Skill share this name. Node >=22.5, TypeScript/ESM,
|
|
4
4
|
pinned playwright-core and installed Chrome. No LLM calls in CLI: host AI generates
|
|
@@ -63,9 +63,28 @@ Collection uses E10Client with in-memory auth, same-origin requests, redirects d
|
|
|
63
63
|
30s request deadlines and integer-safe JSON parsing. Form API independent deployment
|
|
64
64
|
rewrites only /api/ebuilder{appId}/form routes. Common metadata and file routes retain
|
|
65
65
|
original prefixes. Menu jobs use bounded concurrency and shared per-object metadata.
|
|
66
|
+
If the isIndependentDeploy request fails (transport, HTTP, or API-response failure),
|
|
67
|
+
default to non-independent deployment and record a fixed warning in affected inputs
|
|
68
|
+
and receipts. This fallback applies only to that detection request, including an
|
|
69
|
+
auth-related failure of the probe; subsequent authentication failures still abort
|
|
70
|
+
collection. Successful boolean responses retain their routing; non-boolean data
|
|
71
|
+
still fails validation. No fallback converts failed downstream collection to success.
|
|
66
72
|
|
|
67
73
|
Traditional lists collect listInit, base info, columns, order, search, statistics,
|
|
68
74
|
conditions where configured and list buttons. NList retains the exact component config.
|
|
75
|
+
The five traditional-list configuration APIs (list/getBaseInfo, list/getListFields,
|
|
76
|
+
listFilter/getSearchFilters, listStatistics/getStatisticsList, plugin/conditionEditor/getById)
|
|
77
|
+
always use /api/bs/ebuilder/form and include lowercase appid=appId in addition to their
|
|
78
|
+
existing query parameters. These fixed backend paths do not use independent-deployment
|
|
79
|
+
rewriting or try the non-bs counterparts. They require application backend permissions.
|
|
80
|
+
listInit, order, list buttons and other form APIs retain their existing routing.
|
|
81
|
+
Traditional-list conditions retain conditionId and raw data, parse rootdata/rootData into
|
|
82
|
+
rootData with integer-safe IDs, and expose configured=false for absent or empty conditions.
|
|
83
|
+
Only the condition API may omit data in an explicitly successful response (code 0/200);
|
|
84
|
+
authentication, HTTP, malformed JSON and unsuccessful envelopes remain errors. All other
|
|
85
|
+
configuration responses still require data. Successful missing/null conditions normalize
|
|
86
|
+
to configured=false, rootData={}, raw={}.
|
|
87
|
+
This is the normalized condition structure required by the bundled generation guide.
|
|
69
88
|
Fields preserve IDs, main/detail grouping, config and system/custom classification;
|
|
70
89
|
select/radio/checkbox/cascader options are collected. Cascaders with only first-level
|
|
71
90
|
options carry a warning. Unknown/malformed primary configs fail that menu.
|
package/package.json
CHANGED