Skip to content

Commit 13af13e

Browse files
Support prerelease badges and per-command install copy/README formatting
- Add versionPrerelease to BadgeSet and generate NuGet prerelease badge - Render prerelease badge image and markdown block in Packages UI - Add CopyTarget `command-{n}` and support copying individual command entries - Format combined installation commands as README sections (titles + fenced code blocks) - Add command cards with per-command copy buttons and "Copy All" for combined README - Update getInstallCommands (NuGet) to include multiple install variants with comment headings - CSS: ensure search dropdown min-height and add styles for combined-commands, command cards and responsive tweaks
1 parent f395d4b commit 13af13e

4 files changed

Lines changed: 239 additions & 36 deletions

File tree

src/components/PackageSearchDropdown.module.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
right: 0;
88
z-index: 1000;
99
margin-top: 0.5rem;
10+
min-height: 100px;
1011
max-height: 600px;
1112
overflow-y: auto;
1213
background: var(--bg-white);

src/pages/Packages.module.css

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,85 @@
193193
margin-top: 1.75rem;
194194
}
195195

196+
.installationSection h2 {
197+
font-size: 1.5rem;
198+
font-weight: 700;
199+
color: var(--text-color);
200+
margin: 0 0 0.5rem 0;
201+
}
202+
203+
.installationSection .section-description {
204+
font-size: 0.95rem;
205+
color: var(--text-light);
206+
margin: 0 0 1.25rem 0;
207+
}
208+
209+
.combinedCommands {
210+
margin-bottom: 2rem;
211+
}
212+
213+
.combinedCommands .markdown-card {
214+
background: var(--bg-white);
215+
border: 1px solid var(--border-color);
216+
border-radius: 16px;
217+
padding: 1.75rem;
218+
box-shadow: 0 12px 30px var(--shadow-light);
219+
}
220+
221+
.combinedCommands h3 {
222+
font-size: 1.1rem;
223+
font-weight: 600;
224+
color: var(--text-color);
225+
margin: 0;
226+
}
227+
228+
.combinedCommands pre {
229+
margin: 0;
230+
max-height: 400px;
231+
overflow-y: auto;
232+
}
233+
234+
.commandGrid {
235+
display: grid;
236+
gap: 1.25rem;
237+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
238+
}
239+
240+
.commandCard {
241+
background: var(--bg-white);
242+
border: 1px solid var(--border-color);
243+
border-radius: 12px;
244+
padding: 1.5rem;
245+
box-shadow: 0 8px 20px var(--shadow-light);
246+
transition: all 0.2s ease;
247+
}
248+
249+
.commandCard:hover {
250+
box-shadow: 0 12px 28px var(--shadow-color);
251+
border-color: var(--primary-color);
252+
}
253+
254+
.commandCard h3 {
255+
font-size: 1rem;
256+
font-weight: 600;
257+
color: var(--text-color);
258+
margin: 0;
259+
}
260+
261+
.commandCard pre {
262+
margin: 0;
263+
background: var(--bg-light);
264+
padding: 1rem;
265+
border-radius: 8px;
266+
border: 1px solid var(--border-light);
267+
}
268+
269+
.commandCard code {
270+
font-size: 0.875rem;
271+
line-height: 1.5;
272+
color: var(--text-color);
273+
}
274+
196275
.commandBlocks pre {
197276
margin: 0 0 0.75rem 0;
198277
}
@@ -225,4 +304,12 @@
225304
flex-direction: column;
226305
gap: 0.75rem;
227306
}
307+
308+
.commandGrid {
309+
grid-template-columns: 1fr;
310+
}
311+
312+
.commandCard {
313+
padding: 1.25rem;
314+
}
228315
}

src/pages/Packages.tsx

Lines changed: 137 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import styles from './Packages.module.css'
44
import { parsePackageInput, generatePackageBadges, getInstallCommands, type PackageManager } from '../utils/packageBadge'
55
import PackageSearchDropdown from '../components/PackageSearchDropdown'
66

7-
type CopyTarget = 'version' | 'downloads' | 'downloadsMonthly' | 'downloadsRecent' | 'combined' | 'commands'
7+
type CopyTarget = 'version' | 'versionPrerelease' | 'downloads' | 'downloadsMonthly' | 'downloadsRecent' | 'combined' | 'commands' | `command-${number}`
88
type InputMode = 'manual' | 'search'
99
type Registry = 'npm' | 'pypi' | 'nuget' | 'rubygems' | 'crates' | 'maven'
1010

@@ -215,30 +215,61 @@ function Packages() {
215215
if (!badgeData) return
216216

217217
let markdown = ''
218-
switch (target) {
219-
case 'version':
220-
markdown = badgeData.version.markdown
221-
break
222-
case 'downloads':
223-
markdown = badgeData.downloads?.markdown ?? ''
224-
break
225-
case 'downloadsMonthly':
226-
markdown = badgeData.downloadsMonthly?.markdown ?? ''
227-
break
228-
case 'downloadsRecent':
229-
markdown = badgeData.downloadsRecent?.markdown ?? ''
230-
break
231-
case 'combined': {
232-
const parts = [badgeData.version.markdown]
233-
if (badgeData.downloads) parts.push(badgeData.downloads.markdown)
234-
if (badgeData.downloadsMonthly) parts.push(badgeData.downloadsMonthly.markdown)
235-
if (badgeData.downloadsRecent) parts.push(badgeData.downloadsRecent.markdown)
236-
markdown = parts.join('\n')
237-
break
218+
219+
// Handle individual command copying
220+
if (target.startsWith('command-')) {
221+
const commandIndex = parseInt(target.split('-')[1], 10)
222+
const cmd = commands[commandIndex] || ''
223+
// Remove the comment line (e.g., "# .NET CLI\n") from the command
224+
const lines = cmd.split('\n')
225+
markdown = lines[0].startsWith('#')
226+
? lines.slice(1).join('\n').trim()
227+
: cmd.trim()
228+
} else {
229+
switch (target) {
230+
case 'version':
231+
markdown = badgeData.version.markdown
232+
break
233+
case 'versionPrerelease':
234+
markdown = badgeData.versionPrerelease?.markdown ?? ''
235+
break
236+
case 'downloads':
237+
markdown = badgeData.downloads?.markdown ?? ''
238+
break
239+
case 'downloadsMonthly':
240+
markdown = badgeData.downloadsMonthly?.markdown ?? ''
241+
break
242+
case 'downloadsRecent':
243+
markdown = badgeData.downloadsRecent?.markdown ?? ''
244+
break
245+
case 'combined': {
246+
const parts = [badgeData.version.markdown]
247+
if (badgeData.versionPrerelease) parts.push(badgeData.versionPrerelease.markdown)
248+
if (badgeData.downloads) parts.push(badgeData.downloads.markdown)
249+
if (badgeData.downloadsMonthly) parts.push(badgeData.downloadsMonthly.markdown)
250+
if (badgeData.downloadsRecent) parts.push(badgeData.downloadsRecent.markdown)
251+
markdown = parts.join('\n')
252+
break
253+
}
254+
case 'commands':
255+
markdown = commands.map(cmd => {
256+
// Format each command with proper markdown headings and code blocks
257+
const lines = cmd.split('\n')
258+
const titleLine = lines[0]
259+
const title = titleLine.startsWith('#')
260+
? titleLine.replace(/^#\s*/, '').trim()
261+
: null
262+
const codeContent = titleLine.startsWith('#')
263+
? lines.slice(1).join('\n').trim()
264+
: cmd.trim()
265+
266+
if (title) {
267+
return `### ${title}\n\`\`\`bash\n${codeContent}\n\`\`\``
268+
}
269+
return `\`\`\`bash\n${codeContent}\n\`\`\``
270+
}).join('\n\n')
271+
break
238272
}
239-
case 'commands':
240-
markdown = commands.join('\n\n')
241-
break
242273
}
243274

244275
if (markdown) {
@@ -403,6 +434,11 @@ function Packages() {
403434
<a href={badgeData.packageUrl} target="_blank" rel="noopener noreferrer">
404435
<img src={badgeData.version.imageUrl} alt="Version badge" />
405436
</a>
437+
{badgeData.versionPrerelease && (
438+
<a href={badgeData.packageUrl} target="_blank" rel="noopener noreferrer">
439+
<img src={badgeData.versionPrerelease.imageUrl} alt="Prerelease version badge" />
440+
</a>
441+
)}
406442
{badgeData.downloads && (
407443
<a href={badgeData.packageUrl} target="_blank" rel="noopener noreferrer">
408444
<img src={badgeData.downloads.imageUrl} alt="Downloads badge" />
@@ -437,6 +473,18 @@ function Packages() {
437473
<pre><code>{badgeData.version.markdown}</code></pre>
438474
</article>
439475

476+
{badgeData.versionPrerelease && (
477+
<article className="markdown-card">
478+
<header className="output-header">
479+
<h3>Prerelease Version</h3>
480+
<button type="button" className="copy-btn" onClick={() => handleCopy('versionPrerelease')}>
481+
{copiedTarget === 'versionPrerelease' ? '✅ Copied!' : '📋 Copy'}
482+
</button>
483+
</header>
484+
<pre><code>{badgeData.versionPrerelease.markdown}</code></pre>
485+
</article>
486+
)}
487+
440488
{badgeData.downloads && (
441489
<article className="markdown-card">
442490
<header className="output-header">
@@ -484,6 +532,7 @@ function Packages() {
484532
</header>
485533
<pre><code>{[
486534
badgeData.version.markdown,
535+
badgeData.versionPrerelease?.markdown,
487536
badgeData.downloads?.markdown,
488537
badgeData.downloadsMonthly?.markdown,
489538
badgeData.downloadsRecent?.markdown
@@ -493,16 +542,70 @@ function Packages() {
493542

494543
{commands.length > 0 && (
495544
<div className={`${styles.installationSection} installation-section`}>
496-
<header className="output-header">
497-
<h3>Installation Commands</h3>
498-
<button type="button" className="copy-btn" onClick={() => handleCopy('commands')}>
499-
{copiedTarget === 'commands' ? '✅ Copied!' : '📋 Copy'}
500-
</button>
501-
</header>
502-
<div className="command-blocks">
503-
{commands.map((cmd, index) => (
504-
<pre key={index}><code>{cmd}</code></pre>
505-
))}
545+
<h2>Installation Commands</h2>
546+
<p className="section-description">
547+
Choose your preferred installation method:
548+
</p>
549+
550+
<div className={`${styles.combinedCommands} combined-commands`}>
551+
<article className="markdown-card">
552+
<header className="output-header">
553+
<h3>Combined README Format</h3>
554+
<button
555+
type="button"
556+
className="copy-btn"
557+
onClick={() => handleCopy('commands')}
558+
>
559+
{copiedTarget === 'commands' ? '✅ Copied!' : '📋 Copy All'}
560+
</button>
561+
</header>
562+
<pre><code>{commands.map(cmd => {
563+
// Format each command with proper markdown
564+
const lines = cmd.split('\n')
565+
const titleLine = lines[0]
566+
const title = titleLine.startsWith('#')
567+
? titleLine.replace(/^#\s*/, '').trim()
568+
: null
569+
const codeContent = titleLine.startsWith('#')
570+
? lines.slice(1).join('\n').trim()
571+
: cmd.trim()
572+
573+
if (title) {
574+
return `### ${title}\n\`\`\`bash\n${codeContent}\n\`\`\``
575+
}
576+
return `\`\`\`bash\n${codeContent}\n\`\`\``
577+
}).join('\n\n')}</code></pre>
578+
</article>
579+
</div>
580+
581+
<div className={styles.commandGrid}>
582+
{commands.map((cmd, index) => {
583+
// Extract the title from the comment (e.g., "# .NET CLI" -> ".NET CLI")
584+
const lines = cmd.split('\n')
585+
const titleLine = lines[0]
586+
const title = titleLine.startsWith('#')
587+
? titleLine.replace(/^#\s*/, '').trim()
588+
: `Option ${index + 1}`
589+
const codeContent = titleLine.startsWith('#')
590+
? lines.slice(1).join('\n').trim()
591+
: cmd.trim()
592+
593+
return (
594+
<article key={index} className={`${styles.commandCard} markdown-card`}>
595+
<header className="output-header">
596+
<h3>{title}</h3>
597+
<button
598+
type="button"
599+
className="copy-btn"
600+
onClick={() => handleCopy(`command-${index}` as CopyTarget)}
601+
>
602+
{copiedTarget === `command-${index}` ? '✅ Copied!' : '📋 Copy'}
603+
</button>
604+
</header>
605+
<pre><code>{codeContent}</code></pre>
606+
</article>
607+
)
608+
})}
506609
</div>
507610
</div>
508611
)}

src/utils/packageBadge.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ export interface BadgeSet {
1616
markdown: string;
1717
imageUrl: string;
1818
};
19+
versionPrerelease?: {
20+
markdown: string;
21+
imageUrl: string;
22+
};
1923
downloads?: {
2024
markdown: string;
2125
imageUrl: string;
@@ -173,13 +177,18 @@ export function generatePackageBadges(
173177
case 'nuget': {
174178
if (!packageId) return null;
175179
const versionUrl = `https://img.shields.io/nuget/v/${packageId}?style=${style}&logo=nuget`;
180+
const prereleaseUrl = `https://img.shields.io/nuget/vpre/${packageId}?style=${style}&logo=nuget&label=prerelease`;
176181
const downloadsUrl = `https://img.shields.io/nuget/dt/${packageId}?style=${style}&logo=nuget&label=downloads`;
177182

178183
return {
179184
version: {
180185
markdown: `[![NuGet version](${versionUrl})](https://www.nuget.org/packages/${packageId})`,
181186
imageUrl: versionUrl
182187
},
188+
versionPrerelease: {
189+
markdown: `[![NuGet prerelease](${prereleaseUrl})](https://www.nuget.org/packages/${packageId})`,
190+
imageUrl: prereleaseUrl
191+
},
183192
downloads: {
184193
markdown: `[![NuGet downloads](${downloadsUrl})](https://www.nuget.org/packages/${packageId})`,
185194
imageUrl: downloadsUrl
@@ -289,8 +298,11 @@ export function getInstallCommands(
289298

290299
case 'nuget':
291300
return packageId ? [
292-
`dotnet add package ${packageId}`,
293-
`Install-Package ${packageId}`
301+
`# .NET CLI\ndotnet add package ${packageId}`,
302+
`# Package Manager\nInstall-Package ${packageId}`,
303+
`# PackageReference\n<PackageReference Include="${packageId}" Version="*" />`,
304+
`# Paket CLI\npaket add ${packageId}`,
305+
`# Script & Interactive\n#r "nuget: ${packageId}, *"`
294306
] : [];
295307

296308
case 'pypi':

0 commit comments

Comments
 (0)