feat(build): add optional content-hash based build cache to skip rebuilding unchanged Dockerfiles

This commit is contained in:
2026-02-06 14:18:06 +00:00
parent 7131c16f80
commit cc83743f9a
6 changed files with 195 additions and 6 deletions

View File

@@ -4,6 +4,7 @@ import { logger } from './tsdocker.logging.js';
import { Dockerfile } from './classes.dockerfile.js';
import { DockerRegistry } from './classes.dockerregistry.js';
import { RegistryStorage } from './classes.registrystorage.js';
import { TsDockerCache } from './classes.tsdockercache.js';
import type { ITsDockerConfig, IBuildCommandOptions } from './interfaces/index.js';
const smartshellInstance = new plugins.smartshell.Smartshell({
@@ -136,11 +137,54 @@ export class TsDockerManager {
}
logger.log('info', `Building ${toBuild.length} Dockerfiles...`);
await Dockerfile.buildDockerfiles(toBuild, {
platform: options?.platform,
timeout: options?.timeout,
noCache: options?.noCache,
});
if (options?.cached) {
// === CACHED MODE: skip builds for unchanged Dockerfiles ===
logger.log('info', '=== CACHED MODE ACTIVE ===');
const cache = new TsDockerCache();
cache.load();
for (const dockerfileArg of toBuild) {
const skip = await cache.shouldSkipBuild(dockerfileArg.cleanTag, dockerfileArg.content);
if (skip) {
continue;
}
// Cache miss — build this Dockerfile
await dockerfileArg.build({
platform: options?.platform,
timeout: options?.timeout,
noCache: options?.noCache,
});
const imageId = await dockerfileArg.getId();
cache.recordBuild(dockerfileArg.cleanTag, dockerfileArg.content, imageId, dockerfileArg.buildTag);
}
// Perform dependency tagging for all Dockerfiles (even cache hits, since tags may be stale)
for (const dockerfileArg of toBuild) {
const dependentBaseImages = new Set<string>();
for (const other of toBuild) {
if (other.localBaseDockerfile === dockerfileArg && other.baseImage !== dockerfileArg.buildTag) {
dependentBaseImages.add(other.baseImage);
}
}
for (const fullTag of dependentBaseImages) {
logger.log('info', `Tagging ${dockerfileArg.buildTag} as ${fullTag} for local dependency resolution`);
await smartshellInstance.exec(`docker tag ${dockerfileArg.buildTag} ${fullTag}`);
}
}
cache.save();
} else {
// === STANDARD MODE: build all via static helper ===
await Dockerfile.buildDockerfiles(toBuild, {
platform: options?.platform,
timeout: options?.timeout,
noCache: options?.noCache,
});
}
logger.log('success', 'All Dockerfiles built successfully');
return toBuild;