@theia/plugin-utils 1.75.0-next.28

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.
Files changed (122) hide show
  1. package/README.md +30 -0
  2. package/lib/common/constants.d.ts +12 -0
  3. package/lib/common/constants.d.ts.map +1 -0
  4. package/lib/common/constants.js +30 -0
  5. package/lib/common/constants.js.map +1 -0
  6. package/lib/common/constants.spec.d.ts +2 -0
  7. package/lib/common/constants.spec.d.ts.map +1 -0
  8. package/lib/common/constants.spec.js +33 -0
  9. package/lib/common/constants.spec.js.map +1 -0
  10. package/lib/common/contribution-types.d.ts +648 -0
  11. package/lib/common/contribution-types.d.ts.map +1 -0
  12. package/lib/common/contribution-types.js +30 -0
  13. package/lib/common/contribution-types.js.map +1 -0
  14. package/lib/common/manifest-types.d.ts +105 -0
  15. package/lib/common/manifest-types.d.ts.map +1 -0
  16. package/lib/common/manifest-types.js +31 -0
  17. package/lib/common/manifest-types.js.map +1 -0
  18. package/lib/common/manifest-types.spec.d.ts +2 -0
  19. package/lib/common/manifest-types.spec.d.ts.map +1 -0
  20. package/lib/common/manifest-types.spec.js +35 -0
  21. package/lib/common/manifest-types.spec.js.map +1 -0
  22. package/lib/common/package-nls.d.ts +24 -0
  23. package/lib/common/package-nls.d.ts.map +1 -0
  24. package/lib/common/package-nls.js +82 -0
  25. package/lib/common/package-nls.js.map +1 -0
  26. package/lib/common/package-nls.spec.d.ts +2 -0
  27. package/lib/common/package-nls.spec.d.ts.map +1 -0
  28. package/lib/common/package-nls.spec.js +98 -0
  29. package/lib/common/package-nls.spec.js.map +1 -0
  30. package/lib/common/plugin-activation-events.d.ts +7 -0
  31. package/lib/common/plugin-activation-events.d.ts.map +1 -0
  32. package/lib/common/plugin-activation-events.js +95 -0
  33. package/lib/common/plugin-activation-events.js.map +1 -0
  34. package/lib/common/plugin-activation-events.spec.d.ts +2 -0
  35. package/lib/common/plugin-activation-events.spec.d.ts.map +1 -0
  36. package/lib/common/plugin-activation-events.spec.js +89 -0
  37. package/lib/common/plugin-activation-events.spec.js.map +1 -0
  38. package/lib/common/plugin-manifest.d.ts +10 -0
  39. package/lib/common/plugin-manifest.d.ts.map +1 -0
  40. package/lib/common/plugin-manifest.js +35 -0
  41. package/lib/common/plugin-manifest.js.map +1 -0
  42. package/lib/common/plugin-manifest.spec.d.ts +2 -0
  43. package/lib/common/plugin-manifest.spec.d.ts.map +1 -0
  44. package/lib/common/plugin-manifest.spec.js +83 -0
  45. package/lib/common/plugin-manifest.spec.js.map +1 -0
  46. package/lib/common/plugin-model.d.ts +33 -0
  47. package/lib/common/plugin-model.d.ts.map +1 -0
  48. package/lib/common/plugin-model.js +166 -0
  49. package/lib/common/plugin-model.js.map +1 -0
  50. package/lib/common/plugin-model.spec.d.ts +2 -0
  51. package/lib/common/plugin-model.spec.d.ts.map +1 -0
  52. package/lib/common/plugin-model.spec.js +204 -0
  53. package/lib/common/plugin-model.spec.js.map +1 -0
  54. package/lib/common/protocol-shims.d.ts +128 -0
  55. package/lib/common/protocol-shims.d.ts.map +1 -0
  56. package/lib/common/protocol-shims.js +29 -0
  57. package/lib/common/protocol-shims.js.map +1 -0
  58. package/lib/common/test-helpers.d.ts +5 -0
  59. package/lib/common/test-helpers.d.ts.map +1 -0
  60. package/lib/common/test-helpers.js +41 -0
  61. package/lib/common/test-helpers.js.map +1 -0
  62. package/lib/common/utils.d.ts +20 -0
  63. package/lib/common/utils.d.ts.map +1 -0
  64. package/lib/common/utils.js +74 -0
  65. package/lib/common/utils.js.map +1 -0
  66. package/lib/common/utils.spec.d.ts +2 -0
  67. package/lib/common/utils.spec.d.ts.map +1 -0
  68. package/lib/common/utils.spec.js +77 -0
  69. package/lib/common/utils.spec.js.map +1 -0
  70. package/lib/node/normalize-contributions.d.ts +54 -0
  71. package/lib/node/normalize-contributions.d.ts.map +1 -0
  72. package/lib/node/normalize-contributions.js +985 -0
  73. package/lib/node/normalize-contributions.js.map +1 -0
  74. package/lib/node/normalize-contributions.spec.d.ts +2 -0
  75. package/lib/node/normalize-contributions.spec.d.ts.map +1 -0
  76. package/lib/node/normalize-contributions.spec.js +850 -0
  77. package/lib/node/normalize-contributions.spec.js.map +1 -0
  78. package/lib/node/package-nls.d.ts +3 -0
  79. package/lib/node/package-nls.d.ts.map +1 -0
  80. package/lib/node/package-nls.js +45 -0
  81. package/lib/node/package-nls.js.map +1 -0
  82. package/lib/node/plugin-manifest.d.ts +6 -0
  83. package/lib/node/plugin-manifest.d.ts.map +1 -0
  84. package/lib/node/plugin-manifest.js +26 -0
  85. package/lib/node/plugin-manifest.js.map +1 -0
  86. package/lib/node/plugin-model.d.ts +5 -0
  87. package/lib/node/plugin-model.d.ts.map +1 -0
  88. package/lib/node/plugin-model.js +36 -0
  89. package/lib/node/plugin-model.js.map +1 -0
  90. package/lib/node/read-grammars.d.ts +10 -0
  91. package/lib/node/read-grammars.d.ts.map +1 -0
  92. package/lib/node/read-grammars.js +83 -0
  93. package/lib/node/read-grammars.js.map +1 -0
  94. package/lib/node/read-grammars.spec.d.ts +2 -0
  95. package/lib/node/read-grammars.spec.d.ts.map +1 -0
  96. package/lib/node/read-grammars.spec.js +109 -0
  97. package/lib/node/read-grammars.spec.js.map +1 -0
  98. package/package.json +42 -0
  99. package/src/common/constants.spec.ts +34 -0
  100. package/src/common/constants.ts +35 -0
  101. package/src/common/contribution-types.ts +677 -0
  102. package/src/common/manifest-types.spec.ts +36 -0
  103. package/src/common/manifest-types.ts +126 -0
  104. package/src/common/package-nls.spec.ts +122 -0
  105. package/src/common/package-nls.ts +95 -0
  106. package/src/common/plugin-activation-events.spec.ts +104 -0
  107. package/src/common/plugin-activation-events.ts +102 -0
  108. package/src/common/plugin-manifest.spec.ts +94 -0
  109. package/src/common/plugin-manifest.ts +44 -0
  110. package/src/common/plugin-model.spec.ts +246 -0
  111. package/src/common/plugin-model.ts +194 -0
  112. package/src/common/protocol-shims.ts +148 -0
  113. package/src/common/test-helpers.ts +44 -0
  114. package/src/common/utils.spec.ts +86 -0
  115. package/src/common/utils.ts +81 -0
  116. package/src/node/normalize-contributions.spec.ts +931 -0
  117. package/src/node/normalize-contributions.ts +1098 -0
  118. package/src/node/package-nls.ts +45 -0
  119. package/src/node/plugin-manifest.ts +30 -0
  120. package/src/node/plugin-model.ts +33 -0
  121. package/src/node/read-grammars.spec.ts +128 -0
  122. package/src/node/read-grammars.ts +97 -0
@@ -0,0 +1,850 @@
1
+ "use strict";
2
+ // *****************************************************************************
3
+ // Copyright (C) 2026 Maksim Kachurin and others.
4
+ //
5
+ // This program and the accompanying materials are made available under the
6
+ // terms of the Eclipse Public License v. 2.0 which is available at
7
+ // http://www.eclipse.org/legal/epl-2.0.
8
+ //
9
+ // This Source Code may also be made available under the following Secondary
10
+ // Licenses when the conditions for such availability set forth in the Eclipse
11
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ // with the GNU Classpath Exception which is available at
13
+ // https://www.gnu.org/software/classpath/license.html.
14
+ //
15
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
+ // *****************************************************************************
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ const chai_1 = require("chai");
19
+ const contribution_types_1 = require("../common/contribution-types");
20
+ const protocol_shims_1 = require("../common/protocol-shims");
21
+ const normalize_contributions_1 = require("./normalize-contributions");
22
+ const test_helpers_1 = require("../common/test-helpers");
23
+ describe('normalize-contributions', () => {
24
+ describe('deriveDefaultForType', () => {
25
+ it('mirrors VS Code defaults for each json type', () => {
26
+ (0, chai_1.expect)((0, normalize_contributions_1.deriveDefaultForType)('boolean')).to.equal(false);
27
+ (0, chai_1.expect)((0, normalize_contributions_1.deriveDefaultForType)('integer')).to.equal(0);
28
+ (0, chai_1.expect)((0, normalize_contributions_1.deriveDefaultForType)('number')).to.equal(0);
29
+ (0, chai_1.expect)((0, normalize_contributions_1.deriveDefaultForType)('string')).to.equal('');
30
+ (0, chai_1.expect)((0, normalize_contributions_1.deriveDefaultForType)('array')).to.deep.equal([]);
31
+ (0, chai_1.expect)((0, normalize_contributions_1.deriveDefaultForType)('object')).to.deep.equal({});
32
+ // eslint-disable-next-line no-null/no-null
33
+ (0, chai_1.expect)((0, normalize_contributions_1.deriveDefaultForType)(undefined)).to.equal(null);
34
+ });
35
+ it('uses the first type when an array is provided', () => {
36
+ (0, chai_1.expect)((0, normalize_contributions_1.deriveDefaultForType)(['string', 'number'])).to.equal('');
37
+ });
38
+ });
39
+ describe('getScope', () => {
40
+ it('maps monaco scopes to preference scope and overridable flag', () => {
41
+ (0, chai_1.expect)((0, normalize_contributions_1.getScope)('machine-overridable')).to.deep.equal({ scope: protocol_shims_1.PreferenceScope.Folder, overridable: false });
42
+ (0, chai_1.expect)((0, normalize_contributions_1.getScope)('language-overridable')).to.deep.equal({ scope: protocol_shims_1.PreferenceScope.Folder, overridable: true });
43
+ (0, chai_1.expect)((0, normalize_contributions_1.getScope)('application')).to.deep.equal({ scope: protocol_shims_1.PreferenceScope.User, overridable: false });
44
+ (0, chai_1.expect)((0, normalize_contributions_1.getScope)(undefined)).to.deep.equal({ scope: undefined, overridable: false });
45
+ });
46
+ });
47
+ describe('readConfiguration', () => {
48
+ it('derives defaults and property scopes', () => {
49
+ const schema = (0, normalize_contributions_1.readConfiguration)({
50
+ title: 'Sample',
51
+ scope: 'application',
52
+ properties: {
53
+ 'sample.enabled': { type: 'boolean', scope: 'language-overridable' },
54
+ 'sample.path': { type: 'string', default: '/tmp' }
55
+ }
56
+ }, '/tmp/plugin');
57
+ (0, chai_1.expect)(schema?.scope).to.equal(protocol_shims_1.PreferenceScope.User);
58
+ (0, chai_1.expect)(schema?.properties['sample.enabled'].default).to.equal(false);
59
+ (0, chai_1.expect)(schema?.properties['sample.enabled'].overridable).to.equal(true);
60
+ (0, chai_1.expect)(schema?.properties['sample.path'].default).to.equal('/tmp');
61
+ });
62
+ });
63
+ describe('transformIconUrl and readCommand', () => {
64
+ it('resolves file icons and codicons', () => {
65
+ const ctx = (0, test_helpers_1.createNormalizeCtx)();
66
+ (0, chai_1.expect)((0, normalize_contributions_1.transformIconUrl)(ctx, ctx.plugin, './media/icon.png')).to.deep.equal({
67
+ iconUrl: 'hostedPlugin/acme_test_ext/media/icon.png'
68
+ });
69
+ (0, chai_1.expect)((0, normalize_contributions_1.transformIconUrl)(ctx, ctx.plugin, '$(zap)')).to.deep.equal({ themeIcon: '$(zap)' });
70
+ (0, chai_1.expect)((0, normalize_contributions_1.transformIconUrl)(ctx, ctx.plugin, {
71
+ light: './light.png',
72
+ dark: './dark.png'
73
+ })).to.deep.equal({
74
+ iconUrl: {
75
+ light: 'hostedPlugin/acme_test_ext/light.png',
76
+ dark: 'hostedPlugin/acme_test_ext/dark.png'
77
+ }
78
+ });
79
+ const command = (0, normalize_contributions_1.readCommand)(ctx, {
80
+ command: 'sample.run',
81
+ title: 'Run',
82
+ original: 'Original',
83
+ icon: '$(play)'
84
+ }, ctx.plugin);
85
+ (0, chai_1.expect)(command).to.include({
86
+ command: 'sample.run',
87
+ title: 'Run',
88
+ originalTitle: 'Original',
89
+ themeIcon: '$(play)'
90
+ });
91
+ });
92
+ it('honors ctx.transformIconUrl from readCommand / readSubmenu', () => {
93
+ const ctx = (0, test_helpers_1.createNormalizeCtx)();
94
+ ctx.transformIconUrl = () => ({ themeIcon: '$(custom)' });
95
+ (0, chai_1.expect)((0, normalize_contributions_1.readCommand)(ctx, {
96
+ command: 'sample.run',
97
+ title: 'Run',
98
+ icon: '$(play)'
99
+ }, ctx.plugin).themeIcon).to.equal('$(custom)');
100
+ (0, chai_1.expect)((0, normalize_contributions_1.readSubmenus)(ctx, [{
101
+ id: 'sample.submenu',
102
+ label: 'Sample',
103
+ icon: './icon.png'
104
+ }], ctx.plugin)[0].icon).to.equal('$(custom)');
105
+ });
106
+ });
107
+ describe('readKeybinding and menus', () => {
108
+ it('copies keybinding fields', () => {
109
+ (0, chai_1.expect)((0, normalize_contributions_1.readKeybinding)({
110
+ key: 'ctrl+shift+p',
111
+ command: 'sample.run',
112
+ when: 'editorFocus',
113
+ mac: 'cmd+shift+p'
114
+ })).to.deep.equal({
115
+ keybinding: 'ctrl+shift+p',
116
+ command: 'sample.run',
117
+ when: 'editorFocus',
118
+ mac: 'cmd+shift+p',
119
+ linux: undefined,
120
+ win: undefined,
121
+ args: undefined
122
+ });
123
+ });
124
+ it('copies menu fields', () => {
125
+ (0, chai_1.expect)((0, normalize_contributions_1.readMenus)([{
126
+ command: 'sample.run',
127
+ group: 'navigation',
128
+ when: 'true'
129
+ }])).to.deep.equal([{
130
+ command: 'sample.run',
131
+ group: 'navigation',
132
+ when: 'true',
133
+ submenu: undefined,
134
+ alt: undefined
135
+ }]);
136
+ });
137
+ });
138
+ describe('views', () => {
139
+ it('extracts view ids across locations', () => {
140
+ (0, chai_1.expect)((0, normalize_contributions_1.extractPluginViewsIds)({
141
+ explorer: [{ id: 'a', name: 'A' }],
142
+ panel: [{ id: 'b', name: 'B' }]
143
+ })).to.deep.equal(['a', 'b']);
144
+ });
145
+ it('normalizes view containers, views, and welcome content order', () => {
146
+ const ctx = (0, test_helpers_1.createNormalizeCtx)();
147
+ const containers = (0, normalize_contributions_1.readViewsContainers)(ctx, [{
148
+ id: 'sample.container',
149
+ title: 'Sample',
150
+ icon: '$(folder)'
151
+ }], ctx.plugin);
152
+ (0, chai_1.expect)(containers[0]).to.include({
153
+ id: 'sample.container',
154
+ themeIcon: '$(folder)'
155
+ });
156
+ (0, chai_1.expect)(containers[0].iconUrl).to.equal('hostedPlugin/acme_test_ext/%24(folder)');
157
+ const views = (0, normalize_contributions_1.readViews)([{ id: 'sample.view', name: 'Sample View' }]);
158
+ (0, chai_1.expect)(views).to.deep.equal([{
159
+ id: 'sample.view',
160
+ name: 'Sample View',
161
+ when: undefined,
162
+ type: undefined
163
+ }]);
164
+ const welcome = (0, normalize_contributions_1.readViewsWelcome)([{
165
+ view: 'sample.view',
166
+ contents: 'Welcome'
167
+ }], { explorer: [{ id: 'sample.view', name: 'Sample View' }] });
168
+ (0, chai_1.expect)(welcome[0].order).to.equal(0);
169
+ (0, chai_1.expect)((0, normalize_contributions_1.readViewWelcome)({
170
+ view: 'missing.view',
171
+ contents: 'Missing'
172
+ }, ['sample.view']).order).to.equal(1);
173
+ });
174
+ it('skips view containers without a string icon and reports an error', () => {
175
+ const errors = [];
176
+ const ctx = (0, test_helpers_1.createNormalizeCtx)({}, {
177
+ onError: (_kind, message) => {
178
+ errors.push(message);
179
+ }
180
+ });
181
+ (0, chai_1.expect)((0, normalize_contributions_1.readViewContainer)(ctx, {
182
+ id: 'sample.container',
183
+ title: 'Sample'
184
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
185
+ }, ctx.plugin)).to.equal(undefined);
186
+ (0, chai_1.expect)((0, normalize_contributions_1.readViewsContainers)(ctx, [{
187
+ id: 'valid.container',
188
+ title: 'Valid',
189
+ icon: '$(folder)'
190
+ }, {
191
+ id: 'invalid.container',
192
+ title: 'Invalid'
193
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
194
+ }], ctx.plugin)).to.have.lengthOf(1);
195
+ (0, chai_1.expect)(errors.length).to.be.greaterThan(0);
196
+ });
197
+ });
198
+ describe('custom editors and debuggers', () => {
199
+ it('normalizes custom editors with default priority', () => {
200
+ (0, chai_1.expect)((0, normalize_contributions_1.readCustomEditors)([{
201
+ viewType: 'sample.editor',
202
+ displayName: 'Editor'
203
+ }])).to.deep.equal([{
204
+ viewType: 'sample.editor',
205
+ displayName: 'Editor',
206
+ selector: [],
207
+ priority: contribution_types_1.CustomEditorPriority.default
208
+ }]);
209
+ });
210
+ it('copies debugger contributions', () => {
211
+ (0, chai_1.expect)((0, normalize_contributions_1.readDebuggers)([{
212
+ type: 'node',
213
+ label: 'Node Debug',
214
+ program: './out/debug.js',
215
+ configurationAttributes: {},
216
+ configurationSnippets: []
217
+ }])).to.deep.equal([{
218
+ type: 'node',
219
+ label: 'Node Debug',
220
+ program: './out/debug.js',
221
+ languages: undefined,
222
+ enableBreakpointsFor: undefined,
223
+ variables: undefined,
224
+ adapterExecutableCommand: undefined,
225
+ configurationSnippets: [],
226
+ win: undefined,
227
+ winx86: undefined,
228
+ windows: undefined,
229
+ osx: undefined,
230
+ linux: undefined,
231
+ args: undefined,
232
+ runtime: undefined,
233
+ runtimeArgs: undefined,
234
+ configurationAttributes: {}
235
+ }]);
236
+ });
237
+ });
238
+ describe('themes, snippets, icon themes, and colors', () => {
239
+ it('resolves theme and snippet uris', () => {
240
+ const ctx = (0, test_helpers_1.createNormalizeCtx)(undefined, undefined);
241
+ const plugin = (0, test_helpers_1.manifest)({
242
+ name: 'themes',
243
+ contributes: {
244
+ themes: [{ id: 'dark', label: 'Dark', path: './themes/dark.json' }],
245
+ snippets: [{ language: 'typescript', path: './snippets/ts.json' }],
246
+ iconThemes: [{ id: 'set', label: 'Set', path: './icons/theme.json' }]
247
+ }
248
+ });
249
+ ctx.plugin = plugin;
250
+ (0, chai_1.expect)((0, normalize_contributions_1.readThemes)(ctx, plugin)).to.deep.equal([{
251
+ id: 'dark',
252
+ label: 'Dark',
253
+ uri: 'hostedPlugin/acme_themes/themes/dark.json',
254
+ description: undefined,
255
+ uiTheme: undefined
256
+ }]);
257
+ (0, chai_1.expect)((0, normalize_contributions_1.readSnippets)(ctx, plugin)).to.deep.equal([{
258
+ language: 'typescript',
259
+ source: 'Test Extension',
260
+ uri: 'hostedPlugin/acme_themes/snippets/ts.json'
261
+ }]);
262
+ (0, chai_1.expect)((0, normalize_contributions_1.readIconThemes)(ctx, plugin)).to.deep.equal([{
263
+ id: 'set',
264
+ label: 'Set',
265
+ uri: 'hostedPlugin/acme_themes/icons/theme.json',
266
+ description: undefined,
267
+ uiTheme: undefined
268
+ }]);
269
+ });
270
+ it('validates color contributions and reports invalid ids', () => {
271
+ const warnings = [];
272
+ const ctx = (0, test_helpers_1.createNormalizeCtx)({ name: 'colors' }, {
273
+ onWarn: message => {
274
+ warnings.push(message);
275
+ }
276
+ });
277
+ const plugin = (0, test_helpers_1.manifest)({
278
+ name: 'colors',
279
+ contributes: {
280
+ colors: [
281
+ {
282
+ id: 'valid.color',
283
+ description: 'Valid',
284
+ defaults: { light: '#fff', dark: '#000', highContrast: '#111' }
285
+ },
286
+ { id: 'bad id', description: 'Bad', defaults: { light: '#fff', dark: '#000', highContrast: '#111' } }
287
+ ]
288
+ }
289
+ });
290
+ ctx.plugin = plugin;
291
+ const colors = (0, normalize_contributions_1.readColors)(ctx, plugin);
292
+ (0, chai_1.expect)(colors).to.have.lengthOf(1);
293
+ (0, chai_1.expect)(colors[0].defaults).to.deep.equal({ light: '#fff', dark: '#000', hc: '#111' });
294
+ (0, chai_1.expect)(warnings.some(message => message.includes('word[.word]*'))).to.equal(true);
295
+ });
296
+ it('rejects color contributions with empty descriptions', () => {
297
+ const warnings = [];
298
+ const ctx = (0, test_helpers_1.createNormalizeCtx)({ name: 'colors' }, {
299
+ onWarn: message => {
300
+ warnings.push(message);
301
+ }
302
+ });
303
+ const plugin = (0, test_helpers_1.manifest)({
304
+ name: 'colors',
305
+ contributes: {
306
+ colors: [{
307
+ id: 'valid.color',
308
+ description: '',
309
+ defaults: { light: '#fff', dark: '#000', highContrast: '#111' }
310
+ }]
311
+ }
312
+ });
313
+ ctx.plugin = plugin;
314
+ (0, chai_1.expect)((0, normalize_contributions_1.readColors)(ctx, plugin)).to.deep.equal([]);
315
+ (0, chai_1.expect)(warnings.some(message => message.includes('description'))).to.equal(true);
316
+ });
317
+ });
318
+ describe('icons', () => {
319
+ it('accepts icon references and font-based defaults', () => {
320
+ const ctx = (0, test_helpers_1.createNormalizeCtx)({ name: 'icons', publisher: 'acme' });
321
+ const plugin = (0, test_helpers_1.manifest)({
322
+ name: 'icons',
323
+ publisher: 'acme',
324
+ contributes: {
325
+ icons: {
326
+ 'sample-icon': {
327
+ description: 'Sample icon',
328
+ default: 'other-icon'
329
+ },
330
+ 'font-icon': {
331
+ description: 'Font icon',
332
+ default: {
333
+ fontPath: './media/icon.woff2',
334
+ fontCharacter: '\\E001'
335
+ }
336
+ }
337
+ }
338
+ }
339
+ });
340
+ ctx.plugin = plugin;
341
+ const icons = (0, normalize_contributions_1.readIcons)(ctx, plugin);
342
+ (0, chai_1.expect)(icons).to.deep.equal([
343
+ {
344
+ id: 'sample-icon',
345
+ extensionId: 'acme.icons',
346
+ description: 'Sample icon',
347
+ defaults: { id: 'other-icon' }
348
+ },
349
+ {
350
+ id: 'font-icon',
351
+ extensionId: 'acme.icons',
352
+ description: 'Font icon',
353
+ defaults: {
354
+ fontCharacter: '\\E001',
355
+ location: 'hostedPlugin/acme_icons/media/icon.woff2'
356
+ }
357
+ }
358
+ ]);
359
+ });
360
+ });
361
+ describe('terminals, localizations, submenus, and tasks', () => {
362
+ it('reads terminal profiles with required fields only', () => {
363
+ const plugin = (0, test_helpers_1.manifest)({
364
+ name: 'terminal',
365
+ contributes: {
366
+ terminal: {
367
+ profiles: [
368
+ { id: 'bash', title: 'Bash' },
369
+ { id: 'invalid' },
370
+ { id: '', title: 'Empty' },
371
+ { id: 'empty-title', title: '' },
372
+ ]
373
+ }
374
+ }
375
+ });
376
+ (0, chai_1.expect)((0, normalize_contributions_1.readTerminals)(plugin)).to.deep.equal([{ id: 'bash', title: 'Bash' }]);
377
+ });
378
+ it('drops empty theme/snippet paths (avoids resolving to the plugin root)', () => {
379
+ const ctx = (0, test_helpers_1.createNormalizeCtx)(undefined, undefined);
380
+ const plugin = (0, test_helpers_1.manifest)({
381
+ name: 'empty-paths',
382
+ contributes: {
383
+ themes: [{ id: 'bad', path: '' }],
384
+ snippets: [{ language: 'ts', path: '' }],
385
+ iconThemes: [{ id: 'bad', path: '' }],
386
+ }
387
+ });
388
+ ctx.plugin = plugin;
389
+ (0, chai_1.expect)((0, normalize_contributions_1.readThemes)(ctx, plugin)).to.deep.equal([]);
390
+ (0, chai_1.expect)((0, normalize_contributions_1.readSnippets)(ctx, plugin)).to.deep.equal([]);
391
+ (0, chai_1.expect)((0, normalize_contributions_1.readIconThemes)(ctx, plugin)).to.deep.equal([]);
392
+ });
393
+ it('reads localization bundles', () => {
394
+ const plugin = (0, test_helpers_1.manifest)({
395
+ name: 'l10n',
396
+ packagePath: '/tmp/plugin',
397
+ contributes: {
398
+ localizations: [{
399
+ languageId: 'de',
400
+ languageName: 'German',
401
+ translations: [{ id: 'package', path: './package.nls.de.json' }]
402
+ }]
403
+ }
404
+ });
405
+ (0, chai_1.expect)((0, normalize_contributions_1.readLocalizations)(plugin)).to.deep.equal([{
406
+ languageId: 'de',
407
+ languageName: 'German',
408
+ localizedLanguageName: undefined,
409
+ minimalTranslations: undefined,
410
+ translations: [{ id: 'package', path: './package.nls.de.json' }]
411
+ }]);
412
+ });
413
+ it('honors readLocalization / readTranslation ctx hooks', () => {
414
+ const plugin = (0, test_helpers_1.manifest)({
415
+ name: 'l10n',
416
+ packagePath: '/tmp/plugin',
417
+ contributes: {
418
+ localizations: [{
419
+ languageId: 'de',
420
+ translations: [{ id: 'package', path: './package.nls.de.json' }]
421
+ }]
422
+ }
423
+ });
424
+ const ctx = (0, test_helpers_1.createNormalizeCtx)({ name: 'l10n', packagePath: '/tmp/plugin' });
425
+ ctx.readTranslation = (entry, pluginPath) => ({
426
+ id: entry.id,
427
+ path: `${pluginPath}/${entry.path}`
428
+ });
429
+ ctx.readLocalization = (entry, pluginPath) => (0, normalize_contributions_1.readLocalization)(entry, pluginPath, ctx);
430
+ (0, chai_1.expect)((0, normalize_contributions_1.readLocalizations)(plugin, ctx)).to.deep.equal([{
431
+ languageId: 'de',
432
+ languageName: undefined,
433
+ localizedLanguageName: undefined,
434
+ minimalTranslations: undefined,
435
+ translations: [{ id: 'package', path: '/tmp/plugin/./package.nls.de.json' }]
436
+ }]);
437
+ });
438
+ it('reads submenu icons via transformIconUrl', () => {
439
+ const ctx = (0, test_helpers_1.createNormalizeCtx)();
440
+ (0, chai_1.expect)((0, normalize_contributions_1.readSubmenus)(ctx, [{
441
+ id: 'sample.submenu',
442
+ label: 'Sample',
443
+ icon: '$(gear)'
444
+ }], ctx.plugin)).to.deep.equal([{
445
+ icon: '$(gear)',
446
+ id: 'sample.submenu',
447
+ label: 'Sample'
448
+ }]);
449
+ });
450
+ it('builds task definition schema with required type property', () => {
451
+ const definition = (0, normalize_contributions_1.readTaskDefinition)('sample.tasks', {
452
+ type: 'npm',
453
+ required: ['script'],
454
+ properties: {
455
+ script: { type: 'string' }
456
+ }
457
+ });
458
+ (0, chai_1.expect)(definition.taskType).to.equal('npm');
459
+ (0, chai_1.expect)(definition.source).to.equal('sample.tasks');
460
+ (0, chai_1.expect)(definition.properties.all).to.deep.equal(['script']);
461
+ (0, chai_1.expect)((0, normalize_contributions_1.toSchema)({
462
+ type: 'npm',
463
+ required: [],
464
+ properties: { script: { type: 'string' } }
465
+ }).properties.type).to.deep.equal({ type: 'string', const: 'npm' });
466
+ });
467
+ it('honors ctx.toSchema from readTaskDefinition', () => {
468
+ const customSchema = { type: 'object', properties: { custom: { type: 'boolean' } } };
469
+ const definition = (0, normalize_contributions_1.readTaskDefinition)('sample.tasks', {
470
+ type: 'npm',
471
+ required: [],
472
+ properties: {}
473
+ }, {
474
+ toSchema: () => customSchema
475
+ });
476
+ (0, chai_1.expect)(definition.properties.schema).to.equal(customSchema);
477
+ });
478
+ });
479
+ describe('readLanguage', () => {
480
+ it('loads language configuration from a relative json file', async () => {
481
+ const ctx = (0, test_helpers_1.createNormalizeCtx)({ name: 'lang', packagePath: '/tmp/plugin' }, {
482
+ readJsonFile: async () => ({
483
+ autoClosingPairs: [['{', '}'], { open: '[', close: ']', notIn: ['string'] }],
484
+ surroundingPairs: [['(', ')']]
485
+ })
486
+ });
487
+ const languages = await (0, normalize_contributions_1.readLanguages)(ctx, [{
488
+ id: 'sample',
489
+ configuration: './language-configuration.json'
490
+ }], ctx.plugin);
491
+ (0, chai_1.expect)(languages[0].configuration?.autoClosingPairs).to.deep.equal([
492
+ { open: '{', close: '}' },
493
+ { open: '[', close: ']', notIn: ['string'] }
494
+ ]);
495
+ (0, chai_1.expect)(languages[0].configuration?.surroundingPairs).to.deep.equal([{ open: '(', close: ')' }]);
496
+ });
497
+ });
498
+ describe('normalizeContributions', () => {
499
+ it('leaves output empty when the manifest has no contribution kinds', async () => {
500
+ const contributions = await (0, normalize_contributions_1.normalizeContributions)((0, test_helpers_1.createNormalizeCtx)({ name: 'empty' }), {});
501
+ (0, chai_1.expect)(Object.keys(contributions)).to.deep.equal([]);
502
+ });
503
+ it('normalizes supported contribution kinds via injectable readers', async () => {
504
+ const ctx = (0, test_helpers_1.createNormalizeCtx)({
505
+ name: 'full',
506
+ contributes: {
507
+ commands: [{ command: 'sample.run', title: 'Run' }],
508
+ keybindings: [{ key: 'ctrl+r', command: 'sample.run' }],
509
+ configuration: { title: 'Sample', properties: { enabled: { type: 'boolean' } } }
510
+ }
511
+ });
512
+ const contributions = await (0, normalize_contributions_1.normalizeContributions)(ctx, {});
513
+ (0, chai_1.expect)(contributions).to.have.property('commands').with.lengthOf(1);
514
+ (0, chai_1.expect)(contributions).to.have.property('keybindings').with.lengthOf(1);
515
+ (0, chai_1.expect)(contributions).to.have.property('configuration').with.lengthOf(1);
516
+ });
517
+ it('runs the orchestrator for shell layout, passthrough, and async contribution kinds', async () => {
518
+ const readGrammars = async () => [{
519
+ language: 'sample-lang',
520
+ scope: 'source.sample',
521
+ format: 'json',
522
+ grammar: { scopeName: 'source.sample' },
523
+ grammarLocation: './grammar.json'
524
+ }];
525
+ const ctx = (0, test_helpers_1.createNormalizeCtx)({
526
+ name: 'orchestrator',
527
+ displayName: 'Orchestrator Extension',
528
+ contributes: {
529
+ configuration: [
530
+ { title: 'General', properties: { 'sample.enabled': { type: 'boolean' } } },
531
+ { title: 'Advanced', properties: { 'sample.count': { type: 'number' } } }
532
+ ],
533
+ configurationDefaults: { 'sample.enabled': true },
534
+ submenus: [{ id: 'sample.submenu', label: 'Sample', icon: '$(gear)' }],
535
+ customEditors: [{ viewType: 'sample.editor', displayName: 'Editor' }],
536
+ viewsContainers: {
537
+ activitybar: [{ id: 'sample.container', title: 'Sample', icon: './media/icon.png' }],
538
+ panel: [{ id: 'panel.container', title: 'Panel', icon: '$(panel)' }]
539
+ },
540
+ views: {
541
+ explorer: [{ id: 'sample.view', name: 'Sample View' }]
542
+ },
543
+ viewsWelcome: [{ view: 'sample.view', contents: 'Welcome' }],
544
+ commands: [{ command: 'sample.run', title: 'Run' }],
545
+ menus: {
546
+ 'editor/title': [{ command: 'sample.run', group: 'navigation' }]
547
+ },
548
+ keybindings: [{ key: 'ctrl+shift+r', command: 'sample.run' }],
549
+ debuggers: [{ type: 'node', label: 'Node Debug', program: './debug.js' }],
550
+ taskDefinitions: [{
551
+ type: 'npm',
552
+ required: ['script'],
553
+ properties: { script: { type: 'string' } }
554
+ }],
555
+ problemMatchers: [{ name: 'sample-matcher' }],
556
+ problemPatterns: [{ regexp: 'sample' }],
557
+ resourceLabelFormatters: [{ scheme: 'file' }],
558
+ authentication: [{ id: 'github', label: 'GitHub' }],
559
+ notebooks: [{ type: 'sample-notebook', displayName: 'Notebook' }],
560
+ notebookRenderer: [{ id: 'sample-renderer', displayName: 'Renderer' }],
561
+ notebookPreload: [{ type: 'sample-preload' }],
562
+ snippets: [{ language: 'typescript', path: './snippets/ts.json' }],
563
+ themes: [{ id: 'dark', label: 'Dark', path: './themes/dark.json' }],
564
+ colors: [{
565
+ id: 'sample.color',
566
+ description: 'Sample',
567
+ defaults: { light: '#fff', dark: '#000', highContrast: '#111' }
568
+ }],
569
+ iconThemes: [{ id: 'sample-icons', label: 'Icons', path: './icons/theme.json' }],
570
+ icons: {
571
+ 'sample-icon': {
572
+ description: 'Sample icon',
573
+ default: 'other-icon'
574
+ }
575
+ },
576
+ terminal: { profiles: [{ id: 'bash', title: 'Bash' }] },
577
+ localizations: [{
578
+ languageId: 'de',
579
+ languageName: 'German',
580
+ translations: [{ id: 'package', path: './package.nls.de.json' }]
581
+ }],
582
+ languages: [{ id: 'sample-lang', extensions: ['.sample'] }],
583
+ grammars: [{ language: 'sample-lang', scopeName: 'source.sample', path: './grammar.json' }]
584
+ }
585
+ }, { readGrammars });
586
+ const contributions = await (0, normalize_contributions_1.normalizeContributions)(ctx, {});
587
+ (0, chai_1.expect)(contributions.configurationDefaults).to.deep.equal({ 'sample.enabled': true });
588
+ const configuration = contributions.configuration;
589
+ (0, chai_1.expect)(configuration).to.have.lengthOf(2);
590
+ (0, chai_1.expect)(configuration[0].properties['sample.enabled'].owner).to.equal('Orchestrator Extension');
591
+ (0, chai_1.expect)(configuration[0].properties['sample.enabled'].group).to.equal('General');
592
+ (0, chai_1.expect)(contributions.submenus).to.deep.equal([{
593
+ icon: '$(gear)',
594
+ id: 'sample.submenu',
595
+ label: 'Sample'
596
+ }]);
597
+ (0, chai_1.expect)(contributions.customEditors).to.deep.equal([{
598
+ viewType: 'sample.editor',
599
+ displayName: 'Editor',
600
+ selector: [],
601
+ priority: contribution_types_1.CustomEditorPriority.default
602
+ }]);
603
+ (0, chai_1.expect)(contributions.viewsContainers).to.deep.equal({
604
+ left: [{
605
+ id: 'sample.container',
606
+ title: 'Sample',
607
+ iconUrl: 'hostedPlugin/acme_orchestrator/media/icon.png',
608
+ themeIcon: undefined,
609
+ when: undefined
610
+ }],
611
+ bottom: [{
612
+ id: 'panel.container',
613
+ title: 'Panel',
614
+ iconUrl: 'hostedPlugin/acme_orchestrator/%24(panel)',
615
+ themeIcon: '$(panel)',
616
+ when: undefined
617
+ }]
618
+ });
619
+ (0, chai_1.expect)(contributions.views).to.deep.equal({
620
+ explorer: [{ id: 'sample.view', name: 'Sample View', when: undefined, type: undefined }]
621
+ });
622
+ (0, chai_1.expect)(contributions.viewsWelcome).to.deep.equal([{
623
+ view: 'sample.view',
624
+ content: 'Welcome',
625
+ when: undefined,
626
+ enablement: undefined,
627
+ order: 0
628
+ }]);
629
+ (0, chai_1.expect)(contributions.menus).to.deep.equal({
630
+ 'editor/title': [{ command: 'sample.run', group: 'navigation', submenu: undefined, alt: undefined, when: undefined }]
631
+ });
632
+ (0, chai_1.expect)(contributions.debuggers).to.deep.equal([{
633
+ type: 'node',
634
+ label: 'Node Debug',
635
+ program: './debug.js',
636
+ languages: undefined,
637
+ enableBreakpointsFor: undefined,
638
+ variables: undefined,
639
+ adapterExecutableCommand: undefined,
640
+ configurationSnippets: undefined,
641
+ win: undefined,
642
+ winx86: undefined,
643
+ windows: undefined,
644
+ osx: undefined,
645
+ linux: undefined,
646
+ args: undefined,
647
+ runtime: undefined,
648
+ runtimeArgs: undefined,
649
+ configurationAttributes: undefined
650
+ }]);
651
+ (0, chai_1.expect)(contributions.taskDefinitions).to.deep.equal([{
652
+ taskType: 'npm',
653
+ source: 'orchestrator',
654
+ properties: {
655
+ required: ['script'],
656
+ all: ['script'],
657
+ schema: {
658
+ type: 'object',
659
+ required: ['script'],
660
+ properties: {
661
+ type: { type: 'string', const: 'npm' },
662
+ script: { type: 'string' }
663
+ }
664
+ }
665
+ }
666
+ }]);
667
+ (0, chai_1.expect)(contributions.problemMatchers).to.deep.equal([{ name: 'sample-matcher' }]);
668
+ (0, chai_1.expect)(contributions.problemPatterns).to.deep.equal([{ regexp: 'sample' }]);
669
+ (0, chai_1.expect)(contributions.resourceLabelFormatters).to.deep.equal([{ scheme: 'file' }]);
670
+ (0, chai_1.expect)(contributions.authentication).to.deep.equal([{ id: 'github', label: 'GitHub' }]);
671
+ (0, chai_1.expect)(contributions.notebooks).to.deep.equal([{ type: 'sample-notebook', displayName: 'Notebook' }]);
672
+ (0, chai_1.expect)(contributions.notebookRenderer).to.deep.equal([{ id: 'sample-renderer', displayName: 'Renderer' }]);
673
+ (0, chai_1.expect)(contributions.notebookPreload).to.deep.equal([{ type: 'sample-preload' }]);
674
+ (0, chai_1.expect)(contributions.snippets).to.deep.equal([{
675
+ language: 'typescript',
676
+ source: 'Orchestrator Extension',
677
+ uri: 'hostedPlugin/acme_orchestrator/snippets/ts.json'
678
+ }]);
679
+ (0, chai_1.expect)(contributions.themes).to.deep.equal([{
680
+ id: 'dark',
681
+ label: 'Dark',
682
+ uri: 'hostedPlugin/acme_orchestrator/themes/dark.json',
683
+ description: undefined,
684
+ uiTheme: undefined
685
+ }]);
686
+ (0, chai_1.expect)(contributions.colors).to.deep.equal([{
687
+ id: 'sample.color',
688
+ description: 'Sample',
689
+ defaults: { light: '#fff', dark: '#000', hc: '#111' }
690
+ }]);
691
+ (0, chai_1.expect)(contributions.iconThemes).to.deep.equal([{
692
+ id: 'sample-icons',
693
+ label: 'Icons',
694
+ uri: 'hostedPlugin/acme_orchestrator/icons/theme.json',
695
+ description: undefined,
696
+ uiTheme: undefined
697
+ }]);
698
+ (0, chai_1.expect)(contributions.icons).to.deep.equal([{
699
+ id: 'sample-icon',
700
+ extensionId: 'acme.orchestrator',
701
+ description: 'Sample icon',
702
+ defaults: { id: 'other-icon' }
703
+ }]);
704
+ (0, chai_1.expect)(contributions.terminalProfiles).to.deep.equal([{ id: 'bash', title: 'Bash' }]);
705
+ (0, chai_1.expect)(contributions.localizations).to.deep.equal([{
706
+ languageId: 'de',
707
+ languageName: 'German',
708
+ localizedLanguageName: undefined,
709
+ minimalTranslations: undefined,
710
+ translations: [{ id: 'package', path: './package.nls.de.json' }]
711
+ }]);
712
+ (0, chai_1.expect)(contributions.languages).to.deep.equal([{
713
+ id: 'sample-lang',
714
+ aliases: undefined,
715
+ extensions: ['.sample'],
716
+ filenamePatterns: undefined,
717
+ filenames: undefined,
718
+ firstLine: undefined,
719
+ mimetypes: undefined,
720
+ icon: undefined
721
+ }]);
722
+ (0, chai_1.expect)(contributions.grammars).to.deep.equal([{
723
+ language: 'sample-lang',
724
+ scope: 'source.sample',
725
+ format: 'json',
726
+ grammar: { scopeName: 'source.sample' },
727
+ grammarLocation: './grammar.json'
728
+ }]);
729
+ });
730
+ it('merges aliased view container locations and reports async reader failures', async () => {
731
+ const errors = [];
732
+ const ctx = (0, test_helpers_1.createNormalizeCtx)({
733
+ name: 'merge',
734
+ contributes: {
735
+ viewsContainers: {
736
+ activitybar: [{ id: 'left-a', title: 'A', icon: '$(a)' }],
737
+ secondarySidebar: [{ id: 'right-a', title: 'Right', icon: '$(b)' }]
738
+ },
739
+ languages: [{ id: 'broken', configuration: './missing.json' }],
740
+ grammars: [{ language: 'broken', scopeName: 'source.broken', path: './broken.json' }]
741
+ }
742
+ }, {
743
+ readJsonFile: async () => {
744
+ throw new Error('missing language config');
745
+ },
746
+ readGrammars: async () => {
747
+ throw new Error('missing grammar');
748
+ },
749
+ onError: (kind, message) => {
750
+ errors.push({ kind, message });
751
+ }
752
+ });
753
+ const contributions = await (0, normalize_contributions_1.normalizeContributions)(ctx, {});
754
+ (0, chai_1.expect)(contributions.viewsContainers).to.deep.equal({
755
+ left: [{
756
+ id: 'left-a',
757
+ title: 'A',
758
+ iconUrl: 'hostedPlugin/acme_merge/%24(a)',
759
+ themeIcon: '$(a)',
760
+ when: undefined
761
+ }],
762
+ right: [{
763
+ id: 'right-a',
764
+ title: 'Right',
765
+ iconUrl: 'hostedPlugin/acme_merge/%24(b)',
766
+ themeIcon: '$(b)',
767
+ when: undefined
768
+ }]
769
+ });
770
+ (0, chai_1.expect)(contributions).to.not.have.property('languages');
771
+ (0, chai_1.expect)(contributions).to.not.have.property('grammars');
772
+ (0, chai_1.expect)(errors.map(error => error.kind)).to.include.members(['languages', 'grammars']);
773
+ });
774
+ it('continues after reader errors and reports them', async () => {
775
+ const errors = [];
776
+ const ctx = (0, test_helpers_1.createNormalizeCtx)({
777
+ name: 'broken',
778
+ contributes: {
779
+ commands: [{ command: 'ok', title: 'OK' }],
780
+ configuration: { title: 'Broken' }
781
+ }
782
+ }, {
783
+ readConfiguration: () => {
784
+ throw new Error('boom');
785
+ },
786
+ onError: (kind, message) => {
787
+ errors.push({ kind, message });
788
+ }
789
+ });
790
+ const contributions = await (0, normalize_contributions_1.normalizeContributions)(ctx, {});
791
+ (0, chai_1.expect)(contributions).to.have.property('commands').with.lengthOf(1);
792
+ (0, chai_1.expect)(contributions).to.have.property('configuration').that.is.empty;
793
+ (0, chai_1.expect)(errors).to.have.lengthOf(1);
794
+ (0, chai_1.expect)(errors[0].kind).to.equal('configuration');
795
+ (0, chai_1.expect)(errors[0].message).to.be.instanceOf(Error);
796
+ });
797
+ });
798
+ describe('readWalkthroughs', () => {
799
+ it('resolves media and plugin icon URLs for browser-only static hosting', async () => {
800
+ const ctx = (0, test_helpers_1.createNormalizeCtx)({
801
+ name: 'test-plugin',
802
+ publisher: 'test-publisher',
803
+ icon: 'images/icon.png',
804
+ contributes: {
805
+ walkthroughs: [{
806
+ id: 'getStarted',
807
+ title: 'Get Started',
808
+ description: 'Intro',
809
+ steps: [{
810
+ id: 'open',
811
+ title: 'Open a folder',
812
+ description: '',
813
+ media: { markdown: 'walkthroughs/open.md' }
814
+ }]
815
+ }]
816
+ }
817
+ });
818
+ const contributions = await (0, normalize_contributions_1.normalizeContributions)(ctx, {});
819
+ (0, chai_1.expect)(contributions).to.have.property('walkthroughs').with.lengthOf(1);
820
+ const walkthrough = contributions.walkthroughs[0];
821
+ (0, chai_1.expect)(walkthrough.pluginId).to.equal('test-publisher.test-plugin');
822
+ (0, chai_1.expect)(walkthrough.pluginIcon).to.equal('hostedPlugin/test_publisher_test_plugin/images/icon.png');
823
+ (0, chai_1.expect)(walkthrough.steps[0].media).to.deep.equal({
824
+ markdown: 'hostedPlugin/test_publisher_test_plugin/walkthroughs/open.md'
825
+ });
826
+ });
827
+ it('skips walkthroughs with a missing id', () => {
828
+ const warnings = [];
829
+ const ctx = (0, test_helpers_1.createNormalizeCtx)({
830
+ name: 'test-plugin',
831
+ publisher: 'test-publisher',
832
+ contributes: {
833
+ walkthroughs: [{
834
+ id: '',
835
+ title: 'Broken',
836
+ description: '',
837
+ steps: [{ id: 's1', title: 'S1', description: '' }]
838
+ }]
839
+ }
840
+ }, {
841
+ onWarn: msg => {
842
+ warnings.push(msg);
843
+ }
844
+ });
845
+ (0, chai_1.expect)((0, normalize_contributions_1.readWalkthroughs)(ctx.plugin, ctx)).to.be.undefined;
846
+ (0, chai_1.expect)(warnings).to.have.lengthOf(1);
847
+ });
848
+ });
849
+ });
850
+ //# sourceMappingURL=normalize-contributions.spec.js.map