Skip to content
Closed
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
3 changes: 3 additions & 0 deletions .github/workflows/deploy-gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ jobs:
- name: Validate awards data
run: npm run validate:awards

- name: Validate launch metrics
run: npm run validate:launch-metrics

# Build for the GitHub Pages project URL. When the custom domain in
# static/CNAME is verified and active, update these values to
# SITE_URL=https://endusers.cncf.io and BASE_URL=/ so canonical and
Expand Down
78 changes: 60 additions & 18 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,50 @@
# Roadmap: endusers.cncf.io

This roadmap describes the planned evolution of the CNCF End User Community site.
It is a living document; update it as priorities change. Tracking issue: #45.
This roadmap describes the planned evolution of the CNCF End User Community
site. It is a living document; update it as priorities change. Tracking issue:
#45.

## Vision

Be the trusted home of the CNCF End User Community: the place where organizations
running cloud native in production find reference architectures, metrics, events,
and pathways to participate.
Be the trusted home of the CNCF End User Community: the place where
organizations running cloud native in production find reference architectures,
metrics, events, and pathways to participate.

## Guiding principles

- **Audience first**: content serves end users (adopters), not project contributors.
- **Audience first**: content serves end users (adopters), not project
contributors.
- **Data over prose**: metrics, awards, and architectures are generated from
authoritative sources (cncf/landscape, cncf/architecture, cncf/tab), never hand-edited.
authoritative sources (cncf/landscape, cncf/architecture, cncf/tab), never
hand-edited.
- **Verified facts**: every CNCF fact is backed by an authoritative source link.
- **Green pipeline**: the site must always deploy; a broken deploy blocks everything.
- **Green pipeline**: the site must always deploy; a broken deploy blocks
everything.

## Phase 0 — Foundation (current)

Goal: a reliable, trustworthy site skeleton.

- [ ] Restore a green deploy pipeline (blocked: see issue #44)
- [x] Add a LICENSE (PR #40, issue #32)
- [ ] Pin GitHub Actions by SHA and verify downloaded installers (issues #38, #39; PRs #41–#43)
- [ ] Shared validation utilities and test coverage for data pipelines (issue #36, PR #37)
- [ ] Pin GitHub Actions by SHA and verify downloaded installers (issues #38,
#39; PRs #41–#43)
- [ ] Shared validation utilities and test coverage for data pipelines (issue
#36, PR #37)
- [ ] This roadmap, plus GitHub milestones matching these phases (issue #45)

## Phase 1 — Content completeness

Goal: every pillar section is accurate, current, and self-maintaining.

- [ ] Architectures: automated import from cncf/architecture stays in sync (scheduled workflow exists; add freshness indicator on the page)
- [ ] Metrics: scheduled refresh of data/metrics.json with validation gating the build
- [ ] Awards: complete historical winner list, each entry verified against its cncf.io announcement
- [ ] Community: current TAB membership, End User Groups, and engagement pathways
- [ ] Architectures: automated import from cncf/architecture stays in sync
(scheduled workflow exists; add freshness indicator on the page)
- [ ] Metrics: scheduled refresh of data/metrics.json with validation gating the
build
- [ ] Awards: complete historical winner list, each entry verified against its
cncf.io announcement
- [ ] Community: current TAB membership, End User Groups, and engagement
pathways
- [ ] Events: upcoming end-user events at KubeCon + CloudNativeCon
- [ ] Blog: establish a publishing cadence beyond the welcome post

Expand All @@ -43,7 +53,8 @@ Goal: every pillar section is accurate, current, and self-maintaining.
Goal: the project can outlive any single maintainer.

- [ ] MAINTAINERS.md with an explicit process for adding maintainers (issue #47)
- [ ] Governance note describing review/merge expectations, including agent-automation policy
- [ ] Governance note describing review/merge expectations, including
agent-automation policy
- [ ] Good-first-issue curation to recruit human contributors
- [ ] Decide the long-term home of the site (issue #46): transfer to a CNCF org,
remain a personal staging site, or merge into an existing CNCF property
Expand All @@ -53,9 +64,40 @@ Goal: the project can outlive any single maintainer.
Goal: endusers.cncf.io becomes the authoritative end-user destination.

- [ ] DNS cutover to endusers.cncf.io (depends on Phase 2 ownership decision)
- [ ] Cross-linking with contribute.cncf.io and cncf.io (clear audience boundaries)
- [ ] Cross-linking with contribute.cncf.io and cncf.io (clear audience
boundaries)
- [ ] Public feedback loop: content-issue templates and a visible changelog

## Launch success metrics (tracking: #100)

Goal: launch is measurable, not just announced. `data/launch-metrics.json` is
the source of truth for the baseline (captured at the W-6 pre-launch checkpoint)
and 90-day post-launch target for each signal. The table below is **generated
from that file** — do not hand-edit it. Run `npm run sync:launch-metrics-table`
after changing the JSON, and `npm run validate:launch-metrics` fails the build
if the two drift apart.

<!-- LAUNCH-METRICS-TABLE:START -->

| Signal | Baseline (W-6 pre-launch checkpoint, 2026-08-08) | 90-day post-launch target |
| ---------------------------------------------------------- | ------------------------------------------------ | ------------------------- |
| GitHub stars | 0 | 50 |
| GitHub watchers | 0 | 15 |
| GitHub forks | 1 | 8 |
| Good-first-issues claimed or closed by a non-maintainer | 0 | 3 |
| Inbound links from cncf.io / contribute.cncf.io properties | 0 | 1 |

<!-- LAUNCH-METRICS-TABLE:END -->

- [ ] Re-measure each signal at the 90-day mark and record the outcome in
`data/launch-metrics.json`, then re-run
`npm run sync:launch-metrics-table`.
- [ ] Add site analytics (e.g. a privacy-respecting tool such as Plausible or
GoatCounter) so inbound-traffic signals can move beyond a manual check.

Related: #90 (launch strategy), #98 (good-first-issue curation), #104 (launch
window).

## Non-goals

- Contributor-facing documentation (belongs on contribute.cncf.io)
Expand All @@ -64,7 +106,7 @@ Goal: endusers.cncf.io becomes the authoritative end-user destination.

## How this roadmap is maintained

- Strategic gaps and reprioritizations are filed as GitHub issues by the strategist
agent and reviewed by the maintainer.
- Strategic gaps and reprioritizations are filed as GitHub issues by the
strategist agent and reviewed by the maintainer.
- Each phase maps to a GitHub milestone; issues and PRs are tagged accordingly.
- This document is updated by planning PRs, not ad hoc edits.
58 changes: 58 additions & 0 deletions data/launch-metrics.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"generated": false,
"note": "Hand-curated baseline snapshot for the site launch (issue #100). Unlike data/metrics.json, this is not refreshed by an automated pipeline — update it manually when a signal is re-measured, recording the new snapshot date. The ROADMAP.md launch-metrics table is generated FROM this file: run `npm run sync:launch-metrics-table` after any edit here, and `npm run validate:launch-metrics` fails if the two drift out of sync.",
"preLaunchCheckpointAt": "2026-08-08",
"checkpointLabel": "W-6 pre-launch checkpoint",
"inboundLinkMeasurementProcedure": "GitHub code search for the canonical repo URL restricted to the cncf.io/contribute.cncf.io properties' source repos: `gh search code \"cncf/endusers\" --owner cncf --repo cncf/foundation --repo cncf/contribute.cncf.io`. Re-run the identical query at the 90-day mark and record the resulting count as the new value; this keeps the measurement reproducible instead of an ad hoc manual search.",
"signals": [
{
"id": "github-stars",
"label": "GitHub stars",
"source": "https://github.com/cncf/endusers",
"historicalSource": "castrojo/endusers (pre-transfer GitHub account/repo path, not a live URL; account may be renamed/reused, so this is deliberately not rendered as a link; canonical repo is https://github.com/cncf/endusers)",
"baseline": 0,
"target90Day": 50,
"targetWindow": "90 days post-launch",
"note": "Repo-level engagement signal; cheapest to measure, easiest to game, tracked alongside the others rather than alone."
},
{
"id": "github-watchers",
"label": "GitHub watchers",
"source": "https://github.com/cncf/endusers",
"historicalSource": "castrojo/endusers (pre-transfer GitHub account/repo path, not a live URL; account may be renamed/reused, so this is deliberately not rendered as a link; canonical repo is https://github.com/cncf/endusers)",
"baseline": 0,
"target90Day": 15,
"targetWindow": "90 days post-launch",
"note": "Proxy for people who want ongoing visibility into the project, not just a one-time star."
},
{
"id": "github-forks",
"label": "GitHub forks",
"source": "https://github.com/cncf/endusers",
"historicalSource": "castrojo/endusers (pre-transfer GitHub account/repo path, not a live URL; account may be renamed/reused, so this is deliberately not rendered as a link; canonical repo is https://github.com/cncf/endusers)",
"baseline": 1,
"target90Day": 8,
"targetWindow": "90 days post-launch",
"note": "Baseline of 1 is the maintainer's own working fork, not an external contributor."
},
{
"id": "good-first-issue-conversion",
"label": "Good-first-issues claimed or closed by a non-maintainer",
"source": "https://github.com/cncf/endusers/labels/good%20first%20issue",
"historicalSource": "castrojo/endusers/labels/good%20first%20issue (pre-transfer GitHub account/repo path, not a live URL; account may be renamed/reused, so this is deliberately not rendered as a link; canonical repo is https://github.com/cncf/endusers)",
"baseline": 0,
"target90Day": 3,
"targetWindow": "90 days post-launch",
"note": "Directly measures whether curated on-ramp issues (issue #98) convert visitors into contributors."
},
{
"id": "inbound-links-cncf",
"label": "Inbound links from cncf.io / contribute.cncf.io properties",
"source": "see inboundLinkMeasurementProcedure above",
"baseline": 0,
"target90Day": 1,
"targetWindow": "90 days post-launch",
"note": "At least one authoritative CNCF property should link to this site by the 90-day mark; there is no analytics pipeline yet to measure traffic directly, so this uses the reproducible code-search procedure above until one is added."
}
]
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
"validate:architecture-assets": "node scripts/validate-architecture-assets.mjs",
"validate:button-contrast": "node scripts/validate-button-contrast.mjs",
"validate:awards": "node scripts/validate-awards.mjs",
"validate:launch-metrics": "node scripts/validate-launch-metrics.mjs",
"sync:launch-metrics-table": "node scripts/sync-launch-metrics-table.mjs",
"docusaurus": "docusaurus",
"fix:format": "npm run _check:format -- --write",
"fix": "npm run seq -- $(npm -s run _list:fix:*)",
Expand Down
34 changes: 34 additions & 0 deletions scripts/lib/launch-metrics-table.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Shared table-generation logic used by both
// scripts/sync-launch-metrics-table.mjs (writes ROADMAP.md) and
// scripts/validate-launch-metrics.mjs (checks ROADMAP.md hasn't drifted
// from data/launch-metrics.json without being re-synced).
export const START = '<!-- LAUNCH-METRICS-TABLE:START -->';
export const END = '<!-- LAUNCH-METRICS-TABLE:END -->';

export function buildTable(data) {
const checkpointLabel = data.checkpointLabel || 'baseline';
const checkpointAt = data.preLaunchCheckpointAt || data.baselineSnapshotAt;
const header = `| Signal | Baseline (${checkpointLabel}, ${checkpointAt}) | 90-day post-launch target |`;
const divider = '| --- | --- | --- |';
const rows = (data.signals || []).map(
(s) => `| ${s.label} | ${s.baseline} | ${s.target90Day} |`,
);
return [header, divider, ...rows].join('\n');
}

// Prettier re-pads table column widths for readability, so a byte-for-byte
// comparison between the generated table and what's committed would false-
// positive on drift after every `npx prettier --write`. Normalize by
// collapsing whitespace within each cell before comparing.
export function normalizeTable(markdown) {
return markdown
.split('\n')
.filter((line) => !/^\|[\s-]*\|[\s-:]*\|[\s-:]*\|$/.test(line.trim()))
.map((line) =>
line
.split('|')
.map((cell) => cell.trim())
.join('|'),
)
.join('\n');
}
32 changes: 32 additions & 0 deletions scripts/sync-launch-metrics-table.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env node
// Regenerates the launch-metrics table in ROADMAP.md from
// data/launch-metrics.json, which is the actual source of truth (issue
// #100 review feedback: don't let a hand-maintained table drift from the
// validated JSON). Run this after any edit to the JSON; CI verifies via
// validate-launch-metrics.mjs --check that the two haven't drifted apart.
import { readFileSync, writeFileSync } from 'node:fs';
import { buildTable, START, END } from './lib/launch-metrics-table.mjs';

const roadmapPath = new URL('../ROADMAP.md', import.meta.url);
const data = JSON.parse(
readFileSync(new URL('../data/launch-metrics.json', import.meta.url)),
);
const roadmap = readFileSync(roadmapPath, 'utf8');

const startIdx = roadmap.indexOf(START);
const endIdx = roadmap.indexOf(END);
if (startIdx === -1 || endIdx === -1 || endIdx < startIdx) {
console.error(
`Could not find ${START} / ${END} markers in ROADMAP.md — cannot sync table.`,
);
process.exit(1);
}

const before = roadmap.slice(0, startIdx + START.length);
const after = roadmap.slice(endIdx);
const updated = `${before}\n\n${buildTable(data)}\n\n${after}`;

writeFileSync(roadmapPath, updated);
console.log(
'Synced ROADMAP.md launch-metrics table from data/launch-metrics.json',
);
Loading