@dsh-workbench/shared 1.0.0-beta.0 → 1.0.0-beta.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 (70) hide show
  1. package/dist/components/FormNodes.js +5 -1
  2. package/dist/components/FormRows.css +1 -0
  3. package/dist/components/FormRows.d.ts +4 -2
  4. package/dist/components/FormRows.js +5 -4
  5. package/dist/config/constants.d.ts +0 -2
  6. package/dist/config/constants.js +0 -5
  7. package/dist/contract/command.d.ts +22 -0
  8. package/dist/contract/command.js +1 -0
  9. package/dist/contract/data.d.ts +2 -1
  10. package/dist/contract/extension.d.ts +14 -0
  11. package/dist/contract/extension.js +1 -0
  12. package/dist/contract/git.d.ts +38 -0
  13. package/dist/contract/git.js +1 -0
  14. package/dist/contract/lib.d.ts +4 -0
  15. package/dist/contract/lib.js +1 -0
  16. package/dist/host/adapter-set.js +1 -0
  17. package/dist/host/adapters.d.ts +6 -0
  18. package/dist/host/extension.d.ts +7 -0
  19. package/dist/host/extension.js +39 -0
  20. package/dist/host/git.d.ts +2 -33
  21. package/dist/host/http.js +1 -1
  22. package/dist/host/market-index.js +3 -44
  23. package/dist/host/port-types.d.ts +1 -0
  24. package/dist/host/release-packages.generated.d.ts +1 -0
  25. package/dist/host/release-packages.generated.js +10 -0
  26. package/dist/host/tool-kit.d.ts +0 -6
  27. package/dist/host/tool-kit.js +0 -22
  28. package/dist/host/unit-face.d.ts +7 -0
  29. package/dist/host/version-range.js +7 -67
  30. package/dist/host/version.d.ts +3 -2
  31. package/dist/host/version.js +17 -19
  32. package/dist/host.d.ts +6 -0
  33. package/dist/host.js +6 -0
  34. package/dist/i18n/en.js +1 -0
  35. package/dist/i18n/index.d.ts +2 -1
  36. package/dist/i18n/zh.d.ts +1 -0
  37. package/dist/i18n/zh.js +1 -0
  38. package/dist/index.d.ts +4 -0
  39. package/dist/index.js +4 -0
  40. package/dist/lib/version.d.ts +4 -0
  41. package/dist/lib/version.js +21 -0
  42. package/package.json +6 -4
  43. package/src/components/Form.test.tsx +6 -0
  44. package/src/components/FormNodes.tsx +6 -1
  45. package/src/components/FormRows.css +1 -0
  46. package/src/components/FormRows.test.tsx +30 -0
  47. package/src/components/FormRows.tsx +9 -5
  48. package/src/config/constants.ts +0 -6
  49. package/src/contract/command.ts +25 -0
  50. package/src/contract/data.ts +2 -1
  51. package/src/contract/extension.ts +20 -0
  52. package/src/contract/git.ts +20 -0
  53. package/src/contract/lib.ts +5 -0
  54. package/src/host/adapter-set.ts +1 -0
  55. package/src/host/adapters.ts +6 -0
  56. package/src/host/extension.test.ts +55 -0
  57. package/src/host/extension.ts +57 -0
  58. package/src/host/git.ts +6 -14
  59. package/src/host/http.ts +1 -1
  60. package/src/host/market-index.ts +3 -33
  61. package/src/host/port-types.ts +1 -0
  62. package/src/host/release-packages.generated.ts +10 -0
  63. package/src/host/tool-kit.ts +0 -35
  64. package/src/host/unit-face.ts +7 -0
  65. package/src/host/version-range.ts +6 -64
  66. package/src/host/version.ts +18 -18
  67. package/src/host.ts +6 -0
  68. package/src/i18n/en.ts +1 -0
  69. package/src/i18n/zh.ts +1 -0
  70. package/src/index.ts +4 -0
@@ -144,14 +144,18 @@ export function formRowsOf(args) {
144
144
  return args.fields.map((field) => h(FormRow, {
145
145
  key: field.key,
146
146
  label: field.label,
147
+ htmlFor: controlIdOf(field.key),
147
148
  hint: field.hint,
148
149
  problem: args.showProblem ? args.problems[field.key] : undefined,
149
150
  layout: field.layout,
150
151
  }, controlOf(field, args.problems, args.showProblem, args.busy)));
151
152
  }
153
+ function controlIdOf(key) {
154
+ return `wb-form-${key}`;
155
+ }
152
156
  function controlOf(field, problems, showProblem, busy) {
153
157
  return field.control({
154
- id: `wb-form-${field.key}`,
158
+ id: controlIdOf(field.key),
155
159
  name: field.key,
156
160
  invalid: showProblem && Boolean(problems[field.key]),
157
161
  disabled: busy || field.disabled === true,
@@ -29,6 +29,7 @@
29
29
  }
30
30
 
31
31
  .wb-formLabel {
32
+ justify-self: start;
32
33
  min-width: 0;
33
34
  font-size: var(--dsw-font-xs-13-font-size);
34
35
  color: var(--dsw-alias-label-secondary);
@@ -17,6 +17,7 @@ export declare function FormRows({ children }: FormRowsProps): JSX.Element;
17
17
  */
18
18
  export interface FormRowProps {
19
19
  readonly label: ReactNode;
20
+ readonly htmlFor?: string | undefined;
20
21
  readonly hint?: ReactNode | undefined;
21
22
  readonly problem?: ReactNode | undefined;
22
23
  readonly layout?: 'inline' | 'stacked' | undefined;
@@ -25,10 +26,11 @@ export interface FormRowProps {
25
26
  /**
26
27
  * 表单一行:左边标签、右边控件,提示与错误占控件下面那一格。
27
28
  * @param props.label - 这一行的标签;进了这一行,控件自己就别再给 `label`,否则会多一个标签。
29
+ * @param props.htmlFor - 标签挂到哪个控件(同一个 `id`);不给就只是排布,点标签不聚焦 —— `Form` 会自己接好。
28
30
  * @param props.hint - 控件下的说明;给了 `problem` 就不画它 —— 同一格不叠两段话。
29
31
  * @param props.problem - 校验失败那句话;给了就顶掉 `hint` 并给整行加上出错标记。
30
32
  * @param props.layout - `stacked` 才是标签在上、控件在下(默认 `inline`:标签在左)。
31
33
  * @param props.children - 控件本体。
32
- * @returns 一个 `<label>`。
34
+ * @returns 一个 `<div>`:里面的 `<label>` 只包标签文字,不包控件、提示与整行的空白。
33
35
  */
34
- export declare function FormRow({ label, hint, problem, layout, children }: FormRowProps): JSX.Element;
36
+ export declare function FormRow({ label, htmlFor, hint, problem, layout, children }: FormRowProps): JSX.Element;
@@ -11,15 +11,16 @@ export function FormRows({ children }) {
11
11
  /**
12
12
  * 表单一行:左边标签、右边控件,提示与错误占控件下面那一格。
13
13
  * @param props.label - 这一行的标签;进了这一行,控件自己就别再给 `label`,否则会多一个标签。
14
+ * @param props.htmlFor - 标签挂到哪个控件(同一个 `id`);不给就只是排布,点标签不聚焦 —— `Form` 会自己接好。
14
15
  * @param props.hint - 控件下的说明;给了 `problem` 就不画它 —— 同一格不叠两段话。
15
16
  * @param props.problem - 校验失败那句话;给了就顶掉 `hint` 并给整行加上出错标记。
16
17
  * @param props.layout - `stacked` 才是标签在上、控件在下(默认 `inline`:标签在左)。
17
18
  * @param props.children - 控件本体。
18
- * @returns 一个 `<label>`。
19
+ * @returns 一个 `<div>`:里面的 `<label>` 只包标签文字,不包控件、提示与整行的空白。
19
20
  */
20
- export function FormRow({ label, hint, problem, layout = 'inline', children }) {
21
- return (h("label", { className: "wb-formRow", "data-layout": layout, "data-wb-problem": problem === undefined ? undefined : '' },
22
- h("span", { className: "wb-formLabel" }, label),
21
+ export function FormRow({ label, htmlFor, hint, problem, layout = 'inline', children }) {
22
+ return (h("div", { className: "wb-formRow", "data-layout": layout, "data-wb-problem": problem === undefined ? undefined : '' },
23
+ h("label", { className: "wb-formLabel", htmlFor: htmlFor }, label),
23
24
  h("span", { className: "wb-formControl" }, children),
24
25
  problem === undefined ? (hint === undefined ? null : (h("span", { className: "wb-formHint" }, hint))) : (h("span", { className: "wb-formProblem" }, problem))));
25
26
  }
@@ -1,7 +1,5 @@
1
1
  /** 外壳插件的包名:浏览器模块表与宿主按它注册、认领这枚插件。 */
2
2
  export declare const PLUGIN_ID = "@dsh-workbench/core";
3
- /** 与外壳同批发布的兄弟包名:`release.ts` 用它判一版三包是否都已在源上。 */
4
- export declare const RELEASE_SIBLINGS: readonly ["@dsh-workbench/shared", "@dsh-workbench/module-tools"];
5
3
  /** 外壳宿主 HTTP API 的根前缀;prefs / actions / provision 等路由都挂在它下面。 */
6
4
  export declare const API_PREFIX = "/workbench/api";
7
5
  /** 模块偏好读写的路由前缀,挂在外壳 API 下:走写通道,拒写回 write-refused。 */
@@ -1,10 +1,5 @@
1
1
  /** 外壳插件的包名:浏览器模块表与宿主按它注册、认领这枚插件。 */
2
2
  export const PLUGIN_ID = "@dsh-workbench/core";
3
- /** 与外壳同批发布的兄弟包名:`release.ts` 用它判一版三包是否都已在源上。 */
4
- export const RELEASE_SIBLINGS = [
5
- "@dsh-workbench/shared",
6
- "@dsh-workbench/module-tools",
7
- ];
8
3
  /** 外壳宿主 HTTP API 的根前缀;prefs / actions / provision 等路由都挂在它下面。 */
9
4
  export const API_PREFIX = "/workbench/api";
10
5
  /** 模块偏好读写的路由前缀,挂在外壳 API 下:走写通道,拒写回 write-refused。 */
@@ -0,0 +1,22 @@
1
+ export interface CommandFile {
2
+ readonly content: string;
3
+ readonly suffix?: string | undefined;
4
+ }
5
+ export interface CommandRequest {
6
+ readonly argv: readonly string[];
7
+ readonly timeoutMs?: number | undefined;
8
+ readonly cwd?: string | undefined;
9
+ readonly files?: readonly CommandFile[] | undefined;
10
+ readonly appendFileArgs?: string | undefined;
11
+ }
12
+ export interface CommandResult {
13
+ readonly stdout: string;
14
+ readonly stderr: string;
15
+ readonly exitCode: number;
16
+ readonly timedOut: boolean;
17
+ readonly filePaths: readonly string[];
18
+ readonly error?: string | undefined;
19
+ }
20
+ export interface CommandReader {
21
+ run(request: CommandRequest): Promise<CommandResult>;
22
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -16,7 +16,8 @@ export interface BrowserDataRequest extends DataRequestBase {
16
16
  }
17
17
  export interface CommandDataRequest extends DataRequestBase {
18
18
  readonly kind: 'command';
19
- readonly command: string;
19
+ readonly adapter: string;
20
+ readonly argv: readonly string[];
20
21
  }
21
22
  export type DataRequest = HttpDataRequest | BrowserDataRequest | CommandDataRequest;
22
23
  export type DataErrorCode = 'invalid-request' | 'http' | 'auth' | 'timeout' | 'parse' | 'command' | 'browser';
@@ -0,0 +1,14 @@
1
+ export type ExtensionSession = 'persistent' | 'ephemeral' | 'none';
2
+ export interface ExtensionCallOptions {
3
+ readonly key?: string;
4
+ readonly workdir?: string;
5
+ readonly session?: ExtensionSession;
6
+ readonly timeoutMs?: number;
7
+ }
8
+ export interface ExtensionValue<T = unknown> {
9
+ readonly value: T;
10
+ readonly at: number;
11
+ }
12
+ export interface ExtensionCaller {
13
+ json(args: readonly string[], options?: ExtensionCallOptions): Promise<ExtensionValue>;
14
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ export type OriginRead = {
2
+ readonly kind: 'ok';
3
+ readonly remote: string;
4
+ } | {
5
+ readonly kind: 'no-origin';
6
+ readonly detail?: string;
7
+ } | {
8
+ readonly kind: 'unknown';
9
+ readonly reason: string;
10
+ readonly detail?: string;
11
+ };
12
+ export type BranchRead = {
13
+ readonly kind: 'ok';
14
+ readonly branch: string;
15
+ } | {
16
+ readonly kind: 'no-branch';
17
+ readonly detail?: string | undefined;
18
+ } | {
19
+ readonly kind: 'unknown';
20
+ readonly reason: string;
21
+ readonly detail?: string | undefined;
22
+ };
23
+ export type HeadRead = {
24
+ readonly kind: 'ok';
25
+ readonly commit: string;
26
+ } | {
27
+ readonly kind: 'no-head';
28
+ readonly detail?: string;
29
+ } | {
30
+ readonly kind: 'unknown';
31
+ readonly reason: string;
32
+ readonly detail?: string;
33
+ };
34
+ export interface GitReader {
35
+ origin(path: string): Promise<OriginRead>;
36
+ branch(path: string): Promise<BranchRead>;
37
+ head(path: string): Promise<HeadRead>;
38
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import type { LibNeed } from './needs.js';
2
+ export interface LibReader {
3
+ load<T = unknown>(need: LibNeed, entry: string): Promise<T>;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -220,6 +220,7 @@ export function withDefaultAdapterSet(defaults, loaded) {
220
220
  id: set.id ?? defaults.id,
221
221
  version: set.version ?? defaults.version,
222
222
  data: set.data ?? defaults.data,
223
+ libs: set.libs ?? defaults.libs,
223
224
  supply: set.supply ?? defaults.supply,
224
225
  modules: set.modules ?? defaults.modules,
225
226
  profile: set.profile ?? defaults.profile,
@@ -1,5 +1,8 @@
1
1
  import type { DshHostContext } from '../api/dsh.js';
2
2
  import type { DataReader } from '../contract/data.js';
3
+ import type { LibReader } from '../contract/lib.js';
4
+ import type { GitReader } from '../contract/git.js';
5
+ import type { CommandReader } from '../contract/command.js';
3
6
  import type { EnvSupplyPort, HostActions, ModulePort, PluginSurface, ProbePort, ProfilePort, RuntimePort } from './port-types.js';
4
7
  export declare const WORKBENCH_ADAPTERS = "workbenchAdapters";
5
8
  export declare const ADAPTER_SET_API_VERSION = 1;
@@ -9,6 +12,9 @@ export interface AdapterSet {
9
12
  readonly id: string;
10
13
  readonly version: string;
11
14
  readonly data: DataReader;
15
+ readonly libs?: LibReader | undefined;
16
+ readonly git?: GitReader | undefined;
17
+ readonly commands?: CommandReader | undefined;
12
18
  readonly supply: EnvSupplyPort;
13
19
  readonly modules: ModulePort;
14
20
  readonly profile: ProfilePort;
@@ -0,0 +1,7 @@
1
+ import type { DataReader } from '../contract/data.js';
2
+ import type { ExtensionCaller } from '../contract/extension.js';
3
+ import type { ExtensionNeed } from '../contract/needs.js';
4
+ export interface ExtensionCallerOptions {
5
+ readonly suiteId?: string | undefined;
6
+ }
7
+ export declare function extensionCallerOf(data: DataReader, need: ExtensionNeed, callerOptions?: ExtensionCallerOptions): ExtensionCaller;
@@ -0,0 +1,39 @@
1
+ import { WorkbenchDataError } from './data-error.js';
2
+ function cliCodeOf(code) {
3
+ if (code === 'auth')
4
+ return 'auth-required';
5
+ if (code === 'timeout')
6
+ return 'cli-timeout';
7
+ if (code === 'parse')
8
+ return 'cli-bad-output';
9
+ return 'cli-exec-failed';
10
+ }
11
+ export function extensionCallerOf(data, need, callerOptions = {}) {
12
+ return {
13
+ async json(args, options = {}) {
14
+ const result = await data.read({
15
+ kind: 'command',
16
+ adapter: need.runtime,
17
+ argv: [need.name, ...args],
18
+ ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
19
+ });
20
+ if (result.ok)
21
+ return { value: result.data, at: Date.now() };
22
+ const code = result.error.pageCode ?? cliCodeOf(result.error.code);
23
+ const command = [need.name, ...args].join(' ');
24
+ const advice = code === 'cli-unclassified'
25
+ ? `这条命令(${command})在这份 ${need.name} 插件里可能不存在(插件版本旧就会缺):用 workbench_modules_update(id=${callerOptions.suiteId ?? need.name})升级,或在面板的模块状态里点更新,再重试。终端确认:opencli ${need.name} ${args[0] ?? ''} --help`
26
+ : undefined;
27
+ const hint = advice === undefined
28
+ ? result.error.hint
29
+ : result.error.hint === undefined
30
+ ? advice
31
+ : `${advice}\n${result.error.hint}`;
32
+ throw new WorkbenchDataError(code, advice === undefined ? result.error.message : `${result.error.message}。${advice}`, {
33
+ ...(result.error.detail === undefined ? {} : { detail: result.error.detail }),
34
+ ...(hint === undefined ? {} : { hint }),
35
+ ...(result.error.helpUrl === undefined ? {} : { helpUrl: result.error.helpUrl }),
36
+ });
37
+ },
38
+ };
39
+ }
@@ -1,39 +1,8 @@
1
1
  import type { DshShell } from '../api/dsh.js';
2
- export type OriginRead = {
3
- readonly kind: 'ok';
4
- readonly remote: string;
5
- } | {
6
- readonly kind: 'no-origin';
7
- readonly detail?: string;
8
- } | {
9
- readonly kind: 'unknown';
10
- readonly reason: string;
11
- readonly detail?: string;
12
- };
2
+ export type { BranchRead, HeadRead, OriginRead, } from '../contract/git.js';
3
+ import type { BranchRead, HeadRead, OriginRead } from '../contract/git.js';
13
4
  export type OriginReader = (path: string) => Promise<OriginRead>;
14
- export type BranchRead = {
15
- readonly kind: 'ok';
16
- readonly branch: string;
17
- } | {
18
- readonly kind: 'no-branch';
19
- readonly detail?: string | undefined;
20
- } | {
21
- readonly kind: 'unknown';
22
- readonly reason: string;
23
- readonly detail?: string | undefined;
24
- };
25
5
  export type BranchReader = (path: string) => Promise<BranchRead>;
26
- export type HeadRead = {
27
- readonly kind: 'ok';
28
- readonly commit: string;
29
- } | {
30
- readonly kind: 'no-head';
31
- readonly detail?: string;
32
- } | {
33
- readonly kind: 'unknown';
34
- readonly reason: string;
35
- readonly detail?: string;
36
- };
37
6
  export type HeadReader = (path: string) => Promise<HeadRead>;
38
7
  export declare function createHeadReader(options: {
39
8
  shell: DshShell;
package/dist/host/http.js CHANGED
@@ -70,7 +70,7 @@ export function registerApi(ctx, routes) {
70
70
  sendJson(res, 200, await handler(search));
71
71
  }
72
72
  catch (error) {
73
- if (error instanceof WorkbenchDataError) {
73
+ if (isDataError(error)) {
74
74
  sendJson(res, 200, { ok: false, error: toErrorPayload(error) });
75
75
  return;
76
76
  }
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { MARKET_INDEX_FORMAT } from '../contract/protocol/market-index.js';
3
3
  import { marketText } from '../lib/market-index.js';
4
+ import { compareVersions, isValidVersion } from './version.js';
4
5
  export { marketBranchOk, marketInstallTarget, marketText } from '../lib/market-index.js';
5
6
  export const MARKET_INDEX_SEED = '{\n "format": 1,\n "plugins": []\n}\n';
6
7
  export const MARKET_INDEX_README_LINES = [
@@ -23,7 +24,6 @@ export const MARKET_INDEX_README_LINES = [
23
24
  '在工作台的「模块市场 → 发现」里添加本仓库地址,即可浏览与安装。',
24
25
  '',
25
26
  ];
26
- const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/;
27
27
  const NPM_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
28
28
  const GIT_COMMIT = /^[0-9a-f]{40}$/;
29
29
  const MAX_ICON_CHARS = 32 * 1024;
@@ -52,48 +52,7 @@ function problem(kind, detail, at) {
52
52
  return { kind, detail, ...(at === undefined ? {} : { at }) };
53
53
  }
54
54
  export function compareSemver(left, right) {
55
- const a = SEMVER.exec(left);
56
- const b = SEMVER.exec(right);
57
- if (a === null || b === null)
58
- return undefined;
59
- for (let index = 1; index <= 3; index += 1) {
60
- const diff = Number(a[index]) - Number(b[index]);
61
- if (diff !== 0)
62
- return diff > 0 ? 1 : -1;
63
- }
64
- const preA = a[4];
65
- const preB = b[4];
66
- if (preA === undefined && preB === undefined)
67
- return 0;
68
- if (preA === undefined)
69
- return 1;
70
- if (preB === undefined)
71
- return -1;
72
- const leftParts = preA.split('.');
73
- const rightParts = preB.split('.');
74
- for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
75
- const one = leftParts[index];
76
- const other = rightParts[index];
77
- if (one === undefined)
78
- return -1;
79
- if (other === undefined)
80
- return 1;
81
- const oneNumber = /^\d+$/.test(one);
82
- const otherNumber = /^\d+$/.test(other);
83
- if (oneNumber && otherNumber) {
84
- const diff = Number(one) - Number(other);
85
- if (diff !== 0)
86
- return diff > 0 ? 1 : -1;
87
- continue;
88
- }
89
- if (oneNumber)
90
- return -1;
91
- if (otherNumber)
92
- return 1;
93
- if (one !== other)
94
- return one > other ? 1 : -1;
95
- }
96
- return 0;
55
+ return compareVersions(left, right);
97
56
  }
98
57
  function sourceOf(raw, at) {
99
58
  const record = asRecord(raw);
@@ -129,7 +88,7 @@ function versionOf(raw, at) {
129
88
  if (record === undefined)
130
89
  return problem('version', '版本必须是一个对象', at);
131
90
  const version = asText(record['version']);
132
- if (!SEMVER.test(version))
91
+ if (!isValidVersion(version))
133
92
  return problem('version', `版本号不是合法 SemVer:${JSON.stringify(version)}`, at);
134
93
  const source = sourceOf(record['source'], `${at}.source`);
135
94
  if ('kind' in source)
@@ -43,6 +43,7 @@ export interface SupplyLatestOptions {
43
43
  }
44
44
  export interface SupplyMissingPartsOptions {
45
45
  readonly shell: DshShell;
46
+ readonly name: string;
46
47
  readonly registry: string;
47
48
  readonly version: string;
48
49
  readonly force?: boolean;
@@ -0,0 +1 @@
1
+ export declare const RELEASE_PACKAGES: readonly ["@dsh-workbench/adapter-probe", "@dsh-workbench/adapter-profile", "@dsh-workbench/adapter-runtime", "@dsh-workbench/adapter-supply", "@dsh-workbench/adapters-default", "@dsh-workbench/core", "@dsh-workbench/module-tools", "@dsh-workbench/shared"];
@@ -0,0 +1,10 @@
1
+ export const RELEASE_PACKAGES = [
2
+ '@dsh-workbench/adapter-probe',
3
+ '@dsh-workbench/adapter-profile',
4
+ '@dsh-workbench/adapter-runtime',
5
+ '@dsh-workbench/adapter-supply',
6
+ '@dsh-workbench/adapters-default',
7
+ '@dsh-workbench/core',
8
+ '@dsh-workbench/module-tools',
9
+ '@dsh-workbench/shared',
10
+ ];
@@ -12,9 +12,3 @@ export declare const TEXT_OUTPUT: {
12
12
  render: typeof textRender;
13
13
  };
14
14
  export declare function jsonText(value: unknown): string;
15
- export interface CommandHintNames {
16
- readonly plugin: string;
17
- readonly suiteId: string;
18
- }
19
- export type CommandHint = <T>(command: string, run: () => Promise<T>) => Promise<T>;
20
- export declare function commandHintFor(names: CommandHintNames): CommandHint;
@@ -1,4 +1,3 @@
1
- import { WorkbenchDataError } from './data-error.js';
2
1
  export function sessionWorkspace(exec) {
3
2
  const cwd = exec?.agent?.session?.header?.cwd;
4
3
  return typeof cwd === 'string' && cwd.trim() !== '' ? cwd.trim() : null;
@@ -18,24 +17,3 @@ export const TEXT_OUTPUT = { schema: { type: 'object', properties: {}, additiona
18
17
  export function jsonText(value) {
19
18
  return JSON.stringify(value);
20
19
  }
21
- const UNCLASSIFIED = 'cli-unclassified';
22
- export function commandHintFor(names) {
23
- return async (command, run) => {
24
- try {
25
- return await run();
26
- }
27
- catch (error) {
28
- if (!(error instanceof WorkbenchDataError) || error.code !== UNCLASSIFIED)
29
- throw error;
30
- const advice = `这条命令(${command})在这份 ${names.plugin} 插件里可能不存在(插件版本旧就会缺):` +
31
- `用 workbench_modules_update(id=${names.suiteId})升级插件,或在面板的模块状态里点更新,再重试。`;
32
- throw new WorkbenchDataError(error.code, `${error.message}。${advice}`, {
33
- ...(error.detail === undefined ? {} : { detail: error.detail }),
34
- hint: `升级:workbench_modules_update(id=${names.suiteId})。` +
35
- `终端确认:opencli ${names.plugin} ${command} --help${error.hint === undefined ? '' : `\n${error.hint}`}`,
36
- ...(error.helpUrl === undefined ? {} : { helpUrl: error.helpUrl }),
37
- ...(error.project === undefined ? {} : { project: error.project }),
38
- });
39
- }
40
- };
41
- }
@@ -8,6 +8,9 @@ import type { ApprovalCall, ApprovalFacts, ApprovalPolicy } from '../host/tool-a
8
8
  import type { ValueHandoff } from '../host/value-handoff.js';
9
9
  import type { WorkspaceStore } from '../host/workspace-store.js';
10
10
  import type { DataReader } from '../contract/data.js';
11
+ import type { LibReader } from '../contract/lib.js';
12
+ import type { GitReader } from '../contract/git.js';
13
+ import type { CommandReader } from '../contract/command.js';
11
14
  export interface ToolScopeContext {
12
15
  readonly workspace: string | null;
13
16
  readonly preset: string | null;
@@ -23,6 +26,10 @@ export interface UnitMountDeps {
23
26
  readonly ownedUnits: () => readonly string[];
24
27
  readonly installable?: (() => readonly string[]) | undefined;
25
28
  readonly data: DataReader;
29
+ readonly env?: Readonly<Record<string, string | undefined>> | undefined;
30
+ readonly libs?: LibReader | undefined;
31
+ readonly git?: GitReader | undefined;
32
+ readonly commands?: CommandReader | undefined;
26
33
  readonly store: WorkspaceStore;
27
34
  readonly values?: ValueHandoff | undefined;
28
35
  readonly probe?: (unit: string, options?: {
@@ -1,73 +1,13 @@
1
- import { compareVersions } from './version.js';
2
- function readable(version) {
3
- return compareVersions(version, '0.0.0') !== undefined;
4
- }
5
- function segmentsOf(version) {
6
- if (!readable(version))
7
- return undefined;
8
- return version.split('.').map((part) => Number.parseInt(part, 10));
9
- }
10
- function caretUpper(base) {
11
- const [major = 0, minor = 0, patch = 0] = base;
12
- if (major > 0)
13
- return `${major + 1}.0.0`;
14
- return minor > 0 ? `0.${minor + 1}.0` : `0.0.${patch + 1}`;
15
- }
16
- function tildeUpper(base) {
17
- const [major = 0, minor = 0] = base;
18
- return `${major}.${minor + 1}.0`;
19
- }
20
- function comparator(token, version) {
21
- const match = /^(>=|<=|>|<|=|\^|~)?\s*(.+)$/.exec(token.trim());
22
- if (match === null)
23
- return undefined;
24
- const [, operator = '', raw = ''] = match;
25
- const base = segmentsOf(raw.trim());
26
- if (base === undefined)
27
- return undefined;
28
- const at = (other) => compareVersions(version, other);
29
- switch (operator) {
30
- case '^':
31
- return (at(raw) ?? -1) >= 0 && (at(caretUpper(base)) ?? 1) < 0;
32
- case '~':
33
- return (at(raw) ?? -1) >= 0 && (at(tildeUpper(base)) ?? 1) < 0;
34
- default:
35
- break;
36
- }
37
- const compared = at(raw.trim());
38
- if (compared === undefined)
39
- return undefined;
40
- switch (operator) {
41
- case '>=':
42
- return compared >= 0;
43
- case '<=':
44
- return compared <= 0;
45
- case '>':
46
- return compared > 0;
47
- case '<':
48
- return compared < 0;
49
- case '=':
50
- case '':
51
- return compared === 0;
52
- default:
53
- return undefined;
54
- }
55
- }
1
+ import semver from 'semver';
56
2
  export function checkVersionRange(contract, version) {
57
3
  const spec = contract?.trim() ?? '';
58
4
  if (spec === '')
59
5
  return 'satisfied';
60
- if (!readable(version))
6
+ const range = semver.validRange(spec);
7
+ if (range === null)
61
8
  return 'malformed';
62
- const tokens = spec.split(/\s+/).filter((token) => token !== '');
63
- if (tokens.length === 0)
64
- return 'satisfied';
65
- for (const token of tokens) {
66
- const verdict = comparator(token, version);
67
- if (verdict === undefined)
68
- return 'malformed';
69
- if (!verdict)
70
- return 'unsatisfied';
71
- }
72
- return 'satisfied';
9
+ const parsed = semver.coerce(version);
10
+ if (parsed === null)
11
+ return 'malformed';
12
+ return semver.satisfies(parsed, range) ? 'satisfied' : 'unsatisfied';
73
13
  }
@@ -1,3 +1,4 @@
1
- export declare function runningVersionOf(moduleUrl: string): string | undefined;
2
- export declare function compareVersions(a: string, b: string): number | undefined;
1
+ export declare function compareVersions(left: string, right: string): number | undefined;
2
+ export declare function isValidVersion(value: string): boolean;
3
3
  export declare function isNewerVersion(latest: string, installed: string): boolean;
4
+ export declare function runningVersionOf(moduleUrl: string): string | undefined;
@@ -1,25 +1,14 @@
1
1
  import { readFileSync } from 'node:fs';
2
- export function runningVersionOf(moduleUrl) {
3
- try {
4
- const read = JSON.parse(readFileSync(new URL('../../package.json', moduleUrl), 'utf8'));
5
- return typeof read.version === 'string' && read.version !== '' ? read.version : undefined;
6
- }
7
- catch {
2
+ import semver from 'semver';
3
+ export function compareVersions(left, right) {
4
+ const a = semver.valid(left.trim());
5
+ const b = semver.valid(right.trim());
6
+ if (a === null || b === null)
8
7
  return undefined;
9
- }
8
+ return semver.compare(a, b);
10
9
  }
11
- export function compareVersions(a, b) {
12
- const na = a.split('.').map((part) => Number.parseInt(part, 10));
13
- const nb = b.split('.').map((part) => Number.parseInt(part, 10));
14
- if (na.some(Number.isNaN) || nb.some(Number.isNaN))
15
- return undefined;
16
- for (let index = 0; index < Math.max(na.length, nb.length); index += 1) {
17
- const left = na[index] ?? 0;
18
- const right = nb[index] ?? 0;
19
- if (left !== right)
20
- return left > right ? 1 : -1;
21
- }
22
- return 0;
10
+ export function isValidVersion(value) {
11
+ return semver.valid(value.trim()) !== null;
23
12
  }
24
13
  export function isNewerVersion(latest, installed) {
25
14
  if (latest === installed)
@@ -27,3 +16,12 @@ export function isNewerVersion(latest, installed) {
27
16
  const compared = compareVersions(latest, installed);
28
17
  return compared === undefined ? true : compared > 0;
29
18
  }
19
+ export function runningVersionOf(moduleUrl) {
20
+ try {
21
+ const read = JSON.parse(readFileSync(new URL('../../package.json', moduleUrl), 'utf8'));
22
+ return typeof read.version === 'string' && read.version !== '' ? read.version : undefined;
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }