154 lines
5.2 KiB
TypeScript
154 lines
5.2 KiB
TypeScript
#!/usr/bin/env bun
|
|
/**
|
|
* check-fixture-resolves — verifies that every `@suaveplan/testing-integration-*`
|
|
* package's `package.json` `exports."." → import.default` points at a file that
|
|
* actually exists on disk and loads cleanly via dynamic `import()`.
|
|
*
|
|
* The bug this guards against: a vite.config.ts using `preserveModules: true`
|
|
* without `output.entryFileNames: "[name].js"` causes Rollup to derive the
|
|
* entry filename from the package name (e.g. `dist/testing-integration-redis.js`),
|
|
* leaving `package.json`'s declared entry pointing at a file that does not
|
|
* exist. Consumers then get
|
|
* `Failed to resolve entry for package "@suaveplan/testing-integration-<name>"`
|
|
* from esbuild/Vite/Bun.
|
|
*
|
|
* Resolves package paths from the repo's `packages/testing/testing-integration*`
|
|
* tree directly, since the repo-root `scripts/` directory is not a workspace
|
|
* member and cannot import `@suaveplan/*` by name under Bun's hoisting.
|
|
*
|
|
* Exits 0 when all entries resolve and import, 1 when any fail. Prints a
|
|
* per-package row so a CI failure is unambiguous.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
import { join, resolve } from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
const REPO_ROOT = resolve(import.meta.dir, "..");
|
|
const FIXTURES_DIR = join(REPO_ROOT, "packages", "dev");
|
|
|
|
interface ExportConditions {
|
|
import?: string | { default?: string };
|
|
default?: string;
|
|
}
|
|
|
|
interface PackageManifest {
|
|
name?: string;
|
|
exports?: Record<string, string | ExportConditions> | string;
|
|
}
|
|
|
|
interface Result {
|
|
pkg: string;
|
|
ok: boolean;
|
|
error: string | undefined;
|
|
}
|
|
|
|
function discoverFixtures(): Array<{ name: string; dir: string }> {
|
|
const entries = readdirSync(FIXTURES_DIR, { withFileTypes: true });
|
|
const result: Array<{ name: string; dir: string }> = [];
|
|
for (const entry of entries) {
|
|
if (!entry.isDirectory()) continue;
|
|
if (!entry.name.startsWith("testing-integration")) continue;
|
|
const dir = join(FIXTURES_DIR, entry.name);
|
|
const manifestPath = join(dir, "package.json");
|
|
if (!existsSync(manifestPath)) continue;
|
|
const manifest = JSON.parse(
|
|
readFileSync(manifestPath, "utf-8")
|
|
) as PackageManifest;
|
|
if (typeof manifest.name !== "string") continue;
|
|
result.push({ name: manifest.name, dir });
|
|
}
|
|
return result.sort((a, b) => a.name.localeCompare(b.name));
|
|
}
|
|
|
|
function resolveMainEntry(dir: string): string | null {
|
|
const manifest = JSON.parse(
|
|
readFileSync(join(dir, "package.json"), "utf-8")
|
|
) as PackageManifest;
|
|
const exp = manifest.exports;
|
|
if (exp === undefined) return null;
|
|
if (typeof exp === "string") return join(dir, exp);
|
|
const root = exp["."];
|
|
if (root === undefined) return null;
|
|
if (typeof root === "string") return join(dir, root);
|
|
const importEntry = root.import;
|
|
if (typeof importEntry === "string") return join(dir, importEntry);
|
|
if (
|
|
importEntry !== undefined &&
|
|
typeof importEntry === "object" &&
|
|
typeof importEntry.default === "string"
|
|
) {
|
|
return join(dir, importEntry.default);
|
|
}
|
|
if (typeof root.default === "string") return join(dir, root.default);
|
|
return null;
|
|
}
|
|
|
|
async function check(pkg: { name: string; dir: string }): Promise<Result> {
|
|
const entry = resolveMainEntry(pkg.dir);
|
|
if (entry === null) {
|
|
return {
|
|
pkg: pkg.name,
|
|
ok: false,
|
|
error: 'package.json "exports" missing or has no "." entry',
|
|
};
|
|
}
|
|
if (!existsSync(entry)) {
|
|
return {
|
|
pkg: pkg.name,
|
|
ok: false,
|
|
error: `declared entry does not exist on disk: ${entry}`,
|
|
};
|
|
}
|
|
try {
|
|
const mod = await import(pathToFileURL(entry).href);
|
|
if (mod === null || typeof mod !== "object") {
|
|
return {
|
|
pkg: pkg.name,
|
|
ok: false,
|
|
error: `import resolved but module is not an object (got ${typeof mod})`,
|
|
};
|
|
}
|
|
return { pkg: pkg.name, ok: true, error: undefined };
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
return { pkg: pkg.name, ok: false, error: message };
|
|
}
|
|
}
|
|
|
|
async function main(): Promise<number> {
|
|
const fixtures = discoverFixtures();
|
|
if (fixtures.length === 0) {
|
|
process.stderr.write(
|
|
`No testing-integration packages found under ${FIXTURES_DIR}\n`
|
|
);
|
|
return 1;
|
|
}
|
|
|
|
const results = await Promise.all(fixtures.map(check));
|
|
const width = Math.max(...fixtures.map((f) => f.name.length));
|
|
for (const r of results) {
|
|
const tag = r.ok ? "PASS" : "FAIL";
|
|
const padded = r.pkg.padEnd(width, " ");
|
|
const tail = r.ok ? "" : ` — ${r.error}`;
|
|
process.stdout.write(`${tag} ${padded}${tail}\n`);
|
|
}
|
|
|
|
const failed = results.filter((r) => !r.ok);
|
|
if (failed.length > 0) {
|
|
process.stderr.write(
|
|
`\n${failed.length}/${results.length} testing-integration packages failed to resolve.\n`
|
|
);
|
|
return 1;
|
|
}
|
|
process.stdout.write(
|
|
`\nAll ${results.length} testing-integration packages resolve.\n`
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
const code = await main();
|
|
process.exit(code);
|