UNPKG

@swell/cli

Version:

Swell's command line interface/utility

277 lines (276 loc) 10.5 kB
import { input, select } from '@inquirer/prompts'; import { Args, Flags } from '@oclif/core'; import { CreateConfigCommand } from '../../create-config-command.js'; import { ConfigType } from '../../lib/apps/index.js'; import { getFunctionTemplate, } from '../../lib/create/function.js'; import { toFunctionFileName } from '../../lib/create/index.js'; const PERMITTED_TRIGGERS = ['cron', 'model', 'route']; export default class CreateFunction extends CreateConfigCommand { static args = { name: Args.string({ default: '', description: 'Filename with extension (e.g., my-func.ts)', }), trigger: Args.string({ default: '', description: 'cron | model | route', options: PERMITTED_TRIGGERS, }), }; static examples = [ '$ swell create function', '$ swell create function order-handler.ts model -e order.created,order.updated -y', '$ swell create function cleanup.ts cron -s "0 0 * * *" -y', '$ swell create function api-endpoint.ts route -r public -y', ]; static helpMeta = { usageDirect: '<name> <trigger> [...] -y', variantSection: { title: 'TRIGGER OPTIONS', variants: [ { name: 'model', flag: '-e, --events=<value>', description: 'Model events (e.g., order.created,order.updated)', }, { name: 'cron', flag: '-s, --schedule=<value>', description: 'Cron expression (e.g., "0 0 * * *")', }, { name: 'route', flag: '-r, --route=<option>', description: 'Access: public (default) | private', }, ], }, variantFlags: ['events', 'schedule', 'route'], }; static flags = { description: Flags.string({ char: 'd', default: '', description: 'Description', }), events: Flags.string({ char: 'e', default: '', description: 'Model events (e.g., order.created,order.updated)', }), overwrite: Flags.boolean({ default: false, description: 'Overwrite existing file', }), route: Flags.string({ char: 'r', default: '', description: 'Access: public | private', options: ['public', 'private'], }), schedule: Flags.string({ char: 's', default: '', description: 'Cron expression (e.g., "0 0 * * *")', }), yes: Flags.boolean({ char: 'y', description: 'Skip prompts, require all arguments', }), }; static summary = 'Create a serverless function in JavaScript or TypeScript.'; createType = ConfigType.FUNCTION; async run() { const { args, flags } = await this.parse(CreateFunction); const confirmYes = Boolean(flags.yes); // NON-INTERACTIVE PATH if (confirmYes) { // Validate name argument if (!args.name) { this.error('Missing required argument for non-interactive mode: NAME\n\nExample: swell create function my-func.ts model -e order.created -y', { exit: 1 }); } // Validate trigger argument if (!args.trigger) { this.error('Missing required argument for non-interactive mode: TRIGGER\n\nValid triggers: cron, model, route\n\nExample: swell create function my-func.ts model -e order.created -y', { exit: 1 }); } // Validate filename has extension const language = args.name.endsWith('.ts') ? 'ts' : args.name.endsWith('.js') ? 'js' : null; if (!language) { this.error('Function name must end with .ts or .js extension\n\nExample: swell create function my-func.ts model -e order.created -y', { exit: 1 }); } const trigger = args.trigger; // Validate trigger-specific requirements if (trigger === 'model' && !flags.events) { this.error('Missing required flag for model trigger: --events\n\nExample: swell create function my-func.ts model -e order.created,order.updated -y', { exit: 1 }); } if (trigger === 'cron' && !flags.schedule) { this.error('Missing required flag for cron trigger: --schedule\n\nExample: swell create function my-func.ts cron -s "0 0 * * *" -y', { exit: 1 }); } // Build params with defaults const writeFunctionFileParams = { description: flags.description || '', events: flags.events || '', extension: undefined, functionName: args.name, language, route: flags.route || 'public', schedule: flags.schedule || '', trigger, }; // Write file without confirmation await this.writeFunctionFile(writeFunctionFileParams, flags.overwrite, /* shouldConfirm */ false); return; } // INTERACTIVE PATH const { name: functionName, trigger } = args; const { description, events, overwrite, route, schedule } = flags; let writeFunctionFileParams = { description, events, extension: undefined, functionName: functionName || '', language: (functionName?.endsWith('.ts') ? 'ts' : functionName?.endsWith('.js') ? 'js' : undefined), route, schedule, trigger: trigger, }; writeFunctionFileParams = { ...writeFunctionFileParams, ...(await this.gatherInput(writeFunctionFileParams)), }; await this.writeFunctionFile(writeFunctionFileParams, overwrite, /* shouldConfirm */ true); } async gatherInput(params) { // Check if there are any extensions in the app configuration const extensions = this.swellConfig.get('extensions') || []; let { extension } = params; const extensionsWithId = extensions.filter((ext) => ext.id); if (extensionsWithId.length > 0 && !extension) { const extensionChoices = [ { name: 'Not for an extension', value: '' }, ...extensionsWithId.map((ext) => ({ name: ext.id, value: ext.id, })), ]; extension = await select({ choices: extensionChoices, message: 'Is this function for an app extension?', }); } let { language } = params; if (!language) { language = await select({ choices: [ { name: 'TypeScript', value: 'ts', }, { name: 'JavaScript', value: 'js', }, ], message: 'What language are you using?', }); } let { trigger } = params; if (!trigger) { trigger = await select({ choices: [ { name: 'Event (model)', value: 'model', }, { name: 'Schedule (cron)', value: 'cron', }, { name: 'HTTP (route)', value: 'route', }, ], message: 'How will your function be triggered?', }); } let { events } = params; if (trigger === 'model' && !events) { events = await input({ default: 'product.created, etc...', message: 'Enter event types to trigger this function (comma separated)', }); if (events === 'product.created, etc...') { events = ''; } } let { schedule } = params; if (trigger === 'cron' && !schedule) { schedule = await input({ default: '0 0 * * *', message: 'Enter a crontab value', }); } let { route } = params; if (trigger === 'route' && !route) { route = await select({ choices: [ { name: 'Public', value: 'public' }, { name: 'Private', value: 'private' }, ], message: 'Should the HTTP route be public (using public_key) or private (using secret_key)?', }); } let { functionName } = params; if (!functionName) { functionName = await input({ default: functionName, message: 'Name your function', }); } let { description } = params; if (!description) { description = await input({ default: 'Does a thing when another other thing happens', message: 'Describe what the function does', }); } return { description, events: events || '', extension: extension || undefined, functionName, language, route: route || '', schedule: schedule || '', trigger, }; } async writeFunctionFile({ description, events, extension, functionName, language, route, schedule, trigger, }, overwrite, shouldConfirm = true) { const fileName = toFunctionFileName(functionName); await this.createFile({ extension: language, fileBody: getFunctionTemplate({ description, events, extension, functionName, language, route, schedule, trigger, }), fileName, }, overwrite, shouldConfirm); } }