Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c89cc6a0a | |||
| bad663d5e4 | |||
| 4e1d211a14 | |||
| bd3b1ba9dd | |||
| 5d419e40c1 | |||
| 9d295f2633 | |||
| 5d18e53e30 | |||
| 9ad5222b95 | |||
| dba2e2ae68 | |||
| 6c8095260d | |||
| 95c4ae3b16 |
31
.gitea/release-template.md
Normal file
31
.gitea/release-template.md
Normal 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
122
.gitea/workflows/README.md
Normal 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 `main` 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 main
|
||||||
|
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
82
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
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
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
name: Default (not tags)
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags-ignore:
|
|
||||||
- '**'
|
|
||||||
|
|
||||||
env:
|
|
||||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
|
||||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
|
||||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
|
||||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
|
||||||
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
security:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
continue-on-error: true
|
|
||||||
container:
|
|
||||||
image: ${{ env.IMAGE }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Install pnpm and npmci
|
|
||||||
run: |
|
|
||||||
pnpm install -g pnpm
|
|
||||||
pnpm install -g @shipzone/npmci
|
|
||||||
|
|
||||||
- name: Run npm prepare
|
|
||||||
run: npmci npm prepare
|
|
||||||
|
|
||||||
- name: Audit production dependencies
|
|
||||||
run: |
|
|
||||||
npmci command npm config set registry https://registry.npmjs.org
|
|
||||||
npmci command pnpm audit --audit-level=high --prod
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
- name: Audit development dependencies
|
|
||||||
run: |
|
|
||||||
npmci command npm config set registry https://registry.npmjs.org
|
|
||||||
npmci command pnpm audit --audit-level=high --dev
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
test:
|
|
||||||
if: ${{ always() }}
|
|
||||||
needs: security
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: ${{ env.IMAGE }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Test stable
|
|
||||||
run: |
|
|
||||||
npmci node install stable
|
|
||||||
npmci npm install
|
|
||||||
npmci npm test
|
|
||||||
|
|
||||||
- name: Test build
|
|
||||||
run: |
|
|
||||||
npmci node install stable
|
|
||||||
npmci npm install
|
|
||||||
npmci npm build
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
name: Default (tags)
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
env:
|
|
||||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
|
||||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
|
||||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
|
||||||
NPMCI_LOGIN_DOCKER_GITEA: ${{secrets.NPMCI_DOCKER_REGISTRYURL_DEFAULT}}|${{ gitea.repository_owner }}|${{ secrets.GITEA_TOKEN }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
security:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
continue-on-error: true
|
|
||||||
container:
|
|
||||||
image: ${{ env.IMAGE }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Install pnpm and npmci
|
|
||||||
run: |
|
|
||||||
pnpm install -g pnpm
|
|
||||||
pnpm install -g @shipzone/npmci
|
|
||||||
|
|
||||||
- name: Run npm prepare
|
|
||||||
run: npmci npm prepare
|
|
||||||
|
|
||||||
- name: Audit production dependencies
|
|
||||||
run: |
|
|
||||||
npmci command npm config set registry https://registry.npmjs.org
|
|
||||||
npmci command pnpm audit --audit-level=high --prod
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
- name: Audit development dependencies
|
|
||||||
run: |
|
|
||||||
npmci command npm config set registry https://registry.npmjs.org
|
|
||||||
npmci command pnpm audit --audit-level=high --dev
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
test:
|
|
||||||
if: ${{ always() }}
|
|
||||||
needs: security
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: ${{ env.IMAGE }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Test stable
|
|
||||||
run: |
|
|
||||||
npmci node install stable
|
|
||||||
npmci npm install
|
|
||||||
npmci npm test
|
|
||||||
|
|
||||||
- name: Test build
|
|
||||||
run: |
|
|
||||||
npmci node install stable
|
|
||||||
npmci npm install
|
|
||||||
npmci npm build
|
|
||||||
|
|
||||||
release:
|
|
||||||
needs: test
|
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: ${{ env.IMAGE }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
run: |
|
|
||||||
npmci node install stable
|
|
||||||
npmci npm publish
|
|
||||||
|
|
||||||
metadata:
|
|
||||||
needs: test
|
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: ${{ env.IMAGE }}
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Code quality
|
|
||||||
run: |
|
|
||||||
npmci command npm install -g typescript
|
|
||||||
npmci npm prepare
|
|
||||||
npmci npm install
|
|
||||||
|
|
||||||
- name: Trigger
|
|
||||||
run: npmci trigger
|
|
||||||
|
|
||||||
- name: Build docs and upload artifacts
|
|
||||||
run: |
|
|
||||||
npmci node install stable
|
|
||||||
npmci npm install
|
|
||||||
pnpm install -g @git.zone/tsdoc
|
|
||||||
npmci command tsdoc
|
|
||||||
continue-on-error: true
|
|
||||||
129
.gitea/workflows/npm-publish.yml
Normal file
129
.gitea/workflows/npm-publish.yml
Normal 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 ""
|
||||||
248
.gitea/workflows/release.yml
Normal file
248
.gitea/workflows/release.yml
Normal 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/main/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/main/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/main/install.sh | sudo bash"
|
||||||
|
echo ""
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,17 +1,10 @@
|
|||||||
.nogit/
|
.nogit/
|
||||||
|
|
||||||
# artifacts
|
|
||||||
coverage/
|
|
||||||
public/
|
|
||||||
pages/
|
|
||||||
|
|
||||||
# installs
|
# installs
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
# caches
|
# caches
|
||||||
.yarn/
|
|
||||||
.cache/
|
.cache/
|
||||||
.rpt2_cache
|
|
||||||
|
|
||||||
# builds
|
# builds
|
||||||
dist/
|
dist/
|
||||||
|
|||||||
1
.serena/.gitignore
vendored
1
.serena/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
/cache
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby)
|
|
||||||
# * For C, use cpp
|
|
||||||
# * For JavaScript, use typescript
|
|
||||||
# Special requirements:
|
|
||||||
# * csharp: Requires the presence of a .sln file in the project folder.
|
|
||||||
language: typescript
|
|
||||||
|
|
||||||
# the encoding used by text files in the project
|
|
||||||
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
|
|
||||||
encoding: "utf-8"
|
|
||||||
|
|
||||||
# whether to use the project's gitignore file to ignore files
|
|
||||||
# Added on 2025-04-07
|
|
||||||
ignore_all_files_in_gitignore: true
|
|
||||||
# list of additional paths to ignore
|
|
||||||
# same syntax as gitignore, so you can use * and **
|
|
||||||
# Was previously called `ignored_dirs`, please update your config if you are using that.
|
|
||||||
# Added (renamed) on 2025-04-07
|
|
||||||
ignored_paths: []
|
|
||||||
|
|
||||||
# whether the project is in read-only mode
|
|
||||||
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
|
||||||
# Added on 2025-04-18
|
|
||||||
read_only: false
|
|
||||||
|
|
||||||
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
|
|
||||||
# Below is the complete list of tools for convenience.
|
|
||||||
# To make sure you have the latest list of tools, and to view their descriptions,
|
|
||||||
# execute `uv run scripts/print_tool_overview.py`.
|
|
||||||
#
|
|
||||||
# * `activate_project`: Activates a project by name.
|
|
||||||
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
|
||||||
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
|
||||||
# * `delete_lines`: Deletes a range of lines within a file.
|
|
||||||
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
|
|
||||||
# * `execute_shell_command`: Executes a shell command.
|
|
||||||
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
|
|
||||||
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
|
|
||||||
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
|
|
||||||
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
|
||||||
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
|
||||||
# * `initial_instructions`: Gets the initial instructions for the current project.
|
|
||||||
# Should only be used in settings where the system prompt cannot be set,
|
|
||||||
# e.g. in clients you have no control over, like Claude Desktop.
|
|
||||||
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
|
||||||
# * `insert_at_line`: Inserts content at a given line in a file.
|
|
||||||
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
|
||||||
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
|
||||||
# * `list_memories`: Lists memories in Serena's project-specific memory store.
|
|
||||||
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
|
||||||
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
|
|
||||||
# * `read_file`: Reads a file within the project directory.
|
|
||||||
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
|
|
||||||
# * `remove_project`: Removes a project from the Serena configuration.
|
|
||||||
# * `replace_lines`: Replaces a range of lines within a file with new content.
|
|
||||||
# * `replace_symbol_body`: Replaces the full definition of a symbol.
|
|
||||||
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
|
|
||||||
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
|
||||||
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
|
|
||||||
# * `switch_modes`: Activates modes by providing a list of their names
|
|
||||||
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
|
|
||||||
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
|
|
||||||
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
|
|
||||||
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
|
|
||||||
excluded_tools: []
|
|
||||||
|
|
||||||
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
|
||||||
# (contrary to the memories, which are loaded on demand).
|
|
||||||
initial_prompt: ""
|
|
||||||
|
|
||||||
project_name: "npmci"
|
|
||||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -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",
|
||||||
|
|||||||
39
changelog.md
39
changelog.md
@@ -1,5 +1,44 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-02-07 - 7.1.1 - fix(workflows)
|
||||||
|
normalize default branch to 'main' across CI/release workflows and docs; update package metadata; clean .gitignore and fix readme license link
|
||||||
|
|
||||||
|
- Updated workflow triggers and documentation from 'master' to 'main' (.gitea/workflows/ci.yml, .gitea/workflows/release.yml, .gitea/workflows/README.md) including install script URLs referenced in release instructions.
|
||||||
|
- Updated npmextra.json @git.zone/cli metadata: changed projectType from 'npm' to 'deno' and githost from 'gitlab.com' to 'code.foss.global'.
|
||||||
|
- Cleaned .gitignore by removing outdated entries (coverage/, public/, pages/, .yarn/, .rpt2_cache) and minor whitespace fixes.
|
||||||
|
- Normalized license filename link in readme.md from 'LICENSE' to lowercase 'license'.
|
||||||
|
|
||||||
|
## 2026-02-07 - 7.1.0 - feat(installer)
|
||||||
|
switch installer/docs to use main branch; add automated installer instructions and update CI examples to use the installer; add release registries and access level in npmextra.json
|
||||||
|
|
||||||
|
- Replaced master branch URLs with main in install.sh and README examples
|
||||||
|
- Added "Automated Installer (Recommended)" section to README with curl install examples (including versioned install)
|
||||||
|
- Replaced npm install -g @ship.zone/szci in CI examples with the curl-based installer invocation (some uses sudo)
|
||||||
|
- Updated npmextra.json: removed npmAccessLevel for @ship.zone/szci and added release.registries and release.accessLevel entries
|
||||||
|
|
||||||
|
## 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)
|
## 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
|
Rename project from npmci to szci and migrate runtime to Deno; add compiled binaries, installer and wrapper; update imports, env handling and package metadata
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ship.zone/szci",
|
"name": "@ship.zone/szci",
|
||||||
"version": "6.0.0",
|
"version": "7.1.1",
|
||||||
"exports": "./mod.ts",
|
"exports": "./mod.ts",
|
||||||
"nodeModulesDir": "auto",
|
"nodeModulesDir": "auto",
|
||||||
"tasks": {
|
"tasks": {
|
||||||
@@ -39,14 +39,11 @@
|
|||||||
"@std/cli": "jsr:@std/cli@^1.0.0",
|
"@std/cli": "jsr:@std/cli@^1.0.0",
|
||||||
"@std/assert": "jsr:@std/assert@^1.0.0",
|
"@std/assert": "jsr:@std/assert@^1.0.0",
|
||||||
"@api.global/typedrequest": "npm:@api.global/typedrequest@^3.1.10",
|
"@api.global/typedrequest": "npm:@api.global/typedrequest@^3.1.10",
|
||||||
"@push.rocks/lik": "npm:@push.rocks/lik@^6.1.0",
|
|
||||||
"@push.rocks/npmextra": "npm:@push.rocks/npmextra@^5.1.2",
|
"@push.rocks/npmextra": "npm:@push.rocks/npmextra@^5.1.2",
|
||||||
"@push.rocks/projectinfo": "npm:@push.rocks/projectinfo@^5.0.2",
|
"@push.rocks/projectinfo": "npm:@push.rocks/projectinfo@^5.0.2",
|
||||||
"@push.rocks/qenv": "npm:@push.rocks/qenv@^6.0.2",
|
"@push.rocks/qenv": "npm:@push.rocks/qenv@^6.0.2",
|
||||||
"@push.rocks/smartanalytics": "npm:@push.rocks/smartanalytics@^2.0.15",
|
"@push.rocks/smartanalytics": "npm:@push.rocks/smartanalytics@^2.0.15",
|
||||||
"@push.rocks/smartcli": "npm:@push.rocks/smartcli@^4.0.11",
|
"@push.rocks/smartcli": "npm:@push.rocks/smartcli@^4.0.11",
|
||||||
"@push.rocks/smartdelay": "npm:@push.rocks/smartdelay@^3.0.5",
|
|
||||||
"@push.rocks/smartenv": "npm:@push.rocks/smartenv@^5.0.5",
|
|
||||||
"@push.rocks/smartfile": "npm:@push.rocks/smartfile@^11.0.21",
|
"@push.rocks/smartfile": "npm:@push.rocks/smartfile@^11.0.21",
|
||||||
"@push.rocks/smartgit": "npm:@push.rocks/smartgit@^3.1.1",
|
"@push.rocks/smartgit": "npm:@push.rocks/smartgit@^3.1.1",
|
||||||
"@push.rocks/smartlog": "npm:@push.rocks/smartlog@^3.0.7",
|
"@push.rocks/smartlog": "npm:@push.rocks/smartlog@^3.0.7",
|
||||||
@@ -61,7 +58,6 @@
|
|||||||
"@push.rocks/smartstring": "npm:@push.rocks/smartstring@^4.0.8",
|
"@push.rocks/smartstring": "npm:@push.rocks/smartstring@^4.0.8",
|
||||||
"@push.rocks/smartexpect": "npm:@push.rocks/smartexpect@^1.0.15",
|
"@push.rocks/smartexpect": "npm:@push.rocks/smartexpect@^1.0.15",
|
||||||
"@serve.zone/api": "npm:@serve.zone/api@^4.3.11",
|
"@serve.zone/api": "npm:@serve.zone/api@^4.3.11",
|
||||||
"@tsclass/tsclass": "npm:@tsclass/tsclass@^4.1.2",
|
"@tsclass/tsclass": "npm:@tsclass/tsclass@^4.1.2"
|
||||||
"through2": "npm:through2@^4.0.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# Direct piped installation (recommended):
|
# Direct piped installation (recommended):
|
||||||
# curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/master/install.sh | sudo bash
|
# curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/main/install.sh | sudo bash
|
||||||
#
|
#
|
||||||
# With version specification:
|
# With version specification:
|
||||||
# curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/master/install.sh | sudo bash -s -- --version v5.0.0
|
# curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/main/install.sh | sudo bash -s -- --version v5.0.0
|
||||||
#
|
#
|
||||||
# Options:
|
# Options:
|
||||||
# -h, --help Show this help message
|
# -h, --help Show this help message
|
||||||
@@ -60,10 +60,10 @@ if [ $SHOW_HELP -eq 1 ]; then
|
|||||||
echo ""
|
echo ""
|
||||||
echo "Examples:"
|
echo "Examples:"
|
||||||
echo " # Install latest version"
|
echo " # Install latest version"
|
||||||
echo " curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/master/install.sh | sudo bash"
|
echo " curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/main/install.sh | sudo bash"
|
||||||
echo ""
|
echo ""
|
||||||
echo " # Install specific version"
|
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"
|
echo " curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/main/install.sh | sudo bash -s -- --version v5.0.0"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
1
mod.ts
1
mod.ts
@@ -45,4 +45,3 @@ if (import.meta.main) {
|
|||||||
|
|
||||||
// Export for programmatic use
|
// Export for programmatic use
|
||||||
export { Szci } from './ts/szci.classes.szci.ts';
|
export { Szci } from './ts/szci.classes.szci.ts';
|
||||||
export { Dockerfile } from './ts/manager.docker/mod.classes.dockerfile.ts';
|
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
{
|
{
|
||||||
"npmci": {
|
"@ship.zone/szci": {
|
||||||
"npmGlobalTools": [],
|
"npmGlobalTools": [],
|
||||||
"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": "deno",
|
||||||
"module": {
|
"module": {
|
||||||
"githost": "gitlab.com",
|
"githost": "code.foss.global",
|
||||||
"gitscope": "ship.zone",
|
"gitscope": "ship.zone",
|
||||||
"gitrepo": "npmci",
|
"gitrepo": "szci",
|
||||||
"description": "A tool to streamline Node.js and Docker workflows within CI environments, particularly GitLab CI, providing various CI/CD utilities.",
|
"description": "A tool to streamline Node.js and Docker workflows within CI environments, particularly GitLab CI, providing various CI/CD utilities.",
|
||||||
"npmPackagename": "@ship.zone/npmci",
|
"npmPackagename": "@ship.zone/szci",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"Node.js",
|
"Node.js",
|
||||||
@@ -34,6 +33,13 @@
|
|||||||
"continuous integration",
|
"continuous integration",
|
||||||
"continuous deployment"
|
"continuous deployment"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"registries": [
|
||||||
|
"https://verdaccio.lossless.digital",
|
||||||
|
"https://registry.npmjs.org"
|
||||||
|
],
|
||||||
|
"accessLevel": "public"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ship.zone/szci",
|
"name": "@ship.zone/szci",
|
||||||
"version": "6.0.0",
|
"version": "7.1.1",
|
||||||
"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": "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.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"Node.js",
|
"Node.js",
|
||||||
|
|||||||
9733
pnpm-lock.yaml
generated
9733
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
|||||||
onlyBuiltDependencies:
|
|
||||||
- esbuild
|
|
||||||
- mongodb-memory-server
|
|
||||||
- puppeteer
|
|
||||||
@@ -1,2 +1,55 @@
|
|||||||
- focus on cli usage in CI environments.
|
- Focus on CLI usage in CI environments.
|
||||||
- show Gitlab CI, GitHub CI and Gitea CI examples.
|
- 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
|
||||||
|
|||||||
423
readme.md
423
readme.md
@@ -1,28 +1,58 @@
|
|||||||
# @ship.zone/szci
|
# @ship.zone/szci
|
||||||
|
|
||||||
**Serve Zone CI** - A powerful CI/CD tool for streamlining Node.js and Docker workflows within CI environments (GitLab CI, GitHub CI, Gitea CI). Now powered by Deno with standalone executables.
|
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.
|
||||||
|
|
||||||
## ✨ Features
|
## Issue Reporting and Security
|
||||||
|
|
||||||
- 🚀 **Standalone Executables** - No Node.js installation required
|
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.
|
||||||
- 🐳 **Docker Integration** - Build, tag, and push Docker images
|
|
||||||
- 📦 **NPM Management** - Install, test, and publish npm packages
|
## 🏗️ Architecture
|
||||||
- 🔧 **Node.js Version Management** - Install and switch between Node versions
|
|
||||||
- 🔐 **SSH Key Management** - Deploy SSH keys from environment variables
|
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:
|
||||||
- ☁️ **Multi-Registry Support** - Push to multiple Docker registries
|
|
||||||
- 🎯 **Cross-Platform** - Binaries for Linux, macOS, and Windows
|
| 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` |
|
||||||
|
|
||||||
|
```
|
||||||
|
CLI Input (Deno.args)
|
||||||
|
↓
|
||||||
|
SmartCLI Router
|
||||||
|
↓
|
||||||
|
├─ szci docker * → env var bridging → npx @git.zone/tsdocker
|
||||||
|
├─ szci npm * → .npmrc generation → pnpm / npm publish
|
||||||
|
├─ szci node * → version aliasing → nvm install
|
||||||
|
├─ szci git * → token injection → git push --mirror
|
||||||
|
└─ szci ssh * → key parsing → write to ~/.ssh
|
||||||
|
```
|
||||||
|
|
||||||
## 📥 Installation
|
## 📥 Installation
|
||||||
|
|
||||||
### NPM (Recommended)
|
### Automated Installer (Recommended)
|
||||||
|
|
||||||
|
The easiest way to install szci is using the automated installer script. It handles platform detection, binary download, and global setup:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/main/install.sh | sudo bash
|
||||||
|
```
|
||||||
|
|
||||||
|
To install a specific version:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/main/install.sh | sudo bash -s -- --version v7.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Via NPM (downloads pre-compiled binary)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm install -g @ship.zone/szci
|
npm install -g @ship.zone/szci
|
||||||
```
|
```
|
||||||
|
|
||||||
The package will automatically download the appropriate pre-compiled binary for your platform.
|
### From Source
|
||||||
|
|
||||||
### From Source (Deno Required)
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://code.foss.global/ship.zone/szci.git
|
git clone https://code.foss.global/ship.zone/szci.git
|
||||||
@@ -30,86 +60,186 @@ cd szci
|
|||||||
deno task compile
|
deno task compile
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The compiled binaries land in `dist/binaries/` for Linux (x64/arm64), macOS (x64/arm64), and Windows (x64).
|
||||||
|
|
||||||
## 🚀 Quick Start
|
## 🚀 Quick Start
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Install Node.js
|
# Setup Node.js environment
|
||||||
szci node install stable
|
szci node install stable
|
||||||
|
|
||||||
# Install dependencies
|
# Install project dependencies
|
||||||
szci npm install
|
szci npm install
|
||||||
|
|
||||||
# Build Docker images
|
# Build & push Docker images
|
||||||
szci docker build
|
szci docker build
|
||||||
|
szci docker push registry.example.com
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
szci npm test
|
szci npm test
|
||||||
|
|
||||||
# Push Docker images
|
|
||||||
szci docker push registry.example.com
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📖 Usage
|
## 📖 CLI Reference
|
||||||
|
|
||||||
### Node.js Management
|
### `szci docker` — Docker Operations
|
||||||
|
|
||||||
|
All Docker commands delegate to `@git.zone/tsdocker` after bridging environment variables.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Install specific Node.js version
|
szci docker build # Build all Dockerfiles in cwd
|
||||||
szci node install lts
|
szci docker login # Login to all configured registries
|
||||||
szci node install stable
|
szci docker prepare # Alias for login
|
||||||
szci node install 18
|
szci docker push registry.example.com # Push images to a registry
|
||||||
|
szci docker pull registry.example.com # Pull images from a registry
|
||||||
# Install from .nvmrc
|
szci docker test # Test Dockerfiles
|
||||||
szci node install legacy
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### NPM Commands
|
**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
|
```sh
|
||||||
# Install dependencies
|
szci npm install # Runs pnpm install
|
||||||
szci npm install
|
szci npm build # Runs pnpm run build
|
||||||
|
szci npm test # Runs pnpm test
|
||||||
# Run tests
|
szci npm prepare # Generates ~/.npmrc from SZCI_TOKEN_NPM* env vars
|
||||||
szci npm test
|
szci npm publish # Full workflow: prepare → install → build → clean → npm publish
|
||||||
|
|
||||||
# Publish package
|
|
||||||
szci npm publish
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker Workflows
|
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
|
```sh
|
||||||
# Prepare Docker environment
|
szci node install stable # Node.js 22
|
||||||
szci docker prepare
|
szci node install lts # Node.js 20
|
||||||
|
szci node install legacy # Node.js 18
|
||||||
# Build all Dockerfiles
|
szci node install 21 # Any specific version
|
||||||
szci docker build
|
|
||||||
|
|
||||||
# Push to registry
|
|
||||||
szci docker push registry.example.com
|
|
||||||
|
|
||||||
# Pull from registry
|
|
||||||
szci docker pull registry.example.com
|
|
||||||
|
|
||||||
# Test Dockerfiles
|
|
||||||
szci docker test
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### SSH Key Management
|
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
|
```sh
|
||||||
# Deploy SSH keys from environment
|
szci ssh prepare # Deploy SSH keys from env vars to ~/.ssh
|
||||||
szci ssh prepare
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Set environment variables like `NPMCI_SSHKEY_1`, `NPMCI_SSHKEY_2`, etc.
|
Reads all `SZCI_SSHKEY_*` env vars and writes the keys to disk.
|
||||||
|
|
||||||
## 🔧 CI/CD Integration
|
### `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
|
||||||
|
{
|
||||||
|
"@ship.zone/szci": {
|
||||||
|
"npmGlobalTools": ["typescript", "pnpm"],
|
||||||
|
"npmAccessLevel": "public",
|
||||||
|
"npmRegistryUrl": "registry.npmjs.org",
|
||||||
|
"urlCloudly": "https://cloudly.example.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 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`) |
|
||||||
|
|
||||||
|
## 🔐 Environment Variables
|
||||||
|
|
||||||
|
### Docker Registry Authentication
|
||||||
|
|
||||||
|
Pipe-delimited format: `registry|username|password`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
SZCI_LOGIN_DOCKER_1="registry.example.com|myuser|mypass"
|
||||||
|
SZCI_LOGIN_DOCKER_2="ghcr.io|token|ghp_xxxx"
|
||||||
|
```
|
||||||
|
|
||||||
|
In **GitLab CI**, the `CI_JOB_TOKEN` is automatically used for `registry.gitlab.com` — no manual config needed.
|
||||||
|
|
||||||
|
### NPM Registry Authentication
|
||||||
|
|
||||||
|
Pipe-delimited format: `registry|token[|plain]`
|
||||||
|
|
||||||
|
```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
|
### GitLab CI
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
image: denoland/deno:alpine
|
image: node:22
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- prepare
|
- prepare
|
||||||
@@ -117,13 +247,8 @@ stages:
|
|||||||
- test
|
- test
|
||||||
- deploy
|
- deploy
|
||||||
|
|
||||||
variables:
|
|
||||||
SZCI_VERSION: "latest"
|
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- deno install --allow-all --global --name szci https://code.foss.global/ship.zone/szci/raw/branch/master/mod.ts
|
- curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/main/install.sh | bash
|
||||||
# OR use the npm package:
|
|
||||||
# - npm install -g @ship.zone/szci
|
|
||||||
|
|
||||||
prepare:
|
prepare:
|
||||||
stage: prepare
|
stage: prepare
|
||||||
@@ -149,10 +274,12 @@ deploy:
|
|||||||
- master
|
- master
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> 💡 In GitLab CI, `CI_JOB_TOKEN` is automatically detected — szci will login to `registry.gitlab.com` without any extra config.
|
||||||
|
|
||||||
### GitHub Actions
|
### GitHub Actions
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: CI Pipeline
|
name: CI/CD
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -161,140 +288,104 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install SZCI
|
- name: Install szci
|
||||||
run: npm install -g @ship.zone/szci
|
run: curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/main/install.sh | sudo bash
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
run: szci node install stable
|
run: szci node install stable
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install & Build
|
||||||
run: szci npm install
|
run: |
|
||||||
|
szci npm install
|
||||||
|
szci npm build
|
||||||
|
|
||||||
- name: Build Docker Images
|
- name: Test
|
||||||
run: szci docker build
|
|
||||||
|
|
||||||
- name: Run Tests
|
|
||||||
run: szci npm test
|
run: szci npm test
|
||||||
|
|
||||||
- name: Push Images
|
- name: Push Docker
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
run: szci docker push ghcr.io
|
run: szci docker push ghcr.io
|
||||||
|
env:
|
||||||
|
SZCI_LOGIN_DOCKER_1: "ghcr.io|${{ github.actor }}|${{ secrets.GITHUB_TOKEN }}"
|
||||||
```
|
```
|
||||||
|
|
||||||
## ⚙️ Configuration
|
### Gitea CI (Woodpecker)
|
||||||
|
|
||||||
Create an `npmextra.json` file in your project root:
|
```yaml
|
||||||
|
steps:
|
||||||
```json
|
- name: ci
|
||||||
{
|
image: node:22
|
||||||
"npmci": {
|
commands:
|
||||||
"npmGlobalTools": [],
|
- curl -sSL https://code.foss.global/ship.zone/szci/raw/branch/main/install.sh | bash
|
||||||
"npmAccessLevel": "public",
|
- szci node install stable
|
||||||
"npmRegistryUrl": "registry.npmjs.org",
|
- szci npm install
|
||||||
"dockerRegistries": [
|
- szci docker build
|
||||||
"registry.gitlab.com"
|
- szci npm test
|
||||||
],
|
|
||||||
"dockerRegistryRepoMap": {
|
|
||||||
"registry.gitlab.com": "mygroup/myrepo"
|
|
||||||
},
|
|
||||||
"dockerBuildargEnvMap": {
|
|
||||||
"ARG_NAME": "ENV_VAR_NAME"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🐳 Docker Registry Authentication
|
## 🔄 Migration from npmci
|
||||||
|
|
||||||
SZCI supports automatic authentication with:
|
Upgrading from `@ship.zone/npmci`? Three steps:
|
||||||
|
|
||||||
- GitLab CI Registry (via `CI_JOB_TOKEN`)
|
1. **Rename the binary** — replace `npmci` with `szci` in all CI scripts
|
||||||
- Custom registries via environment variables
|
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`)
|
||||||
|
|
||||||
Set `NPMCI_LOGIN_DOCKER*` environment variables:
|
| 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
|
```sh
|
||||||
NPMCI_LOGIN_DOCKER_1="registry.example.com|username|password"
|
# Type check
|
||||||
NPMCI_LOGIN_DOCKER_2="another-registry.com|user2|pass2"
|
deno task check
|
||||||
```
|
|
||||||
|
|
||||||
## 🏗️ Development
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Deno 1.40+ installed
|
|
||||||
|
|
||||||
### Building from Source
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Clone the repository
|
|
||||||
git clone https://code.foss.global/ship.zone/szci.git
|
|
||||||
cd szci
|
|
||||||
|
|
||||||
# Compile for all platforms
|
|
||||||
deno task compile
|
|
||||||
|
|
||||||
# Compile for current platform only
|
|
||||||
deno compile --allow-all --output szci mod.ts
|
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
deno task test
|
deno task test
|
||||||
|
|
||||||
# Development mode
|
# Watch tests
|
||||||
deno task dev --help
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
### Testing
|
## License and Legal Information
|
||||||
|
|
||||||
Tests will be migrated to Deno's native test framework:
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.
|
||||||
|
|
||||||
```sh
|
**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.
|
||||||
deno task test
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📦 Binary Sizes
|
### Trademarks
|
||||||
|
|
||||||
The standalone executables are approximately:
|
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.
|
||||||
- Linux x64: ~800MB
|
|
||||||
- Linux ARM64: ~800MB
|
|
||||||
- macOS x64: ~796MB
|
|
||||||
- macOS ARM64: ~796MB
|
|
||||||
- Windows x64: ~804MB
|
|
||||||
|
|
||||||
Sizes include all dependencies and the Deno runtime.
|
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.
|
||||||
|
|
||||||
## 🔄 Migration from npmci
|
### Company Information
|
||||||
|
|
||||||
If you're upgrading from the old `@ship.zone/npmci` package:
|
Task Venture Capital GmbH
|
||||||
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||||
|
|
||||||
1. Update package references:
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||||
```sh
|
|
||||||
npm uninstall -g @ship.zone/npmci
|
|
||||||
npm install -g @ship.zone/szci
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Update CI configuration files - replace `npmci` with `szci`
|
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.
|
||||||
|
|
||||||
3. The command interface remains the same, only the binary name changed
|
|
||||||
|
|
||||||
## 📝 License
|
|
||||||
|
|
||||||
MIT © Lossless GmbH
|
|
||||||
|
|
||||||
## 🔗 Links
|
|
||||||
|
|
||||||
- [Repository](https://code.foss.global/ship.zone/szci)
|
|
||||||
- [Issues](https://code.foss.global/ship.zone/szci/issues)
|
|
||||||
- [Changelog](./changelog.md)
|
|
||||||
|
|
||||||
## 🤝 Contributing
|
|
||||||
|
|
||||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Built with Deno 🦕**
|
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
|
// Disable TLS certificate validation for testing
|
||||||
import { tap, expect } from '@push.rocks/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',
|
||||||
await cloudlyConnector.announceDockerContainer(
|
sanitizeResources: false,
|
||||||
{
|
sanitizeOps: false,
|
||||||
registryUrl: 'registry.losssless.com',
|
fn: async () => {
|
||||||
tag: 'testcontainer',
|
// Create a proper Szci instance for the connector
|
||||||
version: 'x.x.x',
|
const szciInstance = new Szci();
|
||||||
labels: [],
|
await szciInstance.start();
|
||||||
},
|
|
||||||
'cloudly.lossless.one'
|
const cloudlyConnector = new CloudlyConnector(szciInstance);
|
||||||
);
|
await cloudlyConnector.announceDockerContainer(
|
||||||
|
{
|
||||||
|
registryUrl: 'registry.losssless.com',
|
||||||
|
tag: 'testcontainer',
|
||||||
|
version: 'x.x.x',
|
||||||
|
labels: [],
|
||||||
|
},
|
||||||
|
'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();
|
|
||||||
|
|||||||
206
test/test.ts
206
test/test.ts
@@ -1,107 +1,131 @@
|
|||||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
import { assertEquals, assertExists } from '@std/assert';
|
||||||
import * as path from 'path';
|
import * as path from '@std/path';
|
||||||
import * as smartpath from '@push.rocks/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/');
|
|
||||||
};
|
|
||||||
|
|
||||||
import type * as npmciTypes from '../ts/index.js';
|
|
||||||
const npmci = await import('../ts/index.js');
|
|
||||||
|
|
||||||
// ======
|
// ======
|
||||||
// Docker
|
// Docker
|
||||||
// ======
|
// ======
|
||||||
|
|
||||||
let dockerfile1: npmciTypes.Dockerfile;
|
let dockerfile1: Dockerfile;
|
||||||
let dockerfile2: npmciTypes.Dockerfile;
|
let dockerfile2: Dockerfile;
|
||||||
let sortableArray: npmciTypes.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();
|
||||||
await npmciInstance.start();
|
await szciInstance.start();
|
||||||
dockerfile1 = new npmci.Dockerfile(npmciInstance.dockerManager, {
|
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();
|
||||||
await npmciInstance.start();
|
await szciInstance.start();
|
||||||
return npmci.Dockerfile.readDockerfiles(npmciInstance.dockerManager).then(
|
const readDockerfilesArray = await DockerfileModule.Dockerfile.readDockerfiles(
|
||||||
async (readDockerfilesArrayArg: npmciTypes.Dockerfile[]) => {
|
szciInstance.dockerManager
|
||||||
sortableArray = readDockerfilesArrayArg;
|
|
||||||
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: npmciTypes.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',
|
||||||
await npmciInstance.start();
|
// Allow resource leaks since smartshell creates background processes
|
||||||
return npmciInstance.dockerManager.handleCli({
|
sanitizeResources: false,
|
||||||
_: ['docker', 'build'],
|
sanitizeOps: false,
|
||||||
});
|
fn: async () => {
|
||||||
|
const szciInstance = new Szci();
|
||||||
|
await szciInstance.start();
|
||||||
|
await szciInstance.dockerManager.handleCli({
|
||||||
|
_: ['docker', 'build'],
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should test all Dockerfiles', async () => {
|
Deno.test({
|
||||||
const npmciInstance = new npmci.Npmci();
|
name: 'should test all Dockerfiles',
|
||||||
await npmciInstance.start();
|
// Allow resource leaks since smartshell creates background processes
|
||||||
return npmciInstance.dockerManager.handleCli({
|
sanitizeResources: false,
|
||||||
_: ['docker', 'test'],
|
sanitizeOps: false,
|
||||||
});
|
fn: async () => {
|
||||||
|
const szciInstance = new Szci();
|
||||||
|
await szciInstance.start();
|
||||||
|
await szciInstance.dockerManager.handleCli({
|
||||||
|
_: ['docker', 'test'],
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should test dockerfiles', async () => {
|
Deno.test({
|
||||||
const npmciInstance = new npmci.Npmci();
|
name: 'should login docker daemon',
|
||||||
await npmciInstance.start();
|
// Allow resource leaks since smartshell creates background processes
|
||||||
return npmciInstance.dockerManager.handleCli({
|
sanitizeResources: false,
|
||||||
_: ['docker', 'test'],
|
sanitizeOps: false,
|
||||||
});
|
fn: async () => {
|
||||||
});
|
const szciInstance = new Szci();
|
||||||
|
await szciInstance.start();
|
||||||
tap.test('should login docker daemon', async () => {
|
await szciInstance.dockerManager.handleCli({
|
||||||
const npmciInstance = new npmci.Npmci();
|
_: ['docker', 'login'],
|
||||||
await npmciInstance.start();
|
});
|
||||||
return npmciInstance.dockerManager.handleCli({
|
},
|
||||||
_: ['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'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -109,25 +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.start();
|
// Allow resource leaks for this test since nvm creates background processes
|
||||||
await npmciInstance.nodejsManager.handleCli({
|
sanitizeResources: false,
|
||||||
_: ['node', 'install', 'stable'],
|
sanitizeOps: false,
|
||||||
});
|
fn: async () => {
|
||||||
await npmciInstance.nodejsManager.handleCli({
|
const szciInstance = new Szci();
|
||||||
_: ['node', 'install', 'lts'],
|
await szciInstance.start();
|
||||||
});
|
await szciInstance.nodejsManager.handleCli({
|
||||||
await npmciInstance.nodejsManager.handleCli({
|
_: ['node', 'install', 'stable'],
|
||||||
_: ['node', 'install', 'legacy'],
|
});
|
||||||
});
|
await szciInstance.nodejsManager.handleCli({
|
||||||
|
_: ['node', 'install', 'lts'],
|
||||||
|
});
|
||||||
|
await szciInstance.nodejsManager.handleCli({
|
||||||
|
_: ['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();
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@ship.zone/szci',
|
name: '@ship.zone/szci',
|
||||||
version: '6.0.0',
|
version: '7.1.1',
|
||||||
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: '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.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export class CloudlyConnector {
|
|||||||
optionsArg: plugins.tsclass.container.IContainer,
|
optionsArg: plugins.tsclass.container.IContainer,
|
||||||
testCloudlyUrlArg?: string
|
testCloudlyUrlArg?: string
|
||||||
) {
|
) {
|
||||||
const cloudlyUrl = testCloudlyUrlArg || this.szciRef.npmciConfig.getConfig().urlCloudly;
|
const cloudlyUrl = testCloudlyUrlArg || this.szciRef.szciConfig.getConfig().urlCloudly;
|
||||||
if (!cloudlyUrl) {
|
if (!cloudlyUrl) {
|
||||||
logger.log(
|
logger.log(
|
||||||
'warn',
|
'warn',
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { Szci } from './szci.classes.szci.ts';
|
import { Szci } from './szci.classes.szci.ts';
|
||||||
import { Dockerfile } from './manager.docker/mod.classes.dockerfile.ts';
|
|
||||||
|
|
||||||
export const szciInstance = new Npmci();
|
export const szciInstance = new Szci();
|
||||||
|
|
||||||
export { Dockerfile, Npmci };
|
export { Szci };
|
||||||
|
|
||||||
export const runCli = async () => {
|
export const runCli = async () => {
|
||||||
await szciInstance.start();
|
await szciInstance.start();
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
import { logger } from '../szci.logging.ts';
|
|
||||||
import * as plugins from './mod.plugins.ts';
|
|
||||||
import * as paths from '../szci.paths.ts';
|
|
||||||
import { bash } from '../szci.bash.ts';
|
|
||||||
import { Szci } from '../szci.classes.szci.ts';
|
|
||||||
|
|
||||||
export class SzciCloudronManager {
|
|
||||||
public szciRef: Szci;
|
|
||||||
|
|
||||||
constructor(szciArg: Szci) {
|
|
||||||
this.szciRef = szciArg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* handle cli input
|
|
||||||
* @param argvArg
|
|
||||||
*/
|
|
||||||
public handleCli = async (argvArg: any) => {
|
|
||||||
if (argvArg._.length >= 2) {
|
|
||||||
const action: string = argvArg._[1];
|
|
||||||
switch (action) {
|
|
||||||
case 'deploy':
|
|
||||||
await this.deploy();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
logger.log('error', `>>npmci cloudron ...<< action >>${action}<< not supported`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.log(
|
|
||||||
'info',
|
|
||||||
`>>npmci cloudron ...<< cli arguments invalid... Please read the documentation.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces the version string in CloudronManifest file
|
|
||||||
* @param versionArg
|
|
||||||
*/
|
|
||||||
public deploy = async () => {
|
|
||||||
logger.log('info', 'now deploying to cloudron...');
|
|
||||||
logger.log('info', 'installing cloudron cli...');
|
|
||||||
await bash(`pnpm install -g cloudron`);
|
|
||||||
logger.log('ok', 'cloudron cli installed');
|
|
||||||
|
|
||||||
// lets set the version in the CloudronManifest file
|
|
||||||
await this.prepareCloudronManifest(this.szciRef.npmciConfig.getConfig().projectInfo.npm.version);
|
|
||||||
logger.log('ok', 'CloudronManifest prepared');
|
|
||||||
|
|
||||||
// lets figure out the docker image tag
|
|
||||||
const dockerImageTag = await this.szciRef.npmciConfig.kvStorage.readKey('latestPushedDockerTag');
|
|
||||||
const appName = this.szciRef.npmciConfig.getConfig().cloudronAppName;
|
|
||||||
|
|
||||||
const cloudronEnvVar = Deno.env.get("NPMCI_LOGIN_CLOUDRON");
|
|
||||||
const cloudronServer = cloudronEnvVar.split('|')[0];
|
|
||||||
const cloudronToken = cloudronEnvVar.split('|')[1];
|
|
||||||
await bash(`cloudron update --server ${cloudronServer} --token ${cloudronToken} --image ${dockerImageTag} --app ${appName}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
private prepareCloudronManifest = async (versionArg: string) => {
|
|
||||||
const manifestPath = plugins.path.join(paths.cwd, 'CloudronManifest.json');
|
|
||||||
let manifestFile = plugins.smartfile.fs.toStringSync(manifestPath);
|
|
||||||
manifestFile = manifestFile.replace(/##version##/g, versionArg);
|
|
||||||
plugins.smartfile.memory.toFsSync(manifestFile, manifestPath);
|
|
||||||
logger.log('info', 'Version replaced in CloudronManifest file');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from '../szci.plugins.ts';
|
|
||||||
@@ -1,190 +1,78 @@
|
|||||||
import { logger } from '../szci.logging.ts';
|
import { logger } from '../szci.logging.ts';
|
||||||
import * as plugins from './mod.plugins.ts';
|
|
||||||
import * as paths from '../szci.paths.ts';
|
|
||||||
import { bash } from '../szci.bash.ts';
|
import { bash } from '../szci.bash.ts';
|
||||||
|
|
||||||
// classes
|
|
||||||
import { Szci } from '../szci.classes.szci.ts';
|
import { Szci } from '../szci.classes.szci.ts';
|
||||||
import { Dockerfile } from './mod.classes.dockerfile.ts';
|
|
||||||
import { DockerRegistry } from './mod.classes.dockerregistry.ts';
|
|
||||||
import { RegistryStorage } from './mod.classes.registrystorage.ts';
|
|
||||||
|
|
||||||
export class SzciDockerManager {
|
export class SzciDockerManager {
|
||||||
public szciRef: Szci;
|
public szciRef: Szci;
|
||||||
public npmciRegistryStorage = new RegistryStorage();
|
|
||||||
|
|
||||||
constructor(szciArg: Szci) {
|
constructor(szciArg: Szci) {
|
||||||
this.szciRef = szciArg;
|
this.szciRef = szciArg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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) {
|
||||||
const action: string = argvArg._[1];
|
|
||||||
switch (action) {
|
|
||||||
case 'build':
|
|
||||||
await this.build();
|
|
||||||
break;
|
|
||||||
case 'login':
|
|
||||||
case 'prepare':
|
|
||||||
await this.login();
|
|
||||||
break;
|
|
||||||
case 'test':
|
|
||||||
await this.test();
|
|
||||||
break;
|
|
||||||
case 'push':
|
|
||||||
await this.push(argvArg);
|
|
||||||
break;
|
|
||||||
case 'pull':
|
|
||||||
await this.pull(argvArg);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
logger.log('error', `>>npmci docker ...<< action >>${action}<< not supported`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.log(
|
logger.log(
|
||||||
'info',
|
'info',
|
||||||
`>>npmci docker ...<< cli arguments invalid... Please read the documentation.`
|
`>>szci 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 (Deno.env.get("GITLAB_CI")) {
|
|
||||||
console.log('gitlab ci detected');
|
|
||||||
if (!Deno.env.get("CI_JOB_TOKEN") || Deno.env.get("CI_JOB_TOKEN") === '') {
|
|
||||||
logger.log('error', 'Running in Gitlab CI, but no registry token specified by gitlab!');
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
this.npmciRegistryStorage.addRegistry(
|
|
||||||
new DockerRegistry({
|
|
||||||
registryUrl: 'registry.gitlab.com',
|
|
||||||
username: 'gitlab-ci-token',
|
|
||||||
password: Deno.env.get("CI_JOB_TOKEN"),
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle registries
|
this.bridgeEnvVars();
|
||||||
await plugins.smartobject.forEachMinimatch(
|
|
||||||
Deno.env.toObject(),
|
|
||||||
'NPMCI_LOGIN_DOCKER*',
|
|
||||||
async (envString: string) => {
|
|
||||||
this.npmciRegistryStorage.addRegistry(DockerRegistry.fromEnvString(envString));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
const action: string = argvArg._[1];
|
||||||
* pushes an image towards a registry
|
const extraArgs = argvArg._.slice(2).join(' ');
|
||||||
* @param argvArg
|
|
||||||
*/
|
|
||||||
public push = async (argvArg: any) => {
|
|
||||||
await this.prepare();
|
|
||||||
let dockerRegistryUrls: string[] = [];
|
|
||||||
|
|
||||||
// lets parse the input of cli and npmextra
|
switch (action) {
|
||||||
if (argvArg._.length >= 3 && argvArg._[2] !== 'npmextra') {
|
case 'build':
|
||||||
dockerRegistryUrls.push(argvArg._[2]);
|
await bash(`npx @git.zone/tsdocker build ${extraArgs}`.trim());
|
||||||
} else {
|
break;
|
||||||
if (this.szciRef.npmciConfig.getConfig().dockerRegistries.length === 0) {
|
case 'login':
|
||||||
logger.log(
|
case 'prepare':
|
||||||
'warn',
|
await bash(`npx @git.zone/tsdocker login ${extraArgs}`.trim());
|
||||||
`There are no docker registries listed in npmextra.json! This is strange!`
|
break;
|
||||||
);
|
case 'test':
|
||||||
}
|
await bash(`npx @git.zone/tsdocker test ${extraArgs}`.trim());
|
||||||
dockerRegistryUrls = dockerRegistryUrls.concat(
|
break;
|
||||||
this.szciRef.npmciConfig.getConfig().dockerRegistries
|
case 'push':
|
||||||
);
|
await bash(`npx @git.zone/tsdocker push ${extraArgs}`.trim());
|
||||||
}
|
break;
|
||||||
|
case 'pull':
|
||||||
// lets determine the suffix
|
await bash(`npx @git.zone/tsdocker pull ${extraArgs}`.trim());
|
||||||
let suffix = null;
|
break;
|
||||||
if (argvArg._.length >= 4) {
|
default:
|
||||||
suffix = argvArg._[3];
|
logger.log('error', `>>szci docker ...<< action >>${action}<< not supported`);
|
||||||
}
|
|
||||||
|
|
||||||
// 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.`
|
|
||||||
);
|
|
||||||
Deno.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);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* can be used to get the Dockerfiles in the directory
|
|
||||||
*/
|
|
||||||
getDockerfiles = async () => {
|
|
||||||
const dockerfiles = await Dockerfile.readDockerfiles(this);
|
|
||||||
return dockerfiles;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,410 +0,0 @@
|
|||||||
import * as plugins from './mod.plugins.ts';
|
|
||||||
import * as paths from '../szci.paths.ts';
|
|
||||||
|
|
||||||
import { logger } from '../szci.logging.ts';
|
|
||||||
import { bash } from '../szci.bash.ts';
|
|
||||||
|
|
||||||
import { DockerRegistry } from './mod.classes.dockerregistry.ts';
|
|
||||||
import * as helpers from './mod.helpers.ts';
|
|
||||||
import { SzciDockerManager } from './index.ts';
|
|
||||||
import { Szci } from '../szci.classes.szci.ts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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: SzciDockerManager
|
|
||||||
): 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 build order based on dependencies.
|
|
||||||
* @param dockerfiles An array of Dockerfile instances.
|
|
||||||
* @returns A Promise that resolves to a sorted array of Dockerfiles.
|
|
||||||
*/
|
|
||||||
public static async sortDockerfiles(dockerfiles: Dockerfile[]): Promise<Dockerfile[]> {
|
|
||||||
logger.log('info', 'Sorting Dockerfiles based on dependencies...');
|
|
||||||
|
|
||||||
// Map from cleanTag to Dockerfile instance for quick lookup
|
|
||||||
const tagToDockerfile = new Map<string, Dockerfile>();
|
|
||||||
dockerfiles.forEach((dockerfile) => {
|
|
||||||
tagToDockerfile.set(dockerfile.cleanTag, dockerfile);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Build the dependency graph
|
|
||||||
const graph = new Map<Dockerfile, Dockerfile[]>();
|
|
||||||
dockerfiles.forEach((dockerfile) => {
|
|
||||||
const dependencies: Dockerfile[] = [];
|
|
||||||
const baseImage = dockerfile.baseImage;
|
|
||||||
|
|
||||||
// Check if the baseImage is among the local Dockerfiles
|
|
||||||
if (tagToDockerfile.has(baseImage)) {
|
|
||||||
const baseDockerfile = tagToDockerfile.get(baseImage);
|
|
||||||
dependencies.push(baseDockerfile);
|
|
||||||
dockerfile.localBaseImageDependent = true;
|
|
||||||
dockerfile.localBaseDockerfile = baseDockerfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
graph.set(dockerfile, dependencies);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Perform topological sort
|
|
||||||
const sortedDockerfiles: Dockerfile[] = [];
|
|
||||||
const visited = new Set<Dockerfile>();
|
|
||||||
const tempMarked = new Set<Dockerfile>();
|
|
||||||
|
|
||||||
const visit = (dockerfile: Dockerfile) => {
|
|
||||||
if (tempMarked.has(dockerfile)) {
|
|
||||||
throw new Error(`Circular dependency detected involving ${dockerfile.cleanTag}`);
|
|
||||||
}
|
|
||||||
if (!visited.has(dockerfile)) {
|
|
||||||
tempMarked.add(dockerfile);
|
|
||||||
const dependencies = graph.get(dockerfile) || [];
|
|
||||||
dependencies.forEach((dep) => visit(dep));
|
|
||||||
tempMarked.delete(dockerfile);
|
|
||||||
visited.add(dockerfile);
|
|
||||||
sortedDockerfiles.push(dockerfile);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
dockerfiles.forEach((dockerfile) => {
|
|
||||||
if (!visited.has(dockerfile)) {
|
|
||||||
visit(dockerfile);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.log('error', error.message);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the sorted order
|
|
||||||
sortedDockerfiles.forEach((dockerfile, index) => {
|
|
||||||
logger.log(
|
|
||||||
'info',
|
|
||||||
`Build order ${index + 1}: ${dockerfile.cleanTag}
|
|
||||||
with base image ${dockerfile.baseImage}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return sortedDockerfiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* maps local Dockerfiles dependencies to the 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(
|
|
||||||
dockerfileInstanceArg: Dockerfile,
|
|
||||||
dockerfileNameArg: string
|
|
||||||
): string {
|
|
||||||
let versionString: string;
|
|
||||||
const versionRegex = /Dockerfile_(.+)$/;
|
|
||||||
const regexResultArray = versionRegex.exec(dockerfileNameArg);
|
|
||||||
if (regexResultArray && regexResultArray.length === 2) {
|
|
||||||
versionString = regexResultArray[1];
|
|
||||||
} else {
|
|
||||||
versionString = 'latest';
|
|
||||||
}
|
|
||||||
versionString = versionString.replace(
|
|
||||||
'##version##',
|
|
||||||
dockerfileInstanceArg.npmciDockerManagerRef.szciRef.npmciConfig.getConfig().projectInfo.npm
|
|
||||||
.version
|
|
||||||
);
|
|
||||||
return versionString;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the base image from a Dockerfile content without using external libraries.
|
|
||||||
* @param dockerfileContentArg The content of the Dockerfile as a string.
|
|
||||||
* @returns The base image specified in the first FROM instruction.
|
|
||||||
*/
|
|
||||||
public static dockerBaseImage(dockerfileContentArg: string): string {
|
|
||||||
const lines = dockerfileContentArg.split(/\r?\n/);
|
|
||||||
const args: { [key: string]: string } = {};
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
const trimmedLine = line.trim();
|
|
||||||
|
|
||||||
// Skip empty lines and comments
|
|
||||||
if (trimmedLine === '' || trimmedLine.startsWith('#')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match ARG instructions
|
|
||||||
const argMatch = trimmedLine.match(/^ARG\s+([^\s=]+)(?:=(.*))?$/i);
|
|
||||||
if (argMatch) {
|
|
||||||
const argName = argMatch[1];
|
|
||||||
const argValue = argMatch[2] !== undefined ? argMatch[2] : Deno.env.get(argName) || '';
|
|
||||||
args[argName] = argValue;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match FROM instructions
|
|
||||||
const fromMatch = trimmedLine.match(/^FROM\s+(.+?)(?:\s+AS\s+[^\s]+)?$/i);
|
|
||||||
if (fromMatch) {
|
|
||||||
let baseImage = fromMatch[1].trim();
|
|
||||||
|
|
||||||
// Substitute variables in the base image name
|
|
||||||
baseImage = Dockerfile.substituteVariables(baseImage, args);
|
|
||||||
|
|
||||||
return baseImage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('No FROM instruction found in Dockerfile');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Substitutes variables in a string, supporting default values like ${VAR:-default}.
|
|
||||||
* @param str The string containing variables.
|
|
||||||
* @param vars The object containing variable values.
|
|
||||||
* @returns The string with variables substituted.
|
|
||||||
*/
|
|
||||||
private static substituteVariables(str: string, vars: { [key: string]: string }): string {
|
|
||||||
return str.replace(/\${([^}:]+)(:-([^}]+))?}/g, (_, varName, __, defaultValue) => {
|
|
||||||
if (vars[varName] !== undefined) {
|
|
||||||
return vars[varName];
|
|
||||||
} else if (defaultValue !== undefined) {
|
|
||||||
return defaultValue;
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the docker tag
|
|
||||||
*/
|
|
||||||
public static getDockerTagString(
|
|
||||||
npmciDockerManagerRef: SzciDockerManager,
|
|
||||||
registryArg: string,
|
|
||||||
repoArg: string,
|
|
||||||
versionArg: string,
|
|
||||||
suffixArg?: string
|
|
||||||
): string {
|
|
||||||
// determine wether the repo should be mapped accordingly to the registry
|
|
||||||
const mappedRepo =
|
|
||||||
npmciDockerManagerRef.szciRef.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: SzciDockerManager
|
|
||||||
): Promise<string> {
|
|
||||||
logger.log('info', 'checking for env vars to be supplied to the docker build');
|
|
||||||
let buildArgsString: string = '';
|
|
||||||
for (const dockerArgKey of Object.keys(
|
|
||||||
npmciDockerManagerRef.szciRef.npmciConfig.getConfig().dockerBuildargEnvMap
|
|
||||||
)) {
|
|
||||||
const dockerArgOuterEnvVar =
|
|
||||||
npmciDockerManagerRef.szciRef.npmciConfig.getConfig().dockerBuildargEnvMap[dockerArgKey];
|
|
||||||
logger.log(
|
|
||||||
'note',
|
|
||||||
`docker ARG "${dockerArgKey}" maps to outer env var "${dockerArgOuterEnvVar}"`
|
|
||||||
);
|
|
||||||
const targetValue = Deno.env.get(dockerArgOuterEnvVar);
|
|
||||||
buildArgsString = `${buildArgsString} --build-arg ${dockerArgKey}="${targetValue}"`;
|
|
||||||
}
|
|
||||||
return buildArgsString;
|
|
||||||
}
|
|
||||||
|
|
||||||
// INSTANCE
|
|
||||||
public npmciDockerManagerRef: SzciDockerManager;
|
|
||||||
|
|
||||||
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: SzciDockerManager,
|
|
||||||
options: { filePath?: string; fileContents?: string | Buffer; read?: boolean }
|
|
||||||
) {
|
|
||||||
this.npmciDockerManagerRef = dockerManagerRefArg;
|
|
||||||
this.filePath = options.filePath;
|
|
||||||
this.repo =
|
|
||||||
this.npmciDockerManagerRef.szciRef.npmciEnv.repo.user +
|
|
||||||
'/' +
|
|
||||||
this.npmciDockerManagerRef.szciRef.npmciEnv.repo.repo;
|
|
||||||
this.version = Dockerfile.dockerFileVersion(this, 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.szciRef.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.szciRef.cloudlyConnector.announceDockerContainer({
|
|
||||||
registryUrl: this.pushTag,
|
|
||||||
tag: this.buildTag,
|
|
||||||
labels: [],
|
|
||||||
version: this.npmciDockerManagerRef.szciRef.npmciConfig.getConfig().projectInfo.npm.version,
|
|
||||||
});
|
|
||||||
await this.npmciDockerManagerRef.szciRef.npmciConfig.kvStorage.writeKey(
|
|
||||||
'latestPushedDockerTag',
|
|
||||||
this.pushTag
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.SzciTestDir, '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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { logger } from '../szci.logging.ts';
|
|
||||||
import * as plugins from './mod.plugins.ts';
|
|
||||||
import { bash } from '../szci.bash.ts';
|
|
||||||
|
|
||||||
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...');
|
|
||||||
Deno.exit(1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const registryUrl = dockerRegexResultArray[0].replace('https://', '').replace('http://', '');
|
|
||||||
const username = dockerRegexResultArray[1];
|
|
||||||
const password = dockerRegexResultArray[2];
|
|
||||||
return new DockerRegistry({
|
|
||||||
registryUrl: registryUrl,
|
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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}!`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { logger } from '../szci.logging.ts';
|
|
||||||
import * as plugins from './mod.plugins.ts';
|
|
||||||
|
|
||||||
import { DockerRegistry } from './mod.classes.dockerregistry.ts';
|
|
||||||
|
|
||||||
export class RegistryStorage {
|
|
||||||
objectMap = new plugins.lik.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!');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { logger } from '../szci.logging.ts';
|
|
||||||
import * as plugins from './mod.plugins.ts';
|
|
||||||
import * as paths from '../szci.paths.ts';
|
|
||||||
|
|
||||||
import { Dockerfile } from './mod.classes.dockerfile.ts';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from '../szci.plugins.ts';
|
|
||||||
@@ -22,20 +22,20 @@ export class SzciGitManager {
|
|||||||
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 = Deno.env.get("NPMCI_GIT_GITHUBTOKEN");
|
const githubToken = Deno.env.get("SZCI_GIT_GITHUBTOKEN");
|
||||||
const githubUser = Deno.env.get("NPMCI_GIT_GITHUBGROUP") || this.szciRef.npmciEnv.repo.user;
|
const githubUser = Deno.env.get("SZCI_GIT_GITHUBGROUP") || this.szciRef.szciEnv.repo.user;
|
||||||
const githubRepo = Deno.env.get("NPMCI_GIT_GITHUB") || this.szciRef.npmciEnv.repo.repo;
|
const githubRepo = Deno.env.get("SZCI_GIT_GITHUB") || this.szciRef.szciEnv.repo.repo;
|
||||||
if (
|
if (
|
||||||
this.szciRef.npmciConfig.getConfig().projectInfo.npm.packageJson.private === true ||
|
this.szciRef.szciConfig.getConfig().projectInfo.npm.packageJson.private === true ||
|
||||||
this.szciRef.npmciConfig.getConfig().npmAccessLevel === 'private'
|
this.szciRef.szciConfig.getConfig().npmAccessLevel === 'private'
|
||||||
) {
|
) {
|
||||||
logger.log(
|
logger.log(
|
||||||
'warn',
|
'warn',
|
||||||
@@ -64,7 +64,7 @@ export class SzciGitManager {
|
|||||||
// 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!`);
|
||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ export class SzciNodeJsManager {
|
|||||||
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`);
|
||||||
Deno.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.`
|
||||||
);
|
);
|
||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
@@ -44,11 +44,11 @@ export class SzciNodeJsManager {
|
|||||||
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 SzciNodeJsManager {
|
|||||||
await bash('npm -v');
|
await bash('npm -v');
|
||||||
|
|
||||||
// lets look for further config
|
// lets look for further config
|
||||||
const config = await this.szciRef.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}"`);
|
||||||
|
|||||||
@@ -36,13 +36,13 @@ export class SzciNpmManager {
|
|||||||
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`);
|
||||||
Deno.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.`
|
||||||
);
|
);
|
||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
@@ -53,11 +53,11 @@ export class SzciNpmManager {
|
|||||||
*/
|
*/
|
||||||
public async prepare() {
|
public async prepare() {
|
||||||
logger.log('info', 'running >>npm prepare<<');
|
logger.log('info', 'running >>npm prepare<<');
|
||||||
const config = this.szciRef.npmciConfig.getConfig();
|
const config = this.szciRef.szciConfig.getConfig();
|
||||||
let npmrcFileString: string = '';
|
let npmrcFileString: string = '';
|
||||||
await plugins.smartobject.forEachMinimatch(
|
await plugins.smartobject.forEachMinimatch(
|
||||||
Deno.env.toObject(),
|
Deno.env.toObject(),
|
||||||
'NPMCI_TOKEN_NPM*',
|
'SZCI_TOKEN_NPM*',
|
||||||
(npmEnvArg: string) => {
|
(npmEnvArg: string) => {
|
||||||
if (!npmEnvArg) {
|
if (!npmEnvArg) {
|
||||||
logger.log('note','found empty token...');
|
logger.log('note','found empty token...');
|
||||||
@@ -103,11 +103,11 @@ export class SzciNpmManager {
|
|||||||
let npmAccessCliString = ``;
|
let npmAccessCliString = ``;
|
||||||
let npmRegistryCliString = ``;
|
let npmRegistryCliString = ``;
|
||||||
let publishVerdaccioAsWell = false;
|
let publishVerdaccioAsWell = false;
|
||||||
const config = this.szciRef.npmciConfig.getConfig();
|
const config = this.szciRef.szciConfig.getConfig();
|
||||||
const availableRegistries: string[] = [];
|
const availableRegistries: string[] = [];
|
||||||
await plugins.smartobject.forEachMinimatch(
|
await plugins.smartobject.forEachMinimatch(
|
||||||
Deno.env.toObject(),
|
Deno.env.toObject(),
|
||||||
'NPMCI_TOKEN_NPM*',
|
'SZCI_TOKEN_NPM*',
|
||||||
(npmEnvArg: string) => {
|
(npmEnvArg: string) => {
|
||||||
availableRegistries.push(npmEnvArg.split('|')[0]);
|
availableRegistries.push(npmEnvArg.split('|')[0]);
|
||||||
}
|
}
|
||||||
@@ -166,7 +166,7 @@ export class SzciNpmManager {
|
|||||||
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!:`);
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import * as plugins from './mod.plugins.ts';
|
|
||||||
import * as paths from '../szci.paths.ts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* cleans npmci config files
|
|
||||||
*/
|
|
||||||
export let clean = async (): Promise<void> => {
|
|
||||||
plugins.smartfile.fs.removeSync(paths.SzciPackageConfig);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from '../szci.plugins.ts';
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import * as plugins from './mod.plugins.ts';
|
|
||||||
import { bash } from '../szci.bash.ts';
|
|
||||||
|
|
||||||
export let command = async () => {
|
|
||||||
let wrappedCommand: string = '';
|
|
||||||
let argvArray = ['deno', 'mod.ts', ...Deno.args];
|
|
||||||
for (let i = 3; i < argvArray.length; i++) {
|
|
||||||
wrappedCommand = wrappedCommand + argvArray[i];
|
|
||||||
if (i + 1 !== argvArray.length) {
|
|
||||||
wrappedCommand = wrappedCommand + ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await bash(wrappedCommand);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from '../szci.plugins.ts';
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import * as plugins from './plugins.ts';
|
|
||||||
import * as paths from '../szci.paths.ts';
|
|
||||||
import { logger } from '../szci.logging.ts';
|
|
||||||
import { Szci } from '../szci.classes.szci.ts';
|
|
||||||
|
|
||||||
export const handleCli = async (szciRefArg: Szci, argvArg: any) => {
|
|
||||||
logger.log('info', 'checking execution context');
|
|
||||||
const presentRunnerTags = Deno.env.get("CI_RUNNER_TAGS").split(',').map((stringArg) =>
|
|
||||||
stringArg.trim()
|
|
||||||
);
|
|
||||||
let allDesiredGitlabRunnerTagsPresent = true;
|
|
||||||
for (const desiredRunnerTag of szciRefArg.npmciConfig.getConfig().gitlabRunnerTags) {
|
|
||||||
if (!presentRunnerTags.includes(desiredRunnerTag)) {
|
|
||||||
allDesiredGitlabRunnerTagsPresent = false;
|
|
||||||
logger.log(
|
|
||||||
'error',
|
|
||||||
`Desired runnerRag ${desiredRunnerTag} is missing in current execution context.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!allDesiredGitlabRunnerTagsPresent) {
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from '../szci.plugins.ts';
|
|
||||||
@@ -1,10 +1,22 @@
|
|||||||
import { logger } from '../szci.logging.ts';
|
import { logger } from '../szci.logging.ts';
|
||||||
import * as plugins from './mod.plugins.ts';
|
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();
|
||||||
@@ -14,51 +26,72 @@ export let handleCli = async (argvArg: any) => {
|
|||||||
Deno.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!`);
|
||||||
Deno.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.smartobject.forEachMinimatch(Deno.env.toObject(), 'NPMCI_SSHKEY_*', evaluateSshEnv);
|
|
||||||
if (!Deno.env.get("NPMTS_TEST")) {
|
// Get all env vars and filter for SSH keys
|
||||||
sshInstance.writeToDisk();
|
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();
|
||||||
|
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;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import * as plugins from './mod.plugins.ts';
|
|
||||||
import { bash } from '../szci.bash.ts';
|
|
||||||
import { logger } from '../szci.logging.ts';
|
|
||||||
|
|
||||||
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.smartobject.forEachMinimatch(Deno.env.toObject(), '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',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from '../szci.plugins.ts';
|
|
||||||
@@ -7,9 +7,9 @@ import * as paths from './szci.paths.ts';
|
|||||||
*/
|
*/
|
||||||
export let nvmAvailable = plugins.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: [],
|
||||||
});
|
});
|
||||||
@@ -19,16 +19,16 @@ const npmciSmartshell = new plugins.smartshell.Smartshell({
|
|||||||
*/
|
*/
|
||||||
const checkToolsAvailable = async () => {
|
const checkToolsAvailable = async () => {
|
||||||
// check for nvm
|
// check for nvm
|
||||||
if (!Deno.env.get('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);
|
||||||
@@ -46,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;
|
||||||
@@ -55,13 +55,13 @@ export let bash = async (commandArg: string, retryArg: number = 2): Promise<stri
|
|||||||
retryArg = 0;
|
retryArg = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Deno.env.get('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 (Deno.env.get('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) {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { SzciConfig } from './szci.classes.szciconfig.ts';
|
|||||||
import { CloudlyConnector } from './connector.cloudly/cloudlyconnector.ts';
|
import { CloudlyConnector } from './connector.cloudly/cloudlyconnector.ts';
|
||||||
|
|
||||||
// managers
|
// managers
|
||||||
import { SzciCloudronManager } from './manager.cloudron/index.ts';
|
|
||||||
import { SzciDockerManager } from './manager.docker/index.ts';
|
import { SzciDockerManager } from './manager.docker/index.ts';
|
||||||
import { SzciGitManager } from './manager.git/index.ts';
|
import { SzciGitManager } from './manager.git/index.ts';
|
||||||
import { SzciNodeJsManager } from './manager.nodejs/index.ts';
|
import { SzciNodeJsManager } from './manager.nodejs/index.ts';
|
||||||
@@ -19,19 +18,18 @@ import { SzciNpmManager } from './manager.npm/index.ts';
|
|||||||
|
|
||||||
export class Szci {
|
export class Szci {
|
||||||
public analytics: plugins.smartanalytics.Analytics;
|
public analytics: plugins.smartanalytics.Analytics;
|
||||||
public cloudlyConnector: CloudlyConnector;
|
public cloudlyConnector!: CloudlyConnector;
|
||||||
|
|
||||||
public npmciEnv: SzciEnv;
|
public szciEnv!: SzciEnv;
|
||||||
public npmciInfo: SzciInfo;
|
public szciInfo!: SzciInfo;
|
||||||
public npmciConfig: SzciConfig;
|
public szciConfig!: SzciConfig;
|
||||||
public npmciCli: SzciCli;
|
public szciCli!: SzciCli;
|
||||||
|
|
||||||
// managers
|
// managers
|
||||||
public cloudronManager: SzciCloudronManager;
|
public dockerManager!: SzciDockerManager;
|
||||||
public dockerManager: SzciDockerManager;
|
public gitManager!: SzciGitManager;
|
||||||
public gitManager: SzciGitManager;
|
public nodejsManager!: SzciNodeJsManager;
|
||||||
public nodejsManager: SzciNodeJsManager;
|
public npmManager!: SzciNpmManager;
|
||||||
public npmManager: SzciNpmManager;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.analytics = new plugins.smartanalytics.Analytics({
|
this.analytics = new plugins.smartanalytics.Analytics({
|
||||||
@@ -43,19 +41,18 @@ export class Szci {
|
|||||||
|
|
||||||
public async start() {
|
public async start() {
|
||||||
this.cloudlyConnector = new CloudlyConnector(this);
|
this.cloudlyConnector = new CloudlyConnector(this);
|
||||||
this.npmciEnv = new SzciEnv(this);
|
this.szciEnv = new SzciEnv(this);
|
||||||
this.npmciInfo = new SzciInfo(this);
|
this.szciInfo = new SzciInfo(this);
|
||||||
await this.npmciInfo.printToConsole();
|
await this.szciInfo.printToConsole();
|
||||||
this.npmciCli = new SzciCli(this);
|
this.szciCli = new SzciCli(this);
|
||||||
this.npmciConfig = new SzciConfig(this);
|
this.szciConfig = new SzciConfig(this);
|
||||||
await this.npmciConfig.init();
|
await this.szciConfig.init();
|
||||||
|
|
||||||
// managers
|
// managers
|
||||||
this.cloudronManager = new SzciCloudronManager(this);
|
|
||||||
this.dockerManager = new SzciDockerManager(this);
|
this.dockerManager = new SzciDockerManager(this);
|
||||||
this.gitManager = new SzciGitManager(this);
|
this.gitManager = new SzciGitManager(this);
|
||||||
this.nodejsManager = new SzciNodeJsManager(this);
|
this.nodejsManager = new SzciNodeJsManager(this);
|
||||||
this.npmManager = new SzciNpmManager(this);
|
this.npmManager = new SzciNpmManager(this);
|
||||||
this.npmciCli.startParse();
|
this.szciCli.startParse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,36 +10,12 @@ export class SzciCli {
|
|||||||
constructor(szciArg: Szci) {
|
constructor(szciArg: Szci) {
|
||||||
this.szciRef = szciArg;
|
this.szciRef = szciArg;
|
||||||
this.smartcli = new plugins.smartcli.Smartcli();
|
this.smartcli = new plugins.smartcli.Smartcli();
|
||||||
this.smartcli.addVersion(this.szciRef.npmciInfo.version);
|
this.smartcli.addVersion(this.szciRef.szciInfo.version);
|
||||||
|
|
||||||
// clean
|
// docker
|
||||||
this.smartcli.addCommand('clean').subscribe(
|
this.smartcli.addCommand('docker').subscribe(
|
||||||
async (argv) => {
|
async (argvArg) => {
|
||||||
const modClean = await import('./mod_clean/index.js');
|
await this.szciRef.dockerManager.handleCli(argvArg);
|
||||||
await modClean.clean();
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.log(err);
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// cloudron
|
|
||||||
this.smartcli.addCommand('cloudron').subscribe(
|
|
||||||
async (argv) => {
|
|
||||||
await this.szciRef.cloudronManager.handleCli(argv);
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.log(err);
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// command
|
|
||||||
this.smartcli.addCommand('command').subscribe(
|
|
||||||
async (argv) => {
|
|
||||||
const modCommand = await import('./mod_command/index.js');
|
|
||||||
await modCommand.command();
|
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
@@ -58,17 +34,6 @@ export class SzciCli {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// build
|
|
||||||
this.smartcli.addCommand('docker').subscribe(
|
|
||||||
async (argvArg) => {
|
|
||||||
await this.szciRef.dockerManager.handleCli(argvArg);
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.log(err);
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// node
|
// node
|
||||||
this.smartcli.addCommand('node').subscribe(
|
this.smartcli.addCommand('node').subscribe(
|
||||||
async (argvArg) => {
|
async (argvArg) => {
|
||||||
@@ -90,28 +55,11 @@ export class SzciCli {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.smartcli.addCommand('precheck').subscribe(async (argvArg) => {
|
// ssh
|
||||||
const modPrecheck = await import('./mod_precheck/index.js');
|
|
||||||
await modPrecheck.handleCli(this.szciRef, argvArg);
|
|
||||||
});
|
|
||||||
|
|
||||||
// trigger
|
|
||||||
this.smartcli.addCommand('ssh').subscribe(async (argvArg) => {
|
this.smartcli.addCommand('ssh').subscribe(async (argvArg) => {
|
||||||
const modSsh = await import('./mod_ssh/index.js');
|
const modSsh = await import('./mod_ssh/index.ts');
|
||||||
await modSsh.handleCli(argvArg);
|
await modSsh.handleCli(argvArg);
|
||||||
});
|
});
|
||||||
|
|
||||||
// trigger
|
|
||||||
this.smartcli.addCommand('trigger').subscribe(
|
|
||||||
async (argv) => {
|
|
||||||
const modTrigger = await import('./mod_trigger/index.js');
|
|
||||||
await modTrigger.trigger();
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.log(err);
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public startParse = () => {
|
public startParse = () => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { logger } from './szci.logging.ts';
|
|||||||
import { Szci } from './szci.classes.szci.ts';
|
import { Szci } from './szci.classes.szci.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the main config interface for npmci
|
* the main config interface for szci
|
||||||
*/
|
*/
|
||||||
export interface ISzciOptions {
|
export interface ISzciOptions {
|
||||||
projectInfo: plugins.projectinfo.ProjectInfo;
|
projectInfo: plugins.projectinfo.ProjectInfo;
|
||||||
@@ -15,44 +15,33 @@ export interface ISzciOptions {
|
|||||||
npmAccessLevel?: 'private' | 'public';
|
npmAccessLevel?: 'private' | 'public';
|
||||||
npmRegistryUrl: string;
|
npmRegistryUrl: string;
|
||||||
|
|
||||||
// docker
|
|
||||||
dockerRegistries: string[];
|
|
||||||
dockerRegistryRepoMap: { [key: string]: string };
|
|
||||||
dockerBuildargEnvMap: { [key: string]: string };
|
|
||||||
|
|
||||||
// gitlab
|
|
||||||
gitlabRunnerTags: string[];
|
|
||||||
|
|
||||||
// urls
|
// urls
|
||||||
urlCloudly: string;
|
urlCloudly?: string;
|
||||||
|
|
||||||
// cloudron
|
|
||||||
cloudronAppName?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a config class for Npmci
|
* a config class for Szci
|
||||||
*/
|
*/
|
||||||
export class SzciConfig {
|
export class SzciConfig {
|
||||||
public szciRef: Szci;
|
public szciRef: Szci;
|
||||||
|
|
||||||
public npmciNpmextra: plugins.npmextra.Npmextra;
|
public szciNpmextra!: plugins.npmextra.Npmextra;
|
||||||
public kvStorage: plugins.npmextra.KeyValueStore;
|
public kvStorage!: plugins.npmextra.KeyValueStore;
|
||||||
public npmciQenv: plugins.qenv.Qenv;
|
public szciQenv!: plugins.qenv.Qenv;
|
||||||
|
|
||||||
private configObject: ISzciOptions;
|
private configObject!: ISzciOptions;
|
||||||
|
|
||||||
constructor(szciRefArg: Szci) {
|
constructor(szciRefArg: Szci) {
|
||||||
this.szciRef = szciRefArg;
|
this.szciRef = szciRefArg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async init() {
|
public async init() {
|
||||||
this.npmciNpmextra = new plugins.npmextra.Npmextra(paths.cwd);
|
this.szciNpmextra = new plugins.npmextra.Npmextra(paths.cwd);
|
||||||
this.kvStorage = new plugins.npmextra.KeyValueStore({
|
this.kvStorage = new plugins.npmextra.KeyValueStore({
|
||||||
typeArg: 'userHomeDir',
|
typeArg: 'userHomeDir',
|
||||||
identityArg: `.npmci_${this.szciRef.npmciEnv.repo.user}_${this.szciRef.npmciEnv.repo.repo}`,
|
identityArg: `.szci_${this.szciRef.szciEnv.repo.user}_${this.szciRef.szciEnv.repo.repo}`,
|
||||||
});
|
});
|
||||||
this.npmciQenv = new plugins.qenv.Qenv(
|
this.szciQenv = new plugins.qenv.Qenv(
|
||||||
paths.SzciProjectDir,
|
paths.SzciProjectDir,
|
||||||
paths.SzciProjectNogitDir,
|
paths.SzciProjectNogitDir,
|
||||||
false
|
false
|
||||||
@@ -61,15 +50,11 @@ export class SzciConfig {
|
|||||||
this.configObject = {
|
this.configObject = {
|
||||||
projectInfo: new plugins.projectinfo.ProjectInfo(paths.cwd),
|
projectInfo: new plugins.projectinfo.ProjectInfo(paths.cwd),
|
||||||
npmGlobalTools: [],
|
npmGlobalTools: [],
|
||||||
dockerRegistries: [],
|
|
||||||
dockerRegistryRepoMap: {},
|
|
||||||
npmAccessLevel: 'private',
|
npmAccessLevel: 'private',
|
||||||
npmRegistryUrl: 'registry.npmjs.org',
|
npmRegistryUrl: 'registry.npmjs.org',
|
||||||
gitlabRunnerTags: [],
|
urlCloudly: await this.szciQenv.getEnvVarOnDemand('SZCI_URL_CLOUDLY'),
|
||||||
dockerBuildargEnvMap: {},
|
|
||||||
urlCloudly: await this.npmciQenv.getEnvVarOnDemand('NPMCI_URL_CLOUDLY'),
|
|
||||||
};
|
};
|
||||||
this.configObject = this.npmciNpmextra.dataFor<ISzciOptions>('szci', this.configObject);
|
this.configObject = this.szciNpmextra.dataFor<ISzciOptions>('@ship.zone/szci', this.configObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getConfig(): ISzciOptions {
|
public getConfig(): ISzciOptions {
|
||||||
|
|||||||
@@ -9,15 +9,17 @@ export class SzciEnv {
|
|||||||
|
|
||||||
constructor(szciRefArg: Szci) {
|
constructor(szciRefArg: Szci) {
|
||||||
this.szciRef = szciRefArg;
|
this.szciRef = szciRefArg;
|
||||||
if (!this.repoString && Deno.env.get("GITLAB_CI")) {
|
|
||||||
this.repoString = Deno.env.get("CI_REPOSITORY_URL");
|
// Determine repo string from environment
|
||||||
|
let repoUrl: string | undefined;
|
||||||
|
if (Deno.env.get("GITLAB_CI")) {
|
||||||
|
repoUrl = Deno.env.get("CI_REPOSITORY_URL");
|
||||||
}
|
}
|
||||||
if (!this.repoString && Deno.env.get("NPMCI_COMPUTED_REPOURL")) {
|
if (!repoUrl && Deno.env.get("SZCI_COMPUTED_REPOURL")) {
|
||||||
this.repoString = Deno.env.get("NPMCI_COMPUTED_REPOURL");
|
repoUrl = Deno.env.get("SZCI_COMPUTED_REPOURL");
|
||||||
}
|
|
||||||
if (!this.repoString) {
|
|
||||||
this.repoString = 'https://undefined:undefined@github.com/undefined/undefined.git';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.repoString = repoUrl || 'https://undefined:undefined@github.com/undefined/undefined.git';
|
||||||
this.repo = new plugins.smartstring.GitRepo(this.repoString);
|
this.repo = new plugins.smartstring.GitRepo(this.repoString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
import * as plugins from './szci.plugins.ts';
|
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();
|
export const cwd = Deno.cwd();
|
||||||
|
|
||||||
// package paths
|
// package paths
|
||||||
@@ -9,8 +18,14 @@ export const SzciPackageRoot = plugins.path.join(
|
|||||||
);
|
);
|
||||||
export const SzciPackageConfig = plugins.path.join(SzciPackageRoot, './config.json');
|
export const SzciPackageConfig = plugins.path.join(SzciPackageRoot, './config.json');
|
||||||
|
|
||||||
// project paths
|
// 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 SzciProjectDir = cwd;
|
||||||
export const SzciProjectNogitDir = plugins.path.join(SzciProjectDir, './.nogit');
|
export const SzciProjectNogitDir = plugins.path.join(SzciProjectDir, './.nogit');
|
||||||
export const SzciTestDir = plugins.path.join(cwd, './test');
|
export const SzciTestDir = plugins.path.join(cwd, './test');
|
||||||
export const SzciCacheDir = plugins.path.join(cwd, './.npmci_cache');
|
export const SzciCacheDir = plugins.path.join(cwd, './.szci_cache');
|
||||||
|
|||||||
@@ -14,12 +14,10 @@ import * as servezoneApi from '@serve.zone/api';
|
|||||||
export { servezoneApi };
|
export { servezoneApi };
|
||||||
|
|
||||||
// @push.rocks
|
// @push.rocks
|
||||||
import * as lik from '@push.rocks/lik';
|
|
||||||
import * as npmextra from '@push.rocks/npmextra';
|
import * as npmextra from '@push.rocks/npmextra';
|
||||||
import * as projectinfo from '@push.rocks/projectinfo';
|
import * as projectinfo from '@push.rocks/projectinfo';
|
||||||
import * as qenv from '@push.rocks/qenv';
|
import * as qenv from '@push.rocks/qenv';
|
||||||
import * as smartanalytics from '@push.rocks/smartanalytics';
|
import * as smartanalytics from '@push.rocks/smartanalytics';
|
||||||
import * as smartdelay from '@push.rocks/smartdelay';
|
|
||||||
import * as smartfile from '@push.rocks/smartfile';
|
import * as smartfile from '@push.rocks/smartfile';
|
||||||
import * as smartcli from '@push.rocks/smartcli';
|
import * as smartcli from '@push.rocks/smartcli';
|
||||||
import * as smartgit from '@push.rocks/smartgit';
|
import * as smartgit from '@push.rocks/smartgit';
|
||||||
@@ -35,12 +33,10 @@ import * as smartssh from '@push.rocks/smartssh';
|
|||||||
import * as smartstring from '@push.rocks/smartstring';
|
import * as smartstring from '@push.rocks/smartstring';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
lik,
|
|
||||||
npmextra,
|
npmextra,
|
||||||
projectinfo,
|
projectinfo,
|
||||||
qenv,
|
qenv,
|
||||||
smartanalytics,
|
smartanalytics,
|
||||||
smartdelay,
|
|
||||||
smartfile,
|
smartfile,
|
||||||
smartgit,
|
smartgit,
|
||||||
smartcli,
|
smartcli,
|
||||||
@@ -61,7 +57,4 @@ import * as tsclass from '@tsclass/tsclass';
|
|||||||
|
|
||||||
export { tsclass };
|
export { tsclass };
|
||||||
|
|
||||||
// third party
|
|
||||||
import * as through2 from 'through2';
|
|
||||||
|
|
||||||
export { through2 };
|
|
||||||
|
|||||||
Reference in New Issue
Block a user