Skip to content

CS-10617: Reimplement boxel workspace push command#4356

Open
FadhlanR wants to merge 2 commits intomainfrom
cs-10617-reimplement-boxel-push-command
Open

CS-10617: Reimplement boxel workspace push command#4356
FadhlanR wants to merge 2 commits intomainfrom
cs-10617-reimplement-boxel-push-command

Conversation

@FadhlanR
Copy link
Copy Markdown
Contributor

@FadhlanR FadhlanR commented Apr 8, 2026

Summary

  • Port boxel realm push command from standalone boxel-cli into monorepo
  • Manifest-based incremental sync via .boxel-sync.json with MD5 hashing
  • Options: --delete (remove remote-only files), --dry-run, --force (upload all)
  • Protected files (.realm.json) are never uploaded or deleted

Test plan

  • pnpm --filter @cardstack/boxel-cli test — 38 tests pass
  • pnpm --filter @cardstack/boxel-cli build — builds successfully (101.1 KB)
  • pnpm --filter @cardstack/boxel-cli lint — no errors

🤖 Generated with Claude Code

@habdelra
Copy link
Copy Markdown
Contributor

now that we have real realm integration don't forget to add tests for this against a running realm

@FadhlanR FadhlanR force-pushed the cs-10616-reimplement-boxel-pull-command branch from a0bf836 to d20a12f Compare April 14, 2026 07:39
@FadhlanR FadhlanR force-pushed the cs-10617-reimplement-boxel-push-command branch from 77e92a1 to 61792e6 Compare April 14, 2026 08:37
@FadhlanR FadhlanR force-pushed the cs-10616-reimplement-boxel-pull-command branch from b407dcf to 6d437cb Compare April 14, 2026 08:51
FadhlanR and others added 2 commits April 14, 2026 16:12
Port push command from standalone boxel-cli into monorepo.
Adds manifest-based incremental sync via .boxel-sync.json with MD5 hashing.
Supports --delete, --dry-run, --force options. Protected files (.realm.json)
are never uploaded or deleted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d integration tests

- Move push command from src/commands/push.ts to src/commands/realm/push.ts
- Use ProfileManager for auth instead of validateMatrixEnvVars
- Register push as realm subcommand (boxel realm push)
- Replace mock-based unit tests with real integration tests using fileSystem seeding
- Test cases: basic push, incremental, force, delete, dry-run, manifest ignore

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@FadhlanR FadhlanR changed the base branch from cs-10616-reimplement-boxel-pull-command to main April 14, 2026 09:13
@FadhlanR FadhlanR force-pushed the cs-10617-reimplement-boxel-push-command branch from 61792e6 to ab6364f Compare April 14, 2026 09:15
@FadhlanR FadhlanR marked this pull request as ready for review April 14, 2026 09:16
@FadhlanR FadhlanR requested a review from a team April 14, 2026 09:16
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab6364f6f8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +110 to +114
const currentHash = computeFileHash(localPath);
const previousHash = manifest.files[relativePath];

if (previousHash !== currentHash) {
filesToUpload.set(relativePath, localPath);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reconcile remote state before skipping unchanged files

The incremental branch only compares currentHash against the local manifest and never checks whether the file still exists (or still matches) on the remote realm, so a file deleted or edited remotely after a prior push is treated as "unchanged" and skipped. In that scenario, rerunning boxel realm push without local edits leaves the realm out of sync until users manually force a full upload (--force or deleting the manifest), which can silently preserve remote drift.

Useful? React with 👍 / 👎.

continue;
}
const currentHash = computeFileHash(localPath);
const previousHash = manifest.files[relativePath];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate manifest schema before reading file hashes

A parseable but malformed .boxel-sync.json (for example, missing files after a partial write or manual edit) will crash here with TypeError because manifest.files is dereferenced without validation. Since this path runs whenever workspaceUrl matches, a bad manifest can block all non---force pushes instead of safely falling back to a full upload.

Useful? React with 👍 / 👎.

Comment on lines +130 to +134
await pushCommand(localDir, realmUrl, { profileManager });

let remoteFiles = await pullRemoteFiles(realmUrl);
expect(remoteFiles.get('a.gts')).toContain('a = 999');
expect(remoteFiles.get('b.gts')).toContain('b = 2');
Copy link
Copy Markdown
Contributor

@habdelra habdelra Apr 14, 2026

Choose a reason for hiding this comment

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

let's not verify this way--lets actually look at the server. beacuse you are verifying in the same local dir you wrote too, this doesn't demonstraibly prove that you actually mutated server state. also it's awkward that the push command relies on teh pull command for testing.

// Push again with force (no changes) — should still succeed
await pushCommand(localDir, realmUrl, { force: true, profileManager });

let remoteFiles = await pullRemoteFiles(realmUrl);
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.

ditto--assert realm server state directly

fs.unlinkSync(path.join(localDir, 'remove.gts'));
await pushCommand(localDir, realmUrl, { delete: true, profileManager });

let remoteFiles = await pullRemoteFiles(realmUrl);
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.

ditto--assert realm server state directly

expect(fs.existsSync(path.join(localDir, '.boxel-sync.json'))).toBe(false);

// Remote should not have the file (only index.json from realm creation)
let remoteFiles = await pullRemoteFiles(realmUrl);
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.

ditto--assert realm server state directly


await pushCommand(localDir, realmUrl, { profileManager });

let remoteFiles = await pullRemoteFiles(realmUrl);
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.

ditto--assert realm server state directly

expect(Object.keys(manifest.files)).toContain('data.json');

// Verify files exist on remote via pull
let remoteFiles = await pullRemoteFiles(realmUrl);
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.

ditto--assert realm server state directly


for (const [relativePath, localPath] of filesToUpload) {
try {
await this.uploadFile(relativePath, localPath);
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.

this is doing the uploads serially. I thin kit might be better to use an atomic write, or something that is able to deal with changes concurrently

force?: boolean;
}

class RealmPusher extends RealmSyncBase {
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.

I'm super confused--i thought we were reimplementing this, but RealmSyncBase is already in the repo? which commit added that? we are we not reimplementing that too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

why is your implementation of this so close to the existing boxel-cli?

@habdelra
Copy link
Copy Markdown
Contributor

habdelra commented Apr 14, 2026

Are we really re-implementing this? i just compared your changes to what is currently in boxel-cli repo and they are the same. re-implementing is not the same as copying. i'm confused what's going on. Let's talk about this in office hours

@habdelra
Copy link
Copy Markdown
Contributor

habdelra commented Apr 14, 2026

Are we really re-implementing this? i just compared your changes to what is currently in boxel-cli repo and they are the same. re-implementing is not the same as copying. i'm confused what's going on. Let's talk about this in office hours

so per our discussion with @lukemelia yes, we are copying this over and making targetted adjustments as we see them. sorry for the confusion!

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.

2 participants