diff --git a/lib/main.js b/lib/main.js index 39de2747b..f1619fe70 100644 --- a/lib/main.js +++ b/lib/main.js @@ -78,8 +78,8 @@ var require_tunnel = __commonJS({ self2.maxSockets = self2.options.maxSockets || http.Agent.defaultMaxSockets; self2.requests = []; self2.sockets = []; - self2.on("free", function onFree(socket, host, port, localAddress) { - var options2 = toOptions(host, port, localAddress); + self2.on("free", function onFree(socket, host2, port, localAddress) { + var options2 = toOptions(host2, port, localAddress); for (var i = 0, len = self2.requests.length; i < len; ++i) { var pending = self2.requests[i]; if (pending.host === options2.host && pending.port === options2.port) { @@ -93,9 +93,9 @@ var require_tunnel = __commonJS({ }); } util.inherits(TunnelingAgent, events.EventEmitter); - TunnelingAgent.prototype.addRequest = function addRequest(req, host, port, localAddress) { + TunnelingAgent.prototype.addRequest = function addRequest(req, host2, port, localAddress) { var self2 = this; - var options = mergeOptions({ request: req }, self2.options, toOptions(host, port, localAddress)); + var options = mergeOptions({ request: req }, self2.options, toOptions(host2, port, localAddress)); if (self2.sockets.length >= this.maxSockets) { self2.requests.push(options); return; @@ -218,15 +218,15 @@ var require_tunnel = __commonJS({ cb(secureSocket); }); } - function toOptions(host, port, localAddress) { - if (typeof host === "string") { + function toOptions(host2, port, localAddress) { + if (typeof host2 === "string") { return { - host, + host: host2, port, localAddress }; } - return host; + return host2; } function mergeOptions(target) { for (var i = 1, len = arguments.length; i < len; ++i) { @@ -1092,22 +1092,22 @@ var require_util = __commonJS({ } return url; } - function getHostname(host) { - if (host[0] === "[") { - const idx2 = host.indexOf("]"); + function getHostname(host2) { + if (host2[0] === "[") { + const idx2 = host2.indexOf("]"); assert3(idx2 !== -1); - return host.substring(1, idx2); + return host2.substring(1, idx2); } - const idx = host.indexOf(":"); - if (idx === -1) return host; - return host.substring(0, idx); + const idx = host2.indexOf(":"); + if (idx === -1) return host2; + return host2.substring(0, idx); } - function getServerName(host) { - if (!host) { + function getServerName(host2) { + if (!host2) { return null; } - assert3(typeof host === "string"); - const servername = getHostname(host); + assert3(typeof host2 === "string"); + const servername = getHostname(host2); if (net.isIP(servername)) { return ""; } @@ -1494,34 +1494,34 @@ var require_diagnostics = __commonJS({ const debuglog = fetchDebuglog.enabled ? fetchDebuglog : undiciDebugLog; diagnosticsChannel.channel("undici:client:beforeConnect").subscribe((evt) => { const { - connectParams: { version, protocol, port, host } + connectParams: { version, protocol, port, host: host2 } } = evt; debuglog( "connecting to %s using %s%s", - `${host}${port ? `:${port}` : ""}`, + `${host2}${port ? `:${port}` : ""}`, protocol, version ); }); diagnosticsChannel.channel("undici:client:connected").subscribe((evt) => { const { - connectParams: { version, protocol, port, host } + connectParams: { version, protocol, port, host: host2 } } = evt; debuglog( "connected to %s using %s%s", - `${host}${port ? `:${port}` : ""}`, + `${host2}${port ? `:${port}` : ""}`, protocol, version ); }); diagnosticsChannel.channel("undici:client:connectError").subscribe((evt) => { const { - connectParams: { version, protocol, port, host }, + connectParams: { version, protocol, port, host: host2 }, error: error2 } = evt; debuglog( "connection to %s using %s%s errored - %s", - `${host}${port ? `:${port}` : ""}`, + `${host2}${port ? `:${port}` : ""}`, protocol, version, error2.message @@ -1572,11 +1572,11 @@ var require_diagnostics = __commonJS({ const debuglog = undiciDebugLog.enabled ? undiciDebugLog : websocketDebuglog; diagnosticsChannel.channel("undici:client:beforeConnect").subscribe((evt) => { const { - connectParams: { version, protocol, port, host } + connectParams: { version, protocol, port, host: host2 } } = evt; debuglog( "connecting to %s%s using %s%s", - host, + host2, port ? `:${port}` : "", protocol, version @@ -1584,11 +1584,11 @@ var require_diagnostics = __commonJS({ }); diagnosticsChannel.channel("undici:client:connected").subscribe((evt) => { const { - connectParams: { version, protocol, port, host } + connectParams: { version, protocol, port, host: host2 } } = evt; debuglog( "connected to %s%s using %s%s", - host, + host2, port ? `:${port}` : "", protocol, version @@ -1596,12 +1596,12 @@ var require_diagnostics = __commonJS({ }); diagnosticsChannel.channel("undici:client:connectError").subscribe((evt) => { const { - connectParams: { version, protocol, port, host }, + connectParams: { version, protocol, port, host: host2 }, error: error2 } = evt; debuglog( "connection to %s%s using %s%s errored - %s", - host, + host2, port ? `:${port}` : "", protocol, version, @@ -2502,13 +2502,13 @@ var require_connect = __commonJS({ const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions); timeout = timeout == null ? 1e4 : timeout; allowH2 = allowH2 != null ? allowH2 : false; - return function connect({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) { + return function connect({ hostname, host: host2, protocol, port, servername, localAddress, httpSocket }, callback) { let socket; if (protocol === "https:") { if (!tls) { tls = __require("node:tls"); } - servername = servername || options.servername || util.getServerName(host) || null; + servername = servername || options.servername || util.getServerName(host2) || null; const sessionKey = servername || hostname; assert3(sessionKey); const session = customSession || sessionCache.get(sessionKey) || null; @@ -6286,7 +6286,7 @@ var require_client_h1 = __commonJS({ return method !== "GET" && method !== "HEAD" && method !== "OPTIONS" && method !== "TRACE" && method !== "CONNECT"; } function writeH1(client, request2) { - const { method, path: path6, host, upgrade, blocking, reset } = request2; + const { method, path: path6, host: host2, upgrade, blocking, reset } = request2; let { body, headers, contentLength } = request2; const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH" || method === "QUERY" || method === "PROPFIND" || method === "PROPPATCH"; if (util.isFormDataLike(body)) { @@ -6354,8 +6354,8 @@ var require_client_h1 = __commonJS({ } let header = `${method} ${path6} HTTP/1.1\r `; - if (typeof host === "string") { - header += `host: ${host}\r + if (typeof host2 === "string") { + header += `host: ${host2}\r `; } else { header += client[kHostHeader]; @@ -6878,7 +6878,7 @@ var require_client_h2 = __commonJS({ } function writeH2(client, request2) { const session = client[kHTTP2Session]; - const { method, path: path6, host, upgrade, expectContinue, signal, headers: reqHeaders } = request2; + const { method, path: path6, host: host2, upgrade, expectContinue, signal, headers: reqHeaders } = request2; let { body } = request2; if (upgrade) { util.errorRequest(client, request2, new Error("Upgrade not supported for H2")); @@ -6902,7 +6902,7 @@ var require_client_h2 = __commonJS({ } let stream; const { hostname, port } = client[kUrl]; - headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ""}`; + headers[HTTP2_HEADER_AUTHORITY] = host2 || `${hostname}${port ? `:${port}` : ""}`; headers[HTTP2_HEADER_METHOD] = method; const abort = (err) => { if (request2.aborted || request2.completed) { @@ -7711,7 +7711,7 @@ var require_client = __commonJS({ async function connect(client) { assert3(!client[kConnecting]); assert3(!client[kHTTPContext]); - let { host, hostname, protocol, port } = client[kUrl]; + let { host: host2, hostname, protocol, port } = client[kUrl]; if (hostname[0] === "[") { const idx = hostname.indexOf("]"); assert3(idx !== -1); @@ -7723,7 +7723,7 @@ var require_client = __commonJS({ if (channels.beforeConnect.hasSubscribers) { channels.beforeConnect.publish({ connectParams: { - host, + host: host2, hostname, protocol, port, @@ -7737,7 +7737,7 @@ var require_client = __commonJS({ try { const socket = await new Promise((resolve2, reject) => { client[kConnector]({ - host, + host: host2, hostname, protocol, port, @@ -7770,7 +7770,7 @@ var require_client = __commonJS({ if (channels.connected.hasSubscribers) { channels.connected.publish({ connectParams: { - host, + host: host2, hostname, protocol, port, @@ -7791,7 +7791,7 @@ var require_client = __commonJS({ if (channels.connectError.hasSubscribers) { channels.connectError.publish({ connectParams: { - host, + host: host2, hostname, protocol, port, @@ -8540,8 +8540,8 @@ var require_proxy_agent = __commonJS({ } = opts; opts.path = origin + path6; if (!("host" in headers) && !("Host" in headers)) { - const { host } = new URL2(origin); - headers.host = host; + const { host: host2 } = new URL2(origin); + headers.host = host2; } opts.headers = { ...this[kProxyHeaders], ...headers }; return this.#client[kDispatch](opts, handler2); @@ -8645,8 +8645,8 @@ var require_proxy_agent = __commonJS({ const headers = buildHeaders(opts.headers); throwIfProxyAuthIsSent(headers); if (headers && !("host" in headers) && !("Host" in headers)) { - const { host } = new URL2(opts.origin); - headers.host = host; + const { host: host2 } = new URL2(opts.origin); + headers.host = host2; } return this[kAgent].dispatch( { @@ -21464,7 +21464,7 @@ var require_compare_func = __commonJS({ "use strict"; var arrayify = require_array_ify(); var dotPropGet = require_dot_prop().get; - function compareFunc2(prop) { + function compareFunc3(prop) { return function(a, b) { var ret = 0; arrayify(prop).some(function(el) { @@ -21494,7 +21494,7 @@ var require_compare_func = __commonJS({ return ret; }; } - module2.exports = compareFunc2; + module2.exports = compareFunc3; } }); @@ -35897,8 +35897,8 @@ var require_hosts = __commonJS({ return { user, project, committish: url.hash.slice(1) }; } }; - for (const [name, host] of Object.entries(hosts)) { - hosts[name] = Object.assign({}, defaults2, host); + for (const [name, host2] of Object.entries(hosts)) { + hosts[name] = Object.assign({}, defaults2, host2); } module2.exports = hosts; } @@ -36081,9 +36081,9 @@ var require_lib4 = __commonJS({ "https:": { auth: true }, "git+http:": { auth: true } }; - static addHost(name, host) { - _GitHost.#gitHosts[name] = host; - _GitHost.#gitHosts.byDomain[host.domain] = name; + static addHost(name, host2) { + _GitHost.#gitHosts[name] = host2; + _GitHost.#gitHosts.byDomain[host2.domain] = name; _GitHost.#gitHosts.byShortcut[`${name}:`] = name; _GitHost.#protocols[`${name}:`] = { name }; } @@ -36187,8 +36187,8 @@ var require_lib4 = __commonJS({ return this.sshurl(opts); } }; - for (const [name, host] of Object.entries(hosts)) { - GitHost.addHost(name, host); + for (const [name, host2] of Object.entries(hosts)) { + GitHost.addHost(name, host2); } module2.exports = GitHost; } @@ -36863,8 +36863,8 @@ var require_proxy = __commonJS({ } return false; } - function isLoopbackAddress(host) { - const hostLower = host.toLowerCase(); + function isLoopbackAddress(host2) { + const hostLower = host2.toLowerCase(); return hostLower === "localhost" || hostLower.startsWith("127.") || hostLower.startsWith("[::1]") || hostLower.startsWith("[0:0:0:0:0:0:0:1]"); } var DecodedURL = class extends URL { @@ -40214,20 +40214,20 @@ var CommitParser = class { if (!matches) { return null; } - let [raw, repository = null, prefix, issue2] = matches; - let owner = null; - if (repository) { - const slashIndex = repository.indexOf("/"); + let [raw, repository2 = null, prefix, issue2] = matches; + let owner2 = null; + if (repository2) { + const slashIndex = repository2.indexOf("/"); if (slashIndex !== -1) { - owner = repository.slice(0, slashIndex); - repository = repository.slice(slashIndex + 1); + owner2 = repository2.slice(0, slashIndex); + repository2 = repository2.slice(slashIndex + 1); } } return { raw, action, - owner, - repository, + owner: owner2, + repository: repository2, prefix, issue: issue2 }; @@ -42840,10 +42840,10 @@ function loadTemplates(options = {}) { }; } function compileTemplates(templates) { - const { mainTemplate: mainTemplate3, headerPartial: headerPartial3, commitPartial: commitPartial3, footerPartial: footerPartial3, partials } = templates; - import_handlebars.default.registerPartial("header", headerPartial3); - import_handlebars.default.registerPartial("commit", commitPartial3); - import_handlebars.default.registerPartial("footer", footerPartial3); + const { mainTemplate: mainTemplate4, headerPartial: headerPartial4, commitPartial: commitPartial4, footerPartial: footerPartial4, partials } = templates; + import_handlebars.default.registerPartial("header", headerPartial4); + import_handlebars.default.registerPartial("commit", commitPartial4); + import_handlebars.default.registerPartial("footer", footerPartial4); if (partials) { Object.entries(partials).forEach(([name, partial]) => { if (typeof partial === "string") { @@ -42851,7 +42851,7 @@ function compileTemplates(templates) { } }); } - return import_handlebars.default.compile(mainTemplate3, { + return import_handlebars.default.compile(mainTemplate4, { noEscape: true }); } @@ -43237,13 +43237,13 @@ async function generateNotes(pluginConfig, context3) { const { commits, lastRelease, nextRelease, options, cwd } = context3; const repositoryUrl = options.repositoryUrl.replace(/\.git$/i, ""); const { commitOpts, parserOpts, writerOpts } = await load_changelog_config_default(pluginConfig, context3); - const [match, auth2, host, path6] = /^(?!.+:\/\/)(?:(?.*)@)?(?.*?):(?.*)$/.exec(repositoryUrl) || []; + const [match, auth2, host2, path6] = /^(?!.+:\/\/)(?:(?.*)@)?(?.*?):(?.*)$/.exec(repositoryUrl) || []; let { hostname, port, pathname, protocol } = new URL( - match ? `ssh://${auth2 ? `${auth2}@` : ""}${host}/${path6}` : repositoryUrl + match ? `ssh://${auth2 ? `${auth2}@` : ""}${host2}/${path6}` : repositoryUrl ); port = protocol.includes("ssh") ? "" : port; protocol = protocol && /http[^s]/.test(protocol) ? "http" : "https"; - const [, owner, repository] = /^\/(?[^/]+)?\/?(?.+)?$/.exec(pathname) || []; + const [, owner2, repository2] = /^\/(?[^/]+)?\/?(?.+)?$/.exec(pathname) || []; const { issue: issue2, commit, referenceActions, issuePrefixes } = find_default(hosts_config_default, (conf) => conf.hostname === hostname) || hosts_config_default.default; const parser = new CommitParser({ referenceActions, issuePrefixes, ...parserOpts }); const parsedCommits = filterRevertedCommitsSync( @@ -43269,8 +43269,8 @@ async function generateNotes(pluginConfig, context3) { { version: nextRelease.version, host: format2({ protocol, hostname, port }), - owner, - repository, + owner: owner2, + repository: repository2, previousTag, currentTag, linkCompare: currentTag && previousTag, @@ -43293,6 +43293,373 @@ async function generateNotes(pluginConfig, context3) { return getStream(intoStream.object(parsedCommits).pipe(conventional_changelog_writer_default(changelogContext, writerOpts))); } +// node_modules/conventional-changelog-conventionalcommits/src/constants.js +var DEFAULT_COMMIT_TYPES = Object.freeze([ + { + type: "feat", + section: "Features" + }, + { + type: "feature", + section: "Features" + }, + { + type: "fix", + section: "Bug Fixes" + }, + { + type: "perf", + section: "Performance Improvements" + }, + { + type: "revert", + section: "Reverts" + }, + { + type: "docs", + section: "Documentation", + hidden: true + }, + { + type: "style", + section: "Styles", + hidden: true + }, + { + type: "chore", + section: "Miscellaneous Chores", + hidden: true + }, + { + type: "refactor", + section: "Code Refactoring", + hidden: true + }, + { + type: "test", + section: "Tests", + hidden: true + }, + { + type: "build", + section: "Build System", + hidden: true + }, + { + type: "ci", + section: "Continuous Integration", + hidden: true + } +].map(Object.freeze)); + +// node_modules/conventional-changelog-conventionalcommits/src/parser.js +function createParserOpts2(config) { + return { + headerPattern: /^(\w*)(?:\((.*)\))?!?: (.*)$/, + breakingHeaderPattern: /^(\w*)(?:\((.*)\))?!: (.*)$/, + headerCorrespondence: [ + "type", + "scope", + "subject" + ], + noteKeywords: ["BREAKING CHANGE", "BREAKING-CHANGE"], + revertPattern: /^(?:Revert|revert:)\s"?([\s\S]+?)"?\s*This reverts commit (\w*)\./i, + revertCorrespondence: ["header", "hash"], + issuePrefixes: config?.issuePrefixes || ["#"] + }; +} + +// node_modules/conventional-changelog-conventionalcommits/src/writer.js +var import_compare_func2 = __toESM(require_compare_func(), 1); + +// node_modules/conventional-changelog-conventionalcommits/src/utils.js +function hasIntersection(a, b) { + if (!a || !b) { + return false; + } + let listA = a; + let listB = b; + if (!Array.isArray(listA)) { + listA = [listA]; + } + if (!Array.isArray(listB)) { + listB = [listB]; + } + return listA.some((item) => listB.includes(item)); +} +function matchScope(config = {}, commit) { + const { + scope: targetScope, + scopeOnly = false + } = config; + const includesScope = hasIntersection( + commit.scope?.split(","), + targetScope + ); + return !targetScope || scopeOnly && includesScope || !scopeOnly && (!commit.scope || includesScope); +} + +// node_modules/conventional-changelog-conventionalcommits/src/templates.js +var mainTemplate3 = `{{> header}} +{{#if noteGroups}} +{{#each noteGroups}} + +### \u26A0 {{title}} + +{{#each notes}} +* {{#if commit.scope}}**{{commit.scope}}:** {{/if}}{{text}} +{{/each}} +{{/each}} +{{/if}} +{{#each commitGroups}} + +{{#if title}} +### {{title}} + +{{/if}} +{{#each commits}} +{{> commit root=@root}} +{{/each}} +{{/each}} +{{> footer}} +`; +var headerPartial3 = `## {{#if @root.linkCompare~}} + [{{version}}]({{compareUrlFormat}}) +{{~else}} + {{~version}} +{{~/if}} +{{~#if title}} "{{title}}" +{{~/if}} +{{~#if date}} ({{date}}) +{{/if}} +`; +var commitPartial3 = `*{{#if scope}} **{{scope}}:** +{{~/if}} {{#if subject}} + {{~subject}} +{{~else}} + {{~header}} +{{~/if}} + +{{~!-- commit link --}}{{~#if hash}} {{#if @root.linkReferences~}} + ([{{shortHash}}]({{commitUrlFormat}})) +{{~else}} + {{~shortHash}} +{{~/if}}{{~/if}} + +{{~!-- commit references --}} +{{~#if references~}} + , closes + {{~#each references}} {{#if @root.linkReferences~}} + [ + {{~#if this.owner}} + {{~this.owner}}/ + {{~/if}} + {{~this.repository}}{{this.prefix}}{{this.issue}}]({{issueUrlFormat}}) + {{~else}} + {{~#if this.owner}} + {{~this.owner}}/ + {{~/if}} + {{~this.repository}}{{this.prefix}}{{this.issue}} + {{~/if}}{{/each}} +{{~/if}} + +`; +var footerPartial3 = ``; + +// node_modules/conventional-changelog-conventionalcommits/src/writer.js +var COMMIT_HASH_LENGTH2 = 7; +var releaseAsRegex = /release-as:\s*\w*@?([0-9]+\.[0-9]+\.[0-9a-z]+(-[0-9a-z.]+)?)\s*/i; +var owner = "{{#if this.owner}}{{~this.owner}}{{else}}{{~@root.owner}}{{/if}}"; +var host = "{{~@root.host}}"; +var repository = "{{#if this.repository}}{{~this.repository}}{{else}}{{~@root.repository}}{{/if}}"; +function createWriterOpts2(config) { + const finalConfig = { + types: DEFAULT_COMMIT_TYPES, + issueUrlFormat: "{{host}}/{{owner}}/{{repository}}/issues/{{id}}", + commitUrlFormat: "{{host}}/{{owner}}/{{repository}}/commit/{{hash}}", + compareUrlFormat: "{{host}}/{{owner}}/{{repository}}/compare/{{previousTag}}...{{currentTag}}", + userUrlFormat: "{{host}}/{{user}}", + issuePrefixes: ["#"], + ...config + }; + const commitUrlFormat = expandTemplate(finalConfig.commitUrlFormat, { + host, + owner, + repository + }); + const compareUrlFormat = expandTemplate(finalConfig.compareUrlFormat, { + host, + owner, + repository + }); + const issueUrlFormat = expandTemplate(finalConfig.issueUrlFormat, { + host, + owner, + repository, + id: "{{this.issue}}", + prefix: "{{this.prefix}}" + }); + const commitGroupOrder = finalConfig.types.flatMap((t) => t.section).filter((t) => t); + return { + mainTemplate: mainTemplate3, + headerPartial: headerPartial3.replace(/{{compareUrlFormat}}/g, compareUrlFormat), + commitPartial: commitPartial3.replace(/{{commitUrlFormat}}/g, commitUrlFormat).replace(/{{issueUrlFormat}}/g, issueUrlFormat), + footerPartial: footerPartial3, + transform: (commit, context3) => { + let discard = true; + const issues = []; + const entry = findTypeEntry(finalConfig.types, commit); + if (commit.footer && releaseAsRegex.test(commit.footer) || commit.body && releaseAsRegex.test(commit.body)) { + discard = false; + } + const notes = commit.notes.map((note) => { + discard = false; + return { + ...note, + title: "BREAKING CHANGES" + }; + }); + if ( + // breaking changes attached to any type are still displayed. + discard && (entry === void 0 || entry.hidden) || !matchScope(finalConfig, commit) + ) { + return void 0; + } + const type = entry ? entry.section : commit.type; + const scope = commit.scope === "*" || finalConfig.scope ? "" : commit.scope; + const shortHash = typeof commit.hash === "string" ? commit.hash.substring(0, COMMIT_HASH_LENGTH2) : commit.shortHash; + let { subject } = commit; + if (typeof subject === "string") { + const issueRegEx = `(${finalConfig.issuePrefixes.join("|")})([a-z0-9]+)`; + const re = new RegExp(issueRegEx, "g"); + subject = subject.replace(re, (_, prefix, issue2) => { + issues.push(prefix + issue2); + const url = expandTemplate(finalConfig.issueUrlFormat, { + host: context3.host, + owner: context3.owner, + repository: context3.repository, + id: issue2, + prefix + }); + return `[${prefix}${issue2}](${url})`; + }); + subject = subject.replace(/`[^`]*`|\B@([a-z0-9](?:-?[a-z0-9/]){0,38})/g, (match, user) => { + if (!user) { + return match; + } + if (user.includes("/")) { + return `@${user}`; + } + const usernameUrl = expandTemplate(finalConfig.userUrlFormat, { + host: context3.host, + owner: context3.owner, + repository: context3.repository, + user + }); + return `[@${user}](${usernameUrl})`; + }); + } + const references = commit.references.filter((reference) => !issues.includes(reference.prefix + reference.issue)); + return { + notes, + type, + scope, + shortHash, + subject, + references + }; + }, + groupBy: "type", + // the groupings of commit messages, e.g., Features vs., Bug Fixes, are + // sorted based on their probable importance: + commitGroupsSort: (a, b) => { + const gRankA = commitGroupOrder.indexOf(a.title); + const gRankB = commitGroupOrder.indexOf(b.title); + return gRankA - gRankB; + }, + commitsSort: ["scope", "subject"], + noteGroupsSort: "title", + notesSort: import_compare_func2.default + }; +} +function findTypeEntry(types, commit) { + const typeKey = (commit.revert ? "revert" : commit.type || "").toLowerCase(); + return types.find((entry) => { + if (entry.type !== typeKey) { + return false; + } + if (entry.scope && entry.scope !== commit.scope) { + return false; + } + return true; + }); +} +function expandTemplate(template, context3) { + let expanded = template; + Object.keys(context3).forEach((key) => { + expanded = expanded.replace(new RegExp(`{{${key}}}`, "g"), context3[key]); + }); + return expanded; +} + +// node_modules/conventional-changelog-conventionalcommits/src/whatBump.js +function createWhatBump(config = {}) { + const { + types = DEFAULT_COMMIT_TYPES, + bumpStrict = false + } = config; + const hiddenTypes = bumpStrict && types.reduce((hiddenTypes2, type) => { + if (type.hidden) { + hiddenTypes2.push(type.type); + } + return hiddenTypes2; + }, []); + return function whatBump2(commits) { + let level = 2; + let breakings = 0; + let features = 0; + let bugfixes = 0; + commits.forEach((commit) => { + if (!matchScope(config, commit)) { + return; + } + if (commit.notes.length > 0) { + breakings += commit.notes.length; + level = 0; + } else if (commit.type === "feat" || commit.type === "feature") { + features += 1; + if (level === 2) { + level = 1; + } + } else if (bumpStrict && !hiddenTypes.includes(commit.type)) { + bugfixes += 1; + } + }); + if (config?.preMajor && level < 2) { + level++; + } else if (bumpStrict && level === 2 && !breakings && !features && !bugfixes) { + return null; + } + return { + level, + reason: breakings === 1 ? `There is ${breakings} BREAKING CHANGE and ${features} features` : `There are ${breakings} BREAKING CHANGES and ${features} features` + }; + }; +} + +// node_modules/conventional-changelog-conventionalcommits/src/index.js +function createPreset2(config) { + return { + commits: { + ignore: config?.ignoreCommits, + merges: false + }, + parser: createParserOpts2(config), + writer: createWriterOpts2(config), + whatBump: createWhatBump(config) + }; +} + // src/utils.ts var import_semver3 = __toESM(require_semver2(), 1); @@ -43334,8 +43701,8 @@ var Context = class { } get repo() { if (process.env.GITHUB_REPOSITORY) { - const [owner, repo] = process.env.GITHUB_REPOSITORY.split("/"); - return { owner, repo }; + const [owner2, repo] = process.env.GITHUB_REPOSITORY.split("/"); + return { owner: owner2, repo }; } if (this.payload.repository) { return { @@ -47305,7 +47672,11 @@ async function main() { })) ) : void 0 }, - { commits, logger: { log: console.info.bind(console) } } + { + commits, + cwd: process.cwd(), + logger: { log: console.info.bind(console) } + } ); let shouldContinue = true; if (isPrerelease) { @@ -47352,15 +47723,17 @@ async function main() { const newTag = `${tagPrefix}${newVersion}`; info(`New tag after applying prefix is ${newTag}.`); setOutput("new_tag", newTag); + const resolvedPreset = createPreset2({ + types: mergeWithDefaultChangelogRules(mappedReleaseRules) + }); const changelog = await generateNotes( { - preset: "conventionalcommits", - presetConfig: { - types: mergeWithDefaultChangelogRules(mappedReleaseRules) - } + parserOpts: resolvedPreset.parser, + writerOpts: resolvedPreset.writer }, { commits, + cwd: process.cwd(), logger: { log: console.info.bind(console) }, options: { repositoryUrl: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}` diff --git a/src/action.ts b/src/action.ts index acb6e3d6c..6ee3fadfa 100644 --- a/src/action.ts +++ b/src/action.ts @@ -2,6 +2,7 @@ import * as core from '@actions/core'; import { gte, inc, parse, valid, type ReleaseType } from 'semver'; import { analyzeCommits } from '@semantic-release/commit-analyzer'; import { generateNotes } from '@semantic-release/release-notes-generator'; +import conventionalCommitsPreset from 'conventional-changelog-conventionalcommits'; import { getBranchFromRef, isPr, @@ -138,7 +139,11 @@ export default async function main() { })) : undefined, }, - { commits, logger: { log: console.info.bind(console) } } + { + commits, + cwd: process.cwd(), + logger: { log: console.info.bind(console) }, + } ); // Determine if we should continue with tag creation based on main vs prerelease branch @@ -207,15 +212,22 @@ export default async function main() { core.info(`New tag after applying prefix is ${newTag}.`); core.setOutput('new_tag', newTag); + // Pre-resolve the preset so esbuild can bundle it. Passing + // `preset: 'conventionalcommits'` would trigger a dynamic + // `import-from-esm` lookup that fails on the Actions runner + // where no `node_modules` directory exists. + const resolvedPreset = conventionalCommitsPreset({ + types: mergeWithDefaultChangelogRules(mappedReleaseRules), + }); + const changelog = await generateNotes( { - preset: 'conventionalcommits', - presetConfig: { - types: mergeWithDefaultChangelogRules(mappedReleaseRules), - }, + parserOpts: resolvedPreset.parser, + writerOpts: resolvedPreset.writer, }, { commits, + cwd: process.cwd(), logger: { log: console.info.bind(console) }, options: { repositoryUrl: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}`, diff --git a/tests/bundle.smoke.test.ts b/tests/bundle.smoke.test.ts index 96c8da2a2..d8903b34f 100644 --- a/tests/bundle.smoke.test.ts +++ b/tests/bundle.smoke.test.ts @@ -1,6 +1,16 @@ import { beforeAll, describe, expect, it } from 'vitest'; -import { spawnSync } from 'node:child_process'; -import { copyFileSync, existsSync, mkdtempSync, statSync } from 'node:fs'; +import { spawn, spawnSync } from 'node:child_process'; +import type { ChildProcess } from 'node:child_process'; +import { + copyFileSync, + existsSync, + mkdtempSync, + statSync, + writeFileSync, +} from 'node:fs'; +import { createServer } from 'node:http'; +import type { IncomingMessage, ServerResponse } from 'node:http'; +import type { AddressInfo } from 'node:net'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; @@ -33,6 +43,45 @@ import { join } from 'node:path'; const BUNDLE_PATH = join(process.cwd(), 'lib', 'main.js'); const SPAWN_TIMEOUT_MS = 20_000; +interface ChildResult { + stdout: string; + stderr: string; + status: number | null; + signal: NodeJS.Signals | null; +} + +/** + * Drain stdout/stderr asynchronously while the child runs so large + * bursts of startup output can't deadlock the parent. Always resolves + * (never rejects) so the caller can assert on the captured streams even + * if the child crashed, exited non-zero, or had to be SIGKILLed. + */ +function runChildAndCapture( + child: ChildProcess, + timeoutMs: number +): Promise { + return new Promise((resolve) => { + const stdoutChunks: Buffer[] = []; + const stderrChunks: Buffer[] = []; + child.stdout?.on('data', (chunk: Buffer) => stdoutChunks.push(chunk)); + child.stderr?.on('data', (chunk: Buffer) => stderrChunks.push(chunk)); + + const timer = setTimeout(() => { + child.kill('SIGKILL'); + }, timeoutMs); + + child.on('close', (status, signal) => { + clearTimeout(timer); + resolve({ + stdout: Buffer.concat(stdoutChunks).toString('utf8'), + stderr: Buffer.concat(stderrChunks).toString('utf8'), + status, + signal, + }); + }); + }); +} + describe('bundled action artefact', () => { beforeAll(() => { if (!existsSync(BUNDLE_PATH)) { @@ -58,6 +107,7 @@ describe('bundled action artefact', () => { const isolatedDir = mkdtempSync(join(tmpdir(), 'gh-tag-action-smoke-')); const copiedBundle = join(isolatedDir, 'main.js'); copyFileSync(BUNDLE_PATH, copiedBundle); + writeFileSync(join(isolatedDir, 'package.json'), '{"type":"module"}'); const result = spawnSync(process.execPath, [copiedBundle], { cwd: isolatedDir, @@ -100,4 +150,136 @@ describe('bundled action artefact', () => { // executed successfully. expect(combined).toMatch(/::error::|HttpError|Bad credentials/); }); + + // This second smoke test exists to catch a different class of bug that + // only manifests *after* the bundle has linked — namely, runtime paths + // inside bundled dependencies that try to resolve packages from the + // filesystem (e.g. via `import-from-esm`) and blow up because the + // Actions runtime has no `node_modules`. The canonical regression is + // `@semantic-release/release-notes-generator/lib/load-changelog-config.js` + // calling `importFrom(cwd, 'conventional-changelog-')` when the + // preset is bundled but not installed as a sibling package — which + // previously surfaced as: + // + // TypeError [ERR_INVALID_ARG_TYPE]: The "paths[0]" argument must be + // of type string. Received undefined + // at resolve (node:path:...) + // at resolveToFileURL (lib/main.js:...) + // at importFrom (lib/main.js:...) + // + // To reach that code path we need the action to survive its GitHub + // API calls and proceed to `generateNotes`. We stand up a tiny local + // HTTP server that returns just enough JSON to satisfy `listTags` and + // `compareCommits`, point the spawned child at it via `GITHUB_API_URL`, + // and set `dry_run=true` so the run exits cleanly after changelog + // generation without actually trying to create a tag. + it('runs the bundle end-to-end through generateNotes against a mocked GitHub API', async () => { + const handleRequest = (req: IncomingMessage, res: ServerResponse): void => { + const url = req.url ?? ''; + res.setHeader('content-type', 'application/json'); + + if (url.startsWith('/repos/foo/bar/tags')) { + // Empty tag list → action falls back to the synthetic `v0.0.0` + // baseline and walks the full release-notes path we want to + // exercise. + res.end('[]'); + return; + } + + if (url.startsWith('/repos/foo/bar/compare/')) { + res.end( + JSON.stringify({ + commits: [ + { + sha: '1111111111111111111111111111111111111111', + commit: { + message: 'feat: bundle smoke happy path', + }, + }, + ], + }) + ); + return; + } + + res.statusCode = 404; + res.end( + JSON.stringify({ message: `not mocked: ${req.method ?? 'GET'} ${url}` }) + ); + }; + + const server = createServer(handleRequest); + await new Promise((resolve) => { + server.listen(0, '127.0.0.1', resolve); + }); + + try { + const { port } = server.address() as AddressInfo; + const apiUrl = `http://127.0.0.1:${port}`; + + const isolatedDir = mkdtempSync(join(tmpdir(), 'gh-tag-action-e2e-')); + const copiedBundle = join(isolatedDir, 'main.js'); + copyFileSync(BUNDLE_PATH, copiedBundle); + writeFileSync(join(isolatedDir, 'package.json'), '{"type":"module"}'); + + // We use async `spawn` (not `spawnSync`) here because the bundle + // emits enough stdio during startup to deadlock `spawnSync` on + // macOS when combined with captured pipes — the parent can't drain + // the child's pipes while it is blocked in the sync call, so the + // child eventually wedges and the timeout kills it with empty + // stdio. `spawn` drains both streams on the event loop as bytes + // arrive, which matches real Actions-runner behaviour. + const result = await runChildAndCapture( + spawn(process.execPath, [copiedBundle], { + cwd: isolatedDir, + env: { + PATH: process.env['PATH'] ?? '', + GITHUB_REPOSITORY: 'foo/bar', + GITHUB_REF: 'refs/heads/master', + GITHUB_SHA: '1111111111111111111111111111111111111111', + GITHUB_API_URL: apiUrl, + // `@actions/github` reads this for `repositoryUrl`, which + // the release-notes generator templates into the changelog. + // Pin it to the mock URL so we never accidentally hit a + // real host. + GITHUB_SERVER_URL: apiUrl, + INPUT_GITHUB_TOKEN: 'fake-token-ok', + INPUT_RELEASE_BRANCHES: 'master', + INPUT_DRY_RUN: 'true', + }, + }), + SPAWN_TIMEOUT_MS + ); + + const combined = [result.stdout, result.stderr] + .filter(Boolean) + .join('\n'); + + // Guard against the exact regression that prompted this test. + expect( + combined, + `Spawned bundle exited with status=${String(result.status)} signal=${String( + result.signal + )}. Combined stdio:\n${combined}` + ).not.toMatch(/ERR_INVALID_ARG_TYPE/); + expect(combined).not.toMatch(/paths\[0\]/); + + // Also re-assert no module-resolution errors (defence in depth: + // the first test in this file checks this at startup; this one + // checks it along the release-notes code path). + expect(combined).not.toMatch(/ERR_MODULE_NOT_FOUND/); + expect(combined).not.toMatch(/Cannot find (?:module|package)/); + + // Positive signal: the action walked all the way to the dry-run + // exit gate, which is downstream of `generateNotes`. + expect(combined).toMatch(/Dry run: not performing tag action\./); + expect(result.status).toBe(0); + } finally { + await new Promise((resolve) => { + server.close(() => { + resolve(); + }); + }); + } + }); }); diff --git a/types/semantic.d.ts b/types/semantic.d.ts index 65b1ddf6e..64969f6d3 100644 --- a/types/semantic.d.ts +++ b/types/semantic.d.ts @@ -31,6 +31,22 @@ interface ReleaseRule { readonly scope?: string; } +declare module 'conventional-changelog-conventionalcommits' { + interface PresetConfig { + types?: readonly { type: string; section?: string; hidden?: boolean }[]; + [key: string]: unknown; + } + + interface Preset { + commits: { ignore: unknown; merges: boolean }; + parser: Record; + writer: Record; + whatBump: (...args: unknown[]) => unknown; + } + + export default function createPreset(config?: PresetConfig): Preset; +} + declare module '@semantic-release/commit-analyzer' { export function analyzeCommits( config: { @@ -42,6 +58,7 @@ declare module '@semantic-release/commit-analyzer' { }, args: { commits: readonly Commit[]; + cwd?: string; logger: Logger; } ): Promise; @@ -59,6 +76,7 @@ declare module '@semantic-release/release-notes-generator' { }, args: { commits: readonly Commit[]; + cwd?: string; logger: Logger; options: { repositoryUrl: string;