@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/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
+ }