@aryanbansal-launch/edge-utils 0.1.11 → 0.1.12

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.
@@ -66,19 +66,64 @@ async function run() {
66
66
  }
67
67
 
68
68
  // 1. Redirects
69
- while (true) {
70
- const addRedirect = await question(`Do you want to add a Redirect?${config.redirects.length > 0 ? ' another?' : ''} (y/n): `);
71
- if (addRedirect.toLowerCase() !== 'y') break;
72
-
73
- const source = await question(' Source path (e.g., /source): ');
74
- const destination = await question(' Destination path (e.g., /destination): ');
75
- const code = await question(' Status code (default 308): ');
76
- config.redirects.push({
77
- source,
78
- destination,
79
- statusCode: parseInt(code) || 308
80
- });
81
- console.log(`${colors.green} ✔ Redirect added.${colors.reset}`);
69
+ const redirectMode = await question(`How would you like to add redirects?\n 1) One by one (interactive)\n 2) Bulk import from CSV file\n 3) Bulk import from JSON file\n 4) Skip\nChoose (1-4): `);
70
+
71
+ if (redirectMode === '1') {
72
+ // Interactive mode
73
+ while (true) {
74
+ const addRedirect = await question(`Do you want to add a Redirect?${config.redirects.length > 0 ? ' another?' : ''} (y/n): `);
75
+ if (addRedirect.toLowerCase() !== 'y') break;
76
+
77
+ const source = await question(' Source path (e.g., /source): ');
78
+ const destination = await question(' Destination path (e.g., /destination): ');
79
+ const code = await question(' Status code (default 308): ');
80
+ config.redirects.push({
81
+ source,
82
+ destination,
83
+ statusCode: parseInt(code) || 308
84
+ });
85
+ console.log(`${colors.green} ✔ Redirect added.${colors.reset}`);
86
+ }
87
+ } else if (redirectMode === '2') {
88
+ // CSV import
89
+ const csvPath = await question(' Enter CSV file path (e.g., ./redirects.csv): ');
90
+ try {
91
+ const csvContent = fs.readFileSync(path.resolve(csvPath), 'utf-8');
92
+ const lines = csvContent.split('\n').filter(line => line.trim());
93
+
94
+ // Skip header if present
95
+ const startIndex = lines[0].toLowerCase().includes('source') ? 1 : 0;
96
+
97
+ for (let i = startIndex; i < lines.length; i++) {
98
+ const [source, destination, statusCode] = lines[i].split(',').map(s => s.trim());
99
+ if (source && destination) {
100
+ config.redirects.push({
101
+ source,
102
+ destination,
103
+ statusCode: parseInt(statusCode) || 308
104
+ });
105
+ }
106
+ }
107
+ console.log(`${colors.green} ✔ Imported ${lines.length - startIndex} redirects from CSV.${colors.reset}`);
108
+ } catch (error) {
109
+ console.log(`${colors.red} ✖ Error reading CSV file: ${error.message}${colors.reset}`);
110
+ }
111
+ } else if (redirectMode === '3') {
112
+ // JSON import
113
+ const jsonPath = await question(' Enter JSON file path (e.g., ./redirects.json): ');
114
+ try {
115
+ const jsonContent = fs.readFileSync(path.resolve(jsonPath), 'utf-8');
116
+ const redirects = JSON.parse(jsonContent);
117
+
118
+ if (Array.isArray(redirects)) {
119
+ config.redirects.push(...redirects);
120
+ console.log(`${colors.green} ✔ Imported ${redirects.length} redirects from JSON.${colors.reset}`);
121
+ } else {
122
+ console.log(`${colors.red} ✖ JSON file must contain an array of redirect objects.${colors.reset}`);
123
+ }
124
+ } catch (error) {
125
+ console.log(`${colors.red} ✖ Error reading JSON file: ${error.message}${colors.reset}`);
126
+ }
82
127
  }
83
128
 
84
129
  // 2. Rewrites
@@ -175,6 +175,7 @@ ${colors.dim}──────────────────────
175
175
  ${colors.cyan}npx launch-config${colors.reset}
176
176
  Interactive CLI to manage launch.json
177
177
  Configure: redirects, rewrites, cache priming
178
+ Supports bulk import from CSV/JSON files
178
179
 
179
180
  ${colors.cyan}npx launch-help${colors.reset}
180
181
  Display this help guide
@@ -0,0 +1,12 @@
1
+ source,destination,statusCode
2
+ /old-blog/post-1,/blog/post-1,301
3
+ /old-blog/post-2,/blog/post-2,301
4
+ /old-blog/post-3,/blog/post-3,301
5
+ /products/old-sku-123,/products/new-sku-456,308
6
+ /products/old-sku-789,/products/new-sku-101,308
7
+ /legacy/about,/about,301
8
+ /legacy/contact,/contact,301
9
+ /legacy/pricing,/pricing,301
10
+ /old-shop/*,/shop/*,301
11
+ /archive/*,/blog/archive/*,301
12
+
@@ -0,0 +1,53 @@
1
+ [
2
+ {
3
+ "source": "/old-blog/post-1",
4
+ "destination": "/blog/post-1",
5
+ "statusCode": 301
6
+ },
7
+ {
8
+ "source": "/old-blog/post-2",
9
+ "destination": "/blog/post-2",
10
+ "statusCode": 301
11
+ },
12
+ {
13
+ "source": "/old-blog/post-3",
14
+ "destination": "/blog/post-3",
15
+ "statusCode": 301
16
+ },
17
+ {
18
+ "source": "/products/old-sku-123",
19
+ "destination": "/products/new-sku-456",
20
+ "statusCode": 308
21
+ },
22
+ {
23
+ "source": "/products/old-sku-789",
24
+ "destination": "/products/new-sku-101",
25
+ "statusCode": 308
26
+ },
27
+ {
28
+ "source": "/legacy/about",
29
+ "destination": "/about",
30
+ "statusCode": 301
31
+ },
32
+ {
33
+ "source": "/legacy/contact",
34
+ "destination": "/contact",
35
+ "statusCode": 301
36
+ },
37
+ {
38
+ "source": "/legacy/pricing",
39
+ "destination": "/pricing",
40
+ "statusCode": 301
41
+ },
42
+ {
43
+ "source": "/old-shop/*",
44
+ "destination": "/shop/*",
45
+ "statusCode": 301
46
+ },
47
+ {
48
+ "source": "/archive/*",
49
+ "destination": "/blog/archive/*",
50
+ "statusCode": 301
51
+ }
52
+ ]
53
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aryanbansal-launch/edge-utils",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -22,7 +22,8 @@
22
22
  },
23
23
  "files": [
24
24
  "dist",
25
- "bin"
25
+ "bin",
26
+ "examples"
26
27
  ],
27
28
  "scripts": {
28
29
  "build": "tsc",
package/readme.md CHANGED
@@ -387,14 +387,164 @@ if (redirect) return redirect;
387
387
  - A/B testing redirects
388
388
  - Maintenance mode redirects
389
389
 
390
- **When to Use:**
391
- - **Edge Functions (this utility)**: Dynamic redirects requiring logic (cookies, headers, geo)
392
- - **launch.json**: Static path-to-path redirects (better performance)
393
-
394
390
  **Learn More:** [Edge URL Redirects](https://www.contentstack.com/docs/developers/launch/edge-url-redirects)
395
391
 
396
392
  ---
397
393
 
394
+ ### 📊 Redirects: Config vs Edge Functions
395
+
396
+ Contentstack Launch offers **two ways** to handle redirects. Choose the right approach based on your needs:
397
+
398
+ #### Option 1: Config-Based Redirects (`launch.json`)
399
+
400
+ **Best for:** Static, predictable redirects that don't require logic
401
+
402
+ **Pros:**
403
+ - ⚡ **Faster**: No edge function execution overhead
404
+ - 🎯 **Simpler**: Pure configuration, no code needed
405
+ - 📦 **Bulk-friendly**: Easy to manage hundreds/thousands of redirects
406
+ - 🔧 **Easy updates**: Use `npx launch-config` CLI with CSV/JSON import
407
+
408
+ **Cons:**
409
+ - ❌ No dynamic logic (can't check cookies, headers, geo, etc.)
410
+ - ❌ No conditional redirects based on request data
411
+ - ❌ Limited to exact path or wildcard matching
412
+
413
+ **Setup:**
414
+ ```bash
415
+ # Interactive CLI
416
+ npx launch-config
417
+
418
+ # Or bulk import from CSV/JSON
419
+ npx launch-config
420
+ # Choose option 2 or 3 for bulk import
421
+ ```
422
+
423
+ **Example `launch.json`:**
424
+ ```json
425
+ {
426
+ "redirects": [
427
+ {
428
+ "source": "/old-blog/:slug",
429
+ "destination": "/blog/:slug",
430
+ "statusCode": 301
431
+ },
432
+ {
433
+ "source": "/products/old-*",
434
+ "destination": "/products/new-*",
435
+ "statusCode": 308
436
+ }
437
+ ]
438
+ }
439
+ ```
440
+
441
+ **When to use:**
442
+ - ✅ SEO migrations with hundreds of URL changes
443
+ - ✅ Simple path rewrites (e.g., `/old-path` → `/new-path`)
444
+ - ✅ Bulk product SKU redirects
445
+ - ✅ Static site restructuring
446
+ - ✅ Predictable, rule-based redirects
447
+
448
+ ---
449
+
450
+ #### Option 2: Edge Function Redirects (This Package)
451
+
452
+ **Best for:** Dynamic redirects requiring logic or request inspection
453
+
454
+ **Pros:**
455
+ - 🧠 **Smart**: Access cookies, headers, geo-location, user-agent
456
+ - 🎨 **Flexible**: Complex conditional logic
457
+ - 🔄 **Dynamic**: Redirect based on A/B tests, feature flags, user data
458
+ - 🌍 **Contextual**: Different redirects per country/region
459
+
460
+ **Cons:**
461
+ - 🐌 Slightly slower (edge function execution)
462
+ - 🔧 Requires code changes
463
+ - 📝 More complex for simple redirects
464
+
465
+ **Setup:**
466
+ ```javascript
467
+ import { redirectIfMatch } from '@aryanbansal-launch/edge-utils';
468
+
469
+ export default async function handler(request) {
470
+ // Dynamic redirect based on cookie
471
+ const cookie = request.headers.get('cookie');
472
+ if (cookie?.includes('beta=true')) {
473
+ return Response.redirect(new URL('/beta-features', request.url), 302);
474
+ }
475
+
476
+ // Geo-based redirect
477
+ const country = request.headers.get('x-cs-country');
478
+ if (country === 'FR') {
479
+ return Response.redirect('https://fr.mysite.com', 302);
480
+ }
481
+
482
+ // Conditional redirect
483
+ const redirect = redirectIfMatch(request, {
484
+ path: "/old-page",
485
+ to: "/new-page",
486
+ method: "GET",
487
+ status: 301
488
+ });
489
+ if (redirect) return redirect;
490
+
491
+ return passThrough(request);
492
+ }
493
+ ```
494
+
495
+ **When to use:**
496
+ - ✅ Geo-location based redirects
497
+ - ✅ A/B testing redirects
498
+ - ✅ Cookie/session-based routing
499
+ - ✅ User-agent specific redirects (mobile vs desktop)
500
+ - ✅ Feature flag redirects
501
+ - ✅ Maintenance mode with exceptions
502
+ - ✅ Complex conditional logic
503
+
504
+ ---
505
+
506
+ #### Quick Decision Guide
507
+
508
+ | Scenario | Use Config | Use Edge Function |
509
+ |----------|-----------|-------------------|
510
+ | 500+ simple URL redirects | ✅ | ❌ |
511
+ | SEO migration from old site | ✅ | ❌ |
512
+ | Redirect based on country | ❌ | ✅ |
513
+ | A/B test routing | ❌ | ✅ |
514
+ | Cookie-based redirects | ❌ | ✅ |
515
+ | Mobile vs desktop routing | ❌ | ✅ |
516
+ | Simple path changes | ✅ | ❌ |
517
+ | Maintenance mode (all users) | ✅ | ❌ |
518
+ | Maintenance mode (except admins) | ❌ | ✅ |
519
+ | Bulk product SKU changes | ✅ | ❌ |
520
+
521
+ **💡 Pro Tip:** You can use **both** approaches together! Use `launch.json` for bulk static redirects and edge functions for dynamic logic.
522
+
523
+ **Example Combined Approach:**
524
+ ```javascript
525
+ // launch.json - handles 1000+ static SEO redirects
526
+ {
527
+ "redirects": [
528
+ { "source": "/old-blog/:slug", "destination": "/blog/:slug", "statusCode": 301 }
529
+ // ... 1000 more redirects
530
+ ]
531
+ }
532
+
533
+ // functions/[proxy].edge.js - handles dynamic logic
534
+ export default async function handler(request) {
535
+ // Geo-based redirect (dynamic)
536
+ const country = request.headers.get('x-cs-country');
537
+ if (country === 'FR') {
538
+ return Response.redirect('https://fr.mysite.com', 302);
539
+ }
540
+
541
+ // Static redirects handled by launch.json automatically
542
+ return passThrough(request);
543
+ }
544
+ ```
545
+
546
+ ---
547
+
398
548
  ### ⚛️ Next.js Optimization
399
549
 
400
550
  #### `handleNextJS_RSC(request, options)`
@@ -808,10 +958,10 @@ Next Steps:
808
958
 
809
959
  ### `npx launch-config`
810
960
 
811
- Interactive CLI to manage `launch.json` configuration.
961
+ Interactive CLI to manage `launch.json` configuration with support for bulk imports.
812
962
 
813
963
  **What it does:**
814
- - Add/manage redirects
964
+ - Add/manage redirects (one-by-one or bulk import)
815
965
  - Configure rewrites
816
966
  - Set up cache priming URLs
817
967
  - Preserves existing configuration
@@ -825,13 +975,14 @@ npx launch-config
825
975
  ```
826
976
  🚀 Launch Configuration Generator
827
977
 
828
- Do you want to add a Redirect? (y/n): y
829
- Source path (e.g., /source): /old-page
830
- Destination path (e.g., /destination): /new-page
831
- Status code (default 308): 301
832
- Redirect added.
833
-
834
- Do you want to add a Redirect? another? (y/n): n
978
+ How would you like to add redirects?
979
+ 1) One by one (interactive)
980
+ 2) Bulk import from CSV file
981
+ 3) Bulk import from JSON file
982
+ 4) Skip
983
+ Choose (1-4): 2
984
+ Enter CSV file path (e.g., ./redirects.csv): ./redirects.csv
985
+ ✔ Imported 150 redirects from CSV.
835
986
 
836
987
  Do you want to add a Rewrite? (y/n): y
837
988
  Source path (e.g., /api/*): /api/*
@@ -845,6 +996,47 @@ Enter URLs separated by commas (e.g., /home,/about,/shop): /,/about,/products
845
996
  ✅ Successfully updated launch.json!
846
997
  ```
847
998
 
999
+ #### Bulk Import Formats
1000
+
1001
+ **CSV Format** (`redirects.csv`):
1002
+ ```csv
1003
+ source,destination,statusCode
1004
+ /old-blog/post-1,/blog/post-1,301
1005
+ /old-blog/post-2,/blog/post-2,301
1006
+ /products/old-sku-123,/products/new-sku-456,308
1007
+ /legacy/*,/new/*,301
1008
+ ```
1009
+
1010
+ **JSON Format** (`redirects.json`):
1011
+ ```json
1012
+ [
1013
+ {
1014
+ "source": "/old-blog/post-1",
1015
+ "destination": "/blog/post-1",
1016
+ "statusCode": 301
1017
+ },
1018
+ {
1019
+ "source": "/old-blog/post-2",
1020
+ "destination": "/blog/post-2",
1021
+ "statusCode": 301
1022
+ },
1023
+ {
1024
+ "source": "/products/old-sku-123",
1025
+ "destination": "/products/new-sku-456",
1026
+ "statusCode": 308
1027
+ }
1028
+ ]
1029
+ ```
1030
+
1031
+ **Use Cases for Bulk Import:**
1032
+ - Migrating from another platform with hundreds of URLs
1033
+ - SEO redirects from spreadsheet/database exports
1034
+ - Bulk product SKU changes
1035
+ - Content restructuring with many path changes
1036
+
1037
+ **Example Files:**
1038
+ See `examples/redirects.csv` and `examples/redirects.json` in this package for ready-to-use templates.
1039
+
848
1040
  **Learn More:**
849
1041
  - [Static Redirects](https://www.contentstack.com/docs/developers/launch/edge-url-redirects)
850
1042
  - [Cache Priming](https://www.contentstack.com/docs/developers/launch/cache-priming)