@interactivethings/scripts 0.0.4 → 0.0.6

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.
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ /**
3
+ * This script transforms tokens exported with tokens-studio in Figma into
4
+ * a format that is easier to work with when using MUI.
5
+ * You can run it via `pnpm run design:tokens`
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ const fs = require("fs");
9
+ const argparse_1 = require("argparse");
10
+ const remeda_1 = require("remeda");
11
+ const simplifyValues = (x) => {
12
+ if (typeof x === "string") {
13
+ return x;
14
+ }
15
+ else if (typeof x === "object" && !!x) {
16
+ if ("value" in x) {
17
+ return x.value;
18
+ }
19
+ else {
20
+ return (0, remeda_1.mapValues)(x, simplifyValues);
21
+ }
22
+ }
23
+ };
24
+ const kebabCase = (x) => {
25
+ return x
26
+ .split(" ")
27
+ .map((t, i) => {
28
+ if (i === 0) {
29
+ return t.toLowerCase();
30
+ }
31
+ else {
32
+ return `${t[0].toUpperCase()}${t.substring(1).toLowerCase()}`;
33
+ }
34
+ })
35
+ .join("");
36
+ };
37
+ const renameColorKeys = (k) => {
38
+ return kebabCase(k
39
+ .replace(/-[a-zA-Z0-9]+/, "")
40
+ .replace(" - ", " ")
41
+ .replace(",", "")
42
+ .replace(/^\d+\s+/, ""));
43
+ };
44
+ const renameColorKeysEntries = (obj) => {
45
+ const visitor = (k, v) => {
46
+ if (typeof v === "object") {
47
+ return [renameColorKeys(k), mapEntries(v, visitor)];
48
+ }
49
+ else {
50
+ return [renameColorKeys(k), v];
51
+ }
52
+ };
53
+ return mapEntries(obj, visitor);
54
+ };
55
+ const mapEntries = (x, mapper) => {
56
+ return Object.fromEntries(Object.entries(x).map(([k, v]) => mapper(k, v)));
57
+ };
58
+ const getPalette = (tokensData) => {
59
+ const data = tokensData.global;
60
+ const colorKeys = ["Base", "Functional"];
61
+ let palette = (0, remeda_1.pick)(data, colorKeys);
62
+ palette = (0, remeda_1.mapValues)(palette, simplifyValues);
63
+ palette = renameColorKeysEntries(palette);
64
+ palette = {
65
+ ...palette.base,
66
+ ...(0, remeda_1.pick)(palette, ["functional"]),
67
+ };
68
+ return palette;
69
+ };
70
+ const getTypography = (tokensData) => {
71
+ const sizes = ["Desktop", "Mobile"];
72
+ const res = {};
73
+ const index = {};
74
+ for (const k of [
75
+ "fontFamilies",
76
+ "lineHeights",
77
+ "fontWeights",
78
+ "fontSize",
79
+ "letterSpacing",
80
+ "textDecoration",
81
+ ]) {
82
+ for (const [vName, value] of Object.entries(tokensData.global[k])) {
83
+ index[`${k}.${vName}`] = value;
84
+ }
85
+ }
86
+ const maybeParseToNumber = (x) => {
87
+ const parsed = Number(x);
88
+ if (Number.isNaN(x)) {
89
+ return x;
90
+ }
91
+ else {
92
+ return parsed;
93
+ }
94
+ };
95
+ const maybeParseToPx = (x) => {
96
+ if (typeof x === "number") {
97
+ return `${x}px`;
98
+ }
99
+ else {
100
+ return x;
101
+ }
102
+ };
103
+ const cleanupFns = {
104
+ fontWeight: (x) => {
105
+ const lowered = x.toLowerCase();
106
+ if (lowered == "regular") {
107
+ return 400;
108
+ }
109
+ return lowered;
110
+ },
111
+ fontSize: maybeParseToNumber,
112
+ lineHeight: (x) => maybeParseToPx(maybeParseToNumber(x)),
113
+ paragraphSpacing: maybeParseToNumber,
114
+ letterSpacing: maybeParseToNumber,
115
+ };
116
+ const cleanup = (k, v) => {
117
+ if (k in cleanupFns) {
118
+ return [k, cleanupFns[k](v)];
119
+ }
120
+ else {
121
+ return [k, v];
122
+ }
123
+ };
124
+ const resolve = (str) => {
125
+ if (str[0] === "{" && str[str.length - 1] === "}") {
126
+ const path = str.substring(1, str.length - 1);
127
+ return index[path]?.value;
128
+ }
129
+ else {
130
+ return str;
131
+ }
132
+ };
133
+ for (const size of sizes) {
134
+ const typographies = tokensData.global[size];
135
+ for (const [typo, typoDataRaw] of Object.entries(typographies)) {
136
+ const typoData = typoDataRaw;
137
+ const typoKey = kebabCase(typo.toLowerCase());
138
+ res[typoKey] = res[typoKey] || {};
139
+ res[typoKey][size.toLowerCase()] = mapEntries((0, remeda_1.mapValues)(typoData.value, resolve), cleanup);
140
+ }
141
+ }
142
+ return res;
143
+ };
144
+ const getShadows = (tokenData) => {
145
+ const transformShadow = (shadowData) => {
146
+ const { color, x, y, blur, spread } = shadowData;
147
+ return `${x}px ${y}px ${blur}px ${spread}px ${color}`;
148
+ };
149
+ const shadows = mapEntries(tokenData.global.Elevation, (k, v) => [
150
+ `${Number(k.replace("dp", "").replace("pd", ""))}`,
151
+ Array.isArray(v.value)
152
+ ? v.value.map(transformShadow).join(", ")
153
+ : transformShadow(v.value),
154
+ ]);
155
+ return shadows;
156
+ };
157
+ const transform = (tokenData) => {
158
+ const palette = getPalette(tokenData);
159
+ const typography = getTypography(tokenData);
160
+ const shadows = getShadows(tokenData);
161
+ return {
162
+ palette,
163
+ typography,
164
+ shadows,
165
+ };
166
+ };
167
+ const main = () => {
168
+ const parser = new argparse_1.ArgumentParser();
169
+ parser.add_argument("input");
170
+ parser.add_argument("output");
171
+ const args = parser.parse_args();
172
+ const content = JSON.parse(fs.readFileSync(args.input).toString());
173
+ const transformed = transform(content);
174
+ fs.writeFileSync(args.output === "-" ? process.stdout.fd : args.output, JSON.stringify(transformed, null, 2));
175
+ };
176
+ main();
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const argparse_1 = require("argparse");
4
+ const accessToken = process.env.VERCEL_TOKEN;
5
+ async function fetchDeploymentForCommit(commitSha, teamId, projectId) {
6
+ try {
7
+ const response = await fetch(`https://vercel.com/api/v6/deployments?limit=20&projectId=${projectId}&state=READY,ERROR,BUILDING,QUEUED&teamId=${teamId}`, {
8
+ headers: {
9
+ Authorization: `Bearer ${accessToken}`,
10
+ },
11
+ }).then((x) => x.json());
12
+ const deployments = response.deployments.filter((deployment) => deployment.meta.githubCommitSha === commitSha);
13
+ return deployments;
14
+ }
15
+ catch (error) {
16
+ console.error("Error:", error);
17
+ return [];
18
+ }
19
+ }
20
+ const sleep = (duration) => new Promise((resolve) => setTimeout(resolve, duration));
21
+ async function waitForDeploymentReady({ team, project, commitSha, interval, timeout, }) {
22
+ const start = Date.now();
23
+ const end = start + timeout;
24
+ while (Date.now() < end) {
25
+ const deployments = await fetchDeploymentForCommit(commitSha, team, project);
26
+ if (deployments.length === 0 || deployments[0].state !== "READY") {
27
+ const state = deployments[0].state;
28
+ if (state === "ERROR") {
29
+ throw new Error("Deployment errored");
30
+ }
31
+ console.log(`Deployment not yet ready (state: ${deployments[0].state}), waiting ${interval}ms for deployment with commit ${commitSha}`);
32
+ await sleep(Math.min(end - Date.now(), interval));
33
+ }
34
+ else {
35
+ console.log(`Deployment for commit ${commitSha} is READY`);
36
+ return deployments[0];
37
+ }
38
+ }
39
+ if (Date.now() > end) {
40
+ throw new Error("Timeout for waitForDeploymentReady");
41
+ }
42
+ }
43
+ async function main() {
44
+ const parser = new argparse_1.ArgumentParser();
45
+ parser.add_argument("commit", {
46
+ help: "Commit that started the deployment",
47
+ });
48
+ parser.add_argument("--interval", {
49
+ default: 5000,
50
+ type: Number,
51
+ });
52
+ parser.add_argument("--project", {
53
+ required: true,
54
+ });
55
+ parser.add_argument("--team", {
56
+ required: true,
57
+ });
58
+ parser.add_argument("--timeout", {
59
+ default: 10 * 60 * 1000,
60
+ type: Number,
61
+ });
62
+ const args = parser.parse_args();
63
+ const deployment = await waitForDeploymentReady({
64
+ commitSha: args.commit,
65
+ interval: args.interval,
66
+ timeout: args.timeout,
67
+ team: args.team,
68
+ project: args.project,
69
+ });
70
+ if (!deployment) {
71
+ throw new Error("Could not retrieve deployment");
72
+ }
73
+ console.log(`DEPLOYMENT_URL=https://${deployment.url}`);
74
+ }
75
+ main().catch((e) => {
76
+ console.error(e);
77
+ process.exit(1);
78
+ });
package/package.json CHANGED
@@ -1,15 +1,22 @@
1
1
  {
2
2
  "name": "@interactivethings/scripts",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "main": "index.js",
5
5
  "repository": "git@github.com:interactivethings/ixt-scripts.git",
6
6
  "author": "Interactive Things <we@interactivethings.com>",
7
7
  "license": "MIT",
8
+ "bin": {
9
+ "wait-for-vercel-deploy": "dist/wait-for-vercel-deploy.js",
10
+ "mui-tokens-studio": "mui-tokens-studio.js"
11
+ },
12
+ "files": ["dist", "README.md"],
8
13
  "dependencies": {
14
+ "@types/argparse": "^2.0.12",
9
15
  "argparse": "^2.0.1",
10
16
  "remeda": "^1.19.0"
11
17
  },
12
18
  "devDependencies": {
13
- "@types/node": "^20.3.1"
19
+ "@types/node": "^20.3.1",
20
+ "typescript": "^5.2.2"
14
21
  }
15
22
  }
package/.prettierrc DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "tabWidth": 2,
3
- "useTabs": false
4
- }
@@ -1,227 +0,0 @@
1
- /**
2
- * This script transforms tokens exported with tokens-studio in Figma into
3
- * a format that is easier to work with when using MUI.
4
- * You can run it via `pnpm run design:tokens`
5
- */
6
-
7
- import fs from "fs";
8
-
9
- import { ArgumentParser } from "argparse";
10
- import { mapValues, pick } from "remeda";
11
-
12
- import { $IntentionalAny } from "./types";
13
-
14
- const simplifyValues = (
15
- x: { value: unknown } | string | null
16
- ): $IntentionalAny => {
17
- if (typeof x === "string") {
18
- return x;
19
- } else if (typeof x === "object" && !!x) {
20
- if ("value" in x) {
21
- return x.value;
22
- } else {
23
- return mapValues(x, simplifyValues);
24
- }
25
- }
26
- };
27
-
28
- const kebabCase = (x: string) => {
29
- return x
30
- .split(" ")
31
- .map((t, i) => {
32
- if (i === 0) {
33
- return t.toLowerCase();
34
- } else {
35
- return `${t[0].toUpperCase()}${t.substring(1).toLowerCase()}`;
36
- }
37
- })
38
- .join("");
39
- };
40
- const renameColorKeys = (k: string) => {
41
- return kebabCase(
42
- k
43
- .replace(/-[a-zA-Z0-9]+/, "")
44
- .replace(" - ", " ")
45
- .replace(",", "")
46
- .replace(/^\d+\s+/, "")
47
- );
48
- };
49
-
50
- const renameColorKeysEntries = (obj: $IntentionalAny) => {
51
- const visitor = (k: string, v: $IntentionalAny) => {
52
- if (typeof v === "object") {
53
- return [renameColorKeys(k), mapEntries(v, visitor)] as [
54
- string,
55
- $IntentionalAny
56
- ];
57
- } else {
58
- return [renameColorKeys(k), v] as [string, $IntentionalAny];
59
- }
60
- };
61
- return mapEntries(obj, visitor);
62
- };
63
-
64
- const mapEntries = (
65
- x: $IntentionalAny,
66
- mapper: (k: string, v: $IntentionalAny) => [string, $IntentionalAny]
67
- ) => {
68
- return Object.fromEntries(Object.entries(x).map(([k, v]) => mapper(k, v)));
69
- };
70
-
71
- const getPalette = (tokensData: $IntentionalAny) => {
72
- const data = tokensData.global;
73
- const colorKeys = ["Base", "Functional"];
74
-
75
- let palette = pick(data, colorKeys);
76
- palette = mapValues(palette, simplifyValues);
77
- palette = renameColorKeysEntries(palette);
78
- palette = {
79
- ...palette.base,
80
- ...pick(palette, ["functional"]),
81
- };
82
- return palette;
83
- };
84
-
85
- const getTypography = (tokensData: $IntentionalAny) => {
86
- const sizes = ["Desktop", "Mobile"];
87
- const res = {} as Record<string, $IntentionalAny>;
88
- const index = {} as Record<string, $IntentionalAny>;
89
- for (const k of [
90
- "fontFamilies",
91
- "lineHeights",
92
- "fontWeights",
93
- "fontSize",
94
- "letterSpacing",
95
- "textDecoration",
96
- ]) {
97
- for (const [vName, value] of Object.entries(tokensData.global[k])) {
98
- index[`${k}.${vName}`] = value;
99
- }
100
- }
101
-
102
- const maybeParseToNumber = (x: string | number) => {
103
- const parsed = Number(x);
104
- if (Number.isNaN(x)) {
105
- return x;
106
- } else {
107
- return parsed;
108
- }
109
- };
110
-
111
- const maybeParseToPx = (x: string | number) => {
112
- if (typeof x === "number") {
113
- return `${x}px`;
114
- } else {
115
- return x;
116
- }
117
- };
118
-
119
- const cleanupFns = {
120
- fontWeight: (x: string) => {
121
- const lowered = x.toLowerCase();
122
- if (lowered == "regular") {
123
- return 400;
124
- }
125
- return lowered;
126
- },
127
- fontSize: maybeParseToNumber,
128
- lineHeight: (x: string | number) => maybeParseToPx(maybeParseToNumber(x)),
129
- paragraphSpacing: maybeParseToNumber,
130
- letterSpacing: maybeParseToNumber,
131
- };
132
-
133
- const cleanup = (k: string, v: $IntentionalAny) => {
134
- if (k in cleanupFns) {
135
- return [k, cleanupFns[k as keyof typeof cleanupFns](v)] as [
136
- string,
137
- $IntentionalAny
138
- ];
139
- } else {
140
- return [k, v] as [string, $IntentionalAny];
141
- }
142
- };
143
-
144
- const resolve = (str: string) => {
145
- if (str[0] === "{" && str[str.length - 1] === "}") {
146
- const path = str.substring(1, str.length - 1);
147
- return index[path]?.value;
148
- } else {
149
- return str;
150
- }
151
- };
152
- for (const size of sizes) {
153
- const typographies = tokensData.global[size];
154
- for (const [typo, typoDataRaw] of Object.entries(typographies)) {
155
- const typoData = typoDataRaw as { value: $IntentionalAny };
156
- const typoKey = kebabCase(typo.toLowerCase());
157
- res[typoKey] = res[typoKey] || {};
158
- res[typoKey][size.toLowerCase()] = mapEntries(
159
- mapValues(typoData.value, resolve),
160
- cleanup
161
- );
162
- }
163
- }
164
- return res;
165
- };
166
-
167
- const getShadows = (tokenData: TokenData) => {
168
- const transformShadow = (shadowData: ShadowValue) => {
169
- const { color, x, y, blur, spread } = shadowData;
170
- return `${x}px ${y}px ${blur}px ${spread}px ${color}`;
171
- };
172
-
173
- const shadows = mapEntries(tokenData.global.Elevation, (k, v) => [
174
- `${Number(k.replace("dp", "").replace("pd", ""))}`,
175
- Array.isArray(v.value)
176
- ? v.value.map(transformShadow).join(", ")
177
- : transformShadow(v.value),
178
- ]);
179
- return shadows;
180
- };
181
-
182
- type ShadowValue = {
183
- color: string;
184
- x: string;
185
- y: string;
186
- blur: string;
187
- spread: string;
188
- };
189
- type ShadowRecord = Record<
190
- string,
191
- { value: ShadowValue } | { value: ShadowValue[] }
192
- >;
193
-
194
- type TokenData = {
195
- global: {
196
- Base: $IntentionalAny;
197
- Function: $IntentionalAny;
198
- Elevation: ShadowRecord;
199
- };
200
- };
201
-
202
- const transform = (tokenData: TokenData) => {
203
- const palette = getPalette(tokenData);
204
- const typography = getTypography(tokenData);
205
- const shadows = getShadows(tokenData);
206
- return {
207
- palette,
208
- typography,
209
- shadows,
210
- };
211
- };
212
-
213
- const main = () => {
214
- const parser = new ArgumentParser();
215
- parser.add_argument("input");
216
- parser.add_argument("output");
217
- const args = parser.parse_args();
218
- const content = JSON.parse(fs.readFileSync(args.input).toString());
219
- const transformed = transform(content);
220
-
221
- fs.writeFileSync(
222
- args.output === "-" ? process.stdout.fd : args.output,
223
- JSON.stringify(transformed, null, 2)
224
- );
225
- };
226
-
227
- main();
package/types.ts DELETED
@@ -1 +0,0 @@
1
- export type $IntentionalAny = any