Skip to content

Commit c2d1c88

Browse files
committed
Set up helper to fetch SDK artifacts from Github
1 parent dbd39af commit c2d1c88

File tree

6 files changed

+154
-3
lines changed

6 files changed

+154
-3
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { withGithubSdk } from "@speakeasy-api/docs-md";
2+
3+
export default {
4+
spec: "../specs/glean.yaml",
5+
output: {
6+
pageOutDir: "./docs/glean/api",
7+
embedOutDir: "./src/components/glean-embeds",
8+
framework: "docusaurus",
9+
},
10+
display: {
11+
maxNestingLevel: 2,
12+
},
13+
codeSamples: [
14+
withGithubSdk(
15+
{
16+
language: "typescript",
17+
owner: "gleanwork",
18+
repo: "api-client-typescript",
19+
version: "v0.11.2",
20+
tryItNow: {
21+
outDir: "./public/glean-try-it-now",
22+
urlPrefix: "/glean-try-it-now",
23+
},
24+
},
25+
process.env.GITHUB_TOKEN
26+
),
27+
{
28+
language: "curl",
29+
},
30+
],
31+
};

packages/compiler/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
"@microsoft/api-extractor": "^7.52.15",
3737
"@speakeasy-api/docs-md-react": "0.2.36",
3838
"@speakeasy-api/docs-md-shared": "0.2.36",
39+
"@octokit/request-error": "^6.1.8",
40+
"@octokit/rest": "^21.0.2",
3941
"arg": "^5.0.2",
4042
"chalk": "^5.3.0",
4143
"change-case": "^5.4.4",
@@ -54,4 +56,4 @@
5456
"globals": "^16.3.0",
5557
"typescript": "^5.8.3"
5658
}
57-
}
59+
}

packages/compiler/src/cli/cli.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ async function getSettings(): Promise<Settings> {
132132
configFileImport.output ??= {};
133133

134134
// Parse the settings using Zod to ensure accuracy
135-
const configFileContents = settingsSchema.safeParse(configFileImport);
135+
const configFileContents =
136+
await settingsSchema.safeParseAsync(configFileImport);
136137
if (!configFileContents.success) {
137138
reportError(
138139
`Error parsing config file "${configFilePath}": ${z.prettifyError(configFileContents.error)}`

packages/compiler/src/compiler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { getSettings } from "./settings.ts";
22
export { escapeText } from "./renderers/util.ts";
3+
export { withGithubSdk } from "./fetchers/withGithub.ts";
34
export type { FrameworkConfig } from "./types/FrameworkConfig.ts";
45
export type { Settings } from "./settings.ts";
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { randomUUID } from "node:crypto";
2+
import { createWriteStream, mkdirSync } from "node:fs";
3+
import { tmpdir } from "node:os";
4+
import { join } from "node:path";
5+
import { pipeline } from "node:stream/promises";
6+
7+
import { RequestError } from "@octokit/request-error";
8+
import { Octokit } from "@octokit/rest";
9+
10+
import { error, info } from "../logging.ts";
11+
import type { CodeSampleWithSDK } from "../settings.ts";
12+
import { InternalError } from "../util/internalError.ts";
13+
14+
type GithubSdkConfig = {
15+
owner: string;
16+
repo: string;
17+
version: string;
18+
} & CodeSampleWithSDK;
19+
20+
export async function withGithubSdk(
21+
sdk: GithubSdkConfig,
22+
token?: string
23+
): Promise<CodeSampleWithSDK> {
24+
const githubToken = token ?? process.env.GITHUB_TOKEN;
25+
if (githubToken === undefined) {
26+
error(
27+
"GitHub Personal Access Token is required. Please pass to withGithubSdks or set GITHUB_TOKEN environment variable"
28+
);
29+
process.exit(1);
30+
}
31+
32+
const octokit = new Octokit({
33+
auth: githubToken,
34+
});
35+
36+
const { owner, repo } = sdk;
37+
38+
info(
39+
`Fetching ${sdk.language} SDK from ${owner}/${repo}@${sdk.version ? sdk.version : "latest"}`
40+
);
41+
42+
const tarballUrl = await getTarballUrl(octokit, owner, repo, sdk.version);
43+
44+
const tempDir = join(tmpdir(), `speakeasy-github-${randomUUID()}`);
45+
mkdirSync(tempDir, { recursive: true });
46+
47+
const sdkTarballPath = join(tempDir, `${repo}-${sdk.version}.tar.gz`);
48+
49+
const response = await fetch(tarballUrl, {
50+
headers: {
51+
Authorization: `token ${token}`,
52+
Accept: "application/vnd.github.v3+json",
53+
},
54+
});
55+
56+
if (!response.ok || !response.body) {
57+
error(
58+
`Failed to download archive for repo ${repo}}: ${response.status} ${response.statusText}`
59+
);
60+
process.exit(1);
61+
}
62+
63+
const fileStream = createWriteStream(sdkTarballPath);
64+
await pipeline(response.body as unknown as NodeJS.ReadableStream, fileStream);
65+
66+
return { ...sdk, sdkTarballPath };
67+
}
68+
69+
async function getTarballUrl(
70+
octokit: Octokit,
71+
owner: string,
72+
repo: string,
73+
version?: string
74+
): Promise<string> {
75+
try {
76+
const { data } =
77+
version && version !== "latest"
78+
? await octokit.repos.getReleaseByTag({
79+
owner,
80+
repo,
81+
tag: version,
82+
})
83+
: await octokit.repos.getLatestRelease({
84+
owner,
85+
repo,
86+
});
87+
88+
if (!data.tarball_url) {
89+
error(`No tarball URL found for release ${version} in ${owner}/${repo}`);
90+
process.exit(1);
91+
}
92+
return data.tarball_url;
93+
} catch (e) {
94+
if (e instanceof RequestError) {
95+
if (e.status === 404) {
96+
error(
97+
`Release ${version} not found in ${owner}/${repo}. Make sure the tag exists and the token has access to the repository.`
98+
);
99+
process.exit(1);
100+
}
101+
}
102+
throw new InternalError(
103+
`Failed to fetch artifact from Github: ${(e as Error).message}`
104+
);
105+
}
106+
}

packages/compiler/src/settings.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ export function setInternalSetting<Key extends keyof InternalSettings>(
4242
internalSettings[key] = value;
4343
}
4444

45+
// helper function for use with z.preprocess to unwrap promises for settings values
46+
async function valueOrPromise(value: unknown) {
47+
return await Promise.resolve(value);
48+
}
49+
4550
const sdkCommonProperties = {
4651
/**
4752
* The path to a tarball file of the SDK. Use this option if, for example,
@@ -162,7 +167,12 @@ const otherSdkLanguages = z.object({
162167
...sdkCommonProperties,
163168
});
164169

165-
const codeSample = z.union([curl, typescript, python, otherSdkLanguages]);
170+
const codeSampleWithSdk = z.preprocess(
171+
valueOrPromise,
172+
z.union([typescript, python, otherSdkLanguages])
173+
);
174+
const codeSample = z.union([curl, codeSampleWithSdk]);
175+
export type CodeSampleWithSDK = z.infer<typeof codeSampleWithSdk>;
166176

167177
export const settingsSchema = z.strictObject({
168178
/**

0 commit comments

Comments
 (0)