@aryanbansal-launch/edge-utils 0.1.10 → 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 +192 -0
- package/dist/ai/gemini.js +153 -0
- package/dist/utils/inject.js +45 -0
- package/examples/redirects.csv +12 -0
- package/examples/redirects.json +53 -0
- package/package.json +22 -6
- package/readme.md +1013 -51
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
|
|
@@ -0,0 +1,192 @@
|
|
|
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
|
+
Supports bulk import from CSV/JSON files
|
|
179
|
+
|
|
180
|
+
${colors.cyan}npx launch-help${colors.reset}
|
|
181
|
+
Display this help guide
|
|
182
|
+
|
|
183
|
+
${colors.bright}${colors.yellow}📚 QUICK LINKS${colors.reset}
|
|
184
|
+
${colors.dim}────────────────────────────────────────────────────────────────────${colors.reset}
|
|
185
|
+
|
|
186
|
+
${colors.blue}Documentation:${colors.reset} https://github.com/AryanBansal-launch/launch-edge-utils
|
|
187
|
+
${colors.blue}NPM Package:${colors.reset} https://www.npmjs.com/package/@aryanbansal-launch/edge-utils
|
|
188
|
+
${colors.blue}Launch Docs:${colors.reset} https://www.contentstack.com/docs/developers/launch
|
|
189
|
+
|
|
190
|
+
${colors.dim}────────────────────────────────────────────────────────────────────${colors.reset}
|
|
191
|
+
`);
|
|
192
|
+
|
|
@@ -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
|
+
}
|
|
@@ -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": {
|
|
@@ -13,17 +13,33 @@
|
|
|
13
13
|
},
|
|
14
14
|
"main": "dist/index.js",
|
|
15
15
|
"bin": {
|
|
16
|
-
"create-launch-edge": "
|
|
17
|
-
"launch-config": "
|
|
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"
|
|
21
22
|
},
|
|
22
23
|
"files": [
|
|
23
24
|
"dist",
|
|
24
|
-
"bin"
|
|
25
|
+
"bin",
|
|
26
|
+
"examples"
|
|
25
27
|
],
|
|
26
28
|
"scripts": {
|
|
27
|
-
"build": "tsc"
|
|
28
|
-
|
|
29
|
+
"build": "tsc",
|
|
30
|
+
"prepublishOnly": "npm run build"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"contentstack",
|
|
34
|
+
"launch",
|
|
35
|
+
"edge-functions",
|
|
36
|
+
"edge",
|
|
37
|
+
"middleware",
|
|
38
|
+
"security",
|
|
39
|
+
"nextjs",
|
|
40
|
+
"redirect",
|
|
41
|
+
"geo-location",
|
|
42
|
+
"basic-auth",
|
|
43
|
+
"ip-access"
|
|
44
|
+
]
|
|
29
45
|
}
|