@dsh-workbench/core 1.1.1 → 1.1.3

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 (44) hide show
  1. package/README.md +14 -5
  2. package/bin/dsh-workbench +7 -1
  3. package/client.js +1118 -429
  4. package/dist/client/client.body.js +1118 -429
  5. package/dist/client/index.js +970 -281
  6. package/dist/host/index.js +788 -383
  7. package/dist/types/app/layouts/approval-detail.d.ts +2 -1
  8. package/dist/types/app/layouts/market/discover-confirm.d.ts +1 -1
  9. package/dist/types/app/layouts/market/discover-detail.d.ts +1 -0
  10. package/dist/types/app/layouts/market/discover-installing.d.ts +8 -0
  11. package/dist/types/app/layouts/market/discover-rows.d.ts +4 -2
  12. package/dist/types/app/layouts/market/discover-source-item.d.ts +26 -0
  13. package/dist/types/app/layouts/market/discover-sources-dialog.d.ts +4 -0
  14. package/dist/types/app/layouts/market/discover-states.d.ts +6 -0
  15. package/dist/types/app/layouts/market/discover-transport.d.ts +7 -0
  16. package/dist/types/app/layouts/market/discover-versions.d.ts +2 -1
  17. package/dist/types/app/layouts/market/discover-view.d.ts +2 -0
  18. package/dist/types/app/layouts/market/use-discover.d.ts +11 -1
  19. package/dist/types/app/layouts/market/use-git-transport.d.ts +19 -0
  20. package/dist/types/app/layouts/market/use-module-versions.d.ts +1 -0
  21. package/dist/types/kernel/contract/market-index.d.ts +2 -2
  22. package/dist/types/modules/panels/client.d.ts +1 -1
  23. package/dist/types/modules/panels/model/approval.d.ts +4 -2
  24. package/dist/types/modules/panels/model/diy-publish.d.ts +10 -0
  25. package/dist/types/modules/panels/model/diy-tools.d.ts +0 -1
  26. package/dist/types/modules/panels/model/market-sources-act.d.ts +2 -1
  27. package/dist/types/modules/panels/model/market-sources-tool.d.ts +3 -0
  28. package/dist/types/modules/panels/model/market-sources-tool.test.d.ts +1 -0
  29. package/dist/types/modules/panels/model/market-tools.d.ts +8 -1
  30. package/dist/types/modules/panels/model/publish-claim.d.ts +2 -0
  31. package/dist/types/modules/panels/unit.d.ts +7 -5
  32. package/dist/types/shared/api/host/diy-publish-preview.d.ts +2 -0
  33. package/dist/types/shared/api/host/git-exec.d.ts +2 -0
  34. package/dist/types/shared/api/host/git-probe.d.ts +6 -0
  35. package/dist/types/shared/api/host/git-routes.d.ts +3 -0
  36. package/dist/types/shared/api/host/market-source-init.d.ts +1 -0
  37. package/dist/types/shared/api/host/market-sources-store.d.ts +2 -0
  38. package/dist/types/shared/hooks/market-queries.d.ts +3 -2
  39. package/package.json +3 -3
  40. package/skills/workbench-module-diy/FAMILY.md +3 -4
  41. package/skills/workbench-module-diy/SKILL.md +4 -1
  42. package/skills/workbench-module-diy/WIRING.md +9 -2
  43. package/dist/types/modules/panels/model/diy-changelog.d.ts +0 -2
  44. package/dist/types/shared/api/host/diy-changelog.d.ts +0 -13
@@ -4545,6 +4545,7 @@ function checkVersionRange(contract, version) {
4545
4545
  }
4546
4546
  var MARKET_DISCOVER_ROUTE = "panels/market-discover";
4547
4547
  var MARKET_DOC_ROUTE = "panels/market-doc";
4548
+ var MARKET_GIT_PROBE_ROUTE = "panels/git-probe";
4548
4549
  var MARKET_PUBLISH_PREVIEW_ROUTE = "panels/market-publish-preview";
4549
4550
  var MARKET_SOURCES_RESOURCE = "market-sources";
4550
4551
  var MARKET_SOURCES_ROUTE = "panels/market-sources";
@@ -4577,7 +4578,10 @@ var MARKET_INDEX_README_LINES = [
4577
4578
  "dsh-wb-module release --index <本仓库地址> --index-policy artifacts-in-repo",
4578
4579
  "```",
4579
4580
  "",
4580
- "产物会提交到 `packages/<unit>/`,并更新 `market.json`。",
4581
+ "产物会提交到 `packages/<包名>/`(带 scope 时是 `packages/@scope/名字/`),并更新 `market.json`。",
4582
+ "",
4583
+ "一个包名归第一次发布的人(`market.json` 里的 `owner`)。换一个署名发同名会被拒;要换人,先把这一条的 `owner` 改成新的人。",
4584
+ "同一个市场里,一个模块 id(`metadata.unit`)只能被一个条目使用;另一个包名想用同一个 id / 产物目录也会被拒。",
4581
4585
  "",
4582
4586
  "## 使用这个索引",
4583
4587
  "",
@@ -4683,15 +4687,25 @@ function iconProblem(icon) {
4683
4687
  if (/^https?:\/\//i.test(icon)) return void 0;
4684
4688
  return problem("icon", "icon 只能是 base64 data URL 或 http(s) URL");
4685
4689
  }
4690
+ var MAX_OWNER_CHARS = 200;
4691
+ var UNIT_ID = /^[a-z][a-z0-9-]*$/;
4692
+ function ownerProblem(owner) {
4693
+ if (/[\r\n]/u.test(owner)) return problem("owner", "owner 只能是一行署名");
4694
+ if (owner.length > MAX_OWNER_CHARS) return problem("owner", "owner 超过 200 字");
4695
+ }
4686
4696
  function metadataOf(raw) {
4687
4697
  const record = asRecord$3(raw);
4688
4698
  if (record === void 0) return void 0;
4699
+ const workbench = record["workbench"] === true;
4700
+ const unit = asText$3(record["unit"]);
4689
4701
  const category = asText$3(record["category"]);
4690
4702
  const labels = asTextArray(record["labels"]);
4691
4703
  const keywords = asTextArray(record["keywords"]);
4692
4704
  const sortKeysRecord = asRecord$3(record["sortKeys"]);
4693
4705
  const sortKeys = sortKeysRecord === void 0 ? void 0 : Object.fromEntries(Object.entries(sortKeysRecord).flatMap(([key, value]) => typeof value === "string" || typeof value === "number" ? [[key, value]] : []));
4694
4706
  return {
4707
+ ...workbench ? { workbench: true } : {},
4708
+ ...unit === "" ? {} : { unit },
4695
4709
  ...category === "" ? {} : { category },
4696
4710
  ...labels.length === 0 ? {} : { labels },
4697
4711
  ...keywords.length === 0 ? {} : { keywords },
@@ -4713,14 +4727,21 @@ function entryOf$1(raw, at) {
4713
4727
  const title = asLocalized(record["title"]);
4714
4728
  const description = asLocalized(record["description"]);
4715
4729
  const author = asText$3(record["author"]);
4730
+ const owner = asText$3(record["owner"]);
4731
+ if (owner !== "") {
4732
+ const badOwner = ownerProblem(owner);
4733
+ if (badOwner !== void 0) return badOwner;
4734
+ }
4716
4735
  const homepage = asText$3(record["homepage"]);
4717
4736
  const metadata = metadataOf(record["metadata"]);
4737
+ if (metadata?.unit !== void 0 && !UNIT_ID.test(metadata.unit)) return problem("unit", `metadata.unit 只能是小写字母开头的 a-z0-9-(现在是 ${JSON.stringify(metadata.unit)})`, at);
4718
4738
  return {
4719
4739
  name,
4720
4740
  versions,
4721
4741
  ...title === void 0 ? {} : { title },
4722
4742
  ...description === void 0 ? {} : { description },
4723
4743
  ...author === "" ? {} : { author },
4744
+ ...owner === "" ? {} : { owner },
4724
4745
  ...homepage === "" ? {} : { homepage },
4725
4746
  ...icon === "" ? {} : { icon },
4726
4747
  ...metadata === void 0 ? {} : { metadata }
@@ -4907,6 +4928,84 @@ function gitUrlOk(url) {
4907
4928
  return true;
4908
4929
  }
4909
4930
  //#endregion
4931
+ //#region ../shared/dist/host/market-index-claim.js
4932
+ function foldedName(name) {
4933
+ return name.replace(/\s+/gu, " ").trim().toLowerCase();
4934
+ }
4935
+ function publisherIdentityOf(text) {
4936
+ const raw = (text ?? "").trim();
4937
+ if (raw === "") return void 0;
4938
+ const match = /^(.*?)\s*<([^<>]*)>\s*$/u.exec(raw);
4939
+ const name = (match === null ? raw : match[1]).trim();
4940
+ const email = match === null ? "" : match[2].trim().toLowerCase();
4941
+ if (name === "") return void 0;
4942
+ return email === "" ? { name } : {
4943
+ name,
4944
+ email
4945
+ };
4946
+ }
4947
+ function samePublisher(left, right) {
4948
+ const a = publisherIdentityOf(left);
4949
+ const b = publisherIdentityOf(right);
4950
+ if (a === void 0 || b === void 0) return false;
4951
+ if (a.email !== void 0 && b.email !== void 0) return a.email === b.email;
4952
+ return foldedName(a.name) === foldedName(b.name);
4953
+ }
4954
+ function entryOwnerOf(entry) {
4955
+ const owner = (entry.owner ?? "").trim();
4956
+ if (owner !== "") return owner;
4957
+ const author = (entry.author ?? "").trim();
4958
+ return author === "" ? void 0 : author;
4959
+ }
4960
+ function claimOfEntry(entry, publisher) {
4961
+ if (entry === void 0) return { state: "new" };
4962
+ const owner = entryOwnerOf(entry);
4963
+ if (owner === void 0) return { state: "unowned" };
4964
+ if (publisher === void 0 || publisher.trim() === "") return { state: "unknown" };
4965
+ return samePublisher(owner, publisher) ? {
4966
+ state: "owned",
4967
+ owner
4968
+ } : {
4969
+ state: "conflict",
4970
+ owner
4971
+ };
4972
+ }
4973
+ function marketArtifactPath(name) {
4974
+ return `packages/${name}`;
4975
+ }
4976
+ function entryUnitOf(entry) {
4977
+ const unit = entry.metadata?.unit?.trim();
4978
+ if (unit !== void 0 && unit !== "") return unit;
4979
+ for (const version of entry.versions) {
4980
+ if (version.source.type !== "git") continue;
4981
+ const legacy = /^packages\/([^/]+)$/u.exec(version.source.path ?? "")?.[1];
4982
+ if (legacy !== void 0) return legacy;
4983
+ }
4984
+ }
4985
+ function claimOf(index, input) {
4986
+ const base = claimOfEntry(index.plugins.find((one) => one.name === input.name), input.publisher);
4987
+ if (base.state === "conflict" || base.state === "unknown") return base;
4988
+ const unit = (input.unit ?? "").trim();
4989
+ if (unit !== "") for (const other of index.plugins) {
4990
+ if (other.name === input.name) continue;
4991
+ if (entryUnitOf(other) !== unit) continue;
4992
+ return {
4993
+ state: "unit-taken",
4994
+ entry: other.name
4995
+ };
4996
+ }
4997
+ const path = (input.artifactPath ?? "").trim();
4998
+ if (path !== "") for (const other of index.plugins) {
4999
+ if (other.name === input.name) continue;
5000
+ if (!other.versions.some((one) => one.source.type === "git" && (one.source.path ?? "") === path)) continue;
5001
+ return {
5002
+ state: "path-taken",
5003
+ entry: other.name
5004
+ };
5005
+ }
5006
+ return base;
5007
+ }
5008
+ //#endregion
4910
5009
  //#region ../shared/dist/host/changelog.js
4911
5010
  var CHANGELOG_FILE = "CHANGELOG.md";
4912
5011
  var CHANGELOG_KINDS = [
@@ -5064,6 +5163,25 @@ function appendChangelogEntry(text, options) {
5064
5163
  out.splice(at, 0, ...bullets);
5065
5164
  return `${normalizeWhitespace(out)}\n`;
5066
5165
  }
5166
+ function planChangelog(text, options) {
5167
+ const source = normalizeChangelogText(text);
5168
+ const version = changelogVersion(options.version);
5169
+ const section = changelogSectionOf(source, version);
5170
+ if (section !== void 0 && changelogHasEntry(source, version)) return {
5171
+ action: "kept",
5172
+ version,
5173
+ text: source,
5174
+ section
5175
+ };
5176
+ if (bulletsOf(options.note).length === 0) return void 0;
5177
+ const next = appendChangelogEntry(source, options);
5178
+ return {
5179
+ action: "written",
5180
+ version,
5181
+ text: next,
5182
+ section: changelogSectionOf(next, version) ?? ""
5183
+ };
5184
+ }
5067
5185
  function normalizeWhitespace(rows) {
5068
5186
  const out = [];
5069
5187
  for (const row of rows) {
@@ -5416,6 +5534,80 @@ function bundleInspectHandler(ctx) {
5416
5534
  };
5417
5535
  }
5418
5536
  //#endregion
5537
+ //#region src/shared/api/host/git-exec.ts
5538
+ var NO_PROMPT_ENV = [
5539
+ "GIT_TERMINAL_PROMPT=0",
5540
+ "GIT_ASKPASS=/usr/bin/false",
5541
+ "SSH_ASKPASS=/usr/bin/false",
5542
+ "SSH_ASKPASS_REQUIRE=never",
5543
+ "GCM_INTERACTIVE=never",
5544
+ "GIT_SSH_COMMAND=ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new"
5545
+ ];
5546
+ function gitNoPromptPrefix() {
5547
+ return [quoteShellArg("env"), ...NO_PROMPT_ENV.map((one) => quoteShellArg(one))].join(" ");
5548
+ }
5549
+ function withoutAskpassNoise(text) {
5550
+ return text.split("\n").filter((line) => !/unable to read askpass response/i.test(line)).join("\n").trim();
5551
+ }
5552
+ //#endregion
5553
+ //#region src/shared/api/host/git-probe.ts
5554
+ var PROBE_TIMEOUT_MS = 1e4;
5555
+ var AUTH_TEXT = /authentication failed|could not read (username|password)|permission denied|terminal prompts disabled|host key verification failed|repository not found|no such identity|access denied|unauthorized|forbidden/;
5556
+ function classifyGitProbe(stderr, timedOut) {
5557
+ if (timedOut) return "unreachable";
5558
+ return AUTH_TEXT.test(stderr.toLowerCase()) ? "auth" : "unreachable";
5559
+ }
5560
+ async function probeGitAddress(shell, url, options = {}) {
5561
+ const line = [
5562
+ gitNoPromptPrefix(),
5563
+ quoteShellArg("git"),
5564
+ quoteShellArg("ls-remote"),
5565
+ quoteShellArg(url),
5566
+ quoteShellArg("HEAD")
5567
+ ].join(" ");
5568
+ let result;
5569
+ try {
5570
+ result = await runShell(shell, line, { timeoutMs: options.timeoutMs ?? PROBE_TIMEOUT_MS });
5571
+ } catch (error) {
5572
+ return {
5573
+ status: "unreachable",
5574
+ detail: error instanceof Error ? error.message : String(error)
5575
+ };
5576
+ }
5577
+ if (result.exitCode === 0) return { status: "ok" };
5578
+ const detail = tailLines(withoutAskpassNoise(result.stderr || result.stdout), 3);
5579
+ return {
5580
+ status: classifyGitProbe(`${result.stderr}\n${result.stdout}`, result.timedOut),
5581
+ ...detail === "" ? {} : { detail }
5582
+ };
5583
+ }
5584
+ //#endregion
5585
+ //#region src/shared/api/host/git-routes.ts
5586
+ function gitProbeHandler(ctx) {
5587
+ return async (search) => {
5588
+ const url = param(search, "url");
5589
+ if (url === void 0) return {
5590
+ ok: false,
5591
+ error: {
5592
+ code: "workbench/no-url",
5593
+ message: "要探哪个地址?缺 url。"
5594
+ }
5595
+ };
5596
+ const shell = ctx.get("shell");
5597
+ if (shell === void 0) return {
5598
+ ok: false,
5599
+ error: {
5600
+ code: "workbench/no-shell",
5601
+ message: "这台机器上没有执行缝,探不了 git 地址"
5602
+ }
5603
+ };
5604
+ return {
5605
+ ok: true,
5606
+ ...await probeGitAddress(shell, url)
5607
+ };
5608
+ };
5609
+ }
5610
+ //#endregion
5419
5611
  //#region src/shared/api/host/host-routes.ts
5420
5612
  function createHostRoutes(input) {
5421
5613
  const { ctx, facts, opencli, running, profile } = input;
@@ -5425,6 +5617,7 @@ function createHostRoutes(input) {
5425
5617
  [DSH_PLUGINS_ROUTE]: profile.dshPlugins(ctx),
5426
5618
  [BUNDLE_REGISTRIES_ROUTE]: bundleRegistriesHandler(ctx),
5427
5619
  [BUNDLE_INSPECT_ROUTE]: bundleInspectHandler(ctx),
5620
+ [MARKET_GIT_PROBE_ROUTE]: gitProbeHandler(ctx),
5428
5621
  [MODULE_VERSIONS_ROUTE]: moduleVersionsHandler(ctx, ctx.get("shell"), running, () => profile.selfUpdateView(dshHome(), basename(profile.dirOf(ctx) ?? "")))
5429
5622
  };
5430
5623
  }
@@ -5458,12 +5651,14 @@ var MODULES_UPDATE_DEPENDENCY_TOOL = "workbench_modules_update_dependency";
5458
5651
  var MODULES_UPDATE_PLUGIN_TOOL = "workbench_modules_update_plugin";
5459
5652
  var MODULES_UNINSTALL_PLUGIN_TOOL = "workbench_modules_uninstall_plugin";
5460
5653
  var MODULES_UPDATE_WORKBENCH_TOOL = "workbench_modules_update_workbench";
5654
+ var MARKET_SOURCES_TOOL = "workbench_market_sources";
5655
+ var MARKET_ENABLE_TOOL = "workbench_market_enable";
5656
+ var MARKET_DISABLE_TOOL = "workbench_market_disable";
5461
5657
  var DIY_STATUS_TOOL = "workbench_diy_status";
5462
5658
  var DIY_PANEL_TOOL = "workbench_diy_panel";
5463
5659
  var DIY_COMPONENTS_TOOL = "workbench_diy_components";
5464
5660
  var DIY_RECON_TOOL = "workbench_diy_recon";
5465
5661
  var DIY_SPEC_TOOL = "workbench_diy_spec";
5466
- var DIY_CHANGELOG_TOOL = "workbench_diy_changelog";
5467
5662
  var DIY_INSTALL_TOOL = "workbench_diy_install";
5468
5663
  var DIY_PUBLISH_TOOL = "workbench_diy_publish";
5469
5664
  var DIY_SHOW_TOOL = "workbench_diy_show";
@@ -5497,6 +5692,7 @@ var DIY_DEBUG_STOP_TOOL = "workbench_diy_debug_stop";
5497
5692
  var READ_TOOLS = [
5498
5693
  PANELS_LIST_TOOL,
5499
5694
  MODULES_LIST_TOOL,
5695
+ MARKET_SOURCES_TOOL,
5500
5696
  DIY_STATUS_TOOL,
5501
5697
  DIY_PANEL_TOOL,
5502
5698
  DIY_COMPONENTS_TOOL,
@@ -5538,7 +5734,8 @@ var WRITE_TOOLS = [
5538
5734
  MODULES_UPDATE_PLUGIN_TOOL,
5539
5735
  MODULES_UNINSTALL_PLUGIN_TOOL,
5540
5736
  MODULES_UPDATE_WORKBENCH_TOOL,
5541
- DIY_CHANGELOG_TOOL,
5737
+ MARKET_ENABLE_TOOL,
5738
+ MARKET_DISABLE_TOOL,
5542
5739
  DIY_INSTALL_TOOL,
5543
5740
  DIY_PUBLISH_TOOL,
5544
5741
  DIY_SHOW_TOOL,
@@ -5555,7 +5752,6 @@ var DIY_TOOLS = [
5555
5752
  DIY_COMPONENTS_TOOL,
5556
5753
  DIY_RECON_TOOL,
5557
5754
  DIY_SPEC_TOOL,
5558
- DIY_CHANGELOG_TOOL,
5559
5755
  DIY_INSTALL_TOOL,
5560
5756
  DIY_PUBLISH_TOOL,
5561
5757
  DIY_SHOW_TOOL,
@@ -5832,6 +6028,14 @@ function readDomainsOf(design) {
5832
6028
  return [...found];
5833
6029
  }
5834
6030
  //#endregion
6031
+ //#region src/modules/panels/model/publish-claim.ts
6032
+ function publishClaimDenyOf(claim, name) {
6033
+ if (claim === void 0) return void 0;
6034
+ if (claim.state === "unit-taken") return `市场里 ${claim.entry} 已经用了这个模块 id —— 两个模块不能共用一个,装在一起会撞。给这个模块换一个 unit(dsh.workbench.unit)再发。`;
6035
+ if (claim.state === "path-taken") return `市场仓库里的产物目录已经被 ${claim.entry} 占了 —— 换一个包名再发。`;
6036
+ if (claim.state === "conflict") return `市场里 ${name} 已经有主了(${claim.owner})—— 同一个名字不能换人发。换一个带团队 scope 的包名;你就是原来的作者(只是换了机器或署名),用 author 参数带上原来的署名;确实是接手,让索引维护者改 market.json 里这一条的 owner。`;
6037
+ }
6038
+ //#endregion
5835
6039
  //#region src/modules/panels/model/approval.ts
5836
6040
  var SPEC_LIMIT = 80;
5837
6041
  var TARGET_LIMIT = 20;
@@ -5938,11 +6142,22 @@ function publishVerdict(call, preview) {
5938
6142
  if (raw["verify"] === true || raw["target"] === "local") return { kind: "allow" };
5939
6143
  const refresh = raw["refresh"] === true;
5940
6144
  const unit = textOf$3(raw, "unit");
5941
- const found = unit === "" ? null : preview?.(unit) ?? null;
6145
+ const askedAuthor = textOf$3(raw, "author");
6146
+ const found = unit === "" ? null : preview?.(unit, askedAuthor === "" ? void 0 : askedAuthor) ?? null;
5942
6147
  const asked = textOf$3(raw, "target");
5943
6148
  const target = refresh ? "market" : asked === "" ? found?.market === void 0 ? found?.registry === void 0 ? "tarball" : "team" : "market" : asked;
6149
+ const source = textOf$3(raw, "source");
6150
+ const claimReason = publishClaimDenyOf(found?.claim, found?.name ?? unit);
6151
+ if (source === "" && target !== "tarball" && target !== "local" && claimReason !== void 0) return {
6152
+ kind: "deny",
6153
+ reason: claimReason
6154
+ };
5944
6155
  const label = unit === "" ? "工作台模块" : `工作台模块 [${unit}]`;
5945
6156
  const labelEn = unit === "" ? "the workbench module" : `the workbench module [${unit}]`;
6157
+ if (!refresh && found?.changelog?.missing === true && textOf$3(raw, "changelogNote") === "") return {
6158
+ kind: "deny",
6159
+ reason: `这一版还没有更新日志(${found.changelog.version})—— 带上 changelogNote(一行一条)再调,发布时会一起写进 CHANGELOG.md 并随包发`
6160
+ };
5946
6161
  const copies = {
5947
6162
  team: {
5948
6163
  zh: `把${label}发到团队源,只发构建产物,不发源码,允许吗?`,
@@ -5974,7 +6189,7 @@ function publishVerdict(call, preview) {
5974
6189
  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
6190
  return askOf(`${DIY_PUBLISH_TOOL} ${targetLabel}${refresh ? "(元数据)" : ""}`, question, question);
5976
6191
  }
5977
- var panelsApproval = (call) => {
6192
+ function approvalVerdict(call, marketSource) {
5978
6193
  if (call.name === "workbench_long_tasks_stop") return stopVerdict(call, void 0);
5979
6194
  if (call.name === "workbench_diy_publish") return publishVerdict(call);
5980
6195
  if (call.name === "workbench_diy_debug_start" || call.name === "workbench_diy_debug_resume") {
@@ -6045,9 +6260,41 @@ var panelsApproval = (call) => {
6045
6260
  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
6261
  }
6047
6262
  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?"));
6263
+ case MARKET_ENABLE_TOOL: {
6264
+ const id = text("id");
6265
+ const source = marketSource?.(id);
6266
+ if (marketSource !== void 0 && source === void 0) return {
6267
+ kind: "deny",
6268
+ reason: id === void 0 ? "这台机器上还没有选中的索引源 —— 先在「模块市场 → 发现」里添加并选中一个" : `没有这个索引源:${id}`
6269
+ };
6270
+ if (source !== void 0 && source.policy === "artifacts-in-repo") return { kind: "allow" };
6271
+ if (source !== void 0 && source.kind === "http") return {
6272
+ kind: "deny",
6273
+ reason: "http 索引源只能读,不能当发布目的地"
6274
+ };
6275
+ const where = source === void 0 ? {
6276
+ zh: id === void 0 ? "工作台当前选中的索引源" : `工作台的索引源「${id}」`,
6277
+ en: id === void 0 ? "the selected workbench index source" : `the workbench index source "${id}"`
6278
+ } : {
6279
+ zh: `工作台的索引源「${source.label}」`,
6280
+ en: `the workbench index source "${source.label}"`
6281
+ };
6282
+ 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?`));
6283
+ }
6284
+ case MARKET_DISABLE_TOOL: {
6285
+ const id = text("id");
6286
+ const source = marketSource?.(id);
6287
+ if (marketSource !== void 0 && source === void 0) return {
6288
+ kind: "deny",
6289
+ reason: id === void 0 ? "这台机器上还没有选中的索引源 —— 先在「模块市场 → 发现」里添加并选中一个" : `没有这个索引源:${id}`
6290
+ };
6291
+ 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?"));
6292
+ if (source.kind !== "git" || source.policy !== "artifacts-in-repo") return { kind: "allow" };
6293
+ 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?`));
6294
+ }
6048
6295
  default: return;
6049
6296
  }
6050
- };
6297
+ }
6051
6298
  function recordHeldVerdict(call, recording) {
6052
6299
  if (call.name !== "workbench_diy_record_prepare" || recording === void 0) return void 0;
6053
6300
  const held = recording();
@@ -6057,11 +6304,16 @@ function recordHeldVerdict(call, recording) {
6057
6304
  reason: recordRefusalText(held.sessionId === call.session ? "already-held" : "held-by-other", held)
6058
6305
  };
6059
6306
  }
6060
- function panelsApprovalFor(longTaskRuns, recording, publishPreview) {
6307
+ function panelsApprovalFor(longTaskRuns, recording, publishPreview, marketSources) {
6308
+ const sourceOf = marketSources === void 0 ? void 0 : (id) => {
6309
+ const payload = marketSources.read();
6310
+ const wanted = id === void 0 || id === "" ? payload.selected : id;
6311
+ return wanted === null ? void 0 : payload.sources.find((one) => one.id === wanted);
6312
+ };
6061
6313
  return (call) => {
6062
6314
  if (call.name === "workbench_long_tasks_stop") return stopVerdict(call, longTaskRuns);
6063
6315
  if (call.name === "workbench_diy_publish") return publishVerdict(call, publishPreview);
6064
- return recordHeldVerdict(call, recording) ?? panelsApproval(call);
6316
+ return recordHeldVerdict(call, recording) ?? approvalVerdict(call, sourceOf);
6065
6317
  };
6066
6318
  }
6067
6319
  //#endregion
@@ -6978,7 +7230,7 @@ async function sessionWorkspaceOf(ctx, workspace) {
6978
7230
  dir = parent;
6979
7231
  }
6980
7232
  }
6981
- var OUTPUT$2 = {
7233
+ var OUTPUT$3 = {
6982
7234
  schema: {
6983
7235
  type: "object",
6984
7236
  properties: {
@@ -7123,7 +7375,7 @@ function componentsTool(ctx) {
7123
7375
  },
7124
7376
  additionalProperties: false
7125
7377
  },
7126
- output: OUTPUT$2,
7378
+ output: OUTPUT$3,
7127
7379
  async execute(raw, exec) {
7128
7380
  const { root } = await workspaceOf(ctx, exec);
7129
7381
  const args = asRecord$1(raw);
@@ -7221,97 +7473,6 @@ function registerComponentsTool(ctx, tools) {
7221
7473
  return [tools.register(componentsTool(ctx))];
7222
7474
  }
7223
7475
  //#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
7476
  //#region src/shared/api/host/diy-artifacts.ts
7316
7477
  function artifactsDirOf(ctx, root) {
7317
7478
  const homePath = ctx.get("dshHomePath");
@@ -7598,7 +7759,7 @@ function registerDebugTools(ctx, tools, store) {
7598
7759
  required: [],
7599
7760
  additionalProperties: false
7600
7761
  },
7601
- output: OUTPUT$2,
7762
+ output: OUTPUT$3,
7602
7763
  async execute(raw, exec) {
7603
7764
  const { root, title } = await workspaceOf(ctx, exec);
7604
7765
  const { units } = unitsOf(ctx, root);
@@ -7633,7 +7794,7 @@ function registerDebugTools(ctx, tools, store) {
7633
7794
  name: DIY_DEBUG_PAUSE_TOOL,
7634
7795
  description: "暂停调试(不审批):屏工具停用,不再拿屏当目标。要接着看用 debug_resume。",
7635
7796
  parameters: EMPTY,
7636
- output: OUTPUT$2,
7797
+ output: OUTPUT$3,
7637
7798
  async execute(_raw, exec) {
7638
7799
  const { root } = await workspaceOf(ctx, exec);
7639
7800
  const held = store.live(Date.now());
@@ -7651,7 +7812,7 @@ function registerDebugTools(ctx, tools, store) {
7651
7812
  name: DIY_DEBUG_RESUME_TOOL,
7652
7813
  description: "继续调试(要点头一次):请用户回到这一场调试所在的那一屏。",
7653
7814
  parameters: EMPTY,
7654
- output: OUTPUT$2,
7815
+ output: OUTPUT$3,
7655
7816
  async execute(_raw, exec) {
7656
7817
  const { root } = await workspaceOf(ctx, exec);
7657
7818
  const held = store.live(Date.now());
@@ -7675,7 +7836,7 @@ function registerDebugTools(ctx, tools, store) {
7675
7836
  name: DIY_DEBUG_STOP_TOOL,
7676
7837
  description: "结束调试(不审批):清掉\"占用用户那一屏\"这件事。收工用它。",
7677
7838
  parameters: EMPTY,
7678
- output: OUTPUT$2,
7839
+ output: OUTPUT$3,
7679
7840
  async execute(_raw, exec) {
7680
7841
  const { root } = await workspaceOf(ctx, exec);
7681
7842
  const held = store.live(Date.now());
@@ -7865,7 +8026,7 @@ function registerLogTools(ctx, tools) {
7865
8026
  name: DIY_LOGS_WATCH_TOOL,
7866
8027
  description: "把这一页的控制台听上(每页最多 200 条、单条截 400 字,随页面刷新消失)。",
7867
8028
  parameters: schema,
7868
- output: OUTPUT$2,
8029
+ output: OUTPUT$3,
7869
8030
  async execute(raw, exec) {
7870
8031
  const { page, step } = await run(exec, raw, LOGS_ARM_SCRIPT);
7871
8032
  const error = actErrorOf(step);
@@ -7885,7 +8046,7 @@ function registerLogTools(ctx, tools) {
7885
8046
  name: DIY_LOGS_READ_TOOL,
7886
8047
  description: "读刚才听下来的控制台(最近 50 条;没装钩子会说清,不假装是\"没有报错\")。",
7887
8048
  parameters: schema,
7888
- output: OUTPUT$2,
8049
+ output: OUTPUT$3,
7889
8050
  async execute(raw, exec) {
7890
8051
  const { page, step } = await run(exec, raw, LOGS_READ_SCRIPT);
7891
8052
  const error = actErrorOf(step);
@@ -7904,7 +8065,7 @@ function registerLogTools(ctx, tools) {
7904
8065
  name: DIY_LOGS_STOP_TOOL,
7905
8066
  description: "控制台收听收工:卸掉钩子、扔掉攒下的记录。",
7906
8067
  parameters: schema,
7907
- output: OUTPUT$2,
8068
+ output: OUTPUT$3,
7908
8069
  async execute(raw, exec) {
7909
8070
  const { page, step } = await run(exec, raw, LOGS_STOP_SCRIPT);
7910
8071
  const error = actErrorOf(step);
@@ -8570,7 +8731,7 @@ function registerBrowserTools(ctx, tools) {
8570
8731
  name: row.tool,
8571
8732
  description: row.description,
8572
8733
  parameters: parametersOf(row),
8573
- output: OUTPUT$2,
8734
+ output: OUTPUT$3,
8574
8735
  execute: async (raw, exec) => plainJson(await runBrowserTool(row, ctx, raw, exec))
8575
8736
  };
8576
8737
  return tools.register(definition);
@@ -8609,7 +8770,7 @@ function specTool(ctx) {
8609
8770
  required: ["command"],
8610
8771
  additionalProperties: false
8611
8772
  },
8612
- output: OUTPUT$2,
8773
+ output: OUTPUT$3,
8613
8774
  async execute(raw, _exec) {
8614
8775
  const args = asRecord$1(raw);
8615
8776
  const argv = specArgvOf(asText(args["command"]));
@@ -8636,6 +8797,176 @@ function registerSpecTool(ctx, tools) {
8636
8797
  return [tools.register(specTool(ctx))];
8637
8798
  }
8638
8799
  //#endregion
8800
+ //#region src/modules/panels/model/diy-publish.ts
8801
+ var PUBLISH_TIMEOUT_MS = 18e5;
8802
+ function registerPublishTool(ctx, tools, deps) {
8803
+ const publishTool = {
8804
+ name: DIY_PUBLISH_TOOL,
8805
+ description: "用户说“发布”后才用:把这一格发到 target 指定的出口(不填按环境默认)。这一版还没有更新日志时把 changelogNote 一起带上 —— 发布时写进 CHANGELOG.md,随包发。",
8806
+ parameters: {
8807
+ type: "object",
8808
+ properties: {
8809
+ unit: {
8810
+ type: "string",
8811
+ description: "模块 id(workbench_diy_status 会给)。"
8812
+ },
8813
+ changelogNote: {
8814
+ type: "string",
8815
+ description: "这一版改了什么,一行一条;这一版还没有更新日志时必填(文件不在会先建)。"
8816
+ },
8817
+ changelogType: {
8818
+ type: "string",
8819
+ enum: [...CHANGELOG_KINDS],
8820
+ description: "分类,不填按 changed。"
8821
+ },
8822
+ target: {
8823
+ type: "string",
8824
+ enum: [
8825
+ "team",
8826
+ "public",
8827
+ "tarball",
8828
+ "git",
8829
+ "market",
8830
+ "local"
8831
+ ],
8832
+ description: "发到哪:团队源 / 公共 npm / 安装包 / git / 模块市场索引;不填按环境默认(有可写索引走市场,有私源走团队源,否则安装包)。"
8833
+ },
8834
+ version: {
8835
+ type: "string",
8836
+ description: "可选:patch / minor / major / x.y.z;不填首次用当前版本,之后自动 patch。"
8837
+ },
8838
+ source: {
8839
+ type: "string",
8840
+ description: "发布到哪个索引源(workbench_market_sources 会给 id);不填用唯一可发布的源。"
8841
+ },
8842
+ verify: {
8843
+ type: "boolean",
8844
+ description: "只核已发布的包(交接验证码后用),不再发;不用审批。"
8845
+ },
8846
+ resume: {
8847
+ type: "boolean",
8848
+ description: "中断后接着发(用记录里的出口与版本)。"
8849
+ },
8850
+ license: {
8851
+ type: "string",
8852
+ description: "公开发布用的许可证(MIT / Apache-2.0 / 仓库已有 LICENSE)。"
8853
+ },
8854
+ author: {
8855
+ type: "string",
8856
+ description: "署谁的名(姓名,或 姓名 <邮箱>);用户点了名才传,不传按模块里写的,没写用推断的署名。"
8857
+ },
8858
+ refresh: {
8859
+ type: "boolean",
8860
+ description: "只刷市集元数据(标题/作者/说明),不升版本、不重发产物。"
8861
+ }
8862
+ },
8863
+ required: ["unit"],
8864
+ additionalProperties: false
8865
+ },
8866
+ output: OUTPUT$3,
8867
+ async execute(raw, exec) {
8868
+ const { root } = await workspaceOf(ctx, exec);
8869
+ const found = deps.resolve(root, raw);
8870
+ const shell = ctx.get("shell");
8871
+ if (shell === void 0) throw new Error("这台机器上没有 shell 服务,发布跑不起来");
8872
+ const bin = toolchainBinOf(root) ?? toolchainBinOf(found.dir);
8873
+ if (bin === null) throw new Error(`这个工作区里没有 dsh-wb-module(${join(root, "node_modules")} 下没有)—— 先 pnpm install`);
8874
+ const target = asText(asRecord$1(raw)["target"]);
8875
+ const source = asText(asRecord$1(raw)["source"]);
8876
+ const version = asText(asRecord$1(raw)["version"]);
8877
+ const license = asText(asRecord$1(raw)["license"]);
8878
+ const changelogNote = asText(asRecord$1(raw)["changelogNote"]);
8879
+ const changelogType = asText(asRecord$1(raw)["changelogType"]);
8880
+ let author = asText(asRecord$1(raw)["author"]);
8881
+ const verify = asRecord$1(raw)["verify"] === true;
8882
+ const resume = asRecord$1(raw)["resume"] === true;
8883
+ if (!verify && target !== "local" && author === "" && (deps.previewOf(found.unit)?.author ?? "") === "") author = await deps.inferredAuthorOf(found.unit) ?? "";
8884
+ const argv = [
8885
+ bin,
8886
+ "release",
8887
+ "--cwd",
8888
+ found.dir,
8889
+ "--json"
8890
+ ];
8891
+ if (verify) argv.push("--verify");
8892
+ else argv.push("--yes");
8893
+ if (resume) argv.push("--resume");
8894
+ if (target !== "") argv.push("--target", target);
8895
+ if (source !== "") argv.push("--index-source", source);
8896
+ if (version !== "") argv.push("--version", version);
8897
+ if (license !== "") argv.push("--license", license);
8898
+ if (author !== "") argv.push("--author", author);
8899
+ if (changelogNote !== "") argv.push("--changelog-note", changelogNote);
8900
+ if (changelogType !== "") argv.push("--changelog-type", changelogType);
8901
+ if (asRecord$1(raw)["refresh"] === true) argv.push("--refresh");
8902
+ const result = await runShell(shell, argv.map((one) => quoteShellArg(one)).join(" "), {
8903
+ workdir: root,
8904
+ timeoutMs: PUBLISH_TIMEOUT_MS
8905
+ });
8906
+ const payload = jsonOf(result.stdout);
8907
+ if (result.exitCode === 3 && payload !== void 0) {
8908
+ const needs = asRecord$1(payload["needsUser"]);
8909
+ const stepText = asText(needs["text"]);
8910
+ const command = asText(needs["command"]);
8911
+ return plainJson({
8912
+ ...payload,
8913
+ text: [
8914
+ `还差一步:${stepText}`,
8915
+ ...command === "" ? [] : [`命令:${command}`],
8916
+ "做完跟我说「好了」,我接着发。"
8917
+ ].join("\n")
8918
+ });
8919
+ }
8920
+ if (result.exitCode === 4 && payload !== void 0) {
8921
+ const resumeInfo = asRecord$1(payload["resume"]);
8922
+ return plainJson({
8923
+ ...payload,
8924
+ text: [`发布中断在第 ${asText(resumeInfo["phase"])} 步:${asText(payload["text"])}`, `再调一次 workbench_diy_publish(unit=${found.unit}, resume=true)就能接着发。`].join("\n")
8925
+ });
8926
+ }
8927
+ if (result.exitCode !== 0 || payload === void 0) {
8928
+ const tail = `${result.stderr}\n${result.stdout}`.trim().split("\n").slice(-6).join(" | ");
8929
+ throw new Error(`发布没走通(exit ${String(result.exitCode)})${tail === "" ? "" : `:${tail}`}`);
8930
+ }
8931
+ const name = asText(payload["name"]);
8932
+ const released = asText(payload["version"]);
8933
+ const registry = asText(payload["registry"]);
8934
+ const file = asText(payload["file"]);
8935
+ const install = asText(payload["install"]);
8936
+ const git = asRecord$1(payload["git"]);
8937
+ const targetOfPayload = asText(payload["target"]);
8938
+ const text = verify ? [
8939
+ `已核过 ${name}@${released}`,
8940
+ `安装:${install}`,
8941
+ "没有重新发布。"
8942
+ ].join("\n") : payload["refresh"] === true ? [payload["refreshed"] === true ? `已刷新 ${name} 的市集元数据(版本 ${released} 不变)` : `${name} 的市集元数据本来就是这样,没动`, `市场索引:${asText(asRecord$1(payload["market"])["url"])}`].join("\n") : targetOfPayload === "tarball" ? [
8943
+ `安装包在 ${file}`,
8944
+ `本机装:${install}`,
8945
+ "文件可以从对话里带走,发给同事。"
8946
+ ].join("\n") : targetOfPayload === "git" ? [
8947
+ `已推到 ${asText(git["url"])}(tag ${asText(git["tag"])})`,
8948
+ `安装:${install}`,
8949
+ "已核过 tag 里的产物。"
8950
+ ].join("\n") : targetOfPayload === "market" ? [
8951
+ `已发到市场索引 ${asText(asRecord$1(payload["market"])["url"])}`,
8952
+ `安装:${install}`,
8953
+ "已从索引仓库侧核过产物。"
8954
+ ].join("\n") : targetOfPayload === "local" ? "没发,留在本机。" : [
8955
+ `已发布 ${name}@${released} → ${registry}`,
8956
+ `同事安装:${install}`,
8957
+ "包内容已经从源上核过(dist/host.js / client.js / cordis.patch.yml 都在)。"
8958
+ ].join("\n");
8959
+ const changelog = asRecord$1(payload["changelog"]);
8960
+ const changelogLine = asText(changelog["action"]) === "written" ? `更新日志:已写进 ${CHANGELOG_FILE} 的 ${asText(changelog["version"])}` : asText(changelog["action"]) === "kept" ? `更新日志:这一版已有(${asText(changelog["version"])}),没动` : "";
8961
+ return plainJson({
8962
+ ...payload,
8963
+ text: changelogLine === "" ? text : `${text}\n${changelogLine}`
8964
+ });
8965
+ }
8966
+ };
8967
+ return tools.register(publishTool);
8968
+ }
8969
+ //#endregion
8639
8970
  //#region src/shared/api/host/diy-recon.ts
8640
8971
  var SITE_LIMIT = 6;
8641
8972
  var COMMAND_LIMIT = 12;
@@ -9076,7 +9407,7 @@ function renderStatus(root, lines, options = {}) {
9076
9407
  }
9077
9408
  function publishStatusOf(preview) {
9078
9409
  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");
9410
+ 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
9411
  }
9081
9412
  function renderList(root, modules, shape = "bare") {
9082
9413
  if (modules.length === 0) return [
@@ -9091,7 +9422,6 @@ function renderList(root, modules, shape = "bare") {
9091
9422
  }
9092
9423
  //#endregion
9093
9424
  //#region src/modules/panels/model/diy-tools.ts
9094
- var PUBLISH_TIMEOUT_MS = 18e5;
9095
9425
  function registerDiyTools(ctx, market, panels, debug, previewOf = () => null, inferredAuthorOf = async () => void 0) {
9096
9426
  const supply = supplyPortOf(ctx);
9097
9427
  const tools = ctx.get("tools");
@@ -9111,7 +9441,7 @@ function registerDiyTools(ctx, market, panels, debug, previewOf = () => null, in
9111
9441
  required: [],
9112
9442
  additionalProperties: false
9113
9443
  },
9114
- output: OUTPUT$2,
9444
+ output: OUTPUT$3,
9115
9445
  async execute(raw, exec) {
9116
9446
  const { root } = await workspaceOf(ctx, exec);
9117
9447
  const { units } = unitsOf(ctx, root);
@@ -9154,7 +9484,7 @@ function registerDiyTools(ctx, market, panels, debug, previewOf = () => null, in
9154
9484
  } },
9155
9485
  additionalProperties: false
9156
9486
  },
9157
- output: OUTPUT$2,
9487
+ output: OUTPUT$3,
9158
9488
  async execute(raw, exec) {
9159
9489
  const { root } = await workspaceOf(ctx, exec);
9160
9490
  const { installed, units } = unitsOf(ctx, root);
@@ -9220,7 +9550,7 @@ function registerDiyTools(ctx, market, panels, debug, previewOf = () => null, in
9220
9550
  required: ["keyword"],
9221
9551
  additionalProperties: false
9222
9552
  },
9223
- output: OUTPUT$2,
9553
+ output: OUTPUT$3,
9224
9554
  async execute(raw, _exec) {
9225
9555
  const args = asRecord$1(raw);
9226
9556
  const keywords = keywordsOf(asText(args["keyword"]), asText(args["url"]));
@@ -9256,220 +9586,77 @@ function registerDiyTools(ctx, market, panels, debug, previewOf = () => null, in
9256
9586
  parameters: {
9257
9587
  type: "object",
9258
9588
  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
- },
9589
+ type: "string",
9590
+ description: "模块 id(workbench_diy_status 会给)。"
9591
+ } },
9381
9592
  required: ["unit"],
9382
9593
  additionalProperties: false
9383
9594
  },
9384
- output: OUTPUT$2,
9595
+ output: OUTPUT$3,
9385
9596
  async execute(raw, exec) {
9386
9597
  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
9598
+ const { units } = unitsOf(ctx, root);
9599
+ const found = unitAmong(units, raw, "workbench_diy_show");
9600
+ if (!found.installed) throw new Error(`${found.unit} 还没装 —— 只上板的前提是它装过了。先用 workbench_diy_install(第一次)`);
9601
+ const panel = showOrReason(panels, found.unit, root);
9602
+ const text = panel.ok ? `面板上已经打开它了(${root})—— 验收就走这一张卡(你自己走一遍)` : `面板上还没打开:${panel.reason}。这就是它的下一步(workbench_diy_status 会给出同样的判断)`;
9603
+ return plainJson({
9604
+ workspace: root,
9605
+ unit: found.unit,
9606
+ path: found.dir,
9607
+ panel,
9608
+ text
9417
9609
  });
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"]);
9610
+ }
9611
+ };
9612
+ const installTool = {
9613
+ name: DIY_INSTALL_TOOL,
9614
+ description: "把这个工作区里造好的模块装上并上到当前工作区的面板。这是持久变更,一个模块只装一次:之后改源码只需 dsh-wb-module build(宿主侧秒级热换)。",
9615
+ parameters: {
9616
+ type: "object",
9617
+ properties: { unit: {
9618
+ type: "string",
9619
+ description: "模块 id(workbench_diy_status 会给)。"
9620
+ } },
9621
+ required: ["unit"],
9622
+ additionalProperties: false
9623
+ },
9624
+ output: OUTPUT$3,
9625
+ async execute(raw, exec) {
9626
+ const { root } = await workspaceOf(ctx, exec);
9627
+ const found = unitOf(root, raw, "workbench_diy_install");
9628
+ if (found.installed) {
9629
+ const panel = showOrReason(panels, found.unit, root);
9630
+ 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
9631
  return plainJson({
9435
- ...payload,
9436
- text: [`发布中断在第 ${asText(resumeInfo["phase"])} 步:${asText(payload["text"])}`, `再调一次 workbench_diy_publish(unit=${found.unit}, resume=true)就能接着发。`].join("\n")
9632
+ workspace: root,
9633
+ unit: found.unit,
9634
+ path: found.dir,
9635
+ alreadyInstalled: true,
9636
+ panel,
9637
+ text
9437
9638
  });
9438
9639
  }
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 都在)。"
9640
+ const outcome = await market.installBundle(found.dir);
9641
+ const verdict = verdictOf(outcome.change);
9642
+ const restartRequired = outcome.change.application === "restart-required";
9643
+ const panel = outcome.change.application === "applied" ? showOrReason(panels, found.unit, root) : {
9644
+ ok: false,
9645
+ reason: `安装结果是 ${outcome.change.application},还没生效`
9646
+ };
9647
+ 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}。先把上面那句"没装成"处理掉,再谈上板。`;
9648
+ const text = [
9649
+ `${verdict}:${found.unit}(${found.name})`,
9650
+ panelText,
9651
+ "之后只改源码 + dsh-wb-module build(宿主侧与 client.js 都秒级热换 —— dsh-client-hmr 盯着每个 bundle 的 lib/client.js,重建了就换进已打开的页面):不要再装一遍。"
9470
9652
  ].join("\n");
9471
9653
  return plainJson({
9472
- ...payload,
9654
+ workspace: root,
9655
+ unit: found.unit,
9656
+ path: found.dir,
9657
+ install: outcome.change,
9658
+ module: outcome.module,
9659
+ panel,
9473
9660
  text
9474
9661
  });
9475
9662
  }
@@ -9480,13 +9667,16 @@ function registerDiyTools(ctx, market, panels, debug, previewOf = () => null, in
9480
9667
  ...registerComponentsTool(ctx, tools),
9481
9668
  tools.register(reconTool),
9482
9669
  ...registerSpecTool(ctx, tools),
9483
- ...registerChangelogTool(ctx, tools),
9484
9670
  ...registerLogTools(ctx, tools),
9485
9671
  ...registerBrowserTools(ctx, tools),
9486
9672
  ...registerDebugTools(ctx, tools, debug),
9487
9673
  tools.register(showTool),
9488
9674
  tools.register(installTool),
9489
- tools.register(publishTool)
9675
+ registerPublishTool(ctx, tools, {
9676
+ resolve: (root, raw) => unitOf(root, raw, "workbench_diy_publish"),
9677
+ previewOf,
9678
+ inferredAuthorOf
9679
+ })
9490
9680
  ];
9491
9681
  }
9492
9682
  //#endregion
@@ -9516,27 +9706,22 @@ function selectedGitSource() {
9516
9706
  return parsed.source.policy === "artifacts-in-repo" ? parsed.source : void 0;
9517
9707
  }
9518
9708
  }
9519
- function publishedEntryOf(source, name) {
9709
+ var EMPTY_INDEX = {
9710
+ format: 1,
9711
+ plugins: []
9712
+ };
9713
+ function publishedIndexOf(source) {
9520
9714
  const file = join(workbenchRoot(), "market-index", marketSourceKey(source), "worktree", source.path ?? "market.json");
9521
9715
  if (!existsSync(file)) return void 0;
9522
9716
  try {
9523
9717
  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
- };
9718
+ return parsed.ok ? parsed.index : void 0;
9534
9719
  } catch {
9535
9720
  return;
9536
9721
  }
9537
9722
  }
9538
- function publishedVersionsOf(source, name) {
9539
- return publishedEntryOf(source, name)?.versions;
9723
+ function publishedEntryOf(index, name) {
9724
+ return index?.plugins.find((one) => one.name === name);
9540
9725
  }
9541
9726
  function bumped(version, kind) {
9542
9727
  const match = SEMVER.exec(version);
@@ -9561,17 +9746,21 @@ function nextVersionOf(version, published) {
9561
9746
  if (!exists(candidate)) return candidate;
9562
9747
  }
9563
9748
  }
9564
- function marketOf(unit, dir, name, version) {
9749
+ function marketOf(dir, name, version) {
9565
9750
  const source = selectedGitSource();
9566
9751
  if (source === void 0) return void 0;
9567
- const nextVersion = nextVersionOf(version, publishedVersionsOf(source, name));
9752
+ const index = publishedIndexOf(source);
9753
+ const nextVersion = nextVersionOf(version, publishedEntryOf(index, name)?.versions.map((one) => one.version));
9568
9754
  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 }
9755
+ market: {
9756
+ url: source.url,
9757
+ ...source.ref === void 0 ? {} : { ref: source.ref },
9758
+ indexPath: source.path ?? "market.json",
9759
+ artifactPath: marketArtifactPath(name),
9760
+ files: ARTIFACTS.filter((one) => existsSync(join(dir, one))),
9761
+ ...nextVersion === void 0 ? {} : { nextVersion }
9762
+ },
9763
+ ...index === void 0 ? {} : { index }
9575
9764
  };
9576
9765
  }
9577
9766
  function moduleFactOf(root, unit) {
@@ -9630,7 +9819,7 @@ async function inferredAuthorOf(root, unit, shell) {
9630
9819
  if (fact.author !== "") return fact.author;
9631
9820
  const source = selectedGitSource();
9632
9821
  if (source !== void 0 && fact.name !== "") {
9633
- const published = publishedEntryOf(source, fact.name)?.author ?? "";
9822
+ const published = publishedEntryOf(publishedIndexOf(source), fact.name)?.author ?? "";
9634
9823
  if (published !== "") return published;
9635
9824
  }
9636
9825
  const sibling = root === null || root === void 0 ? void 0 : siblingAuthorOf(root, unit);
@@ -9656,17 +9845,24 @@ function publishPreviewOf(root, unit, options = {}) {
9656
9845
  if (name === "" || version === "") return null;
9657
9846
  const publishConfig = record["publishConfig"];
9658
9847
  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);
9848
+ const market = marketOf(found.dir, name, version);
9849
+ const target = targetVersionOf(version, (options.version ?? "").trim(), market?.market);
9661
9850
  const askedAuthor = (options.author ?? "").trim();
9662
9851
  const author = askedAuthor === "" ? authorOf(record) : askedAuthor;
9852
+ const claim = market === void 0 ? void 0 : claimOf(market.index ?? EMPTY_INDEX, {
9853
+ name,
9854
+ publisher: author,
9855
+ unit,
9856
+ artifactPath: marketArtifactPath(name)
9857
+ });
9663
9858
  return {
9664
9859
  name,
9665
9860
  version,
9666
9861
  ...author === "" ? {} : { author },
9667
9862
  ...typeof registry === "string" && registry !== "" ? { registry } : {},
9668
- ...market === void 0 ? {} : { market },
9669
- changelog: changelogOf(found.dir, target)
9863
+ ...market === void 0 ? {} : { market: market.market },
9864
+ ...claim === void 0 ? {} : { claim },
9865
+ changelog: changelogOf(found.dir, target, (options.changelog ?? "").trim(), kindOf(options.changelogType))
9670
9866
  };
9671
9867
  }
9672
9868
  function authorOf(manifest) {
@@ -9684,13 +9880,30 @@ function targetVersionOf(version, asked, market) {
9684
9880
  if (/^(major|minor|patch)$/.test(asked)) return bumped(version, asked) ?? version;
9685
9881
  return asked;
9686
9882
  }
9687
- function changelogOf(dir, version) {
9883
+ function kindOf(given) {
9884
+ const wanted = (given ?? "").trim();
9885
+ return CHANGELOG_KINDS.includes(wanted) ? wanted : "changed";
9886
+ }
9887
+ function changelogOf(dir, version, note, kind) {
9688
9888
  let text;
9689
9889
  try {
9690
9890
  text = readFileSync(join(dir, CHANGELOG_FILE), "utf8");
9691
9891
  } catch {
9692
9892
  text = void 0;
9693
9893
  }
9894
+ if (note !== "") {
9895
+ const plan = planChangelog(text ?? "", {
9896
+ version,
9897
+ kind,
9898
+ note,
9899
+ date: changelogDate()
9900
+ });
9901
+ if (plan !== void 0) return {
9902
+ version: plan.version,
9903
+ text: plan.section,
9904
+ ...plan.action === "written" ? { pending: true } : {}
9905
+ };
9906
+ }
9694
9907
  if (text === void 0 || !changelogHasEntry(text, version)) return {
9695
9908
  version,
9696
9909
  missing: true
@@ -11013,7 +11226,7 @@ function registerRecordTools(ctx, store) {
11013
11226
  required: ["url"],
11014
11227
  additionalProperties: false
11015
11228
  },
11016
- output: OUTPUT$2,
11229
+ output: OUTPUT$3,
11017
11230
  async execute(raw, exec) {
11018
11231
  const { root } = await workspaceOf(ctx, exec);
11019
11232
  const artifactsDir = artifactsDirOf(ctx, root);
@@ -11113,7 +11326,7 @@ function registerRecordTools(ctx, store) {
11113
11326
  } },
11114
11327
  additionalProperties: false
11115
11328
  },
11116
- output: OUTPUT$2,
11329
+ output: OUTPUT$3,
11117
11330
  async execute(raw, exec) {
11118
11331
  const shell = ctx.get("shell");
11119
11332
  if (shell === void 0) throw new Error("这台机器上没有 shell 服务,读不了录制");
@@ -11243,7 +11456,7 @@ function registerRecordTools(ctx, store) {
11243
11456
  },
11244
11457
  additionalProperties: false
11245
11458
  },
11246
- output: OUTPUT$2,
11459
+ output: OUTPUT$3,
11247
11460
  async execute(raw, exec) {
11248
11461
  const { root } = await workspaceOf(ctx, exec);
11249
11462
  const artifactsDir = artifactsDirOf(ctx, root);
@@ -11325,7 +11538,7 @@ function registerRecordTools(ctx, store) {
11325
11538
  properties: {},
11326
11539
  additionalProperties: false
11327
11540
  },
11328
- output: OUTPUT$2,
11541
+ output: OUTPUT$3,
11329
11542
  async execute(_raw, exec) {
11330
11543
  const shell = ctx.get("shell");
11331
11544
  if (shell === void 0) throw new Error("这台机器上没有 shell 服务,收不了工");
@@ -12566,6 +12779,7 @@ function normalizedRepo(value) {
12566
12779
  return value.trim().replace(/^git\+/, "").replace(/\.git$/, "").toLowerCase();
12567
12780
  }
12568
12781
  function isWorkbenchEntry(entry) {
12782
+ if (entry.metadata?.workbench === true) return true;
12569
12783
  const labels = marketEntryLabels(entry);
12570
12784
  if (labels.includes("workbench")) return true;
12571
12785
  return entry.metadata?.category === "workbench" && labels.length === 0;
@@ -12823,37 +13037,66 @@ function parseMarketSourcesAction(payload) {
12823
13037
  id: id.trim()
12824
13038
  };
12825
13039
  }
13040
+ if (action === "read-only") {
13041
+ const id = record["id"];
13042
+ if (typeof id !== "string" || id.trim() === "") throw new Error("read-only 需要 id");
13043
+ return {
13044
+ action,
13045
+ id: id.trim()
13046
+ };
13047
+ }
12826
13048
  if (action === "init") {
12827
13049
  const id = record["id"];
12828
13050
  if (typeof id !== "string" || id.trim() === "") throw new Error("init 需要 id");
12829
13051
  const ref = typeof record["ref"] === "string" ? record["ref"].trim() : "";
12830
13052
  if (ref !== "" && !marketBranchOk(ref)) throw new Error(`分支名不合格:${ref}`);
13053
+ const stampOnly = record["stampOnly"] === true;
12831
13054
  return {
12832
13055
  action,
12833
13056
  id: id.trim(),
12834
- ...ref === "" ? {} : { ref }
13057
+ ...ref === "" ? {} : { ref },
13058
+ ...stampOnly ? { stampOnly } : {}
12835
13059
  };
12836
13060
  }
12837
13061
  throw new Error(`不认识的索引源动作:${String(action)}`);
12838
13062
  }
13063
+ async function markSourceReadOnly(store, id) {
13064
+ const source = store.read().sources.find((one) => one.id === id);
13065
+ if (source === void 0) throw new Error("这个索引源已经不在了 —— 刷新后再试");
13066
+ if (source.kind !== "git") throw new Error("http 索引源本来就是只读");
13067
+ const next = {
13068
+ ...source,
13069
+ policy: "catalog-only"
13070
+ };
13071
+ if (source.policy !== "catalog-only") await store.apply({
13072
+ action: "save",
13073
+ source: next
13074
+ });
13075
+ return next;
13076
+ }
12839
13077
  function marketSourcesAction(store, init) {
12840
13078
  return {
12841
13079
  resource: MARKET_SOURCES_RESOURCE,
12842
13080
  async handle(payload) {
12843
13081
  const action = parseMarketSourcesAction(payload);
13082
+ if (action.action === "read-only") {
13083
+ const source = await markSourceReadOnly(store, action.id);
13084
+ return {
13085
+ ok: true,
13086
+ ...store.read(),
13087
+ source
13088
+ };
13089
+ }
12844
13090
  if (action.action === "init") {
12845
13091
  if (init === void 0) throw new Error("这台机器上没有 shell 服务,初始化不了索引仓库");
12846
13092
  const result = await init({
12847
13093
  id: action.id,
12848
- ...action.ref === void 0 ? {} : { ref: action.ref }
13094
+ ...action.ref === void 0 ? {} : { ref: action.ref },
13095
+ ...action.stampOnly === true ? { stampOnly: true } : {}
12849
13096
  });
12850
13097
  return {
12851
13098
  ok: true,
12852
- ...await store.apply({
12853
- action: "save",
12854
- source: result.source,
12855
- select: true
12856
- }),
13099
+ ...store.read(),
12857
13100
  branch: result.branch,
12858
13101
  commit: result.commit,
12859
13102
  created: result.created
@@ -12873,20 +13116,29 @@ function publishPreviewRoutes(workshop, inferredAuthorOf = async () => void 0) {
12873
13116
  const unit = (param(search, "unit") ?? "").trim();
12874
13117
  const version = (param(search, "version") ?? "").trim();
12875
13118
  const author = (param(search, "author") ?? "").trim();
13119
+ const changelog = param(search, "changelog") ?? "";
13120
+ const changelogType = (param(search, "changelogType") ?? "").trim();
12876
13121
  if (unit === "") return {
12877
13122
  ok: true,
12878
13123
  preview: null
12879
13124
  };
12880
13125
  let preview = publishPreviewOf(workshop, unit, {
12881
13126
  version,
12882
- author
13127
+ author,
13128
+ changelog,
13129
+ changelogType
12883
13130
  });
12884
13131
  if (preview !== null && author === "" && (preview.author === void 0 || preview.author === "")) {
12885
13132
  const inferred = await inferredAuthorOf(unit);
12886
- if (inferred !== void 0 && inferred !== "") preview = {
12887
- ...preview,
12888
- author: inferred
12889
- };
13133
+ if (inferred !== void 0 && inferred !== "") {
13134
+ const again = publishPreviewOf(workshop, unit, {
13135
+ version,
13136
+ author: inferred,
13137
+ changelog,
13138
+ changelogType
13139
+ });
13140
+ if (again !== null) preview = again;
13141
+ }
12890
13142
  }
12891
13143
  return {
12892
13144
  ok: true,
@@ -12899,7 +13151,6 @@ function publishPreviewRoutes(workshop, inferredAuthorOf = async () => void 0) {
12899
13151
  var DEFAULT_TTL_MS = 3e5;
12900
13152
  var DEFAULT_TIMEOUT_MS$2 = 3e4;
12901
13153
  var DEFAULT_MAX_BYTES$1 = 4194304;
12902
- var GIT_NO_PROMPT = `${quoteShellArg("env")} GIT_TERMINAL_PROMPT=0`;
12903
13154
  function messageOf$1(error) {
12904
13155
  return error instanceof Error ? error.message : String(error);
12905
13156
  }
@@ -12961,7 +13212,7 @@ function gitProblemOf(source) {
12961
13212
  };
12962
13213
  }
12963
13214
  function tail(text) {
12964
- return text.trim().split("\n").filter((line) => line !== "").slice(-3).join(" ");
13215
+ return withoutAskpassNoise(text).split("\n").filter((line) => line !== "").slice(-3).join(" ");
12965
13216
  }
12966
13217
  function classifyGitError(stderr, timedOut) {
12967
13218
  if (timedOut) return {
@@ -13065,7 +13316,7 @@ function createMarketIndexReader(options) {
13065
13316
  }
13066
13317
  };
13067
13318
  try {
13068
- const result = await runShell(shell, `${GIT_NO_PROMPT} ${line}`, { timeoutMs });
13319
+ const result = await runShell(shell, `${gitNoPromptPrefix()} ${line}`, { timeoutMs });
13069
13320
  if (result.exitCode !== 0) return {
13070
13321
  ok: false,
13071
13322
  problem: classifyGitError(result.stderr, result.timedOut),
@@ -13513,17 +13764,21 @@ function createMarketSourceInitializer(options) {
13513
13764
  if (created.exitCode !== 0) throw new Error(`建分支 ${branch} 没过:${tailLines(created.stderr, 4)}`);
13514
13765
  }
13515
13766
  const indexPath = (source.path ?? "market.json").replace(/^\/+/, "") || "market.json";
13516
- const written = [];
13517
13767
  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");
13768
+ const stampOnly = request.stampOnly === true;
13769
+ if (stampOnly && !existsSync(indexFile)) throw new Error(`仓库里还没有 ${indexPath} —— 先选中这个源,按发现页的提示把它初始化成索引`);
13770
+ const written = [];
13771
+ if (!stampOnly) {
13772
+ if (!existsSync(indexFile)) {
13773
+ mkdirSync(dirname(indexFile), { recursive: true });
13774
+ writeFileSync(indexFile, MARKET_INDEX_SEED);
13775
+ written.push(indexPath);
13776
+ }
13777
+ const readmeFile = join(workdir, "README.md");
13778
+ if (!existsSync(readmeFile)) {
13779
+ writeFileSync(readmeFile, MARKET_INDEX_README_LINES.join("\n"));
13780
+ written.push("README.md");
13781
+ }
13527
13782
  }
13528
13783
  if (written.length > 0) {
13529
13784
  const added = await git(["add", ...written], workdir);
@@ -13550,8 +13805,7 @@ function createMarketSourceInitializer(options) {
13550
13805
  };
13551
13806
  await options.store.apply({
13552
13807
  action: "save",
13553
- source: next,
13554
- select: true
13808
+ source: next
13555
13809
  });
13556
13810
  forget(source, next);
13557
13811
  return {
@@ -13678,6 +13932,73 @@ async function dispatchMarket(market, request) {
13678
13932
  };
13679
13933
  }
13680
13934
  //#endregion
13935
+ //#region src/modules/panels/model/market-sources-tool.ts
13936
+ var OUTPUT$2 = {
13937
+ schema: {
13938
+ type: "object",
13939
+ properties: {
13940
+ text: { type: "string" },
13941
+ sources: {
13942
+ type: "array",
13943
+ items: {
13944
+ type: "object",
13945
+ additionalProperties: true
13946
+ }
13947
+ }
13948
+ },
13949
+ additionalProperties: true
13950
+ },
13951
+ render: (_args, value) => {
13952
+ const payload = value;
13953
+ return [{
13954
+ type: "text",
13955
+ text: typeof payload?.text === "string" ? payload.text : JSON.stringify(value)
13956
+ }];
13957
+ }
13958
+ };
13959
+ function marketSourcesTool(read) {
13960
+ return {
13961
+ name: MARKET_SOURCES_TOOL,
13962
+ description: "看本机的索引源:id、地址、是否可发布、当前用的是哪个。",
13963
+ parameters: {
13964
+ type: "object",
13965
+ properties: {},
13966
+ additionalProperties: false
13967
+ },
13968
+ output: OUTPUT$2,
13969
+ async execute() {
13970
+ if (read === void 0) throw new Error("这台机器上没有索引源服务,看不了");
13971
+ const payload = read();
13972
+ const rows = payload.sources.map((source) => ({
13973
+ id: source.id,
13974
+ label: source.label,
13975
+ kind: source.kind,
13976
+ url: source.url,
13977
+ policy: source.policy ?? "catalog-only",
13978
+ ref: source.ref ?? null,
13979
+ path: source.path ?? null,
13980
+ selected: source.id === payload.selected
13981
+ }));
13982
+ if (rows.length === 0) return {
13983
+ sources: rows,
13984
+ selected: payload.selected,
13985
+ text: "这台机器还没有索引源 —— 在「模块市场 → 发现 → 索引源」里加一个。"
13986
+ };
13987
+ const current = rows.find((one) => one.selected);
13988
+ const head = current === void 0 ? "当前没有选中索引源" : `当前:${current.label}(${current.url})`;
13989
+ const lines = rows.map((one) => {
13990
+ const state = one.policy === "artifacts-in-repo" ? "可发布" : "仅读取模块列表";
13991
+ return `${one.selected ? "●" : "○"} ${one.id} · ${one.label} · ${one.kind} · ${state} · ${one.url}`;
13992
+ });
13993
+ return {
13994
+ sources: rows,
13995
+ selected: payload.selected,
13996
+ text: [head, ...lines].join("\n")
13997
+ };
13998
+ }
13999
+ };
14000
+ }
14001
+ //#endregion
13681
14002
  //#region src/modules/panels/model/market-tools.ts
13682
14003
  function supplyNamesOf(entry) {
13683
14004
  return entry.needs.filter((need) => need.kind === "extension" || need.kind === "lib").map((need) => need.name);
@@ -13773,7 +14094,7 @@ function usersOf(shared, names) {
13773
14094
  if (hit === void 0 || hit.usedBy.length === 0) return "";
13774
14095
  return `它被 ${hit.usedBy.join("、")} 用着(动的是它们的供给,它们的状态跟着重探了一遍)`;
13775
14096
  }
13776
- function registerMarketTools(ctx, market) {
14097
+ function registerMarketTools(ctx, market, sources) {
13777
14098
  const tools = ctx.get("tools");
13778
14099
  if (tools === void 0) {
13779
14100
  ctx.logger?.info?.("[dsh-workbench] 没有 tools 服务,跳过模块市场工具注册");
@@ -13972,6 +14293,82 @@ function registerMarketTools(ctx, market) {
13972
14293
  };
13973
14294
  }
13974
14295
  };
14296
+ const enableTool = {
14297
+ name: MARKET_ENABLE_TOOL,
14298
+ description: "把索引源设为可发布(只改本机配置,不碰仓库);要用户审批。",
14299
+ parameters: {
14300
+ type: "object",
14301
+ properties: { id: {
14302
+ type: "string",
14303
+ description: "索引源 id;不填=当前选中的那个。"
14304
+ } },
14305
+ additionalProperties: false
14306
+ },
14307
+ output: OUTPUT$1,
14308
+ async execute(raw) {
14309
+ if (sources === void 0) throw new Error("这台机器上没有索引源服务,设不了可发布");
14310
+ const payload = sources.read();
14311
+ const asked = textArg(raw, "id");
14312
+ const id = asked === "" ? payload.selected : asked;
14313
+ const source = id === null ? void 0 : payload.sources.find((one) => one.id === id);
14314
+ if (source === void 0) {
14315
+ const known = payload.sources.map((one) => one.id).join("、") || "(空)";
14316
+ throw new Error(`没有这个索引源${asked === "" ? "" : `:${asked}`}。现有的:${known};先在「模块市场 → 发现 → 索引源」里加一个。`);
14317
+ }
14318
+ if (source.kind !== "git") throw new Error(`索引源 ${source.id} 是 http 地址,只能读,不能当发布目的地`);
14319
+ if (source.policy === "artifacts-in-repo") return {
14320
+ source,
14321
+ changed: false,
14322
+ text: `已经是可发布:${source.label}(${source.url})`
14323
+ };
14324
+ const result = await sources.init({
14325
+ id: source.id,
14326
+ stampOnly: true
14327
+ });
14328
+ return {
14329
+ source: result.source,
14330
+ branch: result.branch,
14331
+ commit: result.commit,
14332
+ changed: true,
14333
+ text: `已把「${source.label}」设为可发布(${source.url},分支 ${result.branch})—— 仓库本身没动。之后发布可以落到它的 ${source.path ?? "market.json"}。`
14334
+ };
14335
+ }
14336
+ };
14337
+ const readOnlyTool = {
14338
+ name: MARKET_DISABLE_TOOL,
14339
+ description: "把索引源改回只读(不再作为发布目的地;只改本机配置)。",
14340
+ parameters: {
14341
+ type: "object",
14342
+ properties: { id: {
14343
+ type: "string",
14344
+ description: "索引源 id;不填=当前选中的那个。"
14345
+ } },
14346
+ additionalProperties: false
14347
+ },
14348
+ output: OUTPUT$1,
14349
+ async execute(raw) {
14350
+ if (sources === void 0) throw new Error("这台机器上没有索引源服务,改不了");
14351
+ const payload = sources.read();
14352
+ const asked = textArg(raw, "id");
14353
+ const id = asked === "" ? payload.selected : asked;
14354
+ const source = id === null ? void 0 : payload.sources.find((one) => one.id === id);
14355
+ if (source === void 0) {
14356
+ const known = payload.sources.map((one) => one.id).join("、") || "(空)";
14357
+ throw new Error(`没有这个索引源${asked === "" ? "" : `:${asked}`}。现有的:${known};先在「模块市场 → 发现 → 索引源」里加一个。`);
14358
+ }
14359
+ if (source.kind !== "git") throw new Error(`索引源 ${source.id} 是 http 地址,本来就是只读`);
14360
+ if (source.policy === "catalog-only") return {
14361
+ source,
14362
+ changed: false,
14363
+ text: `已经是只读:${source.label}(${source.url})`
14364
+ };
14365
+ return {
14366
+ source: await sources.readOnly(source.id),
14367
+ changed: true,
14368
+ text: `已把「${source.label}」设为只读(${source.url})—— 发布不会再落到它。`
14369
+ };
14370
+ }
14371
+ };
13975
14372
  return [
13976
14373
  tools.register(listTool),
13977
14374
  tools.register(installTool),
@@ -13981,7 +14378,10 @@ function registerMarketTools(ctx, market) {
13981
14378
  tools.register(updateDependencyTool),
13982
14379
  tools.register(updatePluginTool),
13983
14380
  tools.register(uninstallPluginTool),
13984
- tools.register(updateWorkbenchTool)
14381
+ tools.register(updateWorkbenchTool),
14382
+ tools.register(marketSourcesTool(sources === void 0 ? void 0 : () => sources.read())),
14383
+ tools.register(enableTool),
14384
+ tools.register(readOnlyTool)
13985
14385
  ];
13986
14386
  }
13987
14387
  //#endregion
@@ -14099,6 +14499,7 @@ function listedRunUnits(result) {
14099
14499
  }
14100
14500
  function panelsFactsOf(call, manifests) {
14101
14501
  if (call.source === "workbench_panels_list") return [PANELS_FACT];
14502
+ if (call.source === "market-sources") return [PANELS_FACT];
14102
14503
  if (call.source === "workbench_long_tasks_list") return listedRunUnits(call.result).map(runsRouteOf);
14103
14504
  if (call.source === "workbench_modules_list") return [PANELS_FACT, CAPABILITY_FACT];
14104
14505
  if (call.source === "workbench_modules_update_workbench") return [PANELS_FACT, CAPABILITY_FACT];
@@ -14793,13 +15194,17 @@ var OWNED = [{
14793
15194
  },
14794
15195
  registerTools: () => [
14795
15196
  ...registerPanelTools(ctx, panels, values),
14796
- ...registerMarketTools(ctx, market),
15197
+ ...registerMarketTools(ctx, market, {
15198
+ read: () => marketSources.read(),
15199
+ init: marketInit,
15200
+ readOnly: (id) => markSourceReadOnly(marketSources, id)
15201
+ }),
14797
15202
  ...registerDiyTools(ctx, market, panels, debug, (unit) => publishPreviewOf(workshopPathOf(ctx), unit), (unit) => inferredAuthorOf(workshopPathOf(ctx), unit, shell)),
14798
15203
  ...registerRecordTools(ctx, record),
14799
15204
  ...registerLongTaskTools(ctx, longTaskRuns)
14800
15205
  ],
14801
15206
  factsOf: (call) => panelsFactsOf(call, manifests()),
14802
- needsApproval: panelsApprovalFor(longTaskRuns, () => record.live(Date.now()), (unit) => publishPreviewOf(workshopPathOf(ctx), unit)),
15207
+ needsApproval: panelsApprovalFor(longTaskRuns, () => record.live(Date.now()), (unit, author) => publishPreviewOf(workshopPathOf(ctx), unit, author === void 0 ? {} : { author }), marketSources),
14803
15208
  gate: {
14804
15209
  deniedTools: (context) => {
14805
15210
  const boards = panels.boards(context.workspace);