feat(core): Introduce per-invocation TsDockerSession and session-aware local registry and build orchestration; stream and parse buildx output for improved logging and visibility; detect Docker topology and add CI-safe cleanup; update README with multi-arch, parallel-build, caching, and local registry usage and new CLI flags.

This commit is contained in:
2026-02-07 10:30:52 +00:00
parent 63078139ec
commit 101c4286c1
9 changed files with 500 additions and 167 deletions

View File

@@ -6,6 +6,7 @@ import { DockerRegistry } from './classes.dockerregistry.js';
import { RegistryStorage } from './classes.registrystorage.js';
import { TsDockerCache } from './classes.tsdockercache.js';
import { DockerContext } from './classes.dockercontext.js';
import { TsDockerSession } from './classes.tsdockersession.js';
import type { ITsDockerConfig, IBuildCommandOptions } from './interfaces/index.js';
const smartshellInstance = new plugins.smartshell.Smartshell({
@@ -20,6 +21,7 @@ export class TsDockerManager {
public config: ITsDockerConfig;
public projectInfo: any;
public dockerContext: DockerContext;
public session!: TsDockerSession;
private dockerfiles: Dockerfile[] = [];
constructor(config: ITsDockerConfig) {
@@ -77,6 +79,9 @@ export class TsDockerManager {
}
}
// Create session identity (unique ports, names for CI concurrency)
this.session = await TsDockerSession.create();
logger.log('info', `Prepared TsDockerManager with ${this.registryStorage.getAllRegistries().length} registries`);
}
@@ -98,6 +103,10 @@ export class TsDockerManager {
this.dockerfiles = await Dockerfile.readDockerfiles(this);
this.dockerfiles = await Dockerfile.sortDockerfiles(this.dockerfiles);
this.dockerfiles = await Dockerfile.mapDockerfiles(this.dockerfiles);
// Inject session into each Dockerfile
for (const df of this.dockerfiles) {
df.session = this.session;
}
return this.dockerfiles;
}
@@ -187,7 +196,7 @@ export class TsDockerManager {
const total = toBuild.length;
const overallStart = Date.now();
await Dockerfile.startLocalRegistry(this.dockerContext.contextInfo?.isRootless);
await Dockerfile.startLocalRegistry(this.session, this.dockerContext.contextInfo?.isRootless);
try {
if (options?.parallel) {
@@ -240,7 +249,7 @@ export class TsDockerManager {
}
// Push ALL images to local registry (skip if already pushed via buildx)
if (!df.localRegistryTag) {
await Dockerfile.pushToLocalRegistry(df);
await Dockerfile.pushToLocalRegistry(this.session, df);
}
}
}
@@ -280,19 +289,19 @@ export class TsDockerManager {
// Push ALL images to local registry (skip if already pushed via buildx)
if (!dockerfileArg.localRegistryTag) {
await Dockerfile.pushToLocalRegistry(dockerfileArg);
await Dockerfile.pushToLocalRegistry(this.session, dockerfileArg);
}
}
}
} finally {
await Dockerfile.stopLocalRegistry();
await Dockerfile.stopLocalRegistry(this.session);
}
logger.log('info', `Total build time: ${formatDuration(Date.now() - overallStart)}`);
cache.save();
} else {
// === STANDARD MODE: build all via static helper ===
await Dockerfile.buildDockerfiles(toBuild, {
await Dockerfile.buildDockerfiles(toBuild, this.session, {
platform: options?.platform,
timeout: options?.timeout,
noCache: options?.noCache,
@@ -329,7 +338,7 @@ export class TsDockerManager {
* Ensures Docker buildx is set up for multi-architecture builds
*/
private async ensureBuildx(): Promise<void> {
const builderName = this.dockerContext.getBuilderName();
const builderName = this.dockerContext.getBuilderName() + (this.session?.config.builderSuffix || '');
const platforms = this.config.platforms?.join(', ') || 'default';
logger.log('info', `Setting up Docker buildx [${platforms}]...`);
logger.log('info', `Builder: ${builderName}`);
@@ -394,7 +403,7 @@ export class TsDockerManager {
}
// Start local registry (reads from persistent .nogit/docker-registry/)
await Dockerfile.startLocalRegistry(this.dockerContext.contextInfo?.isRootless);
await Dockerfile.startLocalRegistry(this.session, this.dockerContext.contextInfo?.isRootless);
try {
// Push each Dockerfile to each registry via OCI copy
for (const dockerfile of this.dockerfiles) {
@@ -403,7 +412,7 @@ export class TsDockerManager {
}
}
} finally {
await Dockerfile.stopLocalRegistry();
await Dockerfile.stopLocalRegistry(this.session);
}
logger.log('success', 'All images pushed successfully');
@@ -446,11 +455,11 @@ export class TsDockerManager {
logger.log('info', '');
logger.log('info', '=== TEST PHASE ===');
await Dockerfile.startLocalRegistry(this.dockerContext.contextInfo?.isRootless);
await Dockerfile.startLocalRegistry(this.session, this.dockerContext.contextInfo?.isRootless);
try {
await Dockerfile.testDockerfiles(this.dockerfiles);
} finally {
await Dockerfile.stopLocalRegistry();
await Dockerfile.stopLocalRegistry(this.session);
}
logger.log('success', 'All tests completed');
@@ -490,4 +499,16 @@ export class TsDockerManager {
public getDockerfiles(): Dockerfile[] {
return this.dockerfiles;
}
/**
* Cleans up session-specific resources.
* In CI, removes the session-specific buildx builder to avoid accumulation.
*/
public async cleanup(): Promise<void> {
if (this.session?.config.isCI && this.session.config.builderSuffix) {
const builderName = this.dockerContext.getBuilderName() + this.session.config.builderSuffix;
logger.log('info', `CI cleanup: removing buildx builder ${builderName}`);
await smartshellInstance.execSilent(`docker buildx rm ${builderName} 2>/dev/null || true`);
}
}
}