- 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>
248 lines
6.9 KiB
TypeScript
248 lines
6.9 KiB
TypeScript
import chalk from 'chalk';
|
|
import { Command } from 'commander';
|
|
import { readdir, unlink, writeFile } from 'fs/promises';
|
|
import inquirer from 'inquirer';
|
|
import assert from 'node:assert';
|
|
import { exec } from '../utils/exec';
|
|
import {
|
|
readPackageEslint,
|
|
readProjectJson,
|
|
writePackageEslint,
|
|
writeProjectJson,
|
|
} from '../utils/files';
|
|
import { findPiece } from '../utils/piece-utils';
|
|
|
|
const validatePieceName = async (pieceName: string) => {
|
|
console.log(chalk.yellow('Validating piece name....'));
|
|
const pieceNamePattern = /^(?![._])[a-z0-9-]{1,214}$/;
|
|
if (!pieceNamePattern.test(pieceName)) {
|
|
console.log(
|
|
chalk.red(
|
|
`🚨 Invalid piece name: ${pieceName}. Piece names can only contain lowercase letters, numbers, and hyphens.`
|
|
)
|
|
);
|
|
process.exit(1);
|
|
}
|
|
};
|
|
|
|
const validatePackageName = async (packageName: string) => {
|
|
console.log(chalk.yellow('Validating package name....'));
|
|
const packageNamePattern = /^(?:@[a-zA-Z0-9-]+\/)?[a-zA-Z0-9-]+$/;
|
|
if (!packageNamePattern.test(packageName)) {
|
|
console.log(
|
|
chalk.red(
|
|
`🚨 Invalid package name: ${packageName}. Package names can only contain lowercase letters, numbers, and hyphens.`
|
|
)
|
|
);
|
|
process.exit(1);
|
|
}
|
|
};
|
|
|
|
const checkIfPieceExists = async (pieceName: string) => {
|
|
const pieceFolder = await findPiece(pieceName);
|
|
if (pieceFolder) {
|
|
console.log(chalk.red(`🚨 Piece already exists at ${pieceFolder}`));
|
|
process.exit(1);
|
|
}
|
|
};
|
|
|
|
const nxGenerateNodeLibrary = async (
|
|
pieceName: string,
|
|
packageName: string,
|
|
pieceType: string
|
|
) => {
|
|
const nxGenerateCommand = [
|
|
`npx nx generate @nx/node:library`,
|
|
`--directory=packages/pieces/${pieceType}/${pieceName}`,
|
|
`--name=pieces-${pieceName}`,
|
|
`--importPath=${packageName}`,
|
|
'--publishable',
|
|
'--buildable',
|
|
'--projectNameAndRootFormat=as-provided',
|
|
'--strict',
|
|
'--unitTestRunner=none',
|
|
].join(' ');
|
|
|
|
console.log(chalk.blue(`🛠️ Executing nx command: ${nxGenerateCommand}`));
|
|
|
|
await exec(nxGenerateCommand);
|
|
};
|
|
|
|
const removeUnusedFiles = async (pieceName: string, pieceType: string) => {
|
|
const path = `packages/pieces/${pieceType}/${pieceName}/src/lib/`;
|
|
const files = await readdir(path);
|
|
for (const file of files) {
|
|
await unlink(path + file);
|
|
}
|
|
};
|
|
function capitalizeFirstLetter(str: string): string {
|
|
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
}
|
|
const generateIndexTsFile = async (pieceName: string, pieceType: string) => {
|
|
const pieceNameCamelCase = pieceName
|
|
.split('-')
|
|
.map((s, i) => {
|
|
if (i === 0) {
|
|
return s;
|
|
}
|
|
|
|
return s[0].toUpperCase() + s.substring(1);
|
|
})
|
|
.join('');
|
|
|
|
const indexTemplate = `
|
|
import { createPiece, PieceAuth } from "@activepieces/pieces-framework";
|
|
|
|
export const ${pieceNameCamelCase} = createPiece({
|
|
displayName: "${capitalizeFirstLetter(pieceName)}",
|
|
auth: PieceAuth.None(),
|
|
minimumSupportedRelease: '0.36.1',
|
|
logoUrl: "https://cdn.activepieces.com/pieces/${pieceName}.png",
|
|
authors: [],
|
|
actions: [],
|
|
triggers: [],
|
|
});
|
|
`;
|
|
|
|
await writeFile(
|
|
`packages/pieces/${pieceType}/${pieceName}/src/index.ts`,
|
|
indexTemplate
|
|
);
|
|
};
|
|
const updateProjectJsonConfig = async (
|
|
pieceName: string,
|
|
pieceType: string
|
|
) => {
|
|
const projectJson = await readProjectJson(
|
|
`packages/pieces/${pieceType}/${pieceName}`
|
|
);
|
|
const i18nAsset = {
|
|
input: `packages/pieces/${pieceType}/${pieceName}/src/i18n`,
|
|
output: './src/i18n',
|
|
glob: '**/!(i18n.json)'
|
|
}
|
|
assert(
|
|
projectJson.targets?.build?.options,
|
|
'[updateProjectJsonConfig] targets.build.options is required'
|
|
);
|
|
|
|
projectJson.targets.build.dependsOn = ['prebuild', '^build'];
|
|
projectJson.targets.prebuild = {
|
|
dependsOn: ['^build'],
|
|
executor: 'nx:run-commands',
|
|
options: {
|
|
cwd: `packages/pieces/${pieceType}/${pieceName}`,
|
|
command: 'bun install --no-save --silent'
|
|
}
|
|
};
|
|
|
|
projectJson.targets.build.options.buildableProjectDepsInPackageJsonType =
|
|
'dependencies';
|
|
projectJson.targets.build.options.updateBuildableProjectDepsInPackageJson =
|
|
true;
|
|
if(projectJson.targets.build.options.assets){
|
|
projectJson.targets.build.options.assets.push(i18nAsset);
|
|
}
|
|
else{
|
|
projectJson.targets.build.options.assets = [i18nAsset];
|
|
}
|
|
|
|
const lintFilePatterns = projectJson.targets.lint?.options?.lintFilePatterns;
|
|
|
|
if (lintFilePatterns) {
|
|
const patternIndex = lintFilePatterns.findIndex((item) =>
|
|
item.endsWith('package.json')
|
|
);
|
|
if (patternIndex !== -1) lintFilePatterns?.splice(patternIndex, 1);
|
|
} else {
|
|
projectJson.targets.lint = {
|
|
executor: '@nx/eslint:lint',
|
|
outputs: ['{options.outputFile}'],
|
|
};
|
|
}
|
|
|
|
await writeProjectJson(
|
|
`packages/pieces/${pieceType}/${pieceName}`,
|
|
projectJson
|
|
);
|
|
};
|
|
const addEslintFile = async (pieceName: string, pieceType: string) => {
|
|
const eslintFile ={
|
|
"extends": ["../../../../.eslintrc.base.json"],
|
|
"ignorePatterns": ["!**/*"],
|
|
"overrides": [
|
|
{
|
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
|
"rules": {}
|
|
},
|
|
{
|
|
"files": ["*.ts", "*.tsx"],
|
|
"rules": {}
|
|
},
|
|
{
|
|
"files": ["*.js", "*.jsx"],
|
|
"rules": {}
|
|
}
|
|
]
|
|
}
|
|
|
|
|
|
await writePackageEslint(
|
|
`packages/pieces/${pieceType}/${pieceName}`,
|
|
eslintFile
|
|
);
|
|
};
|
|
const setupGeneratedLibrary = async (pieceName: string, pieceType: string) => {
|
|
await removeUnusedFiles(pieceName, pieceType);
|
|
await generateIndexTsFile(pieceName, pieceType);
|
|
await updateProjectJsonConfig(pieceName, pieceType);
|
|
await addEslintFile(pieceName, pieceType);
|
|
};
|
|
|
|
export const createPiece = async (
|
|
pieceName: string,
|
|
packageName: string,
|
|
pieceType: string
|
|
) => {
|
|
await validatePieceName(pieceName);
|
|
await validatePackageName(packageName);
|
|
await checkIfPieceExists(pieceName);
|
|
await nxGenerateNodeLibrary(pieceName, packageName, pieceType);
|
|
await setupGeneratedLibrary(pieceName, pieceType);
|
|
console.log(chalk.green('✨ Done!'));
|
|
console.log(
|
|
chalk.yellow(
|
|
`The piece has been generated at: packages/pieces/${pieceType}/${pieceName}`
|
|
)
|
|
);
|
|
};
|
|
|
|
export const createPieceCommand = new Command('create')
|
|
.description('Create a new piece')
|
|
.action(async () => {
|
|
const questions = [
|
|
{
|
|
type: 'input',
|
|
name: 'pieceName',
|
|
message: 'Enter the piece name:',
|
|
},
|
|
{
|
|
type: 'input',
|
|
name: 'packageName',
|
|
message: 'Enter the package name:',
|
|
default: (answers: any) => `@activepieces/piece-${answers.pieceName}`,
|
|
when: (answers: any) => answers.pieceName !== undefined,
|
|
},
|
|
{
|
|
type: 'list',
|
|
name: 'pieceType',
|
|
message: 'Select the piece type:',
|
|
choices: ['community', 'custom'],
|
|
default: 'community',
|
|
},
|
|
];
|
|
|
|
const answers = await inquirer.prompt(questions);
|
|
createPiece(answers.pieceName, answers.packageName, answers.pieceType);
|
|
});
|