-
Notifications
You must be signed in to change notification settings - Fork 0
New release - enumerate-environment and new documentation #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
144 commits
Select commit
Hold shift + click to select a range
bc502d2
Platform agnostic candidate; modified compiler get to use versions
EvergrowthConsulting b00bc75
Missed updating task.json for the compiler.
EvergrowthConsulting ba74b3e
Missed two more references in the task.json for the expanded function…
EvergrowthConsulting 9d21846
Fix catastrophic spelling error
EvergrowthConsulting f93f599
So many typos
EvergrowthConsulting 49c3cfd
Remove mistaken parameter from task.json
EvergrowthConsulting 4fa8c0b
Change windows detection because Agents don't use normal Windows or s…
EvergrowthConsulting 967a64b
Make directory-safe entry
EvergrowthConsulting beb9030
Add common tools function but don't fully implement yet
EvergrowthConsulting 0433f19
Okay, apparently we can't have nice things because Microsoft
EvergrowthConsulting 7b228e0
***** wept; there's a difference between -Name and -Path
EvergrowthConsulting c465cfe
I love platform agnositicism
EvergrowthConsulting 46e685f
Deactivate targeted folder expansion; it was causing problems
EvergrowthConsulting 89fe707
Misnamed variable; what next?
EvergrowthConsulting eef0ce0
I must be getting tired
EvergrowthConsulting d240f1d
Spacing on log outputs; add app.json validation exposure
EvergrowthConsulting 61760f3
Remove dead platform handling attempts from selective extraction piece
EvergrowthConsulting c7afac3
Missed a piece of code
EvergrowthConsulting aee7198
Add guardrail for ALEXEPath
EvergrowthConsulting adf84a0
The guardrail didn't work?
EvergrowthConsulting f386cd3
Again, why is pathing SO HARD for powershell?
EvergrowthConsulting d67876a
Try a different resolution method?
EvergrowthConsulting ce448f0
Finding strange execution policies
EvergrowthConsulting a8d7ab6
Apparently the file was coming down but strangely
EvergrowthConsulting e965bdb
So much ambiguity when I changed away from extreme manual execution
EvergrowthConsulting 230f886
Removed deprecated function; added a couple lines of debugging
EvergrowthConsulting 76a4a9d
Add multiple debugging routines
EvergrowthConsulting 4951c7a
Aha! A missing directory creation.
EvergrowthConsulting 9abc5f0
I know too many programming languages...
EvergrowthConsulting 30f14fd
More debugging - expand-archive is not working properly
EvergrowthConsulting 70b50ae
Change debugging concepts for platform agnosticism
EvergrowthConsulting 3786a7d
Added rename failure defense
EvergrowthConsulting fdafac6
Frikkin' typos
EvergrowthConsulting a9214ef
Fix debugging bits; improve logging; do we have a winner?
EvergrowthConsulting 17bc18b
Update environment tick and documentation
EvergrowthConsulting f10bdc2
Merge pull request #14 from crazycga/change_to_api
crazycga 9913b66
Add list of companies quick-hit
EvergrowthConsulting 5b9fe64
Added function to get list of modules, confirm module and abstracted …
EvergrowthConsulting 32d7f76
Potential working version with new tools
EvergrowthConsulting 5154a7b
Check in change to extension to grab new items
EvergrowthConsulting 98117da
Add _common folder to extension
EvergrowthConsulting 5d16b29
Try moving location of source file
EvergrowthConsulting 1403221
See if enumerating the parent helps?
EvergrowthConsulting ce6c402
I NEED to see the internals of this
EvergrowthConsulting d3a2b8e
Ugh; forgot to comment the required
EvergrowthConsulting f8ce796
Still troublehsooting because I don't know where the system is puttin…
EvergrowthConsulting f580446
Mass enumeration now
EvergrowthConsulting 6107782
You have to embed commonTools in EACH TASK?
EvergrowthConsulting 3b398da
Commit build script and remove dead soul from test variant
EvergrowthConsulting 36b431c
Correct mis-referenced common tools
EvergrowthConsulting 2dfb655
Move _build.ps1 to correct location
EvergrowthConsulting 8aae1c1
Update mainbuild.yml
crazycga 8a19e38
Try changing reference path on _build script
EvergrowthConsulting 9228a02
Forgot to update task.json with extreme debugging mode switch
EvergrowthConsulting 15597fb
Need extreme debugging
EvergrowthConsulting 5bc71d7
Seriously debugging now
EvergrowthConsulting 2b493fd
Correct for proper internal pathing on scripts
EvergrowthConsulting 087faa8
Expose parseBool for subfunctions to use
EvergrowthConsulting a90b4d0
Forgot to add "path" module
EvergrowthConsulting a67def0
Missed parameter on task.json
EvergrowthConsulting fa8b0c2
Crashing typo; needs fixing
EvergrowthConsulting 4938a93
Update documentation
EvergrowthConsulting 860b1ce
Incremented version number in tasks and top-end; updated repo-level r…
EvergrowthConsulting 41d3d20
Correct release date
EvergrowthConsulting 99fc835
Merge pull request #19 from crazycga/incorporate_new_functions
crazycga 7c0b09c
Correct tagging to only fire on dev-trunk and main
EvergrowthConsulting 39e66be
Merge pull request #20 from crazycga/incorporate_new_functions
crazycga e37c699
Increment version in environments.json
EvergrowthConsulting f4771a0
Merge pull request #21 from crazycga/incorporate_new_functions
crazycga 7f4a6df
I can't believe I missed this
EvergrowthConsulting 5a98fde
Same typo - brave new world
EvergrowthConsulting 203df8b
Merge pull request #22 from crazycga/incorporate_new_functions
crazycga 5b4b1db
Merge branch 'main' into dev_trunk
crazycga 3e5e804
Totally forgot to update environments.json for new guid preparation
EvergrowthConsulting 939f886
Merge pull request #24 from crazycga/incorporate_new_functions
crazycga d7c8356
Improve logging and attempt to fix file upload
EvergrowthConsulting abfac03
Add latency because I think I'm outrunning the API perhaps?
EvergrowthConsulting 1c0b120
Troubleshooting the publish routine
EvergrowthConsulting 867e610
Still troubleshooting wait response and upload routines
EvergrowthConsulting 66a8941
Tilt at that windmill!
EvergrowthConsulting e93fe40
Final candidate version for debug release
EvergrowthConsulting ef1d556
Error capture the new odata.etag properly
EvergrowthConsulting 5bb15ca
Strangely the upload routine now balks at the if-match on the call to…
EvergrowthConsulting 03e18cd
Minor corrections to timing and fixes to publish routine
EvergrowthConsulting bc9962f
Update documentation, etc. for new version
EvergrowthConsulting f81e535
Modify pipeline build to only tag release versions
EvergrowthConsulting 1bf5664
Fix timing declaration issue
EvergrowthConsulting 03c5799
Fix up some logging, make things a little prettier
EvergrowthConsulting 709c941
Prettification of some logs
EvergrowthConsulting 7f83364
Added changes to RELEASE.md
EvergrowthConsulting 34ccec3
Clean up logging in getInstallationStatus; migrate to logger instead …
EvergrowthConsulting 02409ba
More prettifying the logger
EvergrowthConsulting 15000e0
Update to "new" logging standards
EvergrowthConsulting ee9be4a
Same thing
EvergrowthConsulting 86acf21
Merge branch 'main' into dev_trunk
EvergrowthConsulting 76c4229
Logging corrections after the change to the logger
EvergrowthConsulting 1156ae8
Merge branch 'main' into dev_trunk
EvergrowthConsulting 5b169df
First pass at refactoring Build routine to JS
EvergrowthConsulting a90a4bb
Dang it, forgot to add to the build script for the Build-ALPackage
EvergrowthConsulting 305ebe4
Try an agnostic setting for build
EvergrowthConsulting 49f08a8
Rebuild file name
EvergrowthConsulting a0c63d4
Lazy-load undici only on calls that require it; correct file output n…
EvergrowthConsulting 7b8a8fc
Add new function for get-bcdependencies
EvergrowthConsulting e7d613d
Fix task.json for js reference
EvergrowthConsulting 2e61a48
Add get-bcdependencies to build script for commontools
EvergrowthConsulting d282c52
Make command async to survive token acquisitions
EvergrowthConsulting 587fb8e
Messed up the async call
EvergrowthConsulting dfba6d7
Bad call to commonTools
EvergrowthConsulting cf46f7c
Remove function type from usesUndici; not sure this is going to work
EvergrowthConsulting 34260dd
Adding filename capture; locally debugged version
EvergrowthConsulting 48ae495
Migrate VSIX acquisition to JS
EvergrowthConsulting 588ee90
Update task.json for VSIX
EvergrowthConsulting d557a0c
Remove Powershell leftovers
EvergrowthConsulting d2d0bb3
Update RELEASE.md with quick hit before testing platform changes
EvergrowthConsulting 154d462
Path normalization; improve logging and variable setting in Get-VSIXC…
EvergrowthConsulting c1c42d3
Fix the directory walking routine
EvergrowthConsulting 5812713
Fix linux alc call
EvergrowthConsulting 8b9b4b0
Fix path normalization in deploy
EvergrowthConsulting 775e92e
Final documentation update; RC 0.1.8
EvergrowthConsulting ee76bf1
Merge pull request #29 from crazycga/refactor_to_js
crazycga b847655
Candidate to fix incorrect compiler issue
EvergrowthConsulting 13b19c3
Merge pull request #32 from crazycga/correct_alpath_emission
crazycga a644c73
Merge branch 'main' into dev_trunk
crazycga ea55716
Introduce environmental enumeration for bits and pieces
EvergrowthConsulting dd7114c
Update vss extension metadata
EvergrowthConsulting 178aeea
Update file emission and win32 shield non-Windows agents
EvergrowthConsulting 32395bc
Defend docker image enumeration when no images are available
EvergrowthConsulting 9e7dd75
Extract pwsh version to quote-safe variable
EvergrowthConsulting 28c2218
Better defense on pwsh version
EvergrowthConsulting f957d90
Correct string interpolation across differing shell policies
EvergrowthConsulting b4bd6b3
Increment version because .... reasons, I guess.
EvergrowthConsulting bd9303a
Updated the wrong one with the right reason
EvergrowthConsulting 75d8fe7
Still trying to trick the system
EvergrowthConsulting 64b14a9
Try different quoting strategy
EvergrowthConsulting e813284
Further escaping hell
EvergrowthConsulting aeb64a9
Why screw around when you can just use ToString()?
EvergrowthConsulting aee0038
Still trying to make this platform agnostic
EvergrowthConsulting 5da2f17
Clean up instance where docker images error when producing a file and…
EvergrowthConsulting df87b0e
Correct reference to filepath and name in function
EvergrowthConsulting 1a08845
Typos, FTW!
EvergrowthConsulting 68f21bd
Incorporate new error condition into documentation
EvergrowthConsulting b127662
Add enumerate-environment and update documentation
EvergrowthConsulting 827d593
Merge pull request #37 from crazycga/create_new_tasks
crazycga 980f6ee
Merge branch 'main' into dev_trunk
crazycga File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
207 changes: 207 additions & 0 deletions
207
bc-tools-extension/Enumerate-Environment/function_Enumerate-Environment.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| const { execSync } = require('child_process'); | ||
| const path = require('path'); | ||
| const os = require('os'); | ||
| const fs = require('fs'); | ||
| const { PassThrough } = require('stream'); | ||
| const { logger, parseBool, getToken, normalizePath } = require(path.join(__dirname, '_common', 'CommonTools.js')); | ||
|
|
||
| const inputFilenameAndPath = process.env.INPUT_FILEPATHANDNAME; | ||
|
|
||
| // this routine is intended to provide information about the agent on which it is running | ||
| // | ||
| // 1. platform | ||
| // 2. whoami | ||
| // 3. current working directory | ||
| // 4. Powershell version(s) | ||
| // 5. BCContainerHelper existence / version | ||
| // 6. Docker existence / version | ||
| // 7. Docker image list | ||
|
|
||
| (async () => { | ||
| let outputFilenameAndPath; | ||
|
|
||
| if (inputFilenameAndPath && inputFilenameAndPath.trim() !== '') { | ||
| outputFilenameAndPath = normalizePath(inputFilenameAndPath); | ||
| const pathInfo = path.parse(outputFilenameAndPath); | ||
|
|
||
| if (!pathInfo.base || !pathInfo.dir) { | ||
| logger.warn(`Invalid file path supplied: '${outputFilenameAndPath}'. Skipping file production.`); | ||
| outputFilenameAndPath = undefined; | ||
| } | ||
| } | ||
|
|
||
| logger.info('Invoking EGEnumerateEnvironment with the following parameters:'); | ||
| logger.info('FilePathAndName:'.padStart(2).padEnd(30) + `${outputFilenameAndPath}`); | ||
| logger.info(''); | ||
|
|
||
| // 0. setup | ||
| const logColWidth = 30; | ||
|
|
||
| // 1. platform | ||
| logger.info('[platform]:'.padEnd(logColWidth) + `${os.platform()}`); | ||
|
|
||
| // 2. whoami | ||
| let textOut; | ||
| try { | ||
| let whoami = execSync('whoami', { encoding: 'utf8' }); | ||
| textOut = whoami.toString().trim(); | ||
| if (textOut.length > 0) { | ||
| logger.info('[whoami]: '.padEnd(logColWidth) + `${textOut}`); | ||
| } else { | ||
| logger.info('[whoami]:'.padEnd(logColWidth) + 'Apparently a ghost; nothing returned'); | ||
| } | ||
| } catch (err) { | ||
| logger.error(`[whoami]: Encountered an error while executing a 'whoami'`); | ||
| logger.error(`[whoami]: Error: ${err}`); | ||
| } | ||
|
|
||
| // 3. current working directory | ||
| logger.info('[current working directory]:'.padEnd(logColWidth) + `${process.cwd()}`); | ||
|
|
||
| // 4. Powershell version(s) | ||
| let psVersion; | ||
| let pwshVersion; | ||
| if (os.platform() === "win32") { | ||
| try { | ||
| psVersion = execSync( | ||
| `powershell -NoProfile -Command "$v = $PSVersionTable.PSVersion; Write-Output ('' + $v.Major + '.' + $v.Minor + '.' + $v.Build + '.' + $v.Revision)"`, | ||
| { encoding: 'utf8' } | ||
| ).trim(); | ||
| logger.info('[powershell version]:'.padEnd(logColWidth) + `${psVersion}`); | ||
| } catch (err) { | ||
| logger.error(`[powershell version]: Encountered an error while executing a 'powerhsell version'`); | ||
| logger.error(`[powershell version]: Error: ${err}`); | ||
| } | ||
| } else { | ||
| psVersion = "[not installed; Linux environment]"; | ||
| logger.info('[powershell version]:'.padEnd(logColWidth) + `${psVersion}`); | ||
| } | ||
|
|
||
| try { | ||
| const isLinux = process.platform === 'linux'; | ||
|
|
||
| const psCommandRaw = '$PSVersionTable.PSVersion.ToString()'; | ||
| const psCommand = isLinux | ||
| ? psCommandRaw.replace(/(["\\$`])/g, '\\$1') // escape for bash | ||
| : psCommandRaw; // don't escape on Windows | ||
| const fullCommand = `pwsh -NoProfile -Command "${psCommand}"`; | ||
| //const quotedCommand = `"${psCommand.replace(/"/g, '\\"')}"`; | ||
| pwshVersion = execSync(fullCommand, { encoding: 'utf8' }).trim(); | ||
| logger.info('[pwsh version]:'.padEnd(logColWidth) + `${pwshVersion}`); | ||
| } catch (err) { | ||
| logger.error(`[pwsh version]: Encountered an error while executing a 'pwsh version'`); | ||
| logger.error(`[pwsh version]: Error: ${err}`); | ||
| } | ||
|
|
||
| // 5. BCContainerHelper existence / version | ||
| let result; | ||
| let BCContainerHelperPresent = false; | ||
|
|
||
| if (os.platform() === "win32") { | ||
| try { | ||
| const psCommand = `$modulePath = Get-Module -ListAvailable BCContainerHelper | Select-Object -First 1 -ExpandProperty Path; if ($modulePath) { $psd1 = $modulePath -replace '\\[^\\\\]+$', '.psd1'; if (Test-Path $psd1) { $lines = Get-Content $psd1 -Raw; if ($lines -match 'ModuleVersion\\s*=\\s*[\\"\\'']?([0-9\\.]+)[\\"\\'']?') { Write-Output $matches[1]; } else { Write-Output '[version not found]'; } } else { Write-Output '[not installed]'; } } else { Write-Output '[not installed]'; }`; | ||
|
|
||
| result = execSync(`powershell.exe -NoProfile -Command "${psCommand.replace(/\n/g, ' ').replace(/"/g, '\\"')}"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim(); | ||
| if (result === "") { result = '[not installed]' } | ||
| if (result && result != "") { | ||
| logger.info('[BCContainerHelper version]:'.padEnd(logColWidth) + `${result}`); | ||
| BCContainerHelperPresent = true; | ||
| } | ||
| BCContainerHelperPresent = true; | ||
| } catch (err) { | ||
| logger.error(`[BCContainerHelper]: Failed to query module: ${err.message}`); | ||
| logger.info(err); | ||
| } | ||
| } else { | ||
| result = "[not installed; Linux environment]"; | ||
| logger.info('[BCContainerHelper version]:'.padEnd(logColWidth) + `${result}`); | ||
| } | ||
|
|
||
| // 6. Docker existence / version | ||
| let DockerPresent = false; | ||
| let DockerVersionResult; | ||
| try { | ||
| DockerResult = execSync('docker version --format "{{.Client.Version}}"', { stdio: ['pipe', 'pipe', 'pipe'] }); | ||
| if (DockerResult === "") { DockerVersionResult = '[not installed]' } | ||
| else { DockerVersionResult = DockerResult.toString().trim(); } | ||
| if (DockerVersionResult && DockerVersionResult != "") { | ||
| logger.info('[dockerversion]:'.padEnd(logColWidth) + `${DockerVersionResult}`); | ||
| DockerPresent = true; | ||
| } | ||
| } catch (err) { | ||
| const msg = err.message || ''; | ||
| const stderr = err.stderr?.toString() || ''; | ||
|
|
||
| const combined = `${msg}\n${stderr}`; | ||
| const normalized = combined.toLowerCase(); | ||
| if ( | ||
| normalized.includes("'docker' is not recognized") || // Windows case | ||
| normalized.includes("command not found") || // Linux case | ||
| normalized.includes("no such file or directory") // fallback | ||
| ) { | ||
| DockerVersionResult = '[not installed]'; | ||
| if (DockerVersionResult && DockerVersionResult != "") { | ||
| logger.info('[dockerversion]:'.padEnd(logColWidth) + `${DockerVersionResult}`); | ||
| } | ||
| } else { | ||
| logger.error(`[dockerversion]: Unexpected error: ${combined}`); | ||
| } | ||
| } | ||
|
|
||
| // 7. Docker image list | ||
| let DockerPsObject; | ||
| if (DockerPresent) { | ||
| try { | ||
| const psResult = execSync('docker ps -a --no-trunc --format "{{json .}}"', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }); | ||
| const lines = psResult.trim().split('\n'); | ||
| DockerPsObject = lines.filter(line => line && line.trim().startsWith('{') && line.trim().endsWith('}')).map(line => JSON.parse(line)); | ||
|
|
||
| if (DockerPsObject.length > 0) { | ||
| logger.info('[dockerimage]:'.padEnd(logColWidth) + '**Name**'.padEnd(logColWidth) + '**Status**'); | ||
| DockerPsObject.forEach((image, idx) => { | ||
| if (image && image.name != "") { | ||
| logger.info('[dockerimage]:'.padEnd(logColWidth) + `${image.Names}`.padEnd(logColWidth) + `${image.Status}`); | ||
| } | ||
| }); | ||
| } else { | ||
| logger.info('[dockerimage]:'.padEnd(logColWidth) + '[no images]'); | ||
| } | ||
| } catch (err) { | ||
| const msg = err.message || ''; | ||
| const stderr = err.stderr?.toString() || ''; | ||
|
|
||
| const combined = `${msg}\n${stderr}`; | ||
| logger.error(`[dockerimage]: Unexpected error: ${combined}`); | ||
| } | ||
| } else { | ||
| logger.info('[dockerimage]:'.padEnd(logColWidth) + '[not installed]'); | ||
| } | ||
|
|
||
| // Deal with the file if requested (note it has already been parsed at the top of this routine) | ||
| if (outputFilenameAndPath) { | ||
|
|
||
| let dockerList = []; | ||
| try { | ||
| dockerList = DockerPsObject.filter(img => img && img.Names).map(img => ({ name: img.Names, status: img.Status })); | ||
| } catch { | ||
| dockerList = []; | ||
| } | ||
|
|
||
| let candidateFile = { | ||
| platform: os.platform(), | ||
| whoami: textOut, | ||
| workingDirectory: process.cwd(), | ||
| powershellVersion: psVersion, | ||
| pscoreVersion: pwshVersion, | ||
| bcContainerVersion: result, | ||
| dockerVersion: DockerVersionResult, | ||
| dockerImages: dockerList | ||
| } | ||
|
|
||
| let candidateFileString = JSON.stringify(candidateFile); | ||
| fs.writeFileSync(outputFilenameAndPath, candidateFileString); | ||
|
|
||
| logger.info(''); | ||
| logger.info(`Produced file at: ${outputFilenameAndPath}`); | ||
| } | ||
| })(); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| { | ||
| "id": "0d4e6693-bdcb-47c0-a373-67a34549da07", | ||
| "name": "EGEnumerateEnvironment", | ||
| "friendlyName": "Enumerate compiling environment", | ||
| "description": "Provides information about the pipeline environment in the context of the agent.", | ||
| "helpMarkDown": "Please open a GitHub issue at https://github.com/crazycga/bcdevopsextension/issues for queries or support.", | ||
| "category": "Build", | ||
| "author": "Evergrowth Consulting", | ||
| "version": { | ||
| "Major": 0, | ||
| "Minor": 1, | ||
| "Patch": 5 | ||
| }, | ||
| "instanceNameFormat": "Enumerate compiling environment", | ||
| "inputs": [ | ||
| { | ||
| "name": "ProduceFile", | ||
| "type": "boolean", | ||
| "label": "Produce file", | ||
| "defaultValue": false, | ||
| "required": false, | ||
| "helpMarkDown": "Specifies whether or not to produce an output file as one of the artifacts." | ||
| }, | ||
| { | ||
| "name": "FilePathAndName", | ||
| "type": "string", | ||
| "label": "Output file path and name", | ||
| "defaultValue": "$(Build.ArtifactStagingDirectory)/environment.$(System.StageName).$(Agent.JobName).$(Build.BuildId).json", | ||
| "required": false, | ||
| "helpMarkDown": "The output path and name of the output file if specified; default '$(Build.ArtifactStagingDirectory)/environment.$(System.StageName).$(Agent.JobName).$(Build.BuildId).json'" | ||
| } | ||
| ], | ||
| "execution": { | ||
| "Node16": { | ||
| "target": "function_Enumerate-Environment.js" | ||
| }, | ||
| "Node20_1": { | ||
| "target": "function_Enumerate-Environment.js" | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.