Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 3 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ jobs:
- name: Build and publish package
if: ${{ steps.release.outputs.release_created }}
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
bun build
bun publish
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
bun publish --access=public
10 changes: 5 additions & 5 deletions examples/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/**
* StackOne AI provides a unified interface for accessing various SaaS tools through AI-friendly APIs.
*
* # Installation
*
* ```bash
Expand Down Expand Up @@ -72,7 +70,9 @@ quickstart().catch(console.error);
*
* Check out some more examples:
*
* - OpenAI Integration (openai-integration.ts)
* - Error Handling (error-handling.ts)
* - File Uploads (file-uploads.ts)
* - [OpenAI Integration](openai-integration.md)
* - [AI SDK Integration](ai-sdk-integration.md)
* - [Error Handling](error-handling.md)
* - [File Uploads](file-uploads.md)
* - [Custom Base URL](custom-base-url.md)
*/
54 changes: 54 additions & 0 deletions scripts/build-docs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, expect, it } from 'bun:test';
import { convertFileToMarkdown } from './build-docs';

describe('convertFileToMarkdown', () => {
it('should properly convert docstrings to markdown', () => {
const input = `/**
* # Installation
*
* \`\`\`bash
* # Using npm
* npm install stackone-ai-node
* \`\`\`
*
* # Authentication
*
* Set the \`STACKONE_API_KEY\` environment variable:
*
* \`\`\`bash
* export STACKONE_API_KEY=<your-api-key>
* \`\`\`
*/

// Load environment variables from .env file
import * as dotenv from 'dotenv';
dotenv.config();`;

// Adjust the expected output to match our new format with extra spacing between sections
const expected = `# StackOne AI SDK

# Installation

\`\`\`bash
# Using npm
npm install stackone-ai-node
\`\`\`

# Authentication

Set the \`STACKONE_API_KEY\` environment variable:

\`\`\`bash
export STACKONE_API_KEY=<your-api-key>
\`\`\`

\`\`\`typescript
// Load environment variables from .env file
import * as dotenv from 'dotenv';
dotenv.config();
\`\`\``;

const result = convertFileToMarkdown(input);
expect(result).toBe(expected);
});
});
97 changes: 59 additions & 38 deletions scripts/build-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,24 @@ import path from 'node:path';
const DOCS_DIR = path.join(process.cwd(), '.docs');
const EXAMPLES_DIR = path.join(process.cwd(), 'examples');

interface JSDocBlock {
fullMatch: string;
content: string;
start: number;
end: number;
}

/**
* Convert a TypeScript file to markdown, preserving structure.
*/
const convertFileToMarkdown = (tsFile: string): string => {
const content = fs.readFileSync(tsFile, 'utf-8');
export const convertFileToMarkdown = (tsFile: string): string => {
// If the input is a multi-line string with JSDoc blocks, assume it's content
// Otherwise, assume it's a file path
const isContentString = tsFile.includes('/**') && tsFile.includes('*/');
const content = isContentString ? tsFile : fs.readFileSync(tsFile, 'utf-8');

// Add title from filename
const fileName = path.basename(tsFile, '.ts');
const fileName = isContentString ? 'index' : path.basename(tsFile, '.ts');
let title: string;
if (fileName === 'index') {
title = 'StackOne AI SDK';
Expand All @@ -21,52 +31,61 @@ const convertFileToMarkdown = (tsFile: string): string => {

const output: string[] = [`# ${title}\n`];

// Find all docstrings and their positions
// Match docstrings that start and end on their own lines
const docstringPattern = /(\n|\A)\s*\/\*\*(.*?)\*\/(\s*\n|\Z)/gs;
let currentPos = 0;

// Process all matches in the content
let match: RegExpExecArray | null = docstringPattern.exec(content);
while (match !== null) {
const [fullMatch, , docstringContent] = match;
const start = match.index;
const end = start + fullMatch.length;

// If there's code before this docstring, wrap it
if (currentPos < start) {
const code = content.substring(currentPos, start).trim();
if (code) {
output.push('\n```typescript');
output.push(code);
output.push('```\n');
}
}
// Extract JSDoc comments and the code between them
const jsDocRegex = /\/\*\*([\s\S]*?)\*\//g;
let jsDocMatch: RegExpExecArray | null;

// Array to store all JSDoc comments and their positions
const jsDocBlocks: JSDocBlock[] = [];

jsDocMatch = jsDocRegex.exec(content);
while (jsDocMatch !== null) {
jsDocBlocks.push({
fullMatch: jsDocMatch[0],
content: jsDocMatch[1],
start: jsDocMatch.index,
end: jsDocMatch.index + jsDocMatch[0].length,
});
jsDocMatch = jsDocRegex.exec(content);
}

// Process each JSDoc block and the code after it
for (let i = 0; i < jsDocBlocks.length; i++) {
const jsDoc = jsDocBlocks[i];

// Add the docstring content as markdown, removing asterisks from each line
const cleanedDocstring = docstringContent
// Process the docstring content (remove * from the beginning of lines)
const cleanedDocstring = jsDoc.content
.split('\n')
.map((line) => line.trim().replace(/^\s*\*\s?/, ''))
.join('\n')
.trim();

output.push(cleanedDocstring);
currentPos = end;
// Add an extra newline between sections if it starts with a header
if (cleanedDocstring.trim().startsWith('#') && i > 0) {
output.push(`\n${cleanedDocstring}`);
} else {
output.push(cleanedDocstring);
}

// Get the next match
match = docstringPattern.exec(content);
}
// Get the code between this JSDoc block and the next one (or the end of file)
const codeStart = jsDoc.end;
const codeEnd = i < jsDocBlocks.length - 1 ? jsDocBlocks[i + 1].start : content.length;

// Add any remaining code
if (currentPos < content.length) {
const remainingCode = content.substring(currentPos).trim();
if (remainingCode) {
const code = content.substring(codeStart, codeEnd).trim();
if (code) {
output.push('\n```typescript');
output.push(remainingCode);
output.push('```\n');
output.push(code);
output.push('```');
}
}

// If no JSDoc blocks were found, just add the content as code
if (jsDocBlocks.length === 0) {
output.push('\n```typescript');
output.push(content);
output.push('```');
}

return output.join('\n');
};

Expand Down Expand Up @@ -106,4 +125,6 @@ const main = (): void => {
};

// Run the script
main();
if (require.main === module) {
main();
}