Skip to content

Commit 131c997

Browse files
author
figma-bot
committed
Code Connect v1.3.12
1 parent 05443c9 commit 131c997

11 files changed

Lines changed: 401 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
# Code Connect v1.3.12 (TBD)
1+
# Code Connect v1.3.12
22

33
## Fixed
44

5+
- Fixed a problem with path alias imports incorrectly importing index files
6+
- Fixed parsing failure for Compose in Windows.
7+
58
### General
69

710
# Code Connect v1.3.11 (26th November 2025)

cli/npm_catalog.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,6 @@
165165
"@stylexjs/stylex" = "0.16.2"
166166
"@stylexjs/babel-plugin" = "0.16.2"
167167
"@stylexjs/eslint-plugin" = "0.16.2"
168-
"@stylextras/stylex-include" = "0.1.1"
169168
"@tailwindcss/oxide" = "4.1.3"
170169
"@tailwindcss/postcss" = "4.1.3"
171170
"@tailwindcss/vite" = "4.1.3"
@@ -183,6 +182,7 @@
183182
"@figma/plugin-typings" = "1.121.0"
184183
"@figma/widget-typings" = "1.12.1"
185184
"@modelcontextprotocol/sdk" = "1.18.0"
185+
"@sentry/cli" = "2.58.2"
186186
"@types/mocha" = "10.0.10"
187187
"@types/node" = "22.17.2"
188188
"@types/react" = "18.0.26"

cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@figma/code-connect",
3-
"version": "1.3.11",
3+
"version": "1.3.12",
44
"description": "A tool for connecting your design system components in code with your design system in Figma",
55
"keywords": [],
66
"author": "Figma",

cli/src/connect/__test__/project.test.ts

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { CodeConnectReactConfig, getRemoteFileUrl, mapImportPath } from '../project'
1+
import {
2+
CodeConnectReactConfig,
3+
getRemoteFileUrl,
4+
mapImportPath,
5+
mapImportSpecifier,
6+
} from '../project'
27

38
describe('Project helper functions', () => {
49
function getConfig(importPaths: {}): CodeConnectReactConfig {
@@ -72,6 +77,95 @@ describe('Project helper functions', () => {
7277
)
7378
expect(mapped).toEqual('@ui/icons')
7479
})
80+
81+
it('Uses filename for index files (use mapImportSpecifier for better results)', () => {
82+
// Note: mapImportPath uses the resolved file path, so index.ts files return 'index'.
83+
// For better handling of path aliases, use mapImportSpecifier with the original specifier.
84+
const mapped = mapImportPath(
85+
'/Users/test/app/src/AlertTitle/index.ts',
86+
getConfig({ importPaths: { 'src/*': '@acme/package/*' } }),
87+
)
88+
expect(mapped).toEqual('@acme/package/index')
89+
})
90+
91+
it('Uses filename for nested index files', () => {
92+
const mapped = mapImportPath(
93+
'/Users/test/app/src/components/Button/index.tsx',
94+
getConfig({ importPaths: { 'src/*': '@ui/*' } }),
95+
)
96+
expect(mapped).toEqual('@ui/index')
97+
})
98+
99+
it('Uses filename when file is not an index file', () => {
100+
const mapped = mapImportPath(
101+
'/Users/test/app/src/AlertTitle/AlertTitle.tsx',
102+
getConfig({ importPaths: { 'src/*': '@acme/package/*' } }),
103+
)
104+
expect(mapped).toEqual('@acme/package/AlertTitle')
105+
})
106+
})
107+
108+
describe('importSpecifier mappings', () => {
109+
it('Transforms path alias with wildcard to package path', () => {
110+
const mapped = mapImportSpecifier(
111+
'@/AlertTitle',
112+
getConfig({ importPaths: { '@/*': '@acme/package/*' } }),
113+
)
114+
expect(mapped).toEqual('@acme/package/AlertTitle')
115+
})
116+
117+
it('Transforms nested path alias to package path', () => {
118+
const mapped = mapImportSpecifier(
119+
'@/components/Button',
120+
getConfig({ importPaths: { '@/*': '@ui/*' } }),
121+
)
122+
expect(mapped).toEqual('@ui/components/Button')
123+
})
124+
125+
it('Handles exact match without wildcard', () => {
126+
const mapped = mapImportSpecifier(
127+
'@/Button',
128+
getConfig({ importPaths: { '@/Button': '@acme/Button' } }),
129+
)
130+
expect(mapped).toEqual('@acme/Button')
131+
})
132+
133+
it('Handles wildcard replacement without output wildcard', () => {
134+
const mapped = mapImportSpecifier(
135+
'@/components/Button',
136+
getConfig({ importPaths: { '@/*': '@ui' } }),
137+
)
138+
expect(mapped).toEqual('@ui')
139+
})
140+
141+
it('Returns null for non-matching specifiers', () => {
142+
const mapped = mapImportSpecifier(
143+
'./Button',
144+
getConfig({ importPaths: { '@/*': '@acme/package/*' } }),
145+
)
146+
expect(mapped).toBeNull()
147+
})
148+
149+
it('Matches first pattern when multiple patterns could match', () => {
150+
const mapped = mapImportSpecifier(
151+
'@/icons/icon',
152+
getConfig({ importPaths: { '@/icons/*': '@ui/icons/*', '@/*': '@ui/*' } }),
153+
)
154+
expect(mapped).toEqual('@ui/icons/icon')
155+
})
156+
157+
it('Returns null when no importPaths configured', () => {
158+
const mapped = mapImportSpecifier('@/Button', getConfig({}))
159+
expect(mapped).toBeNull()
160+
})
161+
162+
it('Handles special regex characters in pattern', () => {
163+
const mapped = mapImportSpecifier(
164+
'@scope/package/Button',
165+
getConfig({ importPaths: { '@scope/package/*': '@acme/*' } }),
166+
)
167+
expect(mapped).toEqual('@acme/Button')
168+
})
75169
})
76170

77171
describe('getRemoteFileUrl', () => {

cli/src/connect/project.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -826,11 +826,50 @@ export function mapImportPath(filePath: string, config: CodeConnectReactConfig):
826826
}
827827

828828
// If the mapped path ends with a wildcard we want to keep the filename in
829-
// the final path (for non-index imports)
829+
// the final path
830830
if (isMatch(patternParts, pathParts)) {
831-
return value.endsWith('*') ? `${value.slice(0, -1)}${pathParts[0].split('.')[0]}` : value
831+
if (value.endsWith('*')) {
832+
const filename = pathParts[0].split('.')[0]
833+
return `${value.slice(0, -1)}${filename}`
834+
}
835+
return value
832836
}
833837
}
834838

835839
return null
836840
}
841+
842+
/**
843+
* Transform an import specifier (the path in the import statement) using importPaths config.
844+
* This works directly on the module specifier from the source code, preserving the user's intent.
845+
*
846+
* E.g., '@/AlertTitle' with config { "@/*": "@acme/package/*" } → '@acme/package/AlertTitle'
847+
*
848+
* @param specifier The original import specifier from the source file (e.g., '@/AlertTitle', './Button')
849+
* @param config The Code Connect config containing importPaths
850+
* @returns The transformed import path, or null if no mapping matched
851+
*/
852+
export function mapImportSpecifier(
853+
specifier: string,
854+
config: CodeConnectReactConfig,
855+
): string | null {
856+
for (const [pattern, replacement] of Object.entries(config.importPaths || {})) {
857+
// Convert glob pattern to regex: @/* → ^@/(.*)$
858+
// First escape special regex chars, then convert * to capture group
859+
const regexPattern = pattern
860+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape special chars except *
861+
.replace(/\*/g, '(.*)') // Convert * to capture group
862+
const regex = new RegExp(`^${regexPattern}$`)
863+
const match = specifier.match(regex)
864+
865+
if (match) {
866+
// Replace * in replacement with captured group
867+
let result = replacement
868+
if (match[1] !== undefined) {
869+
result = replacement.replace('*', match[1])
870+
}
871+
return result
872+
}
873+
}
874+
return null
875+
}

0 commit comments

Comments
 (0)