Skip to content

Commit

Permalink
fix(sls): Package nested functions too (redwoodjs#5850)
Browse files Browse the repository at this point in the history
  • Loading branch information
dac09 authored Jun 29, 2022
1 parent b189e65 commit 7db2d9e
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 17 deletions.
13 changes: 10 additions & 3 deletions docs/docs/deploy/serverless.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ Once that command completes you should see a message including the URL of your s

From now on you can simply run `yarn rw deploy serverless` when you're ready to deploy (which will also be much faster).


:::info
Remember, if you add or generate new serverless functions (or endpoints), you'll need to update the configuration in your serverless.yml in `./api/serverless.yml`.

By default we only configure the `auth` and `graphql` functions for you.
:::

## Environment Variables

For local deployment (meaning you're deploying from your own machine, or another that you're in control of) you can put any ENV vars that are production-only into `.env.production`. They will override any same-named vars in `.env`. Make sure neither of these files is checked into your code repository!
Expand Down Expand Up @@ -99,6 +106,6 @@ Note that `production` is the default stage when you deploy with `yarn rw server

This will take several minutes, so grab your favorite beverage and enjoy your new $0 monthly bill!

> Pro tip: if you get tired of typing `serverless` each time, you can use the much shorter `sls` alias:
>
> `yarn rw deploy sls`
:::tip Pro tip
If you get tired of typing `serverless` each time, you can use the much shorter `sls` alias: `yarn rw deploy sls`
:::
1 change: 0 additions & 1 deletion packages/api-server/src/__tests__/withWebServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const FIXTURE_PATH = path.resolve(
// because its gitignored
jest.mock('@redwoodjs/internal', () => {
return {
// @ts-expect-error spread error unnecessarily
...jest.requireActual('@redwoodjs/internal'),
findPrerenderedHtml: () => {
return ['about.html', 'mocked.html', 'posts/new.html', 'index.html']
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/__mocks__/@vercel/nft.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
nodeFileTrace: jest.fn(),
}
70 changes: 70 additions & 0 deletions packages/cli/src/commands/deploy/__tests__/nftPack.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import fs from 'fs'
import path from 'path'

import { buildApi, findApiDistFunctions } from '@redwoodjs/internal'

import nftPacker from '../packing/nft'

const FIXTURE_PATH = path.resolve(
__dirname,
'../../../../../../__fixtures__/example-todo-main'
)

let functionDistFiles

beforeAll(() => {
process.env.RWJS_CWD = FIXTURE_PATH

// Actually build the fixture, if we need it
if (!fs.existsSync(path.join(FIXTURE_PATH, 'api/dist/functions'))) {
buildApi()
}

functionDistFiles = findApiDistFunctions()
})

afterAll(() => {
delete process.env.RWJS_CWD
})

test('Check packager detects all functions', () => {
const packageFileMock = jest
.spyOn(nftPacker, 'packageSingleFunction')
.mockResolvedValue(true)

nftPacker.nftPack()

expect(packageFileMock).toHaveBeenCalledTimes(5)
})

test('Creates entry file for nested functions correctly', () => {
const nestedFunction = functionDistFiles.find((fPath) =>
fPath.includes('nested')
)

const [outputPath, content] = nftPacker.generateEntryFile(
nestedFunction,
'nested'
)

expect(outputPath).toBe('./api/dist/zipball/nested/nested.js')
expect(content).toMatchInlineSnapshot(
`"module.exports = require('./api/dist/functions/nested/nested.js')"`
)
})

test('Creates entry file for top level functions correctly', () => {
const graphqlFunction = functionDistFiles.find((fPath) =>
fPath.includes('graphql')
)

const [outputPath, content] = nftPacker.generateEntryFile(
graphqlFunction,
'graphql'
)

expect(outputPath).toBe('./api/dist/zipball/graphql/graphql.js')
expect(content).toMatchInlineSnapshot(
`"module.exports = require('./api/dist/functions/graphql.js')"`
)
})
49 changes: 38 additions & 11 deletions packages/cli/src/commands/deploy/packing/nft.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import { nodeFileTrace } from '@vercel/nft'
import archiver from 'archiver'
import fse from 'fs-extra'

import {
ensurePosixPath,
findApiDistFunctions,
getPaths,
} from '@redwoodjs/internal'

const ZIPBALL_DIR = './api/dist/zipball'

function zipDirectory(source, out) {
Expand All @@ -21,8 +27,20 @@ function zipDirectory(source, out) {
})
}

// returns a tuple of [filePath, fileContent]
function generateEntryFile(functionAbsolutePath, name) {
const relativeImport = ensurePosixPath(
path.relative(getPaths().base, functionAbsolutePath)
)
return [
`${ZIPBALL_DIR}/${name}/${name}.js`,
`module.exports = require('./${relativeImport}')`,
]
}

async function packageSingleFunction(functionFile) {
const { name: functionName } = path.parse(functionFile)

const { fileList: functionDependencyFileList } = await nodeFileTrace([
functionFile,
])
Expand All @@ -35,26 +53,35 @@ async function packageSingleFunction(functionFile) {
)
)
}
const functionEntryPromise = fse.outputFile(
`${ZIPBALL_DIR}/${functionName}/${functionName}.js`,
`module.exports = require('${functionFile}')`
)

const [entryFilePath, content] = generateEntryFile(functionFile, functionName)

// This generates an "entry" file, that just proxies the actual
// function that is nested in api/dist/
const functionEntryPromise = fse.outputFile(entryFilePath, content)
copyPromises.push(functionEntryPromise)

await Promise.all(copyPromises)
await zipDirectory(
await exports.zipDirectory(
`${ZIPBALL_DIR}/${functionName}`,
`${ZIPBALL_DIR}/${functionName}.zip`
)
await fse.remove(`${ZIPBALL_DIR}/${functionName}`)
return
}

async function ntfPack() {
const filesToBePacked = (await fse.readdir('./api/dist/functions'))
.filter((path) => path.endsWith('.js'))
.map((path) => `./api/dist/functions/${path}`)
return Promise.all(filesToBePacked.map(packageSingleFunction))
function nftPack() {
const filesToBePacked = findApiDistFunctions()
return Promise.all(filesToBePacked.map(exports.packageSingleFunction))
}

// We do this, so we can spy the functions in the test
// It didn't make sense to separate into different files
const exports = {
nftPack,
packageSingleFunction,
generateEntryFile,
zipDirectory,
}

export default ntfPack
export default exports
4 changes: 2 additions & 2 deletions packages/cli/src/commands/deploy/serverless.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ export const buildCommands = ({ sides }) => {
enabled: () => sides.includes('api'),
task: async () => {
// Dynamically import this function
// becuase its dependencies are only installed when `rw setup deploy serverless` is run
const { default: nftPack } = await import('./packing/nft')
// because its dependencies are only installed when `rw setup deploy serverless` is run
const { nftPack } = (await import('./packing/nft')).default

await nftPack()
},
Expand Down

0 comments on commit 7db2d9e

Please sign in to comment.