From 02d2f71c375f147c63160b3a01975ca8b24a8149 Mon Sep 17 00:00:00 2001 From: Xi Xu Date: Tue, 25 Nov 2025 17:55:32 +0800 Subject: [PATCH] Add Netlify deploy workflow and unify edge handler Introduces a new GitHub Actions workflow for deploying to Netlify, updates the Vercel workflow name, and refactors the edge function handler to support both Netlify and Vercel platforms with runtime detection. Adds Netlify-specific configuration and redirect files to enable shared handler logic, reducing code duplication and simplifying multi-platform deployments. --- .github/workflows/functions-ntl.yml | 41 +++++++++++ .github/workflows/functions-vc.yml | 2 +- .github/workflows/sync.yml | 106 +++++++++++++++++++--------- 3 files changed, 113 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/functions-ntl.yml diff --git a/.github/workflows/functions-ntl.yml b/.github/workflows/functions-ntl.yml new file mode 100644 index 0000000..18ec62e --- /dev/null +++ b/.github/workflows/functions-ntl.yml @@ -0,0 +1,41 @@ +name: Deploy to Netlify + +on: + workflow_run: + workflows: ["Sync to Pages and Functions"] + types: + - completed + branches: + - main + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + deployments: write + if: ${{ github.event.workflow_run.conclusion == 'success' }} + + steps: + - name: Checkout functions branch + uses: actions/checkout@v6 + with: + ref: functions + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "20" + cache: "npm" + + - name: Install Netlify CLI + run: npm install --global netlify-cli@latest + + - name: Deploy to Netlify + run: netlify deploy --prod --dir=. --auth=${{ secrets.NETLIFY_AUTH_TOKEN }} --site=${{ secrets.NETLIFY_SITE_ID }} diff --git a/.github/workflows/functions-vc.yml b/.github/workflows/functions-vc.yml index 57b55b0..07bdcc2 100644 --- a/.github/workflows/functions-vc.yml +++ b/.github/workflows/functions-vc.yml @@ -1,4 +1,4 @@ -name: Deploy to Vercel Functions +name: Deploy to Vercel on: workflow_run: diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index ec400ee..35cdf56 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -212,7 +212,7 @@ jobs: cp package-lock.json /tmp/functions-conversion/ 2>/dev/null || true cp LICENSE /tmp/functions-conversion/ - - name: Create Vercel Edge Function handler + - name: Create Edge Function handler run: | mkdir -p /tmp/functions-conversion/api cat > /tmp/functions-conversion/api/index.js << 'EOF' @@ -237,51 +237,56 @@ jobs: import { handleRequest } from '../src/index.js'; /** - * Vercel Edge Function handler for all routes. + * Edge Function handler. * - * This adapter enables Xget to run on Vercel's Edge Runtime by mapping - * Vercel's environment model to Cloudflare Workers' expected format. - * - * The Vercel Edge Runtime uses standard Web APIs (Request, Response, fetch), - * making it compatible with the existing Cloudflare Workers code. - * - * @param {Request} request - Standard Web API Request + * @param {Request} request - Standard Web API Request object + * @param {Object} [context] - Platform-specific context (Netlify only) + * @param {Object} [context.geo] - Geolocation data (Netlify) + * @param {string} [context.ip] - Client IP address (Netlify) + * @param {Object} [context.env] - Environment variables (Netlify) + * @param {Function} [context.waitUntil] - Background task extension (Netlify) * @returns {Promise} Standard Web API Response * * @example - * // This is called automatically by Vercel Edge Runtime - * // Runtime invokes: handler(request) - * // Returns: Response with proxied content + * // Netlify invokes with context + * handler(request, { geo: {...}, ip: '1.2.3.4', env: {...}, waitUntil: fn }) * * @example - * // Environment variables usage - * // Vercel dashboard or vercel.json: TIMEOUT_SECONDS = "60" - * // process.env contains: { TIMEOUT_SECONDS: "60" } - * // Mapped to env object and passed to handleRequest + * // Vercel invokes without context + * handler(request) */ - export default async function handler(request) { - // Map process.env to Cloudflare Workers env format + export default async function handler(request, context) { + // Detect runtime environment + const isNetlify = context && (context.geo !== undefined || context.ip !== undefined); + + // Normalize environment variables + const envSource = isNetlify && context.env ? context.env : process.env; const env = { - TIMEOUT_SECONDS: process.env.TIMEOUT_SECONDS, - MAX_RETRIES: process.env.MAX_RETRIES, - RETRY_DELAY_MS: process.env.RETRY_DELAY_MS, - CACHE_DURATION: process.env.CACHE_DURATION, - ALLOWED_METHODS: process.env.ALLOWED_METHODS, - ALLOWED_ORIGINS: process.env.ALLOWED_ORIGINS, - MAX_PATH_LENGTH: process.env.MAX_PATH_LENGTH, + TIMEOUT_SECONDS: envSource.TIMEOUT_SECONDS, + MAX_RETRIES: envSource.MAX_RETRIES, + RETRY_DELAY_MS: envSource.RETRY_DELAY_MS, + CACHE_DURATION: envSource.CACHE_DURATION, + ALLOWED_METHODS: envSource.ALLOWED_METHODS, + ALLOWED_ORIGINS: envSource.ALLOWED_ORIGINS, + MAX_PATH_LENGTH: envSource.MAX_PATH_LENGTH, }; - // Create minimal execution context - // Note: Vercel Edge Runtime doesn't support waitUntil or passThroughOnException + // Create normalized execution context const ctx = { - waitUntil: (promise) => { - // No-op: Vercel doesn't support background tasks - // Cache writes will run synchronously instead - console.warn('waitUntil is not supported in Vercel Edge Runtime'); - }, + waitUntil: isNetlify && context.waitUntil + ? (promise) => context.waitUntil(promise) + : (promise) => { + // No-op on Vercel: background tasks not supported + // Cache writes will run synchronously instead + if (process.env.NODE_ENV !== 'production') { + console.warn('waitUntil is not supported in Vercel Edge Runtime'); + } + }, passThroughOnException: () => { - // No-op: Vercel-specific error handling - console.warn('passThroughOnException is not supported in Vercel Edge Runtime'); + // Not supported on either platform in this context + if (process.env.NODE_ENV !== 'production') { + console.warn('passThroughOnException is not universally supported'); + } } }; @@ -289,7 +294,7 @@ jobs: return handleRequest(request, env, ctx); } - // Vercel Edge Runtime configuration + // Vercel Edge Runtime configuration (ignored by Netlify) export const config = { runtime: 'edge', }; @@ -323,6 +328,37 @@ jobs: } EOF + - name: Create netlify.toml + run: | + cd /tmp/functions-conversion + cat > netlify.toml << 'EOF' + [[edge_functions]] + function = "edge-handler" + path = "/*" + + [build] + publish = "." + EOF + + - name: Create Netlify Edge Function redirect + run: | + cd /tmp/functions-conversion + mkdir -p netlify/edge-functions + cat > netlify/edge-functions/edge-handler.js << 'EOF' + /** + * Netlify Edge Function entry point. + * + * This file serves as a redirect to the edge function handler + * located at /api/index.js. Both Netlify and Vercel can use the same + * handler code, with platform detection handling the differences. + * + * Netlify requires edge functions to be in netlify/edge-functions/, + * while Vercel uses /api/ directory. This approach maintains a single + * source of truth at /api/index.js. + */ + export { default, config } from '../../api/index.js'; + EOF + - name: Export handleRequest from src/index.js run: | cd /tmp/functions-conversion