Skip to content

Commit 2396960

Browse files
fix: support package.json bin objects (#2040)
## PR Checklist - [x] Addresses an existing open issue: fixes #2039 - [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 Updates the `PartialPackageData` type to note that `bin` could be a `Record<string, string>`, not just a `string`. `blockPackageJson` will collect its values if it is an object. Also updates other references to `options.bin` to use a new `getPrimaryBin` function for determining what the main runner is. Per https://docs.npmjs.com/cli/v11/configuring-npm/package-json#bin using the same name as the repo is equivalent to using a string. 🎁
1 parent 66f38d8 commit 2396960

8 files changed

+98
-7
lines changed

src/base.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export const base = createBase({
4545
.optional()
4646
.describe("username on npm to publish packages under"),
4747
bin: z
48-
.string()
48+
.union([z.string(), z.record(z.string())])
4949
.optional()
5050
.describe('value to set in `package.json`\'s `"bin"` property'),
5151
contributors: z

src/blocks/bin/getPrimaryBin.test.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { describe, expect, test } from "vitest";
2+
3+
import { getPrimaryBin } from "./getPrimaryBin.js";
4+
5+
const repository = "test-repository";
6+
7+
describe(getPrimaryBin, () => {
8+
test.each([
9+
[undefined, undefined],
10+
["bin/index.js", "bin/index.js"],
11+
[{ [repository]: "bin/index.js" }, "bin/index.js"],
12+
[{}, undefined],
13+
[{ other: "bin/index.js" }, undefined],
14+
])("%j", (bin, expected) => {
15+
expect(getPrimaryBin(bin, repository)).toBe(expected);
16+
});
17+
});

src/blocks/bin/getPrimaryBin.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export function getPrimaryBin(
2+
bin: Record<string, string | undefined> | string | undefined,
3+
repository: string,
4+
) {
5+
return typeof bin === "object" ? bin[repository] : bin;
6+
}

src/blocks/blockPackageJson.test.ts

+53
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,59 @@ describe("blockPackageJson", () => {
184184
`);
185185
});
186186

187+
test("with object bin", () => {
188+
const creation = testBlock(blockPackageJson, {
189+
options: {
190+
...options,
191+
bin: "bin/index.js",
192+
},
193+
});
194+
195+
expect(creation).toMatchInlineSnapshot(`
196+
{
197+
"files": {
198+
"package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"license":"MIT","author":{"email":"[email protected]"},"type":"module","main":"lib/index.js","bin":"bin/index.js","files":["README.md","bin/index.js","package.json"]}",
199+
},
200+
"scripts": [
201+
{
202+
"commands": [
203+
"pnpm install --no-frozen-lockfile",
204+
],
205+
"phase": 1,
206+
},
207+
],
208+
}
209+
`);
210+
});
211+
212+
test("with string bin", () => {
213+
const creation = testBlock(blockPackageJson, {
214+
options: {
215+
...options,
216+
bin: {
217+
absolute: "bin/absolute.js",
218+
relative: "./bin/relative.js",
219+
},
220+
},
221+
});
222+
223+
expect(creation).toMatchInlineSnapshot(`
224+
{
225+
"files": {
226+
"package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"license":"MIT","author":{"email":"[email protected]"},"type":"module","main":"lib/index.js","bin":{"absolute":"bin/absolute.js","relative":"./bin/relative.js"},"files":["README.md","bin/absolute.js","bin/relative.js","package.json"]}",
227+
},
228+
"scripts": [
229+
{
230+
"commands": [
231+
"pnpm install --no-frozen-lockfile",
232+
],
233+
"phase": 1,
234+
},
235+
],
236+
}
237+
`);
238+
});
239+
187240
test("offline mode", () => {
188241
const creation = testBlock(blockPackageJson, {
189242
offline: true,

src/blocks/blockPackageJson.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export const blockPackageJson = base.createBlock({
6363
packageManager: `pnpm@${options.pnpm}`,
6464
}),
6565
files: [
66-
options.bin?.replace(/^\.\//, ""),
66+
...collectBinFiles(options.bin),
6767
...(addons.properties.files ?? []),
6868
"package.json",
6969
"README.md",
@@ -108,6 +108,16 @@ export const blockPackageJson = base.createBlock({
108108
},
109109
});
110110

111+
function collectBinFiles(bin: Record<string, string> | string | undefined) {
112+
if (!bin) {
113+
return [];
114+
}
115+
116+
const files = typeof bin === "object" ? Object.values(bin) : [bin];
117+
118+
return files.map((file) => file.replace(/^\.\//, ""));
119+
}
120+
111121
function removeRangePrefix(version: string) {
112122
return version.replaceAll(/[\^~><=]/gu, "").split(" ")[0];
113123
}

src/blocks/blockTypeScript.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { base } from "../base.js";
22
import { getPackageDependencies } from "../data/packageData.js";
3+
import { getPrimaryBin } from "./bin/getPrimaryBin.js";
34
import { blockDevelopmentDocs } from "./blockDevelopmentDocs.js";
45
import { blockExampleFiles } from "./blockExampleFiles.js";
56
import { blockGitHubActionsCI } from "./blockGitHubActionsCI.js";
@@ -15,6 +16,8 @@ export const blockTypeScript = base.createBlock({
1516
name: "TypeScript",
1617
},
1718
produce({ options }) {
19+
const primaryBin = getPrimaryBin(options.bin, options.repository);
20+
1821
return {
1922
addons: [
2023
blockDevelopmentDocs({
@@ -86,12 +89,12 @@ export * from "./types.js";
8689
}),
8790
blockVitest({ coverage: { include: ["src"] }, exclude: ["lib"] }),
8891
blockVSCode({
89-
debuggers: options.bin
92+
debuggers: primaryBin
9093
? [
9194
{
9295
name: "Debug Program",
9396
preLaunchTask: "build",
94-
program: options.bin,
97+
program: primaryBin,
9598
request: "launch",
9699
skipFiles: ["<node_internals>/**"],
97100
type: "node",

src/blocks/blockVSCode.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import sortKeys from "sort-keys";
22
import { z } from "zod";
33

44
import { base } from "../base.js";
5+
import { getPrimaryBin } from "./bin/getPrimaryBin.js";
56
import { blockDevelopmentDocs } from "./blockDevelopmentDocs.js";
67

78
export const blockVSCode = base.createBlock({
@@ -30,6 +31,7 @@ export const blockVSCode = base.createBlock({
3031
},
3132
produce({ addons, options }) {
3233
const { debuggers, extensions, settings, tasks } = addons;
34+
const primaryBin = getPrimaryBin(options.bin, options.repository);
3335

3436
return {
3537
addons: [
@@ -40,13 +42,13 @@ export const blockVSCode = base.createBlock({
4042
],
4143
sections: {
4244
Building: {
43-
innerSections: options.bin
45+
innerSections: primaryBin
4446
? [
4547
{
4648
contents: `
4749
This repository includes a [VS Code launch configuration](https://code.visualstudio.com/docs/editor/debugging) for debugging.
4850
To debug a \`bin\` app, add a breakpoint to your code, then run _Debug Program_ from the VS Code Debug panel (or press F5).
49-
VS Code will automatically run the \`build\` task in the background before running \`${options.bin}\`.
51+
VS Code will automatically run the \`build\` task in the background before running \`${primaryBin}\`.
5052
`,
5153
heading: "Built App Debugging",
5254
},

src/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export interface AllContributorsData {
1212

1313
export interface PartialPackageData {
1414
author?: string | { email: string; name: string };
15-
bin?: string;
15+
bin?: Record<string, string> | string;
1616
dependencies?: Record<string, string>;
1717
description?: string;
1818
devDependencies?: Record<string, string>;

0 commit comments

Comments
 (0)