@interactivethings/scripts 0.0.9 → 2.0.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.
Files changed (49) hide show
  1. package/README.md +208 -12
  2. package/codemods/__testfixtures__/sx-to-use-styles.input.js +50 -0
  3. package/codemods/__testfixtures__/sx-to-use-styles.output.js +59 -0
  4. package/codemods/__tests__/defineTest.ts +9 -0
  5. package/codemods/__tests__/sx-to-use-styles.test.js +3 -0
  6. package/codemods/sx-to-use-styles.js +162 -0
  7. package/dist/__tests__/figma-cli.test.js +189 -0
  8. package/dist/__tests__/tokens-studio-cli.test.js +197 -0
  9. package/dist/__tests__/vercel-cli.test.js +86 -0
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +103 -0
  12. package/dist/config.d.ts +78 -0
  13. package/dist/config.js +167 -0
  14. package/dist/figma/api.d.ts +71 -0
  15. package/dist/figma/api.js +175 -0
  16. package/dist/figma/cli.d.ts +20 -0
  17. package/dist/figma/cli.js +163 -0
  18. package/dist/figma/cli.test.js +197 -0
  19. package/dist/figma/images.js +63 -0
  20. package/dist/figma/optimizeImage.js +53 -0
  21. package/dist/figma/utils.d.ts +10 -0
  22. package/dist/figma/utils.js +26 -0
  23. package/dist/index.d.ts +8 -0
  24. package/dist/index.js +53 -0
  25. package/dist/init/cli.d.ts +3 -0
  26. package/dist/init/cli.js +320 -0
  27. package/dist/mui-tokens-studio.js +0 -0
  28. package/dist/plugins/figma/index.js +653 -0
  29. package/dist/plugins/tokens-studio/utils.js +155 -0
  30. package/dist/tokens-studio/__tests__/fixtures/invalid-transformer.js +12 -0
  31. package/dist/tokens-studio/__tests__/fixtures/test-transformer.js +18 -0
  32. package/dist/tokens-studio/cli.d.ts +8 -0
  33. package/dist/tokens-studio/cli.js +153 -0
  34. package/dist/tokens-studio/cli.test.js +160 -0
  35. package/dist/tokens-studio/index.d.ts +14 -0
  36. package/dist/tokens-studio/index.js +56 -0
  37. package/dist/tokens-studio/mui.js +212 -0
  38. package/dist/tokens-studio/require.js +17 -0
  39. package/dist/tokens-studio/tailwind.js +211 -0
  40. package/dist/tokens-studio/types.js +2 -0
  41. package/dist/tokens-studio/utils.d.ts +49 -0
  42. package/dist/tokens-studio/utils.js +156 -0
  43. package/dist/types.d.ts +1 -0
  44. package/dist/vercel/cli.d.ts +4 -0
  45. package/dist/vercel/cli.js +70 -0
  46. package/dist/vercel/cli.test.js +86 -0
  47. package/dist/vercel/deployments.js +41 -0
  48. package/dist/vercel/waitForDeploymentReady.js +43 -0
  49. package/package.json +46 -6
package/README.md CHANGED
@@ -1,28 +1,224 @@
1
1
  # ixt-scripts
2
2
 
3
- Scripts shared across projects
3
+ A collection of useful development tools by Interactive Things for managing design tokens, Figma assets, and deployment workflows.
4
4
 
5
- ## Development
5
+ ## Installation
6
6
 
7
7
  ```bash
8
- # Edit code
9
- yarn publish --access=public
8
+ yarn add @interactivethings/scripts
9
+ # or
10
+ npm install @interactivethings/scripts
10
11
  ```
11
12
 
12
- ## Installation
13
+ ## Quick Start
14
+
15
+ 1. Create a TypeScript configuration file in your project root:
16
+
17
+ ```typescript
18
+ // ixt.config.ts
19
+ import { defineConfig } from "@interactivethings/scripts";
20
+
21
+ export default defineConfig({
22
+ figma: {
23
+ assets: [
24
+ {
25
+ name: "icons",
26
+ url: "https://www.figma.com/design/YOUR_FILE_ID/Design-System?node-id=123-456",
27
+ output: "src/assets/icons",
28
+ },
29
+ {
30
+ name: "illustrations",
31
+ url: "https://www.figma.com/design/YOUR_FILE_ID/Design-System?node-id=789-012",
32
+ output: "src/assets/illustrations",
33
+ },
34
+ ],
35
+ },
36
+ tokensStudio: {
37
+ input: "./tokens",
38
+ output: "src/theme/tokens.json",
39
+ handler: "./transforms/mui-transform.ts",
40
+ },
41
+ });
42
+ ```
43
+
44
+ 2. Create a `.env.local` file with your API tokens:
13
45
 
14
46
  ```bash
15
- yarn add @interactivethings/scripts
47
+ FIGMA_TOKEN=fig_***
48
+ ```
49
+
50
+ ℹ️ Environment variables are loaded using @next/env the same way as Next.js.
51
+
52
+ 3. Use the CLI commands:
53
+
54
+ ```bash
55
+ # Download Figma assets
56
+ ixt figma download
57
+
58
+ # Transform design tokens
59
+ ixt tokens-studio transform
16
60
  ```
17
61
 
18
- ## Usage
62
+ ## Configuration
19
63
 
20
- In the following the steps, bun is used but you can also use ts-node as
21
- a Typescript runtime.
64
+ ixt-scripts uses a unified configuration system with a single `ixt.config.ts` file that provides full TypeScript support and type safety.
22
65
 
23
- ### Tokens studio JSON to MUI JSON
66
+ ### Supported Config Files
67
+
68
+ - `ixt.config.ts` (TypeScript configuration with full type safety)
69
+
70
+ For detailed configuration options and schema reference, see [CONFIG.md](./CONFIG.md).
71
+
72
+ ## Creating Custom Transformers
73
+
74
+ You can create custom transformers for the tokens-studio command:
75
+
76
+ ```typescript
77
+ // transforms/my-transform.ts
78
+ export function transform(input: { metadata: any; tokenData: any }) {
79
+ // Transform the tokens to your desired format
80
+ return {
81
+ colors: transformColors(input.tokenData.colors),
82
+ typography: transformTypography(input.tokenData.typography),
83
+ // ... other transformations
84
+ };
85
+ }
86
+ ```
87
+
88
+ Then reference it in your config:
89
+
90
+ ```typescript
91
+ export default defineConfig({
92
+ tokensStudio: {
93
+ handler: "./transforms/my-transform.ts",
94
+ },
95
+ });
96
+ ```
97
+
98
+ ## Available Commands
99
+
100
+ ### Figma Commands
101
+
102
+ Download assets from Figma:
24
103
 
25
104
  ```bash
26
- # Add this command to package.json scripts
27
- bun ./node_modules/@interactivethings/scripts/mui-tokens-studio.ts src/styles/tokens.studio.json src/styles/tokens.mui.json
105
+ # Download all configured assets
106
+ ixt figma download
107
+
108
+ # Download specific asset collection
109
+ ixt figma download icons
110
+
111
+ # Get node information
112
+ ixt figma get "https://www.figma.com/design/..."
28
113
  ```
114
+
115
+ **Features:**
116
+ - Download SVG icons and optimize them
117
+ - Download assets exported from Figma
118
+ - Automatic asset optimization
119
+
120
+ ⚠️ Assets must be exported in Figma to be visible to the tool.
121
+
122
+ ### Tokens Studio Commands
123
+
124
+ Transform design tokens exported from Tokens Studio:
125
+
126
+ ```bash
127
+ # Transform using config file settings
128
+ ixt tokens-studio transform
129
+
130
+ # Override config settings
131
+ ixt tokens-studio transform --handler ./custom-transform.ts --output theme.json
132
+ ```
133
+
134
+ **Concept:**
135
+ UI frameworks like MUI and Tailwind require variables in specific formats, while designers work with Figma Tokens. The `tokens-studio` tool bridges this gap by transforming design tokens into framework-compatible variables.
136
+
137
+ **Workflow:**
138
+ 1. Designers create tokens in Figma
139
+ 2. Designers export tokens using Tokens Studio plugin
140
+ 3. Developers store token files in the code repository
141
+ 4. Developers run `ixt tokens-studio transform` to convert tokens for their framework
142
+
143
+ ### Vercel Commands
144
+
145
+ Utilities for Vercel deployments:
146
+
147
+ ```bash
148
+ # Wait for deployment to complete
149
+ ixt vercel wait-deployment COMMIT_SHA --team TEAM_ID --project PROJECT_ID
150
+ ```
151
+
152
+ ## Framework Integration Examples
153
+
154
+ After transforming your design tokens, you can use them directly in your framework configuration:
155
+
156
+ ### Tailwind CSS v3
157
+
158
+ ```typescript
159
+ // tailwind.config.ts
160
+ import theme from 'src/theme/tokens.json'
161
+
162
+ export default {
163
+ darkMode: ["class"],
164
+ content: [
165
+ "./src/**/*.{js,ts,jsx,tsx,mdx}",
166
+ process.env.TAILWIND_STORIES === "false"
167
+ ? "!./src/**/*.stories.{js,ts,jsx,tsx,mdx}"
168
+ : undefined,
169
+ ].filter(Boolean),
170
+ theme: {
171
+ extend: {
172
+ colors: {
173
+ ...theme.colors
174
+ }
175
+ }
176
+ }
177
+ }
178
+ ```
179
+
180
+ ### Material-UI (MUI)
181
+
182
+ ```tsx
183
+ // theme.tsx
184
+ import { createTheme } from '@mui/material/styles';
185
+ import tokens from 'src/theme/tokens.json';
186
+
187
+ export const theme = createTheme({
188
+ typography: {
189
+ fontFamily: `${tokens.typography.body1.desktop.fontFamily}, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif`,
190
+ h1: omit(tokens.typography.h1.desktop, ["fontFamily"]),
191
+ h2: omit(tokens.typography.h2.desktop, ["fontFamily"]),
192
+ h3: omit(tokens.typography.h3.desktop, ["fontFamily"]),
193
+ body1: omit(tokens.typography.body1.desktop, ["fontFamily"]),
194
+ body2: omit(tokens.typography.body2.desktop, ["fontFamily"]),
195
+ },
196
+ palette: {
197
+ primary: {
198
+ main: tokens.palette.primary.main,
199
+ light: tokens.palette.primary.light,
200
+ dark: tokens.palette.primary.dark,
201
+ },
202
+ secondary: {
203
+ main: tokens.palette.secondary.main,
204
+ },
205
+ error: {
206
+ main: tokens.palette.functional.error,
207
+ },
208
+ warning: {
209
+ main: tokens.palette.functional.warning,
210
+ },
211
+ success: {
212
+ main: tokens.palette.functional.success,
213
+ },
214
+ },
215
+ });
216
+ ```
217
+
218
+ ## Publishing
219
+
220
+ Publishing is done automatically through semantic-release via a GitHub action job, from the branch main. Commit prefixes will trigger different types of versions and we use the default commit analyser.
221
+
222
+ - 'fix:' commits will trigger a patch version
223
+ - 'feat:' commits will trigger a minor version
224
+ - 'BREAKING CHANGE:' in the body of a commit will trigger a major version
@@ -0,0 +1,50 @@
1
+ import React from "react";
2
+ import { Button, Typography, Paper } from "@mui/material";
3
+
4
+ // Component using MUI and sx props
5
+ const ExampleComponent = () => {
6
+ return (
7
+ <Paper
8
+ sx={{
9
+ py: 2,
10
+ display: "flex",
11
+ flexDirection: "column",
12
+ alignItems: "center",
13
+ justifyContent: "center",
14
+ minHeight: "200px",
15
+ bgcolor: "#f0f0f0",
16
+ }}
17
+ >
18
+ <Typography variant="h4" sx={{ marginBottom: 2 }}>
19
+ Welcome to MUI with sx props!
20
+ </Typography>
21
+ {[1, 2, 3].map((_x) => {
22
+ return (
23
+ <Box
24
+ sx={{
25
+ mx: 3,
26
+ display: "flex",
27
+ flexDirection: "column",
28
+ alignItems: "center",
29
+ justifyContent: "center",
30
+ minHeight: "200px",
31
+ backgroundColor: "#f0f0f0",
32
+ }}
33
+ >
34
+ List item {i}
35
+ </Box>
36
+ );
37
+ })}
38
+ <Button
39
+ variant="contained"
40
+ color="primary"
41
+ sx={{ marginTop: 2 }}
42
+ onClick={() => alert("Button clicked!")}
43
+ >
44
+ Click me
45
+ </Button>
46
+ </Paper>
47
+ );
48
+ };
49
+
50
+ export default ExampleComponent;
@@ -0,0 +1,59 @@
1
+
2
+ import React from "react";
3
+ import { Button, Typography, Paper } from "@mui/material";
4
+
5
+ const useStyles = makeStyles()(_theme => ({
6
+ root0: {
7
+ paddingTop: 2,
8
+ paddingBottom: 2,
9
+ display: "flex",
10
+ flexDirection: "column",
11
+ alignItems: "center",
12
+ justifyContent: "center",
13
+ minHeight: "200px",
14
+ backgroundColor: "#f0f0f0"
15
+ },
16
+
17
+ root1: {
18
+ marginLeft: 3,
19
+ marginRight: 3,
20
+ display: "flex",
21
+ flexDirection: "column",
22
+ alignItems: "center",
23
+ justifyContent: "center",
24
+ minHeight: "200px",
25
+ backgroundColor: "#f0f0f0"
26
+ }
27
+ }));
28
+
29
+ // Component using MUI and sx props
30
+ const ExampleComponent = () => {
31
+ const { classes } = useStyles()
32
+ return (
33
+ (<Paper
34
+ className={classes.root0}
35
+ >
36
+ <Typography variant="h4" sx={{ marginBottom: 2 }}>
37
+ Welcome to MUI with sx props!
38
+ </Typography>
39
+ {[1, 2, 3].map((_x) => {
40
+ return (
41
+ (<Box
42
+ className={classes.root1}
43
+ >List item{i}
44
+ </Box>)
45
+ );
46
+ })}
47
+ <Button
48
+ variant="contained"
49
+ color="primary"
50
+ sx={{ marginTop: 2 }}
51
+ onClick={() => alert("Button clicked!")}
52
+ >
53
+ Click me
54
+ </Button>
55
+ </Paper>)
56
+ );
57
+ };
58
+
59
+ export default ExampleComponent;
@@ -0,0 +1,9 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { defineTest } from "jscodeshift/dist/testUtils";
3
+
4
+ // Necessary for defineTest
5
+ global.it = test;
6
+ global.describe = describe;
7
+ global.expect = expect;
8
+
9
+ export default defineTest;
@@ -0,0 +1,3 @@
1
+ import defineTest from "./defineTest";
2
+
3
+ defineTest(__dirname, "sx-to-use-styles");
@@ -0,0 +1,162 @@
1
+ function findHighestFunctionBlock(path) {
2
+ let current = path;
3
+ let highest = null;
4
+
5
+ while (current && current.node && current.parentPath?.node) {
6
+ const grandParentNode = current.parentPath.node;
7
+ const grandParentType = grandParentNode.type;
8
+ const currentType = current.node.type;
9
+
10
+ if (
11
+ currentType === "BlockStatement" &&
12
+ (grandParentType === "ArrowFunctionExpression" ||
13
+ grandParentType === "FunctionDeclaration" ||
14
+ grandParentType === "ArrowFunctionExpression")
15
+ ) {
16
+ highest = current;
17
+ }
18
+ current = current.parentPath;
19
+ }
20
+
21
+ return highest;
22
+ }
23
+
24
+ /** sx in MUI supports extra properties */
25
+ const propertyMapping = {
26
+ m: "margin",
27
+ p: "padding",
28
+ pt: "paddingTop",
29
+ pb: "paddingBottom",
30
+ pl: "paddingLeft",
31
+ pr: "paddingRight",
32
+ ml: "marginLeft",
33
+ mr: "marginRight",
34
+ mt: "marginTop",
35
+ mb: "marginBottom",
36
+ px: ["paddingLeft", "paddingRight"],
37
+ mx: ["marginLeft", "marginRight"],
38
+ py: ["paddingTop", "paddingBottom"],
39
+ my: ["marginTop", "marginBottom"],
40
+ bgcolor: "backgroundColor",
41
+ };
42
+
43
+ function transform(fileInfo, api) {
44
+ /**
45
+ * @type {import("jscodeshift").JSCodeshift}
46
+ */
47
+ const j = api.jscodeshift;
48
+ const root = j(fileInfo.source);
49
+
50
+ let i = 0;
51
+
52
+ const makeStyleOptions = j.objectExpression([]);
53
+ const makeStyleFunction = j.arrowFunctionExpression(
54
+ [j.identifier("_theme")],
55
+ makeStyleOptions
56
+ );
57
+ const useStylesDeclaration = j.expressionStatement(
58
+ j.assignmentExpression(
59
+ "=",
60
+ j.identifier(`const useStyles`),
61
+ j.callExpression(j.identifier("makeStyles()"), [makeStyleFunction])
62
+ )
63
+ );
64
+
65
+ const alreadyAddedUseStyles = new Set();
66
+ // Step 1: Find every React element with an sx property
67
+ root
68
+ .find(j.JSXElement)
69
+ .find(j.JSXAttribute, {
70
+ name: {
71
+ type: "JSXIdentifier",
72
+ name: "sx",
73
+ },
74
+ })
75
+ .forEach((path) => {
76
+ // Step 2: Extract content of the sx property into a useStyles hook
77
+ const jsxElement = path.parentPath.parentPath; // JSXElement
78
+ const jsxAttributes = jsxElement.node.attributes;
79
+ const sxAttributeIndex = jsxAttributes.findIndex(
80
+ (attr) => attr.name && attr.name.name === "sx"
81
+ );
82
+
83
+ if (sxAttributeIndex >= 0) {
84
+ const sxAttribute = jsxAttributes[sxAttributeIndex];
85
+ const sxValue = sxAttribute.value;
86
+
87
+ if (
88
+ sxValue.expression.properties &&
89
+ sxValue.expression.properties.length < 4
90
+ ) {
91
+ return;
92
+ }
93
+
94
+ // Add the useStyles hook above the function
95
+ let parentFunction = findHighestFunctionBlock(jsxElement);
96
+
97
+ if (!parentFunction) {
98
+ return;
99
+ }
100
+
101
+ // Step 4: Create the useStylesDeclaration with makeStyles
102
+ makeStyleOptions.properties.push(
103
+ j.property("init", j.identifier(`root${i}`), sxValue.expression)
104
+ );
105
+
106
+ // Traverse through the object expression properties
107
+ if (sxValue.expression.type === "ObjectExpression") {
108
+ sxValue.expression.properties = sxValue.expression.properties
109
+ .flatMap((property) => {
110
+ if (
111
+ property.type !== "Property" ||
112
+ property.key.type !== "Identifier"
113
+ ) {
114
+ return property;
115
+ }
116
+
117
+ const propertyName = property.key.name;
118
+ const mappedPropertyName = propertyMapping[propertyName];
119
+
120
+ if (!mappedPropertyName) {
121
+ return property;
122
+ }
123
+
124
+ if (Array.isArray(mappedPropertyName)) {
125
+ return mappedPropertyName.map((name) => ({
126
+ ...property,
127
+ key: name,
128
+ }));
129
+ } else {
130
+ // Replace the property key with the mapped key
131
+ property.key = j.identifier(mappedPropertyName);
132
+ return property;
133
+ }
134
+ })
135
+ .filter((x) => !!x);
136
+ }
137
+
138
+ if (!alreadyAddedUseStyles.has(parentFunction)) {
139
+ parentFunction.node.body.unshift(`const { classes } = useStyles()`);
140
+ alreadyAddedUseStyles.add(parentFunction);
141
+ }
142
+
143
+ // Remove the sx attribute
144
+ jsxAttributes.splice(sxAttributeIndex, 1);
145
+ jsxAttributes.push(
146
+ j.jsxAttribute(
147
+ j.jsxIdentifier("className"),
148
+ j.jsxExpressionContainer(j.identifier(`classes.root${i}`))
149
+ )
150
+ );
151
+
152
+ i++;
153
+ }
154
+ });
155
+
156
+ // Step 5: Insert the useStylesDeclaration at module level
157
+ const lastImport = root.find(j.ImportDeclaration).at(-1);
158
+ lastImport.insertAfter(useStylesDeclaration);
159
+ return root.toSource();
160
+ }
161
+
162
+ module.exports = transform;