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

[BD-46] feat: move CLI design tokens commands to paragon CLI #2609

Merged
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ build:
rm -rf dist/**/__snapshots__
rm -rf dist/__mocks__
rm -rf dist/setupTest.js
node build-scss.js
./bin/paragon-scripts.js build-scss

export TRANSIFEX_RESOURCE = paragon
transifex_langs = "ar,ca,es_419,fr,he,id,ko_KR,pl,pt_BR,ru,th,uk,zh_CN,es_AR,es_ES,pt_PT,tr_TR,it_IT"
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ The Paragon CLI (Command Line Interface) is a tool that provides various utility

### Available Commands

- `paragon install-theme [theme]`: Installs the specific @edx/brand package.
- `paragon install-theme [theme]`: Installs the specific [brand package](https://github.com/openedx/brand-openedx).
- `paragon build-tokens`: Build Paragon's design tokens.
- `paragon replace-variables`: Replace SCSS variables usages or definitions to CSS variables and vice versa in `.scss` files.
- `paragon build-scss`: Compile Paragon's core and themes SCSS into CSS.

Use `paragon help` to see more information.

Expand Down
135 changes: 125 additions & 10 deletions bin/paragon-scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
const chalk = require('chalk');
const themeCommand = require('../lib/install-theme');
const helpCommand = require('../lib/help');

const HELP_COMMAND = 'help';
const buildTokensCommand = require('../lib/build-tokens');
const replaceVariablesCommand = require('../lib/replace-variables');
const buildScssCommand = require('../lib/build-scss');

const COMMANDS = {
/**
Expand All @@ -25,6 +26,9 @@ const COMMANDS = {
* {
* name: '--optionName',
* description: 'optionDescription',
* choices: 'optionChoices',
* defaultValue: 'optionDefaultValue',
* required: true/false,
* },
* ...
* ],
Expand All @@ -43,14 +47,130 @@ const COMMANDS = {
},
],
},
'build-tokens': {
executor: buildTokensCommand,
description: 'CLI to build Paragon design tokens.',
options: [
{
name: '-s, --source',
description: 'Specify the source directory for design tokens.',
defaultValue: '\'\'',
},
{
name: '-b, --build-dir',
description: 'Specify the build directory for the generated tokens.',
defaultValue: './build/',
},
{
name: '--source-tokens-only',
description: 'Include only source design tokens in the build.',
defaultValue: false,
},
{
name: '-t, --themes',
description: 'Specify themes to include in the token build.',
defaultValue: 'light',
},
],
},
'replace-variables': {
executor: replaceVariablesCommand,
description: 'CLI to replace SCSS variables usages or definitions to CSS variables and vice versa in .scss files.',
parameters: [
{
name: '-p, --filePath',
description: 'Path to the file or directory where to replace variables.',
defaultValue: '\'\'',
},
],
options: [
{
name: '-s, --source',
description: 'Type of replacement: usage or definition. If set to "definition" the command will only update SCSS variables definitions with CSS variables, if set to "usage" - all occurrences of SCSS variables will we replaced',
defaultValue: '\'\'',
},
{
name: '-t, --replacementType',
description: 'Type of replacement: usage or definition. If set to "definition" the command will only update SCSS variables definitions with CSS variables, if set to "usage" - all occurrences of SCSS variables will we replaced',
choices: '[usage|definition]',
defaultValue: 'definition',
},
{
name: '-d, --direction',
description: 'Map direction: css-to-scss or scss-to-css, if replacement type parameter is set to "definition" this has no effect.',
choices: '[scss-to-css|css-to-scss]',
defaultValue: 'scss-to-css',
},
],
},
'build-scss': {
executor: buildScssCommand,
description: 'CLI to compile Paragon\'s core and themes SCSS into CSS.',
options: [
{
name: '--corePath',
description: 'Path to the theme\'s core SCSS file, defaults to Paragon\'s core.scss.',
defaultValue: 'styles/scss/core/core.scss',
},
{
name: '--themesPath',
description: `Path to the directory that contains themes' files. Expects directory to have following structure:
themes/
light/
│ ├─ index.css
│ ├─ other_css_files
dark/
│ ├─ index.css
│ ├─ other_css_files
some_other_custom_theme/
│ ├─ index.css
│ ├─ other_css_files
...

where index.css has imported all other CSS files in the theme's subdirectory. The script will output
light.css, dark.css and some_other_custom_theme.css files (together with maps and minified versions).
You can provide any amount of themes. Default to paragon's themes.
`,
defaultValue: 'styles/css/themes',
},
{
name: '--outDir',
description: 'Specifies directory where to out resulting CSS files.',
defaultValue: './dist',
},
{
name: '--defaultThemeVariants',
description: `Specifies default theme variants. Defaults to a single 'light' theme variant.
You can provide multiple default theme variants by passing multiple values, for
example: \`--defaultThemeVariants light dark\`
`,
defaultValue: 'light',
},
],
},
help: {
executor: helpCommand,
executor: (args) => helpCommand(COMMANDS, args),
parameters: [
{
name: 'command',
description: 'Specifies command name.',
defaultValue: '\'\'',
choices: '[install-theme|build-tokens|replace-variables|build-scss]',
required: false,
},
],
description: 'Displays help for available commands.',
},
};

/**
* Executes a Paragon CLI command based on the provided command-line arguments.
*
* @async
* @function executeParagonCommand
*/
viktorrusakov marked this conversation as resolved.
Show resolved Hide resolved
(async () => {
const [command] = process.argv.slice(2);
const [command, ...commandArgs] = process.argv.slice(2);
const executor = COMMANDS[command];

if (!executor) {
Expand All @@ -59,13 +179,8 @@ const COMMANDS = {
return;
}

if (command === HELP_COMMAND) {
helpCommand(COMMANDS);
return;
}

try {
await executor.executor();
await executor.executor(commandArgs);
} catch (error) {
// eslint-disable-next-line no-console
console.error(chalk.red.bold('An error occurred:', error.message));
Expand Down
127 changes: 52 additions & 75 deletions build-scss.js → lib/build-scss.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env node
const fs = require('fs');
const sass = require('sass');
const postCSS = require('postcss');
Expand All @@ -8,7 +7,10 @@ const postCSSMinify = require('postcss-minify');
const combineSelectors = require('postcss-combine-duplicated-selectors');
const { pathToFileURL } = require('url');
const path = require('path');
const { program, Option } = require('commander');
const minimist = require('minimist');
const chalk = require('chalk');
const ora = require('ora');
const { capitalize } = require('./utils');

const paragonThemeOutputFilename = 'theme-urls.json';

Expand Down Expand Up @@ -92,17 +94,19 @@ const compileAndWriteStyleSheets = ({
},
}],
});

const commonPostCssPlugins = [
postCSSImport(),
postCSSCustomMedia({ preserve: true }),
combineSelectors({ removeDuplicatedProperties: true }),
];

const postCSSCompilation = ora(`Compilation for ${capitalize(name)} stylesheet...`).start();
postCSS(commonPostCssPlugins)
.process(compiledStyleSheet.css, { from: stylesPath, map: false })
.then((result) => {
postCSSCompilation.succeed(`Successfully compiled ${capitalize(name)} theme stylesheet`);
fs.writeFileSync(`${outDir}/${name}.css`, result.css);

postCSS([postCSSMinify()])
.process(result.css, { from: `${name}.css`, map: { inline: false } })
.then((minifiedResult) => {
Expand All @@ -129,83 +133,56 @@ const compileAndWriteStyleSheets = ({
isDefaultThemeVariant,
});
}

fs.writeFileSync(`${outDir}/${paragonThemeOutputFilename}`, `${JSON.stringify(paragonThemeOutput, null, 2)}\n`);
})
.then(() => {
ora().succeed(chalk.underline.bold.green(`Successfully built stylesheet for ${capitalize(name)} theme!\n`));
})
.catch((error) => {
ora().fail(chalk.bold(`Failed to build stylesheets for ${capitalize(name)}: ${error.message}`));
});
};

program
.version('0.0.1')
.description('CLI to compile Paragon\'s core and themes\' SCSS into CSS.')
.addOption(
new Option(
'--corePath <corePath>',
'Path to the theme\'s core SCSS file, defaults to Paragon\'s core.scss.',
),
)
.addOption(
new Option(
'--themesPath <themesPath>',
`Path to the directory that contains themes' files. Expects directory to have following structure:
themes/
light/
│ ├─ index.css
│ ├─ other_css_files
dark/
│ ├─ index.css
│ ├─ other_css_files
some_other_custom_theme/
│ ├─ index.css
│ ├─ other_css_files
...

where index.css has imported all other CSS files in the theme's subdirectory. The script will output
light.css, dark.css and some_other_custom_theme.css files (together with maps and minified versions).
You can provide any amount of themes. Default to paragon's themes.
`,
),
)
.addOption(
new Option(
'--outDir <outDir>',
'Specifies directory where to out resulting CSS files.',
),
)
.addOption(
new Option(
'--defaultThemeVariants <defaultThemeVariants...>',
`Specifies default theme variants. Defaults to a single 'light' theme variant.
You can provide multiple default theme variants by passing multiple values, for
example: \`--defaultThemeVariants light dark\`
`,
),
);

program.parse(process.argv);
/**
* Builds SCSS stylesheets based on the provided command arguments.
*
* @param {Array<string>} commandArgs - Command line arguments for building SCSS stylesheets.
*/
function buildScssCommand(commandArgs) {
const defaultArgs = {
corePath: path.resolve(process.cwd(), 'styles/scss/core/core.scss'),
themesPath: path.resolve(process.cwd(), 'styles/css/themes'),
outDir: './dist',
defaultThemeVariants: 'light',
};

const options = program.opts();
const {
corePath = path.resolve(__dirname, 'styles/scss/core/core.scss'),
themesPath = path.resolve(__dirname, 'styles/css/themes'),
outDir = './dist',
defaultThemeVariants = ['light'],
} = options;
const {
corePath,
themesPath,
outDir,
defaultThemeVariants,
} = minimist(commandArgs, { default: defaultArgs });

// Core CSS
compileAndWriteStyleSheets({
name: 'core',
stylesPath: corePath,
outDir,
});
// Core CSS
compileAndWriteStyleSheets({
name: 'core',
stylesPath: corePath,
outDir,
});

// Theme Variants CSS
fs.readdirSync(themesPath, { withFileTypes: true })
.filter((item) => item.isDirectory())
.forEach((themeDir) => {
compileAndWriteStyleSheets({
name: themeDir.name,
stylesPath: `${themesPath}/${themeDir.name}/index.css`,
outDir,
isThemeVariant: true,
isDefaultThemeVariant: defaultThemeVariants.includes(themeDir.name),
// Theme Variants CSS
fs.readdirSync(themesPath, { withFileTypes: true })
.filter((item) => item.isDirectory())
.forEach((themeDir) => {
compileAndWriteStyleSheets({
name: themeDir.name,
stylesPath: `${themesPath}/${themeDir.name}/index.css`,
outDir,
isThemeVariant: true,
isDefaultThemeVariant: defaultThemeVariants.includes(themeDir.name),
});
});
});
}

module.exports = buildScssCommand;