Skip to content

Modernize: Node 24, Bolt 4, googleapis/Sentry/cron upgrades + GHCR/Coolify deploy - #63

Open
ddbruce wants to merge 1 commit into
masterfrom
modernization
Open

Modernize: Node 24, Bolt 4, googleapis/Sentry/cron upgrades + GHCR/Coolify deploy#63
ddbruce wants to merge 1 commit into
masterfrom
modernization

Conversation

@ddbruce

@ddbruce ddbruce commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

Behavior-preserving modernization of chorebot (the heaviest of the set — EOL Node 12 + many major deps). Same behavior: the chores cron reads today's chores from the Google Sheet and posts them (or "No chores tonight!") to Slack with Done! buttons; clicking Done crosses off the chore and tags the user; a second cron POSTs the chore list to the whiteboard server. Verified: 9 unit tests pass; boots on the new stack with /health → 200 and both crons scheduling without error; amd64 image builds with 0 npm vulnerabilities.

Changes

  • Runtime/SDK: Node 12 (EOL) → node:24-alpine (npm ci); @slack/bolt 1.3 → 4.7 (await ack()/next(), custom HTTPReceiver + GET /health; /slack/events and signing verification unchanged, so no Slack app config change); dotenv 8 → 17.
  • Google: googleapis 43 → 173. Switched to the stable object-form google.auth.JWT constructor (same service-account auth, same Sheets v4 read).
  • Sentry / cron: @sentry/node 5 → 10 (we only call Sentry.init — API unchanged; going to v10 specifically was needed to clear transitive OpenTelemetry/uuid advisories); node-cron 2 → 4 (schedule(expr, fn) unchanged). Whiteboard axios.post → native fetch (same JSON body).
  • Kept moment-timezone (bumped within 0.5.x) to avoid changing any date/timezone behavior — Luxon migration is a follow-up.
  • Bug fixes (the "practices not followed" kind):
    • getTodaysChores no longer crashes when the sheet read returns null/undefined; a Sheets error now propagates and is caught/logged by runChores instead of killing the process (same observable result — nothing posted on error).
    • randomPhrase no longer mutates the shared phrases.request array (it pushed the seasonal phrase on every in-season call → unbounded array growth + skewed odds). Now copies before appending.
    • awaited the previously fire-and-forget chat.update / notify posts.
  • Testability/CI: pure logic extracted into utilities/blocks.js + utilities/chores.js with Jest tests; ESLint 6 (EOL, and it was a runtime dep) → ESLint 9 flat config (dev); replaced the dead SSH-password deploy workflows (main.yml/deploy-dev.yml, appleboy/ssh-action@master) with GitHub Actions; added the missing start script and scripts/deploy.sh + config.example.sh.

Deploy notes (no functional change)

  • The Google service-account key (keys/sheets-api.json) is still mounted at runtime, not baked into the image (.dockerignore excludes keys/). Container still runs as root so the mounted key is readable regardless of volume ownership (matches prior behavior).
  • ⚠️ Needs a staging check: the Google Sheets read can't be exercised here without live service-account creds. The API surface used (object-form JWT + spreadsheets.values.get) is stable across these googleapis versions and boots clean, but please confirm one real chores run in staging.

Recommended follow-ups (NOT done)

  • Run non-root + move the service-account key off disk (Workload Identity / injected secret).
  • Replace moment-timezone with Luxon/date-fns.

…+Coolify

Behavior-preserving modernization. Same behavior: on the chores cron it reads
today's chores from the Google Sheet and posts them (or "no chores tonight") to
Slack with Done! buttons; clicking Done crosses off the chore and tags the user;
a second cron POSTs the chore list to the whiteboard server.

Runtime / SDK:
- Node 12 (EOL) -> node:24-alpine (npm ci); @slack/bolt 1.3 -> 4.7 (await
  ack()/next(); custom HTTPReceiver adds GET /health; /slack/events + signing
  verification unchanged); dotenv 8 -> 17.
- googleapis 43 -> 173: switch to the stable object-form google.auth.JWT
  constructor (same service-account auth, same Sheets v4 read).
- @sentry/node 5 -> 10 (init-only usage unchanged); node-cron 2 -> 4
  (schedule(expr, fn) API unchanged). npm audit: 0 vulnerabilities.
- Keep moment-timezone (bumped within 0.5.x) to preserve date/timezone behavior.
- Replace axios (whiteboard POST) with native fetch (same JSON body).

Correctness / robustness:
- Bug fix: getTodaysChores no longer crashes when the sheet read returns
  null/undefined (extracted + guarded filterTodaysChores). A Sheets error now
  propagates and is caught/logged by runChores instead of crashing the process
  (same observable result: nothing posted on error).
- Bug fix: randomPhrase no longer mutates the shared phrases.request array
  (it pushed the seasonal phrase every in-season call -> unbounded growth /
  skewed odds). Now copies before appending.
- await markChoreDone / chat.update and the notify posts.

Testability / tooling:
- Extract pure logic into utilities/blocks.js and utilities/chores.js; export
  phrases for testing. Add Jest tests (9).
- ESLint 6 (EOL, was a runtime dep) -> ESLint 9 flat config (dev), matching the
  repo's original rule set.
- Replace the dead SSH-over-password deploy workflows with GitHub Actions
  (lint + test + amd64 build). Add scripts/deploy.sh + config.example.sh
  (amd64 -> GHCR -> Coolify redeploy); secrets in gitignored scripts/config.sh.
- Add the previously-missing start script.

Verified: 9 unit tests pass; boots on the new stack, /health -> 200, both crons
schedule without error; amd64 image builds with 0 npm vulnerabilities.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant