@dsh-workbench/core 1.1.1 → 1.1.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.
Files changed (32) hide show
  1. package/README.md +14 -5
  2. package/bin/dsh-workbench +7 -1
  3. package/client.js +678 -382
  4. package/dist/client/client.body.js +678 -382
  5. package/dist/client/index.js +545 -249
  6. package/dist/host/index.js +710 -380
  7. package/dist/types/app/layouts/approval-detail.d.ts +2 -1
  8. package/dist/types/app/layouts/market/discover-source-item.d.ts +26 -0
  9. package/dist/types/app/layouts/market/discover-sources-dialog.d.ts +4 -0
  10. package/dist/types/app/layouts/market/discover-view.d.ts +2 -0
  11. package/dist/types/app/layouts/market/use-discover.d.ts +3 -0
  12. package/dist/types/kernel/contract/market-index.d.ts +1 -1
  13. package/dist/types/modules/panels/client.d.ts +1 -1
  14. package/dist/types/modules/panels/model/approval.d.ts +4 -2
  15. package/dist/types/modules/panels/model/diy-publish.d.ts +10 -0
  16. package/dist/types/modules/panels/model/diy-tools.d.ts +0 -1
  17. package/dist/types/modules/panels/model/market-sources-act.d.ts +2 -1
  18. package/dist/types/modules/panels/model/market-sources-tool.d.ts +3 -0
  19. package/dist/types/modules/panels/model/market-sources-tool.test.d.ts +1 -0
  20. package/dist/types/modules/panels/model/market-tools.d.ts +8 -1
  21. package/dist/types/modules/panels/model/publish-claim.d.ts +2 -0
  22. package/dist/types/modules/panels/unit.d.ts +7 -5
  23. package/dist/types/shared/api/host/diy-publish-preview.d.ts +2 -0
  24. package/dist/types/shared/api/host/market-source-init.d.ts +1 -0
  25. package/dist/types/shared/api/host/market-sources-store.d.ts +2 -0
  26. package/dist/types/shared/hooks/market-queries.d.ts +1 -1
  27. package/package.json +3 -3
  28. package/skills/workbench-module-diy/FAMILY.md +3 -4
  29. package/skills/workbench-module-diy/SKILL.md +4 -1
  30. package/skills/workbench-module-diy/WIRING.md +9 -2
  31. package/dist/types/modules/panels/model/diy-changelog.d.ts +0 -2
  32. package/dist/types/shared/api/host/diy-changelog.d.ts +0 -13
@@ -4577,7 +4577,10 @@ var MARKET_INDEX_README_LINES = [
4577
4577
  "dsh-wb-module release --index <本仓库地址> --index-policy artifacts-in-repo",
4578
4578
  "```",
4579
4579
  "",
4580
- "产物会提交到 `packages/<unit>/`,并更新 `market.json`。",
4580
+ "产物会提交到 `packages/<包名>/`(带 scope 时是 `packages/@scope/名字/`),并更新 `market.json`。",
4581
+ "",
4582
+ "一个包名归第一次发布的人(`market.json` 里的 `owner`)。换一个署名发同名会被拒;要换人,先把这一条的 `owner` 改成新的人。",
4583
+ "同一个市场里,一个模块 id(`metadata.unit`)只能被一个条目使用;另一个包名想用同一个 id / 产物目录也会被拒。",
4581
4584
  "",
4582
4585
  "## 使用这个索引",
4583
4586
  "",
@@ -4683,15 +4686,25 @@ function iconProblem(icon) {
4683
4686
  if (/^https?:\/\//i.test(icon)) return void 0;
4684
4687
  return problem("icon", "icon 只能是 base64 data URL 或 http(s) URL");
4685
4688
  }
4689
+ var MAX_OWNER_CHARS = 200;
4690
+ var UNIT_ID = /^[a-z][a-z0-9-]*$/;
4691
+ function ownerProblem(owner) {
4692
+ if (/[\r\n]/u.test(owner)) return problem("owner", "owner 只能是一行署名");
4693
+ if (owner.length > MAX_OWNER_CHARS) return problem("owner", "owner 超过 200 字");
4694
+ }
4686
4695
  function metadataOf(raw) {
4687
4696
  const record = asRecord$3(raw);
4688
4697
  if (record === void 0) return void 0;
4698
+ const workbench = record["workbench"] === true;
4699
+ const unit = asText$3(record["unit"]);
4689
4700
  const category = asText$3(record["category"]);
4690
4701
  const labels = asTextArray(record["labels"]);
4691
4702
  const keywords = asTextArray(record["keywords"]);
4692
4703
  const sortKeysRecord = asRecord$3(record["sortKeys"]);
4693
4704
  const sortKeys = sortKeysRecord === void 0 ? void 0 : Object.fromEntries(Object.entries(sortKeysRecord).flatMap(([key, value]) => typeof value === "string" || typeof value === "number" ? [[key, value]] : []));
4694
4705
  return {
4706
+ ...workbench ? { workbench: true } : {},
4707
+ ...unit === "" ? {} : { unit },
4695
4708
  ...category === "" ? {} : { category },
4696
4709
  ...labels.length === 0 ? {} : { labels },
4697
4710
  ...keywords.length === 0 ? {} : { keywords },
@@ -4713,14 +4726,21 @@ function entryOf$1(raw, at) {
4713
4726
  const title = asLocalized(record["title"]);
4714
4727
  const description = asLocalized(record["description"]);
4715
4728
  const author = asText$3(record["author"]);
4729
+ const owner = asText$3(record["owner"]);
4730
+ if (owner !== "") {
4731
+ const badOwner = ownerProblem(owner);
4732
+ if (badOwner !== void 0) return badOwner;
4733
+ }
4716
4734
  const homepage = asText$3(record["homepage"]);
4717
4735
  const metadata = metadataOf(record["metadata"]);
4736
+ if (metadata?.unit !== void 0 && !UNIT_ID.test(metadata.unit)) return problem("unit", `metadata.unit 只能是小写字母开头的 a-z0-9-(现在是 ${JSON.stringify(metadata.unit)})`, at);
4718
4737
  return {
4719
4738
  name,
4720
4739
  versions,
4721
4740
  ...title === void 0 ? {} : { title },
4722
4741
  ...description === void 0 ? {} : { description },
4723
4742
  ...author === "" ? {} : { author },
4743
+ ...owner === "" ? {} : { owner },
4724
4744
  ...homepage === "" ? {} : { homepage },
4725
4745
  ...icon === "" ? {} : { icon },
4726
4746
  ...metadata === void 0 ? {} : { metadata }
@@ -4907,6 +4927,84 @@ function gitUrlOk(url) {
4907
4927
  return true;
4908
4928
  }
4909
4929
  //#endregion
4930
+ //#region ../shared/dist/host/market-index-claim.js
4931
+ function foldedName(name) {
4932
+ return name.replace(/\s+/gu, " ").trim().toLowerCase();
4933
+ }
4934
+ function publisherIdentityOf(text) {
4935
+ const raw = (text ?? "").trim();
4936
+ if (raw === "") return void 0;
4937
+ const match = /^(.*?)\s*<([^<>]*)>\s*$/u.exec(raw);
4938
+ const name = (match === null ? raw : match[1]).trim();
4939
+ const email = match === null ? "" : match[2].trim().toLowerCase();
4940
+ if (name === "") return void 0;
4941
+ return email === "" ? { name } : {
4942
+ name,
4943
+ email
4944
+ };
4945
+ }
4946
+ function samePublisher(left, right) {
4947
+ const a = publisherIdentityOf(left);
4948
+ const b = publisherIdentityOf(right);
4949
+ if (a === void 0 || b === void 0) return false;
4950
+ if (a.email !== void 0 && b.email !== void 0) return a.email === b.email;
4951
+ return foldedName(a.name) === foldedName(b.name);
4952
+ }
4953
+ function entryOwnerOf(entry) {
4954
+ const owner = (entry.owner ?? "").trim();
4955
+ if (owner !== "") return owner;
4956
+ const author = (entry.author ?? "").trim();
4957
+ return author === "" ? void 0 : author;
4958
+ }
4959
+ function claimOfEntry(entry, publisher) {
4960
+ if (entry === void 0) return { state: "new" };
4961
+ const owner = entryOwnerOf(entry);
4962
+ if (owner === void 0) return { state: "unowned" };
4963
+ if (publisher === void 0 || publisher.trim() === "") return { state: "unknown" };
4964
+ return samePublisher(owner, publisher) ? {
4965
+ state: "owned",
4966
+ owner
4967
+ } : {
4968
+ state: "conflict",
4969
+ owner
4970
+ };
4971
+ }
4972
+ function marketArtifactPath(name) {
4973
+ return `packages/${name}`;
4974
+ }
4975
+ function entryUnitOf(entry) {
4976
+ const unit = entry.metadata?.unit?.trim();
4977
+ if (unit !== void 0 && unit !== "") return unit;
4978
+ for (const version of entry.versions) {
4979
+ if (version.source.type !== "git") continue;
4980
+ const legacy = /^packages\/([^/]+)$/u.exec(version.source.path ?? "")?.[1];
4981
+ if (legacy !== void 0) return legacy;
4982
+ }
4983
+ }
4984
+ function claimOf(index, input) {
4985
+ const base = claimOfEntry(index.plugins.find((one) => one.name === input.name), input.publisher);
4986
+ if (base.state === "conflict" || base.state === "unknown") return base;
4987
+ const unit = (input.unit ?? "").trim();
4988
+ if (unit !== "") for (const other of index.plugins) {
4989
+ if (other.name === input.name) continue;
4990
+ if (entryUnitOf(other) !== unit) continue;
4991
+ return {
4992
+ state: "unit-taken",
4993
+ entry: other.name
4994
+ };
4995
+ }
4996
+ const path = (input.artifactPath ?? "").trim();
4997
+ if (path !== "") for (const other of index.plugins) {
4998
+ if (other.name === input.name) continue;
4999
+ if (!other.versions.some((one) => one.source.type === "git" && (one.source.path ?? "") === path)) continue;
5000
+ return {
5001
+ state: "path-taken",
5002
+ entry: other.name
5003
+ };
5004
+ }
5005
+ return base;
5006
+ }
5007
+ //#endregion
4910
5008
  //#region ../shared/dist/host/changelog.js
4911
5009
  var CHANGELOG_FILE = "CHANGELOG.md";
4912
5010
  var CHANGELOG_KINDS = [
@@ -5064,6 +5162,25 @@ function appendChangelogEntry(text, options) {
5064
5162
  out.splice(at, 0, ...bullets);
5065
5163
  return `${normalizeWhitespace(out)}\n`;
5066
5164
  }
5165
+ function planChangelog(text, options) {
5166
+ const source = normalizeChangelogText(text);
5167
+ const version = changelogVersion(options.version);
5168
+ const section = changelogSectionOf(source, version);
5169
+ if (section !== void 0 && changelogHasEntry(source, version)) return {
5170
+ action: "kept",
5171
+ version,
5172
+ text: source,
5173
+ section
5174
+ };
5175
+ if (bulletsOf(options.note).length === 0) return void 0;
5176
+ const next = appendChangelogEntry(source, options);
5177
+ return {
5178
+ action: "written",
5179
+ version,
5180
+ text: next,
5181
+ section: changelogSectionOf(next, version) ?? ""
5182
+ };
5183
+ }
5067
5184
  function normalizeWhitespace(rows) {
5068
5185
  const out = [];
5069
5186
  for (const row of rows) {
@@ -5458,12 +5575,14 @@ var MODULES_UPDATE_DEPENDENCY_TOOL = "workbench_modules_update_dependency";
5458
5575
  var MODULES_UPDATE_PLUGIN_TOOL = "workbench_modules_update_plugin";
5459
5576
  var MODULES_UNINSTALL_PLUGIN_TOOL = "workbench_modules_uninstall_plugin";
5460
5577
  var MODULES_UPDATE_WORKBENCH_TOOL = "workbench_modules_update_workbench";
5578
+ var MARKET_SOURCES_TOOL = "workbench_market_sources";
5579
+ var MARKET_ENABLE_TOOL = "workbench_market_enable";
5580
+ var MARKET_DISABLE_TOOL = "workbench_market_disable";
5461
5581
  var DIY_STATUS_TOOL = "workbench_diy_status";
5462
5582
  var DIY_PANEL_TOOL = "workbench_diy_panel";
5463
5583
  var DIY_COMPONENTS_TOOL = "workbench_diy_components";
5464
5584
  var DIY_RECON_TOOL = "workbench_diy_recon";
5465
5585
  var DIY_SPEC_TOOL = "workbench_diy_spec";
5466
- var DIY_CHANGELOG_TOOL = "workbench_diy_changelog";
5467
5586
  var DIY_INSTALL_TOOL = "workbench_diy_install";
5468
5587
  var DIY_PUBLISH_TOOL = "workbench_diy_publish";
5469
5588
  var DIY_SHOW_TOOL = "workbench_diy_show";
@@ -5497,6 +5616,7 @@ var DIY_DEBUG_STOP_TOOL = "workbench_diy_debug_stop";
5497
5616
  var READ_TOOLS = [
5498
5617
  PANELS_LIST_TOOL,
5499
5618
  MODULES_LIST_TOOL,
5619
+ MARKET_SOURCES_TOOL,
5500
5620
  DIY_STATUS_TOOL,
5501
5621
  DIY_PANEL_TOOL,
5502
5622
  DIY_COMPONENTS_TOOL,
@@ -5538,7 +5658,8 @@ var WRITE_TOOLS = [
5538
5658
  MODULES_UPDATE_PLUGIN_TOOL,
5539
5659
  MODULES_UNINSTALL_PLUGIN_TOOL,
5540
5660
  MODULES_UPDATE_WORKBENCH_TOOL,
5541
- DIY_CHANGELOG_TOOL,
5661
+ MARKET_ENABLE_TOOL,
5662
+ MARKET_DISABLE_TOOL,
5542
5663
  DIY_INSTALL_TOOL,
5543
5664
  DIY_PUBLISH_TOOL,
5544
5665
  DIY_SHOW_TOOL,
@@ -5555,7 +5676,6 @@ var DIY_TOOLS = [
5555
5676
  DIY_COMPONENTS_TOOL,
5556
5677
  DIY_RECON_TOOL,
5557
5678
  DIY_SPEC_TOOL,
5558
- DIY_CHANGELOG_TOOL,
5559
5679
  DIY_INSTALL_TOOL,
5560
5680
  DIY_PUBLISH_TOOL,
5561
5681
  DIY_SHOW_TOOL,
@@ -5832,6 +5952,14 @@ function readDomainsOf(design) {
5832
5952
  return [...found];
5833
5953
  }
5834
5954
  //#endregion
5955
+ //#region src/modules/panels/model/publish-claim.ts
5956
+ function publishClaimDenyOf(claim, name) {
5957
+ if (claim === void 0) return void 0;
5958
+ if (claim.state === "unit-taken") return `市场里 ${claim.entry} 已经用了这个模块 id —— 两个模块不能共用一个,装在一起会撞。给这个模块换一个 unit(dsh.workbench.unit)再发。`;
5959
+ if (claim.state === "path-taken") return `市场仓库里的产物目录已经被 ${claim.entry} 占了 —— 换一个包名再发。`;
5960
+ if (claim.state === "conflict") return `市场里 ${name} 已经有主了(${claim.owner})—— 同一个名字不能换人发。换一个带团队 scope 的包名;你就是原来的作者(只是换了机器或署名),用 author 参数带上原来的署名;确实是接手,让索引维护者改 market.json 里这一条的 owner。`;
5961
+ }
5962
+ //#endregion
5835
5963
  //#region src/modules/panels/model/approval.ts
5836
5964
  var SPEC_LIMIT = 80;
5837
5965
  var TARGET_LIMIT = 20;
@@ -5938,11 +6066,22 @@ function publishVerdict(call, preview) {
5938
6066
  if (raw["verify"] === true || raw["target"] === "local") return { kind: "allow" };
5939
6067
  const refresh = raw["refresh"] === true;
5940
6068
  const unit = textOf$3(raw, "unit");
5941
- const found = unit === "" ? null : preview?.(unit) ?? null;
6069
+ const askedAuthor = textOf$3(raw, "author");
6070
+ const found = unit === "" ? null : preview?.(unit, askedAuthor === "" ? void 0 : askedAuthor) ?? null;
5942
6071
  const asked = textOf$3(raw, "target");
5943
6072
  const target = refresh ? "market" : asked === "" ? found?.market === void 0 ? found?.registry === void 0 ? "tarball" : "team" : "market" : asked;
6073
+ const source = textOf$3(raw, "source");
6074
+ const claimReason = publishClaimDenyOf(found?.claim, found?.name ?? unit);
6075
+ if (source === "" && target !== "tarball" && target !== "local" && claimReason !== void 0) return {
6076
+ kind: "deny",
6077
+ reason: claimReason
6078
+ };
5944
6079
  const label = unit === "" ? "工作台模块" : `工作台模块 [${unit}]`;
5945
6080
  const labelEn = unit === "" ? "the workbench module" : `the workbench module [${unit}]`;
6081
+ if (!refresh && found?.changelog?.missing === true && textOf$3(raw, "changelogNote") === "") return {
6082
+ kind: "deny",
6083
+ reason: `这一版还没有更新日志(${found.changelog.version})—— 带上 changelogNote(一行一条)再调,发布时会一起写进 CHANGELOG.md 并随包发`
6084
+ };
5946
6085
  const copies = {
5947
6086
  team: {
5948
6087
  zh: `把${label}发到团队源,只发构建产物,不发源码,允许吗?`,
@@ -5974,7 +6113,7 @@ function publishVerdict(call, preview) {
5974
6113
  const question = refresh ? pair(`只刷新${label}在市集里的元数据(标题 / 作者 / 说明),不升版本、不重发产物,允许吗?`, `Only refresh ${labelEn}'s market metadata (title, author, description); no version bump, no artifacts. Allow?`) : pair(copy.zh, copy.en);
5975
6114
  return askOf(`${DIY_PUBLISH_TOOL} ${targetLabel}${refresh ? "(元数据)" : ""}`, question, question);
5976
6115
  }
5977
- var panelsApproval = (call) => {
6116
+ function approvalVerdict(call, marketSource) {
5978
6117
  if (call.name === "workbench_long_tasks_stop") return stopVerdict(call, void 0);
5979
6118
  if (call.name === "workbench_diy_publish") return publishVerdict(call);
5980
6119
  if (call.name === "workbench_diy_debug_start" || call.name === "workbench_diy_debug_resume") {
@@ -6045,9 +6184,41 @@ var panelsApproval = (call) => {
6045
6184
  return askOf(`${MODULES_UNINSTALL_PLUGIN_TOOL} ${plugin ?? ""} uninstalls an opencli plugin on this machine`, pair(`会卸载${target.zh},允许吗?`, `it uninstalls ${target.en}. Allow?`), pair(`agent 想卸载${target.zh},允许吗?`, `The agent wants to uninstall ${target.en}. Allow?`));
6046
6185
  }
6047
6186
  case MODULES_UPDATE_WORKBENCH_TOOL: return askOf(`${MODULES_UPDATE_WORKBENCH_TOOL} replaces the workbench itself with the new version from the registry`, pair("会把工作台本身更新到源上的新版,允许吗?", "it updates the workbench itself to the new version from the registry. Allow?"), pair("agent 想把工作台本身更新到源上的新版,允许吗?", "The agent wants to update the workbench itself from the registry. Allow?"));
6187
+ case MARKET_ENABLE_TOOL: {
6188
+ const id = text("id");
6189
+ const source = marketSource?.(id);
6190
+ if (marketSource !== void 0 && source === void 0) return {
6191
+ kind: "deny",
6192
+ reason: id === void 0 ? "这台机器上还没有选中的索引源 —— 先在「模块市场 → 发现」里添加并选中一个" : `没有这个索引源:${id}`
6193
+ };
6194
+ if (source !== void 0 && source.policy === "artifacts-in-repo") return { kind: "allow" };
6195
+ if (source !== void 0 && source.kind === "http") return {
6196
+ kind: "deny",
6197
+ reason: "http 索引源只能读,不能当发布目的地"
6198
+ };
6199
+ const where = source === void 0 ? {
6200
+ zh: id === void 0 ? "工作台当前选中的索引源" : `工作台的索引源「${id}」`,
6201
+ en: id === void 0 ? "the selected workbench index source" : `the workbench index source "${id}"`
6202
+ } : {
6203
+ zh: `工作台的索引源「${source.label}」`,
6204
+ en: `the workbench index source "${source.label}"`
6205
+ };
6206
+ return askOf(`${MARKET_ENABLE_TOOL}${source === void 0 ? "" : ` ${source.url}`} marks a market index source as a publish destination on this machine`, pair(`把${where.zh}设为可发布,允许吗?`, `Make ${where.en} a publish destination. Allow?`), pair(`会把${where.zh}设为可发布,只改本机配置,不碰仓库,允许吗?`, `It makes ${where.en} a publish destination on this machine, without touching the repository. Allow?`));
6207
+ }
6208
+ case MARKET_DISABLE_TOOL: {
6209
+ const id = text("id");
6210
+ const source = marketSource?.(id);
6211
+ if (marketSource !== void 0 && source === void 0) return {
6212
+ kind: "deny",
6213
+ reason: id === void 0 ? "这台机器上还没有选中的索引源 —— 先在「模块市场 → 发现」里添加并选中一个" : `没有这个索引源:${id}`
6214
+ };
6215
+ if (source === void 0) return askOf(`${MARKET_DISABLE_TOOL} stops using a market index source as a publish destination on this machine`, pair("把工作台当前选中的索引源改回只读,允许吗?", "Make the selected workbench index source read-only again. Allow?"), pair("会把工作台当前选中的索引源改回只读,只改本机配置,不碰仓库,允许吗?", "It makes the selected workbench index source read-only again on this machine, without touching the repository. Allow?"));
6216
+ if (source.kind !== "git" || source.policy !== "artifacts-in-repo") return { kind: "allow" };
6217
+ return askOf(`${MARKET_DISABLE_TOOL} ${source.url} stops using a market index source as a publish destination on this machine`, pair(`把工作台的索引源「${source.label}」改回只读,允许吗?`, `Make the workbench index source "${source.label}" read-only again. Allow?`), pair(`会把工作台的索引源「${source.label}」改回只读,只改本机配置,不碰仓库,允许吗?`, `It makes the workbench index source "${source.label}" read-only again on this machine, without touching the repository. Allow?`));
6218
+ }
6048
6219
  default: return;
6049
6220
  }
6050
- };
6221
+ }
6051
6222
  function recordHeldVerdict(call, recording) {
6052
6223
  if (call.name !== "workbench_diy_record_prepare" || recording === void 0) return void 0;
6053
6224
  const held = recording();
@@ -6057,11 +6228,16 @@ function recordHeldVerdict(call, recording) {
6057
6228
  reason: recordRefusalText(held.sessionId === call.session ? "already-held" : "held-by-other", held)
6058
6229
  };
6059
6230
  }
6060
- function panelsApprovalFor(longTaskRuns, recording, publishPreview) {
6231
+ function panelsApprovalFor(longTaskRuns, recording, publishPreview, marketSources) {
6232
+ const sourceOf = marketSources === void 0 ? void 0 : (id) => {
6233
+ const payload = marketSources.read();
6234
+ const wanted = id === void 0 || id === "" ? payload.selected : id;
6235
+ return wanted === null ? void 0 : payload.sources.find((one) => one.id === wanted);
6236
+ };
6061
6237
  return (call) => {
6062
6238
  if (call.name === "workbench_long_tasks_stop") return stopVerdict(call, longTaskRuns);
6063
6239
  if (call.name === "workbench_diy_publish") return publishVerdict(call, publishPreview);
6064
- return recordHeldVerdict(call, recording) ?? panelsApproval(call);
6240
+ return recordHeldVerdict(call, recording) ?? approvalVerdict(call, sourceOf);
6065
6241
  };
6066
6242
  }
6067
6243
  //#endregion
@@ -6978,7 +7154,7 @@ async function sessionWorkspaceOf(ctx, workspace) {
6978
7154
  dir = parent;
6979
7155
  }
6980
7156
  }
6981
- var OUTPUT$2 = {
7157
+ var OUTPUT$3 = {
6982
7158
  schema: {
6983
7159
  type: "object",
6984
7160
  properties: {
@@ -7123,7 +7299,7 @@ function componentsTool(ctx) {
7123
7299
  },
7124
7300
  additionalProperties: false
7125
7301
  },
7126
- output: OUTPUT$2,
7302
+ output: OUTPUT$3,
7127
7303
  async execute(raw, exec) {
7128
7304
  const { root } = await workspaceOf(ctx, exec);
7129
7305
  const args = asRecord$1(raw);
@@ -7221,97 +7397,6 @@ function registerComponentsTool(ctx, tools) {
7221
7397
  return [tools.register(componentsTool(ctx))];
7222
7398
  }
7223
7399
  //#endregion
7224
- //#region src/shared/api/host/diy-changelog.ts
7225
- function moduleVersionOf(dir) {
7226
- try {
7227
- const version = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")).version;
7228
- return typeof version === "string" ? version.trim() : "";
7229
- } catch {
7230
- return "";
7231
- }
7232
- }
7233
- function writeChangelogEntry(dir, input) {
7234
- const file = join(dir, CHANGELOG_FILE);
7235
- const text = appendChangelogEntry(existsSync(file) ? readFileSync(file, "utf8") : changelogSkeleton(), {
7236
- version: input.version,
7237
- kind: input.kind,
7238
- note: input.note,
7239
- date: input.date ?? changelogDate()
7240
- });
7241
- writeFileSync(file, text);
7242
- return {
7243
- file,
7244
- section: changelogSectionOf(text, input.version) ?? "",
7245
- text
7246
- };
7247
- }
7248
- //#endregion
7249
- //#region src/modules/panels/model/diy-changelog.ts
7250
- function toolOf(ctx) {
7251
- return {
7252
- name: DIY_CHANGELOG_TOOL,
7253
- description: `发布前把这一版写进 ${CHANGELOG_FILE}(\`## v0.1.0\` + \`### ✨ 新增\` 这种小节):传 note 就记一条,文件不在就先建。没写这一版时发布会被拦下。`,
7254
- parameters: {
7255
- type: "object",
7256
- properties: {
7257
- unit: {
7258
- type: "string",
7259
- description: "模块 id(workbench_diy_status 会给)。"
7260
- },
7261
- version: {
7262
- type: "string",
7263
- description: "要写的版本;不填用 package.json 里的 version。"
7264
- },
7265
- type: {
7266
- type: "string",
7267
- enum: [...CHANGELOG_KINDS],
7268
- description: "分类:added 新增 / changed 改进 / fixed 修复 / removed 移除 / security 安全 / deprecated 弃用。"
7269
- },
7270
- note: {
7271
- type: "string",
7272
- description: "这一版改了什么;多行时一行一条(必填)。"
7273
- }
7274
- },
7275
- required: ["unit", "note"],
7276
- additionalProperties: false
7277
- },
7278
- output: OUTPUT$2,
7279
- async execute(raw, exec) {
7280
- const { root } = await workspaceOf(ctx, exec);
7281
- const args = asRecord$1(raw);
7282
- const unit = asText(args["unit"]);
7283
- const { units } = unitsOf(ctx, root);
7284
- if (unit === "") throw new Error(`要一个 unit(这个工作区里:${units.map((one) => one.unit).join(", ") || "(空)"})`);
7285
- const found = units.find((one) => one.unit === unit && one.dir !== "");
7286
- if (found === void 0) throw new Error(`这个工作区里没有单元 id 为「${unit}」的模块(可用的:${units.map((one) => one.unit).join(", ") || "(空)"})`);
7287
- if (!insideRoot(root, found.dir)) throw new Error(`这个模块不在这个工作区里(${found.dir})`);
7288
- const note = asText(args["note"]).trim();
7289
- if (note === "") throw new Error("要一句 note:这一版改了什么");
7290
- const version = (asText(args["version"]) || moduleVersionOf(found.dir)).replace(/^v/, "");
7291
- if (!isValidVersion(version)) throw new Error(`版本要写成 x.y.z(收到「${version}」)—— 也可以不填,用 package.json 里的`);
7292
- const kind = asText(args["type"]) || "changed";
7293
- if (!CHANGELOG_KINDS.includes(kind)) throw new Error(`type 只认 ${CHANGELOG_KINDS.join(" / ")}(收到「${kind}」)`);
7294
- const written = writeChangelogEntry(found.dir, {
7295
- version,
7296
- kind,
7297
- note
7298
- });
7299
- return plainJson({
7300
- ok: true,
7301
- unit,
7302
- version,
7303
- type: kind,
7304
- path: written.file,
7305
- section: written.section,
7306
- text: `${CHANGELOG_FILE} 已记下 ${version}(${kind})。发布时会带进包和市场索引,审批屏会展示它。`
7307
- });
7308
- }
7309
- };
7310
- }
7311
- function registerChangelogTool(ctx, tools) {
7312
- return [tools.register(toolOf(ctx))];
7313
- }
7314
- //#endregion
7315
7400
  //#region src/shared/api/host/diy-artifacts.ts
7316
7401
  function artifactsDirOf(ctx, root) {
7317
7402
  const homePath = ctx.get("dshHomePath");
@@ -7598,7 +7683,7 @@ function registerDebugTools(ctx, tools, store) {
7598
7683
  required: [],
7599
7684
  additionalProperties: false
7600
7685
  },
7601
- output: OUTPUT$2,
7686
+ output: OUTPUT$3,
7602
7687
  async execute(raw, exec) {
7603
7688
  const { root, title } = await workspaceOf(ctx, exec);
7604
7689
  const { units } = unitsOf(ctx, root);
@@ -7633,7 +7718,7 @@ function registerDebugTools(ctx, tools, store) {
7633
7718
  name: DIY_DEBUG_PAUSE_TOOL,
7634
7719
  description: "暂停调试(不审批):屏工具停用,不再拿屏当目标。要接着看用 debug_resume。",
7635
7720
  parameters: EMPTY,
7636
- output: OUTPUT$2,
7721
+ output: OUTPUT$3,
7637
7722
  async execute(_raw, exec) {
7638
7723
  const { root } = await workspaceOf(ctx, exec);
7639
7724
  const held = store.live(Date.now());
@@ -7651,7 +7736,7 @@ function registerDebugTools(ctx, tools, store) {
7651
7736
  name: DIY_DEBUG_RESUME_TOOL,
7652
7737
  description: "继续调试(要点头一次):请用户回到这一场调试所在的那一屏。",
7653
7738
  parameters: EMPTY,
7654
- output: OUTPUT$2,
7739
+ output: OUTPUT$3,
7655
7740
  async execute(_raw, exec) {
7656
7741
  const { root } = await workspaceOf(ctx, exec);
7657
7742
  const held = store.live(Date.now());
@@ -7675,7 +7760,7 @@ function registerDebugTools(ctx, tools, store) {
7675
7760
  name: DIY_DEBUG_STOP_TOOL,
7676
7761
  description: "结束调试(不审批):清掉\"占用用户那一屏\"这件事。收工用它。",
7677
7762
  parameters: EMPTY,
7678
- output: OUTPUT$2,
7763
+ output: OUTPUT$3,
7679
7764
  async execute(_raw, exec) {
7680
7765
  const { root } = await workspaceOf(ctx, exec);
7681
7766
  const held = store.live(Date.now());
@@ -7865,7 +7950,7 @@ function registerLogTools(ctx, tools) {
7865
7950
  name: DIY_LOGS_WATCH_TOOL,
7866
7951
  description: "把这一页的控制台听上(每页最多 200 条、单条截 400 字,随页面刷新消失)。",
7867
7952
  parameters: schema,
7868
- output: OUTPUT$2,
7953
+ output: OUTPUT$3,
7869
7954
  async execute(raw, exec) {
7870
7955
  const { page, step } = await run(exec, raw, LOGS_ARM_SCRIPT);
7871
7956
  const error = actErrorOf(step);
@@ -7885,7 +7970,7 @@ function registerLogTools(ctx, tools) {
7885
7970
  name: DIY_LOGS_READ_TOOL,
7886
7971
  description: "读刚才听下来的控制台(最近 50 条;没装钩子会说清,不假装是\"没有报错\")。",
7887
7972
  parameters: schema,
7888
- output: OUTPUT$2,
7973
+ output: OUTPUT$3,
7889
7974
  async execute(raw, exec) {
7890
7975
  const { page, step } = await run(exec, raw, LOGS_READ_SCRIPT);
7891
7976
  const error = actErrorOf(step);
@@ -7904,7 +7989,7 @@ function registerLogTools(ctx, tools) {
7904
7989
  name: DIY_LOGS_STOP_TOOL,
7905
7990
  description: "控制台收听收工:卸掉钩子、扔掉攒下的记录。",
7906
7991
  parameters: schema,
7907
- output: OUTPUT$2,
7992
+ output: OUTPUT$3,
7908
7993
  async execute(raw, exec) {
7909
7994
  const { page, step } = await run(exec, raw, LOGS_STOP_SCRIPT);
7910
7995
  const error = actErrorOf(step);
@@ -8570,7 +8655,7 @@ function registerBrowserTools(ctx, tools) {
8570
8655
  name: row.tool,
8571
8656
  description: row.description,
8572
8657
  parameters: parametersOf(row),
8573
- output: OUTPUT$2,
8658
+ output: OUTPUT$3,
8574
8659
  execute: async (raw, exec) => plainJson(await runBrowserTool(row, ctx, raw, exec))
8575
8660
  };
8576
8661
  return tools.register(definition);
@@ -8609,7 +8694,7 @@ function specTool(ctx) {
8609
8694
  required: ["command"],
8610
8695
  additionalProperties: false
8611
8696
  },
8612
- output: OUTPUT$2,
8697
+ output: OUTPUT$3,
8613
8698
  async execute(raw, _exec) {
8614
8699
  const args = asRecord$1(raw);
8615
8700
  const argv = specArgvOf(asText(args["command"]));
@@ -8636,6 +8721,176 @@ function registerSpecTool(ctx, tools) {
8636
8721
  return [tools.register(specTool(ctx))];
8637
8722
  }
8638
8723
  //#endregion
8724
+ //#region src/modules/panels/model/diy-publish.ts
8725
+ var PUBLISH_TIMEOUT_MS = 18e5;
8726
+ function registerPublishTool(ctx, tools, deps) {
8727
+ const publishTool = {
8728
+ name: DIY_PUBLISH_TOOL,
8729
+ description: "用户说“发布”后才用:把这一格发到 target 指定的出口(不填按环境默认)。这一版还没有更新日志时把 changelogNote 一起带上 —— 发布时写进 CHANGELOG.md,随包发。",
8730
+ parameters: {
8731
+ type: "object",
8732
+ properties: {
8733
+ unit: {
8734
+ type: "string",
8735
+ description: "模块 id(workbench_diy_status 会给)。"
8736
+ },
8737
+ changelogNote: {
8738
+ type: "string",
8739
+ description: "这一版改了什么,一行一条;这一版还没有更新日志时必填(文件不在会先建)。"
8740
+ },
8741
+ changelogType: {
8742
+ type: "string",
8743
+ enum: [...CHANGELOG_KINDS],
8744
+ description: "分类,不填按 changed。"
8745
+ },
8746
+ target: {
8747
+ type: "string",
8748
+ enum: [
8749
+ "team",
8750
+ "public",
8751
+ "tarball",
8752
+ "git",
8753
+ "market",
8754
+ "local"
8755
+ ],
8756
+ description: "发到哪:团队源 / 公共 npm / 安装包 / git / 模块市场索引;不填按环境默认(有可写索引走市场,有私源走团队源,否则安装包)。"
8757
+ },
8758
+ version: {
8759
+ type: "string",
8760
+ description: "可选:patch / minor / major / x.y.z;不填首次用当前版本,之后自动 patch。"
8761
+ },
8762
+ source: {
8763
+ type: "string",
8764
+ description: "发布到哪个索引源(workbench_market_sources 会给 id);不填用唯一可发布的源。"
8765
+ },
8766
+ verify: {
8767
+ type: "boolean",
8768
+ description: "只核已发布的包(交接验证码后用),不再发;不用审批。"
8769
+ },
8770
+ resume: {
8771
+ type: "boolean",
8772
+ description: "中断后接着发(用记录里的出口与版本)。"
8773
+ },
8774
+ license: {
8775
+ type: "string",
8776
+ description: "公开发布用的许可证(MIT / Apache-2.0 / 仓库已有 LICENSE)。"
8777
+ },
8778
+ author: {
8779
+ type: "string",
8780
+ description: "署谁的名(姓名,或 姓名 <邮箱>);用户点了名才传,不传按模块里写的,没写用推断的署名。"
8781
+ },
8782
+ refresh: {
8783
+ type: "boolean",
8784
+ description: "只刷市集元数据(标题/作者/说明),不升版本、不重发产物。"
8785
+ }
8786
+ },
8787
+ required: ["unit"],
8788
+ additionalProperties: false
8789
+ },
8790
+ output: OUTPUT$3,
8791
+ async execute(raw, exec) {
8792
+ const { root } = await workspaceOf(ctx, exec);
8793
+ const found = deps.resolve(root, raw);
8794
+ const shell = ctx.get("shell");
8795
+ if (shell === void 0) throw new Error("这台机器上没有 shell 服务,发布跑不起来");
8796
+ const bin = toolchainBinOf(root) ?? toolchainBinOf(found.dir);
8797
+ if (bin === null) throw new Error(`这个工作区里没有 dsh-wb-module(${join(root, "node_modules")} 下没有)—— 先 pnpm install`);
8798
+ const target = asText(asRecord$1(raw)["target"]);
8799
+ const source = asText(asRecord$1(raw)["source"]);
8800
+ const version = asText(asRecord$1(raw)["version"]);
8801
+ const license = asText(asRecord$1(raw)["license"]);
8802
+ const changelogNote = asText(asRecord$1(raw)["changelogNote"]);
8803
+ const changelogType = asText(asRecord$1(raw)["changelogType"]);
8804
+ let author = asText(asRecord$1(raw)["author"]);
8805
+ const verify = asRecord$1(raw)["verify"] === true;
8806
+ const resume = asRecord$1(raw)["resume"] === true;
8807
+ if (!verify && target !== "local" && author === "" && (deps.previewOf(found.unit)?.author ?? "") === "") author = await deps.inferredAuthorOf(found.unit) ?? "";
8808
+ const argv = [
8809
+ bin,
8810
+ "release",
8811
+ "--cwd",
8812
+ found.dir,
8813
+ "--json"
8814
+ ];
8815
+ if (verify) argv.push("--verify");
8816
+ else argv.push("--yes");
8817
+ if (resume) argv.push("--resume");
8818
+ if (target !== "") argv.push("--target", target);
8819
+ if (source !== "") argv.push("--index-source", source);
8820
+ if (version !== "") argv.push("--version", version);
8821
+ if (license !== "") argv.push("--license", license);
8822
+ if (author !== "") argv.push("--author", author);
8823
+ if (changelogNote !== "") argv.push("--changelog-note", changelogNote);
8824
+ if (changelogType !== "") argv.push("--changelog-type", changelogType);
8825
+ if (asRecord$1(raw)["refresh"] === true) argv.push("--refresh");
8826
+ const result = await runShell(shell, argv.map((one) => quoteShellArg(one)).join(" "), {
8827
+ workdir: root,
8828
+ timeoutMs: PUBLISH_TIMEOUT_MS
8829
+ });
8830
+ const payload = jsonOf(result.stdout);
8831
+ if (result.exitCode === 3 && payload !== void 0) {
8832
+ const needs = asRecord$1(payload["needsUser"]);
8833
+ const stepText = asText(needs["text"]);
8834
+ const command = asText(needs["command"]);
8835
+ return plainJson({
8836
+ ...payload,
8837
+ text: [
8838
+ `还差一步:${stepText}`,
8839
+ ...command === "" ? [] : [`命令:${command}`],
8840
+ "做完跟我说「好了」,我接着发。"
8841
+ ].join("\n")
8842
+ });
8843
+ }
8844
+ if (result.exitCode === 4 && payload !== void 0) {
8845
+ const resumeInfo = asRecord$1(payload["resume"]);
8846
+ return plainJson({
8847
+ ...payload,
8848
+ text: [`发布中断在第 ${asText(resumeInfo["phase"])} 步:${asText(payload["text"])}`, `再调一次 workbench_diy_publish(unit=${found.unit}, resume=true)就能接着发。`].join("\n")
8849
+ });
8850
+ }
8851
+ if (result.exitCode !== 0 || payload === void 0) {
8852
+ const tail = `${result.stderr}\n${result.stdout}`.trim().split("\n").slice(-6).join(" | ");
8853
+ throw new Error(`发布没走通(exit ${String(result.exitCode)})${tail === "" ? "" : `:${tail}`}`);
8854
+ }
8855
+ const name = asText(payload["name"]);
8856
+ const released = asText(payload["version"]);
8857
+ const registry = asText(payload["registry"]);
8858
+ const file = asText(payload["file"]);
8859
+ const install = asText(payload["install"]);
8860
+ const git = asRecord$1(payload["git"]);
8861
+ const targetOfPayload = asText(payload["target"]);
8862
+ const text = verify ? [
8863
+ `已核过 ${name}@${released}`,
8864
+ `安装:${install}`,
8865
+ "没有重新发布。"
8866
+ ].join("\n") : payload["refresh"] === true ? [payload["refreshed"] === true ? `已刷新 ${name} 的市集元数据(版本 ${released} 不变)` : `${name} 的市集元数据本来就是这样,没动`, `市场索引:${asText(asRecord$1(payload["market"])["url"])}`].join("\n") : targetOfPayload === "tarball" ? [
8867
+ `安装包在 ${file}`,
8868
+ `本机装:${install}`,
8869
+ "文件可以从对话里带走,发给同事。"
8870
+ ].join("\n") : targetOfPayload === "git" ? [
8871
+ `已推到 ${asText(git["url"])}(tag ${asText(git["tag"])})`,
8872
+ `安装:${install}`,
8873
+ "已核过 tag 里的产物。"
8874
+ ].join("\n") : targetOfPayload === "market" ? [
8875
+ `已发到市场索引 ${asText(asRecord$1(payload["market"])["url"])}`,
8876
+ `安装:${install}`,
8877
+ "已从索引仓库侧核过产物。"
8878
+ ].join("\n") : targetOfPayload === "local" ? "没发,留在本机。" : [
8879
+ `已发布 ${name}@${released} → ${registry}`,
8880
+ `同事安装:${install}`,
8881
+ "包内容已经从源上核过(dist/host.js / client.js / cordis.patch.yml 都在)。"
8882
+ ].join("\n");
8883
+ const changelog = asRecord$1(payload["changelog"]);
8884
+ const changelogLine = asText(changelog["action"]) === "written" ? `更新日志:已写进 ${CHANGELOG_FILE} 的 ${asText(changelog["version"])}` : asText(changelog["action"]) === "kept" ? `更新日志:这一版已有(${asText(changelog["version"])}),没动` : "";
8885
+ return plainJson({
8886
+ ...payload,
8887
+ text: changelogLine === "" ? text : `${text}\n${changelogLine}`
8888
+ });
8889
+ }
8890
+ };
8891
+ return tools.register(publishTool);
8892
+ }
8893
+ //#endregion
8639
8894
  //#region src/shared/api/host/diy-recon.ts
8640
8895
  var SITE_LIMIT = 6;
8641
8896
  var COMMAND_LIMIT = 12;
@@ -9076,7 +9331,7 @@ function renderStatus(root, lines, options = {}) {
9076
9331
  }
9077
9332
  function publishStatusOf(preview) {
9078
9333
  if (preview === null) return "";
9079
- return [preview.market === void 0 ? preview.registry === void 0 ? "发布出口:安装包(这个环境没配可写市场索引源,模块也没声明 publishConfig.registry)" : `发布出口:团队源 ${preview.registry}(package.json 的 publishConfig.registry)` : `发布出口:市场索引 ${preview.market.url}${preview.market.ref === void 0 ? "" : `(${preview.market.ref})`}—— 不填 target 时默认发这里;这一版会发 ${preview.market.nextVersion ?? preview.version}`, preview.changelog === void 0 ? void 0 : preview.changelog.missing ? `发布前置:CHANGELOG 还差 ${preview.changelog.version} 这一版 —— 先 workbench_diy_changelog(unit=…)` : `发布前置:CHANGELOG 里已有 ${preview.changelog.version}`].filter((one) => one !== void 0).join("\n");
9334
+ return [preview.market === void 0 ? preview.registry === void 0 ? "发布出口:安装包(这个环境没配可写市场索引源,模块也没声明 publishConfig.registry)" : `发布出口:团队源 ${preview.registry}(package.json 的 publishConfig.registry)` : `发布出口:市场索引 ${preview.market.url}${preview.market.ref === void 0 ? "" : `(${preview.market.ref})`}—— 不填 target 时默认发这里;这一版会发 ${preview.market.nextVersion ?? preview.version}`, preview.changelog === void 0 ? void 0 : preview.changelog.missing ? `发布前置:CHANGELOG 还差 ${preview.changelog.version} 这一版 —— 发布时带 changelogNote(会先建文件)` : `发布前置:CHANGELOG 里已有 ${preview.changelog.version}`].filter((one) => one !== void 0).join("\n");
9080
9335
  }
9081
9336
  function renderList(root, modules, shape = "bare") {
9082
9337
  if (modules.length === 0) return [
@@ -9091,7 +9346,6 @@ function renderList(root, modules, shape = "bare") {
9091
9346
  }
9092
9347
  //#endregion
9093
9348
  //#region src/modules/panels/model/diy-tools.ts
9094
- var PUBLISH_TIMEOUT_MS = 18e5;
9095
9349
  function registerDiyTools(ctx, market, panels, debug, previewOf = () => null, inferredAuthorOf = async () => void 0) {
9096
9350
  const supply = supplyPortOf(ctx);
9097
9351
  const tools = ctx.get("tools");
@@ -9111,7 +9365,7 @@ function registerDiyTools(ctx, market, panels, debug, previewOf = () => null, in
9111
9365
  required: [],
9112
9366
  additionalProperties: false
9113
9367
  },
9114
- output: OUTPUT$2,
9368
+ output: OUTPUT$3,
9115
9369
  async execute(raw, exec) {
9116
9370
  const { root } = await workspaceOf(ctx, exec);
9117
9371
  const { units } = unitsOf(ctx, root);
@@ -9154,7 +9408,7 @@ function registerDiyTools(ctx, market, panels, debug, previewOf = () => null, in
9154
9408
  } },
9155
9409
  additionalProperties: false
9156
9410
  },
9157
- output: OUTPUT$2,
9411
+ output: OUTPUT$3,
9158
9412
  async execute(raw, exec) {
9159
9413
  const { root } = await workspaceOf(ctx, exec);
9160
9414
  const { installed, units } = unitsOf(ctx, root);
@@ -9220,7 +9474,7 @@ function registerDiyTools(ctx, market, panels, debug, previewOf = () => null, in
9220
9474
  required: ["keyword"],
9221
9475
  additionalProperties: false
9222
9476
  },
9223
- output: OUTPUT$2,
9477
+ output: OUTPUT$3,
9224
9478
  async execute(raw, _exec) {
9225
9479
  const args = asRecord$1(raw);
9226
9480
  const keywords = keywordsOf(asText(args["keyword"]), asText(args["url"]));
@@ -9256,220 +9510,77 @@ function registerDiyTools(ctx, market, panels, debug, previewOf = () => null, in
9256
9510
  parameters: {
9257
9511
  type: "object",
9258
9512
  properties: { unit: {
9259
- type: "string",
9260
- description: "模块 id(workbench_diy_status 会给)。"
9261
- } },
9262
- required: ["unit"],
9263
- additionalProperties: false
9264
- },
9265
- output: OUTPUT$2,
9266
- async execute(raw, exec) {
9267
- const { root } = await workspaceOf(ctx, exec);
9268
- const { units } = unitsOf(ctx, root);
9269
- const found = unitAmong(units, raw, "workbench_diy_show");
9270
- if (!found.installed) throw new Error(`${found.unit} 还没装 —— 只上板的前提是它装过了。先用 workbench_diy_install(第一次)`);
9271
- const panel = showOrReason(panels, found.unit, root);
9272
- const text = panel.ok ? `面板上已经打开它了(${root})—— 验收就走这一张卡(你自己走一遍)` : `面板上还没打开:${panel.reason}。这就是它的下一步(workbench_diy_status 会给出同样的判断)`;
9273
- return plainJson({
9274
- workspace: root,
9275
- unit: found.unit,
9276
- path: found.dir,
9277
- panel,
9278
- text
9279
- });
9280
- }
9281
- };
9282
- const installTool = {
9283
- name: DIY_INSTALL_TOOL,
9284
- description: "把这个工作区里造好的模块装上并上到当前工作区的面板。这是持久变更,一个模块只装一次:之后改源码只需 dsh-wb-module build(宿主侧秒级热换)。",
9285
- parameters: {
9286
- type: "object",
9287
- properties: { unit: {
9288
- type: "string",
9289
- description: "模块 id(workbench_diy_status 会给)。"
9290
- } },
9291
- required: ["unit"],
9292
- additionalProperties: false
9293
- },
9294
- output: OUTPUT$2,
9295
- async execute(raw, exec) {
9296
- const { root } = await workspaceOf(ctx, exec);
9297
- const found = unitOf(root, raw, "workbench_diy_install");
9298
- if (found.installed) {
9299
- const panel = showOrReason(panels, found.unit, root);
9300
- const text = ["它已经装过了(就是这个工作区里这一份)—— **不要再装**:改源码 + dsh-wb-module build(宿主侧与 client.js 都秒级热换 —— dsh-client-hmr 盯着每个 bundle 的 lib/client.js,重建了就换进已打开的页面)。", panel.ok ? `面板上已经打开它了(${root})—— 验收就走这一张卡(你自己走一遍)` : `面板上还没打开:${panel.reason}。调 workbench_diy_show(unit=${found.unit})`].join("\n");
9301
- return plainJson({
9302
- workspace: root,
9303
- unit: found.unit,
9304
- path: found.dir,
9305
- alreadyInstalled: true,
9306
- panel,
9307
- text
9308
- });
9309
- }
9310
- const outcome = await market.installBundle(found.dir);
9311
- const verdict = verdictOf(outcome.change);
9312
- const restartRequired = outcome.change.application === "restart-required";
9313
- const panel = outcome.change.application === "applied" ? showOrReason(panels, found.unit, root) : {
9314
- ok: false,
9315
- reason: `安装结果是 ${outcome.change.application},还没生效`
9316
- };
9317
- const panelText = panel.ok ? `面板上已经打开它了(${root})—— 验收就走这一张卡(你自己走一遍)` : outcome.change.application === "applied" ? `面板上还没打开:${panel.reason}。调 workbench_diy_show(unit=${found.unit})试试。` : restartRequired ? `面板上还没打开(${panel.reason}):模块要**重启一次工作台**才注册进面板 —— 把这句话转给用户,重启后再调 workbench_diy_show(unit=${found.unit})。` : `面板上还没打开:${panel.reason}。先把上面那句"没装成"处理掉,再谈上板。`;
9318
- const text = [
9319
- `${verdict}:${found.unit}(${found.name})`,
9320
- panelText,
9321
- "之后只改源码 + dsh-wb-module build(宿主侧与 client.js 都秒级热换 —— dsh-client-hmr 盯着每个 bundle 的 lib/client.js,重建了就换进已打开的页面):不要再装一遍。"
9322
- ].join("\n");
9323
- return plainJson({
9324
- workspace: root,
9325
- unit: found.unit,
9326
- path: found.dir,
9327
- install: outcome.change,
9328
- module: outcome.module,
9329
- panel,
9330
- text
9331
- });
9332
- }
9333
- };
9334
- const publishTool = {
9335
- name: DIY_PUBLISH_TOOL,
9336
- description: "用户说“发布”后才用:把这一格发到 target 指定的出口(不填按环境默认)。",
9337
- parameters: {
9338
- type: "object",
9339
- properties: {
9340
- unit: {
9341
- type: "string",
9342
- description: "模块 id(workbench_diy_status 会给)。"
9343
- },
9344
- target: {
9345
- type: "string",
9346
- enum: [
9347
- "team",
9348
- "public",
9349
- "tarball",
9350
- "git",
9351
- "market",
9352
- "local"
9353
- ],
9354
- description: "发到哪:团队源 / 公共 npm / 安装包 / git / 模块市场索引;不填按环境默认(有可写索引走市场,有私源走团队源,否则安装包)。"
9355
- },
9356
- version: {
9357
- type: "string",
9358
- description: "可选:patch / minor / major / x.y.z;不填首次用当前版本,之后自动 patch。"
9359
- },
9360
- verify: {
9361
- type: "boolean",
9362
- description: "只核已发布的包(交接验证码后用),不再发;不用审批。"
9363
- },
9364
- resume: {
9365
- type: "boolean",
9366
- description: "中断后接着发(用记录里的出口与版本)。"
9367
- },
9368
- license: {
9369
- type: "string",
9370
- description: "公开发布用的许可证(MIT / Apache-2.0 / 仓库已有 LICENSE)。"
9371
- },
9372
- author: {
9373
- type: "string",
9374
- description: "署谁的名(姓名,或 姓名 <邮箱>);用户点了名才传,不传按模块里写的,没写用推断的署名。"
9375
- },
9376
- refresh: {
9377
- type: "boolean",
9378
- description: "只刷市集元数据(标题/作者/说明),不升版本、不重发产物。"
9379
- }
9380
- },
9513
+ type: "string",
9514
+ description: "模块 id(workbench_diy_status 会给)。"
9515
+ } },
9381
9516
  required: ["unit"],
9382
9517
  additionalProperties: false
9383
9518
  },
9384
- output: OUTPUT$2,
9519
+ output: OUTPUT$3,
9385
9520
  async execute(raw, exec) {
9386
9521
  const { root } = await workspaceOf(ctx, exec);
9387
- const found = unitOf(root, raw, "workbench_diy_publish");
9388
- const shell = ctx.get("shell");
9389
- if (shell === void 0) throw new Error("这台机器上没有 shell 服务,发布跑不起来");
9390
- const bin = toolchainBinOf(root) ?? toolchainBinOf(found.dir);
9391
- if (bin === null) throw new Error(`这个工作区里没有 dsh-wb-module(${join(root, "node_modules")} 下没有)—— 先 pnpm install`);
9392
- const target = asText(asRecord$1(raw)["target"]);
9393
- const version = asText(asRecord$1(raw)["version"]);
9394
- const license = asText(asRecord$1(raw)["license"]);
9395
- let author = asText(asRecord$1(raw)["author"]);
9396
- const verify = asRecord$1(raw)["verify"] === true;
9397
- const resume = asRecord$1(raw)["resume"] === true;
9398
- if (!verify && target !== "local" && author === "" && (previewOf(found.unit)?.author ?? "") === "") author = await inferredAuthorOf(found.unit) ?? "";
9399
- const argv = [
9400
- bin,
9401
- "release",
9402
- "--cwd",
9403
- found.dir,
9404
- "--json"
9405
- ];
9406
- if (verify) argv.push("--verify");
9407
- else argv.push("--yes");
9408
- if (resume) argv.push("--resume");
9409
- if (target !== "") argv.push("--target", target);
9410
- if (version !== "") argv.push("--version", version);
9411
- if (license !== "") argv.push("--license", license);
9412
- if (author !== "") argv.push("--author", author);
9413
- if (asRecord$1(raw)["refresh"] === true) argv.push("--refresh");
9414
- const result = await runShell(shell, argv.map((one) => quoteShellArg(one)).join(" "), {
9415
- workdir: root,
9416
- timeoutMs: PUBLISH_TIMEOUT_MS
9522
+ const { units } = unitsOf(ctx, root);
9523
+ const found = unitAmong(units, raw, "workbench_diy_show");
9524
+ if (!found.installed) throw new Error(`${found.unit} 还没装 —— 只上板的前提是它装过了。先用 workbench_diy_install(第一次)`);
9525
+ const panel = showOrReason(panels, found.unit, root);
9526
+ const text = panel.ok ? `面板上已经打开它了(${root})—— 验收就走这一张卡(你自己走一遍)` : `面板上还没打开:${panel.reason}。这就是它的下一步(workbench_diy_status 会给出同样的判断)`;
9527
+ return plainJson({
9528
+ workspace: root,
9529
+ unit: found.unit,
9530
+ path: found.dir,
9531
+ panel,
9532
+ text
9417
9533
  });
9418
- const payload = jsonOf(result.stdout);
9419
- if (result.exitCode === 3 && payload !== void 0) {
9420
- const needs = asRecord$1(payload["needsUser"]);
9421
- const stepText = asText(needs["text"]);
9422
- const command = asText(needs["command"]);
9423
- return plainJson({
9424
- ...payload,
9425
- text: [
9426
- `还差一步:${stepText}`,
9427
- ...command === "" ? [] : [`命令:${command}`],
9428
- "做完跟我说「好了」,我接着发。"
9429
- ].join("\n")
9430
- });
9431
- }
9432
- if (result.exitCode === 4 && payload !== void 0) {
9433
- const resumeInfo = asRecord$1(payload["resume"]);
9534
+ }
9535
+ };
9536
+ const installTool = {
9537
+ name: DIY_INSTALL_TOOL,
9538
+ description: "把这个工作区里造好的模块装上并上到当前工作区的面板。这是持久变更,一个模块只装一次:之后改源码只需 dsh-wb-module build(宿主侧秒级热换)。",
9539
+ parameters: {
9540
+ type: "object",
9541
+ properties: { unit: {
9542
+ type: "string",
9543
+ description: "模块 id(workbench_diy_status 会给)。"
9544
+ } },
9545
+ required: ["unit"],
9546
+ additionalProperties: false
9547
+ },
9548
+ output: OUTPUT$3,
9549
+ async execute(raw, exec) {
9550
+ const { root } = await workspaceOf(ctx, exec);
9551
+ const found = unitOf(root, raw, "workbench_diy_install");
9552
+ if (found.installed) {
9553
+ const panel = showOrReason(panels, found.unit, root);
9554
+ const text = ["它已经装过了(就是这个工作区里这一份)—— **不要再装**:改源码 + dsh-wb-module build(宿主侧与 client.js 都秒级热换 —— dsh-client-hmr 盯着每个 bundle 的 lib/client.js,重建了就换进已打开的页面)。", panel.ok ? `面板上已经打开它了(${root})—— 验收就走这一张卡(你自己走一遍)` : `面板上还没打开:${panel.reason}。调 workbench_diy_show(unit=${found.unit})`].join("\n");
9434
9555
  return plainJson({
9435
- ...payload,
9436
- text: [`发布中断在第 ${asText(resumeInfo["phase"])} 步:${asText(payload["text"])}`, `再调一次 workbench_diy_publish(unit=${found.unit}, resume=true)就能接着发。`].join("\n")
9556
+ workspace: root,
9557
+ unit: found.unit,
9558
+ path: found.dir,
9559
+ alreadyInstalled: true,
9560
+ panel,
9561
+ text
9437
9562
  });
9438
9563
  }
9439
- if (result.exitCode !== 0 || payload === void 0) {
9440
- const tail = `${result.stderr}\n${result.stdout}`.trim().split("\n").slice(-6).join(" | ");
9441
- throw new Error(`发布没走通(exit ${String(result.exitCode)})${tail === "" ? "" : `:${tail}`}`);
9442
- }
9443
- const name = asText(payload["name"]);
9444
- const released = asText(payload["version"]);
9445
- const registry = asText(payload["registry"]);
9446
- const file = asText(payload["file"]);
9447
- const install = asText(payload["install"]);
9448
- const git = asRecord$1(payload["git"]);
9449
- const targetOfPayload = asText(payload["target"]);
9450
- const text = verify ? [
9451
- `已核过 ${name}@${released}`,
9452
- `安装:${install}`,
9453
- "没有重新发布。"
9454
- ].join("\n") : payload["refresh"] === true ? [payload["refreshed"] === true ? `已刷新 ${name} 的市集元数据(版本 ${released} 不变)` : `${name} 的市集元数据本来就是这样,没动`, `市场索引:${asText(asRecord$1(payload["market"])["url"])}`].join("\n") : targetOfPayload === "tarball" ? [
9455
- `安装包在 ${file}`,
9456
- `本机装:${install}`,
9457
- "文件可以从对话里带走,发给同事。"
9458
- ].join("\n") : targetOfPayload === "git" ? [
9459
- `已推到 ${asText(git["url"])}(tag ${asText(git["tag"])})`,
9460
- `安装:${install}`,
9461
- "已核过 tag 里的产物。"
9462
- ].join("\n") : targetOfPayload === "market" ? [
9463
- `已发到市场索引 ${asText(asRecord$1(payload["market"])["url"])}`,
9464
- `安装:${install}`,
9465
- "已从索引仓库侧核过产物。"
9466
- ].join("\n") : targetOfPayload === "local" ? "没发,留在本机。" : [
9467
- `已发布 ${name}@${released} → ${registry}`,
9468
- `同事安装:${install}`,
9469
- "包内容已经从源上核过(dist/host.js / client.js / cordis.patch.yml 都在)。"
9564
+ const outcome = await market.installBundle(found.dir);
9565
+ const verdict = verdictOf(outcome.change);
9566
+ const restartRequired = outcome.change.application === "restart-required";
9567
+ const panel = outcome.change.application === "applied" ? showOrReason(panels, found.unit, root) : {
9568
+ ok: false,
9569
+ reason: `安装结果是 ${outcome.change.application},还没生效`
9570
+ };
9571
+ const panelText = panel.ok ? `面板上已经打开它了(${root})—— 验收就走这一张卡(你自己走一遍)` : outcome.change.application === "applied" ? `面板上还没打开:${panel.reason}。调 workbench_diy_show(unit=${found.unit})试试。` : restartRequired ? `面板上还没打开(${panel.reason}):模块要**重启一次工作台**才注册进面板 —— 把这句话转给用户,重启后再调 workbench_diy_show(unit=${found.unit})。` : `面板上还没打开:${panel.reason}。先把上面那句"没装成"处理掉,再谈上板。`;
9572
+ const text = [
9573
+ `${verdict}:${found.unit}(${found.name})`,
9574
+ panelText,
9575
+ "之后只改源码 + dsh-wb-module build(宿主侧与 client.js 都秒级热换 —— dsh-client-hmr 盯着每个 bundle 的 lib/client.js,重建了就换进已打开的页面):不要再装一遍。"
9470
9576
  ].join("\n");
9471
9577
  return plainJson({
9472
- ...payload,
9578
+ workspace: root,
9579
+ unit: found.unit,
9580
+ path: found.dir,
9581
+ install: outcome.change,
9582
+ module: outcome.module,
9583
+ panel,
9473
9584
  text
9474
9585
  });
9475
9586
  }
@@ -9480,13 +9591,16 @@ function registerDiyTools(ctx, market, panels, debug, previewOf = () => null, in
9480
9591
  ...registerComponentsTool(ctx, tools),
9481
9592
  tools.register(reconTool),
9482
9593
  ...registerSpecTool(ctx, tools),
9483
- ...registerChangelogTool(ctx, tools),
9484
9594
  ...registerLogTools(ctx, tools),
9485
9595
  ...registerBrowserTools(ctx, tools),
9486
9596
  ...registerDebugTools(ctx, tools, debug),
9487
9597
  tools.register(showTool),
9488
9598
  tools.register(installTool),
9489
- tools.register(publishTool)
9599
+ registerPublishTool(ctx, tools, {
9600
+ resolve: (root, raw) => unitOf(root, raw, "workbench_diy_publish"),
9601
+ previewOf,
9602
+ inferredAuthorOf
9603
+ })
9490
9604
  ];
9491
9605
  }
9492
9606
  //#endregion
@@ -9516,27 +9630,22 @@ function selectedGitSource() {
9516
9630
  return parsed.source.policy === "artifacts-in-repo" ? parsed.source : void 0;
9517
9631
  }
9518
9632
  }
9519
- function publishedEntryOf(source, name) {
9633
+ var EMPTY_INDEX = {
9634
+ format: 1,
9635
+ plugins: []
9636
+ };
9637
+ function publishedIndexOf(source) {
9520
9638
  const file = join(workbenchRoot(), "market-index", marketSourceKey(source), "worktree", source.path ?? "market.json");
9521
9639
  if (!existsSync(file)) return void 0;
9522
9640
  try {
9523
9641
  const parsed = parseMarketIndexJson(readFileSync(file, "utf8"));
9524
- if (!parsed.ok) return void 0;
9525
- const entry = parsed.index.plugins.find((one) => one.name === name);
9526
- if (entry === void 0) return {
9527
- versions: [],
9528
- author: ""
9529
- };
9530
- return {
9531
- versions: entry.versions.map((one) => one.version),
9532
- author: typeof entry.author === "string" ? entry.author.trim() : ""
9533
- };
9642
+ return parsed.ok ? parsed.index : void 0;
9534
9643
  } catch {
9535
9644
  return;
9536
9645
  }
9537
9646
  }
9538
- function publishedVersionsOf(source, name) {
9539
- return publishedEntryOf(source, name)?.versions;
9647
+ function publishedEntryOf(index, name) {
9648
+ return index?.plugins.find((one) => one.name === name);
9540
9649
  }
9541
9650
  function bumped(version, kind) {
9542
9651
  const match = SEMVER.exec(version);
@@ -9561,17 +9670,21 @@ function nextVersionOf(version, published) {
9561
9670
  if (!exists(candidate)) return candidate;
9562
9671
  }
9563
9672
  }
9564
- function marketOf(unit, dir, name, version) {
9673
+ function marketOf(dir, name, version) {
9565
9674
  const source = selectedGitSource();
9566
9675
  if (source === void 0) return void 0;
9567
- const nextVersion = nextVersionOf(version, publishedVersionsOf(source, name));
9676
+ const index = publishedIndexOf(source);
9677
+ const nextVersion = nextVersionOf(version, publishedEntryOf(index, name)?.versions.map((one) => one.version));
9568
9678
  return {
9569
- url: source.url,
9570
- ...source.ref === void 0 ? {} : { ref: source.ref },
9571
- indexPath: source.path ?? "market.json",
9572
- artifactPath: `packages/${unit}`,
9573
- files: ARTIFACTS.filter((one) => existsSync(join(dir, one))),
9574
- ...nextVersion === void 0 ? {} : { nextVersion }
9679
+ market: {
9680
+ url: source.url,
9681
+ ...source.ref === void 0 ? {} : { ref: source.ref },
9682
+ indexPath: source.path ?? "market.json",
9683
+ artifactPath: marketArtifactPath(name),
9684
+ files: ARTIFACTS.filter((one) => existsSync(join(dir, one))),
9685
+ ...nextVersion === void 0 ? {} : { nextVersion }
9686
+ },
9687
+ ...index === void 0 ? {} : { index }
9575
9688
  };
9576
9689
  }
9577
9690
  function moduleFactOf(root, unit) {
@@ -9630,7 +9743,7 @@ async function inferredAuthorOf(root, unit, shell) {
9630
9743
  if (fact.author !== "") return fact.author;
9631
9744
  const source = selectedGitSource();
9632
9745
  if (source !== void 0 && fact.name !== "") {
9633
- const published = publishedEntryOf(source, fact.name)?.author ?? "";
9746
+ const published = publishedEntryOf(publishedIndexOf(source), fact.name)?.author ?? "";
9634
9747
  if (published !== "") return published;
9635
9748
  }
9636
9749
  const sibling = root === null || root === void 0 ? void 0 : siblingAuthorOf(root, unit);
@@ -9656,17 +9769,24 @@ function publishPreviewOf(root, unit, options = {}) {
9656
9769
  if (name === "" || version === "") return null;
9657
9770
  const publishConfig = record["publishConfig"];
9658
9771
  const registry = typeof publishConfig === "object" && publishConfig !== null ? publishConfig["registry"] : void 0;
9659
- const market = marketOf(found.unit, found.dir, name, version);
9660
- const target = targetVersionOf(version, (options.version ?? "").trim(), market);
9772
+ const market = marketOf(found.dir, name, version);
9773
+ const target = targetVersionOf(version, (options.version ?? "").trim(), market?.market);
9661
9774
  const askedAuthor = (options.author ?? "").trim();
9662
9775
  const author = askedAuthor === "" ? authorOf(record) : askedAuthor;
9776
+ const claim = market === void 0 ? void 0 : claimOf(market.index ?? EMPTY_INDEX, {
9777
+ name,
9778
+ publisher: author,
9779
+ unit,
9780
+ artifactPath: marketArtifactPath(name)
9781
+ });
9663
9782
  return {
9664
9783
  name,
9665
9784
  version,
9666
9785
  ...author === "" ? {} : { author },
9667
9786
  ...typeof registry === "string" && registry !== "" ? { registry } : {},
9668
- ...market === void 0 ? {} : { market },
9669
- changelog: changelogOf(found.dir, target)
9787
+ ...market === void 0 ? {} : { market: market.market },
9788
+ ...claim === void 0 ? {} : { claim },
9789
+ changelog: changelogOf(found.dir, target, (options.changelog ?? "").trim(), kindOf(options.changelogType))
9670
9790
  };
9671
9791
  }
9672
9792
  function authorOf(manifest) {
@@ -9684,13 +9804,30 @@ function targetVersionOf(version, asked, market) {
9684
9804
  if (/^(major|minor|patch)$/.test(asked)) return bumped(version, asked) ?? version;
9685
9805
  return asked;
9686
9806
  }
9687
- function changelogOf(dir, version) {
9807
+ function kindOf(given) {
9808
+ const wanted = (given ?? "").trim();
9809
+ return CHANGELOG_KINDS.includes(wanted) ? wanted : "changed";
9810
+ }
9811
+ function changelogOf(dir, version, note, kind) {
9688
9812
  let text;
9689
9813
  try {
9690
9814
  text = readFileSync(join(dir, CHANGELOG_FILE), "utf8");
9691
9815
  } catch {
9692
9816
  text = void 0;
9693
9817
  }
9818
+ if (note !== "") {
9819
+ const plan = planChangelog(text ?? "", {
9820
+ version,
9821
+ kind,
9822
+ note,
9823
+ date: changelogDate()
9824
+ });
9825
+ if (plan !== void 0) return {
9826
+ version: plan.version,
9827
+ text: plan.section,
9828
+ ...plan.action === "written" ? { pending: true } : {}
9829
+ };
9830
+ }
9694
9831
  if (text === void 0 || !changelogHasEntry(text, version)) return {
9695
9832
  version,
9696
9833
  missing: true
@@ -11013,7 +11150,7 @@ function registerRecordTools(ctx, store) {
11013
11150
  required: ["url"],
11014
11151
  additionalProperties: false
11015
11152
  },
11016
- output: OUTPUT$2,
11153
+ output: OUTPUT$3,
11017
11154
  async execute(raw, exec) {
11018
11155
  const { root } = await workspaceOf(ctx, exec);
11019
11156
  const artifactsDir = artifactsDirOf(ctx, root);
@@ -11113,7 +11250,7 @@ function registerRecordTools(ctx, store) {
11113
11250
  } },
11114
11251
  additionalProperties: false
11115
11252
  },
11116
- output: OUTPUT$2,
11253
+ output: OUTPUT$3,
11117
11254
  async execute(raw, exec) {
11118
11255
  const shell = ctx.get("shell");
11119
11256
  if (shell === void 0) throw new Error("这台机器上没有 shell 服务,读不了录制");
@@ -11243,7 +11380,7 @@ function registerRecordTools(ctx, store) {
11243
11380
  },
11244
11381
  additionalProperties: false
11245
11382
  },
11246
- output: OUTPUT$2,
11383
+ output: OUTPUT$3,
11247
11384
  async execute(raw, exec) {
11248
11385
  const { root } = await workspaceOf(ctx, exec);
11249
11386
  const artifactsDir = artifactsDirOf(ctx, root);
@@ -11325,7 +11462,7 @@ function registerRecordTools(ctx, store) {
11325
11462
  properties: {},
11326
11463
  additionalProperties: false
11327
11464
  },
11328
- output: OUTPUT$2,
11465
+ output: OUTPUT$3,
11329
11466
  async execute(_raw, exec) {
11330
11467
  const shell = ctx.get("shell");
11331
11468
  if (shell === void 0) throw new Error("这台机器上没有 shell 服务,收不了工");
@@ -12566,6 +12703,7 @@ function normalizedRepo(value) {
12566
12703
  return value.trim().replace(/^git\+/, "").replace(/\.git$/, "").toLowerCase();
12567
12704
  }
12568
12705
  function isWorkbenchEntry(entry) {
12706
+ if (entry.metadata?.workbench === true) return true;
12569
12707
  const labels = marketEntryLabels(entry);
12570
12708
  if (labels.includes("workbench")) return true;
12571
12709
  return entry.metadata?.category === "workbench" && labels.length === 0;
@@ -12823,37 +12961,66 @@ function parseMarketSourcesAction(payload) {
12823
12961
  id: id.trim()
12824
12962
  };
12825
12963
  }
12964
+ if (action === "read-only") {
12965
+ const id = record["id"];
12966
+ if (typeof id !== "string" || id.trim() === "") throw new Error("read-only 需要 id");
12967
+ return {
12968
+ action,
12969
+ id: id.trim()
12970
+ };
12971
+ }
12826
12972
  if (action === "init") {
12827
12973
  const id = record["id"];
12828
12974
  if (typeof id !== "string" || id.trim() === "") throw new Error("init 需要 id");
12829
12975
  const ref = typeof record["ref"] === "string" ? record["ref"].trim() : "";
12830
12976
  if (ref !== "" && !marketBranchOk(ref)) throw new Error(`分支名不合格:${ref}`);
12977
+ const stampOnly = record["stampOnly"] === true;
12831
12978
  return {
12832
12979
  action,
12833
12980
  id: id.trim(),
12834
- ...ref === "" ? {} : { ref }
12981
+ ...ref === "" ? {} : { ref },
12982
+ ...stampOnly ? { stampOnly } : {}
12835
12983
  };
12836
12984
  }
12837
12985
  throw new Error(`不认识的索引源动作:${String(action)}`);
12838
12986
  }
12987
+ async function markSourceReadOnly(store, id) {
12988
+ const source = store.read().sources.find((one) => one.id === id);
12989
+ if (source === void 0) throw new Error("这个索引源已经不在了 —— 刷新后再试");
12990
+ if (source.kind !== "git") throw new Error("http 索引源本来就是只读");
12991
+ const next = {
12992
+ ...source,
12993
+ policy: "catalog-only"
12994
+ };
12995
+ if (source.policy !== "catalog-only") await store.apply({
12996
+ action: "save",
12997
+ source: next
12998
+ });
12999
+ return next;
13000
+ }
12839
13001
  function marketSourcesAction(store, init) {
12840
13002
  return {
12841
13003
  resource: MARKET_SOURCES_RESOURCE,
12842
13004
  async handle(payload) {
12843
13005
  const action = parseMarketSourcesAction(payload);
13006
+ if (action.action === "read-only") {
13007
+ const source = await markSourceReadOnly(store, action.id);
13008
+ return {
13009
+ ok: true,
13010
+ ...store.read(),
13011
+ source
13012
+ };
13013
+ }
12844
13014
  if (action.action === "init") {
12845
13015
  if (init === void 0) throw new Error("这台机器上没有 shell 服务,初始化不了索引仓库");
12846
13016
  const result = await init({
12847
13017
  id: action.id,
12848
- ...action.ref === void 0 ? {} : { ref: action.ref }
13018
+ ...action.ref === void 0 ? {} : { ref: action.ref },
13019
+ ...action.stampOnly === true ? { stampOnly: true } : {}
12849
13020
  });
12850
13021
  return {
12851
13022
  ok: true,
12852
- ...await store.apply({
12853
- action: "save",
12854
- source: result.source,
12855
- select: true
12856
- }),
13023
+ ...store.read(),
12857
13024
  branch: result.branch,
12858
13025
  commit: result.commit,
12859
13026
  created: result.created
@@ -12873,20 +13040,29 @@ function publishPreviewRoutes(workshop, inferredAuthorOf = async () => void 0) {
12873
13040
  const unit = (param(search, "unit") ?? "").trim();
12874
13041
  const version = (param(search, "version") ?? "").trim();
12875
13042
  const author = (param(search, "author") ?? "").trim();
13043
+ const changelog = param(search, "changelog") ?? "";
13044
+ const changelogType = (param(search, "changelogType") ?? "").trim();
12876
13045
  if (unit === "") return {
12877
13046
  ok: true,
12878
13047
  preview: null
12879
13048
  };
12880
13049
  let preview = publishPreviewOf(workshop, unit, {
12881
13050
  version,
12882
- author
13051
+ author,
13052
+ changelog,
13053
+ changelogType
12883
13054
  });
12884
13055
  if (preview !== null && author === "" && (preview.author === void 0 || preview.author === "")) {
12885
13056
  const inferred = await inferredAuthorOf(unit);
12886
- if (inferred !== void 0 && inferred !== "") preview = {
12887
- ...preview,
12888
- author: inferred
12889
- };
13057
+ if (inferred !== void 0 && inferred !== "") {
13058
+ const again = publishPreviewOf(workshop, unit, {
13059
+ version,
13060
+ author: inferred,
13061
+ changelog,
13062
+ changelogType
13063
+ });
13064
+ if (again !== null) preview = again;
13065
+ }
12890
13066
  }
12891
13067
  return {
12892
13068
  ok: true,
@@ -13513,17 +13689,21 @@ function createMarketSourceInitializer(options) {
13513
13689
  if (created.exitCode !== 0) throw new Error(`建分支 ${branch} 没过:${tailLines(created.stderr, 4)}`);
13514
13690
  }
13515
13691
  const indexPath = (source.path ?? "market.json").replace(/^\/+/, "") || "market.json";
13516
- const written = [];
13517
13692
  const indexFile = join(workdir, indexPath);
13518
- if (!existsSync(indexFile)) {
13519
- mkdirSync(dirname(indexFile), { recursive: true });
13520
- writeFileSync(indexFile, MARKET_INDEX_SEED);
13521
- written.push(indexPath);
13522
- }
13523
- const readmeFile = join(workdir, "README.md");
13524
- if (!existsSync(readmeFile)) {
13525
- writeFileSync(readmeFile, MARKET_INDEX_README_LINES.join("\n"));
13526
- written.push("README.md");
13693
+ const stampOnly = request.stampOnly === true;
13694
+ if (stampOnly && !existsSync(indexFile)) throw new Error(`仓库里还没有 ${indexPath} —— 先选中这个源,按发现页的提示把它初始化成索引`);
13695
+ const written = [];
13696
+ if (!stampOnly) {
13697
+ if (!existsSync(indexFile)) {
13698
+ mkdirSync(dirname(indexFile), { recursive: true });
13699
+ writeFileSync(indexFile, MARKET_INDEX_SEED);
13700
+ written.push(indexPath);
13701
+ }
13702
+ const readmeFile = join(workdir, "README.md");
13703
+ if (!existsSync(readmeFile)) {
13704
+ writeFileSync(readmeFile, MARKET_INDEX_README_LINES.join("\n"));
13705
+ written.push("README.md");
13706
+ }
13527
13707
  }
13528
13708
  if (written.length > 0) {
13529
13709
  const added = await git(["add", ...written], workdir);
@@ -13550,8 +13730,7 @@ function createMarketSourceInitializer(options) {
13550
13730
  };
13551
13731
  await options.store.apply({
13552
13732
  action: "save",
13553
- source: next,
13554
- select: true
13733
+ source: next
13555
13734
  });
13556
13735
  forget(source, next);
13557
13736
  return {
@@ -13678,6 +13857,73 @@ async function dispatchMarket(market, request) {
13678
13857
  };
13679
13858
  }
13680
13859
  //#endregion
13860
+ //#region src/modules/panels/model/market-sources-tool.ts
13861
+ var OUTPUT$2 = {
13862
+ schema: {
13863
+ type: "object",
13864
+ properties: {
13865
+ text: { type: "string" },
13866
+ sources: {
13867
+ type: "array",
13868
+ items: {
13869
+ type: "object",
13870
+ additionalProperties: true
13871
+ }
13872
+ }
13873
+ },
13874
+ additionalProperties: true
13875
+ },
13876
+ render: (_args, value) => {
13877
+ const payload = value;
13878
+ return [{
13879
+ type: "text",
13880
+ text: typeof payload?.text === "string" ? payload.text : JSON.stringify(value)
13881
+ }];
13882
+ }
13883
+ };
13884
+ function marketSourcesTool(read) {
13885
+ return {
13886
+ name: MARKET_SOURCES_TOOL,
13887
+ description: "看本机的索引源:id、地址、是否可发布、当前用的是哪个。",
13888
+ parameters: {
13889
+ type: "object",
13890
+ properties: {},
13891
+ additionalProperties: false
13892
+ },
13893
+ output: OUTPUT$2,
13894
+ async execute() {
13895
+ if (read === void 0) throw new Error("这台机器上没有索引源服务,看不了");
13896
+ const payload = read();
13897
+ const rows = payload.sources.map((source) => ({
13898
+ id: source.id,
13899
+ label: source.label,
13900
+ kind: source.kind,
13901
+ url: source.url,
13902
+ policy: source.policy ?? "catalog-only",
13903
+ ref: source.ref ?? null,
13904
+ path: source.path ?? null,
13905
+ selected: source.id === payload.selected
13906
+ }));
13907
+ if (rows.length === 0) return {
13908
+ sources: rows,
13909
+ selected: payload.selected,
13910
+ text: "这台机器还没有索引源 —— 在「模块市场 → 发现 → 索引源」里加一个。"
13911
+ };
13912
+ const current = rows.find((one) => one.selected);
13913
+ const head = current === void 0 ? "当前没有选中索引源" : `当前:${current.label}(${current.url})`;
13914
+ const lines = rows.map((one) => {
13915
+ const state = one.policy === "artifacts-in-repo" ? "可发布" : "仅读取模块列表";
13916
+ return `${one.selected ? "●" : "○"} ${one.id} · ${one.label} · ${one.kind} · ${state} · ${one.url}`;
13917
+ });
13918
+ return {
13919
+ sources: rows,
13920
+ selected: payload.selected,
13921
+ text: [head, ...lines].join("\n")
13922
+ };
13923
+ }
13924
+ };
13925
+ }
13926
+ //#endregion
13681
13927
  //#region src/modules/panels/model/market-tools.ts
13682
13928
  function supplyNamesOf(entry) {
13683
13929
  return entry.needs.filter((need) => need.kind === "extension" || need.kind === "lib").map((need) => need.name);
@@ -13773,7 +14019,7 @@ function usersOf(shared, names) {
13773
14019
  if (hit === void 0 || hit.usedBy.length === 0) return "";
13774
14020
  return `它被 ${hit.usedBy.join("、")} 用着(动的是它们的供给,它们的状态跟着重探了一遍)`;
13775
14021
  }
13776
- function registerMarketTools(ctx, market) {
14022
+ function registerMarketTools(ctx, market, sources) {
13777
14023
  const tools = ctx.get("tools");
13778
14024
  if (tools === void 0) {
13779
14025
  ctx.logger?.info?.("[dsh-workbench] 没有 tools 服务,跳过模块市场工具注册");
@@ -13972,6 +14218,82 @@ function registerMarketTools(ctx, market) {
13972
14218
  };
13973
14219
  }
13974
14220
  };
14221
+ const enableTool = {
14222
+ name: MARKET_ENABLE_TOOL,
14223
+ description: "把索引源设为可发布(只改本机配置,不碰仓库);要用户审批。",
14224
+ parameters: {
14225
+ type: "object",
14226
+ properties: { id: {
14227
+ type: "string",
14228
+ description: "索引源 id;不填=当前选中的那个。"
14229
+ } },
14230
+ additionalProperties: false
14231
+ },
14232
+ output: OUTPUT$1,
14233
+ async execute(raw) {
14234
+ if (sources === void 0) throw new Error("这台机器上没有索引源服务,设不了可发布");
14235
+ const payload = sources.read();
14236
+ const asked = textArg(raw, "id");
14237
+ const id = asked === "" ? payload.selected : asked;
14238
+ const source = id === null ? void 0 : payload.sources.find((one) => one.id === id);
14239
+ if (source === void 0) {
14240
+ const known = payload.sources.map((one) => one.id).join("、") || "(空)";
14241
+ throw new Error(`没有这个索引源${asked === "" ? "" : `:${asked}`}。现有的:${known};先在「模块市场 → 发现 → 索引源」里加一个。`);
14242
+ }
14243
+ if (source.kind !== "git") throw new Error(`索引源 ${source.id} 是 http 地址,只能读,不能当发布目的地`);
14244
+ if (source.policy === "artifacts-in-repo") return {
14245
+ source,
14246
+ changed: false,
14247
+ text: `已经是可发布:${source.label}(${source.url})`
14248
+ };
14249
+ const result = await sources.init({
14250
+ id: source.id,
14251
+ stampOnly: true
14252
+ });
14253
+ return {
14254
+ source: result.source,
14255
+ branch: result.branch,
14256
+ commit: result.commit,
14257
+ changed: true,
14258
+ text: `已把「${source.label}」设为可发布(${source.url},分支 ${result.branch})—— 仓库本身没动。之后发布可以落到它的 ${source.path ?? "market.json"}。`
14259
+ };
14260
+ }
14261
+ };
14262
+ const readOnlyTool = {
14263
+ name: MARKET_DISABLE_TOOL,
14264
+ description: "把索引源改回只读(不再作为发布目的地;只改本机配置)。",
14265
+ parameters: {
14266
+ type: "object",
14267
+ properties: { id: {
14268
+ type: "string",
14269
+ description: "索引源 id;不填=当前选中的那个。"
14270
+ } },
14271
+ additionalProperties: false
14272
+ },
14273
+ output: OUTPUT$1,
14274
+ async execute(raw) {
14275
+ if (sources === void 0) throw new Error("这台机器上没有索引源服务,改不了");
14276
+ const payload = sources.read();
14277
+ const asked = textArg(raw, "id");
14278
+ const id = asked === "" ? payload.selected : asked;
14279
+ const source = id === null ? void 0 : payload.sources.find((one) => one.id === id);
14280
+ if (source === void 0) {
14281
+ const known = payload.sources.map((one) => one.id).join("、") || "(空)";
14282
+ throw new Error(`没有这个索引源${asked === "" ? "" : `:${asked}`}。现有的:${known};先在「模块市场 → 发现 → 索引源」里加一个。`);
14283
+ }
14284
+ if (source.kind !== "git") throw new Error(`索引源 ${source.id} 是 http 地址,本来就是只读`);
14285
+ if (source.policy === "catalog-only") return {
14286
+ source,
14287
+ changed: false,
14288
+ text: `已经是只读:${source.label}(${source.url})`
14289
+ };
14290
+ return {
14291
+ source: await sources.readOnly(source.id),
14292
+ changed: true,
14293
+ text: `已把「${source.label}」设为只读(${source.url})—— 发布不会再落到它。`
14294
+ };
14295
+ }
14296
+ };
13975
14297
  return [
13976
14298
  tools.register(listTool),
13977
14299
  tools.register(installTool),
@@ -13981,7 +14303,10 @@ function registerMarketTools(ctx, market) {
13981
14303
  tools.register(updateDependencyTool),
13982
14304
  tools.register(updatePluginTool),
13983
14305
  tools.register(uninstallPluginTool),
13984
- tools.register(updateWorkbenchTool)
14306
+ tools.register(updateWorkbenchTool),
14307
+ tools.register(marketSourcesTool(sources === void 0 ? void 0 : () => sources.read())),
14308
+ tools.register(enableTool),
14309
+ tools.register(readOnlyTool)
13985
14310
  ];
13986
14311
  }
13987
14312
  //#endregion
@@ -14099,6 +14424,7 @@ function listedRunUnits(result) {
14099
14424
  }
14100
14425
  function panelsFactsOf(call, manifests) {
14101
14426
  if (call.source === "workbench_panels_list") return [PANELS_FACT];
14427
+ if (call.source === "market-sources") return [PANELS_FACT];
14102
14428
  if (call.source === "workbench_long_tasks_list") return listedRunUnits(call.result).map(runsRouteOf);
14103
14429
  if (call.source === "workbench_modules_list") return [PANELS_FACT, CAPABILITY_FACT];
14104
14430
  if (call.source === "workbench_modules_update_workbench") return [PANELS_FACT, CAPABILITY_FACT];
@@ -14793,13 +15119,17 @@ var OWNED = [{
14793
15119
  },
14794
15120
  registerTools: () => [
14795
15121
  ...registerPanelTools(ctx, panels, values),
14796
- ...registerMarketTools(ctx, market),
15122
+ ...registerMarketTools(ctx, market, {
15123
+ read: () => marketSources.read(),
15124
+ init: marketInit,
15125
+ readOnly: (id) => markSourceReadOnly(marketSources, id)
15126
+ }),
14797
15127
  ...registerDiyTools(ctx, market, panels, debug, (unit) => publishPreviewOf(workshopPathOf(ctx), unit), (unit) => inferredAuthorOf(workshopPathOf(ctx), unit, shell)),
14798
15128
  ...registerRecordTools(ctx, record),
14799
15129
  ...registerLongTaskTools(ctx, longTaskRuns)
14800
15130
  ],
14801
15131
  factsOf: (call) => panelsFactsOf(call, manifests()),
14802
- needsApproval: panelsApprovalFor(longTaskRuns, () => record.live(Date.now()), (unit) => publishPreviewOf(workshopPathOf(ctx), unit)),
15132
+ needsApproval: panelsApprovalFor(longTaskRuns, () => record.live(Date.now()), (unit, author) => publishPreviewOf(workshopPathOf(ctx), unit, author === void 0 ? {} : { author }), marketSources),
14803
15133
  gate: {
14804
15134
  deniedTools: (context) => {
14805
15135
  const boards = panels.boards(context.workspace);