Compare commits
2
Commits
fef7e07b49
...
a2e7d9dbaf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2e7d9dbaf | ||
|
|
f94b164190 |
+2
-1
@@ -10,9 +10,10 @@
|
||||
## P0 — Must Fix (blocks public release)
|
||||
|
||||
### DC-020: Restore deleted license-keygen.js — production container in crash-restart loop
|
||||
- **status:** in-progress
|
||||
- **status:** done
|
||||
- **owner:** hermes
|
||||
- **details:** The `refactor(desloppify)` commit (a2e6566) deleted `dashcaddy-api/license-keygen.js` believing it was "stale dev-root noise." It is NOT — it is a required production module. `src/managers/license-manager.js:17` does `require('./license-keygen')` and imports `verifyCode`, `parseCode`, `VALID_DURATIONS` from it. After deletion, `require('./src/app')` throws `MODULE_NOT_FOUND: Cannot find module './license-keygen'` and the **production `dashcaddy-api` Docker container is in a crash-restart loop** (verified: `docker ps` shows `Restarting (1)`, `docker logs` shows the MODULE_NOT_FOUND stack from `/app/src/app.js` → `/app/server.js`). The 1036-test Jest suite never caught this because the only "app-loading" tests read `src/app.js` as a *string* (via `path.join(...,'src','app.js')`), they never execute `require()` on it. Fix: restore the file from git history to `src/managers/license-keygen.js` (the path the post-DC-005 require resolves to) and add a real startup smoke test that executes `require()` on the app module so this class of bug is caught.
|
||||
- **result:** Done across two sessions. (1) Restored `license-keygen.js` from git history. (2) Fixed every `require('../src/...')` → `require('./src/...')` in `server.js` — from the production entry point `/app/server.js`, `../src/` resolves to `/src/` (outside the app) instead of `/app/src/`. (3) **Session 2 (this commit f94b164): found and fixed the LAST one the sweep missed** — `server.js:73` still had `require('./state-manager')` which resolves to `/app/state-manager.js`, a file that does NOT exist (module lives at `src/managers/state-manager.js`). Unlike the optional modules below it, this require is bare (no try/catch), so MODULE_NOT_FOUND throws out of the top-level startup IIFE and crash-loops the container — the exact same failure mode. Fixed to `./src/managers/state-manager` (matches line 146). (4) Hardened the regression guard `app-startup-smoke.test.js`: added a static check that EVERY relative `require()` in `server.js` resolves to a real file on disk (server.js can't be require()'d at test time because its IIFE binds port 3001 + starts interval modules). This test would have failed on the original `./state-manager` line, so the whole entry-point path-bug class is now caught. 1067/1067 tests pass, zero new ESLint warnings.
|
||||
|
||||
### DC-012: Add Kubernetes-style /healthz + /readyz probe aliases + document for fresh users
|
||||
- **status:** done
|
||||
|
||||
@@ -5,25 +5,58 @@
|
||||
* The `refactor(desloppify)` commit deleted `license-keygen.js` thinking it was
|
||||
* stale dev-root noise. It is actually required by `src/managers/license-manager.js`
|
||||
* (`require('./license-keygen')`). The deletion put the production `dashcaddy-api`
|
||||
* container in a crash-restart loop (MODULE_NOT_FOUND from /app/src/app.js). The
|
||||
* full Jest suite (1036 tests) passed anyway because NO test ever executed
|
||||
* `require()` on the real app module — every "app" test read src/app.js as a
|
||||
* string or rebuilt a minimal Express app with copied handlers.
|
||||
* container in a crash-restart loop (MODULE_NOT_FOUND from /app/src/app.js). A second,
|
||||
* masked bug had the same effect from the entry point: server.js used `require('./state-manager')`
|
||||
* which from /app/server.js resolves to /app/state-manager.js (does not exist) instead of
|
||||
* `./src/managers/state-manager`. The full Jest suite passed anyway because NO test ever
|
||||
* executed the real production require graph — every "app" test read src/app.js as a
|
||||
* string or rebuilt a minimal Express app with copied handlers, and server.js was never
|
||||
* loaded at all (requiring it starts the HTTP server + timers, which would leak workers).
|
||||
*
|
||||
* This test closes that gap: it executes the real production require graph and
|
||||
* asserts it resolves without throwing. Any future deletion of a required module,
|
||||
* or any broken relative require, will fail here instead of crashing the container
|
||||
* on the next deploy.
|
||||
* This test closes that gap two ways:
|
||||
* 1. Execute the real src/app.js require graph (catches deleted-module regressions).
|
||||
* 2. Statically verify EVERY relative require in server.js resolves to a real file
|
||||
* (catches entry-point path bugs like the ./state-manager regression, without starting
|
||||
* the server). server.js cannot be require()'d directly because its top-level IIFE
|
||||
* binds port 3001 and starts interval-based feature modules.
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
|
||||
describe('app startup require-graph smoke', () => {
|
||||
it('src/app.js and its entire require graph load without throwing', () => {
|
||||
expect(() => require(path.join(__dirname, '..', 'src', 'app'))).not.toThrow();
|
||||
expect(() => require(path.join(ROOT, 'src', 'app'))).not.toThrow();
|
||||
});
|
||||
|
||||
it('createApp is exported as a function', () => {
|
||||
const mod = require(path.join(__dirname, '..', 'src', 'app'));
|
||||
const mod = require(path.join(ROOT, 'src', 'app'));
|
||||
expect(typeof mod.createApp).toBe('function');
|
||||
});
|
||||
|
||||
it('every relative require() in server.js resolves to a real module', () => {
|
||||
// server.js is the production entry point (Dockerfile CMD ["node","server.js"]).
|
||||
// We statically check its require graph because require()-ing it at test time
|
||||
// starts the HTTP server and interval-based modules (would leak the worker).
|
||||
const serverFile = path.join(ROOT, 'server.js');
|
||||
const src = fs.readFileSync(serverFile, 'utf8')
|
||||
// strip block + line comments so example requires in docstrings don't trip us up
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '')
|
||||
.replace(/(^|[^:\\])\/\/.*$/gm, '$1');
|
||||
|
||||
const requireRe = /require\(\s*['"]([^'"]+)['"]\s*\)/g;
|
||||
const unresolved = [];
|
||||
let match;
|
||||
while ((match = requireRe.exec(src))) {
|
||||
const spec = match[1];
|
||||
if (!spec.startsWith('.')) continue; // only relative specs are path-bug-prone
|
||||
const base = path.resolve(path.dirname(serverFile), spec);
|
||||
const ok = fs.existsSync(base + '.js') ||
|
||||
fs.existsSync(base + '.json') ||
|
||||
fs.existsSync(path.join(base, 'index.js'));
|
||||
if (!ok) unresolved.push(spec);
|
||||
}
|
||||
expect(unresolved).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,7 +70,7 @@ process.on('uncaughtException', (error) => {
|
||||
const portLockManager = require('./src/managers/port-lock-manager');
|
||||
|
||||
// Create servicesStateManager early — needed by workflow engine init
|
||||
const StateManager = require('./state-manager');
|
||||
const StateManager = require('./src/managers/state-manager');
|
||||
const servicesStateManager = new StateManager(SERVICES_FILE);
|
||||
|
||||
// Optional modules
|
||||
|
||||
Reference in New Issue
Block a user