Skip to content

Commit

Permalink
Update to v1.2.3
Browse files Browse the repository at this point in the history
  • Loading branch information
figma-bot committed Dec 5, 2024
1 parent 24ef296 commit 3d2068b
Show file tree
Hide file tree
Showing 26 changed files with 235 additions and 2,572 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# Code Connect v1.2.3

## Features

## Fixed
- In the interactive setup, the automatic file linking now matches components exported from index files

### React
- Fix issue where React component references in `props` would serialize to strings when accessed with `getProps()`
- Fix issue with nesting `figma.boolean` and `getProps`

# Code Connect v1.2.2 (5th November 2024)

## Features
Expand Down
93 changes: 8 additions & 85 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,95 +9,18 @@ Code Connect is easy to set up, easy to maintain, type-safe, and extensible. Out
> [!NOTE]
> Code Connect is available on Organization and Enterprise plans and requires a full Design or Dev Mode seat to use.
## CLI installation
## Documentation

To install Code Connect locally to a React project, you can follow the instructions in the [React README](docs/react.md#installation).
Use Figma’s Code Connect to make your design system easily accessible to your developers and create a shared source of truth for both the design and code elements.

For other platforms, you first need to have Node.js v16 or newer installed on your computer. You can check if you already have Node.js installed and which version by running `node -v`. If you need to install Node.js, instructions for all platforms can be found [on the Node.js website](https://nodejs.org/en/download/package-manager).
Our Getting Started guide will walk you through the process of setting up Code Connect, using our interactive setup to automatically map components, and publishing your first components. **[Getting Started with Code Connect →](/quickstart-guide)**

Once you have Node.js installed, you can install Code Connect globally, so it can be run from anywhere on your machine, by running:
Code Connect comes with support for different frameworks and languages. Our integration guides will walk you through the process of refining your components by mapping props and variants for:

`npm install --global @figma/code-connect`
- **[React (and React Native) →](/react)**
- **[HTML (e.g. Web Components, Angular and Vue) →](/html)**
- **[SwiftUI →](/swiftui)**
- **[Jetpack Compose →](/compose)**.

We hope to provide a way to install Code Connect without requiring Node.js soon.

## Setup

To learn how to implement Code Connect for your platform, please navigate to the platform-specific API usage and documentation.

- [React (or React Native)](docs/react.md)
- [HTML (Web Components, Angular, Vue, etc.)](docs/html.md)
- [SwiftUI](docs/swiftui.md)
- [Jetpack Compose](docs/compose.md)

## General configuration

Code Connect can be configured with a `figma.config.json` file, which must be located in your project root (e.g. alongside the `package.json` or `.xcodeproj` file).

Every platform supports some common configuration options, in addition to any platform-specific options.

### `include` and `exclude`

`include` and `exclude` are lists of globs for where to parse Code Connect files, and for where to search for your component code when using the [interactive setup](docs/react.md#interactive-setup). `include` and `exclude` paths must be relative to the location of the config file.

```jsonp
{
"codeConnect": {
"include": [],
"exclude": ["test/**", "docs/**", "build/**"]
}
}
```

### `parser`

Code Connect will attempt to determine your project type by looking the first ancestor of the working directory which matches one of the following:

- If a `package.json` containing `react` is found, your project is detected as React
- If a `package.json` is found not containing `react`, your project is detected as HTML
- If a file matching `Package.swift` or `*.xcodeproj` is found, your project is detected as Swift
- If a file matching `build.gradle.kts` is found, your project is detected as Jetpack Compose

In case this does not correctly work for your project, you can override the project type by using the `parser` configuration key. Valid values are `react`, `html`, `swift` and `compose`.

```jsonp
{
"codeConnect": {
"parser": "react"
}
}
```

### `label`

`label` allows you to specify the label used in Figma for your Code Connect docs. This defaults to the type of your project (e.g. `React`). You can override this to show a different name in the UI, which can be useful for e.g. showing different versions of the code.

### `documentUrlSubstitutions`

`documentUrlSubstitutions` allows you to specify a set of substitutions which will be run on the `figmaNode` URLs when parsing or publishing documents.

This allows you to use different config files to switch publishing Code Connect between different files, without having to modify every Code Connect file (e.g. if you have a test version of your document you want to publish to). The substitutions are specified as an object, where the key is the string to be replaced, and the value is the string to replace that with.

For example, the config:

```
{
"codeConnect": {
"documentUrlSubstitutions": {
"https://figma.com/design/1234abcd/File-1": "https://figma.com/design/5678dcba/File-2"
}
}
}
```

would change Figma node URLs like `https://figma.com/design/1234abcd/File-1/?node-id=12:345` to `https://figma.com/design/5678dbca/File-2/?node-id=12:345`.

## Common issues

### Connectivity issues due to proxies or network security software

Some proxies or network security software can prevent Code Connect from communicating with Figma's servers. If you encounter issues, you may need to explicitly allow connections to `https://api.figma.com/`. Please reach out to [Figma support](https://help.figma.com/hc/en-us/requests/new) if you are still unable to use Code Connect.

### 413 errors due to too large uploads

Please rerun with the `--batch-size` parameter. This will upload the Code Connect in batches of documents of batch_size length. We suggest starting with 50 and decreasing until converging on a size that works for your Code Connect.
12 changes: 6 additions & 6 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.2",
"version": "1.2.3",
"description": "A tool for connecting your design system components in code with your design system in Figma",
"keywords": [],
"author": "Figma",
Expand Down Expand Up @@ -32,7 +32,7 @@
"dist/**/*"
],
"engines": {
"node": ">=16"
"node": ">=18"
},
"scripts": {
"dev": "tsx src/cli.ts",
Expand Down Expand Up @@ -81,10 +81,10 @@
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@babel/core": "7.25.8",
"@babel/generator": "7.25.7",
"@babel/parser": "7.25.8",
"@babel/types": "7.25.8",
"@babel/core": "7.26.0",
"@babel/generator": "7.26.2",
"@babel/parser": "7.26.0",
"@babel/types": "7.26.0",

"@storybook/csf-tools": "^7.6.7",
"boxen": "5.1.1",
Expand Down
36 changes: 35 additions & 1 deletion cli/src/commands/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export function addConnectCommandToProgram(program: commander.Command) {
)
.option('--skip-validation', 'skip validation of Code Connect docs')
.option('-l --label <label>', 'label to apply to the published files')
.option('--include-raw-templates', 'flag to include any raw figma.template.js files')
.option(
'-b --batch-size <batch_size>',
'optional batch size (in number of documents) to use when uploading. Use this if you hit "request too large" errors. See README for more information.',
Expand All @@ -105,6 +106,7 @@ export function addConnectCommandToProgram(program: commander.Command) {
'specify the node to unpublish. This will unpublish for both React and Storybook.',
)
.option('-l --label <label>', 'label to unpublish for')
.option('--include-raw-templates', 'flag to include any raw figma.template.js files')
.action(withUpdateCheck(handleUnpublish))

addBaseCommand(
Expand All @@ -113,6 +115,7 @@ export function addConnectCommandToProgram(program: commander.Command) {
'Run Code Connect locally to find any files that have figma connections, then converts them to JSON and outputs to stdout.',
)
.option('-l --label <label>', 'label to apply to the parsed files')
.option('--include-raw-templates', 'flag to include any raw figma.template.js files')
.action(withUpdateCheck(handleParse))

addBaseCommand(
Expand Down Expand Up @@ -184,8 +187,29 @@ function transformDocFromParser(
}
}

export function parseRawFile(filePath: string, label: string | undefined): CodeConnectJSON {
const fileContent = fs.readFileSync(filePath, 'utf-8')
const [firstLine, ...templateLines] = fileContent.split('\n')
const figmaNodeUrl = firstLine.replace(/\/\/\s*url=/, '').trim()
const template = templateLines.join('\n')

return {
figmaNode: figmaNodeUrl,
template,
// nestable by default unless user specifies otherwise
templateData: { nestable: true },
language: 'raw',
label: label || 'Code',
source: '',
sourceLocation: { line: -1 },
metadata: {
cliVersion: require('../../package.json').version,
},
}
}

export async function getCodeConnectObjects(
cmd: BaseCommand & { label?: string },
cmd: BaseCommand & { label?: string; includeRawTemplates?: boolean },
projectInfo: ProjectInfo,
silent = false,
): Promise<CodeConnectJSON[]> {
Expand Down Expand Up @@ -251,6 +275,16 @@ export async function getCodeConnectObjects(
}
}

if (cmd.includeRawTemplates) {
const rawTemplateFiles = projectInfo.files.filter((f: string) =>
f.endsWith('.figma.template.js'),
)

const rawTemplateDocs = rawTemplateFiles.map((file) => parseRawFile(file, cmd.label))

codeConnectObjects = codeConnectObjects.concat(rawTemplateDocs)
}

if (cmd.label || projectInfo.config.label) {
logger.info(`Using label "${cmd.label || projectInfo.config.label}"`)
codeConnectObjects.forEach((doc) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"codeConnect": {
"parser": "react",
"importPaths": {},
"paths": {},
"include": [
"*.{tsx, jsx}",
"*.figma.template.js"
],
"exclude": []
}
}
15 changes: 15 additions & 0 deletions cli/src/connect/__test__/e2e/e2e_parse_command/raw/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "tests",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"react": "^18.3.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// url=https://figma.com/design/abc?node=1:1
const figma = require('figma')
const text = figma.currentLayer.__properties__.string('Text')

export default figma.code`def python_code():
return ${text}`
4 changes: 4 additions & 0 deletions cli/src/connect/__test__/e2e/e2e_parse_command_custom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe('e2e test for `parse` command custom parsers', () => {

expect(tidyStdOutput(result.stderr)).toBe(
`Config file found, parsing ./e2e_parse_command/custom_parser using specified include globs
Using custom parser command: node parser/custom_parser.js
Running parser: node parser/custom_parser.js
Debug message from parser!
Success from parser!`,
Expand All @@ -54,6 +55,7 @@ Success from parser!`,

expect(tidyStdOutput(result.stderr)).toBe(
`Config file found, parsing ./e2e_parse_command/custom_parser_warning using specified include globs
Using custom parser command: node parser/custom_parser.js
Warning from parser!`,
)

Expand All @@ -72,6 +74,7 @@ Warning from parser!`,
expect(e.code).toBe(1)
expect(tidyStdOutput(e.stderr)).toBe(
`Config file found, parsing ./e2e_parse_command/custom_parser_error using specified include globs
Using custom parser command: node parser/custom_parser.js
Error from parser!
Errors encountered calling parser, exiting`,
)
Expand All @@ -90,6 +93,7 @@ Errors encountered calling parser, exiting`,
expect(e.code).toBe(1)
expect(tidyStdOutput(e.stderr)).toBe(
`Config file found, parsing ./e2e_parse_command/custom_parser_invalid_response using specified include globs
Using custom parser command: node parser/custom_parser.js
Error returned from parser: Validation error: Required at "docs[0].figmaNode"; Required at "docs[0].template"; Required at "docs[0].templateData"; Required at "docs[0].language"; Required at "docs[0].label". Try re-running the command with --verbose for more information.`,
)
}
Expand Down
45 changes: 45 additions & 0 deletions cli/src/connect/__test__/e2e/e2e_parse_command_raw.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { promisify } from 'util'
import { exec } from 'child_process'
import { tidyStdOutput } from '../../../__test__/utils'
import path from 'path'

describe('e2e test for `parse` command (raw)', () => {
const cliVersion = require('../../../../package.json').version

it('successfully parses raw template file', async () => {
const testPath = path.join(__dirname, 'e2e_parse_command/raw')

const result = await promisify(exec)(
`npx tsx ../../../cli connect parse --skip-update-check --dir ${testPath} --include-raw-templates`,
{
cwd: __dirname,
},
)

expect(tidyStdOutput(result.stderr)).toBe(
`Config file found, parsing ${testPath} using specified include globs`,
)

const json = JSON.parse(result.stdout)

expect(json).toMatchObject([
{
figmaNode: 'https://figma.com/design/abc?node=1:1',
label: 'Code',
language: 'raw',
source: '',
sourceLocation: { line: -1 },
template: `const figma = require('figma')
const text = figma.currentLayer.__properties__.string('Text')
export default figma.code\`def python_code():
return \${text}\`
`,
templateData: {},
metadata: {
cliVersion,
},
},
])
})
})
21 changes: 20 additions & 1 deletion cli/src/connect/intrinsics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { InternalError, ParserContext, ParserError } from './parser_common'
import {
assertIsArrayLiteralExpression,
assertIsStringLiteral,
isUndefinedType,
stripQuotesFromNode,
} from '../typescript/compiler'
import { convertObjectLiteralToJs } from '../typescript/compiler'
Expand Down Expand Up @@ -505,7 +506,9 @@ export function intrinsicToString(
// `const propName = figma.properties.instance('propName')`
if (modifiers.length > 0) {
const instance = `${selector}.__properties__.__instance__('${args.figmaPropName}')`
return [instance, ...modifiers.map(modifierToString)].join('.')
let body = `const instance = ${instance}\n`
body += `return instance && instance.type !== "ERROR" ? ${['instance', ...modifiers.map(modifierToString)].join('.')} : instance`
return `(function () {${body}})()`
}
return `${selector}.__properties__.instance('${args.figmaPropName}')`
}
Expand Down Expand Up @@ -555,6 +558,15 @@ ${Object.entries(args.props).map(
}
}

/**
* Converts an expression to an FCC value, which is a wrapper around the actual value that
* includes the type information. This is used to serialize the value to JSON and then
* deserialize it back to the correct type in the generated code.
*
* @param valueNode
* @param parserContext
* @returns
*/
function expressionToFccEnumValue(
valueNode: ts.Expression,
parserContext: ParserContext,
Expand All @@ -581,10 +593,17 @@ function expressionToFccEnumValue(
return _fcc_templateString(str)
}

// Handles enums, for example `MyEnum.Value`
if (ts.isPropertyAccessExpression(valueNode)) {
return _fcc_identifier(valueNode.getText())
}

// Any other identifiers (except undefined) are treated as React components, for example `MyComponent`.
// We don't support referencing other variables in props object so this should be fine.
if (ts.isIdentifier(valueNode) && !isUndefinedType(valueNode, parserContext.checker)) {
return _fcc_identifier(valueNode.getText())
}

// Fall back to the default conversion in `convertObjectLiteralToJs`
return undefined
}
Expand Down
1 change: 1 addition & 0 deletions cli/src/connect/parser_executables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const FIRST_PARTY_PARSERS: Record<CodeConnectExecutableParser, ParserInfo> = {
'No `parserCommand` specified in config. A command is required when using the `custom` parser.',
)
}
logger.info('Using custom parser command: ' + config.parserCommand)
return config.parserCommand
},
},
Expand Down
Loading

0 comments on commit 3d2068b

Please sign in to comment.