From 843f6dceded8af8cb862de9dde98a47f65331148 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Date: Wed, 22 Dec 2021 00:17:39 +1100 Subject: [PATCH] Replace netlify's zip-it-and-ship-it for serverless deploy (#3782) --- .../deploy/aws-providers/serverless.js | 39 +++++---- packages/cli/src/commands/deploy/aws.js | 82 +++++++++++-------- .../cli/src/commands/deploy/packing/nft.js | 60 ++++++++++++++ .../setup/deploy/providers/aws-serverless.js | 4 +- 4 files changed, 128 insertions(+), 57 deletions(-) create mode 100644 packages/cli/src/commands/deploy/packing/nft.js diff --git a/packages/cli/src/commands/deploy/aws-providers/serverless.js b/packages/cli/src/commands/deploy/aws-providers/serverless.js index b354406549c3..4f68435775a7 100644 --- a/packages/cli/src/commands/deploy/aws-providers/serverless.js +++ b/packages/cli/src/commands/deploy/aws-providers/serverless.js @@ -1,34 +1,33 @@ -export const preRequisites = [ +import ntfPack from '../packing/nft' + +export const preRequisites = () => [ { title: 'Checking if Serverless framework is installed...', command: ['serverless', ['--version']], errorMessage: [ 'Looks like Serverless is not installed.', - 'Please follow the steps at https://www.serverless.com/framework/docs/providers/aws/guide/installation/ to install Serverless.', - ], - }, - { - title: 'Checking if @netlify/zip-it-and-ship-it is installed...', - command: ['yarn', ['zip-it-and-ship-it', '--version']], - errorMessage: [ - 'Looks like @netlify/zip-it-and-ship-it is not installed.', - 'Either run `yarn rw setup aws-serverless` or add it separately as a dev dependency in the api workspace.', + 'Please follow the steps at https://www.serverless.com/framework/docs/getting-started to install Serverless.', ], }, ] -export const buildCommands = [ - { title: 'Building API...', command: ['yarn', ['rw', 'build', 'api']] }, +export const buildCommands = () => [ { - title: 'Packaging API...', - command: [ - 'yarn', - ['zip-it-and-ship-it', 'api/dist/functions/', 'api/dist/zipball'], - ], + title: 'Building API...', + command: ['yarn', ['rw', 'build', 'api']], + }, + { + title: 'Packing Functions...', + task: ntfPack, }, ] -export const deployCommand = { - title: 'Deploying...', - command: ['serverless', ['deploy']], +export const deployCommands = (yargs) => { + const stage = yargs.stage ? ['--stage', yargs.stage] : [] + return [ + { + title: 'Deploying...', + command: ['serverless', ['deploy', ...stage]], + }, + ] } diff --git a/packages/cli/src/commands/deploy/aws.js b/packages/cli/src/commands/deploy/aws.js index 2d6d0134d5ca..2b2339666365 100644 --- a/packages/cli/src/commands/deploy/aws.js +++ b/packages/cli/src/commands/deploy/aws.js @@ -3,6 +3,7 @@ import path from 'path' import execa from 'execa' import Listr from 'listr' +import VerboseRenderer from 'listr-verbose-renderer' import terminalLink from 'terminal-link' import { getPaths } from '../../lib' @@ -29,6 +30,17 @@ export const builder = (yargs) => { default: 'api', type: 'array', }) + .option('verbose', { + describe: 'verbosity of logs', + default: true, + type: 'boolean', + }) + .option('stage', { + describe: + 'serverless stage pass through param: https://www.serverless.com/blog/stages-and-environments', + default: 'dev', + type: 'string', + }) .epilogue( `Also see the ${terminalLink( 'Redwood CLI Reference', @@ -37,10 +49,33 @@ export const builder = (yargs) => { ) } -export const handler = async ({ provider }) => { +export const handler = async (yargs) => { + const { provider, verbose } = yargs const BASE_DIR = getPaths().base const providerData = await import(`./aws-providers/${provider}`) + const mapCommandsToListr = ({ title, command, task, errorMessage }) => { + return { + title: title, + task: task + ? task + : async () => { + try { + const executingCommand = execa(...command, { + cwd: BASE_DIR, + }) + executingCommand.stdout.pipe(process.stdout) + await executingCommand + } catch (error) { + if (errorMessage) { + error.message = error.message + '\n' + errorMessage.join(' ') + } + throw error + } + }, + } + } + const tasks = new Listr( [ providerData.preRequisites && @@ -48,52 +83,27 @@ export const handler = async ({ provider }) => { title: 'Checking pre-requisites', task: () => new Listr( - providerData.preRequisites.map((preReq) => { - return { - title: preReq.title, - task: async () => { - try { - await execa(...preReq.command) - } catch (error) { - error.message = - error.message + '\n' + preReq.errorMessage.join(' ') - throw error - } - }, - } - }) + providerData.preRequisites(yargs).map(mapCommandsToListr) ), }, { title: 'Building and Packaging...', task: () => - new Listr( - providerData.buildCommands.map((commandDetail) => { - return { - title: commandDetail.title, - task: async () => { - await execa(...commandDetail.command, { - cwd: BASE_DIR, - }) - }, - } - }), - { collapse: false } - ), + new Listr(providerData.buildCommands(yargs).map(mapCommandsToListr), { + collapse: false, + }), + }, + { + title: 'Deploying to AWS', + task: () => + new Listr(providerData.deployCommands(yargs).map(mapCommandsToListr)), }, ].filter(Boolean), - { collapse: false } + { collapse: false, renderer: verbose && VerboseRenderer } ) try { await tasks.run() - - console.log(c.green(providerData.deployCommand.title)) - const deploy = execa(...providerData.deployCommand.command, { - cwd: BASE_DIR, - }) - deploy.stdout.pipe(process.stdout) - await deploy } catch (e) { console.log(c.error(e.message)) process.exit(1) diff --git a/packages/cli/src/commands/deploy/packing/nft.js b/packages/cli/src/commands/deploy/packing/nft.js new file mode 100644 index 000000000000..f391bd1babfb --- /dev/null +++ b/packages/cli/src/commands/deploy/packing/nft.js @@ -0,0 +1,60 @@ +import path from 'path' + +import { nodeFileTrace } from '@vercel/nft' +import archiver from 'archiver' +import fse from 'fs-extra' + +const ZIPBALL_DIR = './api/dist/zipball' + +function zipDirectory(source, out) { + const archive = archiver('zip', { zlib: { level: 5 } }) + const stream = fse.createWriteStream(out) + + return new Promise((resolve, reject) => { + archive + .directory(source, false) + .on('error', (err) => reject(err)) + .pipe(stream) + + stream.on('close', () => resolve()) + archive.finalize() + }) +} + +async function packageSingleFunction(functionFile) { + const { name: functionName } = path.parse(functionFile) + const { fileList: functionDependencyFileList } = await nodeFileTrace([ + functionFile, + ]) + const copyPromises = [] + for (const singleDependencyPath of functionDependencyFileList) { + copyPromises.push( + fse.copy( + './' + singleDependencyPath, + `${ZIPBALL_DIR}/${functionName}/${singleDependencyPath}` + ) + ) + } + const functionEntryPromise = fse.outputFile( + `${ZIPBALL_DIR}/${functionName}/${functionName}.js`, + `module.exports = require('${functionFile}')` + ) + copyPromises.push(functionEntryPromise) + + await Promise.all(copyPromises) + await 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)) +} + +export default ntfPack diff --git a/packages/cli/src/commands/setup/deploy/providers/aws-serverless.js b/packages/cli/src/commands/setup/deploy/providers/aws-serverless.js index c2f82a958af3..f5948786a539 100644 --- a/packages/cli/src/commands/setup/deploy/providers/aws-serverless.js +++ b/packages/cli/src/commands/setup/deploy/providers/aws-serverless.js @@ -83,7 +83,9 @@ export const preRequisites = [ ] export const apiDevPackages = [ - '@netlify/zip-it-and-ship-it', + '@vercel/nft', + 'archiver', + 'fs-extra', 'serverless-dotenv-plugin', ]