Skip to content

Commit b03abe8

Browse files
feat: add intake method to blockVitest (#2079)
## PR Checklist - [x] Addresses an existing open issue: fixes #2077 - [x] That issue was marked as [`status: accepting prs`](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22) - [x] Steps in [CONTRIBUTING.md](https://github.com/JoshuaKGoldberg/create-typescript-app/blob/main/.github/CONTRIBUTING.md) were taken ## Overview Attempts to parse the `{}` inside `defineConfig(...)` with [JSON5](http://npmjs.com/package/json5), allowing comments and unquoted object keys. 🎁
1 parent 23e0a30 commit b03abe8

File tree

4 files changed

+172
-9
lines changed

4 files changed

+172
-9
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"input-from-file-json": "^0.5.4",
5252
"input-from-script": "^0.5.4",
5353
"js-yaml": "^4.1.0",
54+
"json5": "^2.2.3",
5455
"lazy-value": "^3.0.0",
5556
"lodash": "^4.17.21",
5657
"npm-user": "^6.1.1",

pnpm-lock.yaml

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/blocks/blockVitest.test.ts

+109-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { testBlock } from "bingo-stratum-testers";
2-
import { describe, expect, test, vi } from "vitest";
1+
import { testBlock, testIntake } from "bingo-stratum-testers";
2+
import { describe, expect, it, test, vi } from "vitest";
33

44
import { blockVitest } from "./blockVitest.js";
55
import { optionsBase } from "./options.fakes.js";
@@ -759,4 +759,111 @@ describe("blockVitest", () => {
759759
}
760760
`);
761761
});
762+
763+
describe("intake", () => {
764+
it("returns undefined when vitest.config.ts does not exist", () => {
765+
const actual = testIntake(blockVitest, {
766+
files: {
767+
src: {},
768+
},
769+
});
770+
771+
expect(actual).toEqual(undefined);
772+
});
773+
774+
it("returns undefined when vitest.config.ts does not contain the expected defineConfig", () => {
775+
const actual = testIntake(blockVitest, {
776+
files: {
777+
"vitest.config.ts": [`invalid`],
778+
},
779+
});
780+
781+
expect(actual).toEqual(undefined);
782+
});
783+
784+
it("returns undefined when vitest.config.ts passes a non-object to defineConfig", () => {
785+
const actual = testIntake(blockVitest, {
786+
files: {
787+
"vitest.config.ts": [`defineConfig("invalid")`],
788+
},
789+
});
790+
791+
expect(actual).toEqual(undefined);
792+
});
793+
794+
it("returns undefined when vitest.config.ts does not pass a test to defineConfig", () => {
795+
const actual = testIntake(blockVitest, {
796+
files: {
797+
"vitest.config.ts": [`defineConfig({ other: true })`],
798+
},
799+
});
800+
801+
expect(actual).toEqual(undefined);
802+
});
803+
804+
it("returns undefined when vitest.config.ts passes unknown test data to defineConfig", () => {
805+
const actual = testIntake(blockVitest, {
806+
files: {
807+
"vitest.config.ts": [`defineConfig({ test: true })`],
808+
},
809+
});
810+
811+
expect(actual).toEqual(undefined);
812+
});
813+
814+
it("returns undefined when vitest.config.ts passes invalid test syntax to defineConfig", () => {
815+
const actual = testIntake(blockVitest, {
816+
files: {
817+
"vitest.config.ts": [`defineConfig({ test: { ! } })`],
818+
},
819+
});
820+
821+
expect(actual).toEqual(undefined);
822+
});
823+
824+
it("returns undefined when vitest.config.ts passes invalid test data to defineConfig", () => {
825+
const actual = testIntake(blockVitest, {
826+
files: {
827+
"vitest.config.ts": [
828+
`defineConfig({ test: { coverage: 'invalid' } })`,
829+
],
830+
},
831+
});
832+
833+
expect(actual).toEqual(undefined);
834+
});
835+
836+
it("returns coverage and exclude when they exist in vitest.config.ts", () => {
837+
const actual = testIntake(blockVitest, {
838+
files: {
839+
"vitest.config.ts": [
840+
`import { defineConfig } from "vitest/config";
841+
842+
export default defineConfig({
843+
test: {
844+
clearMocks: true,
845+
coverage: {
846+
all: true,
847+
exclude: ["src/index.ts"],
848+
include: ["src", "other"],
849+
reporter: ["html", "lcov"],
850+
},
851+
exclude: ["lib", "node_modules"],
852+
setupFiles: ["console-fail-test/setup"],
853+
},
854+
});
855+
`,
856+
],
857+
},
858+
});
859+
860+
expect(actual).toEqual({
861+
coverage: {
862+
exclude: ["src/index.ts"],
863+
include: ["src", "other"],
864+
},
865+
exclude: ["lib", "node_modules"],
866+
});
867+
});
868+
});
762869
});

src/blocks/blockVitest.ts

+52-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import JSON5 from "json5";
12
import { z } from "zod";
23

34
import { base } from "../base.js";
@@ -15,22 +16,66 @@ import { blockRemoveFiles } from "./blockRemoveFiles.js";
1516
import { blockRemoveWorkflows } from "./blockRemoveWorkflows.js";
1617
import { blockTSup } from "./blockTSup.js";
1718
import { blockVSCode } from "./blockVSCode.js";
19+
import { intakeFile } from "./intake/intakeFile.js";
20+
21+
function tryParseJSON5(text: string) {
22+
try {
23+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
24+
return JSON5.parse(text) as Record<string, unknown> | undefined;
25+
} catch {
26+
return undefined;
27+
}
28+
}
29+
30+
const zCoverage = z.object({
31+
exclude: z.array(z.string()).optional(),
32+
include: z.array(z.string()).optional(),
33+
});
34+
35+
const zExclude = z.array(z.string());
36+
37+
const zTest = z.object({
38+
coverage: zCoverage,
39+
exclude: zExclude,
40+
});
1841

1942
export const blockVitest = base.createBlock({
2043
about: {
2144
name: "Vitest",
2245
},
2346
addons: {
2447
actionSteps: z.array(zActionStep).default([]),
25-
coverage: z
26-
.object({
27-
exclude: z.array(z.string()).optional(),
28-
include: z.array(z.string()).optional(),
29-
})
30-
.default({}),
31-
exclude: z.array(z.string()).default([]),
48+
coverage: zCoverage.default({}),
49+
exclude: zExclude.default([]),
3250
flags: z.array(z.string()).default([]),
3351
},
52+
intake({ files }) {
53+
const file = intakeFile(files, ["vitest.config.ts"]);
54+
if (!file) {
55+
return undefined;
56+
}
57+
58+
const normalized = file[0].replaceAll(/[\n\r]/g, "");
59+
const matched = /defineConfig\(\{(.+)\}\)\s*(?:;\s*)?$/u.exec(normalized);
60+
if (!matched) {
61+
return undefined;
62+
}
63+
64+
const rawData = tryParseJSON5(`{${matched[1]}}`);
65+
if (typeof rawData !== "object" || typeof rawData.test !== "object") {
66+
return undefined;
67+
}
68+
69+
const parsedData = zTest.safeParse(rawData.test).data;
70+
if (!parsedData) {
71+
return undefined;
72+
}
73+
74+
return {
75+
coverage: parsedData.coverage,
76+
exclude: parsedData.exclude,
77+
};
78+
},
3479
produce({ addons }) {
3580
const { actionSteps, coverage, exclude = [] } = addons;
3681
const excludeText = JSON.stringify(exclude);

0 commit comments

Comments
 (0)