Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Non-Figma Design Systems like CDT #34

Open
wants to merge 7 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ node_modules/

build

src/build
src/build

theme.generated.json
49 changes: 41 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# VIP Design System Bridge Tool

This is a script designed to take an export of a design system, and insert the tokens into the `theme.json` of a WordPress site. At the moment it only supports exports from Figma, using [this](https://www.figma.com/community/plugin/843461159747178978) plugin.
This is a script designed to take an export of a design system, and insert the tokens into the `theme.json` of a WordPress site. At the moment it supports two types of sources - Figma and CSS.

## Exporting Data from your Design System
## Figma

This is specifically exports made using using [this](https://www.figma.com/community/plugin/843461159747178978) plugin.

Refer to [this WPVIP post](https://wpvip.com/2022/12/09/figma-to-wordpress/) for a tutorial on how to connect a design system in Figma with WordPress, using the Figma Tokens plugin.

## Using the Script
### Using the Script

Once data has been exported from your design system, into either a folder or a single JSON token file the script is almost ready to be run.

Expand All @@ -23,7 +25,7 @@ The script makes some assumptions by default, and its critical to ensure that th
* An existing [`theme.json`](https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-json/) file where the tokens from your export would be inserted. Note that by default, the script does not overwrite the `theme.json`. Instead, it writes to a new file called `theme.generated.json`. This can be overriden using the `--overwrite` flag.
* Based on the theme that is selected from the Figma export, the tokens are inserted directly under `settings->custom`. If a section prefix is desired, use the `--themeJsonSection` option.

### Steps
#### Steps

* In order to get started, you will need to ensure that repo has been cloned locally.
* After that, run the following to install all necessary dependencies for this script:
Expand Down Expand Up @@ -58,16 +60,47 @@ node ingest-tokens.js --tokenPath='<path to token JSON file or directory>' --the
```
* With that, the `theme.json` now has the tokens from your design system export and is ready for usage in your WordPress site.

## CSS

This is done using a single CSS file only.

### Using the Script

Once the CSS file is handy, the script is almost ready to be run. There is another file necessary, which is the theme tokens to CSS map. This would be a JSON file that maps a custom token used in your WordPress site's `theme.json` to a CSS variable in your CSS file. There is an empty token map provided [here](reference-files/default-token-map.json).

Unlike the Figma route above, the CLI arguments look slighty different like so:

```bash
node ingest-tokens.js --tokenPath='<path to CSS file>' --tokenMapPath='<path to tokenMap file>' --themePath='<path to theme directory>'

# Example:
# node ingest-tokens.js --tokenPath=~/valet.css --tokenMapPath=~/token-map.json --themePath=~/vip-go-skeleton/themes/valet/
```

The keys in this JSON file map to keys located under `settings.custom` in the `theme.json`. So, if the key used was `text.primary` that would mean that it is referring to `settings.custom.text.primary` within the `theme.json`.

Using the above, we have taken the [California Design Theme](https://designsystem.webstandards.ca.gov) and come up with an example token map that can be found [here](reference-files/CDT/CDT-token-map.json). The tokens have been selected from [cagov.css](https://github.com/cagov/design-system/blob/main/components/combined-css/dist/cagov.css), and mapped to tokens that can be found in a simplified version of the [VIP Valet theme.json](reference-files/Valet/valet-theme.json). Taken together, this can be used to import a design system like the CDT into a WordPress site.

### Limitations

Due to a bug in the JSON dot notation library used, there are a few things to keep in mind or else your resulting JSON will not be valid:

- Use roman numerals instead of numbers if your keys are going to be numbers.
- Use camel case instead of kebab case for your keys.

## Supported Commands

THe following is a good summary of available command-line options within the script:

```bash
-v, --version output the version number
-h, --help display help for command
--tokenPath <path> path to token JSON file or directory
--sourceSet <source-set(s)> NON-PRO PLUGIN OPTION: source set in the token JSON
--layerSets <layer-set(s)> NON-PRO PLUGIN OPTION: layers built using the source set in token JSON
--theme <theme-name> PRO PLUGIN OPTION: selected $themes set in token JSON
--tokenPath <path> path to token JSON/CSS file or directory of JSON files
--sourceSet <source-set(s)> (FIGMA ONLY) NON-PRO PLUGIN OPTION: source set in the token JSON
--layerSets <layer-set(s)> (FIGMA ONLY) NON-PRO PLUGIN OPTION: layers built using the source set in token JSON
--theme <theme-name> (FIGMA ONLY) PRO PLUGIN OPTION: selected $themes set in token JSON
--themePath <path> path to a WordPress theme
--tokenMapPath <path> path to the theme tokens to CSS map JSON file. This is required if the input is a CSS file, but not if the source is FIGMA
--themeJsonSection <prefix> section to insert tokens into theme.json->settings->custom (default: "")
--overwrite overwrite existing theme.json (default: false)
```
Expand Down
142 changes: 89 additions & 53 deletions ingest-tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const fs = require( 'fs' ).promises;
const path = require( 'path' );
const cp = require( 'child_process' );
const chalk = require( 'chalk' );
const pathLib = require( '@irrelon/path' );
const styleDictionary = require( './src/style-dictionary/main' );
const utility = require( './src/utility' );

Expand All @@ -13,17 +14,18 @@ const generatedThemeFileName = 'theme.generated.json';
const themeFileName = 'theme.json';

commander
.version( '0.5.0', '-v, --version' )
.description( 'Inject the tokens from the provided Figma export into theme.json' )
.version( '1.0.0', '-v, --version' )
.description( 'Inject the tokens from the provided design system export into theme.json, from either a JSON file/directory when the source is FIGMA or a CSS file when its not FIGMA.' )
.usage( '[OPTIONS]...' )
.requiredOption( '--tokenPath <path>', 'path to token JSON file or directory' )
.requiredOption( '--tokenPath <path>', 'path to token JSON/CSS file or directory of JSON files' )
.requiredOption( '--themePath <path>', 'path to a WordPress theme' )
.option( '--sourceSet <theme-name>', 'NON-PRO PLUGIN OPTION: source set in the token JSON' )
.option( '--sourceSet <theme-name>', '(FIGMA ONLY) NON-PRO PLUGIN OPTION: source set in the token JSON' )
.option(
'--layerSets <theme-name>',
'NON-PRO PLUGIN OPTION: layers built using the source set in token JSON'
'(FIGMA ONLY) NON-PRO PLUGIN OPTION: layers built using the source set in token JSON'
)
.option( '--theme <theme-name>', 'PRO PLUGIN OPTION: selected $themes set in token JSON' )
.option( '--theme <theme-name>', '(FIGMA ONLY) PRO PLUGIN OPTION: selected $themes set in token JSON' )
.option( '--tokenMapPath <path>', 'path to the theme tokens to CSS map JSON file. This is required if the input is a CSS file, but not if the source is FIGMA' )
.option(
'--themeJsonSection <prefix>',
'section to insert tokens into theme.json->settings->custom',
Expand All @@ -39,62 +41,96 @@ async function ingestTokens( options ) {
utility.throwErrorForFileNotExisting( tokenPath, 'No core tokens found for path:' );

const tokenJson = await utility.getTokensFromPath( tokenPath );
const { enabledSets, sourceSets } = utility.getThemeSets(
tokenJson,
options.sourceSet,
options.layerSets,
options.theme
);


const themeDirectory = utility.resolvePath( options.themePath );
const themeJsonPath = path.join( themeDirectory, themeFileName );
utility.throwErrorForFileNotExisting( themeJsonPath, 'No theme.json found for path:' );
utility.throwErrorForFileNotExisting( tokenPath, 'No core tokens found for path:' );

const themeJsonBuffer = await fs.readFile( themeJsonPath );
const themeJson = JSON.parse( themeJsonBuffer.toString() );

const tokenTransformerArgs = [
'token-transformer',
`${ tokenPath }`,
`${ transformerFilePath }`,
'--throwErrorWhenNotResolved',
'--expandTypography=true',
];

// Just the source sets are present
if ( enabledSets && enabledSets.length === 0 ) {
tokenTransformerArgs.push( sourceSets );
let builtTokens = {};
if ( tokenPath.endsWith( '.css' ) ) {
if ( ! options.tokenMapPath ) {
utility.throwError( 'A token map path is required when the input is a CSS file.' );
}

const tokenMapPath = utility.resolvePath( options.tokenMapPath );
utility.throwErrorForFileNotExisting( tokenMapPath, 'No theme tokens to CSS map found for path:' );

const tokenMapJson = await utility.getJsonFromPath( tokenMapPath );
const rulesFromTokens = tokenJson.stylesheet.rules.filter( rule => rule.type === 'rule' && rule.declarations.length > 0 );

// The logic here is that if the value of the key is in a CSS format, then go find that value.
// Otherwise, use the value is like if its normal or some other CSS standard.
// In addition, if the CSS value has quotes then they should be removed.
Object.keys(tokenMapJson).forEach(key => {
if ( tokenMapJson[key].startsWith('--') ) {
rulesFromTokens.forEach( rule => {
rule.declarations.forEach( declaration => {
if ( ( typeof declaration.property === 'string' || declaration.property instanceof String ) && declaration.property === tokenMapJson[ key ] ) {
// There is a bug here where if the key is 1 or has a hyphen in it then the json won't be valid.
pathLib.set( builtTokens, key, declaration.value.replace(/["']/g, ""));
}
} );
} );
} else {
pathLib.set( builtTokens, key, tokenMapJson[key]);
}
} );

console.log( '\n' + chalk.green( '✔︎ Processed CSS file' ) );
} else {
const selectedSets = [
// Order the source token sets first for use with token-transformer
...sourceSets,
...enabledSets,
];

tokenTransformerArgs.push( selectedSets, sourceSets );
}

const transformResult = cp.spawnSync( 'npx', tokenTransformerArgs );

if ( transformResult.status > 0 ) {
console.log( [ 'npx', ...tokenTransformerArgs ].join( ' ' ) );

let errorString = transformResult.stderr.toString();
console.error( '\n' );
// Assumption is that the massive minified code is dumped first, followed by the actual error
errorString = errorString.substring( errorString.indexOf( 'Error:' ) );
console.error( errorString );
utility.throwError(
'Unable to process the token file provided. Please review any errors logged above, verify that it is valid.'
const { enabledSets, sourceSets } = utility.getThemeSets(
tokenJson,
options.sourceSet,
options.layerSets,
options.theme
);
} else {
console.log( chalk.green( '✔︎ Processed with token-transformer' ) );
}

const builtTokens = await styleDictionary.getProcessedTokens( transformerFilePath );
const tokenTransformerArgs = [
'token-transformer',
`${ tokenPath }`,
`${ transformerFilePath }`,
'--throwErrorWhenNotResolved',
'--expandTypography=true',
];

// Just the source sets are present
if ( enabledSets && enabledSets.length === 0 ) {
tokenTransformerArgs.push( sourceSets );
} else {
const selectedSets = [
// Order the source token sets first for use with token-transformer
...sourceSets,
...enabledSets,
];

tokenTransformerArgs.push( selectedSets, sourceSets );
}

const transformResult = cp.spawnSync( 'npx', tokenTransformerArgs );

if ( transformResult.status > 0 ) {
console.log( [ 'npx', ...tokenTransformerArgs ].join( ' ' ) );

let errorString = transformResult.stderr.toString();
console.error( '\n' );
// Assumption is that the massive minified code is dumped first, followed by the actual error
errorString = errorString.substring( errorString.indexOf( 'Error:' ) );
console.error( errorString );
utility.throwError(
'Unable to process the token file provided. Please review any errors logged above, verify that it is valid.'
);
} else {
console.log( chalk.green( '✔︎ Processed with token-transformer' ) );
}

builtTokens = await styleDictionary.getProcessedTokens( transformerFilePath );

console.log( '\n' + chalk.green( '✔︎ Processed with Style Dictionary' ) );
}

console.log( '\n' + chalk.green( '✔︎ Processed with Style Dictionary' ) );
const themeJsonBuffer = await fs.readFile( themeJsonPath );
const themeJson = JSON.parse( themeJsonBuffer.toString() );

if ( options.themeJsonSection ) {
themeJson.settings.custom[ options.themeJsonSection ] = {
Expand Down
Loading