@git.zone/tsdocker 1.2.43 → 1.4.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 (47) hide show
  1. package/cli.js +1 -1
  2. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  3. package/dist_ts/00_commitinfo_data.js +9 -0
  4. package/dist_ts/classes.dockerfile.d.ts +85 -0
  5. package/dist_ts/classes.dockerfile.js +366 -0
  6. package/dist_ts/classes.dockerregistry.d.ts +29 -0
  7. package/dist_ts/classes.dockerregistry.js +83 -0
  8. package/dist_ts/classes.registrystorage.d.ts +35 -0
  9. package/dist_ts/classes.registrystorage.js +76 -0
  10. package/dist_ts/classes.tsdockermanager.d.ts +53 -0
  11. package/dist_ts/classes.tsdockermanager.js +222 -0
  12. package/dist_ts/index.d.ts +1 -0
  13. package/dist_ts/index.js +4 -0
  14. package/dist_ts/interfaces/index.d.ts +68 -0
  15. package/dist_ts/interfaces/index.js +2 -0
  16. package/dist_ts/tsdocker.cli.d.ts +1 -0
  17. package/dist_ts/tsdocker.cli.js +179 -0
  18. package/dist_ts/tsdocker.config.d.ts +5 -0
  19. package/dist_ts/tsdocker.config.js +37 -0
  20. package/dist_ts/tsdocker.docker.d.ts +2 -0
  21. package/dist_ts/tsdocker.docker.js +145 -0
  22. package/dist_ts/tsdocker.logging.d.ts +3 -0
  23. package/dist_ts/tsdocker.logging.js +15 -0
  24. package/dist_ts/tsdocker.paths.d.ts +4 -0
  25. package/dist_ts/tsdocker.paths.js +13 -0
  26. package/dist_ts/tsdocker.plugins.d.ts +16 -0
  27. package/dist_ts/tsdocker.plugins.js +19 -0
  28. package/dist_ts/tsdocker.snippets.d.ts +5 -0
  29. package/dist_ts/tsdocker.snippets.js +26 -0
  30. package/npmextra.json +12 -6
  31. package/package.json +26 -21
  32. package/readme.hints.md +95 -26
  33. package/readme.md +32 -33
  34. package/ts/00_commitinfo_data.ts +1 -1
  35. package/ts/classes.dockerfile.ts +462 -0
  36. package/ts/classes.dockerregistry.ts +91 -0
  37. package/ts/classes.registrystorage.ts +83 -0
  38. package/ts/classes.tsdockermanager.ts +254 -0
  39. package/ts/index.ts +2 -2
  40. package/ts/interfaces/index.ts +70 -0
  41. package/ts/tsdocker.cli.ts +129 -16
  42. package/ts/tsdocker.config.ts +17 -10
  43. package/ts/tsdocker.docker.ts +6 -6
  44. package/ts/tsdocker.logging.ts +1 -1
  45. package/ts/tsdocker.paths.ts +6 -1
  46. package/ts/tsdocker.plugins.ts +2 -0
  47. package/ts/tsdocker.snippets.ts +5 -8
package/readme.md CHANGED
@@ -35,13 +35,13 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
35
35
 
36
36
  - Full TypeScript support with excellent IntelliSense
37
37
  - Type-safe configuration
38
- - Modern async/await patterns throughout
38
+ - Modern ESM with async/await patterns throughout
39
39
 
40
40
  ## Installation
41
41
 
42
42
  ```bash
43
- npm install --save-dev @git.zone/tsdocker
44
- # or
43
+ npm install -g @git.zone/tsdocker
44
+ # or for project-local installation
45
45
  pnpm install --save-dev @git.zone/tsdocker
46
46
  ```
47
47
 
@@ -53,9 +53,9 @@ Create an `npmextra.json` file in your project root:
53
53
 
54
54
  ```json
55
55
  {
56
- "npmdocker": {
57
- "baseImage": "hosttoday/ht-docker-node:npmts",
58
- "command": "npmci test stable",
56
+ "@git.zone/tsdocker": {
57
+ "baseImage": "node:20",
58
+ "command": "npm test",
59
59
  "dockerSock": false
60
60
  }
61
61
  }
@@ -64,7 +64,7 @@ Create an `npmextra.json` file in your project root:
64
64
  ### 2. Run Your Tests
65
65
 
66
66
  ```bash
67
- npx tsdocker
67
+ tsdocker
68
68
  ```
69
69
 
70
70
  That's it! tsdocker will:
@@ -128,14 +128,6 @@ tsdocker vscode
128
128
 
129
129
  Launches a containerized VS Code instance accessible via browser at `testing-vscode.git.zone:8443`.
130
130
 
131
- ### Speed Test
132
-
133
- ```bash
134
- tsdocker speedtest
135
- ```
136
-
137
- Runs a network speed test inside a Docker container.
138
-
139
131
  ## Advanced Usage
140
132
 
141
133
  ### Docker-in-Docker Testing
@@ -144,7 +136,7 @@ If you need to run Docker commands inside your test container (e.g., testing Doc
144
136
 
145
137
  ```json
146
138
  {
147
- "npmdocker": {
139
+ "@git.zone/tsdocker": {
148
140
  "baseImage": "docker:latest",
149
141
  "command": "docker run hello-world",
150
142
  "dockerSock": true
@@ -160,7 +152,7 @@ You can use any Docker image as your base:
160
152
 
161
153
  ```json
162
154
  {
163
- "npmdocker": {
155
+ "@git.zone/tsdocker": {
164
156
  "baseImage": "node:20-alpine",
165
157
  "command": "npm test"
166
158
  }
@@ -171,13 +163,13 @@ Popular choices:
171
163
 
172
164
  - `node:20` — Official Node.js images
173
165
  - `node:20-alpine` — Lightweight Alpine-based images
174
- - `hosttoday/ht-docker-node:npmts` — Pre-configured with npmts tooling
166
+ - `node:lts` — Long-term support Node.js version
175
167
 
176
168
  ### CI Integration
177
169
 
178
170
  tsdocker automatically detects CI environments (via `CI=true` env var) and adjusts behavior:
179
171
 
180
- - Skips mounting project directory in CI (assumes code is already in container)
172
+ - Copies project files into container in CI (instead of mounting)
181
173
  - Optimizes for CI execution patterns
182
174
 
183
175
  ## Why tsdocker?
@@ -207,7 +199,7 @@ tsdocker ensures every test run happens in a **clean, reproducible environment**
207
199
  tsdocker is built with TypeScript and provides full type definitions:
208
200
 
209
201
  ```typescript
210
- import { IConfig } from '@git.zone/tsdocker/dist/tsdocker.config';
202
+ import type { IConfig } from '@git.zone/tsdocker/dist_ts/tsdocker.config.js';
211
203
 
212
204
  const config: IConfig = {
213
205
  baseImage: 'node:20',
@@ -222,7 +214,7 @@ const config: IConfig = {
222
214
  ## Requirements
223
215
 
224
216
  - **Docker**: Docker must be installed and accessible via CLI
225
- - **Node.js**: Version 14 or higher recommended
217
+ - **Node.js**: Version 18 or higher (ESM support required)
226
218
 
227
219
  ## How It Works
228
220
 
@@ -271,20 +263,20 @@ sudo usermod -aG docker $USER
271
263
 
272
264
  ```json
273
265
  {
274
- "npmdocker": {
266
+ "@git.zone/tsdocker": {
275
267
  "baseImage": "node:20",
276
268
  "command": "npm test"
277
269
  }
278
270
  }
279
271
  ```
280
272
 
281
- ### Using npmci for multiple Node versions
273
+ ### Running pnpm tests
282
274
 
283
275
  ```json
284
276
  {
285
- "npmdocker": {
286
- "baseImage": "hosttoday/ht-docker-node:npmts",
287
- "command": "npmci test stable"
277
+ "@git.zone/tsdocker": {
278
+ "baseImage": "node:20",
279
+ "command": "corepack enable && pnpm install && pnpm test"
288
280
  }
289
281
  }
290
282
  ```
@@ -293,7 +285,7 @@ sudo usermod -aG docker $USER
293
285
 
294
286
  ```json
295
287
  {
296
- "npmdocker": {
288
+ "@git.zone/tsdocker": {
297
289
  "baseImage": "docker:latest",
298
290
  "command": "sh -c 'docker version && docker ps'",
299
291
  "dockerSock": true
@@ -307,25 +299,32 @@ sudo usermod -aG docker $USER
307
299
  🚀 **Layer caching**: Docker caches image layers — your base image only downloads once
308
300
  🚀 **Prune regularly**: Run `docker system prune` periodically to reclaim disk space
309
301
 
310
- ## Migration from legacy npmdocker scope
302
+ ## Migration from legacy npmdocker
311
303
 
312
- This package was previously published under the `npmdocker` name in the old scope. It is now available as `@git.zone/tsdocker` for better naming consistency. Functionality remains the same.
304
+ This package was previously published under the `npmdocker` name. It is now available as `@git.zone/tsdocker` with modernized ESM support and updated dependencies.
305
+
306
+ Key changes:
307
+ - Configuration key changed from `npmdocker` to `@git.zone/tsdocker` in `npmextra.json`
308
+ - CLI command is now `tsdocker` instead of `npmdocker`
309
+ - Full ESM support with `.js` extensions in imports
313
310
 
314
311
  ## License and Legal Information
315
312
 
316
- This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
313
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
317
314
 
318
315
  **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
319
316
 
320
317
  ### Trademarks
321
318
 
322
- This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
319
+ This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
320
+
321
+ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
323
322
 
324
323
  ### Company Information
325
324
 
326
325
  Task Venture Capital GmbH
327
- Registered at District court Bremen HRB 35230 HB, Germany
326
+ Registered at District Court Bremen HRB 35230 HB, Germany
328
327
 
329
- For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
328
+ For any legal inquiries or further information, please contact us via email at hello@task.vc.
330
329
 
331
330
  By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@git.zone/tsdocker',
6
- version: '1.2.43',
6
+ version: '1.4.0',
7
7
  description: 'develop npm modules cross platform with docker'
8
8
  }
@@ -0,0 +1,462 @@
1
+ import * as plugins from './tsdocker.plugins.js';
2
+ import * as paths from './tsdocker.paths.js';
3
+ import { logger } from './tsdocker.logging.js';
4
+ import { DockerRegistry } from './classes.dockerregistry.js';
5
+ import type { IDockerfileOptions, ITsDockerConfig } from './interfaces/index.js';
6
+ import type { TsDockerManager } from './classes.tsdockermanager.js';
7
+
8
+ const smartshellInstance = new plugins.smartshell.Smartshell({
9
+ executor: 'bash',
10
+ });
11
+
12
+ /**
13
+ * Class Dockerfile represents a Dockerfile on disk
14
+ */
15
+ export class Dockerfile {
16
+ // STATIC METHODS
17
+
18
+ /**
19
+ * Creates instances of class Dockerfile for all Dockerfiles in cwd
20
+ */
21
+ public static async readDockerfiles(managerRef: TsDockerManager): Promise<Dockerfile[]> {
22
+ const entries = await plugins.smartfs.directory(paths.cwd).filter('Dockerfile*').list();
23
+ const fileTree = entries
24
+ .filter(entry => entry.isFile)
25
+ .map(entry => plugins.path.join(paths.cwd, entry.name));
26
+
27
+ const readDockerfilesArray: Dockerfile[] = [];
28
+ logger.log('info', `found ${fileTree.length} Dockerfiles:`);
29
+ console.log(fileTree);
30
+
31
+ for (const dockerfilePath of fileTree) {
32
+ const myDockerfile = new Dockerfile(managerRef, {
33
+ filePath: dockerfilePath,
34
+ read: true,
35
+ });
36
+ readDockerfilesArray.push(myDockerfile);
37
+ }
38
+
39
+ return readDockerfilesArray;
40
+ }
41
+
42
+ /**
43
+ * Sorts Dockerfiles into a build order based on dependencies (topological sort)
44
+ */
45
+ public static async sortDockerfiles(dockerfiles: Dockerfile[]): Promise<Dockerfile[]> {
46
+ logger.log('info', 'Sorting Dockerfiles based on dependencies...');
47
+
48
+ // Map from cleanTag to Dockerfile instance for quick lookup
49
+ const tagToDockerfile = new Map<string, Dockerfile>();
50
+ dockerfiles.forEach((dockerfile) => {
51
+ tagToDockerfile.set(dockerfile.cleanTag, dockerfile);
52
+ });
53
+
54
+ // Build the dependency graph
55
+ const graph = new Map<Dockerfile, Dockerfile[]>();
56
+ dockerfiles.forEach((dockerfile) => {
57
+ const dependencies: Dockerfile[] = [];
58
+ const baseImage = dockerfile.baseImage;
59
+
60
+ // Check if the baseImage is among the local Dockerfiles
61
+ if (tagToDockerfile.has(baseImage)) {
62
+ const baseDockerfile = tagToDockerfile.get(baseImage)!;
63
+ dependencies.push(baseDockerfile);
64
+ dockerfile.localBaseImageDependent = true;
65
+ dockerfile.localBaseDockerfile = baseDockerfile;
66
+ }
67
+
68
+ graph.set(dockerfile, dependencies);
69
+ });
70
+
71
+ // Perform topological sort
72
+ const sortedDockerfiles: Dockerfile[] = [];
73
+ const visited = new Set<Dockerfile>();
74
+ const tempMarked = new Set<Dockerfile>();
75
+
76
+ const visit = (dockerfile: Dockerfile) => {
77
+ if (tempMarked.has(dockerfile)) {
78
+ throw new Error(`Circular dependency detected involving ${dockerfile.cleanTag}`);
79
+ }
80
+ if (!visited.has(dockerfile)) {
81
+ tempMarked.add(dockerfile);
82
+ const dependencies = graph.get(dockerfile) || [];
83
+ dependencies.forEach((dep) => visit(dep));
84
+ tempMarked.delete(dockerfile);
85
+ visited.add(dockerfile);
86
+ sortedDockerfiles.push(dockerfile);
87
+ }
88
+ };
89
+
90
+ try {
91
+ dockerfiles.forEach((dockerfile) => {
92
+ if (!visited.has(dockerfile)) {
93
+ visit(dockerfile);
94
+ }
95
+ });
96
+ } catch (error) {
97
+ logger.log('error', (error as Error).message);
98
+ throw error;
99
+ }
100
+
101
+ // Log the sorted order
102
+ sortedDockerfiles.forEach((dockerfile, index) => {
103
+ logger.log(
104
+ 'info',
105
+ `Build order ${index + 1}: ${dockerfile.cleanTag} with base image ${dockerfile.baseImage}`
106
+ );
107
+ });
108
+
109
+ return sortedDockerfiles;
110
+ }
111
+
112
+ /**
113
+ * Maps local Dockerfiles dependencies to the corresponding Dockerfile class instances
114
+ */
115
+ public static async mapDockerfiles(sortedDockerfileArray: Dockerfile[]): Promise<Dockerfile[]> {
116
+ sortedDockerfileArray.forEach((dockerfileArg) => {
117
+ if (dockerfileArg.localBaseImageDependent) {
118
+ sortedDockerfileArray.forEach((dockfile2: Dockerfile) => {
119
+ if (dockfile2.cleanTag === dockerfileArg.baseImage) {
120
+ dockerfileArg.localBaseDockerfile = dockfile2;
121
+ }
122
+ });
123
+ }
124
+ });
125
+ return sortedDockerfileArray;
126
+ }
127
+
128
+ /**
129
+ * Builds the corresponding real docker image for each Dockerfile class instance
130
+ */
131
+ public static async buildDockerfiles(sortedArrayArg: Dockerfile[]): Promise<Dockerfile[]> {
132
+ for (const dockerfileArg of sortedArrayArg) {
133
+ await dockerfileArg.build();
134
+ }
135
+ return sortedArrayArg;
136
+ }
137
+
138
+ /**
139
+ * Tests all Dockerfiles by calling Dockerfile.test()
140
+ */
141
+ public static async testDockerfiles(sortedArrayArg: Dockerfile[]): Promise<Dockerfile[]> {
142
+ for (const dockerfileArg of sortedArrayArg) {
143
+ await dockerfileArg.test();
144
+ }
145
+ return sortedArrayArg;
146
+ }
147
+
148
+ /**
149
+ * Returns a version for a docker file
150
+ * Dockerfile_latest -> latest
151
+ * Dockerfile_v1.0.0 -> v1.0.0
152
+ * Dockerfile -> latest
153
+ */
154
+ public static dockerFileVersion(
155
+ dockerfileInstanceArg: Dockerfile,
156
+ dockerfileNameArg: string
157
+ ): string {
158
+ let versionString: string;
159
+ const versionRegex = /Dockerfile_(.+)$/;
160
+ const regexResultArray = versionRegex.exec(dockerfileNameArg);
161
+ if (regexResultArray && regexResultArray.length === 2) {
162
+ versionString = regexResultArray[1];
163
+ } else {
164
+ versionString = 'latest';
165
+ }
166
+
167
+ // Replace ##version## placeholder with actual package version if available
168
+ if (dockerfileInstanceArg.managerRef?.projectInfo?.npm?.version) {
169
+ versionString = versionString.replace(
170
+ '##version##',
171
+ dockerfileInstanceArg.managerRef.projectInfo.npm.version
172
+ );
173
+ }
174
+
175
+ return versionString;
176
+ }
177
+
178
+ /**
179
+ * Extracts the base image from a Dockerfile content
180
+ * Handles ARG substitution for variable base images
181
+ */
182
+ public static dockerBaseImage(dockerfileContentArg: string): string {
183
+ const lines = dockerfileContentArg.split(/\r?\n/);
184
+ const args: { [key: string]: string } = {};
185
+
186
+ for (const line of lines) {
187
+ const trimmedLine = line.trim();
188
+
189
+ // Skip empty lines and comments
190
+ if (trimmedLine === '' || trimmedLine.startsWith('#')) {
191
+ continue;
192
+ }
193
+
194
+ // Match ARG instructions
195
+ const argMatch = trimmedLine.match(/^ARG\s+([^\s=]+)(?:=(.*))?$/i);
196
+ if (argMatch) {
197
+ const argName = argMatch[1];
198
+ const argValue = argMatch[2] !== undefined ? argMatch[2] : process.env[argName] || '';
199
+ args[argName] = argValue;
200
+ continue;
201
+ }
202
+
203
+ // Match FROM instructions
204
+ const fromMatch = trimmedLine.match(/^FROM\s+(.+?)(?:\s+AS\s+[^\s]+)?$/i);
205
+ if (fromMatch) {
206
+ let baseImage = fromMatch[1].trim();
207
+
208
+ // Substitute variables in the base image name
209
+ baseImage = Dockerfile.substituteVariables(baseImage, args);
210
+
211
+ return baseImage;
212
+ }
213
+ }
214
+
215
+ throw new Error('No FROM instruction found in Dockerfile');
216
+ }
217
+
218
+ /**
219
+ * Substitutes variables in a string, supporting default values like ${VAR:-default}
220
+ */
221
+ private static substituteVariables(str: string, vars: { [key: string]: string }): string {
222
+ return str.replace(/\${([^}:]+)(:-([^}]+))?}/g, (_, varName, __, defaultValue) => {
223
+ if (vars[varName] !== undefined) {
224
+ return vars[varName];
225
+ } else if (defaultValue !== undefined) {
226
+ return defaultValue;
227
+ } else {
228
+ return '';
229
+ }
230
+ });
231
+ }
232
+
233
+ /**
234
+ * Returns the docker tag string for a given registry and repo
235
+ */
236
+ public static getDockerTagString(
237
+ managerRef: TsDockerManager,
238
+ registryArg: string,
239
+ repoArg: string,
240
+ versionArg: string,
241
+ suffixArg?: string
242
+ ): string {
243
+ // Determine whether the repo should be mapped according to the registry
244
+ const config = managerRef.config;
245
+ const mappedRepo = config.registryRepoMap?.[registryArg];
246
+ const repo = mappedRepo || repoArg;
247
+
248
+ // Determine whether the version contains a suffix
249
+ let version = versionArg;
250
+ if (suffixArg) {
251
+ version = versionArg + '_' + suffixArg;
252
+ }
253
+
254
+ const tagString = `${registryArg}/${repo}:${version}`;
255
+ return tagString;
256
+ }
257
+
258
+ /**
259
+ * Gets build args from environment variable mapping
260
+ */
261
+ public static async getDockerBuildArgs(managerRef: TsDockerManager): Promise<string> {
262
+ logger.log('info', 'checking for env vars to be supplied to the docker build');
263
+ let buildArgsString: string = '';
264
+ const config = managerRef.config;
265
+
266
+ if (config.buildArgEnvMap) {
267
+ for (const dockerArgKey of Object.keys(config.buildArgEnvMap)) {
268
+ const dockerArgOuterEnvVar = config.buildArgEnvMap[dockerArgKey];
269
+ logger.log(
270
+ 'note',
271
+ `docker ARG "${dockerArgKey}" maps to outer env var "${dockerArgOuterEnvVar}"`
272
+ );
273
+ const targetValue = process.env[dockerArgOuterEnvVar];
274
+ if (targetValue) {
275
+ buildArgsString = `${buildArgsString} --build-arg ${dockerArgKey}="${targetValue}"`;
276
+ }
277
+ }
278
+ }
279
+ return buildArgsString;
280
+ }
281
+
282
+ // INSTANCE PROPERTIES
283
+ public managerRef: TsDockerManager;
284
+ public filePath!: string;
285
+ public repo: string;
286
+ public version: string;
287
+ public cleanTag: string;
288
+ public buildTag: string;
289
+ public pushTag!: string;
290
+ public containerName: string;
291
+ public content!: string;
292
+ public baseImage: string;
293
+ public localBaseImageDependent: boolean;
294
+ public localBaseDockerfile!: Dockerfile;
295
+
296
+ constructor(managerRefArg: TsDockerManager, options: IDockerfileOptions) {
297
+ this.managerRef = managerRefArg;
298
+ this.filePath = options.filePath!;
299
+
300
+ // Build repo name from project info or directory name
301
+ const projectInfo = this.managerRef.projectInfo;
302
+ if (projectInfo?.npm?.name) {
303
+ // Use package name, removing scope if present
304
+ const packageName = projectInfo.npm.name.replace(/^@[^/]+\//, '');
305
+ this.repo = packageName;
306
+ } else {
307
+ // Fallback to directory name
308
+ this.repo = plugins.path.basename(paths.cwd);
309
+ }
310
+
311
+ this.version = Dockerfile.dockerFileVersion(this, plugins.path.parse(this.filePath).base);
312
+ this.cleanTag = this.repo + ':' + this.version;
313
+ this.buildTag = this.cleanTag;
314
+ this.containerName = 'dockerfile-' + this.version;
315
+
316
+ if (options.filePath && options.read) {
317
+ const fs = require('fs');
318
+ this.content = fs.readFileSync(plugins.path.resolve(options.filePath), 'utf-8');
319
+ } else if (options.fileContents) {
320
+ this.content = options.fileContents;
321
+ }
322
+
323
+ this.baseImage = Dockerfile.dockerBaseImage(this.content);
324
+ this.localBaseImageDependent = false;
325
+ }
326
+
327
+ /**
328
+ * Builds the Dockerfile
329
+ */
330
+ public async build(): Promise<void> {
331
+ logger.log('info', 'now building Dockerfile for ' + this.cleanTag);
332
+ const buildArgsString = await Dockerfile.getDockerBuildArgs(this.managerRef);
333
+ const config = this.managerRef.config;
334
+
335
+ let buildCommand: string;
336
+
337
+ // Check if multi-platform build is needed
338
+ if (config.platforms && config.platforms.length > 1) {
339
+ // Multi-platform build using buildx
340
+ const platformString = config.platforms.join(',');
341
+ buildCommand = `docker buildx build --platform ${platformString} -t ${this.buildTag} -f ${this.filePath} ${buildArgsString} .`;
342
+
343
+ if (config.push) {
344
+ buildCommand += ' --push';
345
+ } else {
346
+ buildCommand += ' --load';
347
+ }
348
+ } else {
349
+ // Standard build
350
+ const versionLabel = this.managerRef.projectInfo?.npm?.version || 'unknown';
351
+ buildCommand = `docker build --label="version=${versionLabel}" -t ${this.buildTag} -f ${this.filePath} ${buildArgsString} .`;
352
+ }
353
+
354
+ const result = await smartshellInstance.exec(buildCommand);
355
+ if (result.exitCode !== 0) {
356
+ logger.log('error', `Build failed for ${this.cleanTag}`);
357
+ console.log(result.stdout);
358
+ throw new Error(`Build failed for ${this.cleanTag}`);
359
+ }
360
+
361
+ logger.log('ok', `Built ${this.cleanTag}`);
362
+ }
363
+
364
+ /**
365
+ * Pushes the Dockerfile to a registry
366
+ */
367
+ public async push(dockerRegistryArg: DockerRegistry, versionSuffix?: string): Promise<void> {
368
+ this.pushTag = Dockerfile.getDockerTagString(
369
+ this.managerRef,
370
+ dockerRegistryArg.registryUrl,
371
+ this.repo,
372
+ this.version,
373
+ versionSuffix
374
+ );
375
+
376
+ await smartshellInstance.exec(`docker tag ${this.buildTag} ${this.pushTag}`);
377
+ const pushResult = await smartshellInstance.exec(`docker push ${this.pushTag}`);
378
+
379
+ if (pushResult.exitCode !== 0) {
380
+ logger.log('error', `Push failed for ${this.pushTag}`);
381
+ throw new Error(`Push failed for ${this.pushTag}`);
382
+ }
383
+
384
+ // Get image digest
385
+ const inspectResult = await smartshellInstance.exec(
386
+ `docker inspect --format="{{index .RepoDigests 0}}" ${this.pushTag}`
387
+ );
388
+
389
+ if (inspectResult.exitCode === 0 && inspectResult.stdout.includes('@')) {
390
+ const imageDigest = inspectResult.stdout.split('@')[1]?.trim();
391
+ console.log(`The image ${this.pushTag} has digest ${imageDigest}`);
392
+ }
393
+
394
+ logger.log('ok', `Pushed ${this.pushTag}`);
395
+ }
396
+
397
+ /**
398
+ * Pulls the Dockerfile from a registry
399
+ */
400
+ public async pull(registryArg: DockerRegistry, versionSuffixArg?: string): Promise<void> {
401
+ const pullTag = Dockerfile.getDockerTagString(
402
+ this.managerRef,
403
+ registryArg.registryUrl,
404
+ this.repo,
405
+ this.version,
406
+ versionSuffixArg
407
+ );
408
+
409
+ await smartshellInstance.exec(`docker pull ${pullTag}`);
410
+ await smartshellInstance.exec(`docker tag ${pullTag} ${this.buildTag}`);
411
+
412
+ logger.log('ok', `Pulled and tagged ${pullTag} as ${this.buildTag}`);
413
+ }
414
+
415
+ /**
416
+ * Tests the Dockerfile by running a test script if it exists
417
+ */
418
+ public async test(): Promise<void> {
419
+ const testDir = this.managerRef.config.testDir || plugins.path.join(paths.cwd, 'test');
420
+ const testFile = plugins.path.join(testDir, 'test_' + this.version + '.sh');
421
+
422
+ const fs = require('fs');
423
+ const testFileExists = fs.existsSync(testFile);
424
+
425
+ if (testFileExists) {
426
+ logger.log('info', `Running tests for ${this.cleanTag}`);
427
+
428
+ // Run tests in container
429
+ await smartshellInstance.exec(
430
+ `docker run --name tsdocker_test_container --entrypoint="bash" ${this.buildTag} -c "mkdir /tsdocker_test"`
431
+ );
432
+ await smartshellInstance.exec(`docker cp ${testFile} tsdocker_test_container:/tsdocker_test/test.sh`);
433
+ await smartshellInstance.exec(`docker commit tsdocker_test_container tsdocker_test_image`);
434
+
435
+ const testResult = await smartshellInstance.exec(
436
+ `docker run --entrypoint="bash" tsdocker_test_image -x /tsdocker_test/test.sh`
437
+ );
438
+
439
+ // Cleanup
440
+ await smartshellInstance.exec(`docker rm tsdocker_test_container`);
441
+ await smartshellInstance.exec(`docker rmi --force tsdocker_test_image`);
442
+
443
+ if (testResult.exitCode !== 0) {
444
+ throw new Error(`Tests failed for ${this.cleanTag}`);
445
+ }
446
+
447
+ logger.log('ok', `Tests passed for ${this.cleanTag}`);
448
+ } else {
449
+ logger.log('warn', `Skipping tests for ${this.cleanTag} because no test file was found at ${testFile}`);
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Gets the ID of a built Docker image
455
+ */
456
+ public async getId(): Promise<string> {
457
+ const result = await smartshellInstance.exec(
458
+ 'docker inspect --type=image --format="{{.Id}}" ' + this.buildTag
459
+ );
460
+ return result.stdout.trim();
461
+ }
462
+ }