From 6840108f30b2aa24bb4665509aabcc0776589a8e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 10 Jun 2025 13:39:57 +0530 Subject: [PATCH] feat: add publishing pipeline --- .gitignore | 3 + package.json | 2 + scripts/publish-react-components.js | 244 ++++++++++++++++++++++++++++ scripts/yalc-publish-react.js | 50 ++++++ vite.config.ts | 30 ++-- 5 files changed, 316 insertions(+), 13 deletions(-) create mode 100644 scripts/publish-react-components.js create mode 100644 scripts/yalc-publish-react.js diff --git a/.gitignore b/.gitignore index c64fb5c1b..29d3899e4 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,6 @@ yarn-debug.log* .vscode .claude/settings.local.json .cursor + +# react component +dist diff --git a/package.json b/package.json index 296fd1fa2..fbcb42608 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "build:sdk": "BUILD_MODE=library vite build", "build:ui": "BUILD_MODE=ui vite build", "build:react": "BUILD_MODE=react-components vite build", + "package:react": "node scripts/publish-react-components.js", + "package:react:yalc-publish": "node scripts/yalc-publish-react.js", "prepare": "husky install", "size": "size-limit", "story:dev": "histoire dev", diff --git a/scripts/publish-react-components.js b/scripts/publish-react-components.js new file mode 100644 index 000000000..0be397ef4 --- /dev/null +++ b/scripts/publish-react-components.js @@ -0,0 +1,244 @@ +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +console.log('🚀 Building Chatwoot React Components for NPM...'); + +async function publishReactComponents() { + try { + // Step 1: Clean previous builds + console.log('📦 Cleaning previous builds...'); + if (fs.existsSync('dist')) { + fs.rmSync('dist', { recursive: true, force: true }); + } + + // Clean previous React component builds + const reactFiles = [ + 'public/packs/react-components.es.js', + 'public/packs/react-components.cjs.js', + 'public/packs/style.css', + ]; + + reactFiles.forEach(file => { + if (fs.existsSync(file)) { + fs.unlinkSync(file); + console.log(` 🗑️ Removed ${file}`); + } + }); + + // Step 2: Build the React components library + console.log('🔨 Building React components...'); + execSync('pnpm build:react', { stdio: 'inherit' }); + + // Step 3: Create package directory + console.log('📁 Creating package directory...'); + const packageDir = 'dist/react-components'; + fs.mkdirSync(packageDir, { recursive: true }); + + // Step 4: Copy built files + console.log('📋 Copying built files...'); + copyBuildFiles(packageDir); + + // Step 5: Generate package.json + console.log('📄 Generating package.json...'); + generatePackageJson(packageDir); + + // Step 6: Copy additional files (README, etc.) + console.log('📚 Copying documentation...'); + copyDocumentation(packageDir); + + console.log('✅ Package ready in dist/react-components/'); + console.log(''); + console.log('📦 Publishing options:'); + console.log(' • npm publish: cd dist/react-components && npm publish'); + console.log(' • yalc publish: pnpm package:react:yalc-publish'); + console.log(' • local test: cd dist/react-components && npm pack'); + } catch (error) { + console.error('❌ Build failed:', error.message); + process.exit(1); + } +} + +function copyBuildFiles(packageDir) { + // Copy main build outputs + const files = [ + { src: 'public/packs/react-components.es.js', dest: 'index.js' }, // ES module (main) + { src: 'public/packs/react-components.cjs.js', dest: 'index.cjs' }, // CommonJS + { src: 'public/packs/style.css', dest: 'style.css' }, // CSS styles + ]; + + files.forEach(({ src, dest }) => { + if (fs.existsSync(src)) { + fs.copyFileSync(src, path.join(packageDir, dest)); + console.log(` ✓ Copied ${dest}`); + } else { + console.warn(` ⚠️ Warning: ${src} not found`); + } + }); +} + +function generatePackageJson(packageDir) { + // Read version from main package.json + const mainPackage = JSON.parse(fs.readFileSync('package.json', 'utf8')); + + const packageJson = { + name: '@chatwoot/react-components', + version: `${mainPackage.version}-beta.1`, // Use main version + beta suffix + description: + 'React components for Chatwoot messaging interface with Vue Web Components', + + // Entry points for different module systems + main: 'index.cjs', // CommonJS entry point for Node.js + module: 'index.js', // ES module entry point for bundlers + exports: { + '.': { + import: './index.js', // ES modules + require: './index.cjs', // CommonJS + }, + './style.css': './style.css', + }, + + // Peer dependencies - what the consuming app must provide + peerDependencies: { + react: '>=16.8.0', // Hooks support + 'react-dom': '>=16.8.0', + }, + + // Package metadata + keywords: [ + 'chatwoot', + 'react', + 'vue', + 'webcomponents', + 'chat', + 'messaging', + 'customer-support', + 'ui-components', + ], + author: 'Chatwoot', + license: 'MIT', + repository: { + type: 'git', + url: 'https://github.com/chatwoot/chatwoot.git', + directory: 'app/javascript/react-components', + }, + homepage: 'https://chatwoot.com', + bugs: { + url: 'https://github.com/chatwoot/chatwoot/issues', + }, + + // npm publish configuration + files: ['index.js', 'index.cjs', 'style.css', 'README.md', 'package.json'], + + // Engine requirements + engines: { + node: '>=16.0.0', + }, + }; + + fs.writeFileSync( + path.join(packageDir, 'package.json'), + JSON.stringify(packageJson, null, 2) + ); + console.log(' ✓ Generated package.json'); +} + +function copyDocumentation(packageDir) { + // Create a comprehensive README for the package + const readme = `# Chatwoot React Components + +React components for embedding Chatwoot messaging interface with Vue Web Components under the hood. + +## Installation + +\`\`\`bash +npm install @chatwoot/react-components +# or +yarn add @chatwoot/react-components +# or +pnpm add @chatwoot/react-components +\`\`\` + +## Usage + +### Basic Hello World + +\`\`\`jsx +import React from 'react'; +import { HelloWorld } from '@chatwoot/react-components'; +import '@chatwoot/react-components/style.css'; + +function App() { + return ( +
+ +
+ ); +} +\`\`\` + +### Vue Web Component Integration + +\`\`\`jsx +import React from 'react'; +import { VueWebComponentWrapper } from '@chatwoot/react-components'; +import '@chatwoot/react-components/style.css'; + +function App() { + return ( +
+ +
+ ); +} +\`\`\` + +## Components + +### HelloWorld + +A simple React component with counter functionality. + +**Props:** None + +### VueWebComponentWrapper + +A React wrapper for Vue components converted to Web Components. + +**Props:** +- \`initialCount\` (number): Starting count value (default: 0) +- \`title\` (string): Display title (default: "Hello from Vue in React!") +- \`color\` (string): Button color (default: "#42b883") + +## Architecture + +This package demonstrates: +- 🔄 **Vue → Web Components**: Using Vue 3's \`defineCustomElement\` +- ⚛️ **React Integration**: Wrapping Web Components in React components +- 📦 **Dual Build**: ES modules + CommonJS for maximum compatibility +- 🎨 **CSS Isolation**: Scoped styles via Shadow DOM + +## Requirements + +- React 16.8+ (hooks support) +- Modern browsers with Web Components support + +## Development + +This package is part of the [Chatwoot](https://chatwoot.com) project. + +## License + +MIT © [Chatwoot](https://chatwoot.com) +`; + + fs.writeFileSync(path.join(packageDir, 'README.md'), readme); + console.log(' ✓ Generated README.md'); +} + +// Run the script +publishReactComponents(); diff --git a/scripts/yalc-publish-react.js b/scripts/yalc-publish-react.js new file mode 100644 index 000000000..0aa449b0d --- /dev/null +++ b/scripts/yalc-publish-react.js @@ -0,0 +1,50 @@ +const { execSync } = require('child_process'); +const fs = require('fs'); + +console.log('🔗 Publishing Chatwoot React Components via Yalc...'); + +function yalcPublish() { + try { + // Check if yalc is installed + try { + execSync('yalc --version', { stdio: 'pipe' }); + } catch (error) { + console.error('❌ Yalc is not installed globally.'); + console.log('💡 Install yalc: npm install -g yalc'); + process.exit(1); + } + + // Check if package exists + const packageDir = 'dist/react-components'; + if (!fs.existsSync(packageDir)) { + console.log('📦 Package not found. Building first...'); + execSync('node scripts/publish-react-components.js', { + stdio: 'inherit', + }); + } + + // Publish with yalc + console.log('🚀 Publishing to yalc store...'); + execSync('yalc publish', { + cwd: packageDir, + stdio: 'inherit', + }); + + console.log(''); + console.log('✅ Published to yalc store!'); + console.log(''); + console.log('📖 Next steps in your test project:'); + console.log(' 1. yalc add @chatwoot/react-components'); + console.log(' 2. npm install'); + console.log(' 3. Import and use the components'); + console.log(''); + console.log('🔄 To update after changes:'); + console.log(' 1. pnpm package:react:yalc-publish (in this repo)'); + console.log(' 2. yalc update (in test project)'); + } catch (error) { + console.error('❌ Yalc publish failed:', error.message); + process.exit(1); + } +} + +yalcPublish(); diff --git a/vite.config.ts b/vite.config.ts index 56e9a0d15..21f290d38 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -49,7 +49,7 @@ if (isLibraryMode) { plugins = [vue(vueOptions)]; } -let lib = undefined; +let lib; let rollupOptions = {}; if (isLibraryMode) { @@ -66,20 +66,16 @@ if (isLibraryMode) { }; } else if (reactComponentMode) { lib = { - entry: path.resolve(__dirname, './app/javascript/react-components/src/index.jsx'), - formats: ['iife'], // IIFE format for single file - name: 'ChatwootReactComponents', - fileName: () => 'index.js' + entry: path.resolve( + __dirname, + './app/javascript/react-components/src/index.jsx' + ), + formats: ['es', 'cjs'], // ES modules and CommonJS only + fileName: format => `react-components.${format}.js`, }; - + rollupOptions = { external: ['react', 'react-dom'], - output: { - globals: { - react: 'React', - 'react-dom': 'ReactDOM' - } - } }; } @@ -91,6 +87,9 @@ const chunkBuilder = chunkInfo => { if (chunkInfo.name === 'ui') { return `js/ui.js`; } + + // For react components, we need to return different names but can't access format here + // So we'll handle this differently return '[name].js'; }; @@ -106,12 +105,17 @@ export default defineConfig({ ...rollupOptions.output, // [NOTE] when not in library mode, no new keys will be addedd or overwritten // setting dir: isLibraryMode ? 'public/packs' : undefined will not work - ...(isLibraryMode || uiMode || reactComponentMode + ...(isLibraryMode || uiMode ? { dir: 'public/packs', entryFileNames: chunkBuilder, } : {}), + ...(reactComponentMode + ? { + dir: 'public/packs', + } + : {}), inlineDynamicImports: isLibraryMode || uiMode || reactComponentMode, // Disable code-splitting for SDK }, },