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

Commit 6bfc6aa

Browse files
authored
Merge pull request #26 from netlify/netlify-functions/templatesV2
add v2 of templates with assets and no requiring of all the things
2 parents 657e442 + 01df863 commit 6bfc6aa

File tree

9 files changed

+238
-26
lines changed

9 files changed

+238
-26
lines changed

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+
"fs-extra": "^7.0.1",
1314
"get-port": "^4.1.0",
1415
"http-proxy": "^1.17.0",
1516
"inquirer": "^6.2.2",

src/commands/functions/create.js

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const fs = require('fs')
1+
const fs = require('fs-extra')
22
const path = require('path')
33
const { flags } = require('@oclif/command')
44
const Command = require('@netlify/cli-utils')
@@ -8,29 +8,30 @@ const templatesDir = path.resolve(__dirname, '../../functions-templates')
88
class FunctionsCreateCommand extends Command {
99
async run() {
1010
const { flags, args } = this.parse(FunctionsCreateCommand)
11-
const name = await getNameFromArgs(args)
1211
const { config } = this.netlify
13-
const templates = fs
14-
.readdirSync(templatesDir)
15-
.filter(x => path.extname(x) === '.js') // only js templates for now
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
1616
const { templatePath } = await inquirer.prompt([
1717
{
1818
name: 'templatePath',
1919
message: 'pick a template',
2020
type: 'list',
21-
choices: templates.map(t => {
22-
return require(path.join(templatesDir, t)).metadata
23-
// ({ name: path.basename(t, '.js') })
24-
})
21+
choices: templates.map(t => t.metadata)
2522
}
2623
])
24+
// pull the rest of the metadata from the template
25+
const { onComplete, copyAssets, templateCode } = require(path.join(templatesDir, templatePath))
2726

28-
let template = fs
29-
.readFileSync(path.join(templatesDir, `${templatePath}.js`))
30-
.toString()
31-
.split('// --- Netlify Template Below -- //')
32-
if (template.length !== 2) throw new Error('template ' + templatePath + ' badly formatted')
33-
template = '// scaffolded from `netlify functions:create` \n' + template[1]
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+
const name = await getNameFromArgs(args, path.basename(templatePath, '.js'))
3435

3536
this.log(`Creating function ${name}`)
3637

@@ -70,8 +71,14 @@ class FunctionsCreateCommand extends Command {
7071
}
7172

7273
fs.writeFileSync(functionPath, template)
73-
74-
const onComplete = require(path.join(templatesDir, templatePath)).onComplete
74+
if (copyAssets) {
75+
copyAssets.forEach(src =>
76+
fs.copySync(path.join(templatesDir, 'assets', src), path.join(functionsDir, src), {
77+
overwrite: false,
78+
errorOnExist: false // went with this to make it idempotent, might change in future
79+
})
80+
) // copy assets if specified
81+
}
7582
if (onComplete) onComplete() // do whatever the template wants to do after it is scaffolded
7683
}
7784
}
@@ -98,13 +105,14 @@ FunctionsCreateCommand.flags = {
98105
module.exports = FunctionsCreateCommand
99106

100107
// prompt for a name if name not supplied
101-
async function getNameFromArgs(args) {
108+
async function getNameFromArgs(args, defaultName) {
102109
let { name } = args
103110
if (!name) {
104111
let responses = await inquirer.prompt([
105112
{
106113
name: 'name',
107114
message: 'name your function: ',
115+
default: defaultName,
108116
type: 'input',
109117
validate: val => !!val && /^[\w\-.]+$/i.test(val)
110118
// make sure it is not undefined and is a valid filename.

src/functions-templates/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ we dont colocate this inside `src/commands/functions` because oclif will think i
88

99
## providing metadata (and other functionality)
1010

11-
we split the file based on the `// --- Netlify Template Below -- //` string. everything below it is cloned as the template. everything above it can be required and run as a module for configuring the template. for now we simply export a `metadata` object that fits [`inquirer's choices spec`](https://www.npmjs.com/package/inquirer#question).
11+
we split the file based on the `// --- Netlify Template Below -- //` string. everything below it is cloned as the template. everything above it can be required and run as a module for configuring the template. for now we simply export a `metadata` object that fits [`inquirer's choices spec`](https://www.npmjs.com/package/inquirer#question).
1212

1313
once the templating is done we can also call an `onComplete` hook to print a reminder or execute other logic - see `node-fetch.js` for an example.
1414

15+
you can optionally set a `priority` to pin display order.
16+
1517
in future we can think about other options we may want to offer.
1618

1719
## future dev thoughts
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
this folder contains other folders that will be copied alongside the template.
2+
3+
`/app/index.js` contains a plain HTML template that we return from serverless function
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/* Express App */
2+
const express = require('express')
3+
const cors = require('cors')
4+
const morgan = require('morgan')
5+
const bodyParser = require('body-parser')
6+
const compression = require('compression')
7+
8+
/* My express App */
9+
module.exports = function expressApp(functionName) {
10+
const app = express()
11+
const router = express.Router()
12+
13+
// gzip responses
14+
router.use(compression())
15+
16+
// Set router base path for local dev
17+
const routerBasePath = process.env.NODE_ENV === 'dev' ? `/${functionName}` : `/.netlify/functions/${functionName}/`
18+
19+
/* define routes */
20+
router.get('/', (req, res) => {
21+
const html = `
22+
<html>
23+
<head>
24+
<style>
25+
body {
26+
padding: 30px;
27+
}
28+
</style>
29+
</head>
30+
<body>
31+
<h1>Express via '${functionName}' ⊂◉‿◉つ</h1>
32+
33+
<p>I'm using Express running via a <a href='https://www.netlify.com/docs/functions/' target='_blank'>Netlify Function</a>.</p>
34+
35+
<p>Choose a route:</p>
36+
37+
<div>
38+
<a href='/.netlify/functions/${functionName}/users'>View /users route</a>
39+
</div>
40+
41+
<div>
42+
<a href='/.netlify/functions/${functionName}/hello'>View /hello route</a>
43+
</div>
44+
45+
<br/>
46+
<br/>
47+
48+
<div>
49+
<a href='/'>
50+
Go back to demo homepage
51+
</a>
52+
</div>
53+
54+
<br/>
55+
<br/>
56+
57+
<div>
58+
<a href='https://github.com/DavidWells/netlify-functions-express' target='_blank'>
59+
See the source code on github
60+
</a>
61+
</div>
62+
</body>
63+
</html>
64+
`
65+
res.send(html)
66+
})
67+
68+
router.get('/users', (req, res) => {
69+
res.json({
70+
users: [
71+
{
72+
name: 'steve'
73+
},
74+
{
75+
name: 'joe'
76+
}
77+
]
78+
})
79+
})
80+
81+
router.get('/hello/', function(req, res) {
82+
res.send('hello world')
83+
})
84+
85+
// Attach logger
86+
app.use(morgan(customLogger))
87+
88+
// Setup routes
89+
app.use(routerBasePath, router)
90+
91+
// Apply express middlewares
92+
router.use(cors())
93+
router.use(bodyParser.json())
94+
router.use(bodyParser.urlencoded({ extended: true }))
95+
96+
return app
97+
}
98+
99+
function customLogger(tokens, req, res) {
100+
const log = [
101+
tokens.method(req, res),
102+
tokens.url(req, res),
103+
tokens.status(req, res),
104+
tokens.res(req, res, 'content-length'),
105+
'-',
106+
tokens['response-time'](req, res),
107+
'ms'
108+
].join(' ')
109+
110+
if (process.env.NODE_ENV !== 'dev') {
111+
// Log only in AWS context to get back function logs
112+
console.log(log)
113+
}
114+
return log
115+
}

src/functions-templates/auth-fetch.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
exports.metadata = {
2+
name: 'Authenticated Fetch: uses node-fetch and Netlify Identity to access APIs',
3+
value: 'auth-fetch',
4+
short: 'auth-fetch'
5+
}
6+
exports.onComplete = () => {
7+
console.log(`authenticated node-fetch function created from template!`)
8+
console.log('REMINDER: Make sure to install `node-fetch` if you dont have it.')
9+
console.log(
10+
'REMINDER: Make sure to call this function with the Netlify Identity JWT. See https://netlify-gotrue-in-react.netlify.com/ for demo'
11+
)
12+
}
13+
14+
exports.templateCode = () => {
15+
return `
16+
// for a full working demo of Netlify Identity + Functions, see https://netlify-gotrue-in-react.netlify.com/
17+
18+
const fetch = require('node-fetch')
19+
exports.handler = async function(event, context) {
20+
if (!context.clientContext && !context.clientContext.identity) {
21+
return {
22+
statusCode: 500,
23+
body: JSON.stringify({
24+
msg:
25+
'No identity instance detected. Did you enable it?'
26+
}) // Could be a custom message or object i.e. JSON.stringify(err)
27+
};
28+
}
29+
const { identity, user } = context.clientContext;
30+
try {
31+
const response = await fetch('https://api.chucknorris.io/jokes/random');
32+
if (!response.ok) {
33+
// NOT res.status >= 200 && res.status < 300
34+
return { statusCode: response.status, body: response.statusText };
35+
}
36+
const data = await response.json();
37+
38+
return {
39+
statusCode: 200,
40+
body: JSON.stringify({ identity, user, msg: data.value })
41+
};
42+
} catch (err) {
43+
console.log(err); // output to netlify function log
44+
return {
45+
statusCode: 500,
46+
body: JSON.stringify({ msg: err.message }) // Could be a custom message or object i.e. JSON.stringify(err)
47+
};
48+
}
49+
}
50+
`
51+
}
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
// --- Netlify Template Metadata -- //
1+
exports.priority = 1
22
exports.metadata = {
3-
name: 'Basic Hello World function: shows async/await usage, and proper formatting with statusCode and body',
3+
name: 'Basic Hello World function: shows async/await usage, and response formatting',
44
value: 'hello-world',
55
short: 'hello-world'
66
}
7-
// exports.onComplete = () => {} // optional
8-
// --- Netlify Template Below -- //
7+
exports.templateCode = () => {
8+
return `
99
async function hello() {
1010
return Promise.resolve('Hello, World')
1111
}
12-
12+
1313
exports.handler = async function(event, context) {
1414
try {
1515
const body = await hello()
@@ -18,3 +18,5 @@ exports.handler = async function(event, context) {
1818
return { statusCode: 500, body: err.toString() }
1919
}
2020
}
21+
`
22+
}

src/functions-templates/node-fetch.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// --- Netlify Template Metadata -- //
21
exports.metadata = {
32
name: 'Fetch function: uses node-fetch to hit an external API without CORS issues',
43
value: 'node-fetch',
@@ -8,7 +7,9 @@ exports.onComplete = () => {
87
console.log(`node-fetch function created from template!`)
98
console.log('REMINDER: make sure to install `node-fetch` if you dont have it.')
109
}
11-
// --- Netlify Template Below -- //
10+
11+
exports.templateCode = () => {
12+
return `
1213
const fetch = require('node-fetch')
1314
exports.handler = async function(event, context) {
1415
try {
@@ -31,3 +32,5 @@ exports.handler = async function(event, context) {
3132
}
3233
}
3334
}
35+
`
36+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
exports.metadata = {
2+
name: 'Serverless HTTP: dynamic serverside rendering via functions',
3+
value: 'serverless-http',
4+
short: 'serverless-http'
5+
}
6+
exports.onComplete = () => {
7+
console.log(`serverless-http function created from template!`)
8+
console.log('REMINDER: Make sure to `npm install serverless-http express cors morgan body-parser compression`.')
9+
}
10+
exports.copyAssets = ['app/index.js']
11+
12+
exports.templateCode = () => {
13+
return `
14+
// for a full working demo check https://express-via-functions.netlify.com/.netlify/functions/serverless-http
15+
const serverless = require('serverless-http')
16+
const expressApp = require('./app')
17+
18+
// We need to define our function name for express routes to set the correct base path
19+
const functionName = 'serverless-http'
20+
21+
// Initialize express app
22+
const app = expressApp(functionName)
23+
24+
// Export lambda handler
25+
exports.handler = serverless(app)
26+
`
27+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy