Skip to content
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "purview-dev",
"version": "0.5.1",
"version": "0.5.2",
"private": true,
"description": "Purview-Dev public website and unified documentation portal (workspace root).",
"license": "MIT",
Expand Down
20 changes: 20 additions & 0 deletions src/src/lib/releases/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,26 @@ export function selectVersions(versions: Iterable<string>): VersionSelection {
};
}

/**
* The prerelease worth surfacing alongside a stable release: the given
* prerelease when it outranks the stable, otherwise null. A prerelease that
* trails the stable (e.g. `5.0.0-prerelease.19` beside stable `5.0.2`) is
* withheld so the package table never advertises an older build as newer.
* A prerelease is always kept when there is no stable version to compare to.
*/
export function effectivePrerelease(
stable: string | null,
prerelease: string | null,
): string | null {
if (prerelease === null) {
return null;
}
if (stable === null) {
return prerelease;
}
return compareNuGetVersions(prerelease, stable) > 0 ? prerelease : null;
}

/** Compare two GitHub releases by version tag, descending. */
export function sortGitHubReleasesDescending(releases: GitHubReleaseInfo[]): GitHubReleaseInfo[] {
return [...releases].toSorted((a, b) => {
Expand Down
49 changes: 26 additions & 23 deletions src/src/pages/projects/[project].astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { buildBadge, nugetDownloadsBadge, nugetVersionBadge, actionsUrl } from '
import { installAnchorId, installKindFor, installTabs } from '~/lib/install';
import { LLMS_LINK_ATTRS } from '~/lib/llms';
import { loadProjects } from '~/lib/manifest/load';
import { effectivePrerelease } from '~/lib/releases/normalize';
import { projectRepoEnrichment } from '~/lib/releases/repo';
import { projectReleaseSummary } from '~/lib/releases/transform';
import { getReleaseIndex } from '~/lib/releases/runtime';
Expand Down Expand Up @@ -132,7 +133,9 @@ const packageRows: PackageRow[] = releases.packages.map((pkg) => ({
projectUrl,
packageId: pkg.packageId,
latestStable: pkg.latestStable,
latestPrerelease: pkg.latestPrerelease,
// Only surface a prerelease that outranks the stable release, so a trailing
// prerelease never sits beside a newer stable version in the table.
latestPrerelease: effectivePrerelease(pkg.latestStable, pkg.latestPrerelease),
totalDownloads: pkg.totalDownloads,
nugetUrl: pkg.nugetUrl,
deprecated: pkg.deprecated,
Expand Down Expand Up @@ -315,28 +318,6 @@ const packageRows: PackageRow[] = releases.packages.map((pkg) => ({

<div class="mt-12 grid gap-10 lg:grid-cols-[1fr_320px]">
<div class="min-w-0 space-y-12">
{
project.useCases.length > 0 && (
<section>
<h2 class="text-xl font-semibold">Use cases</h2>
<p class="mt-2 text-muted">
Concrete examples of what {project.name} does, and who each one is for.
</p>
<div class="mt-6 grid gap-5 lg:grid-cols-2">
{project.useCases.map((useCase) => (
<UseCaseCard
useCase={useCase}
projectName={project.name}
projectId={project.id}
docsBase={docsPath}
showProject={false}
/>
))}
</div>
</section>
)
}

<section>
<h2 class="text-xl font-semibold">The problem</h2>
<p class="mt-4 leading-relaxed text-muted">{project.description}</p>
Expand Down Expand Up @@ -403,6 +384,28 @@ const packageRows: PackageRow[] = releases.packages.map((pkg) => ({
</div>
</section>

{
project.useCases.length > 0 && (
<section>
<h2 class="text-xl font-semibold">Use cases</h2>
<p class="mt-2 text-muted">
Concrete examples of what {project.name} does, and who each one is for.
</p>
<div class="mt-6 grid gap-5 lg:grid-cols-2">
{project.useCases.map((useCase) => (
<UseCaseCard
useCase={useCase}
projectName={project.name}
projectId={project.id}
docsBase={docsPath}
showProject={false}
/>
))}
</div>
</section>
)
}

{
project.packages.length > 0 && (
<section>
Expand Down
5 changes: 4 additions & 1 deletion src/src/pages/releases/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import SiteLayout from '~/layouts/SiteLayout.astro';
import ReleaseFilter from '~/components/islands/ReleaseFilter.tsx';
import PackageVersions, { type PackageRow } from '~/components/islands/PackageVersions.tsx';
import { loadProjects } from '~/lib/manifest/load';
import { effectivePrerelease } from '~/lib/releases/normalize';
import { flattenReleases, projectReleaseSummary } from '~/lib/releases/transform';
import { getReleaseIndex } from '~/lib/releases/runtime';
import { SITE } from '~/lib/site';
Expand Down Expand Up @@ -30,7 +31,9 @@ const packageRows: PackageRow[] = summaries
projectName: summary.projectName,
packageId: pkg.packageId,
latestStable: pkg.latestStable,
latestPrerelease: pkg.latestPrerelease,
// Only surface a prerelease that outranks the stable release, so a
// trailing prerelease never sits beside a newer stable version.
latestPrerelease: effectivePrerelease(pkg.latestStable, pkg.latestPrerelease),
totalDownloads: pkg.totalDownloads,
nugetUrl: pkg.nugetUrl,
projectUrl: withBase(`/projects/${summary.projectId}/`),
Expand Down
22 changes: 22 additions & 0 deletions src/tests/dist/built-output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { resolve } from 'node:path';

import { LOCAL_PATH_PATTERNS, SECRET_PATTERNS } from '../../src/lib/build-paths';
import { loadProjects } from '../../src/lib/manifest/load';
import { compareNuGetVersions } from '../../src/lib/nuget-version';
import { getReleaseIndex } from '../../src/lib/releases/runtime';
import { projectReleaseSummary } from '../../src/lib/releases/transform';

const DIST = resolve('dist');

Expand Down Expand Up @@ -177,6 +180,25 @@ describe('built use cases', () => {
});
});

describe('built package versions', () => {
test('a package prerelease that trails its stable release is withheld', () => {
const telemetry = loadProjects().find((project) => project.id === 'telemetry-sourcegenerator');
expect(telemetry).toBeDefined();
const index = getReleaseIndex();
const summary = projectReleaseSummary(telemetry!, index.data, index.source);
const pkg = summary.packages.find(
(entry) => entry.packageId === 'Purview.Telemetry.SourceGenerator',
);
expect(pkg?.latestStable).not.toBeNull();
expect(pkg?.latestPrerelease).not.toBeNull();
// The package is a trailing prerelease (e.g. 5.0.0-prerelease.19 beside
// stable 5.0.2), so the table must not advertise it as a current version.
expect(compareNuGetVersions(pkg!.latestPrerelease!, pkg!.latestStable!)).toBeLessThan(0);
const content = requireBuilt('projects/telemetry-sourcegenerator/index.html');
expect(content).not.toContain(pkg!.latestPrerelease!);
});
});

describe('built llms links', () => {
// The LLM text bundles are plain files rather than site pages, so every link
// to one must opt into the external-link treatment.
Expand Down
26 changes: 26 additions & 0 deletions src/tests/unit/releases.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
parseNuGetSearchEntry,
} from '../../src/lib/releases/github';
import {
effectivePrerelease,
isPrereleaseRelease,
isPrereleaseVersion,
isValidVersion,
Expand Down Expand Up @@ -266,6 +267,31 @@ describe('version selection (NuGet precedence)', () => {
});
});

describe('effective prerelease (trailing prereleases are withheld)', () => {
test('withholds a prerelease that trails the stable release', () => {
// The Telemetry SourceGenerator case: stable 5.0.2 beside 5.0.0-prerelease.19.
expect(effectivePrerelease('5.0.2', '5.0.0-prerelease.19')).toBeNull();
});

test('keeps a prerelease that outranks the stable release', () => {
expect(effectivePrerelease('1.1.1', '2.0.0-prerelease.38')).toBe('2.0.0-prerelease.38');
});

test('keeps a prerelease when there is no stable release', () => {
expect(effectivePrerelease(null, '2.0.0-prerelease.32')).toBe('2.0.0-prerelease.32');
});

test('returns null when there is no prerelease', () => {
expect(effectivePrerelease('1.1.1', null)).toBeNull();
expect(effectivePrerelease(null, null)).toBeNull();
});

test('compares four-component versions by their build revision', () => {
expect(effectivePrerelease('13.5.3.6', '13.3.0-prerelease.10')).toBeNull();
expect(effectivePrerelease('13.5.3', '13.5.3.1-prerelease.1')).toBe('13.5.3.1-prerelease.1');
});
});

describe('project/package association and release summaries', () => {
test('enriches a project with repo description and tags', () => {
const cache = readReleaseFixture('index');
Expand Down
Loading