romm-uploader 0.1.1-beta.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.
Files changed (120) hide show
  1. package/.env.example +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +228 -0
  4. package/dist/cache/checksum-cache.d.ts +20 -0
  5. package/dist/cache/checksum-cache.d.ts.map +1 -0
  6. package/dist/cache/checksum-cache.js +134 -0
  7. package/dist/cache/checksum-cache.js.map +1 -0
  8. package/dist/checksum/compute-checksum.d.ts +16 -0
  9. package/dist/checksum/compute-checksum.d.ts.map +1 -0
  10. package/dist/checksum/compute-checksum.js +71 -0
  11. package/dist/checksum/compute-checksum.js.map +1 -0
  12. package/dist/cli.d.ts +7 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +165 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/config.d.ts +34 -0
  17. package/dist/config.d.ts.map +1 -0
  18. package/dist/config.js +95 -0
  19. package/dist/config.js.map +1 -0
  20. package/dist/files/discover-files.d.ts +9 -0
  21. package/dist/files/discover-files.d.ts.map +1 -0
  22. package/dist/files/discover-files.js +34 -0
  23. package/dist/files/discover-files.js.map +1 -0
  24. package/dist/files/discover-sources.d.ts +6 -0
  25. package/dist/files/discover-sources.d.ts.map +1 -0
  26. package/dist/files/discover-sources.js +69 -0
  27. package/dist/files/discover-sources.js.map +1 -0
  28. package/dist/files/discover-types.d.ts +8 -0
  29. package/dist/files/discover-types.d.ts.map +1 -0
  30. package/dist/files/discover-types.js +2 -0
  31. package/dist/files/discover-types.js.map +1 -0
  32. package/dist/files/file-info.d.ts +6 -0
  33. package/dist/files/file-info.d.ts.map +1 -0
  34. package/dist/files/file-info.js +44 -0
  35. package/dist/files/file-info.js.map +1 -0
  36. package/dist/output/planner.d.ts +6 -0
  37. package/dist/output/planner.d.ts.map +1 -0
  38. package/dist/output/planner.js +77 -0
  39. package/dist/output/planner.js.map +1 -0
  40. package/dist/output/records.d.ts +16 -0
  41. package/dist/output/records.d.ts.map +1 -0
  42. package/dist/output/records.js +31 -0
  43. package/dist/output/records.js.map +1 -0
  44. package/dist/output/reporter.d.ts +38 -0
  45. package/dist/output/reporter.d.ts.map +1 -0
  46. package/dist/output/reporter.js +66 -0
  47. package/dist/output/reporter.js.map +1 -0
  48. package/dist/romm/auth-cache.d.ts +27 -0
  49. package/dist/romm/auth-cache.d.ts.map +1 -0
  50. package/dist/romm/auth-cache.js +115 -0
  51. package/dist/romm/auth-cache.js.map +1 -0
  52. package/dist/romm/client-http.d.ts +5 -0
  53. package/dist/romm/client-http.d.ts.map +1 -0
  54. package/dist/romm/client-http.js +44 -0
  55. package/dist/romm/client-http.js.map +1 -0
  56. package/dist/romm/client-mappers.d.ts +18 -0
  57. package/dist/romm/client-mappers.d.ts.map +1 -0
  58. package/dist/romm/client-mappers.js +66 -0
  59. package/dist/romm/client-mappers.js.map +1 -0
  60. package/dist/romm/client-session.d.ts +23 -0
  61. package/dist/romm/client-session.d.ts.map +1 -0
  62. package/dist/romm/client-session.js +116 -0
  63. package/dist/romm/client-session.js.map +1 -0
  64. package/dist/romm/client-types.d.ts +29 -0
  65. package/dist/romm/client-types.d.ts.map +1 -0
  66. package/dist/romm/client-types.js +2 -0
  67. package/dist/romm/client-types.js.map +1 -0
  68. package/dist/romm/client.d.ts +86 -0
  69. package/dist/romm/client.d.ts.map +1 -0
  70. package/dist/romm/client.js +168 -0
  71. package/dist/romm/client.js.map +1 -0
  72. package/dist/romm/upload-duplicate-check.d.ts +16 -0
  73. package/dist/romm/upload-duplicate-check.d.ts.map +1 -0
  74. package/dist/romm/upload-duplicate-check.js +16 -0
  75. package/dist/romm/upload-duplicate-check.js.map +1 -0
  76. package/dist/romm/upload-utils.d.ts +9 -0
  77. package/dist/romm/upload-utils.d.ts.map +1 -0
  78. package/dist/romm/upload-utils.js +31 -0
  79. package/dist/romm/upload-utils.js.map +1 -0
  80. package/dist/types.d.ts +98 -0
  81. package/dist/types.d.ts.map +1 -0
  82. package/dist/types.js +5 -0
  83. package/dist/types.js.map +1 -0
  84. package/dist/workflow/create-client.d.ts +7 -0
  85. package/dist/workflow/create-client.d.ts.map +1 -0
  86. package/dist/workflow/create-client.js +19 -0
  87. package/dist/workflow/create-client.js.map +1 -0
  88. package/dist/workflow/discover-input.d.ts +6 -0
  89. package/dist/workflow/discover-input.d.ts.map +1 -0
  90. package/dist/workflow/discover-input.js +14 -0
  91. package/dist/workflow/discover-input.js.map +1 -0
  92. package/dist/workflow/io.d.ts +6 -0
  93. package/dist/workflow/io.d.ts.map +1 -0
  94. package/dist/workflow/io.js +13 -0
  95. package/dist/workflow/io.js.map +1 -0
  96. package/dist/workflow/process-dry-file.d.ts +26 -0
  97. package/dist/workflow/process-dry-file.d.ts.map +1 -0
  98. package/dist/workflow/process-dry-file.js +51 -0
  99. package/dist/workflow/process-dry-file.js.map +1 -0
  100. package/dist/workflow/process-roms.d.ts +15 -0
  101. package/dist/workflow/process-roms.d.ts.map +1 -0
  102. package/dist/workflow/process-roms.js +26 -0
  103. package/dist/workflow/process-roms.js.map +1 -0
  104. package/dist/workflow/process-upload-file.d.ts +22 -0
  105. package/dist/workflow/process-upload-file.d.ts.map +1 -0
  106. package/dist/workflow/process-upload-file.js +55 -0
  107. package/dist/workflow/process-upload-file.js.map +1 -0
  108. package/dist/workflow/resolve-checksums.d.ts +10 -0
  109. package/dist/workflow/resolve-checksums.d.ts.map +1 -0
  110. package/dist/workflow/resolve-checksums.js +28 -0
  111. package/dist/workflow/resolve-checksums.js.map +1 -0
  112. package/dist/workflow/run-dry.d.ts +6 -0
  113. package/dist/workflow/run-dry.d.ts.map +1 -0
  114. package/dist/workflow/run-dry.js +96 -0
  115. package/dist/workflow/run-dry.js.map +1 -0
  116. package/dist/workflow/run-upload.d.ts +6 -0
  117. package/dist/workflow/run-upload.d.ts.map +1 -0
  118. package/dist/workflow/run-upload.js +131 -0
  119. package/dist/workflow/run-upload.js.map +1 -0
  120. package/package.json +68 -0
package/.env.example ADDED
@@ -0,0 +1,7 @@
1
+ # RomM Connection Settings
2
+ ROMM_BASE_URL=https://your-romm-instance.com
3
+ ROMM_USERNAME=your_username
4
+ ROMM_PASSWORD=your_password
5
+
6
+ # Optional: Override cache locations
7
+ # XDG_CACHE_HOME=/custom/cache/path
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Josh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # RomM Uploader CLI
2
+
3
+ A Linux-first TypeScript CLI for uploading ROMs to your [RomM](https://romm.app/) instance.
4
+
5
+ ## Features
6
+
7
+ - 🔐 OAuth2 authentication with token caching
8
+ - 🔍 Checksum-based duplicate detection (CRC32, MD5, SHA1)
9
+ - 💾 Local checksum cache for faster re-runs
10
+ - 📁 Support for files, directories, and glob patterns
11
+ - 🎮 Platform discovery and resolution
12
+ - 🧪 Dry-run mode for testing
13
+ - 📊 JSON output for scripting
14
+
15
+ ## Prerequisites
16
+
17
+ - Node.js 18+
18
+ - RomM instance (v4.5.0+)
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ # Run without installing
24
+ npx romm-uploader --help
25
+
26
+ # Or install globally
27
+ npm install -g romm-uploader
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ### Basic Upload
33
+
34
+ ```bash
35
+ # Using environment variables
36
+ export ROMM_BASE_URL=https://romm.example.com
37
+ export ROMM_USERNAME=admin
38
+ export ROMM_PASSWORD=secret
39
+
40
+ romm-uploader --platform snes "Super Mario World.smc"
41
+ ```
42
+
43
+ ### With CLI Options
44
+
45
+ ```bash
46
+ romm-uploader \
47
+ --base-url https://romm.example.com \
48
+ --username admin \
49
+ --password secret \
50
+ --platform gba \
51
+ ~/roms/gameboy/*.gba
52
+ ```
53
+
54
+ ### List Platforms
55
+
56
+ ```bash
57
+ romm-uploader --list-platforms
58
+ ```
59
+
60
+ ### Dry Run
61
+
62
+ ```bash
63
+ romm-uploader --dry-run --platform psx ~/roms/playstation/
64
+ ```
65
+
66
+ ### Multiple Input Methods
67
+
68
+ ```bash
69
+ romm-uploader \
70
+ --platform snes \
71
+ --upload-file "game1.smc" \
72
+ --upload-file "game2.smc" \
73
+ --upload-dir ~/roms/more/ \
74
+ --glob "*.sfc"
75
+ ```
76
+
77
+ ## Configuration
78
+
79
+ Configuration precedence (highest to lowest):
80
+ 1. CLI options
81
+ 2. Environment variables
82
+ 3. Defaults
83
+
84
+ ### Environment Variables
85
+
86
+ | Variable | Description |
87
+ |----------|-------------|
88
+ | `ROMM_BASE_URL` | RomM instance URL |
89
+ | `ROMM_USERNAME` | Username for authentication |
90
+ | `ROMM_PASSWORD` | Password for authentication |
91
+
92
+ Copy `.env.example` to `.env` and fill in your values.
93
+
94
+ ## CLI Options
95
+
96
+ ### Connection Options
97
+
98
+ | Option | Description | Default |
99
+ |--------|-------------|---------|
100
+ | `--base-url <url>` | RomM base URL | `ROMM_BASE_URL` env |
101
+ | `--username <user>` | Username | `ROMM_USERNAME` env |
102
+ | `--password <pass>` | Password | `ROMM_PASSWORD` env |
103
+
104
+ ### Platform Options
105
+
106
+ | Option | Description |
107
+ |--------|-------------|
108
+ | `--platform <value>` | Target platform (slug, name, or ID) |
109
+ | `--list-platforms` | List available platforms and exit |
110
+
111
+ ### Input Options
112
+
113
+ | Option | Description |
114
+ |--------|-------------|
115
+ | `[paths...]` | Positional file/directory paths |
116
+ | `--upload-file <path>` | Specific file to upload (repeatable) |
117
+ | `--upload-dir <path>` | Directory to upload from (repeatable) |
118
+ | `--glob <pattern>` | Glob pattern for files (repeatable) |
119
+ | `--recursive` | Include nested directories |
120
+
121
+ ### Checksum Options
122
+
123
+ | Option | Description | Default |
124
+ |--------|-------------|---------|
125
+ | `--checksum <algo>` | Additional hash algorithm | - |
126
+ | `--cache-enabled <bool>` | Enable checksum cache | `true` |
127
+ | `--cache-max-mb <n>` | Cache size limit | `4` |
128
+ | `--cache-path <path>` | Custom cache location | XDG default |
129
+
130
+ ### Auth Options
131
+
132
+ | Option | Description | Default |
133
+ |--------|-------------|---------|
134
+ | `--auth-cache-enabled <bool>` | Enable token cache | `true` |
135
+ | `--auth-cache-path <path>` | Custom auth cache location | XDG default |
136
+ | `--auth-reauth-skew-seconds <n>` | Early refresh window | `60` |
137
+
138
+ ### Request Options
139
+
140
+ | Option | Description | Default |
141
+ |--------|-------------|---------|
142
+ | `--request-timeout-seconds <n>` | HTTP timeout | `60` |
143
+ | `--retry-count <n>` | Retry attempts | `0` |
144
+
145
+ ### Behavior Options
146
+
147
+ | Option | Description | Default |
148
+ |--------|-------------|---------|
149
+ | `--dry-run` | Check only, no upload | `false` |
150
+ | `--concurrency <n>` | Parallel processing | `3` |
151
+
152
+ ### Output Options
153
+
154
+ | Option | Description | Default |
155
+ |--------|-------------|---------|
156
+ | `--json` | JSON output | `false` |
157
+ | `--verbose` | Debug logging | `false` |
158
+
159
+ ## Exit Codes
160
+
161
+ | Code | Meaning |
162
+ |------|---------|
163
+ | `0` | Success (all files processed) |
164
+ | `1` | Partial/complete failure |
165
+ | `2` | Invalid usage/configuration |
166
+
167
+ ## Development
168
+
169
+ ```bash
170
+ # Install dependencies
171
+ npm install
172
+
173
+ # Run in development mode
174
+ npm run dev -- --help
175
+
176
+ # Build
177
+ npm run build
178
+
179
+ # Release gate (must pass before publish)
180
+ npm run release:check
181
+
182
+ # Run tests
183
+ npm test
184
+
185
+ # Lint
186
+ npm run lint
187
+
188
+ # Format
189
+ npm run format
190
+ ```
191
+
192
+ ## Project Structure
193
+
194
+ ```
195
+ .
196
+ ├── src/
197
+ │ ├── cli.ts # Entry point
198
+ │ ├── config.ts # Configuration management
199
+ │ ├── types.ts # Type definitions
200
+ │ ├── files/
201
+ │ │ └── discover-files.ts # File discovery
202
+ │ ├── checksum/
203
+ │ │ └── compute-checksum.ts # Hash computation
204
+ │ ├── cache/
205
+ │ │ └── checksum-cache.ts # Checksum caching
206
+ │ ├── romm/
207
+ │ │ └── client.ts # RomM API client
208
+ │ ├── output/
209
+ │ │ └── reporter.ts # Output formatting
210
+ │ └── workflow/
211
+ │ └── process-roms.ts # Main workflow
212
+ ├── test/
213
+ │ ├── unit/ # Unit tests
214
+ │ └── integration/ # Integration tests
215
+ ├── docs/
216
+ │ └── api-notes.md # RomM API documentation
217
+ └── package.json
218
+ ```
219
+
220
+ ## Release Notes
221
+
222
+ - `npm publish` is guarded by `prepublishOnly`, which runs `npm run release:check`.
223
+ - `npm run release:check` runs typecheck, lint, tests, and package dry-run.
224
+ - Build output is cleaned before compile to prevent stale artifacts.
225
+
226
+ ## License
227
+
228
+ MIT
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Checksum cache module
3
+ * Local disk cache for checksum reuse across runs
4
+ */
5
+ import { ChecksumCacheEntry, ChecksumData } from '../types.js';
6
+ export interface ChecksumCacheOptions {
7
+ enabled: boolean;
8
+ maxMb: number;
9
+ cachePath?: string;
10
+ }
11
+ /**
12
+ * Get cached checksum for a file if valid
13
+ * Validation: absolute path + file size + modification time
14
+ */
15
+ export declare function getCachedChecksum(filePath: string, size: number, mtime: number, options: ChecksumCacheOptions): Promise<ChecksumData | null>;
16
+ /**
17
+ * Store checksum in cache
18
+ */
19
+ export declare function setCachedChecksum(entry: ChecksumCacheEntry, options: ChecksumCacheOptions): Promise<void>;
20
+ //# sourceMappingURL=checksum-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checksum-cache.d.ts","sourceRoot":"","sources":["../../src/cache/checksum-cache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK/D,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA8GD;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAkB9B;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAef"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Checksum cache module
3
+ * Local disk cache for checksum reuse across runs
4
+ */
5
+ import { readFile, writeFile, mkdir } from 'fs/promises';
6
+ import { existsSync } from 'fs';
7
+ import { dirname } from 'path';
8
+ // In-memory cache
9
+ const cache = {
10
+ entries: new Map(),
11
+ totalSize: 0,
12
+ };
13
+ let cacheLoaded = false;
14
+ /**
15
+ * Get default cache path based on XDG spec
16
+ */
17
+ function getDefaultCachePath() {
18
+ const xdgCacheHome = process.env.XDG_CACHE_HOME;
19
+ if (xdgCacheHome) {
20
+ return `${xdgCacheHome}/romm-uploader/checksum-cache.json`;
21
+ }
22
+ return `${process.env.HOME ?? '~'}/.cache/romm-uploader/checksum-cache.json`;
23
+ }
24
+ /**
25
+ * Load cache from disk
26
+ */
27
+ async function loadCache(cachePath) {
28
+ if (cacheLoaded)
29
+ return;
30
+ try {
31
+ if (!existsSync(cachePath)) {
32
+ cacheLoaded = true;
33
+ return;
34
+ }
35
+ const data = await readFile(cachePath, 'utf-8');
36
+ const parsed = JSON.parse(data);
37
+ if (parsed.entries) {
38
+ for (const [key, entry] of Object.entries(parsed.entries)) {
39
+ cache.entries.set(key, entry);
40
+ }
41
+ }
42
+ cacheLoaded = true;
43
+ }
44
+ catch {
45
+ // If cache is corrupt, start fresh
46
+ cache.entries.clear();
47
+ cacheLoaded = true;
48
+ }
49
+ }
50
+ /**
51
+ * Save cache to disk
52
+ */
53
+ async function saveCache(cachePath) {
54
+ try {
55
+ const dir = dirname(cachePath);
56
+ if (!existsSync(dir)) {
57
+ await mkdir(dir, { recursive: true });
58
+ }
59
+ const data = {
60
+ entries: Object.fromEntries(cache.entries),
61
+ version: 1,
62
+ };
63
+ await writeFile(cachePath, JSON.stringify(data, null, 2));
64
+ }
65
+ catch (error) {
66
+ console.warn(`⚠️ Warning: Failed to save checksum cache: ${error instanceof Error ? error.message : 'Unknown error'}`);
67
+ }
68
+ }
69
+ /**
70
+ * Calculate approximate size of cache entry in bytes
71
+ */
72
+ function calculateEntrySize(entry) {
73
+ return (entry.path.length +
74
+ 8 + // size (number)
75
+ 8 + // mtime (number)
76
+ (entry.checksums.crc32?.length ?? 0) +
77
+ (entry.checksums.md5?.length ?? 0) +
78
+ (entry.checksums.sha1?.length ?? 0) +
79
+ 100 // overhead
80
+ );
81
+ }
82
+ /**
83
+ * Evict oldest entries to stay under max size (LRU)
84
+ */
85
+ function evictOldEntries(maxBytes) {
86
+ const entriesArray = Array.from(cache.entries.entries());
87
+ let currentSize = entriesArray.reduce((sum, [, entry]) => sum + calculateEntrySize(entry), 0);
88
+ // Sort by mtime (oldest first)
89
+ entriesArray.sort((a, b) => a[1].mtime - b[1].mtime);
90
+ for (const [key, entry] of entriesArray) {
91
+ if (currentSize <= maxBytes)
92
+ break;
93
+ const entrySize = calculateEntrySize(entry);
94
+ cache.entries.delete(key);
95
+ currentSize -= entrySize;
96
+ }
97
+ }
98
+ /**
99
+ * Get cached checksum for a file if valid
100
+ * Validation: absolute path + file size + modification time
101
+ */
102
+ export async function getCachedChecksum(filePath, size, mtime, options) {
103
+ if (!options.enabled)
104
+ return null;
105
+ const cachePath = options.cachePath ?? getDefaultCachePath();
106
+ await loadCache(cachePath);
107
+ const entry = cache.entries.get(filePath);
108
+ if (!entry)
109
+ return null;
110
+ // Validate cache entry
111
+ if (entry.size !== size || entry.mtime !== mtime) {
112
+ // Cache invalid - file changed
113
+ cache.entries.delete(filePath);
114
+ return null;
115
+ }
116
+ return entry.checksums;
117
+ }
118
+ /**
119
+ * Store checksum in cache
120
+ */
121
+ export async function setCachedChecksum(entry, options) {
122
+ if (!options.enabled)
123
+ return;
124
+ const cachePath = options.cachePath ?? getDefaultCachePath();
125
+ await loadCache(cachePath);
126
+ // Add new entry
127
+ cache.entries.set(entry.path, entry);
128
+ // Evict old entries if over size limit
129
+ const maxBytes = options.maxMb * 1024 * 1024;
130
+ evictOldEntries(maxBytes);
131
+ // Save to disk
132
+ await saveCache(cachePath);
133
+ }
134
+ //# sourceMappingURL=checksum-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checksum-cache.js","sourceRoot":"","sources":["../../src/cache/checksum-cache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAa/B,kBAAkB;AAClB,MAAM,KAAK,GAAc;IACvB,OAAO,EAAE,IAAI,GAAG,EAAE;IAClB,SAAS,EAAE,CAAC;CACb,CAAC;AAEF,IAAI,WAAW,GAAG,KAAK,CAAC;AAExB;;GAEG;AACH,SAAS,mBAAmB;IAC1B,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAChD,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,GAAG,YAAY,oCAAoC,CAAC;IAC7D,CAAC;IACD,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,2CAA2C,CAAC;AAC/E,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,SAAS,CAAC,SAAiB;IACxC,IAAI,WAAW;QAAE,OAAO;IAExB,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,WAAW,GAAG,IAAI,CAAC;YACnB,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEhC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC1D,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAA2B,CAAC,CAAC;YACtD,CAAC;QACH,CAAC;QAED,WAAW,GAAG,IAAI,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;QACnC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACtB,WAAW,GAAG,IAAI,CAAC;IACrB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,SAAS,CAAC,SAAiB;IACxC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;QAC/B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACxC,CAAC;QAED,MAAM,IAAI,GAAG;YACX,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC;YAC1C,OAAO,EAAE,CAAC;SACX,CAAC;QAEF,MAAM,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,+CAA+C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;IAC1H,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,KAAyB;IACnD,OAAO,CACL,KAAK,CAAC,IAAI,CAAC,MAAM;QACjB,CAAC,GAAG,gBAAgB;QACpB,CAAC,GAAG,iBAAiB;QACrB,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,CAAC;QACpC,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,IAAI,CAAC,CAAC;QAClC,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC;QACnC,GAAG,CAAC,WAAW;KAChB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,QAAgB;IACvC,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACzD,IAAI,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAE9F,+BAA+B;IAC/B,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAErD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,YAAY,EAAE,CAAC;QACxC,IAAI,WAAW,IAAI,QAAQ;YAAE,MAAM;QAEnC,MAAM,SAAS,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC5C,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1B,WAAW,IAAI,SAAS,CAAC;IAC3B,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,IAAY,EACZ,KAAa,EACb,OAA6B;IAE7B,IAAI,CAAC,OAAO,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAElC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,mBAAmB,EAAE,CAAC;IAC7D,MAAM,SAAS,CAAC,SAAS,CAAC,CAAC;IAE3B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAE1C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,uBAAuB;IACvB,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QACjD,+BAA+B;QAC/B,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC,SAAS,CAAC;AACzB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAyB,EACzB,OAA6B;IAE7B,IAAI,CAAC,OAAO,CAAC,OAAO;QAAE,OAAO;IAE7B,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,mBAAmB,EAAE,CAAC;IAC7D,MAAM,SAAS,CAAC,SAAS,CAAC,CAAC;IAE3B,gBAAgB;IAChB,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAErC,uCAAuC;IACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC;IAC7C,eAAe,CAAC,QAAQ,CAAC,CAAC;IAE1B,eAAe;IACf,MAAM,SAAS,CAAC,SAAS,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Checksum computation module
3
+ * Streaming hash computation for sha1, md5, crc32
4
+ */
5
+ import { ChecksumData } from '../types.js';
6
+ /**
7
+ * Compute checksums for a file using streaming to avoid high memory usage
8
+ * Always computes: sha1, md5, crc32
9
+ * Optionally computes additional algorithms specified in options
10
+ */
11
+ export declare function computeChecksums(filePath: string, _extraAlgorithms: string[]): Promise<ChecksumData>;
12
+ /**
13
+ * Compute checksums for multiple files
14
+ */
15
+ export declare function computeChecksumsForFiles(filePaths: string[], onProgress?: (filePath: string, checksums: ChecksumData) => void): Promise<Map<string, ChecksumData>>;
16
+ //# sourceMappingURL=compute-checksum.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compute-checksum.d.ts","sourceRoot":"","sources":["../../src/checksum/compute-checksum.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsB3C;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,MAAM,EAAE,GACzB,OAAO,CAAC,YAAY,CAAC,CA+BvB;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EAAE,EACnB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,KAAK,IAAI,GAC/D,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAcpC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Checksum computation module
3
+ * Streaming hash computation for sha1, md5, crc32
4
+ */
5
+ import { createHash } from 'crypto';
6
+ import { createReadStream } from 'fs';
7
+ /**
8
+ * Calculate CRC32 checksum
9
+ * Using a simple implementation (could be optimized with a lookup table)
10
+ */
11
+ function calculateCrc32(buffer) {
12
+ let crc = 0xffffffff;
13
+ for (let i = 0; i < buffer.length; i++) {
14
+ crc ^= buffer[i];
15
+ for (let j = 0; j < 8; j++) {
16
+ crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
17
+ }
18
+ }
19
+ crc = (crc ^ 0xffffffff) >>> 0;
20
+ return crc.toString(16).padStart(8, '0').toUpperCase();
21
+ }
22
+ /**
23
+ * Compute checksums for a file using streaming to avoid high memory usage
24
+ * Always computes: sha1, md5, crc32
25
+ * Optionally computes additional algorithms specified in options
26
+ */
27
+ export async function computeChecksums(filePath, _extraAlgorithms) {
28
+ return new Promise((resolve, reject) => {
29
+ const md5Hash = createHash('md5');
30
+ const sha1Hash = createHash('sha1');
31
+ const crcChunks = [];
32
+ const stream = createReadStream(filePath);
33
+ stream.on('data', (chunk) => {
34
+ const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
35
+ md5Hash.update(bufferChunk);
36
+ sha1Hash.update(bufferChunk);
37
+ crcChunks.push(bufferChunk);
38
+ });
39
+ stream.on('end', () => {
40
+ // Combine all chunks for CRC32 (could be optimized for very large files)
41
+ const combinedBuffer = Buffer.concat(crcChunks);
42
+ const crc32 = calculateCrc32(combinedBuffer);
43
+ resolve({
44
+ crc32,
45
+ md5: md5Hash.digest('hex'),
46
+ sha1: sha1Hash.digest('hex'),
47
+ });
48
+ });
49
+ stream.on('error', (error) => {
50
+ reject(new Error(`Failed to compute checksums for ${filePath}: ${error.message}`));
51
+ });
52
+ });
53
+ }
54
+ /**
55
+ * Compute checksums for multiple files
56
+ */
57
+ export async function computeChecksumsForFiles(filePaths, onProgress) {
58
+ const results = new Map();
59
+ for (const filePath of filePaths) {
60
+ try {
61
+ const checksums = await computeChecksums(filePath, []);
62
+ results.set(filePath, checksums);
63
+ onProgress?.(filePath, checksums);
64
+ }
65
+ catch (error) {
66
+ console.error(`❌ Error computing checksums for ${filePath}:`, error instanceof Error ? error.message : error);
67
+ }
68
+ }
69
+ return results;
70
+ }
71
+ //# sourceMappingURL=compute-checksum.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compute-checksum.js","sourceRoot":"","sources":["../../src/checksum/compute-checksum.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,IAAI,CAAC;AAEtC;;;GAGG;AACH,SAAS,cAAc,CAAC,MAAc;IACpC,IAAI,GAAG,GAAG,UAAU,CAAC;IAErB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;QACjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3B,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IAED,GAAG,GAAG,CAAC,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC/B,OAAO,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAAgB,EAChB,gBAA0B;IAE1B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QAClC,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QACpC,MAAM,SAAS,GAAa,EAAE,CAAC;QAE/B,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAE1C,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAsB,EAAE,EAAE;YAC3C,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACxE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;YAC5B,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;YAC7B,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACpB,yEAAyE;YACzE,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAChD,MAAM,KAAK,GAAG,cAAc,CAAC,cAAc,CAAC,CAAC;YAE7C,OAAO,CAAC;gBACN,KAAK;gBACL,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC1B,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC;aAC7B,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC3B,MAAM,CAAC,IAAI,KAAK,CAAC,mCAAmC,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACrF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,SAAmB,EACnB,UAAgE;IAEhE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAwB,CAAC;IAEhD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;YACjC,UAAU,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,mCAAmC,QAAQ,GAAG,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAChH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * RomM Uploader CLI
4
+ * Linux-first TypeScript CLI for uploading ROMs to RomM
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;GAGG"}