@dsh-workbench/shared 1.1.7 → 1.1.9
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/api/blocker-table.d.ts +1 -0
- package/dist/api/blocker-table.js +1 -1
- package/dist/components/SelectField.css +0 -6
- package/dist/components/SelectField.d.ts +2 -3
- package/dist/components/SelectField.js +9 -30
- package/dist/components/SelectFieldNodes.d.ts +3 -1
- package/dist/components/SelectFieldNodes.js +2 -1
- package/dist/components/SelectFieldWheel.d.ts +9 -0
- package/dist/components/SelectFieldWheel.js +35 -0
- package/dist/config/constants.d.ts +4 -4
- package/dist/config/constants.js +4 -4
- package/dist/contract/capabilities.d.ts +29 -0
- package/dist/contract/capabilities.js +1 -0
- package/dist/contract/needs.d.ts +3 -3
- package/dist/contract/needs.js +1 -1
- package/dist/contract/plugin-source.d.ts +19 -0
- package/dist/contract/protocol/bundle-install.d.ts +3 -1
- package/dist/contract/protocol/bundle-install.js +3 -0
- package/dist/contract/protocol/market-index.d.ts +1 -0
- package/dist/contract/protocol/platform.d.ts +1 -1
- package/dist/contract/runner.d.ts +6 -0
- package/dist/contract/runner.js +1 -0
- package/dist/contract/unit.d.ts +4 -4
- package/dist/host/adapter-contract.d.ts +3 -0
- package/dist/host/adapter-contract.js +93 -0
- package/dist/host/adapter-set.js +6 -4
- package/dist/host/adapter-types.d.ts +1 -1
- package/dist/host/bundle-manager.d.ts +3 -0
- package/dist/host/bundle-manager.js +181 -0
- package/dist/host/cli.js +3 -1
- package/dist/host/config-sync.d.ts +48 -0
- package/dist/host/config-sync.js +342 -0
- package/dist/host/dsh-plugins.d.ts +5 -0
- package/dist/host/dsh-plugins.js +51 -0
- package/dist/host/extension.js +1 -1
- package/dist/host/flow/check.d.ts +9 -0
- package/dist/host/flow/check.js +179 -0
- package/dist/host/flow/expr.d.ts +30 -0
- package/dist/host/flow/expr.js +99 -0
- package/dist/host/flow/kit.d.ts +14 -0
- package/dist/host/flow/kit.js +13 -0
- package/dist/host/flow/run.d.ts +13 -0
- package/dist/host/flow/run.js +287 -0
- package/dist/host/flow/types.d.ts +188 -0
- package/dist/host/flow/types.js +1 -0
- package/dist/host/host-actions.d.ts +2 -0
- package/dist/host/host-actions.js +5 -0
- package/dist/host/host-kit.d.ts +19 -0
- package/dist/host/host-kit.js +14 -0
- package/dist/host/http.d.ts +2 -6
- package/dist/host/http.js +2 -2
- package/dist/host/installed-module.d.ts +23 -0
- package/dist/host/installed-module.js +264 -0
- package/dist/host/lib-install.d.ts +7 -9
- package/dist/host/lib-install.js +9 -12
- package/dist/host/lib-runners.d.ts +3 -5
- package/dist/host/lib-runners.js +2 -2
- package/dist/host/local-declaration.d.ts +7 -0
- package/dist/host/local-declaration.js +33 -0
- package/dist/host/market-index-claim.d.ts +0 -1
- package/dist/host/market-index-claim.js +0 -3
- package/dist/host/market-index-doc.d.ts +1 -45
- package/dist/host/market-index-parse.d.ts +9 -4
- package/dist/host/market-index-parse.js +17 -92
- package/dist/host/market-index.d.ts +5 -6
- package/dist/host/market-index.js +29 -87
- package/dist/host/module-root.d.ts +1 -0
- package/dist/host/module-root.js +5 -0
- package/dist/host/npm-registry.d.ts +10 -0
- package/dist/host/npm-registry.js +16 -0
- package/dist/host/opencli-runner.d.ts +2 -1
- package/dist/host/package-content.d.ts +7 -0
- package/dist/host/package-content.js +69 -0
- package/dist/host/package-kind.d.ts +13 -0
- package/dist/host/package-kind.js +51 -0
- package/dist/host/package-route.d.ts +31 -0
- package/dist/host/package-route.js +57 -0
- package/dist/host/paths.d.ts +11 -19
- package/dist/host/paths.js +16 -43
- package/dist/host/platform.js +1 -1
- package/dist/host/plugin-ledger.d.ts +8 -8
- package/dist/host/plugin-ledger.js +23 -16
- package/dist/host/plugin.d.ts +1 -2
- package/dist/host/plugin.js +4 -5
- package/dist/host/port-types.d.ts +42 -9
- package/dist/host/preflight.d.ts +3 -2
- package/dist/host/profile-create.d.ts +18 -0
- package/dist/host/profile-create.js +118 -0
- package/dist/host/profile-registries.d.ts +10 -0
- package/dist/host/profile-registries.js +150 -0
- package/dist/host/registry-declaration.d.ts +21 -0
- package/dist/host/registry-declaration.js +33 -0
- package/dist/host/release-probe.d.ts +34 -0
- package/dist/host/release-probe.js +88 -0
- package/dist/host/runner.d.ts +26 -0
- package/dist/host/runner.js +50 -0
- package/dist/host/shell.d.ts +0 -2
- package/dist/host/shell.js +0 -7
- package/dist/host/spec.js +1 -1
- package/dist/host/supply-index.d.ts +1 -0
- package/dist/host/supply-index.js +8 -7
- package/dist/host/supply-port.js +13 -3
- package/dist/host/unit-face.d.ts +2 -1
- package/dist/host/version.js +11 -6
- package/dist/host.d.ts +24 -6
- package/dist/host.js +23 -6
- package/dist/i18n/en.js +19 -20
- package/dist/i18n/index.d.ts +20 -21
- package/dist/i18n/runtime-terms.d.ts +15 -0
- package/dist/i18n/runtime-terms.js +22 -0
- package/dist/i18n/zh.d.ts +19 -20
- package/dist/i18n/zh.js +19 -20
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/testing/fake-adapter-set.d.ts +5 -0
- package/dist/testing/fake-adapter-set.js +150 -0
- package/dist/testing/needs.js +1 -1
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +1 -0
- package/package.json +1 -1
- package/src/api/__tests__/blocker-table.test.ts +25 -0
- package/src/api/{workbench-client.test.ts → __tests__/workbench-client.test.ts} +2 -2
- package/src/api/blocker-table.ts +6 -2
- package/src/components/SelectField.css +0 -6
- package/src/components/SelectField.tsx +7 -36
- package/src/components/SelectFieldNodes.tsx +4 -1
- package/src/components/{Card.test.tsx → __tests__/Card.test.tsx} +2 -2
- package/src/components/{CardAnchor.test.tsx → __tests__/CardAnchor.test.tsx} +3 -3
- package/src/components/{CardHead.test.tsx → __tests__/CardHead.test.tsx} +1 -1
- package/src/components/{ChoiceGroup.test.tsx → __tests__/ChoiceGroup.test.tsx} +2 -2
- package/src/components/{ConfirmBar.test.tsx → __tests__/ConfirmBar.test.tsx} +5 -5
- package/src/components/{Field.test.tsx → __tests__/Field.test.tsx} +1 -1
- package/src/components/{Form.test.tsx → __tests__/Form.test.tsx} +5 -5
- package/src/components/{FormActions.test.tsx → __tests__/FormActions.test.tsx} +3 -3
- package/src/components/{FormRows.test.tsx → __tests__/FormRows.test.tsx} +2 -2
- package/src/components/{FormTypes.test.ts → __tests__/FormTypes.test.ts} +1 -1
- package/src/components/{FormWiring.test.tsx → __tests__/FormWiring.test.tsx} +4 -4
- package/src/components/{Link.test.tsx → __tests__/Link.test.tsx} +1 -1
- package/src/components/{Rows.test.tsx → __tests__/Rows.test.tsx} +2 -2
- package/src/components/{SelectField.test.tsx → __tests__/SelectField.test.tsx} +7 -37
- package/src/components/{Spinner.test.tsx → __tests__/Spinner.test.tsx} +4 -4
- package/src/config/{constants.test.ts → __tests__/constants.test.ts} +1 -1
- package/src/config/{storage.test.ts → __tests__/storage.test.ts} +1 -1
- package/src/config/constants.ts +4 -4
- package/src/contract/capabilities.ts +30 -0
- package/src/contract/needs.ts +4 -4
- package/src/contract/plugin-source.ts +24 -0
- package/src/contract/protocol/bundle-install.ts +7 -1
- package/src/contract/protocol/market-index.ts +1 -0
- package/src/contract/protocol/platform.ts +1 -1
- package/src/contract/runner.ts +6 -0
- package/src/contract/unit.ts +4 -4
- package/src/host/{extension.test.ts → __tests__/extension.test.ts} +4 -4
- package/src/host/{install-failure.test.ts → __tests__/install-failure.test.ts} +1 -1
- package/src/host/{installed-sources.test.ts → __tests__/installed-sources.test.ts} +1 -1
- package/src/host/__tests__/lib-runners.test.ts +11 -2
- package/src/host/__tests__/market-index-assemble.test.ts +26 -0
- package/src/host/__tests__/market-index.test.ts +41 -51
- package/src/host/__tests__/plugin-ledger.test.ts +19 -6
- package/src/host/{recipe.test.ts → __tests__/recipe.test.ts} +3 -3
- package/src/host/__tests__/runtime-layout.test.ts +14 -0
- package/src/host/__tests__/supply-index.test.ts +2 -2
- package/src/host/__tests__/supply-port.test.ts +32 -0
- package/src/host/adapter-contract.ts +93 -0
- package/src/host/adapter-set.ts +6 -4
- package/src/host/adapter-types.ts +1 -1
- package/src/host/bundle-manager.ts +195 -0
- package/src/host/cli.ts +3 -1
- package/src/host/config-sync.ts +386 -0
- package/src/host/dsh-plugins.ts +60 -0
- package/src/host/extension.ts +1 -1
- package/src/host/flow/__tests__/advanced.test.ts +302 -0
- package/src/host/flow/__tests__/check.test.ts +169 -0
- package/src/host/flow/__tests__/run.test.ts +301 -0
- package/src/host/flow/check.ts +216 -0
- package/src/host/flow/expr.ts +94 -0
- package/src/host/flow/kit.ts +30 -0
- package/src/host/flow/run.ts +325 -0
- package/src/host/flow/types.ts +199 -0
- package/src/host/host-actions.ts +7 -0
- package/src/host/host-kit.ts +26 -0
- package/src/host/http.ts +6 -8
- package/src/host/installed-module.ts +272 -0
- package/src/host/lib-install.ts +14 -12
- package/src/host/lib-runners.ts +6 -8
- package/src/host/local-declaration.ts +37 -0
- package/src/host/market-index-claim.ts +0 -4
- package/src/host/market-index-doc.ts +1 -53
- package/src/host/market-index-parse.ts +21 -85
- package/src/host/market-index.ts +33 -127
- package/src/host/module-root.ts +6 -0
- package/src/host/npm-registry.ts +21 -0
- package/src/host/package-content.ts +76 -0
- package/src/host/package-kind.ts +63 -0
- package/src/host/package-route.ts +93 -0
- package/src/host/paths.ts +27 -52
- package/src/host/platform.ts +1 -1
- package/src/host/plugin-ledger.ts +25 -18
- package/src/host/plugin.ts +4 -5
- package/src/host/port-types.ts +43 -9
- package/src/host/preflight.ts +3 -2
- package/src/host/profile-create.ts +133 -0
- package/src/host/profile-registries.ts +145 -0
- package/src/host/registry-declaration.ts +70 -0
- package/src/host/release-probe.ts +202 -0
- package/src/host/shell.ts +0 -9
- package/src/host/spec.ts +1 -1
- package/src/host/supply-index.ts +9 -7
- package/src/host/supply-port.ts +13 -3
- package/src/host/unit-face.ts +2 -1
- package/src/host/version.ts +12 -7
- package/src/host.ts +34 -6
- package/src/i18n/{index.test.ts → __tests__/index.test.ts} +3 -3
- package/src/i18n/{merge.test.ts → __tests__/merge.test.ts} +1 -1
- package/src/i18n/{zh.test.ts → __tests__/zh.test.ts} +2 -2
- package/src/i18n/en.ts +19 -21
- package/src/i18n/runtime-terms.ts +26 -0
- package/src/i18n/zh.ts +19 -21
- package/src/index.ts +4 -0
- package/src/lib/{board-fact.test.ts → __tests__/board-fact.test.ts} +1 -1
- package/src/lib/{busy.test.ts → __tests__/busy.test.ts} +1 -1
- package/src/lib/{form-problem.test.ts → __tests__/form-problem.test.ts} +1 -1
- package/src/lib/{git-address.test.ts → __tests__/git-address.test.ts} +1 -1
- package/src/lib/{react.test.ts → __tests__/react.test.ts} +1 -1
- package/src/lib/{text.test.ts → __tests__/text.test.ts} +1 -1
- package/src/lib/{ui.test.ts → __tests__/ui.test.ts} +1 -1
- package/src/testing/__tests__/fake-adapter-set.test.ts +28 -0
- package/src/testing/fake-adapter-set.ts +173 -0
- package/src/testing/needs.ts +1 -1
- package/src/testing.ts +1 -0
- package/src/host/__tests__/command-name.test.ts +0 -16
- package/src/host/__tests__/opencli-runner.test.ts +0 -120
- package/src/host/channel-plugin.ts +0 -55
- package/src/host/command-name.ts +0 -15
- package/src/host/opencli-runner.ts +0 -76
- package/src/host/paths.test.ts +0 -26
- package/src/host/plugin-source.ts +0 -137
- package/src/host/runtime-links.test.ts +0 -31
- package/src/host/runtime-links.ts +0 -23
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import type { DataReader } from '
|
|
3
|
-
import type { ExtensionNeed } from '
|
|
4
|
-
import { extensionCallerOf } from '
|
|
2
|
+
import type { DataReader } from '../../contract/data.js';
|
|
3
|
+
import type { ExtensionNeed } from '../../contract/needs.js';
|
|
4
|
+
import { extensionCallerOf } from '../extension.js';
|
|
5
5
|
|
|
6
6
|
const NEED: ExtensionNeed = {
|
|
7
7
|
kind: 'extension',
|
|
@@ -32,7 +32,7 @@ describe('extensionCallerOf', () => {
|
|
|
32
32
|
await expect(caller.json(['issue-statuses'])).rejects.toMatchObject({
|
|
33
33
|
code: 'cli-unclassified',
|
|
34
34
|
message: expect.stringContaining('workbench_modules_update(id=coding-issues)'),
|
|
35
|
-
hint: expect.stringContaining('
|
|
35
|
+
hint: expect.stringContaining('coding issue-statuses --help'),
|
|
36
36
|
detail: "error: unknown command 'issue-statuses'",
|
|
37
37
|
});
|
|
38
38
|
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import type { LibNeed } from '../../contract/needs.js';
|
|
3
3
|
import type { LibReader } from '../../contract/lib.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { LibRunner } from '../../contract/runner.js';
|
|
5
5
|
import { withLibRunners } from '../lib-runners.js';
|
|
6
6
|
|
|
7
|
-
const runner:
|
|
7
|
+
const runner: LibRunner = async () => ({ stdout: '[]' });
|
|
8
8
|
|
|
9
9
|
function needOf(runs?: readonly string[]): LibNeed {
|
|
10
10
|
return {
|
|
@@ -28,6 +28,15 @@ function logger(): { readonly warn: ReturnType<typeof vi.fn<(message: string) =>
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
describe('withLibRunners', () => {
|
|
31
|
+
it('★ 换一个运行时名字(mcp)照样注入:外壳不认具体名字,只看清单的 runs', async () => {
|
|
32
|
+
const module = { setMcpRunner: vi.fn() };
|
|
33
|
+
const libs = withLibRunners(libsOf(module), { mcp: runner }, logger());
|
|
34
|
+
|
|
35
|
+
await libs.load(needOf(['mcp']), 'src/run.js');
|
|
36
|
+
|
|
37
|
+
expect(module.setMcpRunner).toHaveBeenCalledWith(runner);
|
|
38
|
+
});
|
|
39
|
+
|
|
31
40
|
it('没声明 runs → 原样返回,不注入、不记 warn', async () => {
|
|
32
41
|
const module = { setOpencliRunner: vi.fn() };
|
|
33
42
|
const warn = logger();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { MarketIndex } from '../../contract/protocol/market-index.js';
|
|
3
|
+
import { assembleMarketIndex, marketEntryFilesOf } from '../market-index.js';
|
|
4
|
+
|
|
5
|
+
describe('市场索引的迁移', () => {
|
|
6
|
+
it('迁移时把老产物路径里的 unit 补进元数据', () => {
|
|
7
|
+
const legacy: MarketIndex = {
|
|
8
|
+
format: 1,
|
|
9
|
+
plugins: [
|
|
10
|
+
{
|
|
11
|
+
name: '@scope/module-x',
|
|
12
|
+
versions: [
|
|
13
|
+
{
|
|
14
|
+
version: '0.1.0',
|
|
15
|
+
source: { type: 'git', url: 'https://git.example.com/m.git', commit: 'a'.repeat(40), path: 'packages/x' },
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
const files = marketEntryFilesOf(legacy, 'market');
|
|
22
|
+
const entry = JSON.parse(files[0]?.text ?? '{}');
|
|
23
|
+
expect(entry.metadata).toEqual({ unit: 'x' });
|
|
24
|
+
expect(assembleMarketIndex(files).ok).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -8,9 +8,8 @@ import {
|
|
|
8
8
|
marketEntryFilesOf,
|
|
9
9
|
marketEntryPath,
|
|
10
10
|
marketInstallTarget,
|
|
11
|
-
marketMetaPath,
|
|
12
11
|
marketSourceKey,
|
|
13
|
-
|
|
12
|
+
parseMarketEntryJson,
|
|
14
13
|
parseMarketIndex,
|
|
15
14
|
parseMarketIndexJson,
|
|
16
15
|
parseSourceConfig,
|
|
@@ -353,75 +352,69 @@ describe('索引源与安装目标', () => {
|
|
|
353
352
|
});
|
|
354
353
|
});
|
|
355
354
|
|
|
356
|
-
describe('
|
|
357
|
-
const
|
|
355
|
+
describe('条目文件:一个包一个文件', () => {
|
|
356
|
+
const entryOf = () => ({
|
|
358
357
|
name: '@scope/module-x',
|
|
359
|
-
|
|
360
|
-
source: { type: 'npm' },
|
|
361
|
-
publishTimestamp: timestamp,
|
|
362
|
-
title: { zh: title },
|
|
358
|
+
title: { zh: '探针' },
|
|
359
|
+
versions: [{ version: '0.1.0', source: { type: 'npm' as const }, publishTimestamp: 1_760_000_000 }],
|
|
363
360
|
});
|
|
364
361
|
|
|
365
|
-
it('
|
|
366
|
-
expect(marketEntryPath('market', '@scope/module-x'
|
|
367
|
-
expect(
|
|
368
|
-
'market/@scope/module-x/meta-3-a1b2c3.json',
|
|
369
|
-
);
|
|
370
|
-
expect(marketEntryPath('market', 'plain', '1.0.0')).toBe('market/plain/1.0.0.json');
|
|
362
|
+
it('路径是 market/<包名>.json', () => {
|
|
363
|
+
expect(marketEntryPath('market', '@scope/module-x')).toBe('market/@scope/module-x.json');
|
|
364
|
+
expect(marketEntryPath('market', 'plain')).toBe('market/plain.json');
|
|
371
365
|
});
|
|
372
366
|
|
|
373
|
-
it('
|
|
374
|
-
expect(() => marketEntryPath('market', '../escape'
|
|
375
|
-
expect(() => marketEntryPath('market', '
|
|
367
|
+
it('包名不合法就不给路径(防止写出目录外)', () => {
|
|
368
|
+
expect(() => marketEntryPath('market', '../escape')).toThrow();
|
|
369
|
+
expect(() => marketEntryPath('market', 'Bad Name')).toThrow();
|
|
376
370
|
});
|
|
377
371
|
|
|
378
372
|
it('单条文档能解析,坏文档整条拒', () => {
|
|
379
|
-
const good =
|
|
373
|
+
const good = parseMarketEntryJson(JSON.stringify(entryOf()));
|
|
380
374
|
expect(good.ok).toBe(true);
|
|
381
375
|
if (!good.ok) return;
|
|
382
|
-
expect(good.
|
|
383
|
-
|
|
384
|
-
expect(
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
376
|
+
expect(good.entry.name).toBe('@scope/module-x');
|
|
377
|
+
expect(parseMarketEntryJson(JSON.stringify({ name: 'Bad Name', versions: [] })).ok).toBe(false);
|
|
378
|
+
expect(parseMarketEntryJson(JSON.stringify({ name: '@scope/x' })).ok).toBe(false);
|
|
379
|
+
expect(parseMarketEntryJson('{oops').ok).toBe(false);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('组装:版本按 SemVer 降序,一个包一条', () => {
|
|
383
|
+
const assembled = assembleMarketIndex([
|
|
384
|
+
{
|
|
385
|
+
path: 'market/@scope/module-x.json',
|
|
386
|
+
text: JSON.stringify({
|
|
387
|
+
...entryOf(),
|
|
388
|
+
versions: [
|
|
389
|
+
{ version: '0.2.0', source: { type: 'npm' }, publishTimestamp: 1_760_000_100 },
|
|
390
|
+
{ version: '0.1.0', source: { type: 'npm' }, publishTimestamp: 1_760_000_000 },
|
|
391
|
+
],
|
|
392
|
+
}),
|
|
393
|
+
},
|
|
394
|
+
]);
|
|
400
395
|
expect(assembled.ok).toBe(true);
|
|
401
396
|
if (!assembled.ok) return;
|
|
402
|
-
|
|
403
|
-
expect(entry?.versions.map((one) => one.version)).toEqual(['0.2.0', '0.1.0']);
|
|
404
|
-
expect(entry?.title?.zh).toBe('最新标题');
|
|
397
|
+
expect(assembled.index.plugins[0]?.versions.map((one) => one.version)).toEqual(['0.2.0', '0.1.0']);
|
|
405
398
|
});
|
|
406
399
|
|
|
407
|
-
it('
|
|
400
|
+
it('组装:同一个包出现两次就整份拒', () => {
|
|
408
401
|
const assembled = assembleMarketIndex([
|
|
409
|
-
{ path: 'a
|
|
410
|
-
{ path: 'b
|
|
402
|
+
{ path: 'a.json', text: JSON.stringify(entryOf()) },
|
|
403
|
+
{ path: 'b.json', text: JSON.stringify(entryOf()) },
|
|
411
404
|
]);
|
|
412
405
|
expect(assembled.ok).toBe(false);
|
|
413
406
|
if (assembled.ok) return;
|
|
414
|
-
expect(assembled.problems.some((one) => one.kind === 'duplicate-
|
|
407
|
+
expect(assembled.problems.some((one) => one.kind === 'duplicate-name')).toBe(true);
|
|
415
408
|
});
|
|
416
409
|
|
|
417
410
|
it('从目录读只收 .json,README 不当条目', () => {
|
|
418
411
|
const dir = mkdtempSync(join(tmpdir(), 'wb-entry-'));
|
|
419
412
|
try {
|
|
420
|
-
mkdirSync(
|
|
413
|
+
mkdirSync(dir, { recursive: true });
|
|
421
414
|
writeFileSync(join(dir, 'README.md'), 'index');
|
|
422
|
-
writeFileSync(join(dir, '@scope
|
|
415
|
+
writeFileSync(join(dir, '@scope-module-x.json'), JSON.stringify(entryOf()));
|
|
423
416
|
const files = collectMarketEntryFiles(dir);
|
|
424
|
-
expect(files.map((one) => one.path)).toEqual(['@scope
|
|
417
|
+
expect(files.map((one) => one.path)).toEqual(['@scope-module-x.json']);
|
|
425
418
|
const assembled = assembleMarketIndex(files);
|
|
426
419
|
expect(assembled.ok).toBe(true);
|
|
427
420
|
if (!assembled.ok) return;
|
|
@@ -431,7 +424,7 @@ describe('条目文件:一个版本一个文件', () => {
|
|
|
431
424
|
}
|
|
432
425
|
});
|
|
433
426
|
|
|
434
|
-
it('迁移:一份 format 1
|
|
427
|
+
it('迁移:一份 format 1 拆成每包一个文件,再组装回来一模一样', () => {
|
|
435
428
|
const legacy: MarketIndex = {
|
|
436
429
|
format: 1,
|
|
437
430
|
plugins: [
|
|
@@ -447,10 +440,7 @@ describe('条目文件:一个版本一个文件', () => {
|
|
|
447
440
|
],
|
|
448
441
|
};
|
|
449
442
|
const files = marketEntryFilesOf(legacy, 'market');
|
|
450
|
-
expect(files.map((one) => one.path)).toEqual([
|
|
451
|
-
'market/@scope/module-x/0.2.0.json',
|
|
452
|
-
'market/@scope/module-x/0.1.0.json',
|
|
453
|
-
]);
|
|
443
|
+
expect(files.map((one) => one.path)).toEqual(['market/@scope/module-x.json']);
|
|
454
444
|
const assembled = assembleMarketIndex(files);
|
|
455
445
|
expect(assembled.ok).toBe(true);
|
|
456
446
|
if (!assembled.ok) return;
|
|
@@ -13,7 +13,7 @@ const HOME = '/h/workbench/opencli/home';
|
|
|
13
13
|
|
|
14
14
|
function entry(over: Partial<PluginLedgerEntry> = {}): PluginLedgerEntry {
|
|
15
15
|
return {
|
|
16
|
-
|
|
16
|
+
home: HOME,
|
|
17
17
|
package: 'opencli-plugin-inventory',
|
|
18
18
|
installedAt: '2026-10-14T00:00:00.000Z',
|
|
19
19
|
...over,
|
|
@@ -44,11 +44,11 @@ describe('台账条目的「放手」那一笔', () => {
|
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
it('另一个 home 的那一笔不受影响(隔离 wrapper 会换 home)', () => {
|
|
47
|
-
const other = entry({
|
|
47
|
+
const other = entry({ home: '/h/global/opencli/home' });
|
|
48
48
|
const released = withReleasedEntry([entry(), other], HOME, 'opencli-plugin-inventory', '2026-10-15T00:00:00.000Z');
|
|
49
49
|
|
|
50
|
-
expect(released.find((item) => item.
|
|
51
|
-
expect(heldEntriesOfHome(released, other.
|
|
50
|
+
expect(released.find((item) => item.home !== HOME)?.releasedAt).toBeUndefined();
|
|
51
|
+
expect(heldEntriesOfHome(released, other.home)).toHaveLength(1);
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
it('★ 按插件名找"用户持着的"那一份:插件名与包名两边都认(§3.4 那条映射)', () => {
|
|
@@ -67,8 +67,8 @@ describe('台账条目的「放手」那一笔', () => {
|
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
it('键还是「home + 包名」(同一份文件里两个 home 不互相撒谎)', () => {
|
|
70
|
-
expect(ledgerKeyOf({
|
|
71
|
-
ledgerKeyOf({
|
|
70
|
+
expect(ledgerKeyOf({ home: '/a', package: 'x' })).not.toBe(
|
|
71
|
+
ledgerKeyOf({ home: '/b', package: 'x' }),
|
|
72
72
|
);
|
|
73
73
|
});
|
|
74
74
|
});
|
|
@@ -92,3 +92,16 @@ describe('物理去留:由谁主张说话(动作与结果分层那条)', (
|
|
|
92
92
|
expect(pluginRetentionOf({ held: false, claimedBy: [], trustworthy: true })).toBe('remove');
|
|
93
93
|
});
|
|
94
94
|
});
|
|
95
|
+
|
|
96
|
+
describe('落盘字段改名后的老台账', () => {
|
|
97
|
+
it('★ 老文件里的 opencliHome 还认(改名不动数据)', () => {
|
|
98
|
+
const entries = parsePluginLedger({
|
|
99
|
+
entries: [
|
|
100
|
+
{ opencliHome: '/dsh/home-a', package: 'demo', installedAt: '2026-10-14T00:00:00.000Z' },
|
|
101
|
+
{ home: '/dsh/home-b', package: 'other', installedAt: '2026-10-14T00:00:00.000Z' },
|
|
102
|
+
],
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(entries.map((entry) => entry.home)).toEqual(['/dsh/home-a', '/dsh/home-b']);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import type { CallOverrides, RequestRecipe } from '
|
|
3
|
-
import { describeRecipe, scriptOf, scriptOfMany } from '
|
|
4
|
-
import { callsProblemOf, MAX_RECIPE_CALLS, recipeProblemOf } from '
|
|
2
|
+
import type { CallOverrides, RequestRecipe } from '../../contract/data.js';
|
|
3
|
+
import { describeRecipe, scriptOf, scriptOfMany } from '../recipe.js';
|
|
4
|
+
import { callsProblemOf, MAX_RECIPE_CALLS, recipeProblemOf } from '../recipe-rules.js';
|
|
5
5
|
|
|
6
6
|
const BASE: RequestRecipe = {
|
|
7
7
|
origin: 'https://x.example.com',
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { runtimeLayoutOf, runtimeRootOf } from '../paths.js';
|
|
3
|
+
|
|
4
|
+
describe('运行时根的算法(这里不认识任何具体运行时)', () => {
|
|
5
|
+
it('就是 workbench 根加一个 id,别处自己拼下面的布局', () => {
|
|
6
|
+
const layout = runtimeLayoutOf('demo-runtime');
|
|
7
|
+
|
|
8
|
+
expect(layout.root).toBe(runtimeRootOf('demo-runtime'));
|
|
9
|
+
expect(layout.root.endsWith('/workbench/demo-runtime')).toBe(true);
|
|
10
|
+
expect(layout.vendorPluginsDir).toBe(`${layout.root}/plugins-npm/node_modules`);
|
|
11
|
+
expect(layout.libsPrefix).toBe(`${layout.root}/libs`);
|
|
12
|
+
expect(layout.npmCacheDir).toBe(`${layout.root}/npm-cache`);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -51,7 +51,7 @@ describe('供给索引:一个包只有一个来源', () => {
|
|
|
51
51
|
const index = buildSupplyIndex([
|
|
52
52
|
manifest('a', [extension('coding', [npm('opencli-plugin-coding')])]),
|
|
53
53
|
manifest('b', [extension('coding')]),
|
|
54
|
-
]);
|
|
54
|
+
], { packagePrefix: 'opencli-plugin-' });
|
|
55
55
|
|
|
56
56
|
expect(index.conflicts.map((item) => item.kind)).toEqual(['source-mismatch']);
|
|
57
57
|
expect(index.entryOf('extension', 'coding')?.conflicted).toBe(true);
|
|
@@ -152,7 +152,7 @@ describe('供给索引:一个包只有一个来源', () => {
|
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
it('没写 sources 的 extension 按约定落包名,进索引但不代装', () => {
|
|
155
|
-
const index = buildSupplyIndex([manifest('a', [extension('coding')])]);
|
|
155
|
+
const index = buildSupplyIndex([manifest('a', [extension('coding')])], { packagePrefix: 'opencli-plugin-' });
|
|
156
156
|
|
|
157
157
|
expect(index.entryOf('extension', 'coding')?.package).toBe('opencli-plugin-coding');
|
|
158
158
|
expect(index.sourcesOf('extension', 'coding')).toEqual([]);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { supplyPortOf } from '../supply-port.js';
|
|
3
|
+
import { WORKBENCH_ADAPTERS } from '../adapters.js';
|
|
4
|
+
import { fakeAdapterSet } from '../../testing/fake-adapter-set.js';
|
|
5
|
+
|
|
6
|
+
function ctxOf(set: ReturnType<typeof fakeAdapterSet>): never {
|
|
7
|
+
return { get: (name: string) => (name === WORKBENCH_ADAPTERS ? set : undefined) } as never;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('组合出给外壳的 supply 端口', () => {
|
|
11
|
+
it('给了 CLI 目录就透传(opencli 那类运行时)', () => {
|
|
12
|
+
const port = supplyPortOf(ctxOf(fakeAdapterSet()));
|
|
13
|
+
|
|
14
|
+
expect(port.cliCatalogPath?.()).toBe('/tmp/dsh-workbench-fake/catalog.json');
|
|
15
|
+
expect(port.vendorPluginsDir?.()).toContain('dsh-workbench-fake');
|
|
16
|
+
expect(port.runtimeInstallCommand?.()).toBeTruthy();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('★ 没给就整条不挂(纯 API 的运行时不用瞎编目录)', () => {
|
|
20
|
+
const set = fakeAdapterSet();
|
|
21
|
+
const modules = { ...set.modules } as Record<string, unknown>;
|
|
22
|
+
delete modules['cliCatalogPath'];
|
|
23
|
+
delete modules['vendorPluginsDir'];
|
|
24
|
+
delete modules['runtimeInstallCommand'];
|
|
25
|
+
const port = supplyPortOf(ctxOf({ ...set, modules } as never));
|
|
26
|
+
|
|
27
|
+
expect(port.cliCatalogPath).toBeUndefined();
|
|
28
|
+
expect(port.vendorPluginsDir).toBeUndefined();
|
|
29
|
+
expect(port.runtimeInstallCommand).toBeUndefined();
|
|
30
|
+
expect(port.isolatedRoot()).toBeTruthy();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { DshHostContext } from '../api/dsh.js';
|
|
2
|
+
import type { AdapterSet } from './adapters.js';
|
|
3
|
+
import { validateAdapterSet } from './adapter-set.js';
|
|
4
|
+
|
|
5
|
+
const CAPABILITY_KEYS = ['console', 'pluginMarket', 'browserBridge'] as const;
|
|
6
|
+
const DATA_METHODS = ['read', 'request', 'requestMany', 'submit'] as const;
|
|
7
|
+
|
|
8
|
+
function isNonEmptyString(value: unknown): boolean {
|
|
9
|
+
return typeof value === 'string' && value.trim() !== '';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const CLI_DIR_METHODS = [
|
|
13
|
+
['cliCatalogPath', 'cliCatalogPath'],
|
|
14
|
+
['vendorPluginsDir', 'vendorPluginsDir'],
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
export function adapterContractGaps(set: AdapterSet, ctx: DshHostContext): readonly string[] {
|
|
18
|
+
const gaps: string[] = [];
|
|
19
|
+
const validated = validateAdapterSet(set, '契约');
|
|
20
|
+
if (!validated.ok) {
|
|
21
|
+
gaps.push(`成员不全:${validated.detail}`);
|
|
22
|
+
return gaps;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!Number.isInteger(set.apiVersion) || set.apiVersion < 1) {
|
|
26
|
+
gaps.push(`apiVersion 不是正整数:${String(set.apiVersion)}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const capabilities = set.runtime.capabilities(ctx);
|
|
30
|
+
for (const key of CAPABILITY_KEYS) {
|
|
31
|
+
if (typeof capabilities[key] !== 'boolean') gaps.push(`capabilities.${key} 不是布尔值`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (typeof set.runtime.profile === 'function') {
|
|
35
|
+
const profile = set.runtime.profile(ctx);
|
|
36
|
+
if (!isNonEmptyString(profile?.label)) gaps.push('runtime.profile().label 不是非空字符串');
|
|
37
|
+
for (const key of ['plugin', 'pluginOf', 'pluginThing', 'pluginArea'] as const) {
|
|
38
|
+
const text = profile?.terms?.[key];
|
|
39
|
+
if (!isNonEmptyString(text?.zh) || !isNonEmptyString(text?.en)) {
|
|
40
|
+
gaps.push(`runtime.profile().terms.${key} 要给 zh / en 两份非空文案`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!isNonEmptyString(profile?.installHint?.zh) || !isNonEmptyString(profile?.installHint?.en)) {
|
|
44
|
+
gaps.push('runtime.profile().installHint 要给 zh / en 两份非空文案');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeof set.runtime.commandSurface === 'function') {
|
|
49
|
+
const surface = set.runtime.commandSurface(ctx);
|
|
50
|
+
if (surface !== undefined) {
|
|
51
|
+
if (!isNonEmptyString(surface.name)) gaps.push('runtime.commandSurface().name 不是非空字符串');
|
|
52
|
+
if (!isNonEmptyString(surface.pluginPrefix)) {
|
|
53
|
+
gaps.push('runtime.commandSurface().pluginPrefix 不是非空字符串');
|
|
54
|
+
}
|
|
55
|
+
if (typeof surface.invoke !== 'function') gaps.push('runtime.commandSurface().invoke 不是函数');
|
|
56
|
+
else if (!isNonEmptyString(surface.invoke('site', 'command'))) {
|
|
57
|
+
gaps.push('runtime.commandSurface().invoke(site, command) 拼不出非空命令行');
|
|
58
|
+
}
|
|
59
|
+
if (typeof surface.help !== 'function') gaps.push('runtime.commandSurface().help 不是函数');
|
|
60
|
+
else if (!isNonEmptyString(surface.help('site'))) {
|
|
61
|
+
gaps.push('runtime.commandSurface().help(site) 拼不出非空提示');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const health = set.runtime.health(ctx);
|
|
67
|
+
const commands = set.runtime.commands(ctx);
|
|
68
|
+
for (const [name, status] of Object.entries(health)) {
|
|
69
|
+
if (!isNonEmptyString(status?.command)) gaps.push(`health['${name}'].command 不是非空字符串`);
|
|
70
|
+
if (!['env', 'isolated', 'path'].includes(status?.source)) gaps.push(`health['${name}'].source 不是已知来源`);
|
|
71
|
+
}
|
|
72
|
+
for (const name of Object.keys(commands)) {
|
|
73
|
+
if (health[name] === undefined) gaps.push(`commands 里的 ${name} 在 health 里没有对应条目`);
|
|
74
|
+
else if (!isNonEmptyString(commands[name])) gaps.push(`commands['${name}'] 不是非空字符串`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const method of DATA_METHODS) {
|
|
78
|
+
if (typeof set.data[method] !== 'function') gaps.push(`data.${method} 不是函数`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!isNonEmptyString(set.modules.isolatedRoot())) gaps.push('modules.isolatedRoot() 不是非空字符串');
|
|
82
|
+
if (set.modules.runtimeInstallCommand !== undefined && !isNonEmptyString(set.modules.runtimeInstallCommand())) {
|
|
83
|
+
gaps.push('modules.runtimeInstallCommand() 给了但不是非空字符串');
|
|
84
|
+
}
|
|
85
|
+
for (const [name, method] of CLI_DIR_METHODS) {
|
|
86
|
+
const given = set.modules[method];
|
|
87
|
+
if (given !== undefined && !isNonEmptyString(given())) gaps.push(`modules.${method}() 给了但不是非空字符串(${name})`);
|
|
88
|
+
}
|
|
89
|
+
if (typeof set.probe.probeUnits !== 'function') gaps.push('probe.probeUnits 不是函数');
|
|
90
|
+
if (typeof set.probe.registerPreflight !== 'function') gaps.push('probe.registerPreflight 不是函数');
|
|
91
|
+
|
|
92
|
+
return gaps;
|
|
93
|
+
}
|
package/src/host/adapter-set.ts
CHANGED
|
@@ -61,10 +61,9 @@ const CORE_METHODS = [
|
|
|
61
61
|
const FULL_MEMBERS = ['modules', 'runtime', 'plugins', 'actions'] as const;
|
|
62
62
|
|
|
63
63
|
const FULL_METHODS = [
|
|
64
|
+
['modules', 'isolatedRoot'],
|
|
64
65
|
['modules', 'isolatedHome'],
|
|
65
|
-
['modules', '
|
|
66
|
-
['modules', 'vendorPluginsDir'],
|
|
67
|
-
['modules', 'opencliInstallCommand'],
|
|
66
|
+
['modules', 'afterLibInstall'],
|
|
68
67
|
['modules', 'profileDirOf'],
|
|
69
68
|
['modules', 'profileModules'],
|
|
70
69
|
['modules', 'readInstalledModule'],
|
|
@@ -81,7 +80,10 @@ const FULL_METHODS = [
|
|
|
81
80
|
['modules', 'isNewerVersion'],
|
|
82
81
|
['modules', 'startSelfUpdate'],
|
|
83
82
|
['runtime', 'packageRoot'],
|
|
84
|
-
['runtime', '
|
|
83
|
+
['runtime', 'capabilities'],
|
|
84
|
+
['runtime', 'commands'],
|
|
85
|
+
['runtime', 'runners'],
|
|
86
|
+
['runtime', 'health'],
|
|
85
87
|
['runtime', 'seed'],
|
|
86
88
|
['runtime', 'warnUnwritableRoot'],
|
|
87
89
|
['plugins', 'declarationLookup'],
|
|
@@ -77,7 +77,7 @@ export interface SelfUpdateStart {
|
|
|
77
77
|
readonly forward: string;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
export interface
|
|
80
|
+
export interface RuntimeStatus {
|
|
81
81
|
readonly command: string;
|
|
82
82
|
readonly source: 'env' | 'isolated' | 'path';
|
|
83
83
|
readonly isolated: string | null;
|