@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.
- package/bin/launch-config.js +58 -13
- package/bin/launch-help.js +1 -0
- package/examples/redirects.csv +12 -0
- package/examples/redirects.json +53 -0
- package/package.json +3 -2
- package/readme.md +205 -13
package/bin/launch-config.js
CHANGED
|
@@ -66,19 +66,64 @@ async function run() {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// 1. Redirects
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
source
|
|
78
|
-
destination
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
package/bin/launch-help.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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)
|