@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.
Files changed (106) hide show
  1. package/README.md +123 -25
  2. package/dist/cli.js +7 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/connect.d.ts +15 -0
  5. package/dist/commands/connect.d.ts.map +1 -1
  6. package/dist/commands/connect.js +137 -76
  7. package/dist/commands/connect.js.map +1 -1
  8. package/dist/common/__test__/project.test.d.ts +2 -0
  9. package/dist/common/__test__/project.test.d.ts.map +1 -0
  10. package/dist/common/__test__/project.test.js +14 -0
  11. package/dist/common/__test__/project.test.js.map +1 -0
  12. package/dist/common/api.d.ts +56 -2
  13. package/dist/common/api.d.ts.map +1 -1
  14. package/dist/common/external.d.ts +6 -3
  15. package/dist/common/external.d.ts.map +1 -1
  16. package/dist/common/external.js +13 -1
  17. package/dist/common/external.js.map +1 -1
  18. package/dist/common/figma_connect.d.ts +10 -8
  19. package/dist/common/figma_connect.d.ts.map +1 -1
  20. package/dist/common/intrinsics.d.ts +38 -6
  21. package/dist/common/intrinsics.d.ts.map +1 -1
  22. package/dist/common/intrinsics.js +160 -26
  23. package/dist/common/intrinsics.js.map +1 -1
  24. package/dist/common/logging.d.ts +9 -0
  25. package/dist/common/logging.d.ts.map +1 -1
  26. package/dist/common/logging.js +21 -1
  27. package/dist/common/logging.js.map +1 -1
  28. package/dist/common/updates.d.ts +4 -0
  29. package/dist/common/updates.d.ts.map +1 -0
  30. package/dist/common/updates.js +87 -0
  31. package/dist/common/updates.js.map +1 -0
  32. package/dist/connect/create.d.ts +5 -1
  33. package/dist/connect/create.d.ts.map +1 -1
  34. package/dist/connect/create.js +55 -69
  35. package/dist/connect/create.js.map +1 -1
  36. package/dist/connect/delete_docs.d.ts.map +1 -1
  37. package/dist/connect/delete_docs.js +1 -4
  38. package/dist/connect/delete_docs.js.map +1 -1
  39. package/dist/connect/figma_rest_api.d.ts +5 -0
  40. package/dist/connect/figma_rest_api.d.ts.map +1 -1
  41. package/dist/connect/figma_rest_api.js +10 -1
  42. package/dist/connect/figma_rest_api.js.map +1 -1
  43. package/dist/connect/helpers.d.ts +1 -1
  44. package/dist/connect/helpers.d.ts.map +1 -1
  45. package/dist/connect/helpers.js +6 -5
  46. package/dist/connect/helpers.js.map +1 -1
  47. package/dist/connect/parser_executable_types.d.ts +297 -0
  48. package/dist/connect/parser_executable_types.d.ts.map +1 -0
  49. package/dist/connect/parser_executable_types.js +108 -0
  50. package/dist/connect/parser_executable_types.js.map +1 -0
  51. package/dist/connect/parser_executables.d.ts +8 -0
  52. package/dist/connect/parser_executables.d.ts.map +1 -0
  53. package/dist/connect/parser_executables.js +132 -0
  54. package/dist/connect/parser_executables.js.map +1 -0
  55. package/dist/connect/project.d.ts +137 -0
  56. package/dist/connect/project.d.ts.map +1 -0
  57. package/dist/connect/project.js +411 -0
  58. package/dist/connect/project.js.map +1 -0
  59. package/dist/connect/upload.d.ts.map +1 -1
  60. package/dist/connect/upload.js +1 -4
  61. package/dist/connect/upload.js.map +1 -1
  62. package/dist/connect/validation.d.ts +6 -1
  63. package/dist/connect/validation.d.ts.map +1 -1
  64. package/dist/connect/validation.js +23 -18
  65. package/dist/connect/validation.js.map +1 -1
  66. package/dist/connect/wizard/helpers.d.ts +7 -0
  67. package/dist/connect/wizard/helpers.d.ts.map +1 -0
  68. package/dist/connect/wizard/helpers.js +55 -0
  69. package/dist/connect/wizard/helpers.js.map +1 -0
  70. package/dist/connect/wizard/run_wizard.d.ts +3 -0
  71. package/dist/connect/wizard/run_wizard.d.ts.map +1 -0
  72. package/dist/connect/wizard/run_wizard.js +480 -0
  73. package/dist/connect/wizard/run_wizard.js.map +1 -0
  74. package/dist/parser_scripts/get_file_if_exists.d.ts +2 -0
  75. package/dist/parser_scripts/get_file_if_exists.d.ts.map +1 -0
  76. package/dist/parser_scripts/get_file_if_exists.js +10 -0
  77. package/dist/parser_scripts/get_file_if_exists.js.map +1 -0
  78. package/dist/parser_scripts/get_gradlew_path.d.ts +3 -0
  79. package/dist/parser_scripts/get_gradlew_path.d.ts.map +1 -0
  80. package/dist/parser_scripts/get_gradlew_path.js +24 -0
  81. package/dist/parser_scripts/get_gradlew_path.js.map +1 -0
  82. package/dist/parser_scripts/get_swift_parser_dir.d.ts +2 -0
  83. package/dist/parser_scripts/get_swift_parser_dir.d.ts.map +1 -0
  84. package/dist/parser_scripts/get_swift_parser_dir.js +93 -0
  85. package/dist/parser_scripts/get_swift_parser_dir.js.map +1 -0
  86. package/dist/react/create.d.ts +4 -0
  87. package/dist/react/create.d.ts.map +1 -0
  88. package/dist/react/create.js +132 -0
  89. package/dist/react/create.js.map +1 -0
  90. package/dist/react/parser.d.ts +6 -15
  91. package/dist/react/parser.d.ts.map +1 -1
  92. package/dist/react/parser.js +55 -62
  93. package/dist/react/parser.js.map +1 -1
  94. package/dist/react/parser_template_helpers.d.ts +21 -0
  95. package/dist/react/parser_template_helpers.d.ts.map +1 -1
  96. package/dist/react/parser_template_helpers.js +107 -43
  97. package/dist/react/parser_template_helpers.js.map +1 -1
  98. package/dist/storybook/convert.d.ts +2 -2
  99. package/dist/storybook/convert.d.ts.map +1 -1
  100. package/dist/storybook/convert.js +6 -4
  101. package/dist/storybook/convert.js.map +1 -1
  102. package/package.json +36 -11
  103. package/dist/common/project.d.ts +0 -94
  104. package/dist/common/project.d.ts.map +0 -1
  105. package/dist/common/project.js +0 -198
  106. 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](../../README.md).
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`. Note that depending on what terminal software you're using, you might need to wrap the URL in quotes.
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
- "react": {
112
- "importPaths": {
113
- "src/components/*": "@ui/components"
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
- "react": {
144
- "importPaths": {
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, text, type }) => {
182
+ example: ({ disabled, label, type }) => {
183
183
  return (
184
184
  <Button disabled={disabled} type={type}>
185
- {text}
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 layer_ as it's parameter, rather than a Figma prop name. Note that the nested instance also must be connected separately.
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 [roperty) in Figma and it's options respectively.
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;AAE/D,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,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
+ {"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;AAkCtC,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,SAAS,CAAC,OAAO,QA6CpE"}
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"}
@@ -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("../common/project");
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 --outfile <file>', 'output to JSON file')
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', 'Start the Code Connect Wizard (not implemented yet)');
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 include calls to `figma.connect()` and publishes those to Figma. ' +
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 include calls to `figma.connect()` and unpublish them from Figma. ' +
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 include calls to `figma.connect()`, then converts to JSON and outputs to stdout.').action(handleParse);
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
- return cmd.token ?? process.env.FIGMA_ACCESS_TOKEN;
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
- async function getCodeConnectObjects(dir, cmd, projectInfo) {
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 { files, remoteUrl, config, tsProgram } = projectInfo;
59
- const figmaNodeToFile = new Map();
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, remoteUrl, config, cmd.verbose);
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
- logging_1.logger.info((0, logging_1.success)(file));
139
+ if (!silent || cmd.verbose) {
140
+ logging_1.logger.info((0, logging_1.success)(file));
141
+ }
68
142
  }
69
143
  catch (e) {
70
- logging_1.logger.error(`❌ ${file}`);
71
- if (e instanceof parser_1.ParserError) {
72
- if (cmd.verbose) {
73
- console.trace(e);
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
- logging_1.logger.error(e.toString());
77
- }
78
- }
79
- else {
80
- if (cmd.verbose) {
81
- console.trace(e);
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
- return codeConnectObjects;
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.dir ?? process.cwd();
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
- const storybookCodeConnectObjects = await (0, convert_1.convertStorybookFiles)({
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
- const accessToken = getAccessToken(cmd);
108
- if (!accessToken) {
109
- 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`);
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, allCodeConnectFiles);
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: allCodeConnectFiles });
205
+ (0, upload_1.upload)({ accessToken, docs: codeConnectObjects });
133
206
  }
134
207
  async function handleUnpublish(cmd) {
135
208
  setupHandler(cmd);
136
- let dir = cmd.dir ?? process.cwd();
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
- const storybookCodeConnectObjects = await (0, convert_1.convertStorybookFiles)({
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
- // if we're not doing a dry run, we don't want to output logs
172
- if (!cmd.dryRun) {
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.outfile) {
187
- fs_1.default.writeFileSync(cmd.outfile, JSON.stringify(codeConnectObjects, null, 2));
188
- logging_1.logger.info(`Wrote Code Connect JSON to ${(0, logging_1.highlight)(cmd.outfile)}`);
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(allCodeConnectFiles, undefined, 2));
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.outfile,
270
+ outFile: cmd.outFile,
271
+ outDir: cmd.outDir,
272
+ projectInfo,
212
273
  });
213
274
  }
214
275
  //# sourceMappingURL=connect.js.map