
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
servly-sync
Advanced tools
Bidirectional sync engine for Servly — diff, patch, hash, and conflict resolution for Layout JSON, props, and design tokens
Bidirectional sync engine for keeping design systems and code in sync. Diff, patch, hash, and resolve conflicts across Figma, code editors, CLIs, and AI tools.
Stack-agnostic. Zero dependencies. Works in Node.js, browsers, and edge runtimes.
npm install servly-sync
import { diff, applyPatch, hashComponent } from 'servly-sync';
// 1. Diff two versions of a component layout
const result = diff(
[{ i: 'btn-1', componentId: 'Button', className: 'px-4 py-2' }],
[{ i: 'btn-1', componentId: 'Button', className: 'px-6 py-3' }],
);
console.log(result.hasChanges); // true
console.log(result.summary); // { added: 0, removed: 0, modified: 1, moved: 0 }
console.log(result.patch); // [{ op: 'replace', path: '/0/className', value: 'px-6 py-3' }]
// 2. Apply the patch to get the updated version
const updated = applyPatch(
[{ i: 'btn-1', componentId: 'Button', className: 'px-4 py-2' }],
result.patch,
);
// 3. Hash the component for change detection
const hash = hashComponent({
layout: updated,
propsSchema: [{ name: 'label', type: 'string' }],
});
Compare two values and produce RFC 6902 JSON Patch operations.
import { diff, diffLayout, diffProps, diffTokens, deepEqual } from 'servly-sync';
// General-purpose diff (objects, arrays, primitives)
const result = diff(before, after);
// result.hasChanges — boolean
// result.patch — JSONPatchOperation[]
// result.changedPaths — string[]
// result.summary — { added, removed, modified, moved }
// Domain-specific diffs (same output, scoped base paths)
const layoutDiff = diffLayout(oldLayout, newLayout); // base: /layout
const propsDiff = diffProps(oldProps, newProps); // base: /propsSchema
const tokensDiff = diffTokens(oldTokens, newTokens); // base: /tokens
// Deep equality check
deepEqual({ a: 1 }, { a: 1 }); // true
By default, arrays are diffed by matching items on i, id, or name fields, falling back to index. Override this when your data model uses different ID fields:
// Use any field as the array item key
const result = diff(before, after, {
getKey: (item: any) => item.uuid,
});
// Works with all diff functions
const layoutDiff = diffLayout(oldLayout, newLayout, {
getKey: (item: any) => item.nodeId,
});
Apply JSON Patch operations (RFC 6902) to any value. Returns a new deep-cloned result — the original is never mutated.
import { applyPatch, validatePatch, PatchError } from 'servly-sync';
const updated = applyPatch(document, [
{ op: 'replace', path: '/title', value: 'New Title' },
{ op: 'add', path: '/tags/-', value: 'new-tag' },
{ op: 'remove', path: '/deprecated' },
]);
// Validate without applying
const error = validatePatch(document, operations);
if (error) console.error(error.message);
Supports all six RFC 6902 operations: add, remove, replace, move, copy, test.
DJB2-based hashing for change detection. Compatible with the Figma plugin's hashing, so hashes match across all sync surfaces.
import {
hashValue,
hashLayout,
hashProps,
hashTokens,
hashComponent,
hashComponentParts,
DEFAULT_LAYOUT_FIELDS,
} from 'servly-sync';
// Hash individual parts
const layoutHash = hashLayout(elements);
const propsHash = hashProps(propsSchema);
const tokensHash = hashTokens(tokens);
// Hash an entire component snapshot
const componentHash = hashComponent({
layout: elements,
propsSchema,
tokens,
metadata: { version: '1.0' },
});
// Get per-part hashes to know *which* part changed
const parts = hashComponentParts({ layout: elements, propsSchema });
// { layout: 'a3f2c1b0', props: '7e1d4f2a', tokens: '00000000', metadata: '00000000' }
By default, hashLayout hashes 9 structural fields. Use custom fields when your component model is different:
// Hash only the fields your component model uses
const hash = hashLayout(elements, ['key', 'type', 'children', 'props']);
// Extend the defaults with your own fields
const hash = hashLayout(elements, [...DEFAULT_LAYOUT_FIELDS, 'customField']);
Detect and resolve conflicts when two sources modify the same component concurrently.
import {
detectConflict,
detectAllConflicts,
resolveConflict,
mergeChanges,
} from 'servly-sync';
// Detect a single conflict between two change events
const conflict = detectConflict(localChange, remoteChange);
// Detect all conflicts between sets of changes
const conflicts = detectAllConflicts(localChanges, remoteChanges);
// Resolve with a strategy
const resolution = resolveConflict(conflict, 'auto-merge');
// Strategies: 'local-wins' | 'remote-wins' | 'auto-merge' | 'manual'
// Full merge pipeline — resolves what it can, surfaces the rest
const { merged, conflicts: unresolved, resolutions } = mergeChanges(
baseDocument,
localChanges,
remoteChanges,
'auto-merge',
);
High-level orchestration that coordinates diff, hash, conflict, and patch into a single call. Use these when you want the full sync workflow without manually calling each primitive.
import { preparePush, preparePull, prepareMerge, applyResolution } from 'servly-sync';
// Push: diff local against remote, get patches to send
const pushResult = preparePush(localSnapshot, remoteSnapshot);
// pushResult.patches — JSONPatchOperation[] to apply on remote
// pushResult.hash — hash of local state
// Pull: diff remote against local, get patches to apply locally
const pullResult = preparePull(localSnapshot, remoteSnapshot);
// Merge: when both sides changed, detect and resolve conflicts
const mergeResult = prepareMerge(baseSnapshot, localSnapshot, remoteSnapshot, {
strategy: 'auto-merge',
diffOptions: { getKey: (item: any) => item.uid },
});
// mergeResult.patches — resolved patches
// mergeResult.conflicts — unresolved conflicts (if strategy is 'manual')
// Apply resolved patches to a snapshot
const updated = applyResolution(baseSnapshot, mergeResult.patches);
Track the sync status of each component across local and remote sources.
import {
createSyncState,
recordLocalChange,
recordRemoteChange,
computeStatus,
markSynced,
describeSyncState,
SyncStateStore,
} from 'servly-sync';
// Functional API
let state = createSyncState('btn-primary', initialHash);
state = recordLocalChange(state, changeEvent);
console.log(computeStatus(state)); // 'ahead'
console.log(describeSyncState(state)); // '1 local change(s) to push'
state = markSynced(state, newHash);
console.log(computeStatus(state)); // 'in-sync'
// Class-based store for managing multiple components
const store = new SyncStateStore();
store.recordLocal('btn-primary', changeEvent);
store.recordRemote('card-hero', remoteEvent);
store.getByStatus('conflict'); // components with conflicts
store.getAllConflicts(); // all unresolved conflicts
Statuses: in-sync | ahead | behind | diverged | conflict | unknown
Create and manage sync change events that flow through the system.
import {
createChangeEvent,
createBatch,
chunkBatches,
validateChangeEvent,
groupByComponent,
groupByChangeType,
sortEvents,
} from 'servly-sync';
// Split large event lists into sized batches for rate-limited APIs
const batches = chunkBatches('cli', 'my-tool', events, 50);
// batches[0].changes.length <= 50
Sources: figma | builder | cli | cursor | ai
Change types: layout | props | tokens | style | metadata | component | behavior
Transform design tokens between Figma Variables, CSS custom properties, and Tailwind config.
import {
figmaVariablesToServlyTokens,
servlyTokensToCSS,
servlyTokensToCSSWithModes,
servlyTokensToTailwindConfig,
servlyTokensToTailwindConfigString,
cssToServlyTokens,
buildTokenSyncManifest,
DEFAULT_CATEGORY_RULES,
} from 'servly-sync';
// Figma Variables -> Servly tokens
const { tokens, entries } = figmaVariablesToServlyTokens(figmaVariables);
// Servly tokens -> CSS custom properties
const css = servlyTokensToCSS(tokens);
// :root {
// --ds-primary: #3b82f6;
// --ds-spacing-sm: 8px;
// }
// With light/dark mode support
const cssWithModes = servlyTokensToCSSWithModes(tokens);
// Servly tokens -> Tailwind config
const tailwindConfig = servlyTokensToTailwindConfig(tokens);
// CSS -> Servly tokens (reverse sync)
const parsedTokens = cssToServlyTokens(existingCSS);
Control how token categories are inferred from variable names:
// Add custom category rules (checked before built-in rules)
const { tokens } = figmaVariablesToServlyTokens(variables, {
prefix: 'my',
categoryRules: [
{ keywords: ['motion', 'duration', 'animation'], category: 'motion' },
{ keywords: ['breakpoint'], category: 'responsive' },
],
});
// Or override category inference entirely
const { tokens } = figmaVariablesToServlyTokens(variables, {
inferCategory: (variable) => myCustomCategoryLogic(variable),
});
Bidirectional prop type mapping between design tool types and code types. Ships with a Figma preset — create your own for Sketch, Adobe XD, Penpot, or custom component models.
import {
mapPropType,
reversePropType,
mapPropValue,
extendPreset,
FIGMA_PROP_PRESET,
} from 'servly-sync';
// Map Figma prop types to servly-sync types
mapPropType('TEXT', FIGMA_PROP_PRESET); // 'string'
mapPropType('VARIANT', FIGMA_PROP_PRESET); // 'enum'
mapPropType('BOOLEAN', FIGMA_PROP_PRESET); // 'boolean'
// Reverse map back to Figma types
reversePropType('string', FIGMA_PROP_PRESET); // 'TEXT'
// Create a custom preset for your design tool
const sketchPreset = extendPreset(FIGMA_PROP_PRESET, 'sketch', [
{ sourceType: 'COLOR', targetType: 'color' },
{ sourceType: 'TEXT', targetType: 'string', transformValue: (v) => String(v).trim() },
]);
Register converters between component representations. Each registry is independent — no global state.
import { createFormatRegistry } from 'servly-sync';
const registry = createFormatRegistry();
// Register a Figma -> Snapshot converter
registry.register({
from: 'figma-json',
to: 'snapshot',
convert: (figmaNode) => ({
componentId: figmaNode.id,
hash: '',
timestamp: new Date().toISOString(),
layout: extractLayout(figmaNode),
}),
});
// Convert
const snapshot = registry.convert('figma-json', 'snapshot', figmaNode);
// List registered converters
registry.list(); // [['figma-json', 'snapshot']]
Infer HTML semantic tags from component names and structure. 130+ built-in rules, extensible with your own.
import { inferTag, inferTagsForLayout, getTagRules } from 'servly-sync';
inferTag('Button'); // { tag: 'button', role: 'button', confidence: 0.95 }
inferTag('Hero Image'); // { tag: 'img', confidence: 0.8 }
inferTag('Navbar'); // { tag: 'nav', role: 'navigation', confidence: 0.95 }
// Add custom rules for your component library
inferTag('Widget', {
customRules: [
{ pattern: 'widget', tag: 'section', role: 'region', confidence: 0.95 },
],
});
// Replace built-in rules entirely
inferTag('Button', {
customRules: myRules,
mergeWithDefaults: false,
});
Define extraction/application adapters for any design tool or code framework. The interface lives in servly-sync — implementations live in your code.
import type { SourceAdapter, ComponentSnapshot } from 'servly-sync';
class FigmaAdapter implements SourceAdapter<FigmaNode> {
name = 'figma';
async extract(): Promise<ComponentSnapshot[]> { /* ... */ }
async apply(patches: JSONPatchOperation[]): Promise<void> { /* ... */ }
toSnapshot(node: FigmaNode): ComponentSnapshot { /* ... */ }
fromSnapshot(snapshot: ComponentSnapshot): FigmaNode { /* ... */ }
}
Generate user-facing notifications from sync events.
import {
notificationFromChange,
conflictNotification,
prCreatedNotification,
filterNotifications,
} from 'servly-sync';
servly-sync is the shared core consumed by every surface in the Servly ecosystem:
Figma Plugin ──┐
Code Editor ──┤
CLI ──┼── servly-sync ── diff/patch/hash/conflict ── Any Backend
AI Tools ──┤
MCP Server ──┘
The sync pipeline:
# Install dependencies
npm install
# Build (ESM + CJS + types)
npm run build
# Run tests
npm test
# Watch mode
npm run dev
src/
index.ts Public API exports
types.ts All TypeScript type definitions
diff.ts RFC 6902 diff engine (configurable array keying)
patch.ts JSON Patch application
hash.ts DJB2 hashing (configurable layout fields)
conflict.ts Conflict detection & resolution
syncState.ts Sync state management
changeEvent.ts Change event creation + batch chunking
pipeline.ts Sync orchestration (push/pull/merge)
tokenSync.ts Design token transformations (configurable categories)
propsMapper.ts Bidirectional prop type mapping
formatRegistry.ts Format converter registry
fingerprint.ts Component fingerprinting
semanticTags.ts Semantic tag inference (extensible rules)
notifications.ts Notification generation
__tests__/ Test suite (Vitest)
npm test # single run
npm run test:watch # watch mode
We welcome contributions. Here's how to get started:
mainnpm installnpm testnpm run buildServly is building an open standard for syncing design systems between tools and code. servly-sync is the engine at the center of that — it handles the hard parts of bidirectional sync so that every integration surface (Figma plugins, CLI tools, editor extensions, AI agents) speaks the same language.
servly-sync will validate and transform CIF documents.MIT
FAQs
Bidirectional sync engine for Servly — diff, patch, hash, and conflict resolution for Layout JSON, props, and design tokens
We found that servly-sync demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.