Files
update-tags-action/AGENTS.md

10 KiB

alwaysApply
alwaysApply
true

AGENTS.md

This file provides guidance to LLM agents when working with code in this repository.

Project Overview

GitHub Action that creates/updates Git tags in repositories. Commonly used to maintain major (v1) and minor (v1.2) version tags pointing to the latest patch release (v1.2.3). The action uses itself in CI to manage its own version tags.

Important: TypeScript sources in src/ are transpiled to JavaScript in dist/. Both are checked into the repository. A CI workflow verifies dist/ is up-to-date. Always run npm run package (or npm run bundle) after modifying src/ files.

License Compliance: After modifying NPM dependencies, check license status and re-cache if needed via npm run licensed:status and npm run licensed:cache.

Development Commands

Package manager: npm

# Install dependencies
npm install

# Development workflow
npm run format:write  # Format code with Prettier (80 char lines)
npm run lint          # ESLint check
npm run test          # Run Jest tests
npm run package       # Build src/index.ts -> dist/index.js via Rollup
npm run bundle        # Alias: format + package

# Run a single test file
NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 jest tests/main.test.ts

# CI variants (suppress warnings)
npm run ci-test       # Run tests in CI mode

# Full pre-commit check
npm run all  # format → lint → test → coverage → update-readme → package

# Update README from action.yml
npm run update-readme

# Watch mode
npm run package:watch # Auto-rebuild on changes

# License compliance (after modifying dependencies)
npm run licensed:status  # Check license status of dependencies
npm run licensed:cache   # Re-cache licenses if needed

Code Architecture

Source Structure

  • src/index.ts: Entrypoint that imports and runs main()
  • src/main.ts: Core orchestration logic. Exports run() function that coordinates input parsing, tag processing, and output setting
  • src/inputs.ts: Input parsing and validation. Exports getInputs() that reads action inputs and Inputs interface
  • src/derive.ts: Semver parsing and tag derivation:
    • parseSemver(): Parses version strings into components (prefix, major, minor, patch, prerelease, build)
    • renderTemplate(): Renders Handlebars templates with semver context
    • deriveTags(): Derives tags from a version string using a template
  • src/tags.ts: Tag planning and execution logic:
    • planTagOperations(): Parses tags, pre-resolves refs to SHAs in parallel, plans create/update/skip operations
    • executeTagOperation(): Executes a single planned operation (create, update, or skip with logging)
    • Private helpers for tag creation, updates, and annotation handling
  • action.yml: GitHub Action metadata (inputs/outputs)
  • tests/fixtures/: Mock implementations of @actions/core, @actions/github, and csv-parse for testing

Tag Input Parsing

Uses csv-parse/sync to handle both CSV and newline-delimited formats. Supports per-tag ref and annotation overrides using the format tag:ref:annotation:

  • tag — Use default ref and annotation inputs
  • tag:ref — Override ref for this tag (e.g., v1:main tags v1 to main)
  • tag:ref:annotation — Override both ref and annotation
  • tag::annotation — Override annotation only (empty ref uses default)

Annotations can contain colons; everything after the second colon is treated as the annotation text. Per-tag values override the global ref and annotation inputs.

Tag Derivation

The derive_from input allows automatic generation of tags from a semver version string. Uses Handlebars templates with these placeholders:

  • {{prefix}}: "v" or "V" if input had prefix, empty otherwise
  • {{major}}, {{minor}}, {{patch}}: Version numbers
  • {{prerelease}}, {{build}}: Optional semver components
  • {{version}}: Full version without prefix

Default template {{prefix}}{{major}},{{prefix}}{{major}}.{{minor}} generates major and minor tags (e.g., v1.2.3v1, v1.2). Supports Handlebars conditionals like {{#if prerelease}}...{{/if}}.

Tag Update Logic

  1. Parse and validate inputs (inputs.ts)
  2. Plan all tag operations (tags.ts:planTagOperations):
    • Parse tag:ref:annotation syntax and extract per-tag refs/annotations
    • Pre-resolve all unique refs to SHAs in parallel (optimization)
    • For each tag, check existence and determine operation:
      • If exists + fail mode: Fail action immediately
      • If exists + skip mode: Plan skip
      • If exists + update mode: Plan update if SHA or annotation differs
      • If doesn't exist (404): Plan create
  3. Execute each planned operation (tags.ts:executeTagOperation)
  4. Set outputs with created/updated/skipped tag lists (main.ts)

Testing Patterns

Uses Jest with ESM support. Key pattern for mocking ESM modules:

// Declare mocks BEFORE importing tested module
jest.unstable_mockModule('@actions/core', () => core)

// Dynamic import AFTER mocks
const { run } = await import('../src/main.ts')

Mock fixtures live in tests/fixtures/ (e.g., core.ts mocks @actions/core).

Testing Best Practices

  • Consider edge cases as well as the main success path
  • Tests live in tests/ directory, fixtures in tests/fixtures/
  • Run tests after any refactoring to ensure coverage requirements are met
  • Use @actions/core package for logging (not console) for GitHub Actions compatibility

TypeScript Configuration

  • Target: ES2022, NodeNext module resolution
  • Strict mode enabled throughout
  • Build outputs ESM to dist/index.js with external sourcemaps
  • Line length: 80 chars (enforced by Prettier)

CI/CD

.github/workflows/ci.yml runs:

  1. check-dist: Verify bundled dist/ matches source
  2. lint: ESLint check
  3. test: Run Jest test suite
  4. release-please: Semantic versioning releases
  5. release-tags: Self-referential tag updates after release

Release Process

This project uses release-please to automate versioning and releases based on Conventional Commits.

How It Works

  1. Commit with conventional format: All commits must follow the Conventional Commits specification (e.g., feat:, fix:, chore:, docs:)
  2. Release PR is created: release-please automatically opens/updates a "Release PR" that:
    • Proposes the next version number based on commit types
    • Updates CHANGELOG.md with all changes since last release
    • Updates version numbers in package.json and other files
  3. Review the Release PR: Check the proposed version bump and changelog entries are correct
  4. Merge to release: Merging the Release PR triggers:
    • Creation of a GitHub Release with the changelog
    • Publishing of a new Git tag
    • Execution of the release-tags workflow to update major/minor version tags

Conventional Commit Format

All commit messages must follow this format:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Common types:

  • feat: - New feature (triggers minor version bump)
  • fix: - Bug fix (triggers patch version bump)
  • docs: - Documentation changes only
  • chore: - Maintenance tasks, dependency updates
  • refactor: - Code refactoring without feature/behavior changes
  • test: - Adding or updating tests
  • ci: - CI/CD configuration changes
  • BREAKING CHANGE: - In footer, triggers major version bump

Examples:

feat: add support for multiple tag formats

fix: handle 404 errors when tag doesn't exist

docs: update README with new examples

chore(deps): bump @actions/core to v1.10.0

Action Interface

Inputs:

  • tags: CSV/newline list, supports tag:ref:annotation syntax for per-tag overrides
  • derive_from: Semver version string to derive tags from (e.g., "v1.2.3")
  • derive_from_template: Handlebars template for tag derivation (default: {{prefix}}{{major}},{{prefix}}{{major}}.{{minor}})
  • ref: Default SHA/ref to tag (default: current commit)
  • when_exists: update|skip|fail (default: update)
  • annotation: Default annotation message for tags (default: lightweight/none)
  • dry_run: Log planned operations without executing (default: false)
  • github_token: Auth token (default: github.token)

Either tags or derive_from (or both) must be provided.

Outputs:

  • tags: All created/updated tags
  • created: Newly created tags
  • updated: Updated tags
  • skipped: Skipped tags (already matching or when_exists=skip)

Code Style and Guidelines

  • 80 character line length (Prettier)
  • No semicolons, single quotes, no trailing commas
  • Explicit function return types
  • Type imports: import type * as core from '@actions/core'
  • Error handling via try-catch with core.setFailed()
  • 404 errors specifically caught to distinguish "tag doesn't exist" from other errors
  • Use descriptive variable and function names
  • Keep functions focused and manageable
  • Document functions with JSDoc comments
  • Follow DRY principles and avoid unnecessary complexity
  • Maintain consistency with existing patterns and style
  • Focus comments on explaining "why", not "what" (avoid basic unnecessary comments)
  • Use TypeScript's type system for safety and clarity

Pull Request Guidelines

When creating a pull request (PR), please ensure that:

  • Keep changes focused and minimal (avoid large changes, or consider breaking them into separate, smaller PRs)
  • Formatting checks pass
  • Linting checks pass
  • Unit tests pass and coverage requirements are met
  • The action has been transpiled to JavaScript and the dist directory is up-to-date with the latest changes in the src directory
  • If necessary, the README.md file is updated to reflect any changes in functionality or usage

The body of the PR should include:

  • A summary of the changes
  • A special note of any changes to dependencies
  • A link to any relevant issues or discussions
  • Any additional context that may be helpful for reviewers

Code Review Guidelines

When performing a code review, please follow these guidelines:

  • If there are changes that modify the functionality/usage of the action, validate that there are changes in the README.md file that document the new or modified functionality