Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added --logo and --logo-alt #851

Merged
merged 7 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,7 @@
<img alt="TypeScript: Strict 💪" src="https://img.shields.io/badge/typescript-strict_💪-21bb42.svg" />
</p>

Note that this template is early stage, opinionated, and not endorsed by the TypeScript team.
It can be configured to set up a _lot_ of tooling out of the box.
Each of the included tools exists for a good reason and provides real value.

If you don't want to use any particular tool, you can always remove it manually.
<img align="right" alt="Project logo: the TypeScript blue square with rounded corners, but a plus sign instead of 'TS'" src="./create-typescript-app.png">

## Getting Started

Expand All @@ -47,6 +43,12 @@ Use the corresponding docs page to get started:
- [Initializing from the template](./docs/Initialization.md): creating a new repository with the [_Use this template_](https://github.com/JoshuaKGoldberg/create-typescript-app/generate) button on GitHub
- [Migrating an existing repository](./docs/Migration.md): adding this template's tooling on top of an existing repository

Note that this template is early stage, opinionated, and not endorsed by the TypeScript team.
It can be configured to set up a _lot_ of tooling out of the box.
Each of the included tools exists for a good reason and provides real value.

If you don't want to use any particular tool, you can always remove it manually.

## Explainer

This template is available for anybody who wants to set up a Node application using TypeScript.
Expand Down
Binary file added create-typescript-app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions docs/Options.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ The setup scripts also allow for optional overrides of the following inputs whos
- `--author` _(`string`)_: Username on npm to publish packages under (by default, an existing npm author, or the currently logged in npm user, or `owner.toLowerCase()`)
- `--email` _(`string`)_: Email address to be listed as the point of contact in docs and packages (e.g. `[email protected]`)
- `--funding` _(`string`)_: GitHub organization or username to mention in `funding.yml` (by default, `owner`)
- `--logo` _(`string`)_: Local image file in the repository to display near the top of the README.md as a logo
- `--logoAlt` _(`string`)_: If `--logo` is provided or detected from an existing README.md, alt text that describes the image will be prompted for if not provided

For example, customizing the ownership and users associated with a new repository:

Expand Down
85 changes: 58 additions & 27 deletions src/create/createRerunSuggestion.test.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,72 @@
import { describe, expect, it } from "vitest";

import { Options } from "../shared/types.js";
import { createRerunSuggestion } from "./createRerunSuggestion.js";

const options = {
author: "TestAuthor",
base: "everything",
createRepository: true,
description: "Test description.",
email: "[email protected]",
excludeCompliance: true,
excludeContributors: true,
excludeLintJson: true,
excludeLintKnip: true,
excludeLintMd: false,
excludeLintPackageJson: true,
excludeLintPackages: false,
excludeLintPerfectionist: true,
excludeLintSpelling: false,
excludeLintYml: false,
excludeReleases: false,
excludeRenovate: undefined,
excludeTests: undefined,
funding: undefined,
logo: undefined,
owner: "TestOwner",
repository: "test-repository",
skipGitHubApi: true,
skipInstall: true,
skipRemoval: true,
skipRestore: undefined,
skipUninstall: undefined,
title: "Test Title",
} satisfies Options;

describe("createRerunSuggestion", () => {
it("includes key-value pairs with mixed truthy and falsy values", () => {
const actual = createRerunSuggestion("initialize", options);

expect(actual).toMatchInlineSnapshot(
'"npx create-typescript-app --mode initialize --author TestAuthor --base everything --create-repository true --description \\"Test description.\\" --email [email protected] --exclude-compliance true --exclude-contributors 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\\""',
);
});

it("includes stringified logo when it exists", () => {
const actual = createRerunSuggestion("initialize", {
author: "TestAuthor",
base: "everything",
createRepository: true,
description: "Test description.",
email: "[email protected]",
...options,
logo: {
alt: "Test alt.",
src: "test/src.png",
},
});

expect(actual).toMatchInlineSnapshot(
'"npx create-typescript-app --mode initialize --author TestAuthor --base everything --create-repository true --description \\"Test description.\\" --email [email protected] --exclude-compliance true --exclude-contributors true --exclude-lint-json true --exclude-lint-knip true --exclude-lint-package-json true --exclude-lint-perfectionist true --logo test/src.png --owner TestOwner --repository test-repository --skip-github-api true --skip-install true --skip-removal true --title \\"Test Title\\" --logo-alt \\"Test alt.\\""',
);
});

it("includes exclusions when they exist", () => {
const actual = createRerunSuggestion("initialize", {
...options,
excludeCompliance: true,
excludeContributors: true,
excludeLintJson: true,
excludeLintKnip: true,
excludeLintMd: false,
excludeLintPackageJson: true,
excludeLintPackages: false,
excludeLintPerfectionist: true,
excludeLintSpelling: false,
excludeLintYml: false,
excludeReleases: false,
excludeRenovate: undefined,
excludeTests: undefined,
funding: undefined,
owner: "TestOwner",
repository: "test-repository",
skipGitHubApi: true,
skipInstall: true,
skipRemoval: true,
skipRestore: undefined,
skipUninstall: undefined,
title: "Test Title",
excludeLintMd: true,
excludeLintSpelling: true,
});

expect(actual).toMatchInlineSnapshot(
'"npx create-typescript-app --mode initialize --author TestAuthor --base everything --create-repository true --description \\"Test description.\\" --email [email protected] --exclude-compliance true --exclude-contributors 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 initialize --author TestAuthor --base everything --create-repository true --description \\"Test description.\\" --email [email protected] --exclude-compliance true --exclude-contributors 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\\""',
);
});
});
12 changes: 11 additions & 1 deletion src/create/createRerunSuggestion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@ export function createRerunSuggestion(
mode: Mode,
options: Partial<Options>,
): string {
const args = Object.entries(options)
const optionsNormalized = {
...options,
...(options.logo
? {
logo: options.logo.src,
logoAlt: options.logo.alt,
}
: { logo: undefined }),
};

const args = Object.entries(optionsNormalized)
.filter(([, value]) => !!value)
.map(([key, value]) => {
const valueStringified = `${value}`;
Expand Down
2 changes: 2 additions & 0 deletions src/shared/options/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export const allArgOptions = {
"exclude-renovate": { type: "boolean" },
"exclude-tests": { type: "boolean" },
funding: { type: "string" },
logo: { type: "string" },
"logo-alt": { type: "string" },
owner: { type: "string" },
repository: { type: "string" },
"skip-github-api": { type: "boolean" },
Expand Down
1 change: 1 addition & 0 deletions src/shared/options/augmentOptionsWithExcludes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const optionsBase = {
excludeRenovate: undefined,
excludeTests: undefined,
funding: undefined,
logo: undefined,
owner: "",
repository: "",
skipGitHubApi: false,
Expand Down
4 changes: 1 addition & 3 deletions src/shared/options/getPrefillOrPromptedOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export async function getPrefillOrPromptedOption(
return existingValue;
}

const value = filterPromptCancel(
return filterPromptCancel(
await prompts.text({
message,
placeholder: await getPlaceholder?.(),
Expand All @@ -22,6 +22,4 @@ export async function getPrefillOrPromptedOption(
},
}),
);

return value;
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { readPackageData } from "../../packages.js";
import { tryCatchAsync } from "../../tryCatchAsync.js";
import { tryCatchLazyValueAsync } from "../../tryCatchLazyValueAsync.js";
import { parsePackageAuthor } from "./parsePackageAuthor.js";
import { readTitleFromReadme } from "./readTitleFromReadme.js";
import { readDefaultsFromReadme } from "./readDefaultsFromReadme.js";

export function getGitAndNpmDefaults() {
const gitDefaults = tryCatchLazyValueAsync(async () =>
Expand Down Expand Up @@ -46,6 +46,6 @@ export function getGitAndNpmDefaults() {
(await gitDefaults())?.organization ?? (await packageAuthor()).author,
repository: async () =>
(await gitDefaults())?.name ?? (await packageData()).name,
title: async () => await readTitleFromReadme(),
...readDefaultsFromReadme(),
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { describe, expect, it, vi } from "vitest";

import { readDefaultsFromReadme } from "./readDefaultsFromReadme.js";

const mockReadFileSafe = vi.fn();

vi.mock("../../readFileSafe.js", () => ({
get readFileSafe() {
return mockReadFileSafe;
},
}));

describe("readDefaultsFromReadme", () => {
describe("logo", () => {
it("defaults to undefined when it cannot be found", async () => {
mockReadFileSafe.mockResolvedValue("nothing.");

const logo = await readDefaultsFromReadme().logo();

expect(logo).toBeUndefined();
});

it("parses when found in an unquoted string", async () => {
mockReadFileSafe.mockResolvedValue("<img src=abc/def.jpg/>");

const logo = await readDefaultsFromReadme().logo();

expect(logo).toBe("abc/def.jpg");
});

it("parses when found in a single quoted string", async () => {
mockReadFileSafe.mockResolvedValue("<img src='abc/def.jpg'/>");

const logo = await readDefaultsFromReadme().logo();

expect(logo).toBe("abc/def.jpg");
});

it("parses when found in a double quoted string", async () => {
mockReadFileSafe.mockResolvedValue('<img src="abc/def.jpg"/>');

const logo = await readDefaultsFromReadme().logo();

expect(logo).toBe("abc/def.jpg");
});
});

describe("title", () => {
it("defaults to undefined when it cannot be found", async () => {
mockReadFileSafe.mockResolvedValue("nothing.");

const title = await readDefaultsFromReadme().title();

expect(title).toBeUndefined();
});

it('reads title as markdown from "README.md" when it exists', async () => {
mockReadFileSafe.mockResolvedValue("# My Awesome Package");

const title = await readDefaultsFromReadme().title();

expect(title).toBe("My Awesome Package");
});

it('reads title as HTML from "README.md" when it exists', async () => {
mockReadFileSafe.mockResolvedValue(
'<h1 align="center">My Awesome Package</h1>',
);

const title = await readDefaultsFromReadme().title();

expect(title).toBe("My Awesome Package");
});

it("returns undefined when title does not exist", async () => {
mockReadFileSafe.mockResolvedValue("");

const title = await readDefaultsFromReadme().title();

expect(title).toBeUndefined();
});
});
});
23 changes: 23 additions & 0 deletions src/shared/options/readOptionDefaults/readDefaultsFromReadme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import lazyValue from "lazy-value";

import { readFileSafe } from "../../readFileSafe.js";

export function readDefaultsFromReadme() {
const readme = lazyValue(async () => await readFileSafe("README.md", ""));

const imageTag = lazyValue(
async () => (await readme()).match(/<img.+src.+\/>/)?.[0],
);

return {
logo: async () =>
(await imageTag())
?.match(/src\s*=(.+)?\/>/)?.[1]
?.replaceAll(/^['"]|['"]$/g, ""),
title: async () =>
(await readme())
.match(/^(?:# |<h1\s+align="center">)(.*?)(?:<\/h1>)?$/i)?.[1]
?.trim()
?.replace(/<[^>]+(?:>|$)/g, ""),
};
}
Loading