Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@
# enabled here so dependency updates stay visible, and so the two tools can be
# compared side by side before we settle on one. Expect duplicate PRs until then
# -- that is intentional, not a misconfiguration.
#
# Node pin (issue #126): Dependabot has NO equivalent of Renovate's
# `constraintsFiltering`. Its ignore/allow/versioning-strategy/groups levers all
# operate on semver update *type*, never on runtime compatibility, so it cannot be
# told "only propose updates whose engines.node overlaps ours".
#
# Two mitigations, since it stays enabled for coverage:
# 1. `@types/node` majors are ignored below, so it cannot re-propose the
# typings-ahead-of-runtime drift that issue #126 exists to fix.
# 2. Everything else relies on CI: the `node-pin` job fails on declaration
# drift, and npm's `engine-strict` (server/.npmrc) fails the install itself
# on a wrong runtime. Treat any Dependabot npm PR as needing an engines glance.
version: 2

updates:
Expand All @@ -20,6 +32,10 @@ updates:
update-types:
- "minor"
- "patch"
ignore:
# Node pin (issue #126): @types/node tracks the runtime major, never leads it.
- dependency-name: "@types/node"
update-types: ["version-update:semver-major"]

- package-ecosystem: "github-actions"
directory: "/"
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/build-server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ jobs:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Node.js 22
# The runtime resolved here is the one bundled into the release artifact
# below, so .nvmrc pins what ships to users, not just what CI builds with.
- name: Setup Node.js (pinned by .nvmrc)
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 22
node-version-file: .nvmrc

- name: Install dependencies
working-directory: server
Expand Down
38 changes: 34 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ on:
pull_request:
branches: [main]

# Least privilege. Declared at workflow level rather than on one job: every job here
# only checks out and runs npm, and ci.yml was the sole workflow without a permissions
# block, so all four jobs were inheriting whatever the repo/org default grants.
permissions:
contents: read

jobs:
typecheck:
runs-on: ubuntu-latest
Expand All @@ -12,15 +18,39 @@ jobs:

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 22
node-version-file: .nvmrc

- name: Install dependencies
working-directory: server
run: npm ci

- name: Type-check
working-directory: server
run: npx tsc --noEmit
run: npm run typecheck

- name: Type-check scripts
working-directory: server
run: npm run typecheck:scripts

node-pin:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: .nvmrc

- name: Install dependencies
working-directory: server
run: npm ci

# `npm ci` above already rejects a wrong runtime via engine-strict. This
# catches the other half: declarations that are each valid but have drifted
# apart -- the @types/node-ahead-of-runtime shape from issue #126.
- name: Check Node pin
working-directory: server
run: npm run check:node-pin
Comment thread
coderabbitai[bot] marked this conversation as resolved.

test:
runs-on: ubuntu-latest
Expand All @@ -29,7 +59,7 @@ jobs:

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 22
node-version-file: .nvmrc

- name: Install dependencies
working-directory: server
Expand All @@ -46,7 +76,7 @@ jobs:

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 22
node-version-file: .nvmrc

- name: Install dependencies
working-directory: server
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dependency-health.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 22
node-version-file: .nvmrc
cache: npm
cache-dependency-path: server/package-lock.json

Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
20 changes: 16 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ DMXr bridges DMX lighting fixtures into SignalRGB as first-class canvas devices.

```
DMXr/
├── .nvmrc # Node major (24 LTS) -- single source of truth, read by CI + version managers
├── DMXr.js # SignalRGB plugin (UDP/HTTP color transport)
├── DMXr.qml # SignalRGB settings panel UI
├── docs/images/ # SVG logos, fixture icons
└── server/
├── .npmrc # engine-strict=true -- a wrong Node major fails `npm ci`
├── src/ # All server TypeScript
│ ├── bootstrap/ # Startup orchestration (DMX, library, shutdown)
│ ├── config/ # Settings, remap-preset, server-config stores
│ ├── config/ # Settings, remap-preset, server-config stores, Node-pin check
│ ├── dmx/ # Universe manager, dispatcher, connection pool, monitor, driver factory
│ ├── fixtures/ # Fixture/group/user-fixture stores, color pipeline, channel mapper
│ ├── libraries/ # Library registry (OFL + user fixtures)
Expand All @@ -31,6 +33,7 @@ DMXr/
│ ├── ui/ # Frontend helpers (channel labels, CSS theming, OFL conversion)
│ ├── ui-tests/ # Playwright E2E tests (grid, CRUD, settings, multi-select)
│ └── utils/ # Formatting, validation helpers
├── scripts/ # Dev/ops entry points (service installers, check-node-pin)
├── public/ # Alpine.js web UI (no build step)
│ ├── js/ # app.js + 31 mixin files
│ └── css/ # Feature-scoped CSS files
Expand Down Expand Up @@ -66,11 +69,20 @@ Browser (http://localhost:8080) SignalRGB Plugin (DMXr.js)

## Development

**Node 24 LTS is required and enforced** (issue #126). `.nvmrc` is the single source of
truth; `server/.npmrc` sets `engine-strict=true`, so `npm ci` **fails** on any other major
rather than warning. Use a version manager that reads `.nvmrc` (`fnm use` / `nvm use` /
`mise install`). Never bump the Node major as a routine dependency PR -- it is one
coordinated change across `.nvmrc`, `engines.node`, `@types/node`, CI, and the runtime that
`build-server.yml` bundles into the release artifact.

```bash
cd server
npm test # vitest run (tests co-located: *.test.ts next to source)
npx tsc --noEmit # type check (strict mode) -- the clean-check; no separate ESLint
npm run build # tsc -> dist/
npm test # vitest run (tests co-located: *.test.ts next to source)
npm run typecheck # tsc --noEmit (strict mode) -- the clean-check; no separate ESLint
npm run typecheck:scripts # same strict flags over scripts/ (outside the build's rootDir)
npm run check:node-pin # assert every Node declaration names the same major
npm run build # tsc -> dist/
```

## Key Conventions
Expand Down
22 changes: 22 additions & 0 deletions renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,28 @@
{
"matchManagers": ["npm"],
"groupName": "npm-server"
},
{
"description": "Node pin (issue #126): only propose npm releases whose own engines.node overlaps ours. The constraint is auto-detected from engines.node and .nvmrc -- deliberately NOT declared in a `constraints` block, because Renovate treats a manually-set constraint as fixed and will never offer to bump it, forking the source of truth. Scoped to runtime dependencies per Renovate's guidance that strict filtering across devDependencies filters far more than most users expect.",
"matchManagers": ["npm"],
"matchDepTypes": ["dependencies"],
"constraintsFiltering": "strict"
},
{
"description": "Node pin (issue #126): never propose a Node MAJOR bump. Moving LTS majors touches .nvmrc, engines.node, @types/node, CI and the runtime bundled into the release artifact -- a coordinated decision, not a routine dependency PR. Minor/patch within the pinned major stays enabled so security patches still flow.",
"matchDepNames": ["node"],
"matchUpdateTypes": ["major"],
"enabled": false
},
{
"description": "Node pin (issue #126): keep .nvmrc and any workflow node-version on the pinned major. Bounded rather than disabled so 24.x patches still arrive.",
"matchDatasources": ["node-version"],
"allowedVersions": "<25.0.0"
},
{
"description": "Node pin (issue #126): @types/node must track the runtime major, never lead it. A typings-only bump past the runtime lets tsc accept APIs that do not exist at execution -- this repo was sitting at @types/node 25 against a Node 22 CI, and Node 25 is EOL and never was an LTS.",
"matchPackageNames": ["@types/node"],
"allowedVersions": "<25.0.0"
}
]
}
1 change: 1 addition & 0 deletions server/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
engine-strict=true
33 changes: 21 additions & 12 deletions server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
"test:ui:watch": "vitest --config vitest.config.ui.ts",
"test:ui:update": "UPDATE_BASELINES=1 vitest run --config vitest.config.ui.ts",
"test:contract": "vitest run --config vitest.config.contract.ts",
"test:e2e": "playwright test"
"test:e2e": "playwright test",
"typecheck": "tsc --noEmit",
"typecheck:scripts": "tsc -p tsconfig.scripts.json",
"check:node-pin": "tsx scripts/check-node-pin.ts"
},
"dependencies": {
"@fastify/cors": "^11.2.0",
Expand All @@ -30,19 +33,21 @@
"devDependencies": {
"@playwright/test": "^1.60.0",
"@types/better-sqlite3": "^7.6.13",
"@types/node": "^25.7.0",
"@types/node": "^24.13.3",
"@types/pngjs": "^6.0.5",
"@types/semver": "^7.7.1",
"@vitest/coverage-v8": "^4.1.6",
"fast-check": "^4.8.0",
"pixelmatch": "^7.1.0",
"pngjs": "^7.0.0",
"puppeteer": "^24.43.1",
"semver": "^7.8.5",
"tsx": "^4.21.0",
"typescript": "^6.0.2",
"vitest": "^4.1.3",
"zod": "^4.4.3"
},
"engines": {
"node": ">=18.0.0"
"node": ">=24 <25"
}
}
Loading
Loading