Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,218 @@
import fs from 'fs';
import path from 'path';
import { execSync } from 'child_process';
interface PackageJson {
name: string;
version: string;
[key: string]: any;
}
interface UpdateResult {
success: boolean;
oldVersion?: string;
newVersion?: string;
error?: string;
}
// Function to bump patch version
function bumpPatchVersion(version: string): string {
const parts = version.split('.');
if (parts.length >= 3) {
const patch = parseInt(parts[2]) + 1;
return `${parts[0]}.${parts[1]}.${patch}`;
}
return version;
}
// Function to update package.json
function updatePackageJson(packageJsonPath: string): UpdateResult {
try {
const content = fs.readFileSync(packageJsonPath, 'utf8');
const packageJson: PackageJson = JSON.parse(content);
if (packageJson.version) {
const oldVersion = packageJson.version;
const newVersion = bumpPatchVersion(oldVersion);
console.log(`Bumping ${path.basename(path.dirname(packageJsonPath))}: ${oldVersion} -> ${newVersion}`);
packageJson.version = newVersion;
// Write back to file with proper formatting
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
return { success: true, oldVersion, newVersion };
}
return { success: false, error: 'No version field found in package.json' };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
console.error(`Error updating ${packageJsonPath}:`, errorMessage);
return { success: false, error: errorMessage };
}
}
// Function to check if a piece has changes compared to main
function hasChangesComparedToMain(piecePath: string): boolean {
try {
// Check if there are any changes in the piece directory compared to main
execSync(`git diff --quiet origin/main -- ${piecePath}`, { encoding: 'utf8' });
return false; // No changes
} catch (error) {
if (error instanceof Error && 'status' in error && error.status === 1) {
return true; // Has changes
}
throw error;
}
}
// Function to check if a piece has i18n directory
function hasI18nDirectory(piecePath: string): boolean {
const i18nPath = path.join(piecePath, 'src', 'i18n');
return fs.existsSync(i18nPath) && fs.statSync(i18nPath).isDirectory();
}
// Function to check if a piece has translation-related changes
function hasTranslationChanges(piecePath: string): boolean {
try {
// Check if there are changes in the i18n directory
const i18nChanges = execSync(`git diff --name-only origin/main -- ${piecePath}/src/i18n`, { encoding: 'utf8' }).trim();
return i18nChanges.length > 0;
} catch (error) {
if (error instanceof Error && 'status' in error && error.status === 1) {
return true; // Has changes
}
return false; // No changes or error
}
}
// Function to check if a piece version has already been bumped compared to main
function hasVersionBeenBumped(piecePath: string): boolean {
try {
const packageJsonPath = path.join(piecePath, 'package.json');
if (!fs.existsSync(packageJsonPath)) {
return false;
}
// Get current version
const content = fs.readFileSync(packageJsonPath, 'utf8');
const packageJson: PackageJson = JSON.parse(content);
const currentVersion = packageJson.version;
if (!currentVersion) {
return false;
}
// Get version from main branch
const mainVersion = execSync(`git show origin/main:${packageJsonPath}`, { encoding: 'utf8' });
const mainPackageJson: PackageJson = JSON.parse(mainVersion);
const mainBranchVersion = mainPackageJson.version;
// Compare versions - if current version is higher than main, it's already been bumped
return compareVersions(currentVersion, mainBranchVersion) > 0;
} catch (error) {
// If we can't get the main version (e.g., file doesn't exist on main), assume not bumped
return false;
}
}
// Helper function to compare semantic versions
function compareVersions(version1: string, version2: string): number {
const v1Parts = version1.split('.').map(Number);
const v2Parts = version2.split('.').map(Number);
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
const v1Part = v1Parts[i] || 0;
const v2Part = v2Parts[i] || 0;
if (v1Part > v2Part) return 1;
if (v1Part < v2Part) return -1;
}
return 0;
}
// Function to get all piece directories
function getPieceDirectories(piecesDir: string): string[] {
return fs.readdirSync(piecesDir)
.filter(item => {
const fullPath = path.join(piecesDir, item);
return fs.statSync(fullPath).isDirectory();
});
}
// Main function
function main(): void {
console.log('Finding pieces with translation changes compared to main...');
const piecesWithChanges: string[] = [];
const piecesDir = 'packages/pieces/community';
// Get all piece directories
const pieceDirs = getPieceDirectories(piecesDir);
console.log(`Checking ${pieceDirs.length} pieces for changes...`);
for (const piece of pieceDirs) {
const piecePath = path.join(piecesDir, piece);
// Check if piece has changes compared to main
if (hasChangesComparedToMain(piecePath)) {
// Check if it has i18n directory (indicating translations were added)
if (hasI18nDirectory(piecePath)) {
// Check if the changes are translation-related
if (hasTranslationChanges(piecePath)) {
// Check if version has already been bumped
if (hasVersionBeenBumped(piecePath)) {
console.log(` - ${piece} - has translation changes but version already bumped`);
} else {
piecesWithChanges.push(piece);
console.log(`${piece} - has translation changes and needs version bump`);
}
} else {
console.log(` - ${piece} - has changes but not translation-related`);
}
} else {
console.log(` - ${piece} - has changes but no i18n directory`);
}
} else {
console.log(` - ${piece} - no changes`);
}
}
if (piecesWithChanges.length === 0) {
console.log('\nNo pieces with translation changes found.');
return;
}
console.log(`\nFound ${piecesWithChanges.length} pieces with translation changes:`);
piecesWithChanges.forEach(piece => console.log(` - ${piece}`));
console.log('\nBumping patch versions...');
let successCount = 0;
let errorCount = 0;
for (const piece of piecesWithChanges) {
const packageJsonPath = path.join(piecesDir, piece, 'package.json');
if (fs.existsSync(packageJsonPath)) {
const result = updatePackageJson(packageJsonPath);
if (result.success) {
successCount++;
} else {
errorCount++;
}
} else {
console.error(`Package.json not found for ${piece}`);
errorCount++;
}
}
console.log(`\nSummary:`);
console.log(` Successfully updated: ${successCount} pieces`);
console.log(` Errors: ${errorCount} pieces`);
}
// Run the script
main();

View File

@@ -0,0 +1,50 @@
import assert from 'node:assert'
import { argv } from 'node:process'
import { exec } from '../utils/exec'
import { readPackageJson, readProjectJson } from '../utils/files'
import { findAllPiecesDirectoryInSource } from '../utils/piece-script-utils'
import { isNil } from '@activepieces/shared'
import chalk from 'chalk'
import path from 'node:path'
export const publishPiece = async (name: string): Promise<void> => {
assert(name, '[publishPiece] parameter "name" is required')
const distPaths = await findAllPiecesDirectoryInSource()
const directory = distPaths.find(p => path.basename(p) === name)
if (isNil(directory)) {
console.error(chalk.red(`[publishPiece] can't find the directory with name ${name}`))
return
}
const { version } = await readPackageJson(directory)
const { name: nxProjectName } = await readProjectJson(directory)
await exec(`npx nx build ${nxProjectName}`)
const nxPublishProjectCommand = `
node tools/scripts/publish.mjs \
${nxProjectName} \
${version} \
latest
`
await exec(nxPublishProjectCommand)
console.info(chalk.green.bold(`[publishPiece] success, name=${name}, version=${version}`))
}
const main = async (): Promise<void> => {
const pieceName = argv[2]
await publishPiece(pieceName)
}
/*
* module is entrypoint, not imported i.e. invoked directly
* see https://nodejs.org/api/modules.html#modules_accessing_the_main_module
*/
if (require.main === module) {
main()
}

View File

@@ -0,0 +1,20 @@
import { publishNxProject } from '../utils/publish-nx-project'
import { findAllPiecesDirectoryInSource } from '../utils/piece-script-utils'
import { chunk } from '../../../packages/shared/src/lib/common/utils/utils'
const publishPiece = async (nxProjectPath: string): Promise<void> => {
console.info(`[publishPiece] nxProjectPath=${nxProjectPath}`)
await publishNxProject(nxProjectPath)
}
const main = async () => {
const piecesSource = await findAllPiecesDirectoryInSource()
const piecesSourceChunks = chunk(piecesSource, 30)
for (const chunk of piecesSourceChunks) {
await Promise.all(chunk.map(publishPiece))
await new Promise(resolve => setTimeout(resolve, 5000))
}
}
main()

View File

@@ -0,0 +1,492 @@
import * as fs from 'fs';
import * as path from 'path';
import { execSync } from 'child_process';
import semVer from 'semver';
const contributors = [
{
"name": "Kishan Parmar",
"login": "kishanprmr"
},
{
"name": "Mohammad AbuAboud",
"login": "abuaboud"
},
{
"name": "Damien HEBERT",
"login": "doskyft"
},
{
"name": "AbdulTheActivePiecer",
"login": "AbdulTheActivePiecer"
},
{
"name": "Moayyad Shaddad",
"login": "MoShizzle"
},
{
"name": "TaskMagicKyle",
"login": "TaskMagicKyle"
},
{
"name": "kylebuilds",
"login": "TaskMagicKyle"
},
{
"name": "Salem-Alaa",
"login": "Salem-Alaa"
},
{
"name": "Khaled Mashaly",
"login": "khaledmashaly"
},
{
"name": "abuaboud",
"login": "abuaboud"
},
{
"name": "Mohammed Abu Aboud",
"login": "abuaboud"
},
{
"name": "Mukewa O. Wekalao",
"login": "kanarelo"
},
{
"name": "Willian",
"login": "Willianwg"
},
{
"name": "Aler Denisov",
"login": "alerdenisov"
},
{
"name": "Abdallah-Alwarawreh",
"login": "Abdallah-Alwarawreh"
},
{
"name": "Shay Punter",
"login": "ShayPunter"
},
{
"name": "i-nithin",
"login": "i-nithin"
},
{
"name": "Joe Workman",
"login": "joeworkman"
},
{
"name": "ShayPunter",
"login": "ShayPunter"
},
{
"name": "Vraj Gohil",
"login": "VrajGohil"
},
{
"name": "Matthew Zeiler",
"login": "zeiler"
},
{
"name": "Alexandros Katechis",
"login": "akatechis"
},
{
"name": "JanHolger",
"login": "JanHolger"
},
{
"name": "Andrei Chirko",
"login": "andchir"
},
{
"name": "Landon Moir",
"login": "landonmoir"
},
{
"name": "bibhuty-did-this",
"login": "bibhuty-did-this"
},
{
"name": "Cyril Selasi",
"login": "cyrilselasi"
},
{
"name": "Gunther Schulz",
"login": "Gunther-Schulz"
},
{
"name": "Osama Zakarneh",
"login": "Ozak93"
},
{
"name": "Owlcept",
"login": "Owlcept"
},
{
"name": "Drew Lewis",
"login": "Owlcept"
},
{
"name": "AbdullahBitar",
"login": "AbdullahBitar"
},
{
"name": "Mohammad Abuaboud",
"login": "abuaboud"
},
{
"name": "BBND",
"login": "BBND"
},
{
"name": "Nilesh",
"login": "Nilesh"
},
{
"name": "Karim Khaleel",
"login": "karimkhaleel"
},
{
"name": "[NULL] Dev",
"login": "Abdallah-Alwarawreh"
},
{
"name": "Pablo Fernandez",
"login": "pfernandez98"
},
{
"name": "BastienMe",
"login": "BastienMe"
},
{
"name": "Olivier Sambourg",
"login": "AdamSelene"
},
{
"name": "MoShizzle",
"login": "MoShizzle"
},
{
"name": "Aasim Sani",
"login": "aasimsani"
},
{
"name": "Abdul-rahman Yasir Khalil",
"login": "AbdulTheActivePiecer"
},
{
"name": "awais",
"login": "awais"
},
{
"name": "Lisander Lopez",
"login": "lisander-lopez"
},
{
"name": "OsamaHaikal",
"login": "OsamaHaikal"
},
{
"name": "Maher",
"login": "abaza738"
},
{
"name": "Maher Abaza",
"login": "abaza738"
},
{
"name": "Mukewa Wekalao",
"login": "kanarelo"
},
{
"name": "Mark van Bellen",
"login": "buttonsbond"
},
{
"name": "Denis Gurskij",
"login": "DGurskij"
},
{
"name": "Thibaut Patel",
"login": "tpatel"
},
{
"name": "Bastien Meffre",
"login": "BastienMe"
},
{
"name": "Abdullah Ranginwala",
"login": "abdullahranginwala"
},
{
"name": "pfernandez98",
"login": "pfernandez98"
},
{
"name": "Vitali Borovi",
"login": "Vitalini"
},
{
"name": "Vitali Borovik",
"login": "Vitalini"
},
{
"name": "Vitalik Borovik",
"login": "Vitalini"
},
{
"name": "Armand Giauffret 4",
"login": "ArmanGiau3"
},
{
"name": "Armand Giauffret 3",
"login": "ArmanGiau3"
},
{
"name": "Salem Alwarawreh",
"login": "Salem-Alaa"
},
{
"name": "MyWay",
"login": "MyWay"
},
{
"name": "leenmashni",
"login": "leenmashni"
},
{
"name": "Fábio Ferreira",
"login": "facferreira"
},
{
"name": "Diego Nijboer",
"login": "lldiegon"
},
{
"name": "Enrike Nur",
"login": "w95"
},
{
"name": "Haithem BOUJRIDA",
"login": "hkboujrida"
},
{
"name": "Willian Guedes",
"login": "Willianwg"
},
{
"name": "Daniel Ostapenko",
"login": "denieler"
},
{
"name": "Yann Petitjean",
"login": "yann120"
},
{
"name": "Lawrence Li",
"login": "la3rence"
},
{
"name": "Mario Meyer",
"login": "mariomeyer"
},
{
"name": "aboudzein",
"login": "aboudzein",
},
{
"name": "aboudzeineddin",
"login": "aboudzein",
},
{
"name": "Alexander Storozhevsky",
"login": "astorozhevsky"
},
{
"name": "dentych",
"login": "dentych"
},
{
"name": "Matt Lung",
"login": "Vitalini"
},
{
"name": "joselupianez",
"login": "joselupianez"
},
{
"name": "Hoang Duc Tan",
"login": "tanoggy"
},
{
"name": "Herman Kudria",
"login": "HKudria"
},
{
"name": "Ahmad Ghosheh",
"login": "BLaidzX"
},
{
"name": "Ben",
"login": "bendersej"
},
{
"name": "Rita Gorokhod",
"login": "rita-gorokhod"
},
{
name: "Dennis Rongo",
login: "dennisrongo"
},
{
"name": "x7airworker",
"login": "x7airworker"
},
{
"name": "Camilo Usuga",
"login": "camilou"
},
{
"name": "Fardeen Panjwani",
"login": "fardeenpanjwani-codeglo"
},
{
"name": "Tân Một Nắng",
"login": "tanoggy"
},
{
"name": "ashrafsamhouri",
"login": "ashrafsamhouri"
},
{
"name": "Ahmad-AbuOsbeh",
"login": "Ahmad-AbuOsbeh"
},
{
"name": "Fastkop",
"login": "abuaboud"
},
{
"name": "Abdul",
"login": "AbdulTheActivePiecer"
},
{
"name": "ahmad jaber",
"login": "creed983",
},
{
"name": "creed983",
"login": "creed983",
},
{
"name": "Activepieces Dev",
"login": "ashrafsamhouri"
},
{
"name": "hiasat",
"login": "abuaboud"
},
{
"name": "Mohammad",
"login": "abuaboud"
},
{
"name": "ActivePieces",
"login": "abuaboud"
},
{
"name": "haseebrehmanpc",
"login": "haseebrehmanpc"
},
{
"name": "Haseeb Rehman",
"login": "haseebrehmanpc"
}
]
function cleanAuthors(authors: string[]) {
const cleanedAuthors: string[] = []
authors.forEach(author => {
const contributor = contributors.find(contributor => contributor.name === author);
if (contributor) {
cleanedAuthors.push(contributor.login);
} else {
throw new Error(`Author ${author} not found`);
}
});
return cleanedAuthors;
}
function loadAuthors(directoryPath: string) {
const gitLogCommand = `git log --format="%aN" -- ${directoryPath}`;
const result = execSync(gitLogCommand, { cwd: '/workspace/', encoding: 'utf-8' });
if (result.length === 0) {
return [];
}
const authors = result.trim().split('\n');
authors.forEach(author => {
if (!contributors.find(contributor => contributor.name === author)) {
throw new Error(`Author ${author} not found in ${directoryPath}`);
}
})
return authors;
}
function loadSrcIndexFiles(directoryPath: string) {
const files = fs.readdirSync(directoryPath);
files.forEach(file => {
const filePath = path.join(directoryPath, file);
const stats = fs.statSync(filePath);
if (file === 'tmp' || file === 'framework' || file === 'common') return;
if (stats.isDirectory()) {
const indexPath = path.join(filePath, 'src', 'index.ts');
if (fs.existsSync(indexPath)) {
const authorsOne = cleanAuthors(loadAuthors(filePath));
const authorsTwo = cleanAuthors(loadAuthors(filePath.replace('/community', '')));
const authorsThree = cleanAuthors(loadAuthors(filePath.replace('/community', '/src/lib/apps')));
const authorsFour = cleanAuthors(loadAuthors(filePath.replace('/community', '/src/apps')));
const uniqueAuthors = customSort([...new Set([...authorsOne, ...authorsTwo, ...authorsThree, ...authorsFour])]);
console.log(uniqueAuthors);
const fileContent = fs.readFileSync(indexPath, { encoding: 'utf-8' });
const pattern = /authors: \[(.*?)\]/;
if (!pattern.test(fileContent)) {
throw new Error("Pattern 'authors: [...] not found in the file content. " + indexPath);
}
const modifiedContent = fileContent.replace(/authors: \[(.*?)\]/, `authors: ${JSON.stringify(uniqueAuthors)}`);
fs.writeFileSync(indexPath, modifiedContent, { encoding: 'utf-8' });
const packageJson = path.join(filePath, 'package.json');
const packageJsonContent = JSON.parse(fs.readFileSync(packageJson, { encoding: 'utf-8' }));
packageJsonContent.version = semVer.inc(packageJsonContent.version, 'patch');
fs.writeFileSync(packageJson, JSON.stringify(packageJsonContent, null, 2), { encoding: 'utf-8' });
}
}
});
}
// Sort the official team members last.
const authorsOrder = ['Abdallah-Alwarawreh', 'Salem-Alaa', 'kishanprmr', 'MoShizzle', 'AbdulTheActivePiecer', 'khaledmashaly', 'abuaboud'].map(author => author.toLocaleLowerCase());
function customSort(authors: string[]): string[] {
return authors.sort((a, b) => {
const indexA = authorsOrder.indexOf(a.toLocaleLowerCase());
const indexB = authorsOrder.indexOf(b.toLocaleLowerCase());
// If either author is not found in the specified order, move it to the end
if (indexA === -1) return -1;
if (indexB === -1) return 1;
// Sort based on the index in the specified order
return indexA - indexB;
});
}
const directoryToTraverse = '/workspace/packages/pieces/community'
loadSrcIndexFiles(directoryToTraverse);

View File

@@ -0,0 +1,70 @@
import assert from 'node:assert';
import { PieceMetadata } from '../../../packages/pieces/community/framework/src';
import { StatusCodes } from 'http-status-codes';
import { HttpHeader } from '../../../packages/pieces/community/common/src';
import { AP_CLOUD_API_BASE, findNewPieces, pieceMetadataExists } from '../utils/piece-script-utils';
import { chunk } from '../../../packages/shared/src/lib/common/utils/utils';
assert(process.env['AP_CLOUD_API_KEY'], 'API Key is not defined');
const { AP_CLOUD_API_KEY } = process.env;
const insertPieceMetadata = async (
pieceMetadata: PieceMetadata
): Promise<void> => {
const body = JSON.stringify(pieceMetadata);
const headers = {
['api-key']: AP_CLOUD_API_KEY,
[HttpHeader.CONTENT_TYPE]: 'application/json'
};
const cloudResponse = await fetch(`${AP_CLOUD_API_BASE}/admin/pieces`, {
method: 'POST',
headers,
body
});
if (cloudResponse.status !== StatusCodes.OK && cloudResponse.status !== StatusCodes.CONFLICT) {
throw new Error(await cloudResponse.text());
}
};
const insertMetadataIfNotExist = async (pieceMetadata: PieceMetadata) => {
console.info(
`insertMetadataIfNotExist, name: ${pieceMetadata.name}, version: ${pieceMetadata.version}`
);
const metadataAlreadyExist = await pieceMetadataExists(
pieceMetadata.name,
pieceMetadata.version
);
if (metadataAlreadyExist) {
console.info(`insertMetadataIfNotExist, piece metadata already inserted`);
return;
}
await insertPieceMetadata(pieceMetadata);
};
const insertMetadata = async (piecesMetadata: PieceMetadata[]) => {
const batches = chunk(piecesMetadata, 30)
for (const batch of batches) {
await Promise.all(batch.map(insertMetadataIfNotExist))
await new Promise(resolve => setTimeout(resolve, 5000))
}
};
const main = async () => {
console.log('update pieces metadata: started')
const piecesMetadata = await findNewPieces()
await insertMetadata(piecesMetadata)
console.log('update pieces metadata: completed')
process.exit()
}
main()