name: packages-worker-add-entrypoint description: > Scaffold a new sub-worker inside packages_worker (npm, deps.dev, osv, scorecard, etc.) following the single-service multi-entry-point structure. Use when: "add a new packages worker", "scaffold a sub-worker in packages_worker", "new worker for packages-db", "add npm worker", "add OSV worker", "add deps.dev worker". allowed-tools: Read, Write, Edit, Bash, AskUserQuestion, Glob
packages-worker — Add a New Sub-worker
You are adding a new data-ingestion worker to services/apps/packages_worker/.
The structure follows the same pattern as backend/ (where api.ts and
job-generator.ts share one Dockerfile): one npm package, one Docker image,
each worker in its own src/{worker}/ directory with its own entry point.
services/apps/packages_worker/
src/
bin/
github-repos-enricher.ts ← existing worker
<name>.ts ← entry point you will create
github/ ← existing worker logic
<worker>/ ← directory you will create
index.ts ← main logic for this worker
types.ts
config.ts ← shared — add your config getter here
db.ts ← shared — do not modify
Step 1 — Gather requirements
Ask the engineer for:
- Worker name (kebab-case) — e.g.
npm-sync,osv-sync,scorecard-runner. Used as the entry point filename (src/bin/<name>.ts) and docker-compose service name. - Worker directory name (short, lowercase) — e.g.
npm,osv,scorecard. Becomessrc/<worker>/. - What it does — what data it fetches/writes, what table(s) in packages-db it reads from and writes to.
- External API or data source (if any) — URL, auth method, rate-limit characteristics.
- Required env vars beyond the shared DB vars — e.g.
NPM_API_URL,OSV_API_KEY.
Do not proceed until you have answers to 1–3.
Step 2 — Read existing files first
cat services/apps/packages_worker/src/bin/github-repos-enricher.ts
cat services/apps/packages_worker/src/config.ts
cat services/apps/packages_worker/package.json
cat scripts/services/github-repos-enricher.yaml
These are the canonical references. Do not deviate from the patterns you see there.
Step 3 — Scaffold the files
3a. Worker directory — services/apps/packages_worker/src/<worker>/
Create the directory with at minimum:
types.ts — types specific to this worker (input/output shapes, error kinds if calling an external API).
index.ts — the main logic function(s) this worker runs. What goes here depends entirely on what the worker does — do not force a loop shape if it does not fit. Discuss with the engineer what the execution model should be (continuous loop, one-shot batch, event-driven, etc.) and implement accordingly.
Add any additional files the worker needs (e.g. an API client, a DB query helper). All DB access uses inline pg-promise SQL via qx.select / qx.result / qx.none — do not add files to services/libs/data-access-layer.
3b. Entry point — services/apps/packages_worker/src/bin/<name>.ts
Follow the structure of github-repos-enricher.ts:
- Import
getServiceLoggerfrom@crowd/logging - Import your worker's config getter from
../configandgetPackagesDbfrom../db - Import your worker's main function from
../<worker>/index - Set
liveFilePath/readyFilePathto../tmp/<name>-live.tmp/../tmp/<name>-ready.tmp - Handle SIGINT / SIGTERM with a
shuttingDownflag - In
main(): call config getter → validate any required tokens/keys →await getPackagesDb()→await qx.selectOne('SELECT 1')→fs.mkdirSyncfor the tmp dir →setIntervalwriting probe files every 5000ms → call your worker's main function →clearInterval→process.exit(0) - Fatal handler:
main().catch(err => { log.error({ err }, '<name> fatal error'); process.exit(1) })
3c. Config additions — services/apps/packages_worker/src/config.ts
Read the file first, then add a get<Worker>Config() function:
- Use
requireEnv(name)for string vars,requireEnvInt(name)for integers - No defaults, no
?? undefined— the process must refuse to start on missing config
3d. Docker-compose service — scripts/services/<name>.yaml
Copy scripts/services/github-repos-enricher.yaml and adapt:
- Service names:
<name>(prod) and<name>-dev(dev) command(prod):pnpm run start:<name>command(dev):pnpm run dev:<name>env_file: keep the same four files (backend/.env.dist.local,backend/.env.dist.composed,backend/.env.override.local,backend/.env.override.composed)environment: set any tuning var defaults inline (avoids requiring them in.env.override.localfor local dev)volumes(dev only): bind-mount./services/apps/packages_worker/srcplus everyservices/libs/*/srcdirectory (copy the full list from the enricher yaml for hot reload)
3e. package.json scripts — services/apps/packages_worker/package.json
Read the file first, then add:
"start:<name>": "tsx src/bin/<name>.ts",
"dev:<name>": "tsx watch src/bin/<name>.ts"
3f. Env var files — backend/.env.dist.local and backend/.env.dist.composed
Append new required vars with empty-string defaults (or sensible local values for non-secrets):
NEW_WORKER_API_KEY=
Step 4 — TypeScript check
cd services/apps/packages_worker && pnpm tsc --noEmit
Fix any errors before proceeding.
Checklist before committing
-
src/<worker>/directory created withtypes.tsandindex.ts -
src/bin/<name>.ts— probe files, SIGINT/SIGTERM handler, fail-fast config check,SELECT 1on startup -
config.ts— newget<Worker>Config()usingrequireEnv/requireEnvInt, no defaults -
scripts/services/<name>.yaml— prod + dev services with bind mounts -
package.json—start:<name>anddev:<name>scripts added -
backend/.env.dist.localand.env.dist.composed— new vars documented - No new files in
services/libs/data-access-layer(packages-db uses inline SQL) -
pnpm tsc --noEmitpasses
Use /preflight before opening a PR and /commit to sign off.