diff --git a/README.md b/README.md index 89513353..6379c330 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ jobs: build: runs-on: windows-latest steps: - - uses: microsoft/variable-substitution@v1 + - uses: microsoft/variable-substitution@latest with: files: 'Application/*.json, Application/*.yaml, ./Application/SampleWebApplication/We*.config' env: @@ -31,6 +31,27 @@ jobs: Var2.key1: "value2" SECRET: ${{ secrets.SOME_SECRET }} + ``` +Define `splitChar` to change the character used to split the environment variable names for variable identification and substitution + ```yaml +# .github/workflows/var-substitution.yml +on: [push] +name: variable substitution in json, xml, and yml files + +jobs: + build: + runs-on: windows-latest + steps: + - uses: microsoft/variable-substitution@latest + with: + files: 'Application/*.json, Application/*.yaml, ./Application/SampleWebApplication/We*.config' + splitChar: '__' + env: + Var1: "value1" + Var2__key1: "value2" + Var3__key1__key2: "value3" + SECRET: ${{ secrets.SOME_SECRET }} + ``` # Contributing diff --git a/action.yml b/action.yml index ce3923fd..5748fbc2 100644 --- a/action.yml +++ b/action.yml @@ -5,6 +5,10 @@ inputs: files: description: 'comma separated list of XML/JSON/YAML files in which tokens are to be substituted. Files names must be specified relative to the folder-path.' required: true + splitChar: + description: 'The character within the environment variables used to split the name for variable location. This defaults to `.`' + require: false + default: '.' runs: using: 'node12' main: 'lib/variableSubstitution.js' diff --git a/lib/Tests/jsonVariableSubstitution.test.js b/lib/Tests/jsonVariableSubstitution.test.js index efedc329..05ca6cc5 100644 --- a/lib/Tests/jsonVariableSubstitution.test.js +++ b/lib/Tests/jsonVariableSubstitution.test.js @@ -1,134 +1,263 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const sinon = require("sinon"); -const chai = require("chai"); -const envVariableUtility_1 = require("../operations/envVariableUtility"); -const jsonVariableSubstitutionUtility_1 = require("../operations/jsonVariableSubstitutionUtility"); -var expect = chai.expect; -describe('Test JSON Variable Substitution', () => { - var jsonObject, isApplied; - before(() => { - let stub = sinon.stub(envVariableUtility_1.EnvTreeUtility, "getEnvVarTree").callsFake(() => { - let envVariables = new Map([ - ['system.debug', 'true'], - ['data.ConnectionString', 'database_connection'], - ['data.userName', 'db_admin'], - ['data.password', 'db_pass'], - ['&pl.ch@r@cter.k^y', '*.config'], - ['build.sourceDirectory', 'DefaultWorkingDirectory'], - ['user.profile.name.first', 'firstName'], - ['user.profile', 'replace_all'], - ['constructor.name', 'newConstructorName'], - ['constructor.valueOf', 'constructorNewValue'], - ['profile.users', '["suaggar","rok","asranja", "chaitanya"]'], - ['profile.enabled', 'false'], - ['profile.version', '1173'], - ['profile.somefloat', '97.75'], - ['profile.preimum_level', '{"suaggar": "V4", "rok": "V5", "asranja": { "type" : "V6"}}'] - ]); - let envVarTree = { - value: null, - isEnd: false, - child: { - '__proto__': null - } - }; - for (let [key, value] of envVariables.entries()) { - if (!envVariableUtility_1.isPredefinedVariable(key)) { - let envVarTreeIterator = envVarTree; - let envVariableNameArray = key.split('.'); - for (let variableName of envVariableNameArray) { - if (envVarTreeIterator.child[variableName] === undefined || typeof envVarTreeIterator.child[variableName] === 'function') { - envVarTreeIterator.child[variableName] = { - value: null, - isEnd: false, - child: {} - }; - } - envVarTreeIterator = envVarTreeIterator.child[variableName]; - } - envVarTreeIterator.isEnd = true; - envVarTreeIterator.value = value; - } - } - return envVarTree; - }); - jsonObject = { - 'User.Profile': 'do_not_replace', - 'data': { - 'ConnectionString': 'connect_string', - 'userName': 'name', - 'password': 'pass' - }, - '&pl': { - 'ch@r@cter.k^y': 'v@lue' - }, - 'system': { - 'debug': 'no_change' - }, - 'user.profile': { - 'name.first': 'fname' - }, - 'constructor.name': 'myconstructorname', - 'constructor': { - 'name': 'myconstructorname', - 'valueOf': 'myconstructorvalue' - }, - 'profile': { - 'users': ['arjgupta', 'raagra', 'muthuk'], - 'preimum_level': { - 'arjgupta': 'V1', - 'raagra': 'V2', - 'muthuk': { - 'type': 'V3' - } - }, - "enabled": true, - "version": 2, - "somefloat": 2.3456 - } - }; - let jsonSubsitution = new jsonVariableSubstitutionUtility_1.JsonSubstitution(); - isApplied = jsonSubsitution.substituteJsonVariable(jsonObject, envVariableUtility_1.EnvTreeUtility.getEnvVarTree()); - stub.restore(); - }); - it("Should substitute", () => { - console.log(JSON.stringify(jsonObject)); - expect(isApplied).to.equal(true); - }); - it("Validate simple string change", () => { - expect(jsonObject['data']['ConnectionString']).to.equal('database_connection'); - expect(jsonObject['data']['userName']).to.equal('db_admin'); - }); - it("Validate system variable elimination", () => { - expect(jsonObject['system']['debug']).to.equal('no_change'); - }); - it("Validate special variables", () => { - expect(jsonObject['&pl']['ch@r@cter.k^y']).to.equal('*.config'); - }); - it("Validate case sensitive variables", () => { - expect(jsonObject['User.Profile']).to.equal('do_not_replace'); - }); - it("Validate inbuilt JSON attributes substitution", () => { - expect(jsonObject['constructor.name']).to.equal('newConstructorName'); - expect(jsonObject['constructor']['name']).to.equal('newConstructorName'); - expect(jsonObject['constructor']['valueOf']).to.equal('constructorNewValue'); - }); - it("Validate Array Object", () => { - expect(jsonObject['profile']['users'].length).to.equal(4); - let newArray = ["suaggar", "rok", "asranja", "chaitanya"]; - expect(jsonObject['profile']['users']).to.deep.equal(newArray); - }); - it("Validate Boolean", () => { - expect(jsonObject['profile']['enabled']).to.equal(false); - }); - it("Validate Number(float)", () => { - expect(jsonObject['profile']['somefloat']).to.equal(97.75); - }); - it("Validate Number(int)", () => { - expect(jsonObject['profile']['version']).to.equal(1173); - }); - it("Validate Object", () => { - expect(jsonObject['profile']['preimum_level']).to.deep.equal({ "suaggar": "V4", "rok": "V5", "asranja": { "type": "V6" } }); - }); -}); +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const sinon = require("sinon"); +const chai = require("chai"); +const envVariableUtility_1 = require("../operations/envVariableUtility"); +const jsonVariableSubstitutionUtility_1 = require("../operations/jsonVariableSubstitutionUtility"); +var expect = chai.expect; +describe('Test JSON Variable Substitution with period', () => { + var jsonObject, isApplied; + var splitChar = '.'; + before(() => { + let stub = sinon.stub(envVariableUtility_1.EnvTreeUtility, "getEnvVarTree").callsFake(() => { + let envVariables = new Map([ + ['system.debug', 'true'], + ['data.ConnectionString', 'database_connection'], + ['data.userName', 'db_admin'], + ['data.password', 'db_pass'], + ['&pl.ch@r@cter.k^y', '*.config'], + ['build.sourceDirectory', 'DefaultWorkingDirectory'], + ['user.profile.name.first', 'firstName'], + ['user.profile', 'replace_all'], + ['constructor.name', 'newConstructorName'], + ['constructor.valueOf', 'constructorNewValue'], + ['profile.users', '["suaggar","rok","asranja", "chaitanya"]'], + ['profile.enabled', 'false'], + ['profile.version', '1173'], + ['profile.somefloat', '97.75'], + ['profile.premium_level', '{"suaggar": "V4", "rok": "V5", "asranja": { "type" : "V6"}}'] + ]); + let envVarTree = { + value: null, + isEnd: false, + child: { + '__proto__': null + } + }; + for (let [key, value] of envVariables.entries()) { + if (!envVariableUtility_1.isPredefinedVariable(key)) { + let envVarTreeIterator = envVarTree; + let envVariableNameArray = key.split(splitChar); + for (let variableName of envVariableNameArray) { + if (envVarTreeIterator.child[variableName] === undefined || typeof envVarTreeIterator.child[variableName] === 'function') { + envVarTreeIterator.child[variableName] = { + value: null, + isEnd: false, + child: {} + }; + } + envVarTreeIterator = envVarTreeIterator.child[variableName]; + } + envVarTreeIterator.isEnd = true; + envVarTreeIterator.value = value; + } + } + return envVarTree; + }); + jsonObject = { + 'User.Profile': 'do_not_replace', + 'data': { + 'ConnectionString': 'connect_string', + 'userName': 'name', + 'password': 'pass' + }, + '&pl': { + 'ch@r@cter.k^y': 'v@lue' + }, + 'system': { + 'debug': 'no_change' + }, + 'user.profile': { + 'name.first': 'fname' + }, + 'constructor.name': 'myconstructorname', + 'constructor': { + 'name': 'myconstructorname', + 'valueOf': 'myconstructorvalue' + }, + 'profile': { + 'users': ['arjgupta', 'raagra', 'muthuk'], + 'premium_level': { + 'arjgupta': 'V1', + 'raagra': 'V2', + 'muthuk': { + 'type': 'V3' + } + }, + "enabled": true, + "version": 2, + "somefloat": 2.3456 + } + }; + let jsonSubsitution = new jsonVariableSubstitutionUtility_1.JsonSubstitution(); + isApplied = jsonSubsitution.substituteJsonVariable(jsonObject, envVariableUtility_1.EnvTreeUtility.getEnvVarTree(splitChar)); + stub.restore(); + }); + it("Should substitute", () => { + console.log(JSON.stringify(jsonObject)); + expect(isApplied).to.equal(true); + }); + it("Validate simple string change", () => { + expect(jsonObject['data']['ConnectionString']).to.equal('database_connection'); + expect(jsonObject['data']['userName']).to.equal('db_admin'); + }); + it("Validate system variable elimination", () => { + expect(jsonObject['system']['debug']).to.equal('no_change'); + }); + it("Validate special variables", () => { + expect(jsonObject['&pl']['ch@r@cter.k^y']).to.equal('*.config'); + }); + it("Validate case sensitive variables", () => { + expect(jsonObject['User.Profile']).to.equal('do_not_replace'); + }); + it("Validate inbuilt JSON attributes substitution", () => { + expect(jsonObject['constructor.name']).to.equal('newConstructorName'); + expect(jsonObject['constructor']['name']).to.equal('newConstructorName'); + expect(jsonObject['constructor']['valueOf']).to.equal('constructorNewValue'); + }); + it("Validate Array Object", () => { + expect(jsonObject['profile']['users'].length).to.equal(4); + let newArray = ["suaggar", "rok", "asranja", "chaitanya"]; + expect(jsonObject['profile']['users']).to.deep.equal(newArray); + }); + it("Validate Boolean", () => { + expect(jsonObject['profile']['enabled']).to.equal(false); + }); + it("Validate Number(float)", () => { + expect(jsonObject['profile']['somefloat']).to.equal(97.75); + }); + it("Validate Number(int)", () => { + expect(jsonObject['profile']['version']).to.equal(1173); + }); + it("Validate Object", () => { + expect(jsonObject['profile']['premium_level']).to.deep.equal({ "suaggar": "V4", "rok": "V5", "asranja": { "type": "V6" } }); + }); +}); +describe('Test JSON Variable Substition with underscores', () => { + var jsonObject, isApplied; + var splitChar = '__'; + before(() => { + let stub = sinon.stub(envVariableUtility_1.EnvTreeUtility, "getEnvVarTree").callsFake(() => { + let envVariables = new Map([ + ['system.debug', 'true'], + ['data__ConnectionString', 'database_connection'], + ['data__userName', 'db_admin'], + ['data__password', 'db_pass'], + ['&pl__ch@r@cter__k^y', '*.config'], + ['build__sourceDirectory', 'DefaultWorkingDirectory'], + ['user__profile__name__first', 'firstName'], + ['user__profile', 'replace_all'], + ['constructor__name', 'newConstructorName'], + ['constructor__valueOf', 'constructorNewValue'], + ['profile__users', '["suaggar","rok","asranja", "chaitanya"]'], + ['profile__enabled', 'false'], + ['profile__version', '1173'], + ['profile__somefloat', '97.75'], + ['profile__premium_level', '{"suaggar": "V4", "rok": "V5", "asranja": { "type" : "V6"}}'] + ]); + let envVarTree = { + value: null, + isEnd: false, + child: { + '__proto__': null + } + }; + for (let [key, value] of envVariables.entries()) { + if (!envVariableUtility_1.isPredefinedVariable(key)) { + let envVarTreeIterator = envVarTree; + let envVariableNameArray = key.split(splitChar); + for (let variableName of envVariableNameArray) { + if (envVarTreeIterator.child[variableName] === undefined || typeof envVarTreeIterator.child[variableName] === 'function') { + envVarTreeIterator.child[variableName] = { + value: null, + isEnd: false, + child: {} + }; + } + envVarTreeIterator = envVarTreeIterator.child[variableName]; + } + envVarTreeIterator.isEnd = true; + envVarTreeIterator.value = value; + } + } + return envVarTree; + }); + jsonObject = { + 'User.Profile': 'do_not_replace', + 'data': { + 'ConnectionString': 'connect_string', + 'userName': 'name', + 'password': 'pass' + }, + '&pl': { + 'ch@r@cter.k^y': 'v@lue' + }, + 'system': { + 'debug': 'no_change' + }, + 'user.profile': { + 'name.first': 'fname' + }, + 'constructor.name': 'myconstructorname', + 'constructor': { + 'name': 'myconstructorname', + 'valueOf': 'myconstructorvalue' + }, + 'profile': { + 'users': ['arjgupta', 'raagra', 'muthuk'], + 'premium_level': { + 'arjgupta': 'V1', + 'raagra': 'V2', + 'muthuk': { + 'type': 'V3' + } + }, + "enabled": true, + "version": 2, + "somefloat": 2.3456 + } + }; + let jsonSubsitution = new jsonVariableSubstitutionUtility_1.JsonSubstitution(); + isApplied = jsonSubsitution.substituteJsonVariable(jsonObject, envVariableUtility_1.EnvTreeUtility.getEnvVarTree(splitChar)); + stub.restore(); + }); + it("Should substitute", () => { + console.log(JSON.stringify(jsonObject)); + expect(isApplied).to.equal(true); + }); + it("Validate simple string change", () => { + expect(jsonObject['data']['ConnectionString']).to.equal('database_connection'); + expect(jsonObject['data']['userName']).to.equal('db_admin'); + }); + it("Validate system variable elimination", () => { + expect(jsonObject['system']['debug']).to.equal('no_change'); + }); + it("Validate special variables", () => { + expect(jsonObject['&pl']['ch@r@cter.k^y']).to.equal('*.config'); + }); + it("Validate case sensitive variables", () => { + expect(jsonObject['User.Profile']).to.equal('do_not_replace'); + }); + it("Validate inbuilt JSON attributes substitution", () => { + expect(jsonObject['constructor.name']).to.equal('newConstructorName'); + expect(jsonObject['constructor']['name']).to.equal('newConstructorName'); + expect(jsonObject['constructor']['valueOf']).to.equal('constructorNewValue'); + }); + it("Validate Array Object", () => { + expect(jsonObject['profile']['users'].length).to.equal(4); + let newArray = ["suaggar", "rok", "asranja", "chaitanya"]; + expect(jsonObject['profile']['users']).to.deep.equal(newArray); + }); + it("Validate Boolean", () => { + expect(jsonObject['profile']['enabled']).to.equal(false); + }); + it("Validate Number(float)", () => { + expect(jsonObject['profile']['somefloat']).to.equal(97.75); + }); + it("Validate Number(int)", () => { + expect(jsonObject['profile']['version']).to.equal(1173); + }); + it("Validate Object", () => { + expect(jsonObject['profile']['premium_level']).to.deep.equal({ "suaggar": "V4", "rok": "V5", "asranja": { "type": "V6" } }); + }); +}); diff --git a/lib/Tests/variableSubstitution.test.js b/lib/Tests/variableSubstitution.test.js index 972a9284..11304609 100644 --- a/lib/Tests/variableSubstitution.test.js +++ b/lib/Tests/variableSubstitution.test.js @@ -1,76 +1,76 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const jsonVariableSubstitutionUtility_1 = require("../operations/jsonVariableSubstitutionUtility"); -const variableSubstitution_1 = require("../variableSubstitution"); -const xmlVariableSubstitution_1 = require("../operations/xmlVariableSubstitution"); -const chai_1 = require("chai"); -const path = require("path"); -const sinon = require("sinon"); -describe("Test variable substitution main", () => { - var spy, JsonSubstitutionMock, XmlSubstitutionMock; - before(() => { - spy = sinon.spy(console, "log"); - JsonSubstitutionMock = sinon.mock(jsonVariableSubstitutionUtility_1.JsonSubstitution); - XmlSubstitutionMock = sinon.mock(xmlVariableSubstitution_1.XmlSubstitution); - }); - after(() => { - JsonSubstitutionMock.restore(); - XmlSubstitutionMock.restore(); - spy.restore(); - }); - it("Valid XML", () => { - let file = path.join(__dirname, "/Resources/Web.config"); - let filesArr = file.split(","); - let varSub = new variableSubstitution_1.VariableSubstitution(); - try { - varSub.segregateFilesAndSubstitute(filesArr); - } - catch (e) { - } - chai_1.expect(spy.calledWith("Applying variable substitution on XML file: " + file)).to.be.true; - }); - it("Valid JSON", () => { - let file = path.join(__dirname, "/Resources/test.json"); - let filesArr = file.split(","); - let varSub = new variableSubstitution_1.VariableSubstitution(); - try { - varSub.segregateFilesAndSubstitute(filesArr); - } - catch (e) { - } - chai_1.expect(spy.calledWith("Applying variable substitution on JSON file: " + file)).to.be.true; - }); - it("Invalid JSON", () => { - let file = path.join(__dirname, "/Resources/Wrong_test.json"); - let filesArr = file.split(","); - let varSub = new variableSubstitution_1.VariableSubstitution(); - try { - varSub.segregateFilesAndSubstitute(filesArr); - } - catch (e) { - } - chai_1.expect(spy.calledWith("Applying variable substitution on JSON file: " + file)).to.be.false; - }); - it("Valid YAML", () => { - let file = path.join(__dirname, "/Resources/test.yaml"); - let filesArr = file.split(","); - let varSub = new variableSubstitution_1.VariableSubstitution(); - try { - varSub.segregateFilesAndSubstitute(filesArr); - } - catch (e) { - } - chai_1.expect(spy.calledWith("Applying variable substitution on YAML file: " + file)).to.be.true; - }); - it("Invalid YAML", () => { - let file = path.join(__dirname, "/Resources/Wrong_test.yml"); - let filesArr = file.split(","); - let varSub = new variableSubstitution_1.VariableSubstitution(); - try { - varSub.segregateFilesAndSubstitute(filesArr); - } - catch (e) { - } - chai_1.expect(spy.calledWith("Applying variable substitution on YAML file: " + file)).to.be.false; - }); -}); +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const jsonVariableSubstitutionUtility_1 = require("../operations/jsonVariableSubstitutionUtility"); +const variableSubstitution_1 = require("../variableSubstitution"); +const xmlVariableSubstitution_1 = require("../operations/xmlVariableSubstitution"); +const chai_1 = require("chai"); +const path = require("path"); +const sinon = require("sinon"); +describe("Test variable substitution main", () => { + var spy, JsonSubstitutionMock, XmlSubstitutionMock; + before(() => { + spy = sinon.spy(console, "log"); + JsonSubstitutionMock = sinon.mock(jsonVariableSubstitutionUtility_1.JsonSubstitution); + XmlSubstitutionMock = sinon.mock(xmlVariableSubstitution_1.XmlSubstitution); + }); + after(() => { + JsonSubstitutionMock.restore(); + XmlSubstitutionMock.restore(); + spy.restore(); + }); + it("Valid XML", () => { + let file = path.join(__dirname, "/Resources/Web.config"); + let filesArr = file.split(","); + let varSub = new variableSubstitution_1.VariableSubstitution(); + try { + varSub.segregateFilesAndSubstitute(filesArr); + } + catch (e) { + } + chai_1.expect(spy.calledWith("Applying variable substitution on XML file: " + file)).to.be.true; + }); + it("Valid JSON", () => { + let file = path.join(__dirname, "/Resources/test.json"); + let filesArr = file.split(","); + let varSub = new variableSubstitution_1.VariableSubstitution(); + try { + varSub.segregateFilesAndSubstitute(filesArr); + } + catch (e) { + } + chai_1.expect(spy.calledWith("Applying variable substitution on JSON file: " + file)).to.be.true; + }); + it("Invalid JSON", () => { + let file = path.join(__dirname, "/Resources/Wrong_test.json"); + let filesArr = file.split(","); + let varSub = new variableSubstitution_1.VariableSubstitution(); + try { + varSub.segregateFilesAndSubstitute(filesArr); + } + catch (e) { + } + chai_1.expect(spy.calledWith("Applying variable substitution on JSON file: " + file)).to.be.false; + }); + it("Valid YAML", () => { + let file = path.join(__dirname, "/Resources/test.yaml"); + let filesArr = file.split(","); + let varSub = new variableSubstitution_1.VariableSubstitution(); + try { + varSub.segregateFilesAndSubstitute(filesArr); + } + catch (e) { + } + chai_1.expect(spy.calledWith("Applying variable substitution on YAML file: " + file)).to.be.true; + }); + it("Invalid YAML", () => { + let file = path.join(__dirname, "/Resources/Wrong_test.yml"); + let filesArr = file.split(","); + let varSub = new variableSubstitution_1.VariableSubstitution(); + try { + varSub.segregateFilesAndSubstitute(filesArr); + } + catch (e) { + } + chai_1.expect(spy.calledWith("Applying variable substitution on YAML file: " + file)).to.be.false; + }); +}); diff --git a/lib/Tests/xmlVariableSubstituttion.test.js b/lib/Tests/xmlVariableSubstituttion.test.js index 7895acd5..0bf272a3 100644 --- a/lib/Tests/xmlVariableSubstituttion.test.js +++ b/lib/Tests/xmlVariableSubstituttion.test.js @@ -1,70 +1,70 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const envVarUtility = __importStar(require("../operations/envVariableUtility")); -const xmlDomUtility_1 = require("../operations/xmlDomUtility"); -const xmlVariableSubstitution_1 = require("../operations/xmlVariableSubstitution"); -const chai = require("chai"); -const fs = require("fs"); -const path = require("path"); -const sinon = require("sinon"); -var expect = chai.expect; -describe('Test Xml Variable Substitution', () => { - it("Should substitute", () => { - let envVarUtilityMock = sinon.mock(envVarUtility); - envVarUtilityMock.expects('getVariableMap').returns(new Map([ - ['conntype', 'new_connType'], - ['MyDB', 'TestDB'], - ['webpages:Version', '1.1.7.3'], - ['xdt:Transform', 'DelAttributes'], - ['xdt:Locator', 'Match(tag)'], - ['DefaultConnection', "Url=https://primary;Database=db1;ApiKey=11111111-1111-1111-1111-111111111111;Failover = {Url:'https://secondary', ApiKey:'11111111-1111-1111-1111-111111111111'}"], - ['OtherDefaultConnection', 'connectionStringValue2'], - ['ParameterConnection', 'New_Connection_String From xml var subs'], - ['connectionString', 'replaced_value'], - ['invariantName', 'System.Data.SqlServer'], - ['blatvar', 'ApplicationSettingReplacedValue'], - ['log_level', 'error,warning'], - ['Email:ToOverride', ''] - ])); - function replaceEscapeXMLCharacters(xmlDOMNode) { - if (!xmlDOMNode || typeof xmlDOMNode == 'string') { - return; - } - for (var xmlAttribute in xmlDOMNode.attrs) { - xmlDOMNode.attrs[xmlAttribute] = xmlDOMNode.attrs[xmlAttribute].replace(/'/g, "APOS_CHARACTER_TOKEN"); - } - for (var xmlChild of xmlDOMNode.children) { - replaceEscapeXMLCharacters(xmlChild); - } - } - let source = path.join(__dirname, "/Resources/Web.config"); - let fileBuffer = fs.readFileSync(source); - let fileContent = fileBuffer.toString('utf-8'); - let xmlDomUtilityInstance = new xmlDomUtility_1.XmlDomUtility(fileContent); - let xmlSubstitution = new xmlVariableSubstitution_1.XmlSubstitution(xmlDomUtilityInstance); - let isApplied = xmlSubstitution.substituteXmlVariables(); - expect(isApplied).to.equal(true); - let xmlDocument = xmlDomUtilityInstance.getXmlDom(); - replaceEscapeXMLCharacters(xmlDocument); - let domContent = '\uFEFF' + xmlDomUtilityInstance.getContentWithHeader(xmlDocument); - for (let replacableTokenValue in xmlSubstitution.replacableTokenValues) { - domContent = domContent.split(replacableTokenValue).join(xmlSubstitution.replacableTokenValues[replacableTokenValue]); - } - let expectedResult = path.join(__dirname, "/Resources/Web_Expected.config"); - fileBuffer = fs.readFileSync(expectedResult); - let expectedContent = fileBuffer.toString('utf-8'); - let targetXmlDomUtilityInstance = new xmlDomUtility_1.XmlDomUtility(expectedContent); - let expectedXmlDocument = targetXmlDomUtilityInstance.getXmlDom(); - replaceEscapeXMLCharacters(expectedXmlDocument); - let expectedDomContent = '\uFEFF' + xmlDomUtilityInstance.getContentWithHeader(expectedXmlDocument); - expectedDomContent = expectedDomContent.split("APOS_CHARACTER_TOKEN").join("'"); - expect(domContent).to.equal(expectedDomContent); - }); -}); +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const envVarUtility = __importStar(require("../operations/envVariableUtility")); +const xmlDomUtility_1 = require("../operations/xmlDomUtility"); +const xmlVariableSubstitution_1 = require("../operations/xmlVariableSubstitution"); +const chai = require("chai"); +const fs = require("fs"); +const path = require("path"); +const sinon = require("sinon"); +var expect = chai.expect; +describe('Test Xml Variable Substitution', () => { + it("Should substitute", () => { + let envVarUtilityMock = sinon.mock(envVarUtility); + envVarUtilityMock.expects('getVariableMap').returns(new Map([ + ['conntype', 'new_connType'], + ['MyDB', 'TestDB'], + ['webpages:Version', '1.1.7.3'], + ['xdt:Transform', 'DelAttributes'], + ['xdt:Locator', 'Match(tag)'], + ['DefaultConnection', "Url=https://primary;Database=db1;ApiKey=11111111-1111-1111-1111-111111111111;Failover = {Url:'https://secondary', ApiKey:'11111111-1111-1111-1111-111111111111'}"], + ['OtherDefaultConnection', 'connectionStringValue2'], + ['ParameterConnection', 'New_Connection_String From xml var subs'], + ['connectionString', 'replaced_value'], + ['invariantName', 'System.Data.SqlServer'], + ['blatvar', 'ApplicationSettingReplacedValue'], + ['log_level', 'error,warning'], + ['Email:ToOverride', ''] + ])); + function replaceEscapeXMLCharacters(xmlDOMNode) { + if (!xmlDOMNode || typeof xmlDOMNode == 'string') { + return; + } + for (var xmlAttribute in xmlDOMNode.attrs) { + xmlDOMNode.attrs[xmlAttribute] = xmlDOMNode.attrs[xmlAttribute].replace(/'/g, "APOS_CHARACTER_TOKEN"); + } + for (var xmlChild of xmlDOMNode.children) { + replaceEscapeXMLCharacters(xmlChild); + } + } + let source = path.join(__dirname, "/Resources/Web.config"); + let fileBuffer = fs.readFileSync(source); + let fileContent = fileBuffer.toString('utf-8'); + let xmlDomUtilityInstance = new xmlDomUtility_1.XmlDomUtility(fileContent); + let xmlSubstitution = new xmlVariableSubstitution_1.XmlSubstitution(xmlDomUtilityInstance); + let isApplied = xmlSubstitution.substituteXmlVariables(); + expect(isApplied).to.equal(true); + let xmlDocument = xmlDomUtilityInstance.getXmlDom(); + replaceEscapeXMLCharacters(xmlDocument); + let domContent = '\uFEFF' + xmlDomUtilityInstance.getContentWithHeader(xmlDocument); + for (let replacableTokenValue in xmlSubstitution.replacableTokenValues) { + domContent = domContent.split(replacableTokenValue).join(xmlSubstitution.replacableTokenValues[replacableTokenValue]); + } + let expectedResult = path.join(__dirname, "/Resources/Web_Expected.config"); + fileBuffer = fs.readFileSync(expectedResult); + let expectedContent = fileBuffer.toString('utf-8'); + let targetXmlDomUtilityInstance = new xmlDomUtility_1.XmlDomUtility(expectedContent); + let expectedXmlDocument = targetXmlDomUtilityInstance.getXmlDom(); + replaceEscapeXMLCharacters(expectedXmlDocument); + let expectedDomContent = '\uFEFF' + xmlDomUtilityInstance.getContentWithHeader(expectedXmlDocument); + expectedDomContent = expectedDomContent.split("APOS_CHARACTER_TOKEN").join("'"); + expect(domContent).to.equal(expectedDomContent); + }); +}); diff --git a/lib/operations/envVariableUtility.js b/lib/operations/envVariableUtility.js index fadcccac..86ad11cb 100644 --- a/lib/operations/envVariableUtility.js +++ b/lib/operations/envVariableUtility.js @@ -1,86 +1,86 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -function isPredefinedVariable(variable) { - let predefinedVarPrefix = ['runner.', 'azure_http_user_agent', 'common.', 'system.']; - for (let varPrefix of predefinedVarPrefix) { - if (variable.toLowerCase().startsWith(varPrefix)) { - return true; - } - } - return false; -} -exports.isPredefinedVariable = isPredefinedVariable; -function getVariableMap() { - let variableMap = new Map(); - let variables = process.env; - Object.keys(variables).forEach(key => { - if (!isPredefinedVariable(key)) { - variableMap.set(key, variables[key]); - } - }); - return variableMap; -} -exports.getVariableMap = getVariableMap; -function isEmpty(object) { - if (object == null || object == "") - return true; - return false; -} -exports.isEmpty = isEmpty; -function isObject(object) { - if (object == null || object == "" || typeof (object) != 'object') { - return false; - } - return true; -} -exports.isObject = isObject; -class EnvTreeUtility { - constructor() { - this.envVarTree = null; - } - static getEnvVarTree() { - let util = new EnvTreeUtility(); - if (!util.envVarTree) { - util.envVarTree = util.createEnvTree(getVariableMap()); - } - return util.envVarTree; - } - createEnvTree(envVariables) { - // __proto__ is marked as null, so that custom object can be assgined. - // This replacement do not affect the JSON object, as no inbuilt JSON function is referenced. - let envVarTree = { - value: null, - isEnd: false, - child: { - '__proto__': null - } - }; - for (let [key, value] of envVariables.entries()) { - let envVarTreeIterator = envVarTree; - let envVariableNameArray = key.split('.'); - for (let variableName of envVariableNameArray) { - if (envVarTreeIterator.child[variableName] === undefined || typeof envVarTreeIterator.child[variableName] === 'function') { - envVarTreeIterator.child[variableName] = { - value: null, - isEnd: false, - child: {} - }; - } - envVarTreeIterator = envVarTreeIterator.child[variableName]; - } - envVarTreeIterator.isEnd = true; - envVarTreeIterator.value = value; - } - return envVarTree; - } - checkEnvTreePath(jsonObjectKey, index, jsonObjectKeyLength, envVarTree) { - if (index == jsonObjectKeyLength) { - return envVarTree; - } - if (envVarTree.child[jsonObjectKey[index]] === undefined || typeof envVarTree.child[jsonObjectKey[index]] === 'function') { - return undefined; - } - return this.checkEnvTreePath(jsonObjectKey, index + 1, jsonObjectKeyLength, envVarTree.child[jsonObjectKey[index]]); - } -} -exports.EnvTreeUtility = EnvTreeUtility; +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function isPredefinedVariable(variable) { + let predefinedVarPrefix = ['runner.', 'azure_http_user_agent', 'common.', 'system.']; + for (let varPrefix of predefinedVarPrefix) { + if (variable.toLowerCase().startsWith(varPrefix)) { + return true; + } + } + return false; +} +exports.isPredefinedVariable = isPredefinedVariable; +function getVariableMap() { + let variableMap = new Map(); + let variables = process.env; + Object.keys(variables).forEach(key => { + if (!isPredefinedVariable(key)) { + variableMap.set(key, variables[key]); + } + }); + return variableMap; +} +exports.getVariableMap = getVariableMap; +function isEmpty(object) { + if (object == null || object == "") + return true; + return false; +} +exports.isEmpty = isEmpty; +function isObject(object) { + if (object == null || object == "" || typeof (object) != 'object') { + return false; + } + return true; +} +exports.isObject = isObject; +class EnvTreeUtility { + constructor() { + this.envVarTree = null; + } + static getEnvVarTree(splitChar) { + let util = new EnvTreeUtility(); + if (!util.envVarTree) { + util.envVarTree = util.createEnvTree(getVariableMap(), splitChar); + } + return util.envVarTree; + } + createEnvTree(envVariables, splitChar) { + // __proto__ is marked as null, so that custom object can be assgined. + // This replacement do not affect the JSON object, as no inbuilt JSON function is referenced. + let envVarTree = { + value: null, + isEnd: false, + child: { + '__proto__': null + } + }; + for (let [key, value] of envVariables.entries()) { + let envVarTreeIterator = envVarTree; + let envVariableNameArray = key.split(splitChar); + for (let variableName of envVariableNameArray) { + if (envVarTreeIterator.child[variableName] === undefined || typeof envVarTreeIterator.child[variableName] === 'function') { + envVarTreeIterator.child[variableName] = { + value: null, + isEnd: false, + child: {} + }; + } + envVarTreeIterator = envVarTreeIterator.child[variableName]; + } + envVarTreeIterator.isEnd = true; + envVarTreeIterator.value = value; + } + return envVarTree; + } + checkEnvTreePath(jsonObjectKey, index, jsonObjectKeyLength, envVarTree) { + if (index == jsonObjectKeyLength) { + return envVarTree; + } + if (envVarTree.child[jsonObjectKey[index]] === undefined || typeof envVarTree.child[jsonObjectKey[index]] === 'function') { + return undefined; + } + return this.checkEnvTreePath(jsonObjectKey, index + 1, jsonObjectKeyLength, envVarTree.child[jsonObjectKey[index]]); + } +} +exports.EnvTreeUtility = EnvTreeUtility; diff --git a/lib/operations/fileEncodingUtility.js b/lib/operations/fileEncodingUtility.js index 1ac4ad86..a3aac283 100644 --- a/lib/operations/fileEncodingUtility.js +++ b/lib/operations/fileEncodingUtility.js @@ -1,65 +1,65 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const core = require("@actions/core"); -function detectFileEncodingWithBOM(fileName, buffer) { - core.debug('Detecting file encoding using BOM'); - if (buffer.slice(0, 3).equals(Buffer.from([239, 187, 191]))) { - return { - encoding: 'utf-8', - withBOM: true - }; - } - else if (buffer.slice(0, 4).equals(Buffer.from([255, 254, 0, 0]))) { - throw Error(`Detected file encoding of the file ${fileName} as UTF-32LE. Variable substitution is not supported with file encoding UTF-32LE. Supported encodings are UTF-8 and UTF-16LE.`); - } - else if (buffer.slice(0, 2).equals(Buffer.from([254, 255]))) { - throw Error(`Detected file encoding of the file ${fileName} as UTF-32BE. Variable substitution is not supported with file encoding UTF-32BE. Supported encodings are UTF-8 and UTF-16LE.`); - } - else if (buffer.slice(0, 2).equals(Buffer.from([255, 254]))) { - return { - encoding: 'utf-16le', - withBOM: true - }; - } - else if (buffer.slice(0, 4).equals(Buffer.from([0, 0, 254, 255]))) { - throw Error(`Detected file encoding of the file ${fileName} as UTF-32BE. Variable substitution is not supported with file encoding UTF-32BE. Supported encodings are UTF-8 and UTF-16LE.`); - } - core.debug('Unable to detect File encoding using BOM'); - return null; -} -function detectFileEncodingWithoutBOM(fileName, buffer) { - core.debug('Detecting file encoding without BOM'); - let typeCode = 0; - for (let index = 0; index < 4; index++) { - typeCode = typeCode << 1; - typeCode = typeCode | (buffer[index] > 0 ? 1 : 0); - } - switch (typeCode) { - case 1: - throw Error(`Detected file encoding of the file ${fileName} as UTF-32BE. Variable substitution is not supported with file encoding UTF-32BE. Supported encodings are UTF-8 and UTF-16 LE.`); - case 5: - throw Error(`Detected file encoding of the file ${fileName} as UTF-16BE. Variable substitution is not supported with file encoding UTF-16BE. Supported encodings are UTF-8 and UTF-16 LE.`); - case 8: - throw Error(`Detected file encoding of the file ${fileName} as UTF-32LE. Variable substitution is not supported with file encoding UTF-32LE. Supported encodings are UTF-8 and UTF-16 LE.`); - case 10: - return { - encoding: 'utf-16le', - withBOM: false - }; - case 15: - return { - encoding: 'utf-8', - withBOM: false - }; - default: - throw Error(`Unable to detect encoding of the file ${fileName} (typeCode: ${typeCode}). Supported encodings are UTF-8 and UTF-16 LE.`); - } -} -function detectFileEncoding(fileName, buffer) { - if (buffer.length < 4) { - throw Error(`File buffer is too short to detect encoding type : ${fileName}`); - } - let fileEncoding = detectFileEncodingWithBOM(fileName, buffer) || detectFileEncodingWithoutBOM(fileName, buffer); - return fileEncoding; -} -exports.detectFileEncoding = detectFileEncoding; +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const core = require("@actions/core"); +function detectFileEncodingWithBOM(fileName, buffer) { + core.debug('Detecting file encoding using BOM'); + if (buffer.slice(0, 3).equals(Buffer.from([239, 187, 191]))) { + return { + encoding: 'utf-8', + withBOM: true + }; + } + else if (buffer.slice(0, 4).equals(Buffer.from([255, 254, 0, 0]))) { + throw Error(`Detected file encoding of the file ${fileName} as UTF-32LE. Variable substitution is not supported with file encoding UTF-32LE. Supported encodings are UTF-8 and UTF-16LE.`); + } + else if (buffer.slice(0, 2).equals(Buffer.from([254, 255]))) { + throw Error(`Detected file encoding of the file ${fileName} as UTF-32BE. Variable substitution is not supported with file encoding UTF-32BE. Supported encodings are UTF-8 and UTF-16LE.`); + } + else if (buffer.slice(0, 2).equals(Buffer.from([255, 254]))) { + return { + encoding: 'utf-16le', + withBOM: true + }; + } + else if (buffer.slice(0, 4).equals(Buffer.from([0, 0, 254, 255]))) { + throw Error(`Detected file encoding of the file ${fileName} as UTF-32BE. Variable substitution is not supported with file encoding UTF-32BE. Supported encodings are UTF-8 and UTF-16LE.`); + } + core.debug('Unable to detect File encoding using BOM'); + return null; +} +function detectFileEncodingWithoutBOM(fileName, buffer) { + core.debug('Detecting file encoding without BOM'); + let typeCode = 0; + for (let index = 0; index < 4; index++) { + typeCode = typeCode << 1; + typeCode = typeCode | (buffer[index] > 0 ? 1 : 0); + } + switch (typeCode) { + case 1: + throw Error(`Detected file encoding of the file ${fileName} as UTF-32BE. Variable substitution is not supported with file encoding UTF-32BE. Supported encodings are UTF-8 and UTF-16 LE.`); + case 5: + throw Error(`Detected file encoding of the file ${fileName} as UTF-16BE. Variable substitution is not supported with file encoding UTF-16BE. Supported encodings are UTF-8 and UTF-16 LE.`); + case 8: + throw Error(`Detected file encoding of the file ${fileName} as UTF-32LE. Variable substitution is not supported with file encoding UTF-32LE. Supported encodings are UTF-8 and UTF-16 LE.`); + case 10: + return { + encoding: 'utf-16le', + withBOM: false + }; + case 15: + return { + encoding: 'utf-8', + withBOM: false + }; + default: + throw Error(`Unable to detect encoding of the file ${fileName} (typeCode: ${typeCode}). Supported encodings are UTF-8 and UTF-16 LE.`); + } +} +function detectFileEncoding(fileName, buffer) { + if (buffer.length < 4) { + throw Error(`File buffer is too short to detect encoding type : ${fileName}`); + } + let fileEncoding = detectFileEncodingWithBOM(fileName, buffer) || detectFileEncodingWithoutBOM(fileName, buffer); + return fileEncoding; +} +exports.detectFileEncoding = detectFileEncoding; diff --git a/lib/operations/jsonVariableSubstitutionUtility.js b/lib/operations/jsonVariableSubstitutionUtility.js index a08f5585..f310c972 100644 --- a/lib/operations/jsonVariableSubstitutionUtility.js +++ b/lib/operations/jsonVariableSubstitutionUtility.js @@ -1,50 +1,51 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const core = require("@actions/core"); -const envVariableUtility_1 = require("./envVariableUtility"); -class JsonSubstitution { - constructor() { - this.envTreeUtil = new envVariableUtility_1.EnvTreeUtility(); - } - substituteJsonVariable(jsonObject, envObject) { - let isValueChanged = false; - for (let jsonChild in jsonObject) { - let jsonChildArray = jsonChild.split('.'); - let resultNode = this.envTreeUtil.checkEnvTreePath(jsonChildArray, 0, jsonChildArray.length, envObject); - if (resultNode != undefined) { - if (resultNode.isEnd) { - switch (typeof (jsonObject[jsonChild])) { - case 'number': - console.log('SubstitutingValueonKeyWithNumber', jsonChild, resultNode.value); - jsonObject[jsonChild] = !isNaN(resultNode.value) ? Number(resultNode.value) : resultNode.value; - break; - case 'boolean': - console.log('SubstitutingValueonKeyWithBoolean', jsonChild, resultNode.value); - jsonObject[jsonChild] = (resultNode.value == 'true' ? true : (resultNode.value == 'false' ? false : resultNode.value)); - break; - case 'object': - case null: - try { - console.log('SubstitutingValueonKeyWithObject', jsonChild, resultNode.value); - jsonObject[jsonChild] = JSON.parse(resultNode.value); - } - catch (exception) { - core.debug('unable to substitute the value. falling back to string value'); - jsonObject[jsonChild] = resultNode.value; - } - break; - case 'string': - console.log('SubstitutingValueonKeyWithString', jsonChild, resultNode.value); - jsonObject[jsonChild] = resultNode.value; - } - isValueChanged = true; - } - else { - isValueChanged = this.substituteJsonVariable(jsonObject[jsonChild], resultNode) || isValueChanged; - } - } - } - return isValueChanged; - } -} -exports.JsonSubstitution = JsonSubstitution; +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const core = require("@actions/core"); +const envVariableUtility_1 = require("./envVariableUtility"); +class JsonSubstitution { + constructor() { + this.envTreeUtil = new envVariableUtility_1.EnvTreeUtility(); + this.splitChar = core.getInput("splitChar") || '.'; + } + substituteJsonVariable(jsonObject, envObject) { + let isValueChanged = false; + for (let jsonChild in jsonObject) { + let jsonChildArray = jsonChild.split(this.splitChar); + let resultNode = this.envTreeUtil.checkEnvTreePath(jsonChildArray, 0, jsonChildArray.length, envObject); + if (resultNode != undefined) { + if (resultNode.isEnd) { + switch (typeof (jsonObject[jsonChild])) { + case 'number': + console.log('SubstitutingValueonKeyWithNumber', jsonChild, resultNode.value); + jsonObject[jsonChild] = !isNaN(resultNode.value) ? Number(resultNode.value) : resultNode.value; + break; + case 'boolean': + console.log('SubstitutingValueonKeyWithBoolean', jsonChild, resultNode.value); + jsonObject[jsonChild] = (resultNode.value == 'true' ? true : (resultNode.value == 'false' ? false : resultNode.value)); + break; + case 'object': + case null: + try { + console.log('SubstitutingValueonKeyWithObject', jsonChild, resultNode.value); + jsonObject[jsonChild] = JSON.parse(resultNode.value); + } + catch (exception) { + core.debug('unable to substitute the value. falling back to string value'); + jsonObject[jsonChild] = resultNode.value; + } + break; + case 'string': + console.log('SubstitutingValueonKeyWithString', jsonChild, resultNode.value); + jsonObject[jsonChild] = resultNode.value; + } + isValueChanged = true; + } + else { + isValueChanged = this.substituteJsonVariable(jsonObject[jsonChild], resultNode) || isValueChanged; + } + } + } + return isValueChanged; + } +} +exports.JsonSubstitution = JsonSubstitution; diff --git a/lib/operations/utility.js b/lib/operations/utility.js index 36c3e394..ffe3ad14 100644 --- a/lib/operations/utility.js +++ b/lib/operations/utility.js @@ -1,328 +1,328 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const core = require("@actions/core"); -const os = __importStar(require("os")); -const minimatch = __importStar(require("minimatch")); -const fs = require("fs"); -const path = require("path"); -function findfiles(filepath) { - core.debug("Finding files matching input: " + filepath); - let filesList; - if (filepath.indexOf('*') == -1 && filepath.indexOf('?') == -1) { - // No pattern found, check literal path to a single file - if (exist(filepath)) { - filesList = [filepath]; - } - else { - core.debug('No matching files were found with search pattern: ' + filepath); - return []; - } - } - else { - filepath = path.join(process.env.GITHUB_WORKSPACE, filepath); - let firstWildcardIndex = function (str) { - let idx = str.indexOf('*'); - let idxOfWildcard = str.indexOf('?'); - if (idxOfWildcard > -1) { - return (idx > -1) ? - Math.min(idx, idxOfWildcard) : idxOfWildcard; - } - return idx; - }; - // Find app files matching the specified pattern - core.debug('Matching glob pattern: ' + filepath); - // First find the most complete path without any matching patterns - let idx = firstWildcardIndex(filepath); - core.debug('Index of first wildcard: ' + idx); - let slicedPath = filepath.slice(0, idx); - let findPathRoot = path.dirname(slicedPath); - if (slicedPath.endsWith("\\") || slicedPath.endsWith("/")) { - findPathRoot = slicedPath; - } - core.debug('find root dir: ' + findPathRoot); - // Now we get a list of all files under this root - let allFiles = find(findPathRoot); - // Now matching the pattern against all files - filesList = match(allFiles, filepath, '', { matchBase: true, nocase: !!os.type().match(/^Win/) }); - // Fail if no matching files were found - if (!filesList || filesList.length == 0) { - core.debug('No matching files were found with search pattern: ' + filepath); - return []; - } - } - return filesList; -} -exports.findfiles = findfiles; -class _FindItem { - constructor(path, level) { - this.path = path; - this.level = level; - } -} -function find(findPath) { - if (!findPath) { - core.debug('no path specified'); - return []; - } - // normalize the path, otherwise the first result is inconsistently formatted from the rest of the results - // because path.join() performs normalization. - findPath = path.normalize(findPath); - // debug trace the parameters - core.debug(`findPath: '${findPath}'`); - // return empty if not exists - try { - fs.lstatSync(findPath); - } - catch (err) { - if (err.code == 'ENOENT') { - core.debug('0 results'); - return []; - } - throw err; - } - try { - let result = []; - // push the first item - let stack = [new _FindItem(findPath, 1)]; - let traversalChain = []; // used to detect cycles - while (stack.length) { - // pop the next item and push to the result array - let item = stack.pop(); // non-null because `stack.length` was truthy - result.push(item.path); - // stat the item. the stat info is used further below to determine whether to traverse deeper - // - // stat returns info about the target of a symlink (or symlink chain), - // lstat returns info about a symlink itself - let stats; - // use lstat (not following symlinks) - stats = fs.lstatSync(item.path); - // note, isDirectory() returns false for the lstat of a symlink - if (stats.isDirectory()) { - core.debug(` ${item.path} (directory)`); - // push the child items in reverse onto the stack - let childLevel = item.level + 1; - let childItems = fs.readdirSync(item.path) - .map((childName) => new _FindItem(path.join(item.path, childName), childLevel)); - for (let i = childItems.length - 1; i >= 0; i--) { - stack.push(childItems[i]); - } - } - else { - core.debug(` ${item.path} (file)`); - } - } - core.debug(`${result.length} results`); - return result; - } - catch (err) { - throw new Error('LIB_OperationFailed' + 'find' + err.message); - } -} -function _getDefaultMatchOptions() { - return { - debug: false, - nobrace: true, - noglobstar: false, - dot: true, - noext: false, - nocase: process.platform == 'win32', - nonull: false, - matchBase: false, - nocomment: false, - nonegate: false, - flipNegate: false - }; -} -function cloneMatchOptions(matchOptions) { - return { - debug: matchOptions.debug, - nobrace: matchOptions.nobrace, - noglobstar: matchOptions.noglobstar, - dot: matchOptions.dot, - noext: matchOptions.noext, - nocase: matchOptions.nocase, - nonull: matchOptions.nonull, - matchBase: matchOptions.matchBase, - nocomment: matchOptions.nocomment, - nonegate: matchOptions.nonegate, - flipNegate: matchOptions.flipNegate - }; -} -function match(list, patterns, patternRoot, options) { - // trace parameters - core.debug(`patternRoot: '${patternRoot}'`); - options = options || _getDefaultMatchOptions(); // default match options - // convert pattern to an array - if (typeof patterns == 'string') { - patterns = [patterns]; - } - // hashtable to keep track of matches - let map = {}; - let originalOptions = options; - for (let pattern of patterns) { - core.debug(`pattern: '${pattern}'`); - // trim and skip empty - pattern = (pattern || '').trim(); - if (!pattern) { - core.debug('skipping empty pattern'); - continue; - } - // clone match options - let options = cloneMatchOptions(originalOptions); - // skip comments - if (!options.nocomment && startsWith(pattern, '#')) { - core.debug('skipping comment'); - continue; - } - // set nocomment - brace expansion could result in a leading '#' - options.nocomment = true; - // determine whether pattern is include or exclude - let negateCount = 0; - if (!options.nonegate) { - while (pattern.charAt(negateCount) == '!') { - negateCount++; - } - pattern = pattern.substring(negateCount); // trim leading '!' - if (negateCount) { - core.debug(`trimmed leading '!'. pattern: '${pattern}'`); - } - } - let isIncludePattern = negateCount == 0 || - (negateCount % 2 == 0 && !options.flipNegate) || - (negateCount % 2 == 1 && options.flipNegate); - // set nonegate - brace expansion could result in a leading '!' - options.nonegate = true; - options.flipNegate = false; - // expand braces - required to accurately root patterns - let expanded; - let preExpanded = pattern; - if (options.nobrace) { - expanded = [pattern]; - } - else { - // convert slashes on Windows before calling braceExpand(). unfortunately this means braces cannot - // be escaped on Windows, this limitation is consistent with current limitations of minimatch (3.0.3). - core.debug('expanding braces'); - let convertedPattern = process.platform == 'win32' ? pattern.replace(/\\/g, '/') : pattern; - expanded = minimatch.braceExpand(convertedPattern); - } - // set nobrace - options.nobrace = true; - for (let pattern of expanded) { - if (expanded.length != 1 || pattern != preExpanded) { - core.debug(`pattern: '${pattern}'`); - } - // trim and skip empty - pattern = (pattern || '').trim(); - if (!pattern) { - core.debug('skipping empty pattern'); - continue; - } - // root the pattern when all of the following conditions are true: - if (patternRoot && // patternRoot supplied - !isRooted(pattern) && // AND pattern not rooted - // AND matchBase:false or not basename only - (!options.matchBase || (process.platform == 'win32' ? pattern.replace(/\\/g, '/') : pattern).indexOf('/') >= 0)) { - pattern = ensureRooted(patternRoot, pattern); - core.debug(`rooted pattern: '${pattern}'`); - } - if (isIncludePattern) { - // apply the pattern - core.debug('applying include pattern against original list'); - let matchResults = minimatch.match(list, pattern, options); - core.debug(matchResults.length + ' matches'); - // union the results - for (let matchResult of matchResults) { - map[matchResult] = true; - } - } - else { - // apply the pattern - core.debug('applying exclude pattern against original list'); - let matchResults = minimatch.match(list, pattern, options); - core.debug(matchResults.length + ' matches'); - // substract the results - for (let matchResult of matchResults) { - delete map[matchResult]; - } - } - } - } - // return a filtered version of the original list (preserves order and prevents duplication) - let result = list.filter((item) => map.hasOwnProperty(item)); - core.debug(result.length + ' final results'); - return result; -} -function ensureRooted(root, p) { - if (!root) { - throw new Error('ensureRooted() parameter "root" cannot be empty'); - } - if (!p) { - throw new Error('ensureRooted() parameter "p" cannot be empty'); - } - if (isRooted(p)) { - return p; - } - if (process.platform == 'win32' && root.match(/^[A-Z]:$/i)) { // e.g. C: - return root + p; - } - // ensure root ends with a separator - if (endsWith(root, '/') || (process.platform == 'win32' && endsWith(root, '\\'))) { - // root already ends with a separator - } - else { - root += path.sep; // append separator - } - return root + p; -} -function isRooted(p) { - p = normalizeSeparators(p); - if (!p) { - throw new Error('isRooted() parameter "p" cannot be empty'); - } - if (process.platform == 'win32') { - return startsWith(p, '\\') || // e.g. \ or \hello or \\hello - /^[A-Z]:/i.test(p); // e.g. C: or C:\hello - } - return startsWith(p, '/'); // e.g. /hello -} -function startsWith(str, start) { - return str.slice(0, start.length) == start; -} -function endsWith(str, end) { - return str.slice(-end.length) == end; -} -function normalizeSeparators(p) { - p = p || ''; - if (process.platform == 'win32') { - // convert slashes on Windows - p = p.replace(/\//g, '\\'); - // remove redundant slashes - let isUnc = /^\\\\+[^\\]/.test(p); // e.g. \\hello - return (isUnc ? '\\' : '') + p.replace(/\\\\+/g, '\\'); // preserve leading // for UNC - } - // remove redundant slashes - return p.replace(/\/\/+/g, '/'); -} -function exist(path) { - let exist = false; - try { - exist = !!(path && fs.statSync(path) != null); - } - catch (err) { - if (err && err.code === 'ENOENT') { - exist = false; - } - else { - throw err; - } - } - return exist; -} +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const core = require("@actions/core"); +const os = __importStar(require("os")); +const minimatch = __importStar(require("minimatch")); +const fs = require("fs"); +const path = require("path"); +function findfiles(filepath) { + core.debug("Finding files matching input: " + filepath); + let filesList; + if (filepath.indexOf('*') == -1 && filepath.indexOf('?') == -1) { + // No pattern found, check literal path to a single file + if (exist(filepath)) { + filesList = [filepath]; + } + else { + core.debug('No matching files were found with search pattern: ' + filepath); + return []; + } + } + else { + filepath = path.join(process.env.GITHUB_WORKSPACE, filepath); + let firstWildcardIndex = function (str) { + let idx = str.indexOf('*'); + let idxOfWildcard = str.indexOf('?'); + if (idxOfWildcard > -1) { + return (idx > -1) ? + Math.min(idx, idxOfWildcard) : idxOfWildcard; + } + return idx; + }; + // Find app files matching the specified pattern + core.debug('Matching glob pattern: ' + filepath); + // First find the most complete path without any matching patterns + let idx = firstWildcardIndex(filepath); + core.debug('Index of first wildcard: ' + idx); + let slicedPath = filepath.slice(0, idx); + let findPathRoot = path.dirname(slicedPath); + if (slicedPath.endsWith("\\") || slicedPath.endsWith("/")) { + findPathRoot = slicedPath; + } + core.debug('find root dir: ' + findPathRoot); + // Now we get a list of all files under this root + let allFiles = find(findPathRoot); + // Now matching the pattern against all files + filesList = match(allFiles, filepath, '', { matchBase: true, nocase: !!os.type().match(/^Win/) }); + // Fail if no matching files were found + if (!filesList || filesList.length == 0) { + core.debug('No matching files were found with search pattern: ' + filepath); + return []; + } + } + return filesList; +} +exports.findfiles = findfiles; +class _FindItem { + constructor(path, level) { + this.path = path; + this.level = level; + } +} +function find(findPath) { + if (!findPath) { + core.debug('no path specified'); + return []; + } + // normalize the path, otherwise the first result is inconsistently formatted from the rest of the results + // because path.join() performs normalization. + findPath = path.normalize(findPath); + // debug trace the parameters + core.debug(`findPath: '${findPath}'`); + // return empty if not exists + try { + fs.lstatSync(findPath); + } + catch (err) { + if (err.code == 'ENOENT') { + core.debug('0 results'); + return []; + } + throw err; + } + try { + let result = []; + // push the first item + let stack = [new _FindItem(findPath, 1)]; + let traversalChain = []; // used to detect cycles + while (stack.length) { + // pop the next item and push to the result array + let item = stack.pop(); // non-null because `stack.length` was truthy + result.push(item.path); + // stat the item. the stat info is used further below to determine whether to traverse deeper + // + // stat returns info about the target of a symlink (or symlink chain), + // lstat returns info about a symlink itself + let stats; + // use lstat (not following symlinks) + stats = fs.lstatSync(item.path); + // note, isDirectory() returns false for the lstat of a symlink + if (stats.isDirectory()) { + core.debug(` ${item.path} (directory)`); + // push the child items in reverse onto the stack + let childLevel = item.level + 1; + let childItems = fs.readdirSync(item.path) + .map((childName) => new _FindItem(path.join(item.path, childName), childLevel)); + for (let i = childItems.length - 1; i >= 0; i--) { + stack.push(childItems[i]); + } + } + else { + core.debug(` ${item.path} (file)`); + } + } + core.debug(`${result.length} results`); + return result; + } + catch (err) { + throw new Error('LIB_OperationFailed' + 'find' + err.message); + } +} +function _getDefaultMatchOptions() { + return { + debug: false, + nobrace: true, + noglobstar: false, + dot: true, + noext: false, + nocase: process.platform == 'win32', + nonull: false, + matchBase: false, + nocomment: false, + nonegate: false, + flipNegate: false + }; +} +function cloneMatchOptions(matchOptions) { + return { + debug: matchOptions.debug, + nobrace: matchOptions.nobrace, + noglobstar: matchOptions.noglobstar, + dot: matchOptions.dot, + noext: matchOptions.noext, + nocase: matchOptions.nocase, + nonull: matchOptions.nonull, + matchBase: matchOptions.matchBase, + nocomment: matchOptions.nocomment, + nonegate: matchOptions.nonegate, + flipNegate: matchOptions.flipNegate + }; +} +function match(list, patterns, patternRoot, options) { + // trace parameters + core.debug(`patternRoot: '${patternRoot}'`); + options = options || _getDefaultMatchOptions(); // default match options + // convert pattern to an array + if (typeof patterns == 'string') { + patterns = [patterns]; + } + // hashtable to keep track of matches + let map = {}; + let originalOptions = options; + for (let pattern of patterns) { + core.debug(`pattern: '${pattern}'`); + // trim and skip empty + pattern = (pattern || '').trim(); + if (!pattern) { + core.debug('skipping empty pattern'); + continue; + } + // clone match options + let options = cloneMatchOptions(originalOptions); + // skip comments + if (!options.nocomment && startsWith(pattern, '#')) { + core.debug('skipping comment'); + continue; + } + // set nocomment - brace expansion could result in a leading '#' + options.nocomment = true; + // determine whether pattern is include or exclude + let negateCount = 0; + if (!options.nonegate) { + while (pattern.charAt(negateCount) == '!') { + negateCount++; + } + pattern = pattern.substring(negateCount); // trim leading '!' + if (negateCount) { + core.debug(`trimmed leading '!'. pattern: '${pattern}'`); + } + } + let isIncludePattern = negateCount == 0 || + (negateCount % 2 == 0 && !options.flipNegate) || + (negateCount % 2 == 1 && options.flipNegate); + // set nonegate - brace expansion could result in a leading '!' + options.nonegate = true; + options.flipNegate = false; + // expand braces - required to accurately root patterns + let expanded; + let preExpanded = pattern; + if (options.nobrace) { + expanded = [pattern]; + } + else { + // convert slashes on Windows before calling braceExpand(). unfortunately this means braces cannot + // be escaped on Windows, this limitation is consistent with current limitations of minimatch (3.0.3). + core.debug('expanding braces'); + let convertedPattern = process.platform == 'win32' ? pattern.replace(/\\/g, '/') : pattern; + expanded = minimatch.braceExpand(convertedPattern); + } + // set nobrace + options.nobrace = true; + for (let pattern of expanded) { + if (expanded.length != 1 || pattern != preExpanded) { + core.debug(`pattern: '${pattern}'`); + } + // trim and skip empty + pattern = (pattern || '').trim(); + if (!pattern) { + core.debug('skipping empty pattern'); + continue; + } + // root the pattern when all of the following conditions are true: + if (patternRoot && // patternRoot supplied + !isRooted(pattern) && // AND pattern not rooted + // AND matchBase:false or not basename only + (!options.matchBase || (process.platform == 'win32' ? pattern.replace(/\\/g, '/') : pattern).indexOf('/') >= 0)) { + pattern = ensureRooted(patternRoot, pattern); + core.debug(`rooted pattern: '${pattern}'`); + } + if (isIncludePattern) { + // apply the pattern + core.debug('applying include pattern against original list'); + let matchResults = minimatch.match(list, pattern, options); + core.debug(matchResults.length + ' matches'); + // union the results + for (let matchResult of matchResults) { + map[matchResult] = true; + } + } + else { + // apply the pattern + core.debug('applying exclude pattern against original list'); + let matchResults = minimatch.match(list, pattern, options); + core.debug(matchResults.length + ' matches'); + // substract the results + for (let matchResult of matchResults) { + delete map[matchResult]; + } + } + } + } + // return a filtered version of the original list (preserves order and prevents duplication) + let result = list.filter((item) => map.hasOwnProperty(item)); + core.debug(result.length + ' final results'); + return result; +} +function ensureRooted(root, p) { + if (!root) { + throw new Error('ensureRooted() parameter "root" cannot be empty'); + } + if (!p) { + throw new Error('ensureRooted() parameter "p" cannot be empty'); + } + if (isRooted(p)) { + return p; + } + if (process.platform == 'win32' && root.match(/^[A-Z]:$/i)) { // e.g. C: + return root + p; + } + // ensure root ends with a separator + if (endsWith(root, '/') || (process.platform == 'win32' && endsWith(root, '\\'))) { + // root already ends with a separator + } + else { + root += path.sep; // append separator + } + return root + p; +} +function isRooted(p) { + p = normalizeSeparators(p); + if (!p) { + throw new Error('isRooted() parameter "p" cannot be empty'); + } + if (process.platform == 'win32') { + return startsWith(p, '\\') || // e.g. \ or \hello or \\hello + /^[A-Z]:/i.test(p); // e.g. C: or C:\hello + } + return startsWith(p, '/'); // e.g. /hello +} +function startsWith(str, start) { + return str.slice(0, start.length) == start; +} +function endsWith(str, end) { + return str.slice(-end.length) == end; +} +function normalizeSeparators(p) { + p = p || ''; + if (process.platform == 'win32') { + // convert slashes on Windows + p = p.replace(/\//g, '\\'); + // remove redundant slashes + let isUnc = /^\\\\+[^\\]/.test(p); // e.g. \\hello + return (isUnc ? '\\' : '') + p.replace(/\\\\+/g, '\\'); // preserve leading // for UNC + } + // remove redundant slashes + return p.replace(/\/\/+/g, '/'); +} +function exist(path) { + let exist = false; + try { + exist = !!(path && fs.statSync(path) != null); + } + catch (err) { + if (err && err.code === 'ENOENT') { + exist = false; + } + else { + throw err; + } + } + return exist; +} diff --git a/lib/operations/xmlDomUtility.js b/lib/operations/xmlDomUtility.js index 7efe1634..e64325ba 100644 --- a/lib/operations/xmlDomUtility.js +++ b/lib/operations/xmlDomUtility.js @@ -1,88 +1,88 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var envVarUtility = require("./envVariableUtility"); -var ltx = require('ltx'); -class XmlDomUtility { - constructor(xmlContent) { - this.xmlDomLookUpTable = {}; - this.xmlDomLookUpTable = {}; - this.headerContent = null; - this.xmlDom = ltx.parse(xmlContent); - this.readHeader(xmlContent); - this.buildLookUpTable(this.xmlDom); - } - getXmlDom() { - return this.xmlDom; - } - readHeader(xmlContent) { - let index = xmlContent.indexOf('\n'); - if (index > -1) { - let firstLine = xmlContent.substring(0, index).trim(); - if (firstLine.startsWith("")) { - this.headerContent = firstLine; - } - } - } - getContentWithHeader(xmlDom) { - return xmlDom ? (this.headerContent ? this.headerContent + "\n" : "") + xmlDom.root().toString() : ""; - } - /** - * Define method to create a lookup for DOM - */ - buildLookUpTable(node) { - if (node) { - let nodeName = node.name; - if (nodeName) { - nodeName = nodeName.toLowerCase(); - let listOfNodes = this.xmlDomLookUpTable[nodeName]; - if (listOfNodes == null || !(Array.isArray(listOfNodes))) { - listOfNodes = []; - this.xmlDomLookUpTable[nodeName] = listOfNodes; - } - listOfNodes.push(node); - let childNodes = node.children; - for (let i = 0; i < childNodes.length; i++) { - let childNodeName = childNodes[i].name; - if (childNodeName) { - this.buildLookUpTable(childNodes[i]); - } - } - } - } - } - /** - * Returns array of nodes which match with the tag name. - */ - getElementsByTagName(nodeName) { - if (envVarUtility.isEmpty(nodeName)) - return []; - let selectedElements = this.xmlDomLookUpTable[nodeName.toLowerCase()]; - if (!selectedElements) { - selectedElements = []; - } - return selectedElements; - } - /** - * Search in subtree with provided node name - */ - getChildElementsByTagName(node, tagName) { - if (!envVarUtility.isObject(node)) - return []; - let children = node.children; - let liveNodes = []; - if (children) { - for (let i = 0; i < children.length; i++) { - let childName = children[i].name; - if (!envVarUtility.isEmpty(childName) && tagName == childName) { - liveNodes.push(children[i]); - } - let liveChildNodes = this.getChildElementsByTagName(children[i], tagName); - if (liveChildNodes && liveChildNodes.length > 0) { - liveNodes = liveNodes.concat(liveChildNodes); - } - } - } - return liveNodes; - } -} -exports.XmlDomUtility = XmlDomUtility; +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var envVarUtility = require("./envVariableUtility"); +var ltx = require('ltx'); +class XmlDomUtility { + constructor(xmlContent) { + this.xmlDomLookUpTable = {}; + this.xmlDomLookUpTable = {}; + this.headerContent = null; + this.xmlDom = ltx.parse(xmlContent); + this.readHeader(xmlContent); + this.buildLookUpTable(this.xmlDom); + } + getXmlDom() { + return this.xmlDom; + } + readHeader(xmlContent) { + let index = xmlContent.indexOf('\n'); + if (index > -1) { + let firstLine = xmlContent.substring(0, index).trim(); + if (firstLine.startsWith("")) { + this.headerContent = firstLine; + } + } + } + getContentWithHeader(xmlDom) { + return xmlDom ? (this.headerContent ? this.headerContent + "\n" : "") + xmlDom.root().toString() : ""; + } + /** + * Define method to create a lookup for DOM + */ + buildLookUpTable(node) { + if (node) { + let nodeName = node.name; + if (nodeName) { + nodeName = nodeName.toLowerCase(); + let listOfNodes = this.xmlDomLookUpTable[nodeName]; + if (listOfNodes == null || !(Array.isArray(listOfNodes))) { + listOfNodes = []; + this.xmlDomLookUpTable[nodeName] = listOfNodes; + } + listOfNodes.push(node); + let childNodes = node.children; + for (let i = 0; i < childNodes.length; i++) { + let childNodeName = childNodes[i].name; + if (childNodeName) { + this.buildLookUpTable(childNodes[i]); + } + } + } + } + } + /** + * Returns array of nodes which match with the tag name. + */ + getElementsByTagName(nodeName) { + if (envVarUtility.isEmpty(nodeName)) + return []; + let selectedElements = this.xmlDomLookUpTable[nodeName.toLowerCase()]; + if (!selectedElements) { + selectedElements = []; + } + return selectedElements; + } + /** + * Search in subtree with provided node name + */ + getChildElementsByTagName(node, tagName) { + if (!envVarUtility.isObject(node)) + return []; + let children = node.children; + let liveNodes = []; + if (children) { + for (let i = 0; i < children.length; i++) { + let childName = children[i].name; + if (!envVarUtility.isEmpty(childName) && tagName == childName) { + liveNodes.push(children[i]); + } + let liveChildNodes = this.getChildElementsByTagName(children[i], tagName); + if (liveChildNodes && liveChildNodes.length > 0) { + liveNodes = liveNodes.concat(liveChildNodes); + } + } + } + return liveNodes; + } +} +exports.XmlDomUtility = XmlDomUtility; diff --git a/lib/operations/xmlVariableSubstitution.js b/lib/operations/xmlVariableSubstitution.js index 5a3a438d..35bb83c2 100644 --- a/lib/operations/xmlVariableSubstitution.js +++ b/lib/operations/xmlVariableSubstitution.js @@ -1,138 +1,138 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const core = require("@actions/core"); -let envVarUtility = require('./envVariableUtility'); -const tags = ["applicationSettings", "appSettings", "connectionStrings", "configSections"]; -class XmlSubstitution { - constructor(xmlDomUtilityInstance) { - this.replacableTokenValues = { "APOS_CHARACTER_TOKEN": "'" }; - this.variableMap = envVarUtility.getVariableMap(); - this.xmlDomUtility = xmlDomUtilityInstance; - } - substituteXmlVariables() { - let isSubstitutionApplied = false; - for (let tag of tags) { - let nodes = this.xmlDomUtility.getElementsByTagName(tag); - if (nodes.length == 0) { - core.debug("Unable to find node with tag '" + tag + "' in provided xml file."); - continue; - } - for (let xmlNode of nodes) { - if (envVarUtility.isObject(xmlNode)) { - console.log('Processing substitution for xml node: ', xmlNode.name); - try { - if (xmlNode.name == "configSections") { - isSubstitutionApplied = this.updateXmlConfigNodeAttribute(xmlNode) || isSubstitutionApplied; - } - else if (xmlNode.name == "connectionStrings") { - isSubstitutionApplied = this.updateXmlConnectionStringsNodeAttribute(xmlNode) || isSubstitutionApplied; - } - else { - isSubstitutionApplied = this.updateXmlNodeAttribute(xmlNode) || isSubstitutionApplied; - } - } - catch (error) { - core.debug("Error occurred while processing xml node : " + xmlNode.name); - core.debug(error); - } - } - } - } - return isSubstitutionApplied; - } - updateXmlConfigNodeAttribute(xmlNode) { - let isSubstitutionApplied = false; - let sections = this.xmlDomUtility.getChildElementsByTagName(xmlNode, "section"); - for (let section of sections) { - if (envVarUtility.isObject(section)) { - let sectionName = section.attr('name'); - if (!envVarUtility.isEmpty(sectionName)) { - let customSectionNodes = this.xmlDomUtility.getElementsByTagName(sectionName); - if (customSectionNodes.length != 0) { - let customNode = customSectionNodes[0]; - isSubstitutionApplied = this.updateXmlNodeAttribute(customNode) || isSubstitutionApplied; - } - } - } - } - return isSubstitutionApplied; - } - updateXmlNodeAttribute(xmlDomNode) { - let isSubstitutionApplied = false; - if (envVarUtility.isEmpty(xmlDomNode) || !envVarUtility.isObject(xmlDomNode) || xmlDomNode.name == "#comment") { - core.debug("Provided node is empty or a comment."); - return isSubstitutionApplied; - } - const ConfigFileAppSettingsToken = 'CONFIG_FILE_SETTINGS_TOKEN'; - let xmlDomNodeAttributes = xmlDomNode.attrs; - for (var attributeName in xmlDomNodeAttributes) { - var attributeNameValue = (attributeName === "key" || attributeName == "name") ? xmlDomNodeAttributes[attributeName] : attributeName; - var attributeName = (attributeName === "key" || attributeName == "name") ? "value" : attributeName; - if (this.variableMap.get(attributeNameValue) != undefined) { - let ConfigFileAppSettingsTokenName = ConfigFileAppSettingsToken + '(' + attributeNameValue + ')'; - let isValueReplaced = false; - if (xmlDomNode.getAttr(attributeName) != undefined) { - console.log(`Updating value for key: ${attributeNameValue} with token value: ${ConfigFileAppSettingsTokenName}`); - xmlDomNode.attr(attributeName, ConfigFileAppSettingsTokenName); - isValueReplaced = true; - } - else { - let children = xmlDomNode.children; - for (var childNode of children) { - if (envVarUtility.isObject(childNode) && childNode.name == attributeName) { - if (childNode.children.length === 1) { - console.log(`Updating value for key: ${attributeNameValue} with token value: ${ConfigFileAppSettingsTokenName}`); - childNode.children[0] = ConfigFileAppSettingsTokenName; - isValueReplaced = true; - } - } - } - } - if (isValueReplaced) { - this.replacableTokenValues[ConfigFileAppSettingsTokenName] = this.variableMap.get(attributeNameValue).replace(/"/g, "'"); - isSubstitutionApplied = true; - } - } - } - let children = xmlDomNode.children; - for (var childNode of children) { - if (envVarUtility.isObject(childNode)) { - isSubstitutionApplied = this.updateXmlNodeAttribute(childNode) || isSubstitutionApplied; - } - } - return isSubstitutionApplied; - } - updateXmlConnectionStringsNodeAttribute(xmlDomNode) { - let isSubstitutionApplied = false; - const ConfigFileConnStringToken = 'CONFIG_FILE_CONN_STRING_TOKEN'; - if (envVarUtility.isEmpty(xmlDomNode) || !envVarUtility.isObject(xmlDomNode) || xmlDomNode.name == "#comment") { - core.debug("Provided node is empty or a comment."); - return isSubstitutionApplied; - } - let xmlDomNodeAttributes = xmlDomNode.attrs; - if (xmlDomNodeAttributes.hasOwnProperty("connectionString")) { - if (xmlDomNodeAttributes.hasOwnProperty("name") && this.variableMap.get(xmlDomNodeAttributes.name)) { - let ConfigFileConnStringTokenName = ConfigFileConnStringToken + '(' + xmlDomNodeAttributes.name + ')'; - core.debug(`Substituting connectionString value for connectionString= ${xmlDomNodeAttributes.name} with token value: ${ConfigFileConnStringTokenName}`); - xmlDomNode.attr("connectionString", ConfigFileConnStringTokenName); - this.replacableTokenValues[ConfigFileConnStringTokenName] = this.variableMap.get(xmlDomNodeAttributes.name).replace(/"/g, "'"); - isSubstitutionApplied = true; - } - else if (this.variableMap.get("connectionString") != undefined) { - let ConfigFileConnStringTokenName = ConfigFileConnStringToken + '(connectionString)'; - core.debug(`Substituting connectionString value for connectionString= ${xmlDomNodeAttributes.name} with token value: ${ConfigFileConnStringTokenName}`); - xmlDomNode.attr("connectionString", ConfigFileConnStringTokenName); - this.replacableTokenValues[ConfigFileConnStringTokenName] = this.variableMap.get("connectionString").replace(/"/g, "'"); - isSubstitutionApplied = true; - } - } - let children = xmlDomNode.children; - for (var childNode of children) { - if (envVarUtility.isObject(childNode)) { - isSubstitutionApplied = this.updateXmlConnectionStringsNodeAttribute(childNode) || isSubstitutionApplied; - } - } - return isSubstitutionApplied; - } -} -exports.XmlSubstitution = XmlSubstitution; +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const core = require("@actions/core"); +let envVarUtility = require('./envVariableUtility'); +const tags = ["applicationSettings", "appSettings", "connectionStrings", "configSections"]; +class XmlSubstitution { + constructor(xmlDomUtilityInstance) { + this.replacableTokenValues = { "APOS_CHARACTER_TOKEN": "'" }; + this.variableMap = envVarUtility.getVariableMap(); + this.xmlDomUtility = xmlDomUtilityInstance; + } + substituteXmlVariables() { + let isSubstitutionApplied = false; + for (let tag of tags) { + let nodes = this.xmlDomUtility.getElementsByTagName(tag); + if (nodes.length == 0) { + core.debug("Unable to find node with tag '" + tag + "' in provided xml file."); + continue; + } + for (let xmlNode of nodes) { + if (envVarUtility.isObject(xmlNode)) { + console.log('Processing substitution for xml node: ', xmlNode.name); + try { + if (xmlNode.name == "configSections") { + isSubstitutionApplied = this.updateXmlConfigNodeAttribute(xmlNode) || isSubstitutionApplied; + } + else if (xmlNode.name == "connectionStrings") { + isSubstitutionApplied = this.updateXmlConnectionStringsNodeAttribute(xmlNode) || isSubstitutionApplied; + } + else { + isSubstitutionApplied = this.updateXmlNodeAttribute(xmlNode) || isSubstitutionApplied; + } + } + catch (error) { + core.debug("Error occurred while processing xml node : " + xmlNode.name); + core.debug(error); + } + } + } + } + return isSubstitutionApplied; + } + updateXmlConfigNodeAttribute(xmlNode) { + let isSubstitutionApplied = false; + let sections = this.xmlDomUtility.getChildElementsByTagName(xmlNode, "section"); + for (let section of sections) { + if (envVarUtility.isObject(section)) { + let sectionName = section.attr('name'); + if (!envVarUtility.isEmpty(sectionName)) { + let customSectionNodes = this.xmlDomUtility.getElementsByTagName(sectionName); + if (customSectionNodes.length != 0) { + let customNode = customSectionNodes[0]; + isSubstitutionApplied = this.updateXmlNodeAttribute(customNode) || isSubstitutionApplied; + } + } + } + } + return isSubstitutionApplied; + } + updateXmlNodeAttribute(xmlDomNode) { + let isSubstitutionApplied = false; + if (envVarUtility.isEmpty(xmlDomNode) || !envVarUtility.isObject(xmlDomNode) || xmlDomNode.name == "#comment") { + core.debug("Provided node is empty or a comment."); + return isSubstitutionApplied; + } + const ConfigFileAppSettingsToken = 'CONFIG_FILE_SETTINGS_TOKEN'; + let xmlDomNodeAttributes = xmlDomNode.attrs; + for (var attributeName in xmlDomNodeAttributes) { + var attributeNameValue = (attributeName === "key" || attributeName == "name") ? xmlDomNodeAttributes[attributeName] : attributeName; + var attributeName = (attributeName === "key" || attributeName == "name") ? "value" : attributeName; + if (this.variableMap.get(attributeNameValue) != undefined) { + let ConfigFileAppSettingsTokenName = ConfigFileAppSettingsToken + '(' + attributeNameValue + ')'; + let isValueReplaced = false; + if (xmlDomNode.getAttr(attributeName) != undefined) { + console.log(`Updating value for key: ${attributeNameValue} with token value: ${ConfigFileAppSettingsTokenName}`); + xmlDomNode.attr(attributeName, ConfigFileAppSettingsTokenName); + isValueReplaced = true; + } + else { + let children = xmlDomNode.children; + for (var childNode of children) { + if (envVarUtility.isObject(childNode) && childNode.name == attributeName) { + if (childNode.children.length === 1) { + console.log(`Updating value for key: ${attributeNameValue} with token value: ${ConfigFileAppSettingsTokenName}`); + childNode.children[0] = ConfigFileAppSettingsTokenName; + isValueReplaced = true; + } + } + } + } + if (isValueReplaced) { + this.replacableTokenValues[ConfigFileAppSettingsTokenName] = this.variableMap.get(attributeNameValue).replace(/"/g, "'"); + isSubstitutionApplied = true; + } + } + } + let children = xmlDomNode.children; + for (var childNode of children) { + if (envVarUtility.isObject(childNode)) { + isSubstitutionApplied = this.updateXmlNodeAttribute(childNode) || isSubstitutionApplied; + } + } + return isSubstitutionApplied; + } + updateXmlConnectionStringsNodeAttribute(xmlDomNode) { + let isSubstitutionApplied = false; + const ConfigFileConnStringToken = 'CONFIG_FILE_CONN_STRING_TOKEN'; + if (envVarUtility.isEmpty(xmlDomNode) || !envVarUtility.isObject(xmlDomNode) || xmlDomNode.name == "#comment") { + core.debug("Provided node is empty or a comment."); + return isSubstitutionApplied; + } + let xmlDomNodeAttributes = xmlDomNode.attrs; + if (xmlDomNodeAttributes.hasOwnProperty("connectionString")) { + if (xmlDomNodeAttributes.hasOwnProperty("name") && this.variableMap.get(xmlDomNodeAttributes.name)) { + let ConfigFileConnStringTokenName = ConfigFileConnStringToken + '(' + xmlDomNodeAttributes.name + ')'; + core.debug(`Substituting connectionString value for connectionString= ${xmlDomNodeAttributes.name} with token value: ${ConfigFileConnStringTokenName}`); + xmlDomNode.attr("connectionString", ConfigFileConnStringTokenName); + this.replacableTokenValues[ConfigFileConnStringTokenName] = this.variableMap.get(xmlDomNodeAttributes.name).replace(/"/g, "'"); + isSubstitutionApplied = true; + } + else if (this.variableMap.get("connectionString") != undefined) { + let ConfigFileConnStringTokenName = ConfigFileConnStringToken + '(connectionString)'; + core.debug(`Substituting connectionString value for connectionString= ${xmlDomNodeAttributes.name} with token value: ${ConfigFileConnStringTokenName}`); + xmlDomNode.attr("connectionString", ConfigFileConnStringTokenName); + this.replacableTokenValues[ConfigFileConnStringTokenName] = this.variableMap.get("connectionString").replace(/"/g, "'"); + isSubstitutionApplied = true; + } + } + let children = xmlDomNode.children; + for (var childNode of children) { + if (envVarUtility.isObject(childNode)) { + isSubstitutionApplied = this.updateXmlConnectionStringsNodeAttribute(childNode) || isSubstitutionApplied; + } + } + return isSubstitutionApplied; + } +} +exports.XmlSubstitution = XmlSubstitution; diff --git a/lib/variableSubstitution.js b/lib/variableSubstitution.js index 85f6be36..c17914f8 100644 --- a/lib/variableSubstitution.js +++ b/lib/variableSubstitution.js @@ -1,221 +1,222 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const core = require("@actions/core"); -const envVariableUtility_1 = require("./operations/envVariableUtility"); -const jsonVariableSubstitutionUtility_1 = require("./operations/jsonVariableSubstitutionUtility"); -const xmlDomUtility_1 = require("./operations/xmlDomUtility"); -const xmlVariableSubstitution_1 = require("./operations/xmlVariableSubstitution"); -const utility_1 = require("./operations/utility"); -const fs = require("fs"); -const yaml = require("js-yaml"); -const fileEncoding = require("./operations/fileEncodingUtility"); -class VariableSubstitution { - constructor() { - this.fileContentCache = new Map(); - this.parseException = ""; - } - run() { - return __awaiter(this, void 0, void 0, function* () { - let filesInput = core.getInput("files", { required: true }); - let files = filesInput.split(","); - if (files.length > 0) { - this.segregateFilesAndSubstitute(files); - } - else { - throw Error('File Tranformation is not enabled. Please provide JSON/XML or YAML target files for variable substitution.'); - } - }); - } - segregateFilesAndSubstitute(files) { - let isSubstitutionApplied = false; - for (let file of files) { - let matchedFiles = utility_1.findfiles(file.trim()); - if (matchedFiles.length == 0) { - core.error('No file matched with specific pattern: ' + file); - continue; - } - for (let file of matchedFiles) { - let fileBuffer = fs.readFileSync(file); - let fileEncodeType = fileEncoding.detectFileEncoding(file, fileBuffer); - let fileContent = fileBuffer.toString(fileEncodeType.encoding); - if (fileEncodeType.withBOM) { - fileContent = fileContent.slice(1); - } - if (this.isJson(file, fileContent)) { - console.log("Applying variable substitution on JSON file: " + file); - let jsonSubsitution = new jsonVariableSubstitutionUtility_1.JsonSubstitution(); - let jsonObject = this.fileContentCache.get(file); - let isJsonSubstitutionApplied = jsonSubsitution.substituteJsonVariable(jsonObject, envVariableUtility_1.EnvTreeUtility.getEnvVarTree()); - if (isJsonSubstitutionApplied) { - fs.writeFileSync(file, (fileEncodeType.withBOM ? '\uFEFF' : '') + JSON.stringify(jsonObject, null, 4), { encoding: fileEncodeType.encoding }); - console.log(`Successfully updated file: ${file}`); - } - else { - console.log('Skipped updating file: ' + file); - } - isSubstitutionApplied = isJsonSubstitutionApplied || isSubstitutionApplied; - } - else if (this.isXml(file, fileContent)) { - console.log("Applying variable substitution on XML file: " + file); - let xmlDomUtilityInstance = this.fileContentCache.get(file); - let xmlSubstitution = new xmlVariableSubstitution_1.XmlSubstitution(xmlDomUtilityInstance); - let isXmlSubstitutionApplied = xmlSubstitution.substituteXmlVariables(); - if (isXmlSubstitutionApplied) { - let xmlDocument = xmlDomUtilityInstance.getXmlDom(); - this.replaceEscapeXMLCharacters(xmlDocument); - let domContent = (fileEncodeType.withBOM ? '\uFEFF' : '') + xmlDomUtilityInstance.getContentWithHeader(xmlDocument); - for (let replacableTokenValue in xmlSubstitution.replacableTokenValues) { - core.debug('Substituting original value in place of temp_name: ' + replacableTokenValue); - domContent = domContent.split(replacableTokenValue).join(xmlSubstitution.replacableTokenValues[replacableTokenValue]); - } - fs.writeFileSync(file, domContent, { encoding: fileEncodeType.encoding }); - console.log(`Successfully updated file: ${file}`); - } - else { - console.log('Skipped updating file: ' + file); - } - isSubstitutionApplied = isXmlSubstitutionApplied || isSubstitutionApplied; - } - else if (this.isYaml(file, fileContent)) { - console.log("Applying variable substitution on YAML file: " + file); - let jsonSubsitution = new jsonVariableSubstitutionUtility_1.JsonSubstitution(); - let yamlObject = this.fileContentCache.get(file); - let isYamlSubstitutionApplied = jsonSubsitution.substituteJsonVariable(yamlObject, envVariableUtility_1.EnvTreeUtility.getEnvVarTree()); - if (isYamlSubstitutionApplied) { - fs.writeFileSync(file, (fileEncodeType.withBOM ? '\uFEFF' : '') + yaml.safeDump(yamlObject), { encoding: fileEncodeType.encoding }); - console.log(`Successfully updated config file: ${file}`); - } - else { - console.log('Skipped updating file: ' + file); - } - isSubstitutionApplied = isYamlSubstitutionApplied || isSubstitutionApplied; - } - else { - throw new Error("Could not parse file: " + file + "\n" + this.parseException); - } - } - } - if (!isSubstitutionApplied) { - throw new Error("Failed to apply variable substitution"); - } - } - isJson(file, content) { - try { - content = this.stripJsonComments(content); - let jsonObject = JSON.parse(content); - if (!this.fileContentCache.has(file)) { - this.fileContentCache.set(file, jsonObject); - } - return true; - } - catch (exception) { - this.parseException += "JSON parse error: " + exception + "\n"; - return false; - } - } - isYaml(file, content) { - try { - let yamlObject = yaml.safeLoad(content); - if (!this.fileContentCache.has(file)) { - this.fileContentCache.set(file, yamlObject); - } - return true; - } - catch (exception) { - this.parseException += "YAML parse error: " + exception + "\n"; - return false; - } - } - isXml(file, content) { - try { - let ltxDomUtiltiyInstance = new xmlDomUtility_1.XmlDomUtility(content); - if (!this.fileContentCache.has(file)) { - this.fileContentCache.set(file, ltxDomUtiltiyInstance); - } - return true; - } - catch (exception) { - this.parseException += "XML parse error: " + exception; - return false; - } - } - stripJsonComments(content) { - if (!content || (content.indexOf("//") < 0 && content.indexOf("/*") < 0)) { - return content; - } - var currentChar; - var nextChar; - var insideQuotes = false; - var contentWithoutComments = ''; - var insideComment = 0; - var singlelineComment = 1; - var multilineComment = 2; - for (var i = 0; i < content.length; i++) { - currentChar = content[i]; - nextChar = i + 1 < content.length ? content[i + 1] : ""; - if (insideComment) { - if (insideComment == singlelineComment && (currentChar + nextChar === '\r\n' || currentChar === '\n')) { - i--; - insideComment = 0; - continue; - } - if (insideComment == multilineComment && currentChar + nextChar === '*/') { - i++; - insideComment = 0; - continue; - } - } - else { - if (insideQuotes && currentChar == "\\") { - contentWithoutComments += currentChar + nextChar; - i++; // Skipping checks for next char if escaped - continue; - } - else { - if (currentChar == '"') { - insideQuotes = !insideQuotes; - } - if (!insideQuotes) { - if (currentChar + nextChar === '//') { - insideComment = singlelineComment; - i++; - } - if (currentChar + nextChar === '/*') { - insideComment = multilineComment; - i++; - } - } - } - } - if (!insideComment) { - contentWithoutComments += content[i]; - } - } - return contentWithoutComments; - } - replaceEscapeXMLCharacters(xmlDOMNode) { - if (!xmlDOMNode || typeof xmlDOMNode == 'string') { - return; - } - for (var xmlAttribute in xmlDOMNode.attrs) { - xmlDOMNode.attrs[xmlAttribute] = xmlDOMNode.attrs[xmlAttribute].replace(/'/g, "APOS_CHARACTER_TOKEN"); - } - for (var xmlChild of xmlDOMNode.children) { - this.replaceEscapeXMLCharacters(xmlChild); - } - } -} -exports.VariableSubstitution = VariableSubstitution; -let varSub = new VariableSubstitution(); -varSub.run().catch((error) => { - core.setFailed(error); -}); +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const core = require("@actions/core"); +const envVariableUtility_1 = require("./operations/envVariableUtility"); +const jsonVariableSubstitutionUtility_1 = require("./operations/jsonVariableSubstitutionUtility"); +const xmlDomUtility_1 = require("./operations/xmlDomUtility"); +const xmlVariableSubstitution_1 = require("./operations/xmlVariableSubstitution"); +const utility_1 = require("./operations/utility"); +const fs = require("fs"); +const yaml = require("js-yaml"); +const fileEncoding = require("./operations/fileEncodingUtility"); +class VariableSubstitution { + constructor() { + this.fileContentCache = new Map(); + this.parseException = ""; + } + run() { + return __awaiter(this, void 0, void 0, function* () { + let splitChar = core.getInput("splitChar") || "."; + let filesInput = core.getInput("files", { required: true }); + let files = filesInput.split(","); + if (files.length > 0) { + this.segregateFilesAndSubstitute(files, splitChar); + } + else { + throw Error('File Tranformation is not enabled. Please provide JSON/XML or YAML target files for variable substitution.'); + } + }); + } + segregateFilesAndSubstitute(files, splitChar = '.') { + let isSubstitutionApplied = false; + for (let file of files) { + let matchedFiles = utility_1.findfiles(file.trim()); + if (matchedFiles.length == 0) { + core.error('No file matched with specific pattern: ' + file); + continue; + } + for (let file of matchedFiles) { + let fileBuffer = fs.readFileSync(file); + let fileEncodeType = fileEncoding.detectFileEncoding(file, fileBuffer); + let fileContent = fileBuffer.toString(fileEncodeType.encoding); + if (fileEncodeType.withBOM) { + fileContent = fileContent.slice(1); + } + if (this.isJson(file, fileContent)) { + console.log("Applying variable substitution on JSON file: " + file); + let jsonSubsitution = new jsonVariableSubstitutionUtility_1.JsonSubstitution(); + let jsonObject = this.fileContentCache.get(file); + let isJsonSubstitutionApplied = jsonSubsitution.substituteJsonVariable(jsonObject, envVariableUtility_1.EnvTreeUtility.getEnvVarTree(splitChar)); + if (isJsonSubstitutionApplied) { + fs.writeFileSync(file, (fileEncodeType.withBOM ? '\uFEFF' : '') + JSON.stringify(jsonObject, null, 4), { encoding: fileEncodeType.encoding }); + console.log(`Successfully updated file: ${file}`); + } + else { + console.log('Skipped updating file: ' + file); + } + isSubstitutionApplied = isJsonSubstitutionApplied || isSubstitutionApplied; + } + else if (this.isXml(file, fileContent)) { + console.log("Applying variable substitution on XML file: " + file); + let xmlDomUtilityInstance = this.fileContentCache.get(file); + let xmlSubstitution = new xmlVariableSubstitution_1.XmlSubstitution(xmlDomUtilityInstance); + let isXmlSubstitutionApplied = xmlSubstitution.substituteXmlVariables(); + if (isXmlSubstitutionApplied) { + let xmlDocument = xmlDomUtilityInstance.getXmlDom(); + this.replaceEscapeXMLCharacters(xmlDocument); + let domContent = (fileEncodeType.withBOM ? '\uFEFF' : '') + xmlDomUtilityInstance.getContentWithHeader(xmlDocument); + for (let replacableTokenValue in xmlSubstitution.replacableTokenValues) { + core.debug('Substituting original value in place of temp_name: ' + replacableTokenValue); + domContent = domContent.split(replacableTokenValue).join(xmlSubstitution.replacableTokenValues[replacableTokenValue]); + } + fs.writeFileSync(file, domContent, { encoding: fileEncodeType.encoding }); + console.log(`Successfully updated file: ${file}`); + } + else { + console.log('Skipped updating file: ' + file); + } + isSubstitutionApplied = isXmlSubstitutionApplied || isSubstitutionApplied; + } + else if (this.isYaml(file, fileContent)) { + console.log("Applying variable substitution on YAML file: " + file); + let jsonSubsitution = new jsonVariableSubstitutionUtility_1.JsonSubstitution(); + let yamlObject = this.fileContentCache.get(file); + let isYamlSubstitutionApplied = jsonSubsitution.substituteJsonVariable(yamlObject, envVariableUtility_1.EnvTreeUtility.getEnvVarTree(splitChar)); + if (isYamlSubstitutionApplied) { + fs.writeFileSync(file, (fileEncodeType.withBOM ? '\uFEFF' : '') + yaml.safeDump(yamlObject), { encoding: fileEncodeType.encoding }); + console.log(`Successfully updated config file: ${file}`); + } + else { + console.log('Skipped updating file: ' + file); + } + isSubstitutionApplied = isYamlSubstitutionApplied || isSubstitutionApplied; + } + else { + throw new Error("Could not parse file: " + file + "\n" + this.parseException); + } + } + } + if (!isSubstitutionApplied) { + throw new Error("Failed to apply variable substitution"); + } + } + isJson(file, content) { + try { + content = this.stripJsonComments(content); + let jsonObject = JSON.parse(content); + if (!this.fileContentCache.has(file)) { + this.fileContentCache.set(file, jsonObject); + } + return true; + } + catch (exception) { + this.parseException += "JSON parse error: " + exception + "\n"; + return false; + } + } + isYaml(file, content) { + try { + let yamlObject = yaml.safeLoad(content); + if (!this.fileContentCache.has(file)) { + this.fileContentCache.set(file, yamlObject); + } + return true; + } + catch (exception) { + this.parseException += "YAML parse error: " + exception + "\n"; + return false; + } + } + isXml(file, content) { + try { + let ltxDomUtiltiyInstance = new xmlDomUtility_1.XmlDomUtility(content); + if (!this.fileContentCache.has(file)) { + this.fileContentCache.set(file, ltxDomUtiltiyInstance); + } + return true; + } + catch (exception) { + this.parseException += "XML parse error: " + exception; + return false; + } + } + stripJsonComments(content) { + if (!content || (content.indexOf("//") < 0 && content.indexOf("/*") < 0)) { + return content; + } + var currentChar; + var nextChar; + var insideQuotes = false; + var contentWithoutComments = ''; + var insideComment = 0; + var singlelineComment = 1; + var multilineComment = 2; + for (var i = 0; i < content.length; i++) { + currentChar = content[i]; + nextChar = i + 1 < content.length ? content[i + 1] : ""; + if (insideComment) { + if (insideComment == singlelineComment && (currentChar + nextChar === '\r\n' || currentChar === '\n')) { + i--; + insideComment = 0; + continue; + } + if (insideComment == multilineComment && currentChar + nextChar === '*/') { + i++; + insideComment = 0; + continue; + } + } + else { + if (insideQuotes && currentChar == "\\") { + contentWithoutComments += currentChar + nextChar; + i++; // Skipping checks for next char if escaped + continue; + } + else { + if (currentChar == '"') { + insideQuotes = !insideQuotes; + } + if (!insideQuotes) { + if (currentChar + nextChar === '//') { + insideComment = singlelineComment; + i++; + } + if (currentChar + nextChar === '/*') { + insideComment = multilineComment; + i++; + } + } + } + } + if (!insideComment) { + contentWithoutComments += content[i]; + } + } + return contentWithoutComments; + } + replaceEscapeXMLCharacters(xmlDOMNode) { + if (!xmlDOMNode || typeof xmlDOMNode == 'string') { + return; + } + for (var xmlAttribute in xmlDOMNode.attrs) { + xmlDOMNode.attrs[xmlAttribute] = xmlDOMNode.attrs[xmlAttribute].replace(/'/g, "APOS_CHARACTER_TOKEN"); + } + for (var xmlChild of xmlDOMNode.children) { + this.replaceEscapeXMLCharacters(xmlChild); + } + } +} +exports.VariableSubstitution = VariableSubstitution; +let varSub = new VariableSubstitution(); +varSub.run().catch((error) => { + core.setFailed(error); +}); diff --git a/src/Tests/jsonVariableSubstitution.test.ts b/src/Tests/jsonVariableSubstitution.test.ts index 2e7b3b84..e98273e6 100644 --- a/src/Tests/jsonVariableSubstitution.test.ts +++ b/src/Tests/jsonVariableSubstitution.test.ts @@ -7,8 +7,9 @@ import { JsonSubstitution } from "../operations/jsonVariableSubstitutionUtility" var expect = chai.expect; -describe('Test JSON Variable Substitution', () => { +describe('Test JSON Variable Substitution with period', () => { var jsonObject, isApplied; + var splitChar = '.'; before(() => { let stub = sinon.stub(EnvTreeUtility, "getEnvVarTree").callsFake(() => { @@ -27,7 +28,7 @@ describe('Test JSON Variable Substitution', () => { [ 'profile.enabled', 'false'], [ 'profile.version', '1173'], [ 'profile.somefloat', '97.75'], - [ 'profile.preimum_level', '{"suaggar": "V4", "rok": "V5", "asranja": { "type" : "V6"}}'] + [ 'profile.premium_level', '{"suaggar": "V4", "rok": "V5", "asranja": { "type" : "V6"}}'] ]); let envVarTree = { value: null, @@ -39,17 +40,18 @@ describe('Test JSON Variable Substitution', () => { for(let [key, value] of envVariables.entries()) { if(!isPredefinedVariable(key)) { let envVarTreeIterator = envVarTree; - let envVariableNameArray = key.split('.'); + let envVariableNameArray = key.split(splitChar); for(let variableName of envVariableNameArray) { - if(envVarTreeIterator.child[variableName] === undefined || typeof envVarTreeIterator.child[variableName] === 'function') { - envVarTreeIterator.child[variableName] = { + const jsonObjectKeyChild = envVarTreeIterator.child[variableName.toUpperCase()]; + if(jsonObjectKeyChild === undefined || typeof jsonObjectKeyChild === 'function') { + envVarTreeIterator.child[variableName.toUpperCase()] = { value: null, isEnd: false, child: {} }; } - envVarTreeIterator = envVarTreeIterator.child[variableName]; + envVarTreeIterator = envVarTreeIterator.child[variableName.toUpperCase()]; } envVarTreeIterator.isEnd = true; envVarTreeIterator.value = value; @@ -81,7 +83,7 @@ describe('Test JSON Variable Substitution', () => { }, 'profile': { 'users': ['arjgupta', 'raagra', 'muthuk'], - 'preimum_level': { + 'premium_level': { 'arjgupta': 'V1', 'raagra': 'V2', 'muthuk': { @@ -95,7 +97,7 @@ describe('Test JSON Variable Substitution', () => { }; let jsonSubsitution = new JsonSubstitution(); - isApplied = jsonSubsitution.substituteJsonVariable(jsonObject, EnvTreeUtility.getEnvVarTree()); + isApplied = jsonSubsitution.substituteJsonVariable(jsonObject, EnvTreeUtility.getEnvVarTree(splitChar)); stub.restore(); }); @@ -117,8 +119,145 @@ describe('Test JSON Variable Substitution', () => { expect(jsonObject['&pl']['ch@r@cter.k^y']).to.equal('*.config'); }); - it("Validate case sensitive variables", () => { - expect(jsonObject['User.Profile']).to.equal('do_not_replace'); + it("Validate inbuilt JSON attributes substitution", () => { + expect(jsonObject['constructor.name']).to.equal('newConstructorName'); + expect(jsonObject['constructor']['name']).to.equal('newConstructorName'); + expect(jsonObject['constructor']['valueOf']).to.equal('constructorNewValue'); + }); + + it("Validate Array Object", () => { + expect(jsonObject['profile']['users'].length).to.equal(4); + let newArray = ["suaggar", "rok", "asranja", "chaitanya"]; + expect(jsonObject['profile']['users']).to.deep.equal(newArray); + }); + + it("Validate Boolean", () => { + expect(jsonObject['profile']['enabled']).to.equal(false); + }); + + it("Validate Number(float)", () => { + expect(jsonObject['profile']['somefloat']).to.equal(97.75); + }); + + it("Validate Number(int)", () => { + expect(jsonObject['profile']['version']).to.equal(1173); + }); + + it("Validate Object", () => { + expect(jsonObject['profile']['premium_level']).to.deep.equal({"suaggar": "V4", "rok": "V5", "asranja": { "type" : "V6"}}); + }); +}); + +describe('Test JSON Variable Substition with underscores', () => { + var jsonObject, isApplied; + var splitChar = '__'; + + before(() => { + let stub = sinon.stub(EnvTreeUtility, "getEnvVarTree").callsFake(() => { + let envVariables = new Map([ + [ 'system.debug', 'true'], + [ 'data__ConnectionString', 'database_connection'], + [ 'data__userName', 'db_admin'], + [ 'data__password', 'db_pass'], + [ '&pl__ch@r@cter__k^y', '*.config'], + [ 'build__sourceDirectory', 'DefaultWorkingDirectory'], + [ 'user__profile__name__first', 'firstName'], + [ 'user__profile', 'replace_all'], + [ 'constructor__name', 'newConstructorName'], + [ 'constructor__valueOf', 'constructorNewValue'], + [ 'profile__users', '["suaggar","rok","asranja", "chaitanya"]'], + [ 'profile__enabled', 'false'], + [ 'profile__version', '1173'], + [ 'profile__somefloat', '97.75'], + [ 'profile__premium_level', '{"suaggar": "V4", "rok": "V5", "asranja": { "type" : "V6"}}'] + ]); + let envVarTree = { + value: null, + isEnd: false, + child: { + '__proto__': null + } + }; + for(let [key, value] of envVariables.entries()) { + if(!isPredefinedVariable(key)) { + let envVarTreeIterator = envVarTree; + let envVariableNameArray = key.split(splitChar); + + for(let variableName of envVariableNameArray) { + const jsonObjectKeyChild = envVarTreeIterator.child[variableName.toUpperCase()]; + if(jsonObjectKeyChild === undefined || typeof jsonObjectKeyChild === 'function') { + envVarTreeIterator.child[variableName.toUpperCase()] = { + value: null, + isEnd: false, + child: {} + }; + } + envVarTreeIterator = envVarTreeIterator.child[variableName.toUpperCase()]; + } + envVarTreeIterator.isEnd = true; + envVarTreeIterator.value = value; + } + } + return envVarTree; + }); + + jsonObject = { + 'User.Profile': 'do_not_replace', + 'data': { + 'ConnectionString' : 'connect_string', + 'userName': 'name', + 'password': 'pass' + }, + '&pl': { + 'ch@r@cter.k^y': 'v@lue' + }, + 'system': { + 'debug' : 'no_change' + }, + 'user.profile': { + 'name.first' : 'fname' + }, + 'constructor.name': 'myconstructorname', + 'constructor': { + 'name': 'myconstructorname', + 'valueOf': 'myconstructorvalue' + }, + 'profile': { + 'users': ['arjgupta', 'raagra', 'muthuk'], + 'premium_level': { + 'arjgupta': 'V1', + 'raagra': 'V2', + 'muthuk': { + 'type': 'V3' + } + }, + "enabled": true, + "version": 2, + "somefloat": 2.3456 + } + }; + + let jsonSubsitution = new JsonSubstitution(); + isApplied = jsonSubsitution.substituteJsonVariable(jsonObject, EnvTreeUtility.getEnvVarTree(splitChar)); + stub.restore(); + }); + + it("Should substitute", () => { + console.log(JSON.stringify(jsonObject)); + expect(isApplied).to.equal(true); + }); + + it("Validate simple string change", () => { + expect(jsonObject['data']['ConnectionString']).to.equal('database_connection'); + expect(jsonObject['data']['userName']).to.equal('db_admin'); + }); + + it("Validate system variable elimination", () => { + expect(jsonObject['system']['debug']).to.equal('no_change'); + }); + + it("Validate special variables", () => { + expect(jsonObject['&pl']['ch@r@cter.k^y']).to.equal('*.config'); }); it("Validate inbuilt JSON attributes substitution", () => { @@ -146,6 +285,6 @@ describe('Test JSON Variable Substitution', () => { }); it("Validate Object", () => { - expect(jsonObject['profile']['preimum_level']).to.deep.equal({"suaggar": "V4", "rok": "V5", "asranja": { "type" : "V6"}}); + expect(jsonObject['profile']['premium_level']).to.deep.equal({"suaggar": "V4", "rok": "V5", "asranja": { "type" : "V6"}}); }); }); \ No newline at end of file diff --git a/src/operations/envVariableUtility.ts b/src/operations/envVariableUtility.ts index 7f57113a..ac8df1b6 100644 --- a/src/operations/envVariableUtility.ts +++ b/src/operations/envVariableUtility.ts @@ -13,7 +13,7 @@ export function getVariableMap() { let variables = process.env; Object.keys(variables).forEach(key => { if(!isPredefinedVariable(key)) { - variableMap.set(key, variables[key]); + variableMap.set(key.toUpperCase(), variables[key]); } }); return variableMap; @@ -40,16 +40,16 @@ interface varTreeNode { export class EnvTreeUtility { - public static getEnvVarTree() { + public static getEnvVarTree(splitChar: string) { let util = new EnvTreeUtility(); if(!util.envVarTree) { - util.envVarTree = util.createEnvTree(getVariableMap()); + util.envVarTree = util.createEnvTree(getVariableMap(), splitChar); } return util.envVarTree; } - private createEnvTree(envVariables): varTreeNode { + private createEnvTree(envVariables, splitChar: string): varTreeNode { // __proto__ is marked as null, so that custom object can be assgined. // This replacement do not affect the JSON object, as no inbuilt JSON function is referenced. let envVarTree: varTreeNode = { @@ -61,7 +61,7 @@ export class EnvTreeUtility { }; for(let [key, value] of envVariables.entries()) { let envVarTreeIterator = envVarTree; - let envVariableNameArray = key.split('.'); + let envVariableNameArray = key.split(splitChar); for(let variableName of envVariableNameArray) { if(envVarTreeIterator.child[variableName] === undefined || typeof envVarTreeIterator.child[variableName] === 'function') { @@ -83,10 +83,12 @@ export class EnvTreeUtility { if(index == jsonObjectKeyLength) { return envVarTree; } - if(envVarTree.child[ jsonObjectKey[index] ] === undefined || typeof envVarTree.child[ jsonObjectKey[index] ] === 'function') { + + const jsonObjectKeyChild = envVarTree.child[ jsonObjectKey[index].toUpperCase() ]; + if(jsonObjectKeyChild === undefined || typeof jsonObjectKeyChild === 'function') { return undefined; } - return this.checkEnvTreePath(jsonObjectKey, index + 1, jsonObjectKeyLength, envVarTree.child[ jsonObjectKey[index] ]); + return this.checkEnvTreePath(jsonObjectKey, ++index, jsonObjectKeyLength, jsonObjectKeyChild); } private envVarTree: varTreeNode = null; diff --git a/src/operations/jsonVariableSubstitutionUtility.ts b/src/operations/jsonVariableSubstitutionUtility.ts index eb39abb1..de1323d4 100644 --- a/src/operations/jsonVariableSubstitutionUtility.ts +++ b/src/operations/jsonVariableSubstitutionUtility.ts @@ -5,12 +5,14 @@ import { EnvTreeUtility } from "./envVariableUtility"; export class JsonSubstitution { constructor() { this.envTreeUtil = new EnvTreeUtility(); + this.splitChar = core.getInput("splitChar") || '.'; } substituteJsonVariable(jsonObject, envObject) { + let isValueChanged: boolean = false; for(let jsonChild in jsonObject) { - let jsonChildArray = jsonChild.split('.'); + let jsonChildArray = jsonChild.split(this.splitChar); let resultNode = this.envTreeUtil.checkEnvTreePath(jsonChildArray, 0, jsonChildArray.length, envObject); if(resultNode != undefined) { if(resultNode.isEnd) { @@ -51,4 +53,5 @@ export class JsonSubstitution { } private envTreeUtil: EnvTreeUtility; + private splitChar: string; } \ No newline at end of file diff --git a/src/variableSubstitution.ts b/src/variableSubstitution.ts index 44cc76e4..ad24b549 100644 --- a/src/variableSubstitution.ts +++ b/src/variableSubstitution.ts @@ -12,17 +12,18 @@ import fileEncoding = require('./operations/fileEncodingUtility'); export class VariableSubstitution { async run() { + let splitChar = core.getInput("splitChar") || "."; let filesInput = core.getInput("files", { required: true }); let files = filesInput.split(","); if(files.length > 0){ - this.segregateFilesAndSubstitute(files); + this.segregateFilesAndSubstitute(files, splitChar); } else { throw Error('File Tranformation is not enabled. Please provide JSON/XML or YAML target files for variable substitution.'); } } - segregateFilesAndSubstitute(files: string[]) { + segregateFilesAndSubstitute(files: string[], splitChar: string = '.') { let isSubstitutionApplied: boolean = false; for(let file of files){ let matchedFiles = findfiles(file.trim()); @@ -41,7 +42,7 @@ export class VariableSubstitution { console.log("Applying variable substitution on JSON file: " + file); let jsonSubsitution = new JsonSubstitution(); let jsonObject = this.fileContentCache.get(file); - let isJsonSubstitutionApplied = jsonSubsitution.substituteJsonVariable(jsonObject, EnvTreeUtility.getEnvVarTree()); + let isJsonSubstitutionApplied = jsonSubsitution.substituteJsonVariable(jsonObject, EnvTreeUtility.getEnvVarTree(splitChar)); if(isJsonSubstitutionApplied) { fs.writeFileSync(file, (fileEncodeType.withBOM ? '\uFEFF' : '') + JSON.stringify(jsonObject, null, 4), { encoding: fileEncodeType.encoding }); console.log(`Successfully updated file: ${file}`); @@ -76,7 +77,7 @@ export class VariableSubstitution { console.log("Applying variable substitution on YAML file: " + file); let jsonSubsitution = new JsonSubstitution(); let yamlObject = this.fileContentCache.get(file); - let isYamlSubstitutionApplied = jsonSubsitution.substituteJsonVariable(yamlObject, EnvTreeUtility.getEnvVarTree()); + let isYamlSubstitutionApplied = jsonSubsitution.substituteJsonVariable(yamlObject, EnvTreeUtility.getEnvVarTree(splitChar)); if(isYamlSubstitutionApplied) { fs.writeFileSync(file, (fileEncodeType.withBOM ? '\uFEFF' : '') + yaml.safeDump(yamlObject), { encoding: fileEncodeType.encoding }); console.log(`Successfully updated config file: ${file}`);