-
Notifications
You must be signed in to change notification settings - Fork 5.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add serverless.ts support #7755
Changes from 1 commit
6f03b31
8d797e5
fd7ec3b
a6c1845
6268023
7831aee
59e26cd
a81a340
1c9628c
9dc8101
6ab43c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,12 +18,14 @@ const getConfigFilePath = (servicePath, options = {}) => { | |
const ymlPath = path.join(servicePath, 'serverless.yml'); | ||
const yamlPath = path.join(servicePath, 'serverless.yaml'); | ||
const jsPath = path.join(servicePath, 'serverless.js'); | ||
const tsPath = path.join(servicePath, 'serverless.ts'); | ||
|
||
return BbPromise.props({ | ||
json: fileExists(jsonPath), | ||
yml: fileExists(ymlPath), | ||
yaml: fileExists(yamlPath), | ||
js: fileExists(jsPath), | ||
ts: fileExists(tsPath), | ||
}).then(exists => { | ||
if (exists.yaml) { | ||
return yamlPath; | ||
|
@@ -33,6 +35,8 @@ const getConfigFilePath = (servicePath, options = {}) => { | |
return jsonPath; | ||
} else if (exists.js) { | ||
return jsPath; | ||
} else if (exists.ts) { | ||
return tsPath; | ||
} | ||
|
||
return null; | ||
|
@@ -46,28 +50,34 @@ const getServerlessConfigFilePath = serverless => { | |
); | ||
}; | ||
|
||
const handleJsConfigFile = jsConfigFile => | ||
const handleJsOrTsConfigFile = (configFile, isTs) => | ||
BbPromise.try(() => { | ||
// use require to load serverless.js | ||
if (isTs) { | ||
// eslint-disable-next-line global-require | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove this comment (we do not have this rule turned on) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removed |
||
require('ts-node').register(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer not add it as dependency. I think TS users should just ensure that both Also we need to ensure it'll work with standalone build, which makes it a bit more tricky (as it doesn't have natural access to service or global npm dependencies) I imagine reliable logic of acquisition of const resolveModulePath = require('ncjsm/resolve');
const spawn = require('child-process-ext/spawn')
...
const tsNode = () => {
// 1. Resolve as serverless peer dependency
return resolveModulePath(__dirname, "ts-node")
.catch((error) => {
if (error.code !== "MODULE_NOT_FOUND") throw error;
// 2. Resolve as service dependency
return resolveModulePath(path.dirname(configFile), "ts-node");
})
.catch((error) => {
if (error.code !== "MODULE_NOT_FOUND") throw error;
// 2. Resolve as global installation
return spawn("npm", ["root", "-g"]).then(
({ stdoutBuffer }) => {
return resolveModulePath("/", `${String(stdoutBuffer).trim()}/ts-node`).catch(
(error) => {
if (error.code !== "MODULE_NOT_FOUND") throw error;
return null;
}
);
},
(error) => null
);
})
.then((tsNodePath) => {
if (!tsNodePath) {
throw new ServerlessError(
"Ensure 'ts-node' dependency for working with TypeScript configuration files"
);
}
return require(tsNodePath);
});
}; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I updated this to remove ts-node and resolve it here - some weird stuff I had to do to get it to resolve symlinks (because it returns an object instead of filepath), but works for all 3 cases. Let me know if you like this approach, or if you want to include ts-node as a dep. |
||
} | ||
// use require to load serverless config file | ||
// eslint-disable-next-line global-require | ||
const configExport = require(jsConfigFile); | ||
const configExport = require(configFile); | ||
// In case of a promise result, first resolve it. | ||
return configExport; | ||
}).then(config => { | ||
if (_.isPlainObject(config)) { | ||
return config; | ||
} | ||
throw new Error('serverless.js must export plain object'); | ||
throw new Error(`serverless.${isTs ? 'ts' : 'js'} must export plain object`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's resolve config filename via There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updated |
||
}); | ||
|
||
const getServerlessConfigFile = _.memoize( | ||
serverless => | ||
getServerlessConfigFilePath(serverless).then(configFilePath => { | ||
if (configFilePath !== null) { | ||
const isJSConfigFile = _.last(_.split(configFilePath, '.')) === 'js'; | ||
const fileExtension = _.last(_.split(configFilePath, '.')); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know it's in old code like that, but maybe refactor it to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. modified to use |
||
const isJSConfigFile = fileExtension === 'js'; | ||
const isTSConfigFile = fileExtension === 'ts'; | ||
|
||
if (isJSConfigFile) { | ||
return handleJsConfigFile(configFilePath); | ||
if (isJSConfigFile || isTSConfigFile) { | ||
return handleJsOrTsConfigFile(configFilePath, isTSConfigFile); | ||
} | ||
|
||
return readFile(configFilePath).then(result => result || {}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -146,6 +146,49 @@ describe('#getServerlessConfigFile()', () => { | |
).to.be.rejectedWith('serverless.js must export plain object'); | ||
}); | ||
|
||
it('should return the file content if a serverless.ts file found', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For tests we should use It's the only reliable way to confirm that it works. We may prepare some basic fixture that involves There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a fixture and added a test case. Let me know if you think there's more that I should be testing |
||
const serverlessFilePath = path.join(tmpDirPath, 'serverless.ts'); | ||
writeFileSync(serverlessFilePath, 'module.exports = {"service": "my-ts-service"};'); | ||
|
||
return expect( | ||
getServerlessConfigFile({ | ||
processedInput: { options: {} }, | ||
config: { servicePath: tmpDirPath }, | ||
}) | ||
).to.be.fulfilled.then(result => { | ||
expect(result).to.deep.equal({ service: 'my-ts-service' }); | ||
}); | ||
}); | ||
|
||
it('should return the resolved value if a promise-using serverless.ts file found', () => { | ||
const serverlessFilePath = path.join(tmpDirPath, 'serverless.ts'); | ||
writeFileSync( | ||
serverlessFilePath, | ||
'module.exports = new Promise(resolve => { resolve({"service": "my-ts-service"}); });' | ||
); | ||
|
||
return expect( | ||
getServerlessConfigFile({ | ||
processedInput: { options: {} }, | ||
config: { servicePath: tmpDirPath }, | ||
}) | ||
).to.be.fulfilled.then(result => { | ||
expect(result).to.deep.equal({ service: 'my-ts-service' }); | ||
}); | ||
}); | ||
|
||
it('should throw an error, if serverless.ts export not a plain object', () => { | ||
const serverlessFilePath = path.join(tmpDirPath, 'serverless.ts'); | ||
writeFileSync(serverlessFilePath, 'module.exports = function config() {};'); | ||
|
||
return expect( | ||
getServerlessConfigFile({ | ||
processedInput: { options: {} }, | ||
config: { servicePath: tmpDirPath }, | ||
}) | ||
).to.be.rejectedWith('serverless.ts must export plain object'); | ||
}); | ||
|
||
it('should look in the current working directory if servicePath is undefined', () => { | ||
const serverlessFilePath = path.join(tmpDirPath, 'serverless.yml'); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not introduce
isTs
argument, but simply detect that byconfigFile.endsWith('.ts')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated