diff --git a/changelog.md b/changelog.md index 8959c9c..fa248ee 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,18 @@ # Changelog +## 2026-02-06 - 7.0.0 - BREAKING CHANGE(szci) +delegate Docker operations to @git.zone/tsdocker, remove internal Docker managers and deprecated modules, simplify CLI and env var handling + +- Delegate all Docker actions to @git.zone/tsdocker via npx; SzciDockerManager now bridges SZCI_LOGIN_DOCKER* and CI_JOB_TOKEN to DOCKER_REGISTRY_N and invokes npx @git.zone/tsdocker for build/login/push/pull/test. +- Removed internal Docker implementation: Dockerfile, DockerRegistry, RegistryStorage, related helpers and plugin wrappers removed from ts/manager.docker. +- Removed Cloudron manager and other deprecated modules and their plugin shims: manager.cloudron, mod_clean, mod_command, mod_precheck, mod_trigger (and corresponding CLI commands: cloudron, clean, command, precheck, trigger). +- CLI and exports simplified: Dockerfile export removed from mod.ts; Szci CLI now delegates docker command to the simplified SzciDockerManager. +- Updated environment handling: bridges SZCI_LOGIN_DOCKER* → DOCKER_REGISTRY_N and auto-bridges GitLab CI CI_JOB_TOKEN to DOCKER_REGISTRY_0. +- Node.js default mappings updated: stable→22, lts→20, legacy→18. +- Dependencies and plugins cleaned-up: removed unused/obsolete deps (e.g. @push.rocks/lik, smartdelay, through2) from deno.json and szci.plugins.ts. +- Docs updated (readme.md, readme.hints.md) to reflect architecture, tsdocker delegation, env var bridging and migration notes from npmci. +- BREAKING: CI configs and any workflows relying on internal Docker classes or the removed CLI commands must be updated to use tsdocker and the new env var/command flows. + ## 2025-10-26 - 6.0.1 - fix(tests) Migrate tests to Deno native runner and update Deno config diff --git a/deno.json b/deno.json index e1dfdd8..69dd238 100644 --- a/deno.json +++ b/deno.json @@ -39,14 +39,11 @@ "@std/cli": "jsr:@std/cli@^1.0.0", "@std/assert": "jsr:@std/assert@^1.0.0", "@api.global/typedrequest": "npm:@api.global/typedrequest@^3.1.10", - "@push.rocks/lik": "npm:@push.rocks/lik@^6.1.0", "@push.rocks/npmextra": "npm:@push.rocks/npmextra@^5.1.2", "@push.rocks/projectinfo": "npm:@push.rocks/projectinfo@^5.0.2", "@push.rocks/qenv": "npm:@push.rocks/qenv@^6.0.2", "@push.rocks/smartanalytics": "npm:@push.rocks/smartanalytics@^2.0.15", "@push.rocks/smartcli": "npm:@push.rocks/smartcli@^4.0.11", - "@push.rocks/smartdelay": "npm:@push.rocks/smartdelay@^3.0.5", - "@push.rocks/smartenv": "npm:@push.rocks/smartenv@^5.0.5", "@push.rocks/smartfile": "npm:@push.rocks/smartfile@^11.0.21", "@push.rocks/smartgit": "npm:@push.rocks/smartgit@^3.1.1", "@push.rocks/smartlog": "npm:@push.rocks/smartlog@^3.0.7", @@ -61,7 +58,6 @@ "@push.rocks/smartstring": "npm:@push.rocks/smartstring@^4.0.8", "@push.rocks/smartexpect": "npm:@push.rocks/smartexpect@^1.0.15", "@serve.zone/api": "npm:@serve.zone/api@^4.3.11", - "@tsclass/tsclass": "npm:@tsclass/tsclass@^4.1.2", - "through2": "npm:through2@^4.0.2" + "@tsclass/tsclass": "npm:@tsclass/tsclass@^4.1.2" } } diff --git a/mod.ts b/mod.ts index 2e69fe2..82445b3 100644 --- a/mod.ts +++ b/mod.ts @@ -45,4 +45,3 @@ if (import.meta.main) { // Export for programmatic use export { Szci } from './ts/szci.classes.szci.ts'; -export { Dockerfile } from './ts/manager.docker/mod.classes.dockerfile.ts'; diff --git a/readme.hints.md b/readme.hints.md index 762c6d9..b32e77f 100644 --- a/readme.hints.md +++ b/readme.hints.md @@ -1,5 +1,21 @@ -- focus on cli usage in CI environments. -- show Gitlab CI, GitHub CI and Gitea CI examples. +- Focus on CLI usage in CI environments. +- Show GitLab CI, GitHub CI and Gitea CI examples. + +## Architecture + +szci is a thin orchestrator that delegates heavy lifting to specialized tools: +- **Docker**: Delegates all operations to `@git.zone/tsdocker` via `npx` +- **NPM**: Thin wrapper around `pnpm` with .npmrc generation from `SZCI_TOKEN_NPM*` env vars +- **Node.js**: NVM-based version management (stable=22, lts=20, legacy=18) +- **SSH**: Deploys SSH keys from `SZCI_SSHKEY_*` env vars +- **Git**: GitHub mirroring via `SZCI_GIT_GITHUBTOKEN` +- **Cloudly**: Integration with serve.zone infrastructure + +## Docker Env Var Bridging + +szci bridges its own env var format to tsdocker's format before delegation: +- `SZCI_LOGIN_DOCKER*` -> `DOCKER_REGISTRY_N` (pipe-delimited: `url|user|pass`) +- GitLab CI: `CI_JOB_TOKEN` -> `DOCKER_REGISTRY_0` for `registry.gitlab.com` ## Deno Migration Status @@ -16,8 +32,6 @@ All environment variables have been rebranded from NPMCI_* to SZCI_*: | `NPMCI_GIT_GITHUBTOKEN` | `SZCI_GIT_GITHUBTOKEN` | | `NPMCI_GIT_GITHUBGROUP` | `SZCI_GIT_GITHUBGROUP` | | `NPMCI_GIT_GITHUB` | `SZCI_GIT_GITHUB` | -| `NPMCI_TRIGGER_*` | `SZCI_TRIGGER_*` | -| `NPMCI_LOGIN_CLOUDRON` | `SZCI_LOGIN_CLOUDRON` | | `NPMCI_SSHKEY_*` | `SZCI_SSHKEY_*` | | `NPMCI_LOGIN_DOCKER*` | `SZCI_LOGIN_DOCKER*` | | `NPMCI_TOKEN_NPM*` | `SZCI_TOKEN_NPM*` | @@ -28,4 +42,14 @@ All environment variables have been rebranded from NPMCI_* to SZCI_*: - Uses Deno APIs (`Deno.env`, `Deno.cwd`, `Deno.exit`) - Logger runtime set to 'deno' -- Dynamic imports use `.ts` extensions \ No newline at end of file +- Dynamic imports use `.ts` extensions + +## Removed Modules (v7+) + +The following were removed as dead/obsolete code: +- `mod_trigger` - Deprecated GitLab API v3 +- `mod_precheck` - GitLab-specific runner tag validation +- `manager.cloudron` - Niche Cloudron deployment +- `mod_command` - Trivial bash wrapper +- `mod_clean` - Trivial config cleanup +- Docker manager internals (Dockerfile, DockerRegistry, RegistryStorage classes) - replaced by tsdocker delegation diff --git a/readme.md b/readme.md index 0a8f391..4506fd0 100644 --- a/readme.md +++ b/readme.md @@ -1,28 +1,44 @@ # @ship.zone/szci -**Serve Zone CI** - A powerful CI/CD tool for streamlining Node.js and Docker workflows within CI environments (GitLab CI, GitHub CI, Gitea CI). Now powered by Deno with standalone executables. +A lightweight CI/CD orchestrator built with Deno that unifies Node.js, Docker, NPM, SSH, and Git workflows into a single CLI. Compiles to standalone binaries — no runtime required. -## ✨ Features +## Issue Reporting and Security -- 🚀 **Standalone Executables** - No Node.js installation required -- 🐳 **Docker Integration** - Build, tag, and push Docker images -- 📦 **NPM Management** - Install, test, and publish npm packages -- 🔧 **Node.js Version Management** - Install and switch between Node versions -- 🔐 **SSH Key Management** - Deploy SSH keys from environment variables -- ☁️ **Multi-Registry Support** - Push to multiple Docker registries -- 🎯 **Cross-Platform** - Binaries for Linux, macOS, and Windows +For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly. + +## 🏗️ Architecture + +szci is a **thin orchestrator** — it doesn't reinvent the wheel. Instead, it wires up best-in-class tools and handles the CI-specific glue: + +| Domain | What szci does | Delegates to | +|--------|---------------|-------------| +| 🐳 Docker | Bridges `SZCI_LOGIN_DOCKER*` env vars, auto-detects GitLab CI tokens | [`@git.zone/tsdocker`](https://code.foss.global/git.zone/tsdocker) | +| 📦 NPM | Generates `.npmrc` from `SZCI_TOKEN_NPM*` env vars, handles multi-registry publish | `pnpm` + `npm publish` | +| 🟢 Node.js | Manages Node versions via NVM with named aliases | `nvm` | +| 🔑 SSH | Deploys SSH keys from env vars to `~/.ssh` | `@push.rocks/smartssh` | +| 🔀 Git | Mirrors repos to GitHub | `git remote` | + +``` +CLI Input (Deno.args) + ↓ +SmartCLI Router + ↓ +├─ szci docker * → env var bridging → npx @git.zone/tsdocker +├─ szci npm * → .npmrc generation → pnpm / npm publish +├─ szci node * → version aliasing → nvm install +├─ szci git * → token injection → git push --mirror +└─ szci ssh * → key parsing → write to ~/.ssh +``` ## 📥 Installation -### NPM (Recommended) +### Via NPM (downloads pre-compiled binary) ```sh npm install -g @ship.zone/szci ``` -The package will automatically download the appropriate pre-compiled binary for your platform. - -### From Source (Deno Required) +### From Source ```sh git clone https://code.foss.global/ship.zone/szci.git @@ -30,86 +46,186 @@ cd szci deno task compile ``` +The compiled binaries land in `dist/binaries/` for Linux (x64/arm64), macOS (x64/arm64), and Windows (x64). + ## 🚀 Quick Start ```sh -# Install Node.js +# Setup Node.js environment szci node install stable -# Install dependencies +# Install project dependencies szci npm install -# Build Docker images +# Build & push Docker images szci docker build +szci docker push registry.example.com # Run tests szci npm test - -# Push Docker images -szci docker push registry.example.com ``` -## 📖 Usage +## 📖 CLI Reference -### Node.js Management +### `szci docker` — Docker Operations + +All Docker commands delegate to `@git.zone/tsdocker` after bridging environment variables. ```sh -# Install specific Node.js version -szci node install lts -szci node install stable -szci node install 18 - -# Install from .nvmrc -szci node install legacy +szci docker build # Build all Dockerfiles in cwd +szci docker login # Login to all configured registries +szci docker prepare # Alias for login +szci docker push registry.example.com # Push images to a registry +szci docker pull registry.example.com # Pull images from a registry +szci docker test # Test Dockerfiles ``` -### NPM Commands +**Env var bridging:** Before delegating, szci converts its own env var format to tsdocker's expected format: + +``` +SZCI_LOGIN_DOCKER_1 → DOCKER_REGISTRY_1 +SZCI_LOGIN_DOCKER_2 → DOCKER_REGISTRY_2 +... +``` + +In GitLab CI, `CI_JOB_TOKEN` is automatically bridged as `DOCKER_REGISTRY_0` for `registry.gitlab.com`. + +### `szci npm` — NPM/pnpm Workflows ```sh -# Install dependencies -szci npm install - -# Run tests -szci npm test - -# Publish package -szci npm publish +szci npm install # Runs pnpm install +szci npm build # Runs pnpm run build +szci npm test # Runs pnpm test +szci npm prepare # Generates ~/.npmrc from SZCI_TOKEN_NPM* env vars +szci npm publish # Full workflow: prepare → install → build → clean → npm publish ``` -### Docker Workflows +The `publish` command supports multi-registry publishing. If `npmAccessLevel` is `public` and a Verdaccio registry is configured, it publishes to both npm and Verdaccio automatically. + +### `szci node` — Node.js Version Management ```sh -# Prepare Docker environment -szci docker prepare - -# Build all Dockerfiles -szci docker build - -# Push to registry -szci docker push registry.example.com - -# Pull from registry -szci docker pull registry.example.com - -# Test Dockerfiles -szci docker test +szci node install stable # Node.js 22 +szci node install lts # Node.js 20 +szci node install legacy # Node.js 18 +szci node install 21 # Any specific version ``` -### SSH Key Management +Uses NVM under the hood (auto-detected at `/usr/local/nvm/nvm.sh` or `~/.nvm/nvm.sh`). After installing, it: +1. Sets the installed version as `nvm alias default` +2. Upgrades npm to latest +3. Installs any global tools listed in `npmextra.json` → `npmGlobalTools` + +### `szci ssh` — SSH Key Deployment ```sh -# Deploy SSH keys from environment -szci ssh prepare +szci ssh prepare # Deploy SSH keys from env vars to ~/.ssh ``` -Set environment variables like `NPMCI_SSHKEY_1`, `NPMCI_SSHKEY_2`, etc. +Reads all `SZCI_SSHKEY_*` env vars and writes the keys to disk. -## 🔧 CI/CD Integration +### `szci git` — Git Mirroring + +```sh +szci git mirror # Mirror repository to GitHub +``` + +Pushes all branches and tags to a GitHub mirror. Requires `SZCI_GIT_GITHUBTOKEN`. Refuses to mirror packages marked as `private` in `package.json`. + +## ⚙️ Configuration + +### `npmextra.json` + +Place this in your project root to configure szci behavior: + +```json +{ + "@ship.zone/szci": { + "npmGlobalTools": ["typescript", "pnpm"], + "npmAccessLevel": "public", + "npmRegistryUrl": "registry.npmjs.org", + "urlCloudly": "https://cloudly.example.com" + } +} +``` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `npmGlobalTools` | `string[]` | `[]` | Global npm packages to install during `node install` | +| `npmAccessLevel` | `"public" \| "private"` | `"private"` | Access level for `npm publish` | +| `npmRegistryUrl` | `string` | `"registry.npmjs.org"` | Default npm registry | +| `urlCloudly` | `string?` | — | Cloudly endpoint URL (can also be set via `SZCI_URL_CLOUDLY`) | + +## 🔐 Environment Variables + +### Docker Registry Authentication + +Pipe-delimited format: `registry|username|password` + +```sh +SZCI_LOGIN_DOCKER_1="registry.example.com|myuser|mypass" +SZCI_LOGIN_DOCKER_2="ghcr.io|token|ghp_xxxx" +``` + +In **GitLab CI**, the `CI_JOB_TOKEN` is automatically used for `registry.gitlab.com` — no manual config needed. + +### NPM Registry Authentication + +Pipe-delimited format: `registry|token[|plain]` + +```sh +# Base64-encoded token (default) +SZCI_TOKEN_NPM_1="registry.npmjs.org|dGhlLXRva2VuLWhlcmU=" + +# Plain text token +SZCI_TOKEN_NPM_2="verdaccio.example.com|the-token-here|plain" +``` + +### SSH Keys + +Pipe-delimited format: `host|privKeyBase64|pubKeyBase64` + +```sh +SZCI_SSHKEY_1="github.com|BASE64_PRIVATE_KEY|BASE64_PUBLIC_KEY" +SZCI_SSHKEY_2="gitlab.com|BASE64_PRIVATE_KEY|##" # Use ## to skip a field +``` + +### Git Mirroring + +```sh +SZCI_GIT_GITHUBTOKEN="ghp_your_personal_access_token" +SZCI_GIT_GITHUBGROUP="your-org" # Defaults to repo owner +SZCI_GIT_GITHUB="your-repo" # Defaults to repo name +``` + +### Debugging & Testing + +```sh +DEBUG_SZCI="true" # Log all shell commands before execution +SZCI_TEST="true" # Test mode: mocks bash execution, skips SSH disk writes +``` + +### Full Environment Variable Reference + +| Variable | Purpose | +|----------|---------| +| `SZCI_LOGIN_DOCKER_*` | Docker registry credentials (pipe-delimited) | +| `SZCI_TOKEN_NPM_*` | NPM registry auth tokens (pipe-delimited) | +| `SZCI_SSHKEY_*` | SSH key pairs (pipe-delimited) | +| `SZCI_GIT_GITHUBTOKEN` | GitHub personal access token for mirroring | +| `SZCI_GIT_GITHUBGROUP` | GitHub org/user for mirror target | +| `SZCI_GIT_GITHUB` | GitHub repo name for mirror target | +| `SZCI_URL_CLOUDLY` | Cloudly endpoint URL | +| `SZCI_COMPUTED_REPOURL` | Override auto-detected repo URL | +| `DEBUG_SZCI` | Enable verbose shell command logging | +| `SZCI_TEST` | Enable test mode (mock execution) | + +## 🔄 CI/CD Integration Examples ### GitLab CI ```yaml -image: denoland/deno:alpine +image: node:22 stages: - prepare @@ -117,13 +233,8 @@ stages: - test - deploy -variables: - SZCI_VERSION: "latest" - before_script: - - deno install --allow-all --global --name szci https://code.foss.global/ship.zone/szci/raw/branch/master/mod.ts - # OR use the npm package: - # - npm install -g @ship.zone/szci + - npm install -g @ship.zone/szci prepare: stage: prepare @@ -149,10 +260,12 @@ deploy: - master ``` +> 💡 In GitLab CI, `CI_JOB_TOKEN` is automatically detected — szci will login to `registry.gitlab.com` without any extra config. + ### GitHub Actions ```yaml -name: CI Pipeline +name: CI/CD on: push: @@ -161,147 +274,104 @@ on: jobs: build: runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Install SZCI + - name: Install szci run: npm install -g @ship.zone/szci - name: Setup Node.js run: szci node install stable - - name: Install Dependencies - run: szci npm install + - name: Install & Build + run: | + szci npm install + szci npm build - - name: Build Docker Images - run: szci docker build - - - name: Run Tests + - name: Test run: szci npm test - - name: Push Images + - name: Push Docker if: github.ref == 'refs/heads/main' run: szci docker push ghcr.io + env: + SZCI_LOGIN_DOCKER_1: "ghcr.io|${{ github.actor }}|${{ secrets.GITHUB_TOKEN }}" ``` -## ⚙️ Configuration +### Gitea CI (Woodpecker) -Create an `npmextra.json` file in your project root: - -```json -{ - "npmci": { - "npmGlobalTools": [], - "npmAccessLevel": "public", - "npmRegistryUrl": "registry.npmjs.org", - "dockerRegistries": [ - "registry.gitlab.com" - ], - "dockerRegistryRepoMap": { - "registry.gitlab.com": "mygroup/myrepo" - }, - "dockerBuildargEnvMap": { - "ARG_NAME": "ENV_VAR_NAME" - } - } -} +```yaml +steps: + - name: ci + image: node:22 + commands: + - npm install -g @ship.zone/szci + - szci node install stable + - szci npm install + - szci docker build + - szci npm test ``` -## 🐳 Docker Registry Authentication +## 🔄 Migration from npmci -SZCI supports automatic authentication with: +Upgrading from `@ship.zone/npmci`? Three steps: -- GitLab CI Registry (via `CI_JOB_TOKEN`) -- Custom registries via environment variables +1. **Rename the binary** — replace `npmci` with `szci` in all CI scripts +2. **Rename env vars** — `NPMCI_*` → `SZCI_*` (same formats, just the prefix changed) +3. **Docker ops** — now delegate to `@git.zone/tsdocker` (installed automatically via `npx`) -Set `NPMCI_LOGIN_DOCKER*` environment variables: +| Old | New | +|-----|-----| +| `NPMCI_LOGIN_DOCKER*` | `SZCI_LOGIN_DOCKER*` | +| `NPMCI_TOKEN_NPM*` | `SZCI_TOKEN_NPM*` | +| `NPMCI_SSHKEY_*` | `SZCI_SSHKEY_*` | +| `NPMCI_GIT_GITHUBTOKEN` | `SZCI_GIT_GITHUBTOKEN` | +| `NPMCI_URL_CLOUDLY` | `SZCI_URL_CLOUDLY` | +| `DEBUG_NPMCI` | `DEBUG_SZCI` | +| `NPMTS_TEST` | `SZCI_TEST` | + +## 🛠️ Development ```sh -NPMCI_LOGIN_DOCKER_1="registry.example.com|username|password" -NPMCI_LOGIN_DOCKER_2="another-registry.com|user2|pass2" -``` - -## 🏗️ Development - -### Prerequisites - -- Deno 1.40+ installed - -### Building from Source - -```sh -# Clone the repository -git clone https://code.foss.global/ship.zone/szci.git -cd szci - -# Compile for all platforms -deno task compile - -# Compile for current platform only -deno compile --allow-all --output szci mod.ts +# Type check +deno task check # Run tests deno task test -# Development mode -deno task dev --help -``` - -### Testing - -SZCI uses Deno's native test framework: - -```sh -# Run all tests -deno task test - -# Run tests in watch mode +# Watch tests deno task test:watch -# Run specific test file -deno test --allow-all test/test.cloudly.ts +# Dev mode +deno task dev -- --help + +# Compile binaries for all platforms +deno task compile + +# Format code +deno task fmt + +# Lint +deno task lint ``` -## 📦 Binary Sizes +## License and Legal Information -The standalone executables are approximately: -- Linux x64: ~800MB -- Linux ARM64: ~800MB -- macOS x64: ~796MB -- macOS ARM64: ~796MB -- Windows x64: ~804MB +This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file. -Sizes include all dependencies and the Deno runtime. +**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file. -## 🔄 Migration from npmci +### Trademarks -If you're upgrading from the old `@ship.zone/npmci` package: +This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein. -1. Update package references: - ```sh - npm uninstall -g @ship.zone/npmci - npm install -g @ship.zone/szci - ``` +Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar. -2. Update CI configuration files - replace `npmci` with `szci` +### Company Information -3. The command interface remains the same, only the binary name changed +Task Venture Capital GmbH +Registered at District Court Bremen HRB 35230 HB, Germany -## 📝 License +For any legal inquiries or further information, please contact us via email at hello@task.vc. -MIT © Lossless GmbH - -## 🔗 Links - -- [Repository](https://code.foss.global/ship.zone/szci) -- [Issues](https://code.foss.global/ship.zone/szci/issues) -- [Changelog](./changelog.md) - -## 🤝 Contributing - -Contributions are welcome! Please feel free to submit a Pull Request. - ---- - -**Built with Deno 🦕** +By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works. diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index e1ce779..5ba1262 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -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.' } diff --git a/ts/index.ts b/ts/index.ts index 4e9bb8d..d953691 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -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(); diff --git a/ts/manager.cloudron/index.ts b/ts/manager.cloudron/index.ts deleted file mode 100644 index 4210c47..0000000 --- a/ts/manager.cloudron/index.ts +++ /dev/null @@ -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'); - } -} diff --git a/ts/manager.cloudron/mod.plugins.ts b/ts/manager.cloudron/mod.plugins.ts deleted file mode 100644 index 2a1e080..0000000 --- a/ts/manager.cloudron/mod.plugins.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '../szci.plugins.ts'; \ No newline at end of file diff --git a/ts/manager.docker/index.ts b/ts/manager.docker/index.ts index c4018b5..9ed83f6 100644 --- a/ts/manager.docker/index.ts +++ b/ts/manager.docker/index.ts @@ -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; - } } diff --git a/ts/manager.docker/mod.classes.dockerfile.ts b/ts/manager.docker/mod.classes.dockerfile.ts deleted file mode 100644 index 667a0ac..0000000 --- a/ts/manager.docker/mod.classes.dockerfile.ts +++ /dev/null @@ -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 - */ - public static async readDockerfiles( - szciDockerManagerRefArg: SzciDockerManager - ): Promise { - 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 { - logger.log('info', 'Sorting Dockerfiles based on dependencies...'); - - // Map from cleanTag to Dockerfile instance for quick lookup - const tagToDockerfile = new Map(); - dockerfiles.forEach((dockerfile) => { - tagToDockerfile.set(dockerfile.cleanTag, dockerfile); - }); - - // Build the dependency graph - const graph = new Map(); - 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(); - const tempMarked = new Set(); - - 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 { - 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 { - 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; - } -} diff --git a/ts/manager.docker/mod.classes.dockerregistry.ts b/ts/manager.docker/mod.classes.dockerregistry.ts deleted file mode 100644 index 2718baa..0000000 --- a/ts/manager.docker/mod.classes.dockerregistry.ts +++ /dev/null @@ -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}!`); - } -} diff --git a/ts/manager.docker/mod.classes.registrystorage.ts b/ts/manager.docker/mod.classes.registrystorage.ts deleted file mode 100644 index 5eb7bdb..0000000 --- a/ts/manager.docker/mod.classes.registrystorage.ts +++ /dev/null @@ -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(); - 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!'); - } -} diff --git a/ts/manager.docker/mod.helpers.ts b/ts/manager.docker/mod.helpers.ts deleted file mode 100644 index cd46bcb..0000000 --- a/ts/manager.docker/mod.helpers.ts +++ /dev/null @@ -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'; diff --git a/ts/manager.docker/mod.plugins.ts b/ts/manager.docker/mod.plugins.ts deleted file mode 100644 index f3e2615..0000000 --- a/ts/manager.docker/mod.plugins.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '../szci.plugins.ts'; diff --git a/ts/manager.nodejs/index.ts b/ts/manager.nodejs/index.ts index 708c174..217cd16 100644 --- a/ts/manager.nodejs/index.ts +++ b/ts/manager.nodejs/index.ts @@ -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; } diff --git a/ts/mod_clean/index.ts b/ts/mod_clean/index.ts deleted file mode 100644 index f8aee83..0000000 --- a/ts/mod_clean/index.ts +++ /dev/null @@ -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 => { - 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; - } -}; diff --git a/ts/mod_clean/mod.plugins.ts b/ts/mod_clean/mod.plugins.ts deleted file mode 100644 index f3e2615..0000000 --- a/ts/mod_clean/mod.plugins.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '../szci.plugins.ts'; diff --git a/ts/mod_command/index.ts b/ts/mod_command/index.ts deleted file mode 100644 index ad90faf..0000000 --- a/ts/mod_command/index.ts +++ /dev/null @@ -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 - * - * This allows running arbitrary commands through szci's bash wrapper - * which handles nvm and other environment setup. - */ -export const command = async (): Promise => { - // 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 '); - 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; - } -}; diff --git a/ts/mod_command/mod.plugins.ts b/ts/mod_command/mod.plugins.ts deleted file mode 100644 index f3e2615..0000000 --- a/ts/mod_command/mod.plugins.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '../szci.plugins.ts'; diff --git a/ts/mod_precheck/index.ts b/ts/mod_precheck/index.ts deleted file mode 100644 index 9e9cd67..0000000 --- a/ts/mod_precheck/index.ts +++ /dev/null @@ -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); - } -}; diff --git a/ts/mod_precheck/plugins.ts b/ts/mod_precheck/plugins.ts deleted file mode 100644 index f3e2615..0000000 --- a/ts/mod_precheck/plugins.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '../szci.plugins.ts'; diff --git a/ts/mod_trigger/index.ts b/ts/mod_trigger/index.ts deleted file mode 100644 index 8280e8d..0000000 --- a/ts/mod_trigger/index.ts +++ /dev/null @@ -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 => { - 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 => { - 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}`); - } -}; diff --git a/ts/mod_trigger/mod.plugins.ts b/ts/mod_trigger/mod.plugins.ts deleted file mode 100644 index f3e2615..0000000 --- a/ts/mod_trigger/mod.plugins.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '../szci.plugins.ts'; diff --git a/ts/szci.classes.szci.ts b/ts/szci.classes.szci.ts index 6feb4e0..3d1ecc6 100644 --- a/ts/szci.classes.szci.ts +++ b/ts/szci.classes.szci.ts @@ -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); diff --git a/ts/szci.classes.szcicli.ts b/ts/szci.classes.szcicli.ts index a24e54c..fc6875d 100644 --- a/ts/szci.classes.szcicli.ts +++ b/ts/szci.classes.szcicli.ts @@ -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 = () => { diff --git a/ts/szci.classes.szciconfig.ts b/ts/szci.classes.szciconfig.ts index 3efa8ae..637fa80 100644 --- a/ts/szci.classes.szciconfig.ts +++ b/ts/szci.classes.szciconfig.ts @@ -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('@ship.zone/szci', this.configObject); diff --git a/ts/szci.plugins.ts b/ts/szci.plugins.ts index d8110d0..28f82a5 100644 --- a/ts/szci.plugins.ts +++ b/ts/szci.plugins.ts @@ -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 };