normalize-url 8.0.2 → 8.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/index.d.ts +39 -0
  2. package/index.js +34 -14
  3. package/package.json +1 -1
  4. package/readme.md +35 -0
package/index.d.ts CHANGED
@@ -278,6 +278,45 @@ export type Options = {
278
278
  ```
279
279
  */
280
280
  readonly sortQueryParameters?: boolean;
281
+
282
+ /**
283
+ Removes the entire URL path, leaving only the domain.
284
+
285
+ @default false
286
+
287
+ @example
288
+ ```
289
+ normalizeUrl('https://example.com/path/to/page', {
290
+ removePath: true
291
+ });
292
+ //=> 'https://example.com'
293
+ ```
294
+ */
295
+ readonly removePath?: boolean;
296
+
297
+ /**
298
+ Custom function to transform the URL path components.
299
+
300
+ The function receives an array of non-empty path components and should return a modified array.
301
+
302
+ @default false
303
+
304
+ @example
305
+ ```
306
+ // Keep only the first path component
307
+ normalizeUrl('https://example.com/api/v1/users', {
308
+ transformPath: (pathComponents) => pathComponents.slice(0, 1)
309
+ });
310
+ //=> 'https://example.com/api'
311
+
312
+ // Remove specific components
313
+ normalizeUrl('https://example.com/admin/users', {
314
+ transformPath: (pathComponents) => pathComponents.filter(c => c !== 'admin')
315
+ });
316
+ //=> 'https://example.com/users'
317
+ ```
318
+ */
319
+ readonly transformPath?: (pathComponents: string[]) => string[];
281
320
  };
282
321
 
283
322
  /**
package/index.js CHANGED
@@ -29,14 +29,12 @@ const normalizeDataURL = (urlString, {stripHash}) => {
29
29
  throw new Error(`Invalid URL: ${urlString}`);
30
30
  }
31
31
 
32
- let {type, data, hash} = match.groups;
32
+ const {type, data, hash} = match.groups;
33
33
  const mediaType = type.split(';');
34
- hash = stripHash ? '' : hash;
35
34
 
36
- let isBase64 = false;
37
- if (mediaType[mediaType.length - 1] === 'base64') {
35
+ const isBase64 = mediaType.at(-1) === 'base64';
36
+ if (isBase64) {
38
37
  mediaType.pop();
39
- isBase64 = true;
40
38
  }
41
39
 
42
40
  // Lowercase MIME type
@@ -58,9 +56,7 @@ const normalizeDataURL = (urlString, {stripHash}) => {
58
56
  })
59
57
  .filter(Boolean);
60
58
 
61
- const normalizedMediaType = [
62
- ...attributes,
63
- ];
59
+ const normalizedMediaType = [...attributes];
64
60
 
65
61
  if (isBase64) {
66
62
  normalizedMediaType.push('base64');
@@ -70,7 +66,8 @@ const normalizeDataURL = (urlString, {stripHash}) => {
70
66
  normalizedMediaType.unshift(mimeType);
71
67
  }
72
68
 
73
- return `data:${normalizedMediaType.join(';')},${isBase64 ? data.trim() : data}${hash ? `#${hash}` : ''}`;
69
+ const hashPart = stripHash || !hash ? '' : `#${hash}`;
70
+ return `data:${normalizedMediaType.join(';')},${isBase64 ? data.trim() : data}${hashPart}`;
74
71
  };
75
72
 
76
73
  export default function normalizeUrl(urlString, options) {
@@ -89,6 +86,8 @@ export default function normalizeUrl(urlString, options) {
89
86
  removeDirectoryIndex: false,
90
87
  removeExplicitPort: false,
91
88
  sortQueryParameters: true,
89
+ removePath: false,
90
+ transformPath: false,
92
91
  ...options,
93
92
  };
94
93
 
@@ -191,15 +190,27 @@ export default function normalizeUrl(urlString, options) {
191
190
  }
192
191
 
193
192
  if (Array.isArray(options.removeDirectoryIndex) && options.removeDirectoryIndex.length > 0) {
194
- let pathComponents = urlObject.pathname.split('/');
195
- const lastComponent = pathComponents[pathComponents.length - 1];
193
+ const pathComponents = urlObject.pathname.split('/').filter(Boolean);
194
+ const lastComponent = pathComponents.at(-1);
196
195
 
197
- if (testParameter(lastComponent, options.removeDirectoryIndex)) {
198
- pathComponents = pathComponents.slice(0, -1);
199
- urlObject.pathname = pathComponents.slice(1).join('/') + '/';
196
+ if (lastComponent && testParameter(lastComponent, options.removeDirectoryIndex)) {
197
+ pathComponents.pop();
198
+ urlObject.pathname = pathComponents.length > 0 ? `/${pathComponents.join('/')}/` : '/';
200
199
  }
201
200
  }
202
201
 
202
+ // Remove path
203
+ if (options.removePath) {
204
+ urlObject.pathname = '/';
205
+ }
206
+
207
+ // Transform path components
208
+ if (options.transformPath && typeof options.transformPath === 'function') {
209
+ const pathComponents = urlObject.pathname.split('/').filter(Boolean);
210
+ const newComponents = options.transformPath(pathComponents);
211
+ urlObject.pathname = newComponents?.length > 0 ? `/${newComponents.join('/')}` : '/';
212
+ }
213
+
203
214
  if (urlObject.hostname) {
204
215
  // Remove trailing dot
205
216
  urlObject.hostname = urlObject.hostname.replace(/\.$/, '');
@@ -240,12 +251,21 @@ export default function normalizeUrl(urlString, options) {
240
251
 
241
252
  // Sort query parameters
242
253
  if (options.sortQueryParameters) {
254
+ const originalSearch = urlObject.search;
243
255
  urlObject.searchParams.sort();
244
256
 
245
257
  // Calling `.sort()` encodes the search parameters, so we need to decode them again.
246
258
  try {
247
259
  urlObject.search = decodeURIComponent(urlObject.search);
248
260
  } catch {}
261
+
262
+ // Fix parameters that originally had no equals sign but got one added by URLSearchParams
263
+ const partsWithoutEquals = originalSearch.slice(1).split('&').filter(p => p && !p.includes('='));
264
+ for (const part of partsWithoutEquals) {
265
+ const decoded = decodeURIComponent(part);
266
+ // Only replace at word boundaries to avoid partial matches
267
+ urlObject.search = urlObject.search.replace(`?${decoded}=`, `?${decoded}`).replace(`&${decoded}=`, `&${decoded}`);
268
+ }
249
269
  }
250
270
 
251
271
  if (options.removeTrailingSlash) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "normalize-url",
3
- "version": "8.0.2",
3
+ "version": "8.1.1",
4
4
  "description": "Normalize a URL",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/normalize-url",
package/readme.md CHANGED
@@ -307,6 +307,41 @@ normalizeUrl('www.sindresorhus.com?b=two&a=one&c=three', {
307
307
  //=> 'http://sindresorhus.com/?b=two&a=one&c=three'
308
308
  ```
309
309
 
310
+ ##### removePath
311
+
312
+ Type: `boolean`\
313
+ Default: `false`
314
+
315
+ Removes the entire URL path, leaving only the domain.
316
+
317
+ ```js
318
+ normalizeUrl('https://example.com/path/to/page', {
319
+ removePath: true
320
+ });
321
+ //=> 'https://example.com'
322
+ ```
323
+
324
+ ##### transformPath
325
+
326
+ Type: `Function`\
327
+ Default: `false`
328
+
329
+ Custom function to transform the URL path components. The function receives an array of non-empty path components and should return a modified array.
330
+
331
+ ```js
332
+ // Keep only the first path component
333
+ normalizeUrl('https://example.com/api/v1/users', {
334
+ transformPath: (pathComponents) => pathComponents.slice(0, 1)
335
+ });
336
+ //=> 'https://example.com/api'
337
+
338
+ // Remove specific components
339
+ normalizeUrl('https://example.com/admin/users', {
340
+ transformPath: (pathComponents) => pathComponents.filter(c => c !== 'admin')
341
+ });
342
+ //=> 'https://example.com/users'
343
+ ```
344
+
310
345
  ## Related
311
346
 
312
347
  - [compare-urls](https://github.com/sindresorhus/compare-urls) - Compare URLs by first normalizing them