Repository: JoshuaKGoldberg/create-typescript-app (ts)

Issue:
🚀 Feature: Add an option to opt out of build script
### Bug Report Checklist

- [X] I have pulled the latest `main` branch of the repository.
- [X] I have [searched for related issues](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aissue) and found none that matched my issue.

### Overview

Right now, all projects must have a `"build": "tsup"` task. But not all repos created with this necessarily want that! For example, my browser extensions use `ncc`. Let's make it so the `build` task optional with an `--exclude-build`.

### Additional Info

This feeds into the long-term project vision (#1181) of being built on a totally agnostic, composable core.

💖 

Pull request description:
feat: add --exclude-build option
## PR Checklist

- [x] Addresses an existing open issue: fixes #1559
- [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

Renames the _"minimum"_ to _**"minimal"**_ to reflect that yiou actually can get _less_ than what's in it now.

Patch:
diff --git a/docs/FAQs.md b/docs/FAQs.md
index 1ce013c..ab65b4b 100644
--- a/docs/FAQs.md
+++ b/docs/FAQs.md
@@ -164,10 +164,10 @@ Using `eslint-config-prettier` would be redundant.
 
 The four bases correspond to what have seemed to be the most common user needs of template consumers:
 
-1. **Minimum**: Developers who just want the barest of starting templates.
+1. **Minimal**: Developers who just want the barest of starting templates.
    - They may be very new to TypeScript tooling, or they may have made an informed decision that the additional tooling isn't worth the complexity and/or time investment.
    - Tooling in this base is only what would be essential for a small TypeScript package that can be built, formatted, linted, and released.
-2. **Common**: The common case of users who want the minimum tooling along with common repository management.
+2. **Common**: The common case of users who want the minimal tooling along with common repository management.
    - Tooling added in this base should be essential for a TypeScript repository that additionally automates useful GitHub tasks: contributor recognition, release management, and testing.
 3. **Everything**: Power users (including this repository) who want as much of the latest and greatest safety checks and standardization as possible.
 
diff --git a/docs/Options.md b/docs/Options.md
index d622dec..bedb99c 100644
--- a/docs/Options.md
+++ b/docs/Options.md
@@ -13,8 +13,8 @@ The following required options will be prompted for interactively if not provide
 These required options determine how the creation script will set up and scaffold the repository:
 
 - `--base`: Whether to scaffold the repository with:
-  - `minimum`: Just the bare starter tooling most repositories should ideally include
-  - `common`: Important additions to the minimum starters such as releases and tests
+  - `minimal`: Just the bare starter tooling most repositories should ideally include
+  - `common`: Important additions to the minimal starters such as releases and tests
   - `everything`: The most thorough tooling imaginable: sorting, spellchecking, and more!
   - `prompt`: Fine-grained control over which tooling pieces to use
 - `--mode`: Whether to:
@@ -84,6 +84,7 @@ The setup scripts normally will prompt you to select how much of the tooling you
 Alternately, you can bypass that prompt by providing any number of the following CLI flags:
 
 - `--exclude-all-contributors`: Don't add all-contributors to track contributions and display them in a README.md table.
+- `--exclude-build`: Don't add a build task to generate built `.js`, `.d.ts`, and related output.
 - `--exclude-compliance`: Don't add a GitHub Actions workflow to verify that PRs match an expected format.
 - `--exclude-lint-json`: Don't apply linting and sorting to `*.json` and `*.jsonc` files.
 - `--exclude-lint-knip`: Don't add Knip to detect unused files, dependencies, and code exports.
diff --git a/docs/Tooling.md b/docs/Tooling.md
index bb8cb7f..ab9d952 100644
--- a/docs/Tooling.md
+++ b/docs/Tooling.md
@@ -8,8 +8,8 @@ Those template levels provide common presets of which tooling pieces to enable.
 
 ```plaintext
 ◆  How much tooling would you like the template to set up for you?
-│  ○ minimum     Just the bare starter tooling most repositories should ideally include.
-│  ○ common      Important additions to the minimum starters such as releases and tests.
+│  ○ minimal     Just the bare starter tooling most repositories should ideally include.
+│  ○ common      Important additions to the minimal starters such as releases and tests.
 │  ○ everything  The most thorough tooling imaginable: sorting, spellchecking, and more!
 │  ○ prompt      (allow me to customize)
 └
@@ -17,7 +17,7 @@ Those template levels provide common presets of which tooling pieces to enable.
 
 This table summarizes each tooling piece and which base levels they're included in:
 
-| Tooling Piece                                 | Exclusion Flag                 | Minimum | Common | Everything |
+| Tooling Piece                                 | Exclusion Flag                 | Minimal | Common | Everything |
 | --------------------------------------------- | ------------------------------ | ------- | ------ | ---------- |
 | [Building](#building)                         |                                | ✔️      | ✅     | 💯         |
 | [Compliance](#compliance)                     | `--exclude-compliance`         |         |        | 💯         |
@@ -46,12 +46,12 @@ This table summarizes each tooling piece and which base levels they're included
 
 See also [Options](./Options.md) for how to customize the way template is run.
 
-## "Minimum" Base Level
+## "Minimal" Base Level
 
 These tooling pieces are the ones that most repositories should generally always have enabled.
 Other pieces of tooling are likely to not work as well (or at all) if these are removed.
 
-The _"minimum"_ base is best suited for projects that are very small and not likely to change very frequently.
+The _"minimal"_ base is best suited for projects that are very small and not likely to change very frequently.
 However, they'll be missing out on many of the great tooling pieces enabled in more comprehensive bases.
 We strongly recommend using at least the [_"common"_ base level](#common-base-level) instead for most repositories.
 
diff --git a/src/shared/options/args.ts b/src/shared/options/args.ts
index d160720..ed3a88d 100644
--- a/src/shared/options/args.ts
+++ b/src/shared/options/args.ts
@@ -26,7 +26,8 @@ export const allArgOptions = {
   • everything: that comes with the template (${chalk.cyanBright.bold(
 		"recommended",
 	)})
-  • minimum: amounts of tooling, essentially opting out of everything
+  • common: additions to the minimal starters such as releases and tests
+  • minimal: amounts of tooling, essentially opting out of everything
   • prompt: for which portions to exclude`,
 		docsSection: "core",
 		type: "string",
diff --git a/src/shared/options/augmentOptionsWithExcludes.ts b/src/shared/options/augmentOptionsWithExcludes.ts
index e9a415b..c25b7bf 100644
--- a/src/shared/options/augmentOptionsWithExcludes.ts
+++ b/src/shared/options/augmentOptionsWithExcludes.ts
@@ -47,10 +47,10 @@ export async function augmentOptionsWithExcludes(
 					},
 					{
 						label: makeLabel(
-							"minimum",
+							"minimal",
 							"Just bare starter tooling: building, formatting, linting, and type checking.",
 						),
-						value: "minimum",
+						value: "minimal",
 					},
 					{
 						label: makeLabel("prompt", "(allow me to customize)"),
@@ -64,8 +64,8 @@ export async function augmentOptionsWithExcludes(
 		case undefined:
 			return undefined;
 		case "common":
-		case "minimum":
 		case "everything":
+		case "minimal":
 			return {
 				...options,
 				base,
diff --git a/src/shared/options/exclusionKeys.ts b/src/shared/options/exclusionKeys.ts
index 1173899..47c3ea9 100644
--- a/src/shared/options/exclusionKeys.ts
+++ b/src/shared/options/exclusionKeys.ts
@@ -5,7 +5,7 @@ export type ExclusionKey = `exclude${string}` & keyof Options;
 export interface ExclusionDescription {
 	hint: string;
 	label: string;
-	uncommon?: true;
+	level?: "common" | "minimal";
 }
 
 export const exclusionDescriptions: Record<ExclusionKey, ExclusionDescription> =
@@ -15,28 +15,33 @@ export const exclusionDescriptions: Record<ExclusionKey, ExclusionDescription> =
 			label:
 				"Add all-contributors to track contributions and display them in a README.md table.",
 		},
+		excludeBuild: {
+			hint: "--exclude-build",
+			label: "Add a tsup build step to generate built output files.",
+			level: "minimal",
+		},
 		excludeCompliance: {
 			hint: "--exclude-compliance",
 			label:
 				"Add a GitHub Actions workflow to verify that PRs match an expected format.",
-			uncommon: true,
+			level: "common",
 		},
 		excludeLintESLint: {
 			hint: "--exclude-lint-eslint",
 			label:
 				"Include eslint-plugin-eslint-comment to enforce good practices around ESLint comment directives.",
-			uncommon: true,
+			level: "common",
 		},
 		excludeLintJSDoc: {
 			hint: "--exclude-lint-jsdoc",
 			label:
 				"Include eslint-plugin-jsdoc to enforce good practices around JSDoc comments.",
-			uncommon: true,
+			level: "common",
 		},
 		excludeLintJson: {
 			hint: "--exclude-lint-json",
 			label: "Apply linting and sorting to *.json and *.jsonc files.",
-			uncommon: true,
+			level: "common",
 		},
 		excludeLintKnip: {
 			hint: "--exclude-lint-knip",
@@ -45,53 +50,53 @@ export const exclusionDescriptions: Record<ExclusionKey, ExclusionDescription> =
 		excludeLintMd: {
 			hint: "--exclude-lint-md",
 			label: "Apply linting to *.md files.",
-			uncommon: true,
+			level: "common",
 		},
 		excludeLintPackageJson: {
 			hint: "--exclude-lint-package-json",
 			label:
 				"Add eslint-plugin-package-json to lint for package.json correctness.",
-			uncommon: true,
+			level: "common",
 		},
 		excludeLintPackages: {
 			hint: "--exclude-lint-packages",
 			label:
 				"Add a pnpm dedupe workflow to ensure packages aren't duplicated unnecessarily.",
-			uncommon: true,
+			level: "common",
 		},
 		excludeLintPerfectionist: {
 			hint: "--exclude-lint-perfectionist",
 			label:
 				"Apply eslint-plugin-perfectionist to ensure imports, keys, and so on are in sorted order.",
-			uncommon: true,
+			level: "common",
 		},
 		excludeLintRegex: {
 			hint: "--exclude-lint-regex",
 			label:
 				"Include eslint-plugin-regex to enforce good practices around regular expressions.",
-			uncommon: true,
+			level: "common",
 		},
 		excludeLintSpelling: {
 			hint: "--exclude-lint-spelling",
 			label: "Add cspell to spell check against dictionaries of known words.",
-			uncommon: true,
+			level: "common",
 		},
 		excludeLintStrict: {
 			hint: "--exclude-lint-strict",
 			label:
 				"Include strict logical lint rules such as typescript-eslint's strict config. ",
-			uncommon: true,
+			level: "common",
 		},
 		excludeLintStylistic: {
 			hint: "--exclude-lint-stylistic",
 			label:
 				"Include stylistic lint rules such as typescript-eslint's stylistic config.",
-			uncommon: true,
+			level: "common",
 		},
 		excludeLintYml: {
 			hint: "--exclude-lint-yml",
 			label: "Apply linting and sorting to *.yaml and *.yml files.",
-			uncommon: true,
+			level: "common",
 		},
 		excludeReleases: {
 			hint: "--exclude-releases",
@@ -122,20 +127,25 @@ export function getExclusions(
 			return {
 				...Object.fromEntries(
 					exclusionKeys
-						.filter((exclusion) => exclusionDescriptions[exclusion].uncommon)
+						.filter(
+							(exclusion) =>
+								exclusionDescriptions[exclusion].level === "common",
+						)
 						.map((exclusion) => [exclusion, options[exclusion] ?? true]),
 				),
 			};
-		case "minimum":
+		case "minimal":
 			return {
 				...Object.fromEntries(
-					exclusionKeys.map((exclusion) => [
-						exclusion,
-						options[exclusion] ?? true,
-					]),
+					exclusionKeys
+						.filter(
+							(exclusion) =>
+								exclusionDescriptions[exclusion].level !== "minimal",
+						)
+						.map((exclusion) => [exclusion, options[exclusion] ?? true]),
 				),
 			};
-		// We only really care about exclusions on the common and minimum bases
+		// We only really care about exclusions on the common and minimal bases
 		default:
 			return {};
 	}
diff --git a/src/shared/options/getBase.ts b/src/shared/options/getBase.ts
index a092626..af5e8bf 100644
--- a/src/shared/options/getBase.ts
+++ b/src/shared/options/getBase.ts
@@ -30,5 +30,5 @@ export async function getBase(): Promise<OptionsBase> {
 		return "common";
 	}
 
-	return "minimum";
+	return "minimal";
 }
diff --git a/src/shared/options/optionsSchema.ts b/src/shared/options/optionsSchema.ts
index d144861..e6a097a 100644
--- a/src/shared/options/optionsSchema.ts
+++ b/src/shared/options/optionsSchema.ts
@@ -8,7 +8,7 @@ export const optionsSchemaShape = {
 		.union([
 			z.literal("common"),
 			z.literal("everything"),
-			z.literal("minimum"),
+			z.literal("minimal"),
 			z.literal("prompt"),
 		])
 		.optional(),
@@ -22,6 +22,7 @@ export const optionsSchemaShape = {
 		})
 		.optional(),
 	excludeAllContributors: z.boolean().optional(),
+	excludeBuild: z.boolean().optional(),
 	excludeCompliance: z.boolean().optional(),
 	excludeLintESLint: z.boolean().optional(),
 	excludeLintJSDoc: z.boolean().optional(),
diff --git a/src/shared/types.ts b/src/shared/types.ts
index 7950044..781fab0 100644
--- a/src/shared/types.ts
+++ b/src/shared/types.ts
@@ -25,7 +25,7 @@ export interface PartialPackageData {
 
 export type OptionsAccess = "public" | "restricted";
 
-export type OptionsBase = "common" | "everything" | "minimum" | "prompt";
+export type OptionsBase = "common" | "everything" | "minimal" | "prompt";
 
 export interface OptionsEmail {
 	github: string;
@@ -54,6 +54,7 @@ export interface Options {
 	directory: string;
 	email: OptionsEmail;
 	excludeAllContributors?: boolean;
+	excludeBuild?: boolean;
 	excludeCompliance?: boolean;
 	excludeLintESLint?: boolean;
 	excludeLintJSDoc?: boolean;
diff --git a/src/steps/writing/creation/writePackageJson.ts b/src/steps/writing/creation/writePackageJson.ts
index a04b770..fdaf2c4 100644
--- a/src/steps/writing/creation/writePackageJson.ts
+++ b/src/steps/writing/creation/writePackageJson.ts
@@ -84,7 +84,9 @@ export async function writePackageJson(options: Options) {
 		},
 		scripts: {
 			...existingPackageJson.scripts,
-			build: "tsup",
+			...(!options.excludeBuild && {
+				build: "tsup",
+			}),
 			format: "prettier .",
 			lint: "eslint . --max-warnings 0",
 			...(!options.excludeLintKnip && {


Test patch:
diff --git a/src/bin/help.test.ts b/src/bin/help.test.ts
index 851a6b1..c253a82 100644
--- a/src/bin/help.test.ts
+++ b/src/bin/help.test.ts
@@ -62,7 +62,8 @@ describe("logHelpText", () => {
 			    "
 			  --base (string): Whether to scaffold the repository with:
 			  • everything: that comes with the template (recommended)
-			  • minimum: amounts of tooling, essentially opting out of everything
+			  • common: additions to the minimal starters such as releases and tests
+			  • minimal: amounts of tooling, essentially opting out of everything
 			  • prompt: for which portions to exclude",
 			  ],
 			  [
diff --git a/src/create/createRerunSuggestion.test.ts b/src/create/createRerunSuggestion.test.ts
index b31b020..7dfbe20 100644
--- a/src/create/createRerunSuggestion.test.ts
+++ b/src/create/createRerunSuggestion.test.ts
@@ -133,15 +133,15 @@ describe("createRerunSuggestion", () => {
 		);
 	});
 
-	it("does not list all excludes when using minimum base", () => {
-		const minimum = createRerunSuggestion({
-			base: "minimum",
-			...getExclusions(options, "minimum"),
+	it("does not list all excludes when using minimal base", () => {
+		const minimal = createRerunSuggestion({
+			base: "minimal",
+			...getExclusions(options, "minimal"),
 			excludeLintKnip: undefined,
 		});
 
-		expect(minimum).toMatchInlineSnapshot(
-			`"npx create-typescript-app --base minimum"`,
+		expect(minimal).toMatchInlineSnapshot(
+			`"npx create-typescript-app --base minimal"`,
 		);
 	});
 
diff --git a/src/shared/options/augmentOptionsWithExcludes.test.ts b/src/shared/options/augmentOptionsWithExcludes.test.ts
index c34dfdd..63f2a7e 100644
--- a/src/shared/options/augmentOptionsWithExcludes.test.ts
+++ b/src/shared/options/augmentOptionsWithExcludes.test.ts
@@ -27,6 +27,7 @@ const optionsBase = {
 		npm: "npm@email.com",
 	},
 	excludeAllContributors: undefined,
+	excludeBuild: undefined,
 	excludeCompliance: undefined,
 	excludeLintESLint: undefined,
 	excludeLintJSDoc: undefined,
@@ -84,6 +85,7 @@ describe("augmentOptionsWithExcludes", () => {
 			...optionsBase,
 			base,
 			excludeAllContributors: true,
+			excludeBuild: true,
 			excludeCompliance: true,
 			excludeLintESLint: true,
 			excludeLintJSDoc: true,
@@ -141,10 +143,10 @@ describe("augmentOptionsWithExcludes", () => {
 		});
 	});
 
-	it("uses the 'minimum' base without prompting when provided manually", async () => {
+	it("uses the 'minimal' base without prompting when provided manually", async () => {
 		const options = {
 			...optionsBase,
-			base: "minimum",
+			base: "minimal",
 		} satisfies Options;
 
 		const actual = await augmentOptionsWithExcludes(options);
diff --git a/src/shared/options/getBase.test.ts b/src/shared/options/getBase.test.ts
index b131d33..9f50ce4 100644
--- a/src/shared/options/getBase.test.ts
+++ b/src/shared/options/getBase.test.ts
@@ -10,7 +10,7 @@ vi.mock("../packages.js", () => ({
 }));
 
 describe("getBase", () => {
-	it("should return minimum with minimum scripts", async () => {
+	it("should return minimal with minimal scripts", async () => {
 		mockReadPackageData.mockImplementationOnce(() =>
 			Promise.resolve({
 				scripts: {
@@ -21,7 +21,7 @@ describe("getBase", () => {
 			}),
 		);
 
-		expect(await getBase()).toBe("minimum");
+		expect(await getBase()).toBe("minimal");
 	});
 
 	it("should return common with common scripts", async () => {
diff --git a/src/shared/options/readOptions.test.ts b/src/shared/options/readOptions.test.ts
index c50b9a0..2be4a60 100644
--- a/src/shared/options/readOptions.test.ts
+++ b/src/shared/options/readOptions.test.ts
@@ -588,7 +588,7 @@ describe("readOptions", () => {
 				...emptyOptions,
 				...mockOptions,
 				access: "public",
-				base: "minimum",
+				base: "minimal",
 				description: "mock",
 				directory: "mock",
 				email: {
@@ -642,7 +642,7 @@ describe("readOptions", () => {
 				...emptyOptions,
 				...mockOptions,
 				access: "public",
-				base: "minimum",
+				base: "minimal",
 				description,
 				directory: repository,
 				email: {
@@ -700,7 +700,7 @@ describe("readOptions", () => {
 				...mockOptions,
 				access: "public",
 				auto: true,
-				base: "minimum",
+				base: "minimal",
 				description,
 				directory: repository,
 				email: {
@@ -725,7 +725,7 @@ describe("readOptions", () => {
 			      "access": "public",
 			      "author": undefined,
 			      "auto": true,
-			      "base": "minimum",
+			      "base": "minimal",
 			      "bin": undefined,
 			      "description": "Test description.",
 			      "directory": "test-repository",
diff --git a/src/steps/writing/creation/writePackageJson.test.ts b/src/steps/writing/creation/writePackageJson.test.ts
index 9bb9219..cebb826 100644
--- a/src/steps/writing/creation/writePackageJson.test.ts
+++ b/src/steps/writing/creation/writePackageJson.test.ts
@@ -123,6 +123,7 @@ describe("writePackageJson", () => {
 			...options,
 			bin: "./bin/index.js",
 			excludeAllContributors: true,
+			excludeBuild: true,
 			excludeCompliance: true,
 			excludeLintJson: true,
 			excludeLintKnip: true,
@@ -169,7 +170,6 @@ describe("writePackageJson", () => {
 			    "url": "https://github.com/test-owner/test-repository",
 			  },
 			  "scripts": {
-			    "build": "tsup",
 			    "format": "prettier .",
 			    "lint": "eslint . --max-warnings 0",
 			    "prepare": "husky",