@caweb/cli 1.4.1 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +38 -12
  2. package/bin/css-audit/.editorconfig +12 -0
  3. package/bin/css-audit/.github/workflows/build-report.yml +46 -0
  4. package/bin/css-audit/.github/workflows/merge-trunk-to-report.yml +17 -0
  5. package/bin/css-audit/.github/workflows/node.yaml +32 -0
  6. package/bin/css-audit/.nvmrc +1 -0
  7. package/bin/css-audit/README.md +131 -0
  8. package/bin/css-audit/css-audit.config.js +13 -0
  9. package/bin/css-audit/index.js +38 -0
  10. package/bin/css-audit/package-lock.json +6689 -0
  11. package/bin/css-audit/package.json +56 -0
  12. package/bin/css-audit/public/.gitkeep +1 -0
  13. package/bin/css-audit/src/__tests__/alphas.js +128 -0
  14. package/bin/css-audit/src/__tests__/colors.js +115 -0
  15. package/bin/css-audit/src/__tests__/display-none.js +52 -0
  16. package/bin/css-audit/src/__tests__/important.js +88 -0
  17. package/bin/css-audit/src/__tests__/media-queries.js +84 -0
  18. package/bin/css-audit/src/__tests__/property-values.js +55 -0
  19. package/bin/css-audit/src/__tests__/run.js +25 -0
  20. package/bin/css-audit/src/__tests__/selectors.js +66 -0
  21. package/bin/css-audit/src/audits/alphas.js +70 -0
  22. package/bin/css-audit/src/audits/colors.js +83 -0
  23. package/bin/css-audit/src/audits/display-none.js +39 -0
  24. package/bin/css-audit/src/audits/important.js +60 -0
  25. package/bin/css-audit/src/audits/media-queries.js +96 -0
  26. package/bin/css-audit/src/audits/property-values.js +65 -0
  27. package/bin/css-audit/src/audits/selectors.js +67 -0
  28. package/bin/css-audit/src/audits/typography.js +41 -0
  29. package/bin/css-audit/src/formats/cli-table.js +81 -0
  30. package/bin/css-audit/src/formats/html/_audit-alpha.twig +23 -0
  31. package/bin/css-audit/src/formats/html/_audit-colors.twig +23 -0
  32. package/bin/css-audit/src/formats/html/_audit-default.twig +24 -0
  33. package/bin/css-audit/src/formats/html/index.twig +88 -0
  34. package/bin/css-audit/src/formats/html/style.css +341 -0
  35. package/bin/css-audit/src/formats/html.js +52 -0
  36. package/bin/css-audit/src/formats/json.js +9 -0
  37. package/bin/css-audit/src/run.js +76 -0
  38. package/bin/css-audit/src/utils/__tests__/cli.js +70 -0
  39. package/bin/css-audit/src/utils/__tests__/example-config.config.js +12 -0
  40. package/bin/css-audit/src/utils/__tests__/get-specificity.js +39 -0
  41. package/bin/css-audit/src/utils/cli.js +133 -0
  42. package/bin/css-audit/src/utils/format-report.js +37 -0
  43. package/bin/css-audit/src/utils/get-specificity.js +97 -0
  44. package/bin/css-audit/src/utils/get-values-count.js +17 -0
  45. package/commands/audit.js +135 -0
  46. package/commands/index.js +7 -4
  47. package/commands/webpack/webpack.js +133 -0
  48. package/configs/css-audit.config.cjs +9 -0
  49. package/configs/webpack.config.js +126 -78
  50. package/docs/CREDITS.MD +5 -0
  51. package/docs/ROADMAP.MD +6 -5
  52. package/lib/cli.js +13 -4
  53. package/lib/helpers.js +3 -0
  54. package/package.json +15 -8
  55. package/commands/build.js +0 -80
  56. package/commands/serve.js +0 -94
@@ -0,0 +1,133 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ const minimist = require( 'minimist' );
5
+ const path = require( 'path' );
6
+ const { cosmiconfigSync } = require( 'cosmiconfig' );
7
+
8
+ const getArgsFromCLI = ( excludePrefixes ) => {
9
+ const args = process.argv.slice( 2 );
10
+ if ( excludePrefixes ) {
11
+ return args.filter( ( arg ) => {
12
+ return ! excludePrefixes.some( ( prefix ) =>
13
+ arg.startsWith( prefix )
14
+ );
15
+ } );
16
+ }
17
+ return args;
18
+ };
19
+
20
+ const getFileArgsFromCLI = () => minimist( getArgsFromCLI() )._;
21
+
22
+ /**
23
+ * Get configuration using cosmiconfig.
24
+ *
25
+ * @param {string} env
26
+ */
27
+ const getConfig = ( env ) => {
28
+ const moduleName = 'test' === env ? 'example-config' : 'css-audit';
29
+ const searchFrom =
30
+ 'test' === env ? path.join( __dirname, '__tests__' ) : process.cwd();
31
+
32
+ const explorerSync = cosmiconfigSync( moduleName );
33
+ const { config } = explorerSync.search( searchFrom );
34
+
35
+ try {
36
+ return config;
37
+ } catch ( e ) {
38
+ console.error( e, 'Error retrieving config file.' );
39
+ }
40
+ };
41
+
42
+ /**
43
+ * Get the argument required for running the audit,
44
+ *
45
+ * First get the argument from CLI, and fallback to the
46
+ * config if its not present.
47
+ *
48
+ * @param {string} arg
49
+ * @param {boolean} cliArgOnly
50
+ */
51
+
52
+ const getArg = ( arg, cliArgOnly = false ) => {
53
+ for ( const cliArg of getArgsFromCLI() ) {
54
+ const [ name, value ] = cliArg.split( '=' );
55
+
56
+ if ( name === arg ) {
57
+ return 'undefined' === typeof value ? true : value || null;
58
+ }
59
+ }
60
+
61
+ if ( true === cliArgOnly ) {
62
+ return false;
63
+ }
64
+
65
+ const config = getConfig( process.env.NODE_ENV );
66
+
67
+ const term = arg.substr( 2 );
68
+
69
+ // This is a simple property: value arg e.g. format: json
70
+ const isSimplePropertyValueArg = config.hasOwnProperty( term );
71
+
72
+ if ( isSimplePropertyValueArg ) {
73
+ return 'undefined' === typeof config[ term ]
74
+ ? true
75
+ : config[ term ] || null;
76
+ }
77
+
78
+ if ( config.hasOwnProperty( 'audits' ) ) {
79
+ // Separate the basic audits from property-values.
80
+ const basicAudits = config.audits.filter(
81
+ ( audit ) => term === audit && 'string' === typeof audit
82
+ );
83
+
84
+ // Create an array of values of the property-value audits.
85
+ const propertyValueAudits = config.audits.filter(
86
+ ( audit ) => 'object' === typeof audit && term === audit[ 0 ]
87
+ );
88
+
89
+ const propertyValueValues = ( () => {
90
+ if ( propertyValueAudits.length > 0 ) {
91
+ return propertyValueAudits
92
+ .flat()
93
+ .filter( ( item ) => 'property-values' !== item );
94
+ }
95
+ return [];
96
+ } )();
97
+
98
+ if ( 'undefined' !== basicAudits[ 0 ] && term === basicAudits[ 0 ] ) {
99
+ return true;
100
+ }
101
+
102
+ if ( propertyValueValues.length > 0 ) {
103
+ return propertyValueValues;
104
+ }
105
+ }
106
+
107
+ // The argument cannot be retrieved from CLI or config.
108
+ return false;
109
+ };
110
+
111
+ const getHelp = () => {
112
+ return `Usage: css-audit -- <files...> [options]
113
+
114
+ --colors Run colors audit.
115
+ --important Run !important audit.
116
+ --display-none Run display: none audit.
117
+ --selectors Run selectors audit.
118
+ --media-queries Run media queries audit.
119
+ --property-values Run audit for a given set of property values, comma-separated.
120
+ --recommended Run recommended audits (colors, important, selectors). Default: true.
121
+ --all Run all audits (except property values, as it requires a value).
122
+ --format Format to use for displaying report.
123
+ --help Show this message.
124
+ `;
125
+ };
126
+
127
+ module.exports = {
128
+ getArgsFromCLI,
129
+ getFileArgsFromCLI,
130
+ getArg,
131
+ getConfig,
132
+ getHelp,
133
+ };
@@ -0,0 +1,37 @@
1
+ const FMT_CLI_TABLE = 'cli-table';
2
+ const FMT_JSON = 'json';
3
+ const FMT_HTML = 'html';
4
+
5
+ /**
6
+ * Format the reports using the specified reporter format.
7
+ *
8
+ * @param {Array<Array<Object>>} reports The list of report data.
9
+ * @param {FMT_CLI_TABLE|FMT_JSON} format One of the predefined formats. Defaults to FMT_CLI_TABLE.
10
+ * @return {string} The formatted reports.
11
+ */
12
+ function formatReport( reports, format = FMT_CLI_TABLE ) {
13
+ let formatCallback = false;
14
+ switch ( format ) {
15
+ case FMT_JSON:
16
+ formatCallback = require( '../formats/json' );
17
+ break;
18
+ case FMT_HTML:
19
+ formatCallback = require( '../formats/html' );
20
+ break;
21
+ case FMT_CLI_TABLE:
22
+ default:
23
+ formatCallback = require( '../formats/cli-table' );
24
+ }
25
+
26
+ const formattedReports = formatCallback( reports );
27
+ if ( Array.isArray( formattedReports ) ) {
28
+ return formattedReports.join( '\n' );
29
+ }
30
+
31
+ return formattedReports;
32
+ }
33
+
34
+ module.exports = {
35
+ formats: [ FMT_CLI_TABLE, FMT_JSON ],
36
+ formatReport,
37
+ };
@@ -0,0 +1,97 @@
1
+ const csstree = require( 'css-tree' );
2
+
3
+ /**
4
+ * A recursive callback used to build up the specificity of a selector.
5
+ *
6
+ * This is intented to be used as a `reduce` callback, building up the
7
+ * specificity in the array accumulator as it processes each part of a selector.
8
+ *
9
+ * @param {Array<number>} specificity The specificity as an array.
10
+ * @param {Object} selector A selector node from the css-tree AST.
11
+ * @return {Array} The calculated specificity value.
12
+ */
13
+ function calculateSpecificity( [ a, b, c ], selector ) {
14
+ if ( ! selector.type ) {
15
+ return;
16
+ }
17
+ if ( 'lang' !== selector.name && selector.children ) {
18
+ return selector.children
19
+ .toArray()
20
+ .reduce( calculateSpecificity, [ a, b, c ] );
21
+ }
22
+
23
+ switch ( selector.type ) {
24
+ case 'IdSelector':
25
+ a++;
26
+ break;
27
+ case 'ClassSelector':
28
+ case 'AttributeSelector':
29
+ case 'Nth':
30
+ b++;
31
+ break;
32
+ case 'PseudoClassSelector':
33
+ if ( 'not' === selector.name ) {
34
+ break;
35
+ }
36
+ b++;
37
+ break;
38
+ case 'TypeSelector':
39
+ case 'PseudoElementSelector':
40
+ if ( '*' === selector.name ) {
41
+ break;
42
+ }
43
+ c++;
44
+ break;
45
+ case 'WhiteSpace':
46
+ case 'Combinator':
47
+ case 'Identifier':
48
+ // Whitespace, adjacent selectors (>, ~), … do not impact specificity.
49
+ break;
50
+ case 'Percentage':
51
+ // Part of a keyframe, not to be calculated.
52
+ break;
53
+ default:
54
+ console.warn( 'Unhandled selector type:', selector.type );
55
+ }
56
+ return [ a, b, c ];
57
+ }
58
+
59
+ /**
60
+ * Get the specificity value for a given CSS selector.
61
+ *
62
+ * @param {string} selector A valid CSS selector.
63
+ * @return {number} The calculated specificity value.
64
+ */
65
+ function getSpecificity( selector ) {
66
+ const node = csstree.parse( selector, { context: 'selector' } );
67
+ const selectorList = node.children.toArray();
68
+ const [ a, b, c ] = selectorList.reduce( calculateSpecificity, [
69
+ 0,
70
+ 0,
71
+ 0,
72
+ ] );
73
+ return 100 * a + 10 * b + c;
74
+ }
75
+
76
+ /**
77
+ * Get the specificity value for a given CSS selector, as an array.
78
+ *
79
+ * @param {string} selector A valid CSS selector.
80
+ * @return {Array} The calculated specificity value.
81
+ */
82
+ function getSpecificityArray( selector ) {
83
+ const node = csstree.parse( selector, { context: 'selector' } );
84
+ const selectorList = node.children.toArray();
85
+ const [ a, b, c ] = selectorList.reduce( calculateSpecificity, [
86
+ 0,
87
+ 0,
88
+ 0,
89
+ ] );
90
+ return [ a, b, c ];
91
+ }
92
+
93
+ module.exports = {
94
+ calculateSpecificity,
95
+ getSpecificity,
96
+ getSpecificityArray,
97
+ };
@@ -0,0 +1,17 @@
1
+ module.exports = function ( values ) {
2
+ const uniqueValues = [ ...new Set( values ) ];
3
+
4
+ return uniqueValues
5
+ .map( ( val ) => {
6
+ // Count up how many times this item appears in the full list.
7
+ const count = values.filter( ( c ) => c === val ).length;
8
+ return {
9
+ name: val,
10
+ count,
11
+ };
12
+ } )
13
+ .sort( ( a, b ) => {
14
+ // Reverse sort
15
+ return b.count - a.count;
16
+ } );
17
+ };
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * External dependencies
5
+ */
6
+ import path from 'path';
7
+ import fs from 'fs';
8
+ import { getAllFiles, getAllFilesSync } from 'get-all-files'
9
+
10
+ /**
11
+ * Internal dependencies
12
+ */
13
+
14
+ import {
15
+ runCmd,
16
+ projectPath,
17
+ appPath
18
+ } from '../lib/index.js';
19
+
20
+ /**
21
+ * Run WordPress CSS Audit
22
+ *
23
+ * @param {Object} options
24
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
25
+ * @param {boolean} options.debug True if debug mode is enabled.
26
+ */
27
+ export default async function audit({
28
+ spinner,
29
+ debug
30
+ } ) {
31
+
32
+ let files = [];
33
+
34
+ process.argv.slice(3).forEach( (paths, i) => {
35
+ let resolvePath = path.resolve(paths);
36
+ try {
37
+ // if given path is a directory
38
+ if( fs.statSync(resolvePath).isDirectory() ){
39
+ // get all .css files
40
+ getAllFilesSync(path.resolve(resolvePath)).toArray().forEach(f => {
41
+ if( f.endsWith('.css') ){
42
+ files.push(f)
43
+ }
44
+ })
45
+ // if given path is a file and a .css file
46
+ }else if( fs.statSync(paths).isFile() && paths.endsWith('.css') ){
47
+ files.push(paths)
48
+ }
49
+ // invalid path/file
50
+ } catch (error) {
51
+
52
+ }
53
+
54
+ });
55
+
56
+ // if no files were passed
57
+ if( ! files.length ){
58
+ // if the default build directory exists we audit it
59
+ if( fs.existsSync(path.resolve('build')) ){
60
+ // get all .css files in build folder
61
+ getAllFilesSync(path.resolve('build')).toArray().forEach(f => {
62
+ if( f.endsWith('.css') ){
63
+ files.push(f)
64
+ }
65
+ })
66
+ }
67
+ }
68
+
69
+ // if files are to be audited.
70
+ if( files.length ){
71
+ let auditArgs = [
72
+ ...files
73
+ ];
74
+
75
+ // default audits.
76
+ let audits = [
77
+ 'colors',
78
+ 'alphas',
79
+ 'important',
80
+ 'display-none',
81
+ 'selectors',
82
+ 'media-queries',
83
+ 'typography',
84
+ [ 'property-values', 'font-size' ],
85
+ [ 'property-values', 'padding,padding-top,padding-bottom,padding-right,padding-left' ],
86
+ [ 'property-values', 'margin,margin-top,marin-bottom,marin-right,marin-left' ]
87
+ ]
88
+
89
+ audits.forEach(a => {
90
+ let audit = Array.isArray(a) ? a[0] : a;
91
+
92
+ if( ! process.argv.includes(`--no-${audit}`) ){
93
+ if( Array.isArray(a) ){
94
+ auditArgs.push(`--${audit}=` + a.splice(1).join(','))
95
+ }else{
96
+ auditArgs.push(`--${audit}`)
97
+ }
98
+ }
99
+ })
100
+
101
+ const auditConfigPath = fs.existsSync(path.join(appPath, 'css-audit.config.cjs')) ?
102
+ appPath :
103
+ path.join(projectPath, 'configs')
104
+
105
+
106
+ await runCmd(
107
+ 'auditor',
108
+ auditArgs,
109
+ {
110
+ cwd: auditConfigPath
111
+ }
112
+ ).then(({stderr, stdout}) => {
113
+ // Audit result handling.
114
+ if( stderr ){
115
+ console.log( stderr.toString())
116
+ }
117
+ if( stdout ){
118
+ let msg = stdout.toString().replace('undefined', '');
119
+
120
+ if( 'audit' === process.argv[2] ){
121
+ console.log( msg )
122
+ }else{
123
+ return msg;
124
+ }
125
+
126
+ }
127
+ })
128
+
129
+ }else{
130
+ spinner.warn('No file(s) or directory path(s) were given.');
131
+ spinner.warn('Default build directory was not found.');
132
+ spinner.warn('Auditor did not execute.');
133
+ }
134
+
135
+ };
package/commands/index.js CHANGED
@@ -12,9 +12,12 @@ import installPath from '@wordpress/env/lib/commands/install-path.js';
12
12
  /**
13
13
  * Internal dependencies
14
14
  */
15
- import build from './build.js';
16
- import serve from './serve.js';
15
+ import webpack from './webpack/webpack.js';
16
+
17
17
  import a11y from './a11y.js';
18
+
19
+ import audit from './audit.js'
20
+
18
21
  import shell from './tasks/shell.js';
19
22
 
20
23
  import sync from './sync.js';
@@ -40,9 +43,9 @@ export {
40
43
  start,
41
44
  stop,
42
45
  destroy,
43
- build,
44
- serve,
46
+ webpack,
45
47
  a11y,
48
+ audit,
46
49
  sync,
47
50
  updatePlugins,
48
51
  shell,
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * External dependencies
5
+ */
6
+ import path from 'path';
7
+ import fs from 'fs';
8
+ import deepmerge from 'deepmerge';
9
+
10
+ /**
11
+ * Internal dependencies
12
+ */
13
+ import {
14
+ runCmd,
15
+ projectPath,
16
+ appPath
17
+ } from '../../lib/index.js';
18
+
19
+ import {default as auditor} from '../audit.js';
20
+
21
+ /**
22
+ * Build the current project
23
+ *
24
+ * @param {Object} options
25
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
26
+ * @param {boolean} options.debug True if debug mode is enabled.
27
+ * @param {boolean} options.audit Add CSS-Audit Page to pages served.
28
+ */
29
+ export default async function webpack({
30
+ spinner,
31
+ debug,
32
+ audit
33
+ } ) {
34
+
35
+ spinner.stop();
36
+ console.log( `Building ${path.basename(appPath)} project...` );
37
+
38
+ const webpackCommand = 'build' === process.argv[2] ? 'build' : 'serve' ;
39
+
40
+ const defaultConfigPath = path.join( projectPath, 'configs', 'webpack.config.js' );
41
+ let webpackConfig = await import('file://' + defaultConfigPath);
42
+ let customConfig = {};
43
+
44
+ // Since we use @wordpress/scripts webpack config we can leverage
45
+ // the environment variables as well.
46
+ process.env.WP_COPY_PHP_FILES_TO_DIST = true;
47
+
48
+ // pass any arguments from the cli
49
+ // add our default config as an extension.
50
+ // users can overwrite any values by creating a webconfig of their own.
51
+ let webPackArgs = [
52
+ '--config',
53
+ defaultConfigPath,
54
+ ];
55
+
56
+ // CommonJS
57
+ if( fs.existsSync( path.join(appPath, 'webpack.config.cjs' ))){
58
+ webPackArgs.push(
59
+ '--config',
60
+ path.join(appPath, 'webpack.config.cjs' ),
61
+ '--merge'
62
+ )
63
+
64
+ customConfig = await import('file://' + path.join(appPath, 'webpack.config.cjs' ));
65
+
66
+ // ESM
67
+ }else if( fs.existsSync(path.join(appPath, 'webpack.config.js' )) ){
68
+ webPackArgs.push(
69
+ '--config',
70
+ path.join(appPath, 'webpack.config.js' ),
71
+ '--merge'
72
+ )
73
+
74
+ customConfig = await import('file://' + path.join(appPath, 'webpack.config.js' ));
75
+ }
76
+
77
+ if( customConfig.length ){
78
+ webpackConfig = deepmerge(webpackConfig.default, customConfig.default);
79
+ }
80
+
81
+ // we always run the build command.
82
+ let result = await runCmd(
83
+ 'webpack',
84
+ ['build', ...webPackArgs]
85
+ ).then(({stdout, stderr}) => {
86
+ // we hide the punycode deprecation warning.
87
+ // if an error was thrown
88
+ if( stderr ){
89
+ console.log( stderr.toString().replace(/.*`punycode`.*\n.*/, '') )
90
+ }
91
+
92
+ if(stdout){
93
+ return stdout.toString().replace(/.*`punycode`.*\n.*/, '');
94
+ }
95
+
96
+ });
97
+
98
+ // if serving.
99
+ if( 'serve' === webpackCommand ){
100
+ webPackArgs.push(
101
+ '--open',
102
+ )
103
+
104
+ // run css-auditor
105
+ if( audit ){
106
+ await auditor({ spinner, debug });
107
+ }
108
+
109
+ console.log( "Webpack Server is preparing to launch...\nPress Ctrl + C to shutdown the server.");
110
+
111
+ // run webpack serve command
112
+ await runCmd(
113
+ 'webpack',
114
+ ['serve', ...webPackArgs]
115
+ ).then(({stdout, stderr}) => {
116
+ // if an error was thrown, and no output
117
+ if( stderr && ! stdout){
118
+ console.log( stderr.toString() )
119
+ }
120
+
121
+ if( stdout ){
122
+ spinner.text = "Webpack Server was closed.";
123
+ }
124
+
125
+ });
126
+
127
+ // only build was ran.
128
+ }else{
129
+ spinner.prefixText = result;
130
+ spinner.text = "Done!";
131
+ }
132
+
133
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+
5
+
6
+ module.exports = {
7
+ format: 'html',
8
+ filename: 'css-audit'
9
+ };