Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions packages/sv/src/addons/tests/_setup/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,39 @@ import process from 'node:process';
import { fileURLToPath } from 'node:url';
import { setupGlobal } from 'sv/testing';
import { exec } from 'tinyexec';
import { collectPrewarmSpecs } from './prewarm.ts';

const TEST_DIR = fileURLToPath(new URL('../../../../.test-output/addons/', import.meta.url));
const CI = Boolean(process.env.CI);

export default setupGlobal({
TEST_DIR,
pre: async () => {
if (CI) {
if (!CI) return;

await Promise.all([
// prefetch the storybook cli during ci to reduce fetching errors in tests
const { stdout } = await exec('pnpm', ['dlx', `create-storybook@latest`, '--version']);
console.info('storybook version:', stdout);
}
exec('pnpm', ['dlx', `create-storybook@latest`, '--version']).then(({ stdout }) =>
console.info('storybook version:', stdout)
),
// Warm the pnpm store with every dependency the add-on test projects install, so the
// parallel test files only hard-link from the store instead of each downloading cold -
// which is what saturates the CI runner. Specs are read from source, never hardcoded.
(async () => {
const specs = collectPrewarmSpecs();
const start = Date.now();
const { exitCode, stderr } = await exec('pnpm', ['store', 'add', ...specs], {
throwOnError: false
});
// best effort: a miss just means some installs download cold, it must never fail the suite
if (exitCode !== 0) {
console.warn(`store prewarm skipped (pnpm store add exited ${exitCode})\n${stderr}`);
return;
}
console.info(
`prewarmed ${specs.length} deps into the pnpm store in ${Date.now() - start}ms`
);
})()
]);
}
});
61 changes: 61 additions & 0 deletions packages/sv/src/addons/tests/_setup/prewarm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const ADDONS_DIR = fileURLToPath(new URL('../../', import.meta.url));
const TEMPLATES_DIR = fileURLToPath(new URL('../../../create/templates/', import.meta.url));
const SHARED_DIR = fileURLToPath(new URL('../../../create/shared/', import.meta.url));

// same extraction the `update-deps` script uses, see scripts/update-dependencies.js
const DEP_PATTERNS = [
// sv.dependency('pkg', '^1.2.3') / sv.devDependency('pkg', '^1.2.3')
/sv\.(?:dependency|devDependency)\('([^']+)',\s*'([^']+)'\)/g,
// object literals: { package: 'pkg', version: '^1.2.3' } (ex: tailwind add-on)
/package:\s*'([^']+)',\s*version:\s*'([^']+)'/g,
// constants tagged for update-deps: /* update-deps: pkg */ '^1.2.3' (ex: common.ts)
/\/\*\s*update-deps:\s*(\S+)\s*\*\/\s*'([^']+)'/g
];

function addSpec(specs: Map<string, string>, name: string, version: string): void {
// keep only real registry ranges; skip `workspace:*`, `latest`, placeholders, computed versions
if (!/^[\^~]?\d/.test(version)) return;
specs.set(name, version);
}

function collectFromSources(specs: Map<string, string>): void {
for (const file of fs.readdirSync(ADDONS_DIR)) {
if (!file.endsWith('.ts')) continue;
const content = fs.readFileSync(path.join(ADDONS_DIR, file), 'utf8');
for (const pattern of DEP_PATTERNS) {
for (const [, name, version] of content.matchAll(pattern)) addSpec(specs, name, version);
}
}
}

function collectFromPackageJsons(dir: string, fileName: string, specs: Map<string, string>): void {
if (!fs.existsSync(dir)) return;
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
const filePath = path.join(dir, entry.name, fileName);
if (!fs.existsSync(filePath)) continue;
const pkg = JSON.parse(fs.readFileSync(filePath, 'utf8'));
for (const deps of [pkg.dependencies, pkg.devDependencies]) {
for (const [name, version] of Object.entries(deps ?? {})) {
if (typeof version === 'string') addSpec(specs, name, version);
}
}
}
}

/**
* Collects every `pkg@range` declared across the add-ons and project templates, read straight
* from source (same way `update-deps` does) so it never goes stale. Used to warm the pnpm store
* before the addon test files install their generated projects in parallel.
*/
export function collectPrewarmSpecs(): string[] {
const specs = new Map<string, string>();
collectFromSources(specs);
collectFromPackageJsons(TEMPLATES_DIR, 'package.template.json', specs);
collectFromPackageJsons(SHARED_DIR, 'package.json', specs);
return [...specs].map(([name, version]) => `${name}@${version}`).sort();
}
Loading