Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 843ac72f26 | |||
| 8ea6f6be83 | |||
| a88c816763 | |||
| 7cee6eb187 | |||
| ec911832ed | |||
| 302667305e | |||
| 56cf0c0f52 | |||
| 3a747cfd2f | |||
| 461c4bb5a9 | |||
| 3c3662d935 | |||
| c97306b22a | |||
| 7af0c59708 | |||
| 3a4d510304 | |||
| b1f135a5f4 | |||
| 30a5749fab | |||
| 100f37b857 | |||
| 93cf2ee7bf | |||
| 8cf8e43e59 | |||
| 3e4558abc5 | |||
| 3e0eb5e198 | |||
| 732e9e5cac | |||
| 5bf1779243 | |||
| 4908c21b84 | |||
| b1e2f0d8ea |
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
env:
|
||||
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitlab.com/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
env:
|
||||
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitlab.com/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
|
||||
@@ -16,6 +16,10 @@ node_modules/
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# rust
|
||||
rust/target/
|
||||
dist_rust/
|
||||
|
||||
# AI
|
||||
.claude/
|
||||
.serena/
|
||||
|
||||
@@ -24,4 +24,4 @@
|
||||
"@ship.zone/szci": {
|
||||
"npmGlobalTools": []
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": ["/npmextra.json"],
|
||||
"fileMatch": ["/smartconfig.json"],
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
+103
-2
@@ -1,6 +1,85 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-05-09 - 2.2.8 - fix(package)
|
||||
normalize bin entry path for the tsdocker CLI
|
||||
|
||||
- Updates the package.json bin field from "cli.js" to "./cli.js" for the tsdocker executable.
|
||||
|
||||
## 2026-05-09 - 2.2.7 - fix(docs)
|
||||
update installation and configuration documentation and include the license file in published artifacts
|
||||
|
||||
- Add the MIT license file to the repository and package contents
|
||||
- Refresh README installation examples to use pnpm commands and clarify local vs CI builder and registry behavior
|
||||
- Correct CLI help text to reference .smartconfig.json and update dependency versions in package.json
|
||||
|
||||
## 2026-05-08 - 2.2.6 - fix(tsdockermanager)
|
||||
update project info loading to use the async ProjectInfo factory
|
||||
|
||||
- bump @push.rocks/projectinfo from ^5.0.2 to ^5.1.0
|
||||
- replace direct ProjectInfo construction with await ProjectInfo.create(paths.cwd) during manager preparation
|
||||
|
||||
## 2026-04-30 - 2.2.5 - fix(scripts)
|
||||
use pnpm for the test script build command
|
||||
|
||||
- updates the test script in package.json to run the build through pnpm instead of npm
|
||||
|
||||
## 2026-03-24 - 2.2.4 - fix(config)
|
||||
migrate configuration loading to smartconfig and update build tooling compatibility
|
||||
|
||||
- switch configuration loading and documentation to .smartconfig.json
|
||||
- upgrade build and test dependencies for tsbuild 4.4.0 and TypeScript 6 compatibility
|
||||
- remove deprecated tsconfig baseUrl and paths settings and add an lru-cache override to avoid type issues
|
||||
|
||||
## 2026-03-24 - 2.2.3 - fix(config)
|
||||
update workflow repository URL handling and package config file references
|
||||
|
||||
- Switches Gitea workflow repository URLs to use gitlab.com explicitly for authenticated CI git operations.
|
||||
- Replaces the published config file entry from npmextra.json to .smartconfig.json in package metadata.
|
||||
- Adds Rust build output directories to .gitignore.
|
||||
- Refreshes changelog and README formatting to match the current smartconfig-based configuration.
|
||||
|
||||
## 2026-03-24 - 2.2.2 - fix(config)
|
||||
|
||||
rename npmextra configuration file to .smartconfig.json
|
||||
|
||||
- Moves the existing project metadata and release configuration from npmextra.json to .smartconfig.json without changing its contents.
|
||||
|
||||
## 2026-03-24 - 2.2.1 - fix(config)
|
||||
|
||||
switch configuration loading from npmextra to smartconfig
|
||||
|
||||
- replace the @push.rocks/npmextra dependency with @push.rocks/smartconfig
|
||||
- update config initialization to use Smartconfig for the @git.zone/tsdocker settings
|
||||
- refresh CLI help text to reference smartconfig.json instead of npmextra.json
|
||||
|
||||
## 2026-03-19 - 2.2.0 - feat(cli/buildx)
|
||||
|
||||
add pull control for builds and isolate buildx builders per project
|
||||
|
||||
- adds a new pull build option with --no-pull CLI support and defaults builds to refreshing base images with --pull
|
||||
- passes the selected buildx builder explicitly into build commands instead of relying on global docker buildx use state
|
||||
- generates project-hashed builder suffixes so concurrent runs from different project directories do not share the same local builder
|
||||
- updates session logging to include project hash and builder suffix for easier build diagnostics
|
||||
|
||||
## 2026-03-15 - 2.1.0 - feat(cli)
|
||||
|
||||
add global remote builder configuration and native SSH buildx nodes for multi-platform builds
|
||||
|
||||
- adds a new `tsdocker config` command with subcommands to add, remove, list, and show remote builder definitions
|
||||
- introduces global config support for remote builders stored under `~/.git.zone/tsdocker/config.json`
|
||||
- builds can now create multi-node buildx setups with remote SSH builders and open reverse SSH tunnels so remote nodes can push to the local staging registry
|
||||
- updates the README and CLI help to document remote builder configuration and native cross-platform build workflows
|
||||
|
||||
## 2026-03-12 - 2.0.2 - fix(repo)
|
||||
|
||||
no changes to commit
|
||||
|
||||
## 2026-03-12 - 2.0.1 - fix(repository)
|
||||
|
||||
no changes to commit
|
||||
|
||||
## 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`)
|
||||
@@ -8,10 +87,11 @@ remove legacy container test runner and make the default command show the man pa
|
||||
- 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
|
||||
|
||||
|
||||
## 2026-02-07 - 1.17.3 - fix(registry)
|
||||
|
||||
increase default maxRetries in fetchWithRetry from 3 to 6 to improve resilience when fetching registry resources
|
||||
|
||||
- Changed default maxRetries from 3 to 6 in ts/classes.registrycopy.ts
|
||||
@@ -19,6 +99,7 @@ increase default maxRetries in fetchWithRetry from 3 to 6 to improve resilience
|
||||
- No API or behavior changes besides the increased default retry count
|
||||
|
||||
## 2026-02-07 - 1.17.2 - fix(registry)
|
||||
|
||||
improve HTTP fetch retry logging, backoff calculation, and token-cache warning
|
||||
|
||||
- Include HTTP method in logs and normalize method to uppercase for consistency
|
||||
@@ -28,6 +109,7 @@ improve HTTP fetch retry logging, backoff calculation, and token-cache warning
|
||||
- Add a warning log when clearing cached token after a 401 response
|
||||
|
||||
## 2026-02-07 - 1.17.1 - fix(registrycopy)
|
||||
|
||||
add fetchWithRetry wrapper to apply timeouts, retries with exponential backoff, and token cache handling; use it for registry HTTP requests
|
||||
|
||||
- Introduces fetchWithRetry(url, options, timeoutMs, maxRetries) to wrap fetch with AbortSignal timeout, exponential backoff retries, and retry behavior only for network errors and 5xx responses
|
||||
@@ -36,6 +118,7 @@ add fetchWithRetry wrapper to apply timeouts, retries with exponential backoff,
|
||||
- Adds logging on retry attempts and backoff delays to improve robustness and observability
|
||||
|
||||
## 2026-02-07 - 1.17.0 - feat(tsdocker)
|
||||
|
||||
add Dockerfile filtering, optional skip-build flow, and fallback Docker config credential loading
|
||||
|
||||
- Add TsDockerManager.filterDockerfiles(patterns) to filter discovered Dockerfiles by glob-style patterns and warn when no matches are found
|
||||
@@ -44,6 +127,7 @@ add Dockerfile filtering, optional skip-build flow, and fallback Docker config c
|
||||
- Import RegistryCopy and add info/warn logs when credentials are loaded or missing
|
||||
|
||||
## 2026-02-07 - 1.16.0 - feat(core)
|
||||
|
||||
Introduce per-invocation TsDockerSession and session-aware local registry and build orchestration; stream and parse buildx output for improved logging and visibility; detect Docker topology and add CI-safe cleanup; update README with multi-arch, parallel-build, caching, and local registry usage and new CLI flags.
|
||||
|
||||
- Add TsDockerSession to allocate unique ports, container names and builder suffixes for concurrent runs (especially in CI).
|
||||
@@ -56,6 +140,7 @@ Introduce per-invocation TsDockerSession and session-aware local registry and bu
|
||||
- Large README improvements: multi-arch flow, persistent local registry, parallel builds, caching, new CLI and clean flags, and examples for CI integration.
|
||||
|
||||
## 2026-02-07 - 1.15.1 - fix(registry)
|
||||
|
||||
use persistent local registry and OCI Distribution API image copy for pushes
|
||||
|
||||
- Adds RegistryCopy class implementing the OCI Distribution API to copy images (including multi-arch manifest lists) from the local registry to remote registries.
|
||||
@@ -67,6 +152,7 @@ use persistent local registry and OCI Distribution API image copy for pushes
|
||||
- Breaking change: registry usage and push behavior changed (config.push ignored and local registry mandatory) — bump major version.
|
||||
|
||||
## 2026-02-07 - 1.15.0 - feat(clean)
|
||||
|
||||
Make the `clean` command interactive: add smartinteract prompts, docker context detection, and selective resource removal with support for --all and -y auto-confirm
|
||||
|
||||
- Adds dependency @push.rocks/smartinteract and exposes it from the plugins module
|
||||
@@ -76,6 +162,7 @@ Make the `clean` command interactive: add smartinteract prompts, docker context
|
||||
- Replaces blunt shell commands with safer, interactive selection and adds improved error handling and logging
|
||||
|
||||
## 2026-02-07 - 1.14.0 - feat(build)
|
||||
|
||||
add level-based parallel builds with --parallel and configurable concurrency
|
||||
|
||||
- Introduces --parallel and --parallel=<n> CLI flags to enable level-based parallel Docker builds (default concurrency 4).
|
||||
@@ -86,6 +173,7 @@ add level-based parallel builds with --parallel and configurable concurrency
|
||||
- Updates documentation (readme.hints.md) with usage examples and implementation notes.
|
||||
|
||||
## 2026-02-07 - 1.13.0 - feat(docker)
|
||||
|
||||
add Docker context detection, rootless support, and context-aware buildx registry handling
|
||||
|
||||
- Introduce DockerContext class to detect current Docker context and rootless mode and to log warnings and context info
|
||||
@@ -95,6 +183,7 @@ add Docker context detection, rootless support, and context-aware buildx registr
|
||||
- Pass isRootless into local registry startup and build pipeline; emit rootless-specific warnings and registry reachability hint
|
||||
|
||||
## 2026-02-06 - 1.12.0 - feat(docker)
|
||||
|
||||
add detailed logging for buildx, build commands, local registry, and local dependency info
|
||||
|
||||
- Log startup of local registry including a note about buildx dependency bridging
|
||||
@@ -104,6 +193,7 @@ add detailed logging for buildx, build commands, local registry, and local depen
|
||||
- Non-functional change: purely adds informational logging to improve observability during builds
|
||||
|
||||
## 2026-02-06 - 1.11.0 - feat(docker)
|
||||
|
||||
start temporary local registry for buildx dependency resolution and ensure buildx builder uses host network
|
||||
|
||||
- Introduce a temporary local registry (localhost:5234) with start/stop helpers and push support to expose local images for buildx
|
||||
@@ -114,6 +204,7 @@ start temporary local registry for buildx dependency resolution and ensure build
|
||||
- Ensure buildx builder is created with --driver-opt network=host and recreate existing builder if it lacks host network to allow registry access from build containers
|
||||
|
||||
## 2026-02-06 - 1.10.0 - feat(classes.dockerfile)
|
||||
|
||||
support using a local base image as a build context in buildx commands
|
||||
|
||||
- Adds --build-context flag mapping base image to docker-image://<localTag> when localBaseImageDependent && localBaseDockerfile are set
|
||||
@@ -121,6 +212,7 @@ support using a local base image as a build context in buildx commands
|
||||
- Logs an info message indicating the local build context mapping
|
||||
|
||||
## 2026-02-06 - 1.9.0 - feat(build)
|
||||
|
||||
add verbose build output, progress logging, and timing for builds/tests
|
||||
|
||||
- Add 'verbose' option to build/test flows (interfaces, CLI, and method signatures) to allow streaming raw docker build output or run silently
|
||||
@@ -131,6 +223,7 @@ add verbose build output, progress logging, and timing for builds/tests
|
||||
- Use silent exec variants when verbose is false and stream exec when verbose is true
|
||||
|
||||
## 2026-02-06 - 1.8.0 - feat(build)
|
||||
|
||||
add optional content-hash based build cache to skip rebuilding unchanged Dockerfiles
|
||||
|
||||
- Introduce TsDockerCache to compute SHA-256 of Dockerfile content and persist cache to .nogit/tsdocker_support.json
|
||||
@@ -140,12 +233,14 @@ add optional content-hash based build cache to skip rebuilding unchanged Dockerf
|
||||
- Cache records store contentHash, imageId, buildTag and timestamp
|
||||
|
||||
## 2026-02-06 - 1.7.0 - feat(cli)
|
||||
|
||||
add CLI version display using commitinfo
|
||||
|
||||
- Imported commitinfo from './00_commitinfo_data.js' and called tsdockerCli.addVersion(commitinfo.version) to surface package/commit version in the Smartcli instance
|
||||
- Change made in ts/tsdocker.cli.ts — small user-facing CLI enhancement; no breaking changes
|
||||
|
||||
## 2026-02-06 - 1.6.0 - feat(docker)
|
||||
|
||||
add support for no-cache builds and tag built images for local dependency resolution
|
||||
|
||||
- Introduce IBuildCommandOptions.noCache to control --no-cache behavior
|
||||
@@ -155,6 +250,7 @@ add support for no-cache builds and tag built images for local dependency resolu
|
||||
- Log tagging actions and execute docker tag via smartshellInstance
|
||||
|
||||
## 2026-02-06 - 1.5.0 - feat(build)
|
||||
|
||||
add support for selective builds, platform override and build timeout
|
||||
|
||||
- Introduce IBuildCommandOptions with patterns, platform and timeout to control build behavior
|
||||
@@ -164,6 +260,7 @@ add support for selective builds, platform override and build timeout
|
||||
- Implement streaming exec with timeout to kill long-running builds and surface timeout errors
|
||||
|
||||
## 2026-02-04 - 1.4.3 - fix(dockerfile)
|
||||
|
||||
fix matching of base images to local Dockerfiles by stripping registry prefixes when comparing image references
|
||||
|
||||
- Added Dockerfile.extractRepoVersion(imageRef) to normalize image references by removing registry prefixes (detects registries containing '.' or ':' or 'localhost').
|
||||
@@ -171,13 +268,15 @@ fix matching of base images to local Dockerfiles by stripping registry prefixes
|
||||
- Prevents mismatches when baseImage includes a registry (e.g. "host.today/repo:version") so it correctly matches a local cleanTag like "repo:version".
|
||||
|
||||
## 2026-01-21 - 1.4.2 - fix(classes.dockerfile)
|
||||
|
||||
use a single top-level fs import instead of requiring fs inside methods
|
||||
|
||||
- Added top-level import: import * as fs from 'fs' in ts/classes.dockerfile.ts
|
||||
- Added top-level import: import \* as fs from 'fs' in ts/classes.dockerfile.ts
|
||||
- Removed inline require('fs') calls and replaced with the imported fs in constructor and test() to keep imports consistent
|
||||
- No behavioral change expected; this is a cleanup/refactor to standardize module usage
|
||||
|
||||
## 2026-01-20 - 1.4.1 - fix(docs)
|
||||
|
||||
update README: expand usage, installation, quick start, features, troubleshooting and migration notes
|
||||
|
||||
- Expanded README content: new Quick Start, Installation examples, and detailed Features section (containerized testing, smart Docker builds, multi-registry push, multi-architecture support, zero-config start)
|
||||
@@ -186,6 +285,7 @@ update README: expand usage, installation, quick start, features, troubleshootin
|
||||
- Documentation-only change — no source code modified
|
||||
|
||||
## 2026-01-20 - 1.4.0 - feat(tsdocker)
|
||||
|
||||
add multi-registry and multi-arch Docker build/push/pull manager, registry storage, Dockerfile handling, and new CLI commands
|
||||
|
||||
- Introduce TsDockerManager orchestrator to discover, sort, build, test, push and pull Dockerfiles
|
||||
@@ -198,6 +298,7 @@ add multi-registry and multi-arch Docker build/push/pull manager, registry stora
|
||||
- Update README and readme.hints with new features, configuration examples and command list
|
||||
|
||||
## 2026-01-19 - 1.3.0 - feat(packaging)
|
||||
|
||||
Rename package scope to @git.zone and migrate to ESM; rename CLI/config keys, update entrypoints and imports, bump Node requirement to 18, and adjust scripts/dependencies
|
||||
|
||||
- Package renamed to @git.zone/tsdocker (scope change) — consumers must update package reference.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Task Venture Capital GmbH
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+30
-23
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@git.zone/tsdocker",
|
||||
"version": "2.0.0",
|
||||
"version": "2.2.8",
|
||||
"private": false,
|
||||
"description": "develop npm modules cross platform with docker",
|
||||
"description": "A comprehensive Docker build tool for TypeScript projects with multi-arch support, multi-registry push, and CI-safe session isolation.",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"bin": {
|
||||
"tsdocker": "cli.js"
|
||||
"tsdocker": "./cli.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "(npm run build)",
|
||||
"test": "(pnpm run build)",
|
||||
"build": "(tsbuild)",
|
||||
"buildDocs": "tsdoc"
|
||||
},
|
||||
@@ -18,31 +18,40 @@
|
||||
"url": "https://gitlab.com/gitzone/tsdocker.git"
|
||||
},
|
||||
"keywords": [
|
||||
"docker"
|
||||
"docker",
|
||||
"typescript",
|
||||
"buildx",
|
||||
"multi-arch",
|
||||
"multi-registry",
|
||||
"oci",
|
||||
"container",
|
||||
"ci-cd",
|
||||
"docker-build",
|
||||
"cross-platform"
|
||||
],
|
||||
"author": "Lossless GmbH",
|
||||
"author": "Task Venture Capital GmbH",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://gitlab.com/gitzone/tsdocker/issues"
|
||||
},
|
||||
"homepage": "https://gitlab.com/gitzone/tsdocker#readme",
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^4.1.2",
|
||||
"@git.zone/tsrun": "^2.0.1",
|
||||
"@git.zone/tstest": "^3.1.6",
|
||||
"@types/node": "^25.0.9"
|
||||
"@git.zone/tsbuild": "^4.4.0",
|
||||
"@git.zone/tsrun": "^2.0.3",
|
||||
"@git.zone/tstest": "^3.6.5",
|
||||
"@types/node": "^25.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@push.rocks/lik": "^6.2.2",
|
||||
"@push.rocks/npmextra": "^5.3.3",
|
||||
"@push.rocks/projectinfo": "^5.0.2",
|
||||
"@push.rocks/smartcli": "^4.0.20",
|
||||
"@push.rocks/smartfs": "^1.3.1",
|
||||
"@push.rocks/lik": "^6.4.1",
|
||||
"@push.rocks/projectinfo": "^5.1.0",
|
||||
"@push.rocks/smartcli": "^4.0.21",
|
||||
"@push.rocks/smartconfig": "^6.1.1",
|
||||
"@push.rocks/smartfs": "^1.5.1",
|
||||
"@push.rocks/smartinteract": "^2.0.16",
|
||||
"@push.rocks/smartlog": "^3.1.10",
|
||||
"@push.rocks/smartlog": "^3.2.2",
|
||||
"@push.rocks/smartlog-destination-local": "^9.0.2",
|
||||
"@push.rocks/smartlog-source-ora": "^1.0.9",
|
||||
"@push.rocks/smartshell": "^3.3.0"
|
||||
"@push.rocks/smartshell": "^3.3.8"
|
||||
},
|
||||
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34",
|
||||
"type": "module",
|
||||
@@ -55,10 +64,8 @@
|
||||
"dist_ts_web/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
"npmextra.json",
|
||||
"readme.md"
|
||||
],
|
||||
"pnpm": {
|
||||
"overrides": {}
|
||||
}
|
||||
".smartconfig.json",
|
||||
"readme.md",
|
||||
"license"
|
||||
]
|
||||
}
|
||||
|
||||
Generated
+4011
-3493
File diff suppressed because it is too large
Load Diff
+21
-13
@@ -3,26 +3,27 @@
|
||||
## Module Purpose
|
||||
|
||||
tsdocker is a comprehensive Docker development and building tool. It provides:
|
||||
|
||||
- Building Dockerfiles with dependency ordering
|
||||
- Multi-registry push/pull support
|
||||
- Multi-architecture builds (amd64/arm64)
|
||||
|
||||
## New CLI Commands (2026-01-19)
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `tsdocker` | Show usage / man page |
|
||||
| `tsdocker build` | Build all Dockerfiles with dependency ordering |
|
||||
| `tsdocker push [registry]` | Push images to configured registries |
|
||||
| `tsdocker pull <registry>` | Pull images from registry |
|
||||
| `tsdocker test` | Run container tests (test scripts) |
|
||||
| `tsdocker login` | Login to configured registries |
|
||||
| `tsdocker list` | List discovered Dockerfiles and dependencies |
|
||||
| `tsdocker clean --all` | Clean up Docker environment |
|
||||
| Command | Description |
|
||||
| -------------------------- | ---------------------------------------------- |
|
||||
| `tsdocker` | Show usage / man page |
|
||||
| `tsdocker build` | Build all Dockerfiles with dependency ordering |
|
||||
| `tsdocker push [registry]` | Push images to configured registries |
|
||||
| `tsdocker pull <registry>` | Pull images from registry |
|
||||
| `tsdocker test` | Run container tests (test scripts) |
|
||||
| `tsdocker login` | Login to configured registries |
|
||||
| `tsdocker list` | List discovered Dockerfiles and dependencies |
|
||||
| `tsdocker clean --all` | Clean up Docker environment |
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure in `package.json` under `@git.zone/tsdocker`:
|
||||
Configure in `.smartconfig.json` under `@git.zone/tsdocker`:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -106,6 +107,7 @@ Implementation: `Dockerfile.computeLevels()` groups topologically sorted Dockerf
|
||||
All builds now go through a persistent local registry (`localhost:5234`) with volume storage at `.nogit/docker-registry/`. Pushes use the `RegistryCopy` class (`ts/classes.registrycopy.ts`) which implements the OCI Distribution API to copy images (including multi-arch manifest lists) from the local registry to remote registries. This replaces the old `docker tag + docker push` approach that only worked for single-platform images.
|
||||
|
||||
Key classes:
|
||||
|
||||
- `RegistryCopy` — HTTP-based OCI image copy (auth, blob transfer, manifest handling)
|
||||
- `Dockerfile.push()` — Now delegates to `RegistryCopy.copyImage()`
|
||||
- `Dockerfile.needsLocalRegistry()` — Always returns true
|
||||
@@ -115,10 +117,16 @@ The `config.push` field is now a no-op (kept for backward compat).
|
||||
|
||||
## Build Status
|
||||
|
||||
- Build: ✅ Passes
|
||||
- Build: ✅ Passes (TypeScript 6 via tsbuild 4.4.0)
|
||||
|
||||
## Previous Upgrades (2025-11-22)
|
||||
## Previous Upgrades
|
||||
|
||||
### 2026-03-24
|
||||
- Upgraded `@git.zone/tsbuild` from 4.3.0 to 4.4.0 (TypeScript 6)
|
||||
- Removed deprecated `baseUrl`/`paths` from tsconfig.json
|
||||
- Added pnpm override `lru-cache: ">=11.0.0"` to fix TS6 type incompatibility with lru-cache@10.x
|
||||
|
||||
### 2025-11-22
|
||||
- Updated all @git.zone/_ dependencies to @git.zone/_ scope
|
||||
- Updated all @pushrocks/_ dependencies to @push.rocks/_ scope
|
||||
- Migrated from smartfile v8 to smartfs v1.1.0
|
||||
|
||||
@@ -27,10 +27,10 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
||||
|
||||
```bash
|
||||
# Global installation (recommended for CLI usage)
|
||||
npm install -g @git.zone/tsdocker
|
||||
pnpm add --global @git.zone/tsdocker
|
||||
|
||||
# Or project-local installation
|
||||
pnpm install --save-dev @git.zone/tsdocker
|
||||
pnpm add --save-dev @git.zone/tsdocker
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
@@ -44,6 +44,7 @@ tsdocker build
|
||||
```
|
||||
|
||||
tsdocker will:
|
||||
|
||||
1. 🔍 Discover all `Dockerfile*` files in your project
|
||||
2. 📊 Analyze `FROM` dependencies between them
|
||||
3. 🔄 Sort them topologically
|
||||
@@ -84,49 +85,68 @@ tsdocker push --no-build Dockerfile_api Dockerfile_web
|
||||
|
||||
## CLI Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `tsdocker` | Show usage / man page |
|
||||
| `tsdocker build` | Build all Dockerfiles with dependency ordering |
|
||||
| `tsdocker push` | Build + push images to configured registries |
|
||||
| `tsdocker pull <registry>` | Pull images from a specific registry |
|
||||
| `tsdocker test` | Build + run container test scripts (`test_*.sh`) |
|
||||
| `tsdocker login` | Authenticate with configured registries |
|
||||
| `tsdocker list` | Display discovered Dockerfiles and their dependencies |
|
||||
| `tsdocker clean` | Interactively clean Docker environment |
|
||||
| Command | Description |
|
||||
| -------------------------- | ------------------------------------------------------------ |
|
||||
| `tsdocker` | Show usage / man page |
|
||||
| `tsdocker build` | Build all Dockerfiles with dependency ordering |
|
||||
| `tsdocker push` | Build + push images to configured registries |
|
||||
| `tsdocker pull <registry>` | Pull images from a specific registry |
|
||||
| `tsdocker test` | Build + run container test scripts (`test_*.sh`) |
|
||||
| `tsdocker login` | Authenticate with configured registries |
|
||||
| `tsdocker list` | Display discovered Dockerfiles and their dependencies |
|
||||
| `tsdocker config` | Manage global tsdocker configuration (remote builders, etc.) |
|
||||
| `tsdocker clean` | Interactively clean Docker environment |
|
||||
|
||||
### Build Flags
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `<patterns>` | 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 |
|
||||
| `--cached` | Skip unchanged Dockerfiles (content-hash based) |
|
||||
| `--verbose` | Stream raw `docker build` output |
|
||||
| `--parallel` | Enable level-based parallel builds (default concurrency: 4) |
|
||||
| `--parallel=8` | Parallel builds with custom concurrency |
|
||||
| `--context=mycontext` | Use a specific Docker context |
|
||||
| Flag | Description |
|
||||
| ------------------------ | ------------------------------------------------------------------------------- |
|
||||
| `<patterns>` | 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 |
|
||||
| `--cached` | Skip unchanged Dockerfiles (content-hash based) |
|
||||
| `--verbose` | Stream raw `docker build` output |
|
||||
| `--parallel` | Enable level-based parallel builds (default concurrency: 4) |
|
||||
| `--parallel=8` | Parallel builds with custom concurrency |
|
||||
| `--context=mycontext` | Use a specific Docker context |
|
||||
|
||||
### Push Flags
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `<patterns>` | Positional Dockerfile name patterns to select which images to push |
|
||||
| `--registry=<url>` | Push to a single specific registry instead of all configured |
|
||||
| `--no-build` | Skip the build phase; only push existing images from local registry |
|
||||
| Flag | Description |
|
||||
| ------------------ | ------------------------------------------------------------------- |
|
||||
| `<patterns>` | Positional Dockerfile name patterns to select which images to push |
|
||||
| `--registry=<url>` | Push to a single specific registry instead of all configured |
|
||||
| `--no-build` | Skip the build phase; only push existing images from local registry |
|
||||
|
||||
### Config Subcommands
|
||||
|
||||
| Subcommand | Description |
|
||||
| ---------------- | ----------------------------------- |
|
||||
| `add-builder` | Add or update a remote builder node |
|
||||
| `remove-builder` | Remove a remote builder by name |
|
||||
| `list-builders` | List all configured remote builders |
|
||||
| `show` | Show the full global configuration |
|
||||
|
||||
**`add-builder` flags:**
|
||||
|
||||
| Flag | Description |
|
||||
| ------------------ | --------------------------------------------------------- |
|
||||
| `--name=<name>` | Builder name (e.g. `arm64-builder`) |
|
||||
| `--host=<user@ip>` | SSH host (e.g. `armbuilder@192.168.1.100`) |
|
||||
| `--platform=<p>` | Target platform (e.g. `linux/arm64`) |
|
||||
| `--ssh-key=<path>` | SSH key path (optional, uses SSH agent/config by default) |
|
||||
|
||||
### Clean Flags
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| Flag | Description |
|
||||
| ------- | -------------------------------------------------- |
|
||||
| `--all` | Include all images and volumes (not just dangling) |
|
||||
| `-y` | Auto-confirm all prompts |
|
||||
| `-y` | Auto-confirm all prompts |
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure tsdocker in your `package.json` or `npmextra.json` under the `@git.zone/tsdocker` key:
|
||||
Configure tsdocker in your `.smartconfig.json` under the `@git.zone/tsdocker` key:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -148,13 +168,13 @@ Configure tsdocker in your `package.json` or `npmextra.json` under the `@git.zon
|
||||
|
||||
#### Build & Push Options
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `registries` | `string[]` | `[]` | Registry URLs to push to |
|
||||
| `registryRepoMap` | `object` | `{}` | Map registries to different repository paths |
|
||||
| `buildArgEnvMap` | `object` | `{}` | Map Docker build ARGs to environment variables |
|
||||
| `platforms` | `string[]` | `["linux/amd64"]` | Target architectures for multi-arch builds |
|
||||
| `testDir` | `string` | `./test` | Directory containing test scripts |
|
||||
| Option | Type | Default | Description |
|
||||
| ----------------- | ---------- | ----------------- | ---------------------------------------------- |
|
||||
| `registries` | `string[]` | `[]` | Registry URLs to push to |
|
||||
| `registryRepoMap` | `object` | `{}` | Map registries to different repository paths |
|
||||
| `buildArgEnvMap` | `object` | `{}` | Map Docker build ARGs to environment variables |
|
||||
| `platforms` | `string[]` | `["linux/amd64"]` | Target architectures for multi-arch builds |
|
||||
| `testDir` | `string` | `./test` | Directory containing test scripts |
|
||||
|
||||
## Architecture: How tsdocker Works
|
||||
|
||||
@@ -198,12 +218,12 @@ tsdocker uses a **local OCI registry** as the canonical store for all built imag
|
||||
|
||||
### 🔑 Why a Local Registry?
|
||||
|
||||
| Problem | Solution |
|
||||
|---------|----------|
|
||||
| `docker buildx --load` fails for multi-arch images | `buildx --push` to local registry works for any number of platforms |
|
||||
| `docker push` only pushes single-platform manifests | OCI API copy preserves full manifest lists (multi-arch) |
|
||||
| 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 |
|
||||
| Problem | Solution |
|
||||
| --------------------------------------------------- | ------------------------------------------------------------------- |
|
||||
| `docker buildx --load` fails for multi-arch images | `buildx --push` to local registry works for any number of platforms |
|
||||
| `docker push` only pushes single-platform manifests | OCI API copy preserves full manifest lists (multi-arch) |
|
||||
| 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
|
||||
|
||||
@@ -223,28 +243,29 @@ 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-<sessionId>`
|
||||
- **Builder suffix** — In CI, the buildx builder gets a `-<sessionId>` suffix to prevent collisions
|
||||
- **Builder suffix** — Local runs use a stable project hash; CI runs add the session ID to prevent collisions
|
||||
- **Registry data path** — Local runs use `.nogit/docker-registry/`; CI runs use `.nogit/docker-registry/<sessionId>/`
|
||||
|
||||
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 |
|
||||
| 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.
|
||||
In local dev, the builder suffix is stable per project path, keeping a reusable builder while avoiding collisions between projects. In CI, the session ID is added so parallel jobs do not share builders or registry storage.
|
||||
|
||||
### 🔍 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 |
|
||||
| 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-<context>`) prevent conflicts across Docker contexts. Rootless Docker configurations trigger appropriate warnings.
|
||||
|
||||
@@ -290,10 +311,57 @@ Build for multiple platforms using Docker Buildx:
|
||||
```
|
||||
|
||||
tsdocker automatically:
|
||||
|
||||
- Sets up a Buildx builder with `--driver-opt network=host` (so buildx can reach the local registry)
|
||||
- Pushes multi-platform images to the local registry via `buildx --push`
|
||||
- Copies the full manifest list (including all platform variants) to remote registries on `tsdocker push`
|
||||
|
||||
### 🖥️ Native Remote Builders
|
||||
|
||||
Instead of relying on slow QEMU emulation for cross-platform builds, tsdocker can use **native remote machines** via SSH as build nodes. For example, use a real arm64 machine for `linux/arm64` builds:
|
||||
|
||||
```bash
|
||||
# Add a remote arm64 builder
|
||||
tsdocker config add-builder \
|
||||
--name=arm64-builder \
|
||||
--host=armbuilder@192.168.1.100 \
|
||||
--platform=linux/arm64 \
|
||||
--ssh-key=~/.ssh/id_ed25519
|
||||
|
||||
# List configured builders
|
||||
tsdocker config list-builders
|
||||
|
||||
# Remove a builder
|
||||
tsdocker config remove-builder --name=arm64-builder
|
||||
|
||||
# Show full global config
|
||||
tsdocker config show
|
||||
```
|
||||
|
||||
Global configuration is stored at `~/.git.zone/tsdocker/config.json`.
|
||||
|
||||
**How it works:**
|
||||
|
||||
When remote builders are configured and the project's `platforms` includes a matching platform, tsdocker automatically:
|
||||
|
||||
1. Creates a **multi-node buildx builder** — local node for `linux/amd64`, remote SSH node for `linux/arm64`
|
||||
2. Opens **SSH reverse tunnels** so the remote builder can push to the local staging registry
|
||||
3. Builds natively on each platform's hardware — no QEMU overhead
|
||||
4. Tears down tunnels after the build completes
|
||||
|
||||
```
|
||||
[Local machine] [Remote arm64 machine]
|
||||
registry:2 on localhost:PORT <──── SSH reverse tunnel ──── localhost:PORT
|
||||
BuildKit (amd64) ──push──> BuildKit (arm64) ──push──>
|
||||
localhost:PORT localhost:PORT (tunneled)
|
||||
```
|
||||
|
||||
**Prerequisites for the remote machine:**
|
||||
|
||||
- Docker installed and running
|
||||
- A user with Docker group access (no sudo needed)
|
||||
- SSH key access configured
|
||||
|
||||
### ⚡ Parallel Builds
|
||||
|
||||
Speed up builds by building independent images concurrently:
|
||||
@@ -315,11 +383,11 @@ tsdocker groups Dockerfiles into **dependency levels** using topological analysi
|
||||
|
||||
tsdocker discovers files matching `Dockerfile*`:
|
||||
|
||||
| File Name | Version Tag |
|
||||
|-----------|-------------|
|
||||
| `Dockerfile` | `latest` |
|
||||
| `Dockerfile_v1.0.0` | `v1.0.0` |
|
||||
| `Dockerfile_alpine` | `alpine` |
|
||||
| File Name | Version Tag |
|
||||
| ------------------------ | --------------------------- |
|
||||
| `Dockerfile` | `latest` |
|
||||
| `Dockerfile_v1.0.0` | `v1.0.0` |
|
||||
| `Dockerfile_alpine` | `alpine` |
|
||||
| `Dockerfile_##version##` | Uses `package.json` version |
|
||||
|
||||
### 🎯 Dockerfile Filtering
|
||||
@@ -428,6 +496,7 @@ tsdocker list
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
Discovered Dockerfiles:
|
||||
========================
|
||||
@@ -489,10 +558,10 @@ tsdocker push
|
||||
build-and-push:
|
||||
stage: build
|
||||
script:
|
||||
- npm install -g @git.zone/tsdocker
|
||||
- pnpm add --global @git.zone/tsdocker
|
||||
- tsdocker push
|
||||
variables:
|
||||
DOCKER_REGISTRY_1: "registry.gitlab.com|$CI_REGISTRY_USER|$CI_REGISTRY_PASSWORD"
|
||||
DOCKER_REGISTRY_1: 'registry.gitlab.com|$CI_REGISTRY_USER|$CI_REGISTRY_PASSWORD'
|
||||
```
|
||||
|
||||
**GitHub Actions:**
|
||||
@@ -500,11 +569,11 @@ build-and-push:
|
||||
```yaml
|
||||
- name: Build and Push
|
||||
run: |
|
||||
npm install -g @git.zone/tsdocker
|
||||
pnpm add --global @git.zone/tsdocker
|
||||
tsdocker login
|
||||
tsdocker push
|
||||
env:
|
||||
DOCKER_REGISTRY_1: "ghcr.io|${{ github.actor }}|${{ secrets.GITHUB_TOKEN }}"
|
||||
DOCKER_REGISTRY_1: 'ghcr.io|${{ github.actor }}|${{ secrets.GITHUB_TOKEN }}'
|
||||
```
|
||||
|
||||
**Gitea Actions:**
|
||||
@@ -512,10 +581,10 @@ build-and-push:
|
||||
```yaml
|
||||
- name: Build and Push
|
||||
run: |
|
||||
npm install -g @git.zone/tsdocker
|
||||
pnpm add --global @git.zone/tsdocker
|
||||
tsdocker push
|
||||
env:
|
||||
DOCKER_REGISTRY_1: "gitea.example.com|${{ secrets.REGISTRY_USER }}|${{ secrets.REGISTRY_PASSWORD }}"
|
||||
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.
|
||||
@@ -543,20 +612,20 @@ await manager.push();
|
||||
|
||||
### 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`) |
|
||||
| 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 |
|
||||
|----------|-------------|
|
||||
| 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 |
|
||||
| `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
|
||||
|
||||
@@ -619,7 +688,7 @@ dist_ts
|
||||
|
||||
## 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.
|
||||
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.
|
||||
|
||||
**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.
|
||||
|
||||
@@ -631,7 +700,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Task Venture Capital GmbH
|
||||
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@git.zone/tsdocker',
|
||||
version: '2.0.0',
|
||||
description: 'develop npm modules cross platform with docker'
|
||||
version: '2.2.8',
|
||||
description: 'A comprehensive Docker build tool for TypeScript projects with multi-arch support, multi-registry push, and CI-safe session isolation.'
|
||||
}
|
||||
|
||||
@@ -266,12 +266,15 @@ export class Dockerfile {
|
||||
public static async buildDockerfiles(
|
||||
sortedArrayArg: Dockerfile[],
|
||||
session: TsDockerSession,
|
||||
options?: { platform?: string; timeout?: number; noCache?: boolean; verbose?: boolean; isRootless?: boolean; parallel?: boolean; parallelConcurrency?: number },
|
||||
options?: { platform?: string; timeout?: number; noCache?: boolean; pull?: boolean; verbose?: boolean; isRootless?: boolean; parallel?: boolean; parallelConcurrency?: number; onRegistryStarted?: () => Promise<void>; onBeforeRegistryStop?: () => Promise<void> },
|
||||
): Promise<Dockerfile[]> {
|
||||
const total = sortedArrayArg.length;
|
||||
const overallStart = Date.now();
|
||||
|
||||
await Dockerfile.startLocalRegistry(session, options?.isRootless);
|
||||
if (options?.onRegistryStarted) {
|
||||
await options.onRegistryStarted();
|
||||
}
|
||||
|
||||
try {
|
||||
if (options?.parallel) {
|
||||
@@ -351,6 +354,9 @@ export class Dockerfile {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (options?.onBeforeRegistryStop) {
|
||||
await options.onBeforeRegistryStop();
|
||||
}
|
||||
await Dockerfile.stopLocalRegistry(session);
|
||||
}
|
||||
|
||||
@@ -662,13 +668,14 @@ export class Dockerfile {
|
||||
/**
|
||||
* Builds the Dockerfile
|
||||
*/
|
||||
public async build(options?: { platform?: string; timeout?: number; noCache?: boolean; verbose?: boolean }): Promise<number> {
|
||||
public async build(options?: { platform?: string; timeout?: number; noCache?: boolean; pull?: boolean; verbose?: boolean }): Promise<number> {
|
||||
const startTime = Date.now();
|
||||
const buildArgsString = await Dockerfile.getDockerBuildArgs(this.managerRef);
|
||||
const config = this.managerRef.config;
|
||||
const platformOverride = options?.platform;
|
||||
const timeout = options?.timeout;
|
||||
const noCacheFlag = options?.noCache ? ' --no-cache' : '';
|
||||
const pullFlag = options?.pull !== false ? ' --pull' : '';
|
||||
const verbose = options?.verbose ?? false;
|
||||
|
||||
let buildContextFlag = '';
|
||||
@@ -683,23 +690,24 @@ export class Dockerfile {
|
||||
}
|
||||
|
||||
let buildCommand: string;
|
||||
const builderFlag = this.managerRef.currentBuilderName ? ` --builder ${this.managerRef.currentBuilderName}` : '';
|
||||
|
||||
if (platformOverride) {
|
||||
// Single platform override via buildx
|
||||
buildCommand = `docker buildx build --progress=plain --platform ${platformOverride}${noCacheFlag}${buildContextFlag} --load -t ${this.buildTag} -f ${this.filePath} ${buildArgsString} .`;
|
||||
buildCommand = `docker buildx build${builderFlag} --progress=plain --platform ${platformOverride}${noCacheFlag}${pullFlag}${buildContextFlag} --load -t ${this.buildTag} -f ${this.filePath} ${buildArgsString} .`;
|
||||
logger.log('info', `Build: buildx --platform ${platformOverride} --load`);
|
||||
} else if (config.platforms && config.platforms.length > 1) {
|
||||
// Multi-platform build using buildx — always push to local registry
|
||||
const platformString = config.platforms.join(',');
|
||||
const registryHost = this.session?.config.registryHost || 'localhost:5234';
|
||||
const localTag = `${registryHost}/${this.buildTag}`;
|
||||
buildCommand = `docker buildx build --progress=plain --platform ${platformString}${noCacheFlag}${buildContextFlag} -t ${localTag} -f ${this.filePath} ${buildArgsString} --push .`;
|
||||
buildCommand = `docker buildx build${builderFlag} --progress=plain --platform ${platformString}${noCacheFlag}${pullFlag}${buildContextFlag} -t ${localTag} -f ${this.filePath} ${buildArgsString} --push .`;
|
||||
this.localRegistryTag = localTag;
|
||||
logger.log('info', `Build: buildx --platform ${platformString} --push to local registry`);
|
||||
} else {
|
||||
// Standard build
|
||||
const versionLabel = this.managerRef.projectInfo?.npm?.version || 'unknown';
|
||||
buildCommand = `docker build --progress=plain --label="version=${versionLabel}"${noCacheFlag} -t ${this.buildTag} -f ${this.filePath} ${buildArgsString} .`;
|
||||
buildCommand = `docker build --progress=plain --label="version=${versionLabel}"${noCacheFlag}${pullFlag} -t ${this.buildTag} -f ${this.filePath} ${buildArgsString} .`;
|
||||
logger.log('info', 'Build: docker build (standard)');
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import * as fs from 'fs';
|
||||
import * as plugins from './tsdocker.plugins.js';
|
||||
import { logger } from './tsdocker.logging.js';
|
||||
import type { IGlobalConfig, IRemoteBuilder } from './interfaces/index.js';
|
||||
|
||||
const CONFIG_DIR = plugins.path.join(
|
||||
process.env.HOME || process.env.USERPROFILE || '~',
|
||||
'.git.zone',
|
||||
'tsdocker',
|
||||
);
|
||||
const CONFIG_PATH = plugins.path.join(CONFIG_DIR, 'config.json');
|
||||
|
||||
const DEFAULT_CONFIG: IGlobalConfig = {
|
||||
remoteBuilders: [],
|
||||
};
|
||||
|
||||
export class GlobalConfig {
|
||||
static getConfigPath(): string {
|
||||
return CONFIG_PATH;
|
||||
}
|
||||
|
||||
static load(): IGlobalConfig {
|
||||
try {
|
||||
const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
||||
const parsed = JSON.parse(raw);
|
||||
return {
|
||||
...DEFAULT_CONFIG,
|
||||
...parsed,
|
||||
};
|
||||
} catch {
|
||||
return { ...DEFAULT_CONFIG };
|
||||
}
|
||||
}
|
||||
|
||||
static save(config: IGlobalConfig): void {
|
||||
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
||||
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
||||
}
|
||||
|
||||
static addBuilder(builder: IRemoteBuilder): void {
|
||||
const config = GlobalConfig.load();
|
||||
const existing = config.remoteBuilders.findIndex((b) => b.name === builder.name);
|
||||
if (existing >= 0) {
|
||||
config.remoteBuilders[existing] = builder;
|
||||
logger.log('info', `Updated remote builder: ${builder.name}`);
|
||||
} else {
|
||||
config.remoteBuilders.push(builder);
|
||||
logger.log('info', `Added remote builder: ${builder.name}`);
|
||||
}
|
||||
GlobalConfig.save(config);
|
||||
}
|
||||
|
||||
static removeBuilder(name: string): void {
|
||||
const config = GlobalConfig.load();
|
||||
const before = config.remoteBuilders.length;
|
||||
config.remoteBuilders = config.remoteBuilders.filter((b) => b.name !== name);
|
||||
if (config.remoteBuilders.length < before) {
|
||||
logger.log('info', `Removed remote builder: ${name}`);
|
||||
} else {
|
||||
logger.log('warn', `Remote builder not found: ${name}`);
|
||||
}
|
||||
GlobalConfig.save(config);
|
||||
}
|
||||
|
||||
static getBuilders(): IRemoteBuilder[] {
|
||||
return GlobalConfig.load().remoteBuilders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns remote builders that match any of the requested platforms
|
||||
*/
|
||||
static getBuildersForPlatforms(platforms: string[]): IRemoteBuilder[] {
|
||||
const builders = GlobalConfig.getBuilders();
|
||||
return builders.filter((b) => platforms.includes(b.platform));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import * as plugins from './tsdocker.plugins.js';
|
||||
import { logger } from './tsdocker.logging.js';
|
||||
import type { IRemoteBuilder } from './interfaces/index.js';
|
||||
|
||||
const smartshellInstance = new plugins.smartshell.Smartshell({
|
||||
executor: 'bash',
|
||||
});
|
||||
|
||||
/**
|
||||
* Manages SSH reverse tunnels for remote builder nodes.
|
||||
* Opens tunnels so that the local staging registry (localhost:<port>)
|
||||
* is accessible as localhost:<port> on each remote machine.
|
||||
*/
|
||||
export class SshTunnelManager {
|
||||
private tunnelPids: number[] = [];
|
||||
|
||||
/**
|
||||
* Opens a reverse SSH tunnel to make localPort accessible on the remote machine.
|
||||
* ssh -f -N -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes
|
||||
* -R <localPort>:localhost:<localPort> [-i keyPath] user@host
|
||||
*/
|
||||
async openTunnel(builder: IRemoteBuilder, localPort: number): Promise<void> {
|
||||
const keyOpt = builder.sshKeyPath ? `-i ${builder.sshKeyPath} ` : '';
|
||||
const cmd = [
|
||||
'ssh -f -N',
|
||||
'-o StrictHostKeyChecking=no',
|
||||
'-o ExitOnForwardFailure=yes',
|
||||
`-R ${localPort}:localhost:${localPort}`,
|
||||
`${keyOpt}${builder.host}`,
|
||||
].join(' ');
|
||||
|
||||
logger.log('info', `Opening SSH tunnel to ${builder.host} for port ${localPort}...`);
|
||||
const result = await smartshellInstance.exec(cmd);
|
||||
|
||||
if (result.exitCode !== 0) {
|
||||
throw new Error(
|
||||
`Failed to open SSH tunnel to ${builder.host}: ${result.stderr || 'unknown error'}`
|
||||
);
|
||||
}
|
||||
|
||||
// Find the PID of the tunnel process we just started
|
||||
const pidResult = await smartshellInstance.exec(
|
||||
`pgrep -f "ssh.*-R ${localPort}:localhost:${localPort}.*${builder.host}" | tail -1`
|
||||
);
|
||||
if (pidResult.exitCode === 0 && pidResult.stdout.trim()) {
|
||||
const pid = parseInt(pidResult.stdout.trim(), 10);
|
||||
if (!isNaN(pid)) {
|
||||
this.tunnelPids.push(pid);
|
||||
logger.log('ok', `SSH tunnel to ${builder.host} established (PID ${pid})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens tunnels for all provided remote builders
|
||||
*/
|
||||
async openTunnels(builders: IRemoteBuilder[], localPort: number): Promise<void> {
|
||||
for (const builder of builders) {
|
||||
await this.openTunnel(builder, localPort);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all tunnel processes
|
||||
*/
|
||||
async closeAll(): Promise<void> {
|
||||
for (const pid of this.tunnelPids) {
|
||||
try {
|
||||
process.kill(pid, 'SIGTERM');
|
||||
logger.log('info', `Closed SSH tunnel (PID ${pid})`);
|
||||
} catch {
|
||||
// Process may have already exited
|
||||
}
|
||||
}
|
||||
this.tunnelPids = [];
|
||||
}
|
||||
}
|
||||
+107
-10
@@ -8,7 +8,9 @@ import { TsDockerCache } from './classes.tsdockercache.js';
|
||||
import { DockerContext } from './classes.dockercontext.js';
|
||||
import { TsDockerSession } from './classes.tsdockersession.js';
|
||||
import { RegistryCopy } from './classes.registrycopy.js';
|
||||
import type { ITsDockerConfig, IBuildCommandOptions } from './interfaces/index.js';
|
||||
import { GlobalConfig } from './classes.globalconfig.js';
|
||||
import { SshTunnelManager } from './classes.sshtunnel.js';
|
||||
import type { ITsDockerConfig, IBuildCommandOptions, IRemoteBuilder } from './interfaces/index.js';
|
||||
|
||||
const smartshellInstance = new plugins.smartshell.Smartshell({
|
||||
executor: 'bash',
|
||||
@@ -23,7 +25,10 @@ export class TsDockerManager {
|
||||
public projectInfo: any;
|
||||
public dockerContext: DockerContext;
|
||||
public session!: TsDockerSession;
|
||||
public currentBuilderName?: string;
|
||||
private dockerfiles: Dockerfile[] = [];
|
||||
private activeRemoteBuilders: IRemoteBuilder[] = [];
|
||||
private sshTunnelManager?: SshTunnelManager;
|
||||
|
||||
constructor(config: ITsDockerConfig) {
|
||||
this.config = config;
|
||||
@@ -45,7 +50,7 @@ export class TsDockerManager {
|
||||
|
||||
// Load project info
|
||||
try {
|
||||
const projectinfoInstance = new plugins.projectinfo.ProjectInfo(paths.cwd);
|
||||
const projectinfoInstance = await plugins.projectinfo.ProjectInfo.create(paths.cwd);
|
||||
this.projectInfo = {
|
||||
npm: {
|
||||
name: projectinfoInstance.npm.name,
|
||||
@@ -235,6 +240,7 @@ export class TsDockerManager {
|
||||
const total = toBuild.length;
|
||||
const overallStart = Date.now();
|
||||
await Dockerfile.startLocalRegistry(this.session, this.dockerContext.contextInfo?.isRootless);
|
||||
await this.openRemoteTunnels();
|
||||
|
||||
try {
|
||||
if (options?.parallel) {
|
||||
@@ -261,6 +267,7 @@ export class TsDockerManager {
|
||||
platform: options?.platform,
|
||||
timeout: options?.timeout,
|
||||
noCache: options?.noCache,
|
||||
pull: options?.pull,
|
||||
verbose: options?.verbose,
|
||||
});
|
||||
logger.log('ok', `${progress} Built ${df.cleanTag} in ${formatDuration(elapsed)}`);
|
||||
@@ -306,6 +313,7 @@ export class TsDockerManager {
|
||||
platform: options?.platform,
|
||||
timeout: options?.timeout,
|
||||
noCache: options?.noCache,
|
||||
pull: options?.pull,
|
||||
verbose: options?.verbose,
|
||||
});
|
||||
logger.log('ok', `${progress} Built ${dockerfileArg.cleanTag} in ${formatDuration(elapsed)}`);
|
||||
@@ -332,6 +340,7 @@ export class TsDockerManager {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await this.closeRemoteTunnels();
|
||||
await Dockerfile.stopLocalRegistry(this.session);
|
||||
}
|
||||
|
||||
@@ -343,10 +352,13 @@ export class TsDockerManager {
|
||||
platform: options?.platform,
|
||||
timeout: options?.timeout,
|
||||
noCache: options?.noCache,
|
||||
pull: options?.pull,
|
||||
verbose: options?.verbose,
|
||||
isRootless: this.dockerContext.contextInfo?.isRootless,
|
||||
parallel: options?.parallel,
|
||||
parallelConcurrency: options?.parallelConcurrency,
|
||||
onRegistryStarted: () => this.openRemoteTunnels(),
|
||||
onBeforeRegistryStop: () => this.closeRemoteTunnels(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -373,35 +385,120 @@ export class TsDockerManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures Docker buildx is set up for multi-architecture builds
|
||||
* Ensures Docker buildx is set up for multi-architecture builds.
|
||||
* When remote builders are configured in the global config, creates a multi-node
|
||||
* builder with native nodes instead of relying on QEMU emulation.
|
||||
*/
|
||||
private async ensureBuildx(): Promise<void> {
|
||||
const builderName = this.dockerContext.getBuilderName() + (this.session?.config.builderSuffix || '');
|
||||
const platforms = this.config.platforms?.join(', ') || 'default';
|
||||
logger.log('info', `Setting up Docker buildx [${platforms}]...`);
|
||||
logger.log('info', `Builder: ${builderName}`);
|
||||
|
||||
// Check for remote builders matching our target platforms
|
||||
const requestedPlatforms = this.config.platforms || ['linux/amd64'];
|
||||
const remoteBuilders = GlobalConfig.getBuildersForPlatforms(requestedPlatforms);
|
||||
|
||||
if (remoteBuilders.length > 0) {
|
||||
await this.ensureBuildxWithRemoteNodes(builderName, requestedPlatforms, remoteBuilders);
|
||||
} else {
|
||||
await this.ensureBuildxLocal(builderName);
|
||||
}
|
||||
|
||||
this.currentBuilderName = builderName;
|
||||
logger.log('ok', `Docker buildx ready (builder: ${builderName}, platforms: ${platforms})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a multi-node buildx builder with local + remote SSH nodes.
|
||||
*/
|
||||
private async ensureBuildxWithRemoteNodes(
|
||||
builderName: string,
|
||||
requestedPlatforms: string[],
|
||||
remoteBuilders: IRemoteBuilder[],
|
||||
): Promise<void> {
|
||||
const remotePlatforms = new Set(remoteBuilders.map((b) => b.platform));
|
||||
const localPlatforms = requestedPlatforms.filter((p) => !remotePlatforms.has(p));
|
||||
|
||||
logger.log('info', `Remote builders: ${remoteBuilders.map((b) => `${b.name} (${b.platform} @ ${b.host})`).join(', ')}`);
|
||||
if (localPlatforms.length > 0) {
|
||||
logger.log('info', `Local platforms: ${localPlatforms.join(', ')}`);
|
||||
}
|
||||
|
||||
// Always recreate the builder to ensure correct node topology
|
||||
await smartshellInstance.execSilent(`docker buildx rm ${builderName} 2>/dev/null || true`);
|
||||
|
||||
// Create the local node
|
||||
const localPlatformFlag = localPlatforms.length > 0 ? ` --platform ${localPlatforms.join(',')}` : '';
|
||||
await smartshellInstance.exec(
|
||||
`docker buildx create --name ${builderName} --driver docker-container --driver-opt network=host${localPlatformFlag}`
|
||||
);
|
||||
|
||||
// Append remote nodes
|
||||
for (const builder of remoteBuilders) {
|
||||
logger.log('info', `Appending remote node: ${builder.name} (${builder.platform}) via ssh://${builder.host}`);
|
||||
const appendResult = await smartshellInstance.exec(
|
||||
`docker buildx create --append --name ${builderName} --driver docker-container --driver-opt network=host --platform ${builder.platform} --node ${builder.name} ssh://${builder.host}`
|
||||
);
|
||||
if (appendResult.exitCode !== 0) {
|
||||
throw new Error(`Failed to append remote builder ${builder.name}: ${appendResult.stderr}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Bootstrap all nodes
|
||||
await smartshellInstance.exec(`docker buildx inspect --builder ${builderName} --bootstrap`);
|
||||
|
||||
// Store active remote builders for SSH tunnel setup during build
|
||||
this.activeRemoteBuilders = remoteBuilders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single-node local buildx builder (original behavior, uses QEMU for cross-platform).
|
||||
*/
|
||||
private async ensureBuildxLocal(builderName: string): Promise<void> {
|
||||
const inspectResult = await smartshellInstance.exec(`docker buildx inspect ${builderName} 2>/dev/null`);
|
||||
|
||||
if (inspectResult.exitCode !== 0) {
|
||||
logger.log('info', 'Creating new buildx builder with host network...');
|
||||
await smartshellInstance.exec(
|
||||
`docker buildx create --name ${builderName} --driver docker-container --driver-opt network=host --use`
|
||||
`docker buildx create --name ${builderName} --driver docker-container --driver-opt network=host`
|
||||
);
|
||||
await smartshellInstance.exec('docker buildx inspect --bootstrap');
|
||||
await smartshellInstance.exec(`docker buildx inspect --builder ${builderName} --bootstrap`);
|
||||
} else {
|
||||
const inspectOutput = inspectResult.stdout || '';
|
||||
if (!inspectOutput.includes('network=host')) {
|
||||
logger.log('info', 'Recreating buildx builder with host network (migration)...');
|
||||
await smartshellInstance.exec(`docker buildx rm ${builderName} 2>/dev/null`);
|
||||
await smartshellInstance.exec(
|
||||
`docker buildx create --name ${builderName} --driver docker-container --driver-opt network=host --use`
|
||||
`docker buildx create --name ${builderName} --driver docker-container --driver-opt network=host`
|
||||
);
|
||||
await smartshellInstance.exec('docker buildx inspect --bootstrap');
|
||||
} else {
|
||||
await smartshellInstance.exec(`docker buildx use ${builderName}`);
|
||||
await smartshellInstance.exec(`docker buildx inspect --builder ${builderName} --bootstrap`);
|
||||
}
|
||||
}
|
||||
logger.log('ok', `Docker buildx ready (builder: ${builderName}, platforms: ${platforms})`);
|
||||
this.activeRemoteBuilders = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens SSH reverse tunnels for remote builders so they can reach the local registry.
|
||||
*/
|
||||
private async openRemoteTunnels(): Promise<void> {
|
||||
if (this.activeRemoteBuilders.length === 0) return;
|
||||
|
||||
this.sshTunnelManager = new SshTunnelManager();
|
||||
await this.sshTunnelManager.openTunnels(
|
||||
this.activeRemoteBuilders,
|
||||
this.session.config.registryPort,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes any active SSH tunnels.
|
||||
*/
|
||||
private async closeRemoteTunnels(): Promise<void> {
|
||||
if (this.sshTunnelManager) {
|
||||
await this.sshTunnelManager.closeAll();
|
||||
this.sshTunnelManager = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ import { logger } from './tsdocker.logging.js';
|
||||
|
||||
export interface ISessionConfig {
|
||||
sessionId: string;
|
||||
projectHash: string;
|
||||
registryPort: number;
|
||||
registryHost: string;
|
||||
registryContainerName: string;
|
||||
@@ -17,8 +18,8 @@ export interface ISessionConfig {
|
||||
* Generates unique ports, container names, and builder names so that
|
||||
* concurrent CI jobs on the same Docker host don't collide.
|
||||
*
|
||||
* In local (non-CI) dev the builder suffix is empty, preserving the
|
||||
* persistent builder behavior.
|
||||
* In local (non-CI) dev the builder suffix contains a project hash so
|
||||
* that concurrent runs in different project directories use separate builders.
|
||||
*/
|
||||
export class TsDockerSession {
|
||||
public config: ISessionConfig;
|
||||
@@ -34,16 +35,18 @@ export class TsDockerSession {
|
||||
public static async create(): Promise<TsDockerSession> {
|
||||
const sessionId =
|
||||
process.env.TSDOCKER_SESSION_ID || crypto.randomBytes(4).toString('hex');
|
||||
const projectHash = crypto.createHash('sha256').update(process.cwd()).digest('hex').substring(0, 8);
|
||||
|
||||
const registryPort = await TsDockerSession.allocatePort();
|
||||
const registryHost = `localhost:${registryPort}`;
|
||||
const registryContainerName = `tsdocker-registry-${sessionId}`;
|
||||
|
||||
const { isCI, ciSystem } = TsDockerSession.detectCI();
|
||||
const builderSuffix = isCI ? `-${sessionId}` : '';
|
||||
const builderSuffix = isCI ? `-${projectHash}-${sessionId}` : `-${projectHash}`;
|
||||
|
||||
const config: ISessionConfig = {
|
||||
sessionId,
|
||||
projectHash,
|
||||
registryPort,
|
||||
registryHost,
|
||||
registryContainerName,
|
||||
@@ -99,9 +102,10 @@ export class TsDockerSession {
|
||||
logger.log('info', '=== TSDOCKER SESSION ===');
|
||||
logger.log('info', `Session ID: ${c.sessionId}`);
|
||||
logger.log('info', `Registry: ${c.registryHost} (container: ${c.registryContainerName})`);
|
||||
logger.log('info', `Project hash: ${c.projectHash}`);
|
||||
logger.log('info', `Builder suffix: ${c.builderSuffix}`);
|
||||
if (c.isCI) {
|
||||
logger.log('info', `CI detected: ${c.ciSystem}`);
|
||||
logger.log('info', `Builder suffix: ${c.builderSuffix}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ export interface IBuildCommandOptions {
|
||||
platform?: string; // Single platform override (e.g., 'linux/arm64')
|
||||
timeout?: number; // Build timeout in seconds
|
||||
noCache?: boolean; // Force rebuild without Docker layer cache (--no-cache)
|
||||
pull?: boolean; // Pull fresh base images before building (default: true)
|
||||
cached?: boolean; // Skip builds when Dockerfile content hasn't changed
|
||||
verbose?: boolean; // Stream raw docker build output (default: silent)
|
||||
context?: string; // Explicit Docker context name (--context flag)
|
||||
@@ -95,3 +96,20 @@ export interface IDockerContextInfo {
|
||||
dockerHost?: string; // value of DOCKER_HOST env var, if set
|
||||
topology?: 'socket-mount' | 'dind' | 'local';
|
||||
}
|
||||
|
||||
/**
|
||||
* A remote builder node for native cross-platform builds
|
||||
*/
|
||||
export interface IRemoteBuilder {
|
||||
name: string; // e.g., "arm64-builder"
|
||||
host: string; // e.g., "armbuilder@192.168.190.216"
|
||||
platform: string; // e.g., "linux/arm64"
|
||||
sshKeyPath?: string; // e.g., "~/.ssh/id_ed25519"
|
||||
}
|
||||
|
||||
/**
|
||||
* Global tsdocker configuration stored at ~/.git.zone/tsdocker/config.json
|
||||
*/
|
||||
export interface IGlobalConfig {
|
||||
remoteBuilders: IRemoteBuilder[];
|
||||
}
|
||||
|
||||
+94
-1
@@ -7,6 +7,7 @@ import * as ConfigModule from './tsdocker.config.js';
|
||||
import { logger, ora } from './tsdocker.logging.js';
|
||||
import { TsDockerManager } from './classes.tsdockermanager.js';
|
||||
import { DockerContext } from './classes.dockercontext.js';
|
||||
import { GlobalConfig } from './classes.globalconfig.js';
|
||||
import type { IBuildCommandOptions } from './interfaces/index.js';
|
||||
import { commitinfo } from './00_commitinfo_data.js';
|
||||
|
||||
@@ -33,12 +34,14 @@ COMMANDS
|
||||
test [flags] Build and run container test scripts
|
||||
login Authenticate with configured registries
|
||||
list List discovered Dockerfiles
|
||||
config <subcommand> [flags] Manage global tsdocker configuration
|
||||
clean [-y] [--all] Interactive Docker resource cleanup
|
||||
|
||||
BUILD / PUSH OPTIONS
|
||||
--platform=<p> Target platform (e.g. linux/arm64)
|
||||
--timeout=<s> Build timeout in seconds
|
||||
--no-cache Rebuild without Docker layer cache
|
||||
--no-pull Skip pulling latest base images (use cached)
|
||||
--cached Skip builds when Dockerfile is unchanged
|
||||
--verbose Stream raw docker build output
|
||||
--parallel[=<n>] Parallel builds (optional concurrency limit)
|
||||
@@ -52,8 +55,19 @@ CLEAN OPTIONS
|
||||
-y Auto-confirm all prompts
|
||||
--all Include all images and volumes (not just dangling)
|
||||
|
||||
CONFIG SUBCOMMANDS
|
||||
add-builder Add a remote builder node
|
||||
--name=<n> Builder name (e.g. arm64-builder)
|
||||
--host=<h> SSH host (e.g. user@192.168.1.100)
|
||||
--platform=<p> Platform (e.g. linux/arm64)
|
||||
--ssh-key=<path> SSH key path (optional)
|
||||
remove-builder Remove a remote builder by name
|
||||
--name=<n> Builder name to remove
|
||||
list-builders List all configured remote builders
|
||||
show Show full global config
|
||||
|
||||
CONFIGURATION
|
||||
Configure via npmextra.json under the "@git.zone/tsdocker" key:
|
||||
Configure via .smartconfig.json under the "@git.zone/tsdocker" key:
|
||||
|
||||
registries Array of registry URLs to push to
|
||||
registryRepoMap Map of registry URL to repo path overrides
|
||||
@@ -62,12 +76,17 @@ CONFIGURATION
|
||||
push Boolean, auto-push after build
|
||||
testDir Directory containing test_*.sh scripts
|
||||
|
||||
Global config is stored at ~/.git.zone/tsdocker/config.json
|
||||
and managed via the "config" command.
|
||||
|
||||
EXAMPLES
|
||||
tsdocker build
|
||||
tsdocker build Dockerfile_app --platform=linux/arm64
|
||||
tsdocker push --registry=ghcr.io
|
||||
tsdocker test --verbose
|
||||
tsdocker clean -y --all
|
||||
tsdocker config add-builder --name=arm64 --host=user@host --platform=linux/arm64
|
||||
tsdocker config list-builders
|
||||
`;
|
||||
console.log(manPage);
|
||||
};
|
||||
@@ -102,6 +121,8 @@ export let run = () => {
|
||||
if (argvArg.cache === false) {
|
||||
buildOptions.noCache = true;
|
||||
}
|
||||
// --pull is default true; --no-pull sets pull=false
|
||||
buildOptions.pull = argvArg.pull !== false;
|
||||
if (argvArg.cached) {
|
||||
buildOptions.cached = true;
|
||||
}
|
||||
@@ -152,6 +173,7 @@ export let run = () => {
|
||||
if (argvArg.cache === false) {
|
||||
buildOptions.noCache = true;
|
||||
}
|
||||
buildOptions.pull = argvArg.pull !== false;
|
||||
if (argvArg.verbose) {
|
||||
buildOptions.verbose = true;
|
||||
}
|
||||
@@ -225,6 +247,7 @@ export let run = () => {
|
||||
if (argvArg.cache === false) {
|
||||
buildOptions.noCache = true;
|
||||
}
|
||||
buildOptions.pull = argvArg.pull !== false;
|
||||
if (argvArg.cached) {
|
||||
buildOptions.cached = true;
|
||||
}
|
||||
@@ -280,6 +303,76 @@ export let run = () => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Manage global tsdocker configuration (remote builders, etc.)
|
||||
* Usage: tsdocker config <subcommand> [--name=...] [--host=...] [--platform=...] [--ssh-key=...]
|
||||
*/
|
||||
tsdockerCli.addCommand('config').subscribe(async argvArg => {
|
||||
try {
|
||||
const subcommand = argvArg._[1] as string;
|
||||
|
||||
switch (subcommand) {
|
||||
case 'add-builder': {
|
||||
const name = argvArg.name as string;
|
||||
const host = argvArg.host as string;
|
||||
const platform = argvArg.platform as string;
|
||||
const sshKeyPath = argvArg['ssh-key'] as string | undefined;
|
||||
|
||||
if (!name || !host || !platform) {
|
||||
logger.log('error', 'Required: --name, --host, --platform');
|
||||
logger.log('info', 'Usage: tsdocker config add-builder --name=arm64-builder --host=user@host --platform=linux/arm64 [--ssh-key=~/.ssh/id_ed25519]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
GlobalConfig.addBuilder({ name, host, platform, sshKeyPath });
|
||||
logger.log('success', `Remote builder "${name}" configured: ${platform} via ssh://${host}`);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'remove-builder': {
|
||||
const name = argvArg.name as string;
|
||||
if (!name) {
|
||||
logger.log('error', 'Required: --name');
|
||||
logger.log('info', 'Usage: tsdocker config remove-builder --name=arm64-builder');
|
||||
process.exit(1);
|
||||
}
|
||||
GlobalConfig.removeBuilder(name);
|
||||
logger.log('success', `Remote builder "${name}" removed`);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'list-builders': {
|
||||
const builders = GlobalConfig.getBuilders();
|
||||
if (builders.length === 0) {
|
||||
logger.log('info', 'No remote builders configured');
|
||||
} else {
|
||||
logger.log('info', `${builders.length} remote builder(s):`);
|
||||
for (const b of builders) {
|
||||
const keyInfo = b.sshKeyPath ? ` (key: ${b.sshKeyPath})` : '';
|
||||
logger.log('info', ` ${b.name}: ${b.platform} via ssh://${b.host}${keyInfo}`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'show': {
|
||||
const config = GlobalConfig.load();
|
||||
logger.log('info', `Config file: ${GlobalConfig.getConfigPath()}`);
|
||||
console.log(JSON.stringify(config, null, 2));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
logger.log('error', `Unknown config subcommand: ${subcommand || '(none)'}`);
|
||||
logger.log('info', 'Available: add-builder, remove-builder, list-builders, show');
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.log('error', `Config failed: ${(err as Error).message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
tsdockerCli.addCommand('clean').subscribe(async argvArg => {
|
||||
try {
|
||||
const autoYes = !!argvArg.y;
|
||||
|
||||
@@ -3,8 +3,8 @@ import * as paths from './tsdocker.paths.js';
|
||||
import type { ITsDockerConfig } from './interfaces/index.js';
|
||||
|
||||
const buildConfig = async (): Promise<ITsDockerConfig> => {
|
||||
const npmextra = new plugins.npmextra.Npmextra(paths.cwd);
|
||||
const config = npmextra.dataFor<ITsDockerConfig>('@git.zone/tsdocker', {
|
||||
const smartconfig = new plugins.smartconfig.Smartconfig(paths.cwd);
|
||||
const config = smartconfig.dataFor<ITsDockerConfig>('@git.zone/tsdocker', {
|
||||
registries: [],
|
||||
registryRepoMap: {},
|
||||
buildArgEnvMap: {},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// push.rocks scope
|
||||
import * as lik from '@push.rocks/lik';
|
||||
import * as npmextra from '@push.rocks/npmextra';
|
||||
import * as smartconfig from '@push.rocks/smartconfig';
|
||||
import * as path from 'path';
|
||||
import * as projectinfo from '@push.rocks/projectinfo';
|
||||
import * as smartcli from '@push.rocks/smartcli';
|
||||
@@ -16,7 +16,7 @@ export const smartfs = new SmartFs(new SmartFsProviderNode());
|
||||
|
||||
export {
|
||||
lik,
|
||||
npmextra,
|
||||
smartconfig,
|
||||
path,
|
||||
projectinfo,
|
||||
smartcli,
|
||||
|
||||
+1
-2
@@ -5,8 +5,7 @@
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {}
|
||||
"useDefineForClassFields": false
|
||||
},
|
||||
"exclude": ["dist_*/**/*.d.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user