@hyperfrontend/versioning 0.1.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.
Files changed (353) hide show
  1. package/ARCHITECTURE.md +593 -0
  2. package/CHANGELOG.md +35 -0
  3. package/FUNDING.md +141 -0
  4. package/LICENSE.md +21 -0
  5. package/README.md +195 -0
  6. package/SECURITY.md +82 -0
  7. package/changelog/compare/diff.d.ts +128 -0
  8. package/changelog/compare/diff.d.ts.map +1 -0
  9. package/changelog/compare/index.cjs.js +628 -0
  10. package/changelog/compare/index.cjs.js.map +1 -0
  11. package/changelog/compare/index.d.ts +4 -0
  12. package/changelog/compare/index.d.ts.map +1 -0
  13. package/changelog/compare/index.esm.js +612 -0
  14. package/changelog/compare/index.esm.js.map +1 -0
  15. package/changelog/compare/is-equal.d.ts +114 -0
  16. package/changelog/compare/is-equal.d.ts.map +1 -0
  17. package/changelog/index.cjs.js +6448 -0
  18. package/changelog/index.cjs.js.map +1 -0
  19. package/changelog/index.d.ts +6 -0
  20. package/changelog/index.d.ts.map +1 -0
  21. package/changelog/index.esm.js +6358 -0
  22. package/changelog/index.esm.js.map +1 -0
  23. package/changelog/models/changelog.d.ts +86 -0
  24. package/changelog/models/changelog.d.ts.map +1 -0
  25. package/changelog/models/commit-ref.d.ts +51 -0
  26. package/changelog/models/commit-ref.d.ts.map +1 -0
  27. package/changelog/models/entry.d.ts +84 -0
  28. package/changelog/models/entry.d.ts.map +1 -0
  29. package/changelog/models/index.cjs.js +2043 -0
  30. package/changelog/models/index.cjs.js.map +1 -0
  31. package/changelog/models/index.d.ts +11 -0
  32. package/changelog/models/index.d.ts.map +1 -0
  33. package/changelog/models/index.esm.js +2026 -0
  34. package/changelog/models/index.esm.js.map +1 -0
  35. package/changelog/models/schema.d.ts +68 -0
  36. package/changelog/models/schema.d.ts.map +1 -0
  37. package/changelog/models/section.d.ts +25 -0
  38. package/changelog/models/section.d.ts.map +1 -0
  39. package/changelog/operations/add-entry.d.ts +56 -0
  40. package/changelog/operations/add-entry.d.ts.map +1 -0
  41. package/changelog/operations/add-item.d.ts +18 -0
  42. package/changelog/operations/add-item.d.ts.map +1 -0
  43. package/changelog/operations/filter-by-predicate.d.ts +81 -0
  44. package/changelog/operations/filter-by-predicate.d.ts.map +1 -0
  45. package/changelog/operations/filter-by-range.d.ts +63 -0
  46. package/changelog/operations/filter-by-range.d.ts.map +1 -0
  47. package/changelog/operations/filter-entries.d.ts +9 -0
  48. package/changelog/operations/filter-entries.d.ts.map +1 -0
  49. package/changelog/operations/index.cjs.js +2455 -0
  50. package/changelog/operations/index.cjs.js.map +1 -0
  51. package/changelog/operations/index.d.ts +15 -0
  52. package/changelog/operations/index.d.ts.map +1 -0
  53. package/changelog/operations/index.esm.js +2411 -0
  54. package/changelog/operations/index.esm.js.map +1 -0
  55. package/changelog/operations/merge.d.ts +88 -0
  56. package/changelog/operations/merge.d.ts.map +1 -0
  57. package/changelog/operations/remove-entry.d.ts +45 -0
  58. package/changelog/operations/remove-entry.d.ts.map +1 -0
  59. package/changelog/operations/remove-section.d.ts +50 -0
  60. package/changelog/operations/remove-section.d.ts.map +1 -0
  61. package/changelog/operations/transform.d.ts +143 -0
  62. package/changelog/operations/transform.d.ts.map +1 -0
  63. package/changelog/parse/index.cjs.js +1282 -0
  64. package/changelog/parse/index.cjs.js.map +1 -0
  65. package/changelog/parse/index.d.ts +5 -0
  66. package/changelog/parse/index.d.ts.map +1 -0
  67. package/changelog/parse/index.esm.js +1275 -0
  68. package/changelog/parse/index.esm.js.map +1 -0
  69. package/changelog/parse/line.d.ts +48 -0
  70. package/changelog/parse/line.d.ts.map +1 -0
  71. package/changelog/parse/parser.d.ts +16 -0
  72. package/changelog/parse/parser.d.ts.map +1 -0
  73. package/changelog/parse/tokenizer.d.ts +49 -0
  74. package/changelog/parse/tokenizer.d.ts.map +1 -0
  75. package/changelog/serialize/index.cjs.js +574 -0
  76. package/changelog/serialize/index.cjs.js.map +1 -0
  77. package/changelog/serialize/index.d.ts +6 -0
  78. package/changelog/serialize/index.d.ts.map +1 -0
  79. package/changelog/serialize/index.esm.js +564 -0
  80. package/changelog/serialize/index.esm.js.map +1 -0
  81. package/changelog/serialize/templates.d.ts +81 -0
  82. package/changelog/serialize/templates.d.ts.map +1 -0
  83. package/changelog/serialize/to-json.d.ts +57 -0
  84. package/changelog/serialize/to-json.d.ts.map +1 -0
  85. package/changelog/serialize/to-string.d.ts +30 -0
  86. package/changelog/serialize/to-string.d.ts.map +1 -0
  87. package/commits/index.cjs.js +648 -0
  88. package/commits/index.cjs.js.map +1 -0
  89. package/commits/index.d.ts +3 -0
  90. package/commits/index.d.ts.map +1 -0
  91. package/commits/index.esm.js +629 -0
  92. package/commits/index.esm.js.map +1 -0
  93. package/commits/models/breaking.d.ts +39 -0
  94. package/commits/models/breaking.d.ts.map +1 -0
  95. package/commits/models/commit-type.d.ts +32 -0
  96. package/commits/models/commit-type.d.ts.map +1 -0
  97. package/commits/models/conventional.d.ts +49 -0
  98. package/commits/models/conventional.d.ts.map +1 -0
  99. package/commits/models/index.cjs.js +207 -0
  100. package/commits/models/index.cjs.js.map +1 -0
  101. package/commits/models/index.d.ts +7 -0
  102. package/commits/models/index.d.ts.map +1 -0
  103. package/commits/models/index.esm.js +193 -0
  104. package/commits/models/index.esm.js.map +1 -0
  105. package/commits/parse/body.d.ts +18 -0
  106. package/commits/parse/body.d.ts.map +1 -0
  107. package/commits/parse/footer.d.ts +16 -0
  108. package/commits/parse/footer.d.ts.map +1 -0
  109. package/commits/parse/header.d.ts +15 -0
  110. package/commits/parse/header.d.ts.map +1 -0
  111. package/commits/parse/index.cjs.js +505 -0
  112. package/commits/parse/index.cjs.js.map +1 -0
  113. package/commits/parse/index.d.ts +5 -0
  114. package/commits/parse/index.d.ts.map +1 -0
  115. package/commits/parse/index.esm.js +499 -0
  116. package/commits/parse/index.esm.js.map +1 -0
  117. package/commits/parse/message.d.ts +17 -0
  118. package/commits/parse/message.d.ts.map +1 -0
  119. package/commits/utils/replace-char.d.ts +19 -0
  120. package/commits/utils/replace-char.d.ts.map +1 -0
  121. package/flow/executor/execute.d.ts +72 -0
  122. package/flow/executor/execute.d.ts.map +1 -0
  123. package/flow/executor/index.cjs.js +4402 -0
  124. package/flow/executor/index.cjs.js.map +1 -0
  125. package/flow/executor/index.d.ts +3 -0
  126. package/flow/executor/index.d.ts.map +1 -0
  127. package/flow/executor/index.esm.js +4398 -0
  128. package/flow/executor/index.esm.js.map +1 -0
  129. package/flow/factory.d.ts +58 -0
  130. package/flow/factory.d.ts.map +1 -0
  131. package/flow/index.cjs.js +8506 -0
  132. package/flow/index.cjs.js.map +1 -0
  133. package/flow/index.d.ts +7 -0
  134. package/flow/index.d.ts.map +1 -0
  135. package/flow/index.esm.js +8451 -0
  136. package/flow/index.esm.js.map +1 -0
  137. package/flow/models/flow.d.ts +130 -0
  138. package/flow/models/flow.d.ts.map +1 -0
  139. package/flow/models/index.cjs.js +285 -0
  140. package/flow/models/index.cjs.js.map +1 -0
  141. package/flow/models/index.d.ts +7 -0
  142. package/flow/models/index.d.ts.map +1 -0
  143. package/flow/models/index.esm.js +268 -0
  144. package/flow/models/index.esm.js.map +1 -0
  145. package/flow/models/step.d.ts +108 -0
  146. package/flow/models/step.d.ts.map +1 -0
  147. package/flow/models/types.d.ts +150 -0
  148. package/flow/models/types.d.ts.map +1 -0
  149. package/flow/presets/conventional.d.ts +59 -0
  150. package/flow/presets/conventional.d.ts.map +1 -0
  151. package/flow/presets/independent.d.ts +61 -0
  152. package/flow/presets/independent.d.ts.map +1 -0
  153. package/flow/presets/index.cjs.js +3903 -0
  154. package/flow/presets/index.cjs.js.map +1 -0
  155. package/flow/presets/index.d.ts +4 -0
  156. package/flow/presets/index.d.ts.map +1 -0
  157. package/flow/presets/index.esm.js +3889 -0
  158. package/flow/presets/index.esm.js.map +1 -0
  159. package/flow/presets/synced.d.ts +65 -0
  160. package/flow/presets/synced.d.ts.map +1 -0
  161. package/flow/steps/analyze-commits.d.ts +19 -0
  162. package/flow/steps/analyze-commits.d.ts.map +1 -0
  163. package/flow/steps/calculate-bump.d.ts +27 -0
  164. package/flow/steps/calculate-bump.d.ts.map +1 -0
  165. package/flow/steps/create-commit.d.ts +16 -0
  166. package/flow/steps/create-commit.d.ts.map +1 -0
  167. package/flow/steps/create-tag.d.ts +22 -0
  168. package/flow/steps/create-tag.d.ts.map +1 -0
  169. package/flow/steps/fetch-registry.d.ts +19 -0
  170. package/flow/steps/fetch-registry.d.ts.map +1 -0
  171. package/flow/steps/generate-changelog.d.ts +25 -0
  172. package/flow/steps/generate-changelog.d.ts.map +1 -0
  173. package/flow/steps/index.cjs.js +3523 -0
  174. package/flow/steps/index.cjs.js.map +1 -0
  175. package/flow/steps/index.d.ts +8 -0
  176. package/flow/steps/index.d.ts.map +1 -0
  177. package/flow/steps/index.esm.js +3504 -0
  178. package/flow/steps/index.esm.js.map +1 -0
  179. package/flow/steps/update-packages.d.ts +25 -0
  180. package/flow/steps/update-packages.d.ts.map +1 -0
  181. package/flow/utils/interpolate.d.ts +11 -0
  182. package/flow/utils/interpolate.d.ts.map +1 -0
  183. package/git/factory.d.ts +233 -0
  184. package/git/factory.d.ts.map +1 -0
  185. package/git/index.cjs.js +2863 -0
  186. package/git/index.cjs.js.map +1 -0
  187. package/git/index.d.ts +5 -0
  188. package/git/index.d.ts.map +1 -0
  189. package/git/index.esm.js +2785 -0
  190. package/git/index.esm.js.map +1 -0
  191. package/git/models/commit.d.ts +129 -0
  192. package/git/models/commit.d.ts.map +1 -0
  193. package/git/models/index.cjs.js +755 -0
  194. package/git/models/index.cjs.js.map +1 -0
  195. package/git/models/index.d.ts +7 -0
  196. package/git/models/index.d.ts.map +1 -0
  197. package/git/models/index.esm.js +729 -0
  198. package/git/models/index.esm.js.map +1 -0
  199. package/git/models/ref.d.ts +120 -0
  200. package/git/models/ref.d.ts.map +1 -0
  201. package/git/models/tag.d.ts +141 -0
  202. package/git/models/tag.d.ts.map +1 -0
  203. package/git/operations/commit.d.ts +97 -0
  204. package/git/operations/commit.d.ts.map +1 -0
  205. package/git/operations/head-info.d.ts +29 -0
  206. package/git/operations/head-info.d.ts.map +1 -0
  207. package/git/operations/index.cjs.js +1954 -0
  208. package/git/operations/index.cjs.js.map +1 -0
  209. package/git/operations/index.d.ts +14 -0
  210. package/git/operations/index.d.ts.map +1 -0
  211. package/git/operations/index.esm.js +1903 -0
  212. package/git/operations/index.esm.js.map +1 -0
  213. package/git/operations/log.d.ts +104 -0
  214. package/git/operations/log.d.ts.map +1 -0
  215. package/git/operations/manage-tags.d.ts +60 -0
  216. package/git/operations/manage-tags.d.ts.map +1 -0
  217. package/git/operations/query-tags.d.ts +88 -0
  218. package/git/operations/query-tags.d.ts.map +1 -0
  219. package/git/operations/stage.d.ts +66 -0
  220. package/git/operations/stage.d.ts.map +1 -0
  221. package/git/operations/status.d.ts +173 -0
  222. package/git/operations/status.d.ts.map +1 -0
  223. package/index.cjs.js +16761 -0
  224. package/index.cjs.js.map +1 -0
  225. package/index.d.ts +102 -0
  226. package/index.d.ts.map +1 -0
  227. package/index.esm.js +16427 -0
  228. package/index.esm.js.map +1 -0
  229. package/package.json +200 -0
  230. package/registry/factory.d.ts +18 -0
  231. package/registry/factory.d.ts.map +1 -0
  232. package/registry/index.cjs.js +543 -0
  233. package/registry/index.cjs.js.map +1 -0
  234. package/registry/index.d.ts +5 -0
  235. package/registry/index.d.ts.map +1 -0
  236. package/registry/index.esm.js +535 -0
  237. package/registry/index.esm.js.map +1 -0
  238. package/registry/models/index.cjs.js +69 -0
  239. package/registry/models/index.cjs.js.map +1 -0
  240. package/registry/models/index.d.ts +6 -0
  241. package/registry/models/index.d.ts.map +1 -0
  242. package/registry/models/index.esm.js +66 -0
  243. package/registry/models/index.esm.js.map +1 -0
  244. package/registry/models/package-info.d.ts +55 -0
  245. package/registry/models/package-info.d.ts.map +1 -0
  246. package/registry/models/registry.d.ts +62 -0
  247. package/registry/models/registry.d.ts.map +1 -0
  248. package/registry/models/version-info.d.ts +67 -0
  249. package/registry/models/version-info.d.ts.map +1 -0
  250. package/registry/npm/cache.d.ts +50 -0
  251. package/registry/npm/cache.d.ts.map +1 -0
  252. package/registry/npm/client.d.ts +30 -0
  253. package/registry/npm/client.d.ts.map +1 -0
  254. package/registry/npm/index.cjs.js +456 -0
  255. package/registry/npm/index.cjs.js.map +1 -0
  256. package/registry/npm/index.d.ts +4 -0
  257. package/registry/npm/index.d.ts.map +1 -0
  258. package/registry/npm/index.esm.js +451 -0
  259. package/registry/npm/index.esm.js.map +1 -0
  260. package/semver/compare/compare.d.ts +100 -0
  261. package/semver/compare/compare.d.ts.map +1 -0
  262. package/semver/compare/index.cjs.js +386 -0
  263. package/semver/compare/index.cjs.js.map +1 -0
  264. package/semver/compare/index.d.ts +3 -0
  265. package/semver/compare/index.d.ts.map +1 -0
  266. package/semver/compare/index.esm.js +370 -0
  267. package/semver/compare/index.esm.js.map +1 -0
  268. package/semver/compare/sort.d.ts +36 -0
  269. package/semver/compare/sort.d.ts.map +1 -0
  270. package/semver/format/index.cjs.js +58 -0
  271. package/semver/format/index.cjs.js.map +1 -0
  272. package/semver/format/index.d.ts +2 -0
  273. package/semver/format/index.d.ts.map +1 -0
  274. package/semver/format/index.esm.js +53 -0
  275. package/semver/format/index.esm.js.map +1 -0
  276. package/semver/format/to-string.d.ts +31 -0
  277. package/semver/format/to-string.d.ts.map +1 -0
  278. package/semver/increment/bump.d.ts +37 -0
  279. package/semver/increment/bump.d.ts.map +1 -0
  280. package/semver/increment/index.cjs.js +223 -0
  281. package/semver/increment/index.cjs.js.map +1 -0
  282. package/semver/increment/index.d.ts +2 -0
  283. package/semver/increment/index.d.ts.map +1 -0
  284. package/semver/increment/index.esm.js +219 -0
  285. package/semver/increment/index.esm.js.map +1 -0
  286. package/semver/index.cjs.js +1499 -0
  287. package/semver/index.cjs.js.map +1 -0
  288. package/semver/index.d.ts +6 -0
  289. package/semver/index.d.ts.map +1 -0
  290. package/semver/index.esm.js +1458 -0
  291. package/semver/index.esm.js.map +1 -0
  292. package/semver/models/index.cjs.js +153 -0
  293. package/semver/models/index.cjs.js.map +1 -0
  294. package/semver/models/index.d.ts +5 -0
  295. package/semver/models/index.d.ts.map +1 -0
  296. package/semver/models/index.esm.js +139 -0
  297. package/semver/models/index.esm.js.map +1 -0
  298. package/semver/models/range.d.ts +83 -0
  299. package/semver/models/range.d.ts.map +1 -0
  300. package/semver/models/version.d.ts +78 -0
  301. package/semver/models/version.d.ts.map +1 -0
  302. package/semver/parse/index.cjs.js +799 -0
  303. package/semver/parse/index.cjs.js.map +1 -0
  304. package/semver/parse/index.d.ts +5 -0
  305. package/semver/parse/index.d.ts.map +1 -0
  306. package/semver/parse/index.esm.js +793 -0
  307. package/semver/parse/index.esm.js.map +1 -0
  308. package/semver/parse/range.d.ts +38 -0
  309. package/semver/parse/range.d.ts.map +1 -0
  310. package/semver/parse/version.d.ts +49 -0
  311. package/semver/parse/version.d.ts.map +1 -0
  312. package/workspace/discovery/changelog-path.d.ts +21 -0
  313. package/workspace/discovery/changelog-path.d.ts.map +1 -0
  314. package/workspace/discovery/dependencies.d.ts +145 -0
  315. package/workspace/discovery/dependencies.d.ts.map +1 -0
  316. package/workspace/discovery/discover-changelogs.d.ts +76 -0
  317. package/workspace/discovery/discover-changelogs.d.ts.map +1 -0
  318. package/workspace/discovery/index.cjs.js +2300 -0
  319. package/workspace/discovery/index.cjs.js.map +1 -0
  320. package/workspace/discovery/index.d.ts +13 -0
  321. package/workspace/discovery/index.d.ts.map +1 -0
  322. package/workspace/discovery/index.esm.js +2283 -0
  323. package/workspace/discovery/index.esm.js.map +1 -0
  324. package/workspace/discovery/packages.d.ts +83 -0
  325. package/workspace/discovery/packages.d.ts.map +1 -0
  326. package/workspace/index.cjs.js +4445 -0
  327. package/workspace/index.cjs.js.map +1 -0
  328. package/workspace/index.d.ts +52 -0
  329. package/workspace/index.d.ts.map +1 -0
  330. package/workspace/index.esm.js +4394 -0
  331. package/workspace/index.esm.js.map +1 -0
  332. package/workspace/models/index.cjs.js +284 -0
  333. package/workspace/models/index.cjs.js.map +1 -0
  334. package/workspace/models/index.d.ts +10 -0
  335. package/workspace/models/index.d.ts.map +1 -0
  336. package/workspace/models/index.esm.js +261 -0
  337. package/workspace/models/index.esm.js.map +1 -0
  338. package/workspace/models/project.d.ts +118 -0
  339. package/workspace/models/project.d.ts.map +1 -0
  340. package/workspace/models/workspace.d.ts +139 -0
  341. package/workspace/models/workspace.d.ts.map +1 -0
  342. package/workspace/operations/batch-update.d.ts +99 -0
  343. package/workspace/operations/batch-update.d.ts.map +1 -0
  344. package/workspace/operations/cascade-bump.d.ts +125 -0
  345. package/workspace/operations/cascade-bump.d.ts.map +1 -0
  346. package/workspace/operations/index.cjs.js +2675 -0
  347. package/workspace/operations/index.cjs.js.map +1 -0
  348. package/workspace/operations/index.d.ts +12 -0
  349. package/workspace/operations/index.d.ts.map +1 -0
  350. package/workspace/operations/index.esm.js +2663 -0
  351. package/workspace/operations/index.esm.js.map +1 -0
  352. package/workspace/operations/validate.d.ts +85 -0
  353. package/workspace/operations/validate.d.ts.map +1 -0
@@ -0,0 +1,1282 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Creates a new changelog item.
5
+ *
6
+ * @param description - The description text of the change
7
+ * @param options - Optional configuration for scope, commits, references, and breaking flag
8
+ * @returns A new ChangelogItem object
9
+ */
10
+ function createChangelogItem(description, options) {
11
+ return {
12
+ description,
13
+ scope: options?.scope,
14
+ commits: options?.commits ?? [],
15
+ references: options?.references ?? [],
16
+ breaking: options?.breaking ?? false,
17
+ };
18
+ }
19
+
20
+ /**
21
+ * Maps section headings to their canonical types.
22
+ * Used during parsing to normalize different heading styles.
23
+ */
24
+ const SECTION_TYPE_MAP = {
25
+ // Breaking changes
26
+ 'breaking changes': 'breaking',
27
+ breaking: 'breaking',
28
+ 'breaking change': 'breaking',
29
+ // Features
30
+ features: 'features',
31
+ feature: 'features',
32
+ added: 'features',
33
+ new: 'features',
34
+ // Fixes
35
+ fixes: 'fixes',
36
+ fix: 'fixes',
37
+ 'bug fixes': 'fixes',
38
+ bugfixes: 'fixes',
39
+ fixed: 'fixes',
40
+ // Performance
41
+ performance: 'performance',
42
+ 'performance improvements': 'performance',
43
+ perf: 'performance',
44
+ // Documentation
45
+ documentation: 'documentation',
46
+ docs: 'documentation',
47
+ // Deprecations
48
+ deprecations: 'deprecations',
49
+ deprecated: 'deprecations',
50
+ // Refactoring
51
+ refactoring: 'refactoring',
52
+ refactor: 'refactoring',
53
+ 'code refactoring': 'refactoring',
54
+ // Tests
55
+ tests: 'tests',
56
+ test: 'tests',
57
+ testing: 'tests',
58
+ // Build
59
+ build: 'build',
60
+ 'build system': 'build',
61
+ dependencies: 'build',
62
+ // CI
63
+ ci: 'ci',
64
+ 'continuous integration': 'ci',
65
+ // Chores
66
+ chores: 'chores',
67
+ chore: 'chores',
68
+ maintenance: 'chores',
69
+ // Other
70
+ other: 'other',
71
+ miscellaneous: 'other',
72
+ misc: 'other',
73
+ changed: 'other',
74
+ changes: 'other',
75
+ removed: 'other',
76
+ security: 'other',
77
+ };
78
+ /**
79
+ * Determines the section type from a heading string.
80
+ * Returns 'other' if the heading is not recognized.
81
+ *
82
+ * @param heading - The heading string to parse
83
+ * @returns The corresponding ChangelogSectionType
84
+ */
85
+ function getSectionType(heading) {
86
+ const normalized = heading.toLowerCase().trim();
87
+ return SECTION_TYPE_MAP[normalized] ?? 'other';
88
+ }
89
+
90
+ /**
91
+ * Safe copies of Math built-in methods.
92
+ *
93
+ * These references are captured at module initialization time to protect against
94
+ * prototype pollution attacks. Import only what you need for tree-shaking.
95
+ *
96
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/math
97
+ */
98
+ // Capture references at module initialization time
99
+ const _Math = globalThis.Math;
100
+ // ============================================================================
101
+ // Min/Max
102
+ // ============================================================================
103
+ /**
104
+ * (Safe copy) Returns the larger of zero or more numbers.
105
+ */
106
+ const max = _Math.max;
107
+
108
+ /**
109
+ * Safe copies of Number built-in methods and constants.
110
+ *
111
+ * These references are captured at module initialization time to protect against
112
+ * prototype pollution attacks. Import only what you need for tree-shaking.
113
+ *
114
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/number
115
+ */
116
+ // Capture references at module initialization time
117
+ const _parseInt = globalThis.parseInt;
118
+ // ============================================================================
119
+ // Parsing
120
+ // ============================================================================
121
+ /**
122
+ * (Safe copy) Parses a string and returns an integer.
123
+ */
124
+ const parseInt = _parseInt;
125
+
126
+ /**
127
+ * Line Parser
128
+ *
129
+ * Utilities for parsing individual changelog lines without regex.
130
+ */
131
+ /**
132
+ * Parses a version string from a heading.
133
+ * Examples: "1.2.3", "v1.2.3", "[1.2.3]", "1.2.3 - 2024-01-01"
134
+ *
135
+ * @param heading - The heading string to parse
136
+ * @returns An object containing the parsed version, date, and optional compareUrl
137
+ */
138
+ function parseVersionFromHeading(heading) {
139
+ const trimmed = heading.trim();
140
+ // Check for unreleased
141
+ const lowerHeading = trimmed.toLowerCase();
142
+ if (lowerHeading === 'unreleased' || lowerHeading === '[unreleased]') {
143
+ return { version: 'Unreleased', date: null };
144
+ }
145
+ let pos = 0;
146
+ let version = '';
147
+ let date = null;
148
+ let compareUrl;
149
+ // Skip leading [ if present
150
+ if (trimmed[pos] === '[') {
151
+ pos++;
152
+ }
153
+ // Skip leading 'v' if present
154
+ if (trimmed[pos] === 'v' || trimmed[pos] === 'V') {
155
+ pos++;
156
+ }
157
+ // Parse version number (digits and dots)
158
+ const versionStart = pos;
159
+ while (pos < trimmed.length) {
160
+ const char = trimmed[pos];
161
+ const code = char.charCodeAt(0);
162
+ // Allow digits, dots, hyphens (for prerelease), plus signs
163
+ if ((code >= 48 && code <= 57) || // 0-9
164
+ char === '.' ||
165
+ char === '-' ||
166
+ char === '+' ||
167
+ (code >= 97 && code <= 122) || // a-z (for prerelease tags like alpha, beta, rc)
168
+ (code >= 65 && code <= 90) // A-Z
169
+ ) {
170
+ pos++;
171
+ }
172
+ else {
173
+ break;
174
+ }
175
+ }
176
+ version = trimmed.slice(versionStart, pos);
177
+ // Skip trailing ] if present
178
+ if (trimmed[pos] === ']') {
179
+ pos++;
180
+ }
181
+ // Skip whitespace and separator
182
+ while (pos < trimmed.length && (trimmed[pos] === ' ' || trimmed[pos] === '-' || trimmed[pos] === '–')) {
183
+ pos++;
184
+ }
185
+ // Try to parse date (YYYY-MM-DD format)
186
+ if (pos < trimmed.length) {
187
+ const dateMatch = extractDate(trimmed.slice(pos));
188
+ if (dateMatch) {
189
+ date = dateMatch.date;
190
+ pos += dateMatch.length;
191
+ }
192
+ }
193
+ // Skip to check for compare URL (in parentheses or link)
194
+ while (pos < trimmed.length && trimmed[pos] === ' ') {
195
+ pos++;
196
+ }
197
+ // Check for link at end: [compare](url)
198
+ if (pos < trimmed.length) {
199
+ const linkMatch = extractLink(trimmed.slice(pos));
200
+ if (linkMatch?.url) {
201
+ compareUrl = linkMatch.url;
202
+ }
203
+ }
204
+ return { version, date, compareUrl };
205
+ }
206
+ /**
207
+ * Extracts a date in YYYY-MM-DD format from a string.
208
+ *
209
+ * @param str - The string to extract a date from
210
+ * @returns The extracted date and its length, or null if no date found
211
+ */
212
+ function extractDate(str) {
213
+ let pos = 0;
214
+ // Skip optional parentheses
215
+ if (str[pos] === '(')
216
+ pos++;
217
+ // Parse year (4 digits)
218
+ const yearStart = pos;
219
+ while (pos < str.length && pos - yearStart < 4) {
220
+ const code = str.charCodeAt(pos);
221
+ if (code >= 48 && code <= 57) {
222
+ pos++;
223
+ }
224
+ else {
225
+ break;
226
+ }
227
+ }
228
+ if (pos - yearStart !== 4)
229
+ return null;
230
+ // Expect - or /
231
+ if (str[pos] !== '-' && str[pos] !== '/')
232
+ return null;
233
+ const separator = str[pos];
234
+ pos++;
235
+ // Parse month (2 digits)
236
+ const monthStart = pos;
237
+ while (pos < str.length && pos - monthStart < 2) {
238
+ const code = str.charCodeAt(pos);
239
+ if (code >= 48 && code <= 57) {
240
+ pos++;
241
+ }
242
+ else {
243
+ break;
244
+ }
245
+ }
246
+ if (pos - monthStart !== 2)
247
+ return null;
248
+ // Expect same separator
249
+ if (str[pos] !== separator)
250
+ return null;
251
+ pos++;
252
+ // Parse day (2 digits)
253
+ const dayStart = pos;
254
+ while (pos < str.length && pos - dayStart < 2) {
255
+ const code = str.charCodeAt(pos);
256
+ if (code >= 48 && code <= 57) {
257
+ pos++;
258
+ }
259
+ else {
260
+ break;
261
+ }
262
+ }
263
+ if (pos - dayStart !== 2)
264
+ return null;
265
+ // Skip optional closing parenthesis
266
+ if (str[pos] === ')')
267
+ pos++;
268
+ const dateStr = str.slice(yearStart, dayStart + 2);
269
+ const date = slashToHyphen(dateStr);
270
+ return { date, length: pos };
271
+ }
272
+ /**
273
+ * Replaces forward slashes with hyphens (ReDoS-safe).
274
+ *
275
+ * @param input - The input string
276
+ * @returns String with forward slashes replaced by hyphens
277
+ */
278
+ function slashToHyphen(input) {
279
+ const result = [];
280
+ for (let i = 0; i < input.length; i++) {
281
+ result.push(input[i] === '/' ? '-' : input[i]);
282
+ }
283
+ return result.join('');
284
+ }
285
+ /**
286
+ * Extracts a markdown link from a string.
287
+ *
288
+ * @param str - The string to extract a link from
289
+ * @returns The extracted link text, url, and length, or null if no link found
290
+ */
291
+ function extractLink(str) {
292
+ if (str[0] !== '[')
293
+ return null;
294
+ let pos = 1;
295
+ let depth = 1;
296
+ // Find closing ]
297
+ while (pos < str.length && depth > 0) {
298
+ if (str[pos] === '[')
299
+ depth++;
300
+ else if (str[pos] === ']')
301
+ depth--;
302
+ pos++;
303
+ }
304
+ if (depth !== 0)
305
+ return null;
306
+ const text = str.slice(1, pos - 1);
307
+ // Expect (
308
+ if (str[pos] !== '(')
309
+ return null;
310
+ pos++;
311
+ const urlStart = pos;
312
+ depth = 1;
313
+ while (pos < str.length && depth > 0) {
314
+ if (str[pos] === '(')
315
+ depth++;
316
+ else if (str[pos] === ')')
317
+ depth--;
318
+ pos++;
319
+ }
320
+ if (depth !== 0)
321
+ return null;
322
+ const url = str.slice(urlStart, pos - 1);
323
+ return { text, url, length: pos };
324
+ }
325
+ /**
326
+ * Parses commit references from a line.
327
+ * Examples: (abc1234), [abc1234], commit abc1234
328
+ *
329
+ * @param text - The text to parse for commit references
330
+ * @param baseUrl - Optional base URL for constructing commit links
331
+ * @returns An array of parsed CommitRef objects
332
+ */
333
+ function parseCommitRefs(text, baseUrl) {
334
+ const refs = [];
335
+ let pos = 0;
336
+ while (pos < text.length) {
337
+ // Look for potential hash patterns
338
+ // Common formats: (abc1234), [abc1234], abc1234fabcdef
339
+ // Check for parenthetical hash
340
+ if (text[pos] === '(' || text[pos] === '[') {
341
+ const closeChar = text[pos] === '(' ? ')' : ']';
342
+ const start = pos + 1;
343
+ pos++;
344
+ // Read potential hash
345
+ while (pos < text.length && isHexDigit(text[pos])) {
346
+ pos++;
347
+ }
348
+ // Check if valid hash (7-40 hex chars)
349
+ const hash = text.slice(start, pos);
350
+ if (hash.length >= 7 && hash.length <= 40 && text[pos] === closeChar) {
351
+ refs.push({
352
+ hash,
353
+ shortHash: hash.slice(0, 7),
354
+ url: baseUrl ? `${baseUrl}/commit/${hash}` : undefined,
355
+ });
356
+ pos++; // skip closing bracket
357
+ continue;
358
+ }
359
+ }
360
+ pos++;
361
+ }
362
+ return refs;
363
+ }
364
+ /**
365
+ * Parses issue/PR references from a line.
366
+ * Examples: #123, GH-123, closes #123
367
+ *
368
+ * @param text - The text to parse for issue references
369
+ * @param baseUrl - Optional base URL for constructing issue links
370
+ * @returns An array of parsed IssueRef objects
371
+ */
372
+ function parseIssueRefs(text, baseUrl) {
373
+ const refs = [];
374
+ let pos = 0;
375
+ while (pos < text.length) {
376
+ // Look for # followed by digits
377
+ if (text[pos] === '#') {
378
+ pos++;
379
+ const numStart = pos;
380
+ while (pos < text.length && isDigitChar(text[pos])) {
381
+ pos++;
382
+ }
383
+ if (pos > numStart) {
384
+ const number = parseInt(text.slice(numStart, pos), 10);
385
+ // Check context for PR vs issue
386
+ const beforeHash = text.slice(max(0, numStart - 10), numStart - 1).toLowerCase();
387
+ const type = beforeHash.includes('pr') || beforeHash.includes('pull') ? 'pull-request' : 'issue';
388
+ refs.push({
389
+ number,
390
+ type,
391
+ url: baseUrl ? `${baseUrl}/issues/${number}` : undefined,
392
+ });
393
+ continue;
394
+ }
395
+ }
396
+ pos++;
397
+ }
398
+ return refs;
399
+ }
400
+ /**
401
+ * Parses the scope from a changelog item.
402
+ * Example: "**scope:** description" -> { scope: "scope", description: "description" }
403
+ *
404
+ * @param text - The text to parse for scope
405
+ * @returns An object with optional scope and the description
406
+ */
407
+ function parseScopeFromItem(text) {
408
+ const trimmed = text.trim();
409
+ // Check for **scope:** pattern (colon inside or outside bold)
410
+ if (trimmed.startsWith('**')) {
411
+ let pos = 2;
412
+ const scopeStart = pos;
413
+ // Read until ** or :
414
+ while (pos < trimmed.length && trimmed[pos] !== '*' && trimmed[pos] !== ':') {
415
+ pos++;
416
+ }
417
+ // Handle **scope:** pattern (colon before closing **)
418
+ if (trimmed[pos] === ':' && trimmed[pos + 1] === '*' && trimmed[pos + 2] === '*') {
419
+ const scope = trimmed.slice(scopeStart, pos);
420
+ pos += 3; // skip :**
421
+ // Skip whitespace
422
+ while (trimmed[pos] === ' ')
423
+ pos++;
424
+ return { scope, description: trimmed.slice(pos) };
425
+ }
426
+ // Handle **scope**: pattern (colon after closing **)
427
+ if (trimmed[pos] === '*' && trimmed[pos + 1] === '*') {
428
+ const scope = trimmed.slice(scopeStart, pos);
429
+ pos += 2; // skip **
430
+ // Skip : if present
431
+ if (trimmed[pos] === ':')
432
+ pos++;
433
+ // Skip whitespace
434
+ while (trimmed[pos] === ' ')
435
+ pos++;
436
+ return { scope, description: trimmed.slice(pos) };
437
+ }
438
+ }
439
+ // Check for scope: pattern (without bold)
440
+ const colonPos = trimmed.indexOf(':');
441
+ if (colonPos > 0 && colonPos < 30) {
442
+ // scope shouldn't be too long
443
+ const potentialScope = trimmed.slice(0, colonPos);
444
+ // Scope should be a simple identifier (letters, numbers, hyphens)
445
+ if (isValidScope(potentialScope)) {
446
+ const description = trimmed.slice(colonPos + 1).trim();
447
+ return { scope: potentialScope, description };
448
+ }
449
+ }
450
+ return { description: trimmed };
451
+ }
452
+ /**
453
+ * Checks if a string is a valid scope (alphanumeric with hyphens).
454
+ *
455
+ * @param str - The string to check
456
+ * @returns True if the string is a valid scope identifier
457
+ */
458
+ function isValidScope(str) {
459
+ if (!str || str.length === 0)
460
+ return false;
461
+ for (let i = 0; i < str.length; i++) {
462
+ const code = str.charCodeAt(i);
463
+ if (!(code >= 48 && code <= 57) && // 0-9
464
+ !(code >= 65 && code <= 90) && // A-Z
465
+ !(code >= 97 && code <= 122) && // a-z
466
+ code !== 45 // -
467
+ ) {
468
+ return false;
469
+ }
470
+ }
471
+ return true;
472
+ }
473
+ /**
474
+ * Checks if a character is a hex digit.
475
+ *
476
+ * @param char - The character to check
477
+ * @returns True if the character is a hex digit (0-9, A-F, a-f)
478
+ */
479
+ function isHexDigit(char) {
480
+ const code = char.charCodeAt(0);
481
+ return ((code >= 48 && code <= 57) || // 0-9
482
+ (code >= 65 && code <= 70) || // A-F
483
+ (code >= 97 && code <= 102) // a-f
484
+ );
485
+ }
486
+ /**
487
+ * Checks if a character is a digit.
488
+ *
489
+ * @param char - The character to check
490
+ * @returns True if the character is a digit (0-9)
491
+ */
492
+ function isDigitChar(char) {
493
+ const code = char.charCodeAt(0);
494
+ return code >= 48 && code <= 57;
495
+ }
496
+
497
+ /**
498
+ * Safe copies of Error built-ins via factory functions.
499
+ *
500
+ * Since constructors cannot be safely captured via Object.assign, this module
501
+ * provides factory functions that use Reflect.construct internally.
502
+ *
503
+ * These references are captured at module initialization time to protect against
504
+ * prototype pollution attacks. Import only what you need for tree-shaking.
505
+ *
506
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/error
507
+ */
508
+ // Capture references at module initialization time
509
+ const _Error = globalThis.Error;
510
+ const _Reflect = globalThis.Reflect;
511
+ /**
512
+ * (Safe copy) Creates a new Error using the captured Error constructor.
513
+ * Use this instead of `new Error()`.
514
+ *
515
+ * @param message - Optional error message.
516
+ * @param options - Optional error options.
517
+ * @returns A new Error instance.
518
+ */
519
+ const createError = (message, options) => _Reflect.construct(_Error, [message, options]);
520
+
521
+ /**
522
+ * Changelog Tokenizer
523
+ *
524
+ * A state machine tokenizer that processes markdown character-by-character.
525
+ * No regex is used to ensure ReDoS safety.
526
+ */
527
+ /**
528
+ * Maximum input length to prevent memory exhaustion (1MB)
529
+ */
530
+ const MAX_INPUT_LENGTH = 1024 * 1024;
531
+ /**
532
+ * Tokenizes a changelog markdown string into tokens.
533
+ *
534
+ * @param input - The markdown content to tokenize
535
+ * @returns Array of tokens
536
+ * @throws {Error} If input exceeds maximum length
537
+ */
538
+ function tokenize(input) {
539
+ if (input.length > MAX_INPUT_LENGTH) {
540
+ throw createError(`Input exceeds maximum length of ${MAX_INPUT_LENGTH} characters`);
541
+ }
542
+ const state = {
543
+ pos: 0,
544
+ line: 1,
545
+ column: 1,
546
+ input,
547
+ tokens: [],
548
+ };
549
+ while (state.pos < state.input.length) {
550
+ const char = state.input[state.pos];
551
+ // Check for newline
552
+ if (char === '\n') {
553
+ consumeNewline(state);
554
+ continue;
555
+ }
556
+ // Check for carriage return (handle \r\n)
557
+ if (char === '\r') {
558
+ state.pos++;
559
+ if (state.input[state.pos] === '\n') {
560
+ consumeNewline(state);
561
+ }
562
+ continue;
563
+ }
564
+ // At start of line, check for special markers
565
+ if (state.column === 1) {
566
+ // Check for heading
567
+ if (char === '#') {
568
+ consumeHeading(state);
569
+ continue;
570
+ }
571
+ // Check for list item
572
+ if ((char === '-' || char === '*') && isWhitespace(state.input[state.pos + 1])) {
573
+ consumeListItem(state);
574
+ continue;
575
+ }
576
+ }
577
+ // Check for inline markdown elements
578
+ if (char === '[') {
579
+ consumeLink(state);
580
+ continue;
581
+ }
582
+ if (char === '`') {
583
+ consumeCode(state);
584
+ continue;
585
+ }
586
+ if (char === '*' && state.input[state.pos + 1] === '*') {
587
+ consumeBold(state);
588
+ continue;
589
+ }
590
+ // Default: consume as text
591
+ consumeText(state);
592
+ }
593
+ // Add EOF token
594
+ pushToken(state, 'eof', '');
595
+ return state.tokens;
596
+ }
597
+ /**
598
+ * Consumes a newline character.
599
+ *
600
+ * @param state - The tokenizer state to update
601
+ */
602
+ function consumeNewline(state) {
603
+ // Check if this is a blank line (previous token was also newline or at start)
604
+ const prevToken = state.tokens[state.tokens.length - 1];
605
+ const isBlank = prevToken?.type === 'newline' || prevToken?.type === 'blank-line' || state.tokens.length === 0;
606
+ pushToken(state, isBlank ? 'blank-line' : 'newline', '\n');
607
+ state.pos++;
608
+ state.line++;
609
+ state.column = 1;
610
+ }
611
+ /**
612
+ * Consumes a heading (# through ####).
613
+ *
614
+ * @param state - The tokenizer state to update
615
+ */
616
+ function consumeHeading(state) {
617
+ const startColumn = state.column;
618
+ let level = 0;
619
+ // Count # characters
620
+ while (state.input[state.pos] === '#' && level < 4) {
621
+ state.pos++;
622
+ state.column++;
623
+ level++;
624
+ }
625
+ // Skip whitespace after #
626
+ while (isWhitespace(state.input[state.pos]) && state.input[state.pos] !== '\n') {
627
+ state.pos++;
628
+ state.column++;
629
+ }
630
+ // Consume the rest of the line as heading content
631
+ const contentStart = state.pos;
632
+ while (state.pos < state.input.length && state.input[state.pos] !== '\n') {
633
+ state.pos++;
634
+ state.column++;
635
+ }
636
+ const content = state.input.slice(contentStart, state.pos);
637
+ const type = `heading-${level}`;
638
+ state.tokens.push({
639
+ type,
640
+ value: content.trim(),
641
+ line: state.line,
642
+ column: startColumn,
643
+ });
644
+ }
645
+ /**
646
+ * Consumes a list item (- or *).
647
+ *
648
+ * @param state - The tokenizer state to update
649
+ */
650
+ function consumeListItem(state) {
651
+ const startColumn = state.column;
652
+ // Skip the marker and whitespace
653
+ state.pos++; // skip - or *
654
+ state.column++;
655
+ while (isWhitespace(state.input[state.pos]) && state.input[state.pos] !== '\n') {
656
+ state.pos++;
657
+ state.column++;
658
+ }
659
+ // Consume the rest of the line as list item content
660
+ const contentStart = state.pos;
661
+ while (state.pos < state.input.length && state.input[state.pos] !== '\n') {
662
+ state.pos++;
663
+ state.column++;
664
+ }
665
+ const content = state.input.slice(contentStart, state.pos);
666
+ state.tokens.push({
667
+ type: 'list-item',
668
+ value: content,
669
+ line: state.line,
670
+ column: startColumn,
671
+ });
672
+ }
673
+ /**
674
+ * Consumes a markdown link [text](url).
675
+ *
676
+ * @param state - The tokenizer state to update
677
+ */
678
+ function consumeLink(state) {
679
+ const startColumn = state.column;
680
+ const startLine = state.line;
681
+ // Skip opening [
682
+ state.pos++;
683
+ state.column++;
684
+ // Find closing ]
685
+ const textStart = state.pos;
686
+ let depth = 1;
687
+ while (state.pos < state.input.length && depth > 0) {
688
+ const char = state.input[state.pos];
689
+ if (char === '[')
690
+ depth++;
691
+ else if (char === ']')
692
+ depth--;
693
+ else if (char === '\n') {
694
+ // Link text shouldn't span lines; emit '[' as text and reset
695
+ state.tokens.push({
696
+ type: 'text',
697
+ value: '[',
698
+ line: startLine,
699
+ column: startColumn,
700
+ });
701
+ state.pos = textStart;
702
+ state.column = startColumn + 1;
703
+ return;
704
+ }
705
+ if (depth > 0) {
706
+ state.pos++;
707
+ state.column++;
708
+ }
709
+ }
710
+ if (depth !== 0) {
711
+ // No closing ], emit '[' as text and reset
712
+ state.tokens.push({
713
+ type: 'text',
714
+ value: '[',
715
+ line: startLine,
716
+ column: startColumn,
717
+ });
718
+ state.pos = textStart;
719
+ state.column = startColumn + 1;
720
+ return;
721
+ }
722
+ const linkText = state.input.slice(textStart, state.pos);
723
+ state.pos++; // skip ]
724
+ state.column++;
725
+ // Check for (url)
726
+ if (state.input[state.pos] === '(') {
727
+ state.pos++; // skip (
728
+ state.column++;
729
+ const urlStart = state.pos;
730
+ depth = 1;
731
+ while (state.pos < state.input.length && depth > 0) {
732
+ const char = state.input[state.pos];
733
+ if (char === '(')
734
+ depth++;
735
+ else if (char === ')')
736
+ depth--;
737
+ else if (char === '\n') {
738
+ // URL shouldn't span lines
739
+ break;
740
+ }
741
+ if (depth > 0) {
742
+ state.pos++;
743
+ state.column++;
744
+ }
745
+ }
746
+ if (depth === 0) {
747
+ const linkUrl = state.input.slice(urlStart, state.pos);
748
+ state.pos++; // skip )
749
+ state.column++;
750
+ // Emit link-text token
751
+ state.tokens.push({
752
+ type: 'link-text',
753
+ value: linkText,
754
+ line: startLine,
755
+ column: startColumn,
756
+ });
757
+ // Emit link-url token
758
+ state.tokens.push({
759
+ type: 'link-url',
760
+ value: linkUrl,
761
+ line: startLine,
762
+ column: startColumn + linkText.length + 3,
763
+ });
764
+ return;
765
+ }
766
+ }
767
+ // No URL part, emit as text
768
+ state.tokens.push({
769
+ type: 'text',
770
+ value: `[${linkText}]`,
771
+ line: startLine,
772
+ column: startColumn,
773
+ });
774
+ }
775
+ /**
776
+ * Consumes a code span `code`.
777
+ *
778
+ * @param state - The tokenizer state to update
779
+ */
780
+ function consumeCode(state) {
781
+ const startColumn = state.column;
782
+ const startLine = state.line;
783
+ state.pos++; // skip opening `
784
+ state.column++;
785
+ const contentStart = state.pos;
786
+ while (state.pos < state.input.length && state.input[state.pos] !== '`' && state.input[state.pos] !== '\n') {
787
+ state.pos++;
788
+ state.column++;
789
+ }
790
+ if (state.input[state.pos] === '`') {
791
+ const content = state.input.slice(contentStart, state.pos);
792
+ state.pos++; // skip closing `
793
+ state.column++;
794
+ state.tokens.push({
795
+ type: 'code',
796
+ value: content,
797
+ line: startLine,
798
+ column: startColumn,
799
+ });
800
+ }
801
+ else {
802
+ // No closing `, emit as text
803
+ state.tokens.push({
804
+ type: 'text',
805
+ value: '`' + state.input.slice(contentStart, state.pos),
806
+ line: startLine,
807
+ column: startColumn,
808
+ });
809
+ }
810
+ }
811
+ /**
812
+ * Consumes bold text **text**.
813
+ *
814
+ * @param state - The tokenizer state to update
815
+ */
816
+ function consumeBold(state) {
817
+ const startColumn = state.column;
818
+ const startLine = state.line;
819
+ state.pos += 2; // skip opening **
820
+ state.column += 2;
821
+ const contentStart = state.pos;
822
+ while (state.pos < state.input.length) {
823
+ // Check for closing **
824
+ if (state.input[state.pos] === '*' && state.input[state.pos + 1] === '*') {
825
+ break;
826
+ }
827
+ if (state.input[state.pos] === '\n') {
828
+ state.line++;
829
+ state.column = 0;
830
+ }
831
+ state.pos++;
832
+ state.column++;
833
+ }
834
+ if (state.input[state.pos] === '*' && state.input[state.pos + 1] === '*') {
835
+ const content = state.input.slice(contentStart, state.pos);
836
+ state.pos += 2; // skip closing **
837
+ state.column += 2;
838
+ state.tokens.push({
839
+ type: 'bold',
840
+ value: content,
841
+ line: startLine,
842
+ column: startColumn,
843
+ });
844
+ }
845
+ else {
846
+ // No closing **, emit as text
847
+ state.tokens.push({
848
+ type: 'text',
849
+ value: '**' + state.input.slice(contentStart, state.pos),
850
+ line: startLine,
851
+ column: startColumn,
852
+ });
853
+ }
854
+ }
855
+ /**
856
+ * Consumes plain text until a special character or end of line.
857
+ *
858
+ * @param state - The tokenizer state to update
859
+ */
860
+ function consumeText(state) {
861
+ const startColumn = state.column;
862
+ const startLine = state.line;
863
+ const startPos = state.pos;
864
+ while (state.pos < state.input.length) {
865
+ const char = state.input[state.pos];
866
+ // Stop at newline
867
+ if (char === '\n' || char === '\r') {
868
+ break;
869
+ }
870
+ // Stop at special characters (but only if they start a pattern)
871
+ if (char === '[')
872
+ break;
873
+ if (char === '`')
874
+ break;
875
+ if (char === '*' && state.input[state.pos + 1] === '*')
876
+ break;
877
+ state.pos++;
878
+ state.column++;
879
+ }
880
+ const content = state.input.slice(startPos, state.pos);
881
+ if (content.length > 0) {
882
+ state.tokens.push({
883
+ type: 'text',
884
+ value: content,
885
+ line: startLine,
886
+ column: startColumn,
887
+ });
888
+ }
889
+ }
890
+ /**
891
+ * Pushes a token to the state.
892
+ *
893
+ * @param state - The tokenizer state to update
894
+ * @param type - Token classification (e.g., 'heading-1', 'text', 'link-url')
895
+ * @param value - Text content associated with the token
896
+ */
897
+ function pushToken(state, type, value) {
898
+ state.tokens.push({
899
+ type,
900
+ value,
901
+ line: state.line,
902
+ column: state.column,
903
+ });
904
+ }
905
+ /**
906
+ * Checks if a character is whitespace (but not newline).
907
+ *
908
+ * @param char - The character to check
909
+ * @returns True if the character is whitespace (space or tab)
910
+ */
911
+ function isWhitespace(char) {
912
+ return char === ' ' || char === '\t';
913
+ }
914
+
915
+ /**
916
+ * Changelog Parser
917
+ *
918
+ * Parses a changelog markdown string into a structured Changelog object.
919
+ * Uses a state machine tokenizer for ReDoS-safe parsing.
920
+ */
921
+ /**
922
+ * Parses a changelog markdown string into a Changelog object.
923
+ *
924
+ * @param content - The markdown content to parse
925
+ * @param source - Optional source file path
926
+ * @returns Parsed Changelog object
927
+ */
928
+ function parseChangelog(content, source) {
929
+ const tokens = tokenize(content);
930
+ const state = {
931
+ tokens,
932
+ pos: 0,
933
+ warnings: [],
934
+ };
935
+ // Parse header
936
+ const header = parseHeader(state);
937
+ // Parse entries
938
+ const entries = parseEntries(state);
939
+ // Detect format
940
+ const format = detectFormat(header, entries);
941
+ // Build metadata
942
+ const metadata = {
943
+ format,
944
+ isConventional: format === 'conventional',
945
+ repositoryUrl: state.repositoryUrl,
946
+ warnings: state.warnings,
947
+ };
948
+ return {
949
+ source,
950
+ header,
951
+ entries,
952
+ metadata,
953
+ };
954
+ }
955
+ /**
956
+ * Parses the changelog header section.
957
+ *
958
+ * @param state - The parser state containing tokens and position
959
+ * @returns The parsed ChangelogHeader with title, description, and links
960
+ */
961
+ function parseHeader(state) {
962
+ let title = '# Changelog';
963
+ const description = [];
964
+ const links = [];
965
+ // Look for h1 title
966
+ const headingToken = currentToken(state);
967
+ if (headingToken?.type === 'heading-1') {
968
+ title = `# ${headingToken.value}`;
969
+ advance(state);
970
+ }
971
+ // Skip newlines
972
+ skipNewlines(state);
973
+ // Collect description lines until we hit h2 (version entry)
974
+ while (!isEOF(state) && currentToken(state)?.type !== 'heading-2') {
975
+ const token = currentToken(state);
976
+ if (!token)
977
+ break;
978
+ if (token.type === 'text') {
979
+ description.push(token.value);
980
+ }
981
+ else if (token.type === 'link-text') {
982
+ // Check for link definition
983
+ const nextToken = peek(state, 1);
984
+ if (nextToken?.type === 'link-url') {
985
+ description.push(`[${token.value}](${nextToken.value})`);
986
+ links.push({ label: token.value, url: nextToken.value });
987
+ // Try to detect repository URL
988
+ if (!state.repositoryUrl && nextToken.value.includes('github.com')) {
989
+ state.repositoryUrl = extractRepoUrl(nextToken.value);
990
+ }
991
+ advance(state); // skip link-text
992
+ advance(state); // skip link-url
993
+ continue;
994
+ }
995
+ }
996
+ else if (token.type === 'newline' || token.type === 'blank-line') {
997
+ if (description.length > 0 && description[description.length - 1] !== '') {
998
+ description.push('');
999
+ }
1000
+ }
1001
+ advance(state);
1002
+ }
1003
+ // Trim trailing empty lines
1004
+ while (description.length > 0 && description[description.length - 1] === '') {
1005
+ description.pop();
1006
+ }
1007
+ return { title, description, links };
1008
+ }
1009
+ /**
1010
+ * Parses all changelog entries.
1011
+ *
1012
+ * @param state - The parser state containing tokens and position
1013
+ * @returns An array of parsed ChangelogEntry objects
1014
+ */
1015
+ function parseEntries(state) {
1016
+ const entries = [];
1017
+ while (!isEOF(state)) {
1018
+ // Look for h2 heading (version entry)
1019
+ if (currentToken(state)?.type === 'heading-2') {
1020
+ const entry = parseEntry(state);
1021
+ if (entry) {
1022
+ entries.push(entry);
1023
+ }
1024
+ }
1025
+ else {
1026
+ advance(state);
1027
+ }
1028
+ }
1029
+ return entries;
1030
+ }
1031
+ /**
1032
+ * Parses a single changelog entry.
1033
+ *
1034
+ * @param state - The parser state containing tokens and position
1035
+ * @returns The parsed ChangelogEntry or null if parsing fails
1036
+ */
1037
+ function parseEntry(state) {
1038
+ const headingToken = currentToken(state);
1039
+ if (headingToken?.type !== 'heading-2') {
1040
+ return null;
1041
+ }
1042
+ const { version, date, compareUrl } = parseVersionFromHeading(headingToken.value);
1043
+ const unreleased = version.toLowerCase() === 'unreleased';
1044
+ advance(state); // skip h2
1045
+ skipNewlines(state);
1046
+ // Parse sections
1047
+ const sections = parseSections(state);
1048
+ return {
1049
+ version,
1050
+ date,
1051
+ unreleased,
1052
+ compareUrl,
1053
+ sections,
1054
+ };
1055
+ }
1056
+ /**
1057
+ * Parses sections within an entry.
1058
+ *
1059
+ * @param state - The parser state containing tokens and position
1060
+ * @returns An array of parsed ChangelogSection objects
1061
+ */
1062
+ function parseSections(state) {
1063
+ const sections = [];
1064
+ while (!isEOF(state)) {
1065
+ const token = currentToken(state);
1066
+ // Stop at next version entry (h2)
1067
+ if (token?.type === 'heading-2') {
1068
+ break;
1069
+ }
1070
+ // Parse section (h3)
1071
+ if (token?.type === 'heading-3') {
1072
+ const section = parseSection(state);
1073
+ if (section) {
1074
+ sections.push(section);
1075
+ }
1076
+ }
1077
+ else if (token?.type === 'list-item') {
1078
+ // Items without section heading - create "other" section
1079
+ const items = parseItems(state);
1080
+ if (items.length > 0) {
1081
+ sections.push({
1082
+ type: 'other',
1083
+ heading: 'Changes',
1084
+ items,
1085
+ });
1086
+ }
1087
+ }
1088
+ else {
1089
+ advance(state);
1090
+ }
1091
+ }
1092
+ return sections;
1093
+ }
1094
+ /**
1095
+ * Parses a single section.
1096
+ *
1097
+ * @param state - The parser state containing tokens and position
1098
+ * @returns The parsed ChangelogSection or null if parsing fails
1099
+ */
1100
+ function parseSection(state) {
1101
+ const headingToken = currentToken(state);
1102
+ if (headingToken?.type !== 'heading-3') {
1103
+ return null;
1104
+ }
1105
+ const heading = headingToken.value;
1106
+ const type = getSectionType(heading);
1107
+ advance(state); // skip h3
1108
+ skipNewlines(state);
1109
+ // Parse items
1110
+ const items = parseItems(state);
1111
+ return {
1112
+ type,
1113
+ heading,
1114
+ items,
1115
+ };
1116
+ }
1117
+ /**
1118
+ * Parses list items.
1119
+ *
1120
+ * @param state - The parser state containing tokens and position
1121
+ * @returns An array of parsed ChangelogItem objects
1122
+ */
1123
+ function parseItems(state) {
1124
+ const items = [];
1125
+ while (!isEOF(state)) {
1126
+ const token = currentToken(state);
1127
+ // Stop at headings
1128
+ if (token?.type === 'heading-2' || token?.type === 'heading-3') {
1129
+ break;
1130
+ }
1131
+ // Parse list item
1132
+ if (token?.type === 'list-item') {
1133
+ const item = parseItem(state);
1134
+ if (item) {
1135
+ items.push(item);
1136
+ }
1137
+ }
1138
+ else {
1139
+ advance(state);
1140
+ }
1141
+ }
1142
+ return items;
1143
+ }
1144
+ /**
1145
+ * Parses a single list item.
1146
+ *
1147
+ * @param state - The parser state containing tokens and position
1148
+ * @returns The parsed ChangelogItem or null if parsing fails
1149
+ */
1150
+ function parseItem(state) {
1151
+ const token = currentToken(state);
1152
+ if (token?.type !== 'list-item') {
1153
+ return null;
1154
+ }
1155
+ const text = token.value;
1156
+ const { scope, description } = parseScopeFromItem(text);
1157
+ const commits = parseCommitRefs(text, state.repositoryUrl);
1158
+ const references = parseIssueRefs(text, state.repositoryUrl);
1159
+ // Check for breaking change indicators
1160
+ const breaking = isBreakingItem(text);
1161
+ advance(state);
1162
+ return createChangelogItem(description, {
1163
+ scope,
1164
+ commits,
1165
+ references,
1166
+ breaking,
1167
+ });
1168
+ }
1169
+ /**
1170
+ * Checks if an item indicates a breaking change.
1171
+ *
1172
+ * @param text - The text content of the item
1173
+ * @returns True if the item indicates a breaking change
1174
+ */
1175
+ function isBreakingItem(text) {
1176
+ const lower = text.toLowerCase();
1177
+ return lower.includes('breaking change') || lower.includes('breaking:') || lower.startsWith('!') || lower.includes('[breaking]');
1178
+ }
1179
+ /**
1180
+ * Detects the changelog format.
1181
+ *
1182
+ * @param header - The parsed header of the changelog
1183
+ * @param entries - The parsed changelog entries
1184
+ * @returns The detected ChangelogFormat
1185
+ */
1186
+ function detectFormat(header, entries) {
1187
+ const descriptionText = header.description.join(' ').toLowerCase();
1188
+ // Check for Keep a Changelog
1189
+ if (descriptionText.includes('keep a changelog') || descriptionText.includes('keepachangelog')) {
1190
+ return 'keep-a-changelog';
1191
+ }
1192
+ // Check for conventional changelog patterns
1193
+ const hasConventionalSections = entries.some((entry) => entry.sections.some((section) => ['features', 'fixes', 'performance'].includes(section.type)));
1194
+ if (hasConventionalSections) {
1195
+ return 'conventional';
1196
+ }
1197
+ // Check if we have entries with structured sections
1198
+ if (entries.some((entry) => entry.sections.length > 0)) {
1199
+ return 'custom';
1200
+ }
1201
+ return 'unknown';
1202
+ }
1203
+ /**
1204
+ * Extracts repository URL from a GitHub URL.
1205
+ *
1206
+ * @param url - The URL to extract the repository from
1207
+ * @returns The repository URL or undefined if not found
1208
+ */
1209
+ function extractRepoUrl(url) {
1210
+ // Try to extract base repo URL from various GitHub URL patterns
1211
+ const githubIndex = url.indexOf('github.com/');
1212
+ if (githubIndex !== -1) {
1213
+ const afterGithub = url.slice(githubIndex + 11);
1214
+ const parts = afterGithub.split('/');
1215
+ if (parts.length >= 2) {
1216
+ return `https://github.com/${parts[0]}/${parts[1]}`;
1217
+ }
1218
+ }
1219
+ return undefined;
1220
+ }
1221
+ // ============================================================================
1222
+ // Parser utilities
1223
+ // ============================================================================
1224
+ /**
1225
+ * Gets the current token at the parser position.
1226
+ *
1227
+ * @param state - The parser state
1228
+ * @returns The current token or undefined if at end
1229
+ */
1230
+ function currentToken(state) {
1231
+ return state.tokens[state.pos];
1232
+ }
1233
+ /**
1234
+ * Peeks at a token at an offset from the current position.
1235
+ *
1236
+ * @param state - The parser state
1237
+ * @param offset - The offset from current position
1238
+ * @returns The token at the offset or undefined if out of bounds
1239
+ */
1240
+ function peek(state, offset) {
1241
+ return state.tokens[state.pos + offset];
1242
+ }
1243
+ /**
1244
+ * Advances the parser position by one token.
1245
+ *
1246
+ * @param state - The parser state to advance
1247
+ */
1248
+ function advance(state) {
1249
+ state.pos++;
1250
+ }
1251
+ /**
1252
+ * Checks if the parser has reached the end of the token stream.
1253
+ *
1254
+ * @param state - The parser state
1255
+ * @returns True if at end of file
1256
+ */
1257
+ function isEOF(state) {
1258
+ const token = currentToken(state);
1259
+ return !token || token.type === 'eof';
1260
+ }
1261
+ /**
1262
+ * Skips newline tokens until a non-newline token is found.
1263
+ *
1264
+ * @param state - The parser state
1265
+ */
1266
+ function skipNewlines(state) {
1267
+ while (!isEOF(state)) {
1268
+ const token = currentToken(state);
1269
+ if (token?.type !== 'newline' && token?.type !== 'blank-line') {
1270
+ break;
1271
+ }
1272
+ advance(state);
1273
+ }
1274
+ }
1275
+
1276
+ exports.parseChangelog = parseChangelog;
1277
+ exports.parseCommitRefs = parseCommitRefs;
1278
+ exports.parseIssueRefs = parseIssueRefs;
1279
+ exports.parseScopeFromItem = parseScopeFromItem;
1280
+ exports.parseVersionFromHeading = parseVersionFromHeading;
1281
+ exports.tokenize = tokenize;
1282
+ //# sourceMappingURL=index.cjs.js.map