Skip to content

Commit

Permalink
Replace custom REST API types with @figma/rest-api-spec types (#19)
Browse files Browse the repository at this point in the history
Now that we have official Typescript types for the REST API at @figma/rest-api-spec, use those types instead of the custom types we previously wrote.
  • Loading branch information
james04321 authored Feb 22, 2024
1 parent 5cec84f commit e881303
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 152 deletions.
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"typescript": "^5.1.6"
},
"devDependencies": {
"@figma/rest-api-spec": "^0.10.0",
"@types/jest": "^29.5.3",
"@types/node": "^20.4.5",
"jest": "^29.6.2",
Expand Down
12 changes: 5 additions & 7 deletions src/color.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Color } from './figma_api.js'
import { RGB, RGBA } from '@figma/rest-api-spec'

/**
* Compares two colors for approximate equality since converting between Figma RGBA objects (from 0 -> 1) and
* hex colors can result in slight differences.
*/
export function colorApproximatelyEqual(colorA: Color, colorB: Color) {
export function colorApproximatelyEqual(colorA: RGB | RGBA, colorB: RGB | RGBA) {
return rgbToHex(colorA) === rgbToHex(colorB)
}

export function parseColor(color: string): Color {
export function parseColor(color: string): RGB | RGBA {
color = color.trim()
const hexRegex = /^#([A-Fa-f0-9]{6})([A-Fa-f0-9]{2}){0,1}$/
const hexShorthandRegex = /^#([A-Fa-f0-9]{3})([A-Fa-f0-9]){0,1}$/
Expand Down Expand Up @@ -36,10 +36,8 @@ export function parseColor(color: string): Color {
}
}

export function rgbToHex({ r, g, b, a }: Color) {
if (a === undefined) {
a = 1
}
export function rgbToHex({ r, g, b, ...rest }: RGB | RGBA) {
const a = 'a' in rest ? rest.a : 1

const toHex = (value: number) => {
const hex = Math.round(value * 255).toString(16)
Expand Down
117 changes: 8 additions & 109 deletions src/figma_api.ts
Original file line number Diff line number Diff line change
@@ -1,110 +1,9 @@
import axios from 'axios'

export interface VariableMode {
modeId: string
name: string
}

export interface VariableModeChange {
action: 'CREATE' | 'UPDATE' | 'DELETE'
id?: string
name?: string
variableCollectionId: string
}

export interface VariableCollection {
id: string
name: string
modes: VariableMode[]
defaultModeId: string
remote: boolean
hiddenFromPublishing: boolean
}

export interface VariableCollectionChange
extends Partial<Pick<VariableCollection, 'id' | 'name' | 'hiddenFromPublishing'>> {
action: 'CREATE' | 'UPDATE' | 'DELETE'
initialModeId?: string
}

export interface Color {
r: number
g: number
b: number
a?: number
}

interface VariableAlias {
type: 'VARIABLE_ALIAS'
id: string
}

export type VariableValue = boolean | number | string | Color | VariableAlias

export type VariableScope = 'ALL_SCOPES' | VariableFloatScopes | VariableColorScopes
type VariableFloatScopes = 'TEXT_CONTENT' | 'WIDTH_HEIGHT' | 'GAP'
type VariableColorScopes = 'ALL_FILLS' | 'FRAME_FILL' | 'SHAPE_FILL' | 'TEXT_FILL' | 'STROKE_COLOR'

export type VariableCodeSyntax = { WEB?: string; ANDROID?: string; iOS?: string }

export interface Variable {
id: string
name: string
key: string
variableCollectionId: string
resolvedType: 'BOOLEAN' | 'FLOAT' | 'STRING' | 'COLOR'
valuesByMode: { [modeId: string]: VariableValue }
remote: boolean
description: string
hiddenFromPublishing: boolean
scopes: VariableScope[]
codeSyntax: VariableCodeSyntax
}

export interface VariableChange
extends Partial<
Pick<
Variable,
| 'id'
| 'name'
| 'variableCollectionId'
| 'resolvedType'
| 'description'
| 'hiddenFromPublishing'
| 'scopes'
| 'codeSyntax'
>
> {
action: 'CREATE' | 'UPDATE' | 'DELETE'
}

export interface VariableModeValue {
variableId: string
modeId: string
value: VariableValue
}

export interface ApiGetLocalVariablesResponse {
status: number
error: boolean
meta: {
variableCollections: { [id: string]: VariableCollection }
variables: { [id: string]: Variable }
}
}

export interface ApiPostVariablesPayload {
variableCollections?: VariableCollectionChange[]
variableModes?: VariableModeChange[]
variables?: VariableChange[]
variableModeValues?: VariableModeValue[]
}

interface ApiPostVariablesResponse {
status: number
error: boolean
meta: { tempIdToRealId: { [tempId: string]: string } }
}
import {
GetLocalVariablesResponse,
PostVariablesRequestBody,
PostVariablesResponse,
} from '@figma/rest-api-spec'

export default class FigmaApi {
private baseUrl = 'https://api.figma.com'
Expand All @@ -115,7 +14,7 @@ export default class FigmaApi {
}

async getLocalVariables(fileKey: string) {
const resp = await axios.request<ApiGetLocalVariablesResponse>({
const resp = await axios.request<GetLocalVariablesResponse>({
url: `${this.baseUrl}/v1/files/${fileKey}/variables/local`,
headers: {
Accept: '*/*',
Expand All @@ -126,8 +25,8 @@ export default class FigmaApi {
return resp.data
}

async postVariables(fileKey: string, payload: ApiPostVariablesPayload) {
const resp = await axios.request<ApiPostVariablesResponse>({
async postVariables(fileKey: string, payload: PostVariablesRequestBody) {
const resp = await axios.request<PostVariablesResponse>({
url: `${this.baseUrl}/v1/files/${fileKey}/variables`,
method: 'POST',
headers: {
Expand Down
14 changes: 10 additions & 4 deletions src/token_export.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ApiGetLocalVariablesResponse } from './figma_api.js'
import { GetLocalVariablesResponse } from '@figma/rest-api-spec'
import { tokenFilesFromLocalVariables } from './token_export.js'

describe('tokenFilesFromLocalVariables', () => {
it('ignores remote variables', () => {
const localVariablesResponse: ApiGetLocalVariablesResponse = {
const localVariablesResponse: GetLocalVariablesResponse = {
status: 200,
error: false,
meta: {
Expand All @@ -14,7 +14,9 @@ describe('tokenFilesFromLocalVariables', () => {
modes: [{ modeId: '1:0', name: 'mode1' }],
defaultModeId: '1:0',
remote: true,
key: 'variableKey',
hiddenFromPublishing: false,
variableIds: ['VariableID:2:1'],
},
},
variables: {
Expand Down Expand Up @@ -42,7 +44,7 @@ describe('tokenFilesFromLocalVariables', () => {
})

it('returns token files', () => {
const localVariablesResponse: ApiGetLocalVariablesResponse = {
const localVariablesResponse: GetLocalVariablesResponse = {
status: 200,
error: false,
meta: {
Expand All @@ -56,7 +58,9 @@ describe('tokenFilesFromLocalVariables', () => {
],
defaultModeId: '1:0',
remote: false,
key: 'variableKey',
hiddenFromPublishing: false,
variableIds: ['VariableID:2:1', 'VariableID:2:2', 'VariableID:2:3', 'VariableID:2:4'],
},
},
variables: {
Expand Down Expand Up @@ -246,7 +250,7 @@ describe('tokenFilesFromLocalVariables', () => {
})

it('handles aliases', () => {
const localVariablesResponse: ApiGetLocalVariablesResponse = {
const localVariablesResponse: GetLocalVariablesResponse = {
status: 200,
error: false,
meta: {
Expand All @@ -260,7 +264,9 @@ describe('tokenFilesFromLocalVariables', () => {
],
defaultModeId: '1:0',
remote: false,
key: 'variableKey',
hiddenFromPublishing: false,
variableIds: ['VariableID:2:1', 'VariableID:2:2'],
},
},
variables: {
Expand Down
10 changes: 5 additions & 5 deletions src/token_export.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { GetLocalVariablesResponse, LocalVariable } from '@figma/rest-api-spec'
import { rgbToHex } from './color.js'
import { ApiGetLocalVariablesResponse, Variable } from './figma_api.js'
import { Token, TokensFile } from './token_types.js'

function tokenTypeFromVariable(variable: Variable) {
function tokenTypeFromVariable(variable: LocalVariable) {
switch (variable.resolvedType) {
case 'BOOLEAN':
return 'boolean'
Expand All @@ -16,9 +16,9 @@ function tokenTypeFromVariable(variable: Variable) {
}

function tokenValueFromVariable(
variable: Variable,
variable: LocalVariable,
modeId: string,
localVariables: { [id: string]: Variable },
localVariables: { [id: string]: LocalVariable },
) {
const value = variable.valuesByMode[modeId]
if (typeof value === 'object') {
Expand All @@ -35,7 +35,7 @@ function tokenValueFromVariable(
}
}

export function tokenFilesFromLocalVariables(localVariablesResponse: ApiGetLocalVariablesResponse) {
export function tokenFilesFromLocalVariables(localVariablesResponse: GetLocalVariablesResponse) {
const tokenFiles: { [fileName: string]: TokensFile } = {}
const localVariableCollections = localVariablesResponse.meta.variableCollections
const localVariables = localVariablesResponse.meta.variables
Expand Down
24 changes: 17 additions & 7 deletions src/token_import.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApiGetLocalVariablesResponse } from './figma_api.js'
import { GetLocalVariablesResponse } from '@figma/rest-api-spec'
import {
FlattenedTokensByFile,
generatePostVariablesPayload,
Expand Down Expand Up @@ -124,7 +124,7 @@ describe('readJsonFiles', () => {

describe('generatePostVariablesPayload', () => {
it('does an initial sync', async () => {
const localVariablesResponse = {
const localVariablesResponse: GetLocalVariablesResponse = {
status: 200,
error: false,
meta: {
Expand Down Expand Up @@ -305,7 +305,7 @@ describe('generatePostVariablesPayload', () => {
})

it('does an in-place update', async () => {
const localVariablesResponse: ApiGetLocalVariablesResponse = {
const localVariablesResponse: GetLocalVariablesResponse = {
status: 200,
error: false,
meta: {
Expand All @@ -316,7 +316,9 @@ describe('generatePostVariablesPayload', () => {
modes: [{ modeId: '1:0', name: 'mode1' }],
defaultModeId: '1:0',
remote: false,
key: 'variableKey',
hiddenFromPublishing: false,
variableIds: ['VariableID:2:1', 'VariableID:2:2', 'VariableID:2:3', 'VariableID:2:4'],
},
},
variables: {
Expand Down Expand Up @@ -537,7 +539,7 @@ describe('generatePostVariablesPayload', () => {
})

it('noops when everything is already in sync (with aliases)', () => {
const localVariablesResponse: ApiGetLocalVariablesResponse = {
const localVariablesResponse: GetLocalVariablesResponse = {
status: 200,
error: false,
meta: {
Expand All @@ -548,7 +550,9 @@ describe('generatePostVariablesPayload', () => {
modes: [{ modeId: '1:0', name: 'mode1' }],
defaultModeId: '1:0',
remote: false,
key: 'variableKey',
hiddenFromPublishing: false,
variableIds: ['VariableID:2:1', 'VariableID:2:2'],
},
},
variables: {
Expand Down Expand Up @@ -626,7 +630,7 @@ describe('generatePostVariablesPayload', () => {
})

it('ignores remote collections and variables', () => {
const localVariablesResponse: ApiGetLocalVariablesResponse = {
const localVariablesResponse: GetLocalVariablesResponse = {
status: 200,
error: false,
meta: {
Expand All @@ -637,7 +641,9 @@ describe('generatePostVariablesPayload', () => {
modes: [{ modeId: '1:0', name: 'mode1' }],
defaultModeId: '1:0',
remote: true,
key: 'variableKey',
hiddenFromPublishing: false,
variableIds: ['VariableID:2:1', 'VariableID:2:2'],
},
},
variables: {
Expand Down Expand Up @@ -720,7 +726,7 @@ describe('generatePostVariablesPayload', () => {
})

it('throws on unsupported token types', async () => {
const localVariablesResponse = {
const localVariablesResponse: GetLocalVariablesResponse = {
status: 200,
error: false,
meta: {
Expand All @@ -741,7 +747,7 @@ describe('generatePostVariablesPayload', () => {
})

it('throws on duplicate variable collections in the Figma file', () => {
const localVariablesResponse: ApiGetLocalVariablesResponse = {
const localVariablesResponse: GetLocalVariablesResponse = {
status: 200,
error: false,
meta: {
Expand All @@ -752,15 +758,19 @@ describe('generatePostVariablesPayload', () => {
modes: [{ modeId: '1:0', name: 'mode1' }],
defaultModeId: '1:0',
remote: false,
key: 'variableCollectionKey1',
hiddenFromPublishing: false,
variableIds: [],
},
'VariableCollectionId:1:2': {
id: 'VariableCollectionId:1:2',
name: 'collection',
modes: [{ modeId: '2:0', name: 'mode1' }],
defaultModeId: '2:0',
remote: false,
key: 'variableCollectionKey2',
hiddenFromPublishing: false,
variableIds: [],
},
},
variables: {},
Expand Down
Loading

0 comments on commit e881303

Please sign in to comment.