diff --git a/.claude/skills/community-extensions-update/SKILL.md b/.claude/skills/community-extensions-update/SKILL.md new file mode 100644 index 000000000..10be43931 --- /dev/null +++ b/.claude/skills/community-extensions-update/SKILL.md @@ -0,0 +1,164 @@ +--- +name: community-extensions-update +description: Use when the user asks to refresh, update, or sync the Crossplane Community Extension Projects page (content/master/learn/community-extension-projects.md) with the current state of the crossplane-contrib GitHub organization. Fetches live repo data, reconciles adds/removes against the page, categorizes new entries, and writes the updated page. +--- + +# Update the Community Extension Projects page + +The community extension projects page lists every public, non-archived repo under the `crossplane-contrib` GitHub org, grouped into categories. The list drifts out of date as repos are added, archived, or renamed. This skill reconciles the page against the live org state. + +Target file: `content/master/learn/community-extension-projects.md` +Data source script: `scripts/discover-community-extensions.sh` + +## Procedure + +### 1. Fetch the live repo list + +Run the script and capture its JSON output: + +```bash +./scripts/discover-community-extensions.sh > /tmp/contrib-repos.json +``` + +Each entry has `name`, `url`, `description`, `topics`, and `fork`. The list is already sorted by `name` and only contains public, non-archived repos. + +### 2. Compute the diff against the current page + +Extract the names currently listed on the page and compare to the API output: + +```bash +grep -oE '\[([a-z0-9-]+)\]\(https://github.com/crossplane-contrib/' \ + content/master/learn/community-extension-projects.md \ + | sed 's/\[//; s/\](.*//' | sort -u > /tmp/contrib-from-page.txt + +jq -r '.[].name' /tmp/contrib-repos.json | sort > /tmp/contrib-from-api.txt + +# To add (in API but not on page): +comm -23 /tmp/contrib-from-api.txt /tmp/contrib-from-page.txt + +# To remove (on page but not in API — archived, renamed, deleted): +comm -13 /tmp/contrib-from-api.txt /tmp/contrib-from-page.txt +``` + +Show both lists to the user before editing so they can sanity-check, especially the removals. + +### 3. Categorize new entries + +The page has these sections, in this order: + +- **Providers** — repos that expose external APIs as Crossplane managed resources +- **Functions** — composition functions +- **Configurations** — opinionated configuration packages +- **Tools and utilities** — CLIs, GitHub Actions, libraries, plugins +- **Dashboard** — UIs for visualizing/operating Crossplane + +Apply these heuristics in order: + +| Pattern | Category | +| --- | --- | +| name starts with `provider-` | Providers | +| name contains `provider` (e.g. `crossplane-provider-*`) | Providers | +| name starts with `function-` | Functions | +| name starts with `configuration-` | Configurations | +| description/topics indicate a UI or dashboard | Dashboard | +| anything else | **ask the user** before placing | + +Do **not** silently default ambiguous entries to "Tools and utilities". When the heuristics don't give a confident answer, ask the user — citing the repo's description and topics — and let them pick the section. Examples of historically ambiguous entries: `crossview` (Dashboard, not Tools), `xprin` (testing framework → Tools), repos with empty/null descriptions. + +### 4. Determine removals + +Removals come from the "in page only" diff. These are repos that have been archived, renamed, or deleted upstream. Confirm removals with the user before deleting — a renamed repo should be replaced (e.g., `provider-cloudflare` → `provider-upjet-cloudflare`), not just dropped. + +### 5. Rewrite the section bodies + +For each affected section: + +- Alphabetize the full list using the **target-based** sort defined below. +- Format each line as `- [{{name}}](https://github.com/crossplane-contrib/{{name}})`. +- Keep the section's surrounding `` / `` comments intact. +- Keep the section header (e.g., `## Providers`) and the blank line after it intact. + +#### Alphabetization rule + +Repos are sorted by **target** — the part of the name that identifies what the extension is *for*, not by the full repo name. Qualifiers like `upjet-`, `jet-`, and `crossplane-` aren't part of the target. + +To compute the target, strip the longest matching prefix from the repo name: + +| Section | Strip these prefixes (longest match wins) | +| --- | --- | +| Providers | `crossplane-provider-`, `provider-upjet-`, `provider-jet-`, `provider-` | +| Functions | `function-` | +| Configurations | `configuration-` | +| Tools and utilities | none — sort by full name | +| Dashboard | none — sort by full name | + +Then: + +1. **Primary sort**: by target, lexically (case-insensitive ASCII order — `LC_ALL=C` semantics). +2. **Tiebreak (same target)**: shortest full repo name first, i.e., the form closest to the bare target. This puts `provider-aws` before `provider-upjet-aws`, and `provider-newrelic` before `crossplane-provider-newrelic`. + +Worked examples (Providers): + +- `provider-aws`, `provider-upjet-aws` → both target `aws`. They sort adjacent in the A's, with `provider-aws` first (shorter). +- `crossplane-provider-castai` → target `castai`, sits between `provider-capi` and `provider-civo`. +- `provider-jet-ec`, `provider-upjet-ec` → both target `ec`. Adjacent in the E's, jet- form first (shorter). +- `provider-upjet-cloudflare` → target `cloudflare`, sits between `provider-civo` and `provider-confluent` — **not** down in the U's. +- `provider-upjet-gcp` (target `gcp`) sorts before `provider-gcp-beta` (target `gcp-beta`) because `gcp` < `gcp-beta` lexically. `-beta` is **not** a stripped suffix; it's part of the target. + +When in doubt about whether a substring is a qualifier or part of the target, ask the user — don't invent new strip rules. + +### 6. Preserve page structure + +Do **not** rewrite anything outside the bulleted lists: + +- The Hugo front matter (`---` block at the top) stays as-is. +- The intro paragraph and `{{< hint "note" >}}` block stay as-is. +- The `` / `= YES` comments around the hint block stay as-is. +- Section ordering stays as-is: Providers, Functions, Configurations, Tools and utilities, Dashboard. + +Prefer `Edit` with the smallest possible old/new strings — one Edit per section that changed — over rewriting the whole file. This keeps the diff reviewable. + +### 7. Copy to the latest versioned docs + +The latest released docs version tracks `master` for this page. Determine the version from `config.yaml`'s `latest:` key, then copy the freshly edited file over: + +```bash +LATEST_VERSION=$(grep -E '^\s+latest:' config.yaml | sed -E 's/.*"([^"]+)".*/\1/') +cp content/master/learn/community-extension-projects.md \ + "content/v${LATEST_VERSION}/learn/community-extension-projects.md" +``` + +Confirm the destination path exists before copying — if the file isn't already at `content/v${LATEST_VERSION}/learn/community-extension-projects.md`, stop and surface that to the user rather than creating a new file in an unexpected location. + +### 8. Verify + +After editing and copying: + +```bash +# Re-extract names from each page and compare to the API list. +for f in \ + content/master/learn/community-extension-projects.md \ + "content/v${LATEST_VERSION}/learn/community-extension-projects.md"; do + echo "=== $f ===" + grep -oE '\[([a-z0-9-]+)\]\(https://github.com/crossplane-contrib/' "$f" \ + | sed 's/\[//; s/\](.*//' | sort -u > /tmp/contrib-from-page-after.txt + diff /tmp/contrib-from-api.txt /tmp/contrib-from-page-after.txt && echo "OK" +done + +# Confirm master and the latest version are byte-identical. +diff content/master/learn/community-extension-projects.md \ + "content/v${LATEST_VERSION}/learn/community-extension-projects.md" +``` + +A clean diff on each page means it matches the org; a clean diff between the two pages means the copy succeeded. If anything shows up, investigate before claiming done. + +### 9. Stop at the file edit + +Do not stage, commit, or push. Surface the changed files and the summary of adds/removes/recategorizations to the user; they decide what to do next. + +## Notes + +- The script requires `gh` (authenticated) and `jq`. If either is missing it fails with a clear message. +- 100+ public repos exist in the org but ~85 are non-archived; expect that range. +- If the script returns zero or wildly off counts, stop and surface the error rather than wiping the page. +- This skill updates two paths: `content/master/learn/community-extension-projects.md` and the matching file under `content/v${LATEST_VERSION}/learn/...` where `${LATEST_VERSION}` comes from `config.yaml`. Older versioned copies (anything other than `master` and the current `latest`) are frozen historical snapshots and should not be touched. diff --git a/.gitignore b/.gitignore index b9319f3bc..c960b8d89 100644 --- a/.gitignore +++ b/.gitignore @@ -85,6 +85,6 @@ jekyll_files/* ###################### tmp/* -# AI stuff +# AI stuff - ignore user-specific files, commit shared skills/commands CLAUDE.md -.claude +/.claude/settings.local.json diff --git a/content/master/learn/community-extension-projects.md b/content/master/learn/community-extension-projects.md index 1dee3936a..dc0081583 100644 --- a/content/master/learn/community-extension-projects.md +++ b/content/master/learn/community-extension-projects.md @@ -34,23 +34,23 @@ use by Crossplane adopters. - [provider-capi](https://github.com/crossplane-contrib/provider-capi) - [crossplane-provider-castai](https://github.com/crossplane-contrib/crossplane-provider-castai) - [provider-civo](https://github.com/crossplane-contrib/provider-civo) -- [provider-cloudflare](https://github.com/crossplane-contrib/provider-cloudflare) -- [provider-cloudinit](https://github.com/crossplane-contrib/provider-cloudinit) +- [provider-upjet-cloudflare](https://github.com/crossplane-contrib/provider-upjet-cloudflare) - [provider-confluent](https://github.com/crossplane-contrib/provider-confluent) - [provider-upjet-digitalocean](https://github.com/crossplane-contrib/provider-upjet-digitalocean) +- [provider-dynatrace](https://github.com/crossplane-contrib/provider-dynatrace) - [provider-jet-ec](https://github.com/crossplane-contrib/provider-jet-ec) - [provider-upjet-ec](https://github.com/crossplane-contrib/provider-upjet-ec) - [provider-upjet-edgeadc](https://github.com/crossplane-contrib/provider-upjet-edgeadc) - [provider-jet-equinix](https://github.com/crossplane-contrib/provider-jet-equinix) -- [provider-gcp-beta](https://github.com/crossplane-contrib/provider-gcp-beta) - [provider-upjet-gcp](https://github.com/crossplane-contrib/provider-upjet-gcp) +- [provider-gcp-beta](https://github.com/crossplane-contrib/provider-gcp-beta) - [provider-gitlab](https://github.com/crossplane-contrib/provider-gitlab) - [provider-upjet-github](https://github.com/crossplane-contrib/provider-upjet-github) - [provider-helm](https://github.com/crossplane-contrib/provider-helm) - [provider-http](https://github.com/crossplane-contrib/provider-http) -- [provider-ibm-cloud](https://github.com/crossplane-contrib/provider-ibm-cloud) -- [provider-infoblox-nios](https://github.com/crossplane-contrib/provider-infoblox-nios) - [provider-influxdb](https://github.com/crossplane-contrib/provider-influxdb) +- [provider-infoblox-nios](https://github.com/crossplane-contrib/provider-infoblox-nios) +- [provider-k3s](https://github.com/crossplane-contrib/provider-k3s) - [provider-kafka](https://github.com/crossplane-contrib/provider-kafka) - [provider-upjet-kafka](https://github.com/crossplane-contrib/provider-upjet-kafka) - [provider-keycloak](https://github.com/crossplane-contrib/provider-keycloak) @@ -58,7 +58,6 @@ use by Crossplane adopters. - [provider-kubernetes](https://github.com/crossplane-contrib/provider-kubernetes) - [provider-mongodbatlas](https://github.com/crossplane-contrib/provider-mongodbatlas) - [provider-upjet-mysql](https://github.com/crossplane-contrib/provider-upjet-mysql) -- [provider-newrelic](https://github.com/crossplane-contrib/provider-newrelic) - [crossplane-provider-newrelic](https://github.com/crossplane-contrib/crossplane-provider-newrelic) - [provider-nop](https://github.com/crossplane-contrib/provider-nop) - [provider-okta](https://github.com/crossplane-contrib/provider-okta) @@ -67,15 +66,15 @@ use by Crossplane adopters. - [provider-palette](https://github.com/crossplane-contrib/provider-palette) - [provider-jet-rancher](https://github.com/crossplane-contrib/provider-jet-rancher) - [provider-redpanda](https://github.com/crossplane-contrib/provider-redpanda) -- [provider-secret](https://github.com/crossplane-contrib/provider-secret) +- [provider-sonarqube](https://github.com/crossplane-contrib/provider-sonarqube) - [provider-spotify](https://github.com/crossplane-contrib/provider-spotify) - [provider-sql](https://github.com/crossplane-contrib/provider-sql) - [provider-styra](https://github.com/crossplane-contrib/provider-styra) - [provider-talos](https://github.com/crossplane-contrib/provider-talos) - [provider-tencentcloud](https://github.com/crossplane-contrib/provider-tencentcloud) - [provider-terraform](https://github.com/crossplane-contrib/provider-terraform) -- [provider-jet-vault](https://github.com/crossplane-contrib/provider-jet-vault) - [provider-workflows](https://github.com/crossplane-contrib/provider-workflows) +- [provider-upjet-zitadel](https://github.com/crossplane-contrib/provider-upjet-zitadel) - [provider-zpa](https://github.com/crossplane-contrib/provider-zpa) @@ -85,9 +84,11 @@ use by Crossplane adopters. - [function-auto-ready](https://github.com/crossplane-contrib/function-auto-ready) - [function-cel-filter](https://github.com/crossplane-contrib/function-cel-filter) - [function-cue](https://github.com/crossplane-contrib/function-cue) +- [function-deletion-protection](https://github.com/crossplane-contrib/function-deletion-protection) - [function-dummy](https://github.com/crossplane-contrib/function-dummy) - [function-environment-configs](https://github.com/crossplane-contrib/function-environment-configs) - [function-extra-resources](https://github.com/crossplane-contrib/function-extra-resources) +- [function-gitlab-importer](https://github.com/crossplane-contrib/function-gitlab-importer) - [function-go-templating](https://github.com/crossplane-contrib/function-go-templating) - [function-hcl](https://github.com/crossplane-contrib/function-hcl) - [function-kcl](https://github.com/crossplane-contrib/function-kcl) @@ -115,10 +116,13 @@ use by Crossplane adopters. - [crossplane-diff](https://github.com/crossplane-contrib/crossplane-diff) - [crossplane-lint](https://github.com/crossplane-contrib/crossplane-lint) - [ess-plugin-vault](https://github.com/crossplane-contrib/ess-plugin-vault) +- [resource-state-metrics](https://github.com/crossplane-contrib/resource-state-metrics) - [setup-crossplane-action](https://github.com/crossplane-contrib/setup-crossplane-action) +- [typescript-models](https://github.com/crossplane-contrib/typescript-models) - [x-generation](https://github.com/crossplane-contrib/x-generation) - [xp-testing](https://github.com/crossplane-contrib/xp-testing) - [xpkg-action](https://github.com/crossplane-contrib/xpkg-action) +- [xprin](https://github.com/crossplane-contrib/xprin) ## Dashboard diff --git a/content/v2.2/learn/community-extension-projects.md b/content/v2.2/learn/community-extension-projects.md index 30f3e0b66..dc0081583 100644 --- a/content/v2.2/learn/community-extension-projects.md +++ b/content/v2.2/learn/community-extension-projects.md @@ -34,23 +34,23 @@ use by Crossplane adopters. - [provider-capi](https://github.com/crossplane-contrib/provider-capi) - [crossplane-provider-castai](https://github.com/crossplane-contrib/crossplane-provider-castai) - [provider-civo](https://github.com/crossplane-contrib/provider-civo) -- [provider-cloudflare](https://github.com/crossplane-contrib/provider-cloudflare) -- [provider-cloudinit](https://github.com/crossplane-contrib/provider-cloudinit) +- [provider-upjet-cloudflare](https://github.com/crossplane-contrib/provider-upjet-cloudflare) - [provider-confluent](https://github.com/crossplane-contrib/provider-confluent) - [provider-upjet-digitalocean](https://github.com/crossplane-contrib/provider-upjet-digitalocean) +- [provider-dynatrace](https://github.com/crossplane-contrib/provider-dynatrace) - [provider-jet-ec](https://github.com/crossplane-contrib/provider-jet-ec) - [provider-upjet-ec](https://github.com/crossplane-contrib/provider-upjet-ec) - [provider-upjet-edgeadc](https://github.com/crossplane-contrib/provider-upjet-edgeadc) - [provider-jet-equinix](https://github.com/crossplane-contrib/provider-jet-equinix) -- [provider-gcp-beta](https://github.com/crossplane-contrib/provider-gcp-beta) - [provider-upjet-gcp](https://github.com/crossplane-contrib/provider-upjet-gcp) +- [provider-gcp-beta](https://github.com/crossplane-contrib/provider-gcp-beta) - [provider-gitlab](https://github.com/crossplane-contrib/provider-gitlab) - [provider-upjet-github](https://github.com/crossplane-contrib/provider-upjet-github) - [provider-helm](https://github.com/crossplane-contrib/provider-helm) - [provider-http](https://github.com/crossplane-contrib/provider-http) -- [provider-ibm-cloud](https://github.com/crossplane-contrib/provider-ibm-cloud) -- [provider-infoblox-nios](https://github.com/crossplane-contrib/provider-infoblox-nios) - [provider-influxdb](https://github.com/crossplane-contrib/provider-influxdb) +- [provider-infoblox-nios](https://github.com/crossplane-contrib/provider-infoblox-nios) +- [provider-k3s](https://github.com/crossplane-contrib/provider-k3s) - [provider-kafka](https://github.com/crossplane-contrib/provider-kafka) - [provider-upjet-kafka](https://github.com/crossplane-contrib/provider-upjet-kafka) - [provider-keycloak](https://github.com/crossplane-contrib/provider-keycloak) @@ -58,7 +58,6 @@ use by Crossplane adopters. - [provider-kubernetes](https://github.com/crossplane-contrib/provider-kubernetes) - [provider-mongodbatlas](https://github.com/crossplane-contrib/provider-mongodbatlas) - [provider-upjet-mysql](https://github.com/crossplane-contrib/provider-upjet-mysql) -- [provider-newrelic](https://github.com/crossplane-contrib/provider-newrelic) - [crossplane-provider-newrelic](https://github.com/crossplane-contrib/crossplane-provider-newrelic) - [provider-nop](https://github.com/crossplane-contrib/provider-nop) - [provider-okta](https://github.com/crossplane-contrib/provider-okta) @@ -67,15 +66,15 @@ use by Crossplane adopters. - [provider-palette](https://github.com/crossplane-contrib/provider-palette) - [provider-jet-rancher](https://github.com/crossplane-contrib/provider-jet-rancher) - [provider-redpanda](https://github.com/crossplane-contrib/provider-redpanda) -- [provider-secret](https://github.com/crossplane-contrib/provider-secret) +- [provider-sonarqube](https://github.com/crossplane-contrib/provider-sonarqube) - [provider-spotify](https://github.com/crossplane-contrib/provider-spotify) - [provider-sql](https://github.com/crossplane-contrib/provider-sql) - [provider-styra](https://github.com/crossplane-contrib/provider-styra) - [provider-talos](https://github.com/crossplane-contrib/provider-talos) - [provider-tencentcloud](https://github.com/crossplane-contrib/provider-tencentcloud) - [provider-terraform](https://github.com/crossplane-contrib/provider-terraform) -- [provider-jet-vault](https://github.com/crossplane-contrib/provider-jet-vault) - [provider-workflows](https://github.com/crossplane-contrib/provider-workflows) +- [provider-upjet-zitadel](https://github.com/crossplane-contrib/provider-upjet-zitadel) - [provider-zpa](https://github.com/crossplane-contrib/provider-zpa) @@ -85,9 +84,11 @@ use by Crossplane adopters. - [function-auto-ready](https://github.com/crossplane-contrib/function-auto-ready) - [function-cel-filter](https://github.com/crossplane-contrib/function-cel-filter) - [function-cue](https://github.com/crossplane-contrib/function-cue) +- [function-deletion-protection](https://github.com/crossplane-contrib/function-deletion-protection) - [function-dummy](https://github.com/crossplane-contrib/function-dummy) - [function-environment-configs](https://github.com/crossplane-contrib/function-environment-configs) - [function-extra-resources](https://github.com/crossplane-contrib/function-extra-resources) +- [function-gitlab-importer](https://github.com/crossplane-contrib/function-gitlab-importer) - [function-go-templating](https://github.com/crossplane-contrib/function-go-templating) - [function-hcl](https://github.com/crossplane-contrib/function-hcl) - [function-kcl](https://github.com/crossplane-contrib/function-kcl) @@ -115,14 +116,17 @@ use by Crossplane adopters. - [crossplane-diff](https://github.com/crossplane-contrib/crossplane-diff) - [crossplane-lint](https://github.com/crossplane-contrib/crossplane-lint) - [ess-plugin-vault](https://github.com/crossplane-contrib/ess-plugin-vault) +- [resource-state-metrics](https://github.com/crossplane-contrib/resource-state-metrics) - [setup-crossplane-action](https://github.com/crossplane-contrib/setup-crossplane-action) +- [typescript-models](https://github.com/crossplane-contrib/typescript-models) - [x-generation](https://github.com/crossplane-contrib/x-generation) - [xp-testing](https://github.com/crossplane-contrib/xp-testing) - [xpkg-action](https://github.com/crossplane-contrib/xpkg-action) +- [xprin](https://github.com/crossplane-contrib/xprin) ## Dashboard - [crossview](https://github.com/crossplane-contrib/crossview) - + \ No newline at end of file diff --git a/scripts/discover-community-extensions.sh b/scripts/discover-community-extensions.sh new file mode 100755 index 000000000..0a20acbac --- /dev/null +++ b/scripts/discover-community-extensions.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# +# Fetch all public, non-archived repositories in the crossplane-contrib GitHub +# organization and emit a JSON array sorted by name. Each entry contains the +# fields the community-extensions-update skill needs to categorize and render +# the list on content/master/learn/community-extension-projects.md. +# +# Output schema: +# [ +# { +# "name": "provider-foo", +# "url": "https://github.com/crossplane-contrib/provider-foo", +# "description": "...", +# "topics": ["crossplane", "provider"], +# "fork": false +# }, +# ... +# ] +# +# Requires: gh (authenticated) and jq. + +set -euo pipefail + +ORG="crossplane-contrib" + +for cmd in gh jq; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "error: $cmd is required but not installed" >&2 + exit 1 + fi +done + +# --paginate walks every page; type=public excludes private repos. We then +# filter out archived repos and project the fields we care about. +gh api --paginate "orgs/${ORG}/repos?type=public&per_page=100" \ + | jq -s ' + add + | map(select(.archived == false)) + | map({ + name: .name, + url: .html_url, + description: .description, + topics: (.topics // []), + fork: .fork + }) + | sort_by(.name) + '