From 0f445b4c86f66ec21011a26a9188c94429f2a4fe Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Thu, 12 Mar 2026 10:50:34 +0000 Subject: [PATCH] BREAKING CHANGE(cli): remove legacy container test runner and make the default command show the man page --- assets/Dockerfile | 6 - changelog.md | 7 ++ package.json | 14 +-- pnpm-lock.yaml | 46 -------- readme.hints.md | 11 +- readme.md | 241 ++++++++++++++++++++++++++------------- ts/00_commitinfo_data.ts | 2 +- ts/interfaces/index.ts | 8 -- ts/tsdocker.cli.ts | 104 ++++++++++------- ts/tsdocker.config.ts | 33 +----- ts/tsdocker.docker.ts | 169 --------------------------- ts/tsdocker.paths.ts | 1 - ts/tsdocker.plugins.ts | 8 -- ts/tsdocker.snippets.ts | 34 ------ 14 files changed, 239 insertions(+), 445 deletions(-) delete mode 100644 assets/Dockerfile delete mode 100644 ts/tsdocker.docker.ts delete mode 100644 ts/tsdocker.snippets.ts diff --git a/assets/Dockerfile b/assets/Dockerfile deleted file mode 100644 index ac89984..0000000 --- a/assets/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM hosttoday/ht-docker-node:npmci -RUN yarn global add @git.zone/tsdocker -COPY ./ /workspace -WORKDIR /workspace -ENV CI=true -CMD ["tsdocker","runinside"]; diff --git a/changelog.md b/changelog.md index 8e5cd03..73cf5e3 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2026-03-12 - 2.0.0 - BREAKING CHANGE(cli) +remove legacy container test runner and make the default command show the man page + +- Removes legacy testing and VS Code commands, including `runinside`, `vscode`, generated Dockerfile assets, and related configuration fields (`baseImage`, `command`, `dockerSock`, `keyValueObject`) +- Simplifies configuration and dependencies by dropping qenv-based env loading and unused legacy packages +- Updates CLI and documentation to reflect default help output and the current build/push-focused workflow + ## 2026-02-07 - 1.17.4 - fix() no changes diff --git a/package.json b/package.json index 35326e8..97ec2f2 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,6 @@ "scripts": { "test": "(npm run build)", "build": "(tsbuild)", - "testIntegration": "(npm run clean && npm run setupCheck && npm run testStandard)", - "testStandard": "(cd test/ && tsx ../ts/index.ts)", - "testClean": "(cd test/ && tsx ../ts/index.ts clean --all)", - "testVscode": "(cd test/ && tsx ../ts/index.ts vscode)", - "clean": "(rm -rf test/)", - "compile": "(npmts --notest)", - "setupCheck": "(git clone https://gitlab.com/sandboxzone/sandbox-npmts.git test/)", "buildDocs": "tsdoc" }, "repository": { @@ -43,18 +36,13 @@ "@push.rocks/lik": "^6.2.2", "@push.rocks/npmextra": "^5.3.3", "@push.rocks/projectinfo": "^5.0.2", - "@push.rocks/qenv": "^6.1.3", - "@push.rocks/smartanalytics": "^2.0.15", "@push.rocks/smartcli": "^4.0.20", "@push.rocks/smartfs": "^1.3.1", "@push.rocks/smartinteract": "^2.0.16", "@push.rocks/smartlog": "^3.1.10", "@push.rocks/smartlog-destination-local": "^9.0.2", "@push.rocks/smartlog-source-ora": "^1.0.9", - "@push.rocks/smartopen": "^2.0.0", - "@push.rocks/smartpromise": "^4.2.3", - "@push.rocks/smartshell": "^3.3.0", - "@push.rocks/smartstring": "^4.1.0" + "@push.rocks/smartshell": "^3.3.0" }, "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34", "type": "module", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9008d7c..c25bfd9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,12 +17,6 @@ importers: '@push.rocks/projectinfo': specifier: ^5.0.2 version: 5.0.2 - '@push.rocks/qenv': - specifier: ^6.1.3 - version: 6.1.3 - '@push.rocks/smartanalytics': - specifier: ^2.0.15 - version: 2.0.15 '@push.rocks/smartcli': specifier: ^4.0.20 version: 4.0.20 @@ -41,18 +35,9 @@ importers: '@push.rocks/smartlog-source-ora': specifier: ^1.0.9 version: 1.0.9 - '@push.rocks/smartopen': - specifier: ^2.0.0 - version: 2.0.0 - '@push.rocks/smartpromise': - specifier: ^4.2.3 - version: 4.2.3 '@push.rocks/smartshell': specifier: ^3.3.0 version: 3.3.0 - '@push.rocks/smartstring': - specifier: ^4.1.0 - version: 4.1.0 devDependencies: '@git.zone/tsbuild': specifier: ^4.1.2 @@ -819,9 +804,6 @@ packages: '@push.rocks/qenv@6.1.3': resolution: {integrity: sha512-+z2hsAU/7CIgpYLFqvda8cn9rUBMHqLdQLjsFfRn5jPoD7dJ5rFlpkbhfM4Ws8mHMniwWaxGKo+q/YBhtzRBLg==} - '@push.rocks/smartanalytics@2.0.15': - resolution: {integrity: sha512-IzN1ayF1/d0jqZSqw6B8UYPfnFTFOF1fZRKYi7rTNKPjqKoBNsN1xTGkUA9JDZ7r0irG+guLmf8C3z5MZln9zQ==} - '@push.rocks/smartarchive@4.2.2': resolution: {integrity: sha512-6EpqbKU32D6Gcqsc9+Tn1dOCU5HoTlrqqs/7IdUr9Tirp9Ngtptkapca1Fw/D0kVJ7SSw3kG/miAYnuPMZLEoA==} @@ -1078,10 +1060,6 @@ packages: resolution: {integrity: sha512-tXqwfrekGxGZJB72BFQppywk7413hXVDgcJNeU+kY6xvmzVjf2HxOMbFYhewh1+p4uai1u9n0xcMb0qbbPy4/Q==} deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartlog-interfaces - '@pushrocks/smartpromise@2.0.5': - resolution: {integrity: sha512-9j/chLtIiNkR0MDw7Mpxg9slxAVvAQwUZuiaPYX5KpHdKxQaHLI1VZ8IN0vPhwlfgNO4i4vGXV0wB8BvSDj03g==} - deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartpromise - '@pushrocks/smartpromise@3.1.10': resolution: {integrity: sha512-VeTurbZ1+ZMxBDJk1Y1LV8SN9xLI+oDXKVeCFw41FAGEKOUEqordqFpi6t+7Vhe/TXUZzCVpZ5bXxAxrGf8yTQ==} deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartpromise @@ -1090,18 +1068,10 @@ packages: resolution: {integrity: sha512-bqorOaGXPOuiOSV81luTKrTghg4O4NBRD0zyv7TIqmrMGf4a0uoozaUMp1X8vQdZW+y0gTzUJP9wkzAE6Cci0g==} deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartpromise - '@pushrocks/smartrequest@1.1.56': - resolution: {integrity: sha512-iF6bApmTgd3ZvRK8OHa77UFg8nVZxS1Y6iL8VfHpWOXdSlQZcXo/WbvwxYtu0+wkERAfFtCTGrrLAPGsFm9lhw==} - deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartrequest - '@pushrocks/smartstring@4.0.7': resolution: {integrity: sha512-TxHSar7Cj29E+GOcIj4DeZKWCNVzHKdqnrBRqcBqLqmeYZvzFosLXpFKoaCJDq7MSxuPoCvu5woSdp9YmPXyog==} deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartstring - '@pushrocks/smarturl@2.0.1': - resolution: {integrity: sha512-6KGnf2vHR7hW4mQpAD7gkDVL3QVML3jb/No/Uw+qCqvs0TaQr60Yjm+CXoLxJNCKwmrL+I1yx8mhAHBHfYJiJA==} - deprecated: This package has been deprecated in favour of the new package at @push.rocks/smarturl - '@rolldown/binding-android-arm64@1.0.0-beta.51': resolution: {integrity: sha512-Ctn8FUXKWWQI9pWC61P1yumS9WjQtelNS9riHwV7oCkknPGaAry4o7eFx2KgoLMnI2BgFJYpW7Im8/zX3BuONg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5107,11 +5077,6 @@ snapshots: '@push.rocks/smartlog': 3.1.10 '@push.rocks/smartpath': 6.0.0 - '@push.rocks/smartanalytics@2.0.15': - dependencies: - '@pushrocks/smartpromise': 2.0.5 - '@pushrocks/smartrequest': 1.1.56 - '@push.rocks/smartarchive@4.2.2': dependencies: '@push.rocks/smartdelay': 3.0.5 @@ -5824,19 +5789,10 @@ snapshots: dependencies: '@apiglobal/typedrequest-interfaces': 1.0.20 - '@pushrocks/smartpromise@2.0.5': {} - '@pushrocks/smartpromise@3.1.10': {} '@pushrocks/smartpromise@4.0.2': {} - '@pushrocks/smartrequest@1.1.56': - dependencies: - '@pushrocks/smartpromise': 3.1.10 - '@pushrocks/smarturl': 2.0.1 - agentkeepalive: 4.6.0 - form-data: 4.0.5 - '@pushrocks/smartstring@4.0.7': dependencies: '@pushrocks/isounique': 1.0.5 @@ -5850,8 +5806,6 @@ snapshots: strip-indent: 4.1.1 url: 0.11.4 - '@pushrocks/smarturl@2.0.1': {} - '@rolldown/binding-android-arm64@1.0.0-beta.51': optional: true diff --git a/readme.hints.md b/readme.hints.md index 830d67f..fa67d07 100644 --- a/readme.hints.md +++ b/readme.hints.md @@ -3,7 +3,6 @@ ## Module Purpose tsdocker is a comprehensive Docker development and building tool. It provides: -- Testing npm modules in clean Docker environments (legacy feature) - Building Dockerfiles with dependency ordering - Multi-registry push/pull support - Multi-architecture builds (amd64/arm64) @@ -12,7 +11,7 @@ tsdocker is a comprehensive Docker development and building tool. It provides: | Command | Description | |---------|-------------| -| `tsdocker` | Run tests in container (legacy default behavior) | +| `tsdocker` | Show usage / man page | | `tsdocker build` | Build all Dockerfiles with dependency ordering | | `tsdocker push [registry]` | Push images to configured registries | | `tsdocker pull ` | Pull images from registry | @@ -20,7 +19,6 @@ tsdocker is a comprehensive Docker development and building tool. It provides: | `tsdocker login` | Login to configured registries | | `tsdocker list` | List discovered Dockerfiles and dependencies | | `tsdocker clean --all` | Clean up Docker environment | -| `tsdocker vscode` | Start VS Code in Docker | ## Configuration @@ -45,9 +43,6 @@ Configure in `package.json` under `@git.zone/tsdocker`: ### Configuration Options -- `baseImage`: Base Docker image for testing (legacy) -- `command`: Command to run in container (legacy) -- `dockerSock`: Mount Docker socket (legacy) - `registries`: Array of registry URLs to push to - `registryRepoMap`: Map registry URLs to different repo paths - `buildArgEnvMap`: Map Docker build ARGs to environment variables @@ -78,8 +73,6 @@ ts/ ├── tsdocker.cli.ts (CLI commands) ├── tsdocker.config.ts (configuration) ├── tsdocker.plugins.ts (plugin imports) -├── tsdocker.docker.ts (legacy test runner) -├── tsdocker.snippets.ts (Dockerfile generation) ├── classes.dockerfile.ts (Dockerfile management) ├── classes.dockerregistry.ts (registry authentication) ├── classes.registrystorage.ts (registry storage) @@ -123,8 +116,6 @@ The `config.push` field is now a no-op (kept for backward compat). ## Build Status - Build: ✅ Passes -- Legacy test functionality preserved -- New Docker build functionality added ## Previous Upgrades (2025-11-22) diff --git a/readme.md b/readme.md index 0248345..1fca06e 100644 --- a/readme.md +++ b/readme.md @@ -12,13 +12,15 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community ### 🎯 Key Capabilities -- 🧪 **Containerized Testing** — Run your tests in pristine Docker environments - 🏗️ **Smart Docker Builds** — Automatically discover, sort, and build Dockerfiles by dependency - 🌍 **True Multi-Architecture** — Build for `amd64` and `arm64` simultaneously with Docker Buildx - 🚀 **Multi-Registry Push** — Ship to Docker Hub, GitLab, GitHub Container Registry, and more via OCI Distribution API - ⚡ **Parallel Builds** — Level-based parallel builds with configurable concurrency - 🗄️ **Persistent Local Registry** — All images flow through a local OCI registry with persistent storage - 📦 **Build Caching** — Skip unchanged Dockerfiles with content-hash caching +- 🎯 **Dockerfile Filtering** — Build or push only specific Dockerfiles using glob patterns +- 🔁 **Resilient Push** — Automatic retry with exponential backoff, timeouts, and token refresh for rock-solid pushes +- 🏭 **CI-Safe Isolation** — Unique sessions per invocation prevent collisions in parallel CI pipelines - 🔧 **Zero Config Start** — Works out of the box, scales with your needs ## Installation @@ -33,16 +35,6 @@ pnpm install --save-dev @git.zone/tsdocker ## Quick Start -### 🧪 Run Tests in Docker - -The simplest use case — run your tests in a clean container: - -```bash -tsdocker -``` - -This pulls your configured base image, mounts your project, and executes your test command in isolation. - ### 🏗️ Build Docker Images Got `Dockerfile` files? Build them all with automatic dependency ordering: @@ -68,15 +60,33 @@ tsdocker push # Push to a specific registry tsdocker push --registry=registry.gitlab.com + +# Push without rebuilding (use existing images in local registry) +tsdocker push --no-build ``` -Under the hood, `tsdocker push` uses the **OCI Distribution API** to copy images directly from the local registry to remote registries. This means multi-arch manifest lists are preserved end-to-end — no more single-platform-only pushes. +Under the hood, `tsdocker push` uses the **OCI Distribution API** to copy images directly from the local registry to remote registries. This means multi-arch manifest lists are preserved end-to-end — no more single-platform-only pushes. Every request is protected with **automatic retry** (up to 6 attempts with exponential backoff) and **5-minute timeouts**, so transient network issues don't kill your push mid-transfer. + +### 🎯 Build Only Specific Dockerfiles + +Target specific Dockerfiles by name pattern — dependencies are resolved automatically: + +```bash +# Build only the base image +tsdocker build Dockerfile_base + +# Build anything matching a glob pattern +tsdocker build Dockerfile_app* + +# Push specific images only (skip build phase) +tsdocker push --no-build Dockerfile_api Dockerfile_web +``` ## CLI Commands | Command | Description | |---------|-------------| -| `tsdocker` | Run tests in a fresh Docker container (legacy mode) | +| `tsdocker` | Show usage / man page | | `tsdocker build` | Build all Dockerfiles with dependency ordering | | `tsdocker push` | Build + push images to configured registries | | `tsdocker pull ` | Pull images from a specific registry | @@ -84,12 +94,12 @@ Under the hood, `tsdocker push` uses the **OCI Distribution API** to copy images | `tsdocker login` | Authenticate with configured registries | | `tsdocker list` | Display discovered Dockerfiles and their dependencies | | `tsdocker clean` | Interactively clean Docker environment | -| `tsdocker vscode` | Launch containerized VS Code in browser | ### Build Flags | Flag | Description | |------|-------------| +| `` | Positional Dockerfile name patterns (e.g. `Dockerfile_base`, `Dockerfile_app*`) | | `--platform=linux/arm64` | Override build platform for a single architecture | | `--timeout=600` | Build timeout in seconds | | `--no-cache` | Force rebuild without Docker layer cache | @@ -99,6 +109,14 @@ Under the hood, `tsdocker push` uses the **OCI Distribution API** to copy images | `--parallel=8` | Parallel builds with custom concurrency | | `--context=mycontext` | Use a specific Docker context | +### Push Flags + +| Flag | Description | +|------|-------------| +| `` | Positional Dockerfile name patterns to select which images to push | +| `--registry=` | Push to a single specific registry instead of all configured | +| `--no-build` | Skip the build phase; only push existing images from local registry | + ### Clean Flags | Flag | Description | @@ -138,16 +156,6 @@ Configure tsdocker in your `package.json` or `npmextra.json` under the `@git.zon | `platforms` | `string[]` | `["linux/amd64"]` | Target architectures for multi-arch builds | | `testDir` | `string` | `./test` | Directory containing test scripts | -#### Legacy Testing Options - -These options configure the `tsdocker` default command (containerized test runner): - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `baseImage` | `string` | `hosttoday/ht-docker-node:npmdocker` | Docker image for test environment | -| `command` | `string` | `npmci npm test` | Command to run inside the container | -| `dockerSock` | `boolean` | `false` | Mount Docker socket for DinD scenarios | - ## Architecture: How tsdocker Works tsdocker uses a **local OCI registry** as the canonical store for all built images. This design solves fundamental problems with Docker's local daemon, which cannot hold multi-architecture manifest lists. @@ -155,36 +163,37 @@ tsdocker uses a **local OCI registry** as the canonical store for all built imag ### 📐 Build Flow ``` -┌─────────────────────────────────────────────────┐ -│ tsdocker build │ -│ │ -│ 1. Start local registry (localhost:5234) │ -│ └── Persistent volume: .nogit/docker-registry/ -│ │ -│ 2. For each Dockerfile (topological order): │ -│ ├── Multi-platform: buildx --push → registry │ -│ └── Single-platform: docker build → registry │ -│ │ -│ 3. Stop local registry (data persists on disk) │ -└─────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────┐ +│ tsdocker build │ +│ │ +│ 1. Start local registry (localhost:) │ +│ └── Persistent volume: .nogit/docker-registry/ │ +│ │ +│ 2. For each Dockerfile (topological order): │ +│ ├── Multi-platform: buildx --push → registry │ +│ └── Single-platform: docker build → registry │ +│ │ +│ 3. Stop local registry (data persists on disk) │ +└─────────────────────────────────────────────────────┘ ``` ### 📤 Push Flow ``` -┌──────────────────────────────────────────────────┐ -│ tsdocker push │ -│ │ -│ 1. Start local registry (loads persisted data) │ -│ │ -│ 2. For each image × each remote registry: │ -│ └── OCI Distribution API copy: │ -│ ├── Fetch manifest (single or multi-arch) │ -│ ├── Copy blobs (skip if already exist) │ -│ └── Push manifest with destination tag │ -│ │ -│ 3. Stop local registry │ -└──────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────┐ +│ tsdocker push │ +│ │ +│ 1. Start local registry (loads persisted data) │ +│ │ +│ 2. For each image × each remote registry: │ +│ └── OCI Distribution API copy (with retry): │ +│ ├── Fetch manifest (single or multi-arch) │ +│ ├── Copy blobs (skip if already exist) │ +│ ├── Retry up to 6× with exponential backoff │ +│ └── Push manifest with destination tag │ +│ │ +│ 3. Stop local registry │ +└────────────────────────────────────────────────────────┘ ``` ### 🔑 Why a Local Registry? @@ -196,6 +205,49 @@ tsdocker uses a **local OCI registry** as the canonical store for all built imag | Images lost between build and push phases | Persistent storage at `.nogit/docker-registry/` survives restarts | | Redundant blob uploads on incremental pushes | HEAD checks skip blobs that already exist on the remote | +### 🔁 Resilient Push + +The OCI Distribution API client wraps every HTTP request with: + +- **Timeouts** — 5-minute timeout for blob operations, 30-second timeout for auth/metadata calls via `AbortSignal.timeout()` +- **Automatic Retry** — Up to 6 attempts with exponential backoff (1s → 2s → 4s → 8s → 16s → 32s) +- **Smart Retry Logic** — Retries on network errors (`ECONNRESET`, `fetch failed`) and 5xx server errors; does NOT retry 4xx client errors +- **Token Refresh** — On 401 responses, the cached auth token is cleared so the next retry re-authenticates automatically + +This means transient issues like stale connection pools, brief network blips, or token expiry during long multi-arch pushes (56+ blob operations) are handled gracefully instead of killing the entire transfer. + +### 🏭 CI-Safe Session Isolation + +Every tsdocker invocation gets its own **session** with unique: + +- **Session ID** — Random 8-char hex (override with `TSDOCKER_SESSION_ID`) +- **Registry port** — Dynamically allocated (override with `TSDOCKER_REGISTRY_PORT`) +- **Registry container** — Named `tsdocker-registry-` +- **Builder suffix** — In CI, the buildx builder gets a `-` suffix to prevent collisions + +This prevents resource conflicts when multiple CI jobs run tsdocker in parallel. Auto-detected CI systems: + +| Environment Variable | CI System | +|---------------------|-----------| +| `GITEA_ACTIONS` | Gitea Actions | +| `GITHUB_ACTIONS` | GitHub Actions | +| `GITLAB_CI` | GitLab CI | +| `CI` | Generic CI | + +In local dev, no suffix is added — keeping a persistent builder for faster rebuilds. + +### 🔍 Docker Context & Topology Detection + +tsdocker automatically detects your Docker environment topology: + +| Topology | Detection | Meaning | +|----------|-----------|---------| +| `local` | Default | Standard Docker installation on the host | +| `socket-mount` | `/.dockerenv` exists | Running inside a container with Docker socket mounted | +| `dind` | `DOCKER_HOST` starts with `tcp://` | Docker-in-Docker setup | + +Context-aware builder names (`tsdocker-builder-`) prevent conflicts across Docker contexts. Rootless Docker configurations trigger appropriate warnings. + ## Registry Authentication ### Environment Variables @@ -213,7 +265,7 @@ export DOCKER_REGISTRY_PASSWORD="password" ### Docker Config Fallback -When pushing, tsdocker will also read credentials from `~/.docker/config.json` if no explicit credentials are provided via environment variables. This means `docker login` credentials work automatically. +When pushing, tsdocker will also read credentials from `~/.docker/config.json` if no explicit credentials are provided via environment variables. This means `docker login` credentials work automatically. Docker Hub special cases (`docker.io`, `index.docker.io`, `registry-1.docker.io`) are all recognized. ### Login Command @@ -270,6 +322,26 @@ tsdocker discovers files matching `Dockerfile*`: | `Dockerfile_alpine` | `alpine` | | `Dockerfile_##version##` | Uses `package.json` version | +### 🎯 Dockerfile Filtering + +Build or push only the Dockerfiles you need. Positional arguments are matched against Dockerfile basenames as glob patterns: + +```bash +# Build a single Dockerfile +tsdocker build Dockerfile_base + +# Glob patterns with * and ? wildcards +tsdocker build Dockerfile_app* + +# Multiple patterns +tsdocker build Dockerfile_base Dockerfile_web + +# Push specific images without rebuilding +tsdocker push --no-build Dockerfile_api +``` + +When filtering for `build`, **dependencies are auto-resolved**: if `Dockerfile_app` depends on `Dockerfile_base`, specifying only `Dockerfile_app` will automatically include `Dockerfile_base` in the build order. + ### 🔗 Dependency-Aware Builds If you have multiple Dockerfiles that depend on each other: @@ -347,20 +419,6 @@ Use different repository names for different registries: When pushing, tsdocker maps the local repo name to the registry-specific path. For example, a locally built `myproject:latest` becomes `registry.gitlab.com/mygroup/myproject:latest` and `docker.io/myuser/myproject:latest`. -### 🐳 Docker-in-Docker Testing - -Test Docker-related tools by mounting the Docker socket: - -```json -{ - "@git.zone/tsdocker": { - "baseImage": "docker:latest", - "command": "docker version && docker ps", - "dockerSock": true - } -} -``` - ### 📋 Listing Dockerfiles Inspect your project's Dockerfiles and their relationships: @@ -449,6 +507,19 @@ build-and-push: DOCKER_REGISTRY_1: "ghcr.io|${{ github.actor }}|${{ secrets.GITHUB_TOKEN }}" ``` +**Gitea Actions:** + +```yaml +- name: Build and Push + run: | + npm install -g @git.zone/tsdocker + tsdocker push + env: + DOCKER_REGISTRY_1: "gitea.example.com|${{ secrets.REGISTRY_USER }}|${{ secrets.REGISTRY_PASSWORD }}" +``` + +tsdocker auto-detects all three CI systems and enables session isolation automatically — no extra configuration needed. + ## TypeScript API tsdocker can also be used programmatically: @@ -458,10 +529,6 @@ import { TsDockerManager } from '@git.zone/tsdocker/dist_ts/classes.tsdockermana import type { ITsDockerConfig } from '@git.zone/tsdocker/dist_ts/interfaces/index.js'; const config: ITsDockerConfig = { - baseImage: 'node:20', - command: 'npm test', - dockerSock: false, - keyValueObject: {}, registries: ['docker.io'], platforms: ['linux/amd64', 'linux/arm64'], }; @@ -472,6 +539,25 @@ await manager.build({ parallel: true }); await manager.push(); ``` +## Environment Variables + +### CI & Session Control + +| Variable | Description | +|----------|-------------| +| `TSDOCKER_SESSION_ID` | Override the auto-generated session ID (default: random 8-char hex) | +| `TSDOCKER_REGISTRY_PORT` | Override the dynamically allocated local registry port | +| `CI` | Generic CI detection (also `GITHUB_ACTIONS`, `GITLAB_CI`, `GITEA_ACTIONS`) | + +### Registry Credentials + +| Variable | Description | +|----------|-------------| +| `DOCKER_REGISTRY_1` through `DOCKER_REGISTRY_10` | Pipe-delimited: `registry\|username\|password` | +| `DOCKER_REGISTRY_URL` | Registry URL for single-registry setup | +| `DOCKER_REGISTRY_USER` | Username for single-registry setup | +| `DOCKER_REGISTRY_PASSWORD` | Password for single-registry setup | + ## Requirements - **Docker** — Docker Engine 20+ or Docker Desktop @@ -507,6 +593,15 @@ tsdocker login tsdocker also falls back to `~/.docker/config.json` — ensure you've run `docker login` for your target registries. +### Push fails with "fetch failed" + +tsdocker automatically retries failed requests up to 6 times with exponential backoff. If pushes still fail: + +- Check network connectivity to the target registry +- Verify your credentials haven't expired +- Look for retry log messages (`fetch failed (attempt X/6)`) to diagnose the pattern +- Large layers may need longer timeouts — the default 5-minute timeout per request should cover most cases + ### Circular dependency detected Review your Dockerfiles' `FROM` statements — you have images depending on each other in a loop. @@ -522,16 +617,6 @@ node_modules dist_ts ``` -## Migration from Legacy - -Previously published as `npmdocker`, now `@git.zone/tsdocker`: - -| Old | New | -|-----|-----| -| `npmdocker` command | `tsdocker` command | -| `"npmdocker"` config key | `"@git.zone/tsdocker"` config key | -| CommonJS | ESM with `.js` imports | - ## License and Legal Information This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file. diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index f212431..a49f7f8 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@git.zone/tsdocker', - version: '1.17.4', + version: '2.0.0', description: 'develop npm modules cross platform with docker' } diff --git a/ts/interfaces/index.ts b/ts/interfaces/index.ts index 284e101..e0acbc8 100644 --- a/ts/interfaces/index.ts +++ b/ts/interfaces/index.ts @@ -1,15 +1,7 @@ /** * Configuration interface for tsdocker - * Extends legacy config with new Docker build capabilities */ export interface ITsDockerConfig { - // Legacy (backward compatible) - baseImage: string; - command: string; - dockerSock: boolean; - keyValueObject: { [key: string]: any }; - - // New Docker build config registries?: string[]; registryRepoMap?: { [registry: string]: string }; buildArgEnvMap?: { [dockerArg: string]: string }; diff --git a/ts/tsdocker.cli.ts b/ts/tsdocker.cli.ts index 11fb051..045fcfa 100644 --- a/ts/tsdocker.cli.ts +++ b/ts/tsdocker.cli.ts @@ -3,7 +3,6 @@ import * as paths from './tsdocker.paths.js'; // modules import * as ConfigModule from './tsdocker.config.js'; -import * as DockerModule from './tsdocker.docker.js'; import { logger, ora } from './tsdocker.logging.js'; import { TsDockerManager } from './classes.tsdockermanager.js'; @@ -14,16 +13,69 @@ import { commitinfo } from './00_commitinfo_data.js'; const tsdockerCli = new plugins.smartcli.Smartcli(); tsdockerCli.addVersion(commitinfo.version); +const printManPage = () => { + const manPage = ` +TSDOCKER(1) User Commands TSDOCKER(1) + +NAME + tsdocker - build, test, and push Docker images + +VERSION + ${commitinfo.version} + +SYNOPSIS + tsdocker [options] + +COMMANDS + build [patterns...] [flags] Build Dockerfiles in dependency order + push [patterns...] [flags] Build and push images to registries + pull Pull images from a registry + test [flags] Build and run container test scripts + login Authenticate with configured registries + list List discovered Dockerfiles + clean [-y] [--all] Interactive Docker resource cleanup + +BUILD / PUSH OPTIONS + --platform=

Target platform (e.g. linux/arm64) + --timeout= Build timeout in seconds + --no-cache Rebuild without Docker layer cache + --cached Skip builds when Dockerfile is unchanged + --verbose Stream raw docker build output + --parallel[=] Parallel builds (optional concurrency limit) + --context= Docker context to use + +PUSH-ONLY OPTIONS + --registry= Push to a specific registry + --no-build Push already-built images (skip build step) + +CLEAN OPTIONS + -y Auto-confirm all prompts + --all Include all images and volumes (not just dangling) + +CONFIGURATION + Configure via npmextra.json under the "@git.zone/tsdocker" key: + + registries Array of registry URLs to push to + registryRepoMap Map of registry URL to repo path overrides + buildArgEnvMap Map of Docker build-arg names to env var names + platforms Array of target platforms (default: ["linux/amd64"]) + push Boolean, auto-push after build + testDir Directory containing test_*.sh scripts + +EXAMPLES + tsdocker build + tsdocker build Dockerfile_app --platform=linux/arm64 + tsdocker push --registry=ghcr.io + tsdocker test --verbose + tsdocker clean -y --all +`; + console.log(manPage); +}; + export let run = () => { - // Default command: run tests in container (legacy behavior) - tsdockerCli.standardCommand().subscribe(async argvArg => { - const configArg = await ConfigModule.run().then(DockerModule.run); - if (configArg.exitCode === 0) { - logger.log('success', 'container ended all right!'); - } else { - logger.log('error', `container ended with error! Exit Code is ${configArg.exitCode}`); - process.exit(1); - } + // Default command: print man page + tsdockerCli.standardCommand().subscribe(async () => { + printManPage(); }); /** @@ -228,24 +280,6 @@ export let run = () => { } }); - /** - * this command is executed inside docker and meant for use from outside docker - */ - tsdockerCli.addCommand('runinside').subscribe(async argvArg => { - logger.log('ok', 'Allright. We are now in Docker!'); - ora.text('now trying to run your specified command'); - const configArg = await ConfigModule.run(); - const smartshellInstance = new plugins.smartshell.Smartshell({ - executor: 'bash' - }); - ora.stop(); - await smartshellInstance.exec(configArg.command).then(response => { - if (response.exitCode !== 0) { - process.exit(1); - } - }); - }); - tsdockerCli.addCommand('clean').subscribe(async argvArg => { try { const autoYes = !!argvArg.y; @@ -443,19 +477,5 @@ export let run = () => { } }); - tsdockerCli.addCommand('vscode').subscribe(async argvArg => { - const smartshellInstance = new plugins.smartshell.Smartshell({ - executor: 'bash' - }); - logger.log('ok', `Starting vscode in cwd ${paths.cwd}`); - await smartshellInstance.execAndWaitForLine( - `docker run -p 127.0.0.1:8443:8443 -v "${ - paths.cwd - }:/home/coder/project" registry.gitlab.com/hosttoday/ht-docker-vscode --allow-http --no-auth`, - /Connected to shared process/ - ); - await plugins.smartopen.openUrl('testing-vscode.git.zone:8443'); - }); - tsdockerCli.startParse(); }; diff --git a/ts/tsdocker.config.ts b/ts/tsdocker.config.ts index 2b27b6f..705c93b 100644 --- a/ts/tsdocker.config.ts +++ b/ts/tsdocker.config.ts @@ -1,34 +1,10 @@ import * as plugins from './tsdocker.plugins.js'; import * as paths from './tsdocker.paths.js'; -import * as fs from 'fs'; import type { ITsDockerConfig } from './interfaces/index.js'; -// Re-export ITsDockerConfig as IConfig for backward compatibility -export type IConfig = ITsDockerConfig & { - exitCode?: number; -}; - -const getQenvKeyValueObject = async () => { - let qenvKeyValueObjectArray: { [key: string]: string | number }; - if (fs.existsSync(plugins.path.join(paths.cwd, 'qenv.yml'))) { - qenvKeyValueObjectArray = new plugins.qenv.Qenv(paths.cwd, '.nogit/').keyValueObject; - } else { - qenvKeyValueObjectArray = {}; - } - return qenvKeyValueObjectArray; -}; - -const buildConfig = async (qenvKeyValueObjectArg: { [key: string]: string | number }) => { +const buildConfig = async (): Promise => { const npmextra = new plugins.npmextra.Npmextra(paths.cwd); - const config = npmextra.dataFor('@git.zone/tsdocker', { - // Legacy options (backward compatible) - baseImage: 'hosttoday/ht-docker-node:npmdocker', - init: 'rm -rf node_nodules/ && yarn install', - command: 'npmci npm test', - dockerSock: false, - keyValueObject: qenvKeyValueObjectArg, - - // New Docker build options + const config = npmextra.dataFor('@git.zone/tsdocker', { registries: [], registryRepoMap: {}, buildArgEnvMap: {}, @@ -39,7 +15,6 @@ const buildConfig = async (qenvKeyValueObjectArg: { [key: string]: string | numb return config; }; -export let run = async (): Promise => { - const config = await getQenvKeyValueObject().then(buildConfig); - return config; +export let run = async (): Promise => { + return buildConfig(); }; diff --git a/ts/tsdocker.docker.ts b/ts/tsdocker.docker.ts deleted file mode 100644 index b27c241..0000000 --- a/ts/tsdocker.docker.ts +++ /dev/null @@ -1,169 +0,0 @@ -import * as plugins from './tsdocker.plugins.js'; -import * as paths from './tsdocker.paths.js'; -import * as snippets from './tsdocker.snippets.js'; - -import { logger, ora } from './tsdocker.logging.js'; - -const smartshellInstance = new plugins.smartshell.Smartshell({ - executor: 'bash' -}); - -// interfaces -import type { IConfig } from './tsdocker.config.js'; - -let config: IConfig; - -/** - * the docker data used to build the internal testing container - */ -const dockerData = { - imageTag: 'npmdocker-temp-image:latest', - containerName: 'npmdocker-temp-container', - dockerProjectMountString: '', - dockerSockString: '', - dockerEnvString: '' -}; - -/** - * check if docker is available - */ -const checkDocker = () => { - const done = plugins.smartpromise.defer(); - ora.text('checking docker...'); - - if (smartshellInstance.exec('which docker')) { - logger.log('ok', 'Docker found!'); - done.resolve(); - } else { - done.reject(new Error('docker not found on this machine')); - } - return done.promise; -}; - -/** - * builds the Dockerfile according to the config in the project - */ -const buildDockerFile = async () => { - const done = plugins.smartpromise.defer(); - ora.text('building Dockerfile...'); - const dockerfile: string = snippets.dockerfileSnippet({ - baseImage: config.baseImage, - command: config.command - }); - logger.log('info', `Base image is: ${config.baseImage}`); - logger.log('info', `Command is: ${config.command}`); - await plugins.smartfs.file(plugins.path.join(paths.cwd, 'npmdocker')).write(dockerfile); - logger.log('ok', 'Dockerfile created!'); - ora.stop(); - done.resolve(); - return done.promise; -}; - -/** - * builds the Dockerimage from the built Dockerfile - */ -const buildDockerImage = async () => { - logger.log('info', 'pulling latest base image from registry...'); - await smartshellInstance.exec(`docker pull ${config.baseImage}`); - ora.text('building Dockerimage...'); - const execResult = await smartshellInstance.execSilent( - `docker build --load -f npmdocker -t ${dockerData.imageTag} ${paths.cwd}` - ); - if (execResult.exitCode !== 0) { - console.log(execResult.stdout); - process.exit(1); - } - logger.log('ok', 'Dockerimage built!'); -}; - -const buildDockerProjectMountString = async () => { - if (process.env.CI !== 'true') { - dockerData.dockerProjectMountString = `-v ${paths.cwd}:/workspace`; - } -}; - -/** - * builds an environment string that docker cli understands - */ -const buildDockerEnvString = async () => { - for (const key of Object.keys(config.keyValueObject)) { - const envString = (dockerData.dockerEnvString = - dockerData.dockerEnvString + `-e ${key}=${config.keyValueObject[key]} `); - } -}; - -/** - * creates string to mount the docker.sock inside the testcontainer - */ -const buildDockerSockString = async () => { - if (config.dockerSock) { - dockerData.dockerSockString = `-v /var/run/docker.sock:/var/run/docker.sock`; - } -}; - -/** - * creates a container by running the built Dockerimage - */ -const runDockerImage = async () => { - const done = plugins.smartpromise.defer(); - ora.text('starting Container...'); - ora.stop(); - logger.log('info', 'now running Dockerimage'); - config.exitCode = (await smartshellInstance.exec( - `docker run ${dockerData.dockerProjectMountString} ${dockerData.dockerSockString} ${ - dockerData.dockerEnvString - } --name ${dockerData.containerName} ${dockerData.imageTag}` - )).exitCode; -}; - -/** - * cleans up: deletes the test container - */ -const deleteDockerContainer = async () => { - await smartshellInstance.execSilent(`docker rm -f ${dockerData.containerName}`); -}; - -/** - * cleans up deletes the test image - */ -const deleteDockerImage = async () => { - await smartshellInstance.execSilent(`docker rmi ${dockerData.imageTag}`).then(async response => { - if (response.exitCode !== 0) { - console.log(response.stdout); - } - }); -}; - -const preClean = async () => { - await deleteDockerImage() - .then(deleteDockerContainer) - .then(async () => { - logger.log('ok', 'ensured clean Docker environment!'); - }); -}; - -const postClean = async () => { - await deleteDockerContainer() - .then(deleteDockerImage) - .then(async () => { - logger.log('ok', 'cleaned up!'); - }); - await plugins.smartfs.file(paths.npmdockerFile).delete(); -}; - -export let run = async (configArg: IConfig): Promise => { - config = configArg; - const resultConfig = await checkDocker() - .then(preClean) - .then(buildDockerFile) - .then(buildDockerImage) - .then(buildDockerProjectMountString) - .then(buildDockerEnvString) - .then(buildDockerSockString) - .then(runDockerImage) - .then(postClean) - .catch(err => { - console.log(err); - }); - return config; -}; diff --git a/ts/tsdocker.paths.ts b/ts/tsdocker.paths.ts index 914096c..f582aac 100644 --- a/ts/tsdocker.paths.ts +++ b/ts/tsdocker.paths.ts @@ -11,4 +11,3 @@ export let cwd = process.cwd(); export let packageBase = plugins.path.join(__dirname, '../'); export let assets = plugins.path.join(packageBase, 'assets/'); fs.mkdirSync(assets, { recursive: true }); -export let npmdockerFile = plugins.path.join(cwd, 'npmdocker'); diff --git a/ts/tsdocker.plugins.ts b/ts/tsdocker.plugins.ts index 770b2d8..bfef0fa 100644 --- a/ts/tsdocker.plugins.ts +++ b/ts/tsdocker.plugins.ts @@ -3,17 +3,13 @@ import * as lik from '@push.rocks/lik'; import * as npmextra from '@push.rocks/npmextra'; import * as path from 'path'; import * as projectinfo from '@push.rocks/projectinfo'; -import * as smartpromise from '@push.rocks/smartpromise'; -import * as qenv from '@push.rocks/qenv'; import * as smartcli from '@push.rocks/smartcli'; import { SmartFs, SmartFsProviderNode } from '@push.rocks/smartfs'; import * as smartlog from '@push.rocks/smartlog'; import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-local'; import * as smartlogSouceOra from '@push.rocks/smartlog-source-ora'; -import * as smartopen from '@push.rocks/smartopen'; import * as smartinteract from '@push.rocks/smartinteract'; import * as smartshell from '@push.rocks/smartshell'; -import * as smartstring from '@push.rocks/smartstring'; // Create smartfs instance export const smartfs = new SmartFs(new SmartFsProviderNode()); @@ -23,14 +19,10 @@ export { npmextra, path, projectinfo, - smartpromise, - qenv, smartcli, smartinteract, smartlog, smartlogDestinationLocal, smartlogSouceOra, - smartopen, smartshell, - smartstring }; diff --git a/ts/tsdocker.snippets.ts b/ts/tsdocker.snippets.ts deleted file mode 100644 index 6939402..0000000 --- a/ts/tsdocker.snippets.ts +++ /dev/null @@ -1,34 +0,0 @@ -import * as plugins from './tsdocker.plugins.js'; - -export interface IDockerfileSnippet { - baseImage: string; - command: string; -} - -let getMountSolutionString = (optionsArg: IDockerfileSnippet) => { - if (process.env.CI) { - return 'COPY ./ /workspace'; - } else { - return '# not copying workspcae since not in CI'; - } -}; - -let getGlobalPreparationString = (optionsArg: IDockerfileSnippet) => { - // Always install tsdocker to ensure the latest version is available - return 'RUN npm install -g @git.zone/tsdocker'; -}; - -export let dockerfileSnippet = (optionsArg: IDockerfileSnippet): string => { - return plugins.smartstring.indent.normalize( - ` -FROM ${optionsArg.baseImage} -# For info about what tsdocker does read the docs at https://gitzone.github.io/tsdocker -${getGlobalPreparationString(optionsArg)} -${getMountSolutionString(optionsArg)} -WORKDIR /workspace -ENV CI=true -ENTRYPOINT ["tsdocker"] -CMD ["runinside"] -` - ); -};