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
9 changes: 9 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: PR

on:
pull_request:
branches: [main]

jobs:
build:
uses: purview-dev/build/.github/workflows/purview-build.yml@main
19 changes: 19 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Release

on:
push:
branches: [main]

# Serialize releases; the reusable workflow skips when the v{version} tag already exists.
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
release:
uses: purview-dev/build/.github/workflows/purview-release.yml@main
with:
# The site publishes no NuGet package; tag + GitHub release (with the dist zip attached) only.
release-mode: GitHubRelease
upload-artifacts: true
secrets: inherit
44 changes: 0 additions & 44 deletions .github/workflows/validate.yml

This file was deleted.

20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ output tests.
| `test` | Deterministic unit test suite |
| `test-dist` | Assert built outputs (llms, sitemap, no secrets) |
| `build` | Production build (runs data sync first) |
| `ci-build` | Full CI validation chain used by the shared pipeline |
| `preview` | Preview the production build |
| `check-assets` | Validate branding sources and generated assets |
| `check-links` | Validate internal links across `dist/` |
Expand Down Expand Up @@ -255,6 +256,25 @@ The build generates `/llms.txt`, `/llms-small.txt`, and `/llms-full.txt`
the aggregated documentation. The built outputs are asserted by `tests/dist/`
and `just check-generated`.

## Site versioning and releases

The site is versioned like every other purview-dev repository. The single
source of truth is the `version` field in the root `package.json`; the footer
shows the current `v{version}` (linking to the repository's GitHub releases
page), read at build time by `src/src/lib/site-version.ts`.

Building and release flow run through the shared
[Purview.Build](https://purview.dev/projects/build/) system as a `Web` project
(`Build:ProjectType=Web` in `purview-build.json`): the pipeline runs the root
`package.json` scripts (`bun install`, `bun run format:check`/`bun run lint`,
`bun run test`) and the `ci:build` script, which chains data sync, typecheck,
asset checks, the production build, link crawl, generated-output checks, and
built-output tests. Releasing means bumping `version` in the root
`package.json` and merging: the `Release` workflow (`purview-release.yml`,
`release-mode: GitHubRelease`) runs the pipeline, tags `v{version}`, and uploads
the built `dist/` zip as a GitHub release asset. `deploy.yml` continues to
publish the site to GitHub Pages.

## GitHub Pages deployment

This repository is the organisation Pages repository
Expand Down
4 changes: 4 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ build:
bun run data:sync
bun run build

# Full CI validation chain used by the shared build pipeline (data sync → typecheck → build → checks).
ci-build:
bun run ci:build

# Preview the production build locally.
preview:
bun run preview
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "purview-dev",
"version": "0.1.0",
"private": true,
"description": "Purview-Dev public website and unified documentation portal (workspace root).",
"license": "MIT",
Expand All @@ -12,6 +13,7 @@
"check:assets": "bun run --cwd src check:assets",
"check:generated": "bun run --cwd src check:generated",
"check:links": "bun run --cwd src check:links",
"ci:build": "bun run data:sync && bun run typecheck && bun run check:assets && bun run build && bun run check:links && bun run check:generated && bun run test:dist",
"clean": "bun run --cwd src clean",
"data:sync": "bun run --cwd src data:sync",
"dev": "bun run --cwd src dev",
Expand Down
9 changes: 9 additions & 0 deletions purview-build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Build": {
"ProjectType": "Web",
"WebBuildCommand": "bun run ci:build"
},
"Release": {
"Mode": "None"
}
}
11 changes: 10 additions & 1 deletion src/src/components/SiteFooter.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
import SiteBrand from '~/components/SiteBrand.astro';
import { NAV_ITEMS } from '~/components/SiteNav.astro';
import { loadProjects } from '~/lib/manifest/load';
import { SITE } from '~/lib/site';
import { siteVersion } from '~/lib/site-version';
import { OWNER, SITE } from '~/lib/site';
import { withBase } from '~/lib/urls';

// Shuffled at build time so the footer doesn't always spotlight the same projects.
const footerProjects = loadProjects()
.filter((project) => project.status !== 'archived')
.toSorted(() => Math.random() - 0.5)
.slice(0, 3);

const version = siteVersion();
const releasesUrl = `${SITE.githubUrl}/${OWNER}.github.io/releases`;
---

<footer class="border-t border-border bg-surface">
Expand Down Expand Up @@ -83,6 +87,11 @@ const footerProjects = loadProjects()
<span class="beta-badge">Beta</span>
This site is under active development.
</p>
<p class="flex items-center gap-2">
<a href={releasesUrl} class="pv-link" title={`Purview website v${version}`}
>v{version}</a
>
</p>
<p class="flex items-center gap-2">
Built with <a href="https://astro.build" class="pv-link">Astro</a> and <a
href="https://starlight.astro.build"
Expand Down
9 changes: 8 additions & 1 deletion src/src/components/islands/PackageVersions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export interface PackageRow {
nugetUrl: string;
projectUrl: string;
deprecated?: boolean;
/** Hash link to this package's install section, e.g. "#install-purview-foo". */
installAnchor?: string;
}

type SortKey = 'package' | 'project' | 'stable' | 'prerelease' | 'downloads';
Expand Down Expand Up @@ -175,6 +177,11 @@ export default function PackageVersions({ packages, showProject = true }: Props)
deprecated
</span>
)}
{row.installAnchor && (
<a href={row.installAnchor} class="pv-link ml-2 text-xs whitespace-nowrap">
Install ↓
</a>
)}
</td>
{showProjectColumn && (
<td class="px-4 py-3">
Expand Down Expand Up @@ -265,7 +272,7 @@ export default function PackageVersions({ packages, showProject = true }: Props)
<button
type="button"
onClick={() => toggleSort(column.key)}
class="hover:text-accent font-semibold"
class="hover:text-accent border-0 bg-transparent p-0 font-semibold"
title={column.title}
>
{column.label}
Expand Down
5 changes: 5 additions & 0 deletions src/src/lib/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import type { ResolvedProject } from './manifest/load';
/** How a project's primary NuGet package is consumed. */
export type InstallKind = 'nuget' | 'msbuild-sdk' | 'dotnet-tool';

/** Anchor id for a package's install section, used to link from the packages table. */
export function installAnchorId(packageId: string): string {
return `install-${packageId.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`;
}

/** A single copyable install snippet. */
export interface InstallOption {
/** Short name for the snippet, e.g. "Directory.Packages.props". */
Expand Down
46 changes: 46 additions & 0 deletions src/src/lib/site-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { existsSync, readFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';

/**
* Locate the workspace root: the nearest ancestor package.json that declares a
* `workspaces` array. This is the repository root, which owns the site's
* release version (the same file Purview.Build reads to tag `v{version}`).
*/
export function findWorkspaceRoot(startDir: string = process.cwd()): string {
let directory = resolve(startDir);
for (;;) {
const packagePath = join(directory, 'package.json');
if (existsSync(packagePath)) {
const manifest: unknown = JSON.parse(readFileSync(packagePath, 'utf8'));
const workspaces = (manifest as { workspaces?: unknown }).workspaces;
if (Array.isArray(workspaces) && workspaces.length > 0) {
return directory;
}
}

const parent = dirname(directory);
if (parent === directory) {
throw new Error(`Could not locate a workspace root above ${startDir}`);
}
directory = parent;
}
}

/**
* The site's own version, read from the workspace-root package.json. Used in
* the footer and asserted by the built-output tests.
*/
export function readVersion(root: string): string {
const manifest: unknown = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
const version = (manifest as { version?: string }).version;
if (!version) {
throw new Error(
`The workspace root package.json at ${join(root, 'package.json')} has no version.`,
);
}
return version;
}

export function siteVersion(): string {
return readVersion(findWorkspaceRoot());
}
27 changes: 6 additions & 21 deletions src/src/pages/404.astro
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,13 @@ import { withBase } from '~/lib/urls';
This page seems to have evaporated.
</h1>
<p class="mt-6 max-w-xl text-lg text-muted">
The URL you followed does not exist on {SITE.domain}. The beaker may have
bubbled over — or the link may be out of date.
The URL you followed does not exist on {SITE.domain}. The beaker may have bubbled over — or
the link may be out of date.
</p>
<div class="mt-10 flex flex-wrap justify-center gap-3">
<a
href={withBase('/')}
class="rounded-lg bg-brand px-5 py-2.5 font-medium text-white transition-colors hover:bg-brand-emphasis"
>
Back to the homepage
</a>
<a
href={withBase('/projects/')}
class="rounded-lg border border-border bg-surface px-5 py-2.5 font-medium transition-colors hover:border-brand/40"
>
Browse projects
</a>
<a
href={withBase('/docs/')}
class="rounded-lg px-5 py-2.5 font-medium text-accent underline decoration-1 underline-offset-4 hover:decoration-2"
>
Documentation
</a>
<a href={withBase('/')} class="pv-btn pv-btn-primary"> Back to the homepage </a>
<a href={withBase('/projects/')} class="pv-btn pv-btn-secondary"> Browse projects </a>
<a href={withBase('/docs/')} class="pv-btn pv-btn-ghost"> Documentation </a>
</div>
</div>
</SiteLayout>
</SiteLayout>
Loading
Loading