Skip to content

Commit

Permalink
Merge 591bdd5 into f6d05e6
Browse files Browse the repository at this point in the history
  • Loading branch information
marcomontalbano committed May 21, 2022
2 parents f6d05e6 + 591bdd5 commit 23f48d7
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 25 deletions.
1 change: 1 addition & 0 deletions .figmaexportrc.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = {
['styles', {
fileId: 'fzYhvQpqwhZDUImRz431Qo',
// version: 'xxx123456', // optional - file's version history is only supported on paid Figma plans
// onlyFromPages: ['icons'], // optional - Figma page names (all pages when not specified)
outputters: [
require('@figma-export/output-styles-as-sass')({
output: './output'
Expand Down
1 change: 1 addition & 0 deletions .figmaexportrc.example.local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import outputComponentsAsSvgstore from './packages/output-components-as-svgstore

const styleOptions: StylesCommandOptions = {
fileId: 'fzYhvQpqwhZDUImRz431Qo',
// onlyFromPages: ['icons'], // optional - Figma page names (all pages when not specified)
outputters: [
outputStylesAsCss({
output: './output/styles/css'
Expand Down
1 change: 1 addition & 0 deletions .figmaexportrc.example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import outputComponentsAsEs6 from '@figma-export/output-components-as-es6';
const styleOptions: StylesCommandOptions = {
fileId: 'fzYhvQpqwhZDUImRz431Qo',
// version: 'xxx123456', // optional - file's version history is only supported on paid Figma plans
// onlyFromPages: ['icons'], // optional - Figma page names (all pages when not specified)
outputters: [
outputStylesAsSass({
output: './output'
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ module.exports = {
['styles', {
fileId: 'fzYhvQpqwhZDUImRz431Qo',
// version: 'xxx123456', // optional - file's version history is only supported on paid Figma plans
// onlyFromPages: ['icons'], // optional - Figma page names (all pages when not specified)
outputters: [
require('@figma-export/output-styles-as-sass')({
output: './output/styles'
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/commands/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const addStyles = (prog: Sade, spinner: Ora) => prog
.describe('Export styles from a Figma file.')
.option('-O, --outputter', 'Outputter module or path')
.option('-o, --output', 'Output directory', 'output')
.option('-p, --page', 'Figma page names (all pages when not specified)')
.option('--fileVersion', `A specific version ID to get. Omitting this will get the current version of the file.
https://help.figma.com/hc/en-us/articles/360038006754-View-a-file-s-version-history`)
.example('styles fzYhvQpqwhZDUImRz431Qo -O @figma-export/output-styles-as-css')
Expand All @@ -21,6 +22,7 @@ export const addStyles = (prog: Sade, spinner: Ora) => prog
...opts
}) => {
const outputter = asArray<string>(opts.outputter);
const page = asArray<string>(opts.page);

spinner.info(`Exporting ${fileId} as [${outputter.join(', ')}]`);

Expand All @@ -30,6 +32,7 @@ export const addStyles = (prog: Sade, spinner: Ora) => prog
fileId,
version: fileVersion,
token: process.env.FIGMA_TOKEN || '',
onlyFromPages: page,
outputters: requirePackages<FigmaExport.StyleOutputter>(outputter, { output }),

// eslint-disable-next-line no-param-reassign
Expand Down
81 changes: 65 additions & 16 deletions packages/core/src/lib/export-styles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import * as FigmaExport from './figma';

import { styles as exportStyles } from './export-styles';

import file from './_mocks_/figma.files.json';
import fileNodes from './_mocks_/figma.fileNodes.json';
import fileJson from './_mocks_/figma.files.json';
import fileNodesJson from './_mocks_/figma.fileNodes.json';

const nodeIds = Object.keys(fileNodes.nodes);
const file = fileJson as Figma.FileResponse;
const fileNodes = fileNodesJson as Figma.FileNodesResponse;

const fileNodeIds = Object.keys(fileNodes.nodes);

describe('export-styles', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -54,13 +57,39 @@ describe('export-styles', () => {
});

expect(FigmaExport.getClient).to.have.been.calledOnceWithExactly('token1234');
expect(clientFileNodes).to.have.been.calledOnceWith('fileABCD', { ids: nodeIds, version: 'versionABCD' });
expect(clientFile).to.have.been.calledOnceWithExactly('fileABCD', { version: 'versionABCD' });
expect(clientFileNodes).to.have.been.calledOnceWith('fileABCD', { ids: fileNodeIds, version: 'versionABCD' });
expect(clientFile.firstCall).to.have.been.calledWith('fileABCD', { version: 'versionABCD', depth: 1 });
expect(clientFile.secondCall).to.have.been.calledWith('fileABCD', { version: 'versionABCD', ids: undefined });

expect(logger).to.have.been.callCount(4);
expect(logger.getCall(0)).to.have.been.calledWith('fetching document');
expect(logger.getCall(1)).to.have.been.calledWith('fetching styles');
expect(logger.getCall(2)).to.have.been.calledWith('parsing styles');
expect(logger.getCall(3)).to.have.been.calledWith('exported styles from fileABCD');

expect(outputter).to.have.been.calledOnceWithExactly(pagesWithSvg);
});

it('should use outputter to export styles', async () => {
const pagesWithSvg = await exportStyles({
fileId: 'fileABCD',
version: 'versionABCD',
onlyFromPages: ['octicons-by-github'],
token: 'token1234',
log: logger,
outputters: [outputter],
});

expect(FigmaExport.getClient).to.have.been.calledOnceWithExactly('token1234');
expect(clientFileNodes).to.have.been.calledOnceWith('fileABCD', { ids: fileNodeIds, version: 'versionABCD' });
expect(clientFile.firstCall).to.have.been.calledWith('fileABCD', { version: 'versionABCD', depth: 1 });
expect(clientFile.secondCall).to.have.been.calledWith('fileABCD', { version: 'versionABCD', ids: ['254:0'] });

expect(logger).to.have.been.calledThrice;
expect(logger.firstCall).to.have.been.calledWith('fetching styles');
expect(logger.secondCall).to.have.been.calledWith('parsing styles');
expect(logger.thirdCall).to.have.been.calledWith('exported styles from fileABCD');
expect(logger).to.have.been.callCount(4);
expect(logger.getCall(0)).to.have.been.calledWith('fetching document');
expect(logger.getCall(1)).to.have.been.calledWith('fetching styles');
expect(logger.getCall(2)).to.have.been.calledWith('parsing styles');
expect(logger.getCall(3)).to.have.been.calledWith('exported styles from fileABCD');

expect(outputter).to.have.been.calledOnceWithExactly(pagesWithSvg);
});
Expand All @@ -72,24 +101,44 @@ describe('export-styles', () => {
});

/* eslint-disable no-console */
expect(console.log).to.have.been.calledThrice;
expect((console.log as sinon.SinonSpy<unknown[], unknown>).firstCall).to.have.been.calledWith('fetching styles');
expect((console.log as sinon.SinonSpy<unknown[], unknown>).secondCall).to.have.been.calledWith('parsing styles');
expect((console.log as sinon.SinonSpy<unknown[], unknown>).thirdCall).to.have.been.calledWith('exported styles from fileABCD');
expect(console.log).to.have.been.callCount(4);
expect((console.log as sinon.SinonSpy<unknown[], unknown>).getCall(0)).to.have.been.calledWith('fetching document');
expect((console.log as sinon.SinonSpy<unknown[], unknown>).getCall(1)).to.have.been.calledWith('fetching styles');
expect((console.log as sinon.SinonSpy<unknown[], unknown>).getCall(2)).to.have.been.calledWith('parsing styles');
expect((console.log as sinon.SinonSpy<unknown[], unknown>).getCall(3)).to.have.been.calledWith('exported styles from fileABCD');
});

it('should throw an error when fetching file fails', async () => {
clientFile.returns(Promise.reject(new Error('some error')));
it('should throw an error when fetching document fails', async () => {
clientFile.onFirstCall().returns(Promise.reject(new Error('some error')));

await expect(exportStyles({
fileId: 'fileABCD',
token: 'token1234',
})).to.be.rejectedWith(Error, 'while fetching file "fileABCD": some error');
});

it('should throw an error if styles property is missing when fetching file', async () => {
it('should throw an error when fetching styles fails', async () => {
clientFile.onFirstCall().returns(Promise.resolve({ data: { document: file.document } }));
clientFile.onSecondCall().returns(Promise.reject(new Error('some error')));

await expect(exportStyles({
fileId: 'fileABCD',
token: 'token1234',
})).to.be.rejectedWith(Error, 'while fetching file "fileABCD": some error');
});

it('should throw an error if pages are missing when fetching file', async () => {
clientFile.returns(Promise.resolve({}));

await expect(exportStyles({
fileId: 'fileABCD',
token: 'token1234',
})).to.be.rejectedWith(Error, '\'document\' is missing.');
});

it('should throw an error if styles property is missing when fetching file', async () => {
clientFile.returns(Promise.resolve({ data: { document: file.document } }));

await expect(exportStyles({
fileId: 'fileABCD',
token: 'token1234',
Expand Down
17 changes: 15 additions & 2 deletions packages/core/src/lib/export-styles.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as FigmaExport from '@figma-export/types';

import { getClient } from './figma';
import { getClient, getPages } from './figma';
import { fetchStyles, parseStyles } from './figmaStyles';

export const styles: FigmaExport.StylesCommand = async ({
token,
fileId,
version,
onlyFromPages = [],
outputters = [],
log = (msg): void => {
// eslint-disable-next-line no-console
Expand All @@ -15,8 +16,20 @@ export const styles: FigmaExport.StylesCommand = async ({
}) => {
const client = getClient(token);

log('fetching document');
const { data: { document = null } = {} } = await client.file(fileId, { version, depth: 1 }).catch((error: Error) => {
throw new Error(`while fetching file "${fileId}${version ? `?version=${version}` : ''}": ${error.message}`);
});

if (!document) {
throw new Error('\'document\' is missing.');
}

const ids = getPages((document), { only: onlyFromPages })
.map((page) => page.id);

log('fetching styles');
const styleNodes = await fetchStyles(client, fileId, version);
const styleNodes = await fetchStyles(client, fileId, version, onlyFromPages.length > 0 ? ids : undefined);

log('parsing styles');
const parsedStyles = parseStyles(styleNodes);
Expand Down
13 changes: 8 additions & 5 deletions packages/core/src/lib/figmaStyles/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import * as FigmaExport from '@figma-export/types';

import * as figmaStyles from './index';

import file from '../_mocks_/figma.files.json';
import fileNodes from '../_mocks_/figma.fileNodes.json';
import fileJson from '../_mocks_/figma.files.json';
import fileNodesJson from '../_mocks_/figma.fileNodes.json';

const file = fileJson as Figma.FileResponse;
const fileNodes = fileNodesJson as Figma.FileNodesResponse;

const nodeIds = Object.keys(fileNodes.nodes);

Expand Down Expand Up @@ -62,7 +65,7 @@ describe('figmaStyles.', () => {

const styleNodes = await figmaStyles.fetchStyles(client, 'ABC123', 'version123');

expect(client.file).to.have.been.calledOnceWith('ABC123', { version: 'version123' });
expect(client.file).to.have.been.calledOnceWith('ABC123', { version: 'version123', ids: undefined });
expect(client.fileNodes).to.have.been.calledWith('ABC123', { ids: ['121:10', '131:20'], version: 'version123' });

expect(styleNodes.length).to.equal(2);
Expand All @@ -79,9 +82,9 @@ describe('figmaStyles.', () => {
fileNodes: sinon.stub().resolves({ data: fileNodes }),
};

const styleNodes = await figmaStyles.fetchStyles(client, 'ABC123', 'version123');
const styleNodes = await figmaStyles.fetchStyles(client, 'ABC123', 'version123', ['121:10']);

expect(client.file).to.have.been.calledOnceWith('ABC123', { version: 'version123' });
expect(client.file).to.have.been.calledOnceWith('ABC123', { version: 'version123', ids: ['121:10'] });
expect(client.fileNodes).to.have.been.calledWith('ABC123', { ids: nodeIds, version: 'version123' });

const expectedStyleNodesLength = 30;
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/lib/figmaStyles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ import { parse as parseEffectStyle } from './effectStyle';
import { parse as parseTextStyle } from './textStyle';
// import { parse as parseGridStyle } from './gridStyle';

const fetchStyles = async (client: Figma.ClientInterface, fileId: string, version?: string): Promise<FigmaExport.StyleNode[]> => {
const { data: { styles = null } = {} } = await client.file(fileId, { version }).catch((error: Error) => {
const fetchStyles = async (
client: Figma.ClientInterface,
fileId: string,
version?: string,
ids?: string[],
): Promise<FigmaExport.StyleNode[]> => {
const { data: { styles = null } = {} } = await client.file(fileId, { version, ids }).catch((error: Error) => {
throw new Error(`while fetching file "${fileId}${version ? `?version=${version}` : ''}": ${error.message}`);
});

Expand Down
3 changes: 3 additions & 0 deletions packages/types/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ export type StylesCommandOptions = {
*/
version?: string;

/** Figma page names (all pages when not specified) */
onlyFromPages?: string[];

/** Outputter module name or path */
outputters?: StyleOutputter[];
}
Expand Down

0 comments on commit 23f48d7

Please sign in to comment.