Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
34 changes: 34 additions & 0 deletions .github/scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules

# output
out
dist
*.tgz

# code coverage
coverage
*.lcov

# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# caches
.eslintcache
.cache
*.tsbuildinfo

# IntelliJ based IDEs
.idea

# Finder (MacOS) folder config
.DS_Store
111 changes: 111 additions & 0 deletions .github/scripts/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
---
description: Use Bun instead of Node.js, npm, pnpm, or vite.
globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json"
alwaysApply: false
---

Default to using Bun instead of Node.js.

- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
- Use `bun test` instead of `jest` or `vitest`
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
- Bun automatically loads .env, so don't use dotenv.

## APIs

- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
- `Bun.redis` for Redis. Don't use `ioredis`.
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
- `WebSocket` is built-in. Don't use `ws`.
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
- Bun.$`ls` instead of execa.

## Testing

Use `bun test` to run tests.

```ts#index.test.ts
import { test, expect } from "bun:test";

test("hello world", () => {
expect(1).toBe(1);
});
```

## Frontend

Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.

Server:

```ts#index.ts
import index from "./index.html"

Bun.serve({
routes: {
"/": index,
"/api/users/:id": {
GET: (req) => {
return new Response(JSON.stringify({ id: req.params.id }));
},
},
},
// optional websocket support
websocket: {
open: (ws) => {
ws.send("Hello, world!");
},
message: (ws, message) => {
ws.send(message);
},
close: (ws) => {
// handle close
}
},
development: {
hmr: true,
console: true,
}
})
```

HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.

```html#index.html
<html>
<body>
<h1>Hello, world!</h1>
<script type="module" src="./frontend.tsx"></script>
</body>
</html>
```

With the following `frontend.tsx`:

```tsx#frontend.tsx
import React from "react";

// import .css files directly and it works
import './index.css';

import { createRoot } from "react-dom/client";

const root = createRoot(document.body);

export default function Frontend() {
return <h1>Hello, world!</h1>;
}

root.render(<Frontend />);
```

Then, run index.ts

```sh
bun --hot ./index.ts
```

For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`.
15 changes: 15 additions & 0 deletions .github/scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# scripts

To install dependencies:

```bash
bun install
```

To run:

```bash
bun run
```

This project was created using `bun init` in bun v1.2.17. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
39 changes: 39 additions & 0 deletions .github/scripts/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

132 changes: 132 additions & 0 deletions .github/scripts/generate_code_samples.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#!/usr/bin/env bun

import { readdir, readFile, writeFile } from "fs/promises";
import path from "path";
import yaml from "js-yaml"; // for YAML support

const OPENAPI_URL =
"https://app.stainless.com/api/spec/documented/kernel/openapi.documented.yml";

const TARGET_DIRS = ["browsers", "apps"]; // adjust as needed

function toTitleCase(input: string): string {
if (!input) return "";
return input.charAt(0).toUpperCase() + input.slice(1).toLowerCase();
}

async function fetchOpenAPISpec() {
console.log(`📥 Fetching OpenAPI spec from ${OPENAPI_URL}`);
const res = await fetch(OPENAPI_URL!);
if (!res.ok)
throw new Error(`Failed to fetch OpenAPI spec: ${res.statusText}`);
const text = await res.text();

// Try JSON first, fallback to YAML
try {
return JSON.parse(text);
} catch {
return yaml.load(text);
}
}

async function getMdxFiles(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await getMdxFiles(fullPath)));
} else if (entry.isFile() && entry.name.endsWith(".mdx")) {
files.push(fullPath);
}
}
return files;
}

function extractCodeSamples(
spec: any,
endpoint: string,
method?: string
): string {
const pathItem = spec.paths?.[endpoint];
if (!pathItem) return `⚠️ No spec found for ${endpoint}`;

const blocks: string[] = [];

const methods = method ? [method.toLowerCase()] : Object.keys(pathItem); // all methods if not specified

for (const m of methods) {
const op = pathItem[m];
if (op?.["x-codeSamples"]) {
for (const sample of op["x-codeSamples"]) {
const rawLang = typeof sample.lang === "string" ? sample.lang : "";
const lang = rawLang.toLowerCase();

let fenceInfo: string;
if (["typescript", "ts", "javascript", "js"].includes(lang)) {
fenceInfo = "typescript Typescript";
} else if (rawLang) {
fenceInfo = `${lang} ${toTitleCase(rawLang)}`;
} else {
fenceInfo = "";
}

blocks.push(`\n\`\`\`${fenceInfo}\n${sample.source.trim()}\n\`\`\`\n`);
}
}
}

return blocks.length > 0
? blocks.join("\n")
: `⚠️ No code samples for ${
method ? method.toUpperCase() : "any"
} ${endpoint}`;
}

async function processFile(file: string, spec: any) {
let content = await readFile(file, "utf8");

// Matches {{ get /path }} or {{ post /path }} or {{ /path }}
const mustacheRegex =
/\{\{\s*(?:(get|post|put|delete|patch|options|head)\s+)?(\/[^\s}]+)\s*\}\}/gi;

// Matches <OpenAPICodeGroup>get /path</OpenAPICodeGroup>
// or <OpenAPICodeGroup>/path</OpenAPICodeGroup>
const tagRegex =
/<OpenAPICodeGroup>\s*(?:(get|post|put|delete|patch|options|head)\s+)?(\/[^\s<]+)\s*<\/OpenAPICodeGroup>/gi;

let changed = false;
content = content.replace(mustacheRegex, (_, method, endpoint) => {
changed = true;
return extractCodeSamples(spec, endpoint, method);
});

content = content.replace(tagRegex, (_, method, endpoint) => {
changed = true;
const blocks = extractCodeSamples(spec, endpoint, method).trim();
return `<CodeGroup>\n${blocks}\n</CodeGroup>`;
});

if (changed) {
console.log(`✏️ Updating ${file}`);
await writeFile(file, content, "utf8");
}
}

async function main() {
const spec = await fetchOpenAPISpec();

for (const dir of TARGET_DIRS) {
const files = await getMdxFiles(dir);
for (const file of files) {
await processFile(file, spec);
}
}

console.log("✅ Done updating docs");
}

main().catch((err) => {
console.error(err);
process.exit(1);
});
14 changes: 14 additions & 0 deletions .github/scripts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "scripts",
"private": true,
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
},
"dependencies": {
"@types/js-yaml": "^4.0.9",
"js-yaml": "^4.1.0"
}
}
29 changes: 29 additions & 0 deletions .github/scripts/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,

// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,

// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,

// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}
Loading