@cloud-cli/on
v1.5.7
Published
CLI entry point for the on plugin ecosystem.
Readme
🏃 @cloud-cli/on
General-purpose workflows
A self-hosted, lightweight, high-performance CI/CD runner engine built for Node.js.
Designed with a strict security-first boundary, native JavaScript AST evaluation, zero-DSL template literals, and a built-in terminal log web dashboard.
🌟 Key Highlights
- Strict Code/Data Separation:
run:steps are executed verbatim as raw process scripts. Expressions and dynamic data bindings are isolated strictly toenv:, eliminating shell-injection vectors entirely. - Standard ES Template Syntax (
${...}): No custom DSL wrappers like${{ }}or{{ }}. If a field contains${...}, it evaluates standard JavaScript template string logic via AST. - Deterministic Field Evaluation: No silent fallbacks or ambiguous type conversions. Plain strings remain literal strings; conditions in
if:fields run as strict JS boolean expressions. - Built-in Dark Mode Web UI (
/runs): Monitor job statuses live, inspect workspace inputs, and view ANSI-colored terminal log streams rendered in real-time. - System & Container Execution Drivers: Run steps directly as detached host process groups or inside isolated Docker/Systemd transient units.
- Automatic Secret Redaction: Secrets loaded from
.envare automatically masked (***) across all terminal log outputs and report snapshots.
📦 Project Structure
my-project/
├── on/ # Workflow definitions directory
│ ├── release.yml
│ └── test.yml
├── .env # Local secrets (git-ignored)
├── runner.config.mjs # (Optional) Engine configuration
└── package.json
🚀 Quick Start
1. Install & Run
Run the engine directly via npx or pnpm dlx:
# Start full engine (Ingress HTTP Gateway + 5 Worker Loops)
npx @cloud-cli/on start
2. Configure Secrets (.env)
Secrets are automatically loaded from .env at the root of your project. Prefix secrets with SECRET_:
SECRET_NPM_TOKEN="npm_1234567890abcdef"
SECRET_GITHUB_TOKEN="ghp_1234567890abcdef"
SECRET_GITHUB_WEBHOOK_SECRET="my-webhook-secret"SECRET_GITHUB_WEBHOOK_SECRET is required to validate incoming webhooks from GitHub
3. Define a Workflow (on/release.yml)
name: Build and Publish Release
on:
github:
if: inputs.event === 'push' && inputs.branch === 'main'
concurrency:
group: release-${inputs.repo}
cancel-in-progress: true
steps:
- id: checkout
name: Checkout Code Repository
env:
CLONE_URL: ${inputs.clone_url}
COMMIT_SHA: ${inputs.commit_sha}
run: |
git clone --depth 1 "$CLONE_URL" .
git checkout "$COMMIT_SHA"
- id: install-and-build
name: Install Dependencies & Build
run: |
pnpm install
pnpm run build
- id: publish
name: Publish to NPM
env:
NPM_TOKEN: ${secrets.NPM_TOKEN}
run: |
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
npx --yes semantic-release@24 -b main --no-ci💻 CLI Usage & Commands
npx @cloud-cli/on [command] [options]
Commands
| Command | Description |
| ------------------- | ---------------------------------------------------------------------------------------- |
| start-server | Runs Webhook Ingress Gateway (the HTTP server receiving webhooks). |
| start-workers | Runs Worker Polling loops (Scalable Workers). |
| validate | Parses and validates all YAML workflows in your workflows folder without executing jobs. |
CLI and Environment Options
| Flag | Option | Default | Env | Description |
| ---- | ------------- | --------------------- | --------------------- | ----------------------------------------- |
| -h | --help | — | - | Prints CLI help message and exits. |
| -c | --config | ./runner.config.mjs | RUNNER_CONFIG_FILE | Path to JavaScript configuration file. |
| -d | --database | - | RUNNER_DATABASE_URL | SQLite database file path or HTTP URL. |
| -w | --workflows | on/ | RUNNER_WORKFLOWS | Directory where workflow YAML files live. |
| -p | --port | 11235 | PORT | Port for the Ingress HTTP server. |
| -k | --workers | 5 | RUNNER_WORKERS | Number of worker loop threads to spawn. |
| | | | RUNNER_ADMIN_SECRET | Admin token to refresh secrets via API |
Secrets
⚙️ Configuration Reference
You can customize engine behavior using runner.config.mjs in your project root:
// runner.config.mjs
import { HtmlReporter, SlackReporter, JsonFileReporter } from '@cloud-cli/on/reporters';
export default {
port: 3000,
workers: 5,
workflows: '/home/workflows/',
storagePath: '/tmp/workspaces',
database: 'https://remote.db.com/',
// Global environment variables passed to all steps
env: {
NODE_ENV: 'production',
},
// Custom execution reporters
reporters: [
new JsonFileReporter({ outputDir: './reports/json' }),
new HtmlReporter({ outputDir: './reports/html' }),
new SlackReporter({
webhookUrl: process.env.SLACK_WEBHOOK_URL,
channel: '#ci-deployments',
}),
],
};📐 Deterministic Evaluation Rules
To prevent syntax ambiguity and injection risks, fields in workflow definitions operate under three strict modes:
┌──────────────────────────────────────────────────────────────────────────┐
│ 1. RAW PASSTHROUGH MODE (`run:`) │
│ • Executed verbatim as a shell process command. │
│ • No string replacements or engine parsing performed. │
│ • Access environment variables strictly via shell syntax: $MY_VAR. │
└──────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────┐
│ 2. EXPRESSION MODE (`if:`, `eval:`) │
│ • Evaluated strictly as pure JavaScript expressions via Acorn AST. │
│ • Must be valid JS syntax (e.g. `inputs.branch === 'main'`). │
│ • Automatically coerced to boolean in `if:` conditions. │
└──────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────┐
│ 3. DETERMINISTIC VALUE MODE (`env:`, `name:`, `image:`, `group:`) │
│ • Plain strings WITHOUT `${}` remain 100% raw literal strings. │
│ • Strings WITH `${...}` evaluate as standard ES Template Literals. │
│ • Example: `node:${inputs.node_version}-alpine` │
└──────────────────────────────────────────────────────────────────────────┘
Context Scope Available in Expressions
Within ${...}, if:, and eval: contexts, the following object scopes are exposed:
inputs: Payload key-values received from incoming webhooks.env: Merged environment variables from global config and workflow definitions.secrets: Unmasked secret values loaded from.envor environment variables (SECRET_prefix stripped).steps: Execution statuses and outputs from previous steps in the workflow (steps.<id>.status,steps.<id>.outputs).BUILTIN_HELPERS: JS utilities includingString,Number,Boolean, andJSON.parse/JSON.stringify.
🌐 Webhook Ingress Gateway & Dashboard
The Ingress Gateway listens for incoming HTTP requests and serves the live web UI.
Endpoint Matrix
| Method | Endpoint | Description |
| ---------- | ------------------ | -------------------------------------------------------------------------------------- |
| POST | /webhooks/github | Webhook endpoint for GitHub events. Evaluates on.github.if triggers. |
| GET | /runs | Dashboard: Live dark-mode monitoring page listing recent jobs and worker health. |
| GET | /runs/:jobId | Job Report: Interactive HTML trace view with step timings and terminal log output. |
Dashboard Features
- Real-time Auto-Refresh:
/runsautomatically refreshes job queue statuses (PENDING,RUNNING,SUCCESS,FAILED,CANCELLED). - ANSI Terminal Rendering: Uses
ansi_upto render bash colors, bold highlights, and console outputs accurately in step log boxes. - Payload Inspection: View JSON inputs received from webhooks for easy debugging.
🔒 Security & Hardening
- Environment Variable Boundary:
By forcing shell steps to consume data via process environment variables (
$CLONE_URL), malicious webhook payloads containing shell delimiters (e.g.; rm -rf /) cannot mutate shell script execution trees. - Prototype Pollution Protection:
AST evaluation explicitly blocks access to dangerous JS properties (
constructor,__proto__,prototype). - Payload Size Guard: The Ingress server enforces a strict 5MB payload limit to prevent Out-Of-Memory (OOM) denial-of-service attacks.
- Signal Traps & Resource Cleanup:
Graceful process traps (
SIGINT,SIGTERM) ensure active job handles are safely terminated, file descriptors are closed, and temp.env/.outfiles are removed viatry ... finallyblocks.
Development
pnpm i
pnpm run lint
pnpm run test
pnpm run build