@figma/code-connect 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/common/updates.d.ts.map +1 -1
- package/dist/common/updates.js +2 -4
- package/dist/common/updates.js.map +1 -1
- package/dist/connect/raw_templates.d.ts.map +1 -1
- package/dist/connect/raw_templates.js +19 -15
- package/dist/connect/raw_templates.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Code Connect
|
|
2
2
|
|
|
3
3
|
> [!WARNING]
|
|
4
|
-
>
|
|
4
|
+
> Framework-specific parsers will no longer receive updates or support from August 17th, 2026. Template files will be the only actively maintained way of using Code Connect. See our migration guide for more information and instructions on migrating parser-based Code Connect:
|
|
5
|
+
> https://developers.figma.com/docs/code-connect/templates-migration-guide/
|
|
5
6
|
|
|
6
7
|
Code Connect is a tool for connecting your design system components in code with your design system in Figma. When using Code Connect, Figma's Dev Mode will display true-to-production code snippets from your design system instead of autogenerated code examples. In addition to connecting component definitions, Code Connect also supports mapping properties from code to Figma enabling dynamic and correct examples. This can be useful for when you have an existing design system and are looking to drive consistent and correct adoption of that design system across design and engineering.
|
|
7
8
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"updates.d.ts","sourceRoot":"","sources":["../../src/common/updates.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAWnC,wBAAgB,0BAA0B,SAEzC;AAID,KAAK,WAAW,CAAC,CAAC,SAAS,WAAW,IAAI,CAAC,GAAG,GAAG,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,CAAA;AAIhE,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,WAAW,EAGvD,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC5C,EAAE,sBAA8B,EAAE,GAAE;IAAE,sBAAsB,CAAC,EAAE,OAAO,CAAA;CAAO,IAErE,GAAG,MAAM,WAAW,CAAC,CAAC,CAAC,0BAsBhC;AAyCD,wBAAgB,mBAAmB,CAAC,SAAS,SAAI,GAAG,KAAK,CAKxD;
|
|
1
|
+
{"version":3,"file":"updates.d.ts","sourceRoot":"","sources":["../../src/common/updates.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAWnC,wBAAgB,0BAA0B,SAEzC;AAID,KAAK,WAAW,CAAC,CAAC,SAAS,WAAW,IAAI,CAAC,GAAG,GAAG,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,CAAA;AAIhE,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,WAAW,EAGvD,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC5C,EAAE,sBAA8B,EAAE,GAAE;IAAE,sBAAsB,CAAC,EAAE,OAAO,CAAA;CAAO,IAErE,GAAG,MAAM,WAAW,CAAC,CAAC,CAAC,0BAsBhC;AAyCD,wBAAgB,mBAAmB,CAAC,SAAS,SAAI,GAAG,KAAK,CAKxD;AA0BD,wBAAgB,SAAS,SAGxB"}
|
package/dist/common/updates.js
CHANGED
|
@@ -92,11 +92,9 @@ function exitWithUpdateCheck(errorCode = 1) {
|
|
|
92
92
|
function getUpdateCommand() {
|
|
93
93
|
return 'npm update -g @figma/code-connect';
|
|
94
94
|
}
|
|
95
|
-
// Keep in sync with the deprecation notices in figmadoc/README.md and
|
|
96
|
-
// figmadoc/CHANGELOG.md, which repeat this date.
|
|
97
|
-
const PARSER_DEPRECATION_DATE = 'August 17th, 2026';
|
|
98
95
|
const DEPRECATION_NOTICE = `
|
|
99
|
-
|
|
96
|
+
Framework-specific parsers will no longer receive updates or support from August 17th, 2026. Template files will be the only actively maintained way of using Code Connect. See our migration guide for more information and instructions on migrating parser-based Code Connect:
|
|
97
|
+
https://developers.figma.com/docs/code-connect/templates-migration-guide/`;
|
|
100
98
|
function maybeShowUpdateMessage() {
|
|
101
99
|
if (parserBasedCodeConnectFound && !deprecationWarningHidden) {
|
|
102
100
|
logging_1.logger.warn(DEPRECATION_NOTICE);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"updates.js","sourceRoot":"","sources":["../../src/common/updates.ts"],"names":[],"mappings":";;;;;AAeA,gEAEC;AAQD,kDA4BC;AAyCD,kDAKC;
|
|
1
|
+
{"version":3,"file":"updates.js","sourceRoot":"","sources":["../../src/common/updates.ts"],"names":[],"mappings":"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","sourcesContent":["import chalk from 'chalk'\nimport { logger } from './logging'\nimport { compareVersions } from 'compare-versions'\nimport { BaseCommand } from '../commands/connect'\nimport { Command } from 'commander'\nimport { request } from './fetch'\n\nlet updatedVersionAvailable: string | false | undefined = undefined\nlet message: string | undefined = undefined\nlet parserBasedCodeConnectFound = false\nlet deprecationWarningHidden = false\nlet skipUpdateCheck = false\n\n// Called when the native parser has actually produced Code Connect docs, which\n// is the only definitive signal that a project still uses the legacy parsers.\nexport function flagParserBasedCodeConnect() {\n parserBasedCodeConnectFound = true\n}\n\n// The type of the arguments passed to a command handler:\n// any arguments, then the command arguments, then the Command object\ntype CommandArgs<T extends BaseCommand> = [...any[], T, Command]\n\n// Wrap action handlers to check for updates or a message, and output a message\n// after the action if any are available\nexport function withVersionWarnings<T extends BaseCommand>(\n // The second to last argument is always the command args, but I couldn't work\n // out how to model this with Typescript here\n fn: (...args: any[]) => void | Promise<void>,\n { hideDeprecationWarning = false }: { hideDeprecationWarning?: boolean } = {},\n) {\n return (...args: CommandArgs<T>) => {\n // Get the args passed at the command line (the second to last argument)\n const commandArgs = args[args.length - 2]\n // Anything before that is a regular arg\n const restArgs = args.slice(0, -2)\n\n skipUpdateCheck = !!commandArgs.skipUpdateCheck\n deprecationWarningHidden = hideDeprecationWarning\n\n if (skipUpdateCheck) {\n return fn(...restArgs, commandArgs)\n }\n\n startUpdateCheck()\n\n const result = fn(...restArgs, commandArgs)\n if (result instanceof Promise) {\n result.finally(waitAndCheckForUpdates)\n } else {\n waitAndCheckForUpdates()\n }\n }\n}\n\n// Start checking for updates in the background. We don't wait for this before\n// running the action, as we will show the result at the end\nfunction startUpdateCheck() {\n request\n .get<{ tag_name: string }>('https://api.github.com/repos/figma/code-connect/releases/latest')\n .then((response) => {\n const latestVersion = response.data.tag_name.replace(/^v/, '')\n const currentVersion = require('../../package.json').version\n\n if (compareVersions(latestVersion, currentVersion) === 1) {\n updatedVersionAvailable = latestVersion\n } else {\n updatedVersionAvailable = false\n }\n })\n .catch(() => {\n // Silently fail if we can't check for updates\n updatedVersionAvailable = false\n })\n}\n\n// Wait for up to 1 second for the result of update checking to be available,\n// and output a message if there is an update or a message. If there is no\n// result after the timeout, the app will exit.\nasync function waitAndCheckForUpdates() {\n for (let i = 0; i < 10; i++) {\n if (updatedVersionAvailable !== undefined) {\n break\n }\n await new Promise((resolve) => setTimeout(resolve, 100))\n }\n\n maybeShowUpdateMessage()\n}\n\n// Exit the process, first checking if there is an update or message available.\n// Unlike waitAndCheckForUpdates, this will not wait for the update check to\n// complete, so if the request has not completed yet, nothing will be shown.\n// This is to avoid confusion about when the process _actually_ exits.\nexport function exitWithUpdateCheck(errorCode = 1): never {\n if (!skipUpdateCheck) {\n maybeShowUpdateMessage()\n }\n process.exit(errorCode)\n}\n\nfunction getUpdateCommand() {\n return 'npm update -g @figma/code-connect'\n}\n\nconst DEPRECATION_NOTICE = `\nFramework-specific parsers will no longer receive updates or support from August 17th, 2026. Template files will be the only actively maintained way of using Code Connect. See our migration guide for more information and instructions on migrating parser-based Code Connect:\nhttps://developers.figma.com/docs/code-connect/templates-migration-guide/`\n\nfunction maybeShowUpdateMessage() {\n if (parserBasedCodeConnectFound && !deprecationWarningHidden) {\n logger.warn(DEPRECATION_NOTICE)\n }\n\n if (updatedVersionAvailable) {\n logger.warn(`\\nA new version of the Figma CLI is available. v${require('../../package.json').version} is currently installed, and the latest version available is v${updatedVersionAvailable}.\n\nTo update, run ${chalk.whiteBright('npm install @figma/code-connect@latest')} for React or HTML, or ${chalk.whiteBright('npm install -g @figma/code-connect@latest')} for other targets (or if you have Code Connect installed globally).`)\n }\n\n if (message) {\n logger.warn(`\\n${message}`)\n }\n}\n\nexport function updateCli() {\n const { execSync } = require('child_process')\n execSync(getUpdateCommand(), { stdio: 'inherit' })\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"raw_templates.d.ts","sourceRoot":"","sources":["../../src/connect/raw_templates.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAY7C;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAatD;
|
|
1
|
+
{"version":3,"file":"raw_templates.d.ts","sourceRoot":"","sources":["../../src/connect/raw_templates.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAY7C;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAatD;AA6JD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,CAAC,EAAE,iBAAiB,EAC1B,GAAG,CAAC,EAAE,MAAM,EACZ,cAAc,CAAC,EAAE,cAAc,GAC9B,OAAO,CAAC,eAAe,CAAC,CA+G1B"}
|
|
@@ -46,8 +46,9 @@ function isRawTemplate(content) {
|
|
|
46
46
|
}
|
|
47
47
|
return false;
|
|
48
48
|
}
|
|
49
|
-
// Convert ESM import
|
|
50
|
-
|
|
49
|
+
// Convert the supported ESM Figma import to the runtime's require syntax when
|
|
50
|
+
// the template does not otherwise need bundling.
|
|
51
|
+
const figmaImportRegex = /^import[ \t]+figma[ \t]+from[ \t]+['"]figma['"][ \t]*;?[ \t]*(?:\/\/[^\r\n]*)?$/m;
|
|
51
52
|
// Matches the backend's max template size; we fail here rather than let the
|
|
52
53
|
// server reject the publish request.
|
|
53
54
|
const MAX_TEMPLATE_SIZE_MB = 1;
|
|
@@ -66,9 +67,9 @@ function isRawTemplateSourceFile(filePath) {
|
|
|
66
67
|
/**
|
|
67
68
|
* Validates a template entry's imports: only the default `figma` import,
|
|
68
69
|
* type-only imports and relative helper imports are allowed. Returns whether
|
|
69
|
-
* the entry imports helpers, so the caller knows whether to bundle.
|
|
70
|
+
* the entry imports helpers, so the caller knows whether to bundle it.
|
|
70
71
|
*/
|
|
71
|
-
function
|
|
72
|
+
function validateTemplateImports(filePath, fileContent) {
|
|
72
73
|
const sourceFile = typescript_1.default.createSourceFile(filePath, fileContent, typescript_1.default.ScriptTarget.Latest, true);
|
|
73
74
|
let hasRelativeHelperImports = false;
|
|
74
75
|
for (const statement of sourceFile.statements) {
|
|
@@ -118,10 +119,10 @@ function validateTypeScriptTemplateImports(filePath, fileContent) {
|
|
|
118
119
|
visitRequire(sourceFile);
|
|
119
120
|
return { hasRelativeHelperImports };
|
|
120
121
|
}
|
|
121
|
-
function
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
function rewriteFigmaImport(fileContent) {
|
|
123
|
+
return fileContent.replace(figmaImportRegex, "const figma = require('figma')");
|
|
124
|
+
}
|
|
125
|
+
function transpileTypeScriptTemplate(fileContent) {
|
|
125
126
|
const result = typescript_1.default.transpileModule(fileContent, {
|
|
126
127
|
compilerOptions: {
|
|
127
128
|
module: typescript_1.default.ModuleKind.ESNext,
|
|
@@ -168,7 +169,7 @@ function extractMetadataFields(fileContent) {
|
|
|
168
169
|
}
|
|
169
170
|
async function parseRawFile(filePath, label, config, dir, batchOverrides) {
|
|
170
171
|
let fileContent = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
171
|
-
let
|
|
172
|
+
let shouldBundleTemplate = false;
|
|
172
173
|
// Extract metadata fields BEFORE transpilation to avoid losing comments
|
|
173
174
|
// that appear before type-only imports (which TypeScript erases)
|
|
174
175
|
const { fields, templateStartLine } = extractMetadataFields(fileContent);
|
|
@@ -176,7 +177,7 @@ async function parseRawFile(filePath, label, config, dir, batchOverrides) {
|
|
|
176
177
|
// Validate imports first, before the `codeProperties` skip guard below, so an
|
|
177
178
|
// unsupported import is always a hard error rather than silently swallowed.
|
|
178
179
|
if (isRawTemplateSourceFile(filePath)) {
|
|
179
|
-
|
|
180
|
+
shouldBundleTemplate = validateTemplateImports(filePath, fileContent).hasRelativeHelperImports;
|
|
180
181
|
}
|
|
181
182
|
// A file with no // url= directive that contains the string `codeProperties`
|
|
182
183
|
// is not Code Connect (e.g. Make's code component property definitions). Skip
|
|
@@ -185,15 +186,18 @@ async function parseRawFile(filePath, label, config, dir, batchOverrides) {
|
|
|
185
186
|
if (!figmaUrl && fileContent.includes('codeProperties')) {
|
|
186
187
|
throw new CodePropertiesError(`Skipping ${filePath}: file has no // url= directive and contains "codeProperties", so it is not treated as a Code Connect file.`);
|
|
187
188
|
}
|
|
188
|
-
// Bundle
|
|
189
|
-
//
|
|
190
|
-
if (
|
|
189
|
+
// Bundle templates with helper imports. Helper-free templates stay on the
|
|
190
|
+
// faster path and rewrite the Figma import directly to require syntax.
|
|
191
|
+
if (shouldBundleTemplate) {
|
|
191
192
|
// Confining resolution to the project dir
|
|
192
193
|
const projectRoot = dir ? path_1.default.resolve(dir) : path_1.default.dirname(path_1.default.resolve(filePath));
|
|
193
194
|
fileContent = await (0, raw_template_bundler_1.bundleTemplateWithHelpers)(filePath, projectRoot);
|
|
194
195
|
}
|
|
195
|
-
else
|
|
196
|
-
fileContent =
|
|
196
|
+
else {
|
|
197
|
+
fileContent = rewriteFigmaImport(fileContent);
|
|
198
|
+
if (filePath.endsWith('.ts')) {
|
|
199
|
+
fileContent = transpileTypeScriptTemplate(fileContent);
|
|
200
|
+
}
|
|
197
201
|
}
|
|
198
202
|
// For batch templates, metadata comes from the batch entry instead of comments
|
|
199
203
|
const component = batchOverrides?.component || fields.component;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"raw_templates.js","sourceRoot":"","sources":["../../src/connect/raw_templates.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs'\nimport path from 'path'\nimport ts from 'typescript'\nimport { CodeConnectJSON } from './figma_connect'\nimport { CodeConnectConfig } from './project'\nimport { CodeConnectLabel, getInferredLanguageForRaw } from './label_language_mapping'\nimport { applyDocumentUrlSubstitutions } from './helpers'\nimport {\n bundleTemplateWithHelpers,\n allowedHelperExtensions,\n isRelativeImportPath,\n unsupportedImportError,\n relativeRequireError,\n getRequireCallRequest,\n} from './raw_template_bundler'\n\n/**\n * Thrown when a raw template file has no `// url=` directive but contains the\n * string `codeProperties`. These files (e.g. Make's code component property\n * definitions) are not Code Connect, so callers skip them with a log instead of\n * failing. Every other file is handled exactly as before — a missing url is\n * still a hard error.\n */\nexport class CodePropertiesError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'CodePropertiesError'\n }\n}\n\n/**\n * Returns true if the file content looks like a raw template file, i.e. its\n * leading comment block contains a `// url=`, `// component=`, or `// source=`\n * directive. Used to distinguish raw `.figma.ts` templates from React/HTML\n * Code Connect files that share the same extension.\n */\nexport function isRawTemplate(content: string): boolean {\n for (const line of content.split('\\n')) {\n const trimmed = line.trim()\n if (trimmed === '' || trimmed.startsWith('//')) {\n if (/^\\/\\/\\s*(url|component|source)=/.test(trimmed)) {\n return true\n }\n continue\n }\n // First non-comment, non-blank line reached without finding a known directive — not a raw template\n break\n }\n return false\n}\n\n// Convert ESM import of 'figma' to require syntax. Supports: import figma from 'figma'\nconst figmaImportRegex = /^import\\s+figma\\s+from\\s+['\"]figma['\"]\\s*;?\\s*$/m\n\n// Matches the backend's max template size; we fail here rather than let the\n// server reject the publish request.\nconst MAX_TEMPLATE_SIZE_MB = 1\n\nfunction assertTemplateWithinSizeLimit(filePath: string, template: string): void {\n const sizeMb = Buffer.byteLength(template, 'utf-8') / (1024 * 1024)\n if (sizeMb > MAX_TEMPLATE_SIZE_MB) {\n throw new Error(\n `Template \"${filePath}\" is ${sizeMb.toFixed(2)}mb, which exceeds the ` +\n `${MAX_TEMPLATE_SIZE_MB}mb maximum template size. ` +\n `Reduce the template size, for example by removing unneeded helper imports.`,\n )\n }\n}\n\ninterface TypeScriptImportValidationResult {\n hasRelativeHelperImports: boolean\n}\n\n// Parserless entry templates may be authored in TypeScript or JavaScript.\nfunction isRawTemplateSourceFile(filePath: string): boolean {\n return allowedHelperExtensions.some((ext) => filePath.endsWith(ext))\n}\n\n/**\n * Validates a template entry's imports: only the default `figma` import,\n * type-only imports and relative helper imports are allowed. Returns whether\n * the entry imports helpers, so the caller knows whether to bundle.\n */\nfunction validateTypeScriptTemplateImports(\n filePath: string,\n fileContent: string,\n): TypeScriptImportValidationResult {\n const sourceFile = ts.createSourceFile(filePath, fileContent, ts.ScriptTarget.Latest, true)\n let hasRelativeHelperImports = false\n\n for (const statement of sourceFile.statements) {\n if (ts.isImportDeclaration(statement)) {\n if (statement.importClause?.isTypeOnly) {\n continue\n }\n\n const moduleSpecifierText = ts.isStringLiteral(statement.moduleSpecifier)\n ? statement.moduleSpecifier.text\n : ''\n const importLine = statement.getText(sourceFile).split('\\n')[0]?.trim() ?? 'import ...'\n\n if (moduleSpecifierText === 'figma') {\n const hasDefaultImport = !!statement.importClause?.name\n const hasNamedOrNamespaceImport = !!statement.importClause?.namedBindings\n if (!hasDefaultImport || hasNamedOrNamespaceImport) {\n throw unsupportedImportError(filePath, importLine)\n }\n continue\n }\n\n if (isRelativeImportPath(moduleSpecifierText)) {\n hasRelativeHelperImports = true\n continue\n }\n\n throw unsupportedImportError(filePath, importLine)\n }\n\n // Re-exports aren't supported: the runtime only reads the default export.\n if (ts.isExportDeclaration(statement) && statement.moduleSpecifier && !statement.isTypeOnly) {\n const exportLine = statement.getText(sourceFile).split('\\n')[0]?.trim() ?? 'export ...'\n throw new Error(\n `Template files do not support re-exports ('export ... from ...').\\n` +\n `Found in ${filePath}:\\n` +\n ` ${exportLine}\\n\\n` +\n `Import the helper instead (for example: import { helper } from './helpers').`,\n )\n }\n }\n\n // `require` is for the Figma API only: a pure ESM graph is what lets the\n // bundler emit a flat, tree-shaken template. Walking the AST (not the raw\n // text) keeps a `require(...)` inside an emitted snippet from matching.\n const visitRequire = (node: ts.Node): void => {\n const request = getRequireCallRequest(node)\n if (request && request !== 'figma') {\n throw isRelativeImportPath(request)\n ? relativeRequireError(filePath, request)\n : unsupportedImportError(filePath, `require('${request}')`)\n }\n ts.forEachChild(node, visitRequire)\n }\n visitRequire(sourceFile)\n\n return { hasRelativeHelperImports }\n}\n\nfunction transpileTypeScriptTemplate(filePath: string, fileContent: string): string {\n if (figmaImportRegex.test(fileContent)) {\n fileContent = fileContent.replace(figmaImportRegex, \"const figma = require('figma')\")\n }\n\n const result = ts.transpileModule(fileContent, {\n compilerOptions: {\n module: ts.ModuleKind.ESNext,\n target: ts.ScriptTarget.ES2021,\n removeComments: false,\n },\n })\n\n return result.outputText\n}\n\n/**\n * Extracts metadata fields (url, component, source) from the leading comments\n * of a raw template file. This must be done before transpilation because\n * TypeScript can remove comments that appear before type-only imports.\n */\nfunction extractMetadataFields(fileContent: string): {\n fields: { url?: string; component?: string; source?: string }\n templateStartLine: number\n} {\n const lines = fileContent.split('\\n')\n const fields: { url?: string; component?: string; source?: string } = {}\n let templateStartLine = 0\n\n // Parse consecutive comment lines at the start of the file\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim()\n // Match pattern: // fieldName=value\n const match = line.match(/^\\/\\/\\s*(\\w+)=(.+)$/)\n if (match) {\n const [, fieldName, fieldValue] = match\n const normalizedFieldName = fieldName.toLowerCase()\n if (\n normalizedFieldName === 'url' ||\n normalizedFieldName === 'component' ||\n normalizedFieldName === 'source'\n ) {\n fields[normalizedFieldName] = fieldValue.trim()\n }\n templateStartLine = i + 1\n } else if (line === '' || line.startsWith('//')) {\n // Allow blank lines or other comments, but don't increment template start\n continue\n } else {\n // First non-comment line found\n break\n }\n }\n\n return { fields, templateStartLine }\n}\n\nexport interface BatchOverrides {\n url: string\n source?: string\n component?: string\n batchData: Record<string, any>\n batchFilePath: string\n}\n\nexport async function parseRawFile(\n filePath: string,\n label: string | undefined,\n config?: CodeConnectConfig,\n dir?: string,\n batchOverrides?: BatchOverrides,\n): Promise<CodeConnectJSON> {\n let fileContent = fs.readFileSync(filePath, 'utf-8')\n let shouldBundleTypeScriptTemplate = false\n\n // Extract metadata fields BEFORE transpilation to avoid losing comments\n // that appear before type-only imports (which TypeScript erases)\n const { fields, templateStartLine } = extractMetadataFields(fileContent)\n\n const figmaUrl = batchOverrides?.url || fields.url\n\n // Validate imports first, before the `codeProperties` skip guard below, so an\n // unsupported import is always a hard error rather than silently swallowed.\n if (isRawTemplateSourceFile(filePath)) {\n shouldBundleTypeScriptTemplate = validateTypeScriptTemplateImports(\n filePath,\n fileContent,\n ).hasRelativeHelperImports\n }\n\n // A file with no // url= directive that contains the string `codeProperties`\n // is not Code Connect (e.g. Make's code component property definitions). Skip\n // it (callers log and continue) instead of failing. Every other file is\n // handled exactly as before.\n if (!figmaUrl && fileContent.includes('codeProperties')) {\n throw new CodePropertiesError(\n `Skipping ${filePath}: file has no // url= directive and contains \"codeProperties\", so it is not treated as a Code Connect file.`,\n )\n }\n\n // Bundle when helpers are present; otherwise transpile TS and emit JS verbatim.\n // Bundling takes some extra time.\n if (shouldBundleTypeScriptTemplate) {\n // Confining resolution to the project dir\n const projectRoot = dir ? path.resolve(dir) : path.dirname(path.resolve(filePath))\n fileContent = await bundleTemplateWithHelpers(filePath, projectRoot)\n } else if (filePath.endsWith('.ts')) {\n fileContent = transpileTypeScriptTemplate(filePath, fileContent)\n }\n\n // For batch templates, metadata comes from the batch entry instead of comments\n const component = batchOverrides?.component || fields.component\n const source = batchOverrides?.source || fields.source || ''\n\n if (!figmaUrl) {\n throw new Error(\n batchOverrides\n ? `Missing required \"url\" field in ${batchOverrides.batchFilePath}. Please add \"url\" to each entry in your .figma.batch.json file.`\n : `Missing required url field in ${filePath}. Please add a // url=... comment to the top of the file.`,\n )\n }\n let figmaNodeUrl = figmaUrl\n\n // Extract template from the transpiled content, starting at the line\n // where the metadata comments ended in the original source\n const transpiledLines = fileContent.split('\\n')\n let templateStartIndex = 0\n\n // Skip lines until we've passed the original metadata comment section\n for (let i = 0; i < transpiledLines.length; i++) {\n const line = transpiledLines[i].trim()\n // Skip blank lines and comments at the start\n if (line === '' || line.startsWith('//')) {\n continue\n }\n // First non-comment line in transpiled output\n templateStartIndex = i\n break\n }\n\n let template = transpiledLines.slice(templateStartIndex).join('\\n')\n\n // For batch templates, set globalThis['__FIGMA_BATCH'] so the runtime exposes it as\n // figma.batch. Using globalThis rather than a const so it's accessible from\n // __FIGMA_CODE_CONNECT_REQUIRE's closure regardless of where require('figma') is called.\n // (globalThis works in both browser and Node.js, unlike window.)\n if (batchOverrides) {\n template = `globalThis['__FIGMA_BATCH'] = ${JSON.stringify(\n batchOverrides.batchData,\n )}\\n${template}`\n }\n\n // Check the final template (incl. any bundled helpers and batch data) against\n // the backend's size cap so an oversized template fails here, not on upload.\n assertTemplateWithinSizeLimit(filePath, template)\n\n // Apply documentUrlSubstitutions if provided\n if (config?.documentUrlSubstitutions) {\n figmaNodeUrl = applyDocumentUrlSubstitutions(figmaNodeUrl, config.documentUrlSubstitutions)\n }\n\n // Determine effective label from parameter, config, or default\n const effectiveLabel = label || config?.label || CodeConnectLabel.Code\n\n const language = config?.language || getInferredLanguageForRaw(effectiveLabel)\n\n return {\n figmaNode: figmaNodeUrl,\n component,\n template,\n // nestable by default unless user specifies in template\n // (templateData.nestable AND template.metadata.nestable need to\n // be true for instance to be nested)\n templateData: { nestable: true, isParserless: true },\n language,\n label: effectiveLabel,\n source,\n sourceLocation: { line: -1 },\n metadata: {\n cliVersion: require('../../package.json').version,\n },\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"raw_templates.js","sourceRoot":"","sources":["../../src/connect/raw_templates.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs'\nimport path from 'path'\nimport ts from 'typescript'\nimport { CodeConnectJSON } from './figma_connect'\nimport { CodeConnectConfig } from './project'\nimport { CodeConnectLabel, getInferredLanguageForRaw } from './label_language_mapping'\nimport { applyDocumentUrlSubstitutions } from './helpers'\nimport {\n bundleTemplateWithHelpers,\n allowedHelperExtensions,\n isRelativeImportPath,\n unsupportedImportError,\n relativeRequireError,\n getRequireCallRequest,\n} from './raw_template_bundler'\n\n/**\n * Thrown when a raw template file has no `// url=` directive but contains the\n * string `codeProperties`. These files (e.g. Make's code component property\n * definitions) are not Code Connect, so callers skip them with a log instead of\n * failing. Every other file is handled exactly as before — a missing url is\n * still a hard error.\n */\nexport class CodePropertiesError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'CodePropertiesError'\n }\n}\n\n/**\n * Returns true if the file content looks like a raw template file, i.e. its\n * leading comment block contains a `// url=`, `// component=`, or `// source=`\n * directive. Used to distinguish raw `.figma.ts` templates from React/HTML\n * Code Connect files that share the same extension.\n */\nexport function isRawTemplate(content: string): boolean {\n for (const line of content.split('\\n')) {\n const trimmed = line.trim()\n if (trimmed === '' || trimmed.startsWith('//')) {\n if (/^\\/\\/\\s*(url|component|source)=/.test(trimmed)) {\n return true\n }\n continue\n }\n // First non-comment, non-blank line reached without finding a known directive — not a raw template\n break\n }\n return false\n}\n\n// Convert the supported ESM Figma import to the runtime's require syntax when\n// the template does not otherwise need bundling.\nconst figmaImportRegex =\n /^import[ \\t]+figma[ \\t]+from[ \\t]+['\"]figma['\"][ \\t]*;?[ \\t]*(?:\\/\\/[^\\r\\n]*)?$/m\n\n// Matches the backend's max template size; we fail here rather than let the\n// server reject the publish request.\nconst MAX_TEMPLATE_SIZE_MB = 1\n\nfunction assertTemplateWithinSizeLimit(filePath: string, template: string): void {\n const sizeMb = Buffer.byteLength(template, 'utf-8') / (1024 * 1024)\n if (sizeMb > MAX_TEMPLATE_SIZE_MB) {\n throw new Error(\n `Template \"${filePath}\" is ${sizeMb.toFixed(2)}mb, which exceeds the ` +\n `${MAX_TEMPLATE_SIZE_MB}mb maximum template size. ` +\n `Reduce the template size, for example by removing unneeded helper imports.`,\n )\n }\n}\n\ninterface TemplateImportValidationResult {\n hasRelativeHelperImports: boolean\n}\n\n// Parserless entry templates may be authored in TypeScript or JavaScript.\nfunction isRawTemplateSourceFile(filePath: string): boolean {\n return allowedHelperExtensions.some((ext) => filePath.endsWith(ext))\n}\n\n/**\n * Validates a template entry's imports: only the default `figma` import,\n * type-only imports and relative helper imports are allowed. Returns whether\n * the entry imports helpers, so the caller knows whether to bundle it.\n */\nfunction validateTemplateImports(\n filePath: string,\n fileContent: string,\n): TemplateImportValidationResult {\n const sourceFile = ts.createSourceFile(filePath, fileContent, ts.ScriptTarget.Latest, true)\n let hasRelativeHelperImports = false\n\n for (const statement of sourceFile.statements) {\n if (ts.isImportDeclaration(statement)) {\n if (statement.importClause?.isTypeOnly) {\n continue\n }\n\n const moduleSpecifierText = ts.isStringLiteral(statement.moduleSpecifier)\n ? statement.moduleSpecifier.text\n : ''\n const importLine = statement.getText(sourceFile).split('\\n')[0]?.trim() ?? 'import ...'\n\n if (moduleSpecifierText === 'figma') {\n const hasDefaultImport = !!statement.importClause?.name\n const hasNamedOrNamespaceImport = !!statement.importClause?.namedBindings\n if (!hasDefaultImport || hasNamedOrNamespaceImport) {\n throw unsupportedImportError(filePath, importLine)\n }\n continue\n }\n\n if (isRelativeImportPath(moduleSpecifierText)) {\n hasRelativeHelperImports = true\n continue\n }\n\n throw unsupportedImportError(filePath, importLine)\n }\n\n // Re-exports aren't supported: the runtime only reads the default export.\n if (ts.isExportDeclaration(statement) && statement.moduleSpecifier && !statement.isTypeOnly) {\n const exportLine = statement.getText(sourceFile).split('\\n')[0]?.trim() ?? 'export ...'\n throw new Error(\n `Template files do not support re-exports ('export ... from ...').\\n` +\n `Found in ${filePath}:\\n` +\n ` ${exportLine}\\n\\n` +\n `Import the helper instead (for example: import { helper } from './helpers').`,\n )\n }\n }\n\n // `require` is for the Figma API only: a pure ESM graph is what lets the\n // bundler emit a flat, tree-shaken template. Walking the AST (not the raw\n // text) keeps a `require(...)` inside an emitted snippet from matching.\n const visitRequire = (node: ts.Node): void => {\n const request = getRequireCallRequest(node)\n if (request && request !== 'figma') {\n throw isRelativeImportPath(request)\n ? relativeRequireError(filePath, request)\n : unsupportedImportError(filePath, `require('${request}')`)\n }\n ts.forEachChild(node, visitRequire)\n }\n visitRequire(sourceFile)\n\n return { hasRelativeHelperImports }\n}\n\nfunction rewriteFigmaImport(fileContent: string): string {\n return fileContent.replace(figmaImportRegex, \"const figma = require('figma')\")\n}\n\nfunction transpileTypeScriptTemplate(fileContent: string): string {\n const result = ts.transpileModule(fileContent, {\n compilerOptions: {\n module: ts.ModuleKind.ESNext,\n target: ts.ScriptTarget.ES2021,\n removeComments: false,\n },\n })\n\n return result.outputText\n}\n\n/**\n * Extracts metadata fields (url, component, source) from the leading comments\n * of a raw template file. This must be done before transpilation because\n * TypeScript can remove comments that appear before type-only imports.\n */\nfunction extractMetadataFields(fileContent: string): {\n fields: { url?: string; component?: string; source?: string }\n templateStartLine: number\n} {\n const lines = fileContent.split('\\n')\n const fields: { url?: string; component?: string; source?: string } = {}\n let templateStartLine = 0\n\n // Parse consecutive comment lines at the start of the file\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim()\n // Match pattern: // fieldName=value\n const match = line.match(/^\\/\\/\\s*(\\w+)=(.+)$/)\n if (match) {\n const [, fieldName, fieldValue] = match\n const normalizedFieldName = fieldName.toLowerCase()\n if (\n normalizedFieldName === 'url' ||\n normalizedFieldName === 'component' ||\n normalizedFieldName === 'source'\n ) {\n fields[normalizedFieldName] = fieldValue.trim()\n }\n templateStartLine = i + 1\n } else if (line === '' || line.startsWith('//')) {\n // Allow blank lines or other comments, but don't increment template start\n continue\n } else {\n // First non-comment line found\n break\n }\n }\n\n return { fields, templateStartLine }\n}\n\nexport interface BatchOverrides {\n url: string\n source?: string\n component?: string\n batchData: Record<string, any>\n batchFilePath: string\n}\n\nexport async function parseRawFile(\n filePath: string,\n label: string | undefined,\n config?: CodeConnectConfig,\n dir?: string,\n batchOverrides?: BatchOverrides,\n): Promise<CodeConnectJSON> {\n let fileContent = fs.readFileSync(filePath, 'utf-8')\n let shouldBundleTemplate = false\n\n // Extract metadata fields BEFORE transpilation to avoid losing comments\n // that appear before type-only imports (which TypeScript erases)\n const { fields, templateStartLine } = extractMetadataFields(fileContent)\n\n const figmaUrl = batchOverrides?.url || fields.url\n\n // Validate imports first, before the `codeProperties` skip guard below, so an\n // unsupported import is always a hard error rather than silently swallowed.\n if (isRawTemplateSourceFile(filePath)) {\n shouldBundleTemplate = validateTemplateImports(filePath, fileContent).hasRelativeHelperImports\n }\n\n // A file with no // url= directive that contains the string `codeProperties`\n // is not Code Connect (e.g. Make's code component property definitions). Skip\n // it (callers log and continue) instead of failing. Every other file is\n // handled exactly as before.\n if (!figmaUrl && fileContent.includes('codeProperties')) {\n throw new CodePropertiesError(\n `Skipping ${filePath}: file has no // url= directive and contains \"codeProperties\", so it is not treated as a Code Connect file.`,\n )\n }\n\n // Bundle templates with helper imports. Helper-free templates stay on the\n // faster path and rewrite the Figma import directly to require syntax.\n if (shouldBundleTemplate) {\n // Confining resolution to the project dir\n const projectRoot = dir ? path.resolve(dir) : path.dirname(path.resolve(filePath))\n fileContent = await bundleTemplateWithHelpers(filePath, projectRoot)\n } else {\n fileContent = rewriteFigmaImport(fileContent)\n if (filePath.endsWith('.ts')) {\n fileContent = transpileTypeScriptTemplate(fileContent)\n }\n }\n\n // For batch templates, metadata comes from the batch entry instead of comments\n const component = batchOverrides?.component || fields.component\n const source = batchOverrides?.source || fields.source || ''\n\n if (!figmaUrl) {\n throw new Error(\n batchOverrides\n ? `Missing required \"url\" field in ${batchOverrides.batchFilePath}. Please add \"url\" to each entry in your .figma.batch.json file.`\n : `Missing required url field in ${filePath}. Please add a // url=... comment to the top of the file.`,\n )\n }\n let figmaNodeUrl = figmaUrl\n\n // Extract template from the transpiled content, starting at the line\n // where the metadata comments ended in the original source\n const transpiledLines = fileContent.split('\\n')\n let templateStartIndex = 0\n\n // Skip lines until we've passed the original metadata comment section\n for (let i = 0; i < transpiledLines.length; i++) {\n const line = transpiledLines[i].trim()\n // Skip blank lines and comments at the start\n if (line === '' || line.startsWith('//')) {\n continue\n }\n // First non-comment line in transpiled output\n templateStartIndex = i\n break\n }\n\n let template = transpiledLines.slice(templateStartIndex).join('\\n')\n\n // For batch templates, set globalThis['__FIGMA_BATCH'] so the runtime exposes it as\n // figma.batch. Using globalThis rather than a const so it's accessible from\n // __FIGMA_CODE_CONNECT_REQUIRE's closure regardless of where require('figma') is called.\n // (globalThis works in both browser and Node.js, unlike window.)\n if (batchOverrides) {\n template = `globalThis['__FIGMA_BATCH'] = ${JSON.stringify(\n batchOverrides.batchData,\n )}\\n${template}`\n }\n\n // Check the final template (incl. any bundled helpers and batch data) against\n // the backend's size cap so an oversized template fails here, not on upload.\n assertTemplateWithinSizeLimit(filePath, template)\n\n // Apply documentUrlSubstitutions if provided\n if (config?.documentUrlSubstitutions) {\n figmaNodeUrl = applyDocumentUrlSubstitutions(figmaNodeUrl, config.documentUrlSubstitutions)\n }\n\n // Determine effective label from parameter, config, or default\n const effectiveLabel = label || config?.label || CodeConnectLabel.Code\n\n const language = config?.language || getInferredLanguageForRaw(effectiveLabel)\n\n return {\n figmaNode: figmaNodeUrl,\n component,\n template,\n // nestable by default unless user specifies in template\n // (templateData.nestable AND template.metadata.nestable need to\n // be true for instance to be nested)\n templateData: { nestable: true, isParserless: true },\n language,\n label: effectiveLabel,\n source,\n sourceLocation: { line: -1 },\n metadata: {\n cliVersion: require('../../package.json').version,\n },\n }\n}\n"]}
|