Skip to content

Commit 3d122d8

Browse files
feat: add intake to blockTSup (#2170)
## PR Checklist - [x] Addresses an existing open issue: fixes #2114 - [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 Extracts `defineConfig` parsing into a shared `intakeFileDefineConfig`. Also fills in remaining tests for `intakeFile` while I'm here. 🎁
1 parent 7568b55 commit 3d122d8

File tree

7 files changed

+351
-61
lines changed

7 files changed

+351
-61
lines changed

src/blocks/blockTSup.test.ts

Lines changed: 97 additions & 2 deletions
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 { blockTSup } from "./blockTSup.js";
55
import { optionsBase } from "./options.fakes.js";
@@ -327,4 +327,99 @@ describe("blockTSup", () => {
327327
}
328328
`);
329329
});
330+
331+
describe("intake", () => {
332+
it("returns nothing when tsup.config.ts passes nothing to defineConfig", () => {
333+
const actual = testIntake(blockTSup, {
334+
files: {
335+
"tsup.config.ts": [`defineConfig()`],
336+
},
337+
options: optionsBase,
338+
});
339+
340+
expect(actual).toEqual(undefined);
341+
});
342+
343+
it("returns nothing when tsup.config.ts passes invalid syntax to defineConfig", () => {
344+
const actual = testIntake(blockTSup, {
345+
files: {
346+
"tsup.config.ts": [`defineConfig({ ! })`],
347+
},
348+
options: optionsBase,
349+
});
350+
351+
expect(actual).toEqual(undefined);
352+
});
353+
354+
it("returns entry when it exists alone in tsup.config.ts", () => {
355+
const actual = testIntake(blockTSup, {
356+
files: {
357+
"tsup.config.ts": [
358+
`import { defineConfig } from "tsup";
359+
360+
export default defineConfig({
361+
entry: ["src/**/*.ts", "!src/**/*.test.*"],
362+
});
363+
`,
364+
],
365+
},
366+
options: optionsBase,
367+
});
368+
369+
expect(actual).toEqual({
370+
entry: ["src/**/*.ts", "!src/**/*.test.*"],
371+
properties: {},
372+
});
373+
});
374+
375+
it("returns other properties when they exists without entry in tsup.config.ts", () => {
376+
const actual = testIntake(blockTSup, {
377+
files: {
378+
"tsup.config.ts": [
379+
`import { defineConfig } from "tsup";
380+
381+
export default defineConfig({
382+
bundle: false,
383+
format: "esm",
384+
});
385+
`,
386+
],
387+
},
388+
options: optionsBase,
389+
});
390+
391+
expect(actual).toEqual({
392+
properties: {
393+
bundle: false,
394+
format: "esm",
395+
},
396+
});
397+
});
398+
399+
it("returns entry and other properties when they all exist in tsup.config.ts", () => {
400+
const actual = testIntake(blockTSup, {
401+
files: {
402+
"tsup.config.ts": [
403+
`import { defineConfig } from "tsup";
404+
405+
export default defineConfig({
406+
bundle: false,
407+
entry: ["src/**/*.ts", "!src/**/*.test.*"],
408+
format: "esm",
409+
});
410+
`,
411+
],
412+
},
413+
options: optionsBase,
414+
});
415+
416+
expect(actual).toEqual({
417+
entry: ["src/**/*.ts", "!src/**/*.test.*"],
418+
properties: {
419+
bundle: false,
420+
format: "esm",
421+
},
422+
});
423+
});
424+
});
330425
});

src/blocks/blockTSup.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,34 @@ import { blockReleaseIt } from "./blockReleaseIt.js";
1111
import { blockRemoveDependencies } from "./blockRemoveDependencies.js";
1212
import { blockRemoveFiles } from "./blockRemoveFiles.js";
1313
import { blockRemoveWorkflows } from "./blockRemoveWorkflows.js";
14+
import { intakeFileDefineConfig } from "./intake/intakeFileDefineConfig.js";
1415
import { CommandPhase } from "./phases.js";
1516

17+
const zEntry = z.array(z.string());
18+
const zProperties = z.record(z.unknown());
19+
1620
export const blockTSup = base.createBlock({
1721
about: {
1822
name: "TSup",
1923
},
2024
addons: {
21-
entry: z.array(z.string()).default([]),
22-
properties: z.record(z.unknown()).default({}),
25+
entry: zEntry.default([]),
26+
properties: zProperties.default({}),
2327
runInCI: z.array(z.string()).default([]),
2428
},
29+
intake({ files }) {
30+
const rawData = intakeFileDefineConfig(files, ["tsup.config.ts"]);
31+
if (!rawData) {
32+
return undefined;
33+
}
34+
35+
const { entry: rawEntry, ...rest } = rawData;
36+
37+
return {
38+
entry: zEntry.safeParse(rawEntry).data,
39+
properties: zProperties.safeParse(rest).data,
40+
};
41+
},
2542
produce({ addons, options }) {
2643
const { entry, properties, runInCI } = addons;
2744

src/blocks/blockVitest.test.ts

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,39 +1041,6 @@ describe("blockVitest", () => {
10411041
});
10421042

10431043
describe("intake", () => {
1044-
it("returns nothing when vitest.config.ts does not exist", () => {
1045-
const actual = testIntake(blockVitest, {
1046-
files: {
1047-
src: {},
1048-
},
1049-
options: optionsBase,
1050-
});
1051-
1052-
expect(actual).toEqual({});
1053-
});
1054-
1055-
it("returns nothing when vitest.config.ts does not contain the expected defineConfig", () => {
1056-
const actual = testIntake(blockVitest, {
1057-
files: {
1058-
"vitest.config.ts": [`invalid`],
1059-
},
1060-
options: optionsBase,
1061-
});
1062-
1063-
expect(actual).toEqual({});
1064-
});
1065-
1066-
it("returns nothing when vitest.config.ts passes a non-object to defineConfig", () => {
1067-
const actual = testIntake(blockVitest, {
1068-
files: {
1069-
"vitest.config.ts": [`defineConfig("invalid")`],
1070-
},
1071-
options: optionsBase,
1072-
});
1073-
1074-
expect(actual).toEqual({});
1075-
});
1076-
10771044
it("returns nothing when vitest.config.ts does not pass a test to defineConfig", () => {
10781045
const actual = testIntake(blockVitest, {
10791046
files: {

src/blocks/blockVitest.ts

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { IntakeDirectory } from "bingo-fs";
2-
import JSON5 from "json5";
32
import { z } from "zod";
43

54
import { base } from "../base.js";
@@ -18,16 +17,7 @@ import { blockRemoveFiles } from "./blockRemoveFiles.js";
1817
import { blockRemoveWorkflows } from "./blockRemoveWorkflows.js";
1918
import { blockTSup } from "./blockTSup.js";
2019
import { blockVSCode } from "./blockVSCode.js";
21-
import { intakeFile } from "./intake/intakeFile.js";
22-
23-
function tryParseJSON5(text: string) {
24-
try {
25-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
26-
return JSON5.parse(text) as Record<string, unknown> | undefined;
27-
} catch {
28-
return undefined;
29-
}
30-
}
20+
import { intakeFileDefineConfig } from "./intake/intakeFileDefineConfig.js";
3121

3222
const zCoverage = z.object({
3323
exclude: z.array(z.string()).optional(),
@@ -47,19 +37,8 @@ const zTest = z
4737
.partial();
4838

4939
function intakeFromConfig(files: IntakeDirectory) {
50-
const file = intakeFile(files, ["vitest.config.ts"]);
51-
if (!file) {
52-
return undefined;
53-
}
54-
55-
const normalized = file[0].replaceAll(/[\n\r]/g, "");
56-
const matched = /defineConfig\(\{(.+)\}\)\s*(?:;\s*)?$/u.exec(normalized);
57-
if (!matched) {
58-
return undefined;
59-
}
60-
61-
const rawData = tryParseJSON5(`{${matched[1]}}`);
62-
if (typeof rawData !== "object" || typeof rawData.test !== "object") {
40+
const rawData = intakeFileDefineConfig(files, ["vitest.config.ts"]);
41+
if (typeof rawData?.test !== "object") {
6342
return undefined;
6443
}
6544

src/blocks/intake/intakeFile.test.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,28 @@ describe(intakeFile, () => {
2525
expect(actual).toBeUndefined();
2626
});
2727

28+
it("returns undefined when filePath points to a root-level directory", () => {
29+
const actual = intakeFile(
30+
{
31+
"file.txt": {},
32+
},
33+
["file.txt"],
34+
);
35+
36+
expect(actual).toBeUndefined();
37+
});
38+
39+
it("returns undefined when filePath points to a root-level undefined", () => {
40+
const actual = intakeFile(
41+
{
42+
"file.txt": undefined,
43+
},
44+
["file.txt"],
45+
);
46+
47+
expect(actual).toBeUndefined();
48+
});
49+
2850
it("returns the file when filePath points to a root-level file", () => {
2951
const value = "abc123";
3052

@@ -38,6 +60,17 @@ describe(intakeFile, () => {
3860
expect(actual).toEqual([value]);
3961
});
4062

63+
it("returns undefined when filePath is within a file", () => {
64+
const actual = intakeFile(
65+
{
66+
src: ["abc123"],
67+
},
68+
["src", "file.txt"],
69+
);
70+
71+
expect(actual).toBeUndefined();
72+
});
73+
4174
it("returns the file when filePath points to a file in a directory", () => {
4275
const value = "abc123";
4376

@@ -52,4 +85,90 @@ describe(intakeFile, () => {
5285

5386
expect(actual).toEqual([value]);
5487
});
88+
89+
it("returns undefined when filePath points to a directory in a directory", () => {
90+
const actual = intakeFile(
91+
{
92+
src: {
93+
"file.txt": {},
94+
},
95+
},
96+
["src", "file.txt"],
97+
);
98+
99+
expect(actual).toBeUndefined();
100+
});
101+
102+
it("returns the file when filePath does not point to a file in the root", () => {
103+
const value = "abc123";
104+
105+
const actual = intakeFile(
106+
{
107+
"file.txt": [value],
108+
},
109+
["other.txt"],
110+
);
111+
112+
expect(actual).toBeUndefined();
113+
});
114+
115+
it("returns the file when filePath does not point to a file in a directory", () => {
116+
const value = "abc123";
117+
118+
const actual = intakeFile(
119+
{
120+
src: {
121+
"file.txt": [value],
122+
},
123+
},
124+
["src", "other.txt"],
125+
);
126+
127+
expect(actual).toBeUndefined();
128+
});
129+
130+
it("returns the file when filePath is an array whose first element points to a file in a directory", () => {
131+
const value = "abc123";
132+
133+
const actual = intakeFile(
134+
{
135+
src: {
136+
"file.txt": [value],
137+
},
138+
},
139+
["src", ["file.txt", "other.txt"]],
140+
);
141+
142+
expect(actual).toEqual([value]);
143+
});
144+
145+
it("returns the file when filePath is an array whose second element points to a file in a directory", () => {
146+
const value = "abc123";
147+
148+
const actual = intakeFile(
149+
{
150+
src: {
151+
"file.txt": [value],
152+
},
153+
},
154+
["src", ["other.txt", "file.txt"]],
155+
);
156+
157+
expect(actual).toEqual([value]);
158+
});
159+
160+
it("returns nothing when filePath is an array and no element points to a file in a directory", () => {
161+
const value = "abc123";
162+
163+
const actual = intakeFile(
164+
{
165+
src: {
166+
"file.txt": [value],
167+
},
168+
},
169+
["src", ["other-a.txt", "other-b.txt"]],
170+
);
171+
172+
expect(actual).toBeUndefined();
173+
});
55174
});

0 commit comments

Comments
 (0)