|
1 | 1 | const fs = require('fs-extra')
|
2 | 2 | const path = require('path')
|
| 3 | +const copy = require('copy-template-dir') |
3 | 4 | const { flags } = require('@oclif/command')
|
4 | 5 | const Command = require('@netlify/cli-utils')
|
5 | 6 | const inquirer = require('inquirer')
|
6 | 7 | const readRepoURL = require('../../utils/readRepoURL')
|
7 |
| -const templatesDir = path.resolve(__dirname, '../../functions-templates') |
8 | 8 | const http = require('http')
|
9 | 9 | const fetch = require('node-fetch')
|
10 | 10 | const cp = require('child_process')
|
11 | 11 |
|
| 12 | +const templatesDir = path.resolve(__dirname, '../../functions-templates') |
| 13 | + |
12 | 14 | class FunctionsCreateCommand extends Command {
|
13 | 15 | async run() {
|
14 | 16 | const { flags, args } = this.parse(FunctionsCreateCommand)
|
15 |
| - |
16 |
| - /* get functions dir (and make it if necessary) */ |
17 | 17 | const { config } = this.netlify
|
18 |
| - const functionsDir = flags.functions || (config.build && config.build.functions) |
19 |
| - if (!functionsDir) { |
20 |
| - this.log('No functions folder specified in netlify.toml or as an argument') |
21 |
| - process.exit(1) |
22 |
| - } |
23 | 18 |
|
24 |
| - if (!fs.existsSync(functionsDir)) { |
25 |
| - console.log(`functions folder ${functionsDir} specified in netlify.toml but folder not found, creating it...`) |
26 |
| - fs.mkdirSync(functionsDir) |
27 |
| - console.log(`functions folder ${functionsDir} created`) |
28 |
| - } |
| 19 | + const functionsDir = ensureFunctionDirExists(flags, config) |
29 | 20 |
|
30 | 21 | /* either download from URL or scaffold from template */
|
31 | 22 | 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) |
37 |
| - if (fs.existsSync(fnFolder + '.js') && fs.lstatSync(fnFolder + '.js').isFile()) { |
38 |
| - this.log(`A single file version of the function ${name} already exists at ${fnFolder}.js`) |
39 |
| - process.exit(1) |
40 |
| - } |
41 |
| - |
42 |
| - try { |
43 |
| - fs.mkdirSync(fnFolder, { recursive: true }) |
44 |
| - } catch (e) { |
45 |
| - // Ignore |
46 |
| - } |
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 |
| - }) |
54 |
| - }) |
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 |
| - }) |
| 23 | + await downloadFromURL(flags, args, functionsDir) |
61 | 24 | } 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(/\.js/, ''))) { |
100 |
| - this.log(`A folder version of the function ${name} already exists at ${functionPath.replace(/\.js/, '')}`) |
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 |
| 25 | + await scaffoldFromTemplate(flags, args, functionsDir, this.log) |
114 | 26 | }
|
115 | 27 | }
|
116 | 28 | }
|
@@ -171,17 +83,133 @@ async function getNameFromArgs(args, flags, defaultName) {
|
171 | 83 |
|
172 | 84 | // pick template from our existing templates
|
173 | 85 | 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([ |
| 86 | + // let templates = fs.readdirSync(templatesDir).filter(x => x.split('.').length === 1) // only folders |
| 87 | + const registry = require(path.join(templatesDir, 'template-registry.js')) |
| 88 | + let templates = registry.sort((a, b) => (a.priority || 999) - (b.priority || 999)) // doesnt scale but will be ok for now |
| 89 | + const { chosentemplate } = await inquirer.prompt([ |
179 | 90 | {
|
180 |
| - name: 'templatePath', |
| 91 | + name: 'chosentemplate', |
181 | 92 | message: 'pick a template',
|
182 | 93 | type: 'list',
|
183 |
| - choices: templates.map(t => t.metadata) |
| 94 | + choices: templates.map(t => ({ |
| 95 | + // confusing but this is the format inquirer wants |
| 96 | + name: t.description, |
| 97 | + value: t.name, |
| 98 | + short: t.name |
| 99 | + })) |
184 | 100 | }
|
185 | 101 | ])
|
186 |
| - return templatePath |
| 102 | + return registry.find(x => x.name === chosentemplate) |
| 103 | +} |
| 104 | + |
| 105 | +/* get functions dir (and make it if necessary) */ |
| 106 | +function ensureFunctionDirExists(flags, config) { |
| 107 | + const functionsDir = flags.functions || (config.build && config.build.functions) |
| 108 | + if (!functionsDir) { |
| 109 | + this.log('No functions folder specified in netlify.toml or as an argument') |
| 110 | + process.exit(1) |
| 111 | + } |
| 112 | + if (!fs.existsSync(functionsDir)) { |
| 113 | + console.log(`functions folder ${functionsDir} specified in netlify.toml but folder not found, creating it...`) |
| 114 | + fs.mkdirSync(functionsDir) |
| 115 | + console.log(`functions folder ${functionsDir} created`) |
| 116 | + } |
| 117 | + return functionsDir |
| 118 | +} |
| 119 | + |
| 120 | +// Download files from a given github URL |
| 121 | +async function downloadFromURL(flags, args, functionsDir) { |
| 122 | + const folderContents = await readRepoURL(flags.url) |
| 123 | + const functionName = flags.url.split('/').slice(-1)[0] |
| 124 | + const nameToUse = await getNameFromArgs(args, flags, functionName) |
| 125 | + const fnFolder = path.join(functionsDir, nameToUse) |
| 126 | + if (fs.existsSync(fnFolder + '.js') && fs.lstatSync(fnFolder + '.js').isFile()) { |
| 127 | + this.log(`A single file version of the function ${name} already exists at ${fnFolder}.js`) |
| 128 | + process.exit(1) |
| 129 | + } |
| 130 | + |
| 131 | + try { |
| 132 | + fs.mkdirSync(fnFolder, { recursive: true }) |
| 133 | + } catch (e) { |
| 134 | + // Ignore |
| 135 | + } |
| 136 | + await Promise.all( |
| 137 | + folderContents.map(({ name, download_url }) => { |
| 138 | + return fetch(download_url).then(res => { |
| 139 | + const finalName = path.basename(name, '.js') === functionName ? nameToUse + '.js' : name |
| 140 | + const dest = fs.createWriteStream(path.join(fnFolder, finalName)) |
| 141 | + res.body.pipe(dest) |
| 142 | + }) |
| 143 | + }) |
| 144 | + ) |
| 145 | + |
| 146 | + console.log(`installing dependencies for ${nameToUse}...`) |
| 147 | + cp.exec('npm i', { cwd: path.join(functionsDir, nameToUse) }, () => { |
| 148 | + console.log(`installing dependencies for ${nameToUse} complete `) |
| 149 | + }) |
| 150 | +} |
| 151 | + |
| 152 | +// no --url flag specified, pick from a provided template |
| 153 | +async function scaffoldFromTemplate(flags, args, functionsDir, log) { |
| 154 | + const { onComplete, name: templateName } = await pickTemplate() // pull the rest of the metadata from the template |
| 155 | + |
| 156 | + const pathToTemplate = path.join(templatesDir, templateName) |
| 157 | + if (!fs.existsSync(pathToTemplate)) { |
| 158 | + throw new Error(`there isnt a corresponding folder to the selected name, ${templateName} template is misconfigured`) |
| 159 | + } |
| 160 | + |
| 161 | + const name = await getNameFromArgs(args, flags, templateName) |
| 162 | + |
| 163 | + log(`Creating function ${name}`) |
| 164 | + const functionPath = ensureFunctionPathIsOk(functionsDir, flags, name) |
| 165 | + |
| 166 | + log('from ', pathToTemplate, ' to ', functionPath) |
| 167 | + const vars = { NETLIFY_STUFF_TO_REPLACTE: 'REPLACEMENT' } // SWYX: TODO |
| 168 | + let hasPackageJSON = false |
| 169 | + copy(pathToTemplate, functionPath, vars, (err, createdFiles) => { |
| 170 | + if (err) throw err |
| 171 | + createdFiles.forEach(filePath => { |
| 172 | + log(`Created ${filePath}`) |
| 173 | + if (filePath.includes('package.json')) hasPackageJSON = true |
| 174 | + }) |
| 175 | + // rename functions with different names from default |
| 176 | + if (name !== templateName) { |
| 177 | + fs.renameSync(path.join(functionPath, templateName + '.js'), path.join(functionPath, name + '.js')) |
| 178 | + } |
| 179 | + // npm install |
| 180 | + if (hasPackageJSON) { |
| 181 | + console.log(`installing dependencies for ${name}...`) |
| 182 | + cp.exec('npm i', { cwd: path.join(functionPath) }, () => { |
| 183 | + console.log(`installing dependencies for ${name} complete `) |
| 184 | + }) |
| 185 | + } |
| 186 | + |
| 187 | + if (onComplete) onComplete() // do whatever the template wants to do after it is scaffolded |
| 188 | + }) |
| 189 | +} |
| 190 | + |
| 191 | +function ensureFunctionPathIsOk(functionsDir, flags, name) { |
| 192 | + // const functionPath = flags.dir ? path.join(functionsDir, name, name + '.js') : path.join(functionsDir, name + '.js') |
| 193 | + const functionPath = path.join(functionsDir, name) |
| 194 | + if (fs.existsSync(functionPath)) { |
| 195 | + this.log(`Function ${functionPath} already exists, cancelling...`) |
| 196 | + process.exit(1) |
| 197 | + } |
| 198 | + // if (flags.dir) { |
| 199 | + // const fnFolder = path.join(functionsDir, name) |
| 200 | + // if (fs.existsSync(fnFolder + '.js') && fs.lstatSync(fnFolder + '.js').isFile()) { |
| 201 | + // this.log(`A single file version of the function ${name} already exists at ${fnFolder}.js`) |
| 202 | + // process.exit(1) |
| 203 | + // } |
| 204 | + |
| 205 | + // try { |
| 206 | + // fs.mkdirSync(fnFolder, { recursive: true }) |
| 207 | + // } catch (e) { |
| 208 | + // // Ignore |
| 209 | + // } |
| 210 | + // } else if (fs.existsSync(functionPath.replace(/\.js/, ''))) { |
| 211 | + // this.log(`A folder version of the function ${name} already exists at ${functionPath.replace(/\.js/, '')}`) |
| 212 | + // process.exit(1) |
| 213 | + // } |
| 214 | + return functionPath |
187 | 215 | }
|
0 commit comments