diff --git a/package.json b/package.json
index 09d562a8..3e704deb 100644
--- a/package.json
+++ b/package.json
@@ -44,12 +44,12 @@
"@clack/prompts": "^0.8.2",
"@prettier/sync": "^0.5.2",
"chalk": "^5.3.0",
- "create": "0.1.0-alpha.5",
+ "create": "0.1.0-alpha.6",
"execa": "^9.5.1",
"git-remote-origin-url": "^4.0.0",
"git-url-parse": "^16.0.0",
- "input-from-file": "0.1.0-alpha.0",
- "input-from-file-json": "0.1.0-alpha.1",
+ "input-from-file": "0.1.0-alpha.2",
+ "input-from-file-json": "0.1.0-alpha.2",
"js-yaml": "^4.1.0",
"lazy-value": "^3.0.0",
"npm-user": "^6.1.1",
@@ -82,7 +82,7 @@
"all-contributors-cli": "6.26.1",
"c8": "10.1.2",
"console-fail-test": "0.5.0",
- "create-testers": "0.1.0-alpha.5",
+ "create-testers": "0.1.0-alpha.6",
"cspell": "8.16.1",
"eslint": "9.16.0",
"eslint-plugin-jsdoc": "50.6.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c1343176..19ad37d2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -18,8 +18,8 @@ importers:
specifier: ^5.3.0
version: 5.3.0
create:
- specifier: 0.1.0-alpha.5
- version: 0.1.0-alpha.5
+ specifier: 0.1.0-alpha.6
+ version: 0.1.0-alpha.6
execa:
specifier: ^9.5.1
version: 9.5.2
@@ -30,11 +30,11 @@ importers:
specifier: ^16.0.0
version: 16.0.0
input-from-file:
- specifier: 0.1.0-alpha.0
- version: 0.1.0-alpha.0(create@0.1.0-alpha.5)
+ specifier: 0.1.0-alpha.2
+ version: 0.1.0-alpha.2(create@0.1.0-alpha.6)
input-from-file-json:
- specifier: 0.1.0-alpha.1
- version: 0.1.0-alpha.1(create@0.1.0-alpha.5)
+ specifier: 0.1.0-alpha.2
+ version: 0.1.0-alpha.2(create@0.1.0-alpha.6)
js-yaml:
specifier: ^4.1.0
version: 4.1.0
@@ -127,8 +127,8 @@ importers:
specifier: 0.5.0
version: 0.5.0
create-testers:
- specifier: 0.1.0-alpha.5
- version: 0.1.0-alpha.5(create@0.1.0-alpha.5)
+ specifier: 0.1.0-alpha.6
+ version: 0.1.0-alpha.6(create@0.1.0-alpha.6)
cspell:
specifier: 8.16.1
version: 8.16.1
@@ -1897,14 +1897,14 @@ packages:
typescript:
optional: true
- create-testers@0.1.0-alpha.5:
- resolution: {integrity: sha512-0Wgn9P6zi0Gn89yI3NqB7Qxx+jn2/v5P+VvQTTCWgnh/qkc/DqlKs7t2FZNJdMiSDfokWFpbBCiJmkDzcK8HHQ==}
+ create-testers@0.1.0-alpha.6:
+ resolution: {integrity: sha512-WF5D+6fhZmtR8+wTDXml//HdO4tzwrmo8sjpKBwYGtZXLl8VCkoqYAtKqe2TjUBNXrRTfISAKWbEYQykcD1ukw==}
engines: {node: '>=18'}
peerDependencies:
- create: 0.1.0-alpha.5
+ create: 0.1.0-alpha.6
- create@0.1.0-alpha.5:
- resolution: {integrity: sha512-pHbkdn8a6FTI9Mo09mETR9WyX9I9tka4lQDHNUHEUZuatKP+j4qyStFrktOGXV0hoSg2mTqGV+s4cktbOegdYg==}
+ create@0.1.0-alpha.6:
+ resolution: {integrity: sha512-XWaBK+NMK+LTeT7cod96e4sXQ9K8FGRK05tF9oQIhD5p/zWEfdoGUKQZoEW0WsBHZBiiuP1Y0W0uTPC0LoTBVQ==}
engines: {node: '>=18'}
hasBin: true
@@ -2622,17 +2622,23 @@ packages:
resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
- input-from-file-json@0.1.0-alpha.1:
- resolution: {integrity: sha512-fkB4Y8dwplNkaiRe2O9IX1hYYz1efUxFX5bCKiiqUmrB67yTnUoDeDg+LlKJr/py6L7aPkCObZ6MrF5WH+PDiQ==}
+ input-from-file-json@0.1.0-alpha.2:
+ resolution: {integrity: sha512-YjH+2/nc2SNM/iHmc8EOTyYSwJJeffvoxCdNxVqIwlNmENB/jPh1wu5ffd1VFZKsg878jFG7OEFEmm4YHY9PUg==}
engines: {node: '>=18'}
peerDependencies:
- create: 0.1.0-alpha.0
+ create: 0.1.0-alpha.6
- input-from-file@0.1.0-alpha.0:
- resolution: {integrity: sha512-XcGuSxpNEMN2tlMV+TfMNK21KrUN8T6RNWeIUIyIukJ7TzNbf+lgSTl9rxjAXWmNfFA59puMQk6qsa+HJ76X+g==}
+ input-from-file@0.1.0-alpha.1:
+ resolution: {integrity: sha512-O4BrmU5E8HYNuS8Yqc68N60zSf3rUGCtBcEcYf9DBjMhuLDhNF2XHNjuuOuZTPtNkSZuYk0N+ix2Op+FjWnEHw==}
engines: {node: '>=18'}
peerDependencies:
- create: 0.1.0-alpha.0
+ create: 0.1.0-alpha.6
+
+ input-from-file@0.1.0-alpha.2:
+ resolution: {integrity: sha512-R2YwGgdVIGJJP6/XkFYR3KQaFTGTz5RsEdoGDGWc9D+5yeVVWLdBtcfqIY4IGknXlquVOSORxmVfAiwnZ80sgg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ create: 0.1.0-alpha.6
inquirer@7.3.3:
resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==}
@@ -5769,12 +5775,12 @@ snapshots:
optionalDependencies:
typescript: 5.7.2
- create-testers@0.1.0-alpha.5(create@0.1.0-alpha.5):
+ create-testers@0.1.0-alpha.6(create@0.1.0-alpha.6):
dependencies:
- create: 0.1.0-alpha.5
+ create: 0.1.0-alpha.6
octokit: 4.0.2
- create@0.1.0-alpha.5:
+ create@0.1.0-alpha.6:
dependencies:
execa: 9.5.2
hash-object: 5.0.1
@@ -6673,15 +6679,20 @@ snapshots:
ini@4.1.1: {}
- input-from-file-json@0.1.0-alpha.1(create@0.1.0-alpha.5):
+ input-from-file-json@0.1.0-alpha.2(create@0.1.0-alpha.6):
+ dependencies:
+ create: 0.1.0-alpha.6
+ input-from-file: 0.1.0-alpha.1(create@0.1.0-alpha.6)
+ zod: 3.24.1
+
+ input-from-file@0.1.0-alpha.1(create@0.1.0-alpha.6):
dependencies:
- create: 0.1.0-alpha.5
- input-from-file: 0.1.0-alpha.0(create@0.1.0-alpha.5)
+ create: 0.1.0-alpha.6
zod: 3.24.1
- input-from-file@0.1.0-alpha.0(create@0.1.0-alpha.5):
+ input-from-file@0.1.0-alpha.2(create@0.1.0-alpha.6):
dependencies:
- create: 0.1.0-alpha.5
+ create: 0.1.0-alpha.6
zod: 3.24.1
inquirer@7.3.3:
diff --git a/src/next/blocks/blockAllContributors.test.ts b/src/next/blocks/blockAllContributors.test.ts
new file mode 100644
index 00000000..2226113b
--- /dev/null
+++ b/src/next/blocks/blockAllContributors.test.ts
@@ -0,0 +1,128 @@
+import { testBlock } from "create-testers";
+import { describe, expect, it } from "vitest";
+
+import { blockAllContributors } from "./blockAllContributors.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockAllContributors", () => {
+ it("defaults contributors to [] when not provided", () => {
+ const creation = testBlock(blockAllContributors, { options: optionsBase });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "ignores": [
+ "/.all-contributorsrc",
+ ],
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ ".all-contributorsrc": "{"badgeTemplate":"\\t\\" src=\\"https://img.shields.io/badge/%F0%9F%91%AA_all_contributors-<%= contributors.length %>-21bb42.svg\\" />","commit":false,"commitConvention":"angular","commitType":"docs","contributors":[],"contributorsPerLine":7,"contributorsSortAlphabetically":true,"files":["README.md"],"imageSize":100,"projectName":"test-repository","projectOwner":"test-owner","repoHost":"https://github.com","repoType":"github"}",
+ ".github": {
+ "workflows": {
+ "contributors.yml": "jobs:
+ contributors:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - uses: ./.github/actions/prepare
+ - env:
+ GITHUB_TOKEN: \${{ secrets.ACCESS_TOKEN }}
+ uses: JoshuaKGoldberg/all-contributors-auto-action@v0.5.0
+
+ name: Contributors
+
+ on:
+ push:
+ branches:
+ - main
+ ",
+ },
+ },
+ },
+ "scripts": [
+ {
+ "commands": [
+ "npx -y all-contributors-cli generate",
+ "npx -y all-contributors-cli add test-owner code,content,docs,ideas,infra,maintenance,projectManagement,tool",
+ ],
+ "phase": 2,
+ },
+ ],
+ }
+ `);
+ });
+
+ it("includes contributors when not provided", () => {
+ const creation = testBlock(blockAllContributors, {
+ options: {
+ ...optionsBase,
+ contributors: [
+ {
+ avatar_url: "https://avatars.githubusercontent.com/u/3335181?v=4",
+ contributions: ["bug", "ideas"],
+ login: "JoshuaKGoldberg",
+ name: "Josh Goldberg",
+ profile: "http://www.joshuakgoldberg.com",
+ },
+ ],
+ },
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "ignores": [
+ "/.all-contributorsrc",
+ ],
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ ".all-contributorsrc": "{"badgeTemplate":"\\t\\" src=\\"https://img.shields.io/badge/%F0%9F%91%AA_all_contributors-<%= contributors.length %>-21bb42.svg\\" />","commit":false,"commitConvention":"angular","commitType":"docs","contributors":[{"avatar_url":"https://avatars.githubusercontent.com/u/3335181?v=4","contributions":["bug","ideas"],"login":"JoshuaKGoldberg","name":"Josh Goldberg","profile":"http://www.joshuakgoldberg.com"}],"contributorsPerLine":7,"contributorsSortAlphabetically":true,"files":["README.md"],"imageSize":100,"projectName":"test-repository","projectOwner":"test-owner","repoHost":"https://github.com","repoType":"github"}",
+ ".github": {
+ "workflows": {
+ "contributors.yml": "jobs:
+ contributors:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - uses: ./.github/actions/prepare
+ - env:
+ GITHUB_TOKEN: \${{ secrets.ACCESS_TOKEN }}
+ uses: JoshuaKGoldberg/all-contributors-auto-action@v0.5.0
+
+ name: Contributors
+
+ on:
+ push:
+ branches:
+ - main
+ ",
+ },
+ },
+ },
+ "scripts": [
+ {
+ "commands": [
+ "npx -y all-contributors-cli generate",
+ "npx -y all-contributors-cli add test-owner code,content,docs,ideas,infra,maintenance,projectManagement,tool",
+ ],
+ "phase": 2,
+ },
+ ],
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockAreTheTypesWrong.test.ts b/src/next/blocks/blockAreTheTypesWrong.test.ts
new file mode 100644
index 00000000..04524ed0
--- /dev/null
+++ b/src/next/blocks/blockAreTheTypesWrong.test.ts
@@ -0,0 +1,35 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockAreTheTypesWrong } from "./blockAreTheTypesWrong.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockAreTheTypesWrong", () => {
+ test("production", () => {
+ const creation = testBlock(blockAreTheTypesWrong, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "jobs": [
+ {
+ "name": "Are The Types Wrong?",
+ "steps": [
+ {
+ "run": "npx --yes @arethetypeswrong/cli --pack . --ignore-rules cjs-resolves-to-esm",
+ },
+ ],
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ ],
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockCSpell.test.ts b/src/next/blocks/blockCSpell.test.ts
new file mode 100644
index 00000000..a7ffb5c4
--- /dev/null
+++ b/src/next/blocks/blockCSpell.test.ts
@@ -0,0 +1,143 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockCSpell } from "./blockCSpell.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockCSpell", () => {
+ test("without addons", () => {
+ const creation = testBlock(blockCSpell, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "sections": {
+ "Linting": {
+ "contents": {
+ "items": [
+ "- \`pnpm lint:spelling\` ([cspell](https://cspell.org)): Spell checks across all source files",
+ ],
+ },
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "extensions": [
+ "streetsidesoftware.code-spell-checker",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "jobs": [
+ {
+ "name": "Lint Spelling",
+ "steps": [
+ {
+ "run": "pnpm lint:spelling",
+ },
+ ],
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "properties": {
+ "devDependencies": {
+ "cspell": "8.16.1",
+ },
+ "scripts": {
+ "lint:spelling": "cspell "**" ".github/**/*"",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ "cspell.json": "{"dictionaries":["npm","node","typescript"],"ignorePaths":[".github","CHANGELOG.md","lib","node_modules","pnpm-lock.yaml"]}",
+ },
+ }
+ `);
+ });
+
+ test("with addons", () => {
+ const creation = testBlock(blockCSpell, {
+ addons: {
+ ignores: ["lib/"],
+ words: ["joshuakgoldberg"],
+ },
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "sections": {
+ "Linting": {
+ "contents": {
+ "items": [
+ "- \`pnpm lint:spelling\` ([cspell](https://cspell.org)): Spell checks across all source files",
+ ],
+ },
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "extensions": [
+ "streetsidesoftware.code-spell-checker",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "jobs": [
+ {
+ "name": "Lint Spelling",
+ "steps": [
+ {
+ "run": "pnpm lint:spelling",
+ },
+ ],
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "properties": {
+ "devDependencies": {
+ "cspell": "8.16.1",
+ },
+ "scripts": {
+ "lint:spelling": "cspell "**" ".github/**/*"",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ "cspell.json": "{"dictionaries":["npm","node","typescript"],"ignorePaths":[".github","CHANGELOG.md","lib","lib/","node_modules","pnpm-lock.yaml"],"words":["joshuakgoldberg"]}",
+ },
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockDevelopmentDocs.test.ts b/src/next/blocks/blockDevelopmentDocs.test.ts
new file mode 100644
index 00000000..c057e47a
--- /dev/null
+++ b/src/next/blocks/blockDevelopmentDocs.test.ts
@@ -0,0 +1,145 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockDevelopmentDocs } from "./blockDevelopmentDocs.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockDevelopmentDocs", () => {
+ test("without addons", () => {
+ const creation = testBlock(blockDevelopmentDocs, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "files": {
+ ".github": {
+ "DEVELOPMENT.md": "# Development
+
+ After [forking the repo from GitHub](https://help.github.com/articles/fork-a-repo) and [installing pnpm](https://pnpm.io/installation):
+
+ \`\`\`shell
+ git clone https://github.com//test-repository
+ cd test-repository
+ pnpm install
+ \`\`\`
+ ",
+ },
+ },
+ }
+ `);
+ });
+
+ test("with addons", () => {
+ const creation = testBlock(blockDevelopmentDocs, {
+ addons: {
+ hints: ["> Be excellent to each other!"],
+ sections: {
+ First: {
+ contents: "abc def",
+ },
+ Fourth: {},
+ Second: {
+ contents: {
+ after: ["second-after"],
+ before: "second-before",
+ items: ["- a", "- b", "- c"],
+ plural: "seconds",
+ },
+ innerSections: [
+ {
+ contents: "second-inner",
+ heading: "Seconds Inside",
+ },
+ ],
+ },
+ Third: {
+ contents: {},
+ innerSections: [
+ {
+ contents: "second-inner",
+ heading: "Seconds Inside",
+ },
+ ],
+ },
+ },
+ },
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "files": {
+ ".github": {
+ "DEVELOPMENT.md": "# Development
+
+ After [forking the repo from GitHub](https://help.github.com/articles/fork-a-repo) and [installing pnpm](https://pnpm.io/installation):
+
+ \`\`\`shell
+ git clone https://github.com//test-repository
+ cd test-repository
+ pnpm install
+ \`\`\`
+
+ > Be excellent to each other!
+
+ ## First
+
+ abc def
+ ## Second
+
+ second-before
+ - a
+ - b
+ - c
+
+ seconds
+ second-after
+ ### Seconds Inside
+
+ second-inner
+ ## Third
+
+ ### Seconds Inside
+
+ second-inner",
+ },
+ },
+ }
+ `);
+ });
+
+ test("with options.guide", () => {
+ const creation = testBlock(blockDevelopmentDocs, {
+ options: {
+ ...optionsBase,
+ guide: {
+ href: "https://example.com",
+ title: "My Guide",
+ },
+ },
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "files": {
+ ".github": {
+ "DEVELOPMENT.md": "# Development
+
+ > If you'd like a more guided walkthrough, see [My Guide](https://example.com).
+ > It'll walk you through the common activities you'll need to contribute.
+
+ After [forking the repo from GitHub](https://help.github.com/articles/fork-a-repo) and [installing pnpm](https://pnpm.io/installation):
+
+ \`\`\`shell
+ git clone https://github.com//test-repository
+ cd test-repository
+ pnpm install
+ \`\`\`
+ ",
+ },
+ },
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockDevelopmentDocs.ts b/src/next/blocks/blockDevelopmentDocs.ts
index 597676d9..27501763 100644
--- a/src/next/blocks/blockDevelopmentDocs.ts
+++ b/src/next/blocks/blockDevelopmentDocs.ts
@@ -25,16 +25,12 @@ const zSection = z.object({
}),
])
.optional(),
- innerSections: z.array(zInnerSection).default([]).optional(),
+ innerSections: z.array(zInnerSection).optional(),
});
type Section = z.infer;
function printSection(heading: string, section: Section) {
- if (typeof section === "string") {
- return section;
- }
-
const innerSections = section.innerSections?.flatMap(printInnerSection) ?? [];
if (section.contents === undefined) {
@@ -75,9 +71,9 @@ export const blockDevelopmentDocs = base.createBlock({
? [
`> If you'd like a more guided walkthrough, see [${options.guide.title}](${options.guide.href}).`,
`> It'll walk you through the common activities you'll need to contribute.`,
+ ``,
]
: []),
- ``,
`After [forking the repo from GitHub](https://help.github.com/articles/fork-a-repo) and [installing pnpm](https://pnpm.io/installation):`,
``,
`\`\`\`shell`,
@@ -86,9 +82,7 @@ export const blockDevelopmentDocs = base.createBlock({
`pnpm install`,
`\`\`\``,
``,
- `> This repository includes a list of suggested VS Code extensions.`,
- `> It's a good idea to use [VS Code](https://code.visualstudio.com) and accept its suggestion to install them, as they'll help with development.`,
- ``,
+ ...(addons.hints.length ? [...addons.hints, ``] : []),
...Object.entries(addons.sections)
.sort(([a], [b]) => a.localeCompare(b))
.flatMap(([heading, section]) => printSection(heading, section)),
diff --git a/src/next/blocks/blockESLint.test.ts b/src/next/blocks/blockESLint.test.ts
new file mode 100644
index 00000000..87ad728a
--- /dev/null
+++ b/src/next/blocks/blockESLint.test.ts
@@ -0,0 +1,297 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockESLint } from "./blockESLint.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockESLint", () => {
+ test("without addons", () => {
+ const creation = testBlock(blockESLint, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "words": [
+ "tseslint",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "sections": {
+ "Linting": {
+ "contents": {
+ "after": [
+ "
+ For example, ESLint can be run with \`--fix\` to auto-fix some lint rule complaints:
+
+ \`\`\`shell
+ pnpm run lint --fix
+ \`\`\`
+ ",
+ ],
+ "before": "
+ This package includes several forms of linting to enforce consistent code quality and styling.
+ Each should be shown in VS Code, and can be run manually on the command-line:
+ ",
+ "items": [
+ "- \`pnpm lint\` ([ESLint](https://eslint.org) with [typescript-eslint](https://typescript-eslint.io)): Lints JavaScript and TypeScript source files",
+ ],
+ "plural": "Read the individual documentation for each linter to understand how it can be configured and used best.",
+ },
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "jobs": [
+ {
+ "name": "Lint",
+ "steps": [
+ {
+ "run": "pnpm lint",
+ },
+ ],
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "properties": {
+ "devDependencies": {
+ "@eslint/js": "9.16.0",
+ "@types/node": "22.10.1",
+ "eslint": "9.16.0",
+ "typescript-eslint": "8.18.0",
+ },
+ "scripts": {
+ "lint": "eslint . --max-warnings 0",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "extensions": [
+ "dbaeumer.vscode-eslint",
+ ],
+ "settings": {
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": "explicit",
+ },
+ "eslint.probe": [
+ "javascript",
+ "javascriptreact",
+ "json",
+ "jsonc",
+ "markdown",
+ "typescript",
+ "typescriptreact",
+ "yaml",
+ ],
+ "eslint.rules.customizations": [
+ {
+ "rule": "*",
+ "severity": "warn",
+ },
+ ],
+ },
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ "eslint.config.js": "import eslint from "@eslint/js";
+ import tseslint from "typescript-eslint";
+
+ export default tseslint.config(
+ { ignores: ["lib", "node_modules", "pnpm-lock.yaml"] },
+ { linterOptions: {"reportUnusedDisableDirectives":"error"} },
+ eslint.configs.recommended,
+ { extends: [tseslint.configs.strictTypeChecked, tseslint.configs.stylisticTypeChecked], files: ["**/*.js", "**/*.ts"], languageOptions: {"parserOptions":{"projectService":{"allowDefaultProject":["*.config.*s"]},"tsconfigRootDir":import.meta.dirname}}, }
+ );",
+ },
+ "scripts": [
+ {
+ "commands": [
+ "pnpm lint --fix",
+ ],
+ "phase": 2,
+ },
+ ],
+ }
+ `);
+ });
+
+ test("with addons", () => {
+ const creation = testBlock(blockESLint, {
+ addons: {
+ beforeLint: "Before lint.",
+ extensions: [
+ "a.configs.recommended",
+ {
+ extends: ["b.configs.recommended"],
+ files: ["**/*.b"],
+ rules: {
+ "b/c": "error",
+ "b/d": ["error", { e: "f" }],
+ },
+ },
+ ],
+ ignores: ["generated"],
+ imports: [
+ { source: "eslint-plugin-markdown", specifier: "a", types: true },
+ { source: "eslint-plugin-regexp", specifier: "b" },
+ ],
+ rules: {
+ "a/b": "error",
+ "a/c": ["error", { d: "e" }],
+ },
+ settings: {
+ react: {
+ version: "detect",
+ },
+ },
+ },
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "words": [
+ "tseslint",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "sections": {
+ "Linting": {
+ "contents": {
+ "after": [
+ "
+ For example, ESLint can be run with \`--fix\` to auto-fix some lint rule complaints:
+
+ \`\`\`shell
+ pnpm run lint --fix
+ \`\`\`
+ ",
+ "Before lint.",
+ ],
+ "before": "
+ This package includes several forms of linting to enforce consistent code quality and styling.
+ Each should be shown in VS Code, and can be run manually on the command-line:
+ ",
+ "items": [
+ "- \`pnpm lint\` ([ESLint](https://eslint.org) with [typescript-eslint](https://typescript-eslint.io)): Lints JavaScript and TypeScript source files",
+ ],
+ "plural": "Read the individual documentation for each linter to understand how it can be configured and used best.",
+ },
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "jobs": [
+ {
+ "name": "Lint",
+ "steps": [
+ {
+ "run": "pnpm lint",
+ },
+ ],
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "properties": {
+ "devDependencies": {
+ "@eslint/js": "9.16.0",
+ "@types/eslint-plugin-markdown": "2.0.2",
+ "@types/node": "22.10.1",
+ "eslint": "9.16.0",
+ "eslint-plugin-markdown": "5.1.0",
+ "eslint-plugin-regexp": "2.7.0",
+ "typescript-eslint": "8.18.0",
+ },
+ "scripts": {
+ "lint": "eslint . --max-warnings 0",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "extensions": [
+ "dbaeumer.vscode-eslint",
+ ],
+ "settings": {
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": "explicit",
+ },
+ "eslint.probe": [
+ "javascript",
+ "javascriptreact",
+ "json",
+ "jsonc",
+ "markdown",
+ "typescript",
+ "typescriptreact",
+ "yaml",
+ ],
+ "eslint.rules.customizations": [
+ {
+ "rule": "*",
+ "severity": "warn",
+ },
+ ],
+ },
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ "eslint.config.js": "import eslint from "@eslint/js";
+ import a from "eslint-plugin-markdown"
+ import b from "eslint-plugin-regexp"
+ import tseslint from "typescript-eslint";
+
+ export default tseslint.config(
+ { ignores: ["generated", "lib", "node_modules", "pnpm-lock.yaml"] },
+ { linterOptions: {"reportUnusedDisableDirectives":"error"} },
+ eslint.configs.recommended,
+ a.configs.recommended,{ extends: [b.configs.recommended], files: ["**/*.b"], rules: {"b/c":"error","b/d":["error",{"e":"f"}]}, },{ extends: [tseslint.configs.strictTypeChecked, tseslint.configs.stylisticTypeChecked], files: ["**/*.js", "**/*.ts"], languageOptions: {"parserOptions":{"projectService":{"allowDefaultProject":["*.config.*s"]},"tsconfigRootDir":import.meta.dirname}}, rules: {"a/b":"error","a/c":["error",{"d":"e"}]}, settings: {"react":{"version":"detect"}}, }
+ );",
+ },
+ "scripts": [
+ {
+ "commands": [
+ "pnpm lint --fix",
+ ],
+ "phase": 2,
+ },
+ ],
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockExampleFiles.test.ts b/src/next/blocks/blockExampleFiles.test.ts
new file mode 100644
index 00000000..b6392c37
--- /dev/null
+++ b/src/next/blocks/blockExampleFiles.test.ts
@@ -0,0 +1,50 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockExampleFiles } from "./blockExampleFiles.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockExampleFiles", () => {
+ test("without addons", () => {
+ const creation = testBlock(blockExampleFiles, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`{}`);
+ });
+
+ test("with addons and without mode", () => {
+ const creation = testBlock(blockExampleFiles, {
+ addons: {
+ files: {
+ "index.ts": "console.log('Hello, world!');",
+ },
+ },
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`{}`);
+ });
+
+ test("with addons and mode", () => {
+ const creation = testBlock(blockExampleFiles, {
+ addons: {
+ files: {
+ "index.ts": "console.log('Hello, world!');",
+ },
+ },
+ mode: "new",
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "files": {
+ "src": {
+ "index.ts": "console.log('Hello, world!');",
+ },
+ },
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockGitHubActionsCI.test.ts b/src/next/blocks/blockGitHubActionsCI.test.ts
new file mode 100644
index 00000000..bbe02a89
--- /dev/null
+++ b/src/next/blocks/blockGitHubActionsCI.test.ts
@@ -0,0 +1,216 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockGitHubActionsCI } from "./blockGitHubActionsCI.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockGitHubActionsCI", () => {
+ test("without addons", () => {
+ const creation = testBlock(blockGitHubActionsCI, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "files": {
+ ".github": {
+ "actions": {
+ "prepare": {
+ "action.yml": "description: Prepares the repo for a typical CI job
+
+ name: Prepare
+
+ runs:
+ steps:
+ - uses: pnpm/action-setup@v4
+ with:
+ version: 9
+ - uses: actions/setup-node@v4
+ with:
+ cache: pnpm
+ node-version: '20'
+ - run: pnpm install --frozen-lockfile
+ shell: bash
+ using: composite
+ ",
+ },
+ },
+ "workflows": {
+ "accessibility-alt-text-bot.yml": "jobs:
+ accessibility_alt_text_bot:
+ if: \${{ !endsWith(github.actor, '[bot]') }}
+ runs-on: ubuntu-latest
+ steps:
+ - uses: github/accessibility-alt-text-bot@v1.4.0
+
+ name: Accessibility Alt Text Bot
+
+ on:
+ issue_comment:
+ types:
+ - created
+ - edited
+ issues:
+ types:
+ - edited
+ - opened
+ pull_request:
+ types:
+ - edited
+ - opened
+
+ permissions:
+ issues: write
+ pull-requests: write
+ ",
+ "ci.yml": undefined,
+ "pr-review-requested.yml": "jobs:
+ pr_review_requested:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions-ecosystem/action-remove-labels@v1
+ with:
+ labels: "status: waiting for author"
+ - if: failure()
+ run: |
+ echo "Don't worry if the previous step failed."
+ echo "See https://github.com/actions-ecosystem/action-remove-labels/issues/221."
+
+ name: PR Review Requested
+
+ on:
+ pull_request_target:
+ types:
+ - review_requested
+
+ permissions:
+ pull-requests: write
+ ",
+ },
+ },
+ },
+ }
+ `);
+ });
+
+ test("with addons", () => {
+ const creation = testBlock(blockGitHubActionsCI, {
+ addons: {
+ jobs: [
+ {
+ name: "Validate",
+ steps: [
+ {
+ if: "always()",
+ run: "pnpm validate",
+ with: { ENV_VAR: "true" },
+ },
+ ],
+ },
+ ],
+ },
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "files": {
+ ".github": {
+ "actions": {
+ "prepare": {
+ "action.yml": "description: Prepares the repo for a typical CI job
+
+ name: Prepare
+
+ runs:
+ steps:
+ - uses: pnpm/action-setup@v4
+ with:
+ version: 9
+ - uses: actions/setup-node@v4
+ with:
+ cache: pnpm
+ node-version: '20'
+ - run: pnpm install --frozen-lockfile
+ shell: bash
+ using: composite
+ ",
+ },
+ },
+ "workflows": {
+ "accessibility-alt-text-bot.yml": "jobs:
+ accessibility_alt_text_bot:
+ if: \${{ !endsWith(github.actor, '[bot]') }}
+ runs-on: ubuntu-latest
+ steps:
+ - uses: github/accessibility-alt-text-bot@v1.4.0
+
+ name: Accessibility Alt Text Bot
+
+ on:
+ issue_comment:
+ types:
+ - created
+ - edited
+ issues:
+ types:
+ - edited
+ - opened
+ pull_request:
+ types:
+ - edited
+ - opened
+
+ permissions:
+ issues: write
+ pull-requests: write
+ ",
+ "ci.yml": "jobs:
+ validate:
+ name: Validate
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: ./.github/actions/prepare
+ - if: always()
+ run: pnpm validate
+ with:
+ ENV_VAR: "true"
+
+ name: CI
+
+ on:
+ pull_request: ~
+ push:
+ branches:
+ - main
+ ",
+ "pr-review-requested.yml": "jobs:
+ pr_review_requested:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions-ecosystem/action-remove-labels@v1
+ with:
+ labels: "status: waiting for author"
+ - if: failure()
+ run: |
+ echo "Don't worry if the previous step failed."
+ echo "See https://github.com/actions-ecosystem/action-remove-labels/issues/221."
+
+ name: PR Review Requested
+
+ on:
+ pull_request_target:
+ types:
+ - review_requested
+
+ permissions:
+ pull-requests: write
+ ",
+ },
+ },
+ },
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockGitignore.test.ts b/src/next/blocks/blockGitignore.test.ts
new file mode 100644
index 00000000..67c2b35b
--- /dev/null
+++ b/src/next/blocks/blockGitignore.test.ts
@@ -0,0 +1,41 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockGitignore } from "./blockGitignore.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockGitignore", () => {
+ test("without addons", () => {
+ const creation = testBlock(blockGitignore, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "files": {
+ ".gitignore": "/node_modules
+ ",
+ },
+ }
+ `);
+ });
+
+ test("with addons", () => {
+ const creation = testBlock(blockGitignore, {
+ addons: {
+ ignores: ["/lib"],
+ },
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "files": {
+ ".gitignore": "/lib
+ /node_modules
+ ",
+ },
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockMarkdownlint.test.ts b/src/next/blocks/blockMarkdownlint.test.ts
new file mode 100644
index 00000000..e955adbb
--- /dev/null
+++ b/src/next/blocks/blockMarkdownlint.test.ts
@@ -0,0 +1,169 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockMarkdownlint } from "./blockMarkdownlint.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockMarkdownlint", () => {
+ test("without addons", () => {
+ const creation = testBlock(blockMarkdownlint, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "words": [
+ "markdownlintignore",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "sections": {
+ "Linting": {
+ "contents": {
+ "items": [
+ "- \`pnpm lint:md\` ([Markdownlint](https://github.com/DavidAnson/markdownlint)): Checks Markdown source files",
+ ],
+ },
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "jobs": [
+ {
+ "name": "Lint Markdown",
+ "steps": [
+ {
+ "run": "pnpm lint:md",
+ },
+ ],
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "properties": {
+ "devDependencies": {
+ "markdownlint": "0.36.1",
+ "markdownlint-cli": "0.43.0",
+ "sentences-per-line": "0.2.1",
+ },
+ "scripts": {
+ "lint:md": "markdownlint "**/*.md" ".github/**/*.md" --rules sentences-per-line",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "extensions": [
+ "DavidAnson.vscode-markdownlint",
+ ],
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ ".markdownlint.json": "{"extends":"markdownlint/style/prettier","first-line-h1":false,"no-inline-html":false}",
+ ".markdownlintignore": ".github/CODE_OF_CONDUCT.md
+ CHANGELOG.md
+ node_modules/",
+ },
+ }
+ `);
+ });
+
+ test("with addons", () => {
+ const creation = testBlock(blockMarkdownlint, {
+ addons: {
+ ignores: ["lib/"],
+ },
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "words": [
+ "markdownlintignore",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "sections": {
+ "Linting": {
+ "contents": {
+ "items": [
+ "- \`pnpm lint:md\` ([Markdownlint](https://github.com/DavidAnson/markdownlint)): Checks Markdown source files",
+ ],
+ },
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "jobs": [
+ {
+ "name": "Lint Markdown",
+ "steps": [
+ {
+ "run": "pnpm lint:md",
+ },
+ ],
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "properties": {
+ "devDependencies": {
+ "markdownlint": "0.36.1",
+ "markdownlint-cli": "0.43.0",
+ "sentences-per-line": "0.2.1",
+ },
+ "scripts": {
+ "lint:md": "markdownlint "**/*.md" ".github/**/*.md" --rules sentences-per-line",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "extensions": [
+ "DavidAnson.vscode-markdownlint",
+ ],
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ ".markdownlint.json": "{"extends":"markdownlint/style/prettier","first-line-h1":false,"no-inline-html":false}",
+ ".markdownlintignore": ".github/CODE_OF_CONDUCT.md
+ CHANGELOG.md
+ lib/
+ node_modules/",
+ },
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockPackageJson.test.ts b/src/next/blocks/blockPackageJson.test.ts
new file mode 100644
index 00000000..fe3ae479
--- /dev/null
+++ b/src/next/blocks/blockPackageJson.test.ts
@@ -0,0 +1,61 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockPackageJson } from "./blockPackageJson.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockPackageJson", () => {
+ test("without addons", () => {
+ const creation = testBlock(blockPackageJson, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "files": {
+ "package.json": "{"name":"test-repository","version":"0.0.0","description":"Test description","repository":{"type":"git","url":"https://github.com/test-owner/test-repository"},"license":"MIT","author":{"email":"npm@email.com"},"type":"module","main":"./lib/index.js","files":["README.md","package.json"],"scripts":{},"dependencies":{},"devDependencies":{}}",
+ },
+ "scripts": [
+ {
+ "commands": [
+ "pnpm install",
+ ],
+ "phase": 0,
+ },
+ ],
+ }
+ `);
+ });
+
+ test("with addons", () => {
+ const creation = testBlock(blockPackageJson, {
+ addons: {
+ cleanupCommands: ["pnpm dedupe"],
+ properties: {
+ dependencies: {
+ "is-odd": "3.0.1",
+ },
+ other: true,
+ },
+ },
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "files": {
+ "package.json": "{"name":"test-repository","version":"0.0.0","description":"Test description","repository":{"type":"git","url":"https://github.com/test-owner/test-repository"},"license":"MIT","author":{"email":"npm@email.com"},"type":"module","main":"./lib/index.js","files":["README.md","package.json"],"scripts":{},"dependencies":{"is-odd":"3.0.1"},"devDependencies":{},"other":true}",
+ },
+ "scripts": [
+ {
+ "commands": [
+ "pnpm install",
+ "pnpm dedupe",
+ ],
+ "phase": 0,
+ },
+ ],
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockPrettier.test.ts b/src/next/blocks/blockPrettier.test.ts
new file mode 100644
index 00000000..a87b04d7
--- /dev/null
+++ b/src/next/blocks/blockPrettier.test.ts
@@ -0,0 +1,236 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockPrettier } from "./blockPrettier.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockPrettier", () => {
+ test("without addons", () => {
+ const creation = testBlock(blockPrettier, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "ignores": [
+ ".all-contributorsrc",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "sections": {
+ "Formatting": {
+ "contents": "
+ [Prettier](https://prettier.io) is used to format code.
+ It should be applied automatically when you save files in VS Code or make a Git commit.
+
+ To manually reformat all files, you can run:
+
+ \`\`\`shell
+ pnpm format --write
+ \`\`\`
+ ",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "jobs": [
+ {
+ "name": "Prettier",
+ "steps": [
+ {
+ "run": "pnpm format --list-different",
+ },
+ ],
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "properties": {
+ "devDependencies": {
+ "husky": "9.1.7",
+ "lint-staged": "15.2.10",
+ "prettier": "^3.4.1",
+ },
+ "lint-staged": {
+ "*": "prettier --ignore-unknown --write",
+ },
+ "scripts": {
+ "format": "prettier .",
+ "prepare": "husky",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "extensions": [
+ "esbenp.prettier-vscode",
+ ],
+ "settings": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ },
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ ".husky": {
+ ".gitignore": "_",
+ "pre-commit": [
+ "npx lint-staged",
+ {
+ "mode": 1911,
+ },
+ ],
+ },
+ ".prettierignore": "/.husky
+ /lib
+ /pnpm-lock.yaml",
+ ".prettierrc.json": "{"$schema":"http://json.schemastore.org/prettierrc","useTabs":true}",
+ },
+ "scripts": [
+ {
+ "commands": [
+ "pnpm format --write",
+ ],
+ "phase": 3,
+ },
+ ],
+ }
+ `);
+ });
+
+ test("with addons", () => {
+ const creation = testBlock(blockPrettier, {
+ addons: {
+ ignores: ["generated"],
+ overrides: [{ files: ".nvmrc", options: { parser: "yaml" } }],
+ plugins: [
+ "prettier-plugin-curly",
+ "prettier-plugin-packagejson",
+ "prettier-plugin-sh",
+ ],
+ },
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "ignores": [
+ ".all-contributorsrc",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "sections": {
+ "Formatting": {
+ "contents": "
+ [Prettier](https://prettier.io) is used to format code.
+ It should be applied automatically when you save files in VS Code or make a Git commit.
+
+ To manually reformat all files, you can run:
+
+ \`\`\`shell
+ pnpm format --write
+ \`\`\`
+ ",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "jobs": [
+ {
+ "name": "Prettier",
+ "steps": [
+ {
+ "run": "pnpm format --list-different",
+ },
+ ],
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "properties": {
+ "devDependencies": {
+ "husky": "9.1.7",
+ "lint-staged": "15.2.10",
+ "prettier": "^3.4.1",
+ "prettier-plugin-curly": "0.3.1",
+ "prettier-plugin-packagejson": "2.5.6",
+ "prettier-plugin-sh": "0.14.0",
+ },
+ "lint-staged": {
+ "*": "prettier --ignore-unknown --write",
+ },
+ "scripts": {
+ "format": "prettier .",
+ "prepare": "husky",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "extensions": [
+ "esbenp.prettier-vscode",
+ ],
+ "settings": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ },
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ ".husky": {
+ ".gitignore": "_",
+ "pre-commit": [
+ "npx lint-staged",
+ {
+ "mode": 1911,
+ },
+ ],
+ },
+ ".prettierignore": "/.husky
+ /lib
+ /pnpm-lock.yaml
+ generated",
+ ".prettierrc.json": "{"$schema":"http://json.schemastore.org/prettierrc","overrides":[{"files":".nvmrc","options":{"parser":"yaml"}}],"plugins":["prettier-plugin-curly","prettier-plugin-packagejson","prettier-plugin-sh"],"useTabs":true}",
+ },
+ "scripts": [
+ {
+ "commands": [
+ "pnpm format --write",
+ ],
+ "phase": 3,
+ },
+ ],
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockREADME.test.ts b/src/next/blocks/blockREADME.test.ts
new file mode 100644
index 00000000..2b4e5353
--- /dev/null
+++ b/src/next/blocks/blockREADME.test.ts
@@ -0,0 +1,179 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockREADME } from "./blockREADME.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockREADME", () => {
+ test("options.logo", () => {
+ const creation = testBlock(blockREADME, {
+ options: {
+ ...optionsBase,
+ logo: {
+ alt: "My logo",
+ src: "img.jpg",
+ },
+ },
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "files": {
+ "README.md": "Test Title
+
+ Test description
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ## Usage
+
+ \`\`\`shell
+ npm i test-repository
+ \`\`\`
+ \`\`\`ts
+ import { greet } from "test-repository";
+
+ greet("Hello, world! ๐");
+ \`\`\`
+
+ ## Development
+
+ See [\`.github/CONTRIBUTING.md\`](./.github/CONTRIBUTING.md), then [\`.github/DEVELOPMENT.md\`](./.github/DEVELOPMENT.md).
+ Thanks! ๐
+
+ ## Contributors
+
+
+
+
+
+ ",
+ },
+ }
+ `);
+ });
+
+ test("without addons", () => {
+ const creation = testBlock(blockREADME, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "files": {
+ "README.md": "Test Title
+
+ Test description
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ## Usage
+
+ \`\`\`shell
+ npm i test-repository
+ \`\`\`
+ \`\`\`ts
+ import { greet } from "test-repository";
+
+ greet("Hello, world! ๐");
+ \`\`\`
+
+ ## Development
+
+ See [\`.github/CONTRIBUTING.md\`](./.github/CONTRIBUTING.md), then [\`.github/DEVELOPMENT.md\`](./.github/DEVELOPMENT.md).
+ Thanks! ๐
+
+ ## Contributors
+
+
+
+
+
+ ",
+ },
+ }
+ `);
+ });
+
+ test("with addons", () => {
+ const creation = testBlock(blockREADME, {
+ addons: {
+ notices: ["> Hello, world! ๐"],
+ },
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "files": {
+ "README.md": "Test Title
+
+ Test description
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ## Usage
+
+ \`\`\`shell
+ npm i test-repository
+ \`\`\`
+ \`\`\`ts
+ import { greet } from "test-repository";
+
+ greet("Hello, world! ๐");
+ \`\`\`
+
+ ## Development
+
+ See [\`.github/CONTRIBUTING.md\`](./.github/CONTRIBUTING.md), then [\`.github/DEVELOPMENT.md\`](./.github/DEVELOPMENT.md).
+ Thanks! ๐
+
+ ## Contributors
+
+
+
+
+
+
+ > Hello, world! ๐",
+ },
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockRepositoryBranchProtection.test.ts b/src/next/blocks/blockRepositoryBranchProtection.test.ts
new file mode 100644
index 00000000..bef44aa9
--- /dev/null
+++ b/src/next/blocks/blockRepositoryBranchProtection.test.ts
@@ -0,0 +1,47 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockRepositoryBranchProtection } from "./blockRepositoryBranchProtection.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockRepositoryBranchProtection", () => {
+ test("without addons", () => {
+ const creation = testBlock(blockRepositoryBranchProtection, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "requests": [
+ {
+ "id": "branch-protection",
+ "send": [Function],
+ },
+ ],
+ }
+ `);
+ });
+
+ // TODO for improving the "requests" snapshots:
+ // https://github.com/JoshuaKGoldberg/create/issues/65
+
+ test("with addons", () => {
+ const creation = testBlock(blockRepositoryBranchProtection, {
+ addons: {
+ requiredStatusChecks: ["build", "test"],
+ },
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "requests": [
+ {
+ "id": "branch-protection",
+ "send": [Function],
+ },
+ ],
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockTSup.test.ts b/src/next/blocks/blockTSup.test.ts
new file mode 100644
index 00000000..ccde157f
--- /dev/null
+++ b/src/next/blocks/blockTSup.test.ts
@@ -0,0 +1,184 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockTSup } from "./blockTSup.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockTSup", () => {
+ test("without addons", () => {
+ const creation = testBlock(blockTSup, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "sections": {
+ "Building": {
+ "contents": "
+ Run [**tsup**](https://tsup.egoist.dev) locally to build source files from \`src/\` into output files in \`lib/\`:
+
+ \`\`\`shell
+ pnpm build
+ \`\`\`
+
+ Add \`--watch\` to run the builder in a watch mode that continuously cleans and recreates \`lib/\` as you save files:
+
+ \`\`\`shell
+ pnpm build --watch
+ \`\`\`
+ ",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "beforeLint": "Note that you'll need to run \`pnpm build\` before \`pnpm lint\` so that lint rules which check the file system can pick up on any built files.",
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "jobs": [
+ {
+ "name": "Build",
+ "steps": [
+ {
+ "run": "pnpm build",
+ },
+ {
+ "run": "node ./lib/index.js",
+ },
+ ],
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "properties": {
+ "devDependencies": {
+ "tsup": "8.3.5",
+ },
+ "scripts": {
+ "build": "tsup",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ "tsup.config.ts": "import { defineConfig } from "tsup";
+
+ export default defineConfig({
+ bundle: false,
+ clean: true,
+ dts: true,
+ entry: ["src/**/*.ts"],
+ format: "esm",
+ outDir: "lib",
+ sourcemap: true,
+ });
+ ",
+ },
+ "scripts": undefined,
+ }
+ `);
+ });
+
+ test("with addons", () => {
+ const creation = testBlock(blockTSup, {
+ addons: {
+ entry: ["!src/**/*.test.ts"],
+ },
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "sections": {
+ "Building": {
+ "contents": "
+ Run [**tsup**](https://tsup.egoist.dev) locally to build source files from \`src/\` into output files in \`lib/\`:
+
+ \`\`\`shell
+ pnpm build
+ \`\`\`
+
+ Add \`--watch\` to run the builder in a watch mode that continuously cleans and recreates \`lib/\` as you save files:
+
+ \`\`\`shell
+ pnpm build --watch
+ \`\`\`
+ ",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "beforeLint": "Note that you'll need to run \`pnpm build\` before \`pnpm lint\` so that lint rules which check the file system can pick up on any built files.",
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "jobs": [
+ {
+ "name": "Build",
+ "steps": [
+ {
+ "run": "pnpm build",
+ },
+ {
+ "run": "node ./lib/index.js",
+ },
+ ],
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "properties": {
+ "devDependencies": {
+ "tsup": "8.3.5",
+ },
+ "scripts": {
+ "build": "tsup",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ "tsup.config.ts": "import { defineConfig } from "tsup";
+
+ export default defineConfig({
+ bundle: false,
+ clean: true,
+ dts: true,
+ entry: ["src/**/*.ts","!src/**/*.test.ts"],
+ format: "esm",
+ outDir: "lib",
+ sourcemap: true,
+ });
+ ",
+ },
+ "scripts": undefined,
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockTemplatedBy.ts b/src/next/blocks/blockTemplatedBy.ts
index bcf3aab7..72403124 100644
--- a/src/next/blocks/blockTemplatedBy.ts
+++ b/src/next/blocks/blockTemplatedBy.ts
@@ -1,5 +1,3 @@
-import { z } from "zod";
-
import { base } from "../base.js";
import { blockCSpell } from "./blockCSpell.js";
import { blockREADME } from "./blockREADME.js";
@@ -8,9 +6,6 @@ export const blockTemplatedBy = base.createBlock({
about: {
name: "Templated By Notice",
},
- addons: {
- notices: z.array(z.string()).default([]),
- },
produce() {
return {
addons: [
diff --git a/src/next/blocks/blockTypeScript.test.ts b/src/next/blocks/blockTypeScript.test.ts
new file mode 100644
index 00000000..47f57b24
--- /dev/null
+++ b/src/next/blocks/blockTypeScript.test.ts
@@ -0,0 +1,313 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockTypeScript } from "./blockTypeScript.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockTypeScript", () => {
+ test("without options.bin", () => {
+ const creation = testBlock(blockTypeScript, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "sections": {
+ "Type Checking": {
+ "contents": "
+ You should be able to see suggestions from [TypeScript](https://typescriptlang.org) in your editor for all open files.
+
+ However, it can be useful to run the TypeScript command-line (\`tsc\`) to type check all files in \`src/\`:
+
+ \`\`\`shell
+ pnpm tsc
+ \`\`\`
+
+ Add \`--watch\` to keep the type checker running in a watch mode that updates the display as you save files:
+
+ \`\`\`shell
+ pnpm tsc --watch
+ \`\`\`
+ ",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "files": {
+ "greet.ts": "import { GreetOptions } from "./types.js";
+
+ export function greet(options: GreetOptions | string) {
+ const {
+ logger = console.log.bind(console),
+ message,
+ times = 1,
+ } = typeof options === "string" ? { message: options } : options;
+
+ for (let i = 0; i < times; i += 1) {
+ logger(message);
+ }
+ }
+ ",
+ "index.ts": "export * from "./greet.js";
+ export * from "./types.js";
+ ",
+ "types.ts": "export interface GreetOptions {
+ logger?: (message: string) => void;
+ message: string;
+ times?: number;
+ }
+ ",
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "ignores": [
+ "/lib",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "jobs": [
+ {
+ "name": "Type Check",
+ "steps": [
+ {
+ "run": "pnpm tsc",
+ },
+ ],
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "ignores": [
+ "lib/",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "properties": {
+ "devDependencies": {
+ "typescript": "5.7.2",
+ },
+ "files": [
+ "lib/",
+ ],
+ "main": "./lib/index.js",
+ "scripts": {
+ "tsc": "tsc",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "exclude": [
+ "lib",
+ ],
+ "include": [
+ "src",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "debuggers": [],
+ "settings": {
+ "typescript.tsdk": "node_modules/typescript/lib",
+ },
+ "tasks": [
+ {
+ "detail": "Build the project",
+ "label": "build",
+ "script": "build",
+ "type": "npm",
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ "tsconfig.json": "{"compilerOptions":{"declaration":true,"declarationMap":true,"esModuleInterop":true,"module":"NodeNext","moduleResolution":"NodeNext","noEmit":true,"resolveJsonModule":true,"skipLibCheck":true,"sourceMap":true,"strict":true,"target":"ES2022"},"include":["src"]}",
+ },
+ }
+ `);
+ });
+
+ test("with options.bin", () => {
+ const creation = testBlock(blockTypeScript, {
+ options: {
+ ...optionsBase,
+ bin: "bin/index.mjs",
+ },
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "sections": {
+ "Type Checking": {
+ "contents": "
+ You should be able to see suggestions from [TypeScript](https://typescriptlang.org) in your editor for all open files.
+
+ However, it can be useful to run the TypeScript command-line (\`tsc\`) to type check all files in \`src/\`:
+
+ \`\`\`shell
+ pnpm tsc
+ \`\`\`
+
+ Add \`--watch\` to keep the type checker running in a watch mode that updates the display as you save files:
+
+ \`\`\`shell
+ pnpm tsc --watch
+ \`\`\`
+ ",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "files": {
+ "greet.ts": "import { GreetOptions } from "./types.js";
+
+ export function greet(options: GreetOptions | string) {
+ const {
+ logger = console.log.bind(console),
+ message,
+ times = 1,
+ } = typeof options === "string" ? { message: options } : options;
+
+ for (let i = 0; i < times; i += 1) {
+ logger(message);
+ }
+ }
+ ",
+ "index.ts": "export * from "./greet.js";
+ export * from "./types.js";
+ ",
+ "types.ts": "export interface GreetOptions {
+ logger?: (message: string) => void;
+ message: string;
+ times?: number;
+ }
+ ",
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "ignores": [
+ "/lib",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "jobs": [
+ {
+ "name": "Type Check",
+ "steps": [
+ {
+ "run": "pnpm tsc",
+ },
+ ],
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "ignores": [
+ "lib/",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "properties": {
+ "devDependencies": {
+ "typescript": "5.7.2",
+ },
+ "files": [
+ "lib/",
+ ],
+ "main": "./lib/index.js",
+ "scripts": {
+ "tsc": "tsc",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "exclude": [
+ "lib",
+ ],
+ "include": [
+ "src",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "debuggers": [
+ {
+ "name": "Debug Program",
+ "preLaunchTask": "build",
+ "program": "bin/index.mjs",
+ "request": "launch",
+ "skipFiles": [
+ "/**",
+ ],
+ "type": "node",
+ },
+ ],
+ "settings": {
+ "typescript.tsdk": "node_modules/typescript/lib",
+ },
+ "tasks": [
+ {
+ "detail": "Build the project",
+ "label": "build",
+ "script": "build",
+ "type": "npm",
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ "tsconfig.json": "{"compilerOptions":{"declaration":true,"declarationMap":true,"esModuleInterop":true,"module":"NodeNext","moduleResolution":"NodeNext","noEmit":true,"resolveJsonModule":true,"skipLibCheck":true,"sourceMap":true,"strict":true,"target":"ES2022"},"include":["src"]}",
+ },
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockVSCode.test.ts b/src/next/blocks/blockVSCode.test.ts
new file mode 100644
index 00000000..91c0130a
--- /dev/null
+++ b/src/next/blocks/blockVSCode.test.ts
@@ -0,0 +1,118 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockVSCode } from "./blockVSCode.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockVSCode", () => {
+ test("without addons", () => {
+ const creation = testBlock(blockVSCode, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "hints": [
+ "> This repository includes a list of suggested VS Code extensions.",
+ "> It's a good idea to use [VS Code](https://code.visualstudio.com) and accept its suggestion to install them, as they'll help with development.",
+ ],
+ "sections": {
+ "Building": {
+ "innerSections": [],
+ },
+ "Testing": {
+ "innerSections": [
+ {
+ "contents": "
+ This repository includes a [VS Code launch configuration](https://code.visualstudio.com/docs/editor/debugging) for debugging unit tests.
+ To launch it, open a test file, then run _Debug Current Test File_ from the VS Code Debug panel (or press F5).
+ ",
+ "heading": "Debugging Tests",
+ },
+ ],
+ },
+ },
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ ".vscode": {
+ "extensions.json": undefined,
+ "launch.json": undefined,
+ "settings.json": "{"editor.formatOnSave":true,"editor.rulers":[80]}",
+ "tasks.json": undefined,
+ },
+ },
+ }
+ `);
+ });
+
+ test("with addons", () => {
+ const creation = testBlock(blockVSCode, {
+ addons: {
+ debuggers: [
+ {
+ name: "fake-debugger",
+ other: true,
+ },
+ ],
+ settings: {
+ "editor.formatOnSave": true,
+ },
+ tasks: [
+ {
+ detail: "Build the project",
+ label: "build",
+ script: "build",
+ type: "npm",
+ },
+ ],
+ },
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "hints": [
+ "> This repository includes a list of suggested VS Code extensions.",
+ "> It's a good idea to use [VS Code](https://code.visualstudio.com) and accept its suggestion to install them, as they'll help with development.",
+ ],
+ "sections": {
+ "Building": {
+ "innerSections": [],
+ },
+ "Testing": {
+ "innerSections": [
+ {
+ "contents": "
+ This repository includes a [VS Code launch configuration](https://code.visualstudio.com/docs/editor/debugging) for debugging unit tests.
+ To launch it, open a test file, then run _Debug Current Test File_ from the VS Code Debug panel (or press F5).
+ ",
+ "heading": "Debugging Tests",
+ },
+ ],
+ },
+ },
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ ".vscode": {
+ "extensions.json": undefined,
+ "launch.json": "{"configurations":[{"name":"fake-debugger","other":true}],"version":"0.2.0"}",
+ "settings.json": "{"editor.formatOnSave":true,"editor.rulers":[80]}",
+ "tasks.json": "{"tasks":[{"detail":"Build the project","label":"build","script":"build","type":"npm"}],"version":"2.0.0"}",
+ },
+ },
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/blockVSCode.ts b/src/next/blocks/blockVSCode.ts
index d1779288..81efe730 100644
--- a/src/next/blocks/blockVSCode.ts
+++ b/src/next/blocks/blockVSCode.ts
@@ -34,6 +34,10 @@ export const blockVSCode = base.createBlock({
return {
addons: [
blockDevelopmentDocs({
+ hints: [
+ `> This repository includes a list of suggested VS Code extensions.`,
+ `> It's a good idea to use [VS Code](https://code.visualstudio.com) and accept its suggestion to install them, as they'll help with development.`,
+ ],
sections: {
Building: {
innerSections: options.bin
diff --git a/src/next/blocks/blockVitest.test.ts b/src/next/blocks/blockVitest.test.ts
new file mode 100644
index 00000000..83476358
--- /dev/null
+++ b/src/next/blocks/blockVitest.test.ts
@@ -0,0 +1,492 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test } from "vitest";
+
+import { blockVitest } from "./blockVitest.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockVitest", () => {
+ test("without addons", () => {
+ const creation = testBlock(blockVitest, {
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "ignores": [
+ "coverage",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "extensions": [
+ {
+ "extends": [
+ "vitest.configs.recommended",
+ ],
+ "files": [
+ "**/*.test.*",
+ ],
+ "rules": [
+ {
+ "entries": {
+ "@typescript-eslint/no-unsafe-assignment": "off",
+ },
+ },
+ ],
+ },
+ ],
+ "ignores": [
+ "coverage",
+ "**/*.snap",
+ ],
+ "imports": [
+ {
+ "source": "@vitest/eslint-plugin",
+ "specifier": "vitest",
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "sections": {
+ "Testing": {
+ "contents": "
+ [Vitest](https://vitest.dev) is used for tests.
+ You can run it locally on the command-line:
+
+ \`\`\`shell
+ pnpm run test
+ \`\`\`
+
+ Add the \`--coverage\` flag to compute test coverage and place reports in the \`coverage/\` directory:
+
+ \`\`\`shell
+ pnpm run test --coverage
+ \`\`\`
+
+ Note that [console-fail-test](https://github.com/JoshuaKGoldberg/console-fail-test) is enabled for all test runs.
+ Calls to \`console.log\`, \`console.warn\`, and other console methods will cause a test to fail.
+
+
+ ",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "files": {
+ "greet.test.ts": "import { describe, expect, it, vi } from "vitest";
+
+ import { greet } from "./greet.js";
+
+ const message = "Yay, testing!";
+
+ describe("greet", () => {
+ it("logs to the console once when message is provided as a string", () => {
+ const logger = vi.spyOn(console, "log").mockImplementation(() => undefined);
+
+ greet(message);
+
+ expect(logger).toHaveBeenCalledWith(message);
+ expect(logger).toHaveBeenCalledTimes(1);
+ });
+
+ it("logs to the console once when message is provided as an object", () => {
+ const logger = vi.spyOn(console, "log").mockImplementation(() => undefined);
+
+ greet({ message });
+
+ expect(logger).toHaveBeenCalledWith(message);
+ expect(logger).toHaveBeenCalledTimes(1);
+ });
+
+ it("logs once when times is not provided in an object", () => {
+ const logger = vi.fn();
+
+ greet({ logger, message });
+
+ expect(logger).toHaveBeenCalledWith(message);
+ expect(logger).toHaveBeenCalledTimes(1);
+ });
+
+ it("logs a specified number of times when times is provided", () => {
+ const logger = vi.fn();
+ const times = 7;
+
+ greet({ logger, message, times });
+
+ expect(logger).toHaveBeenCalledWith(message);
+ expect(logger).toHaveBeenCalledTimes(7);
+ });
+ });
+ ",
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "ignores": [
+ "/coverage",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "jobs": [
+ {
+ "name": "Test",
+ "steps": [
+ {
+ "run": "pnpm run test --coverage",
+ },
+ {
+ "if": "always()",
+ "uses": "codecov/codecov-action@v3",
+ },
+ ],
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "properties": {
+ "devDependencies": {
+ "@vitest/coverage-v8": "2.1.8",
+ "@vitest/eslint-plugin": "1.1.14",
+ "console-fail-test": "0.5.0",
+ "vitest": "2.1.8",
+ },
+ "scripts": {
+ "test": "vitest",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "ignores": [
+ "/coverage",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "entry": [
+ "!src/**/*.test.*",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "debuggers": [
+ {
+ "args": [
+ "run",
+ "\${relativeFile}",
+ ],
+ "autoAttachChildProcesses": true,
+ "console": "integratedTerminal",
+ "name": "Debug Current Test File",
+ "program": "\${workspaceRoot}/node_modules/vitest/vitest.mjs",
+ "request": "launch",
+ "skipFiles": [
+ "/**",
+ "**/node_modules/**",
+ ],
+ "smartStep": true,
+ "type": "node",
+ },
+ ],
+ "extensions": [
+ "vitest.explorer",
+ ],
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ "vitest.config.ts": "import { defineConfig } from "vitest/config";
+
+ export default defineConfig({
+ test: {
+ clearMocks: true,
+ coverage: {
+ all: true,
+ exclude: [],
+ include: [],
+ reporter: ["html", "lcov"],
+ },
+ exclude: [, "node_modules"],
+ setupFiles: ["console-fail-test/setup"],
+ },
+ });
+ ",
+ },
+ }
+ `);
+ });
+
+ test("with addons", () => {
+ const creation = testBlock(blockVitest, {
+ addons: {
+ coverage: {
+ directory: "coverage*",
+ flags: "unit",
+ },
+ exclude: ["lib/"],
+ include: ["src/"],
+ },
+ options: optionsBase,
+ });
+
+ expect(creation).toMatchInlineSnapshot(`
+ {
+ "addons": [
+ {
+ "addons": {
+ "ignores": [
+ "coverage*",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "extensions": [
+ {
+ "extends": [
+ "vitest.configs.recommended",
+ ],
+ "files": [
+ "**/*.test.*",
+ ],
+ "rules": [
+ {
+ "entries": {
+ "@typescript-eslint/no-unsafe-assignment": "off",
+ },
+ },
+ ],
+ },
+ ],
+ "ignores": [
+ "coverage*",
+ "**/*.snap",
+ ],
+ "imports": [
+ {
+ "source": "@vitest/eslint-plugin",
+ "specifier": "vitest",
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "sections": {
+ "Testing": {
+ "contents": "
+ [Vitest](https://vitest.dev) is used for tests.
+ You can run it locally on the command-line:
+
+ \`\`\`shell
+ pnpm run test
+ \`\`\`
+
+ Add the \`--coverage\` flag to compute test coverage and place reports in the \`coverage/\` directory:
+
+ \`\`\`shell
+ pnpm run test --coverage
+ \`\`\`
+
+ Note that [console-fail-test](https://github.com/JoshuaKGoldberg/console-fail-test) is enabled for all test runs.
+ Calls to \`console.log\`, \`console.warn\`, and other console methods will cause a test to fail.
+
+
+ ",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "files": {
+ "greet.test.ts": "import { describe, expect, it, vi } from "vitest";
+
+ import { greet } from "./greet.js";
+
+ const message = "Yay, testing!";
+
+ describe("greet", () => {
+ it("logs to the console once when message is provided as a string", () => {
+ const logger = vi.spyOn(console, "log").mockImplementation(() => undefined);
+
+ greet(message);
+
+ expect(logger).toHaveBeenCalledWith(message);
+ expect(logger).toHaveBeenCalledTimes(1);
+ });
+
+ it("logs to the console once when message is provided as an object", () => {
+ const logger = vi.spyOn(console, "log").mockImplementation(() => undefined);
+
+ greet({ message });
+
+ expect(logger).toHaveBeenCalledWith(message);
+ expect(logger).toHaveBeenCalledTimes(1);
+ });
+
+ it("logs once when times is not provided in an object", () => {
+ const logger = vi.fn();
+
+ greet({ logger, message });
+
+ expect(logger).toHaveBeenCalledWith(message);
+ expect(logger).toHaveBeenCalledTimes(1);
+ });
+
+ it("logs a specified number of times when times is provided", () => {
+ const logger = vi.fn();
+ const times = 7;
+
+ greet({ logger, message, times });
+
+ expect(logger).toHaveBeenCalledWith(message);
+ expect(logger).toHaveBeenCalledTimes(7);
+ });
+ });
+ ",
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "ignores": [
+ "/coverage*",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "jobs": [
+ {
+ "name": "Test",
+ "steps": [
+ {
+ "run": "pnpm run test --coverage",
+ },
+ {
+ "if": "always()",
+ "uses": "codecov/codecov-action@v3",
+ "with": {
+ "flags": "unit",
+ },
+ },
+ ],
+ },
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "properties": {
+ "devDependencies": {
+ "@vitest/coverage-v8": "2.1.8",
+ "@vitest/eslint-plugin": "1.1.14",
+ "console-fail-test": "0.5.0",
+ "vitest": "2.1.8",
+ },
+ "scripts": {
+ "test": "vitest",
+ },
+ },
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "ignores": [
+ "/coverage*",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "entry": [
+ "!src/**/*.test.*",
+ ],
+ },
+ "block": [Function],
+ },
+ {
+ "addons": {
+ "debuggers": [
+ {
+ "args": [
+ "run",
+ "\${relativeFile}",
+ ],
+ "autoAttachChildProcesses": true,
+ "console": "integratedTerminal",
+ "name": "Debug Current Test File",
+ "program": "\${workspaceRoot}/node_modules/vitest/vitest.mjs",
+ "request": "launch",
+ "skipFiles": [
+ "/**",
+ "**/node_modules/**",
+ ],
+ "smartStep": true,
+ "type": "node",
+ },
+ ],
+ "extensions": [
+ "vitest.explorer",
+ ],
+ },
+ "block": [Function],
+ },
+ ],
+ "files": {
+ "vitest.config.ts": "import { defineConfig } from "vitest/config";
+
+ export default defineConfig({
+ test: {
+ clearMocks: true,
+ coverage: {
+ all: true,
+ exclude: ["lib/"],
+ include: ["src/"],
+ reporter: ["html", "lcov"],
+ },
+ exclude: ["lib/", "node_modules"],
+ setupFiles: ["console-fail-test/setup"],
+ },
+ });
+ ",
+ },
+ }
+ `);
+ });
+});
diff --git a/src/next/blocks/packageData.test.ts b/src/next/blocks/packageData.test.ts
new file mode 100644
index 00000000..14eafebd
--- /dev/null
+++ b/src/next/blocks/packageData.test.ts
@@ -0,0 +1,36 @@
+import { describe, expect, it, vi } from "vitest";
+
+import { getPackageDependencies } from "./packageData.js";
+
+vi.mock("node:module", () => ({
+ createRequire: () => () => ({
+ dependencies: {
+ "package-dep": "0.0.2",
+ },
+ devDependencies: {
+ "package-dev-dep": "0.0.1",
+ },
+ }),
+}));
+
+describe("packageData", () => {
+ it("returns a devDependency when it exists", () => {
+ const actual = getPackageDependencies("package-dev-dep");
+
+ expect(actual).toEqual({ "package-dev-dep": "0.0.1" });
+ });
+
+ it("returns a dependency when it exists", () => {
+ const actual = getPackageDependencies("package-dep");
+
+ expect(actual).toEqual({ "package-dep": "0.0.2" });
+ });
+
+ it("throws an error when neither exist", () => {
+ const act = () => getPackageDependencies("package-unknown");
+
+ expect(act).toThrowError(
+ "package-unknown is neither in package.json's dependencies nor its devDependencies.",
+ );
+ });
+});
diff --git a/src/next/blocks/packageData.ts b/src/next/blocks/packageData.ts
index bbec5e03..f3def9b8 100644
--- a/src/next/blocks/packageData.ts
+++ b/src/next/blocks/packageData.ts
@@ -4,7 +4,6 @@ const require = createRequire(import.meta.url);
const packageData =
// Importing from above src/ would expand the TS build rootDir
-
require("../../../package.json") as typeof import("../../../package.json");
const getPackageInner = (
diff --git a/src/next/utils/removeTrailingSlash.ts b/src/next/utils/removeTrailingSlash.ts
deleted file mode 100644
index 12dd8500..00000000
--- a/src/next/utils/removeTrailingSlash.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export function removeTrailingSlash(text: string) {
- return text.replace(/\/$/, "");
-}