@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.
- package/dist/browser/core/baseProcessor.js +4 -0
- package/dist/browser/processors/applePanelsProcessor.js +33 -40
- package/dist/browser/processors/astericsGridProcessor.js +31 -26
- package/dist/browser/processors/dotProcessor.js +11 -12
- package/dist/browser/processors/gridset/colorUtils.js +354 -0
- package/dist/browser/processors/gridset/helpers.js +60 -53
- package/dist/browser/processors/gridset/index.js +61 -0
- package/dist/browser/processors/gridset/styleHelpers.js +205 -0
- package/dist/browser/processors/gridset/symbolExtractor.js +331 -0
- package/dist/browser/processors/gridset/symbolSearch.js +248 -0
- package/dist/browser/processors/gridset/symbols.js +39 -72
- package/dist/browser/processors/gridsetProcessor.js +39 -48
- package/dist/browser/processors/obfProcessor.js +39 -53
- package/dist/browser/processors/opmlProcessor.js +11 -12
- package/dist/browser/processors/snap/helpers.js +57 -49
- package/dist/browser/processors/snapProcessor.js +48 -51
- package/dist/browser/processors/touchchatProcessor.js +60 -52
- package/dist/browser/utilities/analytics/history.js +24 -18
- package/dist/browser/utilities/analytics/metrics/comparison.js +16 -16
- package/dist/browser/utilities/analytics/metrics/vocabulary.js +2 -2
- package/dist/browser/utilities/analytics/reference/browser.js +16 -16
- package/dist/browser/utilities/analytics/reference/index.js +44 -35
- package/dist/browser/utils/io.js +78 -21
- package/dist/browser/utils/sqlite.js +8 -10
- package/dist/browser/utils/zip.js +43 -43
- package/dist/browser/validation/baseValidator.js +5 -0
- package/dist/browser/validation/gridsetValidator.js +12 -20
- package/dist/browser/validation/obfValidator.js +6 -5
- package/dist/browser/validation/snapValidator.js +11 -7
- package/dist/browser/validation/touchChatValidator.js +23 -13
- package/dist/cli/index.js +22 -24
- package/dist/core/baseProcessor.d.ts +7 -7
- package/dist/core/baseProcessor.js +4 -0
- package/dist/processors/applePanelsProcessor.js +32 -39
- package/dist/processors/astericsGridProcessor.d.ts +4 -4
- package/dist/processors/astericsGridProcessor.js +30 -25
- package/dist/processors/dotProcessor.js +10 -11
- package/dist/processors/excelProcessor.d.ts +3 -3
- package/dist/processors/excelProcessor.js +14 -20
- package/dist/processors/gridset/helpers.d.ts +12 -14
- package/dist/processors/gridset/helpers.js +60 -79
- package/dist/processors/gridset/imageDebug.d.ts +3 -5
- package/dist/processors/gridset/imageDebug.js +4 -4
- package/dist/processors/gridset/password.d.ts +1 -1
- package/dist/processors/gridset/symbolExtractor.d.ts +5 -3
- package/dist/processors/gridset/symbolExtractor.js +15 -38
- package/dist/processors/gridset/symbolSearch.d.ts +11 -10
- package/dist/processors/gridset/symbolSearch.js +29 -51
- package/dist/processors/gridset/symbols.d.ts +8 -6
- package/dist/processors/gridset/symbols.js +38 -71
- package/dist/processors/gridset/wordlistHelpers.d.ts +4 -6
- package/dist/processors/gridset/wordlistHelpers.js +15 -74
- package/dist/processors/gridsetProcessor.d.ts +2 -2
- package/dist/processors/gridsetProcessor.js +38 -70
- package/dist/processors/obfProcessor.d.ts +2 -2
- package/dist/processors/obfProcessor.js +38 -75
- package/dist/processors/obfsetProcessor.js +2 -3
- package/dist/processors/opmlProcessor.js +10 -11
- package/dist/processors/snap/helpers.d.ts +9 -9
- package/dist/processors/snap/helpers.js +58 -76
- package/dist/processors/snapProcessor.d.ts +2 -2
- package/dist/processors/snapProcessor.js +47 -50
- package/dist/processors/touchchatProcessor.d.ts +2 -2
- package/dist/processors/touchchatProcessor.js +59 -51
- package/dist/types/aac.d.ts +2 -2
- package/dist/utilities/analytics/history.d.ts +8 -8
- package/dist/utilities/analytics/history.js +24 -18
- package/dist/utilities/analytics/index.d.ts +3 -2
- package/dist/utilities/analytics/index.js +9 -10
- package/dist/utilities/analytics/metrics/comparison.d.ts +1 -1
- package/dist/utilities/analytics/metrics/comparison.js +16 -16
- package/dist/utilities/analytics/metrics/vocabulary.d.ts +1 -1
- package/dist/utilities/analytics/metrics/vocabulary.js +2 -2
- package/dist/utilities/analytics/reference/browser.d.ts +9 -9
- package/dist/utilities/analytics/reference/browser.js +16 -16
- package/dist/utilities/analytics/reference/index.d.ts +25 -23
- package/dist/utilities/analytics/reference/index.js +43 -34
- package/dist/utilities/symbolTools.d.ts +8 -6
- package/dist/utilities/symbolTools.js +21 -18
- package/dist/utils/io.d.ts +24 -6
- package/dist/utils/io.js +79 -25
- package/dist/utils/sqlite.d.ts +3 -1
- package/dist/utils/sqlite.js +7 -9
- package/dist/utils/zip.d.ts +7 -3
- package/dist/utils/zip.js +43 -43
- package/dist/validation/applePanelsValidator.d.ts +2 -1
- package/dist/validation/applePanelsValidator.js +10 -11
- package/dist/validation/astericsValidator.d.ts +2 -1
- package/dist/validation/astericsValidator.js +5 -4
- package/dist/validation/baseValidator.d.ts +2 -2
- package/dist/validation/baseValidator.js +5 -0
- package/dist/validation/dotValidator.d.ts +2 -1
- package/dist/validation/dotValidator.js +5 -4
- package/dist/validation/excelValidator.d.ts +2 -1
- package/dist/validation/excelValidator.js +5 -4
- package/dist/validation/gridsetValidator.d.ts +2 -1
- package/dist/validation/gridsetValidator.js +11 -22
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +5 -4
- package/dist/validation/obfValidator.d.ts +2 -1
- package/dist/validation/obfValidator.js +5 -4
- package/dist/validation/obfsetValidator.d.ts +2 -1
- package/dist/validation/obfsetValidator.js +5 -4
- package/dist/validation/opmlValidator.d.ts +2 -1
- package/dist/validation/opmlValidator.js +5 -4
- package/dist/validation/snapValidator.d.ts +2 -1
- package/dist/validation/snapValidator.js +10 -6
- package/dist/validation/touchChatValidator.d.ts +4 -6
- package/dist/validation/touchChatValidator.js +22 -12
- package/dist/validation/validationTypes.d.ts +8 -1
- package/package.json +1 -1
- package/dist/core/fileProcessor.d.ts +0 -7
- 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 {
|
|
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 =
|
|
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
|
|
25
|
-
const
|
|
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
|
|
33
|
-
const
|
|
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
|
|
41
|
-
const
|
|
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
|
|
49
|
-
const
|
|
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
|
|
57
|
-
const
|
|
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
|
|
76
|
-
const
|
|
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(
|
|
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
|
-
|
|
136
|
+
const existingPaths = await Promise.all(requiredFiles.map(async (file) => await pathExists(join(dataPath, file))));
|
|
137
|
+
return existingPaths.every((exists) => exists);
|
|
129
138
|
}
|
package/dist/browser/utils/io.js
CHANGED
|
@@ -20,11 +20,11 @@ export function getNodeRequire() {
|
|
|
20
20
|
}
|
|
21
21
|
return cachedRequire;
|
|
22
22
|
}
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
136
|
+
async function readTextFromInput(input, encoding = 'utf8') {
|
|
131
137
|
if (typeof input === 'string') {
|
|
132
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
fs.writeFileSync(outputPath, data);
|
|
186
|
+
function dirname(path) {
|
|
187
|
+
return getPath().dirname(path);
|
|
146
188
|
}
|
|
147
|
-
|
|
148
|
-
|
|
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 {
|
|
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
|
|
88
|
-
const
|
|
89
|
-
|
|
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
|
-
|
|
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,
|
|
2
|
-
export async function
|
|
3
|
-
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
41
|
-
const zip = await
|
|
33
|
+
const JSZip = module.default || module;
|
|
34
|
+
const zip = input ? await JSZip.loadAsync(await adapter.readBinaryFromInput(input)) : new JSZip();
|
|
42
35
|
return {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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,
|
|
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
|
|
21
|
-
const
|
|
22
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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,
|
|
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
|
|
25
|
-
return validator.validate(content, getBasename(filePath),
|
|
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
|