Skip to content

Commit

Permalink
Code Connect v1.2.1
Browse files Browse the repository at this point in the history
  • Loading branch information
figma-bot committed Oct 23, 2024
1 parent 659795a commit 60b9be4
Show file tree
Hide file tree
Showing 45 changed files with 437 additions and 167 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
# Code Connect v1.2.1 (23rd October 2024)

## Fixed

### React
- Fixed a bug introduced in 1.2.0 where `nestedProps` referencing a hidden layer would result in an error rendering Code Connect

### SwiftUI
- Fixed potential "index is out of bounds" error.

### General
- Changed how the extension makes HTTP requests to resolve issues when connecting through a proxy. Please [submit a support ticket](https://help.figma.com/hc/en-us/requests/new?ticket_form_id=360001744374) if you continue to have connection issues after this update.

### Compose

- Fixed some parsing errors when running the `create` and `publish` commands

# Code Connect v1.2.0

## Features
Expand All @@ -19,6 +36,8 @@
- Case of attribute names is now preserved to support Angular (fixes https://github.com/figma/code-connect/issues/172)
- Fixed a bug with `nestedProps` (fixes https://github.com/figma/code-connect/issues/176)

## Fixed

# Code Connect v1.1.4 (26th September 2024)

## Fixed
Expand Down
15 changes: 8 additions & 7 deletions cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@figma/code-connect",
"version": "1.2.0",
"version": "1.2.1",
"description": "A tool for connecting your design system components in code with your design system in Figma",
"keywords": [],
"author": "Figma",
Expand Down Expand Up @@ -61,6 +61,7 @@
"benchmarking:run": "npx tsx ./src/connect/wizard/__test__/prop_mapping/prop_mapping_benchmarking.ts"
},
"devDependencies": {

"@types/cross-spawn": "^6.0.6",
"@types/jest": "^29.5.13",
"@types/jsdom": "^21.1.7",
Expand All @@ -80,13 +81,12 @@
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/generator": "^7.25.2",
"@babel/parser": "^7.25.2",
"@babel/types": "^7.25.2",
"@babel/core": "7.25.8",
"@babel/generator": "7.25.7",
"@babel/parser": "7.25.8",
"@babel/types": "7.25.8",

"@storybook/csf-tools": "^7.6.7",
"axios": "^1.7.4",
"boxen": "5.1.1",
"chalk": "^4.1.2",
"commander": "^11.1.0",
Expand All @@ -106,7 +106,8 @@
"strip-ansi": "^6.0.0",
"ts-morph": "^23.0.0",
"typescript": "5.4.2",
"zod": "^3.23.6",
"undici": "^5.28.4",
"zod": "^3.23.8",
"zod-validation-error": "^3.2.0"
}
}
1 change: 0 additions & 1 deletion cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import * as commander from 'commander'
import { addConnectCommandToProgram } from './commands/connect'
import { updateCli } from './common/updates'
import { maybePrefillWizardQuestionsForTesting } from './connect/wizard/helpers'

require('dotenv').config()
Expand Down
1 change: 0 additions & 1 deletion cli/src/commands/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { createCodeConnectFromUrl } from '../connect/create'
import {
CodeConnectConfig,
CodeConnectExecutableParserConfig,
CodeConnectHtmlConfig,
CodeConnectReactConfig,
ProjectInfo,
getProjectInfo,
Expand Down
97 changes: 97 additions & 0 deletions cli/src/common/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@

import { ProxyAgent, setGlobalDispatcher } from 'undici'

type Method = 'POST' | 'GET' | 'PUT' | 'DELETE' | 'post' | 'get' | 'put' | 'delete'

type Options<OptionsT extends RequestInit = RequestInit> = OptionsT & {
/* Set the query string of the request from an object */
query?: Record<string, any>
}

class FetchError extends Error {
constructor(
public response: Response,
public data: Record<any, any> | undefined,
) {
super()
}
}

export const isFetchError = (error: unknown): error is FetchError => {
return error instanceof FetchError
}

export function getProxyUrl() {
return (
process.env.HTTPS_PROXY ||
process.env.HTTP_PROXY ||
process.env.https_proxy ||
process.env.http_proxy
)
}

/**
* Creates a ProxyAgent and sets it as the global dispatcher via unidici (which
* affects fetch calls) if a proxy is set either in VS Code settings or as an
* environment variable.
*/
const proxyUrl = getProxyUrl()
const agent = proxyUrl ? new ProxyAgent({ uri: proxyUrl }) : undefined
if (agent) {
setGlobalDispatcher(agent)
}

/**
* Makes a request to the Figma API. This is used by other functions to make
* various types of requests. We return both the response object, and the data
* parsed as JSON, to make it easier to work with the response.
*/
async function makeRequestInternal<ResponseT = unknown>(
url: string,
method: Method,
options: Options = {},
body?: Record<any, any>,
) {
const urlObj = new URL(url)
if (options?.query) {
Object.entries(options.query).forEach(([key, value]) => {
urlObj.searchParams.append(key, value as string)
})
}
url = urlObj.toString()

if (body) {
options.body = JSON.stringify(body)
}

const response = await fetch(url, { ...options, method })
if (!response.ok) {
let data
try {
data = await response.json()
} catch (e) {
data = undefined
}
throw new FetchError(response, data)
}

const text = await response.text()
const data = text ? (JSON.parse(text) as ResponseT) : ({} as ResponseT)

return { response, data }
}

export const request = {
get: <MetaT>(url: string, options: Options = {}) => {
return makeRequestInternal<MetaT>(url, 'GET', options)
},
post: <MetaT>(url: string, body: Record<any, any>, options: Options = {}) => {
return makeRequestInternal<MetaT>(url, 'POST', options, body)
},
put: <MetaT>(url: string, body: Record<any, any>, options: Options = {}) => {
return makeRequestInternal<MetaT>(url, 'PUT', options, body)
},
delete: <MetaT>(url: string, body?: Record<any, any>, options: Options = {}) => {
return makeRequestInternal<MetaT>(url, 'DELETE', options, body)
},
}
8 changes: 4 additions & 4 deletions cli/src/common/updates.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import chalk from 'chalk'
import { logger } from './logging'
import { execSync, spawnSync } from 'child_process'
import axios from 'axios'
import { execSync } from 'child_process'
import { compareVersions } from 'compare-versions'
import { BaseCommand } from '../commands/connect'
import { Command } from 'commander'
import { request } from './fetch'

let updatedVersionAvailable: string | false | undefined = undefined
let message: string | undefined = undefined
Expand Down Expand Up @@ -44,8 +44,8 @@ export function withUpdateCheck<T extends BaseCommand>(
// Start checking for updates in the background. We don't wait for this before
// running the action, as we will show the result at the end
function startUpdateCheck() {
axios
.get('https://api.github.com/repos/figma/code-connect/releases/latest')
request
.get<{ tag_name: string }>('https://api.github.com/repos/figma/code-connect/releases/latest')
.then((response) => {
const latestVersion = response.data.tag_name.replace(/^v/, '')
const currentVersion = require('../../package.json').version
Expand Down
22 changes: 12 additions & 10 deletions cli/src/connect/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import {
parseFileKey,
parseNodeIds,
} from './helpers'
import axios, { isAxiosError } from 'axios'
import fs from 'fs'
import { getApiUrl, getHeaders } from './figma_rest_api'
import { FigmaRestApi, getApiUrl, getHeaders } from './figma_rest_api'
import { exitWithError, logger } from '../common/logging'
import { callParser, handleMessages } from './parser_executables'
import { CodeConnectExecutableParserConfig, ProjectInfo } from './project'
Expand All @@ -15,6 +14,7 @@ import { z } from 'zod'
import { CreateRequestPayload, CreateResponsePayload } from './parser_executable_types'
import { fromError } from 'zod-validation-error'
import { createHtmlCodeConnect } from '../html/create'
import { isFetchError, request } from '../common/fetch'
interface GenerateDocsArgs {
accessToken: string
figmaNodeUrl: string
Expand Down Expand Up @@ -60,16 +60,16 @@ export async function createCodeConnectFromUrl({
logger.info('Fetching component information from Figma...')
const response = process.env.CODE_CONNECT_MOCK_CREATE_API_RESPONSE
? {
status: 200,
response: { status: 200 },
data: JSON.parse(
fs.readFileSync(process.env.CODE_CONNECT_MOCK_CREATE_API_RESPONSE, 'utf-8'),
),
) as { document: FigmaRestApi.Node },
}
: await axios.get(apiUrl, {
: await request.get<{ document: FigmaRestApi.Node }>(apiUrl, {
headers: getHeaders(accessToken),
})

if (response.status === 200) {
if (response.response.status === 200) {
logger.info('Parsing response')
const component = findComponentsInDocument(response.data.document, nodeIds)[0]
if (component === undefined) {
Expand Down Expand Up @@ -127,19 +127,21 @@ export async function createCodeConnectFromUrl({
logger.info(`${file.filePath}`)
})
} else {
logger.error(`Failed to get node information from Figma with status: ${response.status}`)
logger.error(
`Failed to get node information from Figma with status: ${response.response.status}`,
)
logger.debug('Failed to get node information from Figma with Body:', response.data)
}
} catch (err) {
if (isAxiosError(err)) {
if (isFetchError(err)) {
if (err.response) {
logger.error(
`Failed to get node data from Figma (${err.code}): ${err.response?.status} ${err.response?.data?.err ?? err.response?.data?.message}`,
`Failed to get node data from Figma (${err.response.status}): ${err.response.status} ${err.data?.err ?? err.data?.message}`,
)
} else {
logger.error(`Failed to get node data from Figma: ${err.message}`)
}
logger.debug(JSON.stringify(err.response?.data))
logger.debug(JSON.stringify(err.data))
} else {
logger.error(`Failed to create: ${err}`)
}
Expand Down
23 changes: 12 additions & 11 deletions cli/src/connect/delete_docs.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { CodeConnectJSON } from '../connect/figma_connect'
import { logger, underline, highlight } from '../common/logging'
import axios, { isAxiosError } from 'axios'
import { isFetchError, request } from '../common/fetch'
import { logger } from '../common/logging'
import { getApiUrl, getHeaders } from './figma_rest_api'
import { get } from 'lodash'
import { exitWithFeedbackMessage } from './helpers'

interface NodesToDeleteInfo {
Expand All @@ -21,24 +19,27 @@ export async function delete_docs({ accessToken, docs }: Args) {
try {
logger.info(`Unpublishing Code Connect files from Figma...`)

const response = await axios.delete(apiUrl, {
headers: getHeaders(accessToken),
data: { nodes_to_delete: docs },
})
await request.delete(
apiUrl,
{ nodes_to_delete: docs },
{
headers: getHeaders(accessToken),
},
)

logger.info(
`Successfully deleted:\n${docs.map((doc) => `-> ${doc.figmaNode} (${doc.label})`).join('\n')}`,
)
} catch (err) {
if (isAxiosError(err)) {
if (isFetchError(err)) {
if (err.response) {
logger.error(
`Failed to upload to Figma (${err.code}): ${err.response?.status} ${err.response?.data?.err ?? err.response?.data?.message}`,
`Failed to upload to Figma (${err.response.status}): ${err.response.status} ${err.data?.err ?? err.data?.message}`,
)
} else {
logger.error(`Failed to upload to Figma: ${err.message}`)
}
logger.debug(JSON.stringify(err.response?.data))
logger.debug(JSON.stringify(err.data))
} else {
logger.error(`Failed to delete docs: ${err}`)
}
Expand Down
16 changes: 9 additions & 7 deletions cli/src/connect/figma_rest_api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import axios, { isAxiosError } from 'axios'
import { isFetchError, request } from '../common/fetch'
import { logger } from '../common/logging'

const version = require('../../package.json').version
Expand Down Expand Up @@ -54,28 +54,30 @@ export namespace FigmaRestApi {
export async function getDocument(url: string, accessToken: string): Promise<FigmaRestApi.Node> {
try {
logger.info('Fetching component information from Figma...')
const response = await axios.get(url, {
const response = await request.get<{ document: FigmaRestApi.Node }>(url, {
headers: getHeaders(accessToken),
})

if (response.status === 200) {
if (response.response.status === 200) {
logger.info('Successfully fetched component information from Figma')
return response.data.document
} else {
logger.error(`Failed to get node information from Figma with status: ${response.status}`)
logger.error(
`Failed to get node information from Figma with status: ${response.response.status}`,
)
logger.debug('Failed to get node information from Figma with Body:', response.data)
return Promise.reject()
}
} catch (err) {
if (isAxiosError(err)) {
if (isFetchError(err)) {
if (err.response) {
logger.error(
`Failed to get node data from Figma (${err.code}): ${err.response?.status} ${err.response?.data?.err ?? err.response?.data?.message}`,
`Failed to get node data from Figma (${err.response.status}): ${err.response.status} ${err.data?.err ?? err.data?.message}`,
)
} else {
logger.error(`Failed to get node data from Figma: ${err.message}`)
}
logger.debug(JSON.stringify(err.response?.data))
logger.debug(JSON.stringify(err.data))
} else {
logger.error(`Failed to create: ${err}`)
}
Expand Down
2 changes: 1 addition & 1 deletion cli/src/connect/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FigmaRestApi } from './figma_rest_api'
import * as url from 'url'
import { exitWithError, logger } from '../common/logging'
import { logger } from '../common/logging'

const guidRegex = /^I?[0-9]+:[0-9]+(;[0-9]+:[0-9]+)*$/

Expand Down
2 changes: 1 addition & 1 deletion cli/src/connect/intrinsics.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as ts from 'typescript'
import { InternalError, ParserContext, ParserError, findDescendants } from './parser_common'
import { InternalError, ParserContext, ParserError } from './parser_common'
import {
assertIsArrayLiteralExpression,
assertIsStringLiteral,
Expand Down
Loading

0 comments on commit 60b9be4

Please sign in to comment.