Skip to content

feat(deploy): offer to write changes made in Checkly back into the code [RED-985] - #1494

Open
sorccu wants to merge 4 commits into
simo/red-984-deploy-confirm-previewfrom
simo/red-985-deploy-write-back
Open

sorccu wants to merge 4 commits into
simo/red-984-deploy-confirm-previewfrom
simo/red-985-deploy-write-back

Conversation

@sorccu

@sorccu sorccu commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Summary

When an interactive checkly deploy shows a plan in which a resource was edited outside the project (a change with origin: 'remote' or 'both'), the prompt now offers a third choice next to applying and cancelling:

? Apply these changes? ›
    Yes, apply these changes
    Update my code with the changes made in Checkly (deploys nothing)
  ❯ Cancel

Choosing it writes the account's current values into the construct source files, prints each property with its old and new value, lists everything it could not update with the reason, and ends the run without deploying so the user can review the diff and deploy again.

Only what can be written without guessing is written:

  • Checks and check groups, for a fixed per-class table of properties whose spelling is a literal on both sides (name, description, activated, muted, shouldFail, tags, locations, whole-minute frequency, response-time and packet-loss thresholds, an API check's or URL monitor's request.* except assertions, a heartbeat monitor's flat period/grace written together with their units, environmentVariables, a group's concurrency and apiCheckDefaults.*). The table is keyed by exact class; a spec asserts every exported check and group class is listed, so a new construct cannot fall back to a base class's rules.
  • The construct must be declared as new <Class>('<logicalId>', { … }) where <Class> is bound by an import or top-level require from a checkly package (aliases such as CheckGroup are resolved through the names checkly/constructs exports for the class), with the options as an object literal without spreads.
  • An existing value is replaced only when it is itself a plain literal; a missing key is inserted after the last member in the file's own quote style, indentation unit, trailing-comma habit and line ending. Commas and line ends are located through the parser's tokens and comments, so a comma inside a comment cannot mislead a splice. Every other byte of the file is untouched. The edited text is re-parsed and each written value read back before anything is written; files are replaced through a temp file and rename.
  • JavaScript files are parsed with acorn; TypeScript and JSX with typescript-estree, which needs the project's typescript as TypeScript check files already do.

Everything else is skipped with a reason: helper values (Frequency.EVERY_5M, RetryStrategyBuilder, AssertionBuilder), references to other resources, secrets and values the redaction table blanks (decided by difference between the raw and blanked before, so a blanked credential is never written), $hash/$masked values, scripts and code bundles, CLI-spelled paths (shape-changes.ts), sub-minute frequencies, a list the code also changed since the last deploy (a whole-list rewrite from before would erase the local addition), options built from variables or spreads, constructs created dynamically, and a change whose own reported value disagrees with before. A file that changed between planning and writing is refused.

--force, --dry-run and the agent/CI confirmation_required envelope are unchanged; the choice exists only in a terminal and has no flag.

Changes

  • services/write-back/source-file.ts: parse a construct file (both parsers, tokens and comments included) and find the construct call.
  • services/write-back/literal-edit.ts: resolve a property path inside the options literal, render values in the file's style, splice by range.
  • services/write-back/plan.ts: map remote changes to edits per construct class, apply the skip rules, verify by re-parsing, write atomically.
  • helpers/command-preview.ts, commands/authCommand.ts: CommandTerminalPreview.alternatives; the interactive prompt becomes a select when alternatives exist (cursor on Cancel, so Enter alone applies nothing, as before). interactiveConfirm keeps precedence.
  • commands/deploy.ts: offers the alternative when the plan has remote changes; prints the applied and skipped lines.
  • ai-context/references/configure.md: one bullet describing the choice.

Accepted limitations

  • Helper-expression values are a follow-up (RED-986).
  • Only checks and check groups; other resource types spell their properties differently in code.
  • A value Checkly reports as unset is never removed from the code (skipped with a reason).
  • Comments inside a replaced array or object literal are lost.
  • A property inherited from checkly.config.ts defaults is written into the construct, not the config.
  • No non-interactive trigger.

Testing

  • Unit: services/write-back/__tests__/literal-edit.spec.ts (parsing, locating, resolving, rendering, splicing incl. comment cases and read-back), plan.spec.ts (table, skip rules, redactions, cross-check, aliases, atomic write), confirm-or-abort.spec.ts and confirm-flow-deploy.spec.ts (prompt shape, the choice rewriting a real file and deploying nothing).
  • No e2e: the prompt needs a TTY.

Stacked on #1493 (RED-984). Docs: checkly/docs#516, to merge with the CLI release.

🤖 Generated with Claude Code

sorccu and others added 3 commits September 22, 2026 01:05
A source rewriter for the deploy write-back: `source-file.ts` parses a
construct's file (acorn for JavaScript, typescript-estree for TypeScript
and JSX) with its tokens and comments, and finds the one
`new <Class>('<logicalId>', { … })` whose class is bound by an import or
require from a checkly package. `literal-edit.ts` resolves a property path
inside that options literal, accepts only plain literals, renders JSON
data in the file's own quote style, indentation, trailing-comma habit and
line ending, and splices it in by byte range, so nothing else in the file
changes. Commas are located through the token list, so a comma inside a
comment cannot mislead a splice. Everything it cannot write without
guessing is refused with a reason.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e [RED-985]

`planWriteBack` turns the changes a deploy plan attributes to the Checkly
account (`origin: 'remote'` or `'both'`) into literal edits of the
construct files, and `applyWriteBack` writes them through a temporary
file renamed into place. A per-class table names the properties whose
import-format spelling and construct spelling are both literals; values
come from the entry's full-detail `before`. Refused with a reason:
references, secrets and anything the redaction table blanks, hashed or
masked values, CLI-spelled paths, sub-minute frequencies, lists the code
also changed since the last deploy, a change whose own report disagrees
with `before`, and a construct that cannot be found or edited. Every
edit is re-parsed and read back before a file is accepted, and a file
that changed since the plan was made is not overwritten.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ng to apply [RED-985]

When the plan shows a resource edited in Checkly and the code can take
the edit, the interactive prompt becomes a list: apply the changes,
update the code with the values from Checkly and deploy nothing, or
cancel. The cursor starts on Cancel, so Enter alone applies nothing, as
the yes/no question's default did. Choosing the update lists what could
not be written and why, rewrites the files, reports each property with
its old and new value once the files hold it, and ends the run so the
user can review the diff and deploy again. A failed write is reported
as an error naming the files already rewritten. `--force`, `--dry-run`,
read-only commands and the agent/CI envelope are unchanged; a command
that supplies its own confirmation keeps it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The mode assertion compares against what the platform stored after chmod
rather than a literal, since Windows keeps no group bits; the partial
failure message is matched as text, since a Windows path's backslashes
would be read as escapes by a RegExp.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant