@timmsy/riftjs 1.5.0 → 2.0.2

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.
@@ -0,0 +1,51 @@
1
+ name: Publish package
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: read
13
+ packages: write
14
+
15
+ steps:
16
+ - name: Checkout
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Setup Node
20
+ uses: actions/setup-node@v4
21
+ with:
22
+ node-version: 20
23
+ registry-url: https://registry.npmjs.org
24
+
25
+ - name: Install dependencies
26
+ run: npm ci
27
+
28
+ - name: Run tests
29
+ run: npm test
30
+
31
+ - name: Validate tag matches package version
32
+ run: |
33
+ TAG_VERSION="${GITHUB_REF_NAME#v}"
34
+ PKG_VERSION="$(node -p "require('./package.json').version")"
35
+ if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
36
+ echo "Tag version v$TAG_VERSION does not match package.json version $PKG_VERSION"
37
+ exit 1
38
+ fi
39
+
40
+ - name: Publish to npm
41
+ run: npm publish --access public
42
+ env:
43
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
44
+
45
+ - name: Publish to GitHub Packages
46
+ run: |
47
+ echo "@timmsy:registry=https://npm.pkg.github.com" >> "$NPM_CONFIG_USERCONFIG"
48
+ echo "//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}" >> "$NPM_CONFIG_USERCONFIG"
49
+ npm publish --registry=https://npm.pkg.github.com
50
+ env:
51
+ NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 James Timms
3
+ Copyright (c) 2026 James Timms
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -20,7 +20,7 @@ RiftJS simplifies interaction with the Riot Games API and DataDragon static data
20
20
 
21
21
  Install RiftJS via npm:
22
22
 
23
- ```
23
+ ```bash
24
24
  npm install @timmsy/riftjs
25
25
  ```
26
26
 
@@ -33,18 +33,23 @@ You’ll also need a Riot Games API key from [developer.riotgames.com](https://d
33
33
 
34
34
  2. **Configure Environment**:
35
35
  Create a `.env` file in your project root with your API key and region:
36
- ```
36
+
37
+ ```env
37
38
  RIOT_API_KEY=RGAPI-your-api-key-here
38
39
  REGION=EUW1
40
+ TEST_RIOT_ID=YourRiotName
41
+ TEST_TAG_LINE=EUW
39
42
  ```
43
+
40
44
  - Replace `RGAPI-your-api-key-here` with your API key.
41
45
  - Use a short region code (e.g., `EUW1`, `NA1`). See [Region Mapping](#region-mapping) for details.
46
+ - `TEST_RIOT_ID` and `TEST_TAG_LINE` are used by the endpoint test script.
42
47
 
43
48
  ## Usage
44
49
 
45
50
  Here’s a basic example to get started:
46
51
 
47
- ```
52
+ ```js
48
53
  const { RiotAPI, DataDragon } = require('@timmsy/riftjs');
49
54
 
50
55
  // Initialize RiotAPI
@@ -92,22 +97,45 @@ fetchStaticData();
92
97
  ## API Reference
93
98
 
94
99
  ### RiotAPI
100
+
95
101
  - `getAccountByRiotId(riotId, [tagLine], [region])`: Fetch account by Riot ID (e.g., `Timmsy#BRUV`).
96
102
  - `getSummonerByPuuid(puuid, [region])`: Get summoner data by PUUID.
97
103
  - `getMatchlistByPuuid(puuid, [options], [region])`: Get match history (options: `{ start, count }`).
98
- - `getMatchById(matchId, [region])`: Get match details.
104
+ - `getMatchById(matchId, [region])`: Get full match payload (`metadata` + `info`) by match ID.
105
+ - `getMatchTimelineById(matchId, [region])`: Get timeline payload by match ID.
106
+ - `getMatchlistByPuuidAll(puuid, [options], [region], [pacing])`: Fetch all match IDs in pages of 100.
107
+ - `getMatchesWithDetailsByPuuid(puuid, [options], [region], [pacing])`: Fetch all match IDs and their match payloads.
108
+
109
+ `options` supports Riot Match-V5 query params:
110
+ - `startTime` (epoch seconds)
111
+ - `endTime` (epoch seconds)
112
+ - `queue` (int)
113
+ - `type` (string)
114
+ - `start` (int, default `0`)
115
+ - `count` (int, `0-100`, single-page method only)
116
+
117
+ `pacing` helps respect rate limits on multi-request methods:
118
+ - `getMatchlistByPuuidAll`: `{ delayMs, maxMatches }`
119
+ - `getMatchesWithDetailsByPuuid`: `{ pageDelayMs, detailDelayMs, maxMatches }`
99
120
 
100
121
  ### DataDragon
122
+
101
123
  - `getChampions()`: Fetch all champion data.
102
124
  - `getItems()`: Fetch all item data.
125
+ - `new DataDragon()` resolves the latest Data Dragon patch automatically.
126
+ - `new DataDragon('x.y.z')` pins requests to an explicit patch version.
103
127
 
104
128
  ## Region Mapping
105
129
 
106
130
  RiftJS uses a region map to route requests correctly:
131
+
107
132
  - **Platform Routing** (e.g., Summoner V4):
133
+
108
134
  - `EUW1` → `euw1.api.riotgames.com`
109
135
  - `NA1` → `na1.api.riotgames.com`
136
+
110
137
  - **Shard Routing** (e.g., Account V1, Match V5):
138
+
111
139
  - `EUW1` → `europe.api.riotgames.com`
112
140
  - `NA1` → `americas.api.riotgames.com`
113
141
 
@@ -129,17 +157,29 @@ Supported regions: `BR1`, `EUN1`, `EUW1`, `JP1`, `KR`, `LA1`, `LA2`, `NA1`, `OC1
129
157
  ## Development
130
158
 
131
159
  To contribute or run locally:
160
+
132
161
  1. Clone the repo:
133
- ```
162
+
163
+ ```bash
134
164
  git clone https://github.com/timmsy1998/RiftJS.git
135
165
  cd RiftJS
136
166
  ```
167
+
137
168
  2. Install dependencies:
138
- ```
169
+
170
+ ```bash
139
171
  npm install
140
172
  ```
173
+
141
174
  3. Create a `.env` file (see [Setup](#setup)).
142
- 4. Test with `node test.js` (requires a valid API key).
175
+ 4. Run endpoint checks:
176
+
177
+ ```bash
178
+ npm test
179
+ ```
180
+
181
+ - Riot API calls run when `TEST_RIOT_ID` is set.
182
+ - Data Dragon calls always run.
143
183
 
144
184
  ## License
145
185
 
@@ -149,4 +189,4 @@ MIT License © 2025 James Timms. See [LICENSE](LICENSE) for details.
149
189
 
150
190
  - **npm Registry**: [https://www.npmjs.com/package/@timmsy/riftjs](https://www.npmjs.com/package/@timmsy/riftjs)
151
191
  - **GitHub Repository**: [https://github.com/timmsy1998/RiftJS](https://github.com/timmsy1998/RiftJS)
152
- - **Riot Developer Portal**: [https://developer.riotgames.com/](https://developer.riotgames.com/)
192
+ - **Riot Developer Portal**: [https://developer.riotgames.com/](https://developer.riotgames.com/)
@@ -1,29 +1,40 @@
1
1
  const axios = require('axios');
2
2
 
3
- module.exports = (baseURL) => ({
4
- /**
5
- * Get all champion data.
6
- * @returns {Promise<object>} Champion data.
7
- */
8
- async getChampions() {
9
- try {
10
- const response = await axios.get(`${baseURL}/champion.json`);
11
- return response.data;
12
- } catch (error) {
13
- throw new Error(`DataDragon error: ${error.message}`);
3
+ module.exports = (baseURLOrResolver) => {
4
+ const resolveBaseURL = async () => {
5
+ if (typeof baseURLOrResolver === 'function') {
6
+ return baseURLOrResolver();
14
7
  }
15
- },
8
+ return baseURLOrResolver;
9
+ };
16
10
 
17
- /**
18
- * Get all item data.
19
- * @returns {Promise<object>} Item data.
20
- */
21
- async getItems() {
22
- try {
23
- const response = await axios.get(`${baseURL}/item.json`);
24
- return response.data;
25
- } catch (error) {
26
- throw new Error(`DataDragon error: ${error.message}`);
27
- }
28
- },
29
- });
11
+ return {
12
+ /**
13
+ * Fetch champion metadata for the configured Data Dragon version/locale.
14
+ * @returns {Promise<object>} Champion data.
15
+ */
16
+ async getChampions() {
17
+ try {
18
+ const baseURL = await resolveBaseURL();
19
+ const response = await axios.get(`${baseURL}/champion.json`);
20
+ return response.data;
21
+ } catch (error) {
22
+ throw new Error(`DataDragon error: ${error.message}`);
23
+ }
24
+ },
25
+
26
+ /**
27
+ * Fetch item metadata for the configured Data Dragon version/locale.
28
+ * @returns {Promise<object>} Item data.
29
+ */
30
+ async getItems() {
31
+ try {
32
+ const baseURL = await resolveBaseURL();
33
+ const response = await axios.get(`${baseURL}/item.json`);
34
+ return response.data;
35
+ } catch (error) {
36
+ throw new Error(`DataDragon error: ${error.message}`);
37
+ }
38
+ },
39
+ };
40
+ };
package/endpoints/riot.js CHANGED
@@ -1,10 +1,12 @@
1
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
2
+
1
3
  module.exports = (client, defaultRegion, regionMap) => ({
2
4
  /**
3
- * Get account info by Riot ID (gameName and tagLine).
5
+ * Fetch account data by Riot ID.
4
6
  * @param {string} riotId - Riot ID (e.g., "Timmsy#BRUV" or "Timmsy", "BRUV").
5
7
  * @param {string} [tagLine] - Optional tagLine if not included in riotId.
6
- * @param {string} [region] - Short region (defaults to constructor region).
7
- * @returns {Promise<object>} Account data including puuid.
8
+ * @param {string} [region] - Region code used to resolve shard routing.
9
+ * @returns {Promise<object>} Account payload including `puuid`.
8
10
  */
9
11
  async getAccountByRiotId(riotId, tagLine = null, region = defaultRegion) {
10
12
  let gameName, tag;
@@ -27,10 +29,10 @@ module.exports = (client, defaultRegion, regionMap) => ({
27
29
  },
28
30
 
29
31
  /**
30
- * Get summoner data by PUUID.
32
+ * Fetch Summoner-V4 data for a player's PUUID.
31
33
  * @param {string} puuid - Encrypted PUUID.
32
- * @param {string} [region] - Short region (defaults to constructor region).
33
- * @returns {Promise<object>} Summoner data.
34
+ * @param {string} [region] - Region code used to resolve platform routing.
35
+ * @returns {Promise<object>} Summoner payload.
34
36
  */
35
37
  async getSummonerByPuuid(puuid, region = defaultRegion) {
36
38
  const platform = regionMap[region].platform;
@@ -45,10 +47,10 @@ module.exports = (client, defaultRegion, regionMap) => ({
45
47
  },
46
48
 
47
49
  /**
48
- * Get match history by PUUID.
49
- * @param {string} puuid - Summoner's PUUID.
50
- * @param {object} [options] - Query options (e.g., start, count).
51
- * @param {string} [region] - Short region (defaults to constructor region).
50
+ * Fetch match IDs for a PUUID from Match-V5.
51
+ * @param {string} puuid - Player PUUID.
52
+ * @param {object} [options] - Query params such as `start` and `count`.
53
+ * @param {string} [region] - Region code used to resolve shard routing.
52
54
  * @returns {Promise<string[]>} Array of match IDs.
53
55
  */
54
56
  async getMatchlistByPuuid(puuid, options = {}, region = defaultRegion) {
@@ -65,10 +67,10 @@ module.exports = (client, defaultRegion, regionMap) => ({
65
67
  },
66
68
 
67
69
  /**
68
- * Get match details by match ID.
70
+ * Fetch full match details by match ID.
69
71
  * @param {string} matchId - Match ID (e.g., "EUW1_1234567890").
70
- * @param {string} [region] - Short region (defaults to constructor region).
71
- * @returns {Promise<object>} Match data.
72
+ * @param {string} [region] - Region code used to resolve shard routing.
73
+ * @returns {Promise<object>} Match payload.
72
74
  */
73
75
  async getMatchById(matchId, region = defaultRegion) {
74
76
  const shard = regionMap[region].shard;
@@ -81,4 +83,94 @@ module.exports = (client, defaultRegion, regionMap) => ({
81
83
  throw this._handleError(error);
82
84
  }
83
85
  },
84
- });
86
+
87
+ /**
88
+ * Fetch match timeline data by match ID.
89
+ * @param {string} matchId - Match ID (e.g., "EUW1_1234567890").
90
+ * @param {string} [region] - Region code used to resolve shard routing.
91
+ * @returns {Promise<object>} Match timeline payload.
92
+ */
93
+ async getMatchTimelineById(matchId, region = defaultRegion) {
94
+ const shard = regionMap[region].shard;
95
+ try {
96
+ const response = await client.get(`/lol/match/v5/matches/${matchId}/timeline`, {
97
+ baseURL: `https://${shard}`,
98
+ });
99
+ return response.data;
100
+ } catch (error) {
101
+ throw this._handleError(error);
102
+ }
103
+ },
104
+
105
+ /**
106
+ * Fetch all match IDs for a PUUID using Riot's max page size (100).
107
+ * @param {string} puuid - Player PUUID.
108
+ * @param {object} [options] - Riot filters: startTime, endTime, queue, type, start.
109
+ * @param {string} [region] - Region code used to resolve shard routing.
110
+ * @param {object} [pacing] - Pacing controls.
111
+ * @param {number} [pacing.delayMs=1250] - Delay between page requests.
112
+ * @param {number|null} [pacing.maxMatches=null] - Optional cap on total IDs returned.
113
+ * @returns {Promise<string[]>} All matched IDs.
114
+ */
115
+ async getMatchlistByPuuidAll(puuid, options = {}, region = defaultRegion, pacing = {}) {
116
+ const baseStart = Number.isInteger(options.start) ? options.start : 0;
117
+ const pageDelayMs = Number.isInteger(pacing.delayMs) ? pacing.delayMs : 1250;
118
+ const maxMatches = Number.isInteger(pacing.maxMatches) && pacing.maxMatches >= 0 ? pacing.maxMatches : null;
119
+ const filters = {
120
+ startTime: options.startTime,
121
+ endTime: options.endTime,
122
+ queue: options.queue,
123
+ type: options.type,
124
+ };
125
+
126
+ let start = baseStart;
127
+ const allMatchIds = [];
128
+
129
+ while (true) {
130
+ const remaining = maxMatches === null ? 100 : Math.min(100, maxMatches - allMatchIds.length);
131
+ if (remaining <= 0) break;
132
+
133
+ const page = await this.getMatchlistByPuuid(puuid, { ...filters, start, count: remaining }, region);
134
+ allMatchIds.push(...page);
135
+
136
+ if (page.length < remaining) break;
137
+ start += page.length;
138
+ if (pageDelayMs > 0) await sleep(pageDelayMs);
139
+ }
140
+
141
+ return allMatchIds;
142
+ },
143
+
144
+ /**
145
+ * Fetch match IDs and full match payloads for each ID.
146
+ * @param {string} puuid - Player PUUID.
147
+ * @param {object} [options] - Riot filters: startTime, endTime, queue, type, start.
148
+ * @param {string} [region] - Region code used to resolve shard routing.
149
+ * @param {object} [pacing] - Pacing controls.
150
+ * @param {number} [pacing.pageDelayMs=1250] - Delay between matchlist page requests.
151
+ * @param {number} [pacing.detailDelayMs=1250] - Delay between match detail requests.
152
+ * @param {number|null} [pacing.maxMatches=null] - Optional cap on total matches fetched.
153
+ * @returns {Promise<{matchIds: string[], matches: object[]}>} IDs and full match payloads.
154
+ */
155
+ async getMatchesWithDetailsByPuuid(puuid, options = {}, region = defaultRegion, pacing = {}) {
156
+ const pageDelayMs = Number.isInteger(pacing.pageDelayMs) ? pacing.pageDelayMs : 1250;
157
+ const detailDelayMs = Number.isInteger(pacing.detailDelayMs) ? pacing.detailDelayMs : 1250;
158
+ const maxMatches = Number.isInteger(pacing.maxMatches) && pacing.maxMatches >= 0 ? pacing.maxMatches : null;
159
+ const matchIds = await this.getMatchlistByPuuidAll(
160
+ puuid,
161
+ options,
162
+ region,
163
+ { delayMs: pageDelayMs, maxMatches }
164
+ );
165
+ const matches = [];
166
+
167
+ for (let i = 0; i < matchIds.length; i += 1) {
168
+ matches.push(await this.getMatchById(matchIds[i], region));
169
+ if (detailDelayMs > 0 && i < matchIds.length - 1) {
170
+ await sleep(detailDelayMs);
171
+ }
172
+ }
173
+
174
+ return { matchIds, matches };
175
+ },
176
+ });
package/index.js CHANGED
@@ -3,9 +3,8 @@ const axios = require('axios');
3
3
  const riotEndpoints = require('./endpoints/riot');
4
4
  const dataDragonEndpoints = require('./endpoints/datadragon');
5
5
 
6
- // Region mapping: short region to shard
6
+ // Maps region codes to the platform and routing shard hosts required by Riot APIs.
7
7
  const regionMap = {
8
- // Platform Routing (short regions)
9
8
  BR1: { platform: 'br1.api.riotgames.com', shard: 'americas.api.riotgames.com' },
10
9
  EUN1: { platform: 'eun1.api.riotgames.com', shard: 'europe.api.riotgames.com' },
11
10
  EUW1: { platform: 'euw1.api.riotgames.com', shard: 'europe.api.riotgames.com' },
@@ -24,9 +23,6 @@ const regionMap = {
24
23
  VN2: { platform: 'vn2.api.riotgames.com', shard: 'sea.api.riotgames.com' },
25
24
  };
26
25
 
27
- /**
28
- * RiotAPI class for interacting with Riot Games API endpoints.
29
- */
30
26
  class RiotAPI {
31
27
  constructor() {
32
28
  this.apiKey = process.env.RIOT_API_KEY;
@@ -36,14 +32,11 @@ class RiotAPI {
36
32
  this.client = axios.create({
37
33
  headers: { 'X-Riot-Token': this.apiKey },
38
34
  });
39
- // Attach RiotAPI endpoints with region map
35
+ // Attach endpoint methods with a shared axios client and region configuration.
40
36
  Object.assign(this, riotEndpoints(this.client, this.region, regionMap));
41
37
  }
42
38
 
43
- /**
44
- * Handle axios errors and provide meaningful messages.
45
- * @private
46
- */
39
+ // Normalize axios errors to stable Error messages for consumers.
47
40
  _handleError(error) {
48
41
  if (error.response) {
49
42
  const { status, data } = error.response;
@@ -55,17 +48,41 @@ class RiotAPI {
55
48
  }
56
49
  }
57
50
 
58
- /**
59
- * DataDragon class for accessing static game data.
60
- */
61
51
  class DataDragon {
62
- constructor(version = '14.19.1', locale = 'en_US') {
52
+ constructor(version = null, locale = 'en_US') {
63
53
  this.version = version;
64
54
  this.locale = locale;
65
- this.baseURL = `https://ddragon.leagueoflegends.com/cdn/${this.version}/data/${this.locale}`;
66
- // Attach DataDragon endpoints
67
- Object.assign(this, dataDragonEndpoints(this.baseURL));
55
+ this.baseURL = null;
56
+ this._baseURLPromise = null;
57
+
58
+ // Attach static-data endpoint methods that resolve the latest version when needed.
59
+ Object.assign(this, dataDragonEndpoints(() => this._resolveBaseURL()));
60
+ }
61
+
62
+ async _resolveBaseURL() {
63
+ if (this.baseURL) return this.baseURL;
64
+ if (this._baseURLPromise) return this._baseURLPromise;
65
+
66
+ this._baseURLPromise = (async () => {
67
+ if (!this.version) {
68
+ const response = await axios.get('https://ddragon.leagueoflegends.com/api/versions.json');
69
+ const latestVersion = Array.isArray(response.data) ? response.data[0] : null;
70
+ if (!latestVersion) {
71
+ throw new Error('Could not resolve latest Data Dragon version');
72
+ }
73
+ this.version = latestVersion;
74
+ }
75
+
76
+ this.baseURL = `https://ddragon.leagueoflegends.com/cdn/${this.version}/data/${this.locale}`;
77
+ return this.baseURL;
78
+ })();
79
+
80
+ try {
81
+ return await this._baseURLPromise;
82
+ } finally {
83
+ this._baseURLPromise = null;
84
+ }
68
85
  }
69
86
  }
70
87
 
71
- module.exports = { RiotAPI, DataDragon };
88
+ module.exports = { RiotAPI, DataDragon };
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@timmsy/riftjs",
3
- "version": "1.5.0",
3
+ "version": "2.0.2",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
- "test": "echo \"Error: no test specified\" && exit 1"
6
+ "test": "npm run test:endpoints",
7
+ "test:endpoints": "node test-endpoints.js"
7
8
  },
8
9
  "dependencies": {
9
10
  "axios": "^1.7.7",
@@ -28,4 +29,4 @@
28
29
  "type": "git",
29
30
  "url": "git+https://github.com/timmsy1998/RiftJS.git"
30
31
  }
31
- }
32
+ }
@@ -0,0 +1,65 @@
1
+ require('dotenv').config();
2
+ const { RiotAPI, DataDragon } = require('./index');
3
+
4
+ async function run() {
5
+ const riotId = process.env.TEST_RIOT_ID;
6
+ const tagLine = process.env.TEST_TAG_LINE;
7
+ const dd = new DataDragon();
8
+
9
+ if (process.env.RIOT_API_KEY && riotId) {
10
+ const riot = new RiotAPI();
11
+ const account = await riot.getAccountByRiotId(riotId, tagLine);
12
+ console.log('[PASS] getAccountByRiotId:', account.puuid);
13
+
14
+ const summoner = await riot.getSummonerByPuuid(account.puuid);
15
+ console.log('[PASS] getSummonerByPuuid:', {
16
+ puuid: summoner.puuid,
17
+ summonerLevel: summoner.summonerLevel,
18
+ });
19
+
20
+ const matchIds = await riot.getMatchlistByPuuid(account.puuid, { start: 0, count: 3 });
21
+ console.log('[PASS] getMatchlistByPuuid:', matchIds.length, 'match ids');
22
+
23
+ if (matchIds.length > 0) {
24
+ const match = await riot.getMatchById(matchIds[0]);
25
+ console.log('[PASS] getMatchById:', match.metadata.matchId);
26
+
27
+ const timeline = await riot.getMatchTimelineById(matchIds[0]);
28
+ console.log('[PASS] getMatchTimelineById:', timeline.metadata.matchId);
29
+
30
+ const allMatchIds = await riot.getMatchlistByPuuidAll(
31
+ account.puuid,
32
+ { start: 0 },
33
+ undefined,
34
+ { maxMatches: 2, delayMs: 0 }
35
+ );
36
+ console.log('[PASS] getMatchlistByPuuidAll:', allMatchIds.length, 'match ids');
37
+
38
+ const withDetails = await riot.getMatchesWithDetailsByPuuid(
39
+ account.puuid,
40
+ { start: 0 },
41
+ undefined,
42
+ { maxMatches: 2, pageDelayMs: 0, detailDelayMs: 0 }
43
+ );
44
+ console.log('[PASS] getMatchesWithDetailsByPuuid:', withDetails.matches.length, 'matches');
45
+ } else {
46
+ console.log('[SKIP] Match-by-id/timeline/all/details checks: no matches returned');
47
+ }
48
+ } else if (process.env.RIOT_API_KEY) {
49
+ console.log('[SKIP] Riot endpoints: set TEST_RIOT_ID (and TEST_TAG_LINE if needed)');
50
+ } else {
51
+ console.log('[SKIP] Riot endpoints: set RIOT_API_KEY and TEST_RIOT_ID');
52
+ }
53
+
54
+ const champions = await dd.getChampions();
55
+ console.log('[PASS] getChampions:', Object.keys(champions.data || {}).length, 'champions');
56
+ console.log('[INFO] DataDragon version:', dd.version);
57
+
58
+ const items = await dd.getItems();
59
+ console.log('[PASS] getItems:', Object.keys(items.data || {}).length, 'items');
60
+ }
61
+
62
+ run().catch((error) => {
63
+ console.error('[FAIL]', error.message);
64
+ process.exitCode = 1;
65
+ });