Skip to content

Commit edc5a3f

Browse files
authored
Merge branch 'main' into serverless-terraform
2 parents 4f6f85d + 9279218 commit edc5a3f

117 files changed

Lines changed: 2712 additions & 366 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.vale-ci.ini

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,56 @@ Temporal.programming = NO
1414
Temporal.WordList = NO
1515
Temporal.terms = NO
1616

17+
; Temporal.Headings and Temporal.RelativeLinks are intentionally left enabled
18+
; (not listed above) — they're the two high-confidence checks this CI gate runs.
19+
1720
; ignore MDX custom heading-ID comments, e.g. {/* #ha-worker-patterns */}
1821
TokenIgnores = (\{/\*[^\n]*?\*/\})
1922

23+
; These are reference docs whose headings are literal command/metric names
24+
; (e.g. "## audit-log", "### --workflow_id"), not prose — sentence-case
25+
; capitalization doesn't apply, so skip Temporal.Headings for them.
26+
[docs/references/sdk-metrics.mdx]
27+
Temporal.Headings = NO
28+
29+
[docs/tctl-v1/**]
30+
Temporal.Headings = NO
31+
32+
[docs/cli/command-reference/**]
33+
Temporal.Headings = NO
34+
35+
[docs/cloud/tcld/**]
36+
Temporal.Headings = NO
37+
38+
[docs/cloud/references/regions/**]
39+
Temporal.Headings = NO
40+
41+
[docs/cloud/metrics/reference.mdx]
42+
Temporal.Headings = NO
43+
44+
[docs/cloud/metrics/openmetrics/metrics-reference.mdx]
45+
Temporal.Headings = NO
46+
47+
[docs/references/cluster-metrics.mdx]
48+
Temporal.Headings = NO
49+
50+
[docs/references/configuration.mdx]
51+
Temporal.Headings = NO
52+
53+
[docs/references/web-ui-configuration.mdx]
54+
Temporal.Headings = NO
55+
56+
[docs/references/server-options.mdx]
57+
Temporal.Headings = NO
58+
59+
[docs/references/errors.mdx]
60+
Temporal.Headings = NO
61+
62+
[docs/references/commands.mdx]
63+
Temporal.Headings = NO
64+
65+
[docs/references/events.mdx]
66+
Temporal.Headings = NO
67+
2068
[formats]
2169
mdx = md

.vale.ini

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,51 @@ Google.Will = NO
2727
Microsoft.ComplexWords = NO
2828
Google.WordList = NO
2929

30+
; These are reference docs whose headings are literal command/metric names
31+
; (e.g. "## audit-log", "### --workflow_id"), not prose — sentence-case
32+
; capitalization doesn't apply, so skip Temporal.Headings for them.
33+
[docs/references/sdk-metrics.mdx]
34+
Temporal.Headings = NO
35+
36+
[docs/tctl-v1/**]
37+
Temporal.Headings = NO
38+
39+
[docs/cli/command-reference/**]
40+
Temporal.Headings = NO
41+
42+
[docs/cloud/tcld/**]
43+
Temporal.Headings = NO
44+
45+
[docs/cloud/references/regions/**]
46+
Temporal.Headings = NO
47+
48+
[docs/cloud/metrics/reference.mdx]
49+
Temporal.Headings = NO
50+
51+
[docs/cloud/metrics/openmetrics/metrics-reference.mdx]
52+
Temporal.Headings = NO
53+
54+
[docs/references/cluster-metrics.mdx]
55+
Temporal.Headings = NO
56+
57+
[docs/references/configuration.mdx]
58+
Temporal.Headings = NO
59+
60+
[docs/references/web-ui-configuration.mdx]
61+
Temporal.Headings = NO
62+
63+
[docs/references/server-options.mdx]
64+
Temporal.Headings = NO
65+
66+
[docs/references/errors.mdx]
67+
Temporal.Headings = NO
68+
69+
[docs/references/commands.mdx]
70+
Temporal.Headings = NO
71+
72+
[docs/references/events.mdx]
73+
Temporal.Headings = NO
74+
3075
[formats]
3176
mdx = md
3277

CONTRIBUTING.md

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,44 @@ If you are considering using a custom slug, answer these questions first:
162162
- Did you already check the redirects in `vercel.json`?
163163
- Is it in the page in a folder under the topic that makes the most sense?
164164

165+
# Social share images (og:image)
166+
167+
Every doc page automatically gets a social-share preview card generated at build time — the image that
168+
shows up when a link to the page is shared in Slack, X, LinkedIn, etc. It's built from the page's `title`,
169+
`description`, and a section label derived from its folder (e.g. `develop/go/...` → "Go SDK"). There's
170+
nothing to do for a normal page.
171+
172+
If you want a page to use a different image instead of the generated one, you have two options:
173+
174+
- Add an `image` field to the page's front matter, pointing at a static asset (same as the site-wide
175+
default in `docusaurus.config.js`):
176+
177+
```
178+
---
179+
title: My page
180+
image: /img/assets/my-custom-image.png
181+
---
182+
```
183+
184+
- For pages that can't use front matter, override it directly with a `<Head>` component:
185+
186+
```mdx
187+
import Head from '@docusaurus/Head';
188+
189+
<Head>
190+
<meta property="og:image" content="https://docs.temporal.io/img/assets/my-custom-image.png" />
191+
<meta name="twitter:image" content="https://docs.temporal.io/img/assets/my-custom-image.png" />
192+
</Head>
193+
```
194+
195+
The URL must be absolute (`https://docs.temporal.io/...`), not a relative path.
196+
197+
Either way, the generator leaves the page alone.
198+
199+
This only happens during `yarn build` (see below) — running `yarn start` won't show generated or
200+
overridden images, since it skips the production build step entirely. The generator itself lives in
201+
`plugins/og-image/`.
202+
165203
# Local development command reference
166204

167205
The following commands are available to aid in local development:
@@ -177,7 +215,15 @@ Note that the `/build` directory is ignored by Git.
177215

178216
## `yarn start`
179217

180-
This command spins up a local web server and serves the contents of the `/build` directory to [localhost:3000](http://localhost:3000/).
218+
This command spins up a local dev server with hot reload at [localhost:3000](http://localhost:3000/). It
219+
builds from source directly and does not run the production build step, so anything that only happens
220+
during `yarn build` (like generated og:images) won't show up here.
221+
222+
## `yarn serve`
223+
224+
This command serves the contents of the `/build` directory (the output of `yarn build`) at
225+
[localhost:3000](http://localhost:3000/), so you can check the actual production output, including
226+
generated og:images.
181227

182228
## `yarn snipsync`
183229

bin/check-og-build-budget.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env node
2+
3+
// Phase 6: catches build-time/output-size regressions in the og-image
4+
// plugin instead of relying on someone noticing a slow build or a bloated
5+
// deploy. Thresholds below were set from real measurements on the full
6+
// ~625-page docs set (see plugins/og-image/render.js's PNG compression
7+
// step, added specifically because the first measurement here came back
8+
// at 142MB/232KB-per-card): a cold build renders every card fresh in
9+
// ~86s total (~109ms/card) and produces ~17.7MB of PNGs (~29KB/card).
10+
// Thresholds give ~3x headroom over those numbers so normal content growth
11+
// doesn't trip this, while a real regression (e.g. a design change that
12+
// balloons per-card size, or an accidentally-quadratic render path) does.
13+
14+
const fs = require('fs');
15+
const path = require('path');
16+
17+
const STATS_FILE = path.join(process.cwd(), 'build', '.og-image-stats.json');
18+
19+
const MAX_AVG_RENDER_MS = 300;
20+
const MAX_TOTAL_OUTPUT_BYTES = 60 * 1024 * 1024;
21+
const MAX_AVG_IMAGE_BYTES = 80 * 1024;
22+
23+
function main() {
24+
if (!fs.existsSync(STATS_FILE)) {
25+
console.error(`Stats file not found at ${STATS_FILE}. Run \`yarn build\` first.`);
26+
process.exit(1);
27+
}
28+
29+
const stats = JSON.parse(fs.readFileSync(STATS_FILE, 'utf8'));
30+
const { generated, cached, renderMs, outputBytes } = stats;
31+
const totalImages = generated + cached;
32+
const avgRenderMs = generated > 0 ? renderMs / generated : 0;
33+
const avgImageBytes = totalImages > 0 ? outputBytes / totalImages : 0;
34+
35+
console.log(`[check-og-build-budget] ${generated} rendered this build, ${cached} from cache`);
36+
console.log(
37+
`[check-og-build-budget] render time: ${(renderMs / 1000).toFixed(1)}s total, ${avgRenderMs.toFixed(1)}ms/card avg (budget: ${MAX_AVG_RENDER_MS}ms/card)`,
38+
);
39+
console.log(
40+
`[check-og-build-budget] output size: ${(outputBytes / 1024 / 1024).toFixed(1)}MB total, ${(avgImageBytes / 1024).toFixed(1)}KB/card avg (budget: ${(MAX_TOTAL_OUTPUT_BYTES / 1024 / 1024).toFixed(0)}MB total, ${(MAX_AVG_IMAGE_BYTES / 1024).toFixed(0)}KB/card)`,
41+
);
42+
43+
const failures = [];
44+
if (generated > 0 && avgRenderMs > MAX_AVG_RENDER_MS) {
45+
failures.push(`average render time ${avgRenderMs.toFixed(1)}ms/card exceeds budget of ${MAX_AVG_RENDER_MS}ms/card`);
46+
}
47+
if (outputBytes > MAX_TOTAL_OUTPUT_BYTES) {
48+
failures.push(`total output size ${(outputBytes / 1024 / 1024).toFixed(1)}MB exceeds budget of ${(MAX_TOTAL_OUTPUT_BYTES / 1024 / 1024).toFixed(0)}MB`);
49+
}
50+
if (avgImageBytes > MAX_AVG_IMAGE_BYTES) {
51+
failures.push(`average image size ${(avgImageBytes / 1024).toFixed(1)}KB exceeds budget of ${(MAX_AVG_IMAGE_BYTES / 1024).toFixed(0)}KB`);
52+
}
53+
54+
if (failures.length > 0) {
55+
console.error(`\n${failures.length} budget check(s) failed:\n`);
56+
failures.forEach((f) => console.error(` - ${f}`));
57+
process.exit(1);
58+
}
59+
60+
console.log('\nOK: og-image build time and output size are within budget.');
61+
}
62+
63+
main();

bin/generate-og-gallery.js

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
#!/usr/bin/env node
2+
3+
// Dev-only tool: builds a single HTML page listing every generated og:image
4+
// card (thumbnail + section/title/route) so a large batch can be reviewed
5+
// in a browser at once, instead of opening 600+ PNGs one at a time. Not
6+
// part of the build pipeline — run manually after `yarn build`.
7+
8+
const fs = require('fs');
9+
const path = require('path');
10+
const matter = require('gray-matter');
11+
const ogImagePlugin = require('../plugins/og-image');
12+
13+
const DOCS_DIR = path.join(process.cwd(), 'docs');
14+
const BUILD_DIR = path.join(process.cwd(), 'build');
15+
const OUT_FILE = path.join(BUILD_DIR, '__og-gallery.html');
16+
17+
// Section grouping/labeling is purely a gallery-review concern now — the
18+
// generated card itself dropped the section pill in the Figma redesign, so
19+
// this doesn't live in plugins/og-image/index.js (the actual production
20+
// plugin) anymore, only here.
21+
const SDK_LABELS = {
22+
go: 'Go',
23+
python: 'Python',
24+
java: 'Java',
25+
typescript: 'TypeScript',
26+
dotnet: '.NET',
27+
php: 'PHP',
28+
ruby: 'Ruby',
29+
rust: 'Rust',
30+
};
31+
32+
const SECTION_OVERRIDES = {
33+
cli: 'CLI',
34+
'tctl-v1': 'tctl v1',
35+
};
36+
37+
function humanize(id) {
38+
return id
39+
.replace(/[-_]+/g, ' ')
40+
.replace(/\b\w/g, (c) => c.toUpperCase());
41+
}
42+
43+
function resolveSection(docsDir, filePath) {
44+
const rel = path.relative(docsDir, filePath).replace(/\\/g, '/');
45+
const segments = rel.split('/');
46+
if (segments.length === 1) return 'Docs';
47+
const top = segments[0];
48+
if (top === 'develop' && segments[1] && SDK_LABELS[segments[1]]) {
49+
return `${SDK_LABELS[segments[1]]} SDK`;
50+
}
51+
return SECTION_OVERRIDES[top] || humanize(top);
52+
}
53+
54+
async function getSiteUrl() {
55+
const createConfigAsync = require('../docusaurus.config.js');
56+
const config = await createConfigAsync();
57+
return config.url + (config.baseUrl || '/');
58+
}
59+
60+
function escapeHtml(str) {
61+
return String(str).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
62+
}
63+
64+
async function main() {
65+
if (!fs.existsSync(BUILD_DIR)) {
66+
console.error(`Build directory not found at ${BUILD_DIR}. Run \`yarn build\` first.`);
67+
process.exit(1);
68+
}
69+
70+
const siteUrl = await getSiteUrl();
71+
const cards = [];
72+
for (const filePath of ogImagePlugin.walkDir(DOCS_DIR)) {
73+
const raw = fs.readFileSync(filePath, 'utf8');
74+
const { data: frontmatter, content } = matter(raw);
75+
const urlPath = ogImagePlugin.resolveUrlPath(DOCS_DIR, filePath, frontmatter);
76+
const htmlPath = ogImagePlugin.htmlPathForUrlPath(BUILD_DIR, urlPath);
77+
if (!fs.existsSync(htmlPath)) continue;
78+
79+
const section = resolveSection(DOCS_DIR, filePath);
80+
const routePath = urlPath === 'index' ? '/' : `/${urlPath}`;
81+
82+
if (ogImagePlugin.hasManualOverride(frontmatter, content)) {
83+
const id = frontmatter.id || path.basename(filePath).replace(/\.(md|mdx)$/i, '');
84+
const title = ogImagePlugin.extractTitle(content, frontmatter, id);
85+
const overrideImage = ogImagePlugin.overrideImageFor(frontmatter, content, siteUrl);
86+
cards.push({ urlPath: routePath, section, title, isOverride: true, imgSrc: overrideImage });
87+
continue;
88+
}
89+
90+
const id = frontmatter.id || path.basename(filePath).replace(/\.(md|mdx)$/i, '');
91+
const title = ogImagePlugin.extractTitle(content, frontmatter, id);
92+
const description = frontmatter.description;
93+
const hash = ogImagePlugin.hashFor(title, description);
94+
95+
cards.push({ urlPath: routePath, section, title, isOverride: false, imgSrc: `/img/og/${hash}.${ogImagePlugin.IMAGE_EXTENSION}` });
96+
}
97+
98+
cards.sort((a, b) => a.section.localeCompare(b.section) || a.title.localeCompare(b.title));
99+
100+
const html = `<!doctype html>
101+
<html>
102+
<head>
103+
<meta charset="utf-8">
104+
<title>og:image gallery (${cards.length} pages)</title>
105+
<style>
106+
body { background: #0a0a0f; color: #e5e7eb; font-family: system-ui, sans-serif; margin: 0; padding: 24px; }
107+
h1 { font-size: 18px; font-weight: 600; margin: 0 0 16px; }
108+
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(960px, 1fr)); gap: 20px; }
109+
figure { margin: 0; background: #16161d; border-radius: 8px; overflow: hidden; border: 1px solid #26262f; }
110+
figure img { width: 100%; display: block; background: #000; }
111+
figcaption { padding: 10px 12px; font-size: 13px; line-height: 1.4; }
112+
.section { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase; color: #93c5fd; margin-bottom: 4px; }
113+
.override-badge { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase; color: #0a0a0f; background: #facc15; padding: 2px 8px; border-radius: 4px; margin-left: 6px; }
114+
.title { color: #f3f4f6; font-weight: 600; }
115+
.path { color: #6b7280; font-size: 12px; }
116+
a { color: inherit; }
117+
</style>
118+
</head>
119+
<body>
120+
<h1>${cards.length} og:image cards (${cards.filter((c) => c.isOverride).length} manual override(s))</h1>
121+
<div class="grid">
122+
${cards
123+
.map(
124+
(c) => ` <figure>
125+
<a href="${escapeHtml(c.urlPath)}" target="_blank"><img src="${escapeHtml(c.imgSrc)}" loading="lazy"></a>
126+
<figcaption>
127+
<span class="section">${escapeHtml(c.section)}</span>${c.isOverride ? '<span class="override-badge">Override</span>' : ''}<br>
128+
<span class="title">${escapeHtml(c.title)}</span><br>
129+
<span class="path">${escapeHtml(c.urlPath)}</span>
130+
</figcaption>
131+
</figure>`,
132+
)
133+
.join('\n')}
134+
</div>
135+
</body>
136+
</html>
137+
`;
138+
139+
fs.writeFileSync(OUT_FILE, html);
140+
console.log(`[generate-og-gallery] wrote ${cards.length} card(s) to ${path.relative(process.cwd(), OUT_FILE)}`);
141+
}
142+
143+
main().catch((e) => {
144+
console.error(e);
145+
process.exit(1);
146+
});

0 commit comments

Comments
 (0)