Add npm backfill pipeline, shared htpasswd registry auth docs
All checks were successful
continuous-integration/drone/push Build is passing

- drone npm template gains backfill-npm-from-tags (mirrors PyPI)
- document the shared /auth/htpasswd credential model (npm + PyPI +
  docker) and Verdaccio's bcrypt requirement
- point doc-weaver source/path at ../react/doc-weaver
This commit is contained in:
2026-10-21 00:57:08 +05:30
parent 7f951f3fe7
commit 5a1204fbcb
16 changed files with 234 additions and 36 deletions

View File

@@ -733,18 +733,25 @@ with <code>from_secret</code> — never commit credentials.</p>
</ul>
<hr />
<h2 id="type-b-npm-package-npm-libyaml">Type B — npm package (<code>npm-lib.yaml</code>)</h2>
<p>For internal npm libraries (the React packages). One pipeline,
<strong><code>build-and-publish-npm</code></strong>, triggered on <code>tag</code>:</p>
<ul>
<p>For internal npm libraries (the React packages). The file contains <strong>two
pipelines</strong>:</p>
<ol>
<li><strong><code>build-and-publish-npm</code></strong> — triggered on <code>tag</code>:</li>
<li><code>check-version</code> — <strong>guard</strong>: fail unless <code>$DRONE_TAG</code> equals the version in
<code>package.json</code>; query <code>$NPM_REPO_URL</code> for <code>name@version</code>; if it already
exists, exit <code>78</code> and skip the whole build.</li>
<code>package.json</code>; query <code>$NPM_REPO_URL</code> for <code>name@version</code>; if it already
exists, exit <code>78</code> and skip the whole build.</li>
<li><code>quality-gate</code> — write <code>.npmrc</code> from the secrets, <code>npm install</code>, then
<code>npm run quality-gate</code>.</li>
<code>npm run quality-gate</code>.</li>
<li><code>build-package</code> — <code>npm install &amp;&amp; npm run build</code> into <code>dist/</code>.</li>
<li><code>publish-to-private-npm</code> — <code>npm publish --access restricted</code> to
<code>$NPM_REPO_URL</code>.</li>
</ul>
<code>$NPM_REPO_URL</code>.</li>
<li><strong><code>backfill-npm-from-tags</code></strong> — triggered on <code>custom</code>:</li>
<li><code>fetch-tags</code> — <code>git fetch --tags --force</code>.</li>
<li><code>build-and-publish-missing</code> — walk every tag in version order, skip the
ones already on the registry, <code>git checkout</code> the rest, verify
<code>tag == package.json version</code> at that commit, build, and publish. This is
the disaster-recovery / replay path.</li>
</ol>
<p>Secrets: <code>NPM_REPO_URL</code>, <code>NPM_USERNAME</code>, <code>NPM_PASSWORD</code>. Auth is a generated
<code>.npmrc</code> (<code>registry=</code> + <code>_auth=</code> base64 of <code>user:pass</code>) written from secrets at
runtime — the committed <code>.npmrc</code> in a repo only points at the registry URL.</p>
@@ -752,6 +759,16 @@ runtime — the committed <code>.npmrc</code> in a repo only points at the regis
<li><strong>Ship as-is.</strong> Name and version come from <code>package.json</code>, so the same file
is reused verbatim across every npm package.</li>
</ul>
<blockquote>
<p><strong>Registry credentials are shared.</strong> The private npm registry (Verdaccio at
<code>npm.aetoskia.com</code>) authenticates against the <strong>same <code>htpasswd</code> file</strong> as the
private PyPI index and the Docker registry (<code>/auth/htpasswd</code>), with
registration disabled (<code>max_users: -1</code>). <code>NPM_USERNAME</code> / <code>NPM_PASSWORD</code> are
therefore the <strong>same credentials as <code>PIP_USERNAME</code> / <code>PIP_PASSWORD</code></strong> — no
separate npm account exists. Verdaccio's htpasswd plugin accepts <strong>bcrypt</strong>
hashes (<code>$2y$…</code>); non-bcrypt entries that pypiserver tolerates will not
authenticate against npm. See <a href="../93_library_registry/">93 – Library Registry</a>.</p>
</blockquote>
<hr />
<h2 id="type-c-docker-deployment-service-dockeryaml">Type C — Docker deployment (<code>service-docker.yaml</code>)</h2>
<p>For a service that ships a container image — a long-running API/worker, e.g.
@@ -821,8 +838,8 @@ triggered on <code>tag</code>:</p>
<code>package.json</code> version; a service needs a root <code>VERSION</code> file. See
<a href="../91_versioning_and_changelog/">91 – Versioning &amp; Changelog</a>.</li>
<li>Do not rename the pipelines; tooling and runbooks key off
<code>build-and-publish-pypi</code>, <code>build-and-publish-npm</code>, and
<code>build-and-deploy-service</code>.</li>
<code>build-and-publish-pypi</code>, <code>build-and-publish-npm</code>,
<code>build-and-deploy-service</code>, and their <code>backfill-*-from-tags</code> companions.</li>
</ul>
<hr />
<h2 id="related">Related</h2>

View File

@@ -626,6 +626,15 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#publishing-registry-auth" class="md-nav__link">
<span class="md-ellipsis">
Publishing &amp; registry auth
</span>
</a>
</li>
<li class="md-nav__item">
@@ -821,6 +830,15 @@ matching library, its import name, its "use instead of" guidance, and its MCP
endpoint (or, for the auth-server service, a spec pointer). It also serves the
pinned auth-server OpenAPI contract at <code>docs://spec</code>. Query it before writing
any infrastructure.</p>
<h2 id="publishing-registry-auth">Publishing &amp; registry auth</h2>
<p>Python packages publish to the private index (<code>pip.aetoskia.com</code>, <code>pypiserver</code>)
and npm packages to the private registry (<code>npm.aetoskia.com</code>, Verdaccio). Both
authenticate against the <strong>same <code>htpasswd</code> file</strong> (<code>/auth/htpasswd</code>) used by the
Docker registry and the Traefik basic-auth middleware; registration is disabled,
so the CI credentials (<code>PIP_USERNAME</code> / <code>PIP_PASSWORD</code>, <code>NPM_USERNAME</code> /
<code>NPM_PASSWORD</code>) are one and the same account — there is no separate npm user.
Verdaccio requires <strong>bcrypt</strong> htpasswd entries; see
<a href="../90_drone_pipelines/">90 – Drone Pipelines</a> for the CI wiring.</p>
<h2 id="notes">Notes</h2>
<ul>
<li><strong>Hexa</strong> is a full library with its own MCP server on <code>:8008</code>: read

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@
"module": "wiki_development_releasing",
"content": {
"path": "wiki_development_releasing",
"docstring": "# Releasing & Versioning\n\nHow to cut a release of any Aetos repository. The rule lives in the reference\npage [Versioning & Changelog](../lib/91_versioning_and_changelog/); this is the\nrunbook you follow.\n\nEvery repository is one of two Drone shapes — a **PyPI package** or a **Docker\ndeployment** ([Drone Pipelines](../lib/90_drone_pipelines/)). Both release the\nsame way: the version bump and changelog promotion go **in the merge request**,\nand the tag is created on the merged commit.\n\n---\n\n## 🧾 Before you start\n\n- You have an open **merge request** holding the change, and `main` is green\n (the Drone quality gate passed on the last run).\n- You know whether this is a **breaking**, **feature**, or **fix** release.\n- The version file exists:\n - package → `version` in `pyproject.toml`\n - service → `VERSION` at the repo root\n\n---\n\n## 🚦 Pick the next version\n\nThe version file and the tag must be identical. For `0.y.z`, `0.y` is the major\nline (see the reference page); once at `1.0.0`, normal SemVer applies.\n\n| Change | `0.y.z` → | `1.0.0+` → |\n|--------|-----------|------------|\n| Breaking | `0.(y+1).0` | `(x+1).0.0` |\n| Feature | `0.y.(z+1)` | `x.(y+1).0` |\n| Fix | `0.y.(z+1)` | `x.y.(z+1)` |\n\n---\n\n## 🌿 Release in the MR\n\nThe version is known while the change is still a merge request, so the bump\nrides along with it. Do all edits **in the MR**:\n\n- **Reconcile the changelog first.** `[Unreleased]` is a **draft, not the source\n of truth**: diff the change against the last tag\n (`git log <last-tag>..HEAD`, `git diff --stat <last-tag>..HEAD`) and add an\n entry for every user-facing change that is missing. **Always show the proposed\n additions to the user and wait for approval** before promoting — ask rather\n than guess when an entry is ambiguous.\n\n- **Promote the changelog** in `CHANGELOG.md`: move the (now reconciled)\n `[Unreleased]` content under a new `## [x.y.z] - YYYY-MM-DD` heading, leave a\n fresh empty `[Unreleased]` on top, and add the compare links — naming the\n **version/tag**, never a commit id:\n\n ```markdown\n [Unreleased]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.z...HEAD\n [x.y.z]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.(z-1)...x.y.z\n ```\n\n- **Bump the version file** to the intended release version: `version = \"x.y.z\"`\n in `pyproject.toml` (packages) or the `VERSION` file (services).\n\n---\n\n## 🏷️ Tag the merged commit\n\n1. **Merge or squash** the MR into `main`.\n2. **Create the tag** on the resulting commit: `git tag x.y.z` (lightweight).\n3. **Push the tag** (`git push origin x.y.z`) — Drone builds the release.\n\n### 🤖 Agent-assisted tagging\n\nThe agent does not author or merge the MR and never tags before the merge. After\nyou confirm the MR is merged/squashed and tell it to proceed, the agent:\n\n1. Waits for that explicit go-ahead.\n2. Finds the commit that introduced the bump:\n - package:\n `git log -S 'version = \"x.y.z\"' -n 1 --format='%H %s' -- pyproject.toml`\n - service:\n `git log -S 'x.y.z' -n 1 --format='%H %s' -- VERSION`\n3. Verifies that commit's version file equals `x.y.z`, then tags **only** that\n commit: `git tag x.y.z <sha>`.\n4. Does **not** push — it asks you to run `git push origin x.y.z`.\n\n---\n\n## 🤖 What Drone does\n\n- **Type A (package):** asserts `tag == pyproject version`, then skips if that\n version already exists on the index, runs the quality gate, builds, and\n uploads the wheel + sdist.\n- **Type B (service):** asserts `tag == VERSION`, resolves the tag, skips if the\n image already exists, runs the quality gate, builds, pushes, and restarts the\n container.\n\nIf the guard fails, nothing is published — fix the version file, amend the\ncommit, and move the tag **only** if it was never pushed.\n\n---\n\n## 🛟 Backfill / recovery\n\nIf an older tag never published (a past version-file mismatch, an index\noutage):\n\n- **Package:** run the `backfill-pypi-from-tags` pipeline (custom event). It\n checks out every tag, verifies `tag == pyproject version` at that commit,\n and uploads the ones missing from the index. Already-fixed history is never\n rewritten.\n- **Service:** rebuild the image for the missing tag with a fresh commit and a\n new patch version; do not move a published tag.\n\n---\n\n## 🔁 If a release is wrong\n\n- Never reuse or force-move a pushed tag.\n- Fix forward: bump to the next patch, promote the changelog, tag again.\n\n---\n\n## Related\n\n- [Testing & Quality Gates](01_testing.md) · [Versioning & Changelog](../lib/91_versioning_and_changelog/) · [Drone Pipelines](../lib/90_drone_pipelines/)\n",
"docstring": "# Releasing & Versioning\n\nHow to cut a release of any Aetos repository. The rule lives in the reference\npage [Versioning & Changelog](../lib/91_versioning_and_changelog/); this is the\nrunbook you follow.\n\nEvery repository is one of two Drone shapes — a **PyPI package** or a **Docker\ndeployment** ([Drone Pipelines](../lib/90_drone_pipelines/)). Both release the\nsame way: the version bump and changelog promotion go **in the merge request**,\nand the tag is created on the merged commit.\n\n---\n\n## 🧾 Before you start\n\n- You have an open **merge request** holding the change, and `main` is green\n (the Drone quality gate passed on the last run).\n- You know whether this is a **breaking**, **feature**, or **fix** release.\n- The version file exists:\n - package → `version` in `pyproject.toml`\n - service → `VERSION` at the repo root\n\n---\n\n## 🚦 Pick the next version\n\nThe version file and the tag must be identical. For `0.y.z`, `0.y` is the major\nline (see the reference page); once at `1.0.0`, normal SemVer applies.\n\n| Change | `0.y.z` → | `1.0.0+` → |\n|--------|-----------|------------|\n| Breaking | `0.(y+1).0` | `(x+1).0.0` |\n| Feature | `0.y.(z+1)` | `x.(y+1).0` |\n| Fix | `0.y.(z+1)` | `x.y.(z+1)` |\n\n---\n\n## 🌿 Release in the MR\n\nThe version is known while the change is still a merge request, so the bump\nrides along with it. Do all edits **in the MR**:\n\n- **Reconcile the changelog first.** `[Unreleased]` is a **draft, not the source\n of truth**: diff the change against the last tag\n (`git log <last-tag>..HEAD`, `git diff --stat <last-tag>..HEAD`) and add an\n entry for every user-facing change that is missing. **Always show the proposed\n additions to the user and wait for approval** before promoting — ask rather\n than guess when an entry is ambiguous.\n\n- **Promote the changelog** in `CHANGELOG.md`: move the (now reconciled)\n `[Unreleased]` content under a new `## [x.y.z] - YYYY-MM-DD` heading, leave a\n fresh empty `[Unreleased]` on top, and add the compare links — naming the\n **version/tag**, never a commit id:\n\n ```markdown\n [Unreleased]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.z...HEAD\n [x.y.z]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.(z-1)...x.y.z\n ```\n\n- **Bump the version file** to the intended release version: `version = \"x.y.z\"`\n in `pyproject.toml` (packages) or the `VERSION` file (services).\n\n---\n\n## 🏷️ Tag the merged commit\n\n1. **Merge or squash** the MR into `main`.\n2. **Create the tag** on the resulting commit: `git tag x.y.z` (lightweight).\n3. **Push the tag** (`git push origin x.y.z`) — Drone builds the release.\n\n### 🤖 Agent-assisted tagging\n\nThe agent does not author or merge the MR and never tags before the merge. After\nyou confirm the MR is merged/squashed and tell it to proceed, the agent:\n\n1. Waits for that explicit go-ahead.\n2. Finds the commit that introduced the bump:\n - package:\n `git log -S 'version = \"x.y.z\"' -n 1 --format='%H %s' -- pyproject.toml`\n - service:\n `git log -S 'x.y.z' -n 1 --format='%H %s' -- VERSION`\n3. Verifies that commit's version file equals `x.y.z`, then tags **only** that\n commit: `git tag x.y.z <sha>`.\n4. Does **not** push — it asks you to run `git push origin x.y.z`.\n\n---\n\n## 🤖 What Drone does\n\n- **Type A (package):** asserts `tag == pyproject version`, then skips if that\n version already exists on the index, runs the quality gate, builds, and\n uploads the wheel + sdist.\n- **Type B (service):** asserts `tag == VERSION`, resolves the tag, skips if the\n image already exists, runs the quality gate, builds, pushes, and restarts the\n container.\n\nIf the guard fails, nothing is published — fix the version file, amend the\ncommit, and move the tag **only** if it was never pushed.\n\n---\n\n## 🛟 Backfill / recovery\n\nIf an older tag never published (a past version-file mismatch, an index\noutage):\n\n- **Package:** run the matching `backfill-*-from-tags` pipeline (custom event) —\n `backfill-pypi-from-tags` for Python, `backfill-npm-from-tags` for npm. It\n checks out every tag, verifies `tag ==` the version file at that commit, and\n uploads the ones missing from the index. Already-fixed history is never\n rewritten.\n- **Service:** rebuild the image for the missing tag with a fresh commit and a\n new patch version; do not move a published tag.\n\n---\n\n## 🔁 If a release is wrong\n\n- Never reuse or force-move a pushed tag.\n- Fix forward: bump to the next patch, promote the changelog, tag again.\n\n---\n\n## Related\n\n- [Testing & Quality Gates](01_testing.md) · [Versioning & Changelog](../lib/91_versioning_and_changelog/) · [Drone Pipelines](../lib/90_drone_pipelines/)\n",
"objects": {}
}
}

View File

@@ -38,7 +38,7 @@
{
"name": "doc-weaver",
"type": "package",
"path": "doc-weaver",
"path": "react/doc-weaver",
"version_source": "package.json",
"drone": "npm-lib.yaml",
"trigger": "tag",

View File

@@ -87,3 +87,70 @@ steps:
trigger:
event:
- tag
---
kind: pipeline
type: docker
name: backfill-npm-from-tags
platform:
os: linux
arch: arm64
workspace:
path: /drone/src
steps:
- name: fetch-tags
image: alpine/git
commands:
- git fetch --tags --force
- name: build-and-publish-missing
image: node:22-alpine
environment:
NPM_REPO_URL:
from_secret: NPM_REPO_URL
NPM_USERNAME:
from_secret: NPM_USERNAME
NPM_PASSWORD:
from_secret: NPM_PASSWORD
commands:
- apk add --no-cache git
- echo "registry=$NPM_REPO_URL" > /tmp/.npmrc
- echo "_auth=$(echo -n "$NPM_USERNAME:$NPM_PASSWORD" | base64 | tr -d '\n')" >> /tmp/.npmrc
- |
set -e
PACKAGE_NAME=$(node -p "require('./package.json').name")
echo "Package: $PACKAGE_NAME"
for TAG in $(git tag --sort=version:refname); do
VERSION="$TAG"
echo "Version: $VERSION"
if npm view "$PACKAGE_NAME@$VERSION" version --userconfig /tmp/.npmrc >/dev/null 2>&1; then
echo "Exists, skipping"
continue
fi
git checkout --force "$TAG"
TAG_VERSION=$(node -p "require('./package.json').version")
if [ "$TAG" != "$TAG_VERSION" ]; then
echo "Tag $TAG != package.json version $TAG_VERSION at that commit; skipping"
continue
fi
echo "Building $VERSION"
rm -rf dist
npm install
npm run build
echo "Publishing $VERSION"
npm publish --access restricted --userconfig /tmp/.npmrc
done
trigger:
event:
- custom

View File

@@ -52,17 +52,24 @@ Secrets: `PIP_REPO_URL`, `PIP_USERNAME`, `PIP_PASSWORD`.
## Type B — npm package (`npm-lib.yaml`)
For internal npm libraries (the React packages). One pipeline,
**`build-and-publish-npm`**, triggered on `tag`:
For internal npm libraries (the React packages). The file contains **two
pipelines**:
- `check-version` — **guard**: fail unless `$DRONE_TAG` equals the version in
`package.json`; query `$NPM_REPO_URL` for `name@version`; if it already
exists, exit `78` and skip the whole build.
- `quality-gate` — write `.npmrc` from the secrets, `npm install`, then
`npm run quality-gate`.
- `build-package` — `npm install && npm run build` into `dist/`.
- `publish-to-private-npm` — `npm publish --access restricted` to
`$NPM_REPO_URL`.
1. **`build-and-publish-npm`** — triggered on `tag`:
- `check-version` — **guard**: fail unless `$DRONE_TAG` equals the version in
`package.json`; query `$NPM_REPO_URL` for `name@version`; if it already
exists, exit `78` and skip the whole build.
- `quality-gate` — write `.npmrc` from the secrets, `npm install`, then
`npm run quality-gate`.
- `build-package` — `npm install && npm run build` into `dist/`.
- `publish-to-private-npm` — `npm publish --access restricted` to
`$NPM_REPO_URL`.
2. **`backfill-npm-from-tags`** — triggered on `custom`:
- `fetch-tags` — `git fetch --tags --force`.
- `build-and-publish-missing` — walk every tag in version order, skip the
ones already on the registry, `git checkout` the rest, verify
`tag == package.json version` at that commit, build, and publish. This is
the disaster-recovery / replay path.
Secrets: `NPM_REPO_URL`, `NPM_USERNAME`, `NPM_PASSWORD`. Auth is a generated
`.npmrc` (`registry=` + `_auth=` base64 of `user:pass`) written from secrets at
@@ -71,6 +78,15 @@ runtime — the committed `.npmrc` in a repo only points at the registry URL.
- **Ship as-is.** Name and version come from `package.json`, so the same file
is reused verbatim across every npm package.
> **Registry credentials are shared.** The private npm registry (Verdaccio at
> `npm.aetoskia.com`) authenticates against the **same `htpasswd` file** as the
> private PyPI index and the Docker registry (`/auth/htpasswd`), with
> registration disabled (`max_users: -1`). `NPM_USERNAME` / `NPM_PASSWORD` are
> therefore the **same credentials as `PIP_USERNAME` / `PIP_PASSWORD`** — no
> separate npm account exists. Verdaccio's htpasswd plugin accepts **bcrypt**
> hashes (`$2y$…`); non-bcrypt entries that pypiserver tolerates will not
> authenticate against npm. See [93 – Library Registry](93_library_registry.md).
---
## Type C — Docker deployment (`service-docker.yaml`)
@@ -121,8 +137,8 @@ Placeholders to fill in per repo:
`package.json` version; a service needs a root `VERSION` file. See
[91 – Versioning & Changelog](91_versioning_and_changelog.md).
- Do not rename the pipelines; tooling and runbooks key off
`build-and-publish-pypi`, `build-and-publish-npm`, and
`build-and-deploy-service`.
`build-and-publish-pypi`, `build-and-publish-npm`,
`build-and-deploy-service`, and their `backfill-*-from-tags` companions.
---

View File

@@ -53,6 +53,17 @@ endpoint (or, for the auth-server service, a spec pointer). It also serves the
pinned auth-server OpenAPI contract at `docs://spec`. Query it before writing
any infrastructure.
## Publishing & registry auth
Python packages publish to the private index (`pip.aetoskia.com`, `pypiserver`)
and npm packages to the private registry (`npm.aetoskia.com`, Verdaccio). Both
authenticate against the **same `htpasswd` file** (`/auth/htpasswd`) used by the
Docker registry and the Traefik basic-auth middleware; registration is disabled,
so the CI credentials (`PIP_USERNAME` / `PIP_PASSWORD`, `NPM_USERNAME` /
`NPM_PASSWORD`) are one and the same account — there is no separate npm user.
Verdaccio requires **bcrypt** htpasswd entries; see
[90 – Drone Pipelines](90_drone_pipelines.md) for the CI wiring.
## Notes
- **Hexa** is a full library with its own MCP server on `:8008`: read

View File

@@ -26,7 +26,7 @@
{
"name": "doc-weaver",
"type": "package",
"path": "doc-weaver",
"path": "react/doc-weaver",
"version_source": "package.json",
"drone": "npm-lib.yaml",
"trigger": "tag",

View File

@@ -87,3 +87,70 @@ steps:
trigger:
event:
- tag
---
kind: pipeline
type: docker
name: backfill-npm-from-tags
platform:
os: linux
arch: arm64
workspace:
path: /drone/src
steps:
- name: fetch-tags
image: alpine/git
commands:
- git fetch --tags --force
- name: build-and-publish-missing
image: node:22-alpine
environment:
NPM_REPO_URL:
from_secret: NPM_REPO_URL
NPM_USERNAME:
from_secret: NPM_USERNAME
NPM_PASSWORD:
from_secret: NPM_PASSWORD
commands:
- apk add --no-cache git
- echo "registry=$NPM_REPO_URL" > /tmp/.npmrc
- echo "_auth=$(echo -n "$NPM_USERNAME:$NPM_PASSWORD" | base64 | tr -d '\n')" >> /tmp/.npmrc
- |
set -e
PACKAGE_NAME=$(node -p "require('./package.json').name")
echo "Package: $PACKAGE_NAME"
for TAG in $(git tag --sort=version:refname); do
VERSION="$TAG"
echo "Version: $VERSION"
if npm view "$PACKAGE_NAME@$VERSION" version --userconfig /tmp/.npmrc >/dev/null 2>&1; then
echo "Exists, skipping"
continue
fi
git checkout --force "$TAG"
TAG_VERSION=$(node -p "require('./package.json').version")
if [ "$TAG" != "$TAG_VERSION" ]; then
echo "Tag $TAG != package.json version $TAG_VERSION at that commit; skipping"
continue
fi
echo "Building $VERSION"
rm -rf dist
npm install
npm run build
echo "Publishing $VERSION"
npm publish --access restricted --userconfig /tmp/.npmrc
done
trigger:
event:
- custom

View File

@@ -104,9 +104,10 @@ commit, and move the tag **only** if it was never pushed.
If an older tag never published (a past version-file mismatch, an index
outage):
- **Package:** run the `backfill-pypi-from-tags` pipeline (custom event). It
checks out every tag, verifies `tag == pyproject version` at that commit,
and uploads the ones missing from the index. Already-fixed history is never
- **Package:** run the matching `backfill-*-from-tags` pipeline (custom event) —
`backfill-pypi-from-tags` for Python, `backfill-npm-from-tags` for npm. It
checks out every tag, verifies `tag ==` the version file at that commit, and
uploads the ones missing from the index. Already-fixed history is never
rewritten.
- **Service:** rebuild the image for the missing tag with a fresh commit and a
new patch version; do not move a published tag.

View File

@@ -1011,9 +1011,10 @@ commit, and move the tag <strong>only</strong> if it was never pushed.</p>
<p>If an older tag never published (a past version-file mismatch, an index
outage):</p>
<ul>
<li><strong>Package:</strong> run the <code>backfill-pypi-from-tags</code> pipeline (custom event). It
checks out every tag, verifies <code>tag == pyproject version</code> at that commit,
and uploads the ones missing from the index. Already-fixed history is never
<li><strong>Package:</strong> run the matching <code>backfill-*-from-tags</code> pipeline (custom event) —
<code>backfill-pypi-from-tags</code> for Python, <code>backfill-npm-from-tags</code> for npm. It
checks out every tag, verifies <code>tag ==</code> the version file at that commit, and
uploads the ones missing from the index. Already-fixed history is never
rewritten.</li>
<li><strong>Service:</strong> rebuild the image for the missing tag with a fresh commit and a
new patch version; do not move a published tag.</li>

File diff suppressed because one or more lines are too long