mirror of
https://github.com/serverless/serverless.git
synced 2025-12-08 19:46:03 +00:00
203 lines
6.1 KiB
JavaScript
203 lines
6.1 KiB
JavaScript
import { pathToFileURL } from 'url'
|
|
import path from 'path'
|
|
import os from 'os'
|
|
import fs from 'fs'
|
|
import { inspect } from 'util'
|
|
|
|
const originaStdoutlWrite = process.stdout.write
|
|
const originaStderrlWrite = process.stderr.write
|
|
|
|
// Overwrite process.stdout.write
|
|
process.stdout.write = (chunk, encoding, callback) => {
|
|
try {
|
|
// Attempt to parse chunk as JSON
|
|
const obj = JSON.parse(chunk.trim())
|
|
// Utilize util.inspect to improve the readability of the object
|
|
const formatted = inspect(obj, { colors: true, depth: null })
|
|
|
|
// Call the original stdout.write with the formatted string
|
|
originaStdoutlWrite.call(
|
|
process.stdout,
|
|
formatted + '\n',
|
|
encoding,
|
|
callback,
|
|
)
|
|
} catch (error) {
|
|
// If it's not JSON, just output the original message
|
|
originaStdoutlWrite.call(process.stdout, chunk, encoding, callback)
|
|
}
|
|
}
|
|
|
|
// Overwrite process.stdout.write
|
|
process.stderr.write = (chunk, encoding, callback) => {
|
|
try {
|
|
// Attempt to parse chunk as JSON
|
|
const obj = JSON.parse(chunk.trim())
|
|
// inspect without color because stderr is red
|
|
const formatted = inspect(obj, { colors: false, depth: null })
|
|
|
|
// Call the original stdout.write with the formatted string
|
|
originaStderrlWrite.call(
|
|
process.stderr,
|
|
formatted + '\n',
|
|
encoding,
|
|
callback,
|
|
)
|
|
} catch (error) {
|
|
// If it's not JSON, just output the original message
|
|
originaStderrlWrite.call(process.stderr, chunk, encoding, callback)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Custom log function that formats objects with colors regardless of their depth.
|
|
*
|
|
* @param {*} originalFunction Original console function to be modified
|
|
* @returns
|
|
*/
|
|
function customLog(originalFunction) {
|
|
return function (...args) {
|
|
args = args.map((arg) => {
|
|
if (typeof arg === 'object' && arg !== null) {
|
|
// Format the object with colors and unlimited depth
|
|
return inspect(arg, { colors: true, depth: null })
|
|
}
|
|
return arg
|
|
})
|
|
originalFunction.apply(console, args)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* We apply the custom log function only to the console.log and console.info functions.
|
|
* console.error and console.warn are printed in red and handled by the parent process.
|
|
* We don't need to handle process.stdout.write and process.stderr.write
|
|
* Because they can't don't accept objects anyway.
|
|
*/
|
|
console.log = customLog(console.log)
|
|
console.info = customLog(console.info)
|
|
|
|
/**
|
|
* Saves the invocation result to a temporary file in JSON format.
|
|
* The file is named with the process ID to avoid conflicts between concurrent invocations.
|
|
* The result includes both the response and the error if any.
|
|
*
|
|
* @param {*} result
|
|
* @returns {void}
|
|
*/
|
|
const saveInvocationResult = (result) => {
|
|
const filePath = path.join(os.tmpdir(), `sls_${process.pid}.json`)
|
|
|
|
const stringifiedResult = JSON.stringify(result, null, 2)
|
|
|
|
fs.writeFileSync(filePath, stringifiedResult)
|
|
}
|
|
|
|
/**
|
|
* Imports the user handler function from the specified file path.
|
|
* This supports both CommonJS and ESM handler files.
|
|
*
|
|
* @param {string} handlerFileAbsolutePath - Path to handler file
|
|
* @param {string} handlerName - The Handler Name defined in the config file
|
|
* @returns {Promise<Function>}
|
|
*/
|
|
const importFunction = async (handlerFileAbsolutePath, handlerName) => {
|
|
// Check if the file exists
|
|
if (!fs.existsSync(handlerFileAbsolutePath)) {
|
|
throw new Error(`Handler file was not found: ${handlerFileAbsolutePath}`)
|
|
}
|
|
|
|
// Convert the file path to a file URL to ensure
|
|
// it works across different operating systems
|
|
// and for both CommonJS and ESM handler files
|
|
const fileUrl = pathToFileURL(handlerFileAbsolutePath).href
|
|
|
|
// Import the handler file. This works for both CommonJS and ESM handler files
|
|
const handlerFunction = (await import(fileUrl))[handlerName]
|
|
|
|
// Make sure the handler is a function
|
|
if (typeof handlerFunction !== 'function') {
|
|
throw new Error(`Handler is not a function`)
|
|
}
|
|
|
|
// return the function to be invoked
|
|
return handlerFunction
|
|
}
|
|
/**
|
|
* Invoke the handler function with the event, context and callback.
|
|
* We add context functions to the context object we received via WebSockets.
|
|
*
|
|
* @param {*} handlerFunction The handler function to invoke
|
|
* @param {{ event: any, partialContext: any }} options
|
|
* @returns <Promise<any>} a promise that resolves with the response from the handler function
|
|
* @throws {Error} if the handler function is not a function, or if the handler function throws an error
|
|
*/
|
|
const invokeFunction = async (handlerFunction, { event, partialContext }) => {
|
|
return new Promise(async (resolve, reject) => {
|
|
const callback = (error, response) => {
|
|
if (error) {
|
|
reject(error)
|
|
} else {
|
|
resolve(response)
|
|
}
|
|
}
|
|
|
|
const startTime = new Date()
|
|
|
|
const context = {
|
|
...partialContext,
|
|
succeed(result) {
|
|
callback(null, result)
|
|
},
|
|
fail(error) {
|
|
callback(error, null)
|
|
},
|
|
done(error, result) {
|
|
callback(error, result)
|
|
},
|
|
getRemainingTimeInMillis() {
|
|
return Math.max(
|
|
context.timeout * 1000 - (new Date().valueOf() - startTime.valueOf()),
|
|
0,
|
|
)
|
|
},
|
|
}
|
|
|
|
try {
|
|
const response = await handlerFunction(event, context, callback)
|
|
resolve(response)
|
|
} catch (error) {
|
|
reject(error)
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* We parse all the data passed in by the parent process
|
|
*/
|
|
const inputArgs = JSON.parse(process.argv[2])
|
|
const { handlerFileAbsolutePath, handlerName, event, partialContext } =
|
|
inputArgs
|
|
|
|
;(async () => {
|
|
try {
|
|
// Import and invoke the handler function
|
|
const handler = await importFunction(handlerFileAbsolutePath, handlerName)
|
|
|
|
const response = await invokeFunction(handler, { event, partialContext })
|
|
|
|
// Save the response for the parent process to fetch and return to Lambda
|
|
saveInvocationResult({ response, error: null })
|
|
} catch (error) {
|
|
// Save the error for the parent process to fetch and return to Lambda
|
|
saveInvocationResult({
|
|
response: null,
|
|
error: { name: error.name, message: error.message, stack: error.stack },
|
|
})
|
|
|
|
// Exit the child process in case of errors
|
|
console.error(error)
|
|
process.exit(1)
|
|
}
|
|
})()
|