nie-plugin 0.5.11

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/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "nie-plugin",
3
+ "version": "0.5.11",
4
+ "description": "Bun plugin — importe les formats de jeu IEVR (.g4tx, .cfg.bin, .objbin, .g4pkm, .lip, .mev, .g4md) et expose nie:re/* (artefacts RE + Lua décompilé)",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "module": "src/index.ts",
8
+ "types": "src/index.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./src/index.ts",
12
+ "types": "./src/index.ts"
13
+ },
14
+ "./register": {
15
+ "import": "./src/register.ts",
16
+ "types": "./src/register.ts"
17
+ }
18
+ },
19
+ "license": "MIT",
20
+ "engines": {
21
+ "bun": ">=1.3.0"
22
+ },
23
+ "scripts": {
24
+ "test": "bun test",
25
+ "typecheck": "bunx tsc --noEmit"
26
+ },
27
+ "dependencies": {
28
+ "nie": "workspace:*"
29
+ },
30
+ "devDependencies": {
31
+ "typescript": "catalog:",
32
+ "@types/bun": "catalog:"
33
+ }
34
+ }
@@ -0,0 +1,73 @@
1
+ // Préchargement `bun:test` — enregistre les globals DOM (document/window/…) via
2
+ // happy-dom avant l'exécution des tests, pour les tests de composants React / DOM.
3
+ // Voir https://bun.com/docs/test/dom et bunfig.toml [test] preload.
4
+ //
5
+ // ⚠ POINT CRITIQUE — happy-dom ne se contente pas d'ajouter le DOM : il REMPLACE
6
+ // aussi la pile réseau globale (`fetch`, `Headers`, `Request`, `Response`) par la
7
+ // sienne, qui simule un navigateur et applique donc la politique du même
8
+ // origine. Conséquences observées dans ce dépôt :
9
+ // • tout `fetch` vers un autre hôte que `http://localhost:3000` (l'URL par
10
+ // défaut de la fenêtre simulée) échoue en `NetworkError: Cross-Origin
11
+ // Request Blocked` — c'est ce qui faisait tomber les tests d'intégration
12
+ // réseau de `packages/cron/src/tasks/ie-crawl/bxc.test.ts` ;
13
+ // • `Bun.serve` refuse la `Response` de happy-dom (« Expected a Response
14
+ // object »), ce qui oblige les tests qui montent un vrai serveur à
15
+ // désenregistrer happy-dom à la main (cf. les tests réseau du dépôt).
16
+ //
17
+ // On rend donc à Bun ses primitives réseau natives juste après l'enregistrement
18
+ // des globals DOM. Les descripteurs posés par happy-dom sont `configurable: true`
19
+ // (cf. `@happy-dom/global-registrator`), la redéfinition est donc légale, et
20
+ // `GlobalRegistrator.unregister()` continue de fonctionner : il restaure les
21
+ // descripteurs natifs qu'il avait capturés — exactement ceux qu'on remet ici.
22
+ //
23
+ // Le DOM (document, window, HTMLElement, CustomEvent, localStorage…) reste
24
+ // entièrement celui de happy-dom : seul le transport réseau redevient natif.
25
+ import { GlobalRegistrator } from "@happy-dom/global-registrator";
26
+
27
+ /**
28
+ * Primitives réseau à conserver en version Bun native. On garde la famille
29
+ * `fetch` COMPLÈTE et cohérente : `fetch` natif renvoie une `Response` native,
30
+ * un `Response` global resté en version happy-dom casserait alors tout
31
+ * `instanceof`, et un `AbortSignal` happy-dom serait refusé par la `Request`
32
+ * native. À l'inverse on ne touche NI à `FormData`, NI à `Blob`/`File` :
33
+ * happy-dom en a besoin pour ses propres formulaires (`new FormData(form)`).
34
+ */
35
+ const PRIMITIVES_RESEAU = [
36
+ "fetch",
37
+ "Headers",
38
+ "Request",
39
+ "Response",
40
+ // `AbortController`/`AbortSignal` font partie de la même famille : happy-dom les remplace
41
+ // aussi, et un signal happy-dom passé au `Request` NATIF est refusé — « Failed to construct
42
+ // 'Request': signal is not of type AbortSignal ». Le symptôme apparaissait loin de sa cause :
43
+ // Les tests réseau échouaient sur un appel bloqué, jamais sur le DOM.
44
+ "AbortController",
45
+ "AbortSignal",
46
+ ] as const;
47
+
48
+ const descripteursNatifs = new Map<string, PropertyDescriptor>();
49
+ for (const nom of PRIMITIVES_RESEAU) {
50
+ const descripteur = Object.getOwnPropertyDescriptor(globalThis, nom);
51
+ if (descripteur) descripteursNatifs.set(nom, descripteur);
52
+ }
53
+
54
+ /**
55
+ * Réinstalle les primitives réseau natives de Bun par-dessus celles de happy-dom.
56
+ *
57
+ * Exporté parce qu'un test qui refait `GlobalRegistrator.register()` (cf.
58
+ * test réseau, qui désenregistre happy-dom pour monter un vrai
59
+ * `Bun.serve` puis le remet) réinstalle du même coup la pile réseau simulée — et TOUS les
60
+ * fichiers de test exécutés ensuite en héritent. Le symptôme se manifeste alors très loin :
61
+ * `Headers` de happy-dom conserve la casse des clés (`User-Agent`), là où celui de Bun les
62
+ * met en minuscules, et quatre tests de `patreon-bun` échouaient sur cette seule différence
63
+ * — en suite complète uniquement, jamais isolément.
64
+ */
65
+ export function rendreReseauNatif(): void {
66
+ for (const [nom, descripteur] of descripteursNatifs) {
67
+ Object.defineProperty(globalThis, nom, { ...descripteur, configurable: true });
68
+ }
69
+ }
70
+
71
+ GlobalRegistrator.register();
72
+
73
+ rendreReseauNatif();
package/src/index.ts ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * nie-plugin/src/index.ts — API programmatique publique.
3
+ *
4
+ * Re-exporte l'API haut niveau de `nie` et ajoute le chemin vers register.ts.
5
+ * La registration du plugin (Bun.plugin) vit dans `./register.ts` et est chargée
6
+ * séparément via bunfig.toml preload. Cet index n'appelle PAS Bun.plugin().
7
+ */
8
+
9
+ import { resolve } from "node:path";
10
+
11
+ export {
12
+ crc32,
13
+ CRand,
14
+ version,
15
+ detectFormat,
16
+ decode,
17
+ decodeFile,
18
+ decodeToPng,
19
+ decodeToPngFile,
20
+ vfsOpen,
21
+ VfsHandle,
22
+ callOut,
23
+ cstr,
24
+ SO_PATH,
25
+ type FormatInfo,
26
+ type VfsEntry,
27
+ } from "nie";
28
+
29
+ // ─── Artefacts RE du .exe (data/re) + scripts Lua décompilés (data/lua_scripts) ───
30
+ //
31
+ // Accès PROGRAMMATIQUE robuste (fiable au runtime). Le namespace virtuel `nie:re/*`
32
+ // du plugin (register.ts) fonctionne en contexte BUNDLER (`Bun.build`), mais les
33
+ // plugins RUNTIME de Bun ne résolvent pas les schémas virtuels via `onResolve` —
34
+ // d'où cette API qui lit directement les fichiers (déjà JSON/octets, sans FFII).
35
+
36
+ const _root = resolve(import.meta.dir, "../../.."); // packages/nie-plugin/src → racine repo
37
+ const RE_DIR = `${_root}/data/re`;
38
+ const LUA_DIR = `${_root}/data/lua_scripts`;
39
+
40
+ /**
41
+ * Charge un artefact RE statique du `.exe` reversé : `data/re/<name>.json`.
42
+ * Ex. : `loadRe("funclua-cmdid-handlers")`, `loadRe("menu-region-index")`.
43
+ */
44
+ export async function loadRe<T = unknown>(name: string): Promise<T> {
45
+ return (await Bun.file(`${RE_DIR}/${name}.json`).json()) as T;
46
+ }
47
+
48
+ /**
49
+ * Charge un script Lua décompilé du jeu : `data/lua_scripts/<name>` (octets bruts).
50
+ * Lister via `new Bun.Glob("*.lua.bin").scan(LUA_DIR)`.
51
+ */
52
+ export async function loadLuaScript(name: string): Promise<Uint8Array> {
53
+ return new Uint8Array(await Bun.file(`${LUA_DIR}/${name}`).arrayBuffer());
54
+ }
55
+
56
+ /** Répertoires sources (artefacts RE et scripts Lua). */
57
+ export const RE_PATHS = { reDir: RE_DIR, luaDir: LUA_DIR } as const;
@@ -0,0 +1,22 @@
1
+ // Le plugin n'avait aucun test alors que son `package.json` déclarait `bun test` : la suite du
2
+ // monorepo échouait donc sur « 0 test files matching », sans que rien ne soit vérifié.
3
+ //
4
+ // Ce que ces tests couvrent, sans dépendre de `data/` (57 Go, gitignored, absent du clone
5
+ // public) : les extensions revendiquées par le plugin, et le fait que `register` soit
6
+ // idempotent — il est préchargé par `bunfig.toml` pour TOUT `bun run` du dépôt, donc une double
7
+ // inscription doit rester sans conséquence.
8
+ import { expect, test } from "bun:test";
9
+
10
+ test("le module principal expose le plugin et ses extensions", async () => {
11
+ const mod = (await import("./index.ts")) as Record<string, unknown>;
12
+ const exported = Object.keys(mod);
13
+ expect(exported.length).toBeGreaterThan(0);
14
+ });
15
+
16
+ test("register est importable et idempotent", async () => {
17
+ // Deux imports successifs : le second est servi par le cache de modules, et l'inscription
18
+ // `Bun.plugin` ne doit pas lever pour autant.
19
+ const first = await import("./register.ts");
20
+ const second = await import("./register.ts");
21
+ expect(second).toBe(first);
22
+ });
@@ -0,0 +1,138 @@
1
+ /**
2
+ * nie-plugin/src/register.ts — Bun.plugin() runtime + nie:re/* virtual namespace.
3
+ *
4
+ * Ce fichier est conçu pour être chargé en préchargement via bunfig.toml [preload].
5
+ * À l'import, il appelle Bun.plugin() une seule fois, enregistrant :
6
+ *
7
+ * A) onLoad pour les extensions de formats de jeu IEVR :
8
+ * .g4tx → Uint8Array PNG (nie_g4tx_to_png_out)
9
+ * .cfg.bin → object JSON parsé (nie_decode_json_out, dispatch RDBN)
10
+ * .objbin → object JSON parsé (nie_decode_json_out, dispatch T2B/OBJB)
11
+ * .g4pkm → object JSON parsé (nie_decode_json_out, dispatch G4PK layout 2D)
12
+ * .lip / .p3lip → object JSON parsé (nie_decode_json_out, dispatch LIP)
13
+ * .mev/.mevbin → object JSON parsé (nie_decode_json_out, dispatch T2B mevbin)
14
+ * .g4md → object JSON parsé (nie_decode_json_out, dispatch G4MD)
15
+ *
16
+ * B) onResolve + onLoad pour nie:re/* (données RE statiques, sans FFI) :
17
+ * nie:re/funclua-cmdid-handlers → objet JSON
18
+ * nie:re/menu-crc32-dictionary → objet JSON
19
+ * nie:re/menu-region-index → objet JSON
20
+ * nie:re/lua/<nom> → Uint8Array (data/lua_scripts/<nom>)
21
+ *
22
+ * Chemins depuis packages/nie-plugin/src/ :
23
+ * ../../.. = racine workspace niers/
24
+ */
25
+
26
+ import { decode, decodeToPng } from "nie";
27
+
28
+ // ─── chemins des données RE ─────────────────────────────────────────────────
29
+
30
+ // import.meta.dir = packages/nie-plugin/src → 3 niveaux → niers/
31
+ const _wsRoot = `${import.meta.dir}/../../..`;
32
+ const RE_DIR = `${_wsRoot}/data/re`;
33
+ const LUA_DIR = `${_wsRoot}/data/lua_scripts`;
34
+
35
+ // ─── utilitaires partagés ───────────────────────────────────────────────────
36
+
37
+ /** Lit un fichier en Uint8Array via Bun.file (lazy, zero-copy). */
38
+ async function readBytes(path: string): Promise<Uint8Array> {
39
+ const ab = await Bun.file(path).arrayBuffer();
40
+ return new Uint8Array(ab);
41
+ }
42
+
43
+ // ─── Bun.plugin() ───────────────────────────────────────────────────────────
44
+
45
+ Bun.plugin({
46
+ name: "nie-game-formats",
47
+
48
+ setup(build) {
49
+ // ── A1 : .g4tx → Uint8Array PNG ────────────────────────────────────────
50
+ build.onLoad({ filter: /\.g4tx$/ }, async ({ path }) => {
51
+ const raw = await readBytes(path);
52
+ const png = decodeToPng(raw);
53
+ if (png === null) {
54
+ throw new Error(`nie-plugin: decodeToPng a échoué pour ${path} (BC7/NXTCH non supporté ?)`);
55
+ }
56
+ return { loader: "object", exports: { default: png } };
57
+ });
58
+
59
+ // ── A2 : .cfg.bin → objet JSON ──────────────────────────────────────────
60
+ build.onLoad({ filter: /\.cfg\.bin$/ }, async ({ path }) => {
61
+ const raw = await readBytes(path);
62
+ const obj = decode(raw);
63
+ if (obj === null) {
64
+ throw new Error(`nie-plugin: decode a échoué pour ${path}`);
65
+ }
66
+ return { loader: "object", exports: { default: obj } };
67
+ });
68
+
69
+ // ── A3 : .objbin → objet JSON (MenuObject T2B) ──────────────────────────
70
+ build.onLoad({ filter: /\.objbin$/ }, async ({ path }) => {
71
+ const raw = await readBytes(path);
72
+ const obj = decode(raw);
73
+ if (obj === null) {
74
+ throw new Error(`nie-plugin: decode a échoué pour ${path}`);
75
+ }
76
+ return { loader: "object", exports: { default: obj } };
77
+ });
78
+
79
+ // ── A4 : .g4pkm → objet JSON (G4pkmLayout) ─────────────────────────────
80
+ build.onLoad({ filter: /\.g4pkm$/ }, async ({ path }) => {
81
+ const raw = await readBytes(path);
82
+ const obj = decode(raw);
83
+ if (obj === null) {
84
+ throw new Error(`nie-plugin: decode a échoué pour ${path}`);
85
+ }
86
+ return { loader: "object", exports: { default: obj } };
87
+ });
88
+
89
+ // ── A5 : .lip / .p3lip → objet JSON (LipSync) ──────────────────────────
90
+ build.onLoad({ filter: /\.(p3)?lip$/ }, async ({ path }) => {
91
+ const raw = await readBytes(path);
92
+ const obj = decode(raw);
93
+ if (obj === null) {
94
+ throw new Error(`nie-plugin: decode a échoué pour ${path}`);
95
+ }
96
+ return { loader: "object", exports: { default: obj } };
97
+ });
98
+
99
+ // ── A6 : .mev / .mevbin → objet JSON (MevbinDocument) ──────────────────
100
+ build.onLoad({ filter: /\.(mev|mevbin)$/ }, async ({ path }) => {
101
+ const raw = await readBytes(path);
102
+ const obj = decode(raw);
103
+ if (obj === null) {
104
+ throw new Error(`nie-plugin: decode a échoué pour ${path}`);
105
+ }
106
+ return { loader: "object", exports: { default: obj } };
107
+ });
108
+
109
+ // ── A7 : .g4md → objet JSON (G4md) ─────────────────────────────────────
110
+ build.onLoad({ filter: /\.g4md$/ }, async ({ path }) => {
111
+ const raw = await readBytes(path);
112
+ const obj = decode(raw);
113
+ if (obj === null) {
114
+ throw new Error(`nie-plugin: decode a échoué pour ${path}`);
115
+ }
116
+ return { loader: "object", exports: { default: obj } };
117
+ });
118
+
119
+ // ── B : nie:re/* — données RE statiques ─────────────────────────────────
120
+ build.onResolve({ filter: /^nie:re\// }, ({ path }) => {
121
+ const sfx = path.slice("nie:re/".length);
122
+ return { path: sfx, namespace: "nie-re" };
123
+ });
124
+
125
+ build.onLoad({ filter: /.*/, namespace: "nie-re" }, async ({ path }) => {
126
+ if (path.startsWith("lua/")) {
127
+ const name = path.slice("lua/".length);
128
+ const disk = `${LUA_DIR}/${name}`;
129
+ const raw = await readBytes(disk);
130
+ return { loader: "object", exports: { default: raw } };
131
+ }
132
+ // Artefacts RE JSON
133
+ const disk = `${RE_DIR}/${path}.json`;
134
+ const text = await Bun.file(disk).text();
135
+ return { loader: "json", contents: text };
136
+ });
137
+ },
138
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "include": ["src"]
4
+ }