@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.
- package/README.md +208 -12
- package/codemods/__testfixtures__/sx-to-use-styles.input.js +50 -0
- package/codemods/__testfixtures__/sx-to-use-styles.output.js +59 -0
- package/codemods/__tests__/defineTest.ts +9 -0
- package/codemods/__tests__/sx-to-use-styles.test.js +3 -0
- package/codemods/sx-to-use-styles.js +162 -0
- package/dist/__tests__/figma-cli.test.js +189 -0
- package/dist/__tests__/tokens-studio-cli.test.js +197 -0
- package/dist/__tests__/vercel-cli.test.js +86 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +103 -0
- package/dist/config.d.ts +78 -0
- package/dist/config.js +167 -0
- package/dist/figma/api.d.ts +71 -0
- package/dist/figma/api.js +175 -0
- package/dist/figma/cli.d.ts +20 -0
- package/dist/figma/cli.js +163 -0
- package/dist/figma/cli.test.js +197 -0
- package/dist/figma/images.js +63 -0
- package/dist/figma/optimizeImage.js +53 -0
- package/dist/figma/utils.d.ts +10 -0
- package/dist/figma/utils.js +26 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +53 -0
- package/dist/init/cli.d.ts +3 -0
- package/dist/init/cli.js +320 -0
- package/dist/mui-tokens-studio.js +0 -0
- package/dist/plugins/figma/index.js +653 -0
- package/dist/plugins/tokens-studio/utils.js +155 -0
- package/dist/tokens-studio/__tests__/fixtures/invalid-transformer.js +12 -0
- package/dist/tokens-studio/__tests__/fixtures/test-transformer.js +18 -0
- package/dist/tokens-studio/cli.d.ts +8 -0
- package/dist/tokens-studio/cli.js +153 -0
- package/dist/tokens-studio/cli.test.js +160 -0
- package/dist/tokens-studio/index.d.ts +14 -0
- package/dist/tokens-studio/index.js +56 -0
- package/dist/tokens-studio/mui.js +212 -0
- package/dist/tokens-studio/require.js +17 -0
- package/dist/tokens-studio/tailwind.js +211 -0
- package/dist/tokens-studio/types.js +2 -0
- package/dist/tokens-studio/utils.d.ts +49 -0
- package/dist/tokens-studio/utils.js +156 -0
- package/dist/types.d.ts +1 -0
- package/dist/vercel/cli.d.ts +4 -0
- package/dist/vercel/cli.js +70 -0
- package/dist/vercel/cli.test.js +86 -0
- package/dist/vercel/deployments.js +41 -0
- package/dist/vercel/waitForDeploymentReady.js +43 -0
- package/package.json +46 -6
package/dist/config.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.IxtConfigSchema = exports.VercelConfigSchema = exports.TokensStudioConfigSchema = exports.FigmaConfigSchema = exports.FigmaAssetConfigSchema = void 0;
|
|
37
|
+
exports.defineConfig = defineConfig;
|
|
38
|
+
exports.loadConfig = loadConfig;
|
|
39
|
+
exports.mergeConfigWithArgs = mergeConfigWithArgs;
|
|
40
|
+
exports.validateRequiredConfig = validateRequiredConfig;
|
|
41
|
+
const fs = __importStar(require("fs/promises"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const zod_1 = require("zod");
|
|
44
|
+
/**
|
|
45
|
+
* Figma configuration schema
|
|
46
|
+
*/
|
|
47
|
+
exports.FigmaAssetConfigSchema = zod_1.z.object({
|
|
48
|
+
url: zod_1.z
|
|
49
|
+
.string()
|
|
50
|
+
.url()
|
|
51
|
+
.describe("The Figma URL containing the assets to download"),
|
|
52
|
+
output: zod_1.z.string().describe("Output directory where assets will be saved"),
|
|
53
|
+
name: zod_1.z.string().describe("Identifier for this asset collection"),
|
|
54
|
+
});
|
|
55
|
+
exports.FigmaConfigSchema = zod_1.z.object({
|
|
56
|
+
token: zod_1.z
|
|
57
|
+
.string()
|
|
58
|
+
.optional()
|
|
59
|
+
.describe("Figma API token (can be set via FIGMA_TOKEN env var)"),
|
|
60
|
+
assets: zod_1.z
|
|
61
|
+
.array(exports.FigmaAssetConfigSchema)
|
|
62
|
+
.default([])
|
|
63
|
+
.describe("Asset collections to download from Figma"),
|
|
64
|
+
});
|
|
65
|
+
/**
|
|
66
|
+
* Tokens Studio configuration schema
|
|
67
|
+
*/
|
|
68
|
+
exports.TokensStudioConfigSchema = zod_1.z.object({
|
|
69
|
+
input: zod_1.z
|
|
70
|
+
.string()
|
|
71
|
+
.default("./tokens")
|
|
72
|
+
.describe("Input directory containing token files"),
|
|
73
|
+
output: zod_1.z
|
|
74
|
+
.string()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("Default output file path for transformations"),
|
|
77
|
+
handler: zod_1.z
|
|
78
|
+
.string()
|
|
79
|
+
.optional()
|
|
80
|
+
.describe("Default path to the transformer handler file"),
|
|
81
|
+
});
|
|
82
|
+
/**
|
|
83
|
+
* Vercel configuration schema
|
|
84
|
+
*/
|
|
85
|
+
exports.VercelConfigSchema = zod_1.z.object({
|
|
86
|
+
team: zod_1.z.string().optional().describe("Vercel team name (organization)"),
|
|
87
|
+
project: zod_1.z.string().optional().describe("Vercel project name"),
|
|
88
|
+
});
|
|
89
|
+
/**
|
|
90
|
+
* Main IXT Scripts configuration schema
|
|
91
|
+
*/
|
|
92
|
+
exports.IxtConfigSchema = zod_1.z.object({
|
|
93
|
+
figma: exports.FigmaConfigSchema.optional().describe("Figma-related configuration"),
|
|
94
|
+
tokensStudio: exports.TokensStudioConfigSchema.optional().describe("Tokens Studio configuration"),
|
|
95
|
+
vercel: exports.VercelConfigSchema.optional().describe("Vercel deployment configuration"),
|
|
96
|
+
});
|
|
97
|
+
/**
|
|
98
|
+
* Helper function for type-safe configuration definition
|
|
99
|
+
* Provides IntelliSense and validation for the configuration object
|
|
100
|
+
*/
|
|
101
|
+
function defineConfig(config) {
|
|
102
|
+
return exports.IxtConfigSchema.parse(config);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Default configuration values
|
|
106
|
+
*/
|
|
107
|
+
const DEFAULT_CONFIG = {
|
|
108
|
+
figma: {
|
|
109
|
+
assets: [],
|
|
110
|
+
},
|
|
111
|
+
tokensStudio: {
|
|
112
|
+
input: "./tokens",
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Load configuration from a TypeScript file
|
|
117
|
+
* Only supports .ts configuration files for better type safety
|
|
118
|
+
*/
|
|
119
|
+
async function loadConfig(configPath) {
|
|
120
|
+
const possiblePaths = [configPath, "ixt.config.ts"].filter(Boolean);
|
|
121
|
+
for (const potentialPath of possiblePaths) {
|
|
122
|
+
if (!potentialPath)
|
|
123
|
+
continue;
|
|
124
|
+
const resolvedPath = path.isAbsolute(potentialPath)
|
|
125
|
+
? potentialPath
|
|
126
|
+
: path.join(process.cwd(), potentialPath);
|
|
127
|
+
try {
|
|
128
|
+
await fs.access(resolvedPath);
|
|
129
|
+
// Dynamic import for .ts files
|
|
130
|
+
const mod = await Promise.resolve(`${resolvedPath}`).then(s => __importStar(require(s)));
|
|
131
|
+
const config = mod.default || mod;
|
|
132
|
+
return exports.IxtConfigSchema.parse(config);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
// If the file doesn't exist, continue to next option
|
|
136
|
+
if (error.code === "ENOENT") {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
// Re-throw other errors (like parsing errors)
|
|
140
|
+
throw new Error(`Failed to load config from ${resolvedPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// No config file found, return default config
|
|
144
|
+
return DEFAULT_CONFIG;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Merge CLI arguments with configuration
|
|
148
|
+
* CLI arguments take precedence over config file values
|
|
149
|
+
*/
|
|
150
|
+
function mergeConfigWithArgs(config, args, section) {
|
|
151
|
+
const sectionConfig = config[section] || {};
|
|
152
|
+
// CLI args override config values
|
|
153
|
+
return {
|
|
154
|
+
...sectionConfig,
|
|
155
|
+
...Object.fromEntries(Object.entries(args).filter(([, value]) => value !== undefined)),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Validate that required values are present after merging config and args
|
|
160
|
+
*/
|
|
161
|
+
function validateRequiredConfig(mergedConfig, requiredFields) {
|
|
162
|
+
const missing = requiredFields.filter((field) => !mergedConfig[field]);
|
|
163
|
+
if (missing.length > 0) {
|
|
164
|
+
throw new Error(`Missing required configuration: ${missing.join(", ")}. ` +
|
|
165
|
+
"Please provide these in your config file or via CLI arguments.");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
type Color = {
|
|
4
|
+
r: number;
|
|
5
|
+
g: number;
|
|
6
|
+
b: number;
|
|
7
|
+
a: number;
|
|
8
|
+
};
|
|
9
|
+
type FileId = string;
|
|
10
|
+
type NodeId = string;
|
|
11
|
+
export type FigmaNode = ({
|
|
12
|
+
type: "RECTANGLE";
|
|
13
|
+
fills: {
|
|
14
|
+
type: "SOLID";
|
|
15
|
+
color: Color;
|
|
16
|
+
}[];
|
|
17
|
+
effects: {
|
|
18
|
+
type: "DROP_SHADOW";
|
|
19
|
+
offset: {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
};
|
|
23
|
+
color: Color;
|
|
24
|
+
radius: number;
|
|
25
|
+
}[];
|
|
26
|
+
} | {
|
|
27
|
+
type: "TEXT";
|
|
28
|
+
style: {
|
|
29
|
+
fontFamily: string;
|
|
30
|
+
fontWeight: number;
|
|
31
|
+
fontSize: number;
|
|
32
|
+
letterSpacing: number;
|
|
33
|
+
lineHeightPx: number;
|
|
34
|
+
};
|
|
35
|
+
}) & {
|
|
36
|
+
name: string;
|
|
37
|
+
id: string;
|
|
38
|
+
};
|
|
39
|
+
export declare const createAPI: (figmaToken: string) => {
|
|
40
|
+
file: {
|
|
41
|
+
fetch: (fileId: FileId) => Promise<any>;
|
|
42
|
+
};
|
|
43
|
+
nodes: {
|
|
44
|
+
fetch: (fileId: FileId, ids: NodeId[]) => Promise<any>;
|
|
45
|
+
};
|
|
46
|
+
styles: {
|
|
47
|
+
fetch: (fileId: FileId) => Promise<{
|
|
48
|
+
nodes: {
|
|
49
|
+
document: FigmaNode;
|
|
50
|
+
}[];
|
|
51
|
+
}>;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Given a root node, fetches all exports of the nodes in the tree.
|
|
55
|
+
* This is especially useful to fetch all icons or assets from a design system.
|
|
56
|
+
*/
|
|
57
|
+
images: {
|
|
58
|
+
fetch: (fileId: FileId, nodeIds: string[], { onFetchSources, onFetchImage, }?: {
|
|
59
|
+
onFetchSources?: ((sources: {
|
|
60
|
+
name: string;
|
|
61
|
+
format: string;
|
|
62
|
+
}[]) => void) | undefined;
|
|
63
|
+
onFetchImage?: ((url: string) => void) | undefined;
|
|
64
|
+
}) => Promise<{
|
|
65
|
+
data: string | Buffer;
|
|
66
|
+
name: string;
|
|
67
|
+
format: string;
|
|
68
|
+
}[]>;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
export {};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createAPI = void 0;
|
|
7
|
+
const bottleneck_1 = __importDefault(require("bottleneck"));
|
|
8
|
+
const remeda_1 = require("remeda");
|
|
9
|
+
const zod_1 = require("zod");
|
|
10
|
+
const ExportSetting = zod_1.z.object({
|
|
11
|
+
suffix: zod_1.z.string(),
|
|
12
|
+
format: zod_1.z.enum(["SVG", "PNG", "JPG"]),
|
|
13
|
+
constraint: zod_1.z.discriminatedUnion("type", [
|
|
14
|
+
zod_1.z.object({
|
|
15
|
+
type: zod_1.z.literal("SCALE"),
|
|
16
|
+
value: zod_1.z.number(),
|
|
17
|
+
}),
|
|
18
|
+
]),
|
|
19
|
+
});
|
|
20
|
+
const ExportSettings = zod_1.z.array(ExportSetting);
|
|
21
|
+
const ImageResp = zod_1.z.object({ images: zod_1.z.record(zod_1.z.string(), zod_1.z.string()) });
|
|
22
|
+
const textFetch = async (url) => fetch(url).then((res) => res.text());
|
|
23
|
+
const binaryFetch = async (url) => {
|
|
24
|
+
const arrayBuffer = await fetch(url).then((res) => res.arrayBuffer());
|
|
25
|
+
return Buffer.from(arrayBuffer);
|
|
26
|
+
};
|
|
27
|
+
const formatToFetch = {
|
|
28
|
+
SVG: textFetch,
|
|
29
|
+
PNG: binaryFetch,
|
|
30
|
+
JPG: binaryFetch,
|
|
31
|
+
};
|
|
32
|
+
const collectChildren = (nodes) => {
|
|
33
|
+
return nodes.flatMap((node) => {
|
|
34
|
+
var _a;
|
|
35
|
+
return [node, ...collectChildren((_a = node.children) !== null && _a !== void 0 ? _a : [])];
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* @see https://forum.figma.com/archive-21/rest-api-rate-limit-15957
|
|
40
|
+
*/
|
|
41
|
+
const requestsPerMinute = {
|
|
42
|
+
FILE: 120,
|
|
43
|
+
IMAGE: 30,
|
|
44
|
+
VERSION: 60,
|
|
45
|
+
COMMENT: 300,
|
|
46
|
+
WEBHOOK: 300,
|
|
47
|
+
TEAM: 300,
|
|
48
|
+
PROJECT: 300,
|
|
49
|
+
FILE_IMAGE: 300,
|
|
50
|
+
SELECTION: 300,
|
|
51
|
+
RECENT_FILES: 600,
|
|
52
|
+
};
|
|
53
|
+
const getLimiter = (type) => {
|
|
54
|
+
return new bottleneck_1.default({
|
|
55
|
+
minTime: (60 / requestsPerMinute[type]) * 1000,
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
const createAPI = (figmaToken) => {
|
|
59
|
+
const headers = {
|
|
60
|
+
"X-Figma-Token": figmaToken,
|
|
61
|
+
};
|
|
62
|
+
const baseURL = `https://api.figma.com/v1`;
|
|
63
|
+
const fetchOptions = {
|
|
64
|
+
headers: {
|
|
65
|
+
Accept: "application/json",
|
|
66
|
+
...headers,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
const fetchAPI = async (resource) => {
|
|
70
|
+
const res = await fetch(`${baseURL}${resource}`, fetchOptions);
|
|
71
|
+
if (res.status > 299) {
|
|
72
|
+
const text = await res.text();
|
|
73
|
+
throw new Error(text);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const data = await res.json();
|
|
77
|
+
return data;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const api = {
|
|
81
|
+
file: {
|
|
82
|
+
fetch: (fileId) => {
|
|
83
|
+
return fetchAPI(`/files/${fileId}`);
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
nodes: {
|
|
87
|
+
fetch: (fileId, ids) => {
|
|
88
|
+
return fetchAPI(`/files/${fileId}/nodes?ids=${ids.join(",")}`);
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
styles: {
|
|
92
|
+
fetch: async (fileId) => {
|
|
93
|
+
const resp = await fetchAPI(`/files/${fileId}/styles`);
|
|
94
|
+
const { meta: { styles }, } = resp;
|
|
95
|
+
const nodeIds = styles.map((x) => x.node_id);
|
|
96
|
+
return api.nodes.fetch(fileId, nodeIds);
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
/**
|
|
100
|
+
* Given a root node, fetches all exports of the nodes in the tree.
|
|
101
|
+
* This is especially useful to fetch all icons or assets from a design system.
|
|
102
|
+
*/
|
|
103
|
+
images: {
|
|
104
|
+
fetch: async (fileId, nodeIds, { onFetchSources, onFetchImage, } = {}) => {
|
|
105
|
+
const { nodes } = await fetchAPI(`/files/${fileId}/nodes?ids=${nodeIds.join(",")}`);
|
|
106
|
+
const roots = nodeIds.flatMap((id) => nodes[id.replace("-", ":")].document.children);
|
|
107
|
+
const allChildren = collectChildren(roots);
|
|
108
|
+
// Collect all export settings from the children
|
|
109
|
+
const sources = allChildren.flatMap((n) => {
|
|
110
|
+
if (n.exportSettings) {
|
|
111
|
+
return ExportSettings.parse(n.exportSettings).map((setting) => ({
|
|
112
|
+
id: n.id,
|
|
113
|
+
name: n.name,
|
|
114
|
+
...setting,
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
if (sources.length === 0) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
const bottleneck = getLimiter("IMAGE");
|
|
125
|
+
const sourcesByFormatByContraint = (0, remeda_1.groupBy)(sources, (x) => { var _a, _b; return `${x.format}/${(_b = (_a = x.constraint) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : 1}`; });
|
|
126
|
+
// To fetch all the images, we first need to collect all downloadable
|
|
127
|
+
// URLs for the images, then download them.
|
|
128
|
+
// The api/v1/images call gets downloadable URLs. It needs to be called
|
|
129
|
+
// per format and per scale.
|
|
130
|
+
const sourceWithUrls = (await Promise.all(Object.keys(sourcesByFormatByContraint).map(async (formatAndConstraint) => {
|
|
131
|
+
var _a, _b;
|
|
132
|
+
const sources = sourcesByFormatByContraint[formatAndConstraint];
|
|
133
|
+
const format = sources[0].format;
|
|
134
|
+
const scale = (_b = (_a = sources[0].constraint) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : 1;
|
|
135
|
+
const ids = sources.map((x) => x.id).join(",");
|
|
136
|
+
const imageResp = await bottleneck
|
|
137
|
+
.schedule(() => fetch(`https://api.figma.com/v1/images/${fileId}?ids=${ids}&format=${format.toLowerCase()}&scale=${scale}`, fetchOptions))
|
|
138
|
+
.then((res) => res.json())
|
|
139
|
+
.then((x) => {
|
|
140
|
+
try {
|
|
141
|
+
return ImageResp.parse(x);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return { images: {} };
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
// onFetchImage?.(imageResp.images);
|
|
148
|
+
const { images } = imageResp;
|
|
149
|
+
return sources.map((source) => {
|
|
150
|
+
return {
|
|
151
|
+
...source,
|
|
152
|
+
url: images[source.id],
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
})))
|
|
156
|
+
.flat()
|
|
157
|
+
.filter((x) => x.url);
|
|
158
|
+
onFetchSources === null || onFetchSources === void 0 ? void 0 : onFetchSources(sourceWithUrls);
|
|
159
|
+
// All URLs are now collected, we can download them
|
|
160
|
+
return await Promise.all(sourceWithUrls.map(async ({ name, format, url }) => {
|
|
161
|
+
return {
|
|
162
|
+
data: await formatToFetch[format](url).then((x) => {
|
|
163
|
+
onFetchImage === null || onFetchImage === void 0 ? void 0 : onFetchImage(url);
|
|
164
|
+
return x;
|
|
165
|
+
}),
|
|
166
|
+
name,
|
|
167
|
+
format: format.toLowerCase(),
|
|
168
|
+
};
|
|
169
|
+
}));
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
return api;
|
|
174
|
+
};
|
|
175
|
+
exports.createAPI = createAPI;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This script downloads the assets from Figma and saves them into the repository.
|
|
3
|
+
* It is configured via the unified ixt configuration system.
|
|
4
|
+
*
|
|
5
|
+
* @example ixt.config.js
|
|
6
|
+
* import { defineConfig } from '@interactivethings/scripts';
|
|
7
|
+
*
|
|
8
|
+
* export default defineConfig({
|
|
9
|
+
* figma: {
|
|
10
|
+
* assets: [{
|
|
11
|
+
* name: "illustrations",
|
|
12
|
+
* url: "https://www.figma.com/design/ElWWZIcOGFhiT06rzfIwRO/Design-System?node-id=11861-10071",
|
|
13
|
+
* output: "src/assets/illustrations"
|
|
14
|
+
* }]
|
|
15
|
+
* }
|
|
16
|
+
* });
|
|
17
|
+
*/
|
|
18
|
+
import { ArgumentParser } from "argparse";
|
|
19
|
+
export declare const configParser: (parser: ArgumentParser) => void;
|
|
20
|
+
export declare const run: (parser: ArgumentParser) => Promise<void>;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* This script downloads the assets from Figma and saves them into the repository.
|
|
4
|
+
* It is configured via the unified ixt configuration system.
|
|
5
|
+
*
|
|
6
|
+
* @example ixt.config.js
|
|
7
|
+
* import { defineConfig } from '@interactivethings/scripts';
|
|
8
|
+
*
|
|
9
|
+
* export default defineConfig({
|
|
10
|
+
* figma: {
|
|
11
|
+
* assets: [{
|
|
12
|
+
* name: "illustrations",
|
|
13
|
+
* url: "https://www.figma.com/design/ElWWZIcOGFhiT06rzfIwRO/Design-System?node-id=11861-10071",
|
|
14
|
+
* output: "src/assets/illustrations"
|
|
15
|
+
* }]
|
|
16
|
+
* }
|
|
17
|
+
* });
|
|
18
|
+
*/
|
|
19
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
22
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
23
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
24
|
+
}
|
|
25
|
+
Object.defineProperty(o, k2, desc);
|
|
26
|
+
}) : (function(o, m, k, k2) {
|
|
27
|
+
if (k2 === undefined) k2 = k;
|
|
28
|
+
o[k2] = m[k];
|
|
29
|
+
}));
|
|
30
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
31
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
32
|
+
}) : function(o, v) {
|
|
33
|
+
o["default"] = v;
|
|
34
|
+
});
|
|
35
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
36
|
+
var ownKeys = function(o) {
|
|
37
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
38
|
+
var ar = [];
|
|
39
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
40
|
+
return ar;
|
|
41
|
+
};
|
|
42
|
+
return ownKeys(o);
|
|
43
|
+
};
|
|
44
|
+
return function (mod) {
|
|
45
|
+
if (mod && mod.__esModule) return mod;
|
|
46
|
+
var result = {};
|
|
47
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
48
|
+
__setModuleDefault(result, mod);
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
51
|
+
})();
|
|
52
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
53
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
54
|
+
};
|
|
55
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
+
exports.run = exports.configParser = void 0;
|
|
57
|
+
const fs = __importStar(require("fs/promises"));
|
|
58
|
+
const path = __importStar(require("path"));
|
|
59
|
+
const argparse_1 = require("argparse");
|
|
60
|
+
const ora_1 = __importDefault(require("ora"));
|
|
61
|
+
const api_1 = require("./api");
|
|
62
|
+
const utils_1 = require("./utils");
|
|
63
|
+
const config_1 = require("../config");
|
|
64
|
+
const images_1 = require("./images");
|
|
65
|
+
const configParser = (parser) => {
|
|
66
|
+
parser.add_argument("--token", {
|
|
67
|
+
help: "Figma API token (can also use FIGMA_TOKEN env var)",
|
|
68
|
+
});
|
|
69
|
+
parser.add_argument("--config", {
|
|
70
|
+
help: "Path to TypeScript config file (default: looks for ixt.config.ts in current directory)",
|
|
71
|
+
});
|
|
72
|
+
const commands = parser.add_subparsers({
|
|
73
|
+
title: "commands",
|
|
74
|
+
dest: "subcommand",
|
|
75
|
+
});
|
|
76
|
+
const downloadParser = commands.add_parser("download", {
|
|
77
|
+
help: "Download assets from Figma",
|
|
78
|
+
});
|
|
79
|
+
downloadParser.add_argument("name", {
|
|
80
|
+
help: "Pass this to only download assets for a specific name defined in the config",
|
|
81
|
+
});
|
|
82
|
+
const getParser = commands.add_parser("get", {
|
|
83
|
+
help: "Get the Figma node for a given URL",
|
|
84
|
+
});
|
|
85
|
+
getParser.add_argument("url", {
|
|
86
|
+
help: "The Figma URL to fetch the node from",
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
exports.configParser = configParser;
|
|
90
|
+
const run = async (args) => {
|
|
91
|
+
// Load configuration
|
|
92
|
+
const config = await (0, config_1.loadConfig)(args.config);
|
|
93
|
+
// Merge CLI args with config, giving precedence to CLI args
|
|
94
|
+
const mergedConfig = (0, config_1.mergeConfigWithArgs)(config, {
|
|
95
|
+
token: args.token || process.env.FIGMA_TOKEN,
|
|
96
|
+
}, "figma");
|
|
97
|
+
// Validate required configuration
|
|
98
|
+
(0, config_1.validateRequiredConfig)(mergedConfig, ["token"]);
|
|
99
|
+
const api = (0, api_1.createAPI)(mergedConfig.token);
|
|
100
|
+
const assets = mergedConfig.assets || [];
|
|
101
|
+
if (args.subcommand === "download") {
|
|
102
|
+
if (assets.length === 0) {
|
|
103
|
+
throw new Error("No figma assets configured. Please add assets to your config file.");
|
|
104
|
+
}
|
|
105
|
+
const asset = assets.find((a) => a.name === args.name);
|
|
106
|
+
if (!asset) {
|
|
107
|
+
throw new Error(`No asset configuration found for name "${args.name}". Please check your config file. Available assets: ${assets
|
|
108
|
+
.map((a) => a.name)
|
|
109
|
+
.join(", ")}`);
|
|
110
|
+
}
|
|
111
|
+
const { url, output, name } = asset;
|
|
112
|
+
console.log("Downloading assets with config:", url);
|
|
113
|
+
const { figmaFileId, figmaPageId } = (0, utils_1.parseFigmaURL)(url);
|
|
114
|
+
console.log(`Reading assets for "${name}" at ${(0, utils_1.formatFigmaURL)(figmaFileId, figmaPageId)}`);
|
|
115
|
+
const spinner = (0, ora_1.default)("Fetching images").start();
|
|
116
|
+
let total = -1;
|
|
117
|
+
let fetched = 0;
|
|
118
|
+
const images = await api.images.fetch(figmaFileId, [figmaPageId], {
|
|
119
|
+
onFetchSources: (sources) => {
|
|
120
|
+
total = sources.length;
|
|
121
|
+
spinner.text = `Fetching ${total} image(s)`;
|
|
122
|
+
spinner.render();
|
|
123
|
+
},
|
|
124
|
+
onFetchImage: (_url) => {
|
|
125
|
+
fetched += 1;
|
|
126
|
+
spinner.text = `Fetched ${fetched}/${total} image(s)`;
|
|
127
|
+
spinner.render();
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
spinner.stop();
|
|
131
|
+
console.log(`${images.length} asset(s) downloaded`);
|
|
132
|
+
for (const im of images) {
|
|
133
|
+
const filename = `${im.name}.${im.format}`;
|
|
134
|
+
console.log(`Downloaded ${filename}`);
|
|
135
|
+
const outputFile = path.join(output, filename);
|
|
136
|
+
const outputDir = path.dirname(outputFile);
|
|
137
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
138
|
+
await fs.writeFile(outputFile, im.data);
|
|
139
|
+
await (0, images_1.optimizeImage)(outputFile);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if (args.subcommand === "get") {
|
|
143
|
+
const { figmaFileId, figmaPageId } = (0, utils_1.parseFigmaURL)(args.url);
|
|
144
|
+
const node = await api.nodes.fetch(figmaFileId, [figmaPageId]);
|
|
145
|
+
console.log(JSON.stringify(node, null, 2));
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
exports.run = run;
|
|
149
|
+
const main = async () => {
|
|
150
|
+
const parser = new argparse_1.ArgumentParser({
|
|
151
|
+
description: "Download assets from Figma",
|
|
152
|
+
});
|
|
153
|
+
(0, exports.configParser)(parser);
|
|
154
|
+
const args = parser.parse_args();
|
|
155
|
+
await (0, exports.run)(args);
|
|
156
|
+
};
|
|
157
|
+
// Only run main if this file is being executed directly
|
|
158
|
+
if (require.main === module) {
|
|
159
|
+
main().catch((err) => {
|
|
160
|
+
console.error(err);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
});
|
|
163
|
+
}
|