diff --git a/lib/classes/Service.js b/lib/classes/Service.js index 1a74555308de..7e8a4c859df0 100644 --- a/lib/classes/Service.js +++ b/lib/classes/Service.js @@ -20,7 +20,14 @@ class Service { this.serviceObject = null; this.provider = { stage: 'dev', - variableSyntax: '\\${([^{}]+?)}', + variableSyntax: + '\\${(' + + // either 'foo:bar' + '[^{}:]+?:[^:{}][^{}]*?' + + '|' + + // or 'file(...)' + 'file\\([^\\)]*\\)' + + ')}', }; this.custom = {}; this.plugins = []; diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index c2a380c776b9..eb30294e6133 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -1359,6 +1359,49 @@ module.exports = { .populateProperty(property) .should.eventually.eql('my stage is dev'); }); + + it('should support ${file(...)} syntax', () => { + // eslint-disable-next-line no-template-curly-in-string + const property = '${file(~/somedir/../config.yml)}'; + + const expectedFileName = `${os.homedir()}/somedir/../config.yml`; + const configYml = { + foo: 'bar', + }; + const fileExistsStub = sinon.stub(serverless.utils, 'fileExistsSync').returns(true); + const realpathSync = sinon.stub(fse, 'realpathSync').returns(expectedFileName); + const readFileSyncStub = sinon.stub(serverless.utils, 'readFileSync').returns(configYml); + + return serverless.variables + .populateProperty(property) + .then(valueToPopulate => { + expect(realpathSync).to.not.have.been.called; + expect(fileExistsStub).to.have.been.calledWithMatch(expectedFileName); + expect(readFileSyncStub).to.have.been.calledWithMatch(expectedFileName); + expect(valueToPopulate).to.deep.equal(configYml); + }) + .finally(() => { + realpathSync.restore(); + readFileSyncStub.restore(); + fileExistsStub.restore(); + }); + }); + + it('should ignore native CloudFormation variables', () => { + // eslint-disable-next-line no-template-curly-in-string + const property = 'the region is ${AWS::Region}'; + return serverless.variables + .populateProperty(property) + .should.eventually.eql('the region is ${AWS::Region}'); + }); + + it('should ignore CloudFormation references', () => { + // eslint-disable-next-line no-template-curly-in-string + const property = 'the region is ${AnotherResource}'; + return serverless.variables + .populateProperty(property) + .should.eventually.eql('the region is ${AnotherResource}'); + }); }); describe('#populateVariable()', () => { diff --git a/lib/plugins/print/print.test.js b/lib/plugins/print/print.test.js index 917760b6ae01..9819ba371fb3 100644 --- a/lib/plugins/print/print.test.js +++ b/lib/plugins/print/print.test.js @@ -326,38 +326,6 @@ describe('Print', () => { }); }); - it('should resolve self references', () => { - const conf = { - custom: { - me: '${self:}', - }, - provider: {}, - }; - getServerlessConfigFileStub.resolves(conf); - - serverless.processedInput = { - commands: ['print'], - options: {}, - }; - - const expected = { - custom: { - me: { - $ref: '$', - }, - }, - provider: {}, - }; - - return print.print().then(() => { - const message = print.serverless.cli.consoleLog.args.join(); - - expect(getServerlessConfigFileStub.calledOnce).to.equal(true); - expect(print.serverless.cli.consoleLog.called).to.be.equal(true); - expect(YAML.load(message)).to.eql(expected); - }); - }); - describe('should resolve fallback', () => { [ { value: 'hello_123@~:/+', description: 'ascii chars' },