@wfcd/relics 2.0.30 → 2.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [2.0.32](https://github.com/WFCD/warframe-relic-data/compare/v2.0.31...v2.0.32) (2026-08-12)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **deps:** drop unnecessary prod dependencies ([#423](https://github.com/WFCD/warframe-relic-data/issues/423)) ([0ea53b5](https://github.com/WFCD/warframe-relic-data/commit/0ea53b58cccac70d959a7bf00bbe672327c07c8e))
7
+
8
+ ## [2.0.31](https://github.com/WFCD/warframe-relic-data/compare/v2.0.30...v2.0.31) (2026-08-12)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **#412:** uniqueName stamping on relic drops ([#422](https://github.com/WFCD/warframe-relic-data/issues/422)) ([df55cfd](https://github.com/WFCD/warframe-relic-data/commit/df55cfd58e29c3d0504b6201e5611439cfce7f3e)), closes [#412](https://github.com/WFCD/warframe-relic-data/issues/412)
14
+
1
15
  ## [2.0.30](https://github.com/WFCD/warframe-relic-data/compare/v2.0.29...v2.0.30) (2026-08-04)
2
16
 
3
17
 
package/dist/Build.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_VersionManager = require("./VersionManager-ZP_iui8x.cjs");
1
+ const require_VersionManager = require("./VersionManager-CcEflrXR.cjs");
2
2
  //#region src/Build.ts
3
3
  /**
4
4
  * Entrypoint for the build process.
package/dist/Build.d.cts CHANGED
@@ -1 +1 @@
1
- export { };
1
+ export {}
package/dist/Build.d.mts CHANGED
@@ -1 +1 @@
1
- export { };
1
+ export {}
package/dist/Build.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./rolldown-runtime-C0LytTxp.mjs";
2
- import { n as Generator, r as Logger_default, t as VersionManager } from "./VersionManager-W7wuuPML.mjs";
2
+ import { n as Generator, r as Logger_default, t as VersionManager } from "./VersionManager--c_teh5c.mjs";
3
3
  //#region src/Build.ts
4
4
  /**
5
5
  * Entrypoint for the build process.
@@ -1,68 +1,111 @@
1
1
  import "./rolldown-runtime-C0LytTxp.mjs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
- import fetch from "node-fetch";
5
4
  import * as console from "node:console";
6
5
  //#region src/Config.ts
7
6
  const Config = {
8
- warframeRelicDropUrl: "https://drops.warframestat.us/data/relics.json",
9
- warframeMarketItemUrl: "https://api.warframe.market/v2/items",
7
+ warframeItemByNameUrl: "https://api.warframestat.us/items/",
8
+ warframeItemsSparseUrl: "https://api.warframestat.us/items/?only=name,uniqueName",
10
9
  warframeItemsUrl: "https://api.warframestat.us/items/search/Relics?by=category",
10
+ warframeMarketItemUrl: "https://api.warframe.market/v2/items",
11
+ warframePatchlogsUrl: "https://raw.githubusercontent.com/WFCD/warframe-patchlogs/master/data/patchlogs.json",
11
12
  warframeRelicDropInfoUrl: "https://drops.warframestat.us/data/info.json",
12
- warframePatchlogsUrl: "https://raw.githubusercontent.com/WFCD/warframe-patchlogs/master/data/patchlogs.json"
13
+ warframeRelicDropUrl: "https://drops.warframestat.us/data/relics.json"
13
14
  };
14
15
  //#endregion
16
+ //#region src/itemUniqueName.ts
17
+ const QTY_PREFIX = /^\d+X\s+/i;
18
+ const BLUEPRINT_SUFFIX = / Blueprint$/i;
19
+ /**
20
+ * Index sparse warframestat items by lowercase name, preferring higher-ranked uniqueNames.
21
+ * @param {Array<WFCDSparseItem>} items Sparse item list from warframestat
22
+ * @returns {Map<string, string>} Lowercase name → uniqueName
23
+ */
24
+ function buildItemUniqueNameIndex(items) {
25
+ const index = /* @__PURE__ */ new Map();
26
+ items.forEach((item) => {
27
+ if (!(item === null || item === void 0 ? void 0 : item.name) || !(item === null || item === void 0 ? void 0 : item.uniqueName)) return;
28
+ const key = item.name.toLowerCase();
29
+ const existing = index.get(key);
30
+ if (!existing || uniqueNameRank(item.uniqueName) > uniqueNameRank(existing)) index.set(key, item.uniqueName);
31
+ });
32
+ return index;
33
+ }
34
+ /**
35
+ * Strip drop quantity prefixes (e.g. "1200X Kuva" → "Kuva").
36
+ * @param {string} itemName Raw drop item name
37
+ * @returns {string} Name without quantity prefix
38
+ */
39
+ function normalizeRewardName(itemName) {
40
+ return itemName.replace(QTY_PREFIX, "").trim();
41
+ }
42
+ /**
43
+ * Resolve reward uniqueName from WFM gameRef or sparse name index.
44
+ * @param {string} itemName Raw drop item name
45
+ * @param {string|undefined} gameRef WFM gameRef when matched
46
+ * @param {Map<string, string>} itemUniqueNames Sparse (+ Blueprint) index
47
+ * @returns {string} Lotus uniqueName or empty string
48
+ */
49
+ function resolveRewardUniqueName(itemName, gameRef, itemUniqueNames) {
50
+ if (gameRef) return gameRef;
51
+ return itemUniqueNames.get(normalizeRewardName(itemName).toLowerCase()) ?? "";
52
+ }
53
+ /**
54
+ * Prefer in-world item/recipe paths over StoreItems, enemies, and other duplicates.
55
+ * @param {string} uniqueName Lotus path
56
+ * @returns {number} Higher is preferred
57
+ */
58
+ function uniqueNameRank(uniqueName) {
59
+ if (uniqueName.includes("/Enemies/")) return 0;
60
+ if (uniqueName.startsWith("/Lotus/Types/Items/") || uniqueName.startsWith("/Lotus/Types/Recipes/")) return 3;
61
+ if (uniqueName.startsWith("/Lotus/Types/")) return 2;
62
+ if (uniqueName.startsWith("/Lotus/StoreItems/")) return 1;
63
+ return 0;
64
+ }
65
+ //#endregion
15
66
  //#region src/Logger.ts
16
67
  const fromString = (logLevelIsh) => {
17
68
  switch (logLevelIsh === null || logLevelIsh === void 0 ? void 0 : logLevelIsh.toLowerCase()) {
69
+ case "bad":
70
+ case "error": return 0;
71
+ case "debug": return 2;
18
72
  case "fatal": return -1;
19
- case "error":
20
- case "bad": return 0;
21
73
  case "info":
22
74
  case "log": return 1;
23
- case "debug": return 2;
24
75
  default: return -1;
25
76
  }
26
77
  };
27
78
  var Logger = class {
28
- logLevel = fromString(process.env.LOG_LEVEL || "fatal");
29
- log(message) {
30
- if (this.logLevel >= 1) console.log(message);
79
+ logLevel = fromString(process.env.LOG_LEVEL ?? "fatal");
80
+ debug(message) {
81
+ if (this.logLevel === 2) console.debug(message);
31
82
  }
32
83
  error(message) {
33
84
  if (this.logLevel >= 0) console.error(message);
34
85
  }
35
- debug(message) {
36
- if (this.logLevel === 2) console.debug(message);
37
- }
38
86
  fatal(message) {
39
87
  if (this.logLevel >= -1) {
40
88
  console.error(`FATAL: ${message}`);
41
89
  throw new Error(message);
42
90
  }
43
91
  }
92
+ log(message) {
93
+ if (this.logLevel >= 1) console.log(message);
94
+ }
44
95
  };
45
96
  var Logger_default = new Logger();
46
97
  //#endregion
47
98
  //#region src/Generator.ts
48
99
  var Generator = class {
100
+ /** Lowercase item name → uniqueName from sparse warframestat index (+ Blueprint follow-ups) */
101
+ itemUniqueNames;
102
+ relics;
49
103
  relicsRaw;
50
104
  wfcdItems;
51
105
  wfmItems;
52
- relics;
53
106
  constructor() {
54
107
  this.relics = [];
55
- }
56
- /**
57
- * Main function to fetch and generate the relic data
58
- * @returns {Promise<Array<TitaniaRelic>>} The Relics data array
59
- */
60
- async generate() {
61
- Logger_default.log("Starting Generation");
62
- await this.fetchRawData();
63
- this.filterWFCDRelics();
64
- this.generateTitaniaRelics();
65
- return this.relics;
108
+ this.itemUniqueNames = /* @__PURE__ */ new Map();
66
109
  }
67
110
  /**
68
111
  * Fetches all required data from WFCD and WFM.
@@ -87,6 +130,25 @@ var Generator = class {
87
130
  return;
88
131
  }
89
132
  this.wfcdItems = await wfcdItemRequest.json();
133
+ const sparseRequest = await fetch(Config.warframeItemsSparseUrl);
134
+ if (!sparseRequest.ok) {
135
+ Logger_default.error("Failed to fetch sparse items from WFCD!");
136
+ return;
137
+ }
138
+ const sparseItems = await sparseRequest.json();
139
+ this.indexSparseItems(sparseItems);
140
+ }
141
+ /**
142
+ * Main function to fetch and generate the relic data
143
+ * @returns {Promise<Array<TitaniaRelic>>} The Relics data array
144
+ */
145
+ async generate() {
146
+ Logger_default.log("Starting Generation");
147
+ await this.fetchRawData();
148
+ this.filterWFCDRelics();
149
+ await this.resolveSpecialRewardUniqueNames();
150
+ this.generateTitaniaRelics();
151
+ return this.relics;
90
152
  }
91
153
  /**
92
154
  * Generates the relic data
@@ -123,17 +185,46 @@ var Generator = class {
123
185
  }
124
186
  }
125
187
  /**
188
+ * Fetch a single warframestat item by name with components for Blueprint resolution.
189
+ * @param {string} name Item display name
190
+ * @returns {Promise<WFCDSparseItem|undefined>} Item or undefined on failure
191
+ */
192
+ async fetchItemByName(name) {
193
+ const url = `${Config.warframeItemByNameUrl}${encodeURIComponent(name)}?only=name,uniqueName,components`;
194
+ const response = await fetch(url);
195
+ if (!response.ok) {
196
+ Logger_default.debug(`Failed to fetch item by name: ${name} (${response.status})`);
197
+ return;
198
+ }
199
+ const data = await response.json();
200
+ if ("error" in data && data.error) {
201
+ Logger_default.debug(`No item result for: ${name}`);
202
+ return;
203
+ }
204
+ return data;
205
+ }
206
+ /**
207
+ * Filters WFCD's relic data to only include Intact variants, since we just need the base.
208
+ */
209
+ filterWFCDRelics() {
210
+ var _this$relicsRaw;
211
+ var _this$relicsRaw2;
212
+ var _this$relicsRaw3;
213
+ const before = ((_this$relicsRaw = this.relicsRaw) === null || _this$relicsRaw === void 0 ? void 0 : _this$relicsRaw.length) ?? 0;
214
+ this.relicsRaw = (_this$relicsRaw2 = this.relicsRaw) === null || _this$relicsRaw2 === void 0 ? void 0 : _this$relicsRaw2.filter((x) => x.state === "Intact");
215
+ Logger_default.log(`Filtered relics to intact variants. Before: ${before} After: ${((_this$relicsRaw3 = this.relicsRaw) === null || _this$relicsRaw3 === void 0 ? void 0 : _this$relicsRaw3.length) ?? 0}`);
216
+ }
217
+ /**
126
218
  * Generates a single relic from all available data
127
219
  * @param {WFCDRelic} rawRelic relic to pull Titania data from
128
220
  * @returns {TitaniaRelicReward}
129
221
  */
130
222
  generateTitaniaRelic(rawRelic) {
131
- var _this$wfcdItems2;
223
+ var _this$wfcdItems;
132
224
  var _this$wfmItems2;
133
225
  const name = `${rawRelic.tier} ${rawRelic.relicName}`;
134
226
  const rewards = rawRelic.rewards.map((rawReward) => {
135
227
  var _this$wfmItems;
136
- var _this$wfcdItems;
137
228
  const { chance } = rawReward;
138
229
  const { rarity } = rawReward;
139
230
  const wfmInfo = (_this$wfmItems = this.wfmItems) === null || _this$wfmItems === void 0 ? void 0 : _this$wfmItems.data.find((x) => {
@@ -148,7 +239,7 @@ var Generator = class {
148
239
  if (!(wfmInfo || isSpecial)) Logger_default.debug(`Failed to find wfm item for ${rawReward.itemName}`);
149
240
  const item = {
150
241
  name: rawReward.itemName,
151
- uniqueName: ((_this$wfcdItems = this.wfcdItems) === null || _this$wfcdItems === void 0 || (_this$wfcdItems = _this$wfcdItems.find((x) => x.name.toLowerCase() === `${name.trim()} Intact`.toLowerCase())) === null || _this$wfcdItems === void 0 ? void 0 : _this$wfcdItems.uniqueName) || "",
242
+ uniqueName: resolveRewardUniqueName(rawReward.itemName, wfmInfo === null || wfmInfo === void 0 ? void 0 : wfmInfo.gameRef, this.itemUniqueNames),
152
243
  warframeMarket: void 0
153
244
  };
154
245
  if (wfmInfo) item.warframeMarket = {
@@ -156,19 +247,19 @@ var Generator = class {
156
247
  urlName: wfmInfo.slug
157
248
  };
158
249
  return {
159
- rarity,
160
250
  chance,
161
- item
251
+ item,
252
+ rarity
162
253
  };
163
254
  });
164
255
  let drops = [];
165
- const wfcdItem = (_this$wfcdItems2 = this.wfcdItems) === null || _this$wfcdItems2 === void 0 ? void 0 : _this$wfcdItems2.find((x) => x.name.toLowerCase() === `${name.trim()} Intact`.toLowerCase());
256
+ const wfcdItem = (_this$wfcdItems = this.wfcdItems) === null || _this$wfcdItems === void 0 ? void 0 : _this$wfcdItems.find((x) => x.name.toLowerCase() === `${name.trim()} Intact`.toLowerCase());
166
257
  if (!wfcdItem) Logger_default.error(`Failed to get WFCD item for relic: ${name}`);
167
- if (wfcdItem && wfcdItem.drops) drops = wfcdItem.drops.map((rawDrop) => {
258
+ if (wfcdItem === null || wfcdItem === void 0 ? void 0 : wfcdItem.drops) drops = wfcdItem.drops.map((rawDrop) => {
168
259
  return {
169
- rarity: rawDrop.rarity,
170
260
  chance: rawDrop.chance,
171
- location: rawDrop.location
261
+ location: rawDrop.location,
262
+ rarity: rawDrop.rarity
172
263
  };
173
264
  });
174
265
  const wfm = (_this$wfmItems2 = this.wfmItems) === null || _this$wfmItems2 === void 0 ? void 0 : _this$wfmItems2.data.find((x) => {
@@ -176,35 +267,71 @@ var Generator = class {
176
267
  });
177
268
  if (!wfm) Logger_default.error(`Failed to get relic item from wfm: ${name}`);
178
269
  return {
270
+ locations: drops,
179
271
  name,
180
272
  rewards,
181
- locations: drops,
182
- uniqueName: (wfcdItem === null || wfcdItem === void 0 ? void 0 : wfcdItem.uniqueName) || "",
273
+ uniqueName: (wfcdItem === null || wfcdItem === void 0 ? void 0 : wfcdItem.uniqueName) ?? "",
183
274
  vaultInfo: { vaulted: drops.length === 0 },
184
- ...(wfm === null || wfm === void 0 ? void 0 : wfm.id) && (wfm === null || wfm === void 0 ? void 0 : wfm.slug) && { warframeMarket: {
185
- id: wfm === null || wfm === void 0 ? void 0 : wfm.id,
186
- urlName: wfm === null || wfm === void 0 ? void 0 : wfm.slug
275
+ ...(wfm === null || wfm === void 0 ? void 0 : wfm.id) && wfm.slug && { warframeMarket: {
276
+ id: wfm.id,
277
+ urlName: wfm.slug
187
278
  } }
188
279
  };
189
280
  }
190
281
  /**
191
- * Filters WFCD's relic data to only include Intact variants, since we just need the base.
282
+ * Index sparse warframestat items by lowercase name, preferring /Lotus/Types/ paths.
283
+ * @param {Array<WFCDSparseItem>} items Sparse item list from warframestat
192
284
  */
193
- filterWFCDRelics() {
194
- var _this$relicsRaw;
195
- var _this$relicsRaw2;
196
- var _this$relicsRaw3;
197
- const before = ((_this$relicsRaw = this.relicsRaw) === null || _this$relicsRaw === void 0 ? void 0 : _this$relicsRaw.length) || 0;
198
- this.relicsRaw = (_this$relicsRaw2 = this.relicsRaw) === null || _this$relicsRaw2 === void 0 ? void 0 : _this$relicsRaw2.filter((x) => x.state === "Intact");
199
- Logger_default.log(`Filtered relics to intact variants. Before: ${before} After: ${((_this$relicsRaw3 = this.relicsRaw) === null || _this$relicsRaw3 === void 0 ? void 0 : _this$relicsRaw3.length) || 0}`);
285
+ indexSparseItems(items) {
286
+ this.itemUniqueNames = buildItemUniqueNameIndex(items);
287
+ Logger_default.debug(`Indexed ${this.itemUniqueNames.size} sparse item uniqueNames`);
288
+ }
289
+ /**
290
+ * For reward names missing WFM, resolve uniqueNames from sparse index.
291
+ * Blueprint-suffixed names need a parent item fetch for the Blueprint component.
292
+ */
293
+ async resolveSpecialRewardUniqueNames() {
294
+ if (!this.relicsRaw || !this.wfmItems) return;
295
+ const specialNames = /* @__PURE__ */ new Set();
296
+ this.relicsRaw.forEach((relic) => {
297
+ relic.rewards.forEach((reward) => {
298
+ var _this$wfmItems3;
299
+ if (!((_this$wfmItems3 = this.wfmItems) === null || _this$wfmItems3 === void 0 ? void 0 : _this$wfmItems3.data.some((x) => {
300
+ return x.i18n.en.name.toLowerCase() === reward.itemName.toLowerCase();
301
+ }))) specialNames.add(normalizeRewardName(reward.itemName));
302
+ });
303
+ });
304
+ const blueprintParents = /* @__PURE__ */ new Map();
305
+ Array.from(specialNames).forEach((name) => {
306
+ const key = name.toLowerCase();
307
+ if (this.itemUniqueNames.has(key)) return;
308
+ if (!BLUEPRINT_SUFFIX.test(name)) {
309
+ Logger_default.debug(`No uniqueName for special reward: ${name}`);
310
+ return;
311
+ }
312
+ const parent = name.replace(BLUEPRINT_SUFFIX, "").trim();
313
+ if (parent) blueprintParents.set(name, parent);
314
+ });
315
+ const parentCache = /* @__PURE__ */ new Map();
316
+ for (const [blueprintName, parent] of blueprintParents.entries()) {
317
+ var _parentItem$component;
318
+ let parentItem = parentCache.get(parent);
319
+ if (!parentCache.has(parent)) {
320
+ parentItem = await this.fetchItemByName(parent);
321
+ parentCache.set(parent, parentItem);
322
+ }
323
+ const blueprint = parentItem === null || parentItem === void 0 || (_parentItem$component = parentItem.components) === null || _parentItem$component === void 0 ? void 0 : _parentItem$component.find((c) => c.name.toLowerCase() === "blueprint");
324
+ if (blueprint === null || blueprint === void 0 ? void 0 : blueprint.uniqueName) this.itemUniqueNames.set(blueprintName.toLowerCase(), blueprint.uniqueName);
325
+ else Logger_default.debug(`Failed to resolve Blueprint uniqueName for: ${blueprintName}`);
326
+ }
200
327
  }
201
328
  };
202
329
  //#endregion
203
330
  //#region src/VersionManager.ts
204
331
  var VersionManager = class {
332
+ hashPath;
205
333
  versionPath;
206
334
  versionRawPath;
207
- hashPath;
208
335
  /**
209
336
  * Creates a new VersionManager instance.
210
337
  * @param {string} dataDir Folder to write version information to.
@@ -226,7 +353,7 @@ var VersionManager = class {
226
353
  try {
227
354
  await fs.access(this.hashPath);
228
355
  return JSON.parse(await fs.readFile(this.hashPath, "utf-8")).hash !== info.hash;
229
- } catch (ex) {
356
+ } catch {
230
357
  return true;
231
358
  }
232
359
  }
@@ -249,12 +376,12 @@ var VersionManager = class {
249
376
  }
250
377
  const hashInfo = await hashReq.json();
251
378
  const versionInfo = {
252
- version,
253
- title: patchlogs[0].name
379
+ title: patchlogs[0].name,
380
+ version
254
381
  };
255
382
  const hashFile = {
256
- hash: hashInfo.hash,
257
383
  deUpdated: hashInfo.modified,
384
+ hash: hashInfo.hash,
258
385
  timestamp
259
386
  };
260
387
  await fs.writeFile(this.hashPath, JSON.stringify(hashFile, void 0, 2), "utf-8");