@willwade/aac-processors 0.1.20 → 0.2.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 (113) hide show
  1. package/dist/browser/core/baseProcessor.js +4 -0
  2. package/dist/browser/processors/applePanelsProcessor.js +33 -40
  3. package/dist/browser/processors/astericsGridProcessor.js +31 -26
  4. package/dist/browser/processors/dotProcessor.js +11 -12
  5. package/dist/browser/processors/gridset/colorUtils.js +354 -0
  6. package/dist/browser/processors/gridset/helpers.js +60 -53
  7. package/dist/browser/processors/gridset/index.js +61 -0
  8. package/dist/browser/processors/gridset/styleHelpers.js +205 -0
  9. package/dist/browser/processors/gridset/symbolExtractor.js +331 -0
  10. package/dist/browser/processors/gridset/symbolSearch.js +248 -0
  11. package/dist/browser/processors/gridset/symbols.js +39 -72
  12. package/dist/browser/processors/gridsetProcessor.js +39 -48
  13. package/dist/browser/processors/obfProcessor.js +39 -53
  14. package/dist/browser/processors/opmlProcessor.js +11 -12
  15. package/dist/browser/processors/snap/helpers.js +57 -49
  16. package/dist/browser/processors/snapProcessor.js +48 -51
  17. package/dist/browser/processors/touchchatProcessor.js +60 -52
  18. package/dist/browser/utilities/analytics/history.js +24 -18
  19. package/dist/browser/utilities/analytics/metrics/comparison.js +16 -16
  20. package/dist/browser/utilities/analytics/metrics/vocabulary.js +2 -2
  21. package/dist/browser/utilities/analytics/reference/browser.js +16 -16
  22. package/dist/browser/utilities/analytics/reference/index.js +44 -35
  23. package/dist/browser/utils/io.js +78 -21
  24. package/dist/browser/utils/sqlite.js +8 -10
  25. package/dist/browser/utils/zip.js +43 -43
  26. package/dist/browser/validation/baseValidator.js +5 -0
  27. package/dist/browser/validation/gridsetValidator.js +12 -20
  28. package/dist/browser/validation/obfValidator.js +6 -5
  29. package/dist/browser/validation/snapValidator.js +11 -7
  30. package/dist/browser/validation/touchChatValidator.js +23 -13
  31. package/dist/cli/index.js +22 -24
  32. package/dist/core/baseProcessor.d.ts +7 -7
  33. package/dist/core/baseProcessor.js +4 -0
  34. package/dist/processors/applePanelsProcessor.js +32 -39
  35. package/dist/processors/astericsGridProcessor.d.ts +4 -4
  36. package/dist/processors/astericsGridProcessor.js +30 -25
  37. package/dist/processors/dotProcessor.js +10 -11
  38. package/dist/processors/excelProcessor.d.ts +3 -3
  39. package/dist/processors/excelProcessor.js +14 -20
  40. package/dist/processors/gridset/helpers.d.ts +12 -14
  41. package/dist/processors/gridset/helpers.js +60 -79
  42. package/dist/processors/gridset/imageDebug.d.ts +3 -5
  43. package/dist/processors/gridset/imageDebug.js +4 -4
  44. package/dist/processors/gridset/password.d.ts +1 -1
  45. package/dist/processors/gridset/symbolExtractor.d.ts +5 -3
  46. package/dist/processors/gridset/symbolExtractor.js +15 -38
  47. package/dist/processors/gridset/symbolSearch.d.ts +11 -10
  48. package/dist/processors/gridset/symbolSearch.js +29 -51
  49. package/dist/processors/gridset/symbols.d.ts +8 -6
  50. package/dist/processors/gridset/symbols.js +38 -71
  51. package/dist/processors/gridset/wordlistHelpers.d.ts +4 -6
  52. package/dist/processors/gridset/wordlistHelpers.js +15 -74
  53. package/dist/processors/gridsetProcessor.d.ts +2 -2
  54. package/dist/processors/gridsetProcessor.js +38 -70
  55. package/dist/processors/obfProcessor.d.ts +2 -2
  56. package/dist/processors/obfProcessor.js +38 -75
  57. package/dist/processors/obfsetProcessor.js +2 -3
  58. package/dist/processors/opmlProcessor.js +10 -11
  59. package/dist/processors/snap/helpers.d.ts +9 -9
  60. package/dist/processors/snap/helpers.js +58 -76
  61. package/dist/processors/snapProcessor.d.ts +2 -2
  62. package/dist/processors/snapProcessor.js +47 -50
  63. package/dist/processors/touchchatProcessor.d.ts +2 -2
  64. package/dist/processors/touchchatProcessor.js +59 -51
  65. package/dist/types/aac.d.ts +2 -2
  66. package/dist/utilities/analytics/history.d.ts +8 -8
  67. package/dist/utilities/analytics/history.js +24 -18
  68. package/dist/utilities/analytics/index.d.ts +3 -2
  69. package/dist/utilities/analytics/index.js +9 -10
  70. package/dist/utilities/analytics/metrics/comparison.d.ts +1 -1
  71. package/dist/utilities/analytics/metrics/comparison.js +16 -16
  72. package/dist/utilities/analytics/metrics/vocabulary.d.ts +1 -1
  73. package/dist/utilities/analytics/metrics/vocabulary.js +2 -2
  74. package/dist/utilities/analytics/reference/browser.d.ts +9 -9
  75. package/dist/utilities/analytics/reference/browser.js +16 -16
  76. package/dist/utilities/analytics/reference/index.d.ts +25 -23
  77. package/dist/utilities/analytics/reference/index.js +43 -34
  78. package/dist/utilities/symbolTools.d.ts +8 -6
  79. package/dist/utilities/symbolTools.js +21 -18
  80. package/dist/utils/io.d.ts +24 -6
  81. package/dist/utils/io.js +79 -25
  82. package/dist/utils/sqlite.d.ts +3 -1
  83. package/dist/utils/sqlite.js +7 -9
  84. package/dist/utils/zip.d.ts +7 -3
  85. package/dist/utils/zip.js +43 -43
  86. package/dist/validation/applePanelsValidator.d.ts +2 -1
  87. package/dist/validation/applePanelsValidator.js +10 -11
  88. package/dist/validation/astericsValidator.d.ts +2 -1
  89. package/dist/validation/astericsValidator.js +5 -4
  90. package/dist/validation/baseValidator.d.ts +2 -2
  91. package/dist/validation/baseValidator.js +5 -0
  92. package/dist/validation/dotValidator.d.ts +2 -1
  93. package/dist/validation/dotValidator.js +5 -4
  94. package/dist/validation/excelValidator.d.ts +2 -1
  95. package/dist/validation/excelValidator.js +5 -4
  96. package/dist/validation/gridsetValidator.d.ts +2 -1
  97. package/dist/validation/gridsetValidator.js +11 -22
  98. package/dist/validation/index.d.ts +2 -2
  99. package/dist/validation/index.js +5 -4
  100. package/dist/validation/obfValidator.d.ts +2 -1
  101. package/dist/validation/obfValidator.js +5 -4
  102. package/dist/validation/obfsetValidator.d.ts +2 -1
  103. package/dist/validation/obfsetValidator.js +5 -4
  104. package/dist/validation/opmlValidator.d.ts +2 -1
  105. package/dist/validation/opmlValidator.js +5 -4
  106. package/dist/validation/snapValidator.d.ts +2 -1
  107. package/dist/validation/snapValidator.js +10 -6
  108. package/dist/validation/touchChatValidator.d.ts +4 -6
  109. package/dist/validation/touchChatValidator.js +22 -12
  110. package/dist/validation/validationTypes.d.ts +8 -1
  111. package/package.json +1 -1
  112. package/dist/core/fileProcessor.d.ts +0 -7
  113. package/dist/core/fileProcessor.js +0 -57
@@ -13,12 +13,12 @@ export class VocabularyAnalyzer {
13
13
  /**
14
14
  * Analyze vocabulary coverage against core lists
15
15
  */
16
- analyze(metrics, options) {
16
+ async analyze(metrics, options) {
17
17
  // const locale = options?.locale || metrics.locale || 'en';
18
18
  const highEffortThreshold = options?.highEffortThreshold || 5.0;
19
19
  const lowEffortThreshold = options?.lowEffortThreshold || 2.0;
20
20
  // Load reference data
21
- const coreLists = this.referenceLoader.loadCoreLists();
21
+ const coreLists = await this.referenceLoader.loadCoreLists();
22
22
  // Create word to effort map (using lowercase keys for matching)
23
23
  const wordEffortMap = new Map();
24
24
  metrics.buttons.forEach((btn) => {
@@ -5,34 +5,34 @@ export class InMemoryReferenceLoader {
5
5
  constructor(data) {
6
6
  this.data = data;
7
7
  }
8
- loadCoreLists() {
9
- return this.data.coreLists;
8
+ async loadCoreLists() {
9
+ return Promise.resolve(this.data.coreLists);
10
10
  }
11
- loadCommonWords() {
12
- return this.data.commonWords;
11
+ async loadCommonWords() {
12
+ return Promise.resolve(this.data.commonWords);
13
13
  }
14
- loadSynonyms() {
15
- return this.data.synonyms;
14
+ async loadSynonyms() {
15
+ return Promise.resolve(this.data.synonyms);
16
16
  }
17
- loadSentences() {
18
- return this.data.sentences;
17
+ async loadSentences() {
18
+ return Promise.resolve(this.data.sentences);
19
19
  }
20
- loadFringe() {
21
- return this.data.fringe;
20
+ async loadFringe() {
21
+ return Promise.resolve(this.data.fringe);
22
22
  }
23
- loadBaseWords() {
24
- return this.data.baseWords;
23
+ async loadBaseWords() {
24
+ return Promise.resolve(this.data.baseWords);
25
25
  }
26
- loadCommonFringe() {
26
+ async loadCommonFringe() {
27
27
  const commonWords = new Set(this.data.commonWords.words.map((w) => w.toLowerCase()));
28
28
  const coreWords = new Set();
29
29
  this.data.coreLists.forEach((list) => {
30
30
  list.words.forEach((word) => coreWords.add(word.toLowerCase()));
31
31
  });
32
- return Array.from(commonWords).filter((word) => !coreWords.has(word));
32
+ return Promise.resolve(Array.from(commonWords).filter((word) => !coreWords.has(word)));
33
33
  }
34
- loadAll() {
35
- return this.data;
34
+ async loadAll() {
35
+ return Promise.resolve(this.data);
36
36
  }
37
37
  }
38
38
  export async function loadReferenceDataFromUrl(baseUrl, locale = 'en') {
@@ -4,57 +4,63 @@
4
4
  * Loads reference vocabulary lists, core lists, and sentences
5
5
  * for AAC metrics analysis.
6
6
  */
7
- import { getFs, getPath } from '../../../utils/io';
7
+ import { defaultFileAdapter } from '../../../utils/io';
8
8
  export class ReferenceLoader {
9
- constructor(dataDir, locale = 'en') {
9
+ constructor(dataDir, locale = 'en', fileAdapter = defaultFileAdapter) {
10
10
  this.locale = locale;
11
+ this.fileAdapter = fileAdapter;
11
12
  if (dataDir) {
12
13
  this.dataDir = dataDir;
13
14
  }
14
15
  else {
15
16
  // Resolve the data directory relative to this file's location
16
17
  // Use __dirname which works correctly after compilation
17
- this.dataDir = getPath().join(__dirname, 'data');
18
+ this.dataDir = this.fileAdapter.join(__dirname, 'data');
18
19
  }
19
20
  }
20
21
  /**
21
22
  * Load core vocabulary lists
22
23
  */
23
- loadCoreLists() {
24
- const filePath = getPath().join(this.dataDir, `core_lists.${this.locale}.json`);
25
- const content = getFs().readFileSync(filePath, 'utf-8');
24
+ async loadCoreLists() {
25
+ const { readTextFromInput } = this.fileAdapter;
26
+ const filePath = this.fileAdapter.join(this.dataDir, `core_lists.${this.locale}.json`);
27
+ const content = await readTextFromInput(filePath);
26
28
  return JSON.parse(String(content));
27
29
  }
28
30
  /**
29
31
  * Load common words with baseline effort scores
30
32
  */
31
- loadCommonWords() {
32
- const filePath = getPath().join(this.dataDir, `common_words.${this.locale}.json`);
33
- const content = getFs().readFileSync(filePath, 'utf-8');
33
+ async loadCommonWords() {
34
+ const { readTextFromInput, join } = this.fileAdapter;
35
+ const filePath = join(this.dataDir, `common_words.${this.locale}.json`);
36
+ const content = await readTextFromInput(filePath);
34
37
  return JSON.parse(String(content));
35
38
  }
36
39
  /**
37
40
  * Load synonym mappings
38
41
  */
39
- loadSynonyms() {
40
- const filePath = getPath().join(this.dataDir, `synonyms.${this.locale}.json`);
41
- const content = getFs().readFileSync(filePath, 'utf-8');
42
+ async loadSynonyms() {
43
+ const { readTextFromInput, join } = this.fileAdapter;
44
+ const filePath = join(this.dataDir, `synonyms.${this.locale}.json`);
45
+ const content = await readTextFromInput(filePath);
42
46
  return JSON.parse(String(content));
43
47
  }
44
48
  /**
45
49
  * Load test sentences
46
50
  */
47
- loadSentences() {
48
- const filePath = getPath().join(this.dataDir, `sentences.${this.locale}.json`);
49
- const content = getFs().readFileSync(filePath, 'utf-8');
51
+ async loadSentences() {
52
+ const { readTextFromInput, join } = this.fileAdapter;
53
+ const filePath = join(this.dataDir, `sentences.${this.locale}.json`);
54
+ const content = await readTextFromInput(filePath);
50
55
  return JSON.parse(String(content));
51
56
  }
52
57
  /**
53
58
  * Load fringe vocabulary
54
59
  */
55
- loadFringe() {
56
- const filePath = getPath().join(this.dataDir, `fringe.${this.locale}.json`);
57
- const content = getFs().readFileSync(filePath, 'utf-8');
60
+ async loadFringe() {
61
+ const { readTextFromInput, join } = this.fileAdapter;
62
+ const filePath = join(this.dataDir, `fringe.${this.locale}.json`);
63
+ const content = await readTextFromInput(filePath);
58
64
  const data = JSON.parse(String(content));
59
65
  // Flatten nested category words if needed
60
66
  if (Array.isArray(data) && data.length > 0 && data[0].categories) {
@@ -71,9 +77,10 @@ export class ReferenceLoader {
71
77
  /**
72
78
  * Load base words hash map
73
79
  */
74
- loadBaseWords() {
75
- const filePath = getPath().join(this.dataDir, `base_words.${this.locale}.json`);
76
- const content = getFs().readFileSync(filePath, 'utf-8');
80
+ async loadBaseWords() {
81
+ const { readTextFromInput, join } = this.fileAdapter;
82
+ const filePath = join(this.dataDir, `base_words.${this.locale}.json`);
83
+ const content = await readTextFromInput(filePath);
77
84
  return JSON.parse(String(content));
78
85
  }
79
86
  /**
@@ -81,10 +88,10 @@ export class ReferenceLoader {
81
88
  * Common words that are NOT in core vocabulary lists
82
89
  * (matching Ruby loader.rb:413-420)
83
90
  */
84
- loadCommonFringe() {
85
- const commonWordsData = this.loadCommonWords();
91
+ async loadCommonFringe() {
92
+ const commonWordsData = await this.loadCommonWords();
86
93
  const commonWords = new Set(commonWordsData.words.map((w) => w.toLowerCase()));
87
- const coreLists = this.loadCoreLists();
94
+ const coreLists = await this.loadCoreLists();
88
95
  const coreWords = new Set();
89
96
  coreLists.forEach((list) => {
90
97
  list.words.forEach((word) => coreWords.add(word.toLowerCase()));
@@ -96,27 +103,28 @@ export class ReferenceLoader {
96
103
  /**
97
104
  * Get all reference data at once
98
105
  */
99
- loadAll() {
106
+ async loadAll() {
100
107
  return {
101
- coreLists: this.loadCoreLists(),
102
- commonWords: this.loadCommonWords(),
103
- synonyms: this.loadSynonyms(),
104
- sentences: this.loadSentences(),
105
- fringe: this.loadFringe(),
106
- baseWords: this.loadBaseWords(),
108
+ coreLists: await this.loadCoreLists(),
109
+ commonWords: await this.loadCommonWords(),
110
+ synonyms: await this.loadSynonyms(),
111
+ sentences: await this.loadSentences(),
112
+ fringe: await this.loadFringe(),
113
+ baseWords: await this.loadBaseWords(),
107
114
  };
108
115
  }
109
116
  }
110
117
  /**
111
118
  * Get the default reference data path
112
119
  */
113
- export function getReferenceDataPath() {
114
- return String(getPath().join(__dirname, 'data'));
120
+ export function getReferenceDataPath(fileAdapter = defaultFileAdapter) {
121
+ return String(fileAdapter.join(__dirname, 'data'));
115
122
  }
116
123
  /**
117
124
  * Check if reference data files exist
118
125
  */
119
- export function hasReferenceData() {
126
+ export async function hasReferenceData(fileAdapter = defaultFileAdapter) {
127
+ const { pathExists, join } = fileAdapter;
120
128
  const dataPath = getReferenceDataPath();
121
129
  const requiredFiles = [
122
130
  'core_lists.en.json',
@@ -125,5 +133,6 @@ export function hasReferenceData() {
125
133
  'synonyms.en.json',
126
134
  'fringe.en.json',
127
135
  ];
128
- return requiredFiles.every((file) => getFs().existsSync(getPath().join(dataPath, file)));
136
+ const existingPaths = await Promise.all(requiredFiles.map(async (file) => await pathExists(join(dataPath, file))));
137
+ return existingPaths.every((exists) => exists);
129
138
  }
@@ -20,11 +20,11 @@ export function getNodeRequire() {
20
20
  }
21
21
  return cachedRequire;
22
22
  }
23
- export function getFs() {
23
+ function getFs() {
24
24
  if (!cachedFs) {
25
25
  try {
26
26
  const nodeRequire = getNodeRequire();
27
- const fsModule = 'fs';
27
+ const fsModule = 'node:fs';
28
28
  cachedFs = nodeRequire(fsModule);
29
29
  }
30
30
  catch {
@@ -36,7 +36,7 @@ export function getFs() {
36
36
  }
37
37
  return cachedFs;
38
38
  }
39
- export function getPath() {
39
+ function getPath() {
40
40
  if (!cachedPath) {
41
41
  try {
42
42
  const nodeRequire = getNodeRequire();
@@ -114,37 +114,94 @@ export function encodeText(text) {
114
114
  }
115
115
  return new TextEncoder().encode(text);
116
116
  }
117
- export function readBinaryFromInput(input) {
117
+ // extname algorithm from node:path
118
+ const splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; //eslint-disable-line
119
+ const splitTailRe = /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/; //eslint-disable-line
120
+ export function extname(path) {
121
+ const tail = splitDeviceRe.exec(path)?.at(3) ?? '';
122
+ return splitTailRe.exec(tail)?.at(3) ?? '';
123
+ }
124
+ async function readBinaryFromInput(input) {
118
125
  if (typeof input === 'string') {
119
- const fs = getFs();
120
- return fs.readFileSync(input);
126
+ return Promise.resolve(getFs().readFileSync(input));
121
127
  }
122
128
  if (typeof Buffer !== 'undefined' && Buffer.isBuffer(input)) {
123
- return input;
129
+ return Promise.resolve(input);
124
130
  }
125
131
  if (input instanceof ArrayBuffer) {
126
- return new Uint8Array(input);
132
+ return Promise.resolve(new Uint8Array(input));
127
133
  }
128
- return input;
134
+ return Promise.resolve(input);
129
135
  }
130
- export function readTextFromInput(input, encoding = 'utf8') {
136
+ async function readTextFromInput(input, encoding = 'utf8') {
131
137
  if (typeof input === 'string') {
132
- const fs = getFs();
133
- return fs.readFileSync(input, encoding);
138
+ return Promise.resolve(getFs().readFileSync(input, encoding));
134
139
  }
135
140
  if (typeof Buffer !== 'undefined' && Buffer.isBuffer(input)) {
136
- return input.toString(encoding);
141
+ return Promise.resolve(input.toString(encoding));
137
142
  }
138
143
  if (input instanceof ArrayBuffer) {
139
- return decodeText(new Uint8Array(input));
144
+ return Promise.resolve(decodeText(new Uint8Array(input)));
140
145
  }
141
- return decodeText(input);
146
+ return Promise.resolve(decodeText(input));
147
+ }
148
+ async function writeBinaryToPath(outputPath, data) {
149
+ getFs().writeFileSync(outputPath, data);
150
+ await Promise.resolve();
151
+ }
152
+ async function writeTextToPath(outputPath, text) {
153
+ getFs().writeFileSync(outputPath, text, 'utf8');
154
+ await Promise.resolve();
155
+ }
156
+ async function pathExists(path) {
157
+ return Promise.resolve(getFs().existsSync(path));
158
+ }
159
+ async function isDirectory(path) {
160
+ return Promise.resolve(getFs().statSync(path).isDirectory());
161
+ }
162
+ async function getFileSize(path) {
163
+ return Promise.resolve(getFs().statSync(path).size);
164
+ }
165
+ async function mkDir(path, options) {
166
+ getFs().mkdirSync(path, options);
167
+ await Promise.resolve();
168
+ }
169
+ async function listDir(path) {
170
+ return Promise.resolve(getFs().readdirSync(path));
171
+ }
172
+ async function removePath(path, options) {
173
+ getFs().rmSync(path, options);
174
+ await Promise.resolve();
175
+ }
176
+ async function mkTempDir(prefix) {
177
+ const path = join(getOs().tmpdir(), prefix);
178
+ return Promise.resolve(getFs().mkdtempSync(path));
179
+ }
180
+ function join(...pathParts) {
181
+ return getPath().join(...pathParts);
182
+ }
183
+ export function joinWin32(...pathParts) {
184
+ return getPath().win32.join(...pathParts);
142
185
  }
143
- export function writeBinaryToPath(outputPath, data) {
144
- const fs = getFs();
145
- fs.writeFileSync(outputPath, data);
186
+ function dirname(path) {
187
+ return getPath().dirname(path);
146
188
  }
147
- export function writeTextToPath(outputPath, text) {
148
- const fs = getFs();
149
- fs.writeFileSync(outputPath, text, 'utf8');
189
+ function basename(path, suffix) {
190
+ return getPath().basename(path, suffix);
150
191
  }
192
+ export const defaultFileAdapter = {
193
+ readBinaryFromInput,
194
+ readTextFromInput,
195
+ writeBinaryToPath,
196
+ writeTextToPath,
197
+ pathExists,
198
+ isDirectory,
199
+ getFileSize,
200
+ mkDir,
201
+ listDir,
202
+ removePath,
203
+ mkTempDir,
204
+ join,
205
+ dirname,
206
+ basename,
207
+ };
@@ -1,4 +1,4 @@
1
- import { getFs, getNodeRequire, getOs, getPath, isNodeRuntime, readBinaryFromInput } from './io';
1
+ import { defaultFileAdapter, getNodeRequire, isNodeRuntime } from './io';
2
2
  let sqlJsConfig = null;
3
3
  let sqlJsPromise = null;
4
4
  export function configureSqlJs(config) {
@@ -70,6 +70,7 @@ export function requireBetterSqlite3() {
70
70
  return getBetterSqlite3();
71
71
  }
72
72
  export async function openSqliteDatabase(input, options = {}) {
73
+ const { readBinaryFromInput, mkTempDir, writeBinaryToPath, removePath, join } = options.fileAdapter ?? defaultFileAdapter;
73
74
  if (typeof input === 'string') {
74
75
  if (!isNodeRuntime()) {
75
76
  throw new Error('SQLite file paths are not supported in browser environments.');
@@ -78,27 +79,24 @@ export async function openSqliteDatabase(input, options = {}) {
78
79
  const db = new Database(input, { readonly: options.readonly ?? true });
79
80
  return { db };
80
81
  }
81
- const data = readBinaryFromInput(input);
82
+ const data = await readBinaryFromInput(input);
82
83
  if (!isNodeRuntime()) {
83
84
  const SQL = await getSqlJs();
84
85
  const db = new SQL.Database(data);
85
86
  return { db: createSqlJsAdapter(db) };
86
87
  }
87
- const fs = getFs();
88
- const path = getPath();
89
- const os = getOs();
90
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aac-sqlite-'));
91
- const dbPath = path.join(tempDir, 'input.sqlite');
92
- fs.writeFileSync(dbPath, data);
88
+ const tempDir = await mkTempDir('aac-sqlite-');
89
+ const dbPath = join(tempDir, 'input.sqlite');
90
+ await writeBinaryToPath(dbPath, data);
93
91
  const Database = getBetterSqlite3();
94
92
  const db = new Database(dbPath, { readonly: options.readonly ?? true });
95
- const cleanup = () => {
93
+ const cleanup = async () => {
96
94
  try {
97
95
  db.close();
98
96
  }
99
97
  finally {
100
98
  try {
101
- fs.rmSync(tempDir, { recursive: true, force: true });
99
+ await removePath(tempDir, { recursive: true, force: true });
102
100
  }
103
101
  catch (error) {
104
102
  console.warn('Failed to clean up temporary SQLite files:', error);
@@ -1,54 +1,54 @@
1
- import { isNodeRuntime, readBinaryFromInput, getNodeRequire } from './io';
2
- export async function openZipFromInput(input) {
3
- if (typeof input === 'string') {
4
- if (!isNodeRuntime()) {
5
- throw new Error('Zip file paths are not supported in browser environments.');
6
- }
7
- const AdmZip = getNodeRequire()('adm-zip');
8
- const admZip = new AdmZip(input);
9
- return {
10
- zip: {
11
- listFiles: () => admZip.getEntries().map((entry) => entry.entryName),
12
- readFile: (name) => {
13
- const entry = admZip.getEntry(name);
14
- if (!entry) {
15
- throw new Error(`Zip entry not found: ${name}`);
16
- }
17
- return Promise.resolve(entry.getData());
18
- },
19
- },
20
- };
21
- }
22
- const data = readBinaryFromInput(input);
1
+ import { isNodeRuntime, getNodeRequire, defaultFileAdapter, } from './io';
2
+ export async function getZipAdapter(input, fileAdapter) {
3
+ const adapter = fileAdapter ?? defaultFileAdapter;
23
4
  if (isNodeRuntime()) {
24
5
  const AdmZip = getNodeRequire()('adm-zip');
25
- const admZip = new AdmZip(Buffer.from(data));
6
+ const zip = input === undefined
7
+ ? new AdmZip(input)
8
+ : typeof input === 'string'
9
+ ? new AdmZip(input)
10
+ : new AdmZip(Buffer.from(await adapter.readBinaryFromInput(input)));
26
11
  return {
27
- zip: {
28
- listFiles: () => admZip.getEntries().map((entry) => entry.entryName),
29
- readFile: (name) => {
30
- const entry = admZip.getEntry(name);
31
- if (!entry) {
32
- throw new Error(`Zip entry not found: ${name}`);
33
- }
34
- return Promise.resolve(entry.getData());
35
- },
12
+ listFiles: () => {
13
+ return zip
14
+ .getEntries()
15
+ .filter((entry) => !entry.isDirectory)
16
+ .map((entry) => entry.entryName);
17
+ },
18
+ readFile: (name) => {
19
+ const entry = zip.getEntry(name);
20
+ if (!entry)
21
+ throw new Error(`Zip entry not found: ${name}`);
22
+ return Promise.resolve(entry.getData());
23
+ },
24
+ writeFiles: (files) => {
25
+ files.forEach((file) => {
26
+ zip.addFile(file.name, Buffer.from(file.data));
27
+ });
28
+ return Promise.resolve(zip.toBuffer());
36
29
  },
37
30
  };
38
31
  }
39
32
  const module = await import('jszip');
40
- const init = module.default || module;
41
- const zip = await init.loadAsync(data);
33
+ const JSZip = module.default || module;
34
+ const zip = input ? await JSZip.loadAsync(await adapter.readBinaryFromInput(input)) : new JSZip();
42
35
  return {
43
- zip: {
44
- listFiles: () => Object.keys(zip.files),
45
- readFile: async (name) => {
46
- const file = zip.file(name);
47
- if (!file) {
48
- throw new Error(`Zip entry not found: ${name}`);
49
- }
50
- return file.async('uint8array');
51
- },
36
+ listFiles: () => {
37
+ return Object.entries(zip.files)
38
+ .filter(([_, entry]) => !entry.dir)
39
+ .map(([name, _]) => name);
40
+ },
41
+ readFile: async (name) => {
42
+ const file = zip.file(name);
43
+ if (!file)
44
+ throw new Error(`Zip entry not found: ${name}`);
45
+ return file.async('uint8array');
46
+ },
47
+ writeFiles: async (files) => {
48
+ files.forEach((file) => {
49
+ zip.file(file.name, file.data);
50
+ });
51
+ return await zip.generateAsync({ type: 'uint8array' });
52
52
  },
53
53
  };
54
54
  }
@@ -1,3 +1,5 @@
1
+ import { defaultFileAdapter } from '../utils/io';
2
+ import { getZipAdapter } from '../utils/zip';
1
3
  import { ValidationError, } from './validationTypes';
2
4
  /**
3
5
  * Base class for all format validators
@@ -14,6 +16,9 @@ export class BaseValidator {
14
16
  includeWarnings: options.includeWarnings ?? true,
15
17
  stopOnBlocker: options.stopOnBlocker ?? true,
16
18
  customRules: options.customRules || [],
19
+ fileAdapter: defaultFileAdapter,
20
+ zipAdapter: getZipAdapter,
21
+ ...options,
17
22
  };
18
23
  this.reset();
19
24
  }
@@ -1,10 +1,9 @@
1
1
  /* eslint-disable @typescript-eslint/require-await */
2
2
  /* eslint-disable @typescript-eslint/no-unsafe-argument */
3
3
  /* eslint-disable @typescript-eslint/no-unsafe-return */
4
- import JSZip from 'jszip';
5
4
  import * as xml2js from 'xml2js';
6
5
  import { BaseValidator } from './baseValidator';
7
- import { decodeText, getBasename, getFs, toUint8Array } from '../utils/io';
6
+ import { decodeText, defaultFileAdapter, getBasename, toUint8Array, } from '../utils/io';
8
7
  /**
9
8
  * Validator for Grid3/Smartbox Gridset files (.gridset, .gridsetx)
10
9
  */
@@ -15,12 +14,12 @@ export class GridsetValidator extends BaseValidator {
15
14
  /**
16
15
  * Validate a Gridset file from disk
17
16
  */
18
- static async validateFile(filePath) {
17
+ static async validateFile(filePath, fileAdapter) {
18
+ const { readBinaryFromInput, getFileSize } = fileAdapter ?? defaultFileAdapter;
19
19
  const validator = new GridsetValidator();
20
- const fs = getFs();
21
- const content = fs.readFileSync(filePath);
22
- const stats = fs.statSync(filePath);
23
- return validator.validate(content, getBasename(filePath), stats.size);
20
+ const content = await readBinaryFromInput(filePath);
21
+ const size = await getFileSize(filePath);
22
+ return validator.validate(content, getBasename(filePath), size);
24
23
  }
25
24
  /**
26
25
  * Check if content is Gridset format
@@ -109,24 +108,17 @@ export class GridsetValidator extends BaseValidator {
109
108
  * Validate a ZIP archive (.gridset)
110
109
  */
111
110
  async validateZipArchive(content, filename, _filesize) {
112
- let zip;
113
- try {
114
- zip = await JSZip.loadAsync(toUint8Array(content));
115
- }
116
- catch (e) {
117
- this.err(`Failed to open ZIP archive: ${e.message}`, true);
118
- return;
119
- }
120
- const entries = Object.values(zip.files).filter((entry) => !entry.dir);
111
+ const zip = await this._options.zipAdapter(content);
112
+ const entries = zip.listFiles();
121
113
  // Check for gridset.xml (required)
122
114
  await this.add_check('gridset_xml_presence', 'gridset.xml presence', async () => {
123
- const gridsetEntry = entries.find((e) => e.name.toLowerCase() === 'gridset.xml');
115
+ const gridsetEntry = entries.find((e) => e.toLowerCase() === 'gridset.xml');
124
116
  if (!gridsetEntry) {
125
117
  this.err('Missing gridset.xml in archive', true);
126
118
  }
127
119
  else {
128
120
  try {
129
- const gridsetXml = await gridsetEntry.async('string');
121
+ const gridsetXml = await zip.readFile(gridsetEntry);
130
122
  const parser = new xml2js.Parser();
131
123
  const xmlObj = await parser.parseStringPromise(gridsetXml);
132
124
  const gridset = xmlObj.gridset || xmlObj.Gridset;
@@ -144,13 +136,13 @@ export class GridsetValidator extends BaseValidator {
144
136
  });
145
137
  // Check for settings.xml (highly recommended/required for metadata)
146
138
  await this.add_check('settings_xml_presence', 'settings.xml presence', async () => {
147
- const settingsEntry = entries.find((e) => e.name.toLowerCase() === 'settings.xml');
139
+ const settingsEntry = entries.find((e) => e.toLowerCase() === 'settings.xml');
148
140
  if (!settingsEntry) {
149
141
  this.warn('Missing settings.xml in archive (required for full metadata)');
150
142
  }
151
143
  else {
152
144
  try {
153
- const settingsXml = await settingsEntry.async('string');
145
+ const settingsXml = await zip.readFile(settingsEntry);
154
146
  const parser = new xml2js.Parser();
155
147
  const xmlObj = await parser.parseStringPromise(settingsXml);
156
148
  const settings = xmlObj.GridSetSettings || xmlObj.gridSetSettings || xmlObj.GridsetSettings;
@@ -5,7 +5,7 @@
5
5
  /* eslint-disable @typescript-eslint/restrict-template-expressions */
6
6
  import JSZip from 'jszip';
7
7
  import { BaseValidator } from './baseValidator';
8
- import { decodeText, getBasename, getFs, readBinaryFromInput, toUint8Array } from '../utils/io';
8
+ import { decodeText, defaultFileAdapter, getBasename, toUint8Array, } from '../utils/io';
9
9
  const OBF_FORMAT = 'open-board-0.1';
10
10
  const OBF_FORMAT_CURRENT_VERSION = 0.1;
11
11
  /**
@@ -18,11 +18,12 @@ export class ObfValidator extends BaseValidator {
18
18
  /**
19
19
  * Validate an OBF file from disk
20
20
  */
21
- static async validateFile(filePath) {
21
+ static async validateFile(filePath, fileAdapter) {
22
+ const { readBinaryFromInput, getFileSize } = fileAdapter ?? defaultFileAdapter;
22
23
  const validator = new ObfValidator();
23
- const content = readBinaryFromInput(filePath);
24
- const stats = getFs().statSync(filePath);
25
- return validator.validate(content, getBasename(filePath), stats.size);
24
+ const content = await readBinaryFromInput(filePath);
25
+ const size = await getFileSize(filePath);
26
+ return validator.validate(content, getBasename(filePath), size);
26
27
  }
27
28
  /**
28
29
  * Check if content is OBF format