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

@@ -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.