| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- import { existsSync } from 'node:fs'
- import { readFile, stat } from 'node:fs/promises'
- import { join, resolve } from 'node:path'
- import { loadEcosystemReleaseContext } from './lib/ecosystem-packages.mjs'
- const sourceRoot = process.cwd()
- const baselinePath = join(sourceRoot, 'ecosystem', 'experience-baseline.json')
- const smokeMatrixPath = join(sourceRoot, 'ecosystem', 'smoke-matrix.json')
- function fail(message) {
- throw new Error(`[experience-baseline] ${message}`)
- }
- async function readJson(path) {
- return JSON.parse(await readFile(path, 'utf8'))
- }
- async function assertFile(path, label) {
- if (!existsSync(path)) {
- fail(`Missing ${label}: ${path}`)
- }
- const fileStat = await stat(path)
- if (!fileStat.isFile()) {
- fail(`${label} is not a file: ${path}`)
- }
- }
- function assertUnique(items, label, selectId = item => item.id) {
- const seen = new Set()
- for (const item of items || []) {
- const id = selectId(item)
- if (!id) {
- fail(`${label} contains an item without id`)
- }
- if (seen.has(id)) {
- fail(`${label} contains duplicate id ${id}`)
- }
- seen.add(id)
- }
- return seen
- }
- function assertPackageScript(packageJson, scriptName, label) {
- if (!packageJson.scripts?.[scriptName]) {
- fail(`${label} references missing package script ${scriptName}`)
- }
- }
- function packageEntryByName(entries, rootPackage, packageName) {
- if (rootPackage.name === packageName) {
- return {
- packageName,
- packageDir: '.',
- packageJson: rootPackage,
- absoluteDir: sourceRoot
- }
- }
- return entries.find(entry => entry.packageName === packageName)
- }
- const [baseline, smokeMatrix, releaseContext] = await Promise.all([
- readJson(baselinePath),
- readJson(smokeMatrixPath),
- loadEcosystemReleaseContext(sourceRoot)
- ])
- const { rootPackage, wrapperManifest, entries } = releaseContext
- if (baseline.schemaVersion !== 1) {
- fail(`Unsupported schemaVersion ${baseline.schemaVersion}`)
- }
- if (baseline.baseline?.sourceBranch !== wrapperManifest.sourceBranch) {
- fail(`Baseline sourceBranch must match wrappers.json sourceBranch ${wrapperManifest.sourceBranch}`)
- }
- if (baseline.baseline?.rootPackage !== rootPackage.name) {
- fail(`Baseline rootPackage must match package.json name ${rootPackage.name}`)
- }
- for (const scriptName of baseline.requiredScripts || []) {
- assertPackageScript(rootPackage, scriptName, 'requiredScripts')
- }
- const featureIds = assertUnique(baseline.featureGroups, 'featureGroups')
- for (const requiredFeature of [
- 'source-loading',
- 'options',
- 'toolbar-operations',
- 'lifecycle-events',
- 'document-navigation',
- 'layout-visual',
- 'print-export',
- 'wrapper-contract'
- ]) {
- if (!featureIds.has(requiredFeature)) {
- fail(`Missing required feature group ${requiredFeature}`)
- }
- }
- const evidence = new Set()
- for (const featureGroup of baseline.featureGroups || []) {
- if (!Array.isArray(featureGroup.evidence) || !featureGroup.evidence.length) {
- fail(`Feature group ${featureGroup.id} must declare evidence`)
- }
- for (const item of featureGroup.evidence) {
- evidence.add(item)
- }
- }
- for (const requiredEvidence of baseline.requiredEvidence || []) {
- if (!evidence.has(requiredEvidence) && !baseline.cases?.some(item => item.assertions?.includes(requiredEvidence))) {
- fail(`Required evidence ${requiredEvidence} is not covered by feature groups or cases`)
- }
- }
- const wrapperById = new Map(wrapperManifest.wrappers.map(wrapper => [wrapper.id, wrapper]))
- const surfaceIds = assertUnique(baseline.surfaces, 'surfaces')
- for (const requiredSurface of [
- 'vue3-current-component',
- 'compare-page',
- 'pure-web-native-viewer',
- 'react-native-wrapper',
- 'pure-js-native-wrapper',
- 'script-tag-iife'
- ]) {
- if (!surfaceIds.has(requiredSurface)) {
- fail(`Missing required baseline surface ${requiredSurface}`)
- }
- }
- for (const surface of baseline.surfaces || []) {
- const entry = packageEntryByName(entries, rootPackage, surface.packageName)
- if (!entry) {
- fail(`Surface ${surface.id} references unknown package ${surface.packageName}`)
- }
- if (surface.wrapperId) {
- const wrapper = wrapperById.get(surface.wrapperId)
- if (!wrapper) {
- fail(`Surface ${surface.id} references unknown wrapper ${surface.wrapperId}`)
- }
- if (
- surface.packageName !== wrapper.packageName &&
- !wrapper.historicalPackages.includes(surface.packageName)
- ) {
- fail(`Surface ${surface.id} package ${surface.packageName} is not ${wrapper.packageName} or one of its historical aliases`)
- }
- }
- for (const feature of surface.preserves || []) {
- if (!featureIds.has(feature)) {
- fail(`Surface ${surface.id} preserves unknown feature group ${feature}`)
- }
- }
- for (const scriptName of surface.smokeScripts || []) {
- assertPackageScript(rootPackage, scriptName, `Surface ${surface.id}`)
- }
- for (const entryFile of surface.entryFiles || []) {
- await assertFile(resolve(sourceRoot, entryFile), `surface ${surface.id} entry file`)
- }
- }
- const smokeCaseIds = new Set([
- ...(smokeMatrix.cases || []).map(item => item.id),
- ...(smokeMatrix.wrapperCases || []).map(item => item.id)
- ])
- const smokeSurfaces = new Set(smokeMatrix.surfaces || [])
- const smokeMatrixWrappers = new Set((smokeMatrix.wrappers || []).map(wrapper => wrapper.id))
- assertUnique(baseline.cases, 'cases')
- const coveredSurfaces = new Set()
- const coveredFeatures = new Set()
- for (const testCase of baseline.cases || []) {
- if (!surfaceIds.has(testCase.surface)) {
- fail(`Case ${testCase.id} references unknown surface ${testCase.surface}`)
- }
- coveredSurfaces.add(testCase.surface)
- if (testCase.smokeCaseId && !smokeCaseIds.has(testCase.smokeCaseId)) {
- fail(`Case ${testCase.id} references unknown smoke matrix case ${testCase.smokeCaseId}`)
- }
- if (testCase.wrapperId) {
- if (!wrapperById.has(testCase.wrapperId)) {
- fail(`Case ${testCase.id} references unknown wrapper ${testCase.wrapperId}`)
- }
- if (!smokeMatrixWrappers.has(testCase.wrapperId)) {
- fail(`Case ${testCase.id} wrapper ${testCase.wrapperId} is missing from smoke-matrix wrappers`)
- }
- }
- const surface = baseline.surfaces.find(item => item.id === testCase.surface)
- if (surface?.kind !== 'baseline-page' && surface?.kind !== 'baseline-component' && surface?.wrapperId) {
- const smokeSurface = smokeMatrix.wrappers?.find(wrapper => wrapper.id === surface.wrapperId)?.surface
- if (smokeSurface && !smokeSurfaces.has(smokeSurface)) {
- fail(`Case ${testCase.id} maps to unknown smoke surface ${smokeSurface}`)
- }
- }
- for (const scriptName of testCase.scripts || []) {
- assertPackageScript(rootPackage, scriptName, `Case ${testCase.id}`)
- }
- if (!Array.isArray(testCase.features) || !testCase.features.length) {
- fail(`Case ${testCase.id} must declare feature coverage`)
- }
- for (const feature of testCase.features) {
- if (!featureIds.has(feature)) {
- fail(`Case ${testCase.id} references unknown feature group ${feature}`)
- }
- coveredFeatures.add(feature)
- }
- if (!Array.isArray(testCase.assertions) || !testCase.assertions.length) {
- fail(`Case ${testCase.id} must declare assertions`)
- }
- }
- for (const surfaceId of surfaceIds) {
- if (!coveredSurfaces.has(surfaceId)) {
- fail(`No baseline case covers surface ${surfaceId}`)
- }
- }
- for (const featureId of featureIds) {
- if (!coveredFeatures.has(featureId)) {
- fail(`No baseline case covers feature group ${featureId}`)
- }
- }
- console.log(
- `[experience-baseline] Verified ${baseline.surfaces.length} surfaces, ${baseline.cases.length} baseline cases, ${baseline.featureGroups.length} feature groups, and ${baseline.requiredScripts.length} required scripts.`
- )
|