From b4f3a657d509f1867694bfc0c752fd859b66cb93 Mon Sep 17 00:00:00 2001 From: DC Date: Tue, 17 Feb 2026 16:10:20 -0700 Subject: [PATCH 1/2] docs: add production runbook w/ instructions for using GCP cloud scheduler to trigger github actions workflow to avoid job going stale after 60d. --- README.md | 4 + docs/production-runbook.md | 164 ++++++++++++++++++++++ scripts/setup_cloud_scheduler_dispatch.sh | 77 ++++++++++ 3 files changed, 245 insertions(+) create mode 100644 docs/production-runbook.md create mode 100755 scripts/setup_cloud_scheduler_dispatch.sh diff --git a/README.md b/README.md index 9583998..ce489e7 100644 --- a/README.md +++ b/README.md @@ -81,3 +81,7 @@ Synapse is configured via environment variables. - `npm run discord:debug`: Validate Discord connectivity. - `npm run discourse:debug`: Validate Discourse connectivity. - `npm run models:list`: List available Gemini models. + +## Operations + +- Production runbook: [`docs/production-runbook.md`](docs/production-runbook.md) diff --git a/docs/production-runbook.md b/docs/production-runbook.md new file mode 100644 index 0000000..289d18e --- /dev/null +++ b/docs/production-runbook.md @@ -0,0 +1,164 @@ +# Synapse Production Runbook + +This runbook defines how to run Synapse in production with **GitHub Actions as runtime** and **Google Cloud Scheduler as the external trigger**. + +## 1) Production Architecture (No Hosted App Container) + +- Runtime: GitHub Actions workflow [`.github/workflows/daily-digest.yml`](../.github/workflows/daily-digest.yml) +- Trigger: Cloud Scheduler HTTP job calls GitHub `workflow_dispatch` API +- Code path: `npm ci` -> `npm run build` -> `npm start` +- Secrets/config: GitHub repository Secrets + Variables + +Why this setup: +- Avoids hosting a persistent service/container for scheduling. +- Prevents production halts caused by GitHub scheduled workflows being auto-disabled after long inactivity. + +## 2) Trigger Strategy + +Primary trigger: +- Cloud Scheduler invokes `workflow_dispatch` daily. + +Fallback trigger: +- Keep the existing `schedule` block in the workflow as a best-effort backup. +- Keep `workflow_dispatch` enabled for manual recovery. + +## 3) Prerequisites + +- GCP project with Cloud Scheduler API enabled. +- GitHub fine-grained PAT with minimum required permissions for Actions workflow dispatch on this repo. +- Workflow file name: `daily-digest.yml`. + +## 4) Create the Cloud Scheduler Job + +Fast path: +- Use [`scripts/setup_cloud_scheduler_dispatch.sh`](../scripts/setup_cloud_scheduler_dispatch.sh) to create/update the scheduler job with environment-variable placeholders. + +Endpoint: +- `POST https://api.github.com/repos///actions/workflows/daily-digest.yml/dispatches` + +Headers: +- `Accept: application/vnd.github+json` +- `Authorization: Bearer ` +- `X-GitHub-Api-Version: 2022-11-28` +- `Content-Type: application/json` + +Body: +```json +{"ref":"main"} +``` + +Example command: +```bash +gcloud scheduler jobs create http synapse-daily-dispatch \ + --location=us-central1 \ + --schedule="0 13 * * *" \ + --time-zone="Etc/UTC" \ + --uri="https://api.github.com/repos///actions/workflows/daily-digest.yml/dispatches" \ + --http-method=POST \ + --headers="Accept=application/vnd.github+json,X-GitHub-Api-Version=2022-11-28,Content-Type=application/json,Authorization=Bearer " \ + --message-body='{"ref":"main"}' +``` + +Notes: +- Prefer provisioning through IaC so token rotation and changes are auditable. +- PAT should be rotated on a fixed cadence. + +## 5) GitHub Repository Configuration + +Required workflow secrets (minimum): +- `DISCORD_TOKEN` +- `DISCORD_CHANNELS` +- `SLACK_BOT_TOKEN` +- `SLACK_CHANNEL_ID` +- `GEMINI_API_KEY` +- `DISCOURSE_API_KEY` (if Discourse enabled) +- `DISCOURSE_API_USERNAME` (if Discourse enabled) + +Required workflow variables (minimum): +- `DRY_RUN` (`false` in production) +- `LOG_LEVEL` (`info` default) +- `ENABLE_DISCORD` +- `ENABLE_DISCOURSE` +- `DISCOURSE_BASE_URL` (if Discourse enabled) +- `GEMINI_MODEL` +- `MAX_SUMMARY_TOKENS` +- `DIGEST_WINDOW_HOURS` +- `MIN_MESSAGE_LENGTH` +- `EXCLUDE_COMMANDS` +- `EXCLUDE_LINK_ONLY` + +## 6) Operations: Daily Checks + +Check these once per day: +- Cloud Scheduler job execution status is successful. +- A corresponding successful run exists in the `Daily Digest` workflow. +- Slack digest arrived in the expected channel. + +## 7) Alerting Baseline + +Set up two alerts: +- **Trigger failure alert**: Cloud Scheduler job failed. +- **Digest missing alert**: no successful `Daily Digest` run or no Slack post within expected window. + +Target response objective: +- Detect failure within 30 minutes of scheduled run. + +## 8) Incident Playbook + +### A) Scheduler fired, workflow did not start + +Likely causes: +- Invalid/expired GitHub PAT. +- Repository/workflow path changed. + +Actions: +1. Validate Cloud Scheduler HTTP response code and payload. +2. Validate PAT permissions and expiration. +3. Manually run `workflow_dispatch` from GitHub UI. +4. Rotate PAT and update Scheduler headers. + +### B) Workflow started but failed + +Likely causes: +- Invalid API credentials. +- Upstream API rate limits/outage (Discord/Discourse/Gemini/Slack). +- Config drift in repo variables. + +Actions: +1. Inspect failing step in Actions logs. +2. Validate secrets/variables against [`.env.example`](../.env.example). +3. Re-run the workflow once after correction. +4. If still failing, set temporary `DRY_RUN=true` to validate end-to-end flow safely. + +### C) Workflow succeeded but no Slack digest observed + +Likely causes: +- Slack channel ID/token mismatch. +- Filters/window produced no qualifying messages. + +Actions: +1. Inspect final posting logs in workflow. +2. Confirm `SLACK_CHANNEL_ID`, `DIGEST_WINDOW_HOURS`, and filter vars. +3. Run one manual dispatch with temporary broader window for diagnosis. + +## 9) Recovery Procedure + +If a daily run is missed: +1. Resolve root cause. +2. Manually dispatch the workflow (`workflow_dispatch`). +3. Confirm Slack post delivery. +4. Document incident summary and corrective action. + +## 10) Security & Maintenance + +- Rotate GitHub PAT on a fixed schedule. +- Keep PAT scope minimal. +- Restrict who can edit Cloud Scheduler jobs and repository secrets. +- Quarterly review of workflow variables and external API credentials. + +## 11) Change Management + +Before changing schedule/time window: +- Validate with one manual `workflow_dispatch` run. +- Apply scheduler update in non-peak hours. +- Confirm next scheduled invocation succeeds. diff --git a/scripts/setup_cloud_scheduler_dispatch.sh b/scripts/setup_cloud_scheduler_dispatch.sh new file mode 100755 index 0000000..f9cc911 --- /dev/null +++ b/scripts/setup_cloud_scheduler_dispatch.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Synapse Cloud Scheduler -> GitHub workflow_dispatch bootstrap +# +# Usage: +# export GITHUB_PAT="" +# ./scripts/setup_cloud_scheduler_dispatch.sh +# +# Optional overrides: +# JOB_NAME, GCP_PROJECT_ID, GCP_REGION, SCHEDULE, TIME_ZONE, +# GITHUB_OWNER, GITHUB_REPO, WORKFLOW_FILE, GIT_REF + +JOB_NAME="${JOB_NAME:-synapse-daily-dispatch}" +GCP_PROJECT_ID="${GCP_PROJECT_ID:-REPLACE_WITH_GCP_PROJECT_ID}" +GCP_REGION="${GCP_REGION:-us-central1}" +SCHEDULE="${SCHEDULE:-0 13 * * *}" +TIME_ZONE="${TIME_ZONE:-Etc/UTC}" + +GITHUB_OWNER="${GITHUB_OWNER:-REPLACE_WITH_GITHUB_OWNER}" +GITHUB_REPO="${GITHUB_REPO:-REPLACE_WITH_GITHUB_REPO}" +WORKFLOW_FILE="${WORKFLOW_FILE:-daily-digest.yml}" +GIT_REF="${GIT_REF:-main}" + +if ! command -v gcloud >/dev/null 2>&1; then + echo "Error: gcloud CLI is required but not installed." + exit 1 +fi + +if [[ -z "${GITHUB_PAT:-}" ]]; then + echo "Error: GITHUB_PAT environment variable is required." + echo "Example: export GITHUB_PAT='github_pat_xxx'" + exit 1 +fi + +if [[ "$GCP_PROJECT_ID" == "REPLACE_WITH_GCP_PROJECT_ID" ]] || + [[ "$GITHUB_OWNER" == "REPLACE_WITH_GITHUB_OWNER" ]] || + [[ "$GITHUB_REPO" == "REPLACE_WITH_GITHUB_REPO" ]]; then + echo "Error: Replace placeholder values before running." + exit 1 +fi + +URI="https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/actions/workflows/${WORKFLOW_FILE}/dispatches" +MESSAGE_BODY="{\"ref\":\"${GIT_REF}\"}" + +echo "Configuring gcloud project: ${GCP_PROJECT_ID}" +gcloud config set project "$GCP_PROJECT_ID" >/dev/null + +echo "Ensuring Cloud Scheduler API is enabled" +gcloud services enable cloudscheduler.googleapis.com >/dev/null + +echo "Creating or updating scheduler job: ${JOB_NAME}" +if gcloud scheduler jobs describe "$JOB_NAME" --location "$GCP_REGION" >/dev/null 2>&1; then + gcloud scheduler jobs update http "$JOB_NAME" \ + --location "$GCP_REGION" \ + --schedule "$SCHEDULE" \ + --time-zone "$TIME_ZONE" \ + --uri "$URI" \ + --http-method POST \ + --headers "Accept=application/vnd.github+json,X-GitHub-Api-Version=2022-11-28,Content-Type=application/json,Authorization=Bearer ${GITHUB_PAT}" \ + --message-body "$MESSAGE_BODY" + ACTION="updated" +else + gcloud scheduler jobs create http "$JOB_NAME" \ + --location "$GCP_REGION" \ + --schedule "$SCHEDULE" \ + --time-zone "$TIME_ZONE" \ + --uri "$URI" \ + --http-method POST \ + --headers "Accept=application/vnd.github+json,X-GitHub-Api-Version=2022-11-28,Content-Type=application/json,Authorization=Bearer ${GITHUB_PAT}" \ + --message-body "$MESSAGE_BODY" + ACTION="created" +fi + +echo "Job ${ACTION}: ${JOB_NAME}" +echo "Next step: run a manual test trigger" +echo " gcloud scheduler jobs run ${JOB_NAME} --location ${GCP_REGION}" From a6c174b1e259d4e1b203f7ab38aedc53fd47a8f1 Mon Sep 17 00:00:00 2001 From: DC Date: Tue, 17 Feb 2026 16:18:30 -0700 Subject: [PATCH 2/2] test: add coverage widget --- .github/workflows/ci.yml | 16 ++- README.md | 8 ++ package-lock.json | 294 +++++++++++++++++++++++++++++++++++++++ package.json | 2 + 4 files changed, 319 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09d3c83..d650f13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI - Unit Tests +name: CI - Tests & Coverage on: push: @@ -33,3 +33,17 @@ jobs: - name: Run unit tests run: npm test + + - name: Run coverage (report-only) + run: npm run test:coverage + + - name: Add coverage summary to job output + run: | + node -e "const fs=require('fs');const p='coverage/coverage-summary.json';if(!fs.existsSync(p)){console.log('Coverage summary not found');process.exit(1)};const s=JSON.parse(fs.readFileSync(p,'utf8')).total;const pct=(k)=>Number(s[k]?.pct??0).toFixed(2);const lines=['## Coverage Summary (report-only)','',`- Lines: ${pct('lines')}%`,`- Statements: ${pct('statements')}%`,`- Functions: ${pct('functions')}%`,`- Branches: ${pct('branches')}%`,'',`Generated by \`npm run test:coverage\`.`];fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY,lines.join('\n')+'\n');" + + - name: Upload coverage artifacts + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: coverage/ + if-no-files-found: error diff --git a/README.md b/README.md index ce489e7..7715c91 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Synapse +![Coverage](https://github.com/alchemydc/synapse/actions/workflows/ci.yml/badge.svg) + Synapse is an intelligent community digest bot designed to aggregate, summarize, and distribute conversations from your community platforms. It helps teams stay on top of important discussions without getting lost in the noise. ## Features @@ -78,10 +80,16 @@ Synapse is configured via environment variables. - `npm run build`: Compile TypeScript to JavaScript. - `npm start`: Run the production build. - `npm test`: Run unit tests. +- `npm run test:coverage`: Generate report-only coverage output. - `npm run discord:debug`: Validate Discord connectivity. - `npm run discourse:debug`: Validate Discourse connectivity. - `npm run models:list`: List available Gemini models. +## Coverage + +- Coverage is generated natively in GitHub Actions (`CI - Tests & Coverage`) in report-only mode. +- Open the latest CI run and download the `coverage-report` artifact for `lcov` and summary outputs. + ## Operations - Production runbook: [`docs/production-runbook.md`](docs/production-runbook.md) diff --git a/package-lock.json b/package-lock.json index d6fa1b0..3aa7f0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "devDependencies": { "@types/node": "^20.19.16", "@types/node-fetch": "^3.0.3", + "@vitest/coverage-v8": "^1.6.1", "ts-node-dev": "^2.0.0", "typescript": "^5.4.5", "vitest": "^1.6.0" @@ -91,6 +92,88 @@ "zod": "^3.25.76 || ^4.1.8" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ampproject/remapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -631,6 +714,16 @@ "node": ">=18.0.0" } }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -644,6 +737,28 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -1160,6 +1275,34 @@ "node": ">= 20" } }, + "node_modules/@vitest/coverage-v8": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.1.tgz", + "integrity": "sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.4", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.4", + "istanbul-reports": "^3.1.6", + "magic-string": "^0.30.5", + "magicast": "^0.3.3", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "test-exclude": "^6.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "1.6.1" + } + }, "node_modules/@vitest/expect": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", @@ -2145,6 +2288,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -2184,6 +2337,13 @@ "node": ">= 0.4" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/human-signals": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", @@ -2332,6 +2492,71 @@ "dev": true, "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/js-tokens": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", @@ -2400,6 +2625,34 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -3075,6 +3328,19 @@ "node": ">=10" } }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3236,6 +3502,19 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -3249,6 +3528,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/thread-stream": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", diff --git a/package.json b/package.json index 867d1e3..4ffce43 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "prestart": "npm run build", "start": "node dist/main.js", "test": "vitest run", + "test:coverage": "vitest run --coverage --coverage.provider=v8 --coverage.reporter=text-summary --coverage.reporter=json-summary --coverage.reporter=lcov", "test:watch": "vitest", "lint": "echo \"TODO: add eslint\" && exit 0", "discord:debug": "ts-node-dev --transpile-only src/tools/discord_debug.ts", @@ -40,6 +41,7 @@ "devDependencies": { "@types/node": "^20.19.16", "@types/node-fetch": "^3.0.3", + "@vitest/coverage-v8": "^1.6.1", "ts-node-dev": "^2.0.0", "typescript": "^5.4.5", "vitest": "^1.6.0"