Replace deprecated and unmaintained packages

- Removed mkdirp in favor of native fs.mkdir({ recursive: true })
- Replaced ecstatic (unmaintained since 2021) with sirv
- Replaced jsonpath (security vulnerability) with jsonpath-plus
  - Added jsonpathQuery/jsonpathApply helpers in app/common/util.mjs
- Updated sharp: 0.32.6 → 0.34.5
- Updated puppeteer-core: 23.8.0 → 24.37.3

Vulnerabilities reduced from 40 to 1 (only remaining: axios in
threads-api transitive dependency).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt Isenhower 2026-02-15 11:05:49 -08:00
parent e6004154b9
commit 8780463c66
11 changed files with 687 additions and 539 deletions

View File

@ -1,6 +1,5 @@
import fs from 'fs/promises';
import path from 'path';
import mkdirp from 'mkdirp';
export default class ValueCache
{
constructor(key) {
@ -48,7 +47,7 @@ export default class ValueCache
let cachedAt = new Date;
let serialized = JSON.stringify({ expires, data, cachedAt }, undefined, 2);
await mkdirp(path.dirname(this.path));
await fs.mkdir(path.dirname(this.path), { recursive: true });
await fs.writeFile(this.path, serialized);
}
}

View File

@ -1,4 +1,5 @@
import crypto from 'crypto';
import { JSONPath } from 'jsonpath-plus';
export function getTopOfCurrentHour(date = null) {
date ??= new Date;
@ -88,6 +89,16 @@ export function getXRankSeasonId(id) {
* @param {number} expiresIn - Seconds until expiry
* @returns {number} Timestamp to expire the cache (5 minutes early)
*/
export function jsonpathQuery(data, path) {
return JSONPath({ path, json: data });
}
export function jsonpathApply(data, path, fn) {
JSONPath({ path, json: data, resultType: 'all', callback: (result) => {
result.parent[result.parentProperty] = fn(result.value);
}});
}
export function calculateCacheExpiry(expiresIn) {
let expires = Date.now() + expiresIn * 1000;

View File

@ -1,6 +1,5 @@
import fs from 'fs/promises';
import path from 'path';
import mkdirp from 'mkdirp';
import PQueue from 'p-queue';
import prefixedConsole from '../common/prefixedConsole.mjs';
import { normalizeSplatnetResourcePath } from '../common/util.mjs';
@ -70,7 +69,7 @@ export default class ImageProcessor
throw new Error(`Invalid image response code: ${result.status}`);
}
await mkdirp(path.dirname(this.localPath(destination)));
await fs.mkdir(path.dirname(this.localPath(destination)), { recursive: true });
await fs.writeFile(this.localPath(destination), result.body);
} catch (e) {
this.console.error(`Image download failed for ${destination}`, e);

View File

@ -1,7 +1,6 @@
import fs from 'fs/promises';
import path from 'path';
import mkdirp from 'mkdirp';
import jsonpath from 'jsonpath';
import { jsonpathQuery } from '../common/util.mjs';
import get from 'lodash/get.js';
import set from 'lodash/set.js';
import pLimit from 'p-limit';
@ -41,7 +40,7 @@ export class LocalizationProcessor {
*dataIterations(data) {
for (let ruleset of this.rulesetIterations()) {
for (let node of jsonpath.query(data, ruleset.node)) {
for (let node of jsonpathQuery(data, ruleset.node)) {
if (!node) continue;
let id = get(node, ruleset.id);
@ -92,7 +91,7 @@ export class LocalizationProcessor {
data = JSON.stringify(data, undefined, space);
await mkdirp(path.dirname(this.filename));
await fs.mkdir(path.dirname(this.filename), { recursive: true });
await fs.writeFile(this.filename, data);
}

View File

@ -1,8 +1,6 @@
import fs from 'fs/promises';
import path from 'path';
import { Console } from 'node:console';
import mkdirp from 'mkdirp';
import jsonpath from 'jsonpath';
import ical from 'ical-generator';
import pFilter from 'p-filter';
import prefixedConsole from '../../common/prefixedConsole.mjs';
@ -11,7 +9,7 @@ import ImageProcessor from '../ImageProcessor.mjs';
import NsoClient from '../../splatnet/NsoClient.mjs';
import { locales, regionalLocales, defaultLocale } from '../../../src/common/i18n.mjs';
import { LocalizationProcessor } from '../LocalizationProcessor.mjs';
import { deriveId, getDateParts, getTopOfCurrentHour } from '../../common/util.mjs';
import { deriveId, getDateParts, getTopOfCurrentHour, jsonpathQuery, jsonpathApply } from '../../common/util.mjs';
export default class DataUpdater
{
name = null;
@ -120,7 +118,7 @@ export default class DataUpdater
deriveIds(data) {
for (let expression of this.derivedIds) {
jsonpath.apply(data, expression, deriveId);
jsonpathApply(data, expression, deriveId);
}
}
@ -157,14 +155,14 @@ export default class DataUpdater
// This JSONPath library is completely synchronous, so we have to
// build a mapping here after transforming all URLs.
let mapping = {};
for (let url of jsonpath.query(data, expression)) {
for (let url of jsonpathQuery(data, expression)) {
let [path, publicUrl] = await this.imageProcessor.process(url);
mapping[url] = publicUrl;
images[publicUrl] = path;
}
// Now apply the URL transformations
jsonpath.apply(data, expression, url => mapping[url]);
jsonpathApply(data, expression, url => mapping[url]);
}
await ImageProcessor.onIdle();
@ -217,7 +215,7 @@ export default class DataUpdater
}
async writeFile(file, data) {
await mkdirp(path.dirname(file));
await fs.mkdir(path.dirname(file), { recursive: true });
await fs.writeFile(file, data);
}

View File

@ -1,7 +1,6 @@
import fs from 'fs/promises';
import jsonpath from 'jsonpath';
import pLimit from 'p-limit';
import { getFestId } from '../../common/util.mjs';
import { getFestId, jsonpathQuery, jsonpathApply } from '../../common/util.mjs';
import ValueCache from '../../common/ValueCache.mjs';
import { regionTokens } from '../../splatnet/NsoClient.mjs';
import FestivalRankingUpdater from './FestivalRankingUpdater.mjs';
@ -70,7 +69,7 @@ export default class FestivalUpdater extends DataUpdater
let data = await this.splatnet(locale).getFestRecordDataPage(cursor);
// Grab the nodes from the current page
result.data.festRecords.nodes.push(...jsonpath.query(data, '$..festRecords.edges.*.node'));
result.data.festRecords.nodes.push(...jsonpathQuery(data, '$..festRecords.edges.*.node'));
// Update the cursor and next page indicator
cursor = data.data.festRecords.pageInfo.endCursor;
@ -103,7 +102,7 @@ export default class FestivalUpdater extends DataUpdater
}
deriveFestivalIds(data) {
jsonpath.apply(data, '$..nodes.*', node => ({
jsonpathApply(data, '$..nodes.*', node => ({
'__splatoon3ink_id': getFestId(node.id),
...node,
}));

View File

@ -1,5 +1,5 @@
import http from 'http';
import ecstatic from 'ecstatic';
import sirv from 'sirv';
export default class HttpServer
{
@ -16,7 +16,7 @@ export default class HttpServer
return resolve();
}
const handler = ecstatic({ root: './dist' });
const handler = sirv('./dist');
this.#server = http.createServer(handler);
this.#server.on('listening', () => resolve());
this.#server.listen();

View File

@ -1,5 +1,4 @@
import fs from 'fs/promises';
import mkdirp from 'mkdirp';
import Client from './Client.mjs';
export default class FileWriter extends Client {
@ -9,7 +8,7 @@ export default class FileWriter extends Client {
dir = 'temp';
async send(status, generator) {
await mkdirp(this.dir);
await fs.mkdir(this.dir, { recursive: true });
if (status.media?.length > 0) {
let imgFilename = `${this.dir}/${generator.key}.png`;

View File

@ -1,5 +1,4 @@
import fs from 'fs/promises';
import mkdirp from 'mkdirp';
import Client from './Client.mjs';
export default class ImageWriter extends Client {
@ -13,7 +12,7 @@ export default class ImageWriter extends Client {
return;
}
await mkdirp(this.dir);
await fs.mkdir(this.dir, { recursive: true });
let imgFilename = `${this.dir}/${generator.key}.png`;
await fs.writeFile(imgFilename, status.media[0].file);

1164
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -34,20 +34,19 @@
"console-stamp": "^3.0.6",
"cron": "^2.1.0",
"dotenv": "^16.0.2",
"ecstatic": "^4.1.4",
"ical-generator": "^3.6.0",
"jsonpath": "^1.1.1",
"jsonpath-plus": "^10.3.0",
"lodash": "^4.17.21",
"masto": "^7.10.1",
"mkdirp": "^1.0.4",
"nxapi": "^1.6.1-next.170",
"p-filter": "^4.1.0",
"p-limit": "^6.1.0",
"p-queue": "^8.0.1",
"pinia": "^2.3.1",
"puppeteer-core": "^23.8.0",
"puppeteer-core": "^24.37.3",
"s3-sync-client": "^4.3.1",
"sharp": "^0.32.0",
"sharp": "^0.34.5",
"sirv": "^3.0.2",
"threads-api": "^1.4.0",
"twitter-api-v2": "^1.29.0",
"vue": "^3.5.28",