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