mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
9bab92f012
Generated worker entries can import vinext subpaths that must resolve from the published package. Missing package exports break consumers even when the generated template itself looks correct. Add deploy coverage that extracts vinext subpath imports from generated App and Pages worker entries and verifies each one is covered by packages/vinext/package.json exports. Tighten the prefer-shared-utils lint rule so copied shared helpers in repo tests are reported too. Test files mask template literal fixture payloads, while source files still scan generated-source templates.
195 lines
6.0 KiB
TypeScript
195 lines
6.0 KiB
TypeScript
import { afterEach, describe, expect, it } from "vite-plus/test";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { spawnSync } from "node:child_process";
|
|
|
|
const SRC_ROOT = path.resolve(import.meta.dirname, "../packages/vinext/src");
|
|
const TESTS_ROOT = path.resolve(import.meta.dirname);
|
|
|
|
let fixtureDir: string | undefined;
|
|
let fixtureLinkDir: string | undefined;
|
|
let testFixtureLinkDir: string | undefined;
|
|
|
|
function createFixtureDir(): string {
|
|
fixtureDir = fs.mkdtempSync(path.join(os.tmpdir(), "vinext-lint-rule-fixtures-"));
|
|
fixtureLinkDir = path.join(SRC_ROOT, `__lint_rule_fixtures__-${path.basename(fixtureDir)}`);
|
|
fs.symlinkSync(fixtureDir, fixtureLinkDir, "dir");
|
|
return fixtureDir;
|
|
}
|
|
|
|
function writeFixture(name: string, source: string): string {
|
|
const dir = fixtureDir ?? createFixtureDir();
|
|
const file = path.join(dir, name);
|
|
fs.writeFileSync(file, source, "utf-8");
|
|
return path.join(fixtureLinkDir ?? dir, name);
|
|
}
|
|
|
|
function writeTestFixture(name: string, source: string): string {
|
|
fixtureDir ??= fs.mkdtempSync(path.join(os.tmpdir(), "vinext-lint-rule-fixtures-"));
|
|
testFixtureLinkDir ??= path.join(
|
|
TESTS_ROOT,
|
|
`__lint_rule_fixtures__-${path.basename(fixtureDir)}`,
|
|
);
|
|
if (!fs.existsSync(testFixtureLinkDir)) {
|
|
fs.symlinkSync(fixtureDir, testFixtureLinkDir, "dir");
|
|
}
|
|
const file = path.join(fixtureDir, name);
|
|
fs.writeFileSync(file, source, "utf-8");
|
|
return path.join(testFixtureLinkDir, name);
|
|
}
|
|
|
|
function runLint(files: readonly string[]): { status: number | null; output: string } {
|
|
const result = spawnSync("vp", ["lint", ...files], {
|
|
cwd: path.resolve(import.meta.dirname, ".."),
|
|
encoding: "utf-8",
|
|
maxBuffer: 10 * 1024 * 1024,
|
|
});
|
|
return {
|
|
status: result.status,
|
|
output: `${result.stdout}${result.stderr}`,
|
|
};
|
|
}
|
|
|
|
afterEach(() => {
|
|
if (fixtureLinkDir) {
|
|
fs.rmSync(fixtureLinkDir, { force: true });
|
|
fixtureLinkDir = undefined;
|
|
}
|
|
if (testFixtureLinkDir) {
|
|
fs.rmSync(testFixtureLinkDir, { force: true });
|
|
testFixtureLinkDir = undefined;
|
|
}
|
|
if (!fixtureDir) return;
|
|
fs.rmSync(fixtureDir, { recursive: true, force: true });
|
|
fixtureDir = undefined;
|
|
});
|
|
|
|
describe("prefer-shared-utils oxlint rule", () => {
|
|
it("reports local shared-helper definitions across source and generated templates", () => {
|
|
const functionFile = writeFixture(
|
|
"function-helper.ts",
|
|
`
|
|
export function isPromiseLike(value: unknown): boolean {
|
|
return value !== null;
|
|
}
|
|
`,
|
|
);
|
|
const constFile = writeFixture(
|
|
"const-helper.ts",
|
|
`
|
|
export const isPromiseLike = (value: unknown): boolean => value !== null;
|
|
`,
|
|
);
|
|
const generatedTemplateFile = writeFixture(
|
|
"generated-template.ts",
|
|
`
|
|
export const generatedSource = \`
|
|
function isPromiseLike(value) {
|
|
return value !== null;
|
|
}
|
|
\`;
|
|
`,
|
|
);
|
|
const exportConstFile = writeFixture(
|
|
"export-const-helper.ts",
|
|
`
|
|
export const compareStrings = (left: string, right: string): number =>
|
|
left.localeCompare(right);
|
|
`,
|
|
);
|
|
const semanticAliasFile = writeFixture(
|
|
"semantic-alias-helper.ts",
|
|
`
|
|
export const compareAppElementsSlotIds = (left: string, right: string): number =>
|
|
left.localeCompare(right);
|
|
`,
|
|
);
|
|
const reExportFile = writeFixture(
|
|
"re-export-helper.ts",
|
|
`
|
|
export function findFileWithExts(): boolean {
|
|
return false;
|
|
}
|
|
`,
|
|
);
|
|
// A helper definition embedded in a template literal that also contains an
|
|
// apostrophe and a block-comment sequence before it. These must not be
|
|
// treated as string/comment openers that mask the subsequent definition.
|
|
const templateApostropheFile = writeFixture(
|
|
"template-apostrophe.ts",
|
|
`
|
|
export const generatedSource = \`
|
|
This template body documents the user's generated output.
|
|
function findFileWithExts(value) {
|
|
return value;
|
|
}
|
|
\`;
|
|
`,
|
|
);
|
|
|
|
const result = runLint([
|
|
functionFile,
|
|
constFile,
|
|
generatedTemplateFile,
|
|
exportConstFile,
|
|
semanticAliasFile,
|
|
reExportFile,
|
|
templateApostropheFile,
|
|
]);
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.output).toContain("function-helper.ts");
|
|
expect(result.output).toContain("const-helper.ts");
|
|
expect(result.output).toContain("generated-template.ts");
|
|
expect(result.output).toContain("export-const-helper.ts");
|
|
expect(result.output).toContain("semantic-alias-helper.ts");
|
|
expect(result.output).toContain("re-export-helper.ts");
|
|
expect(result.output).toContain("template-apostrophe.ts");
|
|
expect(result.output).toContain("Use shared isPromiseLike");
|
|
expect(result.output).toContain("Use shared compareStrings");
|
|
expect(result.output).toContain("Use shared compareAppElementsSlotIds");
|
|
expect(result.output).toContain("Use shared findFileWithExts");
|
|
});
|
|
|
|
it("reports local shared-helper definitions in tests", () => {
|
|
const testHelperFile = writeTestFixture(
|
|
"copied-record-helper.test.ts",
|
|
`
|
|
function isUnknownRecord(value: unknown): value is Record<string, unknown> {
|
|
return value !== null && typeof value === "object";
|
|
}
|
|
|
|
void isUnknownRecord({});
|
|
`,
|
|
);
|
|
|
|
const result = runLint([testHelperFile]);
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.output).toContain("copied-record-helper.test.ts");
|
|
expect(result.output).toContain("Use shared isUnknownRecord");
|
|
});
|
|
|
|
it("allows canonical modules, comments, and ordinary strings", () => {
|
|
const commentsAndStringsFile = writeFixture(
|
|
"comments-and-strings.ts",
|
|
`
|
|
// function isPromiseLike(value: unknown): boolean
|
|
/* const isPromiseLike = (value: unknown): boolean => true; */
|
|
export const docs = "function isPromiseLike(value) { return true; }";
|
|
`,
|
|
);
|
|
|
|
const result = runLint([
|
|
path.join(SRC_ROOT, "utils/promise.ts"),
|
|
path.join(SRC_ROOT, "server/app-elements-wire.ts"),
|
|
path.join(SRC_ROOT, "entries/pages-entry-helpers.ts"),
|
|
commentsAndStringsFile,
|
|
]);
|
|
|
|
expect(result.status).toBe(0);
|
|
expect(result.output).not.toContain("prefer-shared-utils");
|
|
});
|
|
});
|