@toghen/figma 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +18 -2
- package/cli/bin.mjs +19 -0
- package/cli/bridge.ts +243 -0
- package/cli/check.ts +62 -0
- package/cli/figcraft.ts +764 -0
- package/cli/figcraftApply.ts +1058 -0
- package/cli/figcraftFile.ts +476 -0
- package/cli/figcraftLibrary.ts +147 -0
- package/cli/figcraftRecord.ts +121 -0
- package/cli/figcraftScene.ts +661 -0
- package/cli/main.ts +218 -0
- package/cli/members.ts +65 -0
- package/cli/run.ts +756 -0
- package/component-files.md +665 -0
- package/dist/code.js +24147 -0
- package/dist/manifest.json +23 -0
- package/dist/ui.html +6918 -0
- package/guide.md +985 -0
- package/main/code.ts +50 -0
- package/main/components/apply.ts +704 -0
- package/main/components/endOfRun.ts +168 -0
- package/main/components/errors.ts +17 -0
- package/main/components/fieldWriter.ts +497 -0
- package/main/components/file.ts +263 -0
- package/main/components/receipt.ts +228 -0
- package/main/components/resolve.ts +1193 -0
- package/main/components/run.ts +273 -0
- package/main/components/snapshot.ts +341 -0
- package/main/components/stamp.ts +312 -0
- package/main/components/update.ts +465 -0
- package/main/dispatcher.ts +33 -0
- package/main/engines/batchRunner.ts +200 -0
- package/main/engines/ioAdapter.ts +132 -0
- package/main/engines/operationExecutor.ts +512 -0
- package/main/engines/propertyMapper.ts +221 -0
- package/main/engines/queryEngine.ts +45 -0
- package/main/engines/styleSyncEngine.ts +444 -0
- package/main/engines/variableSyncEngine.ts +561 -0
- package/main/handlers/auditSelection.ts +60 -0
- package/main/handlers/boundLayers.ts +51 -0
- package/main/handlers/cancelOperation.ts +18 -0
- package/main/handlers/executeApplyBindings.ts +69 -0
- package/main/handlers/executeScaffold.ts +59 -0
- package/main/handlers/executeStyleSync.ts +121 -0
- package/main/handlers/executeVariableSync.ts +82 -0
- package/main/handlers/exportDocument.ts +114 -0
- package/main/handlers/getSelection.ts +43 -0
- package/main/handlers/index.ts +30 -0
- package/main/handlers/inspectCorrespondence.ts +172 -0
- package/main/handlers/loadConventions.ts +86 -0
- package/main/handlers/loadDocument.ts +253 -0
- package/main/handlers/lock.ts +270 -0
- package/main/handlers/planApply.ts +157 -0
- package/main/handlers/planApplyBindings.ts +79 -0
- package/main/handlers/planScaffold.ts +84 -0
- package/main/handlers/planStyleSync.ts +49 -0
- package/main/handlers/planVariableSync.ts +49 -0
- package/main/handlers/readSelectionBindings.ts +160 -0
- package/main/handlers/resizeUi.ts +105 -0
- package/main/handlers/runComponent.ts +65 -0
- package/main/handlers/templates.ts +462 -0
- package/main/handlers/tokenGraph.ts +122 -0
- package/main/runtimeState.ts +66 -0
- package/main/utils/abortController.ts +11 -0
- package/main/utils/boundVariables.ts +84 -0
- package/main/utils/buildPayloads.ts +85 -0
- package/main/utils/correspondence.ts +658 -0
- package/main/utils/designSource.ts +122 -0
- package/main/utils/figmaCollectionIndex.ts +120 -0
- package/main/utils/figmaStyleAdapter.ts +268 -0
- package/main/utils/figmaTokenAdapter.ts +255 -0
- package/main/utils/flattenDesignSystem.ts +38 -0
- package/main/utils/folds.ts +203 -0
- package/main/utils/fontStack.ts +57 -0
- package/main/utils/lists.ts +4 -0
- package/main/utils/lockStore.ts +72 -0
- package/main/utils/moves.ts +67 -0
- package/main/utils/pageRoots.ts +58 -0
- package/main/utils/planSymbols.ts +96 -0
- package/main/utils/purpose.ts +351 -0
- package/main/utils/recentStore.ts +166 -0
- package/main/utils/resolverModes.ts +271 -0
- package/main/utils/rootRecord.ts +137 -0
- package/main/utils/shardedData.ts +142 -0
- package/main/utils/standing.ts +106 -0
- package/main/utils/styleTarget.ts +51 -0
- package/main/utils/sum.ts +612 -0
- package/main/utils/symbolicIdRegistry.ts +26 -0
- package/main/utils/syncState.ts +133 -0
- package/main/utils/templateStore.ts +73 -0
- package/main/utils/tokenPath.ts +28 -0
- package/main/utils/tokenStamps.ts +91 -0
- package/manifest.json +15 -0
- package/package.json +64 -4
- package/reference.md +1609 -0
- package/shared/components/artboard.ts +126 -0
- package/shared/components/axes.ts +420 -0
- package/shared/components/bindings.ts +64 -0
- package/shared/components/blueprint.ts +248 -0
- package/shared/components/check.ts +705 -0
- package/shared/components/diff.ts +668 -0
- package/shared/components/fields.ts +404 -0
- package/shared/components/groups.ts +306 -0
- package/shared/components/index.ts +140 -0
- package/shared/components/members.ts +280 -0
- package/shared/components/model.ts +273 -0
- package/shared/components/order.ts +53 -0
- package/shared/components/read.ts +480 -0
- package/shared/components/stack.ts +124 -0
- package/shared/components/steps.ts +186 -0
- package/shared/components/validate.ts +548 -0
- package/shared/conventions.ts +28 -0
- package/shared/inputs.ts +40 -0
- package/shared/operations.ts +233 -0
- package/shared/pendingNames.ts +39 -0
- package/shared/protocol.ts +588 -0
- package/shared/tokenTypeMap.ts +21 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
export type StyleProps = Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A style field that follows a variable instead of holding a value.
|
|
5
|
+
*
|
|
6
|
+
* `field` is a `VariableBindableTextField` on a text style, or a
|
|
7
|
+
* `VariableBindableEffectField` on layer `index` of an effect style. It rides
|
|
8
|
+
* beside `props`, which still carry the resolved value, so a style is complete
|
|
9
|
+
* whether or not a binding lands.
|
|
10
|
+
*
|
|
11
|
+
* `variableId` is always a real Figma id, never a symbolic one: a style plan
|
|
12
|
+
* binds only to variables that already exist. So `resolveOperationIds` and
|
|
13
|
+
* `dependenciesOf` need no case for it.
|
|
14
|
+
*/
|
|
15
|
+
export interface StyleBinding {
|
|
16
|
+
field: string;
|
|
17
|
+
variableId: string;
|
|
18
|
+
index?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Why a sync plan skipped a token, one word each, so skips can be counted and grouped without parsing a sentence.
|
|
23
|
+
* `unchanged` is the quiet one: nothing to do. Every other cause is something a person may want to look at.
|
|
24
|
+
*/
|
|
25
|
+
export type SkipCause =
|
|
26
|
+
/** Figma holds what the document says. */
|
|
27
|
+
| 'unchanged'
|
|
28
|
+
/** Figma holds something else than the last sync wrote: a local edit, left alone. */
|
|
29
|
+
| 'drifted'
|
|
30
|
+
/** A person's copy of a variable or style the sync wrote, left alone. */
|
|
31
|
+
| 'copy'
|
|
32
|
+
/** No variable or style holds a token of this type. */
|
|
33
|
+
| 'unsupported-type'
|
|
34
|
+
/** The token's purpose asks for something a variable cannot hold: a list of scopes, an opacity. */
|
|
35
|
+
| 'refused-routing'
|
|
36
|
+
/** The token's collection is one nothing may be written to: built by an earlier sync, or its default mode removed. */
|
|
37
|
+
| 'closed-collection'
|
|
38
|
+
/** A variable the token links to has another type, which Figma cannot change. */
|
|
39
|
+
| 'type-conflict'
|
|
40
|
+
/** A value of the token no Figma variable can hold, in one context. */
|
|
41
|
+
| 'cannot-write'
|
|
42
|
+
/** A style the token's value cannot be built into. */
|
|
43
|
+
| 'cannot-build'
|
|
44
|
+
/** A text style whose font stack names no family Figma lists. */
|
|
45
|
+
| 'no-font'
|
|
46
|
+
/** A text style whose family was listed and could not be loaded at execute. */
|
|
47
|
+
| 'font-not-loaded';
|
|
48
|
+
|
|
49
|
+
/** A plan's skips counted by cause: only the causes it holds. Their sum is the plan's `skips`. */
|
|
50
|
+
export type SkipCounts = Partial<Record<SkipCause, number>>;
|
|
51
|
+
|
|
52
|
+
/** Counts the skips among `operations` by cause; a skip with none is not counted here. */
|
|
53
|
+
export function countSkipsByCause(operations: readonly Operation[]): SkipCounts {
|
|
54
|
+
const counts: SkipCounts = {};
|
|
55
|
+
for (const op of operations) if (op.kind === 'SKIP' && op.cause) counts[op.cause] = (counts[op.cause] ?? 0) + 1;
|
|
56
|
+
return counts;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type Operation =
|
|
60
|
+
| { kind: 'CREATE_COLLECTION'; symbolicId?: string; name: string; modes: string[] }
|
|
61
|
+
| { kind: 'ADD_MODE'; collectionId: string; collectionName: string; modeName: string }
|
|
62
|
+
/**
|
|
63
|
+
* Deletes a mode its modifier no longer has, with every value in it. Planned only when the request allows
|
|
64
|
+
* breaking, and always beside the `BREAKING` row that says what it costs. The collection's default mode is never named.
|
|
65
|
+
*/
|
|
66
|
+
| { kind: 'DELETE_MODE'; collectionId: string; collectionName: string; modeId: string; modeName: string }
|
|
67
|
+
/**
|
|
68
|
+
* Removes the variable a token had before it changed collection, once the new one is written and every alias is
|
|
69
|
+
* re-pointed. Planned only when the request allows breaking, and always beside the `BREAKING` row that says what it
|
|
70
|
+
* costs. `after` is the symbolic id of the variable that replaces it: a create that failed holds the removal back.
|
|
71
|
+
* `strip` names the person's copies of it, whose stamps are removed once it is (decisions, *A copy whose original
|
|
72
|
+
* Toghen removes*); the `BREAKING` row names them.
|
|
73
|
+
*/
|
|
74
|
+
| { kind: 'DELETE_VARIABLE'; variableId: string; variableName: string; collectionName: string; after?: string; strip?: string[] }
|
|
75
|
+
| {
|
|
76
|
+
kind: 'CREATE_VARIABLE';
|
|
77
|
+
symbolicId?: string;
|
|
78
|
+
collectionId: string;
|
|
79
|
+
path: string;
|
|
80
|
+
variableType: VariableResolvedDataType;
|
|
81
|
+
/** What the variable may be bound to. A variable is made with the scopes of its purpose, never with Figma's default of all. */
|
|
82
|
+
scopes?: VariableScope[];
|
|
83
|
+
/** The key the sync gives the variable: the token its stamp names. */
|
|
84
|
+
tokenPath?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Writes the scopes of a variable that exists: its purpose changed, or it still holds what older Toghen wrote.
|
|
88
|
+
* Not breaking. A variable whose scopes a designer changed gets no such operation, unless drift is overwritten.
|
|
89
|
+
* `path` is the key the sync gives the variable, the token its stamp names.
|
|
90
|
+
*/
|
|
91
|
+
| { kind: 'SET_VARIABLE_SCOPES'; variableId: string; scopes: VariableScope[]; path: string }
|
|
92
|
+
| {
|
|
93
|
+
kind: 'SET_VARIABLE_VALUE';
|
|
94
|
+
variableId: string;
|
|
95
|
+
modeId: string;
|
|
96
|
+
value: VariableValue;
|
|
97
|
+
label: string;
|
|
98
|
+
/**
|
|
99
|
+
* Token path and mode this value belongs to.
|
|
100
|
+
*
|
|
101
|
+
* Carried explicitly because the execute handler used to recover them by
|
|
102
|
+
* regex from `label`. That round trip is not a bijection: a token path
|
|
103
|
+
* segment may legally contain `/`, and a mode name may contain `]`, so
|
|
104
|
+
* the recovered path could differ from the real one — and the variable
|
|
105
|
+
* would then be stamped for a path that does not exist, recreating it on
|
|
106
|
+
* every run.
|
|
107
|
+
*/
|
|
108
|
+
tokenPath: string;
|
|
109
|
+
modeName: string;
|
|
110
|
+
}
|
|
111
|
+
| { kind: 'RENAME_VARIABLE'; variableId: string; name: string; path: string }
|
|
112
|
+
/**
|
|
113
|
+
* The name a stylesheet gives the variable, shown in Dev Mode: `var(--button-background-color)`.
|
|
114
|
+
* Web only: Android and iOS spellings are not Toghen's to invent.
|
|
115
|
+
*/
|
|
116
|
+
| { kind: 'SET_CODE_SYNTAX'; variableId: string; platform: 'WEB'; value: string; path: string }
|
|
117
|
+
/**
|
|
118
|
+
* `tokenPath` is carried for the same reason `SET_VARIABLE_VALUE` carries it.
|
|
119
|
+
* The execute handler used to derive it by turning the *live Figma style's*
|
|
120
|
+
* name back into a path, so a style a designer had renamed got its record
|
|
121
|
+
* written under a path the document does not contain — and the real path
|
|
122
|
+
* stayed permanently dirty. It is the token the style's stamp names.
|
|
123
|
+
*/
|
|
124
|
+
| {
|
|
125
|
+
kind: 'CREATE_STYLE';
|
|
126
|
+
styleType: 'TEXT' | 'EFFECT' | 'PAINT';
|
|
127
|
+
name: string;
|
|
128
|
+
props: StyleProps;
|
|
129
|
+
tokenPath: string;
|
|
130
|
+
bindings?: StyleBinding[];
|
|
131
|
+
}
|
|
132
|
+
| { kind: 'UPDATE_STYLE'; styleId: string; props: StyleProps; tokenPath: string; bindings?: StyleBinding[] }
|
|
133
|
+
/**
|
|
134
|
+
* Writes the stamp of a variable or a style Toghen wrote that moved, so that it records the object's own id
|
|
135
|
+
* (decisions, *Ruled: a copy carries the stamp*). Planned only where nothing else the plan writes to it stamps it;
|
|
136
|
+
* it changes nothing else. `path` is the token its stamp names.
|
|
137
|
+
*/
|
|
138
|
+
| { kind: 'STAMP'; target: 'variable' | 'style'; id: string; path: string }
|
|
139
|
+
| { kind: 'BIND_VARIABLE'; nodeId: string; property: string; variableId: string }
|
|
140
|
+
| { kind: 'UNBIND_VARIABLE'; nodeId: string; property: string }
|
|
141
|
+
/**
|
|
142
|
+
* Apply: one token on one field (a name of the field table) of one layer, written through the field writer. No stamp
|
|
143
|
+
* is written. `variableId` is the token's variable where the field binds it. `value` and `context` are set when the
|
|
144
|
+
* field, or part of it (a colour's alpha on a paint), is written as the token's value: the value at the declared
|
|
145
|
+
* defaults, and their mode name; `written` is the line that says so before Execute.
|
|
146
|
+
*/
|
|
147
|
+
| {
|
|
148
|
+
kind: 'APPLY_TOKEN';
|
|
149
|
+
nodeId: string;
|
|
150
|
+
nodeName: string;
|
|
151
|
+
field: string;
|
|
152
|
+
path: string;
|
|
153
|
+
variableId?: string;
|
|
154
|
+
/** A dash pattern's lengths, in pixels, for a `strokeStyle` token on `dashPattern`. */
|
|
155
|
+
value?: VariableValue | number[];
|
|
156
|
+
context?: string;
|
|
157
|
+
written?: string;
|
|
158
|
+
}
|
|
159
|
+
| {
|
|
160
|
+
kind: 'SKIP';
|
|
161
|
+
/** The sentence: what was left alone and why, naming the token and, where there is one, the context. */
|
|
162
|
+
reason: string;
|
|
163
|
+
path: string;
|
|
164
|
+
/** Why, as one of a closed set, for counting and grouping. Every skip of the variable and style plans carries one; the other plans' skips do not yet. */
|
|
165
|
+
cause?: SkipCause;
|
|
166
|
+
/**
|
|
167
|
+
* Detail the short `reason` cannot carry.
|
|
168
|
+
*
|
|
169
|
+
* A drifted path used to emit a `WARNING` explaining itself *and* a
|
|
170
|
+
* `SKIP` recording the outcome — two rows for one thing that happened,
|
|
171
|
+
* and a summary saying the same number twice as `4 drifted · 4 warnings`.
|
|
172
|
+
* The explanation belongs to the skip it explains.
|
|
173
|
+
*/
|
|
174
|
+
message?: string;
|
|
175
|
+
/** A skip the command line lists, as it lists a warning: a style that cannot be written, which no one wants to find by counting. */
|
|
176
|
+
reported?: true;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Something the plan says without it being a problem or a step: the variables left offered to every field.
|
|
180
|
+
* Shown by the dry run and in the run report beside the warnings, and not counted as one.
|
|
181
|
+
*/
|
|
182
|
+
| { kind: 'NOTE'; message: string }
|
|
183
|
+
/** A relink plan's row: one stamped instance a build would swap to another component. Nothing executes it; each file's Build does. */
|
|
184
|
+
| { kind: 'SWAP_COMPONENT'; path: string; message: string }
|
|
185
|
+
/** `stops`: the plan stopped before its first write, and the run that asked for it stops too. */
|
|
186
|
+
| { kind: 'WARNING'; message: string; path?: string; stops?: true }
|
|
187
|
+
/**
|
|
188
|
+
* A step that loses something in Figma, listed whether or not the plan carries it.
|
|
189
|
+
* It runs nothing: the operation that does the damage is a separate one, present only when breaking is allowed.
|
|
190
|
+
*/
|
|
191
|
+
| {
|
|
192
|
+
kind: 'BREAKING';
|
|
193
|
+
message: string;
|
|
194
|
+
/**
|
|
195
|
+
* A variable the plan removes because its token moved to another collection, by the collection it is in and its name.
|
|
196
|
+
* A dry run counts the removal, so that the name the token keeps is not found in two collections.
|
|
197
|
+
*/
|
|
198
|
+
moved?: { collection: string; name: string };
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* A plan. What it wrote is stamped on each variable and style as it is written,
|
|
203
|
+
* from what Figma reports back, so nothing travels beside the operations.
|
|
204
|
+
*/
|
|
205
|
+
export interface SyncPlan {
|
|
206
|
+
operations: Operation[];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface OperationResult {
|
|
210
|
+
operation: Operation;
|
|
211
|
+
status: 'applied' | 'skipped' | 'failed';
|
|
212
|
+
reason?: string;
|
|
213
|
+
figmaId?: string;
|
|
214
|
+
durationMs?: number;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface OperationSummary {
|
|
218
|
+
total: number;
|
|
219
|
+
applied: number;
|
|
220
|
+
/** Planned skips, prerequisite failures, and cancellations. A plan's warnings, notes and breaking rows are not counted here: the plan counted them under their own names. */
|
|
221
|
+
skipped: number;
|
|
222
|
+
failed: number;
|
|
223
|
+
/**
|
|
224
|
+
* Operations not attempted because the run was cancelled.
|
|
225
|
+
*
|
|
226
|
+
* Counted apart from `failed`: a user pressing Cancel used to produce
|
|
227
|
+
* hundreds of "errors" and suppress `lastSyncedAt`, which made a deliberate
|
|
228
|
+
* stop indistinguishable from a broken document.
|
|
229
|
+
*/
|
|
230
|
+
cancelled: number;
|
|
231
|
+
durationMs: number;
|
|
232
|
+
results: OperationResult[];
|
|
233
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Pending } from './components/steps.js';
|
|
2
|
+
import type { Operation } from './operations.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The names a run of plans would create, in the shape a component dry run takes as `pending`.
|
|
6
|
+
*
|
|
7
|
+
* `operations` are those of a variable plan and a style plan, in the order they were made. A
|
|
8
|
+
* `CREATE_VARIABLE` names its collection by id, which is the id of one that exists
|
|
9
|
+
* (`collections`, as `GET_FIGMA_COLLECTIONS` answers) or the symbolic id of one the plan
|
|
10
|
+
* creates (`CREATE_COLLECTION`). Only text and effect styles are kept: they are the only styles a component file names.
|
|
11
|
+
* The modes of a collection the plan creates, and a mode it adds to one, are kept too, for a `modes` pin.
|
|
12
|
+
* A variable whose collection is neither is left out, since there is no name to give it.
|
|
13
|
+
* A variable a plan removes because its token moved to another collection (a `BREAKING` row that says so) is kept as removed.
|
|
14
|
+
*/
|
|
15
|
+
export function pendingNames(operations: readonly Operation[], collections: readonly { id: string; name: string }[]): Pending {
|
|
16
|
+
const collectionName = new Map(collections.map((collection) => [collection.id, collection.name]));
|
|
17
|
+
for (const operation of operations) {
|
|
18
|
+
if (operation.kind === 'CREATE_COLLECTION' && operation.symbolicId) collectionName.set(operation.symbolicId, operation.name);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const pending: Pending = { variables: [], textStyles: [], effectStyles: [], modes: [] };
|
|
22
|
+
for (const operation of operations) {
|
|
23
|
+
if (operation.kind === 'CREATE_COLLECTION') {
|
|
24
|
+
for (const mode of operation.modes) pending.modes!.push({ collection: operation.name, mode });
|
|
25
|
+
} else if (operation.kind === 'ADD_MODE') {
|
|
26
|
+
pending.modes!.push({ collection: operation.collectionName, mode: operation.modeName });
|
|
27
|
+
} else if (operation.kind === 'BREAKING' && operation.moved) {
|
|
28
|
+
(pending.removed ??= []).push(operation.moved);
|
|
29
|
+
} else if (operation.kind === 'CREATE_VARIABLE') {
|
|
30
|
+
const collection = collectionName.get(operation.collectionId);
|
|
31
|
+
if (collection !== undefined) pending.variables.push({ collection, name: operation.path, type: operation.variableType });
|
|
32
|
+
} else if (operation.kind === 'CREATE_STYLE' && operation.styleType === 'TEXT') {
|
|
33
|
+
pending.textStyles.push(operation.name);
|
|
34
|
+
} else if (operation.kind === 'CREATE_STYLE' && operation.styleType === 'EFFECT') {
|
|
35
|
+
pending.effectStyles!.push(operation.name);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return pending;
|
|
39
|
+
}
|