scrabble-solver 2.18.3 → 2.18.5
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/.nx/workspace-data/d/server-process.json +3 -3
- package/.nx/workspace-data/file-map.json +1615 -1582
- package/.nx/workspace-data/lockfile-dependencies.hash +1 -1
- package/.nx/workspace-data/lockfile-nodes.hash +1 -1
- package/.nx/workspace-data/nx_files.nxt +0 -0
- package/.nx/workspace-data/parsed-lock-file.dependencies.json +1091 -1156
- package/.nx/workspace-data/parsed-lock-file.nodes.json +2038 -2029
- package/.nx/workspace-data/project-graph.json +3290 -3338
- package/.nx/workspace-data/source-maps.json +44 -8
- package/.oxlintrc.json +0 -1
- package/CLAUDE.md +10 -8
- package/bun.lock +707 -424
- package/lerna.json +1 -1
- package/package.json +11 -11
- package/packages/configs/package.json +3 -3
- package/packages/constants/package.json +1 -1
- package/packages/dictionaries/bin/update-dictionaries.js +2 -5
- package/packages/dictionaries/build/Dictionaries.d.ts +1 -1
- package/packages/dictionaries/build/Dictionaries.js +9 -4
- package/packages/dictionaries/build/lib/DiskCache.js +1 -1
- package/packages/dictionaries/build/lib/downloadDictionary.d.ts +1 -1
- package/packages/dictionaries/build/lib/downloadDictionary.js +12 -7
- package/packages/dictionaries/package.json +5 -5
- package/packages/dictionaries/src/Dictionaries.ts +13 -6
- package/packages/dictionaries/src/lib/DiskCache.ts +2 -2
- package/packages/dictionaries/src/lib/downloadDictionary.ts +14 -6
- package/packages/logger/README.md +18 -0
- package/packages/logger/build/constants.d.ts +3 -0
- package/packages/logger/build/constants.js +4 -1
- package/packages/logger/build/describeError.d.ts +5 -0
- package/packages/logger/build/describeError.js +23 -0
- package/packages/logger/build/events.d.ts +92 -0
- package/packages/logger/build/events.js +13 -0
- package/packages/logger/build/formatCsvRow.d.ts +2 -0
- package/packages/logger/build/formatCsvRow.js +20 -0
- package/packages/logger/build/index.d.ts +5 -1
- package/packages/logger/build/index.js +13 -3
- package/packages/logger/build/logError.d.ts +4 -0
- package/packages/logger/build/logError.js +17 -0
- package/packages/logger/build/logEvent.d.ts +3 -0
- package/packages/logger/build/logEvent.js +29 -0
- package/packages/logger/package.json +7 -8
- package/packages/logger/scripts/export-csv.ts +113 -0
- package/packages/logger/scripts/readEvents.ts +34 -0
- package/packages/logger/scripts/tsconfig.json +10 -0
- package/packages/logger/src/constants.ts +9 -0
- package/packages/logger/src/describeError.test.ts +44 -0
- package/packages/logger/src/describeError.ts +30 -0
- package/packages/logger/src/events.ts +103 -0
- package/packages/logger/src/formatCsvRow.test.ts +41 -0
- package/packages/logger/src/formatCsvRow.ts +24 -0
- package/packages/logger/src/index.ts +13 -1
- package/packages/logger/src/logError.ts +24 -0
- package/packages/logger/src/logEvent.test.ts +59 -0
- package/packages/logger/src/logEvent.ts +26 -0
- package/packages/scrabble-solver/.next/BUILD_ID +1 -1
- package/packages/scrabble-solver/.next/build-manifest.json +19 -19
- package/packages/scrabble-solver/.next/cache/.previewinfo +1 -1
- package/packages/scrabble-solver/.next/cache/.rscinfo +1 -1
- package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
- package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +2 -2
- package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack.old +0 -0
- package/packages/scrabble-solver/.next/diagnostics/framework.json +1 -1
- package/packages/scrabble-solver/.next/dynamic-css-manifest.json +1 -1
- package/packages/scrabble-solver/.next/next-minimal-server.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/next-server.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/prerender-manifest.json +4 -4
- package/packages/scrabble-solver/.next/react-loadable-manifest.json +6 -6
- package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
- package/packages/scrabble-solver/.next/server/chunks/30.js +1 -1
- package/packages/scrabble-solver/.next/server/chunks/546.js +1 -1
- package/packages/scrabble-solver/.next/server/chunks/612.js +5 -0
- package/packages/scrabble-solver/.next/server/chunks/712.js +1 -1
- package/packages/scrabble-solver/.next/server/chunks/715.js +1 -0
- package/packages/scrabble-solver/.next/server/chunks/853.js +2 -2
- package/packages/scrabble-solver/.next/server/chunks/961.js +1 -1
- package/packages/scrabble-solver/.next/server/dynamic-css-manifest.js +1 -1
- package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
- package/packages/scrabble-solver/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/404.html +2 -2
- package/packages/scrabble-solver/.next/server/pages/500.html +2 -2
- package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/_document.js +2 -2
- package/packages/scrabble-solver/.next/server/pages/_document.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/_error.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/_error.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js +2 -2
- package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/solve.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/solve.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/verify.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/verify.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/visit.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/visit.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.html +2 -2
- package/packages/scrabble-solver/.next/server/pages/index.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.meta +1 -0
- package/packages/scrabble-solver/.next/server/pages/not-found.html +2 -2
- package/packages/scrabble-solver/.next/server/route-cache/PAGES/8a5edab282632443219e051e4ade2d1d5bbc671c781051bf1437897cbdfea0f1/$/index.html +29 -0
- package/packages/scrabble-solver/.next/server/route-cache/PAGES/8a5edab282632443219e051e4ade2d1d5bbc671c781051bf1437897cbdfea0f1/$/index.json +1 -0
- package/packages/scrabble-solver/.next/server/route-cache/PAGES/8a5edab282632443219e051e4ade2d1d5bbc671c781051bf1437897cbdfea0f1/$/index.meta +1 -0
- package/packages/scrabble-solver/.next/static/{8WnIUTe-yJvPD6stltvYI → YHx536pklAqlc9UGAXCj8}/_buildManifest.js +1 -1
- package/packages/scrabble-solver/.next/static/chunks/101-fee295c7587c0836.js +3 -0
- package/packages/scrabble-solver/.next/static/chunks/{256.6a20b23a766cfebd.js → 256.2a4fa7f278e0a97d.js} +1 -1
- package/packages/scrabble-solver/.next/static/chunks/693.d295da7c7b873986.js +1 -0
- package/packages/scrabble-solver/.next/static/chunks/827.09f660ca0a951d12.js +1 -0
- package/packages/scrabble-solver/.next/static/chunks/980.dd35e807b18063c1.js +1 -0
- package/packages/scrabble-solver/.next/static/chunks/framework-988fd6f19d2744e8.js +1 -0
- package/packages/scrabble-solver/.next/static/chunks/main-d397eb8af33c5163.js +5 -0
- package/packages/scrabble-solver/.next/static/chunks/pages/_app-0c68f165d6de8507.js +1 -0
- package/packages/scrabble-solver/.next/static/chunks/pages/index-ea21d96a88f25000.js +1 -0
- package/packages/scrabble-solver/.next/static/chunks/{webpack-d7138f1ef6b8c9d6.js → webpack-31879a8fb6dcffd0.js} +1 -1
- package/packages/scrabble-solver/.next/static/css/{92f85ce358a0a468.css → 4dbcacc1122ac6b9.css} +1 -1
- package/packages/scrabble-solver/.next/static/css/8b8087461767360a.css +1 -0
- package/packages/scrabble-solver/.next/static/css/{65bcc9440e067113.css → 98051ed25076b896.css} +1 -1
- package/packages/scrabble-solver/.next/trace +22 -22
- package/packages/scrabble-solver/.next/trace-build +1 -1
- package/packages/scrabble-solver/package.json +18 -18
- package/packages/scrabble-solver/public/service-worker.js +1 -1
- package/packages/scrabble-solver/src/api/BadRequestError.ts +6 -0
- package/packages/scrabble-solver/src/api/index.ts +2 -1
- package/packages/scrabble-solver/src/api/withApiLog.ts +85 -0
- package/packages/scrabble-solver/src/components/Button/Button.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +4 -4
- package/packages/scrabble-solver/src/components/Solver/Solver.tsx +2 -2
- package/packages/scrabble-solver/src/hooks/useColumns.ts +5 -7
- package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.tsx +2 -2
- package/packages/scrabble-solver/src/modals/WordsModal/WordsModal.module.scss +1 -1
- package/packages/scrabble-solver/src/modals/WordsModal/WordsModal.tsx +2 -2
- package/packages/scrabble-solver/src/modals/WordsModal/components/WordsTable/WordsTable.tsx +2 -2
- package/packages/scrabble-solver/src/pages/api/dictionary/[locale]/[word].ts +22 -30
- package/packages/scrabble-solver/src/pages/api/dictionary/[locale]/index.ts +36 -42
- package/packages/scrabble-solver/src/pages/api/solve.ts +32 -42
- package/packages/scrabble-solver/src/pages/api/verify.ts +35 -43
- package/packages/scrabble-solver/src/pages/api/visit.ts +38 -13
- package/packages/scrabble-solver/src/pages/index.tsx +2 -2
- package/packages/scrabble-solver/src/parameters/index.ts +0 -2
- package/packages/scrabble-solver/src/sdk/solve.ts +3 -9
- package/packages/scrabble-solver/src/sdk/verify.ts +2 -10
- package/packages/scrabble-solver/src/sdk/visit.ts +8 -3
- package/packages/scrabble-solver/src/state/sagas.ts +7 -1
- package/packages/scrabble-solver/src/styles/_tokens.scss +0 -2
- package/packages/scrabble-solver/src/styles/mixins.scss +0 -5
- package/packages/scrabble-solver/src/styles/variables.module.scss +0 -1
- package/packages/scrabble-solver/src/styles/variables.module.scss.d.ts +0 -1
- package/packages/scrabble-solver/src/styles/variables.scss +1 -1
- package/packages/scrabble-solver/src/types/api.ts +7 -0
- package/packages/scrabble-solver/tsconfig.tsbuildinfo +1 -1
- package/packages/solver/package.json +6 -6
- package/packages/types/package.json +2 -2
- package/packages/word-definitions/package.json +3 -3
- package/packages/word-lists/package.json +3 -3
- package/playwright-report/index.html +6 -6
- package/packages/gaddag/build/Gaddag.d.ts +0 -39
- package/packages/gaddag/build/Gaddag.js +0 -128
- package/packages/gaddag/build/buildGaddag.d.ts +0 -10
- package/packages/gaddag/build/buildGaddag.js +0 -360
- package/packages/gaddag/build/constants.d.ts +0 -12
- package/packages/gaddag/build/constants.js +0 -15
- package/packages/gaddag/build/index.d.ts +0 -3
- package/packages/gaddag/build/index.js +0 -13
- package/packages/logger/build/logger.d.ts +0 -1
- package/packages/logger/build/logger.js +0 -28
- package/packages/logger/scripts/stats.js +0 -73
- package/packages/logger/src/logger.ts +0 -30
- package/packages/scrabble-solver/.next/server/chunks/392.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/101-829ab8a8d310e7a0.js +0 -3
- package/packages/scrabble-solver/.next/static/chunks/693.677c88e0eb1340b5.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/827.c81b92b383a748c9.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/980.7ce17464fc56648a.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/framework-626cbd48ac5aca57.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/main-7e52da35a4beea0b.js +0 -5
- package/packages/scrabble-solver/.next/static/chunks/pages/_app-e3fe880e24c024ab.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/pages/index-b073a1686818dd26.js +0 -1
- package/packages/scrabble-solver/.next/static/css/a691348b788f8bc0.css +0 -1
- package/packages/scrabble-solver/src/api/getServerLoggingData.ts +0 -17
- package/packages/scrabble-solver/src/hooks/useIsCompactLayout.ts +0 -14
- /package/packages/scrabble-solver/.next/static/{8WnIUTe-yJvPD6stltvYI → YHx536pklAqlc9UGAXCj8}/_ssgManifest.js +0 -0
package/lerna.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scrabble-solver",
|
|
3
|
-
"version": "2.18.
|
|
3
|
+
"version": "2.18.5",
|
|
4
4
|
"description": "Scrabble Solver 2 - Free, open-source, cross-platform, multi-language analysis tool for Scrabble, Scrabble Duel, Super Scrabble, Letter League, Crossplay, Literaki, Kelimelik, and Wyrazy. Quickly find the top-scoring words using the given board and tiles.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"scrabble",
|
|
@@ -80,23 +80,23 @@
|
|
|
80
80
|
"packages/scrabble-solver"
|
|
81
81
|
],
|
|
82
82
|
"dependencies": {
|
|
83
|
-
"@scrabble-solver/scrabble-solver": "^2.18.
|
|
83
|
+
"@scrabble-solver/scrabble-solver": "^2.18.5",
|
|
84
84
|
"open-cli": "^9.0.0",
|
|
85
|
-
"wait-on": "^9.1
|
|
85
|
+
"wait-on": "^9.5.1"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
|
-
"@playwright/test": "^1.
|
|
89
|
-
"@types/bun": "^1.4.
|
|
90
|
-
"@types/node": "^26.
|
|
88
|
+
"@playwright/test": "^1.63.0",
|
|
89
|
+
"@types/bun": "^1.4.2",
|
|
90
|
+
"@types/node": "^26.6.4",
|
|
91
91
|
"eslint-import-resolver-typescript": "^4.4.5",
|
|
92
92
|
"eslint-plugin-import": "^2.32.0",
|
|
93
|
-
"eslint-plugin-playwright": "^2.
|
|
93
|
+
"eslint-plugin-playwright": "^2.12.1",
|
|
94
94
|
"eslint-plugin-react": "^7.37.5",
|
|
95
95
|
"lerna": "^10.0.1",
|
|
96
|
-
"np": "^12.
|
|
97
|
-
"oxfmt": "^0.
|
|
98
|
-
"oxlint": "^1.
|
|
99
|
-
"oxlint-tsgolint": "^7.0.
|
|
96
|
+
"np": "^12.1.1",
|
|
97
|
+
"oxfmt": "^0.72.0",
|
|
98
|
+
"oxlint": "^1.87.0",
|
|
99
|
+
"oxlint-tsgolint": "^7.0.2003",
|
|
100
100
|
"rimraf": "^6.1.3",
|
|
101
101
|
"start-server-and-test": "^3.0.12",
|
|
102
102
|
"typescript": "^7.0.2"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrabble-solver/configs",
|
|
3
|
-
"version": "2.18.
|
|
3
|
+
"version": "2.18.5",
|
|
4
4
|
"description": "Game and language configs for Scrabble Solver: board layouts, bonus squares, tile distributions, and points for 8 games and 9 locales.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"scrabble",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"clean": "rimraf build/"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@scrabble-solver/constants": "^2.18.
|
|
37
|
-
"@scrabble-solver/types": "^2.18.
|
|
36
|
+
"@scrabble-solver/constants": "^2.18.5",
|
|
37
|
+
"@scrabble-solver/types": "^2.18.5"
|
|
38
38
|
},
|
|
39
39
|
"gitHead": "1e2d1ad49a288d4a44196d8e3336740da132c637"
|
|
40
40
|
}
|
|
@@ -7,11 +7,8 @@ const rootDirectory = path.resolve(__dirname, '..');
|
|
|
7
7
|
const { dictionaries } = require(rootDirectory);
|
|
8
8
|
|
|
9
9
|
const updateDictionaries = async () => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} catch (error) {
|
|
13
|
-
console.error(error);
|
|
14
|
-
}
|
|
10
|
+
const failedLocales = await dictionaries.update();
|
|
11
|
+
process.exitCode = failedLocales.length > 0 ? 1 : 0;
|
|
15
12
|
};
|
|
16
13
|
|
|
17
14
|
updateDictionaries();
|
|
@@ -21,7 +21,6 @@ class Dictionaries {
|
|
|
21
21
|
return gaddag;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
logger_1.logger.info('Dictionaries - cache miss', { locale });
|
|
25
24
|
return this.updateDictionary(locale);
|
|
26
25
|
}
|
|
27
26
|
remove() {
|
|
@@ -30,14 +29,20 @@ class Dictionaries {
|
|
|
30
29
|
}
|
|
31
30
|
async update(force) {
|
|
32
31
|
const locales = force ? Object.values(types_1.Locale) : this.getLocalesToUpdate();
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
const outcomes = await Promise.allSettled(locales.map((locale) => this.updateDictionary(locale)));
|
|
33
|
+
const failedLocales = [];
|
|
34
|
+
outcomes.forEach((outcome, index) => {
|
|
35
|
+
if (outcome.status === 'rejected') {
|
|
36
|
+
(0, logger_1.logError)('build', outcome.reason, { locale: locales[index] });
|
|
37
|
+
failedLocales.push(locales[index]);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return failedLocales;
|
|
35
41
|
}
|
|
36
42
|
getLocalesToUpdate() {
|
|
37
43
|
return Object.values(types_1.Locale).filter((locale) => this.cache.isStale(locale) !== false);
|
|
38
44
|
}
|
|
39
45
|
async updateDictionary(locale) {
|
|
40
|
-
logger_1.logger.info('Dictionaries - updateDictionary', { locale });
|
|
41
46
|
fs_1.default.mkdirSync(constants_1.OUTPUT_DIRECTORY, { recursive: true });
|
|
42
47
|
const downloadDictionaryProxy = this.downloadDictionaryProxies[locale];
|
|
43
48
|
const gaddag = await downloadDictionaryProxy();
|
|
@@ -28,7 +28,7 @@ class DiskCache {
|
|
|
28
28
|
return gaddag_1.Gaddag.deserialize(new Uint8Array(serialized.buffer, serialized.byteOffset, serialized.byteLength));
|
|
29
29
|
}
|
|
30
30
|
catch (error) {
|
|
31
|
-
logger_1.
|
|
31
|
+
(0, logger_1.logError)('cache', error, { level: 'warn', locale });
|
|
32
32
|
await fs_1.default.promises.rm(filepath, { force: true });
|
|
33
33
|
return undefined;
|
|
34
34
|
}
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.downloadDictionary =
|
|
3
|
+
exports.downloadDictionary = downloadDictionary;
|
|
4
4
|
const gaddag_1 = require("@kamilmielnik/gaddag");
|
|
5
5
|
const logger_1 = require("@scrabble-solver/logger");
|
|
6
6
|
const word_lists_1 = require("@scrabble-solver/word-lists");
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
async function downloadDictionary(locale) {
|
|
8
|
+
const downloadStartedAt = performance.now();
|
|
9
9
|
const words = await (0, word_lists_1.getWordList)(locale);
|
|
10
|
-
|
|
10
|
+
const buildStartedAt = performance.now();
|
|
11
11
|
const gaddag = gaddag_1.Gaddag.fromArray(words);
|
|
12
|
-
logger_1.
|
|
12
|
+
(0, logger_1.logEvent)({
|
|
13
|
+
type: 'build',
|
|
14
|
+
locale,
|
|
15
|
+
words: words.length,
|
|
16
|
+
download_ms: Math.round(buildStartedAt - downloadStartedAt),
|
|
17
|
+
build_ms: Math.round(performance.now() - buildStartedAt),
|
|
18
|
+
});
|
|
13
19
|
return gaddag;
|
|
14
|
-
}
|
|
15
|
-
exports.downloadDictionary = downloadDictionary;
|
|
20
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrabble-solver/dictionaries",
|
|
3
|
-
"version": "2.18.
|
|
3
|
+
"version": "2.18.5",
|
|
4
4
|
"description": "Dictionary manager for Scrabble Solver: downloads per-locale word lists and serves them as GADDAG automata with layered disk/memory caching.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"scrabble",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"test": "bun test src"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@kamilmielnik/gaddag": "^
|
|
41
|
-
"@scrabble-solver/logger": "^2.18.
|
|
42
|
-
"@scrabble-solver/types": "^2.18.
|
|
43
|
-
"@scrabble-solver/word-lists": "^2.18.
|
|
40
|
+
"@kamilmielnik/gaddag": "^3.0.0",
|
|
41
|
+
"@scrabble-solver/logger": "^2.18.5",
|
|
42
|
+
"@scrabble-solver/types": "^2.18.5",
|
|
43
|
+
"@scrabble-solver/word-lists": "^2.18.5"
|
|
44
44
|
},
|
|
45
45
|
"gitHead": "1e2d1ad49a288d4a44196d8e3336740da132c637"
|
|
46
46
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Gaddag } from '@kamilmielnik/gaddag';
|
|
2
|
-
import {
|
|
2
|
+
import { logError } from '@scrabble-solver/logger';
|
|
3
3
|
import { Locale } from '@scrabble-solver/types';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
|
|
@@ -28,7 +28,6 @@ export class Dictionaries {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
logger.info('Dictionaries - cache miss', { locale });
|
|
32
31
|
return this.updateDictionary(locale);
|
|
33
32
|
}
|
|
34
33
|
|
|
@@ -37,10 +36,19 @@ export class Dictionaries {
|
|
|
37
36
|
fs.rmdirSync(OUTPUT_DIRECTORY, { recursive: true });
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
public async update(force?: boolean): Promise<
|
|
39
|
+
public async update(force?: boolean): Promise<Locale[]> {
|
|
41
40
|
const locales = force ? Object.values(Locale) : this.getLocalesToUpdate();
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
const outcomes = await Promise.allSettled(locales.map((locale) => this.updateDictionary(locale)));
|
|
42
|
+
const failedLocales: Locale[] = [];
|
|
43
|
+
|
|
44
|
+
outcomes.forEach((outcome, index) => {
|
|
45
|
+
if (outcome.status === 'rejected') {
|
|
46
|
+
logError('build', outcome.reason, { locale: locales[index] });
|
|
47
|
+
failedLocales.push(locales[index]);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return failedLocales;
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
private getLocalesToUpdate(): Locale[] {
|
|
@@ -48,7 +56,6 @@ export class Dictionaries {
|
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
private async updateDictionary(locale: Locale): Promise<Gaddag> {
|
|
51
|
-
logger.info('Dictionaries - updateDictionary', { locale });
|
|
52
59
|
fs.mkdirSync(OUTPUT_DIRECTORY, { recursive: true });
|
|
53
60
|
const downloadDictionaryProxy = this.downloadDictionaryProxies[locale];
|
|
54
61
|
const gaddag = await downloadDictionaryProxy();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Gaddag } from '@kamilmielnik/gaddag';
|
|
2
|
-
import {
|
|
2
|
+
import { logError } from '@scrabble-solver/logger';
|
|
3
3
|
import { type Locale } from '@scrabble-solver/types';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
|
|
@@ -31,7 +31,7 @@ export class DiskCache implements Cache<Locale, Gaddag> {
|
|
|
31
31
|
const serialized = await fs.promises.readFile(filepath);
|
|
32
32
|
return Gaddag.deserialize(new Uint8Array(serialized.buffer, serialized.byteOffset, serialized.byteLength));
|
|
33
33
|
} catch (error) {
|
|
34
|
-
|
|
34
|
+
logError('cache', error, { level: 'warn', locale });
|
|
35
35
|
await fs.promises.rm(filepath, { force: true });
|
|
36
36
|
return undefined;
|
|
37
37
|
}
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { Gaddag } from '@kamilmielnik/gaddag';
|
|
2
|
-
import {
|
|
2
|
+
import { logEvent } from '@scrabble-solver/logger';
|
|
3
3
|
import { type Locale } from '@scrabble-solver/types';
|
|
4
4
|
import { getWordList } from '@scrabble-solver/word-lists';
|
|
5
5
|
|
|
6
|
-
export
|
|
7
|
-
|
|
6
|
+
export async function downloadDictionary(locale: Locale): Promise<Gaddag> {
|
|
7
|
+
const downloadStartedAt = performance.now();
|
|
8
8
|
const words = await getWordList(locale);
|
|
9
|
-
|
|
9
|
+
const buildStartedAt = performance.now();
|
|
10
10
|
const gaddag = Gaddag.fromArray(words);
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
logEvent({
|
|
13
|
+
type: 'build',
|
|
14
|
+
locale,
|
|
15
|
+
words: words.length,
|
|
16
|
+
download_ms: Math.round(buildStartedAt - downloadStartedAt),
|
|
17
|
+
build_ms: Math.round(performance.now() - buildStartedAt),
|
|
18
|
+
});
|
|
19
|
+
|
|
12
20
|
return gaddag;
|
|
13
|
-
}
|
|
21
|
+
}
|
|
@@ -1,3 +1,21 @@
|
|
|
1
1
|
# @scrabble-solver/logger
|
|
2
2
|
|
|
3
3
|
Logger used server-side.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
$HOME/.scrabble-solver/logs/events.txt
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Export to CSV
|
|
10
|
+
|
|
11
|
+
```shell
|
|
12
|
+
bun run --filter @scrabble-solver/logger export # every year
|
|
13
|
+
bun run --filter @scrabble-solver/logger export 2026 # one year
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
writes one file per event type and year to `$HOME/.scrabble-solver/csv/<type>-YYYY.csv`, e.g. `visit-2026.csv`.
|
|
17
|
+
|
|
18
|
+
## Previous format
|
|
19
|
+
|
|
20
|
+
Before August 2026 the package wrote output to `all.log` and `error.log` in the same directory.
|
|
21
|
+
Those files are neither read nor written anymore and can be deleted.
|
|
@@ -3,7 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.OUTPUT_DIRECTORY = void 0;
|
|
6
|
+
exports.IS_TEST_RUN = exports.CSV_DIRECTORY = exports.EVENTS_FILEPATH = exports.OUTPUT_DIRECTORY = void 0;
|
|
7
7
|
const os_1 = __importDefault(require("os"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
exports.OUTPUT_DIRECTORY = path_1.default.resolve(os_1.default.homedir(), '.scrabble-solver', 'logs');
|
|
10
|
+
exports.EVENTS_FILEPATH = path_1.default.resolve(exports.OUTPUT_DIRECTORY, 'events.txt');
|
|
11
|
+
exports.CSV_DIRECTORY = path_1.default.resolve(os_1.default.homedir(), '.scrabble-solver', 'csv');
|
|
12
|
+
exports.IS_TEST_RUN = process.env.NODE_ENV === 'test' || typeof describe === 'function';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.describeError = describeError;
|
|
4
|
+
function describeError(error) {
|
|
5
|
+
return { message: formatError(error), stack: error instanceof Error ? error.stack : undefined };
|
|
6
|
+
}
|
|
7
|
+
function formatError(error) {
|
|
8
|
+
if (!(error instanceof Error)) {
|
|
9
|
+
return String(error);
|
|
10
|
+
}
|
|
11
|
+
const heading = `${error.name}${formatCode(error)}: ${error.message}`.trimEnd();
|
|
12
|
+
const nested = getNestedErrors(error);
|
|
13
|
+
return nested.length === 0 ? heading : `${heading} (${nested.map(formatError).join('; ')})`;
|
|
14
|
+
}
|
|
15
|
+
function formatCode(error) {
|
|
16
|
+
return 'code' in error && typeof error.code === 'string' ? ` [${error.code}]` : '';
|
|
17
|
+
}
|
|
18
|
+
function getNestedErrors(error) {
|
|
19
|
+
if (error instanceof AggregateError) {
|
|
20
|
+
return error.errors;
|
|
21
|
+
}
|
|
22
|
+
return error.cause === undefined ? [] : [error.cause];
|
|
23
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export type Event = VisitEvent | SolveEvent | VerifyEvent | DownloadEvent | DefinitionEvent | BuildEvent | ErrorEvent;
|
|
2
|
+
export type EventType = Event['type'];
|
|
3
|
+
export type EventOf<T extends EventType> = Extract<Event, {
|
|
4
|
+
type: T;
|
|
5
|
+
}>;
|
|
6
|
+
export type EventValue = string | number | boolean | undefined;
|
|
7
|
+
export type LoggedEvent = Event & {
|
|
8
|
+
timestamp: string;
|
|
9
|
+
};
|
|
10
|
+
export type Operation = 'visit' | 'solve' | 'verify' | 'download' | 'definition' | 'build' | 'cache';
|
|
11
|
+
type VisitEvent = {
|
|
12
|
+
type: 'visit';
|
|
13
|
+
ip?: string;
|
|
14
|
+
ua?: string;
|
|
15
|
+
referrer?: string;
|
|
16
|
+
locale?: string;
|
|
17
|
+
game?: string;
|
|
18
|
+
url?: string;
|
|
19
|
+
};
|
|
20
|
+
type SolveEvent = {
|
|
21
|
+
type: 'solve';
|
|
22
|
+
ip?: string;
|
|
23
|
+
ms: number;
|
|
24
|
+
locale: string;
|
|
25
|
+
game: string;
|
|
26
|
+
tiles: number;
|
|
27
|
+
blanks: number;
|
|
28
|
+
rack: string;
|
|
29
|
+
results: number;
|
|
30
|
+
};
|
|
31
|
+
type VerifyEvent = {
|
|
32
|
+
type: 'verify';
|
|
33
|
+
ip?: string;
|
|
34
|
+
ms: number;
|
|
35
|
+
locale: string;
|
|
36
|
+
game: string;
|
|
37
|
+
tiles: number;
|
|
38
|
+
blanks: number;
|
|
39
|
+
valid: number;
|
|
40
|
+
invalid: number;
|
|
41
|
+
};
|
|
42
|
+
type DownloadEvent = {
|
|
43
|
+
type: 'download';
|
|
44
|
+
ip?: string;
|
|
45
|
+
ms: number;
|
|
46
|
+
locale: string;
|
|
47
|
+
status: number;
|
|
48
|
+
encoding: 'gzip' | 'identity';
|
|
49
|
+
bytes: number;
|
|
50
|
+
};
|
|
51
|
+
type DefinitionEvent = {
|
|
52
|
+
type: 'definition';
|
|
53
|
+
ip?: string;
|
|
54
|
+
ms: number;
|
|
55
|
+
locale: string;
|
|
56
|
+
words: string;
|
|
57
|
+
found: number;
|
|
58
|
+
};
|
|
59
|
+
type BuildEvent = {
|
|
60
|
+
type: 'build';
|
|
61
|
+
locale: string;
|
|
62
|
+
words: number;
|
|
63
|
+
download_ms: number;
|
|
64
|
+
build_ms: number;
|
|
65
|
+
};
|
|
66
|
+
type ErrorEvent = {
|
|
67
|
+
type: 'error';
|
|
68
|
+
level: 'error' | 'warn';
|
|
69
|
+
operation: Operation;
|
|
70
|
+
locale?: string;
|
|
71
|
+
ip?: string;
|
|
72
|
+
ua?: string;
|
|
73
|
+
message: string;
|
|
74
|
+
stack?: string;
|
|
75
|
+
input?: string;
|
|
76
|
+
};
|
|
77
|
+
type FieldsOf<T extends EventType> = Exclude<keyof EventOf<T>, 'type'>;
|
|
78
|
+
export declare const EVENT_FIELDS: {
|
|
79
|
+
readonly visit: readonly ["ip", "ua", "referrer", "locale", "game", "url"];
|
|
80
|
+
readonly solve: readonly ["ip", "ms", "locale", "game", "tiles", "blanks", "rack", "results"];
|
|
81
|
+
readonly verify: readonly ["ip", "ms", "locale", "game", "tiles", "blanks", "valid", "invalid"];
|
|
82
|
+
readonly download: readonly ["ip", "ms", "locale", "status", "encoding", "bytes"];
|
|
83
|
+
readonly definition: readonly ["ip", "ms", "locale", "words", "found"];
|
|
84
|
+
readonly build: readonly ["locale", "words", "download_ms", "build_ms"];
|
|
85
|
+
readonly error: readonly ["level", "operation", "locale", "ip", "ua", "message", "stack", "input"];
|
|
86
|
+
};
|
|
87
|
+
type UnlistedFields = {
|
|
88
|
+
[T in EventType]: Exclude<FieldsOf<T>, (typeof EVENT_FIELDS)[T][number]>;
|
|
89
|
+
}[EventType];
|
|
90
|
+
type Assert<T extends never> = T;
|
|
91
|
+
export type EveryFieldIsListed = Assert<UnlistedFields>;
|
|
92
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EVENT_FIELDS = void 0;
|
|
4
|
+
// Field order is the column order of the exported CSV files - append only, never reorder.
|
|
5
|
+
exports.EVENT_FIELDS = {
|
|
6
|
+
visit: ['ip', 'ua', 'referrer', 'locale', 'game', 'url'],
|
|
7
|
+
solve: ['ip', 'ms', 'locale', 'game', 'tiles', 'blanks', 'rack', 'results'],
|
|
8
|
+
verify: ['ip', 'ms', 'locale', 'game', 'tiles', 'blanks', 'valid', 'invalid'],
|
|
9
|
+
download: ['ip', 'ms', 'locale', 'status', 'encoding', 'bytes'],
|
|
10
|
+
definition: ['ip', 'ms', 'locale', 'words', 'found'],
|
|
11
|
+
build: ['locale', 'words', 'download_ms', 'build_ms'],
|
|
12
|
+
error: ['level', 'operation', 'locale', 'ip', 'ua', 'message', 'stack', 'input'],
|
|
13
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatCsvRow = formatCsvRow;
|
|
4
|
+
const FORMULA_PREFIX = /^[=+\-@\t\r]/;
|
|
5
|
+
function formatCsvRow(values) {
|
|
6
|
+
return `${values.map(formatCsvField).join(',')}\n`;
|
|
7
|
+
}
|
|
8
|
+
function formatCsvField(value) {
|
|
9
|
+
if (typeof value === 'undefined') {
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
12
|
+
const text = typeof value === 'string' ? escapeFormula(value) : String(value);
|
|
13
|
+
return needsQuoting(text) ? `"${text.replaceAll('"', '""')}"` : text;
|
|
14
|
+
}
|
|
15
|
+
function escapeFormula(text) {
|
|
16
|
+
return FORMULA_PREFIX.test(text) ? `'${text}` : text;
|
|
17
|
+
}
|
|
18
|
+
function needsQuoting(text) {
|
|
19
|
+
return /[",\n\r]/.test(text) || text !== text.trim();
|
|
20
|
+
}
|
|
@@ -1 +1,5 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { CSV_DIRECTORY, EVENTS_FILEPATH, OUTPUT_DIRECTORY } from './constants';
|
|
2
|
+
export { EVENT_FIELDS, type Event, type EventOf, type EventType, type EventValue, type LoggedEvent, type Operation, } from './events';
|
|
3
|
+
export { formatCsvRow } from './formatCsvRow';
|
|
4
|
+
export { logError } from './logError';
|
|
5
|
+
export { logEvent } from './logEvent';
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
var
|
|
5
|
-
Object.defineProperty(exports, "
|
|
3
|
+
exports.logEvent = exports.logError = exports.formatCsvRow = exports.EVENT_FIELDS = exports.OUTPUT_DIRECTORY = exports.EVENTS_FILEPATH = exports.CSV_DIRECTORY = void 0;
|
|
4
|
+
var constants_1 = require("./constants");
|
|
5
|
+
Object.defineProperty(exports, "CSV_DIRECTORY", { enumerable: true, get: function () { return constants_1.CSV_DIRECTORY; } });
|
|
6
|
+
Object.defineProperty(exports, "EVENTS_FILEPATH", { enumerable: true, get: function () { return constants_1.EVENTS_FILEPATH; } });
|
|
7
|
+
Object.defineProperty(exports, "OUTPUT_DIRECTORY", { enumerable: true, get: function () { return constants_1.OUTPUT_DIRECTORY; } });
|
|
8
|
+
var events_1 = require("./events");
|
|
9
|
+
Object.defineProperty(exports, "EVENT_FIELDS", { enumerable: true, get: function () { return events_1.EVENT_FIELDS; } });
|
|
10
|
+
var formatCsvRow_1 = require("./formatCsvRow");
|
|
11
|
+
Object.defineProperty(exports, "formatCsvRow", { enumerable: true, get: function () { return formatCsvRow_1.formatCsvRow; } });
|
|
12
|
+
var logError_1 = require("./logError");
|
|
13
|
+
Object.defineProperty(exports, "logError", { enumerable: true, get: function () { return logError_1.logError; } });
|
|
14
|
+
var logEvent_1 = require("./logEvent");
|
|
15
|
+
Object.defineProperty(exports, "logEvent", { enumerable: true, get: function () { return logEvent_1.logEvent; } });
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type EventOf, type Operation } from './events';
|
|
2
|
+
type ErrorContext = Partial<Pick<EventOf<'error'>, 'level' | 'input' | 'ip' | 'locale' | 'ua'>>;
|
|
3
|
+
export declare function logError(operation: Operation, error: unknown, { level, ...context }?: ErrorContext): void;
|
|
4
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logError = logError;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
const describeError_1 = require("./describeError");
|
|
6
|
+
const logEvent_1 = require("./logEvent");
|
|
7
|
+
function logError(operation, error, { level = 'error', ...context } = {}) {
|
|
8
|
+
const description = (0, describeError_1.describeError)(error);
|
|
9
|
+
(0, logEvent_1.logEvent)({ type: 'error', level, operation, ...description, ...context });
|
|
10
|
+
if (level === 'error' && !constants_1.IS_TEST_RUN) {
|
|
11
|
+
process.stderr.write(formatStderrEntry(operation, description));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function formatStderrEntry(operation, { message, stack }) {
|
|
15
|
+
const frames = stack?.split('\n').filter((line) => line.startsWith(' at ')) ?? [];
|
|
16
|
+
return `${[`${operation}: ${message}`, ...frames].join('\n')}\n`;
|
|
17
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.logEvent = logEvent;
|
|
7
|
+
exports.formatEventLine = formatEventLine;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const constants_1 = require("./constants");
|
|
10
|
+
const events_1 = require("./events");
|
|
11
|
+
function logEvent(event) {
|
|
12
|
+
if (constants_1.IS_TEST_RUN) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
fs_1.default.mkdirSync(constants_1.OUTPUT_DIRECTORY, { recursive: true });
|
|
17
|
+
fs_1.default.appendFileSync(constants_1.EVENTS_FILEPATH, formatEventLine(event, new Date()));
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
process.stderr.write(`Could not append to ${constants_1.EVENTS_FILEPATH}: ${String(error)}\n`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function formatEventLine(event, now) {
|
|
24
|
+
const line = { timestamp: formatTimestamp(now), ...event };
|
|
25
|
+
return `${JSON.stringify(line, ['timestamp', 'type', ...events_1.EVENT_FIELDS[event.type]])}\n`;
|
|
26
|
+
}
|
|
27
|
+
function formatTimestamp(date) {
|
|
28
|
+
return `${date.toISOString().slice(0, 19)}Z`;
|
|
29
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrabble-solver/logger",
|
|
3
|
-
"version": "2.18.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.18.5",
|
|
4
|
+
"description": "JSON-lines event log for Scrabble Solver, with CSV export.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"logger",
|
|
7
|
-
"
|
|
7
|
+
"events",
|
|
8
|
+
"csv",
|
|
8
9
|
"scrabble"
|
|
9
10
|
],
|
|
10
11
|
"main": "build/index.js",
|
|
@@ -24,12 +25,10 @@
|
|
|
24
25
|
},
|
|
25
26
|
"homepage": "https://scrabble-solver.org",
|
|
26
27
|
"scripts": {
|
|
27
|
-
"build": "tsc --project .",
|
|
28
|
+
"build": "tsc --project . && tsc --project scripts",
|
|
28
29
|
"clean": "rimraf build/",
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
"dependencies": {
|
|
32
|
-
"winston": "^3.19.0"
|
|
30
|
+
"export": "bun scripts/export-csv.ts",
|
|
31
|
+
"test": "bun test src"
|
|
33
32
|
},
|
|
34
33
|
"gitHead": "1e2d1ad49a288d4a44196d8e3336740da132c637"
|
|
35
34
|
}
|