23 KiB
Gitea npm Package Registry Migration
Goal: Replace the
docker-prep.sh+ tarball workflow with Gitea's built-in npm package registry for@bytelyst/*shared packages.Phase policy: use this Mac as the future single VM and validate the entire flow locally first. No Azure rollout and no publishing to any external or corporate registry in this phase.
1. Decision
We should adopt the local Gitea npm registry as the long-term package-distribution path for @bytelyst/* packages.
We should not switch directly in Azure first.
Instead, we should validate the exact end-to-end flow locally on this Mac:
- local Gitea git hosting
- local Gitea Actions
- local Gitea npm package registry
- local Docker / Docker Compose builds
- local product repos consuming
@bytelyst/*from local Gitea
Once that is stable, we replicate the same validated shape on the Azure single VM.
2. Why We Want This
Current pain
Today many repos still depend on one of these Docker-time workarounds:
file:references to siblinglearning_ai_common_plat/packages/*docker-prep.sh.docker-depsor older.tarballscopy steps- temporary
package.jsonrewriting before builds
This works, but it creates repeated operational drag:
- every build may need prep
- Dockerfiles become repo-specific and fragile
- CI, local development, and Docker are not using one clean dependency source
- path and build-context mismatches keep surfacing during ecosystem validation
Target outcome
We want one dependency source for shared packages:
- local development
- local CI via Gitea Actions
- local Docker builds
- later Azure single-VM builds
That dependency source should be the local Gitea npm registry.
3. Local-First Target Architecture
3.1 This Mac as the rehearsal VM
This Mac
├── local Gitea (git + actions + npm registry)
├── local Docker / Docker Compose
├── learning_ai_common_plat
└── product repos
Flow:
learning_ai_common_plat/packages/*
↓ build locally
↓ publish to local Gitea npm registry
local Gitea npm registry (localhost:3300)
↓ install via semver refs
product repos / Docker builds / local Gitea CI
3.2 Future Azure single VM
After the local rehearsal is proven, Azure should mirror the same shape:
Azure VM
├── Gitea
├── Gitea Actions runner
├── Gitea npm registry
├── Docker / Compose
└── same repo + build flow
The Azure plan should reuse the validated local approach, not invent a second design.
4. What Is In Scope Right Now
Included
- validate local Gitea package-registry access
- validate local Gitea Actions availability
- define local-only package publish flow to local Gitea
- define local-only consumer install flow from local Gitea
- define local-only Docker build flow from local Gitea
- define the later Azure single-VM replication steps
Excluded
- Azure changes right now
- external npm registry publishing
- corporate registry publishing
- non-local deployment actions
- K3s / Helm work
5. Current Local Readiness Snapshot
Validated locally on this Mac:
- local Gitea health endpoint responds
- local Gitea version endpoint responds
- local
bytelystuser auth works - local Gitea Actions workflow exists for
learning_ai_common_plat - local Gitea package registry endpoint is reachable
- local pilot packages have been published to local Gitea successfully
- a clean scratch consumer install from local Gitea works on the host
That is enough to treat this Mac as the single-VM rehearsal environment.
5.1 Validation Results So Far
Proven locally on this Mac
- shared packages build cleanly in
learning_ai_common_plat - a local-only Gitea package token was created and used successfully for local package publishing
- 48
@bytelyst/*packages published to local Gitea npm registry - a clean scratch
pnpm installfrom the local Gitea registry works on all migrated repos - all 10 product repos migrated from
file:refs +docker-prep.shto semver^0.1.0+ registry-backed Dockerfiles - all migrated repos pass host-side
pnpm installand backend typecheck - Docker builds (backend + web) verified for all repos with Dockerfiles
- local Gitea CI green for 8/8 repos with CI workflows (FlowMonk, NoteLett, ActionTrail, LocalMemGPT, NomGap, ChronoMind, JarvisJr, PeakPulse)
docker-prep.shremoved from all 10 reposGITEA_NPM_TOKENadded to act_runner config for CI registry access- stale "Build @bytelyst/* packages" step removed from all CI workflows
Important implementation finding
Publishing @bytelyst/* workspace packages with raw npm publish is not sufficient for this migration.
Why:
- packages with internal
workspace:*dependencies can be published with unresolved workspace specifiers - those published artifacts break downstream consumers
What worked:
pnpm packproduces normalized package metadata for workspace dependencies- publishing the normalized tarball to Gitea works
This is now codified in the local-only helper script:
scripts/publish-local-gitea-packages.sh
That script is currently the authoritative local rehearsal path for publishing @bytelyst/* packages to local Gitea.
No remaining blockers
Host-side installs, Docker builds, and local CI are all validated.
Network topology determines the Docker recipe
| Topology | NETWORK= |
Gitea host | Docker reaches Gitea via | --add-host needed? |
|---|---|---|---|---|
| Local dev (Mac) | corp |
localhost:3300 |
host.docker.internal + --add-host localhost:host-gateway |
Yes — expected |
| VM deployment | (TBD) | <vm-host>:3300 or compose service gitea:3300 |
Native Docker networking | No — ROOT_URL resolves natively |
The --add-host localhost:host-gateway workaround is the correct local-dev pattern when NETWORK=corp, not a generic blocker. On a VM, Gitea's ROOT_URL will be set to the real hostname or compose service name, so tarball URLs resolve without any host-mapping workaround.
At the time of writing (2026-03-24):
- host-side registry usage is validated across all 10 migrated repos
- Docker builds verified for all repos with Dockerfiles
- local Gitea CI green for all 8 repos with CI workflows
- 48
@bytelyst/*packages published to local Gitea docker-prep.shretired from all repos
Verified local FlowMonk Docker recipe (NETWORK=corp):
export TOKEN='<local-gitea-package-token>'
docker build \
--add-host localhost:host-gateway \
--build-arg GITEA_NPM_HOST=host.docker.internal \
--secret id=gitea_npm_token,env=TOKEN \
-f backend/Dockerfile /Users/sd9235/code/mygh/learning_ai_flowmonk
docker build \
--add-host localhost:host-gateway \
--build-arg GITEA_NPM_HOST=host.docker.internal \
--secret id=gitea_npm_token,env=TOKEN \
-f web/Dockerfile /Users/sd9235/code/mygh/learning_ai_flowmonk
On the target VM, the recipe simplifies to just --build-arg GITEA_NPM_HOST=<gitea-host> plus the BuildKit secret — no --add-host needed.
5.2 Remaining Gaps From Local Mac Validation
The local rehearsal on this Mac has validated the host-side registry model, Docker builds (backend + web), and local Gitea CI across 8 product repos.
Validated ✅
- Host-side
pnpm installagainst local Gitea registry (all 10 repos) - Backend + web Docker builds with BuildKit secret +
--add-host(all repos with Dockerfiles) - Local Gitea CI green for all 8 repos with CI workflows
- Runner re-registered against
127.0.0.1(avoids IPv6[::1]declaration failure) GITEA_NPM_TOKENadded to act_runner config for CI registry access- 48
@bytelyst/*packages published and consumed successfully - Backend typecheck passes for all 10 migrated repos
docker-prep.shremoved from all 10 repos
Gaps closed
2 repos not yet migrated→ MindLyst and LysnrAI migrated (2026-03-24)Gitea CI queue stuck→ Runner restarted withGITEA_NPM_TOKEN, all 8 CI repos greenStale common-plat build step in CI→ Removed from all 7 CI workflows
Remaining minor items
- Mobile workspace members remain on
file:refs where present (NomGap, NoteLett) — acceptable for now - Local Gitea Actions has not yet been validated for the package build → publish → consumer flow (CI runs typecheck/test, not publish)
- MindLyst and LysnrAI have no Gitea CI workflows yet (lower priority)
Why this matters for Azure
Azure should be a replication step, not a redesign step.
On the VM, Gitea's ROOT_URL will point to the real hostname, so Docker containers will resolve tarball URLs natively — the --add-host workaround is local-dev only. The Azure VM rollout should wait until the remaining local gaps are cleared for:
- package metadata correctness
- Docker consumer correctness across all product repos (not just FlowMonk pilot)
- local CI correctness for the full build → publish → consume loop
If we skip those validations locally, the first Azure VM trial becomes a debugging environment instead of a deployment environment.
Concrete local-exit criteria before Azure
Before starting Azure VM rollout, we should be able to demonstrate all of the following on this Mac:
- one Gitea deployment shape whose package URLs work for both host installs and Docker builds
- one publish path for
@bytelyst/*packages that works repeatably withpnpm pack - one pilot repo that installs from the registry on the host and inside Docker without fallback tarballs
- one local Gitea Actions path that can build/publish/install with the same registry assumptions
- one documented rollback path that cleanly returns a pilot repo to tarball-based Docker consumption if needed
6. Migration Strategy
Stage A — Local registry rehearsal
Validate the package-registry path locally before changing every repo.
Required outcomes:
- local package token exists
- a small pilot set of
@bytelyst/*packages can be published to local Gitea - a local consumer can install them via semver refs
- a Docker build can install them without
docker-prep.sh - local Gitea Actions can run the same package build/publish path
Stage B — Pilot repo migration
Migrate one repo first.
Recommended pilot:
learning_ai_flowmonk
Reason:
- recent Docker/build work already exposed its edge cases
- both backend and web surfaces exist
- it is a strong canary for dependency-distribution changes
Stage C — Sequential ecosystem rollout
After the pilot is stable, migrate remaining repos sequentially.
7. Local Implementation Plan
7.1 Verify package metadata in learning_ai_common_plat
Every packages/*/package.json should have:
- a valid
name - a valid
version - a valid
files/exportssetup - a successful local
build
Local validation command:
cd /Users/sd9235/code/mygh/learning_ai_common_plat
pnpm -r --filter './packages/*' build
7.2 Create a local-only Gitea package token
Use a token intended only for this Mac rehearsal.
Required scopes:
- package read
- package write
Do not use Azure or external registry credentials here.
7.3 Publish only to local Gitea
In this phase, all npm publish activity must point only to:
http://localhost:3300/api/packages/bytelyst/npm/
No Azure Artifacts. No npmjs.org. No external corporate npm registry.
Use the local-only helper script for this rehearsal:
cd /Users/sd9235/code/mygh/learning_ai_common_plat
GITEA_NPM_TOKEN='<local-token>' bash ./scripts/publish-local-gitea-packages.sh
For a single package:
cd /Users/sd9235/code/mygh/learning_ai_common_plat
GITEA_NPM_TOKEN='<local-token>' bash ./scripts/publish-local-gitea-packages.sh '@bytelyst/errors'
7.4 Start with a minimal package pilot set
Publish a small set first:
@bytelyst/errors@bytelyst/config@bytelyst/api-client
Then expand once the local install path is proven.
7.5 Consumer dependency model after migration
Consumers move from local file refs:
"@bytelyst/auth": "file:../../learning_ai_common_plat/packages/auth"
to semver refs:
"@bytelyst/auth": "^0.1.0"
And resolve via scoped .npmrc or .pnpmrc config pointing @bytelyst to local Gitea.
7.6 Docker model after migration
After a repo is migrated to the registry model, its Dockerfiles should:
- stop copying
.docker-depsor.tarballs - stop depending on
docker-prep.sh - install from local Gitea through scoped registry config
That is the main operational simplification we want.
8. Local Validation Sequence
Step 1 — Validate local Gitea surfaces
Confirm locally:
- Gitea UI is reachable
- package registry API is reachable with auth
- Actions workflow is visible and active
Step 2 — Validate package build output
Run local builds for the shared packages and confirm pack/publish inputs are clean.
Step 3 — Publish a minimal package pilot set to local Gitea only
Success means:
- packages appear in local Gitea package list
- metadata looks correct
- no external registry interaction occurred
Step 4 — Validate local install from Gitea
Use a scratch consumer or a pilot repo surface and verify:
pnpm installresolves@bytelyst/*from local Gitea- lockfile updates are clean
- no fallback to tarballs is required for the tested packages
Step 5 — Validate Docker build from local Gitea
For the pilot repo:
- remove the tested surface's dependency on
docker-prep.sh - add scoped registry config for Docker
- build backend and web without tarball prep
Success means:
- image build works without
.docker-depsor.tarballs - build does not require local package rewrite logic
Current status on this Mac:
- not yet fully green
- the remaining issue is the local Homebrew Gitea instance serving package metadata/tarball URLs across the Docker build boundary
- host-side validation is complete, Docker-side validation is still open
Step 6 — Validate local Gitea Actions path
Add or adapt a local-only workflow in learning_ai_common_plat that:
- builds packages
- optionally publishes to local Gitea npm registry
- never targets an external registry
Step 7 — Validate full local E2E
At the end of the local rehearsal we should be able to say:
- shared packages build locally
- shared packages publish to local Gitea registry
- a pilot consumer installs from local Gitea
- a pilot Docker build succeeds from local Gitea
- local Gitea Actions can drive the same path
Only then should we expand repo-by-repo.
9. Recommended Rollout Order
All 10 repos migrated ✅
- ✅
learning_ai_flowmonk— pilot repo, CI green - ✅
learning_ai_notes— CI green - ✅
learning_ai_trails— CI green - ✅
learning_ai_local_memory_gpt— CI green - ✅
learning_ai_fastgap— CI green (fixed missing@expo/vector-icons) - ✅
learning_ai_clock— CI green - ✅
learning_ai_jarvis_jr— CI green - ✅
learning_ai_peakpulse— CI green (backend only, no web Dockerfile) - ✅
learning_multimodal_memory_agents— non-standard layout (KMP +mindlyst-native/web/), typecheck pass - ✅
learning_voice_ai_agent— non-standard layout (Python desktop +user-dashboard-web/), typecheck pass
10. CI Model For The Local Rehearsal
For the local rehearsal phase, Gitea Actions should do only local work:
- build packages
- test packages
- typecheck packages
- optionally publish to local Gitea only
This is intentionally different from a final enterprise rollout.
The objective is to prove the local VM pattern first.
11. Risks And Guardrails
Risks
- version drift between local source and published package versions
- peer dependency mismatches hidden by current tarball workflow
- Docker auth/config differences from shell installs
- accidental publishing to the wrong registry
- switching too many repos before the pilot is stable
Guardrails
- use local-only credentials for this phase
- keep
@bytelystscoped registry config explicit - do not remove tarball fallback globally until the pilot is green
- migrate one consumer repo at a time
- keep rollback steps documented per repo
12. Rollback Plan
The migration is complete for all 10 repos. If a repo needs to revert to the old file: + docker-prep.sh pattern:
Per-repo rollback steps
git revert <migration-commit>— revertspackage.json,Dockerfile,.npmrc,pnpm-workspace.yaml- Restore
scripts/docker-prep.shfrom the commit just beforechore: remove docker-prep.sh - Re-add the sibling workspace reference in
pnpm-workspace.yaml:packages: - ../learning_ai_common_plat/packages/* - Restore
file:refs inpackage.json— sed:sed -i '' 's|"\^0.1.0"|"file:../../learning_ai_common_plat/packages/PKG_NAME"|g' - Run
pnpm installto regenerate the lockfile - Verify
pnpm run typecheckandpnpm run testpass - Commit and push
Registry-side rollback
No registry-side action is needed. The Gitea packages remain published and don't interfere with file: refs. The .npmrc scoped registry config only activates when the @bytelyst scope resolves through the registry.
When rollback is NOT needed
- If a package is missing from the registry → publish it with
scripts/publish-local-gitea-packages.sh - If a transitive dep is missing → add it explicitly to
package.json(this surfaced for@expo/vector-iconsin NomGap)
13. Azure Single-VM Follow-Through
After the local rehearsal is green, Azure should follow the same validated recipe:
- provision one VM
- install Docker and Gitea
- enable Gitea Actions runner
- enable Gitea packages
- clone the same repos onto the VM
- apply the same local registry/token/package flow
- re-run the pilot repo first
- then expand sequentially across the ecosystem
Azure should be a replication step, not a redesign step.
What Azure still needs beyond current local proof
Even though the codebase is already designed to be highly configurable, Azure VM rollout still requires a few validations that have not been fully cleared by the local Mac rehearsal yet:
- Gitea running in a deployment shape where package tarball URLs are stable for both host consumers and containerized consumers
- registry-backed Docker builds for the pilot repo without
docker-prep.sh - local Gitea Actions or equivalent host-runner proof for package build/publish/install
- a final decision on whether mobile-facing
@bytelyst/*packages stay on localfile:links during the pilot phase or join the registry migration in a later wave - one Azure-ready secrets model for Gitea token handling, service envs, and registry auth that maps cleanly from local env vars to VM secrets/config files
What should require minimal change when moving to Azure
The good news is that most of the ecosystem has already been implemented in a way that keeps scale-up mostly configuration-driven once the registry and image flows are proven.
Expected low-change areas:
- service code already externalizes most environment-specific values via env/config
- Compose and K8s models already map cleanly to Deployments, Services, Ingress, ConfigMaps, and Secrets
- Traefik, readiness probes, service ports, and namespace separation are already documented in a K8s-friendly way
- scaling stateless services should mostly mean changing replica counts, resource requests/limits, and HPA settings
- moving from single-node K3s to multi-node K3s or managed Kubernetes should mostly reuse the same manifests with infra-level adjustments
What is not yet proven enough to call low-change:
- the package-distribution layer for Docker/K8s image builds
- the exact image build/publish flow for the full ecosystem after registry migration
- the complete repo-by-repo removal of tarball-based Docker prep
14. Definition Of Done
This migration plan is locally validated only when all are true:
- local Gitea package publish auth verified
- local package publish path verified — 48 packages published
- local consumer install path verified on the host — all 10 repos
- Docker build path verified without
docker-prep.sh— all repos with Dockerfiles - local Gitea CI verified — 8/8 repos with CI workflows green
- pilot repo migrated successfully end-to-end including Docker (FlowMonk)
- 7 standard repos migrated and verified (NoteLett, ActionTrail, LocalMemGPT, NomGap, ChronoMind, JarvisJr, PeakPulse)
- remaining 2 non-standard repos migrated (MindLyst, LysnrAI)
- rollback path documented (§12)
- Azure single-VM reproduction steps documented (§13)
15. Migration Complete — Summary
create or verify a local-only Gitea package token✅publish✅ (48 packages)@bytelyst/*packages to local Giteamigrate pilot repo (FlowMonk) end-to-end✅expand to remaining standard-layout repos✅ (7 repos)confirm Gitea CI green for all repos✅ (8/8 with CI workflows)migrate remaining 2 non-standard repos (MindLyst, LysnrAI)✅document rollback path✅ (§12)document Azure single-VM reproduction steps✅ (§13)remove✅docker-prep.shfrom all 10 reposadd✅GITEA_NPM_TOKENto act_runner configremove stale common-plat build step from CI workflows✅
Migration completed 2026-03-24. All 10 product repos now consume @bytelyst/* packages from the local Gitea npm registry.
16. Post-Migration Audit (2026-03-24)
After the initial migration was marked complete, a systematic audit was run across all 10 repos. 5 bugs/gaps were found and fixed:
| # | Finding | Severity | Fix | Repo |
|---|---|---|---|---|
| 1 | FlowMonk mobile/package.json had 6 stale file: refs |
BUG | Converted to ^0.1.0 + regenerated lockfile |
learning_ai_flowmonk |
| 2 | MindLyst had 3 stale package-lock.json files (npm artifact, pnpm doesn't use them) |
BUG | Deleted all 3 | learning_multimodal_memory_agents |
| 3 | MindLyst missing .dockerignore |
GAP | Created with proper exclusions (node_modules, .next, dist, KMP build dirs) | learning_multimodal_memory_agents |
| 4 | MindLyst CI workflow had stale "Build @bytelyst/* packages" step + used npm instead of pnpm | BUG | Removed step, added pnpm install, switched to pnpm --filter |
learning_multimodal_memory_agents |
| 5 | LysnrAI CI workflow had stale "Build @bytelyst/* packages" step | BUG | Removed step, added pnpm install |
learning_voice_ai_agent |
Dead file sweep — confirmed clean
docker-prep.sh— removed from all 10 repos ✅.docker-deps/dirs — none tracked ✅*.tgztarballs — none tracked ✅package-lock.json— none tracked (all pnpm repos) ✅- jfrog refs in Dockerfiles — intentional for corp proxy, not dead ✅
Lesson learned
Mobile workspace members (FlowMonk, NomGap, NoteLett) that were previously insulated from the file: → registry change because their lockfiles still resolved transitively will break when the lockfile is regenerated. Always run GITEA_NPM_TOKEN=... pnpm install --no-frozen-lockfile after converting mobile package.json refs.