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

Commit dc51eb2

Browse files
author
sw-yx
committed
add it working
1 parent 26ecd50 commit dc51eb2

33 files changed

+927
-30
lines changed

src/commands/functions/create.js

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,29 @@ const { flags } = require('@oclif/command')
55
const Command = require('@netlify/cli-utils')
66
const inquirer = require('inquirer')
77
const readRepoURL = require('../../utils/readRepoURL')
8+
const { createSiteAddon } = require('../../utils/addons')
89
const http = require('http')
910
const fetch = require('node-fetch')
1011
const cp = require('child_process')
12+
const { createAddon } = require('netlify/src/addons')
1113

1214
const templatesDir = path.resolve(__dirname, '../../functions-templates')
1315

16+
/**
17+
* Be very clear what is the SOURCE (templates dir) vs the DEST (functions dir)
18+
*/
1419
class FunctionsCreateCommand extends Command {
1520
async run() {
1621
const { flags, args } = this.parse(FunctionsCreateCommand)
17-
const { config } = this.netlify
18-
22+
const { config, api, site } = this.netlify
23+
const accessToken = await this.authenticate()
1924
const functionsDir = ensureFunctionDirExists(flags, config, this.log)
2025

2126
/* either download from URL or scaffold from template */
2227
if (flags.url) {
2328
await downloadFromURL(flags, args, functionsDir)
2429
} else {
25-
await scaffoldFromTemplate(flags, args, functionsDir, this.log)
30+
await scaffoldFromTemplate(flags, args, functionsDir, api, site, accessToken, this.log)
2631
}
2732
}
2833
}
@@ -83,23 +88,31 @@ async function getNameFromArgs(args, flags, defaultName) {
8388

8489
// pick template from our existing templates
8590
async function pickTemplate() {
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
91+
// doesnt scale but will be ok for now
92+
const registries = ['js', 'ts', 'go'].flatMap(formatRegistryArrayForInquirer)
8993
const { chosentemplate } = await inquirer.prompt([
9094
{
9195
name: 'chosentemplate',
9296
message: 'pick a template',
9397
type: 'list',
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-
}))
98+
choices: registries
10099
}
101100
])
102-
return registry.find(x => x.name === chosentemplate)
101+
return chosentemplate
102+
function formatRegistryArrayForInquirer(lang) {
103+
const registry = require(path.join(templatesDir, lang, 'template-registry.js'))
104+
.sort((a, b) => (a.priority || 999) - (b.priority || 999))
105+
.map(t => {
106+
t.lang = lang
107+
return {
108+
// confusing but this is the format inquirer wants
109+
name: `[${lang}] ` + t.description,
110+
value: t,
111+
short: lang + '-' + t.name
112+
}
113+
})
114+
return [new inquirer.Separator(`----[${lang.toUpperCase()}]----`), ...registry]
115+
}
103116
}
104117

105118
/* get functions dir (and make it if necessary) */
@@ -150,10 +163,10 @@ async function downloadFromURL(flags, args, functionsDir) {
150163
}
151164

152165
// 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
166+
async function scaffoldFromTemplate(flags, args, functionsDir, api, site, accessToken, log) {
167+
const { onComplete, name: templateName, lang, addons = [] } = await pickTemplate() // pull the rest of the metadata from the template
155168

156-
const pathToTemplate = path.join(templatesDir, templateName)
169+
const pathToTemplate = path.join(templatesDir, lang, templateName)
157170
if (!fs.existsSync(pathToTemplate)) {
158171
throw new Error(`there isnt a corresponding folder to the selected name, ${templateName} template is misconfigured`)
159172
}
@@ -184,6 +197,20 @@ async function scaffoldFromTemplate(flags, args, functionsDir, log) {
184197
})
185198
}
186199

200+
if (addons.length) {
201+
const siteId = site.id
202+
if (!siteId) {
203+
this.log('No site id found, please run inside a site folder or `netlify link`')
204+
return false
205+
}
206+
api.getSite({ siteId }).then(async siteData => {
207+
const arr = addons.map(addonName => {
208+
log('installing addon: ' + addonName)
209+
return createSiteAddon(accessToken, addonName, siteId, siteData, log)
210+
})
211+
return Promise.all(arr)
212+
})
213+
}
187214
if (onComplete) onComplete() // do whatever the template wants to do after it is scaffolded
188215
})
189216
}

src/functions-templates/README.md

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,4 @@ place new templates here and our CLI will pick it up. each template must be in i
66

77
we dont colocate this inside `src/commands/functions` because oclif will think it's a new command.
88

9-
<!--
10-
## providing metadata (and other functionality)
11-
12-
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).
13-
14-
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.
15-
16-
you can optionally set a `priority` to pin display order.
17-
18-
in future we can think about other options we may want to offer.
19-
20-
## future dev thoughts
21-
22-
we will want a way to scale this to TS and Go as well. -->
9+
every function should be registered with their respective `template-registry.js`.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package main
2+
3+
import (
4+
"github.com/aws/aws-lambda-go/events"
5+
"github.com/aws/aws-lambda-go/lambda"
6+
)
7+
8+
func handler(request events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error) {
9+
return &events.APIGatewayProxyResponse{
10+
StatusCode: 200,
11+
Body: "Hello, World",
12+
}, nil
13+
}
14+
15+
func main() {
16+
// Make the handler available for Remote Procedure Call by AWS Lambda
17+
lambda.Start(handler)
18+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// every object should have:
2+
// // a 'name' field that corresponds to a folder
3+
// // "description" is just what shows in the CLI but we use the name as the identifier
4+
// onComplete is optional.
5+
// priority is optional - for controlling what shows first in CLI
6+
module.exports = [
7+
{
8+
name: 'hello-world',
9+
priority: 1,
10+
description: 'Basic Hello World function in Golang'
11+
}
12+
]
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const faunadb = require('faunadb')
2+
3+
/* configure faunaDB Client with our secret */
4+
const q = faunadb.query
5+
const client = new faunadb.Client({
6+
secret: process.env.FAUNADB_SERVER_SECRET
7+
})
8+
9+
/* export our lambda function as named "handler" export */
10+
exports.handler = (event, context, callback) => {
11+
/* parse the string body into a useable JS object */
12+
const data = JSON.parse(event.body)
13+
console.log('Function `todo-create` invoked', data)
14+
const todoItem = {
15+
data: data
16+
}
17+
/* construct the fauna query */
18+
return client
19+
.query(q.Create(q.Ref('classes/todos'), todoItem))
20+
.then(response => {
21+
console.log('success', response)
22+
/* Success! return the response with statusCode 200 */
23+
return callback(null, {
24+
statusCode: 200,
25+
body: JSON.stringify(response)
26+
})
27+
})
28+
.catch(error => {
29+
console.log('error', error)
30+
/* Error! return the error with statusCode 400 */
31+
return callback(null, {
32+
statusCode: 400,
33+
body: JSON.stringify(error)
34+
})
35+
})
36+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/* Import faunaDB sdk */
2+
const faunadb = require('faunadb')
3+
4+
function getId(urlPath) {
5+
return urlPath.match(/([^\/]*)\/*$/)[0]
6+
}
7+
8+
const q = faunadb.query
9+
const client = new faunadb.Client({
10+
secret: process.env.FAUNADB_SERVER_SECRET
11+
})
12+
13+
exports.handler = (event, context, callback) => {
14+
const id = getId(event.path)
15+
console.log(`Function 'todo-delete' invoked. delete id: ${id}`)
16+
return client
17+
.query(q.Delete(q.Ref(`classes/todos/${id}`)))
18+
.then(response => {
19+
console.log('success', response)
20+
return callback(null, {
21+
statusCode: 200,
22+
body: JSON.stringify(response)
23+
})
24+
})
25+
.catch(error => {
26+
console.log('error', error)
27+
return callback(null, {
28+
statusCode: 400,
29+
body: JSON.stringify(error)
30+
})
31+
})
32+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
exports.handler = async (event, context, callback) => {
2+
const { action } = event.queryStringParameters
3+
switch (action) {
4+
case 'create':
5+
return require('./create').handler(event, context, callback)
6+
case 'read':
7+
return require('./read').handler(event, context, callback)
8+
case 'update':
9+
return require('./update').handler(event, context, callback)
10+
case 'delete':
11+
return require('./delete').handler(event, context, callback)
12+
}
13+
return { statusCode: 500, body: 'unrecognized action ' + action }
14+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "fauna-crud",
3+
"version": "1.0.0",
4+
"description": "netlify functions:create - CRUD functionality with Fauna DB",
5+
"main": "fauna-crud.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"keywords": [
10+
"netlify",
11+
"serverless",
12+
"js",
13+
"faunadb"
14+
],
15+
"author": "Netlify",
16+
"license": "MIT",
17+
"dependencies": {
18+
"faunadb": "^2.6.1"
19+
}
20+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/* Import faunaDB sdk */
2+
const faunadb = require('faunadb')
3+
4+
function getId(urlPath) {
5+
return urlPath.match(/([^\/]*)\/*$/)[0]
6+
}
7+
8+
const q = faunadb.query
9+
const client = new faunadb.Client({
10+
secret: process.env.FAUNADB_SERVER_SECRET
11+
})
12+
13+
exports.handler = (event, context, callback) => {
14+
const id = getId(event.path)
15+
console.log(`Function 'todo-read' invoked. Read id: ${id}`)
16+
return client
17+
.query(q.Get(q.Ref(`classes/todos/${id}`)))
18+
.then(response => {
19+
console.log('success', response)
20+
return callback(null, {
21+
statusCode: 200,
22+
body: JSON.stringify(response)
23+
})
24+
})
25+
.catch(error => {
26+
console.log('error', error)
27+
return callback(null, {
28+
statusCode: 400,
29+
body: JSON.stringify(error)
30+
})
31+
})
32+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* Import faunaDB sdk */
2+
const faunadb = require('faunadb')
3+
4+
function getId(urlPath) {
5+
return urlPath.match(/([^\/]*)\/*$/)[0]
6+
}
7+
8+
const q = faunadb.query
9+
const client = new faunadb.Client({
10+
secret: process.env.FAUNADB_SERVER_SECRET
11+
})
12+
13+
exports.handler = (event, context, callback) => {
14+
const data = JSON.parse(event.body)
15+
const id = getId(event.path)
16+
console.log(`Function 'todo-update' invoked. update id: ${id}`)
17+
return client
18+
.query(q.Update(q.Ref(`classes/todos/${id}`), { data }))
19+
.then(response => {
20+
console.log('success', response)
21+
return callback(null, {
22+
statusCode: 200,
23+
body: JSON.stringify(response)
24+
})
25+
})
26+
.catch(error => {
27+
console.log('error', error)
28+
return callback(null, {
29+
statusCode: 400,
30+
body: JSON.stringify(error)
31+
})
32+
})
33+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
exports.handler = async (event, context) => {
2+
console.log('protected function!')
3+
// Reading the context.clientContext will give us the current user
4+
const claims = context.clientContext && context.clientContext.user
5+
console.log('user claims', claims)
6+
7+
if (!claims) {
8+
console.log('No claims! Begone!')
9+
return {
10+
statusCode: 401,
11+
body: JSON.stringify({
12+
data: 'NOT ALLOWED'
13+
})
14+
}
15+
}
16+
17+
return {
18+
statusCode: 200,
19+
body: JSON.stringify({
20+
data: 'auth true'
21+
})
22+
}
23+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "set-cookie",
3+
"version": "1.0.0",
4+
"description": "netlify functions:create - set a cookie with your Netlify Function",
5+
"main": "set-cookie",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"keywords": [
10+
"netlify",
11+
"serverless",
12+
"js"
13+
],
14+
"author": "Netlify",
15+
"license": "MIT",
16+
"dependencies": {
17+
"cookie": "^0.3.1"
18+
}
19+
}

0 commit comments

Comments
 (0)