@swell/cli
Version:
Swell's command line interface/utility
277 lines (276 loc) • 10.5 kB
JavaScript
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);
}
}