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: add options.explainer for blockREADME #1879

Merged
merged 3 commits into from
Jan 15, 2025
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
4 changes: 4 additions & 0 deletions src/next/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export const base = createBase({
.describe(
"email address to be listed as the point of contact in docs and packages",
),
explainer: z
.array(z.string())
.optional()
.describe("additional README.md sentence(s) describing the package"),
funding: z
.string()
.optional()
Expand Down
92 changes: 91 additions & 1 deletion src/next/blocks/blockREADME.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe("blockREADME", () => {
"README.md": "<h1 align="center">Test Title</h1>

<p align="center">
First sentence.
First sentence.
Second sentence.
</p>

Expand All @@ -89,6 +89,47 @@ describe("blockREADME", () => {
`);
});

test("options.explainer", () => {
const creation = testBlock(blockREADME, {
options: {
...options,
explainer: ["And a one.", "And a two."],
},
});

expect(creation).toMatchInlineSnapshot(`
{
"files": {
"README.md": "<h1 align="center">Test Title</h1>

<p align="center">Test description</p>

<p align="center">
<a href="https://github.com/test-owner/test-repository/blob/main/.github/CODE_OF_CONDUCT.md" target="_blank"><img alt="🤝 Code of Conduct: Kept" src="https://img.shields.io/badge/%F0%9F%A4%9D_code_of_conduct-kept-21bb42" /></a>
<a href="https://codecov.io/gh/test-owner/test-repository" target="_blank"><img alt="🧪 Coverage" src="https://img.shields.io/codecov/c/github/test-owner/test-repository?label=%F0%9F%A7%AA%20coverage" /></a>
<a href="https://github.com/test-owner/test-repository/blob/main/LICENSE.md" target="_blank"><img alt="📝 License: MIT" src="https://img.shields.io/badge/%F0%9F%93%9D_license-MIT-21bb42.svg"></a>
<a href="http://npmjs.com/package/test-repository"><img alt="📦 npm version" src="https://img.shields.io/npm/v/test-repository?color=21bb42&label=%F0%9F%93%A6%20npm" /></a>
<img alt="💪 TypeScript: Strict" src="https://img.shields.io/badge/%F0%9F%92%AA_typescript-strict-21bb42.svg" />
</p>

And a one.
And a two.

## Usage

Use it.

## Development

See [\`.github/CONTRIBUTING.md\`](./.github/CONTRIBUTING.md), then [\`.github/DEVELOPMENT.md\`](./.github/DEVELOPMENT.md).
Thanks! 💖

",
},
}
`);
});

test("options.logo without sizing", () => {
const creation = testBlock(blockREADME, {
options: {
Expand Down Expand Up @@ -177,6 +218,55 @@ describe("blockREADME", () => {
`);
});

test("options.explainer and options.logo", () => {
const creation = testBlock(blockREADME, {
options: {
...options,
explainer: ["And a one.", "And a two."],
logo: {
alt: "My logo",
height: 100,
src: "img.jpg",
width: 128,
},
},
});

expect(creation).toMatchInlineSnapshot(`
{
"files": {
"README.md": "<h1 align="center">Test Title</h1>

<p align="center">Test description</p>

<p align="center">
<a href="https://github.com/test-owner/test-repository/blob/main/.github/CODE_OF_CONDUCT.md" target="_blank"><img alt="🤝 Code of Conduct: Kept" src="https://img.shields.io/badge/%F0%9F%A4%9D_code_of_conduct-kept-21bb42" /></a>
<a href="https://codecov.io/gh/test-owner/test-repository" target="_blank"><img alt="🧪 Coverage" src="https://img.shields.io/codecov/c/github/test-owner/test-repository?label=%F0%9F%A7%AA%20coverage" /></a>
<a href="https://github.com/test-owner/test-repository/blob/main/LICENSE.md" target="_blank"><img alt="📝 License: MIT" src="https://img.shields.io/badge/%F0%9F%93%9D_license-MIT-21bb42.svg"></a>
<a href="http://npmjs.com/package/test-repository"><img alt="📦 npm version" src="https://img.shields.io/npm/v/test-repository?color=21bb42&label=%F0%9F%93%A6%20npm" /></a>
<img alt="💪 TypeScript: Strict" src="https://img.shields.io/badge/%F0%9F%92%AA_typescript-strict-21bb42.svg" />
</p>

<img align="right" alt="My logo" height="100" src="img.jpg" width="128">

And a one.
And a two.

## Usage

Use it.

## Development

See [\`.github/CONTRIBUTING.md\`](./.github/CONTRIBUTING.md), then [\`.github/DEVELOPMENT.md\`](./.github/DEVELOPMENT.md).
Thanks! 💖

",
},
}
`);
});

test("without addons", () => {
const creation = testBlock(blockREADME, {
options,
Expand Down
14 changes: 9 additions & 5 deletions src/next/blocks/blockREADME.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { base } from "../base.js";
function printAttributes(attributes: Record<string, number | string>) {
return Object.entries(attributes)
.map(([key, value]) => `${key}="${value}"`)
.sort()
.join(" ");
}

Expand All @@ -20,9 +21,12 @@ export const blockREADME = base.createBlock({
produce({ addons, options }) {
const { badges, notices, sections } = addons;

const logo = options.logo
? `\n<img ${printAttributes({ align: "right", ...options.logo })}>\n`
: "";
const logo =
options.logo &&
`\n<img ${printAttributes({ align: "right", ...options.logo })}>\n`;

const explainer =
options.explainer && `\n${options.explainer.join("\n")}\n`;

return {
files: {
Expand All @@ -37,7 +41,7 @@ export const blockREADME = base.createBlock({
<a href="http://npmjs.com/package/${options.repository}"><img alt="📦 npm version" src="https://img.shields.io/npm/v/${options.repository}?color=21bb42&label=%F0%9F%93%A6%20npm" /></a>
<img alt="💪 TypeScript: Strict" src="https://img.shields.io/badge/%F0%9F%92%AA_typescript-strict-21bb42.svg" />
</p>
${logo}
${[logo, explainer].filter(Boolean).join("")}
## Usage

${options.usage}
Expand All @@ -58,5 +62,5 @@ function formatDescription(description: string) {
return description;
}

return "\n\t" + description.replaceAll(". ", ". \n\t") + "\n";
return "\n\t" + description.replaceAll(". ", ".\n\t") + "\n";
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,50 @@ vi.mock("./getUsageFromReadme.js", () => ({
}));

describe("readDefaultsFromReadme", () => {
describe("explainer", () => {
it("defaults to undefined when it cannot be found", async () => {
const explainer = await readDefaultsFromReadme(
() => Promise.resolve(`nothing.`),
() => Promise.resolve(undefined),
).explainer();

expect(explainer).toBeUndefined();
});

it("parses a line after badges", async () => {
const explainer = await readDefaultsFromReadme(
() =>
Promise.resolve(`
</p>

This is my project.

## Usage
.`),
() => Promise.resolve(undefined),
).explainer();

expect(explainer).toEqual(["This is my project."]);
});

it("parses multiple line after badges", async () => {
const explainer = await readDefaultsFromReadme(
() =>
Promise.resolve(`
</p>

This is my project.
It is good.

## Usage
.`),
() => Promise.resolve(undefined),
).explainer();

expect(explainer).toEqual(["This is my project.", "It is good."]);
});
});

describe("logo", () => {
it("defaults to undefined when it cannot be found", async () => {
const logo = await readDefaultsFromReadme(
Expand Down Expand Up @@ -107,7 +151,7 @@ describe("readDefaultsFromReadme", () => {
const logo = await readDefaultsFromReadme(
() =>
Promise.resolve(`
<img alt='Project logo: a fancy circle' src='abc/def.jpg'/>`),
<img alt='Project logo: a fancy circle' height='117px' src='abc/def.jpg' width=' 117px'/>`),
() => Promise.resolve(undefined),
).logo();

Expand Down
16 changes: 14 additions & 2 deletions src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ export function readDefaultsFromReadme(
);

return {
explainer: async () => {
return />\n\n([\s\S]*?)\n\n## Usage/u
.exec(await readme())?.[1]
.split("\n")
.map((line) => line.trim())
.filter(Boolean);
},

logo: async () => {
const tag = await imageTag();

Expand All @@ -22,18 +30,22 @@ export function readDefaultsFromReadme(

const src = /src\s*=(.+)['"/]>/
.exec(tag)?.[1]
?.replaceAll(/^['"]|['"]$/g, "");
?.split(/\s*\w+=/)[0]
.replaceAll(/^['"]|['"]$/g, "");

if (!src) {
return undefined;
}

return {
alt: /alt=['"](.+)['"]\s*src=/.exec(tag)?.[1] ?? "Project logo",
alt:
/alt=['"](.+)['"]\s*src=/.exec(tag)?.[1].split(/['"]?\s*\w+=/)[0] ??
"Project logo",
src,
...readLogoSizing(src),
};
},

title: async () => {
const text = await readme();
const fromText = (/^<h1\s+align="center">(.+)<\/h1>/.exec(text) ??
Expand Down
Loading