mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-20 21:15:01 +00:00
feat: add publishing pipeline
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -94,3 +94,6 @@ yarn-debug.log*
|
|||||||
.vscode
|
.vscode
|
||||||
.claude/settings.local.json
|
.claude/settings.local.json
|
||||||
.cursor
|
.cursor
|
||||||
|
|
||||||
|
# react component
|
||||||
|
dist
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
"build:sdk": "BUILD_MODE=library vite build",
|
"build:sdk": "BUILD_MODE=library vite build",
|
||||||
"build:ui": "BUILD_MODE=ui vite build",
|
"build:ui": "BUILD_MODE=ui vite build",
|
||||||
"build:react": "BUILD_MODE=react-components 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",
|
"prepare": "husky install",
|
||||||
"size": "size-limit",
|
"size": "size-limit",
|
||||||
"story:dev": "histoire dev",
|
"story:dev": "histoire dev",
|
||||||
|
|||||||
244
scripts/publish-react-components.js
Normal file
244
scripts/publish-react-components.js
Normal file
@@ -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 (
|
||||||
|
<div>
|
||||||
|
<HelloWorld />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Vue Web Component Integration
|
||||||
|
|
||||||
|
\`\`\`jsx
|
||||||
|
import React from 'react';
|
||||||
|
import { VueWebComponentWrapper } from '@chatwoot/react-components';
|
||||||
|
import '@chatwoot/react-components/style.css';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<VueWebComponentWrapper
|
||||||
|
initialCount={10}
|
||||||
|
title="Custom Title"
|
||||||
|
color="#ff6b6b"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## 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();
|
||||||
50
scripts/yalc-publish-react.js
Normal file
50
scripts/yalc-publish-react.js
Normal file
@@ -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();
|
||||||
@@ -49,7 +49,7 @@ if (isLibraryMode) {
|
|||||||
plugins = [vue(vueOptions)];
|
plugins = [vue(vueOptions)];
|
||||||
}
|
}
|
||||||
|
|
||||||
let lib = undefined;
|
let lib;
|
||||||
let rollupOptions = {};
|
let rollupOptions = {};
|
||||||
|
|
||||||
if (isLibraryMode) {
|
if (isLibraryMode) {
|
||||||
@@ -66,20 +66,16 @@ if (isLibraryMode) {
|
|||||||
};
|
};
|
||||||
} else if (reactComponentMode) {
|
} else if (reactComponentMode) {
|
||||||
lib = {
|
lib = {
|
||||||
entry: path.resolve(__dirname, './app/javascript/react-components/src/index.jsx'),
|
entry: path.resolve(
|
||||||
formats: ['iife'], // IIFE format for single file
|
__dirname,
|
||||||
name: 'ChatwootReactComponents',
|
'./app/javascript/react-components/src/index.jsx'
|
||||||
fileName: () => 'index.js'
|
),
|
||||||
|
formats: ['es', 'cjs'], // ES modules and CommonJS only
|
||||||
|
fileName: format => `react-components.${format}.js`,
|
||||||
};
|
};
|
||||||
|
|
||||||
rollupOptions = {
|
rollupOptions = {
|
||||||
external: ['react', 'react-dom'],
|
external: ['react', 'react-dom'],
|
||||||
output: {
|
|
||||||
globals: {
|
|
||||||
react: 'React',
|
|
||||||
'react-dom': 'ReactDOM'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +87,9 @@ const chunkBuilder = chunkInfo => {
|
|||||||
if (chunkInfo.name === 'ui') {
|
if (chunkInfo.name === 'ui') {
|
||||||
return `js/ui.js`;
|
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';
|
return '[name].js';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -106,12 +105,17 @@ export default defineConfig({
|
|||||||
...rollupOptions.output,
|
...rollupOptions.output,
|
||||||
// [NOTE] when not in library mode, no new keys will be addedd or overwritten
|
// [NOTE] when not in library mode, no new keys will be addedd or overwritten
|
||||||
// setting dir: isLibraryMode ? 'public/packs' : undefined will not work
|
// setting dir: isLibraryMode ? 'public/packs' : undefined will not work
|
||||||
...(isLibraryMode || uiMode || reactComponentMode
|
...(isLibraryMode || uiMode
|
||||||
? {
|
? {
|
||||||
dir: 'public/packs',
|
dir: 'public/packs',
|
||||||
entryFileNames: chunkBuilder,
|
entryFileNames: chunkBuilder,
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
|
...(reactComponentMode
|
||||||
|
? {
|
||||||
|
dir: 'public/packs',
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
inlineDynamicImports: isLibraryMode || uiMode || reactComponentMode, // Disable code-splitting for SDK
|
inlineDynamicImports: isLibraryMode || uiMode || reactComponentMode, // Disable code-splitting for SDK
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user