feat(cloud-sync): move the workspace sync engine into @deepnote/cloud-sync by tkislan · Pull Request #568 · deepnote/deepnote · GitHub
Skip to content

feat(cloud-sync): move the workspace sync engine into @deepnote/cloud-sync - #568

Merged
tkislan merged 2 commits into
mainfrom
feat/cloud-sync-workspace-engine
Oct 8, 2026
Merged

tkislan merged 2 commits into
mainfrom
feat/cloud-sync-workspace-engine

Conversation

@tkislan

@tkislan tkislan commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Closes #563.

deepnote sync could only be used as a CLI command: its engine resolved the root from process.cwd(), loaded .env into process.env, prompted through @inquirer/prompts and printed through the CLI output module. This moves the engine into @deepnote/cloud-sync as syncWorkspace(options), so other consumers (the local MCP server in #544, then the editor extension) can call it. deepnote sync becomes a thin adapter over it.

CLI behavior is unchanged: no change to flags, output, -o json or exit codes. cli.ts is untouched.

Changes

  • packages/cloud-sync/src/sync-workspace.ts (new): the engine, ported from commands/sync.ts, behind an explicit contract: WorkspaceSyncOptions (rootDir, baseUrl, token, flags, concurrency, onConflict, onEvent), WorkspaceSyncResult, SyncConflict (five kinds), SyncConflictPolicy ('skip' | 'override' | async function) and SyncEvent (listing-projects, project-outcome, warning, file-transferred). It has no process. reference and no import from commander, chalk, ora, @inquirer/prompts, dotenv or @deepnote/cli.
  • Conflict policy: a string policy answers immediately. A function policy is called one conflict at a time, never in a dry run, and events are held while a call is pending and flushed in order. If it rejects, the run is cancelled at the existing throwIfCancelled checkpoints, a final manifest save is attempted (ignored if it fails), and syncWorkspace rejects with that same error once every worker has settled. The CLI's isPromptExit check is gone.
  • concurrency that is not a positive integer rejects with RangeError before rootDir is created or the API is called. The CLI keeps parseSyncConcurrency and its message.
  • packages/cli/src/commands/sync.ts: now the adapter. runSync(dir, options) resolves the root, creates it unless dry run, loads .env, resolves the token, picks the policy (ask + TTY → a select prompt via describeConflict; ask without a TTY or under -o → 'skip' plus the existing debug line) and maps events to log/warn/debug. DEFAULT_SYNC_CONCURRENCY is re-exported so cli.ts needs no change.
  • Tests: sync.test.ts moves to packages/cloud-sync/src/sync-workspace.test.ts with git mv and is converted to the library API (names and expected values unchanged; select/TTY mocks and console assertions replaced by an onConflict vi.fn and collected SyncEvents). New engine tests cover the cases listed in Move the workspace sync engine into @deepnote/cloud-sync #563. packages/cli/src/commands/sync.test.ts is a new adapter suite that mocks only syncWorkspace.
  • Packages: @deepnote/blocks and @deepnote/database-integrations added to @deepnote/cloud-sync dependencies and external in tsdown.config.ts; fflate moved from the CLI's to cloud-sync's devDependencies. The lockfile diff contains only those changes.
  • Docs: packages/cloud-sync/README.md documents syncWorkspace (options, conflicts, events, cancellation). The pointer to canonicalProjectHash in packages/cloud/docs/project-import-contract.md now names its new location.

Things to check

  • toDecision is not in Move the workspace sync engine into @deepnote/cloud-sync #563. The library treats any policy answer other than the exact string 'override' as 'skip'. Without it, an untyped caller returning undefined or true would act as override at the destructive pushProject sites. Typed callers see no difference. It is documented on SyncConflictPolicy and tested.
  • Held events are cleared before they are delivered in resolveConflict (the original delivered, then cleared). onEvent is now consumer code; a throwing listener would otherwise leave the hold stuck.
  • Only 3 of the 9 index.ts exports in Move the workspace sync engine into @deepnote/cloud-sync #563's removal list are removed (planProjectPaths, pathsOverlap, PlannedProjectPaths). The other six still have importers outside the package (packages/cli/src/utils/publish-mirror.ts, publish-streamlit-app.ts), so Move the workspace sync engine into @deepnote/cloud-sync #563's own condition ("when grep finds no importer") does not hold for them.
  • Engine test count 72 → 102. One test whose subject was TTY detection ("skips a both-sides conflict by default when no terminal can be asked…") is replaced by an equivalent that checks the library default (skip); the rest keep their names and expected values. The new CLI adapter suite has 41 tests.
  • Known limitation, moved unchanged and left as is (Move the workspace sync engine into @deepnote/cloud-sync #563 says to keep every checkpoint): a cancellation that lands after importProject was sent skips the follow-up writeProjectNotebooks (checkpoint at sync-workspace.ts:427), so the manifest baseline is not advanced and the next sync can report a false conflict. Worth a follow-up now that a library caller can trigger it.
  • The engine's Refusing to prune… error still mentions --url, kept byte-identical so CLI output does not change. The behavior when onEvent itself throws is unspecified.

Verification

Run on this branch:

  • pnpm typecheck, pnpm build, pnpm prettier:check and pnpm spell-check exit 0. pnpm biome:check exits 0; its 8 noConsole warnings are in examples/local-runner/*/serve.mjs, which this change does not touch.
  • pnpm vitest run --bail 0: 186 files, 3447 tests, all passed, run with a python symlink on PATH. Without it, 3 tests in packages/cli/src/commands/run.test.ts fail (they expect pythonEnv: 'python', and this host only has python3); they pass with the symlink and fail the same way on main per feat(cloud-sync): add @deepnote/cloud-sync with sync manifest and path modules #554.
  • Built CLI: deepnote sync with no token exits 2 with Missing authentication token. and empty stdout; --concurrency 0 is still rejected by the CLI parser.
  • Not run: the real Deepnote Cloud API (tests mock fetch) and interactive TTY prompting by hand (covered by the mocked adapter tests).

The change was reviewed with the code-review-typescript skill over three rounds; the findings that held up (missing tests for the policy contract, projectDir vs projectName, flush order, final-save failure, and an exhaustiveness check on the event switch) are fixed in this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NZwreo9KDxDk4VuSXgzmC2

Summary by CodeRabbit

  • New Features
    • The cloud-sync package now provides workspace synchronization for pulling and pushing projects, notebooks, and working files.
    • Sync supports conflict policies, progress events, cancellation, and optional pruning of stale local files and projects.
  • Documentation
    • Added reference information for sync options, results, conflict handling, progress events, and cancellation.

…-sync

`@deepnote/cloud-sync` now exports `syncWorkspace(options)`. The caller passes
the root, token and API URL explicitly, supplies a conflict policy ('skip',
'override' or an async function) and receives progress through `onEvent`.
The library never reads `process`, `.env` or a TTY.

`deepnote sync` is a thin adapter over it (`runSync`): it resolves the root,
loads `.env`, resolves the token, turns `--on-conflict` into a policy (asking
through @inquirer/prompts on a terminal) and renders the events and result.
Flags, output, `-o json` and exit codes are unchanged.

The engine tests move to cloud-sync and are converted to the library API; the
CLI gets new adapter tests. A rejecting conflict policy cancels the run at the
existing checkpoints and `syncWorkspace` rejects with that same error.

Closes #563

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZwreo9KDxDk4VuSXgzmC2
@tkislan
tkislan requested a review from a team as a code owner October 8, 2026 11:57
@codecov

codecov Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

@dinohamzic dinohamzic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The move makes sense, nothing blocking code wise, all READMEs also have no outdated references or content as a consequence of the move.

@tkislan

tkislan commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 2ddfa202-0580-49bc-82ca-282734c2ee15
📥 Commits

Reviewing files that changed from the base of the PR and between aa891df and b6771cb.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • packages/cli/package.json
  • packages/cli/src/commands/sync.test.ts
  • packages/cli/src/commands/sync.ts
  • packages/cloud-sync/README.md
  • packages/cloud-sync/package.json
  • packages/cloud-sync/src/index.ts
  • packages/cloud-sync/src/sync-workspace.test.ts
  • packages/cloud-sync/src/sync-workspace.ts
  • packages/cloud-sync/tsdown.config.ts
  • packages/cloud/docs/project-import-contract.md
💤 Files with no reviewable changes (1)
  • packages/cli/package.json

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The workspace synchronization engine moves into @deepnote/cloud-sync, which now exposes syncWorkspace and its options, results, conflict types, and events. The engine handles project and working-file synchronization, conflict policies, cancellation, concurrency, and pruning. The CLI delegates sync execution to the library and retains argument handling, interactive conflict prompts, event presentation, and human or JSON output.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant deepnoteSync
  participant runSync
  participant syncWorkspace
  participant conflictPolicy
  participant eventHandler
  deepnoteSync->>runSync: pass sync arguments
  runSync->>syncWorkspace: pass options and callbacks
  syncWorkspace->>conflictPolicy: request conflict decision
  conflictPolicy-->>syncWorkspace: return skip or override
  syncWorkspace->>eventHandler: emit progress event
Loading

Suggested reviewers: dinohamzic

Merge Risk: ⚪ Minimal · up to b6771

The sync engine moves into @deepnote/cloud-sync, and the CLI now acts as a thin adapter around it. No concrete defects were found. One item to confirm before merging: that no external code still imports the removed path-planning helpers.

🚥 Pre-merge checks | ✅ 4 | ❌ 1 | ❓ 1

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 30 functions across 4 files. (3 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Updates Docs ❓ Inconclusive The OSS repository documentation is updated: packages/cloud-sync/README.md documents syncWorkspace, its options, conflicts, events, and cancellation, and `packages/cloud/docs/project-import-contra… Verify or update the feature entry in the deepnote-internal landing-page roadmap.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: moving the workspace sync engine into @deepnote/cloud-sync.
Linked Issues check ✅ Passed The PR addresses the coding requirements in #563. It adds syncWorkspace and the exported result, conflict, event, policy, and default-concurrency API in @deepnote/cloud-sync. The engine receives `…
Out of Scope Changes check ✅ Passed The reported changes stay within #563. The new engine, CLI adapter, tests, package configuration, README, and the canonicalProjectHash documentation pointer directly support the move and its public …
Full details: Docstring Coverage

Explanation

Docstring coverage is 76.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 30 functions across 4 files. (3 skipped: 3 unsupported.)

Full details: Updates Docs

Explanation

The OSS repository documentation is updated: packages/cloud-sync/README.md documents syncWorkspace, its options, conflicts, events, and cancellation, and packages/cloud/docs/project-import-contract.md updates the implementation reference. This checkout is deepnote/deepnote and does not contain the separate deepnote-internal landing-page roadmap, so that documentation could not be verified.

  • Fix all pre-merge checks with AI
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Some tools did not complete. Review the errors below.

🔧 LanguageTool

LanguageTool checks were skipped: reviews.tools.languagetool.enabled_only requires at least one selection in enabled_rules or enabled_categories. Select rules/categories, set enabled_only to false to use the default rules, or set enabled to false to disable LanguageTool.

🔧 Biome (2.5.14)
packages/cli/src/commands/sync.ts

Biome could not lint this file: configuration resulted in errors. Check the repository's Biome configuration and plugins.

packages/cloud-sync/package.json

Biome could not lint this file: configuration resulted in errors. Check the repository's Biome configuration and plugins.

packages/cloud-sync/src/index.ts

Biome could not lint this file: configuration resulted in errors. Check the repository's Biome configuration and plugins.

  • 2 others

Comment @coderabbitai help to get the list of available commands.

@tkislan
tkislan merged commit e9eb93a into main Oct 8, 2026
22 checks passed
@tkislan
tkislan deleted the feat/cloud-sync-workspace-engine branch October 8, 2026 19:56
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.

Move the workspace sync engine into @deepnote/cloud-sync

2 participants