@git.zone/tsdocker 1.15.0 → 1.15.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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dockerfile.d.ts +12 -5
- package/dist_ts/classes.dockerfile.js +45 -48
- package/dist_ts/classes.registrycopy.d.ts +70 -0
- package/dist_ts/classes.registrycopy.js +359 -0
- package/dist_ts/classes.tsdockermanager.d.ts +2 -1
- package/dist_ts/classes.tsdockermanager.js +28 -18
- package/package.json +1 -1
- package/readme.hints.md +12 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dockerfile.ts +54 -56
- package/ts/classes.registrycopy.ts +511 -0
- package/ts/classes.tsdockermanager.ts +27 -18
package/ts/classes.dockerfile.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as plugins from './tsdocker.plugins.js';
|
|
|
2
2
|
import * as paths from './tsdocker.paths.js';
|
|
3
3
|
import { logger, formatDuration } from './tsdocker.logging.js';
|
|
4
4
|
import { DockerRegistry } from './classes.dockerregistry.js';
|
|
5
|
+
import { RegistryCopy } from './classes.registrycopy.js';
|
|
5
6
|
import type { IDockerfileOptions, ITsDockerConfig, IBuildCommandOptions } from './interfaces/index.js';
|
|
6
7
|
import type { TsDockerManager } from './classes.tsdockermanager.js';
|
|
7
8
|
import * as fs from 'fs';
|
|
@@ -139,31 +140,32 @@ export class Dockerfile {
|
|
|
139
140
|
return sortedDockerfileArray;
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
/**
|
|
143
|
+
/** Local registry is always needed — it's the canonical store for all built images. */
|
|
143
144
|
public static needsLocalRegistry(
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
_dockerfiles?: Dockerfile[],
|
|
146
|
+
_options?: { platform?: string },
|
|
146
147
|
): boolean {
|
|
147
|
-
|
|
148
|
-
if (!hasLocalDeps) return false;
|
|
149
|
-
const config = dockerfiles[0]?.managerRef?.config;
|
|
150
|
-
return !!options?.platform || !!(config?.platforms && config.platforms.length > 1);
|
|
148
|
+
return true;
|
|
151
149
|
}
|
|
152
150
|
|
|
153
|
-
/** Starts a
|
|
151
|
+
/** Starts a persistent registry:2 container on port 5234 with volume storage. */
|
|
154
152
|
public static async startLocalRegistry(isRootless?: boolean): Promise<void> {
|
|
153
|
+
// Ensure persistent storage directory exists
|
|
154
|
+
const registryDataDir = plugins.path.join(paths.cwd, '.nogit', 'docker-registry');
|
|
155
|
+
fs.mkdirSync(registryDataDir, { recursive: true });
|
|
156
|
+
|
|
155
157
|
await smartshellInstance.execSilent(
|
|
156
158
|
`docker rm -f ${LOCAL_REGISTRY_CONTAINER} 2>/dev/null || true`
|
|
157
159
|
);
|
|
158
160
|
const result = await smartshellInstance.execSilent(
|
|
159
|
-
`docker run -d --name ${LOCAL_REGISTRY_CONTAINER} -p ${LOCAL_REGISTRY_PORT}:5000 registry:2`
|
|
161
|
+
`docker run -d --name ${LOCAL_REGISTRY_CONTAINER} -p ${LOCAL_REGISTRY_PORT}:5000 -v "${registryDataDir}:/var/lib/registry" registry:2`
|
|
160
162
|
);
|
|
161
163
|
if (result.exitCode !== 0) {
|
|
162
164
|
throw new Error(`Failed to start local registry: ${result.stderr || result.stdout}`);
|
|
163
165
|
}
|
|
164
166
|
// registry:2 starts near-instantly; brief wait for readiness
|
|
165
167
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
166
|
-
logger.log('info', `Started local registry at ${LOCAL_REGISTRY_HOST} (
|
|
168
|
+
logger.log('info', `Started local registry at ${LOCAL_REGISTRY_HOST} (persistent storage at .nogit/docker-registry/)`);
|
|
167
169
|
if (isRootless) {
|
|
168
170
|
logger.log('warn', `[rootless] Registry on port ${LOCAL_REGISTRY_PORT} — if buildx cannot reach localhost:${LOCAL_REGISTRY_PORT}, try 127.0.0.1:${LOCAL_REGISTRY_PORT}`);
|
|
169
171
|
}
|
|
@@ -246,11 +248,8 @@ export class Dockerfile {
|
|
|
246
248
|
): Promise<Dockerfile[]> {
|
|
247
249
|
const total = sortedArrayArg.length;
|
|
248
250
|
const overallStart = Date.now();
|
|
249
|
-
const useRegistry = Dockerfile.needsLocalRegistry(sortedArrayArg, options);
|
|
250
251
|
|
|
251
|
-
|
|
252
|
-
await Dockerfile.startLocalRegistry(options?.isRootless);
|
|
253
|
-
}
|
|
252
|
+
await Dockerfile.startLocalRegistry(options?.isRootless);
|
|
254
253
|
|
|
255
254
|
try {
|
|
256
255
|
if (options?.parallel) {
|
|
@@ -282,8 +281,9 @@ export class Dockerfile {
|
|
|
282
281
|
|
|
283
282
|
await Dockerfile.runWithConcurrency(tasks, concurrency);
|
|
284
283
|
|
|
285
|
-
// After the entire level completes,
|
|
284
|
+
// After the entire level completes, push all to local registry + tag for deps
|
|
286
285
|
for (const df of level) {
|
|
286
|
+
// Tag in host daemon for dependency resolution
|
|
287
287
|
const dependentBaseImages = new Set<string>();
|
|
288
288
|
for (const other of sortedArrayArg) {
|
|
289
289
|
if (other.localBaseDockerfile === df && other.baseImage !== df.buildTag) {
|
|
@@ -294,7 +294,8 @@ export class Dockerfile {
|
|
|
294
294
|
logger.log('info', `Tagging ${df.buildTag} as ${fullTag} for local dependency resolution`);
|
|
295
295
|
await smartshellInstance.exec(`docker tag ${df.buildTag} ${fullTag}`);
|
|
296
296
|
}
|
|
297
|
-
|
|
297
|
+
// Push ALL images to local registry (skip if already pushed via buildx)
|
|
298
|
+
if (!df.localRegistryTag) {
|
|
298
299
|
await Dockerfile.pushToLocalRegistry(df);
|
|
299
300
|
}
|
|
300
301
|
}
|
|
@@ -321,16 +322,14 @@ export class Dockerfile {
|
|
|
321
322
|
await smartshellInstance.exec(`docker tag ${dockerfileArg.buildTag} ${fullTag}`);
|
|
322
323
|
}
|
|
323
324
|
|
|
324
|
-
// Push to local registry
|
|
325
|
-
if (
|
|
325
|
+
// Push ALL images to local registry (skip if already pushed via buildx)
|
|
326
|
+
if (!dockerfileArg.localRegistryTag) {
|
|
326
327
|
await Dockerfile.pushToLocalRegistry(dockerfileArg);
|
|
327
328
|
}
|
|
328
329
|
}
|
|
329
330
|
}
|
|
330
331
|
} finally {
|
|
331
|
-
|
|
332
|
-
await Dockerfile.stopLocalRegistry();
|
|
333
|
-
}
|
|
332
|
+
await Dockerfile.stopLocalRegistry();
|
|
334
333
|
}
|
|
335
334
|
|
|
336
335
|
logger.log('info', `Total build time: ${formatDuration(Date.now() - overallStart)}`);
|
|
@@ -594,17 +593,12 @@ export class Dockerfile {
|
|
|
594
593
|
buildCommand = `docker buildx build --platform ${platformOverride}${noCacheFlag}${buildContextFlag} --load -t ${this.buildTag} -f ${this.filePath} ${buildArgsString} .`;
|
|
595
594
|
logger.log('info', `Build: buildx --platform ${platformOverride} --load`);
|
|
596
595
|
} else if (config.platforms && config.platforms.length > 1) {
|
|
597
|
-
// Multi-platform build using buildx
|
|
596
|
+
// Multi-platform build using buildx — always push to local registry
|
|
598
597
|
const platformString = config.platforms.join(',');
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
logger.log('info', `Build: buildx --platform ${platformString} --push`);
|
|
604
|
-
} else {
|
|
605
|
-
buildCommand += ' --load';
|
|
606
|
-
logger.log('info', `Build: buildx --platform ${platformString} --load`);
|
|
607
|
-
}
|
|
598
|
+
const localTag = `${LOCAL_REGISTRY_HOST}/${this.buildTag}`;
|
|
599
|
+
buildCommand = `docker buildx build --platform ${platformString}${noCacheFlag}${buildContextFlag} -t ${localTag} -f ${this.filePath} ${buildArgsString} --push .`;
|
|
600
|
+
this.localRegistryTag = localTag;
|
|
601
|
+
logger.log('info', `Build: buildx --platform ${platformString} --push to local registry`);
|
|
608
602
|
} else {
|
|
609
603
|
// Standard build
|
|
610
604
|
const versionLabel = this.managerRef.projectInfo?.npm?.version || 'unknown';
|
|
@@ -645,38 +639,39 @@ export class Dockerfile {
|
|
|
645
639
|
}
|
|
646
640
|
|
|
647
641
|
/**
|
|
648
|
-
* Pushes the Dockerfile to a registry
|
|
642
|
+
* Pushes the Dockerfile to a registry using OCI Distribution API copy
|
|
643
|
+
* from the local registry to the remote registry.
|
|
649
644
|
*/
|
|
650
645
|
public async push(dockerRegistryArg: DockerRegistry, versionSuffix?: string): Promise<void> {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
this.repo,
|
|
655
|
-
this.version,
|
|
656
|
-
versionSuffix
|
|
657
|
-
);
|
|
646
|
+
const destRepo = this.getDestRepo(dockerRegistryArg.registryUrl);
|
|
647
|
+
const destTag = versionSuffix ? `${this.version}_${versionSuffix}` : this.version;
|
|
648
|
+
const registryCopy = new RegistryCopy();
|
|
658
649
|
|
|
659
|
-
|
|
660
|
-
|
|
650
|
+
this.pushTag = `${dockerRegistryArg.registryUrl}/${destRepo}:${destTag}`;
|
|
651
|
+
logger.log('info', `Pushing ${this.pushTag} via OCI copy from local registry...`);
|
|
661
652
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
653
|
+
await registryCopy.copyImage(
|
|
654
|
+
LOCAL_REGISTRY_HOST,
|
|
655
|
+
this.repo,
|
|
656
|
+
this.version,
|
|
657
|
+
dockerRegistryArg.registryUrl,
|
|
658
|
+
destRepo,
|
|
659
|
+
destTag,
|
|
660
|
+
{ username: dockerRegistryArg.username, password: dockerRegistryArg.password },
|
|
670
661
|
);
|
|
671
662
|
|
|
672
|
-
if (inspectResult.exitCode === 0 && inspectResult.stdout.includes('@')) {
|
|
673
|
-
const imageDigest = inspectResult.stdout.split('@')[1]?.trim();
|
|
674
|
-
logger.log('info', `The image ${this.pushTag} has digest ${imageDigest}`);
|
|
675
|
-
}
|
|
676
|
-
|
|
677
663
|
logger.log('ok', `Pushed ${this.pushTag}`);
|
|
678
664
|
}
|
|
679
665
|
|
|
666
|
+
/**
|
|
667
|
+
* Returns the destination repository for a given registry URL,
|
|
668
|
+
* using registryRepoMap if configured, otherwise the default repo.
|
|
669
|
+
*/
|
|
670
|
+
private getDestRepo(registryUrl: string): string {
|
|
671
|
+
const config = this.managerRef.config;
|
|
672
|
+
return config.registryRepoMap?.[registryUrl] || this.repo;
|
|
673
|
+
}
|
|
674
|
+
|
|
680
675
|
/**
|
|
681
676
|
* Pulls the Dockerfile from a registry
|
|
682
677
|
*/
|
|
@@ -696,19 +691,22 @@ export class Dockerfile {
|
|
|
696
691
|
}
|
|
697
692
|
|
|
698
693
|
/**
|
|
699
|
-
* Tests the Dockerfile by running a test script if it exists
|
|
694
|
+
* Tests the Dockerfile by running a test script if it exists.
|
|
695
|
+
* For multi-platform builds, uses the local registry tag so Docker can auto-pull.
|
|
700
696
|
*/
|
|
701
697
|
public async test(): Promise<number> {
|
|
702
698
|
const startTime = Date.now();
|
|
703
699
|
const testDir = this.managerRef.config.testDir || plugins.path.join(paths.cwd, 'test');
|
|
704
700
|
const testFile = plugins.path.join(testDir, 'test_' + this.version + '.sh');
|
|
701
|
+
// Use local registry tag for multi-platform images (not in daemon), otherwise buildTag
|
|
702
|
+
const imageRef = this.localRegistryTag || this.buildTag;
|
|
705
703
|
|
|
706
704
|
const testFileExists = fs.existsSync(testFile);
|
|
707
705
|
|
|
708
706
|
if (testFileExists) {
|
|
709
707
|
// Run tests in container
|
|
710
708
|
await smartshellInstance.exec(
|
|
711
|
-
`docker run --name tsdocker_test_container --entrypoint="bash" ${
|
|
709
|
+
`docker run --name tsdocker_test_container --entrypoint="bash" ${imageRef} -c "mkdir /tsdocker_test"`
|
|
712
710
|
);
|
|
713
711
|
await smartshellInstance.exec(`docker cp ${testFile} tsdocker_test_container:/tsdocker_test/test.sh`);
|
|
714
712
|
await smartshellInstance.exec(`docker commit tsdocker_test_container tsdocker_test_image`);
|