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

Commit 05d4051

Browse files
authored
Merge pull request #40 from netlify/breakUpRegistry
break up the registry into individual files
2 parents 4d44017 + 0dc738d commit 05d4051

File tree

15 files changed

+191
-155
lines changed

15 files changed

+191
-155
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,20 @@ $ netlify functions:create --name hello-world
118118
$ netlify functions:create hello-world --url https://github.com/netlify-labs/all-the-functions/tree/master/functions/9-using-middleware
119119
```
120120
121+
**Function Templates**
122+
123+
Function templates can specify `addons` that they rely on as well as execute arbitrary code after installation in an `onComplete` hook, if a special `.netlify-function-template.js` file exists in the directory:
124+
125+
```js
126+
// .netlify-function-template.js
127+
module.exports = {
128+
addons: ['fauna'],
129+
onComplete() {
130+
console.log(`custom-template function created from template!`)
131+
}
132+
}
133+
```
134+
121135
#### Executing Netlify Functions
122136
123137
After creating serverless functions, Netlify Dev can serve thes to you as part of your local build. This emulates the behaviour of Netlify Functions when deployed to Netlify.

src/commands/functions/create.js

Lines changed: 103 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const copy = require('copy-template-dir')
44
const { flags } = require('@oclif/command')
55
const Command = require('@netlify/cli-utils')
66
const inquirer = require('inquirer')
7-
const readRepoURL = require('../../utils/readRepoURL')
7+
const { readRepoURL, validateRepoURL } = require('../../utils/readRepoURL')
88
const { createSiteAddon } = require('../../utils/addons')
99
const http = require('http')
1010
const fetch = require('node-fetch')
@@ -111,11 +111,22 @@ async function pickTemplate() {
111111
// show separators
112112
return [
113113
new inquirer.Separator(`----[JS]----`),
114-
...jsreg
114+
...jsreg,
115115
// new inquirer.Separator(`----[TS]----`),
116116
// ...tsreg,
117117
// new inquirer.Separator(`----[GO]----`),
118118
// ...goreg
119+
new inquirer.Separator(`----[Special Commands]----`),
120+
{
121+
name: `*** Clone template from Github URL ***`,
122+
value: 'url',
123+
short: 'gh-url'
124+
},
125+
{
126+
name: `*** Report issue with, or suggest a new template ***`,
127+
value: 'report',
128+
short: 'gh-report'
129+
}
119130
]
120131
} else {
121132
// only show filtered results sorted by score
@@ -144,7 +155,9 @@ async function pickTemplate() {
144155
})
145156
}
146157
function formatRegistryArrayForInquirer(lang) {
147-
const registry = require(path.join(templatesDir, lang, 'template-registry.js'))
158+
const folderNames = fs.readdirSync(path.join(templatesDir, lang))
159+
const registry = folderNames
160+
.map(name => require(path.join(templatesDir, lang, name, '.netlify-function-template.js')))
148161
.sort((a, b) => (a.priority || 999) - (b.priority || 999))
149162
.map(t => {
150163
t.lang = lang
@@ -208,61 +221,103 @@ async function downloadFromURL(flags, args, functionsDir) {
208221
cp.exec('npm i', { cwd: path.join(functionsDir, nameToUse) }, () => {
209222
this.log(`installing dependencies for ${nameToUse} complete `)
210223
})
224+
225+
// read, execute, and delete function template file if exists
226+
const fnTemplateFile = path.join(fnFolder, '.netlify-function-template.js')
227+
if (fs.existsSync(fnTemplateFile)) {
228+
const { onComplete, addons = [] } = require(fnTemplateFile)
229+
installAddons.call(this, addons)
230+
if (onComplete) onComplete()
231+
fs.unlinkSync(fnTemplateFile) // delete
232+
}
211233
}
212234

213235
// no --url flag specified, pick from a provided template
214236
async function scaffoldFromTemplate(flags, args, functionsDir) {
215-
const { onComplete, name: templateName, lang, addons = [] } = await pickTemplate() // pull the rest of the metadata from the template
216-
const pathToTemplate = path.join(templatesDir, lang, templateName)
217-
if (!fs.existsSync(pathToTemplate)) {
218-
throw new Error(`there isnt a corresponding folder to the selected name, ${templateName} template is misconfigured`)
219-
}
220-
221-
const name = await getNameFromArgs(args, flags, templateName)
222-
this.log(`Creating function ${name}`)
223-
const functionPath = ensureFunctionPathIsOk(functionsDir, flags, name)
224-
225-
// // SWYX: note to future devs - useful for debugging source to output issues
226-
// this.log('from ', pathToTemplate, ' to ', functionPath)
227-
const vars = { NETLIFY_STUFF_TO_REPLACE: 'REPLACEMENT' } // SWYX: TODO
228-
let hasPackageJSON = false
229-
copy(pathToTemplate, functionPath, vars, (err, createdFiles) => {
230-
if (err) throw err
231-
createdFiles.forEach(filePath => {
232-
this.log(`Created ${filePath}`)
233-
if (filePath.includes('package.json')) hasPackageJSON = true
234-
})
235-
// rename functions with different names from default
236-
if (name !== templateName) {
237-
fs.renameSync(path.join(functionPath, templateName + '.js'), path.join(functionPath, name + '.js'))
237+
const chosentemplate = await pickTemplate() // pull the rest of the metadata from the template
238+
if (chosentemplate === 'url') {
239+
const { chosenurl } = await inquirer.prompt([
240+
{
241+
name: 'chosenurl',
242+
message: 'URL to clone: ',
243+
type: 'input',
244+
validate: val => !!validateRepoURL(val)
245+
// make sure it is not undefined and is a valid filename.
246+
// this has some nuance i have ignored, eg crossenv and i18n concerns
247+
}
248+
])
249+
flags.url = chosenurl.trim()
250+
try {
251+
await downloadFromURL.call(this, flags, args, functionsDir)
252+
} catch (err) {
253+
console.error('Error downloading from URL: ' + flags.url)
254+
console.error(err)
255+
process.exit(1)
238256
}
239-
// npm install
240-
if (hasPackageJSON) {
241-
this.log(`installing dependencies for ${name}...`)
242-
cp.exec('npm i', { cwd: path.join(functionPath) }, () => {
243-
this.log(`installing dependencies for ${name} complete `)
244-
})
257+
} else if (chosentemplate === 'report') {
258+
console.log('opening in browser: https://github.com/netlify/netlify-dev-plugin/issues/new')
259+
require('../../utils/openBrowser.js')('https://github.com/netlify/netlify-dev-plugin/issues/new')
260+
} else {
261+
const { onComplete, name: templateName, lang, addons = [] } = chosentemplate
262+
263+
const pathToTemplate = path.join(templatesDir, lang, templateName)
264+
if (!fs.existsSync(pathToTemplate)) {
265+
throw new Error(
266+
`there isnt a corresponding folder to the selected name, ${templateName} template is misconfigured`
267+
)
245268
}
246269

247-
if (addons.length) {
248-
const { api, site } = this.netlify
249-
const siteId = site.id
250-
if (!siteId) {
251-
this.log('No site id found, please run inside a site folder or `netlify link`')
252-
return false
270+
const name = await getNameFromArgs(args, flags, templateName)
271+
this.log(`Creating function ${name}`)
272+
const functionPath = ensureFunctionPathIsOk(functionsDir, flags, name)
273+
274+
// // SWYX: note to future devs - useful for debugging source to output issues
275+
// this.log('from ', pathToTemplate, ' to ', functionPath)
276+
const vars = { NETLIFY_STUFF_TO_REPLACE: 'REPLACEMENT' } // SWYX: TODO
277+
let hasPackageJSON = false
278+
copy(pathToTemplate, functionPath, vars, (err, createdFiles) => {
279+
if (err) throw err
280+
createdFiles.forEach(filePath => {
281+
this.log(`Created ${filePath}`)
282+
if (filePath.includes('package.json')) hasPackageJSON = true
283+
})
284+
// rename functions with different names from default
285+
if (name !== templateName) {
286+
fs.renameSync(path.join(functionPath, templateName + '.js'), path.join(functionPath, name + '.js'))
253287
}
254-
api.getSite({ siteId }).then(async siteData => {
255-
const accessToken = await this.authenticate()
256-
const arr = addons.map(addonName => {
257-
this.log('installing addon: ' + addonName)
258-
// will prompt for configs if not supplied - we do not yet allow for addon configs supplied by `netlify functions:create` command and may never do so
259-
return createSiteAddon(accessToken, addonName, siteId, siteData, log)
288+
// delete function template file
289+
fs.unlinkSync(path.join(functionPath, '.netlify-function-template.js'))
290+
// npm install
291+
if (hasPackageJSON) {
292+
this.log(`installing dependencies for ${name}...`)
293+
cp.exec('npm i', { cwd: path.join(functionPath) }, () => {
294+
this.log(`installing dependencies for ${name} complete `)
260295
})
261-
return Promise.all(arr)
262-
})
296+
}
297+
installAddons.call(this, addons)
298+
if (onComplete) onComplete() // do whatever the template wants to do after it is scaffolded
299+
})
300+
}
301+
}
302+
303+
async function installAddons(addons = []) {
304+
if (addons.length) {
305+
const { api, site } = this.netlify
306+
const siteId = site.id
307+
if (!siteId) {
308+
this.log('No site id found, please run inside a site folder or `netlify link`')
309+
return false
263310
}
264-
if (onComplete) onComplete() // do whatever the template wants to do after it is scaffolded
265-
})
311+
return api.getSite({ siteId }).then(async siteData => {
312+
const accessToken = await this.authenticate()
313+
const arr = addons.map(addonName => {
314+
this.log('installing addon: ' + addonName)
315+
// will prompt for configs if not supplied - we do not yet allow for addon configs supplied by `netlify functions:create` command and may never do so
316+
return createSiteAddon(accessToken, addonName, siteId, siteData, this.log)
317+
})
318+
return Promise.all(arr)
319+
})
320+
}
266321
}
267322

268323
// we used to allow for a --dir command,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
name: 'apollo-graphql',
3+
description: 'GraphQL function using Apollo-Server-Lambda!',
4+
onComplete() {
5+
console.log(`apollo-graphql function created from template!`)
6+
}
7+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports = {
2+
name: 'auth-fetch',
3+
description: 'Use `node-fetch` library and Netlify Identity to access APIs',
4+
onComplete() {
5+
console.log(`auth-fetch function created from template!`)
6+
console.log(
7+
'REMINDER: Make sure to call this function with the Netlify Identity JWT. See https://netlify-gotrue-in-react.netlify.com/ for demo'
8+
)
9+
}
10+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
name: 'fauna-crud',
3+
description: 'CRUD function using Fauna DB',
4+
addons: ['fauna'], // in future we'll want to pass/prompt args to addons
5+
onComplete() {
6+
console.log(`fauna-crud function created from template!`)
7+
}
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
name: 'hello-world',
3+
priority: 1,
4+
description: 'Basic function that shows async/await usage, and response formatting'
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
name: 'node-fetch',
3+
description: 'Fetch function: uses node-fetch to hit an external API without CORS issues',
4+
onComplete() {
5+
console.log(`node-fetch function created from template!`)
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
name: 'protected-function',
3+
description: 'Function protected Netlify Identity authentication',
4+
onComplete() {
5+
console.log(`protected-function function created from template!`)
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
name: 'serverless-ssr',
3+
description: 'Dynamic serverside rendering via functions',
4+
onComplete() {
5+
console.log(`serverless-ssr function created from template!`)
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
name: 'set-cookie',
3+
description: 'Set a cookie alongside your function',
4+
onComplete() {
5+
console.log(`set-cookie function created from template!`)
6+
}
7+
}

src/functions-templates/js/template-registry.js

Lines changed: 0 additions & 72 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
name: 'using-middleware',
3+
description: 'Using Middleware with middy',
4+
onComplete() {
5+
console.log(`using-middleware function created from template!`)
6+
}
7+
}

src/functions-templates/unused_go/template-registry.js

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/functions-templates/unused_ts/template-registry.js

Lines changed: 0 additions & 19 deletions
This file was deleted.

0 commit comments

Comments
 (0)