BREAKING CHANGE(szci): delegate Docker operations to @git.zone/tsdocker, remove internal Docker managers and deprecated modules, simplify CLI and env var handling

This commit is contained in:
2026-02-06 16:12:41 +00:00
parent 5d18e53e30
commit 9d295f2633
28 changed files with 346 additions and 1181 deletions

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@ship.zone/szci',
version: '6.0.1',
version: '7.0.0',
description: 'Serve Zone CI - A tool to streamline Node.js and Docker workflows within CI environments, particularly GitLab CI, providing various CI/CD utilities. Powered by Deno with standalone executables.'
}

View File

@@ -1,9 +1,8 @@
import { Szci } from './szci.classes.szci.ts';
import { Dockerfile } from './manager.docker/mod.classes.dockerfile.ts';
export const szciInstance = new Szci();
export { Dockerfile, Szci };
export { Szci };
export const runCli = async () => {
await szciInstance.start();

View File

@@ -1,71 +0,0 @@
import { logger } from '../szci.logging.ts';
import * as plugins from './mod.plugins.ts';
import * as paths from '../szci.paths.ts';
import { bash } from '../szci.bash.ts';
import { Szci } from '../szci.classes.szci.ts';
export class SzciCloudronManager {
public szciRef: Szci;
constructor(szciArg: Szci) {
this.szciRef = szciArg;
}
/**
* handle cli input
* @param argvArg
*/
public handleCli = async (argvArg: any) => {
if (argvArg._.length >= 2) {
const action: string = argvArg._[1];
switch (action) {
case 'deploy':
await this.deploy();
break;
default:
logger.log('error', `>>szci cloudron ...<< action >>${action}<< not supported`);
}
} else {
logger.log(
'info',
`>>szci cloudron ...<< cli arguments invalid... Please read the documentation.`
);
}
};
/**
* Replaces the version string in CloudronManifest file
* @param versionArg
*/
public deploy = async () => {
logger.log('info', 'now deploying to cloudron...');
logger.log('info', 'installing cloudron cli...');
await bash(`pnpm install -g cloudron`);
logger.log('ok', 'cloudron cli installed');
// lets set the version in the CloudronManifest file
await this.prepareCloudronManifest(this.szciRef.szciConfig.getConfig().projectInfo.npm.version);
logger.log('ok', 'CloudronManifest prepared');
// lets figure out the docker image tag
const dockerImageTag = await this.szciRef.szciConfig.kvStorage.readKey('latestPushedDockerTag');
const appName = this.szciRef.szciConfig.getConfig().cloudronAppName;
const cloudronEnvVar = Deno.env.get("SZCI_LOGIN_CLOUDRON");
if (!cloudronEnvVar) {
logger.log('error', 'SZCI_LOGIN_CLOUDRON environment variable is not set');
Deno.exit(1);
}
const cloudronServer = cloudronEnvVar.split('|')[0];
const cloudronToken = cloudronEnvVar.split('|')[1];
await bash(`cloudron update --server ${cloudronServer} --token ${cloudronToken} --image ${dockerImageTag} --app ${appName}`);
};
private prepareCloudronManifest = async (versionArg: string) => {
const manifestPath = plugins.path.join(paths.cwd, 'CloudronManifest.json');
let manifestFile = plugins.smartfile.fs.toStringSync(manifestPath);
manifestFile = manifestFile.replace(/##version##/g, versionArg);
plugins.smartfile.memory.toFsSync(manifestFile, manifestPath);
logger.log('info', 'Version replaced in CloudronManifest file');
}
}

View File

@@ -1 +0,0 @@
export * from '../szci.plugins.ts';

View File

@@ -1,190 +1,78 @@
import { logger } from '../szci.logging.ts';
import * as plugins from './mod.plugins.ts';
import * as paths from '../szci.paths.ts';
import { bash } from '../szci.bash.ts';
// classes
import { Szci } from '../szci.classes.szci.ts';
import { Dockerfile } from './mod.classes.dockerfile.ts';
import { DockerRegistry } from './mod.classes.dockerregistry.ts';
import { RegistryStorage } from './mod.classes.registrystorage.ts';
export class SzciDockerManager {
public szciRef: Szci;
public szciRegistryStorage = new RegistryStorage();
constructor(szciArg: Szci) {
this.szciRef = szciArg;
}
/**
* handle cli input
* @param argvArg
* Bridges SZCI_LOGIN_DOCKER* env vars to DOCKER_REGISTRY_N format for tsdocker,
* and handles GitLab CI registry auto-login.
*/
private bridgeEnvVars() {
const env = Deno.env.toObject();
// Bridge GitLab CI registry as DOCKER_REGISTRY_0
if (env['GITLAB_CI']) {
const ciJobToken = env['CI_JOB_TOKEN'];
if (!ciJobToken) {
logger.log('error', 'Running in GitLab CI, but no CI_JOB_TOKEN found!');
Deno.exit(1);
}
Deno.env.set('DOCKER_REGISTRY_0', `registry.gitlab.com|gitlab-ci-token|${ciJobToken}`);
}
// Bridge SZCI_LOGIN_DOCKER* → DOCKER_REGISTRY_N
let registryIndex = 1;
const sortedKeys = Object.keys(env)
.filter((key) => key.startsWith('SZCI_LOGIN_DOCKER'))
.sort();
for (const key of sortedKeys) {
Deno.env.set(`DOCKER_REGISTRY_${registryIndex}`, env[key]);
registryIndex++;
}
}
/**
* Handle cli input by bridging env vars and delegating to tsdocker.
*/
public handleCli = async (argvArg: any) => {
if (argvArg._.length >= 2) {
const action: string = argvArg._[1];
switch (action) {
case 'build':
await this.build();
break;
case 'login':
case 'prepare':
await this.login();
break;
case 'test':
await this.test();
break;
case 'push':
await this.push(argvArg);
break;
case 'pull':
await this.pull(argvArg);
break;
default:
logger.log('error', `>>szci docker ...<< action >>${action}<< not supported`);
}
} else {
if (argvArg._.length < 2) {
logger.log(
'info',
`>>szci docker ...<< cli arguments invalid... Please read the documentation.`
);
}
};
/**
* builds a cwd of Dockerfiles by triggering a promisechain
*/
public build = async () => {
await this.prepare();
logger.log('info', 'now building Dockerfiles...');
await Dockerfile.readDockerfiles(this)
.then(Dockerfile.sortDockerfiles)
.then(Dockerfile.mapDockerfiles)
.then(Dockerfile.buildDockerfiles);
};
/**
* login to the DockerRegistries
*/
public login = async () => {
await this.prepare();
await this.szciRegistryStorage.loginAll();
};
/**
* logs in docker
*/
public prepare = async () => {
// Always login to GitLab Registry
if (Deno.env.get("GITLAB_CI")) {
console.log('gitlab ci detected');
if (!Deno.env.get("CI_JOB_TOKEN") || Deno.env.get("CI_JOB_TOKEN") === '') {
logger.log('error', 'Running in Gitlab CI, but no registry token specified by gitlab!');
Deno.exit(1);
}
this.szciRegistryStorage.addRegistry(
new DockerRegistry({
registryUrl: 'registry.gitlab.com',
username: 'gitlab-ci-token',
password: Deno.env.get("CI_JOB_TOKEN")!,
})
);
return;
}
// handle registries
await plugins.smartobject.forEachMinimatch(
Deno.env.toObject(),
'SZCI_LOGIN_DOCKER*',
async (envString: string) => {
this.szciRegistryStorage.addRegistry(DockerRegistry.fromEnvString(envString));
}
);
return;
};
this.bridgeEnvVars();
/**
* pushes an image towards a registry
* @param argvArg
*/
public push = async (argvArg: any) => {
await this.prepare();
let dockerRegistryUrls: string[] = [];
const action: string = argvArg._[1];
const extraArgs = argvArg._.slice(2).join(' ');
// lets parse the input of cli and npmextra
if (argvArg._.length >= 3 && argvArg._[2] !== 'npmextra') {
dockerRegistryUrls.push(argvArg._[2]);
} else {
if (this.szciRef.szciConfig.getConfig().dockerRegistries.length === 0) {
logger.log(
'warn',
`There are no docker registries listed in npmextra.json! This is strange!`
);
}
dockerRegistryUrls = dockerRegistryUrls.concat(
this.szciRef.szciConfig.getConfig().dockerRegistries
);
}
// lets determine the suffix
let suffix = null;
if (argvArg._.length >= 4) {
suffix = argvArg._[3];
}
// lets push to the registries
for (const dockerRegistryUrl of dockerRegistryUrls) {
const dockerfileArray = await Dockerfile.readDockerfiles(this)
.then(Dockerfile.sortDockerfiles)
.then(Dockerfile.mapDockerfiles);
const dockerRegistryToPushTo = await this.szciRegistryStorage.getRegistryByUrl(
dockerRegistryUrl
);
if (!dockerRegistryToPushTo) {
logger.log(
'error',
`Cannot push to registry ${dockerRegistryUrl}, because it was not found in the authenticated registry list.`
);
Deno.exit(1);
}
for (const dockerfile of dockerfileArray) {
await dockerfile.push(dockerRegistryToPushTo, suffix);
}
switch (action) {
case 'build':
await bash(`npx @git.zone/tsdocker build ${extraArgs}`.trim());
break;
case 'login':
case 'prepare':
await bash(`npx @git.zone/tsdocker login ${extraArgs}`.trim());
break;
case 'test':
await bash(`npx @git.zone/tsdocker test ${extraArgs}`.trim());
break;
case 'push':
await bash(`npx @git.zone/tsdocker push ${extraArgs}`.trim());
break;
case 'pull':
await bash(`npx @git.zone/tsdocker pull ${extraArgs}`.trim());
break;
default:
logger.log('error', `>>szci docker ...<< action >>${action}<< not supported`);
}
};
/**
* pulls an image
*/
public pull = async (argvArg: any) => {
await this.prepare();
const registryUrlArg = argvArg._[2];
let suffix = null;
if (argvArg._.length >= 4) {
suffix = argvArg._[3];
}
const localDockerRegistry = await this.szciRegistryStorage.getRegistryByUrl(registryUrlArg);
const dockerfileArray = await Dockerfile.readDockerfiles(this)
.then(Dockerfile.sortDockerfiles)
.then(Dockerfile.mapDockerfiles);
for (const dockerfile of dockerfileArray) {
await dockerfile.pull(localDockerRegistry, suffix);
}
};
/**
* tests docker files
*/
public test = async () => {
await this.prepare();
return await Dockerfile.readDockerfiles(this).then(Dockerfile.testDockerfiles);
};
/**
* can be used to get the Dockerfiles in the directory
*/
getDockerfiles = async () => {
const dockerfiles = await Dockerfile.readDockerfiles(this);
return dockerfiles;
}
}

View File

@@ -1,410 +0,0 @@
import * as plugins from './mod.plugins.ts';
import * as paths from '../szci.paths.ts';
import { logger } from '../szci.logging.ts';
import { bash } from '../szci.bash.ts';
import { DockerRegistry } from './mod.classes.dockerregistry.ts';
import * as helpers from './mod.helpers.ts';
import { SzciDockerManager } from './index.ts';
import { Szci } from '../szci.classes.szci.ts';
/**
* class Dockerfile represents a Dockerfile on disk in szci
*/
export class Dockerfile {
// STATIC
/**
* creates instance of class Dockerfile for all Dockerfiles in cwd
* @returns Promise<Dockerfile[]>
*/
public static async readDockerfiles(
szciDockerManagerRefArg: SzciDockerManager
): Promise<Dockerfile[]> {
const fileTree = await plugins.smartfile.fs.listFileTree(paths.getCwd(), 'Dockerfile*');
// create the Dockerfile array
const readDockerfilesArray: Dockerfile[] = [];
logger.log('info', `found ${fileTree.length} Dockerfiles:`);
console.log(fileTree);
for (const dockerfilePath of fileTree) {
const myDockerfile = new Dockerfile(szciDockerManagerRefArg, {
filePath: dockerfilePath,
read: true,
});
readDockerfilesArray.push(myDockerfile);
}
return readDockerfilesArray;
}
/**
* Sorts Dockerfiles into a build order based on dependencies.
* @param dockerfiles An array of Dockerfile instances.
* @returns A Promise that resolves to a sorted array of Dockerfiles.
*/
public static async sortDockerfiles(dockerfiles: Dockerfile[]): Promise<Dockerfile[]> {
logger.log('info', 'Sorting Dockerfiles based on dependencies...');
// Map from cleanTag to Dockerfile instance for quick lookup
const tagToDockerfile = new Map<string, Dockerfile>();
dockerfiles.forEach((dockerfile) => {
tagToDockerfile.set(dockerfile.cleanTag, dockerfile);
});
// Build the dependency graph
const graph = new Map<Dockerfile, Dockerfile[]>();
dockerfiles.forEach((dockerfile) => {
const dependencies: Dockerfile[] = [];
const baseImage = dockerfile.baseImage;
// Check if the baseImage is among the local Dockerfiles
if (tagToDockerfile.has(baseImage)) {
const baseDockerfile = tagToDockerfile.get(baseImage)!;
dependencies.push(baseDockerfile);
dockerfile.localBaseImageDependent = true;
dockerfile.localBaseDockerfile = baseDockerfile;
}
graph.set(dockerfile, dependencies);
});
// Perform topological sort
const sortedDockerfiles: Dockerfile[] = [];
const visited = new Set<Dockerfile>();
const tempMarked = new Set<Dockerfile>();
const visit = (dockerfile: Dockerfile) => {
if (tempMarked.has(dockerfile)) {
throw new Error(`Circular dependency detected involving ${dockerfile.cleanTag}`);
}
if (!visited.has(dockerfile)) {
tempMarked.add(dockerfile);
const dependencies = graph.get(dockerfile) || [];
dependencies.forEach((dep) => visit(dep));
tempMarked.delete(dockerfile);
visited.add(dockerfile);
sortedDockerfiles.push(dockerfile);
}
};
try {
dockerfiles.forEach((dockerfile) => {
if (!visited.has(dockerfile)) {
visit(dockerfile);
}
});
} catch (error) {
logger.log('error', (error as Error).message);
throw error;
}
// Log the sorted order
sortedDockerfiles.forEach((dockerfile, index) => {
logger.log(
'info',
`Build order ${index + 1}: ${dockerfile.cleanTag}
with base image ${dockerfile.baseImage}`
);
});
return sortedDockerfiles;
}
/**
* maps local Dockerfiles dependencies to the correspoding Dockerfile class instances
*/
public static async mapDockerfiles(sortedDockerfileArray: Dockerfile[]): Promise<Dockerfile[]> {
sortedDockerfileArray.forEach((dockerfileArg) => {
if (dockerfileArg.localBaseImageDependent) {
sortedDockerfileArray.forEach((dockfile2: Dockerfile) => {
if (dockfile2.cleanTag === dockerfileArg.baseImage) {
dockerfileArg.localBaseDockerfile = dockfile2;
}
});
}
});
return sortedDockerfileArray;
}
/**
* builds the correspoding real docker image for each Dockerfile class instance
*/
public static async buildDockerfiles(sortedArrayArg: Dockerfile[]) {
for (const dockerfileArg of sortedArrayArg) {
await dockerfileArg.build();
}
return sortedArrayArg;
}
/**
* tests all Dockerfiles in by calling class Dockerfile.test();
* @param sortedArrayArg Dockerfile[] that contains all Dockerfiles in cwd
*/
public static async testDockerfiles(sortedArrayArg: Dockerfile[]) {
for (const dockerfileArg of sortedArrayArg) {
await dockerfileArg.test();
}
return sortedArrayArg;
}
/**
* returns a version for a docker file
* @execution SYNC
*/
public static dockerFileVersion(
dockerfileInstanceArg: Dockerfile,
dockerfileNameArg: string
): string {
let versionString: string;
const versionRegex = /Dockerfile_(.+)$/;
const regexResultArray = versionRegex.exec(dockerfileNameArg);
if (regexResultArray && regexResultArray.length === 2) {
versionString = regexResultArray[1];
} else {
versionString = 'latest';
}
versionString = versionString.replace(
'##version##',
dockerfileInstanceArg.szciDockerManagerRef.szciRef.szciConfig.getConfig().projectInfo.npm
.version
);
return versionString;
}
/**
* Extracts the base image from a Dockerfile content without using external libraries.
* @param dockerfileContentArg The content of the Dockerfile as a string.
* @returns The base image specified in the first FROM instruction.
*/
public static dockerBaseImage(dockerfileContentArg: string): string {
const lines = dockerfileContentArg.split(/\r?\n/);
const args: { [key: string]: string } = {};
for (const line of lines) {
const trimmedLine = line.trim();
// Skip empty lines and comments
if (trimmedLine === '' || trimmedLine.startsWith('#')) {
continue;
}
// Match ARG instructions
const argMatch = trimmedLine.match(/^ARG\s+([^\s=]+)(?:=(.*))?$/i);
if (argMatch) {
const argName = argMatch[1];
const argValue = argMatch[2] !== undefined ? argMatch[2] : Deno.env.get(argName) || '';
args[argName] = argValue;
continue;
}
// Match FROM instructions
const fromMatch = trimmedLine.match(/^FROM\s+(.+?)(?:\s+AS\s+[^\s]+)?$/i);
if (fromMatch) {
let baseImage = fromMatch[1].trim();
// Substitute variables in the base image name
baseImage = Dockerfile.substituteVariables(baseImage, args);
return baseImage;
}
}
throw new Error('No FROM instruction found in Dockerfile');
}
/**
* Substitutes variables in a string, supporting default values like ${VAR:-default}.
* @param str The string containing variables.
* @param vars The object containing variable values.
* @returns The string with variables substituted.
*/
private static substituteVariables(str: string, vars: { [key: string]: string }): string {
return str.replace(/\${([^}:]+)(:-([^}]+))?}/g, (_, varName, __, defaultValue) => {
if (vars[varName] !== undefined) {
return vars[varName];
} else if (defaultValue !== undefined) {
return defaultValue;
} else {
return '';
}
});
}
/**
* returns the docker tag
*/
public static getDockerTagString(
szciDockerManagerRef: SzciDockerManager,
registryArg: string,
repoArg: string,
versionArg: string,
suffixArg?: string
): string {
// determine wether the repo should be mapped accordingly to the registry
const mappedRepo =
szciDockerManagerRef.szciRef.szciConfig.getConfig().dockerRegistryRepoMap[registryArg];
const repo = (() => {
if (mappedRepo) {
return mappedRepo;
} else {
return repoArg;
}
})();
// determine wether the version contais a suffix
let version = versionArg;
if (suffixArg) {
version = versionArg + '_' + suffixArg;
}
const tagString = `${registryArg}/${repo}:${version}`;
return tagString;
}
public static async getDockerBuildArgs(
szciDockerManagerRef: SzciDockerManager
): Promise<string> {
logger.log('info', 'checking for env vars to be supplied to the docker build');
let buildArgsString: string = '';
for (const dockerArgKey of Object.keys(
szciDockerManagerRef.szciRef.szciConfig.getConfig().dockerBuildargEnvMap
)) {
const dockerArgOuterEnvVar =
szciDockerManagerRef.szciRef.szciConfig.getConfig().dockerBuildargEnvMap[dockerArgKey];
logger.log(
'note',
`docker ARG "${dockerArgKey}" maps to outer env var "${dockerArgOuterEnvVar}"`
);
const targetValue = Deno.env.get(dockerArgOuterEnvVar);
buildArgsString = `${buildArgsString} --build-arg ${dockerArgKey}="${targetValue}"`;
}
return buildArgsString;
}
// INSTANCE
public szciDockerManagerRef: SzciDockerManager;
public filePath!: string;
public repo: string;
public version: string;
public cleanTag: string;
public buildTag: string;
public pushTag!: string;
public containerName: string;
public content!: string;
public baseImage: string;
public localBaseImageDependent: boolean;
public localBaseDockerfile!: Dockerfile;
constructor(
dockerManagerRefArg: SzciDockerManager,
options: { filePath?: string; fileContents?: string | Uint8Array; read?: boolean }
) {
this.szciDockerManagerRef = dockerManagerRefArg;
this.filePath = options.filePath!;
this.repo =
this.szciDockerManagerRef.szciRef.szciEnv.repo.user +
'/' +
this.szciDockerManagerRef.szciRef.szciEnv.repo.repo;
this.version = Dockerfile.dockerFileVersion(this, plugins.path.parse(this.filePath).base);
this.cleanTag = this.repo + ':' + this.version;
this.buildTag = this.cleanTag;
this.containerName = 'dockerfile-' + this.version;
if (options.filePath && options.read) {
this.content = plugins.smartfile.fs.toStringSync(plugins.path.resolve(options.filePath));
}
this.baseImage = Dockerfile.dockerBaseImage(this.content);
this.localBaseImageDependent = false;
}
/**
* builds the Dockerfile
*/
public async build() {
logger.log('info', 'now building Dockerfile for ' + this.cleanTag);
const buildArgsString = await Dockerfile.getDockerBuildArgs(this.szciDockerManagerRef);
const buildCommand = `docker build --label="version=${
this.szciDockerManagerRef.szciRef.szciConfig.getConfig().projectInfo.npm.version
}" -t ${this.buildTag} -f ${this.filePath} ${buildArgsString} .`;
await bash(buildCommand);
return;
}
/**
* pushes the Dockerfile to a registry
*/
public async push(dockerRegistryArg: DockerRegistry, versionSuffix?: string) {
this.pushTag = Dockerfile.getDockerTagString(
this.szciDockerManagerRef,
dockerRegistryArg.registryUrl,
this.repo,
this.version,
versionSuffix
);
await bash(`docker tag ${this.buildTag} ${this.pushTag}`);
await bash(`docker push ${this.pushTag}`);
const imageDigest = (
await bash(`docker inspect --format="{{index .RepoDigests 0}}" ${this.pushTag}`)
).split('@')[1];
console.log(`The image ${this.pushTag} has digest ${imageDigest}`);
await this.szciDockerManagerRef.szciRef.cloudlyConnector.announceDockerContainer({
registryUrl: this.pushTag,
tag: this.buildTag,
labels: [],
version: this.szciDockerManagerRef.szciRef.szciConfig.getConfig().projectInfo.npm.version,
});
await this.szciDockerManagerRef.szciRef.szciConfig.kvStorage.writeKey(
'latestPushedDockerTag',
this.pushTag
);
}
/**
* pulls the Dockerfile from a registry
*/
public async pull(registryArg: DockerRegistry, versionSuffixArg?: string) {
const pullTag = Dockerfile.getDockerTagString(
this.szciDockerManagerRef,
registryArg.registryUrl,
this.repo,
this.version,
versionSuffixArg
);
await bash(`docker pull ${pullTag}`);
await bash(`docker tag ${pullTag} ${this.buildTag}`);
}
/**
* tests the Dockerfile;
*/
public async test() {
const testFile: string = plugins.path.join(paths.SzciTestDir, 'test_' + this.version + '.sh');
const testFileExists: boolean = plugins.smartfile.fs.fileExistsSync(testFile);
if (testFileExists) {
// run tests
await bash(
`docker run --name szci_test_container --entrypoint="bash" ${this.buildTag} -c "mkdir /szci_test"`
);
await bash(`docker cp ${testFile} szci_test_container:/szci_test/test.sh`);
await bash(`docker commit szci_test_container szci_test_image`);
await bash(`docker run --entrypoint="bash" szci_test_image -x /szci_test/test.sh`);
await bash(`docker rm szci_test_container`);
await bash(`docker rmi --force szci_test_image`);
} else {
logger.log('warn', 'skipping tests for ' + this.cleanTag + ' because no testfile was found!');
}
}
/**
* gets the id of a Dockerfile
*/
public async getId() {
const containerId = await bash(
'docker inspect --type=image --format="{{.Id}}" ' + this.buildTag
);
return containerId;
}
}

View File

@@ -1,47 +0,0 @@
import { logger } from '../szci.logging.ts';
import * as plugins from './mod.plugins.ts';
import { bash } from '../szci.bash.ts';
export interface IDockerRegistryConstructorOptions {
registryUrl: string;
username: string;
password: string;
}
export class DockerRegistry {
public registryUrl: string;
public username: string;
public password: string;
constructor(optionsArg: IDockerRegistryConstructorOptions) {
this.registryUrl = optionsArg.registryUrl;
this.username = optionsArg.username;
this.password = optionsArg.password;
logger.log('info', `created DockerRegistry for ${this.registryUrl}`);
}
public static fromEnvString(envString: string): DockerRegistry {
const dockerRegexResultArray = envString.split('|');
if (dockerRegexResultArray.length !== 3) {
logger.log('error', 'malformed docker env var...');
Deno.exit(1);
}
const registryUrl = dockerRegexResultArray[0].replace('https://', '').replace('http://', '');
const username = dockerRegexResultArray[1];
const password = dockerRegexResultArray[2];
return new DockerRegistry({
registryUrl: registryUrl,
username: username,
password: password,
});
}
public async login() {
if (this.registryUrl === 'docker.io') {
await bash(`docker login -u ${this.username} -p ${this.password}`);
logger.log('info', 'Logged in to standard docker hub');
} else {
await bash(`docker login -u ${this.username} -p ${this.password} ${this.registryUrl}`);
}
logger.log('ok', `docker authenticated for ${this.registryUrl}!`);
}
}

View File

@@ -1,28 +0,0 @@
import { logger } from '../szci.logging.ts';
import * as plugins from './mod.plugins.ts';
import { DockerRegistry } from './mod.classes.dockerregistry.ts';
export class RegistryStorage {
objectMap = new plugins.lik.ObjectMap<DockerRegistry>();
constructor() {
// Nothing here
}
addRegistry(registryArg: DockerRegistry) {
this.objectMap.add(registryArg);
}
getRegistryByUrl(registryUrlArg: string) {
return this.objectMap.findSync((registryArg) => {
return registryArg.registryUrl === registryUrlArg;
});
}
async loginAll() {
await this.objectMap.forEach(async (registryArg) => {
await registryArg.login();
});
logger.log('success', 'logged in successfully into all available DockerRegistries!');
}
}

View File

@@ -1,5 +0,0 @@
import { logger } from '../szci.logging.ts';
import * as plugins from './mod.plugins.ts';
import * as paths from '../szci.paths.ts';
import { Dockerfile } from './mod.classes.dockerfile.ts';

View File

@@ -1 +0,0 @@
export * from '../szci.plugins.ts';

View File

@@ -44,11 +44,11 @@ export class SzciNodeJsManager {
logger.log('info', `now installing node version ${versionArg}`);
let version: string;
if (versionArg === 'stable') {
version = '18';
version = '22';
} else if (versionArg === 'lts') {
version = '16';
version = '20';
} else if (versionArg === 'legacy') {
version = '14';
version = '18';
} else {
version = versionArg;
}

View File

@@ -1,20 +0,0 @@
import * as plugins from './mod.plugins.ts';
import * as paths from '../szci.paths.ts';
import { logger } from '../szci.logging.ts';
/**
* Cleans szci config files from the project directory
*/
export const clean = async (): Promise<void> => {
try {
if (plugins.smartfile.fs.fileExistsSync(paths.SzciPackageConfig)) {
plugins.smartfile.fs.removeSync(paths.SzciPackageConfig);
logger.log('ok', 'Cleaned szci config files');
} else {
logger.log('info', 'No szci config files to clean');
}
} catch (error) {
logger.log('error', `Failed to clean config files: ${(error as Error).message}`);
throw error;
}
};

View File

@@ -1 +0,0 @@
export * from '../szci.plugins.ts';

View File

@@ -1,30 +0,0 @@
import { bash } from '../szci.bash.ts';
import { logger } from '../szci.logging.ts';
/**
* Executes a wrapped command passed via CLI arguments.
* Usage: szci command <your-command-here>
*
* This allows running arbitrary commands through szci's bash wrapper
* which handles nvm and other environment setup.
*/
export const command = async (): Promise<void> => {
// Skip 'deno', 'mod.ts', 'command' and get the rest
const commandArgs = Deno.args.slice(1); // Skip 'command'
if (commandArgs.length === 0) {
logger.log('error', 'No command specified. Usage: szci command <your-command>');
Deno.exit(1);
}
const wrappedCommand = commandArgs.join(' ');
logger.log('info', `Executing command: ${wrappedCommand}`);
try {
await bash(wrappedCommand);
logger.log('ok', 'Command executed successfully');
} catch (error) {
logger.log('error', `Command failed: ${(error as Error).message}`);
throw error;
}
};

View File

@@ -1 +0,0 @@
export * from '../szci.plugins.ts';

View File

@@ -1,29 +0,0 @@
import * as plugins from './plugins.ts';
import * as paths from '../szci.paths.ts';
import { logger } from '../szci.logging.ts';
import { Szci } from '../szci.classes.szci.ts';
export const handleCli = async (szciRefArg: Szci, argvArg: any) => {
logger.log('info', 'checking execution context');
const ciRunnerTags = Deno.env.get("CI_RUNNER_TAGS");
if (!ciRunnerTags) {
logger.log('error', 'CI_RUNNER_TAGS environment variable is not set');
Deno.exit(1);
}
const presentRunnerTags = ciRunnerTags.split(',').map((stringArg) =>
stringArg.trim()
);
let allDesiredGitlabRunnerTagsPresent = true;
for (const desiredRunnerTag of szciRefArg.szciConfig.getConfig().gitlabRunnerTags) {
if (!presentRunnerTags.includes(desiredRunnerTag)) {
allDesiredGitlabRunnerTagsPresent = false;
logger.log(
'error',
`Desired runnerRag ${desiredRunnerTag} is missing in current execution context.`
);
}
}
if (!allDesiredGitlabRunnerTagsPresent) {
Deno.exit(1);
}
};

View File

@@ -1 +0,0 @@
export * from '../szci.plugins.ts';

View File

@@ -1,101 +0,0 @@
import * as plugins from './mod.plugins.ts';
import { logger } from '../szci.logging.ts';
/**
* Interface for parsed trigger configuration
*/
interface ITriggerConfig {
domain: string;
projectId: string;
triggerToken: string;
refName: string;
triggerName: string;
}
/**
* Regex to parse trigger env var format:
* domain|projectId|triggerToken|refName|triggerName (optional)
*/
const TRIGGER_VALUE_REGEX =
/^([a-zA-Z0-9.]+)\|([a-zA-Z0-9.]+)\|([a-zA-Z0-9.]+)\|([a-zA-Z0-9.]+)\|?([a-zA-Z0-9.\-/]*)$/;
/**
* Execute all configured triggers from environment variables
*/
export const trigger = async (): Promise<void> => {
logger.log('info', 'now running triggers');
// Get all env vars and filter for triggers
const envVars = Deno.env.toObject();
const triggerEnvVars = Object.entries(envVars).filter(([key]) =>
key.startsWith('SZCI_TRIGGER_')
);
if (triggerEnvVars.length === 0) {
logger.log('info', 'no triggers configured');
return;
}
// Process each trigger
for (const [key, value] of triggerEnvVars) {
logger.log('info', `Processing trigger from ${key}`);
await executeTrigger(value);
}
logger.log('ok', `executed ${triggerEnvVars.length} trigger(s)`);
};
/**
* Parse a trigger env var string into a config object
*/
const parseTriggerConfig = (triggerEnvVar: string): ITriggerConfig | null => {
const match = TRIGGER_VALUE_REGEX.exec(triggerEnvVar);
if (!match) {
return null;
}
return {
domain: match[1],
projectId: match[2],
triggerToken: match[3],
refName: match[4],
triggerName: match[5] || 'Unnamed Trigger',
};
};
/**
* Execute a single trigger by calling the GitLab API
*/
const executeTrigger = async (triggerEnvVar: string): Promise<void> => {
const config = parseTriggerConfig(triggerEnvVar);
if (!config) {
logger.log('error', 'malformed trigger env var, expected format: domain|projectId|token|ref|name');
return;
}
logger.log('info', `Found Trigger: ${config.triggerName}`);
logger.log('info', `Triggering build for ref "${config.refName}" of "${config.triggerName}"`);
try {
await plugins.smartrequest.postFormData(
`https://${config.domain}/api/v3/projects/${config.projectId}/trigger/builds`,
{},
[
{
name: 'token',
payload: config.triggerToken,
type: 'string',
},
{
name: 'ref',
payload: config.refName,
type: 'string',
},
]
);
logger.log('ok', `Trigger "${config.triggerName}" executed successfully`);
} catch (error) {
logger.log('error', `Failed to execute trigger: ${(error as Error).message}`);
}
};

View File

@@ -1 +0,0 @@
export * from '../szci.plugins.ts';

View File

@@ -11,7 +11,6 @@ import { SzciConfig } from './szci.classes.szciconfig.ts';
import { CloudlyConnector } from './connector.cloudly/cloudlyconnector.ts';
// managers
import { SzciCloudronManager } from './manager.cloudron/index.ts';
import { SzciDockerManager } from './manager.docker/index.ts';
import { SzciGitManager } from './manager.git/index.ts';
import { SzciNodeJsManager } from './manager.nodejs/index.ts';
@@ -27,7 +26,6 @@ export class Szci {
public szciCli!: SzciCli;
// managers
public cloudronManager!: SzciCloudronManager;
public dockerManager!: SzciDockerManager;
public gitManager!: SzciGitManager;
public nodejsManager!: SzciNodeJsManager;
@@ -51,7 +49,6 @@ export class Szci {
await this.szciConfig.init();
// managers
this.cloudronManager = new SzciCloudronManager(this);
this.dockerManager = new SzciDockerManager(this);
this.gitManager = new SzciGitManager(this);
this.nodejsManager = new SzciNodeJsManager(this);

View File

@@ -12,34 +12,10 @@ export class SzciCli {
this.smartcli = new plugins.smartcli.Smartcli();
this.smartcli.addVersion(this.szciRef.szciInfo.version);
// clean
this.smartcli.addCommand('clean').subscribe(
async (argv) => {
const modClean = await import('./mod_clean/index.ts');
await modClean.clean();
},
(err) => {
console.log(err);
Deno.exit(1);
}
);
// cloudron
this.smartcli.addCommand('cloudron').subscribe(
async (argv) => {
await this.szciRef.cloudronManager.handleCli(argv);
},
(err) => {
console.log(err);
Deno.exit(1);
}
);
// command
this.smartcli.addCommand('command').subscribe(
async (argv) => {
const modCommand = await import('./mod_command/index.ts');
await modCommand.command();
// docker
this.smartcli.addCommand('docker').subscribe(
async (argvArg) => {
await this.szciRef.dockerManager.handleCli(argvArg);
},
(err) => {
console.log(err);
@@ -58,17 +34,6 @@ export class SzciCli {
}
);
// build
this.smartcli.addCommand('docker').subscribe(
async (argvArg) => {
await this.szciRef.dockerManager.handleCli(argvArg);
},
(err) => {
console.log(err);
Deno.exit(1);
}
);
// node
this.smartcli.addCommand('node').subscribe(
async (argvArg) => {
@@ -90,28 +55,11 @@ export class SzciCli {
}
);
this.smartcli.addCommand('precheck').subscribe(async (argvArg) => {
const modPrecheck = await import('./mod_precheck/index.ts');
await modPrecheck.handleCli(this.szciRef, argvArg);
});
// trigger
// ssh
this.smartcli.addCommand('ssh').subscribe(async (argvArg) => {
const modSsh = await import('./mod_ssh/index.ts');
await modSsh.handleCli(argvArg);
});
// trigger
this.smartcli.addCommand('trigger').subscribe(
async (argv) => {
const modTrigger = await import('./mod_trigger/index.ts');
await modTrigger.trigger();
},
(err) => {
console.log(err);
Deno.exit(1);
}
);
}
public startParse = () => {

View File

@@ -15,19 +15,8 @@ export interface ISzciOptions {
npmAccessLevel?: 'private' | 'public';
npmRegistryUrl: string;
// docker
dockerRegistries: string[];
dockerRegistryRepoMap: { [key: string]: string };
dockerBuildargEnvMap: { [key: string]: string };
// gitlab
gitlabRunnerTags: string[];
// urls
urlCloudly?: string;
// cloudron
cloudronAppName?: string;
}
/**
@@ -61,12 +50,8 @@ export class SzciConfig {
this.configObject = {
projectInfo: new plugins.projectinfo.ProjectInfo(paths.cwd),
npmGlobalTools: [],
dockerRegistries: [],
dockerRegistryRepoMap: {},
npmAccessLevel: 'private',
npmRegistryUrl: 'registry.npmjs.org',
gitlabRunnerTags: [],
dockerBuildargEnvMap: {},
urlCloudly: await this.szciQenv.getEnvVarOnDemand('SZCI_URL_CLOUDLY'),
};
this.configObject = this.szciNpmextra.dataFor<ISzciOptions>('@ship.zone/szci', this.configObject);

View File

@@ -14,12 +14,10 @@ import * as servezoneApi from '@serve.zone/api';
export { servezoneApi };
// @push.rocks
import * as lik from '@push.rocks/lik';
import * as npmextra from '@push.rocks/npmextra';
import * as projectinfo from '@push.rocks/projectinfo';
import * as qenv from '@push.rocks/qenv';
import * as smartanalytics from '@push.rocks/smartanalytics';
import * as smartdelay from '@push.rocks/smartdelay';
import * as smartfile from '@push.rocks/smartfile';
import * as smartcli from '@push.rocks/smartcli';
import * as smartgit from '@push.rocks/smartgit';
@@ -35,12 +33,10 @@ import * as smartssh from '@push.rocks/smartssh';
import * as smartstring from '@push.rocks/smartstring';
export {
lik,
npmextra,
projectinfo,
qenv,
smartanalytics,
smartdelay,
smartfile,
smartgit,
smartcli,
@@ -61,7 +57,4 @@ import * as tsclass from '@tsclass/tsclass';
export { tsclass };
// third party
import * as through2 from 'through2';
export { through2 };