sync

package
v1.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 1, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Index

Constants

View Source
const IgnoreFile = ".wkignore"

IgnoreFile is the filename at the project root whose patterns exclude files and directories from sync operations (ADR-005 Decision 10).

Variables

This section is empty.

Functions

func ComputeHash

func ComputeHash(data []byte) string

ComputeHash returns the SHA256 hex digest of data.

func FindMetaFiles

func FindMetaFiles(projectRoot, localDir string) (map[string]*AssetMeta, error)

FindMetaFiles scans the .wk/ mirror tree for metas describing assets whose asset path is under localDir. Keys in the returned map are asset paths relative to localDir, so callers can compare them directly against filepath.Walk rel-paths of the asset tree.

func MetaPath

func MetaPath(projectRoot, assetAbs string) (string, error)

MetaPath returns the canonical meta path for an asset located at assetAbs within the project rooted at projectRoot. The meta lives inside projectRoot/.wk/ mirroring the asset's relative position.

func WriteMeta

func WriteMeta(metaPath string, meta *AssetMeta) error

WriteMeta marshals and writes a meta file, creating parent directories under .wk/ as needed.

Types

type AssetMeta

type AssetMeta struct {
	ServerPath   string    `json:"server_path"`
	ZipName      string    `json:"zip_name"`
	Folder       string    `json:"folder"`
	Type         string    `json:"type"` // "recipe", "connection"
	Version      int       `json:"version"`
	RecipeName   string    `json:"recipe_name,omitempty"`
	ContentHash  string    `json:"content_hash"` // SHA256
	LastPulledAt time.Time `json:"last_pulled_at"`
}

AssetMeta is the sidecar metadata stored for each synced asset.

Per ADR-005 Decision 1, metas live under the project's .wk/ mirror tree — the asset at <root>/<rel> has its meta at <root>/.wk/<rel>.meta.json. Meta files never sit next to assets.

RecipeName is populated only when Type == "recipe" and the JSON body in the pull zip contained a parsable top-level "name" field. The Workato package-manifest zip intentionally omits server-side IDs (the wk recipes export endpoint is the only source for those), so downstream local-cleanup paths (e.g. wk recipes delete) match by name — resolving an ID to a name via a single API call first.

func ReadMeta

func ReadMeta(metaPath string) (*AssetMeta, error)

ReadMeta reads and unmarshals a meta file at the given path.

type AssetStatus

type AssetStatus struct {
	FilePath   string     `json:"file_path"`
	Status     FileStatus `json:"status"`
	ServerPath string     `json:"server_path,omitempty"` // from meta, if available
}

AssetStatus represents the sync status of a single asset file.

type CreateMode

type CreateMode int

CreateMode controls folderIDForEntry's fallback behavior when the hierarchy walk fails to resolve an entry's server_path (ADR-007 Decision 13). Default (zero value) is CreateModeBareNames.

const (
	// CreateModeBareNames creates a missing top-level folder when the
	// server_path has no slashes — the greenfield pattern. Nested paths
	// still error; auto-creating a multi-level tree on first push is
	// more likely to mask a typo than to match intent.
	CreateModeBareNames CreateMode = iota

	// CreateModeNever disables auto-create entirely. Any missing folder
	// surfaces as an error. The --no-create CI escape hatch.
	CreateModeNever

	// CreateModeAnyPath creates missing folders at any depth, walking
	// parents and creating each segment in order. The --create-path
	// escape hatch for deliberately spinning up a nested hierarchy.
	CreateModeAnyPath
)

type DiffEntry

type DiffEntry struct {
	Path       string   `json:"path"`
	Type       DiffType `json:"type"`
	LocalHash  string   `json:"local_hash,omitempty"`
	RemoteHash string   `json:"remote_hash,omitempty"`
}

DiffEntry describes the diff state of a single asset path.

type DiffType

type DiffType string

DiffType describes how a local asset differs from its remote counterpart.

const (
	DiffAdded    DiffType = "added"    // exists remote, not local
	DiffModified DiffType = "modified" // both exist, different hash
	DiffDeleted  DiffType = "deleted"  // exists local, not remote
	DiffSame     DiffType = "same"     // identical
)

type FileStatus

type FileStatus string

FileStatus describes the local state of a synced asset.

const (
	StatusUnchanged FileStatus = "unchanged"
	StatusModified  FileStatus = "modified"
	StatusNew       FileStatus = "new"     // local file with no .meta.json sidecar in .wk/
	StatusDeleted   FileStatus = "deleted" // .meta.json exists in .wk/ but asset file is gone
)

type FolderCreated

type FolderCreated struct {
	ServerPath string `json:"server_path"`
	FolderID   int    `json:"folder_id"`
	ProjectID  int    `json:"project_id,omitempty"`
}

FolderCreated records one server-side folder creation triggered by push's resolve-then-create branch (ADR-007 Decision 14). One record per API call — under --create-path a single entry may produce multiple records (one per missing segment). ProjectID is non-zero when the API marked the created folder as a project (is_project=true); it is the separate identifier required by DELETE /projects/{project_id}.

type Matcher

type Matcher struct {
	// contains filtered or unexported fields
}

Matcher evaluates paths against a set of .wkignore patterns.

Semantics follow the subset of gitignore documented in ADR-005 Decision 10:

  • `*` matches within a single path component
  • `**` matches across path separators
  • a trailing `/` restricts the pattern to directories
  • a leading `!` negates a previous match
  • `#` begins a comment; blank lines are ignored
  • all patterns evaluate relative to the project root

Anything beyond this spec (leading-slash anchoring beyond the default, re-inclusion after parent exclusion, character classes) is intentionally out of scope.

func LoadMatcher

func LoadMatcher(projectRoot string) (*Matcher, error)

LoadMatcher reads <projectRoot>/.wkignore and returns a compiled Matcher. If the file is absent, returns a non-nil Matcher that matches nothing (default: include everything).

func (*Matcher) Match

func (m *Matcher) Match(relPath string, isDir bool) bool

Match reports whether relPath (project-root-relative, forward slashes) should be ignored. isDir disambiguates directory-only patterns.

Evaluation is last-match-wins, so a later negating pattern can re-include a previously matched path.

func (*Matcher) ShouldSkip

func (m *Matcher) ShouldSkip(relPath string, isDir bool) bool

ShouldSkip reports whether the walker should skip a path entirely. This combines the implicit .wk/ skip (regardless of .wkignore content, ADR-005 Decision 10) with the user's .wkignore rules.

relPath must be project-root-relative using forward slashes.

type PullResult

type PullResult struct {
	FilePath string `json:"file_path"`
	// Action is one of: "created", "updated", "unchanged", "skipped",
	// "deleted" (server-side delete reconciled locally), "orphaned"
	// (server-side deleted but local copy was modified — kept on disk),
	// or "error" (per-file failure when --skip-errors is active).
	Action string `json:"action"`
	Error  string `json:"error,omitempty"`
}

PullResult describes what happened to a single file during pull.

type PushResult

type PushResult struct {
	FilePath string `json:"file_path"`
	Action   string `json:"action"` // "created", "updated", "deleted", "unchanged" (dry-run)
}

PushResult describes what happened to a single file during push.

type RefreshResult

type RefreshResult struct {
	ServerPath string       `json:"server_path"`
	LocalPath  string       `json:"local_path"`
	FolderID   int          `json:"folder_id,omitempty"`
	ProjectID  int          `json:"project_id,omitempty"`
	State      RefreshState `json:"state"`
	Message    string       `json:"message,omitempty"`
}

RefreshResult is the per-entry outcome emitted by ClassifyEntry. ServerPath / LocalPath come from the incoming entry; FolderID is either the resolved ID (found / current / repaired) or the cached ID from before classification (not-found, so the developer can still see what the local wk.toml had). ProjectID mirrors the same policy for the distinct project identifier (populated when the folder is a project, zero otherwise). Message carries a human detail string for repaired / not-found; empty for found / current.

type RefreshState

type RefreshState string

RefreshState is the per-entry outcome of `wk sync refresh` (ADR-007 Decision 11). Values are stable across human and JSON output so downstream scripts can branch on the string literally.

const (
	// RefreshStateFound: entry had no cached folder_id and the walk
	// succeeded. First-time resolution — no drift involved. The new ID
	// has been written into the in-memory config slot; callers save
	// once per sweep.
	RefreshStateFound RefreshState = "found"

	// RefreshStateCurrent: entry had a cached folder_id and the walk
	// returned the same ID. No cache change needed.
	RefreshStateCurrent RefreshState = "current"

	// RefreshStateRepaired: entry had a cached folder_id but the walk
	// returned a different ID (renamed folder, recreated with a new
	// ID, workspace swap, etc.). Drift was detected; the cache has
	// been rewritten with the fresh ID. CI monitors can alert on this
	// state to see that the workspace changed underfoot.
	RefreshStateRepaired RefreshState = "repaired"

	// RefreshStateNotFound: walk failed — server_path does not describe
	// a real folder. Reported; --prune removes. Entries that had a
	// cached folder_id still show it in the output so the developer can
	// see "used to work" vs "never worked"; the distinction is in the
	// data, not in a separate state label.
	RefreshStateNotFound RefreshState = "not-found"
)

type SyncEngine

type SyncEngine struct {
	// contains filtered or unexported fields
}

SyncEngine coordinates pull, push, status, and diff operations between the local project directory and the Workato workspace.

func NewSyncEngine

func NewSyncEngine(projectRoot string, cfg *config.Config, client api.Client) *SyncEngine

NewSyncEngine creates a SyncEngine wired to the given project and API client. client may be nil for local-only operations (e.g. status).

func (*SyncEngine) ClassifyEntry

func (e *SyncEngine) ClassifyEntry(ctx context.Context, entry config.SyncEntry) (RefreshResult, error)

ClassifyEntry reconciles one [sync] entry against current server state (ADR-007 Decision 11). The Workato API does not expose a single-folder-by-ID endpoint, so cache validation happens by walking the hierarchy via List and comparing the resolved leaf ID against the cached value. On a fresh resolution (found) or cache repair (repaired), writes the new folder_id into the matching config slot in memory; callers persist via config.Save once the full sweep has run.

Returns a non-nil error only for genuine API failures (auth, 5xx, network). "Name not present under expected parent" is distinguished from API failure via the errPathNotResolved sentinel that resolveFolderID wraps into its error, and classifies the entry as not-found rather than halting the sweep.

func (*SyncEngine) Diff

func (e *SyncEngine) Diff(entry config.SyncEntry) ([]DiffEntry, error)

Diff compares local files against remote state for a sync entry. This requires API access to fetch the remote package.

func (*SyncEngine) EnableFolderListCache

func (e *SyncEngine) EnableFolderListCache()

EnableFolderListCache turns on per-parent List memoization for this engine. Safe to call once before a batch of ClassifyEntry calls. Pull/push/status should NOT enable this — they can see mid-run server changes and must reach the API freshly.

func (*SyncEngine) FoldersCreated

func (e *SyncEngine) FoldersCreated() []FolderCreated

FoldersCreated returns the server-folder creations that have happened during this engine's lifetime. Reset is caller-scoped: a fresh engine always starts empty. Safe to call once after all Push invocations complete.

Always returns a non-nil slice so JSON consumers see "[]" rather than "null" when nothing was created — keeps scripts that destructure folders_created from having to special-case the empty case.

func (*SyncEngine) Pull

func (e *SyncEngine) Pull(entry config.SyncEntry, force, skipErrors bool) ([]PullResult, error)

Pull downloads remote assets to the local project directory. If force is false, it aborts when a locally modified file would be overwritten by the fresh export — but only if that file is still present server-side. Files that were modified locally AND removed server-side are NOT conflicts: reconcileDeletions reports them as "orphaned" and leaves them on disk untouched. That narrower semantic means the overwrite check must run after the zip is in hand, not before.

func (*SyncEngine) Push

func (e *SyncEngine) Push(entry config.SyncEntry, dryRun bool, preserveState bool, force bool) ([]PushResult, error)

Push uploads local assets to the remote workspace. If dryRun is true, it reports what would be pushed without making changes. preserveState controls whether recipe active state is preserved on import. If force is true, all tracked files are pushed regardless of local change status.

func (*SyncEngine) SetCreateMode

func (e *SyncEngine) SetCreateMode(mode CreateMode)

SetCreateMode configures how folderIDForEntry handles a failed walk for uncached entries. Call once during command setup, after flag parsing. Default (zero value) is CreateModeBareNames.

func (*SyncEngine) Status

func (e *SyncEngine) Status(entry config.SyncEntry) ([]AssetStatus, error)

Status computes the local modification state for a sync entry. This is a purely local operation -- no API calls are made.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL