diff --git a/README.md b/README.md index ae09915..cd3db37 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A multi-project workspace for Figma plugins and design tools. | Project | Folder | Status | Notes | |---------|--------|--------|-------| -| Variables & Styles Extractor | [`variables-styles-extractor/`](variables-styles-extractor/) | Published v2.1.0 · first published 17 Jan 2026 | Figma plugin · [Community page](https://www.figma.com/community/plugin/1584331992332668732/variables-and-styles-extractor) | +| Variables & Styles Extractor | [`variables-styles-extractor/`](variables-styles-extractor/) | Published v2.1.2 · first published 17 Jan 2026 | Figma plugin · [Community page](https://www.figma.com/community/plugin/1584331992332668732/variables-and-styles-extractor) | --- diff --git a/variables-styles-extractor/README.md b/variables-styles-extractor/README.md index 81acb06..faed9a8 100644 --- a/variables-styles-extractor/README.md +++ b/variables-styles-extractor/README.md @@ -3,11 +3,11 @@ [![Figma Plugin](https://img.shields.io/badge/Figma-Plugin-ff69b4)](https://www.figma.com/community/plugin/1584331992332668732/variables-and-styles-extractor) [![Source: MIT](https://img.shields.io/badge/Source-MIT-yellow.svg)](./LICENSE) [![Distribution: CFRL](https://img.shields.io/badge/Figma%20Distribution-CFRL-blueviolet.svg)](https://www.figma.com/community-free-resource-license/) -[![Version](https://img.shields.io/badge/version-2.1.0-blue.svg)](./package.json) +[![Version](https://img.shields.io/badge/version-2.1.2-blue.svg)](./package.json) **Move your design system anywhere. Export and import Figma variables and styles — selectively, safely, and in Tokens Studio–compatible JSON.** -> 🔍 **Status:** v2.1.0 published to Figma Community · v2.0.0 first published 17 January 2026 +> 🔍 **Status:** v2.1.2 published to Figma Community · v2.0.0 first published 17 January 2026 Variables & Styles Extractor moves complete design systems between Figma files — every variable collection, mode, alias, and style — as clean, re-importable JSON. It runs **100% locally** (zero network access) and stays responsive on large design systems thanks to a batched processing engine with live progress and a real Cancel button. diff --git a/variables-styles-extractor/code.js b/variables-styles-extractor/code.js index 8625813..98b8a14 100644 --- a/variables-styles-extractor/code.js +++ b/variables-styles-extractor/code.js @@ -8,4 +8,4 @@ * @version 2.0.0 * @author Tushar Kant Naik * @website https://tusharkantnaik.com - */const UI_SIZE={simple:{width:905,height:628},advanced:{width:1200,height:628}};figma.showUI(__html__,{width:UI_SIZE.simple.width,height:UI_SIZE.simple.height,themeColors:!0,title:"Variables & Styles Extractor v2.1.0"});const Logger={log(e,t){console.log(`[Variables Extractor] ${e}`,t||""),figma.ui.postMessage({type:"log",message:e,data:t})},send(e,t){figma.ui.postMessage({type:e,data:t})}};function yieldToHost(){return new Promise(function(e){setTimeout(e,0)})}function makeCancelError(){const e=new Error("Operation cancelled");return e.isOperationCancelled=!0,e}function isCancelError(e){return"object"==typeof e&&null!==e&&!0===e.isOperationCancelled}const currentOperation={type:null,cancelRequested:!1,cancellable:!0};function beginOperation(e){return null!==currentOperation.type?(figma.ui.postMessage({type:"operation_denied",requested:e,running:currentOperation.type}),!1):(currentOperation.type=e,currentOperation.cancelRequested=!1,currentOperation.cancellable=!0,!0)}function endOperation(){currentOperation.type=null,currentOperation.cancelRequested=!1,currentOperation.cancellable=!0}function checkCancelled(){if(currentOperation.cancelRequested&¤tOperation.cancellable)throw makeCancelError()}async function withOperation(e,t){if(beginOperation(e))try{await t()}finally{endOperation()}}async function runBatched(e,t,o,a){const s=e.length;for(let n=0;n0&&n>=r)&&l-oo&&(o=t.modes.length);return t=o>20?"enterprise":o>10?"organization":"professional",Object.assign({plan:t},PLAN_LIMITS[t])}async function validateImportAgainstPlan(e,t){const o=t?Object.assign({plan:t},PLAN_LIMITS[t]):await detectCurrentPlan(),a=await figma.variables.getLocalVariableCollectionsAsync(),s=a.reduce((e,t)=>Math.max(e,t.modes.length),0),n=(await figma.variables.getLocalVariablesAsync()).length,r=[];for(const t of e)"_styles"in t||r.push(t);let i=0,l=0;const c=[];for(const e of r){const t=Object.keys(e)[0],a=e[t];if(!a||!a.modes)continue;const s=Object.keys(a.modes).length;s>i&&(i=s),s>o.maxModesPerCollection&&c.push(`"${t}" (${s} modes, limit: ${o.maxModesPerCollection===1/0?"∞":o.maxModesPerCollection})`);const n=Object.values(a.modes)[0];n&&(l+=countNestedVariables(n))}const g=[],d=[];c.length;for(const e of r){const t=Object.keys(e)[0],o=e[t];if(!o||!o.modes)continue;const a=Object.values(o.modes)[0],s=a?countNestedVariables(a):0;s>5e3&&d.push(`Collection "${t}" has ${s} variables, exceeds limit of 5000`)}l>1e3&&g.push(`Large import: ${l} variables. This may take a moment.`),r.length>10&&g.push(`Importing ${r.length} collections. Consider importing in batches.`);const f=new Set;let u=0;for(const e of r){const t=e[Object.keys(e)[0]];if(t&&t.modes)for(const e of Object.keys(t.modes)){const o=flattenVariables(t.modes[e],"");for(const{value:e}of o)e.$libraryRef&&e.$collectionName&&(f.add(e.$collectionName),u++)}}const p=[];let m=0;for(const t of e)if("_styles"in t){const e=t._styles;if(e.textStyles)for(const t of e.textStyles){m++;const e=`${t.fontFamily}|${t.fontStyle}`;p.some(t=>`${t.family}|${t.style}`===e)||p.push({family:t.fontFamily,style:t.fontStyle})}}return Object.assign(Object.assign({currentPlan:o,existing:{collections:a.length,maxModesInAnyCollection:s,totalVariables:n},importing:{collections:r.length,maxModesInAnyCollection:i,totalVariables:l,collectionsExceedingModeLimit:c},warnings:g,errors:d,canImport:0===d.length},f.size>0&&{libraryDependencies:{variableCount:u,collections:Array.from(f)}}),p.length>0&&{fontDependencies:{styleCount:m,fonts:p}})}function countNestedVariables(e,t=0){for(const[,o]of Object.entries(e))o&&"object"==typeof o&&("$type"in o&&"$value"in o?t++:t=countNestedVariables(o,t));return t}const MathUtils={round2:e=>Math.round(100*e)/100,clamp:(e,t,o)=>Math.max(t,Math.min(o,e)),toHexByte:e=>Math.round(255*e).toString(16).padStart(2,"0"),fromHexByte:e=>parseInt(e,16)/255};function calculateHue(e,t,o,a,s){if(a===s)return 0;const n=a-s;let r=0;switch(a){case e:r=((t-o)/n+(t.5?e/(2-s-n):e/(s+n)}const l={h:calculateHue(t,o,a,s,n),s:Math.round(100*i),l:Math.round(100*r)},c=e.a;return void 0!==c&&c<1?Object.assign(Object.assign({},l),{a:MathUtils.round2(c)}):l},toHsb(e){const{r:t,g:o,b:a}=e,s=Math.max(t,o,a),n=Math.min(t,o,a),r=0===s?0:(s-n)/s,i={h:calculateHue(t,o,a,s,n),s:Math.round(100*r),b:Math.round(100*s)},l=e.a;return void 0!==l&&l<1?Object.assign(Object.assign({},i),{a:MathUtils.round2(l)}):i},toAllFormats(e){return{hex:this.toHex(e),rgb:this.toRgb255(e),css:this.toCss(e),hsl:this.toHsl(e),hsb:this.toHsb(e)}}},NamingConverter={convert(e,t){if("original"===t)return e;const o=e.replace(/([a-z])([A-Z])/g,"$1 $2").split(/[\s\/\-_]+/).filter(e=>e.length>0).map(e=>e.toLowerCase());if(0===o.length)return e;switch(t){case"camelCase":return o[0]+o.slice(1).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("");case"kebab-case":return o.join("-");case"snake_case":return o.join("_");default:return e}},convertPath(e,t){return"original"===t?e:e.split("/").map(e=>this.convert(e,t)).join("/")},convertCollectionName(e,t){return this.convert(e,t)},convertModeName(e,t){return this.convert(e,t)},addOriginalName(e,t){if("original"===t)return{converted:e};const o=this.convert(e,t);return o===e?{converted:e}:{converted:o,original:e}}};async function resolveAliasValue(e,t,o=10){if(o<=0)return Logger.log(`⚠️ Max alias resolution depth reached for ${e.name}`),"";let a=e.valuesByMode[t];if(void 0===a){const t=Object.keys(e.valuesByMode);t.length>0&&(a=e.valuesByMode[t[0]])}if(void 0===a)return"";if(isVariableAlias(a)){const e=await figma.variables.getVariableByIdAsync(a.id);return e?resolveAliasValue(e,t,o-1):""}return a}const W3C_TYPE_MAP={color:"color",float:"number",string:"string",boolean:"boolean"},W3CConverter={colorToW3C:e=>e.hex,typeToW3C:e=>W3C_TYPE_MAP[e]||"string",valueToW3C(e,t=!1){const o={$value:"",$type:this.typeToW3C(e.$type)};return t&&"string"==typeof e.$value&&e.$value.startsWith("{")?o.$value=e.$value:"color"===e.$type&&"object"==typeof e.$value?o.$value=e.$value.hex:o.$value=e.$value,e.$description&&(o.$description=e.$description),e.$scopes&&e.$scopes.length>0&&!e.$scopes.includes("ALL_SCOPES")&&(o.$extensions={"com.figma":{scopes:e.$scopes}}),o},collectionToW3C(e,t,o,a){const s={};a&&a!==e&&(s.$description=`Figma collection: ${a}`);const n=Object.keys(t);if(1===n.length)this.addTokensToGroup(s,t[n[0]],o);else for(const e of n){const a=NamingConverter.convertModeName(e,o);s[a]={},this.addTokensToGroup(s[a],t[e],o)}return s},addTokensToGroup(e,t,o){for(const[a,s]of Object.entries(t)){const t=NamingConverter.convert(a,o);if(isExportVariableValue(s)){const o="string"==typeof s.$value&&s.$value.startsWith("{");e[t]=this.valueToW3C(s,o)}else e[t]={},this.addTokensToGroup(e[t],s,o)}},parseW3CToken(e){var t,o;const a=this.w3cTypeToFigma(e.$type),s=(null===(o=null===(t=e.$extensions)||void 0===t?void 0:t["com.figma"])||void 0===o?void 0:o.scopes)||["ALL_SCOPES"];let n;if("color"===a&&"string"==typeof e.$value){const t=ColorParser.parse(e.$value);n=ColorConverter.toAllFormats(t)}else n="string"==typeof e.$value||"number"==typeof e.$value||"boolean"==typeof e.$value?e.$value:JSON.stringify(e.$value);return e.$description?{$type:a,$value:n,$scopes:s,$description:e.$description}:{$type:a,$value:n,$scopes:s}},w3cTypeToFigma:e=>({color:"color",number:"float",dimension:"float",string:"string",boolean:"boolean",fontFamily:"string",fontWeight:"float",duration:"string",cubicBezier:"string"}[e]||"string"),isW3CFormat(e){if("object"!=typeof e||null===e)return!1;const t=e;for(const e of Object.keys(t)){const o=t[e];if("object"==typeof o&&null!==o){if("$value"in o&&"$type"in o)return!0;for(const e of Object.keys(o)){const t=o[e];if("object"==typeof t&&null!==t&&"$value"in t)return!0}}}return Array.isArray(e),!1},w3cToFigmaFormat(e){const t=[];for(const[o,a]of Object.entries(e)){if(o.startsWith("$"))continue;const e={[o]:{modes:{Default:this.w3cGroupToNestedVars(a)}}};t.push(e)}return t},w3cGroupToNestedVars(e){const t={};for(const[o,a]of Object.entries(e))o.startsWith("$")||(this.isW3CToken(a)?t[o]=this.parseW3CToken(a):"object"==typeof a&&null!==a&&(t[o]=this.w3cGroupToNestedVars(a)));return t},isW3CToken:e=>"object"==typeof e&&null!==e&&"$value"in e},TS_FLOAT_SCOPE_MAP={CORNER_RADIUS:"borderRadius",STROKE_FLOAT:"borderWidth",GAP:"spacing",WIDTH_HEIGHT:"sizing",OPACITY:"opacity",FONT_SIZE:"fontSizes",LINE_HEIGHT:"lineHeights",LETTER_SPACING:"letterSpacing",PARAGRAPH_SPACING:"paragraphSpacing"},TS_STRING_SCOPE_MAP={FONT_FAMILY:"fontFamilies",FONT_STYLE:"fontWeights"},TS_TEXT_CASE_MAP={ORIGINAL:"none",UPPER:"uppercase",LOWER:"lowercase",TITLE:"capitalize"},TS_TEXT_DECORATION_MAP={NONE:"none",UNDERLINE:"underline",STRIKETHROUGH:"line-through"},TokensStudioConverter={sanitizeSegment(e){let t=e.replace(/[{}$]/g,"");return 0===t.length&&(t="_"),"__proto__"!==t&&"constructor"!==t&&"prototype"!==t||(t="_"+t),t},sanitizeAliasRef(e){const t=e.substring(1,e.length-1).split("."),o=[];for(let e=0;e"string"==typeof e&&"{"===e.charAt(0)&&"}"===e.charAt(e.length-1),roundNumber:e=>Math.round(1e3*e)/1e3,colorToTS(e){const t=void 0!==e.rgb?e.rgb.a:void 0;return void 0!==t&&t<1?e.css:void 0!==e.rgb&&e.hex.length>7?e.hex.substring(0,7):e.hex},floatTypeFromScopes(e){if(!e)return"number";let t="",o=0;for(let a=0;ae.toLowerCase().replace(/\s+/g,"-")};function convertToTokensStudio(e){const t=TokensStudioConverter,o={},a=[],s={libraryRefsSkipped:0,imagePaintsSkipped:0,blurEffectsSkipped:0},n=[];let r=null;for(let t=0;t0){const e={};for(let o=0;o0){const t=styleSetKey("styles/color");o[t]=e,a.push(t),i.push(t)}}if(void 0!==r.textStyles&&r.textStyles.length>0){const e={};for(let o=0;o0){const t=styleSetKey("styles/typography");o[t]=e,a.push(t),i.push(t)}}if(void 0!==r.effectStyles&&r.effectStyles.length>0){const e={};for(let o=0;o0){const t=styleSetKey("styles/effects");o[t]=e,a.push(t),i.push(t)}}void 0!==r.gridStyles&&r.gridStyles.length>0&&Logger.log("Tokens Studio export: grid styles skipped (no Tokens Studio representation)")}s.libraryRefsSkipped>0&&Logger.log("Tokens Studio export: skipped "+s.libraryRefsSkipped+" library-alias token(s) with no resolvable local value"),s.imagePaintsSkipped>0&&Logger.log("Tokens Studio export: skipped "+s.imagePaintsSkipped+" image paint(s) in color styles (no Tokens Studio representation)"),s.blurEffectsSkipped>0&&Logger.log("Tokens Studio export: skipped "+s.blurEffectsSkipped+" blur effect(s) in effect styles (boxShadow tokens carry shadows only)");const l=[];for(let e=0;e{const a=o<0?o+1:o>1?o-1:o;return a<1/6?e+6*(t-e)*a:a<.5?t:a<2/3?e+(t-e)*(2/3-a)*6:e},r=n<.5?n*(1+s):n+s-n*s,i=2*n-r;return{r:hue2rgb(i,r,a+1/3),g:hue2rgb(i,r,a),b:hue2rgb(i,r,a-1/3),a:null!==(o=e.a)&&void 0!==o?o:1}},fromHsb(e){var t;const o=e.h/360,a=e.s/100,s=e.b/100,n=Math.floor(6*o),r=6*o-n,i=s*(1-a),l=s*(1-r*a),c=s*(1-(1-r)*a),g=[[s,c,i],[l,s,i],[i,s,c],[i,l,s],[c,i,s],[s,i,l]],[d,f,u]=g[n%6];return{r:d,g:f,b:u,a:null!==(t=e.a)&&void 0!==t?t:1}},parse(e){var t;if("object"==typeof e&&null!==e&&"hex"in e&&"rgb"in e)return this.fromHex(e.hex);if("object"==typeof e&&null!==e&&"r"in e&&"g"in e&&"b"in e){const o=e;return o.r<=1&&o.g<=1&&o.b<=1?{r:o.r,g:o.g,b:o.b,a:null!==(t=o.a)&&void 0!==t?t:1}:this.fromRgb255(o)}return"object"==typeof e&&null!==e&&"h"in e&&"s"in e&&"l"in e?this.fromHsl(e):"object"==typeof e&&null!==e&&"h"in e&&"s"in e&&"b"in e?this.fromHsb(e):"string"==typeof e?e.startsWith("rgb")||e.startsWith("hsl")?this.fromCss(e):this.fromHex(e):{r:0,g:0,b:0,a:1}}};class VariableCache{constructor(){this.collectionMap=new Map,this.variableMap=new Map,this.libraryVariableMap=new Map,this.libraryCollectionNames=new Set,this.initialized=!1,this.libraryIndexed=!1}async ensureReady(){this.initialized||(await this.rebuildLocal(),this.initialized=!0)}async initialize(){await this.ensureReady()}async rebuild(){await this.rebuildLocal(),await this.ensureLibraryIndex(),this.initialized=!0}async rebuildLocal(){this.clearLocal();const e=await figma.variables.getLocalVariableCollectionsAsync();for(let t=0;t{try{const o=await figma.variables.importVariableByKeyAsync(e.key);o&&this.libraryVariableMap.set(`${t}/${o.name}`,o)}catch(e){}})}catch(e){Logger.log(` ⚠️ Could not index library collection "${o.name}": ${e}`)}}this.libraryCollectionNames.size>0&&Logger.log(`📚 Indexed ${this.libraryVariableMap.size} library variables from ${this.libraryCollectionNames.size} connected libraries`)}catch(e){Logger.log(`⚠️ Could not access team library: ${e}`)}}getCollection(e){return this.collectionMap.get(e)}getVariable(e){return this.variableMap.get(e)||this.libraryVariableMap.get(e)}setVariable(e,t){this.variableMap.set(e,t)}setCollection(e,t){this.collectionMap.set(e,t)}removeCollection(e){this.collectionMap.delete(e);const t=[];for(const o of this.variableMap.keys())o.startsWith(`${e}/`)&&t.push(o);for(const e of t)this.variableMap.delete(e)}isCollectionAvailable(e){return this.collectionMap.has(e)||this.libraryCollectionNames.has(e)}getLibraryCollectionNames(){return Array.from(this.libraryCollectionNames)}get size(){return this.variableMap.size}get collections(){return this.collectionMap.values()}getVariableKeys(){return Array.from(this.variableMap.keys())}}const variableCache=new VariableCache;function isExportVariableValue(e){return"object"==typeof e&&null!==e&&"$type"in e}function isVariableAlias(e){return"object"==typeof e&&null!==e&&"VARIABLE_ALIAS"===e.type}const TypeMapper={toExportType(e){var t;return null!==(t={COLOR:"color",FLOAT:"float",STRING:"string",BOOLEAN:"boolean"}[e])&&void 0!==t?t:"string"},toFigmaType(e){var t;return null!==(t={color:"COLOR",float:"FLOAT",string:"STRING",boolean:"BOOLEAN"}[e])&&void 0!==t?t:"STRING"},scopesToArray:e=>0===e.length||e.includes("ALL_SCOPES")?["ALL_SCOPES"]:[...e],arrayToScopes:e=>e.includes("ALL_SCOPES")?["ALL_SCOPES"]:e};async function getVariableBindingInfo(e,t){if(!(null==e?void 0:e[t]))return{};const o=e[t];if(!o)return{};const a=await figma.variables.getVariableByIdAsync(o.id);if(!a)return{id:o.id};const s=await figma.variables.getVariableCollectionByIdAsync(a.variableCollectionId);return{id:o.id,name:a.name,collection:null==s?void 0:s.name}}async function extractBindings(e,t){if(!e)return;const o={};for(const a of t){const t=await getVariableBindingInfo(e,a);t.name&&(o[a]=t)}return Object.keys(o).length>0?o:void 0}function flattenVariables(e,t){const o=[];for(const a of Object.keys(e)){const s=e[a],n=t?`${t}/${a}`:a;isExportVariableValue(s)?o.push({path:n,value:s}):o.push(...flattenVariables(s,n))}return o}function getValueAtPath(e,t){const o=t.split("/");let a=e;for(const e of o){if("object"!=typeof a||null===a)return null;if(isExportVariableValue(a))return null;a=a[e]}return isExportVariableValue(a)?a:null}const ColorStyleProcessor={async export(e){var t;const o=null!==(t=null==e?void 0:e.includeImages)&&void 0!==t&&t,a=[],s=await figma.getLocalPaintStylesAsync();return await runSequentialAsync(s,20,async function(e){var t,s,n;if(0===e.paints.length)return;const r=[];let i,l,c;for(const a of e.paints)if("SOLID"===a.type){const e=a.color;let o=null!==(t=a.opacity)&&void 0!==t?t:1;void 0!==e.a&&e.a<1&&1===o&&(o=e.a);const s={r:a.color.r,g:a.color.g,b:a.color.b,a:o},n={type:"SOLID",color:ColorConverter.toAllFormats(s),opacity:MathUtils.round2(o)};r.push(n),i||(i=n.color,l=n.opacity,c=await extractBindings(a.boundVariables,["color"]))}else if("GRADIENT_LINEAR"===a.type||"GRADIENT_RADIAL"===a.type||"GRADIENT_ANGULAR"===a.type||"GRADIENT_DIAMOND"===a.type){const e=a.gradientStops.map(e=>{var t;return{position:MathUtils.round2(e.position),color:ColorConverter.toAllFormats({r:e.color.r,g:e.color.g,b:e.color.b,a:null!==(t=e.color.a)&&void 0!==t?t:1})}}),t=Object.assign(Object.assign({type:a.type,gradientStops:e},a.gradientTransform&&{gradientTransform:a.gradientTransform}),{opacity:MathUtils.round2(null!==(s=a.opacity)&&void 0!==s?s:1)});r.push(t)}else if("IMAGE"===a.type){const t=Object.assign(Object.assign(Object.assign(Object.assign({type:"IMAGE",scaleMode:a.scaleMode},a.imageHash&&{imageHash:a.imageHash}),{opacity:MathUtils.round2(null!==(n=a.opacity)&&void 0!==n?n:1)}),void 0!==a.rotation&&{rotation:a.rotation}),a.filters&&{filters:Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},void 0!==a.filters.exposure&&{exposure:a.filters.exposure}),void 0!==a.filters.contrast&&{contrast:a.filters.contrast}),void 0!==a.filters.saturation&&{saturation:a.filters.saturation}),void 0!==a.filters.temperature&&{temperature:a.filters.temperature}),void 0!==a.filters.tint&&{tint:a.filters.tint}),void 0!==a.filters.highlights&&{highlights:a.filters.highlights}),void 0!==a.filters.shadows&&{shadows:a.filters.shadows})});if(o&&a.imageHash)try{const e=figma.getImageByHash(a.imageHash);if(e){const o=await e.getBytesAsync();if(o){const e=figma.base64Encode(o);t.imageBase64=e}}}catch(t){Logger.log(`⚠️ Could not export image data for style "${e.name}": ${t}`)}r.push(t)}if(0===r.length)return;const g=Object.assign(Object.assign(Object.assign(Object.assign({name:e.name,paints:r},i&&{color:i}),void 0!==l&&{opacity:l}),e.description&&{description:e.description}),c&&Object.keys(c).length>0&&{boundVariables:c});a.push(g)}),a},async importStyles(e,t){let o=0,a=0;const s=new Map;for(const e of await figma.getLocalPaintStylesAsync())s.set(e.name,e);return await runSequentialAsync(e,20,async function(e){var n,r,i,l;let c;s.has(e.name)?(c=s.get(e.name),a++):(c=figma.createPaintStyle(),c.name=e.name,o++),e.description&&(c.description=e.description);const g=[];if(e.paints&&e.paints.length>0){for(const o of e.paints)if("SOLID"===o.type){const a=ColorParser.parse(o.color);let s=null!==(n=o.opacity)&&void 0!==n?n:1;a.a<1&&void 0===o.opacity&&(s=MathUtils.round2(a.a));let r={type:"SOLID",color:{r:a.r,g:a.g,b:a.b},opacity:MathUtils.round2(s)};if(e.boundVariables&&0===g.length)for(const[o,a]of Object.entries(e.boundVariables))if(a.name&&a.collection){const e=t.getVariable(`${a.collection}/${a.name}`);if(e)try{r=figma.variables.setBoundVariableForPaint(r,o,e)}catch(e){Logger.log(`⚠️ Could not bind ${o}: ${e}`)}}g.push(r)}else if("GRADIENT_LINEAR"===o.type||"GRADIENT_RADIAL"===o.type||"GRADIENT_ANGULAR"===o.type||"GRADIENT_DIAMOND"===o.type){const e=o.gradientStops.map(e=>{const t=ColorParser.parse(e.color);return{position:e.position,color:{r:t.r,g:t.g,b:t.b,a:t.a}}}),t=o.gradientTransform?[[o.gradientTransform[0][0],o.gradientTransform[0][1],o.gradientTransform[0][2]],[o.gradientTransform[1][0],o.gradientTransform[1][1],o.gradientTransform[1][2]]]:[[1,0,0],[0,1,0]],a={type:o.type,gradientStops:e,gradientTransform:t,opacity:null!==(r=o.opacity)&&void 0!==r?r:1};g.push(a)}else if("IMAGE"===o.type){let t=null;if(o.imageBase64)try{const a=figma.base64Decode(o.imageBase64);t=figma.createImage(a).hash,Logger.log(`✅ Created image from base64 data for style "${e.name}"`)}catch(t){Logger.log(`⚠️ Could not import image from base64 for style "${e.name}": ${t}`)}if(!t&&o.imageHash){figma.getImageByHash(o.imageHash)?(t=o.imageHash,Logger.log(`✅ Found existing image with hash for style "${e.name}"`)):Logger.log(`⚠️ Image hash not found in file for style "${e.name}", skipping image paint (imageHash cannot be null)`)}if(t){const e=Object.assign(Object.assign({type:"IMAGE",scaleMode:o.scaleMode,imageHash:t,opacity:null!==(i=o.opacity)&&void 0!==i?i:1},void 0!==o.rotation&&{rotation:o.rotation}),o.filters&&{filters:o.filters});g.push(e)}}}else if(e.color){const o=ColorParser.parse(e.color);let a=null!==(l=e.opacity)&&void 0!==l?l:1;o.a<1&&void 0===e.opacity&&(a=MathUtils.round2(o.a));let s={type:"SOLID",color:{r:o.r,g:o.g,b:o.b},opacity:MathUtils.round2(a)};if(e.boundVariables)for(const[o,a]of Object.entries(e.boundVariables))if(a.name&&a.collection){const e=t.getVariable(`${a.collection}/${a.name}`);if(e)try{s=figma.variables.setBoundVariableForPaint(s,o,e)}catch(e){Logger.log(`⚠️ Could not bind ${o}: ${e}`)}}g.push(s)}g.length>0&&(c.paints=g)}),{created:o,updated:a}}},TextStyleProcessor={async export(e){const t=[],o=await figma.getLocalTextStylesAsync();return await runSequentialAsync(o,20,async function(e){const o=Object.assign(Object.assign({name:e.name,fontFamily:e.fontName.family,fontStyle:e.fontName.style,fontSize:e.fontSize,lineHeight:e.lineHeight,letterSpacing:e.letterSpacing,textCase:e.textCase,textDecoration:e.textDecoration},e.description&&{description:e.description}),{boundVariables:await extractBindings(e.boundVariables,["fontSize","lineHeight","letterSpacing","paragraphSpacing","paragraphIndent"])});t.push(o)}),t},async importStyles(e,t){let o=0,a=0;const s=new Map;for(const e of await figma.getLocalTextStylesAsync())s.set(e.name,e);return await runSequentialAsync(e,20,async function(e){let n;s.has(e.name)?(n=s.get(e.name),a++):(n=figma.createTextStyle(),n.name=e.name,o++),e.description&&(n.description=e.description);try{if(await figma.loadFontAsync({family:e.fontFamily,style:e.fontStyle}),n.fontName={family:e.fontFamily,style:e.fontStyle},n.fontSize=e.fontSize,n.lineHeight=e.lineHeight,n.letterSpacing=e.letterSpacing,e.textCase&&(n.textCase=e.textCase),e.textDecoration&&(n.textDecoration=e.textDecoration),e.boundVariables)for(const[o,a]of Object.entries(e.boundVariables))if(a.name&&a.collection){const e=t.getVariable(`${a.collection}/${a.name}`);if(e)try{n.setBoundVariable(o,e)}catch(e){}}}catch(t){Logger.log(`⚠️ Could not load font for ${e.name}: ${t}`)}}),{created:o,updated:a}}},EffectStyleProcessor={async export(e){const t=[],o=await figma.getLocalEffectStylesAsync();return await runSequentialAsync(o,20,async function(e){const o=[];for(const t of e.effects){const e=Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({type:t.type,visible:t.visible},"radius"in t&&{radius:t.radius}),"spread"in t&&{spread:t.spread}),"offset"in t&&{offset:t.offset}),"color"in t&&{color:ColorConverter.toAllFormats(t.color)}),"blendMode"in t&&{blendMode:t.blendMode}),"showShadowBehindNode"in t&&{showShadowBehindNode:t.showShadowBehindNode}),{boundVariables:await extractBindings(t.boundVariables,["color","radius","spread","offsetX","offsetY"])});o.push(e)}const a=Object.assign(Object.assign({name:e.name},e.description&&{description:e.description}),{effects:o});t.push(a)}),t},async importStyles(e,t){let o=0,a=0;const s=new Map;for(const e of await figma.getLocalEffectStylesAsync())s.set(e.name,e);return await runSequentialAsync(e,20,async function(e){let n;s.has(e.name)?(n=s.get(e.name),a++):(n=figma.createEffectStyle(),n.name=e.name,o++),e.description&&(n.description=e.description);const r=e.effects.map(e=>{var t;return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({type:e.type,visible:null===(t=e.visible)||void 0===t||t},void 0!==e.radius&&{radius:e.radius}),void 0!==e.spread&&{spread:e.spread}),void 0!==e.offset&&{offset:e.offset}),void 0!==e.color&&{color:(()=>{const t=ColorParser.parse(e.color);return{r:t.r,g:t.g,b:t.b,a:MathUtils.round2(t.a)}})()}),void 0!==e.blendMode&&{blendMode:e.blendMode}),void 0!==e.showShadowBehindNode&&{showShadowBehindNode:e.showShadowBehindNode})});n.effects=r;for(let o=0;o{var t,o,a,s,n,r,i;const l=e.color?ColorParser.parse(e.color):{r:1,g:0,b:0,a:.1},c={r:l.r,g:l.g,b:l.b,a:MathUtils.round2(l.a)};if("GRID"===e.pattern)return{pattern:"GRID",sectionSize:null!==(t=e.sectionSize)&&void 0!==t?t:10,visible:!1!==e.visible,color:c};const g=null!==(o=e.alignment)&&void 0!==o?o:"STRETCH",d={pattern:e.pattern,gutterSize:null!==(a=e.gutterSize)&&void 0!==a?a:10,count:null!==(s=e.count)&&void 0!==s?s:5,visible:!1!==e.visible,color:c};if("STRETCH"===g)return Object.assign(Object.assign({},d),{alignment:"STRETCH",offset:null!==(n=e.offset)&&void 0!==n?n:0});if("CENTER"===g)return Object.assign(Object.assign({},d),{alignment:"CENTER",sectionSize:null!==(r=e.sectionSize)&&void 0!==r?r:100});{const t=Object.assign(Object.assign({},d),{alignment:g,offset:null!==(i=e.offset)&&void 0!==i?i:0});return void 0!==e.sectionSize&&(t.sectionSize=e.sectionSize),t}});n.layoutGrids=r;for(let o=0;ot.name===e);a?await checkVariablesDiff(i,a.modeId,o,r,"",t):l=!0}t.modifiedVariables.some(e=>e.collection===r)||t.newVariables.some(e=>e.collection===r)?(t.modifiedCollections.push(r),t.summary.collectionsModified++):(t.unchangedCollections.push(r),t.summary.collectionsUnchanged++)}return t}function countVariablesInCollection(e){let t=0;const o=Object.values(e)[0];return o&&(t=countVarsInNestedObj(o)),t}function countVarsInNestedObj(e){let t=0;for(const o of Object.values(e))isExportVariableValue(o)?t++:t+=countVarsInNestedObj(o);return t}async function checkVariablesDiff(e,t,o,a,s,n){for(const[r,i]of Object.entries(o)){const o=s?`${s}/${r}`:r;if(isExportVariableValue(i)){const e=variableCache.getVariable(`${a}/${o}`);if(e){const s=e.valuesByMode[t],r=i.$value;valuesAreDifferent(s,r)?(n.modifiedVariables.push({collection:a,path:o,oldValue:formatValueForDisplay(s),newValue:formatValueForDisplay(r)}),n.summary.variablesModified++):(n.unchangedVariables++,n.summary.variablesUnchanged++)}else n.newVariables.push({collection:a,path:o}),n.summary.variablesNew++}else await checkVariablesDiff(e,t,i,a,o,n)}}function valuesAreDifferent(e,t){if(void 0===e)return!0;if(isVariableAlias(e))return"string"==typeof t&&t.startsWith("{"),!0;if("object"==typeof e&&null!==e&&"r"in e){if("object"==typeof t&&null!==t&&"hex"in t){return ColorConverter.toAllFormats(e).hex.toLowerCase()!==t.hex.toLowerCase()}return!0}return e!==t}function formatValueForDisplay(e){if(void 0===e)return"undefined";if("object"==typeof e&&null!==e){if("hex"in e)return e.hex;if("r"in e)return ColorConverter.toAllFormats(e).hex;if("id"in e)return"{alias}"}return String(e)}async function computeStylesDiff(e,t){if(e.colorStyles){const o=await figma.getLocalPaintStylesAsync(),a=new Set(o.map(e=>e.name));for(const o of e.colorStyles)a.has(o.name)?(t.modifiedStyles.push({type:"color",name:o.name}),t.summary.stylesModified++):(t.newStyles.push({type:"color",name:o.name}),t.summary.stylesNew++)}if(e.textStyles){const o=await figma.getLocalTextStylesAsync(),a=new Set(o.map(e=>e.name));for(const o of e.textStyles)a.has(o.name)?(t.modifiedStyles.push({type:"text",name:o.name}),t.summary.stylesModified++):(t.newStyles.push({type:"text",name:o.name}),t.summary.stylesNew++)}if(e.effectStyles){const o=await figma.getLocalEffectStylesAsync(),a=new Set(o.map(e=>e.name));for(const o of e.effectStyles)a.has(o.name)?(t.modifiedStyles.push({type:"effect",name:o.name}),t.summary.stylesModified++):(t.newStyles.push({type:"effect",name:o.name}),t.summary.stylesNew++)}if(e.gridStyles){const o=await figma.getLocalGridStylesAsync(),a=new Set(o.map(e=>e.name));for(const o of e.gridStyles)a.has(o.name)?(t.modifiedStyles.push({type:"grid",name:o.name}),t.summary.stylesModified++):(t.newStyles.push({type:"grid",name:o.name}),t.summary.stylesNew++)}}function filterStylesByGroup(e,t){return t?e.filter(function(e){return-1!==t.indexOf(getGroupKey(e.name))}):e}async function exportVariables(e,t,o,a,s="original",n="figma",r,i=!1,l,c){var g,d,f,u,p,m,y,h;Logger.log("📤 Starting export..."),Logger.log(` preserveLibraryRefs: ${o}`),Logger.log(` includeImages: ${a}`),Logger.log(` namingConvention: ${s}`),Logger.log(` exportFormat: ${n}`),Logger.log(` resolveAliases: ${i}`),r&&Logger.log(` selectedModes: ${JSON.stringify(r)}`);try{let o=await figma.variables.getLocalVariableCollectionsAsync();(null==e?void 0:e.length)&&(o=o.filter(t=>e.includes(t.name)),Logger.log(`Filtering to ${o.length} selected collections`));const b=[],v={};let S=0;const C=createProgress("export");let A=0;for(let e=0;eo.includes(e.name)),Logger.log(` Filtering to ${t.length} modes: ${t.map(e=>e.name).join(", ")}`)}const o=NamingConverter.convertCollectionName(e.name,s),a={[o]:Object.assign({modes:{}},o!==e.name&&{$originalName:e.name})};for(const e of t){const t=NamingConverter.convertModeName(e.name,s);a[o].modes[t]={}}await runSequentialAsync(e.variableIds,BATCH.SEQ_EXPORT,async function(n){var r,c;L++;const g=await figma.variables.getVariableByIdAsync(n);if(!g)return;if(l&&l[e.name]&&-1===l[e.name].indexOf(getGroupKey(g.name)))return;S++;const d=g.name.split("/").map(e=>NamingConverter.convert(e,s));for(const e of t){const t=NamingConverter.convertModeName(e.name,s),n=a[o].modes[t],l=g.valuesByMode[e.modeId];let f=n;for(let e=0;eNamingConverter.convert(e,s)).join(".")}}`,p=v,b){const e=t.valuesByMode[Object.keys(t.valuesByMode)[0]];"object"==typeof e&&null!==e&&"r"in e?m=ColorConverter.toAllFormats(e):isVariableAlias(e)||(m=e)}}}else p=""}else p="object"==typeof l&&null!==l&&"r"in l?ColorConverter.toAllFormats(l):l;const S=Object.assign(Object.assign(Object.assign({$scopes:TypeMapper.scopesToArray(g.scopes),$type:TypeMapper.toExportType(g.resolvedType),$value:p},g.description&&{$description:g.description}),y&&h&&{$collectionName:h}),y&&b&&Object.assign({$libraryRef:v},void 0!==m&&{$localValue:m}));f[u]=S}},function(){C.report("export_variables","Exporting variables",L,A)}),b.push(a),"w3c"===n&&(v[o]=W3CConverter.collectionToW3C(o,a[o].modes,s,a[o].$originalName))}let $=null;if(t){if($={},t.colorStyles){C.report("export_styles","Exporting styles",0,0,!0);const e=c?c.color:void 0;$.colorStyles=filterStylesByGroup(await ColorStyleProcessor.export({includeImages:a}),e),e&&0===$.colorStyles.length&&delete $.colorStyles}if(t.textStyles){C.report("export_styles","Exporting styles",0,0,!0);const e=c?c.text:void 0;$.textStyles=filterStylesByGroup(await TextStyleProcessor.export(),e),e&&0===$.textStyles.length&&delete $.textStyles}if(t.effectStyles){C.report("export_styles","Exporting styles",0,0,!0);const e=c?c.effect:void 0;$.effectStyles=filterStylesByGroup(await EffectStyleProcessor.export(),e),e&&0===$.effectStyles.length&&delete $.effectStyles}if(t.gridStyles){C.report("export_styles","Exporting styles",0,0,!0);const e=c?c.grid:void 0;$.gridStyles=filterStylesByGroup(await GridStyleProcessor.export(),e),e&&0===$.gridStyles.length&&delete $.gridStyles}Object.keys($).length>0?b.push({_styles:$}):$=null}const w={collections:o.length,variables:S,styles:$?{color:null!==(d=null===(g=$.colorStyles)||void 0===g?void 0:g.length)&&void 0!==d?d:0,text:null!==(u=null===(f=$.textStyles)||void 0===f?void 0:f.length)&&void 0!==u?u:0,effect:null!==(m=null===(p=$.effectStyles)||void 0===p?void 0:p.length)&&void 0!==m?m:0,grid:null!==(h=null===(y=$.gridStyles)||void 0===y?void 0:y.length)&&void 0!==h?h:0}:null};let O;"w3c"===n?($&&Object.keys($).length>0&&(v.$extensions={"com.figma":{styles:$}}),O=JSON.stringify(v,null,2),Logger.log(`✅ Export complete (W3C format): ${w.collections} collections, ${w.variables} variables`)):"tokens-studio"===n?(O=JSON.stringify(convertToTokensStudio(b),null,2),Logger.log(`✅ Export complete (Tokens Studio format): ${w.collections} collections, ${w.variables} variables`)):(O=JSON.stringify(b,null,2),Logger.log(`✅ Export complete: ${w.collections} collections, ${w.variables} variables`)),await sendExportInChunks(O,w,n)}catch(e){if(isCancelError(e))return Logger.log("🛑 Export cancelled"),void figma.ui.postMessage({type:"operation_cancelled",operation:"export",phase:"export",rolledBack:!1,message:"Export cancelled. Nothing was changed."});Logger.log(`❌ Export error: ${e}`),Logger.send("error",{message:`Export failed: ${e}`})}}async function sendExportInChunks(e,t,o){const a=e.length,s=BATCH.EXPORT_CHUNK_BYTES,n=Math.max(1,Math.ceil(a/s));let r=0,i=0;for(;i=55296&&o<=56319&&(t+=1)}const o=e.slice(i,t);figma.ui.postMessage({type:"export_chunk",seq:r,total:n,data:o}),r++,i=t,r%BATCH.EXPORT_YIELD_EVERY===0&&i0?a.modes[s[0]]:{},"");b.push({collectionObj:t,flatPaths:n}),v+=n.length;for(let e=0;e0&&Logger.log(` ✅ Aliases: ${L} resolved, ${$} used fallback values`),y&&t.importStyles){if(Logger.log("📦 Importing styles..."),n.report("import_styles","Importing styles",0,0,!0),y.colorStyles){const e=await ColorStyleProcessor.importStyles(y.colorStyles,variableCache);p+=e.created,m+=e.updated}if(y.textStyles){const e=await TextStyleProcessor.importStyles(y.textStyles,variableCache);p+=e.created,m+=e.updated}if(y.effectStyles){const e=await EffectStyleProcessor.importStyles(y.effectStyles,variableCache);p+=e.created,m+=e.updated}if(y.gridStyles){const e=await GridStyleProcessor.importStyles(y.gridStyles,variableCache);p+=e.created,m+=e.updated}}const w={collectionsCreated:g,variablesCreated:d,variablesUpdated:f,variablesSkipped:u,stylesCreated:p,stylesUpdated:m};figma.commitUndo(),Logger.log("✅ Import complete!"),Logger.send("import_complete",{stats:w,snapshot:s})}catch(e){const t=isCancelError(e);if(t&&!r)return Logger.log("🛑 Import cancelled before any changes were made"),void figma.ui.postMessage({type:"operation_cancelled",operation:"import",phase:"snapshot",rolledBack:!1,message:"Import cancelled. No changes were made."});const o=e instanceof Error?e.message:String(e);if(t?Logger.log("🛑 Import cancelled after mutation started — rolling back..."):Logger.log(`❌ Import error: ${o}`),s){Logger.log("🔄 Attempting automatic rollback to pre-import state..."),Logger.send("import_rolling_back",{error:o});try{await restoreFromSnapshot(s),Logger.log("✅ Automatic rollback successful - file restored to pre-import state"),t?figma.ui.postMessage({type:"operation_cancelled",operation:"import",phase:"rollback",rolledBack:!0,message:"Import cancelled — your file was restored to its previous state."}):Logger.send("import_rollback_complete",{error:o,message:"Import failed but your file has been automatically restored to its previous state."})}catch(e){const t=e instanceof Error?e.message:String(e);Logger.log(`❌ Rollback failed: ${t}`),Logger.send("import_rollback_failed",{error:o,rollbackError:t,message:"Import failed and automatic rollback also failed. Please use Ctrl+Z (Cmd+Z) to undo manually."})}}else Logger.send("error",{message:`Import failed: ${o}. Use Ctrl+Z (Cmd+Z) to undo changes.`})}}function setRawValue(e,t,o){try{if("color"===o.$type){const a=ColorParser.parse(o.$value),s=a.a<1?Object.assign(Object.assign({},a),{a:MathUtils.round2(a.a)}):a;e.setValueForMode(t,s)}else e.setValueForMode(t,o.$value)}catch(e){console.error(`Could not set value: ${e}`)}}function getGroupKey(e){const t=e.indexOf("/");return-1===t?"":e.substring(0,t)}function summarizeGroups(e){const t=Object.create(null),o=[];for(let a=0;a{Logger.log(` ${t+1}. "${e.name}" (id: ${e.id})`)});const t=new Set;let o=0,a=0,s=0;const n=new Map,r=[];for(let i=0;ie.name),variableCount:l.variableIds.length,types:c,groups:summarizeGroups(g)})}r.sort((e,t)=>e.name.localeCompare(t.name));const i=await figma.getLocalPaintStylesAsync(),l=await figma.getLocalTextStylesAsync(),c=await figma.getLocalEffectStylesAsync(),g=await figma.getLocalGridStylesAsync();let d=0;const f=[];for(let e=0;e({family:e,styles:Array.from(t)}));let S=0;for(const e of i)e.boundVariables&&Object.keys(e.boundVariables).length>0&&S++;Logger.send("collections",{collections:r,styles:u,styleGroups:h,libraryDependencies:Array.from(t),fontsUsed:v,stats:{totalVariables:r.reduce((e,t)=>e+t.variableCount,0),totalAliases:o,localAliases:a,libraryAliases:s,styleBindings:S}})}async function clearVariables(e=!1){Logger.log("🗑️ Clearing all variables...");const t=e?null:createProgress("clear");let o=0,a=0,s=!1;try{const n=await figma.variables.getLocalVariableCollectionsAsync(),r=[];let i=0;for(let e=0;e0&&(figma.commitUndo(),s=!0);let l=0;for(let e=0;e0&&(figma.commitUndo(),a=!0);let c=0;const removeStyle=function(e){e.remove(),o++},onBatch=function(e){t&&t.report("clear","Deleting styles",c+e,l)};await runBatched(s,BATCH.SYNC_LIGHT,removeStyle,onBatch),c+=s.length,await runBatched(n,BATCH.SYNC_LIGHT,removeStyle,onBatch),c+=n.length,await runBatched(r,BATCH.SYNC_LIGHT,removeStyle,onBatch),c+=r.length,await runBatched(i,BATCH.SYNC_LIGHT,removeStyle,onBatch),c+=i.length,!e&&a&&figma.commitUndo(),Logger.log(`✅ Cleared ${o} styles`),e||Logger.send("clear_complete",{message:`${o} styles`})}catch(t){if(isCancelError(t)){if(e)throw t;return Logger.log(`🛑 Clear styles cancelled after ${o} styles`),void figma.ui.postMessage({type:"operation_cancelled",operation:"clear",phase:"clear",rolledBack:!1,partial:{collectionsDeleted:0,variablesDeleted:o},message:`Clear cancelled — ${o} styles were already deleted. Remaining items were not touched. Use Cmd+Z to restore deleted items.`})}if(Logger.log(`❌ Clear styles error: ${t}`),e)throw t;Logger.send("error",{message:`Failed to clear styles: ${t}`})}}async function clearAll(e=!1){if(Logger.log("🗑️ Clearing everything..."),e)return await clearVariables(!0),void await clearStyles(!0);let t=!1;try{figma.commitUndo(),t=!0,await clearVariables(!0),await clearStyles(!0),figma.commitUndo(),Logger.send("clear_complete",{message:"all variables and styles"})}catch(e){if(t&&figma.commitUndo(),isCancelError(e))return Logger.log("🛑 Clear all cancelled"),void figma.ui.postMessage({type:"operation_cancelled",operation:"clear",phase:"clear",rolledBack:!1,partial:{collectionsDeleted:0,variablesDeleted:0},message:"Clear cancelled — some items may already have been deleted. Use Cmd+Z to restore deleted items."});Logger.log(`❌ Clear all error: ${e}`),Logger.send("error",{message:`Failed to clear: ${e}`})}}async function createUndoSnapshot(e){var t;Logger.log("📸 Creating snapshot of current file state...");const o=await figma.variables.getLocalVariableCollectionsAsync(),a=[],s=new Map;let n=0;for(let e=0;e({id:e.modeId,name:e.name})),variables:[]},i=await runBatchedAsync(t.variableIds,BATCH.ASYNC_LOOKUP,function(e){return figma.variables.getVariableByIdAsync(e)},function(t){e&&e.report("snapshot","Preparing snapshot (undo safety)",r+t,n)});r+=t.variableIds.length;for(let e=0;e0){t.renameMode(t.modes[0].modeId,e.modes[0].name);for(let o=1;o0&&(s.scopes=a.scopes);for(const t of e.modes){const r=o[t.name],i=a.values[t.name];if(i)if(i.isAlias&&i.aliasName)n.push({variable:s,modeId:r,aliasPath:i.aliasName,aliasCollection:i.aliasCollection||e.name});else if(void 0!==i.value){let e;e="COLOR"===a.type&&"string"==typeof i.value?ColorParser.parse(i.value):i.value,s.setValueForMode(r,e)}}}},function(e,o){t.report("undo_restore","Restoring variables",e,o)}),Logger.log(` Resolving ${n.length} aliases...`),await runBatched(n,BATCH.SYNC_LIGHT,function(e){const t=`${e.aliasCollection}/${e.aliasPath}`,o=variableCache.getVariable(t);o&&e.variable.setValueForMode(e.modeId,{type:"VARIABLE_ALIAS",id:o.id})},function(e,o){t.report("undo_aliases","Restoring aliases",e,o)}),Logger.log(" Restoring styles..."),t.report("undo_styles","Restoring styles",0,0,!0),a.colorStyles&&a.colorStyles.length>0&&await ColorStyleProcessor.importStyles(a.colorStyles,variableCache),a.textStyles&&a.textStyles.length>0&&await TextStyleProcessor.importStyles(a.textStyles,variableCache),a.effectStyles&&a.effectStyles.length>0&&await EffectStyleProcessor.importStyles(a.effectStyles,variableCache),a.gridStyles&&a.gridStyles.length>0&&await GridStyleProcessor.importStyles(a.gridStyles,variableCache),Logger.log("✅ File restored from snapshot")}figma.ui.onmessage=async e=>{switch(e.type){case"cancel_operation":null!==currentOperation.type&&(currentOperation.cancellable?(currentOperation.cancelRequested=!0,Logger.log("🛑 Cancellation requested — finishing current batch…")):Logger.log("⚠️ Rollback in progress — cannot cancel"));break;case"resize_ui":"advanced"===e.mode?figma.ui.resize(UI_SIZE.advanced.width,UI_SIZE.advanced.height):figma.ui.resize(UI_SIZE.simple.width,UI_SIZE.simple.height);break;case"export":await withOperation("export",function(){return exportVariables(e.collections,e.styleOptions,e.preserveLibraryRefs,e.includeImages,e.namingConvention||"original",e.exportFormat||"figma",e.selectedModes,e.resolveAliases||!1,e.selectedGroups,e.selectedStyleGroups)});break;case"import":await withOperation("import",function(){return importVariables(e.data,e.options)});break;case"validate_import":try{const t=JSON.parse(e.data),o=e.plan,a=await validateImportAgainstPlan(t,o);Logger.send("validation_result",a)}catch(e){Logger.send("validation_result",{errors:[`Invalid JSON: ${e instanceof Error?e.message:"Parse error"}`],canImport:!1})}break;case"compute_import_diff":try{const t=JSON.parse(e.data),o=await computeImportDiff(t);Logger.send("import_diff_result",o)}catch(e){Logger.send("import_diff_result",{error:`Failed to compute diff: ${e instanceof Error?e.message:"Unknown error"}`})}break;case"detect_plan":const t=await detectCurrentPlan();Logger.send("plan_detected",t);break;case"clear_variables":await withOperation("clear",function(){return clearVariables(!1)});break;case"clear_styles":await withOperation("clear",function(){return clearStyles(!1)});break;case"clear_all":await withOperation("clear",function(){return clearAll(!1)});break;case"get_collections":await withOperation("scan",getCollections);break;case"check_libraries":try{const t=e.collections;await variableCache.rebuild();const o=[],a=[];for(const e of t)variableCache.isCollectionAvailable(e)?o.push(e):a.push(e);Logger.log(`📚 Library check: ${o.length} available, ${a.length} missing`),o.length>0&&Logger.log(` ✅ Available: ${o.join(", ")}`),a.length>0&&Logger.log(` ❌ Missing: ${a.join(", ")}`),Logger.send("library_check_result",{allAvailable:0===a.length,availableCollections:o,missingCollections:a,requiredCollections:t})}catch(t){Logger.send("library_check_result",{allAvailable:!1,availableCollections:[],missingCollections:e.collections||[],requiredCollections:e.collections||[],error:t instanceof Error?t.message:"Library check failed"})}break;case"check_fonts":try{const t=e.fonts,o=[],a=[],s=await runBatchedAsync(t,BATCH.ASYNC_FONT,function(e){return figma.loadFontAsync({family:e.family,style:e.style}).then(function(){return{font:e,available:!0}}).catch(function(){return{font:e,available:!1}})});for(let e=0;e0&&n>=r)&&l-oo&&(o=t.modes.length);return t=o>20?"enterprise":o>10?"organization":"professional",Object.assign({plan:t},PLAN_LIMITS[t])}async function validateImportAgainstPlan(e,t){const o=t?Object.assign({plan:t},PLAN_LIMITS[t]):await detectCurrentPlan(),a=await figma.variables.getLocalVariableCollectionsAsync(),s=a.reduce((e,t)=>Math.max(e,t.modes.length),0),n=(await figma.variables.getLocalVariablesAsync()).length,r=[];for(const t of e)"_styles"in t||r.push(t);let i=0,l=0;const c=[];for(const e of r){const t=Object.keys(e)[0],a=e[t];if(!a||!a.modes)continue;const s=Object.keys(a.modes).length;s>i&&(i=s),s>o.maxModesPerCollection&&c.push(`"${t}" (${s} modes, limit: ${o.maxModesPerCollection===1/0?"∞":o.maxModesPerCollection})`);const n=Object.values(a.modes)[0];n&&(l+=countNestedVariables(n))}const g=[],d=[];c.length;for(const e of r){const t=Object.keys(e)[0],o=e[t];if(!o||!o.modes)continue;const a=Object.values(o.modes)[0],s=a?countNestedVariables(a):0;s>5e3&&d.push(`Collection "${t}" has ${s} variables, exceeds limit of 5000`)}l>1e3&&g.push(`Large import: ${l} variables. This may take a moment.`),r.length>10&&g.push(`Importing ${r.length} collections. Consider importing in batches.`);const f=new Set;let u=0;const p=new Set,m=[],y=new Map;for(const e of r){const t=Object.keys(e)[0],o=e[t];if(!o||!o.modes)continue;const a=new Set;for(const e of Object.keys(o.modes)){const t=flattenVariables(o.modes[e],"");for(const{path:e}of t)a.add(e)}y.set(t,a);const s=o.$originalName;s&&s!==t&&y.set(s,a)}for(const e of r){const t=e[Object.keys(e)[0]];if(t&&t.modes)for(const e of Object.keys(t.modes)){const o=flattenVariables(t.modes[e],"");for(const{value:e}of o)if(e.$libraryRef&&e.$collectionName&&(f.add(e.$collectionName),u++,"string"==typeof e.$value&&e.$value.startsWith("{"))){const t=e.$value.slice(1,-1).replace(/\./g,"/"),o=e.$collectionName+"\0"+t;if(!p.has(o)){p.add(o);const a=y.get(e.$collectionName);m.push({collection:e.$collectionName,path:t,selfSatisfied:!!a&&a.has(t)})}}}}const h=[];let b=0;for(const t of e)if("_styles"in t){const e=t._styles;if(e.textStyles)for(const t of e.textStyles){b++;const e=`${t.fontFamily}|${t.fontStyle}`;h.some(t=>`${t.family}|${t.style}`===e)||h.push({family:t.fontFamily,style:t.fontStyle})}}return Object.assign(Object.assign({currentPlan:o,existing:{collections:a.length,maxModesInAnyCollection:s,totalVariables:n},importing:{collections:r.length,maxModesInAnyCollection:i,totalVariables:l,collectionsExceedingModeLimit:c},warnings:g,errors:d,canImport:0===d.length},f.size>0&&{libraryDependencies:{variableCount:u,collections:Array.from(f),refs:m}}),h.length>0&&{fontDependencies:{styleCount:b,fonts:h}})}function countNestedVariables(e,t=0){for(const[,o]of Object.entries(e))o&&"object"==typeof o&&("$type"in o&&"$value"in o?t++:t=countNestedVariables(o,t));return t}const MathUtils={round2:e=>Math.round(100*e)/100,clamp:(e,t,o)=>Math.max(t,Math.min(o,e)),toHexByte:e=>Math.round(255*e).toString(16).padStart(2,"0"),fromHexByte:e=>parseInt(e,16)/255};function calculateHue(e,t,o,a,s){if(a===s)return 0;const n=a-s;let r=0;switch(a){case e:r=((t-o)/n+(t.5?e/(2-s-n):e/(s+n)}const l={h:calculateHue(t,o,a,s,n),s:Math.round(100*i),l:Math.round(100*r)},c=e.a;return void 0!==c&&c<1?Object.assign(Object.assign({},l),{a:MathUtils.round2(c)}):l},toHsb(e){const{r:t,g:o,b:a}=e,s=Math.max(t,o,a),n=Math.min(t,o,a),r=0===s?0:(s-n)/s,i={h:calculateHue(t,o,a,s,n),s:Math.round(100*r),b:Math.round(100*s)},l=e.a;return void 0!==l&&l<1?Object.assign(Object.assign({},i),{a:MathUtils.round2(l)}):i},toAllFormats(e){return{hex:this.toHex(e),rgb:this.toRgb255(e),css:this.toCss(e),hsl:this.toHsl(e),hsb:this.toHsb(e)}}},NamingConverter={convert(e,t){if("original"===t)return e;const o=e.replace(/([a-z])([A-Z])/g,"$1 $2").split(/[\s\/\-_]+/).filter(e=>e.length>0).map(e=>e.toLowerCase());if(0===o.length)return e;switch(t){case"camelCase":return o[0]+o.slice(1).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("");case"kebab-case":return o.join("-");case"snake_case":return o.join("_");default:return e}},convertPath(e,t){return"original"===t?e:e.split("/").map(e=>this.convert(e,t)).join("/")},convertCollectionName(e,t){return this.convert(e,t)},convertModeName(e,t){return this.convert(e,t)},addOriginalName(e,t){if("original"===t)return{converted:e};const o=this.convert(e,t);return o===e?{converted:e}:{converted:o,original:e}}};async function resolveAliasValue(e,t,o=10){if(o<=0)return Logger.log(`⚠️ Max alias resolution depth reached for ${e.name}`),"";let a=e.valuesByMode[t];if(void 0===a){const t=Object.keys(e.valuesByMode);t.length>0&&(a=e.valuesByMode[t[0]])}if(void 0===a)return"";if(isVariableAlias(a)){const e=await figma.variables.getVariableByIdAsync(a.id);return e?resolveAliasValue(e,t,o-1):""}return a}function resolveViaConsumer(e,t,o){let a=null;try{a=figma.createFrame(),a.name="__vse-alias-resolver",a.visible=!1,a.resize(1,1),a.setExplicitVariableModeForCollection(t,o);const s=e.resolveForConsumer(a);return s?s.value:void 0}catch(t){return void Logger.log(`⚠️ Consumer-resolve failed for ${e.name}: ${t instanceof Error?t.message:"unknown error"}`)}finally{a&&a.remove()}}const W3C_TYPE_MAP={color:"color",float:"number",string:"string",boolean:"boolean"},W3CConverter={colorToW3C:e=>e.hex,typeToW3C:e=>W3C_TYPE_MAP[e]||"string",valueToW3C(e,t=!1){const o={$value:"",$type:this.typeToW3C(e.$type)};return t&&"string"==typeof e.$value&&e.$value.startsWith("{")?o.$value=e.$value:"color"===e.$type&&"object"==typeof e.$value?o.$value=e.$value.hex:o.$value=e.$value,e.$description&&(o.$description=e.$description),e.$scopes&&e.$scopes.length>0&&!e.$scopes.includes("ALL_SCOPES")&&(o.$extensions={"com.figma":{scopes:e.$scopes}}),o},collectionToW3C(e,t,o,a){const s={};a&&a!==e&&(s.$description=`Figma collection: ${a}`);const n=Object.keys(t);if(1===n.length)this.addTokensToGroup(s,t[n[0]],o);else for(const e of n){const a=NamingConverter.convertModeName(e,o);s[a]={},this.addTokensToGroup(s[a],t[e],o)}return s},addTokensToGroup(e,t,o){for(const[a,s]of Object.entries(t)){const t=NamingConverter.convert(a,o);if(isExportVariableValue(s)){const o="string"==typeof s.$value&&s.$value.startsWith("{");e[t]=this.valueToW3C(s,o)}else e[t]={},this.addTokensToGroup(e[t],s,o)}},parseW3CToken(e){var t,o;const a=this.w3cTypeToFigma(e.$type),s=(null===(o=null===(t=e.$extensions)||void 0===t?void 0:t["com.figma"])||void 0===o?void 0:o.scopes)||["ALL_SCOPES"];let n;if("color"===a&&"string"==typeof e.$value){const t=ColorParser.parse(e.$value);n=ColorConverter.toAllFormats(t)}else n="string"==typeof e.$value||"number"==typeof e.$value||"boolean"==typeof e.$value?e.$value:JSON.stringify(e.$value);return e.$description?{$type:a,$value:n,$scopes:s,$description:e.$description}:{$type:a,$value:n,$scopes:s}},w3cTypeToFigma:e=>({color:"color",number:"float",dimension:"float",string:"string",boolean:"boolean",fontFamily:"string",fontWeight:"float",duration:"string",cubicBezier:"string"}[e]||"string"),isW3CFormat(e){if("object"!=typeof e||null===e)return!1;const t=e;for(const e of Object.keys(t)){const o=t[e];if("object"==typeof o&&null!==o){if("$value"in o&&"$type"in o)return!0;for(const e of Object.keys(o)){const t=o[e];if("object"==typeof t&&null!==t&&"$value"in t)return!0}}}return Array.isArray(e),!1},w3cToFigmaFormat(e){const t=[];for(const[o,a]of Object.entries(e)){if(o.startsWith("$"))continue;const e={[o]:{modes:{Default:this.w3cGroupToNestedVars(a)}}};t.push(e)}return t},w3cGroupToNestedVars(e){const t={};for(const[o,a]of Object.entries(e))o.startsWith("$")||(this.isW3CToken(a)?t[o]=this.parseW3CToken(a):"object"==typeof a&&null!==a&&(t[o]=this.w3cGroupToNestedVars(a)));return t},isW3CToken:e=>"object"==typeof e&&null!==e&&"$value"in e},TS_FLOAT_SCOPE_MAP={CORNER_RADIUS:"borderRadius",STROKE_FLOAT:"borderWidth",GAP:"spacing",WIDTH_HEIGHT:"sizing",OPACITY:"opacity",FONT_SIZE:"fontSizes",LINE_HEIGHT:"lineHeights",LETTER_SPACING:"letterSpacing",PARAGRAPH_SPACING:"paragraphSpacing"},TS_STRING_SCOPE_MAP={FONT_FAMILY:"fontFamilies",FONT_STYLE:"fontWeights"},TS_TEXT_CASE_MAP={ORIGINAL:"none",UPPER:"uppercase",LOWER:"lowercase",TITLE:"capitalize"},TS_TEXT_DECORATION_MAP={NONE:"none",UNDERLINE:"underline",STRIKETHROUGH:"line-through"},TokensStudioConverter={sanitizeSegment(e){let t=e.replace(/[{}$]/g,"");return 0===t.length&&(t="_"),"__proto__"!==t&&"constructor"!==t&&"prototype"!==t||(t="_"+t),t},sanitizeAliasRef(e){const t=e.substring(1,e.length-1).split("."),o=[];for(let e=0;e"string"==typeof e&&"{"===e.charAt(0)&&"}"===e.charAt(e.length-1),roundNumber:e=>Math.round(1e3*e)/1e3,colorToTS(e){const t=void 0!==e.rgb?e.rgb.a:void 0;return void 0!==t&&t<1?e.css:void 0!==e.rgb&&e.hex.length>7?e.hex.substring(0,7):e.hex},floatTypeFromScopes(e){if(!e)return"number";let t="",o=0;for(let a=0;ae.toLowerCase().replace(/\s+/g,"-")};function convertToTokensStudio(e){const t=TokensStudioConverter,o={},a=[],s={libraryRefsSkipped:0,imagePaintsSkipped:0,blurEffectsSkipped:0},n=[];let r=null;for(let t=0;t0){const e={};for(let o=0;o0){const t=styleSetKey("styles/color");o[t]=e,a.push(t),i.push(t)}}if(void 0!==r.textStyles&&r.textStyles.length>0){const e={};for(let o=0;o0){const t=styleSetKey("styles/typography");o[t]=e,a.push(t),i.push(t)}}if(void 0!==r.effectStyles&&r.effectStyles.length>0){const e={};for(let o=0;o0){const t=styleSetKey("styles/effects");o[t]=e,a.push(t),i.push(t)}}void 0!==r.gridStyles&&r.gridStyles.length>0&&Logger.log("Tokens Studio export: grid styles skipped (no Tokens Studio representation)")}s.libraryRefsSkipped>0&&Logger.log("Tokens Studio export: skipped "+s.libraryRefsSkipped+" library-alias token(s) with no resolvable local value"),s.imagePaintsSkipped>0&&Logger.log("Tokens Studio export: skipped "+s.imagePaintsSkipped+" image paint(s) in color styles (no Tokens Studio representation)"),s.blurEffectsSkipped>0&&Logger.log("Tokens Studio export: skipped "+s.blurEffectsSkipped+" blur effect(s) in effect styles (boxShadow tokens carry shadows only)");const l=[];for(let e=0;e{const a=o<0?o+1:o>1?o-1:o;return a<1/6?e+6*(t-e)*a:a<.5?t:a<2/3?e+(t-e)*(2/3-a)*6:e},r=n<.5?n*(1+s):n+s-n*s,i=2*n-r;return{r:hue2rgb(i,r,a+1/3),g:hue2rgb(i,r,a),b:hue2rgb(i,r,a-1/3),a:null!==(o=e.a)&&void 0!==o?o:1}},fromHsb(e){var t;const o=e.h/360,a=e.s/100,s=e.b/100,n=Math.floor(6*o),r=6*o-n,i=s*(1-a),l=s*(1-r*a),c=s*(1-(1-r)*a),g=[[s,c,i],[l,s,i],[i,s,c],[i,l,s],[c,i,s],[s,i,l]],[d,f,u]=g[n%6];return{r:d,g:f,b:u,a:null!==(t=e.a)&&void 0!==t?t:1}},parse(e){var t;if("object"==typeof e&&null!==e&&"hex"in e&&"rgb"in e)return this.fromHex(e.hex);if("object"==typeof e&&null!==e&&"r"in e&&"g"in e&&"b"in e){const o=e;return o.r<=1&&o.g<=1&&o.b<=1?{r:o.r,g:o.g,b:o.b,a:null!==(t=o.a)&&void 0!==t?t:1}:this.fromRgb255(o)}return"object"==typeof e&&null!==e&&"h"in e&&"s"in e&&"l"in e?this.fromHsl(e):"object"==typeof e&&null!==e&&"h"in e&&"s"in e&&"b"in e?this.fromHsb(e):"string"==typeof e?e.startsWith("rgb")||e.startsWith("hsl")?this.fromCss(e):this.fromHex(e):{r:0,g:0,b:0,a:1}}};function chooseAliasCollection(e,t,o,a,s){if(o)return e;if(!a||0===a.length)return null;if(1===a.length)return a[0];const norm=function(e){return String(e).replace(/\s+/g," ").trim().toLowerCase()},n=norm(e);for(let e=0;e{try{const o=await figma.variables.importVariableByKeyAsync(e.key);o&&this.libraryVariableMap.set(`${t}/${o.name}`,o)}catch(e){}})}catch(e){Logger.log(` ⚠️ Could not index library collection "${o.name}": ${e}`)}}this.libraryCollectionNames.size>0&&Logger.log(`📚 Indexed ${this.libraryVariableMap.size} library variables from ${this.libraryCollectionNames.size} connected libraries`)}catch(e){Logger.log(`⚠️ Could not access team library: ${e}`)}}getCollection(e){return this.collectionMap.get(e)}getVariable(e){return this.variableMap.get(e)||this.libraryVariableMap.get(e)}getNameIndex(){if(this.nameIndex)return this.nameIndex;const e=new Map,ingest=function(t,o){const a=t.name;!function(t,o){const a=e.get(o);a?-1===a.indexOf(t)&&a.push(t):e.set(o,[t])}(o.slice(0,o.length-a.length-1),a)};return this.variableMap.forEach(ingest),this.libraryVariableMap.forEach(ingest),this.nameIndex=e,e}resolveTarget(e,t,o=""){const a=this.getVariable(`${e}/${t}`);if(a)return a;const s=this.getNameIndex().get(t);if(!s||0===s.length)return;const n=chooseAliasCollection(e,t,!1,s,o);return n?this.getVariable(`${n}/${t}`):void 0}setVariable(e,t){this.variableMap.set(e,t),this.nameIndex=null}setCollection(e,t){this.collectionMap.set(e,t)}removeCollection(e){this.collectionMap.delete(e);const t=[];for(const o of this.variableMap.keys())o.startsWith(`${e}/`)&&t.push(o);for(const e of t)this.variableMap.delete(e);this.nameIndex=null}isCollectionAvailable(e){return this.collectionMap.has(e)||this.libraryCollectionNames.has(e)}getLibraryCollectionNames(){return Array.from(this.libraryCollectionNames)}get size(){return this.variableMap.size}get collections(){return this.collectionMap.values()}getVariableKeys(){return Array.from(this.variableMap.keys())}}const variableCache=new VariableCache;function isExportVariableValue(e){return"object"==typeof e&&null!==e&&"$type"in e}function isVariableAlias(e){return"object"==typeof e&&null!==e&&"VARIABLE_ALIAS"===e.type}const TypeMapper={toExportType(e){var t;return null!==(t={COLOR:"color",FLOAT:"float",STRING:"string",BOOLEAN:"boolean"}[e])&&void 0!==t?t:"string"},toFigmaType(e){var t;return null!==(t={color:"COLOR",float:"FLOAT",string:"STRING",boolean:"BOOLEAN"}[e])&&void 0!==t?t:"STRING"},scopesToArray:e=>0===e.length||e.includes("ALL_SCOPES")?["ALL_SCOPES"]:[...e],arrayToScopes:e=>e.includes("ALL_SCOPES")?["ALL_SCOPES"]:e};async function getVariableBindingInfo(e,t){if(!(null==e?void 0:e[t]))return{};const o=e[t];if(!o)return{};const a=await figma.variables.getVariableByIdAsync(o.id);if(!a)return{id:o.id};const s=await figma.variables.getVariableCollectionByIdAsync(a.variableCollectionId);return{id:o.id,name:a.name,collection:null==s?void 0:s.name}}async function extractBindings(e,t){if(!e)return;const o={};for(const a of t){const t=await getVariableBindingInfo(e,a);t.name&&(o[a]=t)}return Object.keys(o).length>0?o:void 0}function flattenVariables(e,t){const o=[];for(const a of Object.keys(e)){const s=e[a],n=t?`${t}/${a}`:a;isExportVariableValue(s)?o.push({path:n,value:s}):o.push(...flattenVariables(s,n))}return o}function getValueAtPath(e,t){const o=t.split("/");let a=e;for(const e of o){if("object"!=typeof a||null===a)return null;if(isExportVariableValue(a))return null;a=a[e]}return isExportVariableValue(a)?a:null}const ColorStyleProcessor={async export(e){var t;const o=null!==(t=null==e?void 0:e.includeImages)&&void 0!==t&&t,a=[],s=await figma.getLocalPaintStylesAsync();return await runSequentialAsync(s,20,async function(e){var t,s,n;if(0===e.paints.length)return;const r=[];let i,l,c;for(const a of e.paints)if("SOLID"===a.type){const e=a.color;let o=null!==(t=a.opacity)&&void 0!==t?t:1;void 0!==e.a&&e.a<1&&1===o&&(o=e.a);const s={r:a.color.r,g:a.color.g,b:a.color.b,a:o},n={type:"SOLID",color:ColorConverter.toAllFormats(s),opacity:MathUtils.round2(o)};r.push(n),i||(i=n.color,l=n.opacity,c=await extractBindings(a.boundVariables,["color"]))}else if("GRADIENT_LINEAR"===a.type||"GRADIENT_RADIAL"===a.type||"GRADIENT_ANGULAR"===a.type||"GRADIENT_DIAMOND"===a.type){const e=a.gradientStops.map(e=>{var t;return{position:MathUtils.round2(e.position),color:ColorConverter.toAllFormats({r:e.color.r,g:e.color.g,b:e.color.b,a:null!==(t=e.color.a)&&void 0!==t?t:1})}}),t=Object.assign(Object.assign({type:a.type,gradientStops:e},a.gradientTransform&&{gradientTransform:a.gradientTransform}),{opacity:MathUtils.round2(null!==(s=a.opacity)&&void 0!==s?s:1)});r.push(t)}else if("IMAGE"===a.type){const t=Object.assign(Object.assign(Object.assign(Object.assign({type:"IMAGE",scaleMode:a.scaleMode},a.imageHash&&{imageHash:a.imageHash}),{opacity:MathUtils.round2(null!==(n=a.opacity)&&void 0!==n?n:1)}),void 0!==a.rotation&&{rotation:a.rotation}),a.filters&&{filters:Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},void 0!==a.filters.exposure&&{exposure:a.filters.exposure}),void 0!==a.filters.contrast&&{contrast:a.filters.contrast}),void 0!==a.filters.saturation&&{saturation:a.filters.saturation}),void 0!==a.filters.temperature&&{temperature:a.filters.temperature}),void 0!==a.filters.tint&&{tint:a.filters.tint}),void 0!==a.filters.highlights&&{highlights:a.filters.highlights}),void 0!==a.filters.shadows&&{shadows:a.filters.shadows})});if(o&&a.imageHash)try{const e=figma.getImageByHash(a.imageHash);if(e){const o=await e.getBytesAsync();if(o){const e=figma.base64Encode(o);t.imageBase64=e}}}catch(t){Logger.log(`⚠️ Could not export image data for style "${e.name}": ${t}`)}r.push(t)}if(0===r.length)return;const g=Object.assign(Object.assign(Object.assign(Object.assign({name:e.name,paints:r},i&&{color:i}),void 0!==l&&{opacity:l}),e.description&&{description:e.description}),c&&Object.keys(c).length>0&&{boundVariables:c});a.push(g)}),a},async importStyles(e,t){let o=0,a=0;const s=new Map;for(const e of await figma.getLocalPaintStylesAsync())s.set(e.name,e);return await runSequentialAsync(e,20,async function(e){var n,r,i,l;let c;s.has(e.name)?(c=s.get(e.name),a++):(c=figma.createPaintStyle(),c.name=e.name,o++),e.description&&(c.description=e.description);const g=[];if(e.paints&&e.paints.length>0){for(const o of e.paints)if("SOLID"===o.type){const a=ColorParser.parse(o.color);let s=null!==(n=o.opacity)&&void 0!==n?n:1;a.a<1&&void 0===o.opacity&&(s=MathUtils.round2(a.a));let r={type:"SOLID",color:{r:a.r,g:a.g,b:a.b},opacity:MathUtils.round2(s)};if(e.boundVariables&&0===g.length)for(const[o,a]of Object.entries(e.boundVariables))if(a.name&&a.collection){const e=t.resolveTarget(a.collection,a.name);if(e)try{r=figma.variables.setBoundVariableForPaint(r,o,e)}catch(e){Logger.log(`⚠️ Could not bind ${o}: ${e}`)}}g.push(r)}else if("GRADIENT_LINEAR"===o.type||"GRADIENT_RADIAL"===o.type||"GRADIENT_ANGULAR"===o.type||"GRADIENT_DIAMOND"===o.type){const e=o.gradientStops.map(e=>{const t=ColorParser.parse(e.color);return{position:e.position,color:{r:t.r,g:t.g,b:t.b,a:t.a}}}),t=o.gradientTransform?[[o.gradientTransform[0][0],o.gradientTransform[0][1],o.gradientTransform[0][2]],[o.gradientTransform[1][0],o.gradientTransform[1][1],o.gradientTransform[1][2]]]:[[1,0,0],[0,1,0]],a={type:o.type,gradientStops:e,gradientTransform:t,opacity:null!==(r=o.opacity)&&void 0!==r?r:1};g.push(a)}else if("IMAGE"===o.type){let t=null;if(o.imageBase64)try{const a=figma.base64Decode(o.imageBase64);t=figma.createImage(a).hash,Logger.log(`✅ Created image from base64 data for style "${e.name}"`)}catch(t){Logger.log(`⚠️ Could not import image from base64 for style "${e.name}": ${t}`)}if(!t&&o.imageHash){figma.getImageByHash(o.imageHash)?(t=o.imageHash,Logger.log(`✅ Found existing image with hash for style "${e.name}"`)):Logger.log(`⚠️ Image hash not found in file for style "${e.name}", skipping image paint (imageHash cannot be null)`)}if(t){const e=Object.assign(Object.assign({type:"IMAGE",scaleMode:o.scaleMode,imageHash:t,opacity:null!==(i=o.opacity)&&void 0!==i?i:1},void 0!==o.rotation&&{rotation:o.rotation}),o.filters&&{filters:o.filters});g.push(e)}}}else if(e.color){const o=ColorParser.parse(e.color);let a=null!==(l=e.opacity)&&void 0!==l?l:1;o.a<1&&void 0===e.opacity&&(a=MathUtils.round2(o.a));let s={type:"SOLID",color:{r:o.r,g:o.g,b:o.b},opacity:MathUtils.round2(a)};if(e.boundVariables)for(const[o,a]of Object.entries(e.boundVariables))if(a.name&&a.collection){const e=t.resolveTarget(a.collection,a.name);if(e)try{s=figma.variables.setBoundVariableForPaint(s,o,e)}catch(e){Logger.log(`⚠️ Could not bind ${o}: ${e}`)}}g.push(s)}g.length>0&&(c.paints=g)}),{created:o,updated:a}}},TextStyleProcessor={async export(e){const t=[],o=await figma.getLocalTextStylesAsync();return await runSequentialAsync(o,20,async function(e){const o=Object.assign(Object.assign({name:e.name,fontFamily:e.fontName.family,fontStyle:e.fontName.style,fontSize:e.fontSize,lineHeight:e.lineHeight,letterSpacing:e.letterSpacing,textCase:e.textCase,textDecoration:e.textDecoration},e.description&&{description:e.description}),{boundVariables:await extractBindings(e.boundVariables,["fontSize","lineHeight","letterSpacing","paragraphSpacing","paragraphIndent"])});t.push(o)}),t},async importStyles(e,t){let o=0,a=0;const s=new Map;for(const e of await figma.getLocalTextStylesAsync())s.set(e.name,e);return await runSequentialAsync(e,20,async function(e){let n;s.has(e.name)?(n=s.get(e.name),a++):(n=figma.createTextStyle(),n.name=e.name,o++),e.description&&(n.description=e.description);try{if(await figma.loadFontAsync({family:e.fontFamily,style:e.fontStyle}),n.fontName={family:e.fontFamily,style:e.fontStyle},n.fontSize=e.fontSize,n.lineHeight=e.lineHeight,n.letterSpacing=e.letterSpacing,e.textCase&&(n.textCase=e.textCase),e.textDecoration&&(n.textDecoration=e.textDecoration),e.boundVariables)for(const[o,a]of Object.entries(e.boundVariables))if(a.name&&a.collection){const e=t.resolveTarget(a.collection,a.name);if(e)try{n.setBoundVariable(o,e)}catch(e){}}}catch(t){Logger.log(`⚠️ Could not load font for ${e.name}: ${t}`)}}),{created:o,updated:a}}},EffectStyleProcessor={async export(e){const t=[],o=await figma.getLocalEffectStylesAsync();return await runSequentialAsync(o,20,async function(e){const o=[];for(const t of e.effects){const e=Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({type:t.type,visible:t.visible},"radius"in t&&{radius:t.radius}),"spread"in t&&{spread:t.spread}),"offset"in t&&{offset:t.offset}),"color"in t&&{color:ColorConverter.toAllFormats(t.color)}),"blendMode"in t&&{blendMode:t.blendMode}),"showShadowBehindNode"in t&&{showShadowBehindNode:t.showShadowBehindNode}),{boundVariables:await extractBindings(t.boundVariables,["color","radius","spread","offsetX","offsetY"])});o.push(e)}const a=Object.assign(Object.assign({name:e.name},e.description&&{description:e.description}),{effects:o});t.push(a)}),t},async importStyles(e,t){let o=0,a=0;const s=new Map;for(const e of await figma.getLocalEffectStylesAsync())s.set(e.name,e);return await runSequentialAsync(e,20,async function(e){let n;s.has(e.name)?(n=s.get(e.name),a++):(n=figma.createEffectStyle(),n.name=e.name,o++),e.description&&(n.description=e.description);const r=e.effects.map(e=>{var t;return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({type:e.type,visible:null===(t=e.visible)||void 0===t||t},void 0!==e.radius&&{radius:e.radius}),void 0!==e.spread&&{spread:e.spread}),void 0!==e.offset&&{offset:e.offset}),void 0!==e.color&&{color:(()=>{const t=ColorParser.parse(e.color);return{r:t.r,g:t.g,b:t.b,a:MathUtils.round2(t.a)}})()}),void 0!==e.blendMode&&{blendMode:e.blendMode}),void 0!==e.showShadowBehindNode&&{showShadowBehindNode:e.showShadowBehindNode})});n.effects=r;for(let o=0;o{var t,o,a,s,n,r,i;const l=e.color?ColorParser.parse(e.color):{r:1,g:0,b:0,a:.1},c={r:l.r,g:l.g,b:l.b,a:MathUtils.round2(l.a)};if("GRID"===e.pattern)return{pattern:"GRID",sectionSize:null!==(t=e.sectionSize)&&void 0!==t?t:10,visible:!1!==e.visible,color:c};const g=null!==(o=e.alignment)&&void 0!==o?o:"STRETCH",d={pattern:e.pattern,gutterSize:null!==(a=e.gutterSize)&&void 0!==a?a:10,count:null!==(s=e.count)&&void 0!==s?s:5,visible:!1!==e.visible,color:c};if("STRETCH"===g)return Object.assign(Object.assign({},d),{alignment:"STRETCH",offset:null!==(n=e.offset)&&void 0!==n?n:0});if("CENTER"===g)return Object.assign(Object.assign({},d),{alignment:"CENTER",sectionSize:null!==(r=e.sectionSize)&&void 0!==r?r:100});{const t=Object.assign(Object.assign({},d),{alignment:g,offset:null!==(i=e.offset)&&void 0!==i?i:0});return void 0!==e.sectionSize&&(t.sectionSize=e.sectionSize),t}});n.layoutGrids=r;for(let o=0;ot.name===e);a?await checkVariablesDiff(i,a.modeId,o,r,"",t):l=!0}t.modifiedVariables.some(e=>e.collection===r)||t.newVariables.some(e=>e.collection===r)?(t.modifiedCollections.push(r),t.summary.collectionsModified++):(t.unchangedCollections.push(r),t.summary.collectionsUnchanged++)}return t}function countVariablesInCollection(e){let t=0;const o=Object.values(e)[0];return o&&(t=countVarsInNestedObj(o)),t}function countVarsInNestedObj(e){let t=0;for(const o of Object.values(e))isExportVariableValue(o)?t++:t+=countVarsInNestedObj(o);return t}async function checkVariablesDiff(e,t,o,a,s,n){for(const[r,i]of Object.entries(o)){const o=s?`${s}/${r}`:r;if(isExportVariableValue(i)){const e=variableCache.getVariable(`${a}/${o}`);if(e){const s=e.valuesByMode[t],r=i.$value;valuesAreDifferent(s,r)?(n.modifiedVariables.push({collection:a,path:o,oldValue:formatValueForDisplay(s),newValue:formatValueForDisplay(r)}),n.summary.variablesModified++):(n.unchangedVariables++,n.summary.variablesUnchanged++)}else n.newVariables.push({collection:a,path:o}),n.summary.variablesNew++}else await checkVariablesDiff(e,t,i,a,o,n)}}function valuesAreDifferent(e,t){if(void 0===e)return!0;if(isVariableAlias(e))return"string"==typeof t&&t.startsWith("{"),!0;if("object"==typeof e&&null!==e&&"r"in e){if("object"==typeof t&&null!==t&&"hex"in t){return ColorConverter.toAllFormats(e).hex.toLowerCase()!==t.hex.toLowerCase()}return!0}return e!==t}function formatValueForDisplay(e){if(void 0===e)return"undefined";if("object"==typeof e&&null!==e){if("hex"in e)return e.hex;if("r"in e)return ColorConverter.toAllFormats(e).hex;if("id"in e)return"{alias}"}return String(e)}async function computeStylesDiff(e,t){if(e.colorStyles){const o=await figma.getLocalPaintStylesAsync(),a=new Set(o.map(e=>e.name));for(const o of e.colorStyles)a.has(o.name)?(t.modifiedStyles.push({type:"color",name:o.name}),t.summary.stylesModified++):(t.newStyles.push({type:"color",name:o.name}),t.summary.stylesNew++)}if(e.textStyles){const o=await figma.getLocalTextStylesAsync(),a=new Set(o.map(e=>e.name));for(const o of e.textStyles)a.has(o.name)?(t.modifiedStyles.push({type:"text",name:o.name}),t.summary.stylesModified++):(t.newStyles.push({type:"text",name:o.name}),t.summary.stylesNew++)}if(e.effectStyles){const o=await figma.getLocalEffectStylesAsync(),a=new Set(o.map(e=>e.name));for(const o of e.effectStyles)a.has(o.name)?(t.modifiedStyles.push({type:"effect",name:o.name}),t.summary.stylesModified++):(t.newStyles.push({type:"effect",name:o.name}),t.summary.stylesNew++)}if(e.gridStyles){const o=await figma.getLocalGridStylesAsync(),a=new Set(o.map(e=>e.name));for(const o of e.gridStyles)a.has(o.name)?(t.modifiedStyles.push({type:"grid",name:o.name}),t.summary.stylesModified++):(t.newStyles.push({type:"grid",name:o.name}),t.summary.stylesNew++)}}function filterStylesByGroup(e,t){return t?e.filter(function(e){return-1!==t.indexOf(getGroupKey(e.name))}):e}async function exportVariables(e,t,o,a,s="original",n="figma",r,i=!1,l,c){var g,d,f,u,p,m,y,h;Logger.log("📤 Starting export..."),Logger.log(` preserveLibraryRefs: ${o}`),Logger.log(` includeImages: ${a}`),Logger.log(` namingConvention: ${s}`),Logger.log(` exportFormat: ${n}`),Logger.log(` resolveAliases: ${i}`),r&&Logger.log(` selectedModes: ${JSON.stringify(r)}`);try{let o=await figma.variables.getLocalVariableCollectionsAsync();(null==e?void 0:e.length)&&(o=o.filter(t=>e.includes(t.name)),Logger.log(`Filtering to ${o.length} selected collections`));const b=[],v={};let S=0;const C=createProgress("export");let A=0;for(let e=0;eo.includes(e.name)),Logger.log(` Filtering to ${t.length} modes: ${t.map(e=>e.name).join(", ")}`)}const o=NamingConverter.convertCollectionName(e.name,s),a={[o]:Object.assign({modes:{}},o!==e.name&&{$originalName:e.name})};for(const e of t){const t=NamingConverter.convertModeName(e.name,s);a[o].modes[t]={}}await runSequentialAsync(e.variableIds,BATCH.SEQ_EXPORT,async function(n){var r,c;L++;const g=await figma.variables.getVariableByIdAsync(n);if(!g)return;if(l&&l[e.name]&&-1===l[e.name].indexOf(getGroupKey(g.name)))return;S++;const d=g.name.split("/").map(e=>NamingConverter.convert(e,s));for(const n of t){const t=NamingConverter.convertModeName(n.name,s),l=a[o].modes[t],f=g.valuesByMode[n.modeId];let u=l;for(let e=0;eNamingConverter.convert(e,s)).join(".")}}`,m=S,v){let o=await resolveAliasValue(t,n.modeId);if(""===o){const t=resolveViaConsumer(g,e,n.modeId);void 0!==t&&(o=t)}"object"==typeof o&&null!==o&&"r"in o?y=ColorConverter.toAllFormats(o):""!==o&&(y=o)}}}else m=""}else m="object"==typeof f&&null!==f&&"r"in f?ColorConverter.toAllFormats(f):f;const C=Object.assign(Object.assign(Object.assign({$scopes:TypeMapper.scopesToArray(g.scopes),$type:TypeMapper.toExportType(g.resolvedType),$value:m},g.description&&{$description:g.description}),h&&b&&{$collectionName:b}),h&&v&&Object.assign({$libraryRef:S},void 0!==y&&{$localValue:y}));u[p]=C}},function(){C.report("export_variables","Exporting variables",L,A)}),b.push(a),"w3c"===n&&(v[o]=W3CConverter.collectionToW3C(o,a[o].modes,s,a[o].$originalName))}let $=null;if(t){if($={},t.colorStyles){C.report("export_styles","Exporting styles",0,0,!0);const e=c?c.color:void 0;$.colorStyles=filterStylesByGroup(await ColorStyleProcessor.export({includeImages:a}),e),e&&0===$.colorStyles.length&&delete $.colorStyles}if(t.textStyles){C.report("export_styles","Exporting styles",0,0,!0);const e=c?c.text:void 0;$.textStyles=filterStylesByGroup(await TextStyleProcessor.export(),e),e&&0===$.textStyles.length&&delete $.textStyles}if(t.effectStyles){C.report("export_styles","Exporting styles",0,0,!0);const e=c?c.effect:void 0;$.effectStyles=filterStylesByGroup(await EffectStyleProcessor.export(),e),e&&0===$.effectStyles.length&&delete $.effectStyles}if(t.gridStyles){C.report("export_styles","Exporting styles",0,0,!0);const e=c?c.grid:void 0;$.gridStyles=filterStylesByGroup(await GridStyleProcessor.export(),e),e&&0===$.gridStyles.length&&delete $.gridStyles}Object.keys($).length>0?b.push({_styles:$}):$=null}const w={collections:o.length,variables:S,styles:$?{color:null!==(d=null===(g=$.colorStyles)||void 0===g?void 0:g.length)&&void 0!==d?d:0,text:null!==(u=null===(f=$.textStyles)||void 0===f?void 0:f.length)&&void 0!==u?u:0,effect:null!==(m=null===(p=$.effectStyles)||void 0===p?void 0:p.length)&&void 0!==m?m:0,grid:null!==(h=null===(y=$.gridStyles)||void 0===y?void 0:y.length)&&void 0!==h?h:0}:null};let T;"w3c"===n?($&&Object.keys($).length>0&&(v.$extensions={"com.figma":{styles:$}}),T=JSON.stringify(v,null,2),Logger.log(`✅ Export complete (W3C format): ${w.collections} collections, ${w.variables} variables`)):"tokens-studio"===n?(T=JSON.stringify(convertToTokensStudio(b),null,2),Logger.log(`✅ Export complete (Tokens Studio format): ${w.collections} collections, ${w.variables} variables`)):(T=JSON.stringify(b,null,2),Logger.log(`✅ Export complete: ${w.collections} collections, ${w.variables} variables`)),await sendExportInChunks(T,w,n)}catch(e){if(isCancelError(e))return Logger.log("🛑 Export cancelled"),void figma.ui.postMessage({type:"operation_cancelled",operation:"export",phase:"export",rolledBack:!1,message:"Export cancelled. Nothing was changed."});Logger.log(`❌ Export error: ${e}`),Logger.send("error",{message:`Export failed: ${e}`})}}async function sendExportInChunks(e,t,o){const a=e.length,s=BATCH.EXPORT_CHUNK_BYTES,n=Math.max(1,Math.ceil(a/s));let r=0,i=0;for(;i=55296&&o<=56319&&(t+=1)}const o=e.slice(i,t);figma.ui.postMessage({type:"export_chunk",seq:r,total:n,data:o}),r++,i=t,r%BATCH.EXPORT_YIELD_EVERY===0&&i0?a.modes[s[0]]:{},"");b.push({collectionObj:t,flatPaths:n}),v+=n.length;for(let e=0;e0&&Logger.log(` ✅ Aliases: ${L} resolved, ${$} used fallback values`),y&&t.importStyles){if(Logger.log("📦 Importing styles..."),n.report("import_styles","Importing styles",0,0,!0),y.colorStyles){const e=await ColorStyleProcessor.importStyles(y.colorStyles,variableCache);p+=e.created,m+=e.updated}if(y.textStyles){const e=await TextStyleProcessor.importStyles(y.textStyles,variableCache);p+=e.created,m+=e.updated}if(y.effectStyles){const e=await EffectStyleProcessor.importStyles(y.effectStyles,variableCache);p+=e.created,m+=e.updated}if(y.gridStyles){const e=await GridStyleProcessor.importStyles(y.gridStyles,variableCache);p+=e.created,m+=e.updated}}const w={collectionsCreated:g,variablesCreated:d,variablesUpdated:f,variablesSkipped:u,stylesCreated:p,stylesUpdated:m};figma.commitUndo(),Logger.log("✅ Import complete!"),Logger.send("import_complete",{stats:w,snapshot:s})}catch(e){const t=isCancelError(e);if(t&&!r)return Logger.log("🛑 Import cancelled before any changes were made"),void figma.ui.postMessage({type:"operation_cancelled",operation:"import",phase:"snapshot",rolledBack:!1,message:"Import cancelled. No changes were made."});const o=e instanceof Error?e.message:String(e);if(t?Logger.log("🛑 Import cancelled after mutation started — rolling back..."):Logger.log(`❌ Import error: ${o}`),s){Logger.log("🔄 Attempting automatic rollback to pre-import state..."),Logger.send("import_rolling_back",{error:o});try{await restoreFromSnapshot(s),Logger.log("✅ Automatic rollback successful - file restored to pre-import state"),t?figma.ui.postMessage({type:"operation_cancelled",operation:"import",phase:"rollback",rolledBack:!0,message:"Import cancelled — your file was restored to its previous state."}):Logger.send("import_rollback_complete",{error:o,message:"Import failed but your file has been automatically restored to its previous state."})}catch(e){const t=e instanceof Error?e.message:String(e);Logger.log(`❌ Rollback failed: ${t}`),Logger.send("import_rollback_failed",{error:o,rollbackError:t,message:"Import failed and automatic rollback also failed. Please use Ctrl+Z (Cmd+Z) to undo manually."})}}else Logger.send("error",{message:`Import failed: ${o}. Use Ctrl+Z (Cmd+Z) to undo changes.`})}}function aliasFallbackValue(e){return void 0===e.$localValue?e:Object.assign({$scopes:e.$scopes,$type:e.$type,$value:e.$localValue},void 0!==e.$description&&{$description:e.$description})}function setRawValue(e,t,o){try{if("color"===o.$type){const a=ColorParser.parse(o.$value),s=a.a<1?Object.assign(Object.assign({},a),{a:MathUtils.round2(a.a)}):a;e.setValueForMode(t,s)}else e.setValueForMode(t,o.$value)}catch(e){console.error(`Could not set value: ${e}`)}}function getGroupKey(e){const t=e.indexOf("/");return-1===t?"":e.substring(0,t)}function summarizeGroups(e){const t=Object.create(null),o=[];for(let a=0;a{Logger.log(` ${t+1}. "${e.name}" (id: ${e.id})`)});const t=new Set;let o=0,a=0,s=0;const n=new Map,r=[];for(let i=0;ie.name),variableCount:l.variableIds.length,types:c,groups:summarizeGroups(g)})}r.sort((e,t)=>e.name.localeCompare(t.name));const i=await figma.getLocalPaintStylesAsync(),l=await figma.getLocalTextStylesAsync(),c=await figma.getLocalEffectStylesAsync(),g=await figma.getLocalGridStylesAsync();let d=0;const f=[];for(let e=0;e({family:e,styles:Array.from(t)}));let S=0;for(const e of i)e.boundVariables&&Object.keys(e.boundVariables).length>0&&S++;Logger.send("collections",{collections:r,styles:u,styleGroups:h,libraryDependencies:Array.from(t),fontsUsed:v,stats:{totalVariables:r.reduce((e,t)=>e+t.variableCount,0),totalAliases:o,localAliases:a,libraryAliases:s,styleBindings:S}})}async function clearVariables(e=!1){Logger.log("🗑️ Clearing all variables...");const t=e?null:createProgress("clear");let o=0,a=0,s=!1;try{const n=await figma.variables.getLocalVariableCollectionsAsync(),r=[];let i=0;for(let e=0;e0&&(figma.commitUndo(),s=!0);let l=0;for(let e=0;e0&&(figma.commitUndo(),a=!0);let c=0;const removeStyle=function(e){e.remove(),o++},onBatch=function(e){t&&t.report("clear","Deleting styles",c+e,l)};await runBatched(s,BATCH.SYNC_LIGHT,removeStyle,onBatch),c+=s.length,await runBatched(n,BATCH.SYNC_LIGHT,removeStyle,onBatch),c+=n.length,await runBatched(r,BATCH.SYNC_LIGHT,removeStyle,onBatch),c+=r.length,await runBatched(i,BATCH.SYNC_LIGHT,removeStyle,onBatch),c+=i.length,!e&&a&&figma.commitUndo(),Logger.log(`✅ Cleared ${o} styles`),e||Logger.send("clear_complete",{message:`${o} styles`})}catch(t){if(isCancelError(t)){if(e)throw t;return Logger.log(`🛑 Clear styles cancelled after ${o} styles`),void figma.ui.postMessage({type:"operation_cancelled",operation:"clear",phase:"clear",rolledBack:!1,partial:{collectionsDeleted:0,variablesDeleted:o},message:`Clear cancelled — ${o} styles were already deleted. Remaining items were not touched. Use Cmd+Z to restore deleted items.`})}if(Logger.log(`❌ Clear styles error: ${t}`),e)throw t;Logger.send("error",{message:`Failed to clear styles: ${t}`})}}async function clearAll(e=!1){if(Logger.log("🗑️ Clearing everything..."),e)return await clearVariables(!0),void await clearStyles(!0);let t=!1;try{figma.commitUndo(),t=!0,await clearVariables(!0),await clearStyles(!0),figma.commitUndo(),Logger.send("clear_complete",{message:"all variables and styles"})}catch(e){if(t&&figma.commitUndo(),isCancelError(e))return Logger.log("🛑 Clear all cancelled"),void figma.ui.postMessage({type:"operation_cancelled",operation:"clear",phase:"clear",rolledBack:!1,partial:{collectionsDeleted:0,variablesDeleted:0},message:"Clear cancelled — some items may already have been deleted. Use Cmd+Z to restore deleted items."});Logger.log(`❌ Clear all error: ${e}`),Logger.send("error",{message:`Failed to clear: ${e}`})}}async function createUndoSnapshot(e){var t;Logger.log("📸 Creating snapshot of current file state...");const o=await figma.variables.getLocalVariableCollectionsAsync(),a=[],s=new Map;let n=0;for(let e=0;e({id:e.modeId,name:e.name})),variables:[]},i=await runBatchedAsync(t.variableIds,BATCH.ASYNC_LOOKUP,function(e){return figma.variables.getVariableByIdAsync(e)},function(t){e&&e.report("snapshot","Preparing snapshot (undo safety)",r+t,n)});r+=t.variableIds.length;for(let e=0;e0){t.renameMode(t.modes[0].modeId,e.modes[0].name);for(let o=1;o0&&(s.scopes=a.scopes);for(const t of e.modes){const r=o[t.name],i=a.values[t.name];if(i)if(i.isAlias&&i.aliasName)n.push({variable:s,modeId:r,aliasPath:i.aliasName,aliasCollection:i.aliasCollection||e.name});else if(void 0!==i.value){let e;e="COLOR"===a.type&&"string"==typeof i.value?ColorParser.parse(i.value):i.value,s.setValueForMode(r,e)}}}},function(e,o){t.report("undo_restore","Restoring variables",e,o)}),Logger.log(` Resolving ${n.length} aliases...`),await runBatched(n,BATCH.SYNC_LIGHT,function(e){const t=`${e.aliasCollection}/${e.aliasPath}`,o=variableCache.getVariable(t);o&&e.variable.setValueForMode(e.modeId,{type:"VARIABLE_ALIAS",id:o.id})},function(e,o){t.report("undo_aliases","Restoring aliases",e,o)}),Logger.log(" Restoring styles..."),t.report("undo_styles","Restoring styles",0,0,!0),a.colorStyles&&a.colorStyles.length>0&&await ColorStyleProcessor.importStyles(a.colorStyles,variableCache),a.textStyles&&a.textStyles.length>0&&await TextStyleProcessor.importStyles(a.textStyles,variableCache),a.effectStyles&&a.effectStyles.length>0&&await EffectStyleProcessor.importStyles(a.effectStyles,variableCache),a.gridStyles&&a.gridStyles.length>0&&await GridStyleProcessor.importStyles(a.gridStyles,variableCache),Logger.log("✅ File restored from snapshot")}figma.ui.onmessage=async e=>{switch(e.type){case"cancel_operation":null!==currentOperation.type&&(currentOperation.cancellable?(currentOperation.cancelRequested=!0,Logger.log("🛑 Cancellation requested — finishing current batch…")):Logger.log("⚠️ Rollback in progress — cannot cancel"));break;case"resize_ui":"advanced"===e.mode?figma.ui.resize(UI_SIZE.advanced.width,UI_SIZE.advanced.height):figma.ui.resize(UI_SIZE.simple.width,UI_SIZE.simple.height);break;case"export":await withOperation("export",function(){return exportVariables(e.collections,e.styleOptions,e.preserveLibraryRefs,e.includeImages,e.namingConvention||"original",e.exportFormat||"figma",e.selectedModes,e.resolveAliases||!1,e.selectedGroups,e.selectedStyleGroups)});break;case"import":await withOperation("import",function(){return importVariables(e.data,e.options)});break;case"validate_import":try{const t=JSON.parse(e.data),o=e.plan,a=await validateImportAgainstPlan(t,o);Logger.send("validation_result",a)}catch(e){Logger.send("validation_result",{errors:[`Invalid JSON: ${e instanceof Error?e.message:"Parse error"}`],canImport:!1})}break;case"compute_import_diff":try{const t=JSON.parse(e.data),o=await computeImportDiff(t);Logger.send("import_diff_result",o)}catch(e){Logger.send("import_diff_result",{error:`Failed to compute diff: ${e instanceof Error?e.message:"Unknown error"}`})}break;case"detect_plan":const t=await detectCurrentPlan();Logger.send("plan_detected",t);break;case"clear_variables":await withOperation("clear",function(){return clearVariables(!1)});break;case"clear_styles":await withOperation("clear",function(){return clearStyles(!1)});break;case"clear_all":await withOperation("clear",function(){return clearAll(!1)});break;case"get_collections":await withOperation("scan",getCollections);break;case"check_libraries":try{const t=e.collections,o=e.refs||[];await variableCache.rebuild();const a=[],s=[],n=[];for(const e of t){const t=o.filter(function(t){return t.collection===e});if(variableCache.isCollectionAvailable(e)){a.push(e),n.push({name:e,status:"connected",satisfiable:t.length,total:t.length,fromImport:0});continue}let r=0,i=0;for(const e of t)e.selfSatisfied?(r++,i++):variableCache.resolveTarget(e.collection,e.path)&&r++;t.length>0&&r===t.length?(a.push(e),n.push({name:e,status:"mapped",satisfiable:r,total:t.length,fromImport:i})):r>0?(s.push(e),n.push({name:e,status:"partial",satisfiable:r,total:t.length,fromImport:i})):(s.push(e),n.push({name:e,status:"missing",satisfiable:0,total:t.length,fromImport:0}))}Logger.log(`📚 Library check: ${a.length} available, ${s.length} missing`);for(const e of n)Logger.log(` ${"missing"===e.status?"❌":"partial"===e.status?"⚠️":"✅"} ${e.name}: ${e.status} (${e.satisfiable}/${e.total} refs satisfiable)`);Logger.send("library_check_result",{allAvailable:0===s.length,availableCollections:a,missingCollections:s,requiredCollections:t,collectionStatus:n})}catch(t){Logger.send("library_check_result",{allAvailable:!1,availableCollections:[],missingCollections:e.collections||[],requiredCollections:e.collections||[],collectionStatus:[],error:t instanceof Error?t.message:"Library check failed"})}break;case"check_fonts":try{const t=e.fonts,o=[],a=[],s=await runBatchedAsync(t,BATCH.ASYNC_FONT,function(e){return figma.loadFontAsync({family:e.family,style:e.style}).then(function(){return{font:e,available:!0}}).catch(function(){return{font:e,available:!1}})});for(let e=0;e