Skip to content

Latest commit

 

History

143 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ts-ci-cd-lib

Reusable CI/CD workflows for TetraScience repositories.

Table of Contents

Workflows

Examples below use @main for readability. Production callers should pin reusable workflow references to an immutable release tag or commit SHA.

actionlint

Reusable workflow that runs actionlint to lint all GitHub Actions workflow files in the repo. Catches syntax errors, type mismatches, deprecated features, and security issues in workflow YAML.

Usage

jobs:
  actionlint:
    uses: tetrascience/ts-ci-cd-lib/.github/workflows/ghactionlint.yml@main

Inputs

Input Description Required Default
actionlint_version actionlint version to install No "1.7.12"
actionlint_sha256 SHA-256 of actionlint_<version>_linux_amd64.tar.gz (must match actionlint_version) No pinned to default version
zizmor_version zizmor version to install No "1.16.3"
zizmor_sha256 SHA-256 of zizmor-x86_64-unknown-linux-gnu.tar.gz (must match zizmor_version) No pinned to default version

Both binaries are downloaded from GitHub release assets and verified against pinned SHA-256 hashes. To bump either tool, set the *_version input alongside the matching *_sha256.


knip

Reusable workflow that runs knip to detect unused dependencies, exports, types, and files. Handles checkout, Node setup, Corepack, registry auth, install, and caching — callers just wire it up.

Usage

jobs:
  knip:
    uses: tetrascience/ts-ci-cd-lib/.github/workflows/knip.yml@main
    secrets:
      AUTH_TOKEN: ${{ secrets.JFROG_ARTIFACTORY_READ_NPM_AUTH }}
      REGISTRY: ${{ secrets.JFROG_ARTIFACTORY_NPM_VIRTUAL_URL }}

With options

  knip:
    uses: tetrascience/ts-ci-cd-lib/.github/workflows/knip.yml@main
    with:
      working_directory: packages/my-lib
      args: "--include dependencies"
    secrets:
      AUTH_TOKEN: ${{ secrets.JFROG_ARTIFACTORY_READ_NPM_AUTH }}
      REGISTRY: ${{ secrets.JFROG_ARTIFACTORY_NPM_VIRTUAL_URL }}

Inputs

Input Description Required Default
node_version Node.js version No "20"
knip_version Knip version to run No "6.10.0"
working_directory Directory to run knip in No "."
args Additional arguments passed to knip No ""

Secrets

Secret Description Required
AUTH_TOKEN JFrog npm auth token (for installing private deps) No
REGISTRY JFrog virtual registry URL No

Husky pre-commit

Same tool, no workflow needed — add directly to .husky/pre-commit:

npx --yes knip@6.10.0 --include dependencies

Knip config

For most repos, knip works out of the box. If you need to ignore specific patterns, create knip.json:

{
  "ignore": ["src/generated/**"],
  "ignoreDependencies": ["@types/*"]
}

publish-npm-package

Reusable workflow for publishing npm packages to JFrog Artifactory or the public npm registry.

Usage

name: Publish

on:
  push:
    tags:
      - "v*"

jobs:
  publish:
    uses: tetrascience/ts-ci-cd-lib/.github/workflows/publish-npm-package.yml@main
    with:
      node_version: "20"
    secrets:
      AUTH_TOKEN: ${{ secrets.JFROG_AUTH_TOKEN }}
      REGISTRY: ${{ secrets.JFROG_NPM_REGISTRY }}
      PUBLISH_REGISTRY: ${{ secrets.JFROG_NPM_PUBLISH_REGISTRY }}

Inputs

Input Description Required Default
node_version Node.js version No "20"
working_directory Directory containing the package to publish No "."
prerelease_tag Prerelease tag for version suffix and npm dist-tag (e.g., alpha, beta). Leave empty for non-prerelease versions. No ""
run_tests Whether to run tests before publishing No true
publish_to_public_npm Set to true to confirm publishing to public npm registry No false
pre_install_command Shell command to run after checkout and before registry auth + install. Runs at repo root (not working_directory). Use for codegen that produces the working_directory contents, and keep the value static in the caller workflow. No ""

Secrets

Secret Description Required
AUTH_TOKEN Authentication token for JFrog Artifactory No
REGISTRY JFrog Artifactory npm registry URL (for installing dependencies) No
PUBLISH_REGISTRY Registry URL for publishing the package Yes

Publishing to Public npm Registry

To publish to the public npm registry (https://registry.npmjs.org):

  1. Set publish_to_public_npm: true in the workflow inputs
  2. Configure npm trusted publishing for the package
  3. Set PUBLISH_REGISTRY to https://registry.npmjs.org

The workflow uses OIDC trusted publishing for public npm publishes and automatically updates the package scope from @tetrascience to @tetrascience-npm when publishing to the public registry.

check-links

Reusable workflow for checking broken links in markdown files using lychee. The calling repository provides its own lychee.toml configuration file in the repo root.

Usage

name: Link Check

on:
  push:
    branches: [main]
    paths: ["**/*.md", "lychee.toml", ".lycheeignore"]
  pull_request:
    paths: ["**/*.md", "lychee.toml", ".lycheeignore"]
  schedule:
    - cron: "0 8 * * 1"
  workflow_dispatch:

permissions:
  contents: read

jobs:
  check-links:
    uses: tetrascience/ts-ci-cd-lib/.github/workflows/check-links.yml@main

Inputs

Input Description Required Default
lychee_args Arguments passed to lychee. The calling repo's lychee.toml handles most configuration. No "--cache --max-cache-age 1d ."

Secrets

No secrets required. The workflow uses the automatically available GITHUB_TOKEN for authenticating with the GitHub API (to avoid rate limits when checking GitHub links).

Calling Repo Setup

Create a lychee.toml file in your repository root:

max_concurrency = 4
max_retries = 3
timeout = 20
accept = [200, 204, 301, 429]
exclude = [
  "^http://localhost",
  "^http://127\\.0\\.0\\.1",
  "^https?://example\\.com",
]

e2e-codebuild

Deploys a service to a predev environment, runs E2E tests via CodeBuild, and streams results back to the PR.

Usage

name: E2E Tests

on:
  pull_request:
    types: [opened, synchronize, reopened, labeled]
  push:
    branches: [development]

permissions:
  id-token: write
  contents: read

jobs:
  e2e:
    uses: tetrascience/ts-ci-cd-lib/.github/workflows/e2e-codebuild.yml@main
    with:
      environment: predev5
      deploy_paths: 'src/** migrations/** package.json yarn.lock Dockerfile'
      buildspec: buildspec.e2e.yml
    secrets:
      JFROG_ARTIFACTORY_NPM_VIRTUAL_URL: ${{ secrets.JFROG_ARTIFACTORY_NPM_VIRTUAL_URL }}
      JFROG_ARTIFACTORY_READ_NPM_AUTH: ${{ secrets.JFROG_ARTIFACTORY_READ_NPM_AUTH }}
      GITHUB_PAT: ${{ secrets.ARTIFACT_BUILD_GITHUB_TS_DEVOPS_PAT }}

Environment config

Non-secret test configuration (org slugs, app IDs, subdomain bases, etc.) should live in the service repo as a static config file (e.g. test/e2e/environments.ts) keyed by environment name. The workflow passes E2E_ENVIRONMENT so tests can select the right config at runtime. Only actual secrets (auth tokens) belong in SSM.

Buildspec

Each service provides its own buildspec.e2e.yml. The workflow passes the following as CodeBuild environment variables:

Variable Description
E2E_ENVIRONMENT Target environment name (e.g. predev3, dev)
JFROG_ARTIFACTORY_URL JFrog npm registry URL
JFROG_ARTIFACTORY_AUTH JFrog npm credentials

Authentication is handled by each service's test setup (e.g. a globalSetup that reads the TDP admin password from SSM and logs in). The CodeBuild IAM role has access to read SSM parameters under /tetrascience/{environment}/platform/ADMIN_PASSWORD.

Example buildspec:

version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 20
    commands:
      - npm install -g corepack
      - corepack enable
      - YARN_VERSION=$(node -p "require('./package.json').packageManager.split('@')[1]")
      - corepack prepare "yarn@$YARN_VERSION" --activate
      - |
        if [ -n "$JFROG_ARTIFACTORY_URL" ]; then
          yarn config set npmRegistryServer "$JFROG_ARTIFACTORY_URL"
          yarn config set npmAuthIdent "$JFROG_ARTIFACTORY_AUTH"
          yarn config set npmAlwaysAuth true
        fi
      - yarn install --immutable
  build:
    commands:
      - yarn test:e2e

Inputs

Input Required Default Description
environment Yes Target environment. One of predev, predev2predev8, dev, preuat, uat.
deploy_paths Yes Globs that trigger deploy. Empty = never deploy (observe-only, see below).
buildspec Yes Path to buildspec in the caller repo
deploy_workflow No ci.yml Workflow waited on after pushing to env branch
image_override No Override CodeBuild image
compute_type_override No Override CodeBuild compute (e.g. BUILD_GENERAL1_MEDIUM)
stream_codebuild_logs No false Stream raw CodeBuild logs into the GitHub Actions log
timeout_minutes No 25 Max minutes for the E2E job
passthrough_env No Extra non-secret env vars for the run, one NAME=VALUE per line. See below.
gh_environment No GitHub Environment to source per-env config from. The e2e job runs in it and forwards every E2E_* variable into the run. See below.

Secrets

Secret Required Description
JFROG_ARTIFACTORY_NPM_VIRTUAL_URL Yes JFrog npm registry URL
JFROG_ARTIFACTORY_READ_NPM_AUTH Yes JFrog npm credentials
GITHUB_PAT Yes PAT for cross-repo access + deploy push
ZEPHYR_CYCLE_KEY No Cycle to record into
ZEPHYR_API_TOKEN No Zephyr Scale API token
ZEPHYR_ACCOUNT_ID No Jira account id for executedById
E2E_USER_PASSWORD No Password for a dedicated e2e login user. Masked in the Actions log; reaches CodeBuild as a plaintext env override (see the note below).

The Zephyr secrets and E2E_USER_PASSWORD are forwarded as CodeBuild environment variables only when non-empty, so omitting them leaves the buildspec's own lookups in charge. Pass them when the caller already holds these values as GitHub secrets.

Note on E2E_USER_PASSWORD: it is masked in the GitHub Actions log, but it is forwarded as a plaintext CodeBuild environment override, so it appears in cleartext in the target account's CloudTrail StartBuild request and on the build's environment in the CodeBuild console. Use a dedicated low-privilege e2e user, not a shared credential.

Passing per-environment config

The suite runs inside CodeBuild, so GitHub Environment values do not reach the test process on their own; only what this workflow forwards does. There are two ways to forward config, and they compose.

gh_environment (recommended for per-environment config). Set it to a GitHub Environment name. The e2e job then runs in that Environment and forwards every non-empty E2E_* variable visible to the job (org, repo, and the selected Environment, with the Environment winning) into the run, with no per-variable wiring here. Add a new E2E_* var and it flows through automatically. When gh_environment is unset nothing is forwarded, so callers that do not opt in are unchanged. E2E_USER_PASSWORD is never forwarded as a variable even if set as one; it travels only through the masked secret input. A uses: caller cannot read Environment vars itself, which is why this reads them inside the job.

with:
  environment: uat
  deploy_paths: ''
  buildspec: buildspec.e2e.yml
  gh_environment: uat        # forwards every E2E_* var visible to the job
secrets:
  # a repo or org secret; Environment secrets do not reach a reusable workflow
  E2E_USER_PASSWORD: ${{ secrets.E2E_USER_PASSWORD }}

passthrough_env (for literal or repo-level values). One NAME=VALUE per line, blanks ignored, everything after the first = is the value. Use it for values the caller holds directly (literals or repo-level vars), not Environment vars:

with:
  passthrough_env: |
    E2E_WEBAPP_ORIGIN=https://example.test
    E2E_ORG_SLUG=${{ vars.E2E_ORG_SLUG }}   # a repo-level var, not an Environment var

Precedence when a name comes from more than one source (last wins): passthrough_env, then Environment E2E_* vars, then the reserved vars this workflow controls (E2E_ENVIRONMENT, JFROG_ARTIFACTORY_*). So the Environment overrides passthrough, and the reserved vars override both. Never put secrets in passthrough_env: with: inputs are not masked in logs. Secrets go through declared secrets: inputs, which is why the password is separate. Unset values interpolate to empty and are dropped; have the suite treat blank as unset.

Buildspec authors: a buildspec that assigns these unconditionally will clobber what the workflow passes. Prefer the inbound value:

export ZEPHYR_CYCLE_KEY="${ZEPHYR_CYCLE_KEY:-$(aws ssm get-parameter ... || echo "")}"

How it works

  1. check-changes — compares changed files against deploy_paths. Skipped if deploy_paths is empty.
  2. deploy — pushes to the env branch, waits for the deploy workflow to complete. Skipped if no service code changed.
  3. e2e — uploads source to S3, triggers CodeBuild, writes job summary, and links CloudWatch logs. Raw log streaming is opt-in with stream_codebuild_logs.

Observe-only mode

Passing deploy_paths: '' skips check-changes and deploy, leaving only the CodeBuild run. Use it against environments this pipeline does not deploy, so the suite verifies what is already there rather than what a PR would ship. Those environments reject a non-empty deploy_paths outright.

Pair it with a workflow_dispatch trigger in the caller. The caller workflow must exist on whichever branch you dispatch from.

on:
  workflow_dispatch:

jobs:
  e2e:
    uses: tetrascience/ts-ci-cd-lib/.github/workflows/e2e-codebuild.yml@main
    with:
      environment: uat
      deploy_paths: ''
      buildspec: buildspec.e2e.yml
    secrets: ...

GITHUB_PAT is still declared required even though nothing uses it once the deploy job is skipped.

Infrastructure

The shared CodeBuild project (tdp-e2e) must exist in the target account before an environment can be used. It comes from ts-cloudformation-service/infrastructure/tdp-e2e.yaml, deployed either as a substack of the TDP service stack via EnableE2E=true or as a standalone stack. One project per account serves every repo: source and buildspec are per-call overrides, and uploads are namespaced by repo name.

The workflow derives the role and bucket names by convention, so an environment's CF_ENVIRONMENTS entry must match that stack's EnvironmentName.

Actions

Composite actions are referenced as a step (uses:) inside your own job, unlike the reusable workflows above (which are referenced at the job level).

install-jfrog-npm-package

Installs a single npm package that is published only to a private JFrog Artifactory registry, as a leaf tarball extracted into an already-installed node_modules.

Use this for packages that cannot be added to package.json / yarn.lock — for example in a repo pinned to the public npm registry, where adding a private dependency would break external contributors' yarn install. The action fetches just the one package via npm pack and extracts it in place; it deliberately does not use npm install, which reconciles the whole dependency tree and corrupts a Yarn-managed node_modules (ENOTEMPTY … rmdir node_modules/<pkg>/dist).

Usage

Run it after yarn install (it extracts into the existing node_modules):

steps:
  - uses: actions/checkout@v4
  - uses: actions/setup-node@v4
    with:
      node-version: "24"
      cache: "yarn"
  - run: corepack enable
  - run: yarn install --immutable

  - name: Install ts-lib-zephyr-nodejs (JFrog)
    uses: tetrascience/ts-ci-cd-lib/install-jfrog-npm-package@main
    with:
      package: ts-lib-zephyr-nodejs
      version: "0.4.0"
      # The virtual registry URL is infra info, not a credential — hardcode it
      # (or pass a non-environment-scoped secret).
      registry-url: https://<org>.jfrog.io/artifactory/api/npm/<repo>/
      auth: ${{ secrets.JFROG_ARTIFACTORY_READ_NPM_AUTH }}

Inputs

Input Description Required Default
package npm package name to install (e.g. ts-lib-zephyr-nodejs). Scoped names are supported. Yes
version Exact version to install (e.g. 0.4.0). Yes
registry-url JFrog virtual (read) registry URL, e.g. https://<org>.jfrog.io/artifactory/api/npm/<repo>/. Yes
auth Read-only Artifactory credential; interpreted per auth-type. Pass a secret. Yes
auth-type npm auth field: _auth (base64 username:password) or _authToken (bearer token). No _auth

auth-type: most TetraScience JFROG_ARTIFACTORY_*_NPM_AUTH secrets are a base64 username:password identity (npm _auth, equivalent to Yarn's npmAuthIdent) — the default. Set auth-type: _authToken only if your credential is a bearer token.

About

No description or website provided.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors