Skip to content

Commit 722b87b

Browse files
author
figma-bot
committed
Code Connect v1.4.8
1 parent 2071f2d commit 722b87b

36 files changed

Lines changed: 1961 additions & 111 deletions

CHANGELOG.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,28 @@
1-
# Code Connect v1.4.7 (28 May 2026)
1+
# Code Connect v1.4.8 (8 June 2026)
2+
3+
## Fixed
4+
5+
### General
6+
7+
- Fixed browser compatibility issues where importing `@figma/code-connect` in Storybook or webpack environments would fail with "Can't resolve 'child_process'" or "Console is not a constructor" errors. Node.js-only modules (`child_process`, `console`) are now imported dynamically only when needed by CLI commands, making the package safe to use in browser environments.
8+
9+
### Template files
10+
11+
- Fixed parserless template projects failing to publish or parse when Code Connect could not infer a React, HTML, Swift, or Compose parser. The CLI now proceeds without a parser and uses the default template-file globs.
12+
- Fixed a "`getInstanceSwap(...)?.executeTemplate is not a function`" error that could break a template when `getInstanceSwap('prop')` referenced an instance-swap property that isn't present on the selected node (for example a property that is conditionally hidden by a boolean). The missing property now renders an inline error, consistent with the other accessors, instead of failing the whole template.
13+
- Fixed migrated text-layer references (from the React/HTML `figma.textContent('...')` helper) producing a TypeScript error and silently rendering nothing when the layer was missing. Migration now preserves the original behavior — the text renders when the layer is found, and a clear error is shown when it isn't.
214

315
## Features
416

17+
### Template files
18+
19+
- The `figma connect migrate` command now uses the same `--dir` and `--file` filtering behavior as `publish`, `parse`, and `unpublish`. The migrate-only positional glob argument has been removed.
20+
- The `figma connect migrate` command can now migrate compatible large source files into batch template files. Files with 10 or more Code Connect docs are attempted automatically, `--batch all` forces a batch attempt, and `--batch none` disables batch migration.
21+
22+
# Code Connect v1.4.7 (28 May 2026)
23+
24+
## Fixed
25+
526
### General
627

728
- The `--file` (`-f`) option on `figma connect publish`, `unpublish`, and `parse` now accepts multiple files, so you can target several Code Connect files in one command (e.g. `figma connect publish --file a.figma.ts b.figma.ts`). Previously only a single file path was accepted.

cli/figma-types.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ declare module 'figma' {
103103
propName: string,
104104
options: O,
105105
): EnumOptionsValues<O> | undefined
106-
getInstanceSwap(propName: string): InstanceHandle | undefined
106+
getInstanceSwap(propName: string): InstanceHandle | ErrorHandle | undefined
107107
getSlot(propName: string): ResultSection[] | undefined
108108
hasCodeConnect(): boolean
109109
getPropertyValue(name: string): string | boolean | ErrorHandle

cli/npm_catalog.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@
250250
"@zip.js/zip.js" = "2.8.19"
251251
"zod-to-json-schema" = "^3.23.5"
252252
"zod" = "3.25.58"
253-
"@anthropic-ai/claude-agent-sdk" = "0.2.69"
253+
"@anthropic-ai/claude-agent-sdk" = "0.3.168"
254254
"ai" = "4.1.62"
255255
"@ai-sdk/amazon-bedrock" = "2.1.4"
256256
"@ai-sdk/anthropic" = "1.2.5"

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.4.7",
3+
"version": "1.4.8",
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/commands/__test__/parseRawFile.test.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { parseRawFile, isRawTemplate } from '../../connect/raw_templates'
1+
import { parseRawFile, isRawTemplate, CodePropertiesError } from '../../connect/raw_templates'
22
import { CodeConnectConfig } from '../../connect/project'
33
import { SyntaxHighlightLanguage } from '../../connect/label_language_mapping'
44
import fs from 'fs'
@@ -418,4 +418,47 @@ export default figma.code\`<Button />\``
418418
'TypeScript template files only support importing from',
419419
)
420420
})
421+
422+
it('throws CodePropertiesError (skip) for a url-less file containing codeProperties', () => {
423+
// No url + contains `codeProperties` => skip signal, as long as the file has
424+
// no unsupported import (see the test below).
425+
const fileContent = `// component=Button
426+
export const codeProperties = { value: 1 }`
427+
428+
fs.writeFileSync(tempFilePath, fileContent)
429+
430+
expect(() => parseRawFile(tempFilePath, undefined)).toThrow(CodePropertiesError)
431+
expect(() => parseRawFile(tempFilePath, undefined)).not.toThrow(
432+
'TypeScript template files only support importing from',
433+
)
434+
})
435+
436+
it('fails on a non-figma import even when the file contains codeProperties', () => {
437+
// An unsupported import is always a hard error and takes precedence over the
438+
// codeProperties skip, so an import mistake is never silently swallowed.
439+
const fileContent = `// component=Button
440+
import { helper } from './helper'
441+
export const codeProperties = { value: helper() }`
442+
443+
fs.writeFileSync(tempFilePath, fileContent)
444+
445+
expect(() => parseRawFile(tempFilePath, undefined)).toThrow(
446+
'TypeScript template files only support importing from',
447+
)
448+
expect(() => parseRawFile(tempFilePath, undefined)).not.toThrow(CodePropertiesError)
449+
})
450+
451+
it('still surfaces the original import error for a url-less file WITHOUT codeProperties', () => {
452+
// Unchanged behaviour: only `codeProperties` files are skipped; any other
453+
// url-less file with a non-figma import fails on the import error as before.
454+
const fileContent = `// component=Button
455+
import { helper } from './helper'
456+
export default helper`
457+
458+
fs.writeFileSync(tempFilePath, fileContent)
459+
460+
expect(() => parseRawFile(tempFilePath, undefined)).toThrow(
461+
'TypeScript template files only support importing from',
462+
)
463+
})
421464
})

cli/src/commands/connect.ts

Lines changed: 95 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ import {
4444
writeTemplateFile,
4545
writeVariantTemplateFile,
4646
} from '../connect/migration_helpers'
47-
import { isRawTemplate, parseRawFile } from '../connect/raw_templates'
47+
import {
48+
getBatchMigrationGroups,
49+
writeBatchTemplateFiles,
50+
} from '../connect/migration_batch_helpers'
51+
import { CodePropertiesError, isRawTemplate, parseRawFile } from '../connect/raw_templates'
4852
import { parseBatchFile } from '../connect/batch_templates'
4953
import { convertRemoteFileUrlToRelativePath } from '../connect/wizard/run_wizard'
5054
import { getGitRepoAbsolutePath } from '../connect/project'
@@ -63,8 +67,11 @@ export type BaseCommand = commander.Command & {
6367
exitOnUnreadableFiles: boolean
6468
apiUrl?: string
6569
includeProps?: boolean
70+
batch?: BatchMigrationMode
6671
}
6772

73+
type BatchMigrationMode = 'auto' | 'all' | 'none'
74+
6875
function addBaseCommand(command: commander.Command, name: string, description: string) {
6976
return command
7077
.command(name)
@@ -167,9 +174,8 @@ export function addConnectCommandToProgram(program: commander.Command) {
167174
addBaseCommand(
168175
connectCommand,
169176
'migrate',
170-
'Parse Code Connect files and migrate their templates into .figma.js files that can be published directly without parsing.',
177+
'Parse Code Connect files and migrate their templates into .figma.ts or .figma.js files that can be published directly without parsing.',
171178
)
172-
.argument('[pattern]', 'glob pattern to match files (optional, uses config if not provided)')
173179
.option(
174180
'--include-props',
175181
'preserve __props metadata blocks in migrated templates (removed by default). Use if your templates use the React .getProps() or .render() modifiers, or read executeTemplate().metadata.__props from the migrated components',
@@ -178,6 +184,14 @@ export function addConnectCommandToProgram(program: commander.Command) {
178184
'--javascript',
179185
'output migrated templates as JavaScript (.figma.js) instead of TypeScript (.figma.ts)',
180186
)
187+
.addOption(
188+
new commander.Option(
189+
'--batch <mode>',
190+
'batch migration mode: auto attempts files with 10 or more Code Connect docs; all attempts every source file; none disables batch migration',
191+
)
192+
.choices(['auto', 'all', 'none'])
193+
.default('auto'),
194+
)
181195
.action(withUpdateCheck(handleMigrate))
182196

183197
addBaseCommand(
@@ -364,6 +378,15 @@ export async function getCodeConnectObjects(
364378
}
365379
rawTemplateDocs.push(doc)
366380
} catch (e) {
381+
// A url-less file containing `codeProperties` is not Code Connect (e.g.
382+
// Make's code component property definitions). Log and skip it instead
383+
// of failing — never exit, not even with --exit-on-unreadable-files.
384+
if (e instanceof CodePropertiesError) {
385+
if (!silent || cmd.verbose) {
386+
logger.info(e.message)
387+
}
388+
continue
389+
}
367390
if (!silent || cmd.verbose) {
368391
logger.error(`❌ ${file}`)
369392
if (cmd.verbose) {
@@ -720,14 +743,11 @@ async function handleCreate(nodeUrl: string, cmd: BaseCommand) {
720743
* - Format as valid parserless file w/ url="" comment
721744
* - Name collisions are skipped, unless file is from migration (in which case add "_1")
722745
*/
723-
async function handleMigrate(
724-
pattern: string | undefined,
725-
cmd: BaseCommand & { javascript?: boolean },
726-
) {
746+
async function handleMigrate(cmd: BaseCommand & { javascript?: boolean }) {
727747
setupHandler(cmd)
728748

729749
const dir = cmd.dir ?? process.cwd()
730-
let projectInfo = await getProjectInfo(dir, cmd.config)
750+
let projectInfo = filterProjectInfoByFile(await getProjectInfo(dir, cmd.config), cmd.file)
731751
const documentUrlSubstitutions = projectInfo.config.documentUrlSubstitutions
732752

733753
// Clear documentUrlSubstitutions so we preserve original URLs in migrated templates
@@ -739,44 +759,63 @@ async function handleMigrate(
739759
},
740760
}
741761

742-
// If a glob pattern is provided, override the project files
743-
if (pattern) {
744-
const { globSync } = require('glob')
745-
const files = globSync(pattern, {
746-
cwd: dir,
747-
absolute: true,
748-
nodir: true,
749-
})
750-
751-
if (files.length === 0) {
752-
exitWithError(`No files found matching pattern: ${pattern}`)
753-
}
754-
755-
logger.info(`Found ${files.length} file(s) matching pattern: ${pattern}`)
756-
projectInfo = {
757-
...projectInfo,
758-
files,
759-
}
760-
}
761-
762762
// Parse the files to get Code Connect objects
763763
const allCodeConnectObjects = await getCodeConnectObjects(cmd, projectInfo, false, true)
764764

765-
const codeConnectObjectsByFigmaUrl = groupCodeConnectObjectsByFigmaUrl(allCodeConnectObjects)
766-
767-
const groupCount = Object.keys(codeConnectObjectsByFigmaUrl).length
765+
const allCodeConnectObjectsByFigmaUrl = groupCodeConnectObjectsByFigmaUrl(allCodeConnectObjects)
766+
const groupCount = Object.keys(allCodeConnectObjectsByFigmaUrl).length
768767
if (groupCount === 0) {
769768
exitWithError('No Code Connect objects found to migrate')
770769
}
771770

772771
logger.info(`Found ${groupCount} component(s) to migrate`)
773772
let migratedCount = 0
773+
let batchMigratedCount = 0
774774
let skippedCount = 0
775775
const errors: string[] = []
776+
const batchMigrationWarnings: Array<{
777+
path: string
778+
connectionCount: number
779+
reason: string
780+
}> = []
776781

777782
const filePathsCreated = new Set<string>()
778783
const gitRootPath = getGitRepoAbsolutePath(dir)
779784
const useTypeScript = !cmd.javascript
785+
const batchMigratedDocs = new Set<CodeConnectJSON>()
786+
const batchDocsByPath = getBatchMigrationGroups(allCodeConnectObjects, {
787+
batchAll: cmd.batch === 'all',
788+
disabled: cmd.batch === 'none',
789+
})
790+
791+
for (const [batchPath, docs] of batchDocsByPath) {
792+
const result = writeBatchTemplateFiles(docs, cmd.outDir, dir, {
793+
localSourcePath: batchPath,
794+
filePathsCreated,
795+
includeProps: cmd.includeProps,
796+
useTypeScript,
797+
})
798+
799+
if (result.skipped) {
800+
batchMigrationWarnings.push({
801+
path: batchPath,
802+
connectionCount: docs.length,
803+
reason: result.reason || 'unable to create batch files',
804+
})
805+
} else {
806+
logger.info(
807+
`${success('✓')} Migrated ${result.componentCount} component(s) to batch ${highlight(result.batchPath || batchPath)}`,
808+
)
809+
migratedCount += result.componentCount
810+
batchMigratedCount += result.componentCount
811+
docs.forEach((doc) => batchMigratedDocs.add(doc))
812+
}
813+
}
814+
815+
const regularCodeConnectObjects = allCodeConnectObjects.filter(
816+
(doc) => !batchMigratedDocs.has(doc),
817+
)
818+
const codeConnectObjectsByFigmaUrl = groupCodeConnectObjectsByFigmaUrl(regularCodeConnectObjects)
780819

781820
for (const [figmaUrl, group] of Object.entries(codeConnectObjectsByFigmaUrl)) {
782821
try {
@@ -847,7 +886,10 @@ async function handleMigrate(
847886
const language = getInferredLanguageForRaw(label, docLanguage)
848887
const config: CodeConnectParserlessConfig = {
849888
parser: undefined,
850-
include: [useTypeScript ? '**/*.figma.ts' : '**/*.figma.js'],
889+
include: [
890+
useTypeScript ? '**/*.figma.ts' : '**/*.figma.js',
891+
...(batchMigratedCount > 0 ? ['**/*.figma.batch.json'] : []),
892+
],
851893
language,
852894
label,
853895
...(documentUrlSubstitutions && Object.keys(documentUrlSubstitutions).length > 0
@@ -860,6 +902,20 @@ async function handleMigrate(
860902
}
861903
}
862904

905+
if (batchMigrationWarnings.length > 0) {
906+
console.log('')
907+
logger.warn('Unable to migrate the following files to batch files:')
908+
batchMigrationWarnings.forEach((warning) => {
909+
logger.warn(
910+
`- ${highlight(warning.path)} (${warning.connectionCount} connections): ${formatBatchMigrationWarningReason(warning.reason)}`,
911+
)
912+
})
913+
console.log('')
914+
logger.warn(
915+
"If you'd like to create batch files for these, we recommend using a coding agent. See example prompt: https://developers.figma.com/docs/code-connect/template-files/#migration-script",
916+
)
917+
}
918+
863919
// Summary
864920
console.log('')
865921
logger.info(
@@ -877,3 +933,10 @@ async function handleMigrate(
877933
exitWithError('No files were migrated')
878934
}
879935
}
936+
937+
function formatBatchMigrationWarningReason(reason: string) {
938+
const strippedReason = reason.replace(/^Cannot batch:\s*/i, '')
939+
return strippedReason.replace(/^(\s*)(\S)/, (_match, prefix: string, firstChar: string) => {
940+
return `${prefix}${firstChar.toUpperCase()}`
941+
})
942+
}

cli/src/commands/connect_template.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { LogLevel, exitWithError, highlight, logger, success } from '../common/l
1010
import { CodeConnectJSON } from '../connect/figma_connect'
1111
import { delete_docs } from '../connect/delete_docs'
1212
import { exitWithFeedbackMessage } from '../connect/helpers'
13-
import { isRawTemplate, parseRawFile } from '../connect/raw_templates'
13+
import { CodePropertiesError, isRawTemplate, parseRawFile } from '../connect/raw_templates'
1414
import { parseBatchFile } from '../connect/batch_templates'
1515
import { filterProjectInfoByFile } from './filter_project_info'
1616
import { createCodeConnectFromUrl } from '../connect/create_template'
@@ -92,6 +92,15 @@ async function getCodeConnectObjects(
9292
}
9393
rawTemplateDocs.push(doc)
9494
} catch (e) {
95+
// A url-less file containing `codeProperties` is not Code Connect (e.g.
96+
// Make's code component property definitions). Log and skip it instead
97+
// of failing — never exit, not even with --exit-on-unreadable-files.
98+
if (e instanceof CodePropertiesError) {
99+
if (!silent || cmd.verbose) {
100+
logger.info(e.message)
101+
}
102+
continue
103+
}
95104
if (!silent || cmd.verbose) {
96105
logger.error(`❌ ${file}`)
97106
if (cmd.verbose) {

0 commit comments

Comments
 (0)