checkly 9.5.0 → 10.0.0-rc.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/dist/ai-context/context.js +1 -0
- package/dist/ai-context/context.js.map +1 -1
- package/dist/ai-context/onboarding-boilerplate/checkly-config-template.ts +2 -0
- package/dist/ai-context/public-skills/checkly/SKILL.md +18 -5
- package/dist/ai-context/skills-command/references/communicate.md +6 -3
- package/dist/ai-context/skills-command/references/configure-api-checks.md +1 -9
- package/dist/ai-context/skills-command/references/configure-browser-checks.md +1 -8
- package/dist/ai-context/skills-command/references/configure-check-groups.md +1 -0
- package/dist/ai-context/skills-command/references/configure-dns-monitors.md +1 -9
- package/dist/ai-context/skills-command/references/configure-grpc-monitors.md +24 -0
- package/dist/ai-context/skills-command/references/configure-heartbeat-monitors.md +1 -9
- package/dist/ai-context/skills-command/references/configure-icmp-monitors.md +1 -9
- package/dist/ai-context/skills-command/references/configure-multistep-checks.md +1 -9
- package/dist/ai-context/skills-command/references/configure-playwright-checks.md +1 -1
- package/dist/ai-context/skills-command/references/configure-supporting-constructs.md +6 -2
- package/dist/ai-context/skills-command/references/configure-tcp-monitors.md +1 -10
- package/dist/ai-context/skills-command/references/configure-url-monitors.md +1 -10
- package/dist/ai-context/skills-command/references/configure.md +16 -5
- package/dist/ai-context/skills-command/references/initialize.md +11 -3
- package/dist/ai-context/skills-command/references/investigate-checks.md +1 -1
- package/dist/auth/api-key.d.ts +24 -0
- package/dist/auth/api-key.js +104 -0
- package/dist/auth/api-key.js.map +1 -0
- package/dist/auth/device-flow.d.ts +57 -0
- package/dist/auth/device-flow.js +221 -0
- package/dist/auth/device-flow.js.map +1 -0
- package/dist/auth/index.d.ts +6 -6
- package/dist/auth/index.js +64 -64
- package/dist/auth/index.js.map +1 -1
- package/dist/commands/api.js +43 -10
- package/dist/commands/api.js.map +1 -1
- package/dist/commands/authCommand.d.ts +2 -0
- package/dist/commands/authCommand.js +77 -7
- package/dist/commands/authCommand.js.map +1 -1
- package/dist/commands/debug/parse-project.d.ts +1 -0
- package/dist/commands/debug/parse-project.js +5 -2
- package/dist/commands/debug/parse-project.js.map +1 -1
- package/dist/commands/deploy.d.ts +7 -1
- package/dist/commands/deploy.js +627 -271
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/import/plan.js +18 -4
- package/dist/commands/import/plan.js.map +1 -1
- package/dist/commands/init.js +29 -13
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.d.ts +18 -2
- package/dist/commands/login.js +681 -48
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.js +7 -1
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/pw-test.js +3 -2
- package/dist/commands/pw-test.js.map +1 -1
- package/dist/commands/switch.js +23 -10
- package/dist/commands/switch.js.map +1 -1
- package/dist/commands/test.js +16 -17
- package/dist/commands/test.js.map +1 -1
- package/dist/commands/trigger.js +4 -3
- package/dist/commands/trigger.js.map +1 -1
- package/dist/commands/validate.js +1 -0
- package/dist/commands/validate.js.map +1 -1
- package/dist/constants.d.ts +1 -2
- package/dist/constants.js +0 -1
- package/dist/constants.js.map +1 -1
- package/dist/constructs/alert-channel-codegen.d.ts +6 -1
- package/dist/constructs/alert-channel-codegen.js +12 -6
- package/dist/constructs/alert-channel-codegen.js.map +1 -1
- package/dist/constructs/alert-escalation-policy-codegen.d.ts +7 -0
- package/dist/constructs/alert-escalation-policy-codegen.js +25 -8
- package/dist/constructs/alert-escalation-policy-codegen.js.map +1 -1
- package/dist/constructs/api-assertion.d.ts +0 -2
- package/dist/constructs/api-assertion.js +0 -4
- package/dist/constructs/api-assertion.js.map +1 -1
- package/dist/constructs/api-check-codegen.js +1 -1
- package/dist/constructs/api-check-codegen.js.map +1 -1
- package/dist/constructs/api-check.d.ts +4 -22
- package/dist/constructs/api-check.js +15 -17
- package/dist/constructs/api-check.js.map +1 -1
- package/dist/constructs/api-request-codegen.js +10 -8
- package/dist/constructs/api-request-codegen.js.map +1 -1
- package/dist/constructs/browser-check-bundle.d.ts +19 -13
- package/dist/constructs/browser-check-bundle.js +5 -1
- package/dist/constructs/browser-check-bundle.js.map +1 -1
- package/dist/constructs/browser-check.d.ts +1 -4
- package/dist/constructs/browser-check.js +1 -1
- package/dist/constructs/browser-check.js.map +1 -1
- package/dist/constructs/check-codegen.d.ts +39 -14
- package/dist/constructs/check-codegen.js +101 -23
- package/dist/constructs/check-codegen.js.map +1 -1
- package/dist/constructs/check-group-codegen.js +23 -9
- package/dist/constructs/check-group-codegen.js.map +1 -1
- package/dist/constructs/check-group-v1.d.ts +19 -14
- package/dist/constructs/check-group-v1.js +39 -14
- package/dist/constructs/check-group-v1.js.map +1 -1
- package/dist/constructs/check-group-v2.d.ts +0 -20
- package/dist/constructs/check-group-v2.js +0 -4
- package/dist/constructs/check-group-v2.js.map +1 -1
- package/dist/constructs/check.d.ts +45 -32
- package/dist/constructs/check.js +78 -17
- package/dist/constructs/check.js.map +1 -1
- package/dist/constructs/construct-codegen.d.ts +1 -2
- package/dist/constructs/construct-diagnostics.d.ts +3 -0
- package/dist/constructs/construct-diagnostics.js +11 -0
- package/dist/constructs/construct-diagnostics.js.map +1 -1
- package/dist/constructs/construct.d.ts +25 -4
- package/dist/constructs/construct.js +41 -5
- package/dist/constructs/construct.js.map +1 -1
- package/dist/constructs/dns-request-codegen.js +1 -1
- package/dist/constructs/dns-request-codegen.js.map +1 -1
- package/dist/constructs/email-alert-channel-codegen.js +1 -1
- package/dist/constructs/email-alert-channel-codegen.js.map +1 -1
- package/dist/constructs/frequency-codegen.js +10 -2
- package/dist/constructs/frequency-codegen.js.map +1 -1
- package/dist/constructs/frequency.d.ts +8 -3
- package/dist/constructs/frequency.js +7 -2
- package/dist/constructs/frequency.js.map +1 -1
- package/dist/constructs/grpc-monitor.js +19 -0
- package/dist/constructs/grpc-monitor.js.map +1 -1
- package/dist/constructs/grpc-request-codegen.js +13 -4
- package/dist/constructs/grpc-request-codegen.js.map +1 -1
- package/dist/constructs/grpc-request.d.ts +23 -0
- package/dist/constructs/heartbeat-monitor.d.ts +2 -1
- package/dist/constructs/heartbeat-monitor.js +2 -2
- package/dist/constructs/heartbeat-monitor.js.map +1 -1
- package/dist/constructs/icmp-request-codegen.js +1 -1
- package/dist/constructs/icmp-request-codegen.js.map +1 -1
- package/dist/constructs/incidentio-alert-channel-codegen.d.ts +1 -1
- package/dist/constructs/incidentio-alert-channel-codegen.js +14 -8
- package/dist/constructs/incidentio-alert-channel-codegen.js.map +1 -1
- package/dist/constructs/index.d.ts +1 -1
- package/dist/constructs/index.js +1 -1
- package/dist/constructs/index.js.map +1 -1
- package/dist/constructs/internal/assertion-validation.js +1 -2
- package/dist/constructs/internal/assertion-validation.js.map +1 -1
- package/dist/constructs/internal/codegen/context.d.ts +73 -6
- package/dist/constructs/internal/codegen/context.js +78 -6
- package/dist/constructs/internal/codegen/context.js.map +1 -1
- package/dist/constructs/internal/codegen/index.d.ts +3 -1
- package/dist/constructs/internal/codegen/index.js +2 -1
- package/dist/constructs/internal/codegen/index.js.map +1 -1
- package/dist/constructs/internal/codegen/render.d.ts +68 -0
- package/dist/constructs/internal/codegen/render.js +75 -0
- package/dist/constructs/internal/codegen/render.js.map +1 -0
- package/dist/constructs/internal/common-diagnostics.d.ts +1 -2
- package/dist/constructs/internal/common-diagnostics.js +6 -14
- package/dist/constructs/internal/common-diagnostics.js.map +1 -1
- package/dist/constructs/internal/declaring-file.d.ts +39 -0
- package/dist/constructs/internal/declaring-file.js +139 -0
- package/dist/constructs/internal/declaring-file.js.map +1 -0
- package/dist/constructs/internal/retry-strategy-defaults.d.ts +13 -0
- package/dist/constructs/internal/retry-strategy-defaults.js +14 -0
- package/dist/constructs/internal/retry-strategy-defaults.js.map +1 -0
- package/dist/constructs/key-value-pair-codegen.d.ts +6 -1
- package/dist/constructs/key-value-pair-codegen.js +24 -3
- package/dist/constructs/key-value-pair-codegen.js.map +1 -1
- package/dist/constructs/maintenance-window-codegen.d.ts +7 -0
- package/dist/constructs/maintenance-window-codegen.js +36 -19
- package/dist/constructs/maintenance-window-codegen.js.map +1 -1
- package/dist/constructs/maintenance-window.d.ts +35 -3
- package/dist/constructs/maintenance-window.js +35 -1
- package/dist/constructs/maintenance-window.js.map +1 -1
- package/dist/constructs/monitor.d.ts +1 -1
- package/dist/constructs/msteams-alert-channel-codegen.d.ts +1 -1
- package/dist/constructs/msteams-alert-channel-codegen.js +8 -18
- package/dist/constructs/msteams-alert-channel-codegen.js.map +1 -1
- package/dist/constructs/opsgenie-alert-channel-codegen.js +1 -1
- package/dist/constructs/opsgenie-alert-channel-codegen.js.map +1 -1
- package/dist/constructs/pagerduty-alert-channel-codegen.js +1 -1
- package/dist/constructs/pagerduty-alert-channel-codegen.js.map +1 -1
- package/dist/constructs/phone-call-alert-channel-codegen.js +1 -1
- package/dist/constructs/phone-call-alert-channel-codegen.js.map +1 -1
- package/dist/constructs/playwright-check-bundle.d.ts +7 -1
- package/dist/constructs/playwright-check-bundle.js +3 -0
- package/dist/constructs/playwright-check-bundle.js.map +1 -1
- package/dist/constructs/playwright-check-codegen.d.ts +85 -0
- package/dist/constructs/playwright-check-codegen.js +212 -0
- package/dist/constructs/playwright-check-codegen.js.map +1 -0
- package/dist/constructs/playwright-check.d.ts +14 -24
- package/dist/constructs/playwright-check.js +16 -52
- package/dist/constructs/playwright-check.js.map +1 -1
- package/dist/constructs/retry-strategy-codegen.d.ts +7 -1
- package/dist/constructs/retry-strategy-codegen.js +41 -32
- package/dist/constructs/retry-strategy-codegen.js.map +1 -1
- package/dist/constructs/retry-strategy.d.ts +0 -4
- package/dist/constructs/retry-strategy.js +5 -8
- package/dist/constructs/retry-strategy.js.map +1 -1
- package/dist/constructs/session.d.ts +26 -1
- package/dist/constructs/session.js +33 -2
- package/dist/constructs/session.js.map +1 -1
- package/dist/constructs/slack-alert-channel-codegen.js +1 -1
- package/dist/constructs/slack-alert-channel-codegen.js.map +1 -1
- package/dist/constructs/slack-app-alert-channel-codegen.js +1 -1
- package/dist/constructs/slack-app-alert-channel-codegen.js.map +1 -1
- package/dist/constructs/sms-alert-channel-codegen.js +1 -1
- package/dist/constructs/sms-alert-channel-codegen.js.map +1 -1
- package/dist/constructs/ssl-request-codegen.js +4 -3
- package/dist/constructs/ssl-request-codegen.js.map +1 -1
- package/dist/constructs/status-page-v3-automation-rule-codegen.js +5 -4
- package/dist/constructs/status-page-v3-automation-rule-codegen.js.map +1 -1
- package/dist/constructs/status-page-v3-codegen.js +3 -2
- package/dist/constructs/status-page-v3-codegen.js.map +1 -1
- package/dist/constructs/status-page-v3-component-codegen.js +8 -7
- package/dist/constructs/status-page-v3-component-codegen.js.map +1 -1
- package/dist/constructs/tcp-monitor-codegen.js +3 -2
- package/dist/constructs/tcp-monitor-codegen.js.map +1 -1
- package/dist/constructs/tcp-monitor.d.ts +4 -3
- package/dist/constructs/tcp-monitor.js +3 -2
- package/dist/constructs/tcp-monitor.js.map +1 -1
- package/dist/constructs/telegram-alert-channel-codegen.d.ts +1 -1
- package/dist/constructs/telegram-alert-channel-codegen.js +14 -8
- package/dist/constructs/telegram-alert-channel-codegen.js.map +1 -1
- package/dist/constructs/time-zone.d.ts +24 -0
- package/dist/constructs/time-zone.js +455 -0
- package/dist/constructs/time-zone.js.map +1 -0
- package/dist/constructs/traceroute-request-codegen.js +2 -2
- package/dist/constructs/traceroute-request-codegen.js.map +1 -1
- package/dist/constructs/url-monitor-codegen.js +1 -1
- package/dist/constructs/url-monitor-codegen.js.map +1 -1
- package/dist/constructs/url-monitor.d.ts +3 -2
- package/dist/constructs/url-monitor.js +3 -0
- package/dist/constructs/url-monitor.js.map +1 -1
- package/dist/constructs/url-request-codegen.js +4 -3
- package/dist/constructs/url-request-codegen.js.map +1 -1
- package/dist/constructs/webhook-alert-channel-codegen.js +3 -3
- package/dist/constructs/webhook-alert-channel-codegen.js.map +1 -1
- package/dist/formatters/check-result-detail.js +0 -185
- package/dist/formatters/check-result-detail.js.map +1 -1
- package/dist/helpers/activate-account.d.ts +14 -0
- package/dist/helpers/activate-account.js +25 -0
- package/dist/helpers/activate-account.js.map +1 -0
- package/dist/helpers/cli-mode.d.ts +18 -0
- package/dist/helpers/cli-mode.js +41 -2
- package/dist/helpers/cli-mode.js.map +1 -1
- package/dist/helpers/command-preview.d.ts +44 -0
- package/dist/helpers/command-preview.js +29 -9
- package/dist/helpers/command-preview.js.map +1 -1
- package/dist/helpers/flags.d.ts +10 -0
- package/dist/helpers/flags.js +17 -0
- package/dist/helpers/flags.js.map +1 -1
- package/dist/helpers/onboarding/boilerplate.js +13 -1
- package/dist/helpers/onboarding/boilerplate.js.map +1 -1
- package/dist/helpers/onboarding/index.d.ts +1 -1
- package/dist/helpers/onboarding/index.js +1 -1
- package/dist/helpers/onboarding/index.js.map +1 -1
- package/dist/helpers/onboarding/messages.d.ts +6 -0
- package/dist/helpers/onboarding/messages.js +14 -0
- package/dist/helpers/onboarding/messages.js.map +1 -1
- package/dist/helpers/onboarding/skill-install.js +2 -2
- package/dist/helpers/onboarding/skill-install.js.map +1 -1
- package/dist/helpers/wrap.js +1 -1
- package/dist/helpers/wrap.js.map +1 -1
- package/dist/reporters/abstract-list.js +11 -2
- package/dist/reporters/abstract-list.js.map +1 -1
- package/dist/reporters/github.js +3 -2
- package/dist/reporters/github.js.map +1 -1
- package/dist/reporters/util.d.ts +1 -0
- package/dist/reporters/util.js +5 -58
- package/dist/reporters/util.js.map +1 -1
- package/dist/rest/analytics.js +0 -4
- package/dist/rest/analytics.js.map +1 -1
- package/dist/rest/api.d.ts +8 -1
- package/dist/rest/api.js +21 -3
- package/dist/rest/api.js.map +1 -1
- package/dist/rest/check-results.d.ts +1 -50
- package/dist/rest/check-results.js.map +1 -1
- package/dist/rest/checkly-storage.d.ts +6 -1
- package/dist/rest/checkly-storage.js +13 -2
- package/dist/rest/checkly-storage.js.map +1 -1
- package/dist/rest/errors.d.ts +13 -0
- package/dist/rest/errors.js +5 -1
- package/dist/rest/errors.js.map +1 -1
- package/dist/rest/projects.d.ts +254 -4
- package/dist/rest/projects.js +184 -12
- package/dist/rest/projects.js.map +1 -1
- package/dist/services/abstract-check-runner.d.ts +11 -0
- package/dist/services/abstract-check-runner.js +98 -46
- package/dist/services/abstract-check-runner.js.map +1 -1
- package/dist/services/check-parser/bundler.d.ts +26 -0
- package/dist/services/check-parser/bundler.js +81 -7
- package/dist/services/check-parser/bundler.js.map +1 -1
- package/dist/services/check-parser/cache-hash.d.ts +1 -4
- package/dist/services/check-parser/cache-hash.js.map +1 -1
- package/dist/services/checkly-config-codegen.js +1 -4
- package/dist/services/checkly-config-codegen.js.map +1 -1
- package/dist/services/checkly-config-loader.d.ts +7 -33
- package/dist/services/checkly-config-loader.js +50 -47
- package/dist/services/checkly-config-loader.js.map +1 -1
- package/dist/services/config.d.ts +2 -0
- package/dist/services/config.js +14 -0
- package/dist/services/config.js.map +1 -1
- package/dist/services/content-hash.d.ts +14 -0
- package/dist/services/content-hash.js +22 -0
- package/dist/services/content-hash.js.map +1 -0
- package/dist/services/deploy-diff/diff-lines.d.ts +38 -0
- package/dist/services/deploy-diff/diff-lines.js +191 -0
- package/dist/services/deploy-diff/diff-lines.js.map +1 -0
- package/dist/services/deploy-diff/import-shape.d.ts +171 -0
- package/dist/services/deploy-diff/import-shape.js +633 -0
- package/dist/services/deploy-diff/import-shape.js.map +1 -0
- package/dist/services/deploy-diff/legacy-payload.d.ts +22 -0
- package/dist/services/deploy-diff/legacy-payload.js +51 -0
- package/dist/services/deploy-diff/legacy-payload.js.map +1 -0
- package/dist/services/deploy-diff/plan-summary.d.ts +37 -0
- package/dist/services/deploy-diff/plan-summary.js +154 -0
- package/dist/services/deploy-diff/plan-summary.js.map +1 -0
- package/dist/services/deploy-diff/preview-output.d.ts +61 -0
- package/dist/services/deploy-diff/preview-output.js +394 -0
- package/dist/services/deploy-diff/preview-output.js.map +1 -0
- package/dist/services/deploy-diff/render.d.ts +53 -0
- package/dist/services/deploy-diff/render.js +317 -0
- package/dist/services/deploy-diff/render.js.map +1 -0
- package/dist/services/deploy-diff/shape-changes.d.ts +2 -0
- package/dist/services/deploy-diff/shape-changes.js +35 -0
- package/dist/services/deploy-diff/shape-changes.js.map +1 -0
- package/dist/services/deploy-diff/source-index.d.ts +18 -0
- package/dist/services/deploy-diff/source-index.js +48 -0
- package/dist/services/deploy-diff/source-index.js.map +1 -0
- package/dist/services/deploy-diff/variable-names.d.ts +30 -0
- package/dist/services/deploy-diff/variable-names.js +64 -0
- package/dist/services/deploy-diff/variable-names.js.map +1 -0
- package/dist/services/project-parser.d.ts +5 -0
- package/dist/services/project-parser.js +25 -13
- package/dist/services/project-parser.js.map +1 -1
- package/dist/services/snapshot-service.d.ts +25 -8
- package/dist/services/snapshot-service.js +46 -4
- package/dist/services/snapshot-service.js.map +1 -1
- package/dist/services/test-filters.d.ts +18 -0
- package/dist/services/test-filters.js +10 -0
- package/dist/services/test-filters.js.map +1 -1
- package/dist/services/test-runner.js +7 -3
- package/dist/services/test-runner.js.map +1 -1
- package/dist/services/test-session-cancel.js +1 -1
- package/dist/services/test-session-cancel.js.map +1 -1
- package/dist/services/util.d.ts +17 -2
- package/dist/services/util.js +118 -11
- package/dist/services/util.js.map +1 -1
- package/dist/services/write-back/apply-edits.d.ts +29 -0
- package/dist/services/write-back/apply-edits.js +255 -0
- package/dist/services/write-back/apply-edits.js.map +1 -0
- package/dist/services/write-back/helper-edit.d.ts +135 -0
- package/dist/services/write-back/helper-edit.js +343 -0
- package/dist/services/write-back/helper-edit.js.map +1 -0
- package/dist/services/write-back/imports.d.ts +30 -0
- package/dist/services/write-back/imports.js +82 -0
- package/dist/services/write-back/imports.js.map +1 -0
- package/dist/services/write-back/literal-edit.d.ts +183 -0
- package/dist/services/write-back/literal-edit.js +359 -0
- package/dist/services/write-back/literal-edit.js.map +1 -0
- package/dist/services/write-back/output.d.ts +26 -0
- package/dist/services/write-back/output.js +110 -0
- package/dist/services/write-back/output.js.map +1 -0
- package/dist/services/write-back/plan.d.ts +173 -0
- package/dist/services/write-back/plan.js +968 -0
- package/dist/services/write-back/plan.js.map +1 -0
- package/dist/services/write-back/source-file.d.ts +118 -0
- package/dist/services/write-back/source-file.js +432 -0
- package/dist/services/write-back/source-file.js.map +1 -0
- package/dist/sourcegen/index.d.ts +1 -1
- package/dist/sourcegen/index.js +1 -1
- package/dist/sourcegen/index.js.map +1 -1
- package/dist/sourcegen/program.d.ts +19 -1
- package/dist/sourcegen/program.js +32 -7
- package/dist/sourcegen/program.js.map +1 -1
- package/oclif.manifest.json +676 -635
- package/package.json +13 -13
- package/dist/commands/rules.d.ts +0 -9
- package/dist/commands/rules.js +0 -13
- package/dist/commands/rules.js.map +0 -1
- package/dist/constructs/agentic-check-codegen.d.ts +0 -22
- package/dist/constructs/agentic-check-codegen.js +0 -59
- package/dist/constructs/agentic-check-codegen.js.map +0 -1
- package/dist/constructs/agentic-check.d.ts +0 -237
- package/dist/constructs/agentic-check.js +0 -100
- package/dist/constructs/agentic-check.js.map +0 -1
package/dist/commands/login.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
+
var _a;
|
|
1
2
|
import open from 'open';
|
|
2
3
|
import chalk from 'chalk';
|
|
3
|
-
import {
|
|
4
|
+
import { Flags } from '@oclif/core';
|
|
4
5
|
import prompts from 'prompts';
|
|
6
|
+
import { BaseCommand } from './baseCommand.js';
|
|
5
7
|
import config from '../services/config.js';
|
|
6
8
|
import * as api from '../rest/api.js';
|
|
9
|
+
import { ApiError, ForbiddenError, MissingResponseError, NotFoundError, ProxyConnectionError, UnauthorizedError, } from '../rest/errors.js';
|
|
7
10
|
import { AuthContext } from '../auth/index.js';
|
|
11
|
+
import { DeviceFlow, DeviceFlowError, DeviceFlowNotAllowedError, } from '../auth/device-flow.js';
|
|
12
|
+
import { credentialsFromTokens } from '../auth/api-key.js';
|
|
13
|
+
import { isPersonAtTerminal, detectCliMode, isEnvFlagSet } from '../helpers/cli-mode.js';
|
|
14
|
+
import { activateAccount } from '../helpers/activate-account.js';
|
|
8
15
|
import commonMessages from '../messages/common-messages.js';
|
|
16
|
+
import { detectPackageManager } from '../services/check-parser/package-files/package-manager.js';
|
|
17
|
+
import { getChecklyConfigFile } from '../services/checkly-config-loader.js';
|
|
18
|
+
import { shellQuote } from '../services/shell.js';
|
|
9
19
|
export const selectAccount = async (accounts, { onCancel }) => {
|
|
10
20
|
const { selectedAccount } = await prompts({
|
|
11
21
|
name: 'selectedAccount',
|
|
@@ -15,67 +25,645 @@ export const selectAccount = async (accounts, { onCancel }) => {
|
|
|
15
25
|
}, { onCancel });
|
|
16
26
|
return selectedAccount;
|
|
17
27
|
};
|
|
18
|
-
|
|
28
|
+
/** A login failure with the `reason` code agent-mode output reports for it. */
|
|
29
|
+
class LoginError extends Error {
|
|
30
|
+
reason;
|
|
31
|
+
constructor(reason, message) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.reason = reason;
|
|
34
|
+
this.name = 'LoginError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Classifies a login failure: the device-flow outcomes (`network_error` when
|
|
39
|
+
* the login server cannot be reached), the account problems raised by the
|
|
40
|
+
* login itself, `api_error` for a failed or unreachable Checkly API and
|
|
41
|
+
* `login_failed` for anything else. (`env_credentials` is
|
|
42
|
+
* reported before any of these can happen.)
|
|
43
|
+
*/
|
|
44
|
+
function errorReason(error) {
|
|
45
|
+
if (error instanceof LoginError) {
|
|
46
|
+
return error.reason;
|
|
47
|
+
}
|
|
48
|
+
if (error instanceof DeviceFlowError) {
|
|
49
|
+
switch (error.code) {
|
|
50
|
+
case 'access_denied':
|
|
51
|
+
case 'expired_token':
|
|
52
|
+
case 'invalid_response':
|
|
53
|
+
case 'network_error':
|
|
54
|
+
return error.code;
|
|
55
|
+
case 'invalid_grant':
|
|
56
|
+
return 'code_used';
|
|
57
|
+
}
|
|
58
|
+
return 'login_failed';
|
|
59
|
+
}
|
|
60
|
+
// validateAuthentication() rewords rejected stored credentials into a plain
|
|
61
|
+
// Error that keeps the API error as its cause.
|
|
62
|
+
const apiError = error instanceof Error && error.cause instanceof ApiError ? error.cause : error;
|
|
63
|
+
if (apiError instanceof ApiError || apiError instanceof MissingResponseError
|
|
64
|
+
|| apiError instanceof ProxyConnectionError) {
|
|
65
|
+
return 'api_error';
|
|
66
|
+
}
|
|
67
|
+
return 'login_failed';
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* What to do after logging in, in the current directory's package manager:
|
|
71
|
+
* run the checks of a Checkly project, or set one up. Undefined when that
|
|
72
|
+
* cannot be worked out; the hint is never worth failing a login over.
|
|
73
|
+
*/
|
|
74
|
+
async function nextStepHint() {
|
|
75
|
+
try {
|
|
76
|
+
const packageManager = await detectPackageManager(process.cwd());
|
|
77
|
+
const command = (subcommand) => packageManager.execCommand(['checkly', subcommand]).unsafeDisplayCommand;
|
|
78
|
+
return await getChecklyConfigFile()
|
|
79
|
+
? `To run your checks, run \`${command('test')}\`.`
|
|
80
|
+
: `To create checks for a project, run \`${command('init')}\`.`;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** `https://auth.checklyhq.com/activate` as `auth.checklyhq.com/activate`. */
|
|
87
|
+
function withoutScheme(url) {
|
|
88
|
+
return url.replace(/^https?:\/\//, '');
|
|
89
|
+
}
|
|
90
|
+
/** What Auth0 says when the user cancels on the activation page. */
|
|
91
|
+
const AUTH0_CANCELLED_MESSAGE = 'User did not confirm their request';
|
|
92
|
+
/**
|
|
93
|
+
* The message of a login failure, in our own words for a login cancelled in
|
|
94
|
+
* the browser. Other denials (e.g. by a tenant rule) keep the login
|
|
95
|
+
* server's message, the only place their reason appears.
|
|
96
|
+
*/
|
|
97
|
+
function errorMessage(error) {
|
|
98
|
+
if (error instanceof DeviceFlowError && error.code === 'access_denied'
|
|
99
|
+
&& error.message === AUTH0_CANCELLED_MESSAGE) {
|
|
100
|
+
return 'The login was cancelled in the browser.';
|
|
101
|
+
}
|
|
102
|
+
// An OAuth error without a reason of its own: name its code, the only
|
|
103
|
+
// thing that identifies it for support.
|
|
104
|
+
if (error instanceof DeviceFlowError && errorReason(error) === 'login_failed'
|
|
105
|
+
&& !error.message.includes(`(${error.code})`)) {
|
|
106
|
+
return `The login server refused the login: ${error.message} (${error.code})`;
|
|
107
|
+
}
|
|
108
|
+
return error?.message || String(error);
|
|
109
|
+
}
|
|
110
|
+
// An agent relays a stored code again only while there is still time to
|
|
111
|
+
// approve it.
|
|
112
|
+
const MIN_RELAY_TIME_MS = 60_000;
|
|
113
|
+
class Login extends BaseCommand {
|
|
19
114
|
static hidden = false;
|
|
20
115
|
static idempotent = true;
|
|
21
116
|
static description = 'Login to your Checkly account or create a new one.';
|
|
22
|
-
|
|
117
|
+
static examples = [
|
|
118
|
+
'$ npx checkly login',
|
|
119
|
+
'$ npx checkly login --account-id <id>',
|
|
120
|
+
];
|
|
121
|
+
static flags = {
|
|
122
|
+
'account-id': Flags.string({
|
|
123
|
+
description: 'Use this account instead of asking which one. When already logged in, switch to it '
|
|
124
|
+
+ 'without logging in again, like `checkly switch --account-id`.',
|
|
125
|
+
}),
|
|
126
|
+
'no-browser': Flags.boolean({
|
|
127
|
+
description: 'Only print the login URL and code; do not try to open a browser. '
|
|
128
|
+
+ 'Also honoured through CHECKLY_NO_BROWSER=1, e.g. on headless hosts.',
|
|
129
|
+
default: false,
|
|
130
|
+
}),
|
|
131
|
+
};
|
|
132
|
+
#mode = 'interactive';
|
|
133
|
+
#openBrowser = true;
|
|
134
|
+
#inline = false;
|
|
135
|
+
// Why a stored login stopped working, for the agent to pass on with the
|
|
136
|
+
// next step (a new code, account selection or success).
|
|
137
|
+
#notice;
|
|
138
|
+
// Whether the interactive output so far ends with an empty line, so the
|
|
139
|
+
// next block is separated by exactly one.
|
|
140
|
+
#atBlankLine = false;
|
|
141
|
+
// `--account-id` as given, so the command to run after approval keeps it.
|
|
142
|
+
#requestedAccountId;
|
|
143
|
+
async run() {
|
|
144
|
+
const { flags } = await this.parse(_a);
|
|
145
|
+
const ok = await this.login({ accountId: flags['account-id'], openBrowser: !flags['no-browser'] });
|
|
146
|
+
return this.exit(ok ? 0 : 1);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Runs the whole login flow for the detected CLI mode and stores the
|
|
150
|
+
* credentials. Returns false when the flow did not complete in agent mode
|
|
151
|
+
* (the JSON error line has already been printed); throws otherwise.
|
|
152
|
+
* Other commands call this with `inline: true` to log the user in before
|
|
153
|
+
* they run; the login then writes to stderr so the command's own stdout
|
|
154
|
+
* (e.g. `--output json`) stays clean.
|
|
155
|
+
*/
|
|
156
|
+
async login(options = {}) {
|
|
157
|
+
this.#mode = detectCliMode();
|
|
158
|
+
this.#inline = options.inline ?? false;
|
|
159
|
+
this.#notice = undefined;
|
|
160
|
+
this.#requestedAccountId = options.accountId;
|
|
161
|
+
this.#openBrowser = (options.openBrowser ?? true) && !isEnvFlagSet(process.env.CHECKLY_NO_BROWSER);
|
|
23
162
|
if (config.hasEnvVarsConfigured()) {
|
|
24
|
-
|
|
25
|
-
this
|
|
163
|
+
const message = `${commonMessages.envCredentialsConfigured} You must delete them to use \`npx checkly login\`.`;
|
|
164
|
+
if (this.#mode === 'agent') {
|
|
165
|
+
// Not a success: the variables may be incomplete or wrong, and only
|
|
166
|
+
// the user can remove them.
|
|
167
|
+
const reason = 'env_credentials';
|
|
168
|
+
this.#print(JSON.stringify({ status: 'error', reason, userActionRequired: true, message }));
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
this.warn(message);
|
|
172
|
+
return true;
|
|
26
173
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
174
|
+
// `--account-id` naming a different account switches to it with the
|
|
175
|
+
// stored key; it is not a request to log in again.
|
|
176
|
+
const switchingAccount = config.hasValidCredentials()
|
|
177
|
+
&& Boolean(options.accountId) && options.accountId !== config.getAccountId();
|
|
178
|
+
try {
|
|
179
|
+
// A stored login is only kept as it is while it still works. Dropping an
|
|
180
|
+
// unusable account sends the flow below into the stored-key resume path:
|
|
181
|
+
// account selection with a working key, a new login otherwise.
|
|
182
|
+
if (config.hasValidCredentials() && !switchingAccount) {
|
|
183
|
+
const storedAccount = await this.#checkStoredAccount();
|
|
184
|
+
if (storedAccount.usable && !await this.#wantsToReplaceLogin(storedAccount.name)) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
if (!storedAccount.usable) {
|
|
188
|
+
this.#notice = `Account "${storedAccount.name}" is no longer available with the stored login.`;
|
|
189
|
+
if (this.#mode !== 'agent') {
|
|
190
|
+
this.#print(this.#notice);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// The stored key is reused when switching accounts, and when a previous
|
|
195
|
+
// login stored it but stopped before an account was chosen (agent mode
|
|
196
|
+
// with several accounts, or a cancelled account prompt).
|
|
197
|
+
const reuseStoredKey = switchingAccount || (Boolean(config.getApiKey()) && !config.getAccountId());
|
|
198
|
+
let userName = reuseStoredKey ? await this.#storedKeyUserName() : undefined;
|
|
199
|
+
const usingStoredKey = userName !== undefined;
|
|
200
|
+
if (userName === undefined) {
|
|
201
|
+
if (this.#mode === 'ci') {
|
|
202
|
+
this.error((reuseStoredKey ? 'The stored login is no longer valid and was removed. ' : '')
|
|
203
|
+
+ '`npx checkly login` needs a browser. In CI, set `CHECKLY_API_KEY` and `CHECKLY_ACCOUNT_ID` '
|
|
204
|
+
+ 'in the environment instead.', { exit: 1 });
|
|
205
|
+
}
|
|
206
|
+
if (this.#mode === 'interactive' && !isPersonAtTerminal()) {
|
|
207
|
+
this.error((reuseStoredKey ? 'The stored login is no longer valid and was removed. ' : '')
|
|
208
|
+
+ '`npx checkly login` needs a terminal to show the login code and wait for it. '
|
|
209
|
+
+ 'Run it in a terminal, or set `CHECKLY_API_KEY` and `CHECKLY_ACCOUNT_ID` in the environment. '
|
|
210
|
+
+ 'If you are at a terminal that is not detected as one, set `CHECKLY_CLI_MODE=interactive`; '
|
|
211
|
+
+ 'AI agents set `CHECKLY_CLI_MODE=agent` instead.', { exit: 1 });
|
|
212
|
+
}
|
|
213
|
+
if (reuseStoredKey && this.#mode === 'interactive') {
|
|
214
|
+
this.#print('The stored login is no longer valid. Logging in again.');
|
|
215
|
+
}
|
|
216
|
+
const result = await this.#authenticate();
|
|
217
|
+
if (result === 'pending') {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
if (result === 'stored') {
|
|
221
|
+
userName = await this.#storedKeyUserName();
|
|
222
|
+
if (userName === undefined) {
|
|
223
|
+
throw new LoginError('code_used', 'The login code was already used. Please run the login again.');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
this.#storeNewKey(result.key);
|
|
228
|
+
userName = result.name;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else if (this.#mode === 'interactive' && !switchingAccount && !this.#notice) {
|
|
232
|
+
this.#print(`Continuing the login as ${chalk.bold(userName)}. `
|
|
233
|
+
+ 'Run `npx checkly logout` first to log in as someone else.');
|
|
234
|
+
}
|
|
235
|
+
const { data: accounts } = await api.accounts.getAll();
|
|
236
|
+
const accountSummaries = accounts.map(({ id, name }) => ({ id, name }));
|
|
237
|
+
const account = await this.#pickAccount(accounts, options.accountId, usingStoredKey);
|
|
238
|
+
if (!account) {
|
|
239
|
+
// Agent mode, several accounts, none requested: do not guess.
|
|
240
|
+
// (CI never gets here; #pickAccount throws instead.)
|
|
241
|
+
// A person has to choose; the agent must not pick an account itself.
|
|
242
|
+
this.#print(JSON.stringify({
|
|
243
|
+
status: 'action_required',
|
|
244
|
+
reason: 'select_account',
|
|
245
|
+
userActionRequired: true,
|
|
246
|
+
message: (this.#notice ? `${this.#notice} ` : '')
|
|
247
|
+
+ 'Logged in, but this user belongs to several accounts. Ask the user which one to use, then run '
|
|
248
|
+
+ '`npx checkly login --account-id <id>`'
|
|
249
|
+
+ (this.#inline ? ' and run the original command again' : '')
|
|
250
|
+
+ '. To log in as someone else instead, run `npx checkly logout` first.',
|
|
251
|
+
user: userName,
|
|
252
|
+
choices: accountSummaries,
|
|
253
|
+
next: [{ command: 'npx checkly login --account-id <id>' }],
|
|
254
|
+
}));
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
await activateAccount(account);
|
|
258
|
+
const switched = switchingAccount && usingStoredKey;
|
|
259
|
+
if (this.#mode === 'agent') {
|
|
260
|
+
this.#print(JSON.stringify({
|
|
261
|
+
status: 'success',
|
|
262
|
+
reason: switched ? 'account_switched' : 'logged_in',
|
|
263
|
+
message: (this.#notice ? `${this.#notice} ` : '') + (switched
|
|
264
|
+
? `Switched to account "${account.name}".`
|
|
265
|
+
: `Logged in as ${userName} to account "${account.name}".`),
|
|
266
|
+
user: userName,
|
|
267
|
+
accountId: account.id,
|
|
268
|
+
accountName: account.name,
|
|
269
|
+
}));
|
|
36
270
|
}
|
|
271
|
+
else if (switched) {
|
|
272
|
+
this.#print(`Switched to account ${chalk.cyan.bold(account.name)} (${account.id})`);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
if (this.#mode === 'interactive' && !this.#atBlankLine) {
|
|
276
|
+
this.#print('');
|
|
277
|
+
}
|
|
278
|
+
this.#print(`Logged in as ${chalk.cyan.bold(userName)} to ${chalk.cyan.bold(account.name)}.`);
|
|
279
|
+
// A login another command started goes straight on to that command.
|
|
280
|
+
const hint = this.#mode === 'interactive' && !this.#inline ? await nextStepHint() : undefined;
|
|
281
|
+
if (hint) {
|
|
282
|
+
this.#print('');
|
|
283
|
+
this.#print(hint);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return true;
|
|
37
287
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
288
|
+
catch (error) {
|
|
289
|
+
if (this.#mode !== 'agent') {
|
|
290
|
+
// Expected login failures, including any answer from the login
|
|
291
|
+
// server, get a plain message; anything else keeps its stack for
|
|
292
|
+
// debugging.
|
|
293
|
+
if (error instanceof DeviceFlowError || errorReason(error) !== 'login_failed') {
|
|
294
|
+
this.error(errorMessage(error), { exit: 1 });
|
|
295
|
+
}
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
this.#print(JSON.stringify({ status: 'error', reason: errorReason(error), message: errorMessage(error) }));
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Stores the key of a fresh login and forgets any device code an agent
|
|
304
|
+
* login left behind. The previous account belongs to the
|
|
305
|
+
* previous key, possibly another user, so it is dropped first: if choosing
|
|
306
|
+
* an account fails afterwards, the login is left unfinished (key without
|
|
307
|
+
* account, which the next login resumes) instead of pairing the new key
|
|
308
|
+
* with the old account.
|
|
309
|
+
*/
|
|
310
|
+
#storeNewKey(key) {
|
|
311
|
+
config.data.delete('accountId');
|
|
312
|
+
config.data.delete('accountName');
|
|
313
|
+
config.auth.delete('pendingDeviceAuthorization');
|
|
314
|
+
config.auth.set('apiKey', key);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* The name of the user the stored key belongs to, or undefined when the
|
|
318
|
+
* key is no longer accepted (revoked or expired). A rejected key is
|
|
319
|
+
* deleted so the caller authenticates again instead of reusing it.
|
|
320
|
+
*/
|
|
321
|
+
async #storedKeyUserName() {
|
|
322
|
+
try {
|
|
323
|
+
return (await api.user.get()).data.name;
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
if (error instanceof UnauthorizedError) {
|
|
327
|
+
config.auth.delete('apiKey');
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
#print(line) {
|
|
334
|
+
this.#atBlankLine = line === '';
|
|
335
|
+
if (this.#inline) {
|
|
336
|
+
this.logToStderr(line);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
this.log(line);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// ─── LOGIN STATE ────────────────────────────────────────────
|
|
343
|
+
async #wantsToReplaceLogin(accountName) {
|
|
344
|
+
const accountId = config.data.get('accountId');
|
|
345
|
+
if (this.#mode !== 'interactive') {
|
|
346
|
+
if (this.#mode === 'agent') {
|
|
347
|
+
this.#print(JSON.stringify({
|
|
348
|
+
status: 'success',
|
|
349
|
+
reason: 'already_logged_in',
|
|
350
|
+
message: `Already logged in to account "${accountName}".`,
|
|
351
|
+
accountId,
|
|
352
|
+
accountName,
|
|
353
|
+
}));
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
this.#print(`Already logged in to "${accountName}".`);
|
|
357
|
+
}
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
const { setNewkey } = await prompts({
|
|
361
|
+
name: 'setNewkey',
|
|
52
362
|
type: 'confirm',
|
|
53
|
-
message: `
|
|
54
|
-
initial: true,
|
|
363
|
+
message: `You are currently logged in to "${accountName}". Do you want to log out and log in to a different account?`,
|
|
55
364
|
});
|
|
56
|
-
|
|
57
|
-
|
|
365
|
+
return Boolean(setNewkey);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Checks the stored key against the stored account. If it no longer works
|
|
369
|
+
* (key revoked, access removed, account deleted), drops the stored account,
|
|
370
|
+
* so the stored key is then checked like that of an unfinished login. Other
|
|
371
|
+
* failures, such as the API being unreachable, are thrown. Returns the
|
|
372
|
+
* account's name either way.
|
|
373
|
+
*/
|
|
374
|
+
async #checkStoredAccount() {
|
|
375
|
+
const accountId = config.getAccountId();
|
|
376
|
+
try {
|
|
377
|
+
const { data: account } = await api.accounts.get(accountId);
|
|
378
|
+
// Older CLI versions' `checkly switch` stored only the new account's
|
|
379
|
+
// id, so the stored name can belong to another account.
|
|
380
|
+
if (account.name !== config.data.get('accountName')) {
|
|
381
|
+
config.data.set('accountName', account.name);
|
|
382
|
+
}
|
|
383
|
+
return { usable: true, name: account.name };
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
const unusable = error instanceof UnauthorizedError
|
|
387
|
+
|| error instanceof ForbiddenError
|
|
388
|
+
|| error instanceof NotFoundError;
|
|
389
|
+
if (!unusable) {
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
392
|
+
const accountName = config.data.get('accountName');
|
|
393
|
+
config.data.delete('accountId');
|
|
394
|
+
config.data.delete('accountName');
|
|
395
|
+
return { usable: false, name: accountName || accountId };
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// ─── AUTHENTICATION ─────────────────────────────────────────
|
|
399
|
+
/**
|
|
400
|
+
* Returns the new credentials, or:
|
|
401
|
+
* - 'pending' when an agent-mode login has shown the code and returned
|
|
402
|
+
* without waiting for the user (the next run collects it), or has sent
|
|
403
|
+
* the agent to `npx checkly login` (nothing stored to collect);
|
|
404
|
+
* - 'stored' when another process completed this login with the same code
|
|
405
|
+
* and stored its key.
|
|
406
|
+
*/
|
|
407
|
+
async #authenticate() {
|
|
408
|
+
const deviceFlow = new DeviceFlow();
|
|
409
|
+
let authorization;
|
|
410
|
+
try {
|
|
411
|
+
const pending = this.#mode === 'agent' ? this.#pendingAuthorization() : undefined;
|
|
412
|
+
if (pending) {
|
|
413
|
+
const collected = await this.#collectPendingAuthorization(deviceFlow, pending);
|
|
414
|
+
if (collected !== 'expiring') {
|
|
415
|
+
return collected;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
authorization = await deviceFlow.requestAuthorization();
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
// The device grant is not enabled, or was turned off (rolled back)
|
|
422
|
+
// after a stored code was issued; that code is gone either way.
|
|
423
|
+
if (error instanceof DeviceFlowNotAllowedError) {
|
|
424
|
+
return this.#authenticateWithBrowserCallback();
|
|
425
|
+
}
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
if (this.#mode === 'agent') {
|
|
429
|
+
// Agents usually show a command's output only once it exits, so
|
|
430
|
+
// waiting here would hide the code until it expired. Keep the code and
|
|
431
|
+
// return; the next login (or authenticated command) collects it.
|
|
432
|
+
// Commands an agent runs in parallel before it knows it is logged out
|
|
433
|
+
// each get here; the first code stored wins, so the user approves the
|
|
434
|
+
// one code every later run collects.
|
|
435
|
+
const storedMeanwhile = this.#pendingAuthorization();
|
|
436
|
+
if (storedMeanwhile) {
|
|
437
|
+
authorization = storedMeanwhile;
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
const stored = { ...authorization, authUrl: config.getAuthUrl() };
|
|
441
|
+
config.auth.set('pendingDeviceAuthorization', stored);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// A login an agent's command starts on its own (often just a `whoami`
|
|
445
|
+
// check) must not pop up a browser tab the user did not ask for; the
|
|
446
|
+
// agent relays the URL instead. An explicit `checkly login` opens it for
|
|
447
|
+
// a code this run shows for the first time, whether it requested it or
|
|
448
|
+
// took it over from a parallel run (not when it shows a stored one again).
|
|
449
|
+
const browserOpened = !(this.#mode === 'agent' && this.#inline)
|
|
450
|
+
&& await this.#tryOpenBrowser(authorization.verificationUriComplete);
|
|
451
|
+
this.#announceDeviceCode(authorization, undefined, browserOpened);
|
|
452
|
+
if (this.#mode === 'agent') {
|
|
453
|
+
return 'pending';
|
|
454
|
+
}
|
|
455
|
+
const tokens = await this.#whileWaiting('Waiting for you to finish in the browser', report => deviceFlow.pollForTokens(authorization, {
|
|
456
|
+
onUnexpectedAnswers: failure => report(`Still waiting: the last answer was ${failure}.`),
|
|
457
|
+
}));
|
|
458
|
+
return credentialsFromTokens(tokens);
|
|
459
|
+
}
|
|
460
|
+
/** The stored device code, if it is still usable against this login server. */
|
|
461
|
+
#pendingAuthorization() {
|
|
462
|
+
const stored = config.auth.get('pendingDeviceAuthorization');
|
|
463
|
+
if (!stored) {
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
if (stored.authUrl !== config.getAuthUrl() || !(stored.expiresAt > Date.now())) {
|
|
467
|
+
config.auth.delete('pendingDeviceAuthorization');
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
470
|
+
return stored;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Checks once whether the user has approved the stored code. Still
|
|
474
|
+
* waiting: show the same code again and return 'pending', or return
|
|
475
|
+
* 'expiring' when it is about to expire.
|
|
476
|
+
*/
|
|
477
|
+
async #collectPendingAuthorization(deviceFlow, pending) {
|
|
478
|
+
let result;
|
|
479
|
+
try {
|
|
480
|
+
result = await deviceFlow.pollOnce(pending);
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
// Denied, expired or already used: the code is done either way.
|
|
484
|
+
config.auth.delete('pendingDeviceAuthorization');
|
|
485
|
+
if (error instanceof DeviceFlowError && config.getApiKey()) {
|
|
486
|
+
return 'stored';
|
|
487
|
+
}
|
|
488
|
+
throw error;
|
|
489
|
+
}
|
|
490
|
+
if (!result.tokens) {
|
|
491
|
+
// The login server says it is not approved, and too little time is left
|
|
492
|
+
// for the agent to relay it and the user to approve: the caller requests
|
|
493
|
+
// a new code. After a failed request the code is kept, since it may
|
|
494
|
+
// have been approved.
|
|
495
|
+
if (!result.failure && pending.expiresAt - Date.now() < MIN_RELAY_TIME_MS) {
|
|
496
|
+
config.auth.delete('pendingDeviceAuthorization');
|
|
497
|
+
return 'expiring';
|
|
498
|
+
}
|
|
499
|
+
// Without this, an agent whose user has already approved would be told
|
|
500
|
+
// to run the command again with nothing to suggest that it was the
|
|
501
|
+
// login server, not the user, that has not answered yet.
|
|
502
|
+
const problem = result.failure
|
|
503
|
+
? `The last request to the login server failed (${result.failure}), so an approval may not have been seen yet; `
|
|
504
|
+
+ 'run this command again in a moment.'
|
|
505
|
+
: undefined;
|
|
506
|
+
this.#announceDeviceCode(pending, problem);
|
|
507
|
+
return 'pending';
|
|
508
|
+
}
|
|
509
|
+
config.auth.delete('pendingDeviceAuthorization');
|
|
510
|
+
return credentialsFromTokens(result.tokens);
|
|
511
|
+
}
|
|
512
|
+
// The login pages also offer sign-up, and logging in with a new identity
|
|
513
|
+
// creates the Checkly user (see exchangeAccessTokenForApiKey()).
|
|
514
|
+
static #signUpHint = 'New to Checkly? You can sign up on the same page.';
|
|
515
|
+
/**
|
|
516
|
+
* `browserOpened` only shapes the interactive lines: the pre-filled link is
|
|
517
|
+
* shown when no browser was opened, for the user to open themselves.
|
|
518
|
+
*/
|
|
519
|
+
#announceDeviceCode(authorization, problem, browserOpened = false) {
|
|
520
|
+
this.#announce({
|
|
521
|
+
status: 'action_required',
|
|
522
|
+
reason: 'login_required',
|
|
523
|
+
userActionRequired: true,
|
|
524
|
+
message: (this.#notice ? `${this.#notice} ` : '')
|
|
525
|
+
// A command that started a login on its own (e.g. `whoami`) prints
|
|
526
|
+
// this instead of what was asked for; say so, or the line reads as a
|
|
527
|
+
// plain "not logged in" error and the code goes unused.
|
|
528
|
+
+ (this.#inline ? 'Not logged in to Checkly, so this command needs a login first. ' : '')
|
|
529
|
+
+ `Open ${authorization.verificationUri} in a browser on any device and enter the code `
|
|
530
|
+
+ `${authorization.userCode}. Once the user has approved, run this command again.`
|
|
531
|
+
+ (problem ? ` ${problem}` : '') + ` ${_a.#signUpHint}`,
|
|
532
|
+
verification_uri: authorization.verificationUri,
|
|
533
|
+
verification_uri_complete: authorization.verificationUriComplete,
|
|
534
|
+
user_code: authorization.userCode,
|
|
535
|
+
expires_in: Math.max(0, Math.round((authorization.expiresAt - Date.now()) / 1000)),
|
|
536
|
+
// The step after the user approves, spelled out: no other CLI logs in
|
|
537
|
+
// over two runs, so agents won't expect it. Left out when another
|
|
538
|
+
// command started the login: running that command again is the step,
|
|
539
|
+
// and its arguments aren't known here.
|
|
540
|
+
next: this.#inline
|
|
541
|
+
? undefined
|
|
542
|
+
: [{
|
|
543
|
+
command: 'npx checkly login'
|
|
544
|
+
+ (this.#requestedAccountId ? ` --account-id ${shellQuote(this.#requestedAccountId)}` : ''),
|
|
545
|
+
when: 'after the user has approved in the browser',
|
|
546
|
+
}],
|
|
547
|
+
}, [
|
|
548
|
+
`Visit ${chalk.bold.underline(withoutScheme(authorization.verificationUri))} and enter `
|
|
549
|
+
+ chalk.bold(authorization.userCode),
|
|
550
|
+
// Keeps its scheme: this is the link to click or copy.
|
|
551
|
+
...browserOpened ? [] : [chalk.dim(`Or open ${authorization.verificationUriComplete}`)],
|
|
552
|
+
'',
|
|
553
|
+
]);
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Authorization-code flow with a callback server on localhost. Used while
|
|
557
|
+
* the device grant is not enabled for the CLI client. Only works when the
|
|
558
|
+
* browser runs on the same machine as the CLI.
|
|
559
|
+
*/
|
|
560
|
+
async #authenticateWithBrowserCallback() {
|
|
561
|
+
// This login only completes while the command waits for the browser on
|
|
562
|
+
// this machine. A command an agent started for something else (often
|
|
563
|
+
// just a `whoami` check) would sit there unseen, since agents usually
|
|
564
|
+
// show output only once a command exits; send the agent to the login
|
|
565
|
+
// command instead.
|
|
566
|
+
if (this.#mode === 'agent' && this.#inline) {
|
|
567
|
+
this.#announce({
|
|
568
|
+
status: 'action_required',
|
|
569
|
+
reason: 'login_required',
|
|
570
|
+
userActionRequired: true,
|
|
571
|
+
message: 'Run `npx checkly login` in the background to log in (it waits for the browser on this machine '
|
|
572
|
+
+ 'to finish), relay what it prints, then run the original command again.',
|
|
573
|
+
next: [{ command: 'npx checkly login' }],
|
|
574
|
+
}, []);
|
|
575
|
+
return 'pending';
|
|
576
|
+
}
|
|
577
|
+
const mode = this.#mode === 'interactive'
|
|
578
|
+
? await this.#promptForLoginOrSignUp()
|
|
579
|
+
: 'any';
|
|
580
|
+
const authContext = new AuthContext(mode);
|
|
581
|
+
if (this.#mode === 'interactive') {
|
|
582
|
+
// --no-browser / CHECKLY_NO_BROWSER: don't offer to open one.
|
|
583
|
+
const openUrl = this.#openBrowser && (await prompts({
|
|
584
|
+
name: 'openUrl',
|
|
585
|
+
type: 'confirm',
|
|
586
|
+
message: `Do you want to open a browser window to continue with ${mode === 'signup' ? 'sign up' : 'login'}?`,
|
|
587
|
+
initial: true,
|
|
588
|
+
})).openUrl;
|
|
589
|
+
if (openUrl) {
|
|
590
|
+
await open(authContext.authenticationUrl);
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
// The login completes through a callback to localhost, so a browser
|
|
594
|
+
// elsewhere (e.g. on the laptop an SSH session comes from) won't do.
|
|
595
|
+
this.#print('Please open the following URL in a browser on this machine: '
|
|
596
|
+
+ `\n\n${chalk.cyan(authContext.authenticationUrl)}`);
|
|
597
|
+
}
|
|
58
598
|
}
|
|
59
599
|
else {
|
|
60
|
-
|
|
600
|
+
this.#announce({
|
|
601
|
+
status: 'action_required',
|
|
602
|
+
reason: 'login_required',
|
|
603
|
+
userActionRequired: true,
|
|
604
|
+
message: 'Ask the user to open the URL in a browser on the same machine as this CLI '
|
|
605
|
+
+ `(it completes through a local callback). ${_a.#signUpHint}`,
|
|
606
|
+
verification_uri: authContext.authenticationUrl,
|
|
607
|
+
}, []);
|
|
608
|
+
await this.#tryOpenBrowser(authContext.authenticationUrl);
|
|
61
609
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
? accounts[0]
|
|
68
|
-
: await selectAccount(accounts, { onCancel });
|
|
69
|
-
if (!selectedAccount) {
|
|
70
|
-
this.warn('You must select a valid Checkly account name.');
|
|
71
|
-
this.exit(1);
|
|
610
|
+
return authContext.getAuth0Credentials();
|
|
611
|
+
}
|
|
612
|
+
#announce(payload, interactiveLines) {
|
|
613
|
+
if (this.#mode === 'agent') {
|
|
614
|
+
this.#print(JSON.stringify(payload));
|
|
72
615
|
return;
|
|
73
616
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
617
|
+
for (const line of interactiveLines) {
|
|
618
|
+
this.#print(line);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Opens the URL in a browser as a best effort. Returns whether one can be
|
|
623
|
+
* assumed to have opened on the user's screen: `open` resolves as soon as
|
|
624
|
+
* it starts a launcher, also in a remote session or on a Linux machine
|
|
625
|
+
* without a desktop, where nothing appears.
|
|
626
|
+
*/
|
|
627
|
+
async #tryOpenBrowser(url) {
|
|
628
|
+
if (!this.#openBrowser) {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
try {
|
|
632
|
+
await open(url);
|
|
633
|
+
}
|
|
634
|
+
catch {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
const remote = Boolean(process.env.SSH_CONNECTION || process.env.SSH_TTY);
|
|
638
|
+
const noDesktop = process.platform === 'linux' && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY;
|
|
639
|
+
return !remote && !noDesktop;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Runs `wait` behind the CLI's usual spinner when a person watches the
|
|
643
|
+
* command's own output on a terminal; otherwise prints `message` as a plain
|
|
644
|
+
* line, so piped output gets no animation frames and a login another
|
|
645
|
+
* command started keeps its output on stderr (the spinner writes to
|
|
646
|
+
* stdout). `report` shows a note while waiting.
|
|
647
|
+
*/
|
|
648
|
+
async #whileWaiting(message, wait) {
|
|
649
|
+
if (this.#mode !== 'interactive' || this.#inline || !this.fancy || !process.stdout.isTTY) {
|
|
650
|
+
if (this.#mode === 'interactive') {
|
|
651
|
+
this.#print(chalk.dim(`${message}…`));
|
|
652
|
+
}
|
|
653
|
+
return wait(note => this.#print(note));
|
|
654
|
+
}
|
|
655
|
+
this.style.actionStart(message);
|
|
656
|
+
try {
|
|
657
|
+
const result = await wait(note => this.style.actionStatus(note));
|
|
658
|
+
this.style.actionSuccess();
|
|
659
|
+
// actionSuccess ends with an empty line.
|
|
660
|
+
this.#atBlankLine = true;
|
|
661
|
+
return result;
|
|
662
|
+
}
|
|
663
|
+
catch (error) {
|
|
664
|
+
this.style.actionFailure();
|
|
665
|
+
throw error;
|
|
666
|
+
}
|
|
79
667
|
}
|
|
80
668
|
async #promptForLoginOrSignUp() {
|
|
81
669
|
const { mode } = await prompts({
|
|
@@ -92,5 +680,50 @@ export default class Login extends BaseCommand {
|
|
|
92
680
|
});
|
|
93
681
|
return mode;
|
|
94
682
|
}
|
|
683
|
+
// ─── ACCOUNT SELECTION ──────────────────────────────────────
|
|
684
|
+
/**
|
|
685
|
+
* Returns undefined only in agent mode when several accounts are available
|
|
686
|
+
* and none was requested: the caller reports the choice instead of guessing.
|
|
687
|
+
*/
|
|
688
|
+
async #pickAccount(accounts, requestedId, usingStoredKey) {
|
|
689
|
+
const available = accounts.map(a => `${a.name} (${a.id})`).join(', ');
|
|
690
|
+
if (requestedId) {
|
|
691
|
+
const match = accounts.find(account => account.id === requestedId);
|
|
692
|
+
if (!match) {
|
|
693
|
+
// With the stored key the account list is that user's; the requested
|
|
694
|
+
// account may belong to another identity the user also logs in with.
|
|
695
|
+
throw new LoginError('account_not_found', `No account with id "${requestedId}" is available to this user. `
|
|
696
|
+
+ `Available: ${available}`
|
|
697
|
+
+ (usingStoredKey ? '. To log in as a different user, run `npx checkly logout` first.' : ''));
|
|
698
|
+
}
|
|
699
|
+
return match;
|
|
700
|
+
}
|
|
701
|
+
if (accounts.length === 0) {
|
|
702
|
+
throw new LoginError('no_accounts', 'This user has no Checkly accounts. '
|
|
703
|
+
+ 'To log in as a different user, run `npx checkly logout` first.');
|
|
704
|
+
}
|
|
705
|
+
if (accounts.length === 1) {
|
|
706
|
+
return accounts[0];
|
|
707
|
+
}
|
|
708
|
+
if (this.#mode === 'ci') {
|
|
709
|
+
throw new Error('This user belongs to several accounts: '
|
|
710
|
+
+ `${available}. `
|
|
711
|
+
+ 'Choose one with `npx checkly login --account-id <id>`, or set `CHECKLY_API_KEY` and '
|
|
712
|
+
+ '`CHECKLY_ACCOUNT_ID` in the environment.');
|
|
713
|
+
}
|
|
714
|
+
if (this.#mode === 'agent') {
|
|
715
|
+
return undefined;
|
|
716
|
+
}
|
|
717
|
+
this.#atBlankLine = false;
|
|
718
|
+
const selected = await selectAccount(accounts, {
|
|
719
|
+
onCancel: () => this.error('Command cancelled.\n'),
|
|
720
|
+
});
|
|
721
|
+
if (!selected) {
|
|
722
|
+
throw new Error('You must select a valid Checkly account name.');
|
|
723
|
+
}
|
|
724
|
+
return selected;
|
|
725
|
+
}
|
|
95
726
|
}
|
|
727
|
+
_a = Login;
|
|
728
|
+
export default Login;
|
|
96
729
|
//# sourceMappingURL=login.js.map
|