@aryanbansal-launch/edge-utils 0.1.9 → 0.1.11

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.
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+
3
+ const colors = {
4
+ reset: '\x1b[0m',
5
+ bright: '\x1b[1m',
6
+ green: '\x1b[32m',
7
+ yellow: '\x1b[33m',
8
+ cyan: '\x1b[36m',
9
+ blue: '\x1b[34m',
10
+ magenta: '\x1b[35m',
11
+ dim: '\x1b[2m'
12
+ };
13
+
14
+ console.log(`
15
+ ${colors.bright}${colors.cyan}╔════════════════════════════════════════════════════════════════════╗
16
+ ║ 🚀 Launch Edge Utils - Complete Reference Guide ║
17
+ ╚════════════════════════════════════════════════════════════════════╝${colors.reset}
18
+
19
+ ${colors.bright}${colors.yellow}📦 AVAILABLE METHODS${colors.reset}
20
+ ${colors.dim}────────────────────────────────────────────────────────────────────${colors.reset}
21
+
22
+ ${colors.bright}${colors.green}1. Security & Access Control${colors.reset}
23
+
24
+ ${colors.cyan}blockAICrawlers(request, bots?)${colors.reset}
25
+ Block AI crawlers and bots from accessing your site
26
+ ${colors.dim}Parameters:${colors.reset}
27
+ - request: Request object
28
+ - bots: string[] (optional) - Custom bot list
29
+ ${colors.dim}Default Blocked Bots:${colors.reset}
30
+ claudebot, gptbot, googlebot, bingbot, ahrefsbot,
31
+ yandexbot, semrushbot, mj12bot, facebookexternalhit, twitterbot
32
+ ${colors.dim}Returns:${colors.reset} Response | null
33
+ ${colors.dim}Example:${colors.reset}
34
+ const response = blockAICrawlers(request);
35
+ if (response) return response;
36
+
37
+ ${colors.cyan}blockDefaultDomains(request, options?)${colors.reset}
38
+ Block access via default Launch domains (*.contentstackapps.com)
39
+ ${colors.dim}Parameters:${colors.reset}
40
+ - request: Request object
41
+ - options: { domainToBlock?: string }
42
+ ${colors.dim}Returns:${colors.reset} Response | null
43
+ ${colors.dim}Example:${colors.reset}
44
+ const response = blockDefaultDomains(request);
45
+ if (response) return response;
46
+
47
+ ${colors.cyan}ipAccessControl(request, options)${colors.reset}
48
+ Whitelist or blacklist IPs at the edge
49
+ ${colors.dim}Parameters:${colors.reset}
50
+ - request: Request object
51
+ - options: { allow?: string[], deny?: string[] }
52
+ ${colors.dim}Returns:${colors.reset} Response | null
53
+ ${colors.dim}Example:${colors.reset}
54
+ const response = ipAccessControl(request, {
55
+ allow: ["203.0.113.10", "198.51.100.5"]
56
+ });
57
+ if (response) return response;
58
+
59
+ ${colors.cyan}protectWithBasicAuth(request, options)${colors.reset}
60
+ Add Basic Authentication to protect staging/dev environments
61
+ ${colors.dim}Parameters:${colors.reset}
62
+ - request: Request object
63
+ - options: {
64
+ hostnameIncludes: string,
65
+ username: string,
66
+ password: string,
67
+ realm?: string
68
+ }
69
+ ${colors.dim}Returns:${colors.reset} Promise<Response> | null
70
+ ${colors.dim}Example:${colors.reset}
71
+ const auth = await protectWithBasicAuth(request, {
72
+ hostnameIncludes: "staging.myapp.com",
73
+ username: "admin",
74
+ password: "securepass123"
75
+ });
76
+ if (auth && auth.status === 401) return auth;
77
+
78
+ ${colors.bright}${colors.green}2. Routing & Redirects${colors.reset}
79
+
80
+ ${colors.cyan}redirectIfMatch(request, options)${colors.reset}
81
+ Conditional redirects based on path and method
82
+ ${colors.dim}Parameters:${colors.reset}
83
+ - request: Request object
84
+ - options: {
85
+ path: string,
86
+ method?: string,
87
+ to: string,
88
+ status?: number
89
+ }
90
+ ${colors.dim}Returns:${colors.reset} Response | null
91
+ ${colors.dim}Example:${colors.reset}
92
+ const redirect = redirectIfMatch(request, {
93
+ path: "/old-page",
94
+ to: "/new-page",
95
+ status: 301
96
+ });
97
+ if (redirect) return redirect;
98
+
99
+ ${colors.bright}${colors.green}3. Next.js Optimization${colors.reset}
100
+
101
+ ${colors.cyan}handleNextJS_RSC(request, options)${colors.reset}
102
+ Fix Next.js React Server Components header issues
103
+ ${colors.dim}Parameters:${colors.reset}
104
+ - request: Request object
105
+ - options: { affectedPaths: string[] }
106
+ ${colors.dim}Returns:${colors.reset} Promise<Response> | null
107
+ ${colors.dim}Example:${colors.reset}
108
+ const rsc = await handleNextJS_RSC(request, {
109
+ affectedPaths: ["/shop", "/about", "/products"]
110
+ });
111
+ if (rsc) return rsc;
112
+
113
+ ${colors.bright}${colors.green}4. Geo-Location${colors.reset}
114
+
115
+ ${colors.cyan}getGeoHeaders(request)${colors.reset}
116
+ Extract geo-location data from Launch headers
117
+ ${colors.dim}Parameters:${colors.reset}
118
+ - request: Request object
119
+ ${colors.dim}Returns:${colors.reset} {
120
+ country: string | null,
121
+ region: string | null,
122
+ city: string | null,
123
+ latitude: string | null,
124
+ longitude: string | null
125
+ }
126
+ ${colors.dim}Example:${colors.reset}
127
+ const geo = getGeoHeaders(request);
128
+ if (geo.country === "US") {
129
+ // Custom logic for US visitors
130
+ }
131
+
132
+ ${colors.bright}${colors.green}5. Response Utilities${colors.reset}
133
+
134
+ ${colors.cyan}jsonResponse(body, init?)${colors.reset}
135
+ Create JSON responses easily
136
+ ${colors.dim}Parameters:${colors.reset}
137
+ - body: Record<string, unknown>
138
+ - init: ResponseInit (optional)
139
+ ${colors.dim}Returns:${colors.reset} Response
140
+ ${colors.dim}Example:${colors.reset}
141
+ return jsonResponse({ status: "ok", data: [...] });
142
+
143
+ ${colors.cyan}passThrough(request)${colors.reset}
144
+ Forward request to origin server
145
+ ${colors.dim}Parameters:${colors.reset}
146
+ - request: Request object
147
+ ${colors.dim}Returns:${colors.reset} Promise<Response>
148
+ ${colors.dim}Example:${colors.reset}
149
+ return passThrough(request);
150
+
151
+ ${colors.bright}${colors.green}6. Configuration${colors.reset}
152
+
153
+ ${colors.cyan}generateLaunchConfig(options)${colors.reset}
154
+ Generate launch.json configuration programmatically
155
+ ${colors.dim}Parameters:${colors.reset}
156
+ - options: {
157
+ redirects?: LaunchRedirect[],
158
+ rewrites?: LaunchRewrite[],
159
+ cache?: { cachePriming?: { urls: string[] } }
160
+ }
161
+ ${colors.dim}Returns:${colors.reset} LaunchConfig
162
+ ${colors.dim}Example:${colors.reset}
163
+ const config = generateLaunchConfig({
164
+ redirects: [{ source: "/old", destination: "/new", statusCode: 301 }],
165
+ cache: { cachePriming: { urls: ["/", "/about"] } }
166
+ });
167
+
168
+ ${colors.bright}${colors.yellow}🛠️ CLI COMMANDS${colors.reset}
169
+ ${colors.dim}────────────────────────────────────────────────────────────────────${colors.reset}
170
+
171
+ ${colors.cyan}npx create-launch-edge${colors.reset}
172
+ Initialize edge functions with boilerplate code
173
+ Creates: functions/[proxy].edge.js
174
+
175
+ ${colors.cyan}npx launch-config${colors.reset}
176
+ Interactive CLI to manage launch.json
177
+ Configure: redirects, rewrites, cache priming
178
+
179
+ ${colors.cyan}npx launch-help${colors.reset}
180
+ Display this help guide
181
+
182
+ ${colors.bright}${colors.yellow}📚 QUICK LINKS${colors.reset}
183
+ ${colors.dim}────────────────────────────────────────────────────────────────────${colors.reset}
184
+
185
+ ${colors.blue}Documentation:${colors.reset} https://github.com/AryanBansal-launch/launch-edge-utils
186
+ ${colors.blue}NPM Package:${colors.reset} https://www.npmjs.com/package/@aryanbansal-launch/edge-utils
187
+ ${colors.blue}Launch Docs:${colors.reset} https://www.contentstack.com/docs/developers/launch
188
+
189
+ ${colors.dim}────────────────────────────────────────────────────────────────────${colors.reset}
190
+ `);
191
+
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Generates a complete Gemini AI Chatbot widget script to be injected at the Edge.
3
+ */
4
+ export function getGeminiChatbotScript(options) {
5
+ const { apiKey, botName = "Gemini Assistant", welcomeMessage = "Hi! How can I help you today?", primaryColor = "#1a73e8", } = options;
6
+ return `
7
+ <script>
8
+ (function() {
9
+ // Create styles
10
+ const style = document.createElement('style');
11
+ style.textContent = \`
12
+ #gemini-chat-widget {
13
+ position: fixed;
14
+ bottom: 20px;
15
+ right: 20px;
16
+ z-index: 9999;
17
+ font-family: sans-serif;
18
+ }
19
+ #gemini-chat-button {
20
+ width: 60px;
21
+ height: 60px;
22
+ border-radius: 50%;
23
+ background-color: ${primaryColor};
24
+ color: white;
25
+ border: none;
26
+ cursor: pointer;
27
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ font-size: 24px;
32
+ }
33
+ #gemini-chat-window {
34
+ display: none;
35
+ width: 350px;
36
+ height: 500px;
37
+ background: white;
38
+ border-radius: 12px;
39
+ box-shadow: 0 8px 24px rgba(0,0,0,0.2);
40
+ flex-direction: column;
41
+ overflow: hidden;
42
+ margin-bottom: 15px;
43
+ }
44
+ #gemini-chat-header {
45
+ padding: 15px;
46
+ background: ${primaryColor};
47
+ color: white;
48
+ font-weight: bold;
49
+ display: flex;
50
+ justify-content: space-between;
51
+ }
52
+ #gemini-chat-messages {
53
+ flex: 1;
54
+ padding: 15px;
55
+ overflow-y: auto;
56
+ background: #f7f7f7;
57
+ }
58
+ .gemini-msg {
59
+ margin-bottom: 10px;
60
+ padding: 8px 12px;
61
+ border-radius: 8px;
62
+ max-width: 80%;
63
+ word-wrap: break-word;
64
+ }
65
+ .gemini-msg-bot { background: white; align-self: flex-start; border: 1px solid #eee; }
66
+ .gemini-msg-user { background: ${primaryColor}; color: white; align-self: flex-end; margin-left: auto; }
67
+ #gemini-chat-input-area {
68
+ padding: 10px;
69
+ border-top: 1px solid #eee;
70
+ display: flex;
71
+ }
72
+ #gemini-chat-input {
73
+ flex: 1;
74
+ border: 1px solid #ddd;
75
+ padding: 8px;
76
+ border-radius: 4px;
77
+ outline: none;
78
+ }
79
+ \`;
80
+ document.head.appendChild(style);
81
+
82
+ // Create HTML
83
+ const container = document.createElement('div');
84
+ container.id = 'gemini-chat-widget';
85
+ container.innerHTML = \`
86
+ <div id="gemini-chat-window">
87
+ <div id="gemini-chat-header">
88
+ <span>${botName}</span>
89
+ <button onclick="document.getElementById('gemini-chat-window').style.display='none'" style="background:none; border:none; color:white; cursor:pointer;">×</button>
90
+ </div>
91
+ <div id="gemini-chat-messages">
92
+ <div class="gemini-msg gemini-msg-bot">${welcomeMessage}</div>
93
+ </div>
94
+ <div id="gemini-chat-input-area">
95
+ <input type="text" id="gemini-chat-input" placeholder="Type a message...">
96
+ </div>
97
+ </div>
98
+ <button id="gemini-chat-button">💬</button>
99
+ \`;
100
+ document.body.appendChild(container);
101
+
102
+ const btn = document.getElementById('gemini-chat-button');
103
+ const win = document.getElementById('gemini-chat-window');
104
+ const input = document.getElementById('gemini-chat-input');
105
+ const msgContainer = document.getElementById('gemini-chat-messages');
106
+
107
+ btn.onclick = () => {
108
+ win.style.display = win.style.display === 'flex' ? 'none' : 'flex';
109
+ if (win.style.display === 'flex') input.focus();
110
+ };
111
+
112
+ async function sendMessage() {
113
+ const text = input.value.trim();
114
+ if (!text) return;
115
+
116
+ input.value = '';
117
+ appendMessage('user', text);
118
+
119
+ const loadingMsg = appendMessage('bot', '...');
120
+
121
+ try {
122
+ const response = await fetch('https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=${apiKey}', {
123
+ method: 'POST',
124
+ headers: { 'Content-Type': 'application/json' },
125
+ body: JSON.stringify({
126
+ contents: [{ parts: [{ text }] }]
127
+ })
128
+ });
129
+ const data = await response.json();
130
+ const botReply = data.candidates[0].content.parts[0].text;
131
+ loadingMsg.textContent = botReply;
132
+ } catch (e) {
133
+ loadingMsg.textContent = 'Sorry, I am having trouble connecting.';
134
+ }
135
+ msgContainer.scrollTop = msgContainer.scrollHeight;
136
+ }
137
+
138
+ function appendMessage(role, text) {
139
+ const div = document.createElement('div');
140
+ div.className = 'gemini-msg gemini-msg-' + role;
141
+ div.textContent = text;
142
+ msgContainer.appendChild(div);
143
+ msgContainer.scrollTop = msgContainer.scrollHeight;
144
+ return div;
145
+ }
146
+
147
+ input.onkeypress = (e) => { if (e.key === 'Enter') sendMessage(); };
148
+ })();
149
+ </script>
150
+ \`;
151
+ }
152
+ `;
153
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Utility to inject HTML/scripts into a Response body using TransformStream.
3
+ * This is high-performance as it streams the modification without loading the full body into memory.
4
+ */
5
+ class HTMLRewriterStream extends TransformStream {
6
+ constructor(contentToInject, position = 'before-body-end') {
7
+ const encoder = new TextEncoder();
8
+ const decoder = new TextDecoder();
9
+ super({
10
+ transform(chunk, controller) {
11
+ let html = decoder.decode(chunk);
12
+ if (position === 'before-body-end' && html.includes('</body>')) {
13
+ html = html.replace('</body>', `${contentToInject}</body>`);
14
+ }
15
+ else if (position === 'after-head-start' && html.includes('<head>')) {
16
+ html = html.replace('<head>', `<head>${contentToInject}`);
17
+ }
18
+ controller.enqueue(encoder.encode(html));
19
+ },
20
+ });
21
+ }
22
+ }
23
+ /**
24
+ * Injects a script or HTML snippet into a Response.
25
+ * @param response The original Response object
26
+ * @param options Injection options
27
+ */
28
+ export async function injectScript(response, options) {
29
+ const contentType = response.headers.get('content-type');
30
+ // Only inject into HTML responses
31
+ if (!contentType || !contentType.includes('text/html')) {
32
+ return response;
33
+ }
34
+ const contentToInject = options.scriptUrl
35
+ ? `<script src="${options.scriptUrl}" async defer></script>`
36
+ : (options.script || '');
37
+ if (!contentToInject)
38
+ return response;
39
+ const rewriter = new HTMLRewriterStream(contentToInject, options.position);
40
+ return new Response(response.body?.pipeThrough(rewriter), {
41
+ status: response.status,
42
+ statusText: response.statusText,
43
+ headers: response.headers,
44
+ });
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aryanbansal-launch/edge-utils",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -13,8 +13,9 @@
13
13
  },
14
14
  "main": "dist/index.js",
15
15
  "bin": {
16
- "create-launch-edge": "./bin/launch-init.js",
17
- "launch-config": "./bin/launch-config.js"
16
+ "create-launch-edge": "bin/launch-init.js",
17
+ "launch-config": "bin/launch-config.js",
18
+ "launch-help": "bin/launch-help.js"
18
19
  },
19
20
  "exports": {
20
21
  ".": "./dist/index.js"
@@ -24,6 +25,20 @@
24
25
  "bin"
25
26
  ],
26
27
  "scripts": {
27
- "build": "tsc"
28
- }
28
+ "build": "tsc",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "keywords": [
32
+ "contentstack",
33
+ "launch",
34
+ "edge-functions",
35
+ "edge",
36
+ "middleware",
37
+ "security",
38
+ "nextjs",
39
+ "redirect",
40
+ "geo-location",
41
+ "basic-auth",
42
+ "ip-access"
43
+ ]
29
44
  }
package/readme.md CHANGED
@@ -7,47 +7,73 @@ A comprehensive toolkit for [Contentstack Launch](https://www.contentstack.com/d
7
7
 
8
8
  ---
9
9
 
10
- ## Quick Start (Recommended)
10
+ ## 📋 Table of Contents
11
11
 
12
- Bootstrap your edge environment in seconds using our automated initializer. Run this command from your **project root**:
12
+ - [Quick Start](#-quick-start)
13
+ - [Usage Flow](#-usage-flow)
14
+ - [Complete API Reference](#-complete-api-reference)
15
+ - [Real-World Examples](#-real-world-examples)
16
+ - [CLI Commands](#-cli-commands)
17
+ - [Platform Support](#-platform-support)
18
+
19
+ ---
20
+
21
+ ## ⚡ Quick Start
22
+
23
+ ### Step 1: Install the Package
13
24
 
14
25
  ```bash
15
- # Install the package
16
26
  npm install @aryanbansal-launch/edge-utils
27
+ ```
17
28
 
18
- # Initialize Edge Functions
29
+ ### Step 2: Initialize Edge Functions
30
+
31
+ Run this command from your **project root** (where `package.json` is located):
32
+
33
+ ```bash
19
34
  npx create-launch-edge
20
35
  ```
21
36
 
22
- This command will automatically create the `functions/` directory and a production-ready `[proxy].edge.js` boilerplate handler.
37
+ This automatically creates:
38
+ - `functions/` directory
39
+ - `functions/[proxy].edge.js` with production-ready boilerplate
40
+
41
+ ### Step 3: Customize & Deploy
42
+
43
+ 1. Open `functions/[proxy].edge.js`
44
+ 2. Customize the utilities based on your needs
45
+ 3. Deploy to Contentstack Launch
46
+
47
+ **Need help?** Run:
48
+ ```bash
49
+ npx launch-help
50
+ ```
23
51
 
24
52
  ---
25
53
 
26
- ## Features & Deep Dive
54
+ ## 🔄 Usage Flow
27
55
 
28
- ### 🛡️ Security & Access Control
29
- - **[Block AI Crawlers](https://www.contentstack.com/docs/developers/launch/blocking-ai-crawlers)**: Automatically detects and rejects requests from known scrapers (GPTBot, ClaudeBot, etc.) to protect your content and server resources.
30
- - **[Restricted Default Domains](https://www.contentstack.com/docs/developers/launch/blocking-default-launch-domains-from-google-search)**: By default, Launch provides a `*.contentstackapps.com` domain. This utility forces visitors to your custom domain, which is essential for SEO (preventing duplicate content) and professional branding.
31
- - **[IP Access Control](https://www.contentstack.com/docs/developers/launch/ip-based-access-control-using-edge-functions)**: Create a lightweight firewall at the edge to whitelist internal teams or block malicious IPs before they hit your application logic.
56
+ ### Understanding Edge Functions
32
57
 
33
- ### ⚛️ Next.js Optimization
34
- - **[RSC Header Fix](https://www.contentstack.com/docs/developers/launch/handling-nextjs-rsc-issues-on-launch)**: Next.js React Server Components (RSC) use a special `rsc` header. Sometimes, proxies or caches can incorrectly serve RSC data when a full page load is expected. This utility detects these edge cases and strips the header to ensure the correct response type is served.
58
+ Edge Functions in Contentstack Launch act as **middleware** that runs before requests reach your origin server. Think of them as a chain of checks and transformations:
35
59
 
36
- ### 📍 Performance & Geo-Awareness
37
- - **[Geo-Location Access](https://www.contentstack.com/docs/developers/launch/geolocation-headers-in-launch)**: Contentstack Launch injects geography data into request headers. This utility parses those headers into a clean object (`country`, `city`, `region`, etc.), enabling you to personalize content or restrict features based on user location.
38
- - **[Cache Priming](https://www.contentstack.com/docs/developers/launch/cache-priming)**: Use the `launch-config` CLI to pre-load critical URLs into the edge cache, eliminating "cold start" latency for your first visitors after a deployment.
60
+ ```
61
+ User Request Edge Function Your Application
62
+ ```
39
63
 
40
- ### 🔀 Smart Routing
41
- - **Declarative Redirects**: Handle complex, logic-based redirects at runtime.
42
- - **Runtime vs Config**:
43
- - Use **`launch.json`** ([Static Redirects](https://www.contentstack.com/docs/developers/launch/edge-url-redirects)) for high-performance, simple path-to-path mapping.
44
- - Use **`redirectIfMatch`** (this library) for dynamic redirects that require logic, such as checking cookies, headers, or geo-location before redirecting.
64
+ ### Basic Pattern
45
65
 
46
- ---
66
+ Every utility follows this pattern:
47
67
 
48
- ## 🛠️ Detailed Usage Example
68
+ ```javascript
69
+ const result = utilityFunction(request, options);
70
+ if (result) return result; // Early return if condition met
71
+ // Continue to next check...
72
+ ```
73
+
74
+ ### Complete Handler Example
49
75
 
50
- Your `functions/[proxy].edge.js` acts as a **middleware chain**. You can layer these utilities to create complex edge logic:
76
+ Here's how utilities work together in `functions/[proxy].edge.js`:
51
77
 
52
78
  ```javascript
53
79
  import {
@@ -55,74 +81,828 @@ import {
55
81
  handleNextJS_RSC,
56
82
  blockAICrawlers,
57
83
  ipAccessControl,
84
+ protectWithBasicAuth,
58
85
  redirectIfMatch,
59
86
  getGeoHeaders,
87
+ jsonResponse,
60
88
  passThrough
61
89
  } from "@aryanbansal-launch/edge-utils";
62
90
 
63
91
  export default async function handler(request, context) {
64
- // 1. 🛡️ Force Custom Domain (SEO Best Practice)
65
- // Blocks access via *.contentstackapps.com
92
+ // 1️⃣ SECURITY LAYER
93
+ // Block default domains (SEO best practice)
66
94
  const domainCheck = blockDefaultDomains(request);
67
95
  if (domainCheck) return domainCheck;
68
96
 
69
- // 2. ⚛️ Fix Next.js RSC Header issues
70
- // Prevents "JSON-only" responses on page refreshes
97
+ // Block AI bots and crawlers
98
+ const botCheck = blockAICrawlers(request);
99
+ if (botCheck) return botCheck;
100
+
101
+ // IP-based access control
102
+ const ipCheck = ipAccessControl(request, {
103
+ allow: ["203.0.113.10", "198.51.100.5"]
104
+ });
105
+ if (ipCheck) return ipCheck;
106
+
107
+ // 2️⃣ AUTHENTICATION LAYER
108
+ // Protect staging environments
109
+ const auth = await protectWithBasicAuth(request, {
110
+ hostnameIncludes: "staging.myapp.com",
111
+ username: "admin",
112
+ password: "securepass123"
113
+ });
114
+ if (auth && auth.status === 401) return auth;
115
+
116
+ // 3️⃣ FRAMEWORK FIXES
117
+ // Fix Next.js RSC header issues
71
118
  const rscCheck = await handleNextJS_RSC(request, {
72
- affectedPaths: ["/shop", "/about"]
119
+ affectedPaths: ["/shop", "/products", "/about"]
73
120
  });
74
121
  if (rscCheck) return rscCheck;
75
122
 
76
- // 3. 🤖 Block Aggressive Bots
123
+ // 4️⃣ ROUTING LAYER
124
+ // Handle redirects
125
+ const redirect = redirectIfMatch(request, {
126
+ path: "/old-page",
127
+ to: "/new-page",
128
+ status: 301
129
+ });
130
+ if (redirect) return redirect;
131
+
132
+ // 5️⃣ PERSONALIZATION
133
+ // Get user's location
134
+ const geo = getGeoHeaders(request);
135
+ if (geo.country === "US") {
136
+ console.log(`US visitor from ${geo.city}, ${geo.region}`);
137
+ }
138
+
139
+ // 6️⃣ CUSTOM API ENDPOINTS
140
+ const url = new URL(request.url);
141
+ if (url.pathname === "/api/health") {
142
+ return jsonResponse({
143
+ status: "healthy",
144
+ region: geo.region,
145
+ timestamp: Date.now()
146
+ });
147
+ }
148
+
149
+ // 7️⃣ DEFAULT: Pass to origin
150
+ return passThrough(request);
151
+ }
152
+ ```
153
+
154
+ ---
155
+
156
+ ## 📚 Complete API Reference
157
+
158
+ ### 🛡️ Security & Access Control
159
+
160
+ #### `blockAICrawlers(request, bots?)`
161
+
162
+ Block AI crawlers and bots from accessing your site.
163
+
164
+ **Parameters:**
165
+ - `request` (Request) - The incoming request object
166
+ - `bots` (string[], optional) - Custom list of bot user-agents to block
167
+
168
+ **Returns:** `Response | null`
169
+ - Returns `403 Forbidden` if bot detected
170
+ - Returns `null` if no bot detected (continue processing)
171
+
172
+ **Default Blocked Bots:**
173
+ The following bots are blocked by default (case-insensitive):
174
+ - `claudebot` - Anthropic's Claude AI crawler
175
+ - `gptbot` - OpenAI's GPT crawler
176
+ - `googlebot` - Google's web crawler
177
+ - `bingbot` - Microsoft Bing's crawler
178
+ - `ahrefsbot` - Ahrefs SEO crawler
179
+ - `yandexbot` - Yandex search engine crawler
180
+ - `semrushbot` - SEMrush SEO tool crawler
181
+ - `mj12bot` - Majestic SEO crawler
182
+ - `facebookexternalhit` - Facebook's link preview crawler
183
+ - `twitterbot` - Twitter's link preview crawler
184
+
185
+ **Example:**
186
+ ```javascript
187
+ // Use default bot list
188
+ const response = blockAICrawlers(request);
189
+ if (response) return response;
190
+
191
+ // Custom bot list
192
+ const response = blockAICrawlers(request, [
193
+ "gptbot",
194
+ "claudebot",
195
+ "my-custom-bot"
196
+ ]);
197
+ if (response) return response;
198
+ ```
199
+
200
+ **Use Cases:**
201
+ - Protect content from AI scraping
202
+ - Reduce server load from aggressive crawlers
203
+ - Comply with content usage policies
204
+
205
+ ---
206
+
207
+ #### `blockDefaultDomains(request, options?)`
208
+
209
+ Block access via default Launch domains (`*.contentstackapps.com`).
210
+
211
+ **Parameters:**
212
+ - `request` (Request) - The incoming request object
213
+ - `options` (object, optional)
214
+ - `domainToBlock` (string) - Custom domain to block (default: "contentstackapps.com")
215
+
216
+ **Returns:** `Response | null`
217
+ - Returns `403 Forbidden` if default domain detected
218
+ - Returns `null` if custom domain used
219
+
220
+ **Example:**
221
+ ```javascript
222
+ // Block default Launch domain
223
+ const response = blockDefaultDomains(request);
224
+ if (response) return response;
225
+
226
+ // Block custom domain
227
+ const response = blockDefaultDomains(request, {
228
+ domainToBlock: "myolddomain.com"
229
+ });
230
+ if (response) return response;
231
+ ```
232
+
233
+ **Use Cases:**
234
+ - Force users to custom domain for SEO
235
+ - Prevent duplicate content indexing
236
+ - Professional branding
237
+
238
+ **Learn More:** [Blocking Default Launch Domains](https://www.contentstack.com/docs/developers/launch/blocking-default-launch-domains-from-google-search)
239
+
240
+ ---
241
+
242
+ #### `ipAccessControl(request, options)`
243
+
244
+ Whitelist or blacklist IPs at the edge.
245
+
246
+ **Parameters:**
247
+ - `request` (Request) - The incoming request object
248
+ - `options` (object)
249
+ - `allow` (string[], optional) - Whitelist of allowed IPs
250
+ - `deny` (string[], optional) - Blacklist of denied IPs
251
+
252
+ **Returns:** `Response | null`
253
+ - Returns `403 Forbidden` if IP blocked
254
+ - Returns `null` if IP allowed
255
+
256
+ **Example:**
257
+ ```javascript
258
+ // Whitelist specific IPs (only these can access)
259
+ const response = ipAccessControl(request, {
260
+ allow: ["203.0.113.10", "198.51.100.5"]
261
+ });
262
+ if (response) return response;
263
+
264
+ // Blacklist specific IPs
265
+ const response = ipAccessControl(request, {
266
+ deny: ["192.0.2.100", "192.0.2.101"]
267
+ });
268
+ if (response) return response;
269
+
270
+ // Combine both (deny takes precedence)
271
+ const response = ipAccessControl(request, {
272
+ allow: ["203.0.113.0/24"], // Allow subnet
273
+ deny: ["203.0.113.50"] // Except this one
274
+ });
275
+ if (response) return response;
276
+ ```
277
+
278
+ **Use Cases:**
279
+ - Restrict admin panels to office IPs
280
+ - Block malicious IPs
281
+ - Create staging environment access control
282
+
283
+ **Learn More:** [IP-Based Access Control](https://www.contentstack.com/docs/developers/launch/ip-based-access-control-using-edge-functions)
284
+
285
+ ---
286
+
287
+ #### `protectWithBasicAuth(request, options)`
288
+
289
+ Add Basic Authentication to protect environments.
290
+
291
+ **Parameters:**
292
+ - `request` (Request) - The incoming request object
293
+ - `options` (object)
294
+ - `hostnameIncludes` (string) - Protect URLs containing this hostname
295
+ - `username` (string) - Username for authentication
296
+ - `password` (string) - Password for authentication
297
+ - `realm` (string, optional) - Auth realm name (default: "Protected Area")
298
+
299
+ **Returns:** `Promise<Response> | null`
300
+ - Returns `401 Unauthorized` if auth fails
301
+ - Returns authenticated response if credentials valid
302
+ - Returns `null` if hostname doesn't match
303
+
304
+ **Example:**
305
+ ```javascript
306
+ // Protect staging environment
307
+ const auth = await protectWithBasicAuth(request, {
308
+ hostnameIncludes: "staging.myapp.com",
309
+ username: "admin",
310
+ password: "securepass123",
311
+ realm: "Staging Environment"
312
+ });
313
+ if (auth && auth.status === 401) return auth;
314
+
315
+ // Protect specific path pattern
316
+ const url = new URL(request.url);
317
+ if (url.pathname.startsWith("/admin")) {
318
+ const auth = await protectWithBasicAuth(request, {
319
+ hostnameIncludes: url.hostname,
320
+ username: "admin",
321
+ password: "adminpass"
322
+ });
323
+ if (auth && auth.status === 401) return auth;
324
+ }
325
+ ```
326
+
327
+ **Use Cases:**
328
+ - Protect staging/dev environments
329
+ - Quick password protection for demos
330
+ - Restrict access to admin areas
331
+
332
+ **Security Note:** For production, use proper authentication systems. Basic Auth credentials are base64-encoded, not encrypted.
333
+
334
+ **Learn More:** [Password Protection for Environments](https://www.contentstack.com/docs/developers/launch/password-protection-for-environments)
335
+
336
+ ---
337
+
338
+ ### 🔀 Routing & Redirects
339
+
340
+ #### `redirectIfMatch(request, options)`
341
+
342
+ Conditional redirects based on path and method.
343
+
344
+ **Parameters:**
345
+ - `request` (Request) - The incoming request object
346
+ - `options` (object)
347
+ - `path` (string) - Path to match
348
+ - `to` (string) - Destination path
349
+ - `method` (string, optional) - HTTP method to match (e.g., "GET", "POST")
350
+ - `status` (number, optional) - HTTP status code (default: 301)
351
+
352
+ **Returns:** `Response | null`
353
+ - Returns redirect response if path matches
354
+ - Returns `null` if no match
355
+
356
+ **Example:**
357
+ ```javascript
358
+ // Simple redirect
359
+ const redirect = redirectIfMatch(request, {
360
+ path: "/old-page",
361
+ to: "/new-page",
362
+ status: 301 // Permanent redirect
363
+ });
364
+ if (redirect) return redirect;
365
+
366
+ // Temporary redirect
367
+ const redirect = redirectIfMatch(request, {
368
+ path: "/maintenance",
369
+ to: "/coming-soon",
370
+ status: 302 // Temporary redirect
371
+ });
372
+ if (redirect) return redirect;
373
+
374
+ // Method-specific redirect
375
+ const redirect = redirectIfMatch(request, {
376
+ path: "/api/old-endpoint",
377
+ method: "POST",
378
+ to: "/api/v2/endpoint",
379
+ status: 308 // Permanent redirect (preserves method)
380
+ });
381
+ if (redirect) return redirect;
382
+ ```
383
+
384
+ **Use Cases:**
385
+ - SEO-friendly URL changes
386
+ - Migrate old URLs to new structure
387
+ - A/B testing redirects
388
+ - Maintenance mode redirects
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
+ **Learn More:** [Edge URL Redirects](https://www.contentstack.com/docs/developers/launch/edge-url-redirects)
395
+
396
+ ---
397
+
398
+ ### ⚛️ Next.js Optimization
399
+
400
+ #### `handleNextJS_RSC(request, options)`
401
+
402
+ Fix Next.js React Server Components header issues.
403
+
404
+ **Parameters:**
405
+ - `request` (Request) - The incoming request object
406
+ - `options` (object)
407
+ - `affectedPaths` (string[]) - Array of paths with RSC issues
408
+
409
+ **Returns:** `Promise<Response> | null`
410
+ - Returns modified response if RSC issue detected
411
+ - Returns `null` if no issue
412
+
413
+ **Example:**
414
+ ```javascript
415
+ // Fix specific pages
416
+ const rsc = await handleNextJS_RSC(request, {
417
+ affectedPaths: ["/shop", "/products", "/about"]
418
+ });
419
+ if (rsc) return rsc;
420
+
421
+ // Fix all dynamic routes
422
+ const rsc = await handleNextJS_RSC(request, {
423
+ affectedPaths: ["/blog", "/products", "/categories"]
424
+ });
425
+ if (rsc) return rsc;
426
+ ```
427
+
428
+ **What It Does:**
429
+ Next.js RSC uses a special `rsc: 1` header. Sometimes, caches incorrectly serve RSC data (JSON) when a full page load is expected. This utility detects these cases and strips the header to ensure correct response type.
430
+
431
+ **Use Cases:**
432
+ - Fix "JSON showing instead of page" issues
433
+ - Resolve RSC caching problems
434
+ - Ensure proper page hydration
435
+
436
+ **Learn More:** [Handling Next.js RSC Issues](https://www.contentstack.com/docs/developers/launch/handling-nextjs-rsc-issues-on-launch)
437
+
438
+ ---
439
+
440
+ ### 📍 Geo-Location
441
+
442
+ #### `getGeoHeaders(request)`
443
+
444
+ Extract geo-location data from Launch headers.
445
+
446
+ **Parameters:**
447
+ - `request` (Request) - The incoming request object
448
+
449
+ **Returns:** Object with geo data
450
+ ```typescript
451
+ {
452
+ country: string | null, // ISO country code (e.g., "US")
453
+ region: string | null, // Region/state code (e.g., "CA")
454
+ city: string | null, // City name (e.g., "San Francisco")
455
+ latitude: string | null, // Latitude coordinate
456
+ longitude: string | null // Longitude coordinate
457
+ }
458
+ ```
459
+
460
+ **Example:**
461
+ ```javascript
462
+ // Get user location
463
+ const geo = getGeoHeaders(request);
464
+
465
+ // Country-based logic
466
+ if (geo.country === "US") {
467
+ console.log(`US visitor from ${geo.city}, ${geo.region}`);
468
+ }
469
+
470
+ // Region-specific content
471
+ if (geo.country === "US" && geo.region === "CA") {
472
+ // Show California-specific content
473
+ }
474
+
475
+ // Distance-based logic
476
+ if (geo.latitude && geo.longitude) {
477
+ const userLat = parseFloat(geo.latitude);
478
+ const userLon = parseFloat(geo.longitude);
479
+ // Calculate distance to store, etc.
480
+ }
481
+
482
+ // Redirect based on location
483
+ const geo = getGeoHeaders(request);
484
+ if (geo.country === "FR") {
485
+ return Response.redirect("https://fr.mysite.com", 302);
486
+ }
487
+ ```
488
+
489
+ **Use Cases:**
490
+ - Personalize content by location
491
+ - Show region-specific pricing
492
+ - Redirect to country-specific sites
493
+ - Display nearest store locations
494
+ - Comply with regional regulations
495
+
496
+ **Learn More:** [Geolocation Headers in Launch](https://www.contentstack.com/docs/developers/launch/geolocation-headers-in-launch)
497
+
498
+ ---
499
+
500
+ ### 📤 Response Utilities
501
+
502
+ #### `jsonResponse(body, init?)`
503
+
504
+ Create JSON responses easily.
505
+
506
+ **Parameters:**
507
+ - `body` (object) - JSON-serializable object
508
+ - `init` (ResponseInit, optional) - Additional response options (status, headers, etc.)
509
+
510
+ **Returns:** `Response` with `Content-Type: application/json`
511
+
512
+ **Example:**
513
+ ```javascript
514
+ // Simple JSON response
515
+ return jsonResponse({ status: "ok", message: "Success" });
516
+
517
+ // With custom status
518
+ return jsonResponse(
519
+ { error: "Not found" },
520
+ { status: 404 }
521
+ );
522
+
523
+ // With custom headers
524
+ return jsonResponse(
525
+ { data: [...] },
526
+ {
527
+ status: 200,
528
+ headers: {
529
+ "Cache-Control": "max-age=3600",
530
+ "X-Custom-Header": "value"
531
+ }
532
+ }
533
+ );
534
+
535
+ // API endpoint example
536
+ const url = new URL(request.url);
537
+ if (url.pathname === "/api/user") {
538
+ const geo = getGeoHeaders(request);
539
+ return jsonResponse({
540
+ user: "john_doe",
541
+ location: {
542
+ country: geo.country,
543
+ city: geo.city
544
+ },
545
+ timestamp: Date.now()
546
+ });
547
+ }
548
+ ```
549
+
550
+ **Use Cases:**
551
+ - Create API endpoints at the edge
552
+ - Return structured error messages
553
+ - Build serverless functions
554
+
555
+ ---
556
+
557
+ #### `passThrough(request)`
558
+
559
+ Forward request to origin server.
560
+
561
+ **Parameters:**
562
+ - `request` (Request) - The incoming request object
563
+
564
+ **Returns:** `Promise<Response>` from origin
565
+
566
+ **Example:**
567
+ ```javascript
568
+ // Default: pass everything through
569
+ return passThrough(request);
570
+
571
+ // After all checks
572
+ export default async function handler(request, context) {
573
+ // ... all your checks ...
574
+
575
+ // If nothing matched, pass to origin
576
+ return passThrough(request);
577
+ }
578
+ ```
579
+
580
+ **Use Cases:**
581
+ - Default fallback after edge logic
582
+ - Forward requests that don't need edge processing
583
+
584
+ ---
585
+
586
+ ### ⚙️ Configuration
587
+
588
+ #### `generateLaunchConfig(options)`
589
+
590
+ Generate `launch.json` configuration programmatically.
591
+
592
+ **Parameters:**
593
+ - `options` (object)
594
+ - `redirects` (LaunchRedirect[], optional)
595
+ - `rewrites` (LaunchRewrite[], optional)
596
+ - `cache` (object, optional)
597
+ - `cachePriming` (object)
598
+ - `urls` (string[])
599
+
600
+ **Returns:** `LaunchConfig` object
601
+
602
+ **Types:**
603
+ ```typescript
604
+ interface LaunchRedirect {
605
+ source: string;
606
+ destination: string;
607
+ statusCode?: number;
608
+ response?: {
609
+ headers?: Record<string, string>;
610
+ };
611
+ }
612
+
613
+ interface LaunchRewrite {
614
+ source: string;
615
+ destination: string;
616
+ }
617
+ ```
618
+
619
+ **Example:**
620
+ ```javascript
621
+ import { generateLaunchConfig } from "@aryanbansal-launch/edge-utils";
622
+ import fs from "fs";
623
+
624
+ const config = generateLaunchConfig({
625
+ redirects: [
626
+ {
627
+ source: "/old-blog/:slug",
628
+ destination: "/blog/:slug",
629
+ statusCode: 301
630
+ },
631
+ {
632
+ source: "/products",
633
+ destination: "/shop",
634
+ statusCode: 308
635
+ }
636
+ ],
637
+ rewrites: [
638
+ {
639
+ source: "/api/:path*",
640
+ destination: "https://api.mybackend.com/:path*"
641
+ }
642
+ ],
643
+ cache: {
644
+ cachePriming: {
645
+ urls: ["/", "/about", "/products", "/contact"]
646
+ }
647
+ }
648
+ });
649
+
650
+ // Write to launch.json
651
+ fs.writeFileSync("launch.json", JSON.stringify(config, null, 2));
652
+ ```
653
+
654
+ **Use Cases:**
655
+ - Generate config from CMS data
656
+ - Automate bulk redirects
657
+ - Dynamic configuration management
658
+
659
+ ---
660
+
661
+ ## 🎯 Real-World Examples
662
+
663
+ ### Example 1: E-Commerce Site
664
+
665
+ ```javascript
666
+ export default async function handler(request, context) {
667
+ const url = new URL(request.url);
668
+ const geo = getGeoHeaders(request);
669
+
670
+ // 1. Block bots to reduce costs
77
671
  const botCheck = blockAICrawlers(request);
78
672
  if (botCheck) return botCheck;
79
673
 
80
- // 4. 🧱 Firewall
81
- const ipCheck = ipAccessControl(request, { allow: ["203.0.113.10"] });
82
- if (ipCheck) return ipCheck;
674
+ // 2. Geo-based redirects
675
+ if (geo.country === "UK" && !url.hostname.includes("uk.")) {
676
+ return Response.redirect(`https://uk.myshop.com${url.pathname}`, 302);
677
+ }
83
678
 
84
- // 5. 🔀 Logic-Based Redirects
679
+ // 3. Maintenance mode for specific regions
680
+ if (geo.country === "US" && url.pathname.startsWith("/checkout")) {
681
+ return jsonResponse(
682
+ { error: "Checkout temporarily unavailable in your region" },
683
+ { status: 503 }
684
+ );
685
+ }
686
+
687
+ // 4. Product redirects
85
688
  const redirect = redirectIfMatch(request, {
86
- path: "/legacy-page",
87
- to: "/new-page",
689
+ path: "/products/old-sku-123",
690
+ to: "/products/new-sku-456",
88
691
  status: 301
89
692
  });
90
693
  if (redirect) return redirect;
91
694
 
92
- // 6. 📍 Personalization
695
+ return passThrough(request);
696
+ }
697
+ ```
698
+
699
+ ### Example 2: Multi-Environment Setup
700
+
701
+ ```javascript
702
+ export default async function handler(request, context) {
703
+ const url = new URL(request.url);
704
+
705
+ // 1. Protect staging with Basic Auth
706
+ if (url.hostname.includes("staging")) {
707
+ const auth = await protectWithBasicAuth(request, {
708
+ hostnameIncludes: "staging",
709
+ username: "team",
710
+ password: process.env.STAGING_PASSWORD || "defaultpass"
711
+ });
712
+ if (auth && auth.status === 401) return auth;
713
+ }
714
+
715
+ // 2. Restrict dev environment to office IPs
716
+ if (url.hostname.includes("dev")) {
717
+ const ipCheck = ipAccessControl(request, {
718
+ allow: ["203.0.113.0/24"] // Office IP range
719
+ });
720
+ if (ipCheck) return ipCheck;
721
+ }
722
+
723
+ // 3. Block default domain on production
724
+ if (url.hostname.includes("myapp.com")) {
725
+ const domainCheck = blockDefaultDomains(request);
726
+ if (domainCheck) return domainCheck;
727
+ }
728
+
729
+ return passThrough(request);
730
+ }
731
+ ```
732
+
733
+ ### Example 3: Next.js App with API Routes
734
+
735
+ ```javascript
736
+ export default async function handler(request, context) {
737
+ const url = new URL(request.url);
93
738
  const geo = getGeoHeaders(request);
94
- if (geo.country === "UK") {
95
- // Custom logic for UK visitors...
739
+
740
+ // 1. Fix RSC issues on dynamic pages
741
+ const rscCheck = await handleNextJS_RSC(request, {
742
+ affectedPaths: ["/blog", "/products", "/categories"]
743
+ });
744
+ if (rscCheck) return rscCheck;
745
+
746
+ // 2. Edge API endpoints
747
+ if (url.pathname === "/api/geo") {
748
+ return jsonResponse({
749
+ country: geo.country,
750
+ region: geo.region,
751
+ city: geo.city
752
+ });
753
+ }
754
+
755
+ if (url.pathname === "/api/health") {
756
+ return jsonResponse({
757
+ status: "healthy",
758
+ timestamp: Date.now(),
759
+ region: geo.region
760
+ });
761
+ }
762
+
763
+ // 3. Block bots from expensive pages
764
+ if (url.pathname.startsWith("/search")) {
765
+ const botCheck = blockAICrawlers(request);
766
+ if (botCheck) return botCheck;
96
767
  }
97
768
 
98
- // 7. 🚀 Pass through to Origin
99
769
  return passThrough(request);
100
770
  }
101
771
  ```
102
772
 
103
773
  ---
104
774
 
105
- ## ⚙️ Configuration CLI
775
+ ## 🛠️ CLI Commands
106
776
 
107
- Manage your `launch.json` file interactively to handle bulk settings:
777
+ ### `npx create-launch-edge`
108
778
 
779
+ Initialize edge functions with production-ready boilerplate.
780
+
781
+ **What it does:**
782
+ 1. Checks you're in project root (where `package.json` exists)
783
+ 2. Creates `functions/` directory if needed
784
+ 3. Generates `functions/[proxy].edge.js` with example code
785
+
786
+ **Usage:**
787
+ ```bash
788
+ cd /path/to/your/project
789
+ npx create-launch-edge
790
+ ```
791
+
792
+ **Output:**
793
+ ```
794
+ 🚀 create-launch-edge: Contentstack Launch Initializer
795
+
796
+ ✨ New: Created /functions directory
797
+ ✨ New: Created /functions/[proxy].edge.js
798
+
799
+ 🎉 Setup Complete!
800
+
801
+ Next Steps:
802
+ 1. Open functions/[proxy].edge.js
803
+ 2. Customize your redirects, auth, and RSC paths
804
+ 3. Deploy your project to Contentstack Launch
805
+ ```
806
+
807
+ ---
808
+
809
+ ### `npx launch-config`
810
+
811
+ Interactive CLI to manage `launch.json` configuration.
812
+
813
+ **What it does:**
814
+ - Add/manage redirects
815
+ - Configure rewrites
816
+ - Set up cache priming URLs
817
+ - Preserves existing configuration
818
+
819
+ **Usage:**
109
820
  ```bash
110
821
  npx launch-config
111
822
  ```
112
823
 
113
- ### Supported Settings:
114
- - **Bulk Redirects**: Add multiple sources and destinations easily.
115
- - **Rewrites**: Map internal paths to external APIs or micro-services.
116
- - **Cache Priming**: Add a comma-separated list of URLs to warm up the CDN.
824
+ **Interactive Prompts:**
825
+ ```
826
+ 🚀 Launch Configuration Generator
827
+
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
835
+
836
+ Do you want to add a Rewrite? (y/n): y
837
+ Source path (e.g., /api/*): /api/*
838
+ Destination URL: https://backend.myapp.com/api/*
839
+ ✔ Rewrite added.
840
+
841
+ Do you want to add Cache Priming URLs? (y/n): y
842
+ Note: Only relative paths are supported. No Regex/Wildcards.
843
+ Enter URLs separated by commas (e.g., /home,/about,/shop): /,/about,/products
844
+
845
+ ✅ Successfully updated launch.json!
846
+ ```
847
+
848
+ **Learn More:**
849
+ - [Static Redirects](https://www.contentstack.com/docs/developers/launch/edge-url-redirects)
850
+ - [Cache Priming](https://www.contentstack.com/docs/developers/launch/cache-priming)
851
+
852
+ ---
853
+
854
+ ### `npx launch-help`
855
+
856
+ Display complete reference guide with all methods and examples.
857
+
858
+ **Usage:**
859
+ ```bash
860
+ npx launch-help
861
+ ```
862
+
863
+ **Shows:**
864
+ - All available methods with parameters
865
+ - Return types and examples
866
+ - CLI commands
867
+ - Quick links to documentation
117
868
 
118
869
  ---
119
870
 
120
871
  ## 🌐 Platform Support
121
872
 
122
- This library is exclusively optimized for **[Contentstack Launch](https://www.contentstack.com/docs/developers/launch)**. It assumes an environment where `Request`, `Response`, and standard Edge Global APIs are available.
873
+ This library is exclusively optimized for **[Contentstack Launch](https://www.contentstack.com/docs/developers/launch)**. It assumes an environment where:
874
+ - Standard Web APIs (`Request`, `Response`, `fetch`) are available
875
+ - Edge runtime environment
876
+ - Contentstack Launch geo-location headers
877
+
878
+ **Not compatible with:**
879
+ - Node.js servers
880
+ - Traditional hosting platforms
881
+ - Other edge platforms (Cloudflare Workers, Vercel Edge, etc.)
882
+
883
+ ---
884
+
885
+ ## 🤝 Contributing
886
+
887
+ Contributions are welcome! Please feel free to submit issues or pull requests.
888
+
889
+ **Repository:** https://github.com/AryanBansal-launch/launch-edge-utils
123
890
 
124
891
  ---
125
892
 
126
893
  ## 📄 License
127
894
 
128
895
  Distributed under the MIT License. See `LICENSE` for more information.
896
+
897
+ ---
898
+
899
+ ## 🔗 Useful Links
900
+
901
+ - **[Contentstack Launch Documentation](https://www.contentstack.com/docs/developers/launch)**
902
+ - **[Edge Functions Guide](https://www.contentstack.com/docs/developers/launch/edge-functions)**
903
+ - **[NPM Package](https://www.npmjs.com/package/@aryanbansal-launch/edge-utils)**
904
+ - **[GitHub Repository](https://github.com/AryanBansal-launch/launch-edge-utils)**
905
+
906
+ ---
907
+
908
+ **Made with ❤️ for the Contentstack Launch community**