@dsh-workbench/shared 1.1.8 → 1.1.10
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 -1
- 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/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 +23 -24
- package/dist/i18n/index.d.ts +23 -24
- package/dist/i18n/runtime-terms.d.ts +15 -0
- package/dist/i18n/runtime-terms.js +22 -0
- package/dist/i18n/zh.d.ts +22 -23
- package/dist/i18n/zh.js +22 -23
- 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__/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/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 +23 -25
- package/src/i18n/runtime-terms.ts +26 -0
- package/src/i18n/zh.ts +22 -24
- 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
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { RESTART_REASONS } from '../contract/protocol/bundle-install.js';
|
|
2
|
+
const MANAGER_SERVICE = 'pluginManager';
|
|
3
|
+
const APPLICATIONS = ['applied', 'restart-required', 'overridden', 'failed', 'cancelled'];
|
|
4
|
+
const STAGES = ['install', 'enable', 'remove'];
|
|
5
|
+
const PROBLEMS = [
|
|
6
|
+
'invalid-spec',
|
|
7
|
+
'already-installed',
|
|
8
|
+
'not-found',
|
|
9
|
+
'not-a-package',
|
|
10
|
+
'not-a-bundle',
|
|
11
|
+
'not-a-workbench-module',
|
|
12
|
+
'network',
|
|
13
|
+
'unknown',
|
|
14
|
+
];
|
|
15
|
+
const SPEC_KINDS = ['registry', 'path', 'git', 'tarball'];
|
|
16
|
+
const FAILURE_KINDS = [
|
|
17
|
+
'pnpm-missing',
|
|
18
|
+
'timeout',
|
|
19
|
+
'not-found',
|
|
20
|
+
'no-matching-version',
|
|
21
|
+
'network',
|
|
22
|
+
'disk-full',
|
|
23
|
+
'permission',
|
|
24
|
+
'build-blocked',
|
|
25
|
+
'integrity',
|
|
26
|
+
'unknown',
|
|
27
|
+
];
|
|
28
|
+
const CANCELLATIONS = ['cancelled', 'too-late', 'not-running'];
|
|
29
|
+
function asRecord(value) {
|
|
30
|
+
return typeof value === 'object' && value !== null ? value : undefined;
|
|
31
|
+
}
|
|
32
|
+
function asText(value) {
|
|
33
|
+
return typeof value === 'string' ? value : '';
|
|
34
|
+
}
|
|
35
|
+
function pick(allowed, value) {
|
|
36
|
+
return typeof value === 'string' && allowed.includes(value) ? value : undefined;
|
|
37
|
+
}
|
|
38
|
+
function asRegistry(value) {
|
|
39
|
+
if (value === null)
|
|
40
|
+
return null;
|
|
41
|
+
return typeof value === 'string' ? value : undefined;
|
|
42
|
+
}
|
|
43
|
+
function asTextArray(value) {
|
|
44
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
|
|
45
|
+
}
|
|
46
|
+
function asRegistries(raw) {
|
|
47
|
+
const record = asRecord(raw);
|
|
48
|
+
if (record === undefined)
|
|
49
|
+
return null;
|
|
50
|
+
const registry = asRegistry(record.registry);
|
|
51
|
+
if (registry === undefined)
|
|
52
|
+
return null;
|
|
53
|
+
return {
|
|
54
|
+
registry,
|
|
55
|
+
fallbackRegistries: asTextArray(record.fallbackRegistries),
|
|
56
|
+
resolved: typeof record.resolved === 'string' ? record.resolved : null,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function asPackageResult(raw) {
|
|
60
|
+
const record = asRecord(raw);
|
|
61
|
+
if (record === undefined)
|
|
62
|
+
return undefined;
|
|
63
|
+
const kind = pick(FAILURE_KINDS, record.kind);
|
|
64
|
+
return {
|
|
65
|
+
exitCode: typeof record.exitCode === 'number' ? record.exitCode : -1,
|
|
66
|
+
output: asText(record.output),
|
|
67
|
+
truncated: record.truncated === true,
|
|
68
|
+
logPath: asText(record.logPath),
|
|
69
|
+
...(kind === undefined ? {} : { kind }),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function asChange(raw) {
|
|
73
|
+
const record = asRecord(raw);
|
|
74
|
+
if (record === undefined)
|
|
75
|
+
return null;
|
|
76
|
+
const application = pick(APPLICATIONS, record.application);
|
|
77
|
+
const stage = pick(STAGES, record.stage);
|
|
78
|
+
if (application === undefined || stage === undefined)
|
|
79
|
+
return null;
|
|
80
|
+
const error = asRecord(record.error);
|
|
81
|
+
const packageResult = asPackageResult(record.packageResult);
|
|
82
|
+
const pendingBuilds = asTextArray(record.pendingBuilds);
|
|
83
|
+
const restartReason = pick(RESTART_REASONS, record.restartReason);
|
|
84
|
+
const failedAt = pick(['registry', 'spec-host'], record.failedAt);
|
|
85
|
+
const bundle = asText(record.bundle);
|
|
86
|
+
const version = asText(record.version);
|
|
87
|
+
const errorCode = error === undefined ? '' : asText(error.code);
|
|
88
|
+
const errorDetail = error === undefined ? '' : asText(error.diagnostic);
|
|
89
|
+
return {
|
|
90
|
+
application,
|
|
91
|
+
stage,
|
|
92
|
+
changed: record.changed === true,
|
|
93
|
+
target: asText(record.target),
|
|
94
|
+
...(bundle === '' ? {} : { bundle }),
|
|
95
|
+
...(version === '' ? {} : { version }),
|
|
96
|
+
...(errorCode === '' ? {} : { errorCode }),
|
|
97
|
+
...(errorDetail === '' ? {} : { errorDetail }),
|
|
98
|
+
...(packageResult === undefined ? {} : { packageResult }),
|
|
99
|
+
...(pendingBuilds.length === 0 ? {} : { pendingBuilds }),
|
|
100
|
+
...(restartReason === undefined ? {} : { restartReason }),
|
|
101
|
+
...(failedAt === undefined ? {} : { failedAt }),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function asInspection(raw) {
|
|
105
|
+
const record = asRecord(raw);
|
|
106
|
+
if (record === undefined)
|
|
107
|
+
return null;
|
|
108
|
+
const status = asText(record.status);
|
|
109
|
+
if (status === 'refused') {
|
|
110
|
+
const problem = pick(PROBLEMS, record.problem) ?? 'unknown';
|
|
111
|
+
const registries = Array.isArray(record.registries)
|
|
112
|
+
? record.registries.map(asRegistry).filter((item) => item !== undefined)
|
|
113
|
+
: [];
|
|
114
|
+
return {
|
|
115
|
+
status: 'refused',
|
|
116
|
+
problem,
|
|
117
|
+
reason: asText(record.reason),
|
|
118
|
+
...(registries.length === 0 ? {} : { registries }),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (status !== 'accepted')
|
|
122
|
+
return null;
|
|
123
|
+
const kind = pick(SPEC_KINDS, record.kind);
|
|
124
|
+
const registry = asRegistry(record.registry);
|
|
125
|
+
if (kind === undefined || registry === undefined)
|
|
126
|
+
return null;
|
|
127
|
+
const name = asText(record.name);
|
|
128
|
+
const version = asText(record.version);
|
|
129
|
+
const description = asText(record.description);
|
|
130
|
+
const host = asText(record.host);
|
|
131
|
+
return {
|
|
132
|
+
status: 'accepted',
|
|
133
|
+
kind,
|
|
134
|
+
bundle: typeof record.bundle === 'boolean' ? record.bundle : null,
|
|
135
|
+
registry,
|
|
136
|
+
...(name === '' ? {} : { name }),
|
|
137
|
+
...(version === '' ? {} : { version }),
|
|
138
|
+
...(description === '' ? {} : { description }),
|
|
139
|
+
...(host === '' ? {} : { host }),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
export function readBundleManager(ctx) {
|
|
143
|
+
const face = ctx.get(MANAGER_SERVICE);
|
|
144
|
+
if (face === undefined)
|
|
145
|
+
return undefined;
|
|
146
|
+
if (typeof face.installBundle !== 'function' || typeof face.removeBundle !== 'function')
|
|
147
|
+
return undefined;
|
|
148
|
+
return {
|
|
149
|
+
async registries() {
|
|
150
|
+
if (typeof face.registries !== 'function')
|
|
151
|
+
return null;
|
|
152
|
+
return asRegistries(await face.registries());
|
|
153
|
+
},
|
|
154
|
+
async inspect(spec, registry) {
|
|
155
|
+
if (typeof face.inspect !== 'function')
|
|
156
|
+
return null;
|
|
157
|
+
return asInspection(await face.inspect(spec, registry === undefined ? undefined : { registry }));
|
|
158
|
+
},
|
|
159
|
+
async install(request) {
|
|
160
|
+
const options = {
|
|
161
|
+
...(request.registry === undefined ? {} : { registry: request.registry }),
|
|
162
|
+
...(request.requestId === undefined ? {} : { requestId: request.requestId }),
|
|
163
|
+
...(request.approvedBuilds === undefined ? {} : { approvedBuilds: request.approvedBuilds }),
|
|
164
|
+
};
|
|
165
|
+
return asChange(await face.installBundle?.(request.spec, options));
|
|
166
|
+
},
|
|
167
|
+
async remove(name) {
|
|
168
|
+
return asChange(await face.removeBundle?.(name));
|
|
169
|
+
},
|
|
170
|
+
async setEnabled(name, enabled) {
|
|
171
|
+
if (typeof face.setBundleEnabled !== 'function')
|
|
172
|
+
return null;
|
|
173
|
+
return asChange(await face.setBundleEnabled(name, enabled));
|
|
174
|
+
},
|
|
175
|
+
async cancel(requestId) {
|
|
176
|
+
if (typeof face.cancelInstall !== 'function')
|
|
177
|
+
return null;
|
|
178
|
+
return pick(CANCELLATIONS, (asRecord(await face.cancelInstall(requestId)) ?? {}).status) ?? null;
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
package/dist/host/cli.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { adaptersOf } from './adapters.js';
|
|
2
2
|
export function cliPortOf(ctx) {
|
|
3
|
-
|
|
3
|
+
const commands = adaptersOf(ctx).runtime.commands(ctx);
|
|
4
|
+
const first = Object.values(commands)[0];
|
|
5
|
+
return { command: first ?? '' };
|
|
4
6
|
}
|
|
5
7
|
export function cliCommandOf(ctx) {
|
|
6
8
|
return cliPortOf(ctx).command;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { PostResource, RouteHandler } from './http.js';
|
|
2
|
+
import type { ConfigSyncRow, ConfigSyncSnapshot } from '../contract/protocol/config-sync.js';
|
|
3
|
+
export interface PatchBlock {
|
|
4
|
+
readonly key: string;
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly insert: boolean;
|
|
7
|
+
readonly body: string;
|
|
8
|
+
readonly names: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
export declare function patchBlocks(text: string): PatchBlock[];
|
|
11
|
+
export declare function normalizeBlock(body: string): string;
|
|
12
|
+
export declare function syncRows(source: readonly PatchBlock[], own: readonly PatchBlock[], missing: (block: PatchBlock) => readonly string[]): ConfigSyncRow[];
|
|
13
|
+
export declare function mergeBlocks(ownText: string, incoming: readonly PatchBlock[], overwrite: ReadonlySet<string>): {
|
|
14
|
+
ok: true;
|
|
15
|
+
text: string;
|
|
16
|
+
written: string[];
|
|
17
|
+
skipped: string[];
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
reason: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function readPatchBlocks(path: string): {
|
|
23
|
+
ok: true;
|
|
24
|
+
text: string;
|
|
25
|
+
blocks: PatchBlock[];
|
|
26
|
+
} | {
|
|
27
|
+
ok: false;
|
|
28
|
+
reason: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function writePatchFile(path: string, text: string): void;
|
|
31
|
+
export interface ConfigSyncDeps {
|
|
32
|
+
readonly home: string;
|
|
33
|
+
readonly self: string;
|
|
34
|
+
readonly profileDir: string | null;
|
|
35
|
+
readonly reconcile?: (() => Promise<boolean | void>) | undefined;
|
|
36
|
+
}
|
|
37
|
+
export declare function configSyncHandler(deps: ConfigSyncDeps): RouteHandler;
|
|
38
|
+
export declare function configSyncSnapshot(deps: ConfigSyncDeps, asked?: string): ConfigSyncSnapshot;
|
|
39
|
+
export declare function configSyncApply(deps: ConfigSyncDeps, payload: unknown): Promise<{
|
|
40
|
+
ok: true;
|
|
41
|
+
written: string[];
|
|
42
|
+
skipped: string[];
|
|
43
|
+
verified: boolean;
|
|
44
|
+
} | {
|
|
45
|
+
ok: false;
|
|
46
|
+
text: string;
|
|
47
|
+
}>;
|
|
48
|
+
export declare function configSyncAction(deps: ConfigSyncDeps): PostResource;
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { CONFIG_SYNC_RESOURCE } from '../contract/protocol/config-sync.js';
|
|
4
|
+
const ID_LINE = /^- id:[ \t]*"?([^"\s#]+)/;
|
|
5
|
+
const INSERT_LINE = /^- insert:[ \t]*$/;
|
|
6
|
+
const NESTED_ID = /^[ \t]+-[ \t]*id:[ \t]*"?([^"\s#]+)/;
|
|
7
|
+
const NAME_LINE = /^[ \t]*-?[ \t]*name:[ \t]*['"]?([^'"\s#]+)/;
|
|
8
|
+
const COMMENT_LINE = /^[ \t]*#/;
|
|
9
|
+
const ISOLATION_IDS = new Set([
|
|
10
|
+
'session-persistence-jsonl',
|
|
11
|
+
'storage-json',
|
|
12
|
+
'attachment-local',
|
|
13
|
+
'sandbox-policy',
|
|
14
|
+
]);
|
|
15
|
+
function trimmedTail(lines) {
|
|
16
|
+
let end = lines.length;
|
|
17
|
+
while (end > 0 && (lines[end - 1] ?? '').trim() === '')
|
|
18
|
+
end -= 1;
|
|
19
|
+
return lines.slice(0, end);
|
|
20
|
+
}
|
|
21
|
+
function matched(text, pattern) {
|
|
22
|
+
const found = pattern.exec(text);
|
|
23
|
+
return found === null ? undefined : found[1];
|
|
24
|
+
}
|
|
25
|
+
function blockOf(body, index) {
|
|
26
|
+
const lines = body.split('\n');
|
|
27
|
+
const first = lines[0] ?? '';
|
|
28
|
+
const id = matched(first, ID_LINE);
|
|
29
|
+
const nested = lines.flatMap((line) => {
|
|
30
|
+
const one = matched(line, NESTED_ID);
|
|
31
|
+
return one === undefined ? [] : [one];
|
|
32
|
+
});
|
|
33
|
+
const insert = id === undefined && INSERT_LINE.test(first);
|
|
34
|
+
const label = id ?? nested.join(', ');
|
|
35
|
+
const key = id !== undefined ? `id:${id}` : insert ? `insert:${nested.join(', ')}` : `block:${String(index)}`;
|
|
36
|
+
const names = lines.flatMap((line) => {
|
|
37
|
+
const one = matched(line, NAME_LINE);
|
|
38
|
+
return one === undefined ? [] : [one];
|
|
39
|
+
});
|
|
40
|
+
return { key, id: label === '' ? key : label, insert, body, names };
|
|
41
|
+
}
|
|
42
|
+
function spannedBlocks(text) {
|
|
43
|
+
const lines = text.split('\n');
|
|
44
|
+
const starts = [];
|
|
45
|
+
lines.forEach((line, index) => {
|
|
46
|
+
if (/^- /.test(line) || line === '-')
|
|
47
|
+
starts.push(index);
|
|
48
|
+
});
|
|
49
|
+
const blocks = [];
|
|
50
|
+
for (let i = 0; i < starts.length; i += 1) {
|
|
51
|
+
const from = starts[i] ?? 0;
|
|
52
|
+
const to = i + 1 < starts.length ? (starts[i + 1] ?? lines.length) : lines.length;
|
|
53
|
+
const body = trimmedTail(lines.slice(from, to)).join('\n');
|
|
54
|
+
if (body.trim() === '')
|
|
55
|
+
continue;
|
|
56
|
+
blocks.push({ ...blockOf(body, blocks.length), from, to });
|
|
57
|
+
}
|
|
58
|
+
return blocks;
|
|
59
|
+
}
|
|
60
|
+
export function patchBlocks(text) {
|
|
61
|
+
return spannedBlocks(text).map(({ from: _from, to: _to, ...block }) => block);
|
|
62
|
+
}
|
|
63
|
+
export function normalizeBlock(body) {
|
|
64
|
+
return body
|
|
65
|
+
.split('\n')
|
|
66
|
+
.map((line) => line.replace(/\s+$/, ''))
|
|
67
|
+
.filter((line) => line.trim() !== '' && !COMMENT_LINE.test(line))
|
|
68
|
+
.join('\n');
|
|
69
|
+
}
|
|
70
|
+
function rewriteForTarget(body, self) {
|
|
71
|
+
if (self === '' || self === 'workbench')
|
|
72
|
+
return body;
|
|
73
|
+
return body.replace(/(dshHomePath\(\s*['"])workbench(?=[/'"])/g, `$1${self}`);
|
|
74
|
+
}
|
|
75
|
+
function syncable(block) {
|
|
76
|
+
return !block.insert && !ISOLATION_IDS.has(block.id);
|
|
77
|
+
}
|
|
78
|
+
export function syncRows(source, own, missing) {
|
|
79
|
+
const mine = new Map(own.map((block) => [block.key, block]));
|
|
80
|
+
const rows = [];
|
|
81
|
+
for (const block of source) {
|
|
82
|
+
if (!syncable(block))
|
|
83
|
+
continue;
|
|
84
|
+
const here = mine.get(block.key);
|
|
85
|
+
if (here !== undefined && normalizeBlock(here.body) === normalizeBlock(block.body))
|
|
86
|
+
continue;
|
|
87
|
+
rows.push({
|
|
88
|
+
key: block.key,
|
|
89
|
+
id: block.id,
|
|
90
|
+
state: here === undefined ? 'new' : 'differs',
|
|
91
|
+
body: block.body,
|
|
92
|
+
ownBody: here === undefined ? null : here.body,
|
|
93
|
+
missing: missing(block),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return rows;
|
|
97
|
+
}
|
|
98
|
+
function replaceBlocks(text, blocks) {
|
|
99
|
+
const wanted = new Map(blocks.map((block) => [block.key, block]));
|
|
100
|
+
const spans = spannedBlocks(text).filter((span) => wanted.has(span.key));
|
|
101
|
+
const lines = text.split('\n');
|
|
102
|
+
for (const span of [...spans].reverse()) {
|
|
103
|
+
const replacement = (wanted.get(span.key)?.body ?? '').split('\n');
|
|
104
|
+
lines.splice(span.from, span.to - span.from, ...replacement);
|
|
105
|
+
}
|
|
106
|
+
return lines.join('\n');
|
|
107
|
+
}
|
|
108
|
+
function appendBlocks(text, blocks) {
|
|
109
|
+
const kept = text
|
|
110
|
+
.split('\n')
|
|
111
|
+
.filter((line) => line.trim() !== '[]')
|
|
112
|
+
.join('\n')
|
|
113
|
+
.replace(/\s+$/, '');
|
|
114
|
+
let out = kept === '' ? '' : `${kept}\n`;
|
|
115
|
+
for (const block of blocks)
|
|
116
|
+
out += `\n${block.body}\n`;
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
export function mergeBlocks(ownText, incoming, overwrite) {
|
|
120
|
+
const own = patchBlocks(ownText);
|
|
121
|
+
const mine = new Set(own.map((block) => block.key));
|
|
122
|
+
const written = [];
|
|
123
|
+
const skipped = [];
|
|
124
|
+
const take = [];
|
|
125
|
+
for (const block of incoming) {
|
|
126
|
+
if (!mine.has(block.key) || overwrite.has(block.key)) {
|
|
127
|
+
take.push(block);
|
|
128
|
+
written.push(block.id);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
skipped.push(block.id);
|
|
132
|
+
}
|
|
133
|
+
if (take.length === 0)
|
|
134
|
+
return { ok: true, text: ownText, written, skipped };
|
|
135
|
+
const replaced = replaceBlocks(ownText, take.filter((block) => mine.has(block.key)));
|
|
136
|
+
const text = appendBlocks(replaced, take.filter((block) => !mine.has(block.key)));
|
|
137
|
+
const check = patchBlocks(text);
|
|
138
|
+
const expected = new Set([...mine, ...take.map((block) => block.key)]);
|
|
139
|
+
if (check.length !== expected.size || new Set(check.map((block) => block.key)).size !== expected.size) {
|
|
140
|
+
return { ok: false, reason: '写入前的自检没过:行的 id 会重复或丢行' };
|
|
141
|
+
}
|
|
142
|
+
return { ok: true, text, written, skipped };
|
|
143
|
+
}
|
|
144
|
+
export function readPatchBlocks(path) {
|
|
145
|
+
try {
|
|
146
|
+
const text = readFileSync(path, 'utf8');
|
|
147
|
+
return { ok: true, text, blocks: patchBlocks(text) };
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
return { ok: false, reason: error instanceof Error ? error.message : String(error) };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function patchMode(path) {
|
|
154
|
+
try {
|
|
155
|
+
return statSync(path).mode & 0o777;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return 0o644;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export function writePatchFile(path, text) {
|
|
162
|
+
const mode = patchMode(path);
|
|
163
|
+
const tmp = `${path}.wb-sync-${String(process.pid)}`;
|
|
164
|
+
writeFileSync(tmp, text, { mode });
|
|
165
|
+
try {
|
|
166
|
+
renameSync(tmp, path);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
try {
|
|
170
|
+
unlinkSync(tmp);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export function configSyncHandler(deps) {
|
|
179
|
+
return (search) => ({ ok: true, ...configSyncSnapshot(deps, search.get('source') ?? undefined) });
|
|
180
|
+
}
|
|
181
|
+
function textOf(value) {
|
|
182
|
+
return typeof value === 'string' ? value : '';
|
|
183
|
+
}
|
|
184
|
+
function profileDependencies(dir) {
|
|
185
|
+
try {
|
|
186
|
+
const manifest = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
|
|
187
|
+
return new Set(Object.keys(manifest.dependencies ?? {}));
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return new Set();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function profileNames(profilesDir) {
|
|
194
|
+
try {
|
|
195
|
+
return readdirSync(profilesDir, { withFileTypes: true })
|
|
196
|
+
.filter((entry) => entry.isDirectory() || entry.isSymbolicLink())
|
|
197
|
+
.map((entry) => entry.name)
|
|
198
|
+
.filter((name) => existsSync(join(profilesDir, name, 'package.json')))
|
|
199
|
+
.sort((a, b) => a.localeCompare(b));
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
export function configSyncSnapshot(deps, asked) {
|
|
206
|
+
const profilesDir = join(deps.home, 'profiles');
|
|
207
|
+
const names = profileNames(profilesDir);
|
|
208
|
+
const others = names.filter((name) => name !== deps.self);
|
|
209
|
+
const base = {
|
|
210
|
+
self: deps.self,
|
|
211
|
+
home: deps.home,
|
|
212
|
+
};
|
|
213
|
+
if (deps.profileDir === null) {
|
|
214
|
+
return {
|
|
215
|
+
...base,
|
|
216
|
+
source: '',
|
|
217
|
+
profiles: [],
|
|
218
|
+
rows: [],
|
|
219
|
+
emptyProviders: false,
|
|
220
|
+
reason: '拿不到当前 profile 目录',
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const own = readPatchBlocks(join(deps.profileDir, 'cordis.patch.yml'));
|
|
224
|
+
const emptyProviders = own.ok && !own.blocks.some((block) => block.key.startsWith('id:llm-'));
|
|
225
|
+
const ownBlocks = own.ok ? own.blocks : [];
|
|
226
|
+
const profiles = others.map((name) => {
|
|
227
|
+
const read = readPatchBlocks(join(profilesDir, name, 'cordis.patch.yml'));
|
|
228
|
+
const blocks = read.ok
|
|
229
|
+
? read.blocks
|
|
230
|
+
.filter(syncable)
|
|
231
|
+
.map((block) => ({ ...block, body: rewriteForTarget(block.body, deps.self) }))
|
|
232
|
+
: [];
|
|
233
|
+
return {
|
|
234
|
+
name,
|
|
235
|
+
readable: read.ok,
|
|
236
|
+
candidates: read.ok ? syncRows(blocks, ownBlocks, () => []).length : 0,
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
if (others.length === 0) {
|
|
240
|
+
return {
|
|
241
|
+
...base,
|
|
242
|
+
source: '',
|
|
243
|
+
profiles,
|
|
244
|
+
rows: [],
|
|
245
|
+
emptyProviders,
|
|
246
|
+
reason: '这台机器上只有当前这个 profile,没有可同步的来源',
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const source = others.includes(asked ?? '') ? (asked ?? '') : others.includes('web') ? 'web' : (others[0] ?? '');
|
|
250
|
+
const sourceDir = join(profilesDir, source);
|
|
251
|
+
const read = readPatchBlocks(join(sourceDir, 'cordis.patch.yml'));
|
|
252
|
+
if (!read.ok) {
|
|
253
|
+
return { ...base, source, profiles, rows: [], emptyProviders, reason: read.reason };
|
|
254
|
+
}
|
|
255
|
+
if (!own.ok) {
|
|
256
|
+
return { ...base, source, profiles, rows: [], emptyProviders, reason: own.reason };
|
|
257
|
+
}
|
|
258
|
+
const declared = profileDependencies(sourceDir);
|
|
259
|
+
const modulesDir = join(deps.profileDir, 'node_modules');
|
|
260
|
+
const sourceBlocks = read.blocks
|
|
261
|
+
.filter(syncable)
|
|
262
|
+
.map((block) => ({ ...block, body: rewriteForTarget(block.body, deps.self) }));
|
|
263
|
+
const rows = syncRows(sourceBlocks, own.blocks, (block) => block.names.filter((name) => declared.has(name) && !existsSync(join(modulesDir, name))));
|
|
264
|
+
return { ...base, source, profiles, rows, emptyProviders };
|
|
265
|
+
}
|
|
266
|
+
export async function configSyncApply(deps, payload) {
|
|
267
|
+
if (deps.profileDir === null)
|
|
268
|
+
return { ok: false, text: '拿不到当前 profile 目录,这次没写任何东西' };
|
|
269
|
+
const body = typeof payload === 'object' && payload !== null ? payload : {};
|
|
270
|
+
const source = textOf(body['source']);
|
|
271
|
+
const keys = new Set((Array.isArray(body['keys']) ? body['keys'] : []).map((item) => textOf(item)).filter((item) => item !== ''));
|
|
272
|
+
const overwrite = new Set((Array.isArray(body['overwrite']) ? body['overwrite'] : []).map((item) => textOf(item)));
|
|
273
|
+
if (source === '' || keys.size === 0) {
|
|
274
|
+
return { ok: false, text: '没说清从哪个 profile 同步哪几行' };
|
|
275
|
+
}
|
|
276
|
+
const profilesDir = join(deps.home, 'profiles');
|
|
277
|
+
if (!profileNames(profilesDir).includes(source))
|
|
278
|
+
return { ok: false, text: `没有这个 profile:${source}` };
|
|
279
|
+
if (source === deps.self)
|
|
280
|
+
return { ok: false, text: '来源不能是工作台自己' };
|
|
281
|
+
const sourceDir = join(profilesDir, source);
|
|
282
|
+
const read = readPatchBlocks(join(sourceDir, 'cordis.patch.yml'));
|
|
283
|
+
if (!read.ok)
|
|
284
|
+
return { ok: false, text: `读不到 ${source} 的配置:${read.reason}` };
|
|
285
|
+
const own = readPatchBlocks(join(deps.profileDir, 'cordis.patch.yml'));
|
|
286
|
+
if (!own.ok)
|
|
287
|
+
return { ok: false, text: `读不到工作台自己的配置:${own.reason}` };
|
|
288
|
+
if (read.blocks.some((block) => block.insert && keys.has(block.key))) {
|
|
289
|
+
return { ok: false, text: '插件行不同步:先把插件装到工作台这边,再同步它的设置' };
|
|
290
|
+
}
|
|
291
|
+
const picked = read.blocks
|
|
292
|
+
.filter((block) => keys.has(block.key) && syncable(block))
|
|
293
|
+
.map((block) => ({ ...block, body: rewriteForTarget(block.body, deps.self) }));
|
|
294
|
+
if (picked.length === 0) {
|
|
295
|
+
return { ok: false, text: '选中的行在来源里已经找不到了,重新读一遍再试' };
|
|
296
|
+
}
|
|
297
|
+
const merged = mergeBlocks(own.text, picked, overwrite);
|
|
298
|
+
if (!merged.ok)
|
|
299
|
+
return { ok: false, text: merged.reason };
|
|
300
|
+
const patchPath = join(deps.profileDir, 'cordis.patch.yml');
|
|
301
|
+
const patchChanged = merged.text !== own.text;
|
|
302
|
+
try {
|
|
303
|
+
if (patchChanged)
|
|
304
|
+
writePatchFile(patchPath, merged.text);
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
return { ok: false, text: `写不进去:${error instanceof Error ? error.message : String(error)}` };
|
|
308
|
+
}
|
|
309
|
+
if (deps.reconcile === undefined) {
|
|
310
|
+
return { ok: true, written: merged.written, skipped: merged.skipped, verified: false };
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
const verified = await deps.reconcile();
|
|
314
|
+
if (verified === false) {
|
|
315
|
+
return { ok: true, written: merged.written, skipped: merged.skipped, verified: false };
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
if (patchChanged) {
|
|
320
|
+
try {
|
|
321
|
+
writePatchFile(patchPath, own.text);
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
return { ok: false, text: `写进去了,但启动检查没通过:${error instanceof Error ? error.message : String(error)}。原来的设置没能恢复,手动检查一下` };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
await deps.reconcile();
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
return { ok: false, text: `写进去了,但启动检查没通过:${error instanceof Error ? error.message : String(error)}。已恢复原来的设置` };
|
|
332
|
+
}
|
|
333
|
+
return { ok: false, text: `写进去了,但启动检查没通过:${error instanceof Error ? error.message : String(error)}。已恢复原来的设置` };
|
|
334
|
+
}
|
|
335
|
+
return { ok: true, written: merged.written, skipped: merged.skipped, verified: true };
|
|
336
|
+
}
|
|
337
|
+
export function configSyncAction(deps) {
|
|
338
|
+
return {
|
|
339
|
+
resource: CONFIG_SYNC_RESOURCE,
|
|
340
|
+
handle: (payload) => configSyncApply(deps, payload),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DshHostContext } from '../api/dsh.js';
|
|
2
|
+
import type { RouteHandler } from './http.js';
|
|
3
|
+
import type { DshPluginsSnapshot } from '../contract/protocol/dsh-plugins.js';
|
|
4
|
+
export declare function readDshPlugins(ctx: DshHostContext): Promise<DshPluginsSnapshot>;
|
|
5
|
+
export declare function dshPluginsHandler(ctx: DshHostContext): RouteHandler;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const MANAGER_SERVICE = 'pluginManager';
|
|
2
|
+
function asRecord(value) {
|
|
3
|
+
return typeof value === 'object' && value !== null ? value : undefined;
|
|
4
|
+
}
|
|
5
|
+
function asArray(value) {
|
|
6
|
+
if (Array.isArray(value))
|
|
7
|
+
return value;
|
|
8
|
+
const record = asRecord(value);
|
|
9
|
+
if (record === undefined)
|
|
10
|
+
return [];
|
|
11
|
+
for (const key of ['rows', 'items', 'list', 'bundles', 'plugins']) {
|
|
12
|
+
const inner = record[key];
|
|
13
|
+
if (Array.isArray(inner))
|
|
14
|
+
return inner;
|
|
15
|
+
}
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
function asText(value) {
|
|
19
|
+
return typeof value === 'string' ? value : '';
|
|
20
|
+
}
|
|
21
|
+
function rowOf(value) {
|
|
22
|
+
const record = asRecord(value);
|
|
23
|
+
if (record === undefined)
|
|
24
|
+
return undefined;
|
|
25
|
+
const name = asText(record.name) || asText(record.id);
|
|
26
|
+
if (name === '')
|
|
27
|
+
return undefined;
|
|
28
|
+
return { name, version: asText(record.version), enabled: record.disabled !== true && record.enabled !== false };
|
|
29
|
+
}
|
|
30
|
+
function rowsOf(value) {
|
|
31
|
+
const rows = [];
|
|
32
|
+
for (const item of asArray(value)) {
|
|
33
|
+
const row = rowOf(item);
|
|
34
|
+
if (row !== undefined)
|
|
35
|
+
rows.push(row);
|
|
36
|
+
}
|
|
37
|
+
return rows;
|
|
38
|
+
}
|
|
39
|
+
export async function readDshPlugins(ctx) {
|
|
40
|
+
const manager = ctx.get(MANAGER_SERVICE);
|
|
41
|
+
if (manager === undefined)
|
|
42
|
+
return { available: false, bundles: [], plugins: [] };
|
|
43
|
+
const [bundles, plugins] = await Promise.all([
|
|
44
|
+
manager.listBundles === undefined ? undefined : manager.listBundles(),
|
|
45
|
+
manager.listPlugins === undefined ? undefined : manager.listPlugins(),
|
|
46
|
+
]);
|
|
47
|
+
return { available: true, bundles: rowsOf(bundles), plugins: rowsOf(plugins) };
|
|
48
|
+
}
|
|
49
|
+
export function dshPluginsHandler(ctx) {
|
|
50
|
+
return () => readDshPlugins(ctx);
|
|
51
|
+
}
|
package/dist/host/extension.js
CHANGED
|
@@ -22,7 +22,7 @@ export function extensionCallerOf(data, need, callerOptions = {}) {
|
|
|
22
22
|
const code = result.error.pageCode ?? cliCodeOf(result.error.code);
|
|
23
23
|
const command = [need.name, ...args].join(' ');
|
|
24
24
|
const advice = code === 'cli-unclassified'
|
|
25
|
-
? `这条命令(${command})在这份 ${need.name} 插件里可能不存在(插件版本旧就会缺):用 workbench_modules_update(id=${callerOptions.suiteId ?? need.name}
|
|
25
|
+
? `这条命令(${command})在这份 ${need.name} 插件里可能不存在(插件版本旧就会缺):用 workbench_modules_update(id=${callerOptions.suiteId ?? need.name})升级,或在面板的模块状态里点更新,再重试。终端确认:${need.name} ${args[0] ?? ''} --help`
|
|
26
26
|
: undefined;
|
|
27
27
|
const hint = advice === undefined
|
|
28
28
|
? result.error.hint
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FlowActionRegistry, FlowCheck, FlowDefinition } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* 校验一条流程:id / 步骤、动作存在、引用只指前面、重试与轮询在边界内、子流程也照查。
|
|
4
|
+
* 只做静态检查,不碰平台、不跑动作。
|
|
5
|
+
* @param flow 流程定义。
|
|
6
|
+
* @param actions 动作表(只问 `has`)。
|
|
7
|
+
* @returns 逐条问题;`ok` 为真表示没有。
|
|
8
|
+
*/
|
|
9
|
+
export declare function checkFlow(flow: FlowDefinition, actions: Pick<FlowActionRegistry, 'has'>): FlowCheck;
|