mirror of
https://github.com/docker/docs.git
synced 2026-11-04 10:01:00 +00:00
docs: add automated what's new highlights (#25729)
## Summary Replace the static Featured topics section with a data-driven Whatʼs new timeline. Show the five most important launches by default and let readers expand the full curated 30-day archive. Run a daily Claude Sonnet 5 curator over a rolling 30-day window, with manual dispatch support, deterministic validation, and bot-managed pull requests. Package the editorial workflow as the reusable `curate-whats-new` repository skill so Docker Agent and other Agent Skills-compatible clients can invoke the same process. Preserve the human-reviewed July 13 through August 11, 2026 result and flexible selection criteria as a regression example outside the skillʼs runtime context. @netlify / [Homepage preview](https://deploy-preview-25729--docsdocker.netlify.app/) Generated by Codex
This commit is contained in:
1 parent
58f1389442
commit
678542f456
12 files changed
+685
-45
No files matched your search
@@ -0,0 +1,74 @@
|
||||
---
|
||||
name: curate-whats-new
|
||||
description: Curate noteworthy Docker launches from documentation pull requests merged during a requested period. Use when generating or reviewing data/whats-new.json, preparing the Docker Docs What's new timeline, or deciding which documented releases merit Docker-wide highlights.
|
||||
---
|
||||
|
||||
# Curate What's New
|
||||
|
||||
Treat merged documentation as evidence that a capability shipped, but do not
|
||||
treat a documentation change as news by itself.
|
||||
|
||||
Include an item only when the merged documentation directly shows all of the
|
||||
following:
|
||||
|
||||
- A released user-facing feature, material enhancement, or broader availability
|
||||
milestone that was not available before the period
|
||||
- A substantial capability or workflow, not new syntax or a small control
|
||||
within an existing workflow
|
||||
- Enough Docker-wide editorial significance to merit proactively telling users
|
||||
about it outside product release notes
|
||||
- A useful published page and a factual title and description
|
||||
|
||||
Apply a high bar. The result is a curated launch archive, not a complete
|
||||
changelog. A specialized feature can qualify when its user impact is
|
||||
substantial. A quiet period can produce few or no items.
|
||||
|
||||
## Exclusions
|
||||
|
||||
Exclude documentation maintenance; fixes; rewrites; guidance for old behavior;
|
||||
routine release or generated-content syncs; limitations, prerequisites, and
|
||||
workarounds; narrow flags, settings, command variants, protocols, and
|
||||
compatibility changes; incremental UI, safety, permissions, or observability
|
||||
improvements; and lower-level Engine, Build, networking, or storage changes.
|
||||
These qualify only when they are part of an independently newsworthy
|
||||
product-level launch.
|
||||
|
||||
Judge the user outcome, not PR size, product popularity, labels, changed lines,
|
||||
a dedicated page, or the existence of a new API or command.
|
||||
|
||||
## Select highlights
|
||||
|
||||
Include every qualifying launch; do not impose a quota. Mark the five most
|
||||
important as `featured: true`, or all items when fewer than five qualify. Rank
|
||||
by the magnitude and distinctness of the user outcome and the value of helping
|
||||
its audience discover it. Breadth can matter, but a major capability for a
|
||||
specialized audience can outrank a smaller change for a broad audience. Recency
|
||||
and product variety are not ranking goals.
|
||||
|
||||
Create one item per launch and combine PRs that document the same launch.
|
||||
Preserve existing copy while it remains accurate and qualifies. Change featured
|
||||
status only when the relative importance of the candidate set changes.
|
||||
|
||||
## Procedure
|
||||
|
||||
1. Read `data/whats-new.json`.
|
||||
2. List every PR merged in the requested period:
|
||||
|
||||
```console
|
||||
$ gh pr list --repo docker/docs --state merged --search 'merged:START..END' --limit 200
|
||||
```
|
||||
|
||||
3. Inspect the diff and resulting pages for every plausible candidate.
|
||||
4. Decide what qualifies using only evidence in the merged documentation.
|
||||
5. Replace `period_start`, `period_end`, and `items` in
|
||||
`data/whats-new.json`. Sort items by `published` date, newest first.
|
||||
6. Write `.pr-body.md` with the publication period, selected highlights and
|
||||
source PRs, plus concise reasons for plausible exclusions.
|
||||
|
||||
Each item must contain `product`, `title`, `description`, `url`, `published`,
|
||||
`source_prs`, and `featured`. Use the canonical product name, a published
|
||||
internal URL, the merge date in `YYYY-MM-DD` format, and source PR numbers.
|
||||
|
||||
Write factual, restrained copy. Avoid superlatives, promotional language, and
|
||||
claims about ease or importance. Do not modify tracked files other than
|
||||
`data/whats-new.json`.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Curate What's New"
|
||||
short_description: "Curate noteworthy Docker launches from merged docs"
|
||||
default_prompt: "Use $curate-whats-new to curate Docker launches published during the requested date range."
|
||||
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"period_start": "2026-07-13",
|
||||
"period_end": "2026-08-11",
|
||||
"items": [
|
||||
{
|
||||
"product": "Docker Desktop",
|
||||
"title": "Use Docker VMM on Mac and Windows",
|
||||
"description": "Run Docker Desktop with Docker's container-optimized hypervisor on supported Mac and Windows systems.",
|
||||
"url": "/desktop/features/vmm/",
|
||||
"published": "2026-08-10",
|
||||
"source_prs": [25702, 25726],
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"product": "Docker Sandboxes",
|
||||
"title": "Connect sandboxes through an MCP gateway",
|
||||
"description": "Register local or remote MCP servers on the host, reuse them across sandboxes, and govern server and tool access with Cedar policies.",
|
||||
"url": "/ai/sandboxes/mcp-gateway/",
|
||||
"published": "2026-08-06",
|
||||
"source_prs": [25559, 25707],
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"product": "Docker Sandboxes",
|
||||
"title": "Author version 2 sandbox kits",
|
||||
"description": "Define kit setup, permissions, networking, and API key or OAuth credential requirements with the version 2 kit schema.",
|
||||
"url": "/ai/sandboxes/customize/kit-reference/#schema-versions",
|
||||
"published": "2026-08-06",
|
||||
"source_prs": [25467, 25707],
|
||||
"featured": false
|
||||
},
|
||||
{
|
||||
"product": "Docker Sandboxes",
|
||||
"title": "Run Claude Code with local models",
|
||||
"description": "Route Claude Code sandbox requests to the bundled local-model server or an existing Ollama installation.",
|
||||
"url": "/ai/sandboxes/agents/claude-code/#use-a-local-model",
|
||||
"published": "2026-08-06",
|
||||
"source_prs": [25641, 25707],
|
||||
"featured": false
|
||||
},
|
||||
{
|
||||
"product": "Docker AI Governance",
|
||||
"title": "Search and forward audit events",
|
||||
"description": "Search and export policy decisions from Docker Cloud, or forward events to Splunk, Dynatrace, and custom HTTPS endpoints.",
|
||||
"url": "/ai/sandboxes/governance/audit/",
|
||||
"published": "2026-08-03",
|
||||
"source_prs": [25679, 25687],
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"product": "Docker Desktop",
|
||||
"title": "Install from the Microsoft Store without administrator privileges",
|
||||
"description": "Fresh Microsoft Store installations on Windows use per-user mode by default.",
|
||||
"url": "/desktop/release-notes/#4850",
|
||||
"published": "2026-08-03",
|
||||
"source_prs": [25665],
|
||||
"featured": false
|
||||
},
|
||||
{
|
||||
"product": "Docker Sandboxes",
|
||||
"title": "Share agent skills across sandboxes",
|
||||
"description": "Import skills from supported host agents into a persistent store that sandboxes can share.",
|
||||
"url": "/ai/sandboxes/workflows/#share-agent-skills",
|
||||
"published": "2026-07-24",
|
||||
"source_prs": [25588],
|
||||
"featured": false
|
||||
},
|
||||
{
|
||||
"product": "Docker Sandboxes",
|
||||
"title": "Connect editors and desktop apps over SSH",
|
||||
"description": "Use a sandbox from VS Code, Cursor, Claude Desktop, ChatGPT, or another SSH-capable tool.",
|
||||
"url": "/ai/sandboxes/integrations/",
|
||||
"published": "2026-07-24",
|
||||
"source_prs": [25557],
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"product": "Docker Hardened Images",
|
||||
"title": "Query the DHI catalog from AI assistants",
|
||||
"description": "Search DHI repositories, inspect images, retrieve SBOMs, check CVEs, and manage mirrors through the DHI MCP server.",
|
||||
"url": "/dhi/tools/mcp/",
|
||||
"published": "2026-07-23",
|
||||
"source_prs": [25589],
|
||||
"featured": false
|
||||
},
|
||||
{
|
||||
"product": "Docker Enterprise",
|
||||
"title": "Authenticate GitHub Actions with OIDC connections",
|
||||
"description": "Exchange GitHub OIDC tokens for short-lived access to Docker resources without storing long-lived workflow credentials.",
|
||||
"url": "/enterprise/security/oidc-connections/",
|
||||
"published": "2026-07-21",
|
||||
"source_prs": [25336],
|
||||
"featured": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# Human-reviewed example and regression criteria for the curate-whats-new skill.
|
||||
# This file is not loaded into the skill's runtime context.
|
||||
period_start: 2026-07-13
|
||||
period_end: 2026-08-11
|
||||
example_output: 2026-07-13--2026-08-11.json
|
||||
|
||||
expected_archive:
|
||||
- Docker VMM on Mac and Windows
|
||||
- Docker Sandboxes MCP gateway
|
||||
- Docker Sandboxes kit schema version 2
|
||||
- Claude Code with local models
|
||||
- Docker AI Governance audit search, export, and forwarding
|
||||
- Microsoft Store per-user installation
|
||||
- Shared agent skills across sandboxes
|
||||
- Docker Sandboxes editor and app integrations
|
||||
- Docker Hardened Images MCP server
|
||||
- Enterprise OIDC connections
|
||||
|
||||
featured_guidance:
|
||||
strong:
|
||||
- Docker VMM on Mac and Windows
|
||||
- Docker AI Governance audit search, export, and forwarding
|
||||
- Docker Sandboxes editor and app integrations
|
||||
- Enterprise OIDC connections
|
||||
either_is_reasonable:
|
||||
- Docker Sandboxes MCP gateway
|
||||
- Docker Sandboxes kit schema version 2
|
||||
|
||||
must_exclude:
|
||||
- Gordon safety modes and custom rules
|
||||
- Swarm direct server return networking
|
||||
- Engine image mounts
|
||||
- Unix sockets on Windows
|
||||
- Narrow flags, command variants, protocol options, and compatibility switches
|
||||
|
||||
notes:
|
||||
- Do not require product diversity.
|
||||
- Treat featured choices as editorial judgment, not an exact golden ordering.
|
||||
- Reject lower-level or incremental changes even when they are technically new.
|
||||
@@ -0,0 +1,34 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/docker/docker-agent/refs/heads/main/agent-schema.json
|
||||
models:
|
||||
claude-sonnet-5:
|
||||
provider: anthropic
|
||||
model: claude-sonnet-5
|
||||
max_tokens: 4096
|
||||
temperature: 0.1
|
||||
|
||||
agents:
|
||||
root:
|
||||
model: claude-sonnet-5
|
||||
description: Curates user-facing Docker launches from recently published documentation
|
||||
instruction: |
|
||||
Use the curate-whats-new skill for every request.
|
||||
skills:
|
||||
- curate-whats-new
|
||||
|
||||
toolsets:
|
||||
- type: filesystem
|
||||
tools:
|
||||
- read_file
|
||||
- read_multiple_files
|
||||
- write_file
|
||||
- list_directory
|
||||
- directory_tree
|
||||
- type: shell
|
||||
|
||||
permissions:
|
||||
allow:
|
||||
- shell:cmd=gh pr list --*
|
||||
- shell:cmd=gh pr view --*
|
||||
- shell:cmd=gh pr diff --*
|
||||
- shell:cmd=git diff --*
|
||||
- shell:cmd=git log --*
|
||||
@@ -0,0 +1,160 @@
|
||||
name: Update What's New
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Review the previous 30 complete UTC days every day at 06:00 UTC.
|
||||
- cron: "0 6 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry-run:
|
||||
description: "Propose highlights without opening a pull request"
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: update-whats-new
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
BRANCH_NAME: bot/update-whats-new
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set publication period
|
||||
id: period
|
||||
run: |
|
||||
PERIOD_END=$(date -u -d 'yesterday' +%F)
|
||||
echo "start=$(date -u -d "$PERIOD_END - 29 days" +%F)" >> "$GITHUB_OUTPUT"
|
||||
echo "end=$PERIOD_END" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Configure AWS credentials
|
||||
id: aws-credentials
|
||||
continue-on-error: true
|
||||
uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::710015040892:role/docker-agent-action-20260409141318957000000001
|
||||
aws-region: us-east-1
|
||||
|
||||
- name: Fetch bot token
|
||||
if: steps.aws-credentials.outcome == 'success'
|
||||
run: |
|
||||
PAT=$(aws secretsmanager get-secret-value \
|
||||
--secret-id docker-agent-action/github-app \
|
||||
--query SecretString \
|
||||
--output text | jq -r '.pat')
|
||||
echo "::add-mask::$PAT"
|
||||
echo "GITHUB_APP_TOKEN=$PAT" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Restore open proposal
|
||||
env:
|
||||
GH_TOKEN: ${{ env.GITHUB_APP_TOKEN || github.token }}
|
||||
run: |
|
||||
EXISTING_PR=$(gh pr list --state open --head "$BRANCH_NAME" --json number --jq '.[0].number // empty')
|
||||
if [ -n "$EXISTING_PR" ]; then
|
||||
git fetch origin "$BRANCH_NAME:refs/remotes/origin/$BRANCH_NAME"
|
||||
git show "origin/$BRANCH_NAME:data/whats-new.json" > data/whats-new.json
|
||||
fi
|
||||
|
||||
- name: Curate highlights
|
||||
uses: docker/docker-agent-action@e96a4bb40cac114f64358621e1d08346c8eadc8c # v2.0.1
|
||||
env:
|
||||
GH_TOKEN: ${{ env.GITHUB_APP_TOKEN || github.token }}
|
||||
with:
|
||||
agent: ${{ github.workspace }}/.github/agents/whats-new.yaml
|
||||
prompt: >-
|
||||
Use the curate-whats-new skill to review documentation pull requests merged from
|
||||
${{ steps.period.outputs.start }} through
|
||||
${{ steps.period.outputs.end }}, inclusive.
|
||||
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
github-token: ${{ env.GITHUB_APP_TOKEN || github.token }}
|
||||
timeout: 1200
|
||||
|
||||
- name: Verify agent changes
|
||||
env:
|
||||
PERIOD_START: ${{ steps.period.outputs.start }}
|
||||
PERIOD_END: ${{ steps.period.outputs.end }}
|
||||
run: |
|
||||
CHANGED=$(git status --short | awk '$2 != "data/whats-new.json" && $2 != ".pr-body.md" { print }')
|
||||
if [ -n "$CHANGED" ]; then
|
||||
echo "::error::Agent changed files outside its allowed scope"
|
||||
echo "$CHANGED"
|
||||
exit 1
|
||||
fi
|
||||
test -f .pr-body.md
|
||||
npx prettier --check data/whats-new.json
|
||||
node hack/validate-whats-new.mjs data/whats-new.json "$PERIOD_START" "$PERIOD_END"
|
||||
|
||||
- name: Show proposal
|
||||
if: inputs.dry-run == true
|
||||
run: |
|
||||
git diff -- data/whats-new.json
|
||||
cat .pr-body.md
|
||||
|
||||
- name: Detect changes
|
||||
id: changes
|
||||
if: inputs.dry-run != true
|
||||
run: |
|
||||
if cmp -s \
|
||||
<(git show HEAD:data/whats-new.json | jq -S '.items') \
|
||||
<(jq -S '.items' data/whats-new.json); then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Close obsolete pull request
|
||||
if: steps.changes.outputs.changed == 'false'
|
||||
env:
|
||||
GH_TOKEN: ${{ env.GITHUB_APP_TOKEN || github.token }}
|
||||
run: |
|
||||
EXISTING_PR=$(gh pr list --state open --head "$BRANCH_NAME" --json url --jq '.[0].url // empty')
|
||||
if [ -n "$EXISTING_PR" ]; then
|
||||
gh pr close "$EXISTING_PR" \
|
||||
--comment "The latest sliding-window review no longer proposes changes to the published highlights."
|
||||
fi
|
||||
|
||||
- name: Commit changes
|
||||
if: steps.changes.outputs.changed == 'true'
|
||||
env:
|
||||
PERIOD_END: ${{ steps.period.outputs.end }}
|
||||
run: |
|
||||
git checkout -B "$BRANCH_NAME"
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add data/whats-new.json
|
||||
git commit -m "docs: update what's new for $PERIOD_END"
|
||||
|
||||
- name: Create or update pull request
|
||||
if: steps.changes.outputs.changed == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ env.GITHUB_APP_TOKEN || github.token }}
|
||||
run: |
|
||||
gh auth setup-git
|
||||
EXISTING_PR=$(gh pr list --state open --head "$BRANCH_NAME" --json url --jq '.[0].url // empty')
|
||||
|
||||
if [ -n "$EXISTING_PR" ]; then
|
||||
git push --force --set-upstream origin "$BRANCH_NAME"
|
||||
gh pr edit "$EXISTING_PR" \
|
||||
--title "docs: update what's new" \
|
||||
--body-file .pr-body.md
|
||||
else
|
||||
git push --force --set-upstream origin "$BRANCH_NAME"
|
||||
gh pr create \
|
||||
--title "docs: update what's new" \
|
||||
--body-file .pr-body.md \
|
||||
--base main \
|
||||
--head "$BRANCH_NAME"
|
||||
fi
|
||||
+1
-8
@@ -16,14 +16,7 @@ reference material for everyday development and operations tasks.
|
||||
- [Manuals](/manuals/): Install, configure, and use Docker products.
|
||||
- [Reference](/reference/): Browse CLI, API, and file format documentation.
|
||||
|
||||
## Featured topics
|
||||
|
||||
- [Docker Hardened Images](/dhi/)
|
||||
- [Get started with Docker Sandboxes](/ai/sandboxes/get-started/)
|
||||
- [Docker Desktop overview](/desktop/)
|
||||
- [Install Docker Engine](/engine/install/)
|
||||
- [Dockerfile reference](/reference/dockerfile/)
|
||||
- [Docker Build overview](/build/)
|
||||
{{< whats-new >}}
|
||||
|
||||
## Common questions
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"period_start": "2026-07-13",
|
||||
"period_end": "2026-08-11",
|
||||
"items": [
|
||||
{
|
||||
"product": "Docker Desktop",
|
||||
"title": "Use Docker VMM on Mac and Windows",
|
||||
"description": "Run Docker Desktop with Docker's container-optimized hypervisor on supported Mac and Windows systems.",
|
||||
"url": "/desktop/features/vmm/",
|
||||
"published": "2026-08-10",
|
||||
"source_prs": [25702, 25726],
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"product": "Docker Sandboxes",
|
||||
"title": "Connect sandboxes through an MCP gateway",
|
||||
"description": "Register local or remote MCP servers on the host, reuse them across sandboxes, and govern server and tool access with Cedar policies.",
|
||||
"url": "/ai/sandboxes/mcp-gateway/",
|
||||
"published": "2026-08-06",
|
||||
"source_prs": [25559, 25707],
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"product": "Docker Sandboxes",
|
||||
"title": "Author version 2 sandbox kits",
|
||||
"description": "Define kit setup, permissions, networking, and API key or OAuth credential requirements with the version 2 kit schema.",
|
||||
"url": "/ai/sandboxes/customize/kit-reference/#schema-versions",
|
||||
"published": "2026-08-06",
|
||||
"source_prs": [25467, 25707],
|
||||
"featured": false
|
||||
},
|
||||
{
|
||||
"product": "Docker Sandboxes",
|
||||
"title": "Run Claude Code with local models",
|
||||
"description": "Route Claude Code sandbox requests to the bundled local-model server or an existing Ollama installation.",
|
||||
"url": "/ai/sandboxes/agents/claude-code/#use-a-local-model",
|
||||
"published": "2026-08-06",
|
||||
"source_prs": [25641, 25707],
|
||||
"featured": false
|
||||
},
|
||||
{
|
||||
"product": "Docker AI Governance",
|
||||
"title": "Search and forward audit events",
|
||||
"description": "Search and export policy decisions from Docker Cloud, or forward events to Splunk, Dynatrace, and custom HTTPS endpoints.",
|
||||
"url": "/ai/sandboxes/governance/audit/",
|
||||
"published": "2026-08-03",
|
||||
"source_prs": [25679, 25687],
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"product": "Docker Desktop",
|
||||
"title": "Install from the Microsoft Store without administrator privileges",
|
||||
"description": "Fresh Microsoft Store installations on Windows use per-user mode by default.",
|
||||
"url": "/desktop/release-notes/#4850",
|
||||
"published": "2026-08-03",
|
||||
"source_prs": [25665],
|
||||
"featured": false
|
||||
},
|
||||
{
|
||||
"product": "Docker Sandboxes",
|
||||
"title": "Share agent skills across sandboxes",
|
||||
"description": "Import skills from supported host agents into a persistent store that sandboxes can share.",
|
||||
"url": "/ai/sandboxes/workflows/#share-agent-skills",
|
||||
"published": "2026-07-24",
|
||||
"source_prs": [25588],
|
||||
"featured": false
|
||||
},
|
||||
{
|
||||
"product": "Docker Sandboxes",
|
||||
"title": "Connect editors and desktop apps over SSH",
|
||||
"description": "Use a sandbox from VS Code, Cursor, Claude Desktop, ChatGPT, or another SSH-capable tool.",
|
||||
"url": "/ai/sandboxes/integrations/",
|
||||
"published": "2026-07-24",
|
||||
"source_prs": [25557],
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"product": "Docker Hardened Images",
|
||||
"title": "Query the DHI catalog from AI assistants",
|
||||
"description": "Search DHI repositories, inspect images, retrieve SBOMs, check CVEs, and manage mirrors through the DHI MCP server.",
|
||||
"url": "/dhi/tools/mcp/",
|
||||
"published": "2026-07-23",
|
||||
"source_prs": [25589],
|
||||
"featured": false
|
||||
},
|
||||
{
|
||||
"product": "Docker Enterprise",
|
||||
"title": "Authenticate GitHub Actions with OIDC connections",
|
||||
"description": "Exchange GitHub OIDC tokens for short-lived access to Docker resources without storing long-lived workflow credentials.",
|
||||
"url": "/enterprise/security/oidc-connections/",
|
||||
"published": "2026-07-21",
|
||||
"source_prs": [25336],
|
||||
"featured": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from "node:fs";
|
||||
|
||||
const [path, expectedStart, expectedEnd] = process.argv.slice(2);
|
||||
if (!expectedEnd) {
|
||||
throw new Error(`usage: ${process.argv[1]} FILE START END`);
|
||||
}
|
||||
|
||||
const data = JSON.parse(fs.readFileSync(path, "utf8"));
|
||||
const requiredItemKeys = [
|
||||
"product",
|
||||
"title",
|
||||
"description",
|
||||
"url",
|
||||
"published",
|
||||
"source_prs",
|
||||
"featured",
|
||||
];
|
||||
|
||||
if (data.period_start !== expectedStart) {
|
||||
throw new Error(`period_start must be ${expectedStart}`);
|
||||
}
|
||||
if (data.period_end !== expectedEnd) {
|
||||
throw new Error(`period_end must be ${expectedEnd}`);
|
||||
}
|
||||
if (!Array.isArray(data.items)) {
|
||||
throw new Error("items must be an array");
|
||||
}
|
||||
for (const [index, item] of data.items.entries()) {
|
||||
const missing = requiredItemKeys.filter((key) => !(key in item));
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`item ${index + 1} is missing: ${missing.join(", ")}`);
|
||||
}
|
||||
|
||||
for (const key of requiredItemKeys.slice(0, 4)) {
|
||||
if (typeof item[key] !== "string" || item[key].trim() === "") {
|
||||
throw new Error(`item ${index + 1} has an empty ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!item.url.startsWith("/") || item.url.startsWith("/manuals/")) {
|
||||
throw new Error(`item ${index + 1} url must be an internal published URL`);
|
||||
}
|
||||
if (
|
||||
typeof item.published !== "string" ||
|
||||
!/^\d{4}-\d{2}-\d{2}$/.test(item.published) ||
|
||||
new Date(`${item.published}T00:00:00Z`).toISOString().slice(0, 10) !==
|
||||
item.published
|
||||
) {
|
||||
throw new Error(`item ${index + 1} has an invalid published date`);
|
||||
}
|
||||
if (item.published < data.period_start || item.published > data.period_end) {
|
||||
throw new Error(`item ${index + 1} published date is outside the period`);
|
||||
}
|
||||
if (
|
||||
!Array.isArray(item.source_prs) ||
|
||||
item.source_prs.length === 0 ||
|
||||
!item.source_prs.every((number) => Number.isInteger(number) && number > 0)
|
||||
) {
|
||||
throw new Error(
|
||||
`item ${index + 1} source_prs must contain pull request numbers`,
|
||||
);
|
||||
}
|
||||
if (typeof item.featured !== "boolean") {
|
||||
throw new Error(`item ${index + 1} featured must be a boolean`);
|
||||
}
|
||||
}
|
||||
|
||||
const featuredCount = data.items.filter((item) => item.featured).length;
|
||||
const expectedFeaturedCount = Math.min(5, data.items.length);
|
||||
if (featuredCount !== expectedFeaturedCount) {
|
||||
throw new Error(
|
||||
`items must contain ${expectedFeaturedCount} featured highlights`,
|
||||
);
|
||||
}
|
||||
|
||||
const publishedDates = data.items.map((item) => item.published);
|
||||
const sortedDates = [...publishedDates].sort().reverse();
|
||||
if (publishedDates.join() !== sortedDates.join()) {
|
||||
throw new Error("items must be sorted by published date, newest first");
|
||||
}
|
||||
|
||||
const titles = data.items.map((item) => item.title);
|
||||
if (new Set(titles).size !== titles.length) {
|
||||
throw new Error("items must not contain duplicate titles");
|
||||
}
|
||||
|
||||
const urls = data.items.map((item) => item.url);
|
||||
if (new Set(urls).size !== urls.length) {
|
||||
throw new Error("items must not contain duplicate URLs");
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{{- $data := index hugo.Data "whats-new" -}}
|
||||
{{- with $data.items -}}
|
||||
{{- $items := sort . "published" "desc" -}}
|
||||
{{- $more := where $items "featured" false -}}
|
||||
<section class="py-10 md:py-12" x-data="{ expanded: false }">
|
||||
<div class="container mx-auto max-w-4xl px-4">
|
||||
<header class="mb-6 border-b border-gray-200 pb-4 dark:border-gray-800">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 dark:text-white">
|
||||
What's new
|
||||
</h2>
|
||||
</header>
|
||||
<ol>
|
||||
{{ range $items }}
|
||||
<li
|
||||
{{ if not .featured }}
|
||||
x-cloak x-show="expanded" x-collapse.duration.300ms
|
||||
{{ end }}
|
||||
class="grid grid-cols-[1fr] md:grid-cols-[5rem_1fr] md:gap-x-6"
|
||||
>
|
||||
<time
|
||||
class="hidden pt-5 text-right text-sm text-gray-500 md:block dark:text-gray-400"
|
||||
datetime="{{ .published }}"
|
||||
>{{ .published | time.Format "Jan 2" }}</time
|
||||
>
|
||||
<a
|
||||
href="{{ .url }}"
|
||||
class="group relative block border-l border-gray-200 py-5 pl-7 dark:border-gray-700"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="absolute top-6 -left-[5px] size-[9px] rounded-full bg-gray-300 ring-4 ring-gray-50 transition group-hover:bg-blue-500 dark:bg-gray-600 dark:ring-gray-950 dark:group-hover:bg-blue-400"
|
||||
></span>
|
||||
<span
|
||||
class="mb-1 flex flex-wrap items-center gap-x-2 text-sm font-medium text-blue-600 dark:text-blue-400"
|
||||
>
|
||||
{{ .product }}
|
||||
<time
|
||||
class="font-normal text-gray-500 md:hidden dark:text-gray-400"
|
||||
datetime="{{ .published }}"
|
||||
>{{ .published | time.Format "Jan 2" }}</time
|
||||
>
|
||||
</span>
|
||||
<span class="flex items-center gap-2">
|
||||
<span
|
||||
class="font-medium text-gray-900 transition group-hover:text-blue-500 dark:text-white dark:group-hover:text-blue-400"
|
||||
>{{ .title }}</span
|
||||
>
|
||||
<span
|
||||
class="icon-svg shrink-0 text-gray-400 transition group-hover:translate-x-1 group-hover:text-blue-500 dark:text-gray-500 dark:group-hover:text-blue-400"
|
||||
>
|
||||
{{ partialCached "icon" "arrow-right" "arrow-right" }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="mt-1 block text-sm text-gray-600 dark:text-gray-300"
|
||||
>{{ .description }}</span
|
||||
>
|
||||
</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ol>
|
||||
{{ with $more }}
|
||||
<button
|
||||
type="button"
|
||||
@click="expanded = !expanded"
|
||||
:aria-expanded="expanded"
|
||||
class="mt-4 ml-7 inline-flex items-center gap-1 text-sm font-medium text-blue-600 hover:text-blue-500 md:ml-[6.5rem] dark:text-blue-400 dark:hover:text-blue-300"
|
||||
>
|
||||
<span x-text="expanded ? 'Show less' : 'Show more'">Show more</span>
|
||||
<span class="icon-svg icon-sm" x-show="!expanded">
|
||||
{{ partialCached "icon" "chevron-down" "chevron-down" }}
|
||||
</span>
|
||||
<span class="icon-svg icon-sm" x-cloak x-show="expanded">
|
||||
{{ partialCached "icon" "chevron-up" "chevron-up" }}
|
||||
</span>
|
||||
</button>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,9 @@
|
||||
{{- $data := index hugo.Data "whats-new" -}}
|
||||
{{- with $data.items }}
|
||||
|
||||
## What's new
|
||||
|
||||
{{ range . }}
|
||||
{{ printf "- [%s: %s](%s): %s (%s)\n" .product .title .url .description (.published | time.Format "Jan 2, 2006") -}}
|
||||
{{ end }}
|
||||
{{- end }}
|
||||
+1
-37
@@ -116,43 +116,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Popular Topics Section -->
|
||||
<section class="py-8">
|
||||
<div class="container mx-auto max-w-3xl px-4">
|
||||
<h2
|
||||
class="mb-8 text-center text-2xl font-semibold text-gray-900 dark:text-white"
|
||||
>
|
||||
Featured topics
|
||||
</h2>
|
||||
<div
|
||||
class="divide-y divide-gray-200 rounded-xl border border-gray-200 bg-white dark:divide-gray-700 dark:border-gray-700 dark:bg-gray-900"
|
||||
>
|
||||
{{ range slice
|
||||
(dict "url" "/dhi/" "title" "Docker Hardened Images")
|
||||
(dict "url" "/ai/sandboxes/get-started/" "title" "Get started with Docker Sandboxes")
|
||||
(dict "url" "/desktop/" "title" "Docker Desktop overview")
|
||||
(dict "url" "/engine/install/" "title" "Install Docker Engine")
|
||||
(dict "url" "/reference/dockerfile/" "title" "Dockerfile reference")
|
||||
(dict "url" "/build/" "title" "Docker Build overview")
|
||||
}}
|
||||
<a
|
||||
href="{{ .url }}"
|
||||
class="group flex items-center justify-between px-4 py-3 transition hover:bg-blue-50 dark:hover:bg-gray-700"
|
||||
>
|
||||
<span
|
||||
class="text-gray-900 transition group-hover:text-blue-500 dark:text-white dark:group-hover:text-blue-400"
|
||||
>{{ .title }}</span
|
||||
>
|
||||
<span
|
||||
class="icon-svg text-gray-400 transition group-hover:translate-x-1 dark:text-gray-500"
|
||||
>
|
||||
{{ partialCached "icon" "arrow-right" "arrow-right" }}
|
||||
</span>
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{ partial "whats-new.html" . }}
|
||||
</main>
|
||||
<footer>{{ partialCached "footer.html" . }}</footer>
|
||||
</body>
|
||||
|
||||
Reference in new issue
Block a user