Compare commits

...

92 Commits

Author SHA1 Message Date
32eb0d1d77 v1.5.0
Some checks failed
Default (tags) / security (push) Successful in 39s
Default (tags) / test (push) Failing after 4m0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-02-06 11:58:32 +00:00
7cac628975 feat(build): add support for selective builds, platform override and build timeout 2026-02-06 11:58:32 +00:00
c279dbd55e v1.4.3
Some checks failed
Default (tags) / security (push) Successful in 40s
Default (tags) / test (push) Failing after 4m0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-02-04 14:01:57 +00:00
7b7064864e fix(dockerfile): fix matching of base images to local Dockerfiles by stripping registry prefixes when comparing image references 2026-02-04 14:01:57 +00:00
36f06cef09 v1.4.2
Some checks failed
Default (tags) / security (push) Successful in 36s
Default (tags) / test (push) Failing after 4m2s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-21 00:03:25 +00:00
b0f87deb4b fix(classes.dockerfile): use a single top-level fs import instead of requiring fs inside methods 2026-01-21 00:03:25 +00:00
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
bde4597dd9 1.2.31 2019-05-10 17:09:51 +02:00
80946d4f0c fix(core): update 2019-05-10 17:09:50 +02:00
2155e886ef 1.2.30 2019-05-10 11:46:38 +02:00
8b61a90b5f fix(core): update 2019-05-10 11:46:37 +02:00
f48eadc814 1.2.29 2019-05-10 11:46:00 +02:00
9bdaaa0c30 fix(core): update 2019-05-10 11:45:59 +02:00
5f681ff237 fix(core): update 2019-05-10 11:45:20 +02:00
889a3fdc5a 1.2.28 2019-05-09 10:06:59 +02:00
565c002127 fix(core): update 2019-05-09 10:06:59 +02:00
6fec1a00ea 1.2.27 2018-10-29 01:09:46 +01:00
e1f2c28718 fix(ci): remove npmts from build process 2018-10-29 01:09:45 +01:00
9efab07424 1.2.26 2018-10-29 01:07:40 +01:00
18d16feaa9 fix(core): update 2018-10-29 01:07:39 +01:00
df09ff0f9b 1.2.25 2018-10-28 21:40:32 +01:00
7b50ceb69f fix(clean): images are now cleaned in a more thorough way 2018-10-28 21:40:31 +01:00
d1fd76e8d0 1.2.24 2018-09-16 23:14:33 +02:00
b71fa1bc36 fix(core): update 2018-09-16 23:14:33 +02:00
068d28b95d 1.2.23 2018-09-16 21:08:14 +02:00
ecb458f5cd fix(dependencies): update 2018-09-16 21:08:13 +02:00
9f6fb8aedd 1.2.22 2018-07-21 13:32:51 +02:00
f21df13b7c fix(update to latest standards): update 2018-07-21 13:32:51 +02:00
9c56942739 1.2.21 2018-05-18 13:22:08 +02:00
5f4ab33069 1.2.20 2018-05-18 13:17:52 +02:00
14761a442e fix(ci): add build command to package.json 2018-05-18 13:17:52 +02:00
c4ee967ca9 1.2.19 2018-05-18 13:09:14 +02:00
551b8d0cde fix(package): include npmdocker with npm @gitzone scope 2018-05-18 13:09:14 +02:00
4f23b61e14 update description 2018-01-24 23:51:31 +01:00
799320492a 1.2.18 2017-10-13 18:06:16 +02:00
d2cd294b36 now cleaning up correctly 2017-10-13 18:06:12 +02:00
d3d3363e1b 1.2.17 2017-10-13 17:50:34 +02:00
ccd2cb6cbd update 2017-10-13 17:50:31 +02:00
0b0642f9e3 1.2.16 2017-10-13 16:44:31 +02:00
34753a4ae0 fix docker testing 2017-10-13 16:44:25 +02:00
977ecabb41 1.2.15 2017-10-07 16:48:47 +02:00
6d9d040a8d update ci 2017-10-07 16:48:44 +02:00
b2c08b7221 1.2.14 2017-10-07 14:15:35 +02:00
73b1a73719 update Analytics 2017-10-07 14:15:29 +02:00
449512e156 1.2.13 2017-10-07 14:02:27 +02:00
758e677bab update dependencies 2017-10-07 14:02:24 +02:00
42ffb4a8dd 1.2.12 2017-07-16 13:58:48 +02:00
9176b56035 update dependencies and greeting 2017-07-16 13:58:41 +02:00
b3d78f8ce5 1.2.11 2017-04-21 13:44:25 +02:00
b6a6aac8e5 now includes smartanalytics 2017-04-21 13:44:18 +02:00
32f5ead4f4 1.2.10 2017-04-20 21:57:47 +02:00
c29a261cb6 update to latest standards 2017-04-20 21:57:44 +02:00
de57d45947 1.2.9 2017-04-02 14:54:18 +02:00
00bf1532ca update readme 2017-04-02 14:54:15 +02:00
38fc672bf6 update ci 2017-04-02 14:51:38 +02:00
83771df11d 1.2.8 2017-04-02 14:48:27 +02:00
822e480bdf fix command execution 2017-04-02 14:48:23 +02:00
77c6ef99c1 1.2.7 2017-03-29 01:49:32 +02:00
8e3991c121 update gitlab ci yml 2017-03-29 01:49:27 +02:00
77477587b8 1.2.6 2017-03-29 01:04:43 +02:00
c7e32d6990 update ci 2017-03-29 01:04:25 +02:00
da1c2167d3 1.2.5 2017-03-29 01:01:42 +02:00
d55f7ef7f2 now running async and a lot faster 2017-03-29 01:01:37 +02:00
54 changed files with 11725 additions and 2372 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

26
.gitignore vendored
View File

@@ -1,8 +1,24 @@
node_modules/ .nogit/
# artifacts
coverage/ coverage/
pages/
public/ public/
test/ # installs
assets/ node_modules/
.nogit/
# caches
.yarn/
.cache/
.rpt2_cache
# builds
dist/
dist_*/
# AI
.claude/
.serena/
#------# custom
test

View File

@@ -1,43 +0,0 @@
image: hosttoday/ht-docker-dbase:npmts
services:
- docker:dind
stages:
- test
- release
- trigger
test:
stage: test
script:
- npm install
- npm test
tags:
- docker
- lossless
- priv
release:
image: hosttoday/ht-docker-node:npmts
stage: release
environment: npmjs-com_registry
script:
- npmci publish
only:
- tags
tags:
- docker
- lossless
- priv
trigger:
image: hosttoday/ht-docker-node:npmts
stage: trigger
script:
- npmci trigger
only:
- tags
tags:
- docker
- lossless
- priv

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"]
}
}
}
}
}
}
]
}

4
Dockerfile Normal file
View File

@@ -0,0 +1,4 @@
FROM hosttoday/ht-docker-node:stable
LABEL author="Lossless GmbH <office@lossless.com>"
RUN yarn global add npmci npmdocker npmts ts-node

View File

@@ -1,59 +0,0 @@
# npmdocker
develop npm modules cross platform with docker
## Availabililty
[![npm](https://gitzone.gitlab.io/assets/repo-button-npm.svg)](https://www.npmjs.com/package/npmdocker)
[![git](https://gitzone.gitlab.io/assets/repo-button-git.svg)](https://GitLab.com/gitzone/npmdocker)
[![git](https://gitzone.gitlab.io/assets/repo-button-mirror.svg)](https://github.com/gitzone/npmdocker)
[![docs](https://gitzone.gitlab.io/assets/repo-button-docs.svg)](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/npmdocker.svg)](https://www.npmjs.com/package/npmdocker)
[![Dependency Status](https://david-dm.org/gitzonetools/npmdocker.svg)](https://david-dm.org/gitzonetools/npmdocker)
[![bitHound Dependencies](https://www.bithound.io/github/gitzonetools/npmdocker/badges/dependencies.svg)](https://www.bithound.io/github/gitzonetools/npmdocker/master/dependencies/npm)
[![bitHound Code](https://www.bithound.io/github/gitzonetools/npmdocker/badges/code.svg)](https://www.bithound.io/github/gitzonetools/npmdocker)
[![TypeScript](https://img.shields.io/badge/TypeScript-2.x-blue.svg)](https://nodejs.org/dist/latest-v6.x/docs/api/)
[![node](https://img.shields.io/badge/node->=%206.x.x-blue.svg)](https://nodejs.org/dist/latest-v6.x/docs/api/)
[![JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)
## 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 | **&copy;** [Lossless GmbH](https://lossless.gmbh)
[![npm](https://gitzone.gitlab.io/assets/repo-footer.svg)](https://git.zone)

View File

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

417
changelog.md Normal file
View File

@@ -0,0 +1,417 @@
# Changelog
## 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
- Allow manager.build() to accept options and build only matching Dockerfiles (including dependencies) preserving topological order
- Add CLI parsing for build/push to accept positional Dockerfile patterns and --platform/--timeout flags
- Support single-platform override via docker buildx and multi-platform buildx detection
- 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').
- Use extractRepoVersion when checking tagToDockerfile and when mapping local base dockerfiles to ensure comparisons use repo:tag keys rather than full registry-prefixed references.
- 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
- 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)
- 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.

3
cli.js Executable file
View File

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

2
dist/cli.js vendored
View File

@@ -1,2 +0,0 @@
#!/usr/bin/env node
var index = require("../dist/index.js");

0
dist/index.d.ts vendored
View File

4
dist/index.js vendored
View File

@@ -1,4 +0,0 @@
"use strict";
const cli = require("./npmdocker.cli");
cli.run();
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQ0EsdUNBQXNDO0FBRXRDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsQ0FBQSJ9

View File

@@ -1 +0,0 @@
export declare let run: () => void;

49
dist/npmdocker.cli.js vendored
View File

@@ -1,49 +0,0 @@
"use strict";
const plugins = require("./npmdocker.plugins");
// modules
const ConfigModule = require("./npmdocker.config");
const DockerModule = require("./npmdocker.docker");
let npmdockerCli = new plugins.smartcli.Smartcli();
plugins.beautylog.ora.start();
exports.run = () => {
npmdockerCli.standardTask().then(argvArg => {
let done = plugins.q.defer();
ConfigModule.run()
.then(DockerModule.run)
.then((configArg) => {
done.resolve(configArg);
});
return done.promise;
}).then((configArg) => {
if (configArg.exitCode === 0) {
plugins.beautylog.success('container ended all right!');
}
else {
plugins.beautylog.error('container ended with error!');
process.exit(1);
}
});
npmdockerCli.addCommand('clean').then(argvArg => {
plugins.beautylog.ora.text('cleaning up docker env...');
if (argvArg.all) {
plugins.beautylog.ora.text('killing any running docker containers...');
plugins.shelljs.exec(`docker kill $(docker ps -q)`);
plugins.beautylog.ora.text('removing stopped containers...');
plugins.shelljs.exec(`docker rm $(docker ps -a -q)`);
plugins.beautylog.ora.text('removing images...');
plugins.shelljs.exec(`docker rmi $(docker images -q -f dangling=true)`);
plugins.beautylog.ora.text('removing all other images...');
plugins.shelljs.exec(`docker rmi $(docker images -a -q)`);
plugins.beautylog.ora.text('removing all volumes...');
plugins.shelljs.exec(`docker volume rm $(docker volume ls -f dangling=true -q)`);
}
plugins.beautylog.ora.endOk('docker environment now is clean!');
});
npmdockerCli.addCommand('speedtest').then(argvArg => {
plugins.beautylog.ora.text('cleaning up docker env...');
plugins.shelljs.exec(`docker pull tianon/speedtest && docker run --rm tianon/speedtest`);
plugins.beautylog.ora.endOk('docker environment now is clean!');
});
npmdockerCli.startParse();
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibnBtZG9ja2VyLmNsaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL25wbWRvY2tlci5jbGkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLCtDQUE4QztBQUc5QyxVQUFVO0FBQ1YsbURBQWtEO0FBQ2xELG1EQUFrRDtBQUVsRCxJQUFJLFlBQVksR0FBRyxJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsUUFBUSxFQUFFLENBQUE7QUFFbEQsT0FBTyxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUE7QUFDbEIsUUFBQSxHQUFHLEdBQUc7SUFDZixZQUFZLENBQUMsWUFBWSxFQUFFLENBQUMsSUFBSSxDQUFDLE9BQU87UUFDdEMsSUFBSSxJQUFJLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxLQUFLLEVBQUUsQ0FBQTtRQUM1QixZQUFZLENBQUMsR0FBRyxFQUFFO2FBQ2YsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUM7YUFDdEIsSUFBSSxDQUFDLENBQUMsU0FBUztZQUNkLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUE7UUFDekIsQ0FBQyxDQUFDLENBQUE7UUFDSixNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQTtJQUNyQixDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxTQUErQjtRQUN0QyxFQUFFLENBQUMsQ0FBQyxTQUFTLENBQUMsUUFBUSxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDN0IsT0FBTyxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsNEJBQTRCLENBQUMsQ0FBQTtRQUN6RCxDQUFDO1FBQUMsSUFBSSxDQUFDLENBQUM7WUFDTixPQUFPLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyw2QkFBNkIsQ0FBQyxDQUFBO1lBQ3RELE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUE7UUFDakIsQ0FBQztJQUNILENBQUMsQ0FBQyxDQUFBO0lBRUYsWUFBWSxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQyxJQUFJLENBQUMsT0FBTztRQUMzQyxPQUFPLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsMkJBQTJCLENBQUMsQ0FBQTtRQUN2RCxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztZQUNoQixPQUFPLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsMENBQTBDLENBQUMsQ0FBQTtZQUN0RSxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyw2QkFBNkIsQ0FBQyxDQUFBO1lBRW5ELE9BQU8sQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxnQ0FBZ0MsQ0FBQyxDQUFBO1lBQzVELE9BQU8sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLDhCQUE4QixDQUFDLENBQUE7WUFFcEQsT0FBTyxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLG9CQUFvQixDQUFDLENBQUE7WUFDaEQsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsaURBQWlELENBQUMsQ0FBQTtZQUV2RSxPQUFPLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsOEJBQThCLENBQUMsQ0FBQTtZQUMxRCxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxtQ0FBbUMsQ0FBQyxDQUFBO1lBRXpELE9BQU8sQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyx5QkFBeUIsQ0FBQyxDQUFBO1lBQ3JELE9BQU8sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLDBEQUEwRCxDQUFDLENBQUE7UUFDbEYsQ0FBQztRQUNELE9BQU8sQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxrQ0FBa0MsQ0FBQyxDQUFBO0lBQ2pFLENBQUMsQ0FBQyxDQUFBO0lBRUYsWUFBWSxDQUFDLFVBQVUsQ0FBQyxXQUFXLENBQUMsQ0FBQyxJQUFJLENBQUMsT0FBTztRQUMvQyxPQUFPLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsMkJBQTJCLENBQUMsQ0FBQTtRQUN2RCxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxrRUFBa0UsQ0FBQyxDQUFBO1FBQ3hGLE9BQU8sQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxrQ0FBa0MsQ0FBQyxDQUFBO0lBQ2pFLENBQUMsQ0FBQyxDQUFBO0lBRUYsWUFBWSxDQUFDLFVBQVUsRUFBRSxDQUFBO0FBQzNCLENBQUMsQ0FBQSJ9

View File

@@ -1,9 +0,0 @@
import { IKeyValueObject } from 'qenv';
export interface IConfig {
baseImage: string;
command: string;
dockerSock: boolean;
exitCode?: number;
keyValueObjectArray: IKeyValueObject[];
}
export declare let run: () => Promise<{}>;

View File

@@ -1,37 +0,0 @@
"use strict";
const plugins = require("./npmdocker.plugins");
const paths = require("./npmdocker.paths");
;
let getQenvKeyValueObject = () => {
let done = plugins.q.defer();
let qenvKeyValueObjectArray;
if (plugins.smartfile.fs.fileExistsSync(plugins.path.join(paths.cwd, 'qenv.yml'))) {
qenvKeyValueObjectArray = new plugins.qenv.Qenv(paths.cwd, '.nogit/').keyValueObjectArray;
}
else {
qenvKeyValueObjectArray = [];
}
;
done.resolve(qenvKeyValueObjectArray);
return done.promise;
};
let buildConfig = (qenvKeyValueObjectArrayArg) => {
let done = plugins.q.defer();
let npmextra = new plugins.npmextra.Npmextra(paths.cwd);
let config = npmextra.dataFor('npmdocker', {
baseImage: 'hosttoday/ht-docker-node:npmts',
command: 'npm test',
dockerSock: false,
keyValueObjectArray: qenvKeyValueObjectArrayArg
});
done.resolve(config);
return done.promise;
};
exports.run = () => {
let done = plugins.q.defer();
getQenvKeyValueObject()
.then(buildConfig)
.then(done.resolve);
return done.promise;
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibnBtZG9ja2VyLmNvbmZpZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL25wbWRvY2tlci5jb25maWcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLCtDQUE4QztBQUM5QywyQ0FBMEM7QUFXekMsQ0FBQztBQUVGLElBQUkscUJBQXFCLEdBQUc7SUFDMUIsSUFBSSxJQUFJLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxLQUFLLEVBQUUsQ0FBQTtJQUM1QixJQUFJLHVCQUEwQyxDQUFBO0lBQzlDLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDLGNBQWMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxFQUFFLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ2xGLHVCQUF1QixHQUFHLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxTQUFTLENBQUMsQ0FBQyxtQkFBbUIsQ0FBQTtJQUMzRixDQUFDO0lBQUMsSUFBSSxDQUFDLENBQUM7UUFDTix1QkFBdUIsR0FBRyxFQUFFLENBQUE7SUFDOUIsQ0FBQztJQUFBLENBQUM7SUFDRixJQUFJLENBQUMsT0FBTyxDQUFDLHVCQUF1QixDQUFDLENBQUE7SUFDckMsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUE7QUFDckIsQ0FBQyxDQUFBO0FBRUQsSUFBSSxXQUFXLEdBQUcsQ0FBQywwQkFBMkM7SUFDNUQsSUFBSSxJQUFJLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxLQUFLLEVBQUUsQ0FBQTtJQUM1QixJQUFJLFFBQVEsR0FBRyxJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQTtJQUN2RCxJQUFJLE1BQU0sR0FBRyxRQUFRLENBQUMsT0FBTyxDQUMzQixXQUFXLEVBQ1g7UUFDRSxTQUFTLEVBQUUsZ0NBQWdDO1FBQzNDLE9BQU8sRUFBRSxVQUFVO1FBQ25CLFVBQVUsRUFBRSxLQUFLO1FBQ2pCLG1CQUFtQixFQUFFLDBCQUEwQjtLQUNoRCxDQUNGLENBQUE7SUFDRCxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFBO0lBQ3BCLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFBO0FBQ3JCLENBQUMsQ0FBQTtBQUVVLFFBQUEsR0FBRyxHQUFHO0lBQ2YsSUFBSSxJQUFJLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxLQUFLLEVBQUUsQ0FBQTtJQUM1QixxQkFBcUIsRUFBRTtTQUNwQixJQUFJLENBQUMsV0FBVyxDQUFDO1NBQ2pCLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUE7SUFDckIsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUE7QUFDckIsQ0FBQyxDQUFBIn0=

View File

@@ -1 +0,0 @@
export declare let run: (configArg: any) => Promise<{}>;

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +0,0 @@
export declare let cwd: string;
export declare let packageBase: string;
export declare let assets: string;
export declare let buildContextDir: string;
export declare let dockerfile: string;

View File

@@ -1,12 +0,0 @@
"use strict";
const plugins = require("./npmdocker.plugins");
// directories
exports.cwd = process.cwd();
exports.packageBase = plugins.path.join(__dirname, "../");
exports.assets = plugins.path.join(exports.packageBase, "assets/");
plugins.smartfile.fs.ensureDirSync(exports.assets);
exports.buildContextDir = plugins.path.join(exports.assets, "buildContextDir");
plugins.smartfile.fs.ensureDirSync(exports.buildContextDir);
// files
exports.dockerfile = plugins.path.join(exports.assets, "Dockerfile");
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibnBtZG9ja2VyLnBhdGhzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvbnBtZG9ja2VyLnBhdGhzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSwrQ0FBK0M7QUFFL0MsY0FBYztBQUNILFFBQUEsR0FBRyxHQUFHLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQztBQUNwQixRQUFBLFdBQVcsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7QUFDbEQsUUFBQSxNQUFNLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsbUJBQVcsRUFBRSxTQUFTLENBQUMsQ0FBQztBQUM5RCxPQUFPLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxhQUFhLENBQUMsY0FBTSxDQUFDLENBQUM7QUFFaEMsUUFBQSxlQUFlLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsY0FBTSxFQUFDLGlCQUFpQixDQUFDLENBQUM7QUFDekUsT0FBTyxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsYUFBYSxDQUFDLHVCQUFlLENBQUMsQ0FBQztBQUVwRCxRQUFRO0FBQ0csUUFBQSxVQUFVLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsY0FBTSxFQUFFLFlBQVksQ0FBQyxDQUFDIn0=

View File

@@ -1,12 +0,0 @@
import 'typings-global';
import * as beautylog from 'beautylog';
import * as npmextra from 'npmextra';
import * as path from 'path';
import * as projectinfo from 'projectinfo';
import * as q from 'smartq';
import * as qenv from 'qenv';
import * as shelljs from 'shelljs';
import * as smartcli from 'smartcli';
import * as smartfile from 'smartfile';
import * as smartstring from 'smartstring';
export { beautylog, npmextra, path, projectinfo, q, qenv, shelljs, smartcli, smartfile, smartstring };

View File

@@ -1,23 +0,0 @@
"use strict";
require("typings-global");
const beautylog = require("beautylog");
exports.beautylog = beautylog;
const npmextra = require("npmextra");
exports.npmextra = npmextra;
const path = require("path");
exports.path = path;
const projectinfo = require("projectinfo");
exports.projectinfo = projectinfo;
const q = require("smartq");
exports.q = q;
const qenv = require("qenv");
exports.qenv = qenv;
const shelljs = require("shelljs");
exports.shelljs = shelljs;
const smartcli = require("smartcli");
exports.smartcli = smartcli;
const smartfile = require("smartfile");
exports.smartfile = smartfile;
const smartstring = require("smartstring");
exports.smartstring = smartstring;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibnBtZG9ja2VyLnBsdWdpbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9ucG1kb2NrZXIucGx1Z2lucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsMEJBQXVCO0FBQ3ZCLHVDQUFzQztBQVlwQyw4QkFBUztBQVhYLHFDQUFvQztBQVlsQyw0QkFBUTtBQVhWLDZCQUE0QjtBQVkxQixvQkFBSTtBQVhOLDJDQUEwQztBQVl4QyxrQ0FBVztBQVhiLDRCQUEyQjtBQVl6QixjQUFDO0FBWEgsNkJBQTRCO0FBWTFCLG9CQUFJO0FBWE4sbUNBQWtDO0FBWWhDLDBCQUFPO0FBWFQscUNBQW9DO0FBWWxDLDRCQUFRO0FBWFYsdUNBQXNDO0FBWXBDLDhCQUFTO0FBWFgsMkNBQTBDO0FBWXhDLGtDQUFXIn0=

View File

@@ -1,5 +0,0 @@
export interface IDockerfileSnippet {
baseImage: string;
command: string;
}
export declare let dockerfileSnippet: (optionsArg: IDockerfileSnippet) => string;

View File

@@ -1,21 +0,0 @@
"use strict";
const plugins = require("./npmdocker.plugins");
exports.dockerfileSnippet = (optionsArg) => {
let commandArray = optionsArg.command.split(/\s/);
let commandString = "";
for (let stringItem of commandArray) {
if (!(commandString == "")) {
commandString = commandString + ",";
}
commandString = commandString + '"' + stringItem + '"';
}
;
return plugins.smartstring.indent.normalize(`
FROM ${optionsArg.baseImage}
COPY ./buildContextDir /workspace
WORKDIR /workspace
ENV CI=true
CMD [${commandString}];
`);
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibnBtZG9ja2VyLnNuaXBwZXRzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvbnBtZG9ja2VyLnNuaXBwZXRzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSwrQ0FBK0M7QUFPcEMsUUFBQSxpQkFBaUIsR0FBRyxDQUFDLFVBQTZCO0lBQ3pELElBQUksWUFBWSxHQUFHLFVBQVUsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ2xELElBQUksYUFBYSxHQUFVLEVBQUUsQ0FBQztJQUM5QixHQUFHLENBQUEsQ0FBQyxJQUFJLFVBQVUsSUFBSSxZQUFZLENBQUMsQ0FBQSxDQUFDO1FBQ2hDLEVBQUUsQ0FBQSxDQUFDLENBQUMsQ0FBQyxhQUFhLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQSxDQUFDO1lBQ3ZCLGFBQWEsR0FBRyxhQUFhLEdBQUcsR0FBRyxDQUFDO1FBQ3hDLENBQUM7UUFDRCxhQUFhLEdBQUcsYUFBYSxHQUFHLEdBQUcsR0FBRyxVQUFVLEdBQUcsR0FBRyxDQUFDO0lBQzNELENBQUM7SUFBQSxDQUFDO0lBQ0YsTUFBTSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQztlQUNqQyxVQUFVLENBQUMsU0FBUzs7OztlQUlwQixhQUFhO0tBQ3ZCLENBQUMsQ0FBQztBQUNQLENBQUMsQ0FBQSJ9

View File

@@ -1,7 +1,27 @@
{ {
"npmts":{ "npmts": {
"mode":"default", "mode": "default",
"cli":true, "cli": true
"dockerSock":true },
"@git.zone/cli": {
"projectType": "npm",
"module": {
"githost": "gitlab.com",
"gitscope": "gitzone",
"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": []
}
} }

View File

@@ -1,27 +1,28 @@
{ {
"name": "npmdocker", "name": "@git.zone/tsdocker",
"version": "1.2.4", "version": "1.5.0",
"private": false,
"description": "develop npm modules cross platform with docker", "description": "develop npm modules cross platform with docker",
"main": "dist/index.js", "main": "dist_ts/index.js",
"typings": "dist/index.d.ts", "typings": "dist_ts/index.d.ts",
"bin": { "bin": {
"npmdocker": "dist/cli.js" "tsdocker": "cli.js"
},
"directories": {
"test": "test"
}, },
"scripts": { "scripts": {
"test": "(npm run clean && npm run compile && npm run setupCheck && npm run testStandard && npm run testSpeed && npm run testClean)", "test": "(npm run build)",
"testStandard": "(cd test/ && node ../dist/index.js)", "build": "(tsbuild)",
"testSpeed": "(cd test/ && node ../dist/index.js speedtest)", "testIntegration": "(npm run clean && npm run setupCheck && npm run testStandard)",
"testClean": "(cd test/ && node ../dist/index.js clean --all)", "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/)", "clean": "(rm -rf test/)",
"compile": "(npmts --notest)", "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": { "repository": {
"type": "git", "type": "git",
"url": "git+ssh://git@gitlab.com/gitzone/npmdocker.git" "url": "https://gitlab.com/gitzone/tsdocker.git"
}, },
"keywords": [ "keywords": [
"docker" "docker"
@@ -29,23 +30,46 @@
"author": "Lossless GmbH", "author": "Lossless GmbH",
"license": "MIT", "license": "MIT",
"bugs": { "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": { "devDependencies": {
"npmts-g": "^6.0.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": { "dependencies": {
"@types/shelljs": "0.x.x", "@push.rocks/lik": "^6.2.2",
"beautylog": "^6.1.1", "@push.rocks/npmextra": "^5.3.3",
"npmextra": "^2.0.3", "@push.rocks/projectinfo": "^5.0.2",
"projectinfo": "^3.0.1", "@push.rocks/qenv": "^6.1.3",
"qenv": "^1.1.3", "@push.rocks/smartanalytics": "^2.0.15",
"shelljs": "^0.7.6", "@push.rocks/smartcli": "^4.0.20",
"smartcli": "^2.0.1", "@push.rocks/smartfs": "^1.3.1",
"smartfile": "^4.1.5", "@push.rocks/smartlog": "^3.1.10",
"smartq": "^1.1.0", "@push.rocks/smartlog-destination-local": "^9.0.2",
"smartstring": "^2.0.22", "@push.rocks/smartlog-source-ora": "^1.0.9",
"typings-global": "^1.0.14" "@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.5.0',
description: 'develop npm modules cross platform with docker'
}

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

@@ -0,0 +1,519 @@
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, IBuildCommandOptions } from './interfaces/index.js';
import type { TsDockerManager } from './classes.tsdockermanager.js';
import * as fs from 'fs';
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;
// Extract repo:version from baseImage for comparison with cleanTag
// baseImage may include a registry prefix (e.g., "host.today/repo:version")
// but cleanTag is just "repo:version", so we strip the registry prefix
const baseImageKey = Dockerfile.extractRepoVersion(baseImage);
// Check if the baseImage is among the local Dockerfiles
if (tagToDockerfile.has(baseImageKey)) {
const baseDockerfile = tagToDockerfile.get(baseImageKey)!;
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) {
// Extract repo:version from baseImage for comparison with cleanTag
const baseImageKey = Dockerfile.extractRepoVersion(dockerfileArg.baseImage);
sortedDockerfileArray.forEach((dockfile2: Dockerfile) => {
if (dockfile2.cleanTag === baseImageKey) {
dockerfileArg.localBaseDockerfile = dockfile2;
}
});
}
});
return sortedDockerfileArray;
}
/**
* Builds the corresponding real docker image for each Dockerfile class instance
*/
public static async buildDockerfiles(
sortedArrayArg: Dockerfile[],
options?: { platform?: string; timeout?: number },
): Promise<Dockerfile[]> {
for (const dockerfileArg of sortedArrayArg) {
await dockerfileArg.build(options);
}
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 '';
}
});
}
/**
* Extracts the repo:version part from a full image reference, stripping any registry prefix.
* Examples:
* "registry.example.com/repo:version" -> "repo:version"
* "repo:version" -> "repo:version"
* "host.today/ht-docker-node:npmci" -> "ht-docker-node:npmci"
*/
private static extractRepoVersion(imageRef: string): string {
const parts = imageRef.split('/');
if (parts.length === 1) {
// No registry prefix: "repo:version"
return imageRef;
}
// Check if first part looks like a registry (contains '.' or ':' or is 'localhost')
const firstPart = parts[0];
const looksLikeRegistry =
firstPart.includes('.') || firstPart.includes(':') || firstPart === 'localhost';
if (looksLikeRegistry) {
// Strip registry: "registry.example.com/repo:version" -> "repo:version"
return parts.slice(1).join('/');
}
// No registry prefix, could be "org/repo:version"
return imageRef;
}
/**
* 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) {
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(options?: { platform?: string; timeout?: number }): Promise<void> {
logger.log('info', 'now building Dockerfile for ' + this.cleanTag);
const buildArgsString = await Dockerfile.getDockerBuildArgs(this.managerRef);
const config = this.managerRef.config;
const platformOverride = options?.platform;
const timeout = options?.timeout;
let buildCommand: string;
if (platformOverride) {
// Single platform override via buildx
buildCommand = `docker buildx build --platform ${platformOverride} --load -t ${this.buildTag} -f ${this.filePath} ${buildArgsString} .`;
} else 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} .`;
}
if (timeout) {
// Use streaming execution with timeout
const streaming = await smartshellInstance.execStreaming(buildCommand);
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => {
streaming.childProcess.kill();
reject(new Error(`Build timed out after ${timeout}s for ${this.cleanTag}`));
}, timeout * 1000);
});
const result = await Promise.race([streaming.finalPromise, timeoutPromise]);
if (result.exitCode !== 0) {
logger.log('error', `Build failed for ${this.cleanTag}`);
throw new Error(`Build failed for ${this.cleanTag}`);
}
} else {
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 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,302 @@
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, IBuildCommandOptions } 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 discovered Dockerfiles in dependency order.
* When options.patterns is provided, only matching Dockerfiles (and their dependencies) are built.
*/
public async build(options?: IBuildCommandOptions): Promise<Dockerfile[]> {
if (this.dockerfiles.length === 0) {
await this.discoverDockerfiles();
}
if (this.dockerfiles.length === 0) {
logger.log('warn', 'No Dockerfiles found');
return [];
}
// Determine which Dockerfiles to build
let toBuild = this.dockerfiles;
if (options?.patterns && options.patterns.length > 0) {
// Filter to matching Dockerfiles
const matched = this.dockerfiles.filter((df) => {
const basename = plugins.path.basename(df.filePath);
return options.patterns!.some((pattern) => {
if (pattern.includes('*') || pattern.includes('?')) {
// Convert glob pattern to regex
const regexStr = '^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$';
return new RegExp(regexStr).test(basename);
}
return basename === pattern;
});
});
if (matched.length === 0) {
logger.log('warn', `No Dockerfiles matched patterns: ${options.patterns.join(', ')}`);
return [];
}
// Resolve dependency chain and preserve topological order
toBuild = this.resolveWithDependencies(matched, this.dockerfiles);
logger.log('info', `Matched ${matched.length} Dockerfile(s), building ${toBuild.length} (including dependencies)`);
}
// Check if buildx is needed
if (options?.platform || (this.config.platforms && this.config.platforms.length > 1)) {
await this.ensureBuildx();
}
logger.log('info', `Building ${toBuild.length} Dockerfiles...`);
await Dockerfile.buildDockerfiles(toBuild, {
platform: options?.platform,
timeout: options?.timeout,
});
logger.log('success', 'All Dockerfiles built successfully');
return toBuild;
}
/**
* Resolves a set of target Dockerfiles to include all their local base image dependencies,
* preserving the original topological build order.
*/
private resolveWithDependencies(targets: Dockerfile[], allSorted: Dockerfile[]): Dockerfile[] {
const needed = new Set<Dockerfile>();
const addWithDeps = (df: Dockerfile) => {
if (needed.has(df)) return;
needed.add(df);
if (df.localBaseImageDependent && df.localBaseDockerfile) {
addWithDeps(df.localBaseDockerfile);
}
};
for (const df of targets) addWithDeps(df);
return allSorted.filter((df) => needed.has(df));
}
/**
* 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,4 +1,4 @@
import * as plugins from './npmdocker.plugins' import * as plugins from './tsdocker.plugins.js';
import * as cli from './npmdocker.cli' import * as cli from './tsdocker.cli.js';
cli.run() cli.run();

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

@@ -0,0 +1,79 @@
/**
* 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;
}
/**
* Options for the build command
*/
export interface IBuildCommandOptions {
patterns?: string[]; // Dockerfile name patterns (e.g., ['Dockerfile_base', 'Dockerfile_*'])
platform?: string; // Single platform override (e.g., 'linux/arm64')
timeout?: number; // Build timeout in seconds
}

View File

@@ -1,57 +0,0 @@
import * as plugins from './npmdocker.plugins'
import * as paths from './npmdocker.paths'
// modules
import * as ConfigModule from './npmdocker.config'
import * as DockerModule from './npmdocker.docker'
let npmdockerCli = new plugins.smartcli.Smartcli()
plugins.beautylog.ora.start()
export let run = () => {
npmdockerCli.standardTask().then(argvArg => {
let done = plugins.q.defer()
ConfigModule.run()
.then(DockerModule.run)
.then((configArg) => {
done.resolve(configArg)
})
return done.promise
}).then((configArg: ConfigModule.IConfig) => {
if (configArg.exitCode === 0) {
plugins.beautylog.success('container ended all right!')
} else {
plugins.beautylog.error('container ended with error!')
process.exit(1)
}
})
npmdockerCli.addCommand('clean').then(argvArg => {
plugins.beautylog.ora.text('cleaning up docker env...')
if (argvArg.all) {
plugins.beautylog.ora.text('killing any running docker containers...')
plugins.shelljs.exec(`docker kill $(docker ps -q)`)
plugins.beautylog.ora.text('removing stopped containers...')
plugins.shelljs.exec(`docker rm $(docker ps -a -q)`)
plugins.beautylog.ora.text('removing images...')
plugins.shelljs.exec(`docker rmi $(docker images -q -f dangling=true)`)
plugins.beautylog.ora.text('removing all other images...')
plugins.shelljs.exec(`docker rmi $(docker images -a -q)`)
plugins.beautylog.ora.text('removing all volumes...')
plugins.shelljs.exec(`docker volume rm $(docker volume ls -f dangling=true -q)`)
}
plugins.beautylog.ora.endOk('docker environment now is clean!')
})
npmdockerCli.addCommand('speedtest').then(argvArg => {
plugins.beautylog.ora.text('cleaning up docker env...')
plugins.shelljs.exec(`docker pull tianon/speedtest && docker run --rm tianon/speedtest`)
plugins.beautylog.ora.endOk('docker environment now is clean!')
})
npmdockerCli.startParse()
}

View File

@@ -1,49 +0,0 @@
import * as plugins from './npmdocker.plugins'
import * as paths from "./npmdocker.paths"
// interfaces
import { IKeyValueObject } from 'qenv'
export interface IConfig {
baseImage: string
command: string
dockerSock: boolean
exitCode?: number
keyValueObjectArray: IKeyValueObject[]
};
let getQenvKeyValueObject = () => {
let done = plugins.q.defer()
let qenvKeyValueObjectArray: IKeyValueObject[]
if (plugins.smartfile.fs.fileExistsSync(plugins.path.join(paths.cwd, 'qenv.yml'))) {
qenvKeyValueObjectArray = new plugins.qenv.Qenv(paths.cwd, '.nogit/').keyValueObjectArray
} else {
qenvKeyValueObjectArray = []
};
done.resolve(qenvKeyValueObjectArray)
return done.promise
}
let buildConfig = (qenvKeyValueObjectArrayArg: IKeyValueObject) => {
let done = plugins.q.defer()
let npmextra = new plugins.npmextra.Npmextra(paths.cwd)
let config = npmextra.dataFor<IConfig>(
'npmdocker',
{
baseImage: 'hosttoday/ht-docker-node:npmts',
command: 'npm test',
dockerSock: false,
keyValueObjectArray: qenvKeyValueObjectArrayArg
}
)
done.resolve(config)
return done.promise
}
export let run = () => {
let done = plugins.q.defer()
getQenvKeyValueObject()
.then(buildConfig)
.then(done.resolve)
return done.promise
}

View File

@@ -1,208 +0,0 @@
import * as plugins from './npmdocker.plugins';
import * as paths from './npmdocker.paths';
import * as snippets from './npmdocker.snippets'
// interfaces
import { IConfig } from './npmdocker.config'
let config: IConfig
/**
* the docker data used to build the internal testing container
*/
let dockerData = {
imageTag: 'npmdocker-temp-image:latest',
containerName: 'npmdocker-temp-container',
dockerProjectMountString: '',
dockerSockString: '',
dockerEnvString: ''
}
/**
* check if docker is available
*/
let checkDocker = () => {
let done = plugins.q.defer()
plugins.beautylog.ora.text('checking docker...')
if (plugins.shelljs.which('docker')) {
plugins.beautylog.ok('Docker found!')
done.resolve()
} else {
done.reject(new Error('docker not found on this machine'))
}
return done.promise
}
/**
* builds the Dockerfile according to the config in the project
*/
let buildDockerFile = () => {
let done = plugins.q.defer()
plugins.beautylog.ora.text('building Dockerfile...')
let dockerfile: string = snippets.dockerfileSnippet({
baseImage: config.baseImage,
command: config.command
})
plugins.beautylog.info(`Base image is: ${config.baseImage}`)
plugins.beautylog.info(`Command is: ${config.command}`)
plugins.smartfile.memory.toFsSync(dockerfile, paths.dockerfile)
plugins.beautylog.ok('Dockerfile created!')
done.resolve()
return done.promise
}
/**
* builds the Dockerimage from the built Dockerfile
*/
let buildDockerImage = () => {
let done = plugins.q.defer()
plugins.beautylog.ora.text('pulling latest base image from registry...')
plugins.shelljs.exec(
`docker pull ${config.baseImage}`,
{
silent: true
},
() => {
plugins.beautylog.ora.text('building Dockerimage...')
// are we creating a build context form project ?
if (process.env.CI === 'true') {
plugins.beautylog.ora.text('creating build context...')
plugins.smartfile.fs.copySync(paths.cwd, paths.buildContextDir)
}
plugins.shelljs.exec(
`docker build -f ${paths.dockerfile} -t ${dockerData.imageTag} ${paths.assets}`,
{
silent: true
},
() => {
plugins.beautylog.ok('Dockerimage built!')
done.resolve()
}
)
}
) // first pull latest version of baseImage
return done.promise
}
let buildDockerProjectMountString = () => {
let done = plugins.q.defer()
if (process.env.CI !== 'true') {
dockerData.dockerProjectMountString = `-v ${paths.cwd}:/workspace`
};
done.resolve()
return done.promise
}
/**
* builds an environment string that docker cli understands
*/
let buildDockerEnvString = () => {
let done = plugins.q.defer()
for (let keyValueObjectArg of config.keyValueObjectArray) {
let envString = dockerData.dockerEnvString = dockerData.dockerEnvString + `-e ${keyValueObjectArg.key}=${keyValueObjectArg.value} `
};
done.resolve()
return done.promise
}
/**
* creates string to mount the docker.sock inside the testcontainer
*/
let buildDockerSockString = () => {
let done = plugins.q.defer()
if (config.dockerSock) {
dockerData.dockerSockString = `-v /var/run/docker.sock:/var/run/docker.sock`
};
done.resolve()
return done
}
/**
* creates a container by running the built Dockerimage
*/
let runDockerImage = () => {
let done = plugins.q.defer()
plugins.beautylog.ora.text('starting Container...')
plugins.beautylog.ora.end()
plugins.beautylog.log('now running Dockerimage')
config.exitCode = plugins.shelljs.exec(`docker run ${dockerData.dockerProjectMountString} ${dockerData.dockerSockString} ${dockerData.dockerEnvString} --name ${dockerData.containerName} ${dockerData.imageTag}`).code
done.resolve()
return done.promise
}
/**
* cleans up: deletes the test container
*/
let deleteDockerContainer = () => {
let done = plugins.q.defer()
plugins.shelljs.exec(`docker rm -f ${dockerData.containerName}`, {
silent: true
})
done.resolve()
return done.promise
}
/**
* cleans up deletes the test image
*/
let deleteDockerImage = () => {
let done = plugins.q.defer()
plugins.shelljs.exec(`docker rmi ${dockerData.imageTag}`, {
silent: true
})
done.resolve()
return done.promise
}
/**
* cleans up, deletes the build context
*/
let deleteBuildContext = () => {
let done = plugins.q.defer()
plugins.smartfile.fs.remove(paths.buildContextDir)
.then(() => {
done.resolve()
})
return done.promise
}
let preClean = () => {
let done = plugins.q.defer()
deleteDockerImage()
.then(deleteDockerContainer)
.then(() => {
plugins.beautylog.ok('ensured clean Docker environment!')
done.resolve()
})
}
let postClean = () => {
let done = plugins.q.defer()
deleteDockerContainer()
.then(deleteDockerImage)
.then(deleteBuildContext)
.then(() => {
plugins.beautylog.ok('cleaned up!')
done.resolve()
})
}
export let run = (configArg) => {
let done = plugins.q.defer()
config = configArg
checkDocker()
.then(preClean)
.then(buildDockerFile)
.then(buildDockerImage)
.then(buildDockerProjectMountString)
.then(buildDockerEnvString)
.then(buildDockerSockString)
.then(runDockerImage)
.then(postClean)
.then(() => {
done.resolve(config);
}).catch(err => { console.log(err) })
return done.promise
}

View File

@@ -1,13 +0,0 @@
import * as plugins from "./npmdocker.plugins";
// 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);
export let buildContextDir = plugins.path.join(assets,"buildContextDir");
plugins.smartfile.fs.ensureDirSync(buildContextDir);
// files
export let dockerfile = plugins.path.join(assets, "Dockerfile");

View File

@@ -1,24 +0,0 @@
import 'typings-global'
import * as beautylog from 'beautylog'
import * as npmextra from 'npmextra'
import * as path from 'path'
import * as projectinfo from 'projectinfo'
import * as q from 'smartq'
import * as qenv from 'qenv'
import * as shelljs from 'shelljs'
import * as smartcli from 'smartcli'
import * as smartfile from 'smartfile'
import * as smartstring from 'smartstring'
export {
beautylog,
npmextra,
path,
projectinfo,
q,
qenv,
shelljs,
smartcli,
smartfile,
smartstring
}

View File

@@ -1,24 +0,0 @@
import * as plugins from "./npmdocker.plugins";
export interface IDockerfileSnippet {
baseImage:string;
command:string;
}
export let dockerfileSnippet = (optionsArg:IDockerfileSnippet):string => {
let commandArray = optionsArg.command.split(/\s/);
let commandString:string = "";
for(let stringItem of commandArray){
if(!(commandString == "")){
commandString = commandString + ",";
}
commandString = commandString + '"' + stringItem + '"';
};
return plugins.smartstring.indent.normalize(`
FROM ${optionsArg.baseImage}
COPY ./buildContextDir /workspace
WORKDIR /workspace
ENV CI=true
CMD [${commandString}];
`);
}

232
ts/tsdocker.cli.ts Normal file
View File

@@ -0,0 +1,232 @@
import * as plugins from './tsdocker.plugins.js';
import * as paths from './tsdocker.paths.js';
// modules
import * as ConfigModule from './tsdocker.config.js';
import * as DockerModule from './tsdocker.docker.js';
import { logger, ora } from './tsdocker.logging.js';
import { TsDockerManager } from './classes.tsdockermanager.js';
import type { IBuildCommandOptions } from './interfaces/index.js';
const tsdockerCli = new plugins.smartcli.Smartcli();
export let run = () => {
// Default command: run tests in container (legacy behavior)
tsdockerCli.standardCommand().subscribe(async argvArg => {
const configArg = await ConfigModule.run().then(DockerModule.run);
if (configArg.exitCode === 0) {
logger.log('success', 'container ended all right!');
} else {
logger.log('error', `container ended with error! Exit Code is ${configArg.exitCode}`);
process.exit(1);
}
});
/**
* Build Dockerfiles in dependency order
* Usage: tsdocker build [Dockerfile_patterns...] [--platform=linux/arm64] [--timeout=600]
*/
tsdockerCli.addCommand('build').subscribe(async argvArg => {
try {
const config = await ConfigModule.run();
const manager = new TsDockerManager(config);
await manager.prepare();
const buildOptions: IBuildCommandOptions = {};
const patterns = argvArg._.slice(1) as string[];
if (patterns.length > 0) {
buildOptions.patterns = patterns;
}
if (argvArg.platform) {
buildOptions.platform = argvArg.platform as string;
}
if (argvArg.timeout) {
buildOptions.timeout = Number(argvArg.timeout);
}
await manager.build(buildOptions);
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
* Usage: tsdocker push [Dockerfile_patterns...] [--platform=linux/arm64] [--timeout=600] [--registry=url]
*/
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();
// Parse build options from positional args and flags
const buildOptions: IBuildCommandOptions = {};
const patterns = argvArg._.slice(1) as string[];
if (patterns.length > 0) {
buildOptions.patterns = patterns;
}
if (argvArg.platform) {
buildOptions.platform = argvArg.platform as string;
}
if (argvArg.timeout) {
buildOptions.timeout = Number(argvArg.timeout);
}
// Build images first (if not already built)
await manager.build(buildOptions);
// Get registry from --registry flag
const registryArg = argvArg.registry as string | undefined;
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
*/
tsdockerCli.addCommand('runinside').subscribe(async argvArg => {
logger.log('ok', 'Allright. We are now in Docker!');
ora.text('now trying to run your specified command');
const configArg = await ConfigModule.run();
const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash'
});
ora.stop();
await smartshellInstance.exec(configArg.command).then(response => {
if (response.exitCode !== 0) {
process.exit(1);
}
});
});
tsdockerCli.addCommand('clean').subscribe(async argvArg => {
ora.text('cleaning up docker env...');
if (argvArg.all) {
const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash'
});
ora.text('killing any running docker containers...');
await smartshellInstance.exec(`docker kill $(docker ps -q)`);
ora.text('removing stopped containers...');
await smartshellInstance.exec(`docker rm $(docker ps -a -q)`);
ora.text('removing images...');
await smartshellInstance.exec(`docker rmi -f $(docker images -q -f dangling=true)`);
ora.text('removing all other images...');
await smartshellInstance.exec(`docker rmi $(docker images -a -q)`);
ora.text('removing all volumes...');
await smartshellInstance.exec(`docker volume rm $(docker volume ls -f dangling=true -q)`);
}
ora.finishSuccess('docker environment now is clean!');
});
tsdockerCli.addCommand('vscode').subscribe(async argvArg => {
const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash'
});
logger.log('ok', `Starting vscode in cwd ${paths.cwd}`);
await smartshellInstance.execAndWaitForLine(
`docker run -p 127.0.0.1:8443:8443 -v "${
paths.cwd
}:/home/coder/project" registry.gitlab.com/hosttoday/ht-docker-vscode --allow-http --no-auth`,
/Connected to shared process/
);
await plugins.smartopen.openUrl('testing-vscode.git.zone:8443');
});
tsdockerCli.startParse();
};

45
ts/tsdocker.config.ts Normal file
View File

@@ -0,0 +1,45 @@
import * as plugins from './tsdocker.plugins.js';
import * as paths from './tsdocker.paths.js';
import * as fs from 'fs';
import type { ITsDockerConfig } from './interfaces/index.js';
// Re-export ITsDockerConfig as IConfig for backward compatibility
export type IConfig = ITsDockerConfig & {
exitCode?: number;
};
const getQenvKeyValueObject = async () => {
let qenvKeyValueObjectArray: { [key: string]: string | number };
if (fs.existsSync(plugins.path.join(paths.cwd, 'qenv.yml'))) {
qenvKeyValueObjectArray = new plugins.qenv.Qenv(paths.cwd, '.nogit/').keyValueObject;
} else {
qenvKeyValueObjectArray = {};
}
return qenvKeyValueObjectArray;
};
const buildConfig = async (qenvKeyValueObjectArg: { [key: string]: string | number }) => {
const npmextra = new plugins.npmextra.Npmextra(paths.cwd);
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,
// New Docker build options
registries: [],
registryRepoMap: {},
buildArgEnvMap: {},
platforms: ['linux/amd64'],
push: false,
testDir: undefined,
});
return config;
};
export let run = async (): Promise<IConfig> => {
const config = await getQenvKeyValueObject().then(buildConfig);
return config;
};

169
ts/tsdocker.docker.ts Normal file
View File

@@ -0,0 +1,169 @@
import * as plugins from './tsdocker.plugins.js';
import * as paths from './tsdocker.paths.js';
import * as snippets from './tsdocker.snippets.js';
import { logger, ora } from './tsdocker.logging.js';
const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash'
});
// interfaces
import type { IConfig } from './tsdocker.config.js';
let config: IConfig;
/**
* the docker data used to build the internal testing container
*/
const dockerData = {
imageTag: 'npmdocker-temp-image:latest',
containerName: 'npmdocker-temp-container',
dockerProjectMountString: '',
dockerSockString: '',
dockerEnvString: ''
};
/**
* check if docker is available
*/
const checkDocker = () => {
const done = plugins.smartpromise.defer();
ora.text('checking docker...');
if (smartshellInstance.exec('which docker')) {
logger.log('ok', 'Docker found!');
done.resolve();
} else {
done.reject(new Error('docker not found on this machine'));
}
return done.promise;
};
/**
* builds the Dockerfile according to the config in the project
*/
const buildDockerFile = async () => {
const done = plugins.smartpromise.defer();
ora.text('building Dockerfile...');
const dockerfile: string = snippets.dockerfileSnippet({
baseImage: config.baseImage,
command: config.command
});
logger.log('info', `Base image is: ${config.baseImage}`);
logger.log('info', `Command is: ${config.command}`);
await plugins.smartfs.file(plugins.path.join(paths.cwd, 'npmdocker')).write(dockerfile);
logger.log('ok', 'Dockerfile created!');
ora.stop();
done.resolve();
return done.promise;
};
/**
* builds the Dockerimage from the built Dockerfile
*/
const buildDockerImage = async () => {
logger.log('info', 'pulling latest base image from registry...');
await smartshellInstance.exec(`docker pull ${config.baseImage}`);
ora.text('building Dockerimage...');
const execResult = await smartshellInstance.execSilent(
`docker build --load -f npmdocker -t ${dockerData.imageTag} ${paths.cwd}`
);
if (execResult.exitCode !== 0) {
console.log(execResult.stdout);
process.exit(1);
}
logger.log('ok', 'Dockerimage built!');
};
const buildDockerProjectMountString = async () => {
if (process.env.CI !== 'true') {
dockerData.dockerProjectMountString = `-v ${paths.cwd}:/workspace`;
}
};
/**
* builds an environment string that docker cli understands
*/
const buildDockerEnvString = async () => {
for (const key of Object.keys(config.keyValueObject)) {
const envString = (dockerData.dockerEnvString =
dockerData.dockerEnvString + `-e ${key}=${config.keyValueObject[key]} `);
}
};
/**
* creates string to mount the docker.sock inside the testcontainer
*/
const buildDockerSockString = async () => {
if (config.dockerSock) {
dockerData.dockerSockString = `-v /var/run/docker.sock:/var/run/docker.sock`;
}
};
/**
* creates a container by running the built Dockerimage
*/
const runDockerImage = async () => {
const done = plugins.smartpromise.defer();
ora.text('starting Container...');
ora.stop();
logger.log('info', 'now running Dockerimage');
config.exitCode = (await smartshellInstance.exec(
`docker run ${dockerData.dockerProjectMountString} ${dockerData.dockerSockString} ${
dockerData.dockerEnvString
} --name ${dockerData.containerName} ${dockerData.imageTag}`
)).exitCode;
};
/**
* cleans up: deletes the test container
*/
const deleteDockerContainer = async () => {
await smartshellInstance.execSilent(`docker rm -f ${dockerData.containerName}`);
};
/**
* cleans up deletes the test image
*/
const deleteDockerImage = async () => {
await smartshellInstance.execSilent(`docker rmi ${dockerData.imageTag}`).then(async response => {
if (response.exitCode !== 0) {
console.log(response.stdout);
}
});
};
const preClean = async () => {
await deleteDockerImage()
.then(deleteDockerContainer)
.then(async () => {
logger.log('ok', 'ensured clean Docker environment!');
});
};
const postClean = async () => {
await deleteDockerContainer()
.then(deleteDockerImage)
.then(async () => {
logger.log('ok', 'cleaned up!');
});
await plugins.smartfs.file(paths.npmdockerFile).delete();
};
export let run = async (configArg: IConfig): Promise<IConfig> => {
config = configArg;
const resultConfig = await checkDocker()
.then(preClean)
.then(buildDockerFile)
.then(buildDockerImage)
.then(buildDockerProjectMountString)
.then(buildDockerEnvString)
.then(buildDockerSockString)
.then(runDockerImage)
.then(postClean)
.catch(err => {
console.log(err);
});
return config;
};

17
ts/tsdocker.logging.ts Normal file
View File

@@ -0,0 +1,17 @@
import * as plugins from './tsdocker.plugins.js';
export const logger = new plugins.smartlog.Smartlog({
logContext: {
company: 'Some Company',
companyunit: 'Some CompanyUnit',
containerName: 'Some Containername',
environment: 'local',
runtime: 'node',
zone: 'gitzone'
},
minimumLogLevel: 'silly'
});
logger.addLogDestination(new plugins.smartlogDestinationLocal.DestinationLocal());
export const ora = new plugins.smartlogSouceOra.SmartlogSourceOra();

14
ts/tsdocker.paths.ts Normal file
View File

@@ -0,0 +1,14 @@
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/');
fs.mkdirSync(assets, { recursive: true });
export let npmdockerFile = plugins.path.join(cwd, 'npmdocker');

34
ts/tsdocker.plugins.ts Normal file
View File

@@ -0,0 +1,34 @@
// 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 '@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,
smartlog,
smartlogDestinationLocal,
smartlogSouceOra,
smartopen,
smartshell,
smartstring
};

34
ts/tsdocker.snippets.ts Normal file
View File

@@ -0,0 +1,34 @@
import * as plugins from './tsdocker.plugins.js';
export interface IDockerfileSnippet {
baseImage: string;
command: string;
}
let getMountSolutionString = (optionsArg: IDockerfileSnippet) => {
if (process.env.CI) {
return 'COPY ./ /workspace';
} else {
return '# not copying workspcae since not in CI';
}
};
let getGlobalPreparationString = (optionsArg: IDockerfileSnippet) => {
// Always install tsdocker to ensure the latest version is available
return 'RUN npm install -g @git.zone/tsdocker';
};
export let dockerfileSnippet = (optionsArg: IDockerfileSnippet): string => {
return plugins.smartstring.indent.normalize(
`
FROM ${optionsArg.baseImage}
# For info about what tsdocker does read the docs at https://gitzone.github.io/tsdocker
${getGlobalPreparationString(optionsArg)}
${getMountSolutionString(optionsArg)}
WORKDIR /workspace
ENV CI=true
ENTRYPOINT ["tsdocker"]
CMD ["runinside"]
`
);
};

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,3 +0,0 @@
{
"extends": "tslint-config-standard"
}

1482
yarn.lock

File diff suppressed because it is too large Load Diff