@eve-online-tools/eve-resfile 0.0.1

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,64 @@
1
+ import {
2
+ isVirtualResId,
3
+ loadResfileAssetModule,
4
+ resPathFromVirtualId,
5
+ resolveEveResfileOptions,
6
+ resolveResfileId
7
+ } from "../chunk-IX5WVBZL.js";
8
+ import {
9
+ __async,
10
+ loadResfileIndexData
11
+ } from "../chunk-RP7V25VC.js";
12
+
13
+ // src/rollup/plugin.ts
14
+ var eveResfile = (options = {}) => {
15
+ var _a;
16
+ const userOptions = resolveEveResfileOptions(options, (_a = options.root) != null ? _a : process.cwd());
17
+ const indexOptions = userOptions;
18
+ let indexPromise = null;
19
+ let loadedIndex = null;
20
+ const ensureIndex = () => {
21
+ indexPromise != null ? indexPromise : indexPromise = loadResfileIndexData(indexOptions).then((index) => {
22
+ loadedIndex = index;
23
+ return index;
24
+ });
25
+ return indexPromise;
26
+ };
27
+ return {
28
+ name: "eve-resfile-rollup",
29
+ buildStart() {
30
+ return __async(this, null, function* () {
31
+ const index = yield ensureIndex();
32
+ this.info(
33
+ `Loaded EVE resfileindex (build ${index.buildNumber}, ${index.resPathToCdnPath.size} entries).`
34
+ );
35
+ });
36
+ },
37
+ resolveId(source) {
38
+ return resolveResfileId(source);
39
+ },
40
+ load(id) {
41
+ return __async(this, null, function* () {
42
+ if (!isVirtualResId(id)) {
43
+ return null;
44
+ }
45
+ const resPath = resPathFromVirtualId(id);
46
+ const index = loadedIndex != null ? loadedIndex : yield ensureIndex();
47
+ return loadResfileAssetModule({
48
+ watchMode: this.meta.watchMode,
49
+ assetOrigin: userOptions.assetOrigin,
50
+ index,
51
+ resPath,
52
+ emitAsset: (name, source) => this.emitFile({
53
+ type: "asset",
54
+ name,
55
+ source
56
+ })
57
+ });
58
+ });
59
+ }
60
+ };
61
+ };
62
+ export {
63
+ eveResfile
64
+ };
@@ -0,0 +1,28 @@
1
+ type EveResfileOptions = {
2
+ /** Pin to a specific client build; omit to fetch latest from eveclient_TQ.json */
3
+ buildNumber?: number | string;
4
+ /** Override index CDN (default: https://binaries.eveonline.com) */
5
+ indexOrigin?: string;
6
+ /** Override asset CDN (default: https://resources.eveonline.com) */
7
+ assetOrigin?: string;
8
+ /** Cache dir relative to project root, or an absolute path (default: .cache/eve-resfile) */
9
+ cacheDir?: string;
10
+ /** Project root for resolving cacheDir (Rollup, or when not using the Vite plugin) */
11
+ root?: string;
12
+ };
13
+ type ResolvedEveResfileOptions = {
14
+ buildNumber?: number | string;
15
+ indexOrigin: string;
16
+ assetOrigin: string;
17
+ /** Absolute cache directory path */
18
+ cacheDir: string;
19
+ };
20
+ type ResfileIndex = {
21
+ buildNumber: string;
22
+ resPathToCdnPath: Map<string, string>;
23
+ };
24
+ type EveClientManifest = {
25
+ buildNumber: string;
26
+ };
27
+
28
+ export type { EveClientManifest as E, ResolvedEveResfileOptions as R, ResfileIndex as a, EveResfileOptions as b };
@@ -0,0 +1,28 @@
1
+ type EveResfileOptions = {
2
+ /** Pin to a specific client build; omit to fetch latest from eveclient_TQ.json */
3
+ buildNumber?: number | string;
4
+ /** Override index CDN (default: https://binaries.eveonline.com) */
5
+ indexOrigin?: string;
6
+ /** Override asset CDN (default: https://resources.eveonline.com) */
7
+ assetOrigin?: string;
8
+ /** Cache dir relative to project root, or an absolute path (default: .cache/eve-resfile) */
9
+ cacheDir?: string;
10
+ /** Project root for resolving cacheDir (Rollup, or when not using the Vite plugin) */
11
+ root?: string;
12
+ };
13
+ type ResolvedEveResfileOptions = {
14
+ buildNumber?: number | string;
15
+ indexOrigin: string;
16
+ assetOrigin: string;
17
+ /** Absolute cache directory path */
18
+ cacheDir: string;
19
+ };
20
+ type ResfileIndex = {
21
+ buildNumber: string;
22
+ resPathToCdnPath: Map<string, string>;
23
+ };
24
+ type EveClientManifest = {
25
+ buildNumber: string;
26
+ };
27
+
28
+ export type { EveClientManifest as E, ResolvedEveResfileOptions as R, ResfileIndex as a, EveResfileOptions as b };
@@ -0,0 +1,4 @@
1
+ declare module "res:/*" {
2
+ const src: string;
3
+ export default src;
4
+ }
@@ -0,0 +1,416 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var __async = (__this, __arguments, generator) => {
20
+ return new Promise((resolve2, reject) => {
21
+ var fulfilled = (value) => {
22
+ try {
23
+ step(generator.next(value));
24
+ } catch (e) {
25
+ reject(e);
26
+ }
27
+ };
28
+ var rejected = (value) => {
29
+ try {
30
+ step(generator.throw(value));
31
+ } catch (e) {
32
+ reject(e);
33
+ }
34
+ };
35
+ var step = (x) => x.done ? resolve2(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
36
+ step((generator = generator.apply(__this, __arguments)).next());
37
+ });
38
+ };
39
+
40
+ // src/vite/index.ts
41
+ var vite_exports = {};
42
+ __export(vite_exports, {
43
+ eveResfile: () => eveResfile
44
+ });
45
+ module.exports = __toCommonJS(vite_exports);
46
+
47
+ // src/content-type.ts
48
+ var import_node_path = require("path");
49
+ var CONTENT_TYPES = {
50
+ ".png": "image/png",
51
+ ".jpg": "image/jpeg",
52
+ ".jpeg": "image/jpeg",
53
+ ".gif": "image/gif",
54
+ ".webp": "image/webp",
55
+ ".svg": "image/svg+xml",
56
+ ".ico": "image/x-icon",
57
+ ".dds": "image/vnd-ms.dds",
58
+ ".mp3": "audio/mpeg",
59
+ ".ogg": "audio/ogg",
60
+ ".wav": "audio/wav",
61
+ ".webm": "video/webm",
62
+ ".mp4": "video/mp4",
63
+ ".woff": "font/woff",
64
+ ".woff2": "font/woff2",
65
+ ".ttf": "font/ttf",
66
+ ".json": "application/json",
67
+ ".txt": "text/plain"
68
+ };
69
+ var contentTypeForPath = (resPath) => {
70
+ var _a;
71
+ return (_a = CONTENT_TYPES[(0, import_node_path.extname)(resPath).toLowerCase()]) != null ? _a : "application/octet-stream";
72
+ };
73
+
74
+ // src/constants.ts
75
+ var DEFAULT_INDEX_ORIGIN = "https://binaries.eveonline.com";
76
+ var DEFAULT_ASSET_ORIGIN = "https://resources.eveonline.com";
77
+ var DEFAULT_CACHE_DIR = ".cache/eve-resfile";
78
+ var EVE_CLIENT_MANIFEST = "eveclient_TQ.json";
79
+ var RESFILE_INDEX_PATH = "app:/resfileindex.txt";
80
+ var VIRTUAL_PREFIX = "\0res:";
81
+ var DEV_PROXY_PREFIX = "/__eve_res__/";
82
+ var RES_IMPORT_PREFIX = "res:/";
83
+
84
+ // src/fetch.ts
85
+ var fetchText = (url) => __async(null, null, function* () {
86
+ const response = yield fetch(url);
87
+ if (!response.ok) {
88
+ throw new Error(`Failed to fetch ${url} (${response.status}).`);
89
+ }
90
+ return response.text();
91
+ });
92
+ var fetchBuffer = (url) => __async(null, null, function* () {
93
+ const response = yield fetch(url);
94
+ if (!response.ok) {
95
+ throw new Error(`Failed to fetch ${url} (${response.status}).`);
96
+ }
97
+ const arrayBuffer = yield response.arrayBuffer();
98
+ return Buffer.from(arrayBuffer);
99
+ });
100
+
101
+ // src/index-loader.ts
102
+ var import_promises2 = require("fs/promises");
103
+
104
+ // src/cache.ts
105
+ var import_node_fs = require("fs");
106
+ var import_promises = require("fs/promises");
107
+ var import_node_path2 = require("path");
108
+ var buildCacheDir = (cacheDir, buildNumber) => (0, import_node_path2.join)(cacheDir, buildNumber);
109
+ var buildIndexPath = (cacheDir, buildNumber) => (0, import_node_path2.join)(buildCacheDir(cacheDir, buildNumber), "build-index.txt");
110
+ var resfileIndexPath = (cacheDir, buildNumber) => (0, import_node_path2.join)(buildCacheDir(cacheDir, buildNumber), "resfileindex.txt");
111
+ var resfileMapPath = (cacheDir, buildNumber) => (0, import_node_path2.join)(buildCacheDir(cacheDir, buildNumber), "resfile-map.json");
112
+ var readCachedText = (path) => __async(null, null, function* () {
113
+ if (!(0, import_node_fs.existsSync)(path)) {
114
+ return null;
115
+ }
116
+ return (0, import_promises.readFile)(path, "utf8");
117
+ });
118
+ var writeCachedText = (path, content) => __async(null, null, function* () {
119
+ yield (0, import_promises.mkdir)((0, import_node_path2.join)(path, ".."), { recursive: true });
120
+ yield (0, import_promises.writeFile)(path, content);
121
+ });
122
+ var readCachedMap = (path) => __async(null, null, function* () {
123
+ if (!(0, import_node_fs.existsSync)(path)) {
124
+ return null;
125
+ }
126
+ const parsed = JSON.parse(yield (0, import_promises.readFile)(path, "utf8"));
127
+ return new Map(Object.entries(parsed));
128
+ });
129
+ var writeCachedMap = (path, map) => __async(null, null, function* () {
130
+ yield (0, import_promises.mkdir)((0, import_node_path2.join)(path, ".."), { recursive: true });
131
+ yield (0, import_promises.writeFile)(path, JSON.stringify(Object.fromEntries(map)));
132
+ });
133
+
134
+ // src/parse.ts
135
+ var parseFirstTwoColumns = (line) => {
136
+ const commaIndex = line.indexOf(",");
137
+ if (commaIndex === -1) {
138
+ return null;
139
+ }
140
+ const path = line.slice(0, commaIndex);
141
+ const remainder = line.slice(commaIndex + 1);
142
+ const secondComma = remainder.indexOf(",");
143
+ const cdnPath = secondComma === -1 ? remainder : remainder.slice(0, secondComma);
144
+ if (!path || !cdnPath) {
145
+ return null;
146
+ }
147
+ return [path, cdnPath];
148
+ };
149
+ var parseResfileIndex = (content) => {
150
+ const map = /* @__PURE__ */ new Map();
151
+ for (const line of content.split("\n")) {
152
+ const trimmed = line.trim();
153
+ if (!trimmed) {
154
+ continue;
155
+ }
156
+ const parsed = parseFirstTwoColumns(trimmed);
157
+ if (!parsed) {
158
+ continue;
159
+ }
160
+ const [resPath, cdnPath] = parsed;
161
+ map.set(resPath, cdnPath);
162
+ }
163
+ return map;
164
+ };
165
+ var findResfileIndexCdnPath = (buildIndex) => {
166
+ for (const line of buildIndex.split("\n")) {
167
+ const trimmed = line.trim();
168
+ if (!trimmed) {
169
+ continue;
170
+ }
171
+ const parsed = parseFirstTwoColumns(trimmed);
172
+ if (!parsed) {
173
+ continue;
174
+ }
175
+ const [path, cdnPath] = parsed;
176
+ if (path === RESFILE_INDEX_PATH) {
177
+ return cdnPath;
178
+ }
179
+ }
180
+ throw new Error(`${RESFILE_INDEX_PATH} not found in build index.`);
181
+ };
182
+
183
+ // src/index-loader.ts
184
+ var resolveBuildNumber = (options) => __async(null, null, function* () {
185
+ if (options.buildNumber !== void 0) {
186
+ return String(options.buildNumber);
187
+ }
188
+ const manifestUrl = `${options.indexOrigin}/${EVE_CLIENT_MANIFEST}`;
189
+ const manifest = JSON.parse(yield fetchText(manifestUrl));
190
+ if (!manifest.buildNumber) {
191
+ throw new Error(`Missing buildNumber in ${manifestUrl}.`);
192
+ }
193
+ return manifest.buildNumber;
194
+ });
195
+ var loadBuildIndex = (options, buildNumber) => __async(null, null, function* () {
196
+ const cachedPath = buildIndexPath(options.cacheDir, buildNumber);
197
+ const cached = yield readCachedText(cachedPath);
198
+ if (cached) {
199
+ return cached;
200
+ }
201
+ const buildIndexUrl = `${options.indexOrigin}/eveonline_${buildNumber}.txt`;
202
+ const content = yield fetchText(buildIndexUrl);
203
+ yield writeCachedText(cachedPath, content);
204
+ return content;
205
+ });
206
+ var loadResfileIndex = (options, buildNumber, cdnPath) => __async(null, null, function* () {
207
+ const cachedPath = resfileIndexPath(options.cacheDir, buildNumber);
208
+ const cached = yield readCachedText(cachedPath);
209
+ if (cached) {
210
+ return cached;
211
+ }
212
+ const resfileIndexUrl = `${options.indexOrigin}/${cdnPath}`;
213
+ const content = yield fetchText(resfileIndexUrl);
214
+ yield writeCachedText(cachedPath, content);
215
+ return content;
216
+ });
217
+ var loadResfileIndexData = (options) => __async(null, null, function* () {
218
+ const { cacheDir } = options;
219
+ const buildNumber = yield resolveBuildNumber(options);
220
+ yield (0, import_promises2.mkdir)(buildCacheDir(cacheDir, buildNumber), { recursive: true });
221
+ const cachedMap = yield readCachedMap(resfileMapPath(cacheDir, buildNumber));
222
+ if (cachedMap) {
223
+ return { buildNumber, resPathToCdnPath: cachedMap };
224
+ }
225
+ const buildIndex = yield loadBuildIndex(options, buildNumber);
226
+ const resfileIndexCdnPath = findResfileIndexCdnPath(buildIndex);
227
+ const resfileIndex = yield loadResfileIndex(options, buildNumber, resfileIndexCdnPath);
228
+ const resPathToCdnPath = parseResfileIndex(resfileIndex);
229
+ yield writeCachedMap(resfileMapPath(cacheDir, buildNumber), resPathToCdnPath);
230
+ return { buildNumber, resPathToCdnPath };
231
+ });
232
+
233
+ // src/lookup.ts
234
+ var normalizeResPath = (source) => {
235
+ var _a;
236
+ const withoutQuery = (_a = source.split(/[?#]/)[0]) != null ? _a : source;
237
+ if (withoutQuery.startsWith(RES_IMPORT_PREFIX)) {
238
+ return withoutQuery;
239
+ }
240
+ if (withoutQuery.startsWith("res:")) {
241
+ const path = withoutQuery.slice("res:".length);
242
+ return path.startsWith("/") ? `res:${path}` : `res:/${path}`;
243
+ }
244
+ throw new Error(`Invalid res import "${source}". Expected a path starting with "res:/".`);
245
+ };
246
+ var lookupCdnPath = (index, resPath) => index.get(resPath);
247
+ var devProxyUrl = (resPath) => {
248
+ const encoded = encodeURIComponent(resPath.slice(RES_IMPORT_PREFIX.length));
249
+ return `/__eve_res__/${encoded}`;
250
+ };
251
+ var resPathFromDevProxyUrl = (pathname) => {
252
+ if (!pathname.startsWith("/__eve_res__/")) {
253
+ return null;
254
+ }
255
+ const encoded = pathname.slice("/__eve_res__/".length);
256
+ if (!encoded) {
257
+ return null;
258
+ }
259
+ return `${RES_IMPORT_PREFIX}${decodeURIComponent(encoded)}`;
260
+ };
261
+
262
+ // src/plugin-core.ts
263
+ var import_node_path3 = require("path");
264
+ var resolveEveResfileOptions = (options = {}, root) => {
265
+ var _a, _b, _c;
266
+ const cacheDir = (_a = options.cacheDir) != null ? _a : DEFAULT_CACHE_DIR;
267
+ return {
268
+ buildNumber: options.buildNumber,
269
+ indexOrigin: (_b = options.indexOrigin) != null ? _b : DEFAULT_INDEX_ORIGIN,
270
+ assetOrigin: (_c = options.assetOrigin) != null ? _c : DEFAULT_ASSET_ORIGIN,
271
+ cacheDir: (0, import_node_path3.isAbsolute)(cacheDir) ? cacheDir : (0, import_node_path3.resolve)(root, cacheDir)
272
+ };
273
+ };
274
+ var isVirtualResId = (id) => id.startsWith(VIRTUAL_PREFIX);
275
+ var virtualIdForResPath = (resPath) => `${VIRTUAL_PREFIX}${resPath}`;
276
+ var resPathFromVirtualId = (id) => id.slice(VIRTUAL_PREFIX.length);
277
+ var lookupOrThrow = (index, resPath) => {
278
+ const cdnPath = lookupCdnPath(index.resPathToCdnPath, resPath);
279
+ if (!cdnPath) {
280
+ throw new Error(`${resPath} not found in resfileindex (build ${index.buildNumber}).`);
281
+ }
282
+ return cdnPath;
283
+ };
284
+ var resolveResfileId = (source) => {
285
+ if (!source.startsWith(RES_IMPORT_PREFIX)) {
286
+ return null;
287
+ }
288
+ return virtualIdForResPath(normalizeResPath(source));
289
+ };
290
+ var loadResfileAssetModule = (_0) => __async(null, [_0], function* ({
291
+ watchMode,
292
+ assetOrigin,
293
+ index,
294
+ resPath,
295
+ emitAsset
296
+ }) {
297
+ const cdnPath = lookupOrThrow(index, resPath);
298
+ if (watchMode) {
299
+ return `export default ${JSON.stringify(devProxyUrl(resPath))}`;
300
+ }
301
+ const buffer = yield fetchBuffer(`${assetOrigin}/${cdnPath}`);
302
+ const referenceId = emitAsset((0, import_node_path3.basename)(resPath), buffer);
303
+ return `export default import.meta.ROLLUP_FILE_URL_${referenceId}`;
304
+ });
305
+
306
+ // src/vite/plugin.ts
307
+ var createDevProxyMiddleware = (ensureIndex, assetOrigin) => {
308
+ return (req, res, next) => __async(null, null, function* () {
309
+ if (!req.url || req.method !== "GET") {
310
+ next();
311
+ return;
312
+ }
313
+ const pathname = new URL(req.url, "http://localhost").pathname;
314
+ if (!pathname.startsWith(DEV_PROXY_PREFIX)) {
315
+ next();
316
+ return;
317
+ }
318
+ const resPath = resPathFromDevProxyUrl(pathname);
319
+ if (!resPath) {
320
+ res.statusCode = 400;
321
+ res.end("Invalid resfile proxy path.");
322
+ return;
323
+ }
324
+ let cdnPath;
325
+ try {
326
+ const index = yield ensureIndex();
327
+ cdnPath = lookupOrThrow(index, resPath);
328
+ } catch (error) {
329
+ res.statusCode = 404;
330
+ res.end(error instanceof Error ? error.message : "Resfile not found.");
331
+ return;
332
+ }
333
+ try {
334
+ const buffer = yield fetchBuffer(`${assetOrigin}/${cdnPath}`);
335
+ res.statusCode = 200;
336
+ res.setHeader("Content-Type", contentTypeForPath(resPath));
337
+ res.setHeader("Cache-Control", "public, max-age=86400");
338
+ res.end(buffer);
339
+ } catch (error) {
340
+ res.statusCode = 502;
341
+ res.end(error instanceof Error ? error.message : "Failed to fetch resfile from CDN.");
342
+ }
343
+ });
344
+ };
345
+ var eveResfile = (options = {}) => {
346
+ let resolvedOptions = null;
347
+ let indexPromise = null;
348
+ let loadedIndex = null;
349
+ const ensureIndex = () => {
350
+ if (!resolvedOptions) {
351
+ throw new Error("@eve-online-tools/eve-resfile/vite: configResolved has not run yet.");
352
+ }
353
+ indexPromise != null ? indexPromise : indexPromise = loadResfileIndexData(resolvedOptions).then((index) => {
354
+ loadedIndex = index;
355
+ return index;
356
+ });
357
+ return indexPromise;
358
+ };
359
+ return {
360
+ name: "eve-resfile-vite",
361
+ enforce: "pre",
362
+ configResolved(config) {
363
+ resolvedOptions = resolveEveResfileOptions(options, config.root);
364
+ },
365
+ buildStart() {
366
+ return __async(this, null, function* () {
367
+ const index = yield ensureIndex();
368
+ this.info(
369
+ `Loaded EVE resfileindex (build ${index.buildNumber}, ${index.resPathToCdnPath.size} entries).`
370
+ );
371
+ });
372
+ },
373
+ configureServer: {
374
+ order: "pre",
375
+ handler(server) {
376
+ var _a2;
377
+ server.middlewares.use(
378
+ createDevProxyMiddleware(
379
+ ensureIndex,
380
+ (_a2 = resolvedOptions == null ? void 0 : resolvedOptions.assetOrigin) != null ? _a2 : DEFAULT_ASSET_ORIGIN
381
+ )
382
+ );
383
+ }
384
+ },
385
+ resolveId(source) {
386
+ return resolveResfileId(source);
387
+ },
388
+ load(id) {
389
+ return __async(this, null, function* () {
390
+ if (!isVirtualResId(id)) {
391
+ return null;
392
+ }
393
+ if (!resolvedOptions) {
394
+ throw new Error("@eve-online-tools/eve-resfile/vite: configResolved has not run yet.");
395
+ }
396
+ const resPath = resPathFromVirtualId(id);
397
+ const index = loadedIndex != null ? loadedIndex : yield ensureIndex();
398
+ return loadResfileAssetModule({
399
+ watchMode: this.meta.watchMode,
400
+ assetOrigin: resolvedOptions.assetOrigin,
401
+ index,
402
+ resPath,
403
+ emitAsset: (name, source) => this.emitFile({
404
+ type: "asset",
405
+ name,
406
+ source
407
+ })
408
+ });
409
+ });
410
+ }
411
+ };
412
+ };
413
+ // Annotate the CommonJS export names for ESM import in node:
414
+ 0 && (module.exports = {
415
+ eveResfile
416
+ });
@@ -0,0 +1,6 @@
1
+ import { Plugin } from 'vite';
2
+ import { b as EveResfileOptions } from '../types-DUuyxUdh.cjs';
3
+
4
+ declare const eveResfile: (options?: EveResfileOptions) => Plugin;
5
+
6
+ export { EveResfileOptions, eveResfile };
@@ -0,0 +1,6 @@
1
+ import { Plugin } from 'vite';
2
+ import { b as EveResfileOptions } from '../types-DUuyxUdh.js';
3
+
4
+ declare const eveResfile: (options?: EveResfileOptions) => Plugin;
5
+
6
+ export { EveResfileOptions, eveResfile };