@codeyam/codeyam-cli 0.1.31 → 0.1.33
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/analyzer-template/.build-info.json +6 -6
- package/analyzer-template/log.txt +3 -3
- package/codeyam-cli/src/utils/__tests__/devServerState.test.js +93 -1
- package/codeyam-cli/src/utils/__tests__/devServerState.test.js.map +1 -1
- package/codeyam-cli/src/utils/devServerState.js +32 -0
- package/codeyam-cli/src/utils/devServerState.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +239 -1
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{editor.entity.(_sha)-DOXe0Qx7.js → editor.entity.(_sha)-DhtVC4aI.js} +23 -23
- package/codeyam-cli/src/webserver/build/client/assets/{manifest-30c44d84.js → manifest-79d0d81a.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{root-CLedrjXQ.js → root-L2V0jea7.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{analysisRunner-CuR5TvUx.js → analysisRunner-QgInFGdU.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{index-D4MWAsqb.js → index-zblh9auj.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{init-JObA4lXD.js → init-DaE0CBjk.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{server-build-i8OXK4oL.js → server-build-CNvgz1cC.js} +221 -133
- package/codeyam-cli/src/webserver/build/server/index.js +1 -1
- package/codeyam-cli/src/webserver/build-info.json +5 -5
- package/codeyam-cli/src/webserver/editorProxy.js +178 -16
- package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"buildTimestamp": "2026-04-
|
|
3
|
-
"buildTime":
|
|
4
|
-
"gitCommit": "
|
|
2
|
+
"buildTimestamp": "2026-04-15T16:14:43.677Z",
|
|
3
|
+
"buildTime": 1776269683677,
|
|
4
|
+
"gitCommit": "d00016715e0b175acef32dfd210f767cabcfdd36",
|
|
5
5
|
"nodeVersion": "v20.20.2",
|
|
6
6
|
"contentHash": "c92230c027acb71cab56d2a696876a6a52206bfadd59fbc31a512b00a7ee8826",
|
|
7
|
-
"buildNumber":
|
|
8
|
-
"semanticVersion": "0.1.
|
|
9
|
-
"version": "0.1.
|
|
7
|
+
"buildNumber": 1353,
|
|
8
|
+
"semanticVersion": "0.1.1353",
|
|
9
|
+
"version": "0.1.1353 (2026-04-15T16:14+c92230c)"
|
|
10
10
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
|
-
[4/
|
|
3
|
-
[4/
|
|
2
|
+
[4/15/2026, 4:14:43 PM] > codeyam-combo@1.0.0 mergeDependencies
|
|
3
|
+
[4/15/2026, 4:14:43 PM] > node ./scripts/mergePackageJsonFiles.cjs
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
[4/
|
|
6
|
+
[4/15/2026, 4:14:43 PM] Merged dependencies into root package.json
|
|
7
7
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computeDevServerTransition, } from "../devServerState.js";
|
|
1
|
+
import { computeDevServerTransition, shouldAutoRestartOnError, MAX_AUTO_RESTARTS, AUTO_RESTART_COOLDOWN_MS, } from "../devServerState.js";
|
|
2
2
|
describe('computeDevServerTransition', () => {
|
|
3
3
|
const baseState = {
|
|
4
4
|
url: null,
|
|
@@ -131,4 +131,96 @@ describe('computeDevServerTransition', () => {
|
|
|
131
131
|
});
|
|
132
132
|
});
|
|
133
133
|
});
|
|
134
|
+
describe('shouldAutoRestartOnError', () => {
|
|
135
|
+
const freshState = {
|
|
136
|
+
lastAutoRestartAt: 0,
|
|
137
|
+
autoRestartCount: 0,
|
|
138
|
+
};
|
|
139
|
+
it('should allow restart on first error', () => {
|
|
140
|
+
const now = Date.now();
|
|
141
|
+
const result = shouldAutoRestartOnError(freshState, false, now);
|
|
142
|
+
expect(result.shouldRestart).toBe(true);
|
|
143
|
+
expect(result.nextState.autoRestartCount).toBe(1);
|
|
144
|
+
expect(result.nextState.lastAutoRestartAt).toBe(now);
|
|
145
|
+
});
|
|
146
|
+
it('should allow a second restart after cooldown expires', () => {
|
|
147
|
+
const firstRestartAt = 1000;
|
|
148
|
+
const state = {
|
|
149
|
+
lastAutoRestartAt: firstRestartAt,
|
|
150
|
+
autoRestartCount: 1,
|
|
151
|
+
};
|
|
152
|
+
const now = firstRestartAt + AUTO_RESTART_COOLDOWN_MS + 1;
|
|
153
|
+
const result = shouldAutoRestartOnError(state, false, now);
|
|
154
|
+
expect(result.shouldRestart).toBe(true);
|
|
155
|
+
expect(result.nextState.autoRestartCount).toBe(2);
|
|
156
|
+
});
|
|
157
|
+
it('should block restart when within cooldown window', () => {
|
|
158
|
+
const firstRestartAt = 1000;
|
|
159
|
+
const state = {
|
|
160
|
+
lastAutoRestartAt: firstRestartAt,
|
|
161
|
+
autoRestartCount: 1,
|
|
162
|
+
};
|
|
163
|
+
const now = firstRestartAt + AUTO_RESTART_COOLDOWN_MS - 1;
|
|
164
|
+
const result = shouldAutoRestartOnError(state, false, now);
|
|
165
|
+
expect(result.shouldRestart).toBe(false);
|
|
166
|
+
// State should not change
|
|
167
|
+
expect(result.nextState).toBe(state);
|
|
168
|
+
});
|
|
169
|
+
it('should block restart at exactly the cooldown boundary', () => {
|
|
170
|
+
const firstRestartAt = 1000;
|
|
171
|
+
const state = {
|
|
172
|
+
lastAutoRestartAt: firstRestartAt,
|
|
173
|
+
autoRestartCount: 0,
|
|
174
|
+
};
|
|
175
|
+
const now = firstRestartAt + AUTO_RESTART_COOLDOWN_MS - 1;
|
|
176
|
+
const result = shouldAutoRestartOnError(state, false, now);
|
|
177
|
+
expect(result.shouldRestart).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
it('should block restart when max attempts reached', () => {
|
|
180
|
+
const state = {
|
|
181
|
+
lastAutoRestartAt: 0,
|
|
182
|
+
autoRestartCount: MAX_AUTO_RESTARTS,
|
|
183
|
+
};
|
|
184
|
+
const now = Date.now();
|
|
185
|
+
const result = shouldAutoRestartOnError(state, false, now);
|
|
186
|
+
expect(result.shouldRestart).toBe(false);
|
|
187
|
+
expect(result.nextState).toBe(state);
|
|
188
|
+
});
|
|
189
|
+
it('should block restart when server is already starting', () => {
|
|
190
|
+
const result = shouldAutoRestartOnError(freshState, true, Date.now());
|
|
191
|
+
expect(result.shouldRestart).toBe(false);
|
|
192
|
+
expect(result.nextState).toBe(freshState);
|
|
193
|
+
});
|
|
194
|
+
it('should block restart when starting even if count and cooldown are ok', () => {
|
|
195
|
+
const result = shouldAutoRestartOnError(freshState, true, Date.now());
|
|
196
|
+
expect(result.shouldRestart).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
it('should allow restart after count is reset (simulating successful server run)', () => {
|
|
199
|
+
const state = {
|
|
200
|
+
lastAutoRestartAt: 0,
|
|
201
|
+
autoRestartCount: 0, // reset after successful run
|
|
202
|
+
};
|
|
203
|
+
const result = shouldAutoRestartOnError(state, false, Date.now());
|
|
204
|
+
expect(result.shouldRestart).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
it('should track consecutive failures up to the max', () => {
|
|
207
|
+
let state = freshState;
|
|
208
|
+
const results = [];
|
|
209
|
+
// Simulate MAX_AUTO_RESTARTS + 1 rapid errors (well past cooldown each time)
|
|
210
|
+
for (let i = 0; i <= MAX_AUTO_RESTARTS; i++) {
|
|
211
|
+
const now = (i + 1) * (AUTO_RESTART_COOLDOWN_MS + 1000);
|
|
212
|
+
const result = shouldAutoRestartOnError(state, false, now);
|
|
213
|
+
results.push(result.shouldRestart);
|
|
214
|
+
state = result.nextState;
|
|
215
|
+
}
|
|
216
|
+
// First MAX_AUTO_RESTARTS should succeed, then blocked
|
|
217
|
+
expect(results.slice(0, MAX_AUTO_RESTARTS)).toEqual(Array(MAX_AUTO_RESTARTS).fill(true));
|
|
218
|
+
expect(results[MAX_AUTO_RESTARTS]).toBe(false);
|
|
219
|
+
});
|
|
220
|
+
it('should use exported constants for limits', () => {
|
|
221
|
+
// Sanity check the constants are reasonable
|
|
222
|
+
expect(MAX_AUTO_RESTARTS).toBe(2);
|
|
223
|
+
expect(AUTO_RESTART_COOLDOWN_MS).toBe(30000);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
134
226
|
//# sourceMappingURL=devServerState.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devServerState.test.js","sourceRoot":"","sources":["../../../../../src/utils/__tests__/devServerState.test.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,
|
|
1
|
+
{"version":3,"file":"devServerState.test.js","sourceRoot":"","sources":["../../../../../src/utils/__tests__/devServerState.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -68,4 +68,36 @@ export function computeDevServerTransition(current, response) {
|
|
|
68
68
|
// Unknown status — no changes
|
|
69
69
|
return { ...current, shouldAutoStart: false };
|
|
70
70
|
}
|
|
71
|
+
// ─── Server Error Auto-Restart Logic ────────────────────────────────
|
|
72
|
+
/** Max consecutive auto-restarts before requiring manual intervention. */
|
|
73
|
+
export const MAX_AUTO_RESTARTS = 2;
|
|
74
|
+
/** Minimum interval (ms) between auto-restart attempts. */
|
|
75
|
+
export const AUTO_RESTART_COOLDOWN_MS = 30000;
|
|
76
|
+
/**
|
|
77
|
+
* Decide whether to auto-restart the dev server after an error page
|
|
78
|
+
* is shown in the preview iframe. Pure function for testability.
|
|
79
|
+
*
|
|
80
|
+
* Prevents restart loops by enforcing:
|
|
81
|
+
* - A 30-second cooldown between auto-restarts
|
|
82
|
+
* - A maximum of 2 consecutive auto-restarts (reset when server comes up)
|
|
83
|
+
* - No restart if the server is already starting
|
|
84
|
+
*/
|
|
85
|
+
export function shouldAutoRestartOnError(state, isStarting, now) {
|
|
86
|
+
if (isStarting) {
|
|
87
|
+
return { shouldRestart: false, nextState: state };
|
|
88
|
+
}
|
|
89
|
+
if (now - state.lastAutoRestartAt < AUTO_RESTART_COOLDOWN_MS) {
|
|
90
|
+
return { shouldRestart: false, nextState: state };
|
|
91
|
+
}
|
|
92
|
+
if (state.autoRestartCount >= MAX_AUTO_RESTARTS) {
|
|
93
|
+
return { shouldRestart: false, nextState: state };
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
shouldRestart: true,
|
|
97
|
+
nextState: {
|
|
98
|
+
lastAutoRestartAt: now,
|
|
99
|
+
autoRestartCount: state.autoRestartCount + 1,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
71
103
|
//# sourceMappingURL=devServerState.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devServerState.js","sourceRoot":"","sources":["../../../../src/utils/devServerState.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAuBH;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAAuB,EACvB,QAA8B;IAE9B,IAAI,QAAQ,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO;YACL,GAAG,EAAE,IAAI;YACT,QAAQ,EAAE,IAAI;YACd,UAAU,EAAE,KAAK;YACjB,KAAK,EAAE,QAAQ,CAAC,YAAY,IAAI,oBAAoB;YACpD,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;YAC9C,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,CAAC,GAAG,EAAE,CAAC;QACjB,OAAO;YACL,GAAG,EAAE,QAAQ,CAAC,GAAG;YACjB,QAAQ,EAAE,QAAQ,CAAC,QAAQ,IAAI,IAAI;YACnC,UAAU,EAAE,KAAK;YACjB,KAAK,EAAE,IAAI;YACX,cAAc,EAAE,IAAI;YACpB,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;YAC9C,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACnC,OAAO;YACL,GAAG,OAAO;YACV,UAAU,EAAE,IAAI;YAChB,KAAK,EAAE,IAAI;YACX,cAAc,EAAE,IAAI;YACpB,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAClC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;YAChB,kDAAkD;YAClD,OAAO;gBACL,GAAG,OAAO;gBACV,GAAG,EAAE,IAAI;gBACT,UAAU,EAAE,KAAK;gBACjB,eAAe,EAAE,KAAK;aACvB,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC;YAChC,wDAAwD;YACxD,OAAO;gBACL,GAAG,OAAO;gBACV,kBAAkB,EAAE,IAAI;gBACxB,eAAe,EAAE,IAAI;aACtB,CAAC;QACJ,CAAC;QAED,4CAA4C;QAC5C,OAAO;YACL,GAAG,OAAO;YACV,UAAU,EAAE,KAAK;YACjB,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IAED,8BAA8B;IAC9B,OAAO,EAAE,GAAG,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;AAChD,CAAC"}
|
|
1
|
+
{"version":3,"file":"devServerState.js","sourceRoot":"","sources":["../../../../src/utils/devServerState.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAuBH;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAAuB,EACvB,QAA8B;IAE9B,IAAI,QAAQ,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO;YACL,GAAG,EAAE,IAAI;YACT,QAAQ,EAAE,IAAI;YACd,UAAU,EAAE,KAAK;YACjB,KAAK,EAAE,QAAQ,CAAC,YAAY,IAAI,oBAAoB;YACpD,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;YAC9C,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,CAAC,GAAG,EAAE,CAAC;QACjB,OAAO;YACL,GAAG,EAAE,QAAQ,CAAC,GAAG;YACjB,QAAQ,EAAE,QAAQ,CAAC,QAAQ,IAAI,IAAI;YACnC,UAAU,EAAE,KAAK;YACjB,KAAK,EAAE,IAAI;YACX,cAAc,EAAE,IAAI;YACpB,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;YAC9C,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACnC,OAAO;YACL,GAAG,OAAO;YACV,UAAU,EAAE,IAAI;YAChB,KAAK,EAAE,IAAI;YACX,cAAc,EAAE,IAAI;YACpB,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAClC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;YAChB,kDAAkD;YAClD,OAAO;gBACL,GAAG,OAAO;gBACV,GAAG,EAAE,IAAI;gBACT,UAAU,EAAE,KAAK;gBACjB,eAAe,EAAE,KAAK;aACvB,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC;YAChC,wDAAwD;YACxD,OAAO;gBACL,GAAG,OAAO;gBACV,kBAAkB,EAAE,IAAI;gBACxB,eAAe,EAAE,IAAI;aACtB,CAAC;QACJ,CAAC;QAED,4CAA4C;QAC5C,OAAO;YACL,GAAG,OAAO;YACV,UAAU,EAAE,KAAK;YACjB,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IAED,8BAA8B;IAC9B,OAAO,EAAE,GAAG,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;AAChD,CAAC;AAED,uEAAuE;AAEvE,0EAA0E;AAC1E,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AACnC,2DAA2D;AAC3D,MAAM,CAAC,MAAM,wBAAwB,GAAG,KAAM,CAAC;AAY/C;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CACtC,KAA8B,EAC9B,UAAmB,EACnB,GAAW;IAEX,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IACpD,CAAC;IACD,IAAI,GAAG,GAAG,KAAK,CAAC,iBAAiB,GAAG,wBAAwB,EAAE,CAAC;QAC7D,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IACpD,CAAC;IACD,IAAI,KAAK,CAAC,gBAAgB,IAAI,iBAAiB,EAAE,CAAC;QAChD,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IACpD,CAAC;IAED,OAAO;QACL,aAAa,EAAE,IAAI;QACnB,SAAS,EAAE;YACT,iBAAiB,EAAE,GAAG;YACtB,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,GAAG,CAAC;SAC7C;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
2
|
import zlib from 'zlib';
|
|
3
|
-
import { injectHealthScript, PREVIEW_HEALTH_SCRIPT, getPreviewHealthReport, resetPreviewHealth, } from "../editorProxy.js";
|
|
3
|
+
import { injectHealthScript, PREVIEW_HEALTH_SCRIPT, getPreviewHealthReport, resetPreviewHealth, buildErrorPage, escapeHtml, } from "../editorProxy.js";
|
|
4
4
|
describe('editorProxy', () => {
|
|
5
5
|
describe('normalizeTargetUrl', () => {
|
|
6
6
|
it('should leave localhost as-is (no longer normalizes to 127.0.0.1)', () => {
|
|
@@ -758,5 +758,243 @@ describe('editorProxy', () => {
|
|
|
758
758
|
expect(response.headers.get('cache-control')).toBeNull();
|
|
759
759
|
});
|
|
760
760
|
});
|
|
761
|
+
describe('escapeHtml', () => {
|
|
762
|
+
it('should escape ampersands, angle brackets, and quotes', () => {
|
|
763
|
+
expect(escapeHtml('<script>alert("xss")</script>')).toBe('<script>alert("xss")</script>');
|
|
764
|
+
});
|
|
765
|
+
it('should escape single quotes', () => {
|
|
766
|
+
expect(escapeHtml("it's")).toBe('it's');
|
|
767
|
+
});
|
|
768
|
+
it('should return empty string for empty input', () => {
|
|
769
|
+
expect(escapeHtml('')).toBe('');
|
|
770
|
+
});
|
|
771
|
+
it('should leave safe text unchanged', () => {
|
|
772
|
+
expect(escapeHtml('Hello world 123')).toBe('Hello world 123');
|
|
773
|
+
});
|
|
774
|
+
});
|
|
775
|
+
describe('buildErrorPage', () => {
|
|
776
|
+
it('should generate a complete HTML document', () => {
|
|
777
|
+
const page = buildErrorPage(500, 'Internal Server Error', 'something broke');
|
|
778
|
+
expect(page).toContain('<!DOCTYPE html>');
|
|
779
|
+
expect(page).toContain('<html');
|
|
780
|
+
expect(page).toContain('</html>');
|
|
781
|
+
});
|
|
782
|
+
it('should include the status code and title', () => {
|
|
783
|
+
const page = buildErrorPage(502, 'Dev Server Unreachable', 'details here');
|
|
784
|
+
expect(page).toContain('502');
|
|
785
|
+
expect(page).toContain('Dev Server Unreachable');
|
|
786
|
+
});
|
|
787
|
+
it('should include the error detail text', () => {
|
|
788
|
+
const page = buildErrorPage(500, 'Error', 'Connection refused on port 3000');
|
|
789
|
+
expect(page).toContain('Connection refused on port 3000');
|
|
790
|
+
});
|
|
791
|
+
it('should escape HTML in the detail to prevent XSS', () => {
|
|
792
|
+
const page = buildErrorPage(500, 'Error', '<script>alert("xss")</script>');
|
|
793
|
+
expect(page).not.toContain('<script>alert("xss")</script>');
|
|
794
|
+
expect(page).toContain('<script>alert("xss")</script>');
|
|
795
|
+
});
|
|
796
|
+
it('should include codeyam-server-error postMessage', () => {
|
|
797
|
+
const page = buildErrorPage(500, 'Error', 'detail');
|
|
798
|
+
expect(page).toContain('codeyam-server-error');
|
|
799
|
+
expect(page).toContain('postMessage');
|
|
800
|
+
});
|
|
801
|
+
it('should NOT include codeyam-preview-ready postMessage', () => {
|
|
802
|
+
const page = buildErrorPage(500, 'Error', 'detail');
|
|
803
|
+
expect(page).not.toContain('codeyam-preview-ready');
|
|
804
|
+
});
|
|
805
|
+
it('should include a retry button that sends codeyam-server-error-retry', () => {
|
|
806
|
+
const page = buildErrorPage(500, 'Error', 'detail');
|
|
807
|
+
expect(page).toContain('codeyam-server-error-retry');
|
|
808
|
+
// Should have a button element
|
|
809
|
+
expect(page).toMatch(/<button[^>]*>/i);
|
|
810
|
+
});
|
|
811
|
+
it('should include the status code in the postMessage data', () => {
|
|
812
|
+
const page = buildErrorPage(502, 'Unreachable', 'detail');
|
|
813
|
+
// The postMessage should include statusCode: 502
|
|
814
|
+
expect(page).toContain('502');
|
|
815
|
+
});
|
|
816
|
+
it('should escape HTML in the title to prevent XSS', () => {
|
|
817
|
+
const page = buildErrorPage(500, '<img onerror=alert(1)>', 'detail');
|
|
818
|
+
expect(page).not.toContain('<img onerror=alert(1)>');
|
|
819
|
+
expect(page).toContain('<img onerror=alert(1)>');
|
|
820
|
+
});
|
|
821
|
+
it('should handle multiline error detail', () => {
|
|
822
|
+
const detail = 'Error on line 1\nStack trace line 2\nStack trace line 3';
|
|
823
|
+
const page = buildErrorPage(500, 'Error', detail);
|
|
824
|
+
expect(page).toContain('Error on line 1');
|
|
825
|
+
expect(page).toContain('Stack trace line 3');
|
|
826
|
+
});
|
|
827
|
+
it('should handle empty detail', () => {
|
|
828
|
+
const page = buildErrorPage(500, 'Error', '');
|
|
829
|
+
expect(page).toContain('<!DOCTYPE html>');
|
|
830
|
+
expect(page).toContain('codeyam-server-error');
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
describe('5xx error page serving', () => {
|
|
834
|
+
let targetServer;
|
|
835
|
+
let targetPort;
|
|
836
|
+
afterEach(async () => {
|
|
837
|
+
const { stopEditorProxy } = require('../editorProxy');
|
|
838
|
+
await stopEditorProxy();
|
|
839
|
+
if (targetServer?.listening) {
|
|
840
|
+
await new Promise((resolve) => targetServer.close(() => resolve()));
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
it('should serve an error page instead of raw HTML for 500 responses', async () => {
|
|
844
|
+
const { startEditorProxy } = require('../editorProxy');
|
|
845
|
+
targetServer = http.createServer((_req, res) => {
|
|
846
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
847
|
+
res.end('<html><body>Internal Server Error</body></html>');
|
|
848
|
+
});
|
|
849
|
+
await new Promise((resolve) => targetServer.listen(0, '127.0.0.1', () => resolve()));
|
|
850
|
+
targetPort = targetServer.address().port;
|
|
851
|
+
const result = await startEditorProxy({
|
|
852
|
+
port: 0,
|
|
853
|
+
targetUrl: `http://localhost:${targetPort}`,
|
|
854
|
+
});
|
|
855
|
+
expect(result).not.toBeNull();
|
|
856
|
+
const response = await fetch(`http://127.0.0.1:${result.port}/`);
|
|
857
|
+
expect(response.status).toBe(500);
|
|
858
|
+
const body = await response.text();
|
|
859
|
+
// Should contain the friendly error page, not the raw error
|
|
860
|
+
expect(body).toContain('codeyam-server-error');
|
|
861
|
+
expect(body).toContain('Internal Server Error');
|
|
862
|
+
});
|
|
863
|
+
it('should NOT replace non-HTML 500 responses', async () => {
|
|
864
|
+
const { startEditorProxy } = require('../editorProxy');
|
|
865
|
+
targetServer = http.createServer((_req, res) => {
|
|
866
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
867
|
+
res.end(JSON.stringify({ error: 'server error' }));
|
|
868
|
+
});
|
|
869
|
+
await new Promise((resolve) => targetServer.listen(0, '127.0.0.1', () => resolve()));
|
|
870
|
+
targetPort = targetServer.address().port;
|
|
871
|
+
const result = await startEditorProxy({
|
|
872
|
+
port: 0,
|
|
873
|
+
targetUrl: `http://localhost:${targetPort}`,
|
|
874
|
+
});
|
|
875
|
+
expect(result).not.toBeNull();
|
|
876
|
+
const response = await fetch(`http://127.0.0.1:${result.port}/api/data`);
|
|
877
|
+
expect(response.status).toBe(500);
|
|
878
|
+
const body = await response.text();
|
|
879
|
+
// JSON responses should pass through unchanged
|
|
880
|
+
expect(body).toContain('"error":"server error"');
|
|
881
|
+
expect(body).not.toContain('codeyam-server-error');
|
|
882
|
+
});
|
|
883
|
+
it('should serve an error page for 500 responses with no content type', async () => {
|
|
884
|
+
const { startEditorProxy } = require('../editorProxy');
|
|
885
|
+
// Simulates Next.js returning bare "Internal Server Error" with no Content-Type
|
|
886
|
+
targetServer = http.createServer((_req, res) => {
|
|
887
|
+
res.writeHead(500);
|
|
888
|
+
res.end('Internal Server Error');
|
|
889
|
+
});
|
|
890
|
+
await new Promise((resolve) => targetServer.listen(0, '127.0.0.1', () => resolve()));
|
|
891
|
+
targetPort = targetServer.address().port;
|
|
892
|
+
const result = await startEditorProxy({
|
|
893
|
+
port: 0,
|
|
894
|
+
targetUrl: `http://localhost:${targetPort}`,
|
|
895
|
+
});
|
|
896
|
+
expect(result).not.toBeNull();
|
|
897
|
+
const response = await fetch(`http://127.0.0.1:${result.port}/`);
|
|
898
|
+
expect(response.status).toBe(500);
|
|
899
|
+
const body = await response.text();
|
|
900
|
+
expect(body).toContain('codeyam-server-error');
|
|
901
|
+
expect(body).toContain('<!DOCTYPE html>');
|
|
902
|
+
expect(body).toContain('Internal Server Error');
|
|
903
|
+
});
|
|
904
|
+
it('should serve an error page for 500 text/plain responses', async () => {
|
|
905
|
+
const { startEditorProxy } = require('../editorProxy');
|
|
906
|
+
targetServer = http.createServer((_req, res) => {
|
|
907
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
908
|
+
res.end('Something went wrong');
|
|
909
|
+
});
|
|
910
|
+
await new Promise((resolve) => targetServer.listen(0, '127.0.0.1', () => resolve()));
|
|
911
|
+
targetPort = targetServer.address().port;
|
|
912
|
+
const result = await startEditorProxy({
|
|
913
|
+
port: 0,
|
|
914
|
+
targetUrl: `http://localhost:${targetPort}`,
|
|
915
|
+
});
|
|
916
|
+
expect(result).not.toBeNull();
|
|
917
|
+
const response = await fetch(`http://127.0.0.1:${result.port}/`);
|
|
918
|
+
expect(response.status).toBe(500);
|
|
919
|
+
const body = await response.text();
|
|
920
|
+
expect(body).toContain('codeyam-server-error');
|
|
921
|
+
expect(body).toContain('Something went wrong');
|
|
922
|
+
});
|
|
923
|
+
it('should serve an error page for 503 responses', async () => {
|
|
924
|
+
const { startEditorProxy } = require('../editorProxy');
|
|
925
|
+
targetServer = http.createServer((_req, res) => {
|
|
926
|
+
res.writeHead(503, { 'Content-Type': 'text/html' });
|
|
927
|
+
res.end('<html><body>Service Unavailable</body></html>');
|
|
928
|
+
});
|
|
929
|
+
await new Promise((resolve) => targetServer.listen(0, '127.0.0.1', () => resolve()));
|
|
930
|
+
targetPort = targetServer.address().port;
|
|
931
|
+
const result = await startEditorProxy({
|
|
932
|
+
port: 0,
|
|
933
|
+
targetUrl: `http://localhost:${targetPort}`,
|
|
934
|
+
});
|
|
935
|
+
expect(result).not.toBeNull();
|
|
936
|
+
const response = await fetch(`http://127.0.0.1:${result.port}/`);
|
|
937
|
+
expect(response.status).toBe(503);
|
|
938
|
+
const body = await response.text();
|
|
939
|
+
expect(body).toContain('codeyam-server-error');
|
|
940
|
+
});
|
|
941
|
+
it('should serve an error page for POST requests returning 500', async () => {
|
|
942
|
+
const { startEditorProxy } = require('../editorProxy');
|
|
943
|
+
targetServer = http.createServer((_req, res) => {
|
|
944
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
945
|
+
res.end('<html><body>POST Error</body></html>');
|
|
946
|
+
});
|
|
947
|
+
await new Promise((resolve) => targetServer.listen(0, '127.0.0.1', () => resolve()));
|
|
948
|
+
targetPort = targetServer.address().port;
|
|
949
|
+
const result = await startEditorProxy({
|
|
950
|
+
port: 0,
|
|
951
|
+
targetUrl: `http://localhost:${targetPort}`,
|
|
952
|
+
});
|
|
953
|
+
expect(result).not.toBeNull();
|
|
954
|
+
const response = await fetch(`http://127.0.0.1:${result.port}/api/action`, {
|
|
955
|
+
method: 'POST',
|
|
956
|
+
headers: { 'Content-Type': 'application/json' },
|
|
957
|
+
body: JSON.stringify({ test: true }),
|
|
958
|
+
});
|
|
959
|
+
expect(response.status).toBe(500);
|
|
960
|
+
const body = await response.text();
|
|
961
|
+
expect(body).toContain('codeyam-server-error');
|
|
962
|
+
expect(body).toContain('POST Error');
|
|
963
|
+
});
|
|
964
|
+
it('should NOT serve an error page for 4xx responses', async () => {
|
|
965
|
+
const { startEditorProxy } = require('../editorProxy');
|
|
966
|
+
targetServer = http.createServer((_req, res) => {
|
|
967
|
+
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
968
|
+
res.end('<html><body>Not Found</body></html>');
|
|
969
|
+
});
|
|
970
|
+
await new Promise((resolve) => targetServer.listen(0, '127.0.0.1', () => resolve()));
|
|
971
|
+
targetPort = targetServer.address().port;
|
|
972
|
+
const result = await startEditorProxy({
|
|
973
|
+
port: 0,
|
|
974
|
+
targetUrl: `http://localhost:${targetPort}`,
|
|
975
|
+
});
|
|
976
|
+
expect(result).not.toBeNull();
|
|
977
|
+
const response = await fetch(`http://127.0.0.1:${result.port}/missing`);
|
|
978
|
+
expect(response.status).toBe(404);
|
|
979
|
+
const body = await response.text();
|
|
980
|
+
// 4xx should pass through normally (with health script injected, not error page)
|
|
981
|
+
expect(body).not.toContain('codeyam-server-error');
|
|
982
|
+
expect(body).toContain('Not Found');
|
|
983
|
+
});
|
|
984
|
+
it('should serve an HTML error page for 502 connection failures', async () => {
|
|
985
|
+
const { startEditorProxy } = require('../editorProxy');
|
|
986
|
+
// No target server — connection will fail
|
|
987
|
+
const result = await startEditorProxy({
|
|
988
|
+
port: 0,
|
|
989
|
+
targetUrl: 'http://127.0.0.1:19999',
|
|
990
|
+
});
|
|
991
|
+
expect(result).not.toBeNull();
|
|
992
|
+
const response = await fetch(`http://127.0.0.1:${result.port}/`);
|
|
993
|
+
expect(response.status).toBe(502);
|
|
994
|
+
const body = await response.text();
|
|
995
|
+
expect(body).toContain('codeyam-server-error');
|
|
996
|
+
expect(body).toContain('<!DOCTYPE html>');
|
|
997
|
+
});
|
|
998
|
+
});
|
|
761
999
|
});
|
|
762
1000
|
//# sourceMappingURL=editorProxy.test.js.map
|