@caweb/a11y-webpack-plugin 2.0.1 → 2.0.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.
package/aceconfig.js CHANGED
@@ -2,6 +2,11 @@
2
2
  * Configuration for Accessibility Checker
3
3
  * @link https://www.npmjs.com/package/accessibility-checker
4
4
  */
5
+ /**
6
+ * External dependencies
7
+ */
8
+ import path from 'path';
9
+ import { getArgVal, getAllFlags } from '@caweb/webpack/lib/args.js';
5
10
 
6
11
  let levels = [
7
12
  'violation',
@@ -14,31 +19,32 @@ let levels = [
14
19
  let reportLevels = levels;
15
20
  let failLevels = levels;
16
21
 
17
- // process args
18
- process.argv.forEach((arg) => {
22
+ // remove any levels based on flags
23
+ Object.keys(getAllFlags()).forEach((flag) => {
19
24
  // remove any report levels
20
- if( arg.includes('--no-report-levels-') ){
21
- let r = arg.replace('--no-report-levels-', '')
25
+ if( flag.includes('no-report-levels-') ){
26
+ let r = flag.replace('no-report-levels-', '')
22
27
  delete reportLevels[reportLevels.indexOf(r)]
23
28
  }
24
29
  // remove any fails levels
25
- if( arg.includes('--no-fail-levels-') ){
26
- let f = arg.replace('--no-fail-levels-', '')
30
+ if( flag.includes('no-fail-levels-') ){
31
+ let f = flag.replace('no-fail-levels-', '')
27
32
  delete failLevels[failLevels.indexOf(f)]
28
33
  }
29
34
  })
30
35
 
36
+
31
37
  export default {
32
38
  ruleArchive: "latest",
33
39
  policies: [
34
40
  'WCAG_2_1'
35
41
  ],
36
- failLevels: failLevels.filter(e=>e),
37
- reportLevels: reportLevels.filter(e=>e),
42
+ failLevels: failLevels.filter( Boolean ),
43
+ reportLevels: reportLevels.filter( Boolean ),
38
44
  outputFilename: 'reports',
39
- outputFolder: "/audits/a11y",
45
+ outputFolder: '/audits/a11y',
40
46
  outputFormat: [
41
- 'html', 'json'
47
+ 'html'
42
48
  ],
43
49
  outputFilenameTimestamp: false
44
50
  }
package/index.js CHANGED
@@ -4,63 +4,46 @@
4
4
  * External dependencies
5
5
  */
6
6
  import spawn from 'cross-spawn';
7
- import { getAllFilesSync } from 'get-all-files'
8
7
  import EntryDependency from "webpack/lib/dependencies/EntryDependency.js";
9
8
  import path from 'path';
10
9
  import { isUrl, isValidUrl } from 'check-valid-url';
11
10
  import fs from 'fs';
12
11
  import deepmerge from 'deepmerge';
13
12
  import chalk from 'chalk';
14
- import { fileURLToPath, URL } from 'url';
13
+ import { fileURLToPath, URL, pathToFileURL } from 'url';
14
+ import { createRequire } from 'module';
15
+
16
+ const require = createRequire(import.meta.url);
15
17
 
16
18
  /**
17
19
  * Internal dependencies
18
20
  */
19
- import { landingPage, reporter } from './reporter.js';
21
+ import { landingPage } from './reporter.js';
20
22
 
21
23
  // default configuration
22
24
  import {default as DefaultConfig} from './aceconfig.js';
23
25
 
24
- const boldWhite = chalk.bold.white;
25
- const boldGreen = chalk.bold.green;
26
- const boldBlue = chalk.bold.hex('#03a7fc');
27
- const currentPath = path.dirname(fileURLToPath(import.meta.url));
28
-
29
26
  const pluginName = 'CAWebA11yWebpackPlugin';
30
27
 
31
28
  // IBM Accessibility Checker Plugin
32
- class A11yPlugin {
29
+ class CAWebA11yWebpackPlugin {
33
30
  config = {}
34
31
 
35
32
  constructor(opts = {}) {
36
- // the default publicPath is always the outputFolder
37
- DefaultConfig.publicPath = DefaultConfig.outputFolder;
38
-
39
- // the default output folder is always relative to the current working directory.
40
- DefaultConfig.outputFolder = path.join( process.cwd(), DefaultConfig.outputFolder );
41
-
42
- // if opts.outputFolder is defined
43
- if( opts.outputFolder && ! path.isAbsolute(opts.outputFolder) ){
44
- opts.publicPath = opts.outputFolder;
45
-
46
- // we join the current working directory with the opts.outputFolder
47
- opts.outputFolder = path.join( process.cwd(), opts.outputFolder );
48
- }
49
-
50
33
  this.config = deepmerge(DefaultConfig, opts);
51
34
  }
52
35
 
53
36
 
54
37
  apply(compiler) {
55
38
  const staticDir = {
56
- directory: this.config.outputFolder,
57
- publicPath: encodeURI(this.config.publicPath).replace(':', ''),
39
+ directory: path.join( process.cwd(), this.config.outputFolder ),
40
+ publicPath: encodeURI( this.config.outputFolder ).replace(':', ''),
58
41
  watch: true
59
42
  }
60
43
 
61
44
  let { devServer } = compiler.options;
62
45
  let auditUrl = `${devServer.server}://${devServer.host}:${devServer.port}`;
63
- let nodeModulePath = encodeURI(this.config.publicPath).replace(':', '') + '/node_modules';
46
+ let nodeModulePath = encodeURI(staticDir.publicPath) + '/node_modules/';
64
47
  let pathRewrite = {};
65
48
  pathRewrite[`^${nodeModulePath}`] = '';
66
49
 
@@ -77,7 +60,7 @@ class A11yPlugin {
77
60
  devServer.proxy = [].concat(devServer.proxy, proxy );
78
61
  }
79
62
 
80
- // add our static directory to the devServer
63
+ // // add our static directory to the devServer
81
64
  if( Array.isArray(devServer.static) ){
82
65
  devServer.static.push(staticDir)
83
66
  }else{
@@ -87,61 +70,61 @@ class A11yPlugin {
87
70
  // if dev server allows for multiple pages to be opened
88
71
  // add outputFilename.html to open property.
89
72
  if( Array.isArray(devServer.open) ){
90
- devServer.open.push(`${staticDir.publicPath}/${this.config.outputFilename}.html`)
73
+ devServer.open.push(`${auditUrl}${this.config.outputFolder}/${this.config.outputFilename}.html`)
91
74
  }else if( 'object' === typeof devServer.open && Array.isArray(devServer.open.target) ){
92
- devServer.open.target.push(`${staticDir.publicPath}/${this.config.outputFilename}.html`)
75
+ devServer.open.target.push(`${auditUrl}${this.config.outputFolder}/${this.config.outputFilename}.html`)
93
76
  }
94
77
 
95
- // we always make sure the output folder exists
96
- fs.mkdirSync( staticDir.directory, { recursive: true } );
78
+ // we always make sure the output folder is cleared
79
+ fs.rmSync( staticDir.directory, { recursive: true, force: true } );
97
80
 
98
81
  // Hot Module Replacement
99
- if( compiler?.options?.devServer?.hot ){
100
- // we create a blank file for the hot update to compile on our page.
101
- // this is required for the hot-update to work.
102
- fs.writeFileSync(
103
- path.join(staticDir.directory, `a11y.update.js`),
104
- `` // required for hot-update to compile on our page, blank script for now
105
- );
106
-
107
- // we add the entry to the dependency factory during compilation
108
- compiler.hooks.compilation.tap(
109
- pluginName,
110
- (compilation, { normalModuleFactory }) => {
111
- compilation.dependencyFactories.set(
112
- EntryDependency,
113
- normalModuleFactory
114
- );
115
-
116
- }
117
- );
118
-
119
- // we add the entry before the compilation ends
120
- compiler.hooks.make.tapAsync(
121
- pluginName,
122
- (compilation, callback) => {
123
- const { entry, options, context } = {
124
- entry: path.join( staticDir.directory, 'a11y.update.js'),
125
- options: {
126
- name: 'a11y.update'
127
- },
128
- context: 'a11y'
129
- };
130
-
131
- const dep = new EntryDependency(entry);
132
- dep.loc = {
133
- name: options.name
134
- };
82
+ // if( compiler?.options?.devServer?.hot ){
83
+ // // we create a blank file for the hot update to compile on our page.
84
+ // // this is required for the hot-update to work.
85
+ // fs.writeFileSync(
86
+ // path.join(staticDir.directory, `a11y.update.js`),
87
+ // `` // required for hot-update to compile on our page, blank script for now
88
+ // );
89
+
90
+ // // we add the entry to the dependency factory during compilation
91
+ // compiler.hooks.compilation.tap(
92
+ // pluginName,
93
+ // (compilation, { normalModuleFactory }) => {
94
+ // compilation.dependencyFactories.set(
95
+ // EntryDependency,
96
+ // normalModuleFactory
97
+ // );
98
+
99
+ // }
100
+ // );
101
+
102
+ // // we add the entry before the compilation ends
103
+ // compiler.hooks.make.tapAsync(
104
+ // pluginName,
105
+ // (compilation, callback) => {
106
+ // const { entry, options, context } = {
107
+ // entry: path.join( staticDir.directory, 'a11y.update.js'),
108
+ // options: {
109
+ // name: 'a11y.update'
110
+ // },
111
+ // context: 'a11y'
112
+ // };
113
+
114
+ // const dep = new EntryDependency(entry);
115
+ // dep.loc = {
116
+ // name: options.name
117
+ // };
135
118
 
136
- compilation.addEntry(
137
- context,
138
- dep,
139
- options,
140
- err => {
141
- callback(err);
142
- });
143
- });
144
- }
119
+ // compilation.addEntry(
120
+ // context,
121
+ // dep,
122
+ // options,
123
+ // err => {
124
+ // callback(err);
125
+ // });
126
+ // });
127
+ // }
145
128
 
146
129
  compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
147
130
  // We can audit the html files now that the compilation is done.
@@ -150,29 +133,23 @@ class A11yPlugin {
150
133
  pluginName,
151
134
  (stats, callback) => {
152
135
 
153
- console.log(`<i> ${boldGreen('[webpack-dev-middleware] Running IBM Accessibility scan...')}`);
154
-
155
- // we check the compilers output.publicPath
156
- // if it is not set, we scan the output.path as the publicPath
157
- let result = this.a11yCheck(
158
- ! compiler.options.output.publicPath ||
159
- 'auto' === compiler.options.output.publicPath ?
160
- compiler.options.output.path :
161
- compiler.options.output.publicPath,
136
+ console.log('<i> \x1b[32m[webpack-dev-middleware] Running IBM Accessibility scan...\x1b[0m');
137
+ /**
138
+ * We run the accessibility checker
139
+ *
140
+ * we scan the output.publicPath if its set and not set to 'auto'
141
+ * otherwise we scan the output.path
142
+ */
143
+ let scanDir = compiler.options.output.publicPath && 'auto' !== compiler.options.output.publicPath ?
144
+ compiler.options.output.publicPath :
145
+ compiler.options.output.path;
146
+
147
+ this.a11yCheck(
148
+ scanDir,
162
149
  this.config
163
150
  );
164
151
 
165
- // if( result ){
166
- // // we have to inject the a11y.update.js file into the head in order for the webpack-dev-server scripts to load.
167
- // // let pageContent = fs.readFileSync(path.join(staticDir.directory, `${this.config.outputFilename}.html`))
168
-
169
- // // fs.writeFileSync(
170
- // // path.join(staticDir.directory, `${this.config.outputFilename}.html`),
171
- // // pageContent.toString().replace('</head>', `<script src="./a11y.update.js"></script>\n</head>`)
172
- // // )
173
- // }
174
-
175
- console.log(`<i> ${boldGreen('[webpack-dev-middleware] IBM Accessibilty Report can be viewed at')} ${ boldBlue(new URL(`${auditUrl}${staticDir.publicPath}/${this.config.outputFilename}.html`).toString()) }`);
152
+ console.log(`<i> \x1b[32m[webpack-dev-middleware] IBM Accessibilty Report can be viewed at \x1b[34m ${new URL(`${auditUrl}${staticDir.publicPath}/${this.config.outputFilename}.html`).toString()}\x1b[0m`);
176
153
 
177
154
  });
178
155
  })
@@ -182,6 +159,7 @@ class A11yPlugin {
182
159
  /**
183
160
  * Run accessibility checks
184
161
  *
162
+ * @param {string} target The target URL or directory to scan.
185
163
  * @param {Object} options
186
164
  * @param {boolean} options.debug True if debug mode is enabled.
187
165
  * @param {boolean} options.ruleArchive Specify the rule archive.
@@ -194,7 +172,7 @@ class A11yPlugin {
194
172
  * @param {boolean} options.outputFolder Where the scan results should be saved.
195
173
  * @param {boolean} options.outputFilenameTimestamp Should the timestamp be included in the filename of the reports?
196
174
  */
197
- a11yCheck(url, {
175
+ a11yCheck( target, {
198
176
  debug,
199
177
  ruleArchive,
200
178
  policies,
@@ -207,39 +185,38 @@ class A11yPlugin {
207
185
  outputFilenameTimestamp
208
186
  }){
209
187
 
210
-
211
- let htmlOutput = outputFormat && outputFormat.includes('html');
212
-
213
- // we remove the html output since we generate our own html based on the json output.
214
- // outputFormat = outputFormat.filter(o => 'html' !== o );
188
+ // outputFolder should not be absolute
189
+ outputFolder = path.isAbsolute(outputFolder) ? path.join( process.cwd(), outputFolder ) : outputFolder;
215
190
 
191
+ // accessibility-checker CLI arguments
216
192
  let acheckerArgs = [
217
193
  '--ruleArchive',
218
194
  ruleArchive,
219
195
  '--policies',
220
- Array.isArray(policies) ? policies.filter(e => e).join(',') : policies,
196
+ Array.isArray(policies) ? policies.filter(Boolean).join(',') : policies,
221
197
  '--failLevels',
222
- Array.isArray(failLevels) ? failLevels.filter(e => e).join(',') : failLevels,
198
+ Array.isArray(failLevels) ? failLevels.filter(Boolean).join(',') : failLevels,
223
199
  '--reportLevels',
224
- Array.isArray(reportLevels) ? reportLevels.filter(e => e).join(',') : reportLevels,
200
+ Array.isArray(reportLevels) ? reportLevels.filter(Boolean).join(',') : reportLevels,
225
201
  '--outputFolder',
226
202
  outputFolder,
227
203
  '--outputFormat',
228
204
  outputFormat,
229
205
  '---outputFilenameTimestamp',
230
206
  outputFilenameTimestamp,
231
- url
207
+ target
232
208
  ];
233
209
 
234
- let isValid = fs.existsSync( url ) || 'localhost' === new URL(url).hostname || isUrl( url );
235
- let isDirectory = fs.existsSync( url ) && fs.statSync(url).isDirectory();
210
+ // checks for the target validity
211
+ let isValid = fs.existsSync( target ) || 'localhost' === new URL(target).hostname || isUrl( target );
212
+ // let isDirectory = fs.existsSync( target ) && fs.statSync(target).isDirectory();
236
213
 
214
+ // if the target is valid, we run the accessibility checker
237
215
  if( isValid ){
238
216
 
239
- let outputDir = path.resolve('.', outputFolder );
240
-
217
+ // we run the accessibility-checker CLI
241
218
  let {stderr, stdout} = spawn.sync(
242
- path.resolve(currentPath, '..', '..', 'accessibility-checker', 'bin', 'achecker.js'),
219
+ path.resolve(require.resolve('accessibility-checker'), '..', '..' , 'bin', 'achecker.js'),
243
220
  acheckerArgs,
244
221
  {
245
222
  stdio: 'pipe'
@@ -255,53 +232,39 @@ class A11yPlugin {
255
232
 
256
233
  // we iterate thru the output directory in reverse order,
257
234
  // this way we can remove any empty directories at the end, since files are cycled first.
258
- fs.readdirSync(outputDir, {recursive: true}).reverse().forEach( file => {
235
+ fs.readdirSync(outputFolder, {recursive: true}).reverse().forEach( file => {
236
+ let filePath = path.join(outputFolder, file);
237
+
259
238
  // process each json file file
260
- if( fs.statSync(path.join(outputDir, file)).isFile() ){
239
+ if( fs.statSync(filePath).isFile() ){
261
240
  if ( file.startsWith('summary_') ){
262
241
  // remove the summary files
263
- fs.rmSync( path.join(outputDir, file) )
242
+ fs.rmSync( filePath )
264
243
  return;
265
244
  }
266
-
267
- let oldName = file;
268
- let newName = file;
269
-
270
- // remove the original output directory from the file name
271
- newName = isDirectory ? file.replace(url.replace(':', '_'), '') : file;
272
245
 
273
- newName = newName.replace(/^\\/, '');
246
+ // if the fle ends with .json or .html
247
+ if( file.endsWith('.json') || file.endsWith('.html') ){
248
+ let newName = file.replace( target.replace(':', '_') + '\\', '').replace('.html.html', '.html');
274
249
 
275
- // for some reason .html files have an extra .html in the name
276
- newName = newName.endsWith('.html.html') ? newName.replace('.html.html', '.html') : newName;
277
-
278
- // if the new name is not the same as the old name.
279
- if( newName !== file ){
280
- // rename the file
250
+ // we rename the json/html file to remove the target from the filename
281
251
  fs.renameSync(
282
- path.join(outputDir, oldName ),
283
- path.join(outputDir, newName) );
284
- }
285
-
286
- // we add the file to the audit index
287
- if( ! auditIndex.includes(newName) && newName.endsWith('.html') ){
288
- auditIndex.push( newName );
289
- }
290
-
291
- // if we are generating html output, we need to generate the html file.
292
- if( htmlOutput ){
293
- // let jsonObj = JSON.parse(fs.readFileSync(path.join(outputDir, newName)));
294
-
295
- // we generate the html file
296
- // reporter( jsonObj, { outputFolder, outputFilename: newName.replace(/\.json$/, '') } );
252
+ path.join(outputFolder, file),
253
+ path.join(outputFolder, newName)
254
+ );
255
+
256
+ // we add the file to the audit index
257
+ if( file.endsWith('.html') && ! auditIndex.includes(newName) ){
258
+ auditIndex.push( newName );
259
+ }
297
260
  }
298
261
 
299
- }else if ( fs.statSync(path.join(outputDir, file)).isDirectory() ){
262
+ }else if ( fs.statSync(path.join(outputFolder, file)).isDirectory() ){
300
263
  // process each directory
301
264
  // delete any empty directories.
302
- if( 0 === fs.readdirSync(path.join(outputDir, file)).length ){
265
+ if( 0 === fs.readdirSync(path.join(outputFolder, file)).length ){
303
266
  // remove the directory
304
- fs.rmSync(path.join(outputDir, file), {recursive: true});
267
+ fs.rmSync(path.join(outputFolder, file), {recursive: true});
305
268
  }
306
269
  }
307
270
 
@@ -312,14 +275,13 @@ class A11yPlugin {
312
275
 
313
276
  // we generate the .
314
277
  if( 'a11y' === process.argv[2] ){
315
- // console.log( reportedFile )
278
+ console.log( 'Accessibility Report Generated File: ', path.join(outputFolder, `${outputFilename}.html`) )
316
279
  }else{
317
280
  return true;
318
- // return reportedFile;
319
281
  }
320
282
  }
321
283
  }else{
322
- console.log( `${url} is not a valid url.` )
284
+ console.log( `${target} is not a valid url or directory.` )
323
285
  }
324
286
 
325
287
  } // end of a11yCheck
@@ -327,4 +289,4 @@ class A11yPlugin {
327
289
  } // end of class
328
290
 
329
291
 
330
- export default A11yPlugin;
292
+ export default CAWebA11yWebpackPlugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caweb/a11y-webpack-plugin",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "CAWebPublishing Webpack Plugin to run Accessibility Scans",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -11,6 +11,8 @@
11
11
  "README.md"
12
12
  ],
13
13
  "scripts": {
14
+ "webpack": "webpack",
15
+ "serve": "webpack serve --config ./node_modules/@caweb/webpack/webpack.config.js ./tests/webpack.tests.js --merge",
14
16
  "test": "echo \"Error: run tests from root\" && exit 0"
15
17
  },
16
18
  "author": "CAWebPublishing",
@@ -33,14 +35,12 @@
33
35
  "webpack"
34
36
  ],
35
37
  "dependencies": {
36
- "accessibility-checker": "^4.0.7",
38
+ "accessibility-checker": "^4.0.10",
37
39
  "check-valid-url": "^0.1.0"
38
40
  },
39
41
  "devDependencies": {
40
- "webpack": "^5.101.0",
42
+ "@caweb/html-webpack-plugin": "^2.0.2",
43
+ "webpack": "^5.104.1",
41
44
  "webpack-cli": "^6.0.1"
42
- },
43
- "peerDependencies": {
44
- "@caweb/template": "^1.0.2"
45
45
  }
46
46
  }
package/reporter.js CHANGED
@@ -173,7 +173,7 @@ function initHandleBars(){
173
173
  // Register custom helpers.
174
174
  HandleBars.registerHelper('endsWith', endsWith )
175
175
 
176
- return HandleBars.compile(fs.readFileSync(path.resolve(templateDir, 'patterns', 'index.html')).toString() )
176
+ return HandleBars.compile(fs.readFileSync(path.resolve(templateDir, 'patterns', 'default.html')).toString() )
177
177
 
178
178
  }
179
179