Skip to content

Commit

Permalink
feat!: rename CLI args and improve its output (resolves #116)
Browse files Browse the repository at this point in the history
  • Loading branch information
muhammadsammy committed Jan 6, 2021
1 parent 279b002 commit b1e5e43
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 107 deletions.
49 changes: 28 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Please [follow the guide](https://tailwindcss.com/docs/installation/) to set up
npm install tailwindcss-classnames
```

The project is literally the [classnames](https://www.npmjs.com/package/classnames) project with custom typing and functions for applying pseudo elements. That means it arrives at your browser at approximately **422b** minified and gzipped ([bundlephobia](https://bundlephobia.com/result?p=classnames@2.2.6)).
The project is literally the [classnames](https://www.npmjs.com/package/classnames) project with custom typing and functions for applying pseudo elements. That means it arrives at your browser at approximately **484b** minified and gzipped ([bundlephobia](https://bundlephobia.com/result?p=tailwindcss-classnames)).

## Create classes

Expand Down Expand Up @@ -107,36 +107,43 @@ export const App: React.FC<{disabled}> = ({disabled}) => {

The types included in this package are the default tailwindcss classes, but if you modified your tailwind config file and/or want to add external custom classes, you can use the CLI tool to do this.

### CLI arguments for tailwind config:
Simply run `npx tailwindcss-classnames`.

- -c --config The relative path for TailwindCSS config file.
- -f --classesFile _(Optional)_ The relative path of the file with the custom types.
- -t --typeName _(Optional)_ The name of the type exported from file containing the custom classes.
- -o --output _(Optional)_ The name (or path) to generate the file into.
## CLI arguments:

Add it in your package.json scripts:

```json
"scripts": {
"generate-css-types": "tailwindcss-classnames --config tailwind.config.js"
}
```
-i, --input <input> Name or relative path of the TailwindCSS config file
-o, --output <output> Name or relative path of the generated types file **(optional, default: "tailwindcss-classnames.ts")**
-x, --extra <extra> Name or relative path of the file with the custom extra types **(optional)**
-h, --help display help for command
```

or simply run `npx tailwindcss-classnames`

### example:
## Example of CLI usage:

If you want to add types from external file named `my-custom-classes.ts`
containing the following code:

```ts
export type TCustomClasses =
| "button"
| "sidebar"
| "navbar"
type TCustomClasses =
| "red-button"
| "app-sidebar"
| "app-navbar"
| ...

// Note that you must provide a default export of the type
export default TCustomClasses;
```

You will excute the CLI with the following arguments:
You will execute the CLI with the following arguments:

`tailwindcss-classnames --config path/to/tailwind.config.js --classesFile my-custom-classes --typeName TCustomClasses`
```bash
npx tailwindcss-classnames -i path/to/tailwind.config.js -o path/to/output-file.ts -x my-custom-classes
```

Also, you can add it to your `package.json` scripts for convenience:

```json
"scripts": {
"generate-css-types": "tailwindcss-classnames -i relative/path/to/tailwind.config.js"
}
```
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
"postversion": "git push && git push --tags",
"release": "standard-version",
"pregenerate": "npm run clean && npm run build:lib",
"generate": "tailwindcss init --full && node lib/cli/index.js --config tailwind.config.js --output src/index.ts",
"postgenerate": "npm run clean",
"generate": "tailwindcss init --full && node lib/cli/index.js -i tailwind.config.js -o src/index.ts",
"postgenerate": "npm run clean && prettier src/index.ts --write",
"preupdateConfig": "npm run clean",
"updateConfig": "tailwindcss init --full && node helpers/updateDefaultConfig.js",
"postupdateConfig": "npm run clean && prettier src/cli/lib/defaultTailwindConfig.ts --write"
Expand Down
File renamed without changes.
159 changes: 102 additions & 57 deletions src/cli/core/GeneratedFileWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,88 +5,133 @@ import colors from 'colors';
import {baseTemplateString} from '../lib/baseTemplateString';
import {ClassesGenerator} from './ClassesGenerator';

type CliArguments = {
type TCliOptions = {
configFilename: string | void;
outputFilename: string | void;
customClassesFilename: string | void;
customClassesTypeName: string | void;
};

export class GeneratedFileWriter {
private readonly configFilename: string | void;
private readonly outputFilename: string | void;
private readonly customClassesFilename: string | void;
private readonly customClassesTypeName: string | void;
private readonly isCustomClassesAdded: boolean;
private configFileData = '';

constructor(options: CliArguments) {
this.configFilename = options.configFilename;
this.outputFilename = options.outputFilename;
this.customClassesFilename = options.customClassesFilename;
this.customClassesTypeName = options.customClassesTypeName;
this.isCustomClassesAdded = !!(this.customClassesFilename && this.customClassesTypeName);
private readonly _configFilename: string | void;
private readonly _outputFilename: string | void;
private readonly _customClassesFilename: string | void;
private _configFileData = '';

constructor(options: TCliOptions) {
this._configFilename = options.configFilename;
this._outputFilename = options.outputFilename;
this._customClassesFilename = options.customClassesFilename;
}

public write = async (): Promise<void> => {
if (this.missingCliOptionError().length > 0)
return console.error(this.missingCliOptionError().red);
// Check for missing cli options
if (this.missingCliOptionError().length > 0) {
this.printCliMessage('error', this.missingCliOptionError());
}

// Check for invalid custom classes file content
if (!!this._customClassesFilename) {
return fs
.readFile(`./${this._customClassesFilename}`)
.then(data => {
if (!data.toString().includes('export default')) {
this.printCliMessage(
'error',
'The type having the custom classes must be a default export',
);
return;
}
})
.catch(error => {
this.printCliMessage('error', `Unable to read the file with custom types. ${error}`);
return;
});
}

// If everything goes well, read the tailwind config file
await this.readTailwindConfigFile();

fs.writeFile(`${this.outputFilename}`, this.generateFileContent(), 'utf8').catch(error => {
console.error(colors.red(error));
});
// Then create a file with the generated types
fs.writeFile(`${this._outputFilename}`, this.generateFileContent(), 'utf8')
.then(() => {
this.printCliMessage(
'success',
`Types has successfully been generated in ${this._outputFilename} file.`,
);
})
.catch(error => {
this.printCliMessage('error', error);
});
};

private missingCliOptionError = (): string => {
if (!this.configFilename) return 'tailwindcss config file name or path is not provided';
if (!this.outputFilename) return 'Please provide a valid filename to add generated types to it';
if (this.customClassesTypeName && !this.customClassesFilename)
return 'Please provide the file containing custom classes';
if (this.customClassesFilename && !this.customClassesTypeName)
return 'Please provide the name of the exported custom type';
if (!this._configFilename) return 'tailwindcss config file name or path is not provided';
if (!this._outputFilename)
return 'Please provide a valid filename to add generated types to it';

return '';
};

private readTailwindConfigFile = async (): Promise<void> => {
try {
this.configFileData = await fs.readFile(`./${this.configFilename}`, {encoding: 'utf-8'});
this._configFileData = await fs.readFile(`./${this._configFilename}`, {encoding: 'utf-8'});
} catch (err) {
console.error(`Error Reading: "./${this.configFilename}"`.red);
this.printCliMessage('error', `Error Reading: "./${this._configFilename}"`);
}
};

private generateFileContent = (): string => {
return baseTemplateString
.replace(
/___ALL_CLASSES___/g,
new ClassesGenerator(
eval(this.configFileData.replace(/(['"])?plugins(['"])? *: *\[(.*|\n)*?],?/g, '')),
).generate(),
)
.replace(
/CUSTOM_FORMS_PLUGIN_TYPE/g,
this.configFileData.includes('@tailwindcss/custom-forms')
? '\n | TCustomFormsPluginClasses'
: '',
)
.replace(
/TYPOGRAPHY_PLUGIN_TYPE/g,
this.configFileData.includes('@tailwindcss/typography')
? '\n | TTypographyPluginClasses'
: '',
)
.replace(
/IMPORTED_T_CUSTOM_CLASSES/g,
this.isCustomClassesAdded ? '\n | TCustomClassesFromExternalFile' : '',
)
.replace(
/T_CUSTOM_CLASSES_IMPORT_STATEMENT/g,
this.isCustomClassesAdded
? `import {${this.customClassesTypeName} as TCustomClassesFromExternalFile} from './${this.customClassesFilename}';`
: '',
);
return (
baseTemplateString
// Add all generated classnames types
.replace(
/___ALL_CLASSES___/g,
new ClassesGenerator(
eval(this._configFileData.replace(/(['"])?plugins(['"])? *: *\[(.*|\n)*?],?/g, '')),
).generate(),
)

// Add typing support for first-party plugins if found in the config
.replace(
/CUSTOM_FORMS_PLUGIN_TYPE/g,
this._configFileData.includes('@tailwindcss/custom-forms')
? '\n | TCustomFormsPluginClasses'
: '',
)
.replace(
/TYPOGRAPHY_PLUGIN_TYPE/g,
this._configFileData.includes('@tailwindcss/typography')
? '\n | TTypographyPluginClasses'
: '',
)

// Append the custom classes types from external file if provided.
.replace(
/IMPORTED_T_CUSTOM_CLASSES/g,
!!this._customClassesFilename ? '\n | TCustomClassesFromExternalFile' : '',
)
.replace(
/T_CUSTOM_CLASSES_IMPORT_STATEMENT/g,
!!this._customClassesFilename
? `import TCustomClassesFromExternalFile from './${this._customClassesFilename}';`
: '',
)
);
};

private printCliMessage = (type: 'error' | 'success', message: string): void => {
const formattedMessage = '\n\n' + message + '\n' + '\n\n';

switch (type) {
case 'success':
console.log(colors.black.bgGreen(formattedMessage));
break;
case 'error':
console.error(colors.white.bgRed(formattedMessage));
break;
default:
console.log(formattedMessage);
break;
}
};
}
48 changes: 21 additions & 27 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,36 @@ import inquirer from 'inquirer';
import colors from 'colors';
import {GeneratedFileWriter} from './core/GeneratedFileWriter';

interface InquirerAnswers {
type TInquirerAnswers = {
configFilename: string;
customClassesTypeName: string | void;
customClassesFilename: string | void;
outputFilename: string | void;
}
customClassesFilename: string | void;
};

commander
.option('-c, --config <config>', 'Name or relative path of the TailwindCSS config file')
// Configure CLI options
.option('-i, --input <input>', 'Name or relative path of the TailwindCSS config file')
.option(
'-o, --output <output>',
'Name or relative path of the generated types file',
'tailwindcss-classnames.ts',
)
.option(
'-f, --classesFile <classesFile>',
'Name or relative path of the file with the custom types',
)
.option(
'-t, --typeName <typeName>',
'Name of the type exported from file containing the custom classes',
)
.action(({config, output, classesFile, typeName}: {[key: string]: string | void}) => {
if (config) {
.option('-x, --extra <extra>', 'Name or relative path of the file with the custom extra types')

// Define the action of the CLI
.action(({input, output, classesFile: extra}: {[key: string]: string | void}) => {
// If a required minimum of arguments is supplied to the CLI interface...
if (input) {
// Generate the types and write them to a file on disk
void new GeneratedFileWriter({
configFilename: config,
configFilename: input,
outputFilename: output,
customClassesFilename: classesFile,
customClassesTypeName: typeName,
customClassesFilename: extra,
}).write();
} else {
}
// Otherwise...
else {
// Prompt the user and ask to enter required information
inquirer
.prompt([
{
Expand All @@ -56,21 +55,16 @@ commander
default: null,
message: 'Name or path of the file with the custom types',
},
{
name: 'customClassesTypeName',
type: 'input',
default: null,
message: 'Name of the type exported from file containing the custom classes',
},
])
.then((answers: InquirerAnswers) => {
.then((answers: TInquirerAnswers) => {
// Get the answers and use them to create the file with generated types
void new GeneratedFileWriter({
configFilename: answers.configFilename,
outputFilename: answers.outputFilename,
customClassesFilename: answers.customClassesFilename,
customClassesTypeName: answers.customClassesTypeName,
}).write();
})
// Catch any errors that occur when prompting the user...
.catch(error => {
if (error.isTtyError) {
console.error(colors.red("Prompt couldn't be rendered in the current environment"));
Expand Down

0 comments on commit b1e5e43

Please sign in to comment.