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

Add typescript and golang templates, and let templates install addons #34

Merged
merged 3 commits into from
Mar 20, 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
59 changes: 43 additions & 16 deletions src/commands/functions/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,29 @@ const { flags } = require('@oclif/command')
const Command = require('@netlify/cli-utils')
const inquirer = require('inquirer')
const readRepoURL = require('../../utils/readRepoURL')
const { createSiteAddon } = require('../../utils/addons')
const http = require('http')
const fetch = require('node-fetch')
const cp = require('child_process')
const { createAddon } = require('netlify/src/addons')

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

/**
* Be very clear what is the SOURCE (templates dir) vs the DEST (functions dir)
*/
class FunctionsCreateCommand extends Command {
async run() {
const { flags, args } = this.parse(FunctionsCreateCommand)
const { config } = this.netlify

const { config, api, site } = this.netlify
const accessToken = await this.authenticate()
const functionsDir = ensureFunctionDirExists(flags, config, this.log)

/* either download from URL or scaffold from template */
if (flags.url) {
await downloadFromURL(flags, args, functionsDir)
} else {
await scaffoldFromTemplate(flags, args, functionsDir, this.log)
await scaffoldFromTemplate(flags, args, functionsDir, api, site, accessToken, this.log)
}
}
}
Expand Down Expand Up @@ -83,23 +88,31 @@ async function getNameFromArgs(args, flags, defaultName) {

// pick template from our existing templates
async function pickTemplate() {
// let templates = fs.readdirSync(templatesDir).filter(x => x.split('.').length === 1) // only folders
const registry = require(path.join(templatesDir, 'template-registry.js'))
let templates = registry.sort((a, b) => (a.priority || 999) - (b.priority || 999)) // doesnt scale but will be ok for now
// doesnt scale but will be ok for now
const registries = ['js', 'ts', 'go'].flatMap(formatRegistryArrayForInquirer)
const { chosentemplate } = await inquirer.prompt([
{
name: 'chosentemplate',
message: 'pick a template',
type: 'list',
choices: templates.map(t => ({
// confusing but this is the format inquirer wants
name: t.description,
value: t.name,
short: t.name
}))
choices: registries
}
])
return registry.find(x => x.name === chosentemplate)
return chosentemplate
function formatRegistryArrayForInquirer(lang) {
const registry = require(path.join(templatesDir, lang, 'template-registry.js'))
.sort((a, b) => (a.priority || 999) - (b.priority || 999))
.map(t => {
t.lang = lang
return {
// confusing but this is the format inquirer wants
name: `[${lang}] ` + t.description,
value: t,
short: lang + '-' + t.name
}
})
return [new inquirer.Separator(`----[${lang.toUpperCase()}]----`), ...registry]
}
}

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

// no --url flag specified, pick from a provided template
async function scaffoldFromTemplate(flags, args, functionsDir, log) {
const { onComplete, name: templateName } = await pickTemplate() // pull the rest of the metadata from the template
async function scaffoldFromTemplate(flags, args, functionsDir, api, site, accessToken, log) {
const { onComplete, name: templateName, lang, addons = [] } = await pickTemplate() // pull the rest of the metadata from the template

const pathToTemplate = path.join(templatesDir, templateName)
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`)
}
Expand Down Expand Up @@ -184,6 +197,20 @@ async function scaffoldFromTemplate(flags, args, functionsDir, log) {
})
}

if (addons.length) {
const siteId = site.id
if (!siteId) {
this.log('No site id found, please run inside a site folder or `netlify link`')
return false
}
api.getSite({ siteId }).then(async siteData => {
const arr = addons.map(addonName => {
log('installing addon: ' + addonName)
return createSiteAddon(accessToken, addonName, siteId, siteData, log)
})
return Promise.all(arr)
})
}
if (onComplete) onComplete() // do whatever the template wants to do after it is scaffolded
})
}
Expand Down
15 changes: 1 addition & 14 deletions src/functions-templates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,4 @@ place new templates here and our CLI will pick it up. each template must be in i

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

<!--
## providing metadata (and other functionality)

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

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.

you can optionally set a `priority` to pin display order.

in future we can think about other options we may want to offer.

## future dev thoughts

we will want a way to scale this to TS and Go as well. -->
every function should be registered with their respective `template-registry.js`.
18 changes: 18 additions & 0 deletions src/functions-templates/go/hello-world/hello-world.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package main

import (
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)

func handler(request events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error) {
return &events.APIGatewayProxyResponse{
StatusCode: 200,
Body: "Hello, World",
}, nil
}

func main() {
// Make the handler available for Remote Procedure Call by AWS Lambda
lambda.Start(handler)
}
12 changes: 12 additions & 0 deletions src/functions-templates/go/template-registry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// every object should have:
// // a 'name' field that corresponds to a folder
// // "description" is just what shows in the CLI but we use the name as the identifier
// onComplete is optional.
// priority is optional - for controlling what shows first in CLI
module.exports = [
{
name: 'hello-world',
priority: 1,
description: 'Basic Hello World function in Golang'
}
]
36 changes: 36 additions & 0 deletions src/functions-templates/js/fauna-crud/create.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const faunadb = require('faunadb')

/* configure faunaDB Client with our secret */
const q = faunadb.query
const client = new faunadb.Client({
secret: process.env.FAUNADB_SERVER_SECRET
})

/* export our lambda function as named "handler" export */
exports.handler = (event, context, callback) => {
/* parse the string body into a useable JS object */
const data = JSON.parse(event.body)
console.log('Function `todo-create` invoked', data)
const todoItem = {
data: data
}
/* construct the fauna query */
return client
.query(q.Create(q.Ref('classes/todos'), todoItem))
.then(response => {
console.log('success', response)
/* Success! return the response with statusCode 200 */
return callback(null, {
statusCode: 200,
body: JSON.stringify(response)
})
})
.catch(error => {
console.log('error', error)
/* Error! return the error with statusCode 400 */
return callback(null, {
statusCode: 400,
body: JSON.stringify(error)
})
})
}
32 changes: 32 additions & 0 deletions src/functions-templates/js/fauna-crud/delete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* Import faunaDB sdk */
const faunadb = require('faunadb')

function getId(urlPath) {
return urlPath.match(/([^\/]*)\/*$/)[0]
}

const q = faunadb.query
const client = new faunadb.Client({
secret: process.env.FAUNADB_SERVER_SECRET
})

exports.handler = (event, context, callback) => {
const id = getId(event.path)
console.log(`Function 'todo-delete' invoked. delete id: ${id}`)
return client
.query(q.Delete(q.Ref(`classes/todos/${id}`)))
.then(response => {
console.log('success', response)
return callback(null, {
statusCode: 200,
body: JSON.stringify(response)
})
})
.catch(error => {
console.log('error', error)
return callback(null, {
statusCode: 400,
body: JSON.stringify(error)
})
})
}
14 changes: 14 additions & 0 deletions src/functions-templates/js/fauna-crud/fauna-crud.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
exports.handler = async (event, context, callback) => {
const { action } = event.queryStringParameters
switch (action) {
case 'create':
return require('./create').handler(event, context, callback)
case 'read':
return require('./read').handler(event, context, callback)
case 'update':
return require('./update').handler(event, context, callback)
case 'delete':
return require('./delete').handler(event, context, callback)
}
return { statusCode: 500, body: 'unrecognized action ' + action }
}
20 changes: 20 additions & 0 deletions src/functions-templates/js/fauna-crud/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "fauna-crud",
"version": "1.0.0",
"description": "netlify functions:create - CRUD functionality with Fauna DB",
"main": "fauna-crud.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"netlify",
"serverless",
"js",
"faunadb"
],
"author": "Netlify",
"license": "MIT",
"dependencies": {
"faunadb": "^2.6.1"
}
}
32 changes: 32 additions & 0 deletions src/functions-templates/js/fauna-crud/read.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* Import faunaDB sdk */
const faunadb = require('faunadb')

function getId(urlPath) {
return urlPath.match(/([^\/]*)\/*$/)[0]
}

const q = faunadb.query
const client = new faunadb.Client({
secret: process.env.FAUNADB_SERVER_SECRET
})

exports.handler = (event, context, callback) => {
const id = getId(event.path)
console.log(`Function 'todo-read' invoked. Read id: ${id}`)
return client
.query(q.Get(q.Ref(`classes/todos/${id}`)))
.then(response => {
console.log('success', response)
return callback(null, {
statusCode: 200,
body: JSON.stringify(response)
})
})
.catch(error => {
console.log('error', error)
return callback(null, {
statusCode: 400,
body: JSON.stringify(error)
})
})
}
33 changes: 33 additions & 0 deletions src/functions-templates/js/fauna-crud/update.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* Import faunaDB sdk */
const faunadb = require('faunadb')

function getId(urlPath) {
return urlPath.match(/([^\/]*)\/*$/)[0]
}

const q = faunadb.query
const client = new faunadb.Client({
secret: process.env.FAUNADB_SERVER_SECRET
})

exports.handler = (event, context, callback) => {
const data = JSON.parse(event.body)
const id = getId(event.path)
console.log(`Function 'todo-update' invoked. update id: ${id}`)
return client
.query(q.Update(q.Ref(`classes/todos/${id}`), { data }))
.then(response => {
console.log('success', response)
return callback(null, {
statusCode: 200,
body: JSON.stringify(response)
})
})
.catch(error => {
console.log('error', error)
return callback(null, {
statusCode: 400,
body: JSON.stringify(error)
})
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
exports.handler = async (event, context) => {
console.log('protected function!')
// Reading the context.clientContext will give us the current user
const claims = context.clientContext && context.clientContext.user
console.log('user claims', claims)

if (!claims) {
console.log('No claims! Begone!')
return {
statusCode: 401,
body: JSON.stringify({
data: 'NOT ALLOWED'
})
}
}

return {
statusCode: 200,
body: JSON.stringify({
data: 'auth true'
})
}
}
19 changes: 19 additions & 0 deletions src/functions-templates/js/set-cookie/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "set-cookie",
"version": "1.0.0",
"description": "netlify functions:create - set a cookie with your Netlify Function",
"main": "set-cookie",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"netlify",
"serverless",
"js"
],
"author": "Netlify",
"license": "MIT",
"dependencies": {
"cookie": "^0.3.1"
}
}
Loading