diff --git a/deno.json b/deno.json index a24861469c..c718096f6f 100644 --- a/deno.json +++ b/deno.json @@ -575,7 +575,7 @@ "test:all-runtimes": "deno task test:unit && deno task test:node && deno task test:bun", "test:e2e": "deno task test:e2e:playwright", "test:e2e:playwright": "deno run -A npm:playwright@1.60.0 test --config=tests/e2e/playwright.config.cjs", - "test:e2e:rsc-browser": "deno task generate && DENO_TESTING=1 VF_DISABLE_LRU_INTERVAL=1 SSR_TRANSFORM_PER_PROJECT_LIMIT=0 REVALIDATION_PER_PROJECT_LIMIT=0 NODE_ENV=production LOG_FORMAT=text deno test --no-check --allow-all tests/e2e/regressions/rsc-proxy-hydration.test.ts tests/e2e/regressions/2026-07-27-legacy-router-hydration.test.ts tests/e2e/regressions/2026-07-27-release-asset-page-island-hydration.test.ts tests/e2e/regressions/2026-08-14-server-layout-spa-fallback.test.ts --unstable-worker-options --unstable-net", + "test:e2e:rsc-browser": "deno task generate && DENO_TESTING=1 VF_DISABLE_LRU_INTERVAL=1 SSR_TRANSFORM_PER_PROJECT_LIMIT=0 REVALIDATION_PER_PROJECT_LIMIT=0 NODE_ENV=production LOG_FORMAT=text deno test --no-check --allow-all tests/e2e/regressions/rsc-proxy-hydration.test.ts tests/e2e/regressions/2026-07-27-legacy-router-hydration.test.ts tests/e2e/regressions/2026-07-27-release-asset-page-island-hydration.test.ts tests/e2e/regressions/2026-08-14-server-layout-spa-fallback.test.ts tests/e2e/regressions/dev-ui-browser-bundle.test.ts --unstable-worker-options --unstable-net", "test:e2e:binary": "deno task generate && deno test --allow-all tests/integration/compiled-binary-e2e.test.ts", "test:e2e:binary:fresh": "deno task generate && VERYFRONT_BINARY_FRESH=1 deno test --allow-all tests/integration/compiled-binary-e2e.test.ts", "test:e2e:templates": "deno run --allow-all scripts/test/template-runtime-e2e.ts", diff --git a/scripts/test/run-suite.test.ts b/scripts/test/run-suite.test.ts index 17e787d02c..2cfab35835 100644 --- a/scripts/test/run-suite.test.ts +++ b/scripts/test/run-suite.test.ts @@ -16,6 +16,8 @@ import { buildDenoSuiteCommandArgs, parseDenoSuiteArgs, } from "./run-deno-suite.ts"; +import { LEAF_TEST_SUITES } from "./suites.ts"; +import { classifyTestPath } from "./test-layout.ts"; import { formatSuitePlan, planSuiteFiles, @@ -57,6 +59,21 @@ describe("suite planning parity", () => { } }); + it("runs every root the unit suite claims to own", async () => { + // Regression guard. suites.ts, deno.json's test.include and + // suites.test.ts all place extensions/ and react/ in the unit suite, but + // UNIT_ROOTS omitted them, so 90 extension test files never executed while + // extensions/*/src/** still counted toward the 80% coverage gate. + const plan = await planSuiteFiles({ suite: "coverage:unit" }); + + for (const root of LEGACY_UNIT_ROOTS) { + assert( + plan.files.some((path) => path.startsWith(`${root}/`)), + `the unit suite owns ${root}/ but planned no test file from it`, + ); + } + }); + it("keeps runtime-guarded Deno references eligible for Node", async () => { // `tests/test-file-utils.mjs` owns which sources count as Deno-dependent, // and a file opting out with the runtime-guarded header runs on Node. A @@ -284,11 +301,46 @@ describe("migration command surface", () => { assert(match, "pre-push must invoke a named E2E task"); assert(config.tasks[match[1]], `${match[1]} must exist in deno.json`); }); + + it("routes the Dev UI browser bundle test through the browser E2E lane", async () => { + const config = JSON.parse( + await Deno.readTextFile(new URL("../../deno.json", import.meta.url)), + ); + const task = config.tasks["test:e2e:rsc-browser"] as string | undefined; + const browserBundleTest = + "tests/e2e/regressions/dev-ui-browser-bundle.test.ts"; + + assert(task, "browser E2E task must remain defined"); + assert( + task.includes(browserBundleTest), + "the Chromium-backed Dev UI bundle test needs an explicit browser-capable runner", + ); + assertEquals(classifyTestPath(browserBundleTest), { + kind: "canonical", + path: browserBundleTest, + level: "e2e", + suite: "e2e", + runner: "deno", + }); + }); }); +// Read from the suite registry rather than restated here. A second hand-kept +// copy of the roots is what let ownership and execution drift in the first +// place: it would keep passing while a newly owned root went unplanned. +// scripts/ is excluded for the reason documented on UNPLANNABLE_UNIT_ROOTS in +// run-suite.ts -- deno.json's root `exclude` hides it from the main config. +const LEGACY_UNIT_ROOTS = (LEAF_TEST_SUITES + .find((suite) => suite.id === "unit")?.pathSelectors ?? []) + .filter((root) => root !== "scripts/") + .map((root) => root.replace(/\/$/, "")); + async function legacyUnitParallelFiles(): Promise { - const files = await collectLegacyTestFiles(["src", "cli", "templates"]); - const excluded = new Set([...UNIT_CWD_FILES, ...UNIT_CWD_EXCLUSION_FILES]); + const files = await collectLegacyTestFiles(LEGACY_UNIT_ROOTS); + const excluded = new Set([ + ...UNIT_CWD_FILES, + ...UNIT_CWD_EXCLUSION_FILES, + ]); return sorted( files.filter((path) => !path.includes(".integration.test.ts") && @@ -308,7 +360,7 @@ async function legacyCliIntegrationFiles(): Promise { async function legacyUnitCoverageFiles(): Promise { return sorted( - (await collectLegacyTestFiles(["src", "cli", "templates"])) + (await collectLegacyTestFiles(LEGACY_UNIT_ROOTS)) .filter((path) => !/\.integration\.test\.tsx?$/.test(path)) .filter((path) => !path.startsWith("src/workflow/__tests__/")), ); diff --git a/scripts/test/run-suite.ts b/scripts/test/run-suite.ts index 85e553d35c..e06226342b 100644 --- a/scripts/test/run-suite.ts +++ b/scripts/test/run-suite.ts @@ -7,6 +7,7 @@ import { } from "../../tests/test-file-utils.mjs"; import { DENO_ONLY_TESTS } from "../../tests/deno-only-tests.mjs"; import { discoverTests } from "./test-layout.ts"; +import { LEAF_TEST_SUITES } from "./suites.ts"; export type SuitePlanId = | "unit:parallel" @@ -42,7 +43,28 @@ export interface SuiteFilePlan { readonly files: readonly string[]; } -const UNIT_ROOTS = ["src/", "cli/", "templates/"]; +// deno.json's root `exclude` lists scripts/, so those files are undiscoverable +// under the main config -- `deno test` reports "No test modules found" for them. +// They run through the dedicated `test:scripts` task with scripts/test.deno.json +// instead, so the unit planner skips the root while the registry still owns it. +const UNPLANNABLE_UNIT_ROOTS = new Set(["scripts/"]); + +/** + * Derived from the unit suite's own `pathSelectors` so ownership and execution + * cannot drift: a root added in suites.ts is planned here without a second + * edit. Hardcoding the list is what left extensions/ and react/ owned by the + * unit suite -- `resolveLeafSuiteOwners` said so and suites.test.ts asserted it + * -- while no runner selected them, so 90 extension test files never executed + * even though `--include=src/` still counted every extension package's own + * `src` directory toward the coverage gate. + */ +const UNIT_ROOTS = (() => { + const unit = LEAF_TEST_SUITES.find((suite) => suite.id === "unit"); + if (!unit) { + throw new Error("The leaf suite registry no longer defines a unit suite."); + } + return unit.pathSelectors.filter((root) => !UNPLANNABLE_UNIT_ROOTS.has(root)); +})(); const UNIT_CWD_FILES = [ "cli/router.test.ts", "cli/app/operations/project-creation.test.ts", diff --git a/scripts/test/runtime-e2e-helpers.ts b/scripts/test/runtime-e2e-helpers.ts index 0112c4c43e..39df6d58fe 100644 --- a/scripts/test/runtime-e2e-helpers.ts +++ b/scripts/test/runtime-e2e-helpers.ts @@ -159,16 +159,41 @@ export async function inspectModuleExports( * * `veryfront` pins its co-published extensions to its own exact version, so a * fixture given only the root tarball resolves those pins from the npm - * registry. On a release-cut branch that version is not published yet — it is - * what the release job publishes — and the install fails with ETARGET. Packing + * registry. On a release-cut branch that version is not published yet. It is + * what the release job publishes, and the install fails with ETARGET. Packing * the extensions alongside the root keeps the whole matched set local, which is * also the set a user receives. */ export interface PackedWorkspace { /** Tarball for the root `veryfront` package. */ readonly root: string; - /** Tarballs for every co-published `@veryfront/*` package, in pack order. */ - readonly extensions: readonly string[]; + /** Names of the extensions pinned by the root package itself. */ + readonly rootExtensionNames: readonly string[]; + /** Named tarballs for root and selected-template extensions, in pack order. */ + readonly extensions: readonly PackedExtension[]; +} + +export interface PackedExtension { + readonly name: string; + readonly tarball: string; +} + +const VERYFRONT_EXTENSION_PREFIX = "@veryfront/ext-"; +const VERYFRONT_SCOPE_PREFIX = "@veryfront/"; + +function extensionDirectoryName(name: string): string { + if ( + !name.startsWith(VERYFRONT_EXTENSION_PREFIX) || + name.length === VERYFRONT_EXTENSION_PREFIX.length || + name.slice(VERYFRONT_SCOPE_PREFIX.length).includes("/") + ) { + throw new Error(`Unsupported first-party extension package: ${name}`); + } + return name.slice(VERYFRONT_SCOPE_PREFIX.length); +} + +function compareNames(left: string, right: string): number { + return left < right ? -1 : left > right ? 1 : 0; } async function packOne(packageDir: string, packDir: string): Promise { @@ -192,16 +217,16 @@ async function packOne(packageDir: string, packDir: string): Promise { } /** - * The extensions the root pins to its own exact version — the only ones whose + * The extensions the root pins to its own exact version, the only ones whose * pin cannot be satisfied from the registry on a release-cut branch. * * Derived from the built root manifest rather than a directory listing: the * build emits 29 extension packages but the root co-publishes 6, and installing * the other 23 would change what the fixture exercises. */ -async function listCoPublishedExtensionDirs( +async function listCoPublishedExtensions( rootDir: string, -): Promise { +): Promise> { const manifest = JSON.parse( await Deno.readTextFile(`${rootDir}/npm/package.json`), ) as { version?: string; dependencies?: Record }; @@ -211,80 +236,231 @@ async function listCoPublishedExtensionDirs( } return Object.entries(manifest.dependencies ?? {}) .filter(([name, range]) => - name.startsWith("@veryfront/") && range === version - ) - .map(([name]) => - `${rootDir}/npm/extensions/${name.slice("@veryfront/".length)}` + name.startsWith(VERYFRONT_EXTENSION_PREFIX) && range === version ) - .sort(); + .map(([name]) => ({ + name, + directory: `${rootDir}/npm/extensions/${extensionDirectoryName(name)}`, + })) + .sort((left, right) => compareNames(left.name, right.name)); } export async function packNpmPackage( rootDir: string, workDir: string, + additionalExtensionNames: readonly string[] = [], ): Promise { const packDir = `${workDir}/packed`; await Deno.mkdir(packDir, { recursive: true }); const root = await packOne(`${rootDir}/npm`, packDir); - const extensions: string[] = []; - for (const dir of await listCoPublishedExtensionDirs(rootDir)) { - extensions.push(await packOne(dir, packDir)); + const coPublished = await listCoPublishedExtensions(rootDir); + const directories = new Map( + coPublished.map(({ name, directory }) => [name, directory]), + ); + for (const name of additionalExtensionNames) { + directories.set( + name, + `${rootDir}/npm/extensions/${extensionDirectoryName(name)}`, + ); } - return { root, extensions }; + const extensions: PackedExtension[] = []; + for ( + const [name, directory] of [...directories].sort(([left], [right]) => + compareNames(left, right) + ) + ) { + extensions.push({ name, tarball: await packOne(directory, packDir) }); + } + return { + root, + rootExtensionNames: coPublished.map(({ name }) => name), + extensions, + }; } -/** Package name a packed tarball installs as, read from its own manifest. */ -async function readPackedName(tarballPath: string): Promise { - const result = await runChecked("tar", [ - "-xzOf", - tarballPath, - "package/package.json", - ], { - timeoutMs: 30_000, +/** Resolve selected named packages to local file dependencies. */ +export function packedFileDependencies( + packed: PackedWorkspace, + names: readonly string[], +): Record { + const dependencies: Record = {}; + for (const { name, tarball } of selectPackedExtensions(packed, names)) { + dependencies[name] = `file:${tarball}`; + } + return dependencies; +} + +function selectPackedExtensions( + packed: PackedWorkspace, + names: readonly string[], +): PackedExtension[] { + const extensions = new Map( + packed.extensions.map((extension) => [extension.name, extension]), + ); + return [...new Set(names)].sort().map((name) => { + const extension = extensions.get(name); + if (!extension) { + throw new Error(`Packed extension is unavailable: ${name}`); + } + return extension; }); - const name = JSON.parse(result.stdout).name; - if (typeof name !== "string" || name.length === 0) { - throw new Error(`packed tarball has no name: ${tarballPath}`); +} + +async function extractPackedDenoDependencies( + packed: PackedWorkspace, + names: readonly string[], + destinationRoot: string, + dependencyPrefix: string, +): Promise> { + const dependencies: Record = {}; + for (const { name, tarball } of selectPackedExtensions(packed, names)) { + const directoryName = extensionDirectoryName(name); + const destination = `${destinationRoot}/${directoryName}`; + await Deno.mkdir(destination, { recursive: true }); + await runChecked("tar", ["-xzf", tarball, "-C", destination], { + timeoutMs: 30_000, + }); + dependencies[name] = `file:${dependencyPrefix}/${directoryName}/package`; + } + return dependencies; +} + +async function replaceDirectorySymlink( + path: string, + target: string, +): Promise { + try { + await Deno.remove(path, { recursive: true }); + } catch (error) { + if (!(error instanceof Deno.errors.NotFound)) throw error; } - return name; + await Deno.mkdir(path.slice(0, path.lastIndexOf("/")), { recursive: true }); + await Deno.symlink(target, path, { type: "dir" }); +} + +async function preparePackedDenoDependencies( + packed: PackedWorkspace, + names: readonly string[], + destinationRoot: string, + dependencyPrefix: string, + veryfrontPackageDir: string, +): Promise> { + // Deno resolves a file dependency from its extracted real path instead of + // hoisting its dependencies and Veryfront peer into the caller's tree. + // Install each trusted packed extension in place, then link its Veryfront + // peer to the matching extracted root package. + const dependencies = await extractPackedDenoDependencies( + packed, + names, + destinationRoot, + dependencyPrefix, + ); + for (const { name } of selectPackedExtensions(packed, names)) { + const extensionPackageDir = `${destinationRoot}/${ + extensionDirectoryName(name) + }/package`; + const manifestPath = `${extensionPackageDir}/package.json`; + const manifest = JSON.parse(await Deno.readTextFile(manifestPath)); + if (manifest.peerDependencies?.veryfront !== undefined) { + delete manifest.peerDependencies.veryfront; + if (Object.keys(manifest.peerDependencies).length === 0) { + delete manifest.peerDependencies; + } + await Deno.writeTextFile( + manifestPath, + `${JSON.stringify(manifest, null, 2)}\n`, + ); + } + await runChecked("deno", ["install"], { + cwd: extensionPackageDir, + timeoutMs: 180_000, + }); + await replaceDirectorySymlink( + `${extensionPackageDir}/node_modules/veryfront`, + veryfrontPackageDir, + ); + } + return dependencies; } async function updateVeryfrontDependency( projectDir: string, packed: PackedWorkspace, + extensionNames: readonly string[], + runtime: "node" | "bun", ): Promise { const packagePath = `${projectDir}/package.json`; const pkg = JSON.parse(await Deno.readTextFile(packagePath)); pkg.dependencies ??= {}; pkg.dependencies.veryfront = `file:${packed.root}`; - // The root pins these to its own version. Naming them here keeps the install - // entirely local; without them the pins go to the registry. - for (const tarball of packed.extensions) { - pkg.dependencies[await readPackedName(tarball)] = `file:${tarball}`; + const localExtensions = packedFileDependencies(packed, extensionNames); + Object.assign(pkg.dependencies, localExtensions); + if (runtime === "bun") { + pkg.overrides ??= {}; + Object.assign(pkg.overrides, localExtensions); } await Deno.writeTextFile(packagePath, `${JSON.stringify(pkg, null, 2)}\n`); } async function usePackedVeryfrontDenoTasks( projectDir: string, - tarballPath: string, + packed: PackedWorkspace, + projectExtensionNames: readonly string[], ): Promise { const packagePath = `${projectDir}/package.json`; const pkg = JSON.parse(await Deno.readTextFile(packagePath)); delete pkg.dependencies?.veryfront; - await Deno.writeTextFile(packagePath, `${JSON.stringify(pkg, null, 2)}\n`); + pkg.dependencies ??= {}; const packedCliDir = `${projectDir}/.veryfront-packed-cli`; await Deno.mkdir(packedCliDir, { recursive: true }); - await runChecked("tar", ["-xzf", tarballPath, "-C", packedCliDir], { + await runChecked("tar", ["-xzf", packed.root, "-C", packedCliDir], { timeoutMs: 30_000, }); + const packedCliPackageDir = `${packedCliDir}/package`; + const packedCliPackagePath = `${packedCliPackageDir}/package.json`; + const packedCliPackage = JSON.parse( + await Deno.readTextFile(packedCliPackagePath), + ); + packedCliPackage.dependencies ??= {}; + Object.assign( + packedCliPackage.dependencies, + await preparePackedDenoDependencies( + packed, + packed.rootExtensionNames, + `${packedCliPackageDir}/.veryfront-local-extensions`, + "./.veryfront-local-extensions", + packedCliPackageDir, + ), + ); + await Deno.writeTextFile( + packedCliPackagePath, + `${JSON.stringify(packedCliPackage, null, 2)}\n`, + ); await runChecked("deno", ["install"], { - cwd: `${packedCliDir}/package`, + cwd: packedCliPackageDir, timeoutMs: 180_000, }); + await replaceDirectorySymlink( + `${packedCliPackageDir}/node_modules/veryfront`, + packedCliPackageDir, + ); + + Object.assign( + pkg.dependencies, + await preparePackedDenoDependencies( + packed, + projectExtensionNames, + `${projectDir}/.veryfront-packed-extensions`, + "./.veryfront-packed-extensions", + packedCliPackageDir, + ), + ); + await Deno.writeTextFile(packagePath, `${JSON.stringify(pkg, null, 2)}\n`); - const cliPath = JSON.stringify(`${packedCliDir}/package/esm/cli/main.js`); + const cliPath = JSON.stringify( + `${packedCliPackageDir}/esm/cli/main.js`, + ); const denoConfigPath = `${projectDir}/deno.json`; const config = JSON.parse(await Deno.readTextFile(denoConfigPath)); config.tasks ??= {}; @@ -417,6 +593,7 @@ export async function scaffoldProject( packed: PackedWorkspace, template: string, runtime: RuntimeName, + templateExtensionNames: readonly string[] = [], ): Promise { const caseDir = `${workDir}/${runtime}-${template}`; const projectName = `vf-${runtime}-${template}`; @@ -427,7 +604,12 @@ export async function scaffoldProject( // `npm exec` resolves the CLI package's dependencies into its own prefix, // so the co-published extensions have to be named here too. Otherwise this // reaches the registry for a version that is not published yet. - ...[packed.root, ...packed.extensions].flatMap(( + ...[ + packed.root, + ...packed.extensions + .filter(({ name }) => packed.rootExtensionNames.includes(name)) + .map(({ tarball }) => tarball), + ].flatMap(( tarball, ) => ["--package", tarball]), "--", @@ -452,9 +634,18 @@ export async function scaffoldProject( const projectDir = `${caseDir}/${projectName}`; if (runtime === "deno") { - await usePackedVeryfrontDenoTasks(projectDir, packed.root); + await usePackedVeryfrontDenoTasks( + projectDir, + packed, + templateExtensionNames, + ); } else { - await updateVeryfrontDependency(projectDir, packed); + await updateVeryfrontDependency( + projectDir, + packed, + [...packed.rootExtensionNames, ...templateExtensionNames], + runtime, + ); } return projectDir; diff --git a/scripts/test/runtime-inference-critical-flow.test.ts b/scripts/test/runtime-inference-critical-flow.test.ts index ce244a6768..8c17d9664f 100644 --- a/scripts/test/runtime-inference-critical-flow.test.ts +++ b/scripts/test/runtime-inference-critical-flow.test.ts @@ -20,6 +20,7 @@ import { } from "./runtime-inference-critical-flow.ts"; import { inspectModuleExports, + packedFileDependencies, parseCommaSeparatedFlag, } from "./runtime-e2e-helpers.ts"; import { withMockFetch } from "#veryfront/testing/mock-fetch.ts"; @@ -98,6 +99,41 @@ function unresolvedCancellationFields(): { } describe("runtime inference critical-flow pure contract", () => { + it("maps only selected packed extensions to local file dependencies", () => { + const packed = { + root: "/packs/veryfront.tgz", + rootExtensionNames: ["@veryfront/ext-bundler-esbuild"], + extensions: [ + { + name: "@veryfront/ext-bundler-esbuild", + tarball: "/packs/ext-bundler-esbuild.tgz", + }, + { + name: "@veryfront/ext-content-mdx", + tarball: "/packs/ext-content-mdx.tgz", + }, + ], + }; + + assertEquals( + packedFileDependencies(packed, packed.rootExtensionNames), + { + "@veryfront/ext-bundler-esbuild": "file:/packs/ext-bundler-esbuild.tgz", + }, + ); + assertEquals( + packedFileDependencies(packed, ["@veryfront/ext-content-mdx"]), + { + "@veryfront/ext-content-mdx": "file:/packs/ext-content-mdx.tgz", + }, + ); + assertThrows( + () => packedFileDependencies(packed, ["@veryfront/ext-missing"]), + Error, + "Packed extension is unavailable: @veryfront/ext-missing", + ); + }); + it("selects all runtimes by default in stable order", () => { assertEquals( parseRuntimeSelection([]), diff --git a/scripts/test/template-runtime-e2e.test.ts b/scripts/test/template-runtime-e2e.test.ts index 15591be99b..b8e6501b72 100644 --- a/scripts/test/template-runtime-e2e.test.ts +++ b/scripts/test/template-runtime-e2e.test.ts @@ -1,6 +1,9 @@ import { assertEquals } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; -import { getDevServerCommand } from "./template-runtime-e2e.ts"; +import { + getDevServerCommand, + getTemplateExtensionNames, +} from "./template-runtime-e2e.ts"; import { getDevServerEnvironment, inspectModuleExports, @@ -19,6 +22,7 @@ describe("template runtime E2E commands", () => { "assertCondition", "ensureCommand", "getDevServerCommand", + "getTemplateExtensionNames", "installDependencies", "packNpmPackage", "parseCommaSeparatedFlag", @@ -32,6 +36,17 @@ describe("template runtime E2E commands", () => { ); }); + it("packs only the first-party extensions selected templates own", () => { + assertEquals( + getTemplateExtensionNames(["minimal", "docs-agent", "minimal"]), + [ + "@veryfront/ext-content-mdx", + "@veryfront/ext-document-kreuzberg", + ], + ); + assertEquals(getTemplateExtensionNames(["agentic-workflow"]), []); + }); + it("passes the selected port through Deno task without a separator", () => { assertEquals( getDevServerCommand("deno", 4321), diff --git a/scripts/test/template-runtime-e2e.ts b/scripts/test/template-runtime-e2e.ts index 58c5c4a0d8..98c4fdd1c6 100644 --- a/scripts/test/template-runtime-e2e.ts +++ b/scripts/test/template-runtime-e2e.ts @@ -1,4 +1,5 @@ import { TEMPLATES } from "../../cli/commands/init/catalog.ts"; +import { getTemplateConfig } from "../../templates/index.ts"; import { allocatePort, assertCondition, @@ -64,6 +65,18 @@ const TEMPLATE_ROUTE_EXPECTATIONS: Partial< ], }; +export function getTemplateExtensionNames( + templates: readonly TemplateName[], +): string[] { + return [ + ...new Set( + templates.flatMap((template) => + getTemplateConfig(template)?.firstPartyExtensions ?? [] + ), + ), + ].sort(); +} + function hasFlag(name: string): boolean { return Deno.args.includes(`--${name}`); } @@ -271,6 +284,7 @@ async function testCase( packed, template, runtime, + getTemplateExtensionNames([template]), ); console.log(`test ${label}: install`); @@ -352,7 +366,11 @@ async function main(): Promise { } console.log("pack npm package"); - const packed = await packNpmPackage(rootDir, workDir); + const packed = await packNpmPackage( + rootDir, + workDir, + getTemplateExtensionNames(templates), + ); for (const template of templates) { for (const runtime of runtimes) { diff --git a/scripts/test/test-semantic-audit-migration.ts b/scripts/test/test-semantic-audit-migration.ts index 78d26305df..10c37bd727 100644 --- a/scripts/test/test-semantic-audit-migration.ts +++ b/scripts/test/test-semantic-audit-migration.ts @@ -1226,19 +1226,6 @@ export const TEST_SEMANTIC_AUDIT_MIGRATION_ENTRIES: "removalPr": "PR 4e", }, ), - entry( - "extensions/ext-dev-ui-react/src/browser-bundle.test.ts", - ["browser"], - { - "disposition": "integration-relocation", - "owner": "extensions-templates", - "rationale": - "Exercises filesystem mutation, process, server, network, browser, or multi-component runtime behavior outside the colocated unit boundary.", - "destination": - "tests/integration/semantic-unit-boundary/extensions/ext-dev-ui-react/src/browser-bundle.test.ts", - "removalPr": "PR 4e", - }, - ), entry("extensions/ext-document-kreuzberg/src/index.test.ts", ["process"], { "disposition": "integration-relocation", "owner": "extensions-templates", diff --git a/extensions/ext-dev-ui-react/src/browser-bundle.test.ts b/tests/e2e/regressions/dev-ui-browser-bundle.test.ts similarity index 93% rename from extensions/ext-dev-ui-react/src/browser-bundle.test.ts rename to tests/e2e/regressions/dev-ui-browser-bundle.test.ts index 5757c77abb..701c08c57f 100644 --- a/extensions/ext-dev-ui-react/src/browser-bundle.test.ts +++ b/tests/e2e/regressions/dev-ui-browser-bundle.test.ts @@ -4,9 +4,15 @@ import { closeChromium, getBrowserDiagnosticMessages, launchChromium, -} from "../../../tests/_helpers/playwright.ts"; -import { DEV_UI_BROWSER_BUNDLE, DEV_UI_BROWSER_BUNDLE_SHA256 } from "./dev-ui-bundle.generated.ts"; -import { DEV_UI_STYLES, DEV_UI_STYLES_SHA256 } from "./dev-ui-styles.generated.ts"; +} from "../../_helpers/playwright.ts"; +import { + DEV_UI_BROWSER_BUNDLE, + DEV_UI_BROWSER_BUNDLE_SHA256, +} from "../../../extensions/ext-dev-ui-react/src/dev-ui-bundle.generated.ts"; +import { + DEV_UI_STYLES, + DEV_UI_STYLES_SHA256, +} from "../../../extensions/ext-dev-ui-react/src/dev-ui-styles.generated.ts"; const MAX_PRODUCTION_BUNDLE_BYTES = 512 * 1024; const STYLE_IDENTITY_ATTRIBUTE = "data-veryfront-dev-ui-styles";