@charcoal-ui/icons-cli 5.8.1 → 5.9.0-beta.1
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/dist/index.js +74 -23
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/GitHubClient.ts +1 -1
- package/src/GitlabClient.ts +1 -1
- package/src/codegen.test.ts +86 -0
- package/src/codegen.ts +28 -0
- package/src/figma/FigmaFileClient.test.ts +218 -0
- package/src/figma/FigmaFileClient.ts +96 -27
- package/src/generateSource.test.ts +68 -0
- package/src/generateSource.ts +17 -8
- package/src/getChangedFiles.ts +15 -10
- package/src/index.ts +10 -1
- package/src/utils.ts +10 -0
- package/src/v2/transformCSS.test.ts +113 -0
- package/src/v2/transformCSS.ts +52 -67
- package/src/v2/transformDataUri.test.ts +97 -0
- package/src/v2/transformDataUri.ts +19 -25
package/dist/index.js
CHANGED
|
@@ -63,7 +63,7 @@ function concurrently(tasks) {
|
|
|
63
63
|
//#endregion
|
|
64
64
|
//#region src/figma/FigmaFileClient.ts
|
|
65
65
|
const DRY_RUN = Boolean(process.env.DRY_RUN);
|
|
66
|
-
const matchPath = (0, path_to_regexp.match)("/file/:fileId/:name");
|
|
66
|
+
const matchPath = (0, path_to_regexp.match)(["/file/:fileId/:name", "/design/:fileId/:name"]);
|
|
67
67
|
function extractParams(url) {
|
|
68
68
|
const { pathname, searchParams } = new URL(url);
|
|
69
69
|
const result = matchPath(pathname);
|
|
@@ -83,27 +83,42 @@ function isIconNode(node) {
|
|
|
83
83
|
function parseV2IconName(name) {
|
|
84
84
|
return name.split(",").map((f) => f.split("=").map((s) => s.trim())[1]).join("/").toLowerCase();
|
|
85
85
|
}
|
|
86
|
+
function resolveOutputPath(outputDir, filename) {
|
|
87
|
+
const normalizedOutputDir = path.default.resolve(outputDir);
|
|
88
|
+
const fullname = path.default.resolve(normalizedOutputDir, filename);
|
|
89
|
+
const relativePath = path.default.relative(normalizedOutputDir, fullname);
|
|
90
|
+
if (relativePath.startsWith("..") || path.default.isAbsolute(relativePath)) return null;
|
|
91
|
+
return fullname;
|
|
92
|
+
}
|
|
93
|
+
function sleep(ms) {
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
setTimeout(resolve, ms);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
86
98
|
var FigmaFileClient = class {
|
|
87
99
|
fileId;
|
|
88
100
|
nodeId;
|
|
89
101
|
exportFormat;
|
|
90
102
|
client;
|
|
91
103
|
layoutVersion;
|
|
104
|
+
requestSleepMs;
|
|
92
105
|
components = {};
|
|
93
|
-
|
|
94
|
-
|
|
106
|
+
hasRequested = false;
|
|
107
|
+
static async runFromCli(url, token, outputRootDir, exportFormat, layoutVersion = "v1", requestSleepMs) {
|
|
108
|
+
const client = new this(url, token, exportFormat, layoutVersion, requestSleepMs);
|
|
95
109
|
const outputDir = path.default.join(process.cwd(), outputRootDir, exportFormat);
|
|
96
110
|
console.log(`Exporting components from ${url} using layout ${layoutVersion}`);
|
|
97
111
|
await client.loadSvg(outputDir);
|
|
98
112
|
console.log("success!");
|
|
99
113
|
}
|
|
100
|
-
constructor(url, personalAccessToken, exportFormat, layoutVersion) {
|
|
114
|
+
constructor(url, personalAccessToken, exportFormat, layoutVersion, requestSleepMs) {
|
|
101
115
|
this.client = figma_js.Client({ personalAccessToken });
|
|
102
116
|
const { fileId, nodeId } = extractParams(url);
|
|
103
117
|
this.fileId = fileId;
|
|
104
118
|
this.nodeId = nodeId;
|
|
105
119
|
this.exportFormat = exportFormat;
|
|
106
120
|
this.layoutVersion = layoutVersion;
|
|
121
|
+
this.requestSleepMs = requestSleepMs;
|
|
107
122
|
}
|
|
108
123
|
async loadSvg(outputDir) {
|
|
109
124
|
await (0, fs_extra.remove)(outputDir);
|
|
@@ -122,6 +137,7 @@ var FigmaFileClient = class {
|
|
|
122
137
|
}
|
|
123
138
|
async loadImageUrls() {
|
|
124
139
|
console.log("Getting export urls");
|
|
140
|
+
await this.sleepBeforeRequest();
|
|
125
141
|
const { data } = await this.client.fileImages(this.fileId, {
|
|
126
142
|
format: this.exportFormat,
|
|
127
143
|
ids: Object.keys(this.components),
|
|
@@ -131,26 +147,48 @@ var FigmaFileClient = class {
|
|
|
131
147
|
for (const [id, image] of Object.entries(data.images)) this.components[id].image = image;
|
|
132
148
|
}
|
|
133
149
|
async downloadImages(outputDir) {
|
|
134
|
-
|
|
150
|
+
const components = Object.values(this.components);
|
|
151
|
+
const shouldSleepBetweenRequests = (this.requestSleepMs ?? 0) > 0;
|
|
152
|
+
const downloadImage = async (component) => {
|
|
135
153
|
if (component.image === void 0) return;
|
|
136
154
|
const filename = component.variant ? `${parseV2IconName(component.variant)}/${component.name}.${this.exportFormat}` : `${filenamify(component.name)}.${this.exportFormat}`;
|
|
137
|
-
const fullname =
|
|
155
|
+
const fullname = resolveOutputPath(outputDir, filename);
|
|
156
|
+
if (fullname === null) {
|
|
157
|
+
console.log(`skip invalid output path: ${filename}`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
138
160
|
const dirname = path.default.dirname(fullname);
|
|
139
161
|
if (DRY_RUN) {
|
|
140
162
|
console.log(`[DRY_RUN] skip: ${filename} => ✅ writing...`);
|
|
141
163
|
return;
|
|
142
164
|
}
|
|
165
|
+
await this.sleepBeforeRequest();
|
|
143
166
|
const response = await got.default.get(component.image, this.exportFormat == "pdf" ? { responseType: "buffer" } : {});
|
|
144
167
|
await (0, fs_extra.ensureDir)(dirname);
|
|
145
168
|
console.log(`found: ${filename} => ✅ writing...`);
|
|
146
169
|
await (0, fs_extra.writeFile)(fullname, response.body, "utf8");
|
|
147
|
-
}
|
|
170
|
+
};
|
|
171
|
+
if (shouldSleepBetweenRequests) {
|
|
172
|
+
for (const component of components) await downloadImage(component);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
return concurrently(components.map((component) => async () => downloadImage(component)));
|
|
148
176
|
}
|
|
149
177
|
async getFile() {
|
|
150
178
|
console.log("Processing response");
|
|
179
|
+
await this.sleepBeforeRequest();
|
|
151
180
|
const { data } = await this.client.file(this.fileId.toString());
|
|
152
181
|
return data;
|
|
153
182
|
}
|
|
183
|
+
async sleepBeforeRequest() {
|
|
184
|
+
const requestSleepMs = this.requestSleepMs ?? 0;
|
|
185
|
+
if (requestSleepMs <= 0) return;
|
|
186
|
+
if (this.hasRequested) {
|
|
187
|
+
console.log(`Sleeping ${requestSleepMs}ms before next export request`);
|
|
188
|
+
await sleep(requestSleepMs);
|
|
189
|
+
}
|
|
190
|
+
this.hasRequested = true;
|
|
191
|
+
}
|
|
154
192
|
findComponentsRecursively(child) {
|
|
155
193
|
if (child.type === "COMPONENT") {
|
|
156
194
|
const { name, id } = child;
|
|
@@ -195,18 +233,18 @@ function mustBeDefined(value, name) {
|
|
|
195
233
|
//#endregion
|
|
196
234
|
//#region src/getChangedFiles.ts
|
|
197
235
|
/**
|
|
198
|
-
*
|
|
236
|
+
* dirs 内で変更があったファイル情報を for await で回せるようにするやつ
|
|
199
237
|
*/
|
|
200
|
-
async function* getChangedFiles(
|
|
201
|
-
if (!(0, fs.existsSync)(dir)) throw new Error(`icons-cli: target directory not found (${dir})`);
|
|
238
|
+
async function* getChangedFiles(dirs) {
|
|
239
|
+
for (const dir of dirs) if (!(0, fs.existsSync)(dir)) throw new Error(`icons-cli: target directory not found (${dir})`);
|
|
202
240
|
const gitStatus = await collectGitStatus();
|
|
203
241
|
for (const [relativePath, status] of gitStatus) {
|
|
204
|
-
const fullpath = path.default.resolve(process.cwd(),
|
|
205
|
-
if (!fullpath.startsWith(`${dir}/`)) continue;
|
|
206
|
-
if (!(0, fs.existsSync)(fullpath)) throw new Error(`icons-cli: could not
|
|
242
|
+
const fullpath = path.default.resolve(process.cwd(), relativePath);
|
|
243
|
+
if (!dirs.some((dir) => fullpath.startsWith(`${dir}/`))) continue;
|
|
244
|
+
if (status !== "deleted" && !(0, fs.existsSync)(fullpath)) throw new Error(`icons-cli: could not find file (${fullpath})`);
|
|
207
245
|
yield {
|
|
208
246
|
relativePath,
|
|
209
|
-
content: await fs.promises.readFile(fullpath, { encoding: "utf-8" }),
|
|
247
|
+
content: status === "deleted" ? "" : await fs.promises.readFile(fullpath, { encoding: "utf-8" }),
|
|
210
248
|
status
|
|
211
249
|
};
|
|
212
250
|
}
|
|
@@ -216,7 +254,7 @@ async function collectGitStatus() {
|
|
|
216
254
|
/**
|
|
217
255
|
* @see https://git-scm.com/docs/git-status#_porcelain_format_version_1
|
|
218
256
|
*/
|
|
219
|
-
(await execp(`git status --porcelain`)).split("\n").map((s) => {
|
|
257
|
+
(await execp(`git status --porcelain -uall`)).split("\n").map((s) => {
|
|
220
258
|
return [s.slice(3), s.startsWith(" M") ? "modified" : s.startsWith("??") ? "untracked" : s.startsWith(" D") ? "deleted" : null];
|
|
221
259
|
})
|
|
222
260
|
);
|
|
@@ -258,7 +296,7 @@ var GithubClient = class {
|
|
|
258
296
|
}
|
|
259
297
|
async createTreeFromDiff(outputDir) {
|
|
260
298
|
const tree = [];
|
|
261
|
-
for await (const file of getChangedFiles(outputDir)) {
|
|
299
|
+
for await (const file of getChangedFiles([outputDir])) {
|
|
262
300
|
const item = {
|
|
263
301
|
path: file.relativePath,
|
|
264
302
|
mode: "100644",
|
|
@@ -366,7 +404,7 @@ var GitlabClient = class {
|
|
|
366
404
|
}
|
|
367
405
|
async createActionsFromDiff(outputDir) {
|
|
368
406
|
const actions = [];
|
|
369
|
-
for await (const file of getChangedFiles(outputDir)) actions.push({
|
|
407
|
+
for await (const file of getChangedFiles([outputDir])) actions.push({
|
|
370
408
|
action: file.status === "untracked" ? "create" : file.status === "deleted" ? "delete" : "update",
|
|
371
409
|
filePath: file.relativePath,
|
|
372
410
|
content: file.content
|
|
@@ -449,29 +487,37 @@ const optimizeSvgInDirectory = async (outputDir, replaceColor, ignoreFile) => {
|
|
|
449
487
|
}));
|
|
450
488
|
};
|
|
451
489
|
|
|
490
|
+
//#endregion
|
|
491
|
+
//#region src/codegen.ts
|
|
492
|
+
const LINE_SEPARATOR = /\u2028/gu;
|
|
493
|
+
const PARAGRAPH_SEPARATOR = /\u2029/gu;
|
|
494
|
+
function serializeJavaScriptValue(value) {
|
|
495
|
+
return JSON.stringify(value, null, 2).replace(LINE_SEPARATOR, "\\u2028").replace(PARAGRAPH_SEPARATOR, "\\u2029");
|
|
496
|
+
}
|
|
497
|
+
|
|
452
498
|
//#endregion
|
|
453
499
|
//#region src/generateSource.ts
|
|
454
500
|
const generateIconSvgEmbeddedSource = (svgString) => {
|
|
455
501
|
return `/** This file is auto generated. DO NOT EDIT BY HAND. */
|
|
456
|
-
export default
|
|
502
|
+
export default ${serializeJavaScriptValue(svgString.replace(/\r?\n/g, ""))}
|
|
457
503
|
`;
|
|
458
504
|
};
|
|
459
505
|
const generateMjsEntrypoint = (icons) => `/** This file is auto generated. DO NOT EDIT BY HAND. */
|
|
460
506
|
|
|
461
507
|
export default {
|
|
462
|
-
${icons.map((it) => `
|
|
508
|
+
${icons.map((it) => ` ${serializeJavaScriptValue(it)}: () => import(${serializeJavaScriptValue(`./${it}.js`)}).then(m => m.default)`).join(",\n")}
|
|
463
509
|
}
|
|
464
510
|
`;
|
|
465
511
|
const generateCjsEntrypoint = (icons) => `/** This file is auto generated. DO NOT EDIT BY HAND. */
|
|
466
512
|
|
|
467
513
|
module.exports = {
|
|
468
|
-
${icons.map((it) => `
|
|
514
|
+
${icons.map((it) => ` ${serializeJavaScriptValue(it)}: () => import(${serializeJavaScriptValue(`./${it}.js`)}).then(m => m.default)`).join(",\n")}
|
|
469
515
|
}
|
|
470
516
|
`;
|
|
471
517
|
const generateTypeDefinitionEntrypoint = (icons) => `/** This file is auto generated. DO NOt EDIT BY HAND. */
|
|
472
518
|
|
|
473
519
|
declare var _default: {
|
|
474
|
-
${icons.map((it) => `
|
|
520
|
+
${icons.map((it) => ` ${serializeJavaScriptValue(it)}: () => Promise<string>`).join(";\n")}
|
|
475
521
|
};
|
|
476
522
|
export default _default;
|
|
477
523
|
`;
|
|
@@ -531,12 +577,17 @@ yargs.default.scriptName("icons-cli").command("figma:export", "Load all icons fr
|
|
|
531
577
|
layout: {
|
|
532
578
|
default: "v1",
|
|
533
579
|
describe: "Figma icon file layout version"
|
|
580
|
+
},
|
|
581
|
+
sleepMs: {
|
|
582
|
+
type: "number",
|
|
583
|
+
describe: "Sleep duration between export requests in milliseconds"
|
|
534
584
|
}
|
|
535
|
-
}, async ({ format, layout }) => {
|
|
585
|
+
}, async ({ format, layout, sleepMs }) => {
|
|
536
586
|
mustBeDefined(FIGMA_FILE_URL, "FIGMA_FILE_URL");
|
|
537
587
|
mustBeDefined(FIGMA_TOKEN, "FIGMA_TOKEN");
|
|
538
588
|
mustBeDefined(OUTPUT_ROOT_DIR, "OUTPUT_ROOT_DIR");
|
|
539
|
-
|
|
589
|
+
if (sleepMs !== void 0 && sleepMs < 0) throw new TypeError("sleepMs must be 0 or greater.");
|
|
590
|
+
await FigmaFileClient.runFromCli(FIGMA_FILE_URL, FIGMA_TOKEN, OUTPUT_ROOT_DIR, format, layout, sleepMs);
|
|
540
591
|
}).command("svg:optimize", "Optimize svg files in output directory", {
|
|
541
592
|
color: {
|
|
542
593
|
default: DEFAULT_CURRENT_COLOR_TARGET,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["PQueue","Figma","Octokit","Gitlab","svgo","Svgo","JSDOM","fs","fs","path"],"sources":["../src/concurrently.ts","../src/figma/FigmaFileClient.ts","../src/utils.ts","../src/getChangedFiles.ts","../src/GitHubClient.ts","../src/GitlabClient.ts","../src/svg/optimizeSvg.ts","../src/svg/optimizeSvgInDirectory.ts","../src/generateSource.ts","../src/index.ts"],"sourcesContent":["import PQueue from 'p-queue'\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function concurrently(tasks: (() => Promise<any>)[]) {\n const queue = new PQueue({ concurrency: 3 })\n for (const task of tasks) {\n void queue.add(task)\n }\n queue.start()\n return queue.onIdle()\n}\n","/* eslint-disable no-console */\nimport path from 'path'\nimport camelCase from 'camelcase'\nimport * as Figma from 'figma-js'\nimport { ensureDir, remove, writeFile } from 'fs-extra'\nimport got from 'got'\nimport { match } from 'path-to-regexp'\nimport { concurrently } from '../concurrently'\n\nconst DRY_RUN = Boolean(process.env.DRY_RUN)\n\nconst matchPath = match<{ fileId: string; name: string }>('/file/:fileId/:name')\n\nfunction extractParams(url: string): { fileId: string; nodeId?: string } {\n const { pathname, searchParams } = new URL(url)\n\n const result = matchPath(pathname)\n if (result === false) {\n throw new Error('No fileId found in url')\n }\n\n return {\n fileId: result.params.fileId,\n nodeId: searchParams.get('node-id') ?? undefined,\n }\n}\n\nfunction filenamify(name: string) {\n return camelCase(name, { pascalCase: true }).replace(' ', '')\n}\n\nconst iconName = /^(?:\\d+|Inline)\\s*\\//u\n\nfunction isIconNode(node: Figma.Node) {\n return iconName.test(node.name)\n}\n\nfunction parseV2IconName(name: string) {\n return name\n .split(',')\n .map((f) => f.split('=').map((s) => s.trim())[1])\n .join('/')\n .toLowerCase()\n}\n\ntype ExportFormat = 'svg' | 'pdf'\n\ninterface Component {\n id: string\n name: string\n image?: string\n variant?: string\n}\n\ntype FigmaIconFileLayoutVersion = 'v1' | 'v2'\n\nexport class FigmaFileClient {\n private readonly fileId: string\n private readonly nodeId?: string\n private readonly exportFormat: ExportFormat\n private readonly client: Figma.ClientInterface\n private readonly layoutVersion: FigmaIconFileLayoutVersion\n\n private components: Record<string, Component> = {}\n\n static async runFromCli(\n url: string,\n token: string,\n outputRootDir: string,\n exportFormat: ExportFormat,\n layoutVersion: FigmaIconFileLayoutVersion = 'v1',\n ) {\n const client = new this(url, token, exportFormat, layoutVersion)\n\n const outputDir = path.join(process.cwd(), outputRootDir, exportFormat)\n\n console.log(\n `Exporting components from ${url} using layout ${layoutVersion}`,\n )\n await client.loadSvg(outputDir)\n\n console.log('success!')\n }\n\n constructor(\n url: string,\n personalAccessToken: string,\n exportFormat: ExportFormat,\n layoutVersion: FigmaIconFileLayoutVersion,\n ) {\n this.client = Figma.Client({\n personalAccessToken,\n })\n\n const { fileId, nodeId } = extractParams(url)\n this.fileId = fileId\n this.nodeId = nodeId\n\n this.exportFormat = exportFormat\n this.layoutVersion = layoutVersion\n }\n\n async loadSvg(outputDir: string) {\n await remove(outputDir)\n await ensureDir(outputDir)\n\n await this.loadComponents()\n await this.loadImageUrls()\n await this.downloadImages(outputDir)\n }\n\n private async loadComponents() {\n const { document } = await this.getFile()\n // const { document } = (\n // await import('../../../../blob-report/scripts/doc.json')\n // ).default as Figma.FileResponse\n\n if (this.layoutVersion === 'v2') {\n this.findComponentsV2(document)\n } else {\n // nodeIdが指定されている場合は、IDが一致するノードのみを探索対象にする\n // 指定されていない場合はドキュメント全体が探索対象\n const targets =\n this.nodeId !== undefined\n ? document.children.filter((node) => node.id === this.nodeId)\n : document.children\n\n // 対象ノードの子孫を探索してアイコンのコンポーネントを見つける\n targets.forEach((child) => this.findComponentsRecursively(child))\n }\n\n const len = Object.keys(this.components).length\n if (len === 0) {\n throw new Error('No components found!')\n } else {\n console.log(`found ${len} icons`)\n }\n }\n\n private async loadImageUrls() {\n console.log('Getting export urls')\n\n const { data } = await this.client.fileImages(this.fileId, {\n format: this.exportFormat,\n ids: Object.keys(this.components),\n scale: 1,\n ...(this.exportFormat == 'pdf' ? { use_absolute_bounds: true } : {}),\n })\n\n for (const [id, image] of Object.entries(data.images)) {\n this.components[id].image = image\n }\n }\n\n private async downloadImages(outputDir: string) {\n return concurrently(\n Object.values(this.components).map((component) => async () => {\n if (component.image === undefined) {\n return\n }\n\n const filename = component.variant\n ? `${parseV2IconName(component.variant)}/${component.name}.${\n this.exportFormat\n }`\n : `${filenamify(component.name)}.${this.exportFormat}`\n const fullname = path.join(outputDir, filename)\n const dirname = path.dirname(fullname)\n\n if (DRY_RUN) {\n console.log(`[DRY_RUN] skip: ${filename} => ✅ writing...`)\n return\n }\n\n const response = await got.get(\n component.image,\n this.exportFormat == 'pdf' ? { responseType: 'buffer' } : {},\n )\n\n await ensureDir(dirname)\n\n console.log(`found: ${filename} => ✅ writing...`)\n await writeFile(fullname, response.body, 'utf8')\n }),\n )\n }\n\n private async getFile() {\n console.log('Processing response')\n\n const { data } = await this.client.file(this.fileId.toString())\n\n return data\n }\n\n private findComponentsRecursively(child: Figma.Node) {\n if (child.type === 'COMPONENT') {\n const { name, id } = child\n\n if (isIconNode(child)) {\n this.components[id] = {\n name,\n id,\n }\n }\n } else if ('children' in child) {\n child.children.forEach((grandChild) =>\n this.findComponentsRecursively(grandChild),\n )\n }\n }\n\n private findComponentsV2(document: Figma.Document) {\n const iconsPage = document.children.find(\n (child): child is Figma.Canvas =>\n child.name === 'Icons 一覧' && child.type === 'CANVAS',\n )\n const iconComponentSets = iconsPage?.children.flatMap((c) => {\n if (c.type !== 'FRAME') return []\n return c.children.filter(\n (c): c is Figma.ComponentSet => c.type === 'COMPONENT_SET',\n )\n })\n iconComponentSets?.forEach((set) => {\n set.children.forEach((i) => {\n if (i.type !== 'COMPONENT') return null\n this.components[i.id] = {\n name: set.name,\n variant: i.name,\n id: i.id,\n }\n })\n })\n }\n}\n","import { exec } from 'child_process'\n\n/**\n * FIXME: util.promisify を使うと node-libs-browser に入っている方が使われてしまい、壊れる\n */\nexport const execp = (command: string) =>\n new Promise<string>((resolve, reject) => {\n exec(command, (err, stdout) => {\n if (err) {\n return reject(err)\n }\n\n return resolve(stdout)\n })\n })\n\nexport function mustBeDefined<T>(\n value: T,\n name: string,\n): asserts value is NonNullable<T> {\n if (typeof value === 'undefined') {\n throw new TypeError(`${name} must be defined.`)\n }\n}\n","import { promises as fs, existsSync } from 'fs'\nimport path from 'path'\nimport { execp } from './utils'\n\n/**\n * dir 内で変更があったファイル情報を for await で回せるようにするやつ\n */\nexport async function* getChangedFiles(dir: string) {\n if (!existsSync(dir))\n throw new Error(`icons-cli: target directory not found (${dir})`)\n const gitStatus = await collectGitStatus()\n for (const [relativePath, status] of gitStatus) {\n const fullpath = path.resolve(process.cwd(), '../../', relativePath)\n if (!fullpath.startsWith(`${dir}/`)) {\n continue\n }\n if (!existsSync(fullpath))\n throw new Error(`icons-cli: could not load svg (${fullpath})`)\n const content = await fs.readFile(fullpath, { encoding: 'utf-8' })\n yield { relativePath, content, status }\n }\n}\n\nasync function collectGitStatus() {\n return new Map(\n /**\n * @see https://git-scm.com/docs/git-status#_porcelain_format_version_1\n */\n (await execp(`git status --porcelain`)).split('\\n').map((s) => {\n return [\n s.slice(3),\n s.startsWith(' M')\n ? 'modified'\n : s.startsWith('??')\n ? 'untracked'\n : s.startsWith(' D')\n ? 'deleted'\n : null,\n ] as const\n }),\n )\n}\n","import { Octokit, RestEndpointMethodTypes } from '@octokit/rest'\nimport path from 'path'\nimport { getChangedFiles } from './getChangedFiles'\n\ntype RefResponse =\n ReturnType<GithubClient['createBranch']> extends Promise<infer R> ? R : never\n\ninterface TreeItem {\n path?: string\n mode?: '100644' | '100755' | '040000' | '160000' | '120000'\n type?: 'blob' | 'commit' | 'tree'\n sha?: string | null\n content?: string\n}\n\nexport class GithubClient {\n private readonly api: Octokit\n private readonly now: Date\n\n static async runFromCli(\n repoOwner: string,\n repoName: string,\n token: string,\n defaultBranch: string,\n outputDir: string,\n ): Promise<ReturnType<GithubClient['createPullRequest']> | void> {\n const client = new this(repoOwner, repoName, token, defaultBranch)\n const outputDirFullPath = path.resolve(process.cwd(), outputDir)\n const diff = await client.createTreeFromDiff(outputDirFullPath)\n // eslint-disable-next-line no-console\n console.log(`${diff.length} files are changed`)\n if (diff.length === 0) {\n // eslint-disable-next-line no-console\n console.log('no changes. aborting')\n return\n }\n\n const newBranch = await client.createBranch()\n\n await client.createCommit(diff, newBranch)\n return client.createPullRequest(newBranch)\n }\n\n constructor(\n private readonly repoOwner: string,\n private readonly repoName: string,\n token: string,\n private readonly defaultBranch: string,\n now = new Date(),\n ) {\n this.api = new Octokit({\n auth: token,\n })\n\n this.now = now\n }\n\n get branch() {\n return `icons/update/${this.now.getTime()}`\n }\n\n /**\n * both used for commit message or pull request title\n */\n get message() {\n return `[icons-cli] Update icons ${this.now.toDateString()}`\n }\n\n async createTreeFromDiff(outputDir: string): Promise<TreeItem[]> {\n const tree: TreeItem[] = []\n\n for await (const file of getChangedFiles(outputDir)) {\n const item = {\n path: file.relativePath,\n // 100 はファイル 644 は実行不可なファイルであるという意味\n // @see https://octokit.github.io/rest.js/v18#git-create-tree\n mode: '100644' as const,\n content: file.content,\n }\n\n if (file.status === 'deleted') {\n // https://stackoverflow.com/questions/23637961/how-do-i-mark-a-file-as-deleted-in-a-tree-using-the-github-api\n tree.push({\n ...item,\n sha: null,\n })\n } else {\n tree.push(item)\n }\n }\n\n return tree\n }\n\n async createCommit(\n tree: TreeItem[],\n targetBranch: RefResponse,\n ): Promise<RestEndpointMethodTypes['git']['createCommit']['response']> {\n const parentCommit = await this.api.git.getCommit({\n owner: this.repoOwner,\n repo: this.repoName,\n\n commit_sha: targetBranch.data.object.sha,\n })\n\n const newTree = await this.api.git.createTree({\n owner: this.repoOwner,\n repo: this.repoName,\n\n base_tree: parentCommit.data.tree.sha,\n tree,\n })\n\n // この時点ではどのブランチにも属さないコミットができる\n const commit = await this.api.git.createCommit({\n owner: this.repoOwner,\n repo: this.repoName,\n message: this.message,\n tree: newTree.data.sha,\n parents: [parentCommit.data.sha],\n })\n\n // ref を更新することで、commit が targetBranch に属するようになる\n await this.api.git.updateRef({\n owner: this.repoOwner,\n repo: this.repoName,\n ref: `heads/${this.branch}`,\n sha: commit.data.sha,\n })\n\n return commit\n }\n\n async createPullRequest(\n targetBranch: RefResponse,\n ): Promise<ReturnType<GithubClient['api']['pulls']['create']>> {\n const defaultBranch = await this.getDefaultBranchRef()\n\n return this.api.pulls.create({\n owner: this.repoOwner,\n repo: this.repoName,\n head: targetBranch.data.ref,\n base: defaultBranch.data.ref,\n title: this.message,\n body: '',\n })\n }\n\n private getDefaultBranchRef() {\n return this.api.git.getRef({\n owner: this.repoOwner,\n repo: this.repoName,\n ref: `heads/${this.defaultBranch}`,\n })\n }\n\n async createBranch(): ReturnType<GithubClient['api']['git']['createRef']> {\n const defaultBranch = await this.getDefaultBranchRef()\n\n return this.api.git.createRef({\n owner: this.repoOwner,\n repo: this.repoName,\n ref: `refs/heads/${this.branch}`,\n sha: defaultBranch.data.object.sha,\n })\n }\n}\n","import type { CommitAction } from '@gitbeaker/core/dist/types/services/Commits'\nimport { Gitlab } from '@gitbeaker/node'\nimport path from 'path'\nimport { getChangedFiles } from './getChangedFiles'\n\ntype GitlabApi = InstanceType<typeof Gitlab>\n\nexport class GitlabClient {\n private readonly api: GitlabApi\n private readonly now: Date\n\n static async runFromCli(\n host: string,\n projectId: number,\n privateToken: string,\n defaultBranch: string,\n outputDir: string,\n ) {\n const client = new this(host, projectId, privateToken, defaultBranch)\n const outputDirFullPath = path.resolve(process.cwd(), outputDir)\n const diff = await client.createActionsFromDiff(outputDirFullPath)\n // eslint-disable-next-line no-console\n console.log(`${diff.length} files are changed`)\n if (diff.length === 0) {\n // eslint-disable-next-line no-console\n console.log('no changes. aborting')\n return\n }\n\n await client.createCommit(diff)\n return client.createMergeRequest()\n }\n\n constructor(\n private readonly host: string,\n private readonly projectId: number,\n privateToken: string,\n private readonly defaultBranch: string,\n now = new Date(),\n ) {\n this.api = new Gitlab({\n host: this.host,\n token: privateToken,\n })\n this.now = now\n }\n\n get branch() {\n return `icons/update/${this.now.getTime()}`\n }\n\n /**\n * both used for commit message or merge request title\n */\n get message() {\n return `[icons-cli] Update icons ${this.now.toDateString()}`\n }\n\n async createActionsFromDiff(outputDir: string): Promise<CommitAction[]> {\n const actions: CommitAction[] = []\n\n for await (const file of getChangedFiles(outputDir)) {\n actions.push({\n action:\n file.status === 'untracked'\n ? 'create'\n : file.status === 'deleted'\n ? 'delete'\n : 'update',\n filePath: file.relativePath,\n content: file.content,\n })\n }\n\n return actions\n }\n\n async createCommit(diff: CommitAction[]) {\n return this.api.Commits.create(\n this.projectId,\n this.branch,\n this.message,\n diff,\n {\n start_branch: this.defaultBranch,\n },\n )\n }\n\n createMergeRequest() {\n return this.api.MergeRequests.create(\n this.projectId,\n this.branch,\n this.defaultBranch,\n this.message,\n )\n }\n}\n","import { JSDOM } from 'jsdom'\nimport { parseToRgb } from 'polished'\nimport type { RgbColor, RgbaColor } from 'polished/lib/types/color'\nimport Svgo from 'svgo'\n\nexport const DEFAULT_CURRENT_COLOR_TARGET = '#858585'\n\nconst svgo = new Svgo({\n plugins: [\n // NOTICE: SVGO は「svg 内のすべての fill を currentColor に変える」機能しかない\n // icons-cli に必要なのは「特定の黒っぽい色だけ currentColor に変える」機能\n // なので、convertColors plugin は使わない\n // { convertColors: { currentColor: true } },\n { removeViewBox: false },\n { removeAttrs: { attrs: ['stroke-opacity', 'fill-opacity'] } },\n ],\n})\n\n/**\n * SVGを最適化するオプション\n */\ninterface Options {\n /**\n * currentColorに置換する色 #ffffff\n */\n convertedColor: string\n /**\n * svgoによる最適化を行わない\n */\n withoutOptimizeBySVGO?: boolean\n}\n\nexport async function optimizeSvg(input: string, options: Options) {\n const { document } = new JSDOM(input).window\n const svg = document.querySelector('svg')\n if (!svg) {\n throw new Error('optimizeSvg: input string seems not to have <svg>')\n }\n\n addViewboxToRootSvg(svg)\n convertToCurrentColor(svg, options.convertedColor)\n\n if (options.withoutOptimizeBySVGO === true) {\n return svg.outerHTML\n } else {\n return (await svgo.optimize(svg.outerHTML)).data\n }\n}\n\nconst TARGET_ATTRS = ['fill', 'stroke']\n\nfunction convertToCurrentColor(svg: SVGSVGElement, convertedColor: string) {\n const targetColor = parseColor(convertedColor)\n if (!targetColor) {\n throw new Error(`${convertedColor} is not a valid color`)\n }\n\n for (const attr of TARGET_ATTRS) {\n const targets = Array.from(svg.querySelectorAll<SVGElement>(`[${attr}]`))\n\n for (const el of targets) {\n const value = parseColor(el.getAttribute(attr))\n if (!value) {\n continue\n }\n\n if (!colorEquals(value, targetColor)) {\n continue\n }\n\n el.setAttribute(attr, 'currentColor')\n }\n }\n}\n\nfunction parseColor(value: string | null) {\n if (value == null) {\n return null\n }\n\n try {\n return parseToRgb(value)\n } catch {\n return null\n }\n}\n\nfunction colorEquals(self: RgbColor | RgbaColor, other: RgbColor | RgbaColor) {\n if (self.red !== other.red) {\n return false\n }\n\n if (self.blue !== other.blue) {\n return false\n }\n\n if (self.green !== other.green) {\n return false\n }\n\n if ('alpha' in self) {\n if ('alpha' in other) {\n if (self.alpha !== other.alpha) {\n return false\n }\n }\n }\n\n return true\n}\n\nfunction addViewboxToRootSvg(svg: SVGSVGElement) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const width = svg.getAttribute('width')!\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const height = svg.getAttribute('height')!\n\n svg.setAttribute('viewBox', `0 0 ${width} ${height}`)\n}\n","import path from 'path'\nimport { glob } from 'fs/promises'\nimport fs from 'fs-extra'\nimport { concurrently } from '../concurrently'\nimport { optimizeSvg } from './optimizeSvg'\n\n/* eslint-disable no-console */\n\nexport const optimizeSvgInDirectory = async (\n outputDir: string,\n replaceColor: string,\n ignoreFile?: string,\n) => {\n const rootDir = path.join(outputDir, 'svg')\n\n const ignorePatterns =\n ignoreFile !== undefined\n ? (await fs.readFile(ignoreFile, 'utf8')).trim().split(/\\r?\\n/u)\n : []\n\n const files = await Array.fromAsync(\n glob('**/*.svg', {\n cwd: rootDir,\n }),\n )\n\n await concurrently(\n files.map((file) => async () => {\n console.log(`Optimizing ${file}...`)\n const fullPath = path.join(rootDir, file)\n\n const originalSvg = await fs.readFile(fullPath, 'utf8')\n const optimizedSvg = await optimizeSvg(originalSvg, {\n convertedColor: replaceColor,\n withoutOptimizeBySVGO: ignorePatterns.includes(file),\n })\n await fs.writeFile(fullPath, optimizedSvg)\n }),\n )\n}\n","import path from 'path'\nimport { glob } from 'node:fs/promises'\nimport fs from 'fs-extra'\n\nconst generateIconSvgEmbeddedSource = (svgString: string) => {\n const str = svgString.replace(/\\r?\\n/g, '')\n\n return `/** This file is auto generated. DO NOT EDIT BY HAND. */\nexport default '${str}'\n`\n}\n\nconst generateMjsEntrypoint = (\n icons: string[],\n) => `/** This file is auto generated. DO NOT EDIT BY HAND. */\n\nexport default {\n${icons\n .map((it) => ` '${it}': () => import('./${it}.js').then(m => m.default)`)\n .join(',\\n')}\n}\n`\n\nconst generateCjsEntrypoint = (\n icons: string[],\n) => `/** This file is auto generated. DO NOT EDIT BY HAND. */\n\nmodule.exports = {\n${icons\n .map((it) => ` '${it}': () => import('./${it}.js').then(m => m.default)`)\n .join(',\\n')}\n}\n`\n\nconst generateTypeDefinitionEntrypoint = (\n icons: string[],\n) => `/** This file is auto generated. DO NOt EDIT BY HAND. */\n\ndeclare var _default: {\n${icons.map((it) => ` '${it}': () => Promise<string>`).join(';\\n')}\n};\nexport default _default;\n`\n\nexport const generateEntrypoint = async (\n outputDir: string,\n icons: string[],\n) => {\n const srcRoot = path.join(outputDir, 'src')\n const mjsPath = path.join(srcRoot, 'index.js')\n await fs.ensureFile(mjsPath)\n await fs.writeFile(mjsPath, generateMjsEntrypoint(icons))\n\n const cjsPath = path.join(srcRoot, 'index.cjs')\n await fs.ensureFile(cjsPath)\n await fs.writeFile(cjsPath, generateCjsEntrypoint(icons))\n\n const dtsPath = path.join(srcRoot, 'index.d.ts')\n await fs.ensureFile(dtsPath)\n await fs.writeFile(dtsPath, generateTypeDefinitionEntrypoint(icons))\n}\n\nexport const generateIconSource = async (outputDir: string) => {\n const svgRoot = path.join(outputDir, 'svg')\n const srcRoot = path.join(outputDir, 'src')\n const icons = (await Array.fromAsync(glob('**/*.svg', { cwd: svgRoot })))\n .map(\n (path) => path.slice(0, -4), // e.g. '16/Add.svg' -> '16/Add'\n )\n .sort()\n\n for (const it of icons) {\n const data = await fs.readFile(path.join(svgRoot, `${it}.svg`))\n const outputPath = path.join(srcRoot, `${it}.js`)\n await fs.ensureFile(outputPath)\n await fs.writeFile(\n outputPath,\n generateIconSvgEmbeddedSource(data.toString()),\n )\n }\n\n await generateEntrypoint(outputDir, icons)\n}\n","#!/usr/bin/env node\n\nimport yargs from 'yargs'\nimport { FigmaFileClient } from './figma/FigmaFileClient'\nimport { GithubClient } from './GitHubClient'\nimport { GitlabClient } from './GitlabClient'\nimport { DEFAULT_CURRENT_COLOR_TARGET } from './svg/optimizeSvg'\nimport { optimizeSvgInDirectory } from './svg/optimizeSvgInDirectory'\nimport { generateIconSource } from './generateSource'\nimport { mustBeDefined } from './utils'\n\n/**\n * Figma\n */\nconst FIGMA_TOKEN = process.env.FIGMA_TOKEN\nconst FIGMA_FILE_URL = process.env.FIGMA_FILE_URL\nconst OUTPUT_ROOT_DIR = process.env.OUTPUT_ROOT_DIR\n\n/**\n * GitLab\n */\nconst GITLAB_ACCESS_TOKEN = process.env.GITLAB_ACCESS_TOKEN\nconst GITLAB_DEFAULT_BRANCH = process.env.GITLAB_DEFAULT_BRANCH\nconst GITLAB_HOST = process.env.GITLAB_HOST\nconst GITLAB_PROJECT_ID = process.env.GITLAB_PROJECT_ID\n\n/**\n * GitHub\n */\nconst GITHUB_ACCESS_TOKEN = process.env.GITHUB_ACCESS_TOKEN\nconst GITHUB_REPO_OWNER = process.env.GITHUB_REPO_OWNER\nconst GITHUB_REPO_NAME = process.env.GITHUB_REPO_NAME\nconst GITHUB_DEFAULT_BRANCH = process.env.GITHUB_DEFAULT_BRANCH\n\nvoid yargs\n .scriptName('icons-cli')\n .command(\n 'figma:export',\n 'Load all icons from Figma and save to files',\n {\n format: {\n default: 'svg',\n choices: ['svg', 'pdf'],\n describe: 'Output format',\n },\n layout: {\n default: 'v1',\n describe: 'Figma icon file layout version',\n },\n },\n async ({ format, layout }) => {\n mustBeDefined(FIGMA_FILE_URL, 'FIGMA_FILE_URL')\n mustBeDefined(FIGMA_TOKEN, 'FIGMA_TOKEN')\n mustBeDefined(OUTPUT_ROOT_DIR, 'OUTPUT_ROOT_DIR')\n\n await FigmaFileClient.runFromCli(\n FIGMA_FILE_URL,\n FIGMA_TOKEN,\n OUTPUT_ROOT_DIR,\n format as 'svg' | 'pdf',\n layout as 'v1' | 'v2',\n )\n },\n )\n .command(\n 'svg:optimize',\n 'Optimize svg files in output directory',\n {\n color: {\n default: DEFAULT_CURRENT_COLOR_TARGET,\n type: 'string',\n describe: 'Color code that should be converted into `currentColor`',\n },\n ignoreFile: {\n type: 'string',\n describe:\n 'A file that contains the list of path to SVG files that should not be optimized',\n },\n },\n async ({ color, ignoreFile }) => {\n mustBeDefined(OUTPUT_ROOT_DIR, 'OUTPUT_ROOT_DIR')\n\n await optimizeSvgInDirectory(OUTPUT_ROOT_DIR, color, ignoreFile)\n },\n )\n .command(\n 'files:generate',\n 'Enumerate svg files in output directory and generate icon files',\n {},\n () => {\n mustBeDefined(OUTPUT_ROOT_DIR, 'OUTPUT_ROOT_DIR')\n\n void generateIconSource(OUTPUT_ROOT_DIR).catch((e) => {\n // eslint-disable-next-line no-console\n console.error(e)\n process.exit(1)\n })\n },\n )\n .command(\n 'gitlab:mr',\n 'Create a merge request in the name of icons-cli',\n {},\n async () => {\n mustBeDefined(GITLAB_PROJECT_ID, 'GITLAB_PROJECT_ID')\n mustBeDefined(GITLAB_ACCESS_TOKEN, 'GITLAB_ACCESS_TOKEN')\n mustBeDefined(OUTPUT_ROOT_DIR, 'OUTPUT_ROOT_DIR')\n\n await GitlabClient.runFromCli(\n GITLAB_HOST ?? 'https://gitlab.com',\n Number(GITLAB_PROJECT_ID),\n GITLAB_ACCESS_TOKEN,\n GITLAB_DEFAULT_BRANCH ?? 'main',\n OUTPUT_ROOT_DIR,\n )\n },\n )\n .command(\n 'github:pr',\n 'Create a pull request in the name of icons-cli',\n {},\n async () => {\n mustBeDefined(GITHUB_ACCESS_TOKEN, 'GITHUB_ACCESS_TOKEN')\n mustBeDefined(OUTPUT_ROOT_DIR, 'OUTPUT_ROOT_DIR')\n\n await GithubClient.runFromCli(\n GITHUB_REPO_OWNER ?? 'pixiv',\n GITHUB_REPO_NAME ?? 'charcoal',\n GITHUB_ACCESS_TOKEN,\n GITHUB_DEFAULT_BRANCH ?? 'main',\n OUTPUT_ROOT_DIR,\n )\n },\n )\n .demandCommand()\n .strict()\n .help()\n .parse()\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["PQueue","Figma","Octokit","Gitlab","svgo","Svgo","JSDOM","fs","fs","path"],"sources":["../src/concurrently.ts","../src/figma/FigmaFileClient.ts","../src/utils.ts","../src/getChangedFiles.ts","../src/GitHubClient.ts","../src/GitlabClient.ts","../src/svg/optimizeSvg.ts","../src/svg/optimizeSvgInDirectory.ts","../src/codegen.ts","../src/generateSource.ts","../src/index.ts"],"sourcesContent":["import PQueue from 'p-queue'\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function concurrently(tasks: (() => Promise<any>)[]) {\n const queue = new PQueue({ concurrency: 3 })\n for (const task of tasks) {\n void queue.add(task)\n }\n queue.start()\n return queue.onIdle()\n}\n","/* eslint-disable no-console */\nimport path from 'path'\nimport camelCase from 'camelcase'\nimport * as Figma from 'figma-js'\nimport { ensureDir, remove, writeFile } from 'fs-extra'\nimport got from 'got'\nimport { match } from 'path-to-regexp'\nimport { concurrently } from '../concurrently'\n\nconst DRY_RUN = Boolean(process.env.DRY_RUN)\n\nconst matchPath = match<{ fileId: string; name: string }>([\n '/file/:fileId/:name',\n '/design/:fileId/:name',\n])\n\nfunction extractParams(url: string): { fileId: string; nodeId?: string } {\n const { pathname, searchParams } = new URL(url)\n\n const result = matchPath(pathname)\n if (result === false) {\n throw new Error('No fileId found in url')\n }\n\n return {\n fileId: result.params.fileId,\n nodeId: searchParams.get('node-id') ?? undefined,\n }\n}\n\nfunction filenamify(name: string) {\n return camelCase(name, { pascalCase: true }).replace(' ', '')\n}\n\nconst iconName = /^(?:\\d+|Inline)\\s*\\//u\n\nfunction isIconNode(node: Figma.Node) {\n return iconName.test(node.name)\n}\n\nfunction parseV2IconName(name: string) {\n return name\n .split(',')\n .map((f) => f.split('=').map((s) => s.trim())[1])\n .join('/')\n .toLowerCase()\n}\n\nfunction resolveOutputPath(outputDir: string, filename: string) {\n const normalizedOutputDir = path.resolve(outputDir)\n const fullname = path.resolve(normalizedOutputDir, filename)\n const relativePath = path.relative(normalizedOutputDir, fullname)\n\n if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {\n return null\n }\n\n return fullname\n}\n\ntype ExportFormat = 'svg' | 'pdf'\n\nfunction sleep(ms: number) {\n return new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n}\n\ninterface Component {\n id: string\n name: string\n image?: string\n variant?: string\n}\n\ntype FigmaIconFileLayoutVersion = 'v1' | 'v2'\n\nexport class FigmaFileClient {\n private readonly fileId: string\n private readonly nodeId?: string\n private readonly exportFormat: ExportFormat\n private readonly client: Figma.ClientInterface\n private readonly layoutVersion: FigmaIconFileLayoutVersion\n private readonly requestSleepMs?: number\n\n private components: Record<string, Component> = {}\n private hasRequested = false\n\n static async runFromCli(\n url: string,\n token: string,\n outputRootDir: string,\n exportFormat: ExportFormat,\n layoutVersion: FigmaIconFileLayoutVersion = 'v1',\n requestSleepMs?: number,\n ) {\n const client = new this(\n url,\n token,\n exportFormat,\n layoutVersion,\n requestSleepMs,\n )\n\n const outputDir = path.join(process.cwd(), outputRootDir, exportFormat)\n\n console.log(\n `Exporting components from ${url} using layout ${layoutVersion}`,\n )\n await client.loadSvg(outputDir)\n\n console.log('success!')\n }\n\n constructor(\n url: string,\n personalAccessToken: string,\n exportFormat: ExportFormat,\n layoutVersion: FigmaIconFileLayoutVersion,\n requestSleepMs?: number,\n ) {\n this.client = Figma.Client({\n personalAccessToken,\n })\n\n const { fileId, nodeId } = extractParams(url)\n this.fileId = fileId\n this.nodeId = nodeId\n\n this.exportFormat = exportFormat\n this.layoutVersion = layoutVersion\n this.requestSleepMs = requestSleepMs\n }\n\n async loadSvg(outputDir: string) {\n await remove(outputDir)\n await ensureDir(outputDir)\n\n await this.loadComponents()\n await this.loadImageUrls()\n await this.downloadImages(outputDir)\n }\n\n private async loadComponents() {\n const { document } = await this.getFile()\n // const { document } = (\n // await import('../../../../blob-report/scripts/doc.json')\n // ).default as Figma.FileResponse\n\n if (this.layoutVersion === 'v2') {\n this.findComponentsV2(document)\n } else {\n // nodeIdが指定されている場合は、IDが一致するノードのみを探索対象にする\n // 指定されていない場合はドキュメント全体が探索対象\n const targets =\n this.nodeId !== undefined\n ? document.children.filter((node) => node.id === this.nodeId)\n : document.children\n\n // 対象ノードの子孫を探索してアイコンのコンポーネントを見つける\n targets.forEach((child) => this.findComponentsRecursively(child))\n }\n\n const len = Object.keys(this.components).length\n if (len === 0) {\n throw new Error('No components found!')\n } else {\n console.log(`found ${len} icons`)\n }\n }\n\n private async loadImageUrls() {\n console.log('Getting export urls')\n\n await this.sleepBeforeRequest()\n const { data } = await this.client.fileImages(this.fileId, {\n format: this.exportFormat,\n ids: Object.keys(this.components),\n scale: 1,\n ...(this.exportFormat == 'pdf' ? { use_absolute_bounds: true } : {}),\n })\n\n for (const [id, image] of Object.entries(data.images)) {\n this.components[id].image = image\n }\n }\n\n private async downloadImages(outputDir: string) {\n const components = Object.values(this.components)\n const shouldSleepBetweenRequests = (this.requestSleepMs ?? 0) > 0\n\n const downloadImage = async (component: Component) => {\n if (component.image === undefined) {\n return\n }\n\n const filename = component.variant\n ? `${parseV2IconName(component.variant)}/${component.name}.${this.exportFormat}`\n : `${filenamify(component.name)}.${this.exportFormat}`\n const fullname = resolveOutputPath(outputDir, filename)\n\n if (fullname === null) {\n console.log(`skip invalid output path: ${filename}`)\n return\n }\n\n const dirname = path.dirname(fullname)\n\n if (DRY_RUN) {\n console.log(`[DRY_RUN] skip: ${filename} => ✅ writing...`)\n return\n }\n\n await this.sleepBeforeRequest()\n const response = await got.get(\n component.image,\n this.exportFormat == 'pdf' ? { responseType: 'buffer' } : {},\n )\n\n await ensureDir(dirname)\n\n console.log(`found: ${filename} => ✅ writing...`)\n await writeFile(fullname, response.body, 'utf8')\n }\n\n // sleep指定時は直列実行\n if (shouldSleepBetweenRequests) {\n for (const component of components) {\n await downloadImage(component)\n }\n\n return\n }\n\n // sleep未指定時は並列実行\n return concurrently(\n components.map((component) => async () => downloadImage(component)),\n )\n }\n\n private async getFile() {\n console.log('Processing response')\n\n await this.sleepBeforeRequest()\n const { data } = await this.client.file(this.fileId.toString())\n\n return data\n }\n\n private async sleepBeforeRequest() {\n const requestSleepMs = this.requestSleepMs ?? 0\n\n if (requestSleepMs <= 0) {\n return\n }\n\n if (this.hasRequested) {\n console.log(`Sleeping ${requestSleepMs}ms before next export request`)\n await sleep(requestSleepMs)\n }\n\n this.hasRequested = true\n }\n\n private findComponentsRecursively(child: Figma.Node) {\n if (child.type === 'COMPONENT') {\n const { name, id } = child\n\n if (isIconNode(child)) {\n this.components[id] = {\n name,\n id,\n }\n }\n } else if ('children' in child) {\n child.children.forEach((grandChild) =>\n this.findComponentsRecursively(grandChild),\n )\n }\n }\n\n private findComponentsV2(document: Figma.Document) {\n const iconsPage = document.children.find(\n (child): child is Figma.Canvas =>\n child.name === 'Icons 一覧' && child.type === 'CANVAS',\n )\n const iconComponentSets = iconsPage?.children.flatMap((c) => {\n if (c.type !== 'FRAME') return []\n return c.children.filter(\n (c): c is Figma.ComponentSet => c.type === 'COMPONENT_SET',\n )\n })\n iconComponentSets?.forEach((set) => {\n set.children.forEach((i) => {\n if (i.type !== 'COMPONENT') return null\n this.components[i.id] = {\n name: set.name,\n variant: i.name,\n id: i.id,\n }\n })\n })\n }\n}\n","import { exec } from 'child_process'\nimport { escape } from 'querystring'\n\n/**\n * FIXME: util.promisify を使うと node-libs-browser に入っている方が使われてしまい、壊れる\n */\nexport const execp = (command: string) =>\n new Promise<string>((resolve, reject) => {\n exec(command, (err, stdout) => {\n if (err) {\n return reject(err)\n }\n\n return resolve(stdout)\n })\n })\n\nexport function mustBeDefined<T>(\n value: T,\n name: string,\n): asserts value is NonNullable<T> {\n if (typeof value === 'undefined') {\n throw new TypeError(`${name} must be defined.`)\n }\n}\n\n/**\n * escapeだけでは ( ) が残るので、それも追加でエスケープする\n * @param svg SVG string\n * @returns SVG string encoded as data URI\n */\nexport function encodeSvgAsDataUri(svg: string): string {\n return escape(svg).replaceAll('(', '%28').replaceAll(')', '%29')\n}\n","import { promises as fs, existsSync } from 'fs'\nimport path from 'path'\nimport { execp } from './utils'\n\n/**\n * dirs 内で変更があったファイル情報を for await で回せるようにするやつ\n */\nexport async function* getChangedFiles(dirs: string[]) {\n for (const dir of dirs) {\n if (!existsSync(dir))\n throw new Error(`icons-cli: target directory not found (${dir})`)\n }\n const gitStatus = await collectGitStatus()\n for (const [relativePath, status] of gitStatus) {\n const fullpath = path.resolve(process.cwd(), relativePath)\n if (!dirs.some((dir) => fullpath.startsWith(`${dir}/`))) {\n continue\n }\n if (status !== 'deleted' && !existsSync(fullpath))\n throw new Error(`icons-cli: could not find file (${fullpath})`)\n const content =\n status === 'deleted'\n ? ''\n : await fs.readFile(fullpath, { encoding: 'utf-8' })\n yield { relativePath, content, status }\n }\n}\n\nasync function collectGitStatus() {\n return new Map(\n /**\n * @see https://git-scm.com/docs/git-status#_porcelain_format_version_1\n */\n (await execp(`git status --porcelain -uall`)).split('\\n').map((s) => {\n return [\n s.slice(3),\n s.startsWith(' M')\n ? 'modified'\n : s.startsWith('??')\n ? 'untracked'\n : s.startsWith(' D')\n ? 'deleted'\n : null,\n ] as const\n }),\n )\n}\n","import { Octokit, RestEndpointMethodTypes } from '@octokit/rest'\nimport path from 'path'\nimport { getChangedFiles } from './getChangedFiles'\n\ntype RefResponse =\n ReturnType<GithubClient['createBranch']> extends Promise<infer R> ? R : never\n\ninterface TreeItem {\n path?: string\n mode?: '100644' | '100755' | '040000' | '160000' | '120000'\n type?: 'blob' | 'commit' | 'tree'\n sha?: string | null\n content?: string\n}\n\nexport class GithubClient {\n private readonly api: Octokit\n private readonly now: Date\n\n static async runFromCli(\n repoOwner: string,\n repoName: string,\n token: string,\n defaultBranch: string,\n outputDir: string,\n ): Promise<ReturnType<GithubClient['createPullRequest']> | void> {\n const client = new this(repoOwner, repoName, token, defaultBranch)\n const outputDirFullPath = path.resolve(process.cwd(), outputDir)\n const diff = await client.createTreeFromDiff(outputDirFullPath)\n // eslint-disable-next-line no-console\n console.log(`${diff.length} files are changed`)\n if (diff.length === 0) {\n // eslint-disable-next-line no-console\n console.log('no changes. aborting')\n return\n }\n\n const newBranch = await client.createBranch()\n\n await client.createCommit(diff, newBranch)\n return client.createPullRequest(newBranch)\n }\n\n constructor(\n private readonly repoOwner: string,\n private readonly repoName: string,\n token: string,\n private readonly defaultBranch: string,\n now = new Date(),\n ) {\n this.api = new Octokit({\n auth: token,\n })\n\n this.now = now\n }\n\n get branch() {\n return `icons/update/${this.now.getTime()}`\n }\n\n /**\n * both used for commit message or pull request title\n */\n get message() {\n return `[icons-cli] Update icons ${this.now.toDateString()}`\n }\n\n async createTreeFromDiff(outputDir: string): Promise<TreeItem[]> {\n const tree: TreeItem[] = []\n\n for await (const file of getChangedFiles([outputDir])) {\n const item = {\n path: file.relativePath,\n // 100 はファイル 644 は実行不可なファイルであるという意味\n // @see https://octokit.github.io/rest.js/v18#git-create-tree\n mode: '100644' as const,\n content: file.content,\n }\n\n if (file.status === 'deleted') {\n // https://stackoverflow.com/questions/23637961/how-do-i-mark-a-file-as-deleted-in-a-tree-using-the-github-api\n tree.push({\n ...item,\n sha: null,\n })\n } else {\n tree.push(item)\n }\n }\n\n return tree\n }\n\n async createCommit(\n tree: TreeItem[],\n targetBranch: RefResponse,\n ): Promise<RestEndpointMethodTypes['git']['createCommit']['response']> {\n const parentCommit = await this.api.git.getCommit({\n owner: this.repoOwner,\n repo: this.repoName,\n\n commit_sha: targetBranch.data.object.sha,\n })\n\n const newTree = await this.api.git.createTree({\n owner: this.repoOwner,\n repo: this.repoName,\n\n base_tree: parentCommit.data.tree.sha,\n tree,\n })\n\n // この時点ではどのブランチにも属さないコミットができる\n const commit = await this.api.git.createCommit({\n owner: this.repoOwner,\n repo: this.repoName,\n message: this.message,\n tree: newTree.data.sha,\n parents: [parentCommit.data.sha],\n })\n\n // ref を更新することで、commit が targetBranch に属するようになる\n await this.api.git.updateRef({\n owner: this.repoOwner,\n repo: this.repoName,\n ref: `heads/${this.branch}`,\n sha: commit.data.sha,\n })\n\n return commit\n }\n\n async createPullRequest(\n targetBranch: RefResponse,\n ): Promise<ReturnType<GithubClient['api']['pulls']['create']>> {\n const defaultBranch = await this.getDefaultBranchRef()\n\n return this.api.pulls.create({\n owner: this.repoOwner,\n repo: this.repoName,\n head: targetBranch.data.ref,\n base: defaultBranch.data.ref,\n title: this.message,\n body: '',\n })\n }\n\n private getDefaultBranchRef() {\n return this.api.git.getRef({\n owner: this.repoOwner,\n repo: this.repoName,\n ref: `heads/${this.defaultBranch}`,\n })\n }\n\n async createBranch(): ReturnType<GithubClient['api']['git']['createRef']> {\n const defaultBranch = await this.getDefaultBranchRef()\n\n return this.api.git.createRef({\n owner: this.repoOwner,\n repo: this.repoName,\n ref: `refs/heads/${this.branch}`,\n sha: defaultBranch.data.object.sha,\n })\n }\n}\n","import type { CommitAction } from '@gitbeaker/core/dist/types/services/Commits'\nimport { Gitlab } from '@gitbeaker/node'\nimport path from 'path'\nimport { getChangedFiles } from './getChangedFiles'\n\ntype GitlabApi = InstanceType<typeof Gitlab>\n\nexport class GitlabClient {\n private readonly api: GitlabApi\n private readonly now: Date\n\n static async runFromCli(\n host: string,\n projectId: number,\n privateToken: string,\n defaultBranch: string,\n outputDir: string,\n ) {\n const client = new this(host, projectId, privateToken, defaultBranch)\n const outputDirFullPath = path.resolve(process.cwd(), outputDir)\n const diff = await client.createActionsFromDiff(outputDirFullPath)\n // eslint-disable-next-line no-console\n console.log(`${diff.length} files are changed`)\n if (diff.length === 0) {\n // eslint-disable-next-line no-console\n console.log('no changes. aborting')\n return\n }\n\n await client.createCommit(diff)\n return client.createMergeRequest()\n }\n\n constructor(\n private readonly host: string,\n private readonly projectId: number,\n privateToken: string,\n private readonly defaultBranch: string,\n now = new Date(),\n ) {\n this.api = new Gitlab({\n host: this.host,\n token: privateToken,\n })\n this.now = now\n }\n\n get branch() {\n return `icons/update/${this.now.getTime()}`\n }\n\n /**\n * both used for commit message or merge request title\n */\n get message() {\n return `[icons-cli] Update icons ${this.now.toDateString()}`\n }\n\n async createActionsFromDiff(outputDir: string): Promise<CommitAction[]> {\n const actions: CommitAction[] = []\n\n for await (const file of getChangedFiles([outputDir])) {\n actions.push({\n action:\n file.status === 'untracked'\n ? 'create'\n : file.status === 'deleted'\n ? 'delete'\n : 'update',\n filePath: file.relativePath,\n content: file.content,\n })\n }\n\n return actions\n }\n\n async createCommit(diff: CommitAction[]) {\n return this.api.Commits.create(\n this.projectId,\n this.branch,\n this.message,\n diff,\n {\n start_branch: this.defaultBranch,\n },\n )\n }\n\n createMergeRequest() {\n return this.api.MergeRequests.create(\n this.projectId,\n this.branch,\n this.defaultBranch,\n this.message,\n )\n }\n}\n","import { JSDOM } from 'jsdom'\nimport { parseToRgb } from 'polished'\nimport type { RgbColor, RgbaColor } from 'polished/lib/types/color'\nimport Svgo from 'svgo'\n\nexport const DEFAULT_CURRENT_COLOR_TARGET = '#858585'\n\nconst svgo = new Svgo({\n plugins: [\n // NOTICE: SVGO は「svg 内のすべての fill を currentColor に変える」機能しかない\n // icons-cli に必要なのは「特定の黒っぽい色だけ currentColor に変える」機能\n // なので、convertColors plugin は使わない\n // { convertColors: { currentColor: true } },\n { removeViewBox: false },\n { removeAttrs: { attrs: ['stroke-opacity', 'fill-opacity'] } },\n ],\n})\n\n/**\n * SVGを最適化するオプション\n */\ninterface Options {\n /**\n * currentColorに置換する色 #ffffff\n */\n convertedColor: string\n /**\n * svgoによる最適化を行わない\n */\n withoutOptimizeBySVGO?: boolean\n}\n\nexport async function optimizeSvg(input: string, options: Options) {\n const { document } = new JSDOM(input).window\n const svg = document.querySelector('svg')\n if (!svg) {\n throw new Error('optimizeSvg: input string seems not to have <svg>')\n }\n\n addViewboxToRootSvg(svg)\n convertToCurrentColor(svg, options.convertedColor)\n\n if (options.withoutOptimizeBySVGO === true) {\n return svg.outerHTML\n } else {\n return (await svgo.optimize(svg.outerHTML)).data\n }\n}\n\nconst TARGET_ATTRS = ['fill', 'stroke']\n\nfunction convertToCurrentColor(svg: SVGSVGElement, convertedColor: string) {\n const targetColor = parseColor(convertedColor)\n if (!targetColor) {\n throw new Error(`${convertedColor} is not a valid color`)\n }\n\n for (const attr of TARGET_ATTRS) {\n const targets = Array.from(svg.querySelectorAll<SVGElement>(`[${attr}]`))\n\n for (const el of targets) {\n const value = parseColor(el.getAttribute(attr))\n if (!value) {\n continue\n }\n\n if (!colorEquals(value, targetColor)) {\n continue\n }\n\n el.setAttribute(attr, 'currentColor')\n }\n }\n}\n\nfunction parseColor(value: string | null) {\n if (value == null) {\n return null\n }\n\n try {\n return parseToRgb(value)\n } catch {\n return null\n }\n}\n\nfunction colorEquals(self: RgbColor | RgbaColor, other: RgbColor | RgbaColor) {\n if (self.red !== other.red) {\n return false\n }\n\n if (self.blue !== other.blue) {\n return false\n }\n\n if (self.green !== other.green) {\n return false\n }\n\n if ('alpha' in self) {\n if ('alpha' in other) {\n if (self.alpha !== other.alpha) {\n return false\n }\n }\n }\n\n return true\n}\n\nfunction addViewboxToRootSvg(svg: SVGSVGElement) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const width = svg.getAttribute('width')!\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const height = svg.getAttribute('height')!\n\n svg.setAttribute('viewBox', `0 0 ${width} ${height}`)\n}\n","import path from 'path'\nimport { glob } from 'fs/promises'\nimport fs from 'fs-extra'\nimport { concurrently } from '../concurrently'\nimport { optimizeSvg } from './optimizeSvg'\n\n/* eslint-disable no-console */\n\nexport const optimizeSvgInDirectory = async (\n outputDir: string,\n replaceColor: string,\n ignoreFile?: string,\n) => {\n const rootDir = path.join(outputDir, 'svg')\n\n const ignorePatterns =\n ignoreFile !== undefined\n ? (await fs.readFile(ignoreFile, 'utf8')).trim().split(/\\r?\\n/u)\n : []\n\n const files = await Array.fromAsync(\n glob('**/*.svg', {\n cwd: rootDir,\n }),\n )\n\n await concurrently(\n files.map((file) => async () => {\n console.log(`Optimizing ${file}...`)\n const fullPath = path.join(rootDir, file)\n\n const originalSvg = await fs.readFile(fullPath, 'utf8')\n const optimizedSvg = await optimizeSvg(originalSvg, {\n convertedColor: replaceColor,\n withoutOptimizeBySVGO: ignorePatterns.includes(file),\n })\n await fs.writeFile(fullPath, optimizedSvg)\n }),\n )\n}\n","const SVG_EXTENSION = /\\.svg$/iu\nconst NON_CSS_CLASS_NAME_CHARACTERS = /[^a-z0-9-]+/gu\nconst DUPLICATE_DASHES = /-+/gu\nconst EDGE_DASHES = /^-+|-+$/gu\nconst LINE_SEPARATOR = /\\u2028/gu\nconst PARAGRAPH_SEPARATOR = /\\u2029/gu\n\nexport function serializeJavaScriptValue(value: unknown): string {\n return JSON.stringify(value, null, 2)\n .replace(LINE_SEPARATOR, '\\\\u2028')\n .replace(PARAGRAPH_SEPARATOR, '\\\\u2029')\n}\n\nexport function createSvgDataUri(svg: string): string {\n return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`\n}\n\nexport function createCssClassNameSegment(fileName: string): string {\n const segment = fileName\n .toLowerCase()\n .replace(SVG_EXTENSION, '')\n .replaceAll('.', '-')\n .replace(NON_CSS_CLASS_NAME_CHARACTERS, '-')\n .replace(DUPLICATE_DASHES, '-')\n .replace(EDGE_DASHES, '')\n\n return segment === '' ? 'icon' : segment\n}\n","import path from 'path'\nimport { glob } from 'node:fs/promises'\nimport fs from 'fs-extra'\nimport { serializeJavaScriptValue } from './codegen'\n\nexport const generateIconSvgEmbeddedSource = (svgString: string) => {\n const str = svgString.replace(/\\r?\\n/g, '')\n\n return `/** This file is auto generated. DO NOT EDIT BY HAND. */\nexport default ${serializeJavaScriptValue(str)}\n`\n}\n\nexport const generateMjsEntrypoint = (\n icons: string[],\n) => `/** This file is auto generated. DO NOT EDIT BY HAND. */\n\nexport default {\n${icons\n .map(\n (it) =>\n ` ${serializeJavaScriptValue(it)}: () => import(${serializeJavaScriptValue(`./${it}.js`)}).then(m => m.default)`,\n )\n .join(',\\n')}\n}\n`\n\nexport const generateCjsEntrypoint = (\n icons: string[],\n) => `/** This file is auto generated. DO NOT EDIT BY HAND. */\n\nmodule.exports = {\n${icons\n .map(\n (it) =>\n ` ${serializeJavaScriptValue(it)}: () => import(${serializeJavaScriptValue(`./${it}.js`)}).then(m => m.default)`,\n )\n .join(',\\n')}\n}\n`\n\nexport const generateTypeDefinitionEntrypoint = (\n icons: string[],\n) => `/** This file is auto generated. DO NOt EDIT BY HAND. */\n\ndeclare var _default: {\n${icons\n .map((it) => ` ${serializeJavaScriptValue(it)}: () => Promise<string>`)\n .join(';\\n')}\n};\nexport default _default;\n`\n\nexport const generateEntrypoint = async (\n outputDir: string,\n icons: string[],\n) => {\n const srcRoot = path.join(outputDir, 'src')\n const mjsPath = path.join(srcRoot, 'index.js')\n await fs.ensureFile(mjsPath)\n await fs.writeFile(mjsPath, generateMjsEntrypoint(icons))\n\n const cjsPath = path.join(srcRoot, 'index.cjs')\n await fs.ensureFile(cjsPath)\n await fs.writeFile(cjsPath, generateCjsEntrypoint(icons))\n\n const dtsPath = path.join(srcRoot, 'index.d.ts')\n await fs.ensureFile(dtsPath)\n await fs.writeFile(dtsPath, generateTypeDefinitionEntrypoint(icons))\n}\n\nexport const generateIconSource = async (outputDir: string) => {\n const svgRoot = path.join(outputDir, 'svg')\n const srcRoot = path.join(outputDir, 'src')\n const icons = (await Array.fromAsync(glob('**/*.svg', { cwd: svgRoot })))\n .map(\n (path) => path.slice(0, -4), // e.g. '16/Add.svg' -> '16/Add'\n )\n .sort()\n\n for (const it of icons) {\n const data = await fs.readFile(path.join(svgRoot, `${it}.svg`))\n const outputPath = path.join(srcRoot, `${it}.js`)\n await fs.ensureFile(outputPath)\n await fs.writeFile(\n outputPath,\n generateIconSvgEmbeddedSource(data.toString()),\n )\n }\n\n await generateEntrypoint(outputDir, icons)\n}\n","#!/usr/bin/env node\n\nimport yargs from 'yargs'\nimport { FigmaFileClient } from './figma/FigmaFileClient'\nimport { GithubClient } from './GitHubClient'\nimport { GitlabClient } from './GitlabClient'\nimport { DEFAULT_CURRENT_COLOR_TARGET } from './svg/optimizeSvg'\nimport { optimizeSvgInDirectory } from './svg/optimizeSvgInDirectory'\nimport { generateIconSource } from './generateSource'\nimport { mustBeDefined } from './utils'\n\n/**\n * Figma\n */\nconst FIGMA_TOKEN = process.env.FIGMA_TOKEN\nconst FIGMA_FILE_URL = process.env.FIGMA_FILE_URL\nconst OUTPUT_ROOT_DIR = process.env.OUTPUT_ROOT_DIR\n\n/**\n * GitLab\n */\nconst GITLAB_ACCESS_TOKEN = process.env.GITLAB_ACCESS_TOKEN\nconst GITLAB_DEFAULT_BRANCH = process.env.GITLAB_DEFAULT_BRANCH\nconst GITLAB_HOST = process.env.GITLAB_HOST\nconst GITLAB_PROJECT_ID = process.env.GITLAB_PROJECT_ID\n\n/**\n * GitHub\n */\nconst GITHUB_ACCESS_TOKEN = process.env.GITHUB_ACCESS_TOKEN\nconst GITHUB_REPO_OWNER = process.env.GITHUB_REPO_OWNER\nconst GITHUB_REPO_NAME = process.env.GITHUB_REPO_NAME\nconst GITHUB_DEFAULT_BRANCH = process.env.GITHUB_DEFAULT_BRANCH\n\nvoid yargs\n .scriptName('icons-cli')\n .command(\n 'figma:export',\n 'Load all icons from Figma and save to files',\n {\n format: {\n default: 'svg',\n choices: ['svg', 'pdf'],\n describe: 'Output format',\n },\n layout: {\n default: 'v1',\n describe: 'Figma icon file layout version',\n },\n sleepMs: {\n type: 'number',\n describe: 'Sleep duration between export requests in milliseconds',\n },\n },\n async ({ format, layout, sleepMs }) => {\n mustBeDefined(FIGMA_FILE_URL, 'FIGMA_FILE_URL')\n mustBeDefined(FIGMA_TOKEN, 'FIGMA_TOKEN')\n mustBeDefined(OUTPUT_ROOT_DIR, 'OUTPUT_ROOT_DIR')\n\n if (sleepMs !== undefined && sleepMs < 0) {\n throw new TypeError('sleepMs must be 0 or greater.')\n }\n\n await FigmaFileClient.runFromCli(\n FIGMA_FILE_URL,\n FIGMA_TOKEN,\n OUTPUT_ROOT_DIR,\n format as 'svg' | 'pdf',\n layout as 'v1' | 'v2',\n sleepMs,\n )\n },\n )\n .command(\n 'svg:optimize',\n 'Optimize svg files in output directory',\n {\n color: {\n default: DEFAULT_CURRENT_COLOR_TARGET,\n type: 'string',\n describe: 'Color code that should be converted into `currentColor`',\n },\n ignoreFile: {\n type: 'string',\n describe:\n 'A file that contains the list of path to SVG files that should not be optimized',\n },\n },\n async ({ color, ignoreFile }) => {\n mustBeDefined(OUTPUT_ROOT_DIR, 'OUTPUT_ROOT_DIR')\n\n await optimizeSvgInDirectory(OUTPUT_ROOT_DIR, color, ignoreFile)\n },\n )\n .command(\n 'files:generate',\n 'Enumerate svg files in output directory and generate icon files',\n {},\n () => {\n mustBeDefined(OUTPUT_ROOT_DIR, 'OUTPUT_ROOT_DIR')\n\n void generateIconSource(OUTPUT_ROOT_DIR).catch((e) => {\n // eslint-disable-next-line no-console\n console.error(e)\n process.exit(1)\n })\n },\n )\n .command(\n 'gitlab:mr',\n 'Create a merge request in the name of icons-cli',\n {},\n async () => {\n mustBeDefined(GITLAB_PROJECT_ID, 'GITLAB_PROJECT_ID')\n mustBeDefined(GITLAB_ACCESS_TOKEN, 'GITLAB_ACCESS_TOKEN')\n mustBeDefined(OUTPUT_ROOT_DIR, 'OUTPUT_ROOT_DIR')\n\n await GitlabClient.runFromCli(\n GITLAB_HOST ?? 'https://gitlab.com',\n Number(GITLAB_PROJECT_ID),\n GITLAB_ACCESS_TOKEN,\n GITLAB_DEFAULT_BRANCH ?? 'main',\n OUTPUT_ROOT_DIR,\n )\n },\n )\n .command(\n 'github:pr',\n 'Create a pull request in the name of icons-cli',\n {},\n async () => {\n mustBeDefined(GITHUB_ACCESS_TOKEN, 'GITHUB_ACCESS_TOKEN')\n mustBeDefined(OUTPUT_ROOT_DIR, 'OUTPUT_ROOT_DIR')\n\n await GithubClient.runFromCli(\n GITHUB_REPO_OWNER ?? 'pixiv',\n GITHUB_REPO_NAME ?? 'charcoal',\n GITHUB_ACCESS_TOKEN,\n GITHUB_DEFAULT_BRANCH ?? 'main',\n OUTPUT_ROOT_DIR,\n )\n },\n )\n .demandCommand()\n .strict()\n .help()\n .parse()\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGA,SAAgB,aAAa,OAA+B;CAC1D,MAAM,QAAQ,IAAIA,gBAAO,EAAE,aAAa,GAAG,CAAC;AAC5C,MAAK,MAAM,QAAQ,MACjB,CAAK,MAAM,IAAI,KAAK;AAEtB,OAAM,OAAO;AACb,QAAO,MAAM,QAAQ;;;;;ACAvB,MAAM,UAAU,QAAQ,QAAQ,IAAI,QAAQ;AAE5C,MAAM,sCAAoD,CACxD,uBACA,wBACD,CAAC;AAEF,SAAS,cAAc,KAAkD;CACvE,MAAM,EAAE,UAAU,iBAAiB,IAAI,IAAI,IAAI;CAE/C,MAAM,SAAS,UAAU,SAAS;AAClC,KAAI,WAAW,MACb,OAAM,IAAI,MAAM,yBAAyB;AAG3C,QAAO;EACL,QAAQ,OAAO,OAAO;EACtB,QAAQ,aAAa,IAAI,UAAU,IAAI;EACxC;;AAGH,SAAS,WAAW,MAAc;AAChC,+BAAiB,MAAM,EAAE,YAAY,MAAM,CAAC,CAAC,QAAQ,KAAK,GAAG;;AAG/D,MAAM,WAAW;AAEjB,SAAS,WAAW,MAAkB;AACpC,QAAO,SAAS,KAAK,KAAK,KAAK;;AAGjC,SAAS,gBAAgB,MAAc;AACrC,QAAO,KACJ,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,CAChD,KAAK,IAAI,CACT,aAAa;;AAGlB,SAAS,kBAAkB,WAAmB,UAAkB;CAC9D,MAAM,sBAAsB,aAAK,QAAQ,UAAU;CACnD,MAAM,WAAW,aAAK,QAAQ,qBAAqB,SAAS;CAC5D,MAAM,eAAe,aAAK,SAAS,qBAAqB,SAAS;AAEjE,KAAI,aAAa,WAAW,KAAK,IAAI,aAAK,WAAW,aAAa,CAChE,QAAO;AAGT,QAAO;;AAKT,SAAS,MAAM,IAAY;AACzB,QAAO,IAAI,SAAS,YAAY;AAC9B,aAAW,SAAS,GAAG;GACvB;;AAYJ,IAAa,kBAAb,MAA6B;CAC3B,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CAEjB,AAAQ,aAAwC,EAAE;CAClD,AAAQ,eAAe;CAEvB,aAAa,WACX,KACA,OACA,eACA,cACA,gBAA4C,MAC5C,gBACA;EACA,MAAM,SAAS,IAAI,KACjB,KACA,OACA,cACA,eACA,eACD;EAED,MAAM,YAAY,aAAK,KAAK,QAAQ,KAAK,EAAE,eAAe,aAAa;AAEvE,UAAQ,IACN,6BAA6B,IAAI,gBAAgB,gBAClD;AACD,QAAM,OAAO,QAAQ,UAAU;AAE/B,UAAQ,IAAI,WAAW;;CAGzB,YACE,KACA,qBACA,cACA,eACA,gBACA;AACA,OAAK,SAASC,SAAM,OAAO,EACzB,qBACD,CAAC;EAEF,MAAM,EAAE,QAAQ,WAAW,cAAc,IAAI;AAC7C,OAAK,SAAS;AACd,OAAK,SAAS;AAEd,OAAK,eAAe;AACpB,OAAK,gBAAgB;AACrB,OAAK,iBAAiB;;CAGxB,MAAM,QAAQ,WAAmB;AAC/B,6BAAa,UAAU;AACvB,gCAAgB,UAAU;AAE1B,QAAM,KAAK,gBAAgB;AAC3B,QAAM,KAAK,eAAe;AAC1B,QAAM,KAAK,eAAe,UAAU;;CAGtC,MAAc,iBAAiB;EAC7B,MAAM,EAAE,aAAa,MAAM,KAAK,SAAS;AAKzC,MAAI,KAAK,kBAAkB,KACzB,MAAK,iBAAiB,SAAS;MAU/B,EALE,KAAK,WAAW,SACZ,SAAS,SAAS,QAAQ,SAAS,KAAK,OAAO,KAAK,OAAO,GAC3D,SAAS,UAGP,SAAS,UAAU,KAAK,0BAA0B,MAAM,CAAC;EAGnE,MAAM,MAAM,OAAO,KAAK,KAAK,WAAW,CAAC;AACzC,MAAI,QAAQ,EACV,OAAM,IAAI,MAAM,uBAAuB;MAEvC,SAAQ,IAAI,SAAS,IAAI,QAAQ;;CAIrC,MAAc,gBAAgB;AAC5B,UAAQ,IAAI,sBAAsB;AAElC,QAAM,KAAK,oBAAoB;EAC/B,MAAM,EAAE,SAAS,MAAM,KAAK,OAAO,WAAW,KAAK,QAAQ;GACzD,QAAQ,KAAK;GACb,KAAK,OAAO,KAAK,KAAK,WAAW;GACjC,OAAO;GACP,GAAI,KAAK,gBAAgB,QAAQ,EAAE,qBAAqB,MAAM,GAAG,EAAE;GACpE,CAAC;AAEF,OAAK,MAAM,CAAC,IAAI,UAAU,OAAO,QAAQ,KAAK,OAAO,CACnD,MAAK,WAAW,IAAI,QAAQ;;CAIhC,MAAc,eAAe,WAAmB;EAC9C,MAAM,aAAa,OAAO,OAAO,KAAK,WAAW;EACjD,MAAM,8BAA8B,KAAK,kBAAkB,KAAK;EAEhE,MAAM,gBAAgB,OAAO,cAAyB;AACpD,OAAI,UAAU,UAAU,OACtB;GAGF,MAAM,WAAW,UAAU,UACvB,GAAG,gBAAgB,UAAU,QAAQ,CAAC,GAAG,UAAU,KAAK,GAAG,KAAK,iBAChE,GAAG,WAAW,UAAU,KAAK,CAAC,GAAG,KAAK;GAC1C,MAAM,WAAW,kBAAkB,WAAW,SAAS;AAEvD,OAAI,aAAa,MAAM;AACrB,YAAQ,IAAI,6BAA6B,WAAW;AACpD;;GAGF,MAAM,UAAU,aAAK,QAAQ,SAAS;AAEtC,OAAI,SAAS;AACX,YAAQ,IAAI,mBAAmB,SAAS,kBAAkB;AAC1D;;AAGF,SAAM,KAAK,oBAAoB;GAC/B,MAAM,WAAW,MAAM,YAAI,IACzB,UAAU,OACV,KAAK,gBAAgB,QAAQ,EAAE,cAAc,UAAU,GAAG,EAAE,CAC7D;AAED,iCAAgB,QAAQ;AAExB,WAAQ,IAAI,UAAU,SAAS,kBAAkB;AACjD,iCAAgB,UAAU,SAAS,MAAM,OAAO;;AAIlD,MAAI,4BAA4B;AAC9B,QAAK,MAAM,aAAa,WACtB,OAAM,cAAc,UAAU;AAGhC;;AAIF,SAAO,aACL,WAAW,KAAK,cAAc,YAAY,cAAc,UAAU,CAAC,CACpE;;CAGH,MAAc,UAAU;AACtB,UAAQ,IAAI,sBAAsB;AAElC,QAAM,KAAK,oBAAoB;EAC/B,MAAM,EAAE,SAAS,MAAM,KAAK,OAAO,KAAK,KAAK,OAAO,UAAU,CAAC;AAE/D,SAAO;;CAGT,MAAc,qBAAqB;EACjC,MAAM,iBAAiB,KAAK,kBAAkB;AAE9C,MAAI,kBAAkB,EACpB;AAGF,MAAI,KAAK,cAAc;AACrB,WAAQ,IAAI,YAAY,eAAe,+BAA+B;AACtE,SAAM,MAAM,eAAe;;AAG7B,OAAK,eAAe;;CAGtB,AAAQ,0BAA0B,OAAmB;AACnD,MAAI,MAAM,SAAS,aAAa;GAC9B,MAAM,EAAE,MAAM,OAAO;AAErB,OAAI,WAAW,MAAM,CACnB,MAAK,WAAW,MAAM;IACpB;IACA;IACD;aAEM,cAAc,MACvB,OAAM,SAAS,SAAS,eACtB,KAAK,0BAA0B,WAAW,CAC3C;;CAIL,AAAQ,iBAAiB,UAA0B;AAWjD,GAVkB,SAAS,SAAS,MACjC,UACC,MAAM,SAAS,cAAc,MAAM,SAAS,SAC/C,EACoC,SAAS,SAAS,MAAM;AAC3D,OAAI,EAAE,SAAS,QAAS,QAAO,EAAE;AACjC,UAAO,EAAE,SAAS,QACf,MAA+B,EAAE,SAAS,gBAC5C;IACD,GACiB,SAAS,QAAQ;AAClC,OAAI,SAAS,SAAS,MAAM;AAC1B,QAAI,EAAE,SAAS,YAAa,QAAO;AACnC,SAAK,WAAW,EAAE,MAAM;KACtB,MAAM,IAAI;KACV,SAAS,EAAE;KACX,IAAI,EAAE;KACP;KACD;IACF;;;;;;;;;ACvSN,MAAa,SAAS,YACpB,IAAI,SAAiB,SAAS,WAAW;AACvC,yBAAK,UAAU,KAAK,WAAW;AAC7B,MAAI,IACF,QAAO,OAAO,IAAI;AAGpB,SAAO,QAAQ,OAAO;GACtB;EACF;AAEJ,SAAgB,cACd,OACA,MACiC;AACjC,KAAI,OAAO,UAAU,YACnB,OAAM,IAAI,UAAU,GAAG,KAAK,mBAAmB;;;;;;;;ACfnD,gBAAuB,gBAAgB,MAAgB;AACrD,MAAK,MAAM,OAAO,KAChB,KAAI,oBAAY,IAAI,CAClB,OAAM,IAAI,MAAM,0CAA0C,IAAI,GAAG;CAErE,MAAM,YAAY,MAAM,kBAAkB;AAC1C,MAAK,MAAM,CAAC,cAAc,WAAW,WAAW;EAC9C,MAAM,WAAW,aAAK,QAAQ,QAAQ,KAAK,EAAE,aAAa;AAC1D,MAAI,CAAC,KAAK,MAAM,QAAQ,SAAS,WAAW,GAAG,IAAI,GAAG,CAAC,CACrD;AAEF,MAAI,WAAW,aAAa,oBAAY,SAAS,CAC/C,OAAM,IAAI,MAAM,mCAAmC,SAAS,GAAG;AAKjE,QAAM;GAAE;GAAc,SAHpB,WAAW,YACP,KACA,MAAM,YAAG,SAAS,UAAU,EAAE,UAAU,SAAS,CAAC;GACzB;GAAQ;;;AAI3C,eAAe,mBAAmB;AAChC,QAAO,IAAI;;;;GAIR,MAAM,MAAM,+BAA+B,EAAE,MAAM,KAAK,CAAC,KAAK,MAAM;AACnE,UAAO,CACL,EAAE,MAAM,EAAE,EACV,EAAE,WAAW,KAAK,GACd,aACA,EAAE,WAAW,KAAK,GAChB,cACA,EAAE,WAAW,KAAK,GAChB,YACA,KACT;IACD;EACH;;;;;AC9BH,IAAa,eAAb,MAA0B;CACxB,AAAiB;CACjB,AAAiB;CAEjB,aAAa,WACX,WACA,UACA,OACA,eACA,WAC+D;EAC/D,MAAM,SAAS,IAAI,KAAK,WAAW,UAAU,OAAO,cAAc;EAClE,MAAM,oBAAoB,aAAK,QAAQ,QAAQ,KAAK,EAAE,UAAU;EAChE,MAAM,OAAO,MAAM,OAAO,mBAAmB,kBAAkB;AAE/D,UAAQ,IAAI,GAAG,KAAK,OAAO,oBAAoB;AAC/C,MAAI,KAAK,WAAW,GAAG;AAErB,WAAQ,IAAI,uBAAuB;AACnC;;EAGF,MAAM,YAAY,MAAM,OAAO,cAAc;AAE7C,QAAM,OAAO,aAAa,MAAM,UAAU;AAC1C,SAAO,OAAO,kBAAkB,UAAU;;CAG5C,YACE,AAAiB,WACjB,AAAiB,UACjB,OACA,AAAiB,eACjB,sBAAM,IAAI,MAAM,EAChB;EALiB;EACA;EAEA;AAGjB,OAAK,MAAM,IAAIC,sBAAQ,EACrB,MAAM,OACP,CAAC;AAEF,OAAK,MAAM;;CAGb,IAAI,SAAS;AACX,SAAO,gBAAgB,KAAK,IAAI,SAAS;;;;;CAM3C,IAAI,UAAU;AACZ,SAAO,4BAA4B,KAAK,IAAI,cAAc;;CAG5D,MAAM,mBAAmB,WAAwC;EAC/D,MAAM,OAAmB,EAAE;AAE3B,aAAW,MAAM,QAAQ,gBAAgB,CAAC,UAAU,CAAC,EAAE;GACrD,MAAM,OAAO;IACX,MAAM,KAAK;IAGX,MAAM;IACN,SAAS,KAAK;IACf;AAED,OAAI,KAAK,WAAW,UAElB,MAAK,KAAK;IACR,GAAG;IACH,KAAK;IACN,CAAC;OAEF,MAAK,KAAK,KAAK;;AAInB,SAAO;;CAGT,MAAM,aACJ,MACA,cACqE;EACrE,MAAM,eAAe,MAAM,KAAK,IAAI,IAAI,UAAU;GAChD,OAAO,KAAK;GACZ,MAAM,KAAK;GAEX,YAAY,aAAa,KAAK,OAAO;GACtC,CAAC;EAEF,MAAM,UAAU,MAAM,KAAK,IAAI,IAAI,WAAW;GAC5C,OAAO,KAAK;GACZ,MAAM,KAAK;GAEX,WAAW,aAAa,KAAK,KAAK;GAClC;GACD,CAAC;EAGF,MAAM,SAAS,MAAM,KAAK,IAAI,IAAI,aAAa;GAC7C,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,SAAS,KAAK;GACd,MAAM,QAAQ,KAAK;GACnB,SAAS,CAAC,aAAa,KAAK,IAAI;GACjC,CAAC;AAGF,QAAM,KAAK,IAAI,IAAI,UAAU;GAC3B,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,KAAK,SAAS,KAAK;GACnB,KAAK,OAAO,KAAK;GAClB,CAAC;AAEF,SAAO;;CAGT,MAAM,kBACJ,cAC6D;EAC7D,MAAM,gBAAgB,MAAM,KAAK,qBAAqB;AAEtD,SAAO,KAAK,IAAI,MAAM,OAAO;GAC3B,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,MAAM,aAAa,KAAK;GACxB,MAAM,cAAc,KAAK;GACzB,OAAO,KAAK;GACZ,MAAM;GACP,CAAC;;CAGJ,AAAQ,sBAAsB;AAC5B,SAAO,KAAK,IAAI,IAAI,OAAO;GACzB,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,KAAK,SAAS,KAAK;GACpB,CAAC;;CAGJ,MAAM,eAAoE;EACxE,MAAM,gBAAgB,MAAM,KAAK,qBAAqB;AAEtD,SAAO,KAAK,IAAI,IAAI,UAAU;GAC5B,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,KAAK,cAAc,KAAK;GACxB,KAAK,cAAc,KAAK,OAAO;GAChC,CAAC;;;;;;AC7JN,IAAa,eAAb,MAA0B;CACxB,AAAiB;CACjB,AAAiB;CAEjB,aAAa,WACX,MACA,WACA,cACA,eACA,WACA;EACA,MAAM,SAAS,IAAI,KAAK,MAAM,WAAW,cAAc,cAAc;EACrE,MAAM,oBAAoB,aAAK,QAAQ,QAAQ,KAAK,EAAE,UAAU;EAChE,MAAM,OAAO,MAAM,OAAO,sBAAsB,kBAAkB;AAElE,UAAQ,IAAI,GAAG,KAAK,OAAO,oBAAoB;AAC/C,MAAI,KAAK,WAAW,GAAG;AAErB,WAAQ,IAAI,uBAAuB;AACnC;;AAGF,QAAM,OAAO,aAAa,KAAK;AAC/B,SAAO,OAAO,oBAAoB;;CAGpC,YACE,AAAiB,MACjB,AAAiB,WACjB,cACA,AAAiB,eACjB,sBAAM,IAAI,MAAM,EAChB;EALiB;EACA;EAEA;AAGjB,OAAK,MAAM,IAAIC,uBAAO;GACpB,MAAM,KAAK;GACX,OAAO;GACR,CAAC;AACF,OAAK,MAAM;;CAGb,IAAI,SAAS;AACX,SAAO,gBAAgB,KAAK,IAAI,SAAS;;;;;CAM3C,IAAI,UAAU;AACZ,SAAO,4BAA4B,KAAK,IAAI,cAAc;;CAG5D,MAAM,sBAAsB,WAA4C;EACtE,MAAM,UAA0B,EAAE;AAElC,aAAW,MAAM,QAAQ,gBAAgB,CAAC,UAAU,CAAC,CACnD,SAAQ,KAAK;GACX,QACE,KAAK,WAAW,cACZ,WACA,KAAK,WAAW,YACd,WACA;GACR,UAAU,KAAK;GACf,SAAS,KAAK;GACf,CAAC;AAGJ,SAAO;;CAGT,MAAM,aAAa,MAAsB;AACvC,SAAO,KAAK,IAAI,QAAQ,OACtB,KAAK,WACL,KAAK,QACL,KAAK,SACL,MACA,EACE,cAAc,KAAK,eACpB,CACF;;CAGH,qBAAqB;AACnB,SAAO,KAAK,IAAI,cAAc,OAC5B,KAAK,WACL,KAAK,QACL,KAAK,eACL,KAAK,QACN;;;;;;AC1FL,MAAa,+BAA+B;AAE5C,MAAMC,SAAO,IAAIC,aAAK,EACpB,SAAS,CAKP,EAAE,eAAe,OAAO,EACxB,EAAE,aAAa,EAAE,OAAO,CAAC,kBAAkB,eAAe,EAAE,EAAE,CAC/D,EACF,CAAC;AAgBF,eAAsB,YAAY,OAAe,SAAkB;CACjE,MAAM,EAAE,aAAa,IAAIC,YAAM,MAAM,CAAC;CACtC,MAAM,MAAM,SAAS,cAAc,MAAM;AACzC,KAAI,CAAC,IACH,OAAM,IAAI,MAAM,oDAAoD;AAGtE,qBAAoB,IAAI;AACxB,uBAAsB,KAAK,QAAQ,eAAe;AAElD,KAAI,QAAQ,0BAA0B,KACpC,QAAO,IAAI;KAEX,SAAQ,MAAMF,OAAK,SAAS,IAAI,UAAU,EAAE;;AAIhD,MAAM,eAAe,CAAC,QAAQ,SAAS;AAEvC,SAAS,sBAAsB,KAAoB,gBAAwB;CACzE,MAAM,cAAc,WAAW,eAAe;AAC9C,KAAI,CAAC,YACH,OAAM,IAAI,MAAM,GAAG,eAAe,uBAAuB;AAG3D,MAAK,MAAM,QAAQ,cAAc;EAC/B,MAAM,UAAU,MAAM,KAAK,IAAI,iBAA6B,IAAI,KAAK,GAAG,CAAC;AAEzE,OAAK,MAAM,MAAM,SAAS;GACxB,MAAM,QAAQ,WAAW,GAAG,aAAa,KAAK,CAAC;AAC/C,OAAI,CAAC,MACH;AAGF,OAAI,CAAC,YAAY,OAAO,YAAY,CAClC;AAGF,MAAG,aAAa,MAAM,eAAe;;;;AAK3C,SAAS,WAAW,OAAsB;AACxC,KAAI,SAAS,KACX,QAAO;AAGT,KAAI;AACF,kCAAkB,MAAM;SAClB;AACN,SAAO;;;AAIX,SAAS,YAAY,MAA4B,OAA6B;AAC5E,KAAI,KAAK,QAAQ,MAAM,IACrB,QAAO;AAGT,KAAI,KAAK,SAAS,MAAM,KACtB,QAAO;AAGT,KAAI,KAAK,UAAU,MAAM,MACvB,QAAO;AAGT,KAAI,WAAW,MACb;MAAI,WAAW,OACb;OAAI,KAAK,UAAU,MAAM,MACvB,QAAO;;;AAKb,QAAO;;AAGT,SAAS,oBAAoB,KAAoB;CAE/C,MAAM,QAAQ,IAAI,aAAa,QAAQ;CAEvC,MAAM,SAAS,IAAI,aAAa,SAAS;AAEzC,KAAI,aAAa,WAAW,OAAO,MAAM,GAAG,SAAS;;;;;AC7GvD,MAAa,yBAAyB,OACpC,WACA,cACA,eACG;CACH,MAAM,UAAU,aAAK,KAAK,WAAW,MAAM;CAE3C,MAAM,iBACJ,eAAe,UACV,MAAMG,iBAAG,SAAS,YAAY,OAAO,EAAE,MAAM,CAAC,MAAM,SAAS,GAC9D,EAAE;AAQR,OAAM,cANQ,MAAM,MAAM,gCACnB,YAAY,EACf,KAAK,SACN,CAAC,CACH,EAGO,KAAK,SAAS,YAAY;AAC9B,UAAQ,IAAI,cAAc,KAAK,KAAK;EACpC,MAAM,WAAW,aAAK,KAAK,SAAS,KAAK;EAGzC,MAAM,eAAe,MAAM,YADP,MAAMA,iBAAG,SAAS,UAAU,OAAO,EACH;GAClD,gBAAgB;GAChB,uBAAuB,eAAe,SAAS,KAAK;GACrD,CAAC;AACF,QAAMA,iBAAG,UAAU,UAAU,aAAa;GAC1C,CACH;;;;;AClCH,MAAM,iBAAiB;AACvB,MAAM,sBAAsB;AAE5B,SAAgB,yBAAyB,OAAwB;AAC/D,QAAO,KAAK,UAAU,OAAO,MAAM,EAAE,CAClC,QAAQ,gBAAgB,UAAU,CAClC,QAAQ,qBAAqB,UAAU;;;;;ACL5C,MAAa,iCAAiC,cAAsB;AAGlE,QAAO;iBACQ,yBAHH,UAAU,QAAQ,UAAU,GAAG,CAGC,CAAC;;;AAI/C,MAAa,yBACX,UACG;;;EAGH,MACC,KACE,OACC,KAAK,yBAAyB,GAAG,CAAC,iBAAiB,yBAAyB,KAAK,GAAG,KAAK,CAAC,wBAC7F,CACA,KAAK,MAAM,CAAC;;;AAIf,MAAa,yBACX,UACG;;;EAGH,MACC,KACE,OACC,KAAK,yBAAyB,GAAG,CAAC,iBAAiB,yBAAyB,KAAK,GAAG,KAAK,CAAC,wBAC7F,CACA,KAAK,MAAM,CAAC;;;AAIf,MAAa,oCACX,UACG;;;EAGH,MACC,KAAK,OAAO,KAAK,yBAAyB,GAAG,CAAC,yBAAyB,CACvE,KAAK,MAAM,CAAC;;;;AAKf,MAAa,qBAAqB,OAChC,WACA,UACG;CACH,MAAM,UAAU,aAAK,KAAK,WAAW,MAAM;CAC3C,MAAM,UAAU,aAAK,KAAK,SAAS,WAAW;AAC9C,OAAMC,iBAAG,WAAW,QAAQ;AAC5B,OAAMA,iBAAG,UAAU,SAAS,sBAAsB,MAAM,CAAC;CAEzD,MAAM,UAAU,aAAK,KAAK,SAAS,YAAY;AAC/C,OAAMA,iBAAG,WAAW,QAAQ;AAC5B,OAAMA,iBAAG,UAAU,SAAS,sBAAsB,MAAM,CAAC;CAEzD,MAAM,UAAU,aAAK,KAAK,SAAS,aAAa;AAChD,OAAMA,iBAAG,WAAW,QAAQ;AAC5B,OAAMA,iBAAG,UAAU,SAAS,iCAAiC,MAAM,CAAC;;AAGtE,MAAa,qBAAqB,OAAO,cAAsB;CAC7D,MAAM,UAAU,aAAK,KAAK,WAAW,MAAM;CAC3C,MAAM,UAAU,aAAK,KAAK,WAAW,MAAM;CAC3C,MAAM,SAAS,MAAM,MAAM,qCAAe,YAAY,EAAE,KAAK,SAAS,CAAC,CAAC,EACrE,KACE,WAASC,OAAK,MAAM,GAAG,GAAG,CAC5B,CACA,MAAM;AAET,MAAK,MAAM,MAAM,OAAO;EACtB,MAAM,OAAO,MAAMD,iBAAG,SAAS,aAAK,KAAK,SAAS,GAAG,GAAG,MAAM,CAAC;EAC/D,MAAM,aAAa,aAAK,KAAK,SAAS,GAAG,GAAG,KAAK;AACjD,QAAMA,iBAAG,WAAW,WAAW;AAC/B,QAAMA,iBAAG,UACP,YACA,8BAA8B,KAAK,UAAU,CAAC,CAC/C;;AAGH,OAAM,mBAAmB,WAAW,MAAM;;;;;;;;AC5E5C,MAAM,cAAc,QAAQ,IAAI;AAChC,MAAM,iBAAiB,QAAQ,IAAI;AACnC,MAAM,kBAAkB,QAAQ,IAAI;;;;AAKpC,MAAM,sBAAsB,QAAQ,IAAI;AACxC,MAAM,wBAAwB,QAAQ,IAAI;AAC1C,MAAM,cAAc,QAAQ,IAAI;AAChC,MAAM,oBAAoB,QAAQ,IAAI;;;;AAKtC,MAAM,sBAAsB,QAAQ,IAAI;AACxC,MAAM,oBAAoB,QAAQ,IAAI;AACtC,MAAM,mBAAmB,QAAQ,IAAI;AACrC,MAAM,wBAAwB,QAAQ,IAAI;AAErC,cACF,WAAW,YAAY,CACvB,QACC,gBACA,+CACA;CACE,QAAQ;EACN,SAAS;EACT,SAAS,CAAC,OAAO,MAAM;EACvB,UAAU;EACX;CACD,QAAQ;EACN,SAAS;EACT,UAAU;EACX;CACD,SAAS;EACP,MAAM;EACN,UAAU;EACX;CACF,EACD,OAAO,EAAE,QAAQ,QAAQ,cAAc;AACrC,eAAc,gBAAgB,iBAAiB;AAC/C,eAAc,aAAa,cAAc;AACzC,eAAc,iBAAiB,kBAAkB;AAEjD,KAAI,YAAY,UAAa,UAAU,EACrC,OAAM,IAAI,UAAU,gCAAgC;AAGtD,OAAM,gBAAgB,WACpB,gBACA,aACA,iBACA,QACA,QACA,QACD;EAEJ,CACA,QACC,gBACA,0CACA;CACE,OAAO;EACL,SAAS;EACT,MAAM;EACN,UAAU;EACX;CACD,YAAY;EACV,MAAM;EACN,UACE;EACH;CACF,EACD,OAAO,EAAE,OAAO,iBAAiB;AAC/B,eAAc,iBAAiB,kBAAkB;AAEjD,OAAM,uBAAuB,iBAAiB,OAAO,WAAW;EAEnE,CACA,QACC,kBACA,mEACA,EAAE,QACI;AACJ,eAAc,iBAAiB,kBAAkB;AAEjD,CAAK,mBAAmB,gBAAgB,CAAC,OAAO,MAAM;AAEpD,UAAQ,MAAM,EAAE;AAChB,UAAQ,KAAK,EAAE;GACf;EAEL,CACA,QACC,aACA,mDACA,EAAE,EACF,YAAY;AACV,eAAc,mBAAmB,oBAAoB;AACrD,eAAc,qBAAqB,sBAAsB;AACzD,eAAc,iBAAiB,kBAAkB;AAEjD,OAAM,aAAa,WACjB,eAAe,sBACf,OAAO,kBAAkB,EACzB,qBACA,yBAAyB,QACzB,gBACD;EAEJ,CACA,QACC,aACA,kDACA,EAAE,EACF,YAAY;AACV,eAAc,qBAAqB,sBAAsB;AACzD,eAAc,iBAAiB,kBAAkB;AAEjD,OAAM,aAAa,WACjB,qBAAqB,SACrB,oBAAoB,YACpB,qBACA,yBAAyB,QACzB,gBACD;EAEJ,CACA,eAAe,CACf,QAAQ,CACR,MAAM,CACN,OAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@charcoal-ui/icons-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.0-beta.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": "./dist/index.js",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"build:css-v1": "VERSION=v1 SOURCE_ROOT_DIR=../../../icon-files/svg/ OUTPUT_ROOT_DIR=../icons/css/v1 pnpm tsx src/v2/transformCSS.ts",
|
|
50
50
|
"build:uri": "SOURCE_ROOT_DIR=../../../icon-files/v2/svg/ OUTPUT_ROOT_DIR=../icon-files/v2/datauri pnpm tsx src/v2/transformDataUri.ts",
|
|
51
51
|
"build:uri-v1": "SOURCE_ROOT_DIR=../../../icon-files/svg/ OUTPUT_ROOT_DIR=../icon-files/v1/datauri pnpm tsx src/v2/transformDataUri.ts",
|
|
52
|
+
"test": "vitest run --config vitest.config.ts",
|
|
52
53
|
"typecheck": "tsc --pretty --noEmit",
|
|
53
54
|
"clean": "rimraf dist .tsbuildinfo",
|
|
54
55
|
"cli:icons": "./dist/index.js",
|
package/src/GitHubClient.ts
CHANGED
|
@@ -69,7 +69,7 @@ export class GithubClient {
|
|
|
69
69
|
async createTreeFromDiff(outputDir: string): Promise<TreeItem[]> {
|
|
70
70
|
const tree: TreeItem[] = []
|
|
71
71
|
|
|
72
|
-
for await (const file of getChangedFiles(outputDir)) {
|
|
72
|
+
for await (const file of getChangedFiles([outputDir])) {
|
|
73
73
|
const item = {
|
|
74
74
|
path: file.relativePath,
|
|
75
75
|
// 100 はファイル 644 は実行不可なファイルであるという意味
|
package/src/GitlabClient.ts
CHANGED
|
@@ -59,7 +59,7 @@ export class GitlabClient {
|
|
|
59
59
|
async createActionsFromDiff(outputDir: string): Promise<CommitAction[]> {
|
|
60
60
|
const actions: CommitAction[] = []
|
|
61
61
|
|
|
62
|
-
for await (const file of getChangedFiles(outputDir)) {
|
|
62
|
+
for await (const file of getChangedFiles([outputDir])) {
|
|
63
63
|
actions.push({
|
|
64
64
|
action:
|
|
65
65
|
file.status === 'untracked'
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import vm from 'node:vm'
|
|
2
|
+
import { JSDOM } from 'jsdom'
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
import {
|
|
5
|
+
createCssClassNameSegment,
|
|
6
|
+
createSvgDataUri,
|
|
7
|
+
serializeJavaScriptValue,
|
|
8
|
+
} from './codegen'
|
|
9
|
+
import {
|
|
10
|
+
generateCjsEntrypoint,
|
|
11
|
+
generateIconSvgEmbeddedSource,
|
|
12
|
+
} from './generateSource'
|
|
13
|
+
|
|
14
|
+
describe('serializeJavaScriptValue', () => {
|
|
15
|
+
it.each([
|
|
16
|
+
'',
|
|
17
|
+
`O'Reilly said "hello" twice`,
|
|
18
|
+
String.raw`C:\icons\multi\\slash`,
|
|
19
|
+
`''''`,
|
|
20
|
+
`</script><script>alert("xss")</script>`,
|
|
21
|
+
])('JS文字列として安全にシリアライズできる: %j', (value) => {
|
|
22
|
+
const script = new vm.Script(`value = ${serializeJavaScriptValue(value)}`)
|
|
23
|
+
const context = vm.createContext({ value: undefined as string | undefined })
|
|
24
|
+
script.runInContext(context)
|
|
25
|
+
|
|
26
|
+
expect(context.value).toBe(value)
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('createSvgDataUri', () => {
|
|
31
|
+
it('通常のSVGでもdata URIを生成できる', () => {
|
|
32
|
+
expect(
|
|
33
|
+
createSvgDataUri('<svg xmlns="http://www.w3.org/2000/svg"></svg>'),
|
|
34
|
+
).toBe(
|
|
35
|
+
'data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3C%2Fsvg%3E',
|
|
36
|
+
)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('複数クォートやバックスラッシュを含むSVGでもCSSに安全に埋め込める', () => {
|
|
40
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg"><text>"\\" '' ''' </style><script>alert(1)</script></text></svg>`
|
|
41
|
+
const dataUri = createSvgDataUri(svg)
|
|
42
|
+
|
|
43
|
+
expect(dataUri.startsWith('data:image/svg+xml;utf8,')).toBe(true)
|
|
44
|
+
expect(dataUri).not.toContain('<svg')
|
|
45
|
+
expect(dataUri).not.toContain('</style>')
|
|
46
|
+
expect(dataUri).not.toContain('<script>')
|
|
47
|
+
expect(dataUri).toContain('%22')
|
|
48
|
+
expect(dataUri).toContain('%5C')
|
|
49
|
+
|
|
50
|
+
const dom = new JSDOM(
|
|
51
|
+
'<!doctype html><html><head></head><body></body></html>',
|
|
52
|
+
)
|
|
53
|
+
const style = dom.window.document.createElement('style')
|
|
54
|
+
style.textContent = `.icon { background-image: url("${dataUri}"); }`
|
|
55
|
+
dom.window.document.head.append(style)
|
|
56
|
+
|
|
57
|
+
expect(style.sheet?.cssRules).toHaveLength(1)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('createCssClassNameSegment', () => {
|
|
62
|
+
it('既存の通常ケースを維持する', () => {
|
|
63
|
+
expect(createCssClassNameSegment('Add.Circle.svg')).toBe('add-circle')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('危険な文字をCSS class名から除去する', () => {
|
|
67
|
+
expect(createCssClassNameSegment(`Bad "Name"..svg`)).toBe('bad-name')
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('generateSource', () => {
|
|
72
|
+
it('空文字や連続クォートを含むSVGでも安全なモジュールソースを生成する', () => {
|
|
73
|
+
const source = generateIconSvgEmbeddedSource(`''""\\\n`)
|
|
74
|
+
|
|
75
|
+
expect(source).toContain(serializeJavaScriptValue(`''""\\`))
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('危険なキー名を含んでもCJSエントリポイントが壊れない', () => {
|
|
79
|
+
const iconName = String.raw`16/Bad'"Name\Icon`
|
|
80
|
+
const moduleContext = { module: { exports: {} as Record<string, unknown> } }
|
|
81
|
+
const script = new vm.Script(generateCjsEntrypoint([iconName]))
|
|
82
|
+
script.runInNewContext(moduleContext)
|
|
83
|
+
|
|
84
|
+
expect(Object.keys(moduleContext.module.exports)).toEqual([iconName])
|
|
85
|
+
})
|
|
86
|
+
})
|
package/src/codegen.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const SVG_EXTENSION = /\.svg$/iu
|
|
2
|
+
const NON_CSS_CLASS_NAME_CHARACTERS = /[^a-z0-9-]+/gu
|
|
3
|
+
const DUPLICATE_DASHES = /-+/gu
|
|
4
|
+
const EDGE_DASHES = /^-+|-+$/gu
|
|
5
|
+
const LINE_SEPARATOR = /\u2028/gu
|
|
6
|
+
const PARAGRAPH_SEPARATOR = /\u2029/gu
|
|
7
|
+
|
|
8
|
+
export function serializeJavaScriptValue(value: unknown): string {
|
|
9
|
+
return JSON.stringify(value, null, 2)
|
|
10
|
+
.replace(LINE_SEPARATOR, '\\u2028')
|
|
11
|
+
.replace(PARAGRAPH_SEPARATOR, '\\u2029')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createSvgDataUri(svg: string): string {
|
|
15
|
+
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createCssClassNameSegment(fileName: string): string {
|
|
19
|
+
const segment = fileName
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
.replace(SVG_EXTENSION, '')
|
|
22
|
+
.replaceAll('.', '-')
|
|
23
|
+
.replace(NON_CSS_CLASS_NAME_CHARACTERS, '-')
|
|
24
|
+
.replace(DUPLICATE_DASHES, '-')
|
|
25
|
+
.replace(EDGE_DASHES, '')
|
|
26
|
+
|
|
27
|
+
return segment === '' ? 'icon' : segment
|
|
28
|
+
}
|