The last browser scripts refuse to measure a tree or dev server that is not this checkout
BUG-BROWSER-GATES-CAN-MEASURE-ANOTHER-WORKTREES-TREE, the remainder. A static scan of every internal/ script that drives a browser at a local target found eight with no identity guard (a9550858and59f497ebguarded the rest): built tree check-spa-tab-order -> requireThisTree dev server long-frames-probe, mobile-sheet-probe, theme-flash-probe, plugin-gallery-smoke, share-roundtrip-smoke (--plugin-gallery), smoke-studio, legend-columns-probe (only when aimed at /plugin-designs/; its default target is the DX-life site it compares against on purpose) A dev server has no built manifest, so the new requireDevServesThisCheckout checks the source it serves. It fetches three library files with ?raw and compares them byte for byte with this checkout. Another tree's dev server answers differently or 403s, and the gate exits 75 (NOT MEASURED). Vite answers a plain fetch with the file's text and an import-style request with `export default "…"`; both shapes are accepted, since the first version parsed only the second and refused this checkout's own server. Proven: against a dev server running from 319064a1's parent and a built tree whose manifest differs by one byte, all eight exit 75 and name the differing file (useMapInstance.js). Against this checkout, check-spa-tab-order and smoke-studio PASS, and plugin-gallery-smoke gets past the guard. A re-scan finds no unguarded script left.
This commit is contained in:
parent
92a81ed291
commit
4cc4a1284b
9 changed files with 87 additions and 0 deletions
|
|
@ -168,3 +168,42 @@ export function requirePathInThisCheckout(file, repo, label) {
|
|||
console.log(`${label}: note — ${file} is outside any checkout, so which build produced it cannot be checked.`)
|
||||
return { ok: true, why: 'outside any checkout' }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The same guard for gates aimed at a VITE DEV SERVER, which has no built manifest to fingerprint.
|
||||
*
|
||||
* A dev server serves this repo's SOURCE, so identity is the source itself: ask it for a few of this
|
||||
* checkout's library files with Vite's `?raw` query (the file's exact text, as an ES module string)
|
||||
* and compare byte for byte. A dev server rooted in another worktree with different code answers
|
||||
* differently, or 403s the path — either way it is NOT MEASURED (exit 75). One serving identical code
|
||||
* is, by definition, measuring the same thing. Unreachable is left to the gate's own handling.
|
||||
* Remote (non-loopback) targets are not checked, as in requireThisTree.
|
||||
*/
|
||||
const DEV_FINGERPRINT = ['library/layouts/VerticalTabsLayout.vue', 'library/composables/useMapInstance.js', 'library/composables/useShadowStyles.js']
|
||||
export async function requireDevServesThisCheckout(base, repo, label, files = DEV_FINGERPRINT) {
|
||||
let origin
|
||||
try { origin = new URL(String(base)).origin } catch { return { ok: true, why: 'not a URL' } }
|
||||
if (!/^(localhost|127\.|0\.0\.0\.0$|\[?::1\]?$)/.test(new URL(origin).hostname)) return { ok: true, why: 'remote target' }
|
||||
const refuse = (why) => {
|
||||
console.log(`${label}: NOT MEASURED — the dev server at ${origin} is not serving THIS checkout's source (${why}).`)
|
||||
console.log(' Start `bun x vite --port <yours> --strictPort` in this worktree and pass that URL.')
|
||||
process.exit(75)
|
||||
}
|
||||
for (const rel of files) {
|
||||
let local
|
||||
try { local = readFileSync(resolve(repo, rel), 'utf8') } catch { continue }
|
||||
const r = await fetch(`${origin}/${rel}?raw`, { signal: AbortSignal.timeout(8000) }).catch(() => null)
|
||||
if (!r) return { ok: false, why: 'unreachable' }
|
||||
if (!r.ok) refuse(`${rel} → HTTP ${r.status}`)
|
||||
// A plain fetch (not a module import) gets the file's TEXT served directly; an import-style
|
||||
// request gets it wrapped as `export default "…"`. Accept both shapes — the first version only
|
||||
// parsed the wrapped one and refused this very checkout's own dev server (measured 2026-09-25).
|
||||
const body = await r.text()
|
||||
const m = body.match(/^export default ("(?:[^"\\]|\\.)*")/s)
|
||||
let served = body
|
||||
if (m) { try { served = JSON.parse(m[1]) } catch { served = null } }
|
||||
if (served !== local) refuse(`${rel} differs`)
|
||||
}
|
||||
return { ok: true, why: 'same source' }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,14 @@
|
|||
* actual side by side.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { requireDevServesThisCheckout as __devGuard, requireThisTree as __treeGuard } from './_serves-this-tree.mjs'
|
||||
import { fileURLToPath as __f2p } from 'node:url'
|
||||
import { dirname as __dn, resolve as __rs } from 'node:path'
|
||||
const __ROOT = __rs(__dn(__f2p(import.meta.url)), '..')
|
||||
|
||||
const BASE = process.env.BASE_URL || 'http://127.0.0.1:4173/doxa-maps'
|
||||
// Measure THIS checkout, or measure nothing (exit 75 = NOT MEASURED; see _serves-this-tree.mjs).
|
||||
await __treeGuard(BASE, __ROOT, 'SPA-TAB-ORDER')
|
||||
|
||||
// The Driver's sequence, verbatim from the open-work brief. Prefix-matched (case-insensitive,
|
||||
// whitespace-collapsed) so a row may carry its subtitle.
|
||||
|
|
|
|||
|
|
@ -14,8 +14,14 @@
|
|||
* Plugin gallery dev server: --url http://localhost:5174/plugin-designs/doxa-simple-map/index.html
|
||||
*/
|
||||
import { launch, TELEMETRY } from './lib/browser.mjs'
|
||||
import { requireDevServesThisCheckout } from './_serves-this-tree.mjs'
|
||||
import { fileURLToPath as __f2p } from 'node:url'
|
||||
import { dirname as __dn, resolve as __rs } from 'node:path'
|
||||
const args = process.argv.slice(2); const argOf = (k, d) => { const i = args.indexOf(k); return i >= 0 ? args[i + 1] : d }
|
||||
const URL = argOf('--url', 'http://localhost:3001/pray'), TAG = argOf('--tag', 'doxa-map'), W = +argOf('--width', 1440), H = +argOf('--height', 900), ROWS = +argOf('--rows', 8)
|
||||
// Guard ONLY when pointed at OUR dev gallery (/plugin-designs/…). The default target is the DX-life
|
||||
// site (a different app this probe compares against on purpose), which has no source of ours to match.
|
||||
if (/\/plugin-designs\//.test(URL)) await requireDevServesThisCheckout(URL, __rs(__dn(__f2p(import.meta.url)), '..'), 'LEGEND-COLUMNS')
|
||||
const browser = await launch('firefox', { label: 'legend-columns' })
|
||||
const ctx = await browser.newContext({ viewport: { width: W, height: H }, hasTouch: true, isMobile: W < 600 }); const page = await ctx.newPage()
|
||||
await page.route(TELEMETRY, r => r.abort())
|
||||
|
|
|
|||
|
|
@ -56,12 +56,18 @@
|
|||
import { writeFileSync, mkdirSync } from 'node:fs'
|
||||
import { dirname } from 'node:path'
|
||||
import { launch, DEEP_FIND_SRC, deepEvaluate, TELEMETRY } from './lib/browser.mjs'
|
||||
import { requireDevServesThisCheckout as __devGuard, requireThisTree as __treeGuard } from './_serves-this-tree.mjs'
|
||||
import { fileURLToPath as __f2p } from 'node:url'
|
||||
import { dirname as __dn, resolve as __rs } from 'node:path'
|
||||
const __ROOT = __rs(__dn(__f2p(import.meta.url)), '..')
|
||||
|
||||
const TOOL = 'long-frames-probe'
|
||||
const args = process.argv.slice(2)
|
||||
const argOf = (k, d) => { const i = args.indexOf(k); return i >= 0 ? args[i + 1] : d }
|
||||
const argAll = (k) => args.flatMap((a, i) => a === k && args[i + 1] ? [args[i + 1]] : [])
|
||||
const BASE = String(argOf('--base', 'http://localhost:5174')).replace(/\/+$/, '')
|
||||
// Measure THIS checkout, or measure nothing (exit 75 = NOT MEASURED; see _serves-this-tree.mjs).
|
||||
await __devGuard(BASE, __ROOT, 'LONG-FRAMES')
|
||||
const URLS = argAll('--url')
|
||||
const TAGS = argAll('--tag')
|
||||
const GREP = argOf('--grep', '')
|
||||
|
|
|
|||
|
|
@ -61,11 +61,17 @@
|
|||
import { writeFileSync, mkdirSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { launch, TELEMETRY } from './lib/browser.mjs'
|
||||
import { requireDevServesThisCheckout as __devGuard, requireThisTree as __treeGuard } from './_serves-this-tree.mjs'
|
||||
import { fileURLToPath as __f2p } from 'node:url'
|
||||
import { dirname as __dn, resolve as __rs } from 'node:path'
|
||||
const __ROOT = __rs(__dn(__f2p(import.meta.url)), '..')
|
||||
|
||||
const TOOL = 'mobile-sheet-probe'
|
||||
const args = process.argv.slice(2)
|
||||
const argOf = (k, d) => { const i = args.indexOf(k); return i >= 0 ? args[i + 1] : d }
|
||||
const URL = String(argOf('--url', 'http://localhost:5174/plugin-designs/doxa-simple-map/index.html'))
|
||||
// Measure THIS checkout, or measure nothing (exit 75 = NOT MEASURED; see _serves-this-tree.mjs).
|
||||
await __devGuard(URL, __ROOT, 'MOBILE-SHEET')
|
||||
const TAG = argOf('--tag', 'doxa-map')
|
||||
const NTH = Number(argOf('--nth', 0))
|
||||
const INSTANCE = argOf('--instance', '')
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@
|
|||
*/
|
||||
import { writeFileSync } from 'node:fs'
|
||||
import { launch, deepEvaluate, TELEMETRY } from './lib/browser.mjs'
|
||||
import { requireDevServesThisCheckout as __devGuard, requireThisTree as __treeGuard } from './_serves-this-tree.mjs'
|
||||
import { fileURLToPath as __f2p } from 'node:url'
|
||||
import { dirname as __dn, resolve as __rs } from 'node:path'
|
||||
const __ROOT = __rs(__dn(__f2p(import.meta.url)), '..')
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const argOf = (k, d) => { const i = args.indexOf(k); return i >= 0 ? args[i + 1] : d }
|
||||
|
|
@ -40,6 +44,8 @@ const argOf = (k, d) => { const i = args.indexOf(k); return i >= 0 ? args[i + 1]
|
|||
// cannot reach its subject has not passed and has not failed: it has not run, and the message
|
||||
// blames the user for not starting a server they had started.
|
||||
const BASE = String(argOf('--url', process.env.DEV_URL || 'http://localhost:5173')).replace(/\/+$/, '')
|
||||
// Measure THIS checkout, or measure nothing (exit 75 = NOT MEASURED; see _serves-this-tree.mjs).
|
||||
await __devGuard(BASE, __ROOT, 'PLUGIN-GALLERY-SMOKE')
|
||||
|
||||
// Paint needs a GPU this build box does not have; see the long note at the canvas assertion below.
|
||||
const EXPECT_CANVAS = process.env.EXPECT_CANVAS === '1'
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@
|
|||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { requireDevServesThisCheckout as __devGuard, requireThisTree as __treeGuard } from './_serves-this-tree.mjs'
|
||||
import { fileURLToPath as __f2p } from 'node:url'
|
||||
import { dirname as __dn, resolve as __rs } from 'node:path'
|
||||
const __ROOT = __rs(__dn(__f2p(import.meta.url)), '..')
|
||||
import { launch, deepEvaluate, TELEMETRY } from './lib/browser.mjs'
|
||||
|
||||
const TOOL = 'share-smoke'
|
||||
|
|
@ -63,6 +67,8 @@ const GREP = argOf('--grep', '')
|
|||
const GALLERY = has('--plugin-gallery')
|
||||
? String((() => { const v = args[args.indexOf('--plugin-gallery') + 1]; return v && !v.startsWith('--') ? v : 'http://localhost:5174' })()).replace(/\/+$/, '')
|
||||
: null
|
||||
// Measure THIS checkout, or measure nothing (exit 75 = NOT MEASURED; see _serves-this-tree.mjs).
|
||||
if (GALLERY) await __devGuard(GALLERY, __ROOT, 'SHARE-ROUNDTRIP')
|
||||
|
||||
if (!URLS.length && !GALLERY) {
|
||||
console.error(`[${TOOL}] usage: --url <host page URL> [--url ...] [--tag <tag>] | --plugin-gallery [base] (see header)`)
|
||||
|
|
|
|||
|
|
@ -47,12 +47,18 @@
|
|||
* removing tree-base makes it FAIL on the empty tab list. A gate that cannot fail is worse than none.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { requireDevServesThisCheckout as __devGuard, requireThisTree as __treeGuard } from './_serves-this-tree.mjs'
|
||||
import { fileURLToPath as __f2p } from 'node:url'
|
||||
import { dirname as __dn, resolve as __rs } from 'node:path'
|
||||
const __ROOT = __rs(__dn(__f2p(import.meta.url)), '..')
|
||||
|
||||
const argOf = (flag, dflt) => {
|
||||
const i = process.argv.indexOf(flag)
|
||||
return i > -1 && process.argv[i + 1] ? process.argv[i + 1] : dflt
|
||||
}
|
||||
const BASE = String(argOf('--url', 'http://localhost:5173')).replace(/\/+$/, '')
|
||||
// Measure THIS checkout, or measure nothing (exit 75 = NOT MEASURED; see _serves-this-tree.mjs).
|
||||
await __devGuard(BASE, __ROOT, 'SMOKE-STUDIO')
|
||||
const BUDGET_MS = Number(process.env.SMOKE_BUDGET_MS || 20000)
|
||||
|
||||
// Deep walker — SHADOW DOM DOES NOT PIERCE. A flat querySelectorAll lies about this component, and a
|
||||
|
|
|
|||
|
|
@ -119,11 +119,17 @@
|
|||
import { writeFileSync, mkdirSync, readFileSync, rmSync, mkdtempSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { requireDevServesThisCheckout as __devGuard, requireThisTree as __treeGuard } from './_serves-this-tree.mjs'
|
||||
import { fileURLToPath as __f2p } from 'node:url'
|
||||
import { dirname as __dn, resolve as __rs } from 'node:path'
|
||||
const __ROOT = __rs(__dn(__f2p(import.meta.url)), '..')
|
||||
import { launch, DEEP_FIND_SRC, deepEvaluate, TELEMETRY } from './lib/browser.mjs'
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const argOf = (k, d) => { const i = args.indexOf(k); return i >= 0 ? args[i + 1] : d }
|
||||
const BASE = String(argOf('--url', 'http://localhost:5174')).replace(/\/+$/, '')
|
||||
// Measure THIS checkout, or measure nothing (exit 75 = NOT MEASURED; see _serves-this-tree.mjs).
|
||||
await __devGuard(BASE, __ROOT, 'THEME-FLASH')
|
||||
const GREP = argOf('--grep', '')
|
||||
const TARGET = argOf('--target', '')
|
||||
const TAG = argOf('--tag', '')
|
||||
|
|
|
|||
Loading…
Reference in a new issue