Skip to content
This repository was archived by the owner on Sep 12, 2019. It is now read-only.

Commit 615eb6e

Browse files
authored
Merge pull request #32 from netlify/switchToFolderTemplates
switch functions templating to entirely folder based with a template registry
2 parents a7f3c74 + 5862828 commit 615eb6e

File tree

16 files changed

+321
-190
lines changed

16 files changed

+321
-190
lines changed

package-lock.json

Lines changed: 51 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"@oclif/command": "^1",
1111
"@oclif/config": "^1",
1212
"ascii-table": "0.0.9",
13+
"copy-template-dir": "^1.4.0",
1314
"fs-extra": "^7.0.1",
1415
"get-port": "^4.1.0",
1516
"http-proxy": "^1.17.0",

src/commands/functions/create.js

Lines changed: 130 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,28 @@
11
const fs = require('fs-extra')
22
const path = require('path')
3+
const copy = require('copy-template-dir')
34
const { flags } = require('@oclif/command')
45
const Command = require('@netlify/cli-utils')
56
const inquirer = require('inquirer')
67
const readRepoURL = require('../../utils/readRepoURL')
7-
const templatesDir = path.resolve(__dirname, '../../functions-templates')
88
const http = require('http')
99
const fetch = require('node-fetch')
1010
const cp = require('child_process')
1111

12+
const templatesDir = path.resolve(__dirname, '../../functions-templates')
13+
1214
class FunctionsCreateCommand extends Command {
1315
async run() {
1416
const { flags, args } = this.parse(FunctionsCreateCommand)
15-
16-
/* get functions dir (and make it if necessary) */
1717
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-
}
2318

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)
2920

3021
/* either download from URL or scaffold from template */
3122
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)
6124
} 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)
11426
}
11527
}
11628
}
@@ -171,17 +83,133 @@ async function getNameFromArgs(args, flags, defaultName) {
17183

17284
// pick template from our existing templates
17385
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([
17990
{
180-
name: 'templatePath',
91+
name: 'chosentemplate',
18192
message: 'pick a template',
18293
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+
}))
184100
}
185101
])
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
187215
}

0 commit comments

Comments
 (0)