garrettmills
1d5056b753
All checks were successful
continuous-integration/drone/push Build is passing
91 lines
3.3 KiB
TypeScript
91 lines
3.3 KiB
TypeScript
import {Directive, OptionDefinition} from '../Directive'
|
|
import {PositionalOption} from './options/PositionalOption'
|
|
import {CommandLine} from '../service'
|
|
import {Inject, Injectable} from '../../di'
|
|
import {ErrorWithContext} from '../../util'
|
|
|
|
/**
|
|
* Create a new file based on a template registered with the CommandLine service.
|
|
*/
|
|
@Injectable()
|
|
export class TemplateDirective extends Directive {
|
|
@Inject()
|
|
protected readonly cli!: CommandLine
|
|
|
|
getKeywords(): string | string[] {
|
|
return ['new', 'make']
|
|
}
|
|
|
|
getDescription(): string {
|
|
return 'create a new file from a registered template'
|
|
}
|
|
|
|
getOptions(): OptionDefinition[] {
|
|
const registeredTemplates = this.cli.getTemplates()
|
|
const template = new PositionalOption('template_name', 'the template to base the new file on (e.g. model, controller)')
|
|
template.whitelist(...registeredTemplates.pluck('name').all())
|
|
|
|
const destination = new PositionalOption('file_name', 'canonical name of the file to create (e.g. auth:Group, dash:Activity)')
|
|
|
|
return [template, destination]
|
|
}
|
|
|
|
getHelpText(): string {
|
|
const registeredTemplates = this.cli.getTemplates()
|
|
|
|
return [
|
|
'Modules in Extollo register templates that can be used to quickly create common file types.',
|
|
'',
|
|
'For example, you can create a new model from @extollo/orm using the "model" template:',
|
|
'',
|
|
'./ex new model auth:Group',
|
|
'',
|
|
'This would create a new Group model in the ./src/app/models/auth/Group.model.ts file.',
|
|
'',
|
|
'AVAILABLE TEMPLATES:',
|
|
'',
|
|
...(registeredTemplates.map(template => {
|
|
return ` - ${template.name}: ${template.description}`
|
|
}).all()),
|
|
].join('\n')
|
|
}
|
|
|
|
async handle(): Promise<void> {
|
|
const templateName: string = this.option('template_name')
|
|
const destinationName: string = this.option('file_name')
|
|
|
|
if ( destinationName.includes('/') || destinationName.includes('\\') ) {
|
|
this.error(`The destination should be a canonical name, not a file path.`)
|
|
this.error(`Reference sub-directories using the : character instead.`)
|
|
this.error(`Did you mean ${destinationName.replace(/\/|\\/g, ':')}?`)
|
|
process.exitCode = 1
|
|
return
|
|
}
|
|
|
|
const template = this.cli.getTemplate(templateName)
|
|
if ( !template ) {
|
|
throw new ErrorWithContext(`Unable to find template supposedly registered with name: ${templateName}`, {
|
|
templateName,
|
|
destinationName,
|
|
})
|
|
}
|
|
|
|
const name = destinationName.split(':').reverse()[0]
|
|
const path = this.app().path('..', 'src', 'app', ...template.baseAppPath, ...(`${destinationName}${template.fileSuffix}`).split(':'))
|
|
|
|
if ( await path.exists() ) {
|
|
this.error(`File already exists: ${path}`)
|
|
process.exitCode = 1
|
|
return
|
|
}
|
|
|
|
// Make sure the parent direction exists
|
|
await path.concat('..').mkdir()
|
|
|
|
const contents = await template.render(name, destinationName, path.clone())
|
|
await path.write(contents)
|
|
|
|
this.success(`Created new ${template.name} in ${path}`)
|
|
}
|
|
}
|