Skip to content

Commit 45bb17e

Browse files
authored
Merge pull request #401 from storybookjs/get-storybook-versions-from-npm
Generate Storybook versions from npmjs
2 parents 6379d2a + 9c52e9c commit 45bb17e

8 files changed

Lines changed: 106 additions & 29 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ yarn-error.log*
3737
# Generated content
3838
apps/frontpage/content/docs
3939
apps/frontpage/content/snippets
40+
apps/frontpage/generated-redirects.json
41+
apps/frontpage/generated-versions.json
4042
apps/frontpage/public/docs-assets
4143
apps/frontpage/public/_redirects
4244

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ This project is structured around [Turborepo](https://turbo.build/repo). This do
2525

2626
> Generate redirects file
2727
28+
#### `npx turbo generate-versions`
29+
30+
> Generate Storybook version metadata from npm dist-tags
31+
2832
#### `npx turbo dev`
2933

3034
> Run all apps locally

apps/frontpage/app/versions/route.ts

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import crypto from 'node:crypto';
2-
import { readFile } from 'node:fs/promises';
3-
import path from 'node:path';
42
import { headers } from 'next/headers';
53
import type { NextRequest } from 'next/server';
64
import { BigQuery } from '@google-cloud/bigquery';
75
import { parse as semverParse } from 'semver';
8-
import { latestVersion } from '@repo/utils';
6+
import { readGeneratedVersions } from '../../lib/generated-versions';
97

108
// eslint-disable-next-line no-useless-escape, prefer-named-capture-group -- Escape is absolutely necessary for regex?
119
const SEP_REGEX = /([\.:])/;
@@ -15,13 +13,6 @@ const logger = { log: (..._msgs: unknown[]) => {} };
1513

1614
const { GCP_CREDENTIALS, SKIP_IP_HASH } = process.env;
1715

18-
const versionFilesDir = path.join(
19-
process.cwd(),
20-
'content/docs',
21-
latestVersion.id,
22-
'versions',
23-
);
24-
2516
const md5 = (host: string) => {
2617
const hash = crypto.createHash('md5');
2718
hash.update(host);
@@ -99,27 +90,10 @@ const log = async (searchParams: URLSearchParams) => {
9990
await table.insert([row]);
10091
};
10192

102-
type DistTag = 'latest' | 'next';
103-
10493
const versions = async () => {
10594
logger.log('fetching versions');
10695

107-
async function getVersionData(distTag: DistTag) {
108-
const data = JSON.parse(
109-
await readFile(
110-
new URL(path.join(versionFilesDir, `${distTag}.json`), import.meta.url),
111-
'utf8',
112-
),
113-
);
114-
// Strip off no-longer-used `info` property
115-
const { version } = data as { version: string };
116-
return { version };
117-
}
118-
119-
const latest = await getVersionData('latest');
120-
const next = await getVersionData('next');
121-
122-
return JSON.stringify({ latest, next });
96+
return JSON.stringify(await readGeneratedVersions());
12397
};
12498

12599
export async function GET(request: NextRequest) {
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import path from 'node:path';
2+
import { readFile, writeFile } from 'node:fs/promises';
3+
import { valid as isValidSemver } from 'semver';
4+
5+
export interface GeneratedVersion {
6+
version: string;
7+
}
8+
9+
export interface GeneratedVersions {
10+
latest: GeneratedVersion;
11+
next: GeneratedVersion;
12+
}
13+
14+
const DIST_TAGS_URL =
15+
'https://registry.npmjs.org/-/package/storybook/dist-tags';
16+
17+
export const generatedVersionsFilePath = path.join(
18+
process.cwd(),
19+
'generated-versions.json',
20+
);
21+
22+
function assertDistTagVersion(
23+
distTag: keyof GeneratedVersions,
24+
version: unknown,
25+
): GeneratedVersion {
26+
if (typeof version !== 'string' || !isValidSemver(version)) {
27+
throw new Error(
28+
`Expected a valid semver string for the ${distTag} dist-tag, received ${JSON.stringify(version)}`,
29+
);
30+
}
31+
32+
return { version };
33+
}
34+
35+
export async function fetchGeneratedVersions(): Promise<GeneratedVersions> {
36+
const response = await fetch(DIST_TAGS_URL, {
37+
headers: {
38+
Accept: 'application/json',
39+
'Cache-Control': 'no-cache',
40+
},
41+
});
42+
43+
if (!response.ok) {
44+
throw new Error(
45+
`Failed to fetch Storybook dist-tags from npm: ${String(response.status)} ${response.statusText}`,
46+
);
47+
}
48+
49+
const distTags = (await response.json()) as Partial<
50+
Record<keyof GeneratedVersions, unknown>
51+
>;
52+
53+
return {
54+
latest: assertDistTagVersion('latest', distTags.latest),
55+
next: assertDistTagVersion('next', distTags.next),
56+
};
57+
}
58+
59+
export async function readGeneratedVersions(): Promise<GeneratedVersions> {
60+
const fileContents = await readFile(generatedVersionsFilePath, 'utf8');
61+
const parsed = JSON.parse(fileContents) as Partial<
62+
Record<keyof GeneratedVersions, { version?: unknown }>
63+
>;
64+
65+
return {
66+
latest: assertDistTagVersion('latest', parsed.latest?.version),
67+
next: assertDistTagVersion('next', parsed.next?.version),
68+
};
69+
}
70+
71+
export async function writeGeneratedVersions(
72+
versions: GeneratedVersions,
73+
): Promise<void> {
74+
await writeFile(
75+
generatedVersionsFilePath,
76+
`${JSON.stringify(versions, null, 2)}\n`,
77+
'utf8',
78+
);
79+
}

apps/frontpage/next.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ module.exports = withBundleAnalyzer(
105105
*/
106106
outputFileTracingIncludes: {
107107
'/docs/**': ['./content/docs/**'],
108+
'/versions': ['./generated-versions.json'],
108109
'/md-api/**': ['./content/docs/**', './content/snippets/**'],
109110
},
110111
},

apps/frontpage/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"lint": "next lint",
1010
"typecheck": "tsc --noEmit",
1111
"fetch-docs": "tsx --tsconfig tsconfig.json scripts/get-local-docs.ts",
12+
"generate-versions": "tsx --tsconfig tsconfig.json scripts/generate-versions.ts",
1213
"generate-redirects": "tsx --tsconfig tsconfig.json scripts/generate-redirects.ts",
1314
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
1415
"storybook": "storybook dev -p 6006",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {
2+
fetchGeneratedVersions,
3+
writeGeneratedVersions,
4+
} from '../lib/generated-versions';
5+
6+
async function generate(): Promise<void> {
7+
const versions = await fetchGeneratedVersions();
8+
await writeGeneratedVersions(versions);
9+
}
10+
11+
void generate();

turbo.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
"outputs": ["./apps/frontpage/public/_redirects"],
3333
"cache": false
3434
},
35+
"generate-versions": {
36+
"cache": false,
37+
"outputs": ["./apps/frontpage/generated-versions.json"]
38+
},
3539
"fetch-docs": {
3640
"cache": false
3741
},
@@ -44,7 +48,7 @@
4448
"inputs": ["$TURBO_DEFAULT$", ".env"]
4549
},
4650
"build": {
47-
"dependsOn": ["fetch-docs", "generate-redirects", "^build"],
51+
"dependsOn": ["fetch-docs", "generate-redirects", "generate-versions", "^build"],
4852
"outputs": [
4953
"dist/**",
5054
".next/**",
@@ -54,6 +58,7 @@
5458
"inputs": [
5559
"$TURBO_DEFAULT$",
5660
".env",
61+
"./apps/frontpage/generated-versions.json",
5762
"./apps/frontpage/content/docs/**",
5863
"./apps/frontpage/content/snippets/**",
5964
"./apps/frontpage/public/docs-assets/**"

0 commit comments

Comments
 (0)