Compare commits

..

87 Commits

Author SHA1 Message Date
5d419e40c1 v7.0.0
Some checks failed
Publish to npm / npm-publish (push) Failing after 1m17s
Release / build-and-release (push) Successful in 2m5s
2026-02-06 16:12:41 +00:00
9d295f2633 BREAKING CHANGE(szci): delegate Docker operations to @git.zone/tsdocker, remove internal Docker managers and deprecated modules, simplify CLI and env var handling 2026-02-06 16:12:41 +00:00
5d18e53e30 update 2025-12-14 01:42:14 +00:00
9ad5222b95 update to deno 2025-12-13 13:27:51 +00:00
dba2e2ae68 chore: Remove deprecated Gitea workflows and add new CI/CD configurations 2025-10-28 06:38:02 +00:00
6c8095260d 6.0.1
Some checks failed
Default (tags) / security (push) Failing after 22s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-10-26 17:44:43 +00:00
95c4ae3b16 fix(tests): Migrate tests to Deno native runner and update Deno config 2025-10-26 17:44:43 +00:00
536ebca6ab 6.0.0
Some checks failed
Default (tags) / security (push) Failing after 22s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-10-26 15:23:56 +00:00
88f64536c2 BREAKING CHANGE(szci): Rename project from npmci to szci and migrate runtime to Deno; add compiled binaries, installer and wrapper; update imports, env handling and package metadata 2025-10-26 15:23:56 +00:00
4854d27a19 4.1.37
Some checks failed
Default (tags) / security (push) Failing after 15s
Default (tags) / test (push) Successful in 1m28s
Default (tags) / metadata (push) Failing after 11s
Default (tags) / release (push) Failing after 22s
2024-11-17 01:01:22 +01:00
75a0e8a7d8 fix(docker): Enhanced base image extraction logic from Dockerfile 2024-11-17 01:01:22 +01:00
43eb19f772 4.1.36
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Successful in 1m26s
Default (tags) / metadata (push) Failing after 11s
Default (tags) / release (push) Failing after 21s
2024-11-17 00:50:43 +01:00
dc2665d250 fix(docker): Improve logging for Dockerfile build order with base image details. 2024-11-17 00:50:43 +01:00
00f324e151 4.1.35
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Successful in 1m32s
Default (tags) / metadata (push) Failing after 10s
Default (tags) / release (push) Failing after 21s
2024-11-17 00:32:56 +01:00
e38cc40f11 fix(docker): Fix Dockerfile dependency sorting and enhance environment variable handling for GitHub repos 2024-11-17 00:32:56 +01:00
e9e8acafe4 4.1.34 2024-11-05 02:39:53 +01:00
c763db40bb fix(connector): Remove unused typedrequest implementation in cloudlyconnector 2024-11-05 02:39:53 +01:00
01256480c4 4.1.33 2024-11-05 02:38:21 +01:00
c6918399bf fix(core): Updated dependencies and improved npm preparation logic. 2024-11-05 02:38:21 +01:00
66d28e5081 4.1.32 2024-10-23 21:50:30 +02:00
9de77139ea fix(dependencies): Update project dependencies to latest versions 2024-10-23 21:50:30 +02:00
7b4bf10cc0 4.1.31 2024-05-24 15:54:33 +02:00
9eaa6347c1 fix(core): update 2024-05-24 15:54:32 +02:00
6e4c967917 4.1.30 2024-05-24 00:17:31 +02:00
52a8f42fc9 fix(core): update 2024-05-24 00:17:31 +02:00
8e9a61bbb2 4.1.29 2024-05-23 21:47:07 +02:00
dc809a6023 fix(core): update 2024-05-23 21:47:06 +02:00
7eeca992b0 4.1.28 2024-05-17 16:23:48 +02:00
d018102014 fix(core): update 2024-05-17 16:23:47 +02:00
838f2d6959 4.1.27 2023-08-24 17:25:56 +02:00
0d42e5f6eb fix(core): update 2023-08-24 17:25:55 +02:00
f96de8cdc3 4.1.26 2023-08-09 11:26:22 +02:00
965833916f fix(core): update 2023-08-09 11:26:22 +02:00
81ec1391e3 4.1.25 2023-08-09 11:23:57 +02:00
bbbca44640 fix(core): update 2023-08-09 11:23:56 +02:00
7ba7dc54a1 4.1.24 2023-08-08 23:32:08 +02:00
4f98eeec2a fix(core): update 2023-08-08 23:32:07 +02:00
419a6b9e5f 4.1.23 2023-07-13 16:00:47 +02:00
9c040c34f5 fix(core): update 2023-07-13 16:00:47 +02:00
50d3f2d795 4.1.22 2023-07-12 18:23:32 +02:00
305856b9e1 fix(core): update 2023-07-12 18:23:31 +02:00
3683743f3c 4.1.21 2023-07-12 15:35:39 +02:00
41237e0e5f fix(core): update 2023-07-12 15:35:38 +02:00
bce84a0e74 4.1.20 2023-07-02 00:10:28 +02:00
79bed919d8 fix(core): update 2023-07-02 00:10:27 +02:00
4b202ce00e 4.1.19 2023-07-01 22:09:54 +02:00
16ca787314 fix(core): update 2023-07-01 22:09:54 +02:00
3a9b6f658a 4.1.18 2023-07-01 22:05:44 +02:00
5c5dbf303f fix(core): update 2023-07-01 22:05:43 +02:00
4c07131e51 4.1.17 2023-07-01 19:23:17 +02:00
ba039469ff fix(core): update 2023-07-01 19:23:17 +02:00
49f2498ecd 4.1.16 2023-07-01 19:02:07 +02:00
1060060366 fix(core): update 2023-07-01 19:02:06 +02:00
0fa3a579f7 4.1.15 2023-06-26 10:09:25 +02:00
0e4556d59e fix(core): update 2023-06-26 10:09:24 +02:00
e2e7967fba 4.1.14 2023-06-26 09:28:19 +02:00
45b8d67abf fix(core): update 2023-06-26 09:28:19 +02:00
0a69aa5d62 4.1.13 2023-06-26 02:43:45 +02:00
dfbab1a1df fix(core): update 2023-06-26 02:43:45 +02:00
e58f009a24 4.1.12 2023-06-26 01:03:44 +02:00
2afd9cddc5 fix(core): update 2023-06-26 01:03:44 +02:00
d79c5366ef 4.1.11 2023-06-26 00:09:56 +02:00
8e4f7ad244 fix(core): update 2023-06-26 00:09:54 +02:00
39de3a1601 4.1.10 2023-06-25 23:59:26 +02:00
cb3d2964d1 fix(core): update 2023-06-25 23:59:25 +02:00
6b5390cef8 4.1.9 2023-06-25 23:26:25 +02:00
2736b85de3 fix(core): update 2023-06-25 23:26:25 +02:00
82d7778f59 4.1.8 2023-06-25 23:18:23 +02:00
8c99cc0491 fix(core): update 2023-06-25 23:18:23 +02:00
955e3d0dbe 4.1.7 2023-06-25 23:16:29 +02:00
702ae8bed8 fix(core): update 2023-06-25 23:16:29 +02:00
b6f0723b75 4.1.6 2023-06-25 22:23:31 +02:00
8a2fb30e59 fix(core): update 2023-06-25 22:23:30 +02:00
95b4030120 4.1.5 2023-06-25 21:15:51 +02:00
5c77cfbdc2 fix(core): update 2023-06-25 21:15:50 +02:00
5ea42320a9 4.1.4 2023-05-07 22:35:19 +02:00
d07ebfc9c6 fix(core): update 2023-05-07 22:35:18 +02:00
bbb5718184 4.1.3 2023-05-07 21:30:59 +02:00
0d8b54637c fix(core): update 2023-05-07 21:30:58 +02:00
e51b2e28b9 4.1.2 2022-11-02 18:57:47 +01:00
f767140cc8 fix(core): update 2022-11-02 18:57:47 +01:00
0d4d69f072 4.1.1 2022-11-02 16:43:54 +01:00
a3e628c43f fix(core): update 2022-11-02 16:43:54 +01:00
a58fa135c1 4.1.0 2022-10-24 21:23:14 +02:00
93c7af6c91 feat(precheck): now includes a precheck for more generic runner execution 2022-10-24 21:23:14 +02:00
ad0e12bf7b 4.0.11 2022-10-23 17:19:42 +02:00
498dd6eff6 fix(core): update 2022-10-23 17:19:41 +02:00
67 changed files with 2774 additions and 5913 deletions

View File

@@ -0,0 +1,31 @@
## SZCI {{VERSION}}
Pre-compiled binaries for multiple platforms.
### Installation
#### Option 1: Via npm (recommended)
```bash
npm install -g @ship.zone/szci
```
#### Option 2: Via installer script
```bash
curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/master/install.sh | sudo bash
```
#### Option 3: Direct binary download
Download the appropriate binary for your platform from the assets below and make it executable.
### Supported Platforms
- Linux x86_64 (x64)
- Linux ARM64 (aarch64)
- macOS x86_64 (Intel)
- macOS ARM64 (Apple Silicon)
- Windows x86_64
### Checksums
SHA256 checksums are provided in `SHA256SUMS.txt` for binary verification.
### npm Package
The npm package includes automatic binary detection and installation for your platform.

122
.gitea/workflows/README.md Normal file
View File

@@ -0,0 +1,122 @@
# SZCI Gitea CI/CD Workflows
This directory contains Gitea Actions workflows for automated CI/CD of the SZCI project.
## Workflows
### 1. CI (`ci.yml`)
**Trigger:** Push to `master` branch or pull requests
**Purpose:** Continuous integration checks on every push
**Jobs:**
- **Type Check & Lint** - Validates TypeScript types and code style
- **Build Test** - Compiles binary for Linux x64 and tests execution
- **Build All Platforms** - Compiles all 5 platform binaries and uploads as artifacts
**Usage:** Automatically runs on every commit to ensure code quality
---
### 2. Release (`release.yml`)
**Trigger:** Push of version tags (e.g., `v5.0.0`)
**Purpose:** Creates GitHub releases with pre-compiled binaries
**Jobs:**
1. Validates deno.json version matches git tag
2. Compiles binaries for all 5 platforms
3. Generates SHA256 checksums
4. Creates Gitea release with binaries as assets
5. Cleans up old releases (keeps last 3)
**Usage:**
```bash
# Update version in deno.json to 6.0.1
# Then create and push tag:
git tag v6.0.1
git push origin v6.0.1
```
**Output:** Release at `https://code.foss.global/ship.zone/szci/releases/tag/v6.0.1`
---
### 3. NPM Publish (`npm-publish.yml`)
**Trigger:** Push of version tags (e.g., `v5.0.0`)
**Purpose:** Publishes package to npm registry
**Jobs:**
1. Validates deno.json version matches git tag
2. Compiles all binaries
3. Syncs package.json version
4. Creates npm package
5. Publishes to npm with access token
6. Verifies publication
**Prerequisites:**
- `NPM_TOKEN` must be set in Gitea secrets
**Usage:** Same as release workflow (automatically triggered by version tags)
**Output:** Package at `https://www.npmjs.com/package/@ship.zone/szci`
---
## Release Process
To create a new release:
1. **Update version in deno.json:**
```json
{
"version": "6.0.1"
}
```
2. **Commit the version change:**
```bash
git add deno.json
git commit -m "6.0.1"
```
3. **Create and push tag:**
```bash
git tag v6.0.1
git push origin master
git push origin v6.0.1
```
4. **Workflows run automatically:**
- `release.yml` creates Gitea release with binaries
- `npm-publish.yml` publishes to npm
5. **Verify:**
- Check https://code.foss.global/ship.zone/szci/releases
- Check https://www.npmjs.com/package/@ship.zone/szci
---
## Secrets Required
Configure these in Gitea repository settings:
- `GITHUB_TOKEN` - Gitea access token (auto-provided)
- `NPM_TOKEN` - npm publish token (must be configured)
---
## Binary Artifacts
Each workflow produces binaries for:
- Linux x86_64
- Linux ARM64
- macOS x86_64
- macOS ARM64
- Windows x86_64
Total size per release: ~4GB (5 binaries × ~800MB each)

82
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,82 @@
name: CI
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
check:
name: Type Check & Lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Deno
uses: denoland/setup-deno@v1
with:
deno-version: v2.x
- name: Check TypeScript types
run: deno check mod.ts
- name: Lint code
run: deno lint
continue-on-error: true
- name: Format check
run: deno fmt --check
continue-on-error: true
build:
name: Build Test (Current Platform)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Deno
uses: denoland/setup-deno@v1
with:
deno-version: v2.x
- name: Compile for current platform
run: |
echo "Testing compilation for Linux x86_64..."
deno compile --allow-all --no-check \
--output szci-test \
--target x86_64-unknown-linux-gnu mod.ts
- name: Test binary execution
run: |
chmod +x szci-test
./szci-test --version
build-all:
name: Build All Platforms
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Deno
uses: denoland/setup-deno@v1
with:
deno-version: v2.x
- name: Compile all platform binaries
run: bash scripts/compile-all.sh
- name: Upload all binaries as artifact
uses: actions/upload-artifact@v3
with:
name: szci-binaries.zip
path: dist/binaries/*
retention-days: 30

View File

@@ -0,0 +1,129 @@
name: Publish to npm
on:
push:
tags:
- 'v*'
jobs:
npm-publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Deno
uses: denoland/setup-deno@v1
with:
deno-version: v2.x
- name: Setup Node.js for npm publishing
uses: actions/setup-node@v4
with:
node-version: '18.x'
registry-url: 'https://registry.npmjs.org/'
- name: Get version from tag
id: version
run: |
VERSION=${GITHUB_REF#refs/tags/}
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "version_number=${VERSION#v}" >> $GITHUB_OUTPUT
echo "Publishing version: $VERSION"
- name: Verify deno.json version matches tag
run: |
DENO_VERSION=$(grep -o '"version": "[^"]*"' deno.json | cut -d'"' -f4)
TAG_VERSION="${{ steps.version.outputs.version_number }}"
echo "deno.json version: $DENO_VERSION"
echo "Tag version: $TAG_VERSION"
if [ "$DENO_VERSION" != "$TAG_VERSION" ]; then
echo "ERROR: Version mismatch!"
echo "deno.json has version $DENO_VERSION but tag is $TAG_VERSION"
exit 1
fi
- name: Compile binaries for npm package
run: |
echo "Compiling binaries for npm package..."
deno task compile
echo ""
echo "Binary sizes:"
ls -lh dist/binaries/
- name: Generate SHA256 checksums
run: |
cd dist/binaries
sha256sum * > SHA256SUMS
cat SHA256SUMS
cd ../..
- name: Sync package.json version
run: |
VERSION="${{ steps.version.outputs.version_number }}"
echo "Syncing package.json to version ${VERSION}..."
npm version ${VERSION} --no-git-tag-version --allow-same-version
echo "package.json version: $(grep '"version"' package.json | head -1)"
- name: Create npm package
run: |
echo "Creating npm package..."
npm pack
echo ""
echo "Package created:"
ls -lh *.tgz
- name: Test local installation
run: |
echo "Testing local package installation..."
PACKAGE_FILE=$(ls *.tgz)
npm install -g ${PACKAGE_FILE}
echo ""
echo "Testing szci command:"
szci --version || echo "Note: Binary execution may fail in CI environment"
echo ""
echo "Checking installed files:"
npm ls -g @ship.zone/szci || true
- name: Publish to npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
echo "Publishing to npm registry..."
npm publish --access public
echo ""
echo "✅ Successfully published @ship.zone/szci to npm!"
echo ""
echo "Package info:"
npm view @ship.zone/szci
- name: Verify npm package
run: |
echo "Waiting for npm propagation..."
sleep 30
echo ""
echo "Verifying published package..."
npm view @ship.zone/szci
echo ""
echo "Testing installation from npm:"
npm install -g @ship.zone/szci
echo ""
echo "Package installed successfully!"
which szci || echo "Binary location check skipped"
- name: Publish Summary
run: |
echo "================================================"
echo " npm Publish Complete!"
echo "================================================"
echo ""
echo "✅ Package: @ship.zone/szci"
echo "✅ Version: ${{ steps.version.outputs.version }}"
echo ""
echo "Installation:"
echo " npm install -g @ship.zone/szci"
echo ""
echo "Registry:"
echo " https://www.npmjs.com/package/@ship.zone/szci"
echo ""

View File

@@ -0,0 +1,248 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
build-and-release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Deno
uses: denoland/setup-deno@v1
with:
deno-version: v2.x
- name: Get version from tag
id: version
run: |
VERSION=${GITHUB_REF#refs/tags/}
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "version_number=${VERSION#v}" >> $GITHUB_OUTPUT
echo "Building version: $VERSION"
- name: Verify deno.json version matches tag
run: |
DENO_VERSION=$(grep -o '"version": "[^"]*"' deno.json | cut -d'"' -f4)
TAG_VERSION="${{ steps.version.outputs.version_number }}"
echo "deno.json version: $DENO_VERSION"
echo "Tag version: $TAG_VERSION"
if [ "$DENO_VERSION" != "$TAG_VERSION" ]; then
echo "ERROR: Version mismatch!"
echo "deno.json has version $DENO_VERSION but tag is $TAG_VERSION"
exit 1
fi
- name: Compile binaries for all platforms
run: |
echo "================================================"
echo " SZCI Release Compilation"
echo " Version: ${{ steps.version.outputs.version }}"
echo "================================================"
echo ""
# Clean up old binaries and create fresh directory
rm -rf dist/binaries
mkdir -p dist/binaries
echo "→ Cleaned old binaries from dist/binaries"
echo ""
# Linux x86_64
echo "→ Compiling for Linux x86_64..."
deno compile --allow-all --no-check \
--output dist/binaries/szci-linux-x64 \
--target x86_64-unknown-linux-gnu mod.ts
echo " ✓ Linux x86_64 complete"
# Linux ARM64
echo "→ Compiling for Linux ARM64..."
deno compile --allow-all --no-check \
--output dist/binaries/szci-linux-arm64 \
--target aarch64-unknown-linux-gnu mod.ts
echo " ✓ Linux ARM64 complete"
# macOS x86_64
echo "→ Compiling for macOS x86_64..."
deno compile --allow-all --no-check \
--output dist/binaries/szci-macos-x64 \
--target x86_64-apple-darwin mod.ts
echo " ✓ macOS x86_64 complete"
# macOS ARM64
echo "→ Compiling for macOS ARM64..."
deno compile --allow-all --no-check \
--output dist/binaries/szci-macos-arm64 \
--target aarch64-apple-darwin mod.ts
echo " ✓ macOS ARM64 complete"
# Windows x86_64
echo "→ Compiling for Windows x86_64..."
deno compile --allow-all --no-check \
--output dist/binaries/szci-windows-x64.exe \
--target x86_64-pc-windows-msvc mod.ts
echo " ✓ Windows x86_64 complete"
echo ""
echo "All binaries compiled successfully!"
ls -lh dist/binaries/
- name: Generate SHA256 checksums
run: |
cd dist/binaries
sha256sum * > SHA256SUMS.txt
cat SHA256SUMS.txt
cd ../..
- name: Extract changelog for this version
id: changelog
run: |
VERSION="${{ steps.version.outputs.version }}"
# Check if changelog.md exists
if [ ! -f changelog.md ]; then
echo "No changelog.md found, using default release notes"
cat > /tmp/release_notes.md << EOF
## SZCI $VERSION
Pre-compiled binaries for multiple platforms.
### Installation
Use the installation script:
\`\`\`bash
curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/master/install.sh | sudo bash
\`\`\`
Or download the binary for your platform and make it executable.
### Supported Platforms
- Linux x86_64 (x64)
- Linux ARM64 (aarch64)
- macOS x86_64 (Intel)
- macOS ARM64 (Apple Silicon)
- Windows x86_64
### Checksums
SHA256 checksums are provided in SHA256SUMS.txt
EOF
else
# Try to extract section for this version from changelog.md
awk "/## \[$VERSION\]/,/## \[/" changelog.md | sed '$d' > /tmp/release_notes.md || cat > /tmp/release_notes.md << EOF
## SZCI $VERSION
See changelog.md for full details.
### Installation
Use the installation script:
\`\`\`bash
curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/master/install.sh | sudo bash
\`\`\`
EOF
fi
echo "Release notes:"
cat /tmp/release_notes.md
- name: Delete existing release if it exists
run: |
VERSION="${{ steps.version.outputs.version }}"
echo "Checking for existing release $VERSION..."
# Try to get existing release by tag
EXISTING_RELEASE_ID=$(curl -s \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
"https://code.foss.global/api/v1/repos/ship.zone/szci/releases/tags/$VERSION" \
| jq -r '.id // empty')
if [ -n "$EXISTING_RELEASE_ID" ]; then
echo "Found existing release (ID: $EXISTING_RELEASE_ID), deleting..."
curl -X DELETE -s \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
"https://code.foss.global/api/v1/repos/ship.zone/szci/releases/$EXISTING_RELEASE_ID"
echo "Existing release deleted"
sleep 2
else
echo "No existing release found, proceeding with creation"
fi
- name: Create Gitea Release
run: |
VERSION="${{ steps.version.outputs.version }}"
RELEASE_NOTES=$(cat /tmp/release_notes.md)
# Create the release
echo "Creating release for $VERSION..."
RELEASE_ID=$(curl -X POST -s \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/json" \
"https://code.foss.global/api/v1/repos/ship.zone/szci/releases" \
-d "{
\"tag_name\": \"$VERSION\",
\"name\": \"SZCI $VERSION\",
\"body\": $(jq -Rs . /tmp/release_notes.md),
\"draft\": false,
\"prerelease\": false
}" | jq -r '.id')
echo "Release created with ID: $RELEASE_ID"
# Upload binaries as release assets
for binary in dist/binaries/*; do
filename=$(basename "$binary")
echo "Uploading $filename..."
curl -X POST -s \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary "@$binary" \
"https://code.foss.global/api/v1/repos/ship.zone/szci/releases/$RELEASE_ID/assets?name=$filename"
done
echo "All assets uploaded successfully"
- name: Clean up old releases
run: |
echo "Cleaning up old releases (keeping only last 3)..."
# Fetch all releases sorted by creation date
RELEASES=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
"https://code.foss.global/api/v1/repos/ship.zone/szci/releases" | \
jq -r 'sort_by(.created_at) | reverse | .[3:] | .[].id')
# Delete old releases
if [ -n "$RELEASES" ]; then
echo "Found releases to delete:"
for release_id in $RELEASES; do
echo " Deleting release ID: $release_id"
curl -X DELETE -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
"https://code.foss.global/api/v1/repos/ship.zone/szci/releases/$release_id"
done
echo "Old releases deleted successfully"
else
echo "No old releases to delete (less than 4 releases total)"
fi
echo ""
- name: Release Summary
run: |
echo "================================================"
echo " Release ${{ steps.version.outputs.version }} Complete!"
echo "================================================"
echo ""
echo "Binaries published:"
ls -lh dist/binaries/
echo ""
echo "Release URL:"
echo "https://code.foss.global/ship.zone/szci/releases/tag/${{ steps.version.outputs.version }}"
echo ""
echo "Installation command:"
echo "curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/master/install.sh | sudo bash"
echo ""

4
.gitignore vendored
View File

@@ -17,4 +17,8 @@ node_modules/
dist/ dist/
dist_*/ dist_*/
# deno
deno.lock
.deno/
# custom # custom

View File

@@ -1,127 +0,0 @@
# gitzone ci_default
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
cache:
paths:
- .npmci_cache/
key: '$CI_BUILD_STAGE'
stages:
- security
- test
- release
- metadata
before_script:
- pnpm install -g pnpm
- pnpm install -g @shipzone/pnpm
- npmci npm prepare
# ====================
# security stage
# ====================
# ====================
# security stage
# ====================
auditProductionDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci command npm config set registry https://registry.npmjs.org
- npmci command pnpm audit --audit-level=high --prod
tags:
- lossless
- docker
auditDevDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci command npm config set registry https://registry.npmjs.org
- npmci command pnpm audit --audit-level=high --dev
tags:
- lossless
- docker
allow_failure: true
# ====================
# test stage
# ====================
testStable:
stage: test
script:
- npmci node install stable
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
testBuild:
stage: test
script:
- npmci node install stable
- npmci npm install
- npmci command npm run build
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
release:
stage: release
script:
- npmci node install stable
- npmci npm publish
only:
- tags
tags:
- lossless
- docker
- notpriv
# ====================
# metadata stage
# ====================
codequality:
stage: metadata
allow_failure: true
only:
- tags
script:
- npmci command npm install -g typescript
- npmci npm prepare
- npmci npm install
tags:
- lossless
- docker
- priv
trigger:
stage: metadata
script:
- npmci trigger
only:
- tags
tags:
- lossless
- docker
- notpriv
pages:
stage: metadata
script:
- npmci node install stable
- npmci npm install
- npmci command npm run buildDocs
tags:
- lossless
- docker
- notpriv
only:
- tags
artifacts:
expire_in: 1 week
paths:
- public
allow_failure: true

View File

@@ -5,9 +5,9 @@
"schema": { "schema": {
"type": "object", "type": "object",
"properties": { "properties": {
"npmci": { "szci": {
"type": "object", "type": "object",
"description": "settings for npmci" "description": "settings for szci"
}, },
"gitzone": { "gitzone": {
"type": "object", "type": "object",

108
bin/szci-wrapper.js Executable file
View File

@@ -0,0 +1,108 @@
#!/usr/bin/env node
/**
* SZCI npm wrapper
* This script executes the appropriate pre-compiled binary based on the current platform
*/
import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { existsSync } from 'fs';
import { platform, arch } from 'os';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/**
* Get the binary name for the current platform
*/
function getBinaryName() {
const plat = platform();
const architecture = arch();
// Map Node's platform/arch to our binary naming
const platformMap = {
'darwin': 'macos',
'linux': 'linux',
'win32': 'windows'
};
const archMap = {
'x64': 'x64',
'arm64': 'arm64'
};
const mappedPlatform = platformMap[plat];
const mappedArch = archMap[architecture];
if (!mappedPlatform || !mappedArch) {
console.error(`Error: Unsupported platform/architecture: ${plat}/${architecture}`);
console.error('Supported platforms: Linux, macOS, Windows');
console.error('Supported architectures: x64, arm64');
process.exit(1);
}
// Construct binary name
let binaryName = `szci-${mappedPlatform}-${mappedArch}`;
if (plat === 'win32') {
binaryName += '.exe';
}
return binaryName;
}
/**
* Execute the binary
*/
function executeBinary() {
const binaryName = getBinaryName();
const binaryPath = join(__dirname, '..', 'dist', 'binaries', binaryName);
// Check if binary exists
if (!existsSync(binaryPath)) {
console.error(`Error: Binary not found at ${binaryPath}`);
console.error('This might happen if:');
console.error('1. The postinstall script failed to run');
console.error('2. The platform is not supported');
console.error('3. The package was not installed correctly');
console.error('');
console.error('Try reinstalling the package:');
console.error(' npm uninstall -g @ship.zone/szci');
console.error(' npm install -g @ship.zone/szci');
process.exit(1);
}
// Spawn the binary with all arguments passed through
const child = spawn(binaryPath, process.argv.slice(2), {
stdio: 'inherit',
shell: false
});
// Handle child process events
child.on('error', (err) => {
console.error(`Error executing szci: ${err.message}`);
process.exit(1);
});
child.on('exit', (code, signal) => {
if (signal) {
process.kill(process.pid, signal);
} else {
process.exit(code || 0);
}
});
// Forward signals to child process
const signals = ['SIGINT', 'SIGTERM', 'SIGHUP'];
signals.forEach(signal => {
process.on(signal, () => {
if (!child.killed) {
child.kill(signal);
}
});
});
}
// Execute
executeBinary();

128
changelog.md Normal file
View File

@@ -0,0 +1,128 @@
# Changelog
## 2026-02-06 - 7.0.0 - BREAKING CHANGE(szci)
delegate Docker operations to @git.zone/tsdocker, remove internal Docker managers and deprecated modules, simplify CLI and env var handling
- Delegate all Docker actions to @git.zone/tsdocker via npx; SzciDockerManager now bridges SZCI_LOGIN_DOCKER* and CI_JOB_TOKEN to DOCKER_REGISTRY_N and invokes npx @git.zone/tsdocker for build/login/push/pull/test.
- Removed internal Docker implementation: Dockerfile, DockerRegistry, RegistryStorage, related helpers and plugin wrappers removed from ts/manager.docker.
- Removed Cloudron manager and other deprecated modules and their plugin shims: manager.cloudron, mod_clean, mod_command, mod_precheck, mod_trigger (and corresponding CLI commands: cloudron, clean, command, precheck, trigger).
- CLI and exports simplified: Dockerfile export removed from mod.ts; Szci CLI now delegates docker command to the simplified SzciDockerManager.
- Updated environment handling: bridges SZCI_LOGIN_DOCKER* → DOCKER_REGISTRY_N and auto-bridges GitLab CI CI_JOB_TOKEN to DOCKER_REGISTRY_0.
- Node.js default mappings updated: stable→22, lts→20, legacy→18.
- Dependencies and plugins cleaned-up: removed unused/obsolete deps (e.g. @push.rocks/lik, smartdelay, through2) from deno.json and szci.plugins.ts.
- Docs updated (readme.md, readme.hints.md) to reflect architecture, tsdocker delegation, env var bridging and migration notes from npmci.
- BREAKING: CI configs and any workflows relying on internal Docker classes or the removed CLI commands must be updated to use tsdocker and the new env var/command flows.
## 2025-10-26 - 6.0.1 - fix(tests)
Migrate tests to Deno native runner and update Deno config
- Convert test suites from tap/tapbundle to Deno.test and @std/assert
- Replace CommonJS-style runtime imports with .ts module imports for Deno (test files updated)
- Use Deno.env.set to configure test environment variables and restore working directory after tests
- Update test/test.cloudly.ts to import CloudlyConnector directly and disable TLS verification for tests
- Adjust deno.json version field (6.0.0 -> 5.0.0) as part of Deno configuration changes
- Add local project .claude/settings.local.json for tooling permissions
## 2025-10-26 - 6.0.0 - BREAKING CHANGE(szci)
Rename project from npmci to szci and migrate runtime to Deno; add compiled binaries, installer and wrapper; update imports, env handling and package metadata
- Major rename/refactor: Npmci -> Szci across the codebase (classes, filenames, modules and exports).
- Migrate runtime to Deno: add deno.json, mod.ts entry point, use Deno.std imports and .ts module imports throughout.
- Add compilation and distribution tooling: scripts/compile-all.sh, scripts/install-binary.js, bin/szci-wrapper.js and dist/binaries layout for prebuilt executables.
- Package metadata updated: package.json renamed/rewritten for @ship.zone/szci and bumped to 5.0.0, updated publishConfig, files and scripts.
- Environment API changes: replaced process.env usages with Deno.env accessors and updated path constants (Szci paths).
- Refactored helper modules: npmci.bash -> szci.bash, updated smartshell/bash wrappers and other manager modules to Deno patterns.
- Tests and imports updated to new module paths and package layout; .gitignore updated to ignore deno artifacts.
- Breaking changes to callers: CLI name, class names, programmatic API, binary installation and environment handling have changed and may require updates in integrations and CI configurations.
## 2024-11-17 - 4.1.37 - fix(docker)
Enhanced base image extraction logic from Dockerfile
- Improved dockerBaseImage to accurately extract base images considering ARG variables.
- Added support for parsing Dockerfile content without external libraries.
- Enhanced error handling for missing FROM instructions.
## 2024-11-17 - 4.1.36 - fix(docker)
Improve logging for Dockerfile build order with base image details.
- Enhance logging in Dockerfile sorting process to include base image information.
## 2024-11-17 - 4.1.35 - fix(docker)
Fix Dockerfile dependency sorting and enhance environment variable handling for GitHub repos
- Refined the algorithm for sorting Dockerfiles based on dependencies to ensure proper build order.
- Enhanced environment variable handling in the NpmciEnv class to support conditional assignments.
- Updated various dependencies in package.json for improved performance and compatibility.
- Added error handling to circular dependency detection in Dockerfile sorting.
## 2024-11-05 - 4.1.34 - fix(connector)
Remove unused typedrequest implementation in cloudlyconnector
- Removed commented out code that initialized typedrequest in CloudlyConnector.
## 2024-11-05 - 4.1.33 - fix(core)
Updated dependencies and improved npm preparation logic.
- Updated @git.zone/tsbuild from ^2.1.84 to ^2.2.0.
- Updated @git.zone/tsrun from ^1.2.49 to ^1.3.3.
- Updated @types/node from ^22.7.9 to ^22.8.7.
- Updated @serve.zone/api from ^1.2.1 to ^4.3.1.
- Improved npm preparation logic to handle empty tokens gracefully.
## 2024-10-23 - 4.1.32 - fix(dependencies)
Update project dependencies to latest versions
- Updated development dependencies, including @git.zone/tsbuild and @git.zone/tsrun.
- Updated production dependencies such as @api.global/typedrequest and @push.rocks/smartfile.
## 2022-10-24 - 4.0.11 - prerelease
now includes a precheck for more generic runner execution
- Implemented a precheck feature for runners.
## 2022-10-09 to 2022-10-11 - 4.0.0 to 4.0.10 - migration
internal migrations and fixes
- Major switch to ESM style module: **BREAKING CHANGE**.
- Multiple fixes in core functionalities and module updates.
## 2019-11-26 - 3.1.73 - fixes
correctly setting npm cache and other updates
- Ensured correct npm cache setting during preparation.
- Various core updates.
## 2018-12-23 - 3.1.19 - privacy updates
enhanced mirroring controls for private code
- Now refusing to mirror private code.
## 2018-11-24 - 3.1.2 - ci improvement
removed unnecessary build dependency
- Removed npmts build dependency in CI pipeline.
## 2018-09-22 - 3.0.59 - enhancement
integrated smartlog for improved logging
- Logs now utilize smartlog for better management.
## 2017-09-08 - 3.0.14 - analytics
added analytics features
- Enabled analytics throughout the system.
## 2017-08-29 - 3.0.9 - docker enhancements
docker improvements and build args implementation
- Implemented working `dockerBuildArgEnvMap`.
## 2017-07-27 - 2.4.0 - stability improvements
various updates to stabilize the environment
- Fixed npmci versioning issues.
## 2016-11-25 - 2.3.24 - global tools
improved handling for global tool installations
- Improved install handling for needed global tools.

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env node
process.env.CLI_CALL = 'true';
import * as cliTool from './ts/index.js';
cliTool.runCli();

4
cli.js
View File

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

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env node
process.env.CLI_CALL = 'true';
import * as tsrun from '@gitzone/tsrun';
tsrun.runPath('./cli.child.js', import.meta.url);

63
deno.json Normal file
View File

@@ -0,0 +1,63 @@
{
"name": "@ship.zone/szci",
"version": "7.0.0",
"exports": "./mod.ts",
"nodeModulesDir": "auto",
"tasks": {
"dev": "deno run --allow-all mod.ts",
"compile": "deno task compile:all",
"compile:all": "bash scripts/compile-all.sh",
"test": "deno test --allow-all test/",
"test:watch": "deno test --allow-all --watch test/",
"check": "deno check mod.ts",
"fmt": "deno fmt",
"lint": "deno lint"
},
"lint": {
"rules": {
"tags": [
"recommended"
]
}
},
"fmt": {
"useTabs": false,
"lineWidth": 100,
"indentWidth": 2,
"semiColons": true,
"singleQuote": true
},
"compilerOptions": {
"lib": [
"deno.window"
],
"strict": true
},
"imports": {
"@std/path": "jsr:@std/path@^1.0.0",
"@std/fmt": "jsr:@std/fmt@^1.0.0",
"@std/cli": "jsr:@std/cli@^1.0.0",
"@std/assert": "jsr:@std/assert@^1.0.0",
"@api.global/typedrequest": "npm:@api.global/typedrequest@^3.1.10",
"@push.rocks/npmextra": "npm:@push.rocks/npmextra@^5.1.2",
"@push.rocks/projectinfo": "npm:@push.rocks/projectinfo@^5.0.2",
"@push.rocks/qenv": "npm:@push.rocks/qenv@^6.0.2",
"@push.rocks/smartanalytics": "npm:@push.rocks/smartanalytics@^2.0.15",
"@push.rocks/smartcli": "npm:@push.rocks/smartcli@^4.0.11",
"@push.rocks/smartfile": "npm:@push.rocks/smartfile@^11.0.21",
"@push.rocks/smartgit": "npm:@push.rocks/smartgit@^3.1.1",
"@push.rocks/smartlog": "npm:@push.rocks/smartlog@^3.0.7",
"@push.rocks/smartlog-destination-local": "npm:@push.rocks/smartlog-destination-local@^9.0.0",
"@push.rocks/smartobject": "npm:@push.rocks/smartobject@^1.0.12",
"@push.rocks/smartpath": "npm:@push.rocks/smartpath@^5.0.11",
"@push.rocks/smartpromise": "npm:@push.rocks/smartpromise@^4.0.4",
"@push.rocks/smartrequest": "npm:@push.rocks/smartrequest@^2.0.23",
"@push.rocks/smartshell": "npm:@push.rocks/smartshell@^3.0.6",
"@push.rocks/smartsocket": "npm:@push.rocks/smartsocket@^2.0.22",
"@push.rocks/smartssh": "npm:@push.rocks/smartssh@^2.0.1",
"@push.rocks/smartstring": "npm:@push.rocks/smartstring@^4.0.8",
"@push.rocks/smartexpect": "npm:@push.rocks/smartexpect@^1.0.15",
"@serve.zone/api": "npm:@serve.zone/api@^4.3.11",
"@tsclass/tsclass": "npm:@tsclass/tsclass@^4.1.2"
}
}

257
install.sh Executable file
View File

@@ -0,0 +1,257 @@
#!/bin/bash
# SZCI Installer Script (v5.0+)
# Downloads and installs pre-compiled SZCI binary from Gitea releases
#
# Usage:
# Direct piped installation (recommended):
# curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/master/install.sh | sudo bash
#
# With version specification:
# curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/master/install.sh | sudo bash -s -- --version v5.0.0
#
# Options:
# -h, --help Show this help message
# --version VERSION Install specific version (e.g., v5.0.0)
# --install-dir DIR Installation directory (default: /opt/szci)
set -e
# Default values
SHOW_HELP=0
SPECIFIED_VERSION=""
INSTALL_DIR="/opt/szci"
GITEA_BASE_URL="https://code.foss.global"
GITEA_REPO="ship.zone/szci"
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
SHOW_HELP=1
shift
;;
--version)
SPECIFIED_VERSION="$2"
shift 2
;;
--install-dir)
INSTALL_DIR="$2"
shift 2
;;
*)
echo "Unknown option: $1"
echo "Use -h or --help for usage information"
exit 1
;;
esac
done
if [ $SHOW_HELP -eq 1 ]; then
echo "SZCI Installer Script (v5.0+)"
echo "Downloads and installs pre-compiled SZCI binary"
echo ""
echo "Usage: $0 [options]"
echo ""
echo "Options:"
echo " -h, --help Show this help message"
echo " --version VERSION Install specific version (e.g., v5.0.0)"
echo " --install-dir DIR Installation directory (default: /opt/szci)"
echo ""
echo "Examples:"
echo " # Install latest version"
echo " curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/master/install.sh | sudo bash"
echo ""
echo " # Install specific version"
echo " curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/master/install.sh | sudo bash -s -- --version v5.0.0"
exit 0
fi
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo "Please run as root (sudo bash install.sh or pipe to sudo bash)"
exit 1
fi
# Helper function to detect OS and architecture
detect_platform() {
local os=$(uname -s)
local arch=$(uname -m)
# Map OS
case "$os" in
Linux)
os_name="linux"
;;
Darwin)
os_name="macos"
;;
MINGW*|MSYS*|CYGWIN*)
os_name="windows"
;;
*)
echo "Error: Unsupported operating system: $os"
echo "Supported: Linux, macOS, Windows"
exit 1
;;
esac
# Map architecture
case "$arch" in
x86_64|amd64)
arch_name="x64"
;;
aarch64|arm64)
arch_name="arm64"
;;
*)
echo "Error: Unsupported architecture: $arch"
echo "Supported: x86_64/amd64 (x64), aarch64/arm64 (arm64)"
exit 1
;;
esac
# Construct binary name
if [ "$os_name" = "windows" ]; then
echo "szci-${os_name}-${arch_name}.exe"
else
echo "szci-${os_name}-${arch_name}"
fi
}
# Get latest release version from Gitea API
get_latest_version() {
echo "Fetching latest release version from Gitea..." >&2
local api_url="${GITEA_BASE_URL}/api/v1/repos/${GITEA_REPO}/releases/latest"
local response=$(curl -sSL "$api_url" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$response" ]; then
echo "Error: Failed to fetch latest release information from Gitea API" >&2
echo "URL: $api_url" >&2
exit 1
fi
# Extract tag_name from JSON response
local version=$(echo "$response" | grep -o '"tag_name":"[^"]*"' | cut -d'"' -f4)
if [ -z "$version" ]; then
echo "Error: Could not determine latest version from API response" >&2
exit 1
fi
echo "$version"
}
# Main installation process
echo "================================================"
echo " SZCI Installation Script (v5.0+)"
echo " Serve Zone CI/CD Tool"
echo "================================================"
echo ""
# Detect platform
BINARY_NAME=$(detect_platform)
echo "Detected platform: $BINARY_NAME"
echo ""
# Determine version to install
if [ -n "$SPECIFIED_VERSION" ]; then
VERSION="$SPECIFIED_VERSION"
echo "Installing specified version: $VERSION"
else
VERSION=$(get_latest_version)
echo "Installing latest version: $VERSION"
fi
echo ""
# Construct download URL
DOWNLOAD_URL="${GITEA_BASE_URL}/${GITEA_REPO}/releases/download/${VERSION}/${BINARY_NAME}"
echo "Download URL: $DOWNLOAD_URL"
echo ""
# Clean installation directory - ensure only binary exists
if [ -d "$INSTALL_DIR" ]; then
echo "Cleaning installation directory: $INSTALL_DIR"
rm -rf "$INSTALL_DIR"
fi
# Create fresh installation directory
echo "Creating installation directory: $INSTALL_DIR"
mkdir -p "$INSTALL_DIR"
# Download binary
echo "Downloading SZCI binary..."
TEMP_FILE="$INSTALL_DIR/szci.download"
curl -sSL "$DOWNLOAD_URL" -o "$TEMP_FILE"
if [ $? -ne 0 ]; then
echo "Error: Failed to download binary from $DOWNLOAD_URL"
echo ""
echo "Please check:"
echo " 1. Your internet connection"
echo " 2. The specified version exists: ${GITEA_BASE_URL}/${GITEA_REPO}/releases"
echo " 3. The platform binary is available for this release"
rm -f "$TEMP_FILE"
exit 1
fi
# Check if download was successful (file exists and not empty)
if [ ! -s "$TEMP_FILE" ]; then
echo "Error: Downloaded file is empty or does not exist"
rm -f "$TEMP_FILE"
exit 1
fi
# Move to final location
BINARY_PATH="$INSTALL_DIR/szci"
mv "$TEMP_FILE" "$BINARY_PATH"
if [ $? -ne 0 ] || [ ! -f "$BINARY_PATH" ]; then
echo "Error: Failed to move binary to $BINARY_PATH"
rm -f "$TEMP_FILE" 2>/dev/null
exit 1
fi
# Make executable
chmod +x "$BINARY_PATH"
if [ $? -ne 0 ]; then
echo "Error: Failed to make binary executable"
exit 1
fi
echo "Binary installed successfully to: $BINARY_PATH"
echo ""
# Check if /usr/local/bin is in PATH
if [[ ":$PATH:" == *":/usr/local/bin:"* ]]; then
BIN_DIR="/usr/local/bin"
else
BIN_DIR="/usr/bin"
fi
# Create symlink for global access
ln -sf "$BINARY_PATH" "$BIN_DIR/szci"
echo "Symlink created: $BIN_DIR/szci -> $BINARY_PATH"
echo ""
echo "================================================"
echo " SZCI Installation Complete!"
echo "================================================"
echo ""
echo "Installation details:"
echo " Binary location: $BINARY_PATH"
echo " Symlink location: $BIN_DIR/szci"
echo " Version: $VERSION"
echo ""
echo "Get started:"
echo " szci --version"
echo " szci --help"
echo " szci node install stable # Install Node.js"
echo " szci docker build # Build Docker images"
echo " szci npm test # Run tests"
echo ""
echo "For CI/CD integration examples, visit:"
echo " https://code.foss.global/ship.zone/szci"
echo ""

47
mod.ts Normal file
View File

@@ -0,0 +1,47 @@
#!/usr/bin/env -S deno run --allow-all
/**
* SZCI - Serve Zone CI/CD Tool
*
* A command-line tool for streamlining Node.js and Docker workflows
* within CI environments, particularly GitLab CI, GitHub CI, and Gitea CI.
*
* Required Permissions:
* - --allow-net: Network access for Docker registries, npm, git operations
* - --allow-read: Read configuration files, Dockerfiles, package.json
* - --allow-write: Write configuration files, build artifacts
* - --allow-run: Execute system commands (docker, git, npm, ssh)
* - --allow-sys: Access system information (OS details)
* - --allow-env: Read/write environment variables
*
* @module
*/
import { Szci } from './ts/szci.classes.szci.ts';
/**
* Main entry point for the SZCI application
* Parses command-line arguments and executes the requested command
*/
async function main(): Promise<void> {
// Create Szci instance
const szciInstance = new Szci();
// Start the CLI
// Deno.args is already 0-indexed (unlike Node's process.argv which starts at index 2)
// The smartcli library may expect process.argv format, so we might need to prepend placeholders
await szciInstance.start();
}
// Execute main and handle errors
if (import.meta.main) {
try {
await main();
} catch (error) {
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
Deno.exit(1);
}
}
// Export for programmatic use
export { Szci } from './ts/szci.classes.szci.ts';

View File

@@ -1,22 +1,39 @@
{ {
"npmci": { "@ship.zone/szci": {
"npmGlobalTools": [], "npmGlobalTools": [],
"npmAccessLevel": "public", "npmAccessLevel": "public",
"npmRegistryUrl": "registry.npmjs.org" "npmRegistryUrl": "registry.npmjs.org"
}, },
"npmdocker": { "@git.zone/tsdocker": {
"baseImage": "hosttoday/ht-docker-node:npmci", "baseImage": "hosttoday/ht-docker-node:npmci",
"command": "npmci test stable" "command": "szci test stable"
}, },
"gitzone": { "@git.zone/cli": {
"projectType": "npm", "projectType": "npm",
"module": { "module": {
"githost": "gitlab.com", "githost": "gitlab.com",
"gitscope": "shipzone", "gitscope": "ship.zone",
"gitrepo": "npmci", "gitrepo": "szci",
"description": "node and docker in gitlab ci on steroids", "description": "A tool to streamline Node.js and Docker workflows within CI environments, particularly GitLab CI, providing various CI/CD utilities.",
"npmPackagename": "@shipzone/npmci", "npmPackagename": "@ship.zone/szci",
"license": "MIT" "license": "MIT",
"keywords": [
"Node.js",
"Docker",
"GitLab CI",
"GitHub CI",
"Gitea CI",
"CI/CD",
"automation",
"npm",
"TypeScript",
"cloud",
"SSH",
"registry",
"container management",
"continuous integration",
"continuous deployment"
]
} }
} }
} }

View File

@@ -1,77 +1,67 @@
{ {
"name": "@shipzone/npmci", "name": "@ship.zone/szci",
"version": "4.0.10", "version": "7.0.0",
"private": false, "description": "Serve Zone CI - A tool to streamline Node.js and Docker workflows within CI environments, particularly GitLab CI, providing various CI/CD utilities. Powered by Deno with standalone executables.",
"description": "node and docker in gitlab ci on steroids", "keywords": [
"main": "dist_ts/index.js", "Node.js",
"typings": "dist_ts/index.d.ts", "Docker",
"type": "module", "GitLab CI",
"bin": { "GitHub CI",
"npmci": "cli.js" "Gitea CI",
}, "CI/CD",
"scripts": { "automation",
"test": "tstest test/", "npm",
"build": "tsbuild --allowimplicitany && (npm run testVersion)", "TypeScript",
"testVersion": "(cd test/assets/ && node ../../cli.js -v)", "Deno",
"buildDocs": "tsdoc" "cloud",
"SSH",
"registry",
"container management",
"continuous integration",
"continuous deployment"
],
"homepage": "https://code.foss.global/ship.zone/szci",
"bugs": {
"url": "https://code.foss.global/ship.zone/szci/issues"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+ssh://git@gitlab.com/gitzone/npmci.git" "url": "git+https://code.foss.global/ship.zone/szci.git"
}, },
"author": "Lossless GmbH", "author": "Lossless GmbH",
"license": "MIT", "license": "MIT",
"bugs": { "type": "module",
"url": "https://gitlab.com/gitzone/npmci/issues" "bin": {
"szci": "./bin/szci-wrapper.js"
}, },
"homepage": "https://gitlab.com/gitzone/npmci#README", "scripts": {
"devDependencies": { "postinstall": "node scripts/install-binary.js",
"@gitzone/tsbuild": "^2.1.65", "prepublishOnly": "echo 'Publishing SZCI binaries to npm...'",
"@gitzone/tsrun": "^1.2.37", "test": "echo 'Tests are run with Deno: deno task test'",
"@gitzone/tstest": "^1.0.73", "build": "echo 'no build needed'"
"@pushrocks/tapbundle": "^5.0.4",
"@types/node": "^18.8.3"
},
"dependencies": {
"@apiglobal/typedrequest": "^2.0.10",
"@pushrocks/lik": "^6.0.0",
"@pushrocks/npmextra": "^3.0.9",
"@pushrocks/projectinfo": "^5.0.1",
"@pushrocks/qenv": "^5.0.2",
"@pushrocks/smartanalytics": "^2.0.15",
"@pushrocks/smartcli": "^4.0.6",
"@pushrocks/smartdelay": "^2.0.13",
"@pushrocks/smartenv": "^5.0.3",
"@pushrocks/smartfile": "^10.0.5",
"@pushrocks/smartgit": "^3.0.0",
"@pushrocks/smartlog": "^3.0.1",
"@pushrocks/smartlog-destination-local": "^8.0.8",
"@pushrocks/smartparam": "^1.1.6",
"@pushrocks/smartpath": "^5.0.5",
"@pushrocks/smartpromise": "^3.1.7",
"@pushrocks/smartrequest": "^2.0.11",
"@pushrocks/smartshell": "^2.0.30",
"@pushrocks/smartsocket": "^2.0.7",
"@pushrocks/smartssh": "^2.0.0",
"@pushrocks/smartstring": "^4.0.5",
"@servezone/interfaces": "^1.0.3",
"@tsclass/tsclass": "^4.0.21",
"@types/through2": "^2.0.36",
"through2": "^4.0.2"
}, },
"files": [ "files": [
"ts/**/*", "bin/",
"ts_web/**/*", "scripts/install-binary.js",
"dist/**/*", "readme.md",
"dist_*/**/*", "license",
"dist_ts/**/*", "changelog.md"
"dist_ts_web/**/*",
"assets/**/*",
"cli.js",
"npmextra.json",
"readme.md"
], ],
"browserslist": [ "engines": {
"last 1 chrome versions" "node": ">=14.0.0"
] },
"os": [
"darwin",
"linux",
"win32"
],
"cpu": [
"x64",
"arm64"
],
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34"
} }

4363
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

55
readme.hints.md Normal file
View File

@@ -0,0 +1,55 @@
- Focus on CLI usage in CI environments.
- Show GitLab CI, GitHub CI and Gitea CI examples.
## Architecture
szci is a thin orchestrator that delegates heavy lifting to specialized tools:
- **Docker**: Delegates all operations to `@git.zone/tsdocker` via `npx`
- **NPM**: Thin wrapper around `pnpm` with .npmrc generation from `SZCI_TOKEN_NPM*` env vars
- **Node.js**: NVM-based version management (stable=22, lts=20, legacy=18)
- **SSH**: Deploys SSH keys from `SZCI_SSHKEY_*` env vars
- **Git**: GitHub mirroring via `SZCI_GIT_GITHUBTOKEN`
- **Cloudly**: Integration with serve.zone infrastructure
## Docker Env Var Bridging
szci bridges its own env var format to tsdocker's format before delegation:
- `SZCI_LOGIN_DOCKER*` -> `DOCKER_REGISTRY_N` (pipe-delimited: `url|user|pass`)
- GitLab CI: `CI_JOB_TOKEN` -> `DOCKER_REGISTRY_0` for `registry.gitlab.com`
## Deno Migration Status
The project has been fully migrated from Node.js to Deno runtime.
### Environment Variables
All environment variables have been rebranded from NPMCI_* to SZCI_*:
| Old Name | New Name |
|----------|----------|
| `NPMCI_COMPUTED_REPOURL` | `SZCI_COMPUTED_REPOURL` |
| `NPMCI_URL_CLOUDLY` | `SZCI_URL_CLOUDLY` |
| `NPMCI_GIT_GITHUBTOKEN` | `SZCI_GIT_GITHUBTOKEN` |
| `NPMCI_GIT_GITHUBGROUP` | `SZCI_GIT_GITHUBGROUP` |
| `NPMCI_GIT_GITHUB` | `SZCI_GIT_GITHUB` |
| `NPMCI_SSHKEY_*` | `SZCI_SSHKEY_*` |
| `NPMCI_LOGIN_DOCKER*` | `SZCI_LOGIN_DOCKER*` |
| `NPMCI_TOKEN_NPM*` | `SZCI_TOKEN_NPM*` |
| `NPMTS_TEST` | `SZCI_TEST` |
| `DEBUG_NPMCI` | `DEBUG_SZCI` |
### Runtime
- Uses Deno APIs (`Deno.env`, `Deno.cwd`, `Deno.exit`)
- Logger runtime set to 'deno'
- Dynamic imports use `.ts` extensions
## Removed Modules (v7+)
The following were removed as dead/obsolete code:
- `mod_trigger` - Deprecated GitLab API v3
- `mod_precheck` - GitLab-specific runner tag validation
- `manager.cloudron` - Niche Cloudron deployment
- `mod_command` - Trivial bash wrapper
- `mod_clean` - Trivial config cleanup
- Docker manager internals (Dockerfile, DockerRegistry, RegistryStorage classes) - replaced by tsdocker delegation

437
readme.md
View File

@@ -1,112 +1,377 @@
# @shipzone/npmci # @ship.zone/szci
node and docker in gitlab ci on steroids
## Availabililty and Links A lightweight CI/CD orchestrator built with Deno that unifies Node.js, Docker, NPM, SSH, and Git workflows into a single CLI. Compiles to standalone binaries — no runtime required.
* [npmjs.org (npm package)](https://www.npmjs.com/package/@shipzone/npmci)
* [gitlab.com (source)](https://gitlab.com/shipzone/npmci)
* [github.com (source mirror)](https://github.com/shipzone/npmci)
* [docs (typedoc)](https://shipzone.gitlab.io/npmci/)
## Status for master ## Issue Reporting and Security
Status Category | Status Badge 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.
-- | --
GitLab Pipelines | [![pipeline status](https://gitlab.com/shipzone/npmci/badges/master/pipeline.svg)](https://lossless.cloud)
GitLab Pipline Test Coverage | [![coverage report](https://gitlab.com/shipzone/npmci/badges/master/coverage.svg)](https://lossless.cloud)
npm | [![npm downloads per month](https://badgen.net/npm/dy/@shipzone/npmci)](https://lossless.cloud)
Snyk | [![Known Vulnerabilities](https://badgen.net/snyk/shipzone/npmci)](https://lossless.cloud)
TypeScript Support | [![TypeScript](https://badgen.net/badge/TypeScript/>=%203.x/blue?icon=typescript)](https://lossless.cloud)
node Support | [![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
Code Style | [![Code Style](https://badgen.net/badge/style/prettier/purple)](https://lossless.cloud)
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@shipzone/npmci)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@shipzone/npmci)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@shipzone/npmci)](https://lossless.cloud)
Platform support | [![Supports Windows 10](https://badgen.net/badge/supports%20Windows%2010/yes/green?icon=windows)](https://lossless.cloud) [![Supports Mac OS X](https://badgen.net/badge/supports%20Mac%20OS%20X/yes/green?icon=apple)](https://lossless.cloud)
## Usage ## 🏗️ Architecture
Use TypeScript for best in class instellisense. szci is a **thin orchestrator** — it doesn't reinvent the wheel. Instead, it wires up best-in-class tools and handles the CI-specific glue:
npmci is designed to work in docker CI environments. The following docker images come with npmci presinstalled: | Domain | What szci does | Delegates to |
|--------|---------------|-------------|
| 🐳 Docker | Bridges `SZCI_LOGIN_DOCKER*` env vars, auto-detects GitLab CI tokens | [`@git.zone/tsdocker`](https://code.foss.global/git.zone/tsdocker) |
| 📦 NPM | Generates `.npmrc` from `SZCI_TOKEN_NPM*` env vars, handles multi-registry publish | `pnpm` + `npm publish` |
| 🟢 Node.js | Manages Node versions via NVM with named aliases | `nvm` |
| 🔑 SSH | Deploys SSH keys from env vars to `~/.ssh` | `@push.rocks/smartssh` |
| 🔀 Git | Mirrors repos to GitHub | `git remote` |
Docker Hub: ```
CLI Input (Deno.args)
- [hosttoday/ht-docker-node:npmci](https://hub.docker.com/r/hosttoday/ht-docker-node/)
has LTS node version and npmci preinstalled. SmartCLI Router
- [hosttoday/ht-docker-dbase](https://hub.docker.com/r/hosttoday/ht-docker-dbase/)
based on docker:git, can be used to build docker images in conjunction with docker:dind ├─ szci docker * → env var bridging → npx @git.zone/tsdocker
├─ szci npm * → .npmrc generation → pnpm / npm publish
npmci can be called from commandline and handle a lot of tasks durug ci: ├─ szci node * → version aliasing → nvm install
├─ szci git * → token injection → git push --mirror
```shell └─ szci ssh * → key parsing → write to ~/.ssh
# Handle node versions
npmci node install stable # will install latest stable node version and update PATH for node and npm
npmci node install lts # will install latest LTS node version and update PATH for node and npm versions
npmci node install legacy # will install latest legacy node version and update PATH for node and npm
npmci node install x.x.x # will install any specific node version.
# Handle npm and yarn tasks
npmcu npm login # logs in npm using the auth key provided at env var "NPMCI_TOKEN_NPM"
npmci npm install # installs dependencies using npm or yarn dependending on availablity
npmci npm test # tests the package
npmci npm publish # builds a package and publishes it
# handle docker tasks
npmci docker prepare
## npmci test docker will look at all Dockerfiles and look for according tags on GitLab container registry
# prepare tools
npmci prepare npm # will look for $NPMCI_TOKEN_NPM env var and create .npmrc, so npm is authenticated
npmci prepare docker # will look for $NPMCI_LOGIN_DOCKER in form username|password and authenticate docker
npmci prepare docker-gitlab # will authenticate docker for gitlab container registry
# build containers
npmci docker build # will build containers
## all Dockerfiles named Dockerfile* are picked up.
## specify tags like this Dockerfile_[tag]
## uploads all built images as [username]/[reponame]:[tag]_test to GitLab
## then test in next step with "npmci test docker"
# publish npm module
npmci publish npm # will look vor $NPMCI_TOKEN_NPM env var and push any module in cwd to npm
npmci publish docker
# trigger webhooks
npmci trigger # will look for NPMCI_TRIGGER_1 to NPMCI_TRIGGER_100 in form domain|id|token|ref|name
``` ```
## Configuration ## 📥 Installation
npmci supports the use of npmextra. ### Via NPM (downloads pre-compiled binary)
To configure npmci create a `npmextra.json` file at the root of your project ```sh
npm install -g @ship.zone/szci
```
### From Source
```sh
git clone https://code.foss.global/ship.zone/szci.git
cd szci
deno task compile
```
The compiled binaries land in `dist/binaries/` for Linux (x64/arm64), macOS (x64/arm64), and Windows (x64).
## 🚀 Quick Start
```sh
# Setup Node.js environment
szci node install stable
# Install project dependencies
szci npm install
# Build & push Docker images
szci docker build
szci docker push registry.example.com
# Run tests
szci npm test
```
## 📖 CLI Reference
### `szci docker` — Docker Operations
All Docker commands delegate to `@git.zone/tsdocker` after bridging environment variables.
```sh
szci docker build # Build all Dockerfiles in cwd
szci docker login # Login to all configured registries
szci docker prepare # Alias for login
szci docker push registry.example.com # Push images to a registry
szci docker pull registry.example.com # Pull images from a registry
szci docker test # Test Dockerfiles
```
**Env var bridging:** Before delegating, szci converts its own env var format to tsdocker's expected format:
```
SZCI_LOGIN_DOCKER_1 → DOCKER_REGISTRY_1
SZCI_LOGIN_DOCKER_2 → DOCKER_REGISTRY_2
...
```
In GitLab CI, `CI_JOB_TOKEN` is automatically bridged as `DOCKER_REGISTRY_0` for `registry.gitlab.com`.
### `szci npm` — NPM/pnpm Workflows
```sh
szci npm install # Runs pnpm install
szci npm build # Runs pnpm run build
szci npm test # Runs pnpm test
szci npm prepare # Generates ~/.npmrc from SZCI_TOKEN_NPM* env vars
szci npm publish # Full workflow: prepare → install → build → clean → npm publish
```
The `publish` command supports multi-registry publishing. If `npmAccessLevel` is `public` and a Verdaccio registry is configured, it publishes to both npm and Verdaccio automatically.
### `szci node` — Node.js Version Management
```sh
szci node install stable # Node.js 22
szci node install lts # Node.js 20
szci node install legacy # Node.js 18
szci node install 21 # Any specific version
```
Uses NVM under the hood (auto-detected at `/usr/local/nvm/nvm.sh` or `~/.nvm/nvm.sh`). After installing, it:
1. Sets the installed version as `nvm alias default`
2. Upgrades npm to latest
3. Installs any global tools listed in `npmextra.json``npmGlobalTools`
### `szci ssh` — SSH Key Deployment
```sh
szci ssh prepare # Deploy SSH keys from env vars to ~/.ssh
```
Reads all `SZCI_SSHKEY_*` env vars and writes the keys to disk.
### `szci git` — Git Mirroring
```sh
szci git mirror # Mirror repository to GitHub
```
Pushes all branches and tags to a GitHub mirror. Requires `SZCI_GIT_GITHUBTOKEN`. Refuses to mirror packages marked as `private` in `package.json`.
## ⚙️ Configuration
### `npmextra.json`
Place this in your project root to configure szci behavior:
```json ```json
{ {
"npmci": { "@ship.zone/szci": {
"globalNpmTools": ["npm-check-updates", "protractor", "npmts", "gitzone"] "npmGlobalTools": ["typescript", "pnpm"],
"npmAccessLevel": "public",
"npmRegistryUrl": "registry.npmjs.org",
"urlCloudly": "https://cloudly.example.com"
} }
} }
``` ```
**Available options** | Option | Type | Default | Description |
|--------|------|---------|-------------|
| `npmGlobalTools` | `string[]` | `[]` | Global npm packages to install during `node install` |
| `npmAccessLevel` | `"public" \| "private"` | `"private"` | Access level for `npm publish` |
| `npmRegistryUrl` | `string` | `"registry.npmjs.org"` | Default npm registry |
| `urlCloudly` | `string?` | — | Cloudly endpoint URL (can also be set via `SZCI_URL_CLOUDLY`) |
| setting | example | description | ## 🔐 Environment Variables
| -------------- | ----------------------------- | ------------------------------------------------------------------------------------------------- |
| globalNpmTools | "globalNpmTools": ["gitbook"] | Will look for the specified package names locally and (if not yet present) install them from npm. |
For further information read the linked docs at the top of this README. ### Docker Registry Authentication
Use TypeScript for best in class instellisense. Pipe-delimited format: `registry|username|password`
## Contribution ```sh
SZCI_LOGIN_DOCKER_1="registry.example.com|myuser|mypass"
SZCI_LOGIN_DOCKER_2="ghcr.io|token|ghp_xxxx"
```
We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :) In **GitLab CI**, the `CI_JOB_TOKEN` is automatically used for `registry.gitlab.com` — no manual config needed.
For further information read the linked docs at the top of this readme. ### NPM Registry Authentication
> MIT licensed | **&copy;** [Lossless GmbH](https://lossless.gmbh) Pipe-delimited format: `registry|token[|plain]`
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
[![repo-footer](https://lossless.gitlab.io/publicrelations/repofooter.svg)](https://maintainedby.lossless.com) ```sh
# Base64-encoded token (default)
SZCI_TOKEN_NPM_1="registry.npmjs.org|dGhlLXRva2VuLWhlcmU="
# Plain text token
SZCI_TOKEN_NPM_2="verdaccio.example.com|the-token-here|plain"
```
### SSH Keys
Pipe-delimited format: `host|privKeyBase64|pubKeyBase64`
```sh
SZCI_SSHKEY_1="github.com|BASE64_PRIVATE_KEY|BASE64_PUBLIC_KEY"
SZCI_SSHKEY_2="gitlab.com|BASE64_PRIVATE_KEY|##" # Use ## to skip a field
```
### Git Mirroring
```sh
SZCI_GIT_GITHUBTOKEN="ghp_your_personal_access_token"
SZCI_GIT_GITHUBGROUP="your-org" # Defaults to repo owner
SZCI_GIT_GITHUB="your-repo" # Defaults to repo name
```
### Debugging & Testing
```sh
DEBUG_SZCI="true" # Log all shell commands before execution
SZCI_TEST="true" # Test mode: mocks bash execution, skips SSH disk writes
```
### Full Environment Variable Reference
| Variable | Purpose |
|----------|---------|
| `SZCI_LOGIN_DOCKER_*` | Docker registry credentials (pipe-delimited) |
| `SZCI_TOKEN_NPM_*` | NPM registry auth tokens (pipe-delimited) |
| `SZCI_SSHKEY_*` | SSH key pairs (pipe-delimited) |
| `SZCI_GIT_GITHUBTOKEN` | GitHub personal access token for mirroring |
| `SZCI_GIT_GITHUBGROUP` | GitHub org/user for mirror target |
| `SZCI_GIT_GITHUB` | GitHub repo name for mirror target |
| `SZCI_URL_CLOUDLY` | Cloudly endpoint URL |
| `SZCI_COMPUTED_REPOURL` | Override auto-detected repo URL |
| `DEBUG_SZCI` | Enable verbose shell command logging |
| `SZCI_TEST` | Enable test mode (mock execution) |
## 🔄 CI/CD Integration Examples
### GitLab CI
```yaml
image: node:22
stages:
- prepare
- build
- test
- deploy
before_script:
- npm install -g @ship.zone/szci
prepare:
stage: prepare
script:
- szci node install stable
- szci npm install
build:
stage: build
script:
- szci docker build
test:
stage: test
script:
- szci npm test
deploy:
stage: deploy
script:
- szci docker push $CI_REGISTRY
only:
- master
```
> 💡 In GitLab CI, `CI_JOB_TOKEN` is automatically detected — szci will login to `registry.gitlab.com` without any extra config.
### GitHub Actions
```yaml
name: CI/CD
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install szci
run: npm install -g @ship.zone/szci
- name: Setup Node.js
run: szci node install stable
- name: Install & Build
run: |
szci npm install
szci npm build
- name: Test
run: szci npm test
- name: Push Docker
if: github.ref == 'refs/heads/main'
run: szci docker push ghcr.io
env:
SZCI_LOGIN_DOCKER_1: "ghcr.io|${{ github.actor }}|${{ secrets.GITHUB_TOKEN }}"
```
### Gitea CI (Woodpecker)
```yaml
steps:
- name: ci
image: node:22
commands:
- npm install -g @ship.zone/szci
- szci node install stable
- szci npm install
- szci docker build
- szci npm test
```
## 🔄 Migration from npmci
Upgrading from `@ship.zone/npmci`? Three steps:
1. **Rename the binary** — replace `npmci` with `szci` in all CI scripts
2. **Rename env vars**`NPMCI_*``SZCI_*` (same formats, just the prefix changed)
3. **Docker ops** — now delegate to `@git.zone/tsdocker` (installed automatically via `npx`)
| Old | New |
|-----|-----|
| `NPMCI_LOGIN_DOCKER*` | `SZCI_LOGIN_DOCKER*` |
| `NPMCI_TOKEN_NPM*` | `SZCI_TOKEN_NPM*` |
| `NPMCI_SSHKEY_*` | `SZCI_SSHKEY_*` |
| `NPMCI_GIT_GITHUBTOKEN` | `SZCI_GIT_GITHUBTOKEN` |
| `NPMCI_URL_CLOUDLY` | `SZCI_URL_CLOUDLY` |
| `DEBUG_NPMCI` | `DEBUG_SZCI` |
| `NPMTS_TEST` | `SZCI_TEST` |
## 🛠️ Development
```sh
# Type check
deno task check
# Run tests
deno task test
# Watch tests
deno task test:watch
# Dev mode
deno task dev -- --help
# Compile binaries for all platforms
deno task compile
# Format code
deno task fmt
# Lint
deno task lint
```
## 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.

66
scripts/compile-all.sh Executable file
View File

@@ -0,0 +1,66 @@
#!/bin/bash
set -e
# Get version from deno.json
VERSION=$(cat deno.json | grep -o '"version": *"[^"]*"' | cut -d'"' -f4)
BINARY_DIR="dist/binaries"
echo "================================================"
echo " SZCI Compilation Script"
echo " Version: ${VERSION}"
echo "================================================"
echo ""
echo "Compiling for all supported platforms..."
echo ""
# Clean up old binaries and create fresh directory
rm -rf "$BINARY_DIR"
mkdir -p "$BINARY_DIR"
echo "→ Cleaned old binaries from $BINARY_DIR"
echo ""
# Linux x86_64
echo "→ Compiling for Linux x86_64..."
deno compile --allow-all --no-check --output "$BINARY_DIR/szci-linux-x64" \
--target x86_64-unknown-linux-gnu mod.ts
echo " ✓ Linux x86_64 complete"
echo ""
# Linux ARM64
echo "→ Compiling for Linux ARM64..."
deno compile --allow-all --no-check --output "$BINARY_DIR/szci-linux-arm64" \
--target aarch64-unknown-linux-gnu mod.ts
echo " ✓ Linux ARM64 complete"
echo ""
# macOS x86_64
echo "→ Compiling for macOS x86_64..."
deno compile --allow-all --no-check --output "$BINARY_DIR/szci-macos-x64" \
--target x86_64-apple-darwin mod.ts
echo " ✓ macOS x86_64 complete"
echo ""
# macOS ARM64
echo "→ Compiling for macOS ARM64..."
deno compile --allow-all --no-check --output "$BINARY_DIR/szci-macos-arm64" \
--target aarch64-apple-darwin mod.ts
echo " ✓ macOS ARM64 complete"
echo ""
# Windows x86_64
echo "→ Compiling for Windows x86_64..."
deno compile --allow-all --no-check --output "$BINARY_DIR/szci-windows-x64.exe" \
--target x86_64-pc-windows-msvc mod.ts
echo " ✓ Windows x86_64 complete"
echo ""
echo "================================================"
echo " Compilation Summary"
echo "================================================"
echo ""
ls -lh "$BINARY_DIR/" | tail -n +2
echo ""
echo "✓ All binaries compiled successfully!"
echo ""
echo "Binary location: $BINARY_DIR/"
echo ""

228
scripts/install-binary.js Normal file
View File

@@ -0,0 +1,228 @@
#!/usr/bin/env node
/**
* SZCI npm postinstall script
* Downloads the appropriate binary for the current platform from repository releases
*/
import { platform, arch } from 'os';
import { existsSync, mkdirSync, writeFileSync, chmodSync, unlinkSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import https from 'https';
import { pipeline } from 'stream';
import { promisify } from 'util';
import { createWriteStream } from 'fs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const streamPipeline = promisify(pipeline);
// Configuration
const REPO_BASE = 'https://code.foss.global/ship.zone/szci';
const VERSION = process.env.npm_package_version || '4.1.37';
function getBinaryInfo() {
const plat = platform();
const architecture = arch();
const platformMap = {
'darwin': 'macos',
'linux': 'linux',
'win32': 'windows'
};
const archMap = {
'x64': 'x64',
'arm64': 'arm64'
};
const mappedPlatform = platformMap[plat];
const mappedArch = archMap[architecture];
if (!mappedPlatform || !mappedArch) {
return { supported: false, platform: plat, arch: architecture };
}
let binaryName = `szci-${mappedPlatform}-${mappedArch}`;
if (plat === 'win32') {
binaryName += '.exe';
}
return {
supported: true,
platform: mappedPlatform,
arch: mappedArch,
binaryName,
originalPlatform: plat
};
}
function downloadFile(url, destination) {
return new Promise((resolve, reject) => {
console.log(`Downloading from: ${url}`);
// Follow redirects
const download = (url, redirectCount = 0) => {
if (redirectCount > 5) {
reject(new Error('Too many redirects'));
return;
}
https.get(url, (response) => {
if (response.statusCode === 301 || response.statusCode === 302) {
console.log(`Following redirect to: ${response.headers.location}`);
download(response.headers.location, redirectCount + 1);
return;
}
if (response.statusCode !== 200) {
reject(new Error(`Failed to download: ${response.statusCode} ${response.statusMessage}`));
return;
}
const totalSize = parseInt(response.headers['content-length'], 10);
let downloadedSize = 0;
let lastProgress = 0;
response.on('data', (chunk) => {
downloadedSize += chunk.length;
const progress = Math.round((downloadedSize / totalSize) * 100);
// Only log every 10% to reduce noise
if (progress >= lastProgress + 10) {
console.log(`Download progress: ${progress}%`);
lastProgress = progress;
}
});
const file = createWriteStream(destination);
pipeline(response, file, (err) => {
if (err) {
reject(err);
} else {
console.log('Download complete!');
resolve();
}
});
}).on('error', reject);
};
download(url);
});
}
async function main() {
console.log('===========================================');
console.log(' SZCI - Binary Installation');
console.log('===========================================');
console.log('');
const binaryInfo = getBinaryInfo();
if (!binaryInfo.supported) {
console.error(`❌ Error: Unsupported platform/architecture: ${binaryInfo.platform}/${binaryInfo.arch}`);
console.error('');
console.error('Supported platforms:');
console.error(' • Linux (x64, arm64)');
console.error(' • macOS (x64, arm64)');
console.error(' • Windows (x64)');
console.error('');
console.error('If you believe your platform should be supported, please file an issue:');
console.error(' https://code.foss.global/ship.zone/szci/issues');
process.exit(1);
}
console.log(`Platform: ${binaryInfo.platform} (${binaryInfo.originalPlatform})`);
console.log(`Architecture: ${binaryInfo.arch}`);
console.log(`Binary: ${binaryInfo.binaryName}`);
console.log(`Version: ${VERSION}`);
console.log('');
// Create dist/binaries directory if it doesn't exist
const binariesDir = join(__dirname, '..', 'dist', 'binaries');
if (!existsSync(binariesDir)) {
console.log('Creating binaries directory...');
mkdirSync(binariesDir, { recursive: true });
}
const binaryPath = join(binariesDir, binaryInfo.binaryName);
// Check if binary already exists and skip download
if (existsSync(binaryPath)) {
console.log('✓ Binary already exists, skipping download');
} else {
// Construct download URL
// Try release URL first, fall back to raw branch if needed
const releaseUrl = `${REPO_BASE}/releases/download/v${VERSION}/${binaryInfo.binaryName}`;
const fallbackUrl = `${REPO_BASE}/raw/branch/master/dist/binaries/${binaryInfo.binaryName}`;
console.log('Downloading platform-specific binary...');
console.log('This may take a moment depending on your connection speed.');
console.log('');
try {
// Try downloading from release
await downloadFile(releaseUrl, binaryPath);
} catch (err) {
console.log(`Release download failed: ${err.message}`);
console.log('Trying fallback URL...');
try {
// Try fallback URL
await downloadFile(fallbackUrl, binaryPath);
} catch (fallbackErr) {
console.error(`❌ Error: Failed to download binary`);
console.error(` Primary URL: ${releaseUrl}`);
console.error(` Fallback URL: ${fallbackUrl}`);
console.error('');
console.error('This might be because:');
console.error('1. The release has not been created yet');
console.error('2. Network connectivity issues');
console.error('3. The version specified does not exist');
console.error('');
console.error('You can try:');
console.error('1. Installing from source: https://code.foss.global/ship.zone/szci');
console.error('2. Downloading the binary manually from the releases page');
console.error('3. Building from source with: deno task compile');
// Clean up partial download
if (existsSync(binaryPath)) {
unlinkSync(binaryPath);
}
process.exit(1);
}
}
console.log(`✓ Binary downloaded successfully`);
}
// On Unix-like systems, ensure the binary is executable
if (binaryInfo.originalPlatform !== 'win32') {
try {
console.log('Setting executable permissions...');
chmodSync(binaryPath, 0o755);
console.log('✓ Binary permissions updated');
} catch (err) {
console.error(`⚠️ Warning: Could not set executable permissions: ${err.message}`);
console.error(' You may need to manually run:');
console.error(` chmod +x ${binaryPath}`);
}
}
console.log('');
console.log('✅ SZCI installation completed successfully!');
console.log('');
console.log('You can now use SZCI by running:');
console.log(' szci --help');
console.log('');
console.log('===========================================');
}
// Run the installation
main().catch(err => {
console.error(`❌ Installation failed: ${err.message}`);
process.exit(1);
});

View File

@@ -0,0 +1,2 @@
FROM mygroup/myrepo:sometag2
RUN apt-get update

View File

@@ -1 +1,9 @@
{} {
"gitzone": {
"module": {
"githost": "code.foss.global",
"gitscope": "mygroup",
"gitrepo": "myrepo"
}
}
}

View File

@@ -1,10 +1,21 @@
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; // Disable TLS certificate validation for testing
import { tap, expect } from '@pushrocks/tapbundle'; Deno.env.set('NODE_TLS_REJECT_UNAUTHORIZED', '0');
Deno.env.set('SZCI_TEST', 'true');
Deno.env.set('CI_REPOSITORY_URL', 'https://yyyyyy:xxxxxxxx@gitlab.com/mygroup/myrepo.git');
import * as cloudlyConnectorMod from '../ts/connector.cloudly/cloudlyconnector.js'; import { CloudlyConnector } from '../ts/connector.cloudly/cloudlyconnector.ts';
import { Szci } from '../ts/szci.classes.szci.ts';
tap.test('should be able to announce a container to cloudly', async () => { Deno.test({
const cloudlyConnector = new cloudlyConnectorMod.CloudlyConnector(null); name: 'should be able to announce a container to cloudly',
sanitizeResources: false,
sanitizeOps: false,
fn: async () => {
// Create a proper Szci instance for the connector
const szciInstance = new Szci();
await szciInstance.start();
const cloudlyConnector = new CloudlyConnector(szciInstance);
await cloudlyConnector.announceDockerContainer( await cloudlyConnector.announceDockerContainer(
{ {
registryUrl: 'registry.losssless.com', registryUrl: 'registry.losssless.com',
@@ -14,13 +25,5 @@ tap.test('should be able to announce a container to cloudly', async () => {
}, },
'cloudly.lossless.one' 'cloudly.lossless.one'
); );
},
}); });
tap.test('should close the program despite socket timeout', async (toolsArg) => {
// TODO: remove when unreffed timeouts in webrequest have been solved.
toolsArg.delayFor(0).then(() => {
process.exit();
});
});
tap.start();

View File

@@ -1,104 +1,131 @@
import { tap, expect } from '@pushrocks/tapbundle'; import { assertEquals, assertExists } from '@std/assert';
import * as path from 'path'; import * as path from '@std/path';
import * as smartpath from '@pushrocks/smartpath'; import * as smartpath from '@push.rocks/smartpath';
process.env.NPMTS_TEST = 'true'; // Set up test environment with the NEW SZCI environment variables
process.env.NPMCI_URL_CLOUDLY = 'localhost'; Deno.env.set('SZCI_TEST', 'true');
Deno.env.set('SZCI_URL_CLOUDLY', 'localhost');
Deno.env.set('CI_REPOSITORY_URL', 'https://yyyyyy:xxxxxxxx@gitlab.com/mygroup/myrepo.git');
Deno.env.set('CI_BUILD_TOKEN', 'kjlkjfiudofiufs');
Deno.env.set('SZCI_LOGIN_DOCKER', 'docker.io|someuser|somepass');
Deno.env.set('SZCI_SSHKEY_1', 'hostString|somePrivKey|##');
// set up environment // Get the test assets directory
process.env.CI_REPOSITORY_URL = 'https://yyyyyy:xxxxxxxx@gitlab.com/mygroup/myrepo.git'; const testAssetsDir = path.join(smartpath.get.dirnameFromImportMetaUrl(import.meta.url), 'assets/');
process.env.CI_BUILD_TOKEN = 'kjlkjfiudofiufs';
// Docker // Save original cwd and change to test assets
process.env.NPMCI_LOGIN_DOCKER = 'docker.io|someuser|somepass'; const originalCwd = Deno.cwd();
Deno.chdir(testAssetsDir);
// SSH env import type { Dockerfile } from '../ts/manager.docker/mod.classes.dockerfile.ts';
process.env.NPMCI_SSHKEY_1 = 'hostString|somePrivKey|##'; import { Szci } from '../ts/szci.classes.szci.ts';
import * as DockerfileModule from '../ts/manager.docker/mod.classes.dockerfile.ts';
process.cwd = () => {
return path.join(smartpath.get.dirnameFromImportMetaUrl(import.meta.url), 'assets/');
};
let npmci: typeof import('../ts/index.js');
tap.preTask('should import npmci', async () => {
npmci = await import('../ts/index.js');
});
// ====== // ======
// Docker // Docker
// ====== // ======
let dockerfile1: npmci.Dockerfile; let dockerfile1: Dockerfile;
let dockerfile2: npmci.Dockerfile; let dockerfile2: Dockerfile;
let sortableArray: npmci.Dockerfile[]; let sortableArray: Dockerfile[];
tap.test('should return valid Dockerfiles', async () => { Deno.test('should return valid Dockerfiles', async () => {
const npmciInstance = new npmci.Npmci(); const szciInstance = new Szci();
dockerfile1 = new npmci.Dockerfile(npmciInstance.dockerManager, { await szciInstance.start();
dockerfile1 = new DockerfileModule.Dockerfile(szciInstance.dockerManager, {
filePath: './Dockerfile', filePath: './Dockerfile',
read: true, read: true,
}); });
dockerfile2 = new npmci.Dockerfile(npmciInstance.dockerManager, { dockerfile2 = new DockerfileModule.Dockerfile(szciInstance.dockerManager, {
filePath: './Dockerfile_sometag1', filePath: './Dockerfile_sometag1',
read: true, read: true,
}); });
expect(dockerfile1.version).toEqual('latest'); assertEquals(dockerfile1.version, 'latest');
return expect(dockerfile2.version).toEqual('sometag1'); assertEquals(dockerfile2.version, 'sometag1');
}); });
tap.test('should read a directory of Dockerfiles', async () => { Deno.test('should read a directory of Dockerfiles', async () => {
const npmciInstance = new npmci.Npmci(); const szciInstance = new Szci();
return npmci.Dockerfile.readDockerfiles(npmciInstance.dockerManager).then( await szciInstance.start();
async (readDockerfilesArrayArg: npmci.Dockerfile[]) => { const readDockerfilesArray = await DockerfileModule.Dockerfile.readDockerfiles(
sortableArray = readDockerfilesArrayArg; szciInstance.dockerManager
return expect(readDockerfilesArrayArg[1].version).toEqual('sometag1');
}
); );
sortableArray = readDockerfilesArray;
// The test assets directory should have multiple Dockerfiles
assertExists(readDockerfilesArray, 'readDockerfilesArray should exist');
assertEquals(readDockerfilesArray.length > 0, true, 'Should find at least one Dockerfile');
// Find the sometag1 dockerfile
const sometag1Dockerfile = readDockerfilesArray.find(df => df.version === 'sometag1');
assertExists(sometag1Dockerfile, 'Should find Dockerfile_sometag1');
assertEquals(sometag1Dockerfile?.version, 'sometag1');
}); });
tap.test('should sort an array of Dockerfiles', async () => { Deno.test('should sort an array of Dockerfiles', async () => {
return npmci.Dockerfile.sortDockerfiles(sortableArray).then( // Use the sortableArray from previous test, or create a new one if empty
async (sortedArrayArg: npmci.Dockerfile[]) => { if (!sortableArray || sortableArray.length === 0) {
console.log(sortedArrayArg); const szciInstance = new Szci();
await szciInstance.start();
sortableArray = await DockerfileModule.Dockerfile.readDockerfiles(szciInstance.dockerManager);
} }
);
const sortedArray = await DockerfileModule.Dockerfile.sortDockerfiles(sortableArray);
assertExists(sortedArray, 'sortedArray should exist');
console.log('Sorted dockerfiles:', sortedArray.map(df => df.cleanTag));
}); });
tap.test('should build all Dockerfiles', async () => { Deno.test({
const npmciInstance = new npmci.Npmci(); name: 'should build all Dockerfiles',
return npmciInstance.dockerManager.handleCli({ // Allow resource leaks since smartshell creates background processes
sanitizeResources: false,
sanitizeOps: false,
fn: async () => {
const szciInstance = new Szci();
await szciInstance.start();
await szciInstance.dockerManager.handleCli({
_: ['docker', 'build'], _: ['docker', 'build'],
}); });
},
}); });
tap.test('should test all Dockerfiles', async () => { Deno.test({
const npmciInstance = new npmci.Npmci(); name: 'should test all Dockerfiles',
return npmciInstance.dockerManager.handleCli({ // Allow resource leaks since smartshell creates background processes
sanitizeResources: false,
sanitizeOps: false,
fn: async () => {
const szciInstance = new Szci();
await szciInstance.start();
await szciInstance.dockerManager.handleCli({
_: ['docker', 'test'], _: ['docker', 'test'],
}); });
},
}); });
tap.test('should test dockerfiles', async () => { Deno.test({
const npmciInstance = new npmci.Npmci(); name: 'should login docker daemon',
return npmciInstance.dockerManager.handleCli({ // Allow resource leaks since smartshell creates background processes
_: ['docker', 'test'], sanitizeResources: false,
}); sanitizeOps: false,
}); fn: async () => {
const szciInstance = new Szci();
tap.test('should login docker daemon', async () => { await szciInstance.start();
const npmciInstance = new npmci.Npmci(); await szciInstance.dockerManager.handleCli({
return npmciInstance.dockerManager.handleCli({
_: ['docker', 'login'], _: ['docker', 'login'],
}); });
},
}); });
// === // ===
// SSH // SSH
// === // ===
tap.test('should prepare SSH keys', async () => { Deno.test('should prepare SSH keys', async () => {
const npmciModSsh = await import('../ts/mod_ssh/index.js'); // Ensure test mode is set so we don't actually write to disk
return await npmciModSsh.handleCli({ Deno.env.set('SZCI_TEST', 'true');
const szciModSsh = await import('../ts/mod_ssh/index.ts');
await szciModSsh.handleCli({
_: ['ssh', 'prepare'], _: ['ssh', 'prepare'],
}); });
}); });
@@ -106,24 +133,27 @@ tap.test('should prepare SSH keys', async () => {
// ==== // ====
// node // node
// ==== // ====
tap.test('should install a certain version of node', async () => { Deno.test({
const npmciInstance = new npmci.Npmci(); name: 'should install a certain version of node',
await npmciInstance.nodejsManager.handleCli({ // Allow resource leaks for this test since nvm creates background processes
sanitizeResources: false,
sanitizeOps: false,
fn: async () => {
const szciInstance = new Szci();
await szciInstance.start();
await szciInstance.nodejsManager.handleCli({
_: ['node', 'install', 'stable'], _: ['node', 'install', 'stable'],
}); });
await npmciInstance.nodejsManager.handleCli({ await szciInstance.nodejsManager.handleCli({
_: ['node', 'install', 'lts'], _: ['node', 'install', 'lts'],
}); });
await npmciInstance.nodejsManager.handleCli({ await szciInstance.nodejsManager.handleCli({
_: ['node', 'install', 'legacy'], _: ['node', 'install', 'legacy'],
}); });
},
}); });
// make sure test ends all right // Restore original working directory after all tests
tap.test('reset paths', async () => { Deno.test('reset paths', () => {
process.cwd = () => { Deno.chdir(originalCwd);
return path.join(__dirname, '../');
};
}); });
tap.start();

View File

@@ -1,8 +1,8 @@
/** /**
* autocreated commitinfo by @pushrocks/commitinfo * autocreated commitinfo by @push.rocks/commitinfo
*/ */
export const commitinfo = { export const commitinfo = {
name: '@shipzone/npmci', name: '@ship.zone/szci',
version: '4.0.10', version: '7.0.0',
description: 'node and docker in gitlab ci on steroids' description: 'Serve Zone CI - A tool to streamline Node.js and Docker workflows within CI environments, particularly GitLab CI, providing various CI/CD utilities. Powered by Deno with standalone executables.'
} }

View File

@@ -1,23 +1,23 @@
import * as plugins from '../npmci.plugins.js'; import * as plugins from '../szci.plugins.ts';
import { Npmci } from '../npmci.classes.npmci.js'; import { Szci } from '../szci.classes.szci.ts';
import { logger } from '../npmci.logging.js'; import { logger } from '../szci.logging.ts';
/** /**
* connects to cloudly * connects to cloudly
*/ */
export class CloudlyConnector { export class CloudlyConnector {
public npmciRef: Npmci; public szciRef: Szci;
constructor(npmciRefArg: Npmci) { constructor(szciRefArg: Szci) {
this.npmciRef = npmciRefArg; this.szciRef = szciRefArg;
} }
public async announceDockerContainer( public async announceDockerContainer(
optionsArg: plugins.tsclass.container.IContainer, optionsArg: plugins.tsclass.container.IContainer,
testCloudlyUrlArg?: string testCloudlyUrlArg?: string
) { ) {
const cloudlyUrl = testCloudlyUrlArg || this.npmciRef.npmciConfig.getConfig().urlCloudly; const cloudlyUrl = testCloudlyUrlArg || this.szciRef.szciConfig.getConfig().urlCloudly;
if (!cloudlyUrl) { if (!cloudlyUrl) {
logger.log( logger.log(
'warn', 'warn',
@@ -26,14 +26,6 @@ export class CloudlyConnector {
return; return;
} }
const typedrequest = // lets push to cloudly here
new plugins.typedrequest.TypedRequest<plugins.servezoneInterfaces.requests.IRequest_InformAboutNewContainerImage>(
`https://${cloudlyUrl}/typedrequest`,
'servezonestandard_InformAboutNewContainerVersion'
);
const response = await typedrequest.fire({
containerImageInfo: optionsArg,
});
} }
} }

View File

@@ -1,10 +1,9 @@
import { Npmci } from './npmci.classes.npmci.js'; import { Szci } from './szci.classes.szci.ts';
import { Dockerfile } from './manager.docker/mod.classes.dockerfile.js';
export const npmciInstance = new Npmci(); export const szciInstance = new Szci();
export { Dockerfile, Npmci }; export { Szci };
export const runCli = async () => { export const runCli = async () => {
npmciInstance.start(); await szciInstance.start();
}; };

View File

@@ -1,179 +1,78 @@
import { logger } from '../npmci.logging.js'; import { logger } from '../szci.logging.ts';
import * as plugins from './mod.plugins.js'; import { bash } from '../szci.bash.ts';
import * as paths from '../npmci.paths.js'; import { Szci } from '../szci.classes.szci.ts';
import { bash } from '../npmci.bash.js';
// classes export class SzciDockerManager {
import { Npmci } from '../npmci.classes.npmci.js'; public szciRef: Szci;
import { Dockerfile } from './mod.classes.dockerfile.js';
import { DockerRegistry } from './mod.classes.dockerregistry.js';
import { RegistryStorage } from './mod.classes.registrystorage.js';
export class NpmciDockerManager { constructor(szciArg: Szci) {
public npmciRef: Npmci; this.szciRef = szciArg;
public npmciRegistryStorage = new RegistryStorage();
constructor(npmciArg: Npmci) {
this.npmciRef = npmciArg;
} }
/** /**
* handle cli input * Bridges SZCI_LOGIN_DOCKER* env vars to DOCKER_REGISTRY_N format for tsdocker,
* @param argvArg * and handles GitLab CI registry auto-login.
*/
private bridgeEnvVars() {
const env = Deno.env.toObject();
// Bridge GitLab CI registry as DOCKER_REGISTRY_0
if (env['GITLAB_CI']) {
const ciJobToken = env['CI_JOB_TOKEN'];
if (!ciJobToken) {
logger.log('error', 'Running in GitLab CI, but no CI_JOB_TOKEN found!');
Deno.exit(1);
}
Deno.env.set('DOCKER_REGISTRY_0', `registry.gitlab.com|gitlab-ci-token|${ciJobToken}`);
}
// Bridge SZCI_LOGIN_DOCKER* → DOCKER_REGISTRY_N
let registryIndex = 1;
const sortedKeys = Object.keys(env)
.filter((key) => key.startsWith('SZCI_LOGIN_DOCKER'))
.sort();
for (const key of sortedKeys) {
Deno.env.set(`DOCKER_REGISTRY_${registryIndex}`, env[key]);
registryIndex++;
}
}
/**
* Handle cli input by bridging env vars and delegating to tsdocker.
*/ */
public handleCli = async (argvArg: any) => { public handleCli = async (argvArg: any) => {
if (argvArg._.length >= 2) { if (argvArg._.length < 2) {
logger.log(
'info',
`>>szci docker ...<< cli arguments invalid... Please read the documentation.`
);
return;
}
this.bridgeEnvVars();
const action: string = argvArg._[1]; const action: string = argvArg._[1];
const extraArgs = argvArg._.slice(2).join(' ');
switch (action) { switch (action) {
case 'build': case 'build':
await this.build(); await bash(`npx @git.zone/tsdocker build ${extraArgs}`.trim());
break; break;
case 'login': case 'login':
case 'prepare': case 'prepare':
await this.login(); await bash(`npx @git.zone/tsdocker login ${extraArgs}`.trim());
break; break;
case 'test': case 'test':
await this.test(); await bash(`npx @git.zone/tsdocker test ${extraArgs}`.trim());
break; break;
case 'push': case 'push':
await this.push(argvArg); await bash(`npx @git.zone/tsdocker push ${extraArgs}`.trim());
break; break;
case 'pull': case 'pull':
await this.pull(argvArg); await bash(`npx @git.zone/tsdocker pull ${extraArgs}`.trim());
break; break;
default: default:
logger.log('error', `>>npmci docker ...<< action >>${action}<< not supported`); logger.log('error', `>>szci docker ...<< action >>${action}<< not supported`);
} }
} else {
logger.log(
'info',
`>>npmci docker ...<< cli arguments invalid... Please read the documentation.`
);
}
};
/**
* builds a cwd of Dockerfiles by triggering a promisechain
*/
public build = async () => {
await this.prepare();
logger.log('info', 'now building Dockerfiles...');
await Dockerfile.readDockerfiles(this)
.then(Dockerfile.sortDockerfiles)
.then(Dockerfile.mapDockerfiles)
.then(Dockerfile.buildDockerfiles);
};
/**
* login to the DockerRegistries
*/
public login = async () => {
await this.prepare();
await this.npmciRegistryStorage.loginAll();
};
/**
* logs in docker
*/
public prepare = async () => {
// Always login to GitLab Registry
if (!process.env.CI_BUILD_TOKEN || process.env.CI_BUILD_TOKEN === '') {
logger.log('error', 'No registry token specified by gitlab!');
process.exit(1);
}
this.npmciRegistryStorage.addRegistry(
new DockerRegistry({
registryUrl: 'registry.gitlab.com',
username: 'gitlab-ci-token',
password: process.env.CI_BUILD_TOKEN,
})
);
// handle registries
await plugins.smartparam.forEachMinimatch(
process.env,
'NPMCI_LOGIN_DOCKER*',
async (envString: string) => {
this.npmciRegistryStorage.addRegistry(DockerRegistry.fromEnvString(envString));
}
);
return;
};
/**
* pushes an image towards a registry
* @param argvArg
*/
public push = async (argvArg: any) => {
await this.prepare();
let dockerRegistryUrls: string[] = [];
// lets parse the input of cli and npmextra
if (argvArg._.length >= 3 && argvArg._[2] !== 'npmextra') {
dockerRegistryUrls.push(argvArg._[2]);
} else {
if (this.npmciRef.npmciConfig.getConfig().dockerRegistries.length === 0) {
logger.log(
'warn',
`There are no docker registries listed in npmextra.json! This is strange!`
);
}
dockerRegistryUrls = dockerRegistryUrls.concat(
this.npmciRef.npmciConfig.getConfig().dockerRegistries
);
}
// lets determine the suffix
let suffix = null;
if (argvArg._.length >= 4) {
suffix = argvArg._[3];
}
// lets push to the registries
for (const dockerRegistryUrl of dockerRegistryUrls) {
const dockerfileArray = await Dockerfile.readDockerfiles(this)
.then(Dockerfile.sortDockerfiles)
.then(Dockerfile.mapDockerfiles);
const dockerRegistryToPushTo = await this.npmciRegistryStorage.getRegistryByUrl(
dockerRegistryUrl
);
if (!dockerRegistryToPushTo) {
logger.log(
'error',
`Cannot push to registry ${dockerRegistryUrl}, because it was not found in the authenticated registry list.`
);
process.exit(1);
}
for (const dockerfile of dockerfileArray) {
await dockerfile.push(dockerRegistryToPushTo, suffix);
}
}
};
/**
* pulls an image
*/
public pull = async (argvArg: any) => {
await this.prepare();
const registryUrlArg = argvArg._[2];
let suffix = null;
if (argvArg._.length >= 4) {
suffix = argvArg._[3];
}
const localDockerRegistry = await this.npmciRegistryStorage.getRegistryByUrl(registryUrlArg);
const dockerfileArray = await Dockerfile.readDockerfiles(this)
.then(Dockerfile.sortDockerfiles)
.then(Dockerfile.mapDockerfiles);
for (const dockerfile of dockerfileArray) {
await dockerfile.pull(localDockerRegistry, suffix);
}
};
/**
* tests docker files
*/
public test = async () => {
await this.prepare();
return await Dockerfile.readDockerfiles(this).then(Dockerfile.testDockerfiles);
}; };
} }

View File

@@ -1,328 +0,0 @@
import * as plugins from './mod.plugins.js';
import * as paths from '../npmci.paths.js';
import { logger } from '../npmci.logging.js';
import { bash } from '../npmci.bash.js';
import { DockerRegistry } from './mod.classes.dockerregistry.js';
import * as helpers from './mod.helpers.js';
import { NpmciDockerManager } from './index.js';
import { Npmci } from '../npmci.classes.npmci.js';
/**
* class Dockerfile represents a Dockerfile on disk in npmci
*/
export class Dockerfile {
// STATIC
/**
* creates instance of class Dockerfile for all Dockerfiles in cwd
* @returns Promise<Dockerfile[]>
*/
public static async readDockerfiles(
npmciDockerManagerRefArg: NpmciDockerManager
): Promise<Dockerfile[]> {
const fileTree = await plugins.smartfile.fs.listFileTree(paths.cwd, 'Dockerfile*');
// create the Dockerfile array
const readDockerfilesArray: Dockerfile[] = [];
logger.log('info', `found ${fileTree.length} Dockerfiles:`);
console.log(fileTree);
for (const dockerfilePath of fileTree) {
const myDockerfile = new Dockerfile(npmciDockerManagerRefArg, {
filePath: dockerfilePath,
read: true,
});
readDockerfilesArray.push(myDockerfile);
}
return readDockerfilesArray;
}
/**
* sorts Dockerfiles into a dependency chain
* @param sortableArrayArg an array of instances of class Dockerfile
* @returns Promise<Dockerfile[]>
*/
public static async sortDockerfiles(sortableArrayArg: Dockerfile[]): Promise<Dockerfile[]> {
const done = plugins.smartpromise.defer<Dockerfile[]>();
logger.log('info', 'sorting Dockerfiles:');
const sortedArray: Dockerfile[] = [];
const cleanTagsOriginal = Dockerfile.cleanTagsArrayFunction(sortableArrayArg, sortedArray);
let sorterFunctionCounter: number = 0;
const sorterFunction = () => {
sortableArrayArg.forEach((dockerfileArg) => {
const cleanTags = Dockerfile.cleanTagsArrayFunction(sortableArrayArg, sortedArray);
if (
cleanTags.indexOf(dockerfileArg.baseImage) === -1 &&
sortedArray.indexOf(dockerfileArg) === -1
) {
sortedArray.push(dockerfileArg);
}
if (cleanTagsOriginal.indexOf(dockerfileArg.baseImage) !== -1) {
dockerfileArg.localBaseImageDependent = true;
}
});
if (sortableArrayArg.length === sortedArray.length) {
let counter = 1;
for (const dockerfile of sortedArray) {
logger.log('info', `tag ${counter}: -> ${dockerfile.cleanTag}`);
counter++;
}
done.resolve(sortedArray);
} else if (sorterFunctionCounter < 10) {
sorterFunctionCounter++;
sorterFunction();
}
};
sorterFunction();
return done.promise;
}
/**
* maps local Dockerfiles dependencies to the correspoding Dockerfile class instances
*/
public static async mapDockerfiles(sortedDockerfileArray: Dockerfile[]): Promise<Dockerfile[]> {
sortedDockerfileArray.forEach((dockerfileArg) => {
if (dockerfileArg.localBaseImageDependent) {
sortedDockerfileArray.forEach((dockfile2: Dockerfile) => {
if (dockfile2.cleanTag === dockerfileArg.baseImage) {
dockerfileArg.localBaseDockerfile = dockfile2;
}
});
}
});
return sortedDockerfileArray;
}
/**
* builds the correspoding real docker image for each Dockerfile class instance
*/
public static async buildDockerfiles(sortedArrayArg: Dockerfile[]) {
for (const dockerfileArg of sortedArrayArg) {
await dockerfileArg.build();
}
return sortedArrayArg;
}
/**
* tests all Dockerfiles in by calling class Dockerfile.test();
* @param sortedArrayArg Dockerfile[] that contains all Dockerfiles in cwd
*/
public static async testDockerfiles(sortedArrayArg: Dockerfile[]) {
for (const dockerfileArg of sortedArrayArg) {
await dockerfileArg.test();
}
return sortedArrayArg;
}
/**
* returns a version for a docker file
* @execution SYNC
*/
public static dockerFileVersion(dockerfileNameArg: string): string {
let versionString: string;
const versionRegex = /Dockerfile_([a-zA-Z0-9\.]*)$/;
const regexResultArray = versionRegex.exec(dockerfileNameArg);
if (regexResultArray && regexResultArray.length === 2) {
versionString = regexResultArray[1];
} else {
versionString = 'latest';
}
return versionString;
}
/**
* returns the docker base image for a Dockerfile
*/
public static dockerBaseImage(dockerfileContentArg: string): string {
const baseImageRegex = /FROM\s([a-zA-z0-9\/\-\:]*)\n?/;
const regexResultArray = baseImageRegex.exec(dockerfileContentArg);
return regexResultArray[1];
}
/**
* returns the docker tag
*/
public static getDockerTagString(
npmciDockerManagerRef: NpmciDockerManager,
registryArg: string,
repoArg: string,
versionArg: string,
suffixArg?: string
): string {
// determine wether the repo should be mapped accordingly to the registry
const mappedRepo =
npmciDockerManagerRef.npmciRef.npmciConfig.getConfig().dockerRegistryRepoMap[registryArg];
const repo = (() => {
if (mappedRepo) {
return mappedRepo;
} else {
return repoArg;
}
})();
// determine wether the version contais a suffix
let version = versionArg;
if (suffixArg) {
version = versionArg + '_' + suffixArg;
}
const tagString = `${registryArg}/${repo}:${version}`;
return tagString;
}
public static async getDockerBuildArgs(
npmciDockerManagerRef: NpmciDockerManager
): Promise<string> {
logger.log('info', 'checking for env vars to be supplied to the docker build');
let buildArgsString: string = '';
for (const key of Object.keys(
npmciDockerManagerRef.npmciRef.npmciConfig.getConfig().dockerBuildargEnvMap
)) {
const targetValue =
process.env[
npmciDockerManagerRef.npmciRef.npmciConfig.getConfig().dockerBuildargEnvMap[key]
];
buildArgsString = `${buildArgsString} --build-arg ${key}="${targetValue}"`;
}
return buildArgsString;
}
/**
*
*/
public static cleanTagsArrayFunction(
dockerfileArrayArg: Dockerfile[],
trackingArrayArg: Dockerfile[]
): string[] {
const cleanTagsArray: string[] = [];
dockerfileArrayArg.forEach((dockerfileArg) => {
if (trackingArrayArg.indexOf(dockerfileArg) === -1) {
cleanTagsArray.push(dockerfileArg.cleanTag);
}
});
return cleanTagsArray;
}
// INSTANCE
public npmciDockerManagerRef: NpmciDockerManager;
public filePath: string;
public repo: string;
public version: string;
public cleanTag: string;
public buildTag: string;
public pushTag: string;
public containerName: string;
public content: string;
public baseImage: string;
public localBaseImageDependent: boolean;
public localBaseDockerfile: Dockerfile;
constructor(
dockerManagerRefArg: NpmciDockerManager,
options: { filePath?: string; fileContents?: string | Buffer; read?: boolean }
) {
this.npmciDockerManagerRef = dockerManagerRefArg;
this.filePath = options.filePath;
this.repo =
this.npmciDockerManagerRef.npmciRef.npmciEnv.repo.user +
'/' +
this.npmciDockerManagerRef.npmciRef.npmciEnv.repo.repo;
this.version = Dockerfile.dockerFileVersion(plugins.path.parse(options.filePath).base);
this.cleanTag = this.repo + ':' + this.version;
this.buildTag = this.cleanTag;
this.containerName = 'dockerfile-' + this.version;
if (options.filePath && options.read) {
this.content = plugins.smartfile.fs.toStringSync(plugins.path.resolve(options.filePath));
}
this.baseImage = Dockerfile.dockerBaseImage(this.content);
this.localBaseImageDependent = false;
}
/**
* builds the Dockerfile
*/
public async build() {
logger.log('info', 'now building Dockerfile for ' + this.cleanTag);
const buildArgsString = await Dockerfile.getDockerBuildArgs(this.npmciDockerManagerRef);
const buildCommand = `docker build --label="version=${
this.npmciDockerManagerRef.npmciRef.npmciConfig.getConfig().projectInfo.npm.version
}" -t ${this.buildTag} -f ${this.filePath} ${buildArgsString} .`;
await bash(buildCommand);
return;
}
/**
* pushes the Dockerfile to a registry
*/
public async push(dockerRegistryArg: DockerRegistry, versionSuffix: string = null) {
this.pushTag = Dockerfile.getDockerTagString(
this.npmciDockerManagerRef,
dockerRegistryArg.registryUrl,
this.repo,
this.version,
versionSuffix
);
await bash(`docker tag ${this.buildTag} ${this.pushTag}`);
await bash(`docker push ${this.pushTag}`);
const imageDigest = (
await bash(`docker inspect --format="{{index .RepoDigests 0}}" ${this.pushTag}`)
).split('@')[1];
console.log(`The image ${this.pushTag} has digest ${imageDigest}`);
await this.npmciDockerManagerRef.npmciRef.cloudlyConnector.announceDockerContainer({
registryUrl: this.pushTag,
tag: this.buildTag,
labels: [],
version: this.npmciDockerManagerRef.npmciRef.npmciConfig.getConfig().projectInfo.npm.version,
});
}
/**
* pulls the Dockerfile from a registry
*/
public async pull(registryArg: DockerRegistry, versionSuffixArg: string = null) {
const pullTag = Dockerfile.getDockerTagString(
this.npmciDockerManagerRef,
registryArg.registryUrl,
this.repo,
this.version,
versionSuffixArg
);
await bash(`docker pull ${pullTag}`);
await bash(`docker tag ${pullTag} ${this.buildTag}`);
}
/**
* tests the Dockerfile;
*/
public async test() {
const testFile: string = plugins.path.join(paths.NpmciTestDir, 'test_' + this.version + '.sh');
const testFileExists: boolean = plugins.smartfile.fs.fileExistsSync(testFile);
if (testFileExists) {
// run tests
await bash(
`docker run --name npmci_test_container --entrypoint="bash" ${this.buildTag} -c "mkdir /npmci_test"`
);
await bash(`docker cp ${testFile} npmci_test_container:/npmci_test/test.sh`);
await bash(`docker commit npmci_test_container npmci_test_image`);
await bash(`docker run --entrypoint="bash" npmci_test_image -x /npmci_test/test.sh`);
await bash(`docker rm npmci_test_container`);
await bash(`docker rmi --force npmci_test_image`);
} else {
logger.log('warn', 'skipping tests for ' + this.cleanTag + ' because no testfile was found!');
}
}
/**
* gets the id of a Dockerfile
*/
public async getId() {
const containerId = await bash(
'docker inspect --type=image --format="{{.Id}}" ' + this.buildTag
);
return containerId;
}
}

View File

@@ -1,48 +0,0 @@
import { logger } from '../npmci.logging.js';
import * as plugins from './mod.plugins.js';
import { bash } from '../npmci.bash.js';
export interface IDockerRegistryConstructorOptions {
registryUrl: string;
username: string;
password: string;
}
export class DockerRegistry {
public registryUrl: string;
public username: string;
public password: string;
constructor(optionsArg: IDockerRegistryConstructorOptions) {
this.registryUrl = optionsArg.registryUrl;
this.username = optionsArg.username;
this.password = optionsArg.password;
logger.log('info', `created DockerRegistry for ${this.registryUrl}`);
}
public static fromEnvString(envString: string): DockerRegistry {
const dockerRegexResultArray = envString.split('|');
if (dockerRegexResultArray.length !== 3) {
logger.log('error', 'malformed docker env var...');
process.exit(1);
return;
}
const registryUrl = dockerRegexResultArray[0];
const username = dockerRegexResultArray[1];
const password = dockerRegexResultArray[2];
return new DockerRegistry({
registryUrl: registryUrl,
username: username,
password: password,
});
}
public async login() {
if (this.registryUrl === 'docker.io') {
await bash(`docker login -u ${this.username} -p ${this.password}`);
logger.log('info', 'Logged in to standard docker hub');
} else {
await bash(`docker login -u ${this.username} -p ${this.password} ${this.registryUrl}`);
}
logger.log('ok', `docker authenticated for ${this.registryUrl}!`);
}
}

View File

@@ -1,29 +0,0 @@
import { logger } from '../npmci.logging.js';
import * as plugins from './mod.plugins.js';
import { ObjectMap } from '@pushrocks/lik';
import { DockerRegistry } from './mod.classes.dockerregistry.js';
export class RegistryStorage {
objectMap = new ObjectMap<DockerRegistry>();
constructor() {
// Nothing here
}
addRegistry(registryArg: DockerRegistry) {
this.objectMap.add(registryArg);
}
getRegistryByUrl(registryUrlArg: string) {
return this.objectMap.findSync((registryArg) => {
return registryArg.registryUrl === registryUrlArg;
});
}
async loginAll() {
await this.objectMap.forEach(async (registryArg) => {
await registryArg.login();
});
logger.log('success', 'logged in successfully into all available DockerRegistries!');
}
}

View File

@@ -1,5 +0,0 @@
import { logger } from '../npmci.logging.js';
import * as plugins from './mod.plugins.js';
import * as paths from '../npmci.paths.js';
import { Dockerfile } from './mod.classes.dockerfile.js';

View File

@@ -1 +0,0 @@
export * from '../npmci.plugins.js';

View File

@@ -1,13 +1,13 @@
import { logger } from '../npmci.logging.js'; import { logger } from '../szci.logging.ts';
import * as plugins from './mod.plugins.js'; import * as plugins from './mod.plugins.ts';
import { bash, bashNoError } from '../npmci.bash.js'; import { bash, bashNoError } from '../szci.bash.ts';
import { Npmci } from '../npmci.classes.npmci.js'; import { Szci } from '../szci.classes.szci.ts';
export class NpmciGitManager { export class SzciGitManager {
public npmciRef: Npmci; public szciRef: Szci;
constructor(npmciRefArg: Npmci) { constructor(szciRefArg: Szci) {
this.npmciRef = npmciRefArg; this.szciRef = szciRefArg;
} }
/** /**
@@ -22,20 +22,20 @@ export class NpmciGitManager {
await this.mirror(); await this.mirror();
break; break;
default: default:
logger.log('error', `npmci git -> action >>${action}<< not supported!`); logger.log('error', `szci git -> action >>${action}<< not supported!`);
} }
} else { } else {
logger.log('info', `npmci git -> cli arguments invalid! Please read the documentation.`); logger.log('info', `szci git -> cli arguments invalid! Please read the documentation.`);
} }
}; };
public mirror = async () => { public mirror = async () => {
const githubToken = process.env.NPMCI_GIT_GITHUBTOKEN; const githubToken = Deno.env.get("SZCI_GIT_GITHUBTOKEN");
const githubUser = process.env.NPMCI_GIT_GITHUBGROUP || this.npmciRef.npmciEnv.repo.user; const githubUser = Deno.env.get("SZCI_GIT_GITHUBGROUP") || this.szciRef.szciEnv.repo.user;
const githubRepo = process.env.NPMCI_GIT_GITHUB || this.npmciRef.npmciEnv.repo.repo; const githubRepo = Deno.env.get("SZCI_GIT_GITHUB") || this.szciRef.szciEnv.repo.repo;
if ( if (
this.npmciRef.npmciConfig.getConfig().projectInfo.npm.packageJson.private === true || this.szciRef.szciConfig.getConfig().projectInfo.npm.packageJson.private === true ||
this.npmciRef.npmciConfig.getConfig().npmAccessLevel === 'private' this.szciRef.szciConfig.getConfig().npmAccessLevel === 'private'
) { ) {
logger.log( logger.log(
'warn', 'warn',
@@ -64,8 +64,8 @@ export class NpmciGitManager {
// remove old mirrors // remove old mirrors
await bashNoError('git remote rm mirror'); await bashNoError('git remote rm mirror');
} else { } else {
logger.log('error', `cannot find NPMCI_GIT_GITHUBTOKEN env var!`); logger.log('error', `cannot find SZCI_GIT_GITHUBTOKEN env var!`);
process.exit(1); Deno.exit(1);
} }
}; };
} }

View File

@@ -1 +1 @@
export * from '../npmci.plugins.js'; export * from '../szci.plugins.ts';

View File

@@ -1,15 +1,15 @@
import * as plugins from '../npmci.plugins.js'; import * as plugins from '../szci.plugins.ts';
import * as paths from '../npmci.paths.js'; import * as paths from '../szci.paths.ts';
import { logger } from '../npmci.logging.js'; import { logger } from '../szci.logging.ts';
import { bash, bashNoError, nvmAvailable } from '../npmci.bash.js'; import { bash, bashNoError, nvmAvailable } from '../szci.bash.ts';
import { Npmci } from '../npmci.classes.npmci.js'; import { Szci } from '../szci.classes.szci.ts';
export class NpmciNodeJsManager { export class SzciNodeJsManager {
public npmciRef: Npmci; public szciRef: Szci;
constructor(npmciRefArg: Npmci) { constructor(szciRefArg: Szci) {
this.npmciRef = npmciRefArg; this.szciRef = szciRefArg;
} }
/** /**
@@ -24,15 +24,15 @@ export class NpmciNodeJsManager {
await this.install(argvArg._[2]); await this.install(argvArg._[2]);
break; break;
default: default:
logger.log('error', `>>npmci node ...<< action >>${action}<< not supported`); logger.log('error', `>>szci node ...<< action >>${action}<< not supported`);
process.exit(1); Deno.exit(1);
} }
} else { } else {
logger.log( logger.log(
'error', 'error',
`>>npmci node ...<< cli arguments invalid... Please read the documentation.` `>>szci node ...<< cli arguments invalid... Please read the documentation.`
); );
process.exit(1); Deno.exit(1);
} }
} }
@@ -44,11 +44,11 @@ export class NpmciNodeJsManager {
logger.log('info', `now installing node version ${versionArg}`); logger.log('info', `now installing node version ${versionArg}`);
let version: string; let version: string;
if (versionArg === 'stable') { if (versionArg === 'stable') {
version = '18'; version = '22';
} else if (versionArg === 'lts') { } else if (versionArg === 'lts') {
version = '16'; version = '20';
} else if (versionArg === 'legacy') { } else if (versionArg === 'legacy') {
version = '14'; version = '18';
} else { } else {
version = versionArg; version = versionArg;
} }
@@ -64,7 +64,7 @@ export class NpmciNodeJsManager {
await bash('npm -v'); await bash('npm -v');
// lets look for further config // lets look for further config
const config = await this.npmciRef.npmciConfig.getConfig(); const config = await this.szciRef.szciConfig.getConfig();
logger.log('info', 'Now checking for needed global npm tools...'); logger.log('info', 'Now checking for needed global npm tools...');
for (const npmTool of config.npmGlobalTools) { for (const npmTool of config.npmGlobalTools) {
logger.log('info', `Checking for global "${npmTool}"`); logger.log('info', `Checking for global "${npmTool}"`);

View File

@@ -1,15 +1,15 @@
import * as plugins from './mod.plugins.js'; import * as plugins from './mod.plugins.ts';
import * as paths from '../npmci.paths.js'; import * as paths from '../szci.paths.ts';
import { logger } from '../npmci.logging.js'; import { logger } from '../szci.logging.ts';
import { bash, bashNoError, nvmAvailable } from '../npmci.bash.js'; import { bash, bashNoError, nvmAvailable } from '../szci.bash.ts';
import { Npmci } from '../npmci.classes.npmci.js'; import { Szci } from '../szci.classes.szci.ts';
export class NpmciNpmManager { export class SzciNpmManager {
public npmciRef: Npmci; public szciRef: Szci;
constructor(npmciRefArg: Npmci) { constructor(szciRefArg: Szci) {
this.npmciRef = npmciRefArg; this.szciRef = szciRefArg;
} }
/** /**
@@ -36,15 +36,15 @@ export class NpmciNpmManager {
await this.publish(); await this.publish();
break; break;
default: default:
logger.log('error', `>>npmci npm ...<< action >>${action}<< not supported`); logger.log('error', `>>szci npm ...<< action >>${action}<< not supported`);
process.exit(1); Deno.exit(1);
} }
} else { } else {
logger.log( logger.log(
'info', 'info',
`>>npmci npm ...<< cli arguments invalid... Please read the documentation.` `>>szci npm ...<< cli arguments invalid... Please read the documentation.`
); );
process.exit(1); Deno.exit(1);
} }
} }
@@ -52,13 +52,19 @@ export class NpmciNpmManager {
* authenticates npm with token from env var * authenticates npm with token from env var
*/ */
public async prepare() { public async prepare() {
const config = this.npmciRef.npmciConfig.getConfig(); logger.log('info', 'running >>npm prepare<<');
const config = this.szciRef.szciConfig.getConfig();
let npmrcFileString: string = ''; let npmrcFileString: string = '';
await plugins.smartparam.forEachMinimatch( await plugins.smartobject.forEachMinimatch(
process.env, Deno.env.toObject(),
'NPMCI_TOKEN_NPM*', 'SZCI_TOKEN_NPM*',
(npmEnvArg: string) => { (npmEnvArg: string) => {
if (!npmEnvArg) {
logger.log('note','found empty token...');
return;
}
const npmRegistryUrl = npmEnvArg.split('|')[0]; const npmRegistryUrl = npmEnvArg.split('|')[0];
logger.log('ok', `found token for ${npmRegistryUrl}`);
let npmToken = npmEnvArg.split('|')[1]; let npmToken = npmEnvArg.split('|')[1];
if (npmEnvArg.split('|')[2] && npmEnvArg.split('|')[2] === 'plain') { if (npmEnvArg.split('|')[2] && npmEnvArg.split('|')[2] === 'plain') {
logger.log('ok', 'npm token not base64 encoded.'); logger.log('ok', 'npm token not base64 encoded.');
@@ -77,14 +83,14 @@ export class NpmciNpmManager {
logger.log('info', 'found one or more access tokens'); logger.log('info', 'found one or more access tokens');
} else { } else {
logger.log('error', 'no access token found! Exiting!'); logger.log('error', 'no access token found! Exiting!');
process.exit(1); Deno.exit(1);
} }
// lets save it to disk // lets save it to disk
plugins.smartfile.memory.toFsSync(npmrcFileString, '/root/.npmrc'); plugins.smartfile.memory.toFsSync(npmrcFileString, '/root/.npmrc');
// lets set the cache directory // lets set the cache directory
await bash(`npm config set cache ${paths.NpmciCacheDir} --global `); await bash(`npm config set cache ${paths.SzciCacheDir} --global `);
return; return;
} }
@@ -97,11 +103,11 @@ export class NpmciNpmManager {
let npmAccessCliString = ``; let npmAccessCliString = ``;
let npmRegistryCliString = ``; let npmRegistryCliString = ``;
let publishVerdaccioAsWell = false; let publishVerdaccioAsWell = false;
const config = this.npmciRef.npmciConfig.getConfig(); const config = this.szciRef.szciConfig.getConfig();
const availableRegistries: string[] = []; const availableRegistries: string[] = [];
await plugins.smartparam.forEachMinimatch( await plugins.smartobject.forEachMinimatch(
process.env, Deno.env.toObject(),
'NPMCI_TOKEN_NPM*', 'SZCI_TOKEN_NPM*',
(npmEnvArg: string) => { (npmEnvArg: string) => {
availableRegistries.push(npmEnvArg.split('|')[0]); availableRegistries.push(npmEnvArg.split('|')[0]);
} }
@@ -121,7 +127,7 @@ export class NpmciNpmManager {
npmRegistryCliString = `--registry=https://${config.npmRegistryUrl}`; npmRegistryCliString = `--registry=https://${config.npmRegistryUrl}`;
} else { } else {
logger.log('error', `no registry url specified. Can't publish!`); logger.log('error', `no registry url specified. Can't publish!`);
process.exit(1); Deno.exit(1);
} }
let publishCommand = `npm publish ${npmAccessCliString} ${npmRegistryCliString} `; let publishCommand = `npm publish ${npmAccessCliString} ${npmRegistryCliString} `;
@@ -160,7 +166,7 @@ export class NpmciNpmManager {
logger.log('success', `Nice!!! The build for the publication was successfull!`); logger.log('success', `Nice!!! The build for the publication was successfull!`);
logger.log('info', `Lets clean up so we don't publish any packages that don't belong to us:`); logger.log('info', `Lets clean up so we don't publish any packages that don't belong to us:`);
// -> clean up before we publish stuff // -> clean up before we publish stuff
await bashNoError(`rm -r ./.npmci_cache`); await bashNoError(`rm -r ./.szci_cache`);
await bash(`rm -r ./node_modules`); await bash(`rm -r ./node_modules`);
logger.log('success', `Cleaned up!:`); logger.log('success', `Cleaned up!:`);

View File

@@ -1 +1 @@
export * from '../npmci.plugins.js'; export * from '../szci.plugins.ts';

View File

@@ -1,10 +0,0 @@
import * as plugins from './mod.plugins.js';
import * as paths from '../npmci.paths.js';
/**
* cleans npmci config files
*/
export let clean = async (): Promise<void> => {
plugins.smartfile.fs.removeSync(paths.NpmciPackageConfig);
return;
};

View File

@@ -1 +0,0 @@
export * from '../npmci.plugins.js';

View File

@@ -1,15 +0,0 @@
import * as plugins from './mod.plugins.js';
import { bash } from '../npmci.bash.js';
export let command = async () => {
let wrappedCommand: string = '';
let argvArray = process.argv;
for (let i = 3; i < argvArray.length; i++) {
wrappedCommand = wrappedCommand + argvArray[i];
if (i + 1 !== argvArray.length) {
wrappedCommand = wrappedCommand + ' ';
}
}
await bash(wrappedCommand);
return;
};

View File

@@ -1 +0,0 @@
export * from '../npmci.plugins.js';

View File

@@ -1,64 +1,97 @@
import { logger } from '../npmci.logging.js'; import { logger } from '../szci.logging.ts';
import * as plugins from './mod.plugins.js'; import * as plugins from './mod.plugins.ts';
let sshInstance: plugins.smartssh.SshInstance; let sshInstance: plugins.smartssh.SshInstance;
export let handleCli = async (argvArg: any) => { /**
* Interface for CLI arguments
*/
interface ICliArgs {
_: string[];
[key: string]: unknown;
}
/**
* Handle SSH CLI commands
*/
export const handleCli = async (argvArg: ICliArgs): Promise<void> => {
if (argvArg._.length >= 2) { if (argvArg._.length >= 2) {
const action: string = argvArg._[1]; const action = argvArg._[1];
switch (action) { switch (action) {
case 'prepare': case 'prepare':
await prepare(); await prepare();
break; break;
default: default:
logger.log('error', `action >>${action}<< not supported`); logger.log('error', `action >>${action}<< not supported`);
process.exit(1); Deno.exit(1);
} }
} else { } else {
logger.log('error', `>>npmci ssh ...<< please specify an action!`); logger.log('error', `>>szci ssh ...<< please specify an action!`);
process.exit(1); Deno.exit(1);
} }
}; };
/** /**
* checks if not undefined * Checks if a string value is defined and not a placeholder
*/ */
const notUndefined = (stringArg: string) => { const isValidValue = (value: string | undefined): boolean => {
return stringArg && stringArg !== 'undefined' && stringArg !== '##'; return Boolean(value && value !== 'undefined' && value !== '##');
}; };
/** /**
* checks for ENV vars in form of NPMCI_SSHKEY_* and deploys any found ones * Checks for ENV vars in form of SZCI_SSHKEY_* and deploys any found ones
*/ */
export let prepare = async () => { export const prepare = async (): Promise<void> => {
sshInstance = new plugins.smartssh.SshInstance(); // init ssh instance sshInstance = new plugins.smartssh.SshInstance();
plugins.smartparam.forEachMinimatch(process.env, 'NPMCI_SSHKEY_*', evaluateSshEnv);
if (!process.env.NPMTS_TEST) { // Get all env vars and filter for SSH keys
const envVars = Deno.env.toObject();
const sshKeyEnvVars = Object.entries(envVars).filter(([key]) =>
key.startsWith('SZCI_SSHKEY_')
);
// Process each SSH key env var
for (const [key, value] of sshKeyEnvVars) {
logger.log('info', `Processing SSH key from ${key}`);
addSshKeyFromEnvVar(value);
}
// Only write to disk if not in test mode
if (!Deno.env.get('SZCI_TEST')) {
try {
sshInstance.writeToDisk(); sshInstance.writeToDisk();
logger.log('ok', 'SSH keys written to disk');
} catch (error) {
logger.log('error', `Failed to write SSH keys: ${(error as Error).message}`);
throw error;
}
} else { } else {
logger.log('info', 'In test mode, so not storing SSH keys to disk!'); logger.log('info', 'In test mode, so not storing SSH keys to disk!');
} }
}; };
/** /**
* gets called for each found SSH ENV Var and deploys it * Parses an SSH key env var and adds it to the SSH instance
* Format: host|privKeyBase64|pubKeyBase64
*/ */
const evaluateSshEnv = async (sshkeyEnvVarArg: string) => { const addSshKeyFromEnvVar = (sshkeyEnvVarArg: string): void => {
const sshEnvArray = sshkeyEnvVarArg.split('|'); const [host, privKeyBase64, pubKeyBase64] = sshkeyEnvVarArg.split('|');
const sshKey = new plugins.smartssh.SshKey(); const sshKey = new plugins.smartssh.SshKey();
logger.log('info', 'Found SSH identity for ' + sshEnvArray[1]);
if (notUndefined(sshEnvArray[0])) { logger.log('info', `Found SSH identity for ${host || 'unknown host'}`);
if (isValidValue(host)) {
logger.log('info', '---> host defined!'); logger.log('info', '---> host defined!');
sshKey.host = sshEnvArray[0]; sshKey.host = host;
} }
if (notUndefined(sshEnvArray[1])) { if (isValidValue(privKeyBase64)) {
logger.log('info', '---> privKey defined!'); logger.log('info', '---> privKey defined!');
sshKey.privKeyBase64 = sshEnvArray[1]; sshKey.privKeyBase64 = privKeyBase64;
} }
if (notUndefined(sshEnvArray[2])) { if (isValidValue(pubKeyBase64)) {
logger.log('info', '---> pubKey defined!'); logger.log('info', '---> pubKey defined!');
sshKey.pubKeyBase64 = sshEnvArray[2]; sshKey.pubKeyBase64 = pubKeyBase64;
} }
sshInstance.addKey(sshKey); sshInstance.addKey(sshKey);
return;
}; };

View File

@@ -1 +1 @@
export * from '../npmci.plugins.js'; export * from '../szci.plugins.ts';

View File

@@ -1,43 +0,0 @@
import * as plugins from './mod.plugins.js';
import { bash } from '../npmci.bash.js';
import { logger } from '../npmci.logging.js';
const triggerValueRegex =
/^([a-zA-Z0-9\.]*)\|([a-zA-Z0-9\.]*)\|([a-zA-Z0-9\.]*)\|([a-zA-Z0-9\.]*)\|?([a-zA-Z0-9\.\-\/]*)/;
export let trigger = async () => {
logger.log('info', 'now running triggers');
await plugins.smartparam.forEachMinimatch(process.env, 'NPMCI_TRIGGER_*', evaluateTrigger);
};
const evaluateTrigger = async (triggerEnvVarArg) => {
const triggerRegexResultArray = triggerValueRegex.exec(triggerEnvVarArg);
const regexDomain = triggerRegexResultArray[1];
const regexProjectId = triggerRegexResultArray[2];
const regexProjectTriggerToken = triggerRegexResultArray[3];
const regexRefName = triggerRegexResultArray[4];
let regexTriggerName;
if (triggerRegexResultArray.length === 6) {
regexTriggerName = triggerRegexResultArray[5];
} else {
regexTriggerName = 'Unnamed Trigger';
}
logger.log('info', 'Found Trigger!');
logger.log('info', 'triggering build for ref ' + regexRefName + ' of ' + regexTriggerName);
plugins.smartrequest.postFormData(
'https://gitlab.com/api/v3/projects/' + regexProjectId + '/trigger/builds',
{},
[
{
name: 'token',
payload: regexProjectTriggerToken,
type: 'string',
},
{
name: 'ref',
payload: regexRefName,
type: 'string',
},
]
);
};

View File

@@ -1 +0,0 @@
export * from '../npmci.plugins.js';

View File

@@ -1,55 +0,0 @@
import * as plugins from './npmci.plugins.js';
import { CloudlyConnector } from './connector.cloudly/cloudlyconnector.js';
import { NpmciInfo } from './npmci.classes.npmciinfo.js';
import { NpmciCli } from './npmci.classes.npmcicli.js';
import { NpmciConfig } from './npmci.classes.npmciconfig.js';
// mods
import { NpmciDockerManager } from './manager.docker/index.js';
import { NpmciGitManager } from './manager.git/index.js';
import { NpmciNodeJsManager } from './manager.nodejs/index.js';
import { NpmciNpmManager } from './manager.npm/index.js';
import { NpmciEnv } from './npmci.classes.npmcienv.js';
export class Npmci {
public analytics: plugins.smartanalytics.Analytics;
public cloudlyConnector: CloudlyConnector;
public npmciEnv: NpmciEnv;
public npmciInfo: NpmciInfo;
public npmciConfig: NpmciConfig;
public npmciCli: NpmciCli;
// managers
public dockerManager: NpmciDockerManager;
public gitManager: NpmciGitManager;
public nodejsManager: NpmciNodeJsManager;
public npmManager: NpmciNpmManager;
constructor() {
this.analytics = new plugins.smartanalytics.Analytics({
apiEndPoint: 'https://pubapi.lossless.one/analytics',
projectId: 'gitzone',
appName: 'npmci',
});
this.cloudlyConnector = new CloudlyConnector(this);
this.npmciEnv = new NpmciEnv(this);
this.npmciInfo = new NpmciInfo(this);
this.npmciCli = new NpmciCli(this);
this.npmciConfig = new NpmciConfig(this);
// managers
this.dockerManager = new NpmciDockerManager(this);
this.gitManager = new NpmciGitManager(this);
this.nodejsManager = new NpmciNodeJsManager(this);
this.npmManager = new NpmciNpmManager(this);
}
public async start() {
await this.npmciInfo.printToConsole();
await this.npmciConfig.init();
this.npmciCli.startParse();
}
}

View File

@@ -1,110 +0,0 @@
import { logger } from './npmci.logging.js';
import * as plugins from './npmci.plugins.js';
import * as paths from './npmci.paths.js';
import { Npmci } from './npmci.classes.npmci.js';
export class NpmciCli {
public npmciRef: Npmci;
public smartcli: plugins.smartcli.Smartcli;
constructor(npmciArg: Npmci) {
this.npmciRef = npmciArg;
this.smartcli = new plugins.smartcli.Smartcli();
this.smartcli.addVersion(this.npmciRef.npmciInfo.projectInfo.version);
// clean
this.smartcli.addCommand('clean').subscribe(
async (argv) => {
const modClean = await import('./mod_clean/index.js');
await modClean.clean();
},
(err) => {
console.log(err);
process.exit(1);
}
);
// command
this.smartcli.addCommand('command').subscribe(
async (argv) => {
const modCommand = await import('./mod_command/index.js');
await modCommand.command();
},
(err) => {
console.log(err);
process.exit(1);
}
);
// command
this.smartcli.addCommand('git').subscribe(
async (argvArg) => {
await this.npmciRef.gitManager.handleCli(argvArg);
},
(err) => {
console.log(err);
process.exit(1);
}
);
// build
this.smartcli.addCommand('docker').subscribe(
async (argvArg) => {
await this.npmciRef.dockerManager.handleCli(argvArg);
},
(err) => {
console.log(err);
process.exit(1);
}
);
// node
this.smartcli.addCommand('node').subscribe(
async (argvArg) => {
await this.npmciRef.nodejsManager.handleCli(argvArg);
},
(err) => {
console.log(err);
process.exit(1);
}
);
// npm
this.smartcli.addCommand('npm').subscribe(
async (argvArg) => {
await this.npmciRef.npmManager.handleCli(argvArg);
},
(err) => {
console.log(err);
}
);
// trigger
this.smartcli.addCommand('ssh').subscribe(
async (argvArg) => {
const modSsh = await import('./mod_ssh/index.js');
await modSsh.handleCli(argvArg);
},
(err) => {
console.log(err);
process.exit(1);
}
);
// trigger
this.smartcli.addCommand('trigger').subscribe(
async (argv) => {
const modTrigger = await import('./mod_trigger/index.js');
await modTrigger.trigger();
},
(err) => {
console.log(err);
process.exit(1);
}
);
}
public startParse = () => {
this.smartcli.startParse();
};
}

View File

@@ -1,72 +0,0 @@
import * as plugins from './npmci.plugins.js';
import * as paths from './npmci.paths.js';
import { logger } from './npmci.logging.js';
import { Npmci } from './npmci.classes.npmci.js';
/**
* the main config interface for npmci
*/
export interface INpmciOptions {
projectInfo: plugins.projectinfo.ProjectInfo;
// npm
npmGlobalTools: string[];
npmAccessLevel?: 'private' | 'public';
npmRegistryUrl: string;
// docker
dockerRegistries: string[];
dockerRegistryRepoMap: { [key: string]: string };
dockerBuildargEnvMap: { [key: string]: string };
// urls
urlCloudly: string;
}
/**
* a config class for Npmci
*/
export class NpmciConfig {
public npmciRef: Npmci;
public npmciNpmextra: plugins.npmextra.Npmextra;
public kvStorage: plugins.npmextra.KeyValueStore;
public npmciQenv: plugins.qenv.Qenv;
private configObject: INpmciOptions;
constructor(npmciRefArg: Npmci) {
this.npmciRef = npmciRefArg;
this.npmciNpmextra = new plugins.npmextra.Npmextra(paths.cwd);
this.kvStorage = new plugins.npmextra.KeyValueStore(
'custom',
`${this.npmciRef.npmciEnv.repo.user}_${this.npmciRef.npmciEnv.repo.repo}`
);
this.npmciQenv = new plugins.qenv.Qenv(
paths.NpmciProjectDir,
paths.NpmciProjectNogitDir,
false
);
this.configObject = {
projectInfo: new plugins.projectinfo.ProjectInfo(paths.cwd),
npmGlobalTools: [],
dockerRegistries: [],
dockerRegistryRepoMap: {},
npmAccessLevel: 'private',
npmRegistryUrl: 'registry.npmjs.org',
dockerBuildargEnvMap: {},
urlCloudly: this.npmciQenv.getEnvVarOnDemand('NPMCI_URL_CLOUDLY'),
};
}
public async init() {
this.configObject = this.npmciNpmextra.dataFor<INpmciOptions>('npmci', this.configObject);
}
public getConfig(): INpmciOptions {
return this.configObject;
}
}

View File

@@ -1,18 +0,0 @@
import * as plugins from './npmci.plugins.js';
import { Npmci } from './npmci.classes.npmci.js';
export class NpmciEnv {
public npmciRef: Npmci;
public repoString: string;
public repo: plugins.smartstring.GitRepo;
constructor(npmciRefArg: Npmci) {
this.npmciRef = npmciRefArg;
this.repoString = process.env.CI_REPOSITORY_URL;
if (!this.repoString) {
this.repoString = 'https://undefined:undefined@github.com/undefined/undefined.git';
}
this.repo = new plugins.smartstring.GitRepo(this.repoString);
}
}

View File

@@ -1,17 +0,0 @@
import * as plugins from './npmci.plugins.js';
import * as paths from './npmci.paths.js';
import { logger } from './npmci.logging.js';
import { Npmci } from './npmci.classes.npmci.js';
export class NpmciInfo {
public npmciRef: Npmci;
public projectInfo = new plugins.projectinfo.ProjectinfoNpm(paths.NpmciPackageRoot);
constructor(npmciArg: Npmci) {
this.npmciRef = npmciArg;
}
public printToConsole() {
logger.log('info', `npmci version: ${this.projectInfo.version}`);
}
}

View File

@@ -1,16 +0,0 @@
import * as plugins from './npmci.plugins.js';
export const cwd = process.cwd();
// package paths
export const NpmciPackageRoot = plugins.path.join(
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
'../'
);
export const NpmciPackageConfig = plugins.path.join(NpmciPackageRoot, './config.json');
// project paths
export const NpmciProjectDir = cwd;
export const NpmciProjectNogitDir = plugins.path.join(NpmciProjectDir, './.nogit');
export const NpmciTestDir = plugins.path.join(cwd, './test');
export const NpmciCacheDir = plugins.path.join(cwd, './.npmci_cache');

View File

@@ -1,65 +0,0 @@
// node native
import * as path from 'path';
export { path };
// @apiglobal
import * as typedrequest from '@apiglobal/typedrequest';
export { typedrequest };
// @servezone
import * as servezoneInterfaces from '@servezone/interfaces';
export { servezoneInterfaces };
// @pushrocks
import * as npmextra from '@pushrocks/npmextra';
import * as projectinfo from '@pushrocks/projectinfo';
import * as qenv from '@pushrocks/qenv';
import * as smartanalytics from '@pushrocks/smartanalytics';
import * as smartdelay from '@pushrocks/smartdelay';
import * as smartfile from '@pushrocks/smartfile';
import * as smartcli from '@pushrocks/smartcli';
import * as smartgit from '@pushrocks/smartgit';
import * as smartlog from '@pushrocks/smartlog';
import * as smartlogDestinationLocal from '@pushrocks/smartlog-destination-local';
import * as smartparam from '@pushrocks/smartparam';
import * as smartpath from '@pushrocks/smartpath';
import * as smartpromise from '@pushrocks/smartpromise';
import * as smartrequest from '@pushrocks/smartrequest';
import * as smartshell from '@pushrocks/smartshell';
import * as smartsocket from '@pushrocks/smartsocket';
import * as smartssh from '@pushrocks/smartssh';
import * as smartstring from '@pushrocks/smartstring';
export {
npmextra,
projectinfo,
qenv,
smartanalytics,
smartdelay,
smartfile,
smartgit,
smartcli,
smartlog,
smartlogDestinationLocal,
smartparam,
smartpath,
smartpromise,
smartrequest,
smartshell,
smartsocket,
smartssh,
smartstring,
};
// @tsclass scope
import * as tsclass from '@tsclass/tsclass';
export { tsclass };
// third party
import * as through2 from 'through2';
export { through2 };

View File

@@ -1,17 +1,15 @@
import { logger } from './npmci.logging.js'; import { logger } from './szci.logging.ts';
import * as plugins from './npmci.plugins.js'; import * as plugins from './szci.plugins.ts';
import * as paths from './npmci.paths.js'; import * as paths from './szci.paths.ts';
import * as smartpromise from '@pushrocks/smartpromise';
/** /**
* wether nvm is available or not * wether nvm is available or not
*/ */
export let nvmAvailable = smartpromise.defer<boolean>(); export let nvmAvailable = plugins.smartpromise.defer<boolean>();
/** /**
* the smartshell instance for npmci * the smartshell instance for szci
*/ */
const npmciSmartshell = new plugins.smartshell.Smartshell({ const szciSmartshell = new plugins.smartshell.Smartshell({
executor: 'bash', executor: 'bash',
sourceFilePaths: [], sourceFilePaths: [],
}); });
@@ -21,16 +19,16 @@ const npmciSmartshell = new plugins.smartshell.Smartshell({
*/ */
const checkToolsAvailable = async () => { const checkToolsAvailable = async () => {
// check for nvm // check for nvm
if (!process.env.NPMTS_TEST) { if (!Deno.env.get('SZCI_TEST')) {
if ( if (
(await npmciSmartshell.execSilent(`bash -c "source /usr/local/nvm/nvm.sh"`)).exitCode === 0 (await szciSmartshell.execSilent(`bash -c "source /usr/local/nvm/nvm.sh"`)).exitCode === 0
) { ) {
npmciSmartshell.shellEnv.addSourceFiles([`/usr/local/nvm/nvm.sh`]); szciSmartshell.shellEnv.addSourceFiles([`/usr/local/nvm/nvm.sh`]);
nvmAvailable.resolve(true); nvmAvailable.resolve(true);
} else if ( } else if (
(await npmciSmartshell.execSilent(`bash -c "source ~/.nvm/nvm.sh"`)).exitCode === 0 (await szciSmartshell.execSilent(`bash -c "source ~/.nvm/nvm.sh"`)).exitCode === 0
) { ) {
npmciSmartshell.shellEnv.addSourceFiles([`~/.nvm/nvm.sh`]); szciSmartshell.shellEnv.addSourceFiles([`~/.nvm/nvm.sh`]);
nvmAvailable.resolve(true); nvmAvailable.resolve(true);
} else { } else {
nvmAvailable.resolve(false); nvmAvailable.resolve(false);
@@ -48,7 +46,7 @@ checkToolsAvailable();
*/ */
export let bash = async (commandArg: string, retryArg: number = 2): Promise<string> => { export let bash = async (commandArg: string, retryArg: number = 2): Promise<string> => {
await nvmAvailable.promise; // make sure nvm check has run await nvmAvailable.promise; // make sure nvm check has run
let execResult: plugins.smartshell.IExecResult; let execResult!: plugins.smartshell.IExecResult;
// determine if we fail // determine if we fail
let failOnError: boolean = true; let failOnError: boolean = true;
@@ -57,20 +55,20 @@ export let bash = async (commandArg: string, retryArg: number = 2): Promise<stri
retryArg = 0; retryArg = 0;
} }
if (!process.env.NPMTS_TEST) { if (!Deno.env.get('SZCI_TEST')) {
// NPMTS_TEST is used during testing // SZCI_TEST is used during testing
for (let i = 0; i <= retryArg; i++) { for (let i = 0; i <= retryArg; i++) {
if (process.env.DEBUG_NPMCI === 'true') { if (Deno.env.get('DEBUG_SZCI') === 'true') {
console.log(commandArg); console.log(commandArg);
} }
execResult = await npmciSmartshell.exec(commandArg); execResult = await szciSmartshell.exec(commandArg);
// determine how bash reacts to error and success // determine how bash reacts to error and success
if (execResult.exitCode !== 0 && i === retryArg) { if (execResult.exitCode !== 0 && i === retryArg) {
// something went wrong and retries are exhausted // something went wrong and retries are exhausted
if (failOnError) { if (failOnError) {
logger.log('error', 'something went wrong and retries are exhausted'); logger.log('error', 'something went wrong and retries are exhausted');
process.exit(1); Deno.exit(1);
} }
} else if (execResult.exitCode === 0) { } else if (execResult.exitCode === 0) {
// everything went fine, or no error wanted // everything went fine, or no error wanted

58
ts/szci.classes.szci.ts Normal file
View File

@@ -0,0 +1,58 @@
import * as plugins from './szci.plugins.ts';
// env
import { SzciEnv } from './szci.classes.szcienv.ts';
import { SzciInfo } from './szci.classes.szciinfo.ts';
import { SzciCli } from './szci.classes.szcicli.ts';
import { SzciConfig } from './szci.classes.szciconfig.ts';
// connectors
import { CloudlyConnector } from './connector.cloudly/cloudlyconnector.ts';
// managers
import { SzciDockerManager } from './manager.docker/index.ts';
import { SzciGitManager } from './manager.git/index.ts';
import { SzciNodeJsManager } from './manager.nodejs/index.ts';
import { SzciNpmManager } from './manager.npm/index.ts';
export class Szci {
public analytics: plugins.smartanalytics.Analytics;
public cloudlyConnector!: CloudlyConnector;
public szciEnv!: SzciEnv;
public szciInfo!: SzciInfo;
public szciConfig!: SzciConfig;
public szciCli!: SzciCli;
// managers
public dockerManager!: SzciDockerManager;
public gitManager!: SzciGitManager;
public nodejsManager!: SzciNodeJsManager;
public npmManager!: SzciNpmManager;
constructor() {
this.analytics = new plugins.smartanalytics.Analytics({
apiEndPoint: 'https://pubapi.lossless.one/analytics',
projectId: 'gitzone',
appName: 'szci',
});
}
public async start() {
this.cloudlyConnector = new CloudlyConnector(this);
this.szciEnv = new SzciEnv(this);
this.szciInfo = new SzciInfo(this);
await this.szciInfo.printToConsole();
this.szciCli = new SzciCli(this);
this.szciConfig = new SzciConfig(this);
await this.szciConfig.init();
// managers
this.dockerManager = new SzciDockerManager(this);
this.gitManager = new SzciGitManager(this);
this.nodejsManager = new SzciNodeJsManager(this);
this.npmManager = new SzciNpmManager(this);
this.szciCli.startParse();
}
}

View File

@@ -0,0 +1,68 @@
import { logger } from './szci.logging.ts';
import * as plugins from './szci.plugins.ts';
import * as paths from './szci.paths.ts';
import { Szci } from './szci.classes.szci.ts';
export class SzciCli {
public szciRef: Szci;
public smartcli: plugins.smartcli.Smartcli;
constructor(szciArg: Szci) {
this.szciRef = szciArg;
this.smartcli = new plugins.smartcli.Smartcli();
this.smartcli.addVersion(this.szciRef.szciInfo.version);
// docker
this.smartcli.addCommand('docker').subscribe(
async (argvArg) => {
await this.szciRef.dockerManager.handleCli(argvArg);
},
(err) => {
console.log(err);
Deno.exit(1);
}
);
// git
this.smartcli.addCommand('git').subscribe(
async (argvArg) => {
await this.szciRef.gitManager.handleCli(argvArg);
},
(err) => {
console.log(err);
Deno.exit(1);
}
);
// node
this.smartcli.addCommand('node').subscribe(
async (argvArg) => {
await this.szciRef.nodejsManager.handleCli(argvArg);
},
(err) => {
console.log(err);
Deno.exit(1);
}
);
// npm
this.smartcli.addCommand('npm').subscribe(
async (argvArg) => {
await this.szciRef.npmManager.handleCli(argvArg);
},
(err) => {
console.log(err);
}
);
// ssh
this.smartcli.addCommand('ssh').subscribe(async (argvArg) => {
const modSsh = await import('./mod_ssh/index.ts');
await modSsh.handleCli(argvArg);
});
}
public startParse = () => {
this.smartcli.startParse();
};
}

View File

@@ -0,0 +1,63 @@
import * as plugins from './szci.plugins.ts';
import * as paths from './szci.paths.ts';
import { logger } from './szci.logging.ts';
import { Szci } from './szci.classes.szci.ts';
/**
* the main config interface for szci
*/
export interface ISzciOptions {
projectInfo: plugins.projectinfo.ProjectInfo;
// npm
npmGlobalTools: string[];
npmAccessLevel?: 'private' | 'public';
npmRegistryUrl: string;
// urls
urlCloudly?: string;
}
/**
* a config class for Szci
*/
export class SzciConfig {
public szciRef: Szci;
public szciNpmextra!: plugins.npmextra.Npmextra;
public kvStorage!: plugins.npmextra.KeyValueStore;
public szciQenv!: plugins.qenv.Qenv;
private configObject!: ISzciOptions;
constructor(szciRefArg: Szci) {
this.szciRef = szciRefArg;
}
public async init() {
this.szciNpmextra = new plugins.npmextra.Npmextra(paths.cwd);
this.kvStorage = new plugins.npmextra.KeyValueStore({
typeArg: 'userHomeDir',
identityArg: `.szci_${this.szciRef.szciEnv.repo.user}_${this.szciRef.szciEnv.repo.repo}`,
});
this.szciQenv = new plugins.qenv.Qenv(
paths.SzciProjectDir,
paths.SzciProjectNogitDir,
false
);
this.configObject = {
projectInfo: new plugins.projectinfo.ProjectInfo(paths.cwd),
npmGlobalTools: [],
npmAccessLevel: 'private',
npmRegistryUrl: 'registry.npmjs.org',
urlCloudly: await this.szciQenv.getEnvVarOnDemand('SZCI_URL_CLOUDLY'),
};
this.configObject = this.szciNpmextra.dataFor<ISzciOptions>('@ship.zone/szci', this.configObject);
}
public getConfig(): ISzciOptions {
return this.configObject;
}
}

View File

@@ -0,0 +1,25 @@
import * as plugins from './szci.plugins.ts';
import { Szci } from './szci.classes.szci.ts';
export class SzciEnv {
public szciRef: Szci;
public repoString: string;
public repo: plugins.smartstring.GitRepo;
constructor(szciRefArg: Szci) {
this.szciRef = szciRefArg;
// Determine repo string from environment
let repoUrl: string | undefined;
if (Deno.env.get("GITLAB_CI")) {
repoUrl = Deno.env.get("CI_REPOSITORY_URL");
}
if (!repoUrl && Deno.env.get("SZCI_COMPUTED_REPOURL")) {
repoUrl = Deno.env.get("SZCI_COMPUTED_REPOURL");
}
this.repoString = repoUrl || 'https://undefined:undefined@github.com/undefined/undefined.git';
this.repo = new plugins.smartstring.GitRepo(this.repoString);
}
}

View File

@@ -0,0 +1,18 @@
import * as plugins from './szci.plugins.ts';
import * as paths from './szci.paths.ts';
import { logger } from './szci.logging.ts';
import { Szci } from './szci.classes.szci.ts';
import denoConfig from '../deno.json' with { type: 'json' };
export class SzciInfo {
public szciRef: Szci;
public version = denoConfig.version;
constructor(szciArg: Szci) {
this.szciRef = szciArg;
}
public async printToConsole() {
await logger.log('info', `szci version: ${this.version}`);
}
}

View File

@@ -1,4 +1,4 @@
import * as plugins from './npmci.plugins.js'; import * as plugins from './szci.plugins.ts';
export const logger = new plugins.smartlog.Smartlog({ export const logger = new plugins.smartlog.Smartlog({
logContext: { logContext: {
@@ -6,7 +6,7 @@ export const logger = new plugins.smartlog.Smartlog({
companyunit: 'Some Unit', companyunit: 'Some Unit',
containerName: 'Some ContainerName', containerName: 'Some ContainerName',
environment: 'test', environment: 'test',
runtime: 'node', runtime: 'deno',
zone: 'Some Zone', zone: 'Some Zone',
}, },
}); });

31
ts/szci.paths.ts Normal file
View File

@@ -0,0 +1,31 @@
import * as plugins from './szci.plugins.ts';
/**
* Get current working directory (evaluated at call time, not module load time)
*/
export const getCwd = (): string => Deno.cwd();
/**
* Current working directory - use getCwd() if you need the live value after chdir
* @deprecated Use getCwd() for dynamic cwd resolution
*/
export const cwd = Deno.cwd();
// package paths
export const SzciPackageRoot = plugins.path.join(
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
'../'
);
export const SzciPackageConfig = plugins.path.join(SzciPackageRoot, './config.json');
// project paths - use functions for dynamic resolution
export const getSzciProjectDir = (): string => getCwd();
export const getSzciProjectNogitDir = (): string => plugins.path.join(getCwd(), './.nogit');
export const getSzciTestDir = (): string => plugins.path.join(getCwd(), './test');
export const getSzciCacheDir = (): string => plugins.path.join(getCwd(), './.szci_cache');
// Static paths (for backwards compatibility - captured at module load)
export const SzciProjectDir = cwd;
export const SzciProjectNogitDir = plugins.path.join(SzciProjectDir, './.nogit');
export const SzciTestDir = plugins.path.join(cwd, './test');
export const SzciCacheDir = plugins.path.join(cwd, './.szci_cache');

60
ts/szci.plugins.ts Normal file
View File

@@ -0,0 +1,60 @@
// Deno std libraries
import * as path from '@std/path';
export { path };
// @apiglobal
import * as typedrequest from '@api.global/typedrequest';
export { typedrequest };
// @servezone
import * as servezoneApi from '@serve.zone/api';
export { servezoneApi };
// @push.rocks
import * as npmextra from '@push.rocks/npmextra';
import * as projectinfo from '@push.rocks/projectinfo';
import * as qenv from '@push.rocks/qenv';
import * as smartanalytics from '@push.rocks/smartanalytics';
import * as smartfile from '@push.rocks/smartfile';
import * as smartcli from '@push.rocks/smartcli';
import * as smartgit from '@push.rocks/smartgit';
import * as smartlog from '@push.rocks/smartlog';
import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-local';
import * as smartobject from '@push.rocks/smartobject';
import * as smartpath from '@push.rocks/smartpath';
import * as smartpromise from '@push.rocks/smartpromise';
import * as smartrequest from '@push.rocks/smartrequest';
import * as smartshell from '@push.rocks/smartshell';
import * as smartsocket from '@push.rocks/smartsocket';
import * as smartssh from '@push.rocks/smartssh';
import * as smartstring from '@push.rocks/smartstring';
export {
npmextra,
projectinfo,
qenv,
smartanalytics,
smartfile,
smartgit,
smartcli,
smartlog,
smartlogDestinationLocal,
smartobject,
smartpath,
smartpromise,
smartrequest,
smartshell,
smartsocket,
smartssh,
smartstring,
};
// @tsclass scope
import * as tsclass from '@tsclass/tsclass';
export { tsclass };

View File

@@ -1,10 +0,0 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"useDefineForClassFields": false,
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "nodenext",
"esModuleInterop": true
}
}

64
uninstall.sh Executable file
View File

@@ -0,0 +1,64 @@
#!/bin/bash
# SZCI Uninstaller Script
# Removes SZCI from the system (installed via install.sh)
#
# Usage:
# sudo bash uninstall.sh
set -e
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo "Please run as root (sudo bash uninstall.sh)"
exit 1
fi
echo "================================================"
echo " SZCI Uninstaller"
echo " Serve Zone CI/CD Tool Removal"
echo "================================================"
echo ""
# Default installation directory
INSTALL_DIR="/opt/szci"
# Step 1: Remove global symlinks
echo "Checking for global symlinks..."
if [ -L "/usr/local/bin/szci" ]; then
echo "Removing symlink: /usr/local/bin/szci"
rm -f /usr/local/bin/szci
fi
if [ -L "/usr/bin/szci" ]; then
echo "Removing symlink: /usr/bin/szci"
rm -f /usr/bin/szci
fi
# Step 2: Remove installation directory
if [ -d "$INSTALL_DIR" ]; then
echo "Removing installation directory: $INSTALL_DIR"
rm -rf "$INSTALL_DIR"
else
echo "Installation directory not found: $INSTALL_DIR"
fi
echo ""
echo "================================================"
echo " SZCI Uninstallation Complete!"
echo "================================================"
echo ""
# Check for npm global installation
NODE_PATH=$(which node 2>/dev/null || true)
if [ -n "$NODE_PATH" ]; then
NPM_PATH=$(dirname "$NODE_PATH")/npm
if [ -x "$NPM_PATH" ]; then
echo "If you installed SZCI via npm, you may want to uninstall it with:"
echo " npm uninstall -g @ship.zone/szci"
echo ""
fi
fi
echo "SZCI has been removed from your system."
echo ""