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.
Files changed (372) hide show
  1. package/dist/ai-context/context.js +1 -0
  2. package/dist/ai-context/context.js.map +1 -1
  3. package/dist/ai-context/onboarding-boilerplate/checkly-config-template.ts +2 -0
  4. package/dist/ai-context/public-skills/checkly/SKILL.md +18 -5
  5. package/dist/ai-context/skills-command/references/communicate.md +6 -3
  6. package/dist/ai-context/skills-command/references/configure-api-checks.md +1 -9
  7. package/dist/ai-context/skills-command/references/configure-browser-checks.md +1 -8
  8. package/dist/ai-context/skills-command/references/configure-check-groups.md +1 -0
  9. package/dist/ai-context/skills-command/references/configure-dns-monitors.md +1 -9
  10. package/dist/ai-context/skills-command/references/configure-grpc-monitors.md +24 -0
  11. package/dist/ai-context/skills-command/references/configure-heartbeat-monitors.md +1 -9
  12. package/dist/ai-context/skills-command/references/configure-icmp-monitors.md +1 -9
  13. package/dist/ai-context/skills-command/references/configure-multistep-checks.md +1 -9
  14. package/dist/ai-context/skills-command/references/configure-playwright-checks.md +1 -1
  15. package/dist/ai-context/skills-command/references/configure-supporting-constructs.md +6 -2
  16. package/dist/ai-context/skills-command/references/configure-tcp-monitors.md +1 -10
  17. package/dist/ai-context/skills-command/references/configure-url-monitors.md +1 -10
  18. package/dist/ai-context/skills-command/references/configure.md +16 -5
  19. package/dist/ai-context/skills-command/references/initialize.md +11 -3
  20. package/dist/ai-context/skills-command/references/investigate-checks.md +1 -1
  21. package/dist/auth/api-key.d.ts +24 -0
  22. package/dist/auth/api-key.js +104 -0
  23. package/dist/auth/api-key.js.map +1 -0
  24. package/dist/auth/device-flow.d.ts +57 -0
  25. package/dist/auth/device-flow.js +221 -0
  26. package/dist/auth/device-flow.js.map +1 -0
  27. package/dist/auth/index.d.ts +6 -6
  28. package/dist/auth/index.js +64 -64
  29. package/dist/auth/index.js.map +1 -1
  30. package/dist/commands/api.js +43 -10
  31. package/dist/commands/api.js.map +1 -1
  32. package/dist/commands/authCommand.d.ts +2 -0
  33. package/dist/commands/authCommand.js +77 -7
  34. package/dist/commands/authCommand.js.map +1 -1
  35. package/dist/commands/debug/parse-project.d.ts +1 -0
  36. package/dist/commands/debug/parse-project.js +5 -2
  37. package/dist/commands/debug/parse-project.js.map +1 -1
  38. package/dist/commands/deploy.d.ts +7 -1
  39. package/dist/commands/deploy.js +627 -271
  40. package/dist/commands/deploy.js.map +1 -1
  41. package/dist/commands/import/plan.js +18 -4
  42. package/dist/commands/import/plan.js.map +1 -1
  43. package/dist/commands/init.js +29 -13
  44. package/dist/commands/init.js.map +1 -1
  45. package/dist/commands/login.d.ts +18 -2
  46. package/dist/commands/login.js +681 -48
  47. package/dist/commands/login.js.map +1 -1
  48. package/dist/commands/logout.js +7 -1
  49. package/dist/commands/logout.js.map +1 -1
  50. package/dist/commands/pw-test.js +3 -2
  51. package/dist/commands/pw-test.js.map +1 -1
  52. package/dist/commands/switch.js +23 -10
  53. package/dist/commands/switch.js.map +1 -1
  54. package/dist/commands/test.js +16 -17
  55. package/dist/commands/test.js.map +1 -1
  56. package/dist/commands/trigger.js +4 -3
  57. package/dist/commands/trigger.js.map +1 -1
  58. package/dist/commands/validate.js +1 -0
  59. package/dist/commands/validate.js.map +1 -1
  60. package/dist/constants.d.ts +1 -2
  61. package/dist/constants.js +0 -1
  62. package/dist/constants.js.map +1 -1
  63. package/dist/constructs/alert-channel-codegen.d.ts +6 -1
  64. package/dist/constructs/alert-channel-codegen.js +12 -6
  65. package/dist/constructs/alert-channel-codegen.js.map +1 -1
  66. package/dist/constructs/alert-escalation-policy-codegen.d.ts +7 -0
  67. package/dist/constructs/alert-escalation-policy-codegen.js +25 -8
  68. package/dist/constructs/alert-escalation-policy-codegen.js.map +1 -1
  69. package/dist/constructs/api-assertion.d.ts +0 -2
  70. package/dist/constructs/api-assertion.js +0 -4
  71. package/dist/constructs/api-assertion.js.map +1 -1
  72. package/dist/constructs/api-check-codegen.js +1 -1
  73. package/dist/constructs/api-check-codegen.js.map +1 -1
  74. package/dist/constructs/api-check.d.ts +4 -22
  75. package/dist/constructs/api-check.js +15 -17
  76. package/dist/constructs/api-check.js.map +1 -1
  77. package/dist/constructs/api-request-codegen.js +10 -8
  78. package/dist/constructs/api-request-codegen.js.map +1 -1
  79. package/dist/constructs/browser-check-bundle.d.ts +19 -13
  80. package/dist/constructs/browser-check-bundle.js +5 -1
  81. package/dist/constructs/browser-check-bundle.js.map +1 -1
  82. package/dist/constructs/browser-check.d.ts +1 -4
  83. package/dist/constructs/browser-check.js +1 -1
  84. package/dist/constructs/browser-check.js.map +1 -1
  85. package/dist/constructs/check-codegen.d.ts +39 -14
  86. package/dist/constructs/check-codegen.js +101 -23
  87. package/dist/constructs/check-codegen.js.map +1 -1
  88. package/dist/constructs/check-group-codegen.js +23 -9
  89. package/dist/constructs/check-group-codegen.js.map +1 -1
  90. package/dist/constructs/check-group-v1.d.ts +19 -14
  91. package/dist/constructs/check-group-v1.js +39 -14
  92. package/dist/constructs/check-group-v1.js.map +1 -1
  93. package/dist/constructs/check-group-v2.d.ts +0 -20
  94. package/dist/constructs/check-group-v2.js +0 -4
  95. package/dist/constructs/check-group-v2.js.map +1 -1
  96. package/dist/constructs/check.d.ts +45 -32
  97. package/dist/constructs/check.js +78 -17
  98. package/dist/constructs/check.js.map +1 -1
  99. package/dist/constructs/construct-codegen.d.ts +1 -2
  100. package/dist/constructs/construct-diagnostics.d.ts +3 -0
  101. package/dist/constructs/construct-diagnostics.js +11 -0
  102. package/dist/constructs/construct-diagnostics.js.map +1 -1
  103. package/dist/constructs/construct.d.ts +25 -4
  104. package/dist/constructs/construct.js +41 -5
  105. package/dist/constructs/construct.js.map +1 -1
  106. package/dist/constructs/dns-request-codegen.js +1 -1
  107. package/dist/constructs/dns-request-codegen.js.map +1 -1
  108. package/dist/constructs/email-alert-channel-codegen.js +1 -1
  109. package/dist/constructs/email-alert-channel-codegen.js.map +1 -1
  110. package/dist/constructs/frequency-codegen.js +10 -2
  111. package/dist/constructs/frequency-codegen.js.map +1 -1
  112. package/dist/constructs/frequency.d.ts +8 -3
  113. package/dist/constructs/frequency.js +7 -2
  114. package/dist/constructs/frequency.js.map +1 -1
  115. package/dist/constructs/grpc-monitor.js +19 -0
  116. package/dist/constructs/grpc-monitor.js.map +1 -1
  117. package/dist/constructs/grpc-request-codegen.js +13 -4
  118. package/dist/constructs/grpc-request-codegen.js.map +1 -1
  119. package/dist/constructs/grpc-request.d.ts +23 -0
  120. package/dist/constructs/heartbeat-monitor.d.ts +2 -1
  121. package/dist/constructs/heartbeat-monitor.js +2 -2
  122. package/dist/constructs/heartbeat-monitor.js.map +1 -1
  123. package/dist/constructs/icmp-request-codegen.js +1 -1
  124. package/dist/constructs/icmp-request-codegen.js.map +1 -1
  125. package/dist/constructs/incidentio-alert-channel-codegen.d.ts +1 -1
  126. package/dist/constructs/incidentio-alert-channel-codegen.js +14 -8
  127. package/dist/constructs/incidentio-alert-channel-codegen.js.map +1 -1
  128. package/dist/constructs/index.d.ts +1 -1
  129. package/dist/constructs/index.js +1 -1
  130. package/dist/constructs/index.js.map +1 -1
  131. package/dist/constructs/internal/assertion-validation.js +1 -2
  132. package/dist/constructs/internal/assertion-validation.js.map +1 -1
  133. package/dist/constructs/internal/codegen/context.d.ts +73 -6
  134. package/dist/constructs/internal/codegen/context.js +78 -6
  135. package/dist/constructs/internal/codegen/context.js.map +1 -1
  136. package/dist/constructs/internal/codegen/index.d.ts +3 -1
  137. package/dist/constructs/internal/codegen/index.js +2 -1
  138. package/dist/constructs/internal/codegen/index.js.map +1 -1
  139. package/dist/constructs/internal/codegen/render.d.ts +68 -0
  140. package/dist/constructs/internal/codegen/render.js +75 -0
  141. package/dist/constructs/internal/codegen/render.js.map +1 -0
  142. package/dist/constructs/internal/common-diagnostics.d.ts +1 -2
  143. package/dist/constructs/internal/common-diagnostics.js +6 -14
  144. package/dist/constructs/internal/common-diagnostics.js.map +1 -1
  145. package/dist/constructs/internal/declaring-file.d.ts +39 -0
  146. package/dist/constructs/internal/declaring-file.js +139 -0
  147. package/dist/constructs/internal/declaring-file.js.map +1 -0
  148. package/dist/constructs/internal/retry-strategy-defaults.d.ts +13 -0
  149. package/dist/constructs/internal/retry-strategy-defaults.js +14 -0
  150. package/dist/constructs/internal/retry-strategy-defaults.js.map +1 -0
  151. package/dist/constructs/key-value-pair-codegen.d.ts +6 -1
  152. package/dist/constructs/key-value-pair-codegen.js +24 -3
  153. package/dist/constructs/key-value-pair-codegen.js.map +1 -1
  154. package/dist/constructs/maintenance-window-codegen.d.ts +7 -0
  155. package/dist/constructs/maintenance-window-codegen.js +36 -19
  156. package/dist/constructs/maintenance-window-codegen.js.map +1 -1
  157. package/dist/constructs/maintenance-window.d.ts +35 -3
  158. package/dist/constructs/maintenance-window.js +35 -1
  159. package/dist/constructs/maintenance-window.js.map +1 -1
  160. package/dist/constructs/monitor.d.ts +1 -1
  161. package/dist/constructs/msteams-alert-channel-codegen.d.ts +1 -1
  162. package/dist/constructs/msteams-alert-channel-codegen.js +8 -18
  163. package/dist/constructs/msteams-alert-channel-codegen.js.map +1 -1
  164. package/dist/constructs/opsgenie-alert-channel-codegen.js +1 -1
  165. package/dist/constructs/opsgenie-alert-channel-codegen.js.map +1 -1
  166. package/dist/constructs/pagerduty-alert-channel-codegen.js +1 -1
  167. package/dist/constructs/pagerduty-alert-channel-codegen.js.map +1 -1
  168. package/dist/constructs/phone-call-alert-channel-codegen.js +1 -1
  169. package/dist/constructs/phone-call-alert-channel-codegen.js.map +1 -1
  170. package/dist/constructs/playwright-check-bundle.d.ts +7 -1
  171. package/dist/constructs/playwright-check-bundle.js +3 -0
  172. package/dist/constructs/playwright-check-bundle.js.map +1 -1
  173. package/dist/constructs/playwright-check-codegen.d.ts +85 -0
  174. package/dist/constructs/playwright-check-codegen.js +212 -0
  175. package/dist/constructs/playwright-check-codegen.js.map +1 -0
  176. package/dist/constructs/playwright-check.d.ts +14 -24
  177. package/dist/constructs/playwright-check.js +16 -52
  178. package/dist/constructs/playwright-check.js.map +1 -1
  179. package/dist/constructs/retry-strategy-codegen.d.ts +7 -1
  180. package/dist/constructs/retry-strategy-codegen.js +41 -32
  181. package/dist/constructs/retry-strategy-codegen.js.map +1 -1
  182. package/dist/constructs/retry-strategy.d.ts +0 -4
  183. package/dist/constructs/retry-strategy.js +5 -8
  184. package/dist/constructs/retry-strategy.js.map +1 -1
  185. package/dist/constructs/session.d.ts +26 -1
  186. package/dist/constructs/session.js +33 -2
  187. package/dist/constructs/session.js.map +1 -1
  188. package/dist/constructs/slack-alert-channel-codegen.js +1 -1
  189. package/dist/constructs/slack-alert-channel-codegen.js.map +1 -1
  190. package/dist/constructs/slack-app-alert-channel-codegen.js +1 -1
  191. package/dist/constructs/slack-app-alert-channel-codegen.js.map +1 -1
  192. package/dist/constructs/sms-alert-channel-codegen.js +1 -1
  193. package/dist/constructs/sms-alert-channel-codegen.js.map +1 -1
  194. package/dist/constructs/ssl-request-codegen.js +4 -3
  195. package/dist/constructs/ssl-request-codegen.js.map +1 -1
  196. package/dist/constructs/status-page-v3-automation-rule-codegen.js +5 -4
  197. package/dist/constructs/status-page-v3-automation-rule-codegen.js.map +1 -1
  198. package/dist/constructs/status-page-v3-codegen.js +3 -2
  199. package/dist/constructs/status-page-v3-codegen.js.map +1 -1
  200. package/dist/constructs/status-page-v3-component-codegen.js +8 -7
  201. package/dist/constructs/status-page-v3-component-codegen.js.map +1 -1
  202. package/dist/constructs/tcp-monitor-codegen.js +3 -2
  203. package/dist/constructs/tcp-monitor-codegen.js.map +1 -1
  204. package/dist/constructs/tcp-monitor.d.ts +4 -3
  205. package/dist/constructs/tcp-monitor.js +3 -2
  206. package/dist/constructs/tcp-monitor.js.map +1 -1
  207. package/dist/constructs/telegram-alert-channel-codegen.d.ts +1 -1
  208. package/dist/constructs/telegram-alert-channel-codegen.js +14 -8
  209. package/dist/constructs/telegram-alert-channel-codegen.js.map +1 -1
  210. package/dist/constructs/time-zone.d.ts +24 -0
  211. package/dist/constructs/time-zone.js +455 -0
  212. package/dist/constructs/time-zone.js.map +1 -0
  213. package/dist/constructs/traceroute-request-codegen.js +2 -2
  214. package/dist/constructs/traceroute-request-codegen.js.map +1 -1
  215. package/dist/constructs/url-monitor-codegen.js +1 -1
  216. package/dist/constructs/url-monitor-codegen.js.map +1 -1
  217. package/dist/constructs/url-monitor.d.ts +3 -2
  218. package/dist/constructs/url-monitor.js +3 -0
  219. package/dist/constructs/url-monitor.js.map +1 -1
  220. package/dist/constructs/url-request-codegen.js +4 -3
  221. package/dist/constructs/url-request-codegen.js.map +1 -1
  222. package/dist/constructs/webhook-alert-channel-codegen.js +3 -3
  223. package/dist/constructs/webhook-alert-channel-codegen.js.map +1 -1
  224. package/dist/formatters/check-result-detail.js +0 -185
  225. package/dist/formatters/check-result-detail.js.map +1 -1
  226. package/dist/helpers/activate-account.d.ts +14 -0
  227. package/dist/helpers/activate-account.js +25 -0
  228. package/dist/helpers/activate-account.js.map +1 -0
  229. package/dist/helpers/cli-mode.d.ts +18 -0
  230. package/dist/helpers/cli-mode.js +41 -2
  231. package/dist/helpers/cli-mode.js.map +1 -1
  232. package/dist/helpers/command-preview.d.ts +44 -0
  233. package/dist/helpers/command-preview.js +29 -9
  234. package/dist/helpers/command-preview.js.map +1 -1
  235. package/dist/helpers/flags.d.ts +10 -0
  236. package/dist/helpers/flags.js +17 -0
  237. package/dist/helpers/flags.js.map +1 -1
  238. package/dist/helpers/onboarding/boilerplate.js +13 -1
  239. package/dist/helpers/onboarding/boilerplate.js.map +1 -1
  240. package/dist/helpers/onboarding/index.d.ts +1 -1
  241. package/dist/helpers/onboarding/index.js +1 -1
  242. package/dist/helpers/onboarding/index.js.map +1 -1
  243. package/dist/helpers/onboarding/messages.d.ts +6 -0
  244. package/dist/helpers/onboarding/messages.js +14 -0
  245. package/dist/helpers/onboarding/messages.js.map +1 -1
  246. package/dist/helpers/onboarding/skill-install.js +2 -2
  247. package/dist/helpers/onboarding/skill-install.js.map +1 -1
  248. package/dist/helpers/wrap.js +1 -1
  249. package/dist/helpers/wrap.js.map +1 -1
  250. package/dist/reporters/abstract-list.js +11 -2
  251. package/dist/reporters/abstract-list.js.map +1 -1
  252. package/dist/reporters/github.js +3 -2
  253. package/dist/reporters/github.js.map +1 -1
  254. package/dist/reporters/util.d.ts +1 -0
  255. package/dist/reporters/util.js +5 -58
  256. package/dist/reporters/util.js.map +1 -1
  257. package/dist/rest/analytics.js +0 -4
  258. package/dist/rest/analytics.js.map +1 -1
  259. package/dist/rest/api.d.ts +8 -1
  260. package/dist/rest/api.js +21 -3
  261. package/dist/rest/api.js.map +1 -1
  262. package/dist/rest/check-results.d.ts +1 -50
  263. package/dist/rest/check-results.js.map +1 -1
  264. package/dist/rest/checkly-storage.d.ts +6 -1
  265. package/dist/rest/checkly-storage.js +13 -2
  266. package/dist/rest/checkly-storage.js.map +1 -1
  267. package/dist/rest/errors.d.ts +13 -0
  268. package/dist/rest/errors.js +5 -1
  269. package/dist/rest/errors.js.map +1 -1
  270. package/dist/rest/projects.d.ts +254 -4
  271. package/dist/rest/projects.js +184 -12
  272. package/dist/rest/projects.js.map +1 -1
  273. package/dist/services/abstract-check-runner.d.ts +11 -0
  274. package/dist/services/abstract-check-runner.js +98 -46
  275. package/dist/services/abstract-check-runner.js.map +1 -1
  276. package/dist/services/check-parser/bundler.d.ts +26 -0
  277. package/dist/services/check-parser/bundler.js +81 -7
  278. package/dist/services/check-parser/bundler.js.map +1 -1
  279. package/dist/services/check-parser/cache-hash.d.ts +1 -4
  280. package/dist/services/check-parser/cache-hash.js.map +1 -1
  281. package/dist/services/checkly-config-codegen.js +1 -4
  282. package/dist/services/checkly-config-codegen.js.map +1 -1
  283. package/dist/services/checkly-config-loader.d.ts +7 -33
  284. package/dist/services/checkly-config-loader.js +50 -47
  285. package/dist/services/checkly-config-loader.js.map +1 -1
  286. package/dist/services/config.d.ts +2 -0
  287. package/dist/services/config.js +14 -0
  288. package/dist/services/config.js.map +1 -1
  289. package/dist/services/content-hash.d.ts +14 -0
  290. package/dist/services/content-hash.js +22 -0
  291. package/dist/services/content-hash.js.map +1 -0
  292. package/dist/services/deploy-diff/diff-lines.d.ts +38 -0
  293. package/dist/services/deploy-diff/diff-lines.js +191 -0
  294. package/dist/services/deploy-diff/diff-lines.js.map +1 -0
  295. package/dist/services/deploy-diff/import-shape.d.ts +171 -0
  296. package/dist/services/deploy-diff/import-shape.js +633 -0
  297. package/dist/services/deploy-diff/import-shape.js.map +1 -0
  298. package/dist/services/deploy-diff/legacy-payload.d.ts +22 -0
  299. package/dist/services/deploy-diff/legacy-payload.js +51 -0
  300. package/dist/services/deploy-diff/legacy-payload.js.map +1 -0
  301. package/dist/services/deploy-diff/plan-summary.d.ts +37 -0
  302. package/dist/services/deploy-diff/plan-summary.js +154 -0
  303. package/dist/services/deploy-diff/plan-summary.js.map +1 -0
  304. package/dist/services/deploy-diff/preview-output.d.ts +61 -0
  305. package/dist/services/deploy-diff/preview-output.js +394 -0
  306. package/dist/services/deploy-diff/preview-output.js.map +1 -0
  307. package/dist/services/deploy-diff/render.d.ts +53 -0
  308. package/dist/services/deploy-diff/render.js +317 -0
  309. package/dist/services/deploy-diff/render.js.map +1 -0
  310. package/dist/services/deploy-diff/shape-changes.d.ts +2 -0
  311. package/dist/services/deploy-diff/shape-changes.js +35 -0
  312. package/dist/services/deploy-diff/shape-changes.js.map +1 -0
  313. package/dist/services/deploy-diff/source-index.d.ts +18 -0
  314. package/dist/services/deploy-diff/source-index.js +48 -0
  315. package/dist/services/deploy-diff/source-index.js.map +1 -0
  316. package/dist/services/deploy-diff/variable-names.d.ts +30 -0
  317. package/dist/services/deploy-diff/variable-names.js +64 -0
  318. package/dist/services/deploy-diff/variable-names.js.map +1 -0
  319. package/dist/services/project-parser.d.ts +5 -0
  320. package/dist/services/project-parser.js +25 -13
  321. package/dist/services/project-parser.js.map +1 -1
  322. package/dist/services/snapshot-service.d.ts +25 -8
  323. package/dist/services/snapshot-service.js +46 -4
  324. package/dist/services/snapshot-service.js.map +1 -1
  325. package/dist/services/test-filters.d.ts +18 -0
  326. package/dist/services/test-filters.js +10 -0
  327. package/dist/services/test-filters.js.map +1 -1
  328. package/dist/services/test-runner.js +7 -3
  329. package/dist/services/test-runner.js.map +1 -1
  330. package/dist/services/test-session-cancel.js +1 -1
  331. package/dist/services/test-session-cancel.js.map +1 -1
  332. package/dist/services/util.d.ts +17 -2
  333. package/dist/services/util.js +118 -11
  334. package/dist/services/util.js.map +1 -1
  335. package/dist/services/write-back/apply-edits.d.ts +29 -0
  336. package/dist/services/write-back/apply-edits.js +255 -0
  337. package/dist/services/write-back/apply-edits.js.map +1 -0
  338. package/dist/services/write-back/helper-edit.d.ts +135 -0
  339. package/dist/services/write-back/helper-edit.js +343 -0
  340. package/dist/services/write-back/helper-edit.js.map +1 -0
  341. package/dist/services/write-back/imports.d.ts +30 -0
  342. package/dist/services/write-back/imports.js +82 -0
  343. package/dist/services/write-back/imports.js.map +1 -0
  344. package/dist/services/write-back/literal-edit.d.ts +183 -0
  345. package/dist/services/write-back/literal-edit.js +359 -0
  346. package/dist/services/write-back/literal-edit.js.map +1 -0
  347. package/dist/services/write-back/output.d.ts +26 -0
  348. package/dist/services/write-back/output.js +110 -0
  349. package/dist/services/write-back/output.js.map +1 -0
  350. package/dist/services/write-back/plan.d.ts +173 -0
  351. package/dist/services/write-back/plan.js +968 -0
  352. package/dist/services/write-back/plan.js.map +1 -0
  353. package/dist/services/write-back/source-file.d.ts +118 -0
  354. package/dist/services/write-back/source-file.js +432 -0
  355. package/dist/services/write-back/source-file.js.map +1 -0
  356. package/dist/sourcegen/index.d.ts +1 -1
  357. package/dist/sourcegen/index.js +1 -1
  358. package/dist/sourcegen/index.js.map +1 -1
  359. package/dist/sourcegen/program.d.ts +19 -1
  360. package/dist/sourcegen/program.js +32 -7
  361. package/dist/sourcegen/program.js.map +1 -1
  362. package/oclif.manifest.json +676 -635
  363. package/package.json +13 -13
  364. package/dist/commands/rules.d.ts +0 -9
  365. package/dist/commands/rules.js +0 -13
  366. package/dist/commands/rules.js.map +0 -1
  367. package/dist/constructs/agentic-check-codegen.d.ts +0 -22
  368. package/dist/constructs/agentic-check-codegen.js +0 -59
  369. package/dist/constructs/agentic-check-codegen.js.map +0 -1
  370. package/dist/constructs/agentic-check.d.ts +0 -237
  371. package/dist/constructs/agentic-check.js +0 -100
  372. package/dist/constructs/agentic-check.js.map +0 -1
@@ -0,0 +1,968 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { isDeepStrictEqual } from 'node:util';
5
+ import { ApiCheck } from '../../constructs/api-check.js';
6
+ import { BrowserCheck } from '../../constructs/browser-check.js';
7
+ import { CheckGroupV1 } from '../../constructs/check-group-v1.js';
8
+ import { CheckGroupV2 } from '../../constructs/check-group-v2.js';
9
+ import { Dashboard } from '../../constructs/dashboard.js';
10
+ import { EmailAlertChannel } from '../../constructs/email-alert-channel.js';
11
+ import { IncidentioAlertChannel } from '../../constructs/incidentio-alert-channel.js';
12
+ import { MaintenanceWindow } from '../../constructs/maintenance-window.js';
13
+ import { MSTeamsAlertChannel } from '../../constructs/msteams-alert-channel.js';
14
+ import { OpsgenieAlertChannel } from '../../constructs/opsgenie-alert-channel.js';
15
+ import { PagerdutyAlertChannel } from '../../constructs/pagerduty-alert-channel.js';
16
+ import { PhoneCallAlertChannel } from '../../constructs/phone-call-alert-channel.js';
17
+ import { PrivateLocation } from '../../constructs/private-location.js';
18
+ import { SlackAlertChannel } from '../../constructs/slack-alert-channel.js';
19
+ import { SlackAppAlertChannel } from '../../constructs/slack-app-alert-channel.js';
20
+ import { SmsAlertChannel } from '../../constructs/sms-alert-channel.js';
21
+ import { StatusPage } from '../../constructs/status-page.js';
22
+ import { StatusPageService } from '../../constructs/status-page-service.js';
23
+ import { StatusPageV3, } from '../../constructs/status-page-v3.js';
24
+ import { StatusPageV3AutomationRule, } from '../../constructs/status-page-v3-automation-rule.js';
25
+ import { StatusPageV3Component, } from '../../constructs/status-page-v3-component.js';
26
+ import { TelegramAlertChannel } from '../../constructs/telegram-alert-channel.js';
27
+ import { WebhookAlertChannel } from '../../constructs/webhook-alert-channel.js';
28
+ import { DnsMonitor } from '../../constructs/dns-monitor.js';
29
+ import { GrpcMonitor } from '../../constructs/grpc-monitor.js';
30
+ import { HeartbeatMonitor } from '../../constructs/heartbeat-monitor.js';
31
+ import { IcmpMonitor } from '../../constructs/icmp-monitor.js';
32
+ import { MultiStepCheck } from '../../constructs/multi-step-check.js';
33
+ import { PlaywrightCheck } from '../../constructs/playwright-check.js';
34
+ import { SslMonitor } from '../../constructs/ssl-monitor.js';
35
+ import { TcpMonitor } from '../../constructs/tcp-monitor.js';
36
+ import { TracerouteMonitor } from '../../constructs/traceroute-monitor.js';
37
+ import { UrlMonitor } from '../../constructs/url-monitor.js';
38
+ import { hasEscalationPolicy } from '../../constructs/alert-escalation-policy-codegen.js';
39
+ import { PLAYWRIGHT_CHECK_OMITTED_PROPS } from '../../constructs/playwright-check-codegen.js';
40
+ import { blankRedacted, nodeAt, pointerSegments, UnshapeableError } from '../deploy-diff/import-shape.js';
41
+ import { applyEdits, readsBack } from './apply-edits.js';
42
+ import { exportedNamesOf, findConstructOptions, parseSource, WriteBackSkipped } from './source-file.js';
43
+ /** The whole lines of `text` that the range `[start, end)` touches. */
44
+ function linesSpanning(text, [start, end]) {
45
+ const from = text.lastIndexOf('\n', start - 1) + 1;
46
+ const to = text.indexOf('\n', end);
47
+ return text.slice(from, to === -1 ? text.length : to);
48
+ }
49
+ const identity = (...segments) => ({ pointer: segments, target: segments });
50
+ const set = (segment) => ({ pointer: [segment], target: [segment], set: true });
51
+ const under = (parent, keys) => keys.map(key => identity(...(typeof parent === 'string' ? [parent] : parent), key));
52
+ const assertions = (builder, ...parent) => ({ pointer: [...parent, 'assertions'], target: [...parent, 'assertions'], helper: { kind: 'assertions', builder } });
53
+ // The offset only means something for a sub-minute schedule; for any other
54
+ // the backend assigns one of its own and never reports it as a change.
55
+ const FREQUENCY_RULES = [
56
+ { pointer: ['frequency'], target: ['frequency'], helper: { kind: 'frequency' }, reads: [['frequencyOffset']] },
57
+ {
58
+ pointer: ['frequencyOffset'],
59
+ target: ['frequency'],
60
+ companion: true,
61
+ refuse: before => nodeAt(before, ['frequency']) === 0 ? undefined : 'the offset of a whole-minute schedule is assigned by Checkly',
62
+ },
63
+ ];
64
+ const RETRY_RULES = [
65
+ { pointer: ['retryStrategy'], target: ['retryStrategy'], helper: { kind: 'retryStrategy' }, unless: ['doubleCheck'] },
66
+ { pointer: ['doubleCheck'], target: ['retryStrategy'], companion: true },
67
+ ];
68
+ const GLOBAL_POLICY = 'Checkly uses the global alert policy; remove alertEscalationPolicy from the code by hand';
69
+ const alertRules = (policy) => [
70
+ {
71
+ pointer: ['alertSettings'],
72
+ target: ['alertEscalationPolicy'],
73
+ helper: { kind: 'alertEscalation', policy },
74
+ reads: [['useGlobalAlertSettings']],
75
+ },
76
+ {
77
+ pointer: ['useGlobalAlertSettings'],
78
+ target: ['alertEscalationPolicy'],
79
+ companion: true,
80
+ refuse: before => policy !== 'group-v2' && nodeAt(before, ['useGlobalAlertSettings']) === true ? GLOBAL_POLICY : undefined,
81
+ },
82
+ ];
83
+ // The keys each class writes are `as const` lists typed against the class's
84
+ // props, so that `_everyPropIsListed` below can hold the build to them: a
85
+ // key added to a props type has to be written or named as left out.
86
+ const CHECK_KEYS = ['name', 'description', 'activated', 'muted'];
87
+ const CHECK_SET_KEYS = ['tags', 'locations'];
88
+ const CHECK_HELPER_KEYS = ['frequency', 'retryStrategy', 'alertEscalationPolicy'];
89
+ const CHECK_WRITTEN = [...CHECK_KEYS, ...CHECK_SET_KEYS, ...CHECK_HELPER_KEYS];
90
+ // Every check class takes these; a class whose props omit some (as its
91
+ // codegen's omitted props say) gets the rest.
92
+ const CHECK_RULES = [
93
+ ...CHECK_KEYS.map(key => identity(key)), ...CHECK_SET_KEYS.map(set),
94
+ ...FREQUENCY_RULES, ...RETRY_RULES, ...alertRules('check'),
95
+ ];
96
+ const omitting = (rules, props) => rules.filter(rule => !props.includes(rule.target[0]));
97
+ const omit = (keys, omitted) => keys.filter((key) => !omitted.includes(key));
98
+ // Only the classes extending RuntimeCheck take a runtime and environment
99
+ // variables; a monitor would drop them when synthesized.
100
+ const RUNTIME_CHECK_KEYS = ['runtimeId', 'environmentVariables'];
101
+ const RUNTIME_CHECK_RULES = RUNTIME_CHECK_KEYS.map(key => identity(key));
102
+ // Only ApiCheck, UrlMonitor and TcpMonitor take `shouldFail`.
103
+ const SHOULD_FAIL_KEYS = ['shouldFail'];
104
+ const SHOULD_FAIL_RULES = SHOULD_FAIL_KEYS.map(key => identity(key));
105
+ const RESPONSE_TIME_KEYS = ['degradedResponseTime', 'maxResponseTime'];
106
+ const RESPONSE_TIME_RULES = RESPONSE_TIME_KEYS.map(key => identity(key));
107
+ const PACKET_LOSS_KEYS = [
108
+ 'degradedPacketLossThreshold', 'maxPacketLossThreshold',
109
+ ];
110
+ const BROWSER_KEYS = ['sslCheckDomain', 'aiAutoRepairEnabled'];
111
+ const MULTI_STEP_KEYS = ['aiAutoRepairEnabled'];
112
+ // The keys of each request type the account reports under the same name
113
+ // the construct uses, typed against the construct's interface so a renamed
114
+ // property fails the build; `Covers` below fails it for a key added to the
115
+ // interface but listed nowhere. `assertions` has its own helper rule.
116
+ const API_REQUEST_KEYS = [
117
+ 'url', 'method', 'ipFamily', 'followRedirects', 'skipSSL', 'body', 'bodyType', 'headers', 'queryParameters', 'basicAuth',
118
+ ];
119
+ const URL_REQUEST_KEYS = ['url', 'ipFamily', 'followRedirects', 'skipSSL'];
120
+ const TCP_REQUEST_KEYS = ['hostname', 'port', 'data', 'ipFamily'];
121
+ const DNS_REQUEST_KEYS = [
122
+ 'recordType', 'query', 'nameServer', 'port', 'protocol',
123
+ ];
124
+ const ICMP_REQUEST_KEYS = ['hostname', 'ipFamily', 'pingCount'];
125
+ const GRPC_REQUEST_KEYS = ['url', 'port', 'ipFamily', 'skipSSL', 'timeout'];
126
+ // `metadata` is never written — the account blanks every metadata value —
127
+ // but with a rule the refusal names that reason rather than a generic one.
128
+ const GRPC_CONFIG_KEYS = [
129
+ 'mode', 'tls', 'metadata', 'encoding', 'serviceDefinition', 'method', 'protoContent', 'bfbsContent', 'message',
130
+ 'service',
131
+ ];
132
+ const TRACEROUTE_REQUEST_KEYS = [
133
+ 'url', 'protocol', 'port', 'ipFamily', 'maxHops', 'maxUnknownHops', 'ptrLookup', 'timeout',
134
+ ];
135
+ // A DNS monitor refuses a name server without a port and a port without a
136
+ // name server, so the two are written together or not at all.
137
+ const DNS_REQUEST_RULES = DNS_REQUEST_KEYS.map(key => key === 'nameServer' || key === 'port' ? { ...identity('request', key), group: 'nameServer' } : identity('request', key));
138
+ /**
139
+ * The SSL request is the one the account spells differently from the
140
+ * construct: the wire shape nests the host, port and IP family under
141
+ * `sslConfig`, names the handshake timeout in milliseconds, and lifts the
142
+ * client certificate id to the request level.
143
+ */
144
+ const SSL_NESTED_KEYS = ['hostname', 'port', 'ipFamily'];
145
+ const SSL_CONFIG_KEYS = [
146
+ 'serverName', 'skipChainValidation', 'alertDaysBeforeExpiry', 'clientCertificateMode', 'securityBaseline',
147
+ ];
148
+ const SSL_REQUEST_RULES = [
149
+ ...SSL_NESTED_KEYS.map(key => ({ pointer: ['request', 'sslConfig', key], target: ['request', key] })),
150
+ ...under(['request', 'sslConfig'], SSL_CONFIG_KEYS),
151
+ { pointer: ['request', 'sslConfig', 'handshakeTimeoutMs'], target: ['request', 'sslConfig', 'handshakeTimeout'] },
152
+ { pointer: ['request', 'sslClientCertificateId'], target: ['request', 'sslConfig', 'sslClientCertificateId'] },
153
+ ];
154
+ // A build-time assertion only; nothing reads it.
155
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
156
+ const _everyRequestKeyIsListed = [true, true, true, true, true, true, true, true, true, true];
157
+ const HEARTBEAT_KEYS = ['period', 'periodUnit', 'grace', 'graceUnit'];
158
+ const GROUP_KEYS = [
159
+ 'name', 'activated', 'muted', 'concurrency', 'environmentVariables', 'runtimeId',
160
+ ];
161
+ const GROUP_SET_KEYS = ['tags', 'locations'];
162
+ const GROUP_HELPER_KEYS = [
163
+ 'apiCheckDefaults', 'retryStrategy', 'alertEscalationPolicy',
164
+ ];
165
+ const API_DEFAULT_KEYS = ['url', 'headers', 'queryParameters', 'basicAuth'];
166
+ const GROUP_RULES = [
167
+ ...GROUP_KEYS.map(key => identity(key)), ...GROUP_SET_KEYS.map(set),
168
+ ...under('apiCheckDefaults', API_DEFAULT_KEYS), assertions('AssertionBuilder', 'apiCheckDefaults'),
169
+ ...RETRY_RULES,
170
+ ];
171
+ /**
172
+ * A rule that refuses every change at its pointer with `reason` and writes
173
+ * nothing: a companion with no primary, for a leaf the construct fixes or
174
+ * builds from other props, so the refusal names why rather than saying
175
+ * the property is unknown.
176
+ */
177
+ const refusing = (pointer, reason) => ({ pointer, target: pointer, companion: true, refuse: () => reason });
178
+ const FIXED = 'fixed by the construct; it cannot be changed in the code';
179
+ // An alert channel's own props are flat in the code and nested under
180
+ // `config` on the wire (`synthesize` moves them), so their rules map
181
+ // `config/<key>` onto the top-level prop. A credential key (`url`, `apiKey`,
182
+ // `serviceKey`, `webhookSecret`, the values of `headers` and
183
+ // `queryParameters`) is listed like any other: the account blanks it, so
184
+ // the redaction check refuses it by name, and a change at the leaf itself
185
+ // arrives flagged as a secret and never reaches a rule.
186
+ const config = (keys) => keys.map(key => ({ pointer: ['config', key], target: [key] }));
187
+ const ALERT_CHANNEL_KEYS = [
188
+ 'sendRecovery', 'sendFailure', 'sendDegraded', 'sslExpiry', 'sslExpiryThreshold',
189
+ ];
190
+ const ALERT_CHANNEL_RULES = [
191
+ ...ALERT_CHANNEL_KEYS.map(key => identity(key)),
192
+ refusing(['type'], 'the channel type is the construct\'s class; change the class by hand'),
193
+ ];
194
+ const EMAIL_KEYS = ['address'];
195
+ const SLACK_KEYS = ['url', 'channel'];
196
+ const SLACK_APP_KEYS = ['slackChannels'];
197
+ const WEBHOOK_KEYS = [
198
+ 'name', 'webhookType', 'url', 'template', 'method', 'headers', 'queryParameters', 'webhookSecret',
199
+ ];
200
+ const OPSGENIE_KEYS = ['name', 'apiKey', 'region', 'priority'];
201
+ const PAGERDUTY_KEYS = ['account', 'serviceName', 'serviceKey'];
202
+ // SMS and phone call channels spell the number as `phoneNumber`; the wire has `number`.
203
+ const PHONE_KEYS = ['name'];
204
+ const PHONE_RULES = [...config(PHONE_KEYS), { pointer: ['config', 'number'], target: ['phoneNumber'] }];
205
+ // The webhook-based channels fix the webhook type and method, and spell the
206
+ // template as `payload`; Telegram packs its chat id, thread and payload
207
+ // into the template and its API key into the URL, and incident.io sends
208
+ // its API key as a header, none of which this module unpacks.
209
+ const WEBHOOK_FIXED_RULES = [refusing(['config', 'webhookType'], FIXED), refusing(['config', 'method'], FIXED)];
210
+ const TEMPLATE_RULE = { pointer: ['config', 'template'], target: ['payload'] };
211
+ const MSTEAMS_KEYS = ['name', 'url'];
212
+ const MSTEAMS_RULES = [...config(MSTEAMS_KEYS), TEMPLATE_RULE, ...WEBHOOK_FIXED_RULES];
213
+ const TELEGRAM_KEYS = ['name'];
214
+ const TELEGRAM_RULES = [
215
+ ...config(TELEGRAM_KEYS),
216
+ refusing(['config', 'template'], 'built from chatId, messageThreadId and payload; edit them by hand'),
217
+ ...WEBHOOK_FIXED_RULES,
218
+ ];
219
+ const INCIDENTIO_KEYS = ['name', 'url'];
220
+ const INCIDENTIO_RULES = [
221
+ ...config(INCIDENTIO_KEYS), TEMPLATE_RULE,
222
+ refusing(['config', 'headers'], 'built from apiKey; edit it by hand'),
223
+ ...WEBHOOK_FIXED_RULES,
224
+ ];
225
+ const PRIVATE_LOCATION_KEYS = ['name', 'slugName', 'icon', 'proxyUrl'];
226
+ // `customCSS` is a stylesheet the bundle sends as one string, which the
227
+ // construct takes as a file or content; it is refused, never written.
228
+ const DASHBOARD_KEYS = [
229
+ 'customUrl', 'customDomain', 'logo', 'favicon', 'link', 'header', 'description', 'width', 'refreshRate', 'paginate',
230
+ 'paginationRate', 'checksPerPage', 'useTagsAndOperator', 'hideTags', 'enableIncidents', 'expandChecks', 'showHeader',
231
+ 'isPrivate', 'showP95', 'showP99',
232
+ ];
233
+ const DASHBOARD_SET_KEYS = ['tags'];
234
+ const DASHBOARD_RULES = [
235
+ ...DASHBOARD_KEYS.map(key => identity(key)), ...DASHBOARD_SET_KEYS.map(set),
236
+ refusing(['customCSS'], 'a stylesheet, not a property; edit the file or the content by hand'),
237
+ ];
238
+ // The dates are `Date` objects in the code, written as `new Date('<iso>')`
239
+ // from the ISO string the account reports. The repeat settings only mean
240
+ // something together: an interval written without its unit, or without
241
+ // the date the repetition ends on, would be a different schedule.
242
+ const MAINTENANCE_WINDOW_KEYS = [
243
+ 'name', 'timezone', 'pauseAllChecks', 'silenceAllAlerts',
244
+ ];
245
+ const MAINTENANCE_WINDOW_SET_KEYS = ['tags', 'silenceAlertsTags'];
246
+ const MAINTENANCE_WINDOW_DATE_KEYS = ['startsAt', 'endsAt'];
247
+ const REPEAT_KEYS = ['repeatInterval', 'repeatUnit'];
248
+ const REPEAT_DATE_KEYS = ['repeatEndsAt'];
249
+ const date = (key) => ({ ...identity(key), helper: { kind: 'date' } });
250
+ const MAINTENANCE_WINDOW_RULES = [
251
+ ...MAINTENANCE_WINDOW_KEYS.map(key => identity(key)), ...MAINTENANCE_WINDOW_SET_KEYS.map(set),
252
+ ...MAINTENANCE_WINDOW_DATE_KEYS.map(date),
253
+ ...REPEAT_KEYS.map(key => ({ ...identity(key), group: 'repeat' })),
254
+ ...REPEAT_DATE_KEYS.map(key => ({ ...date(key), group: 'repeat' })),
255
+ ];
256
+ // A v2 status page's cards hold references to its services.
257
+ const STATUS_PAGE_KEYS = [
258
+ 'name', 'url', 'customDomain', 'logo', 'redirectTo', 'favicon', 'defaultTheme',
259
+ ];
260
+ const STATUS_PAGE_RULES = [
261
+ ...STATUS_PAGE_KEYS.map(key => identity(key)),
262
+ refusing(['cards'], 'cards hold status page services; edit them by hand'),
263
+ ];
264
+ const STATUS_PAGE_V3_KEYS = [
265
+ 'name', 'url', 'customDomain', 'description', 'logo', 'logoDark', 'redirectTo', 'favicon', 'defaultTheme',
266
+ 'privacyPolicyLink', 'termsOfServiceLink', 'supportLink', 'footerText', 'googleAnalyticsTag', 'allowIndexing',
267
+ ];
268
+ const THEMES = ['light', 'dark'];
269
+ const THEME_COLOR_KEYS = [
270
+ 'bodyBackgroundColor', 'headerBackgroundColor', 'headerFontColor', 'titleFontColor', 'bodyFontColor',
271
+ 'bodyFontColorMuted', 'navigationFontColor', 'linkFontColor', 'cardBackgroundColor', 'borderColor',
272
+ 'primaryButtonBackgroundColor', 'primaryButtonFontColor',
273
+ ];
274
+ // The account reports each colour as its own leaf; the code holds it under
275
+ // `themeColors.light` or `.dark`, which has to exist for a colour to be added.
276
+ const STATUS_PAGE_V3_RULES = [
277
+ ...STATUS_PAGE_V3_KEYS.map(key => identity(key)),
278
+ ...THEMES.flatMap(theme => under(['themeColors', theme], THEME_COLOR_KEYS)),
279
+ refusing(['version'], FIXED),
280
+ ];
281
+ const STATUS_PAGE_SERVICE_KEYS = ['name'];
282
+ // A component's `showHistoricalData` and `expandedByDefault` travel inside
283
+ // `configuration`; its page and parent are references.
284
+ const COMPONENT_KEYS = [
285
+ 'type', 'name', 'description', 'hidden', 'displayOrder',
286
+ ];
287
+ const COMPONENT_CONFIGURATION_KEYS = [
288
+ 'showHistoricalData', 'expandedByDefault',
289
+ ];
290
+ const COMPONENT_RULES = [
291
+ ...COMPONENT_KEYS.map(key => identity(key)),
292
+ ...COMPONENT_CONFIGURATION_KEYS.map(key => ({ pointer: ['configuration', key], target: [key] })),
293
+ ];
294
+ const AUTOMATION_RULE_KEYS = [
295
+ 'name', 'enabled', 'firstUpdate', 'lastUpdate', 'notifySubscribers',
296
+ ];
297
+ const AUTOMATION_RULE_SET_KEYS = ['tags'];
298
+ const AUTOMATION_RULE_RULES = [
299
+ ...AUTOMATION_RULE_KEYS.map(key => identity(key)), ...AUTOMATION_RULE_SET_KEYS.map(set),
300
+ { pointer: ['coolDownWindowMinutes'], target: ['coolDownMinutes'] },
301
+ ];
302
+ // What each class writes, at the top level of its props. `request`,
303
+ // `apiCheckDefaults` and `themeColors` stand for every rule under them.
304
+ const API_WRITTEN = [...CHECK_WRITTEN, ...SHOULD_FAIL_KEYS, ...RUNTIME_CHECK_KEYS, ...RESPONSE_TIME_KEYS, 'request'];
305
+ const BROWSER_WRITTEN = [...CHECK_WRITTEN, ...RUNTIME_CHECK_KEYS, ...BROWSER_KEYS];
306
+ const MULTI_STEP_WRITTEN = [...CHECK_WRITTEN, ...RUNTIME_CHECK_KEYS, ...MULTI_STEP_KEYS];
307
+ const PLAYWRIGHT_WRITTEN = [...omit(CHECK_WRITTEN, PLAYWRIGHT_CHECK_OMITTED_PROPS), ...RUNTIME_CHECK_KEYS];
308
+ // The monitors with a request and response-time thresholds share one list.
309
+ const MONITOR_WRITTEN = [...CHECK_WRITTEN, ...RESPONSE_TIME_KEYS, 'request'];
310
+ const SHOULD_FAIL_MONITOR_WRITTEN = [...MONITOR_WRITTEN, ...SHOULD_FAIL_KEYS];
311
+ const ICMP_WRITTEN = [...CHECK_WRITTEN, ...PACKET_LOSS_KEYS, 'request'];
312
+ const HEARTBEAT_WRITTEN = [...CHECK_WRITTEN, ...HEARTBEAT_KEYS];
313
+ const GROUP_WRITTEN = [...GROUP_KEYS, ...GROUP_SET_KEYS, ...GROUP_HELPER_KEYS];
314
+ const EMAIL_WRITTEN = [...ALERT_CHANNEL_KEYS, ...EMAIL_KEYS];
315
+ const SLACK_WRITTEN = [...ALERT_CHANNEL_KEYS, ...SLACK_KEYS];
316
+ const SLACK_APP_WRITTEN = [...ALERT_CHANNEL_KEYS, ...SLACK_APP_KEYS];
317
+ const WEBHOOK_WRITTEN = [...ALERT_CHANNEL_KEYS, ...WEBHOOK_KEYS];
318
+ const OPSGENIE_WRITTEN = [...ALERT_CHANNEL_KEYS, ...OPSGENIE_KEYS];
319
+ const PAGERDUTY_WRITTEN = [...ALERT_CHANNEL_KEYS, ...PAGERDUTY_KEYS];
320
+ const PHONE_WRITTEN = [...ALERT_CHANNEL_KEYS, ...PHONE_KEYS, 'phoneNumber'];
321
+ const MSTEAMS_WRITTEN = [...ALERT_CHANNEL_KEYS, ...MSTEAMS_KEYS, 'payload'];
322
+ const TELEGRAM_WRITTEN = [...ALERT_CHANNEL_KEYS, ...TELEGRAM_KEYS];
323
+ const INCIDENTIO_WRITTEN = [...ALERT_CHANNEL_KEYS, ...INCIDENTIO_KEYS, 'payload'];
324
+ const DASHBOARD_WRITTEN = [...DASHBOARD_KEYS, ...DASHBOARD_SET_KEYS];
325
+ const MAINTENANCE_WINDOW_WRITTEN = [
326
+ ...MAINTENANCE_WINDOW_KEYS, ...MAINTENANCE_WINDOW_SET_KEYS, ...MAINTENANCE_WINDOW_DATE_KEYS,
327
+ ...REPEAT_KEYS, ...REPEAT_DATE_KEYS,
328
+ ];
329
+ const STATUS_PAGE_V3_WRITTEN = [...STATUS_PAGE_V3_KEYS, 'themeColors'];
330
+ const COMPONENT_WRITTEN = [...COMPONENT_KEYS, ...COMPONENT_CONFIGURATION_KEYS];
331
+ const AUTOMATION_RULE_WRITTEN = [...AUTOMATION_RULE_KEYS, ...AUTOMATION_RULE_SET_KEYS, 'coolDownMinutes'];
332
+ // A build-time assertion only; nothing reads it. When it fails, the props
333
+ // type named at the failing position gained or lost a key: add it to the
334
+ // class's written list and a rule, or to the reasons above.
335
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
336
+ const _everyPropIsListed = [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true];
337
+ // The same for the other resource types.
338
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
339
+ const _everyResourcePropIsListed = [
340
+ true, true, true, true, true, true, true, true, true, true, true, true,
341
+ true, true, true, true, true, true, true, true, true, true, true, true,
342
+ ];
343
+ export const RULES_BY_CLASS = new Map([
344
+ [ApiCheck, [
345
+ ...CHECK_RULES, ...SHOULD_FAIL_RULES, ...RUNTIME_CHECK_RULES, ...RESPONSE_TIME_RULES,
346
+ ...under('request', API_REQUEST_KEYS), assertions('AssertionBuilder', 'request'),
347
+ ]],
348
+ [BrowserCheck, [...CHECK_RULES, ...RUNTIME_CHECK_RULES, ...BROWSER_KEYS.map(key => identity(key))]],
349
+ [MultiStepCheck, [...CHECK_RULES, ...RUNTIME_CHECK_RULES, ...MULTI_STEP_KEYS.map(key => identity(key))]],
350
+ [PlaywrightCheck, [...omitting(CHECK_RULES, PLAYWRIGHT_CHECK_OMITTED_PROPS), ...RUNTIME_CHECK_RULES]],
351
+ [UrlMonitor, [
352
+ ...CHECK_RULES, ...SHOULD_FAIL_RULES, ...RESPONSE_TIME_RULES, ...under('request', URL_REQUEST_KEYS), assertions('UrlAssertionBuilder', 'request'),
353
+ ]],
354
+ [TcpMonitor, [
355
+ ...CHECK_RULES, ...SHOULD_FAIL_RULES, ...RESPONSE_TIME_RULES, ...under('request', TCP_REQUEST_KEYS), assertions('TcpAssertionBuilder', 'request'),
356
+ ]],
357
+ [DnsMonitor, [...CHECK_RULES, ...RESPONSE_TIME_RULES, ...DNS_REQUEST_RULES, assertions('DnsAssertionBuilder', 'request')]],
358
+ [GrpcMonitor, [
359
+ ...CHECK_RULES, ...RESPONSE_TIME_RULES, ...under('request', GRPC_REQUEST_KEYS),
360
+ ...under(['request', 'grpcConfig'], GRPC_CONFIG_KEYS), assertions('GrpcAssertionBuilder', 'request'),
361
+ ]],
362
+ [SslMonitor, [...CHECK_RULES, ...RESPONSE_TIME_RULES, ...SSL_REQUEST_RULES, assertions('SslAssertionBuilder', 'request')]],
363
+ [TracerouteMonitor, [
364
+ ...CHECK_RULES, ...RESPONSE_TIME_RULES, ...under('request', TRACEROUTE_REQUEST_KEYS),
365
+ assertions('TracerouteAssertionBuilder', 'request'),
366
+ ]],
367
+ [IcmpMonitor, [
368
+ ...CHECK_RULES, ...PACKET_LOSS_KEYS.map(key => identity(key)),
369
+ ...under('request', ICMP_REQUEST_KEYS), assertions('IcmpAssertionBuilder', 'request'),
370
+ ]],
371
+ [HeartbeatMonitor, [
372
+ ...CHECK_RULES,
373
+ ...HEARTBEAT_KEYS.map(key => ({ pointer: ['heartbeat', key], target: [key], group: key.startsWith('period') ? 'period' : 'grace' })),
374
+ ]],
375
+ [CheckGroupV1, [...GROUP_RULES, ...alertRules('group')]],
376
+ [CheckGroupV2, [...GROUP_RULES, ...alertRules('group-v2')]],
377
+ [EmailAlertChannel, [...ALERT_CHANNEL_RULES, ...config(EMAIL_KEYS)]],
378
+ [SlackAlertChannel, [...ALERT_CHANNEL_RULES, ...config(SLACK_KEYS)]],
379
+ [SlackAppAlertChannel, [...ALERT_CHANNEL_RULES, ...config(SLACK_APP_KEYS)]],
380
+ [WebhookAlertChannel, [...ALERT_CHANNEL_RULES, ...config(WEBHOOK_KEYS)]],
381
+ [OpsgenieAlertChannel, [...ALERT_CHANNEL_RULES, ...config(OPSGENIE_KEYS)]],
382
+ [PagerdutyAlertChannel, [...ALERT_CHANNEL_RULES, ...config(PAGERDUTY_KEYS)]],
383
+ [SmsAlertChannel, [...ALERT_CHANNEL_RULES, ...PHONE_RULES]],
384
+ [PhoneCallAlertChannel, [...ALERT_CHANNEL_RULES, ...PHONE_RULES]],
385
+ [MSTeamsAlertChannel, [...ALERT_CHANNEL_RULES, ...MSTEAMS_RULES]],
386
+ [TelegramAlertChannel, [...ALERT_CHANNEL_RULES, ...TELEGRAM_RULES]],
387
+ [IncidentioAlertChannel, [...ALERT_CHANNEL_RULES, ...INCIDENTIO_RULES]],
388
+ [PrivateLocation, PRIVATE_LOCATION_KEYS.map(key => identity(key))],
389
+ [Dashboard, DASHBOARD_RULES],
390
+ [MaintenanceWindow, MAINTENANCE_WINDOW_RULES],
391
+ [StatusPage, STATUS_PAGE_RULES],
392
+ [StatusPageV3, STATUS_PAGE_V3_RULES],
393
+ [StatusPageService, STATUS_PAGE_SERVICE_KEYS.map(key => identity(key))],
394
+ [StatusPageV3Component, COMPONENT_RULES],
395
+ [StatusPageV3AutomationRule, AUTOMATION_RULE_RULES],
396
+ ]);
397
+ /** The top-level props keys each class's rules write, as the build-time assertion above knows them; the spec holds `RULES_BY_CLASS` to it. */
398
+ export const WRITTEN_BY_CLASS = new Map([
399
+ [ApiCheck, API_WRITTEN], [BrowserCheck, BROWSER_WRITTEN], [MultiStepCheck, MULTI_STEP_WRITTEN],
400
+ [PlaywrightCheck, PLAYWRIGHT_WRITTEN],
401
+ [UrlMonitor, SHOULD_FAIL_MONITOR_WRITTEN], [TcpMonitor, SHOULD_FAIL_MONITOR_WRITTEN], [DnsMonitor, MONITOR_WRITTEN],
402
+ [GrpcMonitor, MONITOR_WRITTEN], [SslMonitor, MONITOR_WRITTEN], [TracerouteMonitor, MONITOR_WRITTEN],
403
+ [IcmpMonitor, ICMP_WRITTEN],
404
+ [HeartbeatMonitor, HEARTBEAT_WRITTEN], [CheckGroupV1, GROUP_WRITTEN], [CheckGroupV2, GROUP_WRITTEN],
405
+ [EmailAlertChannel, EMAIL_WRITTEN], [SlackAlertChannel, SLACK_WRITTEN], [SlackAppAlertChannel, SLACK_APP_WRITTEN],
406
+ [WebhookAlertChannel, WEBHOOK_WRITTEN], [OpsgenieAlertChannel, OPSGENIE_WRITTEN],
407
+ [PagerdutyAlertChannel, PAGERDUTY_WRITTEN], [SmsAlertChannel, PHONE_WRITTEN],
408
+ [PhoneCallAlertChannel, PHONE_WRITTEN],
409
+ [MSTeamsAlertChannel, MSTEAMS_WRITTEN], [TelegramAlertChannel, TELEGRAM_WRITTEN],
410
+ [IncidentioAlertChannel, INCIDENTIO_WRITTEN],
411
+ [PrivateLocation, PRIVATE_LOCATION_KEYS], [Dashboard, DASHBOARD_WRITTEN],
412
+ [MaintenanceWindow, MAINTENANCE_WINDOW_WRITTEN], [StatusPage, STATUS_PAGE_KEYS],
413
+ [StatusPageV3, STATUS_PAGE_V3_WRITTEN], [StatusPageService, STATUS_PAGE_SERVICE_KEYS],
414
+ [StatusPageV3Component, COMPONENT_WRITTEN], [StatusPageV3AutomationRule, AUTOMATION_RULE_WRITTEN],
415
+ ]);
416
+ /**
417
+ * Paths that name another resource or a relation rather than a value of this
418
+ * one, by the property the construct spells them as. A refused reference is
419
+ * reported under that property, since the plan above the note names it that
420
+ * way too: Checkly reports a check's group as `groupId`, the construct takes
421
+ * `group`.
422
+ */
423
+ const REFERENCE_PROPERTIES = new Map([
424
+ ['alertChannels', 'alertChannels'],
425
+ ['privateLocations', 'privateLocations'],
426
+ ['alertChannelSubscriptions', 'alertChannelSubscriptions'],
427
+ ['privateLocationAssignments', 'privateLocationAssignments'],
428
+ ['groupId', 'group'],
429
+ ['statusPageId', 'statusPage'],
430
+ ['parentId', 'parent'],
431
+ ['components', 'components'],
432
+ ]);
433
+ /** Properties a remote change to is reported rather than written, with the reason. */
434
+ const NOT_WRITTEN = new Map([
435
+ // Dropped again at the end of `planWriteBack` for a resource whose retry
436
+ // strategy was written: the advice would be wrong there.
437
+ ['doubleCheck', 'replaced by retryStrategy; set the retry strategy in the code by hand'],
438
+ ['runParallel', 'not a property this tool can update'],
439
+ ['triggerIncident', 'Checkly does not report the incident trigger\'s settings; edit it by hand'],
440
+ ['playwrightConfig', 'this tool does not update the Playwright config yet; set it in checkly.config.ts or on the check by hand'],
441
+ ['engine', 'this tool does not update the engine yet; set it by hand'],
442
+ ['engineVersion', 'this tool does not update the engine yet; set it by hand'],
443
+ ]);
444
+ /** Whether a reported value is one of the API's stand-ins (`{ $hash }`, `{ $masked }`, `{ $json }`, `{ $ref }`) or holds one. */
445
+ function withheld(value) {
446
+ if (Array.isArray(value)) {
447
+ return value.some(withheld);
448
+ }
449
+ if (value !== null && typeof value === 'object') {
450
+ const keys = Object.keys(value);
451
+ if (keys.some(key => key === '$hash' || key === '$masked' || key === '$json' || key === '$ref')) {
452
+ return true;
453
+ }
454
+ return Object.values(value).some(withheld);
455
+ }
456
+ return false;
457
+ }
458
+ const startsWith = (segments, prefix) => prefix.length <= segments.length && prefix.every((segment, i) => segments[i] === segment);
459
+ /**
460
+ * The account-side value a change reports, if it reports one: `after` is the
461
+ * current value and `before` the one at the last deploy, read from `remote`
462
+ * when both sides moved. A missing key means the element is absent on that
463
+ * side.
464
+ */
465
+ function reported(change, side) {
466
+ const holder = change.origin === 'both' ? change.remote : change;
467
+ return { present: holder !== undefined && side in holder, value: holder?.[side] };
468
+ }
469
+ /**
470
+ * Whether the change's own report of the account value agrees with what
471
+ * `before` holds at the write path — the one guard on the assumption that
472
+ * `before` is the live resource. A set element is checked by membership:
473
+ * an element the change says is there must be in the list, one it says is
474
+ * gone must not be. A withheld value cannot be compared and passes.
475
+ */
476
+ function agrees(change, rule, remaining, raw) {
477
+ const current = reported(change, 'after');
478
+ if (rule.set && remaining.length > 0) {
479
+ if (!Array.isArray(raw)) {
480
+ return false;
481
+ }
482
+ const previous = reported(change, 'before');
483
+ const holds = (value) => raw.some(element => isDeepStrictEqual(element, value));
484
+ const added = !current.present || withheld(current.value) || holds(current.value);
485
+ const removed = !previous.present || withheld(previous.value) || !holds(previous.value);
486
+ return added && removed;
487
+ }
488
+ // A path into a list that is not an index names an element by key, which
489
+ // only a set does; nothing can be checked against it.
490
+ if (remaining.length > 0 && Array.isArray(raw) && !/^\d+$/.test(remaining[0])) {
491
+ return false;
492
+ }
493
+ if (!current.present) {
494
+ // A leaf the account no longer holds is either gone, or — when it was a
495
+ // scalar or null — became a subtree whose leaves the sibling changes
496
+ // report (a retry strategy where there was null, a policy that gained
497
+ // its reminders). An object that is gone is gone.
498
+ const held = nodeAt(raw, remaining);
499
+ const previous = reported(change, 'before').value;
500
+ const wasLeaf = previous === null || typeof previous !== 'object';
501
+ return held === undefined || (wasLeaf && held !== null && typeof held === 'object');
502
+ }
503
+ const held = nodeAt(raw, remaining);
504
+ // The import format leaves out an optional key the account cleared
505
+ // (`serverName`, `nameServer`, `data`, …), which the change reports as
506
+ // null: at the rule's own leaf that is the same absence, and the writer
507
+ // then refuses it as a value Checkly does not hold. Below the leaf the
508
+ // parent would be written without the key, so the disagreement stands.
509
+ const cleared = remaining.length === 0 && held === undefined && current.value === null;
510
+ return withheld(current.value) || cleared || isDeepStrictEqual(held, current.value);
511
+ }
512
+ /** A reason a change cannot be written, or undefined when it may be. */
513
+ function refusal(change, segments) {
514
+ if (change.cause !== undefined) {
515
+ return `${change.cause}: content, not a property`;
516
+ }
517
+ if (change.secret === true) {
518
+ return 'a secret changed; Checkly does not return its value';
519
+ }
520
+ if (REFERENCE_PROPERTIES.has(segments[0])) {
521
+ return 'references another resource';
522
+ }
523
+ const notWritten = NOT_WRITTEN.get(segments[0]);
524
+ if (notWritten !== undefined) {
525
+ return notWritten;
526
+ }
527
+ if (segments[0] === 'intent') {
528
+ return 'the intent is ordered by the author; edit it by hand';
529
+ }
530
+ return undefined;
531
+ }
532
+ /**
533
+ * The property a refused change is reported under: a reference as the
534
+ * construct spells it, anything else as the path Checkly reports.
535
+ */
536
+ function refusedProperty(change, segments) {
537
+ const reference = REFERENCE_PROPERTIES.get(segments[0]);
538
+ return reference === undefined ? change.path : [reference, ...segments.slice(1)].join('.');
539
+ }
540
+ class EntryContext {
541
+ entry;
542
+ skipped;
543
+ constructor(entry, skipped) {
544
+ this.entry = entry;
545
+ this.skipped = skipped;
546
+ }
547
+ skip(reason, property) {
548
+ const { type, logicalId } = this.entry;
549
+ this.skipped.push(property === undefined ? { type, logicalId, reason } : { type, logicalId, property, reason });
550
+ }
551
+ }
552
+ /** The remote changes of an entry grouped by the construct path they write, or the reasons they cannot be. */
553
+ function candidates(context, rules) {
554
+ const byPath = new Map();
555
+ const segmentsOf = (change) => {
556
+ try {
557
+ return pointerSegments(change.path);
558
+ }
559
+ catch {
560
+ return undefined;
561
+ }
562
+ };
563
+ const ruleFor = (segments) => rules.find(rule => startsWith(segments, rule.pointer));
564
+ const primaryOf = (rule) => rule.companion === undefined
565
+ ? rule
566
+ : rules.find(other => other.companion === undefined && isDeepStrictEqual(other.target, rule.target));
567
+ for (const change of context.entry.changes ?? []) {
568
+ if (change.origin !== 'remote' && change.origin !== 'both') {
569
+ continue;
570
+ }
571
+ const segments = segmentsOf(change);
572
+ if (segments === undefined) {
573
+ context.skip('not a property path', change.path);
574
+ continue;
575
+ }
576
+ const reason = refusal(change, segments);
577
+ if (reason !== undefined) {
578
+ context.skip(reason, refusedProperty(change, segments));
579
+ continue;
580
+ }
581
+ const rule = ruleFor(segments);
582
+ // A refusing rule has a reason of its own and may have no primary.
583
+ const refused = rule?.refuse?.(context.entry.before);
584
+ if (refused !== undefined) {
585
+ context.skip(refused, change.path);
586
+ continue;
587
+ }
588
+ const primary = rule === undefined ? undefined : primaryOf(rule);
589
+ if (rule === undefined || primary === undefined) {
590
+ context.skip('not a property this tool can update', change.path);
591
+ continue;
592
+ }
593
+ const key = primary.target.join('.');
594
+ const candidate = byPath.get(key) ?? { rule: primary, changes: [], local: [] };
595
+ candidate.changes.push({ change, rule });
596
+ byPath.set(key, candidate);
597
+ }
598
+ // A list is written whole from `before`, which knows nothing of an element
599
+ // the code added and has not deployed; such an edit must not be erased.
600
+ // A property written together with others (`group`) is local when any of
601
+ // them is: an interval written next to a unit the code changed would be a
602
+ // schedule nobody set.
603
+ for (const change of context.entry.changes ?? []) {
604
+ if (change.origin !== 'code') {
605
+ continue;
606
+ }
607
+ const segments = segmentsOf(change);
608
+ const rule = segments === undefined ? undefined : ruleFor(segments);
609
+ if (rule === undefined) {
610
+ continue;
611
+ }
612
+ for (const candidate of byPath.values()) {
613
+ if (candidate.rule === primaryOf(rule) || (rule.group !== undefined && candidate.rule.group === rule.group)) {
614
+ candidate.local.push(change);
615
+ }
616
+ }
617
+ }
618
+ return [...byPath.values()];
619
+ }
620
+ function valueFor(context, candidate, before, blanked) {
621
+ const { rule } = candidate;
622
+ const property = rule.target.join('.');
623
+ if (candidate.local.length > 0) {
624
+ context.skip('your code also changed it since the last deploy; merge by hand', property);
625
+ return undefined;
626
+ }
627
+ for (const segments of [rule.pointer, ...(rule.reads ?? [])]) {
628
+ const held = nodeAt(before, segments);
629
+ if (!isDeepStrictEqual(held, nodeAt(blanked, segments))) {
630
+ context.skip('contains a locked or secret value that Checkly does not return', property);
631
+ return undefined;
632
+ }
633
+ if (withheld(held)) {
634
+ context.skip('contains a value Checkly does not return in full', property);
635
+ return undefined;
636
+ }
637
+ }
638
+ for (const { change, rule: own } of candidate.changes) {
639
+ if (change.origin === 'both' && change.remote === undefined) {
640
+ context.skip('Checkly did not report the value it holds', property);
641
+ return undefined;
642
+ }
643
+ const remaining = pointerSegments(change.path).slice(own.pointer.length);
644
+ if (!agrees(change, own, remaining, nodeAt(before, own.pointer))) {
645
+ context.skip('Checkly reported two different current values', property);
646
+ return undefined;
647
+ }
648
+ }
649
+ const raw = nodeAt(before, rule.pointer);
650
+ const found = { value: raw, helper: rule.helper, unless: rule.unless };
651
+ switch (rule.helper?.kind) {
652
+ case 'frequency':
653
+ // A whole-minute schedule stays a number where the code spells it as
654
+ // one; the helper form is for a sub-minute schedule, or a code that
655
+ // uses the helper already.
656
+ found.value = { frequency: raw, frequencyOffset: nodeAt(before, ['frequencyOffset']) };
657
+ found.literalAlternative = typeof raw === 'number' && raw > 0 ? raw : undefined;
658
+ break;
659
+ case 'alertEscalation': {
660
+ const { policy } = rule.helper;
661
+ if (nodeAt(before, ['useGlobalAlertSettings']) === true) {
662
+ if (policy !== 'group-v2') {
663
+ context.skip(GLOBAL_POLICY, property);
664
+ return undefined;
665
+ }
666
+ found.value = 'global';
667
+ break;
668
+ }
669
+ if (!hasEscalationPolicy(raw)) {
670
+ // A group can have no policy of its own while its checks keep
671
+ // theirs, which the construct spells by having none.
672
+ context.skip(policy === 'check'
673
+ ? 'Checkly did not report an alert policy'
674
+ : 'the group has no alert policy of its own; remove alertEscalationPolicy from the code by hand', property);
675
+ return undefined;
676
+ }
677
+ break;
678
+ }
679
+ default:
680
+ break;
681
+ }
682
+ return found;
683
+ }
684
+ /** The edit for a found value: a literal one, or the helper one the rule asks for. */
685
+ function editFor(path, found) {
686
+ const { value, helper, unless, literalAlternative } = found;
687
+ if (helper === undefined) {
688
+ return { path, value };
689
+ }
690
+ return helper.kind === 'assertions'
691
+ ? { path, value, helper: 'assertions', builder: helper.builder, unless, literalAlternative }
692
+ : { path, value, helper: helper.kind, unless, literalAlternative };
693
+ }
694
+ /**
695
+ * Whether the plan holds at least one change the planner would try to
696
+ * write: a remote change on a construct of a known class, at a path the
697
+ * class's table covers. Cheap enough to decide whether to offer the
698
+ * write-back at all, without reading any file.
699
+ */
700
+ export function hasWritableChanges(diff, project) {
701
+ return diff.some(entry => {
702
+ if (entry.foldedInto !== undefined || entry.before === undefined) {
703
+ return false;
704
+ }
705
+ const construct = project.data[entry.type]?.[entry.logicalId];
706
+ const rules = construct === undefined ? undefined : RULES_BY_CLASS.get(construct.constructor);
707
+ if (rules === undefined || construct?.checkFileAbsolutePath === undefined) {
708
+ return false;
709
+ }
710
+ return candidates(new EntryContext(entry, []), rules).length > 0;
711
+ });
712
+ }
713
+ export async function planWriteBack({ diff, project, cwd }) {
714
+ const skipped = [];
715
+ const byFile = new Map();
716
+ for (const entry of diff) {
717
+ if (entry.foldedInto !== undefined || !(entry.changes ?? []).some(c => c.origin === 'remote' || c.origin === 'both')) {
718
+ continue;
719
+ }
720
+ const context = new EntryContext(entry, skipped);
721
+ const construct = project.data[entry.type]?.[entry.logicalId];
722
+ if (construct === undefined) {
723
+ // A resource the deploy removes has no construct to edit; only one the
724
+ // code still declares is worth a line.
725
+ if (String(entry.action).toUpperCase() === 'UPDATE') {
726
+ context.skip('not found in the project');
727
+ }
728
+ continue;
729
+ }
730
+ const rules = RULES_BY_CLASS.get(construct.constructor);
731
+ if (rules === undefined) {
732
+ // A reference to a resource of another project, or a relation, has
733
+ // no properties of its own; a class of the user's own is not known.
734
+ context.skip(exportedNamesOf(construct).size === 0
735
+ ? `${construct.constructor.name} is not a class from checkly/constructs`
736
+ : `${construct.constructor.name} has no properties this tool can update`);
737
+ continue;
738
+ }
739
+ if (construct.checkFileAbsolutePath === undefined) {
740
+ context.skip('the file that declares it is not known');
741
+ continue;
742
+ }
743
+ if (entry.before === undefined) {
744
+ context.skip('Checkly did not report its current state');
745
+ continue;
746
+ }
747
+ let blanked;
748
+ try {
749
+ blanked = blankRedacted(entry.before, entry.redactions);
750
+ }
751
+ catch (err) {
752
+ if (err instanceof UnshapeableError) {
753
+ context.skip('Checkly did not report which of its values are secret');
754
+ continue;
755
+ }
756
+ throw err;
757
+ }
758
+ const found = candidates(context, rules).map(candidate => ({
759
+ candidate, found: valueFor(context, candidate, entry.before, blanked),
760
+ }));
761
+ // A member of a group refused here takes the rest of its group with it,
762
+ // as one the splicer refuses does below.
763
+ const refusedGroups = new Set(found.filter(entry => entry.found === undefined)
764
+ .map(entry => entry.candidate.rule.group));
765
+ const edits = [];
766
+ for (const { candidate, found: value } of found) {
767
+ const { group, target } = candidate.rule;
768
+ if (value === undefined) {
769
+ continue;
770
+ }
771
+ if (group !== undefined && refusedGroups.has(group)) {
772
+ const others = found.filter(entry => entry.candidate.rule.group === group && entry.candidate !== candidate);
773
+ context.skip(`written together with ${others.map(entry => entry.candidate.rule.target.join('.')).join(', ')}`, target.join('.'));
774
+ continue;
775
+ }
776
+ edits.push({
777
+ ...editFor(target, value),
778
+ replacesLocalEdit: candidate.changes.some(({ change }) => change.origin === 'both'),
779
+ group,
780
+ });
781
+ }
782
+ if (edits.length === 0) {
783
+ continue;
784
+ }
785
+ const work = byFile.get(construct.checkFileAbsolutePath) ?? [];
786
+ work.push({ names: exportedNamesOf(construct), context, edits });
787
+ byFile.set(construct.checkFileAbsolutePath, work);
788
+ }
789
+ const files = [];
790
+ const applied = [];
791
+ const constructs = [];
792
+ const imports = [];
793
+ for (const [filePath, work] of byFile) {
794
+ const file = path.relative(cwd, filePath);
795
+ let text;
796
+ try {
797
+ text = await fs.readFile(filePath, 'utf8');
798
+ }
799
+ catch (err) {
800
+ for (const { context } of work) {
801
+ context.skip(`could not read ${file}: ${err.message}`);
802
+ }
803
+ continue;
804
+ }
805
+ const original = text;
806
+ const lines = [];
807
+ const edited = [];
808
+ const added = new Set();
809
+ // Constructs of one file are edited one after another, each against a
810
+ // fresh parse of the text the previous one produced, so no range is stale.
811
+ for (const { names, context, edits } of work) {
812
+ const logicalId = context.entry.logicalId;
813
+ let source;
814
+ let options;
815
+ try {
816
+ source = parseSource(filePath, text);
817
+ options = findConstructOptions(source, logicalId, names);
818
+ }
819
+ catch (err) {
820
+ if (!(err instanceof WriteBackSkipped)) {
821
+ throw err;
822
+ }
823
+ context.skip(`${file}: ${err.message}`);
824
+ continue;
825
+ }
826
+ try {
827
+ // A member of a group the splicer refuses takes the rest of its group
828
+ // with it: a period without its unit would mean something else.
829
+ let attempt = edits;
830
+ let result = applyEdits(source, options, attempt);
831
+ for (;;) {
832
+ const refused = new Set(result.skipped.map(skip => attempt.find(edit => edit.path === skip.path)?.group)
833
+ .filter((group) => group !== undefined));
834
+ const dropped = attempt.filter(edit => edit.group !== undefined && refused.has(edit.group)
835
+ && !result.skipped.some(skip => skip.path === edit.path));
836
+ if (dropped.length === 0) {
837
+ break;
838
+ }
839
+ for (const edit of dropped) {
840
+ context.skip(`written together with ${attempt.filter(e => e.group === edit.group && e !== edit).map(e => e.path.join('.')).join(', ')}`, edit.path.join('.'));
841
+ }
842
+ attempt = attempt.filter(edit => !dropped.includes(edit));
843
+ result = applyEdits(source, options, attempt);
844
+ }
845
+ for (const skip of result.skipped) {
846
+ context.skip(skip.reason, skip.path.join('.'));
847
+ }
848
+ if (result.applied.length === 0) {
849
+ continue;
850
+ }
851
+ // What was written must read back as what was meant, or the file is
852
+ // left alone: a splice that produced something else is a bug, and the
853
+ // user's source is not the place to find out.
854
+ const reparsed = findConstructOptions(parseSource(filePath, result.text), logicalId, names);
855
+ for (const edit of result.applied) {
856
+ if (!readsBack(reparsed, edit)) {
857
+ throw new WriteBackSkipped(`the edited file did not read back as expected at ${edit.path.join('.')}`);
858
+ }
859
+ }
860
+ edited.push({
861
+ file,
862
+ type: context.entry.type,
863
+ logicalId,
864
+ before: linesSpanning(source.text, options.range),
865
+ after: linesSpanning(result.text, reparsed.range),
866
+ });
867
+ text = result.text;
868
+ result.imports.forEach(name => added.add(name));
869
+ for (const edit of result.applied) {
870
+ lines.push({
871
+ file,
872
+ type: context.entry.type,
873
+ logicalId,
874
+ property: edit.path.join('.'),
875
+ previous: edit.previous,
876
+ rendered: edit.rendered,
877
+ replacesLocalEdit: edits.find(e => e.path === edit.path)?.replacesLocalEdit ?? false,
878
+ });
879
+ }
880
+ }
881
+ catch (err) {
882
+ if (!(err instanceof WriteBackSkipped)) {
883
+ throw err;
884
+ }
885
+ // A file that cannot be trusted after one construct's edits is not
886
+ // written at all, whatever the others produced.
887
+ for (const { context: other } of work) {
888
+ other.skip(`${file}: ${err.message}`);
889
+ }
890
+ text = original;
891
+ lines.length = 0;
892
+ edited.length = 0;
893
+ added.clear();
894
+ break;
895
+ }
896
+ }
897
+ if (text !== original) {
898
+ files.push({ path: filePath, text, original });
899
+ applied.push(...lines);
900
+ constructs.push(...edited);
901
+ if (added.size > 0) {
902
+ imports.push({ file, names: [...added] });
903
+ }
904
+ }
905
+ }
906
+ // Saving a retry strategy in Checkly also moves the legacy `doubleCheck`
907
+ // flag it replaced. Once the strategy itself is written, telling the user
908
+ // to set it by hand would be wrong.
909
+ const wroteRetryStrategy = (skip) => applied.some(line => line.type === skip.type && line.logicalId === skip.logicalId && line.property === 'retryStrategy');
910
+ const reported = skipped.filter(skip => !(skip.property === '/doubleCheck' && wroteRetryStrategy(skip)));
911
+ return { files, applied, constructs, skipped: reported, imports };
912
+ }
913
+ /**
914
+ * Writes every planned file through a temporary file in the same directory,
915
+ * flushed to disk and then renamed over the target, so an interrupted write
916
+ * leaves the old file or the new one rather than a torn one. The target is
917
+ * the file itself, not a symlink to it, and it keeps its mode.
918
+ *
919
+ * @throws Error naming the files already rewritten when a later one fails.
920
+ */
921
+ export async function applyWriteBack(plan) {
922
+ const written = [];
923
+ for (const file of plan.files) {
924
+ let temporary;
925
+ try {
926
+ const target = await fs.realpath(file.path);
927
+ // The plan was made from what the file held then; a file edited since
928
+ // is not overwritten with a rewrite of its older self.
929
+ if (await fs.readFile(target, 'utf8') !== file.original) {
930
+ throw new Error('the file changed since the plan was made');
931
+ }
932
+ const { mode } = await fs.stat(target);
933
+ temporary = `${target}.${crypto.randomBytes(4).toString('hex')}.tmp`;
934
+ const handle = await fs.open(temporary, 'wx');
935
+ let failed = false;
936
+ try {
937
+ // Set after opening, since the mode passed to open is filtered by
938
+ // the umask; a filesystem without modes keeps its own.
939
+ await handle.chmod(mode).catch(() => undefined);
940
+ await handle.writeFile(file.text, 'utf8');
941
+ await handle.sync();
942
+ }
943
+ catch (err) {
944
+ failed = true;
945
+ throw err;
946
+ }
947
+ finally {
948
+ // On the way out with an error, the error is the one to report; on
949
+ // success, a close that fails is the write failing late.
950
+ await handle.close().catch(err => {
951
+ if (!failed) {
952
+ throw err;
953
+ }
954
+ });
955
+ }
956
+ await fs.rename(temporary, target);
957
+ written.push(file.path);
958
+ }
959
+ catch (err) {
960
+ if (temporary !== undefined) {
961
+ await fs.rm(temporary, { force: true }).catch(() => undefined);
962
+ }
963
+ const done = written.length === 0 ? 'No file was changed.' : `Already updated: ${written.join(', ')}.`;
964
+ throw new Error(`Could not write ${file.path}: ${err.message} ${done}`, { cause: err });
965
+ }
966
+ }
967
+ }
968
+ //# sourceMappingURL=plan.js.map