Skip to content

Release engineering: adopt deterministic tag-based releases for npm and pub.dev #47

Description

@vm75

Goal

Establish deterministic, tag-driven release engineering for vm75/inditrans.

After this milestone, the repository should have one clear release contract:

vX.Y.Z Git tag
   ├── release identity
   ├── validated package versions
   ├── validated changelog entry
   ├── full CI/test/build gate
   └── publish
        ├── npm (@vm75/inditrans)
        ├── pub.dev (Flutter package)
        └── GitHub Release

The release workflow must never publish merely because code was pushed to main. Publishing happens only for an intentional SemVer tag such as v0.14.0.

Preconditions

  • Milestone 1 is complete, committed, pushed, and merged to main.
  • The release-engineering work starts from the final Milestone 1 tree, not from an earlier repository layout.
  • Do not create the first production release tag as part of this implementation PR. The first tag should be created deliberately after this milestone is merged and the release procedure has been verified.

Scope

1. Establish the repository release contract

Use SemVer tags:

  • v0.13.0
  • v0.13.1
  • v0.14.0

Rules:

  • The Git tag is the authoritative release identity.
  • The tag must match vMAJOR.MINOR.PATCH.
  • Package versions must match the tag without the leading v.
  • A release tag must not be reused or moved.
  • CI on main never publishes.
  • Release CI runs only for matching version tags.
  • Failed validation/test/build jobs must prevent publication.

The existing package manifests remain package metadata:

  • flutter/pubspec.yaml
  • nodejs/package.json

Do not introduce a second independent release-version source.

2. Consolidate changelog management

Create a root CHANGELOG.md using the Keep a Changelog structure.

It should contain:

# Changelog

All notable changes to this project will be documented in this file.

## [Unreleased]

## [0.13.0] - YYYY-MM-DD

...

Requirements:

  • Keep an [Unreleased] section at the top.
  • Maintain release sections in descending version order.
  • Use SemVer release headings.
  • Record user-visible changes rather than implementation-only commit noise.
  • A release should move the relevant entries from [Unreleased] into the dated release section.
  • Link/version references may be added at the bottom if useful.

Consolidate the existing package changelogs into the root changelog so there is one source of release history:

  • flutter/CHANGELOG.md
  • nodejs/CHANGELOG.md

Do not leave two independently maintained changelogs.

3. Define and validate version consistency

Add a small repository-level validation tool, preferably:

tool/verify_release.dart

It should accept a release tag/version and verify at minimum:

  • tag is valid SemVer with the required v prefix;
  • flutter/pubspec.yaml version equals the tag version without v;
  • nodejs/package.json version equals the tag version without v;
  • the root changelog contains a matching release section;
  • the release is not accidentally being built from a mismatched version.

Keep the validation deterministic and suitable for CI.

If the existing .version file is still used by the post-Milestone-1 codebase, migrate its consumers as part of this work and remove it only after there is no remaining functional dependency. The desired end state is that package manifests plus the Git tag provide the release contract rather than an unrelated mutable version file.

4. Move GitHub Actions to the repository root

Create:

.github/
  workflows/
    ci.yml
    release.yml

The workflows belong at repository level because the project contains multiple published packages.

Do not retain a second competing CI/release system under nodejs/.github/workflows/.

5. Implement normal CI

.github/workflows/ci.yml should run for:

  • pull requests targeting main;
  • pushes to main.

It must not publish packages.

CI should exercise the complete supported project surface, including the existing native, WASM/Node.js, and Flutter test/build paths as appropriate to the final Milestone 1 structure.

At minimum, preserve and formalize the current checks:

  • native tests/build;
  • Node.js package tests/build/style checks;
  • Flutter tests/build checks;
  • formatting/static analysis checks where already supported;
  • version consistency checks where applicable.

Use currently supported GitHub Actions versions rather than the legacy checkout@v2 / cache@v2 pattern.

Keep permissions least-privilege.

6. Implement tag-driven release workflow

Create .github/workflows/release.yml.

Trigger only on tags matching:

push:
  tags:
    - 'v*.*.*'

The workflow must:

  1. Check out the exact tagged commit.
  2. Derive the release version from the tag.
  3. Validate the tag/version/changelog contract.
  4. Run the relevant test/build suite.
  5. Build the npm package.
  6. Build/prepare the Flutter package.
  7. Publish npm.
  8. Publish the Flutter package to pub.dev.
  9. Create a GitHub Release associated with the same tag.
  10. Fail before any publication if validation/tests/builds fail.

Do not publish from main pushes.

7. Publishing authentication

Prefer modern trusted publishing/OIDC mechanisms supported by npm and pub.dev rather than long-lived package tokens.

If a provider still requires a secret, use a protected GitHub Actions secret/environment and document exactly which secret is required.

Do not put credentials in the repository, workflow source, package manifests, or generated files.

Use least-privilege GitHub token permissions. The release job should request only the permissions it needs, including the permission required to create the GitHub Release.

8. Remove the old branch-based publishing path

The existing Node.js workflow publishes from pushes to master. Replace that behavior completely.

Specifically:

  • remove the old branch-based publish workflow;
  • do not publish from master;
  • use main as the repository development/default branch;
  • remove the dependency on mikeal/merge-release@master;
  • do not replace one branch-based publishing mechanism with another.

The final repository must have one release path.

9. Revisit Makefile publishing targets

The Makefile may remain useful for local development, testing, and builds.

Release publication should be owned by GitHub Actions.

Remove or clearly demote local publish targets if they create a competing release mechanism. Local commands may remain as explicit package-level build/test helpers, but there must be no ambiguity about the canonical release process.

10. Make release preparation explicit

Document the release procedure in the repository, preferably in the README or a dedicated release-development document.

The documented flow should be:

  1. Develop normally against main.
  2. Add user-visible changes under CHANGELOG.md → [Unreleased].
  3. Prepare a release by setting both package versions to the intended version and moving changelog entries into a dated release section.
  4. Merge the release-preparation change to main.
  5. Confirm CI is green.
  6. Create an annotated tag such as v0.14.0 at the exact release commit.
  7. Push the tag.
  8. Release CI validates, tests, builds, publishes, and creates the GitHub Release.
  9. If release CI fails before publication, fix the release commit/workflow and retry using a corrected immutable release tag according to the documented recovery procedure. Never move an existing published tag.

Do not automate tag creation as part of normal CI.

Expected repository shape

The desired end state is approximately:

inditrans/
├── CHANGELOG.md
├── README.md
├── LICENSE
├── Makefile
├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── release.yml
├── flutter/
│   └── pubspec.yaml
├── nodejs/
│   └── package.json
├── tool/
│   ├── bump_version.dart
│   └── verify_release.dart
└── ...

Adjust this to the final Milestone 1 layout rather than blindly preserving paths that Milestone 1 intentionally changed.

Failure and safety requirements

  • Never publish when version validation fails.
  • Never publish when tests/builds fail.
  • Never publish from an ordinary branch push.
  • Never publish from an unrecognized tag.
  • Never silently publish one package with a different version from the other.
  • Never move/reuse an existing release tag.
  • Do not expose package credentials in logs.
  • A rerun of an already successful release must be considered: package registries may reject duplicate versions, so the workflow/documentation should make the expected recovery behavior explicit.
  • GitHub Release creation should correspond to the exact tag that triggered the workflow.

Verification / testing

Before considering the milestone complete:

  • Verify PR CI runs on a normal PR and does not publish.
  • Verify a push to main runs CI and does not publish.
  • Verify a non-version tag does not trigger the release workflow.
  • Verify a vX.Y.Z tag triggers release validation.
  • Verify a deliberately mismatched package version causes release validation to fail before publishing.
  • Verify a missing changelog release section causes validation to fail before publishing.
  • Verify the full test/build suite passes on the tagged commit.
  • Verify the npm package contains the intended version.
  • Verify the Flutter package contains the intended version.
  • Verify the GitHub Release is attached to the triggering tag.
  • Where possible, exercise registry publication in a safe/dry-run manner before the first real release.
  • Confirm there is no remaining workflow that publishes from master, main, or another branch push.

For the first production release after this milestone, use a deliberate version/tag and verify both registries and the GitHub Release manually.

Acceptance criteria

  • There is exactly one documented release path: SemVer Git tag → release workflow.
  • Release tags use vMAJOR.MINOR.PATCH.
  • flutter/pubspec.yaml and nodejs/package.json must match the tag version.
  • A root Keep a Changelog-compatible CHANGELOG.md exists with an [Unreleased] section.
  • Historical Flutter/Node.js changelog content has been consolidated without losing release history.
  • tool/verify_release.dart (or an equivalent repository-level validator) enforces the release contract.
  • Repository CI lives under root .github/workflows/.
  • CI runs for PRs and pushes to main and never publishes.
  • Release workflow runs only for SemVer tags.
  • Release workflow tests and builds before publication.
  • npm publication is tag-driven.
  • pub.dev publication is tag-driven.
  • GitHub Release creation is tag-driven.
  • Old branch-based master publishing is removed.
  • mikeal/merge-release is removed from the release path.
  • Legacy GitHub Actions versions are upgraded as part of the migration.
  • Release credentials use secure GitHub Actions configuration and, where supported, trusted publishing/OIDC.
  • Makefile/local tooling no longer presents a competing canonical publishing path.
  • Release preparation and recovery procedures are documented.
  • A simulated or safe verification of the release workflow has been completed without creating an unintended production release.

Out of scope

  • Changing transliteration behavior.
  • Adding new scripts/languages.
  • Refactoring the native transliteration core.
  • Changing package APIs unrelated to release engineering.
  • Automatically bumping versions on every commit.
  • Automatically creating release tags from CI.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    release-engineeringRelease, packaging, CI, and publishing infrastructureroadmapPart of the long-term project roadmap

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions