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