shortwords 0.1.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/LICENSE.md +21 -0
- package/README.md +134 -0
- package/bin/cli.js +50 -0
- package/dist/builder.d.ts +42 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +112 -0
- package/dist/history.d.ts +9 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +21 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/types.d.ts +28 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +33 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +60 -0
- package/gui/app.js +43 -0
- package/gui/index.html +53 -0
- package/gui/style.css +100 -0
- package/package.json +68 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Timothy T. Joe
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Shortwords
|
|
2
|
+
|
|
3
|
+
This library generates pronounceable 2, 3, and 4‑letter English words. It is ideal for brainstorming unique project code names, creating short file extensions, or generating random identifiers.
|
|
4
|
+
|
|
5
|
+
### Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install shortwords
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Usage
|
|
12
|
+
|
|
13
|
+
Generate random 3‑letter words.
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import shortwords from "shortwords";
|
|
17
|
+
|
|
18
|
+
const results = shortwords(3);
|
|
19
|
+
console.log([...results]);
|
|
20
|
+
// Example output (your results will vary):
|
|
21
|
+
[
|
|
22
|
+
"fco", "adi", "apq", "imi",
|
|
23
|
+
"aob", "ajy", "vzu", "rif"...
|
|
24
|
+
]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Apply constraints by chaining methods.
|
|
28
|
+
Here we request exactly 5 words of size 4, following the `CVCV` pattern.
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
const results = shortwords(4).pattern('cvcv').results(5);
|
|
32
|
+
|
|
33
|
+
console.log([...results]);
|
|
34
|
+
// Example output (your results will vary):
|
|
35
|
+
['tucu', 'sivu', 'kive', 'rulu', 'gimu'];
|
|
36
|
+
|
|
37
|
+
// Get first word in list
|
|
38
|
+
console.log(results.get(0));
|
|
39
|
+
// output: tucu
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Configure everything in one call using an options object.
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
|
|
46
|
+
// Configuration Signature:
|
|
47
|
+
shortwords(
|
|
48
|
+
size, // integer (2, 3, or 4) for word length
|
|
49
|
+
{
|
|
50
|
+
results?: number, // number of words to generate
|
|
51
|
+
pattern?: string, // syllable pattern string (e.g., "CVC", "VCV")
|
|
52
|
+
lock?: boolean // freeze the instance state
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
// Single-call option:
|
|
57
|
+
const results = shortwords(3, { results: 8, pattern: "VCV" });
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Use `.mix()` to ignore size boundaries and generate a random assortment of 2, 3, or 4‑letter words.
|
|
62
|
+
The result supports native array methods.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
const results = shortwords().mix().results(6);
|
|
66
|
+
// Mixed Size Words:
|
|
67
|
+
['veuq', 'iwdu', 'iof', 'qsi', 'oq', 'luiy'];
|
|
68
|
+
|
|
69
|
+
// Mapped Array (Uppercase):
|
|
70
|
+
results.map((w) => w.toUpperCase());
|
|
71
|
+
// Example output (your results will vary):
|
|
72
|
+
['VEUQ', 'IWDU', 'IOF', 'QSI', 'OQ', 'LUIY'];
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Lock a result to prevent further modifications. Attempts to change after `.lock()` are ignored.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
const results = shortwords(3).results(3).lock();
|
|
79
|
+
|
|
80
|
+
results.pattern('vcv').results(10); // ignored
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Global History
|
|
84
|
+
|
|
85
|
+
Every call to `shortwords()` is recorded in a global history store.
|
|
86
|
+
You can access either:
|
|
87
|
+
|
|
88
|
+
- **The previous run** — the last array generated.
|
|
89
|
+
- **The full active history** — all arrays generated during the current lifecycle, in chronological order.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
shortwords.prev(); // Returns a single array of words from the most recent run.
|
|
93
|
+
shortwords.history(); // Returns multiple arrays of words, one for each run so far.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### GUI
|
|
97
|
+
|
|
98
|
+
Shortwords also ships with a simple GUI.
|
|
99
|
+
Launch it with:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx shortwords gui
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
This opens a local web interface where you can configure length, patterns, and modes interactively, and copy generated results.
|
|
106
|
+
|
|
107
|
+
### API
|
|
108
|
+
|
|
109
|
+
The core `shortwords(size)` function initiates an iterable builder. Omitting `size` generates mixed lengths.
|
|
110
|
+
|
|
111
|
+
- **`.results(n)`** – Set the number of words to return. See below table for maximum counts.
|
|
112
|
+
- **`.pattern(str)`** – Enforce a syllable pattern (e.g., `"cvc"`). Case-insensitive.
|
|
113
|
+
- **`.get(index)`** – Retrieve a specific word without array bracket notation.
|
|
114
|
+
- **`.mix()`** – Ignore size limits and generate mixed 2, 3, and 4‑letter words.
|
|
115
|
+
- **`.lock()`** – Freeze the current state to prevent further chained modifications.
|
|
116
|
+
|
|
117
|
+
#### Global State
|
|
118
|
+
|
|
119
|
+
- **`.prev()`** – Returns a single array of words from the most recent run.
|
|
120
|
+
- **`.history()`** – Returns multiple arrays of words, one for each run so far.
|
|
121
|
+
|
|
122
|
+
Words are generated using strict Consonant (`C`) and Vowel (`V`) pools to ensure pronounceability.
|
|
123
|
+
|
|
124
|
+
| Character Type | Pool | Total |
|
|
125
|
+
| ------------------ | ------------------------------------------------------------- | ----- |
|
|
126
|
+
| **Vowels (V)** | a, e, i, o, u | 5 |
|
|
127
|
+
| **Consonants (C)** | b, c, d, f, g, h, j, k, l, m, n, p, q, r, s, t, v, w, x, y, z | 21 |
|
|
128
|
+
|
|
129
|
+
| Size | Default Patterns | Result Example | Max Count |
|
|
130
|
+
| ----- | ---------------------------------------------- | ---------------- | --------- |
|
|
131
|
+
| **2** | CV, VC | to, up | 210 |
|
|
132
|
+
| **3** | CVC, VCV, CVV, VVC, CCV, VCC | cat, ago, bee | 13,860 |
|
|
133
|
+
| **4** | CVCV, VCVC, CVVC, VCCV, CCVV, VVCC, CCVC, CVCC | code, acid, book | 743,610 |
|
|
134
|
+
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import http from "http";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import open from "open";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
|
|
12
|
+
const guiPath = path.join(__dirname, "../gui");
|
|
13
|
+
const distPath = path.join(__dirname, "../dist");
|
|
14
|
+
|
|
15
|
+
function serveFile(filePath, res) {
|
|
16
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
17
|
+
fs.readFile(filePath, (err, content) => {
|
|
18
|
+
if (err) {
|
|
19
|
+
res.writeHead(404);
|
|
20
|
+
res.end("Not found");
|
|
21
|
+
} else {
|
|
22
|
+
const type =
|
|
23
|
+
ext === ".css" ? "text/css" :
|
|
24
|
+
ext === ".js" ? "application/javascript" :
|
|
25
|
+
"text/html";
|
|
26
|
+
res.writeHead(200, { "Content-Type": type });
|
|
27
|
+
res.end(content);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (args[0] === "gui") {
|
|
33
|
+
const server = http.createServer((req, res) => {
|
|
34
|
+
let filePath;
|
|
35
|
+
if (req.url.startsWith("/dist/")) {
|
|
36
|
+
filePath = path.join(distPath, req.url.replace("/dist/", ""));
|
|
37
|
+
} else {
|
|
38
|
+
filePath = path.join(guiPath, req.url === "/" ? "index.html" : req.url);
|
|
39
|
+
}
|
|
40
|
+
serveFile(filePath, res);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const port = 5555;
|
|
44
|
+
server.listen(port, () => {
|
|
45
|
+
console.log(`Shortwords GUI running at http://localhost:${port}`);
|
|
46
|
+
open(`http://localhost:${port}`);
|
|
47
|
+
});
|
|
48
|
+
} else {
|
|
49
|
+
console.log("Usage: shortwords gui");
|
|
50
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Size, Options } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A chainable builder that wraps generated word lists.
|
|
4
|
+
*/
|
|
5
|
+
export declare class Builder implements Iterable<string> {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(size?: Size, options?: Options, onGenerate?: (words: string[]) => void);
|
|
8
|
+
/**
|
|
9
|
+
* Returns the number of words.
|
|
10
|
+
*/
|
|
11
|
+
get length(): number;
|
|
12
|
+
/**
|
|
13
|
+
* Retrieves a specific word by index without array bracket notation.
|
|
14
|
+
*/
|
|
15
|
+
get(index: number): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Ignores size limits and generates mixed 2, 3, and 4-letter words.
|
|
18
|
+
*/
|
|
19
|
+
mix(): this;
|
|
20
|
+
/**
|
|
21
|
+
* Sets the number of words to return.
|
|
22
|
+
*/
|
|
23
|
+
results(number: number): this;
|
|
24
|
+
/**
|
|
25
|
+
* Enforces a syllable pattern (case-insensitive).
|
|
26
|
+
*/
|
|
27
|
+
pattern(patternString: string): this;
|
|
28
|
+
/**
|
|
29
|
+
* Freezes the current state to prevent further chained modifications.
|
|
30
|
+
*/
|
|
31
|
+
lock(): this;
|
|
32
|
+
/**
|
|
33
|
+
* Returns a standard array copy of the generated words.
|
|
34
|
+
*/
|
|
35
|
+
toArray(): string[];
|
|
36
|
+
/**
|
|
37
|
+
* Maps over the generated words array.
|
|
38
|
+
*/
|
|
39
|
+
map<U>(callbackfn: (value: string, index: number, array: string[]) => U): U[];
|
|
40
|
+
[Symbol.iterator](): Iterator<string>;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAW,OAAO,EAAC,MAAM,YAAY,CAAC;AAGlD;;GAEG;AACH,qBAAa,OAAQ,YAAW,QAAQ,CAAC,MAAM,CAAC;;IAQ9C,YACE,IAAI,CAAC,EAAE,IAAI,EACX,OAAO,CAAC,EAAE,OAAO,EACjB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,EAqBvC;IAUD;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;OAEG;IACH,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAErC;IAED;;OAEG;IACH,GAAG,IAAI,IAAI,CAKV;IAED;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAQ5B;IAED;;OAEG;IACH,OAAO,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAanC;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,CAGX;IAED;;OAEG;IACH,OAAO,IAAI,MAAM,EAAE,CAElB;IAED;;OAEG;IACH,GAAG,CAAC,CAAC,EACH,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAC/D,CAAC,EAAE,CAEL;IAEA,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,CAIrC;CACF"}
|
package/dist/builder.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { genWords, InternalError } from './utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* A chainable builder that wraps generated word lists.
|
|
4
|
+
*/
|
|
5
|
+
export class Builder {
|
|
6
|
+
#words = [];
|
|
7
|
+
#size;
|
|
8
|
+
#count = 20;
|
|
9
|
+
#pattern;
|
|
10
|
+
#isLocked = false;
|
|
11
|
+
#onGenerate;
|
|
12
|
+
constructor(size, options, onGenerate) {
|
|
13
|
+
this.#size = size;
|
|
14
|
+
if (onGenerate) {
|
|
15
|
+
this.#onGenerate = onGenerate;
|
|
16
|
+
}
|
|
17
|
+
if (options) {
|
|
18
|
+
if (options.results !== undefined) {
|
|
19
|
+
if (!Number.isInteger(options.results) || options.results < 0) {
|
|
20
|
+
throw new InternalError('Results count must be a non-negative integer.');
|
|
21
|
+
}
|
|
22
|
+
this.#count = options.results;
|
|
23
|
+
}
|
|
24
|
+
if (options.pattern !== undefined)
|
|
25
|
+
this.#pattern = options.pattern;
|
|
26
|
+
if (options.lock !== undefined)
|
|
27
|
+
this.#isLocked = options.lock;
|
|
28
|
+
}
|
|
29
|
+
this.#execute();
|
|
30
|
+
}
|
|
31
|
+
#execute() {
|
|
32
|
+
if (this.#isLocked)
|
|
33
|
+
return;
|
|
34
|
+
this.#words = genWords(this.#size, this.#pattern, this.#count);
|
|
35
|
+
if (this.#onGenerate) {
|
|
36
|
+
this.#onGenerate(this.#words);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Returns the number of words.
|
|
41
|
+
*/
|
|
42
|
+
get length() {
|
|
43
|
+
return this.#words.length;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Retrieves a specific word by index without array bracket notation.
|
|
47
|
+
*/
|
|
48
|
+
get(index) {
|
|
49
|
+
return this.#words[index];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Ignores size limits and generates mixed 2, 3, and 4-letter words.
|
|
53
|
+
*/
|
|
54
|
+
mix() {
|
|
55
|
+
if (this.#isLocked)
|
|
56
|
+
return this;
|
|
57
|
+
this.#size = undefined;
|
|
58
|
+
this.#execute();
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Sets the number of words to return.
|
|
63
|
+
*/
|
|
64
|
+
results(number) {
|
|
65
|
+
if (this.#isLocked)
|
|
66
|
+
return this;
|
|
67
|
+
if (!Number.isInteger(number) || number < 0) {
|
|
68
|
+
throw new InternalError('Results count must be a non-negative integer.');
|
|
69
|
+
}
|
|
70
|
+
this.#count = number;
|
|
71
|
+
this.#execute();
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Enforces a syllable pattern (case-insensitive).
|
|
76
|
+
*/
|
|
77
|
+
pattern(patternString) {
|
|
78
|
+
if (this.#isLocked)
|
|
79
|
+
return this;
|
|
80
|
+
const normalized = patternString.toUpperCase();
|
|
81
|
+
if (this.#size && normalized.length !== this.#size) {
|
|
82
|
+
throw new InternalError(`Pattern "${patternString}" length does not match size ${this.#size}`);
|
|
83
|
+
}
|
|
84
|
+
this.#pattern = normalized;
|
|
85
|
+
this.#execute();
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Freezes the current state to prevent further chained modifications.
|
|
90
|
+
*/
|
|
91
|
+
lock() {
|
|
92
|
+
this.#isLocked = true;
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Returns a standard array copy of the generated words.
|
|
97
|
+
*/
|
|
98
|
+
toArray() {
|
|
99
|
+
return [...this.#words];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Maps over the generated words array.
|
|
103
|
+
*/
|
|
104
|
+
map(callbackfn) {
|
|
105
|
+
return this.#words.map(callbackfn);
|
|
106
|
+
}
|
|
107
|
+
*[Symbol.iterator]() {
|
|
108
|
+
for (const word of this.#words) {
|
|
109
|
+
yield word;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,OAAO;;IAGlB,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,CAE9B;IAED,UAAU,IAAI,MAAM,EAAE,EAAE,CAavB;CACF"}
|
package/dist/history.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks historical outputs using weak references to prevent memory leaks.
|
|
3
|
+
*/
|
|
4
|
+
export class History {
|
|
5
|
+
#records = [];
|
|
6
|
+
add(wordsArray) {
|
|
7
|
+
this.#records.push(new WeakRef(wordsArray));
|
|
8
|
+
}
|
|
9
|
+
getHistory() {
|
|
10
|
+
const activeRecords = [];
|
|
11
|
+
this.#records = this.#records.filter((ref) => {
|
|
12
|
+
const dereferenced = ref.deref();
|
|
13
|
+
if (dereferenced !== undefined) {
|
|
14
|
+
activeRecords.push(dereferenced);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
});
|
|
19
|
+
return activeRecords;
|
|
20
|
+
}
|
|
21
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Size, Options } from './types.js';
|
|
2
|
+
import { Builder } from './builder.js';
|
|
3
|
+
/**
|
|
4
|
+
* Initiates an iterable builder for generating pronounceable words.
|
|
5
|
+
* Omitting size generates mixed lengths.
|
|
6
|
+
*/
|
|
7
|
+
declare function generate(size?: Size, options?: Options): Builder;
|
|
8
|
+
declare const shortwords: typeof generate & {
|
|
9
|
+
/**
|
|
10
|
+
* Returns a single array of words from the most recent run.
|
|
11
|
+
*/
|
|
12
|
+
prev(): string[] | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Returns multiple arrays of words, one for each run so far.
|
|
15
|
+
*/
|
|
16
|
+
history(): string[][];
|
|
17
|
+
};
|
|
18
|
+
export default shortwords;
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAE,OAAO,EAAC,MAAM,YAAY,CAAC;AACzC,OAAO,EAAC,OAAO,EAAC,MAAM,cAAc,CAAC;AAMrC;;;GAGG;AACH,iBAAS,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAKzD;AAED,QAAA,MAAM,UAAU;IACd;;OAEG;YACK,MAAM,EAAE,GAAG,SAAS;IAI5B;;OAEG;eACQ,MAAM,EAAE,EAAE;CAGrB,CAAC;eAEY,UAAU"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Builder } from './builder.js';
|
|
2
|
+
import { History } from './history.js';
|
|
3
|
+
const history = new History();
|
|
4
|
+
let reference = null;
|
|
5
|
+
/**
|
|
6
|
+
* Initiates an iterable builder for generating pronounceable words.
|
|
7
|
+
* Omitting size generates mixed lengths.
|
|
8
|
+
*/
|
|
9
|
+
function generate(size, options) {
|
|
10
|
+
return new Builder(size, options, (generation) => {
|
|
11
|
+
history.add(generation);
|
|
12
|
+
reference = new WeakRef(generation);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
const shortwords = Object.assign(generate, {
|
|
16
|
+
/**
|
|
17
|
+
* Returns a single array of words from the most recent run.
|
|
18
|
+
*/
|
|
19
|
+
prev() {
|
|
20
|
+
return reference?.deref();
|
|
21
|
+
},
|
|
22
|
+
/**
|
|
23
|
+
* Returns multiple arrays of words, one for each run so far.
|
|
24
|
+
*/
|
|
25
|
+
history() {
|
|
26
|
+
return history.getHistory();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
export default shortwords;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Character length constraints supported by the library.
|
|
3
|
+
*/
|
|
4
|
+
export type Size = 2 | 3 | 4;
|
|
5
|
+
/**
|
|
6
|
+
* Valid syllable structures.
|
|
7
|
+
* 'C' represents a Consonant, and 'V' represents a Vowel.
|
|
8
|
+
*/
|
|
9
|
+
export type Pattern = 'CV' | 'VC' | 'CVC' | 'VCV' | 'CVV' | 'VVC' | 'CCV' | 'VCC' | 'CVCV' | 'VCVC' | 'CVVC' | 'VCCV' | 'CCVV' | 'VVCC' | 'CCVC' | 'CVCC';
|
|
10
|
+
/**
|
|
11
|
+
* Single-call configuration parameters passed as an alternative to chaining.
|
|
12
|
+
*/
|
|
13
|
+
export interface Options {
|
|
14
|
+
/** The total number of words to generate. */
|
|
15
|
+
results?: number;
|
|
16
|
+
/** A strict Consonant/Vowel layout constraint. */
|
|
17
|
+
pattern?: Pattern;
|
|
18
|
+
/** When true, freezes the generated results from further alterations. */
|
|
19
|
+
lock?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare const VOWELS: readonly ['a', 'e', 'i', 'o', 'u'];
|
|
22
|
+
export declare const CONSONANTS: readonly ['b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z'];
|
|
23
|
+
/**
|
|
24
|
+
* Syllable structures grouped by word size.
|
|
25
|
+
* Used when no explicit pattern is specified.
|
|
26
|
+
*/
|
|
27
|
+
export declare const DEFAULTS: Record<Size, Pattern[]>;
|
|
28
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAE7B;;;GAGG;AACH,MAAM,MAAM,OAAO,GACf,IAAI,GACJ,IAAI,GACJ,KAAK,GACL,KAAK,GACL,KAAK,GACL,KAAK,GACL,KAAK,GACL,KAAK,GACL,MAAM,GACN,MAAM,GACN,MAAM,GACN,MAAM,GACN,MAAM,GACN,MAAM,GACN,MAAM,GACN,MAAM,CAAC;AAEX;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yEAAyE;IACzE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,eAAO,MAAM,MAAM,YAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU,CAAC;AAEzD,eAAO,MAAM,UAAU,YACrB,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,CACK,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,CAI5C,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const VOWELS = ['a', 'e', 'i', 'o', 'u'];
|
|
2
|
+
export const CONSONANTS = [
|
|
3
|
+
'b',
|
|
4
|
+
'c',
|
|
5
|
+
'd',
|
|
6
|
+
'f',
|
|
7
|
+
'g',
|
|
8
|
+
'h',
|
|
9
|
+
'j',
|
|
10
|
+
'k',
|
|
11
|
+
'l',
|
|
12
|
+
'm',
|
|
13
|
+
'n',
|
|
14
|
+
'p',
|
|
15
|
+
'q',
|
|
16
|
+
'r',
|
|
17
|
+
's',
|
|
18
|
+
't',
|
|
19
|
+
'v',
|
|
20
|
+
'w',
|
|
21
|
+
'x',
|
|
22
|
+
'y',
|
|
23
|
+
'z'
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Syllable structures grouped by word size.
|
|
27
|
+
* Used when no explicit pattern is specified.
|
|
28
|
+
*/
|
|
29
|
+
export const DEFAULTS = {
|
|
30
|
+
2: ['CV', 'VC'],
|
|
31
|
+
3: ['CVC', 'VCV', 'CVV', 'VVC', 'CCV', 'VCC'],
|
|
32
|
+
4: ['CVCV', 'VCVC', 'CVVC', 'VCCV', 'CCVV', 'VVCC', 'CCVC', 'CVCC']
|
|
33
|
+
};
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Size, Pattern } from './types.js';
|
|
2
|
+
export declare class InternalError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Generates pronounceable 2, 3, and 4-letter English words.
|
|
7
|
+
*/
|
|
8
|
+
export declare function genWords(size: Size | undefined, pattern: Pattern | undefined, count: number): string[];
|
|
9
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAE,OAAO,EAA+B,MAAM,YAAY,CAAC;AAEvE,qBAAa,aAAc,SAAQ,KAAK;IACtC,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAuBD;;GAEG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,IAAI,GAAG,SAAS,EACtB,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,GACZ,MAAM,EAAE,CAoCV"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { VOWELS, CONSONANTS, DEFAULTS } from './types.js';
|
|
2
|
+
export class InternalError extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'InternalError';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function pickLetter(arr) {
|
|
9
|
+
if (!arr || arr.length === 0) {
|
|
10
|
+
throw new InternalError('Expected a non-empty array of letters.');
|
|
11
|
+
}
|
|
12
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
13
|
+
}
|
|
14
|
+
function genPattern(pattern) {
|
|
15
|
+
let word = '';
|
|
16
|
+
for (const char of pattern.toUpperCase()) {
|
|
17
|
+
if (char === 'C') {
|
|
18
|
+
word += pickLetter(CONSONANTS);
|
|
19
|
+
}
|
|
20
|
+
else if (char === 'V') {
|
|
21
|
+
word += pickLetter(VOWELS);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
throw new InternalError(`Invalid structural token "${char}" in pattern.`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return word;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Generates pronounceable 2, 3, and 4-letter English words.
|
|
31
|
+
*/
|
|
32
|
+
export function genWords(size, pattern, count) {
|
|
33
|
+
if (!Number.isInteger(count) || count < 0) {
|
|
34
|
+
throw new InternalError('Results count must be a non-negative integer.');
|
|
35
|
+
}
|
|
36
|
+
if (!pattern && size !== undefined && !(size in DEFAULTS)) {
|
|
37
|
+
throw new InternalError(`Invalid size "${size}". Expected 2, 3, or 4.`);
|
|
38
|
+
}
|
|
39
|
+
if (pattern) {
|
|
40
|
+
for (const char of pattern.toUpperCase()) {
|
|
41
|
+
if (char !== 'C' && char !== 'V') {
|
|
42
|
+
throw new InternalError(`Invalid structural token "${char}" in pattern.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const results = [];
|
|
47
|
+
for (let i = 0; i < count; i++) {
|
|
48
|
+
let activePattern;
|
|
49
|
+
if (pattern) {
|
|
50
|
+
activePattern = pattern;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const activeSize = size || pickLetter([2, 3, 4]);
|
|
54
|
+
const availablePatterns = DEFAULTS[activeSize];
|
|
55
|
+
activePattern = pickLetter(availablePatterns);
|
|
56
|
+
}
|
|
57
|
+
results.push(genPattern(activePattern));
|
|
58
|
+
}
|
|
59
|
+
return results;
|
|
60
|
+
}
|
package/gui/app.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import shortwords from "../dist/index.js";
|
|
2
|
+
|
|
3
|
+
const lengthInput = document.getElementById("length");
|
|
4
|
+
const patternInput = document.getElementById("pattern");
|
|
5
|
+
const modeRadios = document.getElementsByName("mode");
|
|
6
|
+
const generateBtn = document.getElementById("generate");
|
|
7
|
+
const resultArea = document.getElementById("result");
|
|
8
|
+
const copyBtn = document.getElementById("copy");
|
|
9
|
+
const feedback = document.getElementById("feedback");
|
|
10
|
+
|
|
11
|
+
generateBtn.addEventListener("click", () => {
|
|
12
|
+
const size = parseInt(lengthInput.value, 10);
|
|
13
|
+
const pattern = patternInput.value.trim().toUpperCase();
|
|
14
|
+
const mode = [...modeRadios].find(r => r.checked).value;
|
|
15
|
+
|
|
16
|
+
let results;
|
|
17
|
+
try {
|
|
18
|
+
if (mode === "mix") {
|
|
19
|
+
results = shortwords().mix().results(10);
|
|
20
|
+
} else if (pattern) {
|
|
21
|
+
if (pattern.length !== size) {
|
|
22
|
+
feedback.textContent = `Pattern "${pattern}" length (${pattern.length}) does not match size ${size}`;
|
|
23
|
+
resultArea.value = "";
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
results = shortwords(size).pattern(pattern).results(10);
|
|
27
|
+
} else {
|
|
28
|
+
results = shortwords(size).results(10);
|
|
29
|
+
}
|
|
30
|
+
resultArea.value = [...results].join("\n");
|
|
31
|
+
feedback.textContent = "";
|
|
32
|
+
} catch (err) {
|
|
33
|
+
feedback.textContent = err.message;
|
|
34
|
+
resultArea.value = "";
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
copyBtn.addEventListener("click", () => {
|
|
39
|
+
resultArea.select();
|
|
40
|
+
document.execCommand("copy");
|
|
41
|
+
feedback.textContent = "Copied!";
|
|
42
|
+
setTimeout(() => feedback.textContent = "", 2000);
|
|
43
|
+
});
|
package/gui/index.html
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Shortwords GUI</title>
|
|
6
|
+
<link rel="stylesheet" href="style.css">
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<nav class="navbar">
|
|
10
|
+
<div class="nav-left">
|
|
11
|
+
<h1>shortwords</h1>
|
|
12
|
+
<p class="tagline">Generate pronounceable short words</p>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="nav-right">
|
|
15
|
+
<a href="https://www.npmjs.com/package/shortwords">npm</a>
|
|
16
|
+
<a href="https://github.com/timtjoe/shortwords">GitHub</a>
|
|
17
|
+
<a href="https://github.com/timtjoe/shortwords/issues/new">Create Issue</a>
|
|
18
|
+
</div>
|
|
19
|
+
</nav>
|
|
20
|
+
|
|
21
|
+
<main class="content">
|
|
22
|
+
<header class="toolbar">
|
|
23
|
+
<label>Length <input id="length" type="number" min="2" max="4" value="3"></label>
|
|
24
|
+
<label>Pattern <input id="pattern" list="patterns"></label>
|
|
25
|
+
<datalist id="patterns">
|
|
26
|
+
<option value="CVC">
|
|
27
|
+
<option value="VCV">
|
|
28
|
+
<option value="CVV">
|
|
29
|
+
<option value="CVCV">
|
|
30
|
+
</datalist>
|
|
31
|
+
<div class="radio-group">
|
|
32
|
+
<label><input type="radio" name="mode" value="random" checked> Random</label>
|
|
33
|
+
<label><input type="radio" name="mode" value="mix"> Mix</label>
|
|
34
|
+
</div>
|
|
35
|
+
<button id="generate">Generate</button>
|
|
36
|
+
</header>
|
|
37
|
+
|
|
38
|
+
<section class="output">
|
|
39
|
+
<h2>Output</h2>
|
|
40
|
+
<textarea id="result" readonly></textarea>
|
|
41
|
+
<button id="copy">Copy</button>
|
|
42
|
+
<span id="feedback" class="feedback"></span>
|
|
43
|
+
</section>
|
|
44
|
+
</main>
|
|
45
|
+
|
|
46
|
+
<footer class="footer">
|
|
47
|
+
<p>Author: <a href="https://github.com/timtjoe">Timothy T. Joe</a></p>
|
|
48
|
+
<p>© <a href="https://opensource.org/licenses/MIT">MIT License</a></p>
|
|
49
|
+
</footer>
|
|
50
|
+
|
|
51
|
+
<script type="module" src="app.js"></script>
|
|
52
|
+
</body>
|
|
53
|
+
</html>
|
package/gui/style.css
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
body {
|
|
2
|
+
margin: 0;
|
|
3
|
+
font-family: system-ui, sans-serif;
|
|
4
|
+
background: #fff;
|
|
5
|
+
color: #222;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.navbar {
|
|
9
|
+
display: flex;
|
|
10
|
+
justify-content: space-between;
|
|
11
|
+
align-items: center;
|
|
12
|
+
padding: 12px 20px;
|
|
13
|
+
background: #111;
|
|
14
|
+
color: #fff;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.nav-left h1 {
|
|
18
|
+
margin: 0;
|
|
19
|
+
font-size: 20px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.nav-left .tagline {
|
|
23
|
+
margin: 0;
|
|
24
|
+
font-size: 12px;
|
|
25
|
+
color: #aaa;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.nav-right a {
|
|
29
|
+
margin-left: 15px;
|
|
30
|
+
color: #4da3ff;
|
|
31
|
+
text-decoration: none;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.content {
|
|
35
|
+
max-width: 700px;
|
|
36
|
+
margin: 20px auto;
|
|
37
|
+
padding: 0 20px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.toolbar {
|
|
41
|
+
display: flex;
|
|
42
|
+
gap: 12px;
|
|
43
|
+
flex-wrap: wrap;
|
|
44
|
+
background: #f5f5f5;
|
|
45
|
+
padding: 12px;
|
|
46
|
+
border-radius: 6px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.toolbar label {
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
font-size: 14px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.radio-group {
|
|
56
|
+
display: flex;
|
|
57
|
+
gap: 10px;
|
|
58
|
+
align-items: center;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
button {
|
|
62
|
+
background: #007bff;
|
|
63
|
+
color: #fff;
|
|
64
|
+
border: none;
|
|
65
|
+
padding: 8px 14px;
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
border-radius: 4px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
button:hover {
|
|
71
|
+
background: #0056b3;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.output {
|
|
75
|
+
margin-top: 20px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
textarea {
|
|
79
|
+
width: 100%;
|
|
80
|
+
height: 200px;
|
|
81
|
+
font-family: monospace;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.feedback {
|
|
85
|
+
margin-left: 10px;
|
|
86
|
+
color: green;
|
|
87
|
+
font-size: 14px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.footer {
|
|
91
|
+
text-align: center;
|
|
92
|
+
padding: 20px;
|
|
93
|
+
background: #f5f5f5;
|
|
94
|
+
font-size: 14px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.footer a {
|
|
98
|
+
color: #007bff;
|
|
99
|
+
text-decoration: none;
|
|
100
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shortwords",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "This library generates pronounceable sets of 2, 3, and 4-letter English words based on a given pattern.",
|
|
5
|
+
"author": "Timothy T. Joe <timtjoe@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"shortwords": "./bin/cli.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"bin",
|
|
23
|
+
"gui",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"homepage": "https://github.com/timtjoe/shortwords#readme",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/timtjoe/shortwords/issues"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/timtjoe/shortwords.git"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18",
|
|
37
|
+
"npm": ">=9"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc --build --force",
|
|
41
|
+
"prepack": "npm run build",
|
|
42
|
+
"pack": "npm pack",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"test:types": "vitest --typecheck",
|
|
45
|
+
"format": "prettier --write .",
|
|
46
|
+
"lint": "prettier --check .",
|
|
47
|
+
"knip": "knip",
|
|
48
|
+
"release:patch": "npm version patch -m \"Release %s\" && npm publish --access public",
|
|
49
|
+
"release:minor": "npm version minor -m \"Release %s\" && npm publish --access public",
|
|
50
|
+
"release:major": "npm version major -m \"Release %s\" && npm publish --access public"
|
|
51
|
+
},
|
|
52
|
+
"keywords": [
|
|
53
|
+
"word generator",
|
|
54
|
+
"short words",
|
|
55
|
+
"english words",
|
|
56
|
+
"word pattern"
|
|
57
|
+
],
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"open": "^9.1.0"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/node": "^26.1.1",
|
|
63
|
+
"knip": "^6.27.0",
|
|
64
|
+
"prettier": "^3.9.4",
|
|
65
|
+
"typescript": "^7.0.2",
|
|
66
|
+
"vitest": "^4.1.10"
|
|
67
|
+
}
|
|
68
|
+
}
|