import * as plugins from './tsdocker.plugins.js'; import * as paths from './tsdocker.paths.js'; 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 type { ITsDockerConfig } from './interfaces/index.js'; const smartshellInstance = new plugins.smartshell.Smartshell({ executor: 'bash', }); /** * Main orchestrator class for Docker operations */ export class TsDockerManager { public registryStorage: RegistryStorage; public config: ITsDockerConfig; public projectInfo: any; private dockerfiles: Dockerfile[] = []; constructor(config: ITsDockerConfig) { this.config = config; this.registryStorage = new RegistryStorage(); } /** * Prepares the manager by loading project info and registries */ public async prepare(): Promise { // Load project info try { const projectinfoInstance = new plugins.projectinfo.ProjectInfo(paths.cwd); this.projectInfo = { npm: { name: projectinfoInstance.npm.name, version: projectinfoInstance.npm.version, }, }; } catch (err) { logger.log('warn', 'Could not load project info'); this.projectInfo = null; } // Load registries from environment this.registryStorage.loadFromEnv(); // Add registries from config if specified if (this.config.registries) { for (const registryUrl of this.config.registries) { // Check if already loaded from env if (!this.registryStorage.getRegistryByUrl(registryUrl)) { // Try to load credentials for this registry from env const envVarName = registryUrl.replace(/\./g, '_').toUpperCase(); const envString = process.env[`DOCKER_REGISTRY_${envVarName}`]; if (envString) { try { const registry = DockerRegistry.fromEnvString(envString); this.registryStorage.addRegistry(registry); } catch (err) { logger.log('warn', `Could not load credentials for registry ${registryUrl}`); } } } } } logger.log('info', `Prepared TsDockerManager with ${this.registryStorage.getAllRegistries().length} registries`); } /** * Logs in to all configured registries */ public async login(): Promise { if (this.registryStorage.getAllRegistries().length === 0) { logger.log('warn', 'No registries configured'); return; } await this.registryStorage.loginAll(); } /** * Discovers and sorts Dockerfiles in the current directory */ public async discoverDockerfiles(): Promise { this.dockerfiles = await Dockerfile.readDockerfiles(this); this.dockerfiles = await Dockerfile.sortDockerfiles(this.dockerfiles); this.dockerfiles = await Dockerfile.mapDockerfiles(this.dockerfiles); return this.dockerfiles; } /** * Builds all discovered Dockerfiles in dependency order */ public async build(): Promise { if (this.dockerfiles.length === 0) { await this.discoverDockerfiles(); } if (this.dockerfiles.length === 0) { logger.log('warn', 'No Dockerfiles found'); return []; } // Check if buildx is needed if (this.config.platforms && this.config.platforms.length > 1) { await this.ensureBuildx(); } logger.log('info', `Building ${this.dockerfiles.length} Dockerfiles...`); await Dockerfile.buildDockerfiles(this.dockerfiles); logger.log('success', 'All Dockerfiles built successfully'); return this.dockerfiles; } /** * Ensures Docker buildx is set up for multi-architecture builds */ private async ensureBuildx(): Promise { logger.log('info', 'Setting up Docker buildx for multi-platform builds...'); // Check if a buildx builder exists const inspectResult = await smartshellInstance.exec('docker buildx inspect tsdocker-builder 2>/dev/null'); if (inspectResult.exitCode !== 0) { // Create a new buildx builder logger.log('info', 'Creating new buildx builder...'); await smartshellInstance.exec('docker buildx create --name tsdocker-builder --use'); await smartshellInstance.exec('docker buildx inspect --bootstrap'); } else { // Use existing builder await smartshellInstance.exec('docker buildx use tsdocker-builder'); } logger.log('ok', 'Docker buildx ready'); } /** * Pushes all built images to specified registries */ public async push(registryUrls?: string[]): Promise { if (this.dockerfiles.length === 0) { await this.discoverDockerfiles(); } if (this.dockerfiles.length === 0) { logger.log('warn', 'No Dockerfiles found to push'); return; } // Determine which registries to push to let registriesToPush: DockerRegistry[] = []; if (registryUrls && registryUrls.length > 0) { // Push to specified registries for (const url of registryUrls) { const registry = this.registryStorage.getRegistryByUrl(url); if (registry) { registriesToPush.push(registry); } else { logger.log('warn', `Registry ${url} not found in storage`); } } } else { // Push to all configured registries registriesToPush = this.registryStorage.getAllRegistries(); } if (registriesToPush.length === 0) { logger.log('warn', 'No registries available to push to'); return; } // Push each Dockerfile to each registry for (const dockerfile of this.dockerfiles) { for (const registry of registriesToPush) { await dockerfile.push(registry); } } logger.log('success', 'All images pushed successfully'); } /** * Pulls images from a specified registry */ public async pull(registryUrl: string): Promise { if (this.dockerfiles.length === 0) { await this.discoverDockerfiles(); } const registry = this.registryStorage.getRegistryByUrl(registryUrl); if (!registry) { throw new Error(`Registry ${registryUrl} not found`); } for (const dockerfile of this.dockerfiles) { await dockerfile.pull(registry); } logger.log('success', 'All images pulled successfully'); } /** * Runs tests for all Dockerfiles */ public async test(): Promise { if (this.dockerfiles.length === 0) { await this.discoverDockerfiles(); } if (this.dockerfiles.length === 0) { logger.log('warn', 'No Dockerfiles found to test'); return; } await Dockerfile.testDockerfiles(this.dockerfiles); logger.log('success', 'All tests completed'); } /** * Lists all discovered Dockerfiles and their info */ public async list(): Promise { if (this.dockerfiles.length === 0) { await this.discoverDockerfiles(); } console.log('\nDiscovered Dockerfiles:'); console.log('========================\n'); for (let i = 0; i < this.dockerfiles.length; i++) { const df = this.dockerfiles[i]; console.log(`${i + 1}. ${df.filePath}`); console.log(` Tag: ${df.cleanTag}`); console.log(` Base Image: ${df.baseImage}`); console.log(` Version: ${df.version}`); if (df.localBaseImageDependent) { console.log(` Depends on: ${df.localBaseDockerfile?.cleanTag}`); } console.log(''); } return this.dockerfiles; } /** * Gets the cached Dockerfiles (after discovery) */ public getDockerfiles(): Dockerfile[] { return this.dockerfiles; } }