@theia/dev-container 1.72.0-next.59 → 1.72.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/lib/electron-browser/container-connection-contribution.d.ts +5 -2
  2. package/lib/electron-browser/container-connection-contribution.d.ts.map +1 -1
  3. package/lib/electron-browser/container-connection-contribution.js +130 -30
  4. package/lib/electron-browser/container-connection-contribution.js.map +1 -1
  5. package/lib/electron-browser/container-output-provider.d.ts.map +1 -1
  6. package/lib/electron-browser/container-output-provider.js +3 -1
  7. package/lib/electron-browser/container-output-provider.js.map +1 -1
  8. package/lib/electron-browser/dev-container-frontend-module.d.ts.map +1 -1
  9. package/lib/electron-browser/dev-container-frontend-module.js +5 -0
  10. package/lib/electron-browser/dev-container-frontend-module.js.map +1 -1
  11. package/lib/electron-browser/dev-container-startup-contribution.d.ts +15 -0
  12. package/lib/electron-browser/dev-container-startup-contribution.d.ts.map +1 -0
  13. package/lib/electron-browser/dev-container-startup-contribution.js +94 -0
  14. package/lib/electron-browser/dev-container-startup-contribution.js.map +1 -0
  15. package/lib/electron-common/dev-container-preferences.d.ts +12 -0
  16. package/lib/electron-common/dev-container-preferences.d.ts.map +1 -0
  17. package/lib/electron-common/dev-container-preferences.js +44 -0
  18. package/lib/electron-common/dev-container-preferences.js.map +1 -0
  19. package/lib/electron-common/remote-container-connection-provider.d.ts +20 -1
  20. package/lib/electron-common/remote-container-connection-provider.d.ts.map +1 -1
  21. package/lib/electron-node/dev-container-backend-module.d.ts.map +1 -1
  22. package/lib/electron-node/dev-container-backend-module.js +4 -0
  23. package/lib/electron-node/dev-container-backend-module.js.map +1 -1
  24. package/lib/electron-node/dev-container-cli-contribution.d.ts +19 -0
  25. package/lib/electron-node/dev-container-cli-contribution.d.ts.map +1 -0
  26. package/lib/electron-node/dev-container-cli-contribution.js +66 -0
  27. package/lib/electron-node/dev-container-cli-contribution.js.map +1 -0
  28. package/lib/electron-node/dev-container-cli-contribution.spec.d.ts +2 -0
  29. package/lib/electron-node/dev-container-cli-contribution.spec.d.ts.map +1 -0
  30. package/lib/electron-node/dev-container-cli-contribution.spec.js +91 -0
  31. package/lib/electron-node/dev-container-cli-contribution.spec.js.map +1 -0
  32. package/lib/electron-node/dev-container-file-service.d.ts +4 -4
  33. package/lib/electron-node/dev-container-file-service.d.ts.map +1 -1
  34. package/lib/electron-node/dev-container-file-service.js +9 -9
  35. package/lib/electron-node/dev-container-file-service.js.map +1 -1
  36. package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.d.ts +6 -2
  37. package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.d.ts.map +1 -1
  38. package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.js +24 -4
  39. package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.js.map +1 -1
  40. package/lib/electron-node/devcontainer-contributions/variable-resolver-contribution.d.ts +7 -6
  41. package/lib/electron-node/devcontainer-contributions/variable-resolver-contribution.d.ts.map +1 -1
  42. package/lib/electron-node/devcontainer-contributions/variable-resolver-contribution.js +4 -9
  43. package/lib/electron-node/devcontainer-contributions/variable-resolver-contribution.js.map +1 -1
  44. package/lib/electron-node/devcontainer-util.d.ts +19 -0
  45. package/lib/electron-node/devcontainer-util.d.ts.map +1 -0
  46. package/lib/electron-node/devcontainer-util.js +48 -0
  47. package/lib/electron-node/devcontainer-util.js.map +1 -0
  48. package/lib/electron-node/devcontainer-util.spec.d.ts +2 -0
  49. package/lib/electron-node/devcontainer-util.spec.d.ts.map +1 -0
  50. package/lib/electron-node/devcontainer-util.spec.js +128 -0
  51. package/lib/electron-node/devcontainer-util.spec.js.map +1 -0
  52. package/lib/electron-node/docker-container-service.d.ts +3 -3
  53. package/lib/electron-node/docker-container-service.d.ts.map +1 -1
  54. package/lib/electron-node/docker-container-service.js +3 -4
  55. package/lib/electron-node/docker-container-service.js.map +1 -1
  56. package/lib/electron-node/remote-container-connection-provider.d.ts +27 -66
  57. package/lib/electron-node/remote-container-connection-provider.d.ts.map +1 -1
  58. package/lib/electron-node/remote-container-connection-provider.js +269 -311
  59. package/lib/electron-node/remote-container-connection-provider.js.map +1 -1
  60. package/lib/electron-node/remote-docker-container-connection.d.ts +50 -0
  61. package/lib/electron-node/remote-docker-container-connection.d.ts.map +1 -0
  62. package/lib/electron-node/remote-docker-container-connection.js +239 -0
  63. package/lib/electron-node/remote-docker-container-connection.js.map +1 -0
  64. package/lib/electron-node/remote-docker-container-connection.spec.d.ts +2 -0
  65. package/lib/electron-node/remote-docker-container-connection.spec.d.ts.map +1 -0
  66. package/lib/electron-node/remote-docker-container-connection.spec.js +217 -0
  67. package/lib/electron-node/remote-docker-container-connection.spec.js.map +1 -0
  68. package/package.json +7 -7
  69. package/src/electron-browser/container-connection-contribution.ts +155 -38
  70. package/src/electron-browser/container-output-provider.ts +3 -1
  71. package/src/electron-browser/dev-container-frontend-module.ts +6 -0
  72. package/src/electron-browser/dev-container-startup-contribution.ts +99 -0
  73. package/src/electron-common/dev-container-preferences.ts +53 -0
  74. package/src/electron-common/remote-container-connection-provider.ts +23 -1
  75. package/src/electron-node/dev-container-backend-module.ts +5 -0
  76. package/src/electron-node/dev-container-cli-contribution.spec.ts +106 -0
  77. package/src/electron-node/dev-container-cli-contribution.ts +68 -0
  78. package/src/electron-node/dev-container-file-service.ts +10 -10
  79. package/src/electron-node/devcontainer-contributions/main-container-creation-contributions.ts +29 -5
  80. package/src/electron-node/devcontainer-contributions/variable-resolver-contribution.ts +11 -11
  81. package/src/electron-node/devcontainer-util.spec.ts +154 -0
  82. package/src/electron-node/devcontainer-util.ts +49 -0
  83. package/src/electron-node/docker-container-service.ts +6 -7
  84. package/src/electron-node/remote-container-connection-provider.ts +274 -366
  85. package/src/electron-node/{remote-container-connection-provider.spec.ts → remote-docker-container-connection.spec.ts} +105 -4
  86. package/src/electron-node/remote-docker-container-connection.ts +290 -0
  87. package/lib/electron-node/remote-container-connection-provider.spec.d.ts +0 -2
  88. package/lib/electron-node/remote-container-connection-provider.spec.d.ts.map +0 -1
  89. package/lib/electron-node/remote-container-connection-provider.spec.js +0 -131
  90. package/lib/electron-node/remote-container-connection-provider.spec.js.map +0 -1
@@ -15,7 +15,7 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { expect } from 'chai';
18
- import { RemoteDockerContainerConnection } from './remote-container-connection-provider';
18
+ import { RemoteDockerContainerConnection } from './remote-docker-container-connection';
19
19
  import { DevContainerConfiguration } from './devcontainer-file';
20
20
  import { ILogger } from '@theia/core';
21
21
  import * as Docker from 'dockerode';
@@ -24,11 +24,14 @@ class TestableDockerContainerConnection extends RemoteDockerContainerConnection
24
24
  public testGetRemoteEnv(): string[] | undefined {
25
25
  return this.getRemoteEnv();
26
26
  }
27
+ public testBuildShellCommand(cmd: string, args?: string[]): string {
28
+ return this.buildShellCommand(cmd, args);
29
+ }
27
30
  }
28
31
 
29
32
  function createConnection(config: DevContainerConfiguration): TestableDockerContainerConnection {
30
33
  const mockDocker = {
31
- getEvents: () => Promise.resolve({ on: () => { } })
34
+ getEvents: () => Promise.resolve({ on: () => { }, destroy: () => { } })
32
35
  } as unknown as Docker;
33
36
  const mockContainer = {} as unknown as Docker.Container;
34
37
  const mockLogger = {} as ILogger;
@@ -102,8 +105,7 @@ describe('RemoteDockerContainerConnection', () => {
102
105
  }
103
106
  } as DevContainerConfiguration);
104
107
 
105
- const env = connection.testGetRemoteEnv();
106
- expect(env).to.have.lengthOf(0);
108
+ expect(connection.testGetRemoteEnv()).to.be.undefined;
107
109
  });
108
110
 
109
111
  it('should handle values containing equals signs', () => {
@@ -149,4 +151,103 @@ describe('RemoteDockerContainerConnection', () => {
149
151
  expect(env).to.include('SPACED=hello world');
150
152
  });
151
153
  });
154
+
155
+ describe('buildShellCommand', () => {
156
+
157
+ it('should return cmd unchanged when no args are provided', () => {
158
+ const connection = createConnection({ image: 'test' } as DevContainerConfiguration);
159
+ expect(connection.testBuildShellCommand('echo')).to.equal('echo');
160
+ expect(connection.testBuildShellCommand('echo', [])).to.equal('echo');
161
+ });
162
+
163
+ it('should strong-quote arguments to prevent shell expansion', () => {
164
+ const connection = createConnection({ image: 'test' } as DevContainerConfiguration);
165
+ expect(connection.testBuildShellCommand('echo', ['hello world'])).to.equal("echo 'hello world'");
166
+ });
167
+
168
+ it('should handle arguments with dollar signs', () => {
169
+ const connection = createConnection({ image: 'test' } as DevContainerConfiguration);
170
+ expect(connection.testBuildShellCommand('echo', ['$HOME'])).to.equal("echo '$HOME'");
171
+ });
172
+
173
+ it('should handle arguments with backticks', () => {
174
+ const connection = createConnection({ image: 'test' } as DevContainerConfiguration);
175
+ expect(connection.testBuildShellCommand('echo', ['`whoami`'])).to.equal("echo '`whoami`'");
176
+ });
177
+
178
+ it('should handle arguments with single quotes', () => {
179
+ const connection = createConnection({ image: 'test' } as DevContainerConfiguration);
180
+ // Single quotes inside strong-quoted strings are handled by breaking out and using double-quoted quote
181
+ expect(connection.testBuildShellCommand('echo', ["it's"])).to.equal('echo \'it\'"\'"\'s\'');
182
+ });
183
+
184
+ it('should handle arguments with double quotes', () => {
185
+ const connection = createConnection({ image: 'test' } as DevContainerConfiguration);
186
+ expect(connection.testBuildShellCommand('echo', ['say "hi"'])).to.equal("echo 'say \"hi\"'");
187
+ });
188
+
189
+ it('should handle multiple arguments', () => {
190
+ const connection = createConnection({ image: 'test' } as DevContainerConfiguration);
191
+ expect(connection.testBuildShellCommand('node', ['server.js', '--port=8080'])).to.equal("node 'server.js' '--port=8080'");
192
+ });
193
+
194
+ it('should handle arguments with newlines', () => {
195
+ const connection = createConnection({ image: 'test' } as DevContainerConfiguration);
196
+ const result = connection.testBuildShellCommand('echo', ['line1\nline2']);
197
+ // Literal newline is preserved inside single quotes
198
+ expect(result).to.equal("echo 'line1\nline2'");
199
+ });
200
+ });
201
+
202
+ describe('shutdownContainer', () => {
203
+
204
+ function createConnectionWithConfig(config: Partial<DevContainerConfiguration>): { connection: RemoteDockerContainerConnection; stopCalled: () => boolean } {
205
+ const state = { stopCalled: false };
206
+ const mockDocker = {
207
+ getEvents: () => Promise.resolve({ on: () => { }, destroy: () => { } })
208
+ } as unknown as Docker;
209
+ const mockContainer = {
210
+ id: 'test-container-id',
211
+ stop: () => { state.stopCalled = true; return Promise.resolve(); }
212
+ } as unknown as Docker.Container;
213
+ const mockLogger = {} as ILogger;
214
+ const connection = new RemoteDockerContainerConnection({
215
+ id: 'test-id',
216
+ name: 'test',
217
+ type: 'Dev Container',
218
+ docker: mockDocker,
219
+ container: mockContainer,
220
+ config: config as DevContainerConfiguration,
221
+ logger: mockLogger
222
+ });
223
+ return { connection, stopCalled: () => state.stopCalled };
224
+ }
225
+
226
+ it('should not stop container when shutdownAction is none', async () => {
227
+ const { connection, stopCalled } = createConnectionWithConfig({ shutdownAction: 'none' });
228
+ await connection.dispose();
229
+ expect(stopCalled()).to.equal(false);
230
+ });
231
+
232
+ it('should stop container when shutdownAction is stopContainer', async () => {
233
+ const { connection, stopCalled } = createConnectionWithConfig({ shutdownAction: 'stopContainer' });
234
+ await connection.dispose();
235
+ expect(stopCalled()).to.equal(true);
236
+ });
237
+
238
+ it('should default to stopContainer when shutdownAction is not set and no dockerComposeFile', async () => {
239
+ const { connection, stopCalled } = createConnectionWithConfig({});
240
+ await connection.dispose();
241
+ expect(stopCalled()).to.equal(true);
242
+ });
243
+
244
+ it('should not stop container when shutdownAction is none even with dockerComposeFile', async () => {
245
+ const { connection, stopCalled } = createConnectionWithConfig({
246
+ shutdownAction: 'none',
247
+ dockerComposeFile: 'docker-compose.yml'
248
+ });
249
+ await connection.dispose();
250
+ expect(stopCalled()).to.equal(false);
251
+ });
252
+ });
152
253
  });
@@ -0,0 +1,290 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource GmbH 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 { RemoteConnection, RemoteExecOptions, RemoteExecResult, RemoteExecTester } from '@theia/remote/lib/electron-node/remote-types';
18
+ import { RemoteSetupResult } from '@theia/remote/lib/electron-node/setup/remote-setup-service';
19
+ import { Emitter, Event, ILogger } from '@theia/core';
20
+ import { BashQuotingFunctions, ShellQuoting, createShellCommandLine } from '@theia/core/lib/common/shell-quoting';
21
+ import { Socket } from 'net';
22
+ import * as Docker from 'dockerode';
23
+ import { Deferred } from '@theia/core/lib/common/promise-util';
24
+ import { PassThrough } from 'stream';
25
+ import { execFile, execFileSync } from 'child_process';
26
+ import { DevContainerConfiguration } from './devcontainer-file';
27
+ import { resolveComposeFilePath } from './docker-compose/compose-service';
28
+
29
+ export interface RemoteContainerConnectionOptions {
30
+ id: string;
31
+ name: string;
32
+ type: string;
33
+ docker: Docker;
34
+ container: Docker.Container;
35
+ config: DevContainerConfiguration;
36
+ logger: ILogger;
37
+ }
38
+
39
+ export class RemoteDockerContainerConnection implements RemoteConnection {
40
+
41
+ id: string;
42
+ name: string;
43
+ type: string;
44
+ localPort: number;
45
+ remotePort: number;
46
+
47
+ docker: Docker;
48
+ container: Docker.Container;
49
+
50
+ remoteSetupResult!: RemoteSetupResult;
51
+
52
+ protected readonly logger: ILogger;
53
+
54
+ protected config: DevContainerConfiguration;
55
+
56
+ protected dockerEventStream: NodeJS.ReadableStream | undefined;
57
+
58
+ protected readonly onDidDisconnectEmitter = new Emitter<void>();
59
+ onDidDisconnect: Event<void> = this.onDidDisconnectEmitter.event;
60
+
61
+ constructor(options: RemoteContainerConnectionOptions) {
62
+ this.id = options.id;
63
+ this.type = options.type;
64
+ this.name = options.name;
65
+
66
+ this.docker = options.docker;
67
+ this.container = options.container;
68
+
69
+ this.config = options.config;
70
+ this.logger = options.logger;
71
+
72
+ this.docker.getEvents({ filters: { container: [this.container.id], event: ['stop'] } }).then(stream => {
73
+ this.dockerEventStream = stream;
74
+ stream.on('data', () => this.onDidDisconnectEmitter.fire());
75
+ }).catch(e => {
76
+ this.logger.error('Failed to register Docker event listener:', e);
77
+ });
78
+ }
79
+
80
+ protected getRemoteEnv(): string[] | undefined {
81
+ const remoteEnv = this.config.remoteEnv;
82
+ if (!remoteEnv || Object.keys(remoteEnv).length === 0) {
83
+ return undefined;
84
+ }
85
+ const entries = Object.entries(remoteEnv)
86
+ .filter(([, value]) => value !== undefined)
87
+ .map(([key, value]) => `${key}=${value}`);
88
+ return entries.length > 0 ? entries : undefined;
89
+ }
90
+
91
+ /**
92
+ * Builds a shell command string safe for use with `sh -c`.
93
+ * Arguments are strong-quoted (single quotes) using {@link BashQuotingFunctions}
94
+ * so that no shell expansion occurs inside them.
95
+ *
96
+ * **`cmd` is not escaped** and must only contain trusted, internally-constructed
97
+ * values. All untrusted input must be passed via `args`.
98
+ */
99
+ protected buildShellCommand(cmd: string, args?: string[]): string {
100
+ if (!args || args.length === 0) {
101
+ return cmd;
102
+ }
103
+ return `${cmd} ${createShellCommandLine(args.map(a => ({ value: a, quoting: ShellQuoting.Strong })), BashQuotingFunctions)}`;
104
+ }
105
+
106
+ async forwardOut(socket: Socket, port?: number): Promise<void> {
107
+ const node = `${this.remoteSetupResult.nodeDirectory}/bin/node`;
108
+ const devContainerServer = `${this.remoteSetupResult.applicationDirectory}/backend/dev-container-server.js`;
109
+ try {
110
+ const ttySession = await this.container.exec({
111
+ Cmd: ['sh', '-c', this.buildShellCommand(node, [devContainerServer, `-target-port=${port ?? this.remotePort}`])],
112
+ Env: this.getRemoteEnv(),
113
+ AttachStdin: true, AttachStdout: true, AttachStderr: true
114
+ });
115
+
116
+ const stream = await ttySession.start({ hijack: true, stdin: true });
117
+
118
+ socket.pipe(stream);
119
+ ttySession.modem.demuxStream(stream, socket, socket);
120
+ } catch (e) {
121
+ this.logger.error('Failed to forward socket:', e);
122
+ }
123
+ }
124
+
125
+ async exec(cmd: string, args?: string[], options?: RemoteExecOptions): Promise<RemoteExecResult> {
126
+ const deferred = new Deferred<RemoteExecResult>();
127
+ try {
128
+ // TODO add windows container support
129
+ const execution = await this.container.exec({
130
+ Cmd: ['sh', '-c', this.buildShellCommand(cmd, args)], Env: this.getRemoteEnv(), AttachStdout: true, AttachStderr: true
131
+ });
132
+ let stdoutBuffer = '';
133
+ let stderrBuffer = '';
134
+ const stream = await execution?.start({});
135
+ const stdout = new PassThrough();
136
+ stdout.on('data', (chunk: Buffer) => {
137
+ stdoutBuffer += chunk.toString();
138
+ });
139
+ const stderr = new PassThrough();
140
+ stderr.on('data', (chunk: Buffer) => {
141
+ stderrBuffer += chunk.toString();
142
+ });
143
+ execution.modem.demuxStream(stream, stdout, stderr);
144
+ stream?.addListener('close', () => deferred.resolve({ stdout: stdoutBuffer, stderr: stderrBuffer }));
145
+ } catch (e) {
146
+ deferred.reject(e);
147
+ }
148
+ return deferred.promise;
149
+ }
150
+
151
+ async execPartial(cmd: string, tester: RemoteExecTester, args?: string[], options?: RemoteExecOptions): Promise<RemoteExecResult> {
152
+ const deferred = new Deferred<RemoteExecResult>();
153
+ try {
154
+ // TODO add windows container support
155
+ const execution = await this.container.exec({
156
+ Cmd: ['sh', '-c', this.buildShellCommand(cmd, args)], Env: this.getRemoteEnv(), AttachStdout: true, AttachStderr: true
157
+ });
158
+ let stdoutBuffer = '';
159
+ let stderrBuffer = '';
160
+ const stream = await execution?.start({});
161
+
162
+ const cleanupStreams = (): void => {
163
+ stdout.destroy();
164
+ stderr.destroy();
165
+ stream.destroy();
166
+ };
167
+
168
+ stream.on('close', () => {
169
+ if (deferred.state === 'unresolved') {
170
+ deferred.resolve({ stdout: stdoutBuffer, stderr: stderrBuffer });
171
+ }
172
+ });
173
+ const stdout = new PassThrough();
174
+ stdout.on('data', (data: Buffer) => {
175
+ this.logger.debug('REMOTE STDOUT:', data.toString());
176
+ if (deferred.state === 'unresolved') {
177
+ stdoutBuffer += data.toString();
178
+
179
+ if (tester(stdoutBuffer, stderrBuffer)) {
180
+ deferred.resolve({ stdout: stdoutBuffer, stderr: stderrBuffer });
181
+ cleanupStreams();
182
+ }
183
+ }
184
+ });
185
+ const stderr = new PassThrough();
186
+ stderr.on('data', (data: Buffer) => {
187
+ this.logger.debug('REMOTE STDERR:', data.toString());
188
+ if (deferred.state === 'unresolved') {
189
+ stderrBuffer += data.toString();
190
+
191
+ if (tester(stdoutBuffer, stderrBuffer)) {
192
+ deferred.resolve({ stdout: stdoutBuffer, stderr: stderrBuffer });
193
+ cleanupStreams();
194
+ }
195
+ }
196
+ });
197
+ execution.modem.demuxStream(stream, stdout, stderr);
198
+ } catch (e) {
199
+ deferred.reject(e);
200
+ }
201
+ return deferred.promise;
202
+ }
203
+
204
+ getDockerHost(): string[] {
205
+ const dockerHost = process.env.DOCKER_HOST;
206
+ try {
207
+ if (dockerHost) {
208
+ const dockerHostURL = new URL(dockerHost);
209
+ if (dockerHostURL.protocol === 'http:' || dockerHostURL.protocol === 'https:') {
210
+ dockerHostURL.protocol = 'tcp:';
211
+ }
212
+ return ['-H', dockerHostURL.href];
213
+ }
214
+ } catch (e) {
215
+ this.logger.error('Failed to parse DOCKER_HOST:', e);
216
+ }
217
+
218
+ return [];
219
+ }
220
+
221
+ async copy(localPath: string, remotePath: string): Promise<void> {
222
+ const deferred = new Deferred<void>();
223
+ const hostArgs = this.getDockerHost();
224
+
225
+ const subprocess = execFile('docker', [...hostArgs, 'cp', '-a', localPath, `${this.container.id}:${remotePath}`]);
226
+
227
+ let stderr = '';
228
+ subprocess.stderr?.on('data', data => {
229
+ stderr += data.toString();
230
+ });
231
+ subprocess.on('close', code => {
232
+ if (code === 0) {
233
+ deferred.resolve();
234
+ } else {
235
+ deferred.reject(stderr);
236
+ }
237
+ });
238
+ return deferred.promise;
239
+ }
240
+
241
+ disposeSync(): void {
242
+ // cant use dockerode here since this needs to happen on one tick
243
+ this.shutdownContainer(true);
244
+ this.onDidDisconnectEmitter.dispose();
245
+ if (this.dockerEventStream) {
246
+ (this.dockerEventStream as import('stream').Readable).destroy();
247
+ this.dockerEventStream = undefined;
248
+ }
249
+ }
250
+
251
+ async dispose(): Promise<void> {
252
+ await this.shutdownContainer(false);
253
+ this.onDidDisconnectEmitter.dispose();
254
+ if (this.dockerEventStream) {
255
+ (this.dockerEventStream as import('stream').Readable).destroy();
256
+ this.dockerEventStream = undefined;
257
+ }
258
+ }
259
+
260
+ protected async shutdownContainer(sync: boolean): Promise<unknown> {
261
+ const hostArgs = this.getDockerHost();
262
+
263
+ const shutdownAction = this.config.shutdownAction ?? (this.config.dockerComposeFile ? 'stopCompose' : 'stopContainer');
264
+
265
+ if (shutdownAction === 'none') {
266
+ return;
267
+ }
268
+
269
+ if (shutdownAction === 'stopContainer') {
270
+ return sync ? execFileSync('docker', [...hostArgs, 'stop', this.container.id]) : this.container.stop();
271
+ } else if (shutdownAction === 'stopCompose') {
272
+ if (!this.config.dockerComposeFile) {
273
+ this.logger.warn('shutdownAction is stopCompose but dockerComposeFile is not defined, falling back to stopContainer');
274
+ return sync ? execFileSync('docker', [...hostArgs, 'stop', this.container.id]) : this.container.stop();
275
+ }
276
+ const composeFilePath = resolveComposeFilePath(this.config);
277
+ return sync ? execFileSync('docker', [...hostArgs, 'compose', '-f', composeFilePath, 'stop']) :
278
+ new Promise<void>((res, rej) => execFile('docker', [...hostArgs, 'compose', '-f', composeFilePath, 'stop'], err => {
279
+ if (err) {
280
+ this.logger.error('Failed to stop compose:', err);
281
+ rej(err);
282
+ } else {
283
+ res();
284
+ }
285
+ }));
286
+ }
287
+
288
+ }
289
+
290
+ }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=remote-container-connection-provider.spec.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"remote-container-connection-provider.spec.d.ts","sourceRoot":"","sources":["../../src/electron-node/remote-container-connection-provider.spec.ts"],"names":[],"mappings":""}
@@ -1,131 +0,0 @@
1
- "use strict";
2
- // *****************************************************************************
3
- // Copyright (C) 2026 EclipseSource 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 remote_container_connection_provider_1 = require("./remote-container-connection-provider");
20
- class TestableDockerContainerConnection extends remote_container_connection_provider_1.RemoteDockerContainerConnection {
21
- testGetRemoteEnv() {
22
- return this.getRemoteEnv();
23
- }
24
- }
25
- function createConnection(config) {
26
- const mockDocker = {
27
- getEvents: () => Promise.resolve({ on: () => { } })
28
- };
29
- const mockContainer = {};
30
- const mockLogger = {};
31
- return new TestableDockerContainerConnection({
32
- id: 'test-id',
33
- name: 'test',
34
- type: 'Dev Container',
35
- docker: mockDocker,
36
- container: mockContainer,
37
- config,
38
- logger: mockLogger
39
- });
40
- }
41
- describe('RemoteDockerContainerConnection', () => {
42
- describe('getRemoteEnv', () => {
43
- it('should return undefined when remoteEnv is not set', () => {
44
- const connection = createConnection({
45
- image: 'test'
46
- });
47
- (0, chai_1.expect)(connection.testGetRemoteEnv()).to.be.undefined;
48
- });
49
- it('should return undefined when remoteEnv is empty', () => {
50
- const connection = createConnection({
51
- image: 'test',
52
- remoteEnv: {}
53
- });
54
- (0, chai_1.expect)(connection.testGetRemoteEnv()).to.be.undefined;
55
- });
56
- it('should convert remoteEnv entries to KEY=value format', () => {
57
- const connection = createConnection({
58
- image: 'test',
59
- remoteEnv: {
60
- 'MY_VAR': 'my_value',
61
- 'ANOTHER_VAR': 'another_value'
62
- }
63
- });
64
- const env = connection.testGetRemoteEnv();
65
- (0, chai_1.expect)(env).to.have.lengthOf(2);
66
- (0, chai_1.expect)(env).to.include('MY_VAR=my_value');
67
- (0, chai_1.expect)(env).to.include('ANOTHER_VAR=another_value');
68
- });
69
- it('should filter out entries with undefined values', () => {
70
- const connection = createConnection({
71
- image: 'test',
72
- remoteEnv: {
73
- 'KEEP_VAR': 'value',
74
- 'REMOVE_VAR': undefined
75
- }
76
- });
77
- const env = connection.testGetRemoteEnv();
78
- (0, chai_1.expect)(env).to.have.lengthOf(1);
79
- (0, chai_1.expect)(env).to.include('KEEP_VAR=value');
80
- });
81
- it('should return undefined when all entries have undefined values', () => {
82
- const connection = createConnection({
83
- image: 'test',
84
- remoteEnv: {
85
- 'VAR1': undefined,
86
- 'VAR2': undefined
87
- }
88
- });
89
- const env = connection.testGetRemoteEnv();
90
- (0, chai_1.expect)(env).to.have.lengthOf(0);
91
- });
92
- it('should handle values containing equals signs', () => {
93
- const connection = createConnection({
94
- image: 'test',
95
- remoteEnv: {
96
- 'CONNECTION_STRING': 'host=localhost;port=5432'
97
- }
98
- });
99
- const env = connection.testGetRemoteEnv();
100
- (0, chai_1.expect)(env).to.have.lengthOf(1);
101
- (0, chai_1.expect)(env).to.include('CONNECTION_STRING=host=localhost;port=5432');
102
- });
103
- it('should handle empty string values', () => {
104
- const connection = createConnection({
105
- image: 'test',
106
- remoteEnv: {
107
- 'EMPTY_VAR': ''
108
- }
109
- });
110
- const env = connection.testGetRemoteEnv();
111
- (0, chai_1.expect)(env).to.have.lengthOf(1);
112
- (0, chai_1.expect)(env).to.include('EMPTY_VAR=');
113
- });
114
- it('should handle values with special characters', () => {
115
- const connection = createConnection({
116
- image: 'test',
117
- remoteEnv: {
118
- 'PATH_EXTRA': '/usr/local/bin:/custom/path',
119
- 'QUOTED': 'hello "world"',
120
- 'SPACED': 'hello world'
121
- }
122
- });
123
- const env = connection.testGetRemoteEnv();
124
- (0, chai_1.expect)(env).to.have.lengthOf(3);
125
- (0, chai_1.expect)(env).to.include('PATH_EXTRA=/usr/local/bin:/custom/path');
126
- (0, chai_1.expect)(env).to.include('QUOTED=hello "world"');
127
- (0, chai_1.expect)(env).to.include('SPACED=hello world');
128
- });
129
- });
130
- });
131
- //# sourceMappingURL=remote-container-connection-provider.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"remote-container-connection-provider.spec.js","sourceRoot":"","sources":["../../src/electron-node/remote-container-connection-provider.spec.ts"],"names":[],"mappings":"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"}