Skip to content

Commit 132220b

Browse files
committed
Deploy actions from functions directory (#57)
Update manifest to restore inputs but mark deprecated. Extract redirects and rewrite functions targets. Test updates. Tweak error message formatting. Add input parameter checks. Accommodate forked netlify-builder. Fail build if functions fail to deploy. Remove unused package.
1 parent 142b8ad commit 132220b

File tree

6 files changed

+720
-56
lines changed

6 files changed

+720
-56
lines changed

manifest.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ inputs:
88
description: Deploy entire project include web frontend. Proxy domain to Nimbella.
99
default: false
1010

11-
- name: env
11+
- name: envs
1212
description: Environment variables to forward from Netlify CI to Nimbella Projects.
1313
default: []
14+
15+
## These are deprecated: migrate to using Nimbella project.yml instead which permit per-API level control.
16+
- name: functions
17+
description: The source directory containing Netlify functions to deploy to Nimbella.
18+
19+
- name: timeout
20+
description: Timeout LIMIT in milliseconds to apply to all Netlify functions running on Nimbella.
21+
22+
- name: memory
23+
description: Memory LIMIT in MB to apply to all Netlify functions running on Nimbella.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@
2929
},
3030
"homepage": "https://github.com/nimbella/netlify-plugin-nimbella#readme",
3131
"dependencies": {
32+
"cpx": "^1.5.0",
3233
"make-dir": "^3.1.0",
34+
"netlify-lambda": "satyarohith/netlify-lambda#88eac6a37bfb0920897c86ecda8682944a2bf5be",
35+
"netlify-redirect-parser": "^2.5.0",
3336
"nimbella-cli": "https://apigcp.nimbella.io/downloads/nim/nimbella-cli.tgz"
3437
},
3538
"xo": {

src/index.js

Lines changed: 103 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ const {existsSync} = require('fs')
22
const {appendFile, readFile, writeFile} = require('fs').promises
33
const {join} = require('path')
44
const {homedir} = require('os')
5+
const {buildAndDeployNetlifyFunctions, rewriteRedirects} = require('./nfn')
56

67
const nimConfig = join(homedir(), '.nimbella')
7-
let isProject = false
8-
let deployWeb = false
8+
let isProject = false // True if deploying a Nimbella project
9+
let deployWeb = false // True if deploying a Nimbella project with a web folder and proxying the domain
10+
let hasFunctions = false // True if deploying Netlify functions
911

1012
// Disable auto updates of nim.
1113
process.env.NIM_DISABLE_AUTOUPDATE = '1'
@@ -39,44 +41,102 @@ async function deployProject(run, includeWeb) {
3941
// matching rule to the _redirects file. The target is the Nimbella namespace/:splat.
4042
//
4143
// If deploying the web assets as well, then proxy the entire domain instead.
42-
async function processRedirects(run, inputs) {
44+
async function addRedirect(inputs, {namespace, apihost}) {
4345
const redirectRules = []
44-
const {stdout: namespace} = await run.command(`nim auth current`)
45-
const apihost = await getApiHost(run)
4646

4747
if (deployWeb) {
4848
redirectRules.push(`/* https://${namespace}-${apihost}/:splat 200!`)
49-
} else {
50-
let {path: redirectPath} = inputs
51-
redirectPath = redirectPath.endsWith('/')
52-
? redirectPath
53-
: redirectPath + '/'
54-
if (redirectPath) {
55-
redirectRules.push(
56-
`${redirectPath}* https://${apihost}/api/v1/web/${namespace}/:splat 200!`
57-
)
58-
}
49+
} else if (inputs.path) {
50+
const redirectPath = inputs.path.endsWith('/')
51+
? inputs.path
52+
: inputs.path + '/'
53+
const pkg = isProject ? '' : 'default/'
54+
redirectRules.push(
55+
`${redirectPath}* https://${apihost}/api/v1/web/${namespace}/${pkg}:splat 200!`
56+
)
5957
}
6058

6159
return redirectRules
6260
}
6361

62+
/**
63+
* Checks inputs for validity.
64+
* Issues warning if deprecated input properties are used.
65+
* @param {object} inputs
66+
* @returns true iff input parameters are valid
67+
*/
68+
function checkInputsAndNotifyOfDeprecation(inputs) {
69+
const warn = (prop) => {
70+
const chalk = require('chalk')
71+
console.warn(
72+
chalk.yellow(`${prop} is deprecated.`),
73+
'Migrate to Nimbella project.yml.'
74+
)
75+
}
76+
77+
const error = (prop, units) => {
78+
const chalk = require('chalk')
79+
console.warn(chalk.yellow(`${prop} must be a number in ${units}.`))
80+
}
81+
82+
let valid = true
83+
if (inputs) {
84+
if (inputs.functions) warn('[inputs.functions]')
85+
if (inputs.timeout) {
86+
warn('[inputs.timeout]')
87+
if (Number.isNaN(Number(inputs.timeout))) {
88+
error('[inputs.timeout]', 'milliseconds in [100-10000])')
89+
valid = false
90+
}
91+
}
92+
93+
if (inputs.memory) {
94+
warn('[inputs.memory]')
95+
if (Number.isNaN(Number(inputs.memory))) {
96+
error('[inputs.memory]', 'megabytes in [128-512])')
97+
valid = false
98+
}
99+
}
100+
}
101+
102+
return valid
103+
}
104+
105+
async function constructDotEnvFile(inputs) {
106+
if (inputs.envs && inputs.envs.length > 0) {
107+
console.log('Forwarding environment variables:')
108+
let envVars = ''
109+
inputs.envs.forEach((env) => {
110+
console.log(`\t- ${env}`)
111+
envVars += `\n${env} = ${process.env[env]}`
112+
})
113+
await appendFile('.env', envVars)
114+
}
115+
}
116+
64117
module.exports = {
65118
// Execute before build starts.
66119
onPreBuild: async ({utils, inputs}) => {
120+
const valid = checkInputsAndNotifyOfDeprecation(inputs)
121+
if (!valid) {
122+
utils.build.failBuild('Invalid input parameters.')
123+
}
124+
67125
if (
68126
!process.env.NIMBELLA_LOGIN_TOKEN &&
69127
!(await utils.cache.has(nimConfig))
70128
) {
71129
utils.build.failBuild(
72-
'Nimbella login token is not available. Please run `netlify addons:create nimbella` at the base of your local project directory linked to your Netlify site.'
130+
[
131+
'Nimbella login token is not available.',
132+
`Add NIMBELLA_LOGIN_TOKEN to your build environment.',
133+
'You may also run 'netlify addons:create nimbella' in your project directory linked to this Netlify site.`
134+
].join('\n')
73135
)
74136
}
75137

76138
await utils.cache.restore(nimConfig)
77-
78139
const loggedIn = existsSync(nimConfig)
79-
// Login if not logged in before.
80140
if (loggedIn) {
81141
try {
82142
const {stdout} = await utils.run.command('nim auth current', {
@@ -122,28 +182,28 @@ module.exports = {
122182
}
123183

124184
isProject = existsSync('packages') || (deployWeb && existsSync('web'))
185+
hasFunctions = inputs.functions && existsSync(inputs.functions)
186+
187+
if (hasFunctions && isProject) {
188+
utils.build.failBuild(
189+
'Detected both a Nimbella project and a functions directory. Use one or the other.'
190+
)
191+
}
125192
},
126193
// Build and deploy the Nimbella project
127194
onBuild: async ({utils, inputs}) => {
128195
if (process.env.CONTEXT === 'production') {
129196
if (isProject) {
130-
if (inputs.env && inputs.env.length > 0) {
131-
console.log('Forwarding environment variables:')
132-
let envVars = ''
133-
inputs.env.forEach((env) => {
134-
console.log(`\t- ${env}`)
135-
envVars += `\n${env} = ${process.env[env]}`
136-
})
137-
await appendFile('.env', envVars)
138-
}
139-
140197
try {
198+
await constructDotEnvFile(inputs)
141199
await deployProject(utils.run, deployWeb)
142200
} catch (error) {
143201
utils.build.failBuild('Failed to build and deploy the project', {
144202
error
145203
})
146204
}
205+
} else if (hasFunctions) {
206+
return buildAndDeployNetlifyFunctions({utils, inputs})
147207
} else {
148208
console.log(
149209
`Skipping the build and deployment: Nimbella project not detected.`
@@ -157,11 +217,17 @@ module.exports = {
157217
},
158218
// Execute after build is done.
159219
onPostBuild: async ({constants, utils, inputs}) => {
160-
if (process.env.CONTEXT === 'production' && isProject) {
220+
if (process.env.CONTEXT === 'production' && (isProject || hasFunctions)) {
161221
const redirectsFile = join(constants.PUBLISH_DIR, '_redirects')
162-
const redirectRules = await processRedirects(utils.run, inputs)
163-
164-
if (redirectRules.length > 0) {
222+
const {stdout: namespace} = await utils.run.command(`nim auth current`)
223+
const apihost = await getApiHost(utils.run)
224+
const creds = {namespace, apihost}
225+
const fnRewrites = hasFunctions
226+
? await rewriteRedirects(constants, creds)
227+
: []
228+
const redirectRules = await addRedirect(inputs, creds)
229+
230+
if (fnRewrites.length > 0 || redirectRules.length > 0) {
165231
let content = ''
166232
if (existsSync(redirectsFile)) {
167233
content = await readFile(redirectsFile)
@@ -171,8 +237,12 @@ module.exports = {
171237
}
172238

173239
// The rewrites take precedence
174-
await writeFile(redirectsFile, redirectRules.join('\n') + '\n')
240+
fnRewrites.push(...redirectRules)
241+
await writeFile(redirectsFile, fnRewrites.join('\n') + '\n')
175242
await appendFile(redirectsFile, content)
243+
244+
const rf = await readFile(redirectsFile)
245+
console.log(`Redirects:\n${rf}`)
176246
}
177247
}
178248
}

0 commit comments

Comments
 (0)