@theia/dev-container 1.71.0-next.8 → 1.71.0

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 (56) hide show
  1. package/README.md +1 -0
  2. package/lib/electron-browser/container-connection-contribution.d.ts +8 -0
  3. package/lib/electron-browser/container-connection-contribution.d.ts.map +1 -1
  4. package/lib/electron-browser/container-connection-contribution.js +158 -6
  5. package/lib/electron-browser/container-connection-contribution.js.map +1 -1
  6. package/lib/electron-browser/dev-container-frontend-module.d.ts.map +1 -1
  7. package/lib/electron-browser/dev-container-frontend-module.js +3 -0
  8. package/lib/electron-browser/dev-container-frontend-module.js.map +1 -1
  9. package/lib/electron-browser/dev-container-suggestion-contribution.d.ts +16 -0
  10. package/lib/electron-browser/dev-container-suggestion-contribution.d.ts.map +1 -0
  11. package/lib/electron-browser/dev-container-suggestion-contribution.js +96 -0
  12. package/lib/electron-browser/dev-container-suggestion-contribution.js.map +1 -0
  13. package/lib/electron-common/remote-container-connection-provider.d.ts +9 -0
  14. package/lib/electron-common/remote-container-connection-provider.d.ts.map +1 -1
  15. package/lib/electron-node/dev-container-file-service.d.ts.map +1 -1
  16. package/lib/electron-node/dev-container-file-service.js +4 -6
  17. package/lib/electron-node/dev-container-file-service.js.map +1 -1
  18. package/lib/electron-node/devcontainer-contributions/cli-enhancing-creation-contributions.d.ts.map +1 -1
  19. package/lib/electron-node/devcontainer-contributions/cli-enhancing-creation-contributions.js +7 -1
  20. package/lib/electron-node/devcontainer-contributions/cli-enhancing-creation-contributions.js.map +1 -1
  21. package/lib/electron-node/devcontainer-contributions/cli-enhancing-creation-contributions.spec.d.ts +2 -0
  22. package/lib/electron-node/devcontainer-contributions/cli-enhancing-creation-contributions.spec.d.ts.map +1 -0
  23. package/lib/electron-node/devcontainer-contributions/cli-enhancing-creation-contributions.spec.js +421 -0
  24. package/lib/electron-node/devcontainer-contributions/cli-enhancing-creation-contributions.spec.js.map +1 -0
  25. package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.d.ts +28 -1
  26. package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.d.ts.map +1 -1
  27. package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.js +304 -4
  28. package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.js.map +1 -1
  29. package/lib/electron-node/devcontainer-contributions/variable-resolver-contribution.d.ts.map +1 -1
  30. package/lib/electron-node/devcontainer-contributions/variable-resolver-contribution.js +0 -1
  31. package/lib/electron-node/devcontainer-contributions/variable-resolver-contribution.js.map +1 -1
  32. package/lib/electron-node/devcontainer-file.d.ts +8 -1
  33. package/lib/electron-node/devcontainer-file.d.ts.map +1 -1
  34. package/lib/electron-node/devcontainer-file.js +14 -0
  35. package/lib/electron-node/devcontainer-file.js.map +1 -1
  36. package/lib/electron-node/remote-container-connection-provider.d.ts +6 -1
  37. package/lib/electron-node/remote-container-connection-provider.d.ts.map +1 -1
  38. package/lib/electron-node/remote-container-connection-provider.js +112 -4
  39. package/lib/electron-node/remote-container-connection-provider.js.map +1 -1
  40. package/lib/electron-node/remote-container-connection-provider.spec.d.ts +2 -0
  41. package/lib/electron-node/remote-container-connection-provider.spec.d.ts.map +1 -0
  42. package/lib/electron-node/remote-container-connection-provider.spec.js +131 -0
  43. package/lib/electron-node/remote-container-connection-provider.spec.js.map +1 -0
  44. package/package.json +10 -10
  45. package/src/electron-browser/container-connection-contribution.ts +173 -7
  46. package/src/electron-browser/dev-container-frontend-module.ts +4 -0
  47. package/src/electron-browser/dev-container-suggestion-contribution.ts +93 -0
  48. package/src/electron-common/remote-container-connection-provider.ts +10 -0
  49. package/src/electron-node/dev-container-file-service.ts +4 -6
  50. package/src/electron-node/devcontainer-contributions/cli-enhancing-creation-contributions.spec.ts +519 -0
  51. package/src/electron-node/devcontainer-contributions/cli-enhancing-creation-contributions.ts +7 -1
  52. package/src/electron-node/devcontainer-contributions/main-container-creation-contributions.ts +323 -5
  53. package/src/electron-node/devcontainer-contributions/variable-resolver-contribution.ts +0 -1
  54. package/src/electron-node/devcontainer-file.ts +13 -1
  55. package/src/electron-node/remote-container-connection-provider.spec.ts +152 -0
  56. package/src/electron-node/remote-container-connection-provider.ts +121 -5
@@ -0,0 +1,519 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { expect } from 'chai';
18
+ import { SettingsContribution } from './cli-enhancing-creation-contributions';
19
+ import { RemoteCliContext } from '@theia/core/lib/node/remote/remote-cli-contribution';
20
+ import { OS } from '@theia/core/lib/common/os';
21
+ import * as Docker from 'dockerode';
22
+
23
+ describe('SettingsContribution', () => {
24
+ let settingsContribution: SettingsContribution;
25
+
26
+ beforeEach(() => {
27
+ settingsContribution = new SettingsContribution();
28
+ });
29
+
30
+ describe('base64 encoding for complex values', () => {
31
+
32
+ it('should encode nested objects as base64', async () => {
33
+ const nestedSettings = {
34
+ 'editor.codeActionsOnSave': {
35
+ 'source.fixAll': true,
36
+ 'source.organizeImports': true
37
+ }
38
+ };
39
+
40
+ const containerConfig = {
41
+ image: 'test',
42
+ settings: nestedSettings
43
+ };
44
+
45
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
46
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
47
+ const args = settingsContribution.enhanceArgs(context);
48
+
49
+ expect(args).to.have.lengthOf(1);
50
+ expect(args[0]).to.match(/^--set-preference=editor\.codeActionsOnSave=base64:/);
51
+
52
+ const base64Part = args[0].split('base64:')[1];
53
+ const decoded = JSON.parse(Buffer.from(base64Part, 'base64').toString('utf-8'));
54
+ expect(decoded).to.deep.equal(nestedSettings['editor.codeActionsOnSave']);
55
+ });
56
+
57
+ it('should encode deeply nested objects as base64', async () => {
58
+ const deeplyNestedSettings = {
59
+ 'complex.setting': {
60
+ level1: {
61
+ level2: {
62
+ level3: {
63
+ value: 'deep'
64
+ }
65
+ }
66
+ }
67
+ }
68
+ };
69
+
70
+ const containerConfig = {
71
+ image: 'test',
72
+ settings: deeplyNestedSettings
73
+ };
74
+
75
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
76
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
77
+ const args = settingsContribution.enhanceArgs(context);
78
+
79
+ expect(args).to.have.lengthOf(1);
80
+ expect(args[0]).to.match(/^--set-preference=complex\.setting=base64:/);
81
+
82
+ const base64Part = args[0].split('base64:')[1];
83
+ const decoded = JSON.parse(Buffer.from(base64Part, 'base64').toString('utf-8'));
84
+ expect(decoded).to.deep.equal(deeplyNestedSettings['complex.setting']);
85
+ });
86
+
87
+ it('should encode arrays as base64', async () => {
88
+ const arraySettings = {
89
+ 'files.exclude': ['**/.git', '**/.svn', '**/node_modules']
90
+ };
91
+
92
+ const containerConfig = {
93
+ image: 'test',
94
+ settings: arraySettings
95
+ };
96
+
97
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
98
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
99
+ const args = settingsContribution.enhanceArgs(context);
100
+
101
+ expect(args).to.have.lengthOf(1);
102
+ expect(args[0]).to.match(/^--set-preference=files\.exclude=base64:/);
103
+
104
+ const base64Part = args[0].split('base64:')[1];
105
+ const decoded = JSON.parse(Buffer.from(base64Part, 'base64').toString('utf-8'));
106
+ expect(decoded).to.deep.equal(arraySettings['files.exclude']);
107
+ });
108
+
109
+ it('should encode arrays of objects as base64', async () => {
110
+ const complexArraySettings = {
111
+ 'launch.configurations': [
112
+ { type: 'node', request: 'launch', name: 'Launch Program' },
113
+ { type: 'chrome', request: 'attach', name: 'Attach to Chrome' }
114
+ ]
115
+ };
116
+
117
+ const containerConfig = {
118
+ image: 'test',
119
+ settings: complexArraySettings
120
+ };
121
+
122
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
123
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
124
+ const args = settingsContribution.enhanceArgs(context);
125
+
126
+ expect(args).to.have.lengthOf(1);
127
+ expect(args[0]).to.match(/^--set-preference=launch\.configurations=base64:/);
128
+
129
+ const base64Part = args[0].split('base64:')[1];
130
+ const decoded = JSON.parse(Buffer.from(base64Part, 'base64').toString('utf-8'));
131
+ expect(decoded).to.deep.equal(complexArraySettings['launch.configurations']);
132
+ });
133
+
134
+ it('should encode empty objects as base64', async () => {
135
+ const emptyObjectSettings = {
136
+ 'empty.object': {}
137
+ };
138
+
139
+ const containerConfig = {
140
+ image: 'test',
141
+ settings: emptyObjectSettings
142
+ };
143
+
144
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
145
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
146
+ const args = settingsContribution.enhanceArgs(context);
147
+
148
+ expect(args).to.have.lengthOf(1);
149
+ expect(args[0]).to.match(/^--set-preference=empty\.object=base64:/);
150
+
151
+ const base64Part = args[0].split('base64:')[1];
152
+ const decoded = JSON.parse(Buffer.from(base64Part, 'base64').toString('utf-8'));
153
+ expect(decoded).to.deep.equal({});
154
+ });
155
+
156
+ it('should encode empty arrays as base64', async () => {
157
+ const emptyArraySettings = {
158
+ 'empty.array': []
159
+ };
160
+
161
+ const containerConfig = {
162
+ image: 'test',
163
+ settings: emptyArraySettings
164
+ };
165
+
166
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
167
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
168
+ const args = settingsContribution.enhanceArgs(context);
169
+
170
+ expect(args).to.have.lengthOf(1);
171
+ expect(args[0]).to.match(/^--set-preference=empty\.array=base64:/);
172
+
173
+ const base64Part = args[0].split('base64:')[1];
174
+ const decoded = JSON.parse(Buffer.from(base64Part, 'base64').toString('utf-8'));
175
+ expect(decoded).to.deep.equal([]);
176
+ });
177
+
178
+ it('should handle values containing equals signs in objects', async () => {
179
+ const settingsWithEquals = {
180
+ 'test.config': {
181
+ formula: 'a=b+c',
182
+ equation: 'x=y=z'
183
+ }
184
+ };
185
+
186
+ const containerConfig = {
187
+ image: 'test',
188
+ settings: settingsWithEquals
189
+ };
190
+
191
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
192
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
193
+ const args = settingsContribution.enhanceArgs(context);
194
+
195
+ expect(args).to.have.lengthOf(1);
196
+ expect(args[0]).to.match(/^--set-preference=test\.config=base64:/);
197
+
198
+ const base64Part = args[0].split('base64:')[1];
199
+ const decoded = JSON.parse(Buffer.from(base64Part, 'base64').toString('utf-8'));
200
+ expect(decoded).to.deep.equal(settingsWithEquals['test.config']);
201
+ expect(decoded.formula).to.equal('a=b+c');
202
+ expect(decoded.equation).to.equal('x=y=z');
203
+ });
204
+ });
205
+
206
+ describe('round-trip encode/decode validation', () => {
207
+
208
+ const simulateDecode = (encodedArg: string): unknown => {
209
+ const prefixRemoved = encodedArg.substring('--set-preference='.length);
210
+ const firstEqualIndex = prefixRemoved.indexOf('=');
211
+ let rawValue = prefixRemoved.substring(firstEqualIndex + 1);
212
+ if (rawValue.startsWith('base64:')) {
213
+ rawValue = Buffer.from(rawValue.substring('base64:'.length), 'base64').toString('utf-8');
214
+ }
215
+ return JSON.parse(rawValue);
216
+ };
217
+
218
+ it('should successfully round-trip nested objects', async () => {
219
+ const originalValue = {
220
+ nested: {
221
+ deep: {
222
+ value: 'test',
223
+ number: 42
224
+ }
225
+ }
226
+ };
227
+
228
+ const containerConfig = {
229
+ image: 'test',
230
+ settings: { 'test.setting': originalValue }
231
+ };
232
+
233
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
234
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
235
+ const args = settingsContribution.enhanceArgs(context);
236
+
237
+ const decodedValue = simulateDecode(args[0]);
238
+ expect(decodedValue).to.deep.equal(originalValue);
239
+ });
240
+
241
+ it('should successfully round-trip arrays', async () => {
242
+ const originalValue = ['item1', 'item2', 'item3'];
243
+
244
+ const containerConfig = {
245
+ image: 'test',
246
+ settings: { 'test.array': originalValue }
247
+ };
248
+
249
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
250
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
251
+ const args = settingsContribution.enhanceArgs(context);
252
+
253
+ const decodedValue = simulateDecode(args[0]);
254
+ expect(decodedValue).to.deep.equal(originalValue);
255
+ });
256
+
257
+ it('should successfully round-trip values with equals signs', async () => {
258
+ const originalValue = {
259
+ equation: 'a=b=c',
260
+ formula: 'x=y'
261
+ };
262
+
263
+ const containerConfig = {
264
+ image: 'test',
265
+ settings: { 'test.equals': originalValue }
266
+ };
267
+
268
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
269
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
270
+ const args = settingsContribution.enhanceArgs(context);
271
+
272
+ const decodedValue = simulateDecode(args[0]);
273
+ expect(decodedValue).to.deep.equal(originalValue);
274
+ });
275
+
276
+ it('should successfully round-trip empty objects', async () => {
277
+ const originalValue = {};
278
+
279
+ const containerConfig = {
280
+ image: 'test',
281
+ settings: { 'test.empty': originalValue }
282
+ };
283
+
284
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
285
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
286
+ const args = settingsContribution.enhanceArgs(context);
287
+
288
+ const decodedValue = simulateDecode(args[0]);
289
+ expect(decodedValue).to.deep.equal(originalValue);
290
+ });
291
+
292
+ it('should successfully round-trip mixed complex structures', async () => {
293
+ const originalValue = {
294
+ string: 'test',
295
+ number: 123,
296
+ boolean: true,
297
+ array: [1, 'two', { three: 3 }],
298
+ nested: {
299
+ deep: {
300
+ value: 'with=equals'
301
+ }
302
+ }
303
+ };
304
+
305
+ const containerConfig = {
306
+ image: 'test',
307
+ settings: { 'test.complex': originalValue }
308
+ };
309
+
310
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
311
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
312
+ const args = settingsContribution.enhanceArgs(context);
313
+
314
+ const decodedValue = simulateDecode(args[0]);
315
+ expect(decodedValue).to.deep.equal(originalValue);
316
+ });
317
+ });
318
+
319
+ describe('primitive values (not base64 encoded)', () => {
320
+
321
+ it('should not encode string values as base64', async () => {
322
+ const stringSettings = {
323
+ 'editor.fontSize': '14'
324
+ };
325
+
326
+ const containerConfig = {
327
+ image: 'test',
328
+ settings: stringSettings
329
+ };
330
+
331
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
332
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
333
+ const args = settingsContribution.enhanceArgs(context);
334
+
335
+ expect(args).to.have.lengthOf(1);
336
+ expect(args[0]).to.equal('--set-preference=editor.fontSize="14"');
337
+ expect(args[0]).to.not.include('base64:');
338
+ });
339
+
340
+ it('should not encode number values as base64', async () => {
341
+ const numberSettings = {
342
+ 'editor.tabSize': 4
343
+ };
344
+
345
+ const containerConfig = {
346
+ image: 'test',
347
+ settings: numberSettings
348
+ };
349
+
350
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
351
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
352
+ const args = settingsContribution.enhanceArgs(context);
353
+
354
+ expect(args).to.have.lengthOf(1);
355
+ expect(args[0]).to.equal('--set-preference=editor.tabSize=4');
356
+ expect(args[0]).to.not.include('base64:');
357
+ });
358
+
359
+ it('should not encode boolean values as base64', async () => {
360
+ const booleanSettings = {
361
+ 'editor.wordWrap': true
362
+ };
363
+
364
+ const containerConfig = {
365
+ image: 'test',
366
+ settings: booleanSettings
367
+ };
368
+
369
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
370
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
371
+ const args = settingsContribution.enhanceArgs(context);
372
+
373
+ expect(args).to.have.lengthOf(1);
374
+ expect(args[0]).to.equal('--set-preference=editor.wordWrap=true');
375
+ expect(args[0]).to.not.include('base64:');
376
+ });
377
+
378
+ it('should handle strings containing equals signs without base64', async () => {
379
+ const stringWithEquals = {
380
+ 'test.value': 'a=b'
381
+ };
382
+
383
+ const containerConfig = {
384
+ image: 'test',
385
+ settings: stringWithEquals
386
+ };
387
+
388
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
389
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
390
+ const args = settingsContribution.enhanceArgs(context);
391
+
392
+ expect(args).to.have.lengthOf(1);
393
+ expect(args[0]).to.equal('--set-preference=test.value="a=b"');
394
+ expect(args[0]).to.not.include('base64:');
395
+ });
396
+ });
397
+
398
+ describe('vscode customizations', () => {
399
+
400
+ it('should merge settings from customizations.vscode.settings', async () => {
401
+ const containerConfig = {
402
+ image: 'test',
403
+ settings: {
404
+ 'editor.fontSize': '14'
405
+ },
406
+ customizations: {
407
+ vscode: {
408
+ settings: {
409
+ 'editor.tabSize': 4,
410
+ 'files.exclude': ['**/.git']
411
+ }
412
+ }
413
+ }
414
+ };
415
+
416
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
417
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
418
+ const args = settingsContribution.enhanceArgs(context);
419
+
420
+ expect(args).to.have.lengthOf(3);
421
+ expect(args.some(arg => arg.includes('editor.fontSize'))).to.be.true;
422
+ expect(args.some(arg => arg.includes('editor.tabSize'))).to.be.true;
423
+ expect(args.some(arg => arg.includes('files.exclude'))).to.be.true;
424
+ });
425
+
426
+ it('should override settings with customizations.vscode.settings', async () => {
427
+ const containerConfig = {
428
+ image: 'test',
429
+ settings: {
430
+ 'editor.fontSize': '14'
431
+ },
432
+ customizations: {
433
+ vscode: {
434
+ settings: {
435
+ 'editor.fontSize': '16'
436
+ }
437
+ }
438
+ }
439
+ };
440
+
441
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
442
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
443
+ const args = settingsContribution.enhanceArgs(context);
444
+
445
+ expect(args).to.have.lengthOf(1);
446
+ expect(args[0]).to.equal('--set-preference=editor.fontSize="16"');
447
+ });
448
+ });
449
+
450
+ describe('edge cases', () => {
451
+
452
+ it('should return empty array when no config is set', () => {
453
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
454
+ const args = settingsContribution.enhanceArgs(context);
455
+ expect(args).to.be.empty;
456
+ });
457
+
458
+ it('should handle config with no settings', async () => {
459
+ const containerConfig = {
460
+ image: 'test'
461
+ };
462
+
463
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
464
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
465
+ const args = settingsContribution.enhanceArgs(context);
466
+
467
+ expect(args).to.be.empty;
468
+ });
469
+
470
+ it('should clear config after enhanceArgs is called', async () => {
471
+ const containerConfig = {
472
+ image: 'test',
473
+ settings: { 'test.setting': 'value' }
474
+ };
475
+
476
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
477
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
478
+ const firstCall = settingsContribution.enhanceArgs(context);
479
+ const secondCall = settingsContribution.enhanceArgs(context);
480
+
481
+ expect(firstCall).to.have.lengthOf(1);
482
+ expect(secondCall).to.be.empty;
483
+ });
484
+
485
+ it('should handle undefined values', async () => {
486
+ const containerConfig = {
487
+ image: 'test',
488
+ settings: {
489
+ 'test.undefined': undefined
490
+ }
491
+ };
492
+
493
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
494
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
495
+ const args = settingsContribution.enhanceArgs(context);
496
+
497
+ expect(args).to.have.lengthOf(1);
498
+ expect(args[0]).to.equal('--set-preference=test.undefined=undefined');
499
+ });
500
+
501
+ it('should handle special characters in setting keys', async () => {
502
+ const containerConfig = {
503
+ image: 'test',
504
+ settings: {
505
+ 'setting.with-dash': 'value',
506
+ 'setting.with_underscore': 'value'
507
+ }
508
+ };
509
+
510
+ await settingsContribution.handleContainerCreation({} as Docker.ContainerCreateOptions, containerConfig);
511
+ const context: RemoteCliContext = { platform: { os: OS.Type.Linux, arch: 'x64' }, directory: '/workspace' };
512
+ const args = settingsContribution.enhanceArgs(context);
513
+
514
+ expect(args).to.have.lengthOf(2);
515
+ expect(args.some(arg => arg.includes('setting.with-dash'))).to.be.true;
516
+ expect(args.some(arg => arg.includes('setting.with_underscore'))).to.be.true;
517
+ });
518
+ });
519
+ });
@@ -59,7 +59,13 @@ export class SettingsContribution implements RemoteCliContribution, ContainerCre
59
59
  ...(this.currentConfig.customizations?.vscode?.settings ?? [])
60
60
  };
61
61
  this.currentConfig = undefined;
62
- return Object.entries(settings).map(([key, value]) => `--set-preference=${key}=${JSON.stringify(value)}`) ?? [];
62
+ return Object.entries(settings).map(([key, value]) => {
63
+ const jsonValue = JSON.stringify(value);
64
+ if (value && typeof value === 'object') {
65
+ return `--set-preference=${key}=base64:${Buffer.from(jsonValue).toString('base64')}`;
66
+ }
67
+ return `--set-preference=${key}=${jsonValue}`;
68
+ });
63
69
  }
64
70
 
65
71
  async handleContainerCreation(createOptions: Docker.ContainerCreateOptions, containerConfig: DevContainerConfiguration): Promise<void> {