@dsh-workbench/shared 1.1.8 → 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/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 +16 -19
- package/dist/i18n/index.d.ts +17 -20
- package/dist/i18n/runtime-terms.d.ts +15 -0
- package/dist/i18n/runtime-terms.js +22 -0
- package/dist/i18n/zh.d.ts +16 -19
- package/dist/i18n/zh.js +16 -19
- 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/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 +16 -20
- package/src/i18n/runtime-terms.ts +26 -0
- package/src/i18n/zh.ts +16 -20
- 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,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一条流程:按顺序跑的步骤清单;步骤里的动作按名字从注册表取。
|
|
3
|
+
*/
|
|
4
|
+
export interface FlowDefinition {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly title: string;
|
|
7
|
+
readonly target?: FlowValue | undefined;
|
|
8
|
+
readonly stopWhen?: FlowPredicate | undefined;
|
|
9
|
+
readonly steps: readonly FlowStep[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 重试:`attempts` 是额外次数,总共最多 6 次。
|
|
13
|
+
*/
|
|
14
|
+
export interface FlowRetry {
|
|
15
|
+
readonly attempts: number;
|
|
16
|
+
readonly delayMs?: number | undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 轮询:每次调动作,`until` 判停;`maxMs: 0` 表示不限时。
|
|
20
|
+
* `onTimeout: 'continue'` 把 `undefined` 写进步骤结果,交给后面的 `when` 决定。
|
|
21
|
+
*/
|
|
22
|
+
export interface FlowPoll {
|
|
23
|
+
readonly everyMs: number;
|
|
24
|
+
readonly maxMs: number;
|
|
25
|
+
readonly until: FlowPredicate;
|
|
26
|
+
readonly onTimeout?: 'fail' | 'continue' | undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 逐项展开:`over` 求成数组,每项用 `item` 作用域跑一次,最多 `max` 项。
|
|
30
|
+
*/
|
|
31
|
+
export interface FlowForEach {
|
|
32
|
+
readonly over: FlowValue;
|
|
33
|
+
readonly as: string;
|
|
34
|
+
readonly max: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 一步:调用一个注册表里的动作,可选重试、轮询、逐项展开。
|
|
38
|
+
*/
|
|
39
|
+
export interface FlowActionStep {
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly title?: string | undefined;
|
|
42
|
+
readonly action: string;
|
|
43
|
+
readonly with?: Readonly<Record<string, FlowValue>> | undefined;
|
|
44
|
+
readonly when?: FlowPredicate | undefined;
|
|
45
|
+
readonly retry?: FlowRetry | undefined;
|
|
46
|
+
readonly poll?: FlowPoll | undefined;
|
|
47
|
+
readonly forEach?: FlowForEach | undefined;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 一步:跑另一条流程;`with` 求出来的对象就是子流程的 `in`,结果挂在 `steps.<id>` 下。
|
|
51
|
+
*/
|
|
52
|
+
export interface FlowSubFlowStep {
|
|
53
|
+
readonly id: string;
|
|
54
|
+
readonly title?: string | undefined;
|
|
55
|
+
readonly flow: FlowDefinition;
|
|
56
|
+
readonly with?: Readonly<Record<string, FlowValue>> | undefined;
|
|
57
|
+
readonly when?: FlowPredicate | undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 一步要么调动作,要么跑子流程。
|
|
61
|
+
*/
|
|
62
|
+
export type FlowStep = FlowActionStep | FlowSubFlowStep;
|
|
63
|
+
/**
|
|
64
|
+
* 步骤入参里的一个值:字面量、引用,或二选一。
|
|
65
|
+
* 引用只从 `in` / `steps` / `item` / `value` 起头;`value` 只在 `poll.until` 里。
|
|
66
|
+
*/
|
|
67
|
+
export type FlowValue = {
|
|
68
|
+
readonly $ref: string;
|
|
69
|
+
} | {
|
|
70
|
+
readonly $lit: unknown;
|
|
71
|
+
} | {
|
|
72
|
+
readonly $default: readonly [FlowValue, FlowValue];
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* 条件表达式:相等、包含、存在、与、非;没有算术与函数调用。
|
|
76
|
+
*/
|
|
77
|
+
export type FlowPredicate = {
|
|
78
|
+
readonly $eq: readonly [FlowValue, FlowValue];
|
|
79
|
+
} | {
|
|
80
|
+
readonly $in: readonly [FlowValue, readonly unknown[]];
|
|
81
|
+
} | {
|
|
82
|
+
readonly $exists: FlowValue;
|
|
83
|
+
} | {
|
|
84
|
+
readonly $and: readonly FlowPredicate[];
|
|
85
|
+
} | {
|
|
86
|
+
readonly $not: FlowPredicate;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* 求值现场:流程输入、已经跑完的步骤输出、当前 `forEach` 项、当前 `poll` 返回值。
|
|
90
|
+
*/
|
|
91
|
+
export interface FlowScope {
|
|
92
|
+
readonly in: unknown;
|
|
93
|
+
readonly steps: Readonly<Record<string, unknown>>;
|
|
94
|
+
readonly item?: unknown;
|
|
95
|
+
readonly value?: unknown;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 动作执行时拿到的现场:取消信号、这次跑的 id 与工作区、当前步骤 id、一行进度、登录提示。
|
|
99
|
+
*/
|
|
100
|
+
export interface FlowActionContext {
|
|
101
|
+
readonly signal: AbortSignal;
|
|
102
|
+
readonly runId: string;
|
|
103
|
+
readonly workspace: string;
|
|
104
|
+
readonly stepId: string;
|
|
105
|
+
log(text: string): void;
|
|
106
|
+
login(url: string): void;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 动作的可选元数据:给人看的标题、入参形状、以及“会不会改平台”。
|
|
110
|
+
* `writes` 只给外层审批用,不参与执行。
|
|
111
|
+
*/
|
|
112
|
+
export interface FlowActionMeta {
|
|
113
|
+
readonly title?: string | undefined;
|
|
114
|
+
readonly description?: string | undefined;
|
|
115
|
+
readonly input?: Readonly<Record<string, unknown>> | undefined;
|
|
116
|
+
readonly writes?: boolean | undefined;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 一个动作:实现完全自由,引擎只按名字调它。
|
|
120
|
+
*/
|
|
121
|
+
export type FlowAction = (input: unknown, context: FlowActionContext) => Promise<unknown>;
|
|
122
|
+
/**
|
|
123
|
+
* 注册表里的一格:名字、实现、元数据。
|
|
124
|
+
*/
|
|
125
|
+
export interface RegisteredFlowAction {
|
|
126
|
+
readonly name: string;
|
|
127
|
+
readonly action: FlowAction;
|
|
128
|
+
readonly meta: FlowActionMeta;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 动作注册表:流程只能引用这里登记过的名字。
|
|
132
|
+
*/
|
|
133
|
+
export interface FlowActionRegistry {
|
|
134
|
+
register(name: string, action: FlowAction, meta?: FlowActionMeta): void;
|
|
135
|
+
get(name: string): RegisteredFlowAction | undefined;
|
|
136
|
+
has(name: string): boolean;
|
|
137
|
+
list(): readonly RegisteredFlowAction[];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 步骤事件:开始、成功、跳过、失败、需要登录;重试时带第几次。
|
|
141
|
+
*/
|
|
142
|
+
export interface FlowStepEvent {
|
|
143
|
+
readonly stepId: string;
|
|
144
|
+
readonly title: string;
|
|
145
|
+
readonly status: 'running' | 'ok' | 'skipped' | 'failed' | 'needs-login';
|
|
146
|
+
readonly attempt?: number | undefined;
|
|
147
|
+
readonly message?: string | undefined;
|
|
148
|
+
readonly loginUrl?: string | undefined;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 跑一条流程要的那几件:流程、输入、动作表、取消信号、身份与进度回调。
|
|
152
|
+
* `sleep` / `now` 可注入,测试里用假时钟。
|
|
153
|
+
*/
|
|
154
|
+
export interface FlowRunOptions {
|
|
155
|
+
readonly flow: FlowDefinition;
|
|
156
|
+
readonly input: unknown;
|
|
157
|
+
readonly actions: FlowActionRegistry;
|
|
158
|
+
readonly signal: AbortSignal;
|
|
159
|
+
readonly runId: string;
|
|
160
|
+
readonly workspace: string;
|
|
161
|
+
readonly onStep: (event: FlowStepEvent) => void;
|
|
162
|
+
readonly sleep?: ((ms: number, signal: AbortSignal) => Promise<void>) | undefined;
|
|
163
|
+
readonly now?: (() => number) | undefined;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 一次跑的结果:成功、失败或停止;停止不是失败。
|
|
167
|
+
*/
|
|
168
|
+
export interface FlowResult {
|
|
169
|
+
readonly status: 'ok' | 'failed' | 'stopped';
|
|
170
|
+
readonly target: string;
|
|
171
|
+
readonly steps: Readonly<Record<string, unknown>>;
|
|
172
|
+
readonly failedStep?: string | undefined;
|
|
173
|
+
readonly error?: unknown;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 一条校验问题:指到具体路径,给 agent 或人直接改。
|
|
177
|
+
*/
|
|
178
|
+
export interface FlowProblem {
|
|
179
|
+
readonly path: string;
|
|
180
|
+
readonly problem: string;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* 校验结果:`ok` 为真表示没有 problems。
|
|
184
|
+
*/
|
|
185
|
+
export interface FlowCheck {
|
|
186
|
+
readonly ok: boolean;
|
|
187
|
+
readonly problems: readonly FlowProblem[];
|
|
188
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { configSyncAction, configSyncHandler } from './config-sync.js';
|
|
2
|
+
import { profileCreateAction, profileCreateHandler } from './profile-create.js';
|
|
3
|
+
export function createHostActions() {
|
|
4
|
+
return { configSyncAction, configSyncHandler, profileCreateAction, profileCreateHandler };
|
|
5
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { EnvSupplyPort } from './port-types.js';
|
|
2
|
+
import { type FlowHost } from './flow/kit.js';
|
|
3
|
+
import { sessionIdOf, sessionWorkspace } from './tool-kit.js';
|
|
4
|
+
/**
|
|
5
|
+
* 宿主 kit 的公共部分:flow、工具侧 session 读法、外部供给装法。
|
|
6
|
+
*/
|
|
7
|
+
export interface HostKit {
|
|
8
|
+
readonly flow: FlowHost;
|
|
9
|
+
readonly sessionIdOf: typeof sessionIdOf;
|
|
10
|
+
readonly sessionWorkspace: typeof sessionWorkspace;
|
|
11
|
+
readonly libInstallCommand: EnvSupplyPort['libInstallCommand'];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 建一份宿主 kit;每个挂载点一份,没有模块级状态。
|
|
15
|
+
* @returns flow / sessionIdOf / sessionWorkspace / libInstallCommand。
|
|
16
|
+
*/
|
|
17
|
+
export declare function createHostKit(parts: {
|
|
18
|
+
readonly libInstallCommand: EnvSupplyPort['libInstallCommand'];
|
|
19
|
+
}): HostKit;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createFlowHost } from './flow/kit.js';
|
|
2
|
+
import { sessionIdOf, sessionWorkspace } from './tool-kit.js';
|
|
3
|
+
/**
|
|
4
|
+
* 建一份宿主 kit;每个挂载点一份,没有模块级状态。
|
|
5
|
+
* @returns flow / sessionIdOf / sessionWorkspace / libInstallCommand。
|
|
6
|
+
*/
|
|
7
|
+
export function createHostKit(parts) {
|
|
8
|
+
return {
|
|
9
|
+
flow: createFlowHost(),
|
|
10
|
+
sessionIdOf,
|
|
11
|
+
sessionWorkspace,
|
|
12
|
+
libInstallCommand: parts.libInstallCommand,
|
|
13
|
+
};
|
|
14
|
+
}
|
package/dist/host/http.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DshHostContext } from '../api/dsh.js';
|
|
2
2
|
import { type PanelStateStore } from './panel-state.js';
|
|
3
3
|
import { type ProvisionHandler } from './provision.js';
|
|
4
|
+
import type { RuntimeStatus } from './adapter-types.js';
|
|
4
5
|
export type RouteHandler = (search: URLSearchParams) => Promise<unknown> | unknown;
|
|
5
6
|
export type RouteTable = Record<string, RouteHandler>;
|
|
6
7
|
export declare function flag(search: URLSearchParams, name: string): boolean;
|
|
@@ -32,12 +33,7 @@ export declare function preflightHandler(preflight: {
|
|
|
32
33
|
snapshot(): unknown;
|
|
33
34
|
}): RouteHandler;
|
|
34
35
|
export declare function capabilityHandler(probe: (unit: string | undefined, force: boolean) => Promise<unknown> | unknown): RouteHandler;
|
|
35
|
-
export
|
|
36
|
-
readonly command: string;
|
|
37
|
-
readonly source: 'env' | 'isolated' | 'path';
|
|
38
|
-
readonly isolated: string | null;
|
|
39
|
-
}
|
|
40
|
-
export declare function healthHandler(ctx: DshHostContext, opencli?: OpencliHealth): RouteHandler;
|
|
36
|
+
export declare function healthHandler(ctx: DshHostContext, runtimes?: Readonly<Record<string, RuntimeStatus>>): RouteHandler;
|
|
41
37
|
export declare function registerPrefs(ctx: DshHostContext, resources: readonly PostResource[], wrote?: ((resource: string, write: PostWrite) => void) | undefined): () => void;
|
|
42
38
|
export declare function registerActions(ctx: DshHostContext, resources: readonly PostResource[], wrote?: ((resource: string, write: PostWrite) => void) | undefined): () => void;
|
|
43
39
|
export declare function registerPanelState(ctx: DshHostContext, store: PanelStateStore): () => void;
|
package/dist/host/http.js
CHANGED
|
@@ -194,7 +194,7 @@ export function capabilityHandler(probe) {
|
|
|
194
194
|
return { ok: true, ...result };
|
|
195
195
|
};
|
|
196
196
|
}
|
|
197
|
-
export function healthHandler(ctx,
|
|
197
|
+
export function healthHandler(ctx, runtimes) {
|
|
198
198
|
return () => {
|
|
199
199
|
const profile = ctx.get('profileContext');
|
|
200
200
|
const dir = profile?.dir;
|
|
@@ -205,7 +205,7 @@ export function healthHandler(ctx, opencli) {
|
|
|
205
205
|
profileDir: dir ?? null,
|
|
206
206
|
pid: process.pid,
|
|
207
207
|
now: new Date().toISOString(),
|
|
208
|
-
...(
|
|
208
|
+
...(runtimes === undefined ? {} : { runtimes }),
|
|
209
209
|
};
|
|
210
210
|
};
|
|
211
211
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DshHostContext } from '../api/dsh.js';
|
|
2
|
+
import type { InstalledModule, ProfileModule, ProfilePackage } from './adapter-types.js';
|
|
3
|
+
export interface UnitDeclarationSource {
|
|
4
|
+
readonly pkg: string;
|
|
5
|
+
readonly declaration: unknown;
|
|
6
|
+
}
|
|
7
|
+
export declare function profileDirOf(ctx: DshHostContext): string | null;
|
|
8
|
+
export declare function profileManifestOf(profileDir: string | null): Readonly<Record<string, unknown>> | undefined;
|
|
9
|
+
export declare function profileModules(profileDir: string | null): readonly ProfileModule[];
|
|
10
|
+
export declare function createDeclarationLookup(profileDir: string | null): (unit: string) => UnitDeclarationSource | undefined;
|
|
11
|
+
export declare function moduleDeclarationOf(profileDir: string | null, pkg: string): unknown;
|
|
12
|
+
export interface DeclarationGap {
|
|
13
|
+
readonly pkg: string;
|
|
14
|
+
readonly problems: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
export declare function declarationGaps(profileDir: string | null): readonly DeclarationGap[] | undefined;
|
|
17
|
+
export declare function declaredPluginCommands(profileDir: string | null): ReadonlyMap<string, ReadonlySet<string>>;
|
|
18
|
+
export declare function readInstalledModule(profileDir: string | null, pkg: string): InstalledModule;
|
|
19
|
+
export declare function isPackedArtifactSpec(spec: string): boolean;
|
|
20
|
+
export declare function isLocalDependencySpec(spec: string): boolean;
|
|
21
|
+
export declare function isRegistryDependencySpec(spec: string): boolean;
|
|
22
|
+
export declare function readProfilePackage(profileDir: string | null, pkg: string): ProfilePackage | undefined;
|
|
23
|
+
export declare function packageVersionAt(packageRoot: string): string | undefined;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { declarationProblemsOf, readDeclaration } from '../contract/unit-declaration.js';
|
|
4
|
+
import { packageSourceOf } from './installed-sources.js';
|
|
5
|
+
import { scopeOf } from './adapter-types.js';
|
|
6
|
+
import { isPackageName } from './spec.js';
|
|
7
|
+
function asRecord(value) {
|
|
8
|
+
return typeof value === 'object' && value !== null ? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
function asText(value) {
|
|
11
|
+
return typeof value === 'string' ? value : '';
|
|
12
|
+
}
|
|
13
|
+
export function profileDirOf(ctx) {
|
|
14
|
+
const profile = ctx.get('profileContext');
|
|
15
|
+
const dir = typeof profile?.dir === 'string' ? profile.dir.trim() : '';
|
|
16
|
+
return dir === '' ? null : dir;
|
|
17
|
+
}
|
|
18
|
+
function stringsOf(value) {
|
|
19
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
|
|
20
|
+
}
|
|
21
|
+
export function profileManifestOf(profileDir) {
|
|
22
|
+
if (profileDir === null)
|
|
23
|
+
return undefined;
|
|
24
|
+
try {
|
|
25
|
+
return asRecord(JSON.parse(fs.readFileSync(path.join(profileDir, 'package.json'), 'utf8')));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function profileModules(profileDir) {
|
|
32
|
+
const record = profileManifestOf(profileDir);
|
|
33
|
+
if (record === undefined)
|
|
34
|
+
return [];
|
|
35
|
+
const bundles = stringsOf(asRecord(asRecord(record.dsh)?.profile)?.bundles);
|
|
36
|
+
const dependencies = asRecord(record.dependencies) ?? {};
|
|
37
|
+
const names = bundles.length > 0 ? bundles : Object.keys(dependencies);
|
|
38
|
+
const found = [];
|
|
39
|
+
for (const pkg of names) {
|
|
40
|
+
const read = readInstalledModule(profileDir, pkg);
|
|
41
|
+
if (read.kind === 'module') {
|
|
42
|
+
const spec = asText(dependencies[read.pkg]);
|
|
43
|
+
found.push({
|
|
44
|
+
pkg: read.pkg,
|
|
45
|
+
unit: read.unit,
|
|
46
|
+
version: read.version,
|
|
47
|
+
...(read.registry === undefined ? {} : { registry: read.registry }),
|
|
48
|
+
local: isLocalDependencySpec(spec),
|
|
49
|
+
fromRegistry: isRegistryDependencySpec(spec),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return found;
|
|
54
|
+
}
|
|
55
|
+
export function createDeclarationLookup(profileDir) {
|
|
56
|
+
let roster;
|
|
57
|
+
const scan = () => new Map(profileModules(profileDir).map((row) => [row.unit, row.pkg]));
|
|
58
|
+
return (unit) => {
|
|
59
|
+
roster ??= scan();
|
|
60
|
+
let pkg = roster.get(unit);
|
|
61
|
+
if (pkg === undefined) {
|
|
62
|
+
roster = scan();
|
|
63
|
+
pkg = roster.get(unit);
|
|
64
|
+
}
|
|
65
|
+
return pkg === undefined ? undefined : { pkg, declaration: moduleDeclarationOf(profileDir, pkg) };
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function registryFromLockfile(profileDir, pkg) {
|
|
69
|
+
let text;
|
|
70
|
+
try {
|
|
71
|
+
text = fs.readFileSync(path.join(profileDir, 'pnpm-lock.yaml'), 'utf8');
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const at = text.indexOf(` ${pkg}@`);
|
|
77
|
+
const quoted = at === -1 ? text.indexOf(` '${pkg}@`) : at;
|
|
78
|
+
if (quoted === -1)
|
|
79
|
+
return undefined;
|
|
80
|
+
const headerEnd = text.indexOf('\n', quoted);
|
|
81
|
+
const end = headerEnd === -1 ? -1 : text.indexOf('\n ', headerEnd + 1);
|
|
82
|
+
const block = text.slice(quoted, end === -1 ? quoted + 900 : end);
|
|
83
|
+
const match = /tarball:\s*([^\s,}]+)/.exec(block);
|
|
84
|
+
const tarball = match?.[1];
|
|
85
|
+
if (tarball === undefined || !tarball.startsWith('http'))
|
|
86
|
+
return undefined;
|
|
87
|
+
const cut = tarball.lastIndexOf(`/${pkg}/`);
|
|
88
|
+
if (cut <= 'https://'.length)
|
|
89
|
+
return undefined;
|
|
90
|
+
return tarball.slice(0, cut);
|
|
91
|
+
}
|
|
92
|
+
function unquoted(value) {
|
|
93
|
+
const quote = value.charAt(0);
|
|
94
|
+
if (value.length >= 2 && (quote === '"' || quote === "'") && value.endsWith(quote)) {
|
|
95
|
+
return value.slice(1, -1).trim();
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
function registryFromNpmrc(profileDir, pkg) {
|
|
100
|
+
let text;
|
|
101
|
+
try {
|
|
102
|
+
text = fs.readFileSync(path.join(profileDir, '.npmrc'), 'utf8');
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
const scope = scopeOf(pkg);
|
|
108
|
+
let fallback;
|
|
109
|
+
for (const line of text.split('\n')) {
|
|
110
|
+
const trimmed = line.trim();
|
|
111
|
+
if (trimmed === '' || trimmed.startsWith('#') || trimmed.startsWith(';'))
|
|
112
|
+
continue;
|
|
113
|
+
const separator = trimmed.indexOf('=');
|
|
114
|
+
if (separator <= 0)
|
|
115
|
+
continue;
|
|
116
|
+
const key = trimmed.slice(0, separator).trim();
|
|
117
|
+
const value = unquoted(trimmed.slice(separator + 1).trim());
|
|
118
|
+
if (value === '')
|
|
119
|
+
continue;
|
|
120
|
+
if (scope !== null && key === `${scope}:registry`)
|
|
121
|
+
return value;
|
|
122
|
+
if (key === 'registry')
|
|
123
|
+
fallback = value;
|
|
124
|
+
}
|
|
125
|
+
return fallback;
|
|
126
|
+
}
|
|
127
|
+
function registryOf(profileDir, pkg, manifest) {
|
|
128
|
+
const declared = asText(asRecord(manifest.publishConfig)?.registry);
|
|
129
|
+
if (declared !== '')
|
|
130
|
+
return declared;
|
|
131
|
+
const recorded = packageSourceOf(profileDir, pkg);
|
|
132
|
+
if (recorded !== undefined)
|
|
133
|
+
return recorded.registry;
|
|
134
|
+
const locked = registryFromLockfile(profileDir, pkg);
|
|
135
|
+
return locked === undefined ? registryFromNpmrc(profileDir, pkg) : locked;
|
|
136
|
+
}
|
|
137
|
+
export function moduleDeclarationOf(profileDir, pkg) {
|
|
138
|
+
if (profileDir === null || !isPackageName(pkg))
|
|
139
|
+
return undefined;
|
|
140
|
+
try {
|
|
141
|
+
const text = fs.readFileSync(path.join(profileDir, 'node_modules', pkg, 'package.json'), 'utf8');
|
|
142
|
+
return asRecord(asRecord(asRecord(JSON.parse(text))?.dsh)?.workbench);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
export function declarationGaps(profileDir) {
|
|
149
|
+
if (profileManifestOf(profileDir) === undefined)
|
|
150
|
+
return undefined;
|
|
151
|
+
const gaps = [];
|
|
152
|
+
for (const module of profileModules(profileDir)) {
|
|
153
|
+
const problems = declarationProblemsOf(module.pkg, moduleDeclarationOf(profileDir, module.pkg));
|
|
154
|
+
if (problems.length > 0)
|
|
155
|
+
gaps.push({ pkg: module.pkg, problems });
|
|
156
|
+
}
|
|
157
|
+
return gaps;
|
|
158
|
+
}
|
|
159
|
+
export function declaredPluginCommands(profileDir) {
|
|
160
|
+
const declared = new Map();
|
|
161
|
+
for (const module of profileModules(profileDir)) {
|
|
162
|
+
const read = readDeclaration(module.pkg, moduleDeclarationOf(profileDir, module.pkg));
|
|
163
|
+
for (const need of read.manifest?.needs ?? []) {
|
|
164
|
+
if (need.kind !== 'extension')
|
|
165
|
+
continue;
|
|
166
|
+
const name = need.name.trim();
|
|
167
|
+
if (name === '')
|
|
168
|
+
continue;
|
|
169
|
+
const commands = declared.get(name) ?? new Set();
|
|
170
|
+
for (const command of need.commands ?? [])
|
|
171
|
+
commands.add(command);
|
|
172
|
+
declared.set(name, commands);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return declared;
|
|
176
|
+
}
|
|
177
|
+
export function readInstalledModule(profileDir, pkg) {
|
|
178
|
+
if (profileDir === null || !isPackageName(pkg))
|
|
179
|
+
return { kind: 'unknown', pkg };
|
|
180
|
+
let manifest;
|
|
181
|
+
try {
|
|
182
|
+
manifest = JSON.parse(fs.readFileSync(path.join(profileDir, 'node_modules', pkg, 'package.json'), 'utf8'));
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return { kind: 'unknown', pkg };
|
|
186
|
+
}
|
|
187
|
+
const record = asRecord(manifest);
|
|
188
|
+
if (record === undefined)
|
|
189
|
+
return { kind: 'unknown', pkg };
|
|
190
|
+
const declared = asRecord(asRecord(record.dsh)?.workbench);
|
|
191
|
+
const unit = declared === undefined ? '' : asText(declared.unit);
|
|
192
|
+
if (unit === '')
|
|
193
|
+
return { kind: 'not-module', pkg };
|
|
194
|
+
const registry = registryOf(profileDir, pkg, record);
|
|
195
|
+
return {
|
|
196
|
+
kind: 'module',
|
|
197
|
+
pkg,
|
|
198
|
+
unit,
|
|
199
|
+
version: asText(record.version),
|
|
200
|
+
...(registry === undefined ? {} : { registry }),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const PACKED_ARTIFACT = /\.(?:tgz|tar\.gz|tar)$/i;
|
|
204
|
+
export function isPackedArtifactSpec(spec) {
|
|
205
|
+
const trimmed = spec.trim();
|
|
206
|
+
const pkgPath = trimmed.startsWith('file:') ? trimmed.slice('file:'.length) : trimmed;
|
|
207
|
+
return PACKED_ARTIFACT.test(pkgPath);
|
|
208
|
+
}
|
|
209
|
+
export function isLocalDependencySpec(spec) {
|
|
210
|
+
const trimmed = spec.trim();
|
|
211
|
+
if (isPackedArtifactSpec(trimmed))
|
|
212
|
+
return false;
|
|
213
|
+
return (trimmed.startsWith('link:') ||
|
|
214
|
+
trimmed.startsWith('file:') ||
|
|
215
|
+
trimmed.startsWith('./') ||
|
|
216
|
+
trimmed.startsWith('../') ||
|
|
217
|
+
trimmed.startsWith('/'));
|
|
218
|
+
}
|
|
219
|
+
const NON_REGISTRY_SPEC = /^(?:git\+|git:|github:|gitlab:|bitbucket:|https?:|ssh:|workspace:)/i;
|
|
220
|
+
export function isRegistryDependencySpec(spec) {
|
|
221
|
+
const trimmed = spec.trim();
|
|
222
|
+
if (trimmed === '')
|
|
223
|
+
return true;
|
|
224
|
+
if (isLocalDependencySpec(trimmed) || isPackedArtifactSpec(trimmed))
|
|
225
|
+
return false;
|
|
226
|
+
return !NON_REGISTRY_SPEC.test(trimmed);
|
|
227
|
+
}
|
|
228
|
+
export function readProfilePackage(profileDir, pkg) {
|
|
229
|
+
if (profileDir === null || !isPackageName(pkg))
|
|
230
|
+
return undefined;
|
|
231
|
+
let manifest;
|
|
232
|
+
try {
|
|
233
|
+
manifest = JSON.parse(fs.readFileSync(path.join(profileDir, 'node_modules', pkg, 'package.json'), 'utf8'));
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
const record = asRecord(manifest);
|
|
239
|
+
if (record === undefined)
|
|
240
|
+
return undefined;
|
|
241
|
+
const version = asText(record.version);
|
|
242
|
+
if (version === '')
|
|
243
|
+
return undefined;
|
|
244
|
+
const registry = registryOf(profileDir, pkg, record);
|
|
245
|
+
const dependencies = asRecord(profileManifestOf(profileDir)?.dependencies) ?? {};
|
|
246
|
+
const local = isLocalDependencySpec(asText(dependencies[pkg]));
|
|
247
|
+
return {
|
|
248
|
+
pkg,
|
|
249
|
+
version,
|
|
250
|
+
...(registry === undefined ? {} : { registry }),
|
|
251
|
+
...(local ? { local: true } : {}),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
export function packageVersionAt(packageRoot) {
|
|
255
|
+
let manifest;
|
|
256
|
+
try {
|
|
257
|
+
manifest = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
const version = asText(asRecord(manifest)?.version);
|
|
263
|
+
return version === '' ? undefined : version;
|
|
264
|
+
}
|
|
@@ -8,13 +8,11 @@ export declare function libInstallCommand(options: {
|
|
|
8
8
|
readonly source: NpmSource;
|
|
9
9
|
readonly version?: string | undefined;
|
|
10
10
|
readonly env?: NodeJS.ProcessEnv | undefined;
|
|
11
|
+
readonly prefix: string;
|
|
12
|
+
readonly cacheDir?: string | undefined;
|
|
13
|
+
}): string;
|
|
14
|
+
export declare function binInstallCommand(need: BinNeed, os: HostOs, env: NodeJS.ProcessEnv, prefix: string, cacheDir?: string): string | undefined;
|
|
15
|
+
export declare function libUninstallCommand(options: {
|
|
16
|
+
readonly need: LibNeed;
|
|
17
|
+
readonly prefix: string;
|
|
11
18
|
}): string;
|
|
12
|
-
/**
|
|
13
|
-
* 这个 bin 在这台机器上怎么装:作者写了 `install` 且平台对得上才给出那条命令。
|
|
14
|
-
* `command` 原样照抄;`npm` 装进隔离运行时的共用依赖前缀(那份包里的 bin 会自动上 PATH)。
|
|
15
|
-
* @param need 一条 bin 需要。
|
|
16
|
-
* @param os 宿主平台(由平台适配器给出)。
|
|
17
|
-
* @param env 取 registry 环境变量的地方。
|
|
18
|
-
* @returns 一条能直接粘进终端的命令;没声明或平台不对就是 undefined。
|
|
19
|
-
*/
|
|
20
|
-
export declare function binInstallCommand(need: BinNeed, os: HostOs, env?: NodeJS.ProcessEnv): string | undefined;
|
package/dist/host/lib-install.js
CHANGED
|
@@ -1,22 +1,14 @@
|
|
|
1
|
-
import { npmRegistryOf } from './
|
|
1
|
+
import { npmRegistryOf } from './npm-registry.js';
|
|
2
2
|
import { quoteShellArg } from './shell-text.js';
|
|
3
|
-
import { libsPrefix, npmCacheDir } from './paths.js';
|
|
4
3
|
export function libInstallCommand(options) {
|
|
5
4
|
const { need, source, version } = options;
|
|
6
5
|
const registry = npmRegistryOf(source, options.env ?? process.env);
|
|
7
6
|
const spec = version ?? need.version ?? 'latest';
|
|
8
7
|
const registryFlag = registry.ok ? ` --registry ${quoteShellArg(registry.value)}` : '';
|
|
9
|
-
|
|
8
|
+
const cacheFlag = options.cacheDir === undefined ? '' : ` --cache ${quoteShellArg(options.cacheDir)}`;
|
|
9
|
+
return `npm i --prefix ${quoteShellArg(options.prefix)} ${quoteShellArg(`${need.name}@${spec}`)}${registryFlag}${cacheFlag}`;
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
* 这个 bin 在这台机器上怎么装:作者写了 `install` 且平台对得上才给出那条命令。
|
|
13
|
-
* `command` 原样照抄;`npm` 装进隔离运行时的共用依赖前缀(那份包里的 bin 会自动上 PATH)。
|
|
14
|
-
* @param need 一条 bin 需要。
|
|
15
|
-
* @param os 宿主平台(由平台适配器给出)。
|
|
16
|
-
* @param env 取 registry 环境变量的地方。
|
|
17
|
-
* @returns 一条能直接粘进终端的命令;没声明或平台不对就是 undefined。
|
|
18
|
-
*/
|
|
19
|
-
export function binInstallCommand(need, os, env = process.env) {
|
|
11
|
+
export function binInstallCommand(need, os, env, prefix, cacheDir) {
|
|
20
12
|
const install = need.install;
|
|
21
13
|
if (install === undefined)
|
|
22
14
|
return undefined;
|
|
@@ -30,6 +22,8 @@ export function binInstallCommand(need, os, env = process.env) {
|
|
|
30
22
|
if (packageName === '')
|
|
31
23
|
return undefined;
|
|
32
24
|
return libInstallCommand({
|
|
25
|
+
prefix,
|
|
26
|
+
...(cacheDir === undefined ? {} : { cacheDir }),
|
|
33
27
|
need: { kind: 'lib', name: packageName, purpose: need.purpose },
|
|
34
28
|
source: {
|
|
35
29
|
kind: 'npm',
|
|
@@ -40,3 +34,6 @@ export function binInstallCommand(need, os, env = process.env) {
|
|
|
40
34
|
env,
|
|
41
35
|
});
|
|
42
36
|
}
|
|
37
|
+
export function libUninstallCommand(options) {
|
|
38
|
+
return `npm uninstall --prefix ${quoteShellArg(options.prefix)} ${quoteShellArg(options.need.name)}`;
|
|
39
|
+
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import type { LibReader } from '../contract/lib.js';
|
|
2
|
-
import type {
|
|
3
|
-
export
|
|
4
|
-
readonly opencli?: OpencliRunner | undefined;
|
|
5
|
-
}
|
|
2
|
+
import type { LibRunner } from '../contract/runner.js';
|
|
3
|
+
export type LibRuntimes = Readonly<Record<string, LibRunner>>;
|
|
6
4
|
interface Logger {
|
|
7
5
|
warn?(message: string): void;
|
|
8
6
|
}
|
|
9
7
|
/**
|
|
10
|
-
* 给声明了 `runs` 的 lib 注入「跑这个运行时」的执行器:库侧按约定暴露 `
|
|
8
|
+
* 给声明了 `runs` 的 lib 注入「跑这个运行时」的执行器:库侧按约定暴露 `set<Runtime>Runner`
|
|
11
9
|
* (运行时名字首字母大写 + `Runner`),外壳在每次 `load` 之后把执行器交给它。
|
|
12
10
|
*
|
|
13
11
|
* 没暴露的(版本旧)就保持它自己的行为(按 PATH 找),只记一条 warn;不熟悉的运行时同样只 warn。
|
package/dist/host/lib-runners.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
function runnerOf(runtimes, runtime) {
|
|
2
|
-
return runtime
|
|
2
|
+
return runtimes[runtime];
|
|
3
3
|
}
|
|
4
4
|
function setterNameOf(runtime) {
|
|
5
5
|
return `set${runtime.charAt(0).toUpperCase()}${runtime.slice(1)}Runner`;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
|
-
* 给声明了 `runs` 的 lib 注入「跑这个运行时」的执行器:库侧按约定暴露 `
|
|
8
|
+
* 给声明了 `runs` 的 lib 注入「跑这个运行时」的执行器:库侧按约定暴露 `set<Runtime>Runner`
|
|
9
9
|
* (运行时名字首字母大写 + `Runner`),外壳在每次 `load` 之后把执行器交给它。
|
|
10
10
|
*
|
|
11
11
|
* 没暴露的(版本旧)就保持它自己的行为(按 PATH 找),只记一条 warn;不熟悉的运行时同样只 warn。
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { localDirOf } from './spec.js';
|
|
2
|
+
export { localDirOf };
|
|
3
|
+
export declare function packageManifestOf(dir: string): Readonly<Record<string, unknown>> | undefined;
|
|
4
|
+
export declare function dshRecordOf(dir: string): unknown | undefined;
|
|
5
|
+
export declare function packageNameOf(dir: string): string | undefined;
|
|
6
|
+
export declare function pathDeclarationOf(spec: string): unknown | undefined;
|
|
7
|
+
export declare function declarationRefusalOf(name: string, raw: unknown): readonly string[];
|