Compare commits

..
36 Commits
Author SHA1 Message Date
james.maes e84008ee19 Merge pull request #16 from KofTwentyTwo/release/0.3.1
Release 0.3.1
2026-09-23 17:54:02 -05:00
james.maes d0ce5266ee Merge pull request #15 from KofTwentyTwo/feature/GH-14-distroless-debian13
fix(#14): distroless debian13 runtime for node-webapp
2026-09-23 17:52:03 -05:00
james.maes 9c487cfa04 fix(#14): use distroless debian13 runtime for node-webapp
debian12 ships libssl3 3.0.18 with open HIGH/CRITICAL CVEs that fail the Trivy gate; debian13 scans clean.

Closes #14
2026-09-23 17:51:08 -05:00
james.maes 836cb20c5b Merge pull request #13 from KofTwentyTwo/main
chore: back-merge v0.3.0
2026-07-12 10:05:05 -05:00
james.maes 1dc88ef564 Merge pull request #12 from KofTwentyTwo/release/0.3.0
release: Munitor 0.3.0
2026-07-12 10:03:51 -05:00
james.maes db5e076569 Merge pull request #11 from KofTwentyTwo/feature/GH-9-release-closeout
docs(#9): close out 0.3 release
2026-07-12 10:02:53 -05:00
James Maes 0fcd218b9f docs(#9): close out 0.3 release 2026-07-12 10:02:03 -05:00
james.maes e1433d913b Merge pull request #10 from KofTwentyTwo/feature/GH-9-consolidated-gitops-writeback
feat: support consolidated GitOps write-back
2026-07-12 05:43:48 -05:00
james.maes 15d436a8fa Merge pull request #8 from KofTwentyTwo/feature/GH-7-preserve-node-webapp-dockerfile
fix: preserve node-webapp Dockerfiles
2026-07-12 05:43:25 -05:00
James Maes 4b11b3acc8 feat(#9): support consolidated GitOps write-back 2026-07-12 05:39:14 -05:00
James Maes 894a550312 fix: preserve node-webapp Dockerfiles
Add an opt-in repository Dockerfile path while retaining generated images by default. Closes #7
2026-07-11 13:42:35 -05:00
james.maes b83dc90cc6 Merge pull request #6 from KofTwentyTwo/feature/GH-5-pnpm-support
feat: first-class pnpm support (#5)
2026-04-16 16:40:01 -05:00
james.maes e408693ff5 feat: add obsidian-plugin pipeline type with GitHub Release artifacts
New pipeline type for Obsidian plugins: builds with esbuild, runs
quality gates, and publishes main.js/manifest.json/styles.css to
GitHub Releases. Extends github_release job with release_artifacts
parameter for artifact uploads. 616 tests passing.
2026-04-16 16:37:07 -05:00
james.maes c6e993677b chore: migrate Claude state from sidecar to repo
Move SESSION-STATE.md, TODO.md, and reference docs from the ClaudeCode
sidecar repo into docs/. Update .gitignore to allow tracking. Add
CLAUDE.md with project context.
2026-04-16 16:36:58 -05:00
james.maes 7519d62b48 feat: bootstrap pnpm in secondary jobs and add configurable coverage summary path
Secondary jobs (code-quality, coverage, security-scan, e2e, sonar, sbom)
now run install_node with package_manager to activate corepack/pnpm.
Coverage check supports glob patterns for monorepo summary aggregation
via test.coverage.summary_path in .munitor.yml.
2026-03-27 18:37:16 -05:00
james.maes 6de8e9998d fix: run prisma generate from package directory for monorepo support 2026-03-27 17:47:23 -05:00
james.maes 4fbae520ee fix: add -U flag to force Maven SNAPSHOT updates
Without -U, Maven uses cached SNAPSHOTs from the CircleCI cache
without checking for newer versions. This caused builds to use stale
SNAPSHOT dependencies even after new SNAPSHOTs were published.
2026-03-27 15:50:40 -05:00
james.maes f560062cc3 test: add pnpm Dockerfile generation tests 2026-03-27 15:39:28 -05:00
james.maes 452a59fe89 fix: update stale npm_build_and_test reference in node_e2e_test description 2026-03-27 15:33:29 -05:00
james.maes 11a142b353 chore: regenerate packed scripts for pnpm support 2026-03-27 15:18:28 -05:00
james.maes c795fbdc12 test: add pnpm fixture and tests for package manager support 2026-03-27 15:11:41 -05:00
james.maes 86b72f4e3b feat: add pnpm Dockerfile variants for node pipelines 2026-03-27 12:02:01 -05:00
james.maes f1d3bf674e feat: support inline commands in test.setup 2026-03-27 12:01:21 -05:00
james.maes facd9691c4 feat: pass package_manager to node_build_and_test in all templates 2026-03-27 11:58:36 -05:00
james.maes 84ceae9e89 feat: thread package_manager through node_build_and_test job 2026-03-27 11:58:11 -05:00
james.maes 270325371e feat: make cache commands package-manager-aware 2026-03-27 11:57:43 -05:00
james.maes c146ff0bb8 feat: make node_test package-manager-aware 2026-03-27 11:57:29 -05:00
james.maes 9f862b1ccd feat: make node_install_deps package-manager-aware with Prisma detection 2026-03-27 11:57:06 -05:00
james.maes 05aac6bbea feat: activate corepack for pnpm/yarn in install_node 2026-03-27 11:56:30 -05:00
james.maes 5550411187 refactor: update tests and docs for node_* rename 2026-03-27 11:54:11 -05:00
james.maes 3fa00dc9a5 refactor: update templates to use node_* job names 2026-03-27 11:45:46 -05:00
james.maes 90bcb49ca9 refactor: rename npm_* jobs to node_* 2026-03-27 11:44:54 -05:00
james.maes 7acb976d16 refactor: rename npm_* commands to node_* 2026-03-27 11:44:03 -05:00
james.maes 9ac1e09a0d refactor: rename npm_* scripts to node_* 2026-03-27 11:43:20 -05:00
james.maes 7527751532 chore: sync develop with main (v0.2.2 fix) 2026-03-27 10:42:32 -05:00
james.maes 70917472e9 chore: backport health check ordering fix to develop
chore: backport health check ordering fix to develop
2026-03-27 10:20:42 -05:00
77 changed files with 4618 additions and 422 deletions

No files matched your search

+2 -5
View File
@@ -15,8 +15,5 @@ node_modules/
.idea/
.vscode/
# Claude Code
.claude/
docs/TODO.md
docs/PLAN-*.md
docs/SESSION-STATE.md
# Claude Code (keep settings.local.json out -- machine-specific permissions)
.claude/settings.local.json
+16
View File
@@ -0,0 +1,16 @@
# Repository instructions
Read `CLAUDE.md`, `CONTRIBUTING.md`, `docs/SESSION-STATE.md`, and `docs/TODO.md`
before substantive work. Munitor is a CircleCI orb: source changes belong under
`src/`, generated packed scripts must remain synchronized, and consumer behavior
must be covered by BATS fixtures and assertions.
Use feature branches targeting `develop`; `main` is release-only. Run `make all`
before opening a pull request and `make validate` when the CircleCI CLI is
available. Publish development snapshots from feature or `develop`; publish
stable orb versions only from semantic version tags on `main`.
For deployment changes, preserve the established contract: repository-owned
Dockerfiles are opt-in, consolidated GitOps paths are repository-relative
`kustomization.yaml` files, and `production_only` prevents non-main write-back.
Never put credentials in manifests, fixtures, logs, or documentation.
+49
View File
@@ -0,0 +1,49 @@
# Munitor
CircleCI orb that generates CI/CD pipeline configurations from a `.munitor.yml` manifest. Supports 8 pipeline types: java-webapp, gradle-webapp, node-api, node-webapp, terraform, validate-cd-repo, sdk-distribution, argocd-apps.
## Key Commands
```bash
make test # Run all tests (BATS)
make pack # Pack orb YAML from src/
make all # lint + pack + test
make validate # Validate packed orb with CircleCI CLI
```
## Project Structure
- `src/commands/` - CircleCI orb commands (YAML)
- `src/jobs/` - CircleCI orb jobs (YAML)
- `src/scripts/` - Shell scripts included by commands/jobs
- `src/scripts/templates/` - Pipeline config templates (`.yml.tpl`)
- `src/scripts/templates/partials/` - Include partials for templates
- `tests/` - BATS test files
- `tests/fixtures/` - Test fixture `.munitor.yml` files
## Issue Tracker
GitHub Issues (org: KofTwentyTwo). Commit format: `feat(#N): description` or `Closes #N` in body.
## CircleCI Orb
- Namespace: `kof22`
- Orb: `kof22/munitor`
- Latest stable: `kof22/munitor@0.3.1`
- Dev snapshot: `kof22/munitor@dev:snapshot`
`0.3.0` includes first-class pnpm support, repository-owned Dockerfiles for
`node-webapp`, and consolidated Kustomize GitOps write-back with optional
production-only behavior and bounded push-race retries. `0.3.1` moves the
generated `node-webapp` runtime to `distroless/nodejs<N>-debian13` (debian12
failed the Trivy gate on OpenSSL CVEs).
## Session Continuity
- Session state: `docs/SESSION-STATE.md`
- Active tasks: `docs/TODO.md`
- Plans: `docs/PLAN-*.md`
Read `AGENTS.md` and `CONTRIBUTING.md` before changing orb behavior. Feature
branches target `develop`; release branches promote to `main`, and stable orb
publication is driven by a `vX.Y.Z` tag.
+1 -1
View File
@@ -66,7 +66,7 @@ tests/fixtures/
g. Validates output YAML
3. CircleCI continuation runs the generated pipeline, which references
munitor orb jobs/commands by name (e.g., munitor/npm_build_and_test)
munitor orb jobs/commands by name (e.g., munitor/node_build_and_test)
```
## Adding a New Module
+26 -18
View File
@@ -20,7 +20,7 @@ This file is identical across all repos:
version: 2.1
setup: true
orbs:
munitor: kof22/munitor@1
munitor: kof22/munitor@0.3
workflows:
setup:
jobs:
@@ -53,7 +53,7 @@ Munitor validates branch names and will fail on non-conforming names.
| Context | Secrets | Used By |
|---------|---------|---------|
| `ghcr` | `GHCR_TOKEN`, `GHCR_USER` | Docker push to GitHub Container Registry |
| `ghcr` | `GHCR_TOKEN`, `GHCR_USERNAME` | Docker push to GitHub Container Registry |
| `github` | `GITHUB_TOKEN` | Git operations, CD repo updates, SBOM, GitHub Releases |
| `sonarcloud` | `SONAR_TOKEN` | SonarCloud analysis |
| `nvd` | `NVD_API_KEY` | OWASP dependency check (Java only, optional) |
@@ -68,7 +68,7 @@ For Node.js/Next.js applications with Docker deployment.
```yaml
pipeline: node-api
orb_version: dev:snapshot
orb_version: "0.3"
image_name: my-app
docker:
registry: ghcr.io/KofTwentyTwo
@@ -81,7 +81,7 @@ contexts:
```yaml
pipeline: node-api
orb_version: dev:snapshot
orb_version: "0.3"
image_name: my-app
node_version: "22" # default: 20
@@ -145,10 +145,11 @@ For Node.js applications that provide their own Dockerfile (unlike `node-api` wh
```yaml
pipeline: node-webapp
orb_version: dev:snapshot
orb_version: "0.3"
image_name: my-webapp
docker:
registry: ghcr.io/KofTwentyTwo
use_repo_dockerfile: true
contexts:
registry: ghcr
github: github
@@ -158,7 +159,7 @@ contexts:
```yaml
pipeline: node-webapp
orb_version: dev:snapshot
orb_version: "0.3"
image_name: my-webapp
node_version: "22" # default: 20
@@ -167,9 +168,13 @@ sonar:
docker:
registry: ghcr.io/KofTwentyTwo
use_repo_dockerfile: true # preserve the repository Dockerfile
cd:
repo: KofTwentyTwo/my-webapp-cd # omit to skip GitOps CD updates
repo: KofTwentyTwo/cluster-gitops # omit to skip GitOps CD updates
format: kustomize
production_only: true # only main writes this production target
path: tenants/my-app/website/kustomization.yaml
env:
release: staging # default: staging (CD target for release/* branches)
@@ -212,7 +217,7 @@ contexts:
sonar: sonarcloud
```
**Dockerfile:** You must provide your own `Dockerfile` in the repo root. Munitor builds it, runs a health check, scans with Trivy, and pushes to the registry. This is the key difference from `node-api`, which auto-generates a Dockerfile.
**Dockerfile:** Set `docker.use_repo_dockerfile: true` and provide `Dockerfile` in the repo root when the application owns its runtime contract. Munitor preserves and builds it, runs a health check, scans with Trivy, and pushes to the registry. Without the option, Munitor retains the legacy generated Dockerfile behavior. Use `node-api` when an always-generated framework Dockerfile is preferred.
### `java-webapp`
@@ -222,7 +227,7 @@ For Java/Maven applications with Docker deployment.
```yaml
pipeline: java-webapp
orb_version: dev:snapshot
orb_version: "0.3"
image_name: my-service
docker:
registry: ghcr.io/KofTwentyTwo
@@ -236,7 +241,7 @@ contexts:
```yaml
pipeline: java-webapp
orb_version: dev:snapshot
orb_version: "0.3"
image_name: my-service
java_version: "21" # default: 21
@@ -282,7 +287,7 @@ For Terraform/Terragrunt infrastructure repos.
```yaml
pipeline: terraform
orb_version: dev:snapshot
orb_version: "0.3"
terraform:
path: terraform/
```
@@ -291,7 +296,7 @@ terraform:
```yaml
pipeline: terraform
orb_version: dev:snapshot
orb_version: "0.3"
terraform:
path: terraform/ # default: terraform/
live_path: terraform/live # default: terraform/live
@@ -313,14 +318,14 @@ For CD/GitOps repositories containing Kubernetes manifests, Kustomize overlays,
```yaml
pipeline: validate-cd-repo
orb_version: dev:snapshot
orb_version: "0.3"
```
**Full config:**
```yaml
pipeline: validate-cd-repo
orb_version: dev:snapshot
orb_version: "0.3"
kustomize:
version: "5.5.0" # default: 5.5.0
@@ -345,14 +350,14 @@ For ArgoCD app-of-apps repositories that use environment directories (`envs/`) i
```yaml
pipeline: argocd-apps
orb_version: dev:snapshot
orb_version: "0.3"
```
**Full config:**
```yaml
pipeline: argocd-apps
orb_version: dev:snapshot
orb_version: "0.3"
kustomize:
version: "5.5.0" # default: 5.5.0
@@ -377,7 +382,7 @@ For SDK packaging and GitHub Releases. Triggered by semver tags (`v1.2.3`).
```yaml
pipeline: sdk-distribution
orb_version: dev:snapshot
orb_version: "0.3"
contexts:
github: github
```
@@ -465,7 +470,10 @@ Tags follow the format `vX.Y.Z` (git tag) and `X.Y.Z` (Docker tag). Tags are imm
| `java_version` | string | `21` | java-webapp | Java major version |
| `sonar.project_key` | string | -- | node-api, node-webapp, java-webapp | SonarCloud project key. Omit to skip |
| `docker.registry` | string | *required** | node-api, node-webapp, java-webapp | Container registry URL |
| `docker.use_repo_dockerfile` | bool | `false` | node-webapp | Preserve and build the repository-root Dockerfile instead of generating one |
| `cd.repo` | string | -- | node-api, node-webapp, java-webapp | GitOps CD repo (`org/repo`). Omit to skip |
| `cd.path` | string | -- | node-api, node-webapp, java-webapp | Explicit repository-relative `kustomization.yaml` path for consolidated GitOps repositories |
| `cd.production_only` | bool | `false` | node-api, node-webapp, java-webapp | Disable CD write-back from develop, staging, and release workflows |
| `cd.env.release` | string | `staging` | node-api, node-webapp, java-webapp | CD target environment for release branches |
| `coverage.min_instruction` | int | `70` | node-api, node-webapp, java-webapp | Minimum coverage percentage |
| `e2e` | bool | `false` | node-api, node-webapp, java-webapp | Enable Playwright E2E tests |
@@ -508,7 +516,7 @@ Default endpoint: `GET /api/health` on port 3000 (node-api, node-webapp) or 8080
## Troubleshooting
**"Missing required fields: orb_version"** -- Add `orb_version` to your `.munitor.yml`. Use `dev:snapshot` for pre-release testing or `1` once a stable release is published.
**"Missing required fields: orb_version"** -- Add `orb_version` to your `.munitor.yml`. Use `dev:snapshot` for unreleased feature testing or `0.3` for the current stable line.
**"munitor_header: command not found"** -- You're using an older orb version. Update to `dev:snapshot` or wait for the next stable release.
+13
View File
@@ -0,0 +1,13 @@
# PLAN: Preserve node-webapp Dockerfiles
## Goal
Allow `node-webapp` consumers to preserve a repository-owned Dockerfile without breaking existing generated-image consumers.
## Steps
1. [x] Add an opt-in `docker.use_repo_dockerfile` manifest field.
2. [x] Fail clearly when an opted-in repository has no root Dockerfile.
3. [x] Add regression coverage and update the public reference.
4. [ ] Pack, validate, test, and publish the development orb.
5. [ ] Verify the Investing in Chester production image pipeline with the new orb.
+25
View File
@@ -0,0 +1,25 @@
# Munitor session state
## Current status
The `develop` branch contains the complete `0.3.0` feature set. PR #6 added
first-class pnpm support and the Obsidian pipeline, PR #8 added opt-in
repository Dockerfile preservation for `node-webapp`, and PR #10 added
consolidated Kustomize GitOps write-back with production-only controls and
bounded push-race retries. Investing in Chester and QRun marketing both proved
the release path through CircleCI, GHCR, `KofTwentyTwo/cluster-gitops`, and
ArgoCD.
## Release state
- Latest stable target: `kof22/munitor@0.3.0`
- Stable consumer pin: `kof22/munitor@0.3`
- Development aliases: `dev:<commit>` and `dev:snapshot`
- Default branch: `develop`; `main` is release-only
- Release tag: `v0.3.0`
## Verification
Run `make all` before release. The stable tag must publish successfully through
CircleCI before consumer repositories move from immutable development pins to
`0.3`.
+41
View File
@@ -0,0 +1,41 @@
# TODO: Munitor
## Pending
- [ ] Follow makers4 and terraform-proxmox-talos on CircleCI when those repos are next active.
- [ ] Adopt supplemental images on an appropriate consumer.
## Completed
- [x] Prove consolidated production write-back with Investing in Chester and QRun marketing.
- [x] Release v0.3.0 and move the verified consumers to the stable 0.3 line.
- [x] Merge repository Dockerfile preservation and consolidated GitOps write-back.
- [x] Validate first-class pnpm support and merge PR #6.
- [x] Add opt-in repository Dockerfile preservation for node-webapp (GH-7)
- [x] Implement first-class pnpm support (GH-5, 17 commits, 219 tests)
- [x] Rename all npm_* orb components to node_* (backward-incompatible, pre-1.0)
- [x] Thread package_manager through full job/command/script chain
- [x] Add corepack activation for pnpm/yarn in install_node
- [x] Add pnpm Dockerfile variants for node-api and node-webapp
- [x] Add Prisma auto-detection after dependency install
- [x] Support inline commands in test.setup
- [x] Bootstrap pnpm in secondary jobs (code-quality, coverage, e2e, security-scan, sonar, sbom)
- [x] Add configurable coverage_summary_path with monorepo glob support
- [x] Publish dev:snapshot with pnpm support
- [x] Implement gradle-webapp pipeline type (20 new files, 7 modified, 605+ tests)
- [x] Fix pack_generate_config.sh to embed gradle templates
- [x] Fix RC009 orb review (extract inline commands to included script)
- [x] Fix SC2155 shellcheck warnings in extract_munitor_vars.sh
- [x] Add JaCoCo diagnostic output to gradle_jacoco.sh
- [x] Merge gradle-webapp PR #2 to develop
- [x] Release v0.2.0 and publish kof22/munitor@0.2.0
- [x] Create GitHub Release for v0.2.0
- [x] Bump Concilium from dev:snapshot to @0.2 (PR #41)
- [x] Bump Website-Backend from @0.1 to @0.2 (PR #44)
- [x] Bump Website-Frontend from @0.1 to @0.2 (PR #6)
- [x] Onboard Concilium as first gradle-webapp consumer
- [x] Create GitHub Releases for v0.1.0, v0.1.1, v0.1.2
- [x] Publish orb kof22/munitor@0.1.2 (overridable org defaults)
- [x] Implement supplemental Docker image support
- [x] All 8 pipeline types implemented and tested
- [x] Full rebrand from Faber to Munitor
@@ -0,0 +1,174 @@
# Spec: First-Class Package Manager Support (Issue #5)
## Goal
Make `package_manager` a first-class config in Munitor that drives behavior across the full Node.js lifecycle: dependency install, test execution, caching, Dockerfile generation, and post-install hooks (Prisma). Rename all `npm_*` orb jobs/commands to `node_*` to reflect multi-PM support. Release as v0.3.0.
## Context
`MUNITOR_PACKAGE_MANAGER` is parsed from `.munitor.yml` and available at config-generation time via envsubst, but no runtime script uses it. All scripts hardcode npm commands. Projects using `package_manager: pnpm` hit `pnpm: command not found` and must work around it with custom test commands.
## Scope
### In scope
- Rename `npm_*` jobs/commands/scripts to `node_*` (except `npm_auth`)
- Thread `package_manager` parameter through job -> command -> script chain
- Corepack activation for pnpm (and yarn) in `install_node.sh`
- Package-manager-aware dependency install, test fallback, cache, Dockerfile generation
- Prisma schema auto-detection and `prisma generate` after install
- Fix `test.setup` to support inline commands (not just file paths)
- Update all templates, packed scripts, and tests
- v0.3.0 release
### Out of scope
- Yarn support beyond basic corepack plumbing (no yarn-specific testing or validation)
- Monorepo-aware Dockerfile generation (user provides custom Dockerfile for complex monorepos)
- Changes to `npm_auth` (works for all PMs via `.npmrc`)
## Design
### 1. Rename Map
Keep `npm_auth` as-is (genuinely npm-registry-specific; `.npmrc` auth works for pnpm too). Everything else:
**Jobs:**
| Old | New |
|---|---|
| `npm_build_and_test` | `node_build_and_test` |
| `npm_code_quality` | `node_code_quality` |
| `npm_coverage` | `node_coverage` |
| `npm_security_scan` | `node_security_scan` |
| `npm_e2e_test` | `node_e2e_test` |
| `npm_sonar_scan` | `node_sonar_scan` |
| `npm_sbom` | `node_sbom` |
**Commands:**
| Old | New |
|---|---|
| `npm_install` | `node_install_deps` |
| `npm_test` | `node_test` |
| `npm_lint` | `node_lint` |
| `npm_audit` | `node_audit` |
| `npm_coverage_check` | `node_coverage_check` |
| `npm_sonar` | `node_sonar` |
| `npm_sbom` (cmd) | `node_sbom` (cmd) |
| `restore_npm_cache` | `restore_node_cache` |
| `save_npm_cache` | `save_node_cache` |
**Scripts:** Same pattern, `.sh` suffix (e.g., `npm_install.sh` -> `node_install_deps.sh`).
### 2. Parameter Threading
Add `package_manager` (type: string, default: `"npm"`) to:
- `node_build_and_test` job
- `install_node` command
- `node_install_deps` command
- `node_test` command
- `restore_node_cache` / `save_node_cache` commands
Templates pass it: `package_manager: "${MUNITOR_PACKAGE_MANAGER}"`.
At runtime, each command sets `MUNITOR_PACKAGE_MANAGER: << parameters.package_manager >>` in the step's `environment` block, making it available to the included script.
### 3. Corepack Activation (`install_node.sh`)
After nvm install/use, when `MUNITOR_PACKAGE_MANAGER != npm`:
1. Run `corepack enable`
2. Detect version from `package.json` with this priority:
- `packageManager` field (e.g., `"pnpm@9.15.9"`) -- corepack-native declaration
- `engines.<pm>` field (e.g., `"engines": {"pnpm": ">=9"}`)
- Fallback: no version specified (corepack uses latest)
3. If version found: `corepack prepare <pm>@<version> --activate`
4. Print version to build output
5. Export PM binary path to `BASH_ENV` so downstream steps have it on PATH
### 4. Dependency Install (`node_install_deps.sh`, was `npm_install.sh`)
Branch on `MUNITOR_PACKAGE_MANAGER`:
**npm (default):** Current behavior unchanged. Check `package-lock.json`, run `npm ci`, lockfile version warning.
**pnpm:** Check `pnpm-lock.yaml` exists (error message should suggest `pnpm install` to create it, not `npm install`). Run `pnpm install --frozen-lockfile`. Note: `pnpm install` at the repo root handles workspaces natively; `pnpm -r` is NOT needed.
After install (all PMs): Prisma auto-detection (see section 7).
### 5. Test Execution (`node_test.sh`, was `npm_test.sh`)
**Custom commands path:** Unchanged. Already runs arbitrary commands via `bash -c`.
**Default fallback (no custom commands):**
- npm: `npx jest` (current behavior)
- pnpm: `pnpm test` (delegates to package.json scripts, where Jest or Vitest is typically configured)
### 6. Cache (`restore_node_cache.yml` / `save_node_cache.yml`)
Use CircleCI `when/equal` conditions on `package_manager` parameter:
**npm:** Checksum `package-lock.json`, cache `~/.npm`.
**pnpm:** Checksum `pnpm-lock.yaml`, cache pnpm store (get path via `pnpm store path` at save time, use `~/.local/share/pnpm/store` as restore default).
Cache key prefix changes per PM to avoid collisions: `npm-v1-` vs `pnpm-v1-`.
### 7. Prisma Auto-Detection
Added to `node_install_deps.sh` after the dependency install step:
1. `find . -path "*/prisma/schema.prisma" -not -path "*/node_modules/*" -not -path "*/dist/*" -not -path "*/build/*"`
2. For each found schema, run `<exec> prisma generate --schema=<path>`:
- npm: `npx prisma generate --schema=<path>`
- pnpm: `pnpm exec prisma generate --schema=<path>`
3. If no schemas found, skip silently (no output).
4. If schemas found but `prisma` is not in dependencies, log a warning and continue (don't fail the build).
This is not pnpm-specific; it helps any monorepo where Prisma's postinstall hook can't find the schema at the default location.
### 8. Dockerfile Generation (`generate_dockerfile.sh`)
Already has `MUNITOR_PACKAGE_MANAGER` via `extract_munitor_vars.sh`. Changes needed in node-api and node-webapp Dockerfile templates:
**npm (default):** No change. `COPY package*.json` / `npm ci` / `npm run build`.
**pnpm:** In builder stage:
```dockerfile
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build
```
Runner stage: strip pnpm (same pattern as stripping npm). For distroless images (node-webapp), pnpm isn't present in the runner anyway since only standalone output is copied.
**Monorepo note:** Generated Dockerfiles assume single-package or standalone-output builds. Complex monorepo setups should provide a custom Dockerfile (existing escape hatch: place a `Dockerfile` in the repo and Munitor skips generation).
### 9. test.setup Fix (`run_test_setup.sh`)
Current behavior: if the value doesn't point to an existing file, exit with "script not found." This silently confuses users who pass command strings.
Fix: if the value is not a file path, treat it as an inline shell command and run it via `bash -c`. Log clearly which mode was used:
- File: `"Running test setup script: ./scripts/setup.sh"`
- Inline: `"Running test setup command: corepack enable && ..."`
This reduces the need for the inline workaround now that pnpm is auto-handled, but prevents future confusion.
### 10. Template Updates
All 4 templates (`node-webapp.yml.tpl`, `node-api.yml.tpl`) and 2 deploy partials (`node-webapp-deploy.yml.tpl`, `node-api-deploy.yml.tpl`):
- Replace all `munitor/npm_*` references with `munitor/node_*`
- Add `package_manager: "${MUNITOR_PACKAGE_MANAGER}"` to `node_build_and_test` invocations
`packed_generate_config.sh` and `packed_generate_dockerfile.sh`: Regenerated from source (these embed templates).
### 11. Test Updates
- `test_generate_config.sh`: Update all `npm_*` assertions to `node_*`. Add pnpm fixture and tests.
- `test_extract_munitor_vars.sh`: Already tests `MUNITOR_PACKAGE_MANAGER`. Add pnpm-specific fixture.
- `test_scripts.sh`: Update script path references. Add tests for pnpm branching in `node_install_deps.sh` and Prisma detection.
- New fixture: `.munitor.yml` with `package_manager: pnpm` for generate_config tests.
### 12. Version and Release
- Version: **0.3.0** (breaking rename, pre-1.0)
- Consumer repos must regenerate `.circleci/config.yml` after bumping orb version
- `generate_config.sh` output automatically uses new job names
File diff suppressed because it is too large. Load diff
+46
View File
@@ -0,0 +1,46 @@
# PLAN: Gradle-Webapp Pipeline Type
## Goal
Add Gradle build support to Munitor so Gradle-based Java projects can use the same quality gates and deployment workflows as Maven projects.
## Design Decisions
1. **Approach A: Full parallel pipeline** - Completely independent pipeline type with its own commands, scripts, jobs, and template. No shared code with Maven.
2. **JAR discovery (C)**: Use `server_module` config field to construct path `{server_module}/build/libs/*.jar`, excluding `-plain.jar`, `-sources.jar`, `-javadoc.jar`.
3. **No version injection (C)**: Skip Gradle version setting entirely. Rely on `calculate_version` + `export_version_vars` for Docker image tagging.
4. **Cache key (B)**: `gradle-wrapper.properties` + `settings.gradle.kts` with `gradle-v1-` fallback.
## Config Shape
```yaml
pipeline: gradle-webapp
orb_version: "0.2"
image_name: concilium
java_version: "21"
server_module: "concilium-server" # optional, for multi-module shadow JAR
docker:
registry: ghcr.io/koftwentytwo
```
## Files Created (19)
- Scripts (6): `gradle_build.sh`, `gradle_test.sh`, `gradle_jacoco.sh`, `gradle_checkstyle.sh`, `gradle_spotbugs.sh`, `gradle_pmd.sh`, `collect_gradle_test_results.sh`
- Commands (8): `gradle_build.yml`, `gradle_test.yml`, `gradle_jacoco.yml`, `gradle_checkstyle.yml`, `gradle_spotbugs.yml`, `gradle_pmd.yml`, `restore_gradle_cache.yml`, `save_gradle_cache.yml`
- Jobs (3): `gradle_build_and_test.yml`, `gradle_code_quality.yml`, `gradle_coverage.yml`
- Templates (2): `gradle-webapp.yml.tpl`, `partials/gradle-webapp-deploy.yml.tpl`
## Files Modified (6+)
- `extract_munitor_vars.sh` - Added `MUNITOR_SERVER_MODULE`, fixed SC2155 warnings
- `generate_config.sh` - Added `gradle-webapp` to case + `server_module` to known keys
- `generate_dockerfile.sh` - Added `gradle-webapp` case with shadow JAR discovery
- `pack_generate_config.sh` - Added gradle templates to embed loops
- Test files (3) - Added gradle-webapp assertions
## Gotchas Discovered
1. **pack_generate_config.sh has hardcoded template lists** - New templates must be added to the for loops manually. Not auto-discovered.
2. **RC009 orb review** - CircleCI orb-tools review fails on inline commands > 64 chars. Must use `<< include() >>` syntax.
3. **Gradle JaCoCo CSV not default** - Gradle's JaCoCo plugin only generates HTML+XML by default. Projects must explicitly enable `csv.required.set(true)`.
4. **orb_version resolution** - `orb_version: "0.1"` in `.munitor.yml` resolves to latest stable `@0.1.x`, not `dev:snapshot`. New features only available via `dev:snapshot` until released.
+120
View File
@@ -0,0 +1,120 @@
# PLAN: Supplemental Images
## Goal
Add support for building, scanning, and pushing supplemental Docker images (e.g., Liquibase migrations, database backup containers) alongside the main application image. Each supplemental image shares the same version tag as the app, gets Trivy-scanned, and can update the CD repo.
## Config Shape
```yaml
# .munitor.yml
supplemental_images:
- name: migrations
# Option A: auto-generate Dockerfile from base + copy
base: liquibase/liquibase:4.31
copy:
- src: db/changelog/
dest: /liquibase/changelog/
cd:
image_key: migrations.image.tag # Helm mode
# image_name: ghcr.io/org/app-migrations # Kustomize (auto-derived if omitted)
- name: db-backup
# Option B: use repo-provided Dockerfile
dockerfile: docker/Dockerfile.backup
cd:
image_key: dbBackup.image.tag
```
Image name derivation: `{image_name}-{name}` (e.g., `me-health-portal-migrations`).
Version: always same as main app (`PROJECT_VERSION`).
Env tag: same as main app (`DOCKER_ENV_TAG`).
## Architecture
```
docker_build_push job steps:
attach_workspace
restore_version_metadata
generate_dockerfile # Main app Dockerfile
docker_build # Build main app image
health_check # Verify main app boots
restore_trivy_cache
run_trivy_image # Scan main app image
save_trivy_cache
docker_push_ghcr # Push main app (establishes registry auth)
build_push_supplemental [NEW] # For each supplemental: generate Dockerfile, build, scan, push
```
CD repo update: `update_cd_repo` receives supplemental_images JSON, updates all tags in a single commit.
## Files Affected
### New Files
| File | Purpose |
|------|---------|
| `src/scripts/build_push_supplemental.sh` | Loop: auto-generate Dockerfile, build, Trivy scan, push each supplemental image |
| `src/commands/build_push_supplemental.yml` | Orb command wrapping the script |
| `tests/fixtures/java-webapp-supplemental.munitor.yml` | Test fixture with supplemental_images config |
### Modified Files
| File | Change |
|------|--------|
| `src/scripts/extract_munitor_vars.sh` | Parse `supplemental_images` array, export `MUNITOR_SUPPLEMENTAL_IMAGES_JSON` + `MUNITOR_SUPPLEMENTAL` flag |
| `src/scripts/generate_config.sh` | Add `SUPPLEMENTAL` to conditional flags, `supplemental_images` to KNOWN_KEYS |
| `src/scripts/update_cd_repo.sh` | Read `SUPPLEMENTAL_IMAGES_JSON`, update all supplemental image tags in same commit |
| `src/commands/update_cd_repo.yml` | Add `supplemental_images` parameter |
| `src/jobs/docker_build_push.yml` | Add `supplemental_images` param + `build_push_supplemental` step |
| `src/scripts/templates/java-webapp.yml.tpl` | `##IF_SUPPLEMENTAL##` blocks on docker_build_push + update_cd_repo (release-candidate + production) |
| `src/scripts/templates/node-api.yml.tpl` | Same |
| `src/scripts/templates/node-webapp.yml.tpl` | Same |
| `src/scripts/templates/partials/java-webapp-deploy.yml.tpl` | `##IF_SUPPLEMENTAL##` blocks on docker_build_push + update_cd_repo |
| `src/scripts/templates/partials/node-api-deploy.yml.tpl` | Same |
| `src/scripts/templates/partials/node-webapp-deploy.yml.tpl` | Same |
| `tests/test_extract_munitor_vars.sh` | Test supplemental_images parsing |
| `tests/test_generate_config.sh` | Test ##IF_SUPPLEMENTAL## rendering |
| `tests/test_scripts.sh` | Test build_push_supplemental.sh structure |
## Design Decisions
**Loop in same job, not separate jobs.** Supplemental images are lightweight (FROM base + COPY). Sequential build in the same executor avoids spinning up more machines and reuses Docker auth + Trivy installation.
**JSON array config.** Variable number of supplemental images (0-N). JSON array parsed with `jq` handles this cleanly. Follows the same pattern as `MUNITOR_SERVICES_JSON`.
**Auto-generate Dockerfiles.** Most supplemental images are trivial. Auto-generation from `base` + `copy` eliminates boilerplate. Users can still provide custom Dockerfiles.
**Single CD commit.** All image tags (app + supplemental) are updated atomically so ArgoCD picks them up together.
**Docker-producing pipelines only.** supplemental_images only applies to java-webapp, node-api, node-webapp. Not terraform, argocd-apps, validate-cd-repo, sdk-distribution.
## Variable Flow
```
.munitor.yml extract_munitor_vars.sh templates
-------------- ----------------------- ---------
supplemental_images: [...] --> MUNITOR_SUPPLEMENTAL="true" --> ##IF_SUPPLEMENTAL##
MUNITOR_SUPPLEMENTAL_IMAGES_JSON --> docker_build_push(supplemental_images=...)
update_cd_repo(supplemental_images=...)
```
## Script Details
### build_push_supplemental.sh
```
For each entry in SUPPLEMENTAL_IMAGES_JSON:
1. Derive image name: {IMAGE_NAME}-{entry.name}
2. If entry.dockerfile exists: use it
Else: auto-generate Dockerfile.{name} from entry.base + entry.copy
3. docker build -f {dockerfile} -t {REGISTRY}/{derived_name}:{VERSION} .
4. If DOCKER_ENV_TAG set: also tag with env tag
5. trivy image --severity HIGH,CRITICAL --exit-code 1 {full_tag}
6. docker push {full_tag}
7. If env tag: docker push {full_tag_env}
```
### update_cd_repo.sh changes
After updating main image tag, if SUPPLEMENTAL_IMAGES_JSON is non-empty:
- Helm: for each entry with cd.image_key, run `yq -i ".{key} = {VERSION}"`
- Kustomize: for each entry, derive image_name as `{CD_IMAGE_NAME}-{name}` (or use cd.image_name), run yq on kustomization.yaml
@@ -0,0 +1,126 @@
## Prompt: Adopting Munitor Supplemental Images for Pre-Rollout Containers
You are helping a development team configure their `.munitor.yml` file to use the **supplemental images** feature in the Munitor CircleCI orb. This feature builds, Trivy-scans, and pushes additional Docker images (sidecar/init containers) alongside the main application image during CI/CD.
### When to use supplemental images
Use this for any container that runs **before or alongside** the main application deployment:
- **Liquibase migrations** — runs DB changelogs before the app starts
- **Database backup** — snapshots the DB before a migration
- **Data seeding** — loads reference data into a fresh environment
- **Schema validation** — validates DB schema compatibility
- Any other init container or Job that needs its own image
### How it works
1. Munitor builds the main app image as usual
2. For each entry in `supplemental_images`, it either:
- **Auto-generates a Dockerfile** from `base` + `copy` directives, OR
- **Uses a provided Dockerfile** from the repo
3. Each supplemental image is built, scanned with Trivy (HIGH/CRITICAL), and pushed to the same registry
4. Image naming: `{image_name}-{supplemental_name}` (e.g., `me-health-portal-migrations`)
5. All images share the same version tag
6. The CD repo update commits all image tags (main + supplemental) atomically in a single commit
### Configuration reference
Add a `supplemental_images` array to your `.munitor.yml`:
```yaml
supplemental_images:
- name: <string> # REQUIRED. Suffix appended to image_name (e.g., "migrations")
base: <string> # Option A: base Docker image (e.g., "liquibase/liquibase:4.31")
copy: # Files to COPY into the auto-generated Dockerfile
- src: <path> # Source path relative to repo root
dest: <path> # Destination path in the container
dockerfile: <string> # Option B: path to a Dockerfile in the repo (mutually exclusive with base)
cd: # CD repo tag update config
image_key: <string> # For Helm: dot-path in values.yaml (e.g., "migrations.image.tag")
image_name: <string> # For Kustomize: image name in kustomization.yaml (defaults to {image_name}-{name})
```
Each entry must have either `base` or `dockerfile`, not both.
### Example: Liquibase migrations
```yaml
pipeline: java-webapp
image_name: me-health-portal
java_version: "21"
docker:
registry: ghcr.io/dmdbrands
cd:
repo: dmdbrands/me-health-portal-cd-pipeline
supplemental_images:
- name: migrations
base: liquibase/liquibase:4.31
copy:
- src: db/changelog/
dest: /liquibase/changelog/
cd:
image_key: migrations.image.tag
```
This produces:
- Main image: `ghcr.io/dmdbrands/me-health-portal:1.2.3`
- Supplemental: `ghcr.io/dmdbrands/me-health-portal-migrations:1.2.3`
- CD repo `values.yaml` gets both `image.tag: "1.2.3"` and `migrations.image.tag: "1.2.3"` in one commit
### Example: Multiple supplemental images
```yaml
supplemental_images:
- name: migrations
base: liquibase/liquibase:4.31
copy:
- src: db/changelog/
dest: /liquibase/changelog/
cd:
image_key: migrations.image.tag
- name: db-backup
dockerfile: docker/Dockerfile.db-backup
cd:
image_key: dbBackup.image.tag
```
### Example: Kustomize CD format
```yaml
supplemental_images:
- name: migrations
base: liquibase/liquibase:4.31
copy:
- src: db/changelog/
dest: /liquibase/changelog/
cd:
image_name: me-health-portal-migrations # optional, defaults to {image_name}-{name}
```
This updates `kustomization.yaml`:
```yaml
images:
- name: me-health-portal
newTag: "1.2.3"
- name: me-health-portal-migrations
newTag: "1.2.3"
```
### Key constraints
- The `name` field becomes part of the Docker image name, so use lowercase alphanumeric + hyphens only
- `base` + `copy` auto-generates a simple `FROM` + `COPY` Dockerfile — for anything more complex (RUN commands, ENV, etc.), use `dockerfile` instead
- All supplemental images are Trivy-scanned with `--severity HIGH,CRITICAL --exit-code 1` — the build fails if vulnerabilities are found
- The feature is available on all pipeline types: `java-webapp`, `node-api`, `node-webapp`
- If `supplemental_images` is not present in `.munitor.yml`, no supplemental processing occurs (fully backward compatible)
### Your task
Given the team's application context, help them:
1. Identify which pre-rollout tasks need their own container image
2. Determine whether each needs `base` + `copy` (simple) or a custom `dockerfile` (complex)
3. Configure the `supplemental_images` section in their `.munitor.yml`
4. Ensure the CD repo's `values.yaml` or `kustomization.yaml` has matching entries for the supplemental image tags
@@ -0,0 +1,22 @@
# TODO: Supplemental Images
## Implementation Checklist
- [ ] **Step 1:** `extract_munitor_vars.sh` - Parse `supplemental_images` array, export `MUNITOR_SUPPLEMENTAL` + `MUNITOR_SUPPLEMENTAL_IMAGES_JSON`, add to exports and `get_envsubst_vars()`
- [ ] **Step 2:** `generate_config.sh` - Add `SUPPLEMENTAL` to conditional flags list, add `supplemental_images` to KNOWN_KEYS
- [ ] **Step 3:** `build_push_supplemental.sh` (NEW) - Loop: auto-generate Dockerfile, build, Trivy scan, push each supplemental image
- [ ] **Step 4:** `build_push_supplemental.yml` (NEW) - Orb command wrapping the script
- [ ] **Step 5:** `docker_build_push.yml` - Add `supplemental_images` param + `build_push_supplemental` step after `docker_push_ghcr`
- [ ] **Step 6:** `update_cd_repo.sh` - Read `SUPPLEMENTAL_IMAGES_JSON`, update supplemental image tags in same commit (both Helm + Kustomize)
- [ ] **Step 7:** `update_cd_repo.yml` - Add `supplemental_images` parameter + env var
- [ ] **Step 8a:** `java-webapp.yml.tpl` - Add `##IF_SUPPLEMENTAL##` blocks on docker_build_push + update_cd_repo (release-candidate + production)
- [ ] **Step 8b:** `node-api.yml.tpl` - Same
- [ ] **Step 8c:** `node-webapp.yml.tpl` - Same
- [ ] **Step 8d:** `java-webapp-deploy.yml.tpl` - Add `##IF_SUPPLEMENTAL##` blocks on docker_build_push + update_cd_repo
- [ ] **Step 8e:** `node-api-deploy.yml.tpl` - Same
- [ ] **Step 8f:** `node-webapp-deploy.yml.tpl` - Same
- [ ] **Step 9a:** `java-webapp-supplemental.munitor.yml` (NEW) - Test fixture
- [ ] **Step 9b:** `test_extract_munitor_vars.sh` - Test supplemental_images parsing (enabled + default disabled)
- [ ] **Step 9c:** `test_generate_config.sh` - Test ##IF_SUPPLEMENTAL## conditional rendering
- [ ] **Step 9d:** `test_scripts.sh` - Test build_push_supplemental.sh structure (helpers, error handling, jq usage)
- [ ] **Step 10:** Run `make all` - Validate everything passes
@@ -0,0 +1,157 @@
# Design: obsidian-plugin Pipeline Type
New Munitor pipeline type for Obsidian plugins. Handles building, testing, quality gating, and releasing TypeScript/esbuild-based Obsidian plugins to GitHub Releases. No Docker, no CD deployment.
## Decisions
| Decision | Choice | Rationale |
|----------|--------|-----------|
| Pipeline naming | `obsidian-plugin` (specific) | Bakes in Obsidian conventions (hardcoded artifacts, no Docker) |
| Release model | Branch-based (release-candidate + production) | Aligns with SDLC Section 6.3 and org branching strategy |
| Quality gates | All node gates supported as conditionals | SAST, coverage, SBOM, security-scan enabled for obsidian-penny |
| Artifact handling | Hardcoded `main.js manifest.json styles.css` | Convention over configuration for Obsidian-specific type |
| Artifact upload mechanism | Extend existing `github_release` job | Adds `release_artifacts` parameter; minimal orb changes |
| Build output persistence | Use `test.commands` override to chain build + test | Works today with no orb changes; dedicated `build_command` can come later |
## Template Structure
File: `src/scripts/templates/obsidian-plugin.yml.tpl`
Four workflows matching the SDLC branch model:
### pr-checks (feature/\*, hotfix/\*)
Full quality pipeline. Jobs:
- `node_build_and_test` (npm install + build + test via `test.commands`)
- `secrets_scan`
- `sast_scan` (conditional: `##IF_SAST##`)
- `node_code_quality` (requires: build-and-test)
- `node_coverage` (requires: build-and-test)
- `node_security_scan` (requires: build-and-test)
- `node_e2e_test` (conditional: `##IF_E2E##`, requires: build-and-test)
### develop (develop branch)
Smoke check only. Jobs:
- `node_build_and_test`
No deploy partials, no Docker, no CD. This differs from node-api/node-webapp which use `##INCLUDE_DEPLOY##` for develop.
### release-candidate (release/\*)
Full quality pipeline gated before release. Jobs:
- All pr-checks jobs
- `node_sonar_scan` (conditional: `##IF_SONAR##`)
- `node_sbom` (conditional: `##IF_SBOM##`, gated on quality gates)
- `github_release` with `release_artifacts: "main.js manifest.json styles.css"` (gated on quality gates, pre-release via RC version detection)
The `github_release` job requires all quality gate jobs. No Docker gate since there is no Docker job.
### production (main)
Fast-path promotion (quality gates already passed on release/\*). Jobs:
- `node_build_and_test`
- `github_release` with `release_artifacts: "main.js manifest.json styles.css"` (requires: build-and-test, full release)
## Changes to `github_release` Job/Script
### `src/jobs/github_release.yml`
Add optional parameter:
```yaml
parameters:
release_artifacts:
type: string
default: ""
description: "Space-separated list of files to upload to the GitHub Release"
```
Pass to script as `MUNITOR_RELEASE_ARTIFACTS` environment variable.
### `src/scripts/github_release.sh`
After the `gh release create` block, add artifact upload:
```bash
if [[ -n "${MUNITOR_RELEASE_ARTIFACTS:-}" ]]; then
echo "Uploading release artifacts: ${MUNITOR_RELEASE_ARTIFACTS}"
# shellcheck disable=SC2086
gh release upload "${TAG}" ${MUNITOR_RELEASE_ARTIFACTS}
fi
```
Uses `gh release upload` (separate from create) so it works whether the release was just created or already existed. Backward compatible -- existing pipelines pass no artifacts.
## `.munitor.yml` Schema
obsidian-penny configuration:
```yaml
pipeline: obsidian-plugin
orb_version: "0.3"
node_version: "20"
sast: true
sbom: true
test:
commands: ["npm run build", "npm test"]
coverage:
tool: vitest
command: npx vitest run --coverage
contexts:
github: github
```
No new variables in `extract_munitor_vars.sh`. The template uses a subset of existing `MUNITOR_*` variables: `NODE_VERSION`, `PACKAGE_MANAGER`, `SAST`, `SAST_FAIL_ON_FINDINGS`, `SBOM`, `COVERAGE_MIN`, `COVERAGE_TOOL`, `COVERAGE_COMMAND`, `CONTEXT_GITHUB`, and the node feature flags.
### `generate_config.sh` validation
Add `obsidian-plugin` to the pipeline type case statement. Required: `contexts.github`. Not required: `image_name`, `docker.registry`, `cd.repo`.
### `packed_generate_config.sh`
Embed `obsidian-plugin.yml.tpl` in the packed script using the same heredoc pattern as other templates.
## Testing
### Test fixture
`tests/fixtures/obsidian-plugin.munitor.yml` -- mirrors the schema above.
### `tests/test_generate_config.sh`
Positive tests (patterns present):
- Valid YAML output
- All 4 workflows: `pr-checks`, `develop`, `release-candidate`, `production`
- `build-and-test` in all workflows
- `secrets-scan` and `sast-scan` in pr-checks and release-candidate
- `github-release` in release-candidate and production
- `release_artifacts: "main.js manifest.json styles.css"` on github-release jobs
- `security-scan` present
- `sbom` present
- Correct branch filters
Negative tests (patterns absent):
- No `docker-build-push`
- No `update-cd-repo`
- No `image_name` or `registry` references
- No `INCLUDE_DEPLOY` partials
### `tests/test_scripts.sh`
- `github_release.sh` with `MUNITOR_RELEASE_ARTIFACTS` set -- verify upload behavior
- `github_release.sh` with `MUNITOR_RELEASE_ARTIFACTS` empty -- verify no upload (backward compat)
## Consumer Repo Changes (obsidian-penny, post-release)
1. Create `.munitor.yml` (schema above)
2. Bump `.circleci/config.yml` orb version to new release
3. Remove `.github/workflows/release.yml` (redundant)
4. Verify workspace persistence delivers `main.js` to `github_release` job
## Not Changed
- `extract_munitor_vars.sh` -- no new variables needed
- `node_build_and_test` job -- works as-is with `test.commands` override
- Other pipeline templates -- no modifications
- `sdk-distribution.yml.tpl` -- remains tag-based, separate use case
+1 -1
View File
@@ -39,7 +39,7 @@ echo "Packing generate_config.sh..."
echo ''
# Embed each template
for tpl in java-webapp gradle-webapp node-api node-webapp terraform sdk-distribution validate-cd-repo argocd-apps; do
for tpl in java-webapp gradle-webapp node-api node-webapp terraform sdk-distribution validate-cd-repo argocd-apps obsidian-plugin; do
echo "cat > /tmp/munitor/templates/${tpl}.yml.tpl << 'MUNITOR_TPL_EOF'"
cat "${SRC}/templates/${tpl}.yml.tpl"
echo 'MUNITOR_TPL_EOF'
+8
View File
@@ -1,7 +1,15 @@
description: >
Create an annotated git tag and GitHub Release using the version
calculated by GitVersion. Skips if tag or release already exists.
Optionally uploads release artifacts to the created release.
parameters:
release_artifacts:
type: string
default: ""
description: "Space-separated list of files to upload to the GitHub Release."
steps:
- run:
name: Create GitHub Release
environment:
MUNITOR_RELEASE_ARTIFACTS: << parameters.release_artifacts >>
command: << include(scripts/github_release.sh) >>
+5
View File
@@ -5,9 +5,14 @@ parameters:
type: string
default: "20"
description: Node.js major version to install (e.g., 20, 22).
package_manager:
type: string
default: "npm"
description: Package manager to use (npm, pnpm, yarn).
steps:
- run:
name: Install Node.js << parameters.version >>
environment:
NODE_VERSION: << parameters.version >>
MUNITOR_PACKAGE_MANAGER: << parameters.package_manager >>
command: << include(scripts/install_node.sh) >>
+6
View File
@@ -0,0 +1,6 @@
description: >
Run a security audit to check for known vulnerabilities in dependencies.
steps:
- run:
name: Security audit
command: << include(scripts/node_audit.sh) >>
@@ -14,6 +14,12 @@ parameters:
type: string
default: ""
description: Custom command to generate coverage report before threshold check.
coverage_summary_path:
type: string
default: "coverage/coverage-summary.json"
description: >
Path to coverage-summary.json. Supports glob patterns for monorepos
(e.g., packages/*/coverage/coverage-summary.json).
steps:
- run:
name: Coverage threshold check
@@ -21,4 +27,5 @@ steps:
MIN_COVERAGE: << parameters.min_coverage >>
MUNITOR_COVERAGE_TOOL: << parameters.coverage_tool >>
MUNITOR_COVERAGE_COMMAND: << parameters.coverage_command >>
command: << include(scripts/npm_coverage_check.sh) >>
MUNITOR_COVERAGE_SUMMARY_PATH: << parameters.coverage_summary_path >>
command: << include(scripts/node_coverage_check.sh) >>
+13
View File
@@ -0,0 +1,13 @@
description: >
Install project dependencies using the configured package manager.
parameters:
package_manager:
type: string
default: "npm"
description: Package manager to use (npm, pnpm, yarn).
steps:
- run:
name: Install dependencies
environment:
MUNITOR_PACKAGE_MANAGER: << parameters.package_manager >>
command: << include(scripts/node_install_deps.sh) >>
@@ -3,4 +3,4 @@ description: >
steps:
- run:
name: ESLint
command: << include(scripts/npm_lint.sh) >>
command: << include(scripts/node_lint.sh) >>
+6
View File
@@ -0,0 +1,6 @@
description: >
Generate a CycloneDX SBOM from a Node.js project.
steps:
- run:
name: Generate SBOM
command: << include(scripts/node_sbom.sh) >>
@@ -19,4 +19,4 @@ steps:
SONAR_PROJECT_KEY: << parameters.sonar_project_key >>
SONAR_ORG: << parameters.sonar_org >>
SONAR_TOKEN_VAR: << parameters.sonar_token_env >>
command: << include(scripts/npm_sonar.sh) >>
command: << include(scripts/node_sonar.sh) >>
@@ -1,17 +1,22 @@
description: >
Run tests with CI flags, coverage, and JUnit reporter. Supports custom
test commands via JSON array or falls back to Jest defaults.
test commands via JSON array or falls back to package manager defaults.
parameters:
test_commands:
type: string
default: ""
description: JSON array of custom test commands to run instead of Jest.
package_manager:
type: string
default: "npm"
description: Package manager to use (npm, pnpm, yarn).
steps:
- run:
name: npm test
name: Run tests
environment:
MUNITOR_TEST_COMMANDS_JSON: << parameters.test_commands >>
command: << include(scripts/npm_test.sh) >>
MUNITOR_PACKAGE_MANAGER: << parameters.package_manager >>
command: << include(scripts/node_test.sh) >>
- store_test_results:
path: reports/junit
- store_artifacts:
-6
View File
@@ -1,6 +0,0 @@
description: >
Run npm audit to check for known vulnerabilities in dependencies.
steps:
- run:
name: npm audit
command: << include(scripts/npm_audit.sh) >>
-6
View File
@@ -1,6 +0,0 @@
description: >
Run npm ci for a clean, reproducible dependency install.
steps:
- run:
name: npm ci
command: << include(scripts/npm_install.sh) >>
-6
View File
@@ -1,6 +0,0 @@
description: >
Generate a CycloneDX SBOM from an npm project.
steps:
- run:
name: Generate SBOM (npm)
command: << include(scripts/npm_sbom.sh) >>
+24
View File
@@ -0,0 +1,24 @@
description: >
Restore the dependency cache based on lockfile checksum.
parameters:
package_manager:
type: string
default: "npm"
description: Package manager (npm, pnpm, yarn).
steps:
- when:
condition:
equal: ["npm", << parameters.package_manager >>]
steps:
- restore_cache:
keys:
- npm-v1-{{ checksum "package-lock.json" }}
- npm-v1-
- when:
condition:
equal: ["pnpm", << parameters.package_manager >>]
steps:
- restore_cache:
keys:
- pnpm-v1-{{ checksum "pnpm-lock.yaml" }}
- pnpm-v1-
-7
View File
@@ -1,7 +0,0 @@
description: >
Restore the npm cache based on package-lock.json checksum.
steps:
- restore_cache:
keys:
- npm-v1-{{ checksum "package-lock.json" }}
- npm-v1-
+24
View File
@@ -0,0 +1,24 @@
description: >
Save the dependency cache for future builds.
parameters:
package_manager:
type: string
default: "npm"
description: Package manager (npm, pnpm, yarn).
steps:
- when:
condition:
equal: ["npm", << parameters.package_manager >>]
steps:
- save_cache:
key: npm-v1-{{ checksum "package-lock.json" }}
paths:
- ~/.npm
- when:
condition:
equal: ["pnpm", << parameters.package_manager >>]
steps:
- save_cache:
key: pnpm-v1-{{ checksum "pnpm-lock.yaml" }}
paths:
- ~/.local/share/pnpm/store
-7
View File
@@ -1,7 +0,0 @@
description: >
Save the npm cache for future builds.
steps:
- save_cache:
key: npm-v1-{{ checksum "package-lock.json" }}
paths:
- ~/.npm
+7
View File
@@ -30,6 +30,12 @@ parameters:
description: >
Full image name for Kustomize repos (e.g., ghcr.io/KofTwentyTwo/my-app).
Used to select the correct entry in the images[] array.
cd_path:
type: string
default: ""
description: >
Explicit repository-relative kustomization.yaml path. When omitted,
Kustomize mode uses overlays/<environment>/kustomization.yaml.
ci_git_email:
type: string
default: ""
@@ -54,6 +60,7 @@ steps:
IMAGE_KEY: << parameters.image_key >>
CD_FORMAT: << parameters.cd_format >>
CD_IMAGE_NAME: << parameters.cd_image_name >>
CD_PATH: << parameters.cd_path >>
CI_GIT_EMAIL: << parameters.ci_git_email >>
CI_GIT_NAME: << parameters.ci_git_name >>
SUPPLEMENTAL_IMAGES_JSON: << parameters.supplemental_images >>
+8 -1
View File
@@ -1,6 +1,12 @@
description: >
Create an annotated git tag and GitHub Release on main branch merges.
Uses PROJECT_VERSION from GitVersion (persisted by the build job).
Optionally uploads release artifacts (e.g., main.js, manifest.json).
parameters:
release_artifacts:
type: string
default: ""
description: "Space-separated list of files to upload to the GitHub Release."
executor: base
steps:
- checkout
@@ -10,4 +16,5 @@ steps:
name: Restore version metadata
command: <<include(scripts/restore_version_metadata.sh)>>
- install_gh
- github_release
- github_release:
release_artifacts: << parameters.release_artifacts >>
@@ -1,12 +1,16 @@
description: >
Full Node.js build and test job. Installs Node.js, restores cache, runs
npm ci, calculates version, tests with coverage, saves cache, and persists
dependency install, calculates version, tests with coverage, saves cache, and persists
the workspace for downstream jobs.
parameters:
node_version:
type: string
default: "20"
description: Node.js major version to install.
package_manager:
type: string
default: "npm"
description: Package manager to use (npm, pnpm, yarn).
npm_auth:
type: boolean
default: false
@@ -46,14 +50,17 @@ steps:
- checkout
- install_node:
version: << parameters.node_version >>
package_manager: << parameters.package_manager >>
- when:
condition: << parameters.npm_auth >>
steps:
- npm_auth:
npm_scopes: << parameters.npm_scopes >>
npm_default_scope: << parameters.npm_default_scope >>
- restore_npm_cache
- npm_install
- restore_node_cache:
package_manager: << parameters.package_manager >>
- node_install_deps:
package_manager: << parameters.package_manager >>
- when:
condition: << parameters.services >>
steps:
@@ -69,11 +76,13 @@ steps:
- run:
name: Persist version metadata
command: <<include(scripts/persist_version_metadata.sh)>>
- npm_test:
- node_test:
test_commands: << parameters.test_commands >>
package_manager: << parameters.package_manager >>
- store_test_results:
path: reports/junit
- save_npm_cache
- save_node_cache:
package_manager: << parameters.package_manager >>
- persist_to_workspace:
root: .
paths:
@@ -1,7 +1,15 @@
description: >
Run ESLint code quality checks.
Requires workspace from npm_build_and_test.
Requires workspace from node_build_and_test.
parameters:
node_version:
type: string
default: "20"
description: Node.js major version to install.
package_manager:
type: string
default: "npm"
description: Package manager (npm, pnpm, yarn).
resource_class:
type: enum
enum:
@@ -15,7 +23,10 @@ executor:
steps:
- attach_workspace:
at: .
- npm_lint
- install_node:
version: << parameters.node_version >>
package_manager: << parameters.package_manager >>
- node_lint
- store_artifacts:
path: /tmp/eslint-report.json
destination: eslint-report.json
@@ -1,7 +1,15 @@
description: >
Enforce minimum coverage threshold from coverage output.
Requires workspace from npm_build_and_test.
Requires workspace from node_build_and_test.
parameters:
node_version:
type: string
default: "20"
description: Node.js major version to install.
package_manager:
type: string
default: "npm"
description: Package manager (npm, pnpm, yarn).
min_coverage:
type: string
default: "70"
@@ -14,6 +22,11 @@ parameters:
type: string
default: ""
description: Custom command to generate coverage report before threshold check.
coverage_summary_path:
type: string
default: "coverage/coverage-summary.json"
description: >
Path to coverage-summary.json. Supports glob patterns for monorepos.
resource_class:
type: enum
enum:
@@ -27,10 +40,14 @@ executor:
steps:
- attach_workspace:
at: .
- npm_coverage_check:
- install_node:
version: << parameters.node_version >>
package_manager: << parameters.package_manager >>
- node_coverage_check:
min_coverage: << parameters.min_coverage >>
coverage_tool: << parameters.coverage_tool >>
coverage_command: << parameters.coverage_command >>
coverage_summary_path: << parameters.coverage_summary_path >>
- store_artifacts:
path: coverage
destination: coverage-report
@@ -1,6 +1,14 @@
description: >
Run Playwright E2E tests. Requires workspace from npm_build_and_test.
Run Playwright E2E tests. Requires workspace from node_build_and_test.
parameters:
node_version:
type: string
default: "20"
description: Node.js major version to install.
package_manager:
type: string
default: "npm"
description: Package manager (npm, pnpm, yarn).
test_dir:
type: string
default: e2e
@@ -18,6 +26,9 @@ executor:
steps:
- attach_workspace:
at: .
- install_node:
version: << parameters.node_version >>
package_manager: << parameters.package_manager >>
- run_e2e_playwright:
test_dir: << parameters.test_dir >>
- store_artifacts:
@@ -1,11 +1,15 @@
description: >
Generate SBOM and submit dependency graph for Node.js projects.
Requires workspace from npm_build_and_test.
Requires workspace from node_build_and_test.
parameters:
node_version:
type: string
default: "20"
description: Node.js major version to install.
package_manager:
type: string
default: "npm"
description: Package manager (npm, pnpm, yarn).
resource_class:
type: enum
enum:
@@ -21,7 +25,8 @@ steps:
at: .
- install_node:
version: << parameters.node_version >>
- npm_sbom
package_manager: << parameters.package_manager >>
- node_sbom
- submit_dependency_graph:
sbom_path: bom.json
detector_name: CycloneDX npm
@@ -1,7 +1,15 @@
description: >
Run npm audit and Trivy filesystem scan.
Requires workspace from npm_build_and_test.
Requires workspace from node_build_and_test.
parameters:
node_version:
type: string
default: "20"
description: Node.js major version to install.
package_manager:
type: string
default: "npm"
description: Package manager (npm, pnpm, yarn).
resource_class:
type: enum
enum:
@@ -15,7 +23,10 @@ executor:
steps:
- attach_workspace:
at: .
- npm_audit
- install_node:
version: << parameters.node_version >>
package_manager: << parameters.package_manager >>
- node_audit
- restore_trivy_cache
- run_trivy
- save_trivy_cache
@@ -1,11 +1,15 @@
description: >
Run SonarCloud analysis for Node.js projects. Attaches workspace from
npm_build_and_test and uses the sonar-scanner CLI with bundled JRE.
node_build_and_test and uses the sonar-scanner CLI with bundled JRE.
parameters:
node_version:
type: string
default: "20"
description: Node.js major version to install.
package_manager:
type: string
default: "npm"
description: Package manager (npm, pnpm, yarn).
sonar_project_key:
type: string
description: SonarCloud project key.
@@ -29,6 +33,7 @@ steps:
at: .
- install_node:
version: << parameters.node_version >>
- npm_sonar:
package_manager: << parameters.package_manager >>
- node_sonar:
sonar_project_key: << parameters.sonar_project_key >>
sonar_org: << parameters.sonar_org >>
+5
View File
@@ -24,6 +24,10 @@ parameters:
type: string
default: ""
description: Full image name for Kustomize repos.
cd_path:
type: string
default: ""
description: Explicit repository-relative kustomization.yaml path.
ci_git_email:
type: string
default: ""
@@ -52,6 +56,7 @@ steps:
image_key: << parameters.image_key >>
cd_format: << parameters.cd_format >>
cd_image_name: << parameters.cd_image_name >>
cd_path: << parameters.cd_path >>
ci_git_email: << parameters.ci_git_email >>
ci_git_name: << parameters.ci_git_name >>
supplemental_images: << parameters.supplemental_images >>
+13 -4
View File
@@ -38,6 +38,8 @@ extract_munitor_vars() {
fi
MUNITOR_CD_REPO=$(yq '.cd.repo // ""' "${config_file}")
MUNITOR_CD_FORMAT=$(yq '.cd.format // "helm"' "${config_file}")
MUNITOR_CD_PATH=$(yq '.cd.path // ""' "${config_file}")
MUNITOR_CD_PRODUCTION_ONLY=$(yq '.cd.production_only // false' "${config_file}")
MUNITOR_CD_ENV_DEVELOP=$(yq '.cd.env.develop // "develop"' "${config_file}")
MUNITOR_CD_ENV_STAGING=$(yq '.cd.env.staging // "staging"' "${config_file}")
MUNITOR_CD_ENV_PROD=$(yq '.cd.env.prod // "prod"' "${config_file}")
@@ -96,6 +98,7 @@ extract_munitor_vars() {
if [[ -n "${MUNITOR_TEST_COVERAGE_MIN}" ]]; then
MUNITOR_COVERAGE_MIN="${MUNITOR_TEST_COVERAGE_MIN}"
fi
MUNITOR_COVERAGE_SUMMARY_PATH=$(yq '.test.coverage.summary_path // "coverage/coverage-summary.json"' "${config_file}")
# Terraform pipeline settings
MUNITOR_TF_PATH=$(yq '.terraform.path // "terraform/"' "${config_file}")
@@ -193,8 +196,14 @@ extract_munitor_vars() {
if [[ -n "${MUNITOR_CD_REPO}" ]]; then
MUNITOR_CD="true"
if [[ "${MUNITOR_CD_PRODUCTION_ONLY}" == "true" ]]; then
MUNITOR_CD_NON_PROD="false"
else
MUNITOR_CD_NON_PROD="true"
fi
else
MUNITOR_CD="false"
MUNITOR_CD_NON_PROD="false"
fi
if [[ -n "${MUNITOR_CONTEXT_NVD}" ]]; then
@@ -205,7 +214,7 @@ extract_munitor_vars() {
# Export all variables
export MUNITOR_PIPELINE MUNITOR_ORB_VERSION MUNITOR_IMAGE_NAME MUNITOR_JAVA_VERSION MUNITOR_NODE_VERSION MUNITOR_SERVER_MODULE
export MUNITOR_SONAR_PROJECT_KEY MUNITOR_DOCKER_REGISTRY MUNITOR_CD_REPO MUNITOR_CD_FORMAT
export MUNITOR_SONAR_PROJECT_KEY MUNITOR_DOCKER_REGISTRY MUNITOR_CD_REPO MUNITOR_CD_FORMAT MUNITOR_CD_PATH MUNITOR_CD_PRODUCTION_ONLY
export MUNITOR_CD_ENV_DEVELOP MUNITOR_CD_ENV_STAGING MUNITOR_CD_ENV_PROD MUNITOR_CD_ENV_RELEASE
export MUNITOR_COVERAGE_MIN MUNITOR_E2E MUNITOR_SBOM MUNITOR_OWASP
export MUNITOR_CONTEXT_REGISTRY MUNITOR_CONTEXT_GITHUB MUNITOR_CONTEXT_SONAR MUNITOR_CONTEXT_NVD
@@ -214,7 +223,7 @@ extract_munitor_vars() {
export MUNITOR_SERVICES MUNITOR_SERVICES_JSON
export MUNITOR_TEST_SETUP MUNITOR_TEST_SETUP_SCRIPT
export MUNITOR_CUSTOM_TEST MUNITOR_TEST_COMMANDS_JSON
export MUNITOR_COVERAGE_TOOL MUNITOR_COVERAGE_CMD MUNITOR_COVERAGE_COMMAND
export MUNITOR_COVERAGE_TOOL MUNITOR_COVERAGE_CMD MUNITOR_COVERAGE_COMMAND MUNITOR_COVERAGE_SUMMARY_PATH
export MUNITOR_TF_PATH MUNITOR_TF_LIVE_PATH MUNITOR_TF_ENVIRONMENTS MUNITOR_CHECKOV_SKIP MUNITOR_SAST MUNITOR_SAST_FAIL_ON_FINDINGS
export MUNITOR_KUSTOMIZE_VERSION MUNITOR_KUSTOMIZE_BASE_PATH MUNITOR_KUSTOMIZE_LOAD_RESTRICTOR
export MUNITOR_KUSTOMIZE_SCAN_OVERLAY MUNITOR_KUSTOMIZE_OVERLAYS MUNITOR_KUSTOMIZE_OVERLAY_DIR
@@ -223,11 +232,11 @@ extract_munitor_vars() {
export MUNITOR_CI_EMAIL MUNITOR_CI_NAME MUNITOR_NPM_DEFAULT_SCOPE MUNITOR_ORB_SLUG
export MUNITOR_HEALTH_PATH MUNITOR_HEALTH_PORT MUNITOR_HEALTH_DB MUNITOR_HEALTH_MIGRATIONS MUNITOR_HEALTH_SMOKE_PATHS
export MUNITOR_SUPPLEMENTAL MUNITOR_SUPPLEMENTAL_IMAGES_JSON
export MUNITOR_SONAR MUNITOR_CD MUNITOR_NVD
export MUNITOR_SONAR MUNITOR_CD MUNITOR_CD_NON_PROD MUNITOR_NVD
}
# Build the envsubst variable list
get_envsubst_vars() {
# shellcheck disable=SC2016
echo '${MUNITOR_PIPELINE} ${MUNITOR_ORB_VERSION} ${MUNITOR_IMAGE_NAME} ${MUNITOR_JAVA_VERSION} ${MUNITOR_NODE_VERSION} ${MUNITOR_SONAR_PROJECT_KEY} ${MUNITOR_DOCKER_REGISTRY} ${MUNITOR_CD_REPO} ${MUNITOR_CD_FORMAT} ${MUNITOR_CD_ENV_DEVELOP} ${MUNITOR_CD_ENV_STAGING} ${MUNITOR_CD_ENV_PROD} ${MUNITOR_CD_ENV_RELEASE} ${MUNITOR_COVERAGE_MIN} ${MUNITOR_E2E} ${MUNITOR_SBOM} ${MUNITOR_OWASP} ${MUNITOR_CONTEXT_REGISTRY} ${MUNITOR_CONTEXT_GITHUB} ${MUNITOR_CONTEXT_SONAR} ${MUNITOR_CONTEXT_NVD} ${MUNITOR_NPM_AUTH} ${MUNITOR_NPM_SCOPES} ${MUNITOR_SERVICES_JSON} ${MUNITOR_TEST_SETUP_SCRIPT} ${MUNITOR_TEST_COMMANDS_JSON} ${MUNITOR_COVERAGE_TOOL} ${MUNITOR_COVERAGE_COMMAND} ${MUNITOR_GITHUB_RELEASE} ${MUNITOR_SAST} ${MUNITOR_SAST_FAIL_ON_FINDINGS} ${MUNITOR_HEALTH_PATH} ${MUNITOR_HEALTH_PORT} ${MUNITOR_HEALTH_DB} ${MUNITOR_HEALTH_MIGRATIONS} ${MUNITOR_HEALTH_SMOKE_PATHS} ${MUNITOR_TF_PATH} ${MUNITOR_TF_LIVE_PATH} ${MUNITOR_TF_ENVIRONMENTS} ${MUNITOR_CHECKOV_SKIP} ${MUNITOR_KUSTOMIZE_VERSION} ${MUNITOR_KUSTOMIZE_BASE_PATH} ${MUNITOR_KUSTOMIZE_LOAD_RESTRICTOR} ${MUNITOR_KUSTOMIZE_SCAN_OVERLAY} ${MUNITOR_KUSTOMIZE_OVERLAYS} ${MUNITOR_KUSTOMIZE_OVERLAY_DIR} ${MUNITOR_KUBE_LINTER_CONFIG} ${MUNITOR_YAMLLINT_PATHS} ${MUNITOR_CI_EMAIL} ${MUNITOR_CI_NAME} ${MUNITOR_NPM_DEFAULT_SCOPE} ${MUNITOR_ORB_SLUG} ${MUNITOR_PACKAGE_MANAGER} ${MUNITOR_SUPPLEMENTAL_IMAGES_JSON} ${MUNITOR_SERVER_MODULE}'
echo '${MUNITOR_PIPELINE} ${MUNITOR_ORB_VERSION} ${MUNITOR_IMAGE_NAME} ${MUNITOR_JAVA_VERSION} ${MUNITOR_NODE_VERSION} ${MUNITOR_SONAR_PROJECT_KEY} ${MUNITOR_DOCKER_REGISTRY} ${MUNITOR_CD_REPO} ${MUNITOR_CD_FORMAT} ${MUNITOR_CD_PATH} ${MUNITOR_CD_ENV_DEVELOP} ${MUNITOR_CD_ENV_STAGING} ${MUNITOR_CD_ENV_PROD} ${MUNITOR_CD_ENV_RELEASE} ${MUNITOR_COVERAGE_MIN} ${MUNITOR_E2E} ${MUNITOR_SBOM} ${MUNITOR_OWASP} ${MUNITOR_CONTEXT_REGISTRY} ${MUNITOR_CONTEXT_GITHUB} ${MUNITOR_CONTEXT_SONAR} ${MUNITOR_CONTEXT_NVD} ${MUNITOR_NPM_AUTH} ${MUNITOR_NPM_SCOPES} ${MUNITOR_SERVICES_JSON} ${MUNITOR_TEST_SETUP_SCRIPT} ${MUNITOR_TEST_COMMANDS_JSON} ${MUNITOR_COVERAGE_TOOL} ${MUNITOR_COVERAGE_COMMAND} ${MUNITOR_GITHUB_RELEASE} ${MUNITOR_SAST} ${MUNITOR_SAST_FAIL_ON_FINDINGS} ${MUNITOR_HEALTH_PATH} ${MUNITOR_HEALTH_PORT} ${MUNITOR_HEALTH_DB} ${MUNITOR_HEALTH_MIGRATIONS} ${MUNITOR_HEALTH_SMOKE_PATHS} ${MUNITOR_TF_PATH} ${MUNITOR_TF_LIVE_PATH} ${MUNITOR_TF_ENVIRONMENTS} ${MUNITOR_CHECKOV_SKIP} ${MUNITOR_KUSTOMIZE_VERSION} ${MUNITOR_KUSTOMIZE_BASE_PATH} ${MUNITOR_KUSTOMIZE_LOAD_RESTRICTOR} ${MUNITOR_KUSTOMIZE_SCAN_OVERLAY} ${MUNITOR_KUSTOMIZE_OVERLAYS} ${MUNITOR_KUSTOMIZE_OVERLAY_DIR} ${MUNITOR_KUBE_LINTER_CONFIG} ${MUNITOR_YAMLLINT_PATHS} ${MUNITOR_CI_EMAIL} ${MUNITOR_CI_NAME} ${MUNITOR_NPM_DEFAULT_SCOPE} ${MUNITOR_ORB_SLUG} ${MUNITOR_PACKAGE_MANAGER} ${MUNITOR_SUPPLEMENTAL_IMAGES_JSON} ${MUNITOR_SERVER_MODULE} ${MUNITOR_COVERAGE_SUMMARY_PATH}'
}
+5 -2
View File
@@ -2,7 +2,7 @@
set -euo pipefail
CONFIG_FILE="${MUNITOR_CONFIG:-.munitor.yml}"
OUTPUT_FILE="/tmp/generated-config.yml"
OUTPUT_FILE="${MUNITOR_OUTPUT_FILE:-/tmp/generated-config.yml}"
# When run via CircleCI << include() >>, BASH_SOURCE is empty.
# Fall back to MUNITOR_SCRIPT_DIR set by the command YAML.
@@ -80,6 +80,9 @@ validate_required_fields() {
validate-cd-repo|argocd-apps)
# No additional required fields beyond orb_version
;;
obsidian-plugin)
[[ -z "$(yq '.contexts.github // ""' "${config}")" ]] && missing+=("contexts.github")
;;
esac
if [[ ${#missing[@]} -gt 0 ]]; then
@@ -198,7 +201,7 @@ process_conditionals() {
cp "${input}" "${tmpfile}"
# Process each conditional flag
for flag in E2E SBOM SONAR NPM_AUTH SERVICES TEST_SETUP CUSTOM_TEST COVERAGE_CMD GITHUB_RELEASE SAST CD OWASP NVD SUPPLEMENTAL; do
for flag in E2E SBOM SONAR NPM_AUTH SERVICES TEST_SETUP CUSTOM_TEST COVERAGE_CMD GITHUB_RELEASE SAST CD CD_NON_PROD OWASP NVD SUPPLEMENTAL; do
local var_name="MUNITOR_${flag}"
local value="${!var_name:-false}"
local flag_file
+106 -5
View File
@@ -42,7 +42,42 @@ case "${MUNITOR_PIPELINE}" in
nextjs)
# Multi-stage Next.js standalone Dockerfile.
# Assumes `output: 'standalone'` in next.config.js.
cat > Dockerfile <<DOCKERFILE
case "${MUNITOR_PACKAGE_MANAGER}" in
pnpm)
cat > Dockerfile <<DOCKERFILE
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
FROM node:${MUNITOR_NODE_VERSION}-alpine AS builder
WORKDIR /app
RUN corepack enable
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV STANDALONE=true
ARG GIT_COMMIT_SHA
ENV GIT_COMMIT_SHA=\${GIT_COMMIT_SHA}
RUN pnpm run build
FROM node:${MUNITOR_NODE_VERSION}-alpine AS runner
WORKDIR /app
RUN apk update && apk upgrade --no-cache && rm -rf /var/cache/apk/*
RUN npm cache clean --force && rm -rf /usr/local/lib/node_modules /usr/local/bin/npm /usr/local/bin/npx
RUN addgroup --system --gid 1001 nodejs \\
&& adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
CMD ["node", "server.js"]
DOCKERFILE
;;
*)
cat > Dockerfile <<DOCKERFILE
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
WORKDIR /app
COPY package*.json ./
@@ -73,11 +108,38 @@ EXPOSE 3000
ENV PORT=3000
CMD ["node", "server.js"]
DOCKERFILE
;;
esac
;;
express)
# Two-stage Express Dockerfile. Uses package.json "main" field via `node .`.
cat > Dockerfile <<DOCKERFILE
case "${MUNITOR_PACKAGE_MANAGER}" in
pnpm)
cat > Dockerfile <<DOCKERFILE
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prod
FROM node:${MUNITOR_NODE_VERSION}-alpine AS runner
WORKDIR /app
RUN apk update && apk upgrade --no-cache && rm -rf /var/cache/apk/*
RUN npm cache clean --force && rm -rf /usr/local/lib/node_modules /usr/local/bin/npm /usr/local/bin/npx
RUN addgroup --system --gid 1001 nodejs \\
&& adduser --system --uid 1001 appuser -G nodejs
COPY --from=deps /app/node_modules ./node_modules
COPY . .
USER appuser
EXPOSE ${MUNITOR_HEALTH_PORT}
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \\
CMD wget --no-verbose --tries=1 --spider http://localhost:${MUNITOR_HEALTH_PORT}${MUNITOR_HEALTH_PATH} || exit 1
CMD ["node", "."]
DOCKERFILE
;;
*)
cat > Dockerfile <<DOCKERFILE
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
WORKDIR /app
COPY package*.json ./
@@ -97,6 +159,8 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \\
CMD wget --no-verbose --tries=1 --spider http://localhost:${MUNITOR_HEALTH_PORT}${MUNITOR_HEALTH_PATH} || exit 1
CMD ["node", "."]
DOCKERFILE
;;
esac
;;
*)
@@ -197,8 +261,42 @@ DOCKERFILE
;;
node-webapp)
echo "Generating Dockerfile for node-webapp (node ${MUNITOR_NODE_VERSION})"
cat > Dockerfile <<DOCKERFILE
if yq -e '.docker.use_repo_dockerfile == true' "${CONFIG_FILE}" &>/dev/null; then
echo "Using repository Dockerfile for node-webapp"
if [[ ! -f Dockerfile ]]; then
echo "ERROR: docker.use_repo_dockerfile requires a Dockerfile in the repository root." >&2
exit 1
fi
else
echo "Generating Dockerfile for node-webapp (node ${MUNITOR_NODE_VERSION})"
# Single source for the runtime base. debian12 ships libssl3 3.0.18 with
# open HIGH/CRITICAL CVEs that fail the Trivy gate; debian13 scans clean.
NODE_WEBAPP_RUNTIME_IMAGE="gcr.io/distroless/nodejs${MUNITOR_NODE_VERSION}-debian13"
case "${MUNITOR_PACKAGE_MANAGER}" in
pnpm)
cat > Dockerfile <<DOCKERFILE
FROM node:${MUNITOR_NODE_VERSION}-alpine AS builder
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
ARG GIT_COMMIT_SHA
ENV GIT_COMMIT_SHA=\${GIT_COMMIT_SHA}
RUN pnpm run build
FROM ${NODE_WEBAPP_RUNTIME_IMAGE} AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE ${MUNITOR_HEALTH_PORT}
CMD ["server.js"]
DOCKERFILE
;;
*)
cat > Dockerfile <<DOCKERFILE
FROM node:${MUNITOR_NODE_VERSION}-alpine AS builder
WORKDIR /app
COPY package*.json ./
@@ -208,7 +306,7 @@ ARG GIT_COMMIT_SHA
ENV GIT_COMMIT_SHA=\${GIT_COMMIT_SHA}
RUN npm run build
FROM gcr.io/distroless/nodejs${MUNITOR_NODE_VERSION}-debian12 AS runner
FROM ${NODE_WEBAPP_RUNTIME_IMAGE} AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
@@ -217,6 +315,9 @@ COPY --from=builder /app/public ./public
EXPOSE ${MUNITOR_HEALTH_PORT}
CMD ["server.js"]
DOCKERFILE
;;
esac
fi
;;
*)
+16
View File
@@ -56,4 +56,20 @@ else
echo "GitHub Release ${TAG} created."
fi
# Upload release artifacts if specified
if [[ -n "${MUNITOR_RELEASE_ARTIFACTS:-}" ]]; then
echo "Uploading release artifacts: ${MUNITOR_RELEASE_ARTIFACTS}"
for artifact in ${MUNITOR_RELEASE_ARTIFACTS}; do
if [[ ! -f "${artifact}" ]]; then
echo "ERROR: Release artifact '${artifact}' not found."
echo " Working directory: $(pwd)"
echo " Available files: $(ls -la)"
exit 1
fi
done
# shellcheck disable=SC2086
gh release upload "${TAG}" ${MUNITOR_RELEASE_ARTIFACTS} --clobber
echo "Artifacts uploaded to ${TAG}."
fi
echo "github_release completed"
+38
View File
@@ -64,3 +64,41 @@ NODE_BIN_DIR="$(dirname "$(command -v node)")"
} >> "${BASH_ENV}"
echo " BASH_ENV: exported PATH with ${NODE_BIN_DIR}"
# Activate non-npm package managers via corepack
PACKAGE_MANAGER="${MUNITOR_PACKAGE_MANAGER:-npm}"
if [[ "${PACKAGE_MANAGER}" != "npm" ]]; then
echo ""
echo "=== Activating ${PACKAGE_MANAGER} via corepack ==="
corepack enable
PM_VERSION=""
if [[ -f "package.json" ]]; then
# Priority 1: packageManager field (corepack-native, e.g. "pnpm@9.15.9")
PM_FIELD=$(node -e "try{const p=require('./package.json').packageManager||'';console.log(p)}catch{console.log('')}" 2>/dev/null)
if [[ "${PM_FIELD}" == "${PACKAGE_MANAGER}@"* ]]; then
PM_VERSION="${PM_FIELD#*@}"
fi
# Priority 2: engines field (e.g. "engines": {"pnpm": ">=9"})
if [[ -z "${PM_VERSION}" ]]; then
PM_VERSION=$(node -e "try{console.log(require('./package.json').engines?.['${PACKAGE_MANAGER}']||'')}catch{console.log('')}" 2>/dev/null)
fi
fi
if [[ -n "${PM_VERSION}" ]]; then
echo " Detected version: ${PM_VERSION}"
corepack prepare "${PACKAGE_MANAGER}@${PM_VERSION}" --activate
else
echo " No version constraint found, using corepack default"
fi
echo " ${PACKAGE_MANAGER}: $(${PACKAGE_MANAGER} --version)"
# Export PM binary to BASH_ENV for downstream steps
PM_BIN_DIR="$(dirname "$(command -v "${PACKAGE_MANAGER}")")"
if [[ "${PM_BIN_DIR}" != "${NODE_BIN_DIR}" ]]; then
echo "export PATH=\"${PM_BIN_DIR}:\${PATH}\"" >> "${BASH_ENV}"
echo " BASH_ENV: exported PATH with ${PM_BIN_DIR}"
fi
fi
+1
View File
@@ -18,6 +18,7 @@ munitor_check_tool java -version
echo "Running Maven build..."
mvn clean install \
-U \
-DskipTests \
--batch-mode \
--fail-at-end \
@@ -11,7 +11,7 @@ elif ! type munitor_header &>/dev/null; then
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
fi
munitor_header "npm_audit"
munitor_header "node_audit"
echo "Running npm audit..."
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env bash
set -euo pipefail
# Source shared helpers
MUNITOR_HELPERS="${MUNITOR_HELPERS:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/munitor_helpers.sh}"
# shellcheck source=munitor_helpers.sh
if [[ -f "${MUNITOR_HELPERS}" ]]; then source "${MUNITOR_HELPERS}"
elif ! type munitor_header &>/dev/null; then
munitor_header() { echo "=== Munitor: ${1:-unknown} ==="; }
munitor_check_tool() { command -v "$1" &>/dev/null || { echo "ERROR: $1 not found"; exit 1; }; }
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
fi
MIN="${MIN_COVERAGE:-70}"
COVERAGE_TOOL="${MUNITOR_COVERAGE_TOOL:-jest}"
COVERAGE_COMMAND="${MUNITOR_COVERAGE_COMMAND:-}"
SUMMARY_PATH="${MUNITOR_COVERAGE_SUMMARY_PATH:-coverage/coverage-summary.json}"
munitor_header "node_coverage_check (min: ${MIN}%)"
munitor_check_tool node --version
# Validate MIN_COVERAGE is numeric
if ! [[ "${MIN}" =~ ^[0-9]+$ ]]; then
echo "ERROR: MIN_COVERAGE must be a whole number, got '${MIN}'."
exit 1
fi
echo "Checking coverage threshold (minimum: ${MIN}%, tool: ${COVERAGE_TOOL})..."
# Run custom coverage report command if specified
if [[ -n "${COVERAGE_COMMAND}" ]]; then
echo "Running coverage report command: ${COVERAGE_COMMAND}"
bash -c "${COVERAGE_COMMAND}"
fi
# Resolve summary file(s) - supports glob patterns for monorepos
# shellcheck disable=SC2206
SUMMARY_FILES=( ${SUMMARY_PATH} )
if [[ ${#SUMMARY_FILES[@]} -eq 0 ]] || [[ ! -f "${SUMMARY_FILES[0]}" ]]; then
echo "ERROR: Coverage summary not found at ${SUMMARY_PATH}."
echo ""
echo " Ensure your test runner generates coverage-summary.json."
echo " For monorepos, set test.coverage.summary_path in .munitor.yml:"
echo " test:"
echo " coverage:"
echo " summary_path: \"packages/*/coverage/coverage-summary.json\""
exit 1
fi
if [[ ${#SUMMARY_FILES[@]} -eq 1 ]]; then
# Single file - use directly
SUMMARY_FILE="${SUMMARY_FILES[0]}"
echo "Using coverage summary: ${SUMMARY_FILE}"
ACTUAL=$(node -e "
const s = require('./${SUMMARY_FILE}');
console.log(s.total.lines.pct);
")
else
# Multiple files (monorepo) - compute weighted average
echo "Found ${#SUMMARY_FILES[@]} coverage summaries (monorepo mode):"
for f in "${SUMMARY_FILES[@]}"; do
echo " - ${f}"
done
ACTUAL=$(node -e "
const files = process.argv.slice(1);
let totalCovered = 0, totalTotal = 0;
for (const f of files) {
const s = require('./' + f);
totalCovered += s.total.lines.covered;
totalTotal += s.total.lines.total;
}
console.log(totalTotal > 0 ? ((totalCovered / totalTotal) * 100).toFixed(2) : '0');
" "${SUMMARY_FILES[@]}")
fi
echo "Line coverage: ${ACTUAL}%"
# Compare as integers (floor)
ACTUAL_INT=${ACTUAL%.*}
if [[ "${ACTUAL_INT}" -lt "${MIN}" ]]; then
echo "FAIL: Coverage ${ACTUAL}% is below minimum ${MIN}%."
exit 1
fi
echo "Coverage check passed (${ACTUAL}% >= ${MIN}%)."
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
set -euo pipefail
# Source shared helpers
MUNITOR_HELPERS="${MUNITOR_HELPERS:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/munitor_helpers.sh}"
# shellcheck source=munitor_helpers.sh
if [[ -f "${MUNITOR_HELPERS}" ]]; then source "${MUNITOR_HELPERS}"
elif ! type munitor_header &>/dev/null; then
munitor_header() { echo "=== Munitor: ${1:-unknown} ==="; }
munitor_check_tool() { command -v "$1" &>/dev/null || { echo "ERROR: $1 not found"; exit 1; }; }
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
fi
PACKAGE_MANAGER="${MUNITOR_PACKAGE_MANAGER:-npm}"
munitor_header "node_install_deps (${PACKAGE_MANAGER})"
munitor_check_tool node --version
case "${PACKAGE_MANAGER}" in
npm)
munitor_check_tool npm --version
# Verify package-lock.json exists
if [[ ! -f "package-lock.json" ]]; then
echo "ERROR: package-lock.json not found in $(pwd)"
echo " npm ci requires a lockfile. Run 'npm install' locally and commit the lockfile."
exit 1
fi
# Check lockfile version compatibility
LOCKFILE_VERSION=$(node -e "console.log(require('./package-lock.json').lockfileVersion || 'unknown')" 2>/dev/null || echo "unknown")
NPM_MAJOR=$(npm --version 2>/dev/null | cut -d. -f1)
echo " lockfileVersion: ${LOCKFILE_VERSION}"
if [[ "${LOCKFILE_VERSION}" == "3" && -n "${NPM_MAJOR}" && "${NPM_MAJOR}" -lt 11 ]]; then
echo ""
echo "WARNING: package-lock.json uses lockfileVersion 3 (npm 11+)"
echo " but CI is running npm ${NPM_MAJOR} ($(npm --version))."
echo " This will likely cause 'npm ci' to fail."
echo ""
echo " Fix: regenerate your lockfile with the same Node version CI uses."
echo " Add an .nvmrc to your repo to keep local and CI versions in sync."
echo ""
fi
echo ""
echo "Running npm ci..."
npm ci
PKG_COUNT=$(find node_modules -maxdepth 1 -mindepth 1 -type d | wc -l | tr -d ' ')
echo "npm ci complete. ${PKG_COUNT} packages installed."
;;
pnpm)
munitor_check_tool pnpm --version
# Verify pnpm-lock.yaml exists
if [[ ! -f "pnpm-lock.yaml" ]]; then
echo "ERROR: pnpm-lock.yaml not found in $(pwd)"
echo " pnpm install --frozen-lockfile requires a lockfile. Run 'pnpm install' locally and commit the lockfile."
exit 1
fi
echo ""
echo "Running pnpm install --frozen-lockfile..."
pnpm install --frozen-lockfile
PKG_COUNT=$(find node_modules -maxdepth 1 -mindepth 1 -type d | wc -l | tr -d ' ')
echo "pnpm install complete. ${PKG_COUNT} packages installed."
;;
*)
echo "ERROR: Unsupported package manager '${PACKAGE_MANAGER}'"
echo " Supported values: npm, pnpm"
exit 1
;;
esac
# Prisma auto-detection: generate client if schema files are present
PRISMA_SCHEMAS=$(find . -path "*/prisma/schema.prisma" -not -path "*/node_modules/*" -not -path "*/dist/*" -not -path "*/build/*" 2>/dev/null || true)
if [[ -n "${PRISMA_SCHEMAS}" ]]; then
echo ""
echo "=== Prisma schema(s) detected ==="
PRISMA_GENERATED=0
while IFS= read -r SCHEMA_PATH; do
# Resolve the package directory (parent of prisma/)
PKG_DIR=$(dirname "$(dirname "${SCHEMA_PATH}")")
# Check if prisma CLI is available from the package directory.
# In monorepos, prisma may only be installed in a workspace package,
# not at the root, so we must check from the package's own context.
PRISMA_CMD=""
case "${PACKAGE_MANAGER}" in
npm)
if (cd "${PKG_DIR}" && npx prisma --version) &>/dev/null; then
PRISMA_CMD="npx prisma"
fi
;;
pnpm)
if (cd "${PKG_DIR}" && pnpm exec prisma --version) &>/dev/null; then
PRISMA_CMD="pnpm exec prisma"
fi
;;
esac
if [[ -n "${PRISMA_CMD}" ]]; then
echo " Generating Prisma client for: ${SCHEMA_PATH}"
(cd "${PKG_DIR}" && ${PRISMA_CMD} generate --schema=prisma/schema.prisma)
PRISMA_GENERATED=$((PRISMA_GENERATED + 1))
else
echo " WARNING: Found ${SCHEMA_PATH} but prisma CLI not available in ${PKG_DIR}."
echo " Add prisma as a dev dependency in that package."
fi
done <<< "${PRISMA_SCHEMAS}"
if [[ "${PRISMA_GENERATED}" -gt 0 ]]; then
echo "Prisma generate complete (${PRISMA_GENERATED} schema(s))."
fi
fi
@@ -11,7 +11,7 @@ elif ! type munitor_header &>/dev/null; then
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
fi
munitor_header "npm_lint"
munitor_header "node_lint"
echo "Running ESLint..."
@@ -11,7 +11,7 @@ elif ! type munitor_header &>/dev/null; then
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
fi
munitor_header "npm_sbom"
munitor_header "node_sbom"
munitor_check_tool node --version
munitor_check_tool npx --version
@@ -11,7 +11,7 @@ elif ! type munitor_header &>/dev/null; then
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
fi
munitor_header "npm_sonar"
munitor_header "node_sonar"
PROJECT_KEY="${SONAR_PROJECT_KEY:?SONAR_PROJECT_KEY not set}"
ORG="${SONAR_ORG:-KofTwentyTwo}"
@@ -11,7 +11,7 @@ elif ! type munitor_header &>/dev/null; then
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
fi
munitor_header "npm_test"
munitor_header "node_test"
# Prevent interactive prompts from apt/dpkg when test commands install system deps
export DEBIAN_FRONTEND=noninteractive
@@ -34,36 +34,44 @@ if [[ -n "${TEST_COMMANDS_JSON}" && "${TEST_COMMANDS_JSON}" != "[]" && "${TEST_C
echo "Custom test commands complete."
else
# Default: Jest with coverage and JUnit reporter
if ! npx jest --version &>/dev/null; then
echo ""
echo "ERROR: Jest is not installed and no custom test commands are configured."
echo ""
echo " Munitor defaults to 'npx jest' but this project doesn't have Jest."
echo " Add custom test commands to your .munitor.yml:"
echo ""
echo " test:"
echo " commands:"
echo " - \"npm test\""
echo ""
echo " Or install Jest: npm install --save-dev jest"
echo ""
exit 1
PACKAGE_MANAGER="${MUNITOR_PACKAGE_MANAGER:-npm}"
if [[ "${PACKAGE_MANAGER}" == "pnpm" ]]; then
echo "Running pnpm test..."
pnpm test
echo "pnpm test complete."
else
# Default: Jest with coverage and JUnit reporter
if ! npx jest --version &>/dev/null; then
echo ""
echo "ERROR: Jest is not installed and no custom test commands are configured."
echo ""
echo " Munitor defaults to 'npx jest' but this project doesn't have Jest."
echo " Add custom test commands to your .munitor.yml:"
echo ""
echo " test:"
echo " commands:"
echo " - \"npm test\""
echo ""
echo " Or install Jest: npm install --save-dev jest"
echo ""
exit 1
fi
echo "Running Jest tests..."
export JEST_JUNIT_OUTPUT_DIR="reports/junit"
export JEST_JUNIT_OUTPUT_NAME="results.xml"
npx jest \
--ci \
--forceExit \
--coverage \
--coverageReporters=json-summary \
--coverageReporters=lcov \
--reporters=default \
--reporters=jest-junit
echo "Jest tests complete."
fi
echo "Running Jest tests..."
export JEST_JUNIT_OUTPUT_DIR="reports/junit"
export JEST_JUNIT_OUTPUT_NAME="results.xml"
npx jest \
--ci \
--forceExit \
--coverage \
--coverageReporters=json-summary \
--coverageReporters=lcov \
--reporters=default \
--reporters=jest-junit
echo "Jest tests complete."
fi
-57
View File
@@ -1,57 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Source shared helpers
MUNITOR_HELPERS="${MUNITOR_HELPERS:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/munitor_helpers.sh}"
# shellcheck source=munitor_helpers.sh
if [[ -f "${MUNITOR_HELPERS}" ]]; then source "${MUNITOR_HELPERS}"
elif ! type munitor_header &>/dev/null; then
munitor_header() { echo "=== Munitor: ${1:-unknown} ==="; }
munitor_check_tool() { command -v "$1" &>/dev/null || { echo "ERROR: $1 not found"; exit 1; }; }
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
fi
MIN="${MIN_COVERAGE:-70}"
COVERAGE_TOOL="${MUNITOR_COVERAGE_TOOL:-jest}"
COVERAGE_COMMAND="${MUNITOR_COVERAGE_COMMAND:-}"
SUMMARY_FILE="coverage/coverage-summary.json"
munitor_header "npm_coverage_check (min: ${MIN}%)"
munitor_check_tool node --version
# Validate MIN_COVERAGE is numeric
if ! [[ "${MIN}" =~ ^[0-9]+$ ]]; then
echo "ERROR: MIN_COVERAGE must be a whole number, got '${MIN}'."
exit 1
fi
echo "Checking coverage threshold (minimum: ${MIN}%, tool: ${COVERAGE_TOOL})..."
# Run custom coverage report command if specified
if [[ -n "${COVERAGE_COMMAND}" ]]; then
echo "Running coverage report command: ${COVERAGE_COMMAND}"
bash -c "${COVERAGE_COMMAND}"
fi
if [[ ! -f "${SUMMARY_FILE}" ]]; then
echo "ERROR: Coverage summary not found at ${SUMMARY_FILE}."
echo "Ensure your test runner generates coverage/coverage-summary.json."
exit 1
fi
# Extract total line coverage percentage from summary (same format for jest and nyc)
ACTUAL=$(node -e "
const s = require('./${SUMMARY_FILE}');
console.log(s.total.lines.pct);
")
echo "Line coverage: ${ACTUAL}%"
# Compare as integers (floor)
ACTUAL_INT=${ACTUAL%.*}
if [[ "${ACTUAL_INT}" -lt "${MIN}" ]]; then
echo "FAIL: Coverage ${ACTUAL}% is below minimum ${MIN}%."
exit 1
fi
echo "Coverage check passed (${ACTUAL}% >= ${MIN}%)."
-46
View File
@@ -1,46 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Source shared helpers
MUNITOR_HELPERS="${MUNITOR_HELPERS:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/munitor_helpers.sh}"
# shellcheck source=munitor_helpers.sh
if [[ -f "${MUNITOR_HELPERS}" ]]; then source "${MUNITOR_HELPERS}"
elif ! type munitor_header &>/dev/null; then
munitor_header() { echo "=== Munitor: ${1:-unknown} ==="; }
munitor_check_tool() { command -v "$1" &>/dev/null || { echo "ERROR: $1 not found"; exit 1; }; }
munitor_download_with_retry() { curl -fsSL --retry 3 "$1" -o "$2"; }
fi
munitor_header "npm_install"
munitor_check_tool node --version
munitor_check_tool npm --version
# Verify package-lock.json exists
if [[ ! -f "package-lock.json" ]]; then
echo "ERROR: package-lock.json not found in $(pwd)"
echo " npm ci requires a lockfile. Run 'npm install' locally and commit the lockfile."
exit 1
fi
# Check lockfile version compatibility
LOCKFILE_VERSION=$(node -e "console.log(require('./package-lock.json').lockfileVersion || 'unknown')" 2>/dev/null || echo "unknown")
NPM_MAJOR=$(npm --version 2>/dev/null | cut -d. -f1)
echo " lockfileVersion: ${LOCKFILE_VERSION}"
if [[ "${LOCKFILE_VERSION}" == "3" && -n "${NPM_MAJOR}" && "${NPM_MAJOR}" -lt 11 ]]; then
echo ""
echo "WARNING: package-lock.json uses lockfileVersion 3 (npm 11+)"
echo " but CI is running npm ${NPM_MAJOR} ($(npm --version))."
echo " This will likely cause 'npm ci' to fail."
echo ""
echo " Fix: regenerate your lockfile with the same Node version CI uses."
echo " Add an .nvmrc to your repo to keep local and CI versions in sync."
echo ""
fi
echo ""
echo "Running npm ci..."
npm ci
PKG_COUNT=$(find node_modules -maxdepth 1 -mindepth 1 -type d | wc -l | tr -d ' ')
echo "npm ci complete. ${PKG_COUNT} packages installed."
File diff suppressed because it is too large. Load diff
+119 -9
View File
@@ -135,6 +135,8 @@ extract_munitor_vars() {
fi
MUNITOR_CD_REPO=$(yq '.cd.repo // ""' "${config_file}")
MUNITOR_CD_FORMAT=$(yq '.cd.format // "helm"' "${config_file}")
MUNITOR_CD_PATH=$(yq '.cd.path // ""' "${config_file}")
MUNITOR_CD_PRODUCTION_ONLY=$(yq '.cd.production_only // false' "${config_file}")
MUNITOR_CD_ENV_DEVELOP=$(yq '.cd.env.develop // "develop"' "${config_file}")
MUNITOR_CD_ENV_STAGING=$(yq '.cd.env.staging // "staging"' "${config_file}")
MUNITOR_CD_ENV_PROD=$(yq '.cd.env.prod // "prod"' "${config_file}")
@@ -193,6 +195,7 @@ extract_munitor_vars() {
if [[ -n "${MUNITOR_TEST_COVERAGE_MIN}" ]]; then
MUNITOR_COVERAGE_MIN="${MUNITOR_TEST_COVERAGE_MIN}"
fi
MUNITOR_COVERAGE_SUMMARY_PATH=$(yq '.test.coverage.summary_path // "coverage/coverage-summary.json"' "${config_file}")
# Terraform pipeline settings
MUNITOR_TF_PATH=$(yq '.terraform.path // "terraform/"' "${config_file}")
@@ -290,8 +293,14 @@ extract_munitor_vars() {
if [[ -n "${MUNITOR_CD_REPO}" ]]; then
MUNITOR_CD="true"
if [[ "${MUNITOR_CD_PRODUCTION_ONLY}" == "true" ]]; then
MUNITOR_CD_NON_PROD="false"
else
MUNITOR_CD_NON_PROD="true"
fi
else
MUNITOR_CD="false"
MUNITOR_CD_NON_PROD="false"
fi
if [[ -n "${MUNITOR_CONTEXT_NVD}" ]]; then
@@ -302,7 +311,7 @@ extract_munitor_vars() {
# Export all variables
export MUNITOR_PIPELINE MUNITOR_ORB_VERSION MUNITOR_IMAGE_NAME MUNITOR_JAVA_VERSION MUNITOR_NODE_VERSION MUNITOR_SERVER_MODULE
export MUNITOR_SONAR_PROJECT_KEY MUNITOR_DOCKER_REGISTRY MUNITOR_CD_REPO MUNITOR_CD_FORMAT
export MUNITOR_SONAR_PROJECT_KEY MUNITOR_DOCKER_REGISTRY MUNITOR_CD_REPO MUNITOR_CD_FORMAT MUNITOR_CD_PATH MUNITOR_CD_PRODUCTION_ONLY
export MUNITOR_CD_ENV_DEVELOP MUNITOR_CD_ENV_STAGING MUNITOR_CD_ENV_PROD MUNITOR_CD_ENV_RELEASE
export MUNITOR_COVERAGE_MIN MUNITOR_E2E MUNITOR_SBOM MUNITOR_OWASP
export MUNITOR_CONTEXT_REGISTRY MUNITOR_CONTEXT_GITHUB MUNITOR_CONTEXT_SONAR MUNITOR_CONTEXT_NVD
@@ -311,7 +320,7 @@ extract_munitor_vars() {
export MUNITOR_SERVICES MUNITOR_SERVICES_JSON
export MUNITOR_TEST_SETUP MUNITOR_TEST_SETUP_SCRIPT
export MUNITOR_CUSTOM_TEST MUNITOR_TEST_COMMANDS_JSON
export MUNITOR_COVERAGE_TOOL MUNITOR_COVERAGE_CMD MUNITOR_COVERAGE_COMMAND
export MUNITOR_COVERAGE_TOOL MUNITOR_COVERAGE_CMD MUNITOR_COVERAGE_COMMAND MUNITOR_COVERAGE_SUMMARY_PATH
export MUNITOR_TF_PATH MUNITOR_TF_LIVE_PATH MUNITOR_TF_ENVIRONMENTS MUNITOR_CHECKOV_SKIP MUNITOR_SAST MUNITOR_SAST_FAIL_ON_FINDINGS
export MUNITOR_KUSTOMIZE_VERSION MUNITOR_KUSTOMIZE_BASE_PATH MUNITOR_KUSTOMIZE_LOAD_RESTRICTOR
export MUNITOR_KUSTOMIZE_SCAN_OVERLAY MUNITOR_KUSTOMIZE_OVERLAYS MUNITOR_KUSTOMIZE_OVERLAY_DIR
@@ -320,13 +329,13 @@ extract_munitor_vars() {
export MUNITOR_CI_EMAIL MUNITOR_CI_NAME MUNITOR_NPM_DEFAULT_SCOPE MUNITOR_ORB_SLUG
export MUNITOR_HEALTH_PATH MUNITOR_HEALTH_PORT MUNITOR_HEALTH_DB MUNITOR_HEALTH_MIGRATIONS MUNITOR_HEALTH_SMOKE_PATHS
export MUNITOR_SUPPLEMENTAL MUNITOR_SUPPLEMENTAL_IMAGES_JSON
export MUNITOR_SONAR MUNITOR_CD MUNITOR_NVD
export MUNITOR_SONAR MUNITOR_CD MUNITOR_CD_NON_PROD MUNITOR_NVD
}
# Build the envsubst variable list
get_envsubst_vars() {
# shellcheck disable=SC2016
echo '${MUNITOR_PIPELINE} ${MUNITOR_ORB_VERSION} ${MUNITOR_IMAGE_NAME} ${MUNITOR_JAVA_VERSION} ${MUNITOR_NODE_VERSION} ${MUNITOR_SONAR_PROJECT_KEY} ${MUNITOR_DOCKER_REGISTRY} ${MUNITOR_CD_REPO} ${MUNITOR_CD_FORMAT} ${MUNITOR_CD_ENV_DEVELOP} ${MUNITOR_CD_ENV_STAGING} ${MUNITOR_CD_ENV_PROD} ${MUNITOR_CD_ENV_RELEASE} ${MUNITOR_COVERAGE_MIN} ${MUNITOR_E2E} ${MUNITOR_SBOM} ${MUNITOR_OWASP} ${MUNITOR_CONTEXT_REGISTRY} ${MUNITOR_CONTEXT_GITHUB} ${MUNITOR_CONTEXT_SONAR} ${MUNITOR_CONTEXT_NVD} ${MUNITOR_NPM_AUTH} ${MUNITOR_NPM_SCOPES} ${MUNITOR_SERVICES_JSON} ${MUNITOR_TEST_SETUP_SCRIPT} ${MUNITOR_TEST_COMMANDS_JSON} ${MUNITOR_COVERAGE_TOOL} ${MUNITOR_COVERAGE_COMMAND} ${MUNITOR_GITHUB_RELEASE} ${MUNITOR_SAST} ${MUNITOR_SAST_FAIL_ON_FINDINGS} ${MUNITOR_HEALTH_PATH} ${MUNITOR_HEALTH_PORT} ${MUNITOR_HEALTH_DB} ${MUNITOR_HEALTH_MIGRATIONS} ${MUNITOR_HEALTH_SMOKE_PATHS} ${MUNITOR_TF_PATH} ${MUNITOR_TF_LIVE_PATH} ${MUNITOR_TF_ENVIRONMENTS} ${MUNITOR_CHECKOV_SKIP} ${MUNITOR_KUSTOMIZE_VERSION} ${MUNITOR_KUSTOMIZE_BASE_PATH} ${MUNITOR_KUSTOMIZE_LOAD_RESTRICTOR} ${MUNITOR_KUSTOMIZE_SCAN_OVERLAY} ${MUNITOR_KUSTOMIZE_OVERLAYS} ${MUNITOR_KUSTOMIZE_OVERLAY_DIR} ${MUNITOR_KUBE_LINTER_CONFIG} ${MUNITOR_YAMLLINT_PATHS} ${MUNITOR_CI_EMAIL} ${MUNITOR_CI_NAME} ${MUNITOR_NPM_DEFAULT_SCOPE} ${MUNITOR_ORB_SLUG} ${MUNITOR_PACKAGE_MANAGER} ${MUNITOR_SUPPLEMENTAL_IMAGES_JSON} ${MUNITOR_SERVER_MODULE}'
echo '${MUNITOR_PIPELINE} ${MUNITOR_ORB_VERSION} ${MUNITOR_IMAGE_NAME} ${MUNITOR_JAVA_VERSION} ${MUNITOR_NODE_VERSION} ${MUNITOR_SONAR_PROJECT_KEY} ${MUNITOR_DOCKER_REGISTRY} ${MUNITOR_CD_REPO} ${MUNITOR_CD_FORMAT} ${MUNITOR_CD_PATH} ${MUNITOR_CD_ENV_DEVELOP} ${MUNITOR_CD_ENV_STAGING} ${MUNITOR_CD_ENV_PROD} ${MUNITOR_CD_ENV_RELEASE} ${MUNITOR_COVERAGE_MIN} ${MUNITOR_E2E} ${MUNITOR_SBOM} ${MUNITOR_OWASP} ${MUNITOR_CONTEXT_REGISTRY} ${MUNITOR_CONTEXT_GITHUB} ${MUNITOR_CONTEXT_SONAR} ${MUNITOR_CONTEXT_NVD} ${MUNITOR_NPM_AUTH} ${MUNITOR_NPM_SCOPES} ${MUNITOR_SERVICES_JSON} ${MUNITOR_TEST_SETUP_SCRIPT} ${MUNITOR_TEST_COMMANDS_JSON} ${MUNITOR_COVERAGE_TOOL} ${MUNITOR_COVERAGE_COMMAND} ${MUNITOR_GITHUB_RELEASE} ${MUNITOR_SAST} ${MUNITOR_SAST_FAIL_ON_FINDINGS} ${MUNITOR_HEALTH_PATH} ${MUNITOR_HEALTH_PORT} ${MUNITOR_HEALTH_DB} ${MUNITOR_HEALTH_MIGRATIONS} ${MUNITOR_HEALTH_SMOKE_PATHS} ${MUNITOR_TF_PATH} ${MUNITOR_TF_LIVE_PATH} ${MUNITOR_TF_ENVIRONMENTS} ${MUNITOR_CHECKOV_SKIP} ${MUNITOR_KUSTOMIZE_VERSION} ${MUNITOR_KUSTOMIZE_BASE_PATH} ${MUNITOR_KUSTOMIZE_LOAD_RESTRICTOR} ${MUNITOR_KUSTOMIZE_SCAN_OVERLAY} ${MUNITOR_KUSTOMIZE_OVERLAYS} ${MUNITOR_KUSTOMIZE_OVERLAY_DIR} ${MUNITOR_KUBE_LINTER_CONFIG} ${MUNITOR_YAMLLINT_PATHS} ${MUNITOR_CI_EMAIL} ${MUNITOR_CI_NAME} ${MUNITOR_NPM_DEFAULT_SCOPE} ${MUNITOR_ORB_SLUG} ${MUNITOR_PACKAGE_MANAGER} ${MUNITOR_SUPPLEMENTAL_IMAGES_JSON} ${MUNITOR_SERVER_MODULE} ${MUNITOR_COVERAGE_SUMMARY_PATH}'
}
MUNITOR_EXTRACT_EOF
@@ -377,7 +386,42 @@ case "${MUNITOR_PIPELINE}" in
nextjs)
# Multi-stage Next.js standalone Dockerfile.
# Assumes `output: 'standalone'` in next.config.js.
cat > Dockerfile <<DOCKERFILE
case "${MUNITOR_PACKAGE_MANAGER}" in
pnpm)
cat > Dockerfile <<DOCKERFILE
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
FROM node:${MUNITOR_NODE_VERSION}-alpine AS builder
WORKDIR /app
RUN corepack enable
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV STANDALONE=true
ARG GIT_COMMIT_SHA
ENV GIT_COMMIT_SHA=\${GIT_COMMIT_SHA}
RUN pnpm run build
FROM node:${MUNITOR_NODE_VERSION}-alpine AS runner
WORKDIR /app
RUN apk update && apk upgrade --no-cache && rm -rf /var/cache/apk/*
RUN npm cache clean --force && rm -rf /usr/local/lib/node_modules /usr/local/bin/npm /usr/local/bin/npx
RUN addgroup --system --gid 1001 nodejs \\
&& adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
CMD ["node", "server.js"]
DOCKERFILE
;;
*)
cat > Dockerfile <<DOCKERFILE
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
WORKDIR /app
COPY package*.json ./
@@ -408,11 +452,38 @@ EXPOSE 3000
ENV PORT=3000
CMD ["node", "server.js"]
DOCKERFILE
;;
esac
;;
express)
# Two-stage Express Dockerfile. Uses package.json "main" field via `node .`.
cat > Dockerfile <<DOCKERFILE
case "${MUNITOR_PACKAGE_MANAGER}" in
pnpm)
cat > Dockerfile <<DOCKERFILE
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prod
FROM node:${MUNITOR_NODE_VERSION}-alpine AS runner
WORKDIR /app
RUN apk update && apk upgrade --no-cache && rm -rf /var/cache/apk/*
RUN npm cache clean --force && rm -rf /usr/local/lib/node_modules /usr/local/bin/npm /usr/local/bin/npx
RUN addgroup --system --gid 1001 nodejs \\
&& adduser --system --uid 1001 appuser -G nodejs
COPY --from=deps /app/node_modules ./node_modules
COPY . .
USER appuser
EXPOSE ${MUNITOR_HEALTH_PORT}
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \\
CMD wget --no-verbose --tries=1 --spider http://localhost:${MUNITOR_HEALTH_PORT}${MUNITOR_HEALTH_PATH} || exit 1
CMD ["node", "."]
DOCKERFILE
;;
*)
cat > Dockerfile <<DOCKERFILE
FROM node:${MUNITOR_NODE_VERSION}-alpine AS deps
WORKDIR /app
COPY package*.json ./
@@ -432,6 +503,8 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \\
CMD wget --no-verbose --tries=1 --spider http://localhost:${MUNITOR_HEALTH_PORT}${MUNITOR_HEALTH_PATH} || exit 1
CMD ["node", "."]
DOCKERFILE
;;
esac
;;
*)
@@ -532,8 +605,42 @@ DOCKERFILE
;;
node-webapp)
echo "Generating Dockerfile for node-webapp (node ${MUNITOR_NODE_VERSION})"
cat > Dockerfile <<DOCKERFILE
if yq -e '.docker.use_repo_dockerfile == true' "${CONFIG_FILE}" &>/dev/null; then
echo "Using repository Dockerfile for node-webapp"
if [[ ! -f Dockerfile ]]; then
echo "ERROR: docker.use_repo_dockerfile requires a Dockerfile in the repository root." >&2
exit 1
fi
else
echo "Generating Dockerfile for node-webapp (node ${MUNITOR_NODE_VERSION})"
# Single source for the runtime base. debian12 ships libssl3 3.0.18 with
# open HIGH/CRITICAL CVEs that fail the Trivy gate; debian13 scans clean.
NODE_WEBAPP_RUNTIME_IMAGE="gcr.io/distroless/nodejs${MUNITOR_NODE_VERSION}-debian13"
case "${MUNITOR_PACKAGE_MANAGER}" in
pnpm)
cat > Dockerfile <<DOCKERFILE
FROM node:${MUNITOR_NODE_VERSION}-alpine AS builder
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
ARG GIT_COMMIT_SHA
ENV GIT_COMMIT_SHA=\${GIT_COMMIT_SHA}
RUN pnpm run build
FROM ${NODE_WEBAPP_RUNTIME_IMAGE} AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE ${MUNITOR_HEALTH_PORT}
CMD ["server.js"]
DOCKERFILE
;;
*)
cat > Dockerfile <<DOCKERFILE
FROM node:${MUNITOR_NODE_VERSION}-alpine AS builder
WORKDIR /app
COPY package*.json ./
@@ -543,7 +650,7 @@ ARG GIT_COMMIT_SHA
ENV GIT_COMMIT_SHA=\${GIT_COMMIT_SHA}
RUN npm run build
FROM gcr.io/distroless/nodejs${MUNITOR_NODE_VERSION}-debian12 AS runner
FROM ${NODE_WEBAPP_RUNTIME_IMAGE} AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
@@ -552,6 +659,9 @@ COPY --from=builder /app/public ./public
EXPOSE ${MUNITOR_HEALTH_PORT}
CMD ["server.js"]
DOCKERFILE
;;
esac
fi
;;
*)
+8 -7
View File
@@ -16,16 +16,17 @@ munitor_header "run_test_setup"
SETUP_SCRIPT="${MUNITOR_TEST_SETUP_SCRIPT:-}"
if [[ -z "${SETUP_SCRIPT}" ]]; then
echo "No test setup script configured."
echo "No test setup configured."
exit 0
fi
if [[ ! -f "${SETUP_SCRIPT}" ]]; then
echo "ERROR: Test setup script '${SETUP_SCRIPT}' not found."
exit 1
if [[ -f "${SETUP_SCRIPT}" ]]; then
echo "Running test setup script: ${SETUP_SCRIPT}"
chmod +x "${SETUP_SCRIPT}"
"${SETUP_SCRIPT}"
else
echo "Running test setup command: ${SETUP_SCRIPT}"
bash -c "${SETUP_SCRIPT}"
fi
echo "Running test setup: ${SETUP_SCRIPT}"
chmod +x "${SETUP_SCRIPT}"
"${SETUP_SCRIPT}"
echo "Test setup complete."
+4 -2
View File
@@ -223,13 +223,14 @@ workflows:
only:
- /release\/.*/
##ENDIF_SBOM##
##IF_CD##
##IF_CD_NON_PROD##
- munitor/update_cd_repo:
name: update-cd-repo
cd_repo: ${MUNITOR_CD_REPO}
environment: ${MUNITOR_CD_ENV_RELEASE}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
cd_path: ${MUNITOR_CD_PATH}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
@@ -243,7 +244,7 @@ workflows:
branches:
only:
- /release\/.*/
##ENDIF_CD##
##ENDIF_CD_NON_PROD##
##IF_GITHUB_RELEASE##
- munitor/github_release:
name: github-release
@@ -293,6 +294,7 @@ workflows:
environment: ${MUNITOR_CD_ENV_PROD}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
cd_path: ${MUNITOR_CD_PATH}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
+4 -2
View File
@@ -223,13 +223,14 @@ workflows:
only:
- /release\/.*/
##ENDIF_SBOM##
##IF_CD##
##IF_CD_NON_PROD##
- munitor/update_cd_repo:
name: update-cd-repo
cd_repo: ${MUNITOR_CD_REPO}
environment: ${MUNITOR_CD_ENV_RELEASE}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
cd_path: ${MUNITOR_CD_PATH}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
@@ -243,7 +244,7 @@ workflows:
branches:
only:
- /release\/.*/
##ENDIF_CD##
##ENDIF_CD_NON_PROD##
##IF_GITHUB_RELEASE##
- munitor/github_release:
name: github-release
@@ -293,6 +294,7 @@ workflows:
environment: ${MUNITOR_CD_ENV_PROD}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
cd_path: ${MUNITOR_CD_PATH}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
+40 -15
View File
@@ -6,9 +6,10 @@ orbs:
workflows:
pr-checks:
jobs:
- munitor/npm_build_and_test:
- munitor/node_build_and_test:
name: build-and-test
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
##IF_NPM_AUTH##
npm_auth: true
npm_scopes: '${MUNITOR_NPM_SCOPES}'
@@ -45,8 +46,10 @@ workflows:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/npm_code_quality:
- munitor/node_code_quality:
name: code-quality
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
@@ -54,10 +57,13 @@ workflows:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/npm_coverage:
- munitor/node_coverage:
name: coverage
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
min_coverage: "${MUNITOR_COVERAGE_MIN}"
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
##IF_COVERAGE_CMD##
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
##ENDIF_COVERAGE_CMD##
@@ -68,8 +74,10 @@ workflows:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/npm_security_scan:
- munitor/node_security_scan:
name: security-scan
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
@@ -78,8 +86,10 @@ workflows:
- /feature\/.*/
- /hotfix\/.*/
##IF_E2E##
- munitor/npm_e2e_test:
- munitor/node_e2e_test:
name: e2e-tests
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
@@ -94,9 +104,10 @@ workflows:
release-candidate:
jobs:
- munitor/npm_build_and_test:
- munitor/node_build_and_test:
name: build-and-test
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
##IF_NPM_AUTH##
npm_auth: true
npm_scopes: '${MUNITOR_NPM_SCOPES}'
@@ -130,18 +141,23 @@ workflows:
branches:
only:
- /release\/.*/
- munitor/npm_code_quality:
- munitor/node_code_quality:
name: code-quality
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
branches:
only:
- /release\/.*/
- munitor/npm_coverage:
- munitor/node_coverage:
name: coverage
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
min_coverage: "${MUNITOR_COVERAGE_MIN}"
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
##IF_COVERAGE_CMD##
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
##ENDIF_COVERAGE_CMD##
@@ -151,8 +167,10 @@ workflows:
branches:
only:
- /release\/.*/
- munitor/npm_security_scan:
- munitor/node_security_scan:
name: security-scan
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
@@ -160,8 +178,10 @@ workflows:
only:
- /release\/.*/
##IF_E2E##
- munitor/npm_e2e_test:
- munitor/node_e2e_test:
name: e2e-tests
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
@@ -170,9 +190,10 @@ workflows:
- /release\/.*/
##ENDIF_E2E##
##IF_SONAR##
- munitor/npm_sonar_scan:
- munitor/node_sonar_scan:
name: sonar-scan
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
requires:
- build-and-test
@@ -210,9 +231,10 @@ workflows:
only:
- /release\/.*/
##IF_SBOM##
- munitor/npm_sbom:
- munitor/node_sbom:
name: sbom
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
requires:
@@ -229,13 +251,14 @@ workflows:
only:
- /release\/.*/
##ENDIF_SBOM##
##IF_CD##
##IF_CD_NON_PROD##
- munitor/update_cd_repo:
name: update-cd-repo
cd_repo: ${MUNITOR_CD_REPO}
environment: ${MUNITOR_CD_ENV_RELEASE}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
cd_path: ${MUNITOR_CD_PATH}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
@@ -249,7 +272,7 @@ workflows:
branches:
only:
- /release\/.*/
##ENDIF_CD##
##ENDIF_CD_NON_PROD##
##IF_GITHUB_RELEASE##
- munitor/github_release:
name: github-release
@@ -265,9 +288,10 @@ workflows:
production:
jobs:
- munitor/npm_build_and_test:
- munitor/node_build_and_test:
name: build-and-test
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
##IF_NPM_AUTH##
npm_auth: true
npm_scopes: '${MUNITOR_NPM_SCOPES}'
@@ -311,6 +335,7 @@ workflows:
environment: ${MUNITOR_CD_ENV_PROD}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
cd_path: ${MUNITOR_CD_PATH}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
+40 -15
View File
@@ -6,9 +6,10 @@ orbs:
workflows:
pr-checks:
jobs:
- munitor/npm_build_and_test:
- munitor/node_build_and_test:
name: build-and-test
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
##IF_NPM_AUTH##
npm_auth: true
npm_scopes: '${MUNITOR_NPM_SCOPES}'
@@ -45,8 +46,10 @@ workflows:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/npm_code_quality:
- munitor/node_code_quality:
name: code-quality
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
@@ -54,10 +57,13 @@ workflows:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/npm_coverage:
- munitor/node_coverage:
name: coverage
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
min_coverage: "${MUNITOR_COVERAGE_MIN}"
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
##IF_COVERAGE_CMD##
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
##ENDIF_COVERAGE_CMD##
@@ -68,8 +74,10 @@ workflows:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/npm_security_scan:
- munitor/node_security_scan:
name: security-scan
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
@@ -78,8 +86,10 @@ workflows:
- /feature\/.*/
- /hotfix\/.*/
##IF_E2E##
- munitor/npm_e2e_test:
- munitor/node_e2e_test:
name: e2e-tests
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
@@ -94,9 +104,10 @@ workflows:
release-candidate:
jobs:
- munitor/npm_build_and_test:
- munitor/node_build_and_test:
name: build-and-test
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
##IF_NPM_AUTH##
npm_auth: true
npm_scopes: '${MUNITOR_NPM_SCOPES}'
@@ -130,18 +141,23 @@ workflows:
branches:
only:
- /release\/.*/
- munitor/npm_code_quality:
- munitor/node_code_quality:
name: code-quality
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
branches:
only:
- /release\/.*/
- munitor/npm_coverage:
- munitor/node_coverage:
name: coverage
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
min_coverage: "${MUNITOR_COVERAGE_MIN}"
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
##IF_COVERAGE_CMD##
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
##ENDIF_COVERAGE_CMD##
@@ -151,8 +167,10 @@ workflows:
branches:
only:
- /release\/.*/
- munitor/npm_security_scan:
- munitor/node_security_scan:
name: security-scan
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
@@ -160,8 +178,10 @@ workflows:
only:
- /release\/.*/
##IF_E2E##
- munitor/npm_e2e_test:
- munitor/node_e2e_test:
name: e2e-tests
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
@@ -170,9 +190,10 @@ workflows:
- /release\/.*/
##ENDIF_E2E##
##IF_SONAR##
- munitor/npm_sonar_scan:
- munitor/node_sonar_scan:
name: sonar-scan
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
requires:
- build-and-test
@@ -210,9 +231,10 @@ workflows:
only:
- /release\/.*/
##IF_SBOM##
- munitor/npm_sbom:
- munitor/node_sbom:
name: sbom
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
requires:
@@ -229,13 +251,14 @@ workflows:
only:
- /release\/.*/
##ENDIF_SBOM##
##IF_CD##
##IF_CD_NON_PROD##
- munitor/update_cd_repo:
name: update-cd-repo
cd_repo: ${MUNITOR_CD_REPO}
environment: ${MUNITOR_CD_ENV_RELEASE}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
cd_path: ${MUNITOR_CD_PATH}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
@@ -249,7 +272,7 @@ workflows:
branches:
only:
- /release\/.*/
##ENDIF_CD##
##ENDIF_CD_NON_PROD##
##IF_GITHUB_RELEASE##
- munitor/github_release:
name: github-release
@@ -265,9 +288,10 @@ workflows:
production:
jobs:
- munitor/npm_build_and_test:
- munitor/node_build_and_test:
name: build-and-test
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
##IF_NPM_AUTH##
npm_auth: true
npm_scopes: '${MUNITOR_NPM_SCOPES}'
@@ -311,6 +335,7 @@ workflows:
environment: ${MUNITOR_CD_ENV_PROD}
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
cd_path: ${MUNITOR_CD_PATH}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
@@ -0,0 +1,313 @@
version: 2.1
orbs:
munitor: ${MUNITOR_ORB_SLUG}@${MUNITOR_ORB_VERSION}
workflows:
pr-checks:
jobs:
- munitor/node_build_and_test:
name: build-and-test
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
##IF_NPM_AUTH##
npm_auth: true
npm_scopes: '${MUNITOR_NPM_SCOPES}'
npm_default_scope: '${MUNITOR_NPM_DEFAULT_SCOPE}'
##ENDIF_NPM_AUTH##
##IF_SERVICES##
services: '${MUNITOR_SERVICES_JSON}'
##ENDIF_SERVICES##
##IF_TEST_SETUP##
test_setup: ${MUNITOR_TEST_SETUP_SCRIPT}
##ENDIF_TEST_SETUP##
##IF_CUSTOM_TEST##
test_commands: '${MUNITOR_TEST_COMMANDS_JSON}'
##ENDIF_CUSTOM_TEST##
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/secrets_scan:
name: secrets-scan
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
##IF_SAST##
- munitor/sast_scan:
name: sast-scan
fail_on_findings: "${MUNITOR_SAST_FAIL_ON_FINDINGS}"
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
##ENDIF_SAST##
- munitor/node_code_quality:
name: code-quality
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/node_coverage:
name: coverage
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
min_coverage: "${MUNITOR_COVERAGE_MIN}"
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
##IF_COVERAGE_CMD##
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
##ENDIF_COVERAGE_CMD##
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
- munitor/node_security_scan:
name: security-scan
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
##IF_E2E##
- munitor/node_e2e_test:
name: e2e-tests
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
branches:
only:
- /feature\/.*/
- /hotfix\/.*/
##ENDIF_E2E##
develop:
jobs:
- munitor/node_build_and_test:
name: build-and-test
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
##IF_NPM_AUTH##
npm_auth: true
npm_scopes: '${MUNITOR_NPM_SCOPES}'
npm_default_scope: '${MUNITOR_NPM_DEFAULT_SCOPE}'
##ENDIF_NPM_AUTH##
##IF_SERVICES##
services: '${MUNITOR_SERVICES_JSON}'
##ENDIF_SERVICES##
##IF_TEST_SETUP##
test_setup: ${MUNITOR_TEST_SETUP_SCRIPT}
##ENDIF_TEST_SETUP##
##IF_CUSTOM_TEST##
test_commands: '${MUNITOR_TEST_COMMANDS_JSON}'
##ENDIF_CUSTOM_TEST##
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only: develop
release-candidate:
jobs:
- munitor/node_build_and_test:
name: build-and-test
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
##IF_NPM_AUTH##
npm_auth: true
npm_scopes: '${MUNITOR_NPM_SCOPES}'
npm_default_scope: '${MUNITOR_NPM_DEFAULT_SCOPE}'
##ENDIF_NPM_AUTH##
##IF_SERVICES##
services: '${MUNITOR_SERVICES_JSON}'
##ENDIF_SERVICES##
##IF_TEST_SETUP##
test_setup: ${MUNITOR_TEST_SETUP_SCRIPT}
##ENDIF_TEST_SETUP##
##IF_CUSTOM_TEST##
test_commands: '${MUNITOR_TEST_COMMANDS_JSON}'
##ENDIF_CUSTOM_TEST##
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only:
- /release\/.*/
- munitor/secrets_scan:
name: secrets-scan
filters:
branches:
only:
- /release\/.*/
##IF_SAST##
- munitor/sast_scan:
name: sast-scan
fail_on_findings: "${MUNITOR_SAST_FAIL_ON_FINDINGS}"
filters:
branches:
only:
- /release\/.*/
##ENDIF_SAST##
- munitor/node_code_quality:
name: code-quality
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
branches:
only:
- /release\/.*/
- munitor/node_coverage:
name: coverage
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
min_coverage: "${MUNITOR_COVERAGE_MIN}"
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
##IF_COVERAGE_CMD##
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
##ENDIF_COVERAGE_CMD##
requires:
- build-and-test
filters:
branches:
only:
- /release\/.*/
- munitor/node_security_scan:
name: security-scan
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
branches:
only:
- /release\/.*/
##IF_E2E##
- munitor/node_e2e_test:
name: e2e-tests
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
branches:
only:
- /release\/.*/
##ENDIF_E2E##
##IF_SONAR##
- munitor/node_sonar_scan:
name: sonar-scan
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
requires:
- build-and-test
- coverage
context:
- ${MUNITOR_CONTEXT_SONAR}
filters:
branches:
only:
- /release\/.*/
##ENDIF_SONAR##
##IF_SBOM##
- munitor/node_sbom:
name: sbom
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
requires:
- code-quality
- coverage
- secrets-scan
##IF_SAST##
- sast-scan
##ENDIF_SAST##
- security-scan
##IF_E2E##
- e2e-tests
##ENDIF_E2E##
filters:
branches:
only:
- /release\/.*/
##ENDIF_SBOM##
- munitor/github_release:
name: github-release
release_artifacts: "main.js manifest.json styles.css"
requires:
- code-quality
- coverage
- secrets-scan
##IF_SAST##
- sast-scan
##ENDIF_SAST##
- security-scan
##IF_E2E##
- e2e-tests
##ENDIF_E2E##
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only:
- /release\/.*/
production:
jobs:
- munitor/node_build_and_test:
name: build-and-test
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
##IF_NPM_AUTH##
npm_auth: true
npm_scopes: '${MUNITOR_NPM_SCOPES}'
npm_default_scope: '${MUNITOR_NPM_DEFAULT_SCOPE}'
##ENDIF_NPM_AUTH##
##IF_SERVICES##
services: '${MUNITOR_SERVICES_JSON}'
##ENDIF_SERVICES##
##IF_TEST_SETUP##
test_setup: ${MUNITOR_TEST_SETUP_SCRIPT}
##ENDIF_TEST_SETUP##
##IF_CUSTOM_TEST##
test_commands: '${MUNITOR_TEST_COMMANDS_JSON}'
##ENDIF_CUSTOM_TEST##
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only: main
- munitor/github_release:
name: github-release
release_artifacts: "main.js manifest.json styles.css"
requires:
- build-and-test
context:
- ${MUNITOR_CONTEXT_GITHUB}
filters:
branches:
only: main
@@ -124,13 +124,14 @@
branches:
only: __BRANCH_FILTER__
##ENDIF_SBOM##
##IF_CD##
##IF_CD_NON_PROD##
- munitor/update_cd_repo:
name: update-cd-repo
cd_repo: ${MUNITOR_CD_REPO}
environment: __CD_ENVIRONMENT__
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
cd_path: ${MUNITOR_CD_PATH}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
@@ -143,4 +144,4 @@
filters:
branches:
only: __BRANCH_FILTER__
##ENDIF_CD##
##ENDIF_CD_NON_PROD##
@@ -124,13 +124,14 @@
branches:
only: __BRANCH_FILTER__
##ENDIF_SBOM##
##IF_CD##
##IF_CD_NON_PROD##
- munitor/update_cd_repo:
name: update-cd-repo
cd_repo: ${MUNITOR_CD_REPO}
environment: __CD_ENVIRONMENT__
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
cd_path: ${MUNITOR_CD_PATH}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
@@ -143,4 +144,4 @@
filters:
branches:
only: __BRANCH_FILTER__
##ENDIF_CD##
##ENDIF_CD_NON_PROD##
@@ -1,8 +1,9 @@
__WORKFLOW_NAME__:
jobs:
- munitor/npm_build_and_test:
- munitor/node_build_and_test:
name: build-and-test
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
##IF_NPM_AUTH##
npm_auth: true
npm_scopes: '${MUNITOR_NPM_SCOPES}'
@@ -33,17 +34,22 @@
filters:
branches:
only: __BRANCH_FILTER__
- munitor/npm_code_quality:
- munitor/node_code_quality:
name: code-quality
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
branches:
only: __BRANCH_FILTER__
- munitor/npm_coverage:
- munitor/node_coverage:
name: coverage
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
min_coverage: "${MUNITOR_COVERAGE_MIN}"
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
##IF_COVERAGE_CMD##
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
##ENDIF_COVERAGE_CMD##
@@ -52,16 +58,20 @@
filters:
branches:
only: __BRANCH_FILTER__
- munitor/npm_security_scan:
- munitor/node_security_scan:
name: security-scan
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
branches:
only: __BRANCH_FILTER__
##IF_E2E##
- munitor/npm_e2e_test:
- munitor/node_e2e_test:
name: e2e-tests
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
@@ -69,9 +79,10 @@
only: __BRANCH_FILTER__
##ENDIF_E2E##
##IF_SONAR##
- munitor/npm_sonar_scan:
- munitor/node_sonar_scan:
name: sonar-scan
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
requires:
- build-and-test
@@ -107,9 +118,10 @@
branches:
only: __BRANCH_FILTER__
##IF_SBOM##
- munitor/npm_sbom:
- munitor/node_sbom:
name: sbom
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
requires:
@@ -125,13 +137,14 @@
branches:
only: __BRANCH_FILTER__
##ENDIF_SBOM##
##IF_CD##
##IF_CD_NON_PROD##
- munitor/update_cd_repo:
name: update-cd-repo
cd_repo: ${MUNITOR_CD_REPO}
environment: __CD_ENVIRONMENT__
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
cd_path: ${MUNITOR_CD_PATH}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
@@ -144,4 +157,4 @@
filters:
branches:
only: __BRANCH_FILTER__
##ENDIF_CD##
##ENDIF_CD_NON_PROD##
@@ -1,8 +1,9 @@
__WORKFLOW_NAME__:
jobs:
- munitor/npm_build_and_test:
- munitor/node_build_and_test:
name: build-and-test
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
##IF_NPM_AUTH##
npm_auth: true
npm_scopes: '${MUNITOR_NPM_SCOPES}'
@@ -33,17 +34,22 @@
filters:
branches:
only: __BRANCH_FILTER__
- munitor/npm_code_quality:
- munitor/node_code_quality:
name: code-quality
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
branches:
only: __BRANCH_FILTER__
- munitor/npm_coverage:
- munitor/node_coverage:
name: coverage
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
min_coverage: "${MUNITOR_COVERAGE_MIN}"
coverage_tool: ${MUNITOR_COVERAGE_TOOL}
coverage_summary_path: "${MUNITOR_COVERAGE_SUMMARY_PATH}"
##IF_COVERAGE_CMD##
coverage_command: ${MUNITOR_COVERAGE_COMMAND}
##ENDIF_COVERAGE_CMD##
@@ -52,16 +58,20 @@
filters:
branches:
only: __BRANCH_FILTER__
- munitor/npm_security_scan:
- munitor/node_security_scan:
name: security-scan
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
branches:
only: __BRANCH_FILTER__
##IF_E2E##
- munitor/npm_e2e_test:
- munitor/node_e2e_test:
name: e2e-tests
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
requires:
- build-and-test
filters:
@@ -69,9 +79,10 @@
only: __BRANCH_FILTER__
##ENDIF_E2E##
##IF_SONAR##
- munitor/npm_sonar_scan:
- munitor/node_sonar_scan:
name: sonar-scan
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
sonar_project_key: ${MUNITOR_SONAR_PROJECT_KEY}
requires:
- build-and-test
@@ -107,9 +118,10 @@
branches:
only: __BRANCH_FILTER__
##IF_SBOM##
- munitor/npm_sbom:
- munitor/node_sbom:
name: sbom
node_version: "${MUNITOR_NODE_VERSION}"
package_manager: "${MUNITOR_PACKAGE_MANAGER}"
context:
- ${MUNITOR_CONTEXT_GITHUB}
requires:
@@ -125,13 +137,14 @@
branches:
only: __BRANCH_FILTER__
##ENDIF_SBOM##
##IF_CD##
##IF_CD_NON_PROD##
- munitor/update_cd_repo:
name: update-cd-repo
cd_repo: ${MUNITOR_CD_REPO}
environment: __CD_ENVIRONMENT__
cd_format: ${MUNITOR_CD_FORMAT}
cd_image_name: ${MUNITOR_DOCKER_REGISTRY}/${MUNITOR_IMAGE_NAME}
cd_path: ${MUNITOR_CD_PATH}
##IF_SUPPLEMENTAL##
supplemental_images: '${MUNITOR_SUPPLEMENTAL_IMAGES_JSON}'
##ENDIF_SUPPLEMENTAL##
@@ -144,4 +157,4 @@
filters:
branches:
only: __BRANCH_FILTER__
##ENDIF_CD##
##ENDIF_CD_NON_PROD##
+34 -7
View File
@@ -18,6 +18,7 @@ VERSION="${PROJECT_VERSION:?PROJECT_VERSION not set}"
ENVIRONMENT="${CD_ENVIRONMENT:-}"
FORMAT="${CD_FORMAT:-helm}"
IMAGE_NAME="${CD_IMAGE_NAME:-}"
CONFIGURED_PATH="${CD_PATH:-}"
VALUES="${VALUES_FILE:-values.yaml}"
KEY="${IMAGE_KEY:-image.tag}"
@@ -49,8 +50,15 @@ if [[ "${FORMAT}" == "kustomize" ]]; then
exit 1
fi
# Find kustomization.yaml in overlays/<env>/
if [[ -n "${ENVIRONMENT}" ]]; then
# Explicit paths support consolidated GitOps repositories. Existing dedicated
# repositories retain the overlays/<environment> convention by default.
if [[ -n "${CONFIGURED_PATH}" ]]; then
if [[ "${CONFIGURED_PATH}" == /* || "${CONFIGURED_PATH}" == *".."* || "${CONFIGURED_PATH}" != *kustomization.yaml ]]; then
echo "ERROR: cd.path must be a relative path to a kustomization.yaml file without '..'."
exit 1
fi
KUST_FILE="${CONFIGURED_PATH}"
elif [[ -n "${ENVIRONMENT}" ]]; then
KUST_FILE="overlays/${ENVIRONMENT}/kustomization.yaml"
else
KUST_FILE="kustomization.yaml"
@@ -58,12 +66,18 @@ if [[ "${FORMAT}" == "kustomize" ]]; then
if [[ ! -f "${KUST_FILE}" ]]; then
echo "ERROR: Kustomize file '${KUST_FILE}' not found in CD repo."
echo "Available overlays:"
ls overlays/ 2>/dev/null || echo " (no overlays directory)"
if [[ -z "${CONFIGURED_PATH}" ]]; then
echo "Available overlays:"
ls overlays/ 2>/dev/null || echo " (no overlays directory)"
fi
exit 1
fi
echo "Updating ${KUST_FILE}: ${IMAGE_NAME} -> ${VERSION}"
if ! yq -e ".images[] | select(.name == \"${IMAGE_NAME}\")" "${KUST_FILE}" >/dev/null; then
echo "ERROR: Image '${IMAGE_NAME}' is not declared in ${KUST_FILE}."
exit 1
fi
yq -i "(.images[] | select(.name == \"${IMAGE_NAME}\")).newTag = \"${VERSION}\"" "${KUST_FILE}"
yq -i "del((.images[] | select(.name == \"${IMAGE_NAME}\")).digest)" "${KUST_FILE}"
git add "${KUST_FILE}"
@@ -137,7 +151,20 @@ if git diff --cached --quiet; then
exit 0
fi
git commit -m "chore: update image tag to ${VERSION}"
git push origin HEAD
git commit -m "chore: update ${IMAGE_NAME:-image} to ${VERSION}"
echo "CD repo updated successfully."
# Independent pipelines may update a shared GitOps repository concurrently.
for attempt in 1 2 3; do
if git push origin HEAD; then
echo "CD repo updated successfully."
exit 0
fi
if [[ "${attempt}" == "3" ]]; then
echo "ERROR: Could not push the GitOps update after ${attempt} attempts."
exit 1
fi
echo "Push raced with another GitOps update; rebasing before retry ${attempt}/3."
git pull --rebase origin "$(git branch --show-current)"
done
+18
View File
@@ -0,0 +1,18 @@
pipeline: node-webapp
orb_version: dev:snapshot
image_name: investinginchester
node_version: "22"
docker:
registry: ghcr.io/koftwentytwo
use_repo_dockerfile: true
cd:
repo: KofTwentyTwo/cluster-gitops
format: kustomize
production_only: true
path: tenants/investinginchester/website/kustomization.yaml
contexts:
registry: ghcr
github: github
+12
View File
@@ -0,0 +1,12 @@
pipeline: obsidian-plugin
orb_version: dev:snapshot
node_version: "20"
sast: true
sbom: true
test:
commands: ["npm run build", "npm test"]
coverage:
tool: vitest
command: npx vitest run --coverage
contexts:
github: github
+10
View File
@@ -0,0 +1,10 @@
pipeline: node-webapp
orb_version: dev:snapshot
package_manager: pnpm
node_version: "22"
image_name: ghcr.io/koftwentytwo/pnpm-app
docker:
registry: ghcr.io/koftwentytwo
health:
path: /api/health
port: "3000"
+16
View File
@@ -137,6 +137,19 @@ extract_munitor_vars "${FIXTURES_DIR}/java-webapp-sast-warn.munitor.yml"
assert_eq "sast object form" "true" "${MUNITOR_SAST}"
assert_eq "sast fail_on_findings (object false)" "false" "${MUNITOR_SAST_FAIL_ON_FINDINGS}"
# --------------------------------------------------------------------------
# node-webapp consolidated GitOps fixture
# --------------------------------------------------------------------------
echo ""
echo "=== extract_munitor_vars: consolidated GitOps fixture ==="
extract_munitor_vars "${FIXTURES_DIR}/node-webapp-consolidated.munitor.yml"
assert_eq "consolidated cd repo" "KofTwentyTwo/cluster-gitops" "${MUNITOR_CD_REPO}"
assert_eq "consolidated cd format" "kustomize" "${MUNITOR_CD_FORMAT}"
assert_eq "consolidated cd path" "tenants/investinginchester/website/kustomization.yaml" "${MUNITOR_CD_PATH}"
assert_eq "production-only cd" "true" "${MUNITOR_CD_PRODUCTION_ONLY}"
assert_eq "non-production cd disabled" "false" "${MUNITOR_CD_NON_PROD}"
# --------------------------------------------------------------------------
# node.framework defaults
# --------------------------------------------------------------------------
@@ -263,6 +276,9 @@ assert_eq "health_port default 3000" "3000" "${MUNITOR_HEALTH_PORT}"
assert_eq "coverage min default" "70" "${MUNITOR_COVERAGE_MIN}"
assert_eq "package_manager default" "npm" "${MUNITOR_PACKAGE_MANAGER}"
extract_munitor_vars "${FIXTURES_DIR}/pnpm-webapp.munitor.yml"
assert_eq "pnpm package_manager is extracted" "pnpm" "${MUNITOR_PACKAGE_MANAGER}"
# --------------------------------------------------------------------------
# supplemental_images (enabled)
# --------------------------------------------------------------------------
+223 -40
View File
@@ -8,6 +8,8 @@ PASS=0
FAIL=0
GENERATE_SCRIPT="${PROJECT_DIR}/src/scripts/generate_config.sh"
GENERATED_CONFIG=$(mktemp)
trap 'rm -f "${GENERATED_CONFIG}"' EXIT
# --------------------------------------------------------------------------
# Helper: render a fixture by calling the real generate_config.sh script
@@ -17,8 +19,9 @@ render_fixture() {
local fixture="$1"
# Run the real script; capture stderr for diagnostics, return generated YAML
MUNITOR_CONFIG="${fixture}" bash "${GENERATE_SCRIPT}" >/dev/null 2>&1
cat /tmp/generated-config.yml
MUNITOR_CONFIG="${fixture}" MUNITOR_OUTPUT_FILE="${GENERATED_CONFIG}" \
bash "${GENERATE_SCRIPT}" >/dev/null 2>&1
cat "${GENERATED_CONFIG}"
}
run_test() {
@@ -36,7 +39,7 @@ run_test() {
return
}
if echo "${output}" | grep -q "${expected_pattern}"; then
if grep -q "${expected_pattern}" <<< "${output}"; then
echo "PASS"
PASS=$((PASS + 1))
else
@@ -61,7 +64,7 @@ run_negative_test() {
return
}
if echo "${output}" | grep -q "${forbidden_pattern}"; then
if grep -q "${forbidden_pattern}" <<< "${output}"; then
echo "FAIL (found forbidden pattern '${forbidden_pattern}')"
FAIL=$((FAIL + 1))
else
@@ -103,7 +106,7 @@ run_context_test() {
return
fi
if echo "${block}" | grep -q "${expected_pattern}"; then
if grep -q "${expected_pattern}" <<< "${block}"; then
echo "PASS"
PASS=$((PASS + 1))
else
@@ -143,7 +146,7 @@ run_negative_context_test() {
return
fi
if echo "${block}" | grep -q "${forbidden_pattern}"; then
if grep -q "${forbidden_pattern}" <<< "${block}"; then
echo "FAIL (found forbidden '${forbidden_pattern}' within block)"
FAIL=$((FAIL + 1))
else
@@ -175,13 +178,13 @@ run_test "java-webapp: includes sbom when enabled" \
"${FIXTURES_DIR}/java-webapp.munitor.yml" \
"sbom"
run_test "java-webapp: uses mvn sbom (not npm_sbom)" \
run_test "java-webapp: uses mvn sbom (not node_sbom)" \
"${FIXTURES_DIR}/java-webapp.munitor.yml" \
"munitor/sbom"
run_negative_test "java-webapp: does not use npm_sbom" \
run_negative_test "java-webapp: does not use node_sbom" \
"${FIXTURES_DIR}/java-webapp.munitor.yml" \
"munitor/npm_sbom"
"munitor/node_sbom"
# Java-webapp sonar should still gate docker-build-push (regression guard)
run_context_test "java-webapp: sonar-scan blocks docker-build-push" \
@@ -228,26 +231,26 @@ run_test "node-api: includes sonar when project_key set" \
"${FIXTURES_DIR}/node-api.munitor.yml" \
"sonar-scan"
run_test "node-api: uses npm_sonar_scan (not mvn sonar_scan)" \
run_test "node-api: uses node_sonar_scan (not mvn sonar_scan)" \
"${FIXTURES_DIR}/node-api.munitor.yml" \
"munitor/npm_sonar_scan"
"munitor/node_sonar_scan"
run_test "node-api: includes sbom when enabled" \
"${FIXTURES_DIR}/node-api.munitor.yml" \
"sbom"
run_test "node-api: uses npm_sbom (not mvn sbom)" \
run_test "node-api: uses node_sbom (not mvn sbom)" \
"${FIXTURES_DIR}/node-api.munitor.yml" \
"munitor/npm_sbom"
"munitor/node_sbom"
run_negative_test "node-api: does not use mvn sbom job" \
"${FIXTURES_DIR}/node-api.munitor.yml" \
"munitor/sbom:"
# Verify the npm_sbom block includes the node_version parameter
run_context_test "node-api: npm_sbom passes node_version" \
# Verify the node_sbom block includes the node_version parameter
run_context_test "node-api: node_sbom passes node_version" \
"${FIXTURES_DIR}/node-api.munitor.yml" \
"munitor/npm_sbom" \
"munitor/node_sbom" \
"requires:" \
"node_version:"
@@ -263,17 +266,17 @@ run_test "node-api: sonar-scan job still present in workflow" \
"${FIXTURES_DIR}/node-api.munitor.yml" \
"name: sonar-scan"
run_test "node-api: uses npm jobs" \
run_test "node-api: uses node jobs" \
"${FIXTURES_DIR}/node-api.munitor.yml" \
"npm_build_and_test"
"node_build_and_test"
run_test "node-api: includes npm code quality" \
run_test "node-api: includes node code quality" \
"${FIXTURES_DIR}/node-api.munitor.yml" \
"npm_code_quality"
"node_code_quality"
run_test "node-api: includes npm security scan" \
run_test "node-api: includes node security scan" \
"${FIXTURES_DIR}/node-api.munitor.yml" \
"npm_security_scan"
"node_security_scan"
# Test node-api-minimal: e2e/sbom/sonar/extended features should be excluded
run_test "node-api-minimal: renders node 22" \
@@ -349,9 +352,9 @@ run_test "node-api-services: coverage min from test.coverage.min_instruction" \
"${FIXTURES_DIR}/node-api-services.munitor.yml" \
'min_coverage: "70"'
run_test "node-api-services: uses npm_sbom for sbom" \
run_test "node-api-services: uses node_sbom for sbom" \
"${FIXTURES_DIR}/node-api-services.munitor.yml" \
"munitor/npm_sbom"
"munitor/node_sbom"
run_negative_test "node-api-services: does not use mvn sbom job" \
"${FIXTURES_DIR}/node-api-services.munitor.yml" \
@@ -403,33 +406,33 @@ run_test "node-webapp: includes sonar when project_key set" \
"${FIXTURES_DIR}/node-webapp.munitor.yml" \
"sonar-scan"
run_test "node-webapp: uses npm_sonar_scan (not mvn sonar_scan)" \
run_test "node-webapp: uses node_sonar_scan (not mvn sonar_scan)" \
"${FIXTURES_DIR}/node-webapp.munitor.yml" \
"munitor/npm_sonar_scan"
"munitor/node_sonar_scan"
run_test "node-webapp: includes sbom when enabled" \
"${FIXTURES_DIR}/node-webapp.munitor.yml" \
"sbom"
run_test "node-webapp: uses npm_sbom (not mvn sbom)" \
run_test "node-webapp: uses node_sbom (not mvn sbom)" \
"${FIXTURES_DIR}/node-webapp.munitor.yml" \
"munitor/npm_sbom"
"munitor/node_sbom"
run_test "node-webapp: uses npm_build_and_test" \
run_test "node-webapp: uses node_build_and_test" \
"${FIXTURES_DIR}/node-webapp.munitor.yml" \
"npm_build_and_test"
"node_build_and_test"
run_test "node-webapp: includes npm code quality" \
run_test "node-webapp: includes node code quality" \
"${FIXTURES_DIR}/node-webapp.munitor.yml" \
"npm_code_quality"
"node_code_quality"
run_test "node-webapp: includes npm coverage" \
run_test "node-webapp: includes node coverage" \
"${FIXTURES_DIR}/node-webapp.munitor.yml" \
"npm_coverage"
"node_coverage"
run_test "node-webapp: includes npm security scan" \
run_test "node-webapp: includes node security scan" \
"${FIXTURES_DIR}/node-webapp.munitor.yml" \
"npm_security_scan"
"node_security_scan"
# Sonar non-blocking: sonar-scan should NOT be in docker-build-push requires
run_negative_context_test "node-webapp: sonar-scan not in docker-build-push requires" \
@@ -451,6 +454,24 @@ run_context_test "node-webapp: docker-build-push includes health_path" \
"context:" \
"health_path: /api/health"
run_context_test "node-webapp consolidated: production targets explicit GitOps path" \
"${FIXTURES_DIR}/node-webapp-consolidated.munitor.yml" \
"production:" \
"github-release:" \
"cd_path: tenants/investinginchester/website/kustomization.yaml"
run_negative_context_test "node-webapp consolidated: develop does not update production" \
"${FIXTURES_DIR}/node-webapp-consolidated.munitor.yml" \
"develop:" \
"staging:" \
"update-cd-repo"
run_negative_context_test "node-webapp consolidated: release does not update production" \
"${FIXTURES_DIR}/node-webapp-consolidated.munitor.yml" \
"release-candidate:" \
"production:" \
"update-cd-repo"
# Test node-webapp-minimal: e2e/sbom/sonar should be excluded
run_test "node-webapp-minimal: renders node 22" \
"${FIXTURES_DIR}/node-webapp-minimal.munitor.yml" \
@@ -689,13 +710,13 @@ run_negative_context_test "node-api: production has no coverage" \
"${FIXTURES_DIR}/node-api.munitor.yml" \
"production:" \
"release-candidate:" \
"npm_coverage"
"node_coverage"
run_negative_context_test "node-api: production has no security-scan" \
"${FIXTURES_DIR}/node-api.munitor.yml" \
"production:" \
"release-candidate:" \
"npm_security_scan"
"node_security_scan"
# node-api: release-candidate has quality gates
run_context_test "node-api: release-candidate has code-quality" \
@@ -728,13 +749,13 @@ run_negative_context_test "node-webapp: production has no coverage" \
"${FIXTURES_DIR}/node-webapp.munitor.yml" \
"production:" \
"release-candidate:" \
"npm_coverage"
"node_coverage"
run_negative_context_test "node-webapp: production has no security-scan" \
"${FIXTURES_DIR}/node-webapp.munitor.yml" \
"production:" \
"release-candidate:" \
"npm_security_scan"
"node_security_scan"
# node-webapp: release-candidate has quality gates
run_context_test "node-webapp: release-candidate has code-quality" \
@@ -753,6 +774,24 @@ run_test "node-webapp: includes update-cd-repo when cd.repo set" \
"${FIXTURES_DIR}/node-webapp.munitor.yml" \
"update-cd-repo"
# ============================================================
# pnpm node-webapp
# ============================================================
echo ""
echo "=== pnpm node-webapp tests ==="
run_test "pnpm-webapp: uses node_build_and_test" \
"${FIXTURES_DIR}/pnpm-webapp.munitor.yml" \
"node_build_and_test"
run_test "pnpm-webapp: passes package_manager param" \
"${FIXTURES_DIR}/pnpm-webapp.munitor.yml" \
'package_manager: "pnpm"'
run_negative_test "pnpm-webapp: does not reference npm_build_and_test" \
"${FIXTURES_DIR}/pnpm-webapp.munitor.yml" \
"npm_build_and_test"
# terraform: has release-candidate and production workflows
run_test "terraform: has release-candidate workflow" \
"${FIXTURES_DIR}/terraform.munitor.yml" \
@@ -1213,6 +1252,150 @@ run_negative_test "gradle-webapp: no sonar block (sonar not configured)" \
"${FIXTURES_DIR}/gradle-webapp.munitor.yml" \
"sonar-scan"
# ============================================================
# obsidian-plugin template rendering
# ============================================================
echo ""
echo "=== obsidian-plugin Template Rendering Tests ==="
# Basic rendering
run_test "obsidian-plugin: renders node_version" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"node_version: \"20\""
run_test "obsidian-plugin: uses node_build_and_test" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"node_build_and_test"
run_test "obsidian-plugin: includes node code quality" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"node_code_quality"
run_test "obsidian-plugin: includes node coverage" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"node_coverage"
run_test "obsidian-plugin: includes node security scan" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"node_security_scan"
run_test "obsidian-plugin: includes sast-scan" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"sast-scan"
run_test "obsidian-plugin: includes secrets-scan" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"secrets-scan"
run_test "obsidian-plugin: includes sbom when enabled" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"sbom"
run_test "obsidian-plugin: includes test_commands" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"test_commands:"
run_test "obsidian-plugin: includes coverage_command" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"coverage_command:"
run_test "obsidian-plugin: includes coverage_tool vitest" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"coverage_tool: vitest"
# Workflow presence
run_test "obsidian-plugin: has pr-checks workflow" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"pr-checks:"
run_test "obsidian-plugin: has develop workflow" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"develop:"
run_test "obsidian-plugin: has release-candidate workflow" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"release-candidate:"
run_test "obsidian-plugin: has production workflow" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"production:"
# GitHub Release with artifacts
run_test "obsidian-plugin: has github-release job" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"github-release"
run_test "obsidian-plugin: github-release has release_artifacts" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"release_artifacts:"
run_test "obsidian-plugin: release_artifacts includes main.js" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"main.js manifest.json styles.css"
# Release-candidate has quality gates
run_context_test "obsidian-plugin: release-candidate has code-quality" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"release-candidate:" \
"production:" \
"code-quality"
run_context_test "obsidian-plugin: release-candidate has coverage" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"release-candidate:" \
"production:" \
"node_coverage"
run_context_test "obsidian-plugin: release-candidate has security-scan" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"release-candidate:" \
"production:" \
"node_security_scan"
# Production has NO quality gates (fast-path)
run_negative_context_test "obsidian-plugin: production has no code-quality" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"production:" \
"release-candidate:" \
"code-quality"
run_negative_context_test "obsidian-plugin: production has no coverage" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"production:" \
"release-candidate:" \
"node_coverage"
run_negative_context_test "obsidian-plugin: production has no security-scan" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"production:" \
"release-candidate:" \
"node_security_scan"
# Negative tests: no Docker/CD artifacts
run_negative_test "obsidian-plugin: no docker-build-push" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"docker-build-push"
run_negative_test "obsidian-plugin: no update-cd-repo" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"update-cd-repo"
run_negative_test "obsidian-plugin: no image_name" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"image_name:"
run_negative_test "obsidian-plugin: no registry" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"registry:"
run_negative_test "obsidian-plugin: no INCLUDE_DEPLOY" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"INCLUDE_DEPLOY"
run_negative_test "obsidian-plugin: no staging workflow" \
"${FIXTURES_DIR}/obsidian-plugin.munitor.yml" \
"staging:"
echo ""
echo "=== Results: ${PASS} passed, ${FAIL} failed ==="
+235 -2
View File
@@ -472,10 +472,25 @@ image_name: my-webapp
node_version: "22"
EOF
run_generate > /dev/null
if grep -q "gcr.io/distroless/nodejs22-debian12" "${WORK_DIR}/Dockerfile"; then
if grep -q "FROM gcr.io/distroless/nodejs22-debian13 AS runner" "${WORK_DIR}/Dockerfile"; then
pass
else
fail "expected distroless runner image in Dockerfile"
fail "expected distroless debian13 runner image in Dockerfile"
fi
teardown
echo -n " TEST: does not use the debian12 runner (open OpenSSL CVEs)... "
setup
cat > "${WORK_DIR}/.munitor.yml" <<'EOF'
pipeline: node-webapp
image_name: my-webapp
node_version: "22"
EOF
run_generate > /dev/null
if ! grep -q "debian12" "${WORK_DIR}/Dockerfile"; then
pass
else
fail "expected no debian12 base image"
fi
teardown
@@ -495,6 +510,224 @@ else
fi
teardown
# =============================================================================
# Test: node-webapp can preserve a repository Dockerfile
# =============================================================================
echo ""
echo "=== node-webapp Repository Dockerfile ==="
echo -n " TEST: preserves an opted-in repository Dockerfile... "
setup
cat > "${WORK_DIR}/.munitor.yml" <<'EOF'
pipeline: node-webapp
image_name: my-webapp
docker:
use_repo_dockerfile: true
EOF
echo "CUSTOM RUNTIME" > "${WORK_DIR}/Dockerfile"
OUTPUT=$(run_generate)
if grep -q "CUSTOM RUNTIME" "${WORK_DIR}/Dockerfile" && echo "${OUTPUT}" | grep -q "Using repository Dockerfile"; then
pass
else
fail "repository Dockerfile was not preserved"
fi
teardown
echo -n " TEST: rejects opt-in when Dockerfile is missing... "
setup
cat > "${WORK_DIR}/.munitor.yml" <<'EOF'
pipeline: node-webapp
image_name: my-webapp
docker:
use_repo_dockerfile: true
EOF
if OUTPUT=$(run_generate); then
fail "expected missing Dockerfile to fail"
elif echo "${OUTPUT}" | grep -q "requires a Dockerfile"; then
pass
else
fail "expected a clear missing Dockerfile error"
fi
teardown
# =============================================================================
# Test: node-webapp with pnpm generates correct Dockerfile
# =============================================================================
echo ""
echo "=== node-webapp pnpm Dockerfile Generation ==="
echo -n " TEST: pnpm node-webapp includes corepack enable... "
setup
cat > "${WORK_DIR}/.munitor.yml" <<'EOF'
pipeline: node-webapp
image_name: my-webapp
node_version: "22"
package_manager: pnpm
EOF
run_generate > /dev/null
if grep -q 'corepack enable' "${WORK_DIR}/Dockerfile"; then
pass
else
fail "expected corepack enable"
fi
teardown
echo -n " TEST: pnpm node-webapp uses pnpm install --frozen-lockfile... "
setup
cat > "${WORK_DIR}/.munitor.yml" <<'EOF'
pipeline: node-webapp
image_name: my-webapp
node_version: "22"
package_manager: pnpm
EOF
run_generate > /dev/null
if grep -q 'pnpm install --frozen-lockfile' "${WORK_DIR}/Dockerfile"; then
pass
else
fail "expected pnpm install --frozen-lockfile"
fi
teardown
echo -n " TEST: pnpm node-webapp does not contain npm ci... "
setup
cat > "${WORK_DIR}/.munitor.yml" <<'EOF'
pipeline: node-webapp
image_name: my-webapp
node_version: "22"
package_manager: pnpm
EOF
run_generate > /dev/null
if ! grep -q 'npm ci' "${WORK_DIR}/Dockerfile"; then
pass
else
fail "expected no npm ci"
fi
teardown
echo -n " TEST: pnpm node-webapp uses distroless debian13 runner... "
setup
cat > "${WORK_DIR}/.munitor.yml" <<'EOF'
pipeline: node-webapp
image_name: my-webapp
node_version: "22"
package_manager: pnpm
EOF
run_generate > /dev/null
if grep -q "FROM gcr.io/distroless/nodejs22-debian13 AS runner" "${WORK_DIR}/Dockerfile"; then
pass
else
fail "expected distroless debian13 runner image in pnpm Dockerfile"
fi
teardown
# =============================================================================
# Test: node-api (nextjs) with pnpm generates correct Dockerfile
# =============================================================================
echo ""
echo "=== node-api pnpm (nextjs) Dockerfile Generation ==="
echo -n " TEST: pnpm node-api nextjs includes corepack enable... "
setup
cat > "${WORK_DIR}/.munitor.yml" <<'EOF'
pipeline: node-api
image_name: my-app
package_manager: pnpm
EOF
run_generate > /dev/null
if grep -q 'corepack enable' "${WORK_DIR}/Dockerfile"; then
pass
else
fail "expected corepack enable"
fi
teardown
echo -n " TEST: pnpm node-api nextjs uses pnpm install --frozen-lockfile... "
setup
cat > "${WORK_DIR}/.munitor.yml" <<'EOF'
pipeline: node-api
image_name: my-app
package_manager: pnpm
EOF
run_generate > /dev/null
if grep -q 'pnpm install --frozen-lockfile' "${WORK_DIR}/Dockerfile"; then
pass
else
fail "expected pnpm install --frozen-lockfile"
fi
teardown
echo -n " TEST: pnpm node-api nextjs does not contain npm ci... "
setup
cat > "${WORK_DIR}/.munitor.yml" <<'EOF'
pipeline: node-api
image_name: my-app
package_manager: pnpm
EOF
run_generate > /dev/null
if ! grep -q 'npm ci' "${WORK_DIR}/Dockerfile"; then
pass
else
fail "expected no npm ci"
fi
teardown
# =============================================================================
# Test: node-api (express) with pnpm generates correct Dockerfile
# =============================================================================
echo ""
echo "=== node-api pnpm (express) Dockerfile Generation ==="
echo -n " TEST: pnpm node-api express includes corepack enable... "
setup
cat > "${WORK_DIR}/.munitor.yml" <<'EOF'
pipeline: node-api
image_name: my-app
package_manager: pnpm
node:
framework: express
EOF
run_generate > /dev/null
if grep -q 'corepack enable' "${WORK_DIR}/Dockerfile"; then
pass
else
fail "expected corepack enable"
fi
teardown
echo -n " TEST: pnpm node-api express uses pnpm install --frozen-lockfile... "
setup
cat > "${WORK_DIR}/.munitor.yml" <<'EOF'
pipeline: node-api
image_name: my-app
package_manager: pnpm
node:
framework: express
EOF
run_generate > /dev/null
if grep -q 'pnpm install --frozen-lockfile' "${WORK_DIR}/Dockerfile"; then
pass
else
fail "expected pnpm install --frozen-lockfile"
fi
teardown
echo -n " TEST: pnpm node-api express does not contain npm ci... "
setup
cat > "${WORK_DIR}/.munitor.yml" <<'EOF'
pipeline: node-api
image_name: my-app
package_manager: pnpm
node:
framework: express
EOF
run_generate > /dev/null
if ! grep -q 'npm ci' "${WORK_DIR}/Dockerfile"; then
pass
else
fail "expected no npm ci"
fi
teardown
# =============================================================================
# Test: unsupported pipeline type
# =============================================================================
+104 -9
View File
@@ -20,27 +20,27 @@ fail() {
}
# =============================================================================
# Test: npm_test.sh - Custom test commands
# Test: node_test.sh - Custom test commands
# =============================================================================
echo "=== npm_test.sh Tests ==="
echo "=== node_test.sh Tests ==="
echo -n " TEST: runs custom test commands via bash -c (not eval)... "
# Verify the script uses bash -c, not eval
if grep -q 'bash -c "\${CMD}"' "${SRC_SCRIPTS}/npm_test.sh"; then
if grep -q 'bash -c "\${CMD}"' "${SRC_SCRIPTS}/node_test.sh"; then
pass
else
fail "script should use 'bash -c' not 'eval'"
fi
echo -n " TEST: creates reports/junit directory... "
if grep -q 'mkdir -p reports/junit' "${SRC_SCRIPTS}/npm_test.sh"; then
if grep -q 'mkdir -p reports/junit' "${SRC_SCRIPTS}/node_test.sh"; then
pass
else
fail "should create reports/junit"
fi
echo -n " TEST: handles empty TEST_COMMANDS_JSON... "
if grep -q '\[\]' "${SRC_SCRIPTS}/npm_test.sh" && grep -q 'null' "${SRC_SCRIPTS}/npm_test.sh"; then
if grep -q '\[\]' "${SRC_SCRIPTS}/node_test.sh" && grep -q 'null' "${SRC_SCRIPTS}/node_test.sh"; then
pass
else
fail "should handle empty/null JSON"
@@ -288,13 +288,13 @@ else
fi
# =============================================================================
# Test: npm_coverage_check.sh - Security
# Test: node_coverage_check.sh - Security
# =============================================================================
echo ""
echo "=== npm_coverage_check.sh Tests ==="
echo "=== node_coverage_check.sh Tests ==="
echo -n " TEST: uses bash -c for coverage command (not eval)... "
if grep -q 'bash -c "\${COVERAGE_COMMAND}"' "${SRC_SCRIPTS}/npm_coverage_check.sh"; then
if grep -q 'bash -c "\${COVERAGE_COMMAND}"' "${SRC_SCRIPTS}/node_coverage_check.sh"; then
pass
else
fail "should use 'bash -c' not 'eval'"
@@ -320,6 +320,22 @@ else
fail "should resolve environments/<env>/values.yaml"
fi
echo -n " TEST: supports a validated explicit Kustomize path... "
if grep -q 'CONFIGURED_PATH="${CD_PATH:-}"' "${SRC_SCRIPTS}/update_cd_repo.sh" &&
grep -q 'cd.path must be a relative path' "${SRC_SCRIPTS}/update_cd_repo.sh"; then
pass
else
fail "should support and validate an explicit Kustomize path"
fi
echo -n " TEST: retries concurrent shared-repository pushes... "
if grep -q 'for attempt in 1 2 3' "${SRC_SCRIPTS}/update_cd_repo.sh" &&
grep -q 'git pull --rebase' "${SRC_SCRIPTS}/update_cd_repo.sh"; then
pass
else
fail "should rebase and retry shared-repository push races"
fi
echo -n " TEST: deletes digest field when setting newTag (kustomize)... "
if grep -q 'del((.images\[\] | select(.name == \\"${IMAGE_NAME}\\")).digest)' "${SRC_SCRIPTS}/update_cd_repo.sh"; then
pass
@@ -494,6 +510,44 @@ else
fail "should use Alpine JRE with non-root user"
fi
echo ""
echo "=== node_install_deps.sh pnpm support ==="
echo -n " TEST: node_install_deps.sh handles pnpm case... "
if grep -q 'pnpm install --frozen-lockfile' "${SRC_SCRIPTS}/node_install_deps.sh"; then
pass
else
fail "missing pnpm install --frozen-lockfile"
fi
echo -n " TEST: node_install_deps.sh has Prisma auto-detection... "
if grep -q 'prisma/schema.prisma' "${SRC_SCRIPTS}/node_install_deps.sh"; then
pass
else
fail "missing Prisma auto-detection"
fi
echo -n " TEST: node_test.sh handles pnpm fallback... "
if grep -q 'pnpm test' "${SRC_SCRIPTS}/node_test.sh"; then
pass
else
fail "missing pnpm test fallback"
fi
echo -n " TEST: run_test_setup.sh supports inline commands... "
if grep -q 'bash -c' "${SRC_SCRIPTS}/run_test_setup.sh"; then
pass
else
fail "missing bash -c inline command support"
fi
echo -n " TEST: generate_dockerfile.sh has pnpm Dockerfile variant... "
if grep -q 'pnpm install --frozen-lockfile' "${SRC_SCRIPTS}/generate_dockerfile.sh"; then
pass
else
fail "missing pnpm Dockerfile variant"
fi
# =============================================================================
# Test: install_kustomize.sh - Pinned binary install
# =============================================================================
@@ -888,7 +942,7 @@ YQ_VER=$(extract_version YQ_VERSION "${SRC_SCRIPTS}/install_yq.sh")
KUSTOMIZE_VER=$(extract_version KUSTOMIZE_VERSION "${SRC_SCRIPTS}/install_kustomize.sh")
KUBESEC_VER=$(extract_version KUBESEC_VERSION "${SRC_SCRIPTS}/install_kubesec.sh")
TFSEC_TRIVY_VER=$(extract_version TRIVY_VERSION "${SRC_SCRIPTS}/install_tfsec.sh")
SONAR_VER=$(extract_version SONAR_SCANNER_VERSION "${SRC_SCRIPTS}/npm_sonar.sh")
SONAR_VER=$(extract_version SONAR_SCANNER_VERSION "${SRC_SCRIPTS}/node_sonar.sh")
# All URLs target linux/amd64 -- matches the CI executor
URLS=(
@@ -1019,6 +1073,47 @@ else
fail "should detect pmdMain task"
fi
# =============================================================================
# Test: github_release.sh - Release artifact upload support
# =============================================================================
echo ""
echo "=== github_release.sh Artifact Upload Tests ==="
echo -n " TEST: supports MUNITOR_RELEASE_ARTIFACTS env var... "
if grep -q 'MUNITOR_RELEASE_ARTIFACTS' "${SRC_SCRIPTS}/github_release.sh"; then
pass
else
fail "should support MUNITOR_RELEASE_ARTIFACTS"
fi
echo -n " TEST: uses gh release upload for artifacts... "
if grep -q 'gh release upload' "${SRC_SCRIPTS}/github_release.sh"; then
pass
else
fail "should use gh release upload"
fi
echo -n " TEST: validates artifact files exist before upload... "
if grep -q 'not found' "${SRC_SCRIPTS}/github_release.sh" && grep -q '! -f' "${SRC_SCRIPTS}/github_release.sh"; then
pass
else
fail "should validate artifact files exist"
fi
echo -n " TEST: uses --clobber for idempotent uploads... "
if grep -q '\-\-clobber' "${SRC_SCRIPTS}/github_release.sh"; then
pass
else
fail "should use --clobber flag"
fi
echo -n " TEST: skips upload when MUNITOR_RELEASE_ARTIFACTS is empty... "
if grep -q 'if \[\[ -n "\${MUNITOR_RELEASE_ARTIFACTS:-}"' "${SRC_SCRIPTS}/github_release.sh"; then
pass
else
fail "should skip upload when artifacts is empty"
fi
# =============================================================================
# Summary
# =============================================================================