From 85e3bbf39c6cfb6cb9f29a8e4972a73846709477 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 27 Mar 2025 09:59:08 -0400 Subject: [PATCH 1/2] fix: support package.json bin objects --- src/base.ts | 2 +- src/blocks/blockPackageJson.test.ts | 53 +++++++++++++++++++++++++++++ src/blocks/blockPackageJson.ts | 12 ++++++- src/types.ts | 2 +- 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/base.ts b/src/base.ts index 30a5f8609..be42ce9dc 100644 --- a/src/base.ts +++ b/src/base.ts @@ -45,7 +45,7 @@ export const base = createBase({ .optional() .describe("username on npm to publish packages under"), bin: z - .string() + .union([z.string(), z.record(z.string())]) .optional() .describe('value to set in `package.json`\'s `"bin"` property'), contributors: z diff --git a/src/blocks/blockPackageJson.test.ts b/src/blocks/blockPackageJson.test.ts index d2a5816ba..58bcf73f5 100644 --- a/src/blocks/blockPackageJson.test.ts +++ b/src/blocks/blockPackageJson.test.ts @@ -184,6 +184,59 @@ describe("blockPackageJson", () => { `); }); + test("with object bin", () => { + const creation = testBlock(blockPackageJson, { + options: { + ...options, + bin: "bin/index.js", + }, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + "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":"npm@email.com"},"type":"module","main":"lib/index.js","bin":"bin/index.js","files":["README.md","bin/index.js","package.json"]}", + }, + "scripts": [ + { + "commands": [ + "pnpm install --no-frozen-lockfile", + ], + "phase": 1, + }, + ], + } + `); + }); + + test("with string bin", () => { + const creation = testBlock(blockPackageJson, { + options: { + ...options, + bin: { + absolute: "bin/absolute.js", + relative: "./bin/relative.js", + }, + }, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + "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":"npm@email.com"},"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"]}", + }, + "scripts": [ + { + "commands": [ + "pnpm install --no-frozen-lockfile", + ], + "phase": 1, + }, + ], + } + `); + }); + test("offline mode", () => { const creation = testBlock(blockPackageJson, { offline: true, diff --git a/src/blocks/blockPackageJson.ts b/src/blocks/blockPackageJson.ts index c003a5fa2..0b5bad806 100644 --- a/src/blocks/blockPackageJson.ts +++ b/src/blocks/blockPackageJson.ts @@ -63,7 +63,7 @@ export const blockPackageJson = base.createBlock({ packageManager: `pnpm@${options.pnpm}`, }), files: [ - options.bin?.replace(/^\.\//, ""), + ...collectBinFiles(options.bin), ...(addons.properties.files ?? []), "package.json", "README.md", @@ -108,6 +108,16 @@ export const blockPackageJson = base.createBlock({ }, }); +function collectBinFiles(bin: Record | string | undefined) { + if (!bin) { + return []; + } + + const files = typeof bin === "object" ? Object.values(bin) : [bin]; + + return files.map((file) => file.replace(/^\.\//, "")); +} + function removeRangePrefix(version: string) { return version.replaceAll(/[\^~><=]/gu, "").split(" ")[0]; } diff --git a/src/types.ts b/src/types.ts index b30d8adf0..509c86584 100644 --- a/src/types.ts +++ b/src/types.ts @@ -12,7 +12,7 @@ export interface AllContributorsData { export interface PartialPackageData { author?: string | { email: string; name: string }; - bin?: string; + bin?: Record | string; dependencies?: Record; description?: string; devDependencies?: Record; From fc2e6a22d32021eec773bcbb702242af765d0915 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 27 Mar 2025 10:09:05 -0400 Subject: [PATCH 2/2] Handle a 'primary' bin --- src/blocks/bin/getPrimaryBin.test.ts | 17 +++++++++++++++++ src/blocks/bin/getPrimaryBin.ts | 6 ++++++ src/blocks/blockTypeScript.ts | 7 +++++-- src/blocks/blockVSCode.ts | 6 ++++-- 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 src/blocks/bin/getPrimaryBin.test.ts create mode 100644 src/blocks/bin/getPrimaryBin.ts diff --git a/src/blocks/bin/getPrimaryBin.test.ts b/src/blocks/bin/getPrimaryBin.test.ts new file mode 100644 index 000000000..a65a8b8e2 --- /dev/null +++ b/src/blocks/bin/getPrimaryBin.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, test } from "vitest"; + +import { getPrimaryBin } from "./getPrimaryBin.js"; + +const repository = "test-repository"; + +describe(getPrimaryBin, () => { + test.each([ + [undefined, undefined], + ["bin/index.js", "bin/index.js"], + [{ [repository]: "bin/index.js" }, "bin/index.js"], + [{}, undefined], + [{ other: "bin/index.js" }, undefined], + ])("%j", (bin, expected) => { + expect(getPrimaryBin(bin, repository)).toBe(expected); + }); +}); diff --git a/src/blocks/bin/getPrimaryBin.ts b/src/blocks/bin/getPrimaryBin.ts new file mode 100644 index 000000000..d1b3b7a67 --- /dev/null +++ b/src/blocks/bin/getPrimaryBin.ts @@ -0,0 +1,6 @@ +export function getPrimaryBin( + bin: Record | string | undefined, + repository: string, +) { + return typeof bin === "object" ? bin[repository] : bin; +} diff --git a/src/blocks/blockTypeScript.ts b/src/blocks/blockTypeScript.ts index 3a14fed74..52a84da64 100644 --- a/src/blocks/blockTypeScript.ts +++ b/src/blocks/blockTypeScript.ts @@ -1,5 +1,6 @@ import { base } from "../base.js"; import { getPackageDependencies } from "../data/packageData.js"; +import { getPrimaryBin } from "./bin/getPrimaryBin.js"; import { blockDevelopmentDocs } from "./blockDevelopmentDocs.js"; import { blockExampleFiles } from "./blockExampleFiles.js"; import { blockGitHubActionsCI } from "./blockGitHubActionsCI.js"; @@ -15,6 +16,8 @@ export const blockTypeScript = base.createBlock({ name: "TypeScript", }, produce({ options }) { + const primaryBin = getPrimaryBin(options.bin, options.repository); + return { addons: [ blockDevelopmentDocs({ @@ -86,12 +89,12 @@ export * from "./types.js"; }), blockVitest({ coverage: { include: ["src"] }, exclude: ["lib"] }), blockVSCode({ - debuggers: options.bin + debuggers: primaryBin ? [ { name: "Debug Program", preLaunchTask: "build", - program: options.bin, + program: primaryBin, request: "launch", skipFiles: ["/**"], type: "node", diff --git a/src/blocks/blockVSCode.ts b/src/blocks/blockVSCode.ts index e461c8339..b0a9c5e72 100644 --- a/src/blocks/blockVSCode.ts +++ b/src/blocks/blockVSCode.ts @@ -2,6 +2,7 @@ import sortKeys from "sort-keys"; import { z } from "zod"; import { base } from "../base.js"; +import { getPrimaryBin } from "./bin/getPrimaryBin.js"; import { blockDevelopmentDocs } from "./blockDevelopmentDocs.js"; export const blockVSCode = base.createBlock({ @@ -30,6 +31,7 @@ export const blockVSCode = base.createBlock({ }, produce({ addons, options }) { const { debuggers, extensions, settings, tasks } = addons; + const primaryBin = getPrimaryBin(options.bin, options.repository); return { addons: [ @@ -40,13 +42,13 @@ export const blockVSCode = base.createBlock({ ], sections: { Building: { - innerSections: options.bin + innerSections: primaryBin ? [ { contents: ` This repository includes a [VS Code launch configuration](https://code.visualstudio.com/docs/editor/debugging) for debugging. To debug a \`bin\` app, add a breakpoint to your code, then run _Debug Program_ from the VS Code Debug panel (or press F5). -VS Code will automatically run the \`build\` task in the background before running \`${options.bin}\`. +VS Code will automatically run the \`build\` task in the background before running \`${primaryBin}\`. `, heading: "Built App Debugging", },