diff --git a/docs/cloudflare-workers-astro-v6.md b/docs/cloudflare-workers-astro-v6.md new file mode 100644 index 0000000..6b896a5 --- /dev/null +++ b/docs/cloudflare-workers-astro-v6.md @@ -0,0 +1,76 @@ +# Cloudflare Workers + Astro v6 Deployment Notes + +## Stack +- Astro v6 + `@astrojs/cloudflare` adapter (v13+) +- Deployed as **Cloudflare Worker** (not Pages) +- Build: `npm run build` → Deploy: `npx wrangler deploy` + +--- + +## Issue 1 — `wrangler.json` with `pages_build_output_dir` breaks deploy + +**Error:** +``` +The name 'ASSETS' is reserved in Pages projects. Please use a different name for your Assets binding. +``` + +**Cause:** Having `pages_build_output_dir` in `wrangler.json` makes Wrangler treat the project as a Pages project. The adapter generates `dist/server/wrangler.json` with an `ASSETS` binding for static assets, which is reserved in Pages context. + +**Fix:** Remove `pages_build_output_dir` from `wrangler.json`, or delete the file entirely. The adapter generates a complete `dist/server/wrangler.json` — Wrangler picks it up automatically via config redirect. + +--- + +## Issue 2 — `locals.runtime.env` removed in Astro v6 + +**Error:** +``` +Astro.locals.runtime.env has been removed in Astro v6. +Use 'import { env } from "cloudflare:workers"' instead. +``` + +**Cause:** Astro v6 dropped the `locals.runtime.env` pattern for accessing Cloudflare env vars/bindings. + +**Fix:** + +```ts +// ❌ Astro v5 (broken in v6) +const runtime = (locals as any).runtime; +const apiKey = runtime?.env?.RESEND_API_KEY; + +// ✅ Astro v6 +import { env } from 'cloudflare:workers'; +const apiKey = (env as unknown as Record).RESEND_API_KEY; +``` + +Install types and add to `tsconfig.json`: +```bash +npm install --save-dev @cloudflare/workers-types +``` +```json +// tsconfig.json +{ + "compilerOptions": { + "types": ["@cloudflare/workers-types"] + } +} +``` + +--- + +## Env vars in Cloudflare Workers dashboard + +- Set vars under **Worker → Settings → Variables and Secrets** +- They are accessible via `env` from `cloudflare:workers` at runtime +- Do **not** rely on `process.env` or `import.meta.env` for server-side secrets in Workers + +--- + +## Wrangler config redirect (how it works) + +When `astro build` runs, `@astrojs/cloudflare` generates `dist/server/wrangler.json`. Wrangler detects this and logs: +``` +Using redirected Wrangler configuration. +- Configuration being used: "dist/server/wrangler.json" +- Original user's configuration: "wrangler.json" +``` +The generated config includes the worker `main` entry, `assets` binding, `compatibility_date`, and any adapter-detected bindings (e.g. `IMAGES`, `SESSION`). diff --git a/functions/api/api.ts b/functions/api/api.ts deleted file mode 100644 index 8335ce8..0000000 --- a/functions/api/api.ts +++ /dev/null @@ -1,27 +0,0 @@ -export async function onRequest(context: any) { - const { - request, // same as existing Worker API - env, // same as existing Worker API - } = context; - - const client_id = (env as any).GITHUB_CLIENT_ID; - - try { - const url = new URL(request.url); - const redirectUrl = new URL('https://github.com/login/oauth/authorize'); - redirectUrl.searchParams.set('client_id', client_id); - redirectUrl.searchParams.set('redirect_uri', url.origin + '/api/callback'); - redirectUrl.searchParams.set('scope', 'repo user'); - redirectUrl.searchParams.set( - 'state', - crypto.getRandomValues(new Uint8Array(12)).join(''), - ); - return Response.redirect(redirectUrl.href, 301); - - } catch (error: any) { - console.error(error); - return new Response(error.message, { - status: 500, - }); - } -} \ No newline at end of file diff --git a/functions/api/callback.ts b/functions/api/callback.ts deleted file mode 100644 index e116300..0000000 --- a/functions/api/callback.ts +++ /dev/null @@ -1,74 +0,0 @@ -function renderBody(status: string, content: any) { - const html = ` - - `; - const blob = new Blob([html]); - return blob; -} - -export async function onRequest(context: any) { - const { - request, // same as existing Worker API - env, // same as existing Worker API - } = context; - - const client_id = env.GITHUB_CLIENT_ID; - const client_secret = env.GITHUB_CLIENT_SECRET; - - try { - const url = new URL(request.url); - const code = url.searchParams.get('code'); - const response = await fetch( - 'https://github.com/login/oauth/access_token', - { - method: 'POST', - headers: { - 'content-type': 'application/json', - 'user-agent': 'cloudflare-functions-github-oauth-login-demo', - 'accept': 'application/json', - }, - body: JSON.stringify({ client_id, client_secret, code }), - }, - ); - const result = (await response.json()) as any; - if (result.error) { - return new Response(renderBody('error', result), { - headers: { - 'content-type': 'text/html;charset=UTF-8', - }, - status: 401 - }); - } - const token = result.access_token; - const provider = 'github'; - const responseBody = renderBody('success', { - token, - provider, - }); - return new Response(responseBody, { - headers: { - 'content-type': 'text/html;charset=UTF-8', - }, - status: 200 - }); - - } catch (error: any) { - console.error(error); - return new Response(error.message, { - headers: { - 'content-type': 'text/html;charset=UTF-8', - }, - status: 500, - }); - } -} \ No newline at end of file diff --git a/public/admin/config.yml b/public/admin/config.yml index 2140e0f..d73bbf3 100644 --- a/public/admin/config.yml +++ b/public/admin/config.yml @@ -1,9 +1,9 @@ backend: name: github - repo: mbcube/cachetdeco # REPLACE with your GitHub org/repo + repo: mbcube/cachetdeco branch: cloudflare - site_domaine: https://cachetdeco.br-mouad.workers.dev/ - base_url: https://cachetdeco.br-mouad.workers.dev/ + base_url: https://cachetdeco.br-mouad.workers.dev + auth_endpoint: /api/auth locale: fr media_folder: "public/images" diff --git a/src/pages/api/auth.ts b/src/pages/api/auth.ts new file mode 100644 index 0000000..9ae4843 --- /dev/null +++ b/src/pages/api/auth.ts @@ -0,0 +1,20 @@ +import type { APIRoute } from 'astro'; +import { env } from 'cloudflare:workers'; + +export const prerender = false; + +export const GET: APIRoute = async ({ request }) => { + const clientId = (env as any).GITHUB_CLIENT_ID; + + try { + const url = new URL(request.url); + const redirectUrl = new URL('https://github.com/login/oauth/authorize'); + redirectUrl.searchParams.set('client_id', clientId); + redirectUrl.searchParams.set('redirect_uri', url.origin + '/api/callback'); + redirectUrl.searchParams.set('scope', 'repo user'); + redirectUrl.searchParams.set('state', crypto.getRandomValues(new Uint8Array(12)).join('')); + return Response.redirect(redirectUrl.href, 302); + } catch (error: any) { + return new Response(error.message, { status: 500 }); + } +}; diff --git a/src/pages/api/callback.ts b/src/pages/api/callback.ts new file mode 100644 index 0000000..3605653 --- /dev/null +++ b/src/pages/api/callback.ts @@ -0,0 +1,57 @@ +import type { APIRoute } from 'astro'; +import { env } from 'cloudflare:workers'; + +export const prerender = false; + +function renderBody(status: string, content: unknown): string { + return ``; +} + +export const GET: APIRoute = async ({ request }) => { + const clientId = (env as any).GITHUB_CLIENT_ID; + const clientSecret = (env as any).GITHUB_CLIENT_SECRET; + + try { + const url = new URL(request.url); + const code = url.searchParams.get('code'); + + const response = await fetch('https://github.com/login/oauth/access_token', { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'user-agent': 'cachetdeco-github-oauth', + 'accept': 'application/json', + }, + body: JSON.stringify({ client_id: clientId, client_secret: clientSecret, code }), + }); + + const result = (await response.json()) as any; + + if (result.error) { + return new Response(renderBody('error', result), { + status: 401, + headers: { 'content-type': 'text/html;charset=UTF-8' }, + }); + } + + return new Response(renderBody('success', { token: result.access_token, provider: 'github' }), { + status: 200, + headers: { 'content-type': 'text/html;charset=UTF-8' }, + }); + } catch (error: any) { + return new Response(error.message, { + status: 500, + headers: { 'content-type': 'text/html;charset=UTF-8' }, + }); + } +};