@jini-ai/agent-plugins 0.6.17 → 0.6.19
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/CHANGELOG.md +29 -1
- package/NOTICE +14 -0
- package/README.md +35 -14
- package/charts-echarts/plugin.json +39 -0
- package/charts-echarts/skills/charts-echarts/SKILL.md +110 -0
- package/charts-recharts/plugin.json +32 -0
- package/charts-recharts/skills/charts-recharts/SKILL.md +108 -0
- package/cloudflare/NOTES.md +16 -0
- package/cloudflare/form-destinations/cloudflare-d1.mjs +9 -0
- package/cloudflare/lib/api.mjs +96 -0
- package/cloudflare/lib/assets.mjs +65 -0
- package/cloudflare/lib/dns.mjs +32 -0
- package/cloudflare/lib/domain-inventory.mjs +61 -0
- package/cloudflare/lib/domain-status.mjs +48 -0
- package/cloudflare/lib/domains.mjs +95 -0
- package/cloudflare/lib/forms.mjs +254 -0
- package/cloudflare/lib/pages-upload.mjs +51 -0
- package/cloudflare/lib/target.mjs +165 -0
- package/cloudflare/lib/workers-upload.mjs +48 -0
- package/cloudflare/lib/zones.mjs +25 -0
- package/cloudflare/mcp.json +4 -0
- package/cloudflare/plugin.json +96 -0
- package/cloudflare/skills/cloudflare/SKILL.md +57 -0
- package/cloudflare/skills/cloudflare/references/domains.md +31 -0
- package/cloudflare/skills/cloudflare/references/pages.md +18 -0
- package/cloudflare/skills/cloudflare/references/permissions.md +29 -0
- package/cloudflare/skills/cloudflare/references/troubleshooting.md +20 -0
- package/cloudflare/skills/cloudflare/references/workers.md +42 -0
- package/cloudflare/targets/cloudflare-pages.mjs +12 -0
- package/cloudflare/targets/cloudflare-workers.mjs +17 -0
- package/cloudflare/tovu-consent.json +6 -0
- package/cloudflare/tovu-deploy-targets.json +630 -0
- package/cloudflare/tovu-form-destinations.json +12 -0
- package/cloudflare/worker/forms-receiver.mjs +249 -0
- package/composio/mcp.json +10 -0
- package/composio/plugin.json +68 -0
- package/composio/skills/composio/SKILL.md +85 -0
- package/dist/core/identity-namespace.d.ts +12 -0
- package/dist/core/identity-namespace.js +20 -0
- package/dist/folder-zip-node.js +29 -7
- package/dist/lifecycle/bundled-digests.js +4 -3
- package/dist/lifecycle/install.d.ts +7 -0
- package/dist/lifecycle/install.js +45 -3
- package/dist/lifecycle/manifest.d.ts +2 -2
- package/dist/lifecycle/manifest.js +5 -3
- package/dist/lifecycle/node.js +2 -0
- package/dist/lifecycle/persistent-state.js +1 -1
- package/dist/lifecycle/ports.d.ts +8 -0
- package/dist/lifecycle/seed-bundled.js +4 -1
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.js +3 -2
- package/fly/deploy-configs/fly.mjs +115 -0
- package/fly/deploy-ops/fly.mjs +102 -0
- package/fly/mcp.json +4 -0
- package/fly/plugin.json +81 -0
- package/fly/scripts/render-deploy-workflow.mjs +150 -0
- package/fly/skills/fly/SKILL.md +43 -0
- package/fly/skills/fly/references/fly-deploy.template.yml +117 -0
- package/fly/skills/fly/references/fly.template.toml +81 -0
- package/fly/tovu-credential-schemes.json +11 -0
- package/fly/tovu-deploy-configs.json +21 -0
- package/fly/tovu-deploy-ops.json +14 -0
- package/github/deploy-ops/github-actions.mjs +164 -0
- package/github/deployments/github.mjs +415 -0
- package/github/mcp.json +4 -0
- package/github/plugin.json +105 -0
- package/github/skills/github/SKILL.md +249 -0
- package/github/skills/github/references/actions.md +224 -0
- package/github/skills/github/references/auth-and-tokens.md +140 -0
- package/github/skills/github/references/repo-files.md +135 -0
- package/github/skills/github/references/site-backup.md +159 -0
- package/github/skills/github/references/source-control.md +123 -0
- package/github/source-control/backup-push.mjs +242 -0
- package/github/source-control/commit-site.mjs +459 -0
- package/github/source-control/github.mjs +93 -0
- package/github/source-control/write-files.mjs +468 -0
- package/github/tovu-deploy-ops.json +13 -0
- package/github/tovu-source-control.json +117 -0
- package/github-pages/NOTES.md +19 -0
- package/github-pages/mcp.json +4 -0
- package/github-pages/plugin.json +48 -0
- package/github-pages/skills/github-pages/SKILL.md +44 -0
- package/github-pages/targets/github-pages.mjs +543 -0
- package/github-pages/tovu-deploy-targets.json +327 -0
- package/google-login/exports/google-g.svg +1 -0
- package/google-login/exports/sign-in-button.css +68 -0
- package/google-login/exports/sign-in-button.html +4 -0
- package/google-login/mcp.json +4 -0
- package/google-login/plugin.json +59 -0
- package/google-login/skills/google-login/SKILL.md +96 -0
- package/google-login/tovu-login-providers.json +21 -0
- package/google-workspace/README.md +132 -0
- package/google-workspace/form-destinations/README.md +87 -0
- package/google-workspace/form-destinations/dedupe.mjs +43 -0
- package/google-workspace/form-destinations/delivery-result.mjs +69 -0
- package/google-workspace/form-destinations/mapping.mjs +152 -0
- package/google-workspace/form-destinations/sheets.mjs +202 -0
- package/google-workspace/lib/auth.mjs +81 -0
- package/google-workspace/lib/drive-files.mjs +54 -0
- package/google-workspace/lib/form-destination-settings.mjs +72 -0
- package/google-workspace/lib/http.mjs +168 -0
- package/google-workspace/lib/picker-flow.mjs +171 -0
- package/google-workspace/lib/picker-selection.mjs +107 -0
- package/google-workspace/lib/product-state.mjs +93 -0
- package/google-workspace/lib/request-policy.mjs +163 -0
- package/google-workspace/lib/sheets-a1.mjs +79 -0
- package/google-workspace/lib/sheets-admin-csv.mjs +82 -0
- package/google-workspace/lib/sheets-admin-request-kinds.mjs +183 -0
- package/google-workspace/lib/sheets-client.mjs +286 -0
- package/google-workspace/lib/surface-actions.mjs +107 -0
- package/google-workspace/mcp.json +4 -0
- package/google-workspace/plugin.json +77 -0
- package/google-workspace/products.json +12 -0
- package/google-workspace/skills/google-workspace/SKILL.md +82 -0
- package/google-workspace/skills/google-workspace/references/connection.md +102 -0
- package/google-workspace/skills/google-workspace/references/form-delivery.md +50 -0
- package/google-workspace/skills/google-workspace/references/sheets.md +149 -0
- package/google-workspace/skills/google-workspace/references/troubleshooting.md +31 -0
- package/google-workspace/tools/account-info.mjs +18 -0
- package/google-workspace/tools/sheets/README.md +54 -0
- package/google-workspace/tools/sheets/copy-tab.mjs +23 -0
- package/google-workspace/tools/sheets/copy.mjs +23 -0
- package/google-workspace/tools/sheets/create.mjs +35 -0
- package/google-workspace/tools/sheets/find-replace.mjs +38 -0
- package/google-workspace/tools/sheets/find.mjs +52 -0
- package/google-workspace/tools/sheets/get-by-data-filter.mjs +23 -0
- package/google-workspace/tools/sheets/get.mjs +22 -0
- package/google-workspace/tools/sheets/values-append.mjs +27 -0
- package/google-workspace/tools/sheets/values-batch-clear.mjs +20 -0
- package/google-workspace/tools/sheets/values-batch-get.mjs +20 -0
- package/google-workspace/tools/sheets/values-batch-update.mjs +29 -0
- package/google-workspace/tools/sheets/values-by-data-filter.mjs +62 -0
- package/google-workspace/tools/sheets/values-clear.mjs +20 -0
- package/google-workspace/tools/sheets/values-get.mjs +21 -0
- package/google-workspace/tools/sheets/values-update.mjs +26 -0
- package/google-workspace/tools/sheets-admin/README.md +45 -0
- package/google-workspace/tools/sheets-admin/basic-filter.mjs +32 -0
- package/google-workspace/tools/sheets-admin/batch-update.mjs +25 -0
- package/google-workspace/tools/sheets-admin/developer-metadata.mjs +28 -0
- package/google-workspace/tools/sheets-admin/export-csv.mjs +67 -0
- package/google-workspace/tools/sheets-admin/filter-views.mjs +38 -0
- package/google-workspace/tools/sheets-admin/format.mjs +70 -0
- package/google-workspace/tools/sheets-admin/named-ranges.mjs +42 -0
- package/google-workspace/tools/sheets-admin/open-existing.mjs +34 -0
- package/google-workspace/tools/sheets-admin/protected-ranges-get.mjs +18 -0
- package/google-workspace/tools/sheets-admin/sort.mjs +18 -0
- package/google-workspace/tools/sheets-admin/tab-modify.mjs +36 -0
- package/google-workspace/tovu-admin-surfaces.json +63 -0
- package/google-workspace/tovu-agent-tools.json +1178 -0
- package/google-workspace/tovu-connections.json +35 -0
- package/google-workspace/tovu-form-destinations.json +10 -0
- package/google-workspace/ui/README.md +73 -0
- package/google-workspace/ui/connection-panel.handler.mjs +167 -0
- package/google-workspace/ui/connection-panel.html +17 -0
- package/google-workspace/ui/connection-panel.mjs +189 -0
- package/google-workspace/ui/connection-view.mjs +120 -0
- package/google-workspace/ui/destination-panel.handler.mjs +106 -0
- package/google-workspace/ui/destination-panel.html +17 -0
- package/google-workspace/ui/destination-panel.mjs +150 -0
- package/google-workspace/ui/dom.mjs +140 -0
- package/google-workspace/ui/panel.css +170 -0
- package/google-workspace/ui/picker-flow.mjs +87 -0
- package/google-workspace/ui/picker-view.mjs +44 -0
- package/google-workspace/ui/picker.handler.mjs +56 -0
- package/google-workspace/ui/picker.html +17 -0
- package/google-workspace/ui/picker.mjs +88 -0
- package/google-workspace/ui/surface-client.mjs +227 -0
- package/higgsfield-media/mcp.json +9 -0
- package/higgsfield-media/plugin.json +109 -0
- package/higgsfield-media/skills/higgsfield-media/SKILL.md +325 -0
- package/higgsfield-media/skills/higgsfield-media/references/failure-modes.md +116 -0
- package/higgsfield-media/skills/higgsfield-media/references/models-and-plan-gates.md +142 -0
- package/jev/mcp.json +4 -0
- package/jev/plugin.json +44 -0
- package/jev/skills/jev/SKILL.md +308 -0
- package/jev/skills/jev/references/cookbooks.md +197 -0
- package/jev/skills/jev/references/jaggedness.md +135 -0
- package/jev/skills/jev/references/javascript-sdk.md +146 -0
- package/jev/skills/jev/references/patterns.md +123 -0
- package/jev/skills/jev/references/primitives-reference.md +187 -0
- package/langfuse/plugin.json +22 -0
- package/meilisearch/mcp.json +4 -0
- package/meilisearch/plugin.json +54 -0
- package/meilisearch/providers/meilisearch.mjs +651 -0
- package/meilisearch/skills/meilisearch/SKILL.md +43 -0
- package/meilisearch/tovu-search-providers.json +10 -0
- package/netlify/NOTES.md +13 -0
- package/netlify/mcp.json +4 -0
- package/netlify/plugin.json +47 -0
- package/netlify/skills/netlify/SKILL.md +39 -0
- package/netlify/targets/netlify.mjs +384 -0
- package/netlify/tovu-consent.json +6 -0
- package/netlify/tovu-deploy-targets.json +163 -0
- package/opentelemetry/mcp.json +4 -0
- package/opentelemetry/plugin.json +52 -0
- package/opentelemetry/skills/opentelemetry/SKILL.md +39 -0
- package/package.json +161 -6
- package/posthog/mcp.json +4 -0
- package/posthog/plugin.json +46 -0
- package/posthog/skills/posthog/SKILL.md +36 -0
- package/railway/deploy-configs/railway.mjs +135 -0
- package/railway/deploy-ops/railway.mjs +167 -0
- package/railway/mcp.json +4 -0
- package/railway/plugin.json +45 -0
- package/railway/skills/railway/SKILL.md +66 -0
- package/railway/tovu-deploy-configs.json +16 -0
- package/railway/tovu-deploy-ops.json +11 -0
- package/render/deploy-configs/render.mjs +141 -0
- package/render/deploy-ops/render.mjs +152 -0
- package/render/mcp.json +4 -0
- package/render/plugin.json +47 -0
- package/render/skills/render/SKILL.md +63 -0
- package/render/tovu-deploy-configs.json +16 -0
- package/render/tovu-deploy-ops.json +11 -0
- package/resend/NOTES.md +52 -0
- package/resend/adapters/resend.mjs +161 -0
- package/resend/lib/domains.mjs +137 -0
- package/resend/lib/errors.mjs +25 -0
- package/resend/lib/feedback.mjs +118 -0
- package/resend/mcp.json +13 -0
- package/resend/plugin.json +192 -0
- package/resend/skills/resend/SKILL.md +107 -0
- package/resend/tovu-mail-adapters.json +22 -0
- package/s3-compatible/NOTES.md +11 -0
- package/s3-compatible/mcp.json +4 -0
- package/s3-compatible/object-stores/s3-compatible.mjs +287 -0
- package/s3-compatible/plugin.json +56 -0
- package/s3-compatible/skills/s3-compatible/SKILL.md +39 -0
- package/s3-compatible/targets/s3-compatible.mjs +1054 -0
- package/s3-compatible/tovu-deploy-targets.json +432 -0
- package/s3-compatible/tovu-object-stores.json +29 -0
- package/sentry/mcp.json +4 -0
- package/sentry/plugin.json +49 -0
- package/sentry/skills/sentry/SKILL.md +34 -0
- package/shopify/mcp.json +4 -0
- package/shopify/plugin.json +22 -0
- package/shopify/skills/shopify/SKILL.md +103 -0
- package/shopify/skills/shopify/references/catalog-sync.md +74 -0
- package/shopify/skills/shopify/references/domains.md +26 -0
- package/shopify/skills/shopify/references/live-storefront.md +34 -0
- package/shopify/skills/shopify/references/storefront-snippets.md +21 -0
- package/site-compliance/mcp.json +4 -0
- package/site-compliance/plugin.json +34 -0
- package/site-compliance/skills/site-compliance/SKILL.md +220 -0
- package/site-compliance/skills/site-compliance/references/ccpa-cpra.md +115 -0
- package/site-compliance/skills/site-compliance/references/gdpr-eu-uk.md +143 -0
- package/site-compliance/skills/site-compliance/references/wcag-2-2.md +129 -0
- package/site-import/mcp.json +4 -0
- package/site-import/plugin.json +41 -0
- package/site-import/skills/site-import/SKILL.md +232 -0
- package/site-import/skills/site-import/references/content-mapping.md +166 -0
- package/site-import/skills/site-import/references/discovery-and-classification.md +117 -0
- package/site-import/skills/site-import/references/theme-extraction.md +34 -0
- package/slide-design/com.anthropic.claude-code/hooks/hooks.json +4 -0
- package/slide-design/mcp.json +4 -0
- package/slide-design/plugin.json +26 -0
- package/slide-design/skills/slide-design/SKILL.md +103 -0
- package/slide-design/skills/slide-design/references/charts-and-exhibits.md +100 -0
- package/slide-design/skills/slide-design/references/html-deck.md +175 -0
- package/slide-design/skills/slide-design/references/layout-and-style.md +131 -0
- package/slide-design/skills/slide-design/references/review.md +93 -0
- package/slide-design/skills/slide-design/references/storyline.md +118 -0
- package/stripe/plugin.json +22 -0
- package/supabase/mcp.json +26 -0
- package/supabase/plugin.json +78 -0
- package/supabase/skills/supabase/SKILL.md +169 -0
- package/supabase/skills/supabase/references/failure-modes.md +101 -0
- package/ui-ux-design/CHANGELOG.md +28 -0
- package/ui-ux-design/LICENSE +201 -0
- package/ui-ux-design/NOTICE +14 -0
- package/ui-ux-design/README.md +62 -0
- package/ui-ux-design/com.anthropic.claude-code/hooks/hooks.json +4 -0
- package/ui-ux-design/mcp.json +4 -0
- package/ui-ux-design/plugin.json +26 -0
- package/ui-ux-design/skills/frontend-accessibility/SKILL.md +60 -0
- package/ui-ux-design/skills/gstack-design/SKILL.md +53 -0
- package/ui-ux-design/skills/gstack-design/references/consultation.md +30 -0
- package/ui-ux-design/skills/gstack-design/references/html.md +29 -0
- package/ui-ux-design/skills/gstack-design/references/review.md +28 -0
- package/ui-ux-design/skills/gstack-design/references/shotgun.md +33 -0
- package/ui-ux-design/skills/gstack-design/references/upstream-notes.md +23 -0
- package/ui-ux-design/skills/interface-design/ORIGINAL.md +16 -0
- package/ui-ux-design/skills/interface-design/SKILL.md +41 -0
- package/ui-ux-design/skills/interface-design/references/commands/audit.md +40 -0
- package/ui-ux-design/skills/interface-design/references/commands/critique.md +21 -0
- package/ui-ux-design/skills/interface-design/references/commands/extract.md +34 -0
- package/ui-ux-design/skills/interface-design/references/commands/init.md +31 -0
- package/ui-ux-design/skills/interface-design/references/commands/status.md +22 -0
- package/ui-ux-design/skills/interface-design/references/examples/system-precision.md +29 -0
- package/ui-ux-design/skills/interface-design/references/examples/system-warmth.md +29 -0
- package/ui-ux-design/skills/interface-design/references/system-template.md +57 -0
- package/ui-ux-design/skills/shadcn-ui/README.md +244 -0
- package/ui-ux-design/skills/shadcn-ui/SKILL.md +333 -0
- package/ui-ux-design/skills/shadcn-ui/examples/auth-layout.tsx +177 -0
- package/ui-ux-design/skills/shadcn-ui/examples/data-table.tsx +313 -0
- package/ui-ux-design/skills/shadcn-ui/examples/form-pattern.tsx +177 -0
- package/ui-ux-design/skills/shadcn-ui/resources/component-catalog.md +481 -0
- package/ui-ux-design/skills/shadcn-ui/resources/customization-guide.md +516 -0
- package/ui-ux-design/skills/shadcn-ui/resources/migration-guide.md +463 -0
- package/ui-ux-design/skills/shadcn-ui/resources/setup-guide.md +412 -0
- package/ui-ux-design/skills/shadcn-ui/scripts/verify-setup.sh +134 -0
- package/ui-ux-design/skills/ui-ux-design/SKILL.md +171 -0
- package/ui-ux-design/skills/ui-ux-design/references/brand-and-voice.md +78 -0
- package/ui-ux-design/skills/ui-ux-design/references/chrome-mobile-test-matrix.md +137 -0
- package/ui-ux-design/skills/ui-ux-design/references/components-and-states.md +112 -0
- package/ui-ux-design/skills/ui-ux-design/references/delight-and-motion.md +54 -0
- package/ui-ux-design/skills/ui-ux-design/references/foundations.md +145 -0
- package/ui-ux-design/skills/ui-ux-design/references/inclusive-ai-imagery.md +57 -0
- package/ui-ux-design/skills/ui-ux-design/references/kole-jain-uiux-concepts.md +104 -0
- package/ui-ux-design/skills/ui-ux-design/references/kuinetic-reference.md +184 -0
- package/ui-ux-design/skills/ui-ux-design/references/openai-frontend-skill.md +139 -0
- package/ui-ux-design/skills/ui-ux-design/references/premium-ui.md +55 -0
- package/ui-ux-design/skills/ui-ux-design/references/research-and-validation.md +88 -0
- package/ui-ux-design/skills/ui-ux-design/references/sam-crawford-premium-websites.md +103 -0
- package/ui-ux-design/skills/ui-ux-design/references/self-made-web-designer-core-skills.md +119 -0
- package/ui-ux-design/skills/ui-ux-design/references/ui-animation.md +173 -0
- package/ui-ux-design/skills/ui-ux-design/references/ui-loop.md +234 -0
- package/ui-ux-design/skills/ui-ux-design/references/visual-storytelling.md +54 -0
- package/ui-ux-design/skills/vercel-web-design-guidelines/SKILL.md +39 -0
- package/ui-ux-design/skills/web-compliance/SKILL.md +134 -0
- package/ui-ux-design/skills/web-compliance/references/audit-evidence.md +59 -0
- package/vercel/NOTES.md +23 -0
- package/vercel/mcp.json +4 -0
- package/vercel/object-stores/vercel-blob.mjs +147 -0
- package/vercel/plugin.json +47 -0
- package/vercel/skills/vercel/SKILL.md +48 -0
- package/vercel/targets/vercel.mjs +586 -0
- package/vercel/tovu-consent.json +6 -0
- package/vercel/tovu-deploy-targets.json +219 -0
- package/vercel/tovu-object-stores.json +10 -0
- package/wordpress/NOTES.md +33 -0
- package/wordpress/plugin.json +30 -0
- package/wordpress/skills/wordpress/SKILL.md +43 -0
- package/wordpress/skills/wordpress/references/free-site-redirect.md +46 -0
- package/wordpress/skills/wordpress/references/subdomain.md +66 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { API_ROOT, createApi, refusal, segment, PAGES_PROJECTS_PAGING } from './api.mjs';
|
|
3
|
+
import { prepareAssets } from './assets.mjs';
|
|
4
|
+
import { previewDomains, assertDomainApproval, applyDomains } from './domains.mjs';
|
|
5
|
+
import { normalizeDomain } from './zones.mjs';
|
|
6
|
+
import { pagesAddress } from './pages-upload.mjs';
|
|
7
|
+
import { formPageRoutes, formsWorkerParts, normalizeForms, previewForms, provisionForms } from './forms.mjs';
|
|
8
|
+
|
|
9
|
+
const WORKERS_TARGET_ID = 'cloudflare-workers';
|
|
10
|
+
|
|
11
|
+
/** Stable jini-<project> identity, retained for Pages; an explicitly recorded remote name wins. */
|
|
12
|
+
/**
|
|
13
|
+
* Derives a stable, Cloudflare-Pages-safe project name from the caller's
|
|
14
|
+
* `projectName` label. Deterministic (no random suffix) so repeated
|
|
15
|
+
* publishes of the same logical site land on the same Cloudflare Pages
|
|
16
|
+
* project — replaces the OD origin's `od-${projectId}-${hash}` convention,
|
|
17
|
+
* which relied on an OD project id this package has no concept of.
|
|
18
|
+
*/
|
|
19
|
+
export function resourceName({ projectName, config, kit }, _options = {}) {
|
|
20
|
+
if (config.resourceName) {
|
|
21
|
+
if (!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(config.resourceName)) throw refusal({ kit, code: 'CLOUDFLARE_RESOURCE_NAME', message: 'The remote resource name is invalid. Use the exact existing Cloudflare project/script name.' });
|
|
22
|
+
return config.resourceName;
|
|
23
|
+
}
|
|
24
|
+
const label = kit.safeDnsLabel(projectName) || 'site';
|
|
25
|
+
return kit.safeDnsLabel(`jini-${label}`).slice(0, 63);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Read-only resource and domain inspection through the plugin-owned API. */
|
|
29
|
+
export async function previewTarget({ credential, config, kit, projectName, targetId, formDestination }, _options = {}) {
|
|
30
|
+
const api = createApi({ credential, kit });
|
|
31
|
+
const name = resourceName({ projectName, config, kit });
|
|
32
|
+
const domain = config.customDomain ? normalizeDomain({ domain: config.customDomain, kit }) : undefined;
|
|
33
|
+
try { return await inspectTarget({ api, config, kit, targetId, name, domain, formDestination }); }
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (!domain) throw error;
|
|
36
|
+
return { resourceName: name, targetId, hostingChanges: [], domains: { inventoryStatus: 'unknown', changes: [],
|
|
37
|
+
errorCode: 'CLOUDFLARE_INVENTORY_UNKNOWN', hostnames: [domain, `www.${domain}`].map(hostname => ({ hostname, status: 'unknown' })),
|
|
38
|
+
message: `Cannot safely inspect ${domain}. ${error.message} Complete the named read permission or setup step, then preview again.` } };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function inspectTarget({ api, config, kit, targetId, name, domain, formDestination }, _options = {}) {
|
|
43
|
+
const path = targetId === WORKERS_TARGET_ID ? `${api.accountPath}/workers/scripts` : `${api.accountPath}/pages/projects`;
|
|
44
|
+
const resources = await api.list({ path }, targetId === WORKERS_TARGET_ID ? {} : PAGES_PROJECTS_PAGING);
|
|
45
|
+
// Workers scripts are identified by `id`, Pages projects by `name`.
|
|
46
|
+
const existing = resources.find(resource => resource.id === name || resource.name === name);
|
|
47
|
+
const hostingChanges = [{ action: existing ? 'update' : 'create', targetId, resourceName: name }];
|
|
48
|
+
if (targetId === WORKERS_TARGET_ID) {
|
|
49
|
+
await workersAddress({ api, kit, resourceName: name });
|
|
50
|
+
hostingChanges.push({ action: 'enable-workers-dev', targetId, resourceName: name });
|
|
51
|
+
}
|
|
52
|
+
const domains = domain ? await previewDomains({ api, kit, targetId, resourceName: name, domain, zoneId: config.zoneId }) : undefined;
|
|
53
|
+
const formSubmissions = await previewForms({ api, kit, resourceName: name, targetId, formDestination });
|
|
54
|
+
return { resourceName: name, targetId, hostingChanges, ...(domains ? { domains } : {}), ...(formSubmissions ? { formSubmissions } : {}) };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Read workers.dev from Cloudflare. Never manufacture an account subdomain or register one. */
|
|
58
|
+
export async function workersAddress({ api, kit, resourceName }, _options = {}) {
|
|
59
|
+
const result = await api.request({ path: `${api.accountPath}/workers/subdomain` }, { allowMissing: true });
|
|
60
|
+
if (typeof result?.subdomain !== 'string' || !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(result.subdomain)) throw refusal({ kit, code: 'CLOUDFLARE_SUBDOMAIN_REQUIRED', message: `Worker ${resourceName} needs this account's workers.dev subdomain. Open Workers & Pages in the Cloudflare dashboard, set it up, then preview again.` });
|
|
61
|
+
return `https://${resourceName}.${result.subdomain}.workers.dev`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** One lifecycle shared by both hosting variants. The transport-specific uploader is injected.
|
|
65
|
+
* Consent/stale checks precede ALL remote writes, including provisioning and upload sessions. */
|
|
66
|
+
export function createTarget({ credential, config, kit, targetId, upload }, _options = {}) {
|
|
67
|
+
const api = createApi({ credential, kit });
|
|
68
|
+
return {
|
|
69
|
+
id: targetId,
|
|
70
|
+
/** Read-only address resolution, sharing each variant's existing address owner. */
|
|
71
|
+
resolvePublicUrl: ({ projectName }, _options = {}) => {
|
|
72
|
+
const name = resourceName({ projectName, config, kit });
|
|
73
|
+
return targetId === WORKERS_TARGET_ID ? workersAddress({ api, kit, resourceName: name }) : Promise.resolve(pagesAddress({ resourceName: name }));
|
|
74
|
+
},
|
|
75
|
+
async publish(input, options = {}) {
|
|
76
|
+
const merged = { ...input, ...options };
|
|
77
|
+
const name = resourceName({ projectName: input.projectName, config, kit });
|
|
78
|
+
const requestedDomain = config.customDomain ?? merged.metadata?.customDomain;
|
|
79
|
+
const domainInput = { api, kit, targetId, resourceName: name, domain: requestedDomain, zoneId: config.zoneId, approval: merged.metadata?.domainApproval };
|
|
80
|
+
const plan = requestedDomain ? await assertDomainApproval(domainInput) : undefined;
|
|
81
|
+
const prepared = preparePublish({ input: merged, kit, config, plan, targetId });
|
|
82
|
+
const forms = await prepareForms({ api, kit, targetId, resourceName: name, formDestination: merged.metadata?.formDestination, files: merged.files });
|
|
83
|
+
const hosting = await upload({ api, kit, resourceName: name, prepared: prepared.assets, ...(forms ? { forms: forms.parts } : {}) });
|
|
84
|
+
const link = await kit.waitForReachableDeploymentUrl([hosting.url], { providerLabel: targetId });
|
|
85
|
+
const hostnames = plan ? await applyDomains({ api, kit, plan, proof: prepared.proof }) : [];
|
|
86
|
+
return publishResult({ targetId, name, accountId: api.accountId, hosting, link, hostnames, plan, forms });
|
|
87
|
+
},
|
|
88
|
+
checkReachability: url => kit.checkDeploymentUrl(url),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The publish outcome: hosting status, unfinished custom domains, and safe provider metadata (never a key). */
|
|
93
|
+
function publishResult({ targetId, name, accountId, hosting, link, hostnames, plan, forms }, _options = {}) {
|
|
94
|
+
const complete = hostnames.every(hostname => hostname.status === 'ready' && !hostname.operationFailed);
|
|
95
|
+
return { targetId, url: hosting.url, deploymentId: hosting.deploymentId, status: complete ? link.status : 'link-delayed',
|
|
96
|
+
statusMessage: complete ? link.statusMessage : `Hosting is ${link.status}; custom domains are unfinished. ${hostnames.filter(host => host.status !== 'ready' || host.operationFailed).map(host => host.message).join(' ')}`,
|
|
97
|
+
providerMetadata: { resourceName: name, projectName: name, accountId, hosting: { ...link, url: hosting.url }, hostnames, ...(plan ? { domainChanges: plan.changes, zoneId: plan.zone.id } : {}),
|
|
98
|
+
...(forms ? { formSubmissions: forms.summary } : {}) } };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Workers with active forms: provision D1 and build the receiver parts. Pages and form-less sites keep assets only.
|
|
102
|
+
* Runs after consent checks and before the upload session, so a failed provision changes no script. */
|
|
103
|
+
async function prepareForms({ api, kit, targetId, resourceName, formDestination, files }, _options = {}) {
|
|
104
|
+
if (targetId !== WORKERS_TARGET_ID) return undefined;
|
|
105
|
+
const normalized = normalizeForms({ formDestination, kit });
|
|
106
|
+
if (!normalized) return undefined;
|
|
107
|
+
const database = await provisionForms({ api, kit, resourceName });
|
|
108
|
+
const parts = await formsWorkerParts({ manifest: normalized, secret: normalized.secret, databaseId: database.databaseId }, { pageRoutes: formPageRoutes({ files }) });
|
|
109
|
+
return { parts, summary: { forms: normalized.forms.length, databaseId: database.databaseId, databaseName: database.databaseName } };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function preparePublish({ input, kit, config, plan, targetId }, _options = {}) {
|
|
113
|
+
const maxFiles = targetId === WORKERS_TARGET_ID && config.assetPlan === 'paid' ? 100_000 : 20_000;
|
|
114
|
+
const assets = prepareAssets({ files: input.files, responseHeaders: input.responseHeaders, kit }, { maxFiles });
|
|
115
|
+
if (!plan) return { assets };
|
|
116
|
+
const expected = createHash('sha256').update(JSON.stringify({ manifest: assets.manifest, config: assets.config })).digest('hex');
|
|
117
|
+
const path = `/.well-known/tovu-deployment-${expected}.txt`;
|
|
118
|
+
const proofFile = { file: path.slice(1), data: expected, contentType: 'text/plain' };
|
|
119
|
+
return { assets: prepareAssets({ files: [...input.files, proofFile], responseHeaders: input.responseHeaders, kit }, { maxFiles }), proof: { path, expected } };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Verify active status with the account endpoint when an account is supplied. User-owned
|
|
123
|
+
* tokens may require the user endpoint; a scope mismatch alone must not be called rejection.
|
|
124
|
+
* No identity or provider body crosses this boundary. At most two bounded read-only requests.
|
|
125
|
+
* @complexity O(1) requests and response processing (transport bounds response size).
|
|
126
|
+
*/
|
|
127
|
+
export async function verifyCredential({ credential, kit }, _options = {}) {
|
|
128
|
+
const paths = credential.accountId
|
|
129
|
+
? [`/accounts/${segment({ value: credential.accountId })}/tokens/verify`, '/user/tokens/verify']
|
|
130
|
+
: ['/user/tokens/verify'];
|
|
131
|
+
return verifyAt({ credential, kit, paths, index: 0 });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** One verify endpoint; a scope-shaped refusal falls through to the next endpoint when there is one.
|
|
135
|
+
* A bounded recursion (at most `paths.length` requests) rather than a loop, so every path ends in a
|
|
136
|
+
* return and there is no unreachable loop exit. */
|
|
137
|
+
async function verifyAt({ credential, kit, paths, index }) {
|
|
138
|
+
let resp;
|
|
139
|
+
try {
|
|
140
|
+
resp = await kit.fetch(`${API_ROOT}${paths[index]}`, { method: 'GET', headers: { Authorization: `Bearer ${credential.token}` }, redirect: 'error' }, { timeoutMs: kit.timeouts.QUICK });
|
|
141
|
+
} catch { return { ok: false, reason: 'unreachable' }; }
|
|
142
|
+
if (!resp.ok && index + 1 < paths.length && [400, 401, 403, 404].includes(resp.status)) return verifyAt({ credential, kit, paths, index: index + 1 });
|
|
143
|
+
return verifyVerdict({ resp });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** The verdict of the last endpoint tried: only 401/403 or a disabled/expired token is `rejected`. */
|
|
147
|
+
async function verifyVerdict({ resp }) {
|
|
148
|
+
if (!resp.ok) return { ok: false, reason: resp.status === 401 || resp.status === 403 ? 'rejected' : 'unreachable', statusCode: resp.status };
|
|
149
|
+
let json;
|
|
150
|
+
try { json = await resp.json(); }
|
|
151
|
+
catch { return { ok: false, reason: 'unreachable', statusCode: resp.status }; }
|
|
152
|
+
return tokenStatusVerdict({ json, statusCode: resp.status });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** A readable verify body: `active` is ok, `disabled`/`expired` rejected, anything else unreachable. */
|
|
156
|
+
function tokenStatusVerdict({ json, statusCode }) {
|
|
157
|
+
if (json?.success === true && json.result?.status === 'active') return { ok: true };
|
|
158
|
+
if (json?.success === true && ['disabled', 'expired'].includes(json.result?.status)) return { ok: false, reason: 'rejected', statusCode };
|
|
159
|
+
return { ok: false, reason: 'unreachable', statusCode };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function validateConfig(config, _options = {}) {
|
|
163
|
+
if (config.assetPlan && !['free', 'paid'].includes(config.assetPlan)) return 'assetPlan must be free or paid. Select the actual Workers account plan.';
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { refusal, segment } from './api.mjs';
|
|
2
|
+
import { workersAddress } from './target.mjs';
|
|
3
|
+
|
|
4
|
+
/** Pinned protocol date: advance with release acceptance, never silently use today's date. */
|
|
5
|
+
export const COMPATIBILITY_DATE = '2026-10-01';
|
|
6
|
+
const MODULE = 'export default { fetch(request, env) { return env.ASSETS.fetch(request); } };';
|
|
7
|
+
|
|
8
|
+
/** Server buckets are authoritative. Sequential upload bounds concurrency to one request.
|
|
9
|
+
* @complexity O(files + bytes) hashing/encoding and one request per nonempty server bucket. */
|
|
10
|
+
export async function uploadWorker({ api, kit, resourceName, prepared, forms }, _options = {}) {
|
|
11
|
+
const url = await workersAddress({ api, kit, resourceName });
|
|
12
|
+
const scriptPath = `${api.accountPath}/workers/scripts/${segment({ value: resourceName })}`;
|
|
13
|
+
const session = await api.request({ path: `${scriptPath}/assets-upload-session`, method: 'POST', body: { manifest: prepared.manifest } });
|
|
14
|
+
if (!session.jwt || !Array.isArray(session.buckets) || session.buckets.some(bucket => !Array.isArray(bucket))) throw refusal({ kit, code: 'CLOUDFLARE_ASSET_BUCKET_INVALID', message: `Worker ${resourceName} returned an invalid upload session. Preview and retry.`, status: 502 });
|
|
15
|
+
const jwt = await uploadBuckets({ api, kit, session, prepared });
|
|
16
|
+
const form = scriptForm({ jwt, config: prepared.config, forms });
|
|
17
|
+
const result = await api.request({ path: scriptPath, method: 'PUT', body: form }, { timeoutMs: kit.timeouts.DEPLOY });
|
|
18
|
+
await api.request({ path: `${scriptPath}/subdomain`, method: 'POST', body: { enabled: true, previews_enabled: false } });
|
|
19
|
+
return { url, deploymentId: result.id };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Assets-only script, or (with `forms`) the receiver modules, D1 and key bindings, and Worker-first form routes. */
|
|
23
|
+
export function scriptForm({ jwt, config, forms }, _options = {}) {
|
|
24
|
+
const modules = forms?.modules ?? [{ name: 'index.mjs', source: MODULE }];
|
|
25
|
+
const assetsConfig = { html_handling: 'auto-trailing-slash', not_found_handling: '404-page', ...config, ...(forms ? { run_worker_first: forms.runWorkerFirst } : {}) };
|
|
26
|
+
const form = new FormData();
|
|
27
|
+
form.append('metadata', JSON.stringify({ main_module: 'index.mjs', compatibility_date: COMPATIBILITY_DATE, bindings: [{ type: 'assets', name: 'ASSETS' }, ...(forms?.bindings ?? [])], assets: { jwt, config: assetsConfig } }));
|
|
28
|
+
for (const module of modules) form.append(module.name, new File([module.source], module.name, { type: 'application/javascript+module' }));
|
|
29
|
+
return form;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function uploadBuckets({ api, kit, session, prepared }, _options = {}) {
|
|
33
|
+
const nonempty = session.buckets.filter(bucket => bucket.length > 0);
|
|
34
|
+
if (nonempty.length === 0) return session.jwt;
|
|
35
|
+
let completion;
|
|
36
|
+
for (const bucket of nonempty) {
|
|
37
|
+
const form = new FormData();
|
|
38
|
+
for (const hash of bucket) {
|
|
39
|
+
const file = prepared.byHash.get(hash);
|
|
40
|
+
if (!file) throw refusal({ kit, code: 'CLOUDFLARE_ASSET_BUCKET_INVALID', message: `Cloudflare requested unknown asset hash ${hash}. Do not upload; preview and retry.`, status: 502 });
|
|
41
|
+
form.append(hash, new File([file.data.toString('base64')], hash, { type: file.contentType }));
|
|
42
|
+
}
|
|
43
|
+
const result = await api.request({ path: `${api.accountPath}/workers/assets/upload?base64=true`, method: 'POST', jwt: session.jwt, body: form }, { timeoutMs: kit.timeouts.UPLOAD });
|
|
44
|
+
if (result.jwt) completion = result.jwt;
|
|
45
|
+
}
|
|
46
|
+
if (!completion) throw refusal({ kit, code: 'CLOUDFLARE_UPLOAD_INCOMPLETE', message: 'Worker asset upload returned no completion token. Retry publishing; the script has not been changed.', status: 502 });
|
|
47
|
+
return completion;
|
|
48
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { refusal, segment, ZONES_PAGING } from './api.mjs';
|
|
2
|
+
|
|
3
|
+
/** Hostname validation is deliberately strict: no URL, wildcard, port or inferred parent zone. */
|
|
4
|
+
export function normalizeDomain({ domain, kit }, _options = {}) {
|
|
5
|
+
const name = String(domain).trim().toLowerCase().replace(/\.$/, '');
|
|
6
|
+
if (name.length > 253 || !name.includes('.') || !name.split('.').every(label => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label))) {
|
|
7
|
+
throw refusal({ kit, code: 'CLOUDFLARE_INVALID_DOMAIN', message: 'Enter the apex domain as a hostname (without https://, www prefix, port or path), then preview again.' });
|
|
8
|
+
}
|
|
9
|
+
return name;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Absorbs the old Pages zone discovery; verifies active full zone AND exact account ownership. */
|
|
13
|
+
export async function resolveZone({ api, domain, zoneId }, _options = {}) {
|
|
14
|
+
const zones = zoneId ? [{ id: zoneId }] : await api.list({ path: `/zones?account.id=${segment({ value: api.accountId })}` }, ZONES_PAGING);
|
|
15
|
+
const selected = zoneId ? zones[0] : zones.find(zone => zone.name === domain);
|
|
16
|
+
if (!selected?.id) throw refusal({ kit: api.kit, code: 'CLOUDFLARE_ZONE_REQUIRED', message: `No accessible zone matches ${domain}. Add it to this Cloudflare account or select its Zone ID and grant Zone Read.` });
|
|
17
|
+
const zone = await api.request({ path: `/zones/${segment({ value: selected.id })}` });
|
|
18
|
+
assertSelectedZone({ api, zone, selected, domain });
|
|
19
|
+
return { id: selected.id, name: zone.name };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function assertSelectedZone({ api, zone, selected, domain }, _options = {}) {
|
|
23
|
+
if (zone?.name !== domain || zone.account?.id !== api.accountId) throw refusal({ kit: api.kit, code: 'CLOUDFLARE_ZONE_MISMATCH', message: `${domain} does not match zone ${selected.id} in account ${api.accountId}. Select the correct domain, account and Zone ID.` });
|
|
24
|
+
if (zone.status !== 'active' || zone.type !== 'full') throw refusal({ kit: api.kit, code: 'CLOUDFLARE_ZONE_INACTIVE', message: `${domain} needs an active full Cloudflare DNS zone. Finish nameserver setup in Cloudflare, then preview again.` });
|
|
25
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
|
+
"name": "cloudflare",
|
|
4
|
+
"version": "0.3.1",
|
|
5
|
+
"description": "Publishes static exports to Cloudflare Workers or Pages and attaches reviewed custom domains using your saved Cloudflare connection. Workers sites with active forms store submissions in a D1 database in your Cloudflare account for the host to pull. Preview reads current infrastructure; domain changes require human confirmation.",
|
|
6
|
+
"author": "Tovu",
|
|
7
|
+
"license": "Apache-2.0",
|
|
8
|
+
"extensions": {
|
|
9
|
+
"tovu": {
|
|
10
|
+
"vendorAliases": ["pages.dev", "workers.dev"],
|
|
11
|
+
"displayName": "Cloudflare",
|
|
12
|
+
"summary": "Publish static exports with Workers or Pages and review apex/www domain changes before applying them.",
|
|
13
|
+
"id": "tovu/cloudflare",
|
|
14
|
+
"provides": [
|
|
15
|
+
"deploy-target:cloudflare-workers",
|
|
16
|
+
"deploy-target:cloudflare-pages",
|
|
17
|
+
"form-destination:cloudflare-d1"
|
|
18
|
+
],
|
|
19
|
+
"supersedes": {
|
|
20
|
+
"packages": [
|
|
21
|
+
"cloudflare-pages"
|
|
22
|
+
],
|
|
23
|
+
"contributions": [
|
|
24
|
+
{
|
|
25
|
+
"fromPlugins": [
|
|
26
|
+
"deploy",
|
|
27
|
+
"cloudflare-pages"
|
|
28
|
+
],
|
|
29
|
+
"descriptorId": "cloudflare-pages",
|
|
30
|
+
"label": "deploy target"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"credentialCards": [
|
|
35
|
+
{
|
|
36
|
+
"target": "cloudflare-workers",
|
|
37
|
+
"title": "Connect Cloudflare Workers",
|
|
38
|
+
"label": "Cloudflare Workers connection",
|
|
39
|
+
"category": "hosting",
|
|
40
|
+
"intro": "Connect Cloudflare Workers to publish this site, with optional forms and approved custom-domain changes.",
|
|
41
|
+
"usedFor": [
|
|
42
|
+
"Publishes your website on Cloudflare Workers",
|
|
43
|
+
"Collects your site's form submissions",
|
|
44
|
+
"Checks your domain and applies domain changes you approve"
|
|
45
|
+
],
|
|
46
|
+
"keyHint": "Scope to the selected account: Account → Workers Scripts → Edit. Forms also need D1 → Edit. Custom domains need Zone, DNS, Workers Routes, Workers Scripts and Cloudflare Pages reads; certificate polling needs SSL and Certificates → Read. Grant DNS or Workers Routes edit only for the corresponding approved changes.",
|
|
47
|
+
"saveKind": "publish-host",
|
|
48
|
+
"helpUrl": "https://dash.cloudflare.com/profile/api-tokens",
|
|
49
|
+
"helpLabel": "Create a token",
|
|
50
|
+
"baseUrl": "https://api.cloudflare.com"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"target": "cloudflare-pages",
|
|
54
|
+
"title": "Connect Cloudflare Pages",
|
|
55
|
+
"label": "Cloudflare Pages connection",
|
|
56
|
+
"category": "hosting",
|
|
57
|
+
"intro": "Connect Cloudflare Pages to publish this site, with optional approved custom-domain changes.",
|
|
58
|
+
"usedFor": [
|
|
59
|
+
"Publishes your website on Cloudflare Pages",
|
|
60
|
+
"Checks your domain and applies domain changes you approve"
|
|
61
|
+
],
|
|
62
|
+
"keyHint": "Scope to the selected account: Account → Cloudflare Pages → Edit. Custom domains need Zone, DNS, Workers Routes and Workers Scripts reads; attaching a domain needs DNS → Edit. Grant Workers Scripts or Workers Routes edit only for corresponding approved transfers.",
|
|
63
|
+
"saveKind": "publish-host",
|
|
64
|
+
"helpUrl": "https://dash.cloudflare.com/profile/api-tokens",
|
|
65
|
+
"helpLabel": "Create a token",
|
|
66
|
+
"baseUrl": "https://api.cloudflare.com"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"publicDnsResolver": {
|
|
70
|
+
"url": "https://cloudflare-dns.com/dns-query",
|
|
71
|
+
"format": "dns-json"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"keywords": [
|
|
76
|
+
"cloudflare-pages",
|
|
77
|
+
"cloudflare",
|
|
78
|
+
"deploy",
|
|
79
|
+
"publish",
|
|
80
|
+
"hosting",
|
|
81
|
+
"static-site",
|
|
82
|
+
"static-export",
|
|
83
|
+
"go-live",
|
|
84
|
+
"website",
|
|
85
|
+
"deploy-target:cloudflare-pages",
|
|
86
|
+
"workers",
|
|
87
|
+
"deploy-target:cloudflare-workers",
|
|
88
|
+
"custom-domain",
|
|
89
|
+
"dns",
|
|
90
|
+
"pages",
|
|
91
|
+
"cloudflare-workers",
|
|
92
|
+
"custom domain",
|
|
93
|
+
"apex",
|
|
94
|
+
"www"
|
|
95
|
+
]
|
|
96
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cloudflare
|
|
3
|
+
description: Publish a site's static exports to Cloudflare Workers or Pages and attach apex plus www domains after a human reviews the exact current infrastructure and changes. Explains token permissions, limits and partial results.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cloudflare
|
|
7
|
+
|
|
8
|
+
## Connection card
|
|
9
|
+
|
|
10
|
+
Choose the card yourself from its `usedFor` list. Reuse a saved key or working sign-in for that
|
|
11
|
+
task. Never offer a replacement unless the person asks.
|
|
12
|
+
|
|
13
|
+
Open the plugin’s own card for the selected destination: `credential_save {kind:"plugin-provider",target:"cloudflare-workers"}` or `credential_save {kind:"plugin-provider",target:"cloudflare-pages"}`. Each card explains its permissions and collects the Account ID and optional Zone ID along with the masked token. Read `references/permissions.md` before requesting custom-domain access.
|
|
14
|
+
|
|
15
|
+
Use `deployment_get_static_publish_capabilities` → `deployment_preview_static_publish` →
|
|
16
|
+
`deployment_execute_static_publish`. Prefer **Workers** (`cloudflare-workers`) for a **new** destination.
|
|
17
|
+
Keep **Pages** (`cloudflare-pages`) for existing Pages history/bindings or an explicit Pages choice.
|
|
18
|
+
A plugin upgrade never moves an existing site between products. Use `lastPublish` and the exact
|
|
19
|
+
`resourceName` from its config. Never guess a workers.dev account subdomain.
|
|
20
|
+
|
|
21
|
+
This hosts **static exports only**, including exported HTML, CSS, JavaScript and media. It does not run
|
|
22
|
+
the host's server, database, admin, dynamic forms, authentication or server rendering.
|
|
23
|
+
|
|
24
|
+
Load [permissions](references/permissions.md) before requesting a connection, and
|
|
25
|
+
[domains](references/domains.md) before any domain change. Load [Workers](references/workers.md) or
|
|
26
|
+
[Pages](references/pages.md) for the selected target, and [troubleshooting](references/troubleshooting.md)
|
|
27
|
+
when a check fails.
|
|
28
|
+
|
|
29
|
+
A preview with authenticated inspection needs `projectName`, using the SAME label as execute, plus
|
|
30
|
+
`target`, optional `credentialId`, and the descriptor's config fields. It issues authenticated **GETs
|
|
31
|
+
only**. Secrets are resolved server-side, never returned. Domain input is `customDomain` (apex only,
|
|
32
|
+
e.g. the person's actual domain) and optional `zoneId`. Both apex and www are inspected and attached.
|
|
33
|
+
Never invent an account, domain or project name for the person.
|
|
34
|
+
|
|
35
|
+
The execute tool shows the exact current records/bindings/routes and changes in a human confirmation
|
|
36
|
+
card. Only the person's click authorizes domain cutover. A model-supplied `confirmed:true`, opaque
|
|
37
|
+
request flag or chat assertion is not approval. Unknown/incomplete inventory refuses domain changes.
|
|
38
|
+
If state or the credential changes, preview and confirm again. The current HTTP publish route cannot
|
|
39
|
+
approve domains; guide the person to the existing chat confirmation card until the domain UI lands.
|
|
40
|
+
|
|
41
|
+
Keep one saved vendor connection: kind `vendor`, vendor `cloudflare`, encrypted
|
|
42
|
+
`{token, accountId, zoneId?}`. Open `credential_save` with kind `plugin-provider` and the selected target;
|
|
43
|
+
people enter secrets in its secure form, never chat. Keep existing Pages-only `worksFor` compatibility.
|
|
44
|
+
To add Workers/DNS capabilities, the person must explicitly update permissions/compatibility on that
|
|
45
|
+
same credential. Token active status alone proves no capability's permissions.
|
|
46
|
+
If the person asks to replace a token, list the exact permissions needed for the requested
|
|
47
|
+
operation from the permissions table and limit it to the selected account/zones. For a
|
|
48
|
+
Workers-only static publish, request Workers Scripts: Edit; add D1: Edit only for active forms.
|
|
49
|
+
For Pages-only publishing, request Cloudflare Pages: Edit. Add domain permissions only when needed.
|
|
50
|
+
Offer to help enable workers.dev when the chosen deployment needs it. If the account has no
|
|
51
|
+
subdomain, guide the person through Workers & Pages setup; never invent one or enable it silently.
|
|
52
|
+
|
|
53
|
+
Hosting accepted plus unfinished requested domains is `ok:"partial"` (tools: `published:true`,
|
|
54
|
+
`reachable:false`). Report every hostname's state and next step. Never say the requested site is live
|
|
55
|
+
until both hostnames have active validation/certificates and serve the deployed content over HTTPS.
|
|
56
|
+
There is no automatic rollback or step journal. For a partial cutover, inspect the current state and
|
|
57
|
+
confirm a fresh preview before retrying; do not blindly restore old DNS.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Apex and www
|
|
2
|
+
|
|
3
|
+
Supply the apex hostname as `customDomain` and optional `zoneId`; the plugin never discovers a zone
|
|
4
|
+
by removing the first hostname label. It enumerates zones and verifies the exact active full zone,
|
|
5
|
+
account and apex match. The preview reads all DNS records, Worker Custom Domains, Worker routes
|
|
6
|
+
(including disabled/no-script and wildcard/path routes), and every accessible Pages project's domains.
|
|
7
|
+
Permissions denied, malformed responses or bounded inventory exhaustion mean **unknown** and refuse
|
|
8
|
+
all domain changes.
|
|
9
|
+
|
|
10
|
+
Review both hostnames' current records, bindings, routes and the exact change list. Existing address
|
|
11
|
+
records (A/AAAA/CNAME) and other product bindings may be replaced only through the human's card.
|
|
12
|
+
Identical bindings/CNAMEs are reused. Pages-created CNAMEs keep the historical `jini-deploy:` ownership
|
|
13
|
+
marker; an unmarked but identical CNAME may be reused. Existing MX/TXT/CAA are preserved. Relevant
|
|
14
|
+
wildcards and inherited CAA are displayed. Exact records override wildcard DNS, so wildcard DNS is
|
|
15
|
+
preserved. An overlapping Worker route is removed as a WHOLE only when its wider impact was displayed
|
|
16
|
+
and confirmed; automatic splitting is not supported.
|
|
17
|
+
|
|
18
|
+
Workers uses Custom Domains separately for apex and www; Cloudflare manages their DNS and
|
|
19
|
+
certificates. Pages first associates each project domain, then creates its CNAME to the project's
|
|
20
|
+
pages.dev address (Cloudflare flattens the apex). Apex zone and Pages project must be in the same
|
|
21
|
+
account.
|
|
22
|
+
|
|
23
|
+
Execution rechecks the credential and approved state before writes, and checks domain state again
|
|
24
|
+
immediately before cutover. It polls validation/certificate state with bounded delays, then probes a
|
|
25
|
+
content-addressed proof file over guarded HTTPS on BOTH hosts. HTTP 200 from an old site does not
|
|
26
|
+
count. A delayed certificate, validation error or content mismatch remains partial with per-host
|
|
27
|
+
states. Retry with a fresh preview; unrelated records are never cleanup targets.
|
|
28
|
+
|
|
29
|
+
Both names initially serve content; no canonical-host redirect is added automatically. DNS/binding
|
|
30
|
+
cutover is not atomic. The review warns of interruption and broader wildcard impact. There is no
|
|
31
|
+
automatic rollback: inspect current infrastructure before any manual recovery.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Pages compatibility
|
|
2
|
+
|
|
3
|
+
The target ID remains `cloudflare-pages`, while its containing plugin is now `cloudflare`.
|
|
4
|
+
Existing site history, credential IDs/ciphertext/defaults, environment variables, URLs and
|
|
5
|
+
`jini-<project>` names retain their meanings. Use Pages again when history/bindings say Pages, or
|
|
6
|
+
when explicitly requested. A rename alone never migrates infrastructure or broadens `worksFor`.
|
|
7
|
+
|
|
8
|
+
The account credential needs `token` and `accountId`; optional `zoneId` is a default selection.
|
|
9
|
+
Environment fallback still reads `CLOUDFLARE_TOKEN` / `CLOUDFLARE_API_TOKEN` and
|
|
10
|
+
`CLOUDFLARE_ACCOUNT_ID` only in the existing allowed execution modes.
|
|
11
|
+
|
|
12
|
+
Pages retains project lookup/create, upload-token, check-missing, deduplicated upload batches,
|
|
13
|
+
upsert-hashes and deployments. Root `_headers` and `_redirects` are deployment form fields, never
|
|
14
|
+
manifest assets; a nested copy remains an ordinary file. Host headers replace the site's root
|
|
15
|
+
_headers because they are host policy. Limits: 25 MiB/file, 20,000 files including generated files,
|
|
16
|
+
at most 100 files and 75 MiB encoded JSON in each upload batch.
|
|
17
|
+
|
|
18
|
+
Official domain setup: https://developers.cloudflare.com/pages/configuration/custom-domains/
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Token permissions to tick
|
|
2
|
+
|
|
3
|
+
Create a custom API token at Cloudflare → My Profile → API Tokens. Scope Account permissions to the
|
|
4
|
+
chosen account and Zone permissions to the selected zones. Request only the chosen capabilities:
|
|
5
|
+
|
|
6
|
+
| Capability | Cloudflare permission checkboxes |
|
|
7
|
+
|---|---|
|
|
8
|
+
| Workers publish | **Account → Workers Scripts → Edit** |
|
|
9
|
+
| Pages publish | **Account → Cloudflare Pages → Edit** |
|
|
10
|
+
| Form submissions (Workers sites with active forms) | **Account → D1 → Edit** in addition to Workers publish |
|
|
11
|
+
| Preview custom domains, either target | **Zone → Zone → Read**, **Zone → DNS → Read**, **Zone → Workers Routes → Read**, **Account → Workers Scripts → Read**, **Account → Cloudflare Pages → Read** |
|
|
12
|
+
| Workers Custom Domain attachment | **Account → Workers Scripts → Edit**; **Zone → SSL and Certificates → Read** for certificate polling |
|
|
13
|
+
| Pages domain attachment | **Account → Cloudflare Pages → Edit**, **Zone → DNS → Edit** |
|
|
14
|
+
| Approved removal of conflicting DNS | **Zone → DNS → Edit** |
|
|
15
|
+
| Approved removal of another Worker's binding | **Account → Workers Scripts → Edit** |
|
|
16
|
+
| Approved removal of another Pages project's binding | **Account → Cloudflare Pages → Edit** |
|
|
17
|
+
| Approved removal of an overlapping route | **Zone → Workers Routes → Edit** |
|
|
18
|
+
|
|
19
|
+
Edit includes the corresponding reads. Cross-product inspection needs both Workers and Pages reads
|
|
20
|
+
even when only one product is the destination. A 403 on either inventory means **unknown**, not clear.
|
|
21
|
+
If the person cannot grant full inspection, publishing without a custom domain remains available.
|
|
22
|
+
|
|
23
|
+
`GET /accounts/{account_id}/tokens/verify` checks account-owned tokens; user-owned tokens use
|
|
24
|
+
`GET /user/tokens/verify`. The save check tries the account endpoint first and, on a possible scope
|
|
25
|
+
mismatch, the user endpoint. Both check active token status only. A green token check is not proof that
|
|
26
|
+
Workers upload, Pages upload, zone discovery, DNS changes, route transfer or certificate reads work.
|
|
27
|
+
The authenticated preview names the first denied operation and its account/zone resource.
|
|
28
|
+
|
|
29
|
+
Compatibility must explicitly include `cloudflare-workers` for Workers. Historical `worksFor:["cloudflare"]` and `worksFor:["cloudflare-pages"]` stay Pages-only. After granting the required permissions, explicitly update the same connection to include both target IDs if it should serve both products. No background rewrite expands a saved grant.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# What failed and the next step
|
|
2
|
+
|
|
3
|
+
| Code/result | Plain explanation | Next step |
|
|
4
|
+
|---|---|---|
|
|
5
|
+
| `CLOUDFLARE_CREDENTIAL_REQUIRED` | The connection lacks a token or Account ID. | Update it in the secure connection form. |
|
|
6
|
+
| `CLOUDFLARE_API_ERROR` HTTP 401/403 | The named account/zone/project operation was denied. | Check that operation's checkboxes and account/zone scope; keep the same connection ID. |
|
|
7
|
+
| `CLOUDFLARE_API_ERROR` HTTP 429 | Cloudflare is limiting requests. | Wait and preview again. Do not replace a working token. |
|
|
8
|
+
| `CLOUDFLARE_UNREACHABLE` / `CLOUDFLARE_INVALID_RESPONSE` | The provider or network did not return usable data. | Check the connection and Cloudflare status, then retry. |
|
|
9
|
+
| `CLOUDFLARE_SUBDOMAIN_REQUIRED` | This account has no workers.dev address configured. | Finish Workers & Pages setup in the Cloudflare dashboard. |
|
|
10
|
+
| `CLOUDFLARE_ASSET_COUNT_LIMIT` / `CLOUDFLARE_ASSET_SIZE_LIMIT` | The named export exceeds a hosting limit. | Reduce the export/file; choose paid Workers only if the actual account has that plan. |
|
|
11
|
+
| `CLOUDFLARE_ASSET_PATH` | The export contains an invalid or duplicate path. | Fix the named path and export again. |
|
|
12
|
+
| `CLOUDFLARE_ASSET_BUCKET_INVALID` / `CLOUDFLARE_UPLOAD_INCOMPLETE` | The upload session could not complete safely. | Retry publishing; no new script was uploaded after this failure. |
|
|
13
|
+
| `CLOUDFLARE_ZONE_REQUIRED` / `CLOUDFLARE_ZONE_MISMATCH` | Zone, domain and account do not match. | Select the actual apex and Zone ID in the correct account. |
|
|
14
|
+
| `CLOUDFLARE_ZONE_INACTIVE` | The zone is not an active full Cloudflare zone. | Finish nameserver setup, then preview again. |
|
|
15
|
+
| `CLOUDFLARE_INVENTORY_UNKNOWN` / `DOMAIN_INVENTORY_UNKNOWN` | Some current records/bindings/routes could not be inspected. | Complete all inventory read permissions or narrow the account; no domain changes are safe yet. |
|
|
16
|
+
| `CLOUDFLARE_DOMAIN_CONFIRMATION_REQUIRED` / `DOMAIN_CONFIRMATION_REQUIRED` | This call has no authenticated human approval of the exact domain changes. | Run execute through chat and answer its review card. Request flags cannot confirm it. |
|
|
17
|
+
| `CLOUDFLARE_DOMAIN_PLAN_STALE` / `PLAN_STALE` / `CREDENTIAL_CHANGED` | Infrastructure or the selected connection changed after review. | Preview and confirm the new state. |
|
|
18
|
+
| `ok:"partial"`, `reachable:false`, pending hostname | Hosting was uploaded but one or both domains lack validation, certificate or expected content. | Report the hostnames separately, inspect Cloudflare DNS/TLS/CAA status and retry a fresh preview. Never claim live. |
|
|
19
|
+
|
|
20
|
+
Never include a token or raw upstream error body in an explanation.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Workers static assets
|
|
2
|
+
|
|
3
|
+
Stable script name: `jini-<project-name>`, unless `resourceName` supplies the exact existing name.
|
|
4
|
+
The plugin reads the account's workers.dev subdomain. If absent, open Workers & Pages in the
|
|
5
|
+
Cloudflare dashboard and finish workers.dev setup; do not invent or register a subdomain automatically.
|
|
6
|
+
Preview discloses script creation/update and enabling its workers.dev endpoint.
|
|
7
|
+
|
|
8
|
+
Publishing creates an asset upload session, uploads exactly the nonempty server buckets with the
|
|
9
|
+
session JWT, then uses the completion JWT in script metadata. Empty buckets need no upload.
|
|
10
|
+
A minimal module delegates to `env.ASSETS.fetch`; its `ASSETS` binding owns the static files.
|
|
11
|
+
Root `_headers`/`_redirects` go through assets config and are never public assets. Nested copies are
|
|
12
|
+
ordinary assets. Host security headers replace any root site-supplied `_headers` because they are
|
|
13
|
+
host policy.
|
|
14
|
+
|
|
15
|
+
Each file is at most 25 MiB. Default/free limit is 20,000 files, paid is 100,000; `assetPlan:"paid"`
|
|
16
|
+
must describe the real Workers account plan. Generated configuration/content-proof files count.
|
|
17
|
+
Uploads run sequentially to bound memory and concurrency. The compatibility date is pinned in code
|
|
18
|
+
and should advance only after release acceptance.
|
|
19
|
+
|
|
20
|
+
## Form submissions
|
|
21
|
+
|
|
22
|
+
When the site has at least one active form, the same Worker also receives `POST /forms/:slug/submit`
|
|
23
|
+
(the path the host's forms already post to, so exported HTML is unchanged and there is no CORS). Publishing
|
|
24
|
+
finds or creates D1 database `tovu-forms-<script name>`, applies the plugin's forward-only migrations
|
|
25
|
+
(`schema_migrations`), binds it as `TOVU_FORMS`, installs the per-site receiver key as secret
|
|
26
|
+
`TOVU_FORMS_SECRET`, and routes `/forms/*` and `/_tovu/*` to the Worker before static assets. The
|
|
27
|
+
database is never dropped or recreated. The receiver checks only the compact manifest the host supplies
|
|
28
|
+
(slugs, field names, required, max length), honours the host's `_hp` honeypot and rate-limits to 5 valid
|
|
29
|
+
submissions per IP per form per 60 seconds; the host re-validates on pull. No file uploads. The host pulls
|
|
30
|
+
pending rows through `GET /_tovu/forms/pending` and `POST /_tovu/forms/ack` with the receiver key.
|
|
31
|
+
After a submission the visitor returns to the page with `?form=<slug>&form_status=success`. Pages
|
|
32
|
+
that contain a form are also routed to the Worker first (exact paths only; assets and other pages stay
|
|
33
|
+
on the free static path), which hides the form and shows the host's own success message, or one plain
|
|
34
|
+
"Thanks, your submission was received." line when the page has none.
|
|
35
|
+
Needs **Account → D1 → Edit** on the saved token; preview names it when missing. Pages sites with
|
|
36
|
+
forms are not supported yet; preview says so.
|
|
37
|
+
The receiver is form destination `cloudflare-d1` (co-hosted on Workers); the site's `core.forms`
|
|
38
|
+
`receiver` setting keeps it on with `auto` (the default) and turns it off with `none`.
|
|
39
|
+
|
|
40
|
+
Official protocol: https://developers.cloudflare.com/workers/static-assets/direct-upload/
|
|
41
|
+
|
|
42
|
+
A self-hosted operator may explicitly configure `CLOUDFLARE_WORKERS_TOKEN` with `CLOUDFLARE_ACCOUNT_ID`. Historical `CLOUDFLARE_TOKEN` / `CLOUDFLARE_API_TOKEN` environment credentials remain Pages-only; the new target never silently reuses them. A saved compatible connection still takes precedence under the existing resolver.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createTarget, previewTarget, validateConfig, verifyCredential } from '../lib/target.mjs';
|
|
2
|
+
import { uploadPages } from '../lib/pages-upload.mjs';
|
|
3
|
+
|
|
4
|
+
export const CLOUDFLARE_PAGES_TARGET_ID = 'cloudflare-pages';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
destinationConfig: ({ providerMetadata }, _options = {}) => typeof providerMetadata?.resourceName === 'string' ? { resourceName: providerMetadata.resourceName } : {},
|
|
8
|
+
validateConfig,
|
|
9
|
+
verifyCredential,
|
|
10
|
+
preview: (input, options = {}) => previewTarget({ ...input, targetId: CLOUDFLARE_PAGES_TARGET_ID }, options),
|
|
11
|
+
create: (context, options = {}) => createTarget({ ...context, targetId: CLOUDFLARE_PAGES_TARGET_ID, upload: uploadPages }, options),
|
|
12
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createTarget, previewTarget, validateConfig, verifyCredential } from '../lib/target.mjs';
|
|
2
|
+
import { uploadWorker } from '../lib/workers-upload.mjs';
|
|
3
|
+
|
|
4
|
+
/** Workers serves static exports only; the host's own server stays where it runs today.
|
|
5
|
+
* A site with active forms also gets the bundled submissions receiver (D1) on the same Worker; pulling
|
|
6
|
+
* what it stored is form destination `cloudflare-d1` (`form-destinations/cloudflare-d1.mjs`). */
|
|
7
|
+
export default {
|
|
8
|
+
defaultForNewDestination: true,
|
|
9
|
+
// Historical worksFor:[cloudflare] meant Pages only. Adding Workers must not expand that grant.
|
|
10
|
+
defaultCredentialWorksFor: ['cloudflare-workers'],
|
|
11
|
+
acceptsCredential: ({ worksFor }, _options = {}) => worksFor.includes('cloudflare-workers'),
|
|
12
|
+
destinationConfig: ({ providerMetadata }, _options = {}) => typeof providerMetadata?.resourceName === 'string' ? { resourceName: providerMetadata.resourceName } : {},
|
|
13
|
+
validateConfig,
|
|
14
|
+
verifyCredential,
|
|
15
|
+
preview: (input, options = {}) => previewTarget({ ...input, targetId: 'cloudflare-workers' }, options),
|
|
16
|
+
create: (context, options = {}) => createTarget({ ...context, targetId: 'cloudflare-workers', upload: uploadWorker }, options),
|
|
17
|
+
};
|