relizy 1.7.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 +15 -7
- package/dist/index.d.mts +199 -12
- package/dist/index.d.ts +199 -12
- package/dist/index.mjs +5 -2
- package/dist/shared/{relizy.uCxVVpbQ.mjs → relizy.CN-7XcP7.mjs} +1099 -301
- package/package.json +10 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execPromise, logger } from '@maz-ui/node';
|
|
2
2
|
import { execSync, spawnSync } from 'node:child_process';
|
|
3
3
|
import process$1 from 'node:process';
|
|
4
4
|
import { formatJson } from '@maz-ui/utils/helpers/formatJson';
|
|
@@ -9,12 +9,120 @@ import { existsSync, readFileSync, statSync, writeFileSync, rmSync } from 'node:
|
|
|
9
9
|
import path, { join, relative, sep } from 'node:path';
|
|
10
10
|
import { select, confirm, input } from '@inquirer/prompts';
|
|
11
11
|
import micromatch from 'micromatch';
|
|
12
|
+
import * as semver from 'semver';
|
|
12
13
|
import { getErrorMessage } from '@maz-ui/utils/helpers/getErrorMessage';
|
|
13
14
|
import fastGlob from 'fast-glob';
|
|
14
|
-
import * as semver from 'semver';
|
|
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';
|
|
21
|
+
|
|
22
|
+
function shellSingleQuote(value) {
|
|
23
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
24
|
+
}
|
|
25
|
+
async function isAncestor(ancestor, descendant, cwd) {
|
|
26
|
+
try {
|
|
27
|
+
await execPromise(
|
|
28
|
+
`git merge-base --is-ancestor ${shellSingleQuote(ancestor)} ${shellSingleQuote(descendant)}`,
|
|
29
|
+
{ cwd, noStderr: true, noStdout: true, noSuccess: true, noError: true }
|
|
30
|
+
);
|
|
31
|
+
return true;
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function getCommitSubject(ref, cwd) {
|
|
37
|
+
try {
|
|
38
|
+
const { stdout } = await execPromise(
|
|
39
|
+
`git log -1 --format=%s ${shellSingleQuote(ref)}`,
|
|
40
|
+
{ cwd, noStderr: true, noStdout: true, noSuccess: true, noError: true }
|
|
41
|
+
);
|
|
42
|
+
return stdout.trim() || null;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function findReachableCommitBySubject(subject, to, cwd) {
|
|
48
|
+
try {
|
|
49
|
+
const { stdout } = await execPromise(
|
|
50
|
+
`git log ${shellSingleQuote(to)} --grep=${shellSingleQuote(subject)} --fixed-strings --format=%H -n 1`,
|
|
51
|
+
{ cwd, noStderr: true, noStdout: true, noSuccess: true, noError: true }
|
|
52
|
+
);
|
|
53
|
+
return stdout.trim().split("\n")[0]?.trim() || null;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function tagExists(tag, cwd) {
|
|
59
|
+
try {
|
|
60
|
+
const tagRef = `refs/tags/${tag}`;
|
|
61
|
+
await execPromise(
|
|
62
|
+
`git rev-parse --verify --quiet ${shellSingleQuote(tagRef)}`,
|
|
63
|
+
{ cwd, noStderr: true, noStdout: true, noSuccess: true, noError: true }
|
|
64
|
+
);
|
|
65
|
+
return true;
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const LIST_TAGS_MAX_BUFFER = 64 * 1024 * 1024;
|
|
71
|
+
async function listTags({
|
|
72
|
+
pattern,
|
|
73
|
+
cwd,
|
|
74
|
+
logLevel
|
|
75
|
+
} = {}) {
|
|
76
|
+
const list = pattern ? ` --list ${shellSingleQuote(pattern)}` : "";
|
|
77
|
+
try {
|
|
78
|
+
const { stdout } = await execPromise(
|
|
79
|
+
`git tag${list} --sort=-creatordate`,
|
|
80
|
+
{ cwd, logLevel, noStderr: true, noStdout: true, noSuccess: true, maxBuffer: LIST_TAGS_MAX_BUFFER }
|
|
81
|
+
);
|
|
82
|
+
return stdout.trim().split("\n").filter((tag) => tag.length > 0);
|
|
83
|
+
} catch {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function retagAnnotatedLocal({
|
|
88
|
+
tag,
|
|
89
|
+
commit,
|
|
90
|
+
message,
|
|
91
|
+
signed,
|
|
92
|
+
cwd,
|
|
93
|
+
logLevel
|
|
94
|
+
}) {
|
|
95
|
+
const sign = signed ? "-s " : "";
|
|
96
|
+
await execPromise(
|
|
97
|
+
`git tag -f ${sign}-a ${shellSingleQuote(tag)} ${shellSingleQuote(commit)} -m ${shellSingleQuote(message)}`,
|
|
98
|
+
{ cwd, logLevel, noStderr: true, noStdout: true }
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
async function pushTagForce(tag, cwd, logLevel) {
|
|
102
|
+
await execPromise(
|
|
103
|
+
`git push origin ${shellSingleQuote(tag)} --force`,
|
|
104
|
+
{ cwd, logLevel, noStderr: true, noStdout: true }
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
const COMMIT_SEPARATOR = "";
|
|
108
|
+
const FIELD_SEPARATOR = "";
|
|
109
|
+
function getGitDiff(from, to = "HEAD", cwd) {
|
|
110
|
+
const range = from ? `${from}...${to}` : to;
|
|
111
|
+
const output = execSync(
|
|
112
|
+
`git --no-pager log "${range}" --pretty="%x1E%s%x1F%h%x1F%an%x1F%ae%n%b" --name-status`,
|
|
113
|
+
{ cwd, encoding: "utf8" }
|
|
114
|
+
).trim();
|
|
115
|
+
return output.split(COMMIT_SEPARATOR).slice(1).map((record) => {
|
|
116
|
+
const [firstLine = "", ...body] = record.split("\n");
|
|
117
|
+
const [message = "", shortHash = "", authorName = "", authorEmail = ""] = firstLine.split(FIELD_SEPARATOR);
|
|
118
|
+
return {
|
|
119
|
+
message,
|
|
120
|
+
shortHash,
|
|
121
|
+
author: { name: authorName, email: authorEmail },
|
|
122
|
+
body: body.join("\n")
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
}
|
|
18
126
|
|
|
19
127
|
function getPackageDependencies({
|
|
20
128
|
packagePath,
|
|
@@ -134,94 +242,6 @@ function topologicalSort(packages) {
|
|
|
134
242
|
return sorted;
|
|
135
243
|
}
|
|
136
244
|
|
|
137
|
-
function shellSingleQuote(value) {
|
|
138
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
139
|
-
}
|
|
140
|
-
async function isAncestor(ancestor, descendant, cwd) {
|
|
141
|
-
try {
|
|
142
|
-
await execPromise(
|
|
143
|
-
`git merge-base --is-ancestor ${shellSingleQuote(ancestor)} ${shellSingleQuote(descendant)}`,
|
|
144
|
-
{ cwd, noStderr: true, noStdout: true, noSuccess: true, noError: true }
|
|
145
|
-
);
|
|
146
|
-
return true;
|
|
147
|
-
} catch {
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
async function getCommitSubject(ref, cwd) {
|
|
152
|
-
try {
|
|
153
|
-
const { stdout } = await execPromise(
|
|
154
|
-
`git log -1 --format=%s ${shellSingleQuote(ref)}`,
|
|
155
|
-
{ cwd, noStderr: true, noStdout: true, noSuccess: true, noError: true }
|
|
156
|
-
);
|
|
157
|
-
return stdout.trim() || null;
|
|
158
|
-
} catch {
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
async function findReachableCommitBySubject(subject, to, cwd) {
|
|
163
|
-
try {
|
|
164
|
-
const { stdout } = await execPromise(
|
|
165
|
-
`git log ${shellSingleQuote(to)} --grep=${shellSingleQuote(subject)} --fixed-strings --format=%H -n 1`,
|
|
166
|
-
{ cwd, noStderr: true, noStdout: true, noSuccess: true, noError: true }
|
|
167
|
-
);
|
|
168
|
-
return stdout.trim().split("\n")[0]?.trim() || null;
|
|
169
|
-
} catch {
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
async function tagExists(tag, cwd) {
|
|
174
|
-
try {
|
|
175
|
-
const tagRef = `refs/tags/${tag}`;
|
|
176
|
-
await execPromise(
|
|
177
|
-
`git rev-parse --verify --quiet ${shellSingleQuote(tagRef)}`,
|
|
178
|
-
{ cwd, noStderr: true, noStdout: true, noSuccess: true, noError: true }
|
|
179
|
-
);
|
|
180
|
-
return true;
|
|
181
|
-
} catch {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
async function retagAnnotatedLocal({
|
|
186
|
-
tag,
|
|
187
|
-
commit,
|
|
188
|
-
message,
|
|
189
|
-
signed,
|
|
190
|
-
cwd,
|
|
191
|
-
logLevel
|
|
192
|
-
}) {
|
|
193
|
-
const sign = signed ? "-s " : "";
|
|
194
|
-
await execPromise(
|
|
195
|
-
`git tag -f ${sign}-a ${shellSingleQuote(tag)} ${shellSingleQuote(commit)} -m ${shellSingleQuote(message)}`,
|
|
196
|
-
{ cwd, logLevel, noStderr: true, noStdout: true }
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
async function pushTagForce(tag, cwd, logLevel) {
|
|
200
|
-
await execPromise(
|
|
201
|
-
`git push origin ${shellSingleQuote(tag)} --force`,
|
|
202
|
-
{ cwd, logLevel, noStderr: true, noStdout: true }
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
const COMMIT_SEPARATOR = "";
|
|
206
|
-
const FIELD_SEPARATOR = "";
|
|
207
|
-
function getGitDiff(from, to = "HEAD", cwd) {
|
|
208
|
-
const range = from ? `${from}...${to}` : to;
|
|
209
|
-
const output = execSync(
|
|
210
|
-
`git --no-pager log "${range}" --pretty="%x1E%s%x1F%h%x1F%an%x1F%ae%n%b" --name-status`,
|
|
211
|
-
{ cwd, encoding: "utf8" }
|
|
212
|
-
).trim();
|
|
213
|
-
return output.split(COMMIT_SEPARATOR).slice(1).map((record) => {
|
|
214
|
-
const [firstLine = "", ...body] = record.split("\n");
|
|
215
|
-
const [message = "", shortHash = "", authorName = "", authorEmail = ""] = firstLine.split(FIELD_SEPARATOR);
|
|
216
|
-
return {
|
|
217
|
-
message,
|
|
218
|
-
shortHash,
|
|
219
|
-
author: { name: authorName, email: authorEmail },
|
|
220
|
-
body: body.join("\n")
|
|
221
|
-
};
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
|
|
225
245
|
const sessionCache = /* @__PURE__ */ new Map();
|
|
226
246
|
function resetRewrittenTagCache() {
|
|
227
247
|
sessionCache.clear();
|
|
@@ -1104,7 +1124,7 @@ function updateLernaVersion({
|
|
|
1104
1124
|
logger.fail(`Unable to update lerna.json: ${error}`);
|
|
1105
1125
|
}
|
|
1106
1126
|
}
|
|
1107
|
-
function extractVersionFromPackageTag(tag) {
|
|
1127
|
+
function extractVersionFromPackageTag$1(tag) {
|
|
1108
1128
|
const atIndex = tag.lastIndexOf("@");
|
|
1109
1129
|
if (atIndex === -1) {
|
|
1110
1130
|
return null;
|
|
@@ -1399,7 +1419,7 @@ function getIndependentTag({ version, name }) {
|
|
|
1399
1419
|
}
|
|
1400
1420
|
async function getLastStableTag({ logLevel, cwd }) {
|
|
1401
1421
|
const { stdout } = await execPromise(
|
|
1402
|
-
`git tag --sort=-creatordate | grep -E '^[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+$' | head -n 1`,
|
|
1422
|
+
`git tag --sort=-creatordate | grep -E '^[^0-9@]*[0-9]+\\.[0-9]+\\.[0-9]+$' | head -n 1`,
|
|
1403
1423
|
{
|
|
1404
1424
|
logLevel,
|
|
1405
1425
|
noStderr: true,
|
|
@@ -1424,81 +1444,132 @@ async function getLastTag({ logLevel, cwd }) {
|
|
|
1424
1444
|
logger.debug("Last tag:", lastTag || "No tags found");
|
|
1425
1445
|
return lastTag;
|
|
1426
1446
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
return
|
|
1447
|
+
const NEW_VERSION_PLACEHOLDER = "{{newVersion}}";
|
|
1448
|
+
function parseTagVersion(value) {
|
|
1449
|
+
return /^\d/.test(value) ? semver.valid(value) : null;
|
|
1450
|
+
}
|
|
1451
|
+
function splitTagBody(tagBody) {
|
|
1452
|
+
const index = tagBody.indexOf(NEW_VERSION_PLACEHOLDER);
|
|
1453
|
+
if (index === -1) {
|
|
1454
|
+
return null;
|
|
1455
|
+
}
|
|
1456
|
+
return {
|
|
1457
|
+
prefix: tagBody.slice(0, index),
|
|
1458
|
+
suffix: tagBody.slice(index + NEW_VERSION_PLACEHOLDER.length)
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
function extractVersionFromRepoTag(tag, tagBody) {
|
|
1462
|
+
const parts = splitTagBody(tagBody);
|
|
1463
|
+
if (!parts || !tag.startsWith(parts.prefix) || !tag.endsWith(parts.suffix)) {
|
|
1464
|
+
return null;
|
|
1465
|
+
}
|
|
1466
|
+
return parseTagVersion(tag.slice(parts.prefix.length, tag.length - parts.suffix.length));
|
|
1467
|
+
}
|
|
1468
|
+
function extractVersionFromLegacyRepoTag(tag) {
|
|
1469
|
+
return parseTagVersion(tag.startsWith("v") ? tag.slice(1) : tag);
|
|
1470
|
+
}
|
|
1471
|
+
function extractVersionFromPackageTag(tag, packageName) {
|
|
1472
|
+
const prefix = `${packageName}@`;
|
|
1473
|
+
return tag.startsWith(prefix) ? parseTagVersion(tag.slice(prefix.length)) : null;
|
|
1474
|
+
}
|
|
1475
|
+
async function getRepoTags({
|
|
1476
|
+
tagBody,
|
|
1477
|
+
logLevel,
|
|
1478
|
+
cwd
|
|
1479
|
+
}) {
|
|
1480
|
+
const templateTags = [];
|
|
1481
|
+
const legacyTags = [];
|
|
1482
|
+
const unrecognizedTags = [];
|
|
1483
|
+
for (const tag of await listTags({ logLevel, cwd })) {
|
|
1484
|
+
const templateVersion = tagBody ? extractVersionFromRepoTag(tag, tagBody) : null;
|
|
1485
|
+
if (templateVersion) {
|
|
1486
|
+
templateTags.push({ tag, version: templateVersion });
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
const legacyVersion = extractVersionFromLegacyRepoTag(tag);
|
|
1490
|
+
if (legacyVersion) {
|
|
1491
|
+
legacyTags.push({ tag, version: legacyVersion });
|
|
1492
|
+
} else if (!tag.includes("@") && tag.split(/[^\d.]/).some((part) => semver.valid(part))) {
|
|
1493
|
+
unrecognizedTags.push(tag);
|
|
1494
|
+
}
|
|
1445
1495
|
}
|
|
1496
|
+
logger.debug(`Retrieved ${templateTags.length} repo tags following "${tagBody}" and ${legacyTags.length} in the v1.2.3 or 1.2.3 format`);
|
|
1497
|
+
return { tags: [...templateTags, ...legacyTags], unrecognizedTags };
|
|
1446
1498
|
}
|
|
1447
|
-
async function
|
|
1499
|
+
async function getPackageTags({
|
|
1448
1500
|
packageName,
|
|
1449
|
-
limit = 50,
|
|
1450
1501
|
logLevel,
|
|
1451
1502
|
cwd
|
|
1452
1503
|
}) {
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
const
|
|
1456
|
-
|
|
1457
|
-
{
|
|
1458
|
-
|
|
1459
|
-
noStderr: true,
|
|
1460
|
-
noStdout: true,
|
|
1461
|
-
noSuccess: true,
|
|
1462
|
-
cwd
|
|
1463
|
-
}
|
|
1464
|
-
);
|
|
1465
|
-
const tags = stdout.trim().split("\n").filter((tag) => tag.length > 0);
|
|
1466
|
-
logger.debug(`Retrieved ${tags.length} recent tags for package ${packageName}`);
|
|
1467
|
-
return tags;
|
|
1468
|
-
} catch {
|
|
1469
|
-
return [];
|
|
1504
|
+
const tags = [];
|
|
1505
|
+
for (const tag of await listTags({ pattern: `${packageName}@*`, logLevel, cwd })) {
|
|
1506
|
+
const version = extractVersionFromPackageTag(tag, packageName);
|
|
1507
|
+
if (version) {
|
|
1508
|
+
tags.push({ tag, version });
|
|
1509
|
+
}
|
|
1470
1510
|
}
|
|
1511
|
+
logger.debug(`Retrieved ${tags.length} tags for package ${packageName}`);
|
|
1512
|
+
return tags;
|
|
1471
1513
|
}
|
|
1472
|
-
function
|
|
1514
|
+
function selectPreviousTag({
|
|
1473
1515
|
tags,
|
|
1474
|
-
|
|
1475
|
-
|
|
1516
|
+
currentVersion,
|
|
1517
|
+
maxVersion,
|
|
1518
|
+
onlyStable,
|
|
1519
|
+
label
|
|
1476
1520
|
}) {
|
|
1477
|
-
const
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
logger.debug(`Skipping tag ${tag}: cannot extract version`);
|
|
1481
|
-
return false;
|
|
1482
|
-
}
|
|
1483
|
-
if (onlyStable && isPrerelease(tagVersion)) {
|
|
1484
|
-
logger.debug(`Skipping tag ${tag}: prerelease version ${tagVersion} (onlyStable=${onlyStable})`);
|
|
1521
|
+
const candidates = tags.filter(({ tag, version }) => {
|
|
1522
|
+
if (!isTagVersionCompatibleWithCurrent(version, currentVersion)) {
|
|
1523
|
+
logger.debug(`Skipping tag ${tag}: version ${version} has higher major than current ${currentVersion}`);
|
|
1485
1524
|
return false;
|
|
1486
1525
|
}
|
|
1487
|
-
if (
|
|
1488
|
-
logger.debug(`Skipping tag ${tag}: version ${
|
|
1526
|
+
if (semver.valid(maxVersion) && semver.gt(version, maxVersion)) {
|
|
1527
|
+
logger.debug(`Skipping tag ${tag}: version ${version} is higher than ${maxVersion}`);
|
|
1489
1528
|
return false;
|
|
1490
1529
|
}
|
|
1491
|
-
logger.debug(`Tag ${tag} with version ${tagVersion} is compatible`);
|
|
1492
1530
|
return true;
|
|
1493
1531
|
});
|
|
1494
|
-
|
|
1495
|
-
|
|
1532
|
+
const highest = (list) => list.reduce(
|
|
1533
|
+
(best, candidate) => !best || semver.gt(candidate.version, best.version) ? candidate : best,
|
|
1534
|
+
null
|
|
1535
|
+
);
|
|
1536
|
+
if (onlyStable) {
|
|
1537
|
+
const stable = highest(candidates.filter(({ version }) => !isPrerelease(version)));
|
|
1538
|
+
if (stable) {
|
|
1539
|
+
logger.debug(`Previous ${label} tag: ${stable.tag}`);
|
|
1540
|
+
return stable.tag;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
const previous = highest(candidates);
|
|
1544
|
+
if (previous) {
|
|
1545
|
+
if (onlyStable) {
|
|
1546
|
+
logger.info(`No stable ${label} tag found for version ${currentVersion}, falling back to last prerelease tag: ${previous.tag}`);
|
|
1547
|
+
} else {
|
|
1548
|
+
logger.debug(`Previous ${label} tag: ${previous.tag}`);
|
|
1549
|
+
}
|
|
1550
|
+
return previous.tag;
|
|
1551
|
+
}
|
|
1552
|
+
logger.info(`No compatible ${label} tag found for version ${currentVersion}`);
|
|
1553
|
+
return null;
|
|
1554
|
+
}
|
|
1555
|
+
const warnedUnrecognizedTags = /* @__PURE__ */ new Set();
|
|
1556
|
+
function warnUnrecognizedTags({ tags, tagBody, cwd }) {
|
|
1557
|
+
const key = `${cwd}:${tagBody}`;
|
|
1558
|
+
if (tags.length === 0 || warnedUnrecognizedTags.has(key)) {
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
warnedUnrecognizedTags.add(key);
|
|
1562
|
+
logger.warn(
|
|
1563
|
+
`No previous release tag follows templates.tagBody "${tagBody}", but some tags look like one (${tags.slice(0, 3).join(", ")}). If the template was changed, pass the tag of the previous release with --from.`
|
|
1564
|
+
);
|
|
1496
1565
|
}
|
|
1497
1566
|
function getLastRepoTag(options) {
|
|
1498
1567
|
if (options?.pkg) {
|
|
1499
1568
|
return getLastRepoTagWithFiltering({
|
|
1500
1569
|
pkg: options.pkg,
|
|
1501
1570
|
onlyStable: options.onlyStable ?? false,
|
|
1571
|
+
tagBody: options.tagBody,
|
|
1572
|
+
maxVersion: options.currentVersion ?? options.pkg.version,
|
|
1502
1573
|
logLevel: options.logLevel,
|
|
1503
1574
|
cwd: options.cwd
|
|
1504
1575
|
});
|
|
@@ -1511,40 +1582,24 @@ function getLastRepoTag(options) {
|
|
|
1511
1582
|
async function getLastRepoTagWithFiltering({
|
|
1512
1583
|
pkg,
|
|
1513
1584
|
onlyStable,
|
|
1585
|
+
tagBody,
|
|
1586
|
+
maxVersion,
|
|
1514
1587
|
logLevel,
|
|
1515
1588
|
cwd
|
|
1516
1589
|
}) {
|
|
1517
|
-
const
|
|
1518
|
-
if (
|
|
1519
|
-
logger.info("No tags found in repository");
|
|
1590
|
+
const { tags, unrecognizedTags } = await getRepoTags({ tagBody, logLevel, cwd });
|
|
1591
|
+
if (tags.length === 0) {
|
|
1592
|
+
logger.info("No release tags found in repository");
|
|
1593
|
+
warnUnrecognizedTags({ tags: unrecognizedTags, tagBody, cwd });
|
|
1520
1594
|
return null;
|
|
1521
1595
|
}
|
|
1522
|
-
|
|
1523
|
-
tags
|
|
1524
|
-
pkg,
|
|
1525
|
-
|
|
1596
|
+
return selectPreviousTag({
|
|
1597
|
+
tags,
|
|
1598
|
+
currentVersion: pkg.version,
|
|
1599
|
+
maxVersion,
|
|
1600
|
+
onlyStable,
|
|
1601
|
+
label: "repo"
|
|
1526
1602
|
});
|
|
1527
|
-
if (compatibleTags.length > 0) {
|
|
1528
|
-
const lastTag = compatibleTags[0];
|
|
1529
|
-
logger.debug(`Last compatible repo tag: ${lastTag}`);
|
|
1530
|
-
return lastTag;
|
|
1531
|
-
}
|
|
1532
|
-
if (onlyStable) {
|
|
1533
|
-
const prereleaseFallback = filterCompatibleTags({
|
|
1534
|
-
tags: recentTags,
|
|
1535
|
-
pkg,
|
|
1536
|
-
onlyStable: false
|
|
1537
|
-
});
|
|
1538
|
-
if (prereleaseFallback.length > 0) {
|
|
1539
|
-
const fallbackTag = prereleaseFallback[0];
|
|
1540
|
-
logger.info(
|
|
1541
|
-
`No stable repo tag found for version ${pkg.version}, falling back to last prerelease tag: ${fallbackTag}`
|
|
1542
|
-
);
|
|
1543
|
-
return fallbackTag;
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
logger.info(`No compatible tags found for version ${pkg.version}`);
|
|
1547
|
-
return null;
|
|
1548
1603
|
}
|
|
1549
1604
|
async function getLastPackageTag({
|
|
1550
1605
|
pkg,
|
|
@@ -1590,42 +1645,18 @@ async function getLastPackageTagWithFiltering({
|
|
|
1590
1645
|
logLevel,
|
|
1591
1646
|
cwd
|
|
1592
1647
|
}) {
|
|
1593
|
-
const
|
|
1594
|
-
|
|
1595
|
-
limit: 50,
|
|
1596
|
-
logLevel,
|
|
1597
|
-
cwd
|
|
1598
|
-
});
|
|
1599
|
-
if (recentTags.length === 0) {
|
|
1648
|
+
const tags = await getPackageTags({ packageName: pkg.name, logLevel, cwd });
|
|
1649
|
+
if (tags.length === 0) {
|
|
1600
1650
|
logger.info(`No tags found for package ${pkg.name}`);
|
|
1601
1651
|
return null;
|
|
1602
1652
|
}
|
|
1603
|
-
|
|
1604
|
-
tags
|
|
1605
|
-
pkg,
|
|
1606
|
-
|
|
1653
|
+
return selectPreviousTag({
|
|
1654
|
+
tags,
|
|
1655
|
+
currentVersion: pkg.version,
|
|
1656
|
+
maxVersion: pkg.version,
|
|
1657
|
+
onlyStable,
|
|
1658
|
+
label: `${pkg.name} package`
|
|
1607
1659
|
});
|
|
1608
|
-
if (compatibleTags.length > 0) {
|
|
1609
|
-
const lastTag = compatibleTags[0];
|
|
1610
|
-
logger.debug(`Last compatible package tag for ${pkg.name}: ${lastTag}`);
|
|
1611
|
-
return lastTag;
|
|
1612
|
-
}
|
|
1613
|
-
if (onlyStable) {
|
|
1614
|
-
const prereleaseFallback = filterCompatibleTags({
|
|
1615
|
-
tags: recentTags,
|
|
1616
|
-
pkg,
|
|
1617
|
-
onlyStable: false
|
|
1618
|
-
});
|
|
1619
|
-
if (prereleaseFallback.length > 0) {
|
|
1620
|
-
const fallbackTag = prereleaseFallback[0];
|
|
1621
|
-
logger.info(
|
|
1622
|
-
`No stable tags found for package ${pkg.name} with version ${pkg.version}, falling back to last prerelease tag: ${fallbackTag}`
|
|
1623
|
-
);
|
|
1624
|
-
return fallbackTag;
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
logger.info(`No compatible tags found for package ${pkg.name} with version ${pkg.version}`);
|
|
1628
|
-
return null;
|
|
1629
1660
|
}
|
|
1630
1661
|
const NEW_PACKAGE_MARKER = "__NEW_PACKAGE__";
|
|
1631
1662
|
async function resolveFromTagIndependent({
|
|
@@ -1648,6 +1679,18 @@ async function resolveFromTagIndependent({
|
|
|
1648
1679
|
}
|
|
1649
1680
|
return lastPackageTag;
|
|
1650
1681
|
}
|
|
1682
|
+
function getRepoCurrentVersion({ cwd, pkg }) {
|
|
1683
|
+
let rootVersion = null;
|
|
1684
|
+
try {
|
|
1685
|
+
rootVersion = semver.valid(JSON.parse(readFileSync(join(cwd, "package.json"), "utf8")).version);
|
|
1686
|
+
} catch {
|
|
1687
|
+
logger.debug(`Cannot read the root version in ${cwd}, using the version of ${pkg.name}`);
|
|
1688
|
+
}
|
|
1689
|
+
if (!rootVersion || !semver.valid(pkg.version)) {
|
|
1690
|
+
return rootVersion || pkg.version;
|
|
1691
|
+
}
|
|
1692
|
+
return semver.gt(rootVersion, pkg.version) ? rootVersion : pkg.version;
|
|
1693
|
+
}
|
|
1651
1694
|
async function resolveFromTagUnified({
|
|
1652
1695
|
config,
|
|
1653
1696
|
pkg,
|
|
@@ -1659,6 +1702,8 @@ async function resolveFromTagUnified({
|
|
|
1659
1702
|
const lastRepoTag = await getLastRepoTag({
|
|
1660
1703
|
pkg,
|
|
1661
1704
|
onlyStable,
|
|
1705
|
+
tagBody: config.templates.tagBody,
|
|
1706
|
+
currentVersion: getRepoCurrentVersion({ cwd: config.cwd, pkg }),
|
|
1662
1707
|
logLevel,
|
|
1663
1708
|
cwd: config.cwd
|
|
1664
1709
|
});
|
|
@@ -2536,7 +2581,8 @@ const SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
|
2536
2581
|
"_authtoken"
|
|
2537
2582
|
]);
|
|
2538
2583
|
const SECRET_CONTAINER_KEYS = /* @__PURE__ */ new Set(["tokens"]);
|
|
2539
|
-
|
|
2584
|
+
const CIRCULAR = "[Circular]";
|
|
2585
|
+
function redactValue(value, inSecretContainer, keyIsSensitive, ancestors) {
|
|
2540
2586
|
if (typeof value === "string") {
|
|
2541
2587
|
if (value === "") {
|
|
2542
2588
|
return value;
|
|
@@ -2546,26 +2592,29 @@ function redactValue(value, inSecretContainer, keyIsSensitive) {
|
|
|
2546
2592
|
}
|
|
2547
2593
|
return value;
|
|
2548
2594
|
}
|
|
2549
|
-
if (
|
|
2550
|
-
return value
|
|
2595
|
+
if (!value || typeof value !== "object") {
|
|
2596
|
+
return value;
|
|
2551
2597
|
}
|
|
2552
|
-
if (value
|
|
2553
|
-
return
|
|
2598
|
+
if (ancestors.has(value)) {
|
|
2599
|
+
return CIRCULAR;
|
|
2554
2600
|
}
|
|
2555
|
-
|
|
2601
|
+
ancestors.add(value);
|
|
2602
|
+
const result = Array.isArray(value) ? value.map((item) => redactValue(item, inSecretContainer, false, ancestors)) : redactObject(value, inSecretContainer, ancestors);
|
|
2603
|
+
ancestors.delete(value);
|
|
2604
|
+
return result;
|
|
2556
2605
|
}
|
|
2557
|
-
function redactObject(obj, inSecretContainer) {
|
|
2606
|
+
function redactObject(obj, inSecretContainer, ancestors) {
|
|
2558
2607
|
const result = {};
|
|
2559
2608
|
for (const [key, value] of Object.entries(obj)) {
|
|
2560
2609
|
const lowerKey = key.toLowerCase();
|
|
2561
2610
|
const childInContainer = inSecretContainer || SECRET_CONTAINER_KEYS.has(lowerKey);
|
|
2562
2611
|
const keyIsSensitive = SENSITIVE_KEYS.has(lowerKey);
|
|
2563
|
-
result[key] = redactValue(value, childInContainer, keyIsSensitive);
|
|
2612
|
+
result[key] = redactValue(value, childInContainer, keyIsSensitive, ancestors);
|
|
2564
2613
|
}
|
|
2565
2614
|
return result;
|
|
2566
2615
|
}
|
|
2567
2616
|
function redactSecrets(value) {
|
|
2568
|
-
return redactValue(value, false, false);
|
|
2617
|
+
return redactValue(value, false, false, /* @__PURE__ */ new WeakSet());
|
|
2569
2618
|
}
|
|
2570
2619
|
|
|
2571
2620
|
function getDefaultConfig() {
|
|
@@ -2667,7 +2716,7 @@ function getDefaultConfig() {
|
|
|
2667
2716
|
slack: {
|
|
2668
2717
|
enabled: false,
|
|
2669
2718
|
onlyStable: true,
|
|
2670
|
-
|
|
2719
|
+
changelogFormat: "summary",
|
|
2671
2720
|
noAuthors: false,
|
|
2672
2721
|
noPackages: false
|
|
2673
2722
|
}
|
|
@@ -3416,15 +3465,19 @@ function getCommitBody(commit) {
|
|
|
3416
3465
|
const contentLines = lines.filter((line) => {
|
|
3417
3466
|
const trimmedLine = line.trim();
|
|
3418
3467
|
if (!trimmedLine) {
|
|
3419
|
-
return
|
|
3468
|
+
return true;
|
|
3420
3469
|
}
|
|
3421
3470
|
const isFileLine = /^[AMDTUXB](?:\d{3})?\s+/.test(trimmedLine) || /^[RCM]\d{3}\s+/.test(trimmedLine);
|
|
3422
3471
|
return !isFileLine;
|
|
3423
3472
|
});
|
|
3424
|
-
|
|
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) {
|
|
3425
3478
|
return "";
|
|
3426
3479
|
}
|
|
3427
|
-
const indentedBody =
|
|
3480
|
+
const indentedBody = paragraphLines.map((line) => line.trim() ? ` ${line}` : "").join("\n");
|
|
3428
3481
|
return `
|
|
3429
3482
|
|
|
3430
3483
|
${indentedBody}
|
|
@@ -3633,29 +3686,76 @@ ${existingChangelog}`;
|
|
|
3633
3686
|
const BASE_PROMPT = `You are a release-notes rewriter.
|
|
3634
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.
|
|
3635
3688
|
Never ask for clarification. Never reply with questions, greetings, or meta-commentary. Your only output is the rewritten changelog content.
|
|
3636
|
-
Never invent changes
|
|
3637
|
-
|
|
3638
|
-
|
|
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.
|
|
3639
3694
|
Output language: {{language}}.`;
|
|
3640
3695
|
const PROVIDER_RELEASE_PROMPT = `Format the output as markdown with "### <Type>" sections matching the input (Features, Bug Fixes, etc.).
|
|
3641
3696
|
Merge redundant items that describe the same change.
|
|
3642
|
-
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.
|
|
3643
3698
|
Preserve exactly as given: PR and issue references like #123, commit hashes, commit scopes like **auth:**, and all markdown links.
|
|
3644
3699
|
Preserve the \u26A0\uFE0F marker on breaking items and the "#### \u26A0\uFE0F Breaking Changes" section if present.
|
|
3645
3700
|
Purely internal commits (chore, refactor with no user-visible impact) may be dropped unless they carry meaningful information.
|
|
3646
3701
|
Tone: professional, concise, public-facing release notes.`;
|
|
3647
|
-
const TWITTER_PROMPT = `Output plain text
|
|
3648
|
-
|
|
3649
|
-
Remove commit hashes (e.g. "aabf96b") and
|
|
3650
|
-
If
|
|
3651
|
-
If the input has
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
const SLACK_PROMPT = `Format
|
|
3656
|
-
|
|
3657
|
-
|
|
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.
|
|
3658
3716
|
Tone: factual, oriented toward an internal team audience.`;
|
|
3717
|
+
const MAX_LENGTH_PROMPT = `Hard maximum: {{maxLength}} characters. Never exceed it.`;
|
|
3718
|
+
function wrapChangelogPrompt(prompt) {
|
|
3719
|
+
return `<changelog>
|
|
3720
|
+
${prompt}
|
|
3721
|
+
</changelog>
|
|
3722
|
+
|
|
3723
|
+
Rewrite the content inside the <changelog> tag per the rules in the system prompt. Output ONLY the rewritten content, with no preamble, no explanation, no surrounding tags.`;
|
|
3724
|
+
}
|
|
3725
|
+
|
|
3726
|
+
function getModel(config) {
|
|
3727
|
+
const model = config.ai?.providers?.["ai-sdk"]?.model;
|
|
3728
|
+
if (!model) {
|
|
3729
|
+
throw new Error(
|
|
3730
|
+
"No model configured for the ai-sdk provider. Set ai.providers['ai-sdk'].model, e.g. openai('gpt-5-mini') from @ai-sdk/openai."
|
|
3731
|
+
);
|
|
3732
|
+
}
|
|
3733
|
+
return model;
|
|
3734
|
+
}
|
|
3735
|
+
const aiSdkProvider = {
|
|
3736
|
+
name: "ai-sdk",
|
|
3737
|
+
async safetyCheck(config) {
|
|
3738
|
+
getModel(config);
|
|
3739
|
+
try {
|
|
3740
|
+
await import('ai');
|
|
3741
|
+
} catch {
|
|
3742
|
+
throw new Error("ai is not installed. Install it with: pnpm add -D ai");
|
|
3743
|
+
}
|
|
3744
|
+
},
|
|
3745
|
+
async generate(config, request) {
|
|
3746
|
+
const model = getModel(config);
|
|
3747
|
+
const { generateText } = await import('ai');
|
|
3748
|
+
const reasoning = config.ai?.providers?.["ai-sdk"]?.reasoning;
|
|
3749
|
+
const result = await generateText({
|
|
3750
|
+
model,
|
|
3751
|
+
instructions: request.systemPrompt,
|
|
3752
|
+
prompt: wrapChangelogPrompt(request.prompt),
|
|
3753
|
+
...reasoning && { reasoning }
|
|
3754
|
+
});
|
|
3755
|
+
logger.verbose("AI SDK steps:", result.steps?.flatMap((step) => step.content.map((part) => part.type)).join(", "));
|
|
3756
|
+
return result.text.trim();
|
|
3757
|
+
}
|
|
3758
|
+
};
|
|
3659
3759
|
|
|
3660
3760
|
function claudeBinaryAvailable() {
|
|
3661
3761
|
try {
|
|
@@ -3699,16 +3799,11 @@ const claudeCodeProvider = {
|
|
|
3699
3799
|
const { claudeRun } = await import('@yoloship/claude-sdk');
|
|
3700
3800
|
const auth = resolveAuth(config);
|
|
3701
3801
|
const model = config.ai?.providers?.["claude-code"]?.model;
|
|
3702
|
-
const wrappedPrompt = `<changelog>
|
|
3703
|
-
${request.prompt}
|
|
3704
|
-
</changelog>
|
|
3705
|
-
|
|
3706
|
-
Rewrite the content inside the <changelog> tag per the rules in the system prompt. Output ONLY the rewritten content, with no preamble, no explanation, no surrounding tags.`;
|
|
3707
3802
|
const hasAuth = !!(auth.apiKey || auth.oauthToken);
|
|
3708
3803
|
const result = await claudeRun(
|
|
3709
3804
|
{
|
|
3710
3805
|
systemPrompt: request.systemPrompt,
|
|
3711
|
-
prompt:
|
|
3806
|
+
prompt: wrapChangelogPrompt(request.prompt),
|
|
3712
3807
|
maxTurns: 1,
|
|
3713
3808
|
effort: "low",
|
|
3714
3809
|
disableSlashCommands: true,
|
|
@@ -3725,7 +3820,8 @@ Rewrite the content inside the <changelog> tag per the rules in the system promp
|
|
|
3725
3820
|
};
|
|
3726
3821
|
|
|
3727
3822
|
const providers = {
|
|
3728
|
-
"claude-code": claudeCodeProvider
|
|
3823
|
+
"claude-code": claudeCodeProvider,
|
|
3824
|
+
"ai-sdk": aiSdkProvider
|
|
3729
3825
|
};
|
|
3730
3826
|
function getAIProvider(config) {
|
|
3731
3827
|
const name = config.ai?.provider ?? "claude-code";
|
|
@@ -3751,6 +3847,16 @@ function substitutePlaceholders(prompt, vars) {
|
|
|
3751
3847
|
}
|
|
3752
3848
|
return result;
|
|
3753
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
|
+
}
|
|
3754
3860
|
function assemblePrompt(config, target, maxLength) {
|
|
3755
3861
|
const vars = {
|
|
3756
3862
|
language: config.ai?.language ?? "en",
|
|
@@ -3761,9 +3867,10 @@ function assemblePrompt(config, target, maxLength) {
|
|
|
3761
3867
|
return substitutePlaceholders(override, vars);
|
|
3762
3868
|
}
|
|
3763
3869
|
const parts = [BASE_PROMPT, PLATFORM_PROMPTS[target]];
|
|
3764
|
-
if (
|
|
3765
|
-
parts.push(
|
|
3870
|
+
if (maxLength !== void 0) {
|
|
3871
|
+
parts.push(MAX_LENGTH_PROMPT);
|
|
3766
3872
|
}
|
|
3873
|
+
parts.push(...getExtraGuidelines(config, target));
|
|
3767
3874
|
return substitutePlaceholders(parts.join("\n\n"), vars);
|
|
3768
3875
|
}
|
|
3769
3876
|
function applyAIOverride(config, ai) {
|
|
@@ -4705,6 +4812,648 @@ async function postPrComment({
|
|
|
4705
4812
|
}
|
|
4706
4813
|
}
|
|
4707
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
|
+
|
|
4708
5457
|
function extractChangelogSummary(changelog, { stripBoldMarkers = false, maxLength } = {}) {
|
|
4709
5458
|
if (changelog.trim() === "") {
|
|
4710
5459
|
return "";
|
|
@@ -4755,14 +5504,12 @@ function getSlackToken(options) {
|
|
|
4755
5504
|
function getSlackWebhookUrl(options) {
|
|
4756
5505
|
return options.socialWebhookUrl || process.env.RELIZY_SLACK_WEBHOOK_URL || process.env.SLACK_WEBHOOK_URL || null;
|
|
4757
5506
|
}
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
}
|
|
4765
|
-
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 });
|
|
4766
5513
|
}
|
|
4767
5514
|
function formatPackagesForSlack(packages) {
|
|
4768
5515
|
if (packages.length === 0) {
|
|
@@ -4772,75 +5519,96 @@ function formatPackagesForSlack(packages) {
|
|
|
4772
5519
|
(pkg) => pkg.hasTransition ? `\u2022 *${pkg.name}* : \`${pkg.oldVersion}\` \u2192 \`${pkg.newVersion}\`` : `\u2022 *${pkg.name}* : \`${pkg.version}\``
|
|
4773
5520
|
).join("\n");
|
|
4774
5521
|
}
|
|
4775
|
-
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" }) {
|
|
4776
5559
|
const contributorsLine = contributors.length > 0 ? contributors.map((n) => `\u2022 ${n}`).join("\n") : "";
|
|
4777
5560
|
const packagesLine = formatPackagesForSlack(packages);
|
|
5561
|
+
const maxLength = resolveSlackPostMaxLength({ changelogFormat, postMaxLength });
|
|
4778
5562
|
if (template) {
|
|
4779
|
-
const
|
|
4780
|
-
let
|
|
4781
|
-
if (
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
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
|
+
});
|
|
4788
5571
|
} else {
|
|
4789
|
-
|
|
5572
|
+
changelogText = extractChangelogSummary(changelog, { maxLength });
|
|
4790
5573
|
}
|
|
4791
5574
|
return [
|
|
4792
5575
|
{
|
|
4793
5576
|
type: "section",
|
|
4794
5577
|
text: {
|
|
4795
5578
|
type: "mrkdwn",
|
|
4796
|
-
text:
|
|
5579
|
+
text: truncateSlackText(renderSlackTemplate({ ...templateContext, changelog: changelogText }), SLACK_SECTION_MAX_LENGTH)
|
|
4797
5580
|
}
|
|
4798
5581
|
}
|
|
4799
5582
|
];
|
|
4800
5583
|
}
|
|
4801
|
-
const
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
emoji: true
|
|
4808
|
-
}
|
|
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
|
|
4809
5590
|
}
|
|
4810
|
-
|
|
4811
|
-
const
|
|
4812
|
-
if (formattedChangelog) {
|
|
4813
|
-
blocks.push({
|
|
4814
|
-
type: "section",
|
|
4815
|
-
text: {
|
|
4816
|
-
type: "mrkdwn",
|
|
4817
|
-
text: formattedChangelog
|
|
4818
|
-
}
|
|
4819
|
-
});
|
|
4820
|
-
}
|
|
5591
|
+
};
|
|
5592
|
+
const trailingBlocks = [];
|
|
4821
5593
|
if (packages.length > 0) {
|
|
4822
|
-
|
|
5594
|
+
trailingBlocks.push({
|
|
4823
5595
|
type: "section",
|
|
4824
5596
|
text: {
|
|
4825
5597
|
type: "mrkdwn",
|
|
4826
|
-
text:
|
|
4827
|
-
|
|
4828
|
-
${packagesLine}`
|
|
5598
|
+
text: formatListSection("*\u{1F4E6} Packages*", packagesLine.split("\n"), "packages")
|
|
4829
5599
|
}
|
|
4830
5600
|
});
|
|
4831
5601
|
}
|
|
4832
5602
|
if (contributors.length > 0) {
|
|
4833
|
-
|
|
5603
|
+
trailingBlocks.push({
|
|
4834
5604
|
type: "section",
|
|
4835
5605
|
text: {
|
|
4836
5606
|
type: "mrkdwn",
|
|
4837
|
-
text:
|
|
4838
|
-
|
|
4839
|
-
${contributorsLine}`
|
|
5607
|
+
text: formatListSection("*\u2764\uFE0F Contributors*", contributorsLine.split("\n"), "contributors")
|
|
4840
5608
|
}
|
|
4841
5609
|
});
|
|
4842
5610
|
}
|
|
4843
|
-
|
|
5611
|
+
trailingBlocks.push({
|
|
4844
5612
|
type: "divider"
|
|
4845
5613
|
});
|
|
4846
5614
|
const elements = [];
|
|
@@ -4869,19 +5637,39 @@ ${contributorsLine}`
|
|
|
4869
5637
|
});
|
|
4870
5638
|
}
|
|
4871
5639
|
if (elements.length > 0) {
|
|
4872
|
-
|
|
5640
|
+
trailingBlocks.push({
|
|
4873
5641
|
type: "actions",
|
|
4874
5642
|
elements
|
|
4875
5643
|
});
|
|
4876
5644
|
}
|
|
4877
|
-
|
|
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];
|
|
4878
5666
|
}
|
|
4879
5667
|
function mapWebhookError(status, body) {
|
|
4880
5668
|
if (status === 404 || body.includes("no_service")) {
|
|
4881
5669
|
return "The webhook URL is invalid or has been deactivated. Regenerate it in your Slack app settings.";
|
|
4882
5670
|
}
|
|
4883
5671
|
if (body.includes("invalid_payload")) {
|
|
4884
|
-
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".';
|
|
4885
5673
|
}
|
|
4886
5674
|
if (body.includes("channel_not_found")) {
|
|
4887
5675
|
return "The channel bound to this webhook was archived or removed. Create a new webhook.";
|
|
@@ -4953,6 +5741,7 @@ async function postReleaseToSlack({
|
|
|
4953
5741
|
contributors,
|
|
4954
5742
|
packages,
|
|
4955
5743
|
postMaxLength,
|
|
5744
|
+
changelogFormat,
|
|
4956
5745
|
dryRun = false
|
|
4957
5746
|
}) {
|
|
4958
5747
|
const useWebhook = Boolean(webhookUrl);
|
|
@@ -4973,12 +5762,13 @@ async function postReleaseToSlack({
|
|
|
4973
5762
|
changelogUrl,
|
|
4974
5763
|
contributors,
|
|
4975
5764
|
packages,
|
|
4976
|
-
postMaxLength
|
|
5765
|
+
postMaxLength,
|
|
5766
|
+
changelogFormat
|
|
4977
5767
|
});
|
|
4978
5768
|
const fallbackText = `${projectName} ${version} is out!`;
|
|
4979
5769
|
logger.debug(`Message blocks (${blocks.length} blocks)`);
|
|
4980
5770
|
if (dryRun) {
|
|
4981
|
-
const preview = blocks.
|
|
5771
|
+
const preview = blocks.map((b) => b.type === "rich_text" ? richTextToPlainText(b) : b.text?.text ?? "").filter(Boolean).join("\n\n");
|
|
4982
5772
|
const target = useWebhook ? "webhook" : `channel: ${channel}`;
|
|
4983
5773
|
logger.box(`[dry-run] Slack Post Preview (${target})
|
|
4984
5774
|
|
|
@@ -6565,7 +7355,8 @@ async function handleSlackPost({
|
|
|
6565
7355
|
template,
|
|
6566
7356
|
contributors,
|
|
6567
7357
|
packages,
|
|
6568
|
-
postMaxLength: slackConfig.postMaxLength
|
|
7358
|
+
postMaxLength: slackConfig.postMaxLength,
|
|
7359
|
+
changelogFormat: slackConfig.changelogFormat ?? "summary",
|
|
6569
7360
|
dryRun
|
|
6570
7361
|
});
|
|
6571
7362
|
await executeHook("success:slack", config, dryRun);
|
|
@@ -6596,6 +7387,9 @@ async function social(options = {}) {
|
|
|
6596
7387
|
}
|
|
6597
7388
|
});
|
|
6598
7389
|
applyAIOverride(config, options.ai);
|
|
7390
|
+
if (options.slackFormat) {
|
|
7391
|
+
config.social.slack = { ...config.social.slack, changelogFormat: options.slackFormat };
|
|
7392
|
+
}
|
|
6599
7393
|
logger.debug(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
|
|
6600
7394
|
await socialSafetyCheck({ config });
|
|
6601
7395
|
if (!config.release.social && !config.social?.twitter?.enabled && !config.social?.slack?.enabled) {
|
|
@@ -6754,13 +7548,16 @@ async function social(options = {}) {
|
|
|
6754
7548
|
${twitterChangelog}`);
|
|
6755
7549
|
}
|
|
6756
7550
|
}
|
|
6757
|
-
|
|
7551
|
+
const slackChangelogFormat = config.social?.slack?.changelogFormat ?? "summary";
|
|
7552
|
+
const slackBody = slackChangelogFormat === "full" ? richBody : minifiedBody;
|
|
7553
|
+
let slackChangelog = slackBody;
|
|
6758
7554
|
if (hasContent && slackWillPost && isAISocialEnabled(config, "slack")) {
|
|
6759
7555
|
slackChangelog = await generateAISocialChangelog({
|
|
6760
7556
|
config,
|
|
6761
7557
|
rawBody: richBody,
|
|
6762
|
-
fallbackBody:
|
|
6763
|
-
platform: "slack"
|
|
7558
|
+
fallbackBody: slackBody,
|
|
7559
|
+
platform: "slack",
|
|
7560
|
+
maxLength: resolveSlackPostMaxLength({ changelogFormat: slackChangelogFormat, postMaxLength: config.social.slack.postMaxLength })
|
|
6764
7561
|
});
|
|
6765
7562
|
if (dryRun) {
|
|
6766
7563
|
logger.box("[dry-run] AI Slack preview", slackChangelog);
|
|
@@ -7138,8 +7935,9 @@ async function release(options = {}) {
|
|
|
7138
7935
|
bumpResult,
|
|
7139
7936
|
dryRun,
|
|
7140
7937
|
logLevel: config.logLevel,
|
|
7141
|
-
safetyCheck: false
|
|
7938
|
+
safetyCheck: false,
|
|
7142
7939
|
// Already checked in releaseSafetyCheck
|
|
7940
|
+
slackFormat: options.slackFormat
|
|
7143
7941
|
});
|
|
7144
7942
|
} else {
|
|
7145
7943
|
logger.info("Skipping social media posts (--no-social or no social media enabled)");
|
|
@@ -7184,4 +7982,4 @@ async function release(options = {}) {
|
|
|
7184
7982
|
}
|
|
7185
7983
|
}
|
|
7186
7984
|
|
|
7187
|
-
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 };
|