diff --git a/src/shared/options/getBase.test.ts b/src/shared/options/getBase.test.ts new file mode 100644 index 000000000..4e7372171 --- /dev/null +++ b/src/shared/options/getBase.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it, vi } from "vitest"; + +import { getBase } from "./getBase.js"; + +const mockReadPackageData = vi.fn(); +vi.mock("../packages.js", () => ({ + get readPackageData() { + return mockReadPackageData; + }, +})); + +describe("getBase", () => { + it("should return minimum with minimum scripts", async () => { + mockReadPackageData.mockImplementationOnce(() => + Promise.resolve({ + scripts: { + build: "build", + lint: "lint", + test: "test", + }, + }), + ); + + expect(await getBase()).toBe("minimum"); + }); + it("should return common with common scripts", async () => { + mockReadPackageData.mockImplementationOnce(() => + Promise.resolve({ + scripts: { + build: "build", + lint: "lint", + "lint:knip": "knip", + test: "test", + }, + }), + ); + + expect(await getBase()).toBe("common"); + }); + it("should return everything with everything scripts", async () => { + mockReadPackageData.mockImplementationOnce(() => + Promise.resolve({ + scripts: { + build: "build", + lint: "lint", + "lint:knip": "knip", + "lint:md": "md", + "lint:package-json": "package-json", + "lint:packages": "packages", + test: "test", + }, + }), + ); + + expect(await getBase()).toBe("everything"); + }); +}); diff --git a/src/shared/options/getBase.ts b/src/shared/options/getBase.ts new file mode 100644 index 000000000..6e7d3bac4 --- /dev/null +++ b/src/shared/options/getBase.ts @@ -0,0 +1,34 @@ +import { readPackageData } from "../packages.js"; +import { OptionsBase } from "../types.js"; + +const commonScripts = new Set(["lint:knip", "should-semantic-release", "test"]); + +const everythingScripts = new Set([ + "lint:md", + "lint:package-json", + "lint:packages", + "lint:spelling", +]); +export async function getBase(): Promise { + const scripts = Object.keys((await readPackageData()).scripts ?? {}); + + if ( + scripts.reduce( + (acc, curr) => (everythingScripts.has(curr) ? acc + 1 : acc), + 0, + ) >= 3 + ) { + return "everything"; + } + + if ( + scripts.reduce( + (acc, curr) => (commonScripts.has(curr) ? acc + 1 : acc), + 0, + ) >= 2 + ) { + return "common"; + } + + return "minimum"; +} diff --git a/src/shared/options/readOptions.test.ts b/src/shared/options/readOptions.test.ts index 59cf2b150..97cbaddce 100644 --- a/src/shared/options/readOptions.test.ts +++ b/src/shared/options/readOptions.test.ts @@ -111,6 +111,13 @@ vi.mock("./createOptionDefaults/index.js", () => ({ }, })); +const mockReadPackageData = vi.fn(); +vi.mock("../packages.js", () => ({ + get readPackageData() { + return mockReadPackageData; + }, +})); + describe("readOptions", () => { it("returns a cancellation when an arg is invalid", async () => { const validationResult = z @@ -524,4 +531,40 @@ describe("readOptions", () => { }, }); }); + + it("infers base from package scripts during migration", async () => { + mockReadPackageData.mockImplementationOnce(() => + Promise.resolve({ + scripts: { + build: "build", + lint: "lint", + test: "test", + }, + }), + ); + expect(await readOptions(["--offline"], "migrate")).toStrictEqual({ + cancelled: false, + github: mockOptions.github, + options: { + ...emptyOptions, + ...mockOptions, + access: "public", + base: "minimum", + description: "mock", + directory: "mock", + email: { + github: "mock", + npm: "mock", + }, + guide: undefined, + logo: undefined, + mode: "migrate", + offline: true, + owner: "mock", + skipAllContributorsApi: true, + skipGitHubApi: true, + title: "mock", + }, + }); + }); }); diff --git a/src/shared/options/readOptions.ts b/src/shared/options/readOptions.ts index f9a62ed4f..eed8d7947 100644 --- a/src/shared/options/readOptions.ts +++ b/src/shared/options/readOptions.ts @@ -10,6 +10,7 @@ import { augmentOptionsWithExcludes } from "./augmentOptionsWithExcludes.js"; import { createOptionDefaults } from "./createOptionDefaults/index.js"; import { detectEmailRedundancy } from "./detectEmailRedundancy.js"; import { ensureRepositoryExists } from "./ensureRepositoryExists.js"; +import { getBase } from "./getBase.js"; import { GitHub, getGitHub } from "./getGitHub.js"; import { getPrefillOrPromptedOption } from "./getPrefillOrPromptedOption.js"; import { optionsSchema } from "./optionsSchema.js"; @@ -44,6 +45,10 @@ export async function readOptions( tokens: true, }); + if (mode === "migrate" && !values.base) { + values.base = await getBase(); + } + const mappedOptions = { access: values.access, author: values.author, diff --git a/src/shared/types.ts b/src/shared/types.ts index 499a4532f..b54dc69aa 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -19,6 +19,7 @@ export interface PartialPackageData { email?: string; name?: string; repository?: { type: string; url: string } | string; + scripts?: Record; } export type OptionsAccess = "public" | "restricted";