@dsh-workbench/shared 1.0.0-beta.0 → 1.0.0
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.
- package/dist/components/FormNodes.js +5 -1
- package/dist/components/FormRows.css +1 -0
- package/dist/components/FormRows.d.ts +4 -2
- package/dist/components/FormRows.js +5 -4
- package/dist/config/constants.d.ts +0 -2
- package/dist/config/constants.js +0 -5
- package/dist/contract/command.d.ts +22 -0
- package/dist/contract/command.js +1 -0
- package/dist/contract/data.d.ts +2 -1
- package/dist/contract/extension.d.ts +14 -0
- package/dist/contract/extension.js +1 -0
- package/dist/contract/git.d.ts +38 -0
- package/dist/contract/git.js +1 -0
- package/dist/contract/lib.d.ts +4 -0
- package/dist/contract/lib.js +1 -0
- package/dist/contract/protocol/config-sync.d.ts +1 -21
- package/dist/contract/protocol/market-index.d.ts +1 -0
- package/dist/contract/protocol/profile-create.d.ts +0 -3
- package/dist/host/adapter-set.js +1 -0
- package/dist/host/adapters.d.ts +6 -0
- package/dist/host/extension.d.ts +7 -0
- package/dist/host/extension.js +39 -0
- package/dist/host/git.d.ts +2 -52
- package/dist/host/git.js +1 -64
- package/dist/host/http.js +1 -1
- package/dist/host/market-index.js +3 -44
- package/dist/host/port-types.d.ts +1 -0
- package/dist/host/release-packages.generated.d.ts +1 -0
- package/dist/host/release-packages.generated.js +10 -0
- package/dist/host/tool-kit.d.ts +0 -6
- package/dist/host/tool-kit.js +0 -22
- package/dist/host/unit-face.d.ts +7 -0
- package/dist/host/version-range.js +7 -67
- package/dist/host/version.d.ts +3 -2
- package/dist/host/version.js +17 -19
- package/dist/host.d.ts +6 -0
- package/dist/host.js +6 -0
- package/dist/i18n/en.js +3 -1
- package/dist/i18n/index.d.ts +9 -21
- package/dist/i18n/settings.d.ts +5 -19
- package/dist/i18n/settings.js +7 -35
- package/dist/i18n/zh.d.ts +8 -20
- package/dist/i18n/zh.js +3 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/lib/version.d.ts +4 -0
- package/dist/lib/version.js +21 -0
- package/dist/testing/git.d.ts +3 -0
- package/dist/testing/git.js +36 -0
- package/package.json +6 -4
- package/src/components/Form.test.tsx +6 -0
- package/src/components/FormNodes.tsx +6 -1
- package/src/components/FormRows.css +1 -0
- package/src/components/FormRows.test.tsx +30 -0
- package/src/components/FormRows.tsx +9 -5
- package/src/config/constants.ts +0 -6
- package/src/contract/command.ts +25 -0
- package/src/contract/data.ts +2 -1
- package/src/contract/extension.ts +20 -0
- package/src/contract/git.ts +20 -0
- package/src/contract/lib.ts +5 -0
- package/src/contract/protocol/config-sync.ts +1 -24
- package/src/contract/protocol/market-index.ts +1 -0
- package/src/contract/protocol/profile-create.ts +0 -3
- package/src/host/adapter-set.ts +1 -0
- package/src/host/adapters.ts +6 -0
- package/src/host/extension.test.ts +55 -0
- package/src/host/extension.ts +57 -0
- package/src/host/git.ts +6 -92
- package/src/host/http.ts +1 -1
- package/src/host/market-index.ts +3 -33
- package/src/host/port-types.ts +1 -0
- package/src/host/release-packages.generated.ts +10 -0
- package/src/host/tool-kit.ts +0 -35
- package/src/host/unit-face.ts +7 -0
- package/src/host/version-range.ts +6 -64
- package/src/host/version.ts +18 -18
- package/src/host.ts +6 -0
- package/src/i18n/en.ts +3 -1
- package/src/i18n/settings.ts +7 -35
- package/src/i18n/zh.ts +3 -1
- package/src/index.ts +4 -0
|
@@ -2,35 +2,15 @@ export const CONFIG_SYNC_ROUTE = 'config-sync';
|
|
|
2
2
|
|
|
3
3
|
export const CONFIG_SYNC_RESOURCE = 'configSync';
|
|
4
4
|
|
|
5
|
-
export type ConfigSyncState = 'new' | 'differs'
|
|
6
|
-
|
|
7
|
-
export type ConfigSyncAssetState = 'missing' | 'differs' | 'same' | 'external';
|
|
8
|
-
|
|
9
|
-
export interface ConfigSyncAssetFile {
|
|
10
|
-
readonly path: string;
|
|
11
|
-
readonly state: ConfigSyncAssetState;
|
|
12
|
-
readonly bytes: number | null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ConfigSyncAsset {
|
|
16
|
-
readonly key: string;
|
|
17
|
-
readonly path: string;
|
|
18
|
-
readonly kind: 'file' | 'dir';
|
|
19
|
-
readonly state: ConfigSyncAssetState;
|
|
20
|
-
readonly files: readonly ConfigSyncAssetFile[];
|
|
21
|
-
readonly bytes: number;
|
|
22
|
-
readonly reason?: string;
|
|
23
|
-
}
|
|
5
|
+
export type ConfigSyncState = 'new' | 'differs';
|
|
24
6
|
|
|
25
7
|
export interface ConfigSyncRow {
|
|
26
8
|
readonly key: string;
|
|
27
9
|
readonly id: string;
|
|
28
|
-
readonly insert: boolean;
|
|
29
10
|
readonly state: ConfigSyncState;
|
|
30
11
|
readonly body: string;
|
|
31
12
|
readonly ownBody: string | null;
|
|
32
13
|
readonly missing: readonly string[];
|
|
33
|
-
readonly assets: readonly ConfigSyncAsset[];
|
|
34
14
|
}
|
|
35
15
|
|
|
36
16
|
export interface ConfigSyncProfile {
|
|
@@ -52,7 +32,6 @@ export interface ConfigSyncSnapshot {
|
|
|
52
32
|
export interface ConfigSyncApplied {
|
|
53
33
|
readonly written: readonly string[];
|
|
54
34
|
readonly skipped: readonly string[];
|
|
55
|
-
readonly files: readonly string[];
|
|
56
35
|
readonly verified: boolean;
|
|
57
36
|
}
|
|
58
37
|
|
|
@@ -60,6 +39,4 @@ export interface ConfigSyncRequest {
|
|
|
60
39
|
readonly source: string;
|
|
61
40
|
readonly keys: readonly string[];
|
|
62
41
|
readonly overwrite: readonly string[];
|
|
63
|
-
readonly assets?: readonly string[];
|
|
64
|
-
readonly overwriteAssets?: readonly string[];
|
|
65
42
|
}
|
|
@@ -217,6 +217,7 @@ export interface PublishChangelog {
|
|
|
217
217
|
export interface PublishPreview {
|
|
218
218
|
readonly name: string;
|
|
219
219
|
readonly version: string;
|
|
220
|
+
readonly author?: string;
|
|
220
221
|
readonly registry?: string;
|
|
221
222
|
readonly market?: MarketPublishDestination;
|
|
222
223
|
readonly changelog?: PublishChangelog;
|
package/src/host/adapter-set.ts
CHANGED
|
@@ -265,6 +265,7 @@ export function withDefaultAdapterSet(defaults: AdapterSet, loaded: AdapterSetLo
|
|
|
265
265
|
id: set.id ?? defaults.id,
|
|
266
266
|
version: set.version ?? defaults.version,
|
|
267
267
|
data: set.data ?? defaults.data,
|
|
268
|
+
libs: set.libs ?? defaults.libs,
|
|
268
269
|
supply: set.supply ?? defaults.supply,
|
|
269
270
|
modules: set.modules ?? defaults.modules,
|
|
270
271
|
profile: set.profile ?? defaults.profile,
|
package/src/host/adapters.ts
CHANGED
|
@@ -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 {
|
|
4
7
|
EnvSupplyPort,
|
|
5
8
|
HostActions,
|
|
@@ -21,6 +24,9 @@ export interface AdapterSet {
|
|
|
21
24
|
readonly id: string;
|
|
22
25
|
readonly version: string;
|
|
23
26
|
readonly data: DataReader;
|
|
27
|
+
readonly libs?: LibReader | undefined;
|
|
28
|
+
readonly git?: GitReader | undefined;
|
|
29
|
+
readonly commands?: CommandReader | undefined;
|
|
24
30
|
readonly supply: EnvSupplyPort;
|
|
25
31
|
readonly modules: ModulePort;
|
|
26
32
|
readonly profile: ProfilePort;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { DataReader } from '../contract/data.js';
|
|
3
|
+
import type { ExtensionNeed } from '../contract/needs.js';
|
|
4
|
+
import { extensionCallerOf } from './extension.js';
|
|
5
|
+
|
|
6
|
+
const NEED: ExtensionNeed = {
|
|
7
|
+
kind: 'extension',
|
|
8
|
+
runtime: 'opencli',
|
|
9
|
+
name: 'coding',
|
|
10
|
+
purpose: '测试',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function failingData(): DataReader {
|
|
14
|
+
return {
|
|
15
|
+
read: async () => ({
|
|
16
|
+
ok: false,
|
|
17
|
+
error: {
|
|
18
|
+
code: 'command',
|
|
19
|
+
message: 'opencli 这条命令没跑通(退出码 1)',
|
|
20
|
+
detail: "error: unknown command 'issue-statuses'",
|
|
21
|
+
pageCode: 'cli-unclassified',
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
} as unknown as DataReader;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('extensionCallerOf', () => {
|
|
28
|
+
it('cli-unclassified 补“升级插件”的 message 与 hint,id 用调用方给的 suiteId', async () => {
|
|
29
|
+
const caller = extensionCallerOf(failingData(), NEED, {
|
|
30
|
+
suiteId: 'coding-issues',
|
|
31
|
+
});
|
|
32
|
+
await expect(caller.json(['issue-statuses'])).rejects.toMatchObject({
|
|
33
|
+
code: 'cli-unclassified',
|
|
34
|
+
message: expect.stringContaining('workbench_modules_update(id=coding-issues)'),
|
|
35
|
+
hint: expect.stringContaining('opencli coding issue-statuses --help'),
|
|
36
|
+
detail: "error: unknown command 'issue-statuses'",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('别的码不补“插件版本旧”', async () => {
|
|
41
|
+
const caller = extensionCallerOf(failingData(), NEED, {
|
|
42
|
+
suiteId: 'coding-issues',
|
|
43
|
+
});
|
|
44
|
+
const data = {
|
|
45
|
+
read: async () => ({
|
|
46
|
+
ok: false,
|
|
47
|
+
error: { code: 'auth', message: '需要登录', pageCode: 'auth-required' },
|
|
48
|
+
}),
|
|
49
|
+
} as unknown as DataReader;
|
|
50
|
+
await expect(
|
|
51
|
+
extensionCallerOf(data, NEED, { suiteId: 'x' }).json(['issue-statuses']),
|
|
52
|
+
).rejects.toMatchObject({ code: 'auth-required', message: '需要登录' });
|
|
53
|
+
expect(caller).toBeDefined();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { DataReader } from '../contract/data.js';
|
|
2
|
+
import type { ExtensionCaller, ExtensionCallOptions, ExtensionValue } from '../contract/extension.js';
|
|
3
|
+
import type { ExtensionNeed } from '../contract/needs.js';
|
|
4
|
+
import { WorkbenchDataError } from './data-error.js';
|
|
5
|
+
|
|
6
|
+
function cliCodeOf(code: string): string {
|
|
7
|
+
if (code === 'auth') return 'auth-required';
|
|
8
|
+
if (code === 'timeout') return 'cli-timeout';
|
|
9
|
+
if (code === 'parse') return 'cli-bad-output';
|
|
10
|
+
return 'cli-exec-failed';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ExtensionCallerOptions {
|
|
14
|
+
readonly suiteId?: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function extensionCallerOf(
|
|
18
|
+
data: DataReader,
|
|
19
|
+
need: ExtensionNeed,
|
|
20
|
+
callerOptions: ExtensionCallerOptions = {},
|
|
21
|
+
): ExtensionCaller {
|
|
22
|
+
return {
|
|
23
|
+
async json(
|
|
24
|
+
args: readonly string[],
|
|
25
|
+
options: ExtensionCallOptions = {},
|
|
26
|
+
): Promise<ExtensionValue> {
|
|
27
|
+
const result = await data.read({
|
|
28
|
+
kind: 'command',
|
|
29
|
+
adapter: need.runtime,
|
|
30
|
+
argv: [need.name, ...args],
|
|
31
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
32
|
+
});
|
|
33
|
+
if (result.ok) return { value: result.data, at: Date.now() };
|
|
34
|
+
const code = result.error.pageCode ?? cliCodeOf(result.error.code);
|
|
35
|
+
const command = [need.name, ...args].join(' ');
|
|
36
|
+
const advice =
|
|
37
|
+
code === 'cli-unclassified'
|
|
38
|
+
? `这条命令(${command})在这份 ${need.name} 插件里可能不存在(插件版本旧就会缺):用 workbench_modules_update(id=${callerOptions.suiteId ?? need.name})升级,或在面板的模块状态里点更新,再重试。终端确认:opencli ${need.name} ${args[0] ?? ''} --help`
|
|
39
|
+
: undefined;
|
|
40
|
+
const hint =
|
|
41
|
+
advice === undefined
|
|
42
|
+
? result.error.hint
|
|
43
|
+
: result.error.hint === undefined
|
|
44
|
+
? advice
|
|
45
|
+
: `${advice}\n${result.error.hint}`;
|
|
46
|
+
throw new WorkbenchDataError(
|
|
47
|
+
code,
|
|
48
|
+
advice === undefined ? result.error.message : `${result.error.message}。${advice}`,
|
|
49
|
+
{
|
|
50
|
+
...(result.error.detail === undefined ? {} : { detail: result.error.detail }),
|
|
51
|
+
...(hint === undefined ? {} : { hint }),
|
|
52
|
+
...(result.error.helpUrl === undefined ? {} : { helpUrl: result.error.helpUrl }),
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
package/src/host/git.ts
CHANGED
|
@@ -1,99 +1,13 @@
|
|
|
1
|
-
import type { DshShell } from '../api/dsh.js';
|
|
2
|
-
import { quoteShellArg, tailLines } from '../host/shell-text.js';
|
|
3
|
-
import { runShell } from '../host/shell.js';
|
|
4
1
|
|
|
5
|
-
export type
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
export type {
|
|
3
|
+
BranchRead,
|
|
4
|
+
HeadRead,
|
|
5
|
+
OriginRead,
|
|
6
|
+
} from '../contract/git.js';
|
|
7
|
+
import type { BranchRead, HeadRead, OriginRead } from '../contract/git.js';
|
|
9
8
|
|
|
10
9
|
export type OriginReader = (path: string) => Promise<OriginRead>;
|
|
11
10
|
|
|
12
|
-
export type BranchRead =
|
|
13
|
-
| { readonly kind: 'ok'; readonly branch: string }
|
|
14
|
-
| { readonly kind: 'no-branch'; readonly detail?: string | undefined }
|
|
15
|
-
| { readonly kind: 'unknown'; readonly reason: string; readonly detail?: string | undefined };
|
|
16
|
-
|
|
17
11
|
export type BranchReader = (path: string) => Promise<BranchRead>;
|
|
18
12
|
|
|
19
|
-
export type HeadRead =
|
|
20
|
-
| { readonly kind: 'ok'; readonly commit: string }
|
|
21
|
-
| { readonly kind: 'no-head'; readonly detail?: string }
|
|
22
|
-
| { readonly kind: 'unknown'; readonly reason: string; readonly detail?: string };
|
|
23
|
-
|
|
24
13
|
export type HeadReader = (path: string) => Promise<HeadRead>;
|
|
25
|
-
|
|
26
|
-
export function createHeadReader(options: {
|
|
27
|
-
shell: DshShell;
|
|
28
|
-
logger?: { warn?(message: string): void } | undefined;
|
|
29
|
-
}): HeadReader {
|
|
30
|
-
const { shell, logger } = options;
|
|
31
|
-
|
|
32
|
-
return async function readHead(path: string): Promise<HeadRead> {
|
|
33
|
-
const key = String(path ?? '').trim();
|
|
34
|
-
if (!key) return { kind: 'unknown', reason: 'no-path' };
|
|
35
|
-
|
|
36
|
-
let result;
|
|
37
|
-
try {
|
|
38
|
-
result = await runShell(shell, `git -C ${quoteShellArg(key)} rev-parse HEAD`);
|
|
39
|
-
} catch (error) {
|
|
40
|
-
logger?.warn?.(`[dsh-workbench] git rev-parse 执行失败:${String(error)}`);
|
|
41
|
-
return { kind: 'unknown', reason: 'exec-failed', detail: String(error) };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (result.exitCode !== 0) {
|
|
45
|
-
return { kind: 'no-head', detail: tailLines(result.stderr, 3) };
|
|
46
|
-
}
|
|
47
|
-
return { kind: 'ok', commit: result.stdout.trim() };
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function createBranchReader(options: {
|
|
52
|
-
shell: DshShell;
|
|
53
|
-
logger?: { warn?(message: string): void } | undefined;
|
|
54
|
-
}): BranchReader {
|
|
55
|
-
const { shell, logger } = options;
|
|
56
|
-
|
|
57
|
-
return async function readBranch(path: string): Promise<BranchRead> {
|
|
58
|
-
const key = String(path ?? '').trim();
|
|
59
|
-
if (!key) return { kind: 'unknown', reason: 'no-path' };
|
|
60
|
-
|
|
61
|
-
let result;
|
|
62
|
-
try {
|
|
63
|
-
result = await runShell(shell, `git -C ${quoteShellArg(key)} rev-parse --abbrev-ref HEAD`);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
logger?.warn?.(`[dsh-workbench] git rev-parse --abbrev-ref 执行失败:${String(error)}`);
|
|
66
|
-
return { kind: 'unknown', reason: 'exec-failed', detail: String(error) };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const name = result.stdout.trim();
|
|
70
|
-
if (result.exitCode !== 0) return { kind: 'no-branch', detail: tailLines(result.stderr, 3) };
|
|
71
|
-
if (name === '' || name === 'HEAD') return { kind: 'no-branch', detail: name };
|
|
72
|
-
return { kind: 'ok', branch: name };
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function createOriginReader(options: {
|
|
77
|
-
shell: DshShell;
|
|
78
|
-
logger?: { warn?(message: string): void } | undefined;
|
|
79
|
-
}): OriginReader {
|
|
80
|
-
const { shell, logger } = options;
|
|
81
|
-
|
|
82
|
-
return async function readOrigin(path: string): Promise<OriginRead> {
|
|
83
|
-
const key = String(path ?? '').trim();
|
|
84
|
-
if (!key) return { kind: 'unknown', reason: 'no-path' };
|
|
85
|
-
|
|
86
|
-
let result;
|
|
87
|
-
try {
|
|
88
|
-
result = await runShell(shell, `git -C ${quoteShellArg(key)} remote get-url origin`);
|
|
89
|
-
} catch (error) {
|
|
90
|
-
logger?.warn?.(`[dsh-workbench] git remote 执行失败:${String(error)}`);
|
|
91
|
-
return { kind: 'unknown', reason: 'exec-failed', detail: String(error) };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (result.exitCode !== 0) {
|
|
95
|
-
return { kind: 'no-origin', detail: tailLines(result.stderr, 3) };
|
|
96
|
-
}
|
|
97
|
-
return { kind: 'ok', remote: result.stdout.trim() };
|
|
98
|
-
};
|
|
99
|
-
}
|
package/src/host/http.ts
CHANGED
|
@@ -90,7 +90,7 @@ export function registerApi(ctx: DshHostContext, routes: RouteTable): () => void
|
|
|
90
90
|
try {
|
|
91
91
|
sendJson(res, 200, await handler(search));
|
|
92
92
|
} catch (error) {
|
|
93
|
-
if (error
|
|
93
|
+
if (isDataError(error)) {
|
|
94
94
|
sendJson(res, 200, { ok: false, error: toErrorPayload(error) });
|
|
95
95
|
return;
|
|
96
96
|
}
|
package/src/host/market-index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
} from '../contract/protocol/market-index.js';
|
|
14
14
|
import { MARKET_INDEX_FORMAT } from '../contract/protocol/market-index.js';
|
|
15
15
|
import { marketText } from '../lib/market-index.js';
|
|
16
|
+
import { compareVersions, isValidVersion } from './version.js';
|
|
16
17
|
|
|
17
18
|
export { marketBranchOk, marketInstallTarget, marketText } from '../lib/market-index.js';
|
|
18
19
|
|
|
@@ -39,7 +40,6 @@ export const MARKET_INDEX_README_LINES: readonly string[] = [
|
|
|
39
40
|
'',
|
|
40
41
|
];
|
|
41
42
|
|
|
42
|
-
const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/;
|
|
43
43
|
const NPM_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
44
44
|
const GIT_COMMIT = /^[0-9a-f]{40}$/;
|
|
45
45
|
const MAX_ICON_CHARS = 32 * 1024;
|
|
@@ -73,37 +73,7 @@ function problem(kind: MarketIndexProblem['kind'], detail: string, at?: string):
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
export function compareSemver(left: string, right: string): number | undefined {
|
|
76
|
-
|
|
77
|
-
const b = SEMVER.exec(right);
|
|
78
|
-
if (a === null || b === null) return undefined;
|
|
79
|
-
for (let index = 1; index <= 3; index += 1) {
|
|
80
|
-
const diff = Number(a[index]) - Number(b[index]);
|
|
81
|
-
if (diff !== 0) return diff > 0 ? 1 : -1;
|
|
82
|
-
}
|
|
83
|
-
const preA = a[4];
|
|
84
|
-
const preB = b[4];
|
|
85
|
-
if (preA === undefined && preB === undefined) return 0;
|
|
86
|
-
if (preA === undefined) return 1;
|
|
87
|
-
if (preB === undefined) return -1;
|
|
88
|
-
const leftParts = preA.split('.');
|
|
89
|
-
const rightParts = preB.split('.');
|
|
90
|
-
for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
|
|
91
|
-
const one = leftParts[index];
|
|
92
|
-
const other = rightParts[index];
|
|
93
|
-
if (one === undefined) return -1;
|
|
94
|
-
if (other === undefined) return 1;
|
|
95
|
-
const oneNumber = /^\d+$/.test(one);
|
|
96
|
-
const otherNumber = /^\d+$/.test(other);
|
|
97
|
-
if (oneNumber && otherNumber) {
|
|
98
|
-
const diff = Number(one) - Number(other);
|
|
99
|
-
if (diff !== 0) return diff > 0 ? 1 : -1;
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (oneNumber) return -1;
|
|
103
|
-
if (otherNumber) return 1;
|
|
104
|
-
if (one !== other) return one > other ? 1 : -1;
|
|
105
|
-
}
|
|
106
|
-
return 0;
|
|
76
|
+
return compareVersions(left, right);
|
|
107
77
|
}
|
|
108
78
|
|
|
109
79
|
function sourceOf(raw: unknown, at: string): MarketIndexSource | MarketIndexProblem {
|
|
@@ -138,7 +108,7 @@ function versionOf(raw: unknown, at: string): MarketIndexVersion | MarketIndexPr
|
|
|
138
108
|
const record = asRecord(raw);
|
|
139
109
|
if (record === undefined) return problem('version', '版本必须是一个对象', at);
|
|
140
110
|
const version = asText(record['version']);
|
|
141
|
-
if (!
|
|
111
|
+
if (!isValidVersion(version)) return problem('version', `版本号不是合法 SemVer:${JSON.stringify(version)}`, at);
|
|
142
112
|
const source = sourceOf(record['source'], `${at}.source`);
|
|
143
113
|
if ('kind' in source) return source;
|
|
144
114
|
const timestamp = record['publishTimestamp'];
|
package/src/host/port-types.ts
CHANGED
|
@@ -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
|
+
] as const;
|
package/src/host/tool-kit.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { DshToolRunContext } from '../api/dsh.js';
|
|
2
|
-
import { WorkbenchDataError } from './data-error.js';
|
|
3
2
|
|
|
4
3
|
export function sessionWorkspace(exec: DshToolRunContext | undefined): string | null {
|
|
5
4
|
const cwd = exec?.agent?.session?.header?.cwd;
|
|
@@ -25,37 +24,3 @@ export const TEXT_OUTPUT = { schema: { type: 'object', properties: {}, additiona
|
|
|
25
24
|
export function jsonText(value: unknown): string {
|
|
26
25
|
return JSON.stringify(value);
|
|
27
26
|
}
|
|
28
|
-
|
|
29
|
-
const UNCLASSIFIED = 'cli-unclassified';
|
|
30
|
-
|
|
31
|
-
export interface CommandHintNames {
|
|
32
|
-
readonly plugin: string;
|
|
33
|
-
readonly suiteId: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type CommandHint = <T>(command: string, run: () => Promise<T>) => Promise<T>;
|
|
37
|
-
|
|
38
|
-
export function commandHintFor(names: CommandHintNames): CommandHint {
|
|
39
|
-
return async (command, run) => {
|
|
40
|
-
try {
|
|
41
|
-
return await run();
|
|
42
|
-
} catch (error) {
|
|
43
|
-
if (!(error instanceof WorkbenchDataError) || error.code !== UNCLASSIFIED) throw error;
|
|
44
|
-
|
|
45
|
-
const advice =
|
|
46
|
-
`这条命令(${command})在这份 ${names.plugin} 插件里可能不存在(插件版本旧就会缺):` +
|
|
47
|
-
`用 workbench_modules_update(id=${names.suiteId})升级插件,或在面板的模块状态里点更新,再重试。`;
|
|
48
|
-
|
|
49
|
-
throw new WorkbenchDataError(error.code, `${error.message}。${advice}`, {
|
|
50
|
-
...(error.detail === undefined ? {} : { detail: error.detail }),
|
|
51
|
-
hint:
|
|
52
|
-
`升级:workbench_modules_update(id=${names.suiteId})。` +
|
|
53
|
-
`终端确认:opencli ${names.plugin} ${command} --help${
|
|
54
|
-
error.hint === undefined ? '' : `\n${error.hint}`
|
|
55
|
-
}`,
|
|
56
|
-
...(error.helpUrl === undefined ? {} : { helpUrl: error.helpUrl }),
|
|
57
|
-
...(error.project === undefined ? {} : { project: error.project }),
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
}
|
package/src/host/unit-face.ts
CHANGED
|
@@ -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
|
|
|
12
15
|
export interface ToolScopeContext {
|
|
13
16
|
readonly workspace: string | null;
|
|
@@ -22,6 +25,10 @@ export interface UnitMountDeps {
|
|
|
22
25
|
readonly ownedUnits: () => readonly string[];
|
|
23
26
|
readonly installable?: (() => readonly string[]) | undefined;
|
|
24
27
|
readonly data: DataReader;
|
|
28
|
+
readonly env?: Readonly<Record<string, string | undefined>> | undefined;
|
|
29
|
+
readonly libs?: LibReader | undefined;
|
|
30
|
+
readonly git?: GitReader | undefined;
|
|
31
|
+
readonly commands?: CommandReader | undefined;
|
|
25
32
|
readonly store: WorkspaceStore;
|
|
26
33
|
readonly values?: ValueHandoff | undefined;
|
|
27
34
|
readonly probe?: (
|
|
@@ -1,71 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import semver from 'semver';
|
|
2
2
|
|
|
3
3
|
export type VersionVerdict = 'satisfied' | 'unsatisfied' | 'malformed';
|
|
4
4
|
|
|
5
|
-
function readable(version: string): boolean {
|
|
6
|
-
return compareVersions(version, '0.0.0') !== undefined;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function segmentsOf(version: string): number[] | undefined {
|
|
10
|
-
if (!readable(version)) return undefined;
|
|
11
|
-
return version.split('.').map((part) => Number.parseInt(part, 10));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function caretUpper(base: readonly number[]): string {
|
|
15
|
-
const [major = 0, minor = 0, patch = 0] = base;
|
|
16
|
-
if (major > 0) return `${major + 1}.0.0`;
|
|
17
|
-
return minor > 0 ? `0.${minor + 1}.0` : `0.0.${patch + 1}`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function tildeUpper(base: readonly number[]): string {
|
|
21
|
-
const [major = 0, minor = 0] = base;
|
|
22
|
-
return `${major}.${minor + 1}.0`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function comparator(token: string, version: string): boolean | undefined {
|
|
26
|
-
const match = /^(>=|<=|>|<|=|\^|~)?\s*(.+)$/.exec(token.trim());
|
|
27
|
-
if (match === null) return undefined;
|
|
28
|
-
const [, operator = '', raw = ''] = match;
|
|
29
|
-
const base = segmentsOf(raw.trim());
|
|
30
|
-
if (base === undefined) return undefined;
|
|
31
|
-
const at = (other: string): number | undefined => compareVersions(version, other);
|
|
32
|
-
switch (operator) {
|
|
33
|
-
case '^':
|
|
34
|
-
return (at(raw) ?? -1) >= 0 && (at(caretUpper(base)) ?? 1) < 0;
|
|
35
|
-
case '~':
|
|
36
|
-
return (at(raw) ?? -1) >= 0 && (at(tildeUpper(base)) ?? 1) < 0;
|
|
37
|
-
default:
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
const compared = at(raw.trim());
|
|
41
|
-
if (compared === undefined) return undefined;
|
|
42
|
-
switch (operator) {
|
|
43
|
-
case '>=':
|
|
44
|
-
return compared >= 0;
|
|
45
|
-
case '<=':
|
|
46
|
-
return compared <= 0;
|
|
47
|
-
case '>':
|
|
48
|
-
return compared > 0;
|
|
49
|
-
case '<':
|
|
50
|
-
return compared < 0;
|
|
51
|
-
case '=':
|
|
52
|
-
case '':
|
|
53
|
-
return compared === 0;
|
|
54
|
-
default:
|
|
55
|
-
return undefined;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
5
|
export function checkVersionRange(contract: string | undefined, version: string): VersionVerdict {
|
|
60
6
|
const spec = contract?.trim() ?? '';
|
|
61
7
|
if (spec === '') return 'satisfied';
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (verdict === undefined) return 'malformed';
|
|
68
|
-
if (!verdict) return 'unsatisfied';
|
|
69
|
-
}
|
|
70
|
-
return 'satisfied';
|
|
8
|
+
const range = semver.validRange(spec);
|
|
9
|
+
if (range === null) return 'malformed';
|
|
10
|
+
const parsed = semver.coerce(version);
|
|
11
|
+
if (parsed === null) return 'malformed';
|
|
12
|
+
return semver.satisfies(parsed, range) ? 'satisfied' : 'unsatisfied';
|
|
71
13
|
}
|
package/src/host/version.ts
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import semver from 'semver';
|
|
3
|
+
|
|
4
|
+
export function compareVersions(left: string, right: string): number | undefined {
|
|
5
|
+
const a = semver.valid(left.trim());
|
|
6
|
+
const b = semver.valid(right.trim());
|
|
7
|
+
if (a === null || b === null) return undefined;
|
|
8
|
+
return semver.compare(a, b);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isValidVersion(value: string): boolean {
|
|
12
|
+
return semver.valid(value.trim()) !== null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isNewerVersion(latest: string, installed: string): boolean {
|
|
16
|
+
if (latest === installed) return false;
|
|
17
|
+
const compared = compareVersions(latest, installed);
|
|
18
|
+
return compared === undefined ? true : compared > 0;
|
|
19
|
+
}
|
|
2
20
|
|
|
3
21
|
export function runningVersionOf(moduleUrl: string): string | undefined {
|
|
4
22
|
try {
|
|
@@ -10,21 +28,3 @@ export function runningVersionOf(moduleUrl: string): string | undefined {
|
|
|
10
28
|
return undefined;
|
|
11
29
|
}
|
|
12
30
|
}
|
|
13
|
-
|
|
14
|
-
export function compareVersions(a: string, b: string): number | undefined {
|
|
15
|
-
const na = a.split('.').map((part) => Number.parseInt(part, 10));
|
|
16
|
-
const nb = b.split('.').map((part) => Number.parseInt(part, 10));
|
|
17
|
-
if (na.some(Number.isNaN) || nb.some(Number.isNaN)) return undefined;
|
|
18
|
-
for (let index = 0; index < Math.max(na.length, nb.length); index += 1) {
|
|
19
|
-
const left = na[index] ?? 0;
|
|
20
|
-
const right = nb[index] ?? 0;
|
|
21
|
-
if (left !== right) return left > right ? 1 : -1;
|
|
22
|
-
}
|
|
23
|
-
return 0;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function isNewerVersion(latest: string, installed: string): boolean {
|
|
27
|
-
if (latest === installed) return false;
|
|
28
|
-
const compared = compareVersions(latest, installed);
|
|
29
|
-
return compared === undefined ? true : compared > 0;
|
|
30
|
-
}
|
package/src/host.ts
CHANGED
|
@@ -3,6 +3,10 @@ export * from './api/context-ref.js';
|
|
|
3
3
|
export * from './contract/unit.js';
|
|
4
4
|
export * from './contract/needs.js';
|
|
5
5
|
export * from './contract/data.js';
|
|
6
|
+
export * from './contract/extension.js';
|
|
7
|
+
export * from './contract/lib.js';
|
|
8
|
+
export * from './contract/git.js';
|
|
9
|
+
export * from './contract/command.js';
|
|
6
10
|
export * from './contract/unit-board.js';
|
|
7
11
|
export * from './contract/workbench-contract.js';
|
|
8
12
|
export * from './contract/blocker.js';
|
|
@@ -41,6 +45,7 @@ export * from './host/provision.js';
|
|
|
41
45
|
export * from './host/channel-plugin.js';
|
|
42
46
|
export * from './host/recipe.js';
|
|
43
47
|
export * from './host/recipe-rules.js';
|
|
48
|
+
export * from './host/release-packages.generated.js';
|
|
44
49
|
export * from './contract/unit-capability.js';
|
|
45
50
|
export * from './contract/plugin-source.js';
|
|
46
51
|
export * from './contract/cli.js';
|
|
@@ -63,6 +68,7 @@ export * from './host/patch.js';
|
|
|
63
68
|
export * from './host/adapters.js';
|
|
64
69
|
export * from './host/adapter-set.js';
|
|
65
70
|
export * from './host/needs.js';
|
|
71
|
+
export * from './host/extension.js';
|
|
66
72
|
export * from './host/port-types.js';
|
|
67
73
|
export * from './host/version-range.js';
|
|
68
74
|
export * from './host/market-index.js';
|
package/src/i18n/en.ts
CHANGED
|
@@ -277,6 +277,7 @@ export const en: Record<keyof WorkbenchMessages, string> = {
|
|
|
277
277
|
'panels.discover.loading': 'Reading the index',
|
|
278
278
|
'panels.discover.lead': 'Browse modules from an index source; install and go.',
|
|
279
279
|
'panels.discover.addSource': 'Add',
|
|
280
|
+
'panels.discover.addingSource': 'Adding…',
|
|
280
281
|
'panels.discover.addFirstSource': 'Add index source',
|
|
281
282
|
'panels.discover.removeSource': 'Remove',
|
|
282
283
|
'panels.discover.removeSourceNamed': 'Remove {url}',
|
|
@@ -377,7 +378,6 @@ export const en: Record<keyof WorkbenchMessages, string> = {
|
|
|
377
378
|
'panels.discover.tabOverview': 'Overview',
|
|
378
379
|
'panels.discover.tabReadme': 'Readme',
|
|
379
380
|
'panels.discover.tabChangelog': 'Changelog',
|
|
380
|
-
'panels.discover.docTitle': '{kind} for {version}',
|
|
381
381
|
'panels.discover.docReadme': 'readme',
|
|
382
382
|
'panels.discover.docChangelog': 'changelog',
|
|
383
383
|
'panels.discover.docLoading': 'Reading…',
|
|
@@ -622,6 +622,8 @@ export const en: Record<keyof WorkbenchMessages, string> = {
|
|
|
622
622
|
'approval.publish.module': 'Module',
|
|
623
623
|
'approval.publish.target': 'Target',
|
|
624
624
|
'approval.publish.version': 'Version',
|
|
625
|
+
'approval.publish.author': 'Author',
|
|
626
|
+
'approval.publish.authorMissing': 'unsigned (commits use the local git identity)',
|
|
625
627
|
'approval.publish.registry': 'Registry',
|
|
626
628
|
'approval.publish.repo': 'Repository',
|
|
627
629
|
'approval.publish.branch': 'Branch',
|