@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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dockercontext.js +13 -3
- package/dist_ts/classes.dockerfile.d.ts +24 -10
- package/dist_ts/classes.dockerfile.js +163 -77
- package/dist_ts/classes.registrycopy.d.ts +70 -0
- package/dist_ts/classes.registrycopy.js +359 -0
- package/dist_ts/classes.tsdockermanager.d.ts +9 -1
- package/dist_ts/classes.tsdockermanager.js +51 -22
- package/dist_ts/classes.tsdockersession.d.ts +35 -0
- package/dist_ts/classes.tsdockersession.js +92 -0
- package/dist_ts/interfaces/index.d.ts +1 -0
- package/dist_ts/tsdocker.cli.js +4 -1
- package/package.json +1 -1
- package/readme.hints.md +12 -0
- package/readme.md +186 -111
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dockercontext.ts +12 -2
- package/ts/classes.dockerfile.ts +188 -86
- package/ts/classes.registrycopy.ts +511 -0
- package/ts/classes.tsdockermanager.ts +52 -22
- package/ts/classes.tsdockersession.ts +107 -0
- package/ts/interfaces/index.ts +1 -0
- package/ts/tsdocker.cli.ts +3 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { logger } from './tsdocker.logging.js';
|
|
5
|
+
/**
|
|
6
|
+
* OCI Distribution API client for copying images between registries.
|
|
7
|
+
* Supports manifest lists (multi-arch) and single-platform manifests.
|
|
8
|
+
* Uses native fetch (Node 18+).
|
|
9
|
+
*/
|
|
10
|
+
export class RegistryCopy {
|
|
11
|
+
tokenCache = {};
|
|
12
|
+
/**
|
|
13
|
+
* Reads Docker credentials from ~/.docker/config.json for a given registry.
|
|
14
|
+
* Supports base64-encoded "auth" field in the config.
|
|
15
|
+
*/
|
|
16
|
+
static getDockerConfigCredentials(registryUrl) {
|
|
17
|
+
try {
|
|
18
|
+
const configPath = path.join(os.homedir(), '.docker', 'config.json');
|
|
19
|
+
if (!fs.existsSync(configPath))
|
|
20
|
+
return null;
|
|
21
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
22
|
+
const auths = config.auths || {};
|
|
23
|
+
// Try exact match first, then common variations
|
|
24
|
+
const keys = [
|
|
25
|
+
registryUrl,
|
|
26
|
+
`https://${registryUrl}`,
|
|
27
|
+
`http://${registryUrl}`,
|
|
28
|
+
];
|
|
29
|
+
// Docker Hub special cases
|
|
30
|
+
if (registryUrl === 'docker.io' || registryUrl === 'registry-1.docker.io') {
|
|
31
|
+
keys.push('https://index.docker.io/v1/', 'https://index.docker.io/v2/', 'index.docker.io', 'docker.io', 'registry-1.docker.io');
|
|
32
|
+
}
|
|
33
|
+
for (const key of keys) {
|
|
34
|
+
if (auths[key]?.auth) {
|
|
35
|
+
const decoded = Buffer.from(auths[key].auth, 'base64').toString('utf-8');
|
|
36
|
+
const colonIndex = decoded.indexOf(':');
|
|
37
|
+
if (colonIndex > 0) {
|
|
38
|
+
return {
|
|
39
|
+
username: decoded.substring(0, colonIndex),
|
|
40
|
+
password: decoded.substring(colonIndex + 1),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Returns the API base URL for a registry.
|
|
53
|
+
* Docker Hub uses registry-1.docker.io as API endpoint.
|
|
54
|
+
*/
|
|
55
|
+
getRegistryApiBase(registry) {
|
|
56
|
+
if (registry === 'docker.io' || registry === 'index.docker.io') {
|
|
57
|
+
return 'https://registry-1.docker.io';
|
|
58
|
+
}
|
|
59
|
+
// Local registries (localhost) use HTTP
|
|
60
|
+
if (registry.startsWith('localhost') || registry.startsWith('127.0.0.1')) {
|
|
61
|
+
return `http://${registry}`;
|
|
62
|
+
}
|
|
63
|
+
return `https://${registry}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Obtains a Bearer token for registry operations.
|
|
67
|
+
* Follows the standard Docker auth flow:
|
|
68
|
+
* GET /v2/ → 401 with Www-Authenticate → request token
|
|
69
|
+
*/
|
|
70
|
+
async getToken(registry, repo, actions, credentials) {
|
|
71
|
+
const scope = `repository:${repo}:${actions}`;
|
|
72
|
+
const cached = this.tokenCache[`${registry}/${scope}`];
|
|
73
|
+
if (cached && cached.expiry > Date.now()) {
|
|
74
|
+
return cached.token;
|
|
75
|
+
}
|
|
76
|
+
const apiBase = this.getRegistryApiBase(registry);
|
|
77
|
+
// Local registries typically don't need auth
|
|
78
|
+
if (registry.startsWith('localhost') || registry.startsWith('127.0.0.1')) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const checkResp = await fetch(`${apiBase}/v2/`, { method: 'GET' });
|
|
83
|
+
if (checkResp.ok)
|
|
84
|
+
return null; // No auth needed
|
|
85
|
+
const wwwAuth = checkResp.headers.get('www-authenticate') || '';
|
|
86
|
+
const realmMatch = wwwAuth.match(/realm="([^"]+)"/);
|
|
87
|
+
const serviceMatch = wwwAuth.match(/service="([^"]+)"/);
|
|
88
|
+
if (!realmMatch)
|
|
89
|
+
return null;
|
|
90
|
+
const realm = realmMatch[1];
|
|
91
|
+
const service = serviceMatch ? serviceMatch[1] : '';
|
|
92
|
+
const tokenUrl = new URL(realm);
|
|
93
|
+
tokenUrl.searchParams.set('scope', scope);
|
|
94
|
+
if (service)
|
|
95
|
+
tokenUrl.searchParams.set('service', service);
|
|
96
|
+
const headers = {};
|
|
97
|
+
const creds = credentials || RegistryCopy.getDockerConfigCredentials(registry);
|
|
98
|
+
if (creds) {
|
|
99
|
+
headers['Authorization'] = 'Basic ' + Buffer.from(`${creds.username}:${creds.password}`).toString('base64');
|
|
100
|
+
}
|
|
101
|
+
const tokenResp = await fetch(tokenUrl.toString(), { headers });
|
|
102
|
+
if (!tokenResp.ok) {
|
|
103
|
+
const body = await tokenResp.text();
|
|
104
|
+
throw new Error(`Token request failed (${tokenResp.status}): ${body}`);
|
|
105
|
+
}
|
|
106
|
+
const tokenData = await tokenResp.json();
|
|
107
|
+
const token = tokenData.token || tokenData.access_token;
|
|
108
|
+
if (token) {
|
|
109
|
+
// Cache for 5 minutes (conservative)
|
|
110
|
+
this.tokenCache[`${registry}/${scope}`] = {
|
|
111
|
+
token,
|
|
112
|
+
expiry: Date.now() + 5 * 60 * 1000,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return token;
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
logger.log('warn', `Auth for ${registry}: ${err.message}`);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Makes an authenticated request to a registry.
|
|
124
|
+
*/
|
|
125
|
+
async registryFetch(registry, path, options = {}) {
|
|
126
|
+
const apiBase = this.getRegistryApiBase(registry);
|
|
127
|
+
const method = options.method || 'GET';
|
|
128
|
+
const headers = { ...(options.headers || {}) };
|
|
129
|
+
const repo = options.repo || '';
|
|
130
|
+
const actions = options.actions || 'pull';
|
|
131
|
+
const token = await this.getToken(registry, repo, actions, options.credentials);
|
|
132
|
+
if (token) {
|
|
133
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
134
|
+
}
|
|
135
|
+
const url = `${apiBase}${path}`;
|
|
136
|
+
const fetchOptions = { method, headers };
|
|
137
|
+
if (options.body) {
|
|
138
|
+
fetchOptions.body = options.body;
|
|
139
|
+
fetchOptions.duplex = 'half'; // Required for streaming body in Node
|
|
140
|
+
}
|
|
141
|
+
return fetch(url, fetchOptions);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Gets a manifest from a registry (supports both manifest lists and single manifests).
|
|
145
|
+
*/
|
|
146
|
+
async getManifest(registry, repo, reference, credentials) {
|
|
147
|
+
const accept = [
|
|
148
|
+
'application/vnd.oci.image.index.v1+json',
|
|
149
|
+
'application/vnd.docker.distribution.manifest.list.v2+json',
|
|
150
|
+
'application/vnd.oci.image.manifest.v1+json',
|
|
151
|
+
'application/vnd.docker.distribution.manifest.v2+json',
|
|
152
|
+
].join(', ');
|
|
153
|
+
const resp = await this.registryFetch(registry, `/v2/${repo}/manifests/${reference}`, {
|
|
154
|
+
headers: { 'Accept': accept },
|
|
155
|
+
repo,
|
|
156
|
+
actions: 'pull',
|
|
157
|
+
credentials,
|
|
158
|
+
});
|
|
159
|
+
if (!resp.ok) {
|
|
160
|
+
const body = await resp.text();
|
|
161
|
+
throw new Error(`Failed to get manifest ${registry}/${repo}:${reference} (${resp.status}): ${body}`);
|
|
162
|
+
}
|
|
163
|
+
const raw = Buffer.from(await resp.arrayBuffer());
|
|
164
|
+
const contentType = resp.headers.get('content-type') || '';
|
|
165
|
+
const digest = resp.headers.get('docker-content-digest') || this.computeDigest(raw);
|
|
166
|
+
const body = JSON.parse(raw.toString('utf-8'));
|
|
167
|
+
return { contentType, body, digest, raw };
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Checks if a blob exists in the destination registry.
|
|
171
|
+
*/
|
|
172
|
+
async blobExists(registry, repo, digest, credentials) {
|
|
173
|
+
const resp = await this.registryFetch(registry, `/v2/${repo}/blobs/${digest}`, {
|
|
174
|
+
method: 'HEAD',
|
|
175
|
+
repo,
|
|
176
|
+
actions: 'pull,push',
|
|
177
|
+
credentials,
|
|
178
|
+
});
|
|
179
|
+
return resp.ok;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Copies a single blob from source to destination registry.
|
|
183
|
+
* Uses monolithic upload (POST initiate + PUT complete).
|
|
184
|
+
*/
|
|
185
|
+
async copyBlob(srcRegistry, srcRepo, destRegistry, destRepo, digest, srcCredentials, destCredentials) {
|
|
186
|
+
// Check if blob already exists at destination
|
|
187
|
+
const exists = await this.blobExists(destRegistry, destRepo, digest, destCredentials);
|
|
188
|
+
if (exists) {
|
|
189
|
+
logger.log('info', ` Blob ${digest.substring(0, 19)}... already exists, skipping`);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
// Download blob from source
|
|
193
|
+
const getResp = await this.registryFetch(srcRegistry, `/v2/${srcRepo}/blobs/${digest}`, {
|
|
194
|
+
repo: srcRepo,
|
|
195
|
+
actions: 'pull',
|
|
196
|
+
credentials: srcCredentials,
|
|
197
|
+
});
|
|
198
|
+
if (!getResp.ok) {
|
|
199
|
+
throw new Error(`Failed to get blob ${digest} from ${srcRegistry}/${srcRepo}: ${getResp.status}`);
|
|
200
|
+
}
|
|
201
|
+
const blobData = Buffer.from(await getResp.arrayBuffer());
|
|
202
|
+
const blobSize = blobData.length;
|
|
203
|
+
// Initiate upload at destination
|
|
204
|
+
const postResp = await this.registryFetch(destRegistry, `/v2/${destRepo}/blobs/uploads/`, {
|
|
205
|
+
method: 'POST',
|
|
206
|
+
headers: { 'Content-Length': '0' },
|
|
207
|
+
repo: destRepo,
|
|
208
|
+
actions: 'pull,push',
|
|
209
|
+
credentials: destCredentials,
|
|
210
|
+
});
|
|
211
|
+
if (!postResp.ok && postResp.status !== 202) {
|
|
212
|
+
const body = await postResp.text();
|
|
213
|
+
throw new Error(`Failed to initiate upload at ${destRegistry}/${destRepo}: ${postResp.status} ${body}`);
|
|
214
|
+
}
|
|
215
|
+
// Get upload URL from Location header
|
|
216
|
+
let uploadUrl = postResp.headers.get('location') || '';
|
|
217
|
+
if (!uploadUrl) {
|
|
218
|
+
throw new Error(`No upload location returned from ${destRegistry}/${destRepo}`);
|
|
219
|
+
}
|
|
220
|
+
// Make upload URL absolute if relative
|
|
221
|
+
if (uploadUrl.startsWith('/')) {
|
|
222
|
+
const apiBase = this.getRegistryApiBase(destRegistry);
|
|
223
|
+
uploadUrl = `${apiBase}${uploadUrl}`;
|
|
224
|
+
}
|
|
225
|
+
// Complete upload with PUT (monolithic)
|
|
226
|
+
const separator = uploadUrl.includes('?') ? '&' : '?';
|
|
227
|
+
const putUrl = `${uploadUrl}${separator}digest=${encodeURIComponent(digest)}`;
|
|
228
|
+
// For PUT to the upload URL, we need auth
|
|
229
|
+
const token = await this.getToken(destRegistry, destRepo, 'pull,push', destCredentials);
|
|
230
|
+
const putHeaders = {
|
|
231
|
+
'Content-Type': 'application/octet-stream',
|
|
232
|
+
'Content-Length': String(blobSize),
|
|
233
|
+
};
|
|
234
|
+
if (token) {
|
|
235
|
+
putHeaders['Authorization'] = `Bearer ${token}`;
|
|
236
|
+
}
|
|
237
|
+
const putResp = await fetch(putUrl, {
|
|
238
|
+
method: 'PUT',
|
|
239
|
+
headers: putHeaders,
|
|
240
|
+
body: blobData,
|
|
241
|
+
});
|
|
242
|
+
if (!putResp.ok) {
|
|
243
|
+
const body = await putResp.text();
|
|
244
|
+
throw new Error(`Failed to upload blob ${digest} to ${destRegistry}/${destRepo}: ${putResp.status} ${body}`);
|
|
245
|
+
}
|
|
246
|
+
const sizeStr = blobSize > 1048576
|
|
247
|
+
? `${(blobSize / 1048576).toFixed(1)} MB`
|
|
248
|
+
: `${(blobSize / 1024).toFixed(1)} KB`;
|
|
249
|
+
logger.log('info', ` Copied blob ${digest.substring(0, 19)}... (${sizeStr})`);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Pushes a manifest to a registry.
|
|
253
|
+
*/
|
|
254
|
+
async putManifest(registry, repo, reference, manifest, contentType, credentials) {
|
|
255
|
+
const resp = await this.registryFetch(registry, `/v2/${repo}/manifests/${reference}`, {
|
|
256
|
+
method: 'PUT',
|
|
257
|
+
headers: {
|
|
258
|
+
'Content-Type': contentType,
|
|
259
|
+
'Content-Length': String(manifest.length),
|
|
260
|
+
},
|
|
261
|
+
body: manifest,
|
|
262
|
+
repo,
|
|
263
|
+
actions: 'pull,push',
|
|
264
|
+
credentials,
|
|
265
|
+
});
|
|
266
|
+
if (!resp.ok) {
|
|
267
|
+
const body = await resp.text();
|
|
268
|
+
throw new Error(`Failed to put manifest ${registry}/${repo}:${reference} (${resp.status}): ${body}`);
|
|
269
|
+
}
|
|
270
|
+
const digest = resp.headers.get('docker-content-digest') || this.computeDigest(manifest);
|
|
271
|
+
return digest;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Copies a single-platform manifest and all its blobs from source to destination.
|
|
275
|
+
*/
|
|
276
|
+
async copySingleManifest(srcRegistry, srcRepo, destRegistry, destRepo, manifestDigest, srcCredentials, destCredentials) {
|
|
277
|
+
// Get the platform manifest
|
|
278
|
+
const { body: manifest, contentType, raw } = await this.getManifest(srcRegistry, srcRepo, manifestDigest, srcCredentials);
|
|
279
|
+
// Copy config blob
|
|
280
|
+
if (manifest.config?.digest) {
|
|
281
|
+
logger.log('info', ` Copying config blob...`);
|
|
282
|
+
await this.copyBlob(srcRegistry, srcRepo, destRegistry, destRepo, manifest.config.digest, srcCredentials, destCredentials);
|
|
283
|
+
}
|
|
284
|
+
// Copy layer blobs
|
|
285
|
+
const layers = manifest.layers || [];
|
|
286
|
+
for (let i = 0; i < layers.length; i++) {
|
|
287
|
+
const layer = layers[i];
|
|
288
|
+
logger.log('info', ` Copying layer ${i + 1}/${layers.length}...`);
|
|
289
|
+
await this.copyBlob(srcRegistry, srcRepo, destRegistry, destRepo, layer.digest, srcCredentials, destCredentials);
|
|
290
|
+
}
|
|
291
|
+
// Push the platform manifest by digest
|
|
292
|
+
await this.putManifest(destRegistry, destRepo, manifestDigest, raw, contentType, destCredentials);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Copies a complete image (single or multi-arch) from source to destination registry.
|
|
296
|
+
*
|
|
297
|
+
* @param srcRegistry - Source registry host (e.g., "localhost:5234")
|
|
298
|
+
* @param srcRepo - Source repository (e.g., "myapp")
|
|
299
|
+
* @param srcTag - Source tag (e.g., "v1.0.0")
|
|
300
|
+
* @param destRegistry - Destination registry host (e.g., "registry.gitlab.com")
|
|
301
|
+
* @param destRepo - Destination repository (e.g., "org/myapp")
|
|
302
|
+
* @param destTag - Destination tag (e.g., "v1.0.0" or "v1.0.0_arm64")
|
|
303
|
+
* @param credentials - Optional credentials for destination registry
|
|
304
|
+
*/
|
|
305
|
+
async copyImage(srcRegistry, srcRepo, srcTag, destRegistry, destRepo, destTag, credentials) {
|
|
306
|
+
logger.log('info', `Copying ${srcRegistry}/${srcRepo}:${srcTag} -> ${destRegistry}/${destRepo}:${destTag}`);
|
|
307
|
+
// Source is always the local registry (no credentials needed)
|
|
308
|
+
const srcCredentials = null;
|
|
309
|
+
const destCredentials = credentials || RegistryCopy.getDockerConfigCredentials(destRegistry);
|
|
310
|
+
// Get the top-level manifest
|
|
311
|
+
const topManifest = await this.getManifest(srcRegistry, srcRepo, srcTag, srcCredentials);
|
|
312
|
+
const { body, contentType, raw } = topManifest;
|
|
313
|
+
const isManifestList = contentType.includes('manifest.list') ||
|
|
314
|
+
contentType.includes('image.index') ||
|
|
315
|
+
body.manifests !== undefined;
|
|
316
|
+
if (isManifestList) {
|
|
317
|
+
// Multi-arch: copy each platform manifest + blobs, then push the manifest list
|
|
318
|
+
const platforms = (body.manifests || []);
|
|
319
|
+
logger.log('info', `Multi-arch manifest with ${platforms.length} platform(s)`);
|
|
320
|
+
for (const platformEntry of platforms) {
|
|
321
|
+
const platDesc = platformEntry.platform
|
|
322
|
+
? `${platformEntry.platform.os}/${platformEntry.platform.architecture}`
|
|
323
|
+
: platformEntry.digest;
|
|
324
|
+
logger.log('info', `Copying platform: ${platDesc}`);
|
|
325
|
+
await this.copySingleManifest(srcRegistry, srcRepo, destRegistry, destRepo, platformEntry.digest, srcCredentials, destCredentials);
|
|
326
|
+
}
|
|
327
|
+
// Push the manifest list/index with the destination tag
|
|
328
|
+
const digest = await this.putManifest(destRegistry, destRepo, destTag, raw, contentType, destCredentials);
|
|
329
|
+
logger.log('ok', `Pushed manifest list to ${destRegistry}/${destRepo}:${destTag} (${digest.substring(0, 19)}...)`);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
// Single-platform manifest: copy blobs + push manifest
|
|
333
|
+
logger.log('info', 'Single-platform manifest');
|
|
334
|
+
// Copy config blob
|
|
335
|
+
if (body.config?.digest) {
|
|
336
|
+
logger.log('info', ' Copying config blob...');
|
|
337
|
+
await this.copyBlob(srcRegistry, srcRepo, destRegistry, destRepo, body.config.digest, srcCredentials, destCredentials);
|
|
338
|
+
}
|
|
339
|
+
// Copy layer blobs
|
|
340
|
+
const layers = body.layers || [];
|
|
341
|
+
for (let i = 0; i < layers.length; i++) {
|
|
342
|
+
logger.log('info', ` Copying layer ${i + 1}/${layers.length}...`);
|
|
343
|
+
await this.copyBlob(srcRegistry, srcRepo, destRegistry, destRepo, layers[i].digest, srcCredentials, destCredentials);
|
|
344
|
+
}
|
|
345
|
+
// Push the manifest with the destination tag
|
|
346
|
+
const digest = await this.putManifest(destRegistry, destRepo, destTag, raw, contentType, destCredentials);
|
|
347
|
+
logger.log('ok', `Pushed manifest to ${destRegistry}/${destRepo}:${destTag} (${digest.substring(0, 19)}...)`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Computes sha256 digest of a buffer.
|
|
352
|
+
*/
|
|
353
|
+
computeDigest(data) {
|
|
354
|
+
const crypto = require('crypto');
|
|
355
|
+
const hash = crypto.createHash('sha256').update(data).digest('hex');
|
|
356
|
+
return `sha256:${hash}`;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZWdpc3RyeWNvcHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLnJlZ2lzdHJ5Y29weS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxNQUFNLElBQUksQ0FBQztBQUN6QixPQUFPLEtBQUssRUFBRSxNQUFNLElBQUksQ0FBQztBQUN6QixPQUFPLEtBQUssSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUM3QixPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFXL0M7Ozs7R0FJRztBQUNILE1BQU0sT0FBTyxZQUFZO0lBQ2YsVUFBVSxHQUFnQixFQUFFLENBQUM7SUFFckM7OztPQUdHO0lBQ0ksTUFBTSxDQUFDLDBCQUEwQixDQUFDLFdBQW1CO1FBQzFELElBQUksQ0FBQztZQUNILE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxFQUFFLFNBQVMsRUFBRSxhQUFhLENBQUMsQ0FBQztZQUNyRSxJQUFJLENBQUMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxVQUFVLENBQUM7Z0JBQUUsT0FBTyxJQUFJLENBQUM7WUFFNUMsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDO1lBQ2hFLE1BQU0sS0FBSyxHQUFHLE1BQU0sQ0FBQyxLQUFLLElBQUksRUFBRSxDQUFDO1lBRWpDLGdEQUFnRDtZQUNoRCxNQUFNLElBQUksR0FBRztnQkFDWCxXQUFXO2dCQUNYLFdBQVcsV0FBVyxFQUFFO2dCQUN4QixVQUFVLFdBQVcsRUFBRTthQUN4QixDQUFDO1lBRUYsMkJBQTJCO1lBQzNCLElBQUksV0FBVyxLQUFLLFdBQVcsSUFBSSxXQUFXLEtBQUssc0JBQXNCLEVBQUUsQ0FBQztnQkFDMUUsSUFBSSxDQUFDLElBQUksQ0FDUCw2QkFB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
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Dockerfile } from './classes.dockerfile.js';
|
|
2
2
|
import { RegistryStorage } from './classes.registrystorage.js';
|
|
3
3
|
import { DockerContext } from './classes.dockercontext.js';
|
|
4
|
+
import { TsDockerSession } from './classes.tsdockersession.js';
|
|
4
5
|
import type { ITsDockerConfig, IBuildCommandOptions } from './interfaces/index.js';
|
|
5
6
|
/**
|
|
6
7
|
* Main orchestrator class for Docker operations
|
|
@@ -10,6 +11,7 @@ export declare class TsDockerManager {
|
|
|
10
11
|
config: ITsDockerConfig;
|
|
11
12
|
projectInfo: any;
|
|
12
13
|
dockerContext: DockerContext;
|
|
14
|
+
session: TsDockerSession;
|
|
13
15
|
private dockerfiles;
|
|
14
16
|
constructor(config: ITsDockerConfig);
|
|
15
17
|
/**
|
|
@@ -47,7 +49,8 @@ export declare class TsDockerManager {
|
|
|
47
49
|
*/
|
|
48
50
|
pull(registryUrl: string): Promise<void>;
|
|
49
51
|
/**
|
|
50
|
-
* Runs tests for all Dockerfiles
|
|
52
|
+
* Runs tests for all Dockerfiles.
|
|
53
|
+
* Starts the local registry so multi-platform images can be auto-pulled.
|
|
51
54
|
*/
|
|
52
55
|
test(): Promise<void>;
|
|
53
56
|
/**
|
|
@@ -58,4 +61,9 @@ export declare class TsDockerManager {
|
|
|
58
61
|
* Gets the cached Dockerfiles (after discovery)
|
|
59
62
|
*/
|
|
60
63
|
getDockerfiles(): Dockerfile[];
|
|
64
|
+
/**
|
|
65
|
+
* Cleans up session-specific resources.
|
|
66
|
+
* In CI, removes the session-specific buildx builder to avoid accumulation.
|
|
67
|
+
*/
|
|
68
|
+
cleanup(): Promise<void>;
|
|
61
69
|
}
|