Skip to content

Commit 7990c30

Browse files
committed
feat: comprehensive rewrite and modernization of codenames library
- Refactored core API to support both default cities-20 and custom word lists - Added comprehensive test coverage across all modules and themes - Restructured project with proper TypeScript types and ESM exports - Enhanced CLI with better theme handling and error messages - Updated README with detailed API documentation and practical examples - Added extensive blog post documentation for preview deployments - Improved word list generation scripts and validation - Added proper SPDX license headers to all source files - Enhanced factory pattern for theme-specific codename functions - Implemented robust error handling for edge cases and invalid inputs
1 parent d63d37b commit 7990c30

77 files changed

Lines changed: 1950 additions & 279 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
node_modules/
22
*.log
3+
*.local.md
34
.env*
45
.DS_Store
56
dist/

README.md

Lines changed: 114 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,56 @@
99
A lightweight TypeScript library that converts any number into consistent, memorable codename from a curated word list. It's designed for use cases where human-readability is more important than collision resistance, such as generating preview URLs or readable test IDs.
1010

1111
```typescript
12-
// Codename generator using the most memorable 20 cities
13-
import codename from "codenames/cities-20";
12+
// Default: uses the cities-20 word list
13+
import codename from "codenames";
1414

15-
codename(1234); // "london", uses "cities"
15+
codename(1234); // "london"
1616
codename(1234); // "london" (always the same)
1717
codename(5678); // "paris"
1818
codename(6789); // "berlin"
19+
20+
// With custom words
21+
codename(1234, ["one", "two", "three"]); // "two"
1922
```
2023

2124
## Features
2225

2326
- **🎯 Deterministic** - Same input always produces the same codename
2427
- **💬 Human-Readable** - Memorable names instead of random strings
2528
- **🚀 Zero Dependencies** - Lightweight and fast with no external packages
26-
- **⚡ Fast** - 50,000+ generations per second
27-
- **📦 <3KB Core** - Ultra-minimal footprint, themes add ~2KB each
29+
- **⚡ Fast & Tiny** - 50,000+ generations per second, <3KB core + ~2KB per theme
2830
- **🌐 Universal Runtime** - Works in Node.js, Bun, Deno, browsers, and edge runtimes
2931
- **🎨 Multiple Themes** - Cities, animals, colors, space, and more built-in themes
30-
- **📦 Modern JavaScript** - TypeScript with full type safety, ESM package
31-
- **📝 Customizable** - Create your own themes and word lists
3232
- **🤖 CLI Included** - Generate codenames directly from your terminal
3333

3434
## Use Cases
3535

36-
- **Preview Deployments** - Generate unique URLs for pull requests
37-
- **Container Names** - Memorable Docker container identifiers
38-
- **Session IDs** - User-friendly session identifiers for support
39-
- **Feature Flags** - Human-friendly names for A/B tests
40-
- **Test Environments** - Readable identifiers for testing pipelines
41-
- **Test Data Generation** - Consistent test data generation
36+
### Preview Deployments
37+
38+
Managing preview environments can get messy. You end up tracking which PR is deployed where, maintaining state, dealing with conflicts. Here's a simpler approach: use deterministic hashing to map PR numbers to memorable names.
39+
40+
```typescript
41+
import codename from "codenames/cities-20";
42+
43+
// PR #1234 always maps to the same URL
44+
const previewUrl = `https://${codename(1234)}.example.com`;
45+
// => https://london.example.com
46+
47+
// PR #5678 gets a different one
48+
const anotherUrl = `https://${codename(5678)}.example.com`;
49+
// => https://paris.example.com
50+
```
51+
52+
With 20 city names, you get 20 deployment slots. No database needed. The same PR number always produces the same city name, so URLs stay consistent throughout the PR lifecycle.
53+
54+
Plus, it's easier to share "london.example.com" in Slack than "preview-env-1234.k8s.us-east-1.example.com".
55+
56+
### Other Uses
57+
58+
- **Docker Containers**: `docker run --name "app-${codename(buildId)}" myapp``app-tokyo`
59+
- **Session IDs**: `vienna-support` is friendlier than `sess_kJ8Hg2Bx9`
60+
- **Feature Flags**: `berlin-experiment` instead of `experiment_42`
61+
- **Test Data**: Generate predictable usernames (`cat`, `dog`, `bird`)
4262

4363
## Getting Started
4464

@@ -47,23 +67,82 @@ npm install codenames
4767
```
4868

4969
```typescript
50-
// Import the codename generator using the top 20
51-
// words from the curated list of world cities
52-
import codename from "codenames/cities-20";
70+
// Default: uses cities-20 word list
71+
import codename from "codenames";
72+
// OR
73+
import { codename } from "codenames";
5374

54-
// Get a codename for the number 1234
5575
const name = codename(1234); // "london"
5676
```
5777

58-
You can use different word lists and list sizes by importing the appropriate module:
78+
### Using Different Themes
79+
80+
You can import specific themed word lists:
81+
82+
```typescript
83+
// Import from a specific theme
84+
import codename from "codenames/animals-20";
85+
// OR
86+
import { codename } from "codenames/animals-20";
87+
88+
const name = codename(1234); // "cat"
89+
```
90+
91+
### Using Custom Word Lists
92+
93+
For maximum flexibility, use the core function with your own words:
5994

6095
```typescript
61-
import codename from "codenames/colors-50";
96+
// Use your own custom word list
97+
import { codename } from "codenames/core";
6298

63-
const name = codename(1234); // "blue"
99+
const name = codename(1234, ["alpha", "beta", "gamma"]); // "beta"
64100
```
65101

66-
Supported list sizes are 10, 20, 30, 50, and 100. The default is 20.
102+
## API Reference
103+
104+
### Default API (with cities-20)
105+
106+
```typescript
107+
import codename from "codenames"; // default export
108+
import { codename } from "codenames"; // named export
109+
110+
codename(input: number, words?: readonly string[]): string
111+
```
112+
113+
- `input` - The number to convert
114+
- `words` - Optional array of words to use (defaults to cities-20)
115+
116+
### Core API (with custom words)
117+
118+
```typescript
119+
import codename from "codenames/core"; // default export
120+
import { codename } from "codenames/core"; // named export
121+
122+
codename(input: number, words: readonly string[]): string
123+
```
124+
125+
- `input` - The number to convert
126+
- `words` - Required array of words to use
127+
128+
### Themed APIs
129+
130+
```typescript
131+
import codename from "codenames/cities-20"; // default export
132+
import { codename } from "codenames/animals-50"; // named export
133+
// ... and many more
134+
135+
codename(input: number): string
136+
```
137+
138+
All APIs support the same input types:
139+
140+
- Positive integers: `123`, `1234`
141+
- Negative integers: `-42`
142+
- Decimals: `3.14` (converted to integers internally)
143+
- Large numbers: up to `Number.MAX_SAFE_INTEGER`
144+
145+
Supported list sizes are 10, 20, 30, 50, and 100. The default theme uses 20 words.
67146

68147
## Command Line Interface
69148

@@ -132,18 +211,20 @@ done
132211

133212
## Word Lists
134213

135-
- **Adjectives**: good, bad, big, small, new, old, hot, cold, fast, slow, ...
136-
- **Animals**: cat, dog, fish, bird, cow, pig, bee, ant, bat, fly, ...
137-
- **Cities**: paris, london, rome, tokyo, berlin, madrid, sydney, moscow, cairo, dubai, ...
138-
- **Clothing**: shirt, jeans, shoe, hat, sock, dress, coat, belt, tie, pants, ...
139-
- **Colors**: red, blue, green, yellow, black, white, gray, pink, orange, purple, ...
140-
- **Countries**: china, japan, india, france, italy, spain, canada, mexico, brazil, germany, ...
141-
- **Elements**: gold, iron, lead, zinc, tin, copper, silver, carbon, oxygen, helium, ...
142-
- **Emotions**: love, hate, joy, sad, fear, mad, happy, angry, glad, calm, ...
143-
- **Food**: bread, milk, egg, rice, meat, fish, cake, apple, cheese, pasta, ...
144-
- **Gems**: ruby, pearl, jade, opal, amber, diamond, emerald, gold, silver, topaz, ...
145-
- **Nature**: tree, sun, sky, rain, moon, star, wind, sea, water, rock, ...
146-
- **Snacks**: chips, nuts, cookie, pretzel, popcorn, candy, fruit, cheese, cracker, yogurt, ...
214+
Each theme is available in multiple sizes: 10, 20, 30, 50, and 100 words. Choose based on your collision tolerance needs.
215+
216+
- **[Adjectives](words/adjectives.txt)**: good, bad, big, small, new, old, hot, cold, fast, slow, ...
217+
- **[Animals](words/animals.txt)**: cat, dog, fish, bird, cow, pig, bee, ant, bat, fly, ...
218+
- **[Cities](words/cities.txt)**: paris, london, rome, tokyo, berlin, madrid, sydney, moscow, cairo, dubai, ...
219+
- **[Clothing](words/clothing.txt)**: shirt, jeans, shoe, hat, sock, dress, coat, belt, tie, pants, ...
220+
- **[Colors](words/colors.txt)**: red, blue, green, yellow, black, white, gray, pink, orange, purple, ...
221+
- **[Countries](words/countries.txt)**: china, japan, india, france, italy, spain, canada, mexico, brazil, germany, ...
222+
- **[Elements](words/elements.txt)**: gold, iron, lead, zinc, tin, copper, silver, carbon, oxygen, helium, ...
223+
- **[Emotions](words/emotions.txt)**: love, hate, joy, sad, fear, mad, happy, angry, glad, calm, ...
224+
- **[Food](words/food.txt)**: bread, milk, egg, rice, meat, fish, cake, apple, cheese, pasta, ...
225+
- **[Gems](words/gems.txt)**: ruby, pearl, jade, opal, amber, diamond, emerald, gold, silver, topaz, ...
226+
- **[Nature](words/nature.txt)**: tree, sun, sky, rain, moon, star, wind, sea, water, rock, ...
227+
- **[Snacks](words/snacks.txt)**: chips, nuts, cookie, pretzel, popcorn, candy, fruit, cheese, cracker, yogurt, ...
147228

148229
## Contributing
149230

cities.test.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
/* SPDX-FileCopyrightText: 2025-present Kriasoft */
2+
/* SPDX-License-Identifier: MIT */
3+
14
import { describe, expect, test } from "bun:test";
2-
import cityCodename, { cities, type City } from "./words/cities-20";
5+
import cityCodename, { cities, codename, type City } from "./words/cities-20";
36

47
describe("cities-20 theme", () => {
58
describe("structure validation", () => {
@@ -26,37 +29,54 @@ describe("cities-20 theme", () => {
2629
expect(cities).toContain(result);
2730
});
2831

32+
test("exports named codename function", () => {
33+
const result = codename(1234);
34+
expect(cities).toContain(result);
35+
expect(result).toBe(cityCodename(1234));
36+
});
37+
2938
test("returns consistent results", () => {
3039
expect(cityCodename(1)).toBe(cityCodename(1));
40+
expect(codename(1)).toBe(codename(1));
3141
expect(cityCodename(1234)).toBe(cityCodename(1234));
42+
expect(codename(1234)).toBe(codename(1234));
3243
expect(cityCodename(-42)).toBe(cityCodename(-42));
44+
expect(codename(-42)).toBe(codename(-42));
3345
});
3446

3547
test("TypeScript types work correctly", () => {
3648
const city: City = cityCodename(123);
3749
expect(cities).toContain(city);
3850

39-
const typedResult: City = cityCodename(1234);
51+
const typedResult: City = codename(1234);
4052
expect(typedResult).toBe(cityCodename(1234));
4153
});
4254

4355
test("works as factory-created codename function", () => {
4456
expect(typeof cityCodename).toBe("function");
57+
expect(typeof codename).toBe("function");
4558
const result = cityCodename(1234);
4659
expect(cities).toContain(result);
4760
});
4861

4962
test("handles various inputs", () => {
5063
expect(cities).toContain(cityCodename(0));
64+
expect(cities).toContain(codename(0));
5165
expect(cities).toContain(cityCodename(-1));
66+
expect(cities).toContain(codename(-1));
5267
expect(cities).toContain(cityCodename(999999));
68+
expect(cities).toContain(codename(999999));
5369
expect(cities).toContain(cityCodename(Number.MAX_SAFE_INTEGER));
70+
expect(cities).toContain(codename(Number.MAX_SAFE_INTEGER));
5471
});
5572

5673
test("throws on invalid inputs", () => {
5774
expect(() => cityCodename(NaN)).toThrow();
75+
expect(() => codename(NaN)).toThrow();
5876
expect(() => cityCodename(Infinity)).toThrow();
77+
expect(() => codename(Infinity)).toThrow();
5978
expect(() => cityCodename(-Infinity)).toThrow();
79+
expect(() => codename(-Infinity)).toThrow();
6080
});
6181
});
6282
});

0 commit comments

Comments
 (0)