feat(m3u): support #KODIPROP lines placed before #EXTINF (#1234)

* feat(m3u): support #KODIPROP lines placed before #EXTINF

Bumps the iptv-playlist-parser fork pin to v0.15.2-iptvnator.2: Kodi
property lines apply to the next list entry, so #KODIPROP lines placed
above the #EXTINF are now preserved in item.raw (previously the parser
dropped them and ClearKey config in that layout was lost). The DASH +
ClearKey feature (#1225) extracts license config from item.raw, so both
KODIPROP layouts now work on every import path.

Covered by a parser contract case and an extended web-backend /parse
regression (before-EXTINF + between-EXTINF-and-URL + plain channel).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: reflect before-#EXTINF KODIPROP support in the M3U architecture doc

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: list the KODIPROP delta in the parser-fork inventory

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 authored and GitHub committed 2026-07-25 11:43:58 +02:00
1 parent f852bc7459
commit 0ee73f2d0f
5 files changed
+70 -140

No files matched your search

@@ -200,11 +200,18 @@ https://stream.example/news.m3u8`);
const httpClient = new StubHttpClient();
const kid = '00112233445566778899aabbccddeeff';
const key = 'ffeeddccbbaa99887766554433221100';
// First channel uses the KODIPROP-before-#EXTINF layout (supported
// since the 0.15.2-iptvnator.2 parser pin), the second the common
// between-#EXTINF-and-URL layout.
httpClient.queueResponse(`#EXTM3U
#EXTINF:-1 tvg-id="ck" group-title="DASH",Encrypted DASH
#KODIPROP:inputstream.adaptive.license_type=clearkey
#KODIPROP:inputstream.adaptive.license_key=${kid}:${key}
#EXTINF:-1 tvg-id="ck" group-title="DASH",Encrypted DASH
https://stream.example/enc.mpd
#EXTINF:-1 tvg-id="ck2" group-title="DASH",Encrypted DASH 2
#KODIPROP:inputstream.adaptive.license_type=clearkey
#KODIPROP:inputstream.adaptive.license_key=${kid}:${key}
https://stream.example/enc2.mpd
#EXTINF:-1 tvg-id="plain",Plain Channel
https://stream.example/live.m3u8`);
@@ -229,13 +236,18 @@ https://stream.example/live.m3u8`);
};
expect(response.status).toBe(200);
expect(body.playlist.items).toHaveLength(2);
expect(body.playlist.items).toHaveLength(3);
expect(body.playlist.items[0]['drm']).toEqual({
licenseType: 'clearkey',
supported: true,
clearKeys: { [kid]: key },
});
expect(body.playlist.items[1]['drm']).toBeUndefined();
expect(body.playlist.items[1]['drm']).toEqual({
licenseType: 'clearkey',
supported: true,
clearKeys: { [kid]: key },
});
expect(body.playlist.items[2]['drm']).toBeUndefined();
}
);
});
@@ -106,4 +106,30 @@ describe('iptv-playlist-parser contract (4gray fork)', () => {
'https://epg.example/guide.xml'
);
});
it('preserves #KODIPROP lines placed before #EXTINF in item raw', () => {
// Kodi property lines apply to the NEXT entry; the DASH/ClearKey
// support extracts license config from item.raw (#1225).
const result = parse(
[
'#EXTM3U',
'#KODIPROP:inputstream.adaptive.license_type=clearkey',
'#KODIPROP:inputstream.adaptive.license_key=kid123:key456',
'#EXTINF:-1,Encrypted',
'http://example.com/enc.mpd',
'#EXTINF:-1,Plain',
'http://example.com/plain.m3u8',
'',
].join('\n')
);
expect(result.items.length).toBe(2);
expect(result.items[0].raw).toContain(
'#KODIPROP:inputstream.adaptive.license_type=clearkey'
);
expect(result.items[0].raw).toContain(
'#KODIPROP:inputstream.adaptive.license_key=kid123:key456'
);
expect(result.items[1].raw).not.toContain('#KODIPROP');
});
});
+9 -6
View File
@@ -46,10 +46,11 @@ The M3U playlist module provides:
All four parse call sites (Electron `playlist-source.ts` import, `playlist-refresh.worker.ts`, `web-backend` `/parse`, PWA `playlists.service.ts`) use the
[4gray/iptv-playlist-parser](https://github.com/4gray/iptv-playlist-parser) fork, pinned by commit SHA in `package.json`. The fork tracks upstream
`freearhey/iptv-playlist-parser` (currently synced to v0.15.2) plus two deliberate deltas iptvnator depends on:
`freearhey/iptv-playlist-parser` (currently synced to v0.15.2) plus three deliberate deltas iptvnator depends on:
- **`radio` attribute** — `item.radio` (string, `'true'` triggers the radio player, EPG suppression, and external-player gating app-wide). Upstream does not have this field; it must survive every upstream sync.
- **Pipe stripping** — `item.url` is cut at the first `|`; `|User-Agent=` / `|Referer=` params still land in `item.http`. Upstream 0.15.0 stopped stripping, but iptvnator consumes `item.url` verbatim in hls.js/mpv/vlc, catch-up URL building, and url-keyed favorites.
- **`#KODIPROP` lines before `#EXTINF` are preserved** (since `v0.15.2-iptvnator.2`) — Kodi property lines apply to the *next* list entry, so ones placed above the `#EXTINF` are buffered and attached to that item's `raw` in file order (case-insensitive prefix); other stray `#` lines outside an open item are still dropped. The DASH + ClearKey feature extracts `inputstream.adaptive.license_*` config from `item.raw`, so this delta must survive every upstream sync.
There is intentionally **no URL validation** (upstream removed it in 0.15.0): any non-empty non-`#` line after `#EXTINF` becomes the item URL. This is what fixes issue #1189 (Pluto TV JWT URLs longer than validator's 2084-char IE-era limit used to be rejected, and the stalled item index collapsed the whole playlist into one channel). `#` comment lines and unknown directives are appended to `item.raw` and never treated as URLs.
@@ -745,11 +746,13 @@ player in settings.
**DRM data flow** (M3U module only; Xtream/Stalker have no DRM concept):
1. The playlist parser fork does not understand `#KODIPROP:` lines, but keeps
every unknown line between `#EXTINF` and the stream URL in `item.raw` (the
dominant Kodi/TiviMate layout; `#KODIPROP` lines _before_ `#EXTINF` are
dropped by the parser — fixing that requires a parser-fork patch and is
deferred).
1. The playlist parser fork does not interpret `#KODIPROP:` lines, but
preserves them in `item.raw` for **both** layouts: unknown lines between
`#EXTINF` and the stream URL are kept as before, and since parser pin
`v0.15.2-iptvnator.2` `#KODIPROP` lines placed *before* the `#EXTINF` are
buffered and attached to the **next** entry's `raw` in file order (Kodi
semantics, case-insensitive prefix). Other stray `#` lines outside an open
item are still dropped, matching upstream.
2. `extractDrmFromRaw()` (`libs/shared/m3u-utils/src/lib/kodiprop.utils.ts`)
post-processes `raw` inside `createPlaylistObject()` — the single funnel
for all four import paths (Electron URL/file import, refresh worker,
+1 -1
View File
@@ -96,7 +96,7 @@
"epg-parser": "^0.1.6",
"fix-path": "5.0.0",
"hls.js": "1.6.13",
"iptv-playlist-parser": "github:4gray/iptv-playlist-parser#v0.15.2-iptvnator.1",
"iptv-playlist-parser": "github:4gray/iptv-playlist-parser#v0.15.2-iptvnator.2",
"marked": "18.0.5",
"mpegts.js": "1.8.0",
"ms": "2.1.3",
+19 -130
View File
@@ -123,8 +123,8 @@ importers:
specifier: 1.6.13
version: 1.6.13
iptv-playlist-parser:
specifier: github:4gray/iptv-playlist-parser#v0.15.2-iptvnator.1
version: https://codeload.github.com/4gray/iptv-playlist-parser/tar.gz/33f5e9c09539524d758901c02a6f29302833c0a4
specifier: github:4gray/iptv-playlist-parser#v0.15.2-iptvnator.2
version: https://codeload.github.com/4gray/iptv-playlist-parser/tar.gz/d547b6be8a44431c85fbcbcdefb41f28c0dadf6d
marked:
specifier: 18.0.5
version: 18.0.5
@@ -398,7 +398,7 @@ importers:
version: 30.2.0
jest-preset-angular:
specifier: ~15.0.0
version: 15.0.3(c17320d959ee89222a5c83bff087787c)
version: 15.0.3(75c2f4d3e02d43367177c3c83cdeef78)
jest-util:
specifier: ^30.0.2
version: 30.2.0
@@ -431,7 +431,7 @@ importers:
version: 3.4.19(tsx@4.21.0)(yaml@2.8.2)
ts-jest:
specifier: ^29.4.5
version: 29.4.6(@babel/core@7.29.7)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.7))(esbuild@0.28.1)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(esbuild-register@3.6.0(esbuild@0.28.1))(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(@types/node@20.19.9)(typescript@5.9.3)))(typescript@5.9.3)
version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(esbuild@0.28.1)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(esbuild-register@3.6.0(esbuild@0.28.1))(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(@types/node@20.19.9)(typescript@5.9.3)))(typescript@5.9.3)
ts-node:
specifier: 10.9.2
version: 10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(@types/node@20.19.9)(typescript@5.9.3)
@@ -7578,9 +7578,9 @@ packages:
resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==}
engines: {node: '>= 10'}
iptv-playlist-parser@https://codeload.github.com/4gray/iptv-playlist-parser/tar.gz/33f5e9c09539524d758901c02a6f29302833c0a4:
resolution: {tarball: https://codeload.github.com/4gray/iptv-playlist-parser/tar.gz/33f5e9c09539524d758901c02a6f29302833c0a4}
version: 0.15.2-iptvnator.1
iptv-playlist-parser@https://codeload.github.com/4gray/iptv-playlist-parser/tar.gz/d547b6be8a44431c85fbcbcdefb41f28c0dadf6d:
resolution: {tarball: https://codeload.github.com/4gray/iptv-playlist-parser/tar.gz/d547b6be8a44431c85fbcbcdefb41f28c0dadf6d}
version: 0.15.2-iptvnator.2
engines: {node: '>=18'}
iron-webcrypto@1.2.1:
@@ -10508,7 +10508,6 @@ packages:
shaka-player@5.2.1:
resolution: {integrity: sha512-Sj8Kg94Ws8hUxzjHnC570o1jQcZwB7OxIwi25vLI3mOVmL8xpso3HyIqM7hjbgAhsOgdFojpJYwtcvR/t0a9vA==}
engines: {node: '>=18'}
shallow-clone@3.0.1:
resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==}
@@ -12877,12 +12876,6 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-plugin-utils': 7.27.1
optional: true
'@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -12893,12 +12886,6 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-plugin-utils': 7.27.1
optional: true
'@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -12909,12 +12896,6 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-plugin-utils': 7.27.1
optional: true
'@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -12925,12 +12906,6 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-plugin-utils': 7.27.1
optional: true
'@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -12951,12 +12926,6 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-plugin-utils': 7.27.1
optional: true
'@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -12967,12 +12936,6 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-plugin-utils': 7.27.1
optional: true
'@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -12983,12 +12946,6 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-plugin-utils': 7.27.1
optional: true
'@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -13009,12 +12966,6 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-plugin-utils': 7.27.1
optional: true
'@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -13025,12 +12976,6 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-plugin-utils': 7.27.1
optional: true
'@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -13041,12 +12986,6 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-plugin-utils': 7.27.1
optional: true
'@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -13057,12 +12996,6 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-plugin-utils': 7.27.1
optional: true
'@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -13073,12 +13006,6 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-plugin-utils': 7.27.1
optional: true
'@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -13089,12 +13016,6 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-plugin-utils': 7.27.1
optional: true
'@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -13105,12 +13026,6 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-plugin-utils': 7.27.1
optional: true
'@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -13121,12 +13036,6 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-plugin-utils': 7.27.1
optional: true
'@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -17719,13 +17628,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
babel-jest@30.2.0(@babel/core@7.29.7):
babel-jest@30.2.0(@babel/core@7.29.0):
dependencies:
'@babel/core': 7.29.7
'@babel/core': 7.29.0
'@jest/transform': 30.2.0
'@types/babel__core': 7.20.5
babel-plugin-istanbul: 7.0.1
babel-preset-jest: 30.2.0(@babel/core@7.29.7)
babel-preset-jest: 30.2.0(@babel/core@7.29.0)
chalk: 4.1.2
graceful-fs: 4.2.11
slash: 3.0.0
@@ -17838,37 +17747,17 @@ snapshots:
'@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0)
'@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0)
babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7):
dependencies:
'@babel/core': 7.29.7
'@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7)
'@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7)
'@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7)
'@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7)
'@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.29.7)
'@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7)
'@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7)
'@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7)
'@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7)
'@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7)
'@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7)
'@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7)
'@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7)
'@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7)
'@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7)
optional: true
babel-preset-jest@30.2.0(@babel/core@7.28.5):
dependencies:
'@babel/core': 7.28.5
babel-plugin-jest-hoist: 30.2.0
babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5)
babel-preset-jest@30.2.0(@babel/core@7.29.7):
babel-preset-jest@30.2.0(@babel/core@7.29.0):
dependencies:
'@babel/core': 7.29.7
'@babel/core': 7.29.0
babel-plugin-jest-hoist: 30.2.0
babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7)
babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0)
optional: true
bail@2.0.2: {}
@@ -20652,7 +20541,7 @@ snapshots:
ipaddr.js@2.3.0: {}
iptv-playlist-parser@https://codeload.github.com/4gray/iptv-playlist-parser/tar.gz/33f5e9c09539524d758901c02a6f29302833c0a4: {}
iptv-playlist-parser@https://codeload.github.com/4gray/iptv-playlist-parser/tar.gz/d547b6be8a44431c85fbcbcdefb41f28c0dadf6d: {}
iron-webcrypto@1.2.1: {}
@@ -21095,7 +20984,7 @@ snapshots:
optionalDependencies:
jest-resolve: 30.2.0
jest-preset-angular@15.0.3(c17320d959ee89222a5c83bff087787c):
jest-preset-angular@15.0.3(75c2f4d3e02d43367177c3c83cdeef78):
dependencies:
'@angular/compiler-cli': 21.2.17(@angular/compiler@21.2.17)(typescript@5.9.3)
'@angular/core': 21.2.17(@angular/compiler@21.2.17)(rxjs@7.8.2)(zone.js@0.15.1)
@@ -21108,7 +20997,7 @@ snapshots:
jest-util: 30.2.0
jsdom: 26.1.0
pretty-format: 30.2.0
ts-jest: 29.4.6(@babel/core@7.29.7)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.7))(esbuild@0.28.1)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(esbuild-register@3.6.0(esbuild@0.28.1))(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(@types/node@20.19.9)(typescript@5.9.3)))(typescript@5.9.3)
ts-jest: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(esbuild@0.28.1)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(esbuild-register@3.6.0(esbuild@0.28.1))(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(@types/node@20.19.9)(typescript@5.9.3)))(typescript@5.9.3)
typescript: 5.9.3
optionalDependencies:
esbuild: 0.28.1
@@ -24994,7 +24883,7 @@ snapshots:
ts-interface-checker@0.1.13: {}
ts-jest@29.4.6(@babel/core@7.29.7)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.7))(esbuild@0.28.1)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(esbuild-register@3.6.0(esbuild@0.28.1))(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(@types/node@20.19.9)(typescript@5.9.3)))(typescript@5.9.3):
ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(esbuild@0.28.1)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.9)(babel-plugin-macros@3.1.0)(esbuild-register@3.6.0(esbuild@0.28.1))(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(@types/node@20.19.9)(typescript@5.9.3)))(typescript@5.9.3):
dependencies:
bs-logger: 0.2.6
fast-json-stable-stringify: 2.1.0
@@ -25008,10 +24897,10 @@ snapshots:
typescript: 5.9.3
yargs-parser: 21.1.1
optionalDependencies:
'@babel/core': 7.29.7
'@babel/core': 7.29.0
'@jest/transform': 30.2.0
'@jest/types': 30.2.0
babel-jest: 30.2.0(@babel/core@7.29.7)
babel-jest: 30.2.0(@babel/core@7.29.0)
esbuild: 0.28.1
jest-util: 30.2.0