@stalfh233/omc-cli 0.6.7 → 0.6.11
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 +2 -1
- package/dist/approval-flow.js +5 -3
- package/dist/bizservice-config.js +26 -10
- package/dist/call.js +47 -0
- package/dist/capability-overview.js +47 -24
- package/dist/cli-contract.js +73 -26
- package/dist/cli-output.js +41 -2
- package/dist/cli-stdout.js +74 -0
- package/dist/cli.js +135 -52
- package/dist/command-transport.js +8 -1
- package/dist/config-store.js +17 -2
- package/dist/discovery.js +62 -3
- package/dist/field-families.js +35 -6
- package/dist/goal-routes.js +118 -11
- package/dist/human-summary.js +11 -0
- package/dist/lanes/app-contract.js +12 -5
- package/dist/lanes/app.js +221 -134
- package/dist/lanes/apply-changes.js +2 -1
- package/dist/lanes/b2-registration-contract.js +3 -3
- package/dist/lanes/b2-registration.js +890 -693
- package/dist/lanes/businessrule.js +75 -51
- package/dist/lanes/custom-page.js +166 -115
- package/dist/lanes/datarule-contract.js +2 -2
- package/dist/lanes/datarule.js +238 -154
- package/dist/lanes/dictionary.js +253 -110
- package/dist/lanes/environment.js +57 -9
- package/dist/lanes/field.js +160 -107
- package/dist/lanes/folder-contract.js +7 -3
- package/dist/lanes/folder.js +249 -118
- package/dist/lanes/form-contract.js +1 -1
- package/dist/lanes/form.js +188 -64
- package/dist/lanes/list-view.js +405 -369
- package/dist/lanes/meta.js +31 -6
- package/dist/lanes/model-contract.js +1 -1
- package/dist/lanes/model.js +610 -512
- package/dist/lanes/online-js-contract.js +1 -1
- package/dist/lanes/online-js.js +160 -146
- package/dist/lanes/organization.js +905 -640
- package/dist/lanes/programming.js +274 -136
- package/dist/lanes/report-contract.js +2 -2
- package/dist/lanes/report.js +354 -198
- package/dist/lanes/rule-lifecycle.js +294 -140
- package/dist/lanes/runtime-data-contract.js +4 -4
- package/dist/lanes/runtime-data.js +869 -745
- package/dist/lanes/workflow-contract.js +3 -3
- package/dist/lanes/workflow.js +732 -605
- package/dist/model-scaffold.js +26 -5
- package/dist/path-projection.js +196 -14
- package/dist/plan.js +25 -0
- package/dist/programming-workflow.js +55 -3
- package/dist/read-shapes.js +146 -0
- package/dist/read.js +40 -9
- package/dist/recipes/businessrule.js +74 -24
- package/dist/recipes/dictionary.js +7 -4
- package/dist/recipes/form.js +46 -0
- package/dist/recipes/runtime-data.js +3 -2
- package/dist/refusal-guidance.js +29 -5
- package/dist/rule-support.js +14 -1
- package/dist/rule-when.js +2 -2
- package/dist/tool-types.js +9 -3
- package/dist/version-family-gaps.js +101 -0
- package/dist/write.js +2 -1
- package/package.json +6 -4
- package/skills/omc-glossary.md +13 -0
- package/skills/omc-refusals.md +13 -5
- package/skills/omc-tool-selection.md +2 -1
- package/skills/omc-write-hazards.md +5 -2
|
@@ -78,7 +78,7 @@ export const ONLINE_JS_LANE_CONTRACT = Object.freeze({
|
|
|
78
78
|
}),
|
|
79
79
|
"onlinejs.compose": Object.freeze({
|
|
80
80
|
scope: ONLINE_JS_SCOPE,
|
|
81
|
-
description:
|
|
81
|
+
description: `**本调用 = 改 JS + 立即发布整张表单(等同 form.publish,含布局与按钮)**:它把 source 写进 customScript,然后无条件下一次 bizsheet/publish 把整份草稿提拔成发布面(内部 force:true,即使表单此前已发布也会重推)。所以改完 JS **不需要**再单独 form.publish;反过来,form.publish 只提拔布局草稿、**不碰**在线 JS。要只改不发布必须显式传 publish:false。Compose the Online JS source as the customScript element into the form's draftHtmlJson layout array (then publish, promoting it verbatim to publishedHtmlJson). Core contract: preserve the platform template and only extend customScript.innerHTML — the six-element skeleton includes the ${PLATFORM_LIFECYCLE_WRAPPER_MARKER} lifecycle wrapper; the style, toolbar, template, beforeCustomScript, customScript, and afterCustomScript elements are kept or synthesized with platform defaults, while every layout/control element is preserved. customScript is rebuilt canonically as {tagName:'script', id:'customScript', innerHTML:<platform lifecycle wrapper containing source>, attrs:[]} because the designer requires an ARRAY attrs (e.attrs.some) and tagName, else the Online JS editor fails with t.some is not a function. Drives bizsheet/update + bizsheet/publish; publishedHtmlJson is read back verbatim and SHA-256-fingerprinted so the caller can verify byte equality. Read the EXISTING script first with onlinejs.get (decide replace vs INCREMENTAL from what is actually stored). Never overwrites the layout with a standalone script object or a partial [customScript]-only array (regression for issue 11's root cause). summary:true answers "did it land?" without any HTML/JS body (sha + byte counts + draftPublishedInSync); the default readback dedupes the composed draft and the script when they equal the published face, so the same script is never returned three times.`,
|
|
82
82
|
}),
|
|
83
83
|
}),
|
|
84
84
|
});
|
package/dist/lanes/online-js.js
CHANGED
|
@@ -95,167 +95,181 @@ export function registerOnlineJsLane(tools) {
|
|
|
95
95
|
compact: (result) => result,
|
|
96
96
|
}),
|
|
97
97
|
}));
|
|
98
|
-
writeTool(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
98
|
+
writeTool({
|
|
99
|
+
tools,
|
|
100
|
+
name: "onlinejs.compose",
|
|
101
|
+
lane: ONLINE_JS_LANE,
|
|
102
|
+
scope: ONLINE_JS_TOOLS["onlinejs.compose"].scope,
|
|
103
|
+
description: ONLINE_JS_TOOLS["onlinejs.compose"].description,
|
|
104
|
+
inputSchema: {
|
|
105
|
+
schemaCode: z.string().min(1),
|
|
106
|
+
// r28e F9:本 lane 其它工具(form.draft/form.publish/form.get/onlinejs.get)
|
|
107
|
+
// 的 sheetCode 都是 optional(省略 = schemaCode)。此前只有这里必填,
|
|
108
|
+
// dry-run 与真写都拒——同一个 lane 两套约定,调用方要白撞一轮才知道。
|
|
109
|
+
sheetCode: z.string().min(1).optional().describe("表单槽位码;省略时默认 = schemaCode(与 form.draft/form.publish 同口径)"),
|
|
110
|
+
source: z.string().min(1).describe("在线 JS 代码。默认 INCREMENTAL:并入平台 (function(form){…}) 模板的既有脚本,模板与既有注册全部保留;配 replace:true 才是整段替换。脚本不得使用 export/import;事件名必须是平台事件(onLoad/onRendered/onValidate/onPreAction/onCustomAction/onActionDone/onPrintFormDataHandle/onPrintWorkflowLogDataHandle)"),
|
|
111
|
+
replace: z.boolean().optional().describe("true = 以 source 整体替换 customScript(含完整 (function(form){…}) 脚本)。默认 false = 增量并入既有脚本,保留平台模板与既有逻辑"),
|
|
112
|
+
publish: z.boolean().optional(),
|
|
113
|
+
summary: z.boolean().optional().describe("true = 只回同步状态(sha/字节数/是否同步/渲染兼容),不回任何 HTML/JS 原文。确认「改动是否生效」用这个;要看原文用 onlinejs.get"),
|
|
114
|
+
},
|
|
115
|
+
execute: async (session, args) => {
|
|
116
|
+
const shouldPublish = args.publish !== false;
|
|
117
|
+
// r28e F9:和同 lane 其它工具一致——省略 sheetCode 即模型码槽位。
|
|
118
|
+
const targetSheetCode = args.sheetCode ?? args.schemaCode;
|
|
119
|
+
// 1. Read the existing draftHtmlJson.
|
|
120
|
+
const current = await wireCall(session, { path: ENDPOINTS.sheetGet, query: { schemaCode: args.schemaCode, sheetCode: targetSheetCode } });
|
|
121
|
+
const existingSheet = (current.data ?? null);
|
|
122
|
+
const existingRaw = typeof existingSheet?.draftHtmlJson === "string" ? existingSheet.draftHtmlJson : undefined;
|
|
123
|
+
const existingCompat = validateHtmlRender(existingRaw);
|
|
124
|
+
// A persisted legacy face may contain an object or malformed elements.
|
|
125
|
+
// Do not pass any of it through: discard the unproven face and rebuild
|
|
126
|
+
// only the canonical platform skeleton below.
|
|
127
|
+
const existingElements = existingCompat.ok ? parseLayoutElements(existingRaw) : [];
|
|
128
|
+
// 2. Script-level INCREMENTAL merge. The layout read-modify-write above
|
|
129
|
+
// preserved the element skeleton, but the customScript innerHTML was
|
|
130
|
+
// being replaced wholesale — that is how a bare `form.on(...)` (or an
|
|
131
|
+
// `export function`) landed as the ENTIRE script, leaving `form`
|
|
132
|
+
// undefined / a SyntaxError and rendering the form BLANK. Read the
|
|
133
|
+
// existing script and merge the caller's code into it; the platform
|
|
134
|
+
// default (an empty script) is seeded with the lifecycle scaffold.
|
|
135
|
+
const existingScript = String(existingElements.find((element) => element.id === "customScript")?.innerHTML ?? "");
|
|
136
|
+
const composedScript = composeCustomScript(existingScript, args.source, args.replace === true ? "replace" : "merge");
|
|
137
|
+
// 3. Preserve the platform template read-modify-write: keep the
|
|
138
|
+
// style/beforeCustomScript/afterCustomScript skeleton and every
|
|
139
|
+
// layout/control element; customScript carries the MERGED script.
|
|
140
|
+
const composedElements = composeLayoutElements(existingElements, composedScript);
|
|
141
|
+
assertHtmlRender(JSON.stringify(composedElements), { schemaCode: args.schemaCode, sheetCode: targetSheetCode, face: "composedDraftHtmlJson" }, true);
|
|
142
|
+
// 4. Stringify the COMPLETE element array (never partial).
|
|
143
|
+
const composedDraftHtml = JSON.stringify(composedElements);
|
|
144
|
+
// 5. bizsheet/update with the new draftHtmlJson (delegated to the
|
|
145
|
+
// shared form.draft write body so view/attributes scaffolds stay
|
|
146
|
+
// intact when present).
|
|
147
|
+
await executeFormDraftWrite(session, {
|
|
148
148
|
schemaCode: args.schemaCode,
|
|
149
|
-
sheetCode:
|
|
150
|
-
|
|
149
|
+
sheetCode: targetSheetCode,
|
|
150
|
+
draftHtmlJson: composedDraftHtml,
|
|
151
151
|
});
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
152
|
+
// 6. publish promotes the FULL layout (including customScript). Use
|
|
153
|
+
// force:true so compose re-promotes even when the form was
|
|
154
|
+
// previously published — otherwise onlinejs.compose would
|
|
155
|
+
// leave the stale publishedHtmlJson in place after the user
|
|
156
|
+
// already published once.
|
|
157
|
+
let published = false;
|
|
158
|
+
if (shouldPublish) {
|
|
159
|
+
const publishOutcome = await executeFormPublishWrite(session, {
|
|
160
|
+
schemaCode: args.schemaCode,
|
|
161
|
+
sheetCode: targetSheetCode,
|
|
162
|
+
force: true,
|
|
163
|
+
});
|
|
164
|
+
published = publishOutcome.readback.published === true;
|
|
165
|
+
}
|
|
166
|
+
// 7. Readback publishedHtmlJson VERBATIM.
|
|
167
|
+
const readback = await wireCall(session, { path: ENDPOINTS.sheetGet, query: { schemaCode: args.schemaCode, sheetCode: targetSheetCode } });
|
|
168
|
+
const publishedSheet = (readback.data ?? {});
|
|
169
|
+
const publishedRaw = typeof publishedSheet.publishedHtmlJson === "string" ? publishedSheet.publishedHtmlJson : "";
|
|
170
|
+
if (publishedRaw !== "")
|
|
171
|
+
assertHtmlRender(publishedRaw, { schemaCode: args.schemaCode, sheetCode: targetSheetCode, face: "publishedHtmlJson" }, true);
|
|
172
|
+
const publishedElements = parseLayoutElements(publishedRaw);
|
|
173
|
+
// 8. SHA-256 of the verbatim content.
|
|
174
|
+
const publishedSha256 = createHash("sha256").update(publishedRaw).digest("hex");
|
|
175
|
+
const draftSha256 = createHash("sha256").update(composedDraftHtml).digest("hex");
|
|
176
|
+
const publishedScript = readCustomScriptSource(publishedRaw).script;
|
|
177
|
+
const customScriptSha256 = createHash("sha256").update(composedScript, "utf8").digest("hex");
|
|
178
|
+
const draftPublishedInSync = publishedScript !== null && publishedScript === composedScript;
|
|
179
|
+
const htmlRenderCompat = { draft: validateHtmlRender(composedDraftHtml, true), published: validateHtmlRender(publishedRaw, true) };
|
|
180
|
+
// Live 2026-10-21 (customer): this readback shipped the same ~10KB script
|
|
181
|
+
// three times (composedDraftHtmlJson + publishedHtmlJson + customScript),
|
|
182
|
+
// ~45KB for a call whose only question was "did publish land?" — the
|
|
183
|
+
// session's single largest context amplification. Answer that question
|
|
184
|
+
// directly and keep 原文 opt-in.
|
|
185
|
+
if (args.summary === true) {
|
|
186
|
+
return {
|
|
187
|
+
performed: { errcode: 0, published },
|
|
188
|
+
readback: {
|
|
189
|
+
publishedSha256,
|
|
190
|
+
draftSha256,
|
|
191
|
+
draftPublishedInSync,
|
|
192
|
+
publishedElementCount: publishedElements.length,
|
|
193
|
+
scriptElementPresent: publishedElements.some((element) => element.id === "customScript"),
|
|
194
|
+
customScriptMerged: args.replace !== true,
|
|
195
|
+
customScriptSha256,
|
|
196
|
+
customScriptBytes: Buffer.byteLength(composedScript, "utf8"),
|
|
197
|
+
composedDraftBytes: Buffer.byteLength(composedDraftHtml, "utf8"),
|
|
198
|
+
publishedHtmlBytes: Buffer.byteLength(publishedRaw, "utf8"),
|
|
199
|
+
htmlRenderCompat,
|
|
200
|
+
hint: "summary 模式不回原文;要脚本原文用 onlinejs.get,要完整布局用 artifact 或 form.get --full",
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
174
204
|
return {
|
|
175
205
|
performed: { errcode: 0, published },
|
|
176
206
|
readback: {
|
|
207
|
+
// Dedupe: after a successful publish the draft and the published
|
|
208
|
+
// face are the same bytes, and the script also rides inside them.
|
|
209
|
+
// Return the composed payload ONCE (composedDraftHtmlJson keeps the
|
|
210
|
+
// field every consumer already reads) and state the equality instead
|
|
211
|
+
// of repeating ~11KB + ~10KB.
|
|
212
|
+
...(composedDraftHtml === publishedRaw
|
|
213
|
+
? { draftEqualsPublished: true }
|
|
214
|
+
: { publishedHtmlJson: publishedRaw }),
|
|
215
|
+
composedDraftHtmlJson: composedDraftHtml,
|
|
177
216
|
publishedSha256,
|
|
178
217
|
draftSha256,
|
|
179
218
|
draftPublishedInSync,
|
|
180
219
|
publishedElementCount: publishedElements.length,
|
|
181
220
|
scriptElementPresent: publishedElements.some((element) => element.id === "customScript"),
|
|
221
|
+
customScript: composedScript,
|
|
222
|
+
customScriptMatchesPublished: draftPublishedInSync,
|
|
182
223
|
customScriptMerged: args.replace !== true,
|
|
183
|
-
customScriptSha256,
|
|
184
|
-
customScriptBytes: Buffer.byteLength(composedScript, "utf8"),
|
|
185
|
-
composedDraftBytes: Buffer.byteLength(composedDraftHtml, "utf8"),
|
|
186
|
-
publishedHtmlBytes: Buffer.byteLength(publishedRaw, "utf8"),
|
|
187
224
|
htmlRenderCompat,
|
|
188
|
-
hint: "summary 模式不回原文;要脚本原文用 onlinejs.get,要完整布局用 artifact 或 form.get --full",
|
|
189
225
|
},
|
|
190
226
|
};
|
|
191
|
-
}
|
|
192
|
-
return {
|
|
193
|
-
performed: { errcode: 0, published },
|
|
194
|
-
readback: {
|
|
195
|
-
// Dedupe: after a successful publish the draft and the published
|
|
196
|
-
// face are the same bytes, and the script also rides inside them.
|
|
197
|
-
// Return the composed payload ONCE (composedDraftHtmlJson keeps the
|
|
198
|
-
// field every consumer already reads) and state the equality instead
|
|
199
|
-
// of repeating ~11KB + ~10KB.
|
|
200
|
-
...(composedDraftHtml === publishedRaw
|
|
201
|
-
? { draftEqualsPublished: true }
|
|
202
|
-
: { publishedHtmlJson: publishedRaw }),
|
|
203
|
-
composedDraftHtmlJson: composedDraftHtml,
|
|
204
|
-
publishedSha256,
|
|
205
|
-
draftSha256,
|
|
206
|
-
draftPublishedInSync,
|
|
207
|
-
publishedElementCount: publishedElements.length,
|
|
208
|
-
scriptElementPresent: publishedElements.some((element) => element.id === "customScript"),
|
|
209
|
-
customScript: composedScript,
|
|
210
|
-
customScriptMatchesPublished: draftPublishedInSync,
|
|
211
|
-
customScriptMerged: args.replace !== true,
|
|
212
|
-
htmlRenderCompat,
|
|
213
|
-
},
|
|
214
|
-
};
|
|
215
|
-
},
|
|
216
|
-
// Refuse a source that cannot run in the script slot (export/import, unknown
|
|
217
|
-
// form.on event) BEFORE the write permission gate. This is the exact
|
|
218
|
-
// class of mistake that blanked a live form page with no harness signal.
|
|
219
|
-
(args) => lintOnlineJsSource(args.source),
|
|
220
|
-
// r23 F4:dry-run 必须走与真写相同的 compose 校验路径(含 replace/merge 的
|
|
221
|
-
// wrapper 判定),否则 dry-run 过、真写拒——探针按 dry-run 绿灯写正式调用被拒。
|
|
222
|
-
async (session, args) => {
|
|
223
|
-
const current = await wireCall(session, { path: ENDPOINTS.sheetGet, query: { schemaCode: args.schemaCode, sheetCode: args.sheetCode ?? args.schemaCode } });
|
|
224
|
-
const sheet = (current.data ?? null);
|
|
225
|
-
const raw = typeof sheet?.draftHtmlJson === "string" ? sheet.draftHtmlJson : "";
|
|
226
|
-
const compat = validateHtmlRender(raw);
|
|
227
|
-
const elements = compat.ok ? parseLayoutElements(raw) : [];
|
|
228
|
-
const existingScript = String(elements.find((element) => element.id === "customScript")?.innerHTML ?? "");
|
|
229
|
-
const composed = composeCustomScript(existingScript, args.source, args.replace === true ? "replace" : "merge");
|
|
230
|
-
return {
|
|
231
|
-
mode: args.replace === true ? "replace" : "merge",
|
|
232
|
-
wouldChange: composed !== existingScript,
|
|
233
|
-
composedSha256: createHash("sha256").update(composed, "utf8").digest("hex"),
|
|
234
|
-
composedBytes: Buffer.byteLength(composed, "utf8"),
|
|
235
|
-
hint: "dry-run 已执行与真写一致的 compose 校验;该结果即正式调用将落库的脚本形状",
|
|
236
|
-
};
|
|
237
|
-
}, {
|
|
238
|
-
target: (args) => `onlinejs:${String(args.schemaCode ?? "")}/${String(args.sheetCode ?? args.schemaCode ?? "")}`,
|
|
239
|
-
read: async (session, args) => {
|
|
240
|
-
const sheet = await readFormOrNull(session, String(args.schemaCode), String(args.sheetCode ?? args.schemaCode));
|
|
241
|
-
if (!sheet)
|
|
242
|
-
return null;
|
|
243
|
-
const raw = typeof sheet.draftHtmlJson === "string" ? sheet.draftHtmlJson : "";
|
|
244
|
-
let elements = [];
|
|
245
|
-
try {
|
|
246
|
-
const parsed = JSON.parse(raw);
|
|
247
|
-
if (Array.isArray(parsed))
|
|
248
|
-
elements = parsed;
|
|
249
|
-
}
|
|
250
|
-
catch { }
|
|
251
|
-
const script = elements.find((element) => element && typeof element === "object" && element.id === "customScript");
|
|
252
|
-
return { customScript: typeof script?.innerHTML === "string" ? script.innerHTML : null };
|
|
253
227
|
},
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
228
|
+
preflight: // Refuse a source that cannot run in the script slot (export/import, unknown
|
|
229
|
+
// form.on event) BEFORE the write permission gate. This is the exact
|
|
230
|
+
// class of mistake that blanked a live form page with no harness signal.
|
|
231
|
+
(args) => lintOnlineJsSource(args.source),
|
|
232
|
+
dryRunPayload: // r23 F4:dry-run 必须走与真写相同的 compose 校验路径(含 replace/merge 的
|
|
233
|
+
// wrapper 判定),否则 dry-run 过、真写拒——探针按 dry-run 绿灯写正式调用被拒。
|
|
234
|
+
async (session, args) => {
|
|
235
|
+
const current = await wireCall(session, { path: ENDPOINTS.sheetGet, query: { schemaCode: args.schemaCode, sheetCode: args.sheetCode ?? args.schemaCode } });
|
|
236
|
+
const sheet = (current.data ?? null);
|
|
237
|
+
const raw = typeof sheet?.draftHtmlJson === "string" ? sheet.draftHtmlJson : "";
|
|
238
|
+
const compat = validateHtmlRender(raw);
|
|
239
|
+
const elements = compat.ok ? parseLayoutElements(raw) : [];
|
|
240
|
+
const existingScript = String(elements.find((element) => element.id === "customScript")?.innerHTML ?? "");
|
|
241
|
+
const composed = composeCustomScript(existingScript, args.source, args.replace === true ? "replace" : "merge");
|
|
242
|
+
return {
|
|
243
|
+
mode: args.replace === true ? "replace" : "merge",
|
|
244
|
+
wouldChange: composed !== existingScript,
|
|
245
|
+
composedSha256: createHash("sha256").update(composed, "utf8").digest("hex"),
|
|
246
|
+
composedBytes: Buffer.byteLength(composed, "utf8"),
|
|
247
|
+
hint: "dry-run 已执行与真写一致的 compose 校验;该结果即正式调用将落库的脚本形状",
|
|
248
|
+
};
|
|
249
|
+
},
|
|
250
|
+
rollback: {
|
|
251
|
+
target: (args) => `onlinejs:${String(args.schemaCode ?? "")}/${String(args.sheetCode ?? args.schemaCode ?? "")}`,
|
|
252
|
+
read: async (session, args) => {
|
|
253
|
+
const sheet = await readFormOrNull(session, String(args.schemaCode), String(args.sheetCode ?? args.schemaCode));
|
|
254
|
+
if (!sheet)
|
|
255
|
+
return null;
|
|
256
|
+
const raw = typeof sheet.draftHtmlJson === "string" ? sheet.draftHtmlJson : "";
|
|
257
|
+
let elements = [];
|
|
258
|
+
try {
|
|
259
|
+
const parsed = JSON.parse(raw);
|
|
260
|
+
if (Array.isArray(parsed))
|
|
261
|
+
elements = parsed;
|
|
262
|
+
}
|
|
263
|
+
catch { }
|
|
264
|
+
const script = elements.find((element) => element && typeof element === "object" && element.id === "customScript");
|
|
265
|
+
return { customScript: typeof script?.innerHTML === "string" ? script.innerHTML : null };
|
|
266
|
+
},
|
|
267
|
+
inverse: ({ before, args }) => {
|
|
268
|
+
const source = before?.customScript;
|
|
269
|
+
if (typeof source !== "string")
|
|
270
|
+
return { kind: "irreversible", reason: "before 表单没有可识别的 customScript element,无法构造清空脚本的等价 compose 调用" };
|
|
271
|
+
return { kind: "calls", calls: [{ tool: "onlinejs.compose", args: { schemaCode: String(args.schemaCode), sheetCode: String(args.sheetCode ?? args.schemaCode), source, replace: true, publish: args.publish } }] };
|
|
272
|
+
},
|
|
259
273
|
},
|
|
260
274
|
});
|
|
261
275
|
}
|