@dsh-workbench/core 1.0.1 → 1.1.1

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 (39) hide show
  1. package/README.md +3 -0
  2. package/client.js +849 -460
  3. package/dist/client/client.body.js +849 -460
  4. package/dist/client/index.js +720 -331
  5. package/dist/host/index.js +448 -41
  6. package/dist/types/app/layouts/market/discover-search.d.ts +8 -0
  7. package/dist/types/app/layouts/market/discover-sources-dialog.d.ts +4 -0
  8. package/dist/types/app/layouts/market/discover-states.d.ts +3 -0
  9. package/dist/types/app/layouts/market/use-add-module.d.ts +0 -1
  10. package/dist/types/app/layouts/market/use-discover.d.ts +1 -0
  11. package/dist/types/app/layouts/rightbar/MarketView.d.ts +3 -1
  12. package/dist/types/app/layouts/rightbar/market-names.d.ts +8 -0
  13. package/dist/types/app/layouts/rightbar/market-row-types.d.ts +11 -3
  14. package/dist/types/app/layouts/rightbar/market-rows.d.ts +6 -4
  15. package/dist/types/app/layouts/rightbar/market-view-text.d.ts +4 -3
  16. package/dist/types/app/units/unit-titles.d.ts +3 -0
  17. package/dist/types/kernel/contract/bundle-install.d.ts +1 -1
  18. package/dist/types/kernel/contract/market-index.d.ts +1 -1
  19. package/dist/types/modules/panels/model/diy-tools.d.ts +1 -1
  20. package/dist/types/modules/panels/model/market-dispatch.d.ts +8 -0
  21. package/dist/types/modules/panels/model/market-follow-up.d.ts +16 -0
  22. package/dist/types/modules/panels/model/market-sources-act.d.ts +2 -0
  23. package/dist/types/modules/panels/model/market-types.d.ts +5 -0
  24. package/dist/types/modules/panels/model/market.d.ts +2 -0
  25. package/dist/types/modules/panels/model/publish-preview-routes.d.ts +1 -1
  26. package/dist/types/shared/api/host/diy-publish-preview.d.ts +11 -0
  27. package/dist/types/shared/api/host/follow-up-dismiss.d.ts +2 -0
  28. package/dist/types/shared/api/host/migrate-workbench.d.ts +20 -0
  29. package/dist/types/shared/api/host/migration-follow-up.d.ts +12 -0
  30. package/dist/types/shared/hooks/market-queries.d.ts +3 -2
  31. package/dist/types/shared/hooks/resource-view.d.ts +14 -0
  32. package/dist/types/shared/hooks/use-api-resource.d.ts +3 -7
  33. package/package.json +3 -3
  34. package/skills/workbench-module-diy/FAMILY.md +1 -1
  35. package/skills/workbench-module-diy/REVIEW.md +2 -2
  36. package/skills/workbench-module-diy/SKILL.md +1 -0
  37. package/skills/workbench-module-diy/TOOLS.md +1 -1
  38. package/skills/workbench-module-diy/WIRING.md +3 -1
  39. package/skills/workbench-module-diy/WRITES.md +1 -1
@@ -5,8 +5,9 @@ import { createHash, randomUUID } from "node:crypto";
5
5
  import fs, { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
6
6
  import { createRequire } from "node:module";
7
7
  import { fileURLToPath, pathToFileURL } from "node:url";
8
- import { deflateSync, inflateSync } from "node:zlib";
9
8
  import writeFileAtomic from "write-file-atomic";
9
+ import { deflateSync, inflateSync } from "node:zlib";
10
+ import { execa } from "execa";
10
11
  //#region \0rolldown/runtime.js
11
12
  var __create = Object.create;
12
13
  var __defProp = Object.defineProperty;
@@ -86,6 +87,14 @@ function encodeRef(ref) {
86
87
  //#endregion
87
88
  //#region ../shared/dist/contract/unit.js
88
89
  /**
90
+ * 外壳替模块名合成的词表键:模块自己不必写名字键,名字直接从清单里的 title 注册进来。
91
+ * @param unit 单元 id。
92
+ * @returns 该单元名字在词表里的键。
93
+ */
94
+ function unitTitleKey(unit) {
95
+ return `dsh-workbench.unit.${unit}.title`;
96
+ }
97
+ /**
89
98
  * 模块声明的全部环境需要(没写就是空表)。
90
99
  * @param manifest 模块清单。
91
100
  * @returns need 列表。
@@ -382,13 +391,14 @@ function boardSituation(probe, workshop) {
382
391
  /**
383
392
  * 外壳愿意认的协议版本;不在表里的模块当场被拒,不猜语义。
384
393
  */
385
- var SUPPORTED_API_VERSIONS = [2];
394
+ var SUPPORTED_API_VERSIONS = [3];
386
395
  /**
387
396
  * 协议认得的全部键;写了别的键这份清单会被拒(`protocol` 判据与运行时同一套),拼错当场报出来。
388
397
  */
389
398
  var DECLARATION_KEYS = [
390
399
  "apiVersion",
391
400
  "unit",
401
+ "title",
392
402
  "card",
393
403
  "tools",
394
404
  "host",
@@ -400,11 +410,13 @@ var DECLARATION_KEYS = [
400
410
  var DECLARATION_REQUIRED_KEYS = [
401
411
  "apiVersion",
402
412
  "unit",
413
+ "title",
403
414
  "card",
404
415
  "tools",
405
416
  "host"
406
417
  ];
407
- var CARD_KEYS = ["titleKey", "order"];
418
+ var CARD_KEYS = ["order"];
419
+ var TITLE_KEYS = ["zh", "en"];
408
420
  var TOOL_KEYS = [
409
421
  "name",
410
422
  "writes",
@@ -418,6 +430,16 @@ function text$2(value) {
418
430
  function isRecord(value) {
419
431
  return typeof value === "object" && value !== null && !Array.isArray(value);
420
432
  }
433
+ /**
434
+ * 读一份声明里的协议版本;不是对象或 apiVersion 不是整数时给 undefined。
435
+ * @param raw 清单里 dsh.workbench 那段原始值。
436
+ * @returns 声明声明的协议版本,读不出来时 undefined。
437
+ */
438
+ function declarationApiVersionOf(raw) {
439
+ if (!isRecord(raw)) return void 0;
440
+ const version = raw.apiVersion;
441
+ return Number.isInteger(version) ? version : void 0;
442
+ }
421
443
  function outsideKeys(value, allowed) {
422
444
  return Object.keys(value).filter((key) => !allowed.includes(key));
423
445
  }
@@ -437,22 +459,30 @@ function stringArrayProblems(at, value) {
437
459
  function declarationProblemsOf(subject, raw) {
438
460
  if (!isRecord(raw)) return [`${subject}: dsh.workbench 不是一个对象 —— ${JSON.stringify(raw)}`];
439
461
  const version = raw.apiVersion;
440
- if (version === void 0) return [`${subject}: dsh.workbench 没有 apiVersion —— 协议从 v2 起,写 \`"apiVersion": 2\`。缺它 = 外壳不认这个模块`];
462
+ if (version === void 0) return [`${subject}: dsh.workbench 没有 apiVersion —— 协议从 v3 起,写 \`"apiVersion": 3\`。缺它 = 外壳不认这个模块`];
441
463
  if (!Number.isInteger(version) || !SUPPORTED_API_VERSIONS.includes(version)) {
442
- const hint = version === 1 ? " —— v2 把 `env` 的三张表(command / plugins / libs)换成了 `needs`:把外部需要逐条写成 `{ kind, name, purpose, ... }`(kind 是 bin / extension / lib / env),再把 apiVersion 改成 2" : "";
464
+ const hint = version === 2 ? " —— v3 把卡片标题挪成了顶层 `title`(`{ \"zh\": \"…\", \"en\": \"…\" }` 字面量):把 `card.titleKey` 删掉、在顶层写 title,再把 apiVersion 改成 3" : "";
443
465
  return [`${subject}: dsh.workbench.apiVersion 是 ${JSON.stringify(version)},这里只认 ${SUPPORTED_API_VERSIONS.join(" / ")}(要整数)${hint}`];
444
466
  }
445
467
  const problems = [];
446
468
  const unknown = outsideKeys(raw, DECLARATION_KEYS);
447
- if (unknown.length > 0) problems.push(`${subject}: dsh.workbench 里有协议 v2 不认识的键 ${listOf(unknown)} —— 只有 ${listOf(DECLARATION_KEYS)}。(协议外的键会让这份清单被拒:那句声明等于没写)`);
469
+ if (unknown.length > 0) problems.push(`${subject}: dsh.workbench 里有协议 v3 不认识的键 ${listOf(unknown)} —— 只有 ${listOf(DECLARATION_KEYS)}。(协议外的键会让这份清单被拒:那句声明等于没写)`);
448
470
  for (const key of DECLARATION_REQUIRED_KEYS) if (raw[key] === void 0) problems.push(`${subject}: dsh.workbench 缺 ${key}` + (key === "card" ? "(这个模块没有卡片就写 `\"card\": null`)" : ""));
471
+ const title = raw.title;
472
+ if (title !== void 0) {
473
+ if (!isRecord(title)) problems.push(`${subject}: dsh.workbench.title 要是对象(中英各一份),现在是 ${JSON.stringify(title)}`);
474
+ else {
475
+ const extra = outsideKeys(title, TITLE_KEYS);
476
+ if (extra.length > 0) problems.push(`${subject}: dsh.workbench.title 里有不认的键 ${listOf(extra)} —— title 只有 ${listOf(TITLE_KEYS)}`);
477
+ for (const language of TITLE_KEYS) if (text$2(title[language]) === "") problems.push(`${subject}: dsh.workbench.title.${language} 要是非空字符串(市场和面板里显示的名字)`);
478
+ }
479
+ }
449
480
  const card = raw.card;
450
481
  if (card !== void 0 && card !== null) {
451
482
  if (!isRecord(card)) problems.push(`${subject}: dsh.workbench.card 要么是对象、要么是 null,现在是 ${JSON.stringify(card)}`);
452
483
  else {
453
484
  const extra = outsideKeys(card, CARD_KEYS);
454
- if (extra.length > 0) problems.push(`${subject}: dsh.workbench.card 里有不认的键 ${listOf(extra)} —— card 只有 ${listOf(CARD_KEYS)}(卡片默认显不显示由运行期的面板配置决定,清单里没有这一位)`);
455
- if (text$2(card.titleKey) === "") problems.push(`${subject}: dsh.workbench.card.titleKey 要是非空字符串(面板那一格的文案键)`);
485
+ if (extra.length > 0) problems.push(`${subject}: dsh.workbench.card 里有不认的键 ${listOf(extra)} —— card 只有 ${listOf(CARD_KEYS)}(名字写在顶层的 title 里;卡片默认显不显示由运行期的面板配置决定,清单里没有这一位)`);
456
486
  if (typeof card.order !== "number" || !Number.isFinite(card.order)) problems.push(`${subject}: dsh.workbench.card.order 要是数字,现在是 ${JSON.stringify(card.order)}`);
457
487
  }
458
488
  }
@@ -516,15 +546,18 @@ function readDeclaration(subject, raw) {
516
546
  warnings: needsRead.warnings
517
547
  };
518
548
  const card = declaration.card;
549
+ const id = text$2(declaration.unit);
519
550
  return {
520
551
  declaration,
521
552
  problems: [],
522
553
  manifest: {
523
- id: text$2(declaration.unit),
524
- card: card === null ? null : {
525
- titleKey: text$2(card.titleKey),
526
- order: card.order
554
+ id,
555
+ title: {
556
+ zh: text$2(declaration.title.zh),
557
+ en: text$2(declaration.title.en)
527
558
  },
559
+ titleKey: unitTitleKey(id),
560
+ card: card === null ? null : { order: card.order },
528
561
  tools: declaration.tools.map((tool) => ({
529
562
  name: text$2(tool.name),
530
563
  writes: tool.writes
@@ -1192,7 +1225,9 @@ var PROVISION_ACTIONS = [
1192
1225
  "update-dependency",
1193
1226
  "update-plugin",
1194
1227
  "uninstall-plugin",
1195
- "update-workbench"
1228
+ "update-workbench",
1229
+ "migrate-workbench",
1230
+ "dismiss-followup"
1196
1231
  ];
1197
1232
  var DEPENDENCY_ACTIONS = ["update-dependency"];
1198
1233
  function isDependencyAction(action) {
@@ -1203,7 +1238,8 @@ function provisionTargetOf(action) {
1203
1238
  if (action === "install-bundle" || action === "install-version") return "spec";
1204
1239
  if (action === "remove-bundle") return "name";
1205
1240
  if (action === "update-plugin" || action === "uninstall-plugin") return "name";
1206
- if (action === "update-workbench") return "name";
1241
+ if (action === "update-workbench" || action === "migrate-workbench") return "name";
1242
+ if (action === "dismiss-followup") return "id";
1207
1243
  if (action === "cancel-install") return "requestId";
1208
1244
  return "unit";
1209
1245
  }
@@ -1239,10 +1275,14 @@ function requestOf$1(action, value) {
1239
1275
  action,
1240
1276
  name: value
1241
1277
  };
1242
- if (action === "update-workbench") return {
1278
+ if (action === "update-workbench" || action === "migrate-workbench") return {
1243
1279
  action,
1244
1280
  name: value
1245
1281
  };
1282
+ if (action === "dismiss-followup") return {
1283
+ action,
1284
+ id: value
1285
+ };
1246
1286
  if (action === "cancel-install") return {
1247
1287
  action,
1248
1288
  requestId: value
@@ -3990,6 +4030,31 @@ function refusalReason() {
3990
4030
  return "这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上";
3991
4031
  }
3992
4032
  //#endregion
4033
+ //#region ../shared/dist/host/plugin-source.js
4034
+ function envValue(env, name) {
4035
+ const value = env[name]?.trim();
4036
+ return value === void 0 || value === "" ? { ok: false } : {
4037
+ ok: true,
4038
+ value
4039
+ };
4040
+ }
4041
+ function npmRegistryOf(source, env) {
4042
+ const name = source.registryEnv ?? "";
4043
+ if (name !== "") {
4044
+ const fromEnv = envValue(env, name);
4045
+ if (fromEnv.ok) return fromEnv;
4046
+ }
4047
+ const fallback = source.registryDefault?.trim();
4048
+ if (fallback === void 0 || fallback === "") return {
4049
+ ok: false,
4050
+ env: name
4051
+ };
4052
+ return {
4053
+ ok: true,
4054
+ value: fallback
4055
+ };
4056
+ }
4057
+ //#endregion
3993
4058
  //#region ../shared/dist/host/supply-index.js
3994
4059
  function text$1(value) {
3995
4060
  return value === void 0 ? "" : value.trim();
@@ -4015,24 +4080,28 @@ function packageOf(need) {
4015
4080
  function keyOf(kind, pkg) {
4016
4081
  return `${kind}:${pkg}`;
4017
4082
  }
4018
- function sourceCompatible(left, right) {
4083
+ function registryAddressOf(source, env) {
4084
+ const resolved = npmRegistryOf(source, env);
4085
+ return resolved.ok ? resolved.value : void 0;
4086
+ }
4087
+ function sourceCompatible(left, right, env) {
4019
4088
  if (left.kind !== right.kind) return false;
4020
4089
  if (left.kind === "npm" && right.kind === "npm") {
4021
4090
  if (text$1(left.package) !== "" && text$1(right.package) !== "" && text$1(left.package) !== text$1(right.package)) return false;
4022
- if (text$1(left.registryEnv) !== text$1(right.registryEnv)) return false;
4023
- const l = text$1(left.registryDefault);
4024
- const r = text$1(right.registryDefault);
4025
- return l === "" || r === "" || l === r;
4091
+ const l = registryAddressOf(left, env);
4092
+ const r = registryAddressOf(right, env);
4093
+ if (l !== void 0 && r !== void 0) return l === r;
4094
+ return text$1(left.registryEnv) === text$1(right.registryEnv);
4026
4095
  }
4027
4096
  if (left.kind === "git" && right.kind === "git") return text$1(left.url) === text$1(right.url);
4028
4097
  if (left.kind === "local" && right.kind === "local") return text$1(left.env) === text$1(right.env) && text$1(left.path) === text$1(right.path);
4029
4098
  return false;
4030
4099
  }
4031
- function sourcesCompatible(left, right) {
4100
+ function sourcesCompatible(left, right, env) {
4032
4101
  if (left.length !== right.length) return false;
4033
4102
  return left.every((source, index) => {
4034
4103
  const other = right[index];
4035
- return other !== void 0 && sourceCompatible(source, other);
4104
+ return other !== void 0 && sourceCompatible(source, other, env);
4036
4105
  });
4037
4106
  }
4038
4107
  function mergedSource(left, right) {
@@ -4047,13 +4116,13 @@ function mergedSource(left, right) {
4047
4116
  }
4048
4117
  return left;
4049
4118
  }
4050
- function mergeSources(entry, sources) {
4119
+ function mergeSources(entry, sources, env) {
4051
4120
  if (!entry.declared) {
4052
4121
  entry.declared = true;
4053
4122
  entry.sources = [...sources];
4054
4123
  return true;
4055
4124
  }
4056
- if (!sourcesCompatible(entry.sources, sources)) {
4125
+ if (!sourcesCompatible(entry.sources, sources, env)) {
4057
4126
  entry.conflicted = true;
4058
4127
  return false;
4059
4128
  }
@@ -4072,7 +4141,8 @@ function addDeclarer(entry, unit, commands) {
4072
4141
  if (!entry.declarers.includes(unit)) entry.declarers.push(unit);
4073
4142
  for (const command of commands) if (!entry.commands.includes(command)) entry.commands.push(command);
4074
4143
  }
4075
- function buildSupplyIndex(declarations) {
4144
+ function buildSupplyIndex(declarations, options = {}) {
4145
+ const env = options.env ?? {};
4076
4146
  const entries = /* @__PURE__ */ new Map();
4077
4147
  const byName = /* @__PURE__ */ new Map();
4078
4148
  const conflicts = [];
@@ -4103,7 +4173,7 @@ function buildSupplyIndex(declarations) {
4103
4173
  entry.runtime = need.runtime;
4104
4174
  addDeclarer(entry, declaration.id, need.commands ?? []);
4105
4175
  } else addDeclarer(entry, declaration.id, []);
4106
- if (!mergeSources(entry, need.sources ?? [])) conflicts.push({
4176
+ if (!mergeSources(entry, need.sources ?? [], env)) conflicts.push({
4107
4177
  kind: "source-mismatch",
4108
4178
  key: entry.key,
4109
4179
  names: [...entry.names],
@@ -4477,6 +4547,7 @@ var MARKET_DISCOVER_ROUTE = "panels/market-discover";
4477
4547
  var MARKET_DOC_ROUTE = "panels/market-doc";
4478
4548
  var MARKET_PUBLISH_PREVIEW_ROUTE = "panels/market-publish-preview";
4479
4549
  var MARKET_SOURCES_RESOURCE = "market-sources";
4550
+ var MARKET_SOURCES_ROUTE = "panels/market-sources";
4480
4551
  //#endregion
4481
4552
  //#region ../shared/dist/lib/market-index.js
4482
4553
  var BRANCH = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;
@@ -5010,6 +5081,94 @@ var BUNDLE_REGISTRIES_ROUTE = "bundle-registries";
5010
5081
  var BUNDLE_INSPECT_ROUTE = "bundle-inspect";
5011
5082
  var MODULE_VERSIONS_ROUTE = "module-versions";
5012
5083
  //#endregion
5084
+ //#region src/shared/api/host/follow-up-dismiss.ts
5085
+ var DISMISS_FILE = "follow-ups.json";
5086
+ function fileOf$1(runtimeRoot) {
5087
+ return join(runtimeRoot, DISMISS_FILE);
5088
+ }
5089
+ function readDismissed(runtimeRoot) {
5090
+ try {
5091
+ const value = JSON.parse(readFileSync(fileOf$1(runtimeRoot), "utf8"));
5092
+ if (typeof value !== "object" || value === null) return {};
5093
+ const dismissed = value["dismissed"];
5094
+ if (typeof dismissed !== "object" || dismissed === null) return {};
5095
+ return dismissed;
5096
+ } catch {
5097
+ return {};
5098
+ }
5099
+ }
5100
+ function isFollowUpDismissed(runtimeRoot, id) {
5101
+ return Object.prototype.hasOwnProperty.call(readDismissed(runtimeRoot), id);
5102
+ }
5103
+ function dismissFollowUp(runtimeRoot, id) {
5104
+ const file = fileOf$1(runtimeRoot);
5105
+ mkdirSync(dirname(file), { recursive: true });
5106
+ const dismissed = {
5107
+ ...readDismissed(runtimeRoot),
5108
+ [id]: { at: (/* @__PURE__ */ new Date()).toISOString() }
5109
+ };
5110
+ writeFileAtomic.sync(file, `${JSON.stringify({ dismissed }, null, 2)}\n`);
5111
+ }
5112
+ //#endregion
5113
+ //#region src/shared/api/host/migration-follow-up.ts
5114
+ var MIGRATION_FOLLOW_UP_ID = "data-root-migration";
5115
+ function hasEntries(dir) {
5116
+ try {
5117
+ return readdirSync(dir).some((name) => name !== ".DS_Store");
5118
+ } catch {
5119
+ return false;
5120
+ }
5121
+ }
5122
+ function legacyDataIn(root) {
5123
+ return existsSync(join(root, "storages", "workspace.json")) || hasEntries(join(root, "sessions")) || hasEntries(join(root, "attachments"));
5124
+ }
5125
+ function pathsIn(file) {
5126
+ try {
5127
+ const value = JSON.parse(readFileSync(file, "utf8"));
5128
+ if (typeof value !== "object" || value === null) return /* @__PURE__ */ new Set();
5129
+ const tables = value["tables"];
5130
+ if (typeof tables !== "object" || tables === null) return /* @__PURE__ */ new Set();
5131
+ const workspaces = tables["workspaces"];
5132
+ if (typeof workspaces !== "object" || workspaces === null) return /* @__PURE__ */ new Set();
5133
+ const paths = /* @__PURE__ */ new Set();
5134
+ for (const entry of Object.values(workspaces)) {
5135
+ if (typeof entry !== "object" || entry === null) continue;
5136
+ const path = entry["path"];
5137
+ if (typeof path === "string") paths.add(path);
5138
+ }
5139
+ return paths;
5140
+ } catch {
5141
+ return /* @__PURE__ */ new Set();
5142
+ }
5143
+ }
5144
+ function hasMissingPaths(sourceFile, targetFile) {
5145
+ const source = pathsIn(sourceFile);
5146
+ if (source.size === 0) return false;
5147
+ const target = pathsIn(targetFile);
5148
+ for (const path of source) if (!target.has(path)) return true;
5149
+ return false;
5150
+ }
5151
+ function migrationFollowUp(input) {
5152
+ const runtimeRoot = input.runtimeRoot ?? join(input.home, input.profile);
5153
+ if (resolve(runtimeRoot) === resolve(input.home)) return void 0;
5154
+ if (isFollowUpDismissed(runtimeRoot, "data-root-migration")) return void 0;
5155
+ if (!legacyDataIn(runtimeRoot)) return void 0;
5156
+ const marker = join(runtimeRoot, ".data-root-shared");
5157
+ if (!existsSync(marker)) return {
5158
+ id: MIGRATION_FOLLOW_UP_ID,
5159
+ kind: "data-migration",
5160
+ hard: false
5161
+ };
5162
+ const source = join(runtimeRoot, "storages", "workspace.json");
5163
+ const target = join(input.home, "storages", "workspace.json");
5164
+ if (!existsSync(source) || !existsSync(target)) return void 0;
5165
+ return hasMissingPaths(source, target) ? {
5166
+ id: MIGRATION_FOLLOW_UP_ID,
5167
+ kind: "data-migration",
5168
+ hard: false
5169
+ } : void 0;
5170
+ }
5171
+ //#endregion
5013
5172
  //#region src/shared/api/host/bundle-routes.ts
5014
5173
  function registryOf(search) {
5015
5174
  const raw = param(search, "registry");
@@ -5033,6 +5192,25 @@ function bundleRegistriesHandler(ctx) {
5033
5192
  }
5034
5193
  var refusalLog = /* @__PURE__ */ new Map();
5035
5194
  var holdLog = /* @__PURE__ */ new Map();
5195
+ function updateBlockOf(raw, problems) {
5196
+ if (problems.length === 0) return void 0;
5197
+ const declared = declarationApiVersionOf(raw);
5198
+ const newest = Math.max(...SUPPORTED_API_VERSIONS);
5199
+ return declared !== void 0 && declared > newest ? "needs-workbench" : "incompatible";
5200
+ }
5201
+ function titleOf(raw) {
5202
+ if (typeof raw !== "object" || raw === null) return void 0;
5203
+ const title = raw.title;
5204
+ if (typeof title !== "object" || title === null || Array.isArray(title)) return void 0;
5205
+ const record = title;
5206
+ const zh = typeof record.zh === "string" ? record.zh.trim() : "";
5207
+ const en = typeof record.en === "string" ? record.en.trim() : "";
5208
+ if (zh === "" || en === "") return void 0;
5209
+ return {
5210
+ zh,
5211
+ en
5212
+ };
5213
+ }
5036
5214
  async function latestIfReleaseReady(supply, input) {
5037
5215
  const missing = await supply.missingReleaseParts({
5038
5216
  shell: input.shell,
@@ -5075,7 +5253,8 @@ async function workbenchVersion(supply, input) {
5075
5253
  installed: read.version,
5076
5254
  ...latest === void 0 ? {} : { latest },
5077
5255
  ...input.running === void 0 ? {} : { running: input.running },
5078
- ...read.local === true ? { local: true } : {}
5256
+ ...read.local === true ? { local: true } : {},
5257
+ ...input.followUp === void 0 ? {} : { followUp: input.followUp }
5079
5258
  };
5080
5259
  }
5081
5260
  function moduleVersionsHandler(ctx, shell, running, selfUpdate) {
@@ -5101,7 +5280,15 @@ function moduleVersionsHandler(ctx, shell, running, selfUpdate) {
5101
5280
  });
5102
5281
  const raw = supply.moduleDeclarationOf(dir, module.pkg);
5103
5282
  const declared = raw === void 0 ? [] : declarationProblemsOf(module.pkg, raw);
5283
+ const title = titleOf(raw);
5104
5284
  const refused = refusals.get(module.unit) ?? (declared.length === 0 ? void 0 : declared);
5285
+ const latestRaw = latest === void 0 || latest === module.version || shell === void 0 || registry === void 0 ? void 0 : await supply.registryDeclarationOf({
5286
+ shell,
5287
+ name: module.pkg,
5288
+ registry,
5289
+ force
5290
+ });
5291
+ const latestBlocked = latestRaw === void 0 ? void 0 : updateBlockOf(latestRaw, declarationProblemsOf(module.pkg, latestRaw));
5105
5292
  if (refused === void 0) refusalLog.delete(module.pkg);
5106
5293
  else {
5107
5294
  const line = refused.join(";");
@@ -5114,18 +5301,28 @@ function moduleVersionsHandler(ctx, shell, running, selfUpdate) {
5114
5301
  unit: module.unit,
5115
5302
  pkg: module.pkg,
5116
5303
  installed: module.version,
5304
+ ...title === void 0 ? {} : { title },
5117
5305
  ...latest === void 0 ? {} : { latest },
5306
+ ...latestBlocked === void 0 ? {} : { latestBlocked },
5118
5307
  ...module.local === true ? { local: true } : {},
5119
5308
  ...refused === void 0 ? {} : { refused }
5120
5309
  };
5121
5310
  }));
5122
5311
  const selfUpdateNow = selfUpdate?.();
5312
+ const profile = dir === null ? "" : basename(dir);
5313
+ const home = dir === null ? void 0 : dirname(dirname(dir));
5314
+ const followUp = profile === "" || home === void 0 ? void 0 : migrationFollowUp({
5315
+ home,
5316
+ profile,
5317
+ ...process.env.DSH_WORKBENCH_HOME === void 0 || process.env.DSH_WORKBENCH_HOME === "" ? {} : { runtimeRoot: process.env.DSH_WORKBENCH_HOME }
5318
+ });
5123
5319
  const workbench = await workbenchVersion(supply, {
5124
5320
  dir,
5125
5321
  shell,
5126
5322
  fallback,
5127
5323
  force,
5128
5324
  ...running === void 0 ? {} : { running },
5325
+ ...followUp === void 0 ? {} : { followUp },
5129
5326
  logger: ctx.logger
5130
5327
  });
5131
5328
  return {
@@ -5402,6 +5599,7 @@ function screenOnlyToolsFor(pageDebug) {
5402
5599
  }
5403
5600
  var panelsUnit = {
5404
5601
  id: "panels",
5602
+ titleKey: "panels.title",
5405
5603
  card: null,
5406
5604
  tools: [...READ_TOOLS.map((name) => ({
5407
5605
  name,
@@ -5929,7 +6127,7 @@ function resolvePanels(manifests, boards, owned = /* @__PURE__ */ new Set()) {
5929
6127
  const order = hasCard ? rank.get(manifest.id) ?? ordered.length + declared(manifest) : 0;
5930
6128
  return {
5931
6129
  id: manifest.id,
5932
- titleKey: manifest.card?.titleKey ?? manifest.id,
6130
+ titleKey: manifest.titleKey,
5933
6131
  order,
5934
6132
  shown: on,
5935
6133
  source: overrideOf(boards.workspace, manifest.id) === void 0 ? "default" : "user",
@@ -8894,7 +9092,7 @@ function renderList(root, modules, shape = "bare") {
8894
9092
  //#endregion
8895
9093
  //#region src/modules/panels/model/diy-tools.ts
8896
9094
  var PUBLISH_TIMEOUT_MS = 18e5;
8897
- function registerDiyTools(ctx, market, panels, debug, previewOf = () => null) {
9095
+ function registerDiyTools(ctx, market, panels, debug, previewOf = () => null, inferredAuthorOf = async () => void 0) {
8898
9096
  const supply = supplyPortOf(ctx);
8899
9097
  const tools = ctx.get("tools");
8900
9098
  if (tools === void 0) {
@@ -9171,6 +9369,10 @@ function registerDiyTools(ctx, market, panels, debug, previewOf = () => null) {
9171
9369
  type: "string",
9172
9370
  description: "公开发布用的许可证(MIT / Apache-2.0 / 仓库已有 LICENSE)。"
9173
9371
  },
9372
+ author: {
9373
+ type: "string",
9374
+ description: "署谁的名(姓名,或 姓名 <邮箱>);用户点了名才传,不传按模块里写的,没写用推断的署名。"
9375
+ },
9174
9376
  refresh: {
9175
9377
  type: "boolean",
9176
9378
  description: "只刷市集元数据(标题/作者/说明),不升版本、不重发产物。"
@@ -9190,8 +9392,10 @@ function registerDiyTools(ctx, market, panels, debug, previewOf = () => null) {
9190
9392
  const target = asText(asRecord$1(raw)["target"]);
9191
9393
  const version = asText(asRecord$1(raw)["version"]);
9192
9394
  const license = asText(asRecord$1(raw)["license"]);
9395
+ let author = asText(asRecord$1(raw)["author"]);
9193
9396
  const verify = asRecord$1(raw)["verify"] === true;
9194
9397
  const resume = asRecord$1(raw)["resume"] === true;
9398
+ if (!verify && target !== "local" && author === "" && (previewOf(found.unit)?.author ?? "") === "") author = await inferredAuthorOf(found.unit) ?? "";
9195
9399
  const argv = [
9196
9400
  bin,
9197
9401
  "release",
@@ -9205,6 +9409,7 @@ function registerDiyTools(ctx, market, panels, debug, previewOf = () => null) {
9205
9409
  if (target !== "") argv.push("--target", target);
9206
9410
  if (version !== "") argv.push("--version", version);
9207
9411
  if (license !== "") argv.push("--license", license);
9412
+ if (author !== "") argv.push("--author", author);
9208
9413
  if (asRecord$1(raw)["refresh"] === true) argv.push("--refresh");
9209
9414
  const result = await runShell(shell, argv.map((one) => quoteShellArg(one)).join(" "), {
9210
9415
  workdir: root,
@@ -9311,17 +9516,28 @@ function selectedGitSource() {
9311
9516
  return parsed.source.policy === "artifacts-in-repo" ? parsed.source : void 0;
9312
9517
  }
9313
9518
  }
9314
- function publishedVersionsOf(source, name) {
9519
+ function publishedEntryOf(source, name) {
9315
9520
  const file = join(workbenchRoot(), "market-index", marketSourceKey(source), "worktree", source.path ?? "market.json");
9316
9521
  if (!existsSync(file)) return void 0;
9317
9522
  try {
9318
9523
  const parsed = parseMarketIndexJson(readFileSync(file, "utf8"));
9319
9524
  if (!parsed.ok) return void 0;
9320
- return parsed.index.plugins.find((one) => one.name === name)?.versions.map((one) => one.version) ?? [];
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
+ };
9321
9534
  } catch {
9322
9535
  return;
9323
9536
  }
9324
9537
  }
9538
+ function publishedVersionsOf(source, name) {
9539
+ return publishedEntryOf(source, name)?.versions;
9540
+ }
9325
9541
  function bumped(version, kind) {
9326
9542
  const match = SEMVER.exec(version);
9327
9543
  if (match === null) return version;
@@ -9358,6 +9574,71 @@ function marketOf(unit, dir, name, version) {
9358
9574
  ...nextVersion === void 0 ? {} : { nextVersion }
9359
9575
  };
9360
9576
  }
9577
+ function moduleFactOf(root, unit) {
9578
+ if (root === null || root === void 0 || unit === "") return null;
9579
+ const found = modulesIn(root).find((one) => one.unit === unit);
9580
+ if (found === void 0) return null;
9581
+ let manifest;
9582
+ try {
9583
+ manifest = JSON.parse(readFileSync(join(found.dir, "package.json"), "utf8"));
9584
+ } catch {
9585
+ return null;
9586
+ }
9587
+ if (typeof manifest !== "object" || manifest === null) return null;
9588
+ const record = manifest;
9589
+ return {
9590
+ dir: found.dir,
9591
+ name: typeof record["name"] === "string" ? record["name"].trim() : "",
9592
+ author: authorOf(record)
9593
+ };
9594
+ }
9595
+ function siblingAuthorOf(root, unit) {
9596
+ const authors = /* @__PURE__ */ new Set();
9597
+ for (const module of modulesIn(root)) {
9598
+ if (module.unit === unit) continue;
9599
+ try {
9600
+ const manifest = JSON.parse(readFileSync(join(module.dir, "package.json"), "utf8"));
9601
+ if (typeof manifest !== "object" || manifest === null) continue;
9602
+ const author = authorOf(manifest);
9603
+ if (author !== "") authors.add(author);
9604
+ } catch {
9605
+ continue;
9606
+ }
9607
+ }
9608
+ return authors.size === 1 ? [...authors][0] : void 0;
9609
+ }
9610
+ async function localGitIdentityOf(shell, dir) {
9611
+ if (shell === void 0) return void 0;
9612
+ const read = async (argv) => {
9613
+ const result = await runShell(shell, `git -C ${quoteShellArg(dir)} ${argv}`, { workdir: dir });
9614
+ return result.exitCode === 0 ? result.stdout.trim() : "";
9615
+ };
9616
+ const name = await read(`config ${quoteShellArg("user.name")}`);
9617
+ if (name === "") return void 0;
9618
+ const email = await read(`config ${quoteShellArg("user.email")}`);
9619
+ return email === "" ? name : `${name} <${email}>`;
9620
+ }
9621
+ async function lastCommitAuthorOf(shell, dir) {
9622
+ if (shell === void 0) return void 0;
9623
+ const result = await runShell(shell, `git -C ${quoteShellArg(dir)} log -1 --format=${quoteShellArg("%an <%ae>")}`, { workdir: dir });
9624
+ const author = result.exitCode === 0 ? result.stdout.trim() : "";
9625
+ return author === "" ? void 0 : author;
9626
+ }
9627
+ async function inferredAuthorOf(root, unit, shell) {
9628
+ const fact = moduleFactOf(root, unit);
9629
+ if (fact === null) return void 0;
9630
+ if (fact.author !== "") return fact.author;
9631
+ const source = selectedGitSource();
9632
+ if (source !== void 0 && fact.name !== "") {
9633
+ const published = publishedEntryOf(source, fact.name)?.author ?? "";
9634
+ if (published !== "") return published;
9635
+ }
9636
+ const sibling = root === null || root === void 0 ? void 0 : siblingAuthorOf(root, unit);
9637
+ if (sibling !== void 0) return sibling;
9638
+ const local = await localGitIdentityOf(shell, fact.dir);
9639
+ if (local !== void 0) return local;
9640
+ return lastCommitAuthorOf(shell, fact.dir);
9641
+ }
9361
9642
  function publishPreviewOf(root, unit, options = {}) {
9362
9643
  if (root === null || root === void 0 || unit === "") return null;
9363
9644
  const found = modulesIn(root).find((one) => one.unit === unit);
@@ -9377,7 +9658,8 @@ function publishPreviewOf(root, unit, options = {}) {
9377
9658
  const registry = typeof publishConfig === "object" && publishConfig !== null ? publishConfig["registry"] : void 0;
9378
9659
  const market = marketOf(found.unit, found.dir, name, version);
9379
9660
  const target = targetVersionOf(version, (options.version ?? "").trim(), market);
9380
- const author = authorOf(record);
9661
+ const askedAuthor = (options.author ?? "").trim();
9662
+ const author = askedAuthor === "" ? authorOf(record) : askedAuthor;
9381
9663
  return {
9382
9664
  name,
9383
9665
  version,
@@ -11137,7 +11419,7 @@ function findDependency(manifests, id, isReleased) {
11137
11419
  const index = buildSupplyIndex(manifests.map((manifest) => ({
11138
11420
  id: manifest.id,
11139
11421
  needs: needsOf(manifest)
11140
- })));
11422
+ })), { env: process.env });
11141
11423
  const entry = parsed.kind === "extension" || parsed.kind === "lib" ? index.entryOf(parsed.kind, parsed.name) : void 0;
11142
11424
  const conflict = entry?.conflicted === true ? index.conflicts.find((item) => item.names.includes(parsed.name))?.detail : void 0;
11143
11425
  return {
@@ -11469,6 +11751,15 @@ async function updateModuleBundle(unit, deps) {
11469
11751
  registry,
11470
11752
  force: true
11471
11753
  });
11754
+ if (latest !== void 0 && deps.shell !== void 0 && registry !== void 0) {
11755
+ const declared = await supply.registryDeclarationOf({
11756
+ shell: deps.shell,
11757
+ name: pkg,
11758
+ registry,
11759
+ force: true
11760
+ });
11761
+ if (declared !== void 0 && supply.declarationRefusalOf(pkg, declared).length > 0) throw new Error("源上的新版和当前这一版工作台不匹配 —— 先升级工作台,再更新这个模块。");
11762
+ }
11472
11763
  const spec = latest === void 0 ? pkg : `${pkg}@${latest}`;
11473
11764
  const outcome = await replaceDshBundle(deps.routeDeps, spec, { ...registry === void 0 ? {} : { registry } });
11474
11765
  await deps.sweep();
@@ -11578,6 +11869,80 @@ async function updateWorkbenchOnce(deps) {
11578
11869
  };
11579
11870
  }
11580
11871
  //#endregion
11872
+ //#region src/shared/api/host/migrate-workbench.ts
11873
+ async function migrateWorkbench(input) {
11874
+ const script = join(input.home, "profiles", input.profile, "node_modules", input.pkg, "scripts", "share-data-root.mjs");
11875
+ if (!existsSync(script)) throw new Error(`找不到迁移脚本(${script})—— 这一份没装全?`);
11876
+ const runtimeRoot = process.env.DSH_WORKBENCH_HOME ?? join(input.home, input.profile);
11877
+ const result = await input.run({
11878
+ script,
11879
+ env: {
11880
+ DSH_HOME: input.home,
11881
+ WORKBENCH_PROFILE: input.profile,
11882
+ DSH_WORKBENCH_HOME: runtimeRoot
11883
+ }
11884
+ });
11885
+ if (result.exitCode !== 0) {
11886
+ const tail = (result.stderr || result.stdout).trim().split("\n").slice(-6).join("\n");
11887
+ throw new Error(`迁移没成(退出码 ${result.exitCode})${tail === "" ? "" : `:${tail}`}`);
11888
+ }
11889
+ return {
11890
+ added: Number.parseInt(/新增\s+(\d+)\s+个项目/.exec(result.stdout)?.[1] ?? "0", 10),
11891
+ files: Number.parseInt(/补了\s+(\d+)\s+个文件/.exec(result.stdout)?.[1] ?? "0", 10)
11892
+ };
11893
+ }
11894
+ //#endregion
11895
+ //#region src/modules/panels/model/market-follow-up.ts
11896
+ function createWorkbenchFollowUps(deps) {
11897
+ const runMigration = deps.runMigration ?? (async (input) => {
11898
+ const result = await execa(process.execPath, [
11899
+ input.script,
11900
+ "--migrate",
11901
+ "--force"
11902
+ ], {
11903
+ env: {
11904
+ ...process.env,
11905
+ ELECTRON_RUN_AS_NODE: "1",
11906
+ ...input.env
11907
+ },
11908
+ reject: false,
11909
+ timeout: 12e4
11910
+ });
11911
+ return {
11912
+ exitCode: result.exitCode ?? 1,
11913
+ stdout: result.stdout,
11914
+ stderr: result.stderr
11915
+ };
11916
+ });
11917
+ function homeAndProfile() {
11918
+ if (deps.dshHome === void 0 || deps.profileName === void 0) throw new Error("拿不到 DSH_HOME 或 profile 名字 —— 这一步做不了。");
11919
+ const home = deps.dshHome();
11920
+ const profile = deps.profileName();
11921
+ if (profile === null || profile === "") throw new Error("拿不到 profile 名字 —— 这一步做不了。");
11922
+ return {
11923
+ home,
11924
+ profile
11925
+ };
11926
+ }
11927
+ return {
11928
+ async migrateWorkbench() {
11929
+ const { home, profile } = homeAndProfile();
11930
+ const read = deps.workbench?.(PLUGIN_ID);
11931
+ if (read === void 0) throw new Error(`读不到 profile 里装的 ${PLUGIN_ID} —— 迁移做不了。`);
11932
+ return migrateWorkbench({
11933
+ home,
11934
+ profile,
11935
+ pkg: read.pkg,
11936
+ run: runMigration
11937
+ });
11938
+ },
11939
+ async dismissFollowUp(id) {
11940
+ const { home, profile } = homeAndProfile();
11941
+ dismissFollowUp(process.env.DSH_WORKBENCH_HOME ?? join(home, profile), id);
11942
+ }
11943
+ };
11944
+ }
11945
+ //#endregion
11581
11946
  //#region src/modules/panels/model/plugin-actions.ts
11582
11947
  function pluginKeptOf(retention, detail) {
11583
11948
  if (retention === "keep-held") return { kind: "held" };
@@ -11751,7 +12116,7 @@ function createEnvDropper(options) {
11751
12116
  const index = buildSupplyIndex(catalog().filter((item) => options.isReleased?.(item.id) !== true).map((item) => ({
11752
12117
  id: item.id,
11753
12118
  needs: needsOf(item)
11754
- })));
12119
+ })), { env: process.env });
11755
12120
  const extensions = (needs ?? []).filter((need) => need.kind === "extension");
11756
12121
  const libs = (needs ?? []).filter((need) => need.kind === "lib");
11757
12122
  const onMachine = new Set(before.needs.filter((state) => state.kind === "extension" && state.status !== "missing").map((state) => state.name));
@@ -11870,7 +12235,7 @@ function createMarketService(options) {
11870
12235
  const manifest = requireEntry(unit);
11871
12236
  return {
11872
12237
  unit,
11873
- titleKey: manifest.card?.titleKey ?? unit,
12238
+ titleKey: manifest.titleKey,
11874
12239
  needs: needsOf(manifest),
11875
12240
  capability: await capabilityOf(unit, force)
11876
12241
  };
@@ -12181,6 +12546,12 @@ function createMarketService(options) {
12181
12546
  await reprobePlugin(pkg, outcome.name);
12182
12547
  return outcome;
12183
12548
  },
12549
+ ...createWorkbenchFollowUps({
12550
+ dshHome,
12551
+ profileName,
12552
+ workbench,
12553
+ ...options.runMigration === void 0 ? {} : { runMigration: options.runMigration }
12554
+ }),
12184
12555
  updateDependency: updateDependencyRow$1
12185
12556
  };
12186
12557
  return service;
@@ -12410,6 +12781,12 @@ function marketDiscoverRoutes(discover) {
12410
12781
  }
12411
12782
  //#endregion
12412
12783
  //#region src/modules/panels/model/market-sources-act.ts
12784
+ function marketSourcesRoutes(store) {
12785
+ return { [MARKET_SOURCES_ROUTE]: async () => ({
12786
+ ok: true,
12787
+ ...store.read()
12788
+ }) };
12789
+ }
12413
12790
  function asRecord(value) {
12414
12791
  return typeof value === "object" && value !== null ? value : void 0;
12415
12792
  }
@@ -12491,13 +12868,29 @@ function marketSourcesAction(store, init) {
12491
12868
  }
12492
12869
  //#endregion
12493
12870
  //#region src/modules/panels/model/publish-preview-routes.ts
12494
- function publishPreviewRoutes(workshop) {
12495
- return { [MARKET_PUBLISH_PREVIEW_ROUTE]: (search) => {
12871
+ function publishPreviewRoutes(workshop, inferredAuthorOf = async () => void 0) {
12872
+ return { [MARKET_PUBLISH_PREVIEW_ROUTE]: async (search) => {
12496
12873
  const unit = (param(search, "unit") ?? "").trim();
12497
12874
  const version = (param(search, "version") ?? "").trim();
12875
+ const author = (param(search, "author") ?? "").trim();
12876
+ if (unit === "") return {
12877
+ ok: true,
12878
+ preview: null
12879
+ };
12880
+ let preview = publishPreviewOf(workshop, unit, {
12881
+ version,
12882
+ author
12883
+ });
12884
+ if (preview !== null && author === "" && (preview.author === void 0 || preview.author === "")) {
12885
+ const inferred = await inferredAuthorOf(unit);
12886
+ if (inferred !== void 0 && inferred !== "") preview = {
12887
+ ...preview,
12888
+ author: inferred
12889
+ };
12890
+ }
12498
12891
  return {
12499
12892
  ok: true,
12500
- preview: unit === "" ? null : publishPreviewOf(workshop, unit, { version })
12893
+ preview
12501
12894
  };
12502
12895
  } };
12503
12896
  }
@@ -12506,6 +12899,7 @@ function publishPreviewRoutes(workshop) {
12506
12899
  var DEFAULT_TTL_MS = 3e5;
12507
12900
  var DEFAULT_TIMEOUT_MS$2 = 3e4;
12508
12901
  var DEFAULT_MAX_BYTES$1 = 4194304;
12902
+ var GIT_NO_PROMPT = `${quoteShellArg("env")} GIT_TERMINAL_PROMPT=0`;
12509
12903
  function messageOf$1(error) {
12510
12904
  return error instanceof Error ? error.message : String(error);
12511
12905
  }
@@ -12671,7 +13065,7 @@ function createMarketIndexReader(options) {
12671
13065
  }
12672
13066
  };
12673
13067
  try {
12674
- const result = await runShell(shell, line, { timeoutMs });
13068
+ const result = await runShell(shell, `${GIT_NO_PROMPT} ${line}`, { timeoutMs });
12675
13069
  if (result.exitCode !== 0) return {
12676
13070
  ok: false,
12677
13071
  problem: classifyGitError(result.stderr, result.timedOut),
@@ -13228,6 +13622,18 @@ async function dispatchMarket(market, request) {
13228
13622
  action: "cancel-install",
13229
13623
  cancellation: await market.cancelInstall(request.requestId ?? "")
13230
13624
  };
13625
+ if (request.action === "migrate-workbench") return {
13626
+ ok: true,
13627
+ action: "migrate-workbench",
13628
+ ...await market.migrateWorkbench()
13629
+ };
13630
+ if (request.action === "dismiss-followup") {
13631
+ await market.dismissFollowUp(request.id ?? "");
13632
+ return {
13633
+ ok: true,
13634
+ action: "dismiss-followup"
13635
+ };
13636
+ }
13231
13637
  if (request.action === "update-workbench") return {
13232
13638
  ok: true,
13233
13639
  action: "update-workbench",
@@ -14382,12 +14788,13 @@ var OWNED = [{
14382
14788
  ...diyDebugRoutes(debug),
14383
14789
  ...diyRecordRoutes(record),
14384
14790
  ...marketDiscoverRoutes(discover),
14385
- ...publishPreviewRoutes(workshopPathOf(ctx))
14791
+ ...marketSourcesRoutes(marketSources),
14792
+ ...publishPreviewRoutes(workshopPathOf(ctx), (unit) => inferredAuthorOf(workshopPathOf(ctx), unit, shell))
14386
14793
  },
14387
14794
  registerTools: () => [
14388
14795
  ...registerPanelTools(ctx, panels, values),
14389
14796
  ...registerMarketTools(ctx, market),
14390
- ...registerDiyTools(ctx, market, panels, debug, (unit) => publishPreviewOf(workshopPathOf(ctx), unit)),
14797
+ ...registerDiyTools(ctx, market, panels, debug, (unit) => publishPreviewOf(workshopPathOf(ctx), unit), (unit) => inferredAuthorOf(workshopPathOf(ctx), unit, shell)),
14391
14798
  ...registerRecordTools(ctx, record),
14392
14799
  ...registerLongTaskTools(ctx, longTaskRuns)
14393
14800
  ],