mirror of
https://github.com/serverless/serverless.git
synced 2026-01-25 15:07:39 +00:00
208 lines
6.3 KiB
JavaScript
208 lines
6.3 KiB
JavaScript
import isPlainObject from 'type/plain-object/is.js'
|
|
import path from 'path'
|
|
import { promises as fsp } from 'fs'
|
|
import yaml from 'js-yaml'
|
|
import { fileURLToPath } from 'url'
|
|
import getRequire from '../utils/get-require.js'
|
|
import spawn from 'child-process-ext/spawn.js'
|
|
import cloudformationSchema from '@serverless/utils/cloudformation-schema.js'
|
|
import ServerlessError from '../serverless-error.js'
|
|
|
|
const resolveTsNode = async (serviceDir) => {
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
|
|
// 1. If installed aside of a Framework, use it
|
|
try {
|
|
return getRequire(__dirname).resolve('ts-node')
|
|
} catch (slsDepError) {
|
|
if (slsDepError.code !== 'MODULE_NOT_FOUND') {
|
|
throw new ServerlessError(
|
|
`Cannot resolve "ts-node" due to: ${slsDepError.message}`,
|
|
'TS_NODE_RESOLUTION_ERROR',
|
|
)
|
|
}
|
|
|
|
// 2. If installed in a service, use it
|
|
try {
|
|
return getRequire(serviceDir).resolve('ts-node')
|
|
} catch (serviceDepError) {
|
|
if (serviceDepError.code !== 'MODULE_NOT_FOUND') {
|
|
throw new ServerlessError(
|
|
`Cannot resolve "ts-node" due to: ${serviceDepError.message}`,
|
|
'TS_NODE_IN_SERVICE_RESOLUTION_ERROR',
|
|
)
|
|
}
|
|
|
|
// 3. If installed globally, use it
|
|
const { stdoutBuffer } = await (async () => {
|
|
try {
|
|
return await spawn('npm', ['root', '-g'])
|
|
} catch (error) {
|
|
if (error.code !== 'ENOENT') {
|
|
throw new ServerlessError(
|
|
`Cannot resolve "ts-node" due to unexpected "npm" error: ${error.message}`,
|
|
'TS_NODE_NPM_RESOLUTION_ERROR',
|
|
)
|
|
}
|
|
throw new ServerlessError('"ts-node" not found', 'TS_NODE_NOT_FOUND')
|
|
}
|
|
})()
|
|
try {
|
|
const tsNodePath = `${String(stdoutBuffer).trim()}/ts-node`
|
|
return import(tsNodePath)
|
|
.then(() => tsNodePath)
|
|
.catch((err) => {
|
|
if (err.code !== 'MODULE_NOT_FOUND') {
|
|
throw new ServerlessError(
|
|
`Cannot resolve "ts-node" due to: ${err.message}`,
|
|
'TS_NODE_NPM_GLOBAL_RESOLUTION_ERROR',
|
|
)
|
|
}
|
|
throw new ServerlessError(
|
|
'"ts-node" not found',
|
|
'TS_NODE_NOT_FOUND',
|
|
)
|
|
})
|
|
} catch (error) {
|
|
// Catch any synchronous errors (unlikely in this context)
|
|
console.error('An unexpected error occurred:', error)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const readConfigurationFile = async (configurationPath) => {
|
|
try {
|
|
return await fsp.readFile(configurationPath, 'utf8')
|
|
} catch (error) {
|
|
if (error.code === 'ENOENT') {
|
|
throw new ServerlessError(
|
|
`Cannot parse "${path.basename(configurationPath)}": File not found`,
|
|
'CONFIGURATION_NOT_FOUND',
|
|
)
|
|
}
|
|
throw new ServerlessError(
|
|
`Cannot parse "${path.basename(configurationPath)}": ${error.message}`,
|
|
'CONFIGURATION_NOT_ACCESSIBLE',
|
|
)
|
|
}
|
|
}
|
|
|
|
const parseConfigurationFile = async (configurationPath) => {
|
|
switch (path.extname(configurationPath)) {
|
|
case '.yml':
|
|
case '.yaml': {
|
|
const content = await readConfigurationFile(configurationPath)
|
|
try {
|
|
return yaml.load(content, {
|
|
filename: configurationPath,
|
|
schema: cloudformationSchema,
|
|
})
|
|
} catch (error) {
|
|
throw new ServerlessError(
|
|
`Cannot parse "${path.basename(configurationPath)}": ${
|
|
error.message
|
|
}`,
|
|
'CONFIGURATION_PARSE_ERROR',
|
|
)
|
|
}
|
|
}
|
|
case '.json': {
|
|
const content = await readConfigurationFile(configurationPath)
|
|
try {
|
|
return JSON.parse(content)
|
|
} catch (error) {
|
|
throw new ServerlessError(
|
|
`Cannot parse "${path.basename(
|
|
configurationPath,
|
|
)}": JSON parse error: ${error.message}`,
|
|
'CONFIGURATION_PARSE_ERROR',
|
|
)
|
|
}
|
|
}
|
|
case '.ts': {
|
|
if (!process[Symbol.for('ts-node.register.instance')]) {
|
|
const tsNodePath = await (async () => {
|
|
try {
|
|
return await resolveTsNode(path.dirname(configurationPath))
|
|
} catch (error) {
|
|
throw new ServerlessError(
|
|
`Cannot parse "${path.basename(
|
|
configurationPath,
|
|
)}": Resolution of "ts-node" failed with: ${error.message}`,
|
|
'CONFIGURATION_RESOLUTION_ERROR',
|
|
)
|
|
}
|
|
})()
|
|
try {
|
|
const tsNode = await import(tsNodePath)
|
|
tsNode.register()
|
|
} catch (error) {
|
|
throw new ServerlessError(
|
|
`Cannot parse "${path.basename(
|
|
configurationPath,
|
|
)}": Register of "ts-node" failed with: ${error.message}`,
|
|
'CONFIGURATION_RESOLUTION_ERROR',
|
|
)
|
|
}
|
|
}
|
|
}
|
|
// fallthrough
|
|
case '.cjs':
|
|
case '.mjs':
|
|
case '.js': {
|
|
try {
|
|
const content = await import(configurationPath)
|
|
// Support ES default export
|
|
return content.default || content
|
|
} catch (error) {
|
|
throw new ServerlessError(
|
|
`Cannot load "${path.basename(
|
|
configurationPath,
|
|
)}": Initialization error: ${
|
|
error && error.stack ? error.stack : error
|
|
}`,
|
|
'CONFIGURATION_INITIALIZATION_ERROR',
|
|
)
|
|
}
|
|
}
|
|
default:
|
|
throw new ServerlessError(
|
|
`Cannot parse "${path.basename(
|
|
configurationPath,
|
|
)}": Unsupported file extension`,
|
|
'UNSUPPORTED_CONFIGURATION_TYPE',
|
|
)
|
|
}
|
|
}
|
|
|
|
export default async (configurationPath) => {
|
|
configurationPath = path.resolve(configurationPath)
|
|
|
|
let configuration = await parseConfigurationFile(configurationPath)
|
|
|
|
if (!isPlainObject(configuration)) {
|
|
throw new ServerlessError(
|
|
`Invalid configuration at "${path.basename(
|
|
configurationPath,
|
|
)}": Plain object expected`,
|
|
'INVALID_CONFIGURATION_EXPORT',
|
|
)
|
|
}
|
|
|
|
// Ensure no internal complex objects and no circural references
|
|
try {
|
|
configuration = JSON.parse(JSON.stringify(configuration))
|
|
} catch (error) {
|
|
throw new ServerlessError(
|
|
`Invalid configuration at "${path.basename(
|
|
configurationPath,
|
|
)}": Plain JSON structure expected, when parsing observed error: ${
|
|
error.message
|
|
}`,
|
|
'INVALID_CONFIGURATION_STRUCTURE',
|
|
)
|
|
}
|
|
return configuration
|
|
}
|