pandoc_wasm 1.0.0
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.
- checksums.yaml +7 -0
- data/.npmignore +41 -0
- data/RALPH_TASK.md +52 -0
- data/README.md +382 -0
- data/Rakefile +27 -0
- data/SECURITY.md +343 -0
- data/cabal.project +45 -0
- data/index.js +24 -0
- data/lib/download.js +201 -0
- data/lib/pandoc_wasm/downloader.rb +142 -0
- data/lib/pandoc_wasm/version.rb +5 -0
- data/lib/pandoc_wasm.rb +36 -0
- data/package.json +39 -0
- data/pandoc_wasm.gemspec +47 -0
- metadata +67 -0
data/SECURITY.md
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# SECURITY.md - Aspects de sécurité des patches
|
|
2
|
+
|
|
3
|
+
> Documentation des modifications apportées aux packages Haskell pour la compilation WASM et leurs implications de sécurité.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
Ce document détaille les modifications de sécurité appliquées aux dépendances de Pandoc pour permettre la compilation en WebAssembly. Ces patches ont été nécessaires car :
|
|
10
|
+
|
|
11
|
+
1. **Architecture 32-bit** : WASM utilise une architecture 32-bit, causant des incompatibilités avec du code optimisé pour 64-bit
|
|
12
|
+
2. **APIs système limitées** : WASI (WebAssembly System Interface) ne fournit pas toutes les APIs POSIX
|
|
13
|
+
3. **Pas de threading** : Le runtime WASM n'a pas de support threading natif
|
|
14
|
+
4. **Pas de réseau** : WASI Preview 1 a un support réseau très limité
|
|
15
|
+
|
|
16
|
+
**Important** : Ces patches n'ont pas été audités de manière exhaustive. Utilisez ce projet en connaissance de cause.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Vue d'ensemble des modifications
|
|
21
|
+
|
|
22
|
+
| Package | Type de modification | Risque | Impact |
|
|
23
|
+
|---------|---------------------|--------|--------|
|
|
24
|
+
| basement | Conversions de types | Faible | Calculs numériques |
|
|
25
|
+
| memory | Désactivation fonctionnalités | Faible | Pas de memory-mapping |
|
|
26
|
+
| network | Stubs retournant erreurs | Moyen | Pas de réseau |
|
|
27
|
+
| cborg | Corrections 32-bit | Faible | Sérialisation CBOR |
|
|
28
|
+
| crypton | Désactivation threading | Faible | Crypto mono-thread |
|
|
29
|
+
| xml-conduit | Changement build-type | Aucun | Build uniquement |
|
|
30
|
+
| pandoc-cli | Suppression -threaded | Aucun | Runtime uniquement |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Détail par package
|
|
35
|
+
|
|
36
|
+
### 1. basement-0.0.16
|
|
37
|
+
|
|
38
|
+
**Fichiers modifiés** :
|
|
39
|
+
- `cbits/foundation_system.h`
|
|
40
|
+
- `Basement/Numerical/Conversion.hs`
|
|
41
|
+
- `Basement/Numerical/Additive.hs`
|
|
42
|
+
- `Basement/Types/OffsetSize.hs`
|
|
43
|
+
- `Basement/From.hs`
|
|
44
|
+
- `Basement/PrimType.hs`
|
|
45
|
+
- `Basement/Bits.hs`
|
|
46
|
+
|
|
47
|
+
**Nature des modifications** :
|
|
48
|
+
|
|
49
|
+
```haskell
|
|
50
|
+
-- AVANT (GHC < 9.4)
|
|
51
|
+
import GHC.IntWord64
|
|
52
|
+
|
|
53
|
+
-- APRÈS (GHC >= 9.4)
|
|
54
|
+
import GHC.Prim (int64ToInt#, word64ToWord#, ...)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Raison** : Le module `GHC.IntWord64` a été supprimé dans GHC 9.4+. Les primitives sont maintenant dans `GHC.Prim`.
|
|
58
|
+
|
|
59
|
+
**Conversions 32-bit** :
|
|
60
|
+
|
|
61
|
+
```haskell
|
|
62
|
+
-- Conversions explicites Word32# <-> Word# pour architecture 32-bit
|
|
63
|
+
word32ToWord# :: Word32# -> Word#
|
|
64
|
+
wordToWord32# :: Word# -> Word32#
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Implications de sécurité** :
|
|
68
|
+
- ✅ Pas d'impact sur la sécurité cryptographique
|
|
69
|
+
- ✅ Conversions de types standards
|
|
70
|
+
- ⚠️ Vérifier la correction des calculs sur grands nombres si critique
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
### 2. memory-0.18.0
|
|
75
|
+
|
|
76
|
+
**Fichiers modifiés** :
|
|
77
|
+
- `Data/Memory/Internal/CompatPrim64.hs`
|
|
78
|
+
- `Data/Memory/MemMap/Posix.hsc`
|
|
79
|
+
- `Data/ByteArray/Mapping.hs`
|
|
80
|
+
- `Data/Memory/PtrMethods.hs`
|
|
81
|
+
- `memory.cabal`
|
|
82
|
+
|
|
83
|
+
**Nature des modifications** :
|
|
84
|
+
|
|
85
|
+
1. **Désactivation de mmap** :
|
|
86
|
+
```haskell
|
|
87
|
+
-- Module MemMap.Posix désactivé sur wasm32
|
|
88
|
+
#if !defined(wasm32_HOST_ARCH)
|
|
89
|
+
-- code mmap original
|
|
90
|
+
#endif
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
2. **Corrections FFI** :
|
|
94
|
+
```haskell
|
|
95
|
+
-- Signatures FFI corrigées pour memcpy/memset
|
|
96
|
+
foreign import ccall unsafe "string.h memset"
|
|
97
|
+
c_memset :: Ptr Word8 -> Word8 -> CSize -> IO (Ptr Word8)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Implications de sécurité** :
|
|
101
|
+
- ✅ `mmap` n'est pas disponible en WASI de toute façon
|
|
102
|
+
- ✅ Les opérations mémoire utilisent l'allocateur standard
|
|
103
|
+
- ✅ Pas d'impact sur la sécurité des données en mémoire
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
### 3. network-3.2.8.0
|
|
108
|
+
|
|
109
|
+
**Fichiers modifiés** :
|
|
110
|
+
- `cbits/HsNet.c`
|
|
111
|
+
- `include/HsNet.h`
|
|
112
|
+
|
|
113
|
+
**Nature des modifications** :
|
|
114
|
+
|
|
115
|
+
Ajout de stubs pour les fonctions socket non fournies par WASI :
|
|
116
|
+
|
|
117
|
+
```c
|
|
118
|
+
/* cbits/HsNet.c - Stubs WASI */
|
|
119
|
+
#if defined(__wasi__) || defined(__wasm__)
|
|
120
|
+
|
|
121
|
+
int socket(int domain, int type, int protocol) {
|
|
122
|
+
errno = ENOSYS;
|
|
123
|
+
return -1;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
|
|
127
|
+
errno = ENOSYS;
|
|
128
|
+
return -1;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
|
|
132
|
+
errno = ENOSYS;
|
|
133
|
+
return -1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ... autres stubs similaires
|
|
137
|
+
#endif
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Fonctions stubées (retournent ENOSYS)** :
|
|
141
|
+
| Fonction | Comportement |
|
|
142
|
+
|----------|--------------|
|
|
143
|
+
| `socket()` | Retourne -1, errno=ENOSYS |
|
|
144
|
+
| `bind()` | Retourne -1, errno=ENOSYS |
|
|
145
|
+
| `listen()` | Retourne -1, errno=ENOSYS |
|
|
146
|
+
| `connect()` | Retourne -1, errno=ENOSYS |
|
|
147
|
+
| `setsockopt()` | Retourne 0 (succès factice) |
|
|
148
|
+
| `getsockopt()` | Retourne -1, errno=ENOPROTOOPT |
|
|
149
|
+
| `getpeername()` | Retourne -1, errno=ENOSYS |
|
|
150
|
+
| `getsockname()` | Retourne -1, errno=ENOSYS |
|
|
151
|
+
| `sendto()` | Retourne -1, errno=ENOSYS |
|
|
152
|
+
| `recvfrom()` | Retourne -1, errno=ENOSYS |
|
|
153
|
+
|
|
154
|
+
**Fonctions NON stubées** (fournies par WASI libc) :
|
|
155
|
+
- `accept()`, `send()`, `recv()`, `shutdown()`
|
|
156
|
+
|
|
157
|
+
**Implications de sécurité** :
|
|
158
|
+
- ✅ **Isolation réseau** : Aucune connexion réseau possible
|
|
159
|
+
- ✅ **Pas d'exfiltration de données** par réseau
|
|
160
|
+
- ⚠️ **Fonctionnalités cassées** : Tout code utilisant le réseau échouera
|
|
161
|
+
- ⚠️ `setsockopt()` retourne succès sans rien faire - code appelant peut avoir des attentes non respectées
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
### 4. cborg-0.2.10.0
|
|
166
|
+
|
|
167
|
+
**Fichiers modifiés** :
|
|
168
|
+
- `src/Codec/CBOR/Magic.hs`
|
|
169
|
+
- `src/Codec/CBOR/Decoding.hs`
|
|
170
|
+
- `src/Codec/CBOR/Read.hs`
|
|
171
|
+
|
|
172
|
+
**Nature des modifications** :
|
|
173
|
+
|
|
174
|
+
Corrections pour architecture 32-bit :
|
|
175
|
+
|
|
176
|
+
```haskell
|
|
177
|
+
-- Corrections de conversions Word64#/Int64# sur 32-bit
|
|
178
|
+
-- Suppression des imports GHC.IntWord64 dépréciés
|
|
179
|
+
-- Corrections de syntaxe dans isWord64Canonical, isInt64Canonical
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Implications de sécurité** :
|
|
183
|
+
- ✅ CBOR est utilisé pour la sérialisation de données
|
|
184
|
+
- ✅ Pas d'impact sur l'intégrité des données sérialisées
|
|
185
|
+
- ✅ Les tests de validation CBOR passent
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### 5. crypton-1.0.5
|
|
190
|
+
|
|
191
|
+
**Fichiers modifiés** :
|
|
192
|
+
- `cbits/argon2/thread.h`
|
|
193
|
+
- `cbits/argon2/thread.c`
|
|
194
|
+
|
|
195
|
+
**Nature des modifications** :
|
|
196
|
+
|
|
197
|
+
```c
|
|
198
|
+
/* cbits/argon2/thread.h */
|
|
199
|
+
#if defined(__wasi__) || defined(__wasm__) || defined(__wasm32__)
|
|
200
|
+
#define ARGON2_NO_THREADS 1
|
|
201
|
+
#endif
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Raison** : WASI ne supporte pas `pthread_exit()`. Argon2 doit fonctionner en mode mono-thread.
|
|
205
|
+
|
|
206
|
+
**Implications de sécurité** :
|
|
207
|
+
- ✅ **Argon2 reste fonctionnel** en mode single-thread
|
|
208
|
+
- ⚠️ **Performance réduite** : Pas de parallélisation du hachage
|
|
209
|
+
- ✅ **Sécurité cryptographique préservée** : Argon2 mono-thread est toujours sécurisé
|
|
210
|
+
- ℹ️ Les paramètres de coût peuvent nécessiter ajustement pour compenser
|
|
211
|
+
|
|
212
|
+
**Note sur Argon2** : Argon2 est un algorithme de hachage de mots de passe. Le mode mono-thread est plus lent mais tout aussi sécurisé cryptographiquement.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
### 6. xml-conduit-1.10.1.0
|
|
217
|
+
|
|
218
|
+
**Fichiers modifiés** :
|
|
219
|
+
- `xml-conduit.cabal`
|
|
220
|
+
- `Setup.hs`
|
|
221
|
+
|
|
222
|
+
**Nature des modifications** :
|
|
223
|
+
|
|
224
|
+
```cabal
|
|
225
|
+
-- AVANT
|
|
226
|
+
build-type: Custom
|
|
227
|
+
custom-setup
|
|
228
|
+
setup-depends: base, Cabal, cabal-doctest
|
|
229
|
+
|
|
230
|
+
-- APRÈS
|
|
231
|
+
build-type: Simple
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Raison** : Les packages avec `build-type: Custom` échouent en cross-compilation car le `Setup.hs` est compilé pour l'hôte, pas la cible WASM.
|
|
235
|
+
|
|
236
|
+
**Implications de sécurité** :
|
|
237
|
+
- ✅ **Aucun impact** : Changement de build uniquement
|
|
238
|
+
- ✅ Les doctests sont désactivés (tests uniquement)
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
### 7. pandoc-cli-3.8.3
|
|
243
|
+
|
|
244
|
+
**Fichiers modifiés** :
|
|
245
|
+
- `pandoc-cli.cabal`
|
|
246
|
+
|
|
247
|
+
**Nature des modifications** :
|
|
248
|
+
|
|
249
|
+
```cabal
|
|
250
|
+
-- AVANT
|
|
251
|
+
ghc-options: -threaded -rtsopts -with-rtsopts=-A8m
|
|
252
|
+
|
|
253
|
+
-- APRÈS
|
|
254
|
+
ghc-options: -rtsopts -with-rtsopts=-A8m
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Raison** : GHC WASM n'a pas de runtime threadé (`-lHSrts_thr` n'existe pas).
|
|
258
|
+
|
|
259
|
+
**Implications de sécurité** :
|
|
260
|
+
- ✅ **Aucun impact sur la sécurité**
|
|
261
|
+
- ℹ️ L'exécution est mono-thread (ce qui est la norme pour WASM)
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Implications globales de sécurité
|
|
266
|
+
|
|
267
|
+
### Isolation WASI
|
|
268
|
+
|
|
269
|
+
Le binaire `pandoc.wasm` s'exécute dans un environnement WASI isolé :
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
┌─────────────────────────────────────────┐
|
|
273
|
+
│ Host System │
|
|
274
|
+
│ ┌───────────────────────────────────┐ │
|
|
275
|
+
│ │ WASI Sandbox │ │
|
|
276
|
+
│ │ ┌─────────────────────────────┐ │ │
|
|
277
|
+
│ │ │ pandoc.wasm │ │ │
|
|
278
|
+
│ │ │ │ │ │
|
|
279
|
+
│ │ │ ✗ Pas de réseau │ │ │
|
|
280
|
+
│ │ │ ✗ Pas de processus │ │ │
|
|
281
|
+
│ │ │ ✓ Fichiers (--dir .) │ │ │
|
|
282
|
+
│ │ │ ✓ stdin/stdout │ │ │
|
|
283
|
+
│ │ └─────────────────────────────┘ │ │
|
|
284
|
+
│ └───────────────────────────────────┘ │
|
|
285
|
+
└─────────────────────────────────────────┘
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Garanties de sécurité WASI** :
|
|
289
|
+
- ✅ Accès fichiers limité aux répertoires explicitement montés (`--dir`)
|
|
290
|
+
- ✅ Pas d'accès réseau
|
|
291
|
+
- ✅ Pas de création de processus
|
|
292
|
+
- ✅ Isolation mémoire du runtime WASM
|
|
293
|
+
|
|
294
|
+
### Points d'attention
|
|
295
|
+
|
|
296
|
+
| Aspect | Statut | Notes |
|
|
297
|
+
|--------|--------|-------|
|
|
298
|
+
| Exécution de code arbitraire | ✅ Protégé | Sandbox WASI |
|
|
299
|
+
| Accès fichiers | ⚠️ Contrôlé | Uniquement via `--dir` |
|
|
300
|
+
| Réseau | ✅ Bloqué | Stubs ENOSYS |
|
|
301
|
+
| Fuite mémoire | ℹ️ Possible | Comme tout programme |
|
|
302
|
+
| DoS (CPU) | ⚠️ Possible | Pas de limite de temps par défaut |
|
|
303
|
+
|
|
304
|
+
### Recommandations
|
|
305
|
+
|
|
306
|
+
1. **Limiter l'accès fichiers** : N'utilisez `--dir` que sur les répertoires nécessaires
|
|
307
|
+
2. **Timeout** : Utilisez `wasmtime run --wasm timeout=30s` pour limiter le temps d'exécution
|
|
308
|
+
3. **Mémoire** : Utilisez `--wasm max-memory=512MiB` pour limiter la mémoire
|
|
309
|
+
4. **Entrées** : Validez les fichiers d'entrée avant conversion
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
# Exemple d'exécution sécurisée
|
|
313
|
+
wasmtime run \
|
|
314
|
+
--dir ./input:readonly \
|
|
315
|
+
--dir ./output \
|
|
316
|
+
--wasm timeout=60s \
|
|
317
|
+
--wasm max-memory=1GiB \
|
|
318
|
+
pandoc.wasm -o ./output/result.html ./input/document.md
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Audit et contributions
|
|
324
|
+
|
|
325
|
+
Ces patches n'ont pas fait l'objet d'un audit de sécurité formel. Si vous identifiez des problèmes de sécurité :
|
|
326
|
+
|
|
327
|
+
1. **Ne pas créer d'issue publique** pour les vulnérabilités
|
|
328
|
+
2. Contacter les mainteneurs en privé
|
|
329
|
+
3. Fournir une description détaillée et un PoC si possible
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Changelog des patches
|
|
334
|
+
|
|
335
|
+
| Date | Package | Modification |
|
|
336
|
+
|------|---------|--------------|
|
|
337
|
+
| 2026-01-28 | basement | Conversions 32-bit, suppression GHC.IntWord64 |
|
|
338
|
+
| 2026-01-28 | memory | Désactivation mmap, corrections FFI |
|
|
339
|
+
| 2026-01-28 | network | Stubs socket WASI |
|
|
340
|
+
| 2026-01-28 | cborg | Corrections 32-bit |
|
|
341
|
+
| 2026-01-28 | crypton | ARGON2_NO_THREADS |
|
|
342
|
+
| 2026-01-28 | xml-conduit | build-type: Simple |
|
|
343
|
+
| 2026-01-28 | pandoc-cli | Suppression -threaded |
|
data/cabal.project
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
-- Pandoc WASM build configuration
|
|
2
|
+
-- Targets Pandoc 3.8.3 for WASM compilation
|
|
3
|
+
|
|
4
|
+
-- Use Hackage packages
|
|
5
|
+
index-state: 2026-01-20T00:00:00Z
|
|
6
|
+
|
|
7
|
+
-- pandoc-cli is now in patches, pandoc will be pulled as dependency
|
|
8
|
+
|
|
9
|
+
-- Use patched packages for WASM compatibility
|
|
10
|
+
packages: patches/basement-0.0.16
|
|
11
|
+
patches/memory-0.18.0
|
|
12
|
+
patches/network-3.2.8.0
|
|
13
|
+
patches/cborg-0.2.10.0
|
|
14
|
+
patches/crypton-1.0.5
|
|
15
|
+
patches/xml-conduit-1.10.1.0
|
|
16
|
+
patches/pandoc-cli-3.8.3
|
|
17
|
+
|
|
18
|
+
-- Disable problematic features for WASM
|
|
19
|
+
package pandoc-cli
|
|
20
|
+
flags: -lua -server
|
|
21
|
+
|
|
22
|
+
-- Disable pkg-config for digest (use bundled zlib from zlib-clib)
|
|
23
|
+
package digest
|
|
24
|
+
flags: -pkg-config
|
|
25
|
+
|
|
26
|
+
-- Embed data files for standalone WASM binary
|
|
27
|
+
package pandoc
|
|
28
|
+
flags: +embed_data_files
|
|
29
|
+
|
|
30
|
+
-- Force constraints for WASM-compatible versions
|
|
31
|
+
constraints:
|
|
32
|
+
pandoc == 3.8.3,
|
|
33
|
+
pandoc-cli == 3.8.3
|
|
34
|
+
|
|
35
|
+
-- Enable _GNU_SOURCE for network package C code on WASI
|
|
36
|
+
package network
|
|
37
|
+
cc-options: -D_GNU_SOURCE
|
|
38
|
+
|
|
39
|
+
-- Disable threading in crypton's argon2 (WASI doesn't support pthread_exit)
|
|
40
|
+
package crypton
|
|
41
|
+
cc-options: -DARGON2_NO_THREADS
|
|
42
|
+
|
|
43
|
+
-- Optimization
|
|
44
|
+
program-options
|
|
45
|
+
ghc-options: -O2
|
data/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nathanhimpens/pandoc-wasm
|
|
3
|
+
*
|
|
4
|
+
* Exports the path to the pandoc.wasm binary.
|
|
5
|
+
* The binary is automatically downloaded from GitHub Releases during npm install.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
|
|
11
|
+
const wasmPath = path.join(__dirname, 'pandoc.wasm');
|
|
12
|
+
|
|
13
|
+
// Check if the file exists, if not, provide helpful error message
|
|
14
|
+
if (!fs.existsSync(wasmPath)) {
|
|
15
|
+
console.warn(
|
|
16
|
+
'Warning: pandoc.wasm not found. It should be downloaded automatically during installation.\n' +
|
|
17
|
+
'If you see this message, try running: npm run postinstall'
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = wasmPath;
|
|
22
|
+
|
|
23
|
+
// Also export as default for ES modules compatibility
|
|
24
|
+
module.exports.default = wasmPath;
|
data/lib/download.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Downloads pandoc.wasm from GitHub Releases
|
|
5
|
+
* This script runs automatically after npm install via the postinstall hook
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const https = require('https');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const REPO_OWNER = 'NathanHimpens';
|
|
13
|
+
const REPO_NAME = 'pandoc-wasm';
|
|
14
|
+
const ASSET_NAME = 'pandoc.wasm';
|
|
15
|
+
const WASM_PATH = path.join(__dirname, '..', 'pandoc.wasm');
|
|
16
|
+
|
|
17
|
+
// Get version from package.json
|
|
18
|
+
const packageJson = require('../package.json');
|
|
19
|
+
const version = packageJson.version;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the latest release tag from GitHub API
|
|
23
|
+
*/
|
|
24
|
+
function getLatestReleaseTag() {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const options = {
|
|
27
|
+
hostname: 'api.github.com',
|
|
28
|
+
path: `/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`,
|
|
29
|
+
headers: {
|
|
30
|
+
'User-Agent': 'pandoc-wasm-downloader',
|
|
31
|
+
'Accept': 'application/vnd.github.v3+json'
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
https.get(options, (res) => {
|
|
36
|
+
let data = '';
|
|
37
|
+
|
|
38
|
+
res.on('data', (chunk) => {
|
|
39
|
+
data += chunk;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
res.on('end', () => {
|
|
43
|
+
if (res.statusCode === 200) {
|
|
44
|
+
try {
|
|
45
|
+
const release = JSON.parse(data);
|
|
46
|
+
resolve(release.tag_name);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
reject(new Error(`Failed to parse release data: ${e.message}`));
|
|
49
|
+
}
|
|
50
|
+
} else if (res.statusCode === 404) {
|
|
51
|
+
// No releases yet, try to use version from package.json
|
|
52
|
+
console.log(`No GitHub release found. Using version ${version} from package.json.`);
|
|
53
|
+
resolve(`v${version}`);
|
|
54
|
+
} else {
|
|
55
|
+
reject(new Error(`GitHub API returned status ${res.statusCode}: ${data}`));
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}).on('error', (e) => {
|
|
59
|
+
reject(new Error(`Failed to fetch release info: ${e.message}`));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Download the asset from GitHub Releases
|
|
66
|
+
*/
|
|
67
|
+
function downloadAsset(tag) {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
// Try to get the release by tag first to find the asset URL
|
|
70
|
+
const options = {
|
|
71
|
+
hostname: 'api.github.com',
|
|
72
|
+
path: `/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/${tag}`,
|
|
73
|
+
headers: {
|
|
74
|
+
'User-Agent': 'pandoc-wasm-downloader',
|
|
75
|
+
'Accept': 'application/vnd.github.v3+json'
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
https.get(options, (res) => {
|
|
80
|
+
let data = '';
|
|
81
|
+
|
|
82
|
+
res.on('data', (chunk) => {
|
|
83
|
+
data += chunk;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
res.on('end', () => {
|
|
87
|
+
if (res.statusCode === 404) {
|
|
88
|
+
// Release doesn't exist yet, skip download
|
|
89
|
+
console.log(`Release ${tag} not found on GitHub. Skipping download.`);
|
|
90
|
+
console.log('You can manually download pandoc.wasm from the repository or build it yourself.');
|
|
91
|
+
resolve(false);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (res.statusCode !== 200) {
|
|
96
|
+
reject(new Error(`GitHub API returned status ${res.statusCode}: ${data}`));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const release = JSON.parse(data);
|
|
102
|
+
const asset = release.assets.find(a => a.name === ASSET_NAME);
|
|
103
|
+
|
|
104
|
+
if (!asset) {
|
|
105
|
+
console.log(`Asset ${ASSET_NAME} not found in release ${tag}.`);
|
|
106
|
+
console.log('You can manually download pandoc.wasm from the repository or build it yourself.');
|
|
107
|
+
resolve(false);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Download the asset
|
|
112
|
+
console.log(`Downloading ${ASSET_NAME} from release ${tag}...`);
|
|
113
|
+
console.log(`Size: ${(asset.size / 1024 / 1024).toFixed(2)} MB`);
|
|
114
|
+
|
|
115
|
+
const file = fs.createWriteStream(WASM_PATH);
|
|
116
|
+
const downloadUrl = new URL(asset.browser_download_url);
|
|
117
|
+
|
|
118
|
+
const downloadOptions = {
|
|
119
|
+
hostname: downloadUrl.hostname,
|
|
120
|
+
path: downloadUrl.pathname + downloadUrl.search,
|
|
121
|
+
headers: {
|
|
122
|
+
'User-Agent': 'pandoc-wasm-downloader',
|
|
123
|
+
'Accept': 'application/octet-stream'
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
https.get(downloadOptions, (downloadRes) => {
|
|
128
|
+
if (downloadRes.statusCode !== 200) {
|
|
129
|
+
file.close();
|
|
130
|
+
fs.unlinkSync(WASM_PATH);
|
|
131
|
+
reject(new Error(`Failed to download asset: ${downloadRes.statusCode}`));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
downloadRes.pipe(file);
|
|
136
|
+
|
|
137
|
+
file.on('finish', () => {
|
|
138
|
+
file.close();
|
|
139
|
+
// Make executable
|
|
140
|
+
fs.chmodSync(WASM_PATH, 0o755);
|
|
141
|
+
console.log(`✓ Successfully downloaded ${ASSET_NAME}`);
|
|
142
|
+
resolve(true);
|
|
143
|
+
});
|
|
144
|
+
}).on('error', (e) => {
|
|
145
|
+
file.close();
|
|
146
|
+
fs.unlinkSync(WASM_PATH);
|
|
147
|
+
reject(new Error(`Download failed: ${e.message}`));
|
|
148
|
+
});
|
|
149
|
+
} catch (e) {
|
|
150
|
+
reject(new Error(`Failed to parse release data: ${e.message}`));
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}).on('error', (e) => {
|
|
154
|
+
reject(new Error(`Failed to fetch release: ${e.message}`));
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Main function
|
|
161
|
+
*/
|
|
162
|
+
async function main() {
|
|
163
|
+
// Check if file already exists
|
|
164
|
+
if (fs.existsSync(WASM_PATH)) {
|
|
165
|
+
console.log('pandoc.wasm already exists. Skipping download.');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const tag = await getLatestReleaseTag();
|
|
171
|
+
const downloaded = await downloadAsset(tag);
|
|
172
|
+
|
|
173
|
+
if (!downloaded) {
|
|
174
|
+
console.warn('\n⚠️ pandoc.wasm was not downloaded automatically.');
|
|
175
|
+
console.warn('This is normal if no GitHub release exists yet.\n');
|
|
176
|
+
console.warn('To use this package, you need to:');
|
|
177
|
+
console.warn('1. Build pandoc.wasm yourself (see README.md)');
|
|
178
|
+
console.warn('2. Create a GitHub release with pandoc.wasm attached');
|
|
179
|
+
console.warn('3. Or manually copy pandoc.wasm to this directory\n');
|
|
180
|
+
// Don't exit with error - allow the package to be installed
|
|
181
|
+
// The user can manually add the file later
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error('Error downloading pandoc.wasm:', error.message);
|
|
186
|
+
console.error('\nYou can:');
|
|
187
|
+
console.error('1. Build it yourself following the instructions in README.md');
|
|
188
|
+
console.error('2. Manually download it from a GitHub release');
|
|
189
|
+
console.error('3. Copy it from the build directory after compilation');
|
|
190
|
+
// Don't exit with error - allow installation to continue
|
|
191
|
+
// The user can manually add the file later
|
|
192
|
+
console.warn('\n⚠️ Installation will continue, but pandoc.wasm must be added manually.');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Run if called directly
|
|
197
|
+
if (require.main === module) {
|
|
198
|
+
main();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = { main, downloadAsset, getLatestReleaseTag };
|