@dsh-workbench/core 1.1.2 → 1.1.4

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 (34) hide show
  1. package/client.js +1094 -520
  2. package/dist/client/client.body.js +1094 -520
  3. package/dist/client/index.js +946 -372
  4. package/dist/host/index.js +322 -9
  5. package/dist/types/app/layouts/market/discover-confirm.d.ts +4 -3
  6. package/dist/types/app/layouts/market/discover-detail.d.ts +1 -0
  7. package/dist/types/app/layouts/market/discover-installing.d.ts +8 -0
  8. package/dist/types/app/layouts/market/discover-rows.d.ts +4 -2
  9. package/dist/types/app/layouts/market/discover-states.d.ts +6 -0
  10. package/dist/types/app/layouts/market/discover-transport.d.ts +7 -0
  11. package/dist/types/app/layouts/market/discover-versions.d.ts +2 -1
  12. package/dist/types/app/layouts/market/market-check.d.ts +10 -0
  13. package/dist/types/app/layouts/market/market-header.d.ts +1 -3
  14. package/dist/types/app/layouts/market/use-discover.d.ts +10 -2
  15. package/dist/types/app/layouts/market/use-git-transport.d.ts +19 -0
  16. package/dist/types/app/layouts/market/use-module-versions.d.ts +4 -0
  17. package/dist/types/app/layouts/market/use-settling-modules.d.ts +1 -0
  18. package/dist/types/app/layouts/rightbar/MarketView.d.ts +3 -1
  19. package/dist/types/app/layouts/rightbar/market-freshness.d.ts +1 -0
  20. package/dist/types/app/layouts/rightbar/market-row-types.d.ts +4 -1
  21. package/dist/types/app/layouts/rightbar/market-rows.d.ts +2 -0
  22. package/dist/types/app/layouts/rightbar/market-section.d.ts +4 -2
  23. package/dist/types/app/layouts/rightbar/use-market-default.d.ts +12 -0
  24. package/dist/types/kernel/contract/market-index.d.ts +2 -2
  25. package/dist/types/modules/panels/model/market-installed.d.ts +19 -0
  26. package/dist/types/modules/panels/model/market-installed.test.d.ts +1 -0
  27. package/dist/types/modules/panels/model/market-route.d.ts +1 -0
  28. package/dist/types/shared/api/host/git-exec.d.ts +2 -0
  29. package/dist/types/shared/api/host/git-probe.d.ts +9 -0
  30. package/dist/types/shared/api/host/git-routes.d.ts +3 -0
  31. package/dist/types/shared/api/host/market-sources-store.d.ts +2 -1
  32. package/dist/types/shared/api/host/profile-dependencies.d.ts +1 -0
  33. package/dist/types/shared/hooks/market-queries.d.ts +3 -1
  34. package/package.json +3 -3
@@ -388,6 +388,99 @@ function boardSituation(probe, workshop) {
388
388
  if (probe.where !== "" && !insideWorkshop(workshop, probe.where)) return "off-workspace";
389
389
  return "panel-closed";
390
390
  }
391
+ //#endregion
392
+ //#region ../shared/dist/lib/git-address.js
393
+ var SCP = /^(?:([A-Za-z0-9._-]+)@)?([A-Za-z0-9.-]+):(?!\/)(\S+)$/;
394
+ function bare(url) {
395
+ return url.trim().replace(/^git\+/, "");
396
+ }
397
+ function pathOf(pathname) {
398
+ return pathname.replace(/^\/+/, "").replace(/\/+$/, "");
399
+ }
400
+ /**
401
+ * 把一个 git 地址拆成「哪台主机 + 哪个路径」,不管发布者当初用的是 https、ssh 还是
402
+ * scp 那种 `git@host:path` 写法;认不出来给 undefined。
403
+ */
404
+ function gitAddressOf(url) {
405
+ const text = bare(url);
406
+ if (text === "") return void 0;
407
+ if (text.includes("://")) {
408
+ let parsed;
409
+ try {
410
+ parsed = new URL(text);
411
+ } catch {
412
+ return;
413
+ }
414
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:" && parsed.protocol !== "ssh:") return;
415
+ const path = pathOf(parsed.pathname);
416
+ if (path === "") return void 0;
417
+ const port = parsed.port === "" ? void 0 : Number(parsed.port);
418
+ const shared = {
419
+ host: parsed.hostname,
420
+ path,
421
+ ...port === void 0 ? {} : { port }
422
+ };
423
+ if (parsed.protocol !== "ssh:") return {
424
+ kind: "https",
425
+ ...shared
426
+ };
427
+ return {
428
+ kind: "ssh",
429
+ ...shared,
430
+ ...parsed.username === "" ? {} : { user: parsed.username }
431
+ };
432
+ }
433
+ const scp = SCP.exec(text);
434
+ if (scp === null) return void 0;
435
+ const path = pathOf(scp[3] ?? "");
436
+ if (path === "") return void 0;
437
+ return {
438
+ kind: "ssh",
439
+ host: scp[2] ?? "",
440
+ path,
441
+ ...scp[1] === void 0 ? {} : { user: scp[1] }
442
+ };
443
+ }
444
+ /** 一份地址的规范写法:ssh 一律成 `ssh://user@host/path`(pnpm 认这个),https 原样。 */
445
+ function gitAddressText(address) {
446
+ const port = address.port === void 0 ? "" : `:${String(address.port)}`;
447
+ if (address.kind === "ssh") return `ssh://${address.user === void 0 || address.user === "" ? "git" : address.user}@${address.host}${port}/${address.path}`;
448
+ return `https://${address.host}${port}/${address.path}`;
449
+ }
450
+ /**
451
+ * 同一个仓库的两种读法,发布者用的那种排前面。ssh 那条一律规范成 `ssh://` 形式,
452
+ * 因为 `git@host:path` 拼进安装 spec 时 pnpm 不认;https 那条保留发布者的原样(含 http)。
453
+ */
454
+ function gitTransportCandidates(url) {
455
+ const address = gitAddressOf(url);
456
+ if (address === void 0) {
457
+ const only = bare(url);
458
+ return only === "" ? [] : [{
459
+ kind: only.startsWith("ssh") ? "ssh" : "https",
460
+ url: only
461
+ }];
462
+ }
463
+ const primary = {
464
+ kind: address.kind,
465
+ url: address.kind === "ssh" ? gitAddressText(address) : bare(url)
466
+ };
467
+ const other = address.kind === "https" ? {
468
+ kind: "ssh",
469
+ url: gitAddressText({
470
+ kind: "ssh",
471
+ host: address.host,
472
+ path: address.path
473
+ })
474
+ } : {
475
+ kind: "https",
476
+ url: gitAddressText({
477
+ kind: "https",
478
+ host: address.host,
479
+ path: address.path
480
+ })
481
+ };
482
+ return other.url === primary.url ? [primary] : [primary, other];
483
+ }
391
484
  /**
392
485
  * 外壳愿意认的协议版本;不在表里的模块当场被拒,不猜语义。
393
486
  */
@@ -4545,6 +4638,8 @@ function checkVersionRange(contract, version) {
4545
4638
  }
4546
4639
  var MARKET_DISCOVER_ROUTE = "panels/market-discover";
4547
4640
  var MARKET_DOC_ROUTE = "panels/market-doc";
4641
+ var MARKET_GIT_PROBE_ROUTE = "panels/git-probe";
4642
+ var MARKET_INSTALLED_ROUTE = "panels/market-installed";
4548
4643
  var MARKET_PUBLISH_PREVIEW_ROUTE = "panels/market-publish-preview";
4549
4644
  var MARKET_SOURCES_RESOURCE = "market-sources";
4550
4645
  var MARKET_SOURCES_ROUTE = "panels/market-sources";
@@ -5533,6 +5628,92 @@ function bundleInspectHandler(ctx) {
5533
5628
  };
5534
5629
  }
5535
5630
  //#endregion
5631
+ //#region src/shared/api/host/git-exec.ts
5632
+ var NO_PROMPT_ENV = [
5633
+ "GIT_TERMINAL_PROMPT=0",
5634
+ "GIT_ASKPASS=/usr/bin/false",
5635
+ "SSH_ASKPASS=/usr/bin/false",
5636
+ "SSH_ASKPASS_REQUIRE=never",
5637
+ "GCM_INTERACTIVE=never",
5638
+ "GIT_SSH_COMMAND=ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new"
5639
+ ];
5640
+ function gitNoPromptPrefix() {
5641
+ return [quoteShellArg("env"), ...NO_PROMPT_ENV.map((one) => quoteShellArg(one))].join(" ");
5642
+ }
5643
+ function withoutAskpassNoise(text) {
5644
+ return text.split("\n").filter((line) => !/unable to read askpass response/i.test(line)).join("\n").trim();
5645
+ }
5646
+ //#endregion
5647
+ //#region src/shared/api/host/git-probe.ts
5648
+ var PROBE_TIMEOUT_MS = 1e4;
5649
+ 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/;
5650
+ function classifyGitProbe(stderr, timedOut) {
5651
+ if (timedOut) return "unreachable";
5652
+ return AUTH_TEXT.test(stderr.toLowerCase()) ? "auth" : "unreachable";
5653
+ }
5654
+ async function probeGitAddress(shell, url, options = {}) {
5655
+ const line = [
5656
+ gitNoPromptPrefix(),
5657
+ quoteShellArg("git"),
5658
+ quoteShellArg("ls-remote"),
5659
+ quoteShellArg(url),
5660
+ quoteShellArg("HEAD")
5661
+ ].join(" ");
5662
+ let result;
5663
+ try {
5664
+ result = await runShell(shell, line, { timeoutMs: options.timeoutMs ?? PROBE_TIMEOUT_MS });
5665
+ } catch (error) {
5666
+ return {
5667
+ status: "unreachable",
5668
+ detail: error instanceof Error ? error.message : String(error)
5669
+ };
5670
+ }
5671
+ if (result.exitCode === 0) return { status: "ok" };
5672
+ const detail = tailLines(withoutAskpassNoise(result.stderr || result.stdout), 3);
5673
+ return {
5674
+ status: classifyGitProbe(`${result.stderr}\n${result.stdout}`, result.timedOut),
5675
+ ...detail === "" ? {} : { detail }
5676
+ };
5677
+ }
5678
+ var PROBE_CACHE_MS = 6e4;
5679
+ var probeCache = /* @__PURE__ */ new Map();
5680
+ async function probeGitAddressCached(shell, url, options = {}) {
5681
+ const hit = probeCache.get(url);
5682
+ if (hit !== void 0 && Date.now() - hit.at < PROBE_CACHE_MS) return hit.payload;
5683
+ const payload = await probeGitAddress(shell, url, options);
5684
+ probeCache.set(url, {
5685
+ at: Date.now(),
5686
+ payload
5687
+ });
5688
+ return payload;
5689
+ }
5690
+ //#endregion
5691
+ //#region src/shared/api/host/git-routes.ts
5692
+ function gitProbeHandler(ctx) {
5693
+ return async (search) => {
5694
+ const url = param(search, "url");
5695
+ if (url === void 0) return {
5696
+ ok: false,
5697
+ error: {
5698
+ code: "workbench/no-url",
5699
+ message: "要探哪个地址?缺 url。"
5700
+ }
5701
+ };
5702
+ const shell = ctx.get("shell");
5703
+ if (shell === void 0) return {
5704
+ ok: false,
5705
+ error: {
5706
+ code: "workbench/no-shell",
5707
+ message: "这台机器上没有执行缝,探不了 git 地址"
5708
+ }
5709
+ };
5710
+ return {
5711
+ ok: true,
5712
+ ...await probeGitAddress(shell, url)
5713
+ };
5714
+ };
5715
+ }
5716
+ //#endregion
5536
5717
  //#region src/shared/api/host/host-routes.ts
5537
5718
  function createHostRoutes(input) {
5538
5719
  const { ctx, facts, opencli, running, profile } = input;
@@ -5542,6 +5723,7 @@ function createHostRoutes(input) {
5542
5723
  [DSH_PLUGINS_ROUTE]: profile.dshPlugins(ctx),
5543
5724
  [BUNDLE_REGISTRIES_ROUTE]: bundleRegistriesHandler(ctx),
5544
5725
  [BUNDLE_INSPECT_ROUTE]: bundleInspectHandler(ctx),
5726
+ [MARKET_GIT_PROBE_ROUTE]: gitProbeHandler(ctx),
5545
5727
  [MODULE_VERSIONS_ROUTE]: moduleVersionsHandler(ctx, ctx.get("shell"), running, () => profile.selfUpdateView(dshHome(), basename(profile.dirOf(ctx) ?? "")))
5546
5728
  };
5547
5729
  }
@@ -11673,10 +11855,22 @@ function pluginNameOf(entry) {
11673
11855
  }
11674
11856
  //#endregion
11675
11857
  //#region src/modules/panels/model/market-route.ts
11858
+ function packageNameInPath(path) {
11859
+ const parts = path.split("/").filter((part) => part !== "");
11860
+ const last = parts[parts.length - 1];
11861
+ if (last === void 0 || last === "") return void 0;
11862
+ const parent = parts[parts.length - 2];
11863
+ return parent !== void 0 && parent.startsWith("@") ? `${parent}/${last}` : last;
11864
+ }
11676
11865
  function bundleNameOf(change, spec) {
11677
11866
  if (change.bundle !== void 0 && change.bundle !== "") return change.bundle;
11678
- const at = spec.lastIndexOf("@");
11679
- return at > 0 ? spec.slice(0, at) : spec;
11867
+ const at = spec.indexOf("&path:");
11868
+ if (at !== -1) {
11869
+ const inPath = packageNameInPath(spec.slice(at + 6));
11870
+ if (inPath !== void 0) return inPath;
11871
+ }
11872
+ const version = spec.lastIndexOf("@");
11873
+ return version > 0 ? spec.slice(0, version) : spec;
11680
11874
  }
11681
11875
  function installedPluginName(spec, pkg) {
11682
11876
  const local = localDirOf(spec);
@@ -12918,6 +13112,85 @@ function marketDiscoverRoutes(discover) {
12918
13112
  };
12919
13113
  }
12920
13114
  //#endregion
13115
+ //#region src/modules/panels/model/market-installed.ts
13116
+ async function transportOf(source, spec, probe, preferred) {
13117
+ if (source.type !== "git" || probe === void 0) return source;
13118
+ const candidates = gitTransportCandidates(source.url);
13119
+ if (candidates.length === 0) return source;
13120
+ const installed = spec === void 0 ? void 0 : gitAddressOf(spec);
13121
+ const first = preferred ?? installed?.kind;
13122
+ const ordered = first === void 0 ? candidates : [...candidates].sort((left, right) => Number(right.kind === first) - Number(left.kind === first));
13123
+ for (const candidate of ordered) if (await probe(candidate.url).catch(() => "unreachable") === "ok") return {
13124
+ ...source,
13125
+ url: candidate.url
13126
+ };
13127
+ return {
13128
+ ...source,
13129
+ url: ordered[0]?.url ?? source.url
13130
+ };
13131
+ }
13132
+ async function installedFromIndexes(input) {
13133
+ const modules = new Map(input.modules.map((module) => [module.pkg, module]));
13134
+ const rows = [];
13135
+ const seen = /* @__PURE__ */ new Set();
13136
+ for (const source of input.sources) {
13137
+ const index = input.cached(source);
13138
+ if (index === void 0) continue;
13139
+ for (const entry of index.plugins) {
13140
+ const module = modules.get(entry.name);
13141
+ const newest = entry.versions[0];
13142
+ if (module === void 0 || newest === void 0 || seen.has(entry.name)) continue;
13143
+ if (!input.isNewer(newest.version, module.version)) continue;
13144
+ seen.add(entry.name);
13145
+ rows.push({
13146
+ pkg: entry.name,
13147
+ unit: module.unit,
13148
+ version: newest.version,
13149
+ sourceId: source.id,
13150
+ sourceLabel: source.label,
13151
+ source: newest.source
13152
+ });
13153
+ const wanted = input.dependencyOf(entry.name);
13154
+ const host = gitAddressOf(source.url)?.host;
13155
+ const resolved = await transportOf(newest.source, wanted, input.probe, host === void 0 ? void 0 : input.preferred?.(host));
13156
+ rows[rows.length - 1] = {
13157
+ ...rows[rows.length - 1],
13158
+ source: resolved
13159
+ };
13160
+ }
13161
+ }
13162
+ return rows;
13163
+ }
13164
+ function marketInstalledRoutes(read) {
13165
+ return { [MARKET_INSTALLED_ROUTE]: async () => ({
13166
+ ok: true,
13167
+ ...await read()
13168
+ }) };
13169
+ }
13170
+ function transportOfSpec(spec) {
13171
+ const address = gitAddressOf(spec);
13172
+ return address === void 0 ? void 0 : {
13173
+ host: address.host,
13174
+ kind: address.kind
13175
+ };
13176
+ }
13177
+ //#endregion
13178
+ //#region src/shared/api/host/profile-dependencies.ts
13179
+ function dependencyReader(profileDir) {
13180
+ if (profileDir === null) return () => void 0;
13181
+ let dependencies;
13182
+ return (pkg) => {
13183
+ if (dependencies === void 0) try {
13184
+ const raw = JSON.parse(readFileSync(join(profileDir, "package.json"), "utf8")).dependencies;
13185
+ dependencies = typeof raw === "object" && raw !== null ? raw : {};
13186
+ } catch {
13187
+ dependencies = {};
13188
+ }
13189
+ const value = dependencies[pkg];
13190
+ return typeof value === "string" ? value : void 0;
13191
+ };
13192
+ }
13193
+ //#endregion
12921
13194
  //#region src/modules/panels/model/market-sources-act.ts
12922
13195
  function marketSourcesRoutes(store) {
12923
13196
  return { [MARKET_SOURCES_ROUTE]: async () => ({
@@ -13075,7 +13348,6 @@ function publishPreviewRoutes(workshop, inferredAuthorOf = async () => void 0) {
13075
13348
  var DEFAULT_TTL_MS = 3e5;
13076
13349
  var DEFAULT_TIMEOUT_MS$2 = 3e4;
13077
13350
  var DEFAULT_MAX_BYTES$1 = 4194304;
13078
- var GIT_NO_PROMPT = `${quoteShellArg("env")} GIT_TERMINAL_PROMPT=0`;
13079
13351
  function messageOf$1(error) {
13080
13352
  return error instanceof Error ? error.message : String(error);
13081
13353
  }
@@ -13137,7 +13409,7 @@ function gitProblemOf(source) {
13137
13409
  };
13138
13410
  }
13139
13411
  function tail(text) {
13140
- return text.trim().split("\n").filter((line) => line !== "").slice(-3).join(" ");
13412
+ return withoutAskpassNoise(text).split("\n").filter((line) => line !== "").slice(-3).join(" ");
13141
13413
  }
13142
13414
  function classifyGitError(stderr, timedOut) {
13143
13415
  if (timedOut) return {
@@ -13241,7 +13513,7 @@ function createMarketIndexReader(options) {
13241
13513
  }
13242
13514
  };
13243
13515
  try {
13244
- const result = await runShell(shell, `${GIT_NO_PROMPT} ${line}`, { timeoutMs });
13516
+ const result = await runShell(shell, `${gitNoPromptPrefix()} ${line}`, { timeoutMs });
13245
13517
  if (result.exitCode !== 0) return {
13246
13518
  ok: false,
13247
13519
  problem: classifyGitError(result.stderr, result.timedOut),
@@ -13506,6 +13778,15 @@ function createMarketDocReader(options) {
13506
13778
  function fileOf(root) {
13507
13779
  return join(root, "market-index", "sources.json");
13508
13780
  }
13781
+ function transportsOf(raw) {
13782
+ const record = typeof raw === "object" && raw !== null ? raw : {};
13783
+ const found = {};
13784
+ for (const [host, kind] of Object.entries(record)) {
13785
+ if (host === "" || kind !== "https" && kind !== "ssh") continue;
13786
+ found[host] = kind;
13787
+ }
13788
+ return found;
13789
+ }
13509
13790
  function readPayload(path, logger) {
13510
13791
  let raw;
13511
13792
  try {
@@ -13513,7 +13794,8 @@ function readPayload(path, logger) {
13513
13794
  } catch {
13514
13795
  return {
13515
13796
  sources: [],
13516
- selected: null
13797
+ selected: null,
13798
+ transports: {}
13517
13799
  };
13518
13800
  }
13519
13801
  const record = typeof raw === "object" && raw !== null ? raw : {};
@@ -13530,7 +13812,8 @@ function readPayload(path, logger) {
13530
13812
  const selectedRaw = record["selected"];
13531
13813
  return {
13532
13814
  sources,
13533
- selected: typeof selectedRaw === "string" && sources.some((source) => source.id === selectedRaw) ? selectedRaw : sources[0]?.id ?? null
13815
+ selected: typeof selectedRaw === "string" && sources.some((source) => source.id === selectedRaw) ? selectedRaw : sources[0]?.id ?? null,
13816
+ transports: transportsOf(record["transports"])
13534
13817
  };
13535
13818
  }
13536
13819
  async function writePayload(path, payload) {
@@ -13549,6 +13832,18 @@ function createMarketSourcesStore(options) {
13549
13832
  function read() {
13550
13833
  return readPayload(path, options.logger);
13551
13834
  }
13835
+ async function rememberTransport(host, kind) {
13836
+ if (host === "") return;
13837
+ const current = read();
13838
+ const transports = {
13839
+ ...current.transports ?? {},
13840
+ [host]: kind
13841
+ };
13842
+ await writePayload(path, {
13843
+ ...current,
13844
+ transports
13845
+ });
13846
+ }
13552
13847
  async function apply(action) {
13553
13848
  const current = read();
13554
13849
  if (action.action === "save") {
@@ -13580,7 +13875,8 @@ function createMarketSourcesStore(options) {
13580
13875
  }
13581
13876
  return {
13582
13877
  read,
13583
- apply
13878
+ apply,
13879
+ rememberTransport
13584
13880
  };
13585
13881
  }
13586
13882
  //#endregion
@@ -15115,6 +15411,15 @@ var OWNED = [{
15115
15411
  ...diyRecordRoutes(record),
15116
15412
  ...marketDiscoverRoutes(discover),
15117
15413
  ...marketSourcesRoutes(marketSources),
15414
+ ...marketInstalledRoutes(async () => ({ modules: await installedFromIndexes({
15415
+ sources: marketSources.read().sources,
15416
+ cached: (source) => marketIndex.cached(source)?.index,
15417
+ modules: supply.profileModules(profileDir),
15418
+ dependencyOf: dependencyReader(profileDir),
15419
+ isNewer: supply.isNewerVersion,
15420
+ preferred: (host) => marketSources.read().transports?.[host],
15421
+ ...shell === void 0 ? {} : { probe: (url) => probeGitAddressCached(shell, url, { timeoutMs: 4e3 }).then((one) => one.status) }
15422
+ }) })),
15118
15423
  ...publishPreviewRoutes(workshopPathOf(ctx), (unit) => inferredAuthorOf(workshopPathOf(ctx), unit, shell))
15119
15424
  },
15120
15425
  registerTools: () => [
@@ -15152,7 +15457,15 @@ var OWNED = [{
15152
15457
  handle: (payload) => applyPanelsPref(panels, payload)
15153
15458
  },
15154
15459
  provision: {
15155
- handle: (request) => dispatchMarket(market, request),
15460
+ handle: async (request) => {
15461
+ const result = await dispatchMarket(market, request);
15462
+ const application = result.change?.application;
15463
+ if (request.spec !== void 0 && (application === "applied" || application === "restart-required" || application === "overridden")) {
15464
+ const used = transportOfSpec(request.spec);
15465
+ if (used !== void 0) await marketSources.rememberTransport(used.host, used.kind);
15466
+ }
15467
+ return result;
15468
+ },
15156
15469
  affectedUnits: (request) => affectedUnitsOf(manifests(), request)
15157
15470
  }
15158
15471
  };
@@ -1,5 +1,5 @@
1
1
  import type { Translate } from '@dsh-workbench/shared';
2
- import type { DiscoverEntry, DiscoverVersionRow } from '../../../kernel/contract/market-index';
2
+ import type { DiscoverEntry, DiscoverVersionRow, GitTransportKind } from '../../../kernel/contract/market-index';
3
3
  export interface DiscoverChoice {
4
4
  readonly entry: DiscoverEntry;
5
5
  readonly version: DiscoverVersionRow;
@@ -8,7 +8,8 @@ export interface DiscoverConfirmProps {
8
8
  readonly t: Translate;
9
9
  readonly locale: string;
10
10
  readonly choice: DiscoverChoice | null;
11
+ readonly transports: Readonly<Record<string, GitTransportKind>>;
11
12
  readonly onCancel: () => void;
12
- readonly onConfirm: () => void;
13
+ readonly onConfirm: (url?: string) => void;
13
14
  }
14
- export declare function DiscoverConfirm({ t, locale, choice, onCancel, onConfirm }: DiscoverConfirmProps): JSX.Element | null;
15
+ export declare function DiscoverConfirm({ t, locale, choice, transports, onCancel, onConfirm }: DiscoverConfirmProps): JSX.Element | null;
@@ -8,5 +8,6 @@ export interface DiscoverDetailProps {
8
8
  readonly onBack: () => void;
9
9
  readonly onInstall: (entry: DiscoverEntry, version: DiscoverVersionRow) => void;
10
10
  readonly onShowInstalled: (name: string) => void;
11
+ readonly installDisabled?: boolean;
11
12
  }
12
13
  export declare function DiscoverDetail(props: DiscoverDetailProps): JSX.Element;
@@ -0,0 +1,8 @@
1
+ import type { Translate } from '@dsh-workbench/shared';
2
+ export interface DiscoverInstallingProps {
3
+ readonly t: Translate;
4
+ readonly name: string;
5
+ readonly cancelling: boolean;
6
+ readonly onCancel: () => void;
7
+ }
8
+ export declare function DiscoverInstalling({ t, name, cancelling, onCancel }: DiscoverInstallingProps): JSX.Element;
@@ -7,6 +7,7 @@ export interface DiscoverRowsProps {
7
7
  readonly onOpen: (name: string) => void;
8
8
  readonly onInstall: (entry: DiscoverEntry, version: DiscoverVersionRow) => void;
9
9
  readonly onShowInstalled: (name: string) => void;
10
+ readonly installDisabled?: boolean;
10
11
  }
11
12
  export declare function canInstall(version: DiscoverVersionRow | undefined): boolean;
12
13
  export declare function sourceTextOf(t: Translate, version: DiscoverVersionRow): string;
@@ -14,11 +15,12 @@ export declare function MarketIcon({ src, size }: {
14
15
  readonly src?: string | undefined;
15
16
  readonly size?: number;
16
17
  }): JSX.Element;
17
- export declare function EntryAction({ t, entry, title, onInstall, onShowInstalled, }: {
18
+ export declare function EntryAction({ t, entry, title, onInstall, onShowInstalled, installDisabled, }: {
18
19
  readonly t: Translate;
19
20
  readonly entry: DiscoverEntry;
20
21
  readonly title: string;
21
22
  readonly onInstall: (entry: DiscoverEntry, version: DiscoverVersionRow) => void;
22
23
  readonly onShowInstalled: (name: string) => void;
24
+ readonly installDisabled?: boolean;
23
25
  }): JSX.Element | null;
24
- export declare function DiscoverRows({ t, locale, entries, onOpen, onInstall, onShowInstalled }: DiscoverRowsProps): JSX.Element;
26
+ export declare function DiscoverRows({ t, locale, entries, onOpen, onInstall, onShowInstalled, installDisabled }: DiscoverRowsProps): JSX.Element;
@@ -23,3 +23,9 @@ export declare function DiscoverNoSource({ t, onAdd }: {
23
23
  readonly t: Translate;
24
24
  readonly onAdd: () => void;
25
25
  }): JSX.Element;
26
+ export declare function DiscoverReadFailure({ t, text, auth, onRetry, }: {
27
+ readonly t: Translate;
28
+ readonly text: string;
29
+ readonly auth: boolean;
30
+ readonly onRetry: () => void;
31
+ }): JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type { Translate } from '@dsh-workbench/shared';
2
+ import type { GitTransportState } from './use-git-transport';
3
+ export interface DiscoverTransportProps {
4
+ readonly t: Translate;
5
+ readonly transport: GitTransportState;
6
+ }
7
+ export declare function DiscoverTransport({ t, transport }: DiscoverTransportProps): JSX.Element;
@@ -5,5 +5,6 @@ export interface DiscoverVersionsProps {
5
5
  readonly locale: string;
6
6
  readonly entry: DiscoverEntry;
7
7
  readonly onInstall: (entry: DiscoverEntry, version: DiscoverVersionRow) => void;
8
+ readonly installDisabled?: boolean;
8
9
  }
9
- export declare function DiscoverVersions({ t, locale, entry, onInstall }: DiscoverVersionsProps): JSX.Element;
10
+ export declare function DiscoverVersions({ t, locale, entry, onInstall, installDisabled }: DiscoverVersionsProps): JSX.Element;
@@ -0,0 +1,10 @@
1
+ import type { Translate } from '@dsh-workbench/shared';
2
+ export interface MarketCheckProps {
3
+ readonly t: Translate;
4
+ readonly checkedAt: number | null;
5
+ readonly checking: boolean;
6
+ readonly failed: boolean;
7
+ readonly disabled: boolean;
8
+ readonly onCheck: () => void;
9
+ }
10
+ export declare function MarketCheck({ t, checkedAt, checking, failed, disabled, onCheck }: MarketCheckProps): JSX.Element;
@@ -5,9 +5,7 @@ export interface MarketHeaderProps {
5
5
  readonly idPrefix: string;
6
6
  readonly tab: MarketTab;
7
7
  readonly busy: boolean;
8
- readonly checking: boolean;
9
8
  readonly onTab: (tab: MarketTab) => void;
10
- readonly onReload: () => void;
11
9
  readonly onAdd: () => void;
12
10
  }
13
- export declare function MarketHeader({ t, idPrefix, tab, busy, checking, onTab, onReload, onAdd }: MarketHeaderProps): JSX.Element;
11
+ export declare function MarketHeader({ t, idPrefix, tab, busy, onTab, onAdd }: MarketHeaderProps): JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import type { Translate } from '@dsh-workbench/shared';
2
- import type { DiscoverEntry, DiscoverPayload, DiscoverVersionRow, MarketSourceConfig } from '../../../kernel/contract/market-index';
2
+ import type { DiscoverEntry, DiscoverPayload, DiscoverVersionRow, GitTransportKind, MarketSourceConfig } from '../../../kernel/contract/market-index';
3
3
  import { type DiscoverSort } from './discover-view';
4
4
  interface InstallTarget {
5
5
  readonly entry: DiscoverEntry;
@@ -7,6 +7,8 @@ interface InstallTarget {
7
7
  }
8
8
  export type InstallVerdict = {
9
9
  readonly kind: 'done';
10
+ } | {
11
+ readonly kind: 'cancelled';
10
12
  } | {
11
13
  readonly kind: 'failed';
12
14
  readonly text: string;
@@ -23,6 +25,7 @@ export interface DiscoverController {
23
25
  readonly failure: string | null;
24
26
  readonly sources: DiscoverPayload['sources'];
25
27
  readonly selected: string | null;
28
+ readonly transports: Readonly<Record<string, GitTransportKind>>;
26
29
  readonly entries: readonly DiscoverEntry[];
27
30
  readonly shown: readonly DiscoverEntry[];
28
31
  readonly total: number;
@@ -38,10 +41,14 @@ export interface DiscoverController {
38
41
  readonly writeFailed: string | null;
39
42
  readonly provisionFailed: string | null;
40
43
  readonly busy: boolean;
44
+ readonly installing: string | null;
45
+ readonly cancelling: boolean;
46
+ readonly installDisabled: boolean;
41
47
  readonly addingSource: boolean;
42
48
  readonly manageOpen: boolean;
43
49
  readonly problem: {
44
50
  readonly id: string;
51
+ readonly kind: string;
45
52
  readonly detail: string;
46
53
  } | null;
47
54
  readonly uninitialized: {
@@ -70,7 +77,8 @@ export interface DiscoverController {
70
77
  readonly addSource: (url: string) => void;
71
78
  readonly removeSourceById: (id: string) => void;
72
79
  readonly selectSource: (id: string) => void;
73
- readonly install: () => void;
80
+ readonly install: (url?: string) => void;
81
+ readonly cancelInstall: () => void;
74
82
  }
75
83
  export declare function useDiscover(t: Translate, active: boolean, onInstalled: () => void): DiscoverController;
76
84
  export type { InstallTarget };
@@ -0,0 +1,19 @@
1
+ import type { GitProbeStatus, GitTransportKind, MarketIndexGitSource } from '../../../kernel/contract/market-index';
2
+ export type TransportProbe = GitProbeStatus | 'checking' | 'unknown';
3
+ export interface GitTransportRow {
4
+ readonly kind: 'https' | 'ssh';
5
+ readonly url: string;
6
+ }
7
+ export interface GitTransportState {
8
+ readonly rows: readonly GitTransportRow[];
9
+ readonly chosen: string | null;
10
+ readonly states: Readonly<Record<string, TransportProbe>>;
11
+ readonly custom: string;
12
+ readonly customState: TransportProbe | null;
13
+ readonly probing: boolean;
14
+ readonly ready: boolean;
15
+ setChosen(url: string): void;
16
+ setCustom(value: string): void;
17
+ checkCustom(): void;
18
+ }
19
+ export declare function useGitTransport(source: MarketIndexGitSource | null, preferredKind?: GitTransportKind | undefined): GitTransportState;
@@ -1,4 +1,5 @@
1
1
  import type { ModuleVersion, WorkbenchSelfUpdate, WorkbenchVersion } from '../../../kernel/contract/bundle-install';
2
+ import type { MarketInstalledModule } from '../../../kernel/contract/market-index';
2
3
  import type { InstalledPlugin } from '../../../shared/api/installed-plugins';
3
4
  export interface ModuleVersionsState {
4
5
  readonly modules: readonly ModuleVersion[] | null;
@@ -6,6 +7,8 @@ export interface ModuleVersionsState {
6
7
  readonly workbench: WorkbenchVersion | null;
7
8
  readonly selfUpdate: WorkbenchSelfUpdate | null;
8
9
  readonly read: boolean;
10
+ readonly checking: boolean;
11
+ readonly failed: boolean;
9
12
  readonly checkedAt: number | null;
10
13
  }
11
14
  export interface ModuleVersionsView extends ModuleVersionsState {
@@ -18,5 +21,6 @@ export declare function bumpModuleVersionsEpoch(): void;
18
21
  export declare function moduleVersionsValue(): ModuleVersionsState;
19
22
  export declare function resetModuleVersions(): void;
20
23
  export declare function subscribeModuleVersions(listener: () => void): () => void;
24
+ export declare function withMarketUpdates(modules: readonly ModuleVersion[], rows: readonly MarketInstalledModule[]): readonly ModuleVersion[];
21
25
  export declare function runModuleVersionsRead(force?: boolean, fresh?: boolean): Promise<void>;
22
26
  export declare function useModuleVersions(): ModuleVersionsView;
@@ -0,0 +1 @@
1
+ export declare function useSettlingModules(installedKey: string, rosterKey: string, activeKey: string): ReadonlySet<string>;
@@ -20,6 +20,8 @@ export interface MarketViewProps {
20
20
  }) => void;
21
21
  readonly onPending?: ((pending: boolean) => void) | undefined;
22
22
  readonly updates?: ModuleUpdateMap | undefined;
23
+ readonly updatesCheckedAt?: number | null | undefined;
24
+ readonly updatesFailed?: boolean | undefined;
23
25
  readonly installed?: readonly InstalledWorkbenchModule[] | undefined;
24
26
  readonly installedRead?: boolean | undefined;
25
27
  readonly workbench?: WorkbenchVersion | null | undefined;
@@ -30,4 +32,4 @@ export interface MarketViewProps {
30
32
  readonly defaults?: readonly string[] | undefined;
31
33
  readonly changed?: Readonly<Record<string, number>> | undefined;
32
34
  }
33
- export declare function MarketView({ t, titleOf, manifests, installable, capabilities, freshness, onChanged, onPending, updates, installed, installedRead, workbench, selfUpdate, plugins, settling, mock, defaults, changed, }: MarketViewProps): JSX.Element;
35
+ export declare function MarketView({ t, titleOf, manifests, installable, capabilities, freshness, onChanged, onPending, updates, updatesCheckedAt, updatesFailed, installed, installedRead, workbench, selfUpdate, plugins, settling, mock, defaults, changed, }: MarketViewProps): JSX.Element;
@@ -1,6 +1,7 @@
1
1
  import type { Translate } from '@dsh-workbench/shared';
2
2
  export declare const MARKET_STALE_MS = 600000;
3
3
  export declare function checkedAgoText(at: number, now: number, t: Translate): string;
4
+ export declare function checkedAgo(at: number, t: Translate): string;
4
5
  export interface MarketFreshness {
5
6
  readonly loaded: boolean;
6
7
  readonly checking: boolean;