diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fe65ba13..0d386c3b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/examples/index.ts b/examples/index.ts index a0c30b1d..28039b53 100644 --- a/examples/index.ts +++ b/examples/index.ts @@ -1,6 +1,4 @@ /** - * StackOne AI provides a unified interface for accessing various SaaS tools through AI-friendly APIs. - * * # Installation * * ```bash @@ -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) */ diff --git a/scripts/build-docs.test.ts b/scripts/build-docs.test.ts new file mode 100644 index 00000000..59fcdcb6 --- /dev/null +++ b/scripts/build-docs.test.ts @@ -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= + * \`\`\` + */ + +// 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= +\`\`\` + +\`\`\`typescript +// Load environment variables from .env file +import * as dotenv from 'dotenv'; +dotenv.config(); +\`\`\``; + + const result = convertFileToMarkdown(input); + expect(result).toBe(expected); + }); +}); diff --git a/scripts/build-docs.ts b/scripts/build-docs.ts index 60db851f..96ed0155 100644 --- a/scripts/build-docs.ts +++ b/scripts/build-docs.ts @@ -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'; @@ -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'); }; @@ -106,4 +125,6 @@ const main = (): void => { }; // Run the script -main(); +if (require.main === module) { + main(); +}