Documentation menu

Primate compatibility and migration

Flog's automated Primate acceptance fixture lives at internal/cli/testdata/primate-compat. It is deliberately smaller than the real repository but preserves its relevant shape: apps/*, packages/*, the scripts workspace, prerelease package versions, workspace:^ dependencies, peer dependencies, npm aliases, package scripts, publishable packages, and the root lifecycle-script allowlist.

TestPrimateEndToEndCompatibility copies that fixture to a clean temporary directory and uses only flog plus an in-process npm-compatible HTTP registry. It checks:

  • clean installation, canonical flog.lock, workspace links, an npm: alias, peer resolution, and one allowed versus one denied install script;
  • deletion of every node_modules followed by locked offline installation from flog's verified cache;
  • dependency-ordered recursive build and test scripts;
  • every publishable package's dry-run file list and rewritten final manifest against expected-pack.json;
  • rejection of omitted and latest tags for Primate-style prereleases;
  • recursive public prerelease publication under next, including registry-requested web authentication and dependency-first upload order;
  • stable publication under latest; and
  • refusal to publish a maintenance version behind an existing newer latest.

The suite never reads, writes, or publishes the real Primate checkout. The registry binds only to the test process and stores uploads in memory.

Current real-repository result#

An isolated git archive of Primate origin/master at c45c8e28c2fb83a8eb996f50f273c3e77c063c7c was tested with:

flog lock --cwd /path/to/archive

The clean run writes a complete lockfile without adding peer-provider boilerplate to Primate's manifests. Derived importer providers include rxjs, React type packages, @babel/core, @internationalized/date, linkedom, and the website framework peers. Repeating flog lock with matching manifests does not access registry metadata or change the lock bytes.

Before automatic required-peer installation, the same migration process stopped before writing a lockfile with diagnostics such as:

peer dependency missing for importer apps/angular (@app/angular): @angular/common@22.2.2 requires rxjs@^6.5.3 || ^7.4.0

Peer diagnostics name the consuming importer path (and its package name). For transitive failures, a deterministic via chain identifies the dependency path from that importer to the package declaring the peer.

The four manifests that directly declare @angular/common—apps/angular, apps/frontend, apps/website, and packages/angular—do not declare rxjs. After adding compatible providers in a disposable copy, strict resolution next reports that a consumer of workspace @primate/react@0.23.0-pre does not provide @types/react-dom@19. Primate currently relies on its existing package manager to supply undeclared peers automatically. Flog now resolves these required providers automatically and records their exact placement and versions in flog.lock; those four manifests do not need boilerplate rxjs declarations. Optional peers remain absent unless explicitly provided.

Compared with npm, flog deliberately keeps its non-hoisted nested layout and does not silently override incompatible explicit providers. All consumers in one peer environment must share one compatible provider or resolution fails. Generate the first real flog.lock only after a clean flog lock run succeeds and inspect the derived peerProviders edges.

Root manifest#

Change the package-manager declarations to the reviewed flog release:

{
  "packageManager": "flog@<version>",
  "devEngines": {
    "packageManager": {
      "name": "flog",
      "version": "<accepted-range>",
      "onFail": "error"
    }
  }
}

Keep the existing workspace patterns. Keep allowScripts.esbuild and allowScripts.oracledb only after reviewing those exact packages; flog denies all other dependency lifecycle scripts by default.

Remove nub.lock, generate the lock for both the contributor glibc environment and Alpine website builder, inspect it, and commit it:

flog lock \
  --target linux/x64/glibc \
  --target linux/x64/musl

Flog's only supported prerelease lock schema is version 0. If the migration branch already contains the same target-matrix shape from an earlier development artifact labeled version 2, preserve the reviewed graph and change only the root version value from 2 to 0. Verify that one-line diff before running plain locked installs. Flog intentionally does not parse or rewrite the old label. Discard and regenerate an earlier single-target development lock instead of relabeling its incompatible shape.

Do not remove language-specific locks such as Gemfile.lock. Contributor, CI, and release builds should then use plain flog install, which strictly requires the represented target graph to match and does not rewrite an existing lock. The website image in apps/website/Containerfile should also use plain flog install; install itself now enforces the missing-lock assertion.

No npm, pnpm, yarn, bun, or Nub command should update the dependency tree or lockfile after the switch.

Command migration#

Root scripts map as follows:

Current command Flog command
Nub recursive package/app build flog build
Nub recursive package-only build flog run -r --workspace='./packages/**' build
Nub recursive app-only build flog run -r --workspace='./apps/**' build
nub run -r test flog run -r --if-present test
Nub app-only tests flog run --workspace='./apps/**' --if-present test
selected app test:backend / test:frontend use the same workspace selector plus --if-present
nub publish -r --access=public for current prereleases flog publish -r --access public --tag next
nub update -r -i -L flog upgrade -r --latest (review the complete plan, then confirm)
nub view <package> version flog view <package> version

Within package scripts, replace nub run <script> with flog run <script>. Replace npx proby with proby; flog places dependency binaries on the script PATH without invoking another package manager. For a direct shell invocation, use flog x proby.

Remove app prebuild hooks whose only purpose is to recursively query and build an app's dependencies.

Build from the monorepo root with the first-class source graph instead:

flog build --workspace apps/name

For the website deployment builder, use the same production closure for both steps and keep the app's own "build": "primate build" script:

RUN flog install --workspace apps/website --prod
RUN flog build --workspace apps/website --prod

Do not call flog build --prod from the website's prebuild; that would select the website again while it is already being built. --prod omits the Angular, Go, database, Python, React, Ruby, Solid, Svelte, test, and Vue workspaces that are reachable only through website development edges, while retaining registry compilers, types, generators, and shared root build tooling for every selected source workspace. It does not produce the final runtime image; copy the bundled website output in the later image stage as before.

For package/app release validation, retain every permitted result even when an earlier package fails:

flog run -r --if-present --no-bail --jobs auto --json test > test-results.json

Failed selected dependencies block their selected dependents, while later independent packages still run. The versioned report retains success, failure, signal, missing/skipped, blocked, and interrupted outcomes and exits nonzero if the release validation is incomplete or unsuccessful.

flog build is itself the top-level graph operation. Nested Flog invocations used for other package-local work still coordinate successful equivalent work, but no nested dependency-only prebuild is needed for ordinary app builds.

Dependency maintenance uses recursive within-range dry runs and deterministic JSON. Crossing range bounds uses flog upgrade --recursive --latest only at a real terminal with explicit confirmation; it cannot be scripted or combined with JSON or dry-run output. Both paths preserve workspace protocols, aliases, prerelease channels, and peer compatibility contracts. After confirmation, flog upgrade publishes the reviewed manifest and lockfile bytes and installs their exact graph before reporting success. See deliberate dependency updates.

Packing review#

Before replacing Primate's recursive publication command, review flog publish --recursive --dry-run as a complete release set. Flog blocks a workspace dependency compatibility-line transition when a published dependent is omitted or under-bumped, and reports the transitive package chain. See workspace release sets. Use the dedicated incomplete-set override only for a documented compatibility exception.

Build packages, then inspect every publishable artifact without writing it:

flog run -r --workspace='./packages/**' build
flog pack -r --workspace='./packages/**' --dry-run --json > pack-plan.json

The schema-versioned pack plan includes all publishable package matches under packages and records private matches such as @primate/test under omitted with their importer path and private reason. Private apps outside the selection do not appear in either collection.

Review each file list and final manifest. In particular, every workspace: specifier must have become its concrete published range. To retain archives for manual inspection, replace --dry-run with --destination ./artifacts.

Publishing#

Primate package versions ending in -pre require an explicit non-latest tag:

flog publish -r --access public --tag next

Omitting --tag or specifying --tag latest is intentionally rejected. The interactive command displays the complete plan before confirmation and handles OTP or registry web authentication. Automation must opt in explicitly with --yes and should also write --summary <path> for audit and safe resume.

For a stable release, omit --tag to use latest only after reviewing the plan. Flog refuses to move latest behind a newer registry version and revalidates registry state before each package upload. See publishing.md for the remaining non-atomic registry race.

Primate's explicit --access public is preserved in the complete plan, each package's npm-compatible upload document, and the machine-readable summary. This is required for first publication of a scoped package because scoped packages otherwise default to restricted. After an accepted upload, flog confirms that the exact version is anonymously readable before reporting published. A registry that rejects, ignores, or cannot verify the requested access produces a failed, mismatched, or unverified outcome rather than an implicit fallback or false public-success report; an accepted upload may already be irreversible.

Performance sample#

On the development host, the representative fixture completed a clean local registry install in about 45 ms, locked offline reinstall in about 50 ms, recursive build and test in about 480 ms, pack inspection in about 12 ms, and web-authenticated recursive publication in about 210 ms. These numbers are diagnostic rather than a performance gate; the fixture is intentionally much smaller than Primate. The expected real-project cost driver is the initial fully nested, non-hoisted install and sequential workspace scripts.

Migration gate#

Do not open a Primate migration change until all of the following are true:

  1. a clean real-Primate flog lock run resolves every required peer, and all automatic provider placements and remaining explicit-provider conflicts have been inspected;
  2. the generated real flog.lock, build/test results, pack plan, and dry-run publication plan have been inspected by a human; and
  3. within-range recursive upgrades have been dry-run and reviewed, and any flog upgrade --recursive --latest plan has been inspected and explicitly confirmed at a real interactive terminal.