@stalfh233/omc-cli 0.4.37 → 0.4.39
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/dist/bench/call-face-eval.js +1 -1
- package/dist/bench/discovery-cost-eval.js +1 -1
- package/dist/bench/driver.js +1 -1
- package/dist/bench/intent-face-eval.js +1 -1
- package/dist/capability-shape.js +2 -153
- package/dist/cli.js +35 -12
- package/dist/contract-consistency.js +5 -1
- package/dist/execution-metadata.js +4 -3
- package/dist/field-families.js +2 -22
- package/dist/json-column.js +2 -67
- package/dist/lanes/businessrule-contract.js +4 -3
- package/dist/lanes/field.js +3 -1
- package/dist/lanes/list-view-shapes.js +13 -98
- package/dist/lanes/model-contract.js +2 -2
- package/dist/lanes/rule-graph.js +2 -2
- package/dist/lanes/runtime-data-contract.js +6 -83
- package/dist/lanes/runtime-data.js +1 -1
- package/dist/recipes/runtime-data.js +1 -1
- package/dist/tool-types.js +2 -7
- package/dist/zip.js +2 -155
- package/package.json +3 -2
- package/dist/bench/fake-http-server.js +0 -65
- package/dist/fake/app.js +0 -45
- package/dist/fake/b2-registration.js +0 -620
- package/dist/fake/businessrule.js +0 -242
- package/dist/fake/datarule.js +0 -72
- package/dist/fake/dictionary.js +0 -108
- package/dist/fake/environment.js +0 -30
- package/dist/fake/field.js +0 -167
- package/dist/fake/form.js +0 -101
- package/dist/fake/index.js +0 -121
- package/dist/fake/list-view.js +0 -289
- package/dist/fake/model.js +0 -136
- package/dist/fake/online-js.js +0 -18
- package/dist/fake/report.js +0 -253
- package/dist/fake/routes.js +0 -47
- package/dist/fake/rule-lifecycle.js +0 -37
- package/dist/fake/runtime-data.js +0 -367
- package/dist/fake/state.js +0 -67
- package/dist/fake/workflow.js +0 -364
|
@@ -1,85 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { assertDescriptionCarries, assertLaneContract, behaviorBinding, CONTRACT_FAMILY, CONTRACT_REFERENCE_VERSION, describeTool, emitterBinding } from "./contract-support.js";
|
|
3
3
|
import { LANES } from "./lane-ids.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* gate, the three-layer queryCondition, and the propertyType injection are the
|
|
7
|
-
* single source for the handler, the arg schemas, and the fake.
|
|
8
|
-
*/
|
|
9
|
-
export const RUNTIME_DATA_WIRE = Object.freeze({
|
|
10
|
-
queryConditionShape: "List<List<List<FilterModel>>>(level 1 AND across groups, level 2 OR across and-groups, level 3 AND across leaves)",
|
|
11
|
-
dslShape: "or>and>叶子",
|
|
12
|
-
queryBodyCanon: "filters:[], queryVersion:1, queryCode=schemaCode, showTotal",
|
|
13
|
-
typeCastErrcode: 10009,
|
|
14
|
-
emptyModelErrcode: 301005,
|
|
15
|
-
absenceErrcode: 304001,
|
|
16
|
-
formTypeNoWorkflow: "2",
|
|
17
|
-
formTypeWorkflow: "1",
|
|
18
|
-
replayTokenEndpoint: "/api/api/runtime/form/getReplayToken",
|
|
19
|
-
replayTokenErrcode: 100005,
|
|
20
|
-
approvalNpeErrcode: 50000,
|
|
21
|
-
workflowStartDuplicateErrcode: 40000,
|
|
22
|
-
submitChain: "submit 自持久化(ticket 18 源码证据):form/submit 持久化 bizObject.data——formType=2 submitBizSheet→saveBizObject(置 COMPLETED,mergeObject 合并);formType=1 workItem→SubmitDataChangeHandlerImpl.updateBizObject;payload {id,schemaCode,sheetCode,ownerId?,data}+actionCode:\"submit\",owner 服务端以库内行回填,data 剥 engine 键(sequenceStatus/sequenceNo)",
|
|
23
|
-
submitInPlace: "无流程且 bizObjectId 有值时先 form/load 预检(缺席 304001 拒绝),再以预检行+formData 合成最小对象直提 runtime/form/submit(submit 自持久化,无预 save/构建 load);submit 返回 objectId 必须仍是目标 id,否则拒绝;仅无 workflowCode/workItemId/workflowInstanceId 的无 id 便捷链先 form/save 建稿再直提(新建记录);带 workItemId 或 workflowInstanceId 而缺 bizObjectId 时 fail-closed 拒绝(submit-biz-object-id-required);workflowCode + 既有无实例 bizObjectId 在 8.6.26 无 REST 就地面(见 workflowStartCreateOnly),持续拒绝并指向 workflowDraftSave 两段式",
|
|
24
|
-
workflowDraftSave: "门户暂存形状(ticket 13):POST runtime/form/save 顶层带 workflowCode(无 workflowInstanceId/workItemId)→ FormRuntimeController.saveWorkflow→startWorkflow(finishStart=FALSE):同 id 建 DRAFT 行 + DRAFT 流程实例 + 发起 workItem,FormSaveVO 返回 {objectId,workflowInstanceId,workItem:{id}};必须先 GET getReplayToken(isStartWf 分支 verifyReplay,否则 100005),且 data 显式 sequenceStatus:\"DRAFT\"(createAndGetBizObject 空缺省按 PROCESSING 建实例);提交该草稿 = form/submit formType=1 + workItemId + workflowInstanceId + bizObject.id(submitWorkflowByInstanceIdAndItemId),同 id 就地 PROCESSING,绝不走 workflowCode 分支",
|
|
25
|
-
workflowStartCreateOnly: "form/submit formType=1 + workflowCode(无 workItemId)→ submitWorkflowByWorkflowCode→startWorkflowInstance(create)→BizObjectFacadeImpl.createAndGetBizObject 纯 INSERT(BizObjectInsertModel 无存在性检查)→ 既有 id 必 40000 Duplicate entry(TID 771f64ee 全栈:ExecutorBase INSERT→BizObjectServiceImplCompanion:275→40000);8.6.26 唯一 load+update 型就地发起是 WorkflowInstanceFacade 5 参重载(908 行,facade 层供二开扩展消费),无 REST 面",
|
|
26
|
-
sheetResolution: "explicit caller arg > default-form sheetCode from bizsheet/get > schemaCode fallback",
|
|
27
|
-
updateBranch: "POST runtime/form/save 更新既有记录:目标 id 必须放进 bizObject.data[\"id\"](平台 BizObjectModel.getId() 读 data 的 id 键;顶层 bizObject.id 会被忽略并退化为新建);命中既有记录时 FormRuntimeController.saveBizSheet→BizObjectFacadeImpl.updateBizObject,未命中则 createBizObject 插入",
|
|
28
|
-
updateStatusRule: "既有记录的 DRAFT/COMPLETED 状态在未显式传 sequenceStatus 时原样保留(saveBizSheet 分支),COMPLETED 记录可直接 update,无需流程",
|
|
29
|
-
updateMergeRule: "updateBizObject.mergeObject 以库内旧值打底、以本次 data 覆盖 → 部分字段(patch)更新,未传字段保持不变",
|
|
30
|
-
childRowStatusNote: "子表行(CHILD_TABLE(8))在 form-save 路径按每行 `rowStatus` 分派(BizObjectUpdateModel.convertBizObjectOpItem):Added=插入 / Modified=更新 / Deleted=删除;**缺失或其它值=immutable(静默忽略)**。门户前端会为改动行标 `rowStatus:\"Modified\"`;手拼载荷漏了它,子表改动不落库、但写仍返回 errcode 0(真机 2026-10-08:data.update 改子表行无 rowStatus,process_name/计划时间读回全不变)。OMC 自动补:带自身 id 的行→\"Modified\",无 id 的行→\"Added\",显式 rowStatus 原样保留。",
|
|
31
|
-
updateAbsentGuard: "平台对\"带 id 但库内不存在\"不报错而是静默插入(createBizObject),故 data.update 先 form/load 预检,命中缺席则以 304001 拒绝,绝不静默转新增",
|
|
32
|
-
projectionHint: "所选列在默认视图未配置(值可能存在但列表投影为 null);先用 listview.configure 配置列,或用 data.load 读单条权威值",
|
|
33
|
-
formulaFieldNote: "计算字段(FORMULA / 表单「计算」控件、子表计算规则)**不在服务端 API 路径回填/重算**:门户是**前端**算好再提交,API save/update/submit 都不会替它算(真机 2026-09-25:子表计算规则字段落库仍 null;纯主表表达式会算)。OMC 的定位是「字段配好即默认它在前台会算」——**写测试数据时应在 formData 里显式带上预算好的计算值**(让数据与预期自洽),**不要指望平台在 API 保存时替它算,也不要用 API 回读值去断言公式/计算是否正确**。**这类纯前台行为最终由人工在前台(配表单→填表)确认。** 另:带**子表计算字段**的模型,API data.save/data.update 可能因服务端重算(CalculateHandler→FormulaProcessor)NPE 而 50000(真机 2026-10-07,前台路径正常)——此类模型请改用门户录入或避开该字段,别据此判定配置错误。",
|
|
34
|
-
});
|
|
35
|
-
function encodeScalar(value) {
|
|
36
|
-
if (value === null || value === undefined)
|
|
37
|
-
return "";
|
|
38
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean")
|
|
39
|
-
return String(value);
|
|
40
|
-
if (value instanceof Date)
|
|
41
|
-
return value.toISOString();
|
|
42
|
-
return JSON.stringify(value);
|
|
43
|
-
}
|
|
44
|
-
function encodeList(value) {
|
|
45
|
-
if (Array.isArray(value))
|
|
46
|
-
return value.map(encodeScalar).join(";");
|
|
47
|
-
if (typeof value === "string")
|
|
48
|
-
return value;
|
|
49
|
-
return encodeScalar(value);
|
|
50
|
-
}
|
|
51
|
-
const numeric = (value) => Number(value);
|
|
52
|
-
function betweenBounds(expected) {
|
|
53
|
-
const [low, high] = expected.split(";").map(Number);
|
|
54
|
-
return [low ?? 0, high ?? 0];
|
|
55
|
-
}
|
|
56
|
-
export const RUNTIME_QUERY_OPERATORS = Object.freeze({
|
|
57
|
-
Eq: { encode: encodeScalar, matches: (actual, expected) => String(actual ?? "") === expected },
|
|
58
|
-
NotEq: { encode: encodeScalar, matches: (actual, expected) => String(actual ?? "") !== expected },
|
|
59
|
-
Gt: { encode: encodeScalar, matches: (actual, expected) => numeric(actual) > numeric(expected) },
|
|
60
|
-
Lt: { encode: encodeScalar, matches: (actual, expected) => numeric(actual) < numeric(expected) },
|
|
61
|
-
Gte: { encode: encodeScalar, matches: (actual, expected) => numeric(actual) >= numeric(expected) },
|
|
62
|
-
Lte: { encode: encodeScalar, matches: (actual, expected) => numeric(actual) <= numeric(expected) },
|
|
63
|
-
Like: { encode: encodeScalar, matches: (actual, expected) => String(actual ?? "").includes(expected) },
|
|
64
|
-
NotLike: { encode: encodeScalar, matches: (actual, expected) => !String(actual ?? "").includes(expected) },
|
|
65
|
-
In: { encode: encodeList, matches: (actual, expected) => expected.split(";").includes(String(actual ?? "")) },
|
|
66
|
-
NotIn: { encode: encodeList, matches: (actual, expected) => !expected.split(";").includes(String(actual ?? "")) },
|
|
67
|
-
Between: { encode: encodeList, matches: (actual, expected) => { const [low, high] = betweenBounds(expected); return numeric(actual) >= low && numeric(actual) <= high; } },
|
|
68
|
-
NotBetween: { encode: encodeList, matches: (actual, expected) => { const [low, high] = betweenBounds(expected); return !(numeric(actual) >= low && numeric(actual) <= high); } },
|
|
69
|
-
IsNull: { encode: () => "", matches: (actual) => actual === null || actual === undefined },
|
|
70
|
-
IsNotNull: { encode: () => "", matches: (actual) => actual !== null && actual !== undefined },
|
|
71
|
-
});
|
|
72
|
-
export const ENGINE_OWNED_KEYS = Object.freeze(["sequenceStatus", "2147483647_sequenceNo"]);
|
|
73
|
-
export const SUBMIT_STATUS_TRANSITIONS = Object.freeze({
|
|
74
|
-
/** Start submit / start-item finish moves the row DRAFT -> PROCESSING. */
|
|
75
|
-
workflowStart: "PROCESSING",
|
|
76
|
-
/** Approval submit with agree!==false completes the row. */
|
|
77
|
-
approvalAgree: "COMPLETED",
|
|
78
|
-
/** Approval submit with agree===false cancels the row. */
|
|
79
|
-
approvalReject: "CANCELED",
|
|
80
|
-
/** formType=2 submitBizSheet -> saveBizObject completes in place. */
|
|
81
|
-
inPlaceSubmit: "COMPLETED",
|
|
82
|
-
});
|
|
4
|
+
export { RUNTIME_DATA_WIRE, RUNTIME_QUERY_OPERATORS, ENGINE_OWNED_KEYS, SUBMIT_STATUS_TRANSITIONS } from "omc-core/wire-facts";
|
|
5
|
+
import { RUNTIME_DATA_WIRE } from "omc-core/wire-facts";
|
|
83
6
|
export function dataListDescription() {
|
|
84
7
|
const wire = RUNTIME_DATA_WIRE;
|
|
85
8
|
return `按简化过滤 DSL 查询业务对象(runtime/query/list);DSL 在 CLI 内翻译为平台 queryCondition(${wire.dslShape}),并按发布字段元数据为每个条件注入 propertyType(未知过滤字段将被拒绝,可用 rawCondition 逃生);响应仅返回选定 fields 与分页元数据;空模型返回空列表;未配列的自定义字段投影为 null——这是显示面缺列配置、不是数据缺失(data.load 有值),显式选列在结果中全为 null 而发布字段元数据显示其存在时,readback 追加 projectionHint(先用 listview.configure 配置该模型的默认视图列,或用 data.load 读单条权威值)。`;
|
|
@@ -167,10 +90,10 @@ export const RUNTIME_DATA_LANE_CONTRACT = Object.freeze({
|
|
|
167
90
|
dslShape: Object.freeze([emitterBinding("data-list-filter.SIMPLE_OP_MAP", assertRuntimeDataContract)]),
|
|
168
91
|
queryBodyCanon: Object.freeze([behaviorBinding("test/data-list.test.ts#data.list translates simplified filters", assertRuntimeDataContract)]),
|
|
169
92
|
typeCastErrcode: Object.freeze([emitterBinding("data-list-filter.injectPropertyTypes", assertRuntimeDataContract)]),
|
|
170
|
-
emptyModelErrcode: Object.freeze([emitterBinding("
|
|
171
|
-
absenceErrcode: Object.freeze([emitterBinding("
|
|
172
|
-
replayTokenErrcode: Object.freeze([emitterBinding("
|
|
173
|
-
approvalNpeErrcode: Object.freeze([emitterBinding("
|
|
93
|
+
emptyModelErrcode: Object.freeze([emitterBinding("omc-core/wire-facts.RUNTIME_DATA_WIRE", assertRuntimeDataContract)]),
|
|
94
|
+
absenceErrcode: Object.freeze([emitterBinding("omc-core/wire-facts.RUNTIME_DATA_WIRE", assertRuntimeDataContract)]),
|
|
95
|
+
replayTokenErrcode: Object.freeze([emitterBinding("omc-core/wire-facts.RUNTIME_DATA_WIRE", assertRuntimeDataContract)]),
|
|
96
|
+
approvalNpeErrcode: Object.freeze([emitterBinding("omc-core/wire-facts.RUNTIME_DATA_WIRE", assertRuntimeDataContract)]),
|
|
174
97
|
submitChain: Object.freeze([behaviorBinding("test/issue-13-data-submit.test.ts#data.submit with formType=2 issues a WorkItemSubmitVO", assertRuntimeDataContract)]),
|
|
175
98
|
submitInPlace: Object.freeze([behaviorBinding("test/issue-13-data-submit.test.ts#data.submit with bizObjectId submits an existing no-workflow record in place", assertRuntimeDataContract)]),
|
|
176
99
|
workflowDraftSave: Object.freeze([behaviorBinding("test/issue-13-data-submit.test.ts#data.save with workflowCode creates a DRAFT row", assertRuntimeDataContract)]),
|
|
@@ -524,7 +524,7 @@ export function registerRuntimeDataLane(tools) {
|
|
|
524
524
|
inputSchema: {
|
|
525
525
|
environment: environmentArg,
|
|
526
526
|
schemaCode: z.string(),
|
|
527
|
-
bizObjectId: z.string({ required_error: "bizObjectId
|
|
527
|
+
bizObjectId: z.string({ required_error: "bizObjectId 必填(参数名就是 bizObjectId——不是 id 也不是 objectId,r44 两个 agent 都猜了 objectId;取值来自 data.submit 读回的 objectId/sequenceNo 或 data.list 的 row.bizObjectId)" }),
|
|
528
528
|
full: z.boolean().optional().describe("返回未裁剪的完整业务对象(含 owner/creater 等系统字段;默认返回业务字段+身份的精简记录)"),
|
|
529
529
|
capability: capabilityArg,
|
|
530
530
|
},
|
|
@@ -38,7 +38,7 @@ const plainSubmit = Object.freeze({
|
|
|
38
38
|
{ code: "submit-biz-object-absent", action: "bizObjectId 在平台无行(304001)——回 data.save 重建草稿后再直提" },
|
|
39
39
|
{ code: "invalid-input", action: "带了 workItemId/workflowInstanceId 时必须同时带 bizObjectId(fail-closed)" },
|
|
40
40
|
],
|
|
41
|
-
notes: "r23 复盘修正:本配方曾教先 save 再 submit 两步——正确路径是无 id 一调(业务约定:正式数据一律 data.submit)。Create/Update 规则只在 COMPLETED 分发(DomainServiceImpl.java:240-248);规则效果以提交读回/data.load
|
|
41
|
+
notes: "r23 复盘修正:本配方曾教先 save 再 submit 两步——正确路径是无 id 一调(业务约定:正式数据一律 data.submit)。Create/Update 规则只在 COMPLETED 分发(DomainServiceImpl.java:240-248);规则效果以提交读回/data.load 为准(data.load 参数名=bizObjectId,直接用提交读回的 objectId)。data.save 定位=草稿/暂存",
|
|
42
42
|
requiresEnvironmentDerivedIds: true,
|
|
43
43
|
});
|
|
44
44
|
const workflowSubmit = Object.freeze({
|
package/dist/tool-types.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
constructor(payload) {
|
|
5
|
-
super(String(payload["blocker"] ?? "tool refused"));
|
|
6
|
-
this.payload = payload;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
2
|
+
export { ToolRefusalError } from "omc-core/refusal";
|
|
3
|
+
import { ToolRefusalError } from "omc-core/refusal";
|
|
9
4
|
/**
|
|
10
5
|
* The most informative message an error carries. `ToolRefusalError.message` is
|
|
11
6
|
* the BLOCKER code (its constructor sets it so), while the actionable human
|
package/dist/zip.js
CHANGED
|
@@ -1,156 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Ticket 106 — a minimal, dependency-free ZIP reader/writer for the bizservice
|
|
4
|
-
* IE package face.
|
|
5
|
-
*
|
|
6
|
-
* The platform export answers a ZIP with manifest.json + version.md
|
|
7
|
-
* (BizServiceIEController.java:124-138). Java's ZipOutputStream typically
|
|
8
|
-
* writes local headers with zeroed sizes plus a trailing data descriptor
|
|
9
|
-
* (general-purpose bit 3), so the READER resolves entries through the central
|
|
10
|
-
* directory (whose sizes/CRCs are authoritative) and only uses each local
|
|
11
|
-
* header to locate the data offset. The WRITER emits stored (method 0)
|
|
12
|
-
* entries — real zips for fake routes and tests; the platform reader accepts
|
|
13
|
-
* both stored and deflated entries.
|
|
14
|
-
*/
|
|
15
|
-
const EOCD_SIGNATURE = 0x06054b50;
|
|
16
|
-
const CENTRAL_SIGNATURE = 0x02014b50;
|
|
17
|
-
const LOCAL_SIGNATURE = 0x04034b50;
|
|
18
|
-
const EOCD_SCAN_LIMIT = 65_557; // fixed EOCD (22) + max comment (65_535)
|
|
19
|
-
export class ZipFormatError extends Error {
|
|
20
|
-
constructor(message) {
|
|
21
|
-
super(message);
|
|
22
|
-
this.name = "ZipFormatError";
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
/** CRC-32 (IEEE 802.3), table-driven. */
|
|
26
|
-
const CRC_TABLE = (() => {
|
|
27
|
-
const table = new Array(256);
|
|
28
|
-
for (let n = 0; n < 256; n += 1) {
|
|
29
|
-
let c = n;
|
|
30
|
-
for (let k = 0; k < 8; k += 1)
|
|
31
|
-
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
32
|
-
table[n] = c >>> 0;
|
|
33
|
-
}
|
|
34
|
-
return table;
|
|
35
|
-
})();
|
|
36
|
-
function crc32(buffer) {
|
|
37
|
-
let crc = 0xffffffff;
|
|
38
|
-
for (const byte of buffer)
|
|
39
|
-
crc = CRC_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8);
|
|
40
|
-
return (crc ^ 0xffffffff) >>> 0;
|
|
41
|
-
}
|
|
42
|
-
/** Locate the End Of Central Directory record; returns its byte offset. */
|
|
43
|
-
function locateEocd(buffer) {
|
|
44
|
-
const start = Math.max(0, buffer.length - EOCD_SCAN_LIMIT);
|
|
45
|
-
for (let offset = buffer.length - 22; offset >= start; offset -= 1) {
|
|
46
|
-
if (buffer.readUInt32LE(offset) === EOCD_SIGNATURE) {
|
|
47
|
-
const commentLength = buffer.readUInt16LE(offset + 20);
|
|
48
|
-
if (offset + 22 + commentLength === buffer.length)
|
|
49
|
-
return offset;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
throw new ZipFormatError("no end-of-central-directory record found");
|
|
53
|
-
}
|
|
54
|
-
/** Read every central-directory entry as name → decompressed Buffer. */
|
|
55
|
-
export function readZipEntries(buffer) {
|
|
56
|
-
const eocd = locateEocd(buffer);
|
|
57
|
-
const entryCount = buffer.readUInt16LE(eocd + 10);
|
|
58
|
-
let cursor = buffer.readUInt32LE(eocd + 16);
|
|
59
|
-
const entries = new Map();
|
|
60
|
-
for (let index = 0; index < entryCount; index += 1) {
|
|
61
|
-
if (cursor + 46 > buffer.length || buffer.readUInt32LE(cursor) !== CENTRAL_SIGNATURE)
|
|
62
|
-
throw new ZipFormatError(`central directory entry ${index} is malformed`);
|
|
63
|
-
const method = buffer.readUInt16LE(cursor + 10);
|
|
64
|
-
const compressedSize = buffer.readUInt32LE(cursor + 20);
|
|
65
|
-
const nameLength = buffer.readUInt16LE(cursor + 28);
|
|
66
|
-
const extraLength = buffer.readUInt16LE(cursor + 30);
|
|
67
|
-
const commentLength = buffer.readUInt16LE(cursor + 32);
|
|
68
|
-
const localOffset = buffer.readUInt32LE(cursor + 42);
|
|
69
|
-
const name = buffer.subarray(cursor + 46, cursor + 46 + nameLength).toString("utf8");
|
|
70
|
-
// The local header carries its OWN name/extra lengths (they may differ
|
|
71
|
-
// from the central copies); the data starts right after them.
|
|
72
|
-
if (localOffset + 30 > buffer.length || buffer.readUInt32LE(localOffset) !== LOCAL_SIGNATURE)
|
|
73
|
-
throw new ZipFormatError(`local header for "${name}" is malformed`);
|
|
74
|
-
const localNameLength = buffer.readUInt16LE(localOffset + 26);
|
|
75
|
-
const localExtraLength = buffer.readUInt16LE(localOffset + 28);
|
|
76
|
-
const dataStart = localOffset + 30 + localNameLength + localExtraLength;
|
|
77
|
-
const data = buffer.subarray(dataStart, dataStart + compressedSize);
|
|
78
|
-
if (method === 0)
|
|
79
|
-
entries.set(name, Buffer.from(data));
|
|
80
|
-
else if (method === 8)
|
|
81
|
-
entries.set(name, inflateRawSync(data));
|
|
82
|
-
else
|
|
83
|
-
throw new ZipFormatError(`entry "${name}" uses unsupported compression method ${method}`);
|
|
84
|
-
cursor += 46 + nameLength + extraLength + commentLength;
|
|
85
|
-
}
|
|
86
|
-
return entries;
|
|
87
|
-
}
|
|
88
|
-
/** Build a stored-entry ZIP (method 0, UTF-8 names flagged via bit 11). */
|
|
89
|
-
export function buildZip(files) {
|
|
90
|
-
const chunks = [];
|
|
91
|
-
const central = [];
|
|
92
|
-
let offset = 0;
|
|
93
|
-
for (const file of files) {
|
|
94
|
-
const nameBytes = Buffer.from(file.name, "utf8");
|
|
95
|
-
const crc = crc32(file.data);
|
|
96
|
-
const local = Buffer.alloc(30);
|
|
97
|
-
local.writeUInt32LE(LOCAL_SIGNATURE, 0);
|
|
98
|
-
local.writeUInt16LE(0x0800, 6); // UTF-8 name flag
|
|
99
|
-
local.writeUInt16LE(0, 8); // stored
|
|
100
|
-
local.writeUInt16LE(0, 10); // time
|
|
101
|
-
local.writeUInt16LE(0x2100, 12); // date (1996-01-01, deterministic)
|
|
102
|
-
local.writeUInt32LE(crc, 14);
|
|
103
|
-
local.writeUInt32LE(file.data.length, 18);
|
|
104
|
-
local.writeUInt32LE(file.data.length, 22);
|
|
105
|
-
local.writeUInt16LE(nameBytes.length, 26);
|
|
106
|
-
local.writeUInt16LE(0, 28);
|
|
107
|
-
chunks.push(local, nameBytes, file.data);
|
|
108
|
-
const entry = Buffer.alloc(46);
|
|
109
|
-
entry.writeUInt32LE(CENTRAL_SIGNATURE, 0);
|
|
110
|
-
entry.writeUInt16LE(0x031e, 4); // made by UNIX/ZIP 3.0
|
|
111
|
-
entry.writeUInt16LE(0x0014, 6); // extract by DOS/ZIP 2.0
|
|
112
|
-
entry.writeUInt16LE(0x0800, 8);
|
|
113
|
-
entry.writeUInt16LE(0, 10);
|
|
114
|
-
entry.writeUInt16LE(0, 12);
|
|
115
|
-
entry.writeUInt16LE(0x2100, 14);
|
|
116
|
-
entry.writeUInt32LE(crc, 16);
|
|
117
|
-
entry.writeUInt32LE(file.data.length, 20);
|
|
118
|
-
entry.writeUInt32LE(file.data.length, 24);
|
|
119
|
-
entry.writeUInt16LE(nameBytes.length, 28);
|
|
120
|
-
entry.writeUInt16LE(0, 30);
|
|
121
|
-
entry.writeUInt16LE(0, 32);
|
|
122
|
-
entry.writeUInt16LE(0, 34);
|
|
123
|
-
entry.writeUInt16LE(0, 36);
|
|
124
|
-
entry.writeUInt32LE(0, 38);
|
|
125
|
-
entry.writeUInt32LE(offset, 42);
|
|
126
|
-
central.push(entry, nameBytes);
|
|
127
|
-
offset += 30 + nameBytes.length + file.data.length;
|
|
128
|
-
}
|
|
129
|
-
const centralBuffer = Buffer.concat(central);
|
|
130
|
-
const eocd = Buffer.alloc(22);
|
|
131
|
-
eocd.writeUInt32LE(EOCD_SIGNATURE, 0);
|
|
132
|
-
eocd.writeUInt16LE(0, 4);
|
|
133
|
-
eocd.writeUInt16LE(0, 6);
|
|
134
|
-
eocd.writeUInt16LE(files.length, 8);
|
|
135
|
-
eocd.writeUInt16LE(files.length, 10);
|
|
136
|
-
eocd.writeUInt32LE(centralBuffer.length, 12);
|
|
137
|
-
eocd.writeUInt32LE(offset, 16);
|
|
138
|
-
eocd.writeUInt16LE(0, 20);
|
|
139
|
-
return Buffer.concat([...chunks, centralBuffer, eocd]);
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Resolve an entry by BASENAME: the platform export compresses the staging
|
|
143
|
-
* DIRECTORY, so entries carry a `bizService-<uuid>/` prefix
|
|
144
|
-
* (live 2026-09-15: bizService-59d72d4a…/manifest.json).
|
|
145
|
-
*/
|
|
146
|
-
export function zipEntryByBasename(entries, basename) {
|
|
147
|
-
const direct = entries.get(basename);
|
|
148
|
-
if (direct !== undefined)
|
|
149
|
-
return direct;
|
|
150
|
-
const suffix = `/${basename}`;
|
|
151
|
-
for (const [name, data] of entries)
|
|
152
|
-
if (name.endsWith(suffix))
|
|
153
|
-
return data;
|
|
154
|
-
return undefined;
|
|
155
|
-
}
|
|
1
|
+
// C1 拆分:zip 已下沉 omc-core(真 lane 与 @stalfh233/omc-fake-platform 共用唯一事实源);此处 re-export 保持既有 import 路径不变。
|
|
2
|
+
export * from "omc-core/zip";
|
|
156
3
|
//# sourceMappingURL=zip.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stalfh233/omc-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.39",
|
|
4
4
|
"description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -50,8 +50,9 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
+
"@stalfh233/omc-fake-platform": "0.4.39",
|
|
53
54
|
"commander": "14.0.3",
|
|
54
|
-
"omc-core": "0.4.
|
|
55
|
+
"omc-core": "0.4.39",
|
|
55
56
|
"proper-lockfile": "4.1.2",
|
|
56
57
|
"write-file-atomic": "6.0.0",
|
|
57
58
|
"zod": "^3.23.8",
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { createServer } from "node:http";
|
|
2
|
-
import { FakeCloudPivot } from "../fake/index.js";
|
|
3
|
-
export async function startFakeHttpServer() {
|
|
4
|
-
const server = createServer();
|
|
5
|
-
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
6
|
-
const address = server.address();
|
|
7
|
-
if (address === null || typeof address === "string") {
|
|
8
|
-
server.closeAllConnections?.();
|
|
9
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
10
|
-
throw new Error("fake http server has no listen address");
|
|
11
|
-
}
|
|
12
|
-
const url = `http://127.0.0.1:${address.port}`;
|
|
13
|
-
const fake = new FakeCloudPivot({ version: "8.6.26", baseUrl: url });
|
|
14
|
-
server.on("request", (request, response) => {
|
|
15
|
-
const chunks = [];
|
|
16
|
-
request.on("data", (chunk) => chunks.push(chunk));
|
|
17
|
-
request.on("end", () => {
|
|
18
|
-
const target = new URL(request.url ?? "/", url);
|
|
19
|
-
const query = {};
|
|
20
|
-
for (const [key, value] of target.searchParams)
|
|
21
|
-
query[key] = value;
|
|
22
|
-
const raw = Buffer.concat(chunks);
|
|
23
|
-
const contentType = request.headers["content-type"] ?? "";
|
|
24
|
-
const body = raw.length === 0 ? undefined : contentType.includes("json") ? JSON.parse(raw.toString("utf8")) : raw.toString("utf8");
|
|
25
|
-
const headers = {};
|
|
26
|
-
for (const [key, value] of Object.entries(request.headers)) {
|
|
27
|
-
if (typeof value === "string") {
|
|
28
|
-
const capitalized = key.replace(/\b\w/g, (c) => c.toUpperCase()).replace(/-(\w)/g, (_m, c) => `-${c.toUpperCase()}`);
|
|
29
|
-
headers[capitalized] = value;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
fake
|
|
33
|
-
.request({
|
|
34
|
-
path: target.pathname,
|
|
35
|
-
method: request.method === "POST" ? "POST" : "GET",
|
|
36
|
-
...(Object.keys(query).length > 0 ? { query } : {}),
|
|
37
|
-
...(body === undefined ? {} : { body }),
|
|
38
|
-
headers,
|
|
39
|
-
})
|
|
40
|
-
.then((result) => {
|
|
41
|
-
const payload = Buffer.isBuffer(result.body) ? result.body : JSON.stringify(result.body);
|
|
42
|
-
response.writeHead(result.status, Buffer.isBuffer(result.body) ? undefined : { "Content-Type": "application/json" });
|
|
43
|
-
response.end(payload);
|
|
44
|
-
})
|
|
45
|
-
.catch(() => {
|
|
46
|
-
response.writeHead(500, { "Content-Type": "application/json" });
|
|
47
|
-
response.end(JSON.stringify({ errcode: 500, errmsg: "fake transport failure", data: null }));
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
return {
|
|
52
|
-
fake,
|
|
53
|
-
url,
|
|
54
|
-
close: () => new Promise((resolve, reject) => {
|
|
55
|
-
server.closeAllConnections?.();
|
|
56
|
-
server.close((error) => (error ? reject(error) : resolve()));
|
|
57
|
-
}),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
export function canonicalBusinessCalls(fake) {
|
|
61
|
-
return JSON.stringify(fake.state.callLog
|
|
62
|
-
.filter((entry) => !entry.path.startsWith("/user/") && !entry.path.startsWith("/api/public/") && !entry.path.startsWith("/api/api/organization/"))
|
|
63
|
-
.map((entry) => ({ path: entry.path, method: entry.method, body: entry.body ?? null, query: entry.query && Object.keys(entry.query).length > 0 ? entry.query : null })));
|
|
64
|
-
}
|
|
65
|
-
//# sourceMappingURL=fake-http-server.js.map
|
package/dist/fake/app.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { NOT_HANDLED } from "./state.js";
|
|
2
|
-
/** app-management lane: application package CRUD. Twin of src/lanes/app.ts. */
|
|
3
|
-
export const appRoute = (ctx, input) => {
|
|
4
|
-
const q = input.query ?? {};
|
|
5
|
-
const body = (input.body ?? {});
|
|
6
|
-
switch (input.path) {
|
|
7
|
-
case "/api/api/app/apppackage/list":
|
|
8
|
-
return ctx.options.applicationList ?? [...ctx.state.apps.values()].map((app) => ({ appCode: app.code, appName: app.name, id: app.id, groupId: app.groupId, published: app.published !== false }));
|
|
9
|
-
case "/api/api/app/apppackage/list_all_group":
|
|
10
|
-
return [...ctx.state.appGroups.values()].map((group) => ({
|
|
11
|
-
id: group.id,
|
|
12
|
-
code: group.code,
|
|
13
|
-
name: group.name,
|
|
14
|
-
children: [...ctx.state.apps.values()].filter((app) => app.groupId === group.id).map((app) => ({ id: app.id, code: app.code, name: app.name, groupId: app.groupId })),
|
|
15
|
-
}));
|
|
16
|
-
case "/api/api/app/apppackage/create": {
|
|
17
|
-
const code = String(body.code);
|
|
18
|
-
ctx.state.apps.set(code, { id: `app-${ctx.state.apps.size + 1}`, code, name: String(body.name ?? code), groupId: String(body.groupId ?? [...ctx.state.appGroups.keys()][0] ?? "group-1") });
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
case "/api/api/app/apppackage/get": {
|
|
22
|
-
const app = [...ctx.state.apps.values()].find((row) => row.code === q.appCode);
|
|
23
|
-
return app ? { id: app.id, code: app.code, name: app.name, name_i18n: JSON.stringify({ "zh-CN": app.name }), groupId: app.groupId, enabled: true } : null;
|
|
24
|
-
}
|
|
25
|
-
case "/api/api/app/apppackage/update": {
|
|
26
|
-
// AppIntegrationFacadeImpl.updateAppPackage: id-required whole-record
|
|
27
|
-
// update; explicit groupId honored; blank groupId would be forced to
|
|
28
|
-
// the "default" group; enabled !== true wipes workflow favorites.
|
|
29
|
-
const target = [...ctx.state.apps.values()].find((row) => row.id === body.id);
|
|
30
|
-
if (target) {
|
|
31
|
-
if (typeof body.groupId === "string" && body.groupId)
|
|
32
|
-
target.groupId = body.groupId;
|
|
33
|
-
if (typeof body.name === "string" && body.name)
|
|
34
|
-
target.name = body.name;
|
|
35
|
-
}
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
case "/api/api/app/apppackage/delete":
|
|
39
|
-
ctx.state.apps.delete(String(q.appCode));
|
|
40
|
-
return null;
|
|
41
|
-
default:
|
|
42
|
-
return NOT_HANDLED;
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
//# sourceMappingURL=app.js.map
|