@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,117 @@
|
|
|
1
|
+
#>> header
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# fly.io deploy workflow TEMPLATE.
|
|
4
|
+
#
|
|
5
|
+
# Derived from a working production deploy workflow.
|
|
6
|
+
# Write this to `.github/workflows/fly-deploy.yml` in the operator's repo.
|
|
7
|
+
#
|
|
8
|
+
# flyctl runs HERE, on GitHub's runner — never on the operator's machine. That
|
|
9
|
+
# is the point of this path: no local CLI, no local Docker, nothing to install.
|
|
10
|
+
#
|
|
11
|
+
# Replace every `<<PLACEHOLDER: ...>>` before committing.
|
|
12
|
+
# ============================================================================
|
|
13
|
+
# ----------------------------------------------------------------------------
|
|
14
|
+
# Template regions (read by scripts/render-deploy-workflow.mjs; ignore them when copying by hand):
|
|
15
|
+
# `#>> <name>` ... `#<< <name>` the operator version. A host's own rendered workflow replaces a
|
|
16
|
+
# region with that host's block for <name>; the rest is shared.
|
|
17
|
+
# ----------------------------------------------------------------------------
|
|
18
|
+
#
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
# ONE-TIME SETUP THIS WORKFLOW CANNOT DO FOR YOU
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
#
|
|
23
|
+
# 1. Repo secret `FLY_API_TOKEN`
|
|
24
|
+
# Settings -> Secrets and variables -> Actions -> New repository secret
|
|
25
|
+
# Named exactly `FLY_API_TOKEN`, value a fly.io deploy token. A HUMAN must
|
|
26
|
+
# paste this. It cannot be set by an assistant: the GitHub API requires the
|
|
27
|
+
# value encrypted against the repo public key, which would mean the token
|
|
28
|
+
# passing through the model's context — precisely what the saved-credential
|
|
29
|
+
# design exists to prevent.
|
|
30
|
+
#
|
|
31
|
+
# 2. The volume, created before the first deploy — this workflow does not create
|
|
32
|
+
# one, and a missing volume means no persistent storage and silent data loss
|
|
33
|
+
# on the next deploy:
|
|
34
|
+
# flyctl volumes create <name> --region <region> -a <app>
|
|
35
|
+
# The name must match `fly.toml`'s `[[mounts]] source` exactly.
|
|
36
|
+
#
|
|
37
|
+
# 3. App secrets, via `flyctl secrets set -a <app> KEY=value` — never in this
|
|
38
|
+
# file and never in fly.toml's committed `[env]`. Which secrets the server
|
|
39
|
+
# refuses to boot without is the host's to say: follow the host guidance.
|
|
40
|
+
# Generate random ones as:
|
|
41
|
+
# node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"
|
|
42
|
+
#
|
|
43
|
+
# 4. Migrations and any other first-boot step: the host guidance says whether
|
|
44
|
+
# the server applies them itself. Add no step here that it does not ask for.
|
|
45
|
+
# ============================================================================
|
|
46
|
+
|
|
47
|
+
#<< header
|
|
48
|
+
name: Deploy to Fly.io
|
|
49
|
+
|
|
50
|
+
#>> trigger-notes
|
|
51
|
+
# Deploy-only, deliberately separate from any test/CI workflow. It builds and
|
|
52
|
+
# deploys whatever is already on the triggering commit; it runs no tests.
|
|
53
|
+
#
|
|
54
|
+
# `push` fires only on the repo's DEFAULT branch. A push to any other branch
|
|
55
|
+
# will not trigger this — use the `workflow_dispatch` "Run workflow" button, or
|
|
56
|
+
# an API dispatch, to deploy from elsewhere.
|
|
57
|
+
#<< trigger-notes
|
|
58
|
+
on:
|
|
59
|
+
push:
|
|
60
|
+
branches: [<<PLACEHOLDER: default branch, e.g. main>>]
|
|
61
|
+
#>> dispatch
|
|
62
|
+
workflow_dispatch: {}
|
|
63
|
+
#<< dispatch
|
|
64
|
+
|
|
65
|
+
permissions:
|
|
66
|
+
contents: read
|
|
67
|
+
|
|
68
|
+
#>> concurrency-notes
|
|
69
|
+
# One deploy at a time. Two `flyctl deploy` runs racing against a single SQLite
|
|
70
|
+
# volume is a real hazard, not just wasted runner minutes.
|
|
71
|
+
#
|
|
72
|
+
# `cancel-in-progress: false` on purpose: a deploy cut off mid-way is worse than
|
|
73
|
+
# a later push waiting its turn. Expect runs to sit in `queued` behind an active
|
|
74
|
+
# deploy — that is correct behaviour, not a stuck job.
|
|
75
|
+
#<< concurrency-notes
|
|
76
|
+
concurrency:
|
|
77
|
+
#>> concurrency-group
|
|
78
|
+
group: fly-deploy-<<PLACEHOLDER: fly app name>>
|
|
79
|
+
#<< concurrency-group
|
|
80
|
+
cancel-in-progress: false
|
|
81
|
+
|
|
82
|
+
#>> env
|
|
83
|
+
#<< env
|
|
84
|
+
jobs:
|
|
85
|
+
#>> jobs
|
|
86
|
+
#<< jobs
|
|
87
|
+
deploy:
|
|
88
|
+
#>> deploy-needs
|
|
89
|
+
#<< deploy-needs
|
|
90
|
+
runs-on: ubuntu-latest
|
|
91
|
+
steps:
|
|
92
|
+
#>> pre-checkout
|
|
93
|
+
#<< pre-checkout
|
|
94
|
+
- uses: actions/checkout@v4
|
|
95
|
+
#>> checkout-with
|
|
96
|
+
#<< checkout-with
|
|
97
|
+
|
|
98
|
+
- uses: superfly/flyctl-actions/setup-flyctl@v1
|
|
99
|
+
|
|
100
|
+
#>> build
|
|
101
|
+
# Fly's REMOTE builder does the image build. This runner only uploads the
|
|
102
|
+
# build context and polls status, so no Docker or BuildKit setup is needed
|
|
103
|
+
# here.
|
|
104
|
+
#
|
|
105
|
+
# Add one `--build-arg NAME=value` per build argument the host's Dockerfile
|
|
106
|
+
# needs (the host guidance lists them). Build args were lost once already
|
|
107
|
+
# in a history force-push and two deploys failed before anyone noticed — if
|
|
108
|
+
# you are editing an existing copy of this file, diff MODIFIED files, not
|
|
109
|
+
# just added and deleted ones.
|
|
110
|
+
#<< build
|
|
111
|
+
#>> deploy-command
|
|
112
|
+
- run: flyctl deploy --remote-only
|
|
113
|
+
#<< deploy-command
|
|
114
|
+
env:
|
|
115
|
+
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
|
116
|
+
#>> post-deploy
|
|
117
|
+
#<< post-deploy
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# fly.io app config TEMPLATE.
|
|
3
|
+
#
|
|
4
|
+
# Matches the plugin's deploy-configs/fly.mjs renderer. Write this to the
|
|
5
|
+
# root of the operator's repo, next to `Dockerfile`.
|
|
6
|
+
#
|
|
7
|
+
# EVERY `<<PLACEHOLDER: ...>>` below must be replaced before this file is
|
|
8
|
+
# committed. A surviving placeholder is not a harmless default — it is a deploy
|
|
9
|
+
# that either fails confusingly or succeeds against the wrong app.
|
|
10
|
+
#
|
|
11
|
+
# This file is COMMITTED TO THE REPO AND IS PUBLIC. Nothing secret may appear
|
|
12
|
+
# in it. See `[env]` below.
|
|
13
|
+
# ============================================================================
|
|
14
|
+
|
|
15
|
+
# The fly app to deploy into. There is no safe default: a wrong name here either
|
|
16
|
+
# deploys over someone else's app or silently creates a stray one.
|
|
17
|
+
app = "<<PLACEHOLDER: fly app name>>"
|
|
18
|
+
|
|
19
|
+
# UNVERIFIED DEFAULT, not a recommendation. Pick the region the operator
|
|
20
|
+
# actually wants and match it in the volume's own `--region`. If this is left as
|
|
21
|
+
# `iad`, say so explicitly rather than letting them assume it was chosen.
|
|
22
|
+
primary_region = "<<PLACEHOLDER: fly region, e.g. iad — this is NOT a default>>"
|
|
23
|
+
|
|
24
|
+
[build]
|
|
25
|
+
dockerfile = "Dockerfile"
|
|
26
|
+
|
|
27
|
+
# ----------------------------------------------------------------------------
|
|
28
|
+
# NON-SECRET VALUES ONLY. This block is public.
|
|
29
|
+
#
|
|
30
|
+
# Real secrets go through `flyctl secrets set`, never here. The host guidance
|
|
31
|
+
# names the server's boot-required secrets and any production-mode switch that
|
|
32
|
+
# belongs in this block.
|
|
33
|
+
# ----------------------------------------------------------------------------
|
|
34
|
+
[env]
|
|
35
|
+
PORT = "<<PLACEHOLDER: container port the server listens on>>"
|
|
36
|
+
|
|
37
|
+
# ----------------------------------------------------------------------------
|
|
38
|
+
# Persistent state: the server's database and uploaded files live here;
|
|
39
|
+
# everything else in the image is disposable.
|
|
40
|
+
#
|
|
41
|
+
# TWO THINGS ABOUT THIS MOUNT:
|
|
42
|
+
#
|
|
43
|
+
# 1. It must exist BEFORE the first deploy. The workflow does not create it.
|
|
44
|
+
# Without it the app boots with no persistent storage and every write is lost
|
|
45
|
+
# on the next deploy — a silent data loss, not a startup error.
|
|
46
|
+
# flyctl volumes create <name> --region <region> -a <app>
|
|
47
|
+
# `source` below must match that name EXACTLY.
|
|
48
|
+
#
|
|
49
|
+
# 2. The mount HIDES whatever the image has at that path at runtime — not
|
|
50
|
+
# merged, not per-file, gone. Anything the server must read at boot has to
|
|
51
|
+
# live outside the mount path in the image (the host guidance says where).
|
|
52
|
+
# ----------------------------------------------------------------------------
|
|
53
|
+
[[mounts]]
|
|
54
|
+
source = "<<PLACEHOLDER: volume name — must match `flyctl volumes create` exactly>>"
|
|
55
|
+
destination = "<<PLACEHOLDER: volume mount path inside the container>>"
|
|
56
|
+
|
|
57
|
+
[http_service]
|
|
58
|
+
internal_port = <<PLACEHOLDER: container port, the same as PORT>>
|
|
59
|
+
force_https = true
|
|
60
|
+
|
|
61
|
+
# ONE MACHINE ONLY when the server keeps its state on the volume above (SQLite): a
|
|
62
|
+
# fly volume attaches to a single machine — so a second machine does not share
|
|
63
|
+
# this database, it gets its OWN separate volume and its own divergent
|
|
64
|
+
# database file. Writes then land in one or the other depending on which machine
|
|
65
|
+
# served the request. Nothing errors and nothing warns.
|
|
66
|
+
#
|
|
67
|
+
# These two settings keep the one machine up. Neither caps the MAXIMUM —
|
|
68
|
+
# nothing in fly.toml does. Never run `fly scale count` above 1, and never
|
|
69
|
+
# enable autoscaling.
|
|
70
|
+
auto_stop_machines = false
|
|
71
|
+
auto_start_machines = true
|
|
72
|
+
min_machines_running = 1
|
|
73
|
+
|
|
74
|
+
# Point this at a READINESS path (503 until migrations and seeding finish),
|
|
75
|
+
# not a liveness-only one that reports OK on a server that cannot yet serve.
|
|
76
|
+
[[http_service.checks]]
|
|
77
|
+
grace_period = "60s"
|
|
78
|
+
interval = "30s"
|
|
79
|
+
method = "GET"
|
|
80
|
+
timeout = "5s"
|
|
81
|
+
path = "<<PLACEHOLDER: readiness check path, e.g. /readyz>>"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"schemes": [
|
|
4
|
+
{
|
|
5
|
+
"id": "fly-io",
|
|
6
|
+
"prefix": "FlyV1",
|
|
7
|
+
"scheme": "FlyV1",
|
|
8
|
+
"note": "Fly.io tokens (e.g. FlyV1fm2_..., a macaroon) carry their own Authorization scheme word. Verified live 2026-09-03 against https://api.machines.dev/v1/apps/tovu/machines with two independently generated tokens: 'Authorization: Bearer FlyV1fm2_...' (whole token) returned 401 {\"error\":\"Authenticate: token validation error\"}; 'Authorization: FlyV1 fm2_...' (FlyV1 moved to the scheme, remainder as the value) returned 200 with real machine data. Both tokens behaved identically, so it is a scheme problem, not scope or expiry. The 401's WWW-Authenticate header (Basic realm=\"api.machines.dev\") does not name the real scheme, so it cannot be derived from the server's challenge and must be declared here."
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"generators": [
|
|
4
|
+
{
|
|
5
|
+
"id": "fly",
|
|
6
|
+
"label": "Fly.io",
|
|
7
|
+
"module": "deploy-configs/fly.mjs",
|
|
8
|
+
"candidatePaths": [
|
|
9
|
+
{
|
|
10
|
+
"path": "fly.toml",
|
|
11
|
+
"order": 1
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"path": ".github/workflows/fly-deploy.yml",
|
|
15
|
+
"order": 5
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"workflowFile": "fly-deploy.yml"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/** Actions observer and deploy dispatcher: requests stay on api.github.com; job-log archive redirects are never requested. */
|
|
2
|
+
const PLATFORM = "github-actions";
|
|
3
|
+
const text = value => String(value ?? "").slice(0, 2000);
|
|
4
|
+
/** Reject path injection and build a fixed-host repository URL. */
|
|
5
|
+
function base(ctx, target) {
|
|
6
|
+
if (typeof target !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9-]*\/[a-zA-Z0-9_.-]+$/.test(target) || [".", ".."].includes(target.split("/")[1])) ctx.fail("Target must be owner/repo.");
|
|
7
|
+
return `https://api.github.com/repos/${target.split("/").map(encodeURIComponent).join("/")}`;
|
|
8
|
+
}
|
|
9
|
+
function invalidJob({ job }, _options = {}) {
|
|
10
|
+
if (!job || !/^\d+$/.test(String(job.id)) || typeof job.status !== "string") return true;
|
|
11
|
+
return job.steps !== undefined && (!Array.isArray(job.steps) || job.steps.some(s => !s || typeof s.name !== "string"));
|
|
12
|
+
}
|
|
13
|
+
async function selectWorkflowRun({ ctx, input, runs }, _options = {}) {
|
|
14
|
+
let run;
|
|
15
|
+
if (input.runId !== undefined) {
|
|
16
|
+
if (!/^\d+$/.test(input.runId)) ctx.fail("runId must contain only digits.");
|
|
17
|
+
run = (await ctx.get(`${runs}/${input.runId}`)).json;
|
|
18
|
+
} else {
|
|
19
|
+
const branch = input.branch ? `&branch=${encodeURIComponent(input.branch)}` : "";
|
|
20
|
+
const response = (await ctx.get(`${runs}?per_page=5${branch}`)).json;
|
|
21
|
+
if (!Array.isArray(response?.workflow_runs)) ctx.fail("Workflow response must contain workflow_runs.");
|
|
22
|
+
run = response.workflow_runs[0];
|
|
23
|
+
}
|
|
24
|
+
return run;
|
|
25
|
+
}
|
|
26
|
+
/** Select the requested run or latest branch run, then fetch at most 100 jobs. O(1) requests. */
|
|
27
|
+
async function readRun(ctx, input) {
|
|
28
|
+
const repo = base(ctx, input.target); const runs = `${repo}/actions/runs`;
|
|
29
|
+
const run = await selectWorkflowRun({ ctx, input, runs });
|
|
30
|
+
if (!run) return { repo, run: undefined, jobs: [], moreJobs: false };
|
|
31
|
+
if (!/^\d+$/.test(String(run.id)) || typeof run.status !== "string") ctx.fail("Workflow run response is invalid.");
|
|
32
|
+
const result = (await ctx.get(`${runs}/${run.id}/jobs?per_page=100`)).json;
|
|
33
|
+
if (!Array.isArray(result?.jobs)) ctx.fail("Workflow jobs response must contain jobs.");
|
|
34
|
+
if (result.jobs.some(job => invalidJob({ job }))) ctx.fail("Workflow jobs response contains an invalid job.");
|
|
35
|
+
return { repo, run, jobs: result.jobs.slice(0, 100), moreJobs: result.total_count > result.jobs.length || result.jobs.length > 100 };
|
|
36
|
+
}
|
|
37
|
+
/** Unknown conclusions remain unknown so wait-until-finished cannot invent completion. */
|
|
38
|
+
function state(run) {
|
|
39
|
+
if (["queued", "in_progress", "waiting", "requested", "pending"].includes(run.status)) return "deploying";
|
|
40
|
+
if (run.status !== "completed") return "unknown";
|
|
41
|
+
if (run.conclusion === "success") return "healthy";
|
|
42
|
+
if (["failure", "timed_out", "action_required", "startup_failure", "stale"].includes(run.conclusion)) return "failing";
|
|
43
|
+
if (["cancelled", "skipped", "neutral"].includes(run.conclusion)) return "stopped";
|
|
44
|
+
return "unknown";
|
|
45
|
+
}
|
|
46
|
+
const failedSteps = job => (Array.isArray(job.steps) ? job.steps : []).filter(s => ["failure", "timed_out", "cancelled"].includes(s.conclusion));
|
|
47
|
+
/** Normalize a run and its jobs; never return the raw workflow/job bodies. */
|
|
48
|
+
async function status(ctx, input) {
|
|
49
|
+
const { run, jobs, moreJobs } = await readRun(ctx, input);
|
|
50
|
+
if (!run) return { platform: PLATFORM, target: input.target, state: "unknown", summary: "No workflow runs found.", items: [], checkedAt: ctx.nowIso() };
|
|
51
|
+
return { platform: PLATFORM, target: input.target, state: state(run), summary: text(`${text(run.name)}: ${text(run.status)}/${text(run.conclusion)}; head_sha=${text(run.head_sha)}${moreJobs ? "; jobs truncated" : ""}`), items: jobs.map(j => ({ id: text(j.id), name: text(j.name), state: text(j.conclusion ?? j.status), detail: text(`status=${text(j.status)}; conclusion=${text(j.conclusion)}; failed steps: ${failedSteps(j).map(s => text(s.name)).join(", ")}`) })), ...(typeof run.html_url === "string" && run.html_url.startsWith("https://github.com/") ? { url: text(run.html_url) } : {}), checkedAt: ctx.nowIso() };
|
|
52
|
+
}
|
|
53
|
+
function addFailedStepLines({ job, add }, _options = {}) {
|
|
54
|
+
let truncated = false;
|
|
55
|
+
for (const step of failedSteps(job)) {
|
|
56
|
+
const message = `Failed step: ${String(step.name ?? "")}`;
|
|
57
|
+
if (message.length > 2000) truncated = true;
|
|
58
|
+
add({ source: text(job.name), level: "error", message: text(message) });
|
|
59
|
+
}
|
|
60
|
+
return truncated;
|
|
61
|
+
}
|
|
62
|
+
function annotationMessage({ annotation }, _options = {}) {
|
|
63
|
+
const location = annotation.path ? `${String(annotation.path)}:${String(annotation.start_line ?? "")}: ` : "";
|
|
64
|
+
return `${location}${String(annotation.message ?? "")}`;
|
|
65
|
+
}
|
|
66
|
+
function addAnnotationLine({ ctx, job, annotation, add }, _options = {}) {
|
|
67
|
+
if (!annotation || typeof annotation !== "object") ctx.fail("Check annotations response contains an invalid entry.");
|
|
68
|
+
const message = annotationMessage({ annotation });
|
|
69
|
+
add({ source: text(job.name), level: text(annotation.annotation_level ?? "error"), message: text(message) });
|
|
70
|
+
return message.length > 2000;
|
|
71
|
+
}
|
|
72
|
+
async function addAnnotationLines({ ctx, repo, job, add }, _options = {}) {
|
|
73
|
+
if (!/^\d+$/.test(String(job.id))) ctx.fail("Workflow job id is invalid.");
|
|
74
|
+
// The API supports at most 100 annotations per page. Read enough to determine overflow, never redirect.
|
|
75
|
+
const response = await ctx.get(`${repo}/check-runs/${job.id}/annotations?per_page=100`);
|
|
76
|
+
if (!Array.isArray(response.json)) ctx.fail("Check annotations response must be an array.");
|
|
77
|
+
let truncated = false;
|
|
78
|
+
for (const annotation of response.json) {
|
|
79
|
+
if (addAnnotationLine({ ctx, job, annotation, add })) truncated = true;
|
|
80
|
+
}
|
|
81
|
+
if (response.truncated || response.json.length >= 100) truncated = true;
|
|
82
|
+
return truncated;
|
|
83
|
+
}
|
|
84
|
+
/** Return failed steps plus bounded check-run annotations; stop fetching once the line cap is full. */
|
|
85
|
+
async function logs(ctx, input) {
|
|
86
|
+
const { repo, jobs, moreJobs } = await readRun(ctx, input);
|
|
87
|
+
const lines = []; let truncated = moreJobs;
|
|
88
|
+
const failed = jobs.filter(j => ["failure", "timed_out", "action_required", "cancelled"].includes(j.conclusion) || failedSteps(j).length);
|
|
89
|
+
const add = line => {
|
|
90
|
+
if (lines.length < input.limit) lines.push(line);
|
|
91
|
+
else truncated = true;
|
|
92
|
+
if (String(line.message).length > 2000) truncated = true;
|
|
93
|
+
};
|
|
94
|
+
// Bound per-job I/O: at most 10 annotation requests plus the run/jobs requests.
|
|
95
|
+
if (failed.length > 10) truncated = true;
|
|
96
|
+
for (const [index, job] of failed.slice(0, 10).entries()) {
|
|
97
|
+
if (addFailedStepLines({ job, add })) truncated = true;
|
|
98
|
+
if (await addAnnotationLines({ ctx, repo, job, add })) truncated = true;
|
|
99
|
+
if (lines.length >= input.limit && index < failed.length - 1) { truncated = true; break; }
|
|
100
|
+
}
|
|
101
|
+
return { platform: PLATFORM, target: input.target, lines, truncated };
|
|
102
|
+
}
|
|
103
|
+
const FULL_SHA = /^[0-9a-f]{40}$/;
|
|
104
|
+
const RUN_LOOKUPS = 5;
|
|
105
|
+
const RUN_LOOKUP_DELAY_MS = 2000;
|
|
106
|
+
/** Host and GitHub clocks differ; a run created this long before our dispatch can still be ours. */
|
|
107
|
+
const CLOCK_SKEW_MS = 30_000;
|
|
108
|
+
/** Branch or tag names only: the dispatch API runs a workflow on a ref, never at a bare commit. */
|
|
109
|
+
function refName(ctx, ref) {
|
|
110
|
+
const value = ref ?? "main";
|
|
111
|
+
if (typeof value !== "string" || value.length > 200 || !/^[A-Za-z0-9._/-]+$/.test(value) || /^[-/.]|[/.]$|\.\.|\/\/|\.lock$/.test(value)) ctx.fail("ref must be a branch or tag name.");
|
|
112
|
+
if (/^[0-9a-fA-F]{40}$/.test(value)) ctx.fail("ref must be a branch or tag name; GitHub cannot dispatch a workflow at a bare commit SHA. Push the commit to a branch first.");
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
/** Find our run: this workflow, this exact SHA, created no earlier than just before the dispatch. At most RUN_LOOKUPS GETs. */
|
|
116
|
+
async function findRun(ctx, workflow, sha, since) {
|
|
117
|
+
for (let attempt = 0; attempt < RUN_LOOKUPS; attempt++) {
|
|
118
|
+
if (attempt > 0) await ctx.sleep(RUN_LOOKUP_DELAY_MS);
|
|
119
|
+
const response = (await ctx.get(`${workflow}/runs?event=workflow_dispatch&head_sha=${sha}&per_page=10`)).json;
|
|
120
|
+
if (!Array.isArray(response?.workflow_runs)) ctx.fail("Workflow runs response must contain workflow_runs.");
|
|
121
|
+
// A run on another SHA is never reported as this deploy, even if it is the newest.
|
|
122
|
+
const run = response.workflow_runs.find(r => r && r.head_sha === sha && /^\d+$/.test(String(r.id)) && Date.parse(r.created_at) >= since);
|
|
123
|
+
if (run) return run;
|
|
124
|
+
}
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
/** Resolve ref -> full SHA, dispatch the deploy workflow pinned to it, then return the run id. O(1) + bounded lookups.
|
|
128
|
+
* The host supplies the repo's deploy workflow (`input.workflowFile`, declared by the deploy-config owner; plugins never
|
|
129
|
+
* import each other). Its dispatch input `expected_sha` makes it refuse to deploy a ref that moved. */
|
|
130
|
+
async function deploy(ctx, input) {
|
|
131
|
+
const repo = base(ctx, input.target);
|
|
132
|
+
const selection = selectWorkflow({ input });
|
|
133
|
+
if (selection.reason) return { platform: PLATFORM, target: input.target, started: false, summary: selection.reason };
|
|
134
|
+
const workflowFile = selection.file;
|
|
135
|
+
const ref = refName(ctx, input.ref);
|
|
136
|
+
const commit = (await ctx.get(`${repo}/commits/${ref.split("/").map(encodeURIComponent).join("/")}`)).json;
|
|
137
|
+
if (typeof commit?.sha !== "string" || !FULL_SHA.test(commit.sha)) ctx.fail(`Could not resolve '${ref}' to a full commit SHA.`);
|
|
138
|
+
const sha = commit.sha;
|
|
139
|
+
const workflow = `${repo}/actions/workflows/${workflowFile}`;
|
|
140
|
+
const since = Date.parse(ctx.nowIso()) - CLOCK_SKEW_MS;
|
|
141
|
+
const dispatched = await ctx.send({ method: "POST", url: `${workflow}/dispatches`, body: { ref, inputs: { expected_sha: sha } } });
|
|
142
|
+
// Newer API versions may answer with the run directly; otherwise (204) look it up by SHA.
|
|
143
|
+
const run = await dispatchedRun({ ctx, dispatched, workflow, sha, since });
|
|
144
|
+
const pinned = `The workflow refuses to deploy if '${ref}' no longer resolves to ${sha} when it starts.`;
|
|
145
|
+
if (!run) return { platform: PLATFORM, target: input.target, started: true, sha, summary: text(`Dispatched ${workflowFile} on '${ref}' at ${sha}, but its run was not visible yet. ${pinned} Check deployment_ops_status with branch '${ref}' in a minute.`) };
|
|
146
|
+
return { platform: PLATFORM, target: input.target, started: true, sha, runId: String(run.id), ...(typeof run.html_url === "string" && run.html_url.startsWith("https://github.com/") ? { url: text(run.html_url) } : {}), summary: text(`Dispatched ${workflowFile} on '${ref}' at ${sha}; run ${run.id}. ${pinned}`) };
|
|
147
|
+
}
|
|
148
|
+
export default { status, logs, deploy };
|
|
149
|
+
|
|
150
|
+
/** Select only a host-declared active workflow; ambiguity is an actionable no-dispatch result. O(files). */
|
|
151
|
+
function selectWorkflow({ input }, _options = {}) {
|
|
152
|
+
const files = (input.workflowFiles ?? []).filter(file => typeof file === "string" && /^[a-zA-Z0-9_-]+\.ya?ml$/.test(file));
|
|
153
|
+
if (input.workflowFile !== undefined) return files.includes(input.workflowFile) ? { file: input.workflowFile }
|
|
154
|
+
: { reason: `Nothing dispatched: workflowFile must match an installed, active declaration. Available: ${files.join(", ") || "none"}.` };
|
|
155
|
+
if (files.length === 1) return { file: files[0] };
|
|
156
|
+
return { reason: files.length ? `Nothing dispatched: choose workflowFile from ${files.join(", ")}.`
|
|
157
|
+
: "Nothing dispatched: no active plugin declares a deployment workflow. Install or enable the hosting plugin first." };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** New API versions return a run id directly; older ones require the existing bounded lookup. */
|
|
161
|
+
async function dispatchedRun({ ctx, dispatched, workflow, sha, since }, _options = {}) {
|
|
162
|
+
const directId = dispatched.json?.workflow_run_id;
|
|
163
|
+
return directId !== undefined && /^\d+$/.test(String(directId)) ? { id: directId, html_url: dispatched.json.html_url } : findRun(ctx, workflow, sha, since);
|
|
164
|
+
}
|