[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F5YP614PCT0PKG9QCSY220":3,"skill-same:01M6F5YP614PCT0PKG9QCSY220":67},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":32,"linkOnly":33,"markdownContent":34,"markdownBody":35,"frontMatter":36,"refName":40,"commitSha":41,"skillDirectory":42,"forksCount":43,"repositoryHtmlUrl":44,"repositoryHomepageUrl":6,"ownerHtmlUrl":45,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":46,"install":47,"resources":60},"01M6F5YP614PCT0PKG9QCSY220","eas-hosting",null,"Curated upstream guidance for Eas Hosting; use when the workflow matches the user goal.","boisenoise\u002Fskills-collections","boisenoise","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F193180?v=4","01M6CDBDQK6BDN55ZMA6B6PKBM","01M6CDBDVSJ3JPTBYN26FZ7TZK",12,"MIT","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-eas-hosting\u002FSKILL.md",[],[18,19,20,21,22,23,24,25,26,27,28,29,30,31],"hosting","eas hosting","workflow","official","user","use","goal","expo","guidance","upstream","critical","for","when","2026","2026-11-10 10:12:30",false,"---\ndescription: Curated upstream guidance for Eas Hosting; use when the workflow matches the user goal.\nname: eas-hosting\nversion: 1.0.0\nlicense: MIT\nsource_repo: expo\u002Fskills\nsource_type: official\nsource: expo\ndate_added: '2026-09-21'\nrisk: critical\n---\n# EAS Hosting\n\n> **EAS service - costs apply.** EAS Hosting is a paid Expo Application Services product with free-tier limits; production deploys use your plan's request and bandwidth allowance. See https:\u002F\u002Fexpo.dev\u002Fpricing. Authoring API routes and exporting the web bundle are free and open source, and you can self-host the exported server output instead of EAS Hosting.\n\nEAS Hosting deploys your Expo **web app and API routes** to Expo's managed edge (Cloudflare Workers). Export the web bundle with `npx expo export -p web` and ship it with `eas deploy` - the same command deploys any Expo Router API routes bundled alongside it. This skill covers deploying a website, authoring API routes, and the hosting runtime; see the Deployment section below for the deploy workflow.\n\nThese are partial route examples, not production authentication or data-access implementations. Presence of an Authorization header is not verification: validate issuer, audience, signature and expiry with the chosen provider, derive tenant identity server-side and enforce object ownership. Return 401 until a real verifier is implemented. Bound request bodies, upstream timeouts and paid AI calls; rate-limit and redact upstream errors. Public CORS is only for intentionally public endpoints, not access control. Native clients need a configured absolute HTTPS API origin; browser-relative fetch examples are not universally valid in React Native. Verify Expo runtime limits against current EAS docs; Workers capabilities are not all exposed by EAS Hosting.\n\n## When to Use API Routes\n\nUse API routes when you need:\n\n- **Server-side secrets** — API keys, database credentials, or tokens that must never reach the client\n- **Database operations** — Direct database queries that shouldn't be exposed\n- **Third-party API proxies** — Hide API keys when calling external services (OpenAI, Stripe, etc.)\n- **Server-side validation** — Validate data before database writes\n- **Webhook endpoints** — Receive callbacks from services like Stripe or GitHub\n- **Rate limiting** — Control access at the server level\n- **Heavy computation** — Offload processing that would be slow on mobile\n\n## When NOT to Use API Routes\n\nAvoid API routes when:\n\n- **Data is already public** — Use direct fetch to public APIs instead\n- **No secrets required** — Static data or client-safe operations\n- **Real-time updates needed** — Use WebSockets or services like Supabase Realtime\n- **Simple CRUD** — Consider Firebase, Supabase, or Convex for managed backends\n- **File uploads** — Use direct-to-storage uploads (S3 presigned URLs, Cloudflare R2)\n- **Authentication only** — Use Clerk, Auth0, or Firebase Auth instead\n\n## File Structure\n\nAPI routes live in the `app` directory with `+api.ts` suffix:\n\n```\napp\u002F\n  api\u002F\n    hello+api.ts          → GET \u002Fapi\u002Fhello\n    users+api.ts          → \u002Fapi\u002Fusers\n    users\u002F[id]+api.ts     → \u002Fapi\u002Fusers\u002F:id\n  (tabs)\u002F\n    index.tsx\n```\n\n## Basic API Route\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fhello+api.ts\nexport function GET(request: Request) {\n  return Response.json({ message: \"Hello from Expo!\" });\n}\n```\n\n## HTTP Methods\n\nExport named functions for each HTTP method:\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fitems+api.ts\nexport function GET(request: Request) {\n  return Response.json({ items: [] });\n}\n\nexport async function POST(request: Request) {\n  const body = await request.json();\n  return Response.json({ created: body }, { status: 201 });\n}\n\nexport async function PUT(request: Request) {\n  const body = await request.json();\n  return Response.json({ updated: body });\n}\n\nexport async function DELETE(request: Request) {\n  return new Response(null, { status: 204 });\n}\n```\n\n## Dynamic Routes\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fusers\u002F[id]+api.ts\nexport function GET(request: Request, { id }: { id: string }) {\n  return Response.json({ userId: id });\n}\n```\n\n## Request Handling\n\n### Query Parameters\n\n```ts\nexport function GET(request: Request) {\n  const url = new URL(request.url);\n  const page = url.searchParams.get(\"page\") ?? \"1\";\n  const limit = url.searchParams.get(\"limit\") ?? \"10\";\n\n  return Response.json({ page, limit });\n}\n```\n\n### Headers\n\n```ts\nexport function GET(request: Request) {\n  const auth = request.headers.get(\"Authorization\");\n\n  if (!auth) {\n    return Response.json({ error: \"Unauthorized\" }, { status: 401 });\n  }\n\n  return Response.json({ error: \"Token verification not implemented\" }, { status: 401 });\n}\n```\n\n### JSON Body\n\n```ts\nexport async function POST(request: Request) {\n  const { email, password } = await request.json();\n\n  if (!email || !password) {\n    return Response.json({ error: \"Missing fields\" }, { status: 400 });\n  }\n\n  return Response.json({ success: true });\n}\n```\n\n## Environment Variables\n\nUse `process.env` for server-side secrets:\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fai+api.ts\nexport async function POST(request: Request) {\n  const { prompt } = await request.json();\n\n  const response = await fetch(\"https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fchat\u002Fcompletions\", {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application\u002Fjson\",\n      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,\n    },\n    body: JSON.stringify({\n      model: \"gpt-4\",\n      messages: [{ role: \"user\", content: prompt }],\n    }),\n  });\n\n  const data = await response.json();\n  return Response.json(data);\n}\n```\n\nSet environment variables:\n\n- **Local**: Create `.env` file (never commit)\n- **EAS Hosting**: Use `eas env:create` or Expo dashboard\n\n## CORS Headers\n\nAdd CORS for web clients:\n\n```ts\nconst corsHeaders = {\n  \"Access-Control-Allow-Origin\": \"*\",\n  \"Access-Control-Allow-Methods\": \"GET, POST, PUT, DELETE, OPTIONS\",\n  \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\",\n};\n\nexport function OPTIONS() {\n  return new Response(null, { headers: corsHeaders });\n}\n\nexport function GET() {\n  return Response.json({ data: \"value\" }, { headers: corsHeaders });\n}\n```\n\n## Error Handling\n\n```ts\nexport async function POST(request: Request) {\n  try {\n    const body = await request.json();\n    \u002F\u002F Process...\n    return Response.json({ success: true });\n  } catch (error) {\n    console.error(\"API error:\", error);\n    return Response.json({ error: \"Internal server error\" }, { status: 500 });\n  }\n}\n```\n\n## Testing Locally\n\nStart the development server with API routes:\n\n```bash\nnpx expo start\n```\n\nThis starts a local server at `http:\u002F\u002Flocalhost:8081` with full API route support.\n\nTest with curl:\n\n```bash\ncurl http:\u002F\u002Flocalhost:8081\u002Fapi\u002Fhello\ncurl -X POST http:\u002F\u002Flocalhost:8081\u002Fapi\u002Fusers -H \"Content-Type: application\u002Fjson\" -d '{\"name\":\"Test\"}'\n```\n\n## Deployment to EAS Hosting\n\n### Prerequisites\n\n```bash\nnpm install -g eas-cli\neas login\n```\n\n### Deploy\n\nDeploying ships your web bundle and any Expo Router API routes together - `eas deploy` handles both. The export runs whether you have a full website, an API-routes-only backend, or both.\n\n```bash\n# Export the web bundle (includes any API routes)\nnpx expo export -p web\n\n# Deploy a preview (PR-style URL)\nnpx eas-cli@latest deploy\n\n# Deploy to production\nnpx eas-cli@latest deploy --prod\n```\n\nEverything lands on EAS Hosting (Cloudflare Workers).\n\n### Environment Variables for Production\n\n```bash\n# Create a secret\neas env:create --name OPENAI_API_KEY --environment production\n# Enter the value through the CLI secure prompt; do not put it in shell history.\n\n# Or use the Expo dashboard\n```\n\n### Custom Domain\n\nConfigure in `eas.json` or Expo dashboard.\n\n### Automate with EAS Workflows\n\nDeploy the website (and API routes) on every push to main with a `type: deploy` workflow:\n\n`.eas\u002Fworkflows\u002Fdeploy.yml`\n\n```yaml\nname: Deploy\n\non:\n  push:\n    branches:\n      - main\n\n# https:\u002F\u002Fdocs.expo.dev\u002Feas\u002Fworkflows\u002Fsyntax\u002F#deploy\njobs:\n  deploy_web:\n    type: deploy\n    params:\n      prod: true\n```\n\nPreview deploys for pull requests use the same job type with `prod: false`:\n\n```yaml\nname: Web PR Preview\n\non:\n  pull_request:\n    types: [opened, synchronize]\n\njobs:\n  preview:\n    type: deploy\n    params:\n      prod: false\n```\n\nTo author or validate workflow YAML beyond these examples, use the `eas-workflows` skill.\n\n## EAS Hosting Runtime (Cloudflare Workers)\n\nAPI routes run on Cloudflare Workers. Key limitations:\n\n### Missing\u002FLimited APIs\n\n- **No Node.js filesystem** — `fs` module unavailable\n- **No native Node modules** — Use Web APIs or polyfills\n- **Limited execution time** — provider\u002Fplan-specific CPU and wall-clock limits; verify current EAS documentation\n- **No assumed persistent connections** — verify EAS-specific streaming\u002FWebSocket support\n- **fetch is available** — Use standard fetch for HTTP requests\n\n### Use Web APIs Instead\n\n```ts\n\u002F\u002F Use Web Crypto instead of Node crypto\nconst hash = await crypto.subtle.digest(\n  \"SHA-256\",\n  new TextEncoder().encode(\"data\")\n);\n\n\u002F\u002F Use fetch instead of node-fetch\nconst response = await fetch(\"https:\u002F\u002Fapi.example.com\");\n\n\u002F\u002F Use Response\u002FRequest (already available)\nreturn new Response(JSON.stringify(data), {\n  headers: { \"Content-Type\": \"application\u002Fjson\" },\n});\n```\n\n### Database Options\n\nSince filesystem is unavailable, use cloud databases:\n\n- **Cloudflare D1** — SQLite at the edge\n- **Turso** — Distributed SQLite\n- **PlanetScale** — Serverless MySQL\n- **Supabase** — Postgres with REST API\n- **Neon** — Serverless Postgres\n\nExample with Turso:\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fusers+api.ts\nimport { createClient } from \"@libsql\u002Fclient\u002Fweb\";\n\nconst db = createClient({\n  url: process.env.TURSO_URL!,\n  authToken: process.env.TURSO_AUTH_TOKEN!,\n});\n\nexport async function GET() {\n  const result = await db.execute(\"SELECT * FROM users\");\n  return Response.json(result.rows);\n}\n```\n\n## Calling API Routes from Client\n\n```ts\n\u002F\u002F From React Native components\nconst response = await fetch(\"\u002Fapi\u002Fhello\");\nconst data = await response.json();\n\n\u002F\u002F With body\nconst response = await fetch(\"\u002Fapi\u002Fusers\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application\u002Fjson\" },\n  body: JSON.stringify({ name: \"John\" }),\n});\n```\n\n## Common Patterns\n\n### Authentication Middleware\n\n```ts\n\u002F\u002F utils\u002Fauth.ts\nexport async function requireAuth(request: Request) {\n  const token = request.headers.get(\"Authorization\")?.replace(\"Bearer \", \"\");\n\n  if (!token) {\n    throw new Response(JSON.stringify({ error: \"Unauthorized\" }), {\n      status: 401,\n      headers: { \"Content-Type\": \"application\u002Fjson\" },\n    });\n  }\n\n  \u002F\u002F Configure a real verifier and return its verified subject; fail closed meanwhile.\n  throw new Response(\"Unauthorized\", { status: 401 });\n}\n\n\u002F\u002F app\u002Fapi\u002Fprotected+api.ts\nimport { requireAuth } from \"..\u002F..\u002Futils\u002Fauth\";\n\nexport async function GET(request: Request) {\n  const { userId } = await requireAuth(request);\n  return Response.json({ userId });\n}\n```\n\n### Proxy External API\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fweather+api.ts\nexport async function GET(request: Request) {\n  const url = new URL(request.url);\n  const city = url.searchParams.get(\"city\");\n\n  const response = await fetch(\n    `https:\u002F\u002Fapi.weather.com\u002Fv1\u002Fcurrent?city=${city}&key=${process.env.WEATHER_API_KEY}`\n  );\n\n  return Response.json(await response.json());\n}\n```\n\n## Rules\n\n- NEVER expose API keys or secrets in client code\n- ALWAYS validate and sanitize user input\n- Use proper HTTP status codes (200, 201, 400, 401, 404, 500)\n- Handle errors gracefully with try\u002Fcatch\n- Keep API routes focused — one responsibility per endpoint\n- Use TypeScript for type safety\n- Log errors server-side for debugging\n\n## Submitting Feedback\nIf you encounter errors, misleading or outdated information in this skill, report it so Expo can improve:\n```bash\nnpx --yes submit-expo-feedback@latest --category skills --subject \"eas-hosting\" \"\u003Cactionable feedback>\"\n```\nOnly submit when you have something specific and actionable to report. Include as much relevant context as possible.\nIf an AI agent repeatedly failed or the user had to take over an Expo task, load the expo-skill-feedback skill and follow its eval-candidate flow instead of reusing the command above.\n\n\n## Examples\n\n```text\nUser: Apply this skill to my current task.\nAssistant: Follow the workflow in this skill, cite limitations, and ask before risky steps.\n```\n\n## Limitations\n\n- Imported upstream skill; verify credentials, permissions, and safety boundaries before execution.\n- Does not replace environment-specific validation, testing, or maintainer review.\n","# EAS Hosting\n\n> **EAS service - costs apply.** EAS Hosting is a paid Expo Application Services product with free-tier limits; production deploys use your plan's request and bandwidth allowance. See https:\u002F\u002Fexpo.dev\u002Fpricing. Authoring API routes and exporting the web bundle are free and open source, and you can self-host the exported server output instead of EAS Hosting.\n\nEAS Hosting deploys your Expo **web app and API routes** to Expo's managed edge (Cloudflare Workers). Export the web bundle with `npx expo export -p web` and ship it with `eas deploy` - the same command deploys any Expo Router API routes bundled alongside it. This skill covers deploying a website, authoring API routes, and the hosting runtime; see the Deployment section below for the deploy workflow.\n\nThese are partial route examples, not production authentication or data-access implementations. Presence of an Authorization header is not verification: validate issuer, audience, signature and expiry with the chosen provider, derive tenant identity server-side and enforce object ownership. Return 401 until a real verifier is implemented. Bound request bodies, upstream timeouts and paid AI calls; rate-limit and redact upstream errors. Public CORS is only for intentionally public endpoints, not access control. Native clients need a configured absolute HTTPS API origin; browser-relative fetch examples are not universally valid in React Native. Verify Expo runtime limits against current EAS docs; Workers capabilities are not all exposed by EAS Hosting.\n\n## When to Use API Routes\n\nUse API routes when you need:\n\n- **Server-side secrets** — API keys, database credentials, or tokens that must never reach the client\n- **Database operations** — Direct database queries that shouldn't be exposed\n- **Third-party API proxies** — Hide API keys when calling external services (OpenAI, Stripe, etc.)\n- **Server-side validation** — Validate data before database writes\n- **Webhook endpoints** — Receive callbacks from services like Stripe or GitHub\n- **Rate limiting** — Control access at the server level\n- **Heavy computation** — Offload processing that would be slow on mobile\n\n## When NOT to Use API Routes\n\nAvoid API routes when:\n\n- **Data is already public** — Use direct fetch to public APIs instead\n- **No secrets required** — Static data or client-safe operations\n- **Real-time updates needed** — Use WebSockets or services like Supabase Realtime\n- **Simple CRUD** — Consider Firebase, Supabase, or Convex for managed backends\n- **File uploads** — Use direct-to-storage uploads (S3 presigned URLs, Cloudflare R2)\n- **Authentication only** — Use Clerk, Auth0, or Firebase Auth instead\n\n## File Structure\n\nAPI routes live in the `app` directory with `+api.ts` suffix:\n\n```\napp\u002F\n  api\u002F\n    hello+api.ts          → GET \u002Fapi\u002Fhello\n    users+api.ts          → \u002Fapi\u002Fusers\n    users\u002F[id]+api.ts     → \u002Fapi\u002Fusers\u002F:id\n  (tabs)\u002F\n    index.tsx\n```\n\n## Basic API Route\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fhello+api.ts\nexport function GET(request: Request) {\n  return Response.json({ message: \"Hello from Expo!\" });\n}\n```\n\n## HTTP Methods\n\nExport named functions for each HTTP method:\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fitems+api.ts\nexport function GET(request: Request) {\n  return Response.json({ items: [] });\n}\n\nexport async function POST(request: Request) {\n  const body = await request.json();\n  return Response.json({ created: body }, { status: 201 });\n}\n\nexport async function PUT(request: Request) {\n  const body = await request.json();\n  return Response.json({ updated: body });\n}\n\nexport async function DELETE(request: Request) {\n  return new Response(null, { status: 204 });\n}\n```\n\n## Dynamic Routes\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fusers\u002F[id]+api.ts\nexport function GET(request: Request, { id }: { id: string }) {\n  return Response.json({ userId: id });\n}\n```\n\n## Request Handling\n\n### Query Parameters\n\n```ts\nexport function GET(request: Request) {\n  const url = new URL(request.url);\n  const page = url.searchParams.get(\"page\") ?? \"1\";\n  const limit = url.searchParams.get(\"limit\") ?? \"10\";\n\n  return Response.json({ page, limit });\n}\n```\n\n### Headers\n\n```ts\nexport function GET(request: Request) {\n  const auth = request.headers.get(\"Authorization\");\n\n  if (!auth) {\n    return Response.json({ error: \"Unauthorized\" }, { status: 401 });\n  }\n\n  return Response.json({ error: \"Token verification not implemented\" }, { status: 401 });\n}\n```\n\n### JSON Body\n\n```ts\nexport async function POST(request: Request) {\n  const { email, password } = await request.json();\n\n  if (!email || !password) {\n    return Response.json({ error: \"Missing fields\" }, { status: 400 });\n  }\n\n  return Response.json({ success: true });\n}\n```\n\n## Environment Variables\n\nUse `process.env` for server-side secrets:\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fai+api.ts\nexport async function POST(request: Request) {\n  const { prompt } = await request.json();\n\n  const response = await fetch(\"https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fchat\u002Fcompletions\", {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application\u002Fjson\",\n      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,\n    },\n    body: JSON.stringify({\n      model: \"gpt-4\",\n      messages: [{ role: \"user\", content: prompt }],\n    }),\n  });\n\n  const data = await response.json();\n  return Response.json(data);\n}\n```\n\nSet environment variables:\n\n- **Local**: Create `.env` file (never commit)\n- **EAS Hosting**: Use `eas env:create` or Expo dashboard\n\n## CORS Headers\n\nAdd CORS for web clients:\n\n```ts\nconst corsHeaders = {\n  \"Access-Control-Allow-Origin\": \"*\",\n  \"Access-Control-Allow-Methods\": \"GET, POST, PUT, DELETE, OPTIONS\",\n  \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\",\n};\n\nexport function OPTIONS() {\n  return new Response(null, { headers: corsHeaders });\n}\n\nexport function GET() {\n  return Response.json({ data: \"value\" }, { headers: corsHeaders });\n}\n```\n\n## Error Handling\n\n```ts\nexport async function POST(request: Request) {\n  try {\n    const body = await request.json();\n    \u002F\u002F Process...\n    return Response.json({ success: true });\n  } catch (error) {\n    console.error(\"API error:\", error);\n    return Response.json({ error: \"Internal server error\" }, { status: 500 });\n  }\n}\n```\n\n## Testing Locally\n\nStart the development server with API routes:\n\n```bash\nnpx expo start\n```\n\nThis starts a local server at `http:\u002F\u002Flocalhost:8081` with full API route support.\n\nTest with curl:\n\n```bash\ncurl http:\u002F\u002Flocalhost:8081\u002Fapi\u002Fhello\ncurl -X POST http:\u002F\u002Flocalhost:8081\u002Fapi\u002Fusers -H \"Content-Type: application\u002Fjson\" -d '{\"name\":\"Test\"}'\n```\n\n## Deployment to EAS Hosting\n\n### Prerequisites\n\n```bash\nnpm install -g eas-cli\neas login\n```\n\n### Deploy\n\nDeploying ships your web bundle and any Expo Router API routes together - `eas deploy` handles both. The export runs whether you have a full website, an API-routes-only backend, or both.\n\n```bash\n# Export the web bundle (includes any API routes)\nnpx expo export -p web\n\n# Deploy a preview (PR-style URL)\nnpx eas-cli@latest deploy\n\n# Deploy to production\nnpx eas-cli@latest deploy --prod\n```\n\nEverything lands on EAS Hosting (Cloudflare Workers).\n\n### Environment Variables for Production\n\n```bash\n# Create a secret\neas env:create --name OPENAI_API_KEY --environment production\n# Enter the value through the CLI secure prompt; do not put it in shell history.\n\n# Or use the Expo dashboard\n```\n\n### Custom Domain\n\nConfigure in `eas.json` or Expo dashboard.\n\n### Automate with EAS Workflows\n\nDeploy the website (and API routes) on every push to main with a `type: deploy` workflow:\n\n`.eas\u002Fworkflows\u002Fdeploy.yml`\n\n```yaml\nname: Deploy\n\non:\n  push:\n    branches:\n      - main\n\n# https:\u002F\u002Fdocs.expo.dev\u002Feas\u002Fworkflows\u002Fsyntax\u002F#deploy\njobs:\n  deploy_web:\n    type: deploy\n    params:\n      prod: true\n```\n\nPreview deploys for pull requests use the same job type with `prod: false`:\n\n```yaml\nname: Web PR Preview\n\non:\n  pull_request:\n    types: [opened, synchronize]\n\njobs:\n  preview:\n    type: deploy\n    params:\n      prod: false\n```\n\nTo author or validate workflow YAML beyond these examples, use the `eas-workflows` skill.\n\n## EAS Hosting Runtime (Cloudflare Workers)\n\nAPI routes run on Cloudflare Workers. Key limitations:\n\n### Missing\u002FLimited APIs\n\n- **No Node.js filesystem** — `fs` module unavailable\n- **No native Node modules** — Use Web APIs or polyfills\n- **Limited execution time** — provider\u002Fplan-specific CPU and wall-clock limits; verify current EAS documentation\n- **No assumed persistent connections** — verify EAS-specific streaming\u002FWebSocket support\n- **fetch is available** — Use standard fetch for HTTP requests\n\n### Use Web APIs Instead\n\n```ts\n\u002F\u002F Use Web Crypto instead of Node crypto\nconst hash = await crypto.subtle.digest(\n  \"SHA-256\",\n  new TextEncoder().encode(\"data\")\n);\n\n\u002F\u002F Use fetch instead of node-fetch\nconst response = await fetch(\"https:\u002F\u002Fapi.example.com\");\n\n\u002F\u002F Use Response\u002FRequest (already available)\nreturn new Response(JSON.stringify(data), {\n  headers: { \"Content-Type\": \"application\u002Fjson\" },\n});\n```\n\n### Database Options\n\nSince filesystem is unavailable, use cloud databases:\n\n- **Cloudflare D1** — SQLite at the edge\n- **Turso** — Distributed SQLite\n- **PlanetScale** — Serverless MySQL\n- **Supabase** — Postgres with REST API\n- **Neon** — Serverless Postgres\n\nExample with Turso:\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fusers+api.ts\nimport { createClient } from \"@libsql\u002Fclient\u002Fweb\";\n\nconst db = createClient({\n  url: process.env.TURSO_URL!,\n  authToken: process.env.TURSO_AUTH_TOKEN!,\n});\n\nexport async function GET() {\n  const result = await db.execute(\"SELECT * FROM users\");\n  return Response.json(result.rows);\n}\n```\n\n## Calling API Routes from Client\n\n```ts\n\u002F\u002F From React Native components\nconst response = await fetch(\"\u002Fapi\u002Fhello\");\nconst data = await response.json();\n\n\u002F\u002F With body\nconst response = await fetch(\"\u002Fapi\u002Fusers\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application\u002Fjson\" },\n  body: JSON.stringify({ name: \"John\" }),\n});\n```\n\n## Common Patterns\n\n### Authentication Middleware\n\n```ts\n\u002F\u002F utils\u002Fauth.ts\nexport async function requireAuth(request: Request) {\n  const token = request.headers.get(\"Authorization\")?.replace(\"Bearer \", \"\");\n\n  if (!token) {\n    throw new Response(JSON.stringify({ error: \"Unauthorized\" }), {\n      status: 401,\n      headers: { \"Content-Type\": \"application\u002Fjson\" },\n    });\n  }\n\n  \u002F\u002F Configure a real verifier and return its verified subject; fail closed meanwhile.\n  throw new Response(\"Unauthorized\", { status: 401 });\n}\n\n\u002F\u002F app\u002Fapi\u002Fprotected+api.ts\nimport { requireAuth } from \"..\u002F..\u002Futils\u002Fauth\";\n\nexport async function GET(request: Request) {\n  const { userId } = await requireAuth(request);\n  return Response.json({ userId });\n}\n```\n\n### Proxy External API\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fweather+api.ts\nexport async function GET(request: Request) {\n  const url = new URL(request.url);\n  const city = url.searchParams.get(\"city\");\n\n  const response = await fetch(\n    `https:\u002F\u002Fapi.weather.com\u002Fv1\u002Fcurrent?city=${city}&key=${process.env.WEATHER_API_KEY}`\n  );\n\n  return Response.json(await response.json());\n}\n```\n\n## Rules\n\n- NEVER expose API keys or secrets in client code\n- ALWAYS validate and sanitize user input\n- Use proper HTTP status codes (200, 201, 400, 401, 404, 500)\n- Handle errors gracefully with try\u002Fcatch\n- Keep API routes focused — one responsibility per endpoint\n- Use TypeScript for type safety\n- Log errors server-side for debugging\n\n## Submitting Feedback\nIf you encounter errors, misleading or outdated information in this skill, report it so Expo can improve:\n```bash\nnpx --yes submit-expo-feedback@latest --category skills --subject \"eas-hosting\" \"\u003Cactionable feedback>\"\n```\nOnly submit when you have something specific and actionable to report. Include as much relevant context as possible.\nIf an AI agent repeatedly failed or the user had to take over an Expo task, load the expo-skill-feedback skill and follow its eval-candidate flow instead of reusing the command above.\n\n\n## Examples\n\n```text\nUser: Apply this skill to my current task.\nAssistant: Follow the workflow in this skill, cite limitations, and ask before risky steps.\n```\n\n## Limitations\n\n- Imported upstream skill; verify credentials, permissions, and safety boundaries before execution.\n- Does not replace environment-specific validation, testing, or maintainer review.\n",{"name":5,"risk":28,"source":25,"license":14,"version":37,"date_added":38,"description":7,"source_repo":39,"source_type":21},"1.0.0","2026-09-21","expo\u002Fskills","main","7bf2279c8a65758319526a721d5c4b75683aec9c","skills\u002Fantigravity-eas-hosting",3,"https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections","https:\u002F\u002Fgithub.com\u002Fboisenoise","2026-11-12 14:21:24",{"source":8,"skillName":5,"commands":48,"prompt":58,"zipAvailable":59},[49,52,55],{"tool":50,"command":51},"npx","npx skills add boisenoise\u002Fskills-collections --skill eas-hosting",{"tool":53,"command":54},"bunx","bunx skills add boisenoise\u002Fskills-collections --skill eas-hosting",{"tool":56,"command":57},"pnpm","pnpm dlx skills add boisenoise\u002Fskills-collections --skill eas-hosting","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-eas-hosting\u002FSKILL.md",true,[61],{"path":62,"resourceType":63,"resourceTypeDesc":64,"sizeBytes":65,"sourceHtmlUrl":66},"agents\u002Fopenai.yaml","REFERENCE","参考文档",462,"https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-eas-hosting\u002Fagents\u002Fopenai.yaml",[68,94,121,146,165,188],{"skillNo":69,"name":70,"nameZh":6,"description":71,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":72,"licenseFileUrl":73,"sourceHtmlUrl":74,"categories":75,"keywords":76,"publishedAt":32},"01M6WNH92BHVFXHGPDZCPJ0AG0","web-access","Use Treg for public web or platform data only when existing search, browser and connected tools cannot cover the task, with explicit cost and data boundaries.","Apache-2.0","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-web-access\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-web-access\u002FSKILL.md",[],[77,78,79,21,80,81,82,83,84,85,86,87,88,89,90,28,91,92,93,30,31],"web","access","web access","development","browser","search","data","Tools","read","fetch","only","youtube","free tools","platform","free","cover","normal",{"skillNo":95,"name":96,"nameZh":6,"description":97,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":72,"licenseFileUrl":98,"sourceHtmlUrl":99,"categories":100,"keywords":106,"publishedAt":32},"01M6WNF9XAYAB2CZDDJBAKNM93","treg","Use the Treg CLI or MCP catalog to discover, price and call authorized external tools with team-scoped credentials and a user-approved budget.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-treg\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-treg\u002FSKILL.md",[101],{"categoryNo":102,"categoryName":103,"taxonomyType":104,"taxonomyTypeDesc":105},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[96,107,108,109,21,80,110,84,111,112,113,114,23,115,116,28,117,118,119,120,31],"MCP","cli","github","license","Budget","credentials","team","catalog","call","discover","external tools","with","scoped","blob",{"skillNo":122,"name":123,"nameZh":6,"description":124,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":72,"licenseFileUrl":125,"sourceHtmlUrl":126,"categories":127,"keywords":133,"publishedAt":32},"01M6WN93MEAZ4E41DYAHJRPHQT","recruit","Turn a job description into an evidence-linked candidate shortlist through Treg, with job-related ranking and optional authorized business contact enrichment.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-recruit\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-recruit\u002FSKILL.md",[128],{"categoryNo":129,"categoryName":130,"taxonomyType":131,"taxonomyTypeDesc":132},"01JBSEEDCAT000000000000038","人力资源","PROFESSION","适用职业",[123,109,21,110,134,135,136,137,138,139,28,140,141,142,118,29,143,144,145,120,96,31],"https","email","enrichment","business","evidence","phone","ranking","post","job description","person","candidate","reason",{"skillNo":147,"name":148,"nameZh":6,"description":149,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":72,"licenseFileUrl":150,"sourceHtmlUrl":151,"categories":152,"keywords":153,"publishedAt":32},"01M6WN8R5RXSMHM9B4D9FAFX42","rea-growth","Analyze a company’s public growth and content through Treg, distinguish observations from hypotheses, and produce an evidence-linked playbook within an approved budget.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-rea-growth\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-rea-growth\u002FSKILL.md",[],[154,155,156,109,21,110,157,158,134,159,111,160,161,162,138,28,163,164,120,96,31],"growth","rea","rea growth","produce","content","playbook","marketing","analyze","public","observations","company",{"skillNo":166,"name":167,"nameZh":6,"description":168,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":72,"licenseFileUrl":169,"sourceHtmlUrl":170,"categories":171,"keywords":172,"publishedAt":32},"01M6WN2EVE3RQRGWAKBT4S7YT2","make-ugc","Create original AI UGC-style product videos through Treg: research hooks, approve a licensed presenter and voice, generate clips, add captions and report costs.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-make-ugc\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-make-ugc\u002FSKILL.md",[],[173,174,109,175,21,176,110,177,178,179,180,181,28,182,183,184,185,186,187,120],"make","make ugc","research","create","Video","Captions","ads","short","hooks","head","style","niche","trending","talking-head","approve",{"skillNo":189,"name":190,"nameZh":6,"description":191,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":72,"licenseFileUrl":192,"sourceHtmlUrl":193,"categories":194,"keywords":195,"publishedAt":32},"01M6WN10ARAA63Y8K1YNGXJ2RM","lead-signals","Find and qualify public buying signals through Treg, rank fitting business accounts and optionally enrich authorized business contact information.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-lead-signals\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-lead-signals\u002FSKILL.md",[],[196,197,198,109,21,110,199,200,137,201,202,28,203,204,205,206,207,208,209,120],"signals","lead","lead signals","how","find","ready","as","how to","qualify","people","information","enrich","changes","funding"]