diff --git a/docs/docs/actions.rst b/docs/docs/actions.rst new file mode 100644 index 0000000..bd16103 --- /dev/null +++ b/docs/docs/actions.rst @@ -0,0 +1,304 @@ +Actions Reference +================= + +This section documents each GitHub Action provided by ``@nanoforge-dev/actions``. + +.. _action-release-packages: + +release-packages +---------------- + +Publishes monorepo packages to npm with proper dependency sequencing. Packages +are released in topological order to ensure dependencies are available before +dependents are published. + +**Entry Point**: ``src/release-packages/index.ts`` + +Inputs +^^^^^^ + +.. list-table:: + :header-rows: 1 + :widths: 18 10 10 15 47 + + * - Input + - Type + - Required + - Default + - Description + * - ``package`` + - string + - No + - ``"all"`` + - Specific package to release (with dependencies), or ``"all"`` + * - ``exclude`` + - string + - No + - ``""`` + - Comma-separated list of packages to exclude from release + * - ``dry`` + - boolean + - No + - ``false`` + - Perform a dry run without actual publishing + * - ``dev`` + - boolean + - No + - ``false`` + - Publish development versions with commit hash suffix + * - ``tag`` + - string + - No + - ``"dev"`` + - npm dist-tag for dev releases (only valid with ``dev: true``) + +Environment Variables +^^^^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Variable + - Description + * - ``NODE_AUTH_TOKEN`` + - npm authentication token for publishing + * - ``GITHUB_TOKEN`` + - GitHub token for creating releases + +Behavior +^^^^^^^^ + +1. Queries pnpm workspace for all publishable packages +2. Builds a dependency tree to determine release order +3. For each tree level, releases packages in parallel: + + - Checks npm registry to skip already-published versions + - Publishes to npm with provenance + - Creates a GitHub release (for non-dev releases) + - Polls npm registry to confirm availability before proceeding + +4. Generates a job summary listing released and skipped packages + +Dev Mode +^^^^^^^^ + +When ``dev: true``, the action: + +- Appends a timestamp and commit hash to versions (e.g., ``1.0.0-dev.1706140800-abc1234``) +- Publishes with the specified dist-tag (default: ``dev``) +- Skips GitHub release creation +- Replaces ``workspace:^`` with ``workspace:*`` for pinned dev versions + +Example Usage +^^^^^^^^^^^^^ + +.. code-block:: yaml + + - name: Release packages + uses: ./dist/release-packages + with: + package: "@nanoforge-dev/actions" + dry: false + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +---- + +.. _action-create-release-pr: + +create-release-pr +----------------- + +Creates a release pull request that includes version bumps and changelog +generation. This is typically the first step in the release workflow. + +**Entry Point**: ``src/create-release-pr/index.ts`` + +Inputs +^^^^^^ + +.. list-table:: + :header-rows: 1 + :widths: 18 10 10 15 47 + + * - Input + - Type + - Required + - Default + - Description + * - ``package`` + - string + - Yes + - -- + - Package name to release + * - ``version`` + - string + - No + - Auto-generated + - New version (leave empty for cliff-jumper auto-bump) + * - ``dry`` + - boolean + - No + - ``false`` + - Perform a dry run without creating PR + +Environment Variables +^^^^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Variable + - Description + * - ``GITHUB_TOKEN`` + - GitHub token for creating branches and PRs + +Behavior +^^^^^^^^ + +1. Resolves the target package from pnpm workspace +2. Determines the new version (from input or via cliff-jumper dry run) +3. Creates a release branch (``releases/@``) +4. Updates ``package.json`` with the new version +5. Runs cliff-jumper to generate changelog +6. Commits and pushes the release branch +7. Creates a pull request against ``main`` +8. Generates a job summary with version and changelog + +Branch Naming +^^^^^^^^^^^^^ + +Release branches follow the pattern:: + + releases/@ + +For example, ``@nanoforge-dev/actions@1.1.0`` creates branch +``releases/actions@1.1.0``. + +Example Usage +^^^^^^^^^^^^^ + +.. code-block:: yaml + + - name: Create release PR + uses: ./dist/create-release-pr + with: + package: "@nanoforge-dev/actions" + version: "1.2.0" + dry: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +---- + +.. _action-create-release-tag: + +create-release-tag +------------------ + +Creates a git tag after a release pull request is merged. This action is +triggered by the merge event and extracts version information from the branch +name. + +**Entry Point**: ``src/create-release-tag/index.ts`` + +Inputs +^^^^^^ + +.. list-table:: + :header-rows: 1 + :widths: 18 10 10 15 47 + + * - Input + - Type + - Required + - Default + - Description + * - ``commit`` + - string + - Yes + - -- + - Commit SHA of the merge commit + * - ``branch`` + - string + - Yes + - -- + - Head branch of the merged PR (e.g., ``releases/actions@1.1.0``) + +Environment Variables +^^^^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Variable + - Description + * - ``GITHUB_TOKEN`` + - GitHub token for creating tags + +Behavior +^^^^^^^^ + +1. Parses the branch name to extract the package identifier +2. Converts the branch name to a tag name: + - ``releases/actions@1.1.0`` -> ``@nanoforge-dev/actions@1.1.0`` +3. Creates a lightweight git tag pointing to the merge commit + +Tag Format +^^^^^^^^^^ + +Tags follow the npm package identifier format:: + + @nanoforge-dev/@ + +Example Usage +^^^^^^^^^^^^^ + +.. code-block:: yaml + + - name: Create release tag + uses: ./dist/create-release-tag + with: + commit: ${{ github.sha }} + branch: ${{ github.head_ref }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +---- + +Workflow Integration +-------------------- + +The actions are designed to work together in a release pipeline: + +1. **Pre-Release** (manual trigger): + + .. code-block:: yaml + + # Triggers create-release-pr + # Creates releases/@ branch + # Opens PR against main + +2. **Review & Merge**: + + - Team reviews the generated changelog + - PR is merged into main + +3. **Tag Creation** (on PR merge): + + .. code-block:: yaml + + # Triggers create-release-tag + # Creates @nanoforge-dev/@ tag + +4. **Release** (manual or scheduled): + + .. code-block:: yaml + + # Triggers release-packages + # Publishes to npm + # Creates GitHub release diff --git a/docs/docs/api.rst b/docs/docs/api.rst new file mode 100644 index 0000000..94c07c6 --- /dev/null +++ b/docs/docs/api.rst @@ -0,0 +1,209 @@ +API Reference +============= + +This section documents the internal modules and functions used by the actions. + +generate-release-tree +--------------------- + +**Module**: ``src/release-packages/generate-release-tree.ts`` + +Builds a dependency-ordered release tree for monorepo packages. + +Types +^^^^^ + +.. code-block:: typescript + + interface ReleaseEntry { + name: string; // Package name + version: string; // Version to release + changelog?: string; // Parsed changelog content + dependsOn?: string[]; // Internal package dependencies + } + +Functions +^^^^^^^^^ + +.. function:: generateReleaseTree(dry: boolean, devTag?: string, packageName?: string, exclude?: string[]): Promise + + Generates a two-dimensional array of release entries. Each inner array + represents a "level" of packages that can be released in parallel. + + :param dry: If true, skips actual version bumping + :param devTag: Optional dev tag for prerelease versions + :param packageName: Target a specific package (with deps) or ``"all"`` + :param exclude: Package names to exclude from release + :returns: Promise resolving to ordered release tree + + **Algorithm**: + + 1. Queries pnpm for all workspace packages + 2. For dev releases, modifies ``package.json`` to pin workspace deps + 3. Parses changelogs for non-dev releases + 4. Builds dependency graph from package dependencies + 5. Topologically sorts into release levels + 6. Prunes tree based on ``packageName`` or ``exclude`` options + +.. function:: getReleaseEntries(dry: boolean, devTag?: string): Promise + + Internal function that collects all releasable packages from the workspace. + + :param dry: If true, uses mock version bumping + :param devTag: Optional dev tag for version generation + :returns: Promise resolving to flat list of release entries + +release-package +--------------- + +**Module**: ``src/release-packages/release-package.ts`` + +Handles publishing individual packages to npm and creating GitHub releases. + +Functions +^^^^^^^^^ + +.. function:: releasePackage(release: ReleaseEntry, dry: boolean, devTag?: string, doGitRelease?: boolean): Promise + + Publishes a single package to npm. + + :param release: Release entry with name, version, and changelog + :param dry: If true, logs instead of publishing + :param devTag: Optional dist-tag for npm publish + :param doGitRelease: Whether to create a GitHub release (default: ``!devTag``) + :returns: Promise resolving to ``true`` if published, ``false`` if skipped + + **Behavior**: + + 1. Checks npm registry for existing version (skips if found) + 2. Publishes with ``pnpm publish --provenance`` + 3. Creates GitHub release if ``doGitRelease`` is true + 4. Polls registry to confirm publication before returning + +.. function:: checkRegistry(release: ReleaseEntry): Promise + + Checks if a specific version exists on npm. + + :param release: Release entry to check + :returns: Promise resolving to ``true`` if version exists + +.. function:: gitTagAndRelease(release: ReleaseEntry, dry: boolean): Promise + + Creates a GitHub release with the changelog as body. + + :param release: Release entry with changelog + :param dry: If true, logs instead of creating release + +create-release-pr Functions +--------------------------- + +**Module**: ``src/create-release-pr/functions.ts`` + +Utility functions for the pre-release workflow. + +Types +^^^^^ + +.. code-block:: typescript + + interface IPkg { + name: string; // Package name + version: string; // Current version + path: string; // Absolute path to package + private: boolean; // Whether package is private + } + +Functions +^^^^^^^^^ + +.. function:: resolvePackage(name: string): Promise + + Resolves a package from the pnpm workspace. + + :param name: Package name to resolve + :returns: Promise resolving to package info + :raises Error: If package is not found in workspace + + Uses ``pnpm list --filter --recursive --only-projects --prod --json``. + +.. function:: resolveChangelog(path: string, name: string, version: string): Promise + + Extracts the changelog section for a specific version. + + :param path: Path to package directory + :param name: Package name + :param version: Version to extract + :returns: Promise resolving to changelog content + + Parses ``CHANGELOG.md`` and extracts content between version headers. + +.. function:: resolveVersion(name: string): Promise + + Determines the next version using cliff-jumper dry run. + + :param name: Package name + :returns: Promise resolving to next version string + :raises Error: If version cannot be determined + + Runs ``pnpm --filter= run release --dry-run`` and parses output. + +.. function:: checkoutToReleaseBranch(name: string, version: string): Promise + + Creates and checks out a release branch. + + :param name: Package name (scoped) + :param version: Release version + :returns: Promise resolving to branch name + + Creates branch ``releases/@``. + +.. function:: updateVersion(path: string, version: string): Promise + + Updates the version in ``package.json``. + + :param path: Path to package directory + :param version: New version string + +.. function:: runRelease(name: string): Promise + + Runs the release script (cliff-jumper) for a package. + + :param name: Package name + + Executes ``pnpm --filter= run release --skip-automatic-bump --skip-tag``. + +.. function:: pushRelease(name: string, version: string, branch: string): Promise + + Commits and pushes the release changes. + + :param name: Package name + :param version: Release version + :param branch: Branch name to push + + Creates commit with message ``chore(): release @``. + +.. function:: createPR(branchName: string): Promise + + Creates a pull request using the GitHub API. + + :param branchName: Source branch for the PR + + Uses the commit message as the PR title. + +Constants +--------- + +NPM Registry URL +^^^^^^^^^^^^^^^^ + +The actions use the public npm registry:: + + https://registry.npmjs.org/ + +Commit Author +^^^^^^^^^^^^^ + +Automated commits use the GitHub Actions bot identity:: + + user.name: github-actions[bot] + user.email: username@users.noreply.github.com diff --git a/docs/docs/architecture.rst b/docs/docs/architecture.rst new file mode 100644 index 0000000..4364a7f --- /dev/null +++ b/docs/docs/architecture.rst @@ -0,0 +1,165 @@ +Architecture +============ + +Overview +-------- + +NanoForge Actions is a collection of GitHub Actions designed to automate the +release workflow for NanoForge monorepo packages. It provides three main actions +that handle different stages of the release process: + +1. **create-release-pr** -- Creates a release pull request with version bumps + and changelog generation +2. **release-packages** -- Publishes packages to npm with proper dependency + sequencing +3. **create-release-tag** -- Creates git tags after a release PR is merged + +The package is published to npm as ``@nanoforge-dev/actions`` and is built +using tsup with ESM output. + +Technology Stack +---------------- + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Component + - Technology + * - Language + - TypeScript (strict mode) + * - Runtime + - Bun / Node.js 25 + * - CLI Framework + - Commander.js + * - GitHub Integration + - @actions/core, @actions/github + * - Build Tool + - tsup (esbuild-based bundler) + * - Module Format + - ESM + * - Target + - ES2022 + * - Package Manager + - pnpm 10.x + * - Linter + - ESLint 9.x + * - Formatter + - Prettier 3.x + * - CI/CD + - GitHub Actions + +Project Structure +----------------- + +:: + + src/ + +-- create-release-pr/ # Pre-release action + | +-- index.ts # Entry point + | +-- functions.ts # Core functions + | +-- types.ts # Type definitions + +-- release-packages/ # Package publishing action + | +-- index.ts # Entry point + | +-- generate-release-tree.ts # Dependency tree generation + | +-- release-package.ts # Individual package release + +-- create-release-tag/ # Tag creation action + +-- index.ts # Entry point + +Action Pattern +-------------- + +Each action follows a consistent pattern: + +1. **Input Parsing** -- Uses Commander.js to parse CLI arguments combined with + ``@actions/core`` for GitHub Actions inputs +2. **Package Resolution** -- Uses pnpm to query the monorepo workspace for + package information +3. **Dependency Analysis** -- Builds a release tree respecting inter-package + dependencies +4. **Execution** -- Performs the action (publish, PR creation, or tagging) +5. **Summary** -- Generates a GitHub Actions job summary using ``@actions/core`` + +.. code-block:: typescript + + // Typical action structure + import { getInput, summary } from "@actions/core"; + import { program } from "commander"; + + program + .name("action-name") + .description("Action description") + .argument("[package]", "target package", getInput("package")) + .option("--dry", "dry run mode", getBooleanInput("dry")) + .parse(); + + const { dry } = program.opts(); + const [packageName] = program.processedArgs; + + // Action logic... + + await summary.addHeading("Summary").write(); + +Release Tree Algorithm +---------------------- + +The ``release-packages`` action uses a dependency-aware release algorithm: + +1. **Discovery** -- Query pnpm for all workspace packages +2. **Tree Construction** -- Build a directed acyclic graph (DAG) of package + dependencies +3. **Topological Sort** -- Order packages so dependencies are released before + dependents +4. **Parallel Execution** -- Release independent packages in parallel within + each tree level + +.. code-block:: text + + Level 0: [package-a, package-b] (no internal deps) + Level 1: [package-c] (depends on a) + Level 2: [package-d, package-e] (depend on c) + +This ensures that when ``package-d`` is published, its dependency ``package-c`` +is already available on npm. + +Build Pipeline +-------------- + +The project uses ``tsup`` for bundling. The build produces: + +- ESM bundles for each action entry point +- Type declarations (``.d.ts``) +- Source maps + +.. code-block:: bash + + # Full build + pnpm run build + + # What happens internally: + # 1. tsc --noEmit (type checking) + # 2. tsup (bundling) + +GitHub Actions Integration +-------------------------- + +Actions are consumed via the ``uses`` directive in workflow files: + +.. code-block:: yaml + + - name: Release packages + uses: ./dist/release-packages + with: + package: "@nanoforge-dev/actions" + dry: false + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +Each action reads inputs from both: + +- GitHub Actions ``with`` parameters (via ``getInput()``) +- CLI arguments (via Commander.js) + +This dual-mode design allows actions to be used both in CI and locally for +testing. diff --git a/docs/docs/index.rst b/docs/docs/index.rst new file mode 100644 index 0000000..1240d5c --- /dev/null +++ b/docs/docs/index.rst @@ -0,0 +1,9 @@ +Technical Documentation +====================== + +.. toctree:: + :maxdepth: 2 + + architecture + actions + api diff --git a/docs/guides/contributing.rst b/docs/guides/contributing.rst new file mode 100644 index 0000000..c4d22e3 --- /dev/null +++ b/docs/guides/contributing.rst @@ -0,0 +1,234 @@ +Contributing +============ + +This guide explains how to contribute to the NanoForge Actions project. + +Prerequisites +------------- + +- `Node.js `_ version 25 +- `pnpm `_ 10.x +- `Bun `_ runtime + +Setup +----- + +1. **Fork and clone** the repository: + + .. code-block:: bash + + git clone https://github.com//actions.git + cd actions + +2. **Ensure you are on the main branch**: + + .. code-block:: bash + + git checkout main + +3. **Install dependencies**: + + .. code-block:: bash + + pnpm install --frozen-lockfile + + This also sets up Husky git hooks via the ``prepare`` script. + +4. **Create a feature branch**: + + .. code-block:: bash + + git checkout -b feat/my-feature + +Development Workflow +-------------------- + +1. Make your changes in the ``src/`` directory. + +2. Run formatting and lint fixes: + + .. code-block:: bash + + pnpm format + +3. Build the project to verify there are no type errors: + + .. code-block:: bash + + pnpm build + +4. Run the full lint check: + + .. code-block:: bash + + pnpm lint + +5. Test your changes locally with dry run: + + .. code-block:: bash + + bun ./dist//index.js --dry + +Commit Convention +----------------- + +This project uses `Conventional Commits `_. +Every commit message must follow this format: + +:: + + (): + + [optional body] + + [optional footer(s)] + +Valid types: + +.. list-table:: + :header-rows: 1 + :widths: 15 85 + + * - Type + - Purpose + * - ``feat`` + - A new feature + * - ``fix`` + - A bug fix + * - ``docs`` + - Documentation changes + * - ``chore`` + - Maintenance tasks (deps, CI, tooling) + * - ``refactor`` + - Code restructuring without behavior change + * - ``perf`` + - Performance improvements + * - ``test`` + - Adding or updating tests + * - ``style`` + - Code style changes (formatting, whitespace) + +Examples: + +.. code-block:: text + + feat(release-packages): add support for scoped exclusions + fix(create-release-pr): handle missing changelog gracefully + docs: update contributing guide + chore(deps): update @actions/core to v2 + +Commit messages are validated by ``commitlint`` via a git hook. Commits that do +not follow the convention are rejected. + +Pull Request Process +-------------------- + +1. Push your branch to your fork: + + .. code-block:: bash + + git push origin feat/my-feature + +2. `Open a pull request `_ + against the ``main`` branch. + +3. Ensure the CI pipeline passes (lint checks). + +4. Request a review from a maintainer. + +5. Once approved, the PR is merged into ``main``. + +Code Style +---------- + +The project enforces consistent code style through ESLint and Prettier. + +Naming conventions: + +- **Files**: kebab-case (``generate-release-tree.ts``) +- **Functions**: camelCase (``generateReleaseTree``) +- **Interfaces**: PascalCase with ``I`` prefix for data types (``IPkg``) +- **Types**: PascalCase (``ReleaseEntry``) +- **Constants**: camelCase (``octokit``) + +Import ordering (enforced by Prettier plugin): + +1. External packages (``@actions/*``, ``commander``) +2. Relative imports (``./functions``) + +Project Structure +----------------- + +When adding code, follow the existing structure: + +- **Actions**: ``src//`` with ``index.ts`` entry point +- **Shared functions**: Group by concern in the action directory +- **Types**: ``.d.ts`` or ``types.ts`` files alongside implementations + +Adding a New Action +------------------- + +1. Create a new directory under ``src/``: + + .. code-block:: text + + src/my-action/ + +-- index.ts # Entry point with Commander setup + +-- types.ts # Type definitions (if needed) + +2. Implement the action following the existing pattern: + + .. code-block:: typescript + + import { getInput, summary } from "@actions/core"; + import { program } from "commander"; + + program + .name("my action") + .description("Description of my action") + .argument("[arg]", "argument description", getInput("arg")) + .option("--dry", "dry run", getBooleanInput("dry")) + .parse(); + + // Action logic... + + await summary.addHeading("My Action Summary").write(); + +3. Add a build entry in ``tsup.config.ts``: + + .. code-block:: typescript + + entry: ["src/my-action/index.ts"], + outDir: "dist/my-action", + +4. Create corresponding workflow files in ``.github/workflows/``. + +5. Document the action in ``docs/docs/actions.rst``. + +Dependencies +------------ + +Dependencies are managed through pnpm workspace version catalogs defined in +the root ``pnpm-workspace.yaml``. When adding or updating a dependency, use +the catalog reference rather than a direct version. + +.. code-block:: json + + { + "dependencies": { + "@actions/core": "catalog:actions" + } + } + +Reporting Issues +---------------- + +Report bugs and request features on the +`GitHub Issues `_ page. +Issue templates are available to guide your report. + +Security +-------- + +For security vulnerabilities, refer to the ``SECURITY.md`` file in the +repository root for the responsible disclosure process. diff --git a/docs/guides/getting-started.rst b/docs/guides/getting-started.rst new file mode 100644 index 0000000..11c3982 --- /dev/null +++ b/docs/guides/getting-started.rst @@ -0,0 +1,288 @@ +Getting Started +=============== + +This guide explains how to use ``@nanoforge-dev/actions`` to automate releases +in your NanoForge monorepo. + +Prerequisites +------------- + +- `Node.js `_ version 25 or later +- `pnpm `_ 10.x +- `Bun `_ runtime (for local execution) +- A GitHub repository with Actions enabled +- An npm account with publish access + +Setup +----- + +1. **Install the package** as a dev dependency: + + .. code-block:: bash + + pnpm add -D @nanoforge-dev/actions + +2. **Configure npm authentication** in your repository: + + - Go to Settings > Secrets and variables > Actions + - Add ``NPM_PUBLISH_TOKEN`` with your npm automation token + +3. **Copy the workflow files** to your ``.github/workflows/`` directory. + +Release Workflow +---------------- + +Pre-Release (Create Release PR) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Trigger the pre-release workflow to create a release pull request: + +.. code-block:: yaml + + # .github/workflows/pre-release.yml + name: Pre-Release + + on: + workflow_dispatch: + inputs: + version: + description: "New version (leave empty for auto)" + type: string + required: false + dry_run: + description: Perform a dry run? + type: boolean + default: false + + permissions: + contents: write + pull-requests: write + + jobs: + create-release-pr: + name: Create release PR + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 25 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build actions + run: pnpm build + + - name: Create release PR + uses: ./dist/create-release-pr + with: + package: "@your-scope/your-package" + version: ${{ inputs.version }} + dry: ${{ inputs.dry_run }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +Release (Publish to npm) +^^^^^^^^^^^^^^^^^^^^^^^^ + +After the release PR is merged, trigger the release workflow: + +.. code-block:: yaml + + # .github/workflows/release.yml + name: Release + + on: + workflow_dispatch: + inputs: + dry_run: + description: Perform a dry run? + type: boolean + default: false + + permissions: + contents: write + + jobs: + npm-publish: + name: npm publish + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 25 + registry-url: "https://registry.npmjs.org" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build actions + run: pnpm build + + - name: Release packages + uses: ./dist/release-packages + with: + package: "@your-scope/your-package" + dry: ${{ inputs.dry_run }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +Tag Creation (On PR Merge) +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Automatically create tags when release PRs are merged: + +.. code-block:: yaml + + # .github/workflows/release-tag.yml + name: Release Tag + + on: + pull_request: + types: [closed] + branches: [main] + + permissions: + contents: write + + jobs: + create-tag: + name: Create release tag + runs-on: ubuntu-latest + if: github.event.pull_request.merged && startsWith(github.head_ref, 'releases/') + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 25 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build actions + run: pnpm build + + - name: Create release tag + uses: ./dist/create-release-tag + with: + commit: ${{ github.sha }} + branch: ${{ github.head_ref }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +Local Usage +----------- + +The actions can also be run locally for testing: + +.. code-block:: bash + + # Build the actions first + pnpm build + + # Dry run release + bun ./dist/release-packages/index.js @your-scope/package --dry + + # Dry run pre-release + bun ./dist/create-release-pr/index.js @your-scope/package --dry + +CLI Options +^^^^^^^^^^^ + +release-packages: + +.. code-block:: text + + Usage: release packages [options] [package] + + Arguments: + package release a specific package (and its deps) + + Options: + -e, --exclude exclude packages from releasing + --dry skip actual publishing + --dev publish development versions + --tag tag for dev releases (default: "dev") + +create-release-pr: + +.. code-block:: text + + Usage: create release pr [options] [package] + + Arguments: + package package to release + + Options: + --dry skip actual PR creation + --version new version of the package + +Typical Workflow +---------------- + +A typical release workflow: + +1. **Trigger pre-release** via GitHub Actions UI: + + - Select the target package + - Optionally specify a version (or leave empty for auto) + - Run the workflow + +2. **Review the PR**: + + - Check the generated changelog + - Verify the version bump + - Approve and merge + +3. **Tag is created automatically** when the PR merges. + +4. **Trigger release** via GitHub Actions UI: + + - Package is published to npm + - GitHub release is created with changelog + +5. **Verify on npm**: + + .. code-block:: bash + + npm view @your-scope/your-package + +Dev Releases +------------ + +For continuous integration, you can publish development versions: + +.. code-block:: yaml + + - name: Dev release + uses: ./dist/release-packages + with: + package: "all" + dev: true + tag: "next" + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + +This publishes versions like ``1.0.0-next.1706140800-abc1234`` tagged as +``next`` on npm. + +Install dev versions with: + +.. code-block:: bash + + pnpm add @your-scope/package@next diff --git a/docs/guides/index.rst b/docs/guides/index.rst new file mode 100644 index 0000000..2850b34 --- /dev/null +++ b/docs/guides/index.rst @@ -0,0 +1,9 @@ +Guides +====== + +.. toctree:: + :maxdepth: 2 + + getting-started + testing + contributing diff --git a/docs/guides/testing.rst b/docs/guides/testing.rst new file mode 100644 index 0000000..a165ec4 --- /dev/null +++ b/docs/guides/testing.rst @@ -0,0 +1,172 @@ +Testing +======= + +This guide covers how to run quality checks on the NanoForge Actions codebase. + +Running Lint Checks +------------------- + +The project uses ESLint for code quality and Prettier for formatting. Run both +checks with: + +.. code-block:: bash + + pnpm lint + +This executes: + +1. ``prettier --check .`` -- Verifies all files match the expected formatting +2. ``eslint --format=pretty src`` -- Checks TypeScript source files for lint + errors + +Auto-Fixing Issues +------------------ + +To automatically fix formatting and lint issues: + +.. code-block:: bash + + pnpm format + +This executes: + +1. ``prettier --write .`` -- Reformats all files +2. ``eslint --fix --format=pretty src`` -- Auto-fixes lint issues where possible + +Type Checking +------------- + +TypeScript type checking is part of the build process: + +.. code-block:: bash + + # Type check only (no output) + npx tsc --noEmit + + # Full build (includes type checking) + pnpm build + +Building +-------- + +Verify that the project builds without errors: + +.. code-block:: bash + + pnpm build + +This runs type checking and bundles the code with tsup. Output is placed in +``dist/``. + +Pre-Commit Hooks +---------------- + +The project has pre-commit hooks configured via Husky and lint-staged. When you +commit, the following checks run automatically on staged files: + +- **Prettier**: Formats all staged files +- **ESLint**: Fixes lint issues in staged ``src/**/*.ts`` files + +If any check fails, the commit is rejected. Fix the issues and try again. + +CI Pipeline +----------- + +The GitHub Actions CI pipeline (``tests.yml``) runs on: + +- Every pull request targeting ``main`` +- Every push to ``main`` + +The CI pipeline runs ``pnpm lint`` and blocks merging if it fails. + +Local Testing +------------- + +Dry Run Mode +^^^^^^^^^^^^ + +All actions support a ``--dry`` flag for testing without side effects: + +.. code-block:: bash + + # Build first + pnpm build + + # Test release-packages + bun ./dist/release-packages/index.js @nanoforge-dev/actions --dry + + # Test create-release-pr + bun ./dist/create-release-pr/index.js @nanoforge-dev/actions --dry + +The dry run will: + +- Log what would be published +- Skip actual npm publishing +- Skip GitHub release/PR creation +- Still generate the job summary output + +Testing Against npm Registry +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The release action checks the npm registry before publishing. You can verify +this manually: + +.. code-block:: bash + + # Check if a version exists + curl -s https://registry.npmjs.org/@nanoforge-dev/actions/1.0.0 | jq .version + +Testing GitHub API Integration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To test GitHub API calls locally, set the ``GITHUB_TOKEN`` environment variable: + +.. code-block:: bash + + export GITHUB_TOKEN="your-personal-access-token" + bun ./dist/create-release-pr/index.js @nanoforge-dev/actions --dry + +Even with the token set, ``--dry`` prevents actual API calls. + +Verifying a Full Check +----------------------- + +Before submitting a pull request, run the full check locally: + +.. code-block:: bash + + pnpm format && pnpm build + +This ensures your code passes formatting, lint checks, type checking, and +builds correctly. + +Debugging Tips +-------------- + +Verbose Output +^^^^^^^^^^^^^^ + +Actions log their progress to stdout. For more detail, check the GitHub Actions +job logs or run locally with the ``--dry`` flag. + +Registry Polling +^^^^^^^^^^^^^^^^ + +After publishing, the ``release-packages`` action polls npm for up to 5 minutes +to confirm the package is available. If this times out, check: + +1. npm registry status (status.npmjs.org) +2. Your npm token permissions +3. Package name and scope correctness + +Common Issues +^^^^^^^^^^^^^ + +**"Package not found"** -- The package name doesn't match any workspace package. +Check ``pnpm list --recursive`` output. + +**"Could not find the version"** -- cliff-jumper dry run failed to determine +the next version. Ensure your commit history follows conventional commits. + +**"Release failed"** -- npm publish failed. Check token permissions and ensure +the version doesn't already exist. diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..efad56e --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,18 @@ +NanoForge Actions +================= + +``@nanoforge-dev/actions`` provides GitHub Actions for automating the release +workflow of NanoForge monorepo packages. It handles package publishing with +proper dependency sequencing, release PR creation, and tag management. + +.. toctree:: + :maxdepth: 2 + :caption: Technical Documentation + + docs/index + +.. toctree:: + :maxdepth: 2 + :caption: Guides + + guides/index