@workbench-kit/workbench-config 0.0.2-prototype.0.1.4 → 0.0.2-prototype.0.2.11
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/package.json +3 -3
- package/src/index.ts +167 -23
- package/src/keybindings-config.ts +68 -0
- package/src/parse-helpers.ts +46 -0
- package/src/preference-scopes.ts +65 -0
- package/src/settings-config.ts +23 -0
- package/src/user-commands-config.ts +122 -0
- package/src/validation-error.ts +6 -0
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workbench-kit/workbench-config",
|
|
3
|
-
"version": "0.0.2-prototype.0.
|
|
3
|
+
"version": "0.0.2-prototype.0.2.11",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.ts"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@workbench-kit/base": "0.0.2-prototype.0.
|
|
11
|
-
"@workbench-kit/platform": "0.0.2-prototype.0.
|
|
10
|
+
"@workbench-kit/base": "0.0.2-prototype.0.2.11",
|
|
11
|
+
"@workbench-kit/platform": "0.0.2-prototype.0.2.11"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"src",
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { assertKnownKeys, assertRecord } from './parse-helpers.js';
|
|
2
|
+
import { WorkbenchConfigValidationError } from './validation-error.js';
|
|
3
|
+
|
|
1
4
|
export const WORKBENCH_KIT_WORKBENCH_CONFIG_VERSION = '0.0.0' as const;
|
|
2
5
|
|
|
3
6
|
export const WORKBENCH_CONFIG_DIR = '.workbench' as const;
|
|
@@ -6,6 +9,7 @@ export type WorkbenchConfigFileName =
|
|
|
6
9
|
| 'workspace.json'
|
|
7
10
|
| 'settings.json'
|
|
8
11
|
| 'keybindings.json'
|
|
12
|
+
| 'user-commands.json'
|
|
9
13
|
| 'extensions.json'
|
|
10
14
|
| 'extensions.lock.json'
|
|
11
15
|
| 'layout.default.json'
|
|
@@ -16,8 +20,23 @@ export interface WorkbenchExtensionsConfig {
|
|
|
16
20
|
recommendations: readonly string[];
|
|
17
21
|
}
|
|
18
22
|
|
|
23
|
+
export interface WorkbenchExtensionsLockEntry {
|
|
24
|
+
readonly integrity?: string | undefined;
|
|
25
|
+
readonly version: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface WorkbenchExtensionsLock {
|
|
29
|
+
readonly extensions: Readonly<Record<string, WorkbenchExtensionsLockEntry>>;
|
|
30
|
+
readonly lockfileVersion: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
export interface WorkbenchLayoutConfig {
|
|
20
34
|
readonly activityBar: {
|
|
35
|
+
readonly hiddenItemIds?: readonly string[];
|
|
36
|
+
readonly itemOrder?: readonly string[];
|
|
37
|
+
readonly visible: boolean;
|
|
38
|
+
};
|
|
39
|
+
readonly auxiliaryBar: {
|
|
21
40
|
readonly visible: boolean;
|
|
22
41
|
};
|
|
23
42
|
readonly panel: {
|
|
@@ -25,12 +44,14 @@ export interface WorkbenchLayoutConfig {
|
|
|
25
44
|
};
|
|
26
45
|
readonly sideBar: {
|
|
27
46
|
readonly activeViewContainer?: string;
|
|
47
|
+
readonly sizePercent?: number;
|
|
28
48
|
readonly visible: boolean;
|
|
29
49
|
};
|
|
30
50
|
}
|
|
31
51
|
|
|
32
52
|
export type WorkbenchLayoutConfigInput = Partial<{
|
|
33
53
|
activityBar: Partial<WorkbenchLayoutConfig['activityBar']>;
|
|
54
|
+
auxiliaryBar: Partial<WorkbenchLayoutConfig['auxiliaryBar']>;
|
|
34
55
|
panel: Partial<WorkbenchLayoutConfig['panel']>;
|
|
35
56
|
sideBar: Partial<WorkbenchLayoutConfig['sideBar']>;
|
|
36
57
|
}>;
|
|
@@ -39,6 +60,9 @@ export const DEFAULT_WORKBENCH_LAYOUT_CONFIG: WorkbenchLayoutConfig = {
|
|
|
39
60
|
activityBar: {
|
|
40
61
|
visible: true,
|
|
41
62
|
},
|
|
63
|
+
auxiliaryBar: {
|
|
64
|
+
visible: false,
|
|
65
|
+
},
|
|
42
66
|
panel: {
|
|
43
67
|
visible: false,
|
|
44
68
|
},
|
|
@@ -47,19 +71,14 @@ export const DEFAULT_WORKBENCH_LAYOUT_CONFIG: WorkbenchLayoutConfig = {
|
|
|
47
71
|
},
|
|
48
72
|
};
|
|
49
73
|
|
|
50
|
-
export
|
|
51
|
-
constructor(message: string) {
|
|
52
|
-
super(message);
|
|
53
|
-
this.name = 'WorkbenchConfigValidationError';
|
|
54
|
-
}
|
|
55
|
-
}
|
|
74
|
+
export { WorkbenchConfigValidationError };
|
|
56
75
|
|
|
57
76
|
export function parseWorkbenchExtensionsConfig(input: unknown): WorkbenchExtensionsConfig {
|
|
58
77
|
const record = assertRecord(input, 'extensions config');
|
|
59
78
|
|
|
60
79
|
return {
|
|
61
|
-
enabled:
|
|
62
|
-
recommendations:
|
|
80
|
+
enabled: readOptionalStringArrayFromExtensionsConfig(record, 'enabled'),
|
|
81
|
+
recommendations: readOptionalStringArrayFromExtensionsConfig(record, 'recommendations'),
|
|
63
82
|
};
|
|
64
83
|
}
|
|
65
84
|
|
|
@@ -75,31 +94,102 @@ export function parseWorkbenchExtensionsConfigJson(jsonText: string): WorkbenchE
|
|
|
75
94
|
}
|
|
76
95
|
}
|
|
77
96
|
|
|
97
|
+
export function parseWorkbenchExtensionsLock(input: unknown): WorkbenchExtensionsLock {
|
|
98
|
+
const record = assertRecord(input, 'extensions lock');
|
|
99
|
+
const lockfileVersion = record.lockfileVersion;
|
|
100
|
+
if (
|
|
101
|
+
typeof lockfileVersion !== 'number' ||
|
|
102
|
+
!Number.isInteger(lockfileVersion) ||
|
|
103
|
+
lockfileVersion < 1
|
|
104
|
+
) {
|
|
105
|
+
throw new WorkbenchConfigValidationError(
|
|
106
|
+
'Expected extensions lock "lockfileVersion" to be a positive integer.',
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const extensionsRecord = assertRecord(record.extensions ?? {}, 'extensions lock extensions');
|
|
111
|
+
const extensions: Record<string, WorkbenchExtensionsLockEntry> = {};
|
|
112
|
+
for (const [extensionId, rawEntry] of Object.entries(extensionsRecord)) {
|
|
113
|
+
const entry = assertRecord(rawEntry, `extensions lock entry "${extensionId}"`);
|
|
114
|
+
if (typeof entry.version !== 'string' || entry.version.trim().length === 0) {
|
|
115
|
+
throw new WorkbenchConfigValidationError(
|
|
116
|
+
`Expected extensions lock entry "${extensionId}" to include a non-empty version.`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (entry.integrity !== undefined && typeof entry.integrity !== 'string') {
|
|
120
|
+
throw new WorkbenchConfigValidationError(
|
|
121
|
+
`Expected extensions lock entry "${extensionId}" integrity to be a string.`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
extensions[extensionId] = {
|
|
125
|
+
integrity: typeof entry.integrity === 'string' ? entry.integrity : undefined,
|
|
126
|
+
version: entry.version.trim(),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
extensions,
|
|
132
|
+
lockfileVersion,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function parseWorkbenchExtensionsLockJson(jsonText: string): WorkbenchExtensionsLock {
|
|
137
|
+
try {
|
|
138
|
+
return parseWorkbenchExtensionsLock(JSON.parse(jsonText) as unknown);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (error instanceof WorkbenchConfigValidationError) {
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
throw new WorkbenchConfigValidationError('Expected extensions lock to be valid JSON.');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
78
148
|
export function parseWorkbenchLayoutConfig(input: unknown): WorkbenchLayoutConfig {
|
|
79
149
|
const record = assertRecord(input, 'layout config');
|
|
80
|
-
assertKnownKeys(record, ['activityBar', 'panel', 'sideBar'], 'layout config');
|
|
150
|
+
assertKnownKeys(record, ['activityBar', 'auxiliaryBar', 'panel', 'sideBar'], 'layout config');
|
|
81
151
|
|
|
82
152
|
const activityBar = readOptionalRecord(record, 'activityBar');
|
|
153
|
+
const auxiliaryBar = readOptionalRecord(record, 'auxiliaryBar');
|
|
83
154
|
const panel = readOptionalRecord(record, 'panel');
|
|
84
155
|
const sideBar = readOptionalRecord(record, 'sideBar');
|
|
85
156
|
|
|
86
|
-
assertKnownKeys(
|
|
157
|
+
assertKnownKeys(
|
|
158
|
+
activityBar,
|
|
159
|
+
['hiddenItemIds', 'itemOrder', 'visible'],
|
|
160
|
+
'layout config activityBar',
|
|
161
|
+
);
|
|
162
|
+
assertKnownKeys(auxiliaryBar, ['visible'], 'layout config auxiliaryBar');
|
|
87
163
|
assertKnownKeys(panel, ['visible'], 'layout config panel');
|
|
88
|
-
assertKnownKeys(
|
|
164
|
+
assertKnownKeys(
|
|
165
|
+
sideBar,
|
|
166
|
+
['activeViewContainer', 'sizePercent', 'visible'],
|
|
167
|
+
'layout config sideBar',
|
|
168
|
+
);
|
|
89
169
|
|
|
90
170
|
return {
|
|
91
171
|
activityBar: {
|
|
172
|
+
hiddenItemIds: readOptionalStringArray(activityBar, 'hiddenItemIds'),
|
|
173
|
+
itemOrder: readOptionalStringArray(activityBar, 'itemOrder'),
|
|
92
174
|
visible: readOptionalBoolean(
|
|
93
175
|
activityBar,
|
|
94
176
|
'visible',
|
|
95
177
|
DEFAULT_WORKBENCH_LAYOUT_CONFIG.activityBar.visible,
|
|
96
178
|
),
|
|
97
179
|
},
|
|
180
|
+
auxiliaryBar: {
|
|
181
|
+
visible: readOptionalBoolean(
|
|
182
|
+
auxiliaryBar,
|
|
183
|
+
'visible',
|
|
184
|
+
DEFAULT_WORKBENCH_LAYOUT_CONFIG.auxiliaryBar.visible,
|
|
185
|
+
),
|
|
186
|
+
},
|
|
98
187
|
panel: {
|
|
99
188
|
visible: readOptionalBoolean(panel, 'visible', DEFAULT_WORKBENCH_LAYOUT_CONFIG.panel.visible),
|
|
100
189
|
},
|
|
101
190
|
sideBar: {
|
|
102
191
|
...readOptionalLayoutId(sideBar, 'activeViewContainer'),
|
|
192
|
+
...readOptionalSizePercent(sideBar, 'sizePercent'),
|
|
103
193
|
visible: readOptionalBoolean(
|
|
104
194
|
sideBar,
|
|
105
195
|
'visible',
|
|
@@ -121,13 +211,37 @@ export function parseWorkbenchLayoutConfigJson(jsonText: string): WorkbenchLayou
|
|
|
121
211
|
}
|
|
122
212
|
}
|
|
123
213
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
214
|
+
export {
|
|
215
|
+
parseWorkbenchKeybindingsConfig,
|
|
216
|
+
parseWorkbenchKeybindingsConfigJson,
|
|
217
|
+
type WorkbenchKeybindingDefinition,
|
|
218
|
+
} from './keybindings-config.js';
|
|
219
|
+
export {
|
|
220
|
+
parseWorkbenchSettingsConfig,
|
|
221
|
+
parseWorkbenchSettingsConfigJson,
|
|
222
|
+
type WorkbenchSettingsConfig,
|
|
223
|
+
} from './settings-config.js';
|
|
224
|
+
export {
|
|
225
|
+
createEmptyPreferenceValuesByScope,
|
|
226
|
+
FUTURE_PREFERENCE_SCOPES,
|
|
227
|
+
isPreferenceScope,
|
|
228
|
+
mergePreferenceValuesByScope,
|
|
229
|
+
mergeScopedPreferences,
|
|
230
|
+
PREFERENCE_SCOPE_MERGE_ORDER,
|
|
231
|
+
type FuturePreferenceScope,
|
|
232
|
+
type PreferenceScope,
|
|
233
|
+
type PreferenceValuesByScope,
|
|
234
|
+
type ScopedPreferenceLayer,
|
|
235
|
+
} from './preference-scopes.js';
|
|
236
|
+
export {
|
|
237
|
+
parseWorkbenchUserCommandsConfig,
|
|
238
|
+
parseWorkbenchUserCommandsConfigJson,
|
|
239
|
+
type WorkbenchUserCommandAction,
|
|
240
|
+
type WorkbenchUserCommandDefinition,
|
|
241
|
+
type WorkbenchUserCommandExecuteAction,
|
|
242
|
+
type WorkbenchUserCommandSequenceAction,
|
|
243
|
+
type WorkbenchUserCommandsConfig,
|
|
244
|
+
} from './user-commands-config.js';
|
|
131
245
|
|
|
132
246
|
function readOptionalRecord(record: Record<string, unknown>, key: string): Record<string, unknown> {
|
|
133
247
|
const value = record[key];
|
|
@@ -143,6 +257,22 @@ function readOptionalRecord(record: Record<string, unknown>, key: string): Recor
|
|
|
143
257
|
}
|
|
144
258
|
|
|
145
259
|
function readOptionalStringArray(
|
|
260
|
+
record: Record<string, unknown>,
|
|
261
|
+
key: string,
|
|
262
|
+
): readonly string[] | undefined {
|
|
263
|
+
const value = record[key];
|
|
264
|
+
if (value === undefined) {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
|
|
269
|
+
throw new WorkbenchConfigValidationError(`Expected "${key}" to be an array of strings.`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return [...new Set(value.map((item) => item.trim()).filter(Boolean))];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function readOptionalStringArrayFromExtensionsConfig(
|
|
146
276
|
record: Record<string, unknown>,
|
|
147
277
|
key: keyof WorkbenchExtensionsConfig,
|
|
148
278
|
): readonly string[] {
|
|
@@ -193,10 +323,24 @@ function readOptionalLayoutId(
|
|
|
193
323
|
};
|
|
194
324
|
}
|
|
195
325
|
|
|
196
|
-
function
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
326
|
+
function readOptionalSizePercent(
|
|
327
|
+
record: Record<string, unknown>,
|
|
328
|
+
key: string,
|
|
329
|
+
): { readonly sizePercent?: number } {
|
|
330
|
+
const value = record[key];
|
|
331
|
+
if (value === undefined) {
|
|
332
|
+
return {};
|
|
201
333
|
}
|
|
334
|
+
|
|
335
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
336
|
+
throw new WorkbenchConfigValidationError(`Expected "${key}" to be a finite number.`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
sizePercent: clampLayoutSizePercent(value),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function clampLayoutSizePercent(value: number): number {
|
|
345
|
+
return Math.min(90, Math.max(10, value));
|
|
202
346
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertKnownKeys,
|
|
3
|
+
assertRecord,
|
|
4
|
+
readOptionalString,
|
|
5
|
+
readRequiredString,
|
|
6
|
+
} from './parse-helpers.js';
|
|
7
|
+
import { WorkbenchConfigValidationError } from './validation-error.js';
|
|
8
|
+
|
|
9
|
+
export interface WorkbenchKeybindingDefinition {
|
|
10
|
+
readonly args?: readonly unknown[];
|
|
11
|
+
readonly command: string;
|
|
12
|
+
readonly key: string;
|
|
13
|
+
readonly when?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function parseWorkbenchKeybindingsConfig(
|
|
17
|
+
input: unknown,
|
|
18
|
+
): readonly WorkbenchKeybindingDefinition[] {
|
|
19
|
+
if (!Array.isArray(input)) {
|
|
20
|
+
throw new WorkbenchConfigValidationError('Expected keybindings config to be an array.');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return input.map((entry, index) => parseWorkbenchKeybindingDefinition(entry, index));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function parseWorkbenchKeybindingsConfigJson(
|
|
27
|
+
jsonText: string,
|
|
28
|
+
): readonly WorkbenchKeybindingDefinition[] {
|
|
29
|
+
try {
|
|
30
|
+
return parseWorkbenchKeybindingsConfig(JSON.parse(jsonText) as unknown);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error instanceof WorkbenchConfigValidationError) {
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
throw new WorkbenchConfigValidationError('Expected keybindings config to be valid JSON.');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseWorkbenchKeybindingDefinition(
|
|
41
|
+
input: unknown,
|
|
42
|
+
index: number,
|
|
43
|
+
): WorkbenchKeybindingDefinition {
|
|
44
|
+
const record = assertRecord(input, `keybindings[${index}]`);
|
|
45
|
+
assertKnownKeys(record, ['args', 'command', 'key', 'when'], `keybindings[${index}]`);
|
|
46
|
+
|
|
47
|
+
const args = readOptionalArgs(record);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
...(args ? { args } : {}),
|
|
51
|
+
command: readRequiredString(record, 'command'),
|
|
52
|
+
key: readRequiredString(record, 'key'),
|
|
53
|
+
...(readOptionalString(record, 'when') ? { when: readOptionalString(record, 'when') } : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readOptionalArgs(record: Record<string, unknown>): readonly unknown[] | undefined {
|
|
58
|
+
const value = record.args;
|
|
59
|
+
if (value === undefined) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!Array.isArray(value)) {
|
|
64
|
+
throw new WorkbenchConfigValidationError('Expected "args" to be an array.');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return [...value];
|
|
68
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { WorkbenchConfigValidationError } from './validation-error.js';
|
|
2
|
+
|
|
3
|
+
export function assertRecord(value: unknown, label: string): Record<string, unknown> {
|
|
4
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
5
|
+
throw new WorkbenchConfigValidationError(`Expected ${label} to be an object.`);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return value as Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function readRequiredString(record: Record<string, unknown>, key: string): string {
|
|
12
|
+
const value = record[key];
|
|
13
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
14
|
+
throw new WorkbenchConfigValidationError(`Expected "${key}" to be a non-empty string.`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return value.trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function readOptionalString(
|
|
21
|
+
record: Record<string, unknown>,
|
|
22
|
+
key: string,
|
|
23
|
+
): string | undefined {
|
|
24
|
+
const value = record[key];
|
|
25
|
+
if (value === undefined) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
30
|
+
throw new WorkbenchConfigValidationError(`Expected "${key}" to be a non-empty string.`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return value.trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function assertKnownKeys(
|
|
37
|
+
record: Record<string, unknown>,
|
|
38
|
+
keys: readonly string[],
|
|
39
|
+
label: string,
|
|
40
|
+
) {
|
|
41
|
+
const knownKeys = new Set(keys);
|
|
42
|
+
const unknownKeys = Object.keys(record).filter((key) => !knownKeys.has(key));
|
|
43
|
+
if (unknownKeys.length > 0) {
|
|
44
|
+
throw new WorkbenchConfigValidationError(`Unexpected ${label} field "${unknownKeys[0]}".`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { WorkbenchSettingsConfig } from './settings-config.js';
|
|
2
|
+
|
|
3
|
+
/** Runtime preference scopes supported in v1. */
|
|
4
|
+
export type PreferenceScope = 'default' | 'workspace' | 'local';
|
|
5
|
+
|
|
6
|
+
/** Documented future scopes — not merged or persisted yet. */
|
|
7
|
+
export const FUTURE_PREFERENCE_SCOPES = ['user', 'resource', 'secret'] as const;
|
|
8
|
+
|
|
9
|
+
export type FuturePreferenceScope = (typeof FUTURE_PREFERENCE_SCOPES)[number];
|
|
10
|
+
|
|
11
|
+
/** Lower index = lower precedence when merging effective values. */
|
|
12
|
+
export const PREFERENCE_SCOPE_MERGE_ORDER: readonly PreferenceScope[] = [
|
|
13
|
+
'default',
|
|
14
|
+
'workspace',
|
|
15
|
+
'local',
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export type PreferenceValuesByScope = Partial<Record<PreferenceScope, WorkbenchSettingsConfig>>;
|
|
19
|
+
|
|
20
|
+
export interface ScopedPreferenceLayer {
|
|
21
|
+
readonly scope: PreferenceScope;
|
|
22
|
+
readonly values: WorkbenchSettingsConfig;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isPreferenceScope(value: unknown): value is PreferenceScope {
|
|
26
|
+
return value === 'default' || value === 'workspace' || value === 'local';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function mergeScopedPreferences(
|
|
30
|
+
layers: readonly ScopedPreferenceLayer[],
|
|
31
|
+
): WorkbenchSettingsConfig {
|
|
32
|
+
const merged: Record<string, unknown> = {};
|
|
33
|
+
|
|
34
|
+
for (const scope of PREFERENCE_SCOPE_MERGE_ORDER) {
|
|
35
|
+
const layer = layers.find((candidate) => candidate.scope === scope);
|
|
36
|
+
if (!layer) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const [key, value] of Object.entries(layer.values)) {
|
|
41
|
+
merged[key] = value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return merged;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function mergePreferenceValuesByScope(
|
|
49
|
+
valuesByScope: PreferenceValuesByScope,
|
|
50
|
+
): WorkbenchSettingsConfig {
|
|
51
|
+
return mergeScopedPreferences(
|
|
52
|
+
PREFERENCE_SCOPE_MERGE_ORDER.flatMap((scope) => {
|
|
53
|
+
const values = valuesByScope[scope];
|
|
54
|
+
return values ? [{ scope, values }] : [];
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createEmptyPreferenceValuesByScope(): PreferenceValuesByScope {
|
|
60
|
+
return {
|
|
61
|
+
default: {},
|
|
62
|
+
local: {},
|
|
63
|
+
workspace: {},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { WorkbenchConfigValidationError } from './validation-error.js';
|
|
2
|
+
|
|
3
|
+
export type WorkbenchSettingsConfig = Readonly<Record<string, unknown>>;
|
|
4
|
+
|
|
5
|
+
export function parseWorkbenchSettingsConfig(input: unknown): WorkbenchSettingsConfig {
|
|
6
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
|
|
7
|
+
throw new WorkbenchConfigValidationError('Expected settings config to be an object.');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return { ...(input as Record<string, unknown>) };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function parseWorkbenchSettingsConfigJson(jsonText: string): WorkbenchSettingsConfig {
|
|
14
|
+
try {
|
|
15
|
+
return parseWorkbenchSettingsConfig(JSON.parse(jsonText) as unknown);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if (error instanceof WorkbenchConfigValidationError) {
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
throw new WorkbenchConfigValidationError('Expected settings config to be valid JSON.');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertKnownKeys,
|
|
3
|
+
assertRecord,
|
|
4
|
+
readOptionalString,
|
|
5
|
+
readRequiredString,
|
|
6
|
+
} from './parse-helpers.js';
|
|
7
|
+
import { WorkbenchConfigValidationError } from './validation-error.js';
|
|
8
|
+
|
|
9
|
+
export interface WorkbenchUserCommandExecuteAction {
|
|
10
|
+
readonly args?: unknown;
|
|
11
|
+
readonly command: string;
|
|
12
|
+
readonly type: 'executeCommand';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface WorkbenchUserCommandSequenceAction {
|
|
16
|
+
readonly steps: readonly WorkbenchUserCommandAction[];
|
|
17
|
+
readonly type: 'sequence';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type WorkbenchUserCommandAction =
|
|
21
|
+
WorkbenchUserCommandExecuteAction | WorkbenchUserCommandSequenceAction;
|
|
22
|
+
|
|
23
|
+
export interface WorkbenchUserCommandDefinition {
|
|
24
|
+
readonly action: WorkbenchUserCommandAction;
|
|
25
|
+
readonly category?: string | undefined;
|
|
26
|
+
readonly command: string;
|
|
27
|
+
readonly title: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface WorkbenchUserCommandsConfig {
|
|
31
|
+
readonly commands: readonly WorkbenchUserCommandDefinition[];
|
|
32
|
+
readonly version?: number | undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function parseWorkbenchUserCommandsConfig(input: unknown): WorkbenchUserCommandsConfig {
|
|
36
|
+
const record = assertRecord(input, 'user commands config');
|
|
37
|
+
assertKnownKeys(record, ['commands', 'version'], 'user commands config');
|
|
38
|
+
|
|
39
|
+
const commands = record.commands;
|
|
40
|
+
if (!Array.isArray(commands)) {
|
|
41
|
+
throw new WorkbenchConfigValidationError('Expected "commands" to be an array.');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
commands: commands.map((entry, index) => parseUserCommandDefinition(entry, index)),
|
|
46
|
+
...(readOptionalNumber(record, 'version') !== undefined
|
|
47
|
+
? { version: readOptionalNumber(record, 'version') }
|
|
48
|
+
: {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function parseWorkbenchUserCommandsConfigJson(
|
|
53
|
+
jsonText: string,
|
|
54
|
+
): WorkbenchUserCommandsConfig {
|
|
55
|
+
try {
|
|
56
|
+
return parseWorkbenchUserCommandsConfig(JSON.parse(jsonText) as unknown);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error instanceof WorkbenchConfigValidationError) {
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw new WorkbenchConfigValidationError('Expected user commands config to be valid JSON.');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parseUserCommandDefinition(input: unknown, index: number): WorkbenchUserCommandDefinition {
|
|
67
|
+
const record = assertRecord(input, `user commands[${index}]`);
|
|
68
|
+
assertKnownKeys(record, ['action', 'category', 'command', 'title'], `user commands[${index}]`);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
action: parseUserCommandAction(record.action, `user commands[${index}].action`),
|
|
72
|
+
...(readOptionalString(record, 'category')
|
|
73
|
+
? { category: readOptionalString(record, 'category') }
|
|
74
|
+
: {}),
|
|
75
|
+
command: readRequiredString(record, 'command'),
|
|
76
|
+
title: readRequiredString(record, 'title'),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseUserCommandAction(input: unknown, label: string): WorkbenchUserCommandAction {
|
|
81
|
+
const record = assertRecord(input, label);
|
|
82
|
+
const type = readRequiredString(record, 'type');
|
|
83
|
+
|
|
84
|
+
if (type === 'executeCommand') {
|
|
85
|
+
assertKnownKeys(record, ['args', 'command', 'type'], label);
|
|
86
|
+
return {
|
|
87
|
+
...(record.args !== undefined ? { args: record.args } : {}),
|
|
88
|
+
command: readRequiredString(record, 'command'),
|
|
89
|
+
type,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (type === 'sequence') {
|
|
94
|
+
assertKnownKeys(record, ['steps', 'type'], label);
|
|
95
|
+
const steps = record.steps;
|
|
96
|
+
if (!Array.isArray(steps) || steps.length === 0) {
|
|
97
|
+
throw new WorkbenchConfigValidationError(
|
|
98
|
+
`Expected "${label}.steps" to be a non-empty array.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
steps: steps.map((step, index) => parseUserCommandAction(step, `${label}.steps[${index}]`)),
|
|
104
|
+
type,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
throw new WorkbenchConfigValidationError(`Unexpected ${label} type "${type}".`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function readOptionalNumber(record: Record<string, unknown>, key: string): number | undefined {
|
|
112
|
+
const value = record[key];
|
|
113
|
+
if (value === undefined) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
118
|
+
throw new WorkbenchConfigValidationError(`Expected "${key}" to be a finite number.`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return value;
|
|
122
|
+
}
|