@dsh-workbench/shared 0.3.11 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/dist/api/blocker-table.d.ts +3 -3
- package/dist/api/blocker-table.js +47 -23
- package/dist/api/dsh.d.ts +1 -1
- package/dist/api/workbench-client.js +2 -1
- package/dist/components/Activity.css +0 -1
- package/dist/components/ApprovalDetail.css +1 -2
- package/dist/components/ApprovalDetail.d.ts +1 -1
- package/dist/components/ApprovalPlan.css +0 -1
- package/dist/components/Card.css +0 -1
- package/dist/components/Card.d.ts +1 -1
- package/dist/components/CardBoundary.d.ts +1 -1
- package/dist/components/CardHead.css +0 -1
- package/dist/components/CardHead.d.ts +1 -1
- package/dist/components/ChoiceGroup.css +1 -2
- package/dist/components/ConfirmBar.d.ts +1 -1
- package/dist/components/ConfirmBar.js +1 -1
- package/dist/components/Form.css +1 -1
- package/dist/components/FormActions.js +1 -1
- 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/components/LongTaskRow.css +1 -2
- package/dist/components/LongTaskRow.d.ts +1 -1
- package/dist/components/LongTaskRow.js +1 -1
- package/dist/components/Note.css +0 -1
- package/dist/components/ResourceStates.css +0 -1
- package/dist/components/SettingsSection.css +0 -1
- package/dist/components/Spinner.css +0 -1
- package/dist/components/Stack.css +0 -1
- package/dist/components/StateBlock.css +0 -1
- package/dist/components/board-module.d.ts +3 -3
- package/dist/components/icons.d.ts +1 -1
- package/dist/components/icons.js +11 -4
- package/dist/config/constants.d.ts +2 -4
- package/dist/config/constants.js +2 -7
- package/dist/contract/adapters.d.ts +10 -0
- package/dist/contract/cli.d.ts +3 -0
- package/dist/contract/command.d.ts +22 -0
- package/dist/contract/data.d.ts +127 -0
- package/dist/contract/extension.d.ts +14 -0
- package/dist/contract/extension.js +1 -0
- package/dist/{api → contract}/facts.d.ts +12 -3
- package/dist/{api → contract}/facts.js +19 -1
- 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/needs.d.ts +132 -0
- package/dist/contract/needs.js +232 -0
- package/dist/contract/plugin-source.d.ts +1 -0
- package/dist/contract/plugin-source.js +1 -0
- package/dist/contract/protocol/bundle-install.d.ts +136 -0
- package/dist/contract/protocol/bundle-install.js +12 -0
- package/dist/contract/protocol/config-sync.d.ts +55 -0
- package/dist/contract/protocol/config-sync.js +2 -0
- package/dist/contract/protocol/diy.d.ts +4 -0
- package/dist/contract/protocol/diy.js +4 -0
- package/dist/contract/protocol/dsh-plugins.d.ts +11 -0
- package/dist/contract/protocol/dsh-plugins.js +1 -0
- package/dist/contract/protocol/long-tasks.d.ts +13 -0
- package/dist/contract/protocol/long-tasks.js +1 -0
- package/dist/contract/protocol/market-index.d.ts +195 -0
- package/dist/contract/protocol/market-index.js +7 -0
- package/dist/contract/protocol/platform.d.ts +44 -0
- package/dist/contract/protocol/platform.js +1 -0
- package/dist/contract/protocol/preflight-wake.d.ts +7 -0
- package/dist/contract/protocol/preflight-wake.js +1 -0
- package/dist/contract/protocol/profile-create.d.ts +30 -0
- package/dist/contract/protocol/profile-create.js +2 -0
- package/dist/contract/protocol/sidebar-edition.d.ts +14 -0
- package/dist/contract/protocol/sidebar-edition.js +2 -0
- package/dist/contract/protocol/unit-manifests.d.ts +6 -0
- package/dist/contract/protocol/unit-manifests.js +1 -0
- package/dist/contract/unit-board.js +1 -0
- package/dist/contract/unit-capability.d.ts +34 -0
- package/dist/contract/unit-capability.js +1 -0
- package/dist/{host → contract}/unit-declaration.d.ts +7 -5
- package/dist/{api → contract}/unit-declaration.js +19 -13
- package/dist/contract/unit.d.ts +57 -0
- package/dist/contract/unit.js +24 -0
- package/dist/contract/workbench-contract.d.ts +15 -0
- package/dist/contract/workbench-contract.js +1 -0
- package/dist/host/adapter-set.d.ts +26 -0
- package/dist/host/adapter-set.js +244 -0
- package/dist/host/adapter-types.d.ts +79 -0
- package/dist/host/adapter-types.js +23 -0
- package/dist/host/adapters.d.ts +28 -0
- package/dist/host/adapters.js +9 -0
- package/dist/host/browser.d.ts +3 -0
- package/dist/host/browser.js +3 -0
- package/dist/host/changelog.d.ts +21 -0
- package/dist/host/changelog.js +188 -0
- package/dist/host/channel-plugin.d.ts +15 -0
- package/dist/host/channel-plugin.js +42 -0
- package/dist/host/cli.d.ts +4 -0
- package/dist/host/cli.js +7 -0
- package/dist/host/command-name.d.ts +1 -0
- package/dist/host/command-name.js +14 -0
- package/dist/host/data-error.d.ts +13 -0
- package/dist/host/data-error.js +20 -0
- package/dist/host/data-root.js +3 -0
- package/dist/host/diy-debug.js +2 -1
- package/dist/host/diy-record.js +2 -1
- package/dist/host/extension.d.ts +7 -0
- package/dist/host/extension.js +39 -0
- package/dist/host/facts.d.ts +1 -1
- package/dist/host/facts.js +3 -2
- package/dist/host/git.d.ts +2 -33
- package/dist/host/git.js +2 -2
- package/dist/host/http.js +2 -2
- package/dist/host/lib-capabilities.d.ts +22 -0
- package/dist/host/lib-capabilities.js +37 -0
- package/dist/host/lib-install.d.ts +9 -0
- package/dist/host/lib-install.js +10 -0
- package/dist/host/long-task.d.ts +1 -1
- package/dist/host/long-task.js +4 -3
- package/dist/host/market-index.d.ts +22 -0
- package/dist/host/market-index.js +379 -0
- package/dist/host/needs.d.ts +4 -0
- package/dist/host/needs.js +2 -0
- package/dist/host/patch.d.ts +9 -0
- package/dist/host/patch.js +78 -0
- package/dist/host/paths.d.ts +12 -0
- package/dist/host/paths.js +28 -0
- package/dist/host/platform.d.ts +14 -0
- package/dist/host/platform.js +40 -0
- package/dist/host/plugin-source.d.ts +8 -6
- package/dist/host/plugin-source.js +43 -33
- package/dist/host/plugin.d.ts +4 -0
- package/dist/host/plugin.js +39 -0
- package/dist/host/port-types.d.ts +192 -0
- package/dist/host/port-types.js +1 -0
- package/dist/host/preflight.d.ts +28 -0
- package/dist/host/preflight.js +1 -0
- package/dist/host/profile-port.d.ts +4 -0
- package/dist/host/profile-port.js +4 -0
- package/dist/host/provision.d.ts +2 -2
- package/dist/host/provision.js +5 -3
- package/dist/host/recipe-rules.d.ts +5 -0
- package/dist/host/recipe-rules.js +382 -0
- package/dist/host/recipe.d.ts +14 -0
- package/dist/host/recipe.js +283 -0
- package/dist/host/release-packages.generated.d.ts +1 -0
- package/dist/host/release-packages.generated.js +10 -0
- package/dist/host/shell-text.d.ts +9 -2
- package/dist/host/shell-text.js +15 -3
- package/dist/host/shell.d.ts +2 -0
- package/dist/host/shell.js +7 -0
- package/dist/host/spec.d.ts +8 -0
- package/dist/host/spec.js +24 -0
- package/dist/host/supply-index.d.ts +34 -0
- package/dist/host/supply-index.js +202 -0
- package/dist/host/supply-port.d.ts +5 -0
- package/dist/host/supply-port.js +39 -0
- package/dist/host/time.d.ts +12 -0
- package/dist/host/time.js +19 -0
- package/dist/host/tokens.d.ts +26 -0
- package/dist/host/tokens.js +154 -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 +12 -4
- package/dist/host/version-range.d.ts +2 -0
- package/dist/host/version-range.js +13 -0
- package/dist/host/version.d.ts +4 -0
- package/dist/host/version.js +27 -0
- package/dist/host.d.ts +53 -16
- package/dist/host.js +53 -16
- package/dist/i18n/en.js +225 -71
- package/dist/i18n/index.d.ts +299 -73
- package/dist/i18n/merge.js +1 -1
- package/dist/i18n/need-text.d.ts +26 -0
- package/dist/i18n/need-text.js +26 -0
- package/dist/i18n/settings.d.ts +70 -0
- package/dist/i18n/settings.js +136 -0
- package/dist/i18n/zh.d.ts +298 -72
- package/dist/i18n/zh.js +227 -73
- package/dist/index.d.ts +27 -7
- package/dist/index.js +27 -7
- package/dist/{api → lib}/dsh-client.d.ts +11 -1
- package/dist/lib/dsh-client.js +1 -0
- package/dist/lib/market-index.d.ts +7 -0
- package/dist/lib/market-index.js +25 -0
- package/dist/lib/react.d.ts +0 -40
- package/dist/lib/react.js +0 -36
- package/dist/lib/ui.js +3 -9
- package/dist/lib/version.d.ts +4 -0
- package/dist/lib/version.js +21 -0
- package/dist/testing/data.d.ts +13 -0
- package/dist/testing/data.js +46 -0
- package/dist/testing/declaration.d.ts +1 -1
- package/dist/testing/declaration.js +1 -1
- package/dist/testing/host-fakes.d.ts +0 -13
- package/dist/testing/host-fakes.js +0 -24
- package/dist/testing/needs.d.ts +16 -0
- package/dist/testing/needs.js +46 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +2 -0
- package/package.json +22 -4
- package/src/api/__tests__/blocker-table.test.ts +135 -86
- package/src/api/blocker-table.ts +52 -26
- package/src/api/dsh.ts +1 -1
- package/src/api/host-write.ts +0 -1
- package/src/api/workbench-client.test.ts +2 -2
- package/src/api/workbench-client.ts +2 -1
- package/src/components/Activity.css +0 -1
- package/src/components/ApprovalDetail.css +1 -2
- package/src/components/ApprovalDetail.tsx +1 -1
- package/src/components/ApprovalPlan.css +0 -1
- package/src/components/Card.css +0 -1
- package/src/components/Card.tsx +1 -1
- package/src/components/CardAnchor.test.tsx +1 -1
- package/src/components/CardBoundary.tsx +1 -1
- package/src/components/CardHead.css +0 -1
- package/src/components/CardHead.tsx +1 -1
- package/src/components/ChoiceGroup.css +1 -2
- package/src/components/ConfirmBar.tsx +1 -1
- package/src/components/Form.css +1 -1
- package/src/components/Form.test.tsx +6 -0
- package/src/components/Form.tsx +1 -1
- package/src/components/FormActions.tsx +1 -1
- 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/components/LongTaskRow.css +1 -2
- package/src/components/LongTaskRow.tsx +1 -1
- package/src/components/Note.css +0 -1
- package/src/components/ResourceStates.css +0 -1
- package/src/components/SelectFieldNodes.tsx +0 -1
- package/src/components/SettingsSection.css +0 -1
- package/src/components/Spinner.css +0 -1
- package/src/components/Stack.css +0 -1
- package/src/components/StateBlock.css +0 -1
- package/src/components/__tests__/long-task-row.test.ts +1 -1
- package/src/components/board-module.ts +3 -3
- package/src/components/icons.tsx +16 -8
- package/src/config/constants.ts +2 -8
- package/src/{api → contract}/__tests__/facts.test.ts +73 -25
- package/src/{api → contract}/__tests__/long-task.test.ts +3 -3
- package/src/contract/__tests__/needs.test.ts +74 -0
- package/src/{host → contract}/__tests__/unit-declaration.test.ts +1 -1
- package/src/contract/__tests__/unit.test.ts +43 -0
- package/src/contract/adapters.ts +9 -0
- package/src/contract/cli.ts +3 -0
- package/src/contract/command.ts +25 -0
- package/src/contract/data.ts +146 -0
- package/src/contract/extension.ts +20 -0
- package/src/{api → contract}/facts.ts +34 -4
- package/src/contract/git.ts +20 -0
- package/src/contract/lib.ts +5 -0
- package/src/contract/needs.ts +331 -0
- package/src/contract/plugin-source.ts +1 -0
- package/src/contract/protocol/bundle-install.ts +198 -0
- package/src/contract/protocol/config-sync.ts +65 -0
- package/src/contract/protocol/diy.ts +4 -0
- package/src/contract/protocol/dsh-plugins.ts +13 -0
- package/src/contract/protocol/long-tasks.ts +16 -0
- package/src/contract/protocol/market-index.ts +246 -0
- package/src/contract/protocol/platform.ts +47 -0
- package/src/contract/protocol/preflight-wake.ts +17 -0
- package/src/contract/protocol/profile-create.ts +36 -0
- package/src/contract/protocol/sidebar-edition.ts +19 -0
- package/src/contract/protocol/unit-manifests.ts +8 -0
- package/src/{api → contract}/unit-board.ts +0 -1
- package/src/contract/unit-capability.ts +37 -0
- package/src/{host → contract}/unit-declaration.ts +22 -18
- package/src/contract/unit.ts +76 -0
- package/src/contract/workbench-contract.ts +30 -0
- package/src/host/__tests__/adapter-set.test.ts +228 -0
- package/src/host/__tests__/adapters.test.ts +27 -0
- package/src/host/__tests__/changelog.test.ts +102 -0
- package/src/host/__tests__/command-name.test.ts +16 -0
- package/src/host/__tests__/diy-record.test.ts +3 -3
- package/src/host/__tests__/facts.test.ts +5 -5
- package/src/host/__tests__/long-task.test.ts +1 -1
- package/src/host/__tests__/market-index.test.ts +179 -0
- package/src/host/__tests__/plugin-ledger.test.ts +10 -10
- package/src/host/__tests__/provision.test.ts +7 -6
- package/src/host/__tests__/shell-text.test.ts +16 -1
- package/src/host/__tests__/supply-index.test.ts +137 -0
- package/src/host/__tests__/tokens.test.ts +110 -0
- package/src/host/__tests__/version-range.test.ts +21 -0
- package/src/host/adapter-set.ts +299 -0
- package/src/host/adapter-types.ts +105 -0
- package/src/host/adapters.ts +50 -0
- package/src/host/browser.ts +3 -0
- package/src/host/changelog.ts +212 -0
- package/src/host/channel-plugin.ts +55 -0
- package/src/host/cli.ts +11 -0
- package/src/host/command-name.ts +15 -0
- package/src/host/data-error.ts +21 -0
- package/src/host/data-root.ts +2 -0
- package/src/host/diy-debug.ts +2 -1
- package/src/host/diy-record.ts +2 -1
- package/src/host/extension.test.ts +55 -0
- package/src/host/extension.ts +57 -0
- package/src/host/facts.ts +4 -3
- package/src/host/git.ts +8 -16
- package/src/host/http.ts +2 -2
- package/src/host/lib-capabilities.ts +52 -0
- package/src/host/lib-install.ts +18 -0
- package/src/host/long-task.ts +4 -3
- package/src/host/market-index.ts +383 -0
- package/src/host/needs.ts +6 -0
- package/src/host/patch.ts +93 -0
- package/src/host/paths.ts +37 -0
- package/src/host/platform.ts +48 -0
- package/src/host/plugin-source.ts +48 -56
- package/src/host/plugin.ts +39 -0
- package/src/host/port-types.ts +223 -0
- package/src/host/preflight.ts +31 -0
- package/src/host/profile-port.ts +9 -0
- package/src/host/provision.ts +7 -4
- package/src/host/recipe-rules.ts +330 -0
- package/src/host/recipe.test.ts +334 -0
- package/src/host/recipe.ts +323 -0
- package/src/host/release-packages.generated.ts +10 -0
- package/src/host/shell-text.ts +15 -3
- package/src/host/shell.ts +9 -0
- package/src/host/spec.ts +32 -0
- package/src/host/supply-index.ts +256 -0
- package/src/host/supply-port.ts +45 -0
- package/src/host/time.ts +22 -0
- package/src/host/tokens.ts +186 -0
- package/src/host/tool-kit.ts +0 -35
- package/src/host/unit-face.ts +12 -4
- package/src/host/version-range.ts +13 -0
- package/src/host/version.ts +30 -0
- package/src/host.ts +53 -16
- package/src/i18n/en.ts +225 -71
- package/src/i18n/merge.test.ts +6 -6
- package/src/i18n/merge.ts +1 -1
- package/src/i18n/need-text.ts +27 -0
- package/src/i18n/settings.ts +141 -0
- package/src/i18n/zh.ts +228 -73
- package/src/index.ts +27 -7
- package/src/lib/blocker-action.ts +0 -1
- package/src/{api → lib}/dsh-client.ts +12 -1
- package/src/lib/market-index.ts +36 -0
- package/src/lib/react.ts +0 -40
- package/src/lib/text.test.ts +2 -2
- package/src/lib/ui.test.ts +6 -5
- package/src/lib/ui.ts +3 -9
- package/src/testing/__tests__/declaration.test.ts +1 -1
- package/src/testing/data.ts +66 -0
- package/src/testing/declaration.ts +2 -2
- package/src/testing/host-fakes.ts +0 -38
- package/src/testing/needs.ts +80 -0
- package/src/testing.ts +2 -0
- package/dist/api/unit-declaration.d.ts +0 -65
- package/dist/api/unit.d.ts +0 -85
- package/dist/api/unit.js +0 -8
- package/dist/api/workbench-contract.d.ts +0 -44
- package/dist/components/MockStrip.css +0 -39
- package/dist/components/MockStrip.d.ts +0 -15
- package/dist/components/MockStrip.js +0 -8
- package/dist/components/SaveBar.css +0 -7
- package/dist/host/browser-find.d.ts +0 -38
- package/dist/host/browser-find.js +0 -242
- package/dist/host/browser-read.d.ts +0 -28
- package/dist/host/browser-read.js +0 -243
- package/dist/host/opencli-runtime.d.ts +0 -25
- package/dist/host/opencli-runtime.js +0 -56
- package/dist/host/opencli-window.d.ts +0 -2
- package/dist/host/opencli-window.js +0 -8
- package/dist/host/opencli.d.ts +0 -37
- package/dist/host/opencli.js +0 -155
- package/dist/host/unit-declaration.js +0 -213
- package/dist/host/unit-libs.d.ts +0 -37
- package/dist/lib/diy-debug.d.ts +0 -80
- package/dist/lib/diy-debug.js +0 -88
- package/dist/lib/diy-record.d.ts +0 -135
- package/dist/lib/diy-record.js +0 -162
- package/dist/lib/lint-probe.d.ts +0 -1
- package/dist/lib/lint-probe.js +0 -6
- package/dist/lib/panel-state.d.ts +0 -120
- package/dist/lib/panel-state.js +0 -176
- package/src/api/__tests__/unit.test.ts +0 -38
- package/src/api/unit.ts +0 -93
- package/src/api/workbench-contract.ts +0 -59
- package/src/host/__tests__/browser-find.test.ts +0 -330
- package/src/host/__tests__/browser-read.test.ts +0 -228
- package/src/host/__tests__/opencli-runtime.test.ts +0 -46
- package/src/host/browser-find.ts +0 -311
- package/src/host/browser-read.ts +0 -311
- package/src/host/opencli-runtime.ts +0 -79
- package/src/host/opencli-window.ts +0 -11
- package/src/host/opencli.ts +0 -213
- package/src/host/unit-libs.ts +0 -41
- /package/dist/{api/blocker.js → contract/adapters.js} +0 -0
- /package/dist/{api → contract}/blocker.d.ts +0 -0
- /package/dist/{api/dsh-client.js → contract/blocker.js} +0 -0
- /package/dist/{api/unit-board.js → contract/cli.js} +0 -0
- /package/dist/{api/workbench-contract.js → contract/command.js} +0 -0
- /package/dist/{host/unit-libs.js → contract/data.js} +0 -0
- /package/dist/{api → contract}/long-task.d.ts +0 -0
- /package/dist/{api → contract}/long-task.js +0 -0
- /package/dist/{api → contract}/unit-board.d.ts +0 -0
- /package/src/{api → contract}/blocker.ts +0 -0
- /package/src/{api → contract}/long-task.ts +0 -0
|
@@ -193,6 +193,7 @@ export function formRowsOf<Values extends object>(args: {
|
|
|
193
193
|
h(FormRow, {
|
|
194
194
|
key: field.key,
|
|
195
195
|
label: field.label,
|
|
196
|
+
htmlFor: controlIdOf(field.key),
|
|
196
197
|
hint: field.hint,
|
|
197
198
|
problem: args.showProblem ? args.problems[field.key] : undefined,
|
|
198
199
|
layout: field.layout,
|
|
@@ -200,6 +201,10 @@ export function formRowsOf<Values extends object>(args: {
|
|
|
200
201
|
);
|
|
201
202
|
}
|
|
202
203
|
|
|
204
|
+
function controlIdOf(key: string): string {
|
|
205
|
+
return `wb-form-${key}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
203
208
|
function controlOf<K extends string, V, Vals extends object>(
|
|
204
209
|
field: FormField<K, V, Vals>,
|
|
205
210
|
problems: Readonly<Record<string, ReactNode | undefined>>,
|
|
@@ -207,7 +212,7 @@ function controlOf<K extends string, V, Vals extends object>(
|
|
|
207
212
|
busy: boolean,
|
|
208
213
|
): ReactNode {
|
|
209
214
|
return field.control({
|
|
210
|
-
id:
|
|
215
|
+
id: controlIdOf(field.key),
|
|
211
216
|
name: field.key,
|
|
212
217
|
invalid: showProblem && Boolean(problems[field.key]),
|
|
213
218
|
disabled: busy || field.disabled === true,
|
|
@@ -48,6 +48,35 @@ describe('FormRow', () => {
|
|
|
48
48
|
expect(childrenOf(node).filter((one) => one !== null)).toHaveLength(2);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
it('★ 行是 <div>,<label> 只包标签文字 —— 控件、提示与整行空白都不在标签里', () => {
|
|
52
|
+
const node = FormRow({
|
|
53
|
+
label: '项目',
|
|
54
|
+
htmlFor: 'wb-form-project',
|
|
55
|
+
hint: '选错了也能改',
|
|
56
|
+
children: '下拉',
|
|
57
|
+
}) as unknown as { type: unknown; props: { children: unknown[] } };
|
|
58
|
+
const [labelNode, controlNode, hintNode] = node.props.children as {
|
|
59
|
+
type: unknown;
|
|
60
|
+
props: Record<string, unknown>;
|
|
61
|
+
}[];
|
|
62
|
+
|
|
63
|
+
expect(node.type).toBe('div');
|
|
64
|
+
expect(labelNode?.type).toBe('label');
|
|
65
|
+
expect(labelNode?.props['htmlFor']).toBe('wb-form-project');
|
|
66
|
+
expect(texts(labelNode)).toEqual(['项目']);
|
|
67
|
+
expect(controlNode?.props['className']).toBe('wb-formControl');
|
|
68
|
+
expect(hintNode?.props['className']).toBe('wb-formHint');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('没给 htmlFor 就不挂 for(排布照旧,只是点标签不聚焦)', () => {
|
|
72
|
+
const node = FormRow({ label: '项目', children: '下拉' }) as unknown as {
|
|
73
|
+
props: { children: unknown[] };
|
|
74
|
+
};
|
|
75
|
+
const labelNode = node.props.children[0] as { props: Record<string, unknown> };
|
|
76
|
+
|
|
77
|
+
expect(labelNode.props['htmlFor']).toBeUndefined();
|
|
78
|
+
});
|
|
79
|
+
|
|
51
80
|
it('有 problem 就顶掉 hint(同一格,二选一),错误行挂 data-wb-problem 给聚焦那件找', () => {
|
|
52
81
|
const node = FormRow({
|
|
53
82
|
label: '项目',
|
|
@@ -150,6 +179,7 @@ describe('标签列:同一个 FormRows 共用一列(ADR-0052 §11)', () =>
|
|
|
150
179
|
|
|
151
180
|
it('★ 标签不折行;stacked 还是单列、标签照常换行', () => {
|
|
152
181
|
expect(declarations('.wb-formLabel')).toContain('white-space: nowrap');
|
|
182
|
+
expect(declarations('.wb-formLabel')).toContain('justify-self: start');
|
|
153
183
|
expect(declarations(".wb-formRow[data-layout='stacked'] .wb-formLabel")).toContain(
|
|
154
184
|
'white-space: normal',
|
|
155
185
|
);
|
|
@@ -23,6 +23,7 @@ export function FormRows({ children }: FormRowsProps) {
|
|
|
23
23
|
*/
|
|
24
24
|
export interface FormRowProps {
|
|
25
25
|
readonly label: ReactNode;
|
|
26
|
+
readonly htmlFor?: string | undefined;
|
|
26
27
|
readonly hint?: ReactNode | undefined;
|
|
27
28
|
readonly problem?: ReactNode | undefined;
|
|
28
29
|
readonly layout?: 'inline' | 'stacked' | undefined;
|
|
@@ -32,20 +33,23 @@ export interface FormRowProps {
|
|
|
32
33
|
/**
|
|
33
34
|
* 表单一行:左边标签、右边控件,提示与错误占控件下面那一格。
|
|
34
35
|
* @param props.label - 这一行的标签;进了这一行,控件自己就别再给 `label`,否则会多一个标签。
|
|
36
|
+
* @param props.htmlFor - 标签挂到哪个控件(同一个 `id`);不给就只是排布,点标签不聚焦 —— `Form` 会自己接好。
|
|
35
37
|
* @param props.hint - 控件下的说明;给了 `problem` 就不画它 —— 同一格不叠两段话。
|
|
36
38
|
* @param props.problem - 校验失败那句话;给了就顶掉 `hint` 并给整行加上出错标记。
|
|
37
39
|
* @param props.layout - `stacked` 才是标签在上、控件在下(默认 `inline`:标签在左)。
|
|
38
40
|
* @param props.children - 控件本体。
|
|
39
|
-
* @returns 一个 `<label
|
|
41
|
+
* @returns 一个 `<div>`:里面的 `<label>` 只包标签文字,不包控件、提示与整行的空白。
|
|
40
42
|
*/
|
|
41
|
-
export function FormRow({ label, hint, problem, layout = 'inline', children }: FormRowProps) {
|
|
43
|
+
export function FormRow({ label, htmlFor, hint, problem, layout = 'inline', children }: FormRowProps) {
|
|
42
44
|
return (
|
|
43
|
-
<
|
|
45
|
+
<div
|
|
44
46
|
className="wb-formRow"
|
|
45
47
|
data-layout={layout}
|
|
46
48
|
data-wb-problem={problem === undefined ? undefined : ''}
|
|
47
49
|
>
|
|
48
|
-
<
|
|
50
|
+
<label className="wb-formLabel" htmlFor={htmlFor}>
|
|
51
|
+
{label}
|
|
52
|
+
</label>
|
|
49
53
|
<span className="wb-formControl">{children}</span>
|
|
50
54
|
{problem === undefined ? (
|
|
51
55
|
hint === undefined ? null : (
|
|
@@ -54,6 +58,6 @@ export function FormRow({ label, hint, problem, layout = 'inline', children }: F
|
|
|
54
58
|
) : (
|
|
55
59
|
<span className="wb-formProblem">{problem}</span>
|
|
56
60
|
)}
|
|
57
|
-
</
|
|
61
|
+
</div>
|
|
58
62
|
);
|
|
59
63
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { longTaskDot, longTaskStopPress, longTaskStopStage, LONG_TASK_STOP_ARM_MS, type LongTaskStatus } from '../
|
|
1
|
+
import { longTaskDot, longTaskStopPress, longTaskStopStage, LONG_TASK_STOP_ARM_MS, type LongTaskStatus } from '../contract/long-task.js';
|
|
2
2
|
import { Link } from './Link.js';
|
|
3
3
|
import { Fragment, h, useEffect, useState } from '../lib/react.js';
|
|
4
4
|
import {
|
package/src/components/Note.css
CHANGED
package/src/components/Stack.css
CHANGED
|
@@ -43,7 +43,7 @@ describe('两按停止钮:接线(这仓的组件测试是 node 环境,读
|
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
it('★★ 上膛后 3 秒自己落膛:用 LONG_TASK_STOP_ARM_MS,卸载 / 落膛都清掉计时器', () => {
|
|
46
|
-
expect(ROW).toContain("from '../
|
|
46
|
+
expect(ROW).toContain("from '../contract/long-task.js'");
|
|
47
47
|
expect(ROW).toContain('setTimeout(() => setArmed(false), LONG_TASK_STOP_ARM_MS)');
|
|
48
48
|
expect(ROW).toContain('clearTimeout(timer)');
|
|
49
49
|
expect(ROW).toContain('}, [armed]);');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type { SessionsSnapshot, Translate, WorkspaceSnapshot, WorkspaceView } from '../
|
|
3
|
-
import type { UnitManifest } from '../
|
|
4
|
-
import type { CapabilityPayload } from '../
|
|
2
|
+
import type { SessionsSnapshot, Translate, WorkspaceSnapshot, WorkspaceView } from '../lib/dsh-client.js';
|
|
3
|
+
import type { UnitManifest } from '../contract/unit.js';
|
|
4
|
+
import type { CapabilityPayload } from '../contract/workbench-contract.js';
|
|
5
5
|
import type { Dictionaries } from '../i18n/index.js';
|
|
6
6
|
|
|
7
7
|
/**
|
package/src/components/icons.tsx
CHANGED
|
@@ -51,20 +51,28 @@ export function IconConversation({ size = 16 }: IconProps) {
|
|
|
51
51
|
);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
const BRAND_FRAME = 'M10 6H54A5 5 0 0 1 59 11V43A5 5 0 0 1 54 48H10A5 5 0 0 1 5 43V11A5 5 0 0 1 10 6Z M12 10H52A3 3 0 0 1 55 13V41A3 3 0 0 1 52 44H12A3 3 0 0 1 9 41V13A3 3 0 0 1 12 10Z';
|
|
55
|
+
|
|
56
|
+
const BRAND_TAIL =
|
|
57
|
+
'M8.844 13.742C8.967 12.328 8.45 10.4 8.45 9.65C8.45 8.94 8.88 8.43 9.6 8.43C11.285 8.43 12.106 8.281 12.685 8.104C13.71 7.791 14.585 6.768 15.055 5.945C15.137 5.803 14.99 5.641 14.829 5.671C13.829 5.86 12.828 5.376 11.827 4.978C10.659 4.514 9.491 4.707 8.935 4.876C8.805 4.915 8.658 4.819 8.636 4.686C8.468 3.643 7.405 2.615 5.498 2.238C4.54 2.048 3.748 1.574 3.347 1.202C3.252 1.113 3.088 1.125 3.03 1.242C2.628 2.059 2.168 3.82 5.248 6.115C5.82 6.494 6.31 6.785 6.574 7.637C6.72 8.104 6.157 9.168 6.061 9.368C5.157 11.27 5.089 12.19 4.926 13.742Z';
|
|
58
|
+
|
|
59
|
+
const BRAND_TAIL_TRANSFORM = 'translate(14.6263 10.5853) scale(1.95)';
|
|
60
|
+
|
|
54
61
|
/**
|
|
55
|
-
*
|
|
62
|
+
* 品牌标:一台显示器里放着 dsh 的鲸鱼尾巴;默认 24px(它是品牌物,别拿它当通用图标)。
|
|
56
63
|
* @param props.size - 边长像素,同时给 `width` 与 `height`。
|
|
57
64
|
* @returns 一枚 `aria-hidden` 的 svg。
|
|
58
65
|
*/
|
|
59
66
|
export function IconBrandMark({ size = 24 }: IconProps) {
|
|
60
67
|
return (
|
|
61
|
-
<Shell size={size}
|
|
62
|
-
<
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
<Shell size={size} viewBox="0 0 64 64">
|
|
69
|
+
<path d={BRAND_FRAME} fill="currentColor" fillRule="evenodd" />
|
|
70
|
+
<rect x={27.5} y={48} width={9} height={4.75} fill="currentColor" />
|
|
71
|
+
<rect x={19} y={52.75} width={26} height={4.75} rx={2.375} fill="currentColor" />
|
|
72
|
+
<g transform={BRAND_TAIL_TRANSFORM}>
|
|
73
|
+
<path d={BRAND_TAIL} fill="currentColor" />
|
|
74
|
+
<circle cx={6.885} cy={13.742} r={1.95} fill="currentColor" />
|
|
75
|
+
</g>
|
|
68
76
|
</Shell>
|
|
69
77
|
);
|
|
70
78
|
}
|
package/src/config/constants.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/** 外壳插件的包名:浏览器模块表与宿主按它注册、认领这枚插件。 */
|
|
2
2
|
export const PLUGIN_ID = "@dsh-workbench/core";
|
|
3
3
|
|
|
4
|
-
/** 与外壳同批发布的兄弟包名:`release.ts` 用它判一版三包是否都已在源上。 */
|
|
5
|
-
export const RELEASE_SIBLINGS = [
|
|
6
|
-
"@dsh-workbench/shared",
|
|
7
|
-
"@dsh-workbench/module-tools",
|
|
8
|
-
] as const;
|
|
9
|
-
|
|
10
4
|
/** 外壳宿主 HTTP API 的根前缀;prefs / actions / provision 等路由都挂在它下面。 */
|
|
11
5
|
export const API_PREFIX = "/workbench/api";
|
|
12
6
|
|
|
@@ -40,8 +34,8 @@ export const PANEL_STATE_PREFIX = `${API_PREFIX}/${PANEL_STATE_PATH}`;
|
|
|
40
34
|
/** 钉住的 opencli 插件版本:`opencli-runtime` 按它校验、取那一份。 */
|
|
41
35
|
export const OPENCLI_PIN = "1.8.8";
|
|
42
36
|
|
|
43
|
-
/** 缺陷看板套件的 dsh 服务名(`
|
|
44
|
-
export const
|
|
37
|
+
/** 缺陷看板套件的 dsh 服务名(`workbenchIssues`):套件照它登记自己那份服务。 */
|
|
38
|
+
export const ISSUES_SERVICE = "workbenchIssues";
|
|
45
39
|
|
|
46
40
|
/** 面板套件的 dsh 服务名(`workbenchPanels`):宿主经它取套件公布的清单与订阅。 */
|
|
47
41
|
export const PANELS_SERVICE = "workbenchPanels";
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
parseFactFrame,
|
|
6
6
|
type FactFrame,
|
|
7
7
|
type FactTransport,
|
|
8
|
+
type FactTransportMode,
|
|
8
9
|
} from '../facts.js';
|
|
9
10
|
|
|
10
11
|
function fakeTransport(): {
|
|
@@ -52,17 +53,17 @@ describe('createFactChannel', () => {
|
|
|
52
53
|
it('★ 之后的帧只惊动它点名的事实(别的读者不该白跑一次)', () => {
|
|
53
54
|
const wire = fakeTransport();
|
|
54
55
|
const channel = createFactChannel({ transport: wire.transport });
|
|
55
|
-
const
|
|
56
|
+
const inventory = vi.fn();
|
|
56
57
|
const issues = vi.fn();
|
|
57
|
-
channel.subscribe('
|
|
58
|
+
channel.subscribe('inventory/summary', inventory);
|
|
58
59
|
channel.subscribe('issues', issues);
|
|
59
60
|
channel.applyFrame({ epoch: 'e1', rev: 1, facts: [] });
|
|
60
61
|
|
|
61
|
-
channel.applyFrame({ epoch: 'e1', rev: 2, facts: ['
|
|
62
|
+
channel.applyFrame({ epoch: 'e1', rev: 2, facts: ['inventory/summary'] });
|
|
62
63
|
|
|
63
|
-
expect(
|
|
64
|
+
expect(inventory).toHaveBeenCalledTimes(1);
|
|
64
65
|
expect(issues).not.toHaveBeenCalled();
|
|
65
|
-
expect(channel.revisionOf('
|
|
66
|
+
expect(channel.revisionOf('inventory/summary')).toBe(2);
|
|
66
67
|
expect(channel.revisionOf('issues')).toBe(0);
|
|
67
68
|
});
|
|
68
69
|
|
|
@@ -132,33 +133,33 @@ describe('createFactChannel', () => {
|
|
|
132
133
|
const summary = vi.fn();
|
|
133
134
|
const builds = vi.fn();
|
|
134
135
|
const issues = vi.fn();
|
|
135
|
-
channel.subscribe('
|
|
136
|
-
channel.subscribe('
|
|
137
|
-
channel.subscribe('
|
|
136
|
+
channel.subscribe('inventory/summary', summary);
|
|
137
|
+
channel.subscribe('inventory/builds?env=innettest', builds);
|
|
138
|
+
channel.subscribe('issues/list', issues);
|
|
138
139
|
channel.applyFrame({ epoch: 'e1', rev: 1, facts: [] });
|
|
139
140
|
|
|
140
|
-
channel.applyFrame({ epoch: 'e1', rev: 2, facts: ['capability', 'panels', '
|
|
141
|
+
channel.applyFrame({ epoch: 'e1', rev: 2, facts: ['capability', 'panels', 'inventory*'] });
|
|
141
142
|
|
|
142
143
|
expect(summary).toHaveBeenCalledTimes(1);
|
|
143
144
|
expect(builds).toHaveBeenCalledTimes(1);
|
|
144
145
|
expect(issues).not.toHaveBeenCalled();
|
|
145
|
-
expect(channel.revisionOf('
|
|
146
|
+
expect(channel.revisionOf('inventory/summary')).toBeGreaterThan(1);
|
|
146
147
|
});
|
|
147
148
|
|
|
148
149
|
it('命名空间不许凭空造出订阅(订阅表只由读者写),也不许越界到别的模块', () => {
|
|
149
150
|
const wire = fakeTransport();
|
|
150
151
|
const channel = createFactChannel({ transport: wire.transport });
|
|
151
|
-
const
|
|
152
|
+
const demoAnalysis = vi.fn();
|
|
152
153
|
const lookalike = vi.fn();
|
|
153
|
-
channel.subscribe('
|
|
154
|
-
channel.subscribe('
|
|
154
|
+
channel.subscribe('analysis/summary', demoAnalysis);
|
|
155
|
+
channel.subscribe('inventory-extra/summary', lookalike);
|
|
155
156
|
channel.applyFrame({ epoch: 'e1', rev: 1, facts: [] });
|
|
156
157
|
|
|
157
|
-
channel.applyFrame({ epoch: 'e1', rev: 2, facts: ['
|
|
158
|
+
channel.applyFrame({ epoch: 'e1', rev: 2, facts: ['inventory*'] });
|
|
158
159
|
|
|
159
|
-
expect(
|
|
160
|
+
expect(demoAnalysis).not.toHaveBeenCalled();
|
|
160
161
|
expect(lookalike).not.toHaveBeenCalled();
|
|
161
|
-
expect(channel.revisionOf('
|
|
162
|
+
expect(channel.revisionOf('inventory/never-subscribed')).toBe(0);
|
|
162
163
|
expect(channel.subscriberCount()).toBe(2);
|
|
163
164
|
});
|
|
164
165
|
|
|
@@ -166,26 +167,26 @@ describe('createFactChannel', () => {
|
|
|
166
167
|
const wire = fakeTransport();
|
|
167
168
|
const channel = createFactChannel({ transport: wire.transport });
|
|
168
169
|
const listener = vi.fn();
|
|
169
|
-
channel.subscribe('
|
|
170
|
+
channel.subscribe('inventory/builds?env=innettest', listener);
|
|
170
171
|
|
|
171
|
-
channel.applyFrame({ epoch: 'e1', rev: 4, facts: ['
|
|
172
|
+
channel.applyFrame({ epoch: 'e1', rev: 4, facts: ['inventory*'] });
|
|
172
173
|
|
|
173
174
|
expect(listener).not.toHaveBeenCalled();
|
|
174
|
-
expect(channel.revisionOf('
|
|
175
|
+
expect(channel.revisionOf('inventory/builds?env=innettest')).toBeGreaterThan(0);
|
|
175
176
|
});
|
|
176
177
|
|
|
177
178
|
it('refresh(命名空间) 只惊动它下面的事实(本地失效也按模块点名,不是一律全量)', () => {
|
|
178
179
|
const wire = fakeTransport();
|
|
179
180
|
const channel = createFactChannel({ transport: wire.transport });
|
|
180
|
-
const
|
|
181
|
+
const inventory = vi.fn();
|
|
181
182
|
const issues = vi.fn();
|
|
182
|
-
channel.subscribe('
|
|
183
|
-
channel.subscribe('
|
|
183
|
+
channel.subscribe('inventory/summary', inventory);
|
|
184
|
+
channel.subscribe('issues/list', issues);
|
|
184
185
|
channel.applyFrame({ epoch: 'e1', rev: 1, facts: [] });
|
|
185
186
|
|
|
186
|
-
channel.refresh('
|
|
187
|
+
channel.refresh('inventory*');
|
|
187
188
|
|
|
188
|
-
expect(
|
|
189
|
+
expect(inventory).toHaveBeenCalledTimes(1);
|
|
189
190
|
expect(issues).not.toHaveBeenCalled();
|
|
190
191
|
});
|
|
191
192
|
|
|
@@ -204,6 +205,53 @@ describe('createFactChannel', () => {
|
|
|
204
205
|
|
|
205
206
|
expect(seen).toEqual([4]);
|
|
206
207
|
});
|
|
208
|
+
|
|
209
|
+
it('★ 传输来路:没建连是 null,报上来就换档,同一档不重复惊动', () => {
|
|
210
|
+
const wire: { report: ((mode: FactTransportMode) => void) | null } = { report: null };
|
|
211
|
+
const channel = createFactChannel({
|
|
212
|
+
transport: {
|
|
213
|
+
start(handlers) {
|
|
214
|
+
wire.report = handlers.onMode ?? null;
|
|
215
|
+
return () => {};
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
const listener = vi.fn();
|
|
220
|
+
expect(channel.mode()).toBeNull();
|
|
221
|
+
channel.subscribeMode(listener);
|
|
222
|
+
channel.subscribe('panels', vi.fn());
|
|
223
|
+
|
|
224
|
+
wire.report?.('polling');
|
|
225
|
+
expect(channel.mode()).toBe('polling');
|
|
226
|
+
wire.report?.('polling');
|
|
227
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
228
|
+
|
|
229
|
+
wire.report?.('sse');
|
|
230
|
+
expect(channel.mode()).toBe('sse');
|
|
231
|
+
expect(listener).toHaveBeenCalledTimes(2);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('★ 最后一个订阅走掉、传输停了,来路回到没建连', () => {
|
|
235
|
+
const wire: { report: ((mode: FactTransportMode) => void) | null } = { report: null };
|
|
236
|
+
const channel = createFactChannel({
|
|
237
|
+
transport: {
|
|
238
|
+
start(handlers) {
|
|
239
|
+
wire.report = handlers.onMode ?? null;
|
|
240
|
+
return () => {};
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
const listener = vi.fn();
|
|
245
|
+
channel.subscribeMode(listener);
|
|
246
|
+
const off = channel.subscribe('panels', vi.fn());
|
|
247
|
+
wire.report?.('sse');
|
|
248
|
+
listener.mockClear();
|
|
249
|
+
|
|
250
|
+
off();
|
|
251
|
+
|
|
252
|
+
expect(channel.mode()).toBeNull();
|
|
253
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
254
|
+
});
|
|
207
255
|
});
|
|
208
256
|
|
|
209
257
|
describe('parseFactFrame', () => {
|
|
@@ -255,7 +303,7 @@ describe('parseFactFrame', () => {
|
|
|
255
303
|
|
|
256
304
|
describe('factOfRoute', () => {
|
|
257
305
|
it('事实名就是路由名(不另起一套词汇,读与订阅不可能各说各话)', () => {
|
|
258
|
-
expect(factOfRoute('
|
|
306
|
+
expect(factOfRoute('inventory/summary')).toBe('inventory/summary');
|
|
259
307
|
expect(factOfRoute('capability')).toBe('capability');
|
|
260
308
|
});
|
|
261
309
|
});
|
|
@@ -13,16 +13,16 @@ import {
|
|
|
13
13
|
|
|
14
14
|
describe('runsRouteOf', () => {
|
|
15
15
|
it('★ 事实名 = 路由名(三处同一个来源,改一处不可能漏另一处)', () => {
|
|
16
|
-
expect(runsRouteOf('
|
|
16
|
+
expect(runsRouteOf('inventory')).toBe('inventory/runs');
|
|
17
17
|
expect(runsRouteOf('issues')).toBe('issues/runs');
|
|
18
18
|
});
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
describe('stopRouteOf(停止那条写路由)', () => {
|
|
22
22
|
it('★ 就是读路由 + `/stop`(读口与写口同一个单元名,不另造一套命名)', () => {
|
|
23
|
-
expect(stopRouteOf('
|
|
23
|
+
expect(stopRouteOf('inventory')).toBe('inventory/runs/stop');
|
|
24
24
|
expect(stopRouteOf('issues')).toBe('issues/runs/stop');
|
|
25
|
-
expect(stopRouteOf('
|
|
25
|
+
expect(stopRouteOf('inventory')).toBe(`${runsRouteOf('inventory')}/stop`);
|
|
26
26
|
});
|
|
27
27
|
});
|
|
28
28
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
NEED_KINDS,
|
|
4
|
+
NEED_KIND_SPECS,
|
|
5
|
+
SUPPLY_SOURCE_KINDS,
|
|
6
|
+
needProblemsOf,
|
|
7
|
+
needsProblemsOf,
|
|
8
|
+
} from '../needs.js';
|
|
9
|
+
|
|
10
|
+
const npm = { kind: 'npm', package: '@acme/plugin-demo', registryDefault: 'https://registry.test/' };
|
|
11
|
+
const base = { kind: 'lib', name: 'demo', purpose: '测试', sources: [npm] };
|
|
12
|
+
|
|
13
|
+
function errorsOf(need: unknown): readonly string[] {
|
|
14
|
+
return needProblemsOf('demo', need, 0).errors;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('needs 校验矩阵', () => {
|
|
18
|
+
it('★ 四种 kind 的最小声明都过;注册表与 NEED_KINDS 一一对应', () => {
|
|
19
|
+
expect(
|
|
20
|
+
needsProblemsOf('demo', [
|
|
21
|
+
{ kind: 'bin', name: 'gitops', purpose: '部署' },
|
|
22
|
+
{ kind: 'extension', runtime: 'opencli', name: 'demo', purpose: '面板', sources: [npm] },
|
|
23
|
+
base,
|
|
24
|
+
{ kind: 'env', name: 'DSH_TOKEN', secret: true, purpose: '只读接口' },
|
|
25
|
+
]).errors,
|
|
26
|
+
).toEqual([]);
|
|
27
|
+
expect(Object.keys(NEED_KIND_SPECS).sort()).toEqual([...NEED_KINDS].sort());
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('未知 kind / 未知 source.kind → 红,并点名', () => {
|
|
31
|
+
const unknownKind = errorsOf({ kind: 'mcp', name: 'x', purpose: '测试' });
|
|
32
|
+
expect(unknownKind.join(' ')).toContain('needs[0].kind 不认识');
|
|
33
|
+
const unknownSource = errorsOf({ ...base, sources: [{ kind: 'tarball', url: 'x' }] });
|
|
34
|
+
expect(unknownSource.join(' ')).toContain('sources[0].kind 不认识');
|
|
35
|
+
expect(new Set(Object.values(NEED_KIND_SPECS).flatMap((spec) => spec.sources))).toEqual(
|
|
36
|
+
new Set(SUPPLY_SOURCE_KINDS),
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('bin / env 写了 sources → 红(没有安装路径)', () => {
|
|
41
|
+
expect(errorsOf({ kind: 'bin', name: 'x', purpose: '测试', sources: [npm] }).join(' ')).toContain(
|
|
42
|
+
'不支持 sources',
|
|
43
|
+
);
|
|
44
|
+
expect(errorsOf({ kind: 'env', name: 'X', purpose: '测试', sources: [npm] }).join(' ')).toContain(
|
|
45
|
+
'不支持 sources',
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('lib 只认 npm 源;sources 空数组 → 红(不代装就整条删掉)', () => {
|
|
50
|
+
expect(errorsOf({ ...base, sources: [{ kind: 'git', url: 'github:acme/demo' }] }).join(' ')).toContain(
|
|
51
|
+
'只支持 npm',
|
|
52
|
+
);
|
|
53
|
+
expect(errorsOf({ ...base, sources: [] }).join(' ')).toContain('空数组');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('必填项:purpose、extension.runtime、name、required 类型、id 空串', () => {
|
|
57
|
+
expect(errorsOf({ ...base, purpose: '' }).join(' ')).toContain('purpose 没写');
|
|
58
|
+
expect(errorsOf({ kind: 'extension', name: 'x', purpose: '测试' }).join(' ')).toContain('runtime 没写');
|
|
59
|
+
expect(errorsOf({ ...base, name: '' }).join(' ')).toContain('name 没写');
|
|
60
|
+
expect(errorsOf({ ...base, required: 'yes' }).join(' ')).toContain('required 要是布尔');
|
|
61
|
+
expect(errorsOf({ ...base, id: '' }).join(' ')).toContain('id 是空串');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('★ need 内未知字段只是警告;x- 前缀直接忽略(不为可选字段升协议版本)', () => {
|
|
65
|
+
const read = needProblemsOf('demo', { ...base, future: 1, 'x-vendor': true }, 0);
|
|
66
|
+
expect(read.errors).toEqual([]);
|
|
67
|
+
expect(read.warnings.join(' ')).toContain('不认识的字段 future');
|
|
68
|
+
expect(read.warnings.join(' ')).not.toContain('x-vendor');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('没写 needs → 空表不报', () => {
|
|
72
|
+
expect(needsProblemsOf('demo', undefined)).toEqual({ errors: [], warnings: [] });
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { Need } from '../needs.js';
|
|
3
|
+
import { needsOf, provisionableNeedsOf, type UnitManifest } from '../unit.js';
|
|
4
|
+
|
|
5
|
+
function manifest(needs?: readonly Need[]): UnitManifest {
|
|
6
|
+
return {
|
|
7
|
+
id: 'issues',
|
|
8
|
+
card: { titleKey: 'issues.mine', order: 10 },
|
|
9
|
+
tools: [],
|
|
10
|
+
host: { inject: [] },
|
|
11
|
+
...(needs === undefined ? {} : { needs }),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const npm = { kind: 'npm' as const, registryEnv: 'WB_NPM', registryDefault: 'https://registry.example/' };
|
|
16
|
+
|
|
17
|
+
describe('provisionableNeedsOf(缺来源时那条回退口径,宿主与浏览器共用一份)', () => {
|
|
18
|
+
it('写了 sources 的 extension / lib → 宿主可代装', () => {
|
|
19
|
+
const needs: Need[] = [
|
|
20
|
+
{ kind: 'extension', runtime: 'opencli', name: 'demo', purpose: '测试', sources: [npm] },
|
|
21
|
+
{ kind: 'lib', name: 'opencli-workflow', purpose: '测试', sources: [npm] },
|
|
22
|
+
];
|
|
23
|
+
expect(provisionableNeedsOf(manifest(needs)).map((need) => need.name)).toEqual([
|
|
24
|
+
'demo',
|
|
25
|
+
'opencli-workflow',
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('★ 没写 sources 的 extension、bin、env 都不算供给 —— 它们不是市场门槛,只是「我自己装,你帮我判」', () => {
|
|
30
|
+
const needs: Need[] = [
|
|
31
|
+
{ kind: 'extension', runtime: 'opencli', name: 'demo', purpose: '测试' },
|
|
32
|
+
{ kind: 'bin', name: 'gitops', purpose: '测试' },
|
|
33
|
+
{ kind: 'env', name: 'DSH_TOKEN', purpose: '测试' },
|
|
34
|
+
];
|
|
35
|
+
expect(provisionableNeedsOf(manifest(needs))).toEqual([]);
|
|
36
|
+
expect(provisionableNeedsOf(manifest([]))).toEqual([]);
|
|
37
|
+
expect(provisionableNeedsOf(manifest())).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('needsOf 没写就是空表', () => {
|
|
41
|
+
expect(needsOf(manifest())).toEqual([]);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface CommandFile {
|
|
2
|
+
readonly content: string;
|
|
3
|
+
readonly suffix?: string | undefined;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface CommandRequest {
|
|
7
|
+
readonly argv: readonly string[];
|
|
8
|
+
readonly timeoutMs?: number | undefined;
|
|
9
|
+
readonly cwd?: string | undefined;
|
|
10
|
+
readonly files?: readonly CommandFile[] | undefined;
|
|
11
|
+
readonly appendFileArgs?: string | undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CommandResult {
|
|
15
|
+
readonly stdout: string;
|
|
16
|
+
readonly stderr: string;
|
|
17
|
+
readonly exitCode: number;
|
|
18
|
+
readonly timedOut: boolean;
|
|
19
|
+
readonly filePaths: readonly string[];
|
|
20
|
+
readonly error?: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CommandReader {
|
|
24
|
+
run(request: CommandRequest): Promise<CommandResult>;
|
|
25
|
+
}
|