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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # e10-ebuilder-prototype
2
2
 
3
3
  根据 E10 **应用 ID 或后台应用地址生成应用原型,包含已发布菜单中的页面、建模列表和流程页面**。
4
- 当前版本 0.5.0。保持我们的 TypeScript CLI 为主:CLI 管登录、菜单发现、配置采集、截图、任务队列、校验、恢复与打包;宿主 AI 根据完整截图或建模配置生成独立 HTML。CLI 不调用 LLM,不要求额外模型 API Key,也不调用 ui-code-agent 生成。
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.0 --registry=https://registry.npmjs.org/
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): any;
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
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "0.5.0";
1
+ export declare const VERSION = "0.5.2";
2
2
  export declare class CaptureError extends Error {
3
3
  code: string;
4
4
  details?: unknown | undefined;
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.0';
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 independent = await client.request('GET', `/api/ebuilder/form/app/isIndependentDeploy?appId=${state.appId}`);
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 = [...new Set(forms.flatMap((f) => f.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: `/list/getBaseInfo/${item.viewId}?${query}`,
503
- displayColumns: `/list/getListFields/${item.viewId}?${query}&isVirtualForm=false&clientType=PC`,
504
- sorting: `/viewlist/getViewOrder?${query}&viewType=tableView&viewId=${item.viewId}&isVirtualForm=false`,
505
- search: `/listFilter/getSearchFilters/${item.viewId}?${query}&isVirtualForm=false&viewType=tableView`,
506
- statistics: `/listStatistics/getStatisticsList/${item.viewId}?${query}&isVirtualForm=false`,
507
- buttons: `/list/getListButtons/${item.viewId}?passid=${objId}`,
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 = `/plugin/conditionEditor/getById?apid=${state.appId}&id=${decimal(context.conditionId, '条件')}&type=search&objId=${objId}`;
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, () => get(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
- 这些配置统一通过前台登录态接口 `/api/ebuilder/form/...` 读取。
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` | `data[]` |
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.0
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "e10-ebuilder-prototype",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Generate offline E10 application prototypes from published page, modeling-list and workflow menus",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",