create-rigel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/cli.js +122 -0
- package/package.json +47 -0
- package/templates/express/.claude/CLAUDE.md +114 -0
- package/templates/express/.claude/agents/arch-validator.md +85 -0
- package/templates/express/.claude/agents/db-optimizer.md +72 -0
- package/templates/express/.claude/agents/doc-gardener.md +67 -0
- package/templates/express/.claude/agents/garbage-collector.md +94 -0
- package/templates/express/.claude/agents/gate-checker.md +176 -0
- package/templates/express/.claude/agents/reviewer.md +84 -0
- package/templates/express/.claude/agents/security-auditor.md +82 -0
- package/templates/express/.claude/hooks/post-write.sh +93 -0
- package/templates/express/.claude/rules/api.md +149 -0
- package/templates/express/.claude/rules/architecture.md +78 -0
- package/templates/express/.claude/rules/database.md +104 -0
- package/templates/express/.claude/rules/jobs.md +81 -0
- package/templates/express/.claude/rules/security.md +92 -0
- package/templates/express/.claude/rules/testing.md +97 -0
- package/templates/express/.claude/settings.json +35 -0
- package/templates/express/.claude/skills/00-infra-setup/SKILL.md +479 -0
- package/templates/express/.claude/skills/01-write-roadmap/SKILL.md +72 -0
- package/templates/express/.claude/skills/02-write-spec/SKILL.md +119 -0
- package/templates/express/.claude/skills/03-write-plan/SKILL.md +124 -0
- package/templates/express/.claude/skills/04-build-layer/SKILL.md +216 -0
- package/templates/express/.claude/skills/05-validate-layer/SKILL.md +43 -0
- package/templates/express/.claude/skills/06-push-layer/SKILL.md +59 -0
- package/templates/express/.claude/skills/07-layer-check/SKILL.md +62 -0
- package/templates/express/.claude/skills/08-garbage-collect/SKILL.md +86 -0
- package/templates/express/.claude/skills/09-doc-garden/SKILL.md +64 -0
- package/templates/express/.claude/skills/10-db-optimize/SKILL.md +96 -0
- package/templates/express/.claude/skills/11-load-test/SKILL.md +77 -0
- package/templates/express/.dockerignore +43 -0
- package/templates/express/.env.example +42 -0
- package/templates/express/.github/CODEOWNERS +22 -0
- package/templates/express/.github/dependabot.yml +48 -0
- package/templates/express/.github/pull_request_template.md +58 -0
- package/templates/express/.github/workflows/ci.yml +278 -0
- package/templates/express/.github/workflows/load-test.yml +41 -0
- package/templates/express/.gitleaks.toml +18 -0
- package/templates/express/.lintstagedrc.json +3 -0
- package/templates/express/.nvmrc +1 -0
- package/templates/express/.prettierrc +8 -0
- package/templates/express/.sequelizerc +8 -0
- package/templates/express/AGENTS.md +94 -0
- package/templates/express/ARCHITECTURE.md +161 -0
- package/templates/express/Dockerfile +40 -0
- package/templates/express/Makefile +79 -0
- package/templates/express/QUICKSTART.md +131 -0
- package/templates/express/docker-compose.yml +114 -0
- package/templates/express/docs/PLANS.md +111 -0
- package/templates/express/docs/QUALITY_SCORE.md +66 -0
- package/templates/express/docs/design-docs/core-beliefs.md +89 -0
- package/templates/express/docs/design-docs/decisions/ADR-000-infrastructure.md +70 -0
- package/templates/express/docs/design-docs/decisions/ADR-001-observability.md +82 -0
- package/templates/express/docs/design-docs/decisions/index.md +23 -0
- package/templates/express/docs/design-docs/index.md +11 -0
- package/templates/express/docs/design-docs/observability.md +194 -0
- package/templates/express/docs/design-docs/team-workflow.md +64 -0
- package/templates/express/docs/exec-plans/active/.gitkeep +0 -0
- package/templates/express/docs/exec-plans/completed/.gitkeep +0 -0
- package/templates/express/docs/exec-plans/tech-debt-tracker.md +31 -0
- package/templates/express/docs/generated/.gitkeep +0 -0
- package/templates/express/docs/product-specs/ROADMAP.md +49 -0
- package/templates/express/docs/product-specs/draft/.gitkeep +0 -0
- package/templates/express/docs/product-specs/index.md +21 -0
- package/templates/express/docs/product-specs/ready/.gitkeep +0 -0
- package/templates/express/eslint.config.mjs +94 -0
- package/templates/express/gitignore +36 -0
- package/templates/express/infra/monitoring/alloy/config.alloy +59 -0
- package/templates/express/jest.config.ts +61 -0
- package/templates/express/package.json +45 -0
- package/templates/express/scripts/openapi.export.ts +77 -0
- package/templates/express/tests/architecture/isolation.test.ts +51 -0
- package/templates/express/tests/architecture/layers.test.ts +83 -0
- package/templates/express/tests/integration/health.test.ts +40 -0
- package/templates/express/tests/integration/isolation.test.template.ts +88 -0
- package/templates/express/tests/load/smoke.js +46 -0
- package/templates/express/tests/load/soak.js +45 -0
- package/templates/express/tests/load/stress.js +44 -0
- package/templates/express/tests/unit/utils/response.util.test.ts +53 -0
- package/templates/express/tsconfig.build.json +8 -0
- package/templates/express/tsconfig.json +20 -0
- package/templates/fastapi/.claude/CLAUDE.md +122 -0
- package/templates/fastapi/.claude/agents/arch-validator.md +64 -0
- package/templates/fastapi/.claude/agents/db-optimizer.md +59 -0
- package/templates/fastapi/.claude/agents/doc-gardener.md +55 -0
- package/templates/fastapi/.claude/agents/garbage-collector.md +74 -0
- package/templates/fastapi/.claude/agents/gate-checker.md +133 -0
- package/templates/fastapi/.claude/agents/reviewer.md +74 -0
- package/templates/fastapi/.claude/agents/security-auditor.md +69 -0
- package/templates/fastapi/.claude/hooks/post-write.sh +65 -0
- package/templates/fastapi/.claude/rules/api.md +119 -0
- package/templates/fastapi/.claude/rules/architecture.md +84 -0
- package/templates/fastapi/.claude/rules/database.md +153 -0
- package/templates/fastapi/.claude/rules/jobs.md +85 -0
- package/templates/fastapi/.claude/rules/security.md +102 -0
- package/templates/fastapi/.claude/rules/testing.md +207 -0
- package/templates/fastapi/.claude/settings.json +50 -0
- package/templates/fastapi/.claude/skills/00-infra-setup/SKILL.md +311 -0
- package/templates/fastapi/.claude/skills/01-write-roadmap/SKILL.md +61 -0
- package/templates/fastapi/.claude/skills/02-write-spec/SKILL.md +82 -0
- package/templates/fastapi/.claude/skills/03-write-plan/SKILL.md +83 -0
- package/templates/fastapi/.claude/skills/04-build-layer/SKILL.md +170 -0
- package/templates/fastapi/.claude/skills/05-validate-layer/SKILL.md +20 -0
- package/templates/fastapi/.claude/skills/06-push-layer/SKILL.md +31 -0
- package/templates/fastapi/.claude/skills/07-layer-check/SKILL.md +51 -0
- package/templates/fastapi/.claude/skills/08-garbage-collect/SKILL.md +47 -0
- package/templates/fastapi/.claude/skills/09-doc-garden/SKILL.md +9 -0
- package/templates/fastapi/.claude/skills/10-db-optimize/SKILL.md +53 -0
- package/templates/fastapi/.claude/skills/11-load-test/SKILL.md +81 -0
- package/templates/fastapi/.dockerignore +30 -0
- package/templates/fastapi/.github/dependabot.yml +24 -0
- package/templates/fastapi/AGENTS.md +87 -0
- package/templates/fastapi/ARCHITECTURE.md +141 -0
- package/templates/fastapi/Makefile +42 -0
- package/templates/fastapi/README.md +179 -0
- package/templates/fastapi/docs/PLANS.md +26 -0
- package/templates/fastapi/docs/QUALITY_SCORE.md +26 -0
- package/templates/fastapi/docs/design-docs/core-beliefs.md +46 -0
- package/templates/fastapi/docs/design-docs/decisions/ADR-000-infrastructure.md +79 -0
- package/templates/fastapi/docs/design-docs/decisions/index.md +7 -0
- package/templates/fastapi/docs/design-docs/index.md +9 -0
- package/templates/fastapi/docs/exec-plans/active/.gitkeep +0 -0
- package/templates/fastapi/docs/exec-plans/completed/.gitkeep +0 -0
- package/templates/fastapi/docs/exec-plans/tech-debt-tracker.md +19 -0
- package/templates/fastapi/docs/generated/.gitkeep +0 -0
- package/templates/fastapi/docs/product-specs/ROADMAP.md +49 -0
- package/templates/fastapi/docs/product-specs/draft/.gitkeep +0 -0
- package/templates/fastapi/docs/product-specs/index.md +18 -0
- package/templates/fastapi/docs/product-specs/ready/.gitkeep +0 -0
- package/templates/fastapi/gitignore +46 -0
- package/templates/fastapi/scripts/gate.sh +68 -0
- package/templates/nestjs/.claude/CLAUDE.md +121 -0
- package/templates/nestjs/.claude/agents/db-optimizer.md +51 -0
- package/templates/nestjs/.claude/agents/doc-gardener.md +45 -0
- package/templates/nestjs/.claude/agents/garbage-collector.md +50 -0
- package/templates/nestjs/.claude/agents/gate-checker.md +127 -0
- package/templates/nestjs/.claude/agents/reviewer.md +68 -0
- package/templates/nestjs/.claude/agents/security-auditor.md +72 -0
- package/templates/nestjs/.claude/hooks/post-write.sh +91 -0
- package/templates/nestjs/.claude/rules/api.md +87 -0
- package/templates/nestjs/.claude/rules/architecture.md +104 -0
- package/templates/nestjs/.claude/rules/database.md +111 -0
- package/templates/nestjs/.claude/rules/security.md +110 -0
- package/templates/nestjs/.claude/rules/testing.md +145 -0
- package/templates/nestjs/.claude/settings.json +33 -0
- package/templates/nestjs/.claude/skills/00-infra-setup/SKILL.md +222 -0
- package/templates/nestjs/.claude/skills/01-write-spec/SKILL.md +25 -0
- package/templates/nestjs/.claude/skills/02-write-plan/SKILL.md +18 -0
- package/templates/nestjs/.claude/skills/03-build-layer/SKILL.md +242 -0
- package/templates/nestjs/.claude/skills/04-validate-layer/SKILL.md +4 -0
- package/templates/nestjs/.claude/skills/05-push-layer/SKILL.md +6 -0
- package/templates/nestjs/.claude/skills/06-layer-check/SKILL.md +12 -0
- package/templates/nestjs/.claude/skills/07-garbage-collect/SKILL.md +2 -0
- package/templates/nestjs/.claude/skills/08-doc-garden/SKILL.md +4 -0
- package/templates/nestjs/.claude/skills/09-api-sync/SKILL.md +36 -0
- package/templates/nestjs/.claude/skills/10-load-test/SKILL.md +29 -0
- package/templates/nestjs/.env.example +11 -0
- package/templates/nestjs/AGENTS.md +88 -0
- package/templates/nestjs/ARCHITECTURE.md +192 -0
- package/templates/nestjs/docs/PLANS.md +22 -0
- package/templates/nestjs/docs/QUALITY_SCORE.md +27 -0
- package/templates/nestjs/docs/design-docs/core-beliefs.md +18 -0
- package/templates/nestjs/docs/design-docs/decisions/ADR-000-infrastructure.md +58 -0
- package/templates/nestjs/docs/design-docs/decisions/index.md +7 -0
- package/templates/nestjs/docs/design-docs/index.md +7 -0
- package/templates/nestjs/docs/exec-plans/active/.gitkeep +0 -0
- package/templates/nestjs/docs/exec-plans/completed/.gitkeep +0 -0
- package/templates/nestjs/docs/exec-plans/tech-debt-tracker.md +16 -0
- package/templates/nestjs/docs/generated/.gitkeep +0 -0
- package/templates/nestjs/docs/product-specs/draft/.gitkeep +0 -0
- package/templates/nestjs/docs/product-specs/index.md +7 -0
- package/templates/nestjs/docs/product-specs/ready/.gitkeep +0 -0
- package/templates/nestjs/gitignore +8 -0
- package/templates/nextjs/.claude/CLAUDE.md +139 -0
- package/templates/nextjs/.claude/agents/arch-validator.md +66 -0
- package/templates/nextjs/.claude/agents/contract-checker.md +78 -0
- package/templates/nextjs/.claude/agents/doc-gardener.md +52 -0
- package/templates/nextjs/.claude/agents/garbage-collector.md +61 -0
- package/templates/nextjs/.claude/agents/gate-checker.md +129 -0
- package/templates/nextjs/.claude/agents/perf-auditor.md +109 -0
- package/templates/nextjs/.claude/agents/reviewer.md +79 -0
- package/templates/nextjs/.claude/agents/security-auditor.md +50 -0
- package/templates/nextjs/.claude/hooks/post-write.sh +121 -0
- package/templates/nextjs/.claude/rules/api-contract.md +166 -0
- package/templates/nextjs/.claude/rules/architecture.md +99 -0
- package/templates/nextjs/.claude/rules/components.md +122 -0
- package/templates/nextjs/.claude/rules/security.md +122 -0
- package/templates/nextjs/.claude/rules/testing.md +187 -0
- package/templates/nextjs/.claude/scripts/infra-setup.sh +550 -0
- package/templates/nextjs/.claude/settings.json +33 -0
- package/templates/nextjs/.claude/skills/00-infra-setup/SKILL.md +471 -0
- package/templates/nextjs/.claude/skills/01-write-roadmap/SKILL.md +77 -0
- package/templates/nextjs/.claude/skills/02-write-spec/SKILL.md +78 -0
- package/templates/nextjs/.claude/skills/03-write-plan/SKILL.md +91 -0
- package/templates/nextjs/.claude/skills/04-build-layer/SKILL.md +141 -0
- package/templates/nextjs/.claude/skills/05-validate-layer/SKILL.md +13 -0
- package/templates/nextjs/.claude/skills/06-push-layer/SKILL.md +20 -0
- package/templates/nextjs/.claude/skills/07-layer-check/SKILL.md +28 -0
- package/templates/nextjs/.claude/skills/08-garbage-collect/SKILL.md +14 -0
- package/templates/nextjs/.claude/skills/09-doc-garden/SKILL.md +12 -0
- package/templates/nextjs/.claude/skills/10-api-sync/SKILL.md +50 -0
- package/templates/nextjs/.claude/skills/11-perf-budget/SKILL.md +51 -0
- package/templates/nextjs/.dockerignore +25 -0
- package/templates/nextjs/.gitattributes +10 -0
- package/templates/nextjs/.github/CODEOWNERS +24 -0
- package/templates/nextjs/.github/dependabot.yml +31 -0
- package/templates/nextjs/.github/pull_request_template.md +59 -0
- package/templates/nextjs/.github/workflows/ci.yml +143 -0
- package/templates/nextjs/.github/workflows/lighthouse.yml +54 -0
- package/templates/nextjs/.github/workflows/load-test.yml +54 -0
- package/templates/nextjs/.husky/pre-commit +1 -0
- package/templates/nextjs/.lintstagedrc.json +4 -0
- package/templates/nextjs/.mcp.json +8 -0
- package/templates/nextjs/.prettierignore +13 -0
- package/templates/nextjs/.prettierrc +9 -0
- package/templates/nextjs/AGENTS.md +88 -0
- package/templates/nextjs/ARCHITECTURE.md +165 -0
- package/templates/nextjs/Dockerfile +38 -0
- package/templates/nextjs/Makefile +50 -0
- package/templates/nextjs/QUICKSTART.md +128 -0
- package/templates/nextjs/docs/PLANS.md +23 -0
- package/templates/nextjs/docs/QUALITY_SCORE.md +27 -0
- package/templates/nextjs/docs/design-docs/core-beliefs.md +43 -0
- package/templates/nextjs/docs/design-docs/decisions/ADR-000-infrastructure.md +72 -0
- package/templates/nextjs/docs/design-docs/decisions/index.md +7 -0
- package/templates/nextjs/docs/design-docs/index.md +7 -0
- package/templates/nextjs/docs/design-docs/team-workflow.md +66 -0
- package/templates/nextjs/docs/exec-plans/active/.gitkeep +0 -0
- package/templates/nextjs/docs/exec-plans/completed/.gitkeep +0 -0
- package/templates/nextjs/docs/exec-plans/tech-debt-tracker.md +19 -0
- package/templates/nextjs/docs/generated/.gitkeep +0 -0
- package/templates/nextjs/docs/product-specs/ROADMAP.md +59 -0
- package/templates/nextjs/docs/product-specs/draft/.gitkeep +0 -0
- package/templates/nextjs/docs/product-specs/index.md +18 -0
- package/templates/nextjs/docs/product-specs/ready/.gitkeep +0 -0
- package/templates/nextjs/eslint.config.mjs +150 -0
- package/templates/nextjs/gitignore +35 -0
- package/templates/nextjs/lighthouserc.js +37 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Observability — See It & Debug It
|
|
2
|
+
|
|
3
|
+
How traces, metrics, and logs flow through this service, how to look at them in Grafana, and how
|
|
4
|
+
to use them to debug a real problem. Read this once; it pays for itself the first time something
|
|
5
|
+
is slow in a way you can't reproduce locally.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Mental model (one line)
|
|
10
|
+
|
|
11
|
+
> **Traces + metrics → OTLP → otel-lgtm collector. Logs → pino JSON (correlated by a span-reading
|
|
12
|
+
> mixin) → Alloy → Loki.** Helpers live in `config/` so services stay clean; the `NodeSDK` lives
|
|
13
|
+
> only in `providers/telemetry.ts` and boots *before* the app is imported.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
┌────────────────────────────────────────────┐
|
|
17
|
+
│ grafana/otel-lgtm │
|
|
18
|
+
┌─────────────┐ OTLP/HTTP :4318 │ Collector → Tempo (traces) │
|
|
19
|
+
│ │ ───────────────► │ → Prometheus (metrics) │
|
|
20
|
+
│ Node app │ traces+metrics │ → Loki (logs) │
|
|
21
|
+
│ (Express) │ │ Grafana UI :3000 → published on host :3001 │
|
|
22
|
+
│ │ stdout JSON └───────▲──────────────────────────────────────┘
|
|
23
|
+
│ pino ──────┼──► (docker logs) ─┐ │ push to Loki :3100
|
|
24
|
+
└─────────────┘ trace_id/span_id│ ┌───┴────────┐
|
|
25
|
+
└─►│ Alloy │ tails container stdout, parses pino JSON
|
|
26
|
+
└─────────────┘
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Why logs take the side road through Alloy instead of OTLP: under ESM, `@opentelemetry/instrumentation-pino`'s
|
|
30
|
+
OTLP log-sending does not reliably emit. So we **don't** depend on it. Correlation is done
|
|
31
|
+
deterministically by a pino `mixin` that reads the active span; shipping is done by Alloy tailing
|
|
32
|
+
stdout. Decoupled and boring — exactly what you want from logging.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Where the code lives
|
|
37
|
+
|
|
38
|
+
| Concern | File | Notes |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| SDK boot (traces+metrics) | `src/providers/telemetry.ts` | The **only** file importing `NodeSDK`. Boot-only, excluded from coverage. |
|
|
41
|
+
| Boot ordering | `src/runtime/server.ts` | `await startTelemetry()` **then** `await import('./app.js')`. |
|
|
42
|
+
| Logger | `src/config/logger.ts` | pino + trace-correlation mixin + guarded pretty transport. |
|
|
43
|
+
| Spans | `src/config/tracing.ts` | `withSpan(name, attrs, fn)`. No-op when SDK off. |
|
|
44
|
+
| Metrics | `src/config/metrics.ts` | `increment()`, histograms. No-op when SDK off. |
|
|
45
|
+
| Env flags | `src/config/env.ts` | `OTEL_*`. Blank endpoint ⇒ everything no-ops. |
|
|
46
|
+
| Local backend | `docker-compose.yml` (`lgtm`, `alloy`) | All-in-one; nothing to configure. |
|
|
47
|
+
| Log shipping | `infra/monitoring/alloy/config.alloy` | stdout JSON → Loki. |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Quickstart
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# 1. Start the backend (and the app) with telemetry wired up.
|
|
55
|
+
docker compose up -d
|
|
56
|
+
|
|
57
|
+
# 2. Open Grafana.
|
|
58
|
+
# http://localhost:3001 (user: admin password: admin)
|
|
59
|
+
|
|
60
|
+
# 3. Generate some signal.
|
|
61
|
+
curl http://localhost:3000/health
|
|
62
|
+
curl http://localhost:3000/ready
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
> Running the app outside Docker (`npm run dev`)? Start just the backend and point the app at it:
|
|
66
|
+
>
|
|
67
|
+
> ```bash
|
|
68
|
+
> docker run -d --name lgtm -p 3001:3000 -p 4317:4317 -p 4318:4318 grafana/otel-lgtm
|
|
69
|
+
> # in .env: OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
|
|
70
|
+
> ```
|
|
71
|
+
>
|
|
72
|
+
> Logs won't reach Loki this way (Alloy isn't running), but they print to your terminal with
|
|
73
|
+
> `trace_id`, and traces+metrics still flow to Grafana.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## See traces (Tempo)
|
|
78
|
+
|
|
79
|
+
1. Grafana → **Explore** (compass icon) → data source **Tempo**.
|
|
80
|
+
2. Query type **Search** → run. You'll see recent traces, one row per request (e.g. `GET /health`).
|
|
81
|
+
3. Click a trace → the **span waterfall**. Express, the DB query (pg), and Redis each appear as a
|
|
82
|
+
span with its own duration. This is where you find *which part* of a request was slow.
|
|
83
|
+
|
|
84
|
+
What produces these spans: `getNodeAutoInstrumentations()` patches http/express/pg/ioredis
|
|
85
|
+
automatically. Your own service methods add spans via `withSpan(...)` (see below).
|
|
86
|
+
|
|
87
|
+
## See metrics (Prometheus)
|
|
88
|
+
|
|
89
|
+
1. Explore → data source **Prometheus**.
|
|
90
|
+
2. Try `http_server_duration_milliseconds_count` or any custom counter you emitted via
|
|
91
|
+
`increment('domain.action')`. Auto-instrumentation also emits HTTP server duration histograms.
|
|
92
|
+
|
|
93
|
+
## See logs (Loki)
|
|
94
|
+
|
|
95
|
+
1. Explore → data source **Loki**.
|
|
96
|
+
2. Query: `{service_name="myapp"}` (the label Alloy derives from the container name).
|
|
97
|
+
3. Each line is the pino JSON, including `trace_id`, `span_id`, `level`, and your `event` field.
|
|
98
|
+
Filter by level with the `level` label, e.g. `{service_name="myapp", level="error"}`.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Debug a slow request (the payoff)
|
|
103
|
+
|
|
104
|
+
You get a report: "some `/applications` calls take 2 seconds." You can't reproduce it. Here's the loop:
|
|
105
|
+
|
|
106
|
+
1. **Find it in logs.** Loki: `{service_name="myapp"} | json | durationMs > 1000`. You get the
|
|
107
|
+
exact log line for a slow call, with its `trace_id`.
|
|
108
|
+
2. **Jump to the trace.** Click the `trace_id` value → "Tempo" link (Grafana wires log↔trace via
|
|
109
|
+
the shared id). You land on that request's span waterfall.
|
|
110
|
+
3. **Read the waterfall.** One span dominates — say a `pg` query span at 1.8s. Now you know it's the
|
|
111
|
+
database, not your code, not Redis, not the network.
|
|
112
|
+
4. **Confirm the pattern in metrics.** Prometheus: is DB query duration elevated across the board,
|
|
113
|
+
or just this endpoint? That tells you "fix this query" vs "the DB is under load."
|
|
114
|
+
5. **Fix and verify.** After the fix, the same Loki→Tempo→Prometheus loop confirms the slow span is
|
|
115
|
+
gone — no guessing, no "works on my machine."
|
|
116
|
+
|
|
117
|
+
The whole point: **logs tell you *that* it was slow, traces tell you *where*, metrics tell you *how
|
|
118
|
+
often*.** They're joined by `trace_id`, so you move between them in two clicks.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Instrumenting your own code
|
|
123
|
+
|
|
124
|
+
Service methods keep the existing structured log **and** wrap their work in a span:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { withSpan } from '../config/tracing.js'
|
|
128
|
+
import { increment } from '../config/metrics.js'
|
|
129
|
+
import { logger } from '../config/logger.js'
|
|
130
|
+
|
|
131
|
+
export async function createApplication(userId: string, input: CreateApplicationInput) {
|
|
132
|
+
const start = Date.now()
|
|
133
|
+
return withSpan('application.create', { userId }, async () => {
|
|
134
|
+
const app = await applicationRepo.insert(userId, input)
|
|
135
|
+
increment('application.created')
|
|
136
|
+
logger.info({ event: 'application.create', userId, applicationId: app.id, durationMs: Date.now() - start })
|
|
137
|
+
return app
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Because `withSpan` is active, every `logger.*` call inside it automatically carries that span's
|
|
143
|
+
`trace_id`/`span_id` (the mixin reads the active span). You don't thread anything by hand.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Turning it off (tests / CI / local)
|
|
148
|
+
|
|
149
|
+
Leave **`OTEL_EXPORTER_OTLP_ENDPOINT` blank**. `startTelemetry()` returns early, no SDK starts, and
|
|
150
|
+
`withSpan`/`metrics`/`logger` fall back to the `@opentelemetry/api` no-op — zero overhead, no
|
|
151
|
+
network, no backend needed. This is the default for `npm test` and CI. **Never** start the LGTM
|
|
152
|
+
stack in CI.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Production
|
|
157
|
+
|
|
158
|
+
The otel-lgtm image is for **dev/demo/testing**, not production. In prod, point
|
|
159
|
+
`OTEL_EXPORTER_OTLP_ENDPOINT` at your real collector (e.g. Grafana Cloud, an OTel Collector
|
|
160
|
+
deployment, or a vendor endpoint) and run a log agent (Alloy/Promtail/vendor) against stdout. The
|
|
161
|
+
app code does not change — only the endpoint and the log shipper's destination.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Gotchas → solutions (the hard-won bits)
|
|
166
|
+
|
|
167
|
+
1. **ESM import hoisting broke instrumentation.** A static `import` of the app loaded express/pg/redis
|
|
168
|
+
before the SDK started, so nothing got patched. → `server.ts` `await startTelemetry()` first,
|
|
169
|
+
then `await import('./app.js')`. `telemetry.ts` must not statically import any instrumented module.
|
|
170
|
+
2. **Service can't import Providers** (layer rule, enforced by madge). → `withSpan`/`metrics`/`logger`
|
|
171
|
+
live in `config/` (every layer may import Config) and use only the `@opentelemetry/api` global.
|
|
172
|
+
3. **`z.coerce.boolean('false') === true`.** → Use `z.stringbool()` (zod 4) for boolean env flags.
|
|
173
|
+
4. **NodeSDK option/version skew.** → Use the plural `metricReaders` (and `logRecordProcessors` if you
|
|
174
|
+
ever add OTLP logs). Keep every `@opentelemetry/*` on one release line; don't bump `api` past the
|
|
175
|
+
SDK's peer range.
|
|
176
|
+
5. **`pino-pretty` crashed the prod image.** It's a devDependency, absent after `npm ci --omit=dev`.
|
|
177
|
+
→ `logger.ts` only uses the pretty transport if `require.resolve('pino-pretty')` succeeds; prod
|
|
178
|
+
logs JSON.
|
|
179
|
+
6. **Logs never reached Loki under ESM.** The pino→OTLP bridge silently emitted nothing (verified by
|
|
180
|
+
a missing `trace_id`). → Two parts: deterministic correlation via a pino `mixin` reading the active
|
|
181
|
+
span, and shipping via Alloy tailing stdout JSON. Independent of auto-patching.
|
|
182
|
+
7. **Coverage gates.** `telemetry.ts` is boot-only → excluded from coverage (like `server.ts`).
|
|
183
|
+
Unit-test the `config/` helpers against the no-op API (fast, no infra).
|
|
184
|
+
8. **Port clash.** otel-lgtm's Grafana defaults to 3000 → published on host **3001** so it doesn't
|
|
185
|
+
collide with the app/frontend.
|
|
186
|
+
9. **CI.** Leave `OTEL_EXPORTER_OTLP_ENDPOINT` blank in tests so the SDK no-ops; never start the LGTM
|
|
187
|
+
stack in CI. Verify against real backends only in dev.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## See also
|
|
192
|
+
|
|
193
|
+
- `docs/design-docs/decisions/ADR-001-observability.md` — *why* these choices were made.
|
|
194
|
+
- `ARCHITECTURE.md` — the layer rules that put the helpers in Config and the SDK in Providers.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Team Workflow
|
|
2
|
+
|
|
3
|
+
How a 5-person team collaborates on this harness. Pairs with `.github/CODEOWNERS`
|
|
4
|
+
and `.github/pull_request_template.md`.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Branching
|
|
9
|
+
|
|
10
|
+
- `main` is always deployable. No direct pushes.
|
|
11
|
+
- Feature branches: `<type>/<short-slug>` — e.g. `feat/application-stages`, `fix/cursor-decode`.
|
|
12
|
+
- One feature = one branch = one plan in `docs/exec-plans/active/`.
|
|
13
|
+
- Rebase on `main` before opening a PR; keep branches short-lived (< 3 days).
|
|
14
|
+
|
|
15
|
+
## The PR Flow
|
|
16
|
+
|
|
17
|
+
1. Build layer-by-layer with `/build-layer`; each layer is gated and committed.
|
|
18
|
+
2. When the plan's layers are all checked off, run `/garbage-collect`.
|
|
19
|
+
3. Open a PR — the template checklist auto-populates. Fill every box.
|
|
20
|
+
4. CI must be green (lint → typecheck → test → audit → secret-scan → arch-check).
|
|
21
|
+
5. Code Owners review (see below). Address feedback with new commits (don't force-push mid-review).
|
|
22
|
+
6. Squash-merge to `main`. Delete the branch.
|
|
23
|
+
|
|
24
|
+
## Branch Protection (configure on `main` in GitHub → Settings → Branches)
|
|
25
|
+
|
|
26
|
+
- [x] Require a pull request before merging
|
|
27
|
+
- [x] Require approvals: **1** (2 for security-sensitive paths)
|
|
28
|
+
- [x] Require review from Code Owners
|
|
29
|
+
- [x] Dismiss stale approvals when new commits are pushed
|
|
30
|
+
- [x] Require status checks to pass: `ci`
|
|
31
|
+
- [x] Require branches to be up to date before merging
|
|
32
|
+
- [x] Require conversation resolution before merging
|
|
33
|
+
- [x] Do not allow bypassing the above (applies to admins too)
|
|
34
|
+
- [x] Restrict who can push to matching branches (no direct pushes)
|
|
35
|
+
|
|
36
|
+
## Approve vs. Merge
|
|
37
|
+
|
|
38
|
+
| Action | Who |
|
|
39
|
+
|---|---|
|
|
40
|
+
| Open a PR | Anyone |
|
|
41
|
+
| Review / approve | Any engineer not the author |
|
|
42
|
+
| Approve security-sensitive paths (`auth/`, `hooks/`, `config/`, `migrations/`) | A Code Owner for that path (per `CODEOWNERS`) |
|
|
43
|
+
| Merge to `main` | The PR author, once required approvals + CI are green |
|
|
44
|
+
| Merge a security-sensitive PR | A `leads` member |
|
|
45
|
+
|
|
46
|
+
Rule of thumb: **the author merges their own PR** after it's approved and green — this
|
|
47
|
+
keeps ownership with the person who has the most context. Exception: security-sensitive
|
|
48
|
+
PRs are merged by a Code Owner.
|
|
49
|
+
|
|
50
|
+
## Required Reviews by Path
|
|
51
|
+
|
|
52
|
+
These come from `.github/CODEOWNERS`. Edit that file to swap the placeholder team
|
|
53
|
+
slugs for your real ones, then enable "Require review from Code Owners" in branch
|
|
54
|
+
protection so they're enforced:
|
|
55
|
+
|
|
56
|
+
- `/.claude/hooks/`, `/src/providers/auth/`, `/src/config/` → `@your-team/leads` (security-sensitive)
|
|
57
|
+
- `/Dockerfile`, `/docker-compose.yml`, `/.github/workflows/` → `@your-team/platform` (build/containers/CI)
|
|
58
|
+
- `/db/migrations/` → `@your-team/leads` (irreversible DB changes)
|
|
59
|
+
- everything else → `@your-team/engineers`
|
|
60
|
+
|
|
61
|
+
## Hotfixes
|
|
62
|
+
|
|
63
|
+
- Branch from `main`: `fix/<slug>`. Same PR flow, but reviewers prioritise.
|
|
64
|
+
- A hotfix still needs CI green and one approval — no exceptions, even under pressure.
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Technical Debt Tracker
|
|
2
|
+
|
|
3
|
+
Debt is logged here immediately when created. Never ignored until it's a crisis.
|
|
4
|
+
Paid in small daily amounts.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Severity
|
|
9
|
+
|
|
10
|
+
| Level | Meaning | Fix by |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| P0 | Security or data integrity risk | Immediately |
|
|
13
|
+
| P1 | Blocks future feature or causes recurring bugs | Within 2 sprints |
|
|
14
|
+
| P2 | Degrades quality or velocity | Within the quarter |
|
|
15
|
+
| P3 | Code smell, DX improvement | When in the area |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Open Debt
|
|
20
|
+
|
|
21
|
+
| ID | Severity | Area | Description | Created By | Date |
|
|
22
|
+
|---|---|---|---|---|---|
|
|
23
|
+
| *(none yet)* | — | — | — | — | — |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Resolved Debt
|
|
28
|
+
|
|
29
|
+
| ID | Description | Resolved By | Date |
|
|
30
|
+
|---|---|---|---|
|
|
31
|
+
| *(none yet)* | — | — | — |
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Product Roadmap
|
|
2
|
+
|
|
3
|
+
**Product:** *(name)* · **Brief:** *(one-line, or link to the brief)* · **Last updated:** *(YYYY-MM-DD)*
|
|
4
|
+
**Walking skeleton:** *(SPEC-001 — the thinnest end-to-end slice; build this first)*
|
|
5
|
+
|
|
6
|
+
> LIVING document — the altitude ABOVE specs. `/write-roadmap` writes it from a product brief.
|
|
7
|
+
> `/write-spec` reads it to pick the next spec and pre-fill epic + dependency context, and flips
|
|
8
|
+
> a row's **Spec ID** and **Status** as specs are created. Update in place; never delete history.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Bounded Contexts (Epics)
|
|
13
|
+
|
|
14
|
+
| Epic | Bounded Context | Responsibility |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| *(none yet)* | — | — |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Spec Roadmap (dependency-ordered)
|
|
21
|
+
|
|
22
|
+
| Order | Spec ID | Name | Epic | Depends On | Skeleton | Status |
|
|
23
|
+
|---|---|---|---|---|---|---|
|
|
24
|
+
| *(none yet)* | — | — | — | — | — | — |
|
|
25
|
+
|
|
26
|
+
> `Spec ID` is `—` until `/write-spec` creates the file and assigns the real number.
|
|
27
|
+
> `Depends On` references other rows by Spec ID (or by Order if not yet numbered).
|
|
28
|
+
> Exactly one row carries `✅ yes` in `Skeleton`. Every spec appears after its dependencies.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Build Sequence Rationale
|
|
33
|
+
|
|
34
|
+
1. *(walking skeleton — why this thin slice first: proves auth + one entity + one endpoint end-to-end)*
|
|
35
|
+
2. *(next slice — what it unblocks)*
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Roadmap Statuses
|
|
40
|
+
|
|
41
|
+
| Status | Meaning |
|
|
42
|
+
|---|---|
|
|
43
|
+
| NOT_STARTED | On the roadmap, no spec file yet |
|
|
44
|
+
| DRAFT | `/write-spec` wrote a SPEC-XXX (in `draft/`) |
|
|
45
|
+
| READY | Spec approved — plannable |
|
|
46
|
+
| PLANNED | `/write-plan` created a plan |
|
|
47
|
+
| IN_PROGRESS | Being built |
|
|
48
|
+
| SHIPPED | Complete and live |
|
|
49
|
+
| CANCELLED | Removed from the roadmap |
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Product Specs Index
|
|
2
|
+
|
|
3
|
+
> Whole-product decomposition (epics → dependency-ordered specs, walking skeleton) lives in
|
|
4
|
+
> [`ROADMAP.md`](ROADMAP.md). `/write-spec` fills this index per spec as each is created.
|
|
5
|
+
|
|
6
|
+
| ID | Name | Status | Plan | Created |
|
|
7
|
+
|---|---|---|---|---|
|
|
8
|
+
| *(no specs yet)* | — | — | — | — |
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Spec Statuses
|
|
13
|
+
|
|
14
|
+
| Status | Meaning |
|
|
15
|
+
|---|---|
|
|
16
|
+
| DRAFT | Written, not yet reviewed |
|
|
17
|
+
| READY | Reviewed and approved — plannable |
|
|
18
|
+
| PLANNED | Plan created (PLAN-XXX assigned) |
|
|
19
|
+
| IN_PROGRESS | Currently being built |
|
|
20
|
+
| SHIPPED | Feature complete and in production |
|
|
21
|
+
| CANCELLED | No longer being built |
|
|
File without changes
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import tseslint from '@typescript-eslint/eslint-plugin'
|
|
2
|
+
import tsparser from '@typescript-eslint/parser'
|
|
3
|
+
import boundaries from 'eslint-plugin-boundaries'
|
|
4
|
+
|
|
5
|
+
// Layer import boundaries — the mechanical enforcement of ARCHITECTURE.md.
|
|
6
|
+
// eslint-plugin-boundaries v6: the rule is `boundaries/dependencies` (the old
|
|
7
|
+
// `element-types` is deprecated), selectors are object-based, element patterns are
|
|
8
|
+
// folder globs (`src/<layer>/**`), and the TypeScript import resolver is REQUIRED so
|
|
9
|
+
// the project's `.js`-extension ESM specifiers resolve to their .ts files — without
|
|
10
|
+
// it, boundaries classifies nothing and silently enforces nothing.
|
|
11
|
+
const LAYER_ELEMENTS = [
|
|
12
|
+
{ type: 'types', pattern: 'src/types/**' },
|
|
13
|
+
{ type: 'config', pattern: 'src/config/**' },
|
|
14
|
+
{ type: 'models', pattern: 'src/models/**' },
|
|
15
|
+
{ type: 'repo', pattern: 'src/repo/**' },
|
|
16
|
+
{ type: 'services', pattern: 'src/services/**' },
|
|
17
|
+
{ type: 'runtime', pattern: 'src/runtime/**' },
|
|
18
|
+
{ type: 'providers', pattern: 'src/providers/**' },
|
|
19
|
+
{ type: 'utils', pattern: 'src/utils/**' },
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
// Allowed dependencies per layer (mirrors ARCHITECTURE.md "Allowed Imports").
|
|
23
|
+
// `config` and `utils` are cross-cutting; `providers` is wired in by `runtime` only.
|
|
24
|
+
// Each layer lists itself so intra-layer imports stay legal (barrels, logger->env,
|
|
25
|
+
// featureFlags->redis). Anything not allowed is rejected (default: disallow).
|
|
26
|
+
const DEP_RULES = [
|
|
27
|
+
{ from: { type: 'types' }, allow: { to: { type: ['types'] } } },
|
|
28
|
+
{ from: { type: 'utils' }, allow: { to: { type: ['types', 'utils'] } } },
|
|
29
|
+
{ from: { type: 'config' }, allow: { to: { type: ['types', 'utils', 'config'] } } },
|
|
30
|
+
{ from: { type: 'models' }, allow: { to: { type: ['types', 'config', 'utils', 'models'] } } },
|
|
31
|
+
{ from: { type: 'repo' }, allow: { to: { type: ['types', 'config', 'models', 'utils', 'repo'] } } },
|
|
32
|
+
{ from: { type: 'services' }, allow: { to: { type: ['types', 'config', 'repo', 'utils', 'services'] } } },
|
|
33
|
+
{ from: { type: 'providers' }, allow: { to: { type: ['types', 'config', 'utils', 'providers'] } } },
|
|
34
|
+
{
|
|
35
|
+
from: { type: 'runtime' },
|
|
36
|
+
allow: { to: { type: ['types', 'config', 'repo', 'services', 'providers', 'utils', 'runtime'] } },
|
|
37
|
+
},
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
export default [
|
|
41
|
+
{
|
|
42
|
+
files: ['src/**/*.ts'],
|
|
43
|
+
languageOptions: {
|
|
44
|
+
parser: tsparser,
|
|
45
|
+
parserOptions: {
|
|
46
|
+
project: './tsconfig.json',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
plugins: {
|
|
50
|
+
'@typescript-eslint': tseslint,
|
|
51
|
+
boundaries,
|
|
52
|
+
},
|
|
53
|
+
settings: {
|
|
54
|
+
'boundaries/elements': LAYER_ELEMENTS,
|
|
55
|
+
// Required so boundaries resolves the repo's `.js`-extension ESM imports to .ts files.
|
|
56
|
+
'import/resolver': { typescript: { alwaysTryTypes: true } },
|
|
57
|
+
},
|
|
58
|
+
rules: {
|
|
59
|
+
'no-console': 'error',
|
|
60
|
+
'@typescript-eslint/no-explicit-any': 'error',
|
|
61
|
+
'@typescript-eslint/no-unsafe-assignment': 'error',
|
|
62
|
+
'@typescript-eslint/no-floating-promises': 'error',
|
|
63
|
+
// Custom rule: block process.env outside config
|
|
64
|
+
'no-restricted-syntax': [
|
|
65
|
+
'error',
|
|
66
|
+
{
|
|
67
|
+
selector: 'MemberExpression[object.name="process"][property.name="env"]',
|
|
68
|
+
message: 'Use env from src/config/env.ts instead of process.env directly',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
// Mechanical layer-boundary enforcement (v6 rule + object selectors).
|
|
72
|
+
'boundaries/dependencies': [
|
|
73
|
+
'error',
|
|
74
|
+
{
|
|
75
|
+
default: 'disallow',
|
|
76
|
+
rules: DEP_RULES,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
files: ['src/config/env.ts'],
|
|
83
|
+
rules: {
|
|
84
|
+
'no-restricted-syntax': 'off', // env.ts is allowed to use process.env
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
files: ['tests/**/*.ts'],
|
|
89
|
+
rules: {
|
|
90
|
+
'no-console': 'off', // tests can use console for debugging
|
|
91
|
+
'@typescript-eslint/no-explicit-any': 'off', // tests can use any for mocks
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Build output
|
|
5
|
+
dist/
|
|
6
|
+
|
|
7
|
+
# Environment — NEVER commit .env files
|
|
8
|
+
.env
|
|
9
|
+
.env.local
|
|
10
|
+
.env.*.local
|
|
11
|
+
|
|
12
|
+
# Logs
|
|
13
|
+
*.log
|
|
14
|
+
logs/
|
|
15
|
+
|
|
16
|
+
# Coverage
|
|
17
|
+
coverage/
|
|
18
|
+
|
|
19
|
+
# OS
|
|
20
|
+
.DS_Store
|
|
21
|
+
Thumbs.db
|
|
22
|
+
|
|
23
|
+
# IDE
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
|
26
|
+
*.swp
|
|
27
|
+
*.swo
|
|
28
|
+
|
|
29
|
+
# Docker
|
|
30
|
+
docker-compose.override.yml
|
|
31
|
+
|
|
32
|
+
# k6 results
|
|
33
|
+
k6-results/
|
|
34
|
+
|
|
35
|
+
# Sequelize CLI
|
|
36
|
+
.sequelizerc.local
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Grafana Alloy — ship container stdout (pino JSON) to Loki inside otel-lgtm.
|
|
2
|
+
//
|
|
3
|
+
// The app writes structured JSON logs carrying trace_id / span_id (via the pino mixin in
|
|
4
|
+
// src/config/logger.ts). Alloy tails every container's stdout, parses the JSON to surface
|
|
5
|
+
// `level` and `trace_id`, and pushes to Loki. In Grafana you can then jump log ↔ trace.
|
|
6
|
+
//
|
|
7
|
+
// Traces and metrics do NOT pass through here — the app exports those straight to the
|
|
8
|
+
// collector over OTLP. Only logs use this path (it sidesteps the known ESM pino→OTLP bug).
|
|
9
|
+
|
|
10
|
+
discovery.docker "containers" {
|
|
11
|
+
host = "unix:///var/run/docker.sock"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Turn the Docker container name (e.g. "/harness-app-1") into a clean `service_name` label.
|
|
15
|
+
discovery.relabel "containers" {
|
|
16
|
+
targets = discovery.docker.containers.targets
|
|
17
|
+
|
|
18
|
+
rule {
|
|
19
|
+
source_labels = ["__meta_docker_container_name"]
|
|
20
|
+
regex = "/(.*)"
|
|
21
|
+
target_label = "service_name"
|
|
22
|
+
}
|
|
23
|
+
rule {
|
|
24
|
+
source_labels = ["__meta_docker_container_name"]
|
|
25
|
+
regex = "/(.*)"
|
|
26
|
+
target_label = "container"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
loki.source.docker "containers" {
|
|
31
|
+
host = "unix:///var/run/docker.sock"
|
|
32
|
+
targets = discovery.relabel.containers.output
|
|
33
|
+
labels = { job = "docker" }
|
|
34
|
+
forward_to = [loki.process.pino.receiver]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Parse pino JSON so `level` becomes a label and `trace_id` is queryable.
|
|
38
|
+
loki.process "pino" {
|
|
39
|
+
stage.json {
|
|
40
|
+
expressions = {
|
|
41
|
+
level = "level",
|
|
42
|
+
trace_id = "trace_id",
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
stage.labels {
|
|
47
|
+
values = {
|
|
48
|
+
level = "",
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
forward_to = [loki.write.default.receiver]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
loki.write "default" {
|
|
56
|
+
endpoint {
|
|
57
|
+
url = "http://lgtm:3100/loki/api/v1/push"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Config } from 'jest'
|
|
2
|
+
|
|
3
|
+
const config: Config = {
|
|
4
|
+
preset: 'ts-jest/presets/default-esm',
|
|
5
|
+
testEnvironment: 'node',
|
|
6
|
+
extensionsToTreatAsEsm: ['.ts'],
|
|
7
|
+
moduleNameMapper: {
|
|
8
|
+
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
9
|
+
},
|
|
10
|
+
transform: {
|
|
11
|
+
'^.+\\.tsx?$': [
|
|
12
|
+
'ts-jest',
|
|
13
|
+
{
|
|
14
|
+
useESM: true,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
testMatch: ['**/tests/**/*.test.ts'],
|
|
19
|
+
collectCoverageFrom: [
|
|
20
|
+
'src/**/*.ts',
|
|
21
|
+
'!src/**/*.d.ts',
|
|
22
|
+
'!src/runtime/server.ts',
|
|
23
|
+
'!src/providers/telemetry.ts', // boot-only OTel SDK wiring, like server.ts
|
|
24
|
+
],
|
|
25
|
+
coverageDirectory: 'coverage',
|
|
26
|
+
coverageReporters: ['text', 'lcov', 'html'],
|
|
27
|
+
coverageThreshold: {
|
|
28
|
+
'./src/utils/': {
|
|
29
|
+
branches: 100,
|
|
30
|
+
functions: 100,
|
|
31
|
+
lines: 100,
|
|
32
|
+
statements: 100,
|
|
33
|
+
},
|
|
34
|
+
'./src/services/': {
|
|
35
|
+
branches: 90,
|
|
36
|
+
functions: 90,
|
|
37
|
+
lines: 90,
|
|
38
|
+
statements: 90,
|
|
39
|
+
},
|
|
40
|
+
'./src/repo/': {
|
|
41
|
+
branches: 80,
|
|
42
|
+
functions: 80,
|
|
43
|
+
lines: 80,
|
|
44
|
+
statements: 80,
|
|
45
|
+
},
|
|
46
|
+
'./src/runtime/routes/': {
|
|
47
|
+
branches: 75,
|
|
48
|
+
functions: 75,
|
|
49
|
+
lines: 75,
|
|
50
|
+
statements: 75,
|
|
51
|
+
},
|
|
52
|
+
'./src/providers/': {
|
|
53
|
+
branches: 70,
|
|
54
|
+
functions: 70,
|
|
55
|
+
lines: 70,
|
|
56
|
+
statements: 70,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default config
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "harness-typescript-template",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Express + TypeScript + Sequelize harness template",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24",
|
|
8
|
+
"npm": ">=11"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "tsx watch src/runtime/server.ts",
|
|
12
|
+
"build": "tsc -p tsconfig.build.json",
|
|
13
|
+
"start": "node dist/runtime/server.js",
|
|
14
|
+
"test": "jest",
|
|
15
|
+
"test:arch": "jest tests/architecture/",
|
|
16
|
+
"test:coverage": "jest --coverage",
|
|
17
|
+
"test:watch": "jest --watch",
|
|
18
|
+
"load:smoke": "k6 run tests/load/smoke.js",
|
|
19
|
+
"load:stress": "k6 run tests/load/stress.js",
|
|
20
|
+
"load:soak": "k6 run tests/load/soak.js",
|
|
21
|
+
"lint": "eslint src/ --max-warnings=0",
|
|
22
|
+
"lint:fix": "eslint src/ --fix",
|
|
23
|
+
"format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
|
|
24
|
+
"format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"check:circular": "madge --circular src/ --extensions ts",
|
|
27
|
+
"gate": "npm run typecheck && npm run lint && npm run check:circular && npm run test:arch",
|
|
28
|
+
"audit": "npm audit --omit=dev --audit-level=high",
|
|
29
|
+
"openapi:export": "tsx scripts/openapi.export.ts",
|
|
30
|
+
"db:migrate": "sequelize-cli db:migrate",
|
|
31
|
+
"db:migrate:undo": "sequelize-cli db:migrate:undo",
|
|
32
|
+
"db:seed": "sequelize-cli db:seed:all",
|
|
33
|
+
"prepare": "husky"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"express",
|
|
37
|
+
"typescript",
|
|
38
|
+
"sequelize",
|
|
39
|
+
"harness"
|
|
40
|
+
],
|
|
41
|
+
"author": "",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"dependencies": {},
|
|
44
|
+
"devDependencies": {}
|
|
45
|
+
}
|