Skip to content

Commit 204e84a

Browse files
author
figma-bot
committed
Code Connect v1.3.5
1 parent d2a69e9 commit 204e84a

File tree

14 files changed

+175
-51
lines changed

14 files changed

+175
-51
lines changed

CHANGELOG.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
1+
# Code Connect v1.3.5 (4th September 2025)
2+
3+
## Fixed
4+
5+
### General
6+
7+
- Fixed "Failed to fetch embeddings" error if empty payload in interactive setup
8+
- Retain the temporary file and directory when `--verbose` is enabled for improved debugging.
9+
10+
### SwiftUI
11+
12+
- Fixed SourcePackages directory not found.
13+
- Added optional sourcePackagesPath parameter for when the default derived data folder is not used.
14+
- Updated `swift-syntax` to support 601.0.1.
15+
116
# Code Connect v1.3.4 (26th June 2025)
217

3-
### Fixed
18+
## Fixed
419

5-
# React
20+
### React
621

722
- Support getProps inside nestedProps
823

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ let package = Package(
1515
.executable(name: "figma-swift", targets: ["CodeConnectCLI"]),
1616
],
1717
dependencies: [
18-
.package(url: "https://github.com/swiftlang/swift-syntax", "510.0.3"..."600.0.1"),
18+
.package(url: "https://github.com/swiftlang/swift-syntax", "510.0.3"..."601.0.1"),
1919
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"),
2020
.package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.55.3"),
2121
],

cli/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist/

cli/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@figma/code-connect",
3-
"version": "1.3.4",
3+
"version": "1.3.5",
44
"description": "A tool for connecting your design system components in code with your design system in Figma",
55
"keywords": [],
66
"author": "Figma",
@@ -38,7 +38,7 @@
3838
"dev": "tsx src/cli.ts",
3939
"build": "rm -rf dist && npm run typecheck && tsc",
4040
"build:web": "pnpm build",
41-
"build:webpack": "cross-env NODE_OPTIONS=\"--max-old-space-size=8196\" webpack --mode production",
41+
"build:webpack": "node scripts/resolve-dependencies.js && cross-env NODE_OPTIONS=\"--max-old-space-size=8196\" webpack --mode production",
4242
"test": "npm run test:no-coverage -- --coverage",
4343
"test:no-coverage": "cross-env NODE_OPTIONS=\"--experimental-vm-modules --no-deprecation --max-old-space-size=10240\" npx jest --logHeapUsage --workerIdleMemoryLimit=1.5G",
4444
"test:fast": "npm run test -- --testPathIgnorePatterns=template_rendering.test.ts --testPathIgnorePatterns=e2e_parse_command_swift.test.ts --testPathIgnorePatterns=e2e_wizard_swift.test.ts",
@@ -47,7 +47,7 @@
4747
"test:swift": "npm run test -- --runInBand --testPathPattern=e2e_parse_command_swift.test.ts --testPathPattern=e2e_wizard_swift.test.ts --testPathPattern=e2e_parse_command_swift_xcodeproj.test.ts",
4848
"test:non-mac": "npm run test -- --testPathIgnorePatterns=e2e_parse_command_swift.test.ts --testPathIgnorePatterns=e2e_wizard_swift.test.ts --testPathIgnorePatterns=e2e_parse_command_swift_xcodeproj.test.ts",
4949
"bundle": "npm run build && npm pack && mkdir -p bundle && mv figma-code-connect*.tgz bundle",
50-
"bundle:local": "cp package.json package.json.bak && grep -v 'workspace:' package.json > package.json.tmp && mv package.json.tmp package.json && npm run build && npm pack && mkdir -p bundle-local && mv figma-code-connect*.tgz bundle-local; mv package.json.bak package.json",
50+
"bundle:local": "cp package.json package.json.bak && node scripts/resolve-dependencies.js && npm run build && npm pack && mkdir -p bundle-local && mv figma-code-connect*.tgz bundle-local; mv package.json.bak package.json",
5151
"bundle:npm-readme:prepare": "mv README.md ../cli-README.md.bak && cp ../README.md . && npx tsx ../scripts/make_readme_links_absolute.ts",
5252
"bundle:npm-readme:restore": "mv ../cli-README.md.bak README.md",
5353
"bundle:npm": "npm run build && npm run bundle:npm-readme:prepare && npm pack && mkdir -p bundle-npm && mv figma-code-connect*.tgz bundle-npm; npm run bundle:npm-readme:restore",

cli/src/connect/__test__/e2e/e2e_parse_command/swift_parser/swift_parser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/src/connect/__test__/e2e/test_wizard_e2e.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ export function testWizardE2e(testCase: {
3232
// First, we need to ensure the Swift project has been built as we are
3333
// using a local version. We don't need to build the actual project itself
3434
// for this to work.
35-
stdout.write('Building Swift project, this may take a while the first time...\n')
35+
stdout.write(
36+
`Building Swift project at ${path.join(__dirname, '..', '..', '..', '..')}, this may take a while the first time...\n`,
37+
)
3638
execSync('swift build -c release', {
3739
cwd: path.join(__dirname, '..', '..', '..', '..'),
3840
})
@@ -85,7 +87,8 @@ export function testWizardE2e(testCase: {
8587
`
8688
: `{
8789
"codeConnect": {
88-
"include": ${JSON.stringify(testCase.expectedIncludeGlobs)}
90+
"include": ${JSON.stringify(testCase.expectedIncludeGlobs)},
91+
"interactiveSetupFigmaFileUrl": "https://www.figma.com/design/abc123/my-design-system"
8992
}
9093
}
9194
`

cli/src/connect/parser_executables.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type ParserInfo = {
3131
const FIRST_PARTY_PARSERS: Record<CodeConnectExecutableParser, ParserInfo> = {
3232
swift: {
3333
command: async (cwd, config) => {
34-
return `swift run --package-path ${await getSwiftParserDir(cwd, (config as any).xcodeprojPath, (config as any).swiftPackagePath)} figma-swift`
34+
return `swift run --package-path ${await getSwiftParserDir(cwd, (config as any).xcodeprojPath, (config as any).swiftPackagePath, (config as any).sourcePackagesPath)} figma-swift`
3535
},
3636
},
3737
compose: {
@@ -87,10 +87,19 @@ export async function callParser(
8787
...config,
8888
verbose: (payload as any).verbose,
8989
}
90+
9091
const command = await parser.command(cwd, configWithVerbose, payload.mode)
9192
if (parser.temporaryIOFilePath) {
9293
fs.mkdirSync(path.dirname(parser.temporaryIOFilePath), { recursive: true })
9394
fs.writeFileSync(temporaryIOFilePath, JSON.stringify(payload))
95+
96+
// Write to tmp directory before calling parser if verbose is enabled
97+
if ((payload as any).verbose) {
98+
const tmpDir = path.join(cwd, 'tmp')
99+
fs.mkdirSync(tmpDir, { recursive: true })
100+
const tmpFilePath = path.join(tmpDir, 'figma-code-connect-parser-input.json.tmp')
101+
fs.writeFileSync(tmpFilePath, JSON.stringify(payload, null, 2))
102+
}
94103
}
95104
logger.debug(`Running parser: ${command}`)
96105
const commandSplit = command.split(' ')
@@ -150,11 +159,14 @@ export async function callParser(
150159
)
151160
}
152161
if (parser.temporaryIOFilePath) {
153-
fs.unlinkSync(parser.temporaryIOFilePath)
154-
// Delete parent directory if empty after removing temp file
155-
const parentDir = path.dirname(parser.temporaryIOFilePath)
156-
if (fs.readdirSync(parentDir).length === 0) {
157-
fs.rmdirSync(parentDir)
162+
// Retain temp file and directory when verbose mode is enabled
163+
if (!(payload as any).verbose) {
164+
fs.unlinkSync(parser.temporaryIOFilePath)
165+
// Delete parent directory if empty after removing temp file
166+
const parentDir = path.dirname(parser.temporaryIOFilePath)
167+
if (fs.readdirSync(parentDir).length === 0) {
168+
fs.rmdirSync(parentDir)
169+
}
158170
}
159171
}
160172
})

cli/src/connect/project.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,7 @@ export async function getProjectInfoFromConfig(
696696
? ({
697697
react: ['node_modules/**'],
698698
html: ['node_modules/**'],
699-
swift: [],
699+
swift: ['**/__test__/**'],
700700
compose: [],
701701
custom: [],
702702
__unit_test__: [],

cli/src/connect/wizard/prop_mapping_helpers.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,18 @@ async function getEmbeddingsMatchResults({
164164
}) {
165165
const uniqueMatchableNames = getUniqueMatchableNames(propMappingData)
166166

167-
const res = mockResponseName
168-
? await getMockEmbeddingsResponse(uniqueMatchableNames, mockResponseName)
169-
: await fetchEmbeddings({ uniqueMatchableNames, accessToken, figmaUrl })
170-
171167
const matchableNamesEmbeddings: MatchableNameEmbeddings = {}
172168

173-
res?.meta.embeddings.forEach((embedding: number[], index: number) => {
174-
matchableNamesEmbeddings[uniqueMatchableNames[index]] = embedding
175-
})
169+
if (uniqueMatchableNames.length > 0) {
170+
const res = mockResponseName
171+
? await getMockEmbeddingsResponse(uniqueMatchableNames, mockResponseName)
172+
: await fetchEmbeddings({ uniqueMatchableNames, accessToken, figmaUrl })
173+
174+
res?.meta.embeddings.forEach((embedding: number[], index: number) => {
175+
matchableNamesEmbeddings[uniqueMatchableNames[index]] = embedding
176+
})
177+
}
178+
176179
return buildAllEmbeddingsMatchResults(propMappingData, matchableNamesEmbeddings)
177180
}
178181

cli/src/parser_scripts/get_swift_parser_dir.ts

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { spawnSync } from 'child_process'
22
import { exitWithError, logger } from '../common/logging'
33
import { getFileIfExists } from './get_file_if_exists'
44
import path from 'path'
5+
import { readdirSync, existsSync } from 'fs'
56

67
// Find the location of the Code Connect Swift package on disk, so that we can
78
// call `swift run figma-swift` from the correct location. This requires parsing
@@ -15,6 +16,7 @@ export async function getSwiftParserDir(
1516
cwd: string,
1617
xcodeprojPath?: string,
1718
swiftPackagePath?: string,
19+
sourcePackagesPath?: string,
1820
) {
1921
let figmaPackageDir: string | undefined
2022
let xcodeprojFile: string | undefined
@@ -75,16 +77,81 @@ export async function getSwiftParserDir(
7577
// If the version is 'local', the package is installed from a local checkout,
7678
// and the path on disk is the source output by xcodebuild.
7779
figmaPackageDir = figmaPackageSource
80+
} else if (sourcePackagesPath) {
81+
logger.info(`Using custom DerivedData path: ${sourcePackagesPath}`)
82+
83+
// If a custom path is supplied, use it.
84+
figmaPackageDir = `${sourcePackagesPath}/checkouts/code-connect`
7885
} else {
79-
// Otherwise, the package will be installed to
86+
// Otherwise the SourcePackages will typically be located in Xcode's DerivedData directory,
87+
// at ~/Users/{username}/Library/Developer/Xcode/DerivedData/{project}-{xxx}/SourcePackages, or in the Project's root
88+
// at {project}/DerivedData/{project}/SourcePackages (depending on how the user's project is configured).
89+
let hasFoundSourcePackagesDir = false
90+
91+
// 1. First look in the USER_LIBRARY_DIR (e.g. /Users/{username}/Library)
92+
const userLibraryDirectoryMatch = buildSettings.match(/\s+USER_LIBRARY_DIR = (.*)/)
93+
const userLibraryDirectory = userLibraryDirectoryMatch
94+
? userLibraryDirectoryMatch[1]
95+
: undefined
96+
97+
logger.info('Finding Code Connect Swift package')
98+
99+
// Find the project's name using the build settings
100+
const projectNameMatch = buildSettings.match(/\s+PROJECT_NAME = (.*)/)
101+
const projectName = projectNameMatch ? projectNameMatch[1] : undefined
102+
103+
// We can't proceed without the project name (however, this should always exist)
104+
if (!projectName) {
105+
exitWithError('PROJECT_NAME not found in xcodebuild output')
106+
}
107+
108+
// 1. From the folders in the user library directory use a regex to determine the folder name of the project defined by "ProjectName-" and a 28 character hash
109+
// e.g. project-fbybcbnivxfbfeefownexgukzwxd
110+
if (userLibraryDirectory) {
111+
// Default to Xcode's default Dervied Data location (e.g. ~/Users/{username}/Library/Developer/Xcode/DerivedData/project-fbybcbnivxfbfeefownexgukzwxd/SourcePackages)
112+
const root = `${userLibraryDirectory}/Developer/Xcode/DerivedData`
113+
114+
const projectFolderRegex = new RegExp(`${projectName}-[a-zA-Z0-9]{28}`)
115+
const derivedDataFolders = readdirSync(root)
116+
const projectFolder = derivedDataFolders.find((folder: string) =>
117+
projectFolderRegex.test(folder),
118+
)
119+
120+
// If the project folder is found, use it to find the Code Connect package
121+
if (projectFolder) {
122+
figmaPackageDir = `${root}/${projectFolder}/SourcePackages/checkouts/code-connect`
123+
hasFoundSourcePackagesDir = true
124+
} else {
125+
logger.warn('Package not found in user library directory')
126+
}
127+
}
128+
129+
// 2. If SourcePackages couldn't be found in ~/Users/{username}/Library/Developer/Xcode/DerivedData/{project}-{xxx}/SourcePackages, attempt
130+
// to find it in the project root {project}/DerivedData/{project}/SourcePackages
131+
if (!hasFoundSourcePackagesDir) {
132+
const rootDir = buildSettings.match(/\s+LOCROOT = (.*)/)
133+
134+
if (rootDir) {
135+
figmaPackageDir = `${rootDir[1]}/DerivedData/${projectName}/SourcePackages/checkouts/code-connect`
136+
hasFoundSourcePackagesDir = true
137+
} else {
138+
logger.warn('Package not found in project root')
139+
}
140+
}
141+
142+
// 3. Otherwise, the package may be installed to
80143
// <DerviedData>/SourcePackages/checkouts/code-connect. We find the
81144
// DerivedData location from the BUILD_DIR (which points to
82145
// <DerivedData>/Build/Products).
83-
const buildDir = buildSettings.match(/\s+BUILD_DIR = (.*)/)
84-
if (!buildDir) {
85-
exitWithError('BUILD_DIR not found in xcodebuild output')
146+
if (!hasFoundSourcePackagesDir) {
147+
const buildDir = buildSettings.match(/\s+BUILD_DIR = (.*)/)
148+
149+
if (buildDir) {
150+
figmaPackageDir = `${buildDir[1]}/../../SourcePackages/checkouts/code-connect`
151+
} else {
152+
logger.warn('Package not found in build directory')
153+
}
86154
}
87-
figmaPackageDir = `${buildDir[1]}/../../SourcePackages/checkouts/code-connect`
88155
}
89156
} else if (packageSwiftFile) {
90157
const swiftPackageDir = swiftPackagePath ? path.dirname(swiftPackagePath) : undefined
@@ -128,6 +195,27 @@ export async function getSwiftParserDir(
128195
exitWithError('Figma package could not be found')
129196
}
130197

198+
// Check if the figmaPackageDir exists, otherwise we can't proceed,
199+
// as we require the code-connect package to continue
200+
if (!existsSync(figmaPackageDir)) {
201+
exitWithError(`Figma package directory not found at ${figmaPackageDir}`)
202+
}
203+
204+
// We need to ensure that the directory is writable, so we can run the swift run command
205+
// otherwise an "invalid access" error will be thrown by swift.
206+
try {
207+
const packageFile = path.join(figmaPackageDir, 'Package.resolved')
208+
209+
const accessCheck = spawnSync('test', ['-w', packageFile])
210+
if (accessCheck.status !== 0) {
211+
// Directory is not writable, attempt to make it writable
212+
spawnSync('chmod', ['-R', '755', packageFile])
213+
}
214+
logger.info(`Directory enabled for swift run command`)
215+
} catch (e) {
216+
logger.warn(`Unable to verify or modify directory permissions: ${e}`)
217+
}
218+
131219
logger.info(
132220
`Found Code Connect Swift package at ${figmaPackageDir}, building parser binary. This may take a few minutes if this is the first time you've run Code Connect.`,
133221
)

0 commit comments

Comments
 (0)