@figma/code-connect 0.1.2 → 0.2.1
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 +123 -25
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/connect.d.ts +15 -0
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +137 -76
- package/dist/commands/connect.js.map +1 -1
- package/dist/common/__test__/project.test.d.ts +2 -0
- package/dist/common/__test__/project.test.d.ts.map +1 -0
- package/dist/common/__test__/project.test.js +14 -0
- package/dist/common/__test__/project.test.js.map +1 -0
- package/dist/common/api.d.ts +56 -2
- package/dist/common/api.d.ts.map +1 -1
- package/dist/common/external.d.ts +6 -3
- package/dist/common/external.d.ts.map +1 -1
- package/dist/common/external.js +13 -1
- package/dist/common/external.js.map +1 -1
- package/dist/common/figma_connect.d.ts +10 -8
- package/dist/common/figma_connect.d.ts.map +1 -1
- package/dist/common/intrinsics.d.ts +38 -6
- package/dist/common/intrinsics.d.ts.map +1 -1
- package/dist/common/intrinsics.js +160 -26
- package/dist/common/intrinsics.js.map +1 -1
- package/dist/common/logging.d.ts +9 -0
- package/dist/common/logging.d.ts.map +1 -1
- package/dist/common/logging.js +21 -1
- package/dist/common/logging.js.map +1 -1
- package/dist/common/updates.d.ts +4 -0
- package/dist/common/updates.d.ts.map +1 -0
- package/dist/common/updates.js +87 -0
- package/dist/common/updates.js.map +1 -0
- package/dist/connect/create.d.ts +5 -1
- package/dist/connect/create.d.ts.map +1 -1
- package/dist/connect/create.js +55 -69
- package/dist/connect/create.js.map +1 -1
- package/dist/connect/delete_docs.d.ts.map +1 -1
- package/dist/connect/delete_docs.js +1 -4
- package/dist/connect/delete_docs.js.map +1 -1
- package/dist/connect/figma_rest_api.d.ts +5 -0
- package/dist/connect/figma_rest_api.d.ts.map +1 -1
- package/dist/connect/figma_rest_api.js +10 -1
- package/dist/connect/figma_rest_api.js.map +1 -1
- package/dist/connect/helpers.d.ts +1 -1
- package/dist/connect/helpers.d.ts.map +1 -1
- package/dist/connect/helpers.js +6 -5
- package/dist/connect/helpers.js.map +1 -1
- package/dist/connect/parser_executable_types.d.ts +297 -0
- package/dist/connect/parser_executable_types.d.ts.map +1 -0
- package/dist/connect/parser_executable_types.js +108 -0
- package/dist/connect/parser_executable_types.js.map +1 -0
- package/dist/connect/parser_executables.d.ts +8 -0
- package/dist/connect/parser_executables.d.ts.map +1 -0
- package/dist/connect/parser_executables.js +132 -0
- package/dist/connect/parser_executables.js.map +1 -0
- package/dist/connect/project.d.ts +137 -0
- package/dist/connect/project.d.ts.map +1 -0
- package/dist/connect/project.js +411 -0
- package/dist/connect/project.js.map +1 -0
- package/dist/connect/upload.d.ts.map +1 -1
- package/dist/connect/upload.js +1 -4
- package/dist/connect/upload.js.map +1 -1
- package/dist/connect/validation.d.ts +6 -1
- package/dist/connect/validation.d.ts.map +1 -1
- package/dist/connect/validation.js +23 -18
- package/dist/connect/validation.js.map +1 -1
- package/dist/connect/wizard/helpers.d.ts +7 -0
- package/dist/connect/wizard/helpers.d.ts.map +1 -0
- package/dist/connect/wizard/helpers.js +55 -0
- package/dist/connect/wizard/helpers.js.map +1 -0
- package/dist/connect/wizard/run_wizard.d.ts +3 -0
- package/dist/connect/wizard/run_wizard.d.ts.map +1 -0
- package/dist/connect/wizard/run_wizard.js +480 -0
- package/dist/connect/wizard/run_wizard.js.map +1 -0
- package/dist/parser_scripts/get_file_if_exists.d.ts +2 -0
- package/dist/parser_scripts/get_file_if_exists.d.ts.map +1 -0
- package/dist/parser_scripts/get_file_if_exists.js +10 -0
- package/dist/parser_scripts/get_file_if_exists.js.map +1 -0
- package/dist/parser_scripts/get_gradlew_path.d.ts +3 -0
- package/dist/parser_scripts/get_gradlew_path.d.ts.map +1 -0
- package/dist/parser_scripts/get_gradlew_path.js +24 -0
- package/dist/parser_scripts/get_gradlew_path.js.map +1 -0
- package/dist/parser_scripts/get_swift_parser_dir.d.ts +2 -0
- package/dist/parser_scripts/get_swift_parser_dir.d.ts.map +1 -0
- package/dist/parser_scripts/get_swift_parser_dir.js +93 -0
- package/dist/parser_scripts/get_swift_parser_dir.js.map +1 -0
- package/dist/react/create.d.ts +4 -0
- package/dist/react/create.d.ts.map +1 -0
- package/dist/react/create.js +132 -0
- package/dist/react/create.js.map +1 -0
- package/dist/react/parser.d.ts +6 -15
- package/dist/react/parser.d.ts.map +1 -1
- package/dist/react/parser.js +55 -62
- package/dist/react/parser.js.map +1 -1
- package/dist/react/parser_template_helpers.d.ts +21 -0
- package/dist/react/parser_template_helpers.d.ts.map +1 -1
- package/dist/react/parser_template_helpers.js +107 -43
- package/dist/react/parser_template_helpers.js.map +1 -1
- package/dist/storybook/convert.d.ts +2 -2
- package/dist/storybook/convert.d.ts.map +1 -1
- package/dist/storybook/convert.js +6 -4
- package/dist/storybook/convert.js.map +1 -1
- package/package.json +36 -11
- package/dist/common/project.d.ts +0 -94
- package/dist/common/project.d.ts.map +0 -1
- package/dist/common/project.js +0 -198
- package/dist/common/project.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Code Connect (React)
|
|
2
2
|
|
|
3
|
-
For more information about Code Connect as well as guides for other platforms and frameworks, please [go here](
|
|
3
|
+
For more information about Code Connect as well as guides for other platforms and frameworks, please [go here](../README.md).
|
|
4
4
|
|
|
5
5
|
This documentation will help you connect your React components with Figma components using Code Connect. We'll cover basic setup to display your first connected code snippet, followed by making snippets dynamic by using property mappings. Code Connect for React works as both a standalone implementation and as an integration with existing Storybook files to enable easily maintaining both systems in parallel.
|
|
6
6
|
|
|
@@ -14,7 +14,7 @@ npm install @figma/code-connect
|
|
|
14
14
|
|
|
15
15
|
## Basic setup
|
|
16
16
|
|
|
17
|
-
To connect your first component go to Dev Mode in Figma and right-click on the component you want to connect, then choose `Copy link to selection` from the menu. Make sure you are copying the link to a main component and not an instance of the component. The main component will typically be located in a centralized design system library file. Using this link, run `figma connect create
|
|
17
|
+
To connect your first component go to Dev Mode in Figma and right-click on the component you want to connect, then choose `Copy link to selection` from the menu. Make sure you are copying the link to a main component and not an instance of the component. The main component will typically be located in a centralized design system library file. Using this link, run `figma connect create` from inside your React project. Note that depending on what terminal software you're using, you might need to wrap the URL in quotes.
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
20
|
npx figma connect create "https://..." --token <auth token>
|
|
@@ -98,33 +98,25 @@ npx figma connect unpublish --token <token>
|
|
|
98
98
|
|
|
99
99
|
## Configuration
|
|
100
100
|
|
|
101
|
-
To configure the behaviour of the CLI and Code Connect you can create a `figma.config.json` file. This config file will automatically be picked up by the CLI if it's in the same folder where you run the commands, but you can also specify a path to the config file via the `--config` flag.
|
|
101
|
+
To configure the behaviour of the CLI and Code Connect you can create a `figma.config.json` file. This file must be located in the project root, i.e. alongside your `package.json` file. This config file will automatically be picked up by the CLI if it's in the same folder where you run the commands, but you can also specify a path to the config file via the `--config` flag.
|
|
102
102
|
|
|
103
|
-
In addition to the [general configuration](../README.md) for the CLI there is React-specific project configuration that can be specified in the configuration file. This configuration is mainly used to ensure Code Connect can correctly locate your imports as well as display the correct imports within Dev Mode.
|
|
103
|
+
In addition to the [general configuration](../README.md#general-configuration) for the CLI, there is React-specific project configuration that can be specified in the configuration file. This configuration is mainly used to ensure Code Connect can correctly locate your imports as well as display the correct imports within Dev Mode.
|
|
104
104
|
|
|
105
105
|
```jsonp
|
|
106
106
|
{
|
|
107
107
|
"codeConnect": {
|
|
108
108
|
"include": [],
|
|
109
109
|
"exclude": ["test/**", "docs/**", "build/**"],
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
"paths": {
|
|
117
|
-
"@ui/components/*": ["src/components/*"]
|
|
118
|
-
}
|
|
110
|
+
"importPaths": {
|
|
111
|
+
"src/components/*": "@ui/components"
|
|
112
|
+
},
|
|
113
|
+
"paths": {
|
|
114
|
+
"@ui/components/*": ["src/components/*"]
|
|
119
115
|
}
|
|
120
116
|
}
|
|
121
117
|
}
|
|
122
118
|
```
|
|
123
119
|
|
|
124
|
-
### `include` and `exclude`
|
|
125
|
-
|
|
126
|
-
`include` and `exclude` are lists of globs for where to parse Code Connect files. `include` and `exclude` paths must be relative to the location of the config file.
|
|
127
|
-
|
|
128
120
|
### `importPaths`
|
|
129
121
|
|
|
130
122
|
`importPaths` maps relative imports to non-relative imports. This is useful for when you want users of your design system to import components from a specific package as opposed to using relative imports. The mapping uses the file location on disk. For example, if your Code Connect file looks like this:
|
|
@@ -140,10 +132,8 @@ consider the full absolute path of the source file `Button.tsx`.
|
|
|
140
132
|
```
|
|
141
133
|
{
|
|
142
134
|
"codeConnect": {
|
|
143
|
-
"
|
|
144
|
-
"
|
|
145
|
-
"src/components/*": "@ui/components"
|
|
146
|
-
}
|
|
135
|
+
"importPaths": {
|
|
136
|
+
"src/components/*": "@ui/components"
|
|
147
137
|
}
|
|
148
138
|
}
|
|
149
139
|
}
|
|
@@ -159,6 +149,16 @@ import { Button } from '@ui/components'
|
|
|
159
149
|
|
|
160
150
|
This is needed if you're using path aliases in your TypeScript project configuration, so Code Connect can know how to resolve your imports. It should match the `paths` object used in your tsconfig.json.
|
|
161
151
|
|
|
152
|
+
## Custom imports
|
|
153
|
+
|
|
154
|
+
You can override the generated import statements for a connected component by passing an array of `imports`. This might be useful if the automatic resolution does not work well for your use case.
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
figma.connect(Button, "https://...", {
|
|
158
|
+
imports: ["import { Button } from '@lib'"]
|
|
159
|
+
})
|
|
160
|
+
```
|
|
161
|
+
|
|
162
162
|
## Dynamic code snippets
|
|
163
163
|
|
|
164
164
|
With the basic setup as described above, you should have a connected code snippet visible in Dev Mode when inspecting instances of that component. However, the code snippet doesn't yet reflect the entirety of the design. For example, we see the same code snippet for a button whether it has the `type` variant set to `primary` or `secondary`.
|
|
@@ -179,10 +179,10 @@ figma.connect(Button, 'https://...', {
|
|
|
179
179
|
Secondary: 'secondary',
|
|
180
180
|
}),
|
|
181
181
|
},
|
|
182
|
-
example: ({ disabled,
|
|
182
|
+
example: ({ disabled, label, type }) => {
|
|
183
183
|
return (
|
|
184
184
|
<Button disabled={disabled} type={type}>
|
|
185
|
-
{
|
|
185
|
+
{label}
|
|
186
186
|
</Button>
|
|
187
187
|
)
|
|
188
188
|
},
|
|
@@ -235,6 +235,27 @@ export function ButtonExample({ label, disabled, type }) {
|
|
|
235
235
|
|
|
236
236
|
The `figma` import contains helpers for mapping all sorts of properties from design to code. They work for simple mappings where only the naming differs between Figma and code, as well as more complex mappings where the type differs. See the below reference for all the helpers that exist and the ways you can use them to connect Figma and code components using Code Connect.
|
|
237
237
|
|
|
238
|
+
|
|
239
|
+
### figma.connect
|
|
240
|
+
|
|
241
|
+
`figma.connect()` has two signatures for connecting components.
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
// connect a component in code to a Figma component
|
|
245
|
+
figma.connect(Button, "https://...")
|
|
246
|
+
|
|
247
|
+
// connect a Figma component to e.g a native element
|
|
248
|
+
figma.connect("https://...")
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The second option is useful if you want to just render a HTML tag instead of a React component. The first argument is used to determine where your component lives in code, in order to generate an import statement for the component. This isn't needed if you just want to render e.g a `button` tag.
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
figma.connect("https://...", {
|
|
255
|
+
example: () => <button>click me</button>
|
|
256
|
+
})
|
|
257
|
+
```
|
|
258
|
+
|
|
238
259
|
### Strings
|
|
239
260
|
|
|
240
261
|
Strings are the simplest value to map from Figma to code. Simply call `figma.string` with the Figma prop name you want to reference as a parameter. This is useful for things like button labels, header titles, tooltips, etc.
|
|
@@ -331,7 +352,19 @@ figma.connect(Icon32Add, 'https://...')
|
|
|
331
352
|
|
|
332
353
|
### Instance children
|
|
333
354
|
|
|
334
|
-
It's common for components in Figma to have child instances that aren't bound to an instance-swap prop. Similarly to `figma.instance`, we can render the code snippets for these nested instances with `figma.children`. This helper takes the _name of the instance
|
|
355
|
+
It's common for components in Figma to have child instances that aren't bound to an instance-swap prop. Similarly to `figma.instance`, we can render the code snippets for these nested instances with `figma.children`. This helper takes the _name of the instance layer within the parent component_ as its parameter, rather than a Figma prop name.
|
|
356
|
+
|
|
357
|
+
To illustrate this, consider the layer hierarchy in a component vs an instance of that component:
|
|
358
|
+
|
|
359
|
+
Button (Component)
|
|
360
|
+
Icon (Instance) -- "Icon" is the original name of the layer, this is what you should pass to `figma.children()`
|
|
361
|
+
|
|
362
|
+
Button (Instance)
|
|
363
|
+
RenamedIcon (Instance) -- here the instance layer was renamed, which won't break the mapping since we're not using this name
|
|
364
|
+
|
|
365
|
+
Note that the nested instance also must be connected separately.
|
|
366
|
+
|
|
367
|
+
> Layer names may differ between variants in a component set. To ensure the component (Button) can render a nested instance (Icon) for any of those variants, you must either use the wildcard option `figma.children("*")` or ensure that the layer name representing the instance (Icon) is the same across all variants of your component set (Button).
|
|
335
368
|
|
|
336
369
|
```tsx
|
|
337
370
|
// map one child instance with the layer name "Tab"
|
|
@@ -341,11 +374,76 @@ figma.children('Tab')
|
|
|
341
374
|
figma.children(['Tab 1', 'Tab 2'])
|
|
342
375
|
```
|
|
343
376
|
|
|
377
|
+
### Wildcard match
|
|
378
|
+
|
|
379
|
+
`figma.children()` can be used with a single wildcard '*' character, to partially match names or to render any nested child. Wildcards cannot be used with the array argument. Matches are case sensitive.
|
|
380
|
+
|
|
381
|
+
```tsx
|
|
382
|
+
// map any (all) child instances
|
|
383
|
+
figma.children('*')
|
|
384
|
+
|
|
385
|
+
// map any child instances that starts with "Icon"
|
|
386
|
+
figma.children('Icon*')
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Nested properties
|
|
390
|
+
|
|
391
|
+
In cases where you don't want to connect a child component, but instead map its properties on the parent level, you can use
|
|
392
|
+
`figma.nestedProps()` to achieve this. This helper takes the name of the layer as it's first parameter (similar to `figma.children`),
|
|
393
|
+
and a mapping object as the second parameter. These props can then be referenced in the example function.
|
|
394
|
+
|
|
395
|
+
```tsx
|
|
396
|
+
// map the properties of a nested instance named "Button Shape"
|
|
397
|
+
figma.connect(Button, "https://...", {
|
|
398
|
+
props: {
|
|
399
|
+
buttonShape: figma.nestedProps('Button Shape', {
|
|
400
|
+
size: figma.enum({ ... }),
|
|
401
|
+
})
|
|
402
|
+
},
|
|
403
|
+
example: ({ buttonShape }) => <Button size={buttonShape.size} />
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Text Content
|
|
408
|
+
|
|
409
|
+
A common pattern for design systems in Figma is to not use props for texts, but rather rely on instances overriding the text content. `figma.textContent()` allows you to select a child text layer and render its content. It takes a single parameter which is the name of the layer in the original component.
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
figma.connect(Button, "https://...", {
|
|
413
|
+
props: {
|
|
414
|
+
label: figma.textContent("Text Layer")
|
|
415
|
+
},
|
|
416
|
+
example: ({ label }) => <Button>{label}</Button>
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### className
|
|
421
|
+
|
|
422
|
+
For mapping figma properties to a className string, you can use the `figma.className` helper. It takes an array of strings and returns the concatenated string. Any other helper that returns a string (or undefined) can be used in conjunction with this. Undefined values or empty strings will be filtered out from the result
|
|
423
|
+
|
|
424
|
+
```tsx
|
|
425
|
+
figma.connect("https://...", {
|
|
426
|
+
props: {
|
|
427
|
+
className: figma.className([
|
|
428
|
+
'btn-base',
|
|
429
|
+
figma.enum("Size", { Large: 'btn-large' }),
|
|
430
|
+
figma.boolean("Disabled", { true: 'btn-disabled', false: '' }),
|
|
431
|
+
])
|
|
432
|
+
},
|
|
433
|
+
example: ({ className }) => <button className={className} />
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
In Dev Mode this will display as:
|
|
438
|
+
```
|
|
439
|
+
<button className="btn-base btn-large btn-disabled" />
|
|
440
|
+
```
|
|
441
|
+
|
|
344
442
|
## Variant restrictions
|
|
345
443
|
|
|
346
444
|
Sometimes a component in Figma is represented by more than one component in code. For example you may have a single `Button` in your Figma design system with a `type` property to switch between primary, secondary, and danger variants. However, in code this may be represented by three different components, a `PrimaryButton`, `SecondaryButton` and `DangerButton`.
|
|
347
445
|
|
|
348
|
-
To model this behaviour with Code Connect we can make use of something called variant restrictions. Variant restrictions allow you to provide entirely different code samples for different variants of a single Figma component. The keys and values used should match the name of the variant (or
|
|
446
|
+
To model this behaviour with Code Connect we can make use of something called variant restrictions. Variant restrictions allow you to provide entirely different code samples for different variants of a single Figma component. The keys and values used should match the name of the variant (or property) in Figma and it's options respectively.
|
|
349
447
|
|
|
350
448
|
```tsx
|
|
351
449
|
figma.connect(PrimaryButton, 'https://...', {
|
package/dist/cli.js
CHANGED
|
@@ -26,11 +26,18 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
26
26
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
27
|
const commander = __importStar(require("commander"));
|
|
28
28
|
const connect_1 = require("./commands/connect");
|
|
29
|
+
const updates_1 = require("./common/updates");
|
|
29
30
|
require('dotenv').config();
|
|
30
31
|
async function run() {
|
|
31
32
|
const program = new commander.Command().version(require('./../package.json').version);
|
|
32
33
|
program.enablePositionalOptions();
|
|
33
34
|
(0, connect_1.addConnectCommandToProgram)(program);
|
|
35
|
+
program
|
|
36
|
+
.command('update')
|
|
37
|
+
.description('Updates to the latest version of the Figma CLI')
|
|
38
|
+
.action(() => {
|
|
39
|
+
(0, updates_1.updateCli)();
|
|
40
|
+
});
|
|
34
41
|
program.parse(process.argv);
|
|
35
42
|
if (program.args.length < 1) {
|
|
36
43
|
program.outputHelp();
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,qDAAsC;AACtC,gDAA+D;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,qDAAsC;AACtC,gDAA+D;AAC/D,8CAA4C;AAE5C,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAA;AAE1B,KAAK,UAAU,GAAG;IAChB,MAAM,OAAO,GAAG,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,OAAO,CAAC,CAAA;IACrF,OAAO,CAAC,uBAAuB,EAAE,CAAA;IAEjC,IAAA,oCAA0B,EAAC,OAAO,CAAC,CAAA;IAEnC,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,gDAAgD,CAAC;SAC7D,MAAM,CAAC,GAAG,EAAE;QACX,IAAA,mBAAS,GAAE,CAAA;IACb,CAAC,CAAC,CAAA;IAEJ,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,UAAU,EAAE,CAAA;QACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC;AAED,GAAG,EAAE,CAAA"}
|
|
@@ -1,3 +1,18 @@
|
|
|
1
1
|
import * as commander from 'commander';
|
|
2
|
+
import { ProjectInfo } from '../connect/project';
|
|
3
|
+
import { CodeConnectJSON } from '../common/figma_connect';
|
|
4
|
+
export type BaseCommand = commander.Command & {
|
|
5
|
+
token: string;
|
|
6
|
+
verbose: boolean;
|
|
7
|
+
outFile: string;
|
|
8
|
+
outDir: string;
|
|
9
|
+
config: string;
|
|
10
|
+
dryRun: boolean;
|
|
11
|
+
dir: string;
|
|
12
|
+
jsonFile: string;
|
|
13
|
+
};
|
|
2
14
|
export declare function addConnectCommandToProgram(program: commander.Command): void;
|
|
15
|
+
export declare function getAccessToken(cmd: BaseCommand): string;
|
|
16
|
+
export declare function getDir(cmd: BaseCommand): string;
|
|
17
|
+
export declare function getCodeConnectObjects(dir: string, cmd: BaseCommand, projectInfo: ProjectInfo, silent?: boolean): Promise<CodeConnectJSON[]>;
|
|
3
18
|
//# sourceMappingURL=connect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/commands/connect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/commands/connect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,WAAW,CAAA;AAMtC,OAAO,EAGL,WAAW,EAIZ,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAUzD,MAAM,MAAM,WAAW,GAAG,SAAS,CAAC,OAAO,GAAG;IAC5C,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAgBD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,SAAS,CAAC,OAAO,QAyCpE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,UAU9C;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,WAAW,UAEtC;AA6BD,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,WAAW,EAChB,WAAW,EAAE,WAAW,EACxB,MAAM,UAAQ,GACb,OAAO,CAAC,eAAe,EAAE,CAAC,CAmD5B"}
|
package/dist/commands/connect.js
CHANGED
|
@@ -3,16 +3,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.addConnectCommandToProgram = void 0;
|
|
6
|
+
exports.getCodeConnectObjects = exports.getDir = exports.getAccessToken = exports.addConnectCommandToProgram = void 0;
|
|
7
7
|
const parser_1 = require("../react/parser");
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const upload_1 = require("../connect/upload");
|
|
10
10
|
const validation_1 = require("../connect/validation");
|
|
11
11
|
const create_1 = require("../connect/create");
|
|
12
|
-
const project_1 = require("../
|
|
12
|
+
const project_1 = require("../connect/project");
|
|
13
13
|
const logging_1 = require("../common/logging");
|
|
14
14
|
const convert_1 = require("../storybook/convert");
|
|
15
15
|
const delete_docs_1 = require("../connect/delete_docs");
|
|
16
|
+
const parser_executables_1 = require("../connect/parser_executables");
|
|
17
|
+
const zod_validation_error_1 = require("zod-validation-error");
|
|
18
|
+
const parser_executable_types_1 = require("../connect/parser_executable_types");
|
|
19
|
+
const updates_1 = require("../common/updates");
|
|
16
20
|
function addBaseCommand(command, name, description) {
|
|
17
21
|
return command
|
|
18
22
|
.command(name)
|
|
@@ -21,101 +25,170 @@ function addBaseCommand(command, name, description) {
|
|
|
21
25
|
.option('-r --dir <dir>', 'directory to parse')
|
|
22
26
|
.option('-t --token <token>', 'figma access token')
|
|
23
27
|
.option('-v --verbose', 'enable verbose logging for debugging')
|
|
24
|
-
.option('-o --
|
|
28
|
+
.option('-o --outFile <file>', 'specify a file to output generated Code Connect')
|
|
29
|
+
.option('-o --outDir <dir>', 'specify a directory to output generated Code Connect')
|
|
25
30
|
.option('-c --config <path>', 'path to a figma config file')
|
|
26
31
|
.option('--dry-run', 'tests publishing without actually publishing');
|
|
27
32
|
}
|
|
28
33
|
function addConnectCommandToProgram(program) {
|
|
29
34
|
// Main command, invoked with `figma connect`
|
|
30
|
-
const connectCommand = addBaseCommand(program, 'connect', '
|
|
35
|
+
const connectCommand = addBaseCommand(program, 'connect', 'Figma Code Connect');
|
|
31
36
|
// Sub-commands, invoked with e.g. `figma connect publish`
|
|
32
|
-
addBaseCommand(connectCommand, 'publish', 'Run Code Connect locally to find any files that
|
|
37
|
+
addBaseCommand(connectCommand, 'publish', 'Run Code Connect locally to find any files that have figma connections and publish them to Figma. ' +
|
|
33
38
|
'By default this looks for a config file named "figma.config.json", and uses the `include` and `exclude` fields to determine which files to parse. ' +
|
|
34
39
|
'If no config file is found, this parses the current directory. An optional `--dir` flag can be used to specify a directory to parse.')
|
|
35
40
|
.option('--skip-validation', 'skip validation of Code Connect docs')
|
|
36
|
-
.action(handlePublish);
|
|
37
|
-
addBaseCommand(connectCommand, 'unpublish', 'Run to find any files that
|
|
41
|
+
.action((0, updates_1.withUpdateCheck)(handlePublish));
|
|
42
|
+
addBaseCommand(connectCommand, 'unpublish', 'Run to find any files that have figma connections and unpublish them from Figma. ' +
|
|
38
43
|
'By default this looks for a config file named "figma.config.json", and uses the `include` and `exclude` fields to determine which files to parse. ' +
|
|
39
44
|
'If no config file is found, this parses the current directory. An optional `--dir` flag can be used to specify a directory to parse.')
|
|
40
45
|
.option('--node <link_to_node>', 'specify the node to unpublish. This will unpublish for both React and Storybook.')
|
|
41
|
-
.action(handleUnpublish);
|
|
42
|
-
addBaseCommand(connectCommand, 'parse', 'Run Code Connect locally to find any files that
|
|
46
|
+
.action((0, updates_1.withUpdateCheck)(handleUnpublish));
|
|
47
|
+
addBaseCommand(connectCommand, 'parse', 'Run Code Connect locally to find any files that have figma connections, then converts them to JSON and outputs to stdout.').action((0, updates_1.withUpdateCheck)(handleParse));
|
|
43
48
|
addBaseCommand(connectCommand, 'create', 'Generate a Code Connect file with boilerplate in the current directory for a Figma node URL')
|
|
44
49
|
.argument('<figma-node-url>', 'Figma node URL to create the Code Connect file from')
|
|
45
|
-
.action(handleCreate);
|
|
50
|
+
.action((0, updates_1.withUpdateCheck)(handleCreate));
|
|
46
51
|
}
|
|
47
52
|
exports.addConnectCommandToProgram = addConnectCommandToProgram;
|
|
48
53
|
function getAccessToken(cmd) {
|
|
49
|
-
|
|
54
|
+
const token = cmd.token ?? process.env.FIGMA_ACCESS_TOKEN;
|
|
55
|
+
if (!token) {
|
|
56
|
+
(0, logging_1.exitWithError)(`Couldn't find a Figma access token. Please provide one with \`--token <access_token>\` or set the FIGMA_ACCESS_TOKEN environment variable`);
|
|
57
|
+
}
|
|
58
|
+
return token;
|
|
59
|
+
}
|
|
60
|
+
exports.getAccessToken = getAccessToken;
|
|
61
|
+
function getDir(cmd) {
|
|
62
|
+
return cmd.dir ?? process.cwd();
|
|
50
63
|
}
|
|
64
|
+
exports.getDir = getDir;
|
|
51
65
|
function setupHandler(cmd) {
|
|
52
66
|
if (cmd.verbose) {
|
|
53
67
|
logging_1.logger.setLogLevel(logging_1.LogLevel.Debug);
|
|
54
68
|
}
|
|
55
69
|
}
|
|
56
|
-
|
|
70
|
+
function transformDocFromParser(doc, remoteUrl) {
|
|
71
|
+
let source = doc.source;
|
|
72
|
+
if (source) {
|
|
73
|
+
try {
|
|
74
|
+
const url = new URL(source);
|
|
75
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
76
|
+
throw new Error('Invalid URL scheme');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
source = (0, project_1.getRemoteFileUrl)(source, remoteUrl);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
...doc,
|
|
85
|
+
source,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
async function getCodeConnectObjects(dir, cmd, projectInfo, silent = false) {
|
|
89
|
+
if (cmd.jsonFile) {
|
|
90
|
+
try {
|
|
91
|
+
return JSON.parse(fs_1.default.readFileSync(cmd.jsonFile, 'utf8'));
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
logging_1.logger.error('Failed to parse JSON file:', e);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (projectInfo.config.parser === 'react') {
|
|
98
|
+
return getReactCodeConnectObjects(dir, projectInfo, cmd, silent);
|
|
99
|
+
}
|
|
100
|
+
const payload = {
|
|
101
|
+
mode: 'PARSE',
|
|
102
|
+
paths: projectInfo.files,
|
|
103
|
+
config: projectInfo.config,
|
|
104
|
+
};
|
|
105
|
+
try {
|
|
106
|
+
const stdout = await (0, parser_executables_1.callParser)(
|
|
107
|
+
// We use `as` because the React parser makes the types difficult
|
|
108
|
+
// TODO remove once React is an executable parser
|
|
109
|
+
projectInfo.config, payload, projectInfo.absPath);
|
|
110
|
+
const parsed = parser_executable_types_1.ParseResponsePayload.parse(stdout);
|
|
111
|
+
const { hasErrors } = (0, parser_executables_1.handleMessages)(parsed.messages);
|
|
112
|
+
if (hasErrors) {
|
|
113
|
+
(0, logging_1.exitWithError)('Errors encountered calling parser, exiting');
|
|
114
|
+
}
|
|
115
|
+
return parsed.docs.map((doc) => ({
|
|
116
|
+
...transformDocFromParser(doc, projectInfo.remoteUrl),
|
|
117
|
+
metadata: {
|
|
118
|
+
cliVersion: require('../../package.json').version,
|
|
119
|
+
},
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
// zod-validation-error formats the error message into a readable format
|
|
124
|
+
(0, logging_1.exitWithError)(`Error returned from parser: ${(0, zod_validation_error_1.fromError)(e)}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
exports.getCodeConnectObjects = getCodeConnectObjects;
|
|
128
|
+
// The React/Storybook parser is handled as a special case for now. Other
|
|
129
|
+
// parsers uses the separate parser executable model and React will be
|
|
130
|
+
// transitioned to that at some point, but for now the old code path is used
|
|
131
|
+
async function getReactCodeConnectObjects(dir, projectInfo, cmd, silent = false) {
|
|
57
132
|
const codeConnectObjects = [];
|
|
58
|
-
const
|
|
59
|
-
const
|
|
133
|
+
const reactProjectInfo = (0, project_1.getReactProjectInfo)(projectInfo);
|
|
134
|
+
const { files, remoteUrl, config, tsProgram } = reactProjectInfo;
|
|
60
135
|
for (const file of files.filter((f) => (0, parser_1.isFigmaConnectFile)(tsProgram, f))) {
|
|
61
136
|
try {
|
|
62
|
-
const docs = await (0, parser_1.parse)(tsProgram, file,
|
|
63
|
-
for (const doc of docs) {
|
|
64
|
-
figmaNodeToFile.set(doc.figmaNode, file);
|
|
65
|
-
}
|
|
137
|
+
const docs = await (0, parser_1.parse)(tsProgram, file, config, reactProjectInfo.absPath, remoteUrl, cmd.verbose);
|
|
66
138
|
codeConnectObjects.push(...docs);
|
|
67
|
-
|
|
139
|
+
if (!silent || cmd.verbose) {
|
|
140
|
+
logging_1.logger.info((0, logging_1.success)(file));
|
|
141
|
+
}
|
|
68
142
|
}
|
|
69
143
|
catch (e) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
|
|
144
|
+
if (!silent || cmd.verbose) {
|
|
145
|
+
logging_1.logger.error(`❌ ${file}`);
|
|
146
|
+
if (e instanceof parser_1.ParserError) {
|
|
147
|
+
if (cmd.verbose) {
|
|
148
|
+
console.trace(e);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
logging_1.logger.error(e.toString());
|
|
152
|
+
}
|
|
74
153
|
}
|
|
75
154
|
else {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
logging_1.logger.error(new parser_1.InternalError(String(e)).toString());
|
|
155
|
+
if (cmd.verbose) {
|
|
156
|
+
console.trace(e);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
logging_1.logger.error(new parser_1.InternalError(String(e)).toString());
|
|
160
|
+
}
|
|
85
161
|
}
|
|
86
162
|
}
|
|
87
163
|
}
|
|
88
164
|
}
|
|
89
|
-
|
|
165
|
+
const storybookCodeConnectObjects = await (0, convert_1.convertStorybookFiles)({
|
|
166
|
+
projectInfo: (0, project_1.getReactProjectInfo)(projectInfo),
|
|
167
|
+
});
|
|
168
|
+
const allCodeConnectObjects = codeConnectObjects.concat(storybookCodeConnectObjects);
|
|
169
|
+
return allCodeConnectObjects;
|
|
90
170
|
}
|
|
91
171
|
async function handlePublish(cmd) {
|
|
92
172
|
setupHandler(cmd);
|
|
93
|
-
let dir = cmd
|
|
94
|
-
const projectInfo = (0, project_1.getProjectInfo)(dir, cmd.config);
|
|
95
|
-
if (cmd.dryRun) {
|
|
96
|
-
logging_1.logger.info(`Files that would be published:`);
|
|
97
|
-
}
|
|
173
|
+
let dir = getDir(cmd);
|
|
174
|
+
const projectInfo = await (0, project_1.getProjectInfo)(dir, cmd.config);
|
|
98
175
|
const codeConnectObjects = await getCodeConnectObjects(dir, cmd, projectInfo);
|
|
99
|
-
|
|
100
|
-
projectInfo,
|
|
101
|
-
});
|
|
102
|
-
const allCodeConnectFiles = codeConnectObjects.concat(storybookCodeConnectObjects);
|
|
103
|
-
if (allCodeConnectFiles.length === 0) {
|
|
176
|
+
if (codeConnectObjects.length === 0) {
|
|
104
177
|
logging_1.logger.warn(`No Code Connect files found in ${dir} - Make sure you have configured \`include\` and \`exclude\` in your figma.config.json file correctly, or that you are running in a directory that contains Code Connect files.`);
|
|
105
178
|
process.exit(0);
|
|
106
179
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
logging_1.logger.
|
|
110
|
-
process.exit(1);
|
|
180
|
+
if (cmd.dryRun) {
|
|
181
|
+
logging_1.logger.info(`Files that would be published:`);
|
|
182
|
+
logging_1.logger.info(codeConnectObjects.map((o) => `- ${o.component} (${o.figmaNode})`).join('\n'));
|
|
111
183
|
}
|
|
184
|
+
const accessToken = getAccessToken(cmd);
|
|
112
185
|
if (cmd.skipValidation) {
|
|
113
186
|
logging_1.logger.info('Validation skipped');
|
|
114
187
|
}
|
|
115
188
|
else {
|
|
116
189
|
logging_1.logger.info('Validating Code Connect files...');
|
|
117
190
|
var start = new Date().getTime();
|
|
118
|
-
const valid = await (0, validation_1.validateDocs)(accessToken,
|
|
191
|
+
const valid = await (0, validation_1.validateDocs)(cmd, accessToken, codeConnectObjects);
|
|
119
192
|
if (!valid) {
|
|
120
193
|
process.exit(1);
|
|
121
194
|
}
|
|
@@ -129,11 +202,11 @@ async function handlePublish(cmd) {
|
|
|
129
202
|
logging_1.logger.info(`Dry run complete`);
|
|
130
203
|
process.exit(0);
|
|
131
204
|
}
|
|
132
|
-
(0, upload_1.upload)({ accessToken, docs:
|
|
205
|
+
(0, upload_1.upload)({ accessToken, docs: codeConnectObjects });
|
|
133
206
|
}
|
|
134
207
|
async function handleUnpublish(cmd) {
|
|
135
208
|
setupHandler(cmd);
|
|
136
|
-
let dir = cmd
|
|
209
|
+
let dir = getDir(cmd);
|
|
137
210
|
if (cmd.dryRun) {
|
|
138
211
|
logging_1.logger.info(`Files that would be unpublished:`);
|
|
139
212
|
}
|
|
@@ -145,13 +218,9 @@ async function handleUnpublish(cmd) {
|
|
|
145
218
|
];
|
|
146
219
|
}
|
|
147
220
|
else {
|
|
148
|
-
const projectInfo = (0, project_1.getProjectInfo)(dir, cmd.config);
|
|
221
|
+
const projectInfo = await (0, project_1.getProjectInfo)(dir, cmd.config);
|
|
149
222
|
const codeConnectObjects = await getCodeConnectObjects(dir, cmd, projectInfo);
|
|
150
|
-
|
|
151
|
-
projectInfo,
|
|
152
|
-
});
|
|
153
|
-
const allCodeConnectFiles = codeConnectObjects.concat(storybookCodeConnectObjects);
|
|
154
|
-
nodesToDeleteRelevantInfo = allCodeConnectFiles.map((doc) => ({
|
|
223
|
+
nodesToDeleteRelevantInfo = codeConnectObjects.map((doc) => ({
|
|
155
224
|
figmaNode: doc.figmaNode,
|
|
156
225
|
label: doc.label,
|
|
157
226
|
}));
|
|
@@ -168,47 +237,39 @@ async function handleUnpublish(cmd) {
|
|
|
168
237
|
}
|
|
169
238
|
async function handleParse(cmd) {
|
|
170
239
|
setupHandler(cmd);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
logging_1.logger.setLogLevel(logging_1.LogLevel.Error);
|
|
174
|
-
}
|
|
175
|
-
let dir = cmd.dir ?? process.cwd();
|
|
176
|
-
const projectInfo = (0, project_1.getProjectInfo)(dir, cmd.config);
|
|
240
|
+
const dir = cmd.dir ?? process.cwd();
|
|
241
|
+
const projectInfo = await (0, project_1.getProjectInfo)(dir, cmd.config);
|
|
177
242
|
const codeConnectObjects = await getCodeConnectObjects(dir, cmd, projectInfo);
|
|
178
|
-
const storybookCodeConnectObjects = await (0, convert_1.convertStorybookFiles)({
|
|
179
|
-
projectInfo,
|
|
180
|
-
});
|
|
181
|
-
const allCodeConnectFiles = codeConnectObjects.concat(storybookCodeConnectObjects);
|
|
182
243
|
if (cmd.dryRun) {
|
|
183
244
|
logging_1.logger.info(`Dry run complete`);
|
|
184
245
|
process.exit(0);
|
|
185
246
|
}
|
|
186
|
-
if (cmd.
|
|
187
|
-
fs_1.default.writeFileSync(cmd.
|
|
188
|
-
logging_1.logger.info(`Wrote Code Connect JSON to ${(0, logging_1.highlight)(cmd.
|
|
247
|
+
if (cmd.outFile) {
|
|
248
|
+
fs_1.default.writeFileSync(cmd.outFile, JSON.stringify(codeConnectObjects, null, 2));
|
|
249
|
+
logging_1.logger.info(`Wrote Code Connect JSON to ${(0, logging_1.highlight)(cmd.outFile)}`);
|
|
189
250
|
}
|
|
190
251
|
else {
|
|
191
252
|
// don't format the output, so it can be piped to other commands
|
|
192
|
-
console.log(JSON.stringify(
|
|
253
|
+
console.log(JSON.stringify(codeConnectObjects, undefined, 2));
|
|
193
254
|
}
|
|
194
255
|
}
|
|
195
|
-
function handleCreate(nodeUrl, cmd) {
|
|
256
|
+
async function handleCreate(nodeUrl, cmd) {
|
|
196
257
|
setupHandler(cmd);
|
|
258
|
+
const dir = cmd.dir ?? process.cwd();
|
|
259
|
+
const projectInfo = await (0, project_1.getProjectInfo)(dir, cmd.config);
|
|
197
260
|
if (cmd.dryRun) {
|
|
198
261
|
process.exit(0);
|
|
199
262
|
}
|
|
200
263
|
const accessToken = getAccessToken(cmd);
|
|
201
|
-
if (!accessToken) {
|
|
202
|
-
logging_1.logger.error(`Couldn't find a Figma access token. Please provide one with \`--token <access_token>\` or set the FIGMA_ACCESS_TOKEN environment variable`);
|
|
203
|
-
process.exit(1);
|
|
204
|
-
}
|
|
205
264
|
return (0, create_1.createCodeConnectFromUrl)({
|
|
206
265
|
accessToken,
|
|
207
266
|
// We remove \s to allow users to paste URLs inside quotes - the terminal
|
|
208
267
|
// paste will add backslashes, which the quotes preserve, but expected user
|
|
209
268
|
// behaviour would be to strip the quotes
|
|
210
269
|
figmaNodeUrl: nodeUrl.replace(/\\/g, ''),
|
|
211
|
-
outFile: cmd.
|
|
270
|
+
outFile: cmd.outFile,
|
|
271
|
+
outDir: cmd.outDir,
|
|
272
|
+
projectInfo,
|
|
212
273
|
});
|
|
213
274
|
}
|
|
214
275
|
//# sourceMappingURL=connect.js.map
|