chore: add readme to all our sdks (on npm)
This commit is contained in:
@@ -3,7 +3,8 @@
|
|||||||
"version": "1.0.6-local",
|
"version": "1.0.6-local",
|
||||||
"config": {
|
"config": {
|
||||||
"transformPackageJson": false,
|
"transformPackageJson": false,
|
||||||
"transformEnvs": true
|
"transformEnvs": true,
|
||||||
|
"docPath": "apps/public/content/docs/(tracking)/sdks/astro.mdx"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.ts"
|
".": "./index.ts"
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
"name": "@openpanel/express",
|
"name": "@openpanel/express",
|
||||||
"version": "1.0.4-local",
|
"version": "1.0.4-local",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
|
"config": {
|
||||||
|
"docPath": "apps/public/content/docs/(tracking)/sdks/express.mdx"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf dist && tsup",
|
"build": "rm -rf dist && tsup",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
"name": "@openpanel/nextjs",
|
"name": "@openpanel/nextjs",
|
||||||
"version": "1.1.2-local",
|
"version": "1.1.2-local",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
|
"config": {
|
||||||
|
"docPath": "apps/public/content/docs/(tracking)/sdks/nextjs.mdx"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf dist && tsup",
|
"build": "rm -rf dist && tsup",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
"files": ["dist"],
|
"files": ["dist"],
|
||||||
"config": {
|
"config": {
|
||||||
"transformPackageJson": false,
|
"transformPackageJson": false,
|
||||||
"transformEnvs": false
|
"transformEnvs": false,
|
||||||
|
"docPath": "apps/public/content/docs/(tracking)/sdks/nuxt.mdx"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npx nuxt-module-build build",
|
"build": "npx nuxt-module-build build",
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
"name": "@openpanel/react-native",
|
"name": "@openpanel/react-native",
|
||||||
"version": "1.0.4-local",
|
"version": "1.0.4-local",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
|
"config": {
|
||||||
|
"docPath": "apps/public/content/docs/(tracking)/sdks/react-native.mdx"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf dist && tsup",
|
"build": "rm -rf dist && tsup",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
"name": "@openpanel/sdk",
|
"name": "@openpanel/sdk",
|
||||||
"version": "1.0.3-local",
|
"version": "1.0.3-local",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
|
"config": {
|
||||||
|
"docPath": "apps/public/content/docs/(tracking)/sdks/javascript.mdx"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf dist && tsup",
|
"build": "rm -rf dist && tsup",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
"name": "@openpanel/web",
|
"name": "@openpanel/web",
|
||||||
"version": "1.0.6-local",
|
"version": "1.0.6-local",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
|
"config": {
|
||||||
|
"docPath": "apps/public/content/docs/(tracking)/sdks/web.mdx"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf dist && tsup",
|
"build": "rm -rf dist && tsup",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
|
|||||||
234
tooling/publish/generate-readme.ts
Normal file
234
tooling/publish/generate-readme.ts
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import { join, resolve } from 'node:path';
|
||||||
|
import { dirname } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
import type { PackageInfo } from './publish';
|
||||||
|
|
||||||
|
const workspacePath = (relativePath: string) =>
|
||||||
|
resolve(__dirname, '../../', relativePath);
|
||||||
|
|
||||||
|
const dedentContent = (text: string): string => {
|
||||||
|
const lines = text.split('\n');
|
||||||
|
if (lines.length === 0) return text;
|
||||||
|
|
||||||
|
// Find the minimum indentation (excluding empty lines)
|
||||||
|
// We'll dedent code blocks too, so include them in the calculation
|
||||||
|
let minIndent = Number.POSITIVE_INFINITY;
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
// Skip empty lines
|
||||||
|
if (trimmed.length === 0) continue;
|
||||||
|
const indent = line.match(/^(\s*)/)?.[1]?.length ?? 0;
|
||||||
|
if (indent < minIndent) minIndent = indent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no indentation found, return as-is
|
||||||
|
if (minIndent === Number.POSITIVE_INFINITY || minIndent === 0) return text;
|
||||||
|
|
||||||
|
// Remove the common indentation from all lines
|
||||||
|
return lines
|
||||||
|
.map((line) => {
|
||||||
|
// For lines shorter than minIndent, just return them as-is (preserves empty lines)
|
||||||
|
if (line.length < minIndent) return line;
|
||||||
|
// Remove the common indentation
|
||||||
|
const dedented = line.slice(minIndent);
|
||||||
|
// If the line was all whitespace, return empty string to preserve the line
|
||||||
|
return line.trim().length === 0 ? '' : dedented;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformMdxToReadme = (
|
||||||
|
mdxContent: string,
|
||||||
|
packageName: string,
|
||||||
|
): string => {
|
||||||
|
let content = mdxContent;
|
||||||
|
|
||||||
|
// Load MDX component content files
|
||||||
|
const commonSdkConfigPath = workspacePath(
|
||||||
|
'apps/public/src/components/common-sdk-config.mdx',
|
||||||
|
);
|
||||||
|
const webSdkConfigPath = workspacePath(
|
||||||
|
'apps/public/src/components/web-sdk-config.mdx',
|
||||||
|
);
|
||||||
|
|
||||||
|
let commonSdkConfigContent = '';
|
||||||
|
let webSdkConfigContent = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(commonSdkConfigPath)) {
|
||||||
|
commonSdkConfigContent = fs.readFileSync(commonSdkConfigPath, 'utf-8');
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore if file doesn't exist
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(webSdkConfigPath)) {
|
||||||
|
webSdkConfigContent = fs.readFileSync(webSdkConfigPath, 'utf-8');
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore if file doesn't exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract title from frontmatter
|
||||||
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
|
||||||
|
let title = packageName;
|
||||||
|
let description = '';
|
||||||
|
|
||||||
|
if (frontmatterMatch?.[1]) {
|
||||||
|
const frontmatter = frontmatterMatch[1];
|
||||||
|
const titleMatch = frontmatter.match(/^title:\s*(.+)$/m);
|
||||||
|
const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
|
||||||
|
if (titleMatch?.[1]) title = titleMatch[1].trim();
|
||||||
|
if (descMatch?.[1]) description = descMatch[1].trim();
|
||||||
|
|
||||||
|
// Remove frontmatter
|
||||||
|
content = content.replace(/^---\n[\s\S]*?\n---\n/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace MDX component references with their actual content
|
||||||
|
// This must happen before code block protection so component content is also protected
|
||||||
|
if (commonSdkConfigContent) {
|
||||||
|
content = content.replace(
|
||||||
|
/<CommonSdkConfig\s*\/>/g,
|
||||||
|
`\n${commonSdkConfigContent}\n`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (webSdkConfigContent) {
|
||||||
|
content = content.replace(
|
||||||
|
/<WebSdkConfig\s*\/>/g,
|
||||||
|
`\n${webSdkConfigContent}\n`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protect code blocks from transformation
|
||||||
|
// Extract code blocks before any transformations to preserve their content
|
||||||
|
const codeBlockPlaceholders: string[] = [];
|
||||||
|
// Match code blocks: ```language (optional) followed by content until closing ```
|
||||||
|
// Using [\s\S] to match across newlines, non-greedy to stop at first closing ```
|
||||||
|
const codeBlockRegex = /```[\s\S]*?```/g;
|
||||||
|
|
||||||
|
// Extract and replace code blocks with placeholders
|
||||||
|
content = content.replace(codeBlockRegex, (match) => {
|
||||||
|
const placeholder = `__CODE_BLOCK_${codeBlockPlaceholders.length}__`;
|
||||||
|
codeBlockPlaceholders.push(match);
|
||||||
|
return placeholder;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove import statements (outside code blocks)
|
||||||
|
content = content.replace(/^import\s+.*$/gm, '');
|
||||||
|
|
||||||
|
// Handle Tabs component specially - convert to markdown sections
|
||||||
|
// Extract tabs items from items prop
|
||||||
|
const tabsItemsMatch = content.match(/<Tabs\s+items=\{([^\}]+)\}>/);
|
||||||
|
const tabsItems = tabsItemsMatch?.[1]
|
||||||
|
? tabsItemsMatch[1]
|
||||||
|
.replace(/['"]/g, '')
|
||||||
|
.split(',')
|
||||||
|
.map((item) => item.trim())
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// Replace Tabs/Tab structure with markdown sections
|
||||||
|
if (tabsItems.length > 0) {
|
||||||
|
// Match each Tab and convert to a markdown section
|
||||||
|
content = content.replace(
|
||||||
|
/<Tab\s+value="([^"]+)">([\s\S]*?)<\/Tab>/g,
|
||||||
|
(match, value, tabContent) => {
|
||||||
|
const dedented = dedentContent(tabContent).trim();
|
||||||
|
return `\n#### ${value}\n\n${dedented}\n\n`;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// Remove the Tabs wrapper
|
||||||
|
content = content.replace(/<Tabs[^>]*>([\s\S]*?)<\/Tabs>/g, '$1');
|
||||||
|
} else {
|
||||||
|
// Fallback: if no items prop, just convert tabs to sections
|
||||||
|
content = content.replace(
|
||||||
|
/<Tab\s+value="([^"]+)">([\s\S]*?)<\/Tab>/g,
|
||||||
|
(match, value, tabContent) => {
|
||||||
|
const dedented = dedentContent(tabContent).trim();
|
||||||
|
return `\n#### ${value}\n\n${dedented}\n\n`;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
content = content.replace(/<Tabs[^>]*>([\s\S]*?)<\/Tabs>/g, '$1');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove self-closing JSX components (like <CommonSdkConfig />, <WebSdkConfig />)
|
||||||
|
content = content.replace(/<[A-Z][a-zA-Z]*[^>]*\/>/g, '');
|
||||||
|
|
||||||
|
// Remove JSX component tags but preserve content between opening/closing tags
|
||||||
|
// Handle nested components by recursively removing outer tags
|
||||||
|
// This regex matches opening tag, captures content (including nested tags), and closing tag
|
||||||
|
let previousContent = '';
|
||||||
|
while (content !== previousContent) {
|
||||||
|
previousContent = content;
|
||||||
|
// Match JSX components with their content - handles one level of nesting
|
||||||
|
content = content.replace(
|
||||||
|
/<([A-Z][a-zA-Z]*)[^>]*>([\s\S]*?)<\/\1>/g,
|
||||||
|
(match, tagName, innerContent) => {
|
||||||
|
return dedentContent(innerContent).trim();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any remaining JSX tags (self-closing or unmatched)
|
||||||
|
content = content.replace(/<\/?[A-Z][a-zA-Z]*[^>]*>/g, '');
|
||||||
|
|
||||||
|
// Restore code blocks
|
||||||
|
codeBlockPlaceholders.forEach((codeBlock, index) => {
|
||||||
|
content = content.replace(`__CODE_BLOCK_${index}__`, codeBlock);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert internal links (starting with /) to absolute URLs
|
||||||
|
content = content.replace(
|
||||||
|
/\[([^\]]+)\]\((\/[^\)]+)\)/g,
|
||||||
|
'[$1](https://openpanel.dev$2)',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clean up extra blank lines
|
||||||
|
content = content.replace(/\n{3,}/g, '\n\n').trim();
|
||||||
|
|
||||||
|
// Build the README header
|
||||||
|
const docUrl = `https://openpanel.dev/docs/sdks/${packageName.replace('@openpanel/', '')}`;
|
||||||
|
let readme = `# ${title}\n\n`;
|
||||||
|
|
||||||
|
if (description) {
|
||||||
|
readme += `${description}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
readme += `> 📖 **Full documentation:** [${docUrl}](${docUrl})\n\n`;
|
||||||
|
readme += '---\n\n';
|
||||||
|
readme += content;
|
||||||
|
|
||||||
|
return readme;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateReadme = (
|
||||||
|
packages: Record<string, PackageInfo>,
|
||||||
|
dependents: string[],
|
||||||
|
): string[] => {
|
||||||
|
const generatedReadmes: string[] = [];
|
||||||
|
for (const dep of dependents) {
|
||||||
|
const pkg = packages[dep];
|
||||||
|
const docPath = pkg?.config?.docPath;
|
||||||
|
if (!docPath) {
|
||||||
|
console.log(
|
||||||
|
`📝 Skipping README generation for ${dep} (no docPath configured)`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const packagePath = workspacePath(pkg.localPath);
|
||||||
|
const readmePath = join(packagePath, 'README.md');
|
||||||
|
console.log(`📝 Generating README for ${dep}`);
|
||||||
|
const mdxContent = fs.readFileSync(workspacePath(docPath), 'utf-8');
|
||||||
|
const readmeContent = transformMdxToReadme(mdxContent, pkg.name);
|
||||||
|
fs.writeFileSync(readmePath, readmeContent, 'utf-8');
|
||||||
|
generatedReadmes.push(readmePath);
|
||||||
|
}
|
||||||
|
return generatedReadmes;
|
||||||
|
};
|
||||||
@@ -3,12 +3,13 @@ import fs from 'node:fs';
|
|||||||
import { join, resolve } from 'node:path';
|
import { join, resolve } from 'node:path';
|
||||||
import { dirname } from 'node:path';
|
import { dirname } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = dirname(__filename);
|
|
||||||
import arg from 'arg';
|
import arg from 'arg';
|
||||||
import type { ReleaseType } from 'semver';
|
import type { ReleaseType } from 'semver';
|
||||||
import semver, { RELEASE_TYPES } from 'semver';
|
import semver, { RELEASE_TYPES } from 'semver';
|
||||||
|
import { generateReadme } from './generate-readme';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
interface PackageJson {
|
interface PackageJson {
|
||||||
@@ -20,11 +21,12 @@ interface PackageJson {
|
|||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
config?: {
|
config?: {
|
||||||
transformPackageJson?: boolean;
|
transformPackageJson?: boolean;
|
||||||
transformEnvs: boolean;
|
transformEnvs?: boolean;
|
||||||
|
docPath?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PackageInfo extends PackageJson {
|
export interface PackageInfo extends PackageJson {
|
||||||
nextVersion: string;
|
nextVersion: string;
|
||||||
localPath: string;
|
localPath: string;
|
||||||
}
|
}
|
||||||
@@ -146,7 +148,7 @@ const updatePackageJsonForRelease = (
|
|||||||
main: './dist/index.js',
|
main: './dist/index.js',
|
||||||
module: './dist/index.js',
|
module: './dist/index.js',
|
||||||
types: './dist/index.d.ts',
|
types: './dist/index.d.ts',
|
||||||
files: ['dist'],
|
files: ['dist', 'README.md'],
|
||||||
exports: restPkgJson.exports ?? {
|
exports: restPkgJson.exports ?? {
|
||||||
'.': {
|
'.': {
|
||||||
import: './dist/index.js',
|
import: './dist/index.js',
|
||||||
@@ -266,6 +268,7 @@ const publishPackages = (
|
|||||||
const restoreAndUpdateLocal = (
|
const restoreAndUpdateLocal = (
|
||||||
packages: Record<string, PackageInfo>,
|
packages: Record<string, PackageInfo>,
|
||||||
dependents: string[],
|
dependents: string[],
|
||||||
|
generatedReadmes: string[],
|
||||||
): void => {
|
): void => {
|
||||||
const filesToRestore = dependents
|
const filesToRestore = dependents
|
||||||
.map((dep) => join(workspacePath(packages[dep]!.localPath), 'package.json'))
|
.map((dep) => join(workspacePath(packages[dep]!.localPath), 'package.json'))
|
||||||
@@ -273,6 +276,14 @@ const restoreAndUpdateLocal = (
|
|||||||
|
|
||||||
execSync(`git checkout ${filesToRestore}`);
|
execSync(`git checkout ${filesToRestore}`);
|
||||||
|
|
||||||
|
// Clean up auto-generated README files
|
||||||
|
for (const readmePath of generatedReadmes) {
|
||||||
|
if (fs.existsSync(readmePath)) {
|
||||||
|
console.log(`🧹 Removing auto-generated README: ${readmePath}`);
|
||||||
|
fs.unlinkSync(readmePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const dep of dependents) {
|
for (const dep of dependents) {
|
||||||
const { nextVersion, localPath, ...restPkgJson } = packages[dep]!;
|
const { nextVersion, localPath, ...restPkgJson } = packages[dep]!;
|
||||||
console.log(`🚀 Updating ${dep} (${nextVersion}-local)`);
|
console.log(`🚀 Updating ${dep} (${nextVersion}-local)`);
|
||||||
@@ -356,6 +367,8 @@ function main() {
|
|||||||
|
|
||||||
buildPackages(packages, dependents);
|
buildPackages(packages, dependents);
|
||||||
|
|
||||||
|
const generatedReadmes = generateReadme(packages, dependents);
|
||||||
|
|
||||||
if (args['--publish']) {
|
if (args['--publish']) {
|
||||||
const config: PublishConfig = {
|
const config: PublishConfig = {
|
||||||
registry: args['--npm']
|
registry: args['--npm']
|
||||||
@@ -365,7 +378,7 @@ function main() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
publishPackages(packages, dependents, config);
|
publishPackages(packages, dependents, config);
|
||||||
restoreAndUpdateLocal(originalPackages, dependents);
|
restoreAndUpdateLocal(originalPackages, dependents, generatedReadmes);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ All done!');
|
console.log('✅ All done!');
|
||||||
|
|||||||
Reference in New Issue
Block a user