Skip to content

Migrate ts-rs to but-ts (schemars) for SDK type generation#13117

Merged
mtsgrd merged 1 commit intomasterfrom
mg-branch-13
Apr 13, 2026
Merged

Migrate ts-rs to but-ts (schemars) for SDK type generation#13117
mtsgrd merged 1 commit intomasterfrom
mg-branch-13

Conversation

@mtsgrd
Copy link
Copy Markdown
Contributor

@mtsgrd mtsgrd commented Mar 31, 2026

Replace ts-rs (export-ts) with schemars-based but-ts for all SDK types,
and introduce where missing, removing hand-written TypeScript duplicates
and wiring everything through @gitbutler/but-sdk.

Types migrated to export-schema / register_sdk_type!:

  • gitbutler-branch-actions: BranchListingFilter, BranchListing, Author,
    StackReference, BranchListingDetails, StackOrder, SeriesOrder, and all
    upstream integration types (NameAndStatus, StackStatus, Resolution,
    etc.)
  • gitbutler-edit-mode: ConflictEntryPresence (new export-schema feature)
  • but-api/legacy/modes: HeadAndMode, HeadSha
  • but-workspace: StackDetails (added missing register_sdk_type! call)
  • but-github/but-gitlab: AuthStatusResponseSensitive (remove
    skip_serializing_if so nullable fields are always present in JSON)
  • but-core/ui: WorktreeChanges (add rename_all = "camelCase" so
    ignored_changes serializes as ignoredChanges)

Infrastructure:

  • Add export-schema feature to gitbutler-branch-actions and
    gitbutler-edit-mode with conditional schemars guards
  • Propagate gitbutler-branch/export-schema through but-api export-schema
  • Add --source-crates filter to but-ts to avoid false name-collision
    errors
  • Fix but-ts to emit T | null (not T | null | undefined) for nullable
    fields; remove skip_serializing_if from SDK-registered types instead
  • Remove duplicate author.rs from gitbutler-branch-actions
  • Add browser export condition to @gitbutler/but-sdk to fix Playwright
    e2e test failure (node:module externalized error in Vite dev server)
  • Fix broken TypeScript imports across ~40 desktop app files to import
    directly from declaration sites (no re-exports)

Copilot AI review requested due to automatic review settings March 31, 2026 13:27
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 31, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gitbutler-web Ready Ready Preview, Comment Apr 13, 2026 11:30pm

Request Review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR switches several backend-facing TypeScript types from hand-written interfaces to generated definitions produced from Rust structs/enums via ts-rs, and updates desktop/web consumers to import the generated types from @gitbutler/core/api.

Changes:

  • Added ts-rs export support (export-ts feature + derives/field overrides) to multiple Rust crates and core ID types.
  • Added generated TS type files under packages/core/src/generated/ and re-exported them via the generated index barrel.
  • Updated desktop frontend code to consume generated types (and removed redundant hand-written TS type files).

Reviewed changes

Copilot reviewed 30 out of 57 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
scripts/generate-ts-definitions-from-rust.sh Minor formatting change in the TS generation script.
packages/ui/src/lib/components/select/Select.svelte Adjusts generic typing/defaults for Select item typing.
packages/core/src/generated/Author.ts Generated TS type for backend author model.
packages/core/src/generated/BaseBranch.ts Generated TS type for base-branch payload.
packages/core/src/generated/BaseBranchResolution.ts Generated TS type for upstream base-branch resolution request.
packages/core/src/generated/BaseBranchResolutionApproach.ts Generated TS discriminated-union for base-branch resolution approach.
packages/core/src/generated/BranchAuthor.ts Generated TS type for branch listing “author/signature” (renamed).
packages/core/src/generated/BranchListing.ts Generated TS type for branch listings.
packages/core/src/generated/BranchListingDetails.ts Generated TS type for detailed branch listing payload.
packages/core/src/generated/BranchStatus.ts Generated TS discriminated-union for upstream branch status.
packages/core/src/generated/ConflictEntryPresence.ts Generated TS type for conflict entry presence flags.
packages/core/src/generated/EditModeMetadata.ts Generated TS type for edit-mode metadata payload.
packages/core/src/generated/HeadAndMode.ts Generated TS type for head + operating mode payload.
packages/core/src/generated/HeadSha.ts Generated TS type for head SHA payload.
packages/core/src/generated/IntegrationOutcome.ts Generated TS type for integrate-upstream outcome payload.
packages/core/src/generated/NameAndStatus.ts Generated TS helper type for name + status pairs.
packages/core/src/generated/OperatingMode.ts Generated TS discriminated-union for operating mode.
packages/core/src/generated/OutsideWorkspaceMetadata.ts Generated TS type for outside-workspace metadata.
packages/core/src/generated/RemoteCommit.ts Generated TS type for remote commit payload.
packages/core/src/generated/Resolution.ts Generated TS type for per-stack upstream resolution.
packages/core/src/generated/ResolutionApproach.ts Generated TS discriminated-union for per-stack resolution approach.
packages/core/src/generated/SeriesOrder.ts Generated TS type for series commit-order payload.
packages/core/src/generated/StackOrder.ts Generated TS type for stack reorder payload.
packages/core/src/generated/StackReference.ts Generated TS type for stack references in listings.
packages/core/src/generated/StackStatus.ts Generated TS type for stack status payload.
packages/core/src/generated/StackStatuses.ts Generated TS discriminated-union for upstream integration statuses response.
packages/core/src/generated/TreeStatus.ts Generated TS discriminated-union for tree status.
packages/core/src/generated/index.ts Re-exports generated types from a single barrel.
crates/gitbutler-operating-modes/src/lib.rs Adds ts-rs derives/field TS overrides for mode types.
crates/gitbutler-operating-modes/Cargo.toml Adds optional ts-rs dep + export-ts feature.
crates/gitbutler-edit-mode/src/lib.rs Adds ts-rs derive for conflict presence type.
crates/gitbutler-edit-mode/Cargo.toml Adds optional ts-rs dep + export-ts feature.
crates/gitbutler-branch-actions/src/upstream_integration.rs Adds ts-rs derives + TS field overrides for upstream integration types.
crates/gitbutler-branch-actions/src/reorder.rs Adds ts-rs derives + TS type override for commit ID arrays.
crates/gitbutler-branch-actions/src/remote.rs Adds ts-rs derives + TS type overrides for commit metadata.
crates/gitbutler-branch-actions/src/branch.rs Adds ts-rs derives + TS type overrides/skips for listing payloads.
crates/gitbutler-branch-actions/src/base.rs Adds ts-rs derives + TS type overrides for base branch payload.
crates/gitbutler-branch-actions/src/author.rs Adds ts-rs derives + TS type override for URL field.
crates/gitbutler-branch-actions/Cargo.toml Adds optional ts-rs dep + export-ts feature wiring.
crates/but-core/src/id.rs Implements ts_rs::TS for Id<KIND> under export-ts.
crates/but-api/src/legacy/modes.rs Adds ts-rs derives for legacy mode payload types.
crates/but-api/Cargo.toml Adds optional ts-rs dep + export-ts feature wiring across crates.
Cargo.lock Records ts-rs dependency additions.
apps/desktop/src/routes/[projectId]/edit/+page.svelte Switches to importing EditModeMetadata from @gitbutler/core/api.
apps/desktop/src/lib/upstream/types.ts Replaces local TS types with generated imports; keeps helper functions.
apps/desktop/src/lib/stacks/stackEndpoints.ts Switches StackOrder to generated import.
apps/desktop/src/lib/mode/modeService.ts Re-exports mode-related types from generated API.
apps/desktop/src/lib/mode/modeEndpoints.ts Switches mode endpoint types to generated API types.
apps/desktop/src/lib/files/conflicts.ts Switches ConflictEntryPresence type import to generated API.
apps/desktop/src/lib/files/conflictEntryPresence.ts Re-exports ConflictEntryPresence from generated API.
apps/desktop/src/lib/dragging/stackingReorderDropzoneManager.ts Switches StackOrder to generated import.
apps/desktop/src/lib/branches/branchListing.ts Switches BranchListing type import to generated API.
apps/desktop/src/lib/branches/branchEndpoints.ts Switches upstream integration endpoint types to generated API types.
apps/desktop/src/lib/branches/branch.ts Removes hand-written StackOrder type (now generated).
apps/desktop/src/components/workspace/EditCommitPanel.svelte Switches edit/conflict presence types to generated imports.
apps/desktop/src/components/upstream/IntegrateUpstreamModal.svelte Switches upstream integration types to generated imports and aligns payload shapes.
apps/desktop/src/components/branchesPage/BranchListCard.svelte Switches branch listing types to generated imports.

Comment thread crates/gitbutler-branch-actions/src/reorder.rs Outdated
Comment thread crates/gitbutler-branch-actions/src/branch.rs Outdated
Comment thread apps/desktop/src/lib/mode/modeEndpoints.ts Outdated
Copy link
Copy Markdown
Contributor

@estib-vega estib-vega left a comment

Choose a reason for hiding this comment

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

Looks good!

We should start looking a using the generated types from the but-sdk and slowly transition away from the ts-rs types.

We already have a bunch of duplicate generated types

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 30 out of 57 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

packages/ui/src/lib/components/select/Select.svelte:22

  • SelectProps is not generic, but it references a module-scoped T (currently effectively any). This breaks the component’s generics="T extends string" contract and prevents consumers from getting strongly-typed value/options/onselect values. Consider removing the module-level type T = ... entirely and making SelectProps generic (e.g., SelectProps<T extends string = string>), then type $props() as SelectProps<T> in the instance script so inference flows from options/value to onselect.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 29 out of 30 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

packages/ui/src/lib/components/select/Select.svelte:21

  • SelectProps is not generic, but it uses T from the module script. That T is unrelated to the component generic declared in generics="T extends string = string", so value, options, and onselect won’t be typed based on the actual generic argument.

Define SelectProps as SelectProps<T extends string = string> and annotate $props() as SelectProps<T> to connect the prop types to the component generic.

	export type SelectProps = {
		id?: string;
		label?: string;
		disabled?: boolean;
		loading?: boolean;

Comment thread packages/ui/src/lib/components/select/Select.svelte Outdated
Comment thread crates/but-ts/src/main.rs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 193 out of 213 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment thread crates/but-github/src/lib.rs
Comment thread crates/but-gitlab/src/lib.rs
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 205 out of 226 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment thread apps/desktop/src/lib/baseBranch/baseBranch.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 206 out of 227 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

packages/but-sdk/package.json:69

  • The exports.require.default target points to ./src/generated/index.js, but this package is marked "type": "module" and the NAPI build is --esm, so index.js will be treated as an ES module. Node require() callers selecting the require condition will fail to load an ES module from require. Consider either removing the require condition (if CJS isn’t supported) or emitting/providing a separate CommonJS entry (e.g., index.cjs) for the require condition.

Comment thread .github/workflows/push.yaml
Comment thread apps/desktop/src/lib/forge/github/githubUserService.svelte.ts
@mtsgrd
Copy link
Copy Markdown
Contributor Author

mtsgrd commented Apr 1, 2026

@estib-vega @krlvi if you could both just have a quick look at this, everything should be accounted for.

@estib-vega
Copy link
Copy Markdown
Contributor

@mtsgrd hard to parse the whole thing. Thinking about it, I'd say maybe worth waiting for a release before merging this. Just so that we can test it in a nightly for a day or so

@mtsgrd
Copy link
Copy Markdown
Contributor Author

mtsgrd commented Apr 1, 2026

@estib-vega thanks for taking the time. Yes, I'm pretty sure this works, but let's not include it in the release.

Copilot AI review requested due to automatic review settings April 9, 2026 14:44
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 207 out of 228 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

@mtsgrd
Copy link
Copy Markdown
Contributor Author

mtsgrd commented Apr 9, 2026

@krlvi let's merge this?

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 209 out of 229 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment thread apps/desktop/src/lib/dependencies/dependencyService.svelte.ts
Comment thread crates/gitbutler-operating-modes/src/lib.rs Outdated
Rust side:
- Register all frontend-facing types with schemars JsonSchema +
  register_sdk_type!() across gitbutler-branch-actions, but-api,
  but-workspace, but-settings, but-core, but-github, but-gitlab,
  gitbutler-edit-mode, and gitbutler-operating-modes
- Add export-schema Cargo feature to gitbutler-branch-actions and
  gitbutler-edit-mode with conditional schemars guards
- Add rename_all = "camelCase" to but_core::ui::WorktreeChanges
  (fixes ignored_changes field being snake_case in JSON)
- Remove skip_serializing_if from SDK-registered types so nullable
  fields are always serialized (as null), matching the but-ts assumption
- Remove ts-rs derives and Cargo feature flags from all crates
- Delete generate-ts-definitions-from-rust.sh script
- Enhance but-ts: --source-crates filter, nullable-but-required field
  handling, append-to-existing output mode

TypeScript side:
- Delete all ts-rs generated files from @gitbutler/core
- Remove hand-written type duplicates from the desktop app (~40 files)
  and replace with direct imports from @gitbutler/but-sdk
- Add browser export condition to @gitbutler/but-sdk package.json
  with empty stub so Vite serves it instead of the napi index.js
- Add @gitbutler/but-sdk to Vite optimizeDeps.exclude
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 209 out of 229 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment on lines 56 to +71
build: {
rollupOptions: { output: { manualChunks: {} } },
rollupOptions: {
output: {
manualChunks: {},
// When building for the embedded server, merge small chunks to
// reduce the number of files embedded in the binary.
...(process.env.VITE_EMBEDDED_BUILD ? { experimentalMinChunkSize: 500_000 } : {}),
},
},
// Tauri supports es2021
target: "modules",
// minify production builds
minify: !process.env.TAURI_ENV_DEBUG ? "esbuild" : false,
// ship sourcemaps for better sentry error reports
sourcemap: true,
// Sourcemaps are useful for Sentry in Tauri builds but wasteful when
// the frontend is embedded in the server binary.
sourcemap: process.env.VITE_EMBEDDED_BUILD ? false : true,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLI The command-line program `but` @gitbutler/desktop @gitbutler/ui rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants