--- 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 ```bash # 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](src/index.ts)**: Entrypoint that imports and runs `main()` - **[src/main.ts](src/main.ts)**: Core orchestration logic. Exports `run()` function that coordinates input parsing, tag processing, and output setting - **[src/inputs.ts](src/inputs.ts)**: Input parsing and validation. Exports `getInputs()` that reads action inputs and `Inputs` interface - **[src/derive.ts](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](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](action.yml)**: GitHub Action metadata (inputs/outputs) - **[tests/fixtures/](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.3` → `v1`, `v1.2`). Supports Handlebars conditionals like `{{#if prerelease}}...{{/if}}`. ### Tag Update Logic 1. Parse and validate inputs ([inputs.ts](src/inputs.ts)) 2. Plan all tag operations ([tags.ts](src/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](src/tags.ts):executeTagOperation) 4. Set outputs with created/updated/skipped tag lists ([main.ts](src/main.ts)) ### Testing Patterns Uses Jest with ESM support. Key pattern for mocking ESM modules: ```typescript // 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](https://github.com/googleapis/release-please) to automate versioning and releases based on [Conventional Commits](https://www.conventionalcommits.org/). ### 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: ```text [optional scope]: [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:** ```text 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