@@ -3,39 +3,18 @@ const path = require('path')
3
3
const { flags } = require ( '@oclif/command' )
4
4
const Command = require ( '@netlify/cli-utils' )
5
5
const inquirer = require ( 'inquirer' )
6
-
6
+ const readRepoURL = require ( '../../utils/readRepoURL' )
7
7
const templatesDir = path . resolve ( __dirname , '../../functions-templates' )
8
+ const http = require ( 'http' )
9
+ const fetch = require ( 'node-fetch' )
10
+ const cp = require ( 'child_process' )
11
+
8
12
class FunctionsCreateCommand extends Command {
9
13
async run ( ) {
10
14
const { flags, args } = this . parse ( FunctionsCreateCommand )
11
- const { config } = this . netlify
12
- let templates = fs . readdirSync ( templatesDir ) . filter ( x => path . extname ( x ) === '.js' ) // only js templates for now
13
- templates = templates
14
- . map ( t => require ( path . join ( templatesDir , t ) ) )
15
- . sort ( ( a , b ) => ( a . priority || 999 ) - ( b . priority || 999 ) ) // doesnt scale but will be ok for now
16
- const { templatePath } = await inquirer . prompt ( [
17
- {
18
- name : 'templatePath' ,
19
- message : 'pick a template' ,
20
- type : 'list' ,
21
- choices : templates . map ( t => t . metadata )
22
- }
23
- ] )
24
- // pull the rest of the metadata from the template
25
- const { onComplete, copyAssets, templateCode } = require ( path . join ( templatesDir , templatePath ) )
26
-
27
- let template
28
- try {
29
- template = templateCode ( ) // we may pass in args in future to customize the template
30
- } catch ( err ) {
31
- console . error ( 'an error occurred retrieving template code, please check ' + templatePath , err )
32
- process . exit ( 0 )
33
- }
34
-
35
- const name = await getNameFromArgs ( args , flags , path . basename ( templatePath , '.js' ) )
36
-
37
- this . log ( `Creating function ${ name } ` )
38
15
16
+ /* get functions dir (and make it if necessary) */
17
+ const { config } = this . netlify
39
18
const functionsDir = flags . functions || ( config . build && config . build . functions )
40
19
if ( ! functionsDir ) {
41
20
this . log ( 'No functions folder specified in netlify.toml or as an argument' )
@@ -48,14 +27,13 @@ class FunctionsCreateCommand extends Command {
48
27
console . log ( `functions folder ${ functionsDir } created` )
49
28
}
50
29
51
- const functionPath = flags . dir ? path . join ( functionsDir , name , name + '.js' ) : path . join ( functionsDir , name + '.js' )
52
- if ( fs . existsSync ( functionPath ) ) {
53
- this . log ( `Function ${ functionPath } already exists` )
54
- process . exit ( 1 )
55
- }
56
-
57
- if ( flags . dir ) {
58
- const fnFolder = path . join ( functionsDir , name )
30
+ /* either download from URL or scaffold from template */
31
+ if ( flags . url ) {
32
+ // // --url flag specified, download from there
33
+ const folderContents = await readRepoURL ( flags . url )
34
+ const functionName = flags . url . split ( '/' ) . slice ( - 1 ) [ 0 ]
35
+ const nameToUse = await getNameFromArgs ( args , flags , functionName )
36
+ const fnFolder = path . join ( functionsDir , nameToUse )
59
37
if ( fs . existsSync ( fnFolder + '.js' ) && fs . lstatSync ( fnFolder + '.js' ) . isFile ( ) ) {
60
38
this . log ( `A single file version of the function ${ name } already exists at ${ fnFolder } .js` )
61
39
process . exit ( 1 )
@@ -66,21 +44,74 @@ class FunctionsCreateCommand extends Command {
66
44
} catch ( e ) {
67
45
// Ignore
68
46
}
69
- } else if ( fs . existsSync ( functionPath . replace ( / \. j s / , '' ) ) ) {
70
- this . log ( `A folder version of the function ${ name } already exists at ${ functionPath . replace ( / \. j s / , '' ) } ` )
71
- process . exit ( 1 )
72
- }
73
-
74
- fs . writeFileSync ( functionPath , template )
75
- if ( copyAssets ) {
76
- copyAssets . forEach ( src =>
77
- fs . copySync ( path . join ( templatesDir , 'assets' , src ) , path . join ( functionsDir , src ) , {
78
- overwrite : false ,
79
- errorOnExist : false // went with this to make it idempotent, might change in future
47
+ await Promise . all (
48
+ folderContents . map ( ( { name, download_url } ) => {
49
+ return fetch ( download_url ) . then ( res => {
50
+ const finalName = path . basename ( name , '.js' ) === functionName ? nameToUse + '.js' : name
51
+ const dest = fs . createWriteStream ( path . join ( fnFolder , finalName ) )
52
+ res . body . pipe ( dest )
53
+ } )
80
54
} )
81
- ) // copy assets if specified
55
+ )
56
+
57
+ console . log ( `installing dependencies for ${ nameToUse } ...` )
58
+ cp . exec ( 'npm i' , { cwd : path . join ( functionsDir , nameToUse ) } , ( ) => {
59
+ console . log ( `installing dependencies for ${ nameToUse } complete ` )
60
+ } )
61
+ } else {
62
+ // // no --url flag specified, pick from a provided template
63
+ const templatePath = await pickTemplate ( )
64
+ // pull the rest of the metadata from the template
65
+ const { onComplete, copyAssets, templateCode } = require ( path . join ( templatesDir , templatePath ) )
66
+
67
+ let template
68
+ try {
69
+ template = templateCode ( ) // we may pass in args in future to customize the template
70
+ } catch ( err ) {
71
+ console . error ( 'an error occurred retrieving template code, please check ' + templatePath , err )
72
+ process . exit ( 0 )
73
+ }
74
+
75
+ const name = await getNameFromArgs ( args , flags , path . basename ( templatePath , '.js' ) )
76
+
77
+ this . log ( `Creating function ${ name } ` )
78
+
79
+ const functionPath = flags . dir
80
+ ? path . join ( functionsDir , name , name + '.js' )
81
+ : path . join ( functionsDir , name + '.js' )
82
+ if ( fs . existsSync ( functionPath ) ) {
83
+ this . log ( `Function ${ functionPath } already exists` )
84
+ process . exit ( 1 )
85
+ }
86
+
87
+ if ( flags . dir ) {
88
+ const fnFolder = path . join ( functionsDir , name )
89
+ if ( fs . existsSync ( fnFolder + '.js' ) && fs . lstatSync ( fnFolder + '.js' ) . isFile ( ) ) {
90
+ this . log ( `A single file version of the function ${ name } already exists at ${ fnFolder } .js` )
91
+ process . exit ( 1 )
92
+ }
93
+
94
+ try {
95
+ fs . mkdirSync ( fnFolder , { recursive : true } )
96
+ } catch ( e ) {
97
+ // Ignore
98
+ }
99
+ } else if ( fs . existsSync ( functionPath . replace ( / \. j s / , '' ) ) ) {
100
+ this . log ( `A folder version of the function ${ name } already exists at ${ functionPath . replace ( / \. j s / , '' ) } ` )
101
+ process . exit ( 1 )
102
+ }
103
+
104
+ fs . writeFileSync ( functionPath , template )
105
+ if ( copyAssets ) {
106
+ copyAssets . forEach ( src =>
107
+ fs . copySync ( path . join ( templatesDir , 'assets' , src ) , path . join ( functionsDir , src ) , {
108
+ overwrite : false ,
109
+ errorOnExist : false // went with this to make it idempotent, might change in future
110
+ } )
111
+ ) // copy assets if specified
112
+ }
113
+ if ( onComplete ) onComplete ( ) // do whatever the template wants to do after it is scaffolded
82
114
}
83
- if ( onComplete ) onComplete ( ) // do whatever the template wants to do after it is scaffolded
84
115
}
85
116
}
86
117
@@ -104,6 +135,7 @@ FunctionsCreateCommand.examples = [
104
135
FunctionsCreateCommand . flags = {
105
136
name : flags . string ( { char : 'n' , description : 'function name' } ) ,
106
137
functions : flags . string ( { char : 'f' , description : 'functions folder' } ) ,
138
+ url : flags . string ( { char : 'u' , description : 'pull template from URL' } ) ,
107
139
dir : flags . boolean ( {
108
140
char : 'd' ,
109
141
description : 'create function as a directory'
@@ -136,3 +168,20 @@ async function getNameFromArgs(args, flags, defaultName) {
136
168
}
137
169
return name
138
170
}
171
+
172
+ // pick template from our existing templates
173
+ async function pickTemplate ( ) {
174
+ let templates = fs . readdirSync ( templatesDir ) . filter ( x => path . extname ( x ) === '.js' ) // only js templates for now
175
+ templates = templates
176
+ . map ( t => require ( path . join ( templatesDir , t ) ) )
177
+ . sort ( ( a , b ) => ( a . priority || 999 ) - ( b . priority || 999 ) ) // doesnt scale but will be ok for now
178
+ const { templatePath } = await inquirer . prompt ( [
179
+ {
180
+ name : 'templatePath' ,
181
+ message : 'pick a template' ,
182
+ type : 'list' ,
183
+ choices : templates . map ( t => t . metadata )
184
+ }
185
+ ] )
186
+ return templatePath
187
+ }
0 commit comments