@dephub/slug 1.0.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 +21 -0
- package/README.md +201 -0
- package/dist/cli.js +33 -0
- package/dist/core/compare.d.ts +9 -0
- package/dist/core/compare.js +5 -0
- package/dist/core/slug.d.ts +16 -0
- package/dist/core/slug.js +13 -0
- package/dist/core/valid.d.ts +6 -0
- package/dist/core/valid.js +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +9 -0
- package/dist/package.json.js +11 -0
- package/dist/utils/pkg.d.ts +1 -0
- package/dist/utils/pkg.js +7 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Estarlin R
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# @dephub/slug 🔤
|
|
2
|
+
|
|
3
|
+
> URL-friendly slug generator with semantic comparison and validation. Convert strings to clean slugs with full Unicode support.
|
|
4
|
+
|
|
5
|
+
[](https://npmjs.org/package/@dephub/slug)
|
|
6
|
+
[](https://nodejs.org/)
|
|
7
|
+
[](https://nodejs.org/)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Features ✨
|
|
12
|
+
|
|
13
|
+
- 🎯 **Simple API** - `slug()`, `isSlug()`, `compare()`
|
|
14
|
+
- 🔒 **Type Safe** - Full TypeScript support with zero configuration
|
|
15
|
+
- 🌍 **Unicode Support** - Handles accented characters and diacritics
|
|
16
|
+
- 🛠️ **Dual Interface** - Use as CLI tool or programmatic API
|
|
17
|
+
- ⚡ **Lightweight** - Zero dependencies, focused functionality
|
|
18
|
+
- 🔧 **Configurable** - Custom separators, case handling, and fallbacks
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Installation 📲
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @dephub/slug
|
|
26
|
+
# or
|
|
27
|
+
pnpm add @dephub/slug
|
|
28
|
+
# or
|
|
29
|
+
yarn add @dephub/slug
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Usage 🎉
|
|
35
|
+
|
|
36
|
+
### CLI Usage
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Generate slugs
|
|
40
|
+
slug generate "Hello World!"
|
|
41
|
+
# Output: hello-world
|
|
42
|
+
|
|
43
|
+
slug generate "Café & Bar" --separator "_"
|
|
44
|
+
# Output: cafe_bar
|
|
45
|
+
|
|
46
|
+
# Validate slugs
|
|
47
|
+
slug validate "valid-slug-123"
|
|
48
|
+
# Output: ✅ Valid slug format
|
|
49
|
+
|
|
50
|
+
slug validate "Invalid Slug!"
|
|
51
|
+
# Output: ❌ Invalid slug format
|
|
52
|
+
|
|
53
|
+
# Compare strings
|
|
54
|
+
slug compare "Café au lait" "cafe-au-lait"
|
|
55
|
+
# Output: ✅ Strings are equivalent when slugified
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Programmatic Usage
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { slug, isSlug, compare } from '@dephub/slug';
|
|
62
|
+
|
|
63
|
+
// Basic slug generation
|
|
64
|
+
slug(' Héllo Wörld!!! '); // "hello-world"
|
|
65
|
+
|
|
66
|
+
// Custom options
|
|
67
|
+
slug('Hello World', { separator: '_' }); // "hello_world"
|
|
68
|
+
slug('Hello World', { lowercase: false }); // "Hello-World"
|
|
69
|
+
slug('###', { fallback: 'home' }); // "home"
|
|
70
|
+
|
|
71
|
+
// Validation
|
|
72
|
+
isSlug('valid-slug-name'); // true
|
|
73
|
+
isSlug('Invalid Slug!'); // false
|
|
74
|
+
|
|
75
|
+
// Semantic comparison
|
|
76
|
+
compare('Café au lait', 'cafe-au-lait'); // true
|
|
77
|
+
compare('user-profile', 'user_profile'); // false
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Advanced Examples
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// SEO-friendly URLs
|
|
84
|
+
const title = 'My Awesome Blog Post!';
|
|
85
|
+
const urlSlug = slug(title); // "my-awesome-blog-post"
|
|
86
|
+
|
|
87
|
+
// Username normalization
|
|
88
|
+
const username = slug(userInput, { separator: '_' });
|
|
89
|
+
if (!isSlug(username)) {
|
|
90
|
+
throw new Error('Invalid username format');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Database lookup with semantic comparison
|
|
94
|
+
function findProduct(name: string) {
|
|
95
|
+
return products.find((product) => compare(product.name, name));
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## API Reference 📚
|
|
102
|
+
|
|
103
|
+
### `slug(str, options?)`
|
|
104
|
+
|
|
105
|
+
Convert string to URL-friendly slug.
|
|
106
|
+
|
|
107
|
+
**Parameters:**
|
|
108
|
+
|
|
109
|
+
- `str` (string) - Input string to convert
|
|
110
|
+
- `options` (SlugOptions) - Configuration options
|
|
111
|
+
- `separator` (string) - Character to replace spaces (default: `'-'`)
|
|
112
|
+
- `lowercase` (boolean) - Convert to lowercase (default: `true`)
|
|
113
|
+
- `fallback` (string) - Fallback if result empty (default: `'default-slug'`)
|
|
114
|
+
|
|
115
|
+
**Returns:** `string`
|
|
116
|
+
|
|
117
|
+
**Throws:** `TypeError` if input is not a string
|
|
118
|
+
|
|
119
|
+
### `isSlug(str)`
|
|
120
|
+
|
|
121
|
+
Check if string matches valid slug format (lowercase, hyphens, alphanumeric).
|
|
122
|
+
|
|
123
|
+
**Parameters:**
|
|
124
|
+
|
|
125
|
+
- `str` (string) - String to validate
|
|
126
|
+
|
|
127
|
+
**Returns:** `boolean`
|
|
128
|
+
|
|
129
|
+
### `compare(a, b, options?)`
|
|
130
|
+
|
|
131
|
+
Compare two strings by their slugified versions.
|
|
132
|
+
|
|
133
|
+
**Parameters:**
|
|
134
|
+
|
|
135
|
+
- `a` (string) - First string to compare
|
|
136
|
+
- `b` (string) - Second string to compare
|
|
137
|
+
- `options` (SlugOptions) - Slug configuration options
|
|
138
|
+
|
|
139
|
+
**Returns:** `boolean`
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## CLI Commands
|
|
144
|
+
|
|
145
|
+
### `slug generate <input>`
|
|
146
|
+
|
|
147
|
+
Convert string to URL-friendly slug.
|
|
148
|
+
|
|
149
|
+
**Options:**
|
|
150
|
+
|
|
151
|
+
- `--separator` - Separator character (default: "-")
|
|
152
|
+
- `--no-lowercase` - Keep original case
|
|
153
|
+
- `--fallback` - Fallback text if result is empty
|
|
154
|
+
|
|
155
|
+
### `slug validate <input>`
|
|
156
|
+
|
|
157
|
+
Check if string is valid slug format.
|
|
158
|
+
|
|
159
|
+
### `slug compare <first> <second>`
|
|
160
|
+
|
|
161
|
+
Compare two strings by slugified versions.
|
|
162
|
+
|
|
163
|
+
**Options:**
|
|
164
|
+
|
|
165
|
+
- `--separator` - Separator character
|
|
166
|
+
- `--no-lowercase` - Keep original case
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Integration Examples
|
|
171
|
+
|
|
172
|
+
### With File System
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import { slug } from '@dephub/slug';
|
|
176
|
+
import { writeFile } from '@dephub/write';
|
|
177
|
+
|
|
178
|
+
async function createSluggedFile(content: string, filename: string) {
|
|
179
|
+
const sluggedName = slug(filename);
|
|
180
|
+
await writeFile(`./${sluggedName}.txt`, content);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### With Configuration
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { slug, compare } from '@dephub/slug';
|
|
188
|
+
import { log } from '@dephub/log';
|
|
189
|
+
|
|
190
|
+
const categoryName = 'User Settings';
|
|
191
|
+
const normalized = slug(categoryName, { separator: '_' });
|
|
192
|
+
log(`Category: ${normalized}`); // "user_settings"
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## License 📄
|
|
198
|
+
|
|
199
|
+
MIT License – see [LICENSE](LICENSE) for details.
|
|
200
|
+
|
|
201
|
+
**Author:** Estarlin R ([estarlincito.com](https://estarlincito.com))
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cli as a } from "@dephub/cli";
|
|
3
|
+
import { log as l, error as n, success as c } from "@dephub/log";
|
|
4
|
+
import { compare as p } from "./core/compare.js";
|
|
5
|
+
import { slug as m } from "./core/slug.js";
|
|
6
|
+
import { isSlug as g } from "./core/valid.js";
|
|
7
|
+
import { name as u, version as f, description as d } from "./utils/pkg.js";
|
|
8
|
+
a.name(u.split("/")[1] ?? "slug").version(f).description(d);
|
|
9
|
+
a.command("generate", "Convert string to URL-friendly slug").argument("<input>", "Input string to convert").option("--separator", 'Separator character (default: "-")').flag("--no-lowercase", "Keep original case").option("--fallback", "Fallback text if result is empty").action(({ args: r, options: e, flags: o }) => {
|
|
10
|
+
const [s] = r;
|
|
11
|
+
try {
|
|
12
|
+
const t = {
|
|
13
|
+
fallback: e.fallback,
|
|
14
|
+
lowercase: o.lowercase,
|
|
15
|
+
separator: e.separator
|
|
16
|
+
}, i = m(s, t);
|
|
17
|
+
l(i);
|
|
18
|
+
} catch (t) {
|
|
19
|
+
n(t.message), process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
a.command("validate", "Check if string is valid slug format").argument("<input>", "String to validate").action(({ args: r }) => {
|
|
23
|
+
const [e] = r;
|
|
24
|
+
g(e) ? c("Valid slug format") : (n("Invalid slug format"), process.exit(1));
|
|
25
|
+
});
|
|
26
|
+
a.command("compare", "Compare two strings by slugified versions").argument("<first>", "First string to compare").argument("<second>", "Second string to compare").option("--separator", "Separator character").flag("--no-lowercase", "Keep original case").action(({ args: r, options: e, flags: o }) => {
|
|
27
|
+
const [s, t] = r, i = {
|
|
28
|
+
lowercase: o.lowercase,
|
|
29
|
+
separator: e.separator
|
|
30
|
+
};
|
|
31
|
+
p(s, t, i) ? c("Strings are equivalent when slugified") : (n("Strings are different when slugified"), process.exit(1));
|
|
32
|
+
});
|
|
33
|
+
await a.run();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SlugOptions } from './slug.js';
|
|
2
|
+
/**
|
|
3
|
+
* Compare two strings by their slugified versions
|
|
4
|
+
* @param a - First string to compare
|
|
5
|
+
* @param b - Second string to compare
|
|
6
|
+
* @param options - Slug options for comparison
|
|
7
|
+
* @returns True if slugified strings are equal
|
|
8
|
+
*/
|
|
9
|
+
export declare const compare: (a: string, b: string, options?: SlugOptions) => boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert string to URL-friendly slug
|
|
3
|
+
*/
|
|
4
|
+
export interface SlugOptions {
|
|
5
|
+
separator?: string;
|
|
6
|
+
lowercase?: boolean;
|
|
7
|
+
fallback?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Convert string to URL-friendly slug
|
|
11
|
+
* @param str - Input string to convert
|
|
12
|
+
* @param options - Slug configuration options
|
|
13
|
+
* @returns URL-friendly slug string
|
|
14
|
+
* @throws {TypeError} If input is not a string
|
|
15
|
+
*/
|
|
16
|
+
export declare const slug: (str: string, options?: SlugOptions) => string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const o = (r, t = {}) => {
|
|
2
|
+
if (typeof r != "string") throw new TypeError("Input must be string");
|
|
3
|
+
const {
|
|
4
|
+
separator: a = "-",
|
|
5
|
+
lowercase: l = !0,
|
|
6
|
+
fallback: s = "default-slug"
|
|
7
|
+
} = t;
|
|
8
|
+
let e = r.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-zA-Z0-9\s]/g, "").trim().replace(/\s+/g, a);
|
|
9
|
+
return l && (e = e.toLowerCase()), e || s;
|
|
10
|
+
};
|
|
11
|
+
export {
|
|
12
|
+
o as slug
|
|
13
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const version: string, name: string, description: string;
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dephub/slug",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "URL-friendly slug generator with semantic comparison and validation",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"slug": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"slug",
|
|
13
|
+
"url",
|
|
14
|
+
"seo",
|
|
15
|
+
"string",
|
|
16
|
+
"normalize",
|
|
17
|
+
"comparison",
|
|
18
|
+
"validation",
|
|
19
|
+
"typescript",
|
|
20
|
+
"nodejs",
|
|
21
|
+
"dephub"
|
|
22
|
+
],
|
|
23
|
+
"author": {
|
|
24
|
+
"name": "Estarlin R",
|
|
25
|
+
"email": "dev@estarlincito.com",
|
|
26
|
+
"url": "https://estarlincito.com"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18.0.0"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"LICENSE",
|
|
33
|
+
"README.md",
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"homepage": "https://github.com/dephubjs/slug#readme",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/dephubjs/slug.git",
|
|
41
|
+
"directory": "packages/slug"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/dephubjs/slug/issues"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@dephub/cli": "^1.0.0",
|
|
51
|
+
"@dephub/log": "^1.0.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"release": "pnpm publish",
|
|
55
|
+
"check-types": "tsc --noEmit --skipLibCheck",
|
|
56
|
+
"lint": "lint . --max-warnings 0",
|
|
57
|
+
"lint:fix": "lint . --fix",
|
|
58
|
+
"clean": "rm -rf dist",
|
|
59
|
+
"build": "vite build",
|
|
60
|
+
"dev": "vite build -w --mode development"
|
|
61
|
+
}
|
|
62
|
+
}
|