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

DRAFT: reordered design tokens structure #3285

Closed
wants to merge 16 commits into from
Closed
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
5 changes: 5 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ icons/
build-scss.js
component-generator/
example/
package.json
package-lock.json
src/i18n
tsconfig.build.json
dependent-usage.json
14 changes: 14 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,19 @@ module.exports = {
project: './tsconfig.json',
},
},
{
files: ['**/*.json'],
plugins: ['jsonc'],
extends: ['plugin:jsonc/recommended-with-json'],
rules: {
'jsonc/indent': ['error', 2],
'jsonc/quote-props': ['error', 'always'],
'jsonc/quotes': ['error', 'double'],
'jsonc/no-octal': 'error',
'jsonc/no-dupe-keys': 'error',
'jsonc/valid-json-number': 'error',
'jsonc/no-bigint-literals': 'error',
},
},
],
};
3 changes: 2 additions & 1 deletion .stylelintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"color-function-notation": "legacy",
"value-keyword-case": ["lower", {
"ignoreProperties": ["/font-family/"]
}]
}],
"custom-property-empty-line-before": null
}
}
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.PHONY: build
build:
rm -rf ./dist
tsc --project tsconfig.build.json
Expand Down
10 changes: 10 additions & 0 deletions bin/paragon-scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,21 @@ const COMMANDS = {
description: 'Include only source design tokens in the build.',
defaultValue: false,
},
{
name: '--output-token-references',
description: 'Include references for tokens with aliases to other tokens in the build output.',
defaultValue: true,
},
{
name: '-t, --themes',
description: 'Specify themes to include in the token build.',
defaultValue: 'light',
},
{
name: '-v, --verbose',
description: 'Enable verbose logging.',
defaultValue: false,
},
],
},
'replace-variables': {
Expand Down
190 changes: 190 additions & 0 deletions designTokenKeySorter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/* eslint-disable no-console */
const fs = require('fs').promises;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[inform]: If we are satisfied with this functionality, it will be moved to Paragon CLI

const path = require('path');
const chalk = require('chalk');

const tokensDirectory = 'tokens/';

const DESIGN_TOKEN_KEY_ORDER = [
'$type',
'$value',
'$description',
'outputReferences',
'modify',
'source',
];

const shouldApplyFix = process.argv.includes('--fix');

let mismatchWarningCount = 0;
let totalProcessedFileCount = 0;

/**
* Reorders the keys in an object based on a specified key order.
* @param {Object} object - The object to reorder.
* @param {string[]} desiredKeyOrder - The desired order for the keys.
* @returns {Object} - An object containing the reordered object and
* a flag indicating if the key order was mismatched and a list of mismatched keys.
*/
function reorderKeysInObject(object, desiredKeyOrder) {
const objectKeys = Object.keys(object);
const objectKeySet = new Set(objectKeys);
let isOrderMismatched = false;
const mismatchedKeyList = [];

let index = 0;

const reorderedObject = desiredKeyOrder.reduce((accumulatedObject, key) => {
if (objectKeySet.has(key)) {
accumulatedObject[key] = object[key];
if (key !== objectKeys[index]) {
isOrderMismatched = true;
mismatchedKeyList.push(objectKeys[index]);
}
index++;
}
return accumulatedObject;
}, {});

objectKeys.forEach((key) => {
if (!Object.prototype.hasOwnProperty.call(reorderedObject, key)) {
reorderedObject[key] = object[key];
if (index < objectKeys.length && key !== objectKeys[index]) {
isOrderMismatched = true;
mismatchedKeyList.push(objectKeys[index]);
}
index++;
}
});

return {
reorderedObject,
isOrderMismatched,
mismatchedKeys: mismatchedKeyList,
};
}

/**
* Recursively reorders keys in JSON data based on a specified key order for nested objects.
* @param {*} jsonData - The JSON data (object, array, or primitive) to process.
* @param {string} jsonPath - The path to the current data within the JSON structure.
* @returns {Object} - An object containing the reordered data and
* a flag indicating if any key order mismatches were found.
*/
function recursivelyReorderKeys(jsonData, jsonPath = '') {
if (Array.isArray(jsonData)) {
return jsonData.map((item, index) => recursivelyReorderKeys(item, `${jsonPath}[${index}]`));
}

if (typeof jsonData === 'object' && jsonData !== null) {
const {
reorderedObject: reorderedData,
isOrderMismatched: hasMainMismatch,
mismatchedKeys: mainMismatchedKeys,
} = reorderKeysInObject(jsonData, DESIGN_TOKEN_KEY_ORDER);

let hasAnyMismatch = hasMainMismatch;
const mismatches = hasMainMismatch ? { [jsonPath]: mainMismatchedKeys } : {};

Object.entries(reorderedData).reduce((accumulatedMismatches, [key, value]) => {
if (DESIGN_TOKEN_KEY_ORDER.includes(key)) {
reorderedData[key] = value;
return accumulatedMismatches;
}

const result = recursivelyReorderKeys(value, `${jsonPath}.${key}`);
reorderedData[key] = result.reorderedData || result;

if (result.isOrderMismatched) {
Object.assign(accumulatedMismatches, result.mismatches);
hasAnyMismatch = true;
}
return accumulatedMismatches;
}, mismatches);

return {
reorderedData,
isOrderMismatched: hasAnyMismatch,
mismatches,
};
}

return jsonData;
}

/**
* Processes all JSON files in a given directory path,
* reordering keys in each file based on predefined key orders.
* @param {string} directoryPath - The path of the directory containing JSON files.
*/
async function processJsonFilesInDirectory(directoryPath) {
try {
const directoryEntries = await fs.readdir(directoryPath, { withFileTypes: true });

const fileProcessingTasks = directoryEntries.map(async (entry) => {
const entryPath = path.join(directoryPath, entry.name);

if (entry.isDirectory()) {
return processJsonFilesInDirectory(entryPath);
}

if (entry.isFile() && path.extname(entry.name) === '.json') {
try {
const fileContent = await fs.readFile(entryPath, 'utf-8');
const jsonData = JSON.parse(fileContent);

const {
reorderedData,
isOrderMismatched,
mismatches,
} = recursivelyReorderKeys(jsonData);

if (isOrderMismatched) {
mismatchWarningCount++;
if (shouldApplyFix) {
await fs.writeFile(entryPath, `${JSON.stringify(reorderedData, null, 2)}\n`, 'utf-8');
} else {
console.warn(chalk.yellow(`Warning: Key order mismatch in ${entryPath}.`));
console.warn(chalk.red('Mismatched info:'));
Object.entries(mismatches).forEach(([keyPath, keys]) => {
console.warn(chalk.cyan(` Path: ${keyPath.slice(1)}`));
console.warn(chalk.magenta(` Mismatched keys: ${keys.join(', ')}`));
console.warn();
});
}
}

if (!fileContent.endsWith('\n')) {
await fs.writeFile(entryPath, `${fileContent}\n`, 'utf-8');
}

totalProcessedFileCount++;
return null;
} catch (error) {
console.error(chalk.red(`Error processing file ${entryPath}:`), error);
return null;
}
}

return null;
});

await Promise.all(fileProcessingTasks);
} catch (error) {
console.error(chalk.red(`Error reading directory ${directoryPath}:`), error);
}
}

processJsonFilesInDirectory(tokensDirectory).then(() => {
let statusMessage;

if (shouldApplyFix) {
statusMessage = chalk.green(`Processed ${totalProcessedFileCount} files. ${mismatchWarningCount} files were updated.`);
} else if (mismatchWarningCount > 0) {
statusMessage = chalk.yellow(`Processed ${totalProcessedFileCount} files. ${mismatchWarningCount} files have key order mismatches.`);
} else {
statusMessage = chalk.green(`Processed ${totalProcessedFileCount} files. All files are in correct order.`);
}

console.log(statusMessage);
});
Loading
Loading