relizy 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +14 -6
- package/dist/index.d.mts +162 -11
- package/dist/index.d.ts +162 -11
- package/dist/index.mjs +4 -1
- package/dist/shared/{relizy.BljqIKug.mjs → relizy.CN-7XcP7.mjs} +795 -80
- package/package.json +5 -1
|
@@ -15,6 +15,9 @@ import fastGlob from 'fast-glob';
|
|
|
15
15
|
import { upperFirst } from '@maz-ui/utils/helpers/upperFirst';
|
|
16
16
|
import { convert } from 'convert-gitmoji';
|
|
17
17
|
import { fetch as fetch$1 } from 'node-fetch-native';
|
|
18
|
+
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
19
|
+
import { gfmFromMarkdown } from 'mdast-util-gfm';
|
|
20
|
+
import { gfm } from 'micromark-extension-gfm';
|
|
18
21
|
|
|
19
22
|
function shellSingleQuote(value) {
|
|
20
23
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
@@ -2713,7 +2716,7 @@ function getDefaultConfig() {
|
|
|
2713
2716
|
slack: {
|
|
2714
2717
|
enabled: false,
|
|
2715
2718
|
onlyStable: true,
|
|
2716
|
-
|
|
2719
|
+
changelogFormat: "summary",
|
|
2717
2720
|
noAuthors: false,
|
|
2718
2721
|
noPackages: false
|
|
2719
2722
|
}
|
|
@@ -3462,15 +3465,19 @@ function getCommitBody(commit) {
|
|
|
3462
3465
|
const contentLines = lines.filter((line) => {
|
|
3463
3466
|
const trimmedLine = line.trim();
|
|
3464
3467
|
if (!trimmedLine) {
|
|
3465
|
-
return
|
|
3468
|
+
return true;
|
|
3466
3469
|
}
|
|
3467
3470
|
const isFileLine = /^[AMDTUXB](?:\d{3})?\s+/.test(trimmedLine) || /^[RCM]\d{3}\s+/.test(trimmedLine);
|
|
3468
3471
|
return !isFileLine;
|
|
3469
3472
|
});
|
|
3470
|
-
|
|
3473
|
+
const paragraphLines = contentLines.filter((line, index) => line.trim() || contentLines[index - 1]?.trim());
|
|
3474
|
+
while (paragraphLines.length > 0 && !paragraphLines.at(-1)?.trim()) {
|
|
3475
|
+
paragraphLines.pop();
|
|
3476
|
+
}
|
|
3477
|
+
if (paragraphLines.length === 0) {
|
|
3471
3478
|
return "";
|
|
3472
3479
|
}
|
|
3473
|
-
const indentedBody =
|
|
3480
|
+
const indentedBody = paragraphLines.map((line) => line.trim() ? ` ${line}` : "").join("\n");
|
|
3474
3481
|
return `
|
|
3475
3482
|
|
|
3476
3483
|
${indentedBody}
|
|
@@ -3679,29 +3686,35 @@ ${existingChangelog}`;
|
|
|
3679
3686
|
const BASE_PROMPT = `You are a release-notes rewriter.
|
|
3680
3687
|
The user message contains a markdown changelog built from conventional commits, wrapped in a <changelog> tag. Your job: rewrite the content inside that tag.
|
|
3681
3688
|
Never ask for clarification. Never reply with questions, greetings, or meta-commentary. Your only output is the rewritten changelog content.
|
|
3682
|
-
Never invent changes
|
|
3683
|
-
|
|
3684
|
-
|
|
3689
|
+
Never invent changes: if something is not mentioned in the input, it does not exist.
|
|
3690
|
+
Text indented under a bullet is the body of that commit: use it to understand and describe the change, never treat it as a separate change.
|
|
3691
|
+
If the input has one section per package (headings such as "## my-package@1.2.0"), keep each change attributed to its package.
|
|
3692
|
+
Never include compare links, contributor lists, or release metadata: only the content provided.
|
|
3693
|
+
Respond with the rewritten content only: no preamble, no explanation, no surrounding code fence, no <changelog> tag.
|
|
3685
3694
|
Output language: {{language}}.`;
|
|
3686
3695
|
const PROVIDER_RELEASE_PROMPT = `Format the output as markdown with "### <Type>" sections matching the input (Features, Bug Fixes, etc.).
|
|
3687
3696
|
Merge redundant items that describe the same change.
|
|
3688
|
-
Rewrite each bullet for end-user clarity
|
|
3697
|
+
Rewrite each bullet for end-user clarity: focus on what changed for the user, not internal details.
|
|
3689
3698
|
Preserve exactly as given: PR and issue references like #123, commit hashes, commit scopes like **auth:**, and all markdown links.
|
|
3690
3699
|
Preserve the \u26A0\uFE0F marker on breaking items and the "#### \u26A0\uFE0F Breaking Changes" section if present.
|
|
3691
3700
|
Purely internal commits (chore, refactor with no user-visible impact) may be dropped unless they carry meaningful information.
|
|
3692
3701
|
Tone: professional, concise, public-facing release notes.`;
|
|
3693
|
-
const TWITTER_PROMPT = `Output plain text
|
|
3694
|
-
|
|
3695
|
-
Remove commit hashes (e.g. "aabf96b") and
|
|
3696
|
-
If
|
|
3697
|
-
If the input has
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
const SLACK_PROMPT = `Format
|
|
3702
|
-
|
|
3703
|
-
|
|
3702
|
+
const TWITTER_PROMPT = `Output plain text: no markdown syntax at all (no "**", "_", backticks, headings, bullets or links).
|
|
3703
|
+
Never start a word with "#": Twitter turns it into a hashtag. Only add hashtags when the extra guidelines ask for them.
|
|
3704
|
+
Remove commit hashes (e.g. "aabf96b"), PR and issue references (e.g. "#123") and scope markers (e.g. "**auth:**"): mention the scope in the sentence only when it helps.
|
|
3705
|
+
If breaking changes are present, mention them first.
|
|
3706
|
+
If the input has one change, write one substantive sentence about it.
|
|
3707
|
+
If the input has multiple changes, surface the 2 to 4 most user-visible ones.
|
|
3708
|
+
You produce ONLY the changelog content: the outer template adds the project name, version, and URLs around it.
|
|
3709
|
+
Tone: enthusiastic but not cringy. At most 1 or 2 emojis in total.`;
|
|
3710
|
+
const SLACK_PROMPT = `Format the output as standard markdown: "### <Type>" headings matching the input, "- " bullets, **bold**, _italic_ and \`code\`.
|
|
3711
|
+
Never use Slack mrkdwn syntax (single-asterisk bold, <url|text> links): the output is converted for Slack automatically.
|
|
3712
|
+
Keep PR and issue references as the markdown links given in the input (e.g. "([#123](https://...))").
|
|
3713
|
+
If the input has package headings, keep them as "## " headings.
|
|
3714
|
+
If breaking changes are present, lead with them.
|
|
3715
|
+
Keep it scannable: 3 to 8 bullets in total, one line each, focused on what changed for the team.
|
|
3704
3716
|
Tone: factual, oriented toward an internal team audience.`;
|
|
3717
|
+
const MAX_LENGTH_PROMPT = `Hard maximum: {{maxLength}} characters. Never exceed it.`;
|
|
3705
3718
|
function wrapChangelogPrompt(prompt) {
|
|
3706
3719
|
return `<changelog>
|
|
3707
3720
|
${prompt}
|
|
@@ -3834,6 +3847,16 @@ function substitutePlaceholders(prompt, vars) {
|
|
|
3834
3847
|
}
|
|
3835
3848
|
return result;
|
|
3836
3849
|
}
|
|
3850
|
+
function getExtraGuidelines(config, target) {
|
|
3851
|
+
const guidelines = config.ai?.extraGuidelines;
|
|
3852
|
+
if (!guidelines) {
|
|
3853
|
+
return [];
|
|
3854
|
+
}
|
|
3855
|
+
if (typeof guidelines === "string") {
|
|
3856
|
+
return [guidelines];
|
|
3857
|
+
}
|
|
3858
|
+
return [guidelines.global, guidelines[target]].filter((guideline) => !!guideline?.trim());
|
|
3859
|
+
}
|
|
3837
3860
|
function assemblePrompt(config, target, maxLength) {
|
|
3838
3861
|
const vars = {
|
|
3839
3862
|
language: config.ai?.language ?? "en",
|
|
@@ -3844,9 +3867,10 @@ function assemblePrompt(config, target, maxLength) {
|
|
|
3844
3867
|
return substitutePlaceholders(override, vars);
|
|
3845
3868
|
}
|
|
3846
3869
|
const parts = [BASE_PROMPT, PLATFORM_PROMPTS[target]];
|
|
3847
|
-
if (
|
|
3848
|
-
parts.push(
|
|
3870
|
+
if (maxLength !== void 0) {
|
|
3871
|
+
parts.push(MAX_LENGTH_PROMPT);
|
|
3849
3872
|
}
|
|
3873
|
+
parts.push(...getExtraGuidelines(config, target));
|
|
3850
3874
|
return substitutePlaceholders(parts.join("\n\n"), vars);
|
|
3851
3875
|
}
|
|
3852
3876
|
function applyAIOverride(config, ai) {
|
|
@@ -4788,6 +4812,648 @@ async function postPrComment({
|
|
|
4788
4812
|
}
|
|
4789
4813
|
}
|
|
4790
4814
|
|
|
4815
|
+
const SLACK_MAX_BLOCKS = 50;
|
|
4816
|
+
const SLACK_SECTION_MAX_LENGTH = 3e3;
|
|
4817
|
+
const SLACK_RICH_TEXT_BLOCK_MAX_LENGTH = 3e3;
|
|
4818
|
+
const SLACK_RICH_TEXT_MESSAGE_MAX_LENGTH = 11e3;
|
|
4819
|
+
const SLACK_HEADER_MAX_LENGTH = 150;
|
|
4820
|
+
const ELLIPSIS = "\u2026";
|
|
4821
|
+
const SLACK_MAX_LIST_INDENT = 8;
|
|
4822
|
+
function parseMarkdown(markdown) {
|
|
4823
|
+
return fromMarkdown(markdown, {
|
|
4824
|
+
extensions: [gfm()],
|
|
4825
|
+
mdastExtensions: [gfmFromMarkdown()]
|
|
4826
|
+
});
|
|
4827
|
+
}
|
|
4828
|
+
function splitIntoUnits(root) {
|
|
4829
|
+
const units = [];
|
|
4830
|
+
for (const node of root.children) {
|
|
4831
|
+
if (node.type === "heading") {
|
|
4832
|
+
units.push({ type: "heading", node });
|
|
4833
|
+
} else if (node.type === "list") {
|
|
4834
|
+
node.children.forEach((item, index) => units.push({ type: "item", node: item, list: node, index }));
|
|
4835
|
+
} else {
|
|
4836
|
+
units.push({ type: "block", node });
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
return units;
|
|
4840
|
+
}
|
|
4841
|
+
function countOmittedChanges(units) {
|
|
4842
|
+
const items = units.filter((unit) => unit.type === "item").length;
|
|
4843
|
+
return items > 0 ? items : units.filter((unit) => unit.type !== "heading").length;
|
|
4844
|
+
}
|
|
4845
|
+
function formatOmittedChanges(count) {
|
|
4846
|
+
return count > 0 ? `\u2026 and ${count} more change${count > 1 ? "s" : ""}` : "";
|
|
4847
|
+
}
|
|
4848
|
+
function sliceSlackText(text, length) {
|
|
4849
|
+
const end = Math.max(0, length);
|
|
4850
|
+
const lastCode = text.charCodeAt(end - 1);
|
|
4851
|
+
return text.slice(0, lastCode >= 55296 && lastCode <= 56319 ? end - 1 : end);
|
|
4852
|
+
}
|
|
4853
|
+
function dropTrailingHeadings(rendered) {
|
|
4854
|
+
let end = rendered.length;
|
|
4855
|
+
while (end > 0 && rendered[end - 1]?.unit.type === "heading") {
|
|
4856
|
+
end--;
|
|
4857
|
+
}
|
|
4858
|
+
return rendered.slice(0, end);
|
|
4859
|
+
}
|
|
4860
|
+
function getListItemParts(item) {
|
|
4861
|
+
const [first, ...rest] = item.children;
|
|
4862
|
+
if (first?.type === "paragraph") {
|
|
4863
|
+
return { head: first.children, body: rest };
|
|
4864
|
+
}
|
|
4865
|
+
return { head: [], body: item.children };
|
|
4866
|
+
}
|
|
4867
|
+
function getCheckboxPrefix(item) {
|
|
4868
|
+
if (item.checked === true) {
|
|
4869
|
+
return "\u2611 ";
|
|
4870
|
+
}
|
|
4871
|
+
if (item.checked === false) {
|
|
4872
|
+
return "\u2610 ";
|
|
4873
|
+
}
|
|
4874
|
+
return "";
|
|
4875
|
+
}
|
|
4876
|
+
function getListItemNumber(list, index) {
|
|
4877
|
+
return (list.start ?? 1) + index;
|
|
4878
|
+
}
|
|
4879
|
+
function getAltText(node) {
|
|
4880
|
+
return node.alt ?? "";
|
|
4881
|
+
}
|
|
4882
|
+
function toPlainText(node) {
|
|
4883
|
+
if ("value" in node && typeof node.value === "string") {
|
|
4884
|
+
return node.value;
|
|
4885
|
+
}
|
|
4886
|
+
if (node.type === "image" || node.type === "imageReference") {
|
|
4887
|
+
return getAltText(node);
|
|
4888
|
+
}
|
|
4889
|
+
if (node.type === "break") {
|
|
4890
|
+
return " ";
|
|
4891
|
+
}
|
|
4892
|
+
if ("children" in node) {
|
|
4893
|
+
return node.children.map(toPlainText).join("");
|
|
4894
|
+
}
|
|
4895
|
+
return "";
|
|
4896
|
+
}
|
|
4897
|
+
function isAbsoluteUrl(url) {
|
|
4898
|
+
return /^(?:https?:\/\/|mailto:)/i.test(url);
|
|
4899
|
+
}
|
|
4900
|
+
function collapseSoftBreaks(text) {
|
|
4901
|
+
const lines = text.split("\n");
|
|
4902
|
+
return lines.map((line, index) => {
|
|
4903
|
+
const withoutLeading = index > 0 ? line.trimStart() : line;
|
|
4904
|
+
return index < lines.length - 1 ? withoutLeading.trimEnd() : withoutLeading;
|
|
4905
|
+
}).join(" ");
|
|
4906
|
+
}
|
|
4907
|
+
function tableToPlainText(table) {
|
|
4908
|
+
return table.children.map((row) => row.children.map((cell) => toPlainText(cell).trim()).join(" | ")).join("\n");
|
|
4909
|
+
}
|
|
4910
|
+
function escapeMrkdwn(text) {
|
|
4911
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
4912
|
+
}
|
|
4913
|
+
function escapeMrkdwnUrl(url) {
|
|
4914
|
+
return url.replaceAll("<", "%3C").replaceAll(">", "%3E").replaceAll("|", "%7C");
|
|
4915
|
+
}
|
|
4916
|
+
function formatMrkdwnLink(url, label) {
|
|
4917
|
+
if (!isAbsoluteUrl(url)) {
|
|
4918
|
+
return escapeMrkdwn(label);
|
|
4919
|
+
}
|
|
4920
|
+
const safeUrl = escapeMrkdwnUrl(url);
|
|
4921
|
+
return label && label !== url ? `<${safeUrl}|${escapeMrkdwn(label)}>` : `<${safeUrl}>`;
|
|
4922
|
+
}
|
|
4923
|
+
function wrapMrkdwn(marker, children, active) {
|
|
4924
|
+
const inner = renderInlineMrkdwn(children, /* @__PURE__ */ new Set([...active, marker]));
|
|
4925
|
+
if (active.has(marker)) {
|
|
4926
|
+
return inner;
|
|
4927
|
+
}
|
|
4928
|
+
const content = inner.trim();
|
|
4929
|
+
if (!content) {
|
|
4930
|
+
return inner;
|
|
4931
|
+
}
|
|
4932
|
+
const start = inner.indexOf(content);
|
|
4933
|
+
return `${inner.slice(0, start)}${marker}${content}${marker}${inner.slice(start + content.length)}`;
|
|
4934
|
+
}
|
|
4935
|
+
function renderInlineNodeMrkdwn(node, active) {
|
|
4936
|
+
switch (node.type) {
|
|
4937
|
+
case "text":
|
|
4938
|
+
return escapeMrkdwn(collapseSoftBreaks(node.value));
|
|
4939
|
+
case "strong":
|
|
4940
|
+
return wrapMrkdwn("*", node.children, active);
|
|
4941
|
+
case "emphasis":
|
|
4942
|
+
return wrapMrkdwn("_", node.children, active);
|
|
4943
|
+
case "delete":
|
|
4944
|
+
return wrapMrkdwn("~", node.children, active);
|
|
4945
|
+
case "inlineCode":
|
|
4946
|
+
return `\`${escapeMrkdwn(node.value)}\``;
|
|
4947
|
+
case "break":
|
|
4948
|
+
return "\n";
|
|
4949
|
+
case "link":
|
|
4950
|
+
return formatMrkdwnLink(node.url, toPlainText(node));
|
|
4951
|
+
case "image":
|
|
4952
|
+
return formatMrkdwnLink(node.url, getAltText(node));
|
|
4953
|
+
case "linkReference":
|
|
4954
|
+
return renderInlineMrkdwn(node.children, active);
|
|
4955
|
+
case "imageReference":
|
|
4956
|
+
return escapeMrkdwn(getAltText(node));
|
|
4957
|
+
case "html":
|
|
4958
|
+
return escapeMrkdwn(node.value);
|
|
4959
|
+
case "footnoteReference":
|
|
4960
|
+
return `[^${escapeMrkdwn(node.identifier)}]`;
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
function renderInlineMrkdwn(nodes, active = /* @__PURE__ */ new Set()) {
|
|
4964
|
+
return nodes.map((node) => renderInlineNodeMrkdwn(node, active)).join("");
|
|
4965
|
+
}
|
|
4966
|
+
function quoteMrkdwn(text) {
|
|
4967
|
+
return text.split("\n").map((line) => `> ${line}`).join("\n");
|
|
4968
|
+
}
|
|
4969
|
+
function renderListItemMrkdwn(item, list, index, depth) {
|
|
4970
|
+
const indent = " ".repeat(depth);
|
|
4971
|
+
const bullet = depth === 0 ? "\u2022" : "\u25E6";
|
|
4972
|
+
const marker = list.ordered ? `${getListItemNumber(list, index)}.` : bullet;
|
|
4973
|
+
const { head, body } = getListItemParts(item);
|
|
4974
|
+
const lines = [`${indent}${marker} ${getCheckboxPrefix(item)}${renderInlineMrkdwn(head)}`.trimEnd()];
|
|
4975
|
+
for (const child of body) {
|
|
4976
|
+
if (child.type === "list") {
|
|
4977
|
+
child.children.forEach((subItem, subIndex) => lines.push(renderListItemMrkdwn(subItem, child, subIndex, depth + 1)));
|
|
4978
|
+
continue;
|
|
4979
|
+
}
|
|
4980
|
+
const rendered = renderBlockMrkdwn(child);
|
|
4981
|
+
if (rendered) {
|
|
4982
|
+
lines.push(quoteMrkdwn(rendered));
|
|
4983
|
+
}
|
|
4984
|
+
}
|
|
4985
|
+
return lines.join("\n");
|
|
4986
|
+
}
|
|
4987
|
+
function renderBlockMrkdwn(node) {
|
|
4988
|
+
switch (node.type) {
|
|
4989
|
+
case "heading": {
|
|
4990
|
+
const text = renderInlineMrkdwn(node.children, /* @__PURE__ */ new Set(["*"])).trim();
|
|
4991
|
+
return text ? `*${text}*` : "";
|
|
4992
|
+
}
|
|
4993
|
+
case "paragraph":
|
|
4994
|
+
return renderInlineMrkdwn(node.children);
|
|
4995
|
+
case "list":
|
|
4996
|
+
return node.children.map((item, index) => renderListItemMrkdwn(item, node, index, 0)).join("\n");
|
|
4997
|
+
case "blockquote":
|
|
4998
|
+
return quoteMrkdwn(node.children.map(renderBlockMrkdwn).filter(Boolean).join("\n"));
|
|
4999
|
+
case "code":
|
|
5000
|
+
return `\`\`\`
|
|
5001
|
+
${escapeMrkdwn(node.value)}
|
|
5002
|
+
\`\`\``;
|
|
5003
|
+
case "html":
|
|
5004
|
+
return escapeMrkdwn(node.value);
|
|
5005
|
+
case "table":
|
|
5006
|
+
return escapeMrkdwn(tableToPlainText(node));
|
|
5007
|
+
default:
|
|
5008
|
+
return "";
|
|
5009
|
+
}
|
|
5010
|
+
}
|
|
5011
|
+
function renderUnitMrkdwn(unit) {
|
|
5012
|
+
if (unit.type === "item") {
|
|
5013
|
+
return renderListItemMrkdwn(unit.node, unit.list, unit.index, 0);
|
|
5014
|
+
}
|
|
5015
|
+
return renderBlockMrkdwn(unit.node);
|
|
5016
|
+
}
|
|
5017
|
+
function joinMrkdwnUnits(rendered) {
|
|
5018
|
+
let text = "";
|
|
5019
|
+
rendered.forEach((current, index) => {
|
|
5020
|
+
const previous = rendered[index - 1];
|
|
5021
|
+
if (previous) {
|
|
5022
|
+
const sameList = previous.unit.type === "item" && current.unit.type === "item" && previous.unit.list === current.unit.list;
|
|
5023
|
+
text += previous.unit.type === "heading" || sameList ? "\n" : "\n\n";
|
|
5024
|
+
}
|
|
5025
|
+
text += current.output;
|
|
5026
|
+
});
|
|
5027
|
+
return text;
|
|
5028
|
+
}
|
|
5029
|
+
function markdownToSlackMrkdwn(markdown, { maxLength, cutCommits = false } = {}) {
|
|
5030
|
+
if (!markdown.trim()) {
|
|
5031
|
+
return "";
|
|
5032
|
+
}
|
|
5033
|
+
const rendered = splitIntoUnits(parseMarkdown(markdown)).map((unit) => ({ unit, output: renderUnitMrkdwn(unit), length: 0 })).filter(({ output }) => output !== "");
|
|
5034
|
+
const full = joinMrkdwnUnits(rendered);
|
|
5035
|
+
if (maxLength === void 0 || full.length <= maxLength) {
|
|
5036
|
+
return full;
|
|
5037
|
+
}
|
|
5038
|
+
for (let count = rendered.length - 1; count >= 0; count--) {
|
|
5039
|
+
const next = rendered[count];
|
|
5040
|
+
if (cutCommits && next.unit.type !== "heading") {
|
|
5041
|
+
const note2 = formatOmittedChanges(countOmittedChanges(rendered.slice(count + 1).map(({ unit }) => unit)));
|
|
5042
|
+
const suffix = note2 ? `
|
|
5043
|
+
|
|
5044
|
+
_${note2}_` : "";
|
|
5045
|
+
const prefix = joinMrkdwnUnits([...rendered.slice(0, count), { ...next, output: "" }]);
|
|
5046
|
+
const cut = cutMrkdwn(next.output, maxLength - prefix.length - suffix.length - ELLIPSIS.length);
|
|
5047
|
+
if (cut) {
|
|
5048
|
+
return `${prefix}${cut}${ELLIPSIS}${suffix}`;
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
5051
|
+
const kept = dropTrailingHeadings(rendered.slice(0, count));
|
|
5052
|
+
if (kept.length === 0) {
|
|
5053
|
+
continue;
|
|
5054
|
+
}
|
|
5055
|
+
const note = formatOmittedChanges(countOmittedChanges(rendered.slice(kept.length).map(({ unit }) => unit)));
|
|
5056
|
+
const candidate = note ? `${joinMrkdwnUnits(kept)}
|
|
5057
|
+
|
|
5058
|
+
_${note}_` : joinMrkdwnUnits(kept);
|
|
5059
|
+
if (candidate.length <= maxLength) {
|
|
5060
|
+
return candidate;
|
|
5061
|
+
}
|
|
5062
|
+
}
|
|
5063
|
+
return `${sliceSlackText(full, maxLength - 3)}...`;
|
|
5064
|
+
}
|
|
5065
|
+
function cutMrkdwn(text, budget) {
|
|
5066
|
+
if (budget <= 0) {
|
|
5067
|
+
return "";
|
|
5068
|
+
}
|
|
5069
|
+
let cut = sliceSlackText(text, budget);
|
|
5070
|
+
const linkStart = cut.lastIndexOf("<");
|
|
5071
|
+
if (linkStart > cut.lastIndexOf(">")) {
|
|
5072
|
+
cut = cut.slice(0, linkStart);
|
|
5073
|
+
}
|
|
5074
|
+
const entityStart = cut.lastIndexOf("&");
|
|
5075
|
+
if (entityStart > cut.lastIndexOf(";")) {
|
|
5076
|
+
cut = cut.slice(0, entityStart);
|
|
5077
|
+
}
|
|
5078
|
+
cut = cut.trimEnd();
|
|
5079
|
+
return /[^\s•◦>]/.test(cut) ? cut : "";
|
|
5080
|
+
}
|
|
5081
|
+
function hasStyle(style) {
|
|
5082
|
+
return Object.values(style).some(Boolean);
|
|
5083
|
+
}
|
|
5084
|
+
function pushText(elements, text, style) {
|
|
5085
|
+
if (!text) {
|
|
5086
|
+
return;
|
|
5087
|
+
}
|
|
5088
|
+
const previous = elements.at(-1);
|
|
5089
|
+
if (previous?.type === "text" && JSON.stringify(previous.style ?? {}) === JSON.stringify(style)) {
|
|
5090
|
+
previous.text += text;
|
|
5091
|
+
return;
|
|
5092
|
+
}
|
|
5093
|
+
elements.push(hasStyle(style) ? { type: "text", text, style: { ...style } } : { type: "text", text });
|
|
5094
|
+
}
|
|
5095
|
+
function pushLink(elements, url, text, style) {
|
|
5096
|
+
if (!isAbsoluteUrl(url)) {
|
|
5097
|
+
pushText(elements, text, style);
|
|
5098
|
+
return;
|
|
5099
|
+
}
|
|
5100
|
+
const link = { type: "link", url };
|
|
5101
|
+
if (text && text !== url) {
|
|
5102
|
+
link.text = text;
|
|
5103
|
+
}
|
|
5104
|
+
if (hasStyle(style)) {
|
|
5105
|
+
link.style = { ...style };
|
|
5106
|
+
}
|
|
5107
|
+
elements.push(link);
|
|
5108
|
+
}
|
|
5109
|
+
function appendInlineRichText(elements, node, style) {
|
|
5110
|
+
switch (node.type) {
|
|
5111
|
+
case "text":
|
|
5112
|
+
pushText(elements, collapseSoftBreaks(node.value), style);
|
|
5113
|
+
break;
|
|
5114
|
+
case "strong":
|
|
5115
|
+
node.children.forEach((child) => appendInlineRichText(elements, child, { ...style, bold: true }));
|
|
5116
|
+
break;
|
|
5117
|
+
case "emphasis":
|
|
5118
|
+
node.children.forEach((child) => appendInlineRichText(elements, child, { ...style, italic: true }));
|
|
5119
|
+
break;
|
|
5120
|
+
case "delete":
|
|
5121
|
+
node.children.forEach((child) => appendInlineRichText(elements, child, { ...style, strike: true }));
|
|
5122
|
+
break;
|
|
5123
|
+
case "inlineCode":
|
|
5124
|
+
pushText(elements, node.value, { ...style, code: true });
|
|
5125
|
+
break;
|
|
5126
|
+
case "break":
|
|
5127
|
+
pushText(elements, "\n", style);
|
|
5128
|
+
break;
|
|
5129
|
+
case "link":
|
|
5130
|
+
pushLink(elements, node.url, toPlainText(node), style);
|
|
5131
|
+
break;
|
|
5132
|
+
case "image":
|
|
5133
|
+
pushLink(elements, node.url, getAltText(node), style);
|
|
5134
|
+
break;
|
|
5135
|
+
case "linkReference":
|
|
5136
|
+
node.children.forEach((child) => appendInlineRichText(elements, child, style));
|
|
5137
|
+
break;
|
|
5138
|
+
case "imageReference":
|
|
5139
|
+
pushText(elements, getAltText(node), style);
|
|
5140
|
+
break;
|
|
5141
|
+
case "html":
|
|
5142
|
+
pushText(elements, node.value, style);
|
|
5143
|
+
break;
|
|
5144
|
+
case "footnoteReference":
|
|
5145
|
+
pushText(elements, `[^${node.identifier}]`, style);
|
|
5146
|
+
break;
|
|
5147
|
+
}
|
|
5148
|
+
}
|
|
5149
|
+
function renderInlineRichText(nodes, style = {}, prefix = "") {
|
|
5150
|
+
const elements = [];
|
|
5151
|
+
pushText(elements, prefix, style);
|
|
5152
|
+
nodes.forEach((node) => appendInlineRichText(elements, node, style));
|
|
5153
|
+
return elements;
|
|
5154
|
+
}
|
|
5155
|
+
function ensureNotEmpty(elements) {
|
|
5156
|
+
return elements.length > 0 ? elements : [{ type: "text", text: " " }];
|
|
5157
|
+
}
|
|
5158
|
+
function preformatted(value) {
|
|
5159
|
+
return value ? [{ type: "rich_text_preformatted", elements: [{ type: "text", text: value }] }] : [];
|
|
5160
|
+
}
|
|
5161
|
+
function quote(elements) {
|
|
5162
|
+
return elements.length > 0 ? [{ type: "rich_text_quote", elements }] : [];
|
|
5163
|
+
}
|
|
5164
|
+
function blockquoteInline(nodes) {
|
|
5165
|
+
const elements = [];
|
|
5166
|
+
nodes.forEach((node, index) => {
|
|
5167
|
+
if (index > 0) {
|
|
5168
|
+
pushText(elements, "\n", {});
|
|
5169
|
+
}
|
|
5170
|
+
if (node.type === "paragraph" || node.type === "heading") {
|
|
5171
|
+
node.children.forEach((child) => appendInlineRichText(elements, child, node.type === "heading" ? { bold: true } : {}));
|
|
5172
|
+
} else {
|
|
5173
|
+
pushText(elements, toPlainText(node), {});
|
|
5174
|
+
}
|
|
5175
|
+
});
|
|
5176
|
+
return elements;
|
|
5177
|
+
}
|
|
5178
|
+
function renderBodyBlockRichText(node) {
|
|
5179
|
+
switch (node.type) {
|
|
5180
|
+
case "paragraph":
|
|
5181
|
+
return quote(renderInlineRichText(node.children));
|
|
5182
|
+
case "heading":
|
|
5183
|
+
return [{ type: "rich_text_section", elements: ensureNotEmpty(renderInlineRichText(node.children, { bold: true })) }];
|
|
5184
|
+
case "blockquote":
|
|
5185
|
+
return quote(blockquoteInline(node.children));
|
|
5186
|
+
case "code":
|
|
5187
|
+
return preformatted(node.value);
|
|
5188
|
+
case "html":
|
|
5189
|
+
return quote(renderInlineRichText([], {}, node.value));
|
|
5190
|
+
case "table":
|
|
5191
|
+
return preformatted(tableToPlainText(node));
|
|
5192
|
+
default:
|
|
5193
|
+
return [];
|
|
5194
|
+
}
|
|
5195
|
+
}
|
|
5196
|
+
function renderListItemRichText(item, list, index, indent) {
|
|
5197
|
+
const { head, body } = getListItemParts(item);
|
|
5198
|
+
const listElement = {
|
|
5199
|
+
type: "rich_text_list",
|
|
5200
|
+
style: list.ordered ? "ordered" : "bullet",
|
|
5201
|
+
elements: [{ type: "rich_text_section", elements: ensureNotEmpty(renderInlineRichText(head, {}, getCheckboxPrefix(item))) }]
|
|
5202
|
+
};
|
|
5203
|
+
if (indent > 0) {
|
|
5204
|
+
listElement.indent = Math.min(indent, SLACK_MAX_LIST_INDENT);
|
|
5205
|
+
}
|
|
5206
|
+
const offset = list.ordered ? getListItemNumber(list, index) - 1 : 0;
|
|
5207
|
+
if (offset > 0) {
|
|
5208
|
+
listElement.offset = offset;
|
|
5209
|
+
}
|
|
5210
|
+
const elements = [listElement];
|
|
5211
|
+
for (const child of body) {
|
|
5212
|
+
if (child.type === "list") {
|
|
5213
|
+
child.children.forEach((subItem, subIndex) => elements.push(...renderListItemRichText(subItem, child, subIndex, indent + 1)));
|
|
5214
|
+
} else {
|
|
5215
|
+
elements.push(...renderBodyBlockRichText(child));
|
|
5216
|
+
}
|
|
5217
|
+
}
|
|
5218
|
+
return elements;
|
|
5219
|
+
}
|
|
5220
|
+
function renderUnitRichText(unit) {
|
|
5221
|
+
if (unit.type === "heading") {
|
|
5222
|
+
const elements = renderInlineRichText(unit.node.children, { bold: true });
|
|
5223
|
+
return elements.length > 0 ? [{ type: "rich_text_section", elements }] : [];
|
|
5224
|
+
}
|
|
5225
|
+
if (unit.type === "item") {
|
|
5226
|
+
return renderListItemRichText(unit.node, unit.list, unit.index, 0);
|
|
5227
|
+
}
|
|
5228
|
+
const node = unit.node;
|
|
5229
|
+
switch (node.type) {
|
|
5230
|
+
case "paragraph":
|
|
5231
|
+
return [{ type: "rich_text_section", elements: ensureNotEmpty(renderInlineRichText(node.children)) }];
|
|
5232
|
+
case "blockquote":
|
|
5233
|
+
return quote(blockquoteInline(node.children));
|
|
5234
|
+
case "code":
|
|
5235
|
+
return preformatted(node.value);
|
|
5236
|
+
case "html":
|
|
5237
|
+
return [{ type: "rich_text_section", elements: ensureNotEmpty(renderInlineRichText([], {}, node.value)) }];
|
|
5238
|
+
case "table":
|
|
5239
|
+
return preformatted(tableToPlainText(node));
|
|
5240
|
+
default:
|
|
5241
|
+
return [];
|
|
5242
|
+
}
|
|
5243
|
+
}
|
|
5244
|
+
function inlineLength(elements) {
|
|
5245
|
+
return elements.reduce((total, element) => total + (element.type === "text" ? element.text.length : (element.text ?? element.url).length), 0);
|
|
5246
|
+
}
|
|
5247
|
+
function elementLength(element) {
|
|
5248
|
+
return richTextToPlainText({ elements: [element] }).length + 1;
|
|
5249
|
+
}
|
|
5250
|
+
function blocksLength(blocks) {
|
|
5251
|
+
return blocks.map(richTextToPlainText).join("\n\n").length;
|
|
5252
|
+
}
|
|
5253
|
+
function isSameListLevel(previous, element) {
|
|
5254
|
+
return previous?.type === "rich_text_list" && element.type === "rich_text_list" && previous.style === element.style && (previous.indent ?? 0) === (element.indent ?? 0);
|
|
5255
|
+
}
|
|
5256
|
+
function isSameInlineContainer(previous, element) {
|
|
5257
|
+
return (element.type === "rich_text_section" || element.type === "rich_text_quote") && previous?.type === element.type;
|
|
5258
|
+
}
|
|
5259
|
+
function appendElement(elements, element) {
|
|
5260
|
+
const previous = elements.at(-1);
|
|
5261
|
+
if (isSameListLevel(previous, element) && element.type === "rich_text_list") {
|
|
5262
|
+
previous.elements.push(...structuredClone(element.elements));
|
|
5263
|
+
return;
|
|
5264
|
+
}
|
|
5265
|
+
if (isSameInlineContainer(previous, element) && element.type !== "rich_text_list") {
|
|
5266
|
+
const last = previous.elements.at(-1);
|
|
5267
|
+
const separatorStyle = last?.type === "text" ? last.style ?? {} : {};
|
|
5268
|
+
pushText(previous.elements, element.type === "rich_text_quote" ? "\n\n" : "\n", separatorStyle);
|
|
5269
|
+
for (const inline of structuredClone(element.elements)) {
|
|
5270
|
+
if (inline.type === "text") {
|
|
5271
|
+
pushText(previous.elements, inline.text, inline.style ?? {});
|
|
5272
|
+
} else {
|
|
5273
|
+
previous.elements.push(inline);
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
return;
|
|
5277
|
+
}
|
|
5278
|
+
elements.push(structuredClone(element));
|
|
5279
|
+
}
|
|
5280
|
+
function assembleRichTextBlocks(rendered, blockMaxLength) {
|
|
5281
|
+
const blocks = [];
|
|
5282
|
+
let current;
|
|
5283
|
+
let currentLength = 0;
|
|
5284
|
+
let currentHasContent = false;
|
|
5285
|
+
for (const { unit, output, length } of rendered) {
|
|
5286
|
+
const startsSection = unit.type === "heading" && currentHasContent;
|
|
5287
|
+
const overflows = currentHasContent && currentLength + length > blockMaxLength;
|
|
5288
|
+
if (!current || startsSection || overflows) {
|
|
5289
|
+
current = { type: "rich_text", elements: [] };
|
|
5290
|
+
blocks.push(current);
|
|
5291
|
+
currentLength = 0;
|
|
5292
|
+
currentHasContent = false;
|
|
5293
|
+
}
|
|
5294
|
+
output.forEach((element) => appendElement(current.elements, element));
|
|
5295
|
+
currentLength += length;
|
|
5296
|
+
currentHasContent ||= unit.type !== "heading";
|
|
5297
|
+
}
|
|
5298
|
+
return blocks;
|
|
5299
|
+
}
|
|
5300
|
+
function takeInlineCharacters(elements, count) {
|
|
5301
|
+
const kept = [];
|
|
5302
|
+
let remaining = count;
|
|
5303
|
+
for (const element of elements) {
|
|
5304
|
+
const length = inlineLength([element]);
|
|
5305
|
+
if (length <= remaining) {
|
|
5306
|
+
kept.push(structuredClone(element));
|
|
5307
|
+
remaining -= length;
|
|
5308
|
+
continue;
|
|
5309
|
+
}
|
|
5310
|
+
if (element.type === "text") {
|
|
5311
|
+
pushText(kept, sliceSlackText(element.text, remaining), element.style ?? {});
|
|
5312
|
+
}
|
|
5313
|
+
break;
|
|
5314
|
+
}
|
|
5315
|
+
return kept;
|
|
5316
|
+
}
|
|
5317
|
+
function takeElementCharacters(element, count) {
|
|
5318
|
+
const inlines = element.type === "rich_text_list" ? element.elements[0].elements : element.elements;
|
|
5319
|
+
const elements = takeInlineCharacters(inlines, count);
|
|
5320
|
+
if (elements.length === 0) {
|
|
5321
|
+
return void 0;
|
|
5322
|
+
}
|
|
5323
|
+
return element.type === "rich_text_list" ? { ...element, elements: [{ type: "rich_text_section", elements }] } : { ...element, elements };
|
|
5324
|
+
}
|
|
5325
|
+
function withEllipsis(element) {
|
|
5326
|
+
const result = structuredClone(element);
|
|
5327
|
+
const inlines = result.type === "rich_text_list" ? result.elements.at(-1).elements : result.elements;
|
|
5328
|
+
const last = inlines.at(-1);
|
|
5329
|
+
if (last?.type === "text") {
|
|
5330
|
+
last.text = last.text.trimEnd();
|
|
5331
|
+
if (!last.text) {
|
|
5332
|
+
inlines.pop();
|
|
5333
|
+
}
|
|
5334
|
+
}
|
|
5335
|
+
pushText(inlines, ELLIPSIS, {});
|
|
5336
|
+
return result;
|
|
5337
|
+
}
|
|
5338
|
+
function cutRichTextElement(element, budget) {
|
|
5339
|
+
const characters = element.type === "rich_text_list" ? element.elements.reduce((total, section) => total + inlineLength(section.elements), 0) : inlineLength(element.elements);
|
|
5340
|
+
let best;
|
|
5341
|
+
let low = 1;
|
|
5342
|
+
let high = characters - 1;
|
|
5343
|
+
while (low <= high) {
|
|
5344
|
+
const count = Math.floor((low + high) / 2);
|
|
5345
|
+
const taken = takeElementCharacters(element, count);
|
|
5346
|
+
const candidate = taken && withEllipsis(taken);
|
|
5347
|
+
if (candidate && elementLength(candidate) <= budget) {
|
|
5348
|
+
best = candidate;
|
|
5349
|
+
low = count + 1;
|
|
5350
|
+
} else {
|
|
5351
|
+
high = count - 1;
|
|
5352
|
+
}
|
|
5353
|
+
}
|
|
5354
|
+
return best;
|
|
5355
|
+
}
|
|
5356
|
+
function cutRichTextElements(elements, budget) {
|
|
5357
|
+
const kept = [];
|
|
5358
|
+
let remaining = budget;
|
|
5359
|
+
for (const element of elements) {
|
|
5360
|
+
const length = elementLength(element);
|
|
5361
|
+
if (length + ELLIPSIS.length <= remaining) {
|
|
5362
|
+
kept.push(structuredClone(element));
|
|
5363
|
+
remaining -= length;
|
|
5364
|
+
continue;
|
|
5365
|
+
}
|
|
5366
|
+
const cut = cutRichTextElement(element, remaining);
|
|
5367
|
+
if (cut) {
|
|
5368
|
+
return [...kept, cut];
|
|
5369
|
+
}
|
|
5370
|
+
break;
|
|
5371
|
+
}
|
|
5372
|
+
const last = kept.pop();
|
|
5373
|
+
return last ? [...kept, withEllipsis(last)] : [];
|
|
5374
|
+
}
|
|
5375
|
+
function markdownToSlackRichText(markdown, {
|
|
5376
|
+
maxLength,
|
|
5377
|
+
maxBlocks = SLACK_MAX_BLOCKS,
|
|
5378
|
+
maxPayloadLength,
|
|
5379
|
+
blockMaxLength = SLACK_RICH_TEXT_BLOCK_MAX_LENGTH
|
|
5380
|
+
} = {}) {
|
|
5381
|
+
if (!markdown.trim() || maxBlocks < 1) {
|
|
5382
|
+
return [];
|
|
5383
|
+
}
|
|
5384
|
+
const rendered = splitIntoUnits(parseMarkdown(markdown)).map((unit) => {
|
|
5385
|
+
const output = renderUnitRichText(unit);
|
|
5386
|
+
return { unit, output, length: output.reduce((total, element) => total + elementLength(element), 0) };
|
|
5387
|
+
}).filter(({ output }) => output.length > 0);
|
|
5388
|
+
const totalLength = (units) => units.reduce((total, { length }) => total + length, 0);
|
|
5389
|
+
const fits = (candidate) => candidate.length <= maxBlocks && (maxLength === void 0 || blocksLength(candidate) <= maxLength) && (maxPayloadLength === void 0 || JSON.stringify(candidate).length <= maxPayloadLength);
|
|
5390
|
+
const blocks = assembleRichTextBlocks(rendered, blockMaxLength);
|
|
5391
|
+
if (fits(blocks)) {
|
|
5392
|
+
return blocks;
|
|
5393
|
+
}
|
|
5394
|
+
const truncate = (chars) => {
|
|
5395
|
+
let remaining = chars;
|
|
5396
|
+
let index = 0;
|
|
5397
|
+
const kept = [];
|
|
5398
|
+
for (; index < rendered.length && rendered[index].length <= remaining; index++) {
|
|
5399
|
+
kept.push(rendered[index]);
|
|
5400
|
+
remaining -= rendered[index].length;
|
|
5401
|
+
}
|
|
5402
|
+
const next = rendered[index];
|
|
5403
|
+
const cut = next && next.unit.type !== "heading" ? cutRichTextElements(next.output, remaining) : [];
|
|
5404
|
+
const visible = cut.length > 0 ? [...kept, { unit: next.unit, output: cut, length: cut.reduce((total, element) => total + elementLength(element), 0) }] : dropTrailingHeadings(kept);
|
|
5405
|
+
const note = formatOmittedChanges(countOmittedChanges(rendered.slice(cut.length > 0 ? index + 1 : visible.length).map(({ unit }) => unit)));
|
|
5406
|
+
const truncated = assembleRichTextBlocks(visible, blockMaxLength);
|
|
5407
|
+
if (note) {
|
|
5408
|
+
const noteSection = { type: "rich_text_section", elements: [{ type: "text", text: note, style: { italic: true } }] };
|
|
5409
|
+
const last = truncated.at(-1);
|
|
5410
|
+
if (last) {
|
|
5411
|
+
last.elements.push(noteSection);
|
|
5412
|
+
} else {
|
|
5413
|
+
truncated.push({ type: "rich_text", elements: [noteSection] });
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
return truncated;
|
|
5417
|
+
};
|
|
5418
|
+
let best = truncate(0);
|
|
5419
|
+
let low = 1;
|
|
5420
|
+
let high = totalLength(rendered) - 1;
|
|
5421
|
+
while (low <= high) {
|
|
5422
|
+
const chars = Math.floor((low + high) / 2);
|
|
5423
|
+
const candidate = truncate(chars);
|
|
5424
|
+
if (fits(candidate)) {
|
|
5425
|
+
best = candidate;
|
|
5426
|
+
low = chars + 1;
|
|
5427
|
+
} else {
|
|
5428
|
+
high = chars - 1;
|
|
5429
|
+
}
|
|
5430
|
+
}
|
|
5431
|
+
return best;
|
|
5432
|
+
}
|
|
5433
|
+
function inlineToPlainText(elements) {
|
|
5434
|
+
return elements.map((element) => element.type === "text" ? element.text : element.text ?? element.url).join("");
|
|
5435
|
+
}
|
|
5436
|
+
function richTextToPlainText(block) {
|
|
5437
|
+
return block.elements.map((element) => {
|
|
5438
|
+
switch (element.type) {
|
|
5439
|
+
case "rich_text_list":
|
|
5440
|
+
return element.elements.map((section, index) => {
|
|
5441
|
+
const indent = " ".repeat(element.indent ?? 0);
|
|
5442
|
+
const marker = element.style === "ordered" ? `${(element.offset ?? 0) + index + 1}.` : "\u2022";
|
|
5443
|
+
return `${indent}${marker} ${inlineToPlainText(section.elements)}`;
|
|
5444
|
+
}).join("\n");
|
|
5445
|
+
case "rich_text_quote":
|
|
5446
|
+
return quoteMrkdwn(inlineToPlainText(element.elements));
|
|
5447
|
+
case "rich_text_preformatted":
|
|
5448
|
+
return `\`\`\`
|
|
5449
|
+
${inlineToPlainText(element.elements)}
|
|
5450
|
+
\`\`\``;
|
|
5451
|
+
default:
|
|
5452
|
+
return inlineToPlainText(element.elements);
|
|
5453
|
+
}
|
|
5454
|
+
}).join("\n");
|
|
5455
|
+
}
|
|
5456
|
+
|
|
4791
5457
|
function extractChangelogSummary(changelog, { stripBoldMarkers = false, maxLength } = {}) {
|
|
4792
5458
|
if (changelog.trim() === "") {
|
|
4793
5459
|
return "";
|
|
@@ -4838,14 +5504,12 @@ function getSlackToken(options) {
|
|
|
4838
5504
|
function getSlackWebhookUrl(options) {
|
|
4839
5505
|
return options.socialWebhookUrl || process.env.RELIZY_SLACK_WEBHOOK_URL || process.env.SLACK_WEBHOOK_URL || null;
|
|
4840
5506
|
}
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
}
|
|
4848
|
-
return formatted;
|
|
5507
|
+
const SLACK_SUMMARY_MAX_LENGTH = 2500;
|
|
5508
|
+
function resolveSlackPostMaxLength({ changelogFormat = "summary", postMaxLength }) {
|
|
5509
|
+
return postMaxLength ?? (changelogFormat === "full" ? void 0 : SLACK_SUMMARY_MAX_LENGTH);
|
|
5510
|
+
}
|
|
5511
|
+
function formatChangelogForSlack(changelog, maxLength = SLACK_SUMMARY_MAX_LENGTH) {
|
|
5512
|
+
return markdownToSlackMrkdwn(changelog, { maxLength });
|
|
4849
5513
|
}
|
|
4850
5514
|
function formatPackagesForSlack(packages) {
|
|
4851
5515
|
if (packages.length === 0) {
|
|
@@ -4855,75 +5519,96 @@ function formatPackagesForSlack(packages) {
|
|
|
4855
5519
|
(pkg) => pkg.hasTransition ? `\u2022 *${pkg.name}* : \`${pkg.oldVersion}\` \u2192 \`${pkg.newVersion}\`` : `\u2022 *${pkg.name}* : \`${pkg.version}\``
|
|
4856
5520
|
).join("\n");
|
|
4857
5521
|
}
|
|
4858
|
-
function
|
|
5522
|
+
function truncateSlackText(text, maxLength) {
|
|
5523
|
+
return text.length > maxLength ? `${sliceSlackText(text, maxLength - 1)}\u2026` : text;
|
|
5524
|
+
}
|
|
5525
|
+
function formatListSection(title, lines, noun) {
|
|
5526
|
+
const render = (count) => {
|
|
5527
|
+
const omitted = lines.length - count;
|
|
5528
|
+
const kept = lines.slice(0, count);
|
|
5529
|
+
if (omitted > 0) {
|
|
5530
|
+
kept.push(`_\u2026 and ${omitted} more ${noun}_`);
|
|
5531
|
+
}
|
|
5532
|
+
return `${title}
|
|
5533
|
+
|
|
5534
|
+
${kept.join("\n")}`;
|
|
5535
|
+
};
|
|
5536
|
+
for (let count = lines.length; count > 0; count--) {
|
|
5537
|
+
const text = render(count);
|
|
5538
|
+
if (text.length <= SLACK_SECTION_MAX_LENGTH) {
|
|
5539
|
+
return text;
|
|
5540
|
+
}
|
|
5541
|
+
}
|
|
5542
|
+
return truncateSlackText(render(0), SLACK_SECTION_MAX_LENGTH);
|
|
5543
|
+
}
|
|
5544
|
+
function renderSlackTemplate({ template, projectName, version, changelog, contributorsLine, packagesLine, releaseUrl, changelogUrl }) {
|
|
5545
|
+
let message = template.replace("{{projectName}}", projectName).replace("{{newVersion}}", version).replace("{{changelog}}", changelog).replace("{{contributors}}", contributorsLine).replace("{{packages}}", packagesLine);
|
|
5546
|
+
if (releaseUrl) {
|
|
5547
|
+
message = message.replace("{{releaseUrl}}", releaseUrl);
|
|
5548
|
+
} else {
|
|
5549
|
+
message = message.replace("{{releaseUrl}}", "").trim();
|
|
5550
|
+
}
|
|
5551
|
+
if (changelogUrl) {
|
|
5552
|
+
message = message.replace("{{changelogUrl}}", changelogUrl);
|
|
5553
|
+
} else {
|
|
5554
|
+
message = message.replace("{{changelogUrl}}", "").trim();
|
|
5555
|
+
}
|
|
5556
|
+
return message;
|
|
5557
|
+
}
|
|
5558
|
+
function formatSlackMessage({ projectName, version, changelog, releaseUrl, changelogUrl, template, contributors = [], packages = [], postMaxLength, changelogFormat = "summary" }) {
|
|
4859
5559
|
const contributorsLine = contributors.length > 0 ? contributors.map((n) => `\u2022 ${n}`).join("\n") : "";
|
|
4860
5560
|
const packagesLine = formatPackagesForSlack(packages);
|
|
5561
|
+
const maxLength = resolveSlackPostMaxLength({ changelogFormat, postMaxLength });
|
|
4861
5562
|
if (template) {
|
|
4862
|
-
const
|
|
4863
|
-
let
|
|
4864
|
-
if (
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
message = message.replace("{{changelogUrl}}", changelogUrl);
|
|
5563
|
+
const templateContext = { template, projectName, version, contributorsLine, packagesLine, releaseUrl, changelogUrl };
|
|
5564
|
+
let changelogText;
|
|
5565
|
+
if (changelogFormat === "full") {
|
|
5566
|
+
const overhead = renderSlackTemplate({ ...templateContext, changelog: "\0" }).length - 1;
|
|
5567
|
+
changelogText = markdownToSlackMrkdwn(changelog, {
|
|
5568
|
+
maxLength: Math.max(0, Math.min(maxLength ?? Infinity, SLACK_SECTION_MAX_LENGTH - overhead)),
|
|
5569
|
+
cutCommits: true
|
|
5570
|
+
});
|
|
4871
5571
|
} else {
|
|
4872
|
-
|
|
5572
|
+
changelogText = extractChangelogSummary(changelog, { maxLength });
|
|
4873
5573
|
}
|
|
4874
5574
|
return [
|
|
4875
5575
|
{
|
|
4876
5576
|
type: "section",
|
|
4877
5577
|
text: {
|
|
4878
5578
|
type: "mrkdwn",
|
|
4879
|
-
text:
|
|
5579
|
+
text: truncateSlackText(renderSlackTemplate({ ...templateContext, changelog: changelogText }), SLACK_SECTION_MAX_LENGTH)
|
|
4880
5580
|
}
|
|
4881
5581
|
}
|
|
4882
5582
|
];
|
|
4883
5583
|
}
|
|
4884
|
-
const
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
emoji: true
|
|
4891
|
-
}
|
|
5584
|
+
const headerBlock = {
|
|
5585
|
+
type: "header",
|
|
5586
|
+
text: {
|
|
5587
|
+
type: "plain_text",
|
|
5588
|
+
text: truncateSlackText(`\u{1F4E3} ${projectName} ${version} is out!`, SLACK_HEADER_MAX_LENGTH),
|
|
5589
|
+
emoji: true
|
|
4892
5590
|
}
|
|
4893
|
-
|
|
4894
|
-
const
|
|
4895
|
-
if (formattedChangelog) {
|
|
4896
|
-
blocks.push({
|
|
4897
|
-
type: "section",
|
|
4898
|
-
text: {
|
|
4899
|
-
type: "mrkdwn",
|
|
4900
|
-
text: formattedChangelog
|
|
4901
|
-
}
|
|
4902
|
-
});
|
|
4903
|
-
}
|
|
5591
|
+
};
|
|
5592
|
+
const trailingBlocks = [];
|
|
4904
5593
|
if (packages.length > 0) {
|
|
4905
|
-
|
|
5594
|
+
trailingBlocks.push({
|
|
4906
5595
|
type: "section",
|
|
4907
5596
|
text: {
|
|
4908
5597
|
type: "mrkdwn",
|
|
4909
|
-
text:
|
|
4910
|
-
|
|
4911
|
-
${packagesLine}`
|
|
5598
|
+
text: formatListSection("*\u{1F4E6} Packages*", packagesLine.split("\n"), "packages")
|
|
4912
5599
|
}
|
|
4913
5600
|
});
|
|
4914
5601
|
}
|
|
4915
5602
|
if (contributors.length > 0) {
|
|
4916
|
-
|
|
5603
|
+
trailingBlocks.push({
|
|
4917
5604
|
type: "section",
|
|
4918
5605
|
text: {
|
|
4919
5606
|
type: "mrkdwn",
|
|
4920
|
-
text:
|
|
4921
|
-
|
|
4922
|
-
${contributorsLine}`
|
|
5607
|
+
text: formatListSection("*\u2764\uFE0F Contributors*", contributorsLine.split("\n"), "contributors")
|
|
4923
5608
|
}
|
|
4924
5609
|
});
|
|
4925
5610
|
}
|
|
4926
|
-
|
|
5611
|
+
trailingBlocks.push({
|
|
4927
5612
|
type: "divider"
|
|
4928
5613
|
});
|
|
4929
5614
|
const elements = [];
|
|
@@ -4952,19 +5637,39 @@ ${contributorsLine}`
|
|
|
4952
5637
|
});
|
|
4953
5638
|
}
|
|
4954
5639
|
if (elements.length > 0) {
|
|
4955
|
-
|
|
5640
|
+
trailingBlocks.push({
|
|
4956
5641
|
type: "actions",
|
|
4957
5642
|
elements
|
|
4958
5643
|
});
|
|
4959
5644
|
}
|
|
4960
|
-
|
|
5645
|
+
const changelogBlocks = [];
|
|
5646
|
+
if (changelogFormat === "full") {
|
|
5647
|
+
const surroundingBlocks = [headerBlock, ...trailingBlocks];
|
|
5648
|
+
changelogBlocks.push(...markdownToSlackRichText(changelog, {
|
|
5649
|
+
maxLength,
|
|
5650
|
+
maxBlocks: SLACK_MAX_BLOCKS - surroundingBlocks.length,
|
|
5651
|
+
maxPayloadLength: SLACK_RICH_TEXT_MESSAGE_MAX_LENGTH - JSON.stringify(surroundingBlocks).length
|
|
5652
|
+
}));
|
|
5653
|
+
} else {
|
|
5654
|
+
const formattedChangelog = formatChangelogForSlack(changelog, Math.min(postMaxLength ?? SLACK_SUMMARY_MAX_LENGTH, SLACK_SECTION_MAX_LENGTH));
|
|
5655
|
+
if (formattedChangelog) {
|
|
5656
|
+
changelogBlocks.push({
|
|
5657
|
+
type: "section",
|
|
5658
|
+
text: {
|
|
5659
|
+
type: "mrkdwn",
|
|
5660
|
+
text: formattedChangelog
|
|
5661
|
+
}
|
|
5662
|
+
});
|
|
5663
|
+
}
|
|
5664
|
+
}
|
|
5665
|
+
return [headerBlock, ...changelogBlocks, ...trailingBlocks];
|
|
4961
5666
|
}
|
|
4962
5667
|
function mapWebhookError(status, body) {
|
|
4963
5668
|
if (status === 404 || body.includes("no_service")) {
|
|
4964
5669
|
return "The webhook URL is invalid or has been deactivated. Regenerate it in your Slack app settings.";
|
|
4965
5670
|
}
|
|
4966
5671
|
if (body.includes("invalid_payload")) {
|
|
4967
|
-
return
|
|
5672
|
+
return 'The message payload was rejected by Slack (likely exceeds its block limits). Lower social.slack.postMaxLength or switch social.slack.changelogFormat to "summary".';
|
|
4968
5673
|
}
|
|
4969
5674
|
if (body.includes("channel_not_found")) {
|
|
4970
5675
|
return "The channel bound to this webhook was archived or removed. Create a new webhook.";
|
|
@@ -5036,6 +5741,7 @@ async function postReleaseToSlack({
|
|
|
5036
5741
|
contributors,
|
|
5037
5742
|
packages,
|
|
5038
5743
|
postMaxLength,
|
|
5744
|
+
changelogFormat,
|
|
5039
5745
|
dryRun = false
|
|
5040
5746
|
}) {
|
|
5041
5747
|
const useWebhook = Boolean(webhookUrl);
|
|
@@ -5056,12 +5762,13 @@ async function postReleaseToSlack({
|
|
|
5056
5762
|
changelogUrl,
|
|
5057
5763
|
contributors,
|
|
5058
5764
|
packages,
|
|
5059
|
-
postMaxLength
|
|
5765
|
+
postMaxLength,
|
|
5766
|
+
changelogFormat
|
|
5060
5767
|
});
|
|
5061
5768
|
const fallbackText = `${projectName} ${version} is out!`;
|
|
5062
5769
|
logger.debug(`Message blocks (${blocks.length} blocks)`);
|
|
5063
5770
|
if (dryRun) {
|
|
5064
|
-
const preview = blocks.
|
|
5771
|
+
const preview = blocks.map((b) => b.type === "rich_text" ? richTextToPlainText(b) : b.text?.text ?? "").filter(Boolean).join("\n\n");
|
|
5065
5772
|
const target = useWebhook ? "webhook" : `channel: ${channel}`;
|
|
5066
5773
|
logger.box(`[dry-run] Slack Post Preview (${target})
|
|
5067
5774
|
|
|
@@ -6648,7 +7355,8 @@ async function handleSlackPost({
|
|
|
6648
7355
|
template,
|
|
6649
7356
|
contributors,
|
|
6650
7357
|
packages,
|
|
6651
|
-
postMaxLength: slackConfig.postMaxLength
|
|
7358
|
+
postMaxLength: slackConfig.postMaxLength,
|
|
7359
|
+
changelogFormat: slackConfig.changelogFormat ?? "summary",
|
|
6652
7360
|
dryRun
|
|
6653
7361
|
});
|
|
6654
7362
|
await executeHook("success:slack", config, dryRun);
|
|
@@ -6679,6 +7387,9 @@ async function social(options = {}) {
|
|
|
6679
7387
|
}
|
|
6680
7388
|
});
|
|
6681
7389
|
applyAIOverride(config, options.ai);
|
|
7390
|
+
if (options.slackFormat) {
|
|
7391
|
+
config.social.slack = { ...config.social.slack, changelogFormat: options.slackFormat };
|
|
7392
|
+
}
|
|
6682
7393
|
logger.debug(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
|
|
6683
7394
|
await socialSafetyCheck({ config });
|
|
6684
7395
|
if (!config.release.social && !config.social?.twitter?.enabled && !config.social?.slack?.enabled) {
|
|
@@ -6837,13 +7548,16 @@ async function social(options = {}) {
|
|
|
6837
7548
|
${twitterChangelog}`);
|
|
6838
7549
|
}
|
|
6839
7550
|
}
|
|
6840
|
-
|
|
7551
|
+
const slackChangelogFormat = config.social?.slack?.changelogFormat ?? "summary";
|
|
7552
|
+
const slackBody = slackChangelogFormat === "full" ? richBody : minifiedBody;
|
|
7553
|
+
let slackChangelog = slackBody;
|
|
6841
7554
|
if (hasContent && slackWillPost && isAISocialEnabled(config, "slack")) {
|
|
6842
7555
|
slackChangelog = await generateAISocialChangelog({
|
|
6843
7556
|
config,
|
|
6844
7557
|
rawBody: richBody,
|
|
6845
|
-
fallbackBody:
|
|
6846
|
-
platform: "slack"
|
|
7558
|
+
fallbackBody: slackBody,
|
|
7559
|
+
platform: "slack",
|
|
7560
|
+
maxLength: resolveSlackPostMaxLength({ changelogFormat: slackChangelogFormat, postMaxLength: config.social.slack.postMaxLength })
|
|
6847
7561
|
});
|
|
6848
7562
|
if (dryRun) {
|
|
6849
7563
|
logger.box("[dry-run] AI Slack preview", slackChangelog);
|
|
@@ -7221,8 +7935,9 @@ async function release(options = {}) {
|
|
|
7221
7935
|
bumpResult,
|
|
7222
7936
|
dryRun,
|
|
7223
7937
|
logLevel: config.logLevel,
|
|
7224
|
-
safetyCheck: false
|
|
7938
|
+
safetyCheck: false,
|
|
7225
7939
|
// Already checked in releaseSafetyCheck
|
|
7940
|
+
slackFormat: options.slackFormat
|
|
7226
7941
|
});
|
|
7227
7942
|
} else {
|
|
7228
7943
|
logger.info("Skipping social media posts (--no-social or no social media enabled)");
|
|
@@ -7267,4 +7982,4 @@ async function release(options = {}) {
|
|
|
7267
7982
|
}
|
|
7268
7983
|
}
|
|
7269
7984
|
|
|
7270
|
-
export {
|
|
7985
|
+
export { getBumpedPackageIndependently as $, determineReleaseType as A, determineSemverChange as B, executeBuildCmd as C, executeFormatCmd as D, executeHook as E, expandPackagesToBumpWithDependents as F, extractChangelogSummary as G, extractVersionFromPackageTag$1 as H, extractVersionFromRepoTag as I, extractVersionFromTag as J, fetchGitTags as K, filterOutPrivatePackages as L, filterPrivatePackagesUnlessIncluded as M, NEW_PACKAGE_MARKER as N, findGitHubPR as O, PR_COMMENT_MARKER as P, findGitLabMR as Q, findReachableCommitBySubject as R, SLACK_HEADER_MAX_LENGTH as S, formatChangelogForSlack as T, formatPackagesForSlack as U, formatSlackMessage as V, formatTweetMessage as W, generateChangelog as X, generateMarkDown as Y, getAuthCommand as Z, getBumpedIndependentPackages as _, SLACK_MAX_BLOCKS as a, providerRelease as a$, getCIName as a0, getCanaryVersion as a1, getCommitChangedFiles as a2, getCommitSubject as a3, getCurrentGitBranch as a4, getCurrentGitRef as a5, getDefaultConfig as a6, getDependentsOf as a7, getFirstCommit as a8, getGitDiff as a9, gitlab as aA, globDirsAbsolute as aB, hasLernaJson as aC, isAncestor as aD, isBumpedPackage as aE, isChangedPreid as aF, isCommitOnlyInIgnoredPackages as aG, isGraduating as aH, isGraduatingToStableBetweenVersion as aI, isInCI as aJ, isPrerelease as aK, isPrereleaseReleaseType as aL, isStableReleaseType as aM, isTagVersionCompatibleWithCurrent as aN, isVersionAlreadyPublishedError as aO, isVersionPublished as aP, listTags as aQ, loadRelizyConfig as aR, markdownToSlackMrkdwn as aS, markdownToSlackRichText as aT, mergeTypes as aU, parseChangelogMarkdown as aV, parseGitRemoteUrl as aW, postPrComment as aX, postReleaseToSlack as aY, postReleaseToTwitter as aZ, prComment as a_, getGitStatus as aa, getGitUpstream as ab, getIndependentTag as ac, getLastPackageTag as ad, getLastRepoTag as ae, getLastStableTag as af, getLastTag as ag, getModifiedReleaseFilePatterns as ah, getNewPackageCompareBase as ai, getNpmRegistry as aj, getPackageCommits as ak, getPackageDependencies as al, getPackageNewVersion as am, getPackages as an, getPackagesOrBumpedPackages as ao, getPackagesToPublishInIndependentMode as ap, getPackagesToPublishInSelectiveMode as aq, getPreid as ar, getReleaseTagNames as as, getReleaseUrl as at, getRootPackage as au, getShortCommitSha as av, getSlackToken as aw, getSlackWebhookUrl as ax, getTwitterCredentials as ay, github as az, SLACK_RICH_TEXT_BLOCK_MAX_LENGTH as b, providerReleaseSafetyCheck as b0, publish as b1, publishPackage as b2, publishSafetyCheck as b3, pushCommitAndTags as b4, pushTagForce as b5, readPackageJson as b6, readPackages as b7, reconcileFromTag as b8, redactSecrets as b9, release as ba, resetRewrittenTagCache as bb, resolveAllConfiguredRegistryTargets as bc, resolveIgnoredPackagePaths as bd, resolveRegistryTargetsForPackage as be, resolveRegistryTargetsForPackages as bf, resolveSlackPostMaxLength as bg, resolveTags as bh, retagAnnotatedLocal as bi, richTextToPlainText as bj, rollbackModifiedFiles as bk, shouldFilterPrereleaseTags as bl, sliceSlackText as bm, social as bn, socialSafetyCheck as bo, tagExists as bp, topologicalSort as bq, tryPostPrComment as br, updateLernaVersion as bs, withRegistryNpmrc as bt, writeChangelogToFile as bu, writeVersion as bv, SLACK_RICH_TEXT_MESSAGE_MAX_LENGTH as c, SLACK_SECTION_MAX_LENGTH as d, SLACK_SUMMARY_MAX_LENGTH as e, assertBranchUpToDateWithRemote as f, assertReleaseTagsAvailable as g, buildChangelogBody as h, buildCommentBody as i, buildCompareLink as j, buildContributors as k, buildReleaseSummary as l, bump as m, capReleaseTypeForZeroMajor as n, changelog as o, checkGitStatusIfDirty as p, collectContributorNames as q, collectPackageBumps as r, confirmBump as s, createCommitAndTags as t, createGitlabRelease as u, defineConfig as v, detectGitProvider as w, detectPackageManager as x, detectPullRequest as y, determinePublishTag as z };
|