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

break up the registry into individual files #40

Merged
merged 1 commit into from
Mar 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,20 @@ $ netlify functions:create --name hello-world
$ netlify functions:create hello-world --url https://github.com/netlify-labs/all-the-functions/tree/master/functions/9-using-middleware
```

**Function Templates**

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:

```js
// .netlify-function-template.js
module.exports = {
addons: ['fauna'],
onComplete() {
console.log(`custom-template function created from template!`)
}
}
```

#### Executing Netlify Functions

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.
Expand Down
151 changes: 103 additions & 48 deletions src/commands/functions/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const copy = require('copy-template-dir')
const { flags } = require('@oclif/command')
const Command = require('@netlify/cli-utils')
const inquirer = require('inquirer')
const readRepoURL = require('../../utils/readRepoURL')
const { readRepoURL, validateRepoURL } = require('../../utils/readRepoURL')
const { createSiteAddon } = require('../../utils/addons')
const http = require('http')
const fetch = require('node-fetch')
Expand Down Expand Up @@ -111,11 +111,22 @@ async function pickTemplate() {
// show separators
return [
new inquirer.Separator(`----[JS]----`),
...jsreg
...jsreg,
// new inquirer.Separator(`----[TS]----`),
// ...tsreg,
// new inquirer.Separator(`----[GO]----`),
// ...goreg
new inquirer.Separator(`----[Special Commands]----`),
{
name: `*** Clone template from Github URL ***`,
value: 'url',
short: 'gh-url'
},
{
name: `*** Report issue with, or suggest a new template ***`,
value: 'report',
short: 'gh-report'
}
]
} else {
// only show filtered results sorted by score
Expand Down Expand Up @@ -144,7 +155,9 @@ async function pickTemplate() {
})
}
function formatRegistryArrayForInquirer(lang) {
const registry = require(path.join(templatesDir, lang, 'template-registry.js'))
const folderNames = fs.readdirSync(path.join(templatesDir, lang))
const registry = folderNames
.map(name => require(path.join(templatesDir, lang, name, '.netlify-function-template.js')))
.sort((a, b) => (a.priority || 999) - (b.priority || 999))
.map(t => {
t.lang = lang
Expand Down Expand Up @@ -208,61 +221,103 @@ async function downloadFromURL(flags, args, functionsDir) {
cp.exec('npm i', { cwd: path.join(functionsDir, nameToUse) }, () => {
this.log(`installing dependencies for ${nameToUse} complete `)
})

// read, execute, and delete function template file if exists
const fnTemplateFile = path.join(fnFolder, '.netlify-function-template.js')
if (fs.existsSync(fnTemplateFile)) {
const { onComplete, addons = [] } = require(fnTemplateFile)
installAddons.call(this, addons)
if (onComplete) onComplete()
fs.unlinkSync(fnTemplateFile) // delete
}
}

// no --url flag specified, pick from a provided template
async function scaffoldFromTemplate(flags, args, functionsDir) {
const { onComplete, name: templateName, lang, addons = [] } = await pickTemplate() // pull the rest of the metadata from the template
const pathToTemplate = path.join(templatesDir, lang, templateName)
if (!fs.existsSync(pathToTemplate)) {
throw new Error(`there isnt a corresponding folder to the selected name, ${templateName} template is misconfigured`)
}

const name = await getNameFromArgs(args, flags, templateName)
this.log(`Creating function ${name}`)
const functionPath = ensureFunctionPathIsOk(functionsDir, flags, name)

// // SWYX: note to future devs - useful for debugging source to output issues
// this.log('from ', pathToTemplate, ' to ', functionPath)
const vars = { NETLIFY_STUFF_TO_REPLACE: 'REPLACEMENT' } // SWYX: TODO
let hasPackageJSON = false
copy(pathToTemplate, functionPath, vars, (err, createdFiles) => {
if (err) throw err
createdFiles.forEach(filePath => {
this.log(`Created ${filePath}`)
if (filePath.includes('package.json')) hasPackageJSON = true
})
// rename functions with different names from default
if (name !== templateName) {
fs.renameSync(path.join(functionPath, templateName + '.js'), path.join(functionPath, name + '.js'))
const chosentemplate = await pickTemplate() // pull the rest of the metadata from the template
if (chosentemplate === 'url') {
const { chosenurl } = await inquirer.prompt([
{
name: 'chosenurl',
message: 'URL to clone: ',
type: 'input',
validate: val => !!validateRepoURL(val)
// make sure it is not undefined and is a valid filename.
// this has some nuance i have ignored, eg crossenv and i18n concerns
}
])
flags.url = chosenurl.trim()
try {
await downloadFromURL.call(this, flags, args, functionsDir)
} catch (err) {
console.error('Error downloading from URL: ' + flags.url)
console.error(err)
process.exit(1)
}
// npm install
if (hasPackageJSON) {
this.log(`installing dependencies for ${name}...`)
cp.exec('npm i', { cwd: path.join(functionPath) }, () => {
this.log(`installing dependencies for ${name} complete `)
})
} else if (chosentemplate === 'report') {
console.log('opening in browser: https://github.com/netlify/netlify-dev-plugin/issues/new')
require('../../utils/openBrowser.js')('https://github.com/netlify/netlify-dev-plugin/issues/new')
} else {
const { onComplete, name: templateName, lang, addons = [] } = chosentemplate

const pathToTemplate = path.join(templatesDir, lang, templateName)
if (!fs.existsSync(pathToTemplate)) {
throw new Error(
`there isnt a corresponding folder to the selected name, ${templateName} template is misconfigured`
)
}

if (addons.length) {
const { api, site } = this.netlify
const siteId = site.id
if (!siteId) {
this.log('No site id found, please run inside a site folder or `netlify link`')
return false
const name = await getNameFromArgs(args, flags, templateName)
this.log(`Creating function ${name}`)
const functionPath = ensureFunctionPathIsOk(functionsDir, flags, name)

// // SWYX: note to future devs - useful for debugging source to output issues
// this.log('from ', pathToTemplate, ' to ', functionPath)
const vars = { NETLIFY_STUFF_TO_REPLACE: 'REPLACEMENT' } // SWYX: TODO
let hasPackageJSON = false
copy(pathToTemplate, functionPath, vars, (err, createdFiles) => {
if (err) throw err
createdFiles.forEach(filePath => {
this.log(`Created ${filePath}`)
if (filePath.includes('package.json')) hasPackageJSON = true
})
// rename functions with different names from default
if (name !== templateName) {
fs.renameSync(path.join(functionPath, templateName + '.js'), path.join(functionPath, name + '.js'))
}
api.getSite({ siteId }).then(async siteData => {
const accessToken = await this.authenticate()
const arr = addons.map(addonName => {
this.log('installing addon: ' + addonName)
// 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
return createSiteAddon(accessToken, addonName, siteId, siteData, log)
// delete function template file
fs.unlinkSync(path.join(functionPath, '.netlify-function-template.js'))
// npm install
if (hasPackageJSON) {
this.log(`installing dependencies for ${name}...`)
cp.exec('npm i', { cwd: path.join(functionPath) }, () => {
this.log(`installing dependencies for ${name} complete `)
})
return Promise.all(arr)
})
}
installAddons.call(this, addons)
if (onComplete) onComplete() // do whatever the template wants to do after it is scaffolded
})
}
}

async function installAddons(addons = []) {
if (addons.length) {
const { api, site } = this.netlify
const siteId = site.id
if (!siteId) {
this.log('No site id found, please run inside a site folder or `netlify link`')
return false
}
if (onComplete) onComplete() // do whatever the template wants to do after it is scaffolded
})
return api.getSite({ siteId }).then(async siteData => {
const accessToken = await this.authenticate()
const arr = addons.map(addonName => {
this.log('installing addon: ' + addonName)
// 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
return createSiteAddon(accessToken, addonName, siteId, siteData, this.log)
})
return Promise.all(arr)
})
}
}

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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
name: 'using-middleware',
description: 'Using Middleware with middy',
onComplete() {
console.log(`using-middleware function created from template!`)
}
}
12 changes: 0 additions & 12 deletions src/functions-templates/unused_go/template-registry.js

This file was deleted.

19 changes: 0 additions & 19 deletions src/functions-templates/unused_ts/template-registry.js

This file was deleted.

Loading