@js-gamifications/word-search-core 1.0.1

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/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # @js-gamifications/word-search-core
2
+
3
+ Framework-agnostic engine for generating word search puzzles.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm i @js-gamifications/word-search-core
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { createWordSearch } from "@js-gamifications/word-search-core";
15
+
16
+ const puzzle = createWordSearch({
17
+ rows: 12,
18
+ cols: 12,
19
+ words: ["react", "angular", "typescript"],
20
+ allowDiagonal: true
21
+ });
22
+ ```
@@ -0,0 +1,23 @@
1
+ interface WordPlacement {
2
+ word: string;
3
+ startRow: number;
4
+ startCol: number;
5
+ deltaRow: number;
6
+ deltaCol: number;
7
+ }
8
+ interface WordSearchOptions {
9
+ rows: number;
10
+ cols: number;
11
+ words: string[];
12
+ alphabet?: string;
13
+ allowDiagonal?: boolean;
14
+ maxPlacementAttempts?: number;
15
+ }
16
+ interface WordSearchPuzzle {
17
+ grid: string[][];
18
+ placements: WordPlacement[];
19
+ }
20
+
21
+ declare function createWordSearch(options: WordSearchOptions): WordSearchPuzzle;
22
+
23
+ export { type WordPlacement, type WordSearchOptions, type WordSearchPuzzle, createWordSearch };
@@ -0,0 +1,23 @@
1
+ interface WordPlacement {
2
+ word: string;
3
+ startRow: number;
4
+ startCol: number;
5
+ deltaRow: number;
6
+ deltaCol: number;
7
+ }
8
+ interface WordSearchOptions {
9
+ rows: number;
10
+ cols: number;
11
+ words: string[];
12
+ alphabet?: string;
13
+ allowDiagonal?: boolean;
14
+ maxPlacementAttempts?: number;
15
+ }
16
+ interface WordSearchPuzzle {
17
+ grid: string[][];
18
+ placements: WordPlacement[];
19
+ }
20
+
21
+ declare function createWordSearch(options: WordSearchOptions): WordSearchPuzzle;
22
+
23
+ export { type WordPlacement, type WordSearchOptions, type WordSearchPuzzle, createWordSearch };
package/dist/index.js ADDED
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createWordSearch: () => createWordSearch
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/wordSearch.ts
28
+ var DEFAULT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
29
+ function normalizeWord(word) {
30
+ return word.trim().toUpperCase().replace(/\s+/g, "");
31
+ }
32
+ function createDirections(allowDiagonal) {
33
+ const base = [
34
+ [0, 1],
35
+ [1, 0],
36
+ [0, -1],
37
+ [-1, 0]
38
+ ];
39
+ if (!allowDiagonal) {
40
+ return base;
41
+ }
42
+ return [
43
+ ...base,
44
+ [1, 1],
45
+ [1, -1],
46
+ [-1, 1],
47
+ [-1, -1]
48
+ ];
49
+ }
50
+ function randomInt(max) {
51
+ return Math.floor(Math.random() * max);
52
+ }
53
+ function inBounds(row, col, rows, cols) {
54
+ return row >= 0 && row < rows && col >= 0 && col < cols;
55
+ }
56
+ function canPlaceWord(grid, word, startRow, startCol, deltaRow, deltaCol) {
57
+ const firstRow = grid[0];
58
+ if (!firstRow) {
59
+ return false;
60
+ }
61
+ for (let i = 0; i < word.length; i += 1) {
62
+ const row = startRow + i * deltaRow;
63
+ const col = startCol + i * deltaCol;
64
+ if (!inBounds(row, col, grid.length, firstRow.length)) {
65
+ return false;
66
+ }
67
+ const rowCells = grid[row];
68
+ const existing = rowCells?.[col];
69
+ const char = word[i];
70
+ if (!rowCells || existing === void 0 || char === void 0) {
71
+ return false;
72
+ }
73
+ if (existing !== "" && existing !== char) {
74
+ return false;
75
+ }
76
+ }
77
+ return true;
78
+ }
79
+ function placeWord(grid, word, startRow, startCol, deltaRow, deltaCol) {
80
+ for (let i = 0; i < word.length; i += 1) {
81
+ const row = startRow + i * deltaRow;
82
+ const col = startCol + i * deltaCol;
83
+ const rowCells = grid[row];
84
+ const char = word[i];
85
+ if (!rowCells || char === void 0) {
86
+ throw new Error("Invalid word placement coordinates");
87
+ }
88
+ rowCells[col] = char;
89
+ }
90
+ }
91
+ function fillEmptyCells(grid, alphabet) {
92
+ const chars = alphabet.split("");
93
+ for (let row = 0; row < grid.length; row += 1) {
94
+ const rowCells = grid[row];
95
+ if (!rowCells) {
96
+ continue;
97
+ }
98
+ for (let col = 0; col < rowCells.length; col += 1) {
99
+ if (rowCells[col] === "") {
100
+ const nextChar = chars[randomInt(chars.length)];
101
+ rowCells[col] = nextChar ?? chars[0] ?? "A";
102
+ }
103
+ }
104
+ }
105
+ }
106
+ function createWordSearch(options) {
107
+ const rows = options.rows;
108
+ const cols = options.cols;
109
+ const alphabet = options.alphabet ?? DEFAULT_ALPHABET;
110
+ const allowDiagonal = options.allowDiagonal ?? true;
111
+ const maxPlacementAttempts = options.maxPlacementAttempts ?? 250;
112
+ if (rows <= 0 || cols <= 0) {
113
+ throw new Error("rows and cols must be greater than 0");
114
+ }
115
+ if (alphabet.length === 0) {
116
+ throw new Error("alphabet must contain at least one character");
117
+ }
118
+ const words = options.words.map(normalizeWord).filter(Boolean);
119
+ if (words.length === 0) {
120
+ throw new Error("words must include at least one valid entry");
121
+ }
122
+ const sortedWords = [...words].sort((a, b) => b.length - a.length);
123
+ const directions = createDirections(allowDiagonal);
124
+ const grid = Array.from({ length: rows }, () => Array.from({ length: cols }, () => ""));
125
+ const placements = [];
126
+ for (const word of sortedWords) {
127
+ let placed = false;
128
+ for (let attempt = 0; attempt < maxPlacementAttempts; attempt += 1) {
129
+ const direction = directions[randomInt(directions.length)];
130
+ if (!direction) {
131
+ throw new Error("No directions configured for placement");
132
+ }
133
+ const [deltaRow, deltaCol] = direction;
134
+ const startRow = randomInt(rows);
135
+ const startCol = randomInt(cols);
136
+ if (!canPlaceWord(grid, word, startRow, startCol, deltaRow, deltaCol)) {
137
+ continue;
138
+ }
139
+ placeWord(grid, word, startRow, startCol, deltaRow, deltaCol);
140
+ placements.push({ word, startRow, startCol, deltaRow, deltaCol });
141
+ placed = true;
142
+ break;
143
+ }
144
+ if (!placed) {
145
+ throw new Error(`Unable to place word: ${word}`);
146
+ }
147
+ }
148
+ fillEmptyCells(grid, alphabet.toUpperCase());
149
+ return { grid, placements };
150
+ }
151
+ // Annotate the CommonJS export names for ESM import in node:
152
+ 0 && (module.exports = {
153
+ createWordSearch
154
+ });
155
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/wordSearch.ts"],"sourcesContent":["export * from \"./types\";\nexport { createWordSearch } from \"./wordSearch\";\n","import type { WordPlacement, WordSearchOptions, WordSearchPuzzle } from \"./types\";\n\nconst DEFAULT_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n\nfunction normalizeWord(word: string): string {\n return word.trim().toUpperCase().replace(/\\s+/g, \"\");\n}\n\nfunction createDirections(allowDiagonal: boolean): Array<[number, number]> {\n const base: Array<[number, number]> = [\n [0, 1],\n [1, 0],\n [0, -1],\n [-1, 0]\n ];\n\n if (!allowDiagonal) {\n return base;\n }\n\n return [\n ...base,\n [1, 1],\n [1, -1],\n [-1, 1],\n [-1, -1]\n ];\n}\n\nfunction randomInt(max: number): number {\n return Math.floor(Math.random() * max);\n}\n\nfunction inBounds(row: number, col: number, rows: number, cols: number): boolean {\n return row >= 0 && row < rows && col >= 0 && col < cols;\n}\n\nfunction canPlaceWord(\n grid: string[][],\n word: string,\n startRow: number,\n startCol: number,\n deltaRow: number,\n deltaCol: number\n): boolean {\n const firstRow = grid[0];\n if (!firstRow) {\n return false;\n }\n\n for (let i = 0; i < word.length; i += 1) {\n const row = startRow + i * deltaRow;\n const col = startCol + i * deltaCol;\n\n if (!inBounds(row, col, grid.length, firstRow.length)) {\n return false;\n }\n\n const rowCells = grid[row];\n const existing = rowCells?.[col];\n const char = word[i];\n if (!rowCells || existing === undefined || char === undefined) {\n return false;\n }\n\n if (existing !== \"\" && existing !== char) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction placeWord(\n grid: string[][],\n word: string,\n startRow: number,\n startCol: number,\n deltaRow: number,\n deltaCol: number\n): void {\n for (let i = 0; i < word.length; i += 1) {\n const row = startRow + i * deltaRow;\n const col = startCol + i * deltaCol;\n const rowCells = grid[row];\n const char = word[i];\n if (!rowCells || char === undefined) {\n throw new Error(\"Invalid word placement coordinates\");\n }\n\n rowCells[col] = char;\n }\n}\n\nfunction fillEmptyCells(grid: string[][], alphabet: string): void {\n const chars = alphabet.split(\"\");\n for (let row = 0; row < grid.length; row += 1) {\n const rowCells = grid[row];\n if (!rowCells) {\n continue;\n }\n\n for (let col = 0; col < rowCells.length; col += 1) {\n if (rowCells[col] === \"\") {\n const nextChar = chars[randomInt(chars.length)];\n rowCells[col] = nextChar ?? chars[0] ?? \"A\";\n }\n }\n }\n}\n\nexport function createWordSearch(options: WordSearchOptions): WordSearchPuzzle {\n const rows = options.rows;\n const cols = options.cols;\n const alphabet = options.alphabet ?? DEFAULT_ALPHABET;\n const allowDiagonal = options.allowDiagonal ?? true;\n const maxPlacementAttempts = options.maxPlacementAttempts ?? 250;\n\n if (rows <= 0 || cols <= 0) {\n throw new Error(\"rows and cols must be greater than 0\");\n }\n\n if (alphabet.length === 0) {\n throw new Error(\"alphabet must contain at least one character\");\n }\n\n const words = options.words.map(normalizeWord).filter(Boolean);\n if (words.length === 0) {\n throw new Error(\"words must include at least one valid entry\");\n }\n\n const sortedWords = [...words].sort((a, b) => b.length - a.length);\n const directions = createDirections(allowDiagonal);\n const grid = Array.from({ length: rows }, () => Array.from({ length: cols }, () => \"\"));\n const placements: WordPlacement[] = [];\n\n for (const word of sortedWords) {\n let placed = false;\n\n for (let attempt = 0; attempt < maxPlacementAttempts; attempt += 1) {\n const direction = directions[randomInt(directions.length)];\n if (!direction) {\n throw new Error(\"No directions configured for placement\");\n }\n\n const [deltaRow, deltaCol] = direction;\n const startRow = randomInt(rows);\n const startCol = randomInt(cols);\n\n if (!canPlaceWord(grid, word, startRow, startCol, deltaRow, deltaCol)) {\n continue;\n }\n\n placeWord(grid, word, startRow, startCol, deltaRow, deltaCol);\n placements.push({ word, startRow, startCol, deltaRow, deltaCol });\n placed = true;\n break;\n }\n\n if (!placed) {\n throw new Error(`Unable to place word: ${word}`);\n }\n }\n\n fillEmptyCells(grid, alphabet.toUpperCase());\n\n return { grid, placements };\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,128 @@
1
+ // src/wordSearch.ts
2
+ var DEFAULT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
3
+ function normalizeWord(word) {
4
+ return word.trim().toUpperCase().replace(/\s+/g, "");
5
+ }
6
+ function createDirections(allowDiagonal) {
7
+ const base = [
8
+ [0, 1],
9
+ [1, 0],
10
+ [0, -1],
11
+ [-1, 0]
12
+ ];
13
+ if (!allowDiagonal) {
14
+ return base;
15
+ }
16
+ return [
17
+ ...base,
18
+ [1, 1],
19
+ [1, -1],
20
+ [-1, 1],
21
+ [-1, -1]
22
+ ];
23
+ }
24
+ function randomInt(max) {
25
+ return Math.floor(Math.random() * max);
26
+ }
27
+ function inBounds(row, col, rows, cols) {
28
+ return row >= 0 && row < rows && col >= 0 && col < cols;
29
+ }
30
+ function canPlaceWord(grid, word, startRow, startCol, deltaRow, deltaCol) {
31
+ const firstRow = grid[0];
32
+ if (!firstRow) {
33
+ return false;
34
+ }
35
+ for (let i = 0; i < word.length; i += 1) {
36
+ const row = startRow + i * deltaRow;
37
+ const col = startCol + i * deltaCol;
38
+ if (!inBounds(row, col, grid.length, firstRow.length)) {
39
+ return false;
40
+ }
41
+ const rowCells = grid[row];
42
+ const existing = rowCells?.[col];
43
+ const char = word[i];
44
+ if (!rowCells || existing === void 0 || char === void 0) {
45
+ return false;
46
+ }
47
+ if (existing !== "" && existing !== char) {
48
+ return false;
49
+ }
50
+ }
51
+ return true;
52
+ }
53
+ function placeWord(grid, word, startRow, startCol, deltaRow, deltaCol) {
54
+ for (let i = 0; i < word.length; i += 1) {
55
+ const row = startRow + i * deltaRow;
56
+ const col = startCol + i * deltaCol;
57
+ const rowCells = grid[row];
58
+ const char = word[i];
59
+ if (!rowCells || char === void 0) {
60
+ throw new Error("Invalid word placement coordinates");
61
+ }
62
+ rowCells[col] = char;
63
+ }
64
+ }
65
+ function fillEmptyCells(grid, alphabet) {
66
+ const chars = alphabet.split("");
67
+ for (let row = 0; row < grid.length; row += 1) {
68
+ const rowCells = grid[row];
69
+ if (!rowCells) {
70
+ continue;
71
+ }
72
+ for (let col = 0; col < rowCells.length; col += 1) {
73
+ if (rowCells[col] === "") {
74
+ const nextChar = chars[randomInt(chars.length)];
75
+ rowCells[col] = nextChar ?? chars[0] ?? "A";
76
+ }
77
+ }
78
+ }
79
+ }
80
+ function createWordSearch(options) {
81
+ const rows = options.rows;
82
+ const cols = options.cols;
83
+ const alphabet = options.alphabet ?? DEFAULT_ALPHABET;
84
+ const allowDiagonal = options.allowDiagonal ?? true;
85
+ const maxPlacementAttempts = options.maxPlacementAttempts ?? 250;
86
+ if (rows <= 0 || cols <= 0) {
87
+ throw new Error("rows and cols must be greater than 0");
88
+ }
89
+ if (alphabet.length === 0) {
90
+ throw new Error("alphabet must contain at least one character");
91
+ }
92
+ const words = options.words.map(normalizeWord).filter(Boolean);
93
+ if (words.length === 0) {
94
+ throw new Error("words must include at least one valid entry");
95
+ }
96
+ const sortedWords = [...words].sort((a, b) => b.length - a.length);
97
+ const directions = createDirections(allowDiagonal);
98
+ const grid = Array.from({ length: rows }, () => Array.from({ length: cols }, () => ""));
99
+ const placements = [];
100
+ for (const word of sortedWords) {
101
+ let placed = false;
102
+ for (let attempt = 0; attempt < maxPlacementAttempts; attempt += 1) {
103
+ const direction = directions[randomInt(directions.length)];
104
+ if (!direction) {
105
+ throw new Error("No directions configured for placement");
106
+ }
107
+ const [deltaRow, deltaCol] = direction;
108
+ const startRow = randomInt(rows);
109
+ const startCol = randomInt(cols);
110
+ if (!canPlaceWord(grid, word, startRow, startCol, deltaRow, deltaCol)) {
111
+ continue;
112
+ }
113
+ placeWord(grid, word, startRow, startCol, deltaRow, deltaCol);
114
+ placements.push({ word, startRow, startCol, deltaRow, deltaCol });
115
+ placed = true;
116
+ break;
117
+ }
118
+ if (!placed) {
119
+ throw new Error(`Unable to place word: ${word}`);
120
+ }
121
+ }
122
+ fillEmptyCells(grid, alphabet.toUpperCase());
123
+ return { grid, placements };
124
+ }
125
+ export {
126
+ createWordSearch
127
+ };
128
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/wordSearch.ts"],"sourcesContent":["import type { WordPlacement, WordSearchOptions, WordSearchPuzzle } from \"./types\";\n\nconst DEFAULT_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n\nfunction normalizeWord(word: string): string {\n return word.trim().toUpperCase().replace(/\\s+/g, \"\");\n}\n\nfunction createDirections(allowDiagonal: boolean): Array<[number, number]> {\n const base: Array<[number, number]> = [\n [0, 1],\n [1, 0],\n [0, -1],\n [-1, 0]\n ];\n\n if (!allowDiagonal) {\n return base;\n }\n\n return [\n ...base,\n [1, 1],\n [1, -1],\n [-1, 1],\n [-1, -1]\n ];\n}\n\nfunction randomInt(max: number): number {\n return Math.floor(Math.random() * max);\n}\n\nfunction inBounds(row: number, col: number, rows: number, cols: number): boolean {\n return row >= 0 && row < rows && col >= 0 && col < cols;\n}\n\nfunction canPlaceWord(\n grid: string[][],\n word: string,\n startRow: number,\n startCol: number,\n deltaRow: number,\n deltaCol: number\n): boolean {\n const firstRow = grid[0];\n if (!firstRow) {\n return false;\n }\n\n for (let i = 0; i < word.length; i += 1) {\n const row = startRow + i * deltaRow;\n const col = startCol + i * deltaCol;\n\n if (!inBounds(row, col, grid.length, firstRow.length)) {\n return false;\n }\n\n const rowCells = grid[row];\n const existing = rowCells?.[col];\n const char = word[i];\n if (!rowCells || existing === undefined || char === undefined) {\n return false;\n }\n\n if (existing !== \"\" && existing !== char) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction placeWord(\n grid: string[][],\n word: string,\n startRow: number,\n startCol: number,\n deltaRow: number,\n deltaCol: number\n): void {\n for (let i = 0; i < word.length; i += 1) {\n const row = startRow + i * deltaRow;\n const col = startCol + i * deltaCol;\n const rowCells = grid[row];\n const char = word[i];\n if (!rowCells || char === undefined) {\n throw new Error(\"Invalid word placement coordinates\");\n }\n\n rowCells[col] = char;\n }\n}\n\nfunction fillEmptyCells(grid: string[][], alphabet: string): void {\n const chars = alphabet.split(\"\");\n for (let row = 0; row < grid.length; row += 1) {\n const rowCells = grid[row];\n if (!rowCells) {\n continue;\n }\n\n for (let col = 0; col < rowCells.length; col += 1) {\n if (rowCells[col] === \"\") {\n const nextChar = chars[randomInt(chars.length)];\n rowCells[col] = nextChar ?? chars[0] ?? \"A\";\n }\n }\n }\n}\n\nexport function createWordSearch(options: WordSearchOptions): WordSearchPuzzle {\n const rows = options.rows;\n const cols = options.cols;\n const alphabet = options.alphabet ?? DEFAULT_ALPHABET;\n const allowDiagonal = options.allowDiagonal ?? true;\n const maxPlacementAttempts = options.maxPlacementAttempts ?? 250;\n\n if (rows <= 0 || cols <= 0) {\n throw new Error(\"rows and cols must be greater than 0\");\n }\n\n if (alphabet.length === 0) {\n throw new Error(\"alphabet must contain at least one character\");\n }\n\n const words = options.words.map(normalizeWord).filter(Boolean);\n if (words.length === 0) {\n throw new Error(\"words must include at least one valid entry\");\n }\n\n const sortedWords = [...words].sort((a, b) => b.length - a.length);\n const directions = createDirections(allowDiagonal);\n const grid = Array.from({ length: rows }, () => Array.from({ length: cols }, () => \"\"));\n const placements: WordPlacement[] = [];\n\n for (const word of sortedWords) {\n let placed = false;\n\n for (let attempt = 0; attempt < maxPlacementAttempts; attempt += 1) {\n const direction = directions[randomInt(directions.length)];\n if (!direction) {\n throw new Error(\"No directions configured for placement\");\n }\n\n const [deltaRow, deltaCol] = direction;\n const startRow = randomInt(rows);\n const startCol = randomInt(cols);\n\n if (!canPlaceWord(grid, word, startRow, startCol, deltaRow, deltaCol)) {\n continue;\n }\n\n placeWord(grid, word, startRow, startCol, deltaRow, deltaCol);\n placements.push({ word, startRow, startCol, deltaRow, deltaCol });\n placed = true;\n break;\n }\n\n if (!placed) {\n throw new Error(`Unable to place word: ${word}`);\n }\n }\n\n fillEmptyCells(grid, alphabet.toUpperCase());\n\n return { grid, placements };\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@js-gamifications/word-search-core",
3
+ "version": "1.0.1",
4
+ "description": "Framework-agnostic word search engine",
5
+ "license": "MIT",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.mjs",
16
+ "require": "./dist/index.js"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "clean": "rm -rf dist",
22
+ "typecheck": "tsc --noEmit",
23
+ "test": "vitest run",
24
+ "lint": "tsc --noEmit"
25
+ }
26
+ }