Skip to content

Commit bb4526f

Browse files
authored
Merge pull request #1792 from SimplyAName/feature/fix-json-parsing-azure
Fix JSON parsing in tasks when '{' is in the GitVersion output
2 parents ce467e4 + 4069a2f commit bb4526f

File tree

9 files changed

+441
-53
lines changed

9 files changed

+441
-53
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pnpm-debug.log*
88
lerna-debug.log*
99

1010
node_modules
11+
1112
#dist
1213
dist-ssr
1314
*.local
@@ -35,3 +36,6 @@ dist-ssr
3536
junit-report.xml
3637

3738
src/__tests__/test.env
39+
40+
# Unit test files
41+
test.env

README.md

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
GitHub Actions that allow the setup and use of the [GitVersion](https://github.com/GitTools/GitVersion) and [GitReleaseManager](https://github.com/GitTools/GitReleaseManager) tools.
66

7-
[![Build Status](https://github.com/GitTools/actions/workflows/CI/badge.svg)](https://github.com/GitTools/actions/actions)
8-
[![Build Status](https://github.com/GitTools/actions/workflows/release/badge.svg)](https://github.com/GitTools/actions/actions)
7+
[![CI Build Status](https://github.com/GitTools/actions/workflows/CI/badge.svg)](https://github.com/GitTools/actions/actions)
8+
[![Release Build Status](https://github.com/GitTools/actions/workflows/release/badge.svg)](https://github.com/GitTools/actions/actions)
99

1010
[![GitHub Release](https://img.shields.io/github/v/release/gittools/actions?logo=github&sort=semver)](https://github.com/GitTools/actions/releases/latest)
1111

@@ -39,3 +39,104 @@ Examples for usage of **GitReleaseManager**:
3939
### Versioning and Compatibility
4040

4141
You can find the compatibility matrix in the [versions.md](docs/versions.md) file.
42+
43+
## Contributing
44+
45+
### Prerequisites
46+
47+
1. **Linux** - Recommended to build and run
48+
2. **Node.js** - Latest LTS version recommended
49+
3. **.NET SDK** - Version 8.0 or later required for GitVersion and GitReleaseManager tools
50+
4. **Git** - Latest version recommended
51+
52+
### Development Environment Setup
53+
54+
This project is designed to be run and worked on in a Linux or macOS environment.
55+
If developing on Windows, please consider using WSL as it's likely you will run into issues with file paths etc
56+
57+
1. Clone the repository:
58+
59+
```bash
60+
git clone https://github.com/GitTools/actions.git
61+
cd actions
62+
```
63+
64+
2. Install dependencies:
65+
66+
```bash
67+
npm install
68+
```
69+
70+
3. Build the project:
71+
- For local development: `npm run build:local`
72+
- For Azure Pipelines: `npm run build:azure`
73+
- For GitHub Actions: `npm run build:github`
74+
75+
4. Test the project:
76+
77+
```bash
78+
npm run test
79+
```
80+
81+
### Required Knowledge
82+
83+
- **TypeScript/JavaScript** - Primary development languages
84+
- **GitHub Actions** - Understanding of action creation and workflows
85+
- **Azure Pipelines** - Familiarity with pipeline tasks and extensions
86+
- **.NET Tools** - Basic understanding of .NET CLI tools
87+
- **Git** - Strong knowledge of Git versioning and release management
88+
89+
### Project Structure
90+
91+
- `src/tools/` - Core implementation of GitVersion and GitReleaseManager integrations
92+
- `src/agents/` - Build agent implementations for different CI platforms
93+
- `src/__tests__/` - Test suites organized by component
94+
- `docs/examples/` - Usage examples for both GitHub Actions and Azure Pipelines
95+
96+
### Creating Pull Requests
97+
98+
When contributing to this project, please follow these guidelines for creating pull requests:
99+
100+
1. **Fork and Clone**
101+
- Fork the repository to your GitHub account
102+
- Clone your fork locally: `git clone https://github.com/YOUR-USERNAME/actions.git`
103+
- Add upstream remote: `git remote add upstream https://github.com/GitTools/actions.git`
104+
105+
2. **Create a Feature Branch**
106+
- Create a branch from main: `git checkout -b feature/your-feature-name`
107+
- Use descriptive branch names (e.g., `feature/add-new-version-format`, `fix/issue-123`)
108+
109+
3. **Keep Your Branch Updated**
110+
- Fetch upstream changes: `git fetch upstream`
111+
- Rebase your branch on upstream main:
112+
113+
```bash
114+
git checkout main
115+
git rebase upstream/main
116+
git checkout your-branch
117+
git rebase main
118+
```
119+
120+
- Always use rebase instead of merge to maintain a clean history
121+
122+
4. **Make Your Changes**
123+
- Make commits with clear, descriptive messages
124+
- Follow the project's code style and conventions
125+
- Add tests for new features or bug fixes
126+
- Run tests locally to ensure everything passes
127+
128+
5. **Submit the Pull Request**
129+
- Push your changes to your fork: `git push origin your-branch`
130+
- Go to the original repository on GitHub
131+
- Click "New Pull Request" and select your feature branch
132+
- Link any related issues
133+
134+
6. **PR Guidelines**
135+
- Keep PRs focused and single-purpose
136+
- Include tests and documentation updates
137+
- Ensure CI checks pass
138+
139+
7. **After PR is Merged**
140+
- Delete your feature branch locally and remotely
141+
- Update your main branch with the upstream changes
142+
- Celebrate your contribution! 🎉

dist/tools/lib.mjs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ async function run(agent, tool, command) {
2424
const runner = await getToolRunner(agent, tool);
2525
return await runner.run(command);
2626
}
27+
function allIndexesOf(searchString, indexOf) {
28+
if (indexOf.length !== 1) {
29+
throw new Error("indexOf must be a single character");
30+
}
31+
const resultArray = [];
32+
for (let i = 0; i < searchString.length; i++) {
33+
if (searchString[i] === indexOf) {
34+
resultArray.push(i);
35+
}
36+
}
37+
return resultArray;
38+
}
2739

28-
export { getAgent, getToolRunner, parseCliArgs, run };
40+
export { allIndexesOf, getAgent, getToolRunner, parseCliArgs, run };
2941
//# sourceMappingURL=lib.mjs.map

dist/tools/lib.mjs.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/tools/libs/gitversion.mjs

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'node:fs/promises';
44
import 'node:os';
55
import 'node:path';
66
import './semver.mjs';
7+
import { allIndexesOf } from '../lib.mjs';
78

89
class GitVersionSettingsProvider extends SettingsProvider {
910
getExecuteSettings() {
@@ -202,35 +203,76 @@ class Runner extends RunnerBase {
202203
async execute() {
203204
return this.safeExecute(async () => {
204205
const result = await this.tool.executeJson();
205-
this.buildAgent.debug("Parsing GitVersion output");
206206
return this.processGitVersionOutput(result);
207207
}, "GitVersion executed successfully");
208208
}
209209
async command() {
210210
return this.safeExecute(async () => await this.tool.executeCommand(), "GitVersion executed successfully");
211211
}
212212
processGitVersionOutput(result) {
213-
if (result.code === 0) {
214-
const stdout = result.stdout;
215-
if (stdout.lastIndexOf("{") === -1 || stdout.lastIndexOf("}") === -1) {
216-
const errorMessage = "GitVersion output is not valid JSON, see output details";
213+
this.buildAgent.debug("Parsing GitVersion output");
214+
if (result.code !== 0) {
215+
return result;
216+
}
217+
const stdout = result.stdout;
218+
const gitVersionOutput = this.extractGitVersionOutput(stdout);
219+
if (gitVersionOutput === null) {
220+
const errorMessage = "GitVersion output is not valid JSON, see output details";
221+
this.buildAgent.debug(errorMessage);
222+
this.buildAgent.setFailed(errorMessage, true);
223+
return {
224+
code: -1,
225+
error: new Error(errorMessage)
226+
};
227+
}
228+
this.tool.writeGitVersionToAgent(gitVersionOutput);
229+
this.tool.updateBuildNumber();
230+
this.buildAgent.setSucceeded("GitVersion executed successfully", true);
231+
return result;
232+
}
233+
/**
234+
* Attempts to extract and parse a JSON object representing `GitVersionOutput` from the given input string.
235+
* The method assumes the last closing curly brace (`}`) in the input belongs to the end of the JSON object,
236+
* and iteratively expands the search area backwards from each opening curly brace (`{`) until a valid JSON object is found.
237+
* If parsing fails, it logs debug information and continues searching until all possible start positions are exhausted.
238+
*
239+
* @param input - The string containing the potential JSON output from GitVersion.
240+
* @returns The parsed `GitVersionOutput` object if extraction and parsing succeed; otherwise, `null`.
241+
*/
242+
extractGitVersionOutput(input) {
243+
const allStartOfJsonIndexes = allIndexesOf(input, "{");
244+
const endOfJsonIndex = input.lastIndexOf("}") + 1;
245+
if (allStartOfJsonIndexes.length === 0) {
246+
this.buildAgent.debug("No opening curly brace '{' found in input; cannot extract JSON.");
247+
return null;
248+
}
249+
if (endOfJsonIndex === 0) {
250+
this.buildAgent.debug("No closing curly brace '}' found in input; cannot extract JSON.");
251+
return null;
252+
}
253+
let startIndexArrayPos = allStartOfJsonIndexes.length - 1;
254+
let decodePassCount = 1;
255+
let currSearchString = input.substring(allStartOfJsonIndexes[startIndexArrayPos], endOfJsonIndex);
256+
let resultJson = null;
257+
while (resultJson === null && startIndexArrayPos >= 0) {
258+
try {
259+
this.buildAgent.debug(`Starting JSON extraction at ${allStartOfJsonIndexes[startIndexArrayPos]} to ${endOfJsonIndex}`);
260+
resultJson = JSON.parse(currSearchString);
261+
} catch (ex) {
262+
let exObject = new Error("Unable to parse exception object");
263+
if (ex instanceof Error) {
264+
exObject = ex;
265+
}
266+
const errorMessage = `Failed to parse JSON object on pass ${decodePassCount}. Expanding search area from string index ${allStartOfJsonIndexes[startIndexArrayPos]} to ${endOfJsonIndex}.
267+
Previous search area:'${currSearchString}'
268+
Caught Exception: ${exObject.message}`;
217269
this.buildAgent.debug(errorMessage);
218-
this.buildAgent.setFailed(errorMessage, true);
219-
return {
220-
code: -1,
221-
error: new Error(errorMessage)
222-
};
223-
} else {
224-
const jsonOutput = stdout.substring(stdout.lastIndexOf("{"), stdout.lastIndexOf("}") + 1);
225-
const gitVersionOutput = JSON.parse(jsonOutput);
226-
this.tool.writeGitVersionToAgent(gitVersionOutput);
227-
this.tool.updateBuildNumber();
228-
this.buildAgent.setSucceeded("GitVersion executed successfully", true);
229-
return result;
270+
decodePassCount++;
271+
startIndexArrayPos--;
272+
currSearchString = input.substring(allStartOfJsonIndexes[startIndexArrayPos], endOfJsonIndex);
230273
}
231-
} else {
232-
return result;
233274
}
275+
return resultJson;
234276
}
235277
}
236278

dist/tools/libs/gitversion.mjs.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)