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.
@@ -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
- postMaxLength: 2500,
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 false;
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
- if (contentLines.length === 0) {
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 = contentLines.map((line) => ` ${line}`).join("\n");
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 that are not in the input \u2014 if something is not mentioned, it does not exist.
3683
- Never include compare links, contributor lists, or release metadata \u2014 only the content provided.
3684
- Respond with the rewritten content only \u2014 no preamble, no explanation, no surrounding code fence, no <changelog> tag.
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 \u2014 focus on what changed for the user, not internal details.
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 \u2014 no markdown. A leading "#" becomes a hashtag on Twitter, so avoid it.
3694
- Hard maximum: {{maxLength}} characters \u2014 never exceed it.
3695
- Remove commit hashes (e.g. "aabf96b") and PR/issue references (e.g. "#123") \u2014 they are meaningless on Twitter.
3696
- If the input has one change, write one substantive sentence about it (ground it in the input, never invent).
3697
- If the input has multiple changes, surface 2 to 4 highlights.
3698
- You produce ONLY the changelog content \u2014 the outer template will add the project name, version, and URLs around it.
3699
- Tone: enthusiastic but not cringy. At most 1-2 emojis total.
3700
- Do not add hashtags unless the user explicitly supplies them.`;
3701
- const SLACK_PROMPT = `Format with Slack-compatible markdown: *bold*, _italic_, \`code\`, and "-" bullet lists.
3702
- Keep it concise \u2014 3 to 8 bullets maximum.
3703
- If any breaking changes are present, lead with them.
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 (config.ai?.extraGuidelines) {
3848
- parts.push(config.ai.extraGuidelines);
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
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
- function formatChangelogForSlack(changelog, maxLength = 2500) {
4842
- let formatted = changelog.replace(/^### (.+)$/gm, "*$1*").replace(/^## (.+)$/gm, "*$1*").replace(/^# (.+)$/gm, "*$1*").replace(/\*\*(.+?)\*\*/g, "*$1*").replace(/^([ \t]*)-[ \t]+/gm, "$1\u2022 ");
4843
- const linkPattern = /\[([^\]]*)]\(([^)]*)\)/g;
4844
- formatted = formatted.replace(linkPattern, (_, text, url) => `<${url}|${text}>`);
4845
- if (formatted.length > maxLength) {
4846
- formatted = `${formatted.substring(0, maxLength - 3)}...`;
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 formatSlackMessage({ projectName, version, changelog, releaseUrl, changelogUrl, template, contributors = [], packages = [], postMaxLength = 2500 }) {
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 summary = extractChangelogSummary(changelog, { maxLength: postMaxLength });
4863
- let message = template.replace("{{projectName}}", projectName).replace("{{newVersion}}", version).replace("{{changelog}}", summary).replace("{{contributors}}", contributorsLine).replace("{{packages}}", packagesLine);
4864
- if (releaseUrl) {
4865
- message = message.replace("{{releaseUrl}}", releaseUrl);
4866
- } else {
4867
- message = message.replace("{{releaseUrl}}", "").trim();
4868
- }
4869
- if (changelogUrl) {
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
- message = message.replace("{{changelogUrl}}", "").trim();
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: message
5579
+ text: truncateSlackText(renderSlackTemplate({ ...templateContext, changelog: changelogText }), SLACK_SECTION_MAX_LENGTH)
4880
5580
  }
4881
5581
  }
4882
5582
  ];
4883
5583
  }
4884
- const blocks = [
4885
- {
4886
- type: "header",
4887
- text: {
4888
- type: "plain_text",
4889
- text: `\u{1F4E3} ${projectName} ${version} is out!`,
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 formattedChangelog = formatChangelogForSlack(changelog, postMaxLength);
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
- blocks.push({
5594
+ trailingBlocks.push({
4906
5595
  type: "section",
4907
5596
  text: {
4908
5597
  type: "mrkdwn",
4909
- text: `*\u{1F4E6} Packages*
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
- blocks.push({
5603
+ trailingBlocks.push({
4917
5604
  type: "section",
4918
5605
  text: {
4919
5606
  type: "mrkdwn",
4920
- text: `*\u2764\uFE0F Contributors*
4921
-
4922
- ${contributorsLine}`
5607
+ text: formatListSection("*\u2764\uFE0F Contributors*", contributorsLine.split("\n"), "contributors")
4923
5608
  }
4924
5609
  });
4925
5610
  }
4926
- blocks.push({
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
- blocks.push({
5640
+ trailingBlocks.push({
4956
5641
  type: "actions",
4957
5642
  elements
4958
5643
  });
4959
5644
  }
4960
- return blocks;
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 "The message payload was rejected by Slack (likely exceeds 3000 chars per block). Lower social.slack.postMaxLength.";
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.filter((b) => b.type === "header" || b.type === "section").map((b) => b.text?.text ?? "").filter(Boolean).join("\n\n");
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 ?? 2500,
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
- let slackChangelog = minifiedBody;
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: minifiedBody,
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 { getCurrentGitRef as $, expandPackagesToBumpWithDependents as A, extractChangelogSummary as B, extractVersionFromPackageTag$1 as C, extractVersionFromRepoTag as D, extractVersionFromTag as E, fetchGitTags as F, filterOutPrivatePackages as G, filterPrivatePackagesUnlessIncluded as H, findGitHubPR as I, findGitLabMR as J, findReachableCommitBySubject as K, formatChangelogForSlack as L, formatPackagesForSlack as M, NEW_PACKAGE_MARKER as N, formatSlackMessage as O, PR_COMMENT_MARKER as P, formatTweetMessage as Q, generateChangelog as R, generateMarkDown as S, getAuthCommand as T, getBumpedIndependentPackages as U, getBumpedPackageIndependently as V, getCIName as W, getCanaryVersion as X, getCommitChangedFiles as Y, getCommitSubject as Z, getCurrentGitBranch as _, assertBranchUpToDateWithRemote as a, readPackages as a$, getDefaultConfig as a0, getDependentsOf as a1, getFirstCommit as a2, getGitDiff as a3, getGitStatus as a4, getGitUpstream as a5, getIndependentTag as a6, getLastPackageTag as a7, getLastRepoTag as a8, getLastStableTag as a9, isCommitOnlyInIgnoredPackages as aA, isGraduating as aB, isGraduatingToStableBetweenVersion as aC, isInCI as aD, isPrerelease as aE, isPrereleaseReleaseType as aF, isStableReleaseType as aG, isTagVersionCompatibleWithCurrent as aH, isVersionAlreadyPublishedError as aI, isVersionPublished as aJ, listTags as aK, loadRelizyConfig as aL, mergeTypes as aM, parseChangelogMarkdown as aN, parseGitRemoteUrl as aO, postPrComment as aP, postReleaseToSlack as aQ, postReleaseToTwitter as aR, prComment as aS, providerRelease as aT, providerReleaseSafetyCheck as aU, publish as aV, publishPackage as aW, publishSafetyCheck as aX, pushCommitAndTags as aY, pushTagForce as aZ, readPackageJson as a_, getLastTag as aa, getModifiedReleaseFilePatterns as ab, getNewPackageCompareBase as ac, getNpmRegistry as ad, getPackageCommits as ae, getPackageDependencies as af, getPackageNewVersion as ag, getPackages as ah, getPackagesOrBumpedPackages as ai, getPackagesToPublishInIndependentMode as aj, getPackagesToPublishInSelectiveMode as ak, getPreid as al, getReleaseTagNames as am, getReleaseUrl as an, getRootPackage as ao, getShortCommitSha as ap, getSlackToken as aq, getSlackWebhookUrl as ar, getTwitterCredentials as as, github as at, gitlab as au, globDirsAbsolute as av, hasLernaJson as aw, isAncestor as ax, isBumpedPackage as ay, isChangedPreid as az, assertReleaseTagsAvailable as b, reconcileFromTag as b0, redactSecrets as b1, release as b2, resetRewrittenTagCache as b3, resolveAllConfiguredRegistryTargets as b4, resolveIgnoredPackagePaths as b5, resolveRegistryTargetsForPackage as b6, resolveRegistryTargetsForPackages as b7, resolveTags as b8, retagAnnotatedLocal as b9, rollbackModifiedFiles as ba, shouldFilterPrereleaseTags as bb, social as bc, socialSafetyCheck as bd, tagExists as be, topologicalSort as bf, tryPostPrComment as bg, updateLernaVersion as bh, withRegistryNpmrc as bi, writeChangelogToFile as bj, writeVersion as bk, buildChangelogBody as c, buildCommentBody as d, buildCompareLink as e, buildContributors as f, buildReleaseSummary as g, bump as h, capReleaseTypeForZeroMajor as i, changelog as j, checkGitStatusIfDirty as k, collectContributorNames as l, collectPackageBumps as m, confirmBump as n, createCommitAndTags as o, createGitlabRelease as p, defineConfig as q, detectGitProvider as r, detectPackageManager as s, detectPullRequest as t, determinePublishTag as u, determineReleaseType as v, determineSemverChange as w, executeBuildCmd as x, executeFormatCmd as y, executeHook as z };
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 };