Repository: aliyun/fun (js)

Issue:
函数计算没开通的情况，提示开通FC服务
提示去控制台开通

Pull request description:
open FC permissions
fix #238 

Patch:
diff --git a/README-en.md b/README-en.md
index 4b881d6..6a71ac7 100644
--- a/README-en.md
+++ b/README-en.md
@@ -33,6 +33,12 @@ We have prepared a series of tutorials to help you use the Fun tool more easily:
       - [开发函数计算的正确姿势——支持 ES6 语法和 webpack 压缩](https://yq.aliyun.com/articles/701714)：Help quickly build a Function-Compute of nodejs project skeleton, support es6 code compiled into es5.
       - [函数计算实现 oss 上传较小 zip 压缩文件的自动解压](https://github.com/coco-super/simple-fc-uncompress-service-for-oss)：Help quickly build a Function-Compute project to unzip oss files using simple method
       - [函数计算实现 oss 上传较大 zip 压缩文件的自动解压](https://github.com/coco-super/streaming-fc-uncompress-service-for-oss)：Help quickly build a Function-Compute project to unzip oss files using streaming method
+      - [函数计算 TensorFlow CharRNN 五言绝句古诗模板](https://github.com/vangie/poetry)：Template project for writing Five-Character Verse using TensorFlow CharRNN.
+      - [函数计算 selenium chrome java 模板项目](https://github.com/vangie/packed-selenium-java-example)：Java template for selenium chrome compressed by brotli.
+      - [函数计算 couchbase 模板项目](https://github.com/vangie/couchbase-example)：Template project for installing and configuring couchbase on function compute.
+      - [express 项目接入函数计算](https://github.com/muxiangqiu/fc-express-nodejs8)：Help quickly build a express project of Function-Compute.
+      - [nextjs 应用接入到函数计算](https://github.com/muxiangqiu/fc-next-nodejs8)：Help quickly build a next.js project of Function-Compute.
+      - [es6 代码编译成 es5 项目模板](https://github.com/muxiangqiu/fc-skeleton-nodejs8)：Help quickly build a webpack project of Function-Compute in order to compile es6 to es5.
   - **Dependences installation**: Articles on how to use fun install to quickly install  dependencies.
     - [开发函数计算的正确姿势 —— 安装第三方依赖](https://yq.aliyun.com/articles/688062): introduce the basic usage of fun install.
     - [函数运行环境系统动态链接库版本太低？函数计算 fun 神助力分忧解难](https://yq.aliyun.com/articles/690856): Introduce how to use fun install to upgrade the system Dynamic-link libraries.
diff --git a/README.md b/README.md
index 62ff990..11a0537 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,12 @@ Fun 作为一个命令行工具，内置了多个子命令，比如 config、loc
       - [开发函数计算的正确姿势——支持 ES6 语法和 webpack 压缩](https://yq.aliyun.com/articles/701714)：帮助快速搭建一个函数计算 nodejs 项目骨架，支持 es6 代码编译成 es5。
       - [函数计算实现 oss 上传较小 zip 压缩文件的自动解压](https://github.com/coco-super/simple-fc-uncompress-service-for-oss)：快速搭建 oss 上传较小 zip 压缩文件自动解压（简单法）的模板项目。
       - [函数计算实现 oss 上传较大 zip 压缩文件的自动解压](https://github.com/coco-super/streaming-fc-uncompress-service-for-oss)：快速搭建 oss 上传超大 zip 压缩文件自动解压（流式法）的模板项目。
+      - [函数计算 TensorFlow CharRNN 五言绝句古诗模板](https://github.com/vangie/poetry)：介绍把一个 TensorFlow CharRNN 训练的自动写五言绝句古诗的模型部署到函数计算。
+      - [函数计算 selenium chrome java 模板项目](https://github.com/vangie/packed-selenium-java-example)：一个使用 brotli 算法压缩的 selenium chrome java 模板项目。
+      - [函数计算 couchbase 模板项目](https://github.com/vangie/couchbase-example)：该项目模板是一个在阿里云函数计算平台 nodejs8 环境下安装并配置 couchbase 的模板项目。
+      - [express 项目接入函数计算](https://github.com/muxiangqiu/fc-express-nodejs8)：通过该模板，可以快速将 express 项目接入函数计算。
+      - [nextjs 应用接入到函数计算](https://github.com/muxiangqiu/fc-next-nodejs8)：提供了一个 fun 模块，通过该模板，三分钟就可以让 next.js 应用在函数计算中运行起来。
+      - [es6 代码编译成 es5 项目模板](https://github.com/muxiangqiu/fc-skeleton-nodejs8)：提供一个 fun 模板，通过 webpack 将 es6 代码编译成 es5，并且剪切打包压缩成一个 js 文件，然后将该 js 文件上传到函数计算中运行。
   - **依赖安装**：关于如何使用 fun install 快速安装函数依赖的文章。
     - [开发函数计算的正确姿势 —— 安装第三方依赖](https://yq.aliyun.com/articles/688062)：介绍了 fun install 的基本用法。
     - [函数运行环境系统动态链接库版本太低？函数计算 fun 神助力分忧解难](https://yq.aliyun.com/articles/690856)：介绍了如何利用 fun install，升级系统动态链接库。
diff --git a/lib/deploy/deploy-by-tpl.js b/lib/deploy/deploy-by-tpl.js
index 2061c7c..4ed2c47 100644
--- a/lib/deploy/deploy-by-tpl.js
+++ b/lib/deploy/deploy-by-tpl.js
@@ -288,7 +288,7 @@ async function deployCustomDomain(domainName, domainDefinition) {
   var routes = [];
   var transRoute = Object.entries(tplRouteConfig);
   for (var route of transRoute) {
-    let deformedRoute = _.mapKeys(route[1], function(value, key) {
+    let deformedRoute = _.mapKeys(route[1], (value, key) => {
       return _.lowerFirst(key);
     });
     deformedRoute.path = route[0];
diff --git a/lib/deploy/deploy-support.js b/lib/deploy/deploy-support.js
index 64e267e..81a425e 100644
--- a/lib/deploy/deploy-support.js
+++ b/lib/deploy/deploy-support.js
@@ -31,6 +31,8 @@ const readFile = util.promisify(fs.readFile);
 const { green, red } = require('colors');
 const { addEnv } = require('../install/env');
 
+const _ = require('lodash');
+
 const getFcClient = async () => {
   const profile = await getProfile();
 
@@ -290,16 +292,27 @@ async function makeService({
     try {
       service = await fc.getService(serviceName);
     } catch (ex) {
-      
-      if (ex.code === 'AccessDenied' || ex.code === 'ERR_INVALID_CHAR' || ex.code === 'ENOTFOUND') {
-        console.error(red(`The accountId entered is incorrect. We speculate that you used the subuser uid, login name, login name prefix, display name or thers\n`+
-        `please enter the correct accountId, which is the account ID under your primary account. You can reset your account ID by executing fun config\n`+
-        `For the method of taking the primary account ID, please refer to https://account.console.aliyun.com/#/secure`));
+
+      if (ex.code === 'AccessDenied') {
+
+        console.log(red(`\n${ ex.message }`));
+        
+        if (ex.message.indexOf('the caller is not authorized to perform') !== -1) {
+
+          console.error(red(`\nMaybe you can grant AliyunRAMFullAccess policy to the subuser or use the primary account. You can refer to Chinese doc https://github.com/aliyun/fun/blob/master/docs/usage/faq-zh.md#nopermissionerror-you-are-not-authorized-to-do-this-action-resource-acsramxxxxxxxxxxrole-action-ramgetrole or English doc https://github.com/aliyun/fun/blob/master/docs/usage/faq.md#nopermissionerror-you-are-not-authorized-to-do-this-action-resource-acsramxxxxxxxxxxrole-action-ramgetrole for help.\n\nIf you don’t want use the AliyunRAMFullAccess policy or primary account, you can also specify the Role property for Service. You can refer to Chinease doc https://github.com/aliyun/fun/blob/master/docs/specs/2018-04-03-zh-cn.md#aliyunserverlessservice or Enaglish doc https://github.com/aliyun/fun/blob/master/docs/specs/2018-04-03.md#aliyunserverlessservice for help.\n`));
+
+        } else if (ex.message.indexOf('FC service is not enabled for current user') !== -1) {
+
+          console.error(red(`\nFC service is not enabled for current user. Please enable FC service before using fun.\nYou can enable FC service on this page https://www.aliyun.com/product/fc .\n`));
+
+        } else {
+          console.error(red(`\nThe accountId you entered is incorrect. You can only use the primary account id, whether or not you use a sub-account or a primary account ak. You can get primary account ID on this page https://account.console.aliyun.com/#/secure .\n`));
+        }
 
         throw ex;
       } else if (ex.code !== 'ServiceNotFound') {
         debug('error when getService, serviceName is %s, error is: \n%O', serviceName, ex);
-        
+
         console.log(red(`\tretry ${times} times`));
         retry(ex);
       }
@@ -563,7 +576,9 @@ function getTriggerConfig(triggerType, triggerProperties) {
       eventName: triggerProperties.EventName,
       eventVersion: triggerProperties.EventVersion,
       notes: triggerProperties.Notes,
-      filter: triggerProperties.Filter
+      filter: _.mapKeys(triggerProperties.Filter, (value, key) => {
+        return _.lowerFirst(key);
+      })
     };
   }
   console.error(`trigger type is ${triggerType} not supported.`);
@@ -1236,5 +1251,6 @@ module.exports = {
   makeMnsTopic,
   makeInvocationRole,
   getFunCodeAsBase64,
-  getTriggerNameList
+  getTriggerNameList,
+  getTriggerConfig
 };
\ No newline at end of file
diff --git a/lib/init/vcs.js b/lib/init/vcs.js
index a9c9778..bccba82 100644
--- a/lib/init/vcs.js
+++ b/lib/init/vcs.js
@@ -5,6 +5,7 @@ const fs = require('fs');
 const path = require('path');
 const { spawnSync } = require('child_process');
 const debug = require('debug')('fun:vcs');
+const uuid = require('uuid');
 
 function identifyRepo(repoUrl) {
   debug('identify repo...');
@@ -41,12 +42,12 @@ function makeSurePathExists(p) {
 
 }
 
-function cloneArguments(repoType, repoUrl) {
+function cloneArguments(repoType, repoUrl, outputDir) {
   switch (repoType) {
   case 'git':
-    return ['clone', '--depth=1', repoUrl];
+    return ['clone', '--depth=1', repoUrl, outputDir];
   default:
-    return ['clone', repoUrl];
+    return ['clone', repoUrl, outputDir];
   }
 }
 
@@ -64,19 +65,17 @@ async function clone(repoUrl, cloneToDir = '.', checkout) {
   }
 
   debug('repo type is: %s', repoType);
-  debug('repo url is: %s', repoUrl);
+  debug('repo url is: %s', repoUrl);  
+
+  const outputDir = '.fun-init-cache-' + uuid.v1();
+  let repoDir = path.join(cloneToDir, outputDir);
 
   repoUrl = repoUrl.replace(/'^\/+|\/+$/g, '');
-  let tail = repoUrl.split('/').pop();
-  let repoDir = '';
-  if (repoType === 'git') {
-    tail = tail.split('.git')[0];
-  }
-  repoDir = path.join(cloneToDir, tail);
+
   debug('repoDir is %s', repoDir);
   await promptForExistingPath(repoDir, `You've downloaded ${repoDir} before. Is it okay to delete and re-download it?`, true);
   console.log('start cloning...');
-  spawnSync(repoType, cloneArguments(repoType, repoUrl), { cmd: repoDir, stdio: 'inherit' });
+  spawnSync(repoType, cloneArguments(repoType, repoUrl, outputDir), { cmd: repoDir, stdio: 'inherit' });
   console.log('finish cloning.');
 
   if (checkout) {


Test patch:
diff --git a/test/deploy/deploy-by-tpl.test.js b/test/deploy/deploy-by-tpl.test.js
index e634e2b..5f6133f 100644
--- a/test/deploy/deploy-by-tpl.test.js
+++ b/test/deploy/deploy-by-tpl.test.js
@@ -1,6 +1,7 @@
 'use strict';
 
 const proxyquire = require('proxyquire');
+const expect = require('expect.js');
 const sinon = require('sinon');
 const sandbox = sinon.createSandbox();
 const assert = sinon.assert;
@@ -1087,3 +1088,29 @@ describe('custom domain', () => {
     });
   });
 });
+
+describe('cdn domain capitalization', () => {
+
+  it('Domain->domain', async() => {
+    var capitalConfig = deploySupport.getTriggerConfig('CDN', {
+      'EventName': 'CachedObjectsRefreshed',
+      'EventVersion': '1.0.0',
+      'Notes': 'cdn events trigger test',
+      'Filter': {
+        'Domain': [
+          'cdn-trigger.sunfeiyu.top'
+        ]
+      }
+    });
+    expect(capitalConfig).to.eql({
+      'eventName': 'CachedObjectsRefreshed',
+      'eventVersion': '1.0.0',
+      'notes': 'cdn events trigger test',
+      'filter': {
+        'domain': [
+          'cdn-trigger.sunfeiyu.top'
+        ]
+      }
+    });
+  });
+});
diff --git a/test/init/vcs.test.js b/test/init/vcs.test.js
index 6dd24bb..3e6c3a0 100644
--- a/test/init/vcs.test.js
+++ b/test/init/vcs.test.js
@@ -3,6 +3,7 @@
 const proxyquire = require('proxyquire');
 const sinon = require('sinon');
 const expect = require('expect.js');
+const path = require('path');
 
 const sandbox = sinon.createSandbox();
 const fs = {
@@ -10,11 +11,6 @@ const fs = {
   existsSync: sandbox.stub()
 };
 
-const path = {
-  resolve: (a, b) => a,
-  join: (a, b) => a + b
-};
-
 const prompt = {
   promptForExistingPath: sandbox.stub()
 };
@@ -26,10 +22,13 @@ const child_process = {
 const commandExists = {
   sync: sandbox.stub()
 };
+const uuid = {
+  v1: sandbox.stub()
+};
 
 const vcsStub = proxyquire('../../lib/init/vcs', {
+  'uuid': uuid,
   'fs': fs,
-  'path': path,
   './prompt': prompt,
   'child_process': child_process,
   'command-exists': commandExists
@@ -69,11 +68,10 @@ describe('vcs', () => {
   it('clone with https://github.com/foo/bar.git', async () => {
     fs.existsSync.returns(true);
     commandExists.sync.returns(true);
+    uuid.v1.returns('uuid');
     const repoUrl = 'https://github.com/foo/bar.git';
     const repoDir = await vcsStub.clone(repoUrl, '.');
-    sandbox.assert.calledWith(child_process.spawnSync, 'git', ['clone', '--depth=1', repoUrl], {cmd: repoDir, stdio: 'inherit'});
-    expect(repoDir).to.be('.bar');
-
+    sandbox.assert.calledWith(child_process.spawnSync, 'git', ['clone', '--depth=1', repoUrl, '.fun-init-cache-uuid'], {cmd: repoDir, stdio: 'inherit'});
+    expect(repoDir).to.be(path.join(process.cwd(), '.fun-init-cache-uuid'));
   });
-
 });
\ No newline at end of file