Skip to content

Commit

Permalink
feat: synchronize root frame + rectangle (dimensions, rotation, colors)
Browse files Browse the repository at this point in the history
  • Loading branch information
sneko committed Jun 25, 2024
1 parent 5912b55 commit d162d77
Show file tree
Hide file tree
Showing 76 changed files with 3,151 additions and 88 deletions.
1 change: 1 addition & 0 deletions .env.model
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
FIGMA_ACCESS_TOKEN=
PENPOT_ACCESS_TOKEN=
PENPOT_BASE_URL=
50 changes: 36 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@
"dependencies": {
"@commander-js/extra-typings": "^11.1.0",
"@inquirer/prompts": "^3.3.0",
"@types/graphlib": "^2.1.12",
"chalk": "^5.3.0",
"change-case": "^5.4.4",
"graphlib": "^2.1.8",
"microdiff": "^1.3.2",
"patch-package": "^8.0.0",
"ts-custom-error": "^3.3.1",
"ts-graphviz": "^1.8.2",
"uuid": "^9.0.0",
"uuid": "^10.0.0",
"zod": "^3.22.4"
},
"devDependencies": {
Expand All @@ -54,7 +56,7 @@
"@types/chalk": "^2.2.0",
"@types/jest": "^29.4.0",
"@types/node": "^20.9.0",
"@types/uuid": "^9.0.0",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^7.0.1",
"@typescript-eslint/parser": "^7.0.1",
"commitizen": "^4.3.0",
Expand Down
53 changes: 30 additions & 23 deletions src/cli/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Command, Option } from '@commander-js/extra-typings';

import {
CompareOptions,
DocumentOptionsType,
RetrieveOptions,
SetOptions,
SynchronizeOptions,
Expand All @@ -12,7 +13,7 @@ import {
synchronize,
transform,
} from '@figpot/src/features/document';
import { retrieveDocumentsFromInput } from '@figpot/src/features/figma';
import { processDocumentsParametersFromInput, retrieveDocumentsFromInput } from '@figpot/src/features/figma';
import { ensureAccessTokens } from '@figpot/src/utils/environment';

export const program = new Command();
Expand All @@ -22,87 +23,93 @@ program.name('figpot').description('CLI to perform actions between Figma and Pen
const document = program.command('document').description('manage documents');
const debugDocument = document.command('debug').description('manage documents step by step to debug');

const figmaDocumentsOption = new Option('-fd, --figma-document [figmaDocuments...]', 'figma document id as source');
const penpotDocumentsOption = new Option('-pd, --penpot-document [penpotDocuments...]', 'penpot document id as target');
const documentsOption = new Option('-d, --document [documents...]', 'figma document id as source and penpot one as target (`-d figmaId[:penpotId]`)');

document
.command('synchronize')
.description('synchronize Figma documents to Penpot ones')
.addOption(figmaDocumentsOption)
.addOption(penpotDocumentsOption)
.addOption(documentsOption)
.action(async (options) => {
await ensureAccessTokens();

if (!options.figmaDocument || options.figmaDocument === true) {
options.figmaDocument = await retrieveDocumentsFromInput();
let documents: DocumentOptionsType[];
if (!options.document || options.document === true) {
documents = (await retrieveDocumentsFromInput()).map((figmaDocument) => {
return {
figmaDocument: figmaDocument,
};
});
} else {
documents = processDocumentsParametersFromInput(options.document);
}

await synchronize(
SynchronizeOptions.parse({
figmaDocuments: options.figmaDocument,
penpotDocuments: options.penpotDocument,
documents: documents,
})
);
});

debugDocument
.command('retrieve')
.description('save Figma documents locally')
.addOption(figmaDocumentsOption)
.addOption(documentsOption)
.action(async (options) => {
await ensureAccessTokens();

const documents = Array.isArray(options.document) ? processDocumentsParametersFromInput(options.document) : [];

await retrieve(
RetrieveOptions.parse({
figmaDocuments: options.figmaDocument,
documents: documents,
})
);
});

debugDocument
.command('transform')
.description('transform Figma documents format to Penpot one')
.addOption(figmaDocumentsOption)
.addOption(penpotDocumentsOption)
.addOption(documentsOption)
.action(async (options) => {
await ensureAccessTokens();

const documents = Array.isArray(options.document) ? processDocumentsParametersFromInput(options.document) : [];

await transform(
TransformOptions.parse({
figmaDocuments: options.figmaDocument,
penpotDocuments: options.penpotDocument,
documents: documents,
})
);
});

debugDocument
.command('compare')
.description('compare Figma and Penpot documents to know what to operate')
.addOption(figmaDocumentsOption)
.addOption(penpotDocumentsOption)
.addOption(documentsOption)
.action(async (options) => {
await ensureAccessTokens();

const documents = Array.isArray(options.document) ? processDocumentsParametersFromInput(options.document) : [];

await compare(
CompareOptions.parse({
figmaDocuments: options.figmaDocument,
penpotDocuments: options.penpotDocument,
documents: documents,
})
);
});

debugDocument
.command('set')
.description('execute operations')
.addOption(figmaDocumentsOption)
.addOption(penpotDocumentsOption)
.addOption(documentsOption)
.action(async (options) => {
await ensureAccessTokens();

const documents = Array.isArray(options.document) ? processDocumentsParametersFromInput(options.document) : [];

await set(
SetOptions.parse({
figmaDocuments: options.figmaDocument,
penpotDocuments: options.penpotDocument,
documents: documents,
})
);
});
102 changes: 102 additions & 0 deletions src/features/document.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { GetFileResponse } from '@figpot/src/clients/figma';
import { MappingType, getDifferences, transformDocument } from '@figpot/src/features/document';
import { cleanHostedDocument } from '@figpot/src/features/penpot';
import emptyFigmaTree from '@figpot/src/fixtures/documents/empty/figma.json';
import emptyPenpotTree from '@figpot/src/fixtures/documents/empty/penpot.json';
import withRectangeFigmaTree from '@figpot/src/fixtures/documents/rectangle/figma.json';
import withRectangePenpotTree from '@figpot/src/fixtures/documents/rectangle/penpot.json';

describe('document comparaison', () => {
describe('empty', () => {
it('should be equivalent', () => {
const mapping: MappingType = {
lastExport: new Date(),
assets: new Map(),
documents: new Map(),
fonts: new Map(),
nodes: new Map([
['0:0', '00000000-0000-0000-0000-000000000000'],
['0:1', '4bf0e9f6-08c8-809c-8004-85445179c2aa'],
]),
};

const transformedTree = transformDocument(emptyFigmaTree as GetFileResponse, mapping);
const cleanHostedTree = cleanHostedDocument(emptyPenpotTree);

expect(transformedTree).toEqual(cleanHostedTree);

const differences = getDifferences(cleanHostedTree, transformedTree);

expect(differences.newDocumentName).toBeNull();
expect(differences.newTreeOperations).toEqual([]);
});

it('should require changes', () => {
const mapping: MappingType = {
lastExport: new Date(),
assets: new Map(),
documents: new Map(),
fonts: new Map(),
nodes: new Map(),
};

const transformedTree = transformDocument(emptyFigmaTree as GetFileResponse, mapping);
const cleanHostedTree = cleanHostedDocument(emptyPenpotTree);

expect(transformedTree).not.toEqual(cleanHostedTree);

const differences = getDifferences(cleanHostedTree, transformedTree);

expect(differences.newDocumentName).not.toBeNull();
expect(differences.newTreeOperations.length).toBe(3);
expect(differences.newTreeOperations.map((op) => op.type)).toEqual(['add-page', 'mod-obj', 'del-page']);
});
});

describe('with rectangle', () => {
it('should be equivalent', () => {
const mapping: MappingType = {
lastExport: new Date(),
assets: new Map(),
documents: new Map(),
fonts: new Map(),
nodes: new Map([
['0:0', '00000000-0000-0000-0000-000000000000'],
['0:1', '4bf0e9f6-08c8-809c-8004-85445179c2aa'],
['1:2', 'ddfee392-d246-80fc-8004-8664a46a5d1f'],
]),
};

const transformedTree = transformDocument(withRectangeFigmaTree as GetFileResponse, mapping);
const cleanHostedTree = cleanHostedDocument(withRectangePenpotTree);

expect(transformedTree).toEqual(cleanHostedTree);

const differences = getDifferences(cleanHostedTree, transformedTree);

expect(differences.newDocumentName).toBeNull();
expect(differences.newTreeOperations).toEqual([]);
});

it('should require changes', () => {
const mapping: MappingType = {
lastExport: new Date(),
assets: new Map(),
documents: new Map(),
fonts: new Map(),
nodes: new Map(),
};

const transformedTree = transformDocument(withRectangeFigmaTree as GetFileResponse, mapping);
const cleanHostedTree = cleanHostedDocument(withRectangePenpotTree);

expect(transformedTree).not.toEqual(cleanHostedTree);

const differences = getDifferences(cleanHostedTree, transformedTree);

expect(differences.newDocumentName).not.toBeNull();
expect(differences.newTreeOperations.length).toBe(6);
expect(differences.newTreeOperations.map((op) => op.type)).toEqual(['add-page', 'add-obj', 'add-obj', 'del-page', 'del-obj', 'del-obj']);
});
});
});
Loading

0 comments on commit d162d77

Please sign in to comment.