diff --git a/.github/workflows/test-action.yml b/.github/workflows/test-action.yml index 81957b48..9b7ca2e6 100644 --- a/.github/workflows/test-action.yml +++ b/.github/workflows/test-action.yml @@ -7,7 +7,7 @@ on: - '*' permissions: - contents: read + contents: write pull-requests: write checks: write actions: read @@ -27,7 +27,9 @@ jobs: uses: ./ with: dependencies.enabled: false - policies: copyleft + policies: copyleft, und + policies.halt_on_failure: 'false' + api.key: ${{ secrets.SC_API_KEY }} - name: Print stdout scan command diff --git a/CHANGELOG.md b/CHANGELOG.md index 252ac3e7..6cc560e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.3] - 2025-09-18 +### Added +- Added annotations for file and snippet matches +- Added commit comment for each match +- Added link to auto create scanoss.json file + ## [1.2.2] - 2025-09-09 ### Added - Added policies input trimming @@ -125,3 +131,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [1.2.0]: https://github.com/scanoss/gha-code-scan/compare/v1.1.0...v1.2.0 [1.2.1]: https://github.com/scanoss/gha-code-scan/compare/v1.2.0...v1.2.1 [1.2.2]: https://github.com/scanoss/gha-code-scan/compare/v1.2.1...v1.2.2 +[1.2.3]: https://github.com/scanoss/gha-code-scan/compare/v1.2.2...v1.2.3 diff --git a/README.md b/README.md index 2ab371c7..3d77e81d 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ For example workflow runs, check out our | licenses.copyleft.include | List of Copyleft licenses to append to the default list. Provide licenses as a comma-separated list. | Optional | - | | licenses.copyleft.exclude | List of Copyleft licenses to remove from default list. Provide licenses as a comma-separated list. | Optional | - | | licenses.copyleft.explicit | Explicit list of Copyleft licenses to consider. Provide licenses as a comma-separated list. | Optional | - | -| runtimeContainer | Runtime URL | Optional | `ghcr.io/scanoss/scanoss-py:v1.31.5` | +| runtimeContainer | Runtime URL | Optional | `ghcr.io/scanoss/scanoss-py:v1.32.0` | | skipSnippets | Skip the generation of snippets. (scanFiles option must be enabled) | Optional | `false` | | scanFiles | Enable or disable file and snippet scanning | Optional | `true` | | scanossSettings | Settings file to use for scanning. See the SCANOSS settings [documentation](https://scanoss.readthedocs.io/projects/scanoss-py/en/latest/#settings-file) | Optional | `true` | diff --git a/__tests__/github.utils.test.ts b/__tests__/github.utils.test.ts index 14c39f82..829dc683 100644 --- a/__tests__/github.utils.test.ts +++ b/__tests__/github.utils.test.ts @@ -22,7 +22,6 @@ */ import { context, getOctokit } from '@actions/github'; -import * as core from '@actions/core'; import { getSHA, isPullRequest, createCommentOnPR, getFirstRunId } from '../src/utils/github.utils'; // Mock external dependencies @@ -34,10 +33,6 @@ jest.mock('../src/app.input', () => ({ const mockOctokit = { rest: { - actions: { - getWorkflowRun: jest.fn(), - listWorkflowRuns: jest.fn() - }, issues: { createComment: jest.fn() } @@ -182,80 +177,22 @@ describe('GitHub Utils', () => { }); describe('getFirstRunId', () => { - it('should return current runId for non-workflow_dispatch events', async () => { - (context.eventName as any) = 'push'; + it('should always return current runId', async () => { (context.runId as any) = 98765; + (context.eventName as any) = 'push'; // Not workflow_dispatch const result = await getFirstRunId(); expect(result).toBe(98765); - expect(mockOctokit.rest.actions.getWorkflowRun).not.toHaveBeenCalled(); - }); - - it('should find first run for workflow_dispatch events', async () => { - (context.eventName as any) = 'workflow_dispatch'; - (context.runId as any) = 12345; - (context.repo as any) = { owner: 'test-owner', repo: 'test-repo' }; - (context.sha as any) = 'test-sha-123'; - - // Mock current workflow run - mockOctokit.rest.actions.getWorkflowRun.mockResolvedValue({ - data: { - workflow_id: 'test-workflow', - head_sha: 'test-sha-123' - } - }); - - // Mock workflow runs list - mockOctokit.rest.actions.listWorkflowRuns.mockResolvedValue({ - data: { - workflow_runs: [ - { id: 11111, created_at: '2023-01-03T10:00:00Z', event: 'push', head_sha: 'test-sha-123' }, - { id: 22222, created_at: '2023-01-02T10:00:00Z', event: 'push', head_sha: 'test-sha-123' }, - { id: 33333, created_at: '2023-01-01T10:00:00Z', event: 'push', head_sha: 'test-sha-123' } // Oldest - ] - } - }); - - const infoSpy = jest.spyOn(core, 'info').mockImplementation(); - - const result = await getFirstRunId(); - - expect(result).toBe(33333); // Should return the oldest run - expect(infoSpy).toHaveBeenCalledWith('First Run ID found: 33333'); - - infoSpy.mockRestore(); }); - it('should return current runId if no first run is found', async () => { - (context.eventName as any) = 'workflow_dispatch'; + it('should handle different run IDs', async () => { (context.runId as any) = 12345; - - mockOctokit.rest.actions.getWorkflowRun.mockResolvedValue({ - data: { - workflow_id: 'test-workflow', - head_sha: 'test-sha-123' - } - }); - - mockOctokit.rest.actions.listWorkflowRuns.mockResolvedValue({ - data: { - workflow_runs: [] - } - }); + (context.eventName as any) = 'pull_request'; // Not workflow_dispatch const result = await getFirstRunId(); - expect(result).toBe(12345); // Should return current runId as fallback - }); - - it('should throw API errors (no error handling in implementation)', async () => { - (context.eventName as any) = 'workflow_dispatch'; - (context.runId as any) = 12345; - - mockOctokit.rest.actions.getWorkflowRun.mockRejectedValue(new Error('API Error')); - - await expect(getFirstRunId()).rejects.toThrow('API Error'); + expect(result).toBe(12345); }); }); }); diff --git a/__tests__/undeclared-policy-check.test.ts b/__tests__/undeclared-policy-check.test.ts index c7e9d68e..602b4f06 100644 --- a/__tests__/undeclared-policy-check.test.ts +++ b/__tests__/undeclared-policy-check.test.ts @@ -43,7 +43,8 @@ jest.mock('@actions/github', () => ({ context: { repo: { owner: 'mock-owner', repo: 'mock-repo' }, serverUrl: 'github', - runId: 12345678 + runId: 12345678, + ref: 'refs/heads/test-branch' // Add other properties as needed }, getOctokit: jest.fn().mockReturnValue({ diff --git a/action.yml b/action.yml index 50d98503..e7dedcdc 100644 --- a/action.yml +++ b/action.yml @@ -59,7 +59,7 @@ inputs: required: false runtimeContainer: description: 'Specify runtime container to perform the scan.' - default: 'ghcr.io/scanoss/scanoss-py:v1.31.5' + default: 'ghcr.io/scanoss/scanoss-py:v1.32.0' required: false skipSnippets: description: 'Skip the generation of snippets.' @@ -100,6 +100,10 @@ inputs: deptrack.projectversion: description: 'Dependency Track project version' required: false + matchAnnotations: + description: 'Enable or disable match annotations' + required: false + default: 'true' outputs: result-filepath: diff --git a/dist/index.js b/dist/index.js index 28bce407..7f3b0f67 100644 --- a/dist/index.js +++ b/dist/index.js @@ -43154,7 +43154,7 @@ module.exports = __toCommonJS(dist_src_exports); var import_universal_user_agent = __nccwpck_require__(45030); // pkg/dist-src/version.js -var VERSION = "9.0.4"; +var VERSION = "9.0.6"; // pkg/dist-src/defaults.js var userAgent = `octokit-endpoint.js/${VERSION} ${(0, import_universal_user_agent.getUserAgent)()}`; @@ -43259,9 +43259,9 @@ function addQueryParameters(url, parameters) { } // pkg/dist-src/util/extract-url-variable-names.js -var urlVariableRegex = /\{[^}]+\}/g; +var urlVariableRegex = /\{[^{}}]+\}/g; function removeNonChars(variableName) { - return variableName.replace(/^\W+|\W+$/g, "").split(/,/); + return variableName.replace(/(?:^\W+)|(?:(? { const format = options.mediaType.format ? `.${options.mediaType.format}` : "+json"; return `application/vnd.github.${preview}-preview${format}`; @@ -43696,7 +43696,7 @@ __export(dist_src_exports, { module.exports = __toCommonJS(dist_src_exports); // pkg/dist-src/version.js -var VERSION = "9.1.5"; +var VERSION = "9.2.2"; // pkg/dist-src/normalize-paginated-list-response.js function normalizePaginatedListResponse(response) { @@ -43744,7 +43744,7 @@ function iterator(octokit, route, parameters) { const response = await requestMethod({ method, url, headers }); const normalizedResponse = normalizePaginatedListResponse(response); url = ((normalizedResponse.headers.link || "").match( - /<([^>]+)>;\s*rel="next"/ + /<([^<>]+)>;\s*rel="next"/ ) || [])[1]; return { value: normalizedResponse }; } catch (error) { @@ -43857,6 +43857,8 @@ var paginatingEndpoints = [ "GET /orgs/{org}/members/{username}/codespaces", "GET /orgs/{org}/migrations", "GET /orgs/{org}/migrations/{migration_id}/repositories", + "GET /orgs/{org}/organization-roles/{role_id}/teams", + "GET /orgs/{org}/organization-roles/{role_id}/users", "GET /orgs/{org}/outside_collaborators", "GET /orgs/{org}/packages", "GET /orgs/{org}/packages/{package_type}/{package_name}/versions", @@ -46357,7 +46359,7 @@ var RequestError = class extends Error { if (options.request.headers.authorization) { requestCopy.headers = Object.assign({}, options.request.headers, { authorization: options.request.headers.authorization.replace( - / .*$/, + /(?]+)>; rel="deprecation"/); + const matches = headers.link && headers.link.match(/<([^<>]+)>; rel="deprecation"/); const deprecationLink = matches && matches.pop(); log.warn( `[@octokit/request] "${requestOptions.method} ${requestOptions.url}" is deprecated. It is scheduled to be removed on ${headers.sunset}${deprecationLink ? `. See ${deprecationLink}` : ""}` @@ -46569,11 +46572,17 @@ async function getResponseData(response) { function toErrorMessage(data) { if (typeof data === "string") return data; + let suffix; + if ("documentation_url" in data) { + suffix = ` - ${data.documentation_url}`; + } else { + suffix = ""; + } if ("message" in data) { if (Array.isArray(data.errors)) { - return `${data.message}: ${data.errors.map(JSON.stringify).join(", ")}`; + return `${data.message}: ${data.errors.map(JSON.stringify).join(", ")}${suffix}`; } - return data.message; + return `${data.message}${suffix}`; } return `Unknown error: ${JSON.stringify(data)}`; } @@ -65716,6 +65725,83 @@ Buffers.prototype.toString = function(encoding, start, end) { } +/***/ }), + +/***/ 19227: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var bind = __nccwpck_require__(88334); + +var $apply = __nccwpck_require__(54177); +var $call = __nccwpck_require__(2808); +var $reflectApply = __nccwpck_require__(48309); + +/** @type {import('./actualApply')} */ +module.exports = $reflectApply || bind.call($call, $apply); + + +/***/ }), + +/***/ 54177: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./functionApply')} */ +module.exports = Function.prototype.apply; + + +/***/ }), + +/***/ 2808: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./functionCall')} */ +module.exports = Function.prototype.call; + + +/***/ }), + +/***/ 86815: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var bind = __nccwpck_require__(88334); +var $TypeError = __nccwpck_require__(6361); + +var $call = __nccwpck_require__(2808); +var $actualApply = __nccwpck_require__(19227); + +/** @type {(args: [Function, thisArg?: unknown, ...args: unknown[]]) => Function} TODO FIXME, find a way to use import('.') */ +module.exports = function callBindBasic(args) { + if (args.length < 1 || typeof args[0] !== 'function') { + throw new $TypeError('a function is required'); + } + return $actualApply(bind, $call, args); +}; + + +/***/ }), + +/***/ 48309: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./reflectApply')} */ +module.exports = typeof Reflect !== 'undefined' && Reflect && Reflect.apply; + + /***/ }), /***/ 46533: @@ -67987,6 +68073,205 @@ class Deprecation extends Error { exports.Deprecation = Deprecation; +/***/ }), + +/***/ 62693: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var callBind = __nccwpck_require__(86815); +var gOPD = __nccwpck_require__(18501); + +var hasProtoAccessor; +try { + // eslint-disable-next-line no-extra-parens, no-proto + hasProtoAccessor = /** @type {{ __proto__?: typeof Array.prototype }} */ ([]).__proto__ === Array.prototype; +} catch (e) { + if (!e || typeof e !== 'object' || !('code' in e) || e.code !== 'ERR_PROTO_ACCESS') { + throw e; + } +} + +// eslint-disable-next-line no-extra-parens +var desc = !!hasProtoAccessor && gOPD && gOPD(Object.prototype, /** @type {keyof typeof Object.prototype} */ ('__proto__')); + +var $Object = Object; +var $getPrototypeOf = $Object.getPrototypeOf; + +/** @type {import('./get')} */ +module.exports = desc && typeof desc.get === 'function' + ? callBind([desc.get]) + : typeof $getPrototypeOf === 'function' + ? /** @type {import('./get')} */ function getDunder(value) { + // eslint-disable-next-line eqeqeq + return $getPrototypeOf(value == null ? value : $Object(value)); + } + : false; + + +/***/ }), + +/***/ 6123: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('.')} */ +var $defineProperty = Object.defineProperty || false; +if ($defineProperty) { + try { + $defineProperty({}, 'a', { value: 1 }); + } catch (e) { + // IE 8 has a broken defineProperty + $defineProperty = false; + } +} + +module.exports = $defineProperty; + + +/***/ }), + +/***/ 91933: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./eval')} */ +module.exports = EvalError; + + +/***/ }), + +/***/ 28015: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('.')} */ +module.exports = Error; + + +/***/ }), + +/***/ 54415: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./range')} */ +module.exports = RangeError; + + +/***/ }), + +/***/ 46279: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./ref')} */ +module.exports = ReferenceError; + + +/***/ }), + +/***/ 75474: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./syntax')} */ +module.exports = SyntaxError; + + +/***/ }), + +/***/ 6361: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./type')} */ +module.exports = TypeError; + + +/***/ }), + +/***/ 5065: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./uri')} */ +module.exports = URIError; + + +/***/ }), + +/***/ 78308: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('.')} */ +module.exports = Object; + + +/***/ }), + +/***/ 11770: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var GetIntrinsic = __nccwpck_require__(74538); + +var $defineProperty = GetIntrinsic('%Object.defineProperty%', true); + +var hasToStringTag = __nccwpck_require__(99038)(); +var hasOwn = __nccwpck_require__(62157); +var $TypeError = __nccwpck_require__(6361); + +var toStringTag = hasToStringTag ? Symbol.toStringTag : null; + +/** @type {import('.')} */ +module.exports = function setToStringTag(object, value) { + var overrideIfSet = arguments.length > 2 && !!arguments[2] && arguments[2].force; + var nonConfigurable = arguments.length > 2 && !!arguments[2] && arguments[2].nonConfigurable; + if ( + (typeof overrideIfSet !== 'undefined' && typeof overrideIfSet !== 'boolean') + || (typeof nonConfigurable !== 'undefined' && typeof nonConfigurable !== 'boolean') + ) { + throw new $TypeError('if provided, the `overrideIfSet` and `nonConfigurable` options must be booleans'); + } + if (toStringTag && (overrideIfSet || !hasOwn(object, toStringTag))) { + if ($defineProperty) { + $defineProperty(object, toStringTag, { + configurable: !nonConfigurable, + enumerable: false, + value: value, + writable: false + }); + } else { + object[toStringTag] = value; // eslint-disable-line no-param-reassign + } + } +}; + + /***/ }), /***/ 84697: @@ -68972,6 +69257,9 @@ module.exports = class FastFIFO { /***/ 64334: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + var CombinedStream = __nccwpck_require__(85443); var util = __nccwpck_require__(73837); var path = __nccwpck_require__(71017); @@ -68980,23 +69268,20 @@ var https = __nccwpck_require__(95687); var parseUrl = (__nccwpck_require__(57310).parse); var fs = __nccwpck_require__(57147); var Stream = (__nccwpck_require__(12781).Stream); +var crypto = __nccwpck_require__(6113); var mime = __nccwpck_require__(43583); var asynckit = __nccwpck_require__(14812); +var setToStringTag = __nccwpck_require__(11770); +var hasOwn = __nccwpck_require__(62157); var populate = __nccwpck_require__(17142); -// Public API -module.exports = FormData; - -// make it a Stream -util.inherits(FormData, CombinedStream); - /** * Create readable "multipart/form-data" streams. * Can be used to submit forms * and file uploads to other web applications. * * @constructor - * @param {Object} options - Properties to be added/overriden for FormData and CombinedStream + * @param {object} options - Properties to be added/overriden for FormData and CombinedStream */ function FormData(options) { if (!(this instanceof FormData)) { @@ -69009,35 +69294,39 @@ function FormData(options) { CombinedStream.call(this); - options = options || {}; - for (var option in options) { + options = options || {}; // eslint-disable-line no-param-reassign + for (var option in options) { // eslint-disable-line no-restricted-syntax this[option] = options[option]; } } +// make it a Stream +util.inherits(FormData, CombinedStream); + FormData.LINE_BREAK = '\r\n'; FormData.DEFAULT_CONTENT_TYPE = 'application/octet-stream'; -FormData.prototype.append = function(field, value, options) { - - options = options || {}; +FormData.prototype.append = function (field, value, options) { + options = options || {}; // eslint-disable-line no-param-reassign // allow filename as single option - if (typeof options == 'string') { - options = {filename: options}; + if (typeof options === 'string') { + options = { filename: options }; // eslint-disable-line no-param-reassign } var append = CombinedStream.prototype.append.bind(this); // all that streamy business can't handle numbers - if (typeof value == 'number') { - value = '' + value; + if (typeof value === 'number' || value == null) { + value = String(value); // eslint-disable-line no-param-reassign } // https://github.com/felixge/node-form-data/issues/38 - if (util.isArray(value)) { - // Please convert your array into string - // the way web server expects it + if (Array.isArray(value)) { + /* + * Please convert your array into string + * the way web server expects it + */ this._error(new Error('Arrays are not supported.')); return; } @@ -69053,15 +69342,17 @@ FormData.prototype.append = function(field, value, options) { this._trackLength(header, value, options); }; -FormData.prototype._trackLength = function(header, value, options) { +FormData.prototype._trackLength = function (header, value, options) { var valueLength = 0; - // used w/ getLengthSync(), when length is known. - // e.g. for streaming directly from a remote server, - // w/ a known file a size, and not wanting to wait for - // incoming file to finish to get its size. + /* + * used w/ getLengthSync(), when length is known. + * e.g. for streaming directly from a remote server, + * w/ a known file a size, and not wanting to wait for + * incoming file to finish to get its size. + */ if (options.knownLength != null) { - valueLength += +options.knownLength; + valueLength += Number(options.knownLength); } else if (Buffer.isBuffer(value)) { valueLength = value.length; } else if (typeof value === 'string') { @@ -69071,12 +69362,10 @@ FormData.prototype._trackLength = function(header, value, options) { this._valueLength += valueLength; // @check why add CRLF? does this account for custom/multiple CRLFs? - this._overheadLength += - Buffer.byteLength(header) + - FormData.LINE_BREAK.length; + this._overheadLength += Buffer.byteLength(header) + FormData.LINE_BREAK.length; // empty or either doesn't have path or not an http response or not a stream - if (!value || ( !value.path && !(value.readable && value.hasOwnProperty('httpVersion')) && !(value instanceof Stream))) { + if (!value || (!value.path && !(value.readable && hasOwn(value, 'httpVersion')) && !(value instanceof Stream))) { return; } @@ -69086,10 +69375,8 @@ FormData.prototype._trackLength = function(header, value, options) { } }; -FormData.prototype._lengthRetriever = function(value, callback) { - - if (value.hasOwnProperty('fd')) { - +FormData.prototype._lengthRetriever = function (value, callback) { + if (hasOwn(value, 'fd')) { // take read range into a account // `end` = Infinity –> read file till the end // @@ -69098,54 +69385,52 @@ FormData.prototype._lengthRetriever = function(value, callback) { // Fix it when node fixes it. // https://github.com/joyent/node/issues/7819 if (value.end != undefined && value.end != Infinity && value.start != undefined) { - // when end specified // no need to calculate range // inclusive, starts with 0 - callback(null, value.end + 1 - (value.start ? value.start : 0)); + callback(null, value.end + 1 - (value.start ? value.start : 0)); // eslint-disable-line callback-return - // not that fast snoopy + // not that fast snoopy } else { // still need to fetch file size from fs - fs.stat(value.path, function(err, stat) { - - var fileSize; - + fs.stat(value.path, function (err, stat) { if (err) { callback(err); return; } // update final size based on the range options - fileSize = stat.size - (value.start ? value.start : 0); + var fileSize = stat.size - (value.start ? value.start : 0); callback(null, fileSize); }); } - // or http response - } else if (value.hasOwnProperty('httpVersion')) { - callback(null, +value.headers['content-length']); + // or http response + } else if (hasOwn(value, 'httpVersion')) { + callback(null, Number(value.headers['content-length'])); // eslint-disable-line callback-return - // or request stream http://github.com/mikeal/request - } else if (value.hasOwnProperty('httpModule')) { + // or request stream http://github.com/mikeal/request + } else if (hasOwn(value, 'httpModule')) { // wait till response come back - value.on('response', function(response) { + value.on('response', function (response) { value.pause(); - callback(null, +response.headers['content-length']); + callback(null, Number(response.headers['content-length'])); }); value.resume(); - // something else + // something else } else { - callback('Unknown stream'); + callback('Unknown stream'); // eslint-disable-line callback-return } }; -FormData.prototype._multiPartHeader = function(field, value, options) { - // custom header specified (as string)? - // it becomes responsible for boundary - // (e.g. to handle extra CRLFs on .NET servers) - if (typeof options.header == 'string') { +FormData.prototype._multiPartHeader = function (field, value, options) { + /* + * custom header specified (as string)? + * it becomes responsible for boundary + * (e.g. to handle extra CRLFs on .NET servers) + */ + if (typeof options.header === 'string') { return options.header; } @@ -69153,7 +69438,7 @@ FormData.prototype._multiPartHeader = function(field, value, options) { var contentType = this._getContentType(value, options); var contents = ''; - var headers = { + var headers = { // add custom disposition as third element or keep it two elements if not 'Content-Disposition': ['form-data', 'name="' + field + '"'].concat(contentDisposition || []), // if no content type. allow it to be empty array @@ -69161,77 +69446,74 @@ FormData.prototype._multiPartHeader = function(field, value, options) { }; // allow custom headers. - if (typeof options.header == 'object') { + if (typeof options.header === 'object') { populate(headers, options.header); } var header; - for (var prop in headers) { - if (!headers.hasOwnProperty(prop)) continue; - header = headers[prop]; + for (var prop in headers) { // eslint-disable-line no-restricted-syntax + if (hasOwn(headers, prop)) { + header = headers[prop]; - // skip nullish headers. - if (header == null) { - continue; - } + // skip nullish headers. + if (header == null) { + continue; // eslint-disable-line no-restricted-syntax, no-continue + } - // convert all headers to arrays. - if (!Array.isArray(header)) { - header = [header]; - } + // convert all headers to arrays. + if (!Array.isArray(header)) { + header = [header]; + } - // add non-empty headers. - if (header.length) { - contents += prop + ': ' + header.join('; ') + FormData.LINE_BREAK; + // add non-empty headers. + if (header.length) { + contents += prop + ': ' + header.join('; ') + FormData.LINE_BREAK; + } } } return '--' + this.getBoundary() + FormData.LINE_BREAK + contents + FormData.LINE_BREAK; }; -FormData.prototype._getContentDisposition = function(value, options) { - - var filename - , contentDisposition - ; +FormData.prototype._getContentDisposition = function (value, options) { // eslint-disable-line consistent-return + var filename; if (typeof options.filepath === 'string') { // custom filepath for relative paths filename = path.normalize(options.filepath).replace(/\\/g, '/'); - } else if (options.filename || value.name || value.path) { - // custom filename take precedence - // formidable and the browser add a name property - // fs- and request- streams have path property - filename = path.basename(options.filename || value.name || value.path); - } else if (value.readable && value.hasOwnProperty('httpVersion')) { + } else if (options.filename || (value && (value.name || value.path))) { + /* + * custom filename take precedence + * formidable and the browser add a name property + * fs- and request- streams have path property + */ + filename = path.basename(options.filename || (value && (value.name || value.path))); + } else if (value && value.readable && hasOwn(value, 'httpVersion')) { // or try http response filename = path.basename(value.client._httpMessage.path || ''); } if (filename) { - contentDisposition = 'filename="' + filename + '"'; + return 'filename="' + filename + '"'; } - - return contentDisposition; }; -FormData.prototype._getContentType = function(value, options) { - +FormData.prototype._getContentType = function (value, options) { // use custom content-type above all var contentType = options.contentType; // or try `name` from formidable, browser - if (!contentType && value.name) { + if (!contentType && value && value.name) { contentType = mime.lookup(value.name); } // or try `path` from fs-, request- streams - if (!contentType && value.path) { + if (!contentType && value && value.path) { contentType = mime.lookup(value.path); } // or if it's http-reponse - if (!contentType && value.readable && value.hasOwnProperty('httpVersion')) { + if (!contentType && value && value.readable && hasOwn(value, 'httpVersion')) { contentType = value.headers['content-type']; } @@ -69241,18 +69523,18 @@ FormData.prototype._getContentType = function(value, options) { } // fallback to the default content type if `value` is not simple value - if (!contentType && typeof value == 'object') { + if (!contentType && value && typeof value === 'object') { contentType = FormData.DEFAULT_CONTENT_TYPE; } return contentType; }; -FormData.prototype._multiPartFooter = function() { - return function(next) { +FormData.prototype._multiPartFooter = function () { + return function (next) { var footer = FormData.LINE_BREAK; - var lastPart = (this._streams.length === 0); + var lastPart = this._streams.length === 0; if (lastPart) { footer += this._lastBoundary(); } @@ -69261,18 +69543,18 @@ FormData.prototype._multiPartFooter = function() { }.bind(this); }; -FormData.prototype._lastBoundary = function() { +FormData.prototype._lastBoundary = function () { return '--' + this.getBoundary() + '--' + FormData.LINE_BREAK; }; -FormData.prototype.getHeaders = function(userHeaders) { +FormData.prototype.getHeaders = function (userHeaders) { var header; var formHeaders = { 'content-type': 'multipart/form-data; boundary=' + this.getBoundary() }; - for (header in userHeaders) { - if (userHeaders.hasOwnProperty(header)) { + for (header in userHeaders) { // eslint-disable-line no-restricted-syntax + if (hasOwn(userHeaders, header)) { formHeaders[header.toLowerCase()] = userHeaders[header]; } } @@ -69280,11 +69562,14 @@ FormData.prototype.getHeaders = function(userHeaders) { return formHeaders; }; -FormData.prototype.setBoundary = function(boundary) { +FormData.prototype.setBoundary = function (boundary) { + if (typeof boundary !== 'string') { + throw new TypeError('FormData boundary must be a string'); + } this._boundary = boundary; }; -FormData.prototype.getBoundary = function() { +FormData.prototype.getBoundary = function () { if (!this._boundary) { this._generateBoundary(); } @@ -69292,60 +69577,55 @@ FormData.prototype.getBoundary = function() { return this._boundary; }; -FormData.prototype.getBuffer = function() { - var dataBuffer = new Buffer.alloc( 0 ); +FormData.prototype.getBuffer = function () { + var dataBuffer = new Buffer.alloc(0); // eslint-disable-line new-cap var boundary = this.getBoundary(); // Create the form content. Add Line breaks to the end of data. for (var i = 0, len = this._streams.length; i < len; i++) { if (typeof this._streams[i] !== 'function') { - // Add content to the buffer. - if(Buffer.isBuffer(this._streams[i])) { - dataBuffer = Buffer.concat( [dataBuffer, this._streams[i]]); - }else { - dataBuffer = Buffer.concat( [dataBuffer, Buffer.from(this._streams[i])]); + if (Buffer.isBuffer(this._streams[i])) { + dataBuffer = Buffer.concat([dataBuffer, this._streams[i]]); + } else { + dataBuffer = Buffer.concat([dataBuffer, Buffer.from(this._streams[i])]); } // Add break after content. - if (typeof this._streams[i] !== 'string' || this._streams[i].substring( 2, boundary.length + 2 ) !== boundary) { - dataBuffer = Buffer.concat( [dataBuffer, Buffer.from(FormData.LINE_BREAK)] ); + if (typeof this._streams[i] !== 'string' || this._streams[i].substring(2, boundary.length + 2) !== boundary) { + dataBuffer = Buffer.concat([dataBuffer, Buffer.from(FormData.LINE_BREAK)]); } } } // Add the footer and return the Buffer object. - return Buffer.concat( [dataBuffer, Buffer.from(this._lastBoundary())] ); + return Buffer.concat([dataBuffer, Buffer.from(this._lastBoundary())]); }; -FormData.prototype._generateBoundary = function() { +FormData.prototype._generateBoundary = function () { // This generates a 50 character boundary similar to those used by Firefox. - // They are optimized for boyer-moore parsing. - var boundary = '--------------------------'; - for (var i = 0; i < 24; i++) { - boundary += Math.floor(Math.random() * 10).toString(16); - } - this._boundary = boundary; + // They are optimized for boyer-moore parsing. + this._boundary = '--------------------------' + crypto.randomBytes(12).toString('hex'); }; // Note: getLengthSync DOESN'T calculate streams length -// As workaround one can calculate file size manually -// and add it as knownLength option -FormData.prototype.getLengthSync = function() { +// As workaround one can calculate file size manually and add it as knownLength option +FormData.prototype.getLengthSync = function () { var knownLength = this._overheadLength + this._valueLength; - // Don't get confused, there are 3 "internal" streams for each keyval pair - // so it basically checks if there is any value added to the form + // Don't get confused, there are 3 "internal" streams for each keyval pair so it basically checks if there is any value added to the form if (this._streams.length) { knownLength += this._lastBoundary().length; } // https://github.com/form-data/form-data/issues/40 if (!this.hasKnownLength()) { - // Some async length retrievers are present - // therefore synchronous length calculation is false. - // Please use getLength(callback) to get proper length + /* + * Some async length retrievers are present + * therefore synchronous length calculation is false. + * Please use getLength(callback) to get proper length + */ this._error(new Error('Cannot calculate proper length in synchronous way.')); } @@ -69355,7 +69635,7 @@ FormData.prototype.getLengthSync = function() { // Public API to check if length of added values is known // https://github.com/form-data/form-data/issues/196 // https://github.com/form-data/form-data/issues/262 -FormData.prototype.hasKnownLength = function() { +FormData.prototype.hasKnownLength = function () { var hasKnownLength = true; if (this._valuesToMeasure.length) { @@ -69365,7 +69645,7 @@ FormData.prototype.hasKnownLength = function() { return hasKnownLength; }; -FormData.prototype.getLength = function(cb) { +FormData.prototype.getLength = function (cb) { var knownLength = this._overheadLength + this._valueLength; if (this._streams.length) { @@ -69377,13 +69657,13 @@ FormData.prototype.getLength = function(cb) { return; } - asynckit.parallel(this._valuesToMeasure, this._lengthRetriever, function(err, values) { + asynckit.parallel(this._valuesToMeasure, this._lengthRetriever, function (err, values) { if (err) { cb(err); return; } - values.forEach(function(length) { + values.forEach(function (length) { knownLength += length; }); @@ -69391,31 +69671,26 @@ FormData.prototype.getLength = function(cb) { }); }; -FormData.prototype.submit = function(params, cb) { - var request - , options - , defaults = {method: 'post'} - ; - - // parse provided url if it's string - // or treat it as options object - if (typeof params == 'string') { +FormData.prototype.submit = function (params, cb) { + var request; + var options; + var defaults = { method: 'post' }; - params = parseUrl(params); + // parse provided url if it's string or treat it as options object + if (typeof params === 'string') { + params = parseUrl(params); // eslint-disable-line no-param-reassign + /* eslint sort-keys: 0 */ options = populate({ port: params.port, path: params.pathname, host: params.hostname, protocol: params.protocol }, defaults); - - // use custom params - } else { - + } else { // use custom params options = populate(params, defaults); // if no port provided use default one if (!options.port) { - options.port = options.protocol == 'https:' ? 443 : 80; + options.port = options.protocol === 'https:' ? 443 : 80; } } @@ -69423,14 +69698,14 @@ FormData.prototype.submit = function(params, cb) { options.headers = this.getHeaders(params.headers); // https if specified, fallback to http in any other case - if (options.protocol == 'https:') { + if (options.protocol === 'https:') { request = https.request(options); } else { request = http.request(options); } // get content length and fire away - this.getLength(function(err, length) { + this.getLength(function (err, length) { if (err && err !== 'Unknown stream') { this._error(err); return; @@ -69449,7 +69724,7 @@ FormData.prototype.submit = function(params, cb) { request.removeListener('error', callback); request.removeListener('response', onResponse); - return cb.call(this, error, responce); + return cb.call(this, error, responce); // eslint-disable-line no-invalid-this }; onResponse = callback.bind(this, null); @@ -69462,7 +69737,7 @@ FormData.prototype.submit = function(params, cb) { return request; }; -FormData.prototype._error = function(err) { +FormData.prototype._error = function (err) { if (!this.error) { this.error = err; this.pause(); @@ -69473,6 +69748,10 @@ FormData.prototype._error = function(err) { FormData.prototype.toString = function () { return '[object FormData]'; }; +setToStringTag(FormData, 'FormData'); + +// Public API +module.exports = FormData; /***/ }), @@ -69480,18 +69759,606 @@ FormData.prototype.toString = function () { /***/ 17142: /***/ ((module) => { -// populates missing values -module.exports = function(dst, src) { +"use strict"; - Object.keys(src).forEach(function(prop) - { - dst[prop] = dst[prop] || src[prop]; + +// populates missing values +module.exports = function (dst, src) { + Object.keys(src).forEach(function (prop) { + dst[prop] = dst[prop] || src[prop]; // eslint-disable-line no-param-reassign }); return dst; }; +/***/ }), + +/***/ 19320: +/***/ ((module) => { + +"use strict"; + + +/* eslint no-invalid-this: 1 */ + +var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible '; +var toStr = Object.prototype.toString; +var max = Math.max; +var funcType = '[object Function]'; + +var concatty = function concatty(a, b) { + var arr = []; + + for (var i = 0; i < a.length; i += 1) { + arr[i] = a[i]; + } + for (var j = 0; j < b.length; j += 1) { + arr[j + a.length] = b[j]; + } + + return arr; +}; + +var slicy = function slicy(arrLike, offset) { + var arr = []; + for (var i = offset || 0, j = 0; i < arrLike.length; i += 1, j += 1) { + arr[j] = arrLike[i]; + } + return arr; +}; + +var joiny = function (arr, joiner) { + var str = ''; + for (var i = 0; i < arr.length; i += 1) { + str += arr[i]; + if (i + 1 < arr.length) { + str += joiner; + } + } + return str; +}; + +module.exports = function bind(that) { + var target = this; + if (typeof target !== 'function' || toStr.apply(target) !== funcType) { + throw new TypeError(ERROR_MESSAGE + target); + } + var args = slicy(arguments, 1); + + var bound; + var binder = function () { + if (this instanceof bound) { + var result = target.apply( + this, + concatty(args, arguments) + ); + if (Object(result) === result) { + return result; + } + return this; + } + return target.apply( + that, + concatty(args, arguments) + ); + + }; + + var boundLength = max(0, target.length - args.length); + var boundArgs = []; + for (var i = 0; i < boundLength; i++) { + boundArgs[i] = '$' + i; + } + + bound = Function('binder', 'return function (' + joiny(boundArgs, ',') + '){ return binder.apply(this,arguments); }')(binder); + + if (target.prototype) { + var Empty = function Empty() {}; + Empty.prototype = target.prototype; + bound.prototype = new Empty(); + Empty.prototype = null; + } + + return bound; +}; + + +/***/ }), + +/***/ 88334: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var implementation = __nccwpck_require__(19320); + +module.exports = Function.prototype.bind || implementation; + + +/***/ }), + +/***/ 74538: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var undefined; + +var $Object = __nccwpck_require__(78308); + +var $Error = __nccwpck_require__(28015); +var $EvalError = __nccwpck_require__(91933); +var $RangeError = __nccwpck_require__(54415); +var $ReferenceError = __nccwpck_require__(46279); +var $SyntaxError = __nccwpck_require__(75474); +var $TypeError = __nccwpck_require__(6361); +var $URIError = __nccwpck_require__(5065); + +var abs = __nccwpck_require__(19775); +var floor = __nccwpck_require__(60924); +var max = __nccwpck_require__(52419); +var min = __nccwpck_require__(73373); +var pow = __nccwpck_require__(78029); +var round = __nccwpck_require__(59396); +var sign = __nccwpck_require__(39091); + +var $Function = Function; + +// eslint-disable-next-line consistent-return +var getEvalledConstructor = function (expressionSyntax) { + try { + return $Function('"use strict"; return (' + expressionSyntax + ').constructor;')(); + } catch (e) {} +}; + +var $gOPD = __nccwpck_require__(18501); +var $defineProperty = __nccwpck_require__(6123); + +var throwTypeError = function () { + throw new $TypeError(); +}; +var ThrowTypeError = $gOPD + ? (function () { + try { + // eslint-disable-next-line no-unused-expressions, no-caller, no-restricted-properties + arguments.callee; // IE 8 does not throw here + return throwTypeError; + } catch (calleeThrows) { + try { + // IE 8 throws on Object.getOwnPropertyDescriptor(arguments, '') + return $gOPD(arguments, 'callee').get; + } catch (gOPDthrows) { + return throwTypeError; + } + } + }()) + : throwTypeError; + +var hasSymbols = __nccwpck_require__(40587)(); + +var getProto = __nccwpck_require__(13592); +var $ObjectGPO = __nccwpck_require__(5045); +var $ReflectGPO = __nccwpck_require__(78859); + +var $apply = __nccwpck_require__(54177); +var $call = __nccwpck_require__(2808); + +var needsEval = {}; + +var TypedArray = typeof Uint8Array === 'undefined' || !getProto ? undefined : getProto(Uint8Array); + +var INTRINSICS = { + __proto__: null, + '%AggregateError%': typeof AggregateError === 'undefined' ? undefined : AggregateError, + '%Array%': Array, + '%ArrayBuffer%': typeof ArrayBuffer === 'undefined' ? undefined : ArrayBuffer, + '%ArrayIteratorPrototype%': hasSymbols && getProto ? getProto([][Symbol.iterator]()) : undefined, + '%AsyncFromSyncIteratorPrototype%': undefined, + '%AsyncFunction%': needsEval, + '%AsyncGenerator%': needsEval, + '%AsyncGeneratorFunction%': needsEval, + '%AsyncIteratorPrototype%': needsEval, + '%Atomics%': typeof Atomics === 'undefined' ? undefined : Atomics, + '%BigInt%': typeof BigInt === 'undefined' ? undefined : BigInt, + '%BigInt64Array%': typeof BigInt64Array === 'undefined' ? undefined : BigInt64Array, + '%BigUint64Array%': typeof BigUint64Array === 'undefined' ? undefined : BigUint64Array, + '%Boolean%': Boolean, + '%DataView%': typeof DataView === 'undefined' ? undefined : DataView, + '%Date%': Date, + '%decodeURI%': decodeURI, + '%decodeURIComponent%': decodeURIComponent, + '%encodeURI%': encodeURI, + '%encodeURIComponent%': encodeURIComponent, + '%Error%': $Error, + '%eval%': eval, // eslint-disable-line no-eval + '%EvalError%': $EvalError, + '%Float16Array%': typeof Float16Array === 'undefined' ? undefined : Float16Array, + '%Float32Array%': typeof Float32Array === 'undefined' ? undefined : Float32Array, + '%Float64Array%': typeof Float64Array === 'undefined' ? undefined : Float64Array, + '%FinalizationRegistry%': typeof FinalizationRegistry === 'undefined' ? undefined : FinalizationRegistry, + '%Function%': $Function, + '%GeneratorFunction%': needsEval, + '%Int8Array%': typeof Int8Array === 'undefined' ? undefined : Int8Array, + '%Int16Array%': typeof Int16Array === 'undefined' ? undefined : Int16Array, + '%Int32Array%': typeof Int32Array === 'undefined' ? undefined : Int32Array, + '%isFinite%': isFinite, + '%isNaN%': isNaN, + '%IteratorPrototype%': hasSymbols && getProto ? getProto(getProto([][Symbol.iterator]())) : undefined, + '%JSON%': typeof JSON === 'object' ? JSON : undefined, + '%Map%': typeof Map === 'undefined' ? undefined : Map, + '%MapIteratorPrototype%': typeof Map === 'undefined' || !hasSymbols || !getProto ? undefined : getProto(new Map()[Symbol.iterator]()), + '%Math%': Math, + '%Number%': Number, + '%Object%': $Object, + '%Object.getOwnPropertyDescriptor%': $gOPD, + '%parseFloat%': parseFloat, + '%parseInt%': parseInt, + '%Promise%': typeof Promise === 'undefined' ? undefined : Promise, + '%Proxy%': typeof Proxy === 'undefined' ? undefined : Proxy, + '%RangeError%': $RangeError, + '%ReferenceError%': $ReferenceError, + '%Reflect%': typeof Reflect === 'undefined' ? undefined : Reflect, + '%RegExp%': RegExp, + '%Set%': typeof Set === 'undefined' ? undefined : Set, + '%SetIteratorPrototype%': typeof Set === 'undefined' || !hasSymbols || !getProto ? undefined : getProto(new Set()[Symbol.iterator]()), + '%SharedArrayBuffer%': typeof SharedArrayBuffer === 'undefined' ? undefined : SharedArrayBuffer, + '%String%': String, + '%StringIteratorPrototype%': hasSymbols && getProto ? getProto(''[Symbol.iterator]()) : undefined, + '%Symbol%': hasSymbols ? Symbol : undefined, + '%SyntaxError%': $SyntaxError, + '%ThrowTypeError%': ThrowTypeError, + '%TypedArray%': TypedArray, + '%TypeError%': $TypeError, + '%Uint8Array%': typeof Uint8Array === 'undefined' ? undefined : Uint8Array, + '%Uint8ClampedArray%': typeof Uint8ClampedArray === 'undefined' ? undefined : Uint8ClampedArray, + '%Uint16Array%': typeof Uint16Array === 'undefined' ? undefined : Uint16Array, + '%Uint32Array%': typeof Uint32Array === 'undefined' ? undefined : Uint32Array, + '%URIError%': $URIError, + '%WeakMap%': typeof WeakMap === 'undefined' ? undefined : WeakMap, + '%WeakRef%': typeof WeakRef === 'undefined' ? undefined : WeakRef, + '%WeakSet%': typeof WeakSet === 'undefined' ? undefined : WeakSet, + + '%Function.prototype.call%': $call, + '%Function.prototype.apply%': $apply, + '%Object.defineProperty%': $defineProperty, + '%Object.getPrototypeOf%': $ObjectGPO, + '%Math.abs%': abs, + '%Math.floor%': floor, + '%Math.max%': max, + '%Math.min%': min, + '%Math.pow%': pow, + '%Math.round%': round, + '%Math.sign%': sign, + '%Reflect.getPrototypeOf%': $ReflectGPO +}; + +if (getProto) { + try { + null.error; // eslint-disable-line no-unused-expressions + } catch (e) { + // https://github.com/tc39/proposal-shadowrealm/pull/384#issuecomment-1364264229 + var errorProto = getProto(getProto(e)); + INTRINSICS['%Error.prototype%'] = errorProto; + } +} + +var doEval = function doEval(name) { + var value; + if (name === '%AsyncFunction%') { + value = getEvalledConstructor('async function () {}'); + } else if (name === '%GeneratorFunction%') { + value = getEvalledConstructor('function* () {}'); + } else if (name === '%AsyncGeneratorFunction%') { + value = getEvalledConstructor('async function* () {}'); + } else if (name === '%AsyncGenerator%') { + var fn = doEval('%AsyncGeneratorFunction%'); + if (fn) { + value = fn.prototype; + } + } else if (name === '%AsyncIteratorPrototype%') { + var gen = doEval('%AsyncGenerator%'); + if (gen && getProto) { + value = getProto(gen.prototype); + } + } + + INTRINSICS[name] = value; + + return value; +}; + +var LEGACY_ALIASES = { + __proto__: null, + '%ArrayBufferPrototype%': ['ArrayBuffer', 'prototype'], + '%ArrayPrototype%': ['Array', 'prototype'], + '%ArrayProto_entries%': ['Array', 'prototype', 'entries'], + '%ArrayProto_forEach%': ['Array', 'prototype', 'forEach'], + '%ArrayProto_keys%': ['Array', 'prototype', 'keys'], + '%ArrayProto_values%': ['Array', 'prototype', 'values'], + '%AsyncFunctionPrototype%': ['AsyncFunction', 'prototype'], + '%AsyncGenerator%': ['AsyncGeneratorFunction', 'prototype'], + '%AsyncGeneratorPrototype%': ['AsyncGeneratorFunction', 'prototype', 'prototype'], + '%BooleanPrototype%': ['Boolean', 'prototype'], + '%DataViewPrototype%': ['DataView', 'prototype'], + '%DatePrototype%': ['Date', 'prototype'], + '%ErrorPrototype%': ['Error', 'prototype'], + '%EvalErrorPrototype%': ['EvalError', 'prototype'], + '%Float32ArrayPrototype%': ['Float32Array', 'prototype'], + '%Float64ArrayPrototype%': ['Float64Array', 'prototype'], + '%FunctionPrototype%': ['Function', 'prototype'], + '%Generator%': ['GeneratorFunction', 'prototype'], + '%GeneratorPrototype%': ['GeneratorFunction', 'prototype', 'prototype'], + '%Int8ArrayPrototype%': ['Int8Array', 'prototype'], + '%Int16ArrayPrototype%': ['Int16Array', 'prototype'], + '%Int32ArrayPrototype%': ['Int32Array', 'prototype'], + '%JSONParse%': ['JSON', 'parse'], + '%JSONStringify%': ['JSON', 'stringify'], + '%MapPrototype%': ['Map', 'prototype'], + '%NumberPrototype%': ['Number', 'prototype'], + '%ObjectPrototype%': ['Object', 'prototype'], + '%ObjProto_toString%': ['Object', 'prototype', 'toString'], + '%ObjProto_valueOf%': ['Object', 'prototype', 'valueOf'], + '%PromisePrototype%': ['Promise', 'prototype'], + '%PromiseProto_then%': ['Promise', 'prototype', 'then'], + '%Promise_all%': ['Promise', 'all'], + '%Promise_reject%': ['Promise', 'reject'], + '%Promise_resolve%': ['Promise', 'resolve'], + '%RangeErrorPrototype%': ['RangeError', 'prototype'], + '%ReferenceErrorPrototype%': ['ReferenceError', 'prototype'], + '%RegExpPrototype%': ['RegExp', 'prototype'], + '%SetPrototype%': ['Set', 'prototype'], + '%SharedArrayBufferPrototype%': ['SharedArrayBuffer', 'prototype'], + '%StringPrototype%': ['String', 'prototype'], + '%SymbolPrototype%': ['Symbol', 'prototype'], + '%SyntaxErrorPrototype%': ['SyntaxError', 'prototype'], + '%TypedArrayPrototype%': ['TypedArray', 'prototype'], + '%TypeErrorPrototype%': ['TypeError', 'prototype'], + '%Uint8ArrayPrototype%': ['Uint8Array', 'prototype'], + '%Uint8ClampedArrayPrototype%': ['Uint8ClampedArray', 'prototype'], + '%Uint16ArrayPrototype%': ['Uint16Array', 'prototype'], + '%Uint32ArrayPrototype%': ['Uint32Array', 'prototype'], + '%URIErrorPrototype%': ['URIError', 'prototype'], + '%WeakMapPrototype%': ['WeakMap', 'prototype'], + '%WeakSetPrototype%': ['WeakSet', 'prototype'] +}; + +var bind = __nccwpck_require__(88334); +var hasOwn = __nccwpck_require__(62157); +var $concat = bind.call($call, Array.prototype.concat); +var $spliceApply = bind.call($apply, Array.prototype.splice); +var $replace = bind.call($call, String.prototype.replace); +var $strSlice = bind.call($call, String.prototype.slice); +var $exec = bind.call($call, RegExp.prototype.exec); + +/* adapted from https://github.com/lodash/lodash/blob/4.17.15/dist/lodash.js#L6735-L6744 */ +var rePropName = /[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g; +var reEscapeChar = /\\(\\)?/g; /** Used to match backslashes in property paths. */ +var stringToPath = function stringToPath(string) { + var first = $strSlice(string, 0, 1); + var last = $strSlice(string, -1); + if (first === '%' && last !== '%') { + throw new $SyntaxError('invalid intrinsic syntax, expected closing `%`'); + } else if (last === '%' && first !== '%') { + throw new $SyntaxError('invalid intrinsic syntax, expected opening `%`'); + } + var result = []; + $replace(string, rePropName, function (match, number, quote, subString) { + result[result.length] = quote ? $replace(subString, reEscapeChar, '$1') : number || match; + }); + return result; +}; +/* end adaptation */ + +var getBaseIntrinsic = function getBaseIntrinsic(name, allowMissing) { + var intrinsicName = name; + var alias; + if (hasOwn(LEGACY_ALIASES, intrinsicName)) { + alias = LEGACY_ALIASES[intrinsicName]; + intrinsicName = '%' + alias[0] + '%'; + } + + if (hasOwn(INTRINSICS, intrinsicName)) { + var value = INTRINSICS[intrinsicName]; + if (value === needsEval) { + value = doEval(intrinsicName); + } + if (typeof value === 'undefined' && !allowMissing) { + throw new $TypeError('intrinsic ' + name + ' exists, but is not available. Please file an issue!'); + } + + return { + alias: alias, + name: intrinsicName, + value: value + }; + } + + throw new $SyntaxError('intrinsic ' + name + ' does not exist!'); +}; + +module.exports = function GetIntrinsic(name, allowMissing) { + if (typeof name !== 'string' || name.length === 0) { + throw new $TypeError('intrinsic name must be a non-empty string'); + } + if (arguments.length > 1 && typeof allowMissing !== 'boolean') { + throw new $TypeError('"allowMissing" argument must be a boolean'); + } + + if ($exec(/^%?[^%]*%?$/, name) === null) { + throw new $SyntaxError('`%` may not be present anywhere but at the beginning and end of the intrinsic name'); + } + var parts = stringToPath(name); + var intrinsicBaseName = parts.length > 0 ? parts[0] : ''; + + var intrinsic = getBaseIntrinsic('%' + intrinsicBaseName + '%', allowMissing); + var intrinsicRealName = intrinsic.name; + var value = intrinsic.value; + var skipFurtherCaching = false; + + var alias = intrinsic.alias; + if (alias) { + intrinsicBaseName = alias[0]; + $spliceApply(parts, $concat([0, 1], alias)); + } + + for (var i = 1, isOwn = true; i < parts.length; i += 1) { + var part = parts[i]; + var first = $strSlice(part, 0, 1); + var last = $strSlice(part, -1); + if ( + ( + (first === '"' || first === "'" || first === '`') + || (last === '"' || last === "'" || last === '`') + ) + && first !== last + ) { + throw new $SyntaxError('property names with quotes must have matching quotes'); + } + if (part === 'constructor' || !isOwn) { + skipFurtherCaching = true; + } + + intrinsicBaseName += '.' + part; + intrinsicRealName = '%' + intrinsicBaseName + '%'; + + if (hasOwn(INTRINSICS, intrinsicRealName)) { + value = INTRINSICS[intrinsicRealName]; + } else if (value != null) { + if (!(part in value)) { + if (!allowMissing) { + throw new $TypeError('base intrinsic for ' + name + ' exists, but the property is not available.'); + } + return void undefined; + } + if ($gOPD && (i + 1) >= parts.length) { + var desc = $gOPD(value, part); + isOwn = !!desc; + + // By convention, when a data property is converted to an accessor + // property to emulate a data property that does not suffer from + // the override mistake, that accessor's getter is marked with + // an `originalValue` property. Here, when we detect this, we + // uphold the illusion by pretending to see that original data + // property, i.e., returning the value rather than the getter + // itself. + if (isOwn && 'get' in desc && !('originalValue' in desc.get)) { + value = desc.get; + } else { + value = value[part]; + } + } else { + isOwn = hasOwn(value, part); + value = value[part]; + } + + if (isOwn && !skipFurtherCaching) { + INTRINSICS[intrinsicRealName] = value; + } + } + } + return value; +}; + + +/***/ }), + +/***/ 5045: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var $Object = __nccwpck_require__(78308); + +/** @type {import('./Object.getPrototypeOf')} */ +module.exports = $Object.getPrototypeOf || null; + + +/***/ }), + +/***/ 78859: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./Reflect.getPrototypeOf')} */ +module.exports = (typeof Reflect !== 'undefined' && Reflect.getPrototypeOf) || null; + + +/***/ }), + +/***/ 13592: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var reflectGetProto = __nccwpck_require__(78859); +var originalGetProto = __nccwpck_require__(5045); + +var getDunderProto = __nccwpck_require__(62693); + +/** @type {import('.')} */ +module.exports = reflectGetProto + ? function getProto(O) { + // @ts-expect-error TS can't narrow inside a closure, for some reason + return reflectGetProto(O); + } + : originalGetProto + ? function getProto(O) { + if (!O || (typeof O !== 'object' && typeof O !== 'function')) { + throw new TypeError('getProto: not an object'); + } + // @ts-expect-error TS can't narrow inside a closure, for some reason + return originalGetProto(O); + } + : getDunderProto + ? function getProto(O) { + // @ts-expect-error TS can't narrow inside a closure, for some reason + return getDunderProto(O); + } + : null; + + +/***/ }), + +/***/ 57087: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./gOPD')} */ +module.exports = Object.getOwnPropertyDescriptor; + + +/***/ }), + +/***/ 18501: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +/** @type {import('.')} */ +var $gOPD = __nccwpck_require__(57087); + +if ($gOPD) { + try { + $gOPD([], 'length'); + } catch (e) { + // IE 8 has a broken gOPD + $gOPD = null; + } +} + +module.exports = $gOPD; + + /***/ }), /***/ 67356: @@ -70465,6 +71332,113 @@ function patch (fs) { } +/***/ }), + +/***/ 40587: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var origSymbol = typeof Symbol !== 'undefined' && Symbol; +var hasSymbolSham = __nccwpck_require__(57747); + +/** @type {import('.')} */ +module.exports = function hasNativeSymbols() { + if (typeof origSymbol !== 'function') { return false; } + if (typeof Symbol !== 'function') { return false; } + if (typeof origSymbol('foo') !== 'symbol') { return false; } + if (typeof Symbol('bar') !== 'symbol') { return false; } + + return hasSymbolSham(); +}; + + +/***/ }), + +/***/ 57747: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./shams')} */ +/* eslint complexity: [2, 18], max-statements: [2, 33] */ +module.exports = function hasSymbols() { + if (typeof Symbol !== 'function' || typeof Object.getOwnPropertySymbols !== 'function') { return false; } + if (typeof Symbol.iterator === 'symbol') { return true; } + + /** @type {{ [k in symbol]?: unknown }} */ + var obj = {}; + var sym = Symbol('test'); + var symObj = Object(sym); + if (typeof sym === 'string') { return false; } + + if (Object.prototype.toString.call(sym) !== '[object Symbol]') { return false; } + if (Object.prototype.toString.call(symObj) !== '[object Symbol]') { return false; } + + // temp disabled per https://github.com/ljharb/object.assign/issues/17 + // if (sym instanceof Symbol) { return false; } + // temp disabled per https://github.com/WebReflection/get-own-property-symbols/issues/4 + // if (!(symObj instanceof Symbol)) { return false; } + + // if (typeof Symbol.prototype.toString !== 'function') { return false; } + // if (String(sym) !== Symbol.prototype.toString.call(sym)) { return false; } + + var symVal = 42; + obj[sym] = symVal; + for (var _ in obj) { return false; } // eslint-disable-line no-restricted-syntax, no-unreachable-loop + if (typeof Object.keys === 'function' && Object.keys(obj).length !== 0) { return false; } + + if (typeof Object.getOwnPropertyNames === 'function' && Object.getOwnPropertyNames(obj).length !== 0) { return false; } + + var syms = Object.getOwnPropertySymbols(obj); + if (syms.length !== 1 || syms[0] !== sym) { return false; } + + if (!Object.prototype.propertyIsEnumerable.call(obj, sym)) { return false; } + + if (typeof Object.getOwnPropertyDescriptor === 'function') { + // eslint-disable-next-line no-extra-parens + var descriptor = /** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(obj, sym)); + if (descriptor.value !== symVal || descriptor.enumerable !== true) { return false; } + } + + return true; +}; + + +/***/ }), + +/***/ 99038: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var hasSymbols = __nccwpck_require__(57747); + +/** @type {import('.')} */ +module.exports = function hasToStringTagShams() { + return hasSymbols() && !!Symbol.toStringTag; +}; + + +/***/ }), + +/***/ 62157: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var call = Function.prototype.call; +var $hasOwn = Object.prototype.hasOwnProperty; +var bind = __nccwpck_require__(88334); + +/** @type {import('.')} */ +module.exports = bind.call(call, $hasOwn); + + /***/ }), /***/ 44124: @@ -76437,6 +77411,111 @@ var union = baseRest(function(arrays) { module.exports = union; +/***/ }), + +/***/ 19775: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./abs')} */ +module.exports = Math.abs; + + +/***/ }), + +/***/ 60924: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./floor')} */ +module.exports = Math.floor; + + +/***/ }), + +/***/ 57661: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./isNaN')} */ +module.exports = Number.isNaN || function isNaN(a) { + return a !== a; +}; + + +/***/ }), + +/***/ 52419: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./max')} */ +module.exports = Math.max; + + +/***/ }), + +/***/ 73373: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./min')} */ +module.exports = Math.min; + + +/***/ }), + +/***/ 78029: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./pow')} */ +module.exports = Math.pow; + + +/***/ }), + +/***/ 59396: +/***/ ((module) => { + +"use strict"; + + +/** @type {import('./round')} */ +module.exports = Math.round; + + +/***/ }), + +/***/ 39091: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var $isNaN = __nccwpck_require__(57661); + +/** @type {import('./sign')} */ +module.exports = function sign(number) { + if ($isNaN(number) || number === 0) { + return number; + } + return number < 0 ? -1 : +1; +}; + + /***/ }), /***/ 47426: @@ -98076,7 +99155,7 @@ module.exports = { const { parseSetCookie } = __nccwpck_require__(24408) -const { stringify, getHeadersList } = __nccwpck_require__(43121) +const { stringify } = __nccwpck_require__(43121) const { webidl } = __nccwpck_require__(21744) const { Headers } = __nccwpck_require__(10554) @@ -98152,14 +99231,13 @@ function getSetCookies (headers) { webidl.brandCheck(headers, Headers, { strict: false }) - const cookies = getHeadersList(headers).cookies + const cookies = headers.getSetCookie() if (!cookies) { return [] } - // In older versions of undici, cookies is a list of name:value. - return cookies.map((pair) => parseSetCookie(Array.isArray(pair) ? pair[1] : pair)) + return cookies.map((pair) => parseSetCookie(pair)) } /** @@ -98587,14 +99665,15 @@ module.exports = { /***/ }), /***/ 43121: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +/***/ ((module) => { "use strict"; -const assert = __nccwpck_require__(39491) -const { kHeadersList } = __nccwpck_require__(72785) - +/** + * @param {string} value + * @returns {boolean} + */ function isCTLExcludingHtab (value) { if (value.length === 0) { return false @@ -98855,31 +99934,13 @@ function stringify (cookie) { return out.join('; ') } -let kHeadersListNode - -function getHeadersList (headers) { - if (headers[kHeadersList]) { - return headers[kHeadersList] - } - - if (!kHeadersListNode) { - kHeadersListNode = Object.getOwnPropertySymbols(headers).find( - (symbol) => symbol.description === 'headers list' - ) - - assert(kHeadersListNode, 'Headers cannot be parsed') - } - - const headersList = headers[kHeadersListNode] - assert(headersList) - - return headersList -} - module.exports = { isCTLExcludingHtab, - stringify, - getHeadersList + validateCookieName, + validateCookiePath, + validateCookieValue, + toIMFDate, + stringify } @@ -99080,6 +100141,132 @@ function onConnectTimeout (socket) { module.exports = buildConnector +/***/ }), + +/***/ 14462: +/***/ ((module) => { + +"use strict"; + + +/** @type {Record} */ +const headerNameLowerCasedRecord = {} + +// https://developer.mozilla.org/docs/Web/HTTP/Headers +const wellknownHeaderNames = [ + 'Accept', + 'Accept-Encoding', + 'Accept-Language', + 'Accept-Ranges', + 'Access-Control-Allow-Credentials', + 'Access-Control-Allow-Headers', + 'Access-Control-Allow-Methods', + 'Access-Control-Allow-Origin', + 'Access-Control-Expose-Headers', + 'Access-Control-Max-Age', + 'Access-Control-Request-Headers', + 'Access-Control-Request-Method', + 'Age', + 'Allow', + 'Alt-Svc', + 'Alt-Used', + 'Authorization', + 'Cache-Control', + 'Clear-Site-Data', + 'Connection', + 'Content-Disposition', + 'Content-Encoding', + 'Content-Language', + 'Content-Length', + 'Content-Location', + 'Content-Range', + 'Content-Security-Policy', + 'Content-Security-Policy-Report-Only', + 'Content-Type', + 'Cookie', + 'Cross-Origin-Embedder-Policy', + 'Cross-Origin-Opener-Policy', + 'Cross-Origin-Resource-Policy', + 'Date', + 'Device-Memory', + 'Downlink', + 'ECT', + 'ETag', + 'Expect', + 'Expect-CT', + 'Expires', + 'Forwarded', + 'From', + 'Host', + 'If-Match', + 'If-Modified-Since', + 'If-None-Match', + 'If-Range', + 'If-Unmodified-Since', + 'Keep-Alive', + 'Last-Modified', + 'Link', + 'Location', + 'Max-Forwards', + 'Origin', + 'Permissions-Policy', + 'Pragma', + 'Proxy-Authenticate', + 'Proxy-Authorization', + 'RTT', + 'Range', + 'Referer', + 'Referrer-Policy', + 'Refresh', + 'Retry-After', + 'Sec-WebSocket-Accept', + 'Sec-WebSocket-Extensions', + 'Sec-WebSocket-Key', + 'Sec-WebSocket-Protocol', + 'Sec-WebSocket-Version', + 'Server', + 'Server-Timing', + 'Service-Worker-Allowed', + 'Service-Worker-Navigation-Preload', + 'Set-Cookie', + 'SourceMap', + 'Strict-Transport-Security', + 'Supports-Loading-Mode', + 'TE', + 'Timing-Allow-Origin', + 'Trailer', + 'Transfer-Encoding', + 'Upgrade', + 'Upgrade-Insecure-Requests', + 'User-Agent', + 'Vary', + 'Via', + 'WWW-Authenticate', + 'X-Content-Type-Options', + 'X-DNS-Prefetch-Control', + 'X-Frame-Options', + 'X-Permitted-Cross-Domain-Policies', + 'X-Powered-By', + 'X-Requested-With', + 'X-XSS-Protection' +] + +for (let i = 0; i < wellknownHeaderNames.length; ++i) { + const key = wellknownHeaderNames[i] + const lowerCasedKey = key.toLowerCase() + headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] = + lowerCasedKey +} + +// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`. +Object.setPrototypeOf(headerNameLowerCasedRecord, null) + +module.exports = { + wellknownHeaderNames, + headerNameLowerCasedRecord +} + + /***/ }), /***/ 48045: @@ -99912,6 +101099,7 @@ const { InvalidArgumentError } = __nccwpck_require__(48045) const { Blob } = __nccwpck_require__(14300) const nodeUtil = __nccwpck_require__(73837) const { stringify } = __nccwpck_require__(63477) +const { headerNameLowerCasedRecord } = __nccwpck_require__(14462) const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v)) @@ -100121,6 +101309,15 @@ function parseKeepAliveTimeout (val) { return m ? parseInt(m[1], 10) * 1000 : null } +/** + * Retrieves a header name and returns its lowercase value. + * @param {string | Buffer} value Header name + * @returns {string} + */ +function headerNameToString (value) { + return headerNameLowerCasedRecord[value] || value.toLowerCase() +} + function parseHeaders (headers, obj = {}) { // For H2 support if (!Array.isArray(headers)) return headers @@ -100392,6 +101589,7 @@ module.exports = { isIterable, isAsyncIterable, isDestroyed, + headerNameToString, parseRawHeaders, parseHeaders, parseKeepAliveTimeout, @@ -100671,6 +101869,14 @@ const { isUint8Array, isArrayBuffer } = __nccwpck_require__(29830) const { File: UndiciFile } = __nccwpck_require__(78511) const { parseMIMEType, serializeAMimeType } = __nccwpck_require__(685) +let random +try { + const crypto = __nccwpck_require__(6005) + random = (max) => crypto.randomInt(0, max) +} catch { + random = (max) => Math.floor(Math.random(max)) +} + let ReadableStream = globalThis.ReadableStream /** @type {globalThis['File']} */ @@ -100756,7 +101962,7 @@ function extractBody (object, keepalive = false) { // Set source to a copy of the bytes held by object. source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength)) } else if (util.isFormDataLike(object)) { - const boundary = `----formdata-undici-0${`${Math.floor(Math.random() * 1e11)}`.padStart(11, '0')}` + const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}` const prefix = `--${boundary}\r\nContent-Disposition: form-data` /*! formdata-polyfill. MIT License. Jimmy WƤrting */ @@ -102738,6 +103944,7 @@ const { isValidHeaderName, isValidHeaderValue } = __nccwpck_require__(52538) +const util = __nccwpck_require__(73837) const { webidl } = __nccwpck_require__(21744) const assert = __nccwpck_require__(39491) @@ -103291,6 +104498,9 @@ Object.defineProperties(Headers.prototype, { [Symbol.toStringTag]: { value: 'Headers', configurable: true + }, + [util.inspect.custom]: { + enumerable: false } }) @@ -104528,6 +105738,9 @@ function httpRedirectFetch (fetchParams, response) { // https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name request.headersList.delete('authorization') + // https://fetch.spec.whatwg.org/#authentication-entries + request.headersList.delete('proxy-authorization', true) + // "Cookie" and "Host" are forbidden request-headers, which undici doesn't implement. request.headersList.delete('cookie') request.headersList.delete('host') @@ -107036,14 +108249,18 @@ const { isBlobLike, toUSVString, ReadableStreamFrom } = __nccwpck_require__(8398 const assert = __nccwpck_require__(39491) const { isUint8Array } = __nccwpck_require__(29830) +let supportedHashes = [] + // https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable /** @type {import('crypto')|undefined} */ let crypto try { crypto = __nccwpck_require__(6113) + const possibleRelevantHashes = ['sha256', 'sha384', 'sha512'] + supportedHashes = crypto.getHashes().filter((hash) => possibleRelevantHashes.includes(hash)) +/* c8 ignore next 3 */ } catch { - } function responseURL (response) { @@ -107571,66 +108788,56 @@ function bytesMatch (bytes, metadataList) { return true } - // 3. If parsedMetadata is the empty set, return true. + // 3. If response is not eligible for integrity validation, return false. + // TODO + + // 4. If parsedMetadata is the empty set, return true. if (parsedMetadata.length === 0) { return true } - // 4. Let metadata be the result of getting the strongest + // 5. Let metadata be the result of getting the strongest // metadata from parsedMetadata. - const list = parsedMetadata.sort((c, d) => d.algo.localeCompare(c.algo)) - // get the strongest algorithm - const strongest = list[0].algo - // get all entries that use the strongest algorithm; ignore weaker - const metadata = list.filter((item) => item.algo === strongest) + const strongest = getStrongestMetadata(parsedMetadata) + const metadata = filterMetadataListByAlgorithm(parsedMetadata, strongest) - // 5. For each item in metadata: + // 6. For each item in metadata: for (const item of metadata) { // 1. Let algorithm be the alg component of item. const algorithm = item.algo // 2. Let expectedValue be the val component of item. - let expectedValue = item.hash + const expectedValue = item.hash // See https://github.com/web-platform-tests/wpt/commit/e4c5cc7a5e48093220528dfdd1c4012dc3837a0e // "be liberal with padding". This is annoying, and it's not even in the spec. - if (expectedValue.endsWith('==')) { - expectedValue = expectedValue.slice(0, -2) - } - // 3. Let actualValue be the result of applying algorithm to bytes. let actualValue = crypto.createHash(algorithm).update(bytes).digest('base64') - if (actualValue.endsWith('==')) { - actualValue = actualValue.slice(0, -2) + if (actualValue[actualValue.length - 1] === '=') { + if (actualValue[actualValue.length - 2] === '=') { + actualValue = actualValue.slice(0, -2) + } else { + actualValue = actualValue.slice(0, -1) + } } // 4. If actualValue is a case-sensitive match for expectedValue, // return true. - if (actualValue === expectedValue) { - return true - } - - let actualBase64URL = crypto.createHash(algorithm).update(bytes).digest('base64url') - - if (actualBase64URL.endsWith('==')) { - actualBase64URL = actualBase64URL.slice(0, -2) - } - - if (actualBase64URL === expectedValue) { + if (compareBase64Mixed(actualValue, expectedValue)) { return true } } - // 6. Return false. + // 7. Return false. return false } // https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options // https://www.w3.org/TR/CSP2/#source-list-syntax // https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1 -const parseHashWithOptions = /((?sha256|sha384|sha512)-(?[A-z0-9+/]{1}.*={0,2}))( +[\x21-\x7e]?)?/i +const parseHashWithOptions = /(?sha256|sha384|sha512)-((?[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i /** * @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata @@ -107644,8 +108851,6 @@ function parseMetadata (metadata) { // 2. Let empty be equal to true. let empty = true - const supportedHashes = crypto.getHashes() - // 3. For each token returned by splitting metadata on spaces: for (const token of metadata.split(' ')) { // 1. Set empty to false. @@ -107655,7 +108860,11 @@ function parseMetadata (metadata) { const parsedToken = parseHashWithOptions.exec(token) // 3. If token does not parse, continue to the next token. - if (parsedToken === null || parsedToken.groups === undefined) { + if ( + parsedToken === null || + parsedToken.groups === undefined || + parsedToken.groups.algo === undefined + ) { // Note: Chromium blocks the request at this point, but Firefox // gives a warning that an invalid integrity was given. The // correct behavior is to ignore these, and subsequently not @@ -107664,11 +108873,11 @@ function parseMetadata (metadata) { } // 4. Let algorithm be the hash-algo component of token. - const algorithm = parsedToken.groups.algo + const algorithm = parsedToken.groups.algo.toLowerCase() // 5. If algorithm is a hash function recognized by the user // agent, add the parsed token to result. - if (supportedHashes.includes(algorithm.toLowerCase())) { + if (supportedHashes.includes(algorithm)) { result.push(parsedToken.groups) } } @@ -107681,6 +108890,82 @@ function parseMetadata (metadata) { return result } +/** + * @param {{ algo: 'sha256' | 'sha384' | 'sha512' }[]} metadataList + */ +function getStrongestMetadata (metadataList) { + // Let algorithm be the algo component of the first item in metadataList. + // Can be sha256 + let algorithm = metadataList[0].algo + // If the algorithm is sha512, then it is the strongest + // and we can return immediately + if (algorithm[3] === '5') { + return algorithm + } + + for (let i = 1; i < metadataList.length; ++i) { + const metadata = metadataList[i] + // If the algorithm is sha512, then it is the strongest + // and we can break the loop immediately + if (metadata.algo[3] === '5') { + algorithm = 'sha512' + break + // If the algorithm is sha384, then a potential sha256 or sha384 is ignored + } else if (algorithm[3] === '3') { + continue + // algorithm is sha256, check if algorithm is sha384 and if so, set it as + // the strongest + } else if (metadata.algo[3] === '3') { + algorithm = 'sha384' + } + } + return algorithm +} + +function filterMetadataListByAlgorithm (metadataList, algorithm) { + if (metadataList.length === 1) { + return metadataList + } + + let pos = 0 + for (let i = 0; i < metadataList.length; ++i) { + if (metadataList[i].algo === algorithm) { + metadataList[pos++] = metadataList[i] + } + } + + metadataList.length = pos + + return metadataList +} + +/** + * Compares two base64 strings, allowing for base64url + * in the second string. + * +* @param {string} actualValue always base64 + * @param {string} expectedValue base64 or base64url + * @returns {boolean} + */ +function compareBase64Mixed (actualValue, expectedValue) { + if (actualValue.length !== expectedValue.length) { + return false + } + for (let i = 0; i < actualValue.length; ++i) { + if (actualValue[i] !== expectedValue[i]) { + if ( + (actualValue[i] === '+' && expectedValue[i] === '-') || + (actualValue[i] === '/' && expectedValue[i] === '_') + ) { + continue + } + return false + } + } + + return true +} + // https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) { // TODO @@ -108096,7 +109381,8 @@ module.exports = { urlHasHttpsScheme, urlIsHttpHttpsScheme, readAllBytes, - normalizeMethodRecord + normalizeMethodRecord, + parseMetadata } @@ -110183,12 +111469,17 @@ function parseLocation (statusCode, headers) { // https://tools.ietf.org/html/rfc7231#section-6.4.4 function shouldRemoveHeader (header, removeContent, unknownOrigin) { - return ( - (header.length === 4 && header.toString().toLowerCase() === 'host') || - (removeContent && header.toString().toLowerCase().indexOf('content-') === 0) || - (unknownOrigin && header.length === 13 && header.toString().toLowerCase() === 'authorization') || - (unknownOrigin && header.length === 6 && header.toString().toLowerCase() === 'cookie') - ) + if (header.length === 4) { + return util.headerNameToString(header) === 'host' + } + if (removeContent && util.headerNameToString(header).startsWith('content-')) { + return true + } + if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) { + const name = util.headerNameToString(header) + return name === 'authorization' || name === 'cookie' || name === 'proxy-authorization' + } + return false } // https://tools.ietf.org/html/rfc7231#section-6.4 @@ -112386,6 +113677,20 @@ class Pool extends PoolBase { ? { ...options.interceptors } : undefined this[kFactory] = factory + + this.on('connectionError', (origin, targets, error) => { + // If a connection error occurs, we remove the client from the pool, + // and emit a connectionError event. They will not be re-used. + // Fixes https://github.com/nodejs/undici/issues/3895 + for (const target of targets) { + // Do not use kRemoveClient here, as it will close the client, + // but the client cannot be closed in this state. + const idx = this[kClients].indexOf(target) + if (idx !== -1) { + this[kClients].splice(idx, 1) + } + } + }) } [kGetDispatcher] () { @@ -115302,7 +116607,7 @@ UnzipStream.prototype._prepareOutStream = function (vars, entry) { var isDirectory = vars.uncompressedSize === 0 && /[\/\\]$/.test(entry.path); // protect against malicious zip files which want to extract to parent dirs - entry.path = entry.path.replace(/^([/\\]*[.]+[/\\]+)*[/\\]*/, ""); + entry.path = entry.path.replace(/(?<=^|[/\\]+)[.][.]+(?=[/\\]+|$)/g, "."); entry.type = isDirectory ? 'Directory' : 'File'; entry.isDirectory = isDirectory; @@ -115366,7 +116671,7 @@ UnzipStream.prototype._prepareOutStream = function (vars, entry) { entry.skip = true; setImmediate(() => { - entry.emit("error", new Error(message)); + self.emit('error', new Error(message)); }); // try to skip over this entry @@ -123717,7 +125022,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.setDependencyTrackProjectId = exports.setDependencyTrackUploadToken = exports.DEPENDENCY_TRACK_UPLOAD_TOKEN = exports.DEPENDENCY_TRACK_PROJECT_VERSION = exports.DEPENDENCY_TRACK_PROJECT_NAME = exports.DEPENDENCY_TRACK_PROJECT_ID = exports.DEPENDENCY_TRACK_API_KEY = exports.DEPENDENCY_TRACK_URL = exports.DEPENDENCY_TRACK_ENABLED = exports.DEBUG = exports.EXECUTABLE = exports.SETTINGS_FILE_PATH = exports.SCANOSS_SETTINGS = exports.SCAN_FILES = exports.SKIP_SNIPPETS = exports.RUNTIME_CONTAINER = exports.COPYLEFT_LICENSE_EXPLICIT = exports.COPYLEFT_LICENSE_EXCLUDE = exports.COPYLEFT_LICENSE_INCLUDE = exports.REPO_DIR = exports.GITHUB_TOKEN = exports.OUTPUT_FILEPATH = exports.API_URL = exports.API_KEY = exports.DEPENDENCY_SCOPE_INCLUDE = exports.DEPENDENCY_SCOPE_EXCLUDE = exports.DEPENDENCIES_SCOPE = exports.DEPENDENCIES_ENABLED = exports.HALT_ON_ERROR = exports.POLICIES_HALT_ON_FAILURE = exports.POLICIES = void 0; +exports.setDependencyTrackProjectId = exports.setDependencyTrackUploadToken = exports.DEPENDENCY_TRACK_UPLOAD_TOKEN = exports.DEPENDENCY_TRACK_PROJECT_VERSION = exports.DEPENDENCY_TRACK_PROJECT_NAME = exports.DEPENDENCY_TRACK_PROJECT_ID = exports.DEPENDENCY_TRACK_API_KEY = exports.DEPENDENCY_TRACK_URL = exports.DEPENDENCY_TRACK_ENABLED = exports.DEBUG = exports.EXECUTABLE = exports.SETTINGS_FILE_PATH = exports.SCANOSS_SETTINGS = exports.SCAN_FILES = exports.MATCH_ANNOTATIONS = exports.SKIP_SNIPPETS = exports.RUNTIME_CONTAINER = exports.COPYLEFT_LICENSE_EXPLICIT = exports.COPYLEFT_LICENSE_EXCLUDE = exports.COPYLEFT_LICENSE_INCLUDE = exports.REPO_DIR = exports.GITHUB_TOKEN = exports.OUTPUT_FILEPATH = exports.API_URL = exports.API_KEY = exports.DEPENDENCY_SCOPE_INCLUDE = exports.DEPENDENCY_SCOPE_EXCLUDE = exports.DEPENDENCIES_SCOPE = exports.DEPENDENCIES_ENABLED = exports.HALT_ON_ERROR = exports.POLICIES_HALT_ON_FAILURE = exports.POLICIES = void 0; const core = __importStar(__nccwpck_require__(42186)); const path = __importStar(__nccwpck_require__(71017)); const url_utils_1 = __nccwpck_require__(13060); @@ -123792,9 +125097,11 @@ exports.COPYLEFT_LICENSE_EXCLUDE = core.getInput('licenses.copyleft.exclude'); exports.COPYLEFT_LICENSE_EXPLICIT = core.getInput('licenses.copyleft.explicit'); // Runtime Configuration /** Docker container image for scanoss-py execution */ -exports.RUNTIME_CONTAINER = core.getInput('runtimeContainer') || 'ghcr.io/scanoss/scanoss-py:v1.31.5'; +exports.RUNTIME_CONTAINER = core.getInput('runtimeContainer') || 'ghcr.io/scanoss/scanoss-py:v1.32.0'; /** Skip snippet generation during scan */ exports.SKIP_SNIPPETS = core.getInput('skipSnippets') === 'true'; +/** Enable match annotations and commit comments */ +exports.MATCH_ANNOTATIONS = core.getInput('matchAnnotations') === 'true'; /** Enable file scanning */ exports.SCAN_FILES = core.getInput('scanFiles') === 'true'; /** Enable SCANOSS settings file usage */ @@ -123948,12 +125255,18 @@ const dep_track_policy_check_1 = __nccwpck_require__(1669); const dependency_track_service_1 = __nccwpck_require__(57356); const dependency_track_status_service_1 = __nccwpck_require__(55414); const scanoss_service_1 = __nccwpck_require__(73406); +const snippet_annotations_utils_1 = __nccwpck_require__(71325); /** * The main function for the action. * @returns {Promise} Resolves when the action is complete. */ async function run() { try { + // Mask sensitive inputs to prevent accidental leakage in logs + if (inputs.API_KEY) + core.setSecret(inputs.API_KEY); + if (inputs.GITHUB_TOKEN) + core.setSecret(inputs.GITHUB_TOKEN); core.debug(`SCANOSS Scan Action started...`); // create policies core.debug(`Creating policies`); @@ -123979,6 +125292,14 @@ async function run() { } await policy.run(); } + // 5: Create snippet match annotations + if (inputs.MATCH_ANNOTATIONS) { + core.info('Creating match annotations and commit comments...'); + await (0, snippet_annotations_utils_1.createSnippetAnnotations)(inputs.OUTPUT_FILEPATH); + } + else { + core.info('Skipping match annotations - disabled by matchAnnotations parameter'); + } if ((0, github_utils_1.isPullRequest)()) { // create reports const report = await (0, report_service_1.generatePRSummary)(policies); @@ -124305,7 +125626,7 @@ exports.BaseLicenseArgumentBuilder = BaseLicenseArgumentBuilder; /***/ }), -/***/ 73373: +/***/ 88205: /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; @@ -124394,7 +125715,7 @@ exports.CopyLeftArgumentBuilder = CopyLeftArgumentBuilder; */ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.LicenseSummaryArgumentBuilder = void 0; -const copyleft_argument_builder_1 = __nccwpck_require__(73373); +const copyleft_argument_builder_1 = __nccwpck_require__(88205); const app_input_1 = __nccwpck_require__(483); /** * Builds arguments for license summary generation using scanoss-py. @@ -124480,7 +125801,7 @@ const app_config_1 = __nccwpck_require__(29014); const policy_check_1 = __nccwpck_require__(63702); const app_input_1 = __nccwpck_require__(483); const exec = __importStar(__nccwpck_require__(71514)); -const copyleft_argument_builder_1 = __nccwpck_require__(73373); +const copyleft_argument_builder_1 = __nccwpck_require__(88205); const github_service_1 = __nccwpck_require__(43123); /** * This class checks if any of the components identified in the scanner results are subject to copyleft licenses. @@ -124795,10 +126116,6 @@ class DepTrackPolicyCheck extends policy_check_1.PolicyCheck { silent: true // Capture output silently, then selectively display }; const { stdout, stderr, exitCode } = await exec.getExecOutput(app_input_1.EXECUTABLE, args, options); - // Display stdout (policy results) unless it's empty - if (stdout && stdout.trim()) { - core.info(stdout); - } // Only display stderr if it's a real error, not informational messages if (stderr && !this.isInformationalMessage(stderr)) { core.error(stderr); // Display real errors to user @@ -124983,6 +126300,7 @@ class PolicyCheck { _status; _conclusion; _firstRunId = -1; + detailsUrl = null; /** * Initializes the policy check with GitHub integration. */ @@ -125004,6 +126322,7 @@ class PolicyCheck { head_sha: (0, github_utils_1.getSHA)() }); this.checkRunId = result.data.id; + this.detailsUrl = result.data.details_url; this._raw = result.data; this._firstRunId = runId; this._status = STATUS.INITIALIZED; @@ -125031,6 +126350,9 @@ class PolicyCheck { * Returns the URL to this check run. */ get url() { + if (this.detailsUrl) { + return this.detailsUrl; + } return `${github_1.context.serverUrl}/${github_1.context.repo.owner}/${github_1.context.repo.repo}/actions/runs/${this._firstRunId}/job/${this.raw?.id}`; } /** @@ -125292,6 +126614,9 @@ const app_input_1 = __nccwpck_require__(483); const exec = __importStar(__nccwpck_require__(71514)); const undeclared_argument_builder_1 = __nccwpck_require__(25721); const github_service_1 = __nccwpck_require__(43123); +const github_1 = __nccwpck_require__(95438); +const github_utils_1 = __nccwpck_require__(17889); +const fs = __importStar(__nccwpck_require__(57147)); /** * Verifies that all components identified in scanner results are declared in the project's SBOM. * The run method compares components found by the scanner against those declared in the SBOM. @@ -125345,6 +126670,43 @@ class UndeclaredPolicyCheck extends policy_check_1.PolicyCheck { else { details = stdout; } + // Add scanoss.json file link and context based on file existence + details += `\n\n---\n\n`; + details += `**šŸ“ Quick Fix:**\n`; + // Get the correct branch name for links + let branchName = github_1.context.ref.replace('refs/heads/', ''); + if ((0, github_utils_1.isPullRequest)()) { + const pull = github_1.context.payload.pull_request; + if (pull?.head.ref) { + branchName = pull.head.ref; + } + } + if (fs.existsSync(app_input_1.SETTINGS_FILE_PATH)) { + const { owner, repo } = (0, github_utils_1.resolveRepoAndSha)(); + const settingsFileUrl = `https://github.com/${owner}/${repo}/edit/${branchName}/${app_input_1.SETTINGS_FILE_PATH}`; + // Try to replace the existing JSON with merged version + const mergedJson = mergeWithExistingScanossJson(details); + if (mergedJson) { + // Replace the first JSON block with merged version, fenced for readability + details = details.replace(/{[\s\S]*?}/, `\`\`\`json\n${mergedJson}\n\`\`\``); + } + details += `\n\nšŸ“ Quick Fix:\n`; + details += `[Edit ${app_input_1.SETTINGS_FILE_PATH} file](${settingsFileUrl}) and replace with the JSON snippet provided above to declare these components and resolve policy violations.`; + } + else { + // Build JSON content from the details output that already contains the structure + let jsonContent = ''; + const jsonMatch = details.match(/{[\s\S]*?}/); + if (jsonMatch) { + jsonContent = jsonMatch[0]; + } + const encodedJson = encodeURIComponent(jsonContent); + const { owner, repo } = (0, github_utils_1.resolveRepoAndSha)(); + const createFileUrl = `https://github.com/${owner}/${repo}/new/${branchName}?filename=${app_input_1.SETTINGS_FILE_PATH}&value=${encodedJson}`; + details += `\n\nšŸ“ Quick Fix:\n`; + details += `${app_input_1.SETTINGS_FILE_PATH} doesn't exist. Create it in your repository root with the JSON snippet provided above to resolve policy violations.\n\n`; + details += `[Create ${app_input_1.SETTINGS_FILE_PATH} file](${createFileUrl})`; + } const { id } = await this.uploadArtifact(details); core.debug(`Undeclared Artifact ID: ${id}`); if (id) @@ -125368,6 +126730,50 @@ class UndeclaredPolicyCheck extends policy_check_1.PolicyCheck { } } exports.UndeclaredPolicyCheck = UndeclaredPolicyCheck; +/** + * Merges new undeclared components with existing scanoss.json file + */ +function mergeWithExistingScanossJson(policyDetails) { + try { + // Extract new components from policy details + const jsonMatch = policyDetails.match(/{[\s\S]*?}/); + if (!jsonMatch) { + core.warning('Could not extract new components from policy details'); + return null; + } + const newStructure = JSON.parse(jsonMatch[0]); + const newComponents = newStructure.bom?.include || []; + if (newComponents.length === 0) { + core.warning('No new components found to add'); + return null; + } + // Read existing settings file + const existingContent = fs.readFileSync(app_input_1.SETTINGS_FILE_PATH, 'utf8'); + const existingConfig = JSON.parse(existingContent); + // Ensure bom section exists + if (!existingConfig.bom) { + existingConfig.bom = {}; + } + // Ensure include array exists + if (!existingConfig.bom.include) { + existingConfig.bom.include = []; + } + // Ensure include is an array + if (!Array.isArray(existingConfig.bom.include)) { + core.warning('Existing bom.include is not an array, creating new array'); + existingConfig.bom.include = []; + } + // Add all new components (no duplicate checking needed) + existingConfig.bom.include.push(...newComponents); + core.info(`Added ${newComponents.length} new components to existing ${app_input_1.SETTINGS_FILE_PATH} structure`); + // Return formatted JSON + return JSON.stringify(existingConfig, null, 2); + } + catch (error) { + core.warning(`Failed to merge with existing ${app_input_1.SETTINGS_FILE_PATH}: ${error}`); + return null; + } +} /***/ }), @@ -125532,7 +126938,7 @@ class DependencyTrackStatusService { try { const octokit = (0, github_1.getOctokit)(inputs.GITHUB_TOKEN); // eslint-disable-next-line @typescript-eslint/await-thenable - const sha = await (0, github_utils_1.getSHA)(); // TODO review with Groh + const sha = await (0, github_utils_1.getSHA)(); // TODO Review let conclusion; let title; let summary; @@ -126463,10 +127869,11 @@ class ScanService { const args = await this.buildArgs(); const { stdout, stderr, exitCode } = await exec.getExecOutput(app_input_1.EXECUTABLE, args, options); if (exitCode !== 0) { - core.warning(`Scan execution completed with exit code ${exitCode}`); + core.error(`Scan execution completed with exit code ${exitCode}`); if (stderr) { - core.debug(`Scan stderr: ${stderr}`); + core.error(`Scan stderr: ${stderr}`); } + throw new Error(`Scan execution failed with stderr: ${stderr}`); } const scan = await this.parseResult(); return { scan, stdout, stderr }; @@ -126680,69 +128087,637 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.scanossService = exports.ScanOssService = void 0; -const exec = __importStar(__nccwpck_require__(71514)); -const inputs = __importStar(__nccwpck_require__(483)); +exports.scanossService = exports.ScanOssService = void 0; +const exec = __importStar(__nccwpck_require__(71514)); +const inputs = __importStar(__nccwpck_require__(483)); +const core = __importStar(__nccwpck_require__(42186)); +const github_service_1 = __nccwpck_require__(43123); +const app_output_1 = __nccwpck_require__(22698); +/** + * Service for converting SCANOSS scan results to different formats using scanoss-py. + * Currently supports CycloneDX format conversion for integration with other tools. + */ +class ScanOssService { + /** + * Build scanoss-py CycloneDX conversion parameters */ + buildCycloneDXParameters() { + return [ + 'run', + '-v', + `${inputs.REPO_DIR}:/scanoss`, + inputs.RUNTIME_CONTAINER, + 'convert', + '--input', + `./${inputs.OUTPUT_FILEPATH}`, + '--format', + 'cyclonedx', + '--output', + `./${app_output_1.CYCLONEDX_FILE_NAME}` + ]; + } + /** + * Converts SCANOSS results to CycloneDX format using scanoss-py. + * Currently always generates CycloneDX file which can be used by Dependency Track + * or uploaded as an artifact for other integrations. + */ + async scanResultsToCycloneDX() { + try { + core.info('Converting SCANOSS results to CycloneDX format...'); + const options = { + failOnStdErr: false, + ignoreReturnCode: false + }; + const { exitCode } = await exec.getExecOutput(inputs.EXECUTABLE, this.buildCycloneDXParameters(), options); + if (exitCode !== 0) { + return new Error(`Error converting scan results into CycloneDX format`); + } + // Check if CycloneDX file was actually created before trying to upload it + try { + const fs = await Promise.resolve(/* import() */).then(__nccwpck_require__.t.bind(__nccwpck_require__, 57147, 23)); + await fs.promises.access(app_output_1.CYCLONEDX_FILE_NAME, fs.constants.F_OK); + await (0, github_service_1.uploadToArtifacts)(app_output_1.CYCLONEDX_FILE_NAME); + core.info('Successfully converted results into CycloneDX format'); + } + catch (fileError) { + // File doesn't exist - this can happen with empty repos + core.info('CycloneDX conversion completed but no file generated (likely empty repository)'); + } + } + catch (e) { + core.error(e.message); + } + } +} +exports.ScanOssService = ScanOssService; +exports.scanossService = new ScanOssService(); + + +/***/ }), + +/***/ 81655: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +// SPDX-License-Identifier: MIT +/* + Copyright (c) 2025, SCANOSS + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.createFileMatchSummaryAnnotation = exports.createSnippetSummaryAnnotation = void 0; +const core = __importStar(__nccwpck_require__(42186)); +const github_utils_1 = __nccwpck_require__(17889); +const line_parsers_1 = __nccwpck_require__(70622); +/** + * Creates a GitHub URL for the file + * @param filePath - The file path relative to repository root + * @returns GitHub URL for the file + */ +function getFileUrl(filePath) { + const { owner, repo, sha } = (0, github_utils_1.resolveRepoAndSha)(); + return `https://github.com/${owner}/${repo}/blob/${sha}/${filePath}`; +} +/** + * Creates a GitHub URL for the file with line highlighting + * @param filePath - The file path relative to repository root + * @param lineRange - The line range to highlight + * @returns GitHub URL for the file with line anchors + */ +function getFileUrlWithLineHighlight(filePath, lineRange) { + const { owner, repo, sha } = (0, github_utils_1.resolveRepoAndSha)(); + const baseUrl = `https://github.com/${owner}/${repo}/blob/${sha}/${filePath}`; + if (lineRange.start === lineRange.end) { + return `${baseUrl}#L${lineRange.start}`; + } + else { + return `${baseUrl}#L${lineRange.start}-L${lineRange.end}`; + } +} +/** + * Creates a summary annotation for snippet matches (not bound to any file) + * @param snippetMatches - Array of snippet matches with file paths + */ +function createSnippetSummaryAnnotation(snippetMatches) { + const { owner, repo, sha } = (0, github_utils_1.resolveRepoAndSha)(); + const commitUrl = `https://github.com/${owner}/${repo}/commit/${sha}`; + let message = `Found ${snippetMatches.length} snippet matches\n`; + message += `šŸ“ [View detailed comments on commit](${commitUrl})\n\n`; + message += `**Affected Files:**\n`; + // Group matches by file and limit to avoid annotation length issues + const fileGroups = snippetMatches.reduce((groups, { filePath, match }) => { + if (!groups[filePath]) + groups[filePath] = []; + groups[filePath].push(match); + return groups; + }, {}); + const fileEntries = Object.entries(fileGroups).slice(0, 10); // Limit to 10 files + for (const [filePath, matches] of fileEntries) { + const firstMatch = matches[0]; + const localLines = (0, line_parsers_1.parseLineRange)(firstMatch.lines); + const fileUrl = localLines ? getFileUrlWithLineHighlight(filePath, localLines) : getFileUrl(filePath); + message += `- [${filePath}](${fileUrl}) (${matches.length} match${matches.length > 1 ? 'es' : ''})\n`; + } + if (Object.keys(fileGroups).length > 10) { + message += `- ... and ${Object.keys(fileGroups).length - 10} more files\n`; + } + core.notice(message, { + title: 'Code Snippet Matches Summary' + }); + core.info(`Created snippet summary annotation for ${snippetMatches.length} matches`); +} +exports.createSnippetSummaryAnnotation = createSnippetSummaryAnnotation; +/** + * Creates a summary annotation for file matches (not bound to any file) + * @param fileMatches - Array of file matches with file paths + */ +function createFileMatchSummaryAnnotation(fileMatches) { + const { owner, repo, sha } = (0, github_utils_1.resolveRepoAndSha)(); + const commitUrl = `https://github.com/${owner}/${repo}/commit/${sha}`; + let message = `Found ${fileMatches.length} full file matches\n`; + message += `šŸ“ [View detailed comments on commit](${commitUrl})\n\n`; + message += `**Affected Files:**\n`; + // Limit to avoid annotation length issues + const limitedMatches = fileMatches.slice(0, 10); + for (const { filePath, match } of limitedMatches) { + const fileUrl = getFileUrl(filePath); + const component = `${match.component}${match.version ? ` v${match.version}` : ''}`; + message += `- [${filePath}](${fileUrl}) → ${component}\n`; + } + if (fileMatches.length > 10) { + message += `- ... and ${fileMatches.length - 10} more files\n`; + } + core.notice(message, { + title: 'Full File Matches Summary' + }); + core.info(`Created file match summary annotation for ${fileMatches.length} matches`); +} +exports.createFileMatchSummaryAnnotation = createFileMatchSummaryAnnotation; + + +/***/ }), + +/***/ 59049: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +// SPDX-License-Identifier: MIT +/* + Copyright (c) 2025, SCANOSS + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.requestDeduplicator = exports.apiCache = void 0; +/** + * Simple in-memory cache for API responses to avoid duplicate requests + * + * This cache is designed for single GitHub Actions runs and automatically + * expires entries after a reasonable time to avoid stale data. + */ +class ApiCache { + cache = new Map(); + defaultTtl = 300000; // 5 minutes in milliseconds + /** + * Gets a cached value if it exists and hasn't expired + * @param key - The cache key + * @param ttl - Time to live in milliseconds (optional, defaults to 5 minutes) + * @returns The cached value or null if not found/expired + */ + get(key, ttl = this.defaultTtl) { + const entry = this.cache.get(key); + if (!entry) { + return null; + } + const isExpired = Date.now() - entry.timestamp > ttl; + if (isExpired) { + this.cache.delete(key); + return null; + } + return entry.data; + } + /** + * Sets a value in the cache + * @param key - The cache key + * @param value - The value to cache + */ + set(key, value) { + this.cache.set(key, { + data: value, + timestamp: Date.now() + }); + } + /** + * Clears all cached entries + */ + clear() { + this.cache.clear(); + } + /** + * Gets the current cache size + * @returns Number of cached entries + */ + size() { + return this.cache.size; + } + /** + * Generates a cache key from multiple parameters + * @param params - Parameters to include in the key + * @returns A consistent cache key string + */ + static generateKey(...params) { + return params + .filter(p => p !== undefined) + .map(p => String(p)) + .join(':'); + } +} +/** + * Global cache instance for API responses + */ +exports.apiCache = new ApiCache(); +/** + * Request deduplication utility to prevent multiple identical API calls + */ +class RequestDeduplicator { + pending = new Map(); + /** + * Executes a function only once per key, returning the same promise for duplicate calls + * @param key - Unique key for the request + * @param fn - Function to execute + * @returns Promise that resolves to the function result + */ + async deduplicate(key, fn) { + // Check if request is already pending + if (this.pending.has(key)) { + return this.pending.get(key); + } + // Execute the function and cache the promise + const promise = fn().finally(() => { + // Remove from pending when complete + this.pending.delete(key); + }); + this.pending.set(key, promise); + return promise; + } + /** + * Gets the number of pending requests + * @returns Number of pending requests + */ + pendingCount() { + return this.pending.size; + } + /** + * Clears all pending requests (use with caution) + */ + clear() { + this.pending.clear(); + } +} +/** + * Global request deduplicator instance + */ +exports.requestDeduplicator = new RequestDeduplicator(); + + +/***/ }), + +/***/ 7388: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +// SPDX-License-Identifier: MIT +/* + Copyright (c) 2025, SCANOSS + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.createMainConversationComment = exports.createFileCommitComment = exports.createSnippetCommitComment = void 0; const core = __importStar(__nccwpck_require__(42186)); -const github_service_1 = __nccwpck_require__(43123); -const app_output_1 = __nccwpck_require__(22698); +const github_1 = __nccwpck_require__(95438); +const inputs = __importStar(__nccwpck_require__(483)); +const github_utils_1 = __nccwpck_require__(17889); +const line_parsers_1 = __nccwpck_require__(70622); +const api_cache_1 = __nccwpck_require__(59049); /** - * Service for converting SCANOSS scan results to different formats using scanoss-py. - * Currently supports CycloneDX format conversion for integration with other tools. + * Creates a GitHub URL for the file + * @param filePath - The file path relative to repository root + * @returns GitHub URL for the file */ -class ScanOssService { - /** - * Build scanoss-py CycloneDX conversion parameters */ - buildCycloneDXParameters() { - return [ - 'run', - '-v', - `${inputs.REPO_DIR}:/scanoss`, - inputs.RUNTIME_CONTAINER, - 'convert', - '--input', - `./${inputs.OUTPUT_FILEPATH}`, - '--format', - 'cyclonedx', - '--output', - `./${app_output_1.CYCLONEDX_FILE_NAME}` - ]; +function getFileUrl(filePath) { + const { owner, repo, sha } = (0, github_utils_1.resolveRepoAndSha)(); + return `https://github.com/${owner}/${repo}/blob/${sha}/${filePath}`; +} +/** + * Creates a GitHub URL with line highlighting for the file + */ +function getFileUrlWithLineHighlight(filePath, lineRange) { + const { owner, repo, sha } = (0, github_utils_1.resolveRepoAndSha)(); + const baseUrl = `https://github.com/${owner}/${repo}/blob/${sha}/${filePath}`; + if (lineRange.start === lineRange.end) { + return `${baseUrl}#L${lineRange.start}`; } - /** - * Converts SCANOSS results to CycloneDX format using scanoss-py. - * Currently always generates CycloneDX file which can be used by Dependency Track - * or uploaded as an artifact for other integrations. - */ - async scanResultsToCycloneDX() { - try { - core.info('Converting SCANOSS results to CycloneDX format...'); - const options = { - failOnStdErr: false, - ignoreReturnCode: false - }; - const { exitCode } = await exec.getExecOutput(inputs.EXECUTABLE, this.buildCycloneDXParameters(), options); - if (exitCode !== 0) { - return new Error(`Error converting scan results into CycloneDX format`); - } - // Check if CycloneDX file was actually created before trying to upload it - try { - const fs = await Promise.resolve(/* import() */).then(__nccwpck_require__.t.bind(__nccwpck_require__, 57147, 23)); - await fs.promises.access(app_output_1.CYCLONEDX_FILE_NAME, fs.constants.F_OK); - await (0, github_service_1.uploadToArtifacts)(app_output_1.CYCLONEDX_FILE_NAME); - core.info('Successfully converted results into CycloneDX format'); - } - catch (fileError) { - // File doesn't exist - this can happen with empty repos - core.info('CycloneDX conversion completed but no file generated (likely empty repository)'); - } + else { + return `${baseUrl}#L${lineRange.start}-L${lineRange.end}`; + } +} +function formatSnippetAnnotationMessage(filePath, snippet, localLines) { + let message = `Code snippet matches ${snippet.component}`; + if (snippet.version) { + message += ` v${snippet.version}`; + } + message += ` (${snippet.matched}% similarity)`; + // Add license information + if (snippet.licenses && snippet.licenses.length > 0) { + const license = snippet.licenses[0]; + message += `\nLicense: ${license.name}`; + } + // Add source URL + if (snippet.url) { + message += `\nSource: ${snippet.url}`; + } + // Add OSS line range + if (snippet.oss_lines) { + message += `\nOSS Lines: ${snippet.oss_lines}`; + } + // Add view link + const viewUrl = snippet.lines === 'all' ? getFileUrl(filePath) : getFileUrlWithLineHighlight(filePath, localLines); + message += `\nView: ${viewUrl}`; + return message; +} +/** + * Formats the file match information into an annotation message + * @param filePath - The file path + * @param fileMatch - The file match data + * @returns Formatted annotation message + */ +function formatFileAnnotationMessage(filePath, fileMatch) { + const fileUrl = getFileUrl(filePath); + const component = `${fileMatch.component}${fileMatch.version ? ` v${fileMatch.version}` : ''}`; + let message = `**Full file match detected in [${filePath}](${fileUrl})**\n\n`; + message += `- **Component**: ${component}\n`; + if (fileMatch.licenses && fileMatch.licenses.length > 0) { + const license = fileMatch.licenses[0]; + message += `- **License**: ${license.name}\n`; + } + if (fileMatch.url) { + message += `- **Source**: [${fileMatch.url}](${fileMatch.url})\n`; + } + return message; +} +/** + * Creates a commit comment for a snippet match with detailed similarity information + * + * This function processes SCANOSS snippet match results and creates GitHub commit comments + * that provide developers with actionable information about code similarities found in their commits. + * + * @param filePath - The file path relative to repository root where the match was found + * @param snippetMatch - The snippet match data from SCANOSS containing similarity details + * + * @example + * ```typescript + * await createSnippetCommitComment('src/utils/parser.ts', { + * file: 'parser.ts', + * component: 'example-lib', + * version: '1.2.3', + * matched: '85', + * lines: '15-25', + * oss_lines: '10-20', + * licenses: [{ name: 'MIT' }] + * }); + * ``` + * + * @returns Promise - true on success, false on failure or skip + */ +async function createSnippetCommitComment(filePath, snippetMatch) { + const localLines = (0, line_parsers_1.parseLineRange)(snippetMatch.lines); + if (!localLines) { + core.warning(`Could not parse line range: ${snippetMatch.lines} for file: ${filePath}`); + return false; + } + const message = formatSnippetAnnotationMessage(filePath, snippetMatch, localLines); + const commentBody = `šŸ” **Code Similarity Found**\n\n${message}`; + try { + const octokit = (0, github_1.getOctokit)(inputs.GITHUB_TOKEN); + const params = { + owner: github_1.context.repo.owner, + repo: github_1.context.repo.repo, + commit_sha: github_1.context.sha, + path: filePath, + line: localLines.start, + body: commentBody + }; + core.info(`Creating commit comment for snippet match at ${filePath}`); + // Use request deduplication to prevent duplicate comments for the same file + const deduplicationKey = `snippet-comment:${github_1.context.sha}:${filePath}:${snippetMatch.component}`; + await api_cache_1.requestDeduplicator.deduplicate(deduplicationKey, async () => { + return await octokit.rest.repos.createCommitComment(params); + }); + core.info(`Successfully created commit comment for snippet match at ${filePath}`); + return true; + } + catch (error) { + core.error(`Failed to create commit comment for ${filePath}`); + if (error instanceof Error) { + core.error(`Error: ${error.message}`); + // Extra diagnostics (status, url) if present + const status = error?.status; + const url = error?.request?.url; + if (status || url) + core.error(`Context: status=${status ?? 'n/a'} url=${url ?? 'n/a'}`); + core.debug(`Error details: ${JSON.stringify(error, null, 2)}`); } - catch (e) { - core.error(e.message); + return false; + } +} +exports.createSnippetCommitComment = createSnippetCommitComment; +/** + * Creates a commit comment for a file match + * @param filePath - The file path + * @param fileMatch - The file match data + * @returns Promise - true on success, false on failure + */ +async function createFileCommitComment(filePath, fileMatch) { + const message = formatFileAnnotationMessage(filePath, fileMatch); + const commentBody = `šŸ“„ **Full File Match Found**\n\n${message}`; + try { + const octokit = (0, github_1.getOctokit)(inputs.GITHUB_TOKEN); + const params = { + owner: github_1.context.repo.owner, + repo: github_1.context.repo.repo, + commit_sha: github_1.context.sha, + path: filePath, + body: commentBody + }; + core.info(`Creating file commit comment for ${filePath}`); + // Use request deduplication to prevent duplicate comments for the same file + const deduplicationKey = `file-comment:${github_1.context.sha}:${filePath}:${fileMatch.component}${fileMatch.version ? `:v${fileMatch.version}` : ''}`; + await api_cache_1.requestDeduplicator.deduplicate(deduplicationKey, async () => { + return await octokit.rest.repos.createCommitComment(params); + }); + core.info(`Successfully created commit comment for file match at ${filePath}`); + return true; + } + catch (error) { + core.error(`Failed to create commit comment for ${filePath}`); + if (error instanceof Error) { + core.error(`Error: ${error.message}`); + // Extra diagnostics (status, url) if present + const status = error?.status; + const url = error?.request?.url; + if (status || url) + core.error(`Context: status=${status ?? 'n/a'} url=${url ?? 'n/a'}`); + core.debug(`Error details: ${JSON.stringify(error, null, 2)}`); } + return false; } } -exports.ScanOssService = ScanOssService; -exports.scanossService = new ScanOssService(); +exports.createFileCommitComment = createFileCommitComment; +/** + * Creates a main conversation comment with summary and commit link + * @param snippetMatches - Array of snippet matches with file paths + * @param fileMatches - Array of file matches with file paths + */ +async function createMainConversationComment(snippetMatches, fileMatches) { + if (!(0, github_utils_1.isPullRequest)()) { + core.info('Skipping main conversation comment - not in PR context'); + return; + } + const { owner, repo, sha } = (0, github_utils_1.resolveRepoAndSha)(); + const commitUrl = `https://github.com/${owner}/${repo}/commit/${sha}`; + let message = `## šŸ” SCANOSS Code Similarity Detected\n\n`; + if (snippetMatches.length > 0) { + message += `šŸ“„ **${snippetMatches.length} snippet matches** found\n`; + } + if (fileMatches.length > 0) { + message += `šŸ“‹ **${fileMatches.length} full file matches** found\n`; + } + message += `\nšŸ”— **[View detailed findings on commit ${github_1.context.sha.substring(0, 7)}](${commitUrl})**\n\n`; + // Quick overview of most affected files + const allFiles = new Set([...snippetMatches.map(m => m.filePath), ...fileMatches.map(m => m.filePath)]); + if (allFiles.size <= 5) { + message += `**Files with similarities:**\n`; + for (const filePath of Array.from(allFiles).slice(0, 5)) { + message += `- \`${filePath}\`\n`; + } + } + else { + message += `**${allFiles.size} files** contain code similarities\n`; + } + message += `\nšŸ’” Click the commit link above to see detailed annotations for each match.`; + try { + const octokit = (0, github_1.getOctokit)(inputs.GITHUB_TOKEN); + await octokit.rest.issues.createComment({ + issue_number: github_1.context.issue.number, + owner: github_1.context.repo.owner, + repo: github_1.context.repo.repo, + body: message + }); + core.info(`Successfully created main conversation comment (PR #${github_1.context.issue.number})`); + } + catch (error) { + core.error(`Failed to create main conversation comment: ${error}`); + } +} +exports.createMainConversationComment = createMainConversationComment; /***/ }), @@ -126798,7 +128773,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getFirstRunId = exports.createCommentOnPR = exports.getSHA = exports.isPullRequest = void 0; +exports.getFirstRunId = exports.createCommentOnPR = exports.resolveRepoAndSha = exports.getSHA = exports.isPullRequest = void 0; const github_1 = __nccwpck_require__(95438); const core = __importStar(__nccwpck_require__(42186)); const inputs = __importStar(__nccwpck_require__(483)); @@ -126825,6 +128800,30 @@ function getSHA() { return sha; } exports.getSHA = getSHA; +/** + * Resolves the correct repository owner, repo name, and SHA for fork-safe URL generation. + * For pull requests from forks, this ensures URLs point to the correct repository and commit. + */ +function resolveRepoAndSha() { + if (isPullRequest()) { + const pull = github_1.context.payload.pull_request; + if (pull?.head) { + // For PRs, use the head repository and SHA to ensure we point to the correct branch/fork + return { + owner: pull.head.repo?.owner.login || github_1.context.repo.owner, + repo: pull.head.repo?.name || github_1.context.repo.repo, + sha: pull.head.sha || github_1.context.sha + }; + } + } + // Default to context values for non-PR scenarios + return { + owner: github_1.context.repo.owner, + repo: github_1.context.repo.repo, + sha: github_1.context.sha + }; +} +exports.resolveRepoAndSha = resolveRepoAndSha; /** * Creates a comment on the current pull request with the provided message. */ @@ -127006,6 +129005,111 @@ exports.LicenseUtil = LicenseUtil; exports.licenseUtil = new LicenseUtil(); +/***/ }), + +/***/ 70622: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +// SPDX-License-Identifier: MIT +/* + Copyright (c) 2025, SCANOSS + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.parseLineRange = void 0; +/** + * Sanitizes line range string by removing non-numeric, non-comma, non-dash characters + * @param str - The string to sanitize + * @returns Sanitized string containing only digits, commas, and dashes + */ +function sanitizeLineRange(str) { + return str.replace(/[^0-9,-]/g, ''); +} +/** + * Extracts first and last numbers from a string using regex + * @param str - The string to extract numbers from + * @returns Object with first and last numbers as strings, or null if not found + */ +function extractFirstAndLastNumbers(str) { + // Sanitize input to handle formats like "L7-L9, L47-L81" + const sanitized = sanitizeLineRange(str); + const match = sanitized.match(/^(\d+).*?(\d+)(?!.*\d)/); + if (match) { + return { + first: match[1], + last: match[2] + }; + } + return null; +} +/** + * Parses various line range string formats into a standardized LineRange object + * + * This function handles multiple line range formats commonly used in code analysis tools: + * - Simple ranges: "4-142" + * - Complex ranges: "7-9,47-81,99-158" (uses first and last numbers) + * - Single lines: "25" + * - Special values: "all" (defaults to line 1) + * - Prefixed formats: "L7-L9" (strips non-numeric characters) + * + * The function is designed to be robust and handle malformed input gracefully by + * sanitizing the input and falling back to reasonable defaults. + * + * @param lineRange - The line range string to parse (various formats supported) + * @returns Parsed line range object with start and end properties, or null if parsing fails + * + * @example + * ```typescript + * parseLineRange("15-25") // { start: 15, end: 25 } + * parseLineRange("L7-L9,L47-L81") // { start: 7, end: 81 } + * parseLineRange("42") // { start: 42, end: 42 } + * parseLineRange("all") // { start: 1, end: 1 } + * parseLineRange("invalid") // null + * ``` + */ +function parseLineRange(lineRange) { + if (lineRange === 'all') { + return { start: 1, end: 1 }; + } + // Handle complex ranges like "7-9,47-81,99-158" using regex to get first and last numbers + const extracted = extractFirstAndLastNumbers(lineRange); + if (extracted) { + const start = parseInt(extracted.first, 10); + const end = parseInt(extracted.last, 10); + if (!isNaN(start) && !isNaN(end)) { + return { start, end }; + } + } + // Fallback for single number (sanitize first) + const sanitized = sanitizeLineRange(lineRange); + const singleLine = parseInt(sanitized, 10); + if (!isNaN(singleLine)) { + return { start: singleLine, end: singleLine }; + } + return null; +} +exports.parseLineRange = parseLineRange; + + /***/ }), /***/ 96011: @@ -127066,6 +129170,124 @@ const generateTable = (headers, rows, centeredColumns) => { exports.generateTable = generateTable; +/***/ }), + +/***/ 71325: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +// SPDX-License-Identifier: MIT +/* + Copyright (c) 2024, SCANOSS + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ +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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.createSnippetAnnotations = void 0; +const core = __importStar(__nccwpck_require__(42186)); +const fs = __importStar(__nccwpck_require__(57147)); +const github_1 = __nccwpck_require__(95438); +const annotation_creators_1 = __nccwpck_require__(81655); +const github_comment_api_1 = __nccwpck_require__(7388); +/** + * Creates hybrid snippet annotations: summary annotations + commit comments + */ +async function createSnippetAnnotations(resultsPath) { + if (!fs.existsSync(resultsPath)) { + core.warning(`Results file not found: ${resultsPath}`); + return; + } + try { + const resultsContent = fs.readFileSync(resultsPath, 'utf8'); + const results = JSON.parse(resultsContent); + const snippetMatches = []; + const fileMatches = []; + // Collect all matches + for (const [filePath, matches] of Object.entries(results)) { + if (!Array.isArray(matches)) + continue; + for (const match of matches) { + if (match.status === 'pending') { + if (match.id === 'snippet') { + snippetMatches.push({ filePath, match: match }); + } + else if (match.id === 'file') { + fileMatches.push({ filePath, match: match }); + } + } + } + } + // Create summary annotations (not bound to files) + if (snippetMatches.length > 0) { + (0, annotation_creators_1.createSnippetSummaryAnnotation)(snippetMatches); + } + if (fileMatches.length > 0) { + (0, annotation_creators_1.createFileMatchSummaryAnnotation)(fileMatches); + } + // Log GitHub context for debugging + core.info(`GitHub context: owner=${github_1.context.repo.owner}, repo=${github_1.context.repo.repo}, sha=${github_1.context.sha}`); + // Create individual commit comments for each match (in parallel) + const snippetPromises = snippetMatches.map(async ({ filePath, match }) => (0, github_comment_api_1.createSnippetCommitComment)(filePath, match)); + const filePromises = fileMatches.map(async ({ filePath, match }) => (0, github_comment_api_1.createFileCommitComment)(filePath, match)); + const commentResults = await Promise.all([...snippetPromises, ...filePromises]); + const successCount = commentResults.filter(Boolean).length; + const failedCount = commentResults.length - successCount; + if (failedCount > 0) { + core.warning(`${failedCount} commit comments failed to create, ${successCount} succeeded`); + } + // Create main conversation comment if we have any matches + if (snippetMatches.length > 0 || fileMatches.length > 0) { + await (0, github_comment_api_1.createMainConversationComment)(snippetMatches, fileMatches); + } + core.info(`Created summary annotations, attempted ${snippetMatches.length + fileMatches.length} commit comments, and main conversation comment`); + } + catch (error) { + core.error(`Failed to create snippet annotations from ${resultsPath}: ${error}`); + } +} +exports.createSnippetAnnotations = createSnippetAnnotations; + + /***/ }), /***/ 13060: @@ -127241,6 +129463,14 @@ module.exports = require("net"); /***/ }), +/***/ 6005: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:crypto"); + +/***/ }), + /***/ 15673: /***/ ((module) => { diff --git a/dist/licenses.txt b/dist/licenses.txt index bd8bf07b..e5ae97db 100644 --- a/dist/licenses.txt +++ b/dist/licenses.txt @@ -1744,6 +1744,31 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEAL buffers +call-bind-apply-helpers +MIT +MIT License + +Copyright (c) 2024 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + chainsaw MIT/X11 @@ -2090,6 +2115,131 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +dunder-proto +MIT +MIT License + +Copyright (c) 2024 ECMAScript Shims + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +es-define-property +MIT +MIT License + +Copyright (c) 2024 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +es-errors +MIT +MIT License + +Copyright (c) 2024 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +es-object-atoms +MIT +MIT License + +Copyright (c) 2024 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +es-set-tostringtag +MIT +MIT License + +Copyright (c) 2022 ECMAScript Shims + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + event-target-shim MIT The MIT License (MIT) @@ -2164,6 +2314,80 @@ Copyright (c) 2012 Felix Geisendƶrfer (felix@debuggable.com) and contributors THE SOFTWARE. +function-bind +MIT +Copyright (c) 2013 Raynos. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + + +get-intrinsic +MIT +MIT License + +Copyright (c) 2020 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +get-proto +MIT +MIT License + +Copyright (c) 2025 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + glob ISC The ISC License @@ -2183,6 +2407,31 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +gopd +MIT +MIT License + +Copyright (c) 2022 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + graceful-fs ISC The ISC License @@ -2202,6 +2451,81 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +has-symbols +MIT +MIT License + +Copyright (c) 2016 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +has-tostringtag +MIT +MIT License + +Copyright (c) 2021 Inspect JS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +hasown +MIT +MIT License + +Copyright (c) Jordan Harband and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + inherits ISC The ISC License @@ -2385,6 +2709,31 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +math-intrinsics +MIT +MIT License + +Copyright (c) 2024 ECMAScript Shims + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + mime-db MIT (The MIT License) diff --git a/package-lock.json b/package-lock.json index bbb8a398..717f7f7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "scanoss-code-scan-action", - "version": "1.2.1", + "version": "1.2.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "scanoss-code-scan-action", - "version": "1.2.1", + "version": "1.2.3", "license": "MIT", "dependencies": { "@actions/artifact": "^2.3.2", @@ -370,89 +370,20 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { "version": "7.22.9", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", @@ -646,19 +577,21 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -673,109 +606,28 @@ } }, "node_modules/@babel/helpers": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.11.tgz", - "integrity": "sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.11", - "@babel/types": "^7.22.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz", - "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "@babel/types": "^7.28.4" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -961,26 +813,25 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", - "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "dev": true, - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1017,14 +868,14 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1767,30 +1618,18 @@ } }, "node_modules/@octokit/endpoint": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.4.tgz", - "integrity": "sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "license": "MIT", "dependencies": { - "@octokit/types": "^12.0.0", + "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" }, "engines": { "node": ">= 18" } }, - "node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", - "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" - }, - "node_modules/@octokit/endpoint/node_modules/@octokit/types": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", - "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", - "dependencies": { - "@octokit/openapi-types": "^20.0.0" - } - }, "node_modules/@octokit/graphql": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz", @@ -1820,21 +1659,21 @@ "node_modules/@octokit/openapi-types": { "version": "22.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", - "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", - "dev": true + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" }, "node_modules/@octokit/plugin-paginate-rest": { - "version": "9.1.5", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.1.5.tgz", - "integrity": "sha512-WKTQXxK+bu49qzwv4qKbMMRXej1DU2gq017euWyKVudA6MldaSSQuxtz+vGbhxV4CjxpUxjZu6rM2wfc1FiWVg==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", + "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", + "license": "MIT", "dependencies": { - "@octokit/types": "^12.4.0" + "@octokit/types": "^12.6.0" }, "engines": { "node": ">= 18" }, "peerDependencies": { - "@octokit/core": ">=5" + "@octokit/core": "5" } }, "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { @@ -1908,13 +1747,14 @@ } }, "node_modules/@octokit/request": { - "version": "8.1.6", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.6.tgz", - "integrity": "sha512-YhPaGml3ncZC1NfXpP3WZ7iliL1ap6tLkAp6MvbK2fTTPytzVUyUesBBogcdMm86uRYO5rHaM1xIWxigWZ17MQ==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "license": "MIT", "dependencies": { - "@octokit/endpoint": "^9.0.0", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0", + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" }, "engines": { @@ -1922,11 +1762,12 @@ } }, "node_modules/@octokit/request-error": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz", - "integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "license": "MIT", "dependencies": { - "@octokit/types": "^12.0.0", + "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" }, @@ -1934,37 +1775,10 @@ "node": ">= 18" } }, - "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", - "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" - }, - "node_modules/@octokit/request-error/node_modules/@octokit/types": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", - "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", - "dependencies": { - "@octokit/openapi-types": "^20.0.0" - } - }, - "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", - "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" - }, - "node_modules/@octokit/request/node_modules/@octokit/types": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", - "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", - "dependencies": { - "@octokit/openapi-types": "^20.0.0" - } - }, "node_modules/@octokit/types": { "version": "13.5.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", - "dev": true, "dependencies": { "@octokit/openapi-types": "^22.2.0" } @@ -2375,10 +2189,11 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -3013,22 +2828,24 @@ "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -3147,6 +2964,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3539,6 +3369,20 @@ "node": ">=6.0.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3630,15 +3474,46 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -4499,10 +4374,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -4584,12 +4460,15 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -4617,10 +4496,13 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.6", @@ -4668,15 +4550,24 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4691,6 +4582,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -4802,12 +4706,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4900,10 +4804,10 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4912,12 +4816,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -4926,6 +4830,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -5194,6 +5110,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -5977,7 +5894,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -6384,6 +6302,15 @@ "tmpl": "1.0.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -6400,12 +6327,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -6826,10 +6754,11 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -7137,12 +7066,6 @@ "node": ">=10" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", - "dev": true - }, "node_modules/regexp.prototype.flags": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", @@ -7738,20 +7661,12 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -8021,9 +7936,10 @@ } }, "node_modules/undici": { - "version": "5.28.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz", - "integrity": "sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==", + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "license": "MIT", "dependencies": { "@fastify/busboy": "^2.0.0" }, @@ -8042,9 +7958,10 @@ "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" }, "node_modules/unzip-stream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/unzip-stream/-/unzip-stream-0.3.1.tgz", - "integrity": "sha512-RzaGXLNt+CW+T41h1zl6pGz3EaeVhYlK+rdAap+7DxW5kqsqePO8kRtWPaCiVqdhZc86EctSPVYNix30YOMzmw==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/unzip-stream/-/unzip-stream-0.3.4.tgz", + "integrity": "sha512-PyofABPVv+d7fL7GOpusx7eRT9YETY2X04PhwbSipdj6bMxVCFJrr+nm0Mxqbf9hUiTin/UsnuFWBXlDZFy0Cw==", + "license": "MIT", "dependencies": { "binary": "^0.3.0", "mkdirp": "^0.5.1" diff --git a/package.json b/package.json index 8d2d0b83..5772764e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "scanoss-code-scan-action", "description": "SCANOSS Code Scan Action", - "version": "1.2.2", + "version": "1.2.3", "author": "SCANOSS", "private": true, "homepage": "https://github.com/scanoss/code-scan-action/", diff --git a/src/app.input.ts b/src/app.input.ts index cf53ae87..ea72262a 100644 --- a/src/app.input.ts +++ b/src/app.input.ts @@ -109,9 +109,11 @@ export const COPYLEFT_LICENSE_EXPLICIT = core.getInput('licenses.copyleft.explic // Runtime Configuration /** Docker container image for scanoss-py execution */ -export const RUNTIME_CONTAINER = core.getInput('runtimeContainer') || 'ghcr.io/scanoss/scanoss-py:v1.31.5'; +export const RUNTIME_CONTAINER = core.getInput('runtimeContainer') || 'ghcr.io/scanoss/scanoss-py:v1.32.0'; /** Skip snippet generation during scan */ export const SKIP_SNIPPETS = core.getInput('skipSnippets') === 'true'; +/** Enable match annotations and commit comments */ +export const MATCH_ANNOTATIONS = core.getInput('matchAnnotations') === 'true'; /** Enable file scanning */ export const SCAN_FILES = core.getInput('scanFiles') === 'true'; /** Enable SCANOSS settings file usage */ diff --git a/src/main.ts b/src/main.ts index 1944817e..c428adba 100644 --- a/src/main.ts +++ b/src/main.ts @@ -32,6 +32,7 @@ import { DepTrackPolicyCheck } from './policies/dep-track-policy-check'; import { dependencyTrackService } from './services/dependency-track.service'; import { dependencyTrackStatusService } from './services/dependency-track-status.service'; import { scanossService } from './services/scanoss.service'; +import { createSnippetAnnotations } from './utils/snippet-annotations.utils'; /** * The main function for the action. @@ -39,6 +40,10 @@ import { scanossService } from './services/scanoss.service'; */ export async function run(): Promise { try { + // Mask sensitive inputs to prevent accidental leakage in logs + if (inputs.API_KEY) core.setSecret(inputs.API_KEY); + if (inputs.GITHUB_TOKEN) core.setSecret(inputs.GITHUB_TOKEN); + core.debug(`SCANOSS Scan Action started...`); // create policies @@ -71,6 +76,14 @@ export async function run(): Promise { await policy.run(); } + // 5: Create snippet match annotations + if (inputs.MATCH_ANNOTATIONS) { + core.info('Creating match annotations and commit comments...'); + await createSnippetAnnotations(inputs.OUTPUT_FILEPATH); + } else { + core.info('Skipping match annotations - disabled by matchAnnotations parameter'); + } + if (isPullRequest()) { // create reports const report = await generatePRSummary(policies); diff --git a/src/policies/dep-track-policy-check.ts b/src/policies/dep-track-policy-check.ts index 82f9ff81..4d1910f7 100644 --- a/src/policies/dep-track-policy-check.ts +++ b/src/policies/dep-track-policy-check.ts @@ -237,12 +237,6 @@ export class DepTrackPolicyCheck extends PolicyCheck { }; const { stdout, stderr, exitCode } = await exec.getExecOutput(EXECUTABLE, args, options); - - // Display stdout (policy results) unless it's empty - if (stdout && stdout.trim()) { - core.info(stdout); - } - // Only display stderr if it's a real error, not informational messages if (stderr && !this.isInformationalMessage(stderr)) { core.error(stderr); // Display real errors to user diff --git a/src/policies/policy-check.ts b/src/policies/policy-check.ts index ca63dcd5..4668eb83 100644 --- a/src/policies/policy-check.ts +++ b/src/policies/policy-check.ts @@ -71,6 +71,8 @@ export abstract class PolicyCheck { private _firstRunId = -1; + private detailsUrl: string | null = null; + /** * Initializes the policy check with GitHub integration. */ @@ -107,8 +109,8 @@ export abstract class PolicyCheck { name: this.checkName, head_sha: getSHA() }); - this.checkRunId = result.data.id; + this.detailsUrl = result.data.details_url; this._raw = result.data; this._firstRunId = runId; @@ -142,6 +144,9 @@ export abstract class PolicyCheck { * Returns the URL to this check run. */ get url(): string { + if (this.detailsUrl) { + return this.detailsUrl; + } return `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${this._firstRunId}/job/${this.raw?.id}`; } diff --git a/src/policies/undeclared-policy-check.ts b/src/policies/undeclared-policy-check.ts index 827ac289..949a4b26 100644 --- a/src/policies/undeclared-policy-check.ts +++ b/src/policies/undeclared-policy-check.ts @@ -24,11 +24,14 @@ import { PolicyCheck } from './policy-check'; import { CHECK_NAME } from '../app.config'; import * as core from '@actions/core'; -import { EXECUTABLE, SCANOSS_SETTINGS } from '../app.input'; +import { EXECUTABLE, SCANOSS_SETTINGS, SETTINGS_FILE_PATH } from '../app.input'; import * as exec from '@actions/exec'; import { UndeclaredArgumentBuilder } from './argument_builders/components/undeclared-argument-builder'; import { ArgumentBuilder } from './argument_builders/argument-builder'; import { isOverMaxCharacterLimitAPI } from '../services/github.service'; +import { context } from '@actions/github'; +import { isPullRequest, resolveRepoAndSha } from '../utils/github.utils'; +import * as fs from 'fs'; /** * Verifies that all components identified in scanner results are declared in the project's SBOM. @@ -90,6 +93,48 @@ export class UndeclaredPolicyCheck extends PolicyCheck { details = stdout; } + // Add scanoss.json file link and context based on file existence + details += `\n\n---\n\n`; + details += `**šŸ“ Quick Fix:**\n`; + + // Get the correct branch name for links + let branchName = context.ref.replace('refs/heads/', ''); + if (isPullRequest()) { + const pull = context.payload.pull_request; + if (pull?.head.ref) { + branchName = pull.head.ref; + } + } + + if (fs.existsSync(SETTINGS_FILE_PATH)) { + const { owner, repo } = resolveRepoAndSha(); + const settingsFileUrl = `https://github.com/${owner}/${repo}/edit/${branchName}/${SETTINGS_FILE_PATH}`; + + // Try to replace the existing JSON with merged version + const mergedJson = mergeWithExistingScanossJson(details); + if (mergedJson) { + // Replace the first JSON block with merged version, fenced for readability + details = details.replace(/{[\s\S]*?}/, `\`\`\`json\n${mergedJson}\n\`\`\``); + } + + details += `\n\nšŸ“ Quick Fix:\n`; + details += `[Edit ${SETTINGS_FILE_PATH} file](${settingsFileUrl}) and replace with the JSON snippet provided above to declare these components and resolve policy violations.`; + } else { + // Build JSON content from the details output that already contains the structure + let jsonContent = ''; + const jsonMatch = details.match(/{[\s\S]*?}/); + if (jsonMatch) { + jsonContent = jsonMatch[0]; + } + + const encodedJson = encodeURIComponent(jsonContent); + const { owner, repo } = resolveRepoAndSha(); + const createFileUrl = `https://github.com/${owner}/${repo}/new/${branchName}?filename=${SETTINGS_FILE_PATH}&value=${encodedJson}`; + details += `\n\nšŸ“ Quick Fix:\n`; + details += `${SETTINGS_FILE_PATH} doesn't exist. Create it in your repository root with the JSON snippet provided above to resolve policy violations.\n\n`; + details += `[Create ${SETTINGS_FILE_PATH} file](${createFileUrl})`; + } + const { id } = await this.uploadArtifact(details); core.debug(`Undeclared Artifact ID: ${id}`); if (id) details = await this.concatPolicyArtifactURLToPolicyCheck(details, id); @@ -115,3 +160,56 @@ export class UndeclaredPolicyCheck extends PolicyCheck { return UndeclaredPolicyCheck.policyName; } } + +/** + * Merges new undeclared components with existing scanoss.json file + */ +function mergeWithExistingScanossJson(policyDetails: string): string | null { + try { + // Extract new components from policy details + const jsonMatch = policyDetails.match(/{[\s\S]*?}/); + if (!jsonMatch) { + core.warning('Could not extract new components from policy details'); + return null; + } + + const newStructure = JSON.parse(jsonMatch[0]); + const newComponents = newStructure.bom?.include || []; + + if (newComponents.length === 0) { + core.warning('No new components found to add'); + return null; + } + + // Read existing settings file + const existingContent = fs.readFileSync(SETTINGS_FILE_PATH, 'utf8'); + const existingConfig = JSON.parse(existingContent); + + // Ensure bom section exists + if (!existingConfig.bom) { + existingConfig.bom = {}; + } + + // Ensure include array exists + if (!existingConfig.bom.include) { + existingConfig.bom.include = []; + } + + // Ensure include is an array + if (!Array.isArray(existingConfig.bom.include)) { + core.warning('Existing bom.include is not an array, creating new array'); + existingConfig.bom.include = []; + } + + // Add all new components (no duplicate checking needed) + existingConfig.bom.include.push(...newComponents); + + core.info(`Added ${newComponents.length} new components to existing ${SETTINGS_FILE_PATH} structure`); + + // Return formatted JSON + return JSON.stringify(existingConfig, null, 2); + } catch (error) { + core.warning(`Failed to merge with existing ${SETTINGS_FILE_PATH}: ${error}`); + return null; + } +} diff --git a/src/services/dependency-track-status.service.ts b/src/services/dependency-track-status.service.ts index d1ba540c..1dee7748 100644 --- a/src/services/dependency-track-status.service.ts +++ b/src/services/dependency-track-status.service.ts @@ -58,7 +58,7 @@ export class DependencyTrackStatusService { try { const octokit = getOctokit(inputs.GITHUB_TOKEN); // eslint-disable-next-line @typescript-eslint/await-thenable - const sha = await getSHA(); // TODO review with Groh + const sha = await getSHA(); // TODO Review let conclusion: 'success' | 'failure' | 'neutral'; let title: string; diff --git a/src/services/scan.service.ts b/src/services/scan.service.ts index be5985d1..2e7ec9c7 100644 --- a/src/services/scan.service.ts +++ b/src/services/scan.service.ts @@ -195,10 +195,11 @@ export class ScanService { const args = await this.buildArgs(); const { stdout, stderr, exitCode } = await exec.getExecOutput(EXECUTABLE, args, options); if (exitCode !== 0) { - core.warning(`Scan execution completed with exit code ${exitCode}`); + core.error(`Scan execution completed with exit code ${exitCode}`); if (stderr) { - core.debug(`Scan stderr: ${stderr}`); + core.error(`Scan stderr: ${stderr}`); } + throw new Error(`Scan execution failed with stderr: ${stderr}`); } const scan = await this.parseResult(); diff --git a/src/types/annotations.ts b/src/types/annotations.ts new file mode 100644 index 00000000..6c0c6abe --- /dev/null +++ b/src/types/annotations.ts @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT +/* + Copyright (c) 2025, SCANOSS + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/** + * License information from SCANOSS results + */ +export interface LicenseInfo { + name: string; + url?: string; + copyleft?: string; +} + +/** + * Interface representing a snippet match from SCANOSS results + */ +export interface SnippetMatch { + file: string; + component: string; + version?: string; + matched: string; + lines: string; + oss_lines: string; + url?: string; + purl?: string[]; + licenses?: LicenseInfo[]; +} + +/** + * Interface representing a full file match from SCANOSS results + */ +export interface FileMatch { + file: string; + component: string; + version?: string; + url?: string; + purl?: string[]; + licenses?: LicenseInfo[]; +} + +/** + * Interface for parsed line ranges + */ +export interface LineRange { + start: number; + end: number; +} + +/** + * Interface for repository and SHA information + */ +export interface RepoInfo { + owner: string; + repo: string; + sha: string; +} + +/** + * Interface for match with file path context + */ +export interface MatchWithPath { + filePath: string; + match: T; +} + +/** + * Type aliases for common match collections + */ +export type SnippetMatchWithPath = MatchWithPath; +export type FileMatchWithPath = MatchWithPath; diff --git a/src/types/errors.ts b/src/types/errors.ts new file mode 100644 index 00000000..2f5cac02 --- /dev/null +++ b/src/types/errors.ts @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: MIT +/* + Copyright (c) 2025, SCANOSS + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/** + * Base interface for all custom errors in the application + */ +export interface BaseError { + readonly name: string; + readonly message: string; + readonly code: string; + readonly context?: Record; +} + +/** + * Error codes for different types of application errors + */ +export enum ErrorCode { + // Configuration errors + INVALID_CONFIG = 'INVALID_CONFIG', + MISSING_REQUIRED_PARAM = 'MISSING_REQUIRED_PARAM', + + // API errors + GITHUB_API_ERROR = 'GITHUB_API_ERROR', + SCANOSS_API_ERROR = 'SCANOSS_API_ERROR', + DEPENDENCY_TRACK_ERROR = 'DEPENDENCY_TRACK_ERROR', + + // File system errors + FILE_NOT_FOUND = 'FILE_NOT_FOUND', + FILE_READ_ERROR = 'FILE_READ_ERROR', + FILE_WRITE_ERROR = 'FILE_WRITE_ERROR', + + // Processing errors + PARSING_ERROR = 'PARSING_ERROR', + VALIDATION_ERROR = 'VALIDATION_ERROR', + ANNOTATION_ERROR = 'ANNOTATION_ERROR', + + // Network errors + CONNECTION_ERROR = 'CONNECTION_ERROR', + TIMEOUT_ERROR = 'TIMEOUT_ERROR', + AUTHENTICATION_ERROR = 'AUTHENTICATION_ERROR' +} + +/** + * Custom error class for application-specific errors + */ +export class AppError extends Error implements BaseError { + readonly name: string = 'AppError'; + readonly code: string; + readonly context?: Record; + + constructor(message: string, code: ErrorCode, context?: Record) { + super(message); + this.code = code; + this.context = context; + Object.setPrototypeOf(this, AppError.prototype); + } + + /** + * Creates a formatted error message with context + */ + toFormattedMessage(): string { + let formatted = `${this.message}`; + + if (this.context) { + const contextStr = Object.entries(this.context) + .map(([key, value]) => `${key}: ${value}`) + .join(', '); + formatted += ` (${contextStr})`; + } + + return formatted; + } +} + +/** + * Configuration-related error + */ +export class ConfigError extends AppError { + readonly name: string = 'ConfigError'; + + constructor(message: string, context?: Record) { + super(message, ErrorCode.INVALID_CONFIG, context); + Object.setPrototypeOf(this, ConfigError.prototype); + } +} + +/** + * API-related error with additional HTTP context + */ +export class ApiError extends AppError { + readonly name: string = 'ApiError'; + readonly status?: number; + readonly url?: string; + + constructor(message: string, code: ErrorCode, status?: number, url?: string, context?: Record) { + super(message, code, { ...context, status, url }); + this.status = status; + this.url = url; + Object.setPrototypeOf(this, ApiError.prototype); + } +} + +/** + * File system operation error + */ +export class FileSystemError extends AppError { + readonly name: string = 'FileSystemError'; + readonly filePath: string; + + constructor(message: string, code: ErrorCode, filePath: string, context?: Record) { + super(message, code, { ...context, filePath }); + this.filePath = filePath; + Object.setPrototypeOf(this, FileSystemError.prototype); + } +} + +/** + * Error factory functions for common error scenarios + */ +export const ErrorFactory = { + /** + * Creates a configuration error + */ + configError: (message: string, context?: Record) => new ConfigError(message, context), + + /** + * Creates a GitHub API error + */ + githubApiError: (message: string, status?: number, url?: string, context?: Record) => + new ApiError(message, ErrorCode.GITHUB_API_ERROR, status, url, context), + + /** + * Creates a file not found error + */ + fileNotFoundError: (filePath: string, context?: Record) => + new FileSystemError(`File not found: ${filePath}`, ErrorCode.FILE_NOT_FOUND, filePath, context), + + /** + * Creates a parsing error + */ + parsingError: (message: string, context?: Record) => + new AppError(message, ErrorCode.PARSING_ERROR, context), + + /** + * Creates a validation error + */ + validationError: (message: string, context?: Record) => + new AppError(message, ErrorCode.VALIDATION_ERROR, context) +}; diff --git a/src/utils/annotation-creators.ts b/src/utils/annotation-creators.ts new file mode 100644 index 00000000..1ba87d62 --- /dev/null +++ b/src/utils/annotation-creators.ts @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +/* + Copyright (c) 2025, SCANOSS + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +import * as core from '@actions/core'; +import { resolveRepoAndSha } from './github.utils'; +import { SnippetMatch, LineRange, SnippetMatchWithPath, FileMatchWithPath } from '../types/annotations'; +import { parseLineRange } from './line-parsers'; + +/** + * Creates a GitHub URL for the file + * @param filePath - The file path relative to repository root + * @returns GitHub URL for the file + */ +function getFileUrl(filePath: string): string { + const { owner, repo, sha } = resolveRepoAndSha(); + return `https://github.com/${owner}/${repo}/blob/${sha}/${filePath}`; +} + +/** + * Creates a GitHub URL for the file with line highlighting + * @param filePath - The file path relative to repository root + * @param lineRange - The line range to highlight + * @returns GitHub URL for the file with line anchors + */ +function getFileUrlWithLineHighlight(filePath: string, lineRange: LineRange): string { + const { owner, repo, sha } = resolveRepoAndSha(); + const baseUrl = `https://github.com/${owner}/${repo}/blob/${sha}/${filePath}`; + + if (lineRange.start === lineRange.end) { + return `${baseUrl}#L${lineRange.start}`; + } else { + return `${baseUrl}#L${lineRange.start}-L${lineRange.end}`; + } +} + +/** + * Creates a summary annotation for snippet matches (not bound to any file) + * @param snippetMatches - Array of snippet matches with file paths + */ +export function createSnippetSummaryAnnotation(snippetMatches: SnippetMatchWithPath[]): void { + const { owner, repo, sha } = resolveRepoAndSha(); + const commitUrl = `https://github.com/${owner}/${repo}/commit/${sha}`; + + let message = `Found ${snippetMatches.length} snippet matches\n`; + message += `šŸ“ [View detailed comments on commit](${commitUrl})\n\n`; + message += `**Affected Files:**\n`; + + // Group matches by file and limit to avoid annotation length issues + const fileGroups = snippetMatches.reduce( + (groups, { filePath, match }) => { + if (!groups[filePath]) groups[filePath] = []; + groups[filePath].push(match); + return groups; + }, + {} as Record + ); + + const fileEntries = Object.entries(fileGroups).slice(0, 10); // Limit to 10 files + for (const [filePath, matches] of fileEntries) { + const firstMatch = matches[0]; + const localLines = parseLineRange(firstMatch.lines); + const fileUrl = localLines ? getFileUrlWithLineHighlight(filePath, localLines) : getFileUrl(filePath); + message += `- [${filePath}](${fileUrl}) (${matches.length} match${matches.length > 1 ? 'es' : ''})\n`; + } + + if (Object.keys(fileGroups).length > 10) { + message += `- ... and ${Object.keys(fileGroups).length - 10} more files\n`; + } + + core.notice(message, { + title: 'Code Snippet Matches Summary' + }); + + core.info(`Created snippet summary annotation for ${snippetMatches.length} matches`); +} + +/** + * Creates a summary annotation for file matches (not bound to any file) + * @param fileMatches - Array of file matches with file paths + */ +export function createFileMatchSummaryAnnotation(fileMatches: FileMatchWithPath[]): void { + const { owner, repo, sha } = resolveRepoAndSha(); + const commitUrl = `https://github.com/${owner}/${repo}/commit/${sha}`; + + let message = `Found ${fileMatches.length} full file matches\n`; + message += `šŸ“ [View detailed comments on commit](${commitUrl})\n\n`; + message += `**Affected Files:**\n`; + + // Limit to avoid annotation length issues + const limitedMatches = fileMatches.slice(0, 10); + for (const { filePath, match } of limitedMatches) { + const fileUrl = getFileUrl(filePath); + const component = `${match.component}${match.version ? ` v${match.version}` : ''}`; + message += `- [${filePath}](${fileUrl}) → ${component}\n`; + } + + if (fileMatches.length > 10) { + message += `- ... and ${fileMatches.length - 10} more files\n`; + } + + core.notice(message, { + title: 'Full File Matches Summary' + }); + + core.info(`Created file match summary annotation for ${fileMatches.length} matches`); +} diff --git a/src/utils/api-cache.ts b/src/utils/api-cache.ts new file mode 100644 index 00000000..7522cdbe --- /dev/null +++ b/src/utils/api-cache.ts @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: MIT +/* + Copyright (c) 2025, SCANOSS + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/** + * Simple in-memory cache for API responses to avoid duplicate requests + * + * This cache is designed for single GitHub Actions runs and automatically + * expires entries after a reasonable time to avoid stale data. + */ +class ApiCache { + private cache = new Map(); + private readonly defaultTtl = 300000; // 5 minutes in milliseconds + + /** + * Gets a cached value if it exists and hasn't expired + * @param key - The cache key + * @param ttl - Time to live in milliseconds (optional, defaults to 5 minutes) + * @returns The cached value or null if not found/expired + */ + get(key: string, ttl: number = this.defaultTtl): T | null { + const entry = this.cache.get(key); + + if (!entry) { + return null; + } + + const isExpired = Date.now() - entry.timestamp > ttl; + + if (isExpired) { + this.cache.delete(key); + return null; + } + + return entry.data as T; + } + + /** + * Sets a value in the cache + * @param key - The cache key + * @param value - The value to cache + */ + set(key: string, value: T): void { + this.cache.set(key, { + data: value, + timestamp: Date.now() + }); + } + + /** + * Clears all cached entries + */ + clear(): void { + this.cache.clear(); + } + + /** + * Gets the current cache size + * @returns Number of cached entries + */ + size(): number { + return this.cache.size; + } + + /** + * Generates a cache key from multiple parameters + * @param params - Parameters to include in the key + * @returns A consistent cache key string + */ + static generateKey(...params: (string | number | boolean | undefined)[]): string { + return params + .filter(p => p !== undefined) + .map(p => String(p)) + .join(':'); + } +} + +/** + * Global cache instance for API responses + */ +export const apiCache = new ApiCache(); + +/** + * Request deduplication utility to prevent multiple identical API calls + */ +class RequestDeduplicator { + private pending = new Map>(); + + /** + * Executes a function only once per key, returning the same promise for duplicate calls + * @param key - Unique key for the request + * @param fn - Function to execute + * @returns Promise that resolves to the function result + */ + async deduplicate(key: string, fn: () => Promise): Promise { + // Check if request is already pending + if (this.pending.has(key)) { + return this.pending.get(key) as Promise; + } + + // Execute the function and cache the promise + const promise = fn().finally(() => { + // Remove from pending when complete + this.pending.delete(key); + }); + + this.pending.set(key, promise); + return promise; + } + + /** + * Gets the number of pending requests + * @returns Number of pending requests + */ + pendingCount(): number { + return this.pending.size; + } + + /** + * Clears all pending requests (use with caution) + */ + clear(): void { + this.pending.clear(); + } +} + +/** + * Global request deduplicator instance + */ +export const requestDeduplicator = new RequestDeduplicator(); diff --git a/src/utils/github-comment-api.ts b/src/utils/github-comment-api.ts new file mode 100644 index 00000000..ed9dcc47 --- /dev/null +++ b/src/utils/github-comment-api.ts @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: MIT +/* + Copyright (c) 2025, SCANOSS + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +import * as core from '@actions/core'; +import { context, getOctokit } from '@actions/github'; +import * as inputs from '../app.input'; +import { isPullRequest, resolveRepoAndSha } from './github.utils'; +import { SnippetMatch, FileMatch, LineRange, SnippetMatchWithPath, FileMatchWithPath } from '../types/annotations'; +import { parseLineRange } from './line-parsers'; +import { requestDeduplicator } from './api-cache'; + +/** + * Creates a GitHub URL for the file + * @param filePath - The file path relative to repository root + * @returns GitHub URL for the file + */ +function getFileUrl(filePath: string): string { + const { owner, repo, sha } = resolveRepoAndSha(); + return `https://github.com/${owner}/${repo}/blob/${sha}/${filePath}`; +} + +/** + * Creates a GitHub URL with line highlighting for the file + */ +function getFileUrlWithLineHighlight(filePath: string, lineRange: LineRange): string { + const { owner, repo, sha } = resolveRepoAndSha(); + const baseUrl = `https://github.com/${owner}/${repo}/blob/${sha}/${filePath}`; + + if (lineRange.start === lineRange.end) { + return `${baseUrl}#L${lineRange.start}`; + } else { + return `${baseUrl}#L${lineRange.start}-L${lineRange.end}`; + } +} + +function formatSnippetAnnotationMessage(filePath: string, snippet: SnippetMatch, localLines: LineRange): string { + let message = `Code snippet matches ${snippet.component}`; + + if (snippet.version) { + message += ` v${snippet.version}`; + } + + message += ` (${snippet.matched}% similarity)`; + + // Add license information + if (snippet.licenses && snippet.licenses.length > 0) { + const license = snippet.licenses[0]; + message += `\nLicense: ${license.name}`; + } + + // Add source URL + if (snippet.url) { + message += `\nSource: ${snippet.url}`; + } + + // Add OSS line range + if (snippet.oss_lines) { + message += `\nOSS Lines: ${snippet.oss_lines}`; + } + + // Add view link + const viewUrl = snippet.lines === 'all' ? getFileUrl(filePath) : getFileUrlWithLineHighlight(filePath, localLines); + message += `\nView: ${viewUrl}`; + + return message; +} + +/** + * Formats the file match information into an annotation message + * @param filePath - The file path + * @param fileMatch - The file match data + * @returns Formatted annotation message + */ +function formatFileAnnotationMessage(filePath: string, fileMatch: FileMatch): string { + const fileUrl = getFileUrl(filePath); + const component = `${fileMatch.component}${fileMatch.version ? ` v${fileMatch.version}` : ''}`; + + let message = `**Full file match detected in [${filePath}](${fileUrl})**\n\n`; + message += `- **Component**: ${component}\n`; + + if (fileMatch.licenses && fileMatch.licenses.length > 0) { + const license = fileMatch.licenses[0]; + message += `- **License**: ${license.name}\n`; + } + + if (fileMatch.url) { + message += `- **Source**: [${fileMatch.url}](${fileMatch.url})\n`; + } + + return message; +} + +/** + * Creates a commit comment for a snippet match with detailed similarity information + * + * This function processes SCANOSS snippet match results and creates GitHub commit comments + * that provide developers with actionable information about code similarities found in their commits. + * + * @param filePath - The file path relative to repository root where the match was found + * @param snippetMatch - The snippet match data from SCANOSS containing similarity details + * + * @example + * ```typescript + * await createSnippetCommitComment('src/utils/parser.ts', { + * file: 'parser.ts', + * component: 'example-lib', + * version: '1.2.3', + * matched: '85', + * lines: '15-25', + * oss_lines: '10-20', + * licenses: [{ name: 'MIT' }] + * }); + * ``` + * + * @returns Promise - true on success, false on failure or skip + */ +export async function createSnippetCommitComment(filePath: string, snippetMatch: SnippetMatch): Promise { + const localLines = parseLineRange(snippetMatch.lines); + + if (!localLines) { + core.warning(`Could not parse line range: ${snippetMatch.lines} for file: ${filePath}`); + return false; + } + + const message = formatSnippetAnnotationMessage(filePath, snippetMatch, localLines); + const commentBody = `šŸ” **Code Similarity Found**\n\n${message}`; + + try { + const octokit = getOctokit(inputs.GITHUB_TOKEN); + + const params = { + owner: context.repo.owner, + repo: context.repo.repo, + commit_sha: context.sha, + path: filePath, + line: localLines.start, + body: commentBody + }; + + core.info(`Creating commit comment for snippet match at ${filePath}`); + + // Use request deduplication to prevent duplicate comments for the same file + const deduplicationKey = `snippet-comment:${context.sha}:${filePath}:${snippetMatch.component}`; + await requestDeduplicator.deduplicate(deduplicationKey, async () => { + return await octokit.rest.repos.createCommitComment(params); + }); + + core.info(`Successfully created commit comment for snippet match at ${filePath}`); + return true; + } catch (error) { + core.error(`Failed to create commit comment for ${filePath}`); + if (error instanceof Error) { + core.error(`Error: ${error.message}`); + // Extra diagnostics (status, url) if present + const status = (error as any)?.status; + const url = (error as any)?.request?.url; + if (status || url) core.error(`Context: status=${status ?? 'n/a'} url=${url ?? 'n/a'}`); + core.debug(`Error details: ${JSON.stringify(error, null, 2)}`); + } + return false; + } +} + +/** + * Creates a commit comment for a file match + * @param filePath - The file path + * @param fileMatch - The file match data + * @returns Promise - true on success, false on failure + */ +export async function createFileCommitComment(filePath: string, fileMatch: FileMatch): Promise { + const message = formatFileAnnotationMessage(filePath, fileMatch); + const commentBody = `šŸ“„ **Full File Match Found**\n\n${message}`; + + try { + const octokit = getOctokit(inputs.GITHUB_TOKEN); + + const params = { + owner: context.repo.owner, + repo: context.repo.repo, + commit_sha: context.sha, + path: filePath, + body: commentBody + }; + + core.info(`Creating file commit comment for ${filePath}`); + + // Use request deduplication to prevent duplicate comments for the same file + const deduplicationKey = `file-comment:${context.sha}:${filePath}:${fileMatch.component}${fileMatch.version ? `:v${fileMatch.version}` : ''}`; + await requestDeduplicator.deduplicate(deduplicationKey, async () => { + return await octokit.rest.repos.createCommitComment(params); + }); + + core.info(`Successfully created commit comment for file match at ${filePath}`); + return true; + } catch (error) { + core.error(`Failed to create commit comment for ${filePath}`); + if (error instanceof Error) { + core.error(`Error: ${error.message}`); + // Extra diagnostics (status, url) if present + const status = (error as any)?.status; + const url = (error as any)?.request?.url; + if (status || url) core.error(`Context: status=${status ?? 'n/a'} url=${url ?? 'n/a'}`); + core.debug(`Error details: ${JSON.stringify(error, null, 2)}`); + } + return false; + } +} + +/** + * Creates a main conversation comment with summary and commit link + * @param snippetMatches - Array of snippet matches with file paths + * @param fileMatches - Array of file matches with file paths + */ +export async function createMainConversationComment( + snippetMatches: SnippetMatchWithPath[], + fileMatches: FileMatchWithPath[] +): Promise { + if (!isPullRequest()) { + core.info('Skipping main conversation comment - not in PR context'); + return; + } + + const { owner, repo, sha } = resolveRepoAndSha(); + const commitUrl = `https://github.com/${owner}/${repo}/commit/${sha}`; + + let message = `## šŸ” SCANOSS Code Similarity Detected\n\n`; + + if (snippetMatches.length > 0) { + message += `šŸ“„ **${snippetMatches.length} snippet matches** found\n`; + } + + if (fileMatches.length > 0) { + message += `šŸ“‹ **${fileMatches.length} full file matches** found\n`; + } + + message += `\nšŸ”— **[View detailed findings on commit ${context.sha.substring(0, 7)}](${commitUrl})**\n\n`; + + // Quick overview of most affected files + const allFiles = new Set([...snippetMatches.map(m => m.filePath), ...fileMatches.map(m => m.filePath)]); + + if (allFiles.size <= 5) { + message += `**Files with similarities:**\n`; + for (const filePath of Array.from(allFiles).slice(0, 5)) { + message += `- \`${filePath}\`\n`; + } + } else { + message += `**${allFiles.size} files** contain code similarities\n`; + } + + message += `\nšŸ’” Click the commit link above to see detailed annotations for each match.`; + + try { + const octokit = getOctokit(inputs.GITHUB_TOKEN); + + await octokit.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: message + }); + + core.info(`Successfully created main conversation comment (PR #${context.issue.number})`); + } catch (error) { + core.error(`Failed to create main conversation comment: ${error}`); + } +} diff --git a/src/utils/github.utils.ts b/src/utils/github.utils.ts index 34776ad5..9101134c 100644 --- a/src/utils/github.utils.ts +++ b/src/utils/github.utils.ts @@ -55,6 +55,31 @@ export function getSHA(): string { return sha; } +/** + * Resolves the correct repository owner, repo name, and SHA for fork-safe URL generation. + * For pull requests from forks, this ensures URLs point to the correct repository and commit. + */ +export function resolveRepoAndSha(): { owner: string; repo: string; sha: string } { + if (isPullRequest()) { + const pull = context.payload.pull_request; + if (pull?.head) { + // For PRs, use the head repository and SHA to ensure we point to the correct branch/fork + return { + owner: pull.head.repo?.owner.login || context.repo.owner, + repo: pull.head.repo?.name || context.repo.repo, + sha: pull.head.sha || context.sha + }; + } + } + + // Default to context values for non-PR scenarios + return { + owner: context.repo.owner, + repo: context.repo.repo, + sha: context.sha + }; +} + /** * Creates a comment on the current pull request with the provided message. */ diff --git a/src/utils/line-parsers.ts b/src/utils/line-parsers.ts new file mode 100644 index 00000000..cb063f47 --- /dev/null +++ b/src/utils/line-parsers.ts @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +/* + Copyright (c) 2025, SCANOSS + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +import { LineRange } from '../types/annotations'; + +/** + * Sanitizes line range string by removing non-numeric, non-comma, non-dash characters + * @param str - The string to sanitize + * @returns Sanitized string containing only digits, commas, and dashes + */ +function sanitizeLineRange(str: string): string { + return str.replace(/[^0-9,-]/g, ''); +} + +/** + * Extracts first and last numbers from a string using regex + * @param str - The string to extract numbers from + * @returns Object with first and last numbers as strings, or null if not found + */ +function extractFirstAndLastNumbers(str: string): { first: string; last: string } | null { + // Sanitize input to handle formats like "L7-L9, L47-L81" + const sanitized = sanitizeLineRange(str); + const match = sanitized.match(/^(\d+).*?(\d+)(?!.*\d)/); + if (match) { + return { + first: match[1], + last: match[2] + }; + } + return null; +} + +/** + * Parses various line range string formats into a standardized LineRange object + * + * This function handles multiple line range formats commonly used in code analysis tools: + * - Simple ranges: "4-142" + * - Complex ranges: "7-9,47-81,99-158" (uses first and last numbers) + * - Single lines: "25" + * - Special values: "all" (defaults to line 1) + * - Prefixed formats: "L7-L9" (strips non-numeric characters) + * + * The function is designed to be robust and handle malformed input gracefully by + * sanitizing the input and falling back to reasonable defaults. + * + * @param lineRange - The line range string to parse (various formats supported) + * @returns Parsed line range object with start and end properties, or null if parsing fails + * + * @example + * ```typescript + * parseLineRange("15-25") // { start: 15, end: 25 } + * parseLineRange("L7-L9,L47-L81") // { start: 7, end: 81 } + * parseLineRange("42") // { start: 42, end: 42 } + * parseLineRange("all") // { start: 1, end: 1 } + * parseLineRange("invalid") // null + * ``` + */ +export function parseLineRange(lineRange: string): LineRange | null { + if (lineRange === 'all') { + return { start: 1, end: 1 }; + } + + // Handle complex ranges like "7-9,47-81,99-158" using regex to get first and last numbers + const extracted = extractFirstAndLastNumbers(lineRange); + if (extracted) { + const start = parseInt(extracted.first, 10); + const end = parseInt(extracted.last, 10); + + if (!isNaN(start) && !isNaN(end)) { + return { start, end }; + } + } + + // Fallback for single number (sanitize first) + const sanitized = sanitizeLineRange(lineRange); + const singleLine = parseInt(sanitized, 10); + if (!isNaN(singleLine)) { + return { start: singleLine, end: singleLine }; + } + + return null; +} diff --git a/src/utils/snippet-annotations.utils.ts b/src/utils/snippet-annotations.utils.ts new file mode 100644 index 00000000..94d90a6c --- /dev/null +++ b/src/utils/snippet-annotations.utils.ts @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +/* + Copyright (c) 2024, SCANOSS + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import * as core from '@actions/core'; +import * as fs from 'fs'; +import { context } from '@actions/github'; +import { SnippetMatchWithPath, FileMatchWithPath, SnippetMatch, FileMatch } from '../types/annotations'; +import { createSnippetSummaryAnnotation, createFileMatchSummaryAnnotation } from './annotation-creators'; +import { + createSnippetCommitComment, + createFileCommitComment, + createMainConversationComment +} from './github-comment-api'; + +/** + * Creates hybrid snippet annotations: summary annotations + commit comments + */ +export async function createSnippetAnnotations(resultsPath: string): Promise { + if (!fs.existsSync(resultsPath)) { + core.warning(`Results file not found: ${resultsPath}`); + return; + } + + try { + const resultsContent = fs.readFileSync(resultsPath, 'utf8'); + const results = JSON.parse(resultsContent); + + const snippetMatches: SnippetMatchWithPath[] = []; + const fileMatches: FileMatchWithPath[] = []; + + // Collect all matches + for (const [filePath, matches] of Object.entries(results)) { + if (!Array.isArray(matches)) continue; + + for (const match of matches) { + if (match.status === 'pending') { + if (match.id === 'snippet') { + snippetMatches.push({ filePath, match: match as SnippetMatch }); + } else if (match.id === 'file') { + fileMatches.push({ filePath, match: match as FileMatch }); + } + } + } + } + + // Create summary annotations (not bound to files) + if (snippetMatches.length > 0) { + createSnippetSummaryAnnotation(snippetMatches); + } + + if (fileMatches.length > 0) { + createFileMatchSummaryAnnotation(fileMatches); + } + + // Log GitHub context for debugging + core.info(`GitHub context: owner=${context.repo.owner}, repo=${context.repo.repo}, sha=${context.sha}`); + + // Create individual commit comments for each match (in parallel) + const snippetPromises = snippetMatches.map(async ({ filePath, match }) => + createSnippetCommitComment(filePath, match) + ); + const filePromises = fileMatches.map(async ({ filePath, match }) => createFileCommitComment(filePath, match)); + + const commentResults = await Promise.all([...snippetPromises, ...filePromises]); + const successCount = commentResults.filter(Boolean).length; + const failedCount = commentResults.length - successCount; + + if (failedCount > 0) { + core.warning(`${failedCount} commit comments failed to create, ${successCount} succeeded`); + } + + // Create main conversation comment if we have any matches + if (snippetMatches.length > 0 || fileMatches.length > 0) { + await createMainConversationComment(snippetMatches, fileMatches); + } + + core.info( + `Created summary annotations, attempted ${snippetMatches.length + fileMatches.length} commit comments, and main conversation comment` + ); + } catch (error) { + core.error(`Failed to create snippet annotations from ${resultsPath}: ${error}`); + } +} diff --git a/tsconfig.json b/tsconfig.json index 87d3047d..59c4087b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, - "newLine": "lf", + "newLine": "lf" }, "rules": { "@typescript-eslint/no-explicit-any": "off"