utilitish 0.0.8 → 0.0.10
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 +60 -0
- package/dist/array/array-prototype.d.ts +5 -3
- package/dist/array/array-prototype.js +20 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -0
- package/dist/map/map-prototype.js +11 -2
- package/dist/string/string-prototype.d.ts +45 -7
- package/dist/string/string-prototype.js +3 -6
- package/dist/string/string-prototype.spec.js +89 -1
- package/dist/utils/logic.utils.d.ts +0 -1
- package/dist/utils/logic.utils.js +0 -17
- package/dist/utils/slugify.config.d.ts +98 -0
- package/dist/utils/slugify.config.js +271 -0
- package/dist/utils/slugify.utils.d.ts +10 -0
- package/dist/utils/slugify.utils.js +18 -0
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -3,3 +3,63 @@
|
|
|
3
3
|
Ce projet est distribué sous licence [GPL v3](https://www.gnu.org/licenses/gpl-3.0.html) © 2025 Donovan Ferreira.
|
|
4
4
|
|
|
5
5
|
Ceci est une petite librairie qui ajoute plusieurs méthodes au prototype JavaScript.
|
|
6
|
+
|
|
7
|
+
## Configuration Slugify
|
|
8
|
+
|
|
9
|
+
La méthode `slugify()` peut être personnalisée pour répondre à vos besoins spécifiques. Consultez la JSDoc dans votre IDE pour voir tous les exemples disponibles.
|
|
10
|
+
|
|
11
|
+
### Configuration globale
|
|
12
|
+
|
|
13
|
+
Vous pouvez définir une configuration globale qui s'appliquera à tous les appels `slugify()` :
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { setSlugifyConfig } from 'utilitish';
|
|
17
|
+
|
|
18
|
+
// Configuration pour remplacer les symboles de genre
|
|
19
|
+
setSlugifyConfig({
|
|
20
|
+
customReplacements: {
|
|
21
|
+
'♀': 'feminin',
|
|
22
|
+
'♂': 'masculin',
|
|
23
|
+
},
|
|
24
|
+
separator: '_',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
'Test ♀'.slugify(); // "test_feminin"
|
|
28
|
+
'User♂@domain.com'.slugify(); // "user_masculin_at_domain_com"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Configuration par appel
|
|
32
|
+
|
|
33
|
+
Vous pouvez aussi passer une configuration spécifique à chaque appel :
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// Utilise la config globale
|
|
37
|
+
'Hello World'.slugify(); // "hello-world"
|
|
38
|
+
|
|
39
|
+
// Override la config pour cet appel
|
|
40
|
+
'Hello World'.slugify({ separator: '_' }); // "hello_world"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Options de configuration
|
|
44
|
+
|
|
45
|
+
- **`customReplacements`**: Remplacer des caractères spécifiques (ex: "♀" → "feminin")
|
|
46
|
+
- **`separator`**: Caractère de séparation (défaut: "-")
|
|
47
|
+
- **`lowercase`**: Convertir en minuscules (défaut: true)
|
|
48
|
+
- **`removeAccents`**: Supprimer les accents (défaut: true)
|
|
49
|
+
- **`allowedChars`**: Regex des caractères autorisés (défaut: /[a-zA-Z0-9]/)
|
|
50
|
+
- **`maxLength`**: Longueur maximale du slug
|
|
51
|
+
- **`transformers`**: Fonctions de transformation personnalisées
|
|
52
|
+
|
|
53
|
+
### Gestion de la configuration
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { getSlugifyConfig, resetSlugifyConfig } from 'utilitish';
|
|
57
|
+
|
|
58
|
+
// Obtenir la config actuelle
|
|
59
|
+
const currentConfig = getSlugifyConfig();
|
|
60
|
+
|
|
61
|
+
// Réinitialiser aux valeurs par défaut
|
|
62
|
+
resetSlugifyConfig();
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Pour plus d'exemples et cas d'usage avancés, consultez les JSDoc intégrées dans votre IDE.
|
|
@@ -47,7 +47,7 @@ declare global {
|
|
|
47
47
|
* - Returns 0 for empty arrays regardless of type
|
|
48
48
|
*/
|
|
49
49
|
sum(this: number[]): number;
|
|
50
|
-
sum(this: T[], selector
|
|
50
|
+
sum(this: T[], selector: Selector<T, number>): number;
|
|
51
51
|
/**
|
|
52
52
|
* Returns a new array with only unique elements based on strict equality (===).
|
|
53
53
|
* Preserves order of first occurrence.
|
|
@@ -187,7 +187,8 @@ declare global {
|
|
|
187
187
|
* - Returns new array; does not mutate original
|
|
188
188
|
* - Empty arrays return empty array
|
|
189
189
|
*/
|
|
190
|
-
sortAsc(this:
|
|
190
|
+
sortAsc(this: (number | string)[]): T[];
|
|
191
|
+
sortAsc(this: T[], selector: Selector<T, number | string>): T[];
|
|
191
192
|
/**
|
|
192
193
|
* Returns a new sorted copy of the array in descending order.
|
|
193
194
|
* Creates a new array without modifying the original.
|
|
@@ -210,7 +211,8 @@ declare global {
|
|
|
210
211
|
* - Returns new array; does not mutate original
|
|
211
212
|
* - Empty arrays return empty array
|
|
212
213
|
*/
|
|
213
|
-
sortDesc(this:
|
|
214
|
+
sortDesc(this: (number | string)[]): T[];
|
|
215
|
+
sortDesc(this: T[], selector: Selector<T, number | string>): T[];
|
|
214
216
|
/**
|
|
215
217
|
* Swaps the elements at two indices within the array.
|
|
216
218
|
* Modifies the array in place and returns the array itself (for chaining).
|
|
@@ -121,8 +121,26 @@ const logic_utils_1 = require("../utils/logic.utils");
|
|
|
121
121
|
return map;
|
|
122
122
|
});
|
|
123
123
|
(0, core_utils_1.defineIfNotExists)(Array.prototype, 'toObject', function (keySelector, valueSelector) {
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
let entries;
|
|
125
|
+
if (!keySelector && this.length && this.every((item) => Array.isArray(item) && item.length === 2)) {
|
|
126
|
+
entries = this;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const getKey = (0, core_utils_1.resolveSelector)(keySelector, (_, index) => index);
|
|
130
|
+
const getValue = (0, core_utils_1.resolveSelector)(valueSelector, (item) => item);
|
|
131
|
+
entries = this.map((item, index) => [getKey(item, index), getValue(item)]);
|
|
132
|
+
}
|
|
133
|
+
// Validate keys
|
|
134
|
+
for (const [key] of entries) {
|
|
135
|
+
if (key === null || key === undefined) {
|
|
136
|
+
throw new TypeError(`Invalid key: key cannot be null or undefined. Key received: ${String(key)}`);
|
|
137
|
+
}
|
|
138
|
+
const keyType = typeof key;
|
|
139
|
+
if (keyType !== 'string' && keyType !== 'number' && keyType !== 'symbol') {
|
|
140
|
+
throw new TypeError(`Invalid key type: keys must be string, number, or symbol, received ${keyType}. Key value: ${String(key)}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return Object.fromEntries(entries);
|
|
126
144
|
});
|
|
127
145
|
(0, core_utils_1.defineIfNotExists)(Array.prototype, 'toSet', function (selector) {
|
|
128
146
|
const getValue = (0, core_utils_1.resolveSelector)(selector, (item) => item);
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setSlugifyConfig = exports.resetSlugifyConfig = exports.getSlugifyConfig = void 0;
|
|
3
4
|
require("./array/array-constructor");
|
|
4
5
|
require("./array/array-prototype");
|
|
5
6
|
require("./map/map-prototype");
|
|
6
7
|
require("./object/object-prototype");
|
|
7
8
|
require("./set/set-prototype");
|
|
8
9
|
require("./string/string-prototype");
|
|
10
|
+
// Export slugify configuration functions
|
|
11
|
+
var slugify_config_1 = require("./utils/slugify.config");
|
|
12
|
+
Object.defineProperty(exports, "getSlugifyConfig", { enumerable: true, get: function () { return slugify_config_1.getSlugifyConfig; } });
|
|
13
|
+
Object.defineProperty(exports, "resetSlugifyConfig", { enumerable: true, get: function () { return slugify_config_1.resetSlugifyConfig; } });
|
|
14
|
+
Object.defineProperty(exports, "setSlugifyConfig", { enumerable: true, get: function () { return slugify_config_1.setSlugifyConfig; } });
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const core_utils_1 = require("../utils/core.utils");
|
|
4
|
-
const logic_utils_1 = require("../utils/logic.utils");
|
|
5
4
|
/**
|
|
6
5
|
* @see Map.prototype.toList
|
|
7
6
|
*/
|
|
@@ -34,7 +33,17 @@ const logic_utils_1 = require("../utils/logic.utils");
|
|
|
34
33
|
* @see Map.prototype.toObject
|
|
35
34
|
*/
|
|
36
35
|
(0, core_utils_1.defineIfNotExists)(Map.prototype, 'toObject', function () {
|
|
37
|
-
|
|
36
|
+
const entries = Array.from(this.entries());
|
|
37
|
+
for (const [key] of entries) {
|
|
38
|
+
if (key === null || key === undefined) {
|
|
39
|
+
throw new TypeError(`Invalid key: key cannot be null or undefined. Key received: ${String(key)}`);
|
|
40
|
+
}
|
|
41
|
+
const keyType = typeof key;
|
|
42
|
+
if (keyType !== 'string' && keyType !== 'number' && keyType !== 'symbol') {
|
|
43
|
+
throw new TypeError(`Invalid key type: keys must be string, number, or symbol, received ${keyType}. Key value: ${String(key)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return Object.fromEntries(entries);
|
|
38
47
|
});
|
|
39
48
|
/**
|
|
40
49
|
* @see Map.prototype.ensureArray
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SlugifyConfig } from '../utils/slugify.config';
|
|
1
2
|
declare global {
|
|
2
3
|
interface String {
|
|
3
4
|
/**
|
|
@@ -137,24 +138,62 @@ declare global {
|
|
|
137
138
|
/**
|
|
138
139
|
* Converts the string into a URL-friendly slug format.
|
|
139
140
|
* Combines normalization, lowercasing, whitespace handling, and special character removal.
|
|
141
|
+
* Can be customized using global configuration or per-call options.
|
|
140
142
|
*
|
|
141
143
|
* @this {string} The string to slugify
|
|
144
|
+
* @param {SlugifyConfig} [config] - Optional configuration to override global settings
|
|
142
145
|
* @returns {string} A URL-safe slug version of the string
|
|
143
146
|
*
|
|
144
147
|
* @example
|
|
148
|
+
* // Basic usage (default config)
|
|
145
149
|
* 'Hello World'.slugify(); // 'hello-world'
|
|
146
150
|
* 'Héllo Wørld'.slugify(); // 'hello-world'
|
|
147
151
|
* 'Hello World!!!'.slugify(); // 'hello-world'
|
|
148
152
|
*
|
|
153
|
+
* @example
|
|
154
|
+
* // Custom replacements
|
|
155
|
+
* 'Test ♀'.slugify(SlugifyConfig.builder().withCustomReplacements({ "♀": "feminin" }).build()); // 'test-feminin'
|
|
156
|
+
* 'User♂@domain.com'.slugify(SlugifyConfig.builder().withCustomReplacements({ "♂": "masculin", "@": "at" }).build()); // 'usermasculinatdomain-com'
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* // Custom separator
|
|
160
|
+
* 'Hello World'.slugify(SlugifyConfig.builder().withSeparator("_").build()); // 'hello_world'
|
|
161
|
+
* 'Hello World'.slugify(SlugifyConfig.builder().withSeparator("--").build()); // 'hello--world'
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* // Preserve accents
|
|
165
|
+
* 'Éléphant'.slugify(SlugifyConfig.builder().withRemoveAccents(false).build()); // 'éléphant'
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* // Disable lowercasing
|
|
169
|
+
* 'Hello World'.slugify(SlugifyConfig.builder().withCase('default').build()); // 'Hello-World'
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* // Max length
|
|
173
|
+
* 'Very long string'.slugify(SlugifyConfig.builder().withMaxLength(8).build()); // 'very-lon'
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* // Custom transformers
|
|
177
|
+
* 'hello world'.slugify(SlugifyConfig.builder()
|
|
178
|
+
* .withTransformers([(str) => str.replace(/world/g, 'universe')])
|
|
179
|
+
* .build()); // 'hello-universe'
|
|
180
|
+
*
|
|
149
181
|
* @remarks
|
|
150
|
-
* -
|
|
151
|
-
* -
|
|
152
|
-
* -
|
|
153
|
-
* -
|
|
154
|
-
* -
|
|
155
|
-
* -
|
|
182
|
+
* - Uses global configuration by default (see setSlugifyConfig)
|
|
183
|
+
* - Normalizes Unicode characters (NFD decomposition) by default
|
|
184
|
+
* - Removes accents and diacritical marks by default
|
|
185
|
+
* - Converts to lowercase by default
|
|
186
|
+
* - Replaces spaces and special characters with separator (hyphen by default)
|
|
187
|
+
* - Removes leading/trailing separators
|
|
188
|
+
* - Collapses multiple consecutive separators into one
|
|
189
|
+
*
|
|
190
|
+
* @see SlugifyConfig
|
|
191
|
+
* @see setSlugifyConfig
|
|
192
|
+
* @see getSlugifyConfig
|
|
193
|
+
* @see resetSlugifyConfig
|
|
156
194
|
*/
|
|
157
195
|
slugify(): string;
|
|
196
|
+
slugify(config: SlugifyConfig): string;
|
|
158
197
|
/**
|
|
159
198
|
* Replaces a substring between `start` and `end` indices with a given string.
|
|
160
199
|
* Provides precise control over which portion of the string to replace.
|
|
@@ -178,4 +217,3 @@ declare global {
|
|
|
178
217
|
replaceRange(start: number, end: number, replaceString?: string): string;
|
|
179
218
|
}
|
|
180
219
|
}
|
|
181
|
-
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const core_utils_1 = require("../utils/core.utils");
|
|
4
|
+
const slugify_utils_1 = require("../utils/slugify.utils");
|
|
4
5
|
/**
|
|
5
6
|
* @see String.prototype.splitWords
|
|
6
7
|
*/
|
|
@@ -61,12 +62,8 @@ const core_utils_1 = require("../utils/core.utils");
|
|
|
61
62
|
/**
|
|
62
63
|
* @see String.prototype.slugify
|
|
63
64
|
*/
|
|
64
|
-
(0, core_utils_1.defineIfNotExists)(String.prototype, 'slugify', function () {
|
|
65
|
-
return
|
|
66
|
-
.replace(/[̀-ͯ]/g, '')
|
|
67
|
-
.replace(/[^a-zA-Z0-9]+/g, '-')
|
|
68
|
-
.replace(/^-+|-+$/g, '')
|
|
69
|
-
.toLowerCase();
|
|
65
|
+
(0, core_utils_1.defineIfNotExists)(String.prototype, 'slugify', function (config) {
|
|
66
|
+
return (0, slugify_utils_1.slugifyString)(this, config);
|
|
70
67
|
});
|
|
71
68
|
/**
|
|
72
69
|
* @see String.prototype.capitalize
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
require("../string/string-prototype");
|
|
4
|
+
const slugify_config_1 = require("../utils/slugify.config");
|
|
4
5
|
describe('String.prototype', () => {
|
|
5
6
|
describe('capitalize()', () => {
|
|
6
7
|
it('should capitalize the first character', () => {
|
|
@@ -67,11 +68,98 @@ describe('String.prototype', () => {
|
|
|
67
68
|
});
|
|
68
69
|
});
|
|
69
70
|
describe('slugify()', () => {
|
|
70
|
-
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
(0, slugify_config_1.resetSlugifyConfig)(); // Reset to defaults before each test
|
|
73
|
+
});
|
|
74
|
+
it('should slugify a string with default config', () => {
|
|
71
75
|
expect('Hello World!'.slugify()).toBe('hello-world');
|
|
72
76
|
expect("Éléphant à l'été".slugify()).toBe('elephant-a-l-ete');
|
|
73
77
|
expect(' --Hello__World-- '.slugify()).toBe('hello-world');
|
|
74
78
|
});
|
|
79
|
+
describe('custom replacements', () => {
|
|
80
|
+
it('should apply custom replacements per call', () => {
|
|
81
|
+
expect('Test ♀'.slugify(slugify_config_1.SlugifyConfig.builder().withCustomReplacements({ '♀': 'feminin' }).build())).toBe('test-feminin');
|
|
82
|
+
expect('User♂@domain.com'.slugify(slugify_config_1.SlugifyConfig.builder().withCustomReplacements({ '♂': 'masculin', '@': 'at' }).build())).toBe('usermasculinatdomain-com');
|
|
83
|
+
});
|
|
84
|
+
it('should handle special regex characters in replacements', () => {
|
|
85
|
+
expect('Test [bracket]'.slugify(slugify_config_1.SlugifyConfig.builder().withCustomReplacements({ '[': 'left', ']': 'right' }).build())).toBe('test-leftbracketright');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe('separator customization', () => {
|
|
89
|
+
it('should use custom separator globally', () => {
|
|
90
|
+
(0, slugify_config_1.setSlugifyConfig)(slugify_config_1.SlugifyConfig.builder().withSeparator('_').build());
|
|
91
|
+
expect('Hello World'.slugify()).toBe('hello_world');
|
|
92
|
+
expect('Test String'.slugify()).toBe('test_string');
|
|
93
|
+
});
|
|
94
|
+
it('should use custom separator per call', () => {
|
|
95
|
+
expect('Hello World'.slugify(slugify_config_1.SlugifyConfig.builder().withSeparator('_').build())).toBe('hello_world');
|
|
96
|
+
expect('Test String'.slugify(slugify_config_1.SlugifyConfig.builder().withSeparator('__').build())).toBe('test__string');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe('case transformation', () => {
|
|
100
|
+
it('should respect lowercase setting globally', () => {
|
|
101
|
+
(0, slugify_config_1.setSlugifyConfig)(slugify_config_1.SlugifyConfig.builder().withCase('default').build());
|
|
102
|
+
expect('Hello World'.slugify()).toBe('Hello-World');
|
|
103
|
+
});
|
|
104
|
+
it('should respect lowercase setting per call', () => {
|
|
105
|
+
expect('Hello World'.slugify(slugify_config_1.SlugifyConfig.builder().withCase('default').build())).toBe('Hello-World');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
describe('accent removal', () => {
|
|
109
|
+
it('should respect removeAccents setting per call', () => {
|
|
110
|
+
expect('Éléphant'.slugify(slugify_config_1.SlugifyConfig.builder().withRemoveAccents(false).build())).toBe('éléphant');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe('allowed characters', () => {
|
|
114
|
+
it('should use custom allowed characters', () => {
|
|
115
|
+
expect('Hello123!@#'.slugify(slugify_config_1.SlugifyConfig.builder()
|
|
116
|
+
.withAllowedChars(/[a-zA-Z0-9_]/)
|
|
117
|
+
.build())).toBe('hello123');
|
|
118
|
+
});
|
|
119
|
+
it('should throw for invalid allowedChars regex in call', () => {
|
|
120
|
+
expect(() => slugify_config_1.SlugifyConfig.builder()
|
|
121
|
+
.withAllowedChars(/foo|bar/)
|
|
122
|
+
.build()).toThrowError(/Invalid allowedChars:/);
|
|
123
|
+
});
|
|
124
|
+
it('should throw for invalid allowedChars regex in global config', () => {
|
|
125
|
+
expect(() => (0, slugify_config_1.setSlugifyConfig)(slugify_config_1.SlugifyConfig.builder()
|
|
126
|
+
.withAllowedChars(/foo|bar/)
|
|
127
|
+
.build())).toThrowError(/Invalid allowedChars:/);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe('separator special characters', () => {
|
|
131
|
+
it('should handle regex-special separators without breaking', () => {
|
|
132
|
+
expect('a b+c'.slugify(slugify_config_1.SlugifyConfig.builder().withSeparator('.').build())).toBe('a.b.c');
|
|
133
|
+
expect('a.b.c'.slugify(slugify_config_1.SlugifyConfig.builder().withSeparator('*').build())).toBe('a*b*c');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
describe('max length', () => {
|
|
137
|
+
it('should truncate to max length', () => {
|
|
138
|
+
expect('very-long-string-here'.slugify(slugify_config_1.SlugifyConfig.builder().withMaxLength(10).build())).toBe('very-long');
|
|
139
|
+
expect('short'.slugify(slugify_config_1.SlugifyConfig.builder().withMaxLength(10).build())).toBe('short');
|
|
140
|
+
});
|
|
141
|
+
it('should remove trailing separator after truncation', () => {
|
|
142
|
+
expect('hello-world-test'.slugify(slugify_config_1.SlugifyConfig.builder().withMaxLength(8).build())).toBe('hello-wo');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
describe('custom transformers', () => {
|
|
146
|
+
it('should apply custom transformers', () => {
|
|
147
|
+
const config = slugify_config_1.SlugifyConfig.builder()
|
|
148
|
+
.withTransformers([
|
|
149
|
+
(str) => str.replace(/test/g, 'example'),
|
|
150
|
+
(str) => str.toUpperCase(),
|
|
151
|
+
])
|
|
152
|
+
.withCase('upper')
|
|
153
|
+
.build();
|
|
154
|
+
expect('this is a test'.slugify(config)).toBe('THIS-IS-A-EXAMPLE');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
describe('configuration merging', () => {
|
|
158
|
+
it('should merge global and local config correctly', () => {
|
|
159
|
+
// Local config should override global defaults
|
|
160
|
+
expect('Test ♀'.slugify(slugify_config_1.SlugifyConfig.builder().withSeparator('_').withCustomReplacements({ '♀': 'female' }).build())).toBe('test_female');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
75
163
|
});
|
|
76
164
|
describe('replaceRange()', () => {
|
|
77
165
|
it('should replace a single character at the given index', () => {
|
|
@@ -1,24 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.mapToObject = mapToObject;
|
|
4
3
|
exports.sortBy = sortBy;
|
|
5
4
|
const core_utils_1 = require("./core.utils");
|
|
6
|
-
function mapToObject(map) {
|
|
7
|
-
const obj = {};
|
|
8
|
-
for (const [key, value] of map) {
|
|
9
|
-
// Validate that key is not null or undefined
|
|
10
|
-
if (key === null || key === undefined) {
|
|
11
|
-
throw new TypeError(`Invalid key: key cannot be null or undefined. Key received: ${String(key)}`);
|
|
12
|
-
}
|
|
13
|
-
const keyType = typeof key;
|
|
14
|
-
// Only allow string, number, or symbol
|
|
15
|
-
if (keyType !== 'string' && keyType !== 'number' && keyType !== 'symbol') {
|
|
16
|
-
throw new TypeError(`Invalid key type: keys must be string, number, or symbol, received ${keyType}. Key value: ${String(key)}`);
|
|
17
|
-
}
|
|
18
|
-
obj[key] = value;
|
|
19
|
-
}
|
|
20
|
-
return obj;
|
|
21
|
-
}
|
|
22
5
|
function sortBy(arr, direction, selector) {
|
|
23
6
|
if (arr.length === 0)
|
|
24
7
|
return arr.slice();
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration class for the slugify functionality.
|
|
3
|
+
* Provides comprehensive customization options for URL slug generation.
|
|
4
|
+
*/
|
|
5
|
+
export declare class SlugifyConfig {
|
|
6
|
+
private readonly _customReplacements;
|
|
7
|
+
private readonly _separator;
|
|
8
|
+
private readonly _case;
|
|
9
|
+
private readonly _removeAccents;
|
|
10
|
+
private readonly _allowedChars?;
|
|
11
|
+
private readonly _maxLength?;
|
|
12
|
+
private readonly _transformers;
|
|
13
|
+
private constructor();
|
|
14
|
+
static builder(): SlugifyConfigBuilder;
|
|
15
|
+
static default(): SlugifyConfig;
|
|
16
|
+
static create(builder: SlugifyConfigBuilder): SlugifyConfig;
|
|
17
|
+
get customReplacements(): Record<string, string>;
|
|
18
|
+
get separator(): string;
|
|
19
|
+
get case(): 'lower' | 'upper' | 'default';
|
|
20
|
+
get removeAccents(): boolean;
|
|
21
|
+
get allowedChars(): RegExp | undefined;
|
|
22
|
+
get maxLength(): number | undefined;
|
|
23
|
+
get transformers(): Array<(str: string) => string>;
|
|
24
|
+
merge(other: SlugifyConfig): SlugifyConfig;
|
|
25
|
+
private escapeRegex;
|
|
26
|
+
getCharClass(): RegExp;
|
|
27
|
+
applyCustomReplacements(str: string): string;
|
|
28
|
+
applyTransformers(str: string): string;
|
|
29
|
+
applyRemoveAccents(str: string): string;
|
|
30
|
+
replaceNonAllowedChars(str: string): string;
|
|
31
|
+
trimSeparators(str: string): string;
|
|
32
|
+
applyCase(str: string): string;
|
|
33
|
+
applyMaxLength(str: string): string;
|
|
34
|
+
collapseSeparators(str: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Applies the slugify transformations to the given string using this configuration.
|
|
37
|
+
* @param str - The input string to slugify
|
|
38
|
+
* @returns The slugified string
|
|
39
|
+
*/
|
|
40
|
+
slugify(str: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Static method to slugify a string with a temporary configuration built from the builder.
|
|
43
|
+
* @param str - The input string to slugify
|
|
44
|
+
* @param builderFn - A function that configures the builder
|
|
45
|
+
* @returns The slugified string
|
|
46
|
+
*/
|
|
47
|
+
static slugifyWith(str: string, builderFn: (builder: SlugifyConfigBuilder) => SlugifyConfigBuilder): string;
|
|
48
|
+
}
|
|
49
|
+
export declare class SlugifyConfigBuilder {
|
|
50
|
+
customReplacements: Record<string, string>;
|
|
51
|
+
separator: string;
|
|
52
|
+
case: 'lower' | 'upper' | 'default';
|
|
53
|
+
removeAccents: boolean;
|
|
54
|
+
allowedChars?: RegExp;
|
|
55
|
+
maxLength?: number;
|
|
56
|
+
transformers: Array<(str: string) => string>;
|
|
57
|
+
withCustomReplacements(replacements: Record<string, string>): this;
|
|
58
|
+
withSeparator(separator: string): this;
|
|
59
|
+
withCase(caseValue: 'lower' | 'upper' | 'default'): this;
|
|
60
|
+
withRemoveAccents(remove: boolean): this;
|
|
61
|
+
withAllowedChars(regex?: RegExp): this;
|
|
62
|
+
withMaxLength(length?: number): this;
|
|
63
|
+
withTransformers(transformers: Array<(str: string) => string>): this;
|
|
64
|
+
build(): SlugifyConfig;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Default configuration for slugify operations.
|
|
68
|
+
* Provides sensible defaults that can be overridden by users.
|
|
69
|
+
*/
|
|
70
|
+
export declare function getDefaultSlugifyConfig(): SlugifyConfig;
|
|
71
|
+
/**
|
|
72
|
+
* Sets the global slugify configuration that will be used by default.
|
|
73
|
+
* Replaces the current global config with the provided config.
|
|
74
|
+
*
|
|
75
|
+
* @param config - Configuration object to set as global config
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* setSlugifyConfig(
|
|
79
|
+
* SlugifyConfig.builder()
|
|
80
|
+
* .withCustomReplacements({ "♀": "feminin", "♂": "masculin" })
|
|
81
|
+
* .withSeparator("_")
|
|
82
|
+
* .build()
|
|
83
|
+
* );
|
|
84
|
+
*/
|
|
85
|
+
export declare function assertCharClass(regex: RegExp): void;
|
|
86
|
+
export declare function assertSlugifyConfig(builderOrConfig: SlugifyConfigBuilder | SlugifyConfig): void;
|
|
87
|
+
export declare function setSlugifyConfig(config: SlugifyConfig): void;
|
|
88
|
+
/**
|
|
89
|
+
* Gets the current global slugify configuration.
|
|
90
|
+
* Returns the current global config instance.
|
|
91
|
+
*
|
|
92
|
+
* @returns Current global configuration object
|
|
93
|
+
*/
|
|
94
|
+
export declare function getSlugifyConfig(): SlugifyConfig;
|
|
95
|
+
/**
|
|
96
|
+
* Resets the global slugify configuration to default values.
|
|
97
|
+
*/
|
|
98
|
+
export declare function resetSlugifyConfig(): void;
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SlugifyConfigBuilder = exports.SlugifyConfig = void 0;
|
|
4
|
+
exports.getDefaultSlugifyConfig = getDefaultSlugifyConfig;
|
|
5
|
+
exports.assertCharClass = assertCharClass;
|
|
6
|
+
exports.assertSlugifyConfig = assertSlugifyConfig;
|
|
7
|
+
exports.setSlugifyConfig = setSlugifyConfig;
|
|
8
|
+
exports.getSlugifyConfig = getSlugifyConfig;
|
|
9
|
+
exports.resetSlugifyConfig = resetSlugifyConfig;
|
|
10
|
+
/**
|
|
11
|
+
* Configuration class for the slugify functionality.
|
|
12
|
+
* Provides comprehensive customization options for URL slug generation.
|
|
13
|
+
*/
|
|
14
|
+
class SlugifyConfig {
|
|
15
|
+
constructor(builder) {
|
|
16
|
+
this._customReplacements = builder.customReplacements;
|
|
17
|
+
this._separator = builder.separator;
|
|
18
|
+
this._case = builder.case;
|
|
19
|
+
this._removeAccents = builder.removeAccents;
|
|
20
|
+
this._allowedChars = builder.allowedChars;
|
|
21
|
+
this._maxLength = builder.maxLength;
|
|
22
|
+
this._transformers = builder.transformers;
|
|
23
|
+
}
|
|
24
|
+
static builder() {
|
|
25
|
+
return new SlugifyConfigBuilder();
|
|
26
|
+
}
|
|
27
|
+
static default() {
|
|
28
|
+
return SlugifyConfig.builder().build();
|
|
29
|
+
}
|
|
30
|
+
static create(builder) {
|
|
31
|
+
assertSlugifyConfig(builder);
|
|
32
|
+
const config = new SlugifyConfig(builder);
|
|
33
|
+
return Object.freeze(config);
|
|
34
|
+
}
|
|
35
|
+
get customReplacements() {
|
|
36
|
+
return { ...this._customReplacements };
|
|
37
|
+
}
|
|
38
|
+
get separator() {
|
|
39
|
+
return this._separator;
|
|
40
|
+
}
|
|
41
|
+
get case() {
|
|
42
|
+
return this._case;
|
|
43
|
+
}
|
|
44
|
+
get removeAccents() {
|
|
45
|
+
return this._removeAccents;
|
|
46
|
+
}
|
|
47
|
+
get allowedChars() {
|
|
48
|
+
return this._allowedChars;
|
|
49
|
+
}
|
|
50
|
+
get maxLength() {
|
|
51
|
+
return this._maxLength;
|
|
52
|
+
}
|
|
53
|
+
get transformers() {
|
|
54
|
+
return [...this._transformers];
|
|
55
|
+
}
|
|
56
|
+
merge(other) {
|
|
57
|
+
return SlugifyConfig.builder()
|
|
58
|
+
.withCustomReplacements({ ...this.customReplacements, ...other.customReplacements })
|
|
59
|
+
.withSeparator(other.separator || this.separator)
|
|
60
|
+
.withCase(other.case || this.case)
|
|
61
|
+
.withRemoveAccents(other.removeAccents !== undefined ? other.removeAccents : this.removeAccents)
|
|
62
|
+
.withAllowedChars(other.allowedChars || this.allowedChars)
|
|
63
|
+
.withMaxLength(other.maxLength || this.maxLength)
|
|
64
|
+
.withTransformers([...this.transformers, ...other.transformers])
|
|
65
|
+
.build();
|
|
66
|
+
}
|
|
67
|
+
escapeRegex(str) {
|
|
68
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
69
|
+
}
|
|
70
|
+
getCharClass() {
|
|
71
|
+
return this._allowedChars || (this._removeAccents ? /[a-zA-Z0-9]/ : /[a-zA-Z0-9\u00C0-\u017F]/);
|
|
72
|
+
}
|
|
73
|
+
applyCustomReplacements(str) {
|
|
74
|
+
let result = str;
|
|
75
|
+
for (const [pattern, replacement] of Object.entries(this._customReplacements)) {
|
|
76
|
+
const escapedPattern = this.escapeRegex(pattern);
|
|
77
|
+
result = result.replace(new RegExp(escapedPattern, 'g'), replacement);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
applyTransformers(str) {
|
|
82
|
+
return this._transformers.reduce((result, transformer) => transformer(result), str);
|
|
83
|
+
}
|
|
84
|
+
applyRemoveAccents(str) {
|
|
85
|
+
if (!this._removeAccents)
|
|
86
|
+
return str;
|
|
87
|
+
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
88
|
+
}
|
|
89
|
+
replaceNonAllowedChars(str) {
|
|
90
|
+
const charClass = this.getCharClass().source;
|
|
91
|
+
const safeCharClass = charClass.startsWith('[') && charClass.endsWith(']') ? charClass.slice(1, -1) : charClass;
|
|
92
|
+
return str.replace(new RegExp(`[^${safeCharClass}]+`, 'g'), this._separator);
|
|
93
|
+
}
|
|
94
|
+
trimSeparators(str) {
|
|
95
|
+
const safeSeparator = this.escapeRegex(this._separator);
|
|
96
|
+
return str.replace(new RegExp(`^${safeSeparator}+|${safeSeparator}+$`, 'g'), '');
|
|
97
|
+
}
|
|
98
|
+
applyCase(str) {
|
|
99
|
+
if (this._case === 'lower')
|
|
100
|
+
return str.toLowerCase();
|
|
101
|
+
if (this._case === 'upper')
|
|
102
|
+
return str.toUpperCase();
|
|
103
|
+
return str;
|
|
104
|
+
}
|
|
105
|
+
applyMaxLength(str) {
|
|
106
|
+
if (!this._maxLength || this._maxLength <= 0)
|
|
107
|
+
return str;
|
|
108
|
+
let result = str.slice(0, this._maxLength);
|
|
109
|
+
const safeSeparator = this.escapeRegex(this._separator);
|
|
110
|
+
return result.replace(new RegExp(`${safeSeparator}$`), '');
|
|
111
|
+
}
|
|
112
|
+
collapseSeparators(str) {
|
|
113
|
+
const safeSeparator = this.escapeRegex(this._separator);
|
|
114
|
+
return str.replace(new RegExp(`${safeSeparator}+`, 'g'), this._separator);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Applies the slugify transformations to the given string using this configuration.
|
|
118
|
+
* @param str - The input string to slugify
|
|
119
|
+
* @returns The slugified string
|
|
120
|
+
*/
|
|
121
|
+
slugify(str) {
|
|
122
|
+
let result = str;
|
|
123
|
+
result = this.applyCustomReplacements(result);
|
|
124
|
+
result = this.applyTransformers(result);
|
|
125
|
+
result = this.applyRemoveAccents(result);
|
|
126
|
+
result = this.replaceNonAllowedChars(result);
|
|
127
|
+
result = this.trimSeparators(result);
|
|
128
|
+
result = this.applyCase(result);
|
|
129
|
+
result = this.applyMaxLength(result);
|
|
130
|
+
result = this.collapseSeparators(result);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Static method to slugify a string with a temporary configuration built from the builder.
|
|
135
|
+
* @param str - The input string to slugify
|
|
136
|
+
* @param builderFn - A function that configures the builder
|
|
137
|
+
* @returns The slugified string
|
|
138
|
+
*/
|
|
139
|
+
static slugifyWith(str, builderFn) {
|
|
140
|
+
const config = builderFn(SlugifyConfig.builder()).build();
|
|
141
|
+
return config.slugify(str);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
exports.SlugifyConfig = SlugifyConfig;
|
|
145
|
+
class SlugifyConfigBuilder {
|
|
146
|
+
constructor() {
|
|
147
|
+
this.customReplacements = {};
|
|
148
|
+
this.separator = '-';
|
|
149
|
+
this.case = 'lower';
|
|
150
|
+
this.removeAccents = true;
|
|
151
|
+
this.transformers = [];
|
|
152
|
+
}
|
|
153
|
+
withCustomReplacements(replacements) {
|
|
154
|
+
this.customReplacements = { ...replacements };
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
withSeparator(separator) {
|
|
158
|
+
this.separator = separator;
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
withCase(caseValue) {
|
|
162
|
+
this.case = caseValue;
|
|
163
|
+
return this;
|
|
164
|
+
}
|
|
165
|
+
withRemoveAccents(remove) {
|
|
166
|
+
this.removeAccents = remove;
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
withAllowedChars(regex) {
|
|
170
|
+
this.allowedChars = regex;
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
withMaxLength(length) {
|
|
174
|
+
this.maxLength = length;
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
withTransformers(transformers) {
|
|
178
|
+
this.transformers = [...transformers];
|
|
179
|
+
return this;
|
|
180
|
+
}
|
|
181
|
+
build() {
|
|
182
|
+
const config = SlugifyConfig.create(this);
|
|
183
|
+
return config;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
exports.SlugifyConfigBuilder = SlugifyConfigBuilder;
|
|
187
|
+
/**
|
|
188
|
+
* Default configuration for slugify operations.
|
|
189
|
+
* Provides sensible defaults that can be overridden by users.
|
|
190
|
+
*/
|
|
191
|
+
function getDefaultSlugifyConfig() {
|
|
192
|
+
return SlugifyConfig.default();
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Global configuration instance that can be modified by users.
|
|
196
|
+
* Starts with default values but can be updated via setSlugifyConfig().
|
|
197
|
+
*/
|
|
198
|
+
let globalSlugifyConfig = getDefaultSlugifyConfig();
|
|
199
|
+
/**
|
|
200
|
+
* Sets the global slugify configuration that will be used by default.
|
|
201
|
+
* Replaces the current global config with the provided config.
|
|
202
|
+
*
|
|
203
|
+
* @param config - Configuration object to set as global config
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* setSlugifyConfig(
|
|
207
|
+
* SlugifyConfig.builder()
|
|
208
|
+
* .withCustomReplacements({ "♀": "feminin", "♂": "masculin" })
|
|
209
|
+
* .withSeparator("_")
|
|
210
|
+
* .build()
|
|
211
|
+
* );
|
|
212
|
+
*/
|
|
213
|
+
function assertCharClass(regex) {
|
|
214
|
+
const src = regex.source;
|
|
215
|
+
if (!src.startsWith('[') || !src.endsWith(']')) {
|
|
216
|
+
throw new Error(`Invalid allowedChars: must be a character class like /[a-z]/, received ${regex}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function assertSlugifyConfig(builderOrConfig) {
|
|
220
|
+
const allowedChars = builderOrConfig.allowedChars;
|
|
221
|
+
const separator = builderOrConfig.separator;
|
|
222
|
+
const caseValue = builderOrConfig.case;
|
|
223
|
+
const removeAccents = builderOrConfig.removeAccents;
|
|
224
|
+
const maxLength = builderOrConfig.maxLength;
|
|
225
|
+
const customReplacements = builderOrConfig.customReplacements;
|
|
226
|
+
const transformers = builderOrConfig.transformers;
|
|
227
|
+
if (allowedChars !== undefined) {
|
|
228
|
+
if (!(allowedChars instanceof RegExp)) {
|
|
229
|
+
throw new TypeError(`Invalid allowedChars: expected a RegExp, received ${typeof allowedChars}`);
|
|
230
|
+
}
|
|
231
|
+
assertCharClass(allowedChars);
|
|
232
|
+
}
|
|
233
|
+
if (typeof separator !== 'string') {
|
|
234
|
+
throw new TypeError(`Invalid separator: expected a string, received ${typeof separator}`);
|
|
235
|
+
}
|
|
236
|
+
if (caseValue !== 'lower' && caseValue !== 'upper' && caseValue !== 'default') {
|
|
237
|
+
throw new TypeError(`Invalid case value: expected 'lower' | 'upper' | 'default', received ${caseValue}`);
|
|
238
|
+
}
|
|
239
|
+
if (typeof removeAccents !== 'boolean') {
|
|
240
|
+
throw new TypeError(`Invalid removeAccents: expected a boolean, received ${typeof removeAccents}`);
|
|
241
|
+
}
|
|
242
|
+
if (maxLength !== undefined) {
|
|
243
|
+
if (typeof maxLength !== 'number' || !Number.isInteger(maxLength) || maxLength < 0) {
|
|
244
|
+
throw new TypeError(`Invalid maxLength: expected a non-negative integer, received ${maxLength}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (typeof customReplacements !== 'object' || customReplacements === null) {
|
|
248
|
+
throw new TypeError(`Invalid customReplacements: expected an object, received ${typeof customReplacements}`);
|
|
249
|
+
}
|
|
250
|
+
if (!Array.isArray(transformers) || !transformers.every((fn) => typeof fn === 'function')) {
|
|
251
|
+
throw new TypeError(`Invalid transformers: expected an array of functions`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function setSlugifyConfig(config) {
|
|
255
|
+
globalSlugifyConfig = config;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Gets the current global slugify configuration.
|
|
259
|
+
* Returns the current global config instance.
|
|
260
|
+
*
|
|
261
|
+
* @returns Current global configuration object
|
|
262
|
+
*/
|
|
263
|
+
function getSlugifyConfig() {
|
|
264
|
+
return globalSlugifyConfig;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Resets the global slugify configuration to default values.
|
|
268
|
+
*/
|
|
269
|
+
function resetSlugifyConfig() {
|
|
270
|
+
globalSlugifyConfig = getDefaultSlugifyConfig();
|
|
271
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SlugifyConfig } from './slugify.config';
|
|
2
|
+
/**
|
|
3
|
+
* Applies slugify transformations to a string based on the provided configuration.
|
|
4
|
+
* This is the core logic for converting strings to URL-friendly slugs.
|
|
5
|
+
*
|
|
6
|
+
* @param str - The input string to slugify
|
|
7
|
+
* @param config - Configuration object (uses global config if not provided)
|
|
8
|
+
* @returns The slugified string
|
|
9
|
+
*/
|
|
10
|
+
export declare function slugifyString(str: string, config?: SlugifyConfig): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.slugifyString = slugifyString;
|
|
4
|
+
const slugify_config_1 = require("./slugify.config");
|
|
5
|
+
/**
|
|
6
|
+
* Applies slugify transformations to a string based on the provided configuration.
|
|
7
|
+
* This is the core logic for converting strings to URL-friendly slugs.
|
|
8
|
+
*
|
|
9
|
+
* @param str - The input string to slugify
|
|
10
|
+
* @param config - Configuration object (uses global config if not provided)
|
|
11
|
+
* @returns The slugified string
|
|
12
|
+
*/
|
|
13
|
+
function slugifyString(str, config) {
|
|
14
|
+
// Merge provided config with global config
|
|
15
|
+
const globalConfig = (0, slugify_config_1.getSlugifyConfig)();
|
|
16
|
+
const finalConfig = config ? globalConfig.merge(config) : globalConfig;
|
|
17
|
+
return finalConfig.slugify(str);
|
|
18
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "utilitish",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/FDonovan12/utilitish.git"
|
|
7
|
+
},
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public",
|
|
10
|
+
"provenance": false
|
|
11
|
+
},
|
|
4
12
|
"description": "",
|
|
5
13
|
"main": "dist/index.js",
|
|
6
14
|
"types": "dist/index.d.ts",
|