Skip to content

Commit 2f7115b

Browse files
committed
extracted generated “themed” tokens validation to its own action/code
1 parent 6fd4367 commit 2f7115b

File tree

5 files changed

+95
-26
lines changed

5 files changed

+95
-26
lines changed

packages/tokens/scripts/build-parts/generateThemingCssFiles.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import chalk from 'chalk';
1010
import type { Dictionary, PlatformConfig } from 'style-dictionary';
1111
import { fileHeader } from 'style-dictionary/utils';
1212

13+
import { getSourceFromFileWithRootSelector } from './getSourceFromFileWithRootSelector.ts';
14+
1315
export async function generateThemingCssFiles(_dictionary: Dictionary, config: PlatformConfig): Promise<void> {
1416

1517
const commonSource = await getSourceFromFileWithRootSelector(config, 'default', 'common-tokens.css');
@@ -91,31 +93,6 @@ export async function generateThemingCssFiles(_dictionary: Dictionary, config: P
9193
await fs.ensureDir(outputFolder);
9294
await fs.writeFile(`${outputFolder}tokens.css`, outputTokensCss);
9395
}
94-
95-
// extra validation check to make sure that all the common files have actually the same content
96-
const commonSourceCdsG0 = await getSourceFromFileWithRootSelector(config, 'cds-g0', 'common-tokens.css');
97-
const commonSourceCdsG10 = await getSourceFromFileWithRootSelector(config, 'cds-g10', 'common-tokens.css');
98-
const commonSourceCdsG90 = await getSourceFromFileWithRootSelector(config, 'cds-g90', 'common-tokens.css');
99-
const commonSourceCdsG100 = await getSourceFromFileWithRootSelector(config, 'cds-g100', 'common-tokens.css');
100-
101-
Object.entries({
102-
'cds-g0': commonSourceCdsG0,
103-
'cds-g10': commonSourceCdsG10,
104-
'cds-g90': commonSourceCdsG90,
105-
'cds-g100': commonSourceCdsG100
106-
}).forEach(([mode, source]: [string, string]) => {
107-
if (source !== commonSource) {
108-
// we want to interrupt the execution of the script if one of the generated "common" files is different from the others
109-
// note: comment this out if you need to debug why they differ, so the files are saved with the different content
110-
throw new Error(`❌ ${chalk.red.bold('ERROR')} - Generated "common" tokens for mode '${mode}' differ from the ones generated for the 'default' mode (expected to be identical)`);
111-
}
112-
});
113-
}
114-
115-
async function getSourceFromFileWithRootSelector(config: PlatformConfig, theme: string, path: string): Promise<string> {
116-
const rawSource = await fs.readFile(`${config.buildPath}themed-tokens/with-root-selector/${theme}/${path}`, 'utf8');
117-
const header = await fileHeader({});
118-
return rawSource.replace(header, '');
11996
}
12097

12198
function getCssVariablesStalenessComment(): string {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: MPL-2.0
4+
*/
5+
6+
import fs from 'fs-extra';
7+
8+
import type { PlatformConfig } from 'style-dictionary';
9+
import { fileHeader } from 'style-dictionary/utils';
10+
11+
export async function getSourceFromFileWithRootSelector(config: PlatformConfig, theme: string, path: string): Promise<string> {
12+
const rawSource = await fs.readFile(`${config.buildPath}themed-tokens/with-root-selector/${theme}/${path}`, 'utf8');
13+
const header = await fileHeader({});
14+
return rawSource.replace(header, '');
15+
}

packages/tokens/scripts/build-parts/getStyleDictionaryConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export function getStyleDictionaryConfig({ target, mode }: { target: Target, mod
114114
filter: excludePrivateTokens,
115115
}
116116
],
117-
actions: ['generate-css-helpers', 'generate-theming-css-files'],
117+
actions: ['generate-css-helpers', 'validate-theming-css-files', 'generate-theming-css-files'],
118118
},
119119
'docs/json': {
120120
buildPath: 'dist/docs/products/',
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: MPL-2.0
4+
*/
5+
6+
import chalk from 'chalk';
7+
8+
import type { Dictionary, PlatformConfig } from 'style-dictionary';
9+
10+
import { getSourceFromFileWithRootSelector } from './getSourceFromFileWithRootSelector.ts';
11+
import type { Mode } from './getStyleDictionaryConfig.ts';
12+
import { modes } from './getStyleDictionaryConfig.ts';
13+
14+
export async function validateThemingCssFiles(_dictionary: Dictionary, config: PlatformConfig): Promise<void> {
15+
16+
// store all the sources in memory
17+
const allSources = {} as Record<Mode, Record<string, string>>;
18+
for (const mode of modes) {
19+
const commonSource = await getSourceFromFileWithRootSelector(config, mode, 'common-tokens.css');
20+
const themedSource = await getSourceFromFileWithRootSelector(config, mode, 'themed-tokens.css');
21+
allSources[mode] = { commonSource, themedSource }
22+
}
23+
24+
// first validation: make sure that all the common files have actually the same content
25+
const comparisonModes = modes.filter(mode => mode !== 'default');
26+
comparisonModes.forEach((comparisonMode: Mode) => {
27+
if (allSources[comparisonMode].commonSource !== allSources.default.commonSource) {
28+
// we want to interrupt the execution of the script if one of the generated "common" files is different from the others
29+
// note: comment this out if you need to debug why they differ, so the files are saved with the different content
30+
throw new Error(`❌ ${chalk.red.bold('ERROR')} - Generated "common" tokens for mode '${comparisonMode}' differ from the ones generated for the 'default' mode (expected to be identical)`);
31+
}
32+
});
33+
34+
// second validation: make sure there are no orphans CSS variables when "common" and "themed" files are used together
35+
for (const mode of modes) {
36+
const { partialDefinitions: commonDefinitions, partialUsages: commonUsages } = extractAllCssVariables(allSources[mode].commonSource);
37+
const { partialDefinitions: themedDefinitions, partialUsages: themedUsages } = extractAllCssVariables(allSources[mode].themedSource);
38+
const allDefinitions = new Set([...commonDefinitions, ...themedDefinitions]);
39+
const allUsages = new Set([...commonUsages, ...themedUsages]);
40+
const undefinedVariables = [...allUsages].filter(usage => !allDefinitions.has(usage));
41+
if (undefinedVariables.length > 0) {
42+
throw new Error(`❌ ${chalk.red.bold('ERROR')} - Generated "common/themed" token files for mode '${mode} contain CSS variables that not defined in any of the generated files: ${undefinedVariables.map((variable: string) => `\`--token-${variable}\``).join(', ')}`);
43+
}
44+
}
45+
}
46+
47+
// regex for variable definition (`--token-***: ***`) and usage: (`var(--token-***)`)
48+
const varDefRegex = /--token-([a-zA-Z0-9-_]+)\s*:/g;
49+
const varUsageRegex = /var\(\s*--token-([a-zA-Z0-9-_]+)\s*\)/g;
50+
51+
function extractAllCssVariables(source: string) {
52+
const cleanSource = stripCssComments(source);
53+
const partialDefinitions = [];
54+
const partialUsages = [];
55+
56+
// find all definitions and usages in this file
57+
let match;
58+
while ((match = varDefRegex.exec(cleanSource))) {
59+
partialDefinitions.push(match[1]);
60+
}
61+
while ((match = varUsageRegex.exec(cleanSource))) {
62+
partialUsages.push(match[1]);
63+
}
64+
65+
return { partialDefinitions, partialUsages };
66+
}
67+
68+
function stripCssComments(source: string) {
69+
return source.replace(/\/\*[\s\S]*?\*\//g, '');
70+
}

packages/tokens/scripts/build.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { targets, modes, getStyleDictionaryConfig } from './build-parts/getStyle
1818
import { customFormatCssThemedTokensFunctionForTarget } from './build-parts/customFormatCssThemedTokens.ts';
1919
import { customFormatDocsJsonFunction } from './build-parts/customFormatDocsJson.ts';
2020
import { generateCssHelpers } from './build-parts/generateCssHelpers.ts';
21+
import { validateThemingCssFiles } from './build-parts/validateThemingCssFiles.ts';
2122
import { generateThemingCssFiles } from './build-parts/generateThemingCssFiles.ts';
2223
import { generateThemingDocsFiles } from './build-parts/generateThemingDocsFiles.ts';
2324

@@ -281,6 +282,12 @@ StyleDictionary.registerAction({
281282
undo: () => {}
282283
});
283284

285+
StyleDictionary.registerAction({
286+
name: 'validate-theming-css-files',
287+
do: validateThemingCssFiles,
288+
undo: () => {}
289+
});
290+
284291
StyleDictionary.registerAction({
285292
name: 'generate-theming-docs-files',
286293
do: generateThemingDocsFiles,

0 commit comments

Comments
 (0)