Skip to content

Give each package its own version and release only what changed - #37

Merged
jwrosewell merged 20 commits into
rcfrom
feature/own-package-versions
Nov 14, 2026
Merged

jwrosewell merged 20 commits into
rcfrom
feature/own-package-versions

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Step 3.1 of common-ci#306. ci/build-package.ps1 line 35 at 21fa1199 runs npm version with the run's one version for every package, so all 16 packages published 4.5.102 on 4 November 2026 (Publish run 41918901313) whatever had changed. That spends every package's allowance under the weekly release rule, and the release of 13 November would have done the same.

The decisions in the run book apply. Each package has its own version, tagged <package>/<version>, continuing from the version it last published, and only the patch number moves. A package is released when a file that counts under its folders changed since its last tag, and every package that depends on it is released with it, with its requirement raised to the new version. Documentation, tests, examples and manifest text never release a package (James Rosewell's decisions of 7 and 9 November).

Change

  • ci/packages.json lists the 16 packages with Name, Path, ExtraPaths, ExcludePaths, Ignore, DependsOn and Publish, read by common-ci's steps/release-plan.ps1 at 1c5c0e0. DependsOn is the packages here that each remote_package.json requires, and the build stops when a manifest and DependsOn disagree. Both on-premise packages have cxx in ExtraPaths, because their native modules compile from it.
  • Changes that do not count. common-ci leaves out every *.md and LICENSE file and every docs, tests, test and examples folder. Each package's Ignore adds **/__tests__/**, and ExcludePaths leaves out its examples folder. ci/release-plan-filter.ps1 leaves out a package.json or remote_package.json whose only changes are to description, keywords, homepage, repository, bugs, author, license or readme. A change to any other field counts, files, version, the dependencies, main, exports and scripts among them. ci/release-plan-filter.Tests.ps1 holds 25 Pester tests of the filter.
  • ci/release-plan.ps1 loads common-ci's step from the common-ci root the shared workflows run these scripts from, and adds the build plan and the versions the requirements are set from. Only the publish scripts load it, so the pull request builds do not need it. After a publish that stopped part way, it also plans each package not yet tagged at HEAD that depends on one that is, because common-ci's step leaves a package tagged at HEAD out of the plan and so does not count it as a change for its dependants.
  • ci/get-next-package-version.ps1 gives the run the highest planned version as its label, and ci/package-update-required.ps1 prints the plan and answers 1 when it is empty, as common-ci's nightly-publish.configure.ps1 lines 37 to 48 expects.
  • ci/build-package.ps1 packs only the planned packages, each at its own version. Each requirement on a package here becomes ^ and that package's version once the release is out, being its planned version, or its last released version when it is not planned. When nothing is planned, every package is packed at its released version, so the nightly publish still tests packed packages.
  • ci/install-package.ps1 builds a native module from source when its package has no tarball, because that package is not being released.
  • ci/publish-package.ps1 publishes only the planned tarballs, in place of common-ci's npm step, which publishes every tarball in name order and stops at the first failure. Each planned package is published, tagged <package>/<version> and given one GitHub release with its tarball attached before the next, in the plan's order, which lists each package after the packages it depends on. So npm never offers a package before the versions it requires, and a publish that stops part way leaves every package before the failure released and tagged, which the next run then leaves out of its plan. A package npm already has at the planned version, which an earlier run published and did not tag, is tagged and released without publishing it again, as npm refuses a version twice. ci/publish-package.Tests.ps1 runs the script against a repository of three packages, with npm and gh replaced by stand-ins.
  • ci/check-version-bump.ps1 runs common-ci's steps/check-version-bump.ps1 for each planned package against its own tags, and again against the version npm's latest dist-tag gives, so that a tag pushed by hand or a FirstVersion cannot set where a package starts. ci/check-release-interval.ps1 runs common-ci's steps/check-release-interval.ps1 with -TagPrefix for each planned package. common-ci's nightly-publish.yml runs both in place of its own checks, as 51degrees-node is in its OWN_CHECK list (line 142 at 1c5c0e0).
  • The two shared packages keep the code their index.js exports in lib, so the default ignore list applies to them as to every other package. dataExtension.js, exampleConstants.js and optionsExtension.js moved from examples, and exampleOutput.js, keyUtils.js and testConstants.js from tests, with the generated types. A one-line file at each old path re-exports the module in lib, so require('fiftyone.devicedetection.shared/tests/keyUtils') and the other five paths in each package still work. __tests__/layout.test.js in each package checks the exports, the packed files and the re-exports.
  • AGENTS.md describes the scheme in place of the single version, and ci/packages.ps1 loses $PublishedPackages, which nothing reads now.

The merges of rc

313be18 merges rc at 24592acb. rc had changed the shared packages' tests/keyUtils.js and tests/testConstants.js (the deprecated environment variable names of #70 and #88), which this branch moved to lib, so those changes are applied to the files in lib. The environment variable name tests in ci/tests now name the files in lib, where the names are read. e305bf7 changes only AGENTS.md, and 8a5fe26 fixes the indentation of one line in each __tests__/layout.test.js, which the lint refused. 3af4682 merges rc at 7cbf8bc7, which added #94 and #15, with no conflict.

26dea568 changes ci/publish-package.ps1 to publish, tag and release each package in turn, after review found that the earlier script published in name order through common-ci's npm step, so fiftyone.devicedetection reached npm before the seven packages it requires, and that a publish stopping part way left versions on npm that the next run planned again and npm refused. It adds ci/publish-package.Tests.ps1. c21ff4f1 says so in AGENTS.md. A Publish dry run of c21ff4f (run 43520747902) then stopped at the first package, because npm read the relative path package/fiftyone.devicedetection.shared-4.5.103.tgz as a GitHub repository and tried to clone it ("Permission denied (publickey)"). common-ci's npm step had passed npm the full path. 163a4241 makes the test's npm stand-in refuse such a path, and 5e38889e gives npm the tarball's full path. 8d0bc615 tests a rerun whose only package left is a dependant with no change of its own, the case a review of the same scheme in PHP found, and eaf93b1d plans that dependant in ci/release-plan.ps1. b239089c adds the workflow that runs these Pester tests, after review found that nothing would run them once this merged, and says so in AGENTS.md. It changes no script under ci/.

What had to come first

common-ci's #307, #310 and the OWN_CHECK entry reached common-ci's main with common-ci#312 (1c5c0e0). The 16 tags below were pushed on 7 November as lightweight tags with no release, on 384bff2d, the commit every package published 4.5.102 from, and npm's latest is 4.5.102 for all 16. A lightweight tag is dated by its commit, 4 November, so the weekly check counts from then.

fiftyone.pipeline.core/4.5.102                fiftyone.devicedetection.onpremise/4.5.102
fiftyone.pipeline.engines/4.5.102             fiftyone.devicedetection/4.5.102
fiftyone.pipeline.engines.fiftyone/4.5.102    fiftyone.geolocation/4.5.102
fiftyone.pipeline.cloudrequestengine/4.5.102  fiftyone.ipintelligence.shared/4.5.102
fiftyone.pipeline.did/4.5.102                 fiftyone.ipintelligence.cloud/4.5.102
fiftyone.pipeline.translation/4.5.102         fiftyone.ipintelligence.onpremise/4.5.102
fiftyone.devicedetection.shared/4.5.102       fiftyone.ipintelligence.translation/4.5.102
fiftyone.devicedetection.cloud/4.5.102        fiftyone.ipintelligence/4.5.102

Checks

Publishing each package in turn. ci/publish-package.Tests.ps1 runs ci/publish-package.ps1 against a repository of three packages, zeta.base, alpha.app, which requires it, and mid.tool, so name order and dependency order differ. npm and gh are functions that keep a registry and the releases in memory, git is real, and origin is a bare repository. It checks that npm is given a path it reads as a file, that each package is published after the packages it requires, that a version npm already has is tagged and released without publishing it again, that a rerun after a publish that stopped on zeta.base finishes with every package on npm and every tag pushed, that a rerun after one that stopped on alpha.app, whose only reason is zeta.base, publishes alpha.app alone, and that a dry run publishes nothing and pushes no tag.

  • Red, the tests on the script before the fix (3af4682): run 43520481398, 3 of the 4 failed. alpha.app was published before zeta.base, a version npm already had stopped the run ("npm refuses to publish over zeta.base@1.0.1"), and the rerun stopped on alpha.app@1.0.1, which the first run had published and not tagged. The dry run test passed.
  • Green, on 26dea56: run 43520575696, 29 of 29, being these 4 and the filter's 25.
  • Red for the path, the stricter stand-in on 163a424 before the path fix: run 43526575875, all 4 publish tests failed with "npm reads 'package/mid.tool-1.0.1.tgz' as a GitHub repository".
  • Green on 5e38889: run 43526677613, 29 of 29.
  • Red for the dependant, the new test on 8d0bc61 before the plan fix: run 43539483088, 1 of 30 failed, "releases on a rerun a package whose only change is a dependency the stopped publish released", where the rerun published nothing.
  • Green on the head eaf93b1: run 43539560456, 30 of 30, being these 5 and the filter's 25.

Every change to ci/ runs these tests. The CI script tests workflow ran on this pull request at the head b239089 (run 43558971492), as the pull request changes ci/, and 30 of 30 Pester tests passed on Ubuntu and 30 of 30 on Windows, being the 5 of ci/publish-package.Tests.ps1 and the 25 of ci/release-plan-filter.Tests.ps1. It runs again for every pull request and every push to main or rc that changes ci/.

The plan through the real Publish workflow. Run 43541104747 ran this repository's Publish workflow with dryrun on a branch named version/owner-node-37-dryrun4, holding eaf93b1, whose files under ci/ are this head's, with the macOS entries left out of ci/options.json and skip-tests and skip-compare-performance set. A version/ branch was used because common-ci's Package step runs ci/publish-package.ps1 only on main, alpha or a version/ branch (nightly-publish.package.ps1 line 32 at 1c5c0e0). Every job that ran passed.

  • Configure printed this plan, 15 packages changed since their 4.5.102 tag and one released because a package it depends on is.
Name                                 Previous Version Reason
fiftyone.devicedetection.shared      4.5.102  4.5.103 changed since 4.5.102 (17 files)
fiftyone.ipintelligence.shared       4.5.102  4.5.103 changed since 4.5.102 (17 files)
fiftyone.pipeline.core               4.5.102  4.5.103 changed since 4.5.102 (30 files)
fiftyone.pipeline.did                4.5.102  4.5.103 changed since 4.5.102 (7 files)
fiftyone.pipeline.engines            4.5.102  4.5.103 changed since 4.5.102 (11 files)
fiftyone.pipeline.translation        4.5.102  4.5.103 changed since 4.5.102 (1 file)
fiftyone.ipintelligence.translation  4.5.102  4.5.103 a package it depends on is released
fiftyone.pipeline.cloudrequestengine 4.5.102  4.5.103 changed since 4.5.102 (8 files)
fiftyone.pipeline.engines.fiftyone   4.5.102  4.5.103 changed since 4.5.102 (9 files)
fiftyone.devicedetection.cloud       4.5.102  4.5.103 changed since 4.5.102 (2 files)
fiftyone.devicedetection.onpremise   4.5.102  4.5.103 changed since 4.5.102 (8 files)
fiftyone.geolocation                 4.5.102  4.5.103 changed since 4.5.102 (2 files)
fiftyone.ipintelligence.cloud        4.5.102  4.5.103 changed since 4.5.102 (2 files)
fiftyone.ipintelligence.onpremise    4.5.102  4.5.103 changed since 4.5.102 (6 files)
fiftyone.devicedetection             4.5.102  4.5.103 changed since 4.5.102 (1 file)
fiftyone.ipintelligence              4.5.102  4.5.103 changed since 4.5.102 (1 file)

The changes behind each are real code. For example fiftyone.pipeline.translation's one file is translationEngine.js, fiftyone.devicedetection's is deviceDetectionPipelineBuilder.js, and both on-premise packages count the move of cxx. fiftyone.ipintelligence.translation has no counted change of its own and is released because fiftyone.pipeline.translation is.

  • The version check passed each of the 16 packages twice, against its own tags and against npm's 4.5.102, as "The patch place, which needs no agreement". The weekly check reported that the rule is not enforced, as WEEKLY_RELEASES_FROM is not set.
  • Build packed those 16 tarballs, each at 4.5.103 and each requirement on another package here at ^4.5.103.
  • Package ran ci/publish-package.ps1, which, for each of the 16 packages in turn and in the plan's order, ran npm publish --dry-run ("+ fiftyone.pipeline.core@4.5.103" and so on) and then printed "Dry run - tagged 'fiftyone.pipeline.core/4.5.103' locally, not pushed, no release made". fiftyone.devicedetection and fiftyone.ipintelligence, which require the most, came 15th and 16th. npm had none of the 16 at 4.5.103, so none was tagged without publishing. common-ci then printed "Tag update skipped", so no single 4.5.103 tag was made. Afterwards the repository has no tag holding 4.5.103.

Run 43526712326 on 5e38889, before the plan fix for a rerun, gave the same plan and output. Run 43492725405 on 3af4682 ran the earlier script, which published all 16 in name order through common-ci's npm step and then tagged them, so fiftyone.devicedetection came first.

The whole Publish workflow, tests included. Run 43476224513 ran the Publish workflow with dryrun on tmp/owner-node-37-publish, holding 313be18 merged with #95's head 0468ed4, which fixes the example that failed the 13 November publish, and with the macOS entries left out. Configure printed the same 16 packages, Build packed the same 16 tarballs, all six Test jobs on Ubuntu, Ubuntu ARM and Windows with Node.js 22 and 24 installed them with ci/install-package.ps1 and passed, and Compare Performance passed. On that branch name the Package step skips ci/publish-package.ps1, which is why the run above was made.

Which package each kind of change releases. Run 43476196706 checked out 313be18 and common-ci main at 1c5c0e0. The 25 Pester tests of the filter passed. Then it tagged every package locally at HEAD, as if the next release had gone out, and for each of 14 scenarios made one local commit and compared the plan with the changed packages and every package that depends on one of them, worked out separately from DependsOn. All 14 passed, each planned package one patch on from its tag.

Scenario Planned
No change none, and the plan is empty
A code file of fiftyone.pipeline.did, of fiftyone.geolocation, or of fiftyone.ipintelligence.translation that package alone
A code file of fiftyone.pipeline.engines it and its 9 dependants
A code file of fiftyone.pipeline.core it and its 12 dependants
lib/keyUtils.js of fiftyone.devicedetection.shared, or lib/dataExtension.js of fiftyone.ipintelligence.shared that package and its 3 dependants
The cxx submodule moved both on-premise packages, fiftyone.devicedetection and fiftyone.ipintelligence
A dependency in the package.json of fiftyone.pipeline.did that package alone
description, keywords and homepage in three manifests none
A readme, a test, a __tests__ file, an example and a docs file none
The re-exports at the shared packages' old paths none
ci/options.json, a workflow, AGENTS.md and README.md none

The commits after 313be18 change AGENTS.md, two lines of tests, ci/publish-package.ps1 and its tests, and what rc brought in, and none of the files that make the plan (ci/release-plan.ps1, ci/release-plan-filter.ps1 and ci/packages.json).

Full build. Run 43559008982 on tmp/pr-matrix-37-mon7, started by tools/pr-matrix.py without macOS, on the head b239089. All seven Setting 'PullRequestSha' lines are b239089, Configure printed "Left out 2 macOS entries", and each of the six Ubuntu, Ubuntu ARM and Windows jobs on Node 22 and 24 passed 1,377 unit tests with 2 skipped and 132 integration tests with 4 skipped. macOS was left out of this draft's full build under the 9 November decision. The rc head is built with macOS before it merges into main. The full build on e305bf7 failed lint on one line's indentation in each __tests__/layout.test.js, which 8a5fe26 fixes, and run 43539579929 on eaf93b1 passed before the workflow was added.

What remains

  • ExcludePaths and common-ci's default **/examples/** and **/docs/** now leave out the same folders, so ExcludePaths and the code in ci/release-plan.ps1 that reads it could go in a later change.
  • A change only to a package's own readme.md does not release it, so a README fix reaches npm with the package's next release for another reason.
  • ci/build-extension.ps1 and the Node.js versions in ci/options.json decide the native modules the on-premise tarballs carry, but neither is under a package's folders, so a change to either releases nothing. Whether such a change should release the on-premise packages is a rule question for James.
  • The PreBuild jobs still build both native modules on every entry when neither on-premise package is planned.

Each of the 16 packages is listed in ci/packages.json and tagged
<package>/<version>. common-ci's steps/release-plan.ps1 works out which
packages a publish releases. A package is released when a file under its
folders changed since its last tag, and every package depending on it is
released with it. Only the patch number moves.

ci/build-package.ps1 packs only the planned packages, each at its own
version, and requires each package here at the version it has once the
release is out. ci/publish-package.ps1 publishes only those tarballs and
tags each package. ci/check-version-bump.ps1 and
ci/check-release-interval.ps1 check each planned package on its own.

Step 3.1 of 51Degrees/common-ci#306.
ci/release-plan.ps1 looks at COMMON_CI before the common-ci root, and
ci/check-version-bump.ps1 runs the shared check twice for each package.
common-ci's release plan now leaves out documentation, tests, examples
and LICENSE files, and calls a repository's ci/release-plan-filter.ps1
for the files left. This repository's filter does not count a
package.json or remote_package.json whose only changes since the
package's last tag are to description, keywords, homepage, repository,
bugs, author, license, files or readme. A change to any other field,
such as version, dependencies, main, exports or scripts, counts.

Every package also ignores __tests__ folders, the other folder name
jest finds tests in. The two shared packages export modules from their
tests and examples folders, so they turn the defaults off and ignore
only their Markdown and LICENSE files.

ci/release-plan-filter.Tests.ps1 holds the Pester tests of the filter.
fiftyone.devicedetection.shared and fiftyone.ipintelligence.shared
published the modules in their examples and tests folders through
index.js, so the release plan could not ignore those folders for them.
The modules now live in lib, the packages use the default ignore list,
and a test checks the layout.
# Conflicts:
#	ip-intelligence/fiftyone.ipintelligence.shared/index.js
#	ip-intelligence/fiftyone.ipintelligence.shared/lib/exampleOutput.js
#	ip-intelligence/fiftyone.ipintelligence.shared/types/lib/exampleOutput.d.ts
Each old path under examples and tests is a one-line re-export of the module
now in lib, and is listed in files so that it is published. The release plan
still ignores both folders, and the stubs never change, so the code in lib
decides whether a package is released.
…ports fixed

A change to files in package.json decides what a package ships, so it counts
towards a release. The layout test now names the six re-exports, so deleting
one fails, and each re-export carries the licence header. AGENTS.md no longer
calls the shared packages an exception.
@jwrosewell jwrosewell mentioned this pull request Nov 13, 2026
rc changed the shared packages' tests/keyUtils.js and tests/testConstants.js,
which this branch moved to lib with a one-line re-export at the old path. The
changes are applied to the files in lib, and the environment variable name
tests now name the files in lib, where the names are read.
…requires

ci/publish-package.ps1 published every tarball in name order through
common-ci's npm step, which stops at the first failure, and tagged nothing
until all had published. So fiftyone.devicedetection went to npm before the
seven packages it requires at the new version, and a publish that stopped
part way left npm with untagged versions that the next run planned again and
npm refused. Each planned package is now published, tagged and released in
the plan's order before the next, and one npm already has is tagged and
released without publishing it again, so a rerun finishes the release.

ci/publish-package.Tests.ps1 runs the script against a repository of three
packages with npm and gh replaced by stand-ins.
npm reads a relative path such as package/name.tgz as a GitHub repository
and tries to clone it, which is how the Publish dry run failed.
npm read package/<name>.tgz as a GitHub repository and tried to clone it,
which stopped the Publish dry run on the first package.
common-ci's plan leaves out a package tagged at HEAD and does not count it
as a change for its dependants, so after a publish that stopped part way, a
package whose only reason was a dependency already released and tagged was
never released. Such a dependant is now added with the patch after its last
release.
@jwrosewell
jwrosewell marked this pull request as ready for review November 14, 2026 06:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant