@brandlab-digital/conformance 3.7.0 → 3.8.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/README.md +26 -0
- package/dist/current-release.js +5 -5
- package/dist/current-release.js.map +1 -1
- package/dist/public-copy.d.ts +7 -0
- package/dist/public-copy.d.ts.map +1 -1
- package/dist/public-copy.js +8 -3
- package/dist/public-copy.js.map +1 -1
- package/dist/ready-cli.d.ts +38 -0
- package/dist/ready-cli.d.ts.map +1 -0
- package/dist/ready-cli.js +327 -0
- package/dist/ready-cli.js.map +1 -0
- package/dist/site-system/checks.d.ts +389 -0
- package/dist/site-system/checks.d.ts.map +1 -0
- package/dist/site-system/checks.js +96 -0
- package/dist/site-system/checks.js.map +1 -0
- package/dist/site-system/config.d.ts +40 -0
- package/dist/site-system/config.d.ts.map +1 -0
- package/dist/site-system/config.js +214 -0
- package/dist/site-system/config.js.map +1 -0
- package/dist/site-system/index.d.ts +14 -0
- package/dist/site-system/index.d.ts.map +1 -0
- package/dist/site-system/index.js +14 -0
- package/dist/site-system/index.js.map +1 -0
- package/dist/site-system/journeys/access.d.ts +7 -0
- package/dist/site-system/journeys/access.d.ts.map +1 -0
- package/dist/site-system/journeys/access.js +263 -0
- package/dist/site-system/journeys/access.js.map +1 -0
- package/dist/site-system/journeys/browser.d.ts +79 -0
- package/dist/site-system/journeys/browser.d.ts.map +1 -0
- package/dist/site-system/journeys/browser.js +218 -0
- package/dist/site-system/journeys/browser.js.map +1 -0
- package/dist/site-system/journeys/conversion.d.ts +3 -0
- package/dist/site-system/journeys/conversion.d.ts.map +1 -0
- package/dist/site-system/journeys/conversion.js +152 -0
- package/dist/site-system/journeys/conversion.js.map +1 -0
- package/dist/site-system/journeys/menu.d.ts +11 -0
- package/dist/site-system/journeys/menu.d.ts.map +1 -0
- package/dist/site-system/journeys/menu.js +395 -0
- package/dist/site-system/journeys/menu.js.map +1 -0
- package/dist/site-system/journeys/page-helpers.d.ts +146 -0
- package/dist/site-system/journeys/page-helpers.d.ts.map +1 -0
- package/dist/site-system/journeys/page-helpers.js +426 -0
- package/dist/site-system/journeys/page-helpers.js.map +1 -0
- package/dist/site-system/journeys/plan.d.ts +82 -0
- package/dist/site-system/journeys/plan.d.ts.map +1 -0
- package/dist/site-system/journeys/plan.js +112 -0
- package/dist/site-system/journeys/plan.js.map +1 -0
- package/dist/site-system/journeys/shell.d.ts +24 -0
- package/dist/site-system/journeys/shell.d.ts.map +1 -0
- package/dist/site-system/journeys/shell.js +269 -0
- package/dist/site-system/journeys/shell.js.map +1 -0
- package/dist/site-system/playwright-report.d.ts +22 -0
- package/dist/site-system/playwright-report.d.ts.map +1 -0
- package/dist/site-system/playwright-report.js +131 -0
- package/dist/site-system/playwright-report.js.map +1 -0
- package/dist/site-system/records.d.ts +30 -0
- package/dist/site-system/records.d.ts.map +1 -0
- package/dist/site-system/records.js +98 -0
- package/dist/site-system/records.js.map +1 -0
- package/dist/site-system/report.d.ts +116 -0
- package/dist/site-system/report.d.ts.map +1 -0
- package/dist/site-system/report.js +366 -0
- package/dist/site-system/report.js.map +1 -0
- package/dist/site-system/rule-modules.d.ts +8 -0
- package/dist/site-system/rule-modules.d.ts.map +1 -0
- package/dist/site-system/rule-modules.js +12 -0
- package/dist/site-system/rule-modules.js.map +1 -0
- package/dist/site-system/rules/design/d4-distinctness.d.ts +36 -0
- package/dist/site-system/rules/design/d4-distinctness.d.ts.map +1 -0
- package/dist/site-system/rules/design/d4-distinctness.js +207 -0
- package/dist/site-system/rules/design/d4-distinctness.js.map +1 -0
- package/dist/site-system/rules/design/index.d.ts +17 -0
- package/dist/site-system/rules/design/index.d.ts.map +1 -0
- package/dist/site-system/rules/design/index.js +7 -0
- package/dist/site-system/rules/design/index.js.map +1 -0
- package/dist/site-system/rules/enquiry/c2-quote-form.d.ts +31 -0
- package/dist/site-system/rules/enquiry/c2-quote-form.d.ts.map +1 -0
- package/dist/site-system/rules/enquiry/c2-quote-form.js +211 -0
- package/dist/site-system/rules/enquiry/c2-quote-form.js.map +1 -0
- package/dist/site-system/rules/enquiry/common.d.ts +37 -0
- package/dist/site-system/rules/enquiry/common.d.ts.map +1 -0
- package/dist/site-system/rules/enquiry/common.js +59 -0
- package/dist/site-system/rules/enquiry/common.js.map +1 -0
- package/dist/site-system/rules/enquiry/e1-recorded-titles.d.ts +8 -0
- package/dist/site-system/rules/enquiry/e1-recorded-titles.d.ts.map +1 -0
- package/dist/site-system/rules/enquiry/e1-recorded-titles.js +59 -0
- package/dist/site-system/rules/enquiry/e1-recorded-titles.js.map +1 -0
- package/dist/site-system/rules/enquiry/e9-cost-guides.d.ts +22 -0
- package/dist/site-system/rules/enquiry/e9-cost-guides.d.ts.map +1 -0
- package/dist/site-system/rules/enquiry/e9-cost-guides.js +55 -0
- package/dist/site-system/rules/enquiry/e9-cost-guides.js.map +1 -0
- package/dist/site-system/rules/enquiry/html.d.ts +53 -0
- package/dist/site-system/rules/enquiry/html.d.ts.map +1 -0
- package/dist/site-system/rules/enquiry/html.js +294 -0
- package/dist/site-system/rules/enquiry/html.js.map +1 -0
- package/dist/site-system/rules/enquiry/index.d.ts +17 -0
- package/dist/site-system/rules/enquiry/index.d.ts.map +1 -0
- package/dist/site-system/rules/enquiry/index.js +26 -0
- package/dist/site-system/rules/enquiry/index.js.map +1 -0
- package/dist/site-system/rules/enquiry/k8-sections.d.ts +17 -0
- package/dist/site-system/rules/enquiry/k8-sections.d.ts.map +1 -0
- package/dist/site-system/rules/enquiry/k8-sections.js +76 -0
- package/dist/site-system/rules/enquiry/k8-sections.js.map +1 -0
- package/dist/site-system/rules/enquiry/literal.d.ts +29 -0
- package/dist/site-system/rules/enquiry/literal.d.ts.map +1 -0
- package/dist/site-system/rules/enquiry/literal.js +189 -0
- package/dist/site-system/rules/enquiry/literal.js.map +1 -0
- package/dist/site-system/rules/enquiry/t11-licence-classes.d.ts +73 -0
- package/dist/site-system/rules/enquiry/t11-licence-classes.d.ts.map +1 -0
- package/dist/site-system/rules/enquiry/t11-licence-classes.js +275 -0
- package/dist/site-system/rules/enquiry/t11-licence-classes.js.map +1 -0
- package/dist/site-system/rules/enquiry/t5-reviews.d.ts +23 -0
- package/dist/site-system/rules/enquiry/t5-reviews.d.ts.map +1 -0
- package/dist/site-system/rules/enquiry/t5-reviews.js +91 -0
- package/dist/site-system/rules/enquiry/t5-reviews.js.map +1 -0
- package/dist/site-system/rules/shell/banned-wording.d.ts +24 -0
- package/dist/site-system/rules/shell/banned-wording.d.ts.map +1 -0
- package/dist/site-system/rules/shell/banned-wording.js +212 -0
- package/dist/site-system/rules/shell/banned-wording.js.map +1 -0
- package/dist/site-system/rules/shell/budgets.d.ts +43 -0
- package/dist/site-system/rules/shell/budgets.d.ts.map +1 -0
- package/dist/site-system/rules/shell/budgets.js +297 -0
- package/dist/site-system/rules/shell/budgets.js.map +1 -0
- package/dist/site-system/rules/shell/cta-labels.d.ts +17 -0
- package/dist/site-system/rules/shell/cta-labels.d.ts.map +1 -0
- package/dist/site-system/rules/shell/cta-labels.js +86 -0
- package/dist/site-system/rules/shell/cta-labels.js.map +1 -0
- package/dist/site-system/rules/shell/facts.d.ts +33 -0
- package/dist/site-system/rules/shell/facts.d.ts.map +1 -0
- package/dist/site-system/rules/shell/facts.js +217 -0
- package/dist/site-system/rules/shell/facts.js.map +1 -0
- package/dist/site-system/rules/shell/headings.d.ts +5 -0
- package/dist/site-system/rules/shell/headings.d.ts.map +1 -0
- package/dist/site-system/rules/shell/headings.js +23 -0
- package/dist/site-system/rules/shell/headings.js.map +1 -0
- package/dist/site-system/rules/shell/hero-location.d.ts +4 -0
- package/dist/site-system/rules/shell/hero-location.d.ts.map +1 -0
- package/dist/site-system/rules/shell/hero-location.js +45 -0
- package/dist/site-system/rules/shell/hero-location.js.map +1 -0
- package/dist/site-system/rules/shell/html.d.ts +53 -0
- package/dist/site-system/rules/shell/html.d.ts.map +1 -0
- package/dist/site-system/rules/shell/html.js +341 -0
- package/dist/site-system/rules/shell/html.js.map +1 -0
- package/dist/site-system/rules/shell/index.d.ts +18 -0
- package/dist/site-system/rules/shell/index.d.ts.map +1 -0
- package/dist/site-system/rules/shell/index.js +65 -0
- package/dist/site-system/rules/shell/index.js.map +1 -0
- package/dist/site-system/rules/shell/licence.d.ts +4 -0
- package/dist/site-system/rules/shell/licence.d.ts.map +1 -0
- package/dist/site-system/rules/shell/licence.js +60 -0
- package/dist/site-system/rules/shell/licence.js.map +1 -0
- package/dist/site-system/rules/shell/location-wording.d.ts +13 -0
- package/dist/site-system/rules/shell/location-wording.d.ts.map +1 -0
- package/dist/site-system/rules/shell/location-wording.js +15 -0
- package/dist/site-system/rules/shell/location-wording.js.map +1 -0
- package/dist/site-system/rules/shell/navigation.d.ts +5 -0
- package/dist/site-system/rules/shell/navigation.d.ts.map +1 -0
- package/dist/site-system/rules/shell/navigation.js +272 -0
- package/dist/site-system/rules/shell/navigation.js.map +1 -0
- package/dist/site-system/rules/shell/overlays.d.ts +4 -0
- package/dist/site-system/rules/shell/overlays.d.ts.map +1 -0
- package/dist/site-system/rules/shell/overlays.js +93 -0
- package/dist/site-system/rules/shell/overlays.js.map +1 -0
- package/dist/site-system/rules/shell/page.d.ts +51 -0
- package/dist/site-system/rules/shell/page.d.ts.map +1 -0
- package/dist/site-system/rules/shell/page.js +256 -0
- package/dist/site-system/rules/shell/page.js.map +1 -0
- package/dist/site-system/rules/shell/phone.d.ts +13 -0
- package/dist/site-system/rules/shell/phone.d.ts.map +1 -0
- package/dist/site-system/rules/shell/phone.js +99 -0
- package/dist/site-system/rules/shell/phone.js.map +1 -0
- package/dist/site-system/rules/shell/proof-numbers.d.ts +7 -0
- package/dist/site-system/rules/shell/proof-numbers.d.ts.map +1 -0
- package/dist/site-system/rules/shell/proof-numbers.js +81 -0
- package/dist/site-system/rules/shell/proof-numbers.js.map +1 -0
- package/dist/site-system/rules/shell/types.d.ts +80 -0
- package/dist/site-system/rules/shell/types.d.ts.map +1 -0
- package/dist/site-system/rules/shell/types.js +17 -0
- package/dist/site-system/rules/shell/types.js.map +1 -0
- package/dist/site-system/runners.d.ts +37 -0
- package/dist/site-system/runners.d.ts.map +1 -0
- package/dist/site-system/runners.js +48 -0
- package/dist/site-system/runners.js.map +1 -0
- package/dist/site-system/samples.d.ts +43 -0
- package/dist/site-system/samples.d.ts.map +1 -0
- package/dist/site-system/samples.js +99 -0
- package/dist/site-system/samples.js.map +1 -0
- package/dist/site-system/steps.d.ts +66 -0
- package/dist/site-system/steps.d.ts.map +1 -0
- package/dist/site-system/steps.js +45 -0
- package/dist/site-system/steps.js.map +1 -0
- package/dist/site-system/streamed-html.d.ts +9 -0
- package/dist/site-system/streamed-html.d.ts.map +1 -0
- package/dist/site-system/streamed-html.js +43 -0
- package/dist/site-system/streamed-html.js.map +1 -0
- package/dist/site-system-e2e.d.ts +6 -0
- package/dist/site-system-e2e.d.ts.map +1 -0
- package/dist/site-system-e2e.js +54 -0
- package/dist/site-system-e2e.js.map +1 -0
- package/package.json +22 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../../../src/site-system/journeys/shell.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * N6 and N8: the header at every desktop width, and the phone drawer and call bar. The header stays at the top with a\n * readable phone and the quote action from 1024 px up; the drawer is a modal dialog that starts on its close button,\n * keeps focus, closes on Escape, a link tap and a widening past 1280 px; the call bar's phone reports `fab`.\n */\nimport { expect, test, type Locator, type Page } from '@playwright/test';\nimport {\n SITE_SYSTEM_AXE_TAGS,\n describeAxe,\n mainNavigation,\n note,\n openSitePage,\n raiseTimeout,\n runAxe,\n seriousOrCritical,\n settle,\n siteHeader,\n waitUntil,\n} from './browser.js';\nimport { SITE_SYSTEM_PHONE_VIEWPORT, siteSystemJourneyTitle as title, type SiteSystemJourneyPlan } from './plan.js';\n\n/** The visible phone menu button in the header. */\nexport function menuButton(header: Locator, plan: Pick<SiteSystemJourneyPlan, 'menuName'>): Locator {\n const name = plan.menuName;\n return header.getByRole('button', typeof name === 'string' ? { name, exact: true } : { name }).filter({ visible: true }).first();\n}\n\n/** The drawer the menu button opens: the element its aria-controls names, else the visible dialog. Marked `drawer`. */\nexport async function openDrawer(page: Page, button: Locator, how: 'tap' | 'click'): Promise<Locator | null> {\n await (how === 'tap' ? button.tap() : button.click());\n const controls = (await button.getAttribute('aria-controls'))?.trim();\n const named = controls ? page.locator(`[id=\"${controls.replace(/[\"\\\\]/g, '\\\\$&')}\"]`) : null;\n const drawer = named && (await named.count()) === 1 ? named : page.getByRole('dialog').filter({ visible: true }).first();\n if (!(await waitUntil(async () => (await drawer.count()) > 0 && (await drawer.isVisible()), 1_000))) return null;\n await page.waitForTimeout(250);\n await drawer.evaluate((element) => window.__brandlabSiteSystem.markElement(element, 'drawer'));\n return drawer;\n}\n\n/** Whether any dialog is still open or shown. */\nexport async function anyDialogOpen(page: Page): Promise<boolean> {\n return page.evaluate(() => Array.from(document.querySelectorAll('dialog')).some((dialog) => dialog.open)\n || Array.from(document.querySelectorAll('[role=\"dialog\"]')).some((dialog) => window.__brandlabSiteSystem.isRendered(dialog)));\n}\n\n/**\n * Scroll a phone page down past its hero, half a screen at a time, until a call bar shows; return its phone links,\n * or null when none shows before the end of the page.\n */\nexport async function callBarLinks(page: Page): Promise<{ description: string; placement: string; invalid: string | null }[] | null> {\n let previous = -1;\n for (let screens = 1; screens <= 8; screens += 0.5) {\n const scrolled = await page.evaluate((factor) => {\n window.scrollTo({ top: window.innerHeight * factor, behavior: 'instant' });\n return window.scrollY;\n }, screens);\n if (scrolled === previous) return null;\n previous = scrolled;\n await page.waitForTimeout(250);\n const links = await page.evaluate(() => {\n const helpers = window.__brandlabSiteSystem;\n const bar = helpers.findCallBar();\n if (!bar) return null;\n helpers.markElement(bar, 'call-bar');\n return Array.from(bar.querySelectorAll('a[href^=\"tel:\"]')).filter(helpers.isRendered)\n .map((link) => ({ description: helpers.describeElement(link), ...helpers.phonePlacement(link) }));\n });\n if (links) return links;\n }\n return null;\n}\n\nexport function defineShellJourneys(plan: SiteSystemJourneyPlan, runs: ReadonlySet<string>): void {\n const quote = { source: plan.quoteName.source, flags: plan.quoteName.flags };\n\n if (runs.has('N6')) {\n test.describe('header at desktop widths', () => {\n for (const width of plan.desktopWidths) {\n test(title('N6', `the header stays at the top with the phone and quote visible at ${width} px`), async ({ page }, testInfo) => {\n await page.setViewportSize({ width, height: 900 });\n await openSitePage(page, plan.home, plan);\n const header = await siteHeader(page);\n expect(await header.count(), `${plan.home} has no header (a <header> banner landmark)`).toBe(1);\n const problems: string[] = [];\n const inspect = async (moment: string) => {\n const seen = await header.evaluate((element, name) => {\n const helpers = window.__brandlabSiteSystem;\n const inView = (node: Element) => {\n const rect = node.getBoundingClientRect();\n return rect.top >= -1 && rect.bottom <= window.innerHeight + 1;\n };\n const phones = Array.from(element.querySelectorAll('a[href^=\"tel:\"]')).filter(helpers.isRendered);\n const readable = phones.filter((link) => ((link as HTMLElement).innerText.match(/\\d/g) ?? []).length >= 6 && inView(link));\n return { phones: phones.length, readable: readable.length, quotes: helpers.quoteActions(element, name).filter(inView).length };\n }, quote);\n if (seen.readable === 0) problems.push(`${moment}: no phone number is readable in the header (${seen.phones} phone link(s) without the number)`);\n if (seen.quotes === 0) problems.push(`${moment}: no quote action is visible in the header`);\n };\n await inspect('at the top');\n const nav = await mainNavigation(page, plan);\n if (!nav && !(await menuButton(header, plan).count())) problems.push(`neither the menu named \"${plan.navName}\" nor a Menu button is visible`);\n const scrolled = await page.evaluate(() => {\n window.scrollTo({ top: 2_000, behavior: 'instant' });\n return window.scrollY;\n });\n await page.waitForTimeout(300);\n if (scrolled < 400) note(testInfo, `${plan.home} at ${width} px scrolls only ${scrolled} px, too little to show whether the header stays at the top`);\n const top = await header.evaluate((element) => Math.round(element.getBoundingClientRect().top));\n if (scrolled > 0 && Math.abs(top) > 1) problems.push(`after scrolling ${scrolled} px the header's top is at ${top} px (expected 0: keep it sticky)`);\n if (scrolled > 0) await inspect(`after scrolling ${scrolled} px`);\n const overflow = await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth);\n if (overflow > 0) problems.push(`the page scrolls sideways by ${overflow} px`);\n expect(problems).toEqual([]);\n });\n }\n });\n }\n\n if (!runs.has('N8')) return;\n\n test.describe('phone drawer and call bar at 390 px', () => {\n test.use({ viewport: { ...SITE_SYSTEM_PHONE_VIEWPORT }, isMobile: true, hasTouch: true });\n\n test(title('N8', 'the Menu button opens a modal drawer that starts on its close button, keeps focus inside and closes on Escape'), async ({ page }, testInfo) => {\n await openSitePage(page, plan.home, plan);\n const header = await siteHeader(page);\n const button = menuButton(header, plan);\n expect(await button.count(), `no visible \"${String(plan.menuName)}\" button in the header at 390 px`).toBe(1);\n const drawer = await openDrawer(page, button, 'tap');\n expect(drawer, 'tapping the Menu button showed no dialog').not.toBeNull();\n const problems: string[] = [];\n const facts = await drawer!.evaluate((element) => ({\n modal: (element instanceof HTMLDialogElement && element.matches(':modal')) || element.getAttribute('aria-modal') === 'true',\n focus: element.contains(document.activeElement) ? window.__brandlabSiteSystem.accessibleName(document.activeElement!) : null,\n }));\n if (!facts.modal) problems.push('the drawer is not a modal dialog (showModal() or aria-modal=\"true\")');\n if (facts.focus === null) problems.push('focus did not move into the drawer');\n else if (!/close/i.test(facts.focus)) problems.push(`focus starts on \"${facts.focus}\", not the close button`);\n\n const groups = drawer!.locator('button[aria-expanded], summary').filter({ visible: true });\n const groupCount = await groups.count();\n if (groupCount > 0) {\n await groups.nth(0).tap();\n if (groupCount > 1) await groups.nth(1).tap();\n await page.waitForTimeout(150);\n const expanded = await drawer!.evaluate((element) => element.querySelectorAll('button[aria-expanded=\"true\"]').length + element.querySelectorAll('details[open]').length);\n if (expanded !== 1) problems.push(`opening drawer groups left ${expanded} open (one at a time)`);\n } else note(testInfo, 'The drawer has no groups.');\n\n const escaped: string[] = [];\n for (let press = 0; press < 40; press += 1) {\n await page.keyboard.press('Tab');\n const outside = await drawer!.evaluate((element) => {\n const active = document.activeElement;\n if (!active || active === document.body || element.contains(active)) return null;\n return window.__brandlabSiteSystem.describeElement(active);\n });\n if (outside) escaped.push(outside);\n }\n if (escaped.length) problems.push(`Tab moved focus out of the drawer to ${[...new Set(escaped)].slice(0, 3).join(', ')}`);\n await page.keyboard.press('Escape');\n await page.waitForTimeout(300);\n if (await anyDialogOpen(page)) problems.push('Escape did not close the drawer');\n expect(problems).toEqual([]);\n });\n\n test(title('N8', 'axe finds no serious or critical issue with the drawer open'), async ({ page }) => {\n await openSitePage(page, plan.home, plan);\n const drawer = await openDrawer(page, menuButton(await siteHeader(page), plan), 'tap');\n expect(drawer, 'tapping the Menu button showed no dialog').not.toBeNull();\n const outcome = await runAxe(page, ['[data-brandlab-journey~=\"drawer\"]'], { tags: SITE_SYSTEM_AXE_TAGS });\n expect(describeAxe(seriousOrCritical(outcome.violations))).toEqual([]);\n });\n\n test(title('N8', 'a link tap in the drawer closes it and loads the page'), async ({ page }) => {\n await openSitePage(page, plan.home, plan);\n const drawer = await openDrawer(page, menuButton(await siteHeader(page), plan), 'tap');\n expect(drawer, 'tapping the Menu button showed no dialog').not.toBeNull();\n const href = await drawer!.evaluate((element) => {\n const link = Array.from(element.querySelectorAll<HTMLAnchorElement>('a[href]')).find((anchor) => window.__brandlabSiteSystem.isRendered(anchor)\n && anchor.origin === location.origin && anchor.pathname !== location.pathname);\n if (!link) return null;\n window.__brandlabSiteSystem.markElement(link, 'drawer-link');\n return link.pathname;\n });\n expect(href, 'the open drawer shows no link to another page').not.toBeNull();\n await page.locator('[data-brandlab-journey~=\"drawer-link\"]').tap();\n await page.waitForURL((url) => url.pathname === href, { timeout: plan.navigationTimeoutMs });\n await settle(page, plan.settleTimeoutMs);\n expect(await anyDialogOpen(page), `the drawer is still open on ${href}`).toBe(false);\n });\n\n test(title('N8', 'the phone header shows a call control named with the number, and no page scrolls sideways at 390 px'), async ({ page }, testInfo) => {\n raiseTimeout(testInfo, 180_000);\n const problems: string[] = [];\n for (const path of plan.sampled) {\n await openSitePage(page, path, plan);\n await siteHeader(page);\n // Against the device width: a phone browser widens its layout viewport (and innerWidth) to overflowing content.\n const seen = await page.evaluate((width) => {\n const helpers = window.__brandlabSiteSystem;\n const header = document.querySelector('[data-brandlab-journey~=\"header\"]');\n const calls = Array.from(header?.querySelectorAll('a[href^=\"tel:\"]') ?? []).filter(helpers.isRendered)\n .map((link) => ({ description: helpers.describeElement(link), digits: (helpers.accessibleName(link).match(/\\d/g) ?? []).length }));\n return { calls, overflow: document.documentElement.scrollWidth - width };\n }, SITE_SYSTEM_PHONE_VIEWPORT.width);\n if (seen.calls.length === 0) problems.push(`${path}: the phone header shows no call control`);\n for (const call of seen.calls) {\n if (call.digits < 6) problems.push(`${path}: the header's ${call.description} is not named with the phone number`);\n }\n if (seen.overflow > 0) problems.push(`${path}: the page scrolls sideways by ${seen.overflow} px at 390 px`);\n }\n note(testInfo, `Checked ${plan.sampled.join(', ')}`);\n expect(problems).toEqual([]);\n });\n\n test(title('N8', 'the call bar\\'s phone reports placement fab'), async ({ page }, testInfo) => {\n raiseTimeout(testInfo, 180_000);\n const problems: string[] = [];\n for (const path of [plan.home, ...plan.money]) {\n await openSitePage(page, path, plan);\n const links = await callBarLinks(page);\n if (!links) {\n note(testInfo, `${path}: no call bar showed after scrolling.`);\n continue;\n }\n if (links.length === 0) problems.push(`${path}: the call bar shows no phone link`);\n for (const link of links) {\n if (link.placement !== 'fab' || link.invalid) problems.push(`${path}: ${link.description} reports placement ${link.placement}${link.invalid ? ` (it declares \"${link.invalid}\")` : ''}, not fab`);\n }\n }\n expect(problems).toEqual([]);\n });\n });\n\n test.describe('drawer across the desktop breakpoint', () => {\n test(title('N8', 'widening past 1280 px closes the drawer and leaves the page usable'), async ({ page }) => {\n await page.setViewportSize({ width: 1100, height: 800 });\n await openSitePage(page, plan.home, plan);\n let button = menuButton(await siteHeader(page), plan);\n if (!(await button.count())) {\n await page.setViewportSize({ ...SITE_SYSTEM_PHONE_VIEWPORT });\n await page.waitForTimeout(300);\n button = menuButton(await siteHeader(page), plan);\n }\n expect(await button.count(), `no visible \"${String(plan.menuName)}\" button below 1280 px`).toBe(1);\n expect(await openDrawer(page, button, 'click'), 'the Menu button showed no dialog').not.toBeNull();\n await page.setViewportSize({ width: 1440, height: 800 });\n await page.waitForTimeout(400);\n const problems: string[] = [];\n if (await anyDialogOpen(page)) problems.push('the drawer stayed open at 1440 px');\n const usable = await page.evaluate(() => {\n const hit = document.elementFromPoint(window.innerWidth / 2, window.innerHeight * 0.75);\n const locked = [document.documentElement, document.body].some((element) => getComputedStyle(element).overflowY === 'hidden');\n return { covered: Boolean(hit?.closest('dialog, [role=\"dialog\"]')), inert: document.body.inert || Boolean(document.querySelector('main')?.closest('[inert]')), locked };\n });\n if (usable.covered) problems.push('a dialog still covers the page at 1440 px');\n if (usable.inert) problems.push('the page is still inert at 1440 px');\n if (usable.locked) problems.push('page scrolling is still locked at 1440 px');\n expect(problems).toEqual([]);\n });\n });\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journey results from a Playwright JSON report (`--reporter=json`). Site-system journeys carry their check id in
|
|
3
|
+
* square brackets at the start of the test title ("[N3] hover opens after a short delay"); a describe block's title
|
|
4
|
+
* may carry it for every test inside. Each test run becomes a finding: passed (or passed on retry) is a pass, failed
|
|
5
|
+
* or timed out is a fail, a test marked `test.fail()` is a fail whatever happened, and a skipped test is no result at
|
|
6
|
+
* all, so a check with only skipped tests did not run. Only journey checks count: a title that names a rendered,
|
|
7
|
+
* static or record check is a problem, never a result. Notes the journeys record (`note()`, annotation type
|
|
8
|
+
* `brandlab-site-system`) follow the test's message.
|
|
9
|
+
*/
|
|
10
|
+
import { type SiteSystemFinding } from "./checks.js";
|
|
11
|
+
export interface SiteSystemJourneyResults {
|
|
12
|
+
readonly findings: readonly SiteSystemFinding[];
|
|
13
|
+
/** Tests that ran with a known check id. */
|
|
14
|
+
readonly tests: number;
|
|
15
|
+
readonly problems: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
/** The annotation type the journeys' `note()` records. */
|
|
18
|
+
export declare const SITE_SYSTEM_NOTE_ANNOTATION = "brandlab-site-system";
|
|
19
|
+
/** The bracketed ids at the start of a title: "[N3] hover …" is ["N3"], "[N2][N3] …" is ["N2", "N3"]. */
|
|
20
|
+
export declare function siteSystemCheckIdsInTitle(title: string): string[];
|
|
21
|
+
export declare function readSiteSystemJourneyReport(report: unknown): SiteSystemJourneyResults;
|
|
22
|
+
//# sourceMappingURL=playwright-report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwright-report.d.ts","sourceRoot":"","sources":["../../src/site-system/playwright-report.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAA8C,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEjG,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAChD,4CAA4C;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAID,0DAA0D;AAC1D,eAAO,MAAM,2BAA2B,yBAAyB,CAAC;AAGlE,yGAAyG;AACzG,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAGjE;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,OAAO,GAAG,wBAAwB,CAoDrF"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journey results from a Playwright JSON report (`--reporter=json`). Site-system journeys carry their check id in
|
|
3
|
+
* square brackets at the start of the test title ("[N3] hover opens after a short delay"); a describe block's title
|
|
4
|
+
* may carry it for every test inside. Each test run becomes a finding: passed (or passed on retry) is a pass, failed
|
|
5
|
+
* or timed out is a fail, a test marked `test.fail()` is a fail whatever happened, and a skipped test is no result at
|
|
6
|
+
* all, so a check with only skipped tests did not run. Only journey checks count: a title that names a rendered,
|
|
7
|
+
* static or record check is a problem, never a result. Notes the journeys record (`note()`, annotation type
|
|
8
|
+
* `brandlab-site-system`) follow the test's message.
|
|
9
|
+
*/
|
|
10
|
+
import { SITE_SYSTEM_CHECKS } from "./checks.js";
|
|
11
|
+
const KNOWN_IDS = new Set(SITE_SYSTEM_CHECKS.map((check) => check.id));
|
|
12
|
+
const JOURNEY_IDS = new Set(SITE_SYSTEM_CHECKS.filter((check) => check.kind === "journey").map((check) => check.id));
|
|
13
|
+
/** The annotation type the journeys' `note()` records. */
|
|
14
|
+
export const SITE_SYSTEM_NOTE_ANNOTATION = "brandlab-site-system";
|
|
15
|
+
const MAX_ERROR_LENGTH = 240;
|
|
16
|
+
/** The bracketed ids at the start of a title: "[N3] hover …" is ["N3"], "[N2][N3] …" is ["N2", "N3"]. */
|
|
17
|
+
export function siteSystemCheckIdsInTitle(title) {
|
|
18
|
+
const leading = title.match(/^\s*((?:\[[A-Za-z0-9-]+\]\s*)+)/)?.[1] ?? "";
|
|
19
|
+
return [...leading.matchAll(/\[([A-Za-z0-9-]+)\]/g)].map((match) => match[1]);
|
|
20
|
+
}
|
|
21
|
+
export function readSiteSystemJourneyReport(report) {
|
|
22
|
+
const findings = [];
|
|
23
|
+
const problems = [];
|
|
24
|
+
let tests = 0;
|
|
25
|
+
let untagged = 0;
|
|
26
|
+
if (!isRecord(report) || !Array.isArray(report.suites)) {
|
|
27
|
+
return { findings, tests, problems: ["The Playwright report has no suites; is it the JSON reporter's output?"] };
|
|
28
|
+
}
|
|
29
|
+
for (const error of asArray(report.errors)) {
|
|
30
|
+
problems.push(`Playwright reported an error outside the tests: ${firstLine(isRecord(error) ? error.message : error)}`);
|
|
31
|
+
}
|
|
32
|
+
const visitSuite = (suite, inherited) => {
|
|
33
|
+
if (!isRecord(suite))
|
|
34
|
+
return;
|
|
35
|
+
const own = typeof suite.title === "string" ? siteSystemCheckIdsInTitle(suite.title) : [];
|
|
36
|
+
const suiteIds = own.length > 0 ? own : inherited;
|
|
37
|
+
for (const spec of asArray(suite.specs)) {
|
|
38
|
+
if (!isRecord(spec))
|
|
39
|
+
continue;
|
|
40
|
+
const title = typeof spec.title === "string" ? spec.title : "";
|
|
41
|
+
const titleIds = siteSystemCheckIdsInTitle(title);
|
|
42
|
+
const ids = titleIds.length > 0 ? titleIds : suiteIds;
|
|
43
|
+
if (ids.length === 0) {
|
|
44
|
+
untagged += asArray(spec.tests).length;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
for (const id of ids.filter((id) => !JOURNEY_IDS.has(id))) {
|
|
48
|
+
problems.push(KNOWN_IDS.has(id)
|
|
49
|
+
? `The journey "${title}" names [${id}], which is not a journey check, so it was ignored.`
|
|
50
|
+
: `The journey "${title}" names [${id}], which is not a Site System check, so it was ignored.`);
|
|
51
|
+
}
|
|
52
|
+
const known = ids.filter((id) => JOURNEY_IDS.has(id));
|
|
53
|
+
for (const test of asArray(spec.tests)) {
|
|
54
|
+
const outcome = testOutcome(test);
|
|
55
|
+
if (!outcome || known.length === 0)
|
|
56
|
+
continue;
|
|
57
|
+
tests += 1;
|
|
58
|
+
const where = isRecord(test) && typeof test.projectName === "string" && test.projectName ? ` (${test.projectName})` : "";
|
|
59
|
+
const notes = testNotes(test);
|
|
60
|
+
const message = (outcome.status === "fail"
|
|
61
|
+
? `${title}${where} failed: ${outcome.error}`
|
|
62
|
+
: `${title}${where} passed${outcome.retried ? " on retry" : ""}.`) + (notes.length > 0 ? ` Notes: ${notes.join("; ")}` : "");
|
|
63
|
+
for (const checkId of known)
|
|
64
|
+
findings.push({ checkId, status: outcome.status, message });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const child of asArray(suite.suites))
|
|
68
|
+
visitSuite(child, suiteIds);
|
|
69
|
+
};
|
|
70
|
+
for (const suite of report.suites)
|
|
71
|
+
visitSuite(suite, []);
|
|
72
|
+
if (untagged > 0) {
|
|
73
|
+
const one = untagged === 1;
|
|
74
|
+
problems.push(`${untagged} journey test${one ? "" : "s"} in the Playwright report ${one ? "carries" : "carry"} no [check id] in the title, so ${one ? "it counts" : "they count"} towards no check.`);
|
|
75
|
+
}
|
|
76
|
+
return { findings, tests, problems };
|
|
77
|
+
}
|
|
78
|
+
/** Pass, fail or null (skipped or never run) for one test across its retries. */
|
|
79
|
+
function testOutcome(test) {
|
|
80
|
+
if (!isRecord(test))
|
|
81
|
+
return null;
|
|
82
|
+
const results = asArray(test.results).filter(isRecord);
|
|
83
|
+
const last = results.at(-1);
|
|
84
|
+
const status = typeof test.status === "string" ? test.status : null;
|
|
85
|
+
if (status === "skipped" || (!status && (!last || last.status === "skipped")))
|
|
86
|
+
return null;
|
|
87
|
+
// test.fail() turns a failing check green in Playwright; the gate still counts it as failing.
|
|
88
|
+
if (test.expectedStatus === "failed")
|
|
89
|
+
return { status: "fail", retried: false, error: "marked as expected to fail." };
|
|
90
|
+
const passed = status === "expected" || status === "flaky" || (!status && last?.status === "passed");
|
|
91
|
+
if (passed)
|
|
92
|
+
return { status: "pass", retried: status === "flaky" || results.length > 1, error: "" };
|
|
93
|
+
const failing = [...results].reverse().find((result) => result.status !== "passed" && result.status !== "skipped");
|
|
94
|
+
const error = failing ? errorText(failing) : "";
|
|
95
|
+
return { status: "fail", retried: results.length > 1, error: error || `the test ended ${failing?.status ?? status ?? "without a result"}.` };
|
|
96
|
+
}
|
|
97
|
+
/** The journeys' notes on one test: test-level annotations and the last run's (newer Playwright reports both). */
|
|
98
|
+
function testNotes(test) {
|
|
99
|
+
if (!isRecord(test))
|
|
100
|
+
return [];
|
|
101
|
+
const last = asArray(test.results).filter(isRecord).at(-1);
|
|
102
|
+
const notes = new Set();
|
|
103
|
+
for (const annotation of [...asArray(test.annotations), ...asArray(last?.annotations)]) {
|
|
104
|
+
if (!isRecord(annotation) || annotation.type !== SITE_SYSTEM_NOTE_ANNOTATION)
|
|
105
|
+
continue;
|
|
106
|
+
const text = firstLine(annotation.description);
|
|
107
|
+
if (text)
|
|
108
|
+
notes.add(text);
|
|
109
|
+
}
|
|
110
|
+
return [...notes];
|
|
111
|
+
}
|
|
112
|
+
function errorText(result) {
|
|
113
|
+
const error = isRecord(result.error) ? result.error.message : undefined;
|
|
114
|
+
const first = asArray(result.errors).find(isRecord)?.message;
|
|
115
|
+
const text = firstLine(error ?? first);
|
|
116
|
+
return text || (result.status === "timedOut" ? "the test timed out." : "");
|
|
117
|
+
}
|
|
118
|
+
function firstLine(value) {
|
|
119
|
+
if (typeof value !== "string")
|
|
120
|
+
return "";
|
|
121
|
+
const plain = value.replace(/\u001b\[[0-9;]*m/g, "");
|
|
122
|
+
const line = plain.split(/\r?\n/).map((part) => part.trim()).find(Boolean) ?? "";
|
|
123
|
+
return line.length > MAX_ERROR_LENGTH ? `${line.slice(0, MAX_ERROR_LENGTH - 1)}…` : line;
|
|
124
|
+
}
|
|
125
|
+
function asArray(value) {
|
|
126
|
+
return Array.isArray(value) ? value : [];
|
|
127
|
+
}
|
|
128
|
+
function isRecord(value) {
|
|
129
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=playwright-report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwright-report.js","sourceRoot":"","sources":["../../src/site-system/playwright-report.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Journey results from a Playwright JSON report (`--reporter=json`). Site-system journeys carry their check id in\n * square brackets at the start of the test title (\"[N3] hover opens after a short delay\"); a describe block's title\n * may carry it for every test inside. Each test run becomes a finding: passed (or passed on retry) is a pass, failed\n * or timed out is a fail, a test marked `test.fail()` is a fail whatever happened, and a skipped test is no result at\n * all, so a check with only skipped tests did not run. Only journey checks count: a title that names a rendered,\n * static or record check is a problem, never a result. Notes the journeys record (`note()`, annotation type\n * `brandlab-site-system`) follow the test's message.\n */\nimport { SITE_SYSTEM_CHECKS, type SiteSystemCheckId, type SiteSystemFinding } from \"./checks.js\";\n\nexport interface SiteSystemJourneyResults {\n readonly findings: readonly SiteSystemFinding[];\n /** Tests that ran with a known check id. */\n readonly tests: number;\n readonly problems: readonly string[];\n}\n\nconst KNOWN_IDS = new Set<string>(SITE_SYSTEM_CHECKS.map((check) => check.id));\nconst JOURNEY_IDS = new Set<string>(SITE_SYSTEM_CHECKS.filter((check) => check.kind === \"journey\").map((check) => check.id));\n/** The annotation type the journeys' `note()` records. */\nexport const SITE_SYSTEM_NOTE_ANNOTATION = \"brandlab-site-system\";\nconst MAX_ERROR_LENGTH = 240;\n\n/** The bracketed ids at the start of a title: \"[N3] hover …\" is [\"N3\"], \"[N2][N3] …\" is [\"N2\", \"N3\"]. */\nexport function siteSystemCheckIdsInTitle(title: string): string[] {\n const leading = title.match(/^\\s*((?:\\[[A-Za-z0-9-]+\\]\\s*)+)/)?.[1] ?? \"\";\n return [...leading.matchAll(/\\[([A-Za-z0-9-]+)\\]/g)].map((match) => match[1]!);\n}\n\nexport function readSiteSystemJourneyReport(report: unknown): SiteSystemJourneyResults {\n const findings: SiteSystemFinding[] = [];\n const problems: string[] = [];\n let tests = 0;\n let untagged = 0;\n if (!isRecord(report) || !Array.isArray(report.suites)) {\n return { findings, tests, problems: [\"The Playwright report has no suites; is it the JSON reporter's output?\"] };\n }\n for (const error of asArray(report.errors)) {\n problems.push(`Playwright reported an error outside the tests: ${firstLine(isRecord(error) ? error.message : error)}`);\n }\n\n const visitSuite = (suite: unknown, inherited: readonly string[]) => {\n if (!isRecord(suite)) return;\n const own = typeof suite.title === \"string\" ? siteSystemCheckIdsInTitle(suite.title) : [];\n const suiteIds = own.length > 0 ? own : inherited;\n for (const spec of asArray(suite.specs)) {\n if (!isRecord(spec)) continue;\n const title = typeof spec.title === \"string\" ? spec.title : \"\";\n const titleIds = siteSystemCheckIdsInTitle(title);\n const ids = titleIds.length > 0 ? titleIds : suiteIds;\n if (ids.length === 0) {\n untagged += asArray(spec.tests).length;\n continue;\n }\n for (const id of ids.filter((id) => !JOURNEY_IDS.has(id))) {\n problems.push(KNOWN_IDS.has(id)\n ? `The journey \"${title}\" names [${id}], which is not a journey check, so it was ignored.`\n : `The journey \"${title}\" names [${id}], which is not a Site System check, so it was ignored.`);\n }\n const known = ids.filter((id): id is SiteSystemCheckId => JOURNEY_IDS.has(id));\n for (const test of asArray(spec.tests)) {\n const outcome = testOutcome(test);\n if (!outcome || known.length === 0) continue;\n tests += 1;\n const where = isRecord(test) && typeof test.projectName === \"string\" && test.projectName ? ` (${test.projectName})` : \"\";\n const notes = testNotes(test);\n const message = (outcome.status === \"fail\"\n ? `${title}${where} failed: ${outcome.error}`\n : `${title}${where} passed${outcome.retried ? \" on retry\" : \"\"}.`) + (notes.length > 0 ? ` Notes: ${notes.join(\"; \")}` : \"\");\n for (const checkId of known) findings.push({ checkId, status: outcome.status, message });\n }\n }\n for (const child of asArray(suite.suites)) visitSuite(child, suiteIds);\n };\n for (const suite of report.suites) visitSuite(suite, []);\n\n if (untagged > 0) {\n const one = untagged === 1;\n problems.push(`${untagged} journey test${one ? \"\" : \"s\"} in the Playwright report ${one ? \"carries\" : \"carry\"} no [check id] in the title, so ${one ? \"it counts\" : \"they count\"} towards no check.`);\n }\n return { findings, tests, problems };\n}\n\n/** Pass, fail or null (skipped or never run) for one test across its retries. */\nfunction testOutcome(test: unknown): { status: \"pass\" | \"fail\"; retried: boolean; error: string } | null {\n if (!isRecord(test)) return null;\n const results = asArray(test.results).filter(isRecord);\n const last = results.at(-1);\n const status = typeof test.status === \"string\" ? test.status : null;\n if (status === \"skipped\" || (!status && (!last || last.status === \"skipped\"))) return null;\n // test.fail() turns a failing check green in Playwright; the gate still counts it as failing.\n if (test.expectedStatus === \"failed\") return { status: \"fail\", retried: false, error: \"marked as expected to fail.\" };\n const passed = status === \"expected\" || status === \"flaky\" || (!status && last?.status === \"passed\");\n if (passed) return { status: \"pass\", retried: status === \"flaky\" || results.length > 1, error: \"\" };\n const failing = [...results].reverse().find((result) => result.status !== \"passed\" && result.status !== \"skipped\");\n const error = failing ? errorText(failing) : \"\";\n return { status: \"fail\", retried: results.length > 1, error: error || `the test ended ${failing?.status ?? status ?? \"without a result\"}.` };\n}\n\n/** The journeys' notes on one test: test-level annotations and the last run's (newer Playwright reports both). */\nfunction testNotes(test: unknown): string[] {\n if (!isRecord(test)) return [];\n const last = asArray(test.results).filter(isRecord).at(-1);\n const notes = new Set<string>();\n for (const annotation of [...asArray(test.annotations), ...asArray(last?.annotations)]) {\n if (!isRecord(annotation) || annotation.type !== SITE_SYSTEM_NOTE_ANNOTATION) continue;\n const text = firstLine(annotation.description);\n if (text) notes.add(text);\n }\n return [...notes];\n}\n\nfunction errorText(result: Record<string, unknown>): string {\n const error = isRecord(result.error) ? result.error.message : undefined;\n const first = asArray(result.errors).find(isRecord)?.message;\n const text = firstLine(error ?? first);\n return text || (result.status === \"timedOut\" ? \"the test timed out.\" : \"\");\n}\n\nfunction firstLine(value: unknown): string {\n if (typeof value !== \"string\") return \"\";\n const plain = value.replace(/\\u001b\\[[0-9;]*m/g, \"\");\n const line = plain.split(/\\r?\\n/).map((part) => part.trim()).find(Boolean) ?? \"\";\n return line.length > MAX_ERROR_LENGTH ? `${line.slice(0, MAX_ERROR_LENGTH - 1)}…` : line;\n}\n\nfunction asArray(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type SiteSystemRecordFile } from "./steps.js";
|
|
2
|
+
export declare const SITE_SYSTEM_RECORDS_SCHEMA = "brandlab.ready.records.v1";
|
|
3
|
+
export declare const SITE_SYSTEM_RECORD_PATHS: Readonly<Record<SiteSystemRecordFile, string>>;
|
|
4
|
+
export interface SiteSystemRecord {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
/** ISO timestamp of the recording. */
|
|
7
|
+
readonly recordedAt: string;
|
|
8
|
+
/** Where the proof lives: a PR, a file path, a Notion page, a message. Never a secret. */
|
|
9
|
+
readonly evidence: string;
|
|
10
|
+
readonly note?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SiteSystemRecordsDocument {
|
|
13
|
+
readonly schemaVersion: typeof SITE_SYSTEM_RECORDS_SCHEMA;
|
|
14
|
+
readonly records: readonly SiteSystemRecord[];
|
|
15
|
+
}
|
|
16
|
+
/** The records in one file; an absent file has none. Throws when the file is not a valid records file. */
|
|
17
|
+
export declare function readSiteSystemRecords(root: string, file: SiteSystemRecordFile): SiteSystemRecord[];
|
|
18
|
+
/** Validates the id, then appends a dated record to launch.json or human.json (created when absent). */
|
|
19
|
+
export declare function appendSiteSystemRecord(root: string, input: {
|
|
20
|
+
id: string;
|
|
21
|
+
evidence: string;
|
|
22
|
+
note?: string;
|
|
23
|
+
now?: Date;
|
|
24
|
+
}): {
|
|
25
|
+
path: string;
|
|
26
|
+
record: SiteSystemRecord;
|
|
27
|
+
};
|
|
28
|
+
/** The latest record of each id. */
|
|
29
|
+
export declare function latestSiteSystemRecords(records: readonly SiteSystemRecord[]): Map<string, SiteSystemRecord>;
|
|
30
|
+
//# sourceMappingURL=records.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../src/site-system/records.ts"],"names":[],"mappings":"AAMA,OAAO,EAKL,KAAK,oBAAoB,EAC1B,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,0BAA0B,8BAA8B,CAAC;AAEtE,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAAC,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAGnF,CAAC;AAKF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,0FAA0F;IAC1F,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,aAAa,EAAE,OAAO,0BAA0B,CAAC;IAC1D,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC/C;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,oBAAoB,GAAG,gBAAgB,EAAE,CAgClG;AAED,wGAAwG;AACxG,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,IAAI,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,CA0B5C;AAED,oCAAoC;AACpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAO3G"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launch-day and human step records: append-only JSON files under docs/ready/, written by
|
|
3
|
+
* `brandlab-ready record <id> --evidence <ref> [--note <text>]`. The latest record of an id counts.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { SITE_SYSTEM_HUMAN_STEPS, SITE_SYSTEM_LAUNCH_STEPS, SITE_SYSTEM_RECORDED_CHECK_IDS, siteSystemRecordFile, } from "./steps.js";
|
|
8
|
+
export const SITE_SYSTEM_RECORDS_SCHEMA = "brandlab.ready.records.v1";
|
|
9
|
+
export const SITE_SYSTEM_RECORD_PATHS = {
|
|
10
|
+
launch: "docs/ready/launch.json",
|
|
11
|
+
human: "docs/ready/human.json",
|
|
12
|
+
};
|
|
13
|
+
/** Evidence and notes are one line each, so the report table stays readable. */
|
|
14
|
+
const MAX_TEXT_LENGTH = 500;
|
|
15
|
+
/** The records in one file; an absent file has none. Throws when the file is not a valid records file. */
|
|
16
|
+
export function readSiteSystemRecords(root, file) {
|
|
17
|
+
const path = SITE_SYSTEM_RECORD_PATHS[file];
|
|
18
|
+
const absolute = join(root, path);
|
|
19
|
+
if (!existsSync(absolute))
|
|
20
|
+
return [];
|
|
21
|
+
let parsed;
|
|
22
|
+
try {
|
|
23
|
+
parsed = JSON.parse(readFileSync(absolute, "utf8"));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new Error(`${path} is not valid JSON.`);
|
|
27
|
+
}
|
|
28
|
+
if (!isRecord(parsed) || parsed.schemaVersion !== SITE_SYSTEM_RECORDS_SCHEMA || !Array.isArray(parsed.records)) {
|
|
29
|
+
throw new Error(`${path} is not a ready records file (schemaVersion ${SITE_SYSTEM_RECORDS_SCHEMA} with a records list).`);
|
|
30
|
+
}
|
|
31
|
+
return parsed.records.map((record, index) => {
|
|
32
|
+
if (!isRecord(record) ||
|
|
33
|
+
typeof record.id !== "string" ||
|
|
34
|
+
typeof record.recordedAt !== "string" ||
|
|
35
|
+
Number.isNaN(Date.parse(record.recordedAt)) ||
|
|
36
|
+
typeof record.evidence !== "string" ||
|
|
37
|
+
!record.evidence.trim() ||
|
|
38
|
+
(record.note !== undefined && typeof record.note !== "string")) {
|
|
39
|
+
throw new Error(`${path} records[${index}] needs an id, a recordedAt date and evidence.`);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
id: record.id,
|
|
43
|
+
recordedAt: record.recordedAt,
|
|
44
|
+
evidence: record.evidence,
|
|
45
|
+
...(record.note !== undefined ? { note: record.note } : {}),
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/** Validates the id, then appends a dated record to launch.json or human.json (created when absent). */
|
|
50
|
+
export function appendSiteSystemRecord(root, input) {
|
|
51
|
+
const file = siteSystemRecordFile(input.id);
|
|
52
|
+
if (!file) {
|
|
53
|
+
throw new Error(`${input.id} is not a step that can be recorded. Launch-day steps and recorded checks: ${[
|
|
54
|
+
...SITE_SYSTEM_LAUNCH_STEPS.map((step) => step.id),
|
|
55
|
+
...SITE_SYSTEM_RECORDED_CHECK_IDS,
|
|
56
|
+
].join(", ")}. Human steps: ${SITE_SYSTEM_HUMAN_STEPS.map((step) => step.id).join(", ")}.`);
|
|
57
|
+
}
|
|
58
|
+
const evidence = checkedText(input.evidence, "--evidence");
|
|
59
|
+
const note = input.note === undefined ? undefined : checkedText(input.note, "--note");
|
|
60
|
+
const record = {
|
|
61
|
+
id: input.id,
|
|
62
|
+
recordedAt: (input.now ?? new Date()).toISOString(),
|
|
63
|
+
evidence,
|
|
64
|
+
...(note !== undefined ? { note } : {}),
|
|
65
|
+
};
|
|
66
|
+
const path = SITE_SYSTEM_RECORD_PATHS[file];
|
|
67
|
+
const document = {
|
|
68
|
+
schemaVersion: SITE_SYSTEM_RECORDS_SCHEMA,
|
|
69
|
+
records: [...readSiteSystemRecords(root, file), record],
|
|
70
|
+
};
|
|
71
|
+
mkdirSync(dirname(join(root, path)), { recursive: true });
|
|
72
|
+
writeFileSync(join(root, path), `${JSON.stringify(document, null, 2)}\n`);
|
|
73
|
+
return { path, record };
|
|
74
|
+
}
|
|
75
|
+
/** The latest record of each id. */
|
|
76
|
+
export function latestSiteSystemRecords(records) {
|
|
77
|
+
const latest = new Map();
|
|
78
|
+
for (const record of records) {
|
|
79
|
+
const previous = latest.get(record.id);
|
|
80
|
+
if (!previous || Date.parse(record.recordedAt) >= Date.parse(previous.recordedAt))
|
|
81
|
+
latest.set(record.id, record);
|
|
82
|
+
}
|
|
83
|
+
return latest;
|
|
84
|
+
}
|
|
85
|
+
function checkedText(value, flag) {
|
|
86
|
+
const text = value.trim();
|
|
87
|
+
if (!text)
|
|
88
|
+
throw new Error(`${flag} cannot be empty.`);
|
|
89
|
+
if (/[\r\n]/.test(text))
|
|
90
|
+
throw new Error(`${flag} must be one line.`);
|
|
91
|
+
if (text.length > MAX_TEXT_LENGTH)
|
|
92
|
+
throw new Error(`${flag} must be at most ${MAX_TEXT_LENGTH} characters.`);
|
|
93
|
+
return text;
|
|
94
|
+
}
|
|
95
|
+
function isRecord(value) {
|
|
96
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.js","sourceRoot":"","sources":["../../src/site-system/records.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Launch-day and human step records: append-only JSON files under docs/ready/, written by\n * `brandlab-ready record <id> --evidence <ref> [--note <text>]`. The latest record of an id counts.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport {\n SITE_SYSTEM_HUMAN_STEPS,\n SITE_SYSTEM_LAUNCH_STEPS,\n SITE_SYSTEM_RECORDED_CHECK_IDS,\n siteSystemRecordFile,\n type SiteSystemRecordFile,\n} from \"./steps.js\";\n\nexport const SITE_SYSTEM_RECORDS_SCHEMA = \"brandlab.ready.records.v1\";\n\nexport const SITE_SYSTEM_RECORD_PATHS: Readonly<Record<SiteSystemRecordFile, string>> = {\n launch: \"docs/ready/launch.json\",\n human: \"docs/ready/human.json\",\n};\n\n/** Evidence and notes are one line each, so the report table stays readable. */\nconst MAX_TEXT_LENGTH = 500;\n\nexport interface SiteSystemRecord {\n readonly id: string;\n /** ISO timestamp of the recording. */\n readonly recordedAt: string;\n /** Where the proof lives: a PR, a file path, a Notion page, a message. Never a secret. */\n readonly evidence: string;\n readonly note?: string;\n}\n\nexport interface SiteSystemRecordsDocument {\n readonly schemaVersion: typeof SITE_SYSTEM_RECORDS_SCHEMA;\n readonly records: readonly SiteSystemRecord[];\n}\n\n/** The records in one file; an absent file has none. Throws when the file is not a valid records file. */\nexport function readSiteSystemRecords(root: string, file: SiteSystemRecordFile): SiteSystemRecord[] {\n const path = SITE_SYSTEM_RECORD_PATHS[file];\n const absolute = join(root, path);\n if (!existsSync(absolute)) return [];\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(absolute, \"utf8\"));\n } catch {\n throw new Error(`${path} is not valid JSON.`);\n }\n if (!isRecord(parsed) || parsed.schemaVersion !== SITE_SYSTEM_RECORDS_SCHEMA || !Array.isArray(parsed.records)) {\n throw new Error(`${path} is not a ready records file (schemaVersion ${SITE_SYSTEM_RECORDS_SCHEMA} with a records list).`);\n }\n return parsed.records.map((record, index) => {\n if (\n !isRecord(record) ||\n typeof record.id !== \"string\" ||\n typeof record.recordedAt !== \"string\" ||\n Number.isNaN(Date.parse(record.recordedAt)) ||\n typeof record.evidence !== \"string\" ||\n !record.evidence.trim() ||\n (record.note !== undefined && typeof record.note !== \"string\")\n ) {\n throw new Error(`${path} records[${index}] needs an id, a recordedAt date and evidence.`);\n }\n return {\n id: record.id,\n recordedAt: record.recordedAt,\n evidence: record.evidence,\n ...(record.note !== undefined ? { note: record.note } : {}),\n };\n });\n}\n\n/** Validates the id, then appends a dated record to launch.json or human.json (created when absent). */\nexport function appendSiteSystemRecord(\n root: string,\n input: { id: string; evidence: string; note?: string; now?: Date },\n): { path: string; record: SiteSystemRecord } {\n const file = siteSystemRecordFile(input.id);\n if (!file) {\n throw new Error(\n `${input.id} is not a step that can be recorded. Launch-day steps and recorded checks: ${[\n ...SITE_SYSTEM_LAUNCH_STEPS.map((step) => step.id),\n ...SITE_SYSTEM_RECORDED_CHECK_IDS,\n ].join(\", \")}. Human steps: ${SITE_SYSTEM_HUMAN_STEPS.map((step) => step.id).join(\", \")}.`,\n );\n }\n const evidence = checkedText(input.evidence, \"--evidence\");\n const note = input.note === undefined ? undefined : checkedText(input.note, \"--note\");\n const record: SiteSystemRecord = {\n id: input.id,\n recordedAt: (input.now ?? new Date()).toISOString(),\n evidence,\n ...(note !== undefined ? { note } : {}),\n };\n const path = SITE_SYSTEM_RECORD_PATHS[file];\n const document: SiteSystemRecordsDocument = {\n schemaVersion: SITE_SYSTEM_RECORDS_SCHEMA,\n records: [...readSiteSystemRecords(root, file), record],\n };\n mkdirSync(dirname(join(root, path)), { recursive: true });\n writeFileSync(join(root, path), `${JSON.stringify(document, null, 2)}\\n`);\n return { path, record };\n}\n\n/** The latest record of each id. */\nexport function latestSiteSystemRecords(records: readonly SiteSystemRecord[]): Map<string, SiteSystemRecord> {\n const latest = new Map<string, SiteSystemRecord>();\n for (const record of records) {\n const previous = latest.get(record.id);\n if (!previous || Date.parse(record.recordedAt) >= Date.parse(previous.recordedAt)) latest.set(record.id, record);\n }\n return latest;\n}\n\nfunction checkedText(value: string, flag: string): string {\n const text = value.trim();\n if (!text) throw new Error(`${flag} cannot be empty.`);\n if (/[\\r\\n]/.test(text)) throw new Error(`${flag} must be one line.`);\n if (text.length > MAX_TEXT_LENGTH) throw new Error(`${flag} must be at most ${MAX_TEXT_LENGTH} characters.`);\n return text;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n"]}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ready report: every Site System check with its tier, whether it blocks for this site, its status and
|
|
3
|
+
* messages; the existing brandlab-verify search, contact and analytics families (shell tier); the launch-day and
|
|
4
|
+
* human steps; and the exceptions that apply. A blocking check that failed or did not run fails the gate.
|
|
5
|
+
*/
|
|
6
|
+
import type { BrandlabConformanceResult } from "../project.js";
|
|
7
|
+
import { type SiteSystemCheckKind, type SiteSystemCheckScope, type SiteSystemFinding, type SiteSystemTier } from "./checks.js";
|
|
8
|
+
import type { SiteSystemException, SiteSystemSettings } from "./config.js";
|
|
9
|
+
import { type SiteSystemRecord } from "./records.js";
|
|
10
|
+
import { type SiteSystemRecordFile } from "./steps.js";
|
|
11
|
+
export declare const SITE_SYSTEM_READY_SCHEMA = "brandlab.ready.v1";
|
|
12
|
+
export declare const SITE_SYSTEM_READY_REPORT_PATHS: {
|
|
13
|
+
readonly markdown: "docs/ready/ready-report.md";
|
|
14
|
+
readonly json: "docs/ready/ready-report.json";
|
|
15
|
+
};
|
|
16
|
+
export type SiteSystemReadyStatus = "pass" | "fail" | "not-run" | "excepted" | "not-applicable";
|
|
17
|
+
export interface SiteSystemReadyCheck {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly summary: string;
|
|
20
|
+
readonly tier: SiteSystemCheckScope;
|
|
21
|
+
readonly from: string;
|
|
22
|
+
/** A registry check kind, or `verify` for an existing brandlab-verify family. */
|
|
23
|
+
readonly kind: SiteSystemCheckKind | "verify";
|
|
24
|
+
readonly blocks: boolean;
|
|
25
|
+
/** Why the check reports without blocking. */
|
|
26
|
+
readonly reportsOnly?: string;
|
|
27
|
+
readonly status: SiteSystemReadyStatus;
|
|
28
|
+
readonly messages: readonly string[];
|
|
29
|
+
/** Warnings never block; a passing check with warnings shows as "pass (warning)". */
|
|
30
|
+
readonly warnings: readonly string[];
|
|
31
|
+
}
|
|
32
|
+
export interface SiteSystemReadyStep {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly summary: string;
|
|
35
|
+
readonly file: SiteSystemRecordFile;
|
|
36
|
+
readonly recorded: boolean;
|
|
37
|
+
readonly recordedAt?: string;
|
|
38
|
+
readonly evidence?: string;
|
|
39
|
+
readonly note?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface SiteSystemReadyException extends SiteSystemException {
|
|
42
|
+
readonly expired: boolean;
|
|
43
|
+
/** True when it turned a failing or unrun blocking check into `excepted`. */
|
|
44
|
+
readonly applied: boolean;
|
|
45
|
+
readonly state: "applied" | "expired" | "not-needed";
|
|
46
|
+
/** Why an exception is not needed: the check passes, does not apply or does not block. */
|
|
47
|
+
readonly reason?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface SiteSystemReadyVerdict {
|
|
50
|
+
/** The declared tier's blocking checks all pass or are excepted. */
|
|
51
|
+
readonly ready: boolean;
|
|
52
|
+
/** Ready, and every launch-day and human step recorded. */
|
|
53
|
+
readonly launchReady: boolean;
|
|
54
|
+
readonly exitCode: 0 | 1;
|
|
55
|
+
readonly summary: string;
|
|
56
|
+
readonly failed: readonly string[];
|
|
57
|
+
readonly notRun: readonly string[];
|
|
58
|
+
readonly excepted: readonly string[];
|
|
59
|
+
/** Reasons the run is not ready besides the checks (an unreadable config, a missing home or contact page). */
|
|
60
|
+
readonly blockers: readonly string[];
|
|
61
|
+
readonly stepsToRecord: readonly string[];
|
|
62
|
+
}
|
|
63
|
+
export interface SiteSystemReadyInputs {
|
|
64
|
+
/** The Playwright JSON report read, relative to the project, or null when there was none. */
|
|
65
|
+
readonly journeyReport: string | null;
|
|
66
|
+
readonly journeyTests: number;
|
|
67
|
+
readonly pageSource: "base-url" | "build" | "none";
|
|
68
|
+
readonly pages: readonly string[];
|
|
69
|
+
readonly runners: number;
|
|
70
|
+
readonly findingFiles: readonly string[];
|
|
71
|
+
}
|
|
72
|
+
export interface SiteSystemReadyReport {
|
|
73
|
+
readonly schemaVersion: typeof SITE_SYSTEM_READY_SCHEMA;
|
|
74
|
+
readonly generatedAt: string;
|
|
75
|
+
readonly projectId: string | null;
|
|
76
|
+
readonly release: string | null;
|
|
77
|
+
readonly tier: SiteSystemTier | null;
|
|
78
|
+
readonly newBuild: boolean;
|
|
79
|
+
/** Whether the run required every step to be recorded (`--launch`). */
|
|
80
|
+
readonly launch: boolean;
|
|
81
|
+
readonly verdict: SiteSystemReadyVerdict;
|
|
82
|
+
readonly checks: readonly SiteSystemReadyCheck[];
|
|
83
|
+
readonly steps: readonly SiteSystemReadyStep[];
|
|
84
|
+
readonly exceptions: readonly SiteSystemReadyException[];
|
|
85
|
+
readonly inputs: SiteSystemReadyInputs;
|
|
86
|
+
readonly problems: readonly string[];
|
|
87
|
+
}
|
|
88
|
+
/** The brandlab-verify result the shell tier carries: its search, contact and analytics families. */
|
|
89
|
+
export type SiteSystemVerifyResult = Pick<BrandlabConformanceResult, "verification" | "operationalFindings">;
|
|
90
|
+
export interface BuildSiteSystemReadyReportInput {
|
|
91
|
+
readonly projectId: string | null;
|
|
92
|
+
readonly siteSystem: SiteSystemSettings | null;
|
|
93
|
+
readonly release: string | null;
|
|
94
|
+
/** Journey, rule-module and supplied findings. */
|
|
95
|
+
readonly findings: readonly SiteSystemFinding[];
|
|
96
|
+
/** Null when brandlab-verify could not run: its families then did not run. */
|
|
97
|
+
readonly verify: SiteSystemVerifyResult | null;
|
|
98
|
+
readonly launchRecords: readonly SiteSystemRecord[];
|
|
99
|
+
readonly humanRecords: readonly SiteSystemRecord[];
|
|
100
|
+
readonly exceptions: readonly SiteSystemException[];
|
|
101
|
+
readonly inputs: SiteSystemReadyInputs;
|
|
102
|
+
readonly problems: readonly string[];
|
|
103
|
+
readonly now: Date;
|
|
104
|
+
/** Require every launch-day and human step (`brandlab-ready --launch`). */
|
|
105
|
+
readonly launch?: boolean;
|
|
106
|
+
/** Reasons the run is not ready whatever the checks say, as short clauses ("the home page could not be read"). */
|
|
107
|
+
readonly blockers?: readonly string[];
|
|
108
|
+
}
|
|
109
|
+
export declare function buildSiteSystemReadyReport(input: BuildSiteSystemReadyReportInput): SiteSystemReadyReport;
|
|
110
|
+
/** Date-only expiries last to the end of that day in Sydney; timestamps expire at that instant. */
|
|
111
|
+
export declare function isExceptionExpired(expiresAt: string | undefined, now: Date): boolean;
|
|
112
|
+
/** YYYY-MM-DD in Sydney. */
|
|
113
|
+
export declare function sydneyDate(date: Date): string;
|
|
114
|
+
/** Plain English for Romie and agents: the verdict first, then the tables. */
|
|
115
|
+
export declare function renderSiteSystemReadyMarkdown(report: SiteSystemReadyReport): string;
|
|
116
|
+
//# sourceMappingURL=report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/site-system/report.ts"],"names":[],"mappings":"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"}
|