@git.zone/tsdocker 1.15.0 → 1.16.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.
@@ -0,0 +1,107 @@
1
+ import * as crypto from 'crypto';
2
+ import * as net from 'net';
3
+ import { logger } from './tsdocker.logging.js';
4
+
5
+ export interface ISessionConfig {
6
+ sessionId: string;
7
+ registryPort: number;
8
+ registryHost: string;
9
+ registryContainerName: string;
10
+ isCI: boolean;
11
+ ciSystem: string | null;
12
+ builderSuffix: string;
13
+ }
14
+
15
+ /**
16
+ * Per-invocation session identity for tsdocker.
17
+ * Generates unique ports, container names, and builder names so that
18
+ * concurrent CI jobs on the same Docker host don't collide.
19
+ *
20
+ * In local (non-CI) dev the builder suffix is empty, preserving the
21
+ * persistent builder behavior.
22
+ */
23
+ export class TsDockerSession {
24
+ public config: ISessionConfig;
25
+
26
+ private constructor(config: ISessionConfig) {
27
+ this.config = config;
28
+ }
29
+
30
+ /**
31
+ * Creates a new session. Allocates a dynamic port unless overridden
32
+ * via `TSDOCKER_REGISTRY_PORT`.
33
+ */
34
+ public static async create(): Promise<TsDockerSession> {
35
+ const sessionId =
36
+ process.env.TSDOCKER_SESSION_ID || crypto.randomBytes(4).toString('hex');
37
+
38
+ const registryPort = await TsDockerSession.allocatePort();
39
+ const registryHost = `localhost:${registryPort}`;
40
+ const registryContainerName = `tsdocker-registry-${sessionId}`;
41
+
42
+ const { isCI, ciSystem } = TsDockerSession.detectCI();
43
+ const builderSuffix = isCI ? `-${sessionId}` : '';
44
+
45
+ const config: ISessionConfig = {
46
+ sessionId,
47
+ registryPort,
48
+ registryHost,
49
+ registryContainerName,
50
+ isCI,
51
+ ciSystem,
52
+ builderSuffix,
53
+ };
54
+
55
+ const session = new TsDockerSession(config);
56
+ session.logInfo();
57
+ return session;
58
+ }
59
+
60
+ /**
61
+ * Allocates a free TCP port. Respects `TSDOCKER_REGISTRY_PORT` override.
62
+ */
63
+ public static async allocatePort(): Promise<number> {
64
+ const envPort = process.env.TSDOCKER_REGISTRY_PORT;
65
+ if (envPort) {
66
+ const parsed = parseInt(envPort, 10);
67
+ if (!isNaN(parsed) && parsed > 0) {
68
+ return parsed;
69
+ }
70
+ }
71
+
72
+ return new Promise<number>((resolve, reject) => {
73
+ const srv = net.createServer();
74
+ srv.listen(0, '127.0.0.1', () => {
75
+ const addr = srv.address() as net.AddressInfo;
76
+ const port = addr.port;
77
+ srv.close((err) => {
78
+ if (err) reject(err);
79
+ else resolve(port);
80
+ });
81
+ });
82
+ srv.on('error', reject);
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Detects whether we're running inside a CI system.
88
+ */
89
+ private static detectCI(): { isCI: boolean; ciSystem: string | null } {
90
+ if (process.env.GITEA_ACTIONS) return { isCI: true, ciSystem: 'gitea-actions' };
91
+ if (process.env.GITHUB_ACTIONS) return { isCI: true, ciSystem: 'github-actions' };
92
+ if (process.env.GITLAB_CI) return { isCI: true, ciSystem: 'gitlab-ci' };
93
+ if (process.env.CI) return { isCI: true, ciSystem: 'generic' };
94
+ return { isCI: false, ciSystem: null };
95
+ }
96
+
97
+ private logInfo(): void {
98
+ const c = this.config;
99
+ logger.log('info', '=== TSDOCKER SESSION ===');
100
+ logger.log('info', `Session ID: ${c.sessionId}`);
101
+ logger.log('info', `Registry: ${c.registryHost} (container: ${c.registryContainerName})`);
102
+ if (c.isCI) {
103
+ logger.log('info', `CI detected: ${c.ciSystem}`);
104
+ logger.log('info', `Builder suffix: ${c.builderSuffix}`);
105
+ }
106
+ }
107
+ }
@@ -101,4 +101,5 @@ export interface IDockerContextInfo {
101
101
  endpoint: string; // 'unix:///var/run/docker.sock'
102
102
  isRootless: boolean;
103
103
  dockerHost?: string; // value of DOCKER_HOST env var, if set
104
+ topology?: 'socket-mount' | 'dind' | 'local';
104
105
  }
@@ -64,6 +64,7 @@ export let run = () => {
64
64
  }
65
65
 
66
66
  await manager.build(buildOptions);
67
+ await manager.cleanup();
67
68
  logger.log('success', 'Build completed successfully');
68
69
  } catch (err) {
69
70
  logger.log('error', `Build failed: ${(err as Error).message}`);
@@ -117,6 +118,7 @@ export let run = () => {
117
118
  const registries = registryArg ? [registryArg] : undefined;
118
119
 
119
120
  await manager.push(registries);
121
+ await manager.cleanup();
120
122
  logger.log('success', 'Push completed successfully');
121
123
  } catch (err) {
122
124
  logger.log('error', `Push failed: ${(err as Error).message}`);
@@ -180,6 +182,7 @@ export let run = () => {
180
182
 
181
183
  // Run tests
182
184
  await manager.test();
185
+ await manager.cleanup();
183
186
  logger.log('success', 'Tests completed successfully');
184
187
  } catch (err) {
185
188
  logger.log('error', `Tests failed: ${(err as Error).message}`);