@jini-ai/agent-plugins 0.6.16 → 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 +35 -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,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
|
+
"name": "github-pages",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Publishes the site's static export to GitHub Pages with the workspace's own saved token. Ships the host code itself (a host loads it only because that host chose to ship this plugin) and the know-how an agent needs: which token permissions to grant, never guessing the owner, the address and base path, and what each failure means in plain words.",
|
|
6
|
+
"author": "Tovu",
|
|
7
|
+
"license": "Apache-2.0",
|
|
8
|
+
"extensions": {
|
|
9
|
+
"tovu": {
|
|
10
|
+
"vendorAliases": ["github_pages", "github pages", "githubpages", "github.io"],
|
|
11
|
+
"displayName": "GitHub Pages",
|
|
12
|
+
"summary": "Puts your website online on GitHub Pages. The assistant shows you a preview first.",
|
|
13
|
+
"id": "tovu/github-pages",
|
|
14
|
+
"provides": [
|
|
15
|
+
"deploy-target:github-pages"
|
|
16
|
+
],
|
|
17
|
+
"credentialCards": [
|
|
18
|
+
{
|
|
19
|
+
"target": "github-pages",
|
|
20
|
+
"title": "Connect GitHub Pages",
|
|
21
|
+
"label": "GitHub Pages connection",
|
|
22
|
+
"category": "hosting",
|
|
23
|
+
"intro": "Connect GitHub Pages to commit this site’s static export and enable Pages for the selected repository.",
|
|
24
|
+
"usedFor": [
|
|
25
|
+
"Publishes your website to the selected repository's GitHub Pages site"
|
|
26
|
+
],
|
|
27
|
+
"keyHint": "Select only the destination repository. Fine-grained token: Contents and Pages → Read and write. A classic token needs repo scope.",
|
|
28
|
+
"saveKind": "publish-host",
|
|
29
|
+
"helpUrl": "https://github.com/settings/personal-access-tokens/new",
|
|
30
|
+
"helpLabel": "Create a token",
|
|
31
|
+
"baseUrl": "https://api.github.com"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"github-pages",
|
|
38
|
+
"github",
|
|
39
|
+
"deploy",
|
|
40
|
+
"publish",
|
|
41
|
+
"hosting",
|
|
42
|
+
"static-site",
|
|
43
|
+
"static-export",
|
|
44
|
+
"go-live",
|
|
45
|
+
"website",
|
|
46
|
+
"deploy-target:github-pages"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: github-pages
|
|
3
|
+
description: Publish the site's static export to GitHub Pages with the workspace's own saved token. Covers the token, the owner, repo and branch fields, the address and base path, and what each failure means in plain words.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GitHub Pages
|
|
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 **Connect GitHub Pages** card with `credential_save {kind:"plugin-provider",target:"github-pages"}`. Select only the destination repository and grant Contents and Pages **Read and write**. The card links to token creation; the hosting owner verifies and seals it.
|
|
14
|
+
|
|
15
|
+
The steps are the same for every host: `deployment_get_static_publish_capabilities` shows whether a
|
|
16
|
+
credential is saved, `deployment_preview_static_publish` comes first, and `deployment_execute_static_publish`
|
|
17
|
+
publishes once the person confirms. `ok: "partial"` means not reachable yet: never call it failed or live.
|
|
18
|
+
This file is what is particular to GitHub Pages.
|
|
19
|
+
|
|
20
|
+
- **Token:** a fine-grained personal access token (https://github.com/settings/personal-access-tokens)
|
|
21
|
+
for the repository, with **Contents: Read and write** and **Pages: Read and write**. Save it with the plugin card above.
|
|
22
|
+
- **Config fields:** `owner` (user or organization), `repo`, and optional `branch` (default `gh-pages`).
|
|
23
|
+
The host commits the site to that branch and switches GitHub Pages on for it if it is off.
|
|
24
|
+
- **Owner:** never guess it from the person's name or email address. Use the verified token's login
|
|
25
|
+
(`accountLabel` in the capabilities result) and confirm it with the person. If `accountLabel` is null,
|
|
26
|
+
say the account is not known and ask for the GitHub user or organization. Do not offer an example
|
|
27
|
+
name: an invented one was once read back as a real suggestion and published to a repo nobody owned.
|
|
28
|
+
- **Address:** `https://<owner>.github.io/<repo>/`. The site is built with every link under `/<repo>`,
|
|
29
|
+
so previewing shows that base path.
|
|
30
|
+
- **`.nojekyll`:** this plugin adds it, so GitHub does not run Jekyll and drop files whose names start
|
|
31
|
+
with `_`.
|
|
32
|
+
- **Security headers:** GitHub Pages cannot set custom response headers, so none are sent.
|
|
33
|
+
|
|
34
|
+
## Failure messages and what to tell the person
|
|
35
|
+
|
|
36
|
+
| Message | Meaning | Next step |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `GitHub token is required.` | No token in the saved credential. | Open the plugin card above. |
|
|
39
|
+
| `invalid GitHub owner '...'` / `invalid GitHub repo '...'` / `invalid branch name '...'` | A config field has a typo or bad characters. | Correct the owner, repo, or branch name. |
|
|
40
|
+
| `Bad credentials` / `Resource not accessible by personal access token` (401/403) | The token is wrong, expired, or lacks the permissions above. | Check this repo's Contents and Pages access; replace the token only if asked. |
|
|
41
|
+
| `Not Found` (404) | The repo does not exist, or the token cannot see it. | Check the owner and repo names and the token's repository access. |
|
|
42
|
+
| `GitHub Pages build errored.` | GitHub accepted the files but its Pages build failed. | Retry once; if it repeats, check the repo's Pages settings. |
|
|
43
|
+
| `... attempted to redirect an authenticated request ...` | Something redirected the call; the host refused to send the token on. | Retry later; if it repeats, check the server's network or proxy. |
|
|
44
|
+
| `GitHub returned a non-JSON response.` / `fetch timed out after ...` | GitHub or the network had a hiccup. | Retry in a minute. |
|
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file GitHub Pages deploy target of the `github-pages` Agent Plugin.
|
|
5
|
+
*
|
|
6
|
+
* Ported from `@jini-ai/devops` `src/deploy/github-pages.ts` at Jini commit 28f67f9a: the body of
|
|
7
|
+
* {@link bindGitHubPages} is that source with its TypeScript types stripped, same GitHub calls, error
|
|
8
|
+
* texts and redirect guard. Network, timeouts, poll delay, reachability, the redirect guard and
|
|
9
|
+
* `DeployError` come from the host kit the host injects (see {@link bindGitHubPages}), bound once
|
|
10
|
+
* per target: an installed plugin has no npm dependencies.
|
|
11
|
+
*
|
|
12
|
+
* What the first host (Tovu) used to add in its core around this target now lives in the module
|
|
13
|
+
* contract at the bottom: the `.nojekyll` marker, the `/<repo>` base path, and the owner/repo/branch
|
|
14
|
+
* validation. GitHub Pages has no response-header config, so `responseHeaders` is ignored.
|
|
15
|
+
*
|
|
16
|
+
* Plain JS by owner decision (2026-09-29).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export const GITHUB_PAGES_TARGET_ID = "github-pages";
|
|
20
|
+
|
|
21
|
+
/** GitHub Pages runs Jekyll over published content unless this empty root file exists, and Jekyll
|
|
22
|
+
* silently drops every path segment starting with `_`. */
|
|
23
|
+
const NOJEKYLL_FILE = ".nojekyll";
|
|
24
|
+
|
|
25
|
+
/** GitHub owner/org: alphanumeric plus single hyphens, not starting with one, at most 39 characters. */
|
|
26
|
+
const OWNER_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})$/;
|
|
27
|
+
/** GitHub repo name: letters, digits, `.`/`-`/`_`, at most 100 characters. */
|
|
28
|
+
const REPO_PATTERN = /^[A-Za-z0-9_.-]{1,100}$/;
|
|
29
|
+
/** Git branch name: deliberately permissive, but no whitespace or control characters. */
|
|
30
|
+
const BRANCH_PATTERN = /^[A-Za-z0-9._/-]{1,250}$/;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The file set actually committed: `files` plus the `.nojekyll` marker, unless the site ships one.
|
|
34
|
+
*
|
|
35
|
+
* @param {Array<{ file: string, data: string | Uint8Array, contentType?: string }>} files
|
|
36
|
+
*/
|
|
37
|
+
function withNoJekyll(files) {
|
|
38
|
+
return files.some((file) => file.file === NOJEKYLL_FILE) ? files : [...files, { file: NOJEKYLL_FILE, data: "" }];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Binds the ported GitHub Pages code to one host kit.
|
|
43
|
+
*
|
|
44
|
+
* @param {{ fetch(url: string, init: RequestInit, options: { timeoutMs: number }): Promise<Response>,
|
|
45
|
+
* timeouts: { QUICK: number, DEPLOY: number, UPLOAD: number }, sleep(ms: number): Promise<void> } & Record<string, any>} kit
|
|
46
|
+
* The host's deploy kit in the positional ABI installed modules are written against (devops'
|
|
47
|
+
* `DeployHostKit` helpers, called positionally; `DeployError` is `new (message, status?, details?)`).
|
|
48
|
+
* @returns The target class, constructed with `{ token, owner, repo, branch? }` as in devops.
|
|
49
|
+
*/
|
|
50
|
+
export function bindGitHubPages(kit) {
|
|
51
|
+
const { fetch: fetchWithTimeout, timeouts: FETCH_TIMEOUT_MS, sleep, DeployError, checkDeploymentUrl, normalizeDeploymentUrl, waitForReachableDeploymentUrl, assertNotRedirected, redirectGuardInit } = kit;
|
|
52
|
+
const GITHUB_PAGES_TARGET_ID = 'github-pages';
|
|
53
|
+
const GITHUB_API = 'https://api.github.com';
|
|
54
|
+
/** Pinned per GitHub's own recommendation (`X-GitHub-Api-Version` header) so a future server-side default bump can't silently change this file's response shapes underneath it. */
|
|
55
|
+
const GITHUB_API_VERSION = '2026-03-10';
|
|
56
|
+
/** The long-standing GitHub Pages convention branch name (used by `gh-pages`-the-npm-package, `JamesIves/github-pages-deploy-action`, etc.) — deliberately distinct from a repo's default/production branch so a publish never touches source code. */
|
|
57
|
+
const DEFAULT_GITHUB_PAGES_BRANCH = 'gh-pages';
|
|
58
|
+
/**
|
|
59
|
+
* The only two terminal values this file commits to, independently
|
|
60
|
+
* cross-validated against `GET /repos/{owner}/{repo}/pages`'s own confirmed
|
|
61
|
+
* clean enum (`built | building | errored | null` — documented as "the
|
|
62
|
+
* status of the most recent build", the same underlying concept
|
|
63
|
+
* `builds/latest`'s `status` field represents). Any other value —
|
|
64
|
+
* `'building'`, `null`, or an unlisted future value — is treated as
|
|
65
|
+
* in-progress; see `pollGitHubPagesBuild`'s doc comment for why this file
|
|
66
|
+
* does not commit to `builds/latest`'s full enum (GitHub's own docs render
|
|
67
|
+
* that field's enum widget client-side and it did not come through cleanly
|
|
68
|
+
* via a direct fetch of the docs page during this file's research pass).
|
|
69
|
+
*/
|
|
70
|
+
const GITHUB_PAGES_BUILD_SUCCESS = 'built';
|
|
71
|
+
const GITHUB_PAGES_BUILD_FAILURE = 'errored';
|
|
72
|
+
/**
|
|
73
|
+
* `DeployTarget` adapter for GitHub Pages, built on the Git Data API (create
|
|
74
|
+
* blobs → a tree → a commit → create/update a branch ref) rather than the
|
|
75
|
+
* newer artifact-based "Pages deployments" API
|
|
76
|
+
* (`POST /repos/{owner}/{repo}/pages/deployments`). See
|
|
77
|
+
* `packages/devops/source-map.md`'s 2026-07-21 addition for the full
|
|
78
|
+
* research trail; in short: that endpoint requires an `oidc_token` "issued
|
|
79
|
+
* by GitHub Actions certifying the origin of the deployment" — a token this
|
|
80
|
+
* package (running outside an Actions runner, authenticating with a plain
|
|
81
|
+
* PAT) has no way to mint. The Git Data API has no such requirement: any
|
|
82
|
+
* token with repo write access can push a commit and (once the branch
|
|
83
|
+
* exists) enable/observe Pages against it, which is exactly what
|
|
84
|
+
* `actions/deploy-pages`'s *predecessor* tooling (and every non-Actions
|
|
85
|
+
* GitHub Pages deploy tool in the wild, e.g. `gh-pages`/
|
|
86
|
+
* `JamesIves/github-pages-deploy-action` in non-artifact mode) has always
|
|
87
|
+
* done.
|
|
88
|
+
*
|
|
89
|
+
* Unlike `VercelDeployTarget`/`NetlifyDeployTarget`/`CloudflarePagesDeployTarget`,
|
|
90
|
+
* this target does not derive a project/site identity from
|
|
91
|
+
* `input.projectName` — a GitHub Pages "site" is intrinsically the
|
|
92
|
+
* `{owner, repo}` the caller configures, not a freely-named resource this
|
|
93
|
+
* package could create on the fly. `input.projectName` is used only as a
|
|
94
|
+
* human label in the generated commit message.
|
|
95
|
+
*/
|
|
96
|
+
class GitHubPagesDeployTarget {
|
|
97
|
+
config;
|
|
98
|
+
id = GITHUB_PAGES_TARGET_ID;
|
|
99
|
+
constructor(config) {
|
|
100
|
+
this.config = config;
|
|
101
|
+
}
|
|
102
|
+
/** Existing Pages sites can use a custom domain; read it before rendering. A new site has
|
|
103
|
+
* GitHub's fixed owner/repository address until its first Pages branch exists.
|
|
104
|
+
* @complexity One bounded read, no branch writes or Pages provisioning.
|
|
105
|
+
*/
|
|
106
|
+
async resolvePublicUrl(_required, _options = {}) {
|
|
107
|
+
const { owner, repo } = this.config;
|
|
108
|
+
const response = await fetchWithTimeout(`${GITHUB_API}/repos/${enc(owner)}/${enc(repo)}/pages`, redirectGuardInit({ headers: githubHeaders(this.config.token) }), { timeoutMs: FETCH_TIMEOUT_MS.QUICK });
|
|
109
|
+
assertNotRedirected(response, 'GitHub Pages public URL lookup');
|
|
110
|
+
const body = await readGitHubJson(response);
|
|
111
|
+
if (response.status === 404) return `https://${owner.toLowerCase()}.github.io${repo.toLowerCase() === `${owner.toLowerCase()}.github.io` ? '' : `/${repo}`}`;
|
|
112
|
+
if (!response.ok) throw githubError(body, response.status, 'GitHub Pages site lookup failed.');
|
|
113
|
+
const url = githubPagesUrlCandidates(body)[0];
|
|
114
|
+
if (!url) throw new DeployError('GitHub Pages did not report a public URL.', 502);
|
|
115
|
+
return url;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Publishes `input.files` as a new commit on the configured Pages branch
|
|
119
|
+
* (creating the branch if it doesn't exist yet), ensures the repository's
|
|
120
|
+
* Pages site is enabled against that branch, waits for GitHub's own build
|
|
121
|
+
* of that exact commit to finish, then waits for the resulting URL to
|
|
122
|
+
* become publicly reachable.
|
|
123
|
+
*
|
|
124
|
+
* Sequencing note: the branch is pushed to *before* Pages is
|
|
125
|
+
* enabled/verified, not after. `POST /repos/{owner}/{repo}/pages` requires
|
|
126
|
+
* `source.branch` to already exist in the repository — enabling Pages
|
|
127
|
+
* against a branch that doesn't exist yet fails, so the branch must be
|
|
128
|
+
* created first via the Git Data API flow below.
|
|
129
|
+
*
|
|
130
|
+
* @param input - File set plus a caller-chosen label used only for the commit message.
|
|
131
|
+
* @throws {DeployError} If `config.token`/`config.owner`/`config.repo` is
|
|
132
|
+
* missing, or GitHub's API rejects any step of the blob/tree/commit/ref/
|
|
133
|
+
* pages flow, or the resulting Pages build reaches `status: 'errored'`.
|
|
134
|
+
*/
|
|
135
|
+
async publish(input) {
|
|
136
|
+
if (!this.config.token) {
|
|
137
|
+
throw new DeployError('GitHub token is required.', 400);
|
|
138
|
+
}
|
|
139
|
+
const { owner, repo } = this.config;
|
|
140
|
+
if (!owner || !repo) {
|
|
141
|
+
throw new DeployError('GitHub Pages owner and repo are required.', 400);
|
|
142
|
+
}
|
|
143
|
+
const branch = this.config.branch || DEFAULT_GITHUB_PAGES_BRANCH;
|
|
144
|
+
const { parentSha, commitSha } = await pushGitHubPagesFiles({ config: this.config, owner, repo, branch, input });
|
|
145
|
+
const { site, sourceBranchMismatch } = await prepareGitHubPagesSite({ config: this.config, owner, repo, branch, commitSha });
|
|
146
|
+
const candidates = githubPagesUrlCandidates(site);
|
|
147
|
+
const link = await waitForReachableDeploymentUrl(candidates, { providerLabel: 'GitHub Pages' });
|
|
148
|
+
return {
|
|
149
|
+
targetId: this.id,
|
|
150
|
+
url: link.url || candidates[0] || '',
|
|
151
|
+
deploymentId: commitSha,
|
|
152
|
+
status: link.status,
|
|
153
|
+
statusMessage: link.statusMessage,
|
|
154
|
+
...(link.reachableAt !== undefined ? { reachableAt: link.reachableAt } : {}),
|
|
155
|
+
providerMetadata: {
|
|
156
|
+
owner,
|
|
157
|
+
repo,
|
|
158
|
+
branch,
|
|
159
|
+
commitSha,
|
|
160
|
+
branchCreated: !parentSha,
|
|
161
|
+
...(sourceBranchMismatch ? { sourceBranchMismatch: true } : {}),
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/** Probes `url` with the shared reachability checker — no documented GitHub Pages equivalent to Vercel's Deployment Protection auth wall was found, same reasoning `netlify.ts`/`cloudflare-pages.ts` already applied to their own `checkReachability`. */
|
|
166
|
+
async checkReachability(url) {
|
|
167
|
+
return checkDeploymentUrl(url);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function pushGitHubPagesFiles({ config, owner, repo, branch, input }, _options = {}) {
|
|
171
|
+
const parentSha = await getGitHubRefSha(config, owner, repo, branch);
|
|
172
|
+
const treeSha = await createGitHubTreeFromFiles(config, owner, repo, input.files);
|
|
173
|
+
const label = typeof input.projectName === 'string' && input.projectName.trim() ? input.projectName.trim() : 'site';
|
|
174
|
+
const commitSha = await createGitHubCommit(config, owner, repo, `Deploy ${label} via @jini-ai/deploy`, treeSha, parentSha ? [parentSha] : []);
|
|
175
|
+
if (parentSha) {
|
|
176
|
+
await updateGitHubRef(config, owner, repo, branch, commitSha);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
await createGitHubRef(config, owner, repo, branch, commitSha);
|
|
180
|
+
}
|
|
181
|
+
return { parentSha, commitSha };
|
|
182
|
+
}
|
|
183
|
+
async function prepareGitHubPagesSite({ config, owner, repo, branch, commitSha }, _options = {}) {
|
|
184
|
+
const site = await ensureGitHubPagesSite(config, owner, repo, branch);
|
|
185
|
+
const configuredBranch = typeof site.source?.branch === 'string' ? site.source.branch : undefined;
|
|
186
|
+
const sourceBranchMismatch = configuredBranch !== undefined && configuredBranch !== branch;
|
|
187
|
+
const build = await pollGitHubPagesBuild(config, owner, repo, commitSha);
|
|
188
|
+
if (build?.status === GITHUB_PAGES_BUILD_FAILURE) {
|
|
189
|
+
throw new DeployError(build.errorMessage || 'GitHub Pages build errored.', 502, build.raw);
|
|
190
|
+
}
|
|
191
|
+
return { site, sourceBranchMismatch };
|
|
192
|
+
}
|
|
193
|
+
function enc(value) {
|
|
194
|
+
return encodeURIComponent(value);
|
|
195
|
+
}
|
|
196
|
+
function githubHeaders(token, extra = {}) {
|
|
197
|
+
return {
|
|
198
|
+
Authorization: `Bearer ${token}`,
|
|
199
|
+
Accept: 'application/vnd.github+json',
|
|
200
|
+
'X-GitHub-Api-Version': GITHUB_API_VERSION,
|
|
201
|
+
...extra,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async function readGitHubJson(resp) {
|
|
205
|
+
try {
|
|
206
|
+
return (await resp.json());
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
throw new DeployError('GitHub returned a non-JSON response.', resp.status || 502);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* `fallback` (the second operand of the `||`) is unreachable today: every
|
|
214
|
+
* one of this file's 8 call sites passes a non-empty string literal — same
|
|
215
|
+
* situation `netlify.ts`'s `netlifyError` documents for its own callers.
|
|
216
|
+
* Kept as the sane default a future caller forgetting to pass `fallback`
|
|
217
|
+
* would fall through to, not deleted for coverage's sake.
|
|
218
|
+
*/
|
|
219
|
+
function githubError(json, status, fallback) {
|
|
220
|
+
const message = typeof json?.message === 'string' && json.message ? json.message : fallback || `GitHub request failed (${status}).`;
|
|
221
|
+
return new DeployError(message, status, json);
|
|
222
|
+
}
|
|
223
|
+
function sha256Hex(data) {
|
|
224
|
+
return createHash('sha256').update(Buffer.from(data)).digest('hex');
|
|
225
|
+
}
|
|
226
|
+
/** Looks up the tip commit sha of `heads/{branch}`, or `undefined` if the branch does not exist yet (a brand-new Pages publish). */
|
|
227
|
+
async function getGitHubRefSha(config, owner, repo, branch) {
|
|
228
|
+
const resp = await fetchWithTimeout(`${GITHUB_API}/repos/${enc(owner)}/${enc(repo)}/git/ref/heads/${enc(branch)}`, redirectGuardInit({ headers: githubHeaders(config.token) }), { timeoutMs: FETCH_TIMEOUT_MS.QUICK });
|
|
229
|
+
assertNotRedirected(resp, 'GitHub Pages branch lookup');
|
|
230
|
+
if (resp.status === 404)
|
|
231
|
+
return undefined;
|
|
232
|
+
const json = await readGitHubJson(resp);
|
|
233
|
+
if (!resp.ok)
|
|
234
|
+
throw githubError(json, resp.status, 'GitHub Pages branch lookup failed.');
|
|
235
|
+
const object = json.object;
|
|
236
|
+
return typeof object?.sha === 'string' ? object.sha : undefined;
|
|
237
|
+
}
|
|
238
|
+
async function createGitHubBlob(config, owner, repo, data) {
|
|
239
|
+
const resp = await fetchWithTimeout(`${GITHUB_API}/repos/${enc(owner)}/${enc(repo)}/git/blobs`, redirectGuardInit({
|
|
240
|
+
method: 'POST',
|
|
241
|
+
headers: githubHeaders(config.token, { 'Content-Type': 'application/json' }),
|
|
242
|
+
body: JSON.stringify({ content: Buffer.from(data).toString('base64'), encoding: 'base64' }),
|
|
243
|
+
}), { timeoutMs: FETCH_TIMEOUT_MS.QUICK });
|
|
244
|
+
assertNotRedirected(resp, 'GitHub blob creation');
|
|
245
|
+
const json = await readGitHubJson(resp);
|
|
246
|
+
if (!resp.ok)
|
|
247
|
+
throw githubError(json, resp.status, 'GitHub blob creation failed.');
|
|
248
|
+
const sha = typeof json.sha === 'string' ? json.sha : '';
|
|
249
|
+
if (!sha)
|
|
250
|
+
throw new DeployError('GitHub blob response did not include a sha.', 502, json);
|
|
251
|
+
return sha;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Creates one blob per distinct file content (deduped by a local sha256 key
|
|
255
|
+
* — a plain dedup key, not a wire-protocol requirement the way Netlify's
|
|
256
|
+
* SHA1 manifest is, so any stable collision-resistant digest works; sha256
|
|
257
|
+
* via `node:crypto` matches `cloudflare-pages.ts`'s own no-new-dependency
|
|
258
|
+
* choice for the same class of problem) and assembles the resulting tree in
|
|
259
|
+
* one call. Deliberately always creates real blobs rather than using the
|
|
260
|
+
* tree endpoint's inline `content` shortcut — that shortcut only accepts
|
|
261
|
+
* text content, and a static site's asset set (images, fonts, ...) is not
|
|
262
|
+
* reliably valid UTF-8.
|
|
263
|
+
*/
|
|
264
|
+
async function createGitHubTreeFromFiles(config, owner, repo, files) {
|
|
265
|
+
const blobShaByHash = new Map();
|
|
266
|
+
const tree = [];
|
|
267
|
+
for (const file of files) {
|
|
268
|
+
const hash = sha256Hex(file.data);
|
|
269
|
+
let blobSha = blobShaByHash.get(hash);
|
|
270
|
+
if (!blobSha) {
|
|
271
|
+
blobSha = await createGitHubBlob(config, owner, repo, file.data);
|
|
272
|
+
blobShaByHash.set(hash, blobSha);
|
|
273
|
+
}
|
|
274
|
+
tree.push({ path: file.file, mode: '100644', type: 'blob', sha: blobSha });
|
|
275
|
+
}
|
|
276
|
+
const resp = await fetchWithTimeout(`${GITHUB_API}/repos/${enc(owner)}/${enc(repo)}/git/trees`, redirectGuardInit({
|
|
277
|
+
method: 'POST',
|
|
278
|
+
headers: githubHeaders(config.token, { 'Content-Type': 'application/json' }),
|
|
279
|
+
body: JSON.stringify({ tree }),
|
|
280
|
+
}), { timeoutMs: FETCH_TIMEOUT_MS.QUICK });
|
|
281
|
+
assertNotRedirected(resp, 'GitHub tree creation');
|
|
282
|
+
const json = await readGitHubJson(resp);
|
|
283
|
+
if (!resp.ok)
|
|
284
|
+
throw githubError(json, resp.status, 'GitHub tree creation failed.');
|
|
285
|
+
const sha = typeof json.sha === 'string' ? json.sha : '';
|
|
286
|
+
if (!sha)
|
|
287
|
+
throw new DeployError('GitHub tree response did not include a sha.', 502, json);
|
|
288
|
+
return sha;
|
|
289
|
+
}
|
|
290
|
+
async function createGitHubCommit(config, owner, repo, message, treeSha, parents) {
|
|
291
|
+
const resp = await fetchWithTimeout(`${GITHUB_API}/repos/${enc(owner)}/${enc(repo)}/git/commits`, redirectGuardInit({
|
|
292
|
+
method: 'POST',
|
|
293
|
+
headers: githubHeaders(config.token, { 'Content-Type': 'application/json' }),
|
|
294
|
+
body: JSON.stringify({ message, tree: treeSha, parents }),
|
|
295
|
+
}), { timeoutMs: FETCH_TIMEOUT_MS.QUICK });
|
|
296
|
+
assertNotRedirected(resp, 'GitHub commit creation');
|
|
297
|
+
const json = await readGitHubJson(resp);
|
|
298
|
+
if (!resp.ok)
|
|
299
|
+
throw githubError(json, resp.status, 'GitHub commit creation failed.');
|
|
300
|
+
const sha = typeof json.sha === 'string' ? json.sha : '';
|
|
301
|
+
if (!sha)
|
|
302
|
+
throw new DeployError('GitHub commit response did not include a sha.', 502, json);
|
|
303
|
+
return sha;
|
|
304
|
+
}
|
|
305
|
+
async function createGitHubRef(config, owner, repo, branch, sha) {
|
|
306
|
+
const resp = await fetchWithTimeout(`${GITHUB_API}/repos/${enc(owner)}/${enc(repo)}/git/refs`, redirectGuardInit({
|
|
307
|
+
method: 'POST',
|
|
308
|
+
headers: githubHeaders(config.token, { 'Content-Type': 'application/json' }),
|
|
309
|
+
body: JSON.stringify({ ref: `refs/heads/${branch}`, sha }),
|
|
310
|
+
}), { timeoutMs: FETCH_TIMEOUT_MS.QUICK });
|
|
311
|
+
assertNotRedirected(resp, 'GitHub Pages branch creation');
|
|
312
|
+
const json = await readGitHubJson(resp);
|
|
313
|
+
if (!resp.ok)
|
|
314
|
+
throw githubError(json, resp.status, 'GitHub Pages branch creation failed.');
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* `force: true` even though `sha` is always a direct child of the branch's
|
|
318
|
+
* current tip (`createGitHubCommit`'s `parents` was seeded from this exact
|
|
319
|
+
* ref lookup, so this update is a genuine fast-forward): a concurrent
|
|
320
|
+
* publish racing between this file's own ref-lookup and this update could
|
|
321
|
+
* otherwise turn a legitimate fast-forward into a rejected non-fast-forward
|
|
322
|
+
* update (422). `force: true` trades that narrow race for the same
|
|
323
|
+
* "last publish wins" semantics every other target in this package already
|
|
324
|
+
* has (a second concurrent `publish()` call also just overwrites whatever
|
|
325
|
+
* the first one produced).
|
|
326
|
+
*/
|
|
327
|
+
async function updateGitHubRef(config, owner, repo, branch, sha) {
|
|
328
|
+
const resp = await fetchWithTimeout(`${GITHUB_API}/repos/${enc(owner)}/${enc(repo)}/git/refs/heads/${enc(branch)}`, redirectGuardInit({
|
|
329
|
+
method: 'PATCH',
|
|
330
|
+
headers: githubHeaders(config.token, { 'Content-Type': 'application/json' }),
|
|
331
|
+
body: JSON.stringify({ sha, force: true }),
|
|
332
|
+
}), { timeoutMs: FETCH_TIMEOUT_MS.QUICK });
|
|
333
|
+
assertNotRedirected(resp, 'GitHub Pages branch update');
|
|
334
|
+
const json = await readGitHubJson(resp);
|
|
335
|
+
if (!resp.ok)
|
|
336
|
+
throw githubError(json, resp.status, 'GitHub Pages branch update failed.');
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Finds the repository's Pages site config, creating it (pointed at
|
|
340
|
+
* `branch`, `build_type: 'legacy'` — the branch-based build model, matching
|
|
341
|
+
* this file's Git Data API publish mechanism rather than the Actions
|
|
342
|
+
* `'workflow'` build type) if none exists yet. Never mutates an
|
|
343
|
+
* already-enabled site's configuration — see `publish()`'s
|
|
344
|
+
* `sourceBranchMismatch` handling for what happens when the existing site
|
|
345
|
+
* is configured against a different branch than this call targets.
|
|
346
|
+
*/
|
|
347
|
+
async function ensureGitHubPagesSite(config, owner, repo, branch) {
|
|
348
|
+
const getResp = await fetchWithTimeout(`${GITHUB_API}/repos/${enc(owner)}/${enc(repo)}/pages`, redirectGuardInit({ headers: githubHeaders(config.token) }), { timeoutMs: FETCH_TIMEOUT_MS.QUICK });
|
|
349
|
+
assertNotRedirected(getResp, 'GitHub Pages site lookup');
|
|
350
|
+
if (getResp.status === 404) {
|
|
351
|
+
const createResp = await fetchWithTimeout(`${GITHUB_API}/repos/${enc(owner)}/${enc(repo)}/pages`, redirectGuardInit({
|
|
352
|
+
method: 'POST',
|
|
353
|
+
headers: githubHeaders(config.token, { 'Content-Type': 'application/json' }),
|
|
354
|
+
body: JSON.stringify({ source: { branch, path: '/' }, build_type: 'legacy' }),
|
|
355
|
+
}), { timeoutMs: FETCH_TIMEOUT_MS.DEPLOY });
|
|
356
|
+
assertNotRedirected(createResp, 'GitHub Pages site creation');
|
|
357
|
+
const created = await readGitHubJson(createResp);
|
|
358
|
+
if (!createResp.ok)
|
|
359
|
+
throw githubError(created, createResp.status, 'GitHub Pages site creation failed.');
|
|
360
|
+
return created;
|
|
361
|
+
}
|
|
362
|
+
const json = await readGitHubJson(getResp);
|
|
363
|
+
if (!getResp.ok)
|
|
364
|
+
throw githubError(json, getResp.status, 'GitHub Pages site lookup failed.');
|
|
365
|
+
return json;
|
|
366
|
+
}
|
|
367
|
+
async function readPagesBuildResponse({ resp }, _options = {}) {
|
|
368
|
+
if (resp.status === 404)
|
|
369
|
+
return undefined;
|
|
370
|
+
let json;
|
|
371
|
+
try {
|
|
372
|
+
json = await readGitHubJson(resp);
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
// Same "nothing usable to report yet" bucket as the 404 branch above, not a
|
|
376
|
+
// publish failure: every step up to and including enabling the Pages site has
|
|
377
|
+
// already irreversibly succeeded by the time this loop runs (see `publish()`'s
|
|
378
|
+
// call order), so a response this file cannot parse as JSON — an empty/no-content
|
|
379
|
+
// body while GitHub's build-tracking record has not been written yet, or a
|
|
380
|
+
// transient gateway/edge response — is exactly as uninformative as a 404, never a
|
|
381
|
+
// reason to report the whole publish as failed. A genuinely persistent problem
|
|
382
|
+
// still surfaces: this loop is bounded by the same fixed 30-attempt budget as every
|
|
383
|
+
// other outcome (see `proceeds to the reachability wait ... exhausts its 30-attempt
|
|
384
|
+
// budget`), after which `publish()` falls through to the reachability probe with
|
|
385
|
+
// whatever URL it already has — never a false "published: false" for a site that is
|
|
386
|
+
// actually live (`github-pages.test.ts`'s `keeps polling past a builds/latest
|
|
387
|
+
// response that fails to parse as JSON` regression test).
|
|
388
|
+
return undefined;
|
|
389
|
+
}
|
|
390
|
+
if (!resp.ok)
|
|
391
|
+
throw githubError(json, resp.status, 'GitHub Pages build status check failed.');
|
|
392
|
+
return { json };
|
|
393
|
+
}
|
|
394
|
+
function pagesBuildState({ json }, _options = {}) {
|
|
395
|
+
const status = typeof json.status === 'string' ? json.status : '';
|
|
396
|
+
const errorObj = json.error;
|
|
397
|
+
const errorMessage = typeof errorObj?.message === 'string' ? errorObj.message : undefined;
|
|
398
|
+
return { status, ...(errorMessage ? { errorMessage } : {}), raw: json };
|
|
399
|
+
}
|
|
400
|
+
function isPagesBuildForCommit({ json, commitSha }, _options = {}) {
|
|
401
|
+
const commit = typeof json.commit === 'string' ? json.commit : '';
|
|
402
|
+
return commit === commitSha;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Polls `GET /repos/{owner}/{repo}/pages/builds/latest` — deliberately not
|
|
406
|
+
* the Pages site resource's own `status` field (`GET
|
|
407
|
+
* /repos/{owner}/{repo}/pages`), even though that field is documented with
|
|
408
|
+
* a cleaner confirmed enum. Reason: the site-level `status` is shared
|
|
409
|
+
* across every publish to the same site, so immediately after pushing a new
|
|
410
|
+
* commit it can still reflect the *previous* build's terminal state for a
|
|
411
|
+
* moment — a false-positive "already built" race with no way to tell it
|
|
412
|
+
* apart from a genuinely finished new build. `builds/latest`'s `commit`
|
|
413
|
+
* field lets this function confirm it is actually looking at *this*
|
|
414
|
+
* publish's build (`commit === commitSha`) before trusting its `status` at
|
|
415
|
+
* all, closing that race at the cost of committing to a smaller slice of
|
|
416
|
+
* that field's enum (see `GITHUB_PAGES_BUILD_SUCCESS`/`_FAILURE`'s doc
|
|
417
|
+
* comment for why only two values are treated as terminal).
|
|
418
|
+
*
|
|
419
|
+
* A 404 (no build has run yet — expected immediately after a brand-new
|
|
420
|
+
* site's first-ever push/enable, undocumented but consistent with GitHub
|
|
421
|
+
* REST's general "resource doesn't exist yet" convention), a stale,
|
|
422
|
+
* non-matching `commit`, and a response this file cannot parse as JSON are
|
|
423
|
+
* ALL treated as "keep polling," not an error — see the `catch` below for
|
|
424
|
+
* why the non-JSON case joins that list. Any other non-2xx response whose
|
|
425
|
+
* body DID parse still fails fast, matching
|
|
426
|
+
* `pollVercelDeployment`/`pollNetlifyDeploy`'s existing convention. Same
|
|
427
|
+
* fixed 30-attempt/1s-then-2s budget as this package's other targets.
|
|
428
|
+
*/
|
|
429
|
+
async function pollGitHubPagesBuild(config, owner, repo, commitSha) {
|
|
430
|
+
let last = null;
|
|
431
|
+
for (let i = 0; i < 30; i += 1) {
|
|
432
|
+
await sleep(i < 5 ? 1000 : 2000);
|
|
433
|
+
const resp = await fetchWithTimeout(`${GITHUB_API}/repos/${enc(owner)}/${enc(repo)}/pages/builds/latest`, redirectGuardInit({ headers: githubHeaders(config.token) }), { timeoutMs: FETCH_TIMEOUT_MS.DEPLOY });
|
|
434
|
+
assertNotRedirected(resp, 'GitHub Pages build status check');
|
|
435
|
+
const read = await readPagesBuildResponse({ resp });
|
|
436
|
+
const json = read?.json;
|
|
437
|
+
if (read === undefined || !isPagesBuildForCommit({ json, commitSha }))
|
|
438
|
+
continue;
|
|
439
|
+
last = pagesBuildState({ json });
|
|
440
|
+
const { status } = last;
|
|
441
|
+
if (status === GITHUB_PAGES_BUILD_SUCCESS || status === GITHUB_PAGES_BUILD_FAILURE)
|
|
442
|
+
return last;
|
|
443
|
+
}
|
|
444
|
+
return last;
|
|
445
|
+
}
|
|
446
|
+
/** Collects candidate public URLs from a Pages site response, preferring `html_url` (the browser-facing field, standard GitHub REST convention) over the bare `url` field (the API resource URL, which happens to also be public-reachable for Pages but is documented separately). */
|
|
447
|
+
function githubPagesUrlCandidates(site) {
|
|
448
|
+
const urls = [];
|
|
449
|
+
if (site && typeof site.html_url === 'string')
|
|
450
|
+
urls.push(site.html_url);
|
|
451
|
+
if (site && typeof site.url === 'string')
|
|
452
|
+
urls.push(site.url);
|
|
453
|
+
return [...new Set(urls.map(normalizeDeploymentUrl).filter(Boolean))];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return { GitHubPagesDeployTarget };
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** @param {unknown} value */
|
|
460
|
+
function asText(value) {
|
|
461
|
+
return typeof value === "string" ? value : "";
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Reads one credential-check response WITHOUT touching its body: 401/403 means the host refused the
|
|
466
|
+
* credential, any other failure says nothing about it (a 5xx, a rate limit).
|
|
467
|
+
*
|
|
468
|
+
* @param {Response} response
|
|
469
|
+
* @returns {{ ok: true } | { ok: false, reason: "rejected" | "unreachable", statusCode: number }}
|
|
470
|
+
*/
|
|
471
|
+
function classifyCredentialResponse(response) {
|
|
472
|
+
if (response.ok) return { ok: true };
|
|
473
|
+
return { ok: false, reason: response.status === 401 || response.status === 403 ? "rejected" : "unreachable", statusCode: response.status };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* The token's account `login` from a `GET /user` body, or `undefined`. Reads no other field.
|
|
478
|
+
* @param {unknown} body
|
|
479
|
+
* @returns {string | undefined}
|
|
480
|
+
*/
|
|
481
|
+
function readGitHubLogin(body) {
|
|
482
|
+
const login = typeof body === "object" && body !== null ? /** @type {Record<string, unknown>} */ (body).login : undefined;
|
|
483
|
+
return typeof login === "string" && login !== "" ? login : undefined;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/** The module contract a host's deploy-target registry loads (`DeployTargetModule`). Config fields:
|
|
487
|
+
* `owner`, `repo`, optional `branch` (the class defaults it to `gh-pages`). */
|
|
488
|
+
export default {
|
|
489
|
+
/** @param {{ credential: { token: string }, config: { owner?: string, repo?: string, branch?: string }, kit: any }} context */
|
|
490
|
+
create({ credential, config, kit }) {
|
|
491
|
+
const { GitHubPagesDeployTarget } = bindGitHubPages(kit);
|
|
492
|
+
class NoJekyllGitHubPagesDeployTarget extends GitHubPagesDeployTarget {
|
|
493
|
+
/** @param {{ files: Array<{ file: string, data: string | Uint8Array }>, projectName: string }} input */
|
|
494
|
+
publish(input) {
|
|
495
|
+
return super.publish({ ...input, files: withNoJekyll(input.files) });
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return new NoJekyllGitHubPagesDeployTarget({
|
|
499
|
+
token: credential.token,
|
|
500
|
+
owner: asText(config.owner),
|
|
501
|
+
repo: asText(config.repo),
|
|
502
|
+
...(typeof config.branch === "string" ? { branch: config.branch } : {}),
|
|
503
|
+
});
|
|
504
|
+
},
|
|
505
|
+
/**
|
|
506
|
+
* `GET /user`, GitHub's own "who am I" endpoint: 401 for a bad or revoked token. Reads back only
|
|
507
|
+
* `login`, the public handle GitHub prints in every profile and repo URL (a publish is about to
|
|
508
|
+
* print it in `https://<login>.github.io/<repo>/`), never email, plan or orgs.
|
|
509
|
+
* @param {{ credential: { token: string }, kit: any }} context
|
|
510
|
+
*/
|
|
511
|
+
async verifyCredential({ credential, kit }) {
|
|
512
|
+
const response = await kit.fetch("https://api.github.com/user", { headers: { Authorization: `Bearer ${credential.token}`, Accept: "application/vnd.github+json" } }, { timeoutMs: kit.timeouts.QUICK });
|
|
513
|
+
const check = classifyCredentialResponse(response);
|
|
514
|
+
if (!check.ok) return check;
|
|
515
|
+
return { ok: true, accountLabel: readGitHubLogin(await response.json().catch(() => undefined)) };
|
|
516
|
+
},
|
|
517
|
+
/**
|
|
518
|
+
* Shape check at the boundary before any value reaches a URL (the class still escapes each
|
|
519
|
+
* segment). Texts are byte-identical to the checks the first host (Tovu) ran in its core before this moved here.
|
|
520
|
+
* @param {{ owner?: unknown, repo?: unknown, branch?: unknown }} config
|
|
521
|
+
* @returns {string | null}
|
|
522
|
+
*/
|
|
523
|
+
validateConfig(config) {
|
|
524
|
+
const owner = asText(config.owner);
|
|
525
|
+
const repo = asText(config.repo);
|
|
526
|
+
if (!OWNER_PATTERN.test(owner)) return `invalid GitHub owner '${owner.slice(0, 60)}'`;
|
|
527
|
+
if (!REPO_PATTERN.test(repo) || repo === "." || repo === "..") return `invalid GitHub repo '${repo.slice(0, 100)}'`;
|
|
528
|
+
if (config.branch !== undefined && !BRANCH_PATTERN.test(asText(config.branch))) return `invalid branch name '${asText(config.branch).slice(0, 60)}'`;
|
|
529
|
+
return null;
|
|
530
|
+
},
|
|
531
|
+
/** A project site is served from `/<repo>`, so every asset URL must carry that prefix.
|
|
532
|
+
* @param {{ repo?: unknown }} config */
|
|
533
|
+
basePath(config) {
|
|
534
|
+
return `/${asText(config.repo)}`;
|
|
535
|
+
},
|
|
536
|
+
/** The confirmation card reads `owner/repo` as one row, the way GitHub prints a repository.
|
|
537
|
+
* @param {{ owner?: unknown, repo?: unknown, branch?: unknown }} config */
|
|
538
|
+
summarize(config) {
|
|
539
|
+
const rows = [{ label: "Repository", value: `${asText(config.owner)}/${asText(config.repo)}` }];
|
|
540
|
+
if (typeof config.branch === "string") rows.push({ label: "Branch", value: config.branch });
|
|
541
|
+
return rows;
|
|
542
|
+
},
|
|
543
|
+
};
|