@stalfh233/omc-cli 0.4.77 → 0.4.78

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.
@@ -719,21 +719,25 @@ export function validateFormButtons(buttons, tool) {
719
719
  if (!FORM_BUTTON_LOCATIONS.includes(useLocation))
720
720
  throw new ToolRefusalError({ status: "refused", blocker: "form-buttons-invalid", message: `${tool}: buttons[${index}].useLocation 只能是 ${FORM_BUTTON_LOCATIONS.join("|")}(表单工具栏按钮用 FORM)` });
721
721
  const bindAction = typeof button.bindAction === "string" ? button.bindAction : "";
722
+ // `script` is the ergonomic spelling of actionConfig.scriptData (same as
723
+ // listview.configure); the explicit script wins over a stale config blob.
724
+ const scriptShorthand = typeof button.script === "string" ? button.script : undefined;
722
725
  if (!FORM_BUTTON_BIND_ACTIONS.has(bindAction))
723
726
  throw new ToolRefusalError({ status: "refused", blocker: "form-buttons-invalid", message: `${tool}: buttons[${index}].bindAction 必须是 ButtonBindAction 名(SCRIPT/BUSINESS_RULE/FLOW/FORM/ADD_DATA/CUSTOM_PAGE/…)` });
724
727
  const operateType = typeof button.operateType === "string" ? button.operateType : "";
725
728
  if (!FORM_BUTTON_OPERATE_TYPES.has(operateType))
726
729
  throw new ToolRefusalError({ status: "refused", blocker: "form-buttons-invalid", message: `${tool}: buttons[${index}].operateType 必须是 WITH_VIEW 或 WITH_OUT_VIEW` });
730
+ // Decode once: the SCRIPT guard and the script shorthand both read it.
731
+ const rawConfig = button.actionConfig;
732
+ const parsedConfigRaw = typeof rawConfig === "string" ? (() => { try {
733
+ return JSON.parse(rawConfig);
734
+ }
735
+ catch {
736
+ return undefined;
737
+ } })() : rawConfig;
738
+ const config = parsedConfigRaw && typeof parsedConfigRaw === "object" && !Array.isArray(parsedConfigRaw) ? parsedConfigRaw : undefined;
727
739
  if (bindAction === "SCRIPT" || bindAction === "BUSINESS_RULE" || bindAction === "FLOW" || bindAction === "ADD_DATA" || bindAction === "FORM") {
728
- const actionConfig = button.actionConfig;
729
- const record = typeof actionConfig === "string" ? (() => { try {
730
- return JSON.parse(actionConfig);
731
- }
732
- catch {
733
- return undefined;
734
- } })() : actionConfig;
735
- const config = record && typeof record === "object" && !Array.isArray(record) ? record : undefined;
736
- if (bindAction === "SCRIPT" && typeof config?.scriptData !== "string")
740
+ if (bindAction === "SCRIPT" && scriptShorthand === undefined && typeof config?.scriptData !== "string")
737
741
  throw new ToolRefusalError({ status: "refused", blocker: "form-buttons-invalid", message: `${tool}: buttons[${index}] SCRIPT 需要 actionConfig.scriptData(前端脚本源码字符串)` });
738
742
  if ((bindAction === "BUSINESS_RULE" || bindAction === "FLOW") && (typeof button.targetCode !== "string" || button.targetCode === ""))
739
743
  throw new ToolRefusalError({ status: "refused", blocker: "form-buttons-invalid", message: `${tool}: buttons[${index}] ${bindAction} 需要 targetCode` });
@@ -745,9 +749,13 @@ export function validateFormButtons(buttons, tool) {
745
749
  useLocation,
746
750
  bindAction,
747
751
  operateType,
748
- // The platform stores actionConfig as a string; a caller's object is
749
- // serialized once here (double-encoding breaks the renderer).
750
- ...(button.actionConfig !== undefined && typeof button.actionConfig !== "string" ? { actionConfig: JSON.stringify(button.actionConfig) } : {}),
752
+ // actionConfig is a String column: serialize exactly once (double
753
+ // encoding breaks the renderer). `script` merges into scriptData.
754
+ ...(scriptShorthand !== undefined
755
+ ? { actionConfig: JSON.stringify({ ...(config ?? {}), scriptData: scriptShorthand }) }
756
+ : button.actionConfig !== undefined && typeof button.actionConfig !== "string"
757
+ ? { actionConfig: JSON.stringify(button.actionConfig) }
758
+ : {}),
751
759
  sortKey: typeof button.sortKey === "number" ? button.sortKey : index,
752
760
  };
753
761
  });
@@ -779,7 +787,8 @@ export function mergeFormOptionsWithButtons(existingOptions, input, tool) {
779
787
  const removeCodes = input.removeButtonCodes ?? [];
780
788
  if (hasButtons && hasJson)
781
789
  throw new ToolRefusalError({ status: "refused", blocker: "form-buttons-ambiguous", message: `${tool}: buttons 与 customButtonJson 只能给一种(buttons 数组更好读;customButtonJson 用于原样透传读回值)` });
782
- if (!hasButtons && !hasJson && removeCodes.length === 0)
790
+ const scriptPatches = input.buttonScripts ?? [];
791
+ if (!hasButtons && !hasJson && removeCodes.length === 0 && scriptPatches.length === 0)
783
792
  return undefined;
784
793
  let incoming = [];
785
794
  if (hasButtons) {
@@ -802,6 +811,21 @@ export function mergeFormOptionsWithButtons(existingOptions, input, tool) {
802
811
  // `replace` restores the whole-list semantics.
803
812
  const mode = input.buttonsMode ?? "merge";
804
813
  const existing = readFormButtons(options) ?? [];
814
+ if (scriptPatches.length > 0) {
815
+ // Patch scriptData on EXISTING buttons only — the write counterpart of
816
+ // form.get --buttonScripts. An unknown code refuses rather than silently
817
+ // creating a button with no bindAction.
818
+ const known = new Set(existing.map((button) => String(button.code ?? "")));
819
+ const missing = scriptPatches.filter((entry) => !known.has(entry.code)).map((entry) => entry.code);
820
+ if (missing.length > 0)
821
+ throw new ToolRefusalError({
822
+ status: "refused",
823
+ blocker: "form-button-absent",
824
+ buttonCodes: missing,
825
+ available: [...known],
826
+ message: `${tool}: 表单没有按钮 ${missing.join("、")}(buttonScripts 只改已有按钮的脚本);可用:${[...known].join("、") || "(无)"}。新建按钮用 buttons + script`,
827
+ });
828
+ }
805
829
  let final;
806
830
  if (mode === "replace") {
807
831
  final = incoming;
@@ -812,6 +836,27 @@ export function mergeFormOptionsWithButtons(existingOptions, input, tool) {
812
836
  byCode.set(String(button.code), { ...(byCode.get(String(button.code)) ?? {}), ...button });
813
837
  for (const code of removeCodes)
814
838
  byCode.delete(String(code));
839
+ for (const patch of scriptPatches) {
840
+ const current = byCode.get(patch.code);
841
+ if (!current)
842
+ continue;
843
+ let config = {};
844
+ const rawConfig = current.actionConfig;
845
+ if (typeof rawConfig === "string" && rawConfig.trim() !== "") {
846
+ try {
847
+ const parsed = JSON.parse(rawConfig);
848
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed))
849
+ config = parsed;
850
+ }
851
+ catch {
852
+ config = {};
853
+ }
854
+ }
855
+ else if (rawConfig !== null && typeof rawConfig === "object" && !Array.isArray(rawConfig)) {
856
+ config = { ...rawConfig };
857
+ }
858
+ byCode.set(patch.code, { ...current, actionConfig: JSON.stringify({ ...config, scriptData: patch.script }) });
859
+ }
815
860
  final = [...byCode.values()].map((button, index) => ({ ...button, sortKey: typeof button.sortKey === "number" ? button.sortKey : index }));
816
861
  }
817
862
  if (removeCodes.length > 0 && mode === "replace")
@@ -1062,7 +1107,7 @@ export async function executeFormDraftWrite(session, args) {
1062
1107
  // turns them into h_biz_button rows (triggerType=FORM). Merge rather than
1063
1108
  // replace so unrelated options (presetButtonManage, sheetPresetButtonManage)
1064
1109
  // survive.
1065
- const mergedOptions = mergeFormOptionsWithButtons(pre?.options, { buttons: args.buttons, customButtonJson: args.customButtonJson, buttonsMode: args.buttonsMode, removeButtonCodes: args.removeButtonCodes }, "form.draft");
1110
+ const mergedOptions = mergeFormOptionsWithButtons(pre?.options, { buttons: args.buttons, customButtonJson: args.customButtonJson, buttonsMode: args.buttonsMode, removeButtonCodes: args.removeButtonCodes, buttonScripts: args.buttonScripts }, "form.draft");
1066
1111
  const payload = {
1067
1112
  ...(pre ?? {}),
1068
1113
  schemaCode: args.schemaCode,
@@ -1243,7 +1288,8 @@ export async function executeFormPublishWrite(session, args) {
1243
1288
  // drop the buttons (live 2026-10-23).
1244
1289
  const hasButtonDelta = args.buttons !== undefined ||
1245
1290
  args.customButtonJson !== undefined ||
1246
- (Array.isArray(args.removeButtonCodes) && args.removeButtonCodes.length > 0);
1291
+ (Array.isArray(args.removeButtonCodes) && args.removeButtonCodes.length > 0) ||
1292
+ (Array.isArray(args.buttonScripts) && args.buttonScripts.length > 0);
1247
1293
  const forcePromote = args.force === true || args.rebuild === true || hasButtonDelta;
1248
1294
  if (pre.published === true && !forcePromote)
1249
1295
  return { performed: { skipped: "already-published" }, readback: { published: true } };
@@ -1274,7 +1320,7 @@ export async function executeFormPublishWrite(session, args) {
1274
1320
  // h_biz_button(triggerType=FORM) rows during THIS publish call
1275
1321
  // (ModelManagementFacadeImpl.publishBizForm:1022). Passing them here is the
1276
1322
  // only way to create form buttons without the designer.
1277
- const mergedOptions = mergeFormOptionsWithButtons(pre.options, { buttons: args.buttons, customButtonJson: args.customButtonJson, buttonsMode: args.buttonsMode, removeButtonCodes: args.removeButtonCodes }, "form.publish");
1323
+ const mergedOptions = mergeFormOptionsWithButtons(pre.options, { buttons: args.buttons, customButtonJson: args.customButtonJson, buttonsMode: args.buttonsMode, removeButtonCodes: args.removeButtonCodes, buttonScripts: args.buttonScripts }, "form.publish");
1278
1324
  const payload = {
1279
1325
  ...pre,
1280
1326
  ...(mergedOptions !== undefined ? { options: mergedOptions } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stalfh233/omc-cli",
3
- "version": "0.4.77",
3
+ "version": "0.4.78",
4
4
  "description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,9 +50,9 @@
50
50
  "access": "public"
51
51
  },
52
52
  "dependencies": {
53
- "@stalfh233/omc-fake-platform": "0.4.77",
53
+ "@stalfh233/omc-fake-platform": "0.4.78",
54
54
  "commander": "14.0.3",
55
- "omc-core": "0.4.77",
55
+ "omc-core": "0.4.78",
56
56
  "proper-lockfile": "4.1.2",
57
57
  "write-file-atomic": "6.0.0",
58
58
  "zod": "^3.23.8",
@@ -99,6 +99,7 @@ only the section for the capability you need —
99
99
 
100
100
  - **能做哪些事**:表单 draft-first 布局、发布、`publishedHtmlJson` 绑定读回;把已发布字段绑进默认表单;**表单工具栏按钮**(`options.customButtonJson` → publish 生成 FORM 按钮)与按钮执行。
101
101
  - **工具**:`form.get`、`form.draft`、`form.publish`、`configure_form`、`form.runButton`
102
+ - **按钮脚本(JS)读写**:`form.get --buttonScripts` 回 `scriptData`/sha;写用 `form.publish --buttonScripts [{code,script}]`(只改已有按钮脚本),或在 `buttons[]` 里给 `script`。**视图(列表)本身没有 JS 面**(`h_biz_query` 无脚本列);脚本只有三处:表单在线JS、按钮JS、自定义页面 JS。
102
103
  - **recipe**:`omc describe configure_form --example publish-default-form`
103
104
  - **边界 / 陷阱**:默认表单已存在,用 `configure_form`/`form.draft(rebuild)`/`form.publish(rebuild:true)` 改它,不要新建;改字段形状后刷新表单一调用 `form.publish rebuild:true`。
104
105
  - **每个模型交付前其默认表单必须已发布**:只建草稿=用户无法录入/查看。用 `configure_form` 或 `form.draft(rebuild:true)`+`form.publish` 发布。
@@ -110,6 +111,7 @@ only the section for the capability you need —
110
111
 
111
112
  - **能做哪些事**:视图表头列、排序、筛选条件生命周期(create/configure/delete)。
112
113
  - **工具**:`listview.get`、`listview.list`、`listview.create`、`listview.configure`、`listview.delete`
114
+ - **按钮脚本(JS)读写**:`listview.get --buttonCode <code>`(或 `--buttons`)直接回 `scriptData`/`scriptBytes`/`scriptSha256`,不用翻 artifact;写用 `listview.configure --buttonScripts [{code,script}]` 只改脚本(保留其它字段),或在 `buttons[]` 里用 `script` 简写(= `actionConfig.scriptData`)。
113
115
  - **recipe**:`omc describe listview.configure --example configure-default-list`
114
116
  - **边界 / 陷阱**:默认视图已存在,`listview.configure` 省略 code 即配置默认视图。**按字段分组(queryLayer)未取证**——别猜写。
115
117