@interactivethings/scripts 2.2.0 → 2.2.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.
@@ -47,7 +47,7 @@ const run = async (args) => {
47
47
  if (!deployment) {
48
48
  throw new Error("Could not retrieve deployment");
49
49
  }
50
- console.log(`DEPLOYMENT_URL=https://${deployment.url}`);
50
+ console.log(`DEPLOYMENT_URL=${deployment.url}`);
51
51
  break;
52
52
  }
53
53
  };
@@ -32,14 +32,14 @@ const deployments_1 = require("@interactivethings/scripts/vercel/deployments");
32
32
  });
33
33
  (0, vitest_1.describe)("run", () => {
34
34
  (0, vitest_1.it)("should successfully wait for deployment with valid args", async () => {
35
- const mockDeployment = {
36
- url: "test-deployment.vercel.app",
35
+ const mockDeploymentReadyResult = {
36
+ url: "https://test-deployment.vercel.app",
37
37
  state: "READY",
38
38
  meta: {
39
39
  githubCommitSha: "test-commit-sha",
40
40
  },
41
41
  };
42
- vitest_1.vi.mocked(deployments_1.waitForDeploymentReady).mockResolvedValue(mockDeployment);
42
+ vitest_1.vi.mocked(deployments_1.waitForDeploymentReady).mockResolvedValue(mockDeploymentReadyResult);
43
43
  const consoleSpy = vitest_1.vi.spyOn(console, "log").mockImplementation(() => { });
44
44
  const args = {
45
45
  subcommand: "wait-deployment",
@@ -0,0 +1,62 @@
1
+ # Tokens Studio Transform Examples
2
+
3
+ This directory contains example transformer handlers that demonstrate how to use the `ixt tokens-studio transform` command with custom transformation logic.
4
+
5
+ ## Development Setup
6
+
7
+ When working in this repository during development, the TypeScript path mappings are configured to resolve `@interactivethings/scripts/*` imports to the local source files.
8
+
9
+ To compile and check the examples:
10
+
11
+ ```bash
12
+ npx tsc --noEmit --project tsconfig.examples.json
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### MUI Transform Example
18
+
19
+ ```bash
20
+ ixt tokens-studio transform --handler ./examples/mui-transform.ts --input ./tokens --output mui-theme.json
21
+ ```
22
+
23
+ ### Tailwind Transform Example
24
+
25
+ ```bash
26
+ ixt tokens-studio transform --handler ./examples/tailwind-transform.ts --input ./tokens --output tailwind-theme.json
27
+ ```
28
+
29
+ ### Custom Transform Example
30
+
31
+ ```bash
32
+ ixt tokens-studio transform --handler ./examples/custom-transform.js --input ./tokens --output custom-theme.json
33
+ ```
34
+
35
+ ## Creating Your Own Transformer
36
+
37
+ 1. Copy one of the example files as a starting point
38
+ 2. Modify the `transform` function to suit your needs
39
+ 3. The `transform` function receives: `{ metadata, tokenData }`
40
+ 4. Return the transformed data object
41
+
42
+ ### Using in Production
43
+
44
+ When using these examples in your own project after installing `@interactivethings/scripts`:
45
+
46
+ ```typescript
47
+ import {
48
+ kebabCase,
49
+ mapEntries /* ... */,
50
+ } from "@interactivethings/scripts/tokens-studio";
51
+ import { $IntentionalAny } from "@interactivethings/scripts/types";
52
+ ```
53
+
54
+ Or using the alias:
55
+
56
+ ```typescript
57
+ import {
58
+ kebabCase,
59
+ mapEntries /* ... */,
60
+ } from "@interactivethings/scripts/tokens-studio";
61
+ import { $IntentionalAny } from "@interactivethings/scripts/types";
62
+ ```
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Example custom transformer handler
3
+ * This file demonstrates how to create a completely custom transformer
4
+ *
5
+ * Usage:
6
+ * ixt tokens-studio transform --handler ./examples/custom-transform.js --input ./tokens --output theme.json
7
+ */
8
+
9
+ module.exports = {
10
+ transform: (input) => {
11
+ const { metadata, tokenData } = input;
12
+
13
+ // Example: Extract only colors and convert to CSS custom properties
14
+ const colors = tokenData.core || {};
15
+
16
+ const cssVariables = {};
17
+
18
+ // Recursively convert tokens to CSS custom property format
19
+ const convertToCssVars = (obj, prefix = "") => {
20
+ for (const [key, value] of Object.entries(obj)) {
21
+ if (value && typeof value === "object" && "value" in value) {
22
+ // This is a token
23
+ cssVariables[`--${prefix}${key}`] = value.value;
24
+ } else if (value && typeof value === "object") {
25
+ // This is a nested object
26
+ convertToCssVars(value, `${prefix}${key}-`);
27
+ }
28
+ }
29
+ };
30
+
31
+ convertToCssVars(colors, "color-");
32
+
33
+ return {
34
+ cssVariables,
35
+ metadata: {
36
+ generatedAt: new Date().toISOString(),
37
+ tokenSetOrder: metadata.tokenSetOrder,
38
+ },
39
+ };
40
+ },
41
+ };
@@ -0,0 +1,33 @@
1
+ import { defineConfig } from '@interactivethings/scripts';
2
+
3
+ export default defineConfig({
4
+ figma: {
5
+ // Figma API token (can also be set via FIGMA_TOKEN environment variable)
6
+ token: process.env.FIGMA_TOKEN,
7
+
8
+ // Assets to download from Figma
9
+ assets: [
10
+ {
11
+ name: "icons",
12
+ url: "https://www.figma.com/design/ElWWZIcOGFhiT06rzfIwRO/Design-System?node-id=11861-10071",
13
+ output: "src/assets/icons"
14
+ },
15
+ {
16
+ name: "illustrations",
17
+ url: "https://www.figma.com/design/ElWWZIcOGFhiT06rzfIwRO/Design-System?node-id=12345-67890",
18
+ output: "src/assets/illustrations"
19
+ }
20
+ ]
21
+ },
22
+
23
+ tokensStudio: {
24
+ // Input directory containing design tokens
25
+ input: "./design-tokens",
26
+
27
+ // Default output file for transformations
28
+ output: "src/theme/tokens.json",
29
+
30
+ // Default transformer handler
31
+ handler: "./transforms/mui-transform.ts"
32
+ }
33
+ });
@@ -0,0 +1,158 @@
1
+ /**
2
+ * MUI transformer for tokens-studio
3
+ * Transforms tokens exported with tokens-studio in Figma into
4
+ * a format that is easier to work with when using MUI.
5
+ */
6
+
7
+ import { mapValues, pick } from "remeda";
8
+
9
+ import { $IntentionalAny } from "@interactivethings/scripts/types";
10
+ import {
11
+ simplifyValues,
12
+ kebabCase,
13
+ mapEntries,
14
+ maybeParseToNumber,
15
+ maybeParseToPx,
16
+ makeResolver,
17
+ defineTransform,
18
+ type TokenStudioShadowValue,
19
+ mapKeysRecursively,
20
+ } from "@interactivethings/scripts/tokens-studio";
21
+
22
+ // Ideally those types should be imported from your theme's type definitions
23
+ // You should change those lines to match your actual theme structure
24
+ // @example
25
+ // ```
26
+ // import tokensStudioTokens from '../path/to/your/tokens-studio/tokens.json';
27
+ // type MUITokenData = typeof tokensStudioTokens
28
+ // ```
29
+ type MUITokenData = {
30
+ Base: $IntentionalAny;
31
+ Functional: $IntentionalAny;
32
+ Elevation: Record<
33
+ string,
34
+ { value: TokenStudioShadowValue | TokenStudioShadowValue[] }
35
+ >;
36
+ Desktop: $IntentionalAny;
37
+ Mobile: $IntentionalAny;
38
+ fontFamilies: $IntentionalAny;
39
+ lineHeights: $IntentionalAny;
40
+ fontWeights: $IntentionalAny;
41
+ fontSize: $IntentionalAny;
42
+ letterSpacing: $IntentionalAny;
43
+ textDecoration: $IntentionalAny;
44
+ };
45
+
46
+ const renameColorKeys = (k: string) => {
47
+ return kebabCase(
48
+ k
49
+ .replace(/-[a-zA-Z0-9]+/, "")
50
+ .replace(" - ", " ")
51
+ .replace(",", "")
52
+ .replace(/^\d+\s+/, "")
53
+ );
54
+ };
55
+
56
+ const getPalette = (tokensData: MUITokenData) => {
57
+ const data = tokensData;
58
+ const colorKeys = ["Base", "Functional"] as const;
59
+
60
+ let palette = pick(data, colorKeys);
61
+ type Palette = typeof palette;
62
+ palette = mapValues(palette, simplifyValues);
63
+ const tpalette = mapKeysRecursively(palette, renameColorKeys) as unknown as {
64
+ base: Palette["Base"];
65
+ functional: Palette["Functional"];
66
+ };
67
+ palette = {
68
+ ...tpalette.base,
69
+ ...pick(tpalette, ["functional"]),
70
+ };
71
+ return palette;
72
+ };
73
+
74
+ const getTypography = (tokensData: MUITokenData) => {
75
+ const sizes = ["Desktop", "Mobile"] as const;
76
+ const res = {} as Record<string, $IntentionalAny>;
77
+ const resolve = makeResolver(tokensData, [
78
+ "fontFamilies",
79
+ "lineHeights",
80
+ "fontWeights",
81
+ "fontSize",
82
+ "letterSpacing",
83
+ "textDecoration",
84
+ ] as const);
85
+
86
+ const cleanupTypographyFns = {
87
+ fontWeight: (x: string) => {
88
+ const lowered = x.toLowerCase();
89
+ if (lowered == "regular") {
90
+ return 400;
91
+ }
92
+ return lowered;
93
+ },
94
+ fontSize: maybeParseToNumber,
95
+ lineHeight: (x: string | number) => maybeParseToPx(maybeParseToNumber(x)),
96
+ paragraphSpacing: maybeParseToNumber,
97
+ letterSpacing: maybeParseToNumber,
98
+ };
99
+
100
+ const cleanupTypographyValue = (k: string, v: $IntentionalAny) => {
101
+ if (k in cleanupTypographyFns) {
102
+ return [
103
+ k,
104
+ cleanupTypographyFns[k as keyof typeof cleanupTypographyFns](v),
105
+ ] as [string, $IntentionalAny];
106
+ } else {
107
+ return [k, v] as [string, $IntentionalAny];
108
+ }
109
+ };
110
+
111
+ for (const size of sizes) {
112
+ const typographies = tokensData[size];
113
+ for (const [typo, typoDataRaw] of Object.entries(typographies)) {
114
+ const typoData = typoDataRaw as { value: $IntentionalAny };
115
+ const typoKey = kebabCase(typo.toLowerCase());
116
+ res[typoKey] = res[typoKey] || {};
117
+ res[typoKey][size.toLowerCase()] = mapEntries(
118
+ mapValues(typoData.value, resolve),
119
+ cleanupTypographyValue
120
+ );
121
+ }
122
+ }
123
+ return res;
124
+ };
125
+
126
+ const getShadows = (tokenData: MUITokenData) => {
127
+ const transformShadow = (shadowData: TokenStudioShadowValue) => {
128
+ const { color, x, y, blur, spread } = shadowData;
129
+ return `${x}px ${y}px ${blur}px ${spread}px ${color}`;
130
+ };
131
+
132
+ const shadows = mapEntries(tokenData.Elevation, (k, v) => {
133
+ const shadowEntry = v as
134
+ | { value: TokenStudioShadowValue }
135
+ | { value: TokenStudioShadowValue[] };
136
+ return [
137
+ `${Number((k as string).replace("dp", "").replace("pd", ""))}`,
138
+ Array.isArray(shadowEntry.value)
139
+ ? (shadowEntry.value as TokenStudioShadowValue[])
140
+ .map(transformShadow)
141
+ .join(", ")
142
+ : transformShadow(shadowEntry.value as TokenStudioShadowValue),
143
+ ];
144
+ });
145
+ return shadows;
146
+ };
147
+
148
+ export const transform = defineTransform<MUITokenData>((input) => {
149
+ const palette = getPalette(input.tokenData);
150
+ const typography = getTypography(input.tokenData);
151
+ const shadows = getShadows(input.tokenData);
152
+
153
+ return {
154
+ palette,
155
+ typography,
156
+ shadows,
157
+ };
158
+ });
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Tailwind transformer for tokens-studio
3
+ * Transforms tokens exported with tokens-studio in Figma into
4
+ * a format that is compatible with Tailwind CSS.
5
+ */
6
+
7
+ import { mapValues, pick } from "remeda";
8
+ import {
9
+ resolveReferences,
10
+ mapEntries,
11
+ flattenTokens,
12
+ defineTransform,
13
+ } from "@interactivethings/scripts/tokens-studio";
14
+ import { $IntentionalAny } from "../dist/types";
15
+
16
+ // Ideally those types should be imported from your theme's type definitions
17
+ // You should change those lines to match your actual theme structure
18
+ // @example
19
+ // ```
20
+ // import coreTokens from '../path/to/your/core/tokens.json';
21
+ // import functionalTokens from '../path/to/your/functional/tokens.json';
22
+ // type TailwindTokenData = {
23
+ // core: typeof coreTokens;
24
+ // functional: typeof functionalTokens;
25
+ // };
26
+ // ```
27
+ type TailwindTokenData = {
28
+ core: $IntentionalAny;
29
+ functional: $IntentionalAny;
30
+ };
31
+
32
+ export const transform = defineTransform<TailwindTokenData>((input) => {
33
+ const mergedTokens = input.tokenData;
34
+
35
+ const valueMap = new Map<string, string>();
36
+ resolveReferences(mergedTokens, valueMap);
37
+
38
+ const theme = {
39
+ colors: {
40
+ ...pick(mergedTokens.core, [
41
+ "amber",
42
+ "blue",
43
+ "brand",
44
+ "cyan",
45
+ "emerald",
46
+ "fuchsia",
47
+ "green",
48
+ "indigo",
49
+ "light-blue",
50
+ "lime",
51
+ "midnight",
52
+ "monochrome",
53
+ "orange",
54
+ "pink",
55
+ "purple",
56
+ "red",
57
+ "rose",
58
+ "teal",
59
+ "violet",
60
+ "yellow",
61
+ ]),
62
+ ...pick(mergedTokens.functional, [
63
+ "primary",
64
+ "secondary",
65
+ "tertiary",
66
+ "error",
67
+ "success",
68
+ "warning",
69
+ "link",
70
+ "surface",
71
+ "on-surface",
72
+ "surface-inverted",
73
+ "on-surface-inverted",
74
+ "border",
75
+ "input",
76
+ "accent",
77
+ "qualitative",
78
+ "pathways",
79
+ ]),
80
+ },
81
+ borderWidth: {
82
+ ...mergedTokens.functional["border-width"],
83
+ },
84
+ borderRadius: {
85
+ ...mergedTokens.functional["border-radius"],
86
+ },
87
+ fontSize: mergedTokens.core["font-size"],
88
+ fontWeight: mergedTokens.core["font-weight"],
89
+ lineHeight: mergedTokens.core["line-height"],
90
+ spacing: mergedTokens.functional.spacing,
91
+ };
92
+
93
+ const transformKey = (key: string, depth: number) => {
94
+ if (key.startsWith("on-")) {
95
+ if (depth === 1) {
96
+ return key.replace("on-", "foreground-");
97
+ } else {
98
+ return "foreground";
99
+ }
100
+ }
101
+ if (key === "default") {
102
+ return "DEFAULT";
103
+ }
104
+ return key;
105
+ };
106
+
107
+ const flattenedTheme = mapValues(theme, (x) =>
108
+ flattenTokens(x, 0, transformKey)
109
+ ) as {
110
+ colors: Record<string, string>;
111
+ borderWidth: Record<string, string>;
112
+ borderRadius: Record<string, string>;
113
+ fontSize: Record<
114
+ "mobile-large" | "mobile-xxxlarge",
115
+ Record<string, number>
116
+ >;
117
+ fontWeight: Record<string, string>;
118
+ lineHeight: Record<
119
+ "mobile-large" | "mobile-xxxlarge",
120
+ Record<string, number>
121
+ >;
122
+ spacing: Record<string, string>;
123
+ };
124
+
125
+ const lineHeights = flattenedTheme.lineHeight;
126
+ const fontWeights = flattenedTheme.fontWeight;
127
+
128
+ const finalTheme = {
129
+ ...flattenedTheme,
130
+ borderWidth: mapValues(flattenedTheme.borderWidth, (x) => `${x}px`),
131
+ borderRadius: mapValues(flattenedTheme.borderRadius, (x) => `${x}px`),
132
+ fontSize: mapEntries(
133
+ flattenedTheme.fontSize["mobile-large"],
134
+ (k: string, value) => {
135
+ return [
136
+ k.replace(",", ""),
137
+ [
138
+ `${value}px`,
139
+ {
140
+ lineHeight: lineHeights["mobile-large"][k]
141
+ ? `${lineHeights["mobile-large"][k]}px`
142
+ : undefined,
143
+ fontWeight: fontWeights[k],
144
+ },
145
+ ],
146
+ ];
147
+ }
148
+ ),
149
+ fontWeight: flattenedTheme.fontWeight,
150
+ lineHeight: mapEntries(
151
+ flattenedTheme.lineHeight["mobile-large"],
152
+ (k: string, value) => {
153
+ return [k.replace(",", ""), `${value}px`];
154
+ }
155
+ ),
156
+ backgroundImage: {
157
+ "gradient-2":
158
+ "var(--Gradient-2, linear-gradient(135deg, var(--core-brand-electricblue500, #48A1C7) 20.83%, var(--core-brand-nocturne700, #336476) 87.96%))",
159
+ },
160
+ spacing: mapEntries(flattenedTheme.spacing, (k, v) => [
161
+ k.replace("spacing-", ""),
162
+ `${v}px`,
163
+ ]),
164
+ };
165
+
166
+ return finalTheme;
167
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interactivethings/scripts",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "main": "dist/index.js",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",
@@ -36,7 +36,8 @@
36
36
  "files": [
37
37
  "dist",
38
38
  "README.md",
39
- "codemods"
39
+ "codemods",
40
+ "examples"
40
41
  ],
41
42
  "dependencies": {
42
43
  "@next/env": "^15.5.4",
@@ -51,7 +52,7 @@
51
52
  "devDependencies": {
52
53
  "@semantic-release/changelog": "^6.0.3",
53
54
  "@semantic-release/git": "^10.0.1",
54
- "@semantic-release/github": "^11.0.6",
55
+ "@semantic-release/github": "^12.0.5",
55
56
  "@types/argparse": "^2.0.17",
56
57
  "@types/jscodeshift": "^0.11.10",
57
58
  "@types/node": "22",