diff --git a/bun.lock b/bun.lock index 47bbd78..a2b815a 100644 --- a/bun.lock +++ b/bun.lock @@ -9,6 +9,7 @@ "name": "@purview-dev/website", "version": "0.1.0", "dependencies": { + "@pagefind/default-ui": "1.5.2", "preact": "10.29.8", }, "devDependencies": { diff --git a/src/astro.config.ts b/src/astro.config.ts index 7175c2c..76ac6eb 100644 --- a/src/astro.config.ts +++ b/src/astro.config.ts @@ -116,6 +116,7 @@ export default defineConfig({ PageFrame: './src/components/starlight/PageFrame.astro', EditLink: './src/components/starlight/EditLink.astro', PageTitle: './src/components/starlight/PageTitle.astro', + Search: './src/components/starlight/Search.astro', }, plugins: [ starlightSidebarTopics(sidebarTopics), diff --git a/src/fixtures/github/releases/build.json b/src/fixtures/github/releases/build.json index a5602b9..7dfc6e4 100644 --- a/src/fixtures/github/releases/build.json +++ b/src/fixtures/github/releases/build.json @@ -264,7 +264,7 @@ "state": "uploaded", "size": 11433619, "digest": "sha256:0bd5430870e569ca80e6c141844ff234ed730863a2ae553a1bb23cf573e5079e", - "download_count": 2, + "download_count": 3, "created_at": "2026-09-04T14:18:41Z", "updated_at": "2026-09-04T14:18:41Z", "browser_download_url": "https://github.com/purview-dev/build/releases/download/v0.2.2/Purview.Build.0.2.2.nupkg" diff --git a/src/fixtures/github/releases/telemetry-sourcegenerator.json b/src/fixtures/github/releases/telemetry-sourcegenerator.json index 2e60003..675e01f 100644 --- a/src/fixtures/github/releases/telemetry-sourcegenerator.json +++ b/src/fixtures/github/releases/telemetry-sourcegenerator.json @@ -1,4 +1,47 @@ [ + { + "url": "https://api.github.com/repos/purview-dev/telemetry-sourcegenerator/releases/390304693", + "assets_url": "https://api.github.com/repos/purview-dev/telemetry-sourcegenerator/releases/390304693/assets", + "upload_url": "https://uploads.github.com/repos/purview-dev/telemetry-sourcegenerator/releases/390304693/assets{?name,label}", + "html_url": "https://github.com/purview-dev/telemetry-sourcegenerator/releases/tag/v5.0.0-prerelease.9", + "id": 390304693, + "author": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "node_id": "RE_kwDOLblSmc4XQ5O1", + "tag_name": "v5.0.0-prerelease.9", + "target_commitish": "main", + "name": "v5.0.0-prerelease.9", + "draft": false, + "immutable": false, + "prerelease": false, + "created_at": "2026-09-16T22:01:30Z", + "updated_at": "2026-09-16T22:05:51Z", + "published_at": "2026-09-16T22:05:51Z", + "assets": [], + "tarball_url": "https://api.github.com/repos/purview-dev/telemetry-sourcegenerator/tarball/v5.0.0-prerelease.9", + "zipball_url": "https://api.github.com/repos/purview-dev/telemetry-sourcegenerator/zipball/v5.0.0-prerelease.9", + "body": "## What's Changed\n* docs: updated urls in documentation by @kieronlanning in https://github.com/purview-dev/telemetry-sourcegenerator/pull/123\n* docs: add documentation wiki by @kieronlanning in https://github.com/purview-dev/telemetry-sourcegenerator/pull/124\n* docs: refresh performance benchmarks by @kieronlanning in https://github.com/purview-dev/telemetry-sourcegenerator/pull/127\n* fix: make benchmark host builds resolve the generator correctly by @kieronlanning in https://github.com/purview-dev/telemetry-sourcegenerator/pull/125\n* docs: refresh performance benchmarks by @kieronlanning in https://github.com/purview-dev/telemetry-sourcegenerator/pull/126\n* perf: fixed logger v2 benchmarks and added agressive inlining by @kieronlanning in https://github.com/purview-dev/telemetry-sourcegenerator/pull/128\n* chore: bumped version by @kieronlanning in https://github.com/purview-dev/telemetry-sourcegenerator/pull/129\n\n\n**Full Changelog**: https://github.com/purview-dev/telemetry-sourcegenerator/compare/v5.0.0-prerelease.8...v5.0.0-prerelease.9", + "mentions_count": 1 + }, { "url": "https://api.github.com/repos/purview-dev/telemetry-sourcegenerator/releases/388421722", "assets_url": "https://api.github.com/repos/purview-dev/telemetry-sourcegenerator/releases/388421722/assets", diff --git a/src/fixtures/github/repos/aspire-resourcekit.json b/src/fixtures/github/repos/aspire-resourcekit.json index fb19b30..c00a12b 100644 --- a/src/fixtures/github/repos/aspire-resourcekit.json +++ b/src/fixtures/github/repos/aspire-resourcekit.json @@ -73,7 +73,7 @@ "clone_url": "https://github.com/purview-dev/aspire-resourcekit.git", "svn_url": "https://github.com/purview-dev/aspire-resourcekit", "homepage": "https://purview.dev/projects/aspire-resourcekit/", - "size": 4123, + "size": 4151, "stargazers_count": 2, "watchers_count": 2, "language": "C#", @@ -115,7 +115,25 @@ "open_issues": 0, "watchers": 2, "default_branch": "main", - "temp_clone_token": null, + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": true, + "delete_branch_on_merge": true, + "allow_update_branch": true, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", "custom_properties": {}, "organization": { "login": "purview-dev", @@ -138,6 +156,32 @@ "user_view_type": "public", "site_admin": false }, + "security_and_analysis": { + "secret_scanning": { + "status": "enabled" + }, + "secret_scanning_push_protection": { + "status": "enabled" + }, + "dependabot_security_updates": { + "status": "disabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_ai_detection": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + }, + "secret_scanning_delegated_alert_dismissal": { + "status": "disabled" + }, + "secret_scanning_delegated_bypass": { + "status": "disabled" + } + }, "network_count": 0, "subscribers_count": 0 } diff --git a/src/fixtures/github/repos/aspirec4.json b/src/fixtures/github/repos/aspirec4.json index 8abccb8..bc1dbfc 100644 --- a/src/fixtures/github/repos/aspirec4.json +++ b/src/fixtures/github/repos/aspirec4.json @@ -73,7 +73,7 @@ "clone_url": "https://github.com/purview-dev/aspirec4.git", "svn_url": "https://github.com/purview-dev/aspirec4", "homepage": "https://purview.dev/projects/aspirec4/", - "size": 10791, + "size": 10817, "stargazers_count": 9, "watchers_count": 9, "language": "C#", @@ -126,7 +126,25 @@ "open_issues": 1, "watchers": 9, "default_branch": "main", - "temp_clone_token": null, + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": true, + "delete_branch_on_merge": true, + "allow_update_branch": true, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", "custom_properties": {}, "organization": { "login": "purview-dev", @@ -149,6 +167,32 @@ "user_view_type": "public", "site_admin": false }, + "security_and_analysis": { + "secret_scanning": { + "status": "enabled" + }, + "secret_scanning_push_protection": { + "status": "enabled" + }, + "dependabot_security_updates": { + "status": "disabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_ai_detection": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + }, + "secret_scanning_delegated_alert_dismissal": { + "status": "disabled" + }, + "secret_scanning_delegated_bypass": { + "status": "disabled" + } + }, "network_count": 3, "subscribers_count": 0 } diff --git a/src/fixtures/github/repos/build.json b/src/fixtures/github/repos/build.json index 38e36fd..70875a1 100644 --- a/src/fixtures/github/repos/build.json +++ b/src/fixtures/github/repos/build.json @@ -66,14 +66,14 @@ "releases_url": "https://api.github.com/repos/purview-dev/build/releases{/id}", "deployments_url": "https://api.github.com/repos/purview-dev/build/deployments", "created_at": "2026-09-01T18:12:54Z", - "updated_at": "2026-09-16T16:29:28Z", - "pushed_at": "2026-09-16T16:29:24Z", + "updated_at": "2026-09-16T21:00:15Z", + "pushed_at": "2026-09-16T21:00:13Z", "git_url": "git://github.com/purview-dev/build.git", "ssh_url": "git@github.com:purview-dev/build.git", "clone_url": "https://github.com/purview-dev/build.git", "svn_url": "https://github.com/purview-dev/build", "homepage": "https://purview.dev/projects/build/", - "size": 185, + "size": 209, "stargazers_count": 0, "watchers_count": 0, "language": "C#", @@ -119,7 +119,25 @@ "open_issues": 1, "watchers": 0, "default_branch": "main", - "temp_clone_token": null, + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": true, + "delete_branch_on_merge": true, + "allow_update_branch": true, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", "custom_properties": {}, "organization": { "login": "purview-dev", @@ -142,6 +160,32 @@ "user_view_type": "public", "site_admin": false }, + "security_and_analysis": { + "secret_scanning": { + "status": "enabled" + }, + "secret_scanning_push_protection": { + "status": "enabled" + }, + "dependabot_security_updates": { + "status": "disabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_ai_detection": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + }, + "secret_scanning_delegated_alert_dismissal": { + "status": "disabled" + }, + "secret_scanning_delegated_bypass": { + "status": "disabled" + } + }, "network_count": 0, "subscribers_count": 0 } diff --git a/src/fixtures/github/repos/dotnet-logging-source-generators.json b/src/fixtures/github/repos/dotnet-logging-source-generators.json index ce0346f..974b831 100644 --- a/src/fixtures/github/repos/dotnet-logging-source-generators.json +++ b/src/fixtures/github/repos/dotnet-logging-source-generators.json @@ -100,7 +100,25 @@ "open_issues": 0, "watchers": 3, "default_branch": "main", - "temp_clone_token": null, + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": false, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", "custom_properties": {}, "organization": { "login": "purview-dev", @@ -123,6 +141,29 @@ "user_view_type": "public", "site_admin": false }, + "security_and_analysis": { + "secret_scanning": { + "status": "enabled" + }, + "secret_scanning_push_protection": { + "status": "disabled" + }, + "dependabot_security_updates": { + "status": "disabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_ai_detection": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + }, + "secret_scanning_delegated_alert_dismissal": { + "status": "disabled" + } + }, "network_count": 0, "subscribers_count": 0 } diff --git a/src/fixtures/github/repos/dotnet-project-sdk.json b/src/fixtures/github/repos/dotnet-project-sdk.json index 7ebdf10..274d4a2 100644 --- a/src/fixtures/github/repos/dotnet-project-sdk.json +++ b/src/fixtures/github/repos/dotnet-project-sdk.json @@ -73,7 +73,7 @@ "clone_url": "https://github.com/purview-dev/dotnet-project-sdk.git", "svn_url": "https://github.com/purview-dev/dotnet-project-sdk", "homepage": "https://purview.dev/projects/dotnet-project-sdk/", - "size": 1359, + "size": 1401, "stargazers_count": 0, "watchers_count": 0, "language": "C#", @@ -123,7 +123,25 @@ "open_issues": 0, "watchers": 0, "default_branch": "main", - "temp_clone_token": null, + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": true, + "delete_branch_on_merge": true, + "allow_update_branch": true, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", "custom_properties": {}, "organization": { "login": "purview-dev", @@ -146,6 +164,32 @@ "user_view_type": "public", "site_admin": false }, + "security_and_analysis": { + "secret_scanning": { + "status": "enabled" + }, + "secret_scanning_push_protection": { + "status": "enabled" + }, + "dependabot_security_updates": { + "status": "disabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_ai_detection": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + }, + "secret_scanning_delegated_alert_dismissal": { + "status": "disabled" + }, + "secret_scanning_delegated_bypass": { + "status": "disabled" + } + }, "network_count": 0, "subscribers_count": 0 } diff --git a/src/fixtures/github/repos/event-sourcing.json b/src/fixtures/github/repos/event-sourcing.json index 88b852f..3de376c 100644 --- a/src/fixtures/github/repos/event-sourcing.json +++ b/src/fixtures/github/repos/event-sourcing.json @@ -118,7 +118,25 @@ "open_issues": 0, "watchers": 4, "default_branch": "main", - "temp_clone_token": null, + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": true, + "delete_branch_on_merge": true, + "allow_update_branch": true, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", "custom_properties": {}, "organization": { "login": "purview-dev", @@ -141,6 +159,32 @@ "user_view_type": "public", "site_admin": false }, + "security_and_analysis": { + "secret_scanning": { + "status": "enabled" + }, + "secret_scanning_push_protection": { + "status": "enabled" + }, + "dependabot_security_updates": { + "status": "enabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_ai_detection": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + }, + "secret_scanning_delegated_alert_dismissal": { + "status": "disabled" + }, + "secret_scanning_delegated_bypass": { + "status": "disabled" + } + }, "network_count": 0, "subscribers_count": 0 } diff --git a/src/fixtures/github/repos/sourcegeneratorframework.json b/src/fixtures/github/repos/sourcegeneratorframework.json index d99fb05..82de64e 100644 --- a/src/fixtures/github/repos/sourcegeneratorframework.json +++ b/src/fixtures/github/repos/sourcegeneratorframework.json @@ -73,7 +73,7 @@ "clone_url": "https://github.com/purview-dev/sourcegeneratorframework.git", "svn_url": "https://github.com/purview-dev/sourcegeneratorframework", "homepage": "https://purview.dev/projects/sourcegeneratorframework/", - "size": 7985, + "size": 8061, "stargazers_count": 2, "watchers_count": 2, "language": "C#", @@ -123,7 +123,25 @@ "open_issues": 0, "watchers": 2, "default_branch": "main", - "temp_clone_token": null, + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": true, + "delete_branch_on_merge": true, + "allow_update_branch": true, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", "custom_properties": {}, "organization": { "login": "purview-dev", @@ -146,6 +164,32 @@ "user_view_type": "public", "site_admin": false }, + "security_and_analysis": { + "secret_scanning": { + "status": "enabled" + }, + "secret_scanning_push_protection": { + "status": "enabled" + }, + "dependabot_security_updates": { + "status": "disabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_ai_detection": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + }, + "secret_scanning_delegated_alert_dismissal": { + "status": "disabled" + }, + "secret_scanning_delegated_bypass": { + "status": "disabled" + } + }, "network_count": 0, "subscribers_count": 0 } diff --git a/src/fixtures/github/repos/telemetry-sourcegenerator.json b/src/fixtures/github/repos/telemetry-sourcegenerator.json index a359ec4..40fd5bb 100644 --- a/src/fixtures/github/repos/telemetry-sourcegenerator.json +++ b/src/fixtures/github/repos/telemetry-sourcegenerator.json @@ -66,14 +66,14 @@ "releases_url": "https://api.github.com/repos/purview-dev/telemetry-sourcegenerator/releases{/id}", "deployments_url": "https://api.github.com/repos/purview-dev/telemetry-sourcegenerator/deployments", "created_at": "2024-03-04T18:31:56Z", - "updated_at": "2026-09-16T15:25:13Z", - "pushed_at": "2026-09-16T15:25:09Z", + "updated_at": "2026-09-16T22:01:36Z", + "pushed_at": "2026-09-16T22:05:51Z", "git_url": "git://github.com/purview-dev/telemetry-sourcegenerator.git", "ssh_url": "git@github.com:purview-dev/telemetry-sourcegenerator.git", "clone_url": "https://github.com/purview-dev/telemetry-sourcegenerator.git", "svn_url": "https://github.com/purview-dev/telemetry-sourcegenerator", "homepage": "https://purview.dev/projects/telemetry-sourcegenerator/", - "size": 7357, + "size": 7448, "stargazers_count": 35, "watchers_count": 35, "language": "C#", @@ -127,7 +127,25 @@ "open_issues": 0, "watchers": 35, "default_branch": "main", - "temp_clone_token": null, + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": false, + "allow_auto_merge": true, + "delete_branch_on_merge": true, + "allow_update_branch": true, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", "custom_properties": {}, "organization": { "login": "purview-dev", @@ -150,6 +168,32 @@ "user_view_type": "public", "site_admin": false }, + "security_and_analysis": { + "secret_scanning": { + "status": "enabled" + }, + "secret_scanning_push_protection": { + "status": "enabled" + }, + "dependabot_security_updates": { + "status": "enabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_ai_detection": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + }, + "secret_scanning_delegated_alert_dismissal": { + "status": "disabled" + }, + "secret_scanning_delegated_bypass": { + "status": "disabled" + } + }, "network_count": 2, "subscribers_count": 1 } diff --git a/src/fixtures/github/repos/zodsharp.json b/src/fixtures/github/repos/zodsharp.json index 58a0ad0..77bcfa1 100644 --- a/src/fixtures/github/repos/zodsharp.json +++ b/src/fixtures/github/repos/zodsharp.json @@ -73,7 +73,7 @@ "clone_url": "https://github.com/purview-dev/zodsharp.git", "svn_url": "https://github.com/purview-dev/zodsharp", "homepage": "https://purview.dev/projects/zodsharp/", - "size": 970, + "size": 1011, "stargazers_count": 1, "watchers_count": 1, "language": "C#", @@ -113,7 +113,25 @@ "open_issues": 0, "watchers": 1, "default_branch": "main", - "temp_clone_token": null, + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": true, + "delete_branch_on_merge": true, + "allow_update_branch": true, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", "custom_properties": {}, "organization": { "login": "purview-dev", @@ -136,6 +154,32 @@ "user_view_type": "public", "site_admin": false }, + "security_and_analysis": { + "secret_scanning": { + "status": "enabled" + }, + "secret_scanning_push_protection": { + "status": "enabled" + }, + "dependabot_security_updates": { + "status": "disabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_ai_detection": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + }, + "secret_scanning_delegated_alert_dismissal": { + "status": "disabled" + }, + "secret_scanning_delegated_bypass": { + "status": "disabled" + } + }, "network_count": 0, "subscribers_count": 0 } diff --git a/src/fixtures/nuget/purview.telemetry.sourcegenerator.json b/src/fixtures/nuget/purview.telemetry.sourcegenerator.json index 3cf4043..0c5e9ec 100644 --- a/src/fixtures/nuget/purview.telemetry.sourcegenerator.json +++ b/src/fixtures/nuget/purview.telemetry.sourcegenerator.json @@ -54,6 +54,7 @@ "5.0.0-prerelease.5", "5.0.0-prerelease.6", "5.0.0-prerelease.7", - "5.0.0-prerelease.8" + "5.0.0-prerelease.8", + "5.0.0-prerelease.9" ] } diff --git a/src/fixtures/nuget/search/AspireC4.Hosting.json b/src/fixtures/nuget/search/AspireC4.Hosting.json index 229cbf3..0bb9a68 100644 --- a/src/fixtures/nuget/search/AspireC4.Hosting.json +++ b/src/fixtures/nuget/search/AspireC4.Hosting.json @@ -35,7 +35,7 @@ "kieronlanning", "KJL.dev" ], - "totalDownloads": 1342, + "totalDownloads": 1380, "verified": false, "packageTypes": [ { @@ -65,12 +65,12 @@ }, { "version": "13.5.3", - "downloads": 48, + "downloads": 67, "@id": "https://api.nuget.org/v3/registration5-semver1/aspirec4.hosting/13.5.3.json" }, { "version": "13.5.3.1", - "downloads": 50, + "downloads": 69, "@id": "https://api.nuget.org/v3/registration5-semver1/aspirec4.hosting/13.5.3.1.json" } ], diff --git a/src/fixtures/nuget/search/Purview.Build.json b/src/fixtures/nuget/search/Purview.Build.json index 7503335..3984e46 100644 --- a/src/fixtures/nuget/search/Purview.Build.json +++ b/src/fixtures/nuget/search/Purview.Build.json @@ -24,7 +24,7 @@ "owners": [ "kieronlanning" ], - "totalDownloads": 530, + "totalDownloads": 552, "verified": false, "packageTypes": [ { @@ -49,7 +49,7 @@ }, { "version": "0.2.4", - "downloads": 83, + "downloads": 105, "@id": "https://api.nuget.org/v3/registration5-semver1/purview.build/0.2.4.json" } ], diff --git a/src/fixtures/nuget/search/Purview.Telemetry.SourceGenerator.json b/src/fixtures/nuget/search/Purview.Telemetry.SourceGenerator.json index a4081f4..85d7ff7 100644 --- a/src/fixtures/nuget/search/Purview.Telemetry.SourceGenerator.json +++ b/src/fixtures/nuget/search/Purview.Telemetry.SourceGenerator.json @@ -48,7 +48,7 @@ "kieronlanning", "KJL.dev" ], - "totalDownloads": 16253, + "totalDownloads": 16276, "verified": false, "packageTypes": [ { @@ -158,7 +158,7 @@ }, { "version": "3.2.4", - "downloads": 4399, + "downloads": 4415, "@id": "https://api.nuget.org/v3/registration5-semver1/purview.telemetry.sourcegenerator/3.2.4.json" }, { @@ -178,7 +178,7 @@ }, { "version": "4.4.0", - "downloads": 564, + "downloads": 566, "@id": "https://api.nuget.org/v3/registration5-semver1/purview.telemetry.sourcegenerator/4.4.0.json" } ], diff --git a/src/fixtures/releases/index.json b/src/fixtures/releases/index.json index a81e12c..dfe6187 100644 --- a/src/fixtures/releases/index.json +++ b/src/fixtures/releases/index.json @@ -1,6 +1,6 @@ { "schema": 1, - "retrievedAt": "2026-09-16T16:33:38.150Z", + "retrievedAt": "2026-09-17T06:31:26.351Z", "repos": { "purview-dev/telemetry-sourcegenerator": { "name": "telemetry-sourcegenerator", @@ -33,7 +33,7 @@ "stargazersCount": 35, "openIssuesCount": 0, "defaultBranch": "main", - "updatedAt": "2026-09-16T15:25:13Z", + "updatedAt": "2026-09-16T22:01:36Z", "hasDiscussions": false }, "purview-dev/eventsourcing": { @@ -207,7 +207,7 @@ "stargazersCount": 0, "openIssuesCount": 1, "defaultBranch": "main", - "updatedAt": "2026-09-16T16:29:28Z", + "updatedAt": "2026-09-16T21:00:15Z", "hasDiscussions": false }, "purview-dev/dotnet-project-sdk": { @@ -256,6 +256,15 @@ }, "releases": { "purview-dev/telemetry-sourcegenerator": [ + { + "tagName": "v5.0.0-prerelease.9", + "name": "v5.0.0-prerelease.9", + "body": "## What's Changed\n* docs: updated urls in documentation by @kieronlanning in https://github.com/purview-dev/telemetry-sourcegenerator/pull/123\n* docs: add documentation wiki by @kieronlanning in https://github.com/purview-dev/telemetry-sourcegenerator/pull/124\n* docs: refresh performance benchmarks by @kieronlanning in https://github.com/purview-dev/telemetry-sourcegenerator/pull/127\n* fix: make benchmark host builds resolve the generator correctly by @kieronlanning in https://github.com/purview-dev/telemetry-sourcegenerator/pull/125\n* docs: refresh performance benchmarks by @kieronlanning in https://github.com/purview-dev/telemetry-sourcegenerator/pull/126\n* perf: fixed logger v2 benchmarks and added agressive inlining by @kieronlanning in https://github.com/purview-dev/telemetry-sourcegenerator/pull/128\n* chore: bumped version by @kieronlanning in https://github.com/purview-dev/telemetry-sourcegenerator/pull/129\n\n\n**Full Changelog**: https://github.com/purview-dev/telemetry-sourcegenerator/compare/v5.0.0-prerelease.8...v5.0.0-prerelease.9", + "prerelease": false, + "draft": false, + "publishedAt": "2026-09-16T22:05:51Z", + "htmlUrl": "https://github.com/purview-dev/telemetry-sourcegenerator/releases/tag/v5.0.0-prerelease.9" + }, { "tagName": "v5.0.0-prerelease.8", "name": "v5.0.0-prerelease.8", @@ -1222,7 +1231,8 @@ "5.0.0-prerelease.5", "5.0.0-prerelease.6", "5.0.0-prerelease.7", - "5.0.0-prerelease.8" + "5.0.0-prerelease.8", + "5.0.0-prerelease.9" ] }, "Purview.EventSourcing": { @@ -1714,7 +1724,7 @@ "id": "Purview.Telemetry.SourceGenerator", "version": "4.4.0", "description": ".NET Source Generator for interface-based telemetry generating tracing, logs, and metrics.", - "totalDownloads": 16253, + "totalDownloads": 16276, "tags": [ "logs", "log", @@ -1765,7 +1775,7 @@ "id": "AspireC4.Hosting", "version": "13.5.3.1", "description": "Aspire hosting extension for the AspireC4 plugin, generating a dynamic LikeC4 architecture diagram for your Aspire application.", - "totalDownloads": 1342, + "totalDownloads": 1380, "tags": [ "aspire", "dotnet", @@ -1786,7 +1796,7 @@ "id": "Purview.Build", "version": "0.2.4", "description": "Shared build/test/release pipeline for purview-dev. A Modular Pipelines based .NET tool (Purview.Build) with a GitHub composite action and reusable workflows; consumers configure, the pipeline runs.", - "totalDownloads": 530, + "totalDownloads": 552, "tags": [], "published": null, "deprecated": false, diff --git a/src/package.json b/src/package.json index b8e73df..5149cac 100644 --- a/src/package.json +++ b/src/package.json @@ -25,6 +25,7 @@ "typecheck": "astro check" }, "dependencies": { + "@pagefind/default-ui": "1.5.2", "preact": "10.29.8" }, "devDependencies": { diff --git a/src/src/components/islands/CopyCommand.tsx b/src/src/components/islands/CopyCommand.tsx deleted file mode 100644 index 7116d1b..0000000 --- a/src/src/components/islands/CopyCommand.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useState } from 'preact/hooks'; - -interface Props { - command: string; -} - -export default function CopyCommand({ command }: Props) { - const [copied, setCopied] = useState(false); - - const copy = async () => { - try { - if (navigator.clipboard?.writeText) { - await navigator.clipboard.writeText(command); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - } - } catch { - // Clipboard access can be blocked; the button is a convenience only. - } - }; - - return ( -
- {command} - -
- ); -} diff --git a/src/src/components/islands/InstallPanel.tsx b/src/src/components/islands/InstallPanel.tsx new file mode 100644 index 0000000..52d156b --- /dev/null +++ b/src/src/components/islands/InstallPanel.tsx @@ -0,0 +1,54 @@ +import type { InstallOption } from '~/lib/install'; + +import { useState } from 'preact/hooks'; + +function CopyBlock({ label, detail, code }: InstallOption) { + const [copied, setCopied] = useState(false); + + const copy = async () => { + try { + if (navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(code); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + } catch { + // Clipboard access can be blocked; the button is a convenience only. + } + }; + + return ( +
+
+

{label}

+ {detail &&

{detail}

} +
+
+
+          {code}
+        
+ +
+
+ ); +} + +interface Props { + options: InstallOption[]; +} + +export default function InstallPanel({ options }: Props) { + return ( +
+ {options.map((option) => ( + + ))} +
+ ); +} diff --git a/src/src/components/starlight/Search.astro b/src/src/components/starlight/Search.astro new file mode 100644 index 0000000..227b95c --- /dev/null +++ b/src/src/components/starlight/Search.astro @@ -0,0 +1,526 @@ +--- +import project from 'virtual:starlight/project-context'; + +const pagefindTranslations = { + placeholder: Astro.locals.t('search.label'), + ...Object.fromEntries( + Object.entries(Astro.locals.t.all()) + .filter(([key]) => key.startsWith('pagefind.')) + .map(([key, value]) => [key.replace('pagefind.', ''), value]) + ), +}; + +const dataAttributes: DOMStringMap = { 'data-translations': JSON.stringify(pagefindTranslations) }; +if (project.trailingSlash === 'never') dataAttributes['data-strip-trailing-slash'] = ''; +--- + + + + + +
+ { + /* Theme-aware close button. The default Starlight button only shows + on mobile and can overlap the search input's magnifier icon, so it is + replaced here with an in-flow, right-aligned cross shown on every + viewport. The accessible name comes from the translations below. */ + } + + { + import.meta.env.DEV ? ( +
+

{Astro.locals.t('search.devWarning')}

+
+ ) : ( +
+ + ) + } +
+
+
+ +{ + /** + * This is intentionally inlined to avoid briefly showing an invalid shortcut. + * Purposely using the deprecated `navigator.platform` property to detect Apple devices, as the + * user agent is spoofed by some browsers when opening the devtools. + */ +} + + + + + + + \ No newline at end of file diff --git a/src/src/data/projects.yml b/src/src/data/projects.yml index bb2179b..86e477d 100644 --- a/src/src/data/projects.yml +++ b/src/src/data/projects.yml @@ -207,6 +207,7 @@ projects: - _Sidebar.md targetFrameworks: - net10.0 + install: dotnet-tool packages: - id: Purview.Build description: Pinned dotnet tool implementing the shared pipeline. @@ -231,6 +232,7 @@ projects: - _Sidebar.md targetFrameworks: - msbuild + install: msbuild-sdk packages: - id: Purview.DotNetProjectSdk description: MSBuild SDK for standardised project defaults. diff --git a/src/src/env.d.ts b/src/src/env.d.ts index 448582a..896e2ca 100644 --- a/src/src/env.d.ts +++ b/src/src/env.d.ts @@ -22,3 +22,18 @@ declare module 'virtual:starlight/components/MobileMenuToggle' { const MobileMenuToggle: AstroComponentFactory; export default MobileMenuToggle; } + +declare module 'virtual:starlight/project-context' { + interface StarlightProjectContext { + build: { format: 'directory' | 'file' }; + root: string; + srcDir: string; + trailingSlash: 'always' | 'never' | 'ignore'; + } + const project: StarlightProjectContext; + export default project; +} + +declare module 'virtual:starlight/pagefind-config' { + export const pagefindUserConfig: Record; +} diff --git a/src/src/lib/install.ts b/src/src/lib/install.ts new file mode 100644 index 0000000..a3a3cc0 --- /dev/null +++ b/src/src/lib/install.ts @@ -0,0 +1,122 @@ +import type { ResolvedProject } from './manifest/load'; + +/** How a project's primary NuGet package is consumed. */ +export type InstallKind = 'nuget' | 'msbuild-sdk' | 'dotnet-tool'; + +export interface InstallOption { + /** Short name for the install target, e.g. ".NET CLI". */ + label: string; + /** Optional one-line hint shown under the label. */ + detail?: string; + /** Copyable install snippet (may span multiple lines). */ + code: string; +} + +/** Resolve the install kind for a project, defaulting to a regular NuGet package. */ +export function installKindFor(project: Pick): InstallKind { + return project.install; +} + +/** Version placeholder used when no published version is available yet. */ +const VERSION_PLACEHOLDER = ''; + +/** Interpolate the latest version, falling back to a placeholder. */ +function withVersion(version: string | null): string { + return version ?? VERSION_PLACEHOLDER; +} + +function nugetOptions(packageId: string, version: string | null): InstallOption[] { + return [ + { + label: '.NET CLI', + code: `dotnet add package ${packageId}`, + }, + { + label: 'CPM — Directory.Packages.props', + detail: 'Add to your central package file.', + code: ``, + }, + { + label: 'CPM — Project file', + detail: 'The version is resolved from central package management.', + code: ``, + }, + { + label: 'PackageReference', + code: ``, + }, + { + label: 'File-Based Apps', + detail: 'Reference a package from a file-based app.', + code: `#:package ${packageId}@${withVersion(version)}`, + }, + { + label: 'Script & interactive', + detail: 'Load the package in C# scripts and notebooks.', + code: `#r "nuget: ${packageId}, ${withVersion(version)}"`, + }, + { + label: 'PMC (NuGet\\Install-Package.ps1)', + detail: 'Run from the Package Manager Console.', + code: `Install-Package ${packageId} -Version ${withVersion(version)}`, + }, + ]; +} + +function msbuildSdkOptions(packageId: string, version: string | null): InstallOption[] { + return [ + { + label: 'global.json', + detail: 'Pin the SDK for the whole repository.', + code: `{\n "msbuild-sdks": {\n "${packageId}": "${withVersion(version)}"\n }\n}`, + }, + { + label: 'SDK', + detail: 'Reference the SDK from an individual project file.', + code: ``, + }, + { + label: 'File-Based Apps', + detail: 'Reference the SDK from a file-based app.', + code: `#:sdk ${packageId}@${withVersion(version)}`, + }, + ]; +} + +function dotnetToolOptions(packageId: string): InstallOption[] { + return [ + { + label: '.NET CLI (Global)', + code: `dotnet tool install --global ${packageId}`, + }, + { + label: '.NET CLI (Local)', + detail: 'Create a local tool manifest first.', + code: 'dotnet new tool-manifest', + }, + { + label: '.NET CLI (Local)', + detail: 'Install the tool into the local manifest.', + code: `dotnet tool install ${packageId}`, + }, + ]; +} + +/** + * Build the ordered list of install options for a package of the given kind. + * The order matches the canonical display order on the project page. + */ +export function installOptions( + kind: InstallKind, + packageId: string, + version: string | null, +): InstallOption[] { + switch (kind) { + case 'msbuild-sdk': + return msbuildSdkOptions(packageId, version); + case 'dotnet-tool': + return dotnetToolOptions(packageId); + case 'nuget': + return nugetOptions(packageId, version); + } +} diff --git a/src/src/lib/manifest/load.ts b/src/src/lib/manifest/load.ts index 7bd924d..0efacc1 100644 --- a/src/src/lib/manifest/load.ts +++ b/src/src/lib/manifest/load.ts @@ -23,6 +23,7 @@ export interface ResolvedProject extends ProjectRecord { packages: NonNullable; related: NonNullable; discussions: boolean; + install: NonNullable; } export type ResolvedExternalProject = Omit< @@ -54,6 +55,7 @@ function withDefaults(record: ProjectRecord): ResolvedProject { packages: record.packages ?? PROJECT_DEFAULTS.packages, related: record.related ?? PROJECT_DEFAULTS.related, discussions: record.discussions ?? PROJECT_DEFAULTS.discussions, + install: record.install ?? PROJECT_DEFAULTS.install, repoOwner, repoName, sourceUrl: repo, diff --git a/src/src/lib/manifest/schema.ts b/src/src/lib/manifest/schema.ts index cc7d30c..b71967d 100644 --- a/src/src/lib/manifest/schema.ts +++ b/src/src/lib/manifest/schema.ts @@ -12,6 +12,9 @@ export const CATEGORIES = [ export const STATUSES = ['stable', 'preview', 'archived'] as const; +/** How the primary NuGet package is consumed, which drives the Install options. */ +export const INSTALL_KINDS = ['nuget', 'msbuild-sdk', 'dotnet-tool'] as const; + const docsPathSchema = z.object({ source: z.literal('github-path'), path: z.string().min(1, 'must be a non-empty repository path such as "docs"'), @@ -57,6 +60,7 @@ const projectSchema = z.object({ featured: z.boolean().optional(), order: z.number().int().nonnegative().optional(), docs: docsSchema.optional(), + install: z.enum(INSTALL_KINDS).optional(), targetFrameworks: z.array(z.string()).optional(), packages: z.array(packageSchema).optional(), related: z.array(z.string()).optional(), @@ -99,10 +103,12 @@ export const PROJECT_DEFAULTS = { packages: [] as ProjectPackage[], related: [] as string[], discussions: false, + install: 'nuget', } satisfies { featured: boolean; order: number; packages: ProjectPackage[]; related: string[]; discussions: boolean; + install: (typeof INSTALL_KINDS)[number]; }; diff --git a/src/src/lib/releases/transform.ts b/src/src/lib/releases/transform.ts index 212a92d..2f54a55 100644 --- a/src/src/lib/releases/transform.ts +++ b/src/src/lib/releases/transform.ts @@ -7,7 +7,12 @@ import type { } from './types'; import { nugetPackageUrl } from '../urls'; -import { isPrereleaseRelease, selectVersions, sortGitHubReleasesDescending } from './normalize'; +import { + isPrereleaseRelease, + normalizePrereleaseVersion, + selectVersions, + sortGitHubReleasesDescending, +} from './normalize'; export type DataSource = 'live' | 'cache' | 'fixture'; @@ -25,6 +30,24 @@ export interface ReleaseEntry { prerelease: boolean; } +/** + * Fold GitHub release tag versions into the NuGet candidate list when the + * flat-container index has not caught up with a release. Tags already present + * in the NuGet list are left untouched; only missing tags are appended. + */ +function reconcileWithGitHubReleases(versions: string[], releases: GitHubReleaseInfo[]): string[] { + const known = new Set(versions.map(normalizePrereleaseVersion)); + const reconciled = [...versions]; + for (const release of releases) { + const version = normalizePrereleaseVersion(release.tagName); + if (!known.has(version)) { + known.add(version); + reconciled.push(version); + } + } + return reconciled; +} + /** Build the release summary for a single catalogue project. */ export function projectReleaseSummary( project: ResolvedProject, @@ -41,7 +64,16 @@ export function projectReleaseSummary( const index = cache.packages[pkg.id]; const search = cache.packageSearch[pkg.id] ?? null; const versions = index?.versions ?? []; - const selection = selectVersions(versions); + // The NuGet flat-container index can lag behind a just-completed release + // (the pipeline pushes to NuGet before creating the GitHub release), so + // GitHub release tags missing from the NuGet list are added as candidates. + // NuGet remains authoritative whenever it is caught up: an absent tag only + // ever fills a gap and never outranks a version NuGet already lists. The + // package must already exist on NuGet to reconcile, so a version is never + // invented for a package NuGet has not published. + const candidateVersions = + versions.length > 0 ? reconcileWithGitHubReleases(versions, releases) : versions; + const selection = selectVersions(candidateVersions); return { packageId: pkg.id, description: pkg.description ?? search?.description ?? null, diff --git a/src/src/pages/projects/[project].astro b/src/src/pages/projects/[project].astro index 2249b8d..1e97011 100644 --- a/src/src/pages/projects/[project].astro +++ b/src/src/pages/projects/[project].astro @@ -1,8 +1,9 @@ --- import SiteLayout from '~/layouts/SiteLayout.astro'; -import CopyCommand from '~/components/islands/CopyCommand.tsx'; +import InstallPanel from '~/components/islands/InstallPanel.tsx'; import StatusBadge from '~/components/StatusBadge.astro'; import { buildBadge, nugetDownloadsBadge, nugetVersionBadge, actionsUrl } from '~/lib/badges'; +import { installKindFor, installOptions } from '~/lib/install'; import { loadProjects } from '~/lib/manifest/load'; import { projectRepoEnrichment } from '~/lib/releases/repo'; import { projectReleaseSummary } from '~/lib/releases/transform'; @@ -40,6 +41,17 @@ const supersededBy = project.supersededBy const primaryPackage = project.packages.find((pkg) => pkg.primary) ?? project.packages[0]; const docsPath = project.docs ? withBase(`/docs/${project.id}/`) : null; const targetFrameworks = project.targetFrameworks ?? []; +const primaryRelease = primaryPackage + ? releases.packages.find((pkg) => pkg.packageId === primaryPackage.id) + : undefined; +const installSnippets = + primaryPackage && project.status !== 'archived' + ? installOptions( + installKindFor(project), + primaryPackage.id, + primaryRelease?.latestStable ?? primaryRelease?.latestPrerelease ?? null, + ) + : []; ---

Install

- + ) } diff --git a/src/tests/unit/install.test.ts b/src/tests/unit/install.test.ts new file mode 100644 index 0000000..9829a71 --- /dev/null +++ b/src/tests/unit/install.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, test } from 'bun:test'; + +import { installKindFor, installOptions } from '../../src/lib/install'; + +describe('installKindFor', () => { + test('returns the declared install kind', () => { + expect(installKindFor({ install: 'dotnet-tool' })).toBe('dotnet-tool'); + expect(installKindFor({ install: 'msbuild-sdk' })).toBe('msbuild-sdk'); + expect(installKindFor({ install: 'nuget' })).toBe('nuget'); + }); +}); + +describe('installOptions — nuget', () => { + test('renders every option in canonical order with the latest version', () => { + const options = installOptions('nuget', 'Purview.EventSourcing', '2.0.0-prerelease.32'); + expect(options.map((option) => option.label)).toEqual([ + '.NET CLI', + 'CPM — Directory.Packages.props', + 'CPM — Project file', + 'PackageReference', + 'File-Based Apps', + 'Script & interactive', + 'PMC (NuGet\\Install-Package.ps1)', + ]); + expect(options[0]?.code).toBe('dotnet add package Purview.EventSourcing'); + expect(options[1]?.code).toBe( + '', + ); + expect(options[2]?.code).toBe(''); + expect(options[3]?.code).toBe( + '', + ); + expect(options[4]?.code).toBe('#:package Purview.EventSourcing@2.0.0-prerelease.32'); + expect(options[5]?.code).toBe('#r "nuget: Purview.EventSourcing, 2.0.0-prerelease.32"'); + expect(options[6]?.code).toBe( + 'Install-Package Purview.EventSourcing -Version 2.0.0-prerelease.32', + ); + }); + + test('falls back to a version placeholder when no version is known', () => { + const options = installOptions('nuget', 'Purview.Foo', null); + expect(options[1]?.code).toBe(''); + expect(options[4]?.code).toBe('#:package Purview.Foo@'); + expect(options[0]?.code).toBe('dotnet add package Purview.Foo'); + }); +}); + +describe('installOptions — msbuild-sdk', () => { + test('renders the SDK options with version interpolation', () => { + const options = installOptions( + 'msbuild-sdk', + 'Purview.DotNetProjectSdk', + '1.0.0-prerelease.55', + ); + expect(options.map((option) => option.label)).toEqual([ + 'global.json', + 'SDK', + 'File-Based Apps', + ]); + expect(options[0]?.code).toBe( + '{\n "msbuild-sdks": {\n "Purview.DotNetProjectSdk": "1.0.0-prerelease.55"\n }\n}', + ); + expect(options[1]?.code).toBe( + '', + ); + expect(options[2]?.code).toBe('#:sdk Purview.DotNetProjectSdk@1.0.0-prerelease.55'); + }); +}); + +describe('installOptions — dotnet-tool', () => { + test('renders global and local tool install commands', () => { + const options = installOptions('dotnet-tool', 'Purview.Build', '0.2.4'); + expect(options.map((option) => option.label)).toEqual([ + '.NET CLI (Global)', + '.NET CLI (Local)', + '.NET CLI (Local)', + ]); + expect(options[0]?.code).toBe('dotnet tool install --global Purview.Build'); + expect(options[1]?.code).toBe('dotnet new tool-manifest'); + expect(options[2]?.code).toBe('dotnet tool install Purview.Build'); + }); +}); diff --git a/src/tests/unit/manifest.test.ts b/src/tests/unit/manifest.test.ts index c239bfb..a3507c3 100644 --- a/src/tests/unit/manifest.test.ts +++ b/src/tests/unit/manifest.test.ts @@ -45,6 +45,16 @@ describe('project manifest', () => { expect(telemetry?.sourceUrl).toBe('https://github.com/purview-dev/telemetry-sourcegenerator'); }); + test('resolves the install kind, defaulting to nuget', () => { + const projects = loadProjects(); + const telemetry = projects.find((p) => p.id === 'telemetry-sourcegenerator'); + expect(telemetry?.install).toBe('nuget'); + const sdk = projects.find((p) => p.id === 'dotnet-project-sdk'); + expect(sdk?.install).toBe('msbuild-sdk'); + const build = projects.find((p) => p.id === 'build'); + expect(build?.install).toBe('dotnet-tool'); + }); + test('rejects a repository outside the purview-dev org', () => { expect(() => parseManifest( diff --git a/src/tests/unit/releases.test.ts b/src/tests/unit/releases.test.ts index e91ac1a..bf0fd21 100644 --- a/src/tests/unit/releases.test.ts +++ b/src/tests/unit/releases.test.ts @@ -1,3 +1,5 @@ +import type { ReleaseCacheData } from '../../src/lib/releases/types'; + import { describe, expect, test } from 'bun:test'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; @@ -47,7 +49,7 @@ describe('GitHub normalisation (real fixtures)', () => { if (!latest) { throw new Error('The release fixture is unexpectedly empty.'); } - expect(latest.tagName).toBe('v5.0.0-prerelease.8'); + expect(latest.tagName).toBe('v5.0.0-prerelease.9'); // The org publishes prerelease tags with the GitHub prerelease flag set to // false; classification must follow the semver tag, not the flag. expect(latest.prerelease).toBe(false); @@ -191,7 +193,7 @@ describe('project/package association and release summaries', () => { const telemetry = projects.find((project) => project.id === 'telemetry-sourcegenerator'); expect(telemetry).toBeDefined(); const summary = projectReleaseSummary(telemetry!, cache!, 'fixture'); - expect(summary.latestRelease?.tagName).toBe('v5.0.0-prerelease.8'); + expect(summary.latestRelease?.tagName).toBe('v5.0.0-prerelease.9'); expect(summary.latestStableRelease?.tagName).toBe('v4.1.0'); expect(summary.repoDescription).toContain('source generator'); expect(summary.tags.length).toBeGreaterThan(0); @@ -200,7 +202,7 @@ describe('project/package association and release summaries', () => { (entry) => entry.packageId === 'Purview.Telemetry.SourceGenerator', ); expect(pkg?.latestStable).toBe('4.4.0'); - expect(pkg?.latestPrerelease).toBe('5.0.0-prerelease.8'); + expect(pkg?.latestPrerelease).toBe('5.0.0-prerelease.9'); expect(pkg?.totalDownloads).toBeGreaterThan(0); }); @@ -225,3 +227,79 @@ describe('project/package association and release summaries', () => { expect(summary.archived).toBe(true); }); }); + +describe('NuGet index lag reconciliation', () => { + const PACKAGE = 'Purview.Telemetry.SourceGenerator'; + const REPOSITORY = 'purview-dev/telemetry-sourcegenerator'; + + function laggedCache(): ReleaseCacheData { + const cache = readReleaseFixture('index'); + expect(cache).not.toBeNull(); + const releases = cache!.releases[REPOSITORY] ?? []; + const packageIndex = cache!.packages[PACKAGE] ?? { id: PACKAGE, versions: [] }; + // Drop the latest prerelease from the NuGet index while the GitHub release + // for it still exists, simulating the flat-container index lagging behind + // a just-completed release (publish runs before the GitHub release is cut). + return { + ...cache!, + releases: { + ...cache!.releases, + [REPOSITORY]: [ + parseGitHubRelease({ tag_name: 'v5.0.0-prerelease.9', prerelease: false }), + ...releases, + ], + }, + packages: { + ...cache!.packages, + [PACKAGE]: { + id: PACKAGE, + versions: packageIndex.versions.filter((version) => version !== '5.0.0-prerelease.9'), + }, + }, + }; + } + + test('surfaces a GitHub prerelease tag missing from the NuGet index', () => { + const projects = loadProjects(); + const telemetry = projects.find((project) => project.id === 'telemetry-sourcegenerator'); + expect(telemetry).toBeDefined(); + const summary = projectReleaseSummary(telemetry!, laggedCache(), 'fixture'); + const pkg = summary.packages.find((entry) => entry.packageId === PACKAGE); + expect(pkg?.latestPrerelease).toBe('5.0.0-prerelease.9'); + // The stable selection is unaffected by the lag. + expect(pkg?.latestStable).toBe('4.4.0'); + }); + + test('surfaces a GitHub stable tag missing from the NuGet index', () => { + const cache = laggedCache(); + cache.releases[REPOSITORY] = [ + parseGitHubRelease({ tag_name: 'v4.4.0', prerelease: false }), + ...(cache.releases[REPOSITORY] ?? []), + ]; + const packageIndex = cache.packages[PACKAGE] ?? { id: PACKAGE, versions: [] }; + cache.packages[PACKAGE] = { + id: PACKAGE, + versions: packageIndex.versions.filter((version) => version !== '4.4.0'), + }; + const projects = loadProjects(); + const telemetry = projects.find((project) => project.id === 'telemetry-sourcegenerator'); + expect(telemetry).toBeDefined(); + const summary = projectReleaseSummary(telemetry!, cache, 'fixture'); + const pkg = summary.packages.find((entry) => entry.packageId === PACKAGE); + expect(pkg?.latestStable).toBe('4.4.0'); + expect(pkg?.latestPrerelease).toBe('5.0.0-prerelease.9'); + }); + + test('does not invent versions for packages NuGet has never published', () => { + const cache = laggedCache(); + cache.packages[PACKAGE] = { id: PACKAGE, versions: [] }; + const projects = loadProjects(); + const telemetry = projects.find((project) => project.id === 'telemetry-sourcegenerator'); + expect(telemetry).toBeDefined(); + const summary = projectReleaseSummary(telemetry!, cache, 'fixture'); + const pkg = summary.packages.find((entry) => entry.packageId === PACKAGE); + expect(pkg?.latestStable).toBeNull(); + expect(pkg?.latestPrerelease).toBeNull(); + expect(pkg?.hasAnyRelease).toBe(false); + }); +});