@analitikisler/ai-tag-debugger 0.8.2 → 0.10.0
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/CHANGELOG.md +25 -0
- package/README.md +174 -47
- package/README.tr.md +305 -178
- package/package.json +5 -2
- package/src/agent.js +1 -1
- package/src/audit.js +27 -13
- package/src/checks.js +123 -1
- package/src/cli.js +116 -71
- package/src/discover.js +148 -0
- package/src/help.js +104 -0
- package/src/i18n.js +22 -0
- package/src/journeys.js +87 -8
- package/src/mcp.js +52 -11
- package/src/plan-html.js +76 -0
- package/src/plan.js +14 -0
- package/src/providers.js +1 -1
- package/src/replay.js +47 -0
- package/src/report.js +34 -10
- package/src/sheet.js +321 -0
- package/src/workspace.js +77 -0
package/src/workspace.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readdir, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Where plans and runs are kept unless --data-dir says otherwise. */
|
|
5
|
+
export const DEFAULT_DATA_DIR = "aitd";
|
|
6
|
+
export const PLAN_FILE = "measurement-plan.json";
|
|
7
|
+
export const PLAN_HTML_FILE = "plan.html";
|
|
8
|
+
|
|
9
|
+
const ID = /^\d{4}-\d{2}-\d{2}_\d{4}(\d{2})?$/;
|
|
10
|
+
|
|
11
|
+
/** The folder name for a site: its host without "www.", e.g. https://www.example.com/x -> example.com. */
|
|
12
|
+
export function domainOf(url) {
|
|
13
|
+
let host;
|
|
14
|
+
try {
|
|
15
|
+
host = new URL(url).hostname;
|
|
16
|
+
} catch {
|
|
17
|
+
throw new Error(`"${url}" is not a full URL. Use e.g. https://www.example.com`);
|
|
18
|
+
}
|
|
19
|
+
return host.toLowerCase().replace(/^www\./, "");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
23
|
+
|
|
24
|
+
/** A local-time ID, YYYY-MM-DD_HHMM, with seconds added (_HHMMSS) when taken lists the minute's ID already. */
|
|
25
|
+
export function newId(taken = [], now = new Date()) {
|
|
26
|
+
const minute = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}`;
|
|
27
|
+
if (!taken.includes(minute)) return minute;
|
|
28
|
+
return `${minute}${pad(now.getSeconds())}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const siteDir = (dataDir = DEFAULT_DATA_DIR, site) => path.resolve(dataDir, domainOf(site));
|
|
32
|
+
export const plansDir = (dataDir, site) => path.join(siteDir(dataDir, site), "plans");
|
|
33
|
+
export const runsDir = (dataDir, site) => path.join(siteDir(dataDir, site), "runs");
|
|
34
|
+
|
|
35
|
+
async function listIds(dir) {
|
|
36
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
37
|
+
return entries.filter((e) => e.isDirectory() && ID.test(e.name)).map((e) => e.name).sort();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The site's plan IDs, oldest first and newest last. */
|
|
41
|
+
export const listPlanIds = (dataDir, site) => listIds(plansDir(dataDir, site));
|
|
42
|
+
export const listRunIds = (dataDir, site) => listIds(runsDir(dataDir, site));
|
|
43
|
+
|
|
44
|
+
/** A new, not yet created folder for the site's next plan. */
|
|
45
|
+
export async function newPlanDir(dataDir, site, now) {
|
|
46
|
+
const id = newId(await listPlanIds(dataDir, site), now);
|
|
47
|
+
return { id, dir: path.join(plansDir(dataDir, site), id) };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A new, not yet created folder for the site's next run. */
|
|
51
|
+
export async function newRunDir(dataDir, site, now) {
|
|
52
|
+
const id = newId(await listRunIds(dataDir, site), now);
|
|
53
|
+
return { id, dir: path.join(runsDir(dataDir, site), id) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Finds a plan file from a reference: "latest" or "last", "first", a plan ID, or a path to a plan
|
|
58
|
+
* file or the folder that holds one. Returns { id, file }; id is null for a path outside the site's plans.
|
|
59
|
+
*/
|
|
60
|
+
export async function resolvePlan(ref, { dataDir = DEFAULT_DATA_DIR, site } = {}) {
|
|
61
|
+
const value = String(ref ?? "latest").trim();
|
|
62
|
+
const byKeyword = ["latest", "last", "first"].includes(value);
|
|
63
|
+
if (byKeyword || ID.test(value)) {
|
|
64
|
+
if (!site) throw new Error(`Add --site <url> to find plan "${value}".`);
|
|
65
|
+
const ids = await listPlanIds(dataDir, site);
|
|
66
|
+
if (!ids.length) throw new Error(`No plan yet for ${domainOf(site)}. Draft one with: aitd plan --site ${site}`);
|
|
67
|
+
const id = value === "first" ? ids[0] : byKeyword ? ids.at(-1) : ids.includes(value) ? value : null;
|
|
68
|
+
if (!id) throw new Error(`No plan ${value} for ${domainOf(site)}. Available: ${ids.join(", ")}`);
|
|
69
|
+
return { id, file: path.join(plansDir(dataDir, site), id, PLAN_FILE) };
|
|
70
|
+
}
|
|
71
|
+
const info = await stat(value).catch(() => null);
|
|
72
|
+
if (!info) throw new Error(`No plan found at ${value}. Use latest, first, a plan ID, or a path to a plan file or folder.`);
|
|
73
|
+
const file = info.isDirectory() ? path.join(value, PLAN_FILE) : value;
|
|
74
|
+
if (info.isDirectory() && !(await stat(file).catch(() => null))) throw new Error(`${value} has no ${PLAN_FILE}.`);
|
|
75
|
+
const id = path.basename(path.dirname(path.resolve(file)));
|
|
76
|
+
return { id: ID.test(id) ? id : null, file: path.resolve(file) };
|
|
77
|
+
}
|