Skip to content

feat(cloud): sync a local folder with Deepnote Cloud - #601

Draft
tkislan wants to merge 2 commits into
tomaskislan/blu-6518-store-the-deepnote-api-token-in-env-and-resolve-it-forfrom
tomaskislan/blu-6519-sync-a-local-folder-with-deepnote-cloud-from-the-extension
Draft

tkislan wants to merge 2 commits into
tomaskislan/blu-6518-store-the-deepnote-api-token-in-env-and-resolve-it-forfrom
tomaskislan/blu-6519-sync-a-local-folder-with-deepnote-cloud-from-the-extension

Conversation

@tkislan

@tkislan tkislan commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai ignore

Summary

Adds "Deepnote: Sync with Deepnote Cloud", run from the Command Palette or the sync button in the Deepnote explorer title bar. It calls syncWorkspace from @deepnote/cloud-sync once: projects changed in Deepnote are pulled, projects changed locally are pushed, and each conflict gets an Override or Skip prompt. A summary of the counts follows.

  • Choosing the folder: the command lists the workspace's synced folders (those holding .deepnote-sync.json) and then "Choose another folder…". With no synced folders, it opens a folder dialog. A folder chosen inside a sync root syncs that root.
  • Refused folders: a folder outside the open workspace folders is refused, and so is one that contains or is inside another synced folder. These checks run before any token is requested. The nested-folder search passes exclude: null to workspace.findFiles, because any other value keeps files.exclude applied and can hide a nested manifest.
  • Before syncing: a first sync asks before downloading every project. Unsaved Deepnote notebooks get a "Save and Sync" modal, and nothing syncs if one stays unsaved.
  • Conflicts: cloud-changed-after-local-edit, changed-on-both-sides and untracked-local-directory each get a modal with one override button and "Skip". Dismissing the modal skips.
  • Summary: pulled, pushed, unchanged, skipped and failed counts, plus projects kept locally after they left Deepnote and local files that were not uploaded. It has a "Show Log" button and is a warning when any project failed. Every project outcome is logged.
  • One sync at a time: a second run while a sync is in progress shows a message and does nothing else. The summary is not awaited, so leaving it on screen does not block the next sync.
  • Token lookup: IDeepnoteApiTokenService.getToken and getOrPromptToken take an optional syncRoot, which stops the .env lookup there. The command passes the folder being synced, so every sync of a folder, the first included, reads DEEPNOTE_TOKEN only from that folder's .env, as deepnote sync does. Calls without the argument behave as before.
  • Availability: the command is disabled and the button hidden in untrusted workspaces and in the web extension host, the same as Set and Clear API Token.

Closes #597

Stacked on #563 (BLU-6518, the token service), so it targets that branch. Merge #563 first.

Deviations from the issue

  • When the workspace folder is a filesystem or drive root (/, D:\), the overlap check skips the parent lookup. Without the guard, such a folder's own manifest would make every sync after the first fail as "overlaps itself". The issue doesn't mention this case.
  • The unit tests cover more than the issue's list: a dismissed folder dialog, a failed sync-root lookup for a folder chosen in the dialog, a token lookup that throws (the in-progress flag still clears), the quick-pick and dialog options, and logging of outcomes without a detail and of warnings.

Verification

  • npm run typecheck, npm run lint (no findings in the changed files), npm run format and npm run spell-check all pass.
  • npm test: 2991 passing, 0 failing. The main-based baseline was 2958.
  • The new tests were checked against 20 single-line bugs applied to the compiled code one at a time, and each bug failed at least one test. Examples: dropping syncRoot from the token lookup, exclude: undefined, swapped override labels, awaiting the summary, and a flag not cleared after a throw.

Not verified

  • No real Deepnote workspace was used: syncWorkspace is replaced by a stub in the tests. The round trip, the Cursor check and the public docs PR in the issue's "Manual follow-up" are not done.
  • Windows was not run. The drive-root guard was confirmed for / only, and the new unit tests compare raw paths, so they may fail on Windows. CI runs on Ubuntu.
  • Known and not addressed here, all from review:
    • After a rejected token on a first sync, "Set API Token" can warn that a .env above the folder overrides the saved token, although sync ignores that .env.
    • In a trusted virtual (vscode-vfs) workspace, the first-sync confirmation appears before the token service refuses the non-local folder.

@linear-code

linear-code Bot commented Nov 4, 2026

Copy link
Copy Markdown

BLU-6519

@codecov

codecov Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.20253% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 38%. Comparing base (8a3a464) to head (948b57d).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
.../cloud/syncWithDeepnoteCloudCommandHandler.node.ts 95% 2 Missing and 3 partials ⚠️
src/notebooks/serviceRegistry.node.ts 0% 1 Missing ⚠️
Additional details and impacted files
@@                                             Coverage Diff                                              @@
##           tomaskislan/blu-6518-store-the-deepnote-api-token-in-env-and-resolve-it-for    #601    +/-   ##
============================================================================================================
  Coverage                                                                           38%     38%            
============================================================================================================
  Files                                                                              825     826     +1     
  Lines                                                                            41273   41425   +152     
  Branches                                                                          9087    9114    +27     
============================================================================================================
+ Hits                                                                             15837   15984   +147     
- Misses                                                                           23348   23350     +2     
- Partials                                                                          2088    2091     +3     
Files with missing lines Coverage Δ
...oks/deepnote/cloud/deepnoteApiTokenService.node.ts 93% <100%> (ø)
src/notebooks/deepnote/cloud/types.ts 100% <ø> (+100%) ⬆️
src/platform/common/constants.ts 100% <100%> (ø)
src/platform/common/utils/localize.ts 93% <100%> (+<1%) ⬆️
src/notebooks/serviceRegistry.node.ts 0% <0%> (ø)
.../cloud/syncWithDeepnoteCloudCommandHandler.node.ts 95% <95%> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Adds "Deepnote: Sync with Deepnote Cloud", run from the Command Palette
or the Deepnote explorer title bar. It calls `syncWorkspace` from
`@deepnote/cloud-sync` once to pull projects changed in Deepnote, push
projects changed locally, and ask Override or Skip for each conflict,
then reports the counts.

- The command offers the workspace's synced folders, or a folder from a
  dialog, which syncs the nearest sync root at or above it.
- It refuses a folder outside the open workspace folders, or one that
  contains or is inside another synced folder. The nested check passes
  `exclude: null` to `findFiles`, because any other value keeps
  `files.exclude` applied and can hide a nested manifest.
- A first sync asks before downloading every project, and unsaved
  Deepnote notebooks must be saved first.
- One sync runs at a time. The summary is not awaited, so leaving it on
  screen does not block the next sync.
- `getToken` and `getOrPromptToken` take an optional `syncRoot`, which
  stops the `.env` lookup there. Every sync of a folder, the first
  included, then reads `DEEPNOTE_TOKEN` only from that folder's `.env`,
  as `deepnote sync` does, instead of from a `.env` above it until the
  manifest exists.

Closes #597
The "Save and Sync" check only looked at notebook documents. A
.deepnote file reopened with Reopen Editor With > Text Editor is a text
document, so its unsaved edits were skipped. A pull then wrote the
cloud version under the dirty buffer, and choosing Overwrite in the
"content of the file is newer" dialog made the next sync push the
pre-pull text back, undoing the cloud change.

Dirty text documents with the file scheme and a .deepnote path now go
through the same save-and-recheck flow. The scheme check leaves out
notebook cells, which carry the notebook's .deepnote path under their
own scheme.
@tkislan
tkislan force-pushed the tomaskislan/blu-6519-sync-a-local-folder-with-deepnote-cloud-from-the-extension branch from 980d76e to 948b57d Compare November 4, 2026 11:23
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