tinybeeai 0.0.0-stage → 0.16.1
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/.railway/railway.ts +83 -0
- package/.railway/tsconfig.json +13 -0
- package/AGENTS.md +102 -0
- package/Dockerfile +108 -0
- package/LICENSE +21 -0
- package/README.md +275 -2
- package/bin/localdost.js +29 -0
- package/bin/tinybeeai.js +28 -0
- package/compose.yml +63 -0
- package/demo/payloads/ci_failure.json +46 -0
- package/demo/payloads/outage.json +18 -0
- package/demo/payloads/pr_comment_edited.json +50 -0
- package/demo/payloads/pr_merged.json +80 -0
- package/demo/payloads/pr_opened.json +69 -0
- package/demo/payloads/pr_synchronize.json +69 -0
- package/demo/payloads/ticket_created.json +138 -0
- package/demo/sample-app/playwright.config.ts +20 -0
- package/demo/sample-app/server.ts +163 -0
- package/demo/sample-app/tests/checkout.spec.ts +13 -0
- package/demo/sample-app/tests/smoke.spec.ts +8 -0
- package/dist/agent/approval-retries.js +59 -0
- package/dist/agent/approval-retries.js.map +1 -0
- package/dist/agent/approvals.js +390 -0
- package/dist/agent/approvals.js.map +1 -0
- package/dist/agent/chat-bridge.js +154 -0
- package/dist/agent/chat-bridge.js.map +1 -0
- package/dist/agent/chat-lead.js +475 -0
- package/dist/agent/chat-lead.js.map +1 -0
- package/dist/agent/chat-loop.js +106 -0
- package/dist/agent/chat-loop.js.map +1 -0
- package/dist/agent/chat-mcp.js +49 -0
- package/dist/agent/chat-mcp.js.map +1 -0
- package/dist/agent/chat-tool-blocks.js +453 -0
- package/dist/agent/chat-tool-blocks.js.map +1 -0
- package/dist/agent/chat-view.js +94 -0
- package/dist/agent/chat-view.js.map +1 -0
- package/dist/agent/chat-views.js +781 -0
- package/dist/agent/chat-views.js.map +1 -0
- package/dist/agent/chat.js +1703 -0
- package/dist/agent/chat.js.map +1 -0
- package/dist/agent/destinations.js +150 -0
- package/dist/agent/destinations.js.map +1 -0
- package/dist/agent/llm-cli.js +451 -0
- package/dist/agent/llm-cli.js.map +1 -0
- package/dist/agent/llm-codex.js +493 -0
- package/dist/agent/llm-codex.js.map +1 -0
- package/dist/agent/llm-openai.js +211 -0
- package/dist/agent/llm-openai.js.map +1 -0
- package/dist/agent/llm.js +1464 -0
- package/dist/agent/llm.js.map +1 -0
- package/dist/agent/memory.js +33 -0
- package/dist/agent/memory.js.map +1 -0
- package/dist/agent/mock-views.js +93 -0
- package/dist/agent/mock-views.js.map +1 -0
- package/dist/agent/model-availability.js +207 -0
- package/dist/agent/model-availability.js.map +1 -0
- package/dist/agent/model-errors.js +188 -0
- package/dist/agent/model-errors.js.map +1 -0
- package/dist/agent/model-guard.js +311 -0
- package/dist/agent/model-guard.js.map +1 -0
- package/dist/agent/prompts.js +374 -0
- package/dist/agent/prompts.js.map +1 -0
- package/dist/agent/qa-lenses/qa-auth.md +32 -0
- package/dist/agent/qa-lenses/qa-check-then-act.md +33 -0
- package/dist/agent/qa-lenses/qa-control-has-no-effect.md +32 -0
- package/dist/agent/qa-lenses/qa-dates.md +31 -0
- package/dist/agent/qa-lenses/qa-double-submit.md +34 -0
- package/dist/agent/qa-lenses/qa-feature-flag.md +29 -0
- package/dist/agent/qa-lenses/qa-migration.md +35 -0
- package/dist/agent/qa-lenses/qa-money.md +38 -0
- package/dist/agent/qa-lenses/qa-response-shape-break.md +33 -0
- package/dist/agent/qa-lenses/qa-validation.md +29 -0
- package/dist/agent/qa-prompts.js +660 -0
- package/dist/agent/qa-prompts.js.map +1 -0
- package/dist/agent/reasoning.js +55 -0
- package/dist/agent/reasoning.js.map +1 -0
- package/dist/agent/redact.js +48 -0
- package/dist/agent/redact.js.map +1 -0
- package/dist/agent/rules.js +927 -0
- package/dist/agent/rules.js.map +1 -0
- package/dist/agent/runs.js +440 -0
- package/dist/agent/runs.js.map +1 -0
- package/dist/agent/send-retry.js +62 -0
- package/dist/agent/send-retry.js.map +1 -0
- package/dist/agent/team-notes.js +288 -0
- package/dist/agent/team-notes.js.map +1 -0
- package/dist/agent/token-meter.js +31 -0
- package/dist/agent/token-meter.js.map +1 -0
- package/dist/agent/triage.js +506 -0
- package/dist/agent/triage.js.map +1 -0
- package/dist/api.js +1047 -0
- package/dist/api.js.map +1 -0
- package/dist/approval-targets.js +107 -0
- package/dist/approval-targets.js.map +1 -0
- package/dist/bee/cloudflared.js +108 -0
- package/dist/bee/cloudflared.js.map +1 -0
- package/dist/bee/health.js +99 -0
- package/dist/bee/health.js.map +1 -0
- package/dist/bee/hive-client.js +75 -0
- package/dist/bee/hive-client.js.map +1 -0
- package/dist/bee/identity.js +39 -0
- package/dist/bee/identity.js.map +1 -0
- package/dist/bee/models.js +510 -0
- package/dist/bee/models.js.map +1 -0
- package/dist/bee/pairing.js +351 -0
- package/dist/bee/pairing.js.map +1 -0
- package/dist/bee/relay-client.js +50 -0
- package/dist/bee/relay-client.js.map +1 -0
- package/dist/bee/routes.js +214 -0
- package/dist/bee/routes.js.map +1 -0
- package/dist/bee/setup-page.js +223 -0
- package/dist/bee/setup-page.js.map +1 -0
- package/dist/bee/state.js +41 -0
- package/dist/bee/state.js.map +1 -0
- package/dist/bee/status.js +117 -0
- package/dist/bee/status.js.map +1 -0
- package/dist/bee/tunnel-manager.js +245 -0
- package/dist/bee/tunnel-manager.js.map +1 -0
- package/dist/bee/updater.js +268 -0
- package/dist/bee/updater.js.map +1 -0
- package/dist/boot.js +41 -0
- package/dist/boot.js.map +1 -0
- package/dist/call/index.js +145 -0
- package/dist/call/index.js.map +1 -0
- package/dist/case-folders.js +294 -0
- package/dist/case-folders.js.map +1 -0
- package/dist/cases.js +554 -0
- package/dist/cases.js.map +1 -0
- package/dist/chat-blocks.js +709 -0
- package/dist/chat-blocks.js.map +1 -0
- package/dist/chat-parse-util.js +38 -0
- package/dist/chat-parse-util.js.map +1 -0
- package/dist/chat-results.js +96 -0
- package/dist/chat-results.js.map +1 -0
- package/dist/chat-stats.js +193 -0
- package/dist/chat-stats.js.map +1 -0
- package/dist/chat-view-specs.js +186 -0
- package/dist/chat-view-specs.js.map +1 -0
- package/dist/cli/doctor-ops.js +361 -0
- package/dist/cli/doctor-ops.js.map +1 -0
- package/dist/cli/doctor.js +289 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/dogfood.js +70 -0
- package/dist/cli/dogfood.js.map +1 -0
- package/dist/cli/env-file.js +34 -0
- package/dist/cli/env-file.js.map +1 -0
- package/dist/cli/eval-chat-cases.js +359 -0
- package/dist/cli/eval-chat-cases.js.map +1 -0
- package/dist/cli/eval-chat-results.js +85 -0
- package/dist/cli/eval-chat-results.js.map +1 -0
- package/dist/cli/eval-chat.js +508 -0
- package/dist/cli/eval-chat.js.map +1 -0
- package/dist/cli/eval-designer.js +67 -0
- package/dist/cli/eval-designer.js.map +1 -0
- package/dist/cli/eval-qa-review.js +284 -0
- package/dist/cli/eval-qa-review.js.map +1 -0
- package/dist/cli/golden-export.js +35 -0
- package/dist/cli/golden-export.js.map +1 -0
- package/dist/cli/index.js +90 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.js +746 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/scan-repo.js +199 -0
- package/dist/cli/scan-repo.js.map +1 -0
- package/dist/cli/seed.js +108 -0
- package/dist/cli/seed.js.map +1 -0
- package/dist/cli/tinybee.js +247 -0
- package/dist/cli/tinybee.js.map +1 -0
- package/dist/config.js +138 -0
- package/dist/config.js.map +1 -0
- package/dist/connect/aivision-local.js +833 -0
- package/dist/connect/aivision-local.js.map +1 -0
- package/dist/connect/aivision-token.js +84 -0
- package/dist/connect/aivision-token.js.map +1 -0
- package/dist/connect/crypto.js +65 -0
- package/dist/connect/crypto.js.map +1 -0
- package/dist/connect/deps.js +110 -0
- package/dist/connect/deps.js.map +1 -0
- package/dist/connect/engines.js +144 -0
- package/dist/connect/engines.js.map +1 -0
- package/dist/connect/github-access.js +74 -0
- package/dist/connect/github-access.js.map +1 -0
- package/dist/connect/github-app.js +129 -0
- package/dist/connect/github-app.js.map +1 -0
- package/dist/connect/index.js +9 -0
- package/dist/connect/index.js.map +1 -0
- package/dist/connect/model-choices.js +201 -0
- package/dist/connect/model-choices.js.map +1 -0
- package/dist/connect/providers.js +900 -0
- package/dist/connect/providers.js.map +1 -0
- package/dist/connect/readiness.js +232 -0
- package/dist/connect/readiness.js.map +1 -0
- package/dist/connect/redact.js +16 -0
- package/dist/connect/redact.js.map +1 -0
- package/dist/connect/registry.js +406 -0
- package/dist/connect/registry.js.map +1 -0
- package/dist/connect/store.js +283 -0
- package/dist/connect/store.js.map +1 -0
- package/dist/connect/tunnel.js +134 -0
- package/dist/connect/tunnel.js.map +1 -0
- package/dist/connect/types.js +8 -0
- package/dist/connect/types.js.map +1 -0
- package/dist/connections/index.js +5 -0
- package/dist/connections/index.js.map +1 -0
- package/dist/connections/manager.js +223 -0
- package/dist/connections/manager.js.map +1 -0
- package/dist/connections/presets.js +264 -0
- package/dist/connections/presets.js.map +1 -0
- package/dist/connections/store.js +132 -0
- package/dist/connections/store.js.map +1 -0
- package/dist/connections/tools.js +206 -0
- package/dist/connections/tools.js.map +1 -0
- package/dist/db-schema.js +355 -0
- package/dist/db-schema.js.map +1 -0
- package/dist/db.js +379 -0
- package/dist/db.js.map +1 -0
- package/dist/delivery.js +49 -0
- package/dist/delivery.js.map +1 -0
- package/dist/deploy-wait.js +701 -0
- package/dist/deploy-wait.js.map +1 -0
- package/dist/disk.js +243 -0
- package/dist/disk.js.map +1 -0
- package/dist/dot-health.js +966 -0
- package/dist/dot-health.js.map +1 -0
- package/dist/dot-remove.js +246 -0
- package/dist/dot-remove.js.map +1 -0
- package/dist/events.js +38 -0
- package/dist/events.js.map +1 -0
- package/dist/evidence-pack.js +423 -0
- package/dist/evidence-pack.js.map +1 -0
- package/dist/flows/answers.js +289 -0
- package/dist/flows/answers.js.map +1 -0
- package/dist/flows/change.js +811 -0
- package/dist/flows/change.js.map +1 -0
- package/dist/flows/checkStatus.js +524 -0
- package/dist/flows/checkStatus.js.map +1 -0
- package/dist/flows/ci.js +236 -0
- package/dist/flows/ci.js.map +1 -0
- package/dist/flows/ciFailure.js +76 -0
- package/dist/flows/ciFailure.js.map +1 -0
- package/dist/flows/common.js +522 -0
- package/dist/flows/common.js.map +1 -0
- package/dist/flows/default-branch.js +29 -0
- package/dist/flows/default-branch.js.map +1 -0
- package/dist/flows/defectDraft.js +447 -0
- package/dist/flows/defectDraft.js.map +1 -0
- package/dist/flows/defects.js +623 -0
- package/dist/flows/defects.js.map +1 -0
- package/dist/flows/digest.js +295 -0
- package/dist/flows/digest.js.map +1 -0
- package/dist/flows/impact.js +82 -0
- package/dist/flows/impact.js.map +1 -0
- package/dist/flows/index.js +23 -0
- package/dist/flows/index.js.map +1 -0
- package/dist/flows/intent-sources.js +72 -0
- package/dist/flows/intent-sources.js.map +1 -0
- package/dist/flows/mergeCases.js +169 -0
- package/dist/flows/mergeCases.js.map +1 -0
- package/dist/flows/nightlyRun.js +82 -0
- package/dist/flows/nightlyRun.js.map +1 -0
- package/dist/flows/outage.js +64 -0
- package/dist/flows/outage.js.map +1 -0
- package/dist/flows/postMerge.js +5 -0
- package/dist/flows/postMerge.js.map +1 -0
- package/dist/flows/prCheck.js +5 -0
- package/dist/flows/prCheck.js.map +1 -0
- package/dist/flows/prTarget.js +66 -0
- package/dist/flows/prTarget.js.map +1 -0
- package/dist/flows/qa.js +363 -0
- package/dist/flows/qa.js.map +1 -0
- package/dist/flows/questions.js +160 -0
- package/dist/flows/questions.js.map +1 -0
- package/dist/flows/recovery.js +64 -0
- package/dist/flows/recovery.js.map +1 -0
- package/dist/flows/rerun.js +50 -0
- package/dist/flows/rerun.js.map +1 -0
- package/dist/flows/testDesign.js +294 -0
- package/dist/flows/testDesign.js.map +1 -0
- package/dist/flows/testDesignJob.js +46 -0
- package/dist/flows/testDesignJob.js.map +1 -0
- package/dist/flows/testRepo.js +228 -0
- package/dist/flows/testRepo.js.map +1 -0
- package/dist/flows/testSavings.js +381 -0
- package/dist/flows/testSavings.js.map +1 -0
- package/dist/flows/testStatus.js +343 -0
- package/dist/flows/testStatus.js.map +1 -0
- package/dist/flows/ticketContext.js +425 -0
- package/dist/flows/ticketContext.js.map +1 -0
- package/dist/flows/ticketReview.js +249 -0
- package/dist/flows/ticketReview.js.map +1 -0
- package/dist/flows/ticks.js +73 -0
- package/dist/flows/ticks.js.map +1 -0
- package/dist/hive/admin.js +122 -0
- package/dist/hive/admin.js.map +1 -0
- package/dist/hive/auth.js +130 -0
- package/dist/hive/auth.js.map +1 -0
- package/dist/hive/billing.js +278 -0
- package/dist/hive/billing.js.map +1 -0
- package/dist/hive/config.js +63 -0
- package/dist/hive/config.js.map +1 -0
- package/dist/hive/db.js +186 -0
- package/dist/hive/db.js.map +1 -0
- package/dist/hive/embed.js +76 -0
- package/dist/hive/embed.js.map +1 -0
- package/dist/hive/errors.js +17 -0
- package/dist/hive/errors.js.map +1 -0
- package/dist/hive/google.js +75 -0
- package/dist/hive/google.js.map +1 -0
- package/dist/hive/index.js +25 -0
- package/dist/hive/index.js.map +1 -0
- package/dist/hive/jwt.js +57 -0
- package/dist/hive/jwt.js.map +1 -0
- package/dist/hive/keys.js +61 -0
- package/dist/hive/keys.js.map +1 -0
- package/dist/hive/mail.js +27 -0
- package/dist/hive/mail.js.map +1 -0
- package/dist/hive/native-push.js +255 -0
- package/dist/hive/native-push.js.map +1 -0
- package/dist/hive/pairing.js +342 -0
- package/dist/hive/pairing.js.map +1 -0
- package/dist/hive/presence.js +121 -0
- package/dist/hive/presence.js.map +1 -0
- package/dist/hive/push.js +185 -0
- package/dist/hive/push.js.map +1 -0
- package/dist/hive/relay.js +205 -0
- package/dist/hive/relay.js.map +1 -0
- package/dist/hive/routes.js +228 -0
- package/dist/hive/routes.js.map +1 -0
- package/dist/hive/server.js +12 -0
- package/dist/hive/server.js.map +1 -0
- package/dist/hive/session.js +28 -0
- package/dist/hive/session.js.map +1 -0
- package/dist/hive/tenants.js +287 -0
- package/dist/hive/tenants.js.map +1 -0
- package/dist/hive/tunnel/cloudflare.js +160 -0
- package/dist/hive/tunnel/cloudflare.js.map +1 -0
- package/dist/hive/tunnel/mock.js +52 -0
- package/dist/hive/tunnel/mock.js.map +1 -0
- package/dist/hive/tunnel/provider.js +19 -0
- package/dist/hive/tunnel/provider.js.map +1 -0
- package/dist/hive/updates.js +61 -0
- package/dist/hive/updates.js.map +1 -0
- package/dist/http-error.js +11 -0
- package/dist/http-error.js.map +1 -0
- package/dist/impact/classify.js +226 -0
- package/dist/impact/classify.js.map +1 -0
- package/dist/impact/find.js +259 -0
- package/dist/impact/find.js.map +1 -0
- package/dist/impact/pr.js +54 -0
- package/dist/impact/pr.js.map +1 -0
- package/dist/impact/store.js +365 -0
- package/dist/impact/store.js.map +1 -0
- package/dist/impact/text.js +158 -0
- package/dist/impact/text.js.map +1 -0
- package/dist/index.js +288 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/adf.js +397 -0
- package/dist/integrations/adf.js.map +1 -0
- package/dist/integrations/bot-marker.js +103 -0
- package/dist/integrations/bot-marker.js.map +1 -0
- package/dist/integrations/github-issues.js +705 -0
- package/dist/integrations/github-issues.js.map +1 -0
- package/dist/integrations/github.js +1284 -0
- package/dist/integrations/github.js.map +1 -0
- package/dist/integrations/issue-create.js +60 -0
- package/dist/integrations/issue-create.js.map +1 -0
- package/dist/integrations/jira.js +279 -0
- package/dist/integrations/jira.js.map +1 -0
- package/dist/integrations/linear.js +273 -0
- package/dist/integrations/linear.js.map +1 -0
- package/dist/integrations/own-identity-warm.js +47 -0
- package/dist/integrations/own-identity-warm.js.map +1 -0
- package/dist/integrations/own-identity.js +39 -0
- package/dist/integrations/own-identity.js.map +1 -0
- package/dist/integrations/status-change.js +42 -0
- package/dist/integrations/status-change.js.map +1 -0
- package/dist/lead-defects.js +202 -0
- package/dist/lead-defects.js.map +1 -0
- package/dist/lead-digest.js +333 -0
- package/dist/lead-digest.js.map +1 -0
- package/dist/lead-release.js +286 -0
- package/dist/lead-release.js.map +1 -0
- package/dist/lead.js +328 -0
- package/dist/lead.js.map +1 -0
- package/dist/legacy-names.js +51 -0
- package/dist/legacy-names.js.map +1 -0
- package/dist/limits.js +44 -0
- package/dist/limits.js.map +1 -0
- package/dist/link-trust.js +224 -0
- package/dist/link-trust.js.map +1 -0
- package/dist/links.js +219 -0
- package/dist/links.js.map +1 -0
- package/dist/loopback.js +35 -0
- package/dist/loopback.js.map +1 -0
- package/dist/model-cap.js +60 -0
- package/dist/model-cap.js.map +1 -0
- package/dist/model-usage.js +173 -0
- package/dist/model-usage.js.map +1 -0
- package/dist/monitor/backfill.js +39 -0
- package/dist/monitor/backfill.js.map +1 -0
- package/dist/monitor/browser-probe.js +198 -0
- package/dist/monitor/browser-probe.js.map +1 -0
- package/dist/monitor/catch-up.js +31 -0
- package/dist/monitor/catch-up.js.map +1 -0
- package/dist/monitor/pollers.js +1606 -0
- package/dist/monitor/pollers.js.map +1 -0
- package/dist/monitor/probes.js +178 -0
- package/dist/monitor/probes.js.map +1 -0
- package/dist/monitor/self-health.js +228 -0
- package/dist/monitor/self-health.js.map +1 -0
- package/dist/monitor/statuspage.js +105 -0
- package/dist/monitor/statuspage.js.map +1 -0
- package/dist/nightly-scheduler.js +127 -0
- package/dist/nightly-scheduler.js.map +1 -0
- package/dist/nightly.js +141 -0
- package/dist/nightly.js.map +1 -0
- package/dist/notify/headline.js +114 -0
- package/dist/notify/headline.js.map +1 -0
- package/dist/notify/push.js +174 -0
- package/dist/notify/push.js.map +1 -0
- package/dist/object-cards.js +585 -0
- package/dist/object-cards.js.map +1 -0
- package/dist/object-links.js +47 -0
- package/dist/object-links.js.map +1 -0
- package/dist/onboarding-checks.js +141 -0
- package/dist/onboarding-checks.js.map +1 -0
- package/dist/onboarding.js +401 -0
- package/dist/onboarding.js.map +1 -0
- package/dist/path-filters.js +62 -0
- package/dist/path-filters.js.map +1 -0
- package/dist/phone/alerts.js +67 -0
- package/dist/phone/alerts.js.map +1 -0
- package/dist/phone/auth.js +240 -0
- package/dist/phone/auth.js.map +1 -0
- package/dist/phone/pairing.js +442 -0
- package/dist/phone/pairing.js.map +1 -0
- package/dist/phone/prefs.js +27 -0
- package/dist/phone/prefs.js.map +1 -0
- package/dist/phone/tailscale.js +161 -0
- package/dist/phone/tailscale.js.map +1 -0
- package/dist/pr-scope.js +441 -0
- package/dist/pr-scope.js.map +1 -0
- package/dist/proc.js +156 -0
- package/dist/proc.js.map +1 -0
- package/dist/profile/cache.js +108 -0
- package/dist/profile/cache.js.map +1 -0
- package/dist/profile/ci.js +216 -0
- package/dist/profile/ci.js.map +1 -0
- package/dist/profile/index.js +8 -0
- package/dist/profile/index.js.map +1 -0
- package/dist/profile/job.js +135 -0
- package/dist/profile/job.js.map +1 -0
- package/dist/profile/queries/go-tags.scm +47 -0
- package/dist/profile/queries/java-tags.scm +25 -0
- package/dist/profile/queries/javascript-tags.scm +104 -0
- package/dist/profile/queries/python-tags.scm +19 -0
- package/dist/profile/queries/typescript-tags.scm +28 -0
- package/dist/profile/scan-thread.js +42 -0
- package/dist/profile/scan-thread.js.map +1 -0
- package/dist/profile/scan-worker.js +16 -0
- package/dist/profile/scan-worker.js.map +1 -0
- package/dist/profile/scan.js +351 -0
- package/dist/profile/scan.js.map +1 -0
- package/dist/profile/stack.js +528 -0
- package/dist/profile/stack.js.map +1 -0
- package/dist/profile/store.js +35 -0
- package/dist/profile/store.js.map +1 -0
- package/dist/profile/tests.js +399 -0
- package/dist/profile/tests.js.map +1 -0
- package/dist/queue.js +350 -0
- package/dist/queue.js.map +1 -0
- package/dist/refs.js +756 -0
- package/dist/refs.js.map +1 -0
- package/dist/release-notes.js +272 -0
- package/dist/release-notes.js.map +1 -0
- package/dist/reports/build.js +243 -0
- package/dist/reports/build.js.map +1 -0
- package/dist/reports/index.js +211 -0
- package/dist/reports/index.js.map +1 -0
- package/dist/reports/store.js +150 -0
- package/dist/reports/store.js.map +1 -0
- package/dist/reports/ticket-titles.js +124 -0
- package/dist/reports/ticket-titles.js.map +1 -0
- package/dist/reports/trace-map.js +1278 -0
- package/dist/reports/trace-map.js.map +1 -0
- package/dist/reports/traceability.js +422 -0
- package/dist/reports/traceability.js.map +1 -0
- package/dist/repos/mirror.js +356 -0
- package/dist/repos/mirror.js.map +1 -0
- package/dist/repos-github.js +108 -0
- package/dist/repos-github.js.map +1 -0
- package/dist/repos.js +891 -0
- package/dist/repos.js.map +1 -0
- package/dist/review/backend-anthropic.js +90 -0
- package/dist/review/backend-anthropic.js.map +1 -0
- package/dist/review/backend-cli.js +248 -0
- package/dist/review/backend-cli.js.map +1 -0
- package/dist/review/backend-codex.js +81 -0
- package/dist/review/backend-codex.js.map +1 -0
- package/dist/review/backend-mock.js +686 -0
- package/dist/review/backend-mock.js.map +1 -0
- package/dist/review/backend-only.js +168 -0
- package/dist/review/backend-only.js.map +1 -0
- package/dist/review/backend.js +105 -0
- package/dist/review/backend.js.map +1 -0
- package/dist/review/baseline/render-v1.js +483 -0
- package/dist/review/baseline/render-v1.js.map +1 -0
- package/dist/review/change-summary.js +197 -0
- package/dist/review/change-summary.js.map +1 -0
- package/dist/review/changelog.js +40 -0
- package/dist/review/changelog.js.map +1 -0
- package/dist/review/check.js +494 -0
- package/dist/review/check.js.map +1 -0
- package/dist/review/checkout.js +185 -0
- package/dist/review/checkout.js.map +1 -0
- package/dist/review/classify.js +178 -0
- package/dist/review/classify.js.map +1 -0
- package/dist/review/coverage.js +347 -0
- package/dist/review/coverage.js.map +1 -0
- package/dist/review/decision-eval.js +121 -0
- package/dist/review/decision-eval.js.map +1 -0
- package/dist/review/deliver.js +104 -0
- package/dist/review/deliver.js.map +1 -0
- package/dist/review/designer-eval.js +280 -0
- package/dist/review/designer-eval.js.map +1 -0
- package/dist/review/dogfood-digest.js +136 -0
- package/dist/review/dogfood-digest.js.map +1 -0
- package/dist/review/dogfood-eval.js +486 -0
- package/dist/review/dogfood-eval.js.map +1 -0
- package/dist/review/environment.js +109 -0
- package/dist/review/environment.js.map +1 -0
- package/dist/review/eval.js +405 -0
- package/dist/review/eval.js.map +1 -0
- package/dist/review/glob.js +110 -0
- package/dist/review/glob.js.map +1 -0
- package/dist/review/golden-export.js +219 -0
- package/dist/review/golden-export.js.map +1 -0
- package/dist/review/impact.js +406 -0
- package/dist/review/impact.js.map +1 -0
- package/dist/review/imports.js +542 -0
- package/dist/review/imports.js.map +1 -0
- package/dist/review/index.js +512 -0
- package/dist/review/index.js.map +1 -0
- package/dist/review/intent.js +128 -0
- package/dist/review/intent.js.map +1 -0
- package/dist/review/lenses.js +167 -0
- package/dist/review/lenses.js.map +1 -0
- package/dist/review/linked-tickets.js +293 -0
- package/dist/review/linked-tickets.js.map +1 -0
- package/dist/review/merge.js +370 -0
- package/dist/review/merge.js.map +1 -0
- package/dist/review/on-demand.js +218 -0
- package/dist/review/on-demand.js.map +1 -0
- package/dist/review/prompt-eval.js +299 -0
- package/dist/review/prompt-eval.js.map +1 -0
- package/dist/review/prompt-variants.js +37 -0
- package/dist/review/prompt-variants.js.map +1 -0
- package/dist/review/readability-eval.js +217 -0
- package/dist/review/readability-eval.js.map +1 -0
- package/dist/review/readability.js +295 -0
- package/dist/review/readability.js.map +1 -0
- package/dist/review/render.js +2409 -0
- package/dist/review/render.js.map +1 -0
- package/dist/review/repo.js +194 -0
- package/dist/review/repo.js.map +1 -0
- package/dist/review/report-plan.js +90 -0
- package/dist/review/report-plan.js.map +1 -0
- package/dist/review/routes.js +195 -0
- package/dist/review/routes.js.map +1 -0
- package/dist/review/run-summary.js +34 -0
- package/dist/review/run-summary.js.map +1 -0
- package/dist/review/run.js +245 -0
- package/dist/review/run.js.map +1 -0
- package/dist/review/scope.js +454 -0
- package/dist/review/scope.js.map +1 -0
- package/dist/review/security.js +432 -0
- package/dist/review/security.js.map +1 -0
- package/dist/review/signals.js +550 -0
- package/dist/review/signals.js.map +1 -0
- package/dist/review/specs.js +150 -0
- package/dist/review/specs.js.map +1 -0
- package/dist/review/ste.js +133 -0
- package/dist/review/ste.js.map +1 -0
- package/dist/review/test-repo-file.js +18 -0
- package/dist/review/test-repo-file.js.map +1 -0
- package/dist/review/test-repo-lines.js +53 -0
- package/dist/review/test-repo-lines.js.map +1 -0
- package/dist/review/ticket-on-demand.js +232 -0
- package/dist/review/ticket-on-demand.js.map +1 -0
- package/dist/review/yaml.js +298 -0
- package/dist/review/yaml.js.map +1 -0
- package/dist/room.js +361 -0
- package/dist/room.js.map +1 -0
- package/dist/routes/activity.js +219 -0
- package/dist/routes/activity.js.map +1 -0
- package/dist/routes/call.js +29 -0
- package/dist/routes/call.js.map +1 -0
- package/dist/routes/cards.js +40 -0
- package/dist/routes/cards.js.map +1 -0
- package/dist/routes/cases.js +133 -0
- package/dist/routes/cases.js.map +1 -0
- package/dist/routes/connect.js +268 -0
- package/dist/routes/connect.js.map +1 -0
- package/dist/routes/connection-presets.js +32 -0
- package/dist/routes/connection-presets.js.map +1 -0
- package/dist/routes/connections.js +177 -0
- package/dist/routes/connections.js.map +1 -0
- package/dist/routes/disk.js +19 -0
- package/dist/routes/disk.js.map +1 -0
- package/dist/routes/docs.js +71 -0
- package/dist/routes/docs.js.map +1 -0
- package/dist/routes/dot-suggest.js +1002 -0
- package/dist/routes/dot-suggest.js.map +1 -0
- package/dist/routes/evidence-pack.js +16 -0
- package/dist/routes/evidence-pack.js.map +1 -0
- package/dist/routes/health.js +121 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/impact.js +48 -0
- package/dist/routes/impact.js.map +1 -0
- package/dist/routes/lead.js +50 -0
- package/dist/routes/lead.js.map +1 -0
- package/dist/routes/links.js +13 -0
- package/dist/routes/links.js.map +1 -0
- package/dist/routes/markdown.js +62 -0
- package/dist/routes/markdown.js.map +1 -0
- package/dist/routes/onboarding.js +47 -0
- package/dist/routes/onboarding.js.map +1 -0
- package/dist/routes/phone.js +231 -0
- package/dist/routes/phone.js.map +1 -0
- package/dist/routes/release-notes.js +30 -0
- package/dist/routes/release-notes.js.map +1 -0
- package/dist/routes/reports.js +102 -0
- package/dist/routes/reports.js.map +1 -0
- package/dist/routes/repos.js +91 -0
- package/dist/routes/repos.js.map +1 -0
- package/dist/routes/review.js +283 -0
- package/dist/routes/review.js.map +1 -0
- package/dist/routes/roles.js +114 -0
- package/dist/routes/roles.js.map +1 -0
- package/dist/routes/room.js +21 -0
- package/dist/routes/room.js.map +1 -0
- package/dist/routes/runner.js +156 -0
- package/dist/routes/runner.js.map +1 -0
- package/dist/routes/runs.js +84 -0
- package/dist/routes/runs.js.map +1 -0
- package/dist/routes/setup.js +18 -0
- package/dist/routes/setup.js.map +1 -0
- package/dist/routes/test-runs.js +220 -0
- package/dist/routes/test-runs.js.map +1 -0
- package/dist/routes/ticket-status.js +17 -0
- package/dist/routes/ticket-status.js.map +1 -0
- package/dist/routes/traceability.js +143 -0
- package/dist/routes/traceability.js.map +1 -0
- package/dist/routes/true.js +142 -0
- package/dist/routes/true.js.map +1 -0
- package/dist/routes/updates.js +70 -0
- package/dist/routes/updates.js.map +1 -0
- package/dist/runner/adapters/index.js +12 -0
- package/dist/runner/adapters/index.js.map +1 -0
- package/dist/runner/adapters/playwright.js +68 -0
- package/dist/runner/adapters/playwright.js.map +1 -0
- package/dist/runner/aivision/cases.js +172 -0
- package/dist/runner/aivision/cases.js.map +1 -0
- package/dist/runner/aivision/cli.js +201 -0
- package/dist/runner/aivision/cli.js.map +1 -0
- package/dist/runner/aivision/index.js +281 -0
- package/dist/runner/aivision/index.js.map +1 -0
- package/dist/runner/aivision/server.js +394 -0
- package/dist/runner/aivision/server.js.map +1 -0
- package/dist/runner/aivision/shared.js +140 -0
- package/dist/runner/aivision/shared.js.map +1 -0
- package/dist/runner/artifact-cap.js +189 -0
- package/dist/runner/artifact-cap.js.map +1 -0
- package/dist/runner/artifacts.js +433 -0
- package/dist/runner/artifacts.js.map +1 -0
- package/dist/runner/command.js +564 -0
- package/dist/runner/command.js.map +1 -0
- package/dist/runner/copyfiles.js +221 -0
- package/dist/runner/copyfiles.js.map +1 -0
- package/dist/runner/engines.js +32 -0
- package/dist/runner/engines.js.map +1 -0
- package/dist/runner/evidence.js +114 -0
- package/dist/runner/evidence.js.map +1 -0
- package/dist/runner/host-env.js +160 -0
- package/dist/runner/host-env.js.map +1 -0
- package/dist/runner/index.js +1221 -0
- package/dist/runner/index.js.map +1 -0
- package/dist/runner/observe.js +603 -0
- package/dist/runner/observe.js.map +1 -0
- package/dist/runner/playwright-json.js +108 -0
- package/dist/runner/playwright-json.js.map +1 -0
- package/dist/runner/pwgen/exec.js +192 -0
- package/dist/runner/pwgen/exec.js.map +1 -0
- package/dist/runner/pwgen/explore.js +111 -0
- package/dist/runner/pwgen/explore.js.map +1 -0
- package/dist/runner/pwgen/index.js +423 -0
- package/dist/runner/pwgen/index.js.map +1 -0
- package/dist/runner/pwgen/propose.js +69 -0
- package/dist/runner/pwgen/propose.js.map +1 -0
- package/dist/runner/pwgen/source.js +632 -0
- package/dist/runner/pwgen/source.js.map +1 -0
- package/dist/runner/setup.js +222 -0
- package/dist/runner/setup.js.map +1 -0
- package/dist/runner/step-reporter.js +107 -0
- package/dist/runner/step-reporter.js.map +1 -0
- package/dist/runner/test-repo.js +326 -0
- package/dist/runner/test-repo.js.map +1 -0
- package/dist/runner/typed-value.js +46 -0
- package/dist/runner/typed-value.js.map +1 -0
- package/dist/runner/verify.js +133 -0
- package/dist/runner/verify.js.map +1 -0
- package/dist/runner/worktree.js +349 -0
- package/dist/runner/worktree.js.map +1 -0
- package/dist/sandbox.js +61 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/server.js +262 -0
- package/dist/server.js.map +1 -0
- package/dist/shadow-week.js +126 -0
- package/dist/shadow-week.js.map +1 -0
- package/dist/shutdown.js +92 -0
- package/dist/shutdown.js.map +1 -0
- package/dist/slack/index.js +645 -0
- package/dist/slack/index.js.map +1 -0
- package/dist/stages/config.js +147 -0
- package/dist/stages/config.js.map +1 -0
- package/dist/stages/index.js +41 -0
- package/dist/stages/index.js.map +1 -0
- package/dist/suites.js +293 -0
- package/dist/suites.js.map +1 -0
- package/dist/target-url.js +116 -0
- package/dist/target-url.js.map +1 -0
- package/dist/test-runs.js +662 -0
- package/dist/test-runs.js.map +1 -0
- package/dist/ticket-keys.js +97 -0
- package/dist/ticket-keys.js.map +1 -0
- package/dist/ticket-supersede.js +105 -0
- package/dist/ticket-supersede.js.map +1 -0
- package/dist/tinybee/ed25519.js +99 -0
- package/dist/tinybee/ed25519.js.map +1 -0
- package/dist/tinybee/models-routes.js +19 -0
- package/dist/tinybee/models-routes.js.map +1 -0
- package/dist/tinybee/pair-payload.js +41 -0
- package/dist/tinybee/pair-payload.js.map +1 -0
- package/dist/tinybee/update-manifest.js +77 -0
- package/dist/tinybee/update-manifest.js.map +1 -0
- package/dist/tinybee/well-known.js +64 -0
- package/dist/tinybee/well-known.js.map +1 -0
- package/dist/tinybee/workflows/activity.js +23 -0
- package/dist/tinybee/workflows/activity.js.map +1 -0
- package/dist/tinybee/workflows/cron.js +210 -0
- package/dist/tinybee/workflows/cron.js.map +1 -0
- package/dist/tinybee/workflows/engine.js +371 -0
- package/dist/tinybee/workflows/engine.js.map +1 -0
- package/dist/tinybee/workflows/from-text.js +203 -0
- package/dist/tinybee/workflows/from-text.js.map +1 -0
- package/dist/tinybee/workflows/routes.js +189 -0
- package/dist/tinybee/workflows/routes.js.map +1 -0
- package/dist/tinybee/workflows/scheduler.js +100 -0
- package/dist/tinybee/workflows/scheduler.js.map +1 -0
- package/dist/tinybee/workflows/skills.js +227 -0
- package/dist/tinybee/workflows/skills.js.map +1 -0
- package/dist/tinybee/workflows/store.js +205 -0
- package/dist/tinybee/workflows/store.js.map +1 -0
- package/dist/tinybee/workflows/types.js +180 -0
- package/dist/tinybee/workflows/types.js.map +1 -0
- package/dist/tinybee/workflows/yaml.js +323 -0
- package/dist/tinybee/workflows/yaml.js.map +1 -0
- package/dist/true/api.js +495 -0
- package/dist/true/api.js.map +1 -0
- package/dist/true/client.js +292 -0
- package/dist/true/client.js.map +1 -0
- package/dist/true/clock.js +36 -0
- package/dist/true/clock.js.map +1 -0
- package/dist/true/events.js +125 -0
- package/dist/true/events.js.map +1 -0
- package/dist/true/hosts.js +98 -0
- package/dist/true/hosts.js.map +1 -0
- package/dist/true/index.js +29 -0
- package/dist/true/index.js.map +1 -0
- package/dist/true/key.js +54 -0
- package/dist/true/key.js.map +1 -0
- package/dist/true/line.js +69 -0
- package/dist/true/line.js.map +1 -0
- package/dist/true/mask.js +31 -0
- package/dist/true/mask.js.map +1 -0
- package/dist/true/names.js +114 -0
- package/dist/true/names.js.map +1 -0
- package/dist/true/outbox.js +233 -0
- package/dist/true/outbox.js.map +1 -0
- package/dist/true/replay.js +164 -0
- package/dist/true/replay.js.map +1 -0
- package/dist/true/settings.js +123 -0
- package/dist/true/settings.js.map +1 -0
- package/dist/true/status.js +189 -0
- package/dist/true/status.js.map +1 -0
- package/dist/true/sync-cases.js +222 -0
- package/dist/true/sync-cases.js.map +1 -0
- package/dist/true/sync-manual.js +295 -0
- package/dist/true/sync-manual.js.map +1 -0
- package/dist/true/sync-runs.js +109 -0
- package/dist/true/sync-runs.js.map +1 -0
- package/dist/true/worker.js +414 -0
- package/dist/true/worker.js.map +1 -0
- package/dist/types.js +32 -0
- package/dist/types.js.map +1 -0
- package/dist/updates/asks.js +38 -0
- package/dist/updates/asks.js.map +1 -0
- package/dist/updates/index.js +835 -0
- package/dist/updates/index.js.map +1 -0
- package/dist/updates/owner.js +24 -0
- package/dist/updates/owner.js.map +1 -0
- package/dist/updates/prefs.js +118 -0
- package/dist/updates/prefs.js.map +1 -0
- package/dist/updates/route.js +78 -0
- package/dist/updates/route.js.map +1 -0
- package/dist/updates/sources.js +345 -0
- package/dist/updates/sources.js.map +1 -0
- package/dist/updates/words.js +452 -0
- package/dist/updates/words.js.map +1 -0
- package/dist/version.js +53 -0
- package/dist/version.js.map +1 -0
- package/dist/view-compute.js +382 -0
- package/dist/view-compute.js.map +1 -0
- package/dist/view-spec.js +894 -0
- package/dist/view-spec.js.map +1 -0
- package/dist/web/apple-touch-icon.png +0 -0
- package/dist/web/assets/ApprovalCard-Br_z5z7G.css +1 -0
- package/dist/web/assets/ApprovalCard-CfGhGFo_.js +3 -0
- package/dist/web/assets/CardFromRef-BaTGmZ9c.js +2 -0
- package/dist/web/assets/CardFromRef-DO7i2uDd.css +1 -0
- package/dist/web/assets/Cases-8l_6UcLh.js +2 -0
- package/dist/web/assets/Cases-CpfED0uB.css +1 -0
- package/dist/web/assets/Chat-BKDiR0qt.js +3 -0
- package/dist/web/assets/Chat-DxjdyYUu.css +1 -0
- package/dist/web/assets/ConfirmDialog-CIMveD-b.js +2 -0
- package/dist/web/assets/ConnectSheet-Cm5vbZdR.js +4 -0
- package/dist/web/assets/Docs-HKGuY0WE.js +2 -0
- package/dist/web/assets/Docs-fodKx8S7.css +1 -0
- package/dist/web/assets/DotDetail-DskS3NWe.js +2 -0
- package/dist/web/assets/DotDetail-OWRSD_1x.css +1 -0
- package/dist/web/assets/DotStage-BZDclfzV.css +1 -0
- package/dist/web/assets/DotStage-CtMNzCN3.js +2 -0
- package/dist/web/assets/Dots-BqCsS34m.js +2 -0
- package/dist/web/assets/EvidencePack-DQ3kCJX4.js +2 -0
- package/dist/web/assets/EvidencePack-DV_9ETsA.css +1 -0
- package/dist/web/assets/FailedDeliveries-1F2kFcs9.js +2 -0
- package/dist/web/assets/FailedDeliveries-DXa2_VvI.css +1 -0
- package/dist/web/assets/Feed-5wiHRTFK.js +2 -0
- package/dist/web/assets/GfmView-C-NYaduC.js +4 -0
- package/dist/web/assets/ImpactList-C-RNuDB0.js +2 -0
- package/dist/web/assets/ImpactList-C612dFdw.css +1 -0
- package/dist/web/assets/Inbox-kc1bKVL_.js +2 -0
- package/dist/web/assets/JoinScreen-C3L4wmP3.js +2 -0
- package/dist/web/assets/JoinScreen-COeAUv5v.css +1 -0
- package/dist/web/assets/LeadDialog-N3Zul9Rw.js +8 -0
- package/dist/web/assets/LeadDialog-xyWjAuAS.css +1 -0
- package/dist/web/assets/ListeningLine-BRFNaczY.js +2 -0
- package/dist/web/assets/ListeningLine-pN8CgyiK.css +1 -0
- package/dist/web/assets/Mascots-pIyGpxtD.js +2 -0
- package/dist/web/assets/ModelChoices-BxO1zyyr.js +2 -0
- package/dist/web/assets/ModelChoices-DQJnVxfs.css +1 -0
- package/dist/web/assets/NewDotDialog-CDPUWmgN.js +2 -0
- package/dist/web/assets/NewDotDialog-DrPPqkSZ.css +1 -0
- package/dist/web/assets/NewTestRunDialog-BQBthVHE.js +2 -0
- package/dist/web/assets/NoBreakNames-Csxdqdt6.js +2 -0
- package/dist/web/assets/Pair-omex_K7H.js +2 -0
- package/dist/web/assets/PushSection-CmqHlE_2.js +2 -0
- package/dist/web/assets/ReleaseNotes-B6VqFewP.js +2 -0
- package/dist/web/assets/ReleaseNotes-DBJUL7Vm.css +1 -0
- package/dist/web/assets/ReportPage-BHS1xWEt.js +162 -0
- package/dist/web/assets/Reports-BEHnDIb1.js +2 -0
- package/dist/web/assets/ReviewChangeDialog-BVSt-cw-.js +2 -0
- package/dist/web/assets/ReviewChangeDialog-BbIlHRx0.css +1 -0
- package/dist/web/assets/Room-BoG_uge4.js +2 -0
- package/dist/web/assets/Room-evAfiW_e.css +1 -0
- package/dist/web/assets/RunActions-ZY7yf1AF.js +2 -0
- package/dist/web/assets/RunDetail-C8St20GQ.css +1 -0
- package/dist/web/assets/RunDetail-qADXjBvd.js +9 -0
- package/dist/web/assets/RunRow-BKEJv8ib.js +2 -0
- package/dist/web/assets/RunnerTry-Dgpp67-s.js +6 -0
- package/dist/web/assets/RunnerTry-DsweWOfO.css +1 -0
- package/dist/web/assets/Settings-fs-QJwNM.js +2 -0
- package/dist/web/assets/Setup-DI6hBEP-.js +2 -0
- package/dist/web/assets/TestRows-wh03T5Li.js +4 -0
- package/dist/web/assets/TestRun-DTOyF-Pv.js +2 -0
- package/dist/web/assets/TestRuns-ggDjm1Q8.js +2 -0
- package/dist/web/assets/TinyBeeAddLaptop-bc3pmh3T.js +2 -0
- package/dist/web/assets/TinyBeeAi-CkyOzhm5.js +2 -0
- package/dist/web/assets/TinyBeeConnections-B2K_P38u.js +2 -0
- package/dist/web/assets/TinyBeeHome-DSpxX5KS.js +3 -0
- package/dist/web/assets/TinyBeePlan-BamiLziu.js +2 -0
- package/dist/web/assets/TinyBeeSignup-vHcz0Wen.js +2 -0
- package/dist/web/assets/TinyBeeSkills-By9RLUse.js +2 -0
- package/dist/web/assets/TinyBeeWorkflow-DeNO7Jsh.js +2 -0
- package/dist/web/assets/TinyBeeWorkflowEditor-B6OGC2OT.js +2 -0
- package/dist/web/assets/TinyBeeWorkflows-BN4-h6lt.js +2 -0
- package/dist/web/assets/Trace-BBCI1aIB.css +1 -0
- package/dist/web/assets/Trace-aF92UWaH.js +2 -0
- package/dist/web/assets/UpdatesSection-CCWEbkue.js +2 -0
- package/dist/web/assets/Welcome-CCcy558Q.js +2 -0
- package/dist/web/assets/activity-C3qilrDm.css +1 -0
- package/dist/web/assets/activity-CBfTSxqw.js +2 -0
- package/dist/web/assets/authedMedia-BTYt_tyu.js +2 -0
- package/dist/web/assets/beeApi-BHW8Tx1s.js +2 -0
- package/dist/web/assets/data-CmXvx3Mj.js +2 -0
- package/dist/web/assets/engines-CfdJODFj.css +1 -0
- package/dist/web/assets/format-BBZ8vFyT.js +2 -0
- package/dist/web/assets/forms-Coc7p2UF.css +1 -0
- package/dist/web/assets/hivePush-DZJDokj7.js +2 -0
- package/dist/web/assets/impactModel-MY1OFuiP.js +2 -0
- package/dist/web/assets/index-B6qKm1NW.js +13 -0
- package/dist/web/assets/index-BvKyT1X7.css +1 -0
- package/dist/web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/web/assets/lead-BL_P_kri.css +1 -0
- package/dist/web/assets/model-BIQy5IVG.js +2 -0
- package/dist/web/assets/modelsApi-PUSySRQw.js +2 -0
- package/dist/web/assets/newDotModel-BaLLyw57.js +2 -0
- package/dist/web/assets/onboarding-bfKqnM4Z.css +1 -0
- package/dist/web/assets/phone-Rs7FoyG0.css +1 -0
- package/dist/web/assets/phoneApi-BG_1-M-D.js +3 -0
- package/dist/web/assets/releaseModel-EynYMVOC.js +2 -0
- package/dist/web/assets/releaseNotesModel-A1Rtv3cV.js +2 -0
- package/dist/web/assets/reports-BWaA1HhM.js +37 -0
- package/dist/web/assets/reports-DXwdWQsB.css +1 -0
- package/dist/web/assets/router-W6nZsQ33.js +2 -0
- package/dist/web/assets/runs-BY0Owv93.css +1 -0
- package/dist/web/assets/shadowWeekModel-BUUiww09.js +2 -0
- package/dist/web/assets/storage-yvL36AWt.js +2 -0
- package/dist/web/assets/store-IqrqHuFR.js +2 -0
- package/dist/web/assets/testruns-BzIZ0cR0.css +1 -0
- package/dist/web/assets/testruns-DyaqX4ji.js +2 -0
- package/dist/web/assets/true-9Q68cs5b.js +2 -0
- package/dist/web/assets/true-DPGBpoir.css +1 -0
- package/dist/web/assets/ui-DcmhYyYp.js +13 -0
- package/dist/web/assets/ui-DxEDgbIx.css +1 -0
- package/dist/web/assets/usePauseDot-BV60ZVy1.js +2 -0
- package/dist/web/assets/usePauseDot-NrSzMI1C.css +1 -0
- package/dist/web/assets/usePush-Dsm-TjlG.js +2 -0
- package/dist/web/assets/useRefChips-CbafNx59.js +68 -0
- package/dist/web/assets/useRestoreDot-D0B6W82H.js +2 -0
- package/dist/web/assets/view-worker-pyeVAhUT.js +2 -0
- package/dist/web/assets/workflowUi-QYsd3n9J.js +2 -0
- package/dist/web/favicon.png +0 -0
- package/dist/web/icon-192.png +0 -0
- package/dist/web/icon-512.png +0 -0
- package/dist/web/icon-maskable-512.png +0 -0
- package/dist/web/index.html +45 -0
- package/dist/web/manifest.webmanifest +23 -0
- package/dist/web/mascots/argus-attention.svg +7 -0
- package/dist/web/mascots/argus-ok.svg +7 -0
- package/dist/web/mascots/argus-outage.svg +7 -0
- package/dist/web/mascots/argus-running.svg +7 -0
- package/dist/web/mascots/bolt-attention.svg +7 -0
- package/dist/web/mascots/bolt-ok.svg +7 -0
- package/dist/web/mascots/bolt-outage.svg +7 -0
- package/dist/web/mascots/bolt-running.svg +7 -0
- package/dist/web/mascots/echo-attention.svg +7 -0
- package/dist/web/mascots/echo-ok.svg +7 -0
- package/dist/web/mascots/echo-outage.svg +7 -0
- package/dist/web/mascots/echo-running.svg +7 -0
- package/dist/web/mascots/lens-attention.svg +7 -0
- package/dist/web/mascots/lens-ok.svg +7 -0
- package/dist/web/mascots/lens-outage.svg +7 -0
- package/dist/web/mascots/lens-running.svg +7 -0
- package/dist/web/mascots/moss-attention.svg +7 -0
- package/dist/web/mascots/moss-ok.svg +7 -0
- package/dist/web/mascots/moss-outage.svg +7 -0
- package/dist/web/mascots/moss-running.svg +7 -0
- package/dist/web/mascots/nit-attention.svg +7 -0
- package/dist/web/mascots/nit-ok.svg +7 -0
- package/dist/web/mascots/nit-outage.svg +7 -0
- package/dist/web/mascots/nit-running.svg +7 -0
- package/dist/web/mascots/nova-attention.svg +7 -0
- package/dist/web/mascots/nova-ok.svg +7 -0
- package/dist/web/mascots/nova-outage.svg +7 -0
- package/dist/web/mascots/nova-running.svg +7 -0
- package/dist/web/mascots/penn-attention.svg +7 -0
- package/dist/web/mascots/penn-ok.svg +7 -0
- package/dist/web/mascots/penn-outage.svg +7 -0
- package/dist/web/mascots/penn-running.svg +7 -0
- package/dist/web/mascots/pip-attention.svg +7 -0
- package/dist/web/mascots/pip-ok.svg +7 -0
- package/dist/web/mascots/pip-outage.svg +7 -0
- package/dist/web/mascots/pip-running.svg +7 -0
- package/dist/web/mascots/ward-attention.svg +7 -0
- package/dist/web/mascots/ward-ok.svg +7 -0
- package/dist/web/mascots/ward-outage.svg +7 -0
- package/dist/web/mascots/ward-running.svg +7 -0
- package/dist/web/sw.js +98 -0
- package/dist/webhook-seen.js +53 -0
- package/dist/webhook-seen.js.map +1 -0
- package/dist/webhooks.js +625 -0
- package/dist/webhooks.js.map +1 -0
- package/dist/zip.js +63 -0
- package/dist/zip.js.map +1 -0
- package/docs/API-V2.md +532 -0
- package/docs/CHAT-EVAL.md +130 -0
- package/docs/CHAT-UI.md +416 -0
- package/docs/CONNECTIONS.md +89 -0
- package/docs/DATA-MODEL.md +518 -0
- package/docs/DEPLOY.md +577 -0
- package/docs/DOGFOOD-LEDGER.md +81 -0
- package/docs/GITHUB.md +148 -0
- package/docs/INTEGRATION-MODEL.md +200 -0
- package/docs/LEAD-DOT.md +553 -0
- package/docs/LIST-PATTERNS.md +52 -0
- package/docs/MULTI-REPO.md +766 -0
- package/docs/PARITY.md +276 -0
- package/docs/PR-REVIEW-QA.md +1266 -0
- package/docs/PR-REVIEW.md +357 -0
- package/docs/SANDBOX.md +320 -0
- package/docs/SETUP.md +776 -0
- package/docs/SLACK.md +80 -0
- package/docs/STAGES.md +619 -0
- package/docs/TRACE-MAP.md +89 -0
- package/docs/TRACKERS.md +225 -0
- package/docs/VOICE.md +36 -0
- package/docs/tinybee/00-README.md +45 -0
- package/docs/tinybee/01-PRD.md +216 -0
- package/docs/tinybee/02-architecture-pairing.md +199 -0
- package/docs/tinybee/03-local-agent.md +222 -0
- package/docs/tinybee/04-local-setup-ui.md +128 -0
- package/docs/tinybee/05-mobile-app-ux.md +199 -0
- package/docs/tinybee/06-environments-and-delivery.md +148 -0
- package/docs/tinybee/07-coding-agent-handoff.md +116 -0
- package/docs/tinybee/OPEN-QUESTIONS.md +26 -0
- package/docs/tinybee/PROGRESS.md +134 -0
- package/package.json +111 -4
|
@@ -0,0 +1,1002 @@
|
|
|
1
|
+
// New dot in two steps (KAT-97). GET /api/dots/repos lists the repos the connected GitHub account
|
|
2
|
+
// can see, recent first. GET /api/dots/suggest?repo=owner/name returns every default a new dot
|
|
3
|
+
// inherits, each with its source: the tracker from Setup, a base URL from deployments, the homepage,
|
|
4
|
+
// or the Playwright config, setup steps and the test command from package.json, the lockfile, the
|
|
5
|
+
// Playwright config, and CI workflows, the model and AI-Vision from Setup, and the brief from the
|
|
6
|
+
// Product area role preset (its brief and its six stages) with the repo filled in. The client posts `input` to POST /api/dots.
|
|
7
|
+
//
|
|
8
|
+
// TODO(KAT-85): when Watch.repos lands, accept several repos here and return a role per repo.
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { baseUrlFromConfig } from '../cli/init.js';
|
|
11
|
+
import { config, setting } from '../config.js';
|
|
12
|
+
import { list } from '../db.js';
|
|
13
|
+
import { fetchT } from '../connect/deps.js';
|
|
14
|
+
import { apiBase, appAuth, ghHeaders } from '../connect/github-app.js';
|
|
15
|
+
import { getMeta } from '../connect/store.js';
|
|
16
|
+
import { parseCommand, PLAYWRIGHT_TEST, playwrightTail, scriptRun, shQuote, splitSetupSteps, validateRunner, workingDirectoryError, } from '../runner/command.js';
|
|
17
|
+
import { ROLE_PRESETS } from './roles.js';
|
|
18
|
+
import { DEMO_REPO } from '../onboarding.js';
|
|
19
|
+
const PW_CONFIGS = ['playwright.config.ts', 'playwright.config.mts', 'playwright.config.js', 'playwright.config.mjs', 'playwright.config.cjs'];
|
|
20
|
+
const SEGMENT_RE = /^[A-Za-z0-9_.-]{1,100}$/;
|
|
21
|
+
/**
|
|
22
|
+
* "owner/name" as a GitHub API path, or undefined. Each part is checked and encoded on its own, and
|
|
23
|
+
* dot-only parts are refused: `repo=../user` would otherwise resolve `/repos/../user` to `/user`.
|
|
24
|
+
*/
|
|
25
|
+
export function repoPath(full) {
|
|
26
|
+
const parts = full.split('/');
|
|
27
|
+
if (parts.length !== 2 || !parts.every((x) => SEGMENT_RE.test(x) && !/^\.+$/.test(x)))
|
|
28
|
+
return undefined;
|
|
29
|
+
return parts.map(encodeURIComponent).join('/');
|
|
30
|
+
}
|
|
31
|
+
// A new dot starts as a product area, with all six stages on (docs/STAGES.md section 6.3).
|
|
32
|
+
const DEFAULT_ROLE = 'product-area';
|
|
33
|
+
export class SuggestError extends Error {
|
|
34
|
+
status;
|
|
35
|
+
constructor(status, message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.status = status;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// GitHub access
|
|
41
|
+
function githubConnected() {
|
|
42
|
+
return !!((setting('GITHUB_APP_ID') && setting('GITHUB_PRIVATE_KEY')) || setting('GITHUB_TOKEN'));
|
|
43
|
+
}
|
|
44
|
+
/** A token that can read `full`: the PAT or gh token, or an App installation token for the repo. */
|
|
45
|
+
async function tokenFor(full) {
|
|
46
|
+
const id = setting('GITHUB_APP_ID');
|
|
47
|
+
const key = setting('GITHUB_PRIVATE_KEY');
|
|
48
|
+
if (id && key) {
|
|
49
|
+
const auth = await appAuth(id, key);
|
|
50
|
+
if (!full || !repoPath(full))
|
|
51
|
+
return undefined;
|
|
52
|
+
const jwt = (await auth({ type: 'app' })).token;
|
|
53
|
+
const res = await fetchT(`${apiBase()}/repos/${repoPath(full)}/installation`, { headers: ghHeaders(jwt) });
|
|
54
|
+
if (!res.ok)
|
|
55
|
+
throw new SuggestError(404, `The GitHub App is not installed on ${full}. Install it on the repo, then try again.`);
|
|
56
|
+
const inst = (await res.json());
|
|
57
|
+
return (await auth({ type: 'installation', installationId: inst.id })).token;
|
|
58
|
+
}
|
|
59
|
+
return setting('GITHUB_TOKEN') || undefined;
|
|
60
|
+
}
|
|
61
|
+
function client(token) {
|
|
62
|
+
const headers = token ? ghHeaders(token) : { Accept: 'application/vnd.github+json', 'User-Agent': 'localdost' };
|
|
63
|
+
return {
|
|
64
|
+
async get(p) {
|
|
65
|
+
const res = await fetchT(`${apiBase()}${p}`, { headers });
|
|
66
|
+
if (!res.ok)
|
|
67
|
+
return { status: res.status };
|
|
68
|
+
try {
|
|
69
|
+
return { status: res.status, body: (await res.json()) };
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return { status: res.status };
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
async raw(p) {
|
|
76
|
+
const res = await fetchT(`${apiBase()}${p}`, { headers: { ...headers, Accept: 'application/vnd.github.raw+json' } });
|
|
77
|
+
return res.ok ? await res.text() : undefined;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function repoChoice(r) {
|
|
82
|
+
const full = typeof r.full_name === 'string' ? r.full_name : '';
|
|
83
|
+
const [owner, name] = full.split('/');
|
|
84
|
+
if (!owner || !name)
|
|
85
|
+
return undefined;
|
|
86
|
+
const out = { fullName: full, owner, name, private: !!r.private };
|
|
87
|
+
if (typeof r.default_branch === 'string')
|
|
88
|
+
out.defaultBranch = r.default_branch;
|
|
89
|
+
if (typeof r.language === 'string' && r.language)
|
|
90
|
+
out.language = r.language;
|
|
91
|
+
if (typeof r.pushed_at === 'string')
|
|
92
|
+
out.pushedAt = r.pushed_at;
|
|
93
|
+
if (typeof r.description === 'string' && r.description)
|
|
94
|
+
out.description = r.description.slice(0, 200);
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
/** Repos the connection can see, most recently pushed first. `q` filters by owner/name. */
|
|
98
|
+
export async function listRepos(q = '') {
|
|
99
|
+
if (!githubConnected())
|
|
100
|
+
return { connected: false, repos: [] };
|
|
101
|
+
const needle = q.trim().toLowerCase();
|
|
102
|
+
const seen = new Map();
|
|
103
|
+
const add = (r) => {
|
|
104
|
+
const c = repoChoice(r);
|
|
105
|
+
if (c && !seen.has(c.fullName.toLowerCase()))
|
|
106
|
+
seen.set(c.fullName.toLowerCase(), c);
|
|
107
|
+
};
|
|
108
|
+
try {
|
|
109
|
+
const id = setting('GITHUB_APP_ID');
|
|
110
|
+
const key = setting('GITHUB_PRIVATE_KEY');
|
|
111
|
+
if (id && key) {
|
|
112
|
+
const auth = await appAuth(id, key);
|
|
113
|
+
const jwt = (await auth({ type: 'app' })).token;
|
|
114
|
+
const inst = await client(jwt).get('/app/installations?per_page=100');
|
|
115
|
+
for (const i of inst.body ?? []) {
|
|
116
|
+
const tok = (await auth({ type: 'installation', installationId: i.id })).token;
|
|
117
|
+
const res = await client(tok).get('/installation/repositories?per_page=100');
|
|
118
|
+
for (const r of res.body?.repositories ?? [])
|
|
119
|
+
add(r);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const gh = client(setting('GITHUB_TOKEN'));
|
|
124
|
+
for (let page = 1; page <= 3; page++) {
|
|
125
|
+
const res = await gh.get(`/user/repos?per_page=100&sort=pushed&page=${page}`);
|
|
126
|
+
if (!res.body) {
|
|
127
|
+
if (page === 1)
|
|
128
|
+
return { connected: true, repos: [], error: `GitHub answered HTTP ${res.status} for the repo list.` };
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
for (const r of res.body)
|
|
132
|
+
add(r);
|
|
133
|
+
if (res.body.length < 100)
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// A repo the list does not hold, typed in full: look it up directly (public repos, other orgs).
|
|
138
|
+
if (repoPath(needle) && ![...seen.keys()].includes(needle)) {
|
|
139
|
+
const tok = await tokenFor(needle).catch(() => setting('GITHUB_TOKEN'));
|
|
140
|
+
const one = await client(tok).get(`/repos/${repoPath(needle)}`);
|
|
141
|
+
if (one.body)
|
|
142
|
+
add(one.body);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
return { connected: true, repos: [], error: err instanceof Error ? err.message : String(err) };
|
|
147
|
+
}
|
|
148
|
+
const repos = [...seen.values()]
|
|
149
|
+
.filter((r) => !needle || r.fullName.toLowerCase().includes(needle))
|
|
150
|
+
.sort((a, b) => (b.pushedAt ?? '').localeCompare(a.pushedAt ?? ''))
|
|
151
|
+
.slice(0, 50);
|
|
152
|
+
return { connected: true, repos };
|
|
153
|
+
}
|
|
154
|
+
/** The package manager of the project in `dir` (a folder of the repo, or the root): its lockfile, else the root's. */
|
|
155
|
+
function packageManager(paths, dir) {
|
|
156
|
+
for (const base of dir ? [dir, ''] : ['']) {
|
|
157
|
+
const has = (f) => paths.has(base ? `${base}/${f}` : f);
|
|
158
|
+
if (has('pnpm-lock.yaml'))
|
|
159
|
+
return { pm: 'pnpm', lockfile: 'pnpm-lock.yaml' };
|
|
160
|
+
if (has('yarn.lock'))
|
|
161
|
+
return { pm: 'yarn', lockfile: 'yarn.lock' };
|
|
162
|
+
if (has('bun.lock'))
|
|
163
|
+
return { pm: 'bun', lockfile: 'bun.lock' };
|
|
164
|
+
if (has('bun.lockb'))
|
|
165
|
+
return { pm: 'bun', lockfile: 'bun.lockb' };
|
|
166
|
+
if (has('package-lock.json'))
|
|
167
|
+
return { pm: 'npm', lockfile: 'package-lock.json' };
|
|
168
|
+
if (has('npm-shrinkwrap.json'))
|
|
169
|
+
return { pm: 'npm', lockfile: 'npm-shrinkwrap.json' };
|
|
170
|
+
}
|
|
171
|
+
return { pm: 'npm' };
|
|
172
|
+
}
|
|
173
|
+
const INSTALL = {
|
|
174
|
+
npm: (l) => (l ? 'npm ci' : 'npm install'),
|
|
175
|
+
pnpm: (l) => `corepack enable && pnpm install${l ? ' --frozen-lockfile' : ''}`,
|
|
176
|
+
yarn: (l) => `corepack enable && yarn install${l ? ' --frozen-lockfile' : ''}`,
|
|
177
|
+
bun: (l) => `bun install${l ? ' --frozen-lockfile' : ''}`,
|
|
178
|
+
};
|
|
179
|
+
const EXEC = { npm: 'npx', pnpm: 'pnpm exec', yarn: 'yarn', bun: 'bunx' };
|
|
180
|
+
const RUN = { npm: 'npm run', pnpm: 'pnpm run', yarn: 'yarn', bun: 'bun run' };
|
|
181
|
+
const unquote = (v) => v
|
|
182
|
+
.replace(/\s+#.*$/, '')
|
|
183
|
+
.trim()
|
|
184
|
+
.replace(/^(['"])(.*)\1$/, '$2');
|
|
185
|
+
const CD_LINE = /^cd\s+(?:"([^"]*)"|'([^']*)'|([^\s;&|<>()"']+))\s*(?:&&\s*([\s\S]*))?$/;
|
|
186
|
+
/**
|
|
187
|
+
* A `cd` inside a run block sets the folder of the lines after it (KAT-134), as in the shell the
|
|
188
|
+
* block runs in. `cd web` alone is dropped; `cd web && npm ci` keeps `npm ci`. `cd` with no folder,
|
|
189
|
+
* `cd -`, and a cd inside a subshell are left as plain lines.
|
|
190
|
+
*/
|
|
191
|
+
function addRuns(step, lines) {
|
|
192
|
+
for (let line of lines) {
|
|
193
|
+
for (;;) {
|
|
194
|
+
const m = CD_LINE.exec(line);
|
|
195
|
+
if (!m || (m[3] ?? m[1] ?? m[2]) === '-')
|
|
196
|
+
break;
|
|
197
|
+
step.cd = foldCd(step.cd, m[1] ?? m[2] ?? m[3] ?? '');
|
|
198
|
+
line = (m[4] ?? '').trim();
|
|
199
|
+
if (!line)
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
if (line)
|
|
203
|
+
step.runs.push({ run: line, ...(step.cd ? { cd: step.cd } : {}) });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** `dir` with a cd target applied. An absolute target replaces it; `..` and variables stay visible so checkoutDir can refuse them. */
|
|
207
|
+
function foldCd(dir, target) {
|
|
208
|
+
if (target.startsWith('/') || /^[$~`]/.test(target) || target.includes('${{'))
|
|
209
|
+
return target;
|
|
210
|
+
return path.posix.join(dir ?? '', target) || '.';
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* `run:` steps per job, in order, each with the working-directory it runs in. A small reader for the
|
|
214
|
+
* shapes GitHub workflows use; no YAML dependency. A step's own `working-directory` wins over the
|
|
215
|
+
* job's `defaults.run.working-directory`, which wins over the workflow's.
|
|
216
|
+
*/
|
|
217
|
+
export function workflowJobs(src) {
|
|
218
|
+
const lines = src.replace(/\r/g, '').split('\n');
|
|
219
|
+
const jobs = [];
|
|
220
|
+
let workflowDir;
|
|
221
|
+
let section = 'other';
|
|
222
|
+
let jobIndent = -1;
|
|
223
|
+
let cur;
|
|
224
|
+
// Steps of the current job.
|
|
225
|
+
let inSteps = false;
|
|
226
|
+
let stepsIndent = -1;
|
|
227
|
+
let dashIndent = -1;
|
|
228
|
+
let keyIndent = -1;
|
|
229
|
+
let step;
|
|
230
|
+
let jobDefaults = false;
|
|
231
|
+
const endStep = () => {
|
|
232
|
+
if (step && cur)
|
|
233
|
+
for (const r of step.runs)
|
|
234
|
+
cur.steps.push({ run: r.run, ...(step.dir ? { dir: step.dir } : {}), ...(r.cd ? { cd: r.cd } : {}) });
|
|
235
|
+
step = undefined;
|
|
236
|
+
};
|
|
237
|
+
for (let i = 0; i < lines.length; i++) {
|
|
238
|
+
const line = lines[i];
|
|
239
|
+
const text = line.trim();
|
|
240
|
+
if (!text || text.startsWith('#'))
|
|
241
|
+
continue;
|
|
242
|
+
const indent = line.length - line.trimStart().length;
|
|
243
|
+
if (indent === 0) {
|
|
244
|
+
endStep();
|
|
245
|
+
inSteps = false;
|
|
246
|
+
section = /^jobs:\s*$/.test(line) ? 'jobs' : /^defaults:\s*$/.test(line) ? 'defaults' : 'other';
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (section === 'defaults') {
|
|
250
|
+
const wd = /^working-directory:\s*(.+)$/.exec(text);
|
|
251
|
+
if (wd)
|
|
252
|
+
workflowDir = unquote(wd[1]);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (section !== 'jobs')
|
|
256
|
+
continue;
|
|
257
|
+
if (inSteps && indent <= stepsIndent && !text.startsWith('-')) {
|
|
258
|
+
endStep();
|
|
259
|
+
inSteps = false;
|
|
260
|
+
}
|
|
261
|
+
const jobHead = /^([A-Za-z0-9_-]+):\s*$/.exec(text);
|
|
262
|
+
if (!inSteps && jobHead && (jobIndent < 0 || indent === jobIndent)) {
|
|
263
|
+
endStep();
|
|
264
|
+
jobIndent = indent;
|
|
265
|
+
cur = { job: jobHead[1], steps: [] };
|
|
266
|
+
jobs.push(cur);
|
|
267
|
+
jobDefaults = false;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (!cur)
|
|
271
|
+
continue;
|
|
272
|
+
if (!inSteps) {
|
|
273
|
+
if (/^steps:\s*$/.test(text)) {
|
|
274
|
+
inSteps = true;
|
|
275
|
+
stepsIndent = indent;
|
|
276
|
+
dashIndent = -1;
|
|
277
|
+
}
|
|
278
|
+
else if (/^defaults:\s*$/.test(text))
|
|
279
|
+
jobDefaults = true;
|
|
280
|
+
else if (jobDefaults) {
|
|
281
|
+
const wd = /^working-directory:\s*(.+)$/.exec(text);
|
|
282
|
+
if (wd)
|
|
283
|
+
cur.defaultDir = unquote(wd[1]);
|
|
284
|
+
}
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
// Inside steps: find the key this line sets, at the step's own key indent.
|
|
288
|
+
let key = text;
|
|
289
|
+
let at = indent;
|
|
290
|
+
const dash = /^-\s+/.exec(text);
|
|
291
|
+
if (dash && (dashIndent < 0 || indent === dashIndent)) {
|
|
292
|
+
endStep();
|
|
293
|
+
dashIndent = indent;
|
|
294
|
+
step = { runs: [] };
|
|
295
|
+
key = text.slice(dash[0].length);
|
|
296
|
+
at = indent + dash[0].length;
|
|
297
|
+
keyIndent = at;
|
|
298
|
+
}
|
|
299
|
+
else if (!step)
|
|
300
|
+
continue;
|
|
301
|
+
if (at !== keyIndent)
|
|
302
|
+
continue;
|
|
303
|
+
const wd = /^working-directory:\s*(.+)$/.exec(key);
|
|
304
|
+
if (wd) {
|
|
305
|
+
step.dir = unquote(wd[1]);
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
const run = /^run:\s*(.*)$/.exec(key);
|
|
309
|
+
if (!run)
|
|
310
|
+
continue;
|
|
311
|
+
const rest = run[1].trim();
|
|
312
|
+
if (rest === '|' || rest === '>' || rest === '|-' || rest === '>-') {
|
|
313
|
+
const block = [];
|
|
314
|
+
let j = i + 1;
|
|
315
|
+
let blockIndent = -1;
|
|
316
|
+
for (; j < lines.length; j++) {
|
|
317
|
+
const l = lines[j];
|
|
318
|
+
if (!l.trim())
|
|
319
|
+
continue;
|
|
320
|
+
const ind = l.length - l.trimStart().length;
|
|
321
|
+
if (blockIndent < 0)
|
|
322
|
+
blockIndent = ind;
|
|
323
|
+
if (ind < blockIndent || ind <= keyIndent)
|
|
324
|
+
break;
|
|
325
|
+
block.push(l.trim());
|
|
326
|
+
}
|
|
327
|
+
i = j - 1;
|
|
328
|
+
const joined = rest.startsWith('>') ? [block.join(' ')] : block;
|
|
329
|
+
addRuns(step, joined.filter((x) => x && !x.startsWith('#')));
|
|
330
|
+
}
|
|
331
|
+
else if (rest) {
|
|
332
|
+
addRuns(step, [rest.replace(/^(['"])(.*)\1$/, '$2')]);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
endStep();
|
|
336
|
+
return jobs.map((j) => ({
|
|
337
|
+
job: j.job,
|
|
338
|
+
steps: j.steps.map((st) => {
|
|
339
|
+
const base = st.dir ?? j.defaultDir ?? workflowDir;
|
|
340
|
+
const dir = st.cd ? foldCd(base, st.cd) : base;
|
|
341
|
+
return { run: st.run, ...(dir ? { workingDirectory: dir } : {}) };
|
|
342
|
+
}),
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
/** `run:` steps per job, in order, without their working directories. */
|
|
346
|
+
export function workflowRunSteps(src) {
|
|
347
|
+
return workflowJobs(src).map((j) => ({ job: j.job, steps: j.steps.map((st) => st.run) }));
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* A working-directory from a workflow as a clean path inside the checkout: `./web/` becomes `web`.
|
|
351
|
+
* Undefined for the root, or when the value cannot be used (an expression, an absolute path, `..`).
|
|
352
|
+
*/
|
|
353
|
+
export function checkoutDir(dir, notes) {
|
|
354
|
+
if (!dir)
|
|
355
|
+
return undefined;
|
|
356
|
+
const d = path.posix.normalize(dir.trim()).replace(/\/+$/, '');
|
|
357
|
+
if (!d || d === '.')
|
|
358
|
+
return undefined;
|
|
359
|
+
if (/[$`~]/.test(d) || workingDirectoryError(d)) {
|
|
360
|
+
notes?.push(`Ignored the CI working-directory "${dir}": it is not a plain folder inside the repo. Set Working directory if the tests live in a subfolder.`);
|
|
361
|
+
return undefined;
|
|
362
|
+
}
|
|
363
|
+
return d;
|
|
364
|
+
}
|
|
365
|
+
const ENV_PREFIX = /^((?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S*)\s+)*)([\s\S]*)$/;
|
|
366
|
+
/**
|
|
367
|
+
* A package.json script that runs Playwright as a test command: leading `VAR=value` words stay in
|
|
368
|
+
* front, and the package manager's exec prefix goes between them and the program, so
|
|
369
|
+
* `CI=1 playwright test` becomes `CI=1 npx playwright test`, never `npx CI=1 playwright test`.
|
|
370
|
+
*/
|
|
371
|
+
export function scriptAsCommand(value, pm) {
|
|
372
|
+
const [, env = '', rest = ''] = ENV_PREFIX.exec(value.trim()) ?? [];
|
|
373
|
+
const exec = EXEC[pm];
|
|
374
|
+
const prog = rest.startsWith('npx ') || rest.startsWith(`${exec} `) ? rest : `${exec} ${rest}`;
|
|
375
|
+
return `${env}${prog}`;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* A test command for a package.json script whose body runs Playwright. The runner appends its
|
|
379
|
+
* options (--reporter=json, --output, --config) to the end of the command (src/runner/command.ts
|
|
380
|
+
* `forwardingArgv` adds the `--` npm needs), so they must reach Playwright from there:
|
|
381
|
+
* - the script is one Playwright command: that command, run through the package manager's exec;
|
|
382
|
+
* - Playwright is the last step of `a && playwright test`: `npm run <script>`, which keeps the
|
|
383
|
+
* earlier steps and passes the options on to the last one;
|
|
384
|
+
* - Playwright is an earlier step (`playwright test && node report.js`): that step alone, since
|
|
385
|
+
* appended options would reach the last one. The other steps are named in `notes`.
|
|
386
|
+
* Null when no step of the script runs Playwright.
|
|
387
|
+
*/
|
|
388
|
+
export function scriptTestCommand(name, body, pm, notes, scripts = {}) {
|
|
389
|
+
const tail = playwrightTail(body);
|
|
390
|
+
const hooks = scriptHooks(scripts, name);
|
|
391
|
+
if (hooks.length) {
|
|
392
|
+
// npm runs pre<name> and post<name> only for `<pm> run <name>`, so unwrapping the script would skip
|
|
393
|
+
// them (fixtures created in a pretest hook). Keep the call, which the runner forwards its options
|
|
394
|
+
// through when the script's last command is Playwright.
|
|
395
|
+
if (tail)
|
|
396
|
+
return `${RUN[pm]} ${name}`;
|
|
397
|
+
addNote(notes, `The package.json script "${name}" has ${hooks.join(' and ')}, so it must run through ${pm}. Its last command is not Playwright, so the runner's options cannot reach the tests. Type the test command.`);
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
if (tail) {
|
|
401
|
+
if (!tail.pre.length) {
|
|
402
|
+
const direct = scriptAsCommand(tail.last, pm);
|
|
403
|
+
if (parseCommand(direct).ok)
|
|
404
|
+
return direct;
|
|
405
|
+
}
|
|
406
|
+
return `${RUN[pm]} ${name}`;
|
|
407
|
+
}
|
|
408
|
+
const steps = splitSetupSteps(body);
|
|
409
|
+
const at = steps.findIndex((x) => /\bplaywright\s+test\b/.test(x));
|
|
410
|
+
if (at < 0)
|
|
411
|
+
return null;
|
|
412
|
+
const direct = scriptAsCommand(steps[at], pm);
|
|
413
|
+
if (!parseCommand(direct).ok)
|
|
414
|
+
return null;
|
|
415
|
+
const others = steps.filter((_, i) => i !== at).join(' && ');
|
|
416
|
+
addNote(notes, `The package.json script "${name}" runs "${others}" next to Playwright. Options can only reach the last step of a script, so the test command runs just the Playwright step. Add the other steps to Setup steps if the tests need them.`);
|
|
417
|
+
return direct;
|
|
418
|
+
}
|
|
419
|
+
/** The lifecycle hooks (`pre<name>`, `post<name>`) a script has in `scripts`. */
|
|
420
|
+
export function scriptHooks(scripts, name) {
|
|
421
|
+
return [`pre${name}`, `post${name}`].filter((h) => Object.hasOwn(scripts, h));
|
|
422
|
+
}
|
|
423
|
+
const addNote = (notes, note) => {
|
|
424
|
+
if (notes && !notes.includes(note))
|
|
425
|
+
notes.push(note);
|
|
426
|
+
};
|
|
427
|
+
/**
|
|
428
|
+
* True when a CI step runs tests: the step is `playwright test`, or it calls a package.json script whose
|
|
429
|
+
* body does. This is the one classifier for what may become the test command. Matching a script name
|
|
430
|
+
* as text inside the step ("e2e" in `npm run e2e:install`, "playwright" in `npx playwright install`)
|
|
431
|
+
* would pick an install step, which produces no test results (KAT-156).
|
|
432
|
+
*/
|
|
433
|
+
export function stepRunsTests(step, scripts) {
|
|
434
|
+
const parsed = parseCommand(step);
|
|
435
|
+
if (!parsed.ok)
|
|
436
|
+
return PLAYWRIGHT_TEST.test(step);
|
|
437
|
+
const call = scriptRun(parsed.argv, scripts);
|
|
438
|
+
return call ? PLAYWRIGHT_TEST.test(scripts[call.name] ?? '') : PLAYWRIGHT_TEST.test(parsed.argv.join(' '));
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* The test command for a CI step that runs Playwright. A step that calls a package.json script
|
|
442
|
+
* (`npm run test:e2e`) is resolved through scriptTestCommand, so the runner's options reach
|
|
443
|
+
* Playwright; the step's variables and extra arguments (`-- --project=chromium`) are kept. Any
|
|
444
|
+
* other step is the command itself. Null when the step cannot be a test command.
|
|
445
|
+
*/
|
|
446
|
+
function ciTestCommand(step, scripts, notes) {
|
|
447
|
+
const parsed = parseCommand(step);
|
|
448
|
+
if (!parsed.ok || !stepRunsTests(step, scripts))
|
|
449
|
+
return null;
|
|
450
|
+
const call = scriptRun(parsed.argv, scripts);
|
|
451
|
+
if (!call)
|
|
452
|
+
return step;
|
|
453
|
+
const resolved = scriptTestCommand(call.name, scripts[call.name], call.pm, notes, scripts);
|
|
454
|
+
if (!resolved)
|
|
455
|
+
return null;
|
|
456
|
+
const env = Object.entries(parsed.env)
|
|
457
|
+
.map(([k, v]) => `${k}=${shQuote(v)} `)
|
|
458
|
+
.join('');
|
|
459
|
+
if (!call.extra.length)
|
|
460
|
+
return `${env}${resolved}`;
|
|
461
|
+
const viaRun = resolved.endsWith(` ${RUN[call.pm]} ${call.name}`) || resolved === `${RUN[call.pm]} ${call.name}`;
|
|
462
|
+
return `${env}${resolved}${viaRun && call.pm === 'npm' ? ' --' : ''} ${call.extra.map(shQuote).join(' ')}`;
|
|
463
|
+
}
|
|
464
|
+
function browsersFromConfig(src) {
|
|
465
|
+
if (!src)
|
|
466
|
+
return ['chromium'];
|
|
467
|
+
const out = new Set();
|
|
468
|
+
if (/Desktop Chrome|chromium|Pixel|Galaxy/i.test(src))
|
|
469
|
+
out.add('chromium');
|
|
470
|
+
if (/firefox/i.test(src))
|
|
471
|
+
out.add('firefox');
|
|
472
|
+
if (/webkit|Safari|iPhone|iPad/i.test(src))
|
|
473
|
+
out.add('webkit');
|
|
474
|
+
return out.size ? [...out] : ['chromium'];
|
|
475
|
+
}
|
|
476
|
+
/** Setup steps, test command, and smoke grep from the files of the default branch. */
|
|
477
|
+
export function guessRunner(files) {
|
|
478
|
+
const paths = new Set(files.paths);
|
|
479
|
+
const notes = [];
|
|
480
|
+
// The one place a package.json, its scripts, and its Playwright config are read: relative to the
|
|
481
|
+
// folder the tests run in (the CI job's working-directory, KAT-146), else the repo root.
|
|
482
|
+
const projects = new Map();
|
|
483
|
+
const projectAt = (dir) => {
|
|
484
|
+
const known = projects.get(dir ?? '');
|
|
485
|
+
if (known)
|
|
486
|
+
return known;
|
|
487
|
+
const rel = (f) => (dir ? `${dir}/${f}` : f);
|
|
488
|
+
const pkgPath = rel('package.json');
|
|
489
|
+
const pkgText = files.text[pkgPath];
|
|
490
|
+
let pkg = {};
|
|
491
|
+
let pkgInvalid = false;
|
|
492
|
+
if (pkgText) {
|
|
493
|
+
try {
|
|
494
|
+
const parsed = JSON.parse(pkgText);
|
|
495
|
+
if (parsed && typeof parsed === 'object')
|
|
496
|
+
pkg = parsed;
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
pkgInvalid = true;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const configPath = dir
|
|
503
|
+
? PW_CONFIGS.map(rel).find((f) => paths.has(f))
|
|
504
|
+
: (PW_CONFIGS.find((f) => paths.has(f)) ??
|
|
505
|
+
files.paths.find((p) => PW_CONFIGS.some((f) => p.endsWith(`/${f}`)) && p.split('/').length <= 3 && !p.includes('node_modules')));
|
|
506
|
+
const project = {
|
|
507
|
+
...(dir ? { dir } : {}),
|
|
508
|
+
pkgPath,
|
|
509
|
+
...(pkgText ? { pkgText } : {}),
|
|
510
|
+
pkgInvalid,
|
|
511
|
+
scripts: pkg.scripts && typeof pkg.scripts === 'object' ? pkg.scripts : {},
|
|
512
|
+
deps: { ...pkg.dependencies, ...pkg.devDependencies },
|
|
513
|
+
...(configPath ? { configPath, config: files.text[configPath] } : {}),
|
|
514
|
+
};
|
|
515
|
+
projects.set(dir ?? '', project);
|
|
516
|
+
return project;
|
|
517
|
+
};
|
|
518
|
+
// Test command: a CI step that runs Playwright, else a package.json script, else the default.
|
|
519
|
+
let command = null;
|
|
520
|
+
let commandSource = '';
|
|
521
|
+
let setup = null;
|
|
522
|
+
let setupSource = '';
|
|
523
|
+
let workingDirectory;
|
|
524
|
+
const workflows = files.paths.filter((p) => /^\.github\/workflows\/[^/]+\.ya?ml$/.test(p)).sort();
|
|
525
|
+
for (const wf of workflows) {
|
|
526
|
+
const src = files.text[wf];
|
|
527
|
+
if (!src)
|
|
528
|
+
continue;
|
|
529
|
+
for (const job of workflowJobs(src)) {
|
|
530
|
+
const idx = job.steps.findIndex((s) => stepRunsTests(s.run, projectAt(checkoutDir(s.workingDirectory)).scripts));
|
|
531
|
+
if (idx < 0)
|
|
532
|
+
continue;
|
|
533
|
+
const step = job.steps[idx].run;
|
|
534
|
+
// The tests run in the folder the CI step ran in. Earlier steps from another folder keep their own via a subshell.
|
|
535
|
+
const testDir = checkoutDir(job.steps[idx].workingDirectory, notes);
|
|
536
|
+
if (testDir)
|
|
537
|
+
workingDirectory = testDir;
|
|
538
|
+
const name = wf.split('/').pop();
|
|
539
|
+
const ci = step.includes('${{') ? null : ciTestCommand(step, projectAt(testDir).scripts, notes);
|
|
540
|
+
if (ci) {
|
|
541
|
+
command = ci;
|
|
542
|
+
commandSource = `CI workflow ${name}, job ${job.job}`;
|
|
543
|
+
}
|
|
544
|
+
const ciPm = packageManager(paths, testDir).pm;
|
|
545
|
+
const before = job.steps
|
|
546
|
+
.slice(0, idx)
|
|
547
|
+
.filter((s) => !s.run.includes('${{'))
|
|
548
|
+
.map((s) => {
|
|
549
|
+
const text = s.run.replace(/\s+--with-deps\b/, '');
|
|
550
|
+
const dir = checkoutDir(s.workingDirectory, notes);
|
|
551
|
+
if (dir === testDir)
|
|
552
|
+
return text;
|
|
553
|
+
const rel = path.posix.relative(testDir ?? '.', dir ?? '.') || '.';
|
|
554
|
+
return `(cd ${shQuote(rel)} && ${text})`;
|
|
555
|
+
});
|
|
556
|
+
if (before.length) {
|
|
557
|
+
// `actions/setup-*` and Corepack steps do not run here, so pnpm and yarn need the bootstrap the
|
|
558
|
+
// lockfile path adds, unless a run step already does it.
|
|
559
|
+
const needsCorepack = (ciPm === 'pnpm' || ciPm === 'yarn') &&
|
|
560
|
+
before.some((s) => new RegExp(`\\b${ciPm}\\b`).test(s)) &&
|
|
561
|
+
!before.some((s) => /\bcorepack\s+(enable|prepare)\b|\b(npm|npx)\b.*\s(-g|--global)\b.*\b(pnpm|yarn)\b/.test(s));
|
|
562
|
+
setup = (needsCorepack ? ['corepack enable', ...before] : before).join('\n');
|
|
563
|
+
setupSource = `CI workflow ${name}, job ${job.job}`;
|
|
564
|
+
if (job.steps.slice(0, idx).some((s) => /--with-deps/.test(s.run)))
|
|
565
|
+
notes.push('Dropped --with-deps from the browser install: it needs root on this machine.');
|
|
566
|
+
}
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
if (command || setup)
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
// Everything below reads the project the tests run in.
|
|
573
|
+
const proj = projectAt(workingDirectory);
|
|
574
|
+
const { pm, lockfile } = packageManager(paths, workingDirectory);
|
|
575
|
+
if (proj.pkgInvalid)
|
|
576
|
+
notes.push(`${proj.pkgPath} is not valid JSON.`);
|
|
577
|
+
const { scripts, pkgText, configPath, config } = proj;
|
|
578
|
+
const hasPlaywright = !!proj.deps['@playwright/test'] || !!proj.deps.playwright || !!configPath || (!!workingDirectory && !!projectAt().deps['@playwright/test']);
|
|
579
|
+
const webServer = !!config && /webServer\s*:/.test(config);
|
|
580
|
+
const configUrl = config ? baseUrlFromConfig(config).url : undefined;
|
|
581
|
+
// A config below the root is found from the root; inside the working directory it is found by name.
|
|
582
|
+
const configArg = configPath && !workingDirectory && !PW_CONFIGS.includes(configPath) ? ` --config ${configPath}` : '';
|
|
583
|
+
if (!command) {
|
|
584
|
+
const order = ['test:e2e', 'e2e', 'test:playwright', 'playwright', 'test'];
|
|
585
|
+
const names = [...order.filter((n) => scripts[n]), ...Object.keys(scripts).filter((n) => !order.includes(n))];
|
|
586
|
+
const script = names.find((n) => /playwright\s+test/.test(scripts[n]));
|
|
587
|
+
if (script) {
|
|
588
|
+
command = scriptTestCommand(script, scripts[script], pm, notes, scripts);
|
|
589
|
+
// A script with hooks that cannot take the options stays empty (the note says why); any other
|
|
590
|
+
// script that cannot be unwrapped runs through the package manager.
|
|
591
|
+
if (!command && !scriptHooks(scripts, script).length)
|
|
592
|
+
command = `${RUN[pm]} ${script}`;
|
|
593
|
+
if (command)
|
|
594
|
+
commandSource = `package.json script "${script}"${command === `${RUN[pm]} ${script}` ? `, run through ${pm}` : ''}`;
|
|
595
|
+
}
|
|
596
|
+
else if (hasPlaywright) {
|
|
597
|
+
command = `${EXEC[pm]} playwright test${configArg}`;
|
|
598
|
+
commandSource = configPath ? `Playwright config ${configPath}` : 'the @playwright/test dependency';
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
commandSource = 'No Playwright config or dependency found. Add one, or type the test command.';
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (!setup && pkgText) {
|
|
605
|
+
const steps = [INSTALL[pm](!!lockfile)];
|
|
606
|
+
const why = [lockfile ?? `${proj.pkgPath} without a lockfile`];
|
|
607
|
+
if (hasPlaywright) {
|
|
608
|
+
steps.push(`${EXEC[pm]} playwright install ${browsersFromConfig(config).join(' ')}`);
|
|
609
|
+
why.push('Playwright browsers');
|
|
610
|
+
}
|
|
611
|
+
if (scripts.build) {
|
|
612
|
+
steps.push(`${RUN[pm]} build`);
|
|
613
|
+
why.push('build script');
|
|
614
|
+
}
|
|
615
|
+
setup = steps.join('\n');
|
|
616
|
+
setupSource = why.join(', ');
|
|
617
|
+
}
|
|
618
|
+
if (!setup)
|
|
619
|
+
setupSource = `No ${proj.pkgPath === 'package.json' ? 'package.json at the repo root' : proj.pkgPath}. Add install steps if the tests need them.`;
|
|
620
|
+
let smokeGrep = null;
|
|
621
|
+
let smokeSource = 'No @smoke tag found. Every run tests the whole suite.';
|
|
622
|
+
for (const p of files.paths.filter((x) => /smoke/i.test(x) && /\.(spec|test)\.[cm]?[jt]sx?$/.test(x))) {
|
|
623
|
+
if (files.text[p]?.includes('@smoke')) {
|
|
624
|
+
smokeGrep = '@smoke';
|
|
625
|
+
smokeSource = `@smoke tag in ${p}`;
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
const out = { setup, setupSource, command, commandSource, smokeGrep, smokeSource, webServer, notes };
|
|
630
|
+
if (workingDirectory)
|
|
631
|
+
out.workingDirectory = workingDirectory;
|
|
632
|
+
if (configPath)
|
|
633
|
+
out.configPath = configPath;
|
|
634
|
+
if (configUrl)
|
|
635
|
+
out.configUrl = configUrl;
|
|
636
|
+
return out;
|
|
637
|
+
}
|
|
638
|
+
/** Fills a role preset's brief with the repo: "a release" becomes "a release of acme/shop". */
|
|
639
|
+
export function fillPreset(preset, repo) {
|
|
640
|
+
const full = `${repo.owner}/${repo.name}`;
|
|
641
|
+
const fill = (s) => s
|
|
642
|
+
?.replace(/\bthe default branch\b/g, repo.defaultBranch)
|
|
643
|
+
.replace(/\ba release\b/g, `a release of ${full}`)
|
|
644
|
+
.replace(/^A merge\b/, `A merge to ${full}`)
|
|
645
|
+
.replace(/\bMain stays releasable\b/, `${full} ${repo.defaultBranch} stays releasable`);
|
|
646
|
+
const d = preset.defaults;
|
|
647
|
+
const out = { ...d };
|
|
648
|
+
for (const k of ['mission', 'notifyWhen', 'mustNot'])
|
|
649
|
+
if (d[k] !== undefined)
|
|
650
|
+
out[k] = fill(d[k]);
|
|
651
|
+
return out;
|
|
652
|
+
}
|
|
653
|
+
const isLocal = (u) => /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(:|\/|$)/i.test(u);
|
|
654
|
+
/** The base URL: the app the checkout starts, a staging or production deployment, the homepage, or the config URL. */
|
|
655
|
+
export function guessBaseUrl(input) {
|
|
656
|
+
const notes = [];
|
|
657
|
+
const withUrl = input.deployments.filter((d) => d.url && /^https?:\/\//.test(d.url));
|
|
658
|
+
const previews = input.deployments.filter((d) => /preview|pr[-_ ]?\d+|review/i.test(d.environment));
|
|
659
|
+
if (previews.length)
|
|
660
|
+
notes.push('This repo deploys previews. Pull request runs use the preview URL of each PR.');
|
|
661
|
+
const pick = withUrl.find((d) => /stag|qa|test|uat/i.test(d.environment)) ??
|
|
662
|
+
withUrl.find((d) => /prod|live/i.test(d.environment)) ??
|
|
663
|
+
withUrl.find((d) => !/preview|pr[-_ ]?\d+|review/i.test(d.environment));
|
|
664
|
+
const cfg = input.runner.configPath;
|
|
665
|
+
if (input.runner.webServer && (!input.runner.configUrl || isLocal(input.runner.configUrl))) {
|
|
666
|
+
const other = pick?.url ?? input.homepage;
|
|
667
|
+
if (other)
|
|
668
|
+
notes.push(`Found ${other}. Set it as the base URL to test the deployed app instead.`);
|
|
669
|
+
return { value: null, source: `Not needed: ${cfg} starts the app from the checkout`, notes };
|
|
670
|
+
}
|
|
671
|
+
if (pick)
|
|
672
|
+
return { value: pick.url, source: `GitHub deployment to ${pick.environment}`, notes };
|
|
673
|
+
if (input.homepage && /^https?:\/\//.test(input.homepage))
|
|
674
|
+
return { value: input.homepage, source: 'Repo homepage on GitHub', notes };
|
|
675
|
+
if (input.runner.configUrl && !isLocal(input.runner.configUrl))
|
|
676
|
+
return { value: input.runner.configUrl, source: `baseURL in ${cfg}`, notes };
|
|
677
|
+
if (input.runner.configUrl)
|
|
678
|
+
return { value: input.runner.configUrl, source: `baseURL in ${cfg}. Start the app before the tests, or set a deployed URL.`, notes };
|
|
679
|
+
return { value: null, source: 'No deployment, homepage, or Playwright baseURL found', notes };
|
|
680
|
+
}
|
|
681
|
+
// Tracker choices from Setup
|
|
682
|
+
async function trackerChoices(repoName, notes, issues) {
|
|
683
|
+
const { providerOptions, readiness } = await import('../connect/registry.js');
|
|
684
|
+
const choices = [];
|
|
685
|
+
let pick;
|
|
686
|
+
let source = 'No tracker is connected in Setup';
|
|
687
|
+
const near = (a, b) => {
|
|
688
|
+
const x = a.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
689
|
+
const y = b.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
690
|
+
return !!x && !!y && (x.includes(y) || y.includes(x));
|
|
691
|
+
};
|
|
692
|
+
const repo = repoName.toLowerCase();
|
|
693
|
+
if ((await readiness('linear')).state === 'ready') {
|
|
694
|
+
try {
|
|
695
|
+
const teams = await providerOptions('linear', 'teams', {});
|
|
696
|
+
const saved = getMeta('linear').settings?.teamId;
|
|
697
|
+
const mine = teams
|
|
698
|
+
.filter((t) => t.hint)
|
|
699
|
+
.map((t) => ({ kind: 'linear', label: `Linear team ${t.label} (${t.hint})`, tracker: { kind: 'linear', teamKeys: [t.hint] } }));
|
|
700
|
+
choices.push(...mine);
|
|
701
|
+
const savedIdx = teams.findIndex((t) => t.value === saved);
|
|
702
|
+
const nearIdx = teams.findIndex((t) => near(t.label, repo) || near(t.hint ?? '', repo));
|
|
703
|
+
if (savedIdx >= 0) {
|
|
704
|
+
pick = mine[savedIdx];
|
|
705
|
+
source = 'Linear team chosen in Setup';
|
|
706
|
+
}
|
|
707
|
+
else if (nearIdx >= 0) {
|
|
708
|
+
pick = mine[nearIdx];
|
|
709
|
+
source = 'Linear team whose name matches the repo';
|
|
710
|
+
}
|
|
711
|
+
else if (mine.length) {
|
|
712
|
+
pick = mine[0];
|
|
713
|
+
source = mine.length === 1 ? 'The only Linear team' : `First of ${mine.length} Linear teams. Pick another if it is wrong.`;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
catch (err) {
|
|
717
|
+
notes.push(`Could not list Linear teams: ${err instanceof Error ? err.message : String(err)}`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if ((await readiness('jira')).state === 'ready') {
|
|
721
|
+
try {
|
|
722
|
+
const base = setting('JIRA_BASE_URL').replace(/\/$/, '');
|
|
723
|
+
const projects = await providerOptions('jira', 'projects', {});
|
|
724
|
+
const saved = getMeta('jira').settings?.projectKey;
|
|
725
|
+
const mine = projects.map((p) => ({
|
|
726
|
+
kind: 'jira',
|
|
727
|
+
label: `Jira project ${p.label} (${p.value})`,
|
|
728
|
+
tracker: { kind: 'jira', baseUrl: base, projectKeys: [p.value] },
|
|
729
|
+
}));
|
|
730
|
+
choices.push(...mine);
|
|
731
|
+
const savedIdx = projects.findIndex((p) => p.value === saved);
|
|
732
|
+
const nearIdx = projects.findIndex((p) => near(p.label, repo) || near(p.value, repo));
|
|
733
|
+
if (!pick || savedIdx >= 0) {
|
|
734
|
+
if (savedIdx >= 0) {
|
|
735
|
+
pick = mine[savedIdx];
|
|
736
|
+
source = 'Jira project chosen in Setup';
|
|
737
|
+
}
|
|
738
|
+
else if (nearIdx >= 0) {
|
|
739
|
+
pick = mine[nearIdx];
|
|
740
|
+
source = 'Jira project whose name matches the repo';
|
|
741
|
+
}
|
|
742
|
+
else if (mine.length) {
|
|
743
|
+
pick = mine[0];
|
|
744
|
+
source = mine.length === 1 ? 'The only Jira project' : `First of ${mine.length} Jira projects. Pick another if it is wrong.`;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
catch (err) {
|
|
749
|
+
notes.push(`Could not list Jira projects: ${err instanceof Error ? err.message : String(err)}`);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
// GitHub Issues of the repo (KAT-87): always a choice when the repo has issues, the default when no other tracker is connected.
|
|
753
|
+
if (issues) {
|
|
754
|
+
const gi = { kind: 'github', label: `GitHub Issues in ${issues.full}`, tracker: { kind: 'github', repos: [issues.full] } };
|
|
755
|
+
choices.push(gi);
|
|
756
|
+
if (!pick) {
|
|
757
|
+
pick = gi;
|
|
758
|
+
source = 'GitHub Issues of the repo. No other tracker is connected in Setup';
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return { choices, ...(pick ? { pick } : {}), source };
|
|
762
|
+
}
|
|
763
|
+
// The suggestion
|
|
764
|
+
const MAX_OPTIONAL_READS = 20;
|
|
765
|
+
const isPwConfig = (p) => PW_CONFIGS.some((f) => p === f || p.endsWith(`/${f}`)) && p.split('/').length <= 3 && !p.includes('node_modules');
|
|
766
|
+
const isWorkflow = (p) => /^\.github\/workflows\/[^/]+\.ya?ml$/.test(p);
|
|
767
|
+
const isSmokeSpec = (p) => /smoke/i.test(p) && /\.(spec|test)\.[cm]?[jt]sx?$/.test(p) && !p.includes('node_modules');
|
|
768
|
+
/**
|
|
769
|
+
* The files to read from the repo. `package.json` and the Playwright configs come first and are never
|
|
770
|
+
* dropped by the cap: setup steps and the test command depend on them. Workflows (in the order
|
|
771
|
+
* `guessRunner` reads them) and smoke specs share what is left of the cap, which keeps the number of
|
|
772
|
+
* GitHub calls bounded on repos with many of either.
|
|
773
|
+
*/
|
|
774
|
+
export function filesToRead(paths) {
|
|
775
|
+
const required = [...paths.filter((p) => p === 'package.json'), ...paths.filter(isPwConfig)];
|
|
776
|
+
const optional = [...paths.filter(isWorkflow).sort(), ...paths.filter(isSmokeSpec)];
|
|
777
|
+
return [...required, ...optional.slice(0, Math.max(0, MAX_OPTIONAL_READS - required.length))];
|
|
778
|
+
}
|
|
779
|
+
const MAX_NESTED_PACKAGES = 5;
|
|
780
|
+
/**
|
|
781
|
+
* The package.json files of the folders the CI workflows run in (`working-directory`, `defaults.run`,
|
|
782
|
+
* `cd`), for a monorepo whose root package.json holds no scripts (KAT-146). They can only be named
|
|
783
|
+
* once the workflows are read, so suggestDot reads them in a second round.
|
|
784
|
+
*/
|
|
785
|
+
export function nestedPackageFiles(paths, text) {
|
|
786
|
+
const have = new Set(paths);
|
|
787
|
+
const out = new Set();
|
|
788
|
+
for (const wf of paths.filter(isWorkflow).sort()) {
|
|
789
|
+
const src = text[wf];
|
|
790
|
+
if (!src)
|
|
791
|
+
continue;
|
|
792
|
+
for (const job of workflowJobs(src))
|
|
793
|
+
for (const st of job.steps) {
|
|
794
|
+
const dir = checkoutDir(st.workingDirectory);
|
|
795
|
+
if (dir && have.has(`${dir}/package.json`) && text[`${dir}/package.json`] === undefined)
|
|
796
|
+
out.add(`${dir}/package.json`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return [...out].slice(0, MAX_NESTED_PACKAGES);
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* The runner of a new dot with every field checked on its own against validateRunner, so one bad
|
|
803
|
+
* field is left out with a note instead of making POST /api/dots refuse the whole suggestion.
|
|
804
|
+
*/
|
|
805
|
+
export function acceptedRunner(candidate, notes) {
|
|
806
|
+
const out = { mode: candidate.mode };
|
|
807
|
+
for (const [key, value] of Object.entries(candidate)) {
|
|
808
|
+
if (key === 'mode')
|
|
809
|
+
continue;
|
|
810
|
+
const bad = validateRunner({ mode: candidate.mode, [key]: value });
|
|
811
|
+
if (bad)
|
|
812
|
+
notes.push(`The detected ${RUNNER_FIELD_LABEL[key] ?? key} was left out: ${bad}`);
|
|
813
|
+
else
|
|
814
|
+
out[key] = value;
|
|
815
|
+
}
|
|
816
|
+
return out;
|
|
817
|
+
}
|
|
818
|
+
const RUNNER_FIELD_LABEL = { setup: 'setup steps', command: 'test command', workingDirectory: 'working directory' };
|
|
819
|
+
function uniqueName(base, owner) {
|
|
820
|
+
const taken = new Set(list('watches')
|
|
821
|
+
.filter((w) => !w.archived)
|
|
822
|
+
.map((w) => w.name.toLowerCase()));
|
|
823
|
+
if (!taken.has(base.toLowerCase()))
|
|
824
|
+
return base;
|
|
825
|
+
const withOwner = `${base} (${owner})`;
|
|
826
|
+
if (!taken.has(withOwner.toLowerCase()))
|
|
827
|
+
return withOwner;
|
|
828
|
+
for (let i = 2; i < 100; i++)
|
|
829
|
+
if (!taken.has(`${base} ${i}`.toLowerCase()))
|
|
830
|
+
return `${base} ${i}`;
|
|
831
|
+
return `${base} ${Date.now().toString(36)}`;
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Demo mode with no GitHub: the sample shop as the repo, so the first-run flow can make a dot that
|
|
835
|
+
* runs the sample specs (KAT-257). Same shape as a real suggestion; the runner is the demo runner.
|
|
836
|
+
*/
|
|
837
|
+
export async function demoSuggestion() {
|
|
838
|
+
const [owner, name] = DEMO_REPO.split('/');
|
|
839
|
+
const repo = { fullName: DEMO_REPO, owner, name, private: false, defaultBranch: 'main', description: 'The sample shop that demo mode starts.' };
|
|
840
|
+
const base = `http://localhost:${config.sampleAppPort}`;
|
|
841
|
+
const jira = {
|
|
842
|
+
kind: 'jira',
|
|
843
|
+
label: 'Jira project SHOP (demo)',
|
|
844
|
+
tracker: { kind: 'jira', baseUrl: 'https://example.atlassian.net', projectKeys: ['SHOP'] },
|
|
845
|
+
};
|
|
846
|
+
const { readiness } = await import('../connect/registry.js');
|
|
847
|
+
const model = await readiness('model');
|
|
848
|
+
const preset = ROLE_PRESETS.find((p) => p.key === DEFAULT_ROLE);
|
|
849
|
+
const brief = fillPreset(preset, repo);
|
|
850
|
+
const presetSource = `${preset.label} role preset`;
|
|
851
|
+
const dotName = uniqueName(name, owner);
|
|
852
|
+
const input = {
|
|
853
|
+
...brief,
|
|
854
|
+
name: dotName,
|
|
855
|
+
repo: { owner, name, defaultBranch: 'main' },
|
|
856
|
+
runner: { mode: 'demo' },
|
|
857
|
+
tracker: jira.tracker,
|
|
858
|
+
baseUrl: base,
|
|
859
|
+
probes: [{ url: `${base}/health` }],
|
|
860
|
+
};
|
|
861
|
+
return {
|
|
862
|
+
repo,
|
|
863
|
+
name: { value: dotName, source: dotName === name ? 'Repo name' : 'Repo name, made unique among your dots' },
|
|
864
|
+
tracker: { value: jira.tracker, source: 'The demo Jira project', choices: [jira] },
|
|
865
|
+
baseUrl: { value: base, source: 'The sample shop demo mode starts' },
|
|
866
|
+
setup: { value: null, source: 'Not needed: demo mode runs the sample shop specs' },
|
|
867
|
+
command: { value: null, source: 'Demo mode runs the sample shop specs' },
|
|
868
|
+
smokeGrep: { value: null, source: 'Not set' },
|
|
869
|
+
model: { value: model.detail, source: 'Setup. One model serves every dot.' },
|
|
870
|
+
aivision: { value: null, source: 'Off in demo mode' },
|
|
871
|
+
role: { value: preset.key, label: preset.label, source: 'Default role for a new dot' },
|
|
872
|
+
mission: { value: brief.mission ?? null, source: presetSource },
|
|
873
|
+
notifyWhen: { value: brief.notifyWhen ?? null, source: presetSource },
|
|
874
|
+
mustNot: { value: brief.mustNot ?? null, source: presetSource },
|
|
875
|
+
input,
|
|
876
|
+
notes: ['Demo mode: the sample shop stands in for a GitHub repo, and comments go to the outbox.'],
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
export async function suggestDot(full) {
|
|
880
|
+
const rp = repoPath(full);
|
|
881
|
+
if (!rp)
|
|
882
|
+
throw new SuggestError(400, 'repo must look like owner/name.');
|
|
883
|
+
if (config.demo && !githubConnected() && full.toLowerCase() === DEMO_REPO)
|
|
884
|
+
return demoSuggestion();
|
|
885
|
+
if (!githubConnected())
|
|
886
|
+
throw new SuggestError(409, 'Connect GitHub in Setup first, so localdost can read the repo.');
|
|
887
|
+
const gh = client(await tokenFor(full));
|
|
888
|
+
const meta = await gh.get(`/repos/${rp}`);
|
|
889
|
+
if (!meta.body)
|
|
890
|
+
throw new SuggestError(meta.status === 404 ? 404 : 502, meta.status === 404 ? `GitHub has no repo ${full}, or this connection cannot see it.` : `GitHub answered HTTP ${meta.status} for ${full}.`);
|
|
891
|
+
const r = meta.body;
|
|
892
|
+
const choice = repoChoice(r);
|
|
893
|
+
const branch = choice.defaultBranch ?? 'main';
|
|
894
|
+
const repo = { ...choice, defaultBranch: branch };
|
|
895
|
+
const notes = [];
|
|
896
|
+
// Files: one tree read, then only the files the detectors use.
|
|
897
|
+
const tree = await gh.get(`/repos/${rp}/git/trees/${encodeURIComponent(branch)}?recursive=1`);
|
|
898
|
+
const paths = (tree.body?.tree ?? []).filter((t) => t.type === 'blob').map((t) => t.path);
|
|
899
|
+
if (!tree.body)
|
|
900
|
+
notes.push(`Could not read the file list (HTTP ${tree.status}). Setup steps and the test command use defaults.`);
|
|
901
|
+
const text = {};
|
|
902
|
+
await Promise.all(filesToRead(paths).map(async (p) => {
|
|
903
|
+
text[p] = await gh
|
|
904
|
+
.raw(`/repos/${rp}/contents/${p.split('/').map(encodeURIComponent).join('/')}?ref=${encodeURIComponent(branch)}`)
|
|
905
|
+
.catch(() => undefined);
|
|
906
|
+
}));
|
|
907
|
+
await Promise.all(nestedPackageFiles(paths, text).map(async (p) => {
|
|
908
|
+
text[p] = await gh
|
|
909
|
+
.raw(`/repos/${rp}/contents/${p.split('/').map(encodeURIComponent).join('/')}?ref=${encodeURIComponent(branch)}`)
|
|
910
|
+
.catch(() => undefined);
|
|
911
|
+
}));
|
|
912
|
+
const runner = guessRunner({ paths, text });
|
|
913
|
+
notes.push(...runner.notes);
|
|
914
|
+
// Deployments: the latest per environment, with the URL of its newest status.
|
|
915
|
+
const deployments = [];
|
|
916
|
+
const deps = await gh.get(`/repos/${rp}/deployments?per_page=30`);
|
|
917
|
+
const latest = new Map();
|
|
918
|
+
for (const d of deps.body ?? [])
|
|
919
|
+
if (!latest.has(d.environment))
|
|
920
|
+
latest.set(d.environment, d.id);
|
|
921
|
+
await Promise.all([...latest].slice(0, 6).map(async ([environment, id]) => {
|
|
922
|
+
const st = await gh.get(`/repos/${rp}/deployments/${Number(id)}/statuses?per_page=5`);
|
|
923
|
+
const url = (st.body ?? []).find((s) => s.state === 'success' && s.environment_url)?.environment_url;
|
|
924
|
+
deployments.push(url ? { environment, url } : { environment });
|
|
925
|
+
}));
|
|
926
|
+
if (!deps.body?.length) {
|
|
927
|
+
const envs = await gh.get(`/repos/${rp}/environments`);
|
|
928
|
+
for (const e of envs.body?.environments ?? [])
|
|
929
|
+
deployments.push({ environment: e.name });
|
|
930
|
+
}
|
|
931
|
+
const base = guessBaseUrl({ deployments, homepage: typeof r.homepage === 'string' && r.homepage ? r.homepage : undefined, runner });
|
|
932
|
+
notes.push(...base.notes);
|
|
933
|
+
const tracker = await trackerChoices(repo.name, notes, r.has_issues === false ? undefined : { full: choice.fullName });
|
|
934
|
+
const { readiness } = await import('../connect/registry.js');
|
|
935
|
+
const [model, aivision] = await Promise.all([readiness('model'), readiness('aivision')]);
|
|
936
|
+
const aiOn = aivision.state === 'ready';
|
|
937
|
+
const preset = ROLE_PRESETS.find((p) => p.key === DEFAULT_ROLE);
|
|
938
|
+
const brief = fillPreset(preset, repo);
|
|
939
|
+
const presetSource = `${preset.label} role preset`;
|
|
940
|
+
const name = uniqueName(repo.name, repo.owner);
|
|
941
|
+
const candidate = { mode: 'local' };
|
|
942
|
+
if (runner.setup)
|
|
943
|
+
candidate.setup = runner.setup;
|
|
944
|
+
if (runner.command)
|
|
945
|
+
candidate.command = runner.command;
|
|
946
|
+
if (runner.smokeGrep)
|
|
947
|
+
candidate.smokeGrep = runner.smokeGrep;
|
|
948
|
+
if (runner.workingDirectory)
|
|
949
|
+
candidate.workingDirectory = runner.workingDirectory;
|
|
950
|
+
if (aiOn)
|
|
951
|
+
candidate.aivision = { backend: 'server' };
|
|
952
|
+
const runnerInput = acceptedRunner(candidate, notes);
|
|
953
|
+
const input = {
|
|
954
|
+
...brief,
|
|
955
|
+
name,
|
|
956
|
+
repo: { owner: repo.owner, name: repo.name, defaultBranch: branch },
|
|
957
|
+
runner: runnerInput,
|
|
958
|
+
...(tracker.pick ? { tracker: tracker.pick.tracker } : {}),
|
|
959
|
+
...(base.value ? { baseUrl: base.value } : {}),
|
|
960
|
+
};
|
|
961
|
+
return {
|
|
962
|
+
repo,
|
|
963
|
+
name: { value: name, source: name === repo.name ? 'Repo name' : 'Repo name, made unique among your dots' },
|
|
964
|
+
tracker: { value: tracker.pick?.tracker ?? null, source: tracker.source, choices: tracker.choices },
|
|
965
|
+
baseUrl: { value: base.value, source: base.source },
|
|
966
|
+
setup: runnerInput.setup
|
|
967
|
+
? { value: runnerInput.setup, source: runner.setupSource }
|
|
968
|
+
: { value: null, source: runner.setup ? 'Left out: the detected steps are not valid for a dot' : runner.setupSource },
|
|
969
|
+
command: { value: runnerInput.command ?? null, source: runnerInput.command ? runner.commandSource : 'Default: npx playwright test' },
|
|
970
|
+
smokeGrep: { value: runner.smokeGrep, source: runner.smokeSource },
|
|
971
|
+
model: { value: model.detail, source: 'Setup. One model serves every dot.' },
|
|
972
|
+
aivision: {
|
|
973
|
+
value: aiOn ? { backend: 'server' } : null,
|
|
974
|
+
source: aiOn ? 'AI-Vision server connected in Setup' : 'Off. Connect AI-Vision in Setup to add natural-language cases.',
|
|
975
|
+
},
|
|
976
|
+
role: { value: preset.key, label: preset.label, source: 'Default role for a new dot' },
|
|
977
|
+
mission: { value: brief.mission ?? null, source: presetSource },
|
|
978
|
+
notifyWhen: { value: brief.notifyWhen ?? null, source: presetSource },
|
|
979
|
+
mustNot: { value: brief.mustNot ?? null, source: presetSource },
|
|
980
|
+
input,
|
|
981
|
+
notes,
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
export async function register(app) {
|
|
985
|
+
app.get('/api/dots/repos', async (req, reply) => {
|
|
986
|
+
reply.header('Cache-Control', 'no-store');
|
|
987
|
+
return listRepos(typeof req.query.q === 'string' ? req.query.q.slice(0, 200) : '');
|
|
988
|
+
});
|
|
989
|
+
app.get('/api/dots/suggest', async (req, reply) => {
|
|
990
|
+
reply.header('Cache-Control', 'no-store');
|
|
991
|
+
try {
|
|
992
|
+
return await suggestDot(String(req.query.repo ?? '').trim());
|
|
993
|
+
}
|
|
994
|
+
catch (err) {
|
|
995
|
+
if (err instanceof SuggestError)
|
|
996
|
+
return reply.code(err.status).send({ error: err.message });
|
|
997
|
+
const msg = err instanceof Error && err.name === 'TimeoutError' ? 'GitHub did not answer in time.' : err instanceof Error ? err.message : String(err);
|
|
998
|
+
return reply.code(502).send({ error: msg });
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
//# sourceMappingURL=dot-suggest.js.map
|