Skip to content

sidj-thr-org/actions-ci

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@sidj-thr/actions-ci

CI utilities — a modular, extensible CLI for GitHub automation. Replaces inline YAML scripts with tested, versioned Node.js commands.

Installation

npm install @sidj-thr/actions-ci

Or run directly in a GitHub Actions step:

- run: npx @sidj-thr/actions-ci pending-approvals --pr-number ${{ github.event.pull_request.number }}

Usage

actions-ci <command> [flags]

Commands:
  pending-approvals   Check PR approval status and post a review-status comment

Flags:
  --help, -h          Show help
  --version, -v       Show version

Commands

pending-approvals

Checks whether a PR has the required approvals from the right roles (Management/Team Lead and Members), then upserts a ## Review Status comment on the PR summarising the current state.

actions-ci pending-approvals \
  --pr-number 123 \
  --maintainers-team management \
  --team-leads-team team-leads \
  --min-approvals 2
Flag Description Default
--pr-number PR number to check (required)
--repo owner/repo string $GITHUB_REPOSITORY
--maintainers-team GitHub team slug for Management (required)
--team-leads-team GitHub team slug for Team Leads (required)
--min-approvals Minimum total approvals required 2

Exits with code 1 if the PR is not yet approved.

Comment format

The command upserts a single ## Review Status comment on the PR. Examples of what it posts:

Approved:

## Review Status
**Current Status: ✅ APPROVED**
Approvals so far: Management: 1, Team Lead: 1

Pending — missing codeowner approval:

## Review Status
**Current Status: ❌ PENDING**
Approvals so far: Member: 1

Pending reviews: Needs 1 Management or Team Lead.

Pending — codeowner present but total below threshold:

## Review Status
**Current Status: ❌ PENDING**
Approvals so far: Management: 1

Pending reviews: Needs 1 more from Management, Team Lead, or Member.

Zero-count roles are omitted from the summary line. If the comment already exists (identified by the ## Review Status marker) it is updated in-place rather than creating a new one.

Environment variables (required)

Variable Description
GITHUB_TOKEN Token used to post the review-status comment
GITHUB_APP_ID GitHub App ID used for team membership resolution
GITHUB_PRIVATE_KEY GitHub App private key (PEM)

Secrets are never accepted as CLI flags. They must be supplied via environment variables. This prevents tokens from appearing in the process list (ps aux), shell history, or CI log echoes.

Example GitHub Actions step

- name: Check PR approvals
  env:
    GITHUB_TOKEN: ${{ secrets.CI_TOKEN }}
    GITHUB_APP_ID: ${{ secrets.APP_ID }}
    GITHUB_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}
  run: |
    npx @sidj-thr/actions-ci pending-approvals \
      --pr-number ${{ github.event.pull_request.number }} \
      --maintainers-team management \
      --team-leads-team team-leads \
      --min-approvals 2

Security model

Threat Mitigation
Token in process list Secrets are env-only; no --token flag exists
Token in shell history Same — nothing to type
Token in CI log echo Same — nothing to echo
Token in error messages sanitizeError() + redact() on all output
CodeQL taint flow argv→API validatePrNumber() + validateRepo() applied before every call
Prototype pollution via argv paparam used (no minimist); paparam does not expose raw prototype-writable objects
Dependency CVEs Minimal runtime deps; run npm audit before releasing
Secrets in test files All network calls mocked; fixture tokens are fake sentinel values
Stack trace leaking secrets err.message only, never err.stack

How to add a new subcommand

  1. Create lib/commands/<name>/index.js:

    'use strict'
    const { command, flag, summary, footer } = require('paparam')
    const { Command } = require('../../command')
    const { validatePrNumber, sanitizeError, exitWithError } = require('../../helpers')
    const { myDomainFn } = require('./helpers')
    
    class MyCommand extends Command {
      constructor () {
        super({
          name: 'my-command',
          description: 'One-line description for --help',
          secrets: [
            { envVar: 'MY_SECRET', description: 'Token for ...' }
          ]
        })
      }
    
      toCommand () {
        const cmd = command(
          'my-command',
          summary(this.description),
          flag('--some-flag <value>', 'A flag'),
          footer(this._secretsFooter()),
          async () => {
            try { await this.run(cmd.flags) }
            catch (err) { exitWithError(sanitizeError(err)) }
          }
        )
        return cmd
      }
    
      async _run (flags) {
        // secrets already validated by base class
        // validate inputs first
        // call helpers — never pass secrets as arguments
      }
    }
    
    module.exports = new MyCommand()
  2. Create lib/commands/<name>/helpers.js — domain logic. Read secrets from process.env inside functions; never pass them as parameters.

  3. Register in main.js — add one line:

    const myCommand = require('./lib/commands/my-command/index')
    // ...
    const prog = command(
      'actions-ci',
      // ...
      pendingApprovals.toCommand(),
      myCommand.toCommand()   // ← add this
    )
  4. Write tests in test/unit/<name>-index.test.js and test/unit/<name>-helpers.test.js. Mock all network calls. Use fake sentinel tokens (ghp_FAKE_TOKEN_FOR_TESTING_ONLY_...) — they match the redact() pattern and let you test redaction logic.

Nothing in the framework layer (main.js, lib/command.js, lib/helpers.js) needs to change unless you are adding new generic behaviour that should be available to all commands.

Development

# Install dependencies
npm install

# Run tests
npm test

# Lint
npm run lint

# Lint + auto-fix
npm run lint:fix

# Check for dependency vulnerabilities
npm run audit

Publishing

This package has no build step — it is plain Node.js CJS with no compilation or bundling required.

npm publish automatically runs npm test first via the prepublishOnly hook, so a failing test suite will abort the publish before anything reaches the registry.

# Publish to npm (runs tests automatically first via prepublishOnly)
npm publish

In CI, tests should also run in a dedicated job before the publish job, so failures are caught at PR time rather than at publish time.

Requirements

Node.js >=18.0.0

License

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors