Repository navigation
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #601 +/- ##
======================================
Coverage 38% 38%
======================================
Files 824 825 +1
Lines 41251 41418 +167
Branches 9079 9111 +32
======================================
+ Hits 15816 15944 +128
- Misses 23350 23397 +47
+ Partials 2085 2077 -8
🚀 New features to boost your workflow:
|
tkislan
force-pushed
the
tomaskislan/blu-6519-sync-a-local-folder-with-deepnote-cloud-from-the-extension
branch
from
November 4, 2026 11:23
980d76e to
948b57d
Compare
Base automatically changed from
tomaskislan/blu-6518-store-the-deepnote-api-token-in-env-and-resolve-it-for
to
main
November 5, 2026 14:49
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
force-pushed
the
tomaskislan/blu-6519-sync-a-local-folder-with-deepnote-cloud-from-the-extension
branch
from
November 5, 2026 14:55
948b57d to
544fb1f
Compare
Uri.fsPath lower-cases a Windows drive letter, but the temp directory from os.tmpdir() keeps an upper-case one. Paths the test built from it then differed from the handler's fsPath-derived paths, so six tests would fail on Windows. The temp directory now goes through Uri.file(...).fsPath once, as the API token service test already does.
The API URL was always https://api.<deepnote.domain>, so the extension could not talk to a local or mock Deepnote API. When deepnote.apiUrl is set, getApiEndpoint returns it without trailing slashes; when it is empty (the default) nothing changes. Browser links still use deepnote.domain. The setting has the same resource scope as deepnote.domain. Saved tokens are keyed by the API URL, so a saved token is never sent to a different one.
Adds a cloud E2E group. The suite starts test/e2e/helpers/mockDeepnoteCloud.ts on a free port and sets deepnote.apiUrl in its temp workspace's .vscode/settings.json. It then saves an API token, which the mock checks with GET /v2/me, and syncs three times: - first sync: downloads the cloud project - second sync: pushes a local edit - third sync: keeps the cloud version in a conflict The mock serves the Public API v2 routes that the token check and sync call (me, list projects, export, import) from a directory with one subdirectory per project. Its response types are generated from the published OpenAPI spec (npm run generate:e2e:deepnote-cloud-api). It also runs on its own for manual testing (npm run mock:deepnote-cloud). fflate is a new devDependency; the mock uses it to build and read project zip archives. clickDialogOkButton now takes the button label, because the sync folder dialog sets openLabel to "Sync Here".
getToken and getOrPromptToken took a sync-only syncRoot argument, so every future Deepnote Cloud caller would have had to pick where the .env lookup stops. The argument existed because the lookup fell back to the workspace folder when no sync manifest was found. A folder's first sync could then read a .env above the folder, and later syncs could not. The lookup now runs from the resource's folder up to the nearest folder holding .deepnote-sync.json, and reads only that first folder when there is none. A folder reads the same .env before and after its first sync, as deepnote/deepnote#543 proposes for every CLI command. A .env at the workspace root no longer applies to files in unsynced subfolders.
The mock wrote any import archive whose entries were .deepnote files that parse. Deepnote Cloud answers 422 when a document belongs to another project, has other than one notebook, or disagrees with the others on the project name or on the integration ids it declares. The mock now does the same, before any write. A body that is not a ZIP gets 422 instead of the 500 from the error middleware. The new mockDeepnoteCloud.e2e.test.ts in the cloud group posts archives to the import route over HTTP and checks the status, the message and that rejected imports leave the project's files unchanged.
This was referenced Nov 7, 2026
…sync-a-local-folder-with-deepnote-cloud-from-the-extension
… folder The sync command showed a pick of the synced folders plus "Choose another folder…" on every run, even when only one folder was synced. It now works like VS Code's Git commands, which use the only repository directly and keep "Initialize Repository" separate: - "Sync with Deepnote Cloud" syncs folders that are already synced. With none, it offers "Sync a New Folder…". With one, it syncs it without asking. With several, it asks, listing the folder of the active editor first. - "Sync a New Folder with Deepnote Cloud…" opens the folder dialog and runs the existing checks and first-sync confirmation. It is in the Command Palette and the Deepnote explorer's "…" menu. Both commands share the one-sync-at-a-time guard. The offer is not awaited, so the guard is clear by the time its button runs the other command. The cloud E2E suite now syncs a first folder through the offer, syncs it again without a pick, adds a second folder with the new command, and checks the pick order with a file from the second folder open.
…sync-a-local-folder-with-deepnote-cloud-from-the-extension
Each command method now calls its folder chooser directly and passes only the chosen folder to syncFolder, instead of forwarding a chooser callback through three methods. The one-sync-at-a-time guard moves into runOneSyncAtATime, which still wraps the folder choice, so no dialog or pick opens while a sync runs. The commands are registered with registerCommand's thisArg. The unit test stub now binds the captured handler to it as VS Code does, and a new test covers a failed search for synced folders, whose error handling is now separate from the new-folder chooser's.
Every sync used to run in one `before` hook, with each test only asserting on values recorded there. Now `before` only starts the mock and opens the workspace. Each test creates its own cloud projects and local folders, runs the commands, and asserts. `afterEach` empties both sides, and after a failed attempt it first closes the prompts the failure left open, so the sync behind them cannot block later tests. Each local folder gets the token from its own `.env`, so only the Set API Token test relies on a saved token, and that test clears it. New coverage: - a project with several notebook files, pulled, pushed with an edited and a new notebook, and mirrored after cloud edits, adds and deletes; - a conflict in one of several projects resolved with Skip; - local notebooks outside every project and projects deleted in Deepnote, reported and kept; - a project's `.files` directory, which sync neither uploads nor removes on a pull because the extension does not pass `allFiles`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
@coderabbitai ignore
Summary
Adds two commands that sync local folders with Deepnote Cloud: "Deepnote: Sync with Deepnote Cloud", run from the Command Palette or the sync button in the Deepnote explorer title bar, and "Deepnote: Sync a New Folder with Deepnote Cloud…". Both call
syncWorkspacefrom@deepnote/cloud-synconce: 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..deepnote-sync.json. With none, it offers "Sync a New Folder…". With one, it syncs it without asking. With several, it asks, listing first the folder that contains the file in the active editor.exclude: nulltoworkspace.findFiles, because any other value keepsfiles.excludeapplied and can hide a nested manifest..deepnotefiles with unsaved changes in a text editor, get a "Save and Sync" modal. Nothing syncs if one stays unsaved.cloud-changed-after-local-edit,changed-on-both-sidesanduntracked-local-directoryeach get a modal with one override button and "Skip". Dismissing the modal skips..filesdirectory alone, asdeepnote syncdoes without--all-files. The issue keepsallFilesout of scope.IDeepnoteApiTokenServicenow readsDEEPNOTE_TOKENfrom the.envfiles between the resource's folder and the nearest folder holding.deepnote-sync.json. With no such folder, it reads only the resource's own folder..envabove the folder that later syncs ignored..env, asdeepnote syncdoes. This is the rule Get deepnote sync and publish ready for the editor and agents deepnote#543 proposes for every CLI command..envat the workspace root no longer applies to files in subfolders that are not synced.deepnote.apiUrlsetting: the base URL of the Deepnote API, with the sameresourcescope asdeepnote.domain.https://api.<deepnote.domain>.deepnote.domain.cloudgroup runs against a mock Deepnote Cloud intest/e2e/helpers/mockDeepnoteCloud.ts.npm run generate:e2e:deepnote-cloud-api), andnpm run mock:deepnote-cloudruns it for manual testing.mockDeepnoteCloud.e2e.test.tschecks this over HTTP (9 tests).syncWithDeepnoteCloud.e2e.test.ts(10 tests) opens one workspace. Each test creates its own cloud projects and local folders, runs the commands through the UI, and checks the summary and the files on both sides. Each folder's.envholds the token, except in the test that saves one with Set API Token and syncs with it. The tests cover:.filesdirectory, neither uploaded nor removed by a pull.fflateis a new devDependency, used for the project ZIPs.Closes #597
Builds on #563 (BLU-6518, the token service).
Deviations from the issue
syncRootargument. The issue asked for an optionalsyncRootargument ongetTokenandgetOrPromptToken. The token service is shared with future Deepnote Cloud features, so the lookup rule changed instead (see "Token lookup").deepnote.apiUrlsetting and thecloudE2E group with its mock./,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.Verification
npm run typecheck,npm run lint(no findings in the changed files),npm run formatandnpm run spell-checkall pass.npm test: 2985 passing, 0 failing.cloudE2E group passed locally, 19 tests, on an extension packaged from the branch's current source.exclude: undefined, swapped override labels, awaiting the summary, awaiting the "Sync a New Folder…" offer, a separate guard for the new command, and not stripping the notebook URI's query before matching the active editor..filespull test.['alpha', 'beta']instead of['beta', 'alpha']). The rewritten test checks the same order.Not verified
syncWorkspace, and the E2E suite runs it against the mock. The issue's "Manual follow-up" is not done: the round trip against real Deepnote, the Cursor check and the public docs PR./only, and the new unit tests compare raw paths, so they may fail on Windows. CI runs on Ubuntu.vscode-vfs) workspace, the first-sync confirmation appears before the token service refuses the non-local folder.