@theia/dev-container 1.71.0-next.8 → 1.72.0-next.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
@@ -17,7 +17,7 @@
17
17
  import * as net from 'net';
18
18
  import {
19
19
  ContainerConnectionOptions, ContainerConnectionResult,
20
- DevContainerFile, RemoteContainerConnectionProvider
20
+ DevContainerFile, RemoteContainerConnectionProvider, RunningContainerInfo
21
21
  } from '../electron-common/remote-container-connection-provider';
22
22
  import { RemoteConnection, RemoteExecOptions, RemoteExecResult, RemoteExecTester, RemoteStatusReport } from '@theia/remote/lib/electron-node/remote-types';
23
23
  import { RemoteSetupResult, RemoteSetupService } from '@theia/remote/lib/electron-node/setup/remote-setup-service';
@@ -146,7 +146,7 @@ export class DevContainerConnectionProvider implements RemoteContainerConnection
146
146
 
147
147
  return {
148
148
  containerId: container.id,
149
- workspacePath: devContainerConfig.workspaceFolder ?? (await container.inspect()).Mounts[0].Destination,
149
+ workspacePath: devContainerConfig.workspaceFolder ?? this.inferWorkspacePath(await container.inspect()),
150
150
  port: localPort.toString(),
151
151
  };
152
152
  } catch (e) {
@@ -182,6 +182,103 @@ export class DevContainerConnectionProvider implements RemoteContainerConnection
182
182
  return connection.container.inspect();
183
183
  }
184
184
 
185
+ async listRunningContainers(): Promise<RunningContainerInfo[]> {
186
+ try {
187
+ const docker = new Docker();
188
+ const containers = await docker.listContainers({ all: false });
189
+ return containers.map(container => ({
190
+ id: container.Id,
191
+ name: (container.Names[0] ?? '').replace(/^\//, ''),
192
+ image: container.Image,
193
+ status: container.Status
194
+ }));
195
+ } catch (e) {
196
+ console.error('Failed to list running containers:', e);
197
+ return [];
198
+ }
199
+ }
200
+
201
+ async attachToContainer(containerId: string): Promise<ContainerConnectionResult> {
202
+ const docker = new Docker();
203
+ const container = docker.getContainer(containerId);
204
+ const containerInfo = await container.inspect();
205
+
206
+ const progress = await this.messageService.showProgress({
207
+ text: 'Attaching to container',
208
+ });
209
+ try {
210
+ const report: RemoteStatusReport = message => progress.report({ message });
211
+ report('Connecting to remote system...');
212
+
213
+ const remote = new RemoteDockerContainerConnection({
214
+ id: generateUuid(),
215
+ name: containerInfo.Name.replace(/^\//, ''),
216
+ type: 'Dev Container',
217
+ docker,
218
+ container,
219
+ config: DevContainerConfiguration.empty(),
220
+ logger: this.logger
221
+ });
222
+
223
+ const result = await this.remoteSetup.setup({
224
+ connection: remote,
225
+ report,
226
+ });
227
+ remote.remoteSetupResult = result;
228
+
229
+ const registration = this.remoteConnectionService.register(remote);
230
+ const server = await this.serverProvider.getProxyServer(socket => {
231
+ remote.forwardOut(socket);
232
+ });
233
+ remote.onDidDisconnect(() => {
234
+ server.close();
235
+ registration.dispose();
236
+ });
237
+ const localPort = (server.address() as net.AddressInfo).port;
238
+ remote.localPort = localPort;
239
+
240
+ const workspacePath = this.inferWorkspacePath(containerInfo);
241
+
242
+ return {
243
+ containerId: container.id,
244
+ workspacePath,
245
+ port: localPort.toString(),
246
+ };
247
+ } catch (e) {
248
+ this.messageService.error(e.message);
249
+ console.error(e);
250
+ throw e;
251
+ } finally {
252
+ progress.cancel();
253
+ }
254
+ }
255
+
256
+ protected inferWorkspacePath(containerInfo: Docker.ContainerInspectInfo): string {
257
+ // Skip mounts that are injected by HostConfigSharingContribution
258
+ // (SSH dir, gitconfig) — these are not workspace mounts.
259
+ const workspaceMount = containerInfo.Mounts.find(m =>
260
+ !m.Destination.endsWith('/.ssh') &&
261
+ !m.Destination.endsWith('/.gitconfig') &&
262
+ m.Destination !== '/tmp/host_gitconfig'
263
+ );
264
+ return (workspaceMount?.Destination ?? containerInfo.Config.WorkingDir) || '/';
265
+ }
266
+
267
+ async removeContainer(containerId: string): Promise<void> {
268
+ try {
269
+ const docker = new Docker();
270
+ const container = docker.getContainer(containerId);
271
+ const info = await container.inspect();
272
+ if (info.State.Running) {
273
+ await container.stop();
274
+ }
275
+ await container.remove();
276
+ } catch (e) {
277
+ console.error('Failed to remove container:', e);
278
+ throw e;
279
+ }
280
+ }
281
+
185
282
  dispose(): void {
186
283
 
187
284
  }
@@ -251,12 +348,23 @@ export class RemoteDockerContainerConnection implements RemoteConnection {
251
348
  this.logger = options.logger;
252
349
  }
253
350
 
351
+ protected getRemoteEnv(): string[] | undefined {
352
+ const remoteEnv = this.config.remoteEnv;
353
+ if (!remoteEnv || Object.keys(remoteEnv).length === 0) {
354
+ return undefined;
355
+ }
356
+ return Object.entries(remoteEnv)
357
+ .filter(([, value]) => value !== undefined)
358
+ .map(([key, value]) => `${key}=${value}`);
359
+ }
360
+
254
361
  async forwardOut(socket: Socket, port?: number): Promise<void> {
255
362
  const node = `${this.remoteSetupResult.nodeDirectory}/bin/node`;
256
363
  const devContainerServer = `${this.remoteSetupResult.applicationDirectory}/backend/dev-container-server.js`;
257
364
  try {
258
365
  const ttySession = await this.container.exec({
259
366
  Cmd: ['sh', '-c', `${node} ${devContainerServer} -target-port=${port ?? this.remotePort}`],
367
+ Env: this.getRemoteEnv(),
260
368
  AttachStdin: true, AttachStdout: true, AttachStderr: true
261
369
  });
262
370
 
@@ -274,7 +382,9 @@ export class RemoteDockerContainerConnection implements RemoteConnection {
274
382
  const deferred = new Deferred<RemoteExecResult>();
275
383
  try {
276
384
  // TODO add windows container support
277
- const execution = await this.container.exec({ Cmd: ['sh', '-c', `${cmd} ${args?.join(' ') ?? ''}`], AttachStdout: true, AttachStderr: true });
385
+ const execution = await this.container.exec({
386
+ Cmd: ['sh', '-c', `${cmd} ${args?.join(' ') ?? ''}`], Env: this.getRemoteEnv(), AttachStdout: true, AttachStderr: true
387
+ });
278
388
  let stdoutBuffer = '';
279
389
  let stderrBuffer = '';
280
390
  const stream = await execution?.start({});
@@ -298,7 +408,9 @@ export class RemoteDockerContainerConnection implements RemoteConnection {
298
408
  const deferred = new Deferred<RemoteExecResult>();
299
409
  try {
300
410
  // TODO add windows container support
301
- const execution = await this.container.exec({ Cmd: ['sh', '-c', `${cmd} ${args?.join(' ') ?? ''}`], AttachStdout: true, AttachStderr: true });
411
+ const execution = await this.container.exec({
412
+ Cmd: ['sh', '-c', `${cmd} ${args?.join(' ') ?? ''}`], Env: this.getRemoteEnv(), AttachStdout: true, AttachStderr: true
413
+ });
302
414
  let stdoutBuffer = '';
303
415
  let stderrBuffer = '';
304
416
  const stream = await execution?.start({});
@@ -386,11 +498,15 @@ export class RemoteDockerContainerConnection implements RemoteConnection {
386
498
  protected async shutdownContainer(sync: boolean): Promise<unknown> {
387
499
  const remoteHost = this.getDockerHost();
388
500
 
389
- const shutdownAction = this.config.shutdownAction ?? this.config.dockerComposeFile ? 'stopCompose' : 'stopContainer';
501
+ const shutdownAction = this.config.shutdownAction ?? (this.config.dockerComposeFile ? 'stopCompose' : 'stopContainer');
390
502
 
391
503
  if (shutdownAction === 'stopContainer') {
392
504
  return sync ? execSync(`docker ${remoteHost}stop ${this.container.id}`) : this.container.stop();
393
505
  } else if (shutdownAction === 'stopCompose') {
506
+ if (!this.config.dockerComposeFile) {
507
+ console.warn('shutdownAction is stopCompose but dockerComposeFile is not defined, falling back to stopContainer');
508
+ return sync ? execSync(`docker ${remoteHost}stop ${this.container.id}`) : this.container.stop();
509
+ }
394
510
  const composeFilePath = resolveComposeFilePath(this.config);
395
511
  return sync ? execSync(`docker ${remoteHost}compose -f ${composeFilePath} stop`) :
396
512
  new Promise<void>((res, rej) => exec(`docker ${remoteHost}compose -f ${composeFilePath} stop`, err => {