@forthtilliath/ts-kit 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @forthtilliath/ts-kit
2
2
 
3
3
  Framework-agnostic TypeScript utility functions — arrays, objects, strings,
4
- async control-flow, dates, numbers, browser file downloads, and a couple of
5
- `Array` extensions with better type-narrowing.
4
+ async control-flow, dates, numbers, browser file downloads, changelog/version
5
+ parsing, and a couple of `Array` extensions with better type-narrowing.
6
6
 
7
7
  ## Install
8
8
 
@@ -103,6 +103,14 @@ import { chunk } from "@forthtilliath/ts-kit/array/chunk";
103
103
  - `randomId()` — `crypto.randomUUID()` with fallbacks for older/insecure
104
104
  contexts.
105
105
 
106
+ ### `markdown`
107
+
108
+ - `parseChangelogNotes(notes)` — parses a small subset of Markdown
109
+ (`### heading`, `- item`, `**bold**`) commonly found in GitHub release
110
+ notes into a list of typed blocks (`heading` / `item` / `text`, each with
111
+ `bold`-aware segments), ready for a UI layer to render without a full
112
+ Markdown dependency.
113
+
106
114
  ### `maths`
107
115
 
108
116
  - `sum(numbers)` — sum of an array of numbers.
@@ -144,6 +152,11 @@ import { chunk } from "@forthtilliath/ts-kit/array/chunk";
144
152
  - `escapeHtml(text)` — basic HTML entity escaping (`&`, `<`, `>`, `"`) for
145
153
  inserting user text into an HTML template.
146
154
 
155
+ ### `version`
156
+
157
+ - `compareVersions(a, b)` — compares two `"x.y.z"` version strings, segment
158
+ by segment; `-1`/`0`/`1`.
159
+
147
160
  ## Scripts
148
161
 
149
162
  ```bash
package/dist/index.d.ts CHANGED
@@ -4,8 +4,10 @@ export * from "./classes/index.js";
4
4
  export * from "./date/index.js";
5
5
  export * from "./files/index.js";
6
6
  export * from "./id/index.js";
7
+ export * from "./markdown/index.js";
7
8
  export * from "./maths/index.js";
8
9
  export * from "./number/index.js";
9
10
  export * from "./object/index.js";
10
11
  export * from "./string/index.js";
12
+ export * from "./version/index.js";
11
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -9,7 +9,9 @@ export * from "./classes/index.js";
9
9
  export * from "./date/index.js";
10
10
  export * from "./files/index.js";
11
11
  export * from "./id/index.js";
12
+ export * from "./markdown/index.js";
12
13
  export * from "./maths/index.js";
13
14
  export * from "./number/index.js";
14
15
  export * from "./object/index.js";
15
16
  export * from "./string/index.js";
17
+ export * from "./version/index.js";
@@ -0,0 +1,2 @@
1
+ export * from "./parseChangelogNotes.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/markdown/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC"}
@@ -0,0 +1 @@
1
+ export * from "./parseChangelogNotes.js";
@@ -0,0 +1,16 @@
1
+ export interface ChangelogSegment {
2
+ text: string;
3
+ bold: boolean;
4
+ }
5
+ export type ChangelogBlock = {
6
+ type: "heading";
7
+ text: string;
8
+ } | {
9
+ type: "item";
10
+ segments: ChangelogSegment[];
11
+ } | {
12
+ type: "text";
13
+ segments: ChangelogSegment[];
14
+ };
15
+ export declare function parseChangelogNotes(notes: string): ChangelogBlock[];
16
+ //# sourceMappingURL=parseChangelogNotes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseChangelogNotes.d.ts","sourceRoot":"","sources":["../../src/markdown/parseChangelogNotes.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,gBAAgB,EAAE,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAAC;AAsBnD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,EAAE,CAiBnE"}
@@ -0,0 +1,43 @@
1
+ // GitHub release notes (and changelogs in general) are usually written in a
2
+ // small subset of Markdown ("### Heading", "- item", "**bold**"). Rendered
3
+ // as-is in a plain UI text node, the raw syntax shows up unstyled — this
4
+ // parser turns it into a small set of typed blocks a UI layer can render
5
+ // (heading / bullet item / plain text), without pulling in a full Markdown
6
+ // dependency for this narrow use case.
7
+ function parseInlineSegments(text) {
8
+ const segments = [];
9
+ const boldPattern = /\*\*(.+?)\*\*/g;
10
+ let lastIndex = 0;
11
+ let match;
12
+ while ((match = boldPattern.exec(text)) !== null) {
13
+ if (match.index > lastIndex) {
14
+ segments.push({ text: text.slice(lastIndex, match.index), bold: false });
15
+ }
16
+ // The capture group is mandatory in the pattern, so it always matches —
17
+ // the `?? ""` fallback is only here to satisfy `noUncheckedIndexedAccess`.
18
+ segments.push({ text: match[1] ?? "", bold: true });
19
+ lastIndex = boldPattern.lastIndex;
20
+ }
21
+ if (lastIndex < text.length) {
22
+ segments.push({ text: text.slice(lastIndex), bold: false });
23
+ }
24
+ return segments;
25
+ }
26
+ export function parseChangelogNotes(notes) {
27
+ return notes
28
+ .split("\n")
29
+ .map((line) => line.trim())
30
+ .filter((line) => line.length > 0)
31
+ .map((line) => {
32
+ if (line.startsWith("### ")) {
33
+ return { type: "heading", text: line.slice(4).trim() };
34
+ }
35
+ if (line.startsWith("- ")) {
36
+ return {
37
+ type: "item",
38
+ segments: parseInlineSegments(line.slice(2).trim()),
39
+ };
40
+ }
41
+ return { type: "text", segments: parseInlineSegments(line) };
42
+ });
43
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Compares two "x.y.z" version strings, segment by segment.
3
+ *
4
+ * @returns -1 if `a` < `b`, 0 if they're equal, 1 if `a` > `b`.
5
+ */
6
+ export declare function compareVersions(a: string, b: string): number;
7
+ //# sourceMappingURL=compareVersions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compareVersions.d.ts","sourceRoot":"","sources":["../../src/version/compareVersions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAU5D"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Compares two "x.y.z" version strings, segment by segment.
3
+ *
4
+ * @returns -1 if `a` < `b`, 0 if they're equal, 1 if `a` > `b`.
5
+ */
6
+ export function compareVersions(a, b) {
7
+ const partsA = a.split(".").map(Number);
8
+ const partsB = b.split(".").map(Number);
9
+ const length = Math.max(partsA.length, partsB.length);
10
+ for (let i = 0; i < length; i++) {
11
+ const numA = partsA[i] ?? 0;
12
+ const numB = partsB[i] ?? 0;
13
+ if (numA !== numB)
14
+ return numA > numB ? 1 : -1;
15
+ }
16
+ return 0;
17
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./compareVersions.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/version/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC"}
@@ -0,0 +1 @@
1
+ export * from "./compareVersions.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forthtilliath/ts-kit",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {