@aryanbansal-launch/edge-utils 0.1.3 → 0.1.5
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-init.js +137 -0
- package/package.json +6 -2
- package/readme.md +32 -85
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
const functionsDir = path.join(process.cwd(), 'functions');
|
|
7
|
+
const edgeFile = path.join(functionsDir, '[proxy].edge.js');
|
|
8
|
+
|
|
9
|
+
// Simple ANSI colors for better terminal output
|
|
10
|
+
const colors = {
|
|
11
|
+
reset: '\x1b[0m',
|
|
12
|
+
bright: '\x1b[1m',
|
|
13
|
+
green: '\x1b[32m',
|
|
14
|
+
yellow: '\x1b[33m',
|
|
15
|
+
cyan: '\x1b[36m',
|
|
16
|
+
blue: '\x1b[34m'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const template = `
|
|
20
|
+
import {
|
|
21
|
+
jsonResponse,
|
|
22
|
+
passThrough,
|
|
23
|
+
redirectIfMatch,
|
|
24
|
+
protectWithBasicAuth,
|
|
25
|
+
ipAccessControl,
|
|
26
|
+
blockAICrawlers,
|
|
27
|
+
getGeoHeaders,
|
|
28
|
+
handleNextJS_RSC
|
|
29
|
+
} from "@aryanbansal-launch/edge-utils";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Default Edge Handler for Contentstack Launch
|
|
33
|
+
* This file was automatically generated by @aryanbansal-launch/edge-utils
|
|
34
|
+
*/
|
|
35
|
+
export default async function handler(request, context) {
|
|
36
|
+
// 1. ⚛️ Fix Next.js RSC issues for specific paths
|
|
37
|
+
const rscResponse = await handleNextJS_RSC(request, {
|
|
38
|
+
affectedPaths: ["/my-rsc-page", "/another-page"]
|
|
39
|
+
});
|
|
40
|
+
if (rscResponse) return rscResponse;
|
|
41
|
+
|
|
42
|
+
// 2. 🛡️ Block AI bots immediately
|
|
43
|
+
const botResponse = blockAICrawlers(request);
|
|
44
|
+
if (botResponse) return botResponse;
|
|
45
|
+
|
|
46
|
+
// 3. 🧱 IP Whitelisting
|
|
47
|
+
const ipResponse = ipAccessControl(request, { allow: ["203.0.113.10"] });
|
|
48
|
+
if (ipResponse) return ipResponse;
|
|
49
|
+
|
|
50
|
+
// 4. 🔐 Domain-specific Basic Auth
|
|
51
|
+
const authResponse = await protectWithBasicAuth(request, {
|
|
52
|
+
hostnameIncludes: "staging.myapp.com",
|
|
53
|
+
username: "admin",
|
|
54
|
+
password: "securepassword123"
|
|
55
|
+
});
|
|
56
|
+
if (authResponse && authResponse.status === 401) return authResponse;
|
|
57
|
+
|
|
58
|
+
// 5. 🔀 SEO-friendly Redirects
|
|
59
|
+
const redirectResponse = redirectIfMatch(request, {
|
|
60
|
+
path: "/legacy-url",
|
|
61
|
+
to: "/modern-url",
|
|
62
|
+
status: 301
|
|
63
|
+
});
|
|
64
|
+
if (redirectResponse) return redirectResponse;
|
|
65
|
+
|
|
66
|
+
// 6. 📍 Geo-Location Access
|
|
67
|
+
const geo = getGeoHeaders(request);
|
|
68
|
+
if (geo.country === "US") {
|
|
69
|
+
console.log(\`User from \${geo.city}, \${geo.region}\`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 7. 📤 Custom JSON Responses
|
|
73
|
+
if (new URL(request.url).pathname === "/api/health") {
|
|
74
|
+
return jsonResponse({ status: "healthy", region: geo.region });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 8. 🚀 Pass through to origin
|
|
78
|
+
return passThrough(request);
|
|
79
|
+
}
|
|
80
|
+
`.trim();
|
|
81
|
+
|
|
82
|
+
async function init() {
|
|
83
|
+
console.log(`\n${colors.bright}${colors.cyan}🚀 Edge Utilities: Contentstack Launch Setup${colors.reset}\n`);
|
|
84
|
+
|
|
85
|
+
let actionsTaken = 0;
|
|
86
|
+
|
|
87
|
+
// 1. Root level check: Look for package.json
|
|
88
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
89
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
90
|
+
console.log(`${colors.red}❌ Error: Root directory not detected.${colors.reset}`);
|
|
91
|
+
console.log(`${colors.yellow}Contentstack Launch requires the 'functions' folder to be at your project root${colors.reset}`);
|
|
92
|
+
console.log(`${colors.yellow}(the same directory as your package.json).${colors.reset}\n`);
|
|
93
|
+
console.log(`Please ${colors.bright}cd${colors.reset} into your project root and try again.`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// 2. Folder existence check
|
|
99
|
+
if (!fs.existsSync(functionsDir)) {
|
|
100
|
+
fs.mkdirSync(functionsDir, { recursive: true });
|
|
101
|
+
console.log(`${colors.green}✨ New:${colors.reset} Created /functions directory`);
|
|
102
|
+
actionsTaken++;
|
|
103
|
+
} else {
|
|
104
|
+
console.log(`${colors.blue}ℹ️ Existing:${colors.reset} /functions directory already found`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 3. File existence check (don't overwrite user's work)
|
|
108
|
+
if (!fs.existsSync(edgeFile)) {
|
|
109
|
+
fs.writeFileSync(edgeFile, template + '\n');
|
|
110
|
+
console.log(`${colors.green}✨ New:${colors.reset} Created /functions/[proxy].edge.js`);
|
|
111
|
+
actionsTaken++;
|
|
112
|
+
} else {
|
|
113
|
+
console.log(`${colors.blue}ℹ️ Existing:${colors.reset} /functions/[proxy].edge.js already found`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Final Summary Messages based on the state
|
|
117
|
+
if (actionsTaken === 0) {
|
|
118
|
+
console.log(`\n${colors.bright}${colors.blue}🏁 Everything is already set up!${colors.reset}`);
|
|
119
|
+
console.log(`No changes were made to your existing files.`);
|
|
120
|
+
} else {
|
|
121
|
+
console.log(`\n${colors.bright}${colors.green}🎉 Setup Complete!${colors.reset}`);
|
|
122
|
+
console.log(`Successfully prepared your edge environment.`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(`\n${colors.bright}Next Steps:${colors.reset}`);
|
|
126
|
+
console.log(`1. Open ${colors.cyan}functions/[proxy].edge.js${colors.reset}`);
|
|
127
|
+
console.log(`2. Customize your redirects, auth, and RSC paths`);
|
|
128
|
+
console.log(`3. Deploy your project to Contentstack Launch\n`);
|
|
129
|
+
|
|
130
|
+
console.log(`${colors.blue}Documentation:${colors.reset} https://github.com/AryanBansal-launch/launch-edge-utils#readme\n`);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error(`\n${colors.red}❌ Error during setup:${colors.reset}`, error.message);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
init();
|
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.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -12,11 +12,15 @@
|
|
|
12
12
|
"url": "https://github.com/AryanBansal-launch/launch-edge-utils/issues"
|
|
13
13
|
},
|
|
14
14
|
"main": "dist/index.js",
|
|
15
|
+
"bin": {
|
|
16
|
+
"launch-init": "./bin/launch-init.js"
|
|
17
|
+
},
|
|
15
18
|
"exports": {
|
|
16
19
|
".": "./dist/index.js"
|
|
17
20
|
},
|
|
18
21
|
"files": [
|
|
19
|
-
"dist"
|
|
22
|
+
"dist",
|
|
23
|
+
"bin"
|
|
20
24
|
],
|
|
21
25
|
"scripts": {
|
|
22
26
|
"build": "tsc"
|
package/readme.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
# 🚀 Edge Utils
|
|
1
|
+
# 🚀 Edge Utils for Contentstack Launch
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@launch/edge-utils)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](#platform-support)
|
|
6
5
|
|
|
7
|
-
A lightweight,
|
|
6
|
+
A lightweight, high-performance toolkit specifically designed for **Contentstack Launch Edge Functions**. Speed up your development with production-ready utilities for security, authentication, routing, and Next.js compatibility—all optimized to run at the edge.
|
|
8
7
|
|
|
9
8
|
---
|
|
10
9
|
|
|
@@ -13,25 +12,35 @@ A lightweight, developer-friendly toolkit for **Edge Computing**. Speed up your
|
|
|
13
12
|
- 🛡️ **Security First**: Block AI crawlers and manage IP access with ease.
|
|
14
13
|
- 🔐 **Edge Auth**: Implement Basic Auth directly at the edge for specific hostnames.
|
|
15
14
|
- 📍 **Geo-Aware**: Easily extract location data from request headers.
|
|
16
|
-
- ⚛️ **Next.js Ready**: Built-in fixes for RSC header issues on
|
|
15
|
+
- ⚛️ **Next.js Ready**: Built-in fixes for RSC header issues on Launch proxies.
|
|
17
16
|
- 🔀 **Smart Routing**: Declarative redirects based on path and method.
|
|
18
17
|
- ⚡ **Zero Dependencies**: Lightweight and optimized for edge runtime limits.
|
|
19
18
|
|
|
20
19
|
---
|
|
21
20
|
|
|
22
|
-
##
|
|
21
|
+
## ⚡ Quick Start (Recommended)
|
|
23
22
|
|
|
23
|
+
Set up your entire edge environment in seconds with our automated CLI tool.
|
|
24
|
+
|
|
25
|
+
### 1. Install
|
|
24
26
|
```bash
|
|
25
|
-
npm install @launch/edge-utils
|
|
27
|
+
npm install @aryanbansal-launch/edge-utils
|
|
26
28
|
```
|
|
27
29
|
|
|
30
|
+
### 2. Initialize
|
|
31
|
+
Run this command from your **project root**:
|
|
32
|
+
```bash
|
|
33
|
+
npx launch-init
|
|
34
|
+
```
|
|
35
|
+
This will automatically create the `functions/` directory and a boilerplate `[proxy].edge.js` handler for you.
|
|
36
|
+
|
|
28
37
|
---
|
|
29
38
|
|
|
30
39
|
## 🛠️ Usage Example
|
|
31
40
|
|
|
32
|
-
|
|
41
|
+
Once initialized, your `functions/[proxy].edge.js` will look like a powerful middleware chain:
|
|
33
42
|
|
|
34
|
-
```
|
|
43
|
+
```javascript
|
|
35
44
|
import {
|
|
36
45
|
jsonResponse,
|
|
37
46
|
passThrough,
|
|
@@ -41,12 +50,12 @@ import {
|
|
|
41
50
|
blockAICrawlers,
|
|
42
51
|
getGeoHeaders,
|
|
43
52
|
handleNextJS_RSC
|
|
44
|
-
} from "@launch/edge-utils";
|
|
53
|
+
} from "@aryanbansal-launch/edge-utils";
|
|
45
54
|
|
|
46
|
-
export default async function handler(request
|
|
55
|
+
export default async function handler(request, context) {
|
|
47
56
|
// 1. ⚛️ Fix Next.js RSC issues for specific paths
|
|
48
57
|
const rscResponse = await handleNextJS_RSC(request, {
|
|
49
|
-
affectedPaths: ["/
|
|
58
|
+
affectedPaths: ["/shop", "/about"]
|
|
50
59
|
});
|
|
51
60
|
if (rscResponse) return rscResponse;
|
|
52
61
|
|
|
@@ -54,11 +63,11 @@ export default async function handler(request: Request) {
|
|
|
54
63
|
const botResponse = blockAICrawlers(request);
|
|
55
64
|
if (botResponse) return botResponse;
|
|
56
65
|
|
|
57
|
-
//
|
|
66
|
+
// 3. 🧱 IP Whitelisting
|
|
58
67
|
const ipResponse = ipAccessControl(request, { allow: ["203.0.113.10"] });
|
|
59
68
|
if (ipResponse) return ipResponse;
|
|
60
69
|
|
|
61
|
-
//
|
|
70
|
+
// 4. 🔐 Domain-specific Basic Auth (e.g., for staging)
|
|
62
71
|
const authResponse = await protectWithBasicAuth(request, {
|
|
63
72
|
hostnameIncludes: "staging.myapp.com",
|
|
64
73
|
username: "admin",
|
|
@@ -66,7 +75,7 @@ export default async function handler(request: Request) {
|
|
|
66
75
|
});
|
|
67
76
|
if (authResponse && authResponse.status === 401) return authResponse;
|
|
68
77
|
|
|
69
|
-
//
|
|
78
|
+
// 5. 🔀 SEO-friendly Redirects
|
|
70
79
|
const redirectResponse = redirectIfMatch(request, {
|
|
71
80
|
path: "/legacy-url",
|
|
72
81
|
to: "/modern-url",
|
|
@@ -74,16 +83,9 @@ export default async function handler(request: Request) {
|
|
|
74
83
|
});
|
|
75
84
|
if (redirectResponse) return redirectResponse;
|
|
76
85
|
|
|
77
|
-
//
|
|
86
|
+
// 6. 📍 Geo-Location Access
|
|
78
87
|
const geo = getGeoHeaders(request);
|
|
79
|
-
|
|
80
|
-
console.log(`User from ${geo.city}, ${geo.region}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// 6. 📤 Custom JSON Responses
|
|
84
|
-
if (new URL(request.url).pathname === "/api/health") {
|
|
85
|
-
return jsonResponse({ status: "healthy", region: geo.region });
|
|
86
|
-
}
|
|
88
|
+
console.log(`Request from ${geo.city}, ${geo.country}`);
|
|
87
89
|
|
|
88
90
|
// 7. 🚀 Pass through to origin
|
|
89
91
|
return passThrough(request);
|
|
@@ -95,78 +97,23 @@ export default async function handler(request: Request) {
|
|
|
95
97
|
## 📖 API Reference
|
|
96
98
|
|
|
97
99
|
### 🛡️ Security
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
Blocks common AI crawlers (GPTBot, ClaudeBot, etc.) to protect your content from scraping.
|
|
101
|
-
- **Parameters**: `request: Request`, `bots?: string[]` (optional list to override defaults)
|
|
102
|
-
- **Returns**: `Response` (403) or `null`.
|
|
103
|
-
|
|
104
|
-
#### `ipAccessControl`
|
|
105
|
-
Simple firewall for your edge function.
|
|
106
|
-
- **Options**:
|
|
107
|
-
- `allow`: Array of IPs allowed to access.
|
|
108
|
-
- `deny`: Array of IPs to block.
|
|
109
|
-
- **Returns**: `Response` (403) or `null`.
|
|
100
|
+
- **`blockAICrawlers(request, bots?)`**: Blocks common AI crawlers.
|
|
101
|
+
- **`ipAccessControl(request, { allow?, deny? })`**: Simple IP-based firewall.
|
|
110
102
|
|
|
111
103
|
### 🔐 Authentication
|
|
112
|
-
|
|
113
|
-
#### `protectWithBasicAuth`
|
|
114
|
-
Prompt for credentials based on the hostname.
|
|
115
|
-
- **Options**:
|
|
116
|
-
- `hostnameIncludes`: Match substring in hostname (e.g., ".dev").
|
|
117
|
-
- `username`: Required username.
|
|
118
|
-
- `password`: Required password.
|
|
119
|
-
- `realm`: Optional realm name for the auth prompt.
|
|
120
|
-
- **Returns**: `Promise<Response>` or `null`.
|
|
104
|
+
- **`protectWithBasicAuth(request, options)`**: Prompt for credentials based on hostname.
|
|
121
105
|
|
|
122
106
|
### 🔀 Redirection
|
|
123
|
-
|
|
124
|
-
#### `redirectIfMatch`
|
|
125
|
-
Perform redirects directly at the edge to reduce latency.
|
|
126
|
-
- **Options**:
|
|
127
|
-
- `path`: The path to match.
|
|
128
|
-
- `method`: HTTP method to match (optional, defaults to any).
|
|
129
|
-
- `to`: Target path or URL.
|
|
130
|
-
- `status`: HTTP status code (default: 301).
|
|
131
|
-
- **Returns**: `Response` (Redirect) or `null`.
|
|
107
|
+
- **`redirectIfMatch(request, options)`**: Perform SEO-friendly redirects at the edge.
|
|
132
108
|
|
|
133
109
|
### 📍 Geo Location
|
|
134
|
-
|
|
135
|
-
#### `getGeoHeaders`
|
|
136
|
-
Extracts geo-information provided by edge platform headers.
|
|
137
|
-
- **Returns**: Object with `country`, `region`, `city`, `latitude`, `longitude`.
|
|
110
|
+
- **`getGeoHeaders(request)`**: Returns an object with `country`, `region`, `city`, `latitude`, `longitude`.
|
|
138
111
|
|
|
139
112
|
### ⚛️ Next.js
|
|
140
|
-
|
|
141
|
-
#### `handleNextJS_RSC`
|
|
142
|
-
Handles Next.js React Server Component (RSC) header issues on Contentstack Launch. It detects requests to "affected paths" that have the `rsc` header but lack the `_rsc` query parameter, and strips the header to prevent proxy/caching issues.
|
|
143
|
-
- **Options**:
|
|
144
|
-
- `affectedPaths`: Array of pathnames (e.g., `['/shop', '/about']`) where this fix should apply.
|
|
145
|
-
- **Returns**: `Promise<Response>` (the re-fetched request without RSC header) or `null`.
|
|
113
|
+
- **`handleNextJS_RSC(request, { affectedPaths })`**: Resolves RSC header issues on Contentstack Launch.
|
|
146
114
|
|
|
147
115
|
---
|
|
148
116
|
|
|
149
117
|
## 🌐 Platform Support
|
|
150
118
|
|
|
151
|
-
|
|
152
|
-
- [Contentstack Launch](https://www.contentstack.com/docs/developers/launch)
|
|
153
|
-
- [Vercel Edge Functions](https://vercel.com/docs/concepts/functions/edge-functions)
|
|
154
|
-
- [Cloudflare Workers](https://workers.cloudflare.com/)
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
## 🤝 Contributing
|
|
159
|
-
|
|
160
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
161
|
-
|
|
162
|
-
1. Fork the Project
|
|
163
|
-
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
|
164
|
-
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
|
|
165
|
-
4. Push to the Branch (`git push origin feature/AmazingFeature`)
|
|
166
|
-
5. Open a Pull Request
|
|
167
|
-
|
|
168
|
-
---
|
|
169
|
-
|
|
170
|
-
## 📄 License
|
|
171
|
-
|
|
172
|
-
Distributed under the MIT License. See `LICENSE` for more information.
|
|
119
|
+
This library is exclusively optimized for **[Contentstack Launch](https://www.contentstack.com/docs/developers/launch)**.
|