Skip to content

Commit

Permalink
Merge pull request activepieces#3704 from kishanprmr/cli/create-trigger
Browse files Browse the repository at this point in the history
feat(cli): create trigger command
  • Loading branch information
abuaboud authored Jan 20, 2024
2 parents 36a0cbd + 5218e98 commit 30415b9
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 26 deletions.
11 changes: 10 additions & 1 deletion packages/cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Command } from 'commander';
import { createPieceCommand } from './lib/commands/create-piece';
import { createActionCommand } from './lib/commands/create-action';
import { createPieceCommand } from './lib/commands/create-piece';
import { createTriggerCommand } from './lib/commands/create-trigger';

const pieceCommand = new Command('pieces')
.description('Manage pieces');
Expand All @@ -11,11 +12,19 @@ const actionCommand = new Command('actions')
.description('Manage actions');

actionCommand.addCommand(createActionCommand);

const triggerCommand = new Command('triggers')
.description('Manage triggers')

triggerCommand.addCommand(createTriggerCommand)


const program = new Command();

program.version('0.0.1').description('Activepieces CLI');

program.addCommand(pieceCommand);
program.addCommand(actionCommand);
program.addCommand(triggerCommand)

program.parse(process.argv);
49 changes: 24 additions & 25 deletions packages/cli/src/lib/commands/create-piece.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import chalk from 'chalk';
import { Command } from 'commander';
import { rm, writeFile } from 'fs/promises';
import inquirer from 'inquirer';
import assert from 'node:assert';
import { exec } from '../utils/exec';
import {
Expand All @@ -8,16 +10,15 @@ import {
writePackageEslint,
writeProjectJson,
} from '../utils/files';
import { Command } from 'commander';
import inquirer from 'inquirer';
import { findPieceSourceDirectory } from '../utils/piece-utils';

const validatePieceName = async (pieceName: string) => {
console.log(chalk.yellow('Validating piece name....'));
const pieceNamePattern = /^[A-Za-z0-9-]+$/;
if (!pieceNamePattern.test(pieceName)) {
console.log(
chalk.red(
'> piece name should contain alphanumeric characters and hyphens only, provided name : ${pieceName}'
`🚨 Invalid piece name: ${pieceName}. Piece names can only contain lowercase letters, numbers, and hyphens.`
)
);
process.exit(1);
Expand All @@ -30,40 +31,38 @@ const validatePackageName = async (packageName: string) => {
if (!packageNamePattern.test(packageName)) {
console.log(
chalk.red(
'> Invalid package name: ${packageName}. Package names can only contain lowercase letters, numbers, and hyphens.'
`🚨 Invalid package name: ${packageName}. Package names can only contain lowercase letters, numbers, and hyphens.`
)
);
process.exit(1);
}
};

const validatePieceType = async (pieceType: string) => {
if (!['community', 'custom'].includes(pieceType)) {
console.log(
chalk.red(
'> piece type can be either custom or community only.provided type :${pieceType}'
)
);
const checkIfPieceExists = async (pieceName: string) => {
const path = await findPieceSourceDirectory(pieceName);
if (path) {
console.log(chalk.red(`🚨 Piece already exists at ${path}`));
process.exit(1);
}
};

const nxGenerateNodeLibrary = async (
pieceName: string,
packageName: string,
pieceType: string
) => {
const nxGenerateCommand = `
npx nx generate @nx/node:library ${pieceName} \
--directory=pieces/${pieceType} \
--importPath=${packageName} \
--publishable \
--buildable \
--standaloneConfig \
--strict \
--unitTestRunner=none
`;

console.log(chalk.blue('Executing nx command: ${nxGenerateCommand}'));
const nxGenerateCommand = [
`npx nx generate @nx/node:library ${pieceName}`,
`--directory=pieces/${pieceType}`,
`--importPath=${packageName}`,
'--publishable',
'--buildable',
'--standaloneConfig',
'--strict',
'--unitTestRunner=none'
].join(' ')

console.log(chalk.blue(`🛠️ Executing nx command: ${nxGenerateCommand}`));

await exec(nxGenerateCommand);
};
Expand Down Expand Up @@ -162,13 +161,13 @@ const createPiece = async (
) => {
await validatePieceName(pieceName);
await validatePackageName(packageName);
await validatePieceType(pieceType);
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}'
`The piece has been generated at: packages/pieces/${pieceType}/${pieceName}`
)
);
};
Expand Down
147 changes: 147 additions & 0 deletions packages/cli/src/lib/commands/create-trigger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import chalk from 'chalk';
import { Command } from 'commander';
import inquirer from 'inquirer';
import { writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { checkIfFileExists, makeFolderRecursive } from '../utils/files';
import { displayNameToCamelCase, displayNameToKebabCase, findPieceSourceDirectory } from '../utils/piece-utils';

function createTriggerTemplate(displayName: string, description: string, technique: string) {
const camelCase = displayNameToCamelCase(displayName)
let triggerTemplate = ''
if (technique === 'polling') {
triggerTemplate = `
import { createTrigger, TriggerStrategy, PiecePropValueSchema } from '@activepieces/pieces-framework';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import dayjs from 'dayjs';
// replace auth with piece auth varible
const polling: Polling< PiecePropValueSchema<typeof auth>, Record<string, never> > = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ propsValue, lastFetchEpochMS }) => {
// implement the logic to fetch the items
const items = [ {id: 1, created_date: '2021-01-01T00:00:00Z'}, {id: 2, created_date: '2021-01-01T00:00:00Z'}];
return items.map((item) => ({
epochMilliSeconds: dayjs(item.created_date).valueOf(),
data: item,
}));
}
}
export const ${camelCase} = createTrigger({
// auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication,
name: '${camelCase}',
displayName: '${displayName}',
description: '${description}',
props: {},
sampleData: {},
type: TriggerStrategy.POLLING,
async test(context) {
const { store, auth, propsValue } = context;
return await pollingHelper.test(polling, { store, auth, propsValue });
},
async onEnable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onEnable(polling, { store, auth, propsValue });
},
async onDisable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onDisable(polling, { store, auth, propsValue });
},
async run(context) {
const { store, auth, propsValue } = context;
return await pollingHelper.poll(polling, { store, auth, propsValue });
},
});`;
}
else {
triggerTemplate = `
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
export const ${camelCase} = createTrigger({
// auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication,
name: '${camelCase}',
displayName: '${displayName}',
description: '${description}',
props: {},
sampleData: {},
type: TriggerStrategy.WEBHOOK,
async onEnable(context){
// implement webhook creation logic
},
async onDisable(context){
// implement webhook deletion logic
},
async run(context){
return [context.payload.body]
}
})`;

}

return triggerTemplate
}
const checkIfPieceExists = async (pieceName: string) => {
const path = await findPieceSourceDirectory(pieceName);
if (!path) {
console.log(chalk.red(`🚨 Piece ${pieceName} not found`));
process.exit(1);
}
};

const checkIfTriggerExists = async (triggerPath: string) => {
if (await checkIfFileExists(triggerPath)) {
console.log(chalk.red(`🚨 Trigger already exists at ${triggerPath}`));
process.exit(1);
}
}
const createTrigger = async (pieceName: string, displayTriggerName: string, triggerDescription: string, triggerTechnique: string) => {
const triggerTemplate = createTriggerTemplate(displayTriggerName, triggerDescription, triggerTechnique)
const triggerName = displayNameToKebabCase(displayTriggerName)
const path = await findPieceSourceDirectory(pieceName);
await checkIfPieceExists(pieceName);
console.log(chalk.blue(`Piece path: ${path}`))

const triggersFolder = join(path, 'src', 'lib', 'triggers')
const triggerPath = join(triggersFolder, `${triggerName}.ts`)
await checkIfTriggerExists(triggerPath)

await makeFolderRecursive(triggersFolder);
await writeFile(triggerPath, triggerTemplate);
console.log(chalk.yellow('✨'), `Trigger ${triggerPath} created`);
};


export const createTriggerCommand = new Command('create')
.description('Create a new trigger')
.action(async () => {
const questions = [
{
type: 'input',
name: 'pieceName',
message: 'Enter the piece folder name:',
placeholder: 'google-drive',
},
{
type: 'input',
name: 'triggerName',
message: 'Enter the trigger display name:',
},
{
type: 'input',
name: 'triggerDescription',
message: 'Enter the trigger description:',
},
{
type: 'list',
name: 'triggerTechnique',
message: 'Select the trigger technique:',
choices: ['polling', 'webhook'],
default: 'webhook',
},
];

const answers = await inquirer.prompt(questions);
createTrigger(answers.pieceName, answers.triggerName, answers.triggerDescription, answers.triggerTechnique);
});

0 comments on commit 30415b9

Please sign in to comment.