fix(artifacts): auto-capture leaves a web app's own HTML pages alone

At a session's Stop every untracked .html was snapshotted as a report, so a
web project not yet committed had its index.html taken into its owner's
Artifacts. An index.html, a page under a site folder (www, public, dist,
build, src, static, templates, assets, site, _site) or one beside scripts or
stylesheets (outside the workspace root) is now a site's own page and left
alone; a standalone HTML report is still captured.
This commit is contained in:
2026-10-30 15:22:19 +07:00
parent d09c9d6633
commit 240444eafd
3 changed files with 47 additions and 5 deletions
+17
View File
@@ -981,6 +981,23 @@ describe("outputs captured at a session's stop", () => {
);
assert.deepEqual(await artifacts.captureOutputs("no-such-session"), []);
});
it("leaves a web app's own pages alone and still takes a standalone HTML report", async () => {
const put = (rel, text = "<p>x</p>") => {
fs.mkdirSync(path.dirname(path.join(ws, rel)), { recursive: true });
fs.writeFileSync(path.join(ws, rel), text);
const soon = new Date(Date.now() + 5 * 60 * 1000);
fs.utimesSync(path.join(ws, rel), soon, soon);
};
put("app/index.html");
put("app/www/page.html");
put("widget/view.html");
put("widget/view.js", "x()");
put("summary-report.html");
put("out/q3.html");
const got = (await artifacts.captureOutputs("s1")).map((r) => r.source).sort();
assert.deepEqual(got, ["out/q3.html", "summary-report.html"]);
});
});
describe("unowned rows", () => {
+29 -4
View File
@@ -551,13 +551,38 @@ async function pinFile(req, { laneId, path: rel, title, description, sessionId }
const OUTPUT_EXT = /\.(pptx|docx|xlsx|pdf|html?)$/i;
/** Most files one stop captures, so a script that writes hundreds stays bounded. */
const OUTPUT_MAX = 20;
/** Folders that hold a site's own pages, never a report someone reads. */
const SITE_DIR_RE = /^(www|public|dist|build|src|static|templates|assets|site|_site)$/i;
/** What sits next to a site's page: its scripts and styles. */
const SITE_ASSET_RE = /\.(m?js|jsx|tsx?|vue|svelte|css|s[ac]ss|less)$/i;
/**
* An HTML file that is a web app's own page, not a report (`app/www/index.html`
* of a project not yet committed): an `index.html`, one under a site folder
* (`SITE_DIR_RE`), or one beside scripts or stylesheets in its folder. The
* workspace root is exempt from that last test — a repo keeps its config
* `.js` there next to the reports people write.
*/
async function isSitePage(cwd, rel) {
const parts = rel.split("/");
const name = parts.pop();
if (/^index\.html?$/i.test(name)) return true;
if (parts.some((d) => SITE_DIR_RE.test(d))) return true;
if (!parts.length) return false;
try {
const names = await fs.promises.readdir(path.join(cwd, ...parts));
return names.some((n) => SITE_ASSET_RE.test(n));
} catch {
return false;
}
}
const capturing = new Map();
/**
* The workspace's documents a session created or changed since it started:
* untracked or modified files in a repo (an HTML file only when untracked, so a
* site's own pages are not taken), every file of a plain folder. Paths are
* workspace-relative.
* untracked or modified files in a repo (an HTML file only when untracked and
* not a site's own page — `isSitePage`), every file of a plain folder. Paths
* are workspace-relative.
*/
async function outputCandidates(lane, since) {
const gd = require("./git-diff");
@@ -574,7 +599,7 @@ async function outputCandidates(lane, since) {
const out = [];
for (const rel of new Set(paths)) {
if (!OUTPUT_EXT.test(rel)) continue;
if (/\.html?$/i.test(rel) && tracked.has(rel)) continue;
if (/\.html?$/i.test(rel) && (tracked.has(rel) || (await isSitePage(lane.cwd, rel)))) continue;
let st;
try {
st = await fs.promises.lstat(path.join(lane.cwd, rel));