From ee445dd8ee1b3e812abb30e34e2e9313ad69c0c2 Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Mon, 20 Jan 2020 19:43:10 +0800 Subject: [PATCH] feat: add create plugin (#26) * chore: fix boilerplate * chore: add create cmd * chore: add create cmd * test: fix case * chore: clean code --- packages/faas-cli-plugin-create/package.json | 28 +++ packages/faas-cli-plugin-create/src/index.ts | 162 ++++++++++++++++++ packages/faas-cli-plugin-create/src/list.json | 10 ++ .../test/create.test.ts | 43 +++++ .../faas-cli-plugin-create/test/helper.ts | 34 ++++ packages/faas-cli-plugin-create/tsconfig.json | 22 +++ packages/faas-cli/package.json | 3 +- packages/faas-cli/src/index.ts | 2 + 8 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 packages/faas-cli-plugin-create/package.json create mode 100644 packages/faas-cli-plugin-create/src/index.ts create mode 100644 packages/faas-cli-plugin-create/src/list.json create mode 100644 packages/faas-cli-plugin-create/test/create.test.ts create mode 100644 packages/faas-cli-plugin-create/test/helper.ts create mode 100644 packages/faas-cli-plugin-create/tsconfig.json diff --git a/packages/faas-cli-plugin-create/package.json b/packages/faas-cli-plugin-create/package.json new file mode 100644 index 00000000..e985f882 --- /dev/null +++ b/packages/faas-cli-plugin-create/package.json @@ -0,0 +1,28 @@ +{ + "name": "@midwayjs/fcli-plugin-create", + "version": "0.2.10", + "main": "dist/index", + "typings": "dist/index.d.ts", + "dependencies": { + "@midwayjs/fcli-command-core": "^0.2.10", + "enquirer": "^2.3.4", + "light-generator": "^1.3.2" + }, + "devDependencies": { + "midway-bin": "1" + }, + "files": [ + "src", + "dist" + ], + "scripts": { + "build": "npm run lint && midway-bin build -c", + "lint": "../../node_modules/.bin/tslint --format prose -c ../../tslint.json src/**/*.ts test/**/*.ts", + "test": "NODE_ENV=test midway-bin test --ts --full-trace", + "debug": "npm run lint && NODE_ENV=test midway-bin test --ts --full-trace --inspect-brk=9229", + "cov": "NODE_ENV=unittest midway-bin cov --ts", + "clean": "midway-bin clean", + "autod": "midway-bin autod" + }, + "gitHead": "b67e2753cbdcc91813067ba2a1bb1ce7e85a3dff" +} diff --git a/packages/faas-cli-plugin-create/src/index.ts b/packages/faas-cli-plugin-create/src/index.ts new file mode 100644 index 00000000..6334cdbe --- /dev/null +++ b/packages/faas-cli-plugin-create/src/index.ts @@ -0,0 +1,162 @@ +import { BasePlugin } from '@midwayjs/fcli-command-core'; +import { join } from 'path'; +const templateList = require('./list'); +const { LightGenerator } = require('light-generator'); +const { Select, Input, Form } = require('enquirer'); + +async function sleep(timeout) { + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, timeout); + }); +} + +// class wide constants +const validTemplates = Object.keys(templateList); +const humanReadableTemplateList = `${validTemplates + .slice(0, -1) + .map(template => `"${template}"`) + .join(', ')} and "${validTemplates.slice(-1)}"`; + +export class CreatePlugin extends BasePlugin { + core: any; + options: any; + servicePath = this.core.config.servicePath; + showPrompt = true; + npmClient = 'npm'; + _innerPrompt; + + set prompt(value) { + const originRun = value.run; + value.run = async () => { + await this.beforePromptSubmit(); + return originRun.call(value); + }; + this._innerPrompt = value; + } + + get prompt() { + return this._innerPrompt; + } + + commands = { + create: { + usage: 'Create new Ali FaaS service', + lifecycleEvents: ['create'], + options: { + template: { + usage: `Template for the service. Available templates: ${humanReadableTemplateList}`, + shortcut: 't', + }, + path: { + usage: + 'The path where the service should be created (e.g. --path my-service)', + shortcut: 'p', + }, + }, + }, + }; + + hooks = { + 'create:create': this.create.bind(this), + }; + + async beforePromptSubmit() {} + + async create() { + this.core.cli.log('Generating boilerplate...'); + + if (this.options['template']) { + await this.createFromTemplate(); + } else { + this.prompt = new Select({ + name: 'templateName', + message: 'Hello, traveller.\n Which template do you like?', + choices: Object.keys(templateList).map(template => { + return `${template} - ${templateList[template].desc}`; + }), + result: value => { + return value.split(' - ')[0]; + }, + show: this.showPrompt, + }); + + this.options.template = await this.prompt.run(); + await this.createFromTemplate(); + } + // done + this.printUsage(); + } + + async createFromTemplate() { + if (!this.options.path) { + this.prompt = new Input({ + message: `The directory where the service should be created`, + initial: 'my_new_serverless', + show: this.showPrompt, + }); + const targetPath = await this.prompt.run(); + this.options.path = targetPath; + } + + const boilerplatePath = this.options.path || ''; + const newPath = join(this.servicePath, boilerplatePath); + const lightGenerator = new LightGenerator(); + const generator = lightGenerator.defineNpmPackage({ + npmClient: this.npmClient, + npmPackage: templateList[this.options.template].package, + targetPath: newPath, + }); + + const args = await generator.getParameterList(); + const argsKeys = Object.keys(args); + if (argsKeys && argsKeys.length) { + this.prompt = new Form({ + name: 'user', + message: 'Please provide the following information:', + choices: argsKeys.map(argsKey => { + return { + name: `${argsKey}`, + message: `${args[argsKey].desc}`, + initial: `${args[argsKey].default}`, + }; + }), + show: this.showPrompt, + }); + const parameters = await this.prompt.run(); + await this.readyGenerate(); + await generator.run(parameters); + } else { + await this.readyGenerate(); + await generator.run(); + } + this.core.cli.log( + `Successfully generated boilerplate for template: "${this.options.template}"` + ); + this.core.cli.log(); + } + + async readyGenerate() { + this.core.cli.log(); + await sleep(1000); + this.core.cli.log('1...'); + await sleep(1000); + this.core.cli.log('2...'); + await sleep(1000); + this.core.cli.log('3...'); + await sleep(1000); + this.core.cli.log('Enjoy it...'); + this.core.cli.log(); + } + + printUsage() { + this.core.cli.log(`Usage: + - cd ${this.options.path} + - ${this.npmClient} install + - ${this.npmClient} run test + - and read README.md + `); + this.core.cli.log('Document: https://midwayjs.org/faas'); + } +} diff --git a/packages/faas-cli-plugin-create/src/list.json b/packages/faas-cli-plugin-create/src/list.json new file mode 100644 index 00000000..63425aee --- /dev/null +++ b/packages/faas-cli-plugin-create/src/list.json @@ -0,0 +1,10 @@ +{ + "faas-standard": { + "desc": "A serverless boilerplate for aliyun fc, tecent scf and so on", + "package": "@midwayjs/faas-boilerplate-standard" + }, + "faas-layer": { + "desc": "A serverless runtime layer boilerplate", + "package": "@midwayjs/faas-boilerplate-layer" + } +} diff --git a/packages/faas-cli-plugin-create/test/create.test.ts b/packages/faas-cli-plugin-create/test/create.test.ts new file mode 100644 index 00000000..4143b516 --- /dev/null +++ b/packages/faas-cli-plugin-create/test/create.test.ts @@ -0,0 +1,43 @@ +import { CommandHookCore, loadSpec } from '@midwayjs/fcli-command-core'; +import { join } from 'path'; +import { remove, existsSync, readFileSync } from 'fs-extra'; +import { TestCreatePlugin } from './helper'; +import * as assert from 'assert'; + +describe('/test/create.test.ts', () => { + const baseDir = join(__dirname, './tmp'); + beforeEach(async () => { + if (existsSync(baseDir)) { + await remove(baseDir); + } + }); + it('base create faas boilerplate', async () => { + const core = new CommandHookCore({ + config: { + servicePath: baseDir, + }, + commands: ['create'], + service: loadSpec(baseDir), + provider: 'aliyun', + options: { + template: 'faas-standard', + path: 'my_serverless', + }, + log: console, + }); + core.addPlugin(TestCreatePlugin); + await core.ready(); + await core.invoke(['create']); + assert(existsSync(join(baseDir, 'my_serverless/f.yml'))); + assert(existsSync(join(baseDir, 'my_serverless/src'))); + assert(existsSync(join(baseDir, 'my_serverless/test'))); + assert(existsSync(join(baseDir, 'my_serverless/tsconfig.json'))); + assert(existsSync(join(baseDir, 'my_serverless/package.json'))); + const contents = readFileSync( + join(baseDir, 'my_serverless/f.yml'), + 'utf-8' + ); + assert(/serverless-hello-world/.test(contents)); + await remove(baseDir); + }); +}); diff --git a/packages/faas-cli-plugin-create/test/helper.ts b/packages/faas-cli-plugin-create/test/helper.ts new file mode 100644 index 00000000..b5a56611 --- /dev/null +++ b/packages/faas-cli-plugin-create/test/helper.ts @@ -0,0 +1,34 @@ +import { CreatePlugin } from '../src'; + +export class TestCreatePlugin extends CreatePlugin { + // showPrompt = false; + promptAction; + + mockPrompt(arr) { + this.promptAction = arr; + } + + async beforePromptSubmit() { + this.prompt.once('prompt', async () => { + const value = this.promptAction.shift(); + if (value) { + for (const flag of value) { + if (Array.isArray(flag)) { + await this.prompt.keypress.apply(this.prompt, flag); + } else if (typeof flag === 'string') { + try { + for (const key of flag.split('')) { + await this.prompt.keypress(key); + } + } catch (err) { + console.error(err); + } + } + } + } + + await this.prompt.submit(); + this.prompt.close(); + }); + } +} diff --git a/packages/faas-cli-plugin-create/tsconfig.json b/packages/faas-cli-plugin-create/tsconfig.json new file mode 100644 index 00000000..aa9b0585 --- /dev/null +++ b/packages/faas-cli-plugin-create/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "target": "ES2018", + "module": "commonjs", + "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "inlineSourceMap":true, + "noImplicitThis": true, + "noUnusedLocals": true, + "stripInternal": true, + "pretty": true, + "declaration": true, + "outDir": "dist" + }, + "exclude": [ + "dist", + "node_modules", + "test" + ] +} diff --git a/packages/faas-cli/package.json b/packages/faas-cli/package.json index 6de8c649..63dc288c 100644 --- a/packages/faas-cli/package.json +++ b/packages/faas-cli/package.json @@ -9,7 +9,8 @@ "@midwayjs/fcli-plugin-fc": "^0.2.10", "@midwayjs/fcli-plugin-invoke": "^0.2.10", "@midwayjs/fcli-plugin-package": "^0.2.10", - "@midwayjs/fcli-plugin-test": "^0.2.10" + "@midwayjs/fcli-plugin-test": "^0.2.10", + "@midwayjs/fcli-plugin-create": "^0.2.10" }, "bin": { "mf": "bin/fun.js", diff --git a/packages/faas-cli/src/index.ts b/packages/faas-cli/src/index.ts index 544719c0..7c74fa8e 100644 --- a/packages/faas-cli/src/index.ts +++ b/packages/faas-cli/src/index.ts @@ -4,9 +4,11 @@ import { InvokePlugin } from '@midwayjs/fcli-plugin-invoke'; import { PackagePlugin } from '@midwayjs/fcli-plugin-package'; import { DeployPlugin } from '@midwayjs/fcli-plugin-deploy'; import { AliyunFCPlugin } from '@midwayjs/fcli-plugin-fc'; +import { CreatePlugin } from '@midwayjs/fcli-plugin-create'; export class CLI extends BaseCLI { loadDefaultPlugin() { + this.core.addPlugin(CreatePlugin); this.core.addPlugin(InvokePlugin); this.core.addPlugin(TestPlugin); this.core.addPlugin(PackagePlugin);