Compare commits

...

30 Commits

Author SHA1 Message Date
9805324746 v1.4.1 2026-01-20 12:28:28 +00:00
808066d8c3 fix(docs): update README: expand usage, installation, quick start, features, troubleshooting and migration notes 2026-01-20 12:28:28 +00:00
6922d19454 v1.4.0 2026-01-20 09:33:31 +00:00
e1492f8ec4 feat(tsdocker): add multi-registry and multi-arch Docker build/push/pull manager, registry storage, Dockerfile handling, and new CLI commands 2026-01-20 09:33:31 +00:00
e9a12f1c17 v1.3.0 2026-01-19 22:08:59 +00:00
6995010a2c 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 2026-01-19 22:08:59 +00:00
e0cbc9cfec v1.2.43 2025-12-13 09:44:37 +00:00
c538e6b10b fix(packaging): Rename package scope to @git.zone and migrate deps/CI; pin pnpm and enable ESM packaging 2025-12-13 09:44:37 +00:00
c31df766fc v1.2.42 2025-11-22 18:08:31 +00:00
0c626c20e7 fix(package.json): Add packageManager field to package.json to pin pnpm version 2025-11-22 18:08:31 +00:00
c07f10b97b v1.2.41 2025-11-22 18:07:39 +00:00
08d32f0370 fix(core): Migrate to @git.zone / @push.rocks packages, replace smartfile with smartfs and adapt filesystem usage; update dev deps and remove CI/lint config 2025-11-22 18:07:39 +00:00
ac386f01e0 1.2.40 2021-09-30 11:14:44 +02:00
08ead4258f fix(core): update 2021-09-30 11:14:43 +02:00
f930f3a6a7 1.2.39 2019-05-28 11:31:40 +02:00
b6d4a76c70 fix(core): update 2019-05-28 11:31:39 +02:00
9a7ecd27e5 1.2.38 2019-05-27 15:50:38 +02:00
11b70b0ddf fix(core): update 2019-05-27 15:50:38 +02:00
79f8cb5e0e 1.2.37 2019-05-27 13:14:12 +02:00
3e1286b9ac fix(core): update 2019-05-27 13:14:12 +02:00
a8ae886959 1.2.36 2019-05-21 17:43:38 +02:00
da1c977a62 fix(core): update 2019-05-21 17:43:37 +02:00
b99b55a05b 1.2.35 2019-05-21 17:41:25 +02:00
133bf0abe5 fix(core): update 2019-05-21 17:41:25 +02:00
df260bbab9 1.2.34 2019-05-21 17:35:37 +02:00
080bd2bc48 fix(core): update 2019-05-21 17:35:36 +02:00
a0032b8168 1.2.33 2019-05-12 18:52:23 +02:00
a09efd1125 fix(core): update 2019-05-12 18:52:23 +02:00
4ce28c7979 1.2.32 2019-05-12 18:30:33 +02:00
415eaea56e fix(bin name): change from npmdocker to tsdocker 2019-05-12 18:30:32 +02:00
34 changed files with 11204 additions and 2351 deletions

View File

@@ -0,0 +1,66 @@
name: Default (not tags)
on:
push:
tags-ignore:
- '**'
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_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Install pnpm and npmci
run: |
pnpm install -g pnpm
pnpm install -g @ship.zone/npmci
- name: Run npm prepare
run: npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build

View File

@@ -0,0 +1,124 @@
name: Default (tags)
on:
push:
tags:
- '*'
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_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build
release:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Release
run: |
npmci node install stable
npmci npm publish
metadata:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
continue-on-error: true
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Code quality
run: |
npmci command npm install -g typescript
npmci npm install
- name: Trigger
run: npmci trigger
- name: Build docs and upload artifacts
run: |
npmci node install stable
npmci npm install
pnpm install -g @git.zone/tsdoc
npmci command tsdoc
continue-on-error: true

20
.gitignore vendored
View File

@@ -3,18 +3,22 @@
# artifacts
coverage/
public/
pages/
# installs
node_modules/
# caches and builds
# caches
.yarn/
.cache/
dist/
dist_web/
dist_serve/
dist_ts_web/
.rpt2_cache
# custom
test/
# builds
dist/
dist_*/
# AI
.claude/
.serena/
#------# custom
test

View File

@@ -1,69 +0,0 @@
image: hosttoday/ht-docker-dbase:npmci
services:
- docker:dind
stages:
- mirror
- test
- release
- trigger
- pages
mirror:
image: hosttoday/ht-docker-node:npmci
stage: mirror
script:
- npmci git mirror
tags:
- docker
test:
stage: test
script:
- npmci node install stable
- npmci npm install
- npmci npm test
tags:
- docker
- lossless
- priv
release:
stage: release
environment: npmjs-com_registry
script:
- npmci npm prepare
- npmci npm publish
only:
- tags
tags:
- docker
- lossless
- priv
trigger:
stage: trigger
script:
- npmci trigger
only:
- tags
tags:
- docker
- lossless
- priv
pages:
image: hosttoday/ht-docker-node:npmci
stage: pages
script:
- npmci command yarn global add npmpage
- npmci command npmpage
only:
- tags
tags:
- docker
artifacts:
expire_in: 1 week
paths:
- public

11
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "npm test",
"name": "Run npm test",
"request": "launch",
"type": "node-terminal"
}
]
}

26
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,26 @@
{
"json.schemas": [
{
"fileMatch": ["/npmextra.json"],
"schema": {
"type": "object",
"properties": {
"npmci": {
"type": "object",
"description": "settings for npmci"
},
"gitzone": {
"type": "object",
"description": "settings for gitzone",
"properties": {
"projectType": {
"type": "string",
"enum": ["website", "element", "service", "npm", "wcc"]
}
}
}
}
}
}
]
}

View File

@@ -1,62 +0,0 @@
# @gitzone/npmdocker
develop npm modules cross platform with docker
## Availabililty and Links
* [npmjs.org (npm package)](https://www.npmjs.com/package/@gitzone/npmdocker)
* [gitlab.com (source)](https://gitlab.com/gitzone/npmdocker)
* [github.com (source mirror)](https://github.com/gitzone/npmdocker)
* [docs (typedoc)](https://gitzone.gitlab.io/npmdocker/)
## Status for master
[![build status](https://gitlab.com/gitzone/npmdocker/badges/master/build.svg)](https://gitlab.com/gitzone/npmdocker/commits/master)
[![coverage report](https://gitlab.com/gitzone/npmdocker/badges/master/coverage.svg)](https://gitlab.com/gitzone/npmdocker/commits/master)
[![npm downloads per month](https://img.shields.io/npm/dm/@gitzone/npmdocker.svg)](https://www.npmjs.com/package/@gitzone/npmdocker)
[![Known Vulnerabilities](https://snyk.io/test/npm/@gitzone/npmdocker/badge.svg)](https://snyk.io/test/npm/@gitzone/npmdocker)
[![TypeScript](https://img.shields.io/badge/TypeScript->=%203.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
[![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
[![JavaScript Style Guide](https://img.shields.io/badge/code%20style-prettier-ff69b4.svg)](https://prettier.io/)
## Usage
Use TypeScript for best in class instellisense.
### Why does this package exist?
Sometimes you want a clean and fresh linux environment everytime you test your package.
Usually this is the default i CI, but locally behaviour tends to defer.
### Where does it work
The npmdocker package works in everywhere where the docker cli is available. e.g.:
- docker toolbox
- native docker application
- docker in docker
- mounted docker.sock
### How do I use it?
create a npmextra.json in the project's root directory
```json
{
"npmdocker": {
"baseImage": "hosttoday/ht-docker-node:npmts",
"command": "npmci test stable",
"dockerSock": false
}
}
```
| option | description |
| ----------- | ------------------------------------------------------------------------------------- |
| baseImage | the base image that is the context for your project |
| command | the cli command to run within the the project's directory inside the docker container |
| dockersSock | wether or not the testcontainer will have access to the docker.sock of the host |
For further information read the linked docs at the top of this readme.
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy.html)
[![repo-footer](https://gitzone.gitlab.io/assets/repo-footer.svg)](https://maintainedby.lossless.com)

View File

@@ -1,6 +1,6 @@
FROM hosttoday/ht-docker-node:npmci
RUN yarn global add npmdocker
RUN yarn global add @git.zone/tsdocker
COPY ./ /workspace
WORKDIR /workspace
ENV CI=true
CMD ["npmdocker","runinside"];
CMD ["tsdocker","runinside"];

394
changelog.md Normal file
View File

@@ -0,0 +1,394 @@
# Changelog
## 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)
- Added troubleshooting and performance tips including registry login guidance and circular dependency advice
- Updated migration notes from legacy npmdocker to @git.zone/tsdocker (command and config key changes, ESM guidance)
- 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
- Add Dockerfile class with dependency-aware build order, buildx support, push/pull and test flows (new large module)
- Add DockerRegistry and RegistryStorage classes to manage registry credentials, login/logout and environment loading
- Add CLI commands: build, push, pull, test, login, list (and integrate TsDockerManager into CLI)
- Extend configuration (ITsDockerConfig) with registries, registryRepoMap, buildArgEnvMap, platforms, push and testDir; re-export as IConfig for backwards compatibility
- Add @push.rocks/lik to dependencies and import it in tsdocker.plugins
- Remove legacy speedtest command and related package.json script
- 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.
- Configuration key changed from 'npmdocker' to '@git.zone/tsdocker' in npmextra.json; update project config accordingly.
- CLI command renamed from 'npmdocker' to 'tsdocker' and entrypoint/entrypoint binary references updated.
- Project migrated to ESM: imports now use .js extensions, package main/typings point to dist_ts, and ts source uses ESM patterns — Node >=18 required.
- Build/test scripts changed to use tsx and updated test task names; CI/workflow and npmextra release registries updated.
- Dependencies/devDependencies bumped; smartfs, smartcli and tsbuild versions updated.
- Docker build command now uses '--load' and default base images/installation behavior adjusted (global install of tsdocker in image).
## 2025-12-13 - 1.2.43 - fix(packaging)
Rename package scope to @git.zone and migrate deps/CI; pin pnpm and enable ESM packaging
- Rename npm package scope from @gitzone/tsdocker to @git.zone/tsdocker (package.json, commitinfo, README, npmextra)
- Migrate devDependencies from @gitzone/_ to @git.zone/_ and ensure runtime packages use @push.rocks/\* where applicable
- Replace smartfile usage with smartfs and update code to use async smartfs.file(...).write()/delete() patterns
- Add packageManager pin for pnpm, set type: "module", add files array and pnpm.overrides in package.json
- Add tsconfig.json with NodeNext/ES2022 settings and other ESM-related adjustments
- Add Gitea CI workflows (.gitea/workflows/default_tags.yaml and default_nottags.yaml) for test, audit and release flows
- Update assets Dockerfile to reference @git.zone/tsdocker and other packaging/CI related scripts
- Update .gitignore to consolidate dist patterns and add AI/tooling excludes (.claude/, .serena/)
- Update README, readme.hints.md and changelog to document the scope rename, dependency migrations and SmartFS migration
## 2025-11-22 - 1.2.42 - fix(package.json)
Add packageManager field to package.json to pin pnpm version
- Add packageManager: "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" to package.json to lock pnpm CLI version and integrity
## 2025-11-22 - 1.2.41 - fix(core)
Migrate to @git.zone / @push.rocks packages, replace smartfile with smartfs and adapt filesystem usage; update dev deps and remove CI/lint config
- Updated devDependencies from @gitzone/_ to @git.zone/_ (tsbuild, tsrun, tstest) and bumped versions
- Re-scoped runtime dependencies from @pushrocks/_ to @push.rocks/_ and updated package versions
- Replaced deprecated smartfile usage with new async smartfs API; added SmartFs instance in ts/tsdocker.plugins.ts
- Switched sync filesystem calls to Node fs where appropriate (fs.existsSync, fs.mkdirSync) and updated code to await smartfs.file(...).write()/delete()
- Made buildDockerFile async and awaited file write/delete operations to ensure correct async flow
- Updated CLI bootstrap to require @git.zone/tsrun in cli.ts.js
- Removed tslint.json and cleaned up CI configuration (.gitlab-ci.yml content removed)
- Added readme.hints.md describing the migration and dependency changes
## 2021-09-30 - 1.2.40 - release (no code changes)
Routine release tag with no recorded source changes.
- Tagged release only (no changelogged changes).
## 2021-09-30 - 1.2.39 - fix(core)
Core maintenance updates.
- Internal core updates and maintenance.
## 2019-05-28 - 1.2.38 - fix(core)
Core maintenance updates.
- Internal core updates and maintenance.
## 2019-05-27 - 1.2.37 - fix(core)
Core maintenance updates.
- Internal core updates and maintenance.
## 2019-05-27 - 1.2.36 - fix(core)
Core maintenance updates.
- Internal core updates and maintenance.
## 2019-05-21 - 1.2.35 - fix(core)
Core maintenance updates.
- Internal core updates and maintenance.
## 2019-05-21 - 1.2.34 - fix(core)
Core maintenance updates.
- Internal core updates and maintenance.
## 2019-05-12 - 1.2.33 - fix(core)
Core maintenance updates.
- Internal core updates and maintenance.
## 2019-05-12 - 1.2.32 - fix(core)
Core maintenance updates.
- Internal core updates and maintenance.
## 2019-05-12 - 1.2.31 - fix(bin name)
Rename of the published CLI binary.
- Changed published binary name from "npmdocker" to "tsdocker".
## 2019-05-10 - 1.2.30 - fix(core)
Core maintenance updates.
- Internal core updates and maintenance.
## 2019-05-10 - 1.2.29 - fix(core)
Core maintenance updates.
- Internal core updates and maintenance.
## 2019-05-10 - 1.2.28 - fix(core)
Core maintenance updates.
- Internal core updates and maintenance.
## 2019-05-09 - 1.2.27 - fix(core)
Core maintenance updates.
- Internal core updates and maintenance.
## 2018-10-29 - 1.2.26 - fix(ci)
CI build process change.
- Removed "npmts" from the build process.
## 2018-10-29 - 1.2.25 - fix(core)
Core maintenance updates.
- Internal core updates and maintenance.
## 2018-10-28 - 1.2.24 - fix(clean)
Improved image cleanup.
- Images are now cleaned in a more thorough way.
## 2018-09-16 - 1.2.23 - fix(core)
Core maintenance updates.
- Internal core updates and maintenance.
## 2018-09-16 - 1.2.22 - fix(dependencies)
Dependency updates.
- Updated dependencies (maintenance).
## 2018-07-21 - 1.2.21 - fix(update to latest standards)
Standards/update alignment.
- Updated codebase to latest standards (general maintenance).
## 2018-05-18 - 1.2.20 - release (no code changes)
Tagged release with no recorded source changes.
- Tagged release only (no changelogged changes).
## 2018-05-18 - 1.2.19 - fix(ci)
CI improvements.
- Added a build command to package.json to support CI builds.
## 2018-05-18 - 1.2.18 - fix(package)
Packaging change for scoped publish.
- Include npmdocker under the @git.zone npm scope.
## 2018-01-24 - 1.2.18 - update
Documentation update.
- Updated package description.
## 2017-10-13 - 1.2.17 - fix(cleanup)
Cleanup behavior fix.
- Now cleans up correctly after operations.
## 2017-10-13 - 1.2.16 - update
Miscellaneous updates.
- General maintenance and updates.
## 2017-10-13 - 1.2.15 - fix(test)
Testing improvements.
- Fixed Docker testing.
## 2017-10-07 - 1.2.14 - ci
CI improvements.
- Updated CI configuration.
## 2017-10-07 - 1.2.13 - update(analytics)
Analytics integration.
- Updated Analytics integration.
## 2017-10-07 - 1.2.12 - update(dependencies)
Dependency updates.
- Updated dependencies.
## 2017-07-16 - 1.2.11 - update
Dependency and greeting update.
- Updated dependencies and changed greeting text.
## 2017-04-21 - 1.2.10 - feature
Added analytics.
- Now includes SmartAnalytics.
## 2017-04-02 - 1.2.8 - docs & ci
Docs and CI updates.
- Updated README and CI configuration.
## 2017-04-02 - 1.2.7 - fix(command)
Command execution fix.
- Fixed command execution behavior.
## 2017-03-28 - 1.2.6 - ci
CI configuration update.
- Updated .gitlab-ci.yml for correct images/steps.
## 2017-03-28 - 1.2.5 - ci
CI improvements.
- Further CI updates.
## 2017-03-28 - 1.2.4 - perf
Performance improvements.
- Now runs asynchronously and is significantly faster.
## 2017-02-12 - 1.2.3 - feature
New cleanup and diagnostics features.
- Added speedtest utility.
- Added removal of volumes.
## 2017-02-11 - 1.2.2 - feature
Cleanup enhancement.
- Added "clean --all" option to remove more artifacts.
## 2017-02-11 - 1.2.1 - maintenance
Docs and dependency updates.
- Updated README and dependencies.
## 2016-08-04 - 1.2.0 - maintenance
Dependency cleanup.
- Removed unnecessary dependencies.
## 2016-07-29 - 1.1.6 - feature
Environment support.
- Added support for qenv.
## 2016-07-29 - 1.1.5 - fix
Container cleanup improvements.
- Now also removes old running containers.
## 2016-07-29 - 1.1.4 - fix
Namespace conflict avoidance.
- Removes previous containers to avoid name-space conflicts after errors.
## 2016-07-29 - 1.1.3 - ci
CI image configuration.
- Added correct images for GitLab CI.
## 2016-07-29 - 1.1.2 - ci
CI fixes.
- Fixed GitLab CI configuration.
## 2016-07-28 - 1.1.1 - ci
CI fixes and configuration.
- Fixed gitlab.yml and CI issues.
## 2016-07-28 - 1.1.0 - feature
Docker-in-Docker support.
- Improved support for Docker-in-Docker scenarios.
## 2016-07-28 - 1.0.5 - feature & ci
Docker socket option and CI update.
- Added dockerSock option.
- Updated .gitlab-ci.yml.
## 2016-07-19 - 1.0.4 - release (no code changes)
Tagged release with no recorded source changes.
- Tagged release only (no changelogged changes).
## 2016-07-19 - 1.0.3 - feature
Environment tagging.
- Added environment tag support.
## 2016-07-19 - 1.0.2 - milestone
CLI and stability improvements.
- Wired up CLI usage.
- Marked as fully working.
## 2016-07-19 - 1.0.1 - initial improvements
Early project refinements and Docker integration.
- Added/updated Docker integration and configuration.
- Improved config handling and path management.
- Updated Docker handling and removed test artifacts.
## 2016-07-13 - 1.0.0 - initial
Initial release.
- Added README and initial project scaffolding.

2
cli.js
View File

@@ -1,3 +1,3 @@
#!/usr/bin/env node
process.env.CLI_CALL = 'true';
require('./dist/index');
import('./dist_ts/index.js');

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env node
process.env.CLI_CALL = 'true';
require('@gitzone/tsrun');
require('./ts/index');

View File

@@ -3,18 +3,25 @@
"mode": "default",
"cli": true
},
"npmci": {
"npmGlobalTools": [],
"npmAccessLevel": "public"
},
"gitzone": {
"@git.zone/cli": {
"projectType": "npm",
"module": {
"githost": "gitlab.com",
"gitscope": "gitzone",
"gitrepo": "npmdocker",
"shortDescription": "develop npm modules cross platform with docker",
"npmPackagename": "@gitzone/npmdocker",
"gitrepo": "tsdocker",
"description": "develop npm modules cross platform with docker",
"npmPackagename": "@git.zone/tsdocker",
"license": "MIT"
}
},
"release": {
"accessLevel": "public",
"registries": [
"https://verdaccio.lossless.digital",
"https://registry.npmjs.org"
]
}
},
"@ship.zone/szci": {
"npmGlobalTools": []
}
}

2060
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,28 @@
{
"name": "@gitzone/tsdocker",
"version": "1.2.31",
"name": "@git.zone/tsdocker",
"version": "1.4.1",
"private": false,
"description": "develop npm modules cross platform with docker",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",
"bin": {
"npmdocker": "cli.js"
"tsdocker": "cli.js"
},
"scripts": {
"test": "(npm run clean && npm run setupCheck && npm run testStandard && npm run testSpeed)",
"test": "(npm run build)",
"build": "(tsbuild)",
"testStandard": "(cd test/ && node ../cli.ts.js)",
"testSpeed": "(cd test/ && node ../cli.ts.js speedtest)",
"testClean": "(cd test/ && node ../cli.ts.js clean --all)",
"testVscode": "(cd test/ && node ../cli.ts.js vscode)",
"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/)"
"setupCheck": "(git clone https://gitlab.com/sandboxzone/sandbox-npmts.git test/)",
"buildDocs": "tsdoc"
},
"repository": {
"type": "git",
"url": "git+ssh://git@gitlab.com/gitzone/npmdocker.git"
"url": "https://gitlab.com/gitzone/tsdocker.git"
},
"keywords": [
"docker"
@@ -29,32 +30,46 @@
"author": "Lossless GmbH",
"license": "MIT",
"bugs": {
"url": "https://gitlab.com/gitzone/npmdocker/issues"
"url": "https://gitlab.com/gitzone/tsdocker/issues"
},
"homepage": "https://gitlab.com/gitzone/npmdocker#README",
"homepage": "https://gitlab.com/gitzone/tsdocker#readme",
"devDependencies": {
"@gitzone/tsbuild": "^2.1.11",
"@gitzone/tsrun": "^1.2.6",
"@gitzone/tstest": "^1.0.20",
"@pushrocks/tapbundle": "^3.0.9",
"@types/node": "^12.0.0",
"tslint": "^5.16.0",
"tslint-config-prettier": "^1.18.0"
"@git.zone/tsbuild": "^4.1.2",
"@git.zone/tsrun": "^2.0.1",
"@git.zone/tstest": "^3.1.6",
"@types/node": "^25.0.9"
},
"dependencies": {
"@pushrocks/npmextra": "^3.0.3",
"@pushrocks/projectinfo": "^4.0.2",
"@pushrocks/qenv": "^4.0.0",
"@pushrocks/smartanalytics": "^2.0.15",
"@pushrocks/smartcli": "^3.0.7",
"@pushrocks/smartfile": "^7.0.2",
"@pushrocks/smartlog": "^2.0.19",
"@pushrocks/smartlog-destination-local": "^7.0.5",
"@pushrocks/smartlog-source-ora": "^1.0.4",
"@pushrocks/smartopen": "^1.0.8",
"@pushrocks/smartpromise": "^3.0.2",
"@pushrocks/smartshell": "^2.0.13",
"@pushrocks/smartstring": "^3.0.10",
"@types/shelljs": "^0.8.5"
"@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/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"
},
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34",
"type": "module",
"files": [
"ts/**/*",
"ts_web/**/*",
"dist/**/*",
"dist_*/**/*",
"dist_ts/**/*",
"dist_ts_web/**/*",
"assets/**/*",
"cli.js",
"npmextra.json",
"readme.md"
],
"pnpm": {
"overrides": {}
}
}

8743
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

109
readme.hints.md Normal file
View File

@@ -0,0 +1,109 @@
# tsdocker Project Hints
## 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)
## New CLI Commands (2026-01-19)
| Command | Description |
|---------|-------------|
| `tsdocker` | Run tests in container (legacy default behavior) |
| `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 |
| `tsdocker vscode` | Start VS Code in Docker |
## Configuration
Configure in `package.json` under `@git.zone/tsdocker`:
```json
{
"@git.zone/tsdocker": {
"registries": ["registry.gitlab.com", "docker.io"],
"registryRepoMap": {
"registry.gitlab.com": "host.today/ht-docker-node"
},
"buildArgEnvMap": {
"NODE_VERSION": "NODE_VERSION"
},
"platforms": ["linux/amd64", "linux/arm64"],
"push": false,
"testDir": "./test"
}
}
```
### 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
- `platforms`: Target architectures for buildx
- `push`: Auto-push after build
- `testDir`: Directory containing test scripts
## Registry Authentication
Set environment variables for registry login:
```bash
# Pipe-delimited format (numbered 1-10)
export DOCKER_REGISTRY_1="registry.gitlab.com|username|password"
export DOCKER_REGISTRY_2="docker.io|username|password"
# Or individual registry format
export DOCKER_REGISTRY_URL="registry.gitlab.com"
export DOCKER_REGISTRY_USER="username"
export DOCKER_REGISTRY_PASSWORD="password"
```
## File Structure
```
ts/
├── index.ts (entry point)
├── 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)
├── classes.tsdockermanager.ts (orchestrator)
└── interfaces/
└── index.ts (type definitions)
```
## Dependencies
- `@push.rocks/lik`: Object mapping utilities
- `@push.rocks/smartfs`: Filesystem operations
- `@push.rocks/smartshell`: Shell command execution
- `@push.rocks/smartcli`: CLI framework
- `@push.rocks/projectinfo`: Project metadata
## Build Status
- Build: ✅ Passes
- Legacy test functionality preserved
- New Docker build functionality added
## Previous Upgrades (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

479
readme.md Normal file
View File

@@ -0,0 +1,479 @@
# @git.zone/tsdocker
> 🐳 The ultimate Docker development toolkit for TypeScript projects — build, test, and ship containerized applications with ease.
## Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
## What is tsdocker?
**tsdocker** is a comprehensive Docker development and building tool that handles everything from testing npm packages in clean environments to building and pushing multi-architecture Docker images across multiple registries.
### 🎯 Key Capabilities
- 🧪 **Containerized Testing** — Run your tests in pristine Docker environments
- 🏗️ **Smart Docker Builds** — Automatically discover, sort, and build Dockerfiles by dependency
- 🚀 **Multi-Registry Push** — Ship to Docker Hub, GitLab, GitHub Container Registry, and more
- 🔧 **Multi-Architecture** — Build for `amd64` and `arm64` with Docker Buildx
-**Zero Config Start** — Works out of the box, scales with your needs
## Installation
```bash
# Global installation (recommended for CLI usage)
npm install -g @git.zone/tsdocker
# Or project-local installation
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:
```bash
tsdocker build
```
tsdocker will:
1. 🔍 Discover all `Dockerfile*` files in your project
2. 📊 Analyze `FROM` dependencies between them
3. 🔄 Sort them topologically
4. 🏗️ Build each image in the correct order
### 📤 Push to Registries
Ship your images to one or all configured registries:
```bash
# Push to all configured registries
tsdocker push
# Push to a specific registry
tsdocker push registry.gitlab.com
```
## CLI Commands
| Command | Description |
|---------|-------------|
| `tsdocker` | Run tests in a fresh Docker container |
| `tsdocker build` | Build all Dockerfiles with dependency ordering |
| `tsdocker push [registry]` | Push images to configured registries |
| `tsdocker pull <registry>` | Pull images from a specific registry |
| `tsdocker test` | Run container test scripts (test_*.sh) |
| `tsdocker login` | Authenticate with configured registries |
| `tsdocker list` | Display discovered Dockerfiles and their dependencies |
| `tsdocker clean --all` | ⚠️ Aggressively clean Docker environment |
| `tsdocker vscode` | Launch containerized VS Code in browser |
## Configuration
Configure tsdocker in your `package.json` or `npmextra.json`:
```json
{
"@git.zone/tsdocker": {
"baseImage": "node:20",
"command": "npm test",
"dockerSock": false,
"registries": ["registry.gitlab.com", "docker.io"],
"registryRepoMap": {
"registry.gitlab.com": "myorg/myproject"
},
"buildArgEnvMap": {
"NODE_VERSION": "NODE_VERSION"
},
"platforms": ["linux/amd64", "linux/arm64"],
"push": false,
"testDir": "./test"
}
}
```
### Configuration Options
#### Testing Options (Legacy)
| Option | Type | Description |
|--------|------|-------------|
| `baseImage` | `string` | Docker image for test environment (default: `hosttoday/ht-docker-node:npmdocker`) |
| `command` | `string` | Command to run inside container (default: `npmci npm test`) |
| `dockerSock` | `boolean` | Mount Docker socket for DinD scenarios (default: `false`) |
#### Build & Push Options
| Option | Type | 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[]` | Target architectures (default: `["linux/amd64"]`) |
| `push` | `boolean` | Auto-push after build (default: `false`) |
| `testDir` | `string` | Directory containing test scripts |
## Registry Authentication
### Environment Variables
```bash
# Pipe-delimited format (supports DOCKER_REGISTRY_1 through DOCKER_REGISTRY_10)
export DOCKER_REGISTRY_1="registry.gitlab.com|username|password"
export DOCKER_REGISTRY_2="docker.io|username|password"
# Individual registry format
export DOCKER_REGISTRY_URL="registry.gitlab.com"
export DOCKER_REGISTRY_USER="username"
export DOCKER_REGISTRY_PASSWORD="password"
```
### Login Command
```bash
tsdocker login
```
Authenticates with all configured registries.
## Advanced Usage
### 🔀 Multi-Architecture Builds
Build for multiple platforms using Docker Buildx:
```json
{
"@git.zone/tsdocker": {
"platforms": ["linux/amd64", "linux/arm64"]
}
}
```
tsdocker automatically sets up a Buildx builder when multiple platforms are specified.
### 📦 Dockerfile Naming Conventions
tsdocker discovers files matching `Dockerfile*`:
| File Name | Version Tag |
|-----------|-------------|
| `Dockerfile` | `latest` |
| `Dockerfile_v1.0.0` | `v1.0.0` |
| `Dockerfile_alpine` | `alpine` |
| `Dockerfile_##version##` | Uses `package.json` version |
### 🔗 Dependency-Aware Builds
If you have multiple Dockerfiles that depend on each other:
```dockerfile
# Dockerfile_base
FROM node:20-alpine
RUN npm install -g typescript
# Dockerfile_app
FROM myproject:base
COPY . .
RUN npm run build
```
tsdocker automatically detects that `Dockerfile_app` depends on `Dockerfile_base` and builds them in the correct order.
### 🧪 Container Test Scripts
Create test scripts in your test directory:
```bash
# test/test_latest.sh
#!/bin/bash
node --version
npm --version
echo "Container tests passed!"
```
Run with:
```bash
tsdocker test
```
### 🔧 Build Args from Environment
Pass environment variables as Docker build arguments:
```json
{
"@git.zone/tsdocker": {
"buildArgEnvMap": {
"NPM_TOKEN": "NPM_TOKEN",
"NODE_VERSION": "NODE_VERSION"
}
}
}
```
```dockerfile
ARG NPM_TOKEN
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
```
### 🐳 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:
```bash
tsdocker list
```
Output:
```
Discovered Dockerfiles:
========================
1. Dockerfile_base
Tag: myproject:base
Base Image: node:20-alpine
Version: base
2. Dockerfile_app
Tag: myproject:app
Base Image: myproject:base
Version: app
Depends on: myproject:base
```
### 🗺️ Registry Repo Mapping
Use different repository names for different registries:
```json
{
"@git.zone/tsdocker": {
"registries": ["registry.gitlab.com", "docker.io"],
"registryRepoMap": {
"registry.gitlab.com": "mygroup/myproject",
"docker.io": "myuser/myproject"
}
}
}
```
## Environment Variables
### qenv Integration
tsdocker automatically loads environment variables from `qenv.yml`:
```yaml
# qenv.yml
API_KEY: your-api-key
DATABASE_URL: postgres://localhost/test
```
These are injected into your test container automatically.
## Examples
### Basic Test Configuration
```json
{
"@git.zone/tsdocker": {
"baseImage": "node:20",
"command": "npm test"
}
}
```
### Full Production Setup
```json
{
"@git.zone/tsdocker": {
"baseImage": "node:20-alpine",
"command": "pnpm test",
"registries": ["registry.gitlab.com", "ghcr.io", "docker.io"],
"registryRepoMap": {
"registry.gitlab.com": "myorg/myapp",
"ghcr.io": "myorg/myapp",
"docker.io": "myuser/myapp"
},
"buildArgEnvMap": {
"NPM_TOKEN": "NPM_TOKEN"
},
"platforms": ["linux/amd64", "linux/arm64"],
"testDir": "./docker-tests"
}
}
```
### CI/CD Integration
```yaml
# .gitlab-ci.yml
build:
stage: build
script:
- npm install -g @git.zone/tsdocker
- tsdocker build
- tsdocker push
# GitHub Actions
- name: Build and Push
run: |
npm install -g @git.zone/tsdocker
tsdocker login
tsdocker build
tsdocker push
env:
DOCKER_REGISTRY_1: "ghcr.io|${{ github.actor }}|${{ secrets.GITHUB_TOKEN }}"
```
## Requirements
- **Docker** — Docker Engine or Docker Desktop must be installed
- **Node.js** — Version 18 or higher (ESM support required)
- **Docker Buildx** — Required for multi-architecture builds (included in Docker Desktop)
## Why tsdocker?
### 🎯 The Problem
Managing Docker workflows manually is tedious:
- Remembering build order for dependent images
- Pushing to multiple registries with different credentials
- Setting up Buildx for multi-arch builds
- Ensuring consistent test environments
### ✨ The Solution
tsdocker automates the entire workflow:
- **One command** to build all images in dependency order
- **One command** to push to all registries
- **Automatic** Buildx setup for multi-platform builds
- **Consistent** containerized test environments
## TypeScript API
tsdocker exposes its types for programmatic use:
```typescript
import type { ITsDockerConfig } from '@git.zone/tsdocker/dist_ts/interfaces/index.js';
import { TsDockerManager } from '@git.zone/tsdocker/dist_ts/classes.tsdockermanager.js';
const config: ITsDockerConfig = {
baseImage: 'node:20',
command: 'npm test',
dockerSock: false,
keyValueObject: {},
registries: ['docker.io'],
platforms: ['linux/amd64'],
};
const manager = new TsDockerManager(config);
await manager.prepare();
await manager.build();
await manager.push();
```
## Troubleshooting
### "docker not found"
Ensure Docker is installed and in your PATH:
```bash
docker --version
```
### Multi-arch build fails
Make sure Docker Buildx is available:
```bash
docker buildx version
docker buildx create --use
```
### Registry authentication fails
Check your environment variables are set correctly:
```bash
echo $DOCKER_REGISTRY_1
tsdocker login
```
### Circular dependency detected
Review your Dockerfiles' `FROM` statements — you have images depending on each other in a loop.
## Performance Tips
🚀 **Use specific tags**: `node:20-alpine` is smaller and faster than `node:latest`
🚀 **Leverage caching**: Docker layers are cached — your builds get faster over time
🚀 **Prune regularly**: `docker system prune` reclaims disk space
🚀 **Use .dockerignore**: Exclude `node_modules`, `.git`, etc. from build context
## 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.
**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.
### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
### Company Information
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.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

8
ts/00_commitinfo_data.ts Normal file
View File

@@ -0,0 +1,8 @@
/**
* autocreated commitinfo by @push.rocks/commitinfo
*/
export const commitinfo = {
name: '@git.zone/tsdocker',
version: '1.4.1',
description: 'develop npm modules cross platform with docker'
}

View File

@@ -1,20 +0,0 @@
/**
* smartanalytics:
* We count executions of this tool to keep track which of our tools are really used.
* This insight is used to plan spending our limited resources for improving them.
* Any submitted analytics data is fully anonymized (no Ips or any other personal information is tracked).
* Feel free to dig into the smartanalytics package, if you are interested in how it works.
* Our privacy policy can be found here: https://lossless.gmbh/privacy.html
* The privacy policy is also linked in the readme, so we hope this behaviour does not come as a surprise to you.
* Have a nice day and regards
* Your Open Source team at Lossless GmbH :)
*/
import * as smartanalytics from '@pushrocks/smartanalytics';
const npmdockerAnalytics = new smartanalytics.Analytics({
apiEndPoint: 'https://pubapi.lossless.one',
appName: 'tsdocker',
projectId: 'gitzone'
});
npmdockerAnalytics.recordEvent('npmtoolexecution', {
somedata: 'somedata'
});

462
ts/classes.dockerfile.ts Normal file
View File

@@ -0,0 +1,462 @@
import * as plugins from './tsdocker.plugins.js';
import * as paths from './tsdocker.paths.js';
import { logger } from './tsdocker.logging.js';
import { DockerRegistry } from './classes.dockerregistry.js';
import type { IDockerfileOptions, ITsDockerConfig } from './interfaces/index.js';
import type { TsDockerManager } from './classes.tsdockermanager.js';
const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash',
});
/**
* Class Dockerfile represents a Dockerfile on disk
*/
export class Dockerfile {
// STATIC METHODS
/**
* Creates instances of class Dockerfile for all Dockerfiles in cwd
*/
public static async readDockerfiles(managerRef: TsDockerManager): Promise<Dockerfile[]> {
const entries = await plugins.smartfs.directory(paths.cwd).filter('Dockerfile*').list();
const fileTree = entries
.filter(entry => entry.isFile)
.map(entry => plugins.path.join(paths.cwd, entry.name));
const readDockerfilesArray: Dockerfile[] = [];
logger.log('info', `found ${fileTree.length} Dockerfiles:`);
console.log(fileTree);
for (const dockerfilePath of fileTree) {
const myDockerfile = new Dockerfile(managerRef, {
filePath: dockerfilePath,
read: true,
});
readDockerfilesArray.push(myDockerfile);
}
return readDockerfilesArray;
}
/**
* Sorts Dockerfiles into a build order based on dependencies (topological sort)
*/
public static async sortDockerfiles(dockerfiles: Dockerfile[]): Promise<Dockerfile[]> {
logger.log('info', 'Sorting Dockerfiles based on dependencies...');
// Map from cleanTag to Dockerfile instance for quick lookup
const tagToDockerfile = new Map<string, Dockerfile>();
dockerfiles.forEach((dockerfile) => {
tagToDockerfile.set(dockerfile.cleanTag, dockerfile);
});
// Build the dependency graph
const graph = new Map<Dockerfile, Dockerfile[]>();
dockerfiles.forEach((dockerfile) => {
const dependencies: Dockerfile[] = [];
const baseImage = dockerfile.baseImage;
// Check if the baseImage is among the local Dockerfiles
if (tagToDockerfile.has(baseImage)) {
const baseDockerfile = tagToDockerfile.get(baseImage)!;
dependencies.push(baseDockerfile);
dockerfile.localBaseImageDependent = true;
dockerfile.localBaseDockerfile = baseDockerfile;
}
graph.set(dockerfile, dependencies);
});
// Perform topological sort
const sortedDockerfiles: Dockerfile[] = [];
const visited = new Set<Dockerfile>();
const tempMarked = new Set<Dockerfile>();
const visit = (dockerfile: Dockerfile) => {
if (tempMarked.has(dockerfile)) {
throw new Error(`Circular dependency detected involving ${dockerfile.cleanTag}`);
}
if (!visited.has(dockerfile)) {
tempMarked.add(dockerfile);
const dependencies = graph.get(dockerfile) || [];
dependencies.forEach((dep) => visit(dep));
tempMarked.delete(dockerfile);
visited.add(dockerfile);
sortedDockerfiles.push(dockerfile);
}
};
try {
dockerfiles.forEach((dockerfile) => {
if (!visited.has(dockerfile)) {
visit(dockerfile);
}
});
} catch (error) {
logger.log('error', (error as Error).message);
throw error;
}
// Log the sorted order
sortedDockerfiles.forEach((dockerfile, index) => {
logger.log(
'info',
`Build order ${index + 1}: ${dockerfile.cleanTag} with base image ${dockerfile.baseImage}`
);
});
return sortedDockerfiles;
}
/**
* Maps local Dockerfiles dependencies to the corresponding Dockerfile class instances
*/
public static async mapDockerfiles(sortedDockerfileArray: Dockerfile[]): Promise<Dockerfile[]> {
sortedDockerfileArray.forEach((dockerfileArg) => {
if (dockerfileArg.localBaseImageDependent) {
sortedDockerfileArray.forEach((dockfile2: Dockerfile) => {
if (dockfile2.cleanTag === dockerfileArg.baseImage) {
dockerfileArg.localBaseDockerfile = dockfile2;
}
});
}
});
return sortedDockerfileArray;
}
/**
* Builds the corresponding real docker image for each Dockerfile class instance
*/
public static async buildDockerfiles(sortedArrayArg: Dockerfile[]): Promise<Dockerfile[]> {
for (const dockerfileArg of sortedArrayArg) {
await dockerfileArg.build();
}
return sortedArrayArg;
}
/**
* Tests all Dockerfiles by calling Dockerfile.test()
*/
public static async testDockerfiles(sortedArrayArg: Dockerfile[]): Promise<Dockerfile[]> {
for (const dockerfileArg of sortedArrayArg) {
await dockerfileArg.test();
}
return sortedArrayArg;
}
/**
* Returns a version for a docker file
* Dockerfile_latest -> latest
* Dockerfile_v1.0.0 -> v1.0.0
* Dockerfile -> latest
*/
public static dockerFileVersion(
dockerfileInstanceArg: Dockerfile,
dockerfileNameArg: string
): string {
let versionString: string;
const versionRegex = /Dockerfile_(.+)$/;
const regexResultArray = versionRegex.exec(dockerfileNameArg);
if (regexResultArray && regexResultArray.length === 2) {
versionString = regexResultArray[1];
} else {
versionString = 'latest';
}
// Replace ##version## placeholder with actual package version if available
if (dockerfileInstanceArg.managerRef?.projectInfo?.npm?.version) {
versionString = versionString.replace(
'##version##',
dockerfileInstanceArg.managerRef.projectInfo.npm.version
);
}
return versionString;
}
/**
* Extracts the base image from a Dockerfile content
* Handles ARG substitution for variable base images
*/
public static dockerBaseImage(dockerfileContentArg: string): string {
const lines = dockerfileContentArg.split(/\r?\n/);
const args: { [key: string]: string } = {};
for (const line of lines) {
const trimmedLine = line.trim();
// Skip empty lines and comments
if (trimmedLine === '' || trimmedLine.startsWith('#')) {
continue;
}
// Match ARG instructions
const argMatch = trimmedLine.match(/^ARG\s+([^\s=]+)(?:=(.*))?$/i);
if (argMatch) {
const argName = argMatch[1];
const argValue = argMatch[2] !== undefined ? argMatch[2] : process.env[argName] || '';
args[argName] = argValue;
continue;
}
// Match FROM instructions
const fromMatch = trimmedLine.match(/^FROM\s+(.+?)(?:\s+AS\s+[^\s]+)?$/i);
if (fromMatch) {
let baseImage = fromMatch[1].trim();
// Substitute variables in the base image name
baseImage = Dockerfile.substituteVariables(baseImage, args);
return baseImage;
}
}
throw new Error('No FROM instruction found in Dockerfile');
}
/**
* Substitutes variables in a string, supporting default values like ${VAR:-default}
*/
private static substituteVariables(str: string, vars: { [key: string]: string }): string {
return str.replace(/\${([^}:]+)(:-([^}]+))?}/g, (_, varName, __, defaultValue) => {
if (vars[varName] !== undefined) {
return vars[varName];
} else if (defaultValue !== undefined) {
return defaultValue;
} else {
return '';
}
});
}
/**
* Returns the docker tag string for a given registry and repo
*/
public static getDockerTagString(
managerRef: TsDockerManager,
registryArg: string,
repoArg: string,
versionArg: string,
suffixArg?: string
): string {
// Determine whether the repo should be mapped according to the registry
const config = managerRef.config;
const mappedRepo = config.registryRepoMap?.[registryArg];
const repo = mappedRepo || repoArg;
// Determine whether the version contains a suffix
let version = versionArg;
if (suffixArg) {
version = versionArg + '_' + suffixArg;
}
const tagString = `${registryArg}/${repo}:${version}`;
return tagString;
}
/**
* Gets build args from environment variable mapping
*/
public static async getDockerBuildArgs(managerRef: TsDockerManager): Promise<string> {
logger.log('info', 'checking for env vars to be supplied to the docker build');
let buildArgsString: string = '';
const config = managerRef.config;
if (config.buildArgEnvMap) {
for (const dockerArgKey of Object.keys(config.buildArgEnvMap)) {
const dockerArgOuterEnvVar = config.buildArgEnvMap[dockerArgKey];
logger.log(
'note',
`docker ARG "${dockerArgKey}" maps to outer env var "${dockerArgOuterEnvVar}"`
);
const targetValue = process.env[dockerArgOuterEnvVar];
if (targetValue) {
buildArgsString = `${buildArgsString} --build-arg ${dockerArgKey}="${targetValue}"`;
}
}
}
return buildArgsString;
}
// INSTANCE PROPERTIES
public managerRef: TsDockerManager;
public filePath!: string;
public repo: string;
public version: string;
public cleanTag: string;
public buildTag: string;
public pushTag!: string;
public containerName: string;
public content!: string;
public baseImage: string;
public localBaseImageDependent: boolean;
public localBaseDockerfile!: Dockerfile;
constructor(managerRefArg: TsDockerManager, options: IDockerfileOptions) {
this.managerRef = managerRefArg;
this.filePath = options.filePath!;
// Build repo name from project info or directory name
const projectInfo = this.managerRef.projectInfo;
if (projectInfo?.npm?.name) {
// Use package name, removing scope if present
const packageName = projectInfo.npm.name.replace(/^@[^/]+\//, '');
this.repo = packageName;
} else {
// Fallback to directory name
this.repo = plugins.path.basename(paths.cwd);
}
this.version = Dockerfile.dockerFileVersion(this, plugins.path.parse(this.filePath).base);
this.cleanTag = this.repo + ':' + this.version;
this.buildTag = this.cleanTag;
this.containerName = 'dockerfile-' + this.version;
if (options.filePath && options.read) {
const fs = require('fs');
this.content = fs.readFileSync(plugins.path.resolve(options.filePath), 'utf-8');
} else if (options.fileContents) {
this.content = options.fileContents;
}
this.baseImage = Dockerfile.dockerBaseImage(this.content);
this.localBaseImageDependent = false;
}
/**
* Builds the Dockerfile
*/
public async build(): Promise<void> {
logger.log('info', 'now building Dockerfile for ' + this.cleanTag);
const buildArgsString = await Dockerfile.getDockerBuildArgs(this.managerRef);
const config = this.managerRef.config;
let buildCommand: string;
// Check if multi-platform build is needed
if (config.platforms && config.platforms.length > 1) {
// Multi-platform build using buildx
const platformString = config.platforms.join(',');
buildCommand = `docker buildx build --platform ${platformString} -t ${this.buildTag} -f ${this.filePath} ${buildArgsString} .`;
if (config.push) {
buildCommand += ' --push';
} else {
buildCommand += ' --load';
}
} else {
// Standard build
const versionLabel = this.managerRef.projectInfo?.npm?.version || 'unknown';
buildCommand = `docker build --label="version=${versionLabel}" -t ${this.buildTag} -f ${this.filePath} ${buildArgsString} .`;
}
const result = await smartshellInstance.exec(buildCommand);
if (result.exitCode !== 0) {
logger.log('error', `Build failed for ${this.cleanTag}`);
console.log(result.stdout);
throw new Error(`Build failed for ${this.cleanTag}`);
}
logger.log('ok', `Built ${this.cleanTag}`);
}
/**
* Pushes the Dockerfile to a registry
*/
public async push(dockerRegistryArg: DockerRegistry, versionSuffix?: string): Promise<void> {
this.pushTag = Dockerfile.getDockerTagString(
this.managerRef,
dockerRegistryArg.registryUrl,
this.repo,
this.version,
versionSuffix
);
await smartshellInstance.exec(`docker tag ${this.buildTag} ${this.pushTag}`);
const pushResult = await smartshellInstance.exec(`docker push ${this.pushTag}`);
if (pushResult.exitCode !== 0) {
logger.log('error', `Push failed for ${this.pushTag}`);
throw new Error(`Push failed for ${this.pushTag}`);
}
// Get image digest
const inspectResult = await smartshellInstance.exec(
`docker inspect --format="{{index .RepoDigests 0}}" ${this.pushTag}`
);
if (inspectResult.exitCode === 0 && inspectResult.stdout.includes('@')) {
const imageDigest = inspectResult.stdout.split('@')[1]?.trim();
console.log(`The image ${this.pushTag} has digest ${imageDigest}`);
}
logger.log('ok', `Pushed ${this.pushTag}`);
}
/**
* Pulls the Dockerfile from a registry
*/
public async pull(registryArg: DockerRegistry, versionSuffixArg?: string): Promise<void> {
const pullTag = Dockerfile.getDockerTagString(
this.managerRef,
registryArg.registryUrl,
this.repo,
this.version,
versionSuffixArg
);
await smartshellInstance.exec(`docker pull ${pullTag}`);
await smartshellInstance.exec(`docker tag ${pullTag} ${this.buildTag}`);
logger.log('ok', `Pulled and tagged ${pullTag} as ${this.buildTag}`);
}
/**
* Tests the Dockerfile by running a test script if it exists
*/
public async test(): Promise<void> {
const testDir = this.managerRef.config.testDir || plugins.path.join(paths.cwd, 'test');
const testFile = plugins.path.join(testDir, 'test_' + this.version + '.sh');
const fs = require('fs');
const testFileExists = fs.existsSync(testFile);
if (testFileExists) {
logger.log('info', `Running tests for ${this.cleanTag}`);
// Run tests in container
await smartshellInstance.exec(
`docker run --name tsdocker_test_container --entrypoint="bash" ${this.buildTag} -c "mkdir /tsdocker_test"`
);
await smartshellInstance.exec(`docker cp ${testFile} tsdocker_test_container:/tsdocker_test/test.sh`);
await smartshellInstance.exec(`docker commit tsdocker_test_container tsdocker_test_image`);
const testResult = await smartshellInstance.exec(
`docker run --entrypoint="bash" tsdocker_test_image -x /tsdocker_test/test.sh`
);
// Cleanup
await smartshellInstance.exec(`docker rm tsdocker_test_container`);
await smartshellInstance.exec(`docker rmi --force tsdocker_test_image`);
if (testResult.exitCode !== 0) {
throw new Error(`Tests failed for ${this.cleanTag}`);
}
logger.log('ok', `Tests passed for ${this.cleanTag}`);
} else {
logger.log('warn', `Skipping tests for ${this.cleanTag} because no test file was found at ${testFile}`);
}
}
/**
* Gets the ID of a built Docker image
*/
public async getId(): Promise<string> {
const result = await smartshellInstance.exec(
'docker inspect --type=image --format="{{.Id}}" ' + this.buildTag
);
return result.stdout.trim();
}
}

View File

@@ -0,0 +1,91 @@
import * as plugins from './tsdocker.plugins.js';
import { logger } from './tsdocker.logging.js';
import type { IDockerRegistryOptions } from './interfaces/index.js';
const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash',
});
/**
* Represents a Docker registry with authentication capabilities
*/
export class DockerRegistry {
public registryUrl: string;
public username: string;
public password: string;
constructor(optionsArg: IDockerRegistryOptions) {
this.registryUrl = optionsArg.registryUrl;
this.username = optionsArg.username;
this.password = optionsArg.password;
logger.log('info', `created DockerRegistry for ${this.registryUrl}`);
}
/**
* Creates a DockerRegistry instance from a pipe-delimited environment string
* Format: "registryUrl|username|password"
*/
public static fromEnvString(envString: string): DockerRegistry {
const dockerRegexResultArray = envString.split('|');
if (dockerRegexResultArray.length !== 3) {
logger.log('error', 'malformed docker env var...');
throw new Error('malformed docker env var, expected format: registryUrl|username|password');
}
const registryUrl = dockerRegexResultArray[0].replace('https://', '').replace('http://', '');
const username = dockerRegexResultArray[1];
const password = dockerRegexResultArray[2];
return new DockerRegistry({
registryUrl: registryUrl,
username: username,
password: password,
});
}
/**
* Creates a DockerRegistry from environment variables
* Looks for DOCKER_REGISTRY, DOCKER_REGISTRY_USER, DOCKER_REGISTRY_PASSWORD
* Or for a specific registry: DOCKER_REGISTRY_<NAME>, etc.
*/
public static fromEnv(registryName?: string): DockerRegistry | null {
const prefix = registryName ? `DOCKER_REGISTRY_${registryName.toUpperCase()}_` : 'DOCKER_REGISTRY_';
const registryUrl = process.env[`${prefix}URL`] || process.env['DOCKER_REGISTRY'];
const username = process.env[`${prefix}USER`] || process.env['DOCKER_REGISTRY_USER'];
const password = process.env[`${prefix}PASSWORD`] || process.env['DOCKER_REGISTRY_PASSWORD'];
if (!registryUrl || !username || !password) {
return null;
}
return new DockerRegistry({
registryUrl: registryUrl.replace('https://', '').replace('http://', ''),
username,
password,
});
}
/**
* Logs in to the Docker registry
*/
public async login(): Promise<void> {
if (this.registryUrl === 'docker.io') {
await smartshellInstance.exec(`docker login -u ${this.username} -p ${this.password}`);
logger.log('info', 'Logged in to standard docker hub');
} else {
await smartshellInstance.exec(`docker login -u ${this.username} -p ${this.password} ${this.registryUrl}`);
}
logger.log('ok', `docker authenticated for ${this.registryUrl}!`);
}
/**
* Logs out from the Docker registry
*/
public async logout(): Promise<void> {
if (this.registryUrl === 'docker.io') {
await smartshellInstance.exec('docker logout');
} else {
await smartshellInstance.exec(`docker logout ${this.registryUrl}`);
}
logger.log('info', `logged out from ${this.registryUrl}`);
}
}

View File

@@ -0,0 +1,83 @@
import * as plugins from './tsdocker.plugins.js';
import { logger } from './tsdocker.logging.js';
import { DockerRegistry } from './classes.dockerregistry.js';
/**
* Storage class for managing multiple Docker registries
*/
export class RegistryStorage {
public objectMap = new plugins.lik.ObjectMap<DockerRegistry>();
constructor() {
// Nothing here
}
/**
* Adds a registry to the storage
*/
public addRegistry(registryArg: DockerRegistry): void {
this.objectMap.add(registryArg);
}
/**
* Gets a registry by its URL
*/
public getRegistryByUrl(registryUrlArg: string): DockerRegistry | undefined {
return this.objectMap.findSync((registryArg) => {
return registryArg.registryUrl === registryUrlArg;
});
}
/**
* Gets all registries
*/
public getAllRegistries(): DockerRegistry[] {
return this.objectMap.getArray();
}
/**
* Logs in to all registries
*/
public async loginAll(): Promise<void> {
await this.objectMap.forEach(async (registryArg) => {
await registryArg.login();
});
logger.log('success', 'logged in successfully into all available DockerRegistries!');
}
/**
* Logs out from all registries
*/
public async logoutAll(): Promise<void> {
await this.objectMap.forEach(async (registryArg) => {
await registryArg.logout();
});
logger.log('info', 'logged out from all DockerRegistries');
}
/**
* Loads registries from environment variables
* Looks for DOCKER_REGISTRY_1, DOCKER_REGISTRY_2, etc. (pipe-delimited format)
* Or individual registries like DOCKER_REGISTRY_GITLAB_URL, etc.
*/
public loadFromEnv(): void {
// Check for numbered registry env vars (pipe-delimited format)
for (let i = 1; i <= 10; i++) {
const envVar = process.env[`DOCKER_REGISTRY_${i}`];
if (envVar) {
try {
const registry = DockerRegistry.fromEnvString(envVar);
this.addRegistry(registry);
} catch (err) {
logger.log('warn', `Failed to parse DOCKER_REGISTRY_${i}: ${(err as Error).message}`);
}
}
}
// Check for default registry
const defaultRegistry = DockerRegistry.fromEnv();
if (defaultRegistry) {
this.addRegistry(defaultRegistry);
}
}
}

View File

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

View File

@@ -1,5 +1,4 @@
import './analytics';
import * as plugins from './tsdocker.plugins';
import * as cli from './tsdocker.cli';
import * as plugins from './tsdocker.plugins.js';
import * as cli from './tsdocker.cli.js';
cli.run();

70
ts/interfaces/index.ts Normal file
View File

@@ -0,0 +1,70 @@
/**
* 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 };
platforms?: string[]; // ['linux/amd64', 'linux/arm64']
push?: boolean;
testDir?: string;
}
/**
* Options for constructing a DockerRegistry
*/
export interface IDockerRegistryOptions {
registryUrl: string;
username: string;
password: string;
}
/**
* Information about a discovered Dockerfile
*/
export interface IDockerfileInfo {
filePath: string;
fileName: string;
version: string;
baseImage: string;
buildTag: string;
localBaseImageDependent: boolean;
}
/**
* Options for creating a Dockerfile instance
*/
export interface IDockerfileOptions {
filePath?: string;
fileContents?: string;
read?: boolean;
}
/**
* Result from a Docker build operation
*/
export interface IBuildResult {
success: boolean;
tag: string;
duration?: number;
error?: string;
}
/**
* Result from a Docker push operation
*/
export interface IPushResult {
success: boolean;
registry: string;
tag: string;
digest?: string;
error?: string;
}

View File

@@ -1,17 +1,19 @@
import * as plugins from './tsdocker.plugins';
import * as paths from './tsdocker.paths';
import * as plugins from './tsdocker.plugins.js';
import * as paths from './tsdocker.paths.js';
// modules
import * as ConfigModule from './tsdocker.config';
import * as DockerModule from './tsdocker.docker';
import * as ConfigModule from './tsdocker.config.js';
import * as DockerModule from './tsdocker.docker.js';
import { logger, ora } from './tsdocker.logging';
import { logger, ora } from './tsdocker.logging.js';
import { TsDockerManager } from './classes.tsdockermanager.js';
const tsdockerCli = new plugins.smartcli.Smartcli();
export let run = () => {
tsdockerCli.standardTask().subscribe(async argvArg => {
let configArg = await ConfigModule.run().then(DockerModule.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 {
@@ -20,6 +22,127 @@ export let run = () => {
}
});
/**
* Build all Dockerfiles in dependency order
*/
tsdockerCli.addCommand('build').subscribe(async argvArg => {
try {
const config = await ConfigModule.run();
const manager = new TsDockerManager(config);
await manager.prepare();
await manager.build();
logger.log('success', 'Build completed successfully');
} catch (err) {
logger.log('error', `Build failed: ${(err as Error).message}`);
process.exit(1);
}
});
/**
* Push built images to configured registries
*/
tsdockerCli.addCommand('push').subscribe(async argvArg => {
try {
const config = await ConfigModule.run();
const manager = new TsDockerManager(config);
await manager.prepare();
// Login first
await manager.login();
// Build images first (if not already built)
await manager.build();
// Get registry from arguments if specified
const registryArg = argvArg._[1]; // e.g., tsdocker push registry.gitlab.com
const registries = registryArg ? [registryArg] : undefined;
await manager.push(registries);
logger.log('success', 'Push completed successfully');
} catch (err) {
logger.log('error', `Push failed: ${(err as Error).message}`);
process.exit(1);
}
});
/**
* Pull images from a specified registry
*/
tsdockerCli.addCommand('pull').subscribe(async argvArg => {
try {
const registryArg = argvArg._[1]; // e.g., tsdocker pull registry.gitlab.com
if (!registryArg) {
logger.log('error', 'Registry URL required. Usage: tsdocker pull <registry-url>');
process.exit(1);
}
const config = await ConfigModule.run();
const manager = new TsDockerManager(config);
await manager.prepare();
// Login first
await manager.login();
await manager.pull(registryArg);
logger.log('success', 'Pull completed successfully');
} catch (err) {
logger.log('error', `Pull failed: ${(err as Error).message}`);
process.exit(1);
}
});
/**
* Run container tests for all Dockerfiles
*/
tsdockerCli.addCommand('test').subscribe(async argvArg => {
try {
const config = await ConfigModule.run();
const manager = new TsDockerManager(config);
await manager.prepare();
// Build images first
await manager.build();
// Run tests
await manager.test();
logger.log('success', 'Tests completed successfully');
} catch (err) {
logger.log('error', `Tests failed: ${(err as Error).message}`);
process.exit(1);
}
});
/**
* Login to configured registries
*/
tsdockerCli.addCommand('login').subscribe(async argvArg => {
try {
const config = await ConfigModule.run();
const manager = new TsDockerManager(config);
await manager.prepare();
await manager.login();
logger.log('success', 'Login completed successfully');
} catch (err) {
logger.log('error', `Login failed: ${(err as Error).message}`);
process.exit(1);
}
});
/**
* List discovered Dockerfiles and their dependencies
*/
tsdockerCli.addCommand('list').subscribe(async argvArg => {
try {
const config = await ConfigModule.run();
const manager = new TsDockerManager(config);
await manager.prepare();
await manager.list();
} catch (err) {
logger.log('error', `List failed: ${(err as Error).message}`);
process.exit(1);
}
});
/**
* this command is executed inside docker and meant for use from outside docker
*/
@@ -62,16 +185,6 @@ export let run = () => {
ora.finishSuccess('docker environment now is clean!');
});
tsdockerCli.addCommand('speedtest').subscribe(async argvArg => {
const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash'
});
logger.log('ok', 'Starting speedtest');
await smartshellInstance.exec(
`docker pull tianon/speedtest && docker run --rm tianon/speedtest`
);
});
tsdockerCli.addCommand('vscode').subscribe(async argvArg => {
const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash'

View File

@@ -1,17 +1,16 @@
import * as plugins from './tsdocker.plugins';
import * as paths from './tsdocker.paths';
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';
export interface IConfig {
baseImage: string;
command: string;
dockerSock: boolean;
// Re-export ITsDockerConfig as IConfig for backward compatibility
export type IConfig = ITsDockerConfig & {
exitCode?: number;
keyValueObject: any[];
}
};
const getQenvKeyValueObject = async () => {
let qenvKeyValueObjectArray: { [key: string]: string | number };
if (plugins.smartfile.fs.fileExistsSync(plugins.path.join(paths.cwd, 'qenv.yml'))) {
if (fs.existsSync(plugins.path.join(paths.cwd, 'qenv.yml'))) {
qenvKeyValueObjectArray = new plugins.qenv.Qenv(paths.cwd, '.nogit/').keyValueObject;
} else {
qenvKeyValueObjectArray = {};
@@ -21,12 +20,21 @@ const getQenvKeyValueObject = async () => {
const buildConfig = async (qenvKeyValueObjectArg: { [key: string]: string | number }) => {
const npmextra = new plugins.npmextra.Npmextra(paths.cwd);
const config = npmextra.dataFor<IConfig>('npmdocker', {
const config = npmextra.dataFor<IConfig>('@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
keyValueObject: qenvKeyValueObjectArg,
// New Docker build options
registries: [],
registryRepoMap: {},
buildArgEnvMap: {},
platforms: ['linux/amd64'],
push: false,
testDir: undefined,
});
return config;
};

View File

@@ -1,15 +1,15 @@
import * as plugins from './tsdocker.plugins';
import * as paths from './tsdocker.paths';
import * as snippets from './tsdocker.snippets';
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';
import { logger, ora } from './tsdocker.logging.js';
const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash'
});
// interfaces
import { IConfig } from './tsdocker.config';
import type { IConfig } from './tsdocker.config.js';
let config: IConfig;
@@ -43,7 +43,7 @@ const checkDocker = () => {
/**
* builds the Dockerfile according to the config in the project
*/
const buildDockerFile = () => {
const buildDockerFile = async () => {
const done = plugins.smartpromise.defer();
ora.text('building Dockerfile...');
const dockerfile: string = snippets.dockerfileSnippet({
@@ -52,7 +52,7 @@ const buildDockerFile = () => {
});
logger.log('info', `Base image is: ${config.baseImage}`);
logger.log('info', `Command is: ${config.command}`);
plugins.smartfile.memory.toFsSync(dockerfile, plugins.path.join(paths.cwd, 'npmdocker'));
await plugins.smartfs.file(plugins.path.join(paths.cwd, 'npmdocker')).write(dockerfile);
logger.log('ok', 'Dockerfile created!');
ora.stop();
done.resolve();
@@ -67,7 +67,7 @@ const buildDockerImage = async () => {
await smartshellInstance.exec(`docker pull ${config.baseImage}`);
ora.text('building Dockerimage...');
const execResult = await smartshellInstance.execSilent(
`docker build -f npmdocker -t ${dockerData.imageTag} ${paths.cwd}`
`docker build --load -f npmdocker -t ${dockerData.imageTag} ${paths.cwd}`
);
if (execResult.exitCode !== 0) {
console.log(execResult.stdout);
@@ -148,7 +148,7 @@ const postClean = async () => {
.then(async () => {
logger.log('ok', 'cleaned up!');
});
plugins.smartfile.fs.removeSync(paths.npmdockerFile);
await plugins.smartfs.file(paths.npmdockerFile).delete();
};
export let run = async (configArg: IConfig): Promise<IConfig> => {

View File

@@ -1,4 +1,4 @@
import * as plugins from './tsdocker.plugins';
import * as plugins from './tsdocker.plugins.js';
export const logger = new plugins.smartlog.Smartlog({
logContext: {

View File

@@ -1,8 +1,14 @@
import * as plugins from './tsdocker.plugins';
import * as plugins from './tsdocker.plugins.js';
import * as fs from 'fs';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// directories
export let cwd = process.cwd();
export let packageBase = plugins.path.join(__dirname, '../');
export let assets = plugins.path.join(packageBase, 'assets/');
plugins.smartfile.fs.ensureDirSync(assets);
fs.mkdirSync(assets, { recursive: true });
export let npmdockerFile = plugins.path.join(cwd, 'npmdocker');

View File

@@ -1,26 +1,30 @@
// pushrocks scope
import * as npmextra from '@pushrocks/npmextra';
// push.rocks scope
import * as lik from '@push.rocks/lik';
import * as npmextra from '@push.rocks/npmextra';
import * as path from 'path';
import * as projectinfo from '@pushrocks/projectinfo';
import * as smartpromise from '@pushrocks/smartpromise';
import * as qenv from '@pushrocks/qenv';
import * as smartcli from '@pushrocks/smartcli';
import * as smartfile from '@pushrocks/smartfile';
import * as smartlog from '@pushrocks/smartlog';
import * as smartlogDestinationLocal from '@pushrocks/smartlog-destination-local';
import * as smartlogSouceOra from '@pushrocks/smartlog-source-ora';
import * as smartopen from '@pushrocks/smartopen';
import * as smartshell from '@pushrocks/smartshell';
import * as smartstring from '@pushrocks/smartstring';
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 smartshell from '@push.rocks/smartshell';
import * as smartstring from '@push.rocks/smartstring';
// Create smartfs instance
export const smartfs = new SmartFs(new SmartFsProviderNode());
export {
lik,
npmextra,
path,
projectinfo,
smartpromise,
qenv,
smartcli,
smartfile,
smartlog,
smartlogDestinationLocal,
smartlogSouceOra,

View File

@@ -1,4 +1,4 @@
import * as plugins from './tsdocker.plugins';
import * as plugins from './tsdocker.plugins.js';
export interface IDockerfileSnippet {
baseImage: string;
@@ -14,23 +14,20 @@ let getMountSolutionString = (optionsArg: IDockerfileSnippet) => {
};
let getGlobalPreparationString = (optionsArg: IDockerfileSnippet) => {
if (optionsArg.baseImage !== 'hosttoday/ht-docker-node:npmdocker') {
return 'RUN npm install -g npmdocker';
} else {
return '# not installing npmdocker since it is included in the base image';
}
// 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 npmdocker does read the docs at https://gitzone.github.io/npmdocker
# 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 ["npmdocker"]
ENTRYPOINT ["tsdocker"]
CMD ["runinside"]
`
);

12
tsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"baseUrl": ".",
"paths": {}
},
"exclude": ["dist_*/**/*.d.ts"]
}

View File

@@ -1,17 +0,0 @@
{
"extends": ["tslint:latest", "tslint-config-prettier"],
"rules": {
"semicolon": [true, "always"],
"no-console": false,
"ordered-imports": false,
"object-literal-sort-keys": false,
"member-ordering": {
"options":{
"order": [
"static-method"
]
}
}
},
"defaultSeverity": "warning"
}