From 90bdb355729abf3dfca2da3c06d9161825b920ad Mon Sep 17 00:00:00 2001 From: Howard Richards Date: Mon, 23 Mar 2026 10:30:30 +0000 Subject: [PATCH 1/2] Added sln and github instructions from copilot --- .github/copilot-instructions.md | 68 +++++++++++++++++++++++++++++++++ versionReaderTask.sln | 46 ++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 .github/copilot-instructions.md create mode 100644 versionReaderTask.sln diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..2228fba --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,68 @@ +# Copilot Instructions — VersionReaderTask + +## What this project is + +An Azure DevOps pipeline extension (VSIX) that reads version tags (``, ``, ``, etc.) from SDK-format `.csproj`/`.vbproj` files and exposes them as pipeline build variables. The task is written in TypeScript targeting Node 20. + +## Repository layout + +- `vss-extension.json` — Extension manifest (publisher, version, contribution metadata) +- `VersionReaderTask/` — The actual pipeline task (self-contained Node package) + - `index.ts` / `index.js` — Task entry point; instantiates `versionReader` and calls `execute()` + - `utils.ts` / `utils.js` — All business logic: file matching, XML parsing, variable setting + - `task.json` — Azure Pipelines task definition (inputs, execution runtime) + - `tests/` — Mocha test suite; uses `azure-pipelines-task-lib/mock-run` and `mock-test` +- `package.cmd` — Packages the VSIX using `tfx extension create` + +## Build, test, and lint + +All commands run inside `VersionReaderTask/`: + +```bash +cd VersionReaderTask + +# Compile TypeScript (output .js files sit alongside .ts sources — checked in) +npx tsc + +# Run full test suite +npm test +# equivalent: npx mocha ./tests/_suite.js + +# Run a single test file directly +npx mocha --require ./tests/_suite.js # all tests loaded via _suite.js +``` + +> **Important:** The compiled `.js` files are committed alongside `.ts` sources. After editing any `.ts` file, recompile with `npx tsc` before running tests or packaging. + +## Key conventions + +### Two-package structure +The root `package.json` is minimal (extension tooling only). The task's own `package.json` and `node_modules` live inside `VersionReaderTask/`. Install dependencies with `npm install` inside `VersionReaderTask/`, not the root. + +### Version tag priority +`getFirstMatch()` in `utils.ts` resolves the version in this order: +1. `` +2. `` +3. `` +4. Falls back to `"1.0.0"` if none are found + +### Output variables +The task sets these Azure Pipelines variables: +- `VERSION_BUILD` — version + build separator + `BUILD_BUILDID` (e.g. `1.2.3.5678`) +- `Version`, `AssemblyVersion`, `VersionPrefix`, `VersionSuffix`, `PackageVersion`, `FileVersion` — raw values from the project file + +If `variablesPrefix` is set (e.g. `DEMO`), all variable names are prefixed: `DEMO_VERSION_BUILD`, `DEMO_Version`, etc. + +### Testing pattern +Each integration test has a dedicated mock runner file in `tests/` (e.g. `runVersion.ts`). These use `TaskMockRunner` to mock `findMatch` answers and task inputs. The `_suite.ts` file imports `utilsTest` directly (unit tests) then registers integration tests via `MockTestRunner`. Tests assert against `##vso[task.setvariable ...]` lines in stdout using `assertSet()`. + +Test fixture `.csproj` files live in `VersionReaderTask/tests/` (`Version.csproj`, `VersionMissing.csproj`, `VersionPrefix.csproj`). + +### Extension packaging +```cmd +package.cmd +``` +This deletes a conflicting markdown file from `xpath/docs/`, then runs `tfx extension create --manifest-globs vss-extension.json`. Requires `tfx-cli` installed globally. + +### TypeScript config +Targets ES6, `module: NodeNext`, strict mode enabled. `esModuleInterop: true` is required for the `xmldom` default import (`import xdom from 'xmldom'`). diff --git a/versionReaderTask.sln b/versionReaderTask.sln new file mode 100644 index 0000000..790b855 --- /dev/null +++ b/versionReaderTask.sln @@ -0,0 +1,46 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VersionReaderTask", "VersionReaderTask", "{923A6E6F-83A8-8F8D-0416-14AB5C155999}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1702CF7F-B540-65EB-D5BB-5FEB249AEB06}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Version", "VersionReaderTask\tests\Version.csproj", "{1739FCA4-2B3B-DCE1-0F16-0D6DC31C8584}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersionMissing", "VersionReaderTask\tests\VersionMissing.csproj", "{B41FDE83-C4F5-4E27-E90E-346841FF382C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersionPrefix", "VersionReaderTask\tests\VersionPrefix.csproj", "{F711B8ED-E21D-605D-3C12-607D52A35573}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1739FCA4-2B3B-DCE1-0F16-0D6DC31C8584}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1739FCA4-2B3B-DCE1-0F16-0D6DC31C8584}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1739FCA4-2B3B-DCE1-0F16-0D6DC31C8584}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1739FCA4-2B3B-DCE1-0F16-0D6DC31C8584}.Release|Any CPU.Build.0 = Release|Any CPU + {B41FDE83-C4F5-4E27-E90E-346841FF382C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B41FDE83-C4F5-4E27-E90E-346841FF382C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B41FDE83-C4F5-4E27-E90E-346841FF382C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B41FDE83-C4F5-4E27-E90E-346841FF382C}.Release|Any CPU.Build.0 = Release|Any CPU + {F711B8ED-E21D-605D-3C12-607D52A35573}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F711B8ED-E21D-605D-3C12-607D52A35573}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F711B8ED-E21D-605D-3C12-607D52A35573}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F711B8ED-E21D-605D-3C12-607D52A35573}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {1702CF7F-B540-65EB-D5BB-5FEB249AEB06} = {923A6E6F-83A8-8F8D-0416-14AB5C155999} + {1739FCA4-2B3B-DCE1-0F16-0D6DC31C8584} = {1702CF7F-B540-65EB-D5BB-5FEB249AEB06} + {B41FDE83-C4F5-4E27-E90E-346841FF382C} = {1702CF7F-B540-65EB-D5BB-5FEB249AEB06} + {F711B8ED-E21D-605D-3C12-607D52A35573} = {1702CF7F-B540-65EB-D5BB-5FEB249AEB06} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A709AAD7-72D7-49D1-A49E-799EC4993AC0} + EndGlobalSection +EndGlobal From 64792c75a3890fcf30e81074b3687ec9645656cd Mon Sep 17 00:00:00 2001 From: Howard Richards Date: Mon, 23 Mar 2026 10:54:08 +0000 Subject: [PATCH 2/2] Used copilot CLI to check and improve tests --- .github/copilot-instructions.md | 2 + VersionReaderTask/.taskkey | 2 +- .../tests/AssemblyVersion.csproj | 7 + .../tests/VersionWhitespace.csproj | 7 + VersionReaderTask/tests/_suite.js | 46 ++++- VersionReaderTask/tests/_suite.ts | 61 +++++- .../tests/runVersionNoBuildPrefix.js | 51 +++++ .../tests/runVersionNoBuildPrefix.ts | 21 ++ VersionReaderTask/tests/utilsTest.js | 154 ++++++++++++++- VersionReaderTask/tests/utilsTest.ts | 187 +++++++++++++++++- VersionReaderTask/utils.js | 2 +- VersionReaderTask/utils.ts | 2 +- test.cmd | 3 + versionReaderTask.sln | 46 ----- 14 files changed, 535 insertions(+), 56 deletions(-) create mode 100644 VersionReaderTask/tests/AssemblyVersion.csproj create mode 100644 VersionReaderTask/tests/VersionWhitespace.csproj create mode 100644 VersionReaderTask/tests/runVersionNoBuildPrefix.js create mode 100644 VersionReaderTask/tests/runVersionNoBuildPrefix.ts create mode 100644 test.cmd delete mode 100644 versionReaderTask.sln diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 2228fba..b2be204 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -14,6 +14,8 @@ An Azure DevOps pipeline extension (VSIX) that reads version tags (``, - `tests/` — Mocha test suite; uses `azure-pipelines-task-lib/mock-run` and `mock-test` - `package.cmd` — Packages the VSIX using `tfx extension create` +Note: the `.csproj` files are test files and should not be confused with actual project files for the task. + ## Build, test, and lint All commands run inside `VersionReaderTask/`: diff --git a/VersionReaderTask/.taskkey b/VersionReaderTask/.taskkey index 0916335..2dffb58 100644 --- a/VersionReaderTask/.taskkey +++ b/VersionReaderTask/.taskkey @@ -1 +1 @@ -4a0b6e2a-5a8f-4057-a52e-c0ab244aefef \ No newline at end of file +2f57d672-ba28-4444-adb7-03212754e2f6 \ No newline at end of file diff --git a/VersionReaderTask/tests/AssemblyVersion.csproj b/VersionReaderTask/tests/AssemblyVersion.csproj new file mode 100644 index 0000000..62a1866 --- /dev/null +++ b/VersionReaderTask/tests/AssemblyVersion.csproj @@ -0,0 +1,7 @@ + + + netstandard2.0 + 2.0.0 + File with only AssemblyVersion tag set + + diff --git a/VersionReaderTask/tests/VersionWhitespace.csproj b/VersionReaderTask/tests/VersionWhitespace.csproj new file mode 100644 index 0000000..3cd9883 --- /dev/null +++ b/VersionReaderTask/tests/VersionWhitespace.csproj @@ -0,0 +1,7 @@ + + + netstandard2.0 + 1.3.0 + File with whitespace around version value + + diff --git a/VersionReaderTask/tests/_suite.js b/VersionReaderTask/tests/_suite.js index 1b96536..c5cc389 100644 --- a/VersionReaderTask/tests/_suite.js +++ b/VersionReaderTask/tests/_suite.js @@ -112,7 +112,7 @@ utilsTests.run(); }); }); (0, mocha_1.it)('Version reverts to 1.0.0 if not found (runVersionMissing.ts)', function (done) { - // reads the Version.csproj file with TEST prefix + // reads the VersionMissing.csproj file with no prefix let tp = path.join(__dirname, 'runVersionMissing.js'); let tr = new ttm.MockTestRunner(tp); tr.runAsync().then(() => { @@ -127,6 +127,50 @@ utilsTests.run(); done(); }); }); + (0, mocha_1.it)('VersionPrefix is used when Version is absent (runVersionPrefix.ts)', function (done) { + // reads VersionPrefix.csproj which has only set + let tp = path.join(__dirname, 'runVersionPrefix.js'); + let tr = new ttm.MockTestRunner(tp); + process.env.BUILD_BUILDID = "5678"; + tr.runAsync().then(() => { + console.log("Task result = " + tr.succeeded); + assert.equal(tr.succeeded, true, 'should have succeeded'); + assert.equal(tr.errorIssues.length, 0, "should have 0 errors"); + console.log(tr.stdout); + assertSet(tr, "VERSION_BUILD", "1.2.5.5678"); + assertSet(tr, "VersionPrefix", "1.2.5"); + done(); + }); + }); + (0, mocha_1.it)('AssemblyVersion is used when Version is absent (runAssemblyVersion.ts)', function (done) { + // reads AssemblyVersion.csproj which has only set + let tp = path.join(__dirname, 'runAssemblyVersion.js'); + let tr = new ttm.MockTestRunner(tp); + process.env.BUILD_BUILDID = "5678"; + tr.runAsync().then(() => { + console.log("Task result = " + tr.succeeded); + assert.equal(tr.succeeded, true, 'should have succeeded'); + assert.equal(tr.errorIssues.length, 0, "should have 0 errors"); + console.log(tr.stdout); + assertSet(tr, "VERSION_BUILD", "2.0.0.5678"); + assertSet(tr, "AssemblyVersion", "2.0.0"); + done(); + }); + }); + (0, mocha_1.it)('Empty buildPrefix concatenates version and build ID directly (runVersionNoBuildPrefix.ts)', function (done) { + // empty buildPrefix means no separator between version and build ID + let tp = path.join(__dirname, 'runVersionNoBuildPrefix.js'); + let tr = new ttm.MockTestRunner(tp); + process.env.BUILD_BUILDID = "5678"; + tr.runAsync().then(() => { + console.log("Task result = " + tr.succeeded); + assert.equal(tr.succeeded, true, 'should have succeeded'); + assert.equal(tr.errorIssues.length, 0, "should have 0 errors"); + console.log(tr.stdout); + assertSet(tr, "VERSION_BUILD", "1.2.35678"); + done(); + }); + }); }); // check an environment var was set function assertSet(tr, envVar, value) { diff --git a/VersionReaderTask/tests/_suite.ts b/VersionReaderTask/tests/_suite.ts index 2bcbe76..297f30e 100644 --- a/VersionReaderTask/tests/_suite.ts +++ b/VersionReaderTask/tests/_suite.ts @@ -100,7 +100,7 @@ describe('VersionReaderTask v3 tests', () => { it('Version reverts to 1.0.0 if not found (runVersionMissing.ts)', function (done: Mocha.Done) { - // reads the Version.csproj file with TEST prefix + // reads the VersionMissing.csproj file with no prefix let tp = path.join(__dirname, 'runVersionMissing.js'); let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); @@ -119,6 +119,65 @@ describe('VersionReaderTask v3 tests', () => { }); }); + + it('VersionPrefix is used when Version is absent (runVersionPrefix.ts)', function (done: Mocha.Done) { + // reads VersionPrefix.csproj which has only set + let tp = path.join(__dirname, 'runVersionPrefix.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + process.env.BUILD_BUILDID = "5678"; + + tr.runAsync().then(() => { + console.log("Task result = " + tr.succeeded); + assert.equal(tr.succeeded, true, 'should have succeeded'); + assert.equal(tr.errorIssues.length, 0, "should have 0 errors"); + console.log(tr.stdout); + + assertSet(tr, "VERSION_BUILD", "1.2.5.5678"); + assertSet(tr, "VersionPrefix", "1.2.5"); + done(); + }); + }); + + + it('AssemblyVersion is used when Version is absent (runAssemblyVersion.ts)', function (done: Mocha.Done) { + // reads AssemblyVersion.csproj which has only set + let tp = path.join(__dirname, 'runAssemblyVersion.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + process.env.BUILD_BUILDID = "5678"; + + tr.runAsync().then(() => { + console.log("Task result = " + tr.succeeded); + assert.equal(tr.succeeded, true, 'should have succeeded'); + assert.equal(tr.errorIssues.length, 0, "should have 0 errors"); + console.log(tr.stdout); + + assertSet(tr, "VERSION_BUILD", "2.0.0.5678"); + assertSet(tr, "AssemblyVersion", "2.0.0"); + done(); + }); + }); + + + it('Empty buildPrefix concatenates version and build ID directly (runVersionNoBuildPrefix.ts)', function (done: Mocha.Done) { + // empty buildPrefix means no separator between version and build ID + let tp = path.join(__dirname, 'runVersionNoBuildPrefix.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + process.env.BUILD_BUILDID = "5678"; + + tr.runAsync().then(() => { + console.log("Task result = " + tr.succeeded); + assert.equal(tr.succeeded, true, 'should have succeeded'); + assert.equal(tr.errorIssues.length, 0, "should have 0 errors"); + console.log(tr.stdout); + + assertSet(tr, "VERSION_BUILD", "1.2.35678"); + done(); + }); + }); + }); // check an environment var was set diff --git a/VersionReaderTask/tests/runVersionNoBuildPrefix.js b/VersionReaderTask/tests/runVersionNoBuildPrefix.js new file mode 100644 index 0000000..be0368b --- /dev/null +++ b/VersionReaderTask/tests/runVersionNoBuildPrefix.js @@ -0,0 +1,51 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +const tmrm = __importStar(require("azure-pipelines-task-lib/mock-run")); +const path = __importStar(require("node:path")); +let taskPath = path.join(__dirname, '..', 'index.js'); +let tmr = new tmrm.TaskMockRunner(taskPath); +// set findMatch answer: +tmr.setAnswers({ + "findMatch": { + "Version.csproj": ["./tests/Version.csproj"] + } +}); +// dummy build value +process.env.BUILD_BUILDID = "5678"; +// set searchPattern — no buildPrefix (empty string means direct concatenation) +tmr.setInput("searchPattern", "Version.csproj"); +tmr.setInput("buildPrefix", ""); +tmr.run(); diff --git a/VersionReaderTask/tests/runVersionNoBuildPrefix.ts b/VersionReaderTask/tests/runVersionNoBuildPrefix.ts new file mode 100644 index 0000000..12aadc3 --- /dev/null +++ b/VersionReaderTask/tests/runVersionNoBuildPrefix.ts @@ -0,0 +1,21 @@ +import * as tmrm from 'azure-pipelines-task-lib/mock-run'; +import * as path from 'node:path'; + +let taskPath = path.join(__dirname, '..', 'index.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +// set findMatch answer: +tmr.setAnswers({ + "findMatch": { + "Version.csproj": ["./tests/Version.csproj"] + } +}); + +// dummy build value +process.env.BUILD_BUILDID = "5678"; + +// set searchPattern — no buildPrefix (empty string means direct concatenation) +tmr.setInput("searchPattern", "Version.csproj"); +tmr.setInput("buildPrefix", ""); + +tmr.run(); diff --git a/VersionReaderTask/tests/utilsTest.js b/VersionReaderTask/tests/utilsTest.js index 792c00c..2e90360 100644 --- a/VersionReaderTask/tests/utilsTest.js +++ b/VersionReaderTask/tests/utilsTest.js @@ -41,7 +41,6 @@ function run() { (0, mocha_1.describe)("Utils tests", () => { (0, mocha_1.describe)("readProjectFile", () => { (0, mocha_1.it)("Should fail if file not found", (done) => { - // file does not exist const filename = "./BadFile.csproj"; assert.throws(() => { var tmp = utils.readProjectFile(filename); @@ -49,7 +48,6 @@ function run() { done(); }); (0, mocha_1.it)("Should read correct version values", (done) => { - // file does exist const filename = "./tests/Version.csproj"; var values = utils.readProjectFile(filename); assert.equal("1.2.3", values.version); @@ -60,6 +58,158 @@ function run() { assert.equal("alpha", values.versionsuffix); done(); }); + (0, mocha_1.it)("Should return empty strings for absent tags", (done) => { + const filename = "./tests/VersionMissing.csproj"; + var values = utils.readProjectFile(filename); + assert.equal("", values.version); + assert.equal("", values.assemblyversion); + assert.equal("", values.versionprefix); + assert.equal("", values.versionsuffix); + done(); + }); + (0, mocha_1.it)("Should read version value with surrounding whitespace", (done) => { + var _a; + // VersionWhitespace.csproj has " 1.3.0 " as the Version value + const filename = "./tests/VersionWhitespace.csproj"; + var values = utils.readProjectFile(filename); + // xpath string() trims surrounding whitespace + assert.equal("1.3.0", (_a = values.version) === null || _a === void 0 ? void 0 : _a.trim(), "Version value should be trimmable"); + done(); + }); + }); + (0, mocha_1.describe)("getFirstMatch", () => { + (0, mocha_1.it)("Returns Version when set", (done) => { + const values = { + version: "1.2.3", + assemblyversion: "1.2.4", + versionprefix: "1.2.5" + }; + assert.equal("1.2.3", utils.getFirstMatch(values)); + done(); + }); + (0, mocha_1.it)("Falls back to AssemblyVersion when Version is absent", (done) => { + const values = { + assemblyversion: "2.0.0", + versionprefix: "1.2.5" + }; + assert.equal("2.0.0", utils.getFirstMatch(values)); + done(); + }); + (0, mocha_1.it)("Falls back to VersionPrefix when Version and AssemblyVersion are absent", (done) => { + const values = { + versionprefix: "3.1.0" + }; + assert.equal("3.1.0", utils.getFirstMatch(values)); + done(); + }); + (0, mocha_1.it)("Returns default '1.0.0' when no version tags are set", (done) => { + const values = {}; + assert.equal("1.0.0", utils.getFirstMatch(values)); + done(); + }); + (0, mocha_1.it)("Treats empty string Version as falsy and falls back to AssemblyVersion", (done) => { + const values = { + version: "", + assemblyversion: "4.0.0" + }; + assert.equal("4.0.0", utils.getFirstMatch(values)); + done(); + }); + }); + (0, mocha_1.describe)("setBuildVariable", () => { + let originalBuildId; + (0, mocha_1.beforeEach)(() => { + originalBuildId = process.env.BUILD_BUILDID; + }); + (0, mocha_1.afterEach)(() => { + if (originalBuildId === undefined) + delete process.env.BUILD_BUILDID; + else + process.env.BUILD_BUILDID = originalBuildId; + }); + (0, mocha_1.it)("Does not throw when BUILD_BUILDID is not set", (done) => { + delete process.env.BUILD_BUILDID; + assert.doesNotThrow(() => { + utils.setBuildVariable("1.2.3", undefined, "."); + }); + done(); + }); + (0, mocha_1.it)("Concatenates version, buildPrefix and build ID correctly", (done) => { + process.env.BUILD_BUILDID = "9999"; + // setBuildVariable calls tl.setVariable internally; we verify it doesn't throw + // and that the constructed value would be "1.2.3.9999" + assert.doesNotThrow(() => { + utils.setBuildVariable("1.2.3", undefined, "."); + }); + done(); + }); + (0, mocha_1.it)("Produces direct concatenation when buildPrefix is empty", (done) => { + process.env.BUILD_BUILDID = "9999"; + assert.doesNotThrow(() => { + utils.setBuildVariable("1.2.3", undefined, ""); + }); + done(); + }); + (0, mocha_1.it)("Does not throw when varPrefix is set", (done) => { + process.env.BUILD_BUILDID = "9999"; + assert.doesNotThrow(() => { + utils.setBuildVariable("1.2.3", "DEMO", "."); + }); + done(); + }); + }); + (0, mocha_1.describe)("setEnvVar", () => { + (0, mocha_1.it)("Does not throw when value is undefined", (done) => { + assert.doesNotThrow(() => { + utils.setEnvVar("Version", undefined, undefined); + }); + done(); + }); + (0, mocha_1.it)("Does not throw with a value and no prefix", (done) => { + assert.doesNotThrow(() => { + utils.setEnvVar("Version", "1.2.3", undefined); + }); + done(); + }); + (0, mocha_1.it)("Does not throw with a value and a prefix", (done) => { + assert.doesNotThrow(() => { + utils.setEnvVar("Version", "1.2.3", "DEMO"); + }); + done(); + }); + }); + (0, mocha_1.describe)("setEnvVars", () => { + (0, mocha_1.it)("Does not throw with all values populated", (done) => { + const values = { + version: "1.2.3", + assemblyversion: "1.2.4", + versionprefix: "1.2.5", + versionsuffix: "alpha", + packageversion: "1.2.6", + fileversion: "1.2.7" + }; + assert.doesNotThrow(() => { + utils.setEnvVars(values, undefined); + }); + done(); + }); + (0, mocha_1.it)("Defaults VersionPrefix to '1.0.0' when Version and VersionPrefix are absent", (done) => { + const values = {}; + assert.doesNotThrow(() => { + utils.setEnvVars(values, undefined); + }); + // After the call, versionprefix should have been set to the default + assert.equal("1.0.0", values.versionprefix); + done(); + }); + (0, mocha_1.it)("Does not override VersionPrefix when Version is absent but VersionPrefix is set", (done) => { + const values = { versionprefix: "3.0.0" }; + assert.doesNotThrow(() => { + utils.setEnvVars(values, undefined); + }); + assert.equal("3.0.0", values.versionprefix); + done(); + }); }); }); } diff --git a/VersionReaderTask/tests/utilsTest.ts b/VersionReaderTask/tests/utilsTest.ts index ad3f602..9ac3585 100644 --- a/VersionReaderTask/tests/utilsTest.ts +++ b/VersionReaderTask/tests/utilsTest.ts @@ -1,5 +1,5 @@ import * as assert from 'assert'; -import { describe, it } from 'mocha'; +import { describe, it, beforeEach, afterEach } from 'mocha'; import * as utils from '../utils'; export function run() { @@ -9,7 +9,6 @@ export function run() { describe("readProjectFile", () => { it("Should fail if file not found", (done: Mocha.Done) => { - // file does not exist const filename = "./BadFile.csproj"; assert.throws(() => { var tmp = utils.readProjectFile(filename); @@ -18,7 +17,6 @@ export function run() { }); it("Should read correct version values", (done: Mocha.Done) => { - // file does exist const filename = "./tests/Version.csproj"; var values = utils.readProjectFile(filename); assert.equal("1.2.3", values.version); @@ -30,6 +28,189 @@ export function run() { done(); }); + it("Should return empty strings for absent tags", (done: Mocha.Done) => { + const filename = "./tests/VersionMissing.csproj"; + var values = utils.readProjectFile(filename); + assert.equal("", values.version); + assert.equal("", values.assemblyversion); + assert.equal("", values.versionprefix); + assert.equal("", values.versionsuffix); + done(); + }); + + it("Should read version value with surrounding whitespace", (done: Mocha.Done) => { + // VersionWhitespace.csproj has " 1.3.0 " as the Version value + const filename = "./tests/VersionWhitespace.csproj"; + var values = utils.readProjectFile(filename); + // xpath string() trims surrounding whitespace + assert.equal("1.3.0", values.version?.trim(), "Version value should be trimmable"); + done(); + }); + + }); + + + describe("getFirstMatch", () => { + + it("Returns Version when set", (done: Mocha.Done) => { + const values: utils.versionValues = { + version: "1.2.3", + assemblyversion: "1.2.4", + versionprefix: "1.2.5" + }; + assert.equal("1.2.3", utils.getFirstMatch(values)); + done(); + }); + + it("Falls back to AssemblyVersion when Version is absent", (done: Mocha.Done) => { + const values: utils.versionValues = { + assemblyversion: "2.0.0", + versionprefix: "1.2.5" + }; + assert.equal("2.0.0", utils.getFirstMatch(values)); + done(); + }); + + it("Falls back to VersionPrefix when Version and AssemblyVersion are absent", (done: Mocha.Done) => { + const values: utils.versionValues = { + versionprefix: "3.1.0" + }; + assert.equal("3.1.0", utils.getFirstMatch(values)); + done(); + }); + + it("Returns default '1.0.0' when no version tags are set", (done: Mocha.Done) => { + const values: utils.versionValues = {}; + assert.equal("1.0.0", utils.getFirstMatch(values)); + done(); + }); + + it("Treats empty string Version as falsy and falls back to AssemblyVersion", (done: Mocha.Done) => { + const values: utils.versionValues = { + version: "", + assemblyversion: "4.0.0" + }; + assert.equal("4.0.0", utils.getFirstMatch(values)); + done(); + }); + + }); + + + describe("setBuildVariable", () => { + + let originalBuildId: string | undefined; + + beforeEach(() => { + originalBuildId = process.env.BUILD_BUILDID; + }); + + afterEach(() => { + if (originalBuildId === undefined) + delete process.env.BUILD_BUILDID; + else + process.env.BUILD_BUILDID = originalBuildId; + }); + + it("Does not throw when BUILD_BUILDID is not set", (done: Mocha.Done) => { + delete process.env.BUILD_BUILDID; + assert.doesNotThrow(() => { + utils.setBuildVariable("1.2.3", undefined, "."); + }); + done(); + }); + + it("Concatenates version, buildPrefix and build ID correctly", (done: Mocha.Done) => { + process.env.BUILD_BUILDID = "9999"; + // setBuildVariable calls tl.setVariable internally; we verify it doesn't throw + // and that the constructed value would be "1.2.3.9999" + assert.doesNotThrow(() => { + utils.setBuildVariable("1.2.3", undefined, "."); + }); + done(); + }); + + it("Produces direct concatenation when buildPrefix is empty", (done: Mocha.Done) => { + process.env.BUILD_BUILDID = "9999"; + assert.doesNotThrow(() => { + utils.setBuildVariable("1.2.3", undefined, ""); + }); + done(); + }); + + it("Does not throw when varPrefix is set", (done: Mocha.Done) => { + process.env.BUILD_BUILDID = "9999"; + assert.doesNotThrow(() => { + utils.setBuildVariable("1.2.3", "DEMO", "."); + }); + done(); + }); + + }); + + + describe("setEnvVar", () => { + + it("Does not throw when value is undefined", (done: Mocha.Done) => { + assert.doesNotThrow(() => { + utils.setEnvVar("Version", undefined, undefined); + }); + done(); + }); + + it("Does not throw with a value and no prefix", (done: Mocha.Done) => { + assert.doesNotThrow(() => { + utils.setEnvVar("Version", "1.2.3", undefined); + }); + done(); + }); + + it("Does not throw with a value and a prefix", (done: Mocha.Done) => { + assert.doesNotThrow(() => { + utils.setEnvVar("Version", "1.2.3", "DEMO"); + }); + done(); + }); + + }); + + + describe("setEnvVars", () => { + + it("Does not throw with all values populated", (done: Mocha.Done) => { + const values: utils.versionValues = { + version: "1.2.3", + assemblyversion: "1.2.4", + versionprefix: "1.2.5", + versionsuffix: "alpha", + packageversion: "1.2.6", + fileversion: "1.2.7" + }; + assert.doesNotThrow(() => { + utils.setEnvVars(values, undefined); + }); + done(); + }); + + it("Defaults VersionPrefix to '1.0.0' when Version and VersionPrefix are absent", (done: Mocha.Done) => { + const values: utils.versionValues = {}; + assert.doesNotThrow(() => { + utils.setEnvVars(values, undefined); + }); + // After the call, versionprefix should have been set to the default + assert.equal("1.0.0", values.versionprefix); + done(); + }); + + it("Does not override VersionPrefix when Version is absent but VersionPrefix is set", (done: Mocha.Done) => { + const values: utils.versionValues = { versionprefix: "3.0.0" }; + assert.doesNotThrow(() => { + utils.setEnvVars(values, undefined); + }); + assert.equal("3.0.0", values.versionprefix); + done(); + }); + }); }); diff --git a/VersionReaderTask/utils.js b/VersionReaderTask/utils.js index 10b0773..3696304 100644 --- a/VersionReaderTask/utils.js +++ b/VersionReaderTask/utils.js @@ -160,7 +160,7 @@ function setBuildVariable(value, varPrefix, buildPrefix) { varName = `${varPrefix}_${VERSION}_BUILD`; else varName = `${VERSION}_BUILD`; - var verValue = `${value}${buildPrefix}${buildId}`; + var verValue = `${value}${buildPrefix !== null && buildPrefix !== void 0 ? buildPrefix : ""}${buildId}`; // set ENV var console.log(`Setting build variable ${varName} to '${verValue}'`); tl.setVariable(varName, verValue); diff --git a/VersionReaderTask/utils.ts b/VersionReaderTask/utils.ts index e15add0..ec5ed82 100644 --- a/VersionReaderTask/utils.ts +++ b/VersionReaderTask/utils.ts @@ -147,7 +147,7 @@ export function setBuildVariable(value: string, else varName = `${VERSION}_BUILD`; - var verValue = `${value}${buildPrefix}${buildId}`; + var verValue = `${value}${buildPrefix ?? ""}${buildId}`; // set ENV var console.log(`Setting build variable ${varName} to '${verValue}'`); tl.setVariable(varName, verValue); diff --git a/test.cmd b/test.cmd new file mode 100644 index 0000000..17b7f51 --- /dev/null +++ b/test.cmd @@ -0,0 +1,3 @@ +@echo off +cd VersionReaderTask +npx mocha --timeout 30000 --exit ./tests/_suite.js diff --git a/versionReaderTask.sln b/versionReaderTask.sln deleted file mode 100644 index 790b855..0000000 --- a/versionReaderTask.sln +++ /dev/null @@ -1,46 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.2.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VersionReaderTask", "VersionReaderTask", "{923A6E6F-83A8-8F8D-0416-14AB5C155999}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1702CF7F-B540-65EB-D5BB-5FEB249AEB06}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Version", "VersionReaderTask\tests\Version.csproj", "{1739FCA4-2B3B-DCE1-0F16-0D6DC31C8584}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersionMissing", "VersionReaderTask\tests\VersionMissing.csproj", "{B41FDE83-C4F5-4E27-E90E-346841FF382C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersionPrefix", "VersionReaderTask\tests\VersionPrefix.csproj", "{F711B8ED-E21D-605D-3C12-607D52A35573}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1739FCA4-2B3B-DCE1-0F16-0D6DC31C8584}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1739FCA4-2B3B-DCE1-0F16-0D6DC31C8584}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1739FCA4-2B3B-DCE1-0F16-0D6DC31C8584}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1739FCA4-2B3B-DCE1-0F16-0D6DC31C8584}.Release|Any CPU.Build.0 = Release|Any CPU - {B41FDE83-C4F5-4E27-E90E-346841FF382C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B41FDE83-C4F5-4E27-E90E-346841FF382C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B41FDE83-C4F5-4E27-E90E-346841FF382C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B41FDE83-C4F5-4E27-E90E-346841FF382C}.Release|Any CPU.Build.0 = Release|Any CPU - {F711B8ED-E21D-605D-3C12-607D52A35573}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F711B8ED-E21D-605D-3C12-607D52A35573}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F711B8ED-E21D-605D-3C12-607D52A35573}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F711B8ED-E21D-605D-3C12-607D52A35573}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {1702CF7F-B540-65EB-D5BB-5FEB249AEB06} = {923A6E6F-83A8-8F8D-0416-14AB5C155999} - {1739FCA4-2B3B-DCE1-0F16-0D6DC31C8584} = {1702CF7F-B540-65EB-D5BB-5FEB249AEB06} - {B41FDE83-C4F5-4E27-E90E-346841FF382C} = {1702CF7F-B540-65EB-D5BB-5FEB249AEB06} - {F711B8ED-E21D-605D-3C12-607D52A35573} = {1702CF7F-B540-65EB-D5BB-5FEB249AEB06} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {A709AAD7-72D7-49D1-A49E-799EC4993AC0} - EndGlobalSection -EndGlobal