@devopsplaybook.io/common-utils 1.10.1-beta.23.b1906d7 → 1.11.0-beta.24.d8f9aef
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/README.md +73 -27
- package/dist/src/ConfigBase.d.ts +31 -0
- package/dist/src/ConfigBase.js +58 -16
- package/dist/src/DbUtils.d.ts +20 -3
- package/dist/src/DbUtils.js +93 -2
- package/dist/src/DbUtilsNoTelemetry.d.ts +4 -1
- package/dist/src/DbUtilsNoTelemetry.js +62 -6
- package/dist/src/PostgresDbUtils.d.ts +41 -10
- package/dist/src/PostgresDbUtils.js +357 -304
- package/dist/src/SqlDbUtils.d.ts +12 -4
- package/dist/src/SqlDbUtils.js +76 -30
- package/dist/src/users/Auth.d.ts +11 -1
- package/dist/src/users/Auth.js +160 -44
- package/dist/src/users/User.d.ts +10 -0
- package/dist/src/users/User.js +30 -10
- package/dist/src/users/UserApiToken.d.ts +4 -0
- package/dist/src/users/UserApiToken.js +11 -0
- package/dist/src/users/UsersApiTokensData.d.ts +12 -0
- package/dist/src/users/UsersApiTokensData.js +130 -33
- package/dist/src/users/UsersData.d.ts +20 -1
- package/dist/src/users/UsersData.js +150 -52
- package/dist/src/users/UsersRoutes.js +178 -61
- package/dist/src/users/index.d.ts +8 -0
- package/dist/src/users/index.js +24 -0
- package/package.json +58 -1
- package/.github/workflows/main-build.yml +0 -18
- package/.github/workflows/pr-check.yml +0 -27
- package/.github/workflows/reusable-merge-build.yml +0 -197
- package/.github/workflows/reusable-npm-merge.yml +0 -135
- package/.github/workflows/reusable-npm-pr.yml +0 -183
- package/.github/workflows/reusable-npm-upgrade.yml +0 -92
- package/.github/workflows/reusable-pr-verify.yml +0 -181
- package/AGENTS.md +0 -105
- package/index.ts +0 -18
- package/jest.config.js +0 -17
- package/prettierrc.json +0 -5
- package/src/ConfigBase.spec.ts +0 -108
- package/src/ConfigBase.ts +0 -297
- package/src/DbUtils.spec.ts +0 -23
- package/src/DbUtils.ts +0 -116
- package/src/DbUtilsNoTelemetry.spec.ts +0 -168
- package/src/DbUtilsNoTelemetry.ts +0 -117
- package/src/LLM.spec.ts +0 -303
- package/src/LLM.ts +0 -204
- package/src/Notifications.spec.ts +0 -265
- package/src/Notifications.ts +0 -201
- package/src/OTelContext.spec.ts +0 -58
- package/src/OTelContext.ts +0 -63
- package/src/PostgresDbUtils.spec.ts +0 -153
- package/src/PostgresDbUtils.ts +0 -666
- package/src/SqlDbUtils.spec.ts +0 -108
- package/src/SqlDbUtils.ts +0 -152
- package/src/SystemCommand.spec.ts +0 -18
- package/src/SystemCommand.ts +0 -23
- package/src/Timeout.spec.ts +0 -18
- package/src/Timeout.ts +0 -12
- package/src/users/Auth.spec.ts +0 -268
- package/src/users/Auth.ts +0 -202
- package/src/users/User.ts +0 -75
- package/src/users/UserApiToken.ts +0 -55
- package/src/users/UserPassword.spec.ts +0 -28
- package/src/users/UserPassword.ts +0 -20
- package/src/users/UserSession.ts +0 -9
- package/src/users/UsersApiTokensData.spec.ts +0 -158
- package/src/users/UsersApiTokensData.ts +0 -125
- package/src/users/UsersData.ts +0 -141
- package/src/users/UsersRoutes.ts +0 -374
- package/tsconfig.json +0 -15
- package/tsconfig.spec.json +0 -8
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
name: "Reusable: PR Verify"
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
workflow_call:
|
|
5
|
-
inputs:
|
|
6
|
-
docker_platforms:
|
|
7
|
-
description: "Docker platforms to build for"
|
|
8
|
-
required: false
|
|
9
|
-
type: string
|
|
10
|
-
default: "linux/arm64/v8,linux/amd64"
|
|
11
|
-
node_app_directories:
|
|
12
|
-
description: "JSON array of Node.js app directories to build, lint, and test"
|
|
13
|
-
required: false
|
|
14
|
-
type: string
|
|
15
|
-
default: ""
|
|
16
|
-
node_version:
|
|
17
|
-
description: "Node.js version to use"
|
|
18
|
-
required: false
|
|
19
|
-
type: string
|
|
20
|
-
default: "22"
|
|
21
|
-
secrets:
|
|
22
|
-
DOCKER_HUB_USERNAME:
|
|
23
|
-
required: true
|
|
24
|
-
DOCKER_HUB_ACCESS_TOKEN:
|
|
25
|
-
required: true
|
|
26
|
-
QUALITY_DASHBOARD_TOKEN:
|
|
27
|
-
required: false
|
|
28
|
-
QUALITY_DASHBOARD_URL:
|
|
29
|
-
required: false
|
|
30
|
-
|
|
31
|
-
jobs:
|
|
32
|
-
version-check:
|
|
33
|
-
runs-on: ubuntu-latest
|
|
34
|
-
steps:
|
|
35
|
-
- uses: actions/checkout@v7
|
|
36
|
-
|
|
37
|
-
- name: Fetch main branch
|
|
38
|
-
run: git fetch origin main --depth=1
|
|
39
|
-
|
|
40
|
-
- name: Verify version is higher than main
|
|
41
|
-
run: |
|
|
42
|
-
PR_VERSION=$(jq -r '.version' package.json)
|
|
43
|
-
MAIN_VERSION=$(git show origin/main:package.json | jq -r '.version')
|
|
44
|
-
|
|
45
|
-
echo "PR version: ${PR_VERSION}"
|
|
46
|
-
echo "Main version: ${MAIN_VERSION}"
|
|
47
|
-
|
|
48
|
-
if [ "$PR_VERSION" = "$MAIN_VERSION" ]; then
|
|
49
|
-
echo "::error::PR version (${PR_VERSION}) must be higher than main branch version (${MAIN_VERSION}). Please bump the version in package.json."
|
|
50
|
-
exit 1
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
HIGHER=$(printf '%s\n%s' "$PR_VERSION" "$MAIN_VERSION" | sort -V | tail -n1)
|
|
54
|
-
if [ "$HIGHER" != "$PR_VERSION" ]; then
|
|
55
|
-
echo "::error::PR version (${PR_VERSION}) is lower than main branch version (${MAIN_VERSION}). Please bump the version in package.json."
|
|
56
|
-
exit 1
|
|
57
|
-
fi
|
|
58
|
-
|
|
59
|
-
echo "Version check passed: ${PR_VERSION} > ${MAIN_VERSION}"
|
|
60
|
-
|
|
61
|
-
node-build:
|
|
62
|
-
needs: version-check
|
|
63
|
-
if: inputs.node_app_directories != ''
|
|
64
|
-
strategy:
|
|
65
|
-
matrix:
|
|
66
|
-
app: ${{ fromJSON(inputs.node_app_directories) }}
|
|
67
|
-
runs-on: ubuntu-latest
|
|
68
|
-
defaults:
|
|
69
|
-
run:
|
|
70
|
-
working-directory: ${{ matrix.app }}
|
|
71
|
-
steps:
|
|
72
|
-
- uses: actions/checkout@v7
|
|
73
|
-
|
|
74
|
-
- name: Setup Node.js
|
|
75
|
-
uses: actions/setup-node@v7
|
|
76
|
-
with:
|
|
77
|
-
node-version: ${{ inputs.node_version }}
|
|
78
|
-
cache: "npm"
|
|
79
|
-
cache-dependency-path: ${{ matrix.app }}/package-lock.json
|
|
80
|
-
|
|
81
|
-
- name: Install dependencies
|
|
82
|
-
run: npm ci
|
|
83
|
-
|
|
84
|
-
- name: Build
|
|
85
|
-
run: npm run build
|
|
86
|
-
|
|
87
|
-
- name: Lint
|
|
88
|
-
run: npm run lint
|
|
89
|
-
|
|
90
|
-
- name: Test
|
|
91
|
-
run: npm run test
|
|
92
|
-
|
|
93
|
-
- name: Zip coverage and upload to Quality Dashboard
|
|
94
|
-
if: always()
|
|
95
|
-
env:
|
|
96
|
-
QUALITY_DASHBOARD_URL: ${{ secrets.QUALITY_DASHBOARD_URL }}
|
|
97
|
-
QUALITY_DASHBOARD_TOKEN: ${{ secrets.QUALITY_DASHBOARD_TOKEN }}
|
|
98
|
-
run: |
|
|
99
|
-
APP_NAME="${{ matrix.app }}"
|
|
100
|
-
PACKAGE_NAME=$(node -p "require('./package.json').name")
|
|
101
|
-
REPORT_KEY="$(echo "$APP_NAME" | sed 's/[^a-zA-Z0-9._:\-\/]/_/g')_coverage"
|
|
102
|
-
|
|
103
|
-
if [ -d coverage ]; then
|
|
104
|
-
if [ -z "$QUALITY_DASHBOARD_URL" ] || [ -z "$QUALITY_DASHBOARD_TOKEN" ]; then
|
|
105
|
-
echo "Quality Dashboard URL or token not set, skipping upload"
|
|
106
|
-
exit 0
|
|
107
|
-
fi
|
|
108
|
-
# Zip coverage contents at root level so the jest processor
|
|
109
|
-
# finds coverage-final.json / clover.xml directly
|
|
110
|
-
(cd coverage && zip -r "${{ runner.temp }}/coverage.zip" .)
|
|
111
|
-
|
|
112
|
-
META=$(jq -n \
|
|
113
|
-
--arg key "$REPORT_KEY" \
|
|
114
|
-
--arg pkg "$PACKAGE_NAME" \
|
|
115
|
-
'{
|
|
116
|
-
key: $key,
|
|
117
|
-
displayName: "Coverage: \($pkg)",
|
|
118
|
-
processor: "jest"
|
|
119
|
-
}')
|
|
120
|
-
|
|
121
|
-
echo "Uploading coverage: $REPORT_KEY"
|
|
122
|
-
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
123
|
-
-X POST "$QUALITY_DASHBOARD_URL/api/reports/" \
|
|
124
|
-
-H "x-upload-token: $QUALITY_DASHBOARD_TOKEN" \
|
|
125
|
-
-F "meta=$META" \
|
|
126
|
-
-F "file=@${{ runner.temp }}/coverage.zip" \
|
|
127
|
-
--max-time 30)
|
|
128
|
-
|
|
129
|
-
if [ "$HTTP_CODE" = "201" ]; then
|
|
130
|
-
echo "\u2713 Coverage uploaded ($HTTP_CODE)"
|
|
131
|
-
else
|
|
132
|
-
echo "\u26a0 Upload returned HTTP $HTTP_CODE"
|
|
133
|
-
fi
|
|
134
|
-
else
|
|
135
|
-
echo "No coverage/ directory found, skipping upload"
|
|
136
|
-
fi
|
|
137
|
-
|
|
138
|
-
docker-build:
|
|
139
|
-
needs: version-check
|
|
140
|
-
runs-on: ubuntu-latest
|
|
141
|
-
steps:
|
|
142
|
-
- uses: actions/checkout@v7
|
|
143
|
-
|
|
144
|
-
- name: Set up QEMU
|
|
145
|
-
uses: docker/setup-qemu-action@v4
|
|
146
|
-
|
|
147
|
-
- name: Set up Docker Buildx
|
|
148
|
-
uses: docker/setup-buildx-action@v4
|
|
149
|
-
|
|
150
|
-
- name: Login to Docker Hub
|
|
151
|
-
uses: docker/login-action@v4
|
|
152
|
-
with:
|
|
153
|
-
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
154
|
-
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
|
155
|
-
|
|
156
|
-
- name: Build and Push Docker Image
|
|
157
|
-
env:
|
|
158
|
-
DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
159
|
-
DOCKER_PLATFORMS: ${{ inputs.docker_platforms }}
|
|
160
|
-
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
161
|
-
run: |
|
|
162
|
-
set -e
|
|
163
|
-
SERVICE_NAME=$(jq -r '.name' package.json)
|
|
164
|
-
IMAGE="${DOCKER_HUB_USERNAME}/${SERVICE_NAME}"
|
|
165
|
-
|
|
166
|
-
# `beta-pr-<number>` is the immutable per-PR reference: it identifies
|
|
167
|
-
# exactly the artifact validated by this pull request, and the merge
|
|
168
|
-
# build promotes it as-is to the semantic version tags.
|
|
169
|
-
PR_TAG="${PR_NUMBER}"
|
|
170
|
-
if [ -z "${PR_TAG}" ]; then
|
|
171
|
-
PR_TAG="manual-${GITHUB_SHA::7}"
|
|
172
|
-
fi
|
|
173
|
-
|
|
174
|
-
echo "Building ${IMAGE}:beta-pr-${PR_TAG} and ${IMAGE}:beta"
|
|
175
|
-
docker buildx build \
|
|
176
|
-
--platform "${DOCKER_PLATFORMS}" \
|
|
177
|
-
--push \
|
|
178
|
-
-f Dockerfile \
|
|
179
|
-
-t "${IMAGE}:beta-pr-${PR_TAG}" \
|
|
180
|
-
-t "${IMAGE}:beta" \
|
|
181
|
-
.
|
package/AGENTS.md
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# AGENTS.md
|
|
2
|
-
|
|
3
|
-
This file provides context for AI coding agents working on this repository.
|
|
4
|
-
|
|
5
|
-
## Repository Overview
|
|
6
|
-
|
|
7
|
-
`@devopsplaybook.io/common-utils` is a shared npm library that centralizes utility modules used across devopsplaybook.io server projects. It serves two purposes:
|
|
8
|
-
|
|
9
|
-
1. **Node.js library** (`src/`): Database access (SQLite via `better-sqlite3`, PostgreSQL via `pg.Pool`), configuration loading, OpenTelemetry context management, and small helpers.
|
|
10
|
-
2. **Reusable GitHub Actions workflows** (`.github/workflows/reusable-*.yml`): Standardized CI/CD pipelines adopted by all projects in the organization.
|
|
11
|
-
|
|
12
|
-
## Architecture
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
index.ts # Barrel re-exports for all modules
|
|
16
|
-
src/
|
|
17
|
-
OTelContext.ts # createOTelContext() factory (tracer/meter/logger singletons)
|
|
18
|
-
ConfigBase.ts # Abstract base class for 3-layer config (env > config.json > defaults)
|
|
19
|
-
SqlDbUtils.ts # SQLite operations (better-sqlite3, synchronous)
|
|
20
|
-
PostgresDbUtils.ts # PostgreSQL operations (pg.Pool, async/Promise)
|
|
21
|
-
DbUtils.ts # Unified facade dispatching to Sql or Postgres
|
|
22
|
-
DbUtilsNoTelemetry.ts # Same DB ops without OTel span overhead
|
|
23
|
-
users/ # Auth & user management module set
|
|
24
|
-
User.ts # User model, roles and application-defined scopes
|
|
25
|
-
UserSession.ts # Decoded JWT session
|
|
26
|
-
UserApiToken.ts # User API token model (SHA-256 hash only)
|
|
27
|
-
Auth.ts # JWT auth (key init, guards, session decode) + API token resolution
|
|
28
|
-
UserPassword.ts # bcrypt password hashing/verification
|
|
29
|
-
UsersData.ts # Users table CRUD (SQLite/Postgres)
|
|
30
|
-
UsersApiTokensData.ts # users_api_tokens table CRUD (SQLite/Postgres)
|
|
31
|
-
UsersRoutes.ts # Standard fastify user management routes (incl. self-service API tokens)
|
|
32
|
-
SystemCommand.ts # Promise wrapper around child_process.exec
|
|
33
|
-
Timeout.ts # Promise wrapper around setTimeout
|
|
34
|
-
LLM.ts # OpenAI-compatible chat completions client (LLMClient)
|
|
35
|
-
*.spec.ts # Co-located test files
|
|
36
|
-
.github/workflows/
|
|
37
|
-
main-build.yml # Caller: push to main -> reusable-npm-merge
|
|
38
|
-
pr-check.yml # Caller: PR to main -> reusable-npm-pr
|
|
39
|
-
reusable-npm-merge.yml # Lint + test + build + publish release to npm
|
|
40
|
-
reusable-npm-pr.yml # Lint + test + build + publish beta tag + comment PR
|
|
41
|
-
reusable-npm-upgrade.yml # npm-check-updates + auto PR (manual, workflow_call)
|
|
42
|
-
reusable-pr-verify.yml # Matrix Node.js + multi-platform Docker build -> beta-pr-<PR> and beta
|
|
43
|
-
reusable-merge-build.yml # Promotes the PR image to the version tags on merge (no build/lint/test)
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Weekly Dependency Updates
|
|
47
|
-
|
|
48
|
-
The scheduled `npm-upgrade.yml` caller was retired: it created dependency upgrade pull requests every week that no one merged. `reusable-npm-upgrade.yml` is kept for manual, on-demand upgrades (`workflow_call`; adopting repositories can trigger it through their own `workflow_dispatch` caller).
|
|
49
|
-
|
|
50
|
-
The weekly update of the shared npm libraries is agent-driven instead: a Kubernetes CronJob (`planner-shared-libraries-update` in the `DidierHoarau/didier-home` GitOps repository) creates a Planner task in the `Projects/xMaintenance` project every Friday, and the planner-llm-agent executes the `update-shared-libraries` skill from `DidierHoarau/planner-llm-agent-config`. The skill updates `otel-utils` first, then `otel-utils-fastify` and `common-utils` once the freshly published `otel-utils` version is visible on npm, then the consumer repositories discovered with GitHub code search. It applies minor/patch updates only (majors are reported, never applied), skips repositories already current against the npm registry, and delivers one squash-merged pull request per repository after green checks. Do not reintroduce a scheduled upgrade workflow in this repository: it would compete with the agent-driven update.
|
|
51
|
-
|
|
52
|
-
## Key Conventions
|
|
53
|
-
|
|
54
|
-
- **TypeScript**: Target ES2019, CommonJS output, strict mode enabled, declarations generated.
|
|
55
|
-
- **Linting**: Uses `oxlint` with its default **recommended** preset. Minimize disable comments (`eslint-disable`-style comments are honored by oxlint); use them only when the rule cannot be satisfied (e.g. `no-explicit-any` is not enabled by the recommended preset, so `any` needs no directive).
|
|
56
|
-
- **Tests**: Jest with `@swc/jest` transform (`jsc.target` matches tsconfig ES2019) and `v8` coverage provider. Spec files live next to source (`*.spec.ts`). Run with `npm run test`. The `tsconfig.spec.json` includes jest types; `npm run build` also type-checks specs (`tsc -p tsconfig.spec.json --noEmit`).
|
|
57
|
-
- **No default exports**: All modules use named exports only.
|
|
58
|
-
- **OTel dependency injection**: Every DB module exposes a `*SetOTel(tracer, logger)` function that must be called before `*Init()`. OTel instances are stored as module-level singletons.
|
|
59
|
-
- **Auth modules**: `AuthSetOTel(tracer)` and `UsersDataSetOTel(tracer)` must be called before `AuthInit`. Application scopes are registered through `AuthInit(context, config, allScopes)`; `UsersRoutes` relies on `req.tracerSpanApi` set by the `otel-utils-fastify` hooks.
|
|
60
|
-
- **ModuleLogger pattern**: `StandardLogger` only exposes `createModuleLogger(name)`. DB modules call `logger.createModuleLogger("ModuleName")` internally. Never call `.info()` or `.error()` directly on a `StandardLogger`.
|
|
61
|
-
- **SQLite-first SQL**: Write SQL with `?` placeholders. The `DbUtils` facade and `DbUtilsNoTelemetry` module auto-convert to `$1, $2, ...` for Postgres via `convertToPostgresPlaceholders()`.
|
|
62
|
-
- **Migration convention**: SQL files named `init-NNNN.sql`. `init-0000.sql` must create the `metadata` table. Subsequent files are applied in lexicographic order; applied versions are tracked in `metadata` for idempotency.
|
|
63
|
-
- **Immutable Docker builds**: images are built once on the pull request (`reusable-pr-verify.yml` pushes `beta-pr-<PR number>` and `beta`) and promoted unchanged on merge (`reusable-merge-build.yml` writes `<version>`, `<major>`, `<minor>` and `latest`). The merge workflow runs no build, lint or test: it resolves the merged PR, carbon-copies the manifest with `docker buildx imagetools create --prefer-index=false`, then reads back each published digest and fails on a mismatch. It falls back to a full build only when no `beta-pr-<PR number>` image can be promoted. Image name and version come from the root `package.json`, and PR branches must be up to date with the default branch before merging.
|
|
64
|
-
- **Reusable workflow permissions ceiling**: `reusable-merge-build.yml` may request only `contents: read`. A called workflow cannot exceed its caller's permissions, and callers use the default read-only token whose ceiling is `contents: read, pull-requests: none`. Adding `pull-requests: read` does not fail here — it fails in every consuming repo at startup with `Invalid workflow file ... requesting 'pull-requests: read', but is only allowed 'pull-requests: none'`. `GET /repos/{owner}/{repo}/commits/{sha}/pulls` works with `contents: read`, so no extra scope is needed.
|
|
65
|
-
|
|
66
|
-
## Build and Verification
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
npm install
|
|
70
|
-
npm run build # tsc -> dist/
|
|
71
|
-
npm run lint # oxlint src (must pass with 0 errors)
|
|
72
|
-
npm run test # jest --coverage (all tests must pass)
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
All three commands must pass before committing. The CI pipeline (`reusable-npm-merge.yml`) runs the same checks.
|
|
76
|
-
|
|
77
|
-
## Dependencies
|
|
78
|
-
|
|
79
|
-
| Package | Role |
|
|
80
|
-
| ------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
81
|
-
| `@devopsplaybook.io/otel-utils` | `StandardTracer`, `StandardLogger`, `StandardMeter`, `ModuleLogger`, `ConfigOTelInterface` |
|
|
82
|
-
| `better-sqlite3` | Synchronous SQLite driver (NOT the callback-based `sqlite3`) |
|
|
83
|
-
| `pg` | PostgreSQL client with connection pooling |
|
|
84
|
-
| `uuid` | v14+ (ESM -- requires `jest.mock("uuid")` in tests) |
|
|
85
|
-
| `fs-extra` | Async/sync file operations, `readJson`/`ensureDir` |
|
|
86
|
-
| `bcrypt` | Password hashing (users module) |
|
|
87
|
-
| `jsonwebtoken` | JWT signing/verification (auth module) |
|
|
88
|
-
| `fastify` | HTTP framework types used by `UsersRoutes` |
|
|
89
|
-
|
|
90
|
-
## Known Gotchas
|
|
91
|
-
|
|
92
|
-
- **uuid ESM**: `uuid` v14+ ships ESM. In any test that transitively imports `uuid`, add `jest.mock("uuid", () => ({ v4: () => "mock-uuid-1234" }))` to avoid `SyntaxError: Unexpected token 'export'`.
|
|
93
|
-
- **better-sqlite3 is synchronous**: `SqlDbUtils` functions return values directly (not Promises). `PostgresDbUtils` functions return Promises. The `DbUtils` facade returns `number | Promise<number>` depending on the active backend.
|
|
94
|
-
- **Disable-comment placement**: `eslint-disable-next-line` applies to the **immediately following line only** (oxlint honors these comments too). When disabling a rule inside a function call argument, place the comment directly before the offending expression, not before the function call.
|
|
95
|
-
- **pg callback typing**: Always explicitly type pg callback parameters: `(error: Error | null, result: { rowCount: number | null })`. TypeScript cannot infer these from the overloaded `pool.query` signature.
|
|
96
|
-
|
|
97
|
-
## Adopting in Other Projects
|
|
98
|
-
|
|
99
|
-
See the [README](./README.md) for full adoption instructions. The typical pattern:
|
|
100
|
-
|
|
101
|
-
1. Add `@devopsplaybook.io/common-utils` as a dependency.
|
|
102
|
-
2. Replace the project's `OTelContext.ts` with `createOTelContext()`.
|
|
103
|
-
3. Replace the project's `Config` class to `extend ConfigBase`.
|
|
104
|
-
4. Replace DB utility files with re-exports from `common-utils`.
|
|
105
|
-
5. Update `App.ts` to call `*SetOTel()` and `*Init()` from `common-utils`.
|
package/index.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export * from "./src/OTelContext";
|
|
2
|
-
export * from "./src/ConfigBase";
|
|
3
|
-
export * from "./src/DbUtils";
|
|
4
|
-
export * from "./src/DbUtilsNoTelemetry";
|
|
5
|
-
export * from "./src/SqlDbUtils";
|
|
6
|
-
export * from "./src/PostgresDbUtils";
|
|
7
|
-
export * from "./src/Notifications";
|
|
8
|
-
export * from "./src/LLM";
|
|
9
|
-
export * from "./src/SystemCommand";
|
|
10
|
-
export * from "./src/Timeout";
|
|
11
|
-
export * from "./src/users/User";
|
|
12
|
-
export * from "./src/users/UserSession";
|
|
13
|
-
export * from "./src/users/UserApiToken";
|
|
14
|
-
export * from "./src/users/Auth";
|
|
15
|
-
export * from "./src/users/UserPassword";
|
|
16
|
-
export * from "./src/users/UsersData";
|
|
17
|
-
export * from "./src/users/UsersApiTokensData";
|
|
18
|
-
export * from "./src/users/UsersRoutes";
|
package/jest.config.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
moduleFileExtensions: ["ts", "js"],
|
|
3
|
-
transform: {
|
|
4
|
-
"^.+\\.(ts|tsx)$": [
|
|
5
|
-
"@swc/jest",
|
|
6
|
-
{
|
|
7
|
-
jsc: {
|
|
8
|
-
target: "es2019",
|
|
9
|
-
},
|
|
10
|
-
},
|
|
11
|
-
],
|
|
12
|
-
},
|
|
13
|
-
coverageProvider: "v8",
|
|
14
|
-
testMatch: ["/**/src/**/*.spec.(ts|js)"],
|
|
15
|
-
testPathIgnorePatterns: ["/node_modules/", "/dist/"],
|
|
16
|
-
testEnvironment: "node",
|
|
17
|
-
};
|
package/prettierrc.json
DELETED
package/src/ConfigBase.spec.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import * as fse from "fs-extra";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import * as os from "os";
|
|
4
|
-
|
|
5
|
-
jest.mock("uuid", () => ({
|
|
6
|
-
v4: () => "mock-uuid-1234",
|
|
7
|
-
}));
|
|
8
|
-
|
|
9
|
-
import { ConfigBase } from "./ConfigBase";
|
|
10
|
-
|
|
11
|
-
/** Concrete subclass for testing. */
|
|
12
|
-
class TestConfig extends ConfigBase {
|
|
13
|
-
public MY_SETTING = "default_value";
|
|
14
|
-
public SECRET_KEY = "secret_default";
|
|
15
|
-
|
|
16
|
-
constructor(configFile?: string) {
|
|
17
|
-
super("test-service", configFile);
|
|
18
|
-
this.addConfigField({ field: "MY_SETTING" });
|
|
19
|
-
this.addConfigField({ field: "SECRET_KEY", sensitive: true });
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe("ConfigBase", () => {
|
|
24
|
-
let tmpDir: string;
|
|
25
|
-
let configPath: string;
|
|
26
|
-
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
tmpDir = fse.mkdtempSync(path.join(os.tmpdir(), "config-test-"));
|
|
29
|
-
configPath = path.join(tmpDir, "config.json");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
fse.removeSync(tmpDir);
|
|
34
|
-
// Clean up env vars that tests may have set
|
|
35
|
-
delete process.env.MY_SETTING;
|
|
36
|
-
delete process.env.SECRET_KEY;
|
|
37
|
-
delete process.env.LOG_LEVEL;
|
|
38
|
-
delete process.env.DATA_DIR;
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("should initialise with default values", () => {
|
|
42
|
-
const config = new TestConfig(configPath);
|
|
43
|
-
expect(config.SERVICE_ID).toBe("test-service");
|
|
44
|
-
expect(config.MY_SETTING).toBe("default_value");
|
|
45
|
-
expect(config.SECRET_KEY).toBe("secret_default");
|
|
46
|
-
expect(config.LOG_LEVEL).toBe("info");
|
|
47
|
-
expect(config.DATABASE_TYPE).toBe("sqlite");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("should load values from config.json", async () => {
|
|
51
|
-
fse.writeJsonSync(configPath, {
|
|
52
|
-
MY_SETTING: "from_file",
|
|
53
|
-
LOG_LEVEL: "debug",
|
|
54
|
-
});
|
|
55
|
-
const config = new TestConfig(configPath);
|
|
56
|
-
await config.reload();
|
|
57
|
-
expect(config.MY_SETTING).toBe("from_file");
|
|
58
|
-
expect(config.LOG_LEVEL).toBe("debug");
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("should prefer environment variables over config.json", async () => {
|
|
62
|
-
fse.writeJsonSync(configPath, {
|
|
63
|
-
MY_SETTING: "from_file",
|
|
64
|
-
});
|
|
65
|
-
process.env.MY_SETTING = "from_env";
|
|
66
|
-
const config = new TestConfig(configPath);
|
|
67
|
-
await config.reload();
|
|
68
|
-
expect(config.MY_SETTING).toBe("from_env");
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("should mask sensitive fields in log output", async () => {
|
|
72
|
-
const logs: string[] = [];
|
|
73
|
-
const config = new TestConfig(configPath);
|
|
74
|
-
await config.reload((msg) => logs.push(msg));
|
|
75
|
-
const secretLog = logs.find((l) => l.includes("SECRET_KEY"));
|
|
76
|
-
expect(secretLog).toContain("********************");
|
|
77
|
-
expect(secretLog).not.toContain("secret_default");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("should not mask non-sensitive fields in log output", async () => {
|
|
81
|
-
const logs: string[] = [];
|
|
82
|
-
const config = new TestConfig(configPath);
|
|
83
|
-
await config.reload((msg) => logs.push(msg));
|
|
84
|
-
const settingLog = logs.find((l) => l.includes("MY_SETTING"));
|
|
85
|
-
expect(settingLog).toContain("default_value");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("should survive missing config.json gracefully", async () => {
|
|
89
|
-
const config = new TestConfig("/nonexistent/path/config.json");
|
|
90
|
-
await config.reload();
|
|
91
|
-
// Should keep defaults
|
|
92
|
-
expect(config.MY_SETTING).toBe("default_value");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("should register additional fields via addConfigField", async () => {
|
|
96
|
-
fse.writeJsonSync(configPath, { MY_SETTING: "updated" });
|
|
97
|
-
const config = new TestConfig(configPath);
|
|
98
|
-
await config.reload();
|
|
99
|
-
expect(config.MY_SETTING).toBe("updated");
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("should handle DATABASE_TYPE field", async () => {
|
|
103
|
-
fse.writeJsonSync(configPath, { DATABASE_TYPE: "postgres" });
|
|
104
|
-
const config = new TestConfig(configPath);
|
|
105
|
-
await config.reload();
|
|
106
|
-
expect(config.DATABASE_TYPE).toBe("postgres");
|
|
107
|
-
});
|
|
108
|
-
});
|