@sit-onyx/figma-utils 1.0.0-beta.4 → 1.0.0-beta.5
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 +1 -5
- package/dist/cli.js +2 -1
- package/dist/commands/import-flags.d.ts +13 -0
- package/dist/commands/import-flags.js +42 -0
- package/dist/commands/import-icons.js +2 -5
- package/dist/flags/generate.d.ts +8 -0
- package/dist/flags/generate.js +18 -0
- package/dist/flags/parse.d.ts +12 -0
- package/dist/flags/parse.js +34 -0
- package/dist/icons/generate.js +3 -8
- package/dist/icons/parse.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/types/figma.d.ts +7 -0
- package/dist/utils/fs.js +1 -1
- package/dist/{icons → utils}/optimize.d.ts +2 -2
- package/dist/utils/optimize.js +25 -0
- package/dist/variables/parse.js +3 -1
- package/package.json +3 -3
- package/dist/icons/optimize.js +0 -25
package/README.md
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
<div align="center" style="text-align: center">
|
|
2
|
-
<
|
|
3
|
-
<source media="(prefers-color-scheme: dark)" type="image/svg+xml" srcset="https://raw.githubusercontent.com/SchwarzIT/onyx/main/.github/onyx-logo-light.svg">
|
|
4
|
-
<source media="(prefers-color-scheme: light)" type="image/svg+xml" srcset="https://raw.githubusercontent.com/SchwarzIT/onyx/main/.github/onyx-logo-dark.svg">
|
|
5
|
-
<img alt="onyx logo" src="https://raw.githubusercontent.com/SchwarzIT/onyx/main/.github/onyx-logo-dark.svg" width="160px">
|
|
6
|
-
</picture>
|
|
2
|
+
<img alt="onyx logo" src="https://raw.githubusercontent.com/SchwarzIT/onyx/main/.github/onyx-logo.svg" height="96px">
|
|
7
3
|
</div>
|
|
8
4
|
|
|
9
5
|
<br>
|
package/dist/cli.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import { fileURLToPath, URL } from "node:url";
|
|
5
|
+
import { importFlagsCommand } from "./commands/import-flags.js";
|
|
5
6
|
import { importIconsCommand } from "./commands/import-icons.js";
|
|
6
7
|
import { importVariablesCommand } from "./commands/import-variables.js";
|
|
7
8
|
const packageJson = JSON.parse(fs.readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
|
|
8
9
|
const cli = new Command();
|
|
9
10
|
cli.version(packageJson.version, "-v, --version").description(packageJson.description);
|
|
10
|
-
const availableCommands = [importVariablesCommand, importIconsCommand];
|
|
11
|
+
const availableCommands = [importVariablesCommand, importIconsCommand, importFlagsCommand];
|
|
11
12
|
availableCommands.forEach((command) => cli.addCommand(command));
|
|
12
13
|
cli.parse();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
export type ImportFlagsCommandOptions = {
|
|
3
|
+
fileKey: string;
|
|
4
|
+
token: string;
|
|
5
|
+
pageId: string;
|
|
6
|
+
dir?: string;
|
|
7
|
+
metaFile?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const importFlagsCommand: Command;
|
|
10
|
+
/**
|
|
11
|
+
* Action to run when executing the import action. Only intended to be called manually for testing.
|
|
12
|
+
*/
|
|
13
|
+
export declare function importFlagsCommandAction(options: ImportFlagsCommandOptions): Promise<void>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { writeFlagMetadata } from "../flags/generate.js";
|
|
5
|
+
import { parseComponentsToFlags } from "../flags/parse.js";
|
|
6
|
+
import { fetchFigmaComponents, fetchFigmaSVGs } from "../index.js";
|
|
7
|
+
import { optimizeSvg } from "../utils/optimize.js";
|
|
8
|
+
export const importFlagsCommand = new Command("import-flags")
|
|
9
|
+
.description("CLI tool to import SVG flags from Figma.")
|
|
10
|
+
.requiredOption("-k, --file-key <string>", "Figma file key (required)")
|
|
11
|
+
.requiredOption("-t, --token <string>", "Figma access token with scope `file_read` or `files:read` (required)")
|
|
12
|
+
.requiredOption("-p, --page-id <string>", "Figma page ID that contains the flags (required)")
|
|
13
|
+
.option("-d, --dir <string>", "Directory to save the flags to. Defaults to current working directory of the script.")
|
|
14
|
+
.option("-m, --meta-file <string>", 'JSON filename/path to write flag metadata to (country name etc.). Must end with ".json". If unset, no metadata will be generated.')
|
|
15
|
+
.action(importFlagsCommandAction);
|
|
16
|
+
/**
|
|
17
|
+
* Action to run when executing the import action. Only intended to be called manually for testing.
|
|
18
|
+
*/
|
|
19
|
+
export async function importFlagsCommandAction(options) {
|
|
20
|
+
console.log("Fetching components from Figma API...");
|
|
21
|
+
const data = await fetchFigmaComponents(options.fileKey, options.token);
|
|
22
|
+
console.log("Parsing Figma flags...");
|
|
23
|
+
const parsedFlags = parseComponentsToFlags({
|
|
24
|
+
components: data.meta.components,
|
|
25
|
+
pageId: options.pageId,
|
|
26
|
+
});
|
|
27
|
+
const outputDirectory = options.dir ?? process.cwd();
|
|
28
|
+
console.log(`Fetching SVG content for ${parsedFlags.length} flags...`);
|
|
29
|
+
const svgContents = await fetchFigmaSVGs(options.fileKey, parsedFlags.map(({ id }) => id), options.token);
|
|
30
|
+
console.log("Optimizing and writing flag files...");
|
|
31
|
+
await mkdir(outputDirectory, { recursive: true });
|
|
32
|
+
await Promise.all(parsedFlags.map((flag) => {
|
|
33
|
+
const content = optimizeSvg(svgContents[flag.id], "image");
|
|
34
|
+
const fullPath = path.join(outputDirectory, `${flag.code}.svg`);
|
|
35
|
+
return writeFile(fullPath, content, "utf-8");
|
|
36
|
+
}));
|
|
37
|
+
if (options.metaFile) {
|
|
38
|
+
console.log("Writing flag metadata...");
|
|
39
|
+
await writeFlagMetadata(options.metaFile, parsedFlags);
|
|
40
|
+
}
|
|
41
|
+
console.log("Done.");
|
|
42
|
+
}
|
|
@@ -2,10 +2,9 @@ import { Command } from "commander";
|
|
|
2
2
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { writeIconMetadata } from "../icons/generate.js";
|
|
5
|
-
import { optimizeSvg } from "../icons/optimize.js";
|
|
6
5
|
import { parseComponentsToIcons } from "../icons/parse.js";
|
|
7
6
|
import { fetchFigmaComponents, fetchFigmaSVGs } from "../index.js";
|
|
8
|
-
import {
|
|
7
|
+
import { optimizeSvg } from "../utils/optimize.js";
|
|
9
8
|
export const importIconsCommand = new Command("import-icons")
|
|
10
9
|
.description("CLI tool to import SVG icons from Figma.")
|
|
11
10
|
.requiredOption("-k, --file-key <string>", "Figma file key (required)")
|
|
@@ -31,9 +30,7 @@ export async function importIconsCommandAction(options) {
|
|
|
31
30
|
console.log(`Fetching SVG content for ${parsedIcons.length} icons...`);
|
|
32
31
|
const svgContents = await fetchFigmaSVGs(options.fileKey, parsedIcons.map(({ id }) => id), options.token);
|
|
33
32
|
console.log("Optimizing and writing icon files...");
|
|
34
|
-
|
|
35
|
-
await mkdir(outputDirectory, { recursive: true });
|
|
36
|
-
}
|
|
33
|
+
await mkdir(outputDirectory, { recursive: true });
|
|
37
34
|
await Promise.all(parsedIcons.map((icon) => {
|
|
38
35
|
const content = optimizeSvg(svgContents[icon.id]);
|
|
39
36
|
const fullPath = path.join(outputDirectory, `${icon.name}.svg`);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ParsedFlag } from "../types/figma.js";
|
|
2
|
+
/**
|
|
3
|
+
* Writes a JSON file with metadata of the given flags (code, name etc.).
|
|
4
|
+
*
|
|
5
|
+
* @param path File path of the .json file, e.g. "./metadata.json"
|
|
6
|
+
* @param flags Flags to write metadata for
|
|
7
|
+
*/
|
|
8
|
+
export declare const writeFlagMetadata: (path: string, flags: ParsedFlag[]) => Promise<void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Writes a JSON file with metadata of the given flags (code, name etc.).
|
|
5
|
+
*
|
|
6
|
+
* @param path File path of the .json file, e.g. "./metadata.json"
|
|
7
|
+
* @param flags Flags to write metadata for
|
|
8
|
+
*/
|
|
9
|
+
export const writeFlagMetadata = async (path, flags) => {
|
|
10
|
+
const metaDirname = dirname(path);
|
|
11
|
+
await mkdir(metaDirname, { recursive: true });
|
|
12
|
+
const flagMetadata = flags.reduce((meta, flag) => {
|
|
13
|
+
const { id: _id, code, ...rest } = flag;
|
|
14
|
+
meta[code] = rest;
|
|
15
|
+
return meta;
|
|
16
|
+
}, {});
|
|
17
|
+
await writeFile(path, JSON.stringify(flagMetadata, null, 2), "utf-8");
|
|
18
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Component, ParsedFlag } from "../types/figma.js";
|
|
2
|
+
export type ParseFlagComponentsOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* Available Figma components.
|
|
5
|
+
*/
|
|
6
|
+
components: Component[];
|
|
7
|
+
/**
|
|
8
|
+
* Page ID that contains all flags. Components will be filtered accordingly.
|
|
9
|
+
*/
|
|
10
|
+
pageId: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const parseComponentsToFlags: (options: ParseFlagComponentsOptions) => ParsedFlag[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map of country names for country codes that are not (yet) supported by `Intl.DisplayNames`.
|
|
3
|
+
*/
|
|
4
|
+
const UNKNOWN_COUNTRY_NAMES = {
|
|
5
|
+
"CA-BC": "British Columbia",
|
|
6
|
+
"GB-ENG": "England",
|
|
7
|
+
"GB-SCT": "Scotland",
|
|
8
|
+
"GB-WLS": "Wales",
|
|
9
|
+
"US-HI": "Hawaii",
|
|
10
|
+
};
|
|
11
|
+
export const parseComponentsToFlags = (options) => {
|
|
12
|
+
const pageComponents = options.components.filter(({ containing_frame }) => containing_frame.pageId === options.pageId);
|
|
13
|
+
const countryCodeFormatter = new Intl.DisplayNames("en", { type: "region" });
|
|
14
|
+
return (pageComponents
|
|
15
|
+
.map((component) => {
|
|
16
|
+
const code = component.description.trim();
|
|
17
|
+
let internationalName = UNKNOWN_COUNTRY_NAMES[code] ?? "";
|
|
18
|
+
try {
|
|
19
|
+
internationalName = countryCodeFormatter.of(code) || internationalName;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// noop
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
id: component.node_id,
|
|
26
|
+
code,
|
|
27
|
+
continent: component.containing_frame.name.trim(),
|
|
28
|
+
internationalName,
|
|
29
|
+
};
|
|
30
|
+
})
|
|
31
|
+
// remove invalid flags without a country code
|
|
32
|
+
.filter(({ code }) => code)
|
|
33
|
+
.sort((a, b) => a.code.localeCompare(b.code)));
|
|
34
|
+
};
|
package/dist/icons/generate.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
-
import { isDirectory } from "../utils/fs.js";
|
|
4
3
|
/**
|
|
5
4
|
* Writes a JSON file with metadata of the given icons (category, aliases etc.).
|
|
6
5
|
*
|
|
@@ -9,14 +8,10 @@ import { isDirectory } from "../utils/fs.js";
|
|
|
9
8
|
*/
|
|
10
9
|
export const writeIconMetadata = async (path, icons) => {
|
|
11
10
|
const metaDirname = dirname(path);
|
|
12
|
-
|
|
13
|
-
await mkdir(metaDirname, { recursive: true });
|
|
14
|
-
}
|
|
11
|
+
await mkdir(metaDirname, { recursive: true });
|
|
15
12
|
const iconMetadata = icons.reduce((meta, icon) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
aliases: icon.aliases,
|
|
19
|
-
};
|
|
13
|
+
const { id: _id, name, ...rest } = icon;
|
|
14
|
+
meta[name] = rest;
|
|
20
15
|
return meta;
|
|
21
16
|
}, {});
|
|
22
17
|
await writeFile(path, JSON.stringify(iconMetadata, null, 2), "utf-8");
|
package/dist/icons/parse.js
CHANGED
|
@@ -5,11 +5,11 @@ export const parseComponentsToIcons = (options) => {
|
|
|
5
5
|
return {
|
|
6
6
|
id: component.node_id,
|
|
7
7
|
name: component.name,
|
|
8
|
+
category: component.containing_frame.name.trim(),
|
|
8
9
|
aliases: component.description
|
|
9
10
|
.split(options.aliasSeparator ?? ",")
|
|
10
11
|
.map((alias) => alias.trim())
|
|
11
12
|
.filter((i) => i !== ""),
|
|
12
|
-
category: component.containing_frame.name.trim(),
|
|
13
13
|
};
|
|
14
14
|
})
|
|
15
15
|
.sort((a, b) => a.name.localeCompare(b.name));
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
export * from "./flags/generate.js";
|
|
2
|
+
export * from "./flags/parse.js";
|
|
1
3
|
export * from "./icons/generate.js";
|
|
2
|
-
export * from "./icons/optimize.js";
|
|
3
4
|
export * from "./icons/parse.js";
|
|
4
5
|
export * from "./types/figma.js";
|
|
5
6
|
export * from "./utils/fetch.js";
|
|
7
|
+
export * from "./utils/optimize.js";
|
|
6
8
|
export * from "./variables/generate.js";
|
|
7
9
|
export * from "./variables/parse.js";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
export * from "./flags/generate.js";
|
|
2
|
+
export * from "./flags/parse.js";
|
|
1
3
|
export * from "./icons/generate.js";
|
|
2
|
-
export * from "./icons/optimize.js";
|
|
3
4
|
export * from "./icons/parse.js";
|
|
4
5
|
export * from "./types/figma.js";
|
|
5
6
|
export * from "./utils/fetch.js";
|
|
7
|
+
export * from "./utils/optimize.js";
|
|
6
8
|
export * from "./variables/generate.js";
|
|
7
9
|
export * from "./variables/parse.js";
|
package/dist/types/figma.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export type Variable = {
|
|
|
20
20
|
name: string;
|
|
21
21
|
variableCollectionId: string;
|
|
22
22
|
hiddenFromPublishing: boolean;
|
|
23
|
+
deletedButReferenced?: boolean;
|
|
23
24
|
valuesByMode: Record<string, VariableValue>;
|
|
24
25
|
};
|
|
25
26
|
export type VariableValue = RGBAValue | ColorsAlias | number;
|
|
@@ -104,3 +105,9 @@ export type ParsedIcon = {
|
|
|
104
105
|
aliases: string[];
|
|
105
106
|
category: string;
|
|
106
107
|
};
|
|
108
|
+
export type ParsedFlag = {
|
|
109
|
+
id: string;
|
|
110
|
+
code: string;
|
|
111
|
+
internationalName: string;
|
|
112
|
+
continent: string;
|
|
113
|
+
};
|
package/dist/utils/fs.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Optimizes the given SVG content for usage inside an icon library using [svgo](https://svgo.dev).
|
|
3
3
|
* Will apply the following optimizations:
|
|
4
|
-
* - remove all fills so the color can be set via CSS
|
|
5
4
|
* - remove dimensions (height/width) so it can be set via CSS
|
|
6
5
|
* - "preset-default" to reduce file size and redundant information
|
|
6
|
+
* - (only if type "icon"): remove all fills so the color can be set via CSS
|
|
7
7
|
*/
|
|
8
|
-
export declare const optimizeSvg: (svgContent: string) => string;
|
|
8
|
+
export declare const optimizeSvg: (svgContent: string, type?: "icon" | "image") => string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { optimize } from "svgo";
|
|
2
|
+
/**
|
|
3
|
+
* Optimizes the given SVG content for usage inside an icon library using [svgo](https://svgo.dev).
|
|
4
|
+
* Will apply the following optimizations:
|
|
5
|
+
* - remove dimensions (height/width) so it can be set via CSS
|
|
6
|
+
* - "preset-default" to reduce file size and redundant information
|
|
7
|
+
* - (only if type "icon"): remove all fills so the color can be set via CSS
|
|
8
|
+
*/
|
|
9
|
+
export const optimizeSvg = (svgContent, type = "icon") => {
|
|
10
|
+
const plugins = [{ name: "preset-default" }, { name: "removeDimensions" }];
|
|
11
|
+
if (type === "icon") {
|
|
12
|
+
plugins.push({
|
|
13
|
+
name: "removeAttrs",
|
|
14
|
+
params: {
|
|
15
|
+
// remove all fills so we can set the color via CSS
|
|
16
|
+
attrs: ["fill"],
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const { data } = optimize(svgContent, {
|
|
21
|
+
multipass: true,
|
|
22
|
+
plugins,
|
|
23
|
+
});
|
|
24
|
+
return data;
|
|
25
|
+
};
|
package/dist/variables/parse.js
CHANGED
|
@@ -14,7 +14,9 @@ export const parseFigmaVariables = (apiResponse, options) => {
|
|
|
14
14
|
*/
|
|
15
15
|
Object.values(apiResponse.meta.variables).forEach((variable) => {
|
|
16
16
|
const collection = apiResponse.meta.variableCollections[variable.variableCollectionId];
|
|
17
|
-
if (variable.hiddenFromPublishing ||
|
|
17
|
+
if (variable.hiddenFromPublishing ||
|
|
18
|
+
variable.deletedButReferenced ||
|
|
19
|
+
collection.hiddenFromPublishing)
|
|
18
20
|
return;
|
|
19
21
|
// parse variable value for every mode
|
|
20
22
|
Object.values(collection.modes).forEach((mode) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sit-onyx/figma-utils",
|
|
3
3
|
"description": "Utility functions and CLI for importing data from the Figma API into different formats (e.g. CSS, SCSS etc.)",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"@sit-onyx/figma-utils": "./dist/cli.js"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"dist"
|
|
11
11
|
],
|
|
12
12
|
"engines": {
|
|
13
|
-
"node": ">=
|
|
13
|
+
"node": ">=20"
|
|
14
14
|
},
|
|
15
15
|
"types": "./dist/index.d.ts",
|
|
16
16
|
"exports": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"url": "https://github.com/SchwarzIT/onyx/issues"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"commander": "^
|
|
32
|
+
"commander": "^14.0.0"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
35
|
"svgo": "^3.3.2"
|
package/dist/icons/optimize.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { optimize } from "svgo";
|
|
2
|
-
/**
|
|
3
|
-
* Optimizes the given SVG content for usage inside an icon library using [svgo](https://svgo.dev).
|
|
4
|
-
* Will apply the following optimizations:
|
|
5
|
-
* - remove all fills so the color can be set via CSS
|
|
6
|
-
* - remove dimensions (height/width) so it can be set via CSS
|
|
7
|
-
* - "preset-default" to reduce file size and redundant information
|
|
8
|
-
*/
|
|
9
|
-
export const optimizeSvg = (svgContent) => {
|
|
10
|
-
const { data } = optimize(svgContent, {
|
|
11
|
-
multipass: true,
|
|
12
|
-
plugins: [
|
|
13
|
-
{ name: "preset-default" },
|
|
14
|
-
{ name: "removeDimensions" },
|
|
15
|
-
{
|
|
16
|
-
name: "removeAttrs",
|
|
17
|
-
params: {
|
|
18
|
-
// remove all fills so we can set the color via CSS
|
|
19
|
-
attrs: ["fill"],
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
],
|
|
23
|
-
});
|
|
24
|
-
return data;
|
|
25
|
-
};
|