From d3c5a50a20ade41b164844a566f8e28988911b08 Mon Sep 17 00:00:00 2001 From: baxyz Date: Tue, 7 Apr 2026 21:26:23 +0000 Subject: [PATCH 1/4] =?UTF-8?q?ci(release):=20=F0=9F=91=B7=20reorder=20bui?= =?UTF-8?q?ld=20step=20for=20improved=20workflow=20-=20move=20build=20proj?= =?UTF-8?q?ect=20step=20after=20version=20bump?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e2f3a30..953c8ba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -111,9 +111,6 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Build project - run: pnpm run build - - name: Bump version id: version shell: bash @@ -153,6 +150,9 @@ jobs: echo "✅ Version bumped to $NEW_VERSION" + - name: Build project + run: pnpm run build + - name: Run coherency checks run: pnpm coherency From 1153d2fdacef4f2afc47ee2d7917d73d3066f71f Mon Sep 17 00:00:00 2001 From: baxyz Date: Tue, 7 Apr 2026 21:49:32 +0000 Subject: [PATCH 2/4] =?UTF-8?q?docs(object):=20=F0=9F=93=9D=20add=20@since?= =?UTF-8?q?=20annotations=20to=20helper=20functions=20docs(observable):=20?= =?UTF-8?q?=F0=9F=93=9D=20add=20@since=20annotations=20to=20observable=20h?= =?UTF-8?q?elpers=20docs(promise):=20=F0=9F=93=9D=20add=20@since=20annotat?= =?UTF-8?q?ions=20to=20promise=20helpers=20docs(string):=20=F0=9F=93=9D=20?= =?UTF-8?q?add=20@since=20annotations=20to=20string=20helpers=20docs(type)?= =?UTF-8?q?:=20=F0=9F=93=9D=20add=20@since=20annotations=20to=20type=20hel?= =?UTF-8?q?pers=20docs(url):=20=F0=9F=93=9D=20add=20@since=20annotations?= =?UTF-8?q?=20to=20URL=20helpers=20docs(version):=20=F0=9F=93=9D=20add=20@?= =?UTF-8?q?since=20annotations=20to=20version=20helpers=20build:=20?= =?UTF-8?q?=F0=9F=93=A6=20remove=20unused=20build=20steps=20and=20add=20we?= =?UTF-8?q?bsite=20metadata=20generation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .template/category/package.json | 10 +- helpers/array/arrayEquals.ts | 1 + helpers/array/chunk.ts | 1 + helpers/array/deepCompare.ts | 1 + helpers/array/difference.ts | 1 + helpers/array/intersection.ts | 1 + helpers/array/oneInCommon.ts | 1 + helpers/array/quickCompare.ts | 1 + helpers/array/sort.ts | 9 + helpers/array/unique.ts | 1 + helpers/date/compare.ts | 2 + helpers/date/difference.ts | 1 + helpers/date/format.ts | 3 + helpers/date/is.ts | 1 + helpers/date/safeDate.ts | 2 + helpers/date/timestamp.ts | 2 + helpers/function/debounce.ts | 1 + helpers/function/isDefinedAndNotNull.ts | 1 + helpers/function/memoize.ts | 1 + helpers/function/returnOrThrowError.ts | 1 + helpers/function/throttle.ts | 1 + helpers/number/clamp.ts | 1 + helpers/number/random.ts | 2 + helpers/number/roundTo.ts | 1 + helpers/object/deepClone.ts | 1 + helpers/object/deepCompare.ts | 2 + helpers/object/deepMerge.ts | 1 + helpers/object/get.ts | 1 + helpers/object/quickCompare.ts | 1 + helpers/object/removeUndefinedNull.ts | 4 + helpers/object/set.ts | 1 + helpers/observable/combine.ts | 1 + helpers/observable/combineLatest.ts | 1 + helpers/promise/consoleLogPromise.ts | 1 + helpers/promise/delay.ts | 1 + helpers/promise/falsyPromiseOrThrow.ts | 1 + helpers/promise/meaningPromiseOrThrow.ts | 1 + helpers/promise/retry.ts | 1 + helpers/promise/truthyPromiseOrThrow.ts | 1 + helpers/string/camelCase.ts | 1 + helpers/string/capitalize.ts | 1 + helpers/string/errorToReadableMessage.ts | 3 + helpers/string/kebabCase.ts | 1 + helpers/string/labelize.ts | 1 + helpers/string/slugify.ts | 1 + helpers/type/isEmpty.ts | 1 + helpers/type/isSpecialObject.ts | 1 + helpers/type/typeChecks.ts | 10 + helpers/url/cleanPath.ts | 1 + helpers/url/extractPureURI.ts | 1 + helpers/url/onlyPath.ts | 4 + helpers/url/relativeURLToAbsolute.ts | 6 + helpers/url/withLeadingSlash.ts | 4 + helpers/url/withTrailingSlash.ts | 4 + helpers/url/withoutLeadingSlash.ts | 4 + helpers/url/withoutTrailingSlash.ts | 4 + helpers/version/compare.ts | 1 + helpers/version/increment.ts | 1 + helpers/version/parse.ts | 2 + helpers/version/satisfiesRange.ts | 1 + helpers/version/stripV.ts | 1 + scripts/build/build-website-metadata.ts | 391 +++++++++++++++++++++++ scripts/build/index.ts | 18 +- 63 files changed, 509 insertions(+), 20 deletions(-) create mode 100644 scripts/build/build-website-metadata.ts diff --git a/.template/category/package.json b/.template/category/package.json index ae6fcae..3eeed88 100644 --- a/.template/category/package.json +++ b/.template/category/package.json @@ -17,9 +17,9 @@ "types": "./lib/index.d.ts", "import": "./lib/index.js" }, - "./examples.json": "./examples.json", - "./api.json": "./api.json", - "./licenses.json": "./licenses.json", + "./meta/api.json": "./meta/api.json", + "./meta/examples.json": "./meta/examples.json", + "./meta/licenses.json": "./meta/licenses.json", "./package.json": "./package.json" }, "keywords": [ @@ -31,9 +31,7 @@ "lib/index.js", "lib/index.d.ts", "lib/index.js.map", - "examples.json", - "api.json", - "licenses.json", + "meta/", "LICENSE.md", "package.json", "README.md" diff --git a/helpers/array/arrayEquals.ts b/helpers/array/arrayEquals.ts index cc7be2b..1800139 100644 --- a/helpers/array/arrayEquals.ts +++ b/helpers/array/arrayEquals.ts @@ -14,6 +14,7 @@ import { isArray, isObject } from "radashi"; * @param arr1 One list * @param arr2 Another list * @returns `true` if the list contain the same items, `false` otherwise. + * @since 1.0.0 */ export function arrayEquals(arr1: T[], arr2: T[]): boolean { return arr1.length === arr2.length && arr1.every((v1) => arr2.some((v2) => { diff --git a/helpers/array/chunk.ts b/helpers/array/chunk.ts index 616382b..92e2f5f 100644 --- a/helpers/array/chunk.ts +++ b/helpers/array/chunk.ts @@ -9,6 +9,7 @@ * @param array - The array to chunk * @param size - The size of each chunk * @returns Array of chunks + * @since 1.9.0 */ export function chunk(array: T[], size: number): T[][] { if (size <= 0) return []; diff --git a/helpers/array/deepCompare.ts b/helpers/array/deepCompare.ts index 47f40bb..201581e 100644 --- a/helpers/array/deepCompare.ts +++ b/helpers/array/deepCompare.ts @@ -13,6 +13,7 @@ * @param arrA - First array to compare * @param arrB - Second array to compare * @returns `true` if arrays are deeply equal, `false` otherwise + * @since 2.0.0 */ export function deepCompare(arrA: T[], arrB: T[]): boolean { // Quick reference equality check diff --git a/helpers/array/difference.ts b/helpers/array/difference.ts index 78921aa..ab27621 100644 --- a/helpers/array/difference.ts +++ b/helpers/array/difference.ts @@ -9,6 +9,7 @@ * @param array1 - First array * @param array2 - Second array * @returns Array with items from first array not present in second array + * @since 1.9.0 */ export function difference(array1: T[], array2: T[]): T[] { const set2 = new Set(array2); diff --git a/helpers/array/intersection.ts b/helpers/array/intersection.ts index 72fe49a..c16b371 100644 --- a/helpers/array/intersection.ts +++ b/helpers/array/intersection.ts @@ -11,6 +11,7 @@ * @param a First array * @param b Second array * @returns The intersection of the two arrays + * @since 1.0.0 */ export function intersection(a: readonly T[], b: readonly T[]): T[] { return a.filter((v) => b.includes(v)); diff --git a/helpers/array/oneInCommon.ts b/helpers/array/oneInCommon.ts index 85069af..98dc414 100644 --- a/helpers/array/oneInCommon.ts +++ b/helpers/array/oneInCommon.ts @@ -10,6 +10,7 @@ * @param a One list * @param b Another list * @returns `true` if one item is in common, `false` otherwise. + * @since 1.0.0 */ export function oneInCommon(a: readonly T[], b: readonly T[]): boolean { return a.some((i) => b.includes(i)); diff --git a/helpers/array/quickCompare.ts b/helpers/array/quickCompare.ts index d6e7d6d..12bb0f8 100644 --- a/helpers/array/quickCompare.ts +++ b/helpers/array/quickCompare.ts @@ -11,6 +11,7 @@ * @param arrA - First array to compare * @param arrB - Second array to compare * @returns `true` if arrays are identical according to JSON.stringify, `false` otherwise + * @since 2.0.0 */ export function quickCompare(arrA: T[], arrB: T[]): boolean { try { diff --git a/helpers/array/sort.ts b/helpers/array/sort.ts index 96e71e1..c88454f 100644 --- a/helpers/array/sort.ts +++ b/helpers/array/sort.ts @@ -6,6 +6,7 @@ /** * Sort function type for arrays + * @since 1.9.0 */ export type SortFn = (a: T, b: T) => number; @@ -14,6 +15,7 @@ export type SortFn = (a: T, b: T) => number; * @param a - First number * @param b - Second number * @returns Sort order + * @since 1.9.0 */ export const sortNumberAscFn: SortFn = (a: number, b: number) => a - b; @@ -22,6 +24,7 @@ export const sortNumberAscFn: SortFn = (a: number, b: number) => a - b; * @param a - First number * @param b - Second number * @returns Sort order + * @since 1.9.0 */ export const sortNumberDescFn: SortFn = (a: number, b: number) => b - a; @@ -30,6 +33,7 @@ export const sortNumberDescFn: SortFn = (a: number, b: number) => b - a; * @param a - First string * @param b - Second string * @returns Sort order + * @since 1.9.0 */ export const sortStringAscFn: SortFn = (a: string, b: string) => a.localeCompare(b); @@ -38,6 +42,7 @@ export const sortStringAscFn: SortFn = (a: string, b: string) => a.local * @param a - First string * @param b - Second string * @returns Sort order + * @since 1.9.0 */ export const sortStringDescFn: SortFn = (a: string, b: string) => b.localeCompare(a); @@ -46,6 +51,7 @@ export const sortStringDescFn: SortFn = (a: string, b: string) => b.loca * @param a - First string * @param b - Second string * @returns Sort order + * @since 1.9.0 */ export const sortStringAscInsensitiveFn: SortFn = (a: string, b: string) => a.toLowerCase().localeCompare(b.toLowerCase()); @@ -55,6 +61,7 @@ export const sortStringAscInsensitiveFn: SortFn = (a: string, b: string) * @param property - The property to sort by (defaults to trying 'value', 'label', 'title', 'description') * @param caseInsensitive - Whether to ignore case * @returns Sort function + * @since 1.9.0 */ export function createSortByStringFn>( property?: keyof T, @@ -88,6 +95,7 @@ export function createSortByStringFn>( * Creates a sort function for objects by number property * @param property - The property to sort by (defaults to 'value') * @returns Sort function + * @since 1.9.0 */ export function createSortByNumberFn>( property?: keyof T @@ -104,6 +112,7 @@ export function createSortByNumberFn>( * Creates a sort function for objects by date property * @param property - The property to sort by (defaults to 'date') * @returns Sort function + * @since 1.9.0 */ export function createSortByDateFn>( property?: keyof T diff --git a/helpers/array/unique.ts b/helpers/array/unique.ts index 4b9c383..d725eff 100644 --- a/helpers/array/unique.ts +++ b/helpers/array/unique.ts @@ -8,6 +8,7 @@ * Removes duplicate values from an array * @param array - The array to remove duplicates from * @returns New array with unique values only + * @since 1.9.0 */ export function unique(array: T[]): T[] { return Array.from(new Set(array)); diff --git a/helpers/date/compare.ts b/helpers/date/compare.ts index 1af050b..e934536 100644 --- a/helpers/date/compare.ts +++ b/helpers/date/compare.ts @@ -6,6 +6,7 @@ /** * Options for date comparison + * @since 2.0.0 */ export interface DateCompareOptions { /** @@ -26,6 +27,7 @@ export interface DateCompareOptions { * @param dateB - Second date to compare * @param options - Comparison options * @returns `true` if dates are identical according to the specified precision, `false` otherwise + * @since 2.0.0 */ export function compare(dateA: Date, dateB: Date, options: DateCompareOptions = {}): boolean { const { precision = 'milliseconds' } = options; diff --git a/helpers/date/difference.ts b/helpers/date/difference.ts index c245de2..ec88040 100644 --- a/helpers/date/difference.ts +++ b/helpers/date/difference.ts @@ -9,6 +9,7 @@ * @param date1 - First date * @param date2 - Second date * @returns Number of days difference + * @since 2.0.0 */ export function daysDifference(date1: Date, date2: Date): number { const oneDay = 24 * 60 * 60 * 1000; diff --git a/helpers/date/format.ts b/helpers/date/format.ts index 93ff1b9..5259da7 100644 --- a/helpers/date/format.ts +++ b/helpers/date/format.ts @@ -14,6 +14,7 @@ import { safeDate } from './safeDate'; * @example * toISO8601(new Date('2025-01-19T12:30:00Z')) // '2025-01-19T12:30:00.000Z' * toISO8601(1737290400000) // '2025-01-19T12:00:00.000Z' + * @since 2.0.0 */ export function toISO8601(date: Date | number | string): string | null { const d = safeDate(date); @@ -31,6 +32,7 @@ export function toISO8601(date: Date | number | string): string | null { * @example * toRFC3339(new Date('2025-01-19T12:30:45.123Z')) // '2025-01-19T12:30:45Z' * toRFC3339(new Date('2025-01-19T12:30:45.123Z'), true) // '2025-01-19T12:30:45.123Z' + * @since 2.0.0 */ export function toRFC3339( date: Date | number | string, @@ -55,6 +57,7 @@ export function toRFC3339( * @returns RFC 2822 formatted string or null if invalid date * @example * toRFC2822(new Date('2025-01-19T12:30:00Z')) // 'Sun, 19 Jan 2025 12:30:00 +0000' + * @since 2.0.0 */ export function toRFC2822(date: Date | number | string): string | null { const d = safeDate(date); diff --git a/helpers/date/is.ts b/helpers/date/is.ts index 1a26a56..363e3fa 100644 --- a/helpers/date/is.ts +++ b/helpers/date/is.ts @@ -9,6 +9,7 @@ * @param date1 - First date * @param date2 - Second date * @returns True if same day + * @since 2.0.0 */ export function isSameDay(date1: Date, date2: Date): boolean { return ( diff --git a/helpers/date/safeDate.ts b/helpers/date/safeDate.ts index 1b60a91..eb2c866 100644 --- a/helpers/date/safeDate.ts +++ b/helpers/date/safeDate.ts @@ -10,6 +10,7 @@ import { normalizeTimestamp } from './timestamp'; * Safely creates a Date object from various input types * @param input - String, number, or Date input * @returns Valid Date object or null if invalid + * @since 1.9.0 */ export function safeDate(input: string | number | Date | null | undefined): Date | null { if (input === null || input === undefined || input === '' || input === 0) { @@ -40,6 +41,7 @@ export function safeDate(input: string | number | Date | null | undefined): Date * Formats a date to ISO string or returns null * @param input - Date input * @returns ISO string or null + * @since 1.9.0 */ export function dateToISOString(input: string | number | Date | null | undefined): string | null { const date = safeDate(input); diff --git a/helpers/date/timestamp.ts b/helpers/date/timestamp.ts index df0ad1d..beaf58d 100644 --- a/helpers/date/timestamp.ts +++ b/helpers/date/timestamp.ts @@ -8,6 +8,7 @@ * Checks if a timestamp is likely in seconds (Java/Unix style) vs milliseconds (JavaScript style) * @param timestamp - The timestamp to check * @returns True if timestamp appears to be in seconds + * @since 1.9.0 */ export function isTimestampInSeconds(timestamp: number): boolean { // Timestamps before year 2001 in milliseconds are less than 10^10 @@ -18,6 +19,7 @@ export function isTimestampInSeconds(timestamp: number): boolean { * Converts a timestamp to JavaScript milliseconds format * @param timestamp - The timestamp (in seconds or milliseconds) * @returns Timestamp in milliseconds + * @since 1.9.0 */ export function normalizeTimestamp(timestamp: number): number { return isTimestampInSeconds(timestamp) ? timestamp * 1000 : timestamp; diff --git a/helpers/function/debounce.ts b/helpers/function/debounce.ts index f4fb223..4bc7e15 100644 --- a/helpers/function/debounce.ts +++ b/helpers/function/debounce.ts @@ -9,6 +9,7 @@ * @param func - The function to debounce * @param delay - The number of milliseconds to delay * @returns The debounced function + * @since 1.9.0 */ export function debounce any>( func: T, diff --git a/helpers/function/isDefinedAndNotNull.ts b/helpers/function/isDefinedAndNotNull.ts index e30b5a2..6c5bc57 100644 --- a/helpers/function/isDefinedAndNotNull.ts +++ b/helpers/function/isDefinedAndNotNull.ts @@ -7,6 +7,7 @@ /** * Check if a given value of unknown data type is defined and not null * @param value + * @since 1.0.0 */ export function isDefinedAndNotNull(value: T | undefined | null): boolean { return value !== undefined && value !== null; diff --git a/helpers/function/memoize.ts b/helpers/function/memoize.ts index b524308..5a5e180 100644 --- a/helpers/function/memoize.ts +++ b/helpers/function/memoize.ts @@ -8,6 +8,7 @@ * Returns a memoized version of the function that caches results * @param func - The function to memoize * @returns The memoized function + * @since 1.9.0 */ export function memoize any>(func: T): T { const cache = new Map>(); diff --git a/helpers/function/returnOrThrowError.ts b/helpers/function/returnOrThrowError.ts index fd4f0a6..9ca37f2 100644 --- a/helpers/function/returnOrThrowError.ts +++ b/helpers/function/returnOrThrowError.ts @@ -12,6 +12,7 @@ import { isDefinedAndNotNull } from './isDefinedAndNotNull'; * @param value A possible non-defined value. * @param error The error message to throw. * @returns A defined value or an error. + * @since 1.0.0 */ export function returnOrThrowError(value: T | undefined | null, error: string): T { if (isDefinedAndNotNull(value)) { diff --git a/helpers/function/throttle.ts b/helpers/function/throttle.ts index 1d961b0..782a1ba 100644 --- a/helpers/function/throttle.ts +++ b/helpers/function/throttle.ts @@ -9,6 +9,7 @@ * @param func - The function to throttle * @param wait - The number of milliseconds to throttle invocations to * @returns The throttled function + * @since 1.9.0 */ export function throttle any>( func: T, diff --git a/helpers/number/clamp.ts b/helpers/number/clamp.ts index e975e0b..44ef52d 100644 --- a/helpers/number/clamp.ts +++ b/helpers/number/clamp.ts @@ -10,6 +10,7 @@ * @param min - Minimum value * @param max - Maximum value * @returns Clamped value + * @since 1.9.0 */ export function clamp(value: number, min: number, max: number): number { return Math.min(Math.max(value, min), max); diff --git a/helpers/number/random.ts b/helpers/number/random.ts index cdae21c..50404c4 100644 --- a/helpers/number/random.ts +++ b/helpers/number/random.ts @@ -9,6 +9,7 @@ * @param min - Minimum value * @param max - Maximum value * @returns Random number between min and max + * @since 1.9.0 */ export function randomBetween(min: number, max: number): number { return Math.random() * (max - min) + min; @@ -19,6 +20,7 @@ export function randomBetween(min: number, max: number): number { * @param min - Minimum value * @param max - Maximum value * @returns Random integer between min and max + * @since 1.9.0 */ export function randomIntBetween(min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1)) + min; diff --git a/helpers/number/roundTo.ts b/helpers/number/roundTo.ts index e648172..8f3c6fb 100644 --- a/helpers/number/roundTo.ts +++ b/helpers/number/roundTo.ts @@ -9,6 +9,7 @@ * @param value - The number to round * @param decimals - Number of decimal places * @returns Rounded number + * @since 1.9.0 */ export function roundTo(value: number, decimals: number): number { const multiplier = Math.pow(10, decimals); diff --git a/helpers/object/deepClone.ts b/helpers/object/deepClone.ts index d660f1d..4fe3913 100644 --- a/helpers/object/deepClone.ts +++ b/helpers/object/deepClone.ts @@ -8,6 +8,7 @@ * Creates a deep copy of an object or array * @param obj - The object to clone * @returns Deep cloned object + * @since 1.9.0 */ export function deepClone(obj: T): T { if (obj === null || typeof obj !== "object") { diff --git a/helpers/object/deepCompare.ts b/helpers/object/deepCompare.ts index 9e24072..2f47fbb 100644 --- a/helpers/object/deepCompare.ts +++ b/helpers/object/deepCompare.ts @@ -10,6 +10,7 @@ import { isSpecialObject } from '../type/isSpecialObject'; /** * Result type for deep comparison when objects are not identical + * @since 2.0.0 */ export interface DeepCompareResult { [key: string]: "onlyA" | "onlyB" | false | DeepCompareResult; @@ -21,6 +22,7 @@ export interface DeepCompareResult { * @param objA - First object to compare (can be object, undefined, or null) * @param objB - Second object to compare (can be object, undefined, or null) * @returns `true` if objects are identical, `false` if incompatible types, or a `DeepCompareResult` object detailing differences + * @since 2.0.0 */ export function deepCompare(objA: object | undefined | null, objB: object | undefined | null): true | false | DeepCompareResult { // Quick reference equality check diff --git a/helpers/object/deepMerge.ts b/helpers/object/deepMerge.ts index d2e3478..d9f1df2 100644 --- a/helpers/object/deepMerge.ts +++ b/helpers/object/deepMerge.ts @@ -9,6 +9,7 @@ * @param target - The target object * @param sources - The source objects to merge * @returns The merged object + * @since 1.9.0 */ export function deepMerge>(target: T, ...sources: Record[]): T { if (!sources.length) return target; diff --git a/helpers/object/get.ts b/helpers/object/get.ts index 2745367..82dee38 100644 --- a/helpers/object/get.ts +++ b/helpers/object/get.ts @@ -10,6 +10,7 @@ * @param path - The dot-notated path (e.g., 'a.b.c') * @param defaultValue - Default value if path doesn't exist * @returns The value at the path or default value + * @since 1.9.0 */ export function get(obj: any, path: string, defaultValue?: T): T | undefined { const keys = path.split('.'); diff --git a/helpers/object/quickCompare.ts b/helpers/object/quickCompare.ts index e3406c0..021d40a 100644 --- a/helpers/object/quickCompare.ts +++ b/helpers/object/quickCompare.ts @@ -11,6 +11,7 @@ * @param objA - First object to compare * @param objB - Second object to compare * @returns `true` if objects are identical according to JSON.stringify, `false` otherwise + * @since 2.0.0 */ export function quickCompare(objA: unknown, objB: unknown): boolean { // Quick reference equality check diff --git a/helpers/object/removeUndefinedNull.ts b/helpers/object/removeUndefinedNull.ts index 88aa53a..b708c9e 100644 --- a/helpers/object/removeUndefinedNull.ts +++ b/helpers/object/removeUndefinedNull.ts @@ -11,6 +11,7 @@ import { isNullish } from "radashi"; * * @param obj an object * @returns A shallow copy of the object without null or undefined values + * @since 1.0.0 */ export function removeUndefinedNull>(obj: T): Partial; @@ -19,6 +20,7 @@ export function removeUndefinedNull>(obj: T | null | undefined): Partial | null | undefined { return obj ? Object.entries(obj) diff --git a/helpers/object/set.ts b/helpers/object/set.ts index d0930ce..bc525a2 100644 --- a/helpers/object/set.ts +++ b/helpers/object/set.ts @@ -10,6 +10,7 @@ * @param path - The dot-notated path (e.g., 'a.b.c') * @param value - The value to set * @returns The modified object + * @since 1.9.0 */ export function set(obj: Record, path: string, value: any): Record { const keys = path.split('.'); diff --git a/helpers/observable/combine.ts b/helpers/observable/combine.ts index 25f54e2..9b24eca 100644 --- a/helpers/observable/combine.ts +++ b/helpers/observable/combine.ts @@ -34,6 +34,7 @@ type combineOptions = { * @returns an observable that emits the result of the map function * @see combineLatestOperator * @see mapOperator + * @since 1.0.0 */ export function combine( source1: Observable, diff --git a/helpers/observable/combineLatest.ts b/helpers/observable/combineLatest.ts index 0ff6e0e..c60ccb5 100644 --- a/helpers/observable/combineLatest.ts +++ b/helpers/observable/combineLatest.ts @@ -50,6 +50,7 @@ export function combineLatest>>( * @returns {Observable} An Observable of projected values from the most recent * values from each input Observable, or an array of the most recent values from * each input Observable. + * @since 1.0.0 */ export function combineLatest(input: any): any { if (Array.isArray(input)) { diff --git a/helpers/promise/consoleLogPromise.ts b/helpers/promise/consoleLogPromise.ts index ad7df77..225f0ef 100644 --- a/helpers/promise/consoleLogPromise.ts +++ b/helpers/promise/consoleLogPromise.ts @@ -7,6 +7,7 @@ /* * This program is under the terms of the GNU Lesser General Public License version 3 * The full license information can be found in LICENSE in the root directory of this project. + * @since 1.0.0 */ export function consoleLogPromise(prefix?: string): (data: T) => T { diff --git a/helpers/promise/delay.ts b/helpers/promise/delay.ts index 8cb3038..f67ea05 100644 --- a/helpers/promise/delay.ts +++ b/helpers/promise/delay.ts @@ -9,6 +9,7 @@ * @param ms - Milliseconds to delay * @param value - Optional value to resolve with * @returns Promise that resolves after delay + * @since 1.9.0 */ export function delay(ms: number, value?: T): Promise { return new Promise(resolve => { diff --git a/helpers/promise/falsyPromiseOrThrow.ts b/helpers/promise/falsyPromiseOrThrow.ts index 34698b8..3213987 100644 --- a/helpers/promise/falsyPromiseOrThrow.ts +++ b/helpers/promise/falsyPromiseOrThrow.ts @@ -7,6 +7,7 @@ /* * This program is under the terms of the GNU Lesser General Public License version 3 * The full license information can be found in LICENSE in the root directory of this project. + * @since 1.0.0 */ export function falsyPromiseOrThrow(error: string): (data: T) => T | never { diff --git a/helpers/promise/meaningPromiseOrThrow.ts b/helpers/promise/meaningPromiseOrThrow.ts index cf757b9..502d054 100644 --- a/helpers/promise/meaningPromiseOrThrow.ts +++ b/helpers/promise/meaningPromiseOrThrow.ts @@ -7,6 +7,7 @@ /* * This program is under the terms of the GNU Lesser General Public License version 3 * The full license information can be found in LICENSE in the root directory of this project. + * @since 1.0.0 */ export function meaningPromiseOrThrow( diff --git a/helpers/promise/retry.ts b/helpers/promise/retry.ts index 887c57d..23854d6 100644 --- a/helpers/promise/retry.ts +++ b/helpers/promise/retry.ts @@ -10,6 +10,7 @@ * @param maxAttempts - Maximum number of attempts * @param delayMs - Delay between attempts in milliseconds * @returns Promise that resolves with the result or rejects with the last error + * @since 1.9.0 */ export async function retry( fn: () => Promise, diff --git a/helpers/promise/truthyPromiseOrThrow.ts b/helpers/promise/truthyPromiseOrThrow.ts index 08ef8d2..dd5e28a 100644 --- a/helpers/promise/truthyPromiseOrThrow.ts +++ b/helpers/promise/truthyPromiseOrThrow.ts @@ -7,6 +7,7 @@ /* * This program is under the terms of the GNU Lesser General Public License version 3 * The full license information can be found in LICENSE in the root directory of this project. + * @since 1.0.0 */ export function truthyPromiseOrThrow(error: string): (data: T) => T | never { diff --git a/helpers/string/camelCase.ts b/helpers/string/camelCase.ts index 012ceef..0092d15 100644 --- a/helpers/string/camelCase.ts +++ b/helpers/string/camelCase.ts @@ -8,6 +8,7 @@ * Converts kebab-case to camelCase * @param str - The kebab-case string to convert * @returns String in camelCase + * @since 1.9.0 */ export function camelCase(str: string): string { return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()); diff --git a/helpers/string/capitalize.ts b/helpers/string/capitalize.ts index e920957..95a26c2 100644 --- a/helpers/string/capitalize.ts +++ b/helpers/string/capitalize.ts @@ -8,6 +8,7 @@ * Capitalizes the first letter of a string * @param str - The string to capitalize * @returns String with first letter capitalized + * @since 1.9.0 */ export function capitalize(str: string): string { if (!str) return str; diff --git a/helpers/string/errorToReadableMessage.ts b/helpers/string/errorToReadableMessage.ts index 147ef32..e490f41 100644 --- a/helpers/string/errorToReadableMessage.ts +++ b/helpers/string/errorToReadableMessage.ts @@ -17,6 +17,7 @@ import { isObject } from "radashi"; * @param error an error * @param stringify stringifies the error if no extractable message is found * @returns a readable message or a stringified error if stringify is true, otherwise undefined + * @since 1.0.0 */ export function errorToReadableMessage(error: unknown, stringify: true | string): string; @@ -26,6 +27,7 @@ export function errorToReadableMessage(error: unknown, stringify: true | string) * @param error an error * @param stringify stringifies the error if no extractable message is found * @returns a readable message or a stringified error if stringify is true, otherwise undefined + * @since 1.0.0 */ export function errorToReadableMessage(error?: unknown, stringify?: boolean | string): string | undefined; @@ -35,6 +37,7 @@ export function errorToReadableMessage(error?: unknown, stringify?: boolean | st * @param error an error * @param stringify stringifies the error if no extractable message is found * @returns a readable message or a stringified error if stringify is true, otherwise undefined + * @since 1.0.0 */ export function errorToReadableMessage(error?: unknown, stringify?: boolean | string): string | undefined { // Create a control return diff --git a/helpers/string/kebabCase.ts b/helpers/string/kebabCase.ts index 0f4104b..f446a71 100644 --- a/helpers/string/kebabCase.ts +++ b/helpers/string/kebabCase.ts @@ -8,6 +8,7 @@ * Converts camelCase to kebab-case * @param str - The camelCase string to convert * @returns String in kebab-case + * @since 1.9.0 */ export function kebabCase(str: string): string { return str diff --git a/helpers/string/labelize.ts b/helpers/string/labelize.ts index d1a8fe4..4d6510b 100644 --- a/helpers/string/labelize.ts +++ b/helpers/string/labelize.ts @@ -15,6 +15,7 @@ * Transform string to lowercase with capitalized first letters and with spaces between words * * @param str the string to convert + * @since 1.0.0 */ export function labelize(str: string): string { return str diff --git a/helpers/string/slugify.ts b/helpers/string/slugify.ts index 7f0eacb..289849b 100644 --- a/helpers/string/slugify.ts +++ b/helpers/string/slugify.ts @@ -13,6 +13,7 @@ * @example * slugify('Hello World!'); * // 'hello-world' + * @since 2.0.0 */ export function slugify(str: string): string { return str diff --git a/helpers/type/isEmpty.ts b/helpers/type/isEmpty.ts index dcaa640..23ef3f9 100644 --- a/helpers/type/isEmpty.ts +++ b/helpers/type/isEmpty.ts @@ -24,6 +24,7 @@ import { isSpecialObject } from './isSpecialObject'; * isEmpty([]) // true * isEmpty({}) // true * isEmpty('foo') // false + * @since 2.0.0 */ export function isEmpty(value: unknown): boolean { if (value === null || value === undefined) { diff --git a/helpers/type/isSpecialObject.ts b/helpers/type/isSpecialObject.ts index 3245817..7774a84 100644 --- a/helpers/type/isSpecialObject.ts +++ b/helpers/type/isSpecialObject.ts @@ -10,6 +10,7 @@ * * @param value - The value to check * @returns `true` if the value is a special object, `false` otherwise + * @since 2.0.0 */ export function isSpecialObject(value: unknown): boolean { if (value === null || value === undefined) { diff --git a/helpers/type/typeChecks.ts b/helpers/type/typeChecks.ts index 0b9d3d1..27dd30a 100644 --- a/helpers/type/typeChecks.ts +++ b/helpers/type/typeChecks.ts @@ -6,6 +6,7 @@ /** * Type for values that can be T, undefined, or null + * @since 1.9.0 */ export type Maybe = T | undefined | null; @@ -13,6 +14,7 @@ export type Maybe = T | undefined | null; * Checks if a value is set (not undefined nor null) * @param value - The value to check * @returns True if value is not undefined nor null + * @since 1.9.0 */ export function isSet(value: Maybe): value is T { return value !== undefined && value !== null; @@ -22,6 +24,7 @@ export function isSet(value: Maybe): value is T { * Checks if a value is a string * @param value - The value to check * @returns True if value is a string + * @since 1.9.0 */ export function isString(value: unknown): value is string { return typeof value === 'string'; @@ -31,6 +34,7 @@ export function isString(value: unknown): value is string { * Checks if a value is a number * @param value - The value to check * @returns True if value is a number + * @since 1.9.0 */ export function isNumber(value: unknown): value is number { return typeof value === 'number' && !isNaN(value); @@ -40,6 +44,7 @@ export function isNumber(value: unknown): value is number { * Checks if a value is a boolean * @param value - The value to check * @returns True if value is a boolean + * @since 1.9.0 */ export function isBoolean(value: unknown): value is boolean { return typeof value === 'boolean'; @@ -49,6 +54,7 @@ export function isBoolean(value: unknown): value is boolean { * Checks if a value is an array * @param value - The value to check * @returns True if value is an array + * @since 1.9.0 */ export function isArray(value: unknown): value is unknown[] { return Array.isArray(value); @@ -58,6 +64,7 @@ export function isArray(value: unknown): value is unknown[] { * Checks if a value is a plain object * @param value - The value to check * @returns True if value is a plain object + * @since 1.9.0 */ export function isObject(value: unknown): value is Record { return value !== null && typeof value === 'object' && !Array.isArray(value); @@ -67,6 +74,7 @@ export function isObject(value: unknown): value is Record { * Checks if a value is a function * @param value - The value to check * @returns True if value is a function + * @since 1.9.0 */ export function isFunction(value: unknown): value is Function { return typeof value === 'function'; @@ -76,6 +84,7 @@ export function isFunction(value: unknown): value is Function { * Checks if a value is a Date * @param value - The value to check * @returns True if value is a Date + * @since 1.9.0 */ export function isDate(value: unknown): value is Date { return value instanceof Date && !isNaN(value.getTime()); @@ -85,6 +94,7 @@ export function isDate(value: unknown): value is Date { * Checks if a string is a valid regex * @param value - The string to check * @returns True if the string is a valid regex pattern + * @since 1.9.0 */ export function isValidRegex(value: string): boolean { try { diff --git a/helpers/url/cleanPath.ts b/helpers/url/cleanPath.ts index 1681986..8d06983 100644 --- a/helpers/url/cleanPath.ts +++ b/helpers/url/cleanPath.ts @@ -18,6 +18,7 @@ * cleanPath(undefined) // => undefined * cleanPath(null) // => null * ``` + * @since 1.0.0 */ export function cleanPath( url: string | undefined | null, diff --git a/helpers/url/extractPureURI.ts b/helpers/url/extractPureURI.ts index 6939ed6..a097884 100644 --- a/helpers/url/extractPureURI.ts +++ b/helpers/url/extractPureURI.ts @@ -14,6 +14,7 @@ * * @param url - The URL string to process * @returns The URI without query parameters and fragments, or the original value if undefined/null + * @since 1.9.0 */ export function extractPureURI(url: string): string; export function extractPureURI(url: undefined): undefined; diff --git a/helpers/url/onlyPath.ts b/helpers/url/onlyPath.ts index 4ea477d..2cf66c4 100644 --- a/helpers/url/onlyPath.ts +++ b/helpers/url/onlyPath.ts @@ -25,6 +25,7 @@ * onlyPath(undefined) // => undefined * onlyPath(null) // => null * ``` + * @since 1.0.0 */ export function onlyPath(url: string): string @@ -49,6 +50,7 @@ export function onlyPath(url: string): string * onlyPath(undefined) // => undefined * onlyPath(null) // => null * ``` + * @since 1.0.0 */ export function onlyPath(url: null): null @@ -73,6 +75,7 @@ export function onlyPath(url: null): null * onlyPath(undefined) // => undefined * onlyPath(null) // => null * ``` + * @since 1.0.0 */ export function onlyPath(url: undefined): undefined @@ -97,6 +100,7 @@ export function onlyPath(url: undefined): undefined * onlyPath(undefined) // => undefined * onlyPath(null) // => null * ``` + * @since 1.0.0 */ export function onlyPath( url: string | undefined | null, diff --git a/helpers/url/relativeURLToAbsolute.ts b/helpers/url/relativeURLToAbsolute.ts index 636dab3..d2a4a37 100644 --- a/helpers/url/relativeURLToAbsolute.ts +++ b/helpers/url/relativeURLToAbsolute.ts @@ -13,6 +13,12 @@ import { withoutTrailingSlash } from "./withoutTrailingSlash"; import { withLeadingSlash } from "./withLeadingSlash"; import { cleanPath } from "./cleanPath"; +/** + * Converts a relative URL to an absolute URL using the current document base URI. + * @param relativeUrl - The relative URL to convert + * @returns The absolute URL + * @since 1.0.0 + */ export function relativeURLToAbsolute(relativeUrl: string): string { return ( withoutTrailingSlash(document.baseURI ?? window.location.origin) + diff --git a/helpers/url/withLeadingSlash.ts b/helpers/url/withLeadingSlash.ts index 7dd3d05..0d377ec 100644 --- a/helpers/url/withLeadingSlash.ts +++ b/helpers/url/withLeadingSlash.ts @@ -24,6 +24,7 @@ * withLeadingSlash(undefined) // => undefined * withLeadingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withLeadingSlash(url: string): string @@ -47,6 +48,7 @@ export function withLeadingSlash(url: string): string * withLeadingSlash(undefined) // => undefined * withLeadingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withLeadingSlash(url: undefined): undefined @@ -70,6 +72,7 @@ export function withLeadingSlash(url: undefined): undefined * withLeadingSlash(undefined) // => undefined * withLeadingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withLeadingSlash(url: null): null @@ -93,6 +96,7 @@ export function withLeadingSlash(url: null): null * withLeadingSlash(undefined) // => undefined * withLeadingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withLeadingSlash( url: string | undefined | null, diff --git a/helpers/url/withTrailingSlash.ts b/helpers/url/withTrailingSlash.ts index 3a5039c..910663f 100644 --- a/helpers/url/withTrailingSlash.ts +++ b/helpers/url/withTrailingSlash.ts @@ -24,6 +24,7 @@ * withTrailingSlash(undefined) // => undefined * withTrailingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withTrailingSlash(url: string): string @@ -47,6 +48,7 @@ export function withTrailingSlash(url: string): string * withTrailingSlash(undefined) // => undefined * withTrailingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withTrailingSlash(url: undefined): undefined @@ -70,6 +72,7 @@ export function withTrailingSlash(url: undefined): undefined * withTrailingSlash(undefined) // => undefined * withTrailingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withTrailingSlash(url: null): null @@ -93,6 +96,7 @@ export function withTrailingSlash(url: null): null * withTrailingSlash(undefined) // => undefined * withTrailingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withTrailingSlash( url: string | undefined | null, diff --git a/helpers/url/withoutLeadingSlash.ts b/helpers/url/withoutLeadingSlash.ts index 02b45c3..58a38e6 100644 --- a/helpers/url/withoutLeadingSlash.ts +++ b/helpers/url/withoutLeadingSlash.ts @@ -25,6 +25,7 @@ * withoutLeadingSlash(undefined) // => undefined * withoutLeadingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withoutLeadingSlash(url: string): string @@ -49,6 +50,7 @@ export function withoutLeadingSlash(url: string): string * withoutLeadingSlash(undefined) // => undefined * withoutLeadingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withoutLeadingSlash(url: undefined): undefined @@ -73,6 +75,7 @@ export function withoutLeadingSlash(url: undefined): undefined * withoutLeadingSlash(undefined) // => undefined * withoutLeadingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withoutLeadingSlash(url: null): null @@ -97,6 +100,7 @@ export function withoutLeadingSlash(url: null): null * withoutLeadingSlash(undefined) // => undefined * withoutLeadingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withoutLeadingSlash( url: string | undefined | null, diff --git a/helpers/url/withoutTrailingSlash.ts b/helpers/url/withoutTrailingSlash.ts index c7880e4..f854fe6 100644 --- a/helpers/url/withoutTrailingSlash.ts +++ b/helpers/url/withoutTrailingSlash.ts @@ -25,6 +25,7 @@ * withoutTrailingSlash(undefined) // => undefined * withoutTrailingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withoutTrailingSlash(url: string): string @@ -49,6 +50,7 @@ export function withoutTrailingSlash(url: string): string * withoutTrailingSlash(undefined) // => undefined * withoutTrailingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withoutTrailingSlash(url: undefined): undefined @@ -73,6 +75,7 @@ export function withoutTrailingSlash(url: undefined): undefined * withoutTrailingSlash(undefined) // => undefined * withoutTrailingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withoutTrailingSlash(url: null): null @@ -97,6 +100,7 @@ export function withoutTrailingSlash(url: null): null * withoutTrailingSlash(undefined) // => undefined * withoutTrailingSlash(null) // => null * ``` + * @since 1.0.0 */ export function withoutTrailingSlash( url: string | undefined | null, diff --git a/helpers/version/compare.ts b/helpers/version/compare.ts index d76a586..2295513 100644 --- a/helpers/version/compare.ts +++ b/helpers/version/compare.ts @@ -80,6 +80,7 @@ function comparePrerelease(pre1: string[], pre2: string[]): number { * compare('1.0.0-alpha', '1.0.0-beta') // -1 * compare('1.0.0-alpha.1', '1.0.0-alpha.2') // -1 * compare('1.0.0+build1', '1.0.0+build2') // 0 (build metadata ignored) + * @since 1.9.0 */ export function compare(version1: string, version2: string): number { const v1 = parse(version1); diff --git a/helpers/version/increment.ts b/helpers/version/increment.ts index f69c405..e58cbd6 100644 --- a/helpers/version/increment.ts +++ b/helpers/version/increment.ts @@ -9,6 +9,7 @@ * @param version - The version to increment * @param type - The increment type ('major', 'minor', 'patch') * @returns Incremented version string + * @since 1.9.0 */ export function increment( version: string, diff --git a/helpers/version/parse.ts b/helpers/version/parse.ts index dcd369d..16c6bf8 100644 --- a/helpers/version/parse.ts +++ b/helpers/version/parse.ts @@ -6,6 +6,7 @@ /** * Represents a parsed semantic version according to SemVer 2.0.0 specification + * @since 2.0.0 */ export interface ParsedVersion { /** Major version number */ @@ -36,6 +37,7 @@ export interface ParsedVersion { * parse('v1.0.0-alpha.1') // { major: 1, minor: 0, patch: 0, prerelease: ['alpha', '1'], build: [] } * parse('2.0.0+build.123') // { major: 2, minor: 0, patch: 0, prerelease: [], build: ['build', '123'] } * parse('1.0.0-beta+exp.sha.5114f85') // { major: 1, minor: 0, patch: 0, prerelease: ['beta'], build: ['exp', 'sha', '5114f85'] } + * @since 2.0.0 */ export function parse(version: string): ParsedVersion { // Remove optional 'v' prefix diff --git a/helpers/version/satisfiesRange.ts b/helpers/version/satisfiesRange.ts index e99cd87..51f766f 100644 --- a/helpers/version/satisfiesRange.ts +++ b/helpers/version/satisfiesRange.ts @@ -9,6 +9,7 @@ * @param version - Version to check * @param range - Range pattern (e.g., ">=1.0.0", "~1.2.0", "^1.0.0") * @returns True if version satisfies the range + * @since 1.9.0 */ export function satisfiesRange(version: string, range: string): boolean { const normalize = (v: string) => v.replace(/^v/, ''); diff --git a/helpers/version/stripV.ts b/helpers/version/stripV.ts index 74e5486..4be88f7 100644 --- a/helpers/version/stripV.ts +++ b/helpers/version/stripV.ts @@ -20,6 +20,7 @@ * stripV("") // "" * stripV("1.0.0-beta") // "1.0.0-beta" * ``` + * @since 1.9.0 */ export function stripV(version: string): string; export function stripV(version: null): null; diff --git a/scripts/build/build-website-metadata.ts b/scripts/build/build-website-metadata.ts new file mode 100644 index 0000000..ebb0d0a --- /dev/null +++ b/scripts/build/build-website-metadata.ts @@ -0,0 +1,391 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { readdir } from 'node:fs/promises'; +import { join } from 'node:path'; +import { ensureDir } from 'fs-extra'; +import { + Application, + type CommentTag, + type DeclarationReflection, + type ParameterReflection, + type ProjectReflection, + type SignatureReflection, + type TypeParameterReflection, +} from 'typedoc'; +import { DIR } from '../constants'; +import { readFileJson, writeFile } from '../utils'; +import { getExternalDependencies } from './helpers/get-external-dependencies.helper'; + +// --------------------------------------------------------------------------- +// Types — website-enriched metadata +// --------------------------------------------------------------------------- + +interface WebsiteTypeParam { + readonly name: string; + readonly constraint?: string; + readonly default?: string; +} + +interface WebsiteParam { + readonly name: string; + readonly type: string; + readonly description: string; + readonly optional?: boolean; + readonly defaultValue?: string; +} + +interface WebsiteReturn { + readonly type: string; + readonly description: string; +} + +interface WebsiteSignature { + readonly signature: string; + readonly description: string; + readonly params: readonly WebsiteParam[]; + readonly returns: WebsiteReturn; + readonly typeParameters?: readonly WebsiteTypeParam[]; +} + +interface WebsiteExample { + readonly title: string; + readonly description: string; + readonly code: string; +} + +interface WebsiteFunction { + readonly name: string; + readonly kind: 'function' | 'type' | 'interface' | 'variable'; + readonly description: string; + readonly since: string; + readonly signatures: readonly WebsiteSignature[]; + readonly examples: readonly WebsiteExample[]; + readonly sourceFile: string; +} + +interface WebsiteDependency { + readonly name: string; + readonly license: string; + readonly homepage?: string; + readonly repository?: string; +} + +interface WebsiteApiJson { + readonly category: string; + readonly version: string; + readonly functions: readonly WebsiteFunction[]; +} + +interface WebsiteExamplesJson { + readonly category: string; + readonly functions: readonly { name: string; examples: readonly WebsiteExample[] }[]; +} + +interface WebsiteLicensesJson { + readonly category: string; + readonly dependencies: readonly WebsiteDependency[]; +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function extractText(summary: Array<{ kind: string; text: string }> | undefined): string { + if (!summary) return ''; + return summary.map(s => s.text).join('').trim(); +} + +function extractTagText(tags: CommentTag[] | undefined, tagName: string): string | undefined { + if (!tags) return undefined; + const tag = tags.find(t => t.tag === tagName); + if (!tag) return undefined; + return extractText(tag.content as Array<{ kind: string; text: string }>); +} + +function extractExamples(tags: CommentTag[] | undefined): string[] { + if (!tags) return []; + return tags + .filter(t => t.tag === '@example') + .map(t => extractText(t.content as Array<{ kind: string; text: string }>)) + .filter(Boolean); +} + +function serializeType(type: unknown): string { + if (!type) return 'unknown'; + const t = type as Record; + if (t.type === 'intrinsic') return t.name as string; + if (t.type === 'array') return `${serializeType(t.elementType)}[]`; + if (t.type === 'reference') { + const args = t.typeArguments as unknown[] | undefined; + const name = t.name as string; + if (args?.length) return `${name}<${args.map(serializeType).join(', ')}>`; + return name; + } + if (t.type === 'union') return (t.types as unknown[]).map(serializeType).join(' | '); + if (t.type === 'intersection') return (t.types as unknown[]).map(serializeType).join(' & '); + if (t.type === 'literal') return JSON.stringify(t.value); + if (t.type === 'tuple') { + const elems = (t.elements as unknown[]) ?? []; + return `[${elems.map(serializeType).join(', ')}]`; + } + if (t.type === 'reflection') { + const decl = t.declaration as Record | undefined; + if (decl?.signatures) return 'function'; + return 'object'; + } + if (t.type === 'typeOperator') { + const op = t.operator as string; + const target = serializeType(t.target); + return `${op} ${target}`; + } + return String(t.name || t.type || 'unknown'); +} + +function buildSignatureString(sig: SignatureReflection): string { + const params = sig.parameters + ?.map((p: ParameterReflection) => { + const opt = p.flags?.isOptional ? '?' : ''; + return `${p.name}${opt}: ${serializeType(p.type)}`; + }) + .join(', ') ?? ''; + const ret = serializeType(sig.type); + const typeParams = sig.typeParameters + ?.map((tp: TypeParameterReflection) => { + let s = tp.name; + if (tp.type) s += ` extends ${serializeType(tp.type)}`; + if (tp.default) s += ` = ${serializeType(tp.default)}`; + return s; + }) + .join(', '); + const generics = typeParams ? `<${typeParams}>` : ''; + return `${sig.name}${generics}(${params}): ${ret}`; +} + +function processSignature(sig: SignatureReflection): WebsiteSignature { + const comment = sig.comment; + const description = extractText(comment?.summary as Array<{ kind: string; text: string }> | undefined); + const returnsDesc = extractTagText(comment?.blockTags as CommentTag[] | undefined, '@returns') ?? ''; + + return { + signature: buildSignatureString(sig), + description, + params: (sig.parameters ?? []).map((p: ParameterReflection) => ({ + name: p.name, + type: serializeType(p.type), + description: extractText(p.comment?.summary as Array<{ kind: string; text: string }> | undefined), + ...(p.flags?.isOptional ? { optional: true } : {}), + ...(p.defaultValue ? { defaultValue: p.defaultValue } : {}), + })), + returns: { + type: serializeType(sig.type), + description: returnsDesc, + }, + ...(sig.typeParameters?.length ? { + typeParameters: sig.typeParameters.map((tp: TypeParameterReflection) => ({ + name: tp.name, + ...(tp.type ? { constraint: serializeType(tp.type) } : {}), + ...(tp.default ? { default: serializeType(tp.default) } : {}), + })), + } : {}), + }; +} + +function processMember(child: DeclarationReflection): WebsiteFunction | undefined { + const kindMap: Record = { + 64: 'function', + 2097152: 'type', + 256: 'interface', + 32: 'variable', + }; + + const kind = kindMap[child.kind]; + if (!kind) return undefined; + + // All signatures (supports overloads) + const signatures = (child.signatures ?? []).map(sig => + processSignature(sig as SignatureReflection) + ); + + // Primary comment (first signature or declaration) + const primarySig = child.signatures?.[0] as SignatureReflection | undefined; + const comment = primarySig?.comment ?? child.comment; + + const description = extractText(comment?.summary as Array<{ kind: string; text: string }> | undefined); + const since = extractTagText(comment?.blockTags as CommentTag[] | undefined, '@since') ?? 'unknown'; + const examples = extractExamples(comment?.blockTags as CommentTag[] | undefined); + + // Source file name + const sourceFile = child.sources?.[0]?.fileName + ?? `${child.name}.ts`; + const fileName = sourceFile.includes('/') + ? sourceFile.split('/').pop()! + : sourceFile; + + return { + name: child.name, + kind, + description, + since, + signatures, + examples: examples.map(code => ({ + title: child.name, + description: '', + code, + })), + sourceFile: fileName, + }; +} + +function readDependencyLicense(packageName: string): WebsiteDependency { + const pkgJsonPath = join(DIR.ROOT, 'node_modules', packageName, 'package.json'); + const pkg = readFileJson>(pkgJsonPath); + + const repository = typeof pkg.repository === 'string' + ? pkg.repository + : (pkg.repository as Record | undefined)?.url; + + const cleanRepo = repository + ?.replace(/^git\+/, '') + ?.replace(/\.git$/, ''); + + const license = typeof pkg.license === 'string' + ? pkg.license + : (pkg.license as Record | undefined)?.type ?? 'UNKNOWN'; + + return { + name: packageName, + license, + ...(pkg.homepage ? { homepage: pkg.homepage as string } : {}), + ...(cleanRepo ? { repository: cleanRepo } : {}), + }; +} + +// --------------------------------------------------------------------------- +// Merge examples from .example.ts files +// --------------------------------------------------------------------------- + +interface HelperExamples { + helper: string; + examples: Array<{ title: string; description: string; code: string; assert: () => void }>; +} + +async function loadExampleFiles( + category: string +): Promise> { + const categoryPath = join(DIR.HELPERS, category); + const files = await readdir(categoryPath); + const exampleFiles = files.filter(f => f.endsWith('.example.ts')).sort(); + + const map = new Map(); + + for (const file of exampleFiles) { + const filePath = join(process.cwd(), categoryPath, file); + const mod = await import(filePath) as { default: HelperExamples }; + const helperExamples = mod.default; + + map.set(helperExamples.helper, helperExamples.examples.map(ex => ({ + title: ex.title, + description: ex.description, + code: ex.code, + }))); + } + + return map; +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +/** + * Generates enriched metadata files for the website in `build//meta/`. + * Produces three files per category: + * - `meta/api.json` — full function docs with overloads, @since, type parameters + * - `meta/examples.json` — examples per function + * - `meta/licenses.json` — third-party dependency license metadata + * + * @param validCategories - Categories that were successfully built + */ +export async function buildWebsiteMetadata(validCategories: string[]): Promise { + const rootPkg = readFileJson>(join(DIR.ROOT, 'package.json')); + const version = rootPkg.version as string; + + for (const category of validCategories) { + const categoryPath = join(DIR.HELPERS, category); + const metaDir = join(DIR.BUILD, category, 'meta'); + await ensureDir(metaDir); + + const files = await readdir(categoryPath); + const sourceFiles = files + .filter(f => f.endsWith('.ts')) + .filter(f => !f.match(/\.\w+\.ts$/)) + .filter(f => f !== 'index.ts'); + + if (sourceFiles.length === 0) continue; + + // --- API --- + const entryPoint = join(categoryPath, 'index.ts'); + + const app = await Application.bootstrapWithPlugins({ + entryPoints: [entryPoint], + tsconfig: join(DIR.ROOT, 'tsconfig.json'), + excludeInternal: true, + excludePrivate: true, + disableSources: false, // keep sources for sourceFile + }); + + const project: ProjectReflection | undefined = await app.convert(); + if (!project) { + console.error(` ⚠️ TypeDoc failed for ${category}`); + continue; + } + + const functions: WebsiteFunction[] = []; + + for (const child of project.children ?? []) { + const fn = processMember(child as DeclarationReflection); + if (fn) functions.push(fn); + } + + // Merge .example.ts examples into functions (richer than JSDoc @example) + const exampleMap = await loadExampleFiles(category); + const enrichedFunctions = functions.map(fn => { + const fileExamples = exampleMap.get(fn.name); + if (fileExamples?.length) { + return { ...fn, examples: fileExamples }; + } + return fn; + }); + + enrichedFunctions.sort((a, b) => a.name.localeCompare(b.name)); + + const apiJson: WebsiteApiJson = { + category, + version, + functions: enrichedFunctions, + }; + + writeFile(join(metaDir, 'api.json'), JSON.stringify(apiJson, null, 2)); + + // --- Examples (standalone file for convenience) --- + const examplesJson: WebsiteExamplesJson = { + category, + functions: enrichedFunctions + .filter(fn => fn.examples.length > 0) + .map(fn => ({ name: fn.name, examples: fn.examples })), + }; + + writeFile(join(metaDir, 'examples.json'), JSON.stringify(examplesJson, null, 2)); + + // --- Licenses --- + const externalDeps = await getExternalDependencies(category); + const dependencies = externalDeps.map(readDependencyLicense); + + const licensesJson: WebsiteLicensesJson = { category, dependencies }; + writeFile(join(metaDir, 'licenses.json'), JSON.stringify(licensesJson, null, 2)); + } +} diff --git a/scripts/build/index.ts b/scripts/build/index.ts index b923659..9aeb4b6 100644 --- a/scripts/build/index.ts +++ b/scripts/build/index.ts @@ -8,9 +8,7 @@ import { emptyDir } from "fs-extra"; import { DIR } from "../constants"; import { buildCategories } from "./build-categories"; import { buildBundle } from "./build-bundle"; -import { buildExamples } from "./build-examples"; -import { buildApiDocs } from "./build-api-docs"; -import { buildLicenses } from "./build-licenses"; +import { buildWebsiteMetadata } from "./build-website-metadata"; async function main() { // Create or empty the /build directory @@ -20,17 +18,9 @@ async function main() { // Build all individual categories const validCategories = await buildCategories(); - // Generate examples.json for each category - await buildExamples(validCategories); - console.info(" ✔️📝 Built examples"); - - // Generate api.json for each category - await buildApiDocs(validCategories); - console.info(" ✔️📖 Built API docs"); - - // Generate licenses.json for each category - await buildLicenses(validCategories); - console.info(" ✔️⚖️ Built licenses"); + // Generate enriched website metadata in meta/ + await buildWebsiteMetadata(validCategories); + console.info(" ✔️🌐 Built website metadata"); // Build the bundle package with all valid categories await buildBundle(validCategories); From ff2e167e6d4a1f18284e86cb110ad814288ef154 Mon Sep 17 00:00:00 2001 From: baxyz Date: Tue, 7 Apr 2026 22:16:19 +0000 Subject: [PATCH 3/4] =?UTF-8?q?docs(promise):=20=F0=9F=93=9D=20update=20fu?= =?UTF-8?q?nction=20descriptions=20and=20remove=20license=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helpers/promise/consoleLogPromise.ts | 8 ++++---- helpers/promise/falsyPromiseOrThrow.ts | 8 ++++---- helpers/promise/meaningPromiseOrThrow.ts | 9 +++++---- helpers/promise/truthyPromiseOrThrow.ts | 8 ++++---- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/helpers/promise/consoleLogPromise.ts b/helpers/promise/consoleLogPromise.ts index 225f0ef..7235c05 100644 --- a/helpers/promise/consoleLogPromise.ts +++ b/helpers/promise/consoleLogPromise.ts @@ -4,12 +4,12 @@ * SPDX-License-Identifier: LGPL-3.0-or-later */ -/* - * This program is under the terms of the GNU Lesser General Public License version 3 - * The full license information can be found in LICENSE in the root directory of this project. +/** + * Returns a function that logs data to the console and passes it through. + * @param prefix - Optional prefix for the console log + * @returns A function that logs and returns the data * @since 1.0.0 */ - export function consoleLogPromise(prefix?: string): (data: T) => T { return (data: T) => { console.log(prefix, data); diff --git a/helpers/promise/falsyPromiseOrThrow.ts b/helpers/promise/falsyPromiseOrThrow.ts index 3213987..c64c9dc 100644 --- a/helpers/promise/falsyPromiseOrThrow.ts +++ b/helpers/promise/falsyPromiseOrThrow.ts @@ -4,12 +4,12 @@ * SPDX-License-Identifier: LGPL-3.0-or-later */ -/* - * This program is under the terms of the GNU Lesser General Public License version 3 - * The full license information can be found in LICENSE in the root directory of this project. +/** + * Returns a function that passes through falsy data or throws an error. + * @param error - The error message to throw if data is truthy + * @returns A function that returns the data if falsy, or throws * @since 1.0.0 */ - export function falsyPromiseOrThrow(error: string): (data: T) => T | never { return (data: unknown) => { if (data) { diff --git a/helpers/promise/meaningPromiseOrThrow.ts b/helpers/promise/meaningPromiseOrThrow.ts index 502d054..3dfbc8f 100644 --- a/helpers/promise/meaningPromiseOrThrow.ts +++ b/helpers/promise/meaningPromiseOrThrow.ts @@ -4,12 +4,13 @@ * SPDX-License-Identifier: LGPL-3.0-or-later */ -/* - * This program is under the terms of the GNU Lesser General Public License version 3 - * The full license information can be found in LICENSE in the root directory of this project. +/** + * Returns a function that passes through meaningful data or throws an error. + * Data is considered meaningless if it is null, undefined, empty string, empty object, or empty array. + * @param error - The error message to throw if data is meaningless + * @returns A function that returns the data if meaningful, or throws * @since 1.0.0 */ - export function meaningPromiseOrThrow( error: string ): (data: T) => T | never { diff --git a/helpers/promise/truthyPromiseOrThrow.ts b/helpers/promise/truthyPromiseOrThrow.ts index dd5e28a..814b782 100644 --- a/helpers/promise/truthyPromiseOrThrow.ts +++ b/helpers/promise/truthyPromiseOrThrow.ts @@ -4,12 +4,12 @@ * SPDX-License-Identifier: LGPL-3.0-or-later */ -/* - * This program is under the terms of the GNU Lesser General Public License version 3 - * The full license information can be found in LICENSE in the root directory of this project. +/** + * Returns a function that passes through truthy data or throws an error. + * @param error - The error message to throw if data is falsy + * @returns A function that returns the data if truthy, or throws * @since 1.0.0 */ - export function truthyPromiseOrThrow(error: string): (data: T) => T | never { return (data: unknown) => { if (data) { From 48c6267d4fd3ef241bc9d50535e86b120d63a4ce Mon Sep 17 00:00:00 2001 From: baxyz Date: Tue, 7 Apr 2026 22:17:03 +0000 Subject: [PATCH 4/4] =?UTF-8?q?refactor(function):=20=E2=99=BB=EF=B8=8F=20?= =?UTF-8?q?use=20ReflectionKind=20for=20member=20kind=20mapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/build/build-website-metadata.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/build/build-website-metadata.ts b/scripts/build/build-website-metadata.ts index ebb0d0a..78628d5 100644 --- a/scripts/build/build-website-metadata.ts +++ b/scripts/build/build-website-metadata.ts @@ -13,6 +13,7 @@ import { type DeclarationReflection, type ParameterReflection, type ProjectReflection, + ReflectionKind, type SignatureReflection, type TypeParameterReflection, } from 'typedoc'; @@ -196,10 +197,10 @@ function processSignature(sig: SignatureReflection): WebsiteSignature { function processMember(child: DeclarationReflection): WebsiteFunction | undefined { const kindMap: Record = { - 64: 'function', - 2097152: 'type', - 256: 'interface', - 32: 'variable', + [ReflectionKind.Function]: 'function', + [ReflectionKind.TypeAlias]: 'type', + [ReflectionKind.Interface]: 'interface', + [ReflectionKind.Variable]: 'variable', }; const kind = kindMap[child.kind];