@cccarv82/freya 1.0.24 → 1.0.26
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/cli/web-ui.js +25 -4
- package/cli/web.js +93 -0
- package/package.json +1 -1
package/cli/web-ui.js
CHANGED
|
@@ -4,7 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
(function () {
|
|
6
6
|
const $ = (id) => document.getElementById(id);
|
|
7
|
-
|
|
7
|
+
const state = {
|
|
8
|
+
lastReportPath: null,
|
|
9
|
+
lastText: '',
|
|
10
|
+
reports: [],
|
|
11
|
+
selectedReport: null,
|
|
12
|
+
lastPlan: '',
|
|
13
|
+
lastApplied: null,
|
|
14
|
+
autoApply: true,
|
|
15
|
+
autoRunReports: false
|
|
16
|
+
};
|
|
8
17
|
|
|
9
18
|
function applyTheme(theme) {
|
|
10
19
|
document.documentElement.setAttribute('data-theme', theme);
|
|
@@ -225,10 +234,21 @@
|
|
|
225
234
|
headers: body ? { 'Content-Type': 'application/json' } : {},
|
|
226
235
|
body: body ? JSON.stringify(body) : undefined
|
|
227
236
|
});
|
|
228
|
-
|
|
237
|
+
|
|
238
|
+
// Read as text first so we can surface errors even if backend returns HTML/plaintext
|
|
239
|
+
const raw = await res.text();
|
|
240
|
+
let json = null;
|
|
241
|
+
try {
|
|
242
|
+
json = raw ? JSON.parse(raw) : {};
|
|
243
|
+
} catch (e) {
|
|
244
|
+
const snippet = (raw || '').slice(0, 1200);
|
|
245
|
+
throw new Error(`Non-JSON response (${res.status} ${res.statusText}) from ${p}:\n\n${snippet}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
229
248
|
if (!res.ok) {
|
|
230
249
|
const detail = json.details ? ('\n' + json.details) : '';
|
|
231
|
-
|
|
250
|
+
const err = (json.error || `Request failed (${res.status})`) + detail;
|
|
251
|
+
throw new Error(err);
|
|
232
252
|
}
|
|
233
253
|
return json;
|
|
234
254
|
}
|
|
@@ -348,7 +368,8 @@
|
|
|
348
368
|
setPill('ok', 'init ok');
|
|
349
369
|
} catch (e) {
|
|
350
370
|
setPill('err', 'init failed');
|
|
351
|
-
|
|
371
|
+
const msg = e && (e.stack || e.message) ? (e.stack || e.message) : String(e);
|
|
372
|
+
setOut(msg);
|
|
352
373
|
}
|
|
353
374
|
}
|
|
354
375
|
|
package/cli/web.js
CHANGED
|
@@ -266,6 +266,65 @@ function redactSecrets(text) {
|
|
|
266
266
|
return out;
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
function debugLogPath(workspaceDir) {
|
|
270
|
+
const dir = path.join(workspaceDir, '.debuglogs');
|
|
271
|
+
try { fs.mkdirSync(dir, { recursive: true }); } catch {}
|
|
272
|
+
return path.join(dir, 'debug.jsonl');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function safeString(v, max = 2000) {
|
|
276
|
+
const t = String(v == null ? '' : v);
|
|
277
|
+
return t.length > max ? (t.slice(0, max) + '…') : t;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function summarizePayload(payload) {
|
|
281
|
+
if (!payload || typeof payload !== 'object') return null;
|
|
282
|
+
const keys = Object.keys(payload);
|
|
283
|
+
const summary = { keys };
|
|
284
|
+
|
|
285
|
+
if (typeof payload.text === 'string') {
|
|
286
|
+
const red = redactSecrets(payload.text);
|
|
287
|
+
summary.textLen = payload.text.length;
|
|
288
|
+
summary.textSha1 = sha1(red);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (typeof payload.plan === 'string') {
|
|
292
|
+
summary.planLen = payload.plan.length;
|
|
293
|
+
summary.planSha1 = sha1(payload.plan);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (typeof payload.webhookUrl === 'string') {
|
|
297
|
+
summary.webhookHost = (() => { try { return new URL(payload.webhookUrl).host; } catch { return null; } })();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (typeof payload.relPath === 'string') summary.relPath = payload.relPath;
|
|
301
|
+
if (typeof payload.script === 'string') summary.script = payload.script;
|
|
302
|
+
|
|
303
|
+
return summary;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function rotateDebugLog(filePath, maxBytes = 5 * 1024 * 1024) {
|
|
307
|
+
try {
|
|
308
|
+
if (!fs.existsSync(filePath)) return;
|
|
309
|
+
const st = fs.statSync(filePath);
|
|
310
|
+
if (st.size <= maxBytes) return;
|
|
311
|
+
const dir = path.dirname(filePath);
|
|
312
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
313
|
+
const rotated = path.join(dir, 'debug-' + stamp + '.jsonl');
|
|
314
|
+
fs.renameSync(filePath, rotated);
|
|
315
|
+
} catch {}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function writeDebugEvent(workspaceDir, event) {
|
|
319
|
+
try {
|
|
320
|
+
if (!workspaceDir) return;
|
|
321
|
+
const filePath = debugLogPath(workspaceDir);
|
|
322
|
+
rotateDebugLog(filePath);
|
|
323
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), ...event });
|
|
324
|
+
fs.appendFileSync(filePath, line + '\n', 'utf8');
|
|
325
|
+
} catch {}
|
|
326
|
+
}
|
|
327
|
+
|
|
269
328
|
async function publishRobust(webhookUrl, text, opts = {}) {
|
|
270
329
|
const u = new URL(webhookUrl);
|
|
271
330
|
const isDiscord = u.hostname.includes('discord.com') || u.hostname.includes('discordapp.com');
|
|
@@ -282,6 +341,25 @@ async function publishRobust(webhookUrl, text, opts = {}) {
|
|
|
282
341
|
|
|
283
342
|
function safeJson(res, code, obj) {
|
|
284
343
|
const body = JSON.stringify(obj);
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
const dbg = res && res.__freyaDebug;
|
|
347
|
+
if (dbg && dbg.workspaceDir) {
|
|
348
|
+
const error = obj && (obj.error || obj.details)
|
|
349
|
+
? safeString(redactSecrets(JSON.stringify({ error: obj.error, details: obj.details })), 1600)
|
|
350
|
+
: null;
|
|
351
|
+
writeDebugEvent(dbg.workspaceDir, {
|
|
352
|
+
type: 'http_response',
|
|
353
|
+
reqId: dbg.reqId,
|
|
354
|
+
method: dbg.method,
|
|
355
|
+
url: dbg.url,
|
|
356
|
+
status: code,
|
|
357
|
+
bytes: Buffer.byteLength(body),
|
|
358
|
+
error
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
} catch {}
|
|
362
|
+
|
|
285
363
|
res.writeHead(code, {
|
|
286
364
|
'Content-Type': 'application/json; charset=utf-8',
|
|
287
365
|
'Cache-Control': 'no-store',
|
|
@@ -766,10 +844,13 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
766
844
|
const host = '127.0.0.1';
|
|
767
845
|
|
|
768
846
|
const server = http.createServer(async (req, res) => {
|
|
847
|
+
const reqId = Math.random().toString(16).slice(2) + Date.now().toString(16);
|
|
848
|
+
res.__freyaDebug = { reqId, method: req.method, url: req.url || '' };
|
|
769
849
|
try {
|
|
770
850
|
if (!req.url) return safeJson(res, 404, { error: 'Not found' });
|
|
771
851
|
|
|
772
852
|
if (req.method === 'GET' && req.url === '/') {
|
|
853
|
+
try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch {}
|
|
773
854
|
const body = html(dir || './freya');
|
|
774
855
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
|
|
775
856
|
res.end(body);
|
|
@@ -797,6 +878,18 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
797
878
|
const requestedDir = payload.dir || dir || './freya';
|
|
798
879
|
const workspaceDir = normalizeWorkspaceDir(requestedDir);
|
|
799
880
|
|
|
881
|
+
// debug logging (always on)
|
|
882
|
+
try {
|
|
883
|
+
res.__freyaDebug.workspaceDir = workspaceDir;
|
|
884
|
+
writeDebugEvent(workspaceDir, {
|
|
885
|
+
type: 'http_request',
|
|
886
|
+
reqId,
|
|
887
|
+
method: req.method,
|
|
888
|
+
url: req.url,
|
|
889
|
+
payload: summarizePayload(payload)
|
|
890
|
+
});
|
|
891
|
+
} catch {}
|
|
892
|
+
|
|
800
893
|
if (req.url === '/api/pick-dir') {
|
|
801
894
|
const picked = await pickDirectoryNative();
|
|
802
895
|
return safeJson(res, 200, { dir: picked });
|