@dsh-workbench/shared 1.0.1 → 1.1.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/dist/contract/protocol/bundle-install.d.ts +9 -1
- package/dist/contract/protocol/market-index.d.ts +1 -0
- package/dist/contract/protocol/market-index.js +1 -0
- package/dist/contract/unit-declaration.d.ts +11 -5
- package/dist/contract/unit-declaration.js +43 -17
- package/dist/contract/unit.d.ts +18 -3
- package/dist/contract/unit.js +8 -0
- package/dist/contract/workbench-contract.d.ts +1 -1
- package/dist/host/provision.d.ts +1 -1
- package/dist/host/provision.js +8 -2
- package/dist/host/supply-index.d.ts +4 -1
- package/dist/host/supply-index.js +18 -12
- package/dist/i18n/en.js +54 -32
- package/dist/i18n/index.d.ts +56 -34
- package/dist/i18n/zh.d.ts +55 -33
- package/dist/i18n/zh.js +55 -33
- package/dist/lib/dsh-client.d.ts +3 -1
- package/dist/testing/needs.js +5 -2
- package/package.json +1 -1
- package/src/contract/__tests__/unit-declaration.test.ts +34 -1
- package/src/contract/__tests__/unit.test.ts +1 -1
- package/src/contract/protocol/bundle-install.ts +11 -1
- package/src/contract/protocol/market-index.ts +1 -0
- package/src/contract/unit-declaration.ts +53 -22
- package/src/contract/unit.ts +22 -3
- package/src/contract/workbench-contract.ts +3 -1
- package/src/host/__tests__/provision.test.ts +2 -0
- package/src/host/__tests__/supply-index.test.ts +42 -6
- package/src/host/provision.ts +6 -2
- package/src/host/supply-index.ts +40 -11
- package/src/i18n/en.ts +54 -32
- package/src/i18n/zh.ts +55 -33
- package/src/lib/dsh-client.ts +3 -1
- package/src/testing/__tests__/declaration.test.ts +7 -3
- package/src/testing/needs.ts +5 -2
|
@@ -4,6 +4,7 @@ export const MARKET_DISCOVER_ROUTE = 'panels/market-discover';
|
|
|
4
4
|
export const MARKET_DOC_ROUTE = 'panels/market-doc';
|
|
5
5
|
export const MARKET_PUBLISH_PREVIEW_ROUTE = 'panels/market-publish-preview';
|
|
6
6
|
export const MARKET_SOURCES_RESOURCE = 'market-sources';
|
|
7
|
+
export const MARKET_SOURCES_ROUTE = 'panels/market-sources';
|
|
7
8
|
export const MARKET_WORKBENCH_LABEL = 'workbench';
|
|
8
9
|
|
|
9
10
|
export type MarketSourceKind = 'http' | 'git';
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { UnitHostNeeds, UnitManifest } from '../contract/unit.js';
|
|
1
|
+
import type { UnitHostNeeds, UnitManifest, UnitTitle } from '../contract/unit.js';
|
|
2
|
+
import { unitTitleKey } from '../contract/unit.js';
|
|
2
3
|
import { needsProblemsOf, type Need } from './needs.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* 声明协议当前版本;模块清单的 dsh.workbench.apiVersion 要写这个整数。
|
|
6
7
|
*/
|
|
7
|
-
export const WORKBENCH_API_VERSION =
|
|
8
|
+
export const WORKBENCH_API_VERSION = 3;
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* 外壳愿意认的协议版本;不在表里的模块当场被拒,不猜语义。
|
|
@@ -12,10 +13,9 @@ export const WORKBENCH_API_VERSION = 2;
|
|
|
12
13
|
export const SUPPORTED_API_VERSIONS: readonly number[] = [WORKBENCH_API_VERSION];
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
+
* 清单里那半张卡片声明:只剩排序位;卡片默认显不显示不在这里(那是运行期面板配置的事)。
|
|
16
17
|
*/
|
|
17
18
|
export interface UnitCardDeclaration {
|
|
18
|
-
readonly titleKey: string;
|
|
19
19
|
readonly order: number;
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -29,11 +29,12 @@ export interface UnitToolDeclaration {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* 模块清单里 dsh.workbench 那一段的完整形状(协议版本、单元 id
|
|
32
|
+
* 模块清单里 dsh.workbench 那一段的完整形状(协议版本、单元 id、名字、卡片、工具、宿主与环境需要)。
|
|
33
33
|
*/
|
|
34
34
|
export interface UnitDeclaration {
|
|
35
35
|
readonly apiVersion: number;
|
|
36
36
|
readonly unit: string;
|
|
37
|
+
readonly title: UnitTitle;
|
|
37
38
|
readonly card: UnitCardDeclaration | null;
|
|
38
39
|
readonly tools: readonly UnitToolDeclaration[];
|
|
39
40
|
readonly host: UnitHostNeeds;
|
|
@@ -46,6 +47,7 @@ export interface UnitDeclaration {
|
|
|
46
47
|
export const DECLARATION_KEYS: readonly string[] = [
|
|
47
48
|
'apiVersion',
|
|
48
49
|
'unit',
|
|
50
|
+
'title',
|
|
49
51
|
'card',
|
|
50
52
|
'tools',
|
|
51
53
|
'host',
|
|
@@ -58,12 +60,14 @@ export const DECLARATION_KEYS: readonly string[] = [
|
|
|
58
60
|
export const DECLARATION_REQUIRED_KEYS: readonly string[] = [
|
|
59
61
|
'apiVersion',
|
|
60
62
|
'unit',
|
|
63
|
+
'title',
|
|
61
64
|
'card',
|
|
62
65
|
'tools',
|
|
63
66
|
'host',
|
|
64
67
|
];
|
|
65
68
|
|
|
66
|
-
const CARD_KEYS = ['
|
|
69
|
+
const CARD_KEYS = ['order'];
|
|
70
|
+
const TITLE_KEYS = ['zh', 'en'];
|
|
67
71
|
const TOOL_KEYS = ['name', 'writes', 'key'];
|
|
68
72
|
|
|
69
73
|
const TOOL_KEY_SHAPE = /^[a-z][a-zA-Z0-9]*$/;
|
|
@@ -77,6 +81,17 @@ function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
|
77
81
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
/**
|
|
85
|
+
* 读一份声明里的协议版本;不是对象或 apiVersion 不是整数时给 undefined。
|
|
86
|
+
* @param raw 清单里 dsh.workbench 那段原始值。
|
|
87
|
+
* @returns 声明声明的协议版本,读不出来时 undefined。
|
|
88
|
+
*/
|
|
89
|
+
export function declarationApiVersionOf(raw: unknown): number | undefined {
|
|
90
|
+
if (!isRecord(raw)) return undefined;
|
|
91
|
+
const version = raw.apiVersion;
|
|
92
|
+
return Number.isInteger(version) ? (version as number) : undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
80
95
|
function outsideKeys(value: Readonly<Record<string, unknown>>, allowed: readonly string[]): string[] {
|
|
81
96
|
return Object.keys(value).filter((key) => !allowed.includes(key));
|
|
82
97
|
}
|
|
@@ -110,10 +125,9 @@ export function declarationProblemsOf(subject: string, raw: unknown): readonly s
|
|
|
110
125
|
}
|
|
111
126
|
if (!Number.isInteger(version) || !SUPPORTED_API_VERSIONS.includes(version as number)) {
|
|
112
127
|
const hint =
|
|
113
|
-
version ===
|
|
114
|
-
?
|
|
115
|
-
'
|
|
116
|
-
'再把 apiVersion 改成 2'
|
|
128
|
+
version === 2
|
|
129
|
+
? ' —— v3 把卡片标题挪成了顶层 `title`(`{ "zh": "…", "en": "…" }` 字面量):' +
|
|
130
|
+
'把 `card.titleKey` 删掉、在顶层写 title,再把 apiVersion 改成 3'
|
|
117
131
|
: '';
|
|
118
132
|
return [
|
|
119
133
|
`${subject}: dsh.workbench.apiVersion 是 ${JSON.stringify(version)},这里只认 ` +
|
|
@@ -140,6 +154,29 @@ export function declarationProblemsOf(subject: string, raw: unknown): readonly s
|
|
|
140
154
|
}
|
|
141
155
|
}
|
|
142
156
|
|
|
157
|
+
const title = raw.title;
|
|
158
|
+
if (title !== undefined) {
|
|
159
|
+
if (!isRecord(title)) {
|
|
160
|
+
problems.push(
|
|
161
|
+
`${subject}: dsh.workbench.title 要是对象(中英各一份),现在是 ${JSON.stringify(title)}`,
|
|
162
|
+
);
|
|
163
|
+
} else {
|
|
164
|
+
const extra = outsideKeys(title, TITLE_KEYS);
|
|
165
|
+
if (extra.length > 0) {
|
|
166
|
+
problems.push(
|
|
167
|
+
`${subject}: dsh.workbench.title 里有不认的键 ${listOf(extra)} —— title 只有 ${listOf(TITLE_KEYS)}`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
for (const language of TITLE_KEYS) {
|
|
171
|
+
if (text(title[language]) === '') {
|
|
172
|
+
problems.push(
|
|
173
|
+
`${subject}: dsh.workbench.title.${language} 要是非空字符串(市场和面板里显示的名字)`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
143
180
|
const card = raw.card;
|
|
144
181
|
if (card !== undefined && card !== null) {
|
|
145
182
|
if (!isRecord(card)) {
|
|
@@ -149,12 +186,9 @@ export function declarationProblemsOf(subject: string, raw: unknown): readonly s
|
|
|
149
186
|
if (extra.length > 0) {
|
|
150
187
|
problems.push(
|
|
151
188
|
`${subject}: dsh.workbench.card 里有不认的键 ${listOf(extra)} —— card 只有 ${listOf(CARD_KEYS)}` +
|
|
152
|
-
|
|
189
|
+
`(名字写在顶层的 title 里;卡片默认显不显示由运行期的面板配置决定,清单里没有这一位)`,
|
|
153
190
|
);
|
|
154
191
|
}
|
|
155
|
-
if (text(card.titleKey) === '') {
|
|
156
|
-
problems.push(`${subject}: dsh.workbench.card.titleKey 要是非空字符串(面板那一格的文案键)`);
|
|
157
|
-
}
|
|
158
192
|
if (typeof card.order !== 'number' || !Number.isFinite(card.order)) {
|
|
159
193
|
problems.push(`${subject}: dsh.workbench.card.order 要是数字,现在是 ${JSON.stringify(card.order)}`);
|
|
160
194
|
}
|
|
@@ -257,18 +291,15 @@ export function readDeclaration(subject: string, raw: unknown): DeclarationRead
|
|
|
257
291
|
const needsRead = needsProblemsOf(subject, declaration.needs);
|
|
258
292
|
if (needsRead.errors.length > 0) return { problems: needsRead.errors, warnings: needsRead.warnings };
|
|
259
293
|
const card = declaration.card;
|
|
294
|
+
const id = text(declaration.unit);
|
|
260
295
|
return {
|
|
261
296
|
declaration,
|
|
262
297
|
problems: [],
|
|
263
298
|
manifest: {
|
|
264
|
-
id
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
: {
|
|
269
|
-
titleKey: text(card.titleKey),
|
|
270
|
-
order: card.order,
|
|
271
|
-
},
|
|
299
|
+
id,
|
|
300
|
+
title: { zh: text(declaration.title.zh), en: text(declaration.title.en) },
|
|
301
|
+
titleKey: unitTitleKey(id),
|
|
302
|
+
card: card === null ? null : { order: card.order },
|
|
272
303
|
tools: declaration.tools.map((tool) => ({
|
|
273
304
|
name: text(tool.name),
|
|
274
305
|
writes: tool.writes,
|
package/src/contract/unit.ts
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import type { ExtensionNeed, LibNeed, Need } from './needs.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* 模块的人类可读名字:中英各一份,写在 package.json 的 dsh.workbench.title 里。
|
|
5
|
+
*/
|
|
6
|
+
export interface UnitTitle {
|
|
7
|
+
readonly zh: string;
|
|
8
|
+
readonly en: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 外壳替模块名合成的词表键:模块自己不必写名字键,名字直接从清单里的 title 注册进来。
|
|
13
|
+
* @param unit 单元 id。
|
|
14
|
+
* @returns 该单元名字在词表里的键。
|
|
15
|
+
*/
|
|
16
|
+
export function unitTitleKey(unit: string): string {
|
|
17
|
+
return `dsh-workbench.unit.${unit}.title`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 一张卡片在清单里的静态声明:只剩排序位;显不显示由运行期面板配置定。
|
|
5
22
|
*/
|
|
6
23
|
export interface UnitCard {
|
|
7
|
-
readonly titleKey: string;
|
|
8
24
|
readonly order: number;
|
|
9
25
|
}
|
|
10
26
|
|
|
@@ -33,10 +49,13 @@ export interface PluginRelease {
|
|
|
33
49
|
}
|
|
34
50
|
|
|
35
51
|
/**
|
|
36
|
-
* 装配期用的模块清单:单元 id
|
|
52
|
+
* 装配期用的模块清单:单元 id、名字、名字键、卡片、工具、宿主服务与环境需要,比声明少一层协议包装。
|
|
53
|
+
* titleKey 恒等于 unitTitleKey(id):有没有卡片都指向同一个词表键,展示方照它取名字。
|
|
37
54
|
*/
|
|
38
55
|
export interface UnitManifest {
|
|
39
56
|
readonly id: string;
|
|
57
|
+
readonly title?: UnitTitle;
|
|
58
|
+
readonly titleKey: string;
|
|
40
59
|
readonly card: UnitCard | null;
|
|
41
60
|
readonly tools: readonly UnitTool[];
|
|
42
61
|
readonly host: UnitHostNeeds;
|
|
@@ -2,12 +2,16 @@ import { describe, expect, it } from 'vitest';
|
|
|
2
2
|
import type { ExtensionNeed, LibNeed, SupplySource } from '../../contract/needs.js';
|
|
3
3
|
import { buildSupplyIndex, type SupplyDeclaration } from '../supply-index.js';
|
|
4
4
|
|
|
5
|
-
function npm(
|
|
5
|
+
function npm(
|
|
6
|
+
pkg?: string,
|
|
7
|
+
registryEnv = 'DSH_WB_REGISTRY',
|
|
8
|
+
registryDefault = 'https://registry.invalid/',
|
|
9
|
+
): SupplySource {
|
|
6
10
|
return {
|
|
7
11
|
kind: 'npm',
|
|
8
12
|
...(pkg === undefined ? {} : { package: pkg }),
|
|
9
13
|
registryEnv,
|
|
10
|
-
registryDefault
|
|
14
|
+
registryDefault,
|
|
11
15
|
};
|
|
12
16
|
}
|
|
13
17
|
|
|
@@ -77,23 +81,55 @@ describe('供给索引:一个包只有一个来源', () => {
|
|
|
77
81
|
expect(index.sourcesOf('extension', 'coding')).toEqual([npm('opencli-plugin-coding')]);
|
|
78
82
|
});
|
|
79
83
|
|
|
80
|
-
it('★★ 共用库两家用不同的 registryEnv
|
|
84
|
+
it('★★ 共用库两家用不同的 registryEnv、解析到同一个地址:收成一家,不算冲突', () => {
|
|
81
85
|
const index = buildSupplyIndex([
|
|
82
86
|
manifest('argocd', [lib('opencli-workflow', [npm(undefined, 'DSH_WORKBENCH_ARGOCD_REGISTRY')])]),
|
|
83
87
|
manifest('cmdb', [lib('opencli-workflow', [npm(undefined, 'DSH_WORKBENCH_CMDB_REGISTRY')])]),
|
|
84
88
|
]);
|
|
85
89
|
|
|
90
|
+
expect(index.conflicts).toEqual([]);
|
|
91
|
+
expect(index.entryOf('lib', 'opencli-workflow')?.conflicted).toBe(false);
|
|
92
|
+
expect(index.entryOf('lib', 'opencli-workflow')?.declarers).toEqual(['argocd', 'cmdb']);
|
|
93
|
+
expect(index.sourcesOf('lib', 'opencli-workflow')).toHaveLength(1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('★★ registryEnv 名不同、其中一家被环境变量指到别处:判冲突', () => {
|
|
97
|
+
const index = buildSupplyIndex(
|
|
98
|
+
[
|
|
99
|
+
manifest('jenkins', [lib('opencli-workflow', [npm(undefined, 'DSH_WB_JENKINS_REGISTRY')])]),
|
|
100
|
+
manifest('argocd', [lib('opencli-workflow', [npm(undefined, 'DSH_WB_ARGOCD_REGISTRY')])]),
|
|
101
|
+
],
|
|
102
|
+
{ env: { DSH_WB_JENKINS_REGISTRY: 'https://mirror.invalid/' } },
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
expect(index.conflicts.map((item) => item.kind)).toEqual(['source-mismatch']);
|
|
106
|
+
expect(index.sourcesOf('lib', 'opencli-workflow')).toEqual([]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('★★ 两家默认源就不同:判冲突,哪怕 registryEnv 同名', () => {
|
|
110
|
+
const index = buildSupplyIndex([
|
|
111
|
+
manifest('a', [lib('opencli-workflow', [npm(undefined, 'DSH_WB_REGISTRY', 'https://r-a.invalid/')])]),
|
|
112
|
+
manifest('b', [lib('opencli-workflow', [npm(undefined, 'DSH_WB_REGISTRY', 'https://r-b.invalid/')])]),
|
|
113
|
+
]);
|
|
114
|
+
|
|
86
115
|
expect(index.conflicts).toHaveLength(1);
|
|
87
|
-
expect(index.conflicts[0]?.kind).toBe('source-mismatch');
|
|
88
116
|
expect(index.entryOf('lib', 'opencli-workflow')?.conflicted).toBe(true);
|
|
89
|
-
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('★ 两家都没写默认源、registryEnv 又不同:解析不出地址,按名字判冲突', () => {
|
|
120
|
+
const index = buildSupplyIndex([
|
|
121
|
+
manifest('a', [lib('opencli-workflow', [{ kind: 'npm', registryEnv: 'DSH_WB_A_REGISTRY' }])]),
|
|
122
|
+
manifest('b', [lib('opencli-workflow', [{ kind: 'npm', registryEnv: 'DSH_WB_B_REGISTRY' }])]),
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
expect(index.conflicts).toHaveLength(1);
|
|
90
126
|
});
|
|
91
127
|
|
|
92
128
|
it('★★ 三家三个源:同一个包只报一条冲突,declarers 合并齐', () => {
|
|
93
129
|
const index = buildSupplyIndex([
|
|
94
130
|
manifest('a', [lib('opencli-workflow', [npm(undefined, 'DSH_WB_A_REGISTRY')])]),
|
|
95
131
|
manifest('b', [lib('opencli-workflow', [npm(undefined, 'DSH_WB_B_REGISTRY')])]),
|
|
96
|
-
manifest('c', [lib('opencli-workflow', [npm(undefined, 'DSH_WB_C_REGISTRY')])]),
|
|
132
|
+
manifest('c', [lib('opencli-workflow', [npm(undefined, 'DSH_WB_C_REGISTRY', 'https://mirror.invalid/')])]),
|
|
97
133
|
]);
|
|
98
134
|
|
|
99
135
|
expect(index.conflicts).toHaveLength(1);
|
package/src/host/provision.ts
CHANGED
|
@@ -15,6 +15,8 @@ export const PROVISION_ACTIONS = [
|
|
|
15
15
|
'update-plugin',
|
|
16
16
|
'uninstall-plugin',
|
|
17
17
|
'update-workbench',
|
|
18
|
+
'migrate-workbench',
|
|
19
|
+
'dismiss-followup',
|
|
18
20
|
] as const satisfies readonly ProvisionAction[];
|
|
19
21
|
|
|
20
22
|
export const DEPENDENCY_ACTIONS = ['update-dependency'] as const satisfies readonly ProvisionAction[];
|
|
@@ -37,7 +39,8 @@ export function provisionTargetOf(action: ProvisionAction): ProvisionTarget {
|
|
|
37
39
|
if (action === 'install-bundle' || action === 'install-version') return 'spec';
|
|
38
40
|
if (action === 'remove-bundle') return 'name';
|
|
39
41
|
if (action === 'update-plugin' || action === 'uninstall-plugin') return 'name';
|
|
40
|
-
if (action === 'update-workbench') return 'name';
|
|
42
|
+
if (action === 'update-workbench' || action === 'migrate-workbench') return 'name';
|
|
43
|
+
if (action === 'dismiss-followup') return 'id';
|
|
41
44
|
if (action === 'cancel-install') return 'requestId';
|
|
42
45
|
return 'unit';
|
|
43
46
|
}
|
|
@@ -81,7 +84,8 @@ function requestOf(action: ProvisionAction, value: string): ProvisionRequest {
|
|
|
81
84
|
if (action === 'install-bundle' || action === 'install-version') return { action, spec: value };
|
|
82
85
|
if (action === 'remove-bundle') return { action, name: value };
|
|
83
86
|
if (action === 'update-plugin' || action === 'uninstall-plugin') return { action, name: value };
|
|
84
|
-
if (action === 'update-workbench') return { action, name: value };
|
|
87
|
+
if (action === 'update-workbench' || action === 'migrate-workbench') return { action, name: value };
|
|
88
|
+
if (action === 'dismiss-followup') return { action, id: value };
|
|
85
89
|
if (action === 'cancel-install') return { action, requestId: value };
|
|
86
90
|
return { action, unit: value };
|
|
87
91
|
}
|
package/src/host/supply-index.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { ExtensionNeed, LibNeed, Need, SupplySource } from '../contract/needs.js';
|
|
2
|
+
import { npmRegistryOf } from './plugin-source.js';
|
|
2
3
|
|
|
3
4
|
export interface SupplyDeclaration {
|
|
4
5
|
readonly id: string;
|
|
5
6
|
readonly needs?: readonly Need[];
|
|
6
7
|
}
|
|
7
8
|
|
|
9
|
+
export interface SupplyIndexOptions {
|
|
10
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
export interface SupplyEntry {
|
|
9
14
|
readonly key: string;
|
|
10
15
|
readonly kind: 'extension' | 'lib';
|
|
@@ -81,16 +86,28 @@ function keyOf(kind: 'extension' | 'lib', pkg: string): string {
|
|
|
81
86
|
return `${kind}:${pkg}`;
|
|
82
87
|
}
|
|
83
88
|
|
|
84
|
-
function
|
|
89
|
+
function registryAddressOf(
|
|
90
|
+
source: Extract<SupplySource, { kind: 'npm' }>,
|
|
91
|
+
env: Readonly<Record<string, string | undefined>>,
|
|
92
|
+
): string | undefined {
|
|
93
|
+
const resolved = npmRegistryOf(source, env);
|
|
94
|
+
return resolved.ok ? resolved.value : undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function sourceCompatible(
|
|
98
|
+
left: SupplySource,
|
|
99
|
+
right: SupplySource,
|
|
100
|
+
env: Readonly<Record<string, string | undefined>>,
|
|
101
|
+
): boolean {
|
|
85
102
|
if (left.kind !== right.kind) return false;
|
|
86
103
|
if (left.kind === 'npm' && right.kind === 'npm') {
|
|
87
104
|
if (text(left.package) !== '' && text(right.package) !== '' && text(left.package) !== text(right.package)) {
|
|
88
105
|
return false;
|
|
89
106
|
}
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
return
|
|
107
|
+
const l = registryAddressOf(left, env);
|
|
108
|
+
const r = registryAddressOf(right, env);
|
|
109
|
+
if (l !== undefined && r !== undefined) return l === r;
|
|
110
|
+
return text(left.registryEnv) === text(right.registryEnv);
|
|
94
111
|
}
|
|
95
112
|
if (left.kind === 'git' && right.kind === 'git') return text(left.url) === text(right.url);
|
|
96
113
|
if (left.kind === 'local' && right.kind === 'local') {
|
|
@@ -99,11 +116,15 @@ function sourceCompatible(left: SupplySource, right: SupplySource): boolean {
|
|
|
99
116
|
return false;
|
|
100
117
|
}
|
|
101
118
|
|
|
102
|
-
function sourcesCompatible(
|
|
119
|
+
function sourcesCompatible(
|
|
120
|
+
left: readonly SupplySource[],
|
|
121
|
+
right: readonly SupplySource[],
|
|
122
|
+
env: Readonly<Record<string, string | undefined>>,
|
|
123
|
+
): boolean {
|
|
103
124
|
if (left.length !== right.length) return false;
|
|
104
125
|
return left.every((source, index) => {
|
|
105
126
|
const other = right[index];
|
|
106
|
-
return other !== undefined && sourceCompatible(source, other);
|
|
127
|
+
return other !== undefined && sourceCompatible(source, other, env);
|
|
107
128
|
});
|
|
108
129
|
}
|
|
109
130
|
|
|
@@ -120,13 +141,17 @@ function mergedSource(left: SupplySource, right: SupplySource): SupplySource {
|
|
|
120
141
|
return left;
|
|
121
142
|
}
|
|
122
143
|
|
|
123
|
-
function mergeSources(
|
|
144
|
+
function mergeSources(
|
|
145
|
+
entry: MutableEntry,
|
|
146
|
+
sources: readonly SupplySource[],
|
|
147
|
+
env: Readonly<Record<string, string | undefined>>,
|
|
148
|
+
): boolean {
|
|
124
149
|
if (!entry.declared) {
|
|
125
150
|
entry.declared = true;
|
|
126
151
|
entry.sources = [...sources];
|
|
127
152
|
return true;
|
|
128
153
|
}
|
|
129
|
-
if (!sourcesCompatible(entry.sources, sources)) {
|
|
154
|
+
if (!sourcesCompatible(entry.sources, sources, env)) {
|
|
130
155
|
entry.conflicted = true;
|
|
131
156
|
return false;
|
|
132
157
|
}
|
|
@@ -150,7 +175,11 @@ function addDeclarer(entry: MutableEntry, unit: string, commands: readonly strin
|
|
|
150
175
|
for (const command of commands) if (!entry.commands.includes(command)) entry.commands.push(command);
|
|
151
176
|
}
|
|
152
177
|
|
|
153
|
-
export function buildSupplyIndex(
|
|
178
|
+
export function buildSupplyIndex(
|
|
179
|
+
declarations: readonly SupplyDeclaration[],
|
|
180
|
+
options: SupplyIndexOptions = {},
|
|
181
|
+
): SupplyIndex {
|
|
182
|
+
const env = options.env ?? {};
|
|
154
183
|
const entries = new Map<string, MutableEntry>();
|
|
155
184
|
const byName = new Map<string, MutableEntry>();
|
|
156
185
|
const conflicts: SupplyConflict[] = [];
|
|
@@ -186,7 +215,7 @@ export function buildSupplyIndex(declarations: readonly SupplyDeclaration[]): Su
|
|
|
186
215
|
} else {
|
|
187
216
|
addDeclarer(entry, declaration.id, []);
|
|
188
217
|
}
|
|
189
|
-
const same = mergeSources(entry, need.sources ?? []);
|
|
218
|
+
const same = mergeSources(entry, need.sources ?? [], env);
|
|
190
219
|
if (!same) {
|
|
191
220
|
conflicts.push({
|
|
192
221
|
kind: 'source-mismatch',
|