diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 346cc5e35..07fbfb32d 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -37,7 +37,7 @@ module.exports = { files: ["**/*.ts"], parser: "@typescript-eslint/parser", rules: { - // These off-by-default rules work well for this repo and we like them on. + // These off-/differently-configured-by-default rules work well for this repo and we like them on. "jsdoc/informative-docs": "error", "logical-assignment-operators": [ "error", @@ -53,6 +53,15 @@ module.exports = { "jsdoc/require-returns": "off", }, }, + { + files: "**/*.md/*.ts", + rules: { + "n/no-missing-import": [ + "error", + { allowModules: ["create-typescript-app"] }, + ], + }, + }, { excludedFiles: ["**/*.md/*.ts"], extends: [ diff --git a/script/create-test-e2e.js b/script/create-test-e2e.js index bdff17148..552ab33c6 100644 --- a/script/create-test-e2e.js +++ b/script/create-test-e2e.js @@ -4,7 +4,7 @@ import { strict as assert } from "node:assert"; const author = "Test Author"; const description = "Test description."; const email = "test@email.com"; -const repository = "test-repository"; +const repository = "create-typescript-app"; const owner = "TestOwner"; const title = "Test Title"; diff --git a/src/bin/index.ts b/src/bin/index.ts index 7380671ba..b8778c319 100644 --- a/src/bin/index.ts +++ b/src/bin/index.ts @@ -57,7 +57,7 @@ export async function bin(args: string[]) { prompts.log.info( [ chalk.italic(`Tip: to run again with the same input values, use:`), - chalk.blue(createRerunSuggestion(mode, options)), + chalk.blue(createRerunSuggestion(options)), ].join(" "), ); diff --git a/src/create/createRerunSuggestion.test.ts b/src/create/createRerunSuggestion.test.ts index 4ae61e24d..437d7e994 100644 --- a/src/create/createRerunSuggestion.test.ts +++ b/src/create/createRerunSuggestion.test.ts @@ -29,6 +29,7 @@ const options = { excludeTests: undefined, funding: undefined, logo: undefined, + mode: "create", owner: "TestOwner", repository: "test-repository", skipGitHubApi: true, @@ -41,37 +42,39 @@ const options = { describe("createRerunSuggestion", () => { it("includes key-value pairs with mixed truthy and falsy values", () => { - const actual = createRerunSuggestion("initialize", options); + const actual = createRerunSuggestion(options); expect(actual).toMatchInlineSnapshot( - '"npx create-typescript-app --mode initialize --base everything --access public --author TestAuthor --create-repository true --description \\"Test description.\\" --email-github github@email.com --email-npm npm@email.com --exclude-compliance true --exclude-contributors true --exclude-lint-jsdoc true --exclude-lint-json true --exclude-lint-knip true --exclude-lint-package-json true --exclude-lint-perfectionist true --owner TestOwner --repository test-repository --skip-github-api true --skip-install true --skip-removal true --title \\"Test Title\\""', + '"npx create-typescript-app --mode create --base everything --access public --author TestAuthor --create-repository true --description \\"Test description.\\" --email-github github@email.com --email-npm npm@email.com --exclude-compliance true --exclude-contributors true --exclude-lint-jsdoc true --exclude-lint-json true --exclude-lint-knip true --exclude-lint-package-json true --exclude-lint-perfectionist true --mode create --owner TestOwner --repository test-repository --skip-github-api true --skip-install true --skip-removal true --title \\"Test Title\\""', ); }); it("includes stringified logo when it exists", () => { - const actual = createRerunSuggestion("initialize", { + const actual = createRerunSuggestion({ ...options, logo: { alt: "Test alt.", src: "test/src.png", }, + mode: "initialize", }); expect(actual).toMatchInlineSnapshot( - '"npx create-typescript-app --mode initialize --base everything --access public --author TestAuthor --create-repository true --description \\"Test description.\\" --email-github github@email.com --email-npm npm@email.com --exclude-compliance true --exclude-contributors true --exclude-lint-jsdoc true --exclude-lint-json true --exclude-lint-knip true --exclude-lint-package-json true --exclude-lint-perfectionist true --logo test/src.png --logo-alt \\"Test alt.\\" --owner TestOwner --repository test-repository --skip-github-api true --skip-install true --skip-removal true --title \\"Test Title\\""', + '"npx create-typescript-app --mode initialize --base everything --access public --author TestAuthor --create-repository true --description \\"Test description.\\" --email-github github@email.com --email-npm npm@email.com --exclude-compliance true --exclude-contributors true --exclude-lint-jsdoc true --exclude-lint-json true --exclude-lint-knip true --exclude-lint-package-json true --exclude-lint-perfectionist true --logo test/src.png --logo-alt \\"Test alt.\\" --mode initialize --owner TestOwner --repository test-repository --skip-github-api true --skip-install true --skip-removal true --title \\"Test Title\\""', ); }); it("includes exclusions when they exist", () => { - const actual = createRerunSuggestion("initialize", { + const actual = createRerunSuggestion({ ...options, excludeCompliance: true, excludeLintMd: true, excludeLintSpelling: true, + mode: "initialize", }); expect(actual).toMatchInlineSnapshot( - '"npx create-typescript-app --mode initialize --base everything --access public --author TestAuthor --create-repository true --description \\"Test description.\\" --email-github github@email.com --email-npm npm@email.com --exclude-compliance true --exclude-contributors true --exclude-lint-jsdoc true --exclude-lint-json true --exclude-lint-knip true --exclude-lint-md true --exclude-lint-package-json true --exclude-lint-perfectionist true --exclude-lint-spelling true --owner TestOwner --repository test-repository --skip-github-api true --skip-install true --skip-removal true --title \\"Test Title\\""', + '"npx create-typescript-app --mode initialize --base everything --access public --author TestAuthor --create-repository true --description \\"Test description.\\" --email-github github@email.com --email-npm npm@email.com --exclude-compliance true --exclude-contributors true --exclude-lint-jsdoc true --exclude-lint-json true --exclude-lint-knip true --exclude-lint-md true --exclude-lint-package-json true --exclude-lint-perfectionist true --exclude-lint-spelling true --mode initialize --owner TestOwner --repository test-repository --skip-github-api true --skip-install true --skip-removal true --title \\"Test Title\\""', ); }); }); diff --git a/src/create/createRerunSuggestion.ts b/src/create/createRerunSuggestion.ts index 6923462ba..bdfc0a317 100644 --- a/src/create/createRerunSuggestion.ts +++ b/src/create/createRerunSuggestion.ts @@ -1,4 +1,3 @@ -import { Mode } from "../bin/mode.js"; import { allArgOptions } from "../shared/options/args.js"; import { Options } from "../shared/types.js"; @@ -8,10 +7,7 @@ function getFirstMatchingArg(key: string) { ); } -export function createRerunSuggestion( - mode: Mode, - options: Partial, -): string { +export function createRerunSuggestion(options: Partial): string { const optionsNormalized = { ...options, ...(options.email @@ -42,5 +38,5 @@ export function createRerunSuggestion( }) .join(" "); - return `npx create-typescript-app --mode ${mode} ${args}`; + return `npx create-typescript-app --mode ${options.mode} ${args}`; } diff --git a/src/create/createWithOptions.ts b/src/create/createWithOptions.ts index fcaceecd2..ca9ba7b78 100644 --- a/src/create/createWithOptions.ts +++ b/src/create/createWithOptions.ts @@ -16,7 +16,7 @@ export async function createWithOptions({ github, options }: GitHubAndOptions) { [ "Writing structure", async () => { - await writeStructure(options, "create"); + await writeStructure(options); }, ], [ diff --git a/src/create/index.ts b/src/create/index.ts index 4b77341e4..61535d107 100644 --- a/src/create/index.ts +++ b/src/create/index.ts @@ -11,7 +11,7 @@ import { createRerunSuggestion } from "./createRerunSuggestion.js"; import { createWithOptions } from "./createWithOptions.js"; export async function create(args: string[]): Promise { - const inputs = await readOptions(args); + const inputs = await readOptions(args, "create"); if (inputs.cancelled) { return { code: StatusCodes.Cancelled, @@ -49,8 +49,9 @@ export async function create(args: string[]): Promise { "Consider creating a GitHub repository from the new directory:", lines: [ `cd ${inputs.options.repository}`, - createRerunSuggestion("initialize", { + createRerunSuggestion({ ...inputs.options, + mode: "initialize", skipGitHubApi: false, skipInstall: false, }), diff --git a/src/initialize/index.ts b/src/initialize/index.ts index d9a9d08f5..a6b7c2c8b 100644 --- a/src/initialize/index.ts +++ b/src/initialize/index.ts @@ -7,7 +7,7 @@ import { runOrRestore } from "../shared/runOrRestore.js"; import { initializeWithOptions } from "./initializeWithOptions.js"; export const initialize: ModeRunner = async (args) => { - const inputs = await readOptions(args); + const inputs = await readOptions(args, "initialize"); if (inputs.cancelled) { return { code: StatusCodes.Cancelled, diff --git a/src/initialize/initializeWithOptions.ts b/src/initialize/initializeWithOptions.ts index c47fd832e..1a8a63b6b 100644 --- a/src/initialize/initializeWithOptions.ts +++ b/src/initialize/initializeWithOptions.ts @@ -19,7 +19,7 @@ export async function initializeWithOptions({ [ "Updating local files", async () => { - await updateLocalFiles(options, "initialize"); + await updateLocalFiles(options); }, ], ["Updating README.md", updateReadme], diff --git a/src/migrate/index.ts b/src/migrate/index.ts index f7698a0ef..be6cbf6e0 100644 --- a/src/migrate/index.ts +++ b/src/migrate/index.ts @@ -7,7 +7,7 @@ import { runOrRestore } from "../shared/runOrRestore.js"; import { migrateWithOptions } from "./migrateWithOptions.js"; export const migrate: ModeRunner = async (args) => { - const inputs = await readOptions(args); + const inputs = await readOptions(args, "migrate"); if (inputs.cancelled) { return { code: StatusCodes.Cancelled, diff --git a/src/migrate/migrateWithOptions.ts b/src/migrate/migrateWithOptions.ts index 0fc48e89c..b862a9459 100644 --- a/src/migrate/migrateWithOptions.ts +++ b/src/migrate/migrateWithOptions.ts @@ -19,7 +19,7 @@ export async function migrateWithOptions({ [ "Writing structure", async () => { - await writeStructure(options, "migrate"); + await writeStructure(options); }, ], [ @@ -31,7 +31,7 @@ export async function migrateWithOptions({ [ "Updating local files", async () => { - await updateLocalFiles(options, "migrate"); + await updateLocalFiles(options); }, ], [ diff --git a/src/shared/options/args.ts b/src/shared/options/args.ts index 3a9d05de4..157994685 100644 --- a/src/shared/options/args.ts +++ b/src/shared/options/args.ts @@ -29,6 +29,7 @@ export const allArgOptions = { funding: { type: "string" }, logo: { type: "string" }, "logo-alt": { type: "string" }, + mode: { type: "string" }, owner: { type: "string" }, repository: { type: "string" }, "skip-github-api": { type: "boolean" }, diff --git a/src/shared/options/augmentOptionsWithExcludes.test.ts b/src/shared/options/augmentOptionsWithExcludes.test.ts index fcad32346..7855426b0 100644 --- a/src/shared/options/augmentOptionsWithExcludes.test.ts +++ b/src/shared/options/augmentOptionsWithExcludes.test.ts @@ -34,6 +34,7 @@ const optionsBase = { excludeTests: undefined, funding: undefined, logo: undefined, + mode: "create", owner: "", repository: "", skipGitHubApi: false, diff --git a/src/shared/options/optionsSchema.ts b/src/shared/options/optionsSchema.ts index 02807b75e..380419061 100644 --- a/src/shared/options/optionsSchema.ts +++ b/src/shared/options/optionsSchema.ts @@ -41,6 +41,9 @@ export const optionsSchemaShape = { funding: z.string().optional(), logo: z.string().optional(), logoAlt: z.string().optional(), + mode: z + .union([z.literal("create"), z.literal("initialize"), z.literal("migrate")]) + .optional(), owner: z.string().optional(), repository: z.string().optional(), skipGitHubApi: z.boolean().optional(), diff --git a/src/shared/options/readOptions.test.ts b/src/shared/options/readOptions.test.ts index b00476ca7..e1d3760b2 100644 --- a/src/shared/options/readOptions.test.ts +++ b/src/shared/options/readOptions.test.ts @@ -112,7 +112,7 @@ describe("readOptions", () => { .object({ base: optionsSchemaShape.base }) .safeParse({ base: "b" }); - const actual = await readOptions(["--base", "b"]); + const actual = await readOptions(["--base", "b"], "create"); expect(actual).toStrictEqual({ cancelled: true, @@ -126,7 +126,7 @@ describe("readOptions", () => { mockDetectEmailRedundancy.mockReturnValue(error); mockGetPrefillOrPromptedOption.mockImplementation(() => undefined); - expect(await readOptions([])).toStrictEqual({ + expect(await readOptions([], "create")).toStrictEqual({ cancelled: true, error, options: { @@ -139,7 +139,7 @@ describe("readOptions", () => { mockDetectEmailRedundancy.mockReturnValue(false); mockGetPrefillOrPromptedOption.mockImplementation(() => undefined); - expect(await readOptions([])).toStrictEqual({ + expect(await readOptions([], "create")).toStrictEqual({ cancelled: true, options: { ...emptyOptions, @@ -153,7 +153,7 @@ describe("readOptions", () => { .mockImplementationOnce(() => "MockOwner") .mockImplementation(() => undefined); - expect(await readOptions([])).toStrictEqual({ + expect(await readOptions([], "create")).toStrictEqual({ cancelled: true, options: { ...emptyOptions, @@ -170,7 +170,7 @@ describe("readOptions", () => { .mockImplementation(() => undefined); mockEnsureRepositoryExists.mockResolvedValue({}); - expect(await readOptions([])).toStrictEqual({ + expect(await readOptions([], "create")).toStrictEqual({ cancelled: true, options: { ...emptyOptions, @@ -191,7 +191,7 @@ describe("readOptions", () => { repository: mockOptions.repository, }); - expect(await readOptions([])).toStrictEqual({ + expect(await readOptions([], "create")).toStrictEqual({ cancelled: true, options: { ...emptyOptions, @@ -213,7 +213,7 @@ describe("readOptions", () => { repository: mockOptions.repository, }); - expect(await readOptions([])).toStrictEqual({ + expect(await readOptions([], "create")).toStrictEqual({ cancelled: true, options: { ...emptyOptions, @@ -236,7 +236,7 @@ describe("readOptions", () => { repository: mockOptions.repository, }); - expect(await readOptions(["--logo", "logo.svg"])).toStrictEqual({ + expect(await readOptions(["--logo", "logo.svg"], "create")).toStrictEqual({ cancelled: true, options: { ...emptyOptions, @@ -260,7 +260,7 @@ describe("readOptions", () => { repository: mockOptions.repository, }); - expect(await readOptions([])).toStrictEqual({ + expect(await readOptions([], "create")).toStrictEqual({ cancelled: true, options: { ...emptyOptions, @@ -286,7 +286,7 @@ describe("readOptions", () => { }); mockAugmentOptionsWithExcludes.mockResolvedValue(undefined); - expect(await readOptions([])).toStrictEqual({ + expect(await readOptions([], "create")).toStrictEqual({ cancelled: true, options: { ...emptyOptions, @@ -305,7 +305,9 @@ describe("readOptions", () => { }); mockGetPrefillOrPromptedOption.mockImplementation(() => "mock"); - expect(await readOptions(["--base", mockOptions.base])).toStrictEqual({ + expect( + await readOptions(["--base", mockOptions.base], "create"), + ).toStrictEqual({ cancelled: false, github: mockOptions.github, options: { diff --git a/src/shared/options/readOptions.ts b/src/shared/options/readOptions.ts index 877d77e81..eb01a9db8 100644 --- a/src/shared/options/readOptions.ts +++ b/src/shared/options/readOptions.ts @@ -2,6 +2,7 @@ import { parseArgs } from "node:util"; import { titleCase } from "title-case"; import { z } from "zod"; +import { Mode } from "../../bin/mode.js"; import { withSpinner } from "../cli/spinners.js"; import { Options, OptionsLogo } from "../types.js"; import { allArgOptions } from "./args.js"; @@ -30,7 +31,10 @@ export interface OptionsParseSuccess extends GitHubAndOptions { export type OptionsParseResult = OptionsParseCancelled | OptionsParseSuccess; -export async function readOptions(args: string[]): Promise { +export async function readOptions( + args: string[], + mode: Mode, +): Promise { const defaults = readOptionDefaults(); const { values } = parseArgs({ args, @@ -193,6 +197,7 @@ export async function readOptions(args: string[]): Promise { email: typeof email === "string" ? { github: email, npm: email } : email, funding: options.funding ?? (await defaults.funding()), logo, + mode, owner: options.owner, repository, title: options.title, diff --git a/src/shared/types.ts b/src/shared/types.ts index dc550aca3..a85730e4e 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -1,3 +1,5 @@ +import { Mode } from "../bin/mode.js"; + export interface AllContributorContributor { contributions: string[]; login: string; @@ -59,6 +61,7 @@ export interface Options { excludeTests?: boolean; funding?: string; logo: OptionsLogo | undefined; + mode: Mode; owner: string; repository: string; skipGitHubApi?: boolean; diff --git a/src/steps/finalizeDependencies.test.ts b/src/steps/finalizeDependencies.test.ts index bb1aebc7d..7b2714c6b 100644 --- a/src/steps/finalizeDependencies.test.ts +++ b/src/steps/finalizeDependencies.test.ts @@ -41,6 +41,7 @@ const options = { excludeTests: undefined, funding: undefined, logo: undefined, + mode: "create", owner: "StubOwner", repository: "stub-repository", skipGitHubApi: false, diff --git a/src/steps/updateLocalFiles.test.ts b/src/steps/updateLocalFiles.test.ts index 453599734..86e28e949 100644 --- a/src/steps/updateLocalFiles.test.ts +++ b/src/steps/updateLocalFiles.test.ts @@ -44,6 +44,7 @@ const options = { excludeTests: undefined, funding: undefined, logo: undefined, + mode: "create", owner: "StubOwner", repository: "stub-repository", skipGitHubApi: false, @@ -62,7 +63,7 @@ describe("updateLocalFiles", () => { mockReplaceInFile.mockRejectedValue(error); await expect(async () => { - await updateLocalFiles(options, "initialize"); + await updateLocalFiles({ ...options, mode: "initialize" }); }).rejects.toThrowErrorMatchingInlineSnapshot( '"Failed to replace /Create TypeScript App/g with Stub Title in ./.github/**/*,./*.*"', ); @@ -72,7 +73,7 @@ describe("updateLocalFiles", () => { mockReadFileSafeAsJson.mockResolvedValue(null); mockReplaceInFile.mockResolvedValue([]); - await updateLocalFiles(options, "initialize"); + await updateLocalFiles({ ...options, mode: "initialize" }); expect(mockReplaceInFile.mock.calls).toMatchInlineSnapshot(` [ @@ -214,7 +215,7 @@ describe("updateLocalFiles", () => { mockReadFileSafeAsJson.mockResolvedValue({}); mockReplaceInFile.mockResolvedValue([]); - await updateLocalFiles(options, "initialize"); + await updateLocalFiles({ ...options, mode: "initialize" }); expect(mockReplaceInFile.mock.calls).toMatchInlineSnapshot(` [ @@ -356,7 +357,7 @@ describe("updateLocalFiles", () => { mockReadFileSafeAsJson.mockResolvedValue({}); mockReplaceInFile.mockResolvedValue([]); - await updateLocalFiles(options, "initialize"); + await updateLocalFiles({ ...options, mode: "initialize" }); expect(mockReplaceInFile).not.toHaveBeenCalledWith({ files: ["./.github/**/*", "./*.*"], @@ -372,7 +373,7 @@ describe("updateLocalFiles", () => { }); mockReplaceInFile.mockResolvedValue([]); - await updateLocalFiles(options, "initialize"); + await updateLocalFiles({ ...options, mode: "initialize" }); expect(mockReplaceInFile).toHaveBeenCalledWith({ files: ["./.github/**/*", "./*.*"], @@ -387,7 +388,7 @@ describe("updateLocalFiles", () => { }); mockReplaceInFile.mockResolvedValue([]); - await updateLocalFiles(options, "initialize"); + await updateLocalFiles({ ...options, mode: "initialize" }); expect(mockReplaceInFile).toHaveBeenCalledWith({ files: "./package.json", @@ -402,7 +403,7 @@ describe("updateLocalFiles", () => { }); mockReplaceInFile.mockResolvedValue([]); - await updateLocalFiles(options, "migrate"); + await updateLocalFiles({ ...options, mode: "migrate" }); expect(mockReplaceInFile).not.toHaveBeenCalledWith({ files: "./package.json", @@ -417,7 +418,7 @@ describe("updateLocalFiles", () => { }); mockReplaceInFile.mockResolvedValue([]); - await updateLocalFiles(options, "initialize"); + await updateLocalFiles({ ...options, mode: "initialize" }); expect(mockReplaceInFile).toHaveBeenCalledWith({ files: "./package.json", @@ -432,7 +433,7 @@ describe("updateLocalFiles", () => { }); mockReplaceInFile.mockResolvedValue([]); - await updateLocalFiles(options, "migrate"); + await updateLocalFiles({ ...options, mode: "migrate" }); expect(mockReplaceInFile).not.toHaveBeenCalledWith({ files: "./package.json", diff --git a/src/steps/updateLocalFiles.ts b/src/steps/updateLocalFiles.ts index 741aa7bfd..dc15fcc3b 100644 --- a/src/steps/updateLocalFiles.ts +++ b/src/steps/updateLocalFiles.ts @@ -1,6 +1,5 @@ import replaceInFile from "replace-in-file"; -import { Mode } from "../bin/mode.js"; import { readFileSafeAsJson } from "../shared/readFileSafeAsJson.js"; import { Options } from "../shared/types.js"; @@ -9,7 +8,7 @@ interface ExistingPackageData { version?: string; } -export async function updateLocalFiles(options: Options, mode: Mode) { +export async function updateLocalFiles(options: Options) { const existingPackage = ((await readFileSafeAsJson("./package.json")) ?? {}) as ExistingPackageData; @@ -19,7 +18,9 @@ export async function updateLocalFiles(options: Options, mode: Mode) { [/create-typescript-app/g, options.repository], [/\/\*\n.+\*\/\n\n/gs, ``, ".eslintrc.cjs"], [/"author": ".+"/g, `"author": "${options.author}"`, "./package.json"], - ...(mode === "migrate" ? [] : [[/"bin": ".+\n/g, ``, "./package.json"]]), + ...(options.mode === "migrate" + ? [] + : [[/"bin": ".+\n/g, ``, "./package.json"]]), [/"test:create": ".+\n/g, ``, "./package.json"], [/"test:initialize": ".*/g, ``, "./package.json"], [/"initialize": ".*/g, ``, "./package.json"], @@ -46,7 +47,7 @@ export async function updateLocalFiles(options: Options, mode: Mode) { replacements.push([existingPackage.description, options.description]); } - if (mode === "initialize" && existingPackage.version) { + if (options.mode === "initialize" && existingPackage.version) { replacements.push([ new RegExp(`"version": "${existingPackage.version}"`, "g"), `"version": "0.0.0"`, diff --git a/src/steps/writeReadme/findIntroSectionClose.test.ts b/src/steps/writeReadme/findIntroSectionClose.test.ts index 5dae9e73a..60da73e68 100644 --- a/src/steps/writeReadme/findIntroSectionClose.test.ts +++ b/src/steps/writeReadme/findIntroSectionClose.test.ts @@ -40,7 +40,7 @@ describe("findIntroSectionClose", () => { Next line. `, - 13, + 14, ], ])("%s", (contents, expected) => { expect(findIntroSectionClose(contents)).toEqual(expected); diff --git a/src/steps/writeReadme/findIntroSectionClose.ts b/src/steps/writeReadme/findIntroSectionClose.ts index 913a5843e..e910b5f3b 100644 --- a/src/steps/writeReadme/findIntroSectionClose.ts +++ b/src/steps/writeReadme/findIntroSectionClose.ts @@ -18,5 +18,5 @@ export function findIntroSectionClose(contents: string) { } // Lastly, go for the second line altogether - return contents.indexOf("\n", 2); + return contents.indexOf("\n", 1) + 1; } diff --git a/src/steps/writeReadme/generateTopContent.test.ts b/src/steps/writeReadme/generateTopContent.test.ts index e654015aa..030a4bb9d 100644 --- a/src/steps/writeReadme/generateTopContent.test.ts +++ b/src/steps/writeReadme/generateTopContent.test.ts @@ -28,6 +28,7 @@ const optionsBase = { excludeTests: undefined, funding: undefined, logo: undefined, + mode: "create", owner: "", repository: "", skipGitHubApi: false, @@ -64,7 +65,18 @@ describe("findExistingBadges", () => { \\"Style: \\"TypeScript: -

" +

+ + ## Usage + + \`\`\`shell + npm i + \`\`\` + \`\`\`ts + import { greet } from \\"\\"; + + greet(\\"Hello, world! πŸ’–\\"); + \`\`\`" `); }); @@ -97,11 +109,22 @@ describe("findExistingBadges", () => { \\"Style: \\"TypeScript: -

" +

+ + ## Usage + + \`\`\`shell + npm i + \`\`\` + \`\`\`ts + import { greet } from \\"\\"; + + greet(\\"Hello, world! πŸ’–\\"); + \`\`\`" `); }); - it("push existing badgesΒ to the end when there is an existing unknown badge", () => { + it("push existing badges to the end when there is an existing unknown badge", () => { expect( generateTopContent(optionsBase, [ `Unknown Badge`, @@ -131,7 +154,48 @@ describe("findExistingBadges", () => { \\"Style: \\"TypeScript: \\"Unknown -

" +

+ + ## Usage + + \`\`\`shell + npm i + \`\`\` + \`\`\`ts + import { greet } from \\"\\"; + + greet(\\"Hello, world! πŸ’–\\"); + \`\`\`" `); }); + + it("does not include a greet section when the mode is migrate", () => { + expect(generateTopContent({ ...optionsBase, mode: "migrate" }, [])) + .toMatchInlineSnapshot(` + "

+ +

+ +

+ + + + \\"All + + + + + \\"Codecov + + + \\"Contributor + + + \\"License: + + \\"Style: + \\"TypeScript: +

" + `); + }); }); diff --git a/src/steps/writeReadme/generateTopContent.ts b/src/steps/writeReadme/generateTopContent.ts index c7f631708..7fdb5f90f 100644 --- a/src/steps/writeReadme/generateTopContent.ts +++ b/src/steps/writeReadme/generateTopContent.ts @@ -79,5 +79,20 @@ export function generateTopContent(options: Options, existingBadges: string[]) { ${[...badges, ...remainingExistingBadges] .map((badge) => `\t${badge}`) .join("\n")} -

`; +

${ + options.mode === "migrate" + ? "" + : ` + +## Usage + +\`\`\`shell +npm i ${options.repository} +\`\`\` +\`\`\`ts +import { greet } from "${options.repository}"; + +greet("Hello, world! πŸ’–"); +\`\`\`` + }`; } diff --git a/src/steps/writeReadme/index.test.ts b/src/steps/writeReadme/index.test.ts index 4c3646bf3..32aa3528e 100644 --- a/src/steps/writeReadme/index.test.ts +++ b/src/steps/writeReadme/index.test.ts @@ -48,6 +48,7 @@ const options = { excludeTests: undefined, funding: "TestFunding", logo: undefined, + mode: "create", owner: "TestOwner", repository: "test-repository", skipGitHubApi: false, @@ -96,6 +97,17 @@ describe("writeReadme", () => { \\"TypeScript:

+ ## Usage + + \`\`\`shell + npm i test-repository + \`\`\` + \`\`\`ts + import { greet } from \\"test-repository\\"; + + greet(\\"Hello, world! πŸ’–\\"); + \`\`\` + ## Contributors @@ -120,7 +132,7 @@ describe("writeReadme", () => { }); it("adds sections when the README.md already exists and is sparse", async () => { - mockReadFileSafe.mockResolvedValueOnce(`# ${options.title}`); + mockReadFileSafe.mockResolvedValueOnce(`# ${options.title}\n`); await writeReadme(options); @@ -154,7 +166,18 @@ describe("writeReadme", () => { \\"Style: \\"TypeScript: -

e +

+ + ## Usage + + \`\`\`shell + npm i test-repository + \`\`\` + \`\`\`ts + import { greet } from \\"test-repository\\"; + + greet(\\"Hello, world! πŸ’–\\"); + \`\`\` ## Contributors @@ -180,7 +203,7 @@ describe("writeReadme", () => { }); it("adds all-contributors content when directed to and the indicator does not yet exist", async () => { - mockReadFileSafe.mockResolvedValueOnce(`# ${options.title}`); + mockReadFileSafe.mockResolvedValueOnce(`# ${options.title}\n`); await writeReadme({ ...options, @@ -217,7 +240,18 @@ describe("writeReadme", () => { \\"Style: \\"TypeScript: -

e +

+ + ## Usage + + \`\`\`shell + npm i test-repository + \`\`\` + \`\`\`ts + import { greet } from \\"test-repository\\"; + + greet(\\"Hello, world! πŸ’–\\"); + \`\`\` ## Contributors @@ -326,6 +360,17 @@ describe("writeReadme", () => { \\"Contributor

+ ## Usage + + \`\`\`shell + npm i test-repository + \`\`\` + \`\`\`ts + import { greet } from \\"test-repository\\"; + + greet(\\"Hello, world! πŸ’–\\"); + \`\`\` + ## Contributors diff --git a/src/steps/writing/creation/createESLintRC.test.ts b/src/steps/writing/creation/createESLintRC.test.ts index 7c4d7e52d..16c9bd716 100644 --- a/src/steps/writing/creation/createESLintRC.test.ts +++ b/src/steps/writing/creation/createESLintRC.test.ts @@ -38,6 +38,7 @@ function fakeOptions(getExcludeValue: (exclusionName: string) => boolean) { ].map((key) => [key, getExcludeValue(key)]), ), logo: undefined, + mode: "create", owner: "TestOwner", repository: "test-repository", skipGitHubApi: true, @@ -73,6 +74,15 @@ describe("createESLintRC", () => { \\"operator-assignment\\": \\"error\\", }, }, + { + files: \\"**/*.md/*.ts\\", + rules: { + \\"n/no-missing-import\\": [ + \\"error\\", + { allowModules: [\\"create-typescript-app\\"] }, + ], + }, + }, { extends: [\\"plugin:@typescript-eslint/recommended-type-checked\\"], files: [\\"**/*.ts\\"], @@ -148,6 +158,15 @@ describe("createESLintRC", () => { \\"jsdoc/require-returns\\": \\"off\\", }, }, + { + files: \\"**/*.md/*.ts\\", + rules: { + \\"n/no-missing-import\\": [ + \\"error\\", + { allowModules: [\\"create-typescript-app\\"] }, + ], + }, + }, { excludedFiles: [\\"**/*.md/*.ts\\"], extends: [ diff --git a/src/steps/writing/creation/createESLintRC.ts b/src/steps/writing/creation/createESLintRC.ts index 9a5948482..33197e7d5 100644 --- a/src/steps/writing/creation/createESLintRC.ts +++ b/src/steps/writing/creation/createESLintRC.ts @@ -75,6 +75,15 @@ module.exports = { } }, }, + { + files: "**/*.md/*.ts", + rules: { + "n/no-missing-import": [ + "error", + { allowModules: ["create-typescript-app"] }, + ], + }, + }, { ${ options.excludeLintMd diff --git a/src/steps/writing/creation/dotGitHub/createDevelopment.test.ts b/src/steps/writing/creation/dotGitHub/createDevelopment.test.ts index 80163acc9..9fdbbb403 100644 --- a/src/steps/writing/creation/dotGitHub/createDevelopment.test.ts +++ b/src/steps/writing/creation/dotGitHub/createDevelopment.test.ts @@ -28,6 +28,7 @@ const options = { excludeTests: undefined, funding: undefined, logo: undefined, + mode: "create", owner: "TestOwner", repository: "test-repository", skipGitHubApi: false, diff --git a/src/steps/writing/creation/index.ts b/src/steps/writing/creation/index.ts index 4b2ac3f71..897e6b350 100644 --- a/src/steps/writing/creation/index.ts +++ b/src/steps/writing/creation/index.ts @@ -1,4 +1,3 @@ -import { Mode } from "../../../bin/mode.js"; import { Options } from "../../../shared/types.js"; import { Structure } from "../types.js"; import { createDotGitHub } from "./dotGitHub/index.js"; @@ -7,15 +6,12 @@ import { createDotVSCode } from "./dotVSCode.js"; import { createRootFiles } from "./rootFiles.js"; import { createSrc } from "./src.js"; -export async function createStructure( - options: Options, - mode: Mode, -): Promise { +export async function createStructure(options: Options): Promise { return { ".github": await createDotGitHub(options), ".husky": createDotHusky(), ".vscode": await createDotVSCode(options), - ...(mode !== "migrate" && { src: await createSrc(options) }), + ...(options.mode !== "migrate" && { src: await createSrc(options) }), ...(await createRootFiles(options)), }; } diff --git a/src/steps/writing/creation/writePackageJson.test.ts b/src/steps/writing/creation/writePackageJson.test.ts index 90b83eed8..dc2d8fb56 100644 --- a/src/steps/writing/creation/writePackageJson.test.ts +++ b/src/steps/writing/creation/writePackageJson.test.ts @@ -36,6 +36,7 @@ const options = { excludeTests: false, funding: undefined, logo: undefined, + mode: "create", owner: "test-owner", repository: "test-repository", skipGitHubApi: false, diff --git a/src/steps/writing/writeStructure.ts b/src/steps/writing/writeStructure.ts index 71d2ab1ac..6ac110464 100644 --- a/src/steps/writing/writeStructure.ts +++ b/src/steps/writing/writeStructure.ts @@ -1,12 +1,11 @@ import { $ } from "execa"; -import { Mode } from "../../bin/mode.js"; import { Options } from "../../shared/types.js"; import { createStructure } from "./creation/index.js"; import { writeStructureWorker } from "./writeStructureWorker.js"; -export async function writeStructure(options: Options, mode: Mode) { - await writeStructureWorker(await createStructure(options, mode), "."); +export async function writeStructure(options: Options) { + await writeStructureWorker(await createStructure(options), "."); // https://github.com/JoshuaKGoldberg/create-typescript-app/issues/718 await $`chmod ug+x .husky/pre-commit`;